推荐一种超简单的硬件位带bitband操作方法,让变量,寄存器控制,io访问更便捷,无需用户计算位置(代码片段)

果果小师弟 果果小师弟     2023-02-14     129

关键词:

摘要:51 单片机中通过关键字 sbit来实现定义,操作时除了被操作的那一位发生改变之外,其它位不受影响。不过在STM32里面就没有 sbit 关键字了,不能直接对寄存器的进行单个位操作,如果你想单独修改寄存器某一位的话,其实还是有办法的—位带操作。

说明: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来访问控制

下面这个图非常具有代表性。

0x20000000地址的字节变量 bit0 映射到0x22000000来控制。
0x20000000地址的字节变量 bit1 映射到0x22000004来控制。
0x20000000地址的字节变量 bit2 映射到0x22000008来控制。
..........依次类推


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

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

(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空间变量

定义:

typedef struct 
  uint8_t bit0 : 1;
  uint8_t bit1 : 1;
  uint8_t bit2 : 1;
  uint8_t bit3 : 1;        
  uint8_t bit4 : 1;
  uint8_t bit5 : 1;
  uint8_t bit6 : 1;
  uint8_t bit7 : 1;                
 TEST __attribute__((bitband));

TEST tTestVar;

我们定义了一个8bit的变量tTestVar,控制每个bit的方法如下:

tTestVar.bit0 = 1;
tTestVar.bit1 = 1;        
tTestVar.bit2 = 1;
tTestVar.bit3 = 0;        
tTestVar.bit4 = 0;
tTestVar.bit5 = 1;        
tTestVar.bit6 = 1;
tTestVar.bit7 = 1;        

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

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

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

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

typedef struct 
  uint16_t ODR0 : 1;
  uint16_t ODR1 : 1;
  uint16_t ODR2 : 1;
  uint16_t ODR3 : 1;        
  uint16_t ODR4 : 1;
  uint16_t ODR5 : 1;
  uint16_t ODR6 : 1;
  uint16_t ODR7 : 1;        
  uint16_t ODR8 : 1;
  uint16_t ODR9 : 1;
  uint16_t ODR10 : 1;
  uint16_t ODR11 : 1;        
  uint16_t ODR12 : 1;
  uint16_t ODR13 : 1;
  uint16_t ODR14 : 1;
  uint16_t ODR15 : 1;        
  uint16_t Reserved : 16;        
 GPIO_ORD  __attribute__((bitband));

GPIO_ORD *GPIOA_ODR = (GPIO_ORD *)(&GPIOA->ODR);
GPIO_ORD *GPIOB_ODR = (GPIO_ORD *)(&GPIOB->ODR);
GPIO_ORD *GPIOC_ODR = (GPIO_ORD *)(&GPIOC->ODR);
GPIO_ORD *GPIOD_ODR = (GPIO_ORD *)(&GPIOD->ODR);
GPIO_ORD *GPIOE_ODR = (GPIO_ORD *)(&GPIOE->ODR);
GPIO_ORD *GPIOF_ODR = (GPIO_ORD *)(&GPIOF->ODR);
GPIO_ORD *GPIOJ_ODR = (GPIO_ORD *)(&GPIOJ->ODR);
GPIO_ORD *GPIOK_ODR = (GPIO_ORD *)(&GPIOK->ODR);

GPIO输入寄存器定义如下:

我们软件定义如下:

typedef struct 
  uint16_t IDR0 : 1;
  uint16_t IDR1 : 1;
  uint16_t IDR2 : 1;
  uint16_t IDR3 : 1;        
  uint16_t IDR4 : 1;
  uint16_t IDR5 : 1;
  uint16_t IDR6 : 1;
  uint16_t IDR7 : 1;        
  uint16_t IDR8 : 1;
  uint16_t IDR9 : 1;
  uint16_t IDR10 : 1;
  uint16_t IDR11 : 1;        
  uint16_t IDR12 : 1;
  uint16_t IDR13 : 1;
  uint16_t IDR14 : 1;
  uint16_t IDR15 : 1;        
  uint16_t Reserved : 16;        
 GPIO_IDR __attribute__((bitband));

GPIO_IDR *GPIOA_IDR = (GPIO_IDR *)(&GPIOA->IDR);
GPIO_IDR *GPIOB_IDR = (GPIO_IDR *)(&GPIOB->IDR);
GPIO_IDR *GPIOC_IDR = (GPIO_IDR *)(&GPIOC->IDR);
GPIO_IDR *GPIOD_IDR = (GPIO_IDR *)(&GPIOD->IDR);
GPIO_IDR *GPIOE_IDR = (GPIO_IDR *)(&GPIOE->IDR);
GPIO_IDR *GPIOF_IDR = (GPIO_IDR *)(&GPIOF->IDR);
GPIO_IDR *GPIOJ_IDR = (GPIO_IDR *)(&GPIOJ->IDR);
GPIO_IDR *GPIOK_IDR = (GPIO_IDR *)(&GPIOK->IDR);

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

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

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

我们的定义如下:

typedef struct 
  uint16_t CEN  : 1;
  uint16_t UDIS : 1;
  uint16_t URS  : 1;
  uint16_t OPM  : 1;
  uint16_t DIR  : 1;        
  uint16_t CMS  : 2;
  uint16_t APRE : 1;
  uint16_t CKD  : 2;        
  uint16_t Reserved : 6;        
 TIM_CR1 __attribute__((bitband));

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

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

由于标准库,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))

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

