[应用相关] 《嵌入式 - 深入剖析STM32》详解STM32位带操作

[复制链接]
1067|33
 楼主| 结合国际经验 发表于 2022-2-24 12:52 | 显示全部楼层 |阅读模式
我相信很多朋友在学习单片机之前都学习过51单片机,假设在51单片机的P1.1的IO口上挂了一个LED,那么你单独对LED的操作就是P1.1 = 0或P1.1 = 1,这样你就可以单独的对P1端的第一个IO口进行上下拉操作,然而对于STM32,是没有这种操作的,那么为了像51单片机一样能够单独的对某个端的某一个IO单独操作,就引入了位带操作,简而言之,就是为了去单独操作STM32里面PA的第1个IO口,所以才有了位带这样的操作机制。

 楼主| 结合国际经验 发表于 2022-2-24 12:53 | 显示全部楼层
1 什么是位带操作
在讲解位带操作之前,首先要搞清楚什么是位带操作。我们知道,32位的处理器的32位地址总线提供了4G的地址空间,几乎所有的嵌入式产品是足够用的。Cortex-M就利用了额外的空间实现了称为位带(Bit-Banding)操作的硬件属性,该技术使用地址空间的两个不同区域来指向同一物理地址。在主位带区域,每个地址对应一个字节的数据,在“位带别名”区域中,每个地址对应同一个数据的一个位。
 楼主| 结合国际经验 发表于 2022-2-24 15:21 | 显示全部楼层
如下图所示。在CM3的寄存器映射图中有1MB的 bit band区,这里被称为位带区,与之对应的是32MB的bit band别名区,这里被称为位带别名区。

637056217320a28a42.png
 楼主| 结合国际经验 发表于 2022-2-24 15:23 | 显示全部楼层
STM32的位带别名区会把位带区中的每一位膨胀成一个32位的字,所以相应的别名区的内存也会是位带区的32倍。从上图可以看出,位带操作同时支持SRAM和片上外设,支持位带操作的两个内存区域范围如下:

  1. SRAM区:0x20000000 ~ 0x200FFFFF,最低1M的范围;
  2. 片上外设区: 0x40000000 ~ 0x400FFFFF,最低1M的范围;
 楼主| 结合国际经验 发表于 2022-2-24 15:23 | 显示全部楼层
位带操作就是把位带区中一个地址的8个位分别映射到位带别名区的8个地址(LSB有效,即最低位有效),通过操作相应地址的方式实现操作某个位。以SRAM为例,位带区和位带别名区的映射如下图所示:
 楼主| 结合国际经验 发表于 2022-2-24 15:26 | 显示全部楼层
 楼主| 结合国际经验 发表于 2022-2-24 15:26 | 显示全部楼层
位带区里每个地址的每1位膨胀为别名区里一个32位的字(32位处理器中,1字=4字节),例如:0x20000000的第0位对应0x22000000,第1位对应0x22000004等。

 楼主| 结合国际经验 发表于 2022-2-24 15:39 | 显示全部楼层
2 位带操作的计算公式
既然位带操作属于Cortex-M内核的一部分,那么在Cortex-M官方手册也是给出了相应的计算公式的,其通用公式如下:

  1. 别名区地址 = 别名区起始地址 + (位字节地址偏移量 * 8 + n) * 4
 楼主| 结合国际经验 发表于 2022-2-24 15:40 | 显示全部楼层
其中,8表示一个字节有8位,4表示膨胀了4个字节,因此位带区和位带别名区也就是32倍的关系。
两个区的计算公式为:
  1. SRAM区:AliasAddr = 0x22000000 + (A - 0x20000000) * 32 + n * 4
  2. 片上外设区:AliasAddr = 0x42000000 + (A - 0x40000000)* 32 + n * 4
 楼主| 结合国际经验 发表于 2022-2-24 15:41 | 显示全部楼层
其中,AliasAddr是别名区的地址,A是位带区的地址,n是该端口的上的某一位。

接下来就是对这个地址进行操作了,写1,该位输出1,写0,就输出0。
 楼主| 结合国际经验 发表于 2022-2-24 15:49 | 显示全部楼层
