详解Cortex-M位带操作

我相信很多朋友在学习单片机之前都学习过51单片机,假设在51单片机的p1.1的io口上挂了一个led,那么你单独对led的操作就是p1.1 = 0或p1.1 = 1,这样你就可以单独的对p1端的第一个io口进行上下拉操作,然而对于stm32,是没有这种操作的,那么为了像51单片机一样能够单独的对某个端的某一个io单独操作,就引入了__位带操作__,简而言之,就是为了去单独操作stm32里面pa的第1个io口,所以才有了位带这样的操作机制。
1 什么是位带操作在讲解位带操作之前,首先要搞清楚什么是位带操作。我们知道,32位的处理器的32位地址总线提供了4g的地址空间,几乎所有的嵌入式产品是足够用的。 cortex-m就利用了额外的空间实现了称为位带(bit-banding)操作的硬件属性,该技术使用地址空间的两个不同区域来指向同一物理地址 。在主位带区域,每个地址对应一个字节的数据,在“位带别名”区域中,每个地址对应同一个数据的一个位。
如下图所示。在cm3的寄存器映射图中有1mb的 bit band区,这里被称为位带区,与之对应的是32mb的bit band别名区,这里被称为位带别名区。
stm32的位带别名区会把位带区中的每一位膨胀成一个32位的字,所以相应的别名区的内存也会是位带区的32倍。从上图可以看出,位带操作同时支持sram和片上外设,支持位带操作的两个内存区域范围如下:
sram区:0x20000000 ~ 0x200fffff,最低1m的范围;
片上外设区: 0x40000000 ~ 0x400fffff,最低1m的范围;
位带操作就是把位带区中一个地址的8个位分别映射到位带别名区的8个地址(lsb有效,即最低位有效),通过操作相应地址的方式实现操作某个位。以sram为例,位带区和位带别名区的映射如下图所示:
位带区里每个地址的每1位膨胀为别名区里一个32位的字(32位处理器中,1字=4字节),例如:0x20000000的第0位对应0x22000000,第1位对应0x22000004等。
2 位带操作的计算公式既然位带操作属于cortex-m内核的一部分,那么在cortex-m官方手册也是给出了相应的计算公式的,其通用公式如下:
别名区地址 = 别名区起始地址 + (位字节地址偏移量 * 8 + n) * 4
其中,8表示一个字节有8位,4表示膨胀了4个字节,因此位带区和位带别名区也就是32倍的关系。
两个区的计算公式为:
sram区:aliasaddr = 0x22000000 + (a - 0x20000000) * 32 + n * 4
片上外设区:aliasaddr = 0x42000000 + (a - 0x40000000)* 32 + n * 4
其中,aliasaddr是别名区的地址,a是位带区的地址,n是该端口的上的某一位。
接下来就是对这个地址进行操作了,写1,该位输出1,写0,就输出0。
3 位带操作代码实现这里stm32f1为例,根据stm32的《rm0008 reference manual》手册,其gpio的地址映射如下:
gpiox_odr 寄存器如下:
每个寄存器32位,占4个地址,在访问或修改某个寄存器时,是从首地址开始的,逻辑运算则是直接可涵盖到32bit,offset 为 0x0c。gpioa 的首地址为0x40010800,因此gpiox_odr 寄存器的地址为0x4001080c。则所有的gpio映射如下:
//io口地址映射#define gpioa_odr_addr (gpioa_base+12) //0x4001080c #define gpiob_odr_addr (gpiob_base+12) //0x40010c0c #define gpioc_odr_addr (gpioc_base+12) //0x4001100c #define gpiod_odr_addr (gpiod_base+12) //0x4001140c #define gpioe_odr_addr (gpioe_base+12) //0x4001180c #define gpiof_odr_addr (gpiof_base+12) //0x40011a0c #define gpiog_odr_addr (gpiog_base+12) //0x40011e0c #define gpioa_idr_addr (gpioa_base+8) //0x40010808 #define gpiob_idr_addr (gpiob_base+8) //0x40010c08 #define gpioc_idr_addr (gpioc_base+8) //0x40011008 #define gpiod_idr_addr (gpiod_base+8) //0x40011408 #define gpioe_idr_addr (gpioe_base+8) //0x40011808 #define gpiof_idr_addr (gpiof_base+8) //0x40011a08 #define gpiog_idr_addr (gpiog_base+8) //0x40011e08上述只是位带区的地址,根据位带操作的计算公式,则操作位带别名区的地址方法如下:
//io口操作宏定义#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))以上代码的第一句是转换的关键,当然相对的前面的计算公式做了优化,也就是将sram和片上外设合并在一起。addr & 0xf0000000 得到sram和片上外设的首地址,然后加0x2000000表示位带别名区相对位带区的偏移量,(addr &0xfffff)<<5)和(bitnum<<2)就是前面“*32”和“*4”,只是换成了移位操作,因为移位操作相对乘法运算速度更快。
好了,接下来使用位带操作来写一个gpio流水灯,同时使用库函数来做比较。
【main.c】
/* includes ------------------------------------------------------------------*/#include stm32f1_bsp_led.h/* private typedef -----------------------------------------------------------*//* private define ------------------------------------------------------------*//* private macro -------------------------------------------------------------*//* private variables ---------------------------------------------------------*//* private function prototypes -----------------------------------------------*//*简单延时函数*/void delay(uint32_t xms); /* private functions ---------------------------------------------------------*//** * @brief 主函数 * @param none * @retval */int main(void){ /* led 初始化 */ led_gpio_config(); while (1) {#if 0 gpio_setbits(gpiob,gpio_pin_0); // 亮 delay(0xfffff); gpio_resetbits(gpiob,gpio_pin_0); // 灭 gpio_setbits(gpiog,gpio_pin_6); // 亮 delay(0xfffff); gpio_resetbits(gpiog,gpio_pin_6); // 灭 gpio_setbits(gpiog,gpio_pin_7); // 亮 delay(0xfffff); gpio_resetbits(gpiog,gpio_pin_7); // 灭#else pbout(0) = 1; // 亮 delay(0xfffff); pbout(0) = 0; // 灭 pgout(6) = 1; // 亮 delay(0xfffff); pgout(6) = 0; // 灭 pgout(7) = 1; // 亮 delay(0xfffff); pgout(7) = 0; // 灭#endif }}/** * @brief 延时函数 * @param xms 延时长度 * @retval none */void delay( uint32_t xms){ //for(; ncount != 0; ncount--);(方法一) while(xms--);//(方法二)}【stm32f1_bsp_led.c】
/* includes ------------------------------------------------------------------*/#include stm32f1_bsp_led.h/* private typedef -----------------------------------------------------------*//* private define ------------------------------------------------------------*//* private macro -------------------------------------------------------------*//* private variables ---------------------------------------------------------*//* private function prototypes -----------------------------------------------*//* private functions ---------------------------------------------------------*//** * @brief 初始化led的gpio * @param none * @retval none */void led_gpio_config(void){ /*定义一个gpio_inittypedef类型的结构体*/ gpio_inittypedef gpio_initstructure; /*开启led的外设时钟*/ rcc_apb2periphclockcmd( rcc_apb2periph_gpiob|rcc_apb2periph_gpiog, enable); /*设置io口*/ gpio_initstructure.gpio_mode = gpio_mode_out_pp; //设置引脚模式为通用推挽输出 gpio_initstructure.gpio_speed = gpio_speed_50mhz; //设置引脚速率为50mhz /*调用库函数,初始化gpiob0*/ gpio_initstructure.gpio_pin = gpio_pin_0; //选择要控制的gpiob引脚 gpio_init(gpiob, &gpio_initstructure); gpio_initstructure.gpio_pin = gpio_pin_6|gpio_pin_7;/*选择要控制的引脚*/ gpio_init(gpiog, &gpio_initstructure); /* 开启所有led灯*/ gpio_setbits(gpiob, gpio_pin_0); gpio_setbits(gpiog, gpio_pin_6|gpio_pin_7); }【stm32f1_bsp_led.h】
#ifndef __stm32f1_bsp_led_h__#define __stm32f1_bsp_led_h__#ifdef __cplusplus extern c {#endif /* includes ------------------------------------------------------------------*/#include stm32f10x.h/* exported types ------------------------------------------------------------*//* exported constants --------------------------------------------------------*//* exported macro ------------------------------------------------------------*///位带操作,实现51类似的gpio控制功能//具体实现思想,参考brr=i;} //输出低电平#define digitaltoggle(p,i) {p->odr ^=i;} //输出反转状态/* 定义控制io的宏 */#define led1_toggle digitaltoggle(gpiob,gpio_pin_0)#define led1_on digitalhi(gpiob,gpio_pin_0)#define led1_off digitallo(gpiob,gpio_pin_0)#define led2_toggle digitaltoggle(gpioc,gpio_pin_4)#define led2_on digitalhi(gpiog,gpio_pin_6)#define led2_off digitallo(gpiog,gpio_pin_6)#define led3_toggle digitaltoggle(gpioc,gpio_pin_3)#define led3_on digitalhi(gpiog,gpio_pin_7)#define led3_off digitallo(gpiog,gpio_pin_7)/* exported functions ------------------------------------------------------- */void led_gpio_config(void);#ifdef cplusplus}#endif#endif /* __stm32f1_bsp_led_h__ */不管使用哪种方式,其实验现象都是一样的,但是使用位带操作更方便些,操作者步骤更少,下面举例说明。
实例:欲设置地址 0x2000_0000 中的比特 2,则使用普通操作和位带操作的设置过程如下图所示:
普通操作和位带操作的汇编对比代码如下:
位带读操作相对简单,普通操作和位带操作的设置过程如下图所示:
普通操作和位带操作的汇编对比代码如下:
可以看出位带操作的步骤更少,相对普通操作更简洁。
而且位带操作属于原子操作,在多任务系统中,位带操作可以解决共享资源中的紊乱危象,关于该部分内容可以参看《cortex-m3权威指南》。
__总的来说,位带的主要优点__是数据的一个单独位可以通过一条指令来读或者写,而不需要操作一些利的寄存器。例如,一条从位带别名区域地址进行读操作的ldr指令会将值0或者1加1载入寄存器。类似的,一条str指令在向位带别名区的地址写入时,只是修改主区域中数据的一位。当然修改需要由硬件来执行读写操作,但是只有一条指令(str)被取指并执行。


微波雷达传感器在智能衣柜灯的应用
初探Azure无服务器架构
监控摄像机添加后不出图像怎么办?
物联入门—斑点猫智能猫眼S200
天线是什么?无线电波的波长、频率与传播速度的关系
详解Cortex-M位带操作
全球PI膜集中度高,柔性印刷电路是主要印刷场景
AT32讲堂045 | 雅特力AT32F435/437xx GPIO使用指南
芯海科技通过ISO 26262功能安全管理体系ASIL D认证
介绍蜂窝网络及LPWAN互联
【彩色光模块】CWDM光模块和DWDM光模块知识百科
RN-NTQX9 农田小区域气象环境监测系统
预计华为“鸿蒙”智能手机将占到中国市场新机购买量的46%
物联网云平台的独特优势,推动着我国智慧消防体系建设日益完善
高性价比蓝牙耳机推荐,500元蓝牙耳机性价比之王
压控电流源电路原理图讲解
索尼发布新无线耳机:功能上黑科技满满
一款优秀的智能机器人应该具备哪些功能
科技部发布“国家质量基础的共性技术研究与应用”2020报指南的通知
使用新的Nsight Compute改进导航和性能可视化