通过结构体的方式来定义和使用寄存器地址

2019-11-29 09:14发布

在上一篇文章《IMX6UL裸机实现C语言LED实验》中,我们通过给每个寄存器宏定义一个地址的方式来使用寄存器地址,但是这种给每个寄存器定义一个值的方式太繁琐了,所以今天我们就来把他改成结构体的形式。

1、编写外设结构体

先将同属于一个外设的所有寄存器编写到一个结构体里面,如 IO 复用寄存器组的结构体如下:


/*

* IOMUX 寄存器组

*/

1 typedef struct 

2 {

3 volatile unsigned int BOOT_MODE0;

4 volatile unsigned int BOOT_MODE1;

5 volatile unsigned int SNVS_TAMPER0;

6 volatile unsigned int SNVS_TAMPER1;

………

107 volatile unsigned int CSI_DATA00;

108 volatile unsigned int CSI_DATA01;

109 volatile unsigned int CSI_DATA02;

110 volatile unsigned int CSI_DATA03;

111 volatile unsigned int CSI_DATA04;

112 volatile unsigned int CSI_DATA05;

113 volatile unsigned int CSI_DATA06;

114 volatile unsigned int CSI_DATA07; /* 为了缩短代码,其余 IO 复用寄存器省略 */ 

115}IOMUX_SW_MUX_Tpye;




上述结构体 IOMUX_SW_MUX_Type 就是 IO 复用寄存器组,成员变量是每个 IO 对应的复用寄存器,每个寄存器的地址是 32 位,每个成员都使用“volatile”进行了修饰,目的是防止编译器优化。

2、定义 IO 复用寄存器组的基地址

根据结构体 IOMUX_SW_MUX_Type 的定义,其第一个成员变量为 BOOT_MODE0,也就是 BOOT_MODE0 这个 IO 的 IO 复用寄存器,查找 I.MX6U 的参考手册可以得知其地址为0X020E0014,所以 IO 复用寄存器组的基地址就是 0X020E0014,定义如下:

#define IOMUX_SW_MUX_BASE (0X020E0014)

3、定义访问指针访问指针定义如下:

#define IOMUX_SW_MUX ((IOMUX_SW_MUX_Type *)IOMUX_SW_MUX_BASE)

通过上面三步我们就可以通过“IOMUX_SW_MUX->GPIO1_IO03”来访问 GPIO1_IO03 的IO 复用寄存器了。同样的,其他的外设寄存器都可以通过这三步来定义。

新建文件 imx6ul.h 用来存放外设寄存器定义,在 imx6ul.h 中输入如下代码:

1 #define CCM_BASE (0X020C4000)

2 #define CCM_ANALOG_BASE (0X020C8000)

3 #define IOMUX_SW_MUX_BASE (0X020E0014)

4 #define IOMUX_SW_PAD_BASE (0X020E0204)

5 #define GPIO1_BASE (0x0209C000)

6 #define GPIO2_BASE (0x020A0000)

7 #define GPIO3_BASE (0x020A4000)

8 #define GPIO4_BASE (0x020A8000)

9 #define GPIO5_BASE (0x020AC000)

10

11 /*

12 * CCM 寄存器结构体定义,分为 CCM 和 CCM_ANALOG 

13 */

14 typedef struct 

15 {

16 volatile unsigned int CCR;

17 volatile unsigned int CCDR;

18 volatile unsigned int CSR;

……

46 volatile unsigned int CCGR6;

47 volatile unsigned int RESERVED_3[1];

48 volatile unsigned int CMEOR;

49 } CCM_Type; 

50

51 typedef struct 

52 {

53 volatile unsigned int PLL_ARM;

54 volatile unsigned int PLL_ARM_SET;

55 volatile unsigned int PLL_ARM_CLR;

56 volatile unsigned int PLL_ARM_TOG;

……

110 volatile unsigned int MISC2;

111 volatile unsigned int MISC2_SET;

112 volatile unsigned int MISC2_CLR;

113 volatile unsigned int MISC2_TOG;

114 } CCM_ANALOG_Type;

115

116 /*

117 * IOMUX 寄存器组

118 */

119 typedef struct

120 {

121 volatile unsigned int BOOT_MODE0;

122 volatile unsigned int BOOT_MODE1;

123 volatile unsigned int SNVS_TAMPER0;

……

241 volatile unsigned int CSI_DATA04;

242 volatile unsigned int CSI_DATA05;

243 volatile unsigned int CSI_DATA06;

244 volatile unsigned int CSI_DATA07;

245 }IOMUX_SW_MUX_Type;

246

247 typedef struct

248 {

249 volatile unsigned int DRAM_ADDR00;

250 volatile unsigned int DRAM_ADDR01;

……

419 volatile unsigned int GRP_DDRPKE;

420 volatile unsigned int GRP_DDRMODE;

421 volatile unsigned int GRP_DDR_TYPE;

422 }IOMUX_SW_PAD_Type;

423

424 /*

425 * GPIO 寄存器结构体

426 */

427 typedef struct

428 {

429 volatile unsigned int DR;

430 volatile unsigned int GDIR;

431 volatile unsigned int PSR;

432 volatile unsigned int ICR1;

433 volatile unsigned int ICR2;

434 volatile unsigned int IMR;

435 volatile unsigned int ISR;

436 volatile unsigned int EDGE_SEL;

437 }GPIO_Type;

438

439

440 /*

441 * 外设指针

442 */

443 #define CCM ((CCM_Type *)CCM_BASE)

444 #define CCM_ANALOG ((CCM_ANALOG_Type *)CCM_ANALOG_BASE)

445 #define IOMUX_SW_MUX ((IOMUX_SW_MUX_Type *)IOMUX_SW_MUX_BASE)

