请选择 进入手机版 | 继续访问电脑版

硬汉嵌入式论坛

 找回密码
 立即注册
查看: 5212|回复: 23
收起左侧

[GPIO] 推荐一种超简单的硬件位带bitband操作方法,让变量,寄存器控制,IO访问更便捷,无需用户计算位置(2021-12-10)

  [复制链接]

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
发表于 2021-11-10 23:00:19 | 显示全部楼层 |阅读模式
说明:

M3,M4内核都支持硬件位带操作,M7内核不支持。


硬件位带操作优势

优势1:

比如我们在地址0x2000 0000定义了一个变量unit8_t  a, 如果我们要将此变量的bit0清零,而其它bit不变。

a & = ~0x01

这个过程就需要读变量a,修改bit0,然后重新赋值给变量a,也就是读 - 修改 - 写经典三部曲,如果我们使用硬件位带就可以一步就完成,也就是所谓的原子操作,优势是不用担心中断或者RTOS任务打断。

优势2:
操作便捷,适合用于需要频繁操作修改的场合,移植性强。不频繁的直接标准库或者HAL库配置即可。


背景知识

这个点知道不知道都没有关系,不影响我们使用硬件位带,可以直接看下面案例的操作方法,完全不需要用户去了解。

位带操作就是对变量每个bit的操作,以M4内核的STM32F4为例:

(1)将1MB地址范围 0x20000000 - 0x200FFFFF  映射到32MB空间范围0x22000000 -  0x23FFFFFF ----> 这个对应STM32F4的通用RAM空间。

也就是说1MB空间每个bit都拓展为32bit来访问控制

1.png

下面这个图非常具有代表性。
0x20000000地址的字节变量 bit0 映射到0x22000000来控制。
0x20000000地址的字节变量 bit1 映射到0x22000004来控制。
0x20000000地址的字节变量 bit2 映射到0x22000008来控制。
..........依次类推

23.png


(2)将1MB地址范围 0x40000000 - 0x400FFFFF  映射到32MB空间范围0x42000000 -  0x43FFFFFF ----> 这个对应STM32F4的外设空间。

同样也是1MB空间每个bit都拓展为32bit来访问控制

2.png

(3)举例,比如访问0x2000 0010地址里面字节变量的bit2

那么实际要访问的就是:

bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)

0x22000208 = 0x22000000 + (0x10*32) + (2*4)

通过对地址空间0x22000208 进行赋值为0x01就表示bit2置位,赋值为0x00就表示bit2清零,对这个地址空间读取操作就可以反应bit2的数值。


超简单实现方案和四个经典案例

这种硬件未带让用户去使用非常不方便,还需要倒腾地址计算。

这里以MDK为例,提供一种IDE支持的,直接加后缀__attribute__((bitband))即可,对于M3和M4可以直接转换为硬件位带实现。

案例1:超简单控制RAM空间变量:

定义:
  1. typedef struct {
  2.   uint8_t bit0 : 1;
  3.   uint8_t bit1 : 1;
  4.   uint8_t bit2 : 1;
  5.   uint8_t bit3 : 1;        
  6.   uint8_t bit4 : 1;
  7.   uint8_t bit5 : 1;
  8.   uint8_t bit6 : 1;
  9.   uint8_t bit7 : 1;               
  10. } TEST __attribute__((bitband));

  11. TEST tTestVar;
复制代码

我们定义了一个8bit的变量tTestVar,控制每个bit的方法如下:
  1. tTestVar.bit0 = 1;
  2. tTestVar.bit1 = 1;        
  3. tTestVar.bit2 = 1;
  4. tTestVar.bit3 = 0;        
  5. tTestVar.bit4 = 0;
  6. tTestVar.bit5 = 1;        
  7. tTestVar.bit6 = 1;
  8. tTestVar.bit7 = 1;        
复制代码

看汇编,已经修改为硬件位带:

1234.png

案例2:超简单控制GPIO输入输出寄存器:

======================
注意:
芯片外设使用硬件位带,如果没有起作用的话,注意使用__attribute__((bitband))的正确姿势即可
https://www.armbbs.cn/forum.php?mod=viewthread&tid=110029
=====================

GPIO里面最常用的就是输入输出。

GPIO输出寄存器定义如下,每个bit控制一个IO引脚。

1.png

