I.MX6U 裸机开发6. 结构体编程风格使用寄存器
- 一、STM32 库函数的开发风格
- 二、代码编写
- 1. 清除 bss 段
- 2. 完整启动文件
- 3. 定义寄存器结构体
- 4. main.c
- 使能时钟:
- 初始化led,
- 开关led
- 完整的main.c
- 5. Makefile
- 6. 链接文件
一、STM32 库函数的开发风格
STM32库函数通过将寄存器定义为结构体的方式,提供了一种高效且易于维护的编程风格。
在STM32库中,给每个外设会对应定义一个结构体,结构体的每个成员变量对应一个寄存器。例如,GPIO外设的寄存器定义如下:
typedef struct
{
__IO uint32_t MODER; /*!< GPIO 端口模式寄存器, 地址偏移: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO 端口输出类型寄存器, 地址偏移: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO 端口输出速度寄存器, 地址偏移: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO 端口上拉/下拉寄存器, 地址偏移: 0x0C */
__IO uint32_t IDR; /*!< GPIO 端口输入数据寄存器, 地址偏移: 0x10 */
__IO uint32_t ODR; /*!< GPIO 端口输出数据寄存器, 地址偏移: 0x14 */
__IO uint32_t BSRR; /*!< GPIO 端口位设置/重置寄存器, 地址偏移: 0x18 */
__IO uint32_t LCKR; /*!< GPIO 端口配置锁定寄存器, 地址偏移: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO 端口复用功能寄存器, 地址偏移: 0x20-0x24 */
} GPIO_TypeDef;
然后,通过定义指向这些结构体的指针,可以方便地访问和操作寄存器。例如:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
这样做有以下好处:
- 代码可读性高:使用结构体定义寄存器后,代码更加直观。例如,设置GPIOA的模式寄存器可以写成
GPIOA->MODER = 0x00000000;
,一目了然。 - 易于维护:当寄存器地址或结构发生变化时,只需修改结构体定义或基地址定义,其他代码无需改动,极大地提高了代码的可维护性。
- 减少错误:通过结构体成员访问寄存器,可以避免直接使用魔法数(硬编码的地址),减少了出错的可能性。
- 便于调试:结构体的使用使得调试工具能够更好地解析和显示寄存器的内容,方便开发人员进行调试。
- 代码复用性高:结构体定义的寄存器可以在不同的项目中复用,减少了重复代码的编写,提高了开发效率。
二、代码编写
1. 清除 bss 段
BSS 段用于存储程序中未初始化的全局变量和静态变量。
下面定义两个全局符号 _bss_start 和 _bss_end,并将它们分别与 __bss_start 和 __bss_end 关联起来。
这些符号通常用于标识 BSS 段的起始和结束位置。
.global _bss_start
_bs_start:
.word __bss_start
.global _bss_end
_bss_end:
.word __bss_end
下面是清除的过程 :
/** 清除 bss 段 */
LDR R0, =__bss_start /** 获取 bss 段的起始地址 */
LDR R1, =__bss_end /** 获取 bss 段的结束地址 */
MOV R2, #0 /** 设置清零的值 */
B bss_loop /** 跳转到 bss_loop */
bss_loop:
STMIA R0!, {R2} /** 将 R2 的值存储到 R0 指向的地址,然后 R0 += 4 */
CMP R0, R1 /** 比较 R0 和 R1 的值, 即有没有到结尾 */
BLE bss_loop /** 如果 R0 <= R1,跳转到 bss_loop */
bss_end:
2. 完整启动文件
.global _start
.global _bss_start
_bs_start:
.word __bss_start
.global _bss_end
_bss_end:
.word __bss_end
_start:
/** 设置处理进入 SVC 模式 */
MRS R0, CPSR /** 读取当前的 CPSR */
BIC R0, R0, #0x1F /** 清除 CPSR 的低 5 位 */
ORR R0, R0, #0x13 /** 设置 CPSR 为 SVC 模式 */
MSR CPSR, R0 /** 设置 CPSR */
/** 清除 bss 段 */
LDR R0, =__bss_start /** 获取 bss 段的起始地址 */
LDR R1, =__bss_end /** 获取 bss 段的结束地址 */
MOV R2, #0 /** 设置清零的值 */
B bss_loop /** 跳转到 bss_loop */
bss_loop:
STMIA R0!, {R2} /** 将 R2 的值存储到 R0 指向的地址,然后 R0 += 4 */
CMP R0, R1 /** 比较 R0 和 R1 的值, 即有没有到结尾 */
BLE bss_loop /** 如果 R0 <= R1,跳转到 bss_loop */
bss_end:
/** 设置堆栈 */
LDR SP, =0x80200000
B main /* 跳转到C语言main函数*/
3. 定义寄存器结构体
通过 《IMX6ULL参考手册》 P 658,可以看到相关结构体的地址:
如下进行定义:
/* GPIO寄存器结构体定义 */
typedef struct
{
__IO uint32_t CCR;
__IO uint32_t CCDR;
__IO uint32_t CSR;
__IO uint32_t CCSR;
__IO uint32_t CACRR;
__IO uint32_t CBCDR;
__IO uint32_t CBCMR;
__IO uint32_t CSCMR1;
__IO uint32_t CSCMR2;
__IO uint32_t CSCDR1;
__IO uint32_t CS1CDR;
__IO uint32_t CS2CDR;
__IO uint32_t CDCDR;
__IO uint32_t CHSCCDR;
__IO uint32_t CSCDR2;
__IO uint32_t CSCDR3;
__IO uint32_t reserved1[2];
__IO uint32_t CDHIPR;
__IO uint32_t reserved2[2];
__IO uint32_t CLPCR;
__IO uint32_t CISR;
__IO uint32_t CIMR;
__IO uint32_t CCOSR;
__IO uint32_t CGPR;
__IO uint32_t CCGR0;
__IO uint32_t CCGR1;
__IO uint32_t CCGR2;
__IO uint32_t CCGR3;
__IO uint32_t CCGR4;
__IO uint32_t CCGR5;
__IO uint32_t CCGR6;
__IO uint32_t reserved3[1];
__IO uint32_t CMEOR;
} CCM_Type;
类似的对IOMUX_SW_MUX 、CCM_ANALOG、 IOMUX_SW_PAD进行定义,完整文件参考正点原子教程代码。
4. main.c
对前一章的内容进行修改
使能时钟:
int enable_clock(void){
CCM_CCGR1 = 0xffffffff;
CCM_CCGR2 = 0xffffffff;
CCM_CCGR3 = 0xffffffff;
CCM_CCGR4 = 0xffffffff;
CCM_CCGR5 = 0xffffffff;
CCM_CCGR6 = 0xffffffff;
return 0;
}
改为:
int enable_clock(void){
CCM->CCGR1 = 0xffffffff;
CCM->CCGR2 = 0xffffffff;
CCM->CCGR3 = 0xffffffff;
CCM->CCGR4 = 0xffffffff;
CCM->CCGR5 = 0xffffffff;
CCM->CCGR6 = 0xffffffff;
return 0;
}
初始化led,
/**
* 初始化LED
* @return
*/
void init_led(void){
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x5; // 设置复用为GPIO1_IO03
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0; // 设置电气属性
GPIO1_GDIR |= (1<<3); // 设置为输出
}
改为:
/**
* 初始化LED
* @return
*/
void init_led(void){
IOMUX_SW_MUX->GPIO1_IO03 = 0x5; // 设置复用为GPIO1_IO03
IOMUX_SW_PAD->GPIO1_IO03 = 0x10B0; // 设置电气属性
GPIO1->GDIR |= (1 << 3); // 设置为输出
}
开关led
void led_on(void){
GPIO1_DR &= ~(1<<3);
}
void led_off(void){
GPIO1_DR |= (1<<3);
}
改为:
void led_on(void){
GPIO1->DR &= ~(1 << 3);
}
void led_off(void){
GPIO1->DR |= (1 << 3);
}
完整的main.c
#include "imx6u.h"
/**
* 使能时钟
* @return
*/
int enable_clock(void){
CCM->CCGR1 = 0xffffffff;
CCM->CCGR2 = 0xffffffff;
CCM->CCGR3 = 0xffffffff;
CCM->CCGR4 = 0xffffffff;
CCM->CCGR5 = 0xffffffff;
CCM->CCGR6 = 0xffffffff;
return 0;
}
/**
* 初始化LED
* @return
*/
void init_led(void){
IOMUX_SW_MUX->GPIO1_IO03 = 0x5; // 设置复用为GPIO1_IO03
IOMUX_SW_PAD->GPIO1_IO03 = 0x10B0; // 设置电气属性
GPIO1->GDIR |= (1 << 3); // 设置为输出
}
void led_on(void){
GPIO1->DR &= ~(1 << 3);
}
void led_off(void){
GPIO1->DR |= (1 << 3);
}
void delay_short(volatile unsigned int n){
while(n--);
}
void delay(volatile unsigned int n){
while(n--){
delay_short(0x7ff);
}
}
int main(void){
/** 初始化LED **/
enable_clock();
// 初始化LED
init_led();
while(1){
led_on();
delay(500);
led_off();
delay(500);
}
return 0;
}
5. Makefile
首先定义几个符号:
objs := start.o main.o
ld := arm-linux-gnueabihf-ld
cc := arm-linux-gnueabihf-gcc
objcopy := arm-linux-gnueabihf-objcopy
objdump := arm-linux-gnueabihf-objdump
注意和前节区别 , 这里赋值使用了冒号等于。在 Makefile 中,objs := start.o main.o 和 objs = start.o main.o 的区别如下:
- := 是立即展开赋值符号,表示在赋值时立即展开右边的值,并将其赋给左边的变量。也就是说,objs 的值在赋值时就已经确定,不会再改变。
- = 是延迟展开赋值符号,表示在每次使用变量时才展开右边的值。也就是说,objs 的值在每次使用时都会重新计算。
在大多数情况下,这两者的效果是相同的,但在某些复杂的场景下,使用 := 可以避免一些意外的结果
6. 链接文件
与上节一致。
运行编译烧录:
make clean
make
make download
将SD卡安装到开发板,可以看到LED1闪烁,与前一篇文件效果相同。