五、基地址、偏移地址和模块内寄存器地址分布
随着嵌入式芯片的飞速发展,现在一个普通的MCU内部往往包含着几十个乃至上百个外设模块,包还常用的Gpio,各种定时器,IIC、SPI等通讯模组,如何高效的部署和管理成百上千的寄存器就变得尤为重要。
如上图所示,同通常我们根据芯片的参考手册来部署寄存器地址采用的是基地址+地址偏移量的方式。首先根据具体MCU内核提供的预定义寄存器映射,我们可以找到外设寄存器的基地址(#define PHRIPH_BASE ((uint32_t)0x40000000)),然后根据具体外设所在的总线(AHB或APB)的偏移地址计算给总线所在的地址(#define AHB1PERIPH_BASE (PERIPH_BASE + 0X00020000)),之后在计算具体外设的基地址(#define GPIOA_BASE (AHB1PERIPH_BASE + 0X00000000)),最后按照参考手册中具体模块内部的寄存器分布设计一个模块寄存器分布结构体(GPIO_TypeDef)并将模块的寄存器分布结构体指针指向模块基地址(#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)),这样做的好处是我们在调用GPIOA时直接就定位到了该模块的基地址及内部个寄存器的地址偏移。
六、#转化为字符串##合并变量名
1.#操作符
#操作不包括宏定义#define中的开头#,其主要作用是将#define后面的宏定义转化为一个字符串,例
#define Debug_Cout(var) printf(#var"=%d\n",var)
void main(void)
{
int reg=1;
Debug_Cout(reg);
//展开后结果为:printf("reg""=%d\n",reg),编译器会把两个隔开的字符串当做一个字符串处理
}
2、# #操作符
在嵌入式开发中经常会看到IO口的命名是PTA、PTB、PTC等类似的同类模块的命名,在同类函数操作过程中往往只是某些关键字的区别比如操作GpioA模块的输出和操作GpioB模块可能只是在操作函数中字符A与B的区别,这时候我们可以采用字符串连接的方式来简化同类的操作,列
#define PT(X,n,Reg) BITBAND_REG(PT# #X# #_BASE_PTR-># #Reg,n) //寄存器位带操作
#define PTA0_OUT PT(A,0,PDDR)
这样做的好处是可以直接箱操作51单片机的引脚一样用PTA0_OUT=1或0来直接控制引脚的输出,这里PT(A,0,PDDR)就相当于BITBAND_REG(PTA_BASE_PTR->PDDR,0),# #起的作用就是在字符串间的连接作用。
3、#和# #的宏参数不支持宏
#define A B
#define PT(x) PT# #x
如果我们调用PT(A),他的展开结果为PTA而不会是PTB,这是因为有# #的地方不会对宏进行展开,只是直接的进行合并变量名,如果一定要传递宏展开后的参数,可以采取下面的办法
#define A B
#define _PT(x) PT# #x
#define PT(x) _PT(x)
这样就可以避免在# #后在进行宏展开尴尬。