typedef struct                                

        uint32_t tGPRS_TERM_ON : 1;   
        uint32_t tGPRS_RESET :1;   
        uint32_t tNRF24L01_CE :1;   
        uint32_t tNRF905_TX_EN :1;  
        uint32_t tNRF905_TRX_CE :1;

        uint32_t tNRF905_PWR_UP :1;   
        uint32_t tESP8266_G0 :1;  
        uint32_t tESP8266_G2 :1;   
               
        uint32_t tLED1 :1;           
        uint32_t tLED2  :1;         
        uint32_t tLED3  :1;         
        uint32_t tLED4 :1;           
        uint32_t tTP_NRST   :1;      
        uint32_t tAD7606_OS0  :1;   
        uint32_t tAD7606_OS1  :1;   
        uint32_t tAD7606_OS2  :1;   
               
        uint32_t tY50_0 :1;         
        uint32_t tY50_1  :1;         
        uint32_t tY50_2  :1;         
        uint32_t tY50_3  :1;         
        uint32_t tY50_4  :1;         
        uint32_t tY50_5  :1;         
        uint32_t tY50_6  :1;         
        uint32_t tY50_7   :1;               

        uint32_t tAD7606_RESET  :1;
        uint32_t tAD7606_RANGE  :1;  
        uint32_t tY33_2 :1;         
        uint32_t tY33_3  :1;         
        uint32_t tY33_4  :1;         
        uint32_t tY33_5  :1;         
        uint32_t tY33_6  :1;         
        uint32_t tY33_7   :1;        
       
FMCIO_ODR __attribute__((bitband));

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总线协议中这有点混乱,并且在锁定序列期间,它可能导致其他总线主控的延迟更长。

推荐一种超简单的硬件位带bitband操作方法,让变量,寄存器控制,io访问更便捷,无需用户计算位置(代码片段)

摘要:51单片机中通过关键字sbit来实现位定义,操作时除了被操作的那一位发生改变之外,其它位不受影响。不过在STM32里面就没有sbit关键字了,不能直接对寄存器的进行单个位操作,如果你想单独修改寄存... 查看详情

第二种bitband操作的方式-让ide来帮忙算地址

要使用Bitband来訪问外设,一定要得出相应的映射地址。人工计算肯定是不靠谱的,并且也没人想这么干。因此能够通过Excel,拉个列表来计算。想想,这也是一个不错的招数。可是后来想想,还是嫌麻烦,毕竟还是须要建立表... 查看详情

163vip邮箱怎么注册?两种超简单的注册方式介绍

怎么注册163vip邮箱?在这里,小编介绍两种注册vip邮箱的方式,希望对你有帮助。方法一1、在浏览器网址栏输入163.net,回车进入2、点击屏幕右侧的“立即注册按钮”3、选择套餐以及使用年限4、依次设置用户名、密码、再次输... 查看详情

有没有一种简单的方法可以让烧瓶中的会话超时?

】有没有一种简单的方法可以让烧瓶中的会话超时?【英文标题】:Isthereaneasywaytomakesessionstimeoutinflask?【发布时间】:2012-07-3118:03:55【问题描述】:我正在使用烧瓶建立一个网站,用户拥有帐户并能够登录。我正在使用烧瓶主体... 查看详情

有没有一种简单的方法可以让两个 jQuery 效果紧随其后?

】有没有一种简单的方法可以让两个jQuery效果紧随其后?【英文标题】:IsthereasimplewaytodotwojQueryeffectsrightaftereachother?【发布时间】:2016-10-2503:46:27【问题描述】:基本上,我有一个要应用highlight和bounce效果的div。如果我只做一个... 查看详情

