@乔木 发表于 2017-8-23 22:12:18

【JESSE】STM32初学(寄存器版)——位段操作

接上篇:【JESSE】STM32初学(寄存器版)——位段操作

前几篇连载中,我们对IO口的读写都是直接操作相应的寄存器位,不免会显得麻烦,有没有简单一点的方法操控它呢。有,我们可以使用位段操作。
现在诸多开发板的例程里都会加入位段操作,但是大部分都是展示了一个实现过程。就算正点原子的教程中也是草草带过(我最早是在正点原子的程序中接触到位段),并没深入讲解位段究竟是个什么样的过程。但是,位段作为内核机制,我觉得还是有必要好好讲讲的。
我将原子的sys.h文件copy到了工程目录下,为了表示对原作者的尊重,我保留了原子的标识(并非打广告),只是将一些用不到的东西以注释的形式进行屏蔽,并且添加了一些原本没有的东西。

原子在STM32F4开发指南中也对其中内容进行了简单介绍



中间截掉的部分便是上面的代码部分。
这些文档虽然都展示了位段操作的过程,但是对其中原理还是不懂。
这种时候我们就要借助一些更权威的工具书了,在《ARM Cortex-M3与Cortex-M4权威指南》第6章第7小节对位段操作进行了比较详细的介绍。考虑到可能有很多同学并没有这本书,并且我讲的也没书上说的清楚,这里我要大量贴图了,勿怪





看到这,什么是位段呢。位段就是Cotex-M3/M4为了方便操作,以一个字大小的寄存器代替了一个位。

STM32中对读写的操作都是以字或半字(stm32中一个字是四个字节)为单位的,如果想要对一个位进行读写,就需要像前面操作的一样,进行移位。
为了简化操作,将位进行膨胀,膨胀到字的层面,不就可以直接进行读写了吗?这就避免了移位操作,但是地址膨胀带来的是地址空间上的占用,所以这种膨胀非常有限,仅仅在SRAM和片上外设区有各有1M的位段区和与之对应的32M别名区。

现在我们知道,要想操作位段区的某一位,就要去找对应的别名区地址,那别名区的地址怎么算呢,文中有公式,但是如何去理解呢。在这我谈谈我的理解。
位段区和别名区都是连续的地址空间,都有各自的开始地址,这个开始地址肯定是要先去掉的,膨胀的是空间,跟它的起始地址是无关的。将一个位膨胀为一个字,扩大了32倍,这就可以将位段区的每一位和别名区的每个字一一对应。
假如我们要计算字节A中第B位在别名区的地址,那就应该A-位段区起始地址,再将空间膨胀,即(A-起始地址)*32,这就算到了字节A膨胀后在别名区的地址偏移,这时候一个位对应的是一个字(4字节),所以B相对于A的地址偏移应该是B*4,即算得B在别名区的地址偏移为(A-起始地址)*32+B*4;这时候再加上别名区的起始地址便是B在别名区的地址了。
这和文档或是程序中的算法是一致的,我们也可以算算看,
((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
((addr & 0xF0000000)这是取得了位段区的起始地址,
((addr & 0xF0000000)+0x2000000这便是取得别名区的起始地址(位段区与相应的别名区的高4位地址是一致的,只是别名区起始地址相对位段区起始地址偏移了0x2000000)。
((addr &0xFFFFF)<<5)这一步便是将空间进行膨胀,因为位段区的大小是1M,所以位段区的地址范围便是0x00000~0xFFFFF,(addr &0xFFFFF)这是获得字节A(按上面的例子加入)的偏移地址,左移了5位,就相当于乘了32倍;
(bitnum<<2)这是获取位B在别名区相对于A的偏移,左移2位,便是相当于乘了4倍。
不知道我讲清楚了没有,也不知道讲的对不对。

讲了实现原理,我们就可以来看看程序效果
void led_toggle(uint8_t led_index)
{
    if((led_index&LED1)==LED1)   GPIOG->ODR ^= 0x01<<6;      
    if((led_index&LED2)==LED2)   GPIOD->ODR ^= 0x01<<4;
    if((led_index&LED3)==LED3)   GPIOD->ODR ^= 0x01<<5;
    if((led_index&LED4)==LED4)   GPIOK->ODR ^= 0x01<<3;
}这是直接操作相应位的方式

void sys_led_toggle(uint8_t led_index)
{
    if((led_index&LED1)==LED1)    PLED1 = !PLED1;
    if((led_index&LED2)==LED2)    PLED2 = !PLED2;
    if((led_index&LED3)==LED3)    PLED3 = !PLED3;
    if((led_index&LED4)==LED4)    PLED4 = !PLED4;
}这是应用了位段的方式,直接对相应寄存器进行赋值即可。

也可用位段的方式对当前状态的读取,但是这里需要注意的是,将一个位膨胀成一个字之后,它所能表示的就不单单只是‘0’和‘1’(尽管它的作用还是用于表示‘0’和‘1’),所以如果想通过读取别名区的寄存器值为‘1’还是不为‘1’来判断相应位的状态,这种做法是非常危险的,别名区里的寄存器非常可能并不是存储的‘1’,而是别的非零数,所以这里要用判‘0’的方式,我就见过别人掉进这个坑里,逻辑上没有任何问题,但是程序死活出不来。

最后上传个工程和PDF版的Cortex-M3/M4权威指南(光pdf就140M:L)

下一篇:【JESSE】STM32初学(寄存器版)——USART


二子 发表于 2018-10-3 23:01:24

感谢楼主的无私分享!
页: [1]
查看完整版本: 【JESSE】STM32初学(寄存器版)——位段操作