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

这篇具有很好参考价值的文章主要介绍了嵌入式开发(5)位带(位段)操作。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、位带操作

       位带操作常用于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。

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

2.映射表

SRAM区域的位段地址重映射

嵌入式开发(5)位带(位段)操作
       我们可以看到表格从0x20000000bit[0]——》0x0x20000000bit[1],别名区地址从0x22000000bit[0]——》0x22000004bit[0],增加了4个字节,也就是32bit。

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

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

下面为一个简单的例子:
(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

详细示意图参考如下:
嵌入式开发(5)位带(位段)操作

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:最高级别的优化等级,有可能导致程序不能运行,也会使用以空间换时间的方法,导致程序体积增大。

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

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

示例1:-O0
嵌入式开发(5)位带(位段)操作
示例1:-O2
嵌入式开发(5)位带(位段)操作

按键例子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关键字

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

添加volatile关键字

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

跑马灯例程:

/******************************************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)则会被重映射到对位段区域的半字大小的传输。在位段别名地址上执行非字传输时,地址值仍然应该是字对齐的。继续学习!!文章来源地址https://www.toymoban.com/news/detail-402477.html

到了这里,关于嵌入式开发(5)位带(位段)操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《嵌入式系统开发实践》实践一 Linux 文件I/O操作

    一、 实验目的 掌握函数stat中文件属性的应用; 掌握系统函数system、opendir等的使用; 掌握struct dirent的结构体变量的使用方法; 掌握文件属性的判断; 掌握系统函数open、read、write、close的使用方法。 掌握设备操作的系统函数使用方法。 二、 实验任务与要求 测试文件S_IRU

    2023年04月14日
    浏览(47)
  • C/C++语言学习路线: 嵌入式开发、底层软件、操作系统方向(持续更新)

    1.1 视频教程点到为止 1.2 炫技视频看看就行 1.3 编程游戏不玩也罢 有些游戏的主题任务就是编程,游戏和实际应用环境有一定差异(工具、操作流程),在初级阶段主要是熟悉实际场景,而且多数是通过前端语言进行游戏,对底层软件学习的帮助不大。 Coding Games: PHP、C、

    2024年04月28日
    浏览(77)
  • 嵌入式软件开发常用的编辑代码工具、比较工具和测试工具的使用说明和操作步骤

    嵌入式软件开发常用的编辑代码工具有: Eclipse Eclipse是一款开源的集成开发环境,支持多种编程语言和插件,适用于多种嵌入式开发平台。以下是Eclipse的使用说明和操作步骤: 步骤1:创建新工程 单击“File”菜单,选择“New”→“Project”选项,选择工程类型和开发平台。

    2024年02月02日
    浏览(97)
  • 嵌入式操作系统(嵌入式学习)

    嵌入式操作系统是一种专门设计和优化用于嵌入式系统的操作系统。它是在资源受限的嵌入式设备上运行的操作系统,如微控制器、嵌入式处理器和系统芯片等。 嵌入式操作系统的主要目标是提供对硬件资源的有效管理和对应用程序的调度,以实现系统的可靠性、实时性和效

    2024年02月10日
    浏览(56)
  • 关于嵌入式开发的一些信息汇总:嵌入式C开发人员、嵌入式系统Linux

    这篇文章是关于嵌入式开发的一些基本信息,供想入行的人参考。有一些作者本人的想法,以及来自外网的大拿的文章翻译而来,原文链接在此Learning Linux for embedded systems,再次感谢,支持原创。 普通C开发人员和嵌入式C开发人员之间的 基本区别在于 ,因为嵌入式C程序被设

    2024年02月03日
    浏览(65)
  • 适合嵌入式开发的GUI(嵌入式学习)

    嵌入式开发中的GUI(图形用户界面)是指在嵌入式系统中实现图形化的用户界面,以便用户可以通过触摸屏、按钮、鼠标或其他输入设备与系统进行交互。 以下是一些常见的嵌入式GUI开发方法: 轻量级GUI库:一些轻量级的GUI库适用于嵌入式系统,例如uGFX、LittlevGL、Nanogui等。

    2024年02月09日
    浏览(67)
  • 嵌入式Linux:如何进行嵌入式Linux开发?

    目录 1、裸机开发 2、SDK开发 3、驱动开发 3.1、字符设备驱动 3.2、块设备驱动 3.3、网络设备驱动 4、应用开发 嵌入式Linux开发主要有四种方式:裸机开发、SDK开发、驱动开发和应用开发。 裸机开发通常指在没有操作系统支持的环境中直接在硬件上运行程序的开发。这种开发方

    2024年01月25日
    浏览(77)
  • 嵌入式开发:单片机嵌入式Linux学习路径

    SOC(System on a Chip)的本质区别在于架构和功能。低端SOC如基于Cortex-M架构的芯片,如STM32和NXP LPC1xxx系列,不具备MMU(Memory Management Unit),适用于轻量级实时操作系统如uCOS和FreeRTOS。高端SOC如基于Cortex-A架构的芯片,如三星、高通、飞卡、全志和瑞芯微等,具备MMU,支持虚拟内

    2024年02月15日
    浏览(54)
  • 【嵌入式基础】串口通信操作实例

    本文主要介绍流水灯实验和串口通信的实验过程,对串口协议和RS-232标准,RS232电平与TTL电平的区别,以及\\\"USB/TTL转232\\\"模块(以CH340芯片模块为例)的工作原理这些知识也有了一定的涉及。 目录 一、了解串口协议以及\\\"USB/TTL转232\\\"模块的工作原理 1、串口协议 2、RS-232标准 3、

    2024年02月01日
    浏览(61)
  • 嵌入式linux学习之实践操作

    # 前沿 ##1. 安装交叉编译器 * 在 开发板光盘 A- 基础资料 -5 、开发工具 -1 、交叉编译器 路径下找到 st-example-image-qt wayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh 。将它拷贝到 Ubuntu 虚拟机上。 拷贝到 Ubuntu 后,赋予 st-example-image-qtwayland-openstlinux-weston-stm32mp1-x86_64-toolch

    2024年01月25日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包