3 位带操作代码实现
这里STM32F1为例,根据STM32的《RM0008 Reference manual》手册,其GPIO的地址映射如下:
433696217388154cec.png
 楼主| 结合国际经验 发表于 2022-2-24 15:50 | 显示全部楼层
GPIOx_ODR 寄存器如下:
8505621738cef03c9.png
 楼主| 结合国际经验 发表于 2022-2-24 15:51 | 显示全部楼层
每个寄存器32位,占4个地址,在访问或修改某个寄存器时,是从首地址开始的,逻辑运算则是直接可涵盖到32bit,offset 为 0x0C。GPIOA 的首地址为0x40010800,因此GPIOx_ODR 寄存器的地址为0x4001080C。则所有的GPIO映射如下:
  1. //IO口地址映射
  2. #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C
  3. #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C
  4. #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C
  5. #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C
  6. #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C
  7. #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C   
  8. #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C   

  9. #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808
  10. #define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08
  11. #define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008
  12. #define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408
  13. #define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808
  14. #define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08
  15. #define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08
 楼主| 结合国际经验 发表于 2022-2-24 15:52 | 显示全部楼层
上述只是位带区的地址,根据位带操作的计算公式,则操作位带别名区的地址方法如下:
  1. //IO口操作宏定义
  2. #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
  3. #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
  4. #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
 楼主| 结合国际经验 发表于 2022-2-24 15:53 | 显示全部楼层
以上代码的第一句是转换的关键,当然相对的前面的计算公式做了优化,也就是将SRAM和片上外设合并在一起。addr & 0XF0000000 得到SRAM和片上外设的首地址,然后加0x2000000表示位带别名区相对位带区的偏移量,(addr &0xFFFFF)<<5)和(bitnum<<2)就是前面“*32”和“*4”,只是换成了移位操作,因为移位操作相对乘法运算速度更快。
 楼主| 结合国际经验 发表于 2022-2-24 16:18 | 显示全部楼层
好了,接下来使用位带操作来写一个GPIO流水灯,同时使用库函数来做比较。

【main.c】
  1. /**
  2.   ******************************************************************************
  3.   * [url=home.php?mod=space&uid=288409]@file[/url]                main.c
  4.   * [url=home.php?mod=space&uid=187600]@author[/url]              BruceOu
  5.   * [url=home.php?mod=space&uid=70829]@lib[/url] version         V3.5.0
  6.   * [url=home.php?mod=space&uid=895143]@version[/url]             V1.0
  7.   * [url=home.php?mod=space&uid=212281]@date[/url]                2021-10-05
  8.   * [url=home.php?mod=space&uid=1333120]@blog[/url]                https://blog.bruceou.cn/
  9.   * @Official Accounts   嵌入式实验楼
  10.   * [url=home.php?mod=space&uid=247401]@brief[/url]               流水灯
  11.   ******************************************************************************
  12.   */
  13. /** Includes********************************************************************/
  14. #include "./LED/stm32f103_led.h"

  15. /*简单延时函数*/
  16. void Delay(u32 xms);

  17. /**
  18.   * @brief     主函数
  19.   * @param     None
  20.   * @retval   
  21.   */
  22. int main(void)
  23. {       
  24.         /* LED 初始化 */
  25.         LED_GPIO_Config();         

  26.         while (1)
  27.         {
  28. #if 0
  29.                 GPIO_SetBits(GPIOB,GPIO_Pin_0);                          // 亮
  30.                 Delay(0xfFfff);
  31.                 GPIO_ResetBits(GPIOB,GPIO_Pin_0);                  // 灭

  32.                 GPIO_SetBits(GPIOG,GPIO_Pin_6);                          // 亮
  33.                 Delay(0xfFfff);
  34.                 GPIO_ResetBits(GPIOG,GPIO_Pin_6);                  // 灭

  35.                 GPIO_SetBits(GPIOG,GPIO_Pin_7);                          // 亮
  36.                 Delay(0xffFff);
  37.                 GPIO_ResetBits(GPIOG,GPIO_Pin_7);                  // 灭
  38. #else
  39.                 PBout(0) = 1;                          // 亮
  40.                 Delay(0xfFfff);
  41.                 PBout(0) = 0;                    // 灭

  42.                 PGout(6) = 1;                          // 亮
  43.                 Delay(0xfFfff);
  44.                 PGout(6) = 0;                    // 灭

  45.                 PGout(7) = 1;                          // 亮
  46.                 Delay(0xffFff);
  47.                 PGout(7) = 0;                    // 灭
  48. #endif
  49.                
  50.         }
  51.                
  52. }

  53. /**
  54.   * @brief  延时函数
  55.   * @param  
  56.             xms 延时长度
  57.   * @retval None
  58.   */
  59. void Delay( u32 xms)       
  60. {
  61.         //for(; nCount != 0; nCount--);(方法一)
  62.         while(xms--);//(方法二)
  63. }
 楼主| 结合国际经验 发表于 2022-2-24 16:20 | 显示全部楼层
