最近学习IIC协议,试着自己写iic的STM32驱动程序,由于一直用的stm32库开发,调用库函数写起iic驱动程序来并不方便,就看看别人的驱动程序是怎么写的,在学习的过程中发现正点原子采用位带操作写的驱动程序写的特别精简,个人觉得采用位带操作比调用库函数方便不是一点半点。因此又查资料仔细了解了一下位带操作就有了这篇笔记,个人水平有限难免有出错的地方。
要点:找到需要位带操作的寄存器的地址,并把它转换为位带别名区地址,参考工程实操中的前三行代码。
1.什么是位带?
支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在CM3 中,有两个区中实现了位带。其中一个是SRAM区的最低 1MB 范围,第二个则是片内外设区的最低1MB 范围。这两个位带中的地址除了可以像普通的RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。例如使用位带操作的目的是能够像C51那样直接给i0口置高置低,例如在51单片机中这样就可以直接给P0^1口置位和复位,代码如下:
P0^1=1;
P0^1=0;
位带操作就是让stm32系列单片机能够实现这样直接对位进行操作。在stm32开发过程中如果用库函数开发不使用位带操作给某个口置位和复位操作,代码如下:
GPIO_SetBits(GPIO_IIC,IIC_SCL_Pin);
GPIO_ResetBits(GPIO_IIC,IIC_SDA_Pin);
如果采用位带操就能像C51那样快速而又简单的对IO口进行置位和复位操作。代码如下:
IIC_SCL=1;
IIC_SCL=0;
2.位带别名区与比特
欲设置地址 0x2000_0000中的比特 2,如果不使用位带则不能像C51那样对这个比特位单独操作,需要先把0x2000_0000处的值读到寄存器,例如读到的值为二进制00000000,然后将第二个值置位变为00000010,然后再将这个值写回原地址0x2000_0000处。如果使用位带则只需要将1写到0x2000_0000中的比特 2对应的地址0x22000008处,例如:*(0x22000008)=1这样就像C51那样操作IO口。位带别名区与位带区如下图所示:
3.工程中实操
这里参考正点原子的代码
#define BITBAND(addr,bitnum) ((addr&0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
设置输入输出方向寄存器后给GPIOA的1口置位和复位只需要如下代码所示,这样再对IO操作是不是就很简单了。
PAout(1)=1;
PAout(1)=0;
上述的第一行代码,主要是把位带地址+序号转换为别名地址的宏。第二行把该地址转换成一个指针,第三行方便使用,第5行和第6行对寄存器地址进行宏定义。当使用位带功能时,要访问的变量必须用volatile 来定义。因为C 编译器并不知道同一个比特可以有两个地址。所以就要通过volatile,使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的考虑,在中途使用寄存器来操作数据的复本,直到最后才把复本写回——这会导致按不同的方式访问同一个位会得到不一致的结果 。
参考:《CM3权威指南》