你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32开发项目:GPIO的位带操作

[复制链接]
STMCU小助手 发布时间:2022-4-14 10:32
背景介绍
位操作就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。51 单片机中通过关键字 sbit 来实现位定义,STM32-M3, M4内核的单片机中没有这样的关键字,而是通过访问位带别名区来实现位操作。

以STM32F407为例,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,另一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字( F407 的系统总线是 32 位的,按照 4 个字节访问的时候是最快的,所以膨胀成 4 个字节来访问是最高效的),当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。下图所示为STM32F407的位带地址区域。
LA)567I77JS}N]2CXZ][F46.png

外设位带区
的地址为:0x40000000~0x400F0000,大小为 1MB,这 1MB 的大小包含了 APB1/2 和 AHB1 上面所有外设的寄存器,AHB2/3 总线上的寄存器没有包括。AHB2 总线上的外设地址范围为:0x50000000~0x50060BFF,AHB3 总线上的外设地址范为:0xA0000000~0xA0000FFF。外设位带区经过膨胀后的位带别名区地址为:0x42000000~0x43FFFFFF,这部分地址空间为保留地址,没有跟任何的外设地址重合。

SRAM位带区的地址为:0x20000000~0x200F0000,大小为 1MB,经过膨胀后的位带别名区地址为:0x22000000~0x23FFFFFF,大小为 32MB。通过位带别名操作 SRAM 的比特位用得很少。

我们可以通过指针的形式访问位带别名区地址从而达到操作位带区比特位的效果。

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

  1. AliasAddr = 0x42000000+ (A-0x40000000)*8*4 +n*4
复制代码

0x42000000 是外设位带别名区的起始地址,0x40000000 是外设位带区的起始地址,(A-0x40000000)表示该比特前面有多少个字节,一个字节有 8 位,所以*8,一个位膨胀后是 4 个字节,所以*4,n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字节,所以也*4。

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

  1. AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4
复制代码

公式分析同上。

为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址+位序号”转换成别名区地址统一成一个宏。

  1. #define BITBAND(addr, bitnum) ((addr&0xF0000000)+0x2000000+((addr&0x000FFFFF)<<5)+(bitnum<<2))
复制代码

addr & 0xF0000000 是为了区别 SRAM 还是外设,实际效果就是取出 4 或者 2,如果是外设,则取出的是 4,+0x0200 0000 之后就等于 0x4200 0000,0x4200 0000 是外设别名区的起始地址。如果是 SRAM,则取出的是 2,+0x0200 0000 之后就等于 0x2200 0000,0x22000000 是 SRAM 别名区的起始地址。

addr & 0x000F FFFF 屏蔽了高三位,相当于减去 0x2000 0000 或者 0x4000 0000,但是为什么是屏蔽高三位?因为外设的最高地址是:0x2010 0000,跟起始地址 0x2000 0000 相减的时候,总是低 5 位才有效,所以干脆就把高三位屏蔽掉来达到减去起始地址的效果,具体屏蔽掉多少位跟最高地址有关。SRAM 同理分析即可。<<5 相当于*8*4 (2^5),<<2 相当于*4 (2^2)。

实现GPIO位带操作的源码如下:

  1. /*----Realization of bit-band operation of GPIO----*/
  2. #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))

  3. #define MEM_VAL(addr)  *((unsigned long  *)(addr))
  4. #define MEM_ADDR(addr)  ((unsigned long  *)addr)

  5. #define BIT_VAL(addr, bitnum)   MEM_VAL(BITBAND(addr, bitnum))
  6. #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

  7. //IO address mapping by value
  8. #define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x4001080C
  9. #define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40010C0C
  10. #define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x4001100C
  11. #define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x4001140C
  12. #define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x4001180C
  13. #define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40011A0C
  14. #define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40011E0C

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

  22. //Operation of single IO.
  23. //Note that n must less than 16.
  24. #define GPIOA_OUT(n)   BIT_VAL(GPIOA_ODR_Addr,n)  //Output
  25. #define GPIOA_IN(n)    BIT_VAL(GPIOA_IDR_Addr,n)  //Input

  26. #define GPIOB_OUT(n)   BIT_VAL(GPIOB_ODR_Addr,n)
  27. #define GPIOB_IN(n)    BIT_VAL(GPIOB_IDR_Addr,n)

  28. #define GPIOC_OUT(n)   BIT_VAL(GPIOC_ODR_Addr,n)
  29. #define GPIOC_IN(n)    BIT_VAL(GPIOC_IDR_Addr,n)

  30. #define GPIOD_OUT(n)   BIT_VAL(GPIOD_ODR_Addr,n)
  31. #define GPIOD_IN(n)    BIT_VAL(GPIOD_IDR_Addr,n)

  32. #define GPIOE_OUT(n)   BIT_VAL(GPIOE_ODR_Addr,n)
  33. #define GPIOE_IN(n)    BIT_VAL(GPIOE_IDR_Addr,n)

  34. #define GPIOF_OUT(n)   BIT_VAL(GPIOF_ODR_Addr,n)
  35. #define GPIOF_IN(n)    BIT_VAL(GPIOF_IDR_Addr,n)

  36. #define GPIOG_OUT(n)   BIT_VAL(GPIOG_ODR_Addr,n)
  37. #define GPIOG_IN(n)    BIT_VAL(GPIOG_IDR_Addr,n)
  38. //get address
  39. #define GPIOA_OUT_ADDR(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //Output
  40. #define GPIOA_IN_ADDR(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //Input

  41. #define GPIOB_OUT_ADDR(n)   BIT_ADDR(GPIOB_ODR_Addr,n)
  42. #define GPIOB_IN_ADDR(n)    BIT_ADDR(GPIOB_IDR_Addr,n)

  43. #define GPIOC_OUT_ADDR(n)   BIT_ADDR(GPIOC_ODR_Addr,n)
  44. #define GPIOC_IN_ADDR(n)    BIT_ADDR(GPIOC_IDR_Addr,n)

  45. #define GPIOD_OUT_ADDR(n)   BIT_ADDR(GPIOD_ODR_Addr,n)
  46. #define GPIOD_IN_ADDR(n)    BIT_ADDR(GPIOD_IDR_Addr,n)

  47. #define GPIOE_OUT_ADDR(n)   BIT_ADDR(GPIOE_ODR_Addr,n)
  48. #define GPIOE_IN_ADDR(n)    BIT_ADDR(GPIOE_IDR_Addr,n)

  49. #define GPIOF_OUT_ADDR(n)   BIT_ADDR(GPIOF_ODR_Addr,n)
  50. #define GPIOF_IN_ADDR(n)    BIT_ADDR(GPIOF_IDR_Addr,n)

  51. #define GPIOG_OUT_ADDR(n)   BIT_ADDR(GPIOG_ODR_Addr,n)
  52. #define GPIOG_IN_ADDR(n)    BIT_ADDR(GPIOG_IDR_Addr,n)
复制代码

使用指南
上述的源码中主要定义了四种宏(以GPIOA为例):
GPIOA_OUT(n),GPIOA的第n个引脚输出寄存器对应的值
GPIOA_OUT_ADDR(n),GPIOA的第n个引脚输出寄存器对应的位带地址
GPIOA_IN(n),GPIOA的第n个引脚输入寄存器对应的值
GPIOA_IN_ADDR(n),GPIOA的第n个引脚输入寄存器对应的位带地址

通过使用GPIOA的第n个引脚输出/输入寄存器对应的值的宏定义,可以方便的设定/获取GPIO端口的值。示例如下:

  1. #define SYS_LED                                                GPIOA_OUT(15)
  2. #define SYS_EXTI_0_STATE                        GPIOB_IN(0)

  3. SYS_LED = 1;                                                //点亮LED灯,设定PA15端口的值为1
  4. SYS_LED = 0;                                                //关闭LED灯,设定PA15端口的值为0

  5. uint8_t state = GPIOB_IN(0);                //获取PB0端口的值
复制代码

有时候我们需要需要顺序操作多个GPIO端口的值,一种便捷的方法就是构造GPIO位带操作指针数组,然后通过数组的标号枚举操作每个GPIO端口。示例如下:

  1. #define CD4066_1_E1_ADDR                        GPIOA_OUT_ADDR(4)
  2. #define CD4066_2_E1_ADDR                        GPIOA_OUT_ADDR(1)
  3. #define CD4066_3_E1_ADDR                        GPIOA_OUT_ADDR(0)
  4. #define CD4066_1_E2_ADDR                        GPIOE_OUT_ADDR(8)
  5. #define CD4066_2_E2_ADDR                        GPIOE_OUT_ADDR(7)
  6. #define CD4066_3_E2_ADDR                        GPIOB_OUT_ADDR(1)

  7. #define CD4052_A1_ADDR                                GPIOE_OUT_ADDR(3)
  8. #define CD4052_B1_ADDR                                GPIOE_OUT_ADDR(4)
  9. #define CD4052_INH1_ADDR                        GPIOE_OUT_ADDR(5)

  10. #define CD4052_A2_ADDR                                GPIOC_OUT_ADDR(4)
  11. #define CD4052_B2_ADDR                                GPIOC_OUT_ADDR(5)
  12. #define CD4052_INH2_ADDR                        GPIOB_OUT_ADDR(0)

  13. #define WAS3157B_SEL1_ADDR                        GPIOE_OUT_ADDR(2)
  14. #define WAS3157B_SEL2_ADDR                        GPIOA_OUT_ADDR(7)

  15. /**
  16. * @brief 位带操作的端口指针 数组
  17. *                 设置这个数组的目的是方便在for循环中顺序操作端口的高低电平
  18. */
  19. unsigned long  *PortArray_Addr[] =
  20. {
  21.                 CD4066_1_E1_ADDR,
  22.                 CD4066_2_E1_ADDR,
  23.                 CD4066_3_E1_ADDR,
  24.                 CD4066_1_E2_ADDR,
  25.                 CD4066_2_E2_ADDR,
  26.                 CD4066_3_E2_ADDR,
  27.                 CD4052_A1_ADDR,
  28.                 CD4052_B1_ADDR,
  29.                 CD4052_INH1_ADDR,
  30.                 CD4052_A2_ADDR,
  31.                 CD4052_B2_ADDR,
  32.                 CD4052_INH2_ADDR,
  33.                 WAS3157B_SEL1_ADDR,
  34.                 WAS3157B_SEL2_ADDR,
  35. };

  36. /**
  37. * @brief 刷新线圈寄存器,根据线圈寄存器的值执行对应动作
  38. *                 将GPIO端口的输出状态设置为线圈寄存器对应变量的值
  39. * @param none
  40. * @retval none
  41. */
  42. void User_RefreshCoilsRegister()
  43. {
  44.         /*省略无关代码*/
  45.         
  46.         for(uint8_t i = 0; i < sizeof(PortArray_Addr)/sizeof(*PortArray_Addr); i++)
  47.         {
  48.                 *PortArray_Addr<i> = CoilsReg_GetBit(4 + i);
  49.         }
  50.         
  51.         /*省略无关代码*/
  52. }
  53. </i>
复制代码

在某些项目中,某些功能是数个GPIO端口操作的集合,同时这些功能又在每个通道中重复一次,这个时候如果为每个功能都定义一个函数会比较麻烦。可以采用带参数的宏定义来处理多通道中的GPIO端口操作的集合。示例如下:

  1. //定义位带操作的端口指针数组(每个变量都是多个通道同一个功能控制端口的集合)
  2. unsigned long *CD4066_1E_PortArray_Addr[] = { CD4066_1E_1_ADDR, CD4066_1E_2_ADDR };
  3. unsigned long *CD4066_2E_PortArray_Addr[] = { CD4066_2E_1_ADDR, CD4066_2E_2_ADDR };
  4. unsigned long *CD4066_3E_PortArray_Addr[] = { CD4066_3E_1_ADDR, CD4066_3E_2_ADDR };
  5. unsigned long *CD4052_A_PortArray_Addr[] = { CD4052_A1_ADDR, CD4052_A2_ADDR };
  6. unsigned long *CD4052_B_PortArray_Addr[] = { CD4052_B1_ADDR, CD4052_B2_ADDR };
  7. unsigned long *CD4052_INH_PortArray_Addr[] = { CD4052_INH1_ADDR, CD4052_INH2_ADDR };
  8. unsigned long *WAS3157B_SEL_PortArray_Addr[] = { WAS3157B_SEL1_ADDR, WAS3157B_SEL2_ADDR };

  9. //采用带参数的宏定义来处理多通道中的GPIO端口操作的集合,从而避免麻烦的函数声明与定义
  10. #define SENSOR_FUN_AcceToVelo(n)                *CD4052_INH_PortArray_Addr[n] = 0;\
  11.                                                                                 *CD4052_A_PortArray_Addr[n] = 0;\
  12.                                                                                 *CD4052_B_PortArray_Addr[n] = 1;\
  13.                                                                                 *WAS3157B_SEL_PortArray_Addr[n] = 1

  14. #define SENSOR_FUN_AcceToDisp(n)                *CD4052_INH_PortArray_Addr[n] = 0;\
  15.                                                                                 *CD4052_A_PortArray_Addr[n] = 0;\
  16.                                                                                 *CD4052_B_PortArray_Addr[n] = 0;\
  17.                                                                                 *WAS3157B_SEL_PortArray_Addr[n] = 0

  18. #define SENSOR_FUN_VeloToVelo(n)                *CD4052_INH_PortArray_Addr[n] = 0;\
  19.                                                                                 *CD4052_A_PortArray_Addr[n] = 0;\
  20.                                                                                 *CD4052_B_PortArray_Addr[n] = 0;\
  21.                                                                                 *WAS3157B_SEL_PortArray_Addr[n] = 1

  22. #define SENSOR_FUN_VeloToDisp(n)                *CD4052_INH_PortArray_Addr[n] = 0;\
  23.                                                                                 *CD4052_A_PortArray_Addr[n] = 1;\
  24.                                                                                 *CD4052_B_PortArray_Addr[n] = 0;\
  25.                                                                                 *WAS3157B_SEL_PortArray_Addr[n] = 0

  26. #define SENSOR_FUN_DispToDisp(n)                *CD4052_INH_PortArray_Addr[n] = 0;\
  27.                                                                                 *CD4052_A_PortArray_Addr[n] = 0;\
  28.                                                                                 *CD4052_B_PortArray_Addr[n] = 1;\
  29.                                                                                 *WAS3157B_SEL_PortArray_Addr[n] = 0

  30. //在业务处理函数中,可以像调用函数那样调用宏定义
  31. void User_SensorFunctionHandler(uint8_t chan, uint8_t fun)
  32. {
  33.         switch(fun)
  34.         {
  35.         case 0:
  36.                 SENSOR_FUN_AcceToVelo(chan);
  37.                 break;
  38.         case 1:
  39.                 SENSOR_FUN_AcceToDisp(chan);
  40.                 break;
  41.         case 2:
  42.                 SENSOR_FUN_VeloToVelo(chan);
  43.                 break;
  44.         /*省略部分代码*/
  45.         default:
  46.                 break:
  47.         }
  48. }



复制代码

收藏 评论0 发布时间:2022-4-14 10:32

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版