【stm32f103_led.c】
  1. /**
  2.   ******************************************************************************
  3.   * @file        stm32f103_led.c
  4.   * @author      BruceOu
  5.   * @lib version V3.5.0
  6.         * @version     V1.0
  7.   * @date        2021-10
  8.   * @brief       LED Init
  9.   ******************************************************************************
  10.   */
  11. /* Includes*********************************************************************/
  12. #include "./LED/stm32f103_led.h"

  13. /**
  14.   * @brief  初始化LED的GPIO
  15.   * @param  None
  16.   * @retval None
  17.   */
  18. void LED_GPIO_Config(void)
  19. {               
  20.                 /*定义一个GPIO_InitTypeDef类型的结构体*/
  21.                 GPIO_InitTypeDef GPIO_InitStructure;

  22.                 /*开启LED的外设时钟*/
  23.                 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE);

  24.                 /*设置IO口*/                                                                                  
  25.                 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置引脚模式为通用推挽输出
  26.                 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置引脚速率为50MHz

  27.                 /*调用库函数,初始化GPIOB0*/
  28.                 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;        //选择要控制的GPIOB引脚               
  29.                 GPIO_Init(GPIOB, &GPIO_InitStructure);       
  30.                                                                                           
  31.                 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;/*选择要控制的引脚*/               
  32.                 GPIO_Init(GPIOG, &GPIO_InitStructure);       
  33.                   
  34.                 /* 开启所有led灯        */
  35.                 GPIO_SetBits(GPIOB, GPIO_Pin_0);
  36.                 GPIO_SetBits(GPIOG, GPIO_Pin_6|GPIO_Pin_7);         
  37. }
 楼主| 结合国际经验 发表于 2022-2-24 16:22 | 显示全部楼层
【stm32f103_led.h】
  1. #ifndef _LED_H
  2. #define        _LED_H

  3. #include "stm32f10x.h"

  4. //位带操作,实现51类似的GPIO控制功能
  5. //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
  6. //IO口操作宏定义
  7. #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
  8. #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
  9. #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

  10. //IO口地址映射
  11. #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C
  12. #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C
  13. #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C
  14. #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C
  15. #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C
  16. #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C   
  17. #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C   

  18. #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808
  19. #define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08
  20. #define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008
  21. #define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408
  22. #define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808
  23. #define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08
  24. #define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08

  25. //IO口操作,只对单一的IO口!
  26. //确保n的值小于16!
  27. #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出
  28. #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入

  29. #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出
  30. #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入

  31. #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出
  32. #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入

  33. #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出
  34. #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入

  35. #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出
  36. #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

  37. #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出
  38. #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

  39. #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出
  40. #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

  41. void LED_GPIO_Config(void);

  42. #endif /* _LED_H */
 楼主| 结合国际经验 发表于 2022-2-24 16:58 | 显示全部楼层
不管使用哪种方式,其实验现象都是一样的,但是使用位带操作更方便些,操作者步骤更少,下面举例说明。

实例:欲设置地址 0x2000_0000 中的比特 2,则使用普通操作和位带操作的设置过程如下图所示:

23289621748a6e1b4b.png
 楼主| 结合国际经验 发表于 2022-2-24 17:09 | 显示全部楼层
普通操作和位带操作的汇编对比代码如下:
5628621748b88d2bf.png
您需要登录后才可以回帖 登录 | 注册

本版积分规则

66

主题

775

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部