我们软件定义如下:
  1. typedef struct {
  2.   uint16_t ODR0 : 1;
  3.   uint16_t ODR1 : 1;
  4.   uint16_t ODR2 : 1;
  5.   uint16_t ODR3 : 1;        
  6.   uint16_t ODR4 : 1;
  7.   uint16_t ODR5 : 1;
  8.   uint16_t ODR6 : 1;
  9.   uint16_t ODR7 : 1;        
  10.   uint16_t ODR8 : 1;
  11.   uint16_t ODR9 : 1;
  12.   uint16_t ODR10 : 1;
  13.   uint16_t ODR11 : 1;        
  14.   uint16_t ODR12 : 1;
  15.   uint16_t ODR13 : 1;
  16.   uint16_t ODR14 : 1;
  17.   uint16_t ODR15 : 1;        
  18.   uint16_t Reserved : 16;        
  19. } GPIO_ORD  __attribute__((bitband));

  20. GPIO_ORD *GPIOA_ODR = (GPIO_ORD *)(&GPIOA->ODR);
  21. GPIO_ORD *GPIOB_ODR = (GPIO_ORD *)(&GPIOB->ODR);
  22. GPIO_ORD *GPIOC_ODR = (GPIO_ORD *)(&GPIOC->ODR);
  23. GPIO_ORD *GPIOD_ODR = (GPIO_ORD *)(&GPIOD->ODR);
  24. GPIO_ORD *GPIOE_ODR = (GPIO_ORD *)(&GPIOE->ODR);
  25. GPIO_ORD *GPIOF_ODR = (GPIO_ORD *)(&GPIOF->ODR);
  26. GPIO_ORD *GPIOJ_ODR = (GPIO_ORD *)(&GPIOJ->ODR);
  27. GPIO_ORD *GPIOK_ODR = (GPIO_ORD *)(&GPIOK->ODR);
复制代码

GPIO输入寄存器定义如下:

2.png

我们软件定义如下:
  1. typedef struct {
  2.   uint16_t IDR0 : 1;
  3.   uint16_t IDR1 : 1;
  4.   uint16_t IDR2 : 1;
  5.   uint16_t IDR3 : 1;        
  6.   uint16_t IDR4 : 1;
  7.   uint16_t IDR5 : 1;
  8.   uint16_t IDR6 : 1;
  9.   uint16_t IDR7 : 1;        
  10.   uint16_t IDR8 : 1;
  11.   uint16_t IDR9 : 1;
  12.   uint16_t IDR10 : 1;
  13.   uint16_t IDR11 : 1;        
  14.   uint16_t IDR12 : 1;
  15.   uint16_t IDR13 : 1;
  16.   uint16_t IDR14 : 1;
  17.   uint16_t IDR15 : 1;        
  18.   uint16_t Reserved : 16;        
  19. } GPIO_IDR __attribute__((bitband));

  20. GPIO_IDR *GPIOA_IDR = (GPIO_IDR *)(&GPIOA->IDR);
  21. GPIO_IDR *GPIOB_IDR = (GPIO_IDR *)(&GPIOB->IDR);
  22. GPIO_IDR *GPIOC_IDR = (GPIO_IDR *)(&GPIOC->IDR);
  23. GPIO_IDR *GPIOD_IDR = (GPIO_IDR *)(&GPIOD->IDR);
  24. GPIO_IDR *GPIOE_IDR = (GPIO_IDR *)(&GPIOE->IDR);
  25. GPIO_IDR *GPIOF_IDR = (GPIO_IDR *)(&GPIOF->IDR);
  26. GPIO_IDR *GPIOJ_IDR = (GPIO_IDR *)(&GPIOJ->IDR);
  27. GPIO_IDR *GPIOK_IDR = (GPIO_IDR *)(&GPIOK->IDR);
复制代码

实际操作效果动态,注意看调试状态寄存器变化,控制GPIOA的PIN0到PIN3

1.gif

案例3:超方便的寄存器修改:

比如定时器TIM1的CR寄存器:

1.png

我们的定义如下:
  1. typedef struct {
  2.   uint16_t CEN  : 1;
  3.   uint16_t UDIS : 1;
  4.   uint16_t URS  : 1;
  5.   uint16_t OPM  : 1;
  6.   uint16_t DIR  : 1;        
  7.   uint16_t CMS  : 2;
  8.   uint16_t APRE : 1;
  9.   uint16_t CKD  : 2;        
  10.   uint16_t Reserved : 6;        
  11. } TIM_CR1 __attribute__((bitband));

  12. TIM_CR1 *TIM1_CR1 = (TIM_CR1 *)(&TIM1->CR1);

复制代码

实际操作动态效果,注意看调试状态寄存器变化,设置TIM1 CR1寄存器的每个bit控制:

1.gif

由于标准库,HAL库配置这些已经非常方便了,我们再使用这种方式意义不是很大,但对于需要频繁操作的地方,这种方式就非常好使了,言简意赅,移植性强,强力推荐,而且是原子操作方式,不用怕中断打断。

案例4:应用进阶:
最后我们来个进阶,比如我们通过32位带宽的FMC总线扩展出来32个GPIO,如果我们采用如下使用方式就非常不直观

