收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
一、位带操作
位带操作常用于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关键字,反汇编分析。
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!