草庐IT

嵌入式开发(5)位带(位段)操作

Yank_k 2023-07-30 原文

目录

一、位带操作

       位带操作常用于I/O高度密集访问的芯片。

       参考权威指南:Bit-band operation support allows a single load/store operation to access (read/write) to a single data bit. In the Cortex-M3 and Cortex-M4 processors, this is supported in two pre-defined memory regions(静态映射) called bit-band regions. One of them is
位带操作支持允许单个加载/存储操作访问(读/写)单个数据位。在Cortex-M3和Cortex-M4处理器中,这在两个预定义的内存区域中得到支持(静态映射) 称为位带区域

回想以前写51代码

P0 = 0x10;     //将P0端口设置为0x10
P1_0=1;        //将P1端口0号引脚设置为高电平
a = P2_2;      //获取P2端口2号引脚的电平

       根据上述的方法,我们可以发现快速定位修改某个引脚的电平还有获取引脚的状态

       GPIO_SetBits、GPIO_ResetBits、GPIO_WriteBit操作IO口的性能没有达到极致,因为这些函数都需要进行现场保护和现场恢复的动作,比较耗时间,没有进行一步到位,使用位带操作则没有上述的烦恼,简单快速!

       举例来说,可以利用其实现从通用目的输入/输出CGPIO)端口往串行设备的串行数据传输。 由于串行数据和时钟信号的访问是分开的,因此应用程序代码实现起来也非常简单。

示例1:

GPIO_SetBits(GPIOF,GPIO_Pin_9);
修改为
PFout(9)=1;

示例2:

GPIO_ResetBits(GPIOF,GPIO_Pin_9);
修改为
PFout(9)=0;

       因为使用对引脚设置电平或读取电平,库函数效率是不高的,很难应付高性能的场合,如下代码,修改某引脚电平要执行起码3行代码,还不包括隐含的调用函数与函数返回的过程。

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  GPIOx->BSRRL = GPIO_Pin;
}

       对于这些8位处理器,可位寻址的数据具有特殊的数据类型,而且需要特殊的 指令来访问位数据。尽管Cortex-M3和Cortex-M4处理器中没有位操作的特殊指令,不过定义了特殊的存储器区域后,对这些区域的数据访问会被自动转换为位段操作。

       了解位段操作之前,首先得了解位带区别名区

二、位带区和别名区

1.定义

       位带区是存储器映射包括两个位段区域。这些区域将存储器别名区域中的每个字映射 到存储器位段区域中的相应位。在别名区域写入字时,相当于对位段区域的目标位执行读-修改-写操作。其中一个位于SRAM区域的第一个1MB,另一个则位于外设区域的第一个1MB ,这两个区域可以同普通存储器一样访问,而且还可以通过名为位段别名的一块独立的存储器区域进行访问。

       别名区是按照一定的映射关系,将位带区的每一个bit 映射到位带别名区的每一个字 (不是字节,stm32中字的宽度为4字节)。
在实际使用过程中,操作位带别名区的字,就是操作位带区的bit。


There are two regions of memory for bit-band operations:(以下两个区域用于位带操作)
0x20000000~0x200FFFFF (SRAM, 1MB)
0x40000000~0x400FFFFF (Peripherals, 1MB)

2.映射表

SRAM区域的位段地址重映射


       我们可以看到表格从0x20000000bit[0]——》0x0x20000000bit[1],别名区地址从0x22000000bit[0]——》0x22000004bit[0],增加了4个字节,也就是32bit。

外设存储器区域的位段地址重映射

下面为一个简单的例子:
(1)将地址0x20000000设置为0x3355AACC。
(2)读地址0x22000008。本次读访问被重映射为到0x20000000的读访问,返回值为1。(0x3355AACC 的bit[2])。
(3)将0x22000008写为0。本次写访问被重映射为到地址0x20000000的读一修改写。数值0x3355AACC 被从存储器中读出来,清除第2位后,结果0x3355AAC8被写入地址0x20000000。
(4)现在读取0x20000000,这样会得到返回值0x3355AAC8( bit[2]被清除)。在访问位段别名地址时,只会用到数据的LSB(bi[0])。另外,对位段别名区域的访问不应该是非对齐的。若非对齐访问在位段别名地址区域内执行,结果是不可预测的。

