我是靠谱客的博主 直率百褶裙,这篇文章主要介绍访问外设寄存器的四种方式一、最直接的方式二、定义成常量而非宏方式一 vs 方式二三、支持多实例四、定义成结构体总结,现在分享给大家,希望可以做个参考。

访问外设寄存器是底层驱动编程的基本工作,访问外设寄存器都有哪些方式呢?

https://blog.csdn.net/zoomdy/article/details/80284054
mingdu.zheng at gmail dot com

一、最直接的方式

将外设寄存器地址强制转换成指针,然后对该指针进行操作,这是最基本也是最直接的方式了。

复制代码
1
2
3
4
5
6
7
8
#define GPIO0_OUTPUT_EN (*((volatile uint32_t*)(0x10012000 + 0x008))) #define GPIO0_PORT (*((volatile uint32_t*)(0x10012000 + 0x00C))) #define GPIO0_PUE (*((volatile uint32_t*)(0x10012000 + 0x010))) GPIO0_OUTPUT_EN |= 1UL << 0; GPIO0_PUE |= 1UL << 0; GPIO0_PORT |= 1UL << 0;

二、定义成常量而非宏

常量会生成调试符号,在调试阶段,可以查看其内容,使用常量替代宏可以改善调试体验。

复制代码
1
2
3
4
5
6
7
8
volatile uint32_t * const GPIO0_OUTPUT_EN = (volatile uint32_t*)(0x10012000 + 0x008); volatile uint32_t * const GPIO0_PORT = (volatile uint32_t*)(0x10012000 + 0x00c); volatile uint32_t * const GPIO0_PUE = (volatile uint32_t*)(0x10012000 + 0x0010); *GPIO0_OUTPUT_EN |= 1UL << 0; *GPIO0_PUE |= 1UL << 0; *GPIO0_PORT |= 1UL << 0;

方式一 vs 方式二

同样是-O2优化选项的情况下,生成的汇编代码相同。默认链接参数下,方式二占用更多的data空间,编译器为每个常量分配了存储空间,加入--gc-sections参数后,会释放常量存储空间,因为别的地方没有引用到。常量是会生成调试符号的,所以使用常量的形式更方便调试。

三、支持多实例

单片机里往往有多个相同的外设,除了基地址不同,其它寄存器的定义则完全相同,为了简化代码,这时候就需要对多实例的支持。同一份代码,只需要传入不同的基地址,就能操作不同的外设。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define WRITE_REG(addr, data) (*((volatile uint32_t*)(addr)) = (data)) #define READ_REG(addr) (*((volatile uint32_t*)(addr))) #define GPIO0_BASE 0x10012000 #define GPIO1_BASE 0x10018000 #define GPIO_OUTPUT_EN 0x008 #define GPIO_PORT 0x00C #define GPIO_PUE 0x010 static void gpio_init(uint32_t base) { uint32_t reg; reg = READ_REG(base + GPIO_OUTPUT_EN); reg |= 1UL << 0; WRITE_REG(base + GPIO_OUTPUT_EN, reg); reg = READ_REG(base + GPIO_PUE); reg |= 1UL << 0; WRITE_REG(base + GPIO_PUE, reg); reg = READ_REG(base + GPIO_PORT); reg |= 1UL << 0; WRITE_REG(base + GPIO_PORT, reg); } gpio_init(GPIO0_BASE); gpio_init(GPIO1_BASE);

四、定义成结构体

结构体形式很自然地支持多实例。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct { volatile uint32_t res[2]; volatile uint32_t output_en; volatile uint32_t port; volatile uint32_t pue; }gpio_t; #define GPIO0_BASE 0x10012000 #define GPIO1_BASE 0x10018000 #define GPIO0 ((gpio_t*)(GPIO0_BASE)) #define GPIO1 ((gpio_t*)(GPIO1_BASE)) static void gpio_init(gpio_t* gpio) { gpio->output_en |= 1UL << 0; gpio->pue |= 1UL << 0; gpio->port |= 1UL << 0; } gpio_init(GPIO0); gpio_init(GPIO1);

或者用常量定义代替宏定义

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct { volatile uint32_t res[2]; volatile uint32_t output_en; volatile uint32_t port; volatile uint32_t pue; }gpio_t; #define GPIO0_BASE 0x10012000 #define GPIO1_BASE 0x10018000 gpio_t * const GPIO0 = (gpio_t*)(GPIO0_BASE); gpio_t * const GPIO1 = (gpio_t*)(GPIO1_BASE); static void gpio_init(gpio_t* gpio) { gpio->output_en |= 1UL << 0; gpio->pue |= 1UL << 0; gpio->port |= 1UL << 0; } gpio_init(GPIO0); gpio_init(GPIO1);

总结

推荐定义成结构体形式,如果在乎调试体验,那么将操作指针(上文中的GPIO0和GPIO1)定义成常量。

最后

以上就是直率百褶裙最近收集整理的关于访问外设寄存器的四种方式一、最直接的方式二、定义成常量而非宏方式一 vs 方式二三、支持多实例四、定义成结构体总结的全部内容,更多相关访问外设寄存器的四种方式一、最直接的方式二、定义成常量而非宏方式一内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(74)

评论列表共有 0 条评论

立即
投稿
返回
顶部