访问外设寄存器的四种方式

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

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

一、最直接的方式

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

#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;

二、定义成常量而非宏

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

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参数后,会释放常量存储空间,因为别的地方没有引用到。常量是会生成调试符号的,所以使用常量的形式更方便调试。

三、支持多实例

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

#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);

四、定义成结构体

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

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);

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

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)定义成常量。

  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值