三、位段操作优势

参考权威指南,位段操作还可简化跳转决断。例如,若跳转应该基于外设中某个状态寄存器的一位来执行,除了:

· 读取整个寄存器
· 屏蔽未使用的位
· 比较和跳转

还可以将操作简化为:

· 通过位段别名读取状态位(得到0或1)
· 比较和跳转

       除了可以提高少数几个指令的位操作速度外,Cortex-M3 和Cortex-M4处理器的位段特性还可用于资源(如I/0端口的各引脚)被不止一个进程共用的情形。位段操作最重要的一个优势或特点在于它的原子性。换句话说, 读—修改一写的流程不能被其他总线行为打断。若没有这种特性,在进行读一修改一写的软件流程时,可能会出现下面的问题:假定输出端口的第0位被主程序使用而第1 位被中断处理使用,所示,基于读一修改一写操作的软件可能会引起数据冲突。利用位段特性,这种竞态现象是可以避免的,这是因为读修改写是在硬件等级执行的,是原子性的,而中断无法在操作时产生。多任务系统中也有类似的问题。例如,若输出端口的第0位被进程A使用而第1 位被进程B使用,基于软件的读修改写可能会引起数据冲突。与前面的类似,位段特性可以确保每个任务的位访问是独立的,因此不会产生数据冲突

       可通过一个映射公式说明别名区域中的每个字与位段区域中各个位之间的对应关系。

四、映射公式

关于IO引脚对应的访问地址,可以参考以下公式

SRAM寄存器的位带别名 = 0x22000000 + (寄存器的地址-0x20000000)32 + 引脚编号4
外设区域寄存器的位带别名 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

示例:
下例说明如何将 SRAM 地址 0x20000300 处字节的位 2 映射到别名区域:0x22006008 = 0x22000000 + (0x30032) + (24)
对地址 0x22006008 执行写操作相当于在 SRAM 地址 0x20000300 处字节的位 2 执行读-修 改-写操作。
对地址 0x22006008 执行读操作将返回 SRAM 地址 0x20000300 处字节的位 2 的值(0x01 表示位置位,0x00 表示位复位)。

五、寄存器地址与别名地址转换技巧

1.确定某端口访问起始地址,如端口F访问起始地址为GPIOF_BASE

