寄存器:实际上寄存器就是一个功能单元,对寄存器进行操作就是对这个功能单元进行操作。每个寄存器32bit,占四个字节。比如对ODR寄存器进行操作,让GPIOB所有的IO口输出高电平,那么先找到对应的地址0x4001 0C0C,直接写这个的话,编译器只会认为这是一个16进制数而不是一个地址,地址都是正数且且占四个字节的,因此要进行转换,*(unsigned int*)(0x4001 0C0C)这样可以进行强制转换为4个字节的整型指针数据,再用*把这个指针的地址取出来。因此从底层来看,*(unsigned int*)(0x4001 0C0C)=0xFFFF就可以让16个IO口都输出高电平了。为了更简单的使用寄存器(功能单元),可以利用宏定义的方式,将宏名(寄存器名)与宏体(寄存器实际地址)进行联系起来,后面直接对寄存器进行操作即可。如#define GPIOB_ODR *(unsigned int*)(0x4001 0C0C)
GPIOB_ODR=0xFFFF; 这样操作就简单多了。接下来就是层层套娃找到GPIOB端口的所有功能单元地址,将他们分不同的功能改个别名变成寄存器,以后直接操作对应的寄存器就OK啦。
对与寄存器地址:各个寄存器的地址=外设基地址+寄存器相对于外设基地址的偏移。偏移地址需要查看用户手册,然后直接加上外设基地址,即可得出该寄存器地址。在这里,又可以引出一个问题了,总线基地址!我们平时用到的外设基本都是挂载在APB1,APB2上的,因此通过总线基地址加上外设偏移地址,即可得出该外设的基地址。各个外设的地址=总线基地址+外设相对于总线基地址的偏移。所以一整个逻辑就是:CPU外有多条总线,每条总线上都会挂载很多的外设,每个外设上都有很多种不同的寄存器(功能单元)。基地址多种,总外设基地址->总线基地址->外设基地址。
由于一个外设上的寄存器(功能单元)有很多,我们可以将其封装成一个结构体,结构体内容包括该外设的所有寄存器,例如GPIOA所拥有的所有寄存器。之后再通过这个语句:#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE),即可对GPIOA外设首地址GPIOA_BASE进行强制类型转换,告诉编译器将这个地址解释为一个指向GPIO_TypeDef类型的指针。其中GPIOA_BASE是GPIOA的外设首地址,往后偏移即为该外设上各个寄存器的地址。因此,我们通过这个结构体,即可对GPIOA外设上的寄存器(功能单元)进行访问和赋值。