STM32 位段详解

目录

1 定义

2 位带操作

2.1 范围

2.2 位带操作

2.3代码实现

3 位段的优点

4 STM32的3种不同GPIO驱动

4.1 库函数版

4.2 寄存器版

4.3 位段版


1 定义

首先需要明确下,位段,位带和别名区这三个名词

位段:STM32用户参考手册使用的名字

位带:CortexM3参考手册使用的

别名区:地址总线上用来位访问地址区域,

所以说,位段和位带是一个意思,是不同手册的不同叫法。

由上述的名词解释得知,位带功能并不是STM32独有的,是CortexM3的功能(CortexM4也有这样的功能)。MCS51有位操作,以一位(bit)为数据对象的操作,MCS51可以简单的将P1口的第2位独立操作:P1.2=0;P1.2=1 ;这样就把P1口的第三个脚(bit2)置0置1。而STM32的位段、位带别名区最重要的就为了实现这样的功能。

2 位带操作

2.1 范围

位带是有范围的,并不是CortexM3全部地址空间都支持的。在 CM3中,有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。

支持位带操作的两个内存区的范围是:

0x2000_0000‐0x200F_FFFF(SRAM 区中最低1MB区域)

0x4000_0000‐0x400F_FFFF(片上外设区中的最低 1MB)

2.2 位带操作

对 SRAM 位带区的某个比特,记该比特所在字节的地址为A,位序号为 n (0<=n<=7),则它在别名区的地址为:

AliasAddr = 0x22000000 + ((A‐0x20000000)*8+n)*4 =0x22000000 + (A‐0x20000000)*32 + n*4

对于片上外设位带区的某个比特,记该比特所在字节的地址为A,位序号为 n (0<=n<=7),则该比特在别名区的地址为:

AliasAddr = 0x42000000 + ((A‐0x40000000)*8+n)*4 = 0x42000000 + (A‐0x40000000)*32 + n*4

上式中,“*4”表示一个字为 4 个字节,“*8”表示一个字节中有 8 个比特。

2.3代码实现

把“位带地址+位序号”转换别名地址宏为:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000 + ((addr &0xFF FFF)<<5) + (bitnum<<2))

把该地址转换成一个指针:

#define MEM_ADDR(addr, bitnum) *((volatile unsigned long *)((addr & 0xF0000000)+0x2000000 + ((addr &0xFF FFF)<<5) + (bitnum<<2)))

其中

addr的取值范围:

0x2000_0000‐0x200F_FFFF

0x4000_0000‐0x400F_FFFF

注意:addr取值要32位对齐

bitnum的取值范围:

0-31

解析:

(addr & 0xf0000000) + 0x02000000:

区分SRAM还是外设,如果是外设,结果为4,再加0x2000000就等于0x4200000,0x42000000就是外设别名位带区。如果是SRAM,结果为2,再加上0x2000000就等于0x22000000,0x22000000就是SRAM别名位带区。

addr & 0x00ffffff:

屏蔽了最高2位,相当于减去0x20000000或者0x40000000。因为位带区的有效范围是1M,即0x100000,这样子就做到了低6位有效。

<< 5:

等价于乘以32

<< 2:

等价于乘以4

特别提醒:

当你使用位带功能时,要访问的变量必须用 volatile 来定义。因为 C 编译器并不知道同一个比特可以有两个地址。所以就要通过 volatile,使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的考虑。

3 位段的优点

最容易想到的就是通过 GPIO 的管脚来单独控制每盏 LED 的点亮与熄灭。另一方面,也对操作串行接口器件提供了很大的方便(典型如 74HC165,CD4094)。位带操作可以把代码缩小, 速度更快,效率更高,更安全。总之位带操作对于硬件 I/O 密集型的底层程序最有用处了

位带操作还能用来化简跳转的判断。

当跳转依据是某个位时,以前必须这样做

1、读取整个寄存器

2、掩蔽不需要的位

3、比较并跳转

使用位带操作后

1、从未带别名区读取状态位

2、比较并跳转

当然,对于写入操作也从4步精简到3步

4 STM32的3种不同GPIO驱动

4.1 库函数版

最常用的版本,使用ST标准外设库

void  Led_Key_Init(void)
{  
  GPIO_InitTypeDef GPIO_Init_s;
  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);//使能时钟  
  GPIO_Init_s.GPIO_Pin = GPIO_Pin_4;
  GPIO_Init_s.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_Init_s.GPIO_OType = GPIO_OType_PP;
  GPIO_Init_s.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init_s.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOE, &GPIO_Init_s);
}

开源代码:

https://github.com/strongercjd/STM32F207VCT6/tree/master/06-GPIO-Input-Output

4.2 寄存器版

寄存器版其实就是把ST标准外设库拷贝出来,可以简化一些操作


/********使用寄存器---start********/
//TP---PA15

#define TP_PORT      GPIOA      
#define TP_PIN      GPIO_Pin_15