#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint16_t BSRRL;    /*!< GPIO port bit set/reset low register,  Address offset: 0x18      */
  __IO uint16_t BSRRH;    /*!< GPIO port bit set/reset high register, Address offset: 0x1A      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

2.根据要访问的寄存器地址计算偏移值,如计算
GPIOF的ODR寄存器地址 = GPIOF_BASE+0x14;

3.根据以下公式进行换算

寄存器的位带别名地址 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

详细示意图参考如下:

4.设置PF9引脚电平代码如下

uint32_t *PF9_BitBand = (uint32_t *)(0x42000000 + (GPIOF_BASE + 0x14 - 0x40000000)*32 + 9*4);

更优解的方法:

uint32_t *PF9_BitBand   = (uint32_t *)(0x42000000 + ((uint32_t)&GPIOF->ODR - 0x40000000)*32 + 9*4);

将端口的访问封装为Pxout、Pxin,例如端口F引脚电平设置PFout,端口A引脚电平读取PAin。

六、代码调整

#define PFout(n)	*(volatile uint32_t *)(0x42000000 + (GPIOF_BASE + 0x14 - 0x40000000)*32 + n*4)
#define PAin(n)	*(volatile uint32_t *)(0x42000000 + (GPIOA_BASE + 0x10 - 0x40000000)*32 + n*4)

七、编译优化

优化:编译器想尽办法去压缩程序存储空间,提高运行速度。

一般编译器,优化有多个等级:-O0、-O1、-O2、-O3。

-O0:缺省优化级别,不压缩程序存储空间,不提高程序运行速度,保证程序的可靠执行。
-O1:轻度优化,轻度压缩程序存储空间,轻度优化程序运行速度。
-O2:推荐优化等级,在程序存储空间和程序运行速度取得平衡点。
-O3:最高级别的优化等级,有可能导致程序不能运行,也会使用以空间换时间的方法,导致程序体积增大。

在编译器中也可以设计代码优化程度

示例1:-O0

示例1:-O2

按键例子1,任何时刻按下按键,灯无法响应:

#define PAin(n) *((uint32_t *)(0x42000000 + (((uint32_t)&GPIOA->IDR) - 0x40000000)*32 + (n)*4))
PFout(9) = PAin(0);

经过编译阶段,会得到恒定的结果。

PFout(9)=1;

按键例子2,任何时刻按下按键,灯能够立即响应点亮或熄灭:

#define PAin(n) *((volatile uint32_t *)(0x42000000 + (((uint32_t)&GPIOA->IDR) - 0x40000000)*32 + (n)*4))
PFout(9) = PAin(0);

编译器不会去优化*((volatile uint32_t *)(0x42000000 + (((uint32_t)&GPIOA->IDR) - 0x40000000)*32 + (n)4))变为恒定的值;
而是每次都是小心翼翼地取执行
((volatile uint32_t *)(0x42000000 + (((uint32_t)&GPIOA->IDR) - 0x40000000)*32 + (n)*4)),读取该地址上的值。

PFout(9) = PAin(0);

八、volatile关键字

目的为了防止编译器优化

需要注意的是,在使用位段特性时,可能需要将被访问的变量定义为volatile 。C编译器不知道同个数据会以两个不同的地址访问,因此需要利用volatile属性,以确保在每次访问变量时,操作的是存储器位置而不是处理器内的本地备份。

1.应用场景

volatile关键字分析,往往应用在三种场合

(1)多线程编程共享全局变量的时候,该全局变量要加上volatile进行修饰,让编译器不要省略该变量的访问。
(2)裸机编程的时候,某函数与中断服务函数共享全局变量的时候,该全局变量要加上volatile进行修饰,让编译器不要省略该变量的访问。
(3)ARM定义寄存器的时候,寄存器是指向一个地址,要加上volatile进行修饰,让编译器不要优化而省略该变量的访问。

编译器不要优化该变量指的是防止编译器出现优化过度,导致代码运行失效。
加上volatile关键字生成的汇编代码会发生明显的变化,同样调用delay函数,灯的速度发生变化!

2.delay函数在-O2等级,是否添加volatile关键字,反汇编分析。

不添加volatile关键字

添加volatile关键字

跑马灯例程:

/******************************************sys.h********************************/
注意:代码不完整,可以自行去下载正点原子stm32f407的例程看看
#define SYSTEM_SUPPORT_OS		0		
#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)) 
//IO�ڵ�ַӳ��
#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014     

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
 

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n) 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n) 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n) 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n) 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n) 

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n) 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n) 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n) 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  


void WFI_SET(void);		
void INTX_DISABLE(void);
void INTX_ENABLE(void);	
void MSR_MSP(u32 addr);	
#endif

/**********************************************sys.c********************************/
__asm void WFI_SET(void)
{
	WFI;		  
}

__asm void INTX_DISABLE(void)
{
	CPSID   I
	BX      LR	  
}

__asm void INTX_ENABLE(void)
{
	CPSIE   I
	BX      LR  
}
__asm void MSR_MSP(u32 addr) 
{
	MSR MSP, r0 			//set Main Stack value
	BX r14
}

/**********************************************main.c********************************/
 int main(void)
 {  
    delay_init();            //延时函数初始化    
    LED_Init();         //初始化与LED连接的硬件接口
    while(1)
    {
        PAout(8)=1; //LED0输出低
        PDout(2)=0;//LED1输出高
        delay_ms(500);
        PAout(8)=0;//LED0输出高
        PDout(2)=1;//LED1输出低
        delay_ms(500);
    }
 }

总结

       位段操作并不局限于字传输,字节传输或半字传输也可以执行。例如,在用字节访问指令C LDRB/STRB)访问位段别名地址区域时,所产生的对位段区域的访问就是字节大小的。类似地,对位段别名的半字传输CLDRH/STRH)则会被重映射到对位段区域的半字大小的传输。在位段别名地址上执行非字传输时,地址值仍然应该是字对齐的。继续学习!!

有关嵌入式开发(5)位带(位段)操作的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  3. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  4. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  5. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  9. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  10. ruby-on-rails - 如何处理 Grape 中特定操作的过滤器之前? - 2

    我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?

随机推荐