说明:定义MCU头文件的通用方法。
参考:ST(意法半导体)的STM32库内提供的头文件。
日期:2012/09/12 lin
step1. 先分类寄存器,比如定时器、GPIO口、串口等; 针对每类寄存器用同样的结构体去实现它的内部项目,然后定义结构体指针,指向对应寄存器的首地址即可。
这样定义的好处是:一个结构体代表一类寄存器;单纯定义结构体以及结构体指针变量均不会占用内存,编译器不会对其分配任何地址,即完全不会占用内存;给出一个初始地址,
结构体内部可自动算出各个变量(即内部的小寄存器)的地址。
MCU中的存储模式很重要,所以要先了解一下大端模式与小端模式,否则会把结构弄反; 内存是“由前到后即低地址在前、高地址在后”排列的;
小端模式就是数据的低字节存储在前(低地址),高字节存储在后(高地址);反之,大端模式就是数据的高字节存储在前(低地址),低字节存储在后(高地址)。
例如, 选择“8位且小端模式的MCU”为例。(16位、32位、64位机都类似)
8位的GPIO口:
typedef struct{
volatile unsigned char GPIO;
}_GPIO;
16位的定时器TIMER(8位机的16位寄存器一般都是由两个8位的寄存器构成,假设二者地址是连续,则就可定义到一个结构体中,如果不连续,则需分别定义。下面例子是连续的):
typedef struct{
volatile unsigned char TIMER_L; //TIMER定时器的低位寄存器
volatile unsigned char TIMER_H; //TIMER定时器的高位寄存器
}_TIMER;
结构体说明:
(1) 结构体内部变量及类型
第一、内部变量即为该寄存器内部包含的分寄存器;第二、必须是volatile类型,这种类型不会被编译器优化,它的意思是该变量的值随时都可能会变化,这种变化
不一定是代码中操作它才变的,也可能是内部自动变化,比如定时器,一旦打开之后,内部的计数寄存器的值就会一直增。
(2) 结构体对齐
第一、结构体内部每个变量类型都必须与实际寄存器所占的内存宽度一致; 第二、拼在一起时,变量之间不可出现空隙,一旦出现空隙,则后面的变量就跟实际的
地址不对应了。
(3) 结构体内部变量的排布
结构体定义方式就是根据一个初始地址,即可推算出结构体内部各个变量的地址,所以内部变量在排布时,必须跟手册中规定的实际地址对应。
step2. 定义寄存器的绝对基地址(或 某一块功能的绝对基地址以及它所包含的各个寄存器的偏移地址)
例如,
#define GPIO_BASE_ADD (0x01) //GPIO的绝对基地址
#define GPIOA_BASE_ADD (GPIO_BASE_ADD) // GPIOA的绝对基地址
#define GPIOB_BASE_ADD (GPIO_BASE_ADD+0x01) // GPIOB的绝对基地址
#define GPIOB_BASE_ADD (GPIO_BASE_ADD+0x02) // GPIOC的绝对基地址
#define TIMER_BASE_ADD (0x06) // TIMER的绝对基地址
#define TIMER0_BASE_ADD (TIMER_BASE_ADD) // TIMER0的绝对基地址
#define TIMER1_BASE_ADD (TIMER_BASE_ADD+0x02) // TIMER1的绝对基地址
#define TIMER2_BASE_ADD (TIMER_BASE_ADD+0x04) // TIMER2的绝对基地址
step3. 定义寄存器指针
#define GPIOA ((_GPIO*)GPIOA_BASE_ADD)
#define GPIOB ((_GPIO*)GPIOB_BASE_ADD)
#define GPIOC ((_GPIO*)GPIOC_BASE_ADD)
#define TIMER0 ((_TIMER*)TIMER0_BASE_ADD)
#define TIMER1 ((_TIMER*)TIMER1_BASE_ADD)
#define TIMER2 ((_TIMER*)TIMER2_BASE_ADD)
step4. 调用方法
GPIOA、GPIOB、GPIOC、TIMER0、TIMER1、TIMER2都属于指针类型,所以在调用时,要注意一点。例如,对GPIOA赋值时,GPIOA->GPIO = 0xff;
定义函数时,假如是需要使用它们作为形参,必须定义成对应的指针类型,例如:
void SET_GPIO(_GPIO* GPIO_X)
{
GPIO_X->GPIO = 0xff;
}
unsigned short Read_TIMER(_TIMER* TIMER_X)
{
unsigned short t;
t = TIMER_X->TIMER_H;
t = (t<<8) | TIMER_X->TIMER_L;
return t;
}
总结: 对于操作MCU,实际就是根据MCU的数据手册,操作各种寄存器,本质上就是操作寄存器所代表的内存地址。
阅读(2511) | 评论(0) | 转发(0) |