位带操作的认识

版权声明:文章为本人原创,未经同意请勿转载,联系我:[email protected] https://blog.csdn.net/Emmy_kanly/article/details/80865318

    接触STM32快一年了,前段时间才偶尔听到一个叫位带操作的概念... 惭愧惭愧

    经过查阅前辈们的经验和相关资料,有了点认识在此记录一下,有不正确的地方请多多指教

/***********************************************万能分割线*************************************************/

    网上已有很多前辈细致介绍了何为位带操作,我看后受益匪浅,经过自己的理解,在此,个人觉得有必要回归英文技术手册,定位到这个操作最重要的一个特性,也是它最值得被使用的地方:

    高亮处很清晰地描述了位带操作的“原子”特性(概念不清晰的自行查阅一下),即在位带操作过程中硬件层面不允许被中断线程打断。  我们知道在一个项目中会出现main进程下可能还有很多中断线程,中断出现的机制为程序处理“突发”事件带来很大的便捷,提高了效率,但是也带来一个隐患:变量的原子操作问题(姑且这么个叫法吧,没找着术语╮(╯▽╰)╭)

    这个问题最大的体现就是一个变量进入中断服务函数被修改后,退出时被原进程(打断前在执行的进程)干扰而未完成真实修改,一方面是编译器的优化,另外就是在汇编层面这个修改的指令是可以被打断的,放入一个例子吧:

    这是在汇编语言中同一个修改变量的指令,左侧是没有位带操作的,四条汇编指令每条的中间都有间隔,实际上都是可以被打断的,假设在左侧任一箭头处被打断进入服务函数修改同一个变量,退出时回到箭头下方继续执行,我们会发现箭头下方的指令也在修改原地址处的值!这可能导致中断修改的又被改了回去!  而图的右侧可以知道使用了位带操作即实现了“原子操作”,想要打断此修改只能在箭头处打断,而无论在哪个箭头处打断退出,变量可以完成整个:地址设置--修改值--写回地址 流程,这保证了变量安全被使用,不会因为是共享资源而被各线程强占

/***********************************************************分割一下***************************************************************/

    明白了位带操作最大的好处,是时候深入研究什么是位带操作了(笔者发现M4,M4内核都有位带操作的描述,而未在M0手册上找到,不知是不是本来就没有)

    用过51的朋友都知道,在操作51的IO口时,用其定义好的sbit P1_0 = P1^0; P1_0 = 1;就可以对P1.0口置1,用起来贼方便,可以STM32中一大堆GPIO寄存器意味着不能愉快地这么简单写了......    然而ARM内核还是提供了另一套可以这么写的方式,就是使用位带区:每个GPIO寄存器都有一个唯一的内存地址,这些地址都在位带区中有定义,想要操作它就需要在RAM中先改写这个地址的内容,最后再从RAM写回寄存器。内核不允许我们直接修改IO口寄存器地址的值,却也提供了一条路,就是用位带别名区,把位带区的地址映射到别名区,修改别名区的内容就等于修改了位带区地址里的内容

    STM32的位带别名区有两个,如下图:

    这两个别名区用法差不多,都可以对相应的位带区内容进行修改

   

上图是STM32F1手册中给的映射公式和简单的举例,没有加以详细的说明,接下来说说我的理解方式,这里放一张很多前辈都用过的图,我发现这张图足以解释并理解公式的含义了:

    我们先不看手册举的例子,假设我要设置图中0X20000000的第1个bit为1,来理解使用位带操作要知道些什么,这里要交代一下,位带区的1个字节(8bit)地址对应位带别名区4个字节(32bit)的地址,要时刻记住这个前提!

①知道要设置的寄存器地址是什么

②确定要设置寄存器的哪一位

③知道该寄存器地址对应的位带别名区地址是什么

④找到位带别名区的基地址和位带区基地址

    经过以上几步来看看我们要配置些什么:

bit_word_addr = 位带别名区的基地址 + (要设置的地址 - 要设置地址所在位带区的基地址)* 32 + (要设置地址的位数 * 4),好啦,翻译成代码就是:

bit_word_addr = 0X22000000 + (0X20000000 - 0X20000000) * 32 + (1 * 4),由此可以定位到要位带操作的地址为bit_word_addr = 0X22000000 + 4 = 0X22000004,也就是图中第3编号的0X22000004地址

    这里乘以32是为了定位到要设置位的寄存器地址在位带别名区的地址,定位到寄存器在别名区的地址后,再按照位带区一个位占别名区四个位的规律定位到要设置的位在别名区的哪个地址,这句话理解了这个公式也就看懂啦

    当我们要操作一个地址,写入值进去时要做的一件事就是把这个地址变成指针指向的地址来操作这个指针:

*( (volatile unsigned long)* (addr) ),addr即为上面计算出来的地址,为了方便通常都会封装这步得到下面大家喜闻乐见的样子:

#define BITBAND(addr, bitnum) (0X22000000 + (addr - 0X20000000) * 32 + (bitnum * 4))

#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))

#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

至此,我们想设置一个GPIO口时,可以用库函数已经封装好地址的GPIO->ODR,传入要操作的位即可咯,用起来跟51一样舒服

BIT_ADDR(GPIO->ODR,5) = 1; 搞定,喜欢更简洁的朋友还能再继续封装自己喜欢用的样子,位带操作就这样实现了,既可以简化IO设置的问题,还有安全保证,一举两得

猜你喜欢

转载自blog.csdn.net/Emmy_kanly/article/details/80865318