446 #define IOMUX_SW_PAD ((IOMUX_SW_PAD_Type *)IOMUX_SW_PAD_BASE)

447 #define GPIO1 ((GPIO_Type *)GPIO1_BASE)

448 #define GPIO2 ((GPIO_Type *)GPIO2_BASE)

449 #define GPIO3 ((GPIO_Type *)GPIO3_BASE)

450 #define GPIO4 ((GPIO_Type *)GPIO4_BASE)

451 #define GPIO5 ((GPIO_Type *)GPIO5_BASE)

在编写寄存器组结构体的时候注意寄存器的地址是否连续,有些外设的寄存器地址可能不是连续的,会有一些保留地址,因此我们需要在结构体中留出这些保留的寄存器。比如 CCM 的CCGR6 寄存器地址为 0X020C4080,而寄存器 CMEOR 的地址为 0X020C4088。按照地址顺序递增的原理,寄存器 CMEOR 的地址应该是 0X020C4084,但是实际上 CMEOR 的地址是0X020C4088,相当于中间跳过了 0X020C4088-0X020C4080=8 个字节,如果寄存器地址连续的话应该只差 4 个字节(32 位),但是现在差了 8 个字节,所以需要在寄存器 CCGR6 和 CMEOR直接加入一个保留寄存器,这个就是代码中第 47 行 RESERVED_3[1]的来源。如果不添加保留为来占位的话就会导致寄存器地址错位!

main.c 文件中修改成如下所示内容:

1 #include "imx6ul.h"

2

3 /*

4 * @description : 使能 I.MX6U 所有外设时钟

5 * @param : 无

6 * @return : 无

7 */

8 void clk_enable(void)
 
9 {

10 CCM->CCGR0 = 0XFFFFFFFF;

11 CCM->CCGR1 = 0XFFFFFFFF;

12 CCM->CCGR2 = 0XFFFFFFFF;

13 CCM->CCGR3 = 0XFFFFFFFF;

14 CCM->CCGR4 = 0XFFFFFFFF;

15 CCM->CCGR5 = 0XFFFFFFFF;

16 CCM->CCGR6 = 0XFFFFFFFF; 
 
17 }

18

19 /*

20 * @description : 初始化 LED 对应的 GPIO

21 * @param : 无

22 * @return : 无

23 */

24 void led_init(void) 
 
25 {

26 /* 1、初始化 IO 复用 */

27 IOMUX_SW_MUX->GPIO1_IO03 = 0X5; /* 复用为 GPIO1_IO03 */ 
 
28

29

30 /* 2、配置 GPIO1_IO03 的 IO 属性

31 *bit 16:0 HYS 关闭

32 *bit [15:14]: 00 默认下拉

33 *bit [13]: 0 kepper 功能

34 *bit [12]: 1 pull/keeper 使能

35 *bit [11]: 0 关闭开路输出

36 *bit [7:6]: 10 速度 100Mhz

37 *bit [5:3]: 110 R0/6 驱动能力

38 *bit [0]: 0 低转换率

39 */

40 IOMUX_SW_PAD->GPIO1_IO03 = 0X10B0;
 
 41

42

43 /* 3、初始化 GPIO */

44 GPIO1->GDIR = 0X0000008; /* GPIO1_IO03 设置为输出 */

45

46 /* 4、设置 GPIO1_IO03 输出低电平,打开 LED0 */ 
 
47 GPIO1->DR &= ~(1 << 3);

48

49 }

50

51 /*

52 * @description : 打开 LED 灯

53 * @param : 无

54 * @return : 无

55 */

56 void led_on(void) 57 {

58 /* 将 GPIO1_DR 的 bit3 清零 */

59 GPIO1->DR &= ~(1<<3);

60 }

61

62 /*

63 * @description : 关闭 LED 灯

64 * @param : 无

65 * @return : 无

66 */

67 void led_off(void) 
 
68 {

69 /* 将 GPIO1_DR 的 bit3 置 1 */

70 GPIO1->DR |= (1<<3);

71 }

72

73 /*

74 * @description : 短时间延时函数

75 * @param - n : 要延时循环次数(空操作循环次数,模式延时)

76 * @return : 无

77 */

78 void delay_short(volatile unsigned int n)

79 {

80 while(n--){}

81 }

82

83 /*

84 * @description : 延时函数,在 396Mhz 的主频下

85 * 延时时间大约为 1ms

86 * @param - n : 要延时的 ms 数

87 * @return : 无

88 */

89 void delay(volatile unsigned int n) 

90 {

91 while(n--)

92 {

93 delay_short(0x7ff); 
 
94 }

95 }

96

97 /*

98 * @description : mian 函数

99 * @param : 无

100 * @return : 无

101 */

102 int main(void)

103 {

104 clk_enable(); /* 使能所有的时钟 */

105 led_init(); /* 初始化 led */

106

107 while(1) /* 死循环 */

108 {

109 led_off(); /* 关闭 LED */
 
110 delay(500); /* 延时 500ms */ 
 
111

112 led_on(); /* 打开 LED */ 
 
113 delay(500); /* 延时 500ms */

114 }

115

116 return 0;

117 }

main.c 中 7 个函数的函数体写法变了,寄存器的访问采用 imx6ul.h 中定义的外设指针。比如第 27 行设置GPIO1_IO03 的复用功能就可以通过“IOMUX_SW_MUX->GPIO1_IO03”来给寄存 SW_MUX_CTL_PAD_GPIO1_IO03赋值。

编译链接下载的代码和验证过程与上一篇文章《IMX6UL裸机实现C语言LED实验》一样,此处不再重复,请自行查阅。