#define TP_OUT()   TP_PORT->CRH&=0X0FFFFFFF; TP_PORT->CRH|=0X50000000;   //!IO输出
#define TP_IN()    TP_PORT->CRH&=0X0FFFFFFF; TP_PORT->CRH|=0X40000000;   //!IO输入
#define TP_READ()  (((TP_PORT->IDR)>>15)?1:0)   //!<IO读入

#define TP_SET()    TP_PORT->BSRR = TP_PIN;//写1
#define TP_CLR()    TP_PORT->BRR  = TP_PIN;//写0

#define TP_DATA_SET()   TP_SET() //IO写1
#define TP_DATA_CLR()   TP_CLR() //IO写0
#define TP_DATA_OUT()   TP_OUT() //将IO设为输出
#define TP_DATA_IN()    TP_IN()  //将IO设为输入
#define TP_DATA_READ()  TP_READ()//读取IO的电平

/********使用寄存器---end********/

开源代码:

https://github.com/strongercjd/STM32F207VCT6/tree/master/06-GPIO-Input-Output

4.3 位段版

基于位段的,操作方便

/*LED配置---PE4*/
#define  GPIO_IDR_OFFSET  (GPIOE_BASE+0x10 - PERIPH_BASE)
#define  GPIO_ODR_OFFSET  (GPIOE_BASE+0x14 - PERIPH_BASE)

#define  GPIO_BitNumber   4
#define  GPIO_OUT_BB     (PERIPH_BB_BASE + (GPIO_ODR_OFFSET * 32) + (GPIO_BitNumber * 4))    
#define  GPIO_OUT_DATA           *(__IO uint32_t *)GPIO_OUT_BB

#define  GPIO_IN_BB     (PERIPH_BB_BASE + (GPIO_IDR_OFFSET * 32) + (GPIO_BitNumber * 4))    
#define  GPIO_IN_DATA           *(__IO uint32_t *)GPIO_IN_BB

#define  GPIO_DIR_REG     *(__IO uint32_t *)(GPIOE_BASE+0X00)


#define PE4_SET()  GPIO_OUT_DATA = 1   //!< IO写1
#define PE4_CLR()  GPIO_OUT_DATA = 0   //!< IO写0
#define PE4_OUT()  GPIO_DIR_REG = (((GPIO_DIR_REG) & 0xFFFFFCFF) | 0x00000100)  //!IO输出
#define PE4_IN()   GPIO_DIR_REG = ((GPIO_DIR_REG) & 0xFFFFFCFF)  //!IO输入
#define PE4_READ() GPIO_IN_DATA   //!<IO读入

将上述整理为通用GPIO驱动代码

*GPIO配置---PXX*/
#define GPIO_SET(GPIOx_BASE,GPIO_BitNumber)   *(__IO uint32_t *)((PERIPH_BB_BASE + ((GPIOx_BASE+0x14 - PERIPH_BASE) * 32) + (GPIO_BitNumber * 4))) = 1   //!< IO写1
#define GPIO_CLR(GPIOx_BASE,GPIO_BitNumber)   *(__IO uint32_t *)((PERIPH_BB_BASE + ((GPIOx_BASE+0x14 - PERIPH_BASE) * 32) + (GPIO_BitNumber * 4))) = 0   //!< IO写0

#define GPIO_OUT(GPIOx_BASE,GPIO_BitNumber)   *(__IO uint32_t *)(GPIOx_BASE+0X00)  &= ~(GPIO_MODER_MODER0 << (GPIO_BitNumber * 2));\
                                              *(__IO uint32_t *)(GPIOx_BASE+0X00) |= ((GPIO_Mode_OUT) << (GPIO_BitNumber * 2)); //!IO输出

#define GPIO_IN(GPIOx_BASE,GPIO_BitNumber)    *(__IO uint32_t *)(GPIOx_BASE+0X00)  &= ~(GPIO_MODER_MODER0 << (GPIO_BitNumber * 2));\
                                              *(__IO uint32_t *)(GPIOx_BASE+0X00) |= ((GPIO_Mode_IN) << (GPIO_BitNumber * 2));  //!IO输入
                                              
#define GPIO_READ(GPIOx_BASE,GPIO_BitNumber)  *(__IO uint32_t *)((PERIPH_BB_BASE + ((GPIOx_BASE+0x10 - PERIPH_BASE) * 32) + (GPIO_BitNumber * 4)))   //!<IO读入

调用方法

GPIOx_BASE:GPIOE_BASE基地址,而不是GPIOE

GPIO_BitNumber:4,而不是GPIO_Pin_4

GPIO_OUT(GPIOE_BASE,4);

开源代码:

https://github.com/strongercjd/STM32F207VCT6/tree/master/08-GPIO-bit-band

点击查看本文所在的专辑,STM32F207教程

关注公众号,第一时间收到文章更新。评论区不能及时看到,需要交流可以到公众号沟通

猜你喜欢

转载自blog.csdn.net/Firefly_cjd/article/details/107351639
今日推荐