#define  HC574_PORT         *(uint32_t *)0x64001000

操作bit1 =0清零,就需要如下操作:

HC574_PORT &= ~(1<<1);

操作bit2和bit10置位,就需要如下操作:

HC574_PORT |= ( ( 1<< 2) | (1<<10))

这种操作会导致以后的代码修改非常不便,别人移植使用也非常不方便。如果我们改成如下方式,就方便太多了。

  1. typedef struct                                
  2. {
  3.         uint32_t tGPRS_TERM_ON : 1;   
  4.         uint32_t tGPRS_RESET :1;   
  5.         uint32_t tNRF24L01_CE :1;   
  6.         uint32_t tNRF905_TX_EN :1;  
  7.         uint32_t tNRF905_TRX_CE :1;

  8.         uint32_t tNRF905_PWR_UP :1;   
  9.         uint32_t tESP8266_G0 :1;  
  10.         uint32_t tESP8266_G2 :1;   
  11.                
  12.         uint32_t tLED1 :1;           
  13.         uint32_t tLED2  :1;         
  14.         uint32_t tLED3  :1;         
  15.         uint32_t tLED4 :1;           
  16.         uint32_t tTP_NRST   :1;      
  17.         uint32_t tAD7606_OS0  :1;   
  18.         uint32_t tAD7606_OS1  :1;   
  19.         uint32_t tAD7606_OS2  :1;   
  20.                
  21.         uint32_t tY50_0 :1;         
  22.         uint32_t tY50_1  :1;         
  23.         uint32_t tY50_2  :1;         
  24.         uint32_t tY50_3  :1;         
  25.         uint32_t tY50_4  :1;         
  26.         uint32_t tY50_5  :1;         
  27.         uint32_t tY50_6  :1;         
  28.         uint32_t tY50_7   :1;               

  29.         uint32_t tAD7606_RESET  :1;
  30.         uint32_t tAD7606_RANGE  :1;  
  31.         uint32_t tY33_2 :1;         
  32.         uint32_t tY33_3  :1;         
  33.         uint32_t tY33_4  :1;         
  34.         uint32_t tY33_5  :1;         
  35.         uint32_t tY33_6  :1;         
  36.         uint32_t tY33_7   :1;        
  37.       
  38. }FMCIO_ODR __attribute__((bitband));

  39. FMCIO_ODR *FMC_EXTIO = (FMCIO_ODR *)0x60001000;
复制代码

比如控制AD7606的OS0引脚高电平就是

FMC_EXTIO->tAD7606_OS0  = 1;

控制OS0引脚是低电平就是:
FMC_EXTIO->tAD7606_OS0  = 0;

简单易用,超方便。


M7内核为什么不支持
M内核权威指南作者Joseph Yiu回复:
1、Cache问题,如果SRAM所在区域开启了读写Cache,使用位带操作的话,会有数据一致性问题。
2、位带需要总线锁机制,在AHB总线协议中这相对容易实现,但在AXI总线协议中这有点混乱,并且在锁定序列期间,它可能导致其他总线主控的延迟更长。

QQ截图20211110214027.png



评分

参与人数 1金币 +20 收起 理由
missfox + 20 很给力!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2021-11-11 00:07:04 | 显示全部楼层
更新完毕。
回复

使用道具 举报

44

主题

554

回帖

691

积分

金牌会员

积分
691
发表于 2021-11-11 07:47:11 | 显示全部楼层
学习了,
回复

使用道具 举报

5

主题

519

回帖

534

积分

金牌会员

积分
534
发表于 2021-11-11 08:19:19 | 显示全部楼层
NICE................
回复

使用道具 举报

23

主题

1401

回帖

1470

积分

至尊会员

积分
1470
发表于 2021-11-11 09:48:39 | 显示全部楼层
不错不错,之前我还推荐过这种用法
代码不规范,亲人两行泪!
回复

使用道具 举报

8

主题

135

回帖

159

积分

初级会员

积分
159
发表于 2021-11-11 11:46:51 | 显示全部楼层
这样操作有硬件操作,不同于位域,快很多。
回复

使用道具 举报

1

主题

103

回帖

106

积分

初级会员

积分
106
发表于 2021-11-11 15:14:41 | 显示全部楼层
FMCIO_ODR *FMC_EXTIO = (FMCIO_ODR *)0x60001000;   这个地址是随便可以设置的不?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2021-11-12 09:06:54 | 显示全部楼层
西点钟灵毓秀 发表于 2021-11-11 15:14
FMCIO_ODR *FMC_EXTIO = (FMCIO_ODR *)0x60001000;   这个地址是随便可以设置的不?