有没有一种简单的方法可以让 unicode 在 python 中工作?

】有没有一种简单的方法可以让unicode在python中工作?【英文标题】:Isthereaneasywaytomakeunicodeworkinpython?【发布时间】:2012-09-1510:18:07【问题描述】:我正在尝试处理python2.7.2中的unicode。我知道有.encode(\'utf-8\')的东西,但是当我添... 查看详情

绘制简洁流程图的操作方法

...:  电脑  迅捷画图  实用系数:☆☆☆☆☆  推荐理由:该软件操作简单,只需简单拖动、摆放和点击,就可轻松制作精美的流程图和思维导图,方便好用  绘制简洁流程图操作方法介绍:  1. 查看详情

stm32中的位带(bit-band)操作

    支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在CM3中,有两个区中实现了位带。其中一个是SRAM区的最低1MB范围,第二个则是片内外设区的最低1MB范围。这两个区中的地址除了可... 查看详情

ntldrismissing最简单解决方法

...经过几十年以来的发展,计算机操作系统已经由一开始的简单控制循环体发展成为较为复杂的分布式操作系统,再加上计算机用户需求的愈发多样化,计算机操作系统已经成为既复杂而又庞大的计算机软件系统之一。参考技术A... 查看详情

有没有一种简单的方法可以让 emscripten 发出 wasm 而不是修改它的名字?

】有没有一种简单的方法可以让emscripten发出wasm而不是修改它的名字?【英文标题】:Isthereaneasywaytoaskemscriptenemitwasm&notmanglingitsnames?【发布时间】:2021-12-0414:49:38【问题描述】:这是我的C代码:#include<emscripten.h>#ifdef__cplus... 查看详情

vue操控dom

...么操作dom呢?以下是常用的三种方法:1、jQuery操作dom(推荐指数:★☆☆☆☆):只要拿jQuery的选择器,选中相应的dom进行操作就可以了,但是大家都知道jQuery获取元素是查找页面所有,相当于“循环”所有元素直至找到需要... 查看详情

一种简单的图像修复方法

该方法可以用于美颜中的祛斑,通过快速迭代的方式去除斑点。假设输入图像为:计算方向权重:对于其他方向,操作类似We。最终的输出为: 对要修复的区域,反复进行同样的操作即可。 简单的matlab仿真代码如下:clea... 查看详情

有没有一种简单的方法可以让 UITableView 通过披露指示器突出显示用户选择了哪一行?

】有没有一种简单的方法可以让UITableView通过披露指示器突出显示用户选择了哪一行?【英文标题】:isthereaneasywaytohaveaUITableViewhighlightwhichrowisselectedtoauserviaadisclosureindicator?【发布时间】:2011-11-0305:43:12【问题描述】:如果选择... 查看详情

计算机硬件介绍

...语言的作用及与操作系统和硬件的关系 : 编程是一种人与计算机之间沟通的语言,可以让计算机完成程序员所下达的工作;操作系统是一种软件,用来控制下层硬件。2.应用程序-》操作系统-》硬件: 应用程序被操... 查看详情

了解移动用户的隐私期望:一种基于推荐的crowdsourcing方法

...一篇论文,特写下总结,若有纰漏,还望指出。目录引言推荐机制1.1为什么要了解移动用户的隐私期望1、移动设备的广泛使用存在一些潜在的隐私威胁和信息泄漏。                           ... 查看详情

简单的推荐算法(代码片段)

...协同过滤算法是一种利用集体智慧的方法,它类似与朋友推荐,当你想要看一个电影时,你会去询问跟你有着相同喜好的人有没有自己没看过的好电影。这就是协同过滤的核心思想技术详述简介在开始讲解本次的前,我们先介绍... 查看详情

后端程序员就靠它吃饭了,推荐一份夯实基础的操作系统书单!

?学习编程,操作系统是你必须要掌握的基础知识,那么操作系统到底是什么呢?这还用说么,操作系统不就是Windows、Linux、Mac、IOS、Android、IOS这类我们天天都在用的东西么?此话不假,但是操作系统作为计算机科学里的一门基... 查看详情

hpc7000刀片,怎样安装操作系统

...显示器来安装。或者使用7000和面的OA管理中ILO远程安装。推荐使用ILO安装。方便一些。。如果你不懂可以拨800问。推荐让集成商或厂家的人来装。 查看详情