这个是我们开发板上那个32bit带宽FMC设做的32路GPIO扩展
根据你的地址是什么随意设置。
回复

使用道具 举报

44

主题

554

回帖

691

积分

金牌会员

积分
691
发表于 2021-11-20 16:28:21 | 显示全部楼层
学习学习了,好好研究研究
回复

使用道具 举报

2

主题

16

回帖

22

积分

新手上路

积分
22
发表于 2021-11-21 23:19:41 | 显示全部楼层
请教一个,F1 能这样用吗?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2021-11-22 01:13:25 | 显示全部楼层
lfgcx 发表于 2021-11-21 23:19
请教一个,F1 能这样用吗?

没问题。
回复

使用道具 举报

116

主题

798

回帖

1146

积分

至尊会员

积分
1146
QQ
发表于 2021-11-29 15:11:46 | 显示全部楼层
如果定义了一个共用体,占用4个字节空间, 其中一个结构体是位操作, 定义了32位, 这样可以吗? 最近老是被这个共用体搞脑子
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2021-11-29 16:28:48 | 显示全部楼层
jcx0324 发表于 2021-11-29 15:11
如果定义了一个共用体,占用4个字节空间, 其中一个结构体是位操作, 定义了32位, 这样可以吗? 最近老是被这个 ...

这个__attribute__((bitband))不能用于union联合体。

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2021-12-10 09:13:24 | 显示全部楼层
更新案例2.
回复

使用道具 举报

330

主题

2022

回帖

3017

积分

版主

Rank: 7Rank: 7Rank: 7

积分
3017
发表于 2022-11-23 10:39:50 | 显示全部楼层
有一点没有明白,案列1,2,3,4中的结构体分别用uint8 、uint16,uint32来定义各自的bit。都是bit,却用不同的宽度的变量类型来定义?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2022-11-23 16:07:50 | 显示全部楼层
caicaptain2 发表于 2022-11-23 10:39
有一点没有明白,案列1,2,3,4中的结构体分别用uint8 、uint16,uint32来定义各自的bit。都是bit,却用不 ...

我这里是跟要访问的寄存器或者变量位宽一致定义的,比如那个定时器的寄存器TIM1的CR, 它是16bit的,我的位带里面的定义也是采用的这个。
回复

使用道具 举报

1

主题

103

回帖

106

积分

初级会员

积分
106
发表于 2023-9-19 17:40:09 | 显示全部楼层
__attribute__((bitband)); 里面的  bitband 是指什么?在哪个库里面了
回复

使用道具 举报

3

主题

91

回帖

100

积分

初级会员

积分
100
发表于 2023-9-20 09:19:30 | 显示全部楼层
西点钟灵毓秀 发表于 2023-9-19 17:40
__attribute__((bitband)); 里面的  bitband 是指什么?在哪个库里面了

C语言GNU拓展
回复

使用道具 举报

0

主题

59

回帖

59

积分

初级会员

积分
59
发表于 2023-9-20 17:39:01 | 显示全部楼层
位带操作是32位对应一位;对于寄存器中多位占位情况,它如何利用位带操作?是分成多步吗,像你举例中TMR控制寄存器中的 uint16_t CMS  : 2;,编译器会自动处理成什么样子
回复

使用道具 举报

13

主题

89

回帖

128

积分

初级会员

积分
128
发表于 2023-9-23 14:09:03 | 显示全部楼层
请问一下IAR是怎么实现的呀?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2023-9-24 11:20:00 | 显示全部楼层
qq1646544 发表于 2023-9-23 14:09
请问一下IAR是怎么实现的呀?

IAR没有找到对应的项。
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2023-9-24 11:21:31 | 显示全部楼层
zhousun 发表于 2023-9-20 17:39
位带操作是32位对应一位;对于寄存器中多位占位情况,它如何利用位带操作?是分成多步吗,像你举例中TMR控 ...

可以字,半字,字节操作。
回复

使用道具 举报

0

主题

59

回帖

59

积分

初级会员

积分
59
发表于 2023-9-25 21:48:00 | 显示全部楼层
eric2013 发表于 2023-9-24 11:21
可以字,半字,字节操作。

如果这样,那位带操作就没有优势了,因为位带只是对位操作,两位以上通过字节操作就是传统的读修改写的模式
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
 楼主| 发表于 2023-9-25 22:37:38 | 显示全部楼层
zhousun 发表于 2023-9-25 21:48
如果这样,那位带操作就没有优势了,因为位带只是对位操作,两位以上通过字节操作就是传统的读修改写的模 ...

是的,多bit定义的不行。这种的最好用户手动1个bit,1个bit定义,然后单个bit设置值。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-3-28 18:09 , Processed in 0.370997 second(s), 29 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表