0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详解Cortex-M位带操作

嵌入式大杂烩 来源:嵌入式大杂烩 作者:嵌入式大杂烩 2023-04-27 15:03 次阅读

我相信很多朋友在学习单片机之前都学习过51单片机,假设在51单片机的P1.1的IO口上挂了一个LED,那么你单独对LED的操作就是P1.1 = 0或P1.1 = 1,这样你就可以单独的对P1端的第一个IO口进行上下拉操作,然而对于STM32,是没有这种操作的,那么为了像51单片机一样能够单独的对某个端的某一个IO单独操作,就引入了__位带操作__,简而言之,就是为了去单独操作STM32里面PA的第1个IO口,所以才有了位带这样的操作机制。

1 什么是位带操作

在讲解位带操作之前,首先要搞清楚什么是位带操作。我们知道,32位的处理器的32位地址总线提供了4G的地址空间,几乎所有的嵌入式产品是足够用的。 Cortex-M就利用了额外的空间实现了称为位带(Bit-Banding)操作的硬件属性,该技术使用地址空间的两个不同区域来指向同一物理地址 。在主位带区域,每个地址对应一个字节的数据,在“位带别名”区域中,每个地址对应同一个数据的一个位。

如下图所示。在CM3的寄存器映射图中有1MB的 bit band区,这里被称为位带区,与之对应的是32MB的bit band别名区,这里被称为位带别名区。

C:\\Users\\BruceOu\\Desktop\\20191203162525194.png

STM32的位带别名区会把位带区中的每一位膨胀成一个32位的字,所以相应的别名区的内存也会是位带区的32倍。从上图可以看出,位带操作同时支持SRAM和片上外设,支持位带操作的两个内存区域范围如下:

SRAM区:0x20000000 ~ 0x200FFFFF,最低1M的范围;

片上外设区: 0x40000000 ~ 0x400FFFFF,最低1M的范围;

位带操作就是把位带区中一个地址的8个位分别映射到位带别名区的8个地址(LSB有效,即最低位有效),通过操作相应地址的方式实现操作某个位。以SRAM为例,位带区和位带别名区的映射如下图所示:

C:\\Users\\BruceOu\\Desktop\\005xOcwJzy7gW7umetS44.png

位带区里每个地址的每1位膨胀为别名区里一个32位的字(32位处理器中,1字=4字节),例如:0x20000000的第0位对应0x22000000,第1位对应0x22000004等。

2 位带操作的计算公式

既然位带操作属于Cortex-M内核的一部分,那么在Cortex-M官方手册也是给出了相应的计算公式的,其通用公式如下:

别名区地址 = 别名区起始地址 + (位字节地址偏移量 * 8 + n) * 4

其中,8表示一个字节有8位,4表示膨胀了4个字节,因此位带区和位带别名区也就是32倍的关系。

两个区的计算公式为:

SRAM区:AliasAddr = 0x22000000 + (A - 0x20000000) * 32 + n * 4

片上外设区:AliasAddr = 0x42000000 + (A - 0x40000000)* 32 + n * 4

其中,AliasAddr是别名区的地址,A是位带区的地址,n是该端口的上的某一位。

接下来就是对这个地址进行操作了,写1,该位输出1,写0,就输出0。

3 位带操作代码实现

这里STM32F1为例,根据STM32的《RM0008 Reference manual》手册,其GPIO的地址映射如下:

1682578697232m0vpen34s4

GPIOx_ODR 寄存器如下:

1682578697691w447n9kidh

每个寄存器32位,占4个地址,在访问或修改某个寄存器时,是从首地址开始的,逻辑运算则是直接可涵盖到32bit,offset 为 0x0C。GPIOA 的首地址为0x40010800,因此GPIOx_ODR 寄存器的地址为0x4001080C。则所有的GPIO映射如下:

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

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08

上述只是位带区的地址,根据位带操作的计算公式,则操作位带别名区的地址方法如下:

//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

以上代码的第一句是转换的关键,当然相对的前面的计算公式做了优化,也就是将SRAM和片上外设合并在一起。addr & 0XF0000000 得到SRAM和片上外设的首地址,然后加0x2000000表示位带别名区相对位带区的偏移量,(addr &0xFFFFF)<<5)和(bitnum<<2)就是前面“*32”和“*4”,只是换成了移位操作,因为移位操作相对乘法运算速度更快。

好了,接下来使用位带操作来写一个GPIO流水灯,同时使用库函数来做比较。

【main.c】

/* Includes ------------------------------------------------------------------*/
#include "stm32f1_bsp_led.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/*简单延时函数*/
void Delay(uint32_t xms); 

/* Private functions ---------------------------------------------------------*/

/**
  * @brief     主函数
  * @param     None
  * @retval    
  */
int main(void)
{
    /* LED 初始化 */
    LED_GPIO_Config();
 
    while (1)
    {
#if 0
        GPIO_SetBits(GPIOB,GPIO_Pin_0);  // 亮
        Delay(0xfFfff);
        GPIO_ResetBits(GPIOB,GPIO_Pin_0);  // 灭

        GPIO_SetBits(GPIOG,GPIO_Pin_6);  // 亮
        Delay(0xfFfff);
        GPIO_ResetBits(GPIOG,GPIO_Pin_6);  // 灭

        GPIO_SetBits(GPIOG,GPIO_Pin_7);  // 亮
        Delay(0xffFff);
        GPIO_ResetBits(GPIOG,GPIO_Pin_7);  // 灭
#else
        PBout(0) = 1;  // 亮
        Delay(0xfFfff);
        PBout(0) = 0;  // 灭

        PGout(6) = 1;  // 亮
        Delay(0xfFfff);
        PGout(6) = 0;  // 灭

        PGout(7) = 1;  // 亮
        Delay(0xffFff);
        PGout(7) = 0;  // 灭
#endif

    }
}

/**
  * @brief  延时函数
  * @param  
            xms 延时长度
  * @retval None
  */
void Delay( uint32_t xms)
{
    //for(; nCount != 0; nCount--);(方法一)
    while(xms--);//(方法二)
}

【stm32f1_bsp_led.c】

/* Includes ------------------------------------------------------------------*/
#include "stm32f1_bsp_led.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/**
  * @brief  初始化LED的GPIO
  * @param  None
  * @retval None
  */
void LED_GPIO_Config(void)
{
    /*定义一个GPIO_InitTypeDef类型的结构体*/
    GPIO_InitTypeDef GPIO_InitStructure;

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

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

    /*调用库函数,初始化GPIOB0*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  //选择要控制的GPIOB引脚
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;/*选择要控制的引脚*/
    GPIO_Init(GPIOG, &GPIO_InitStructure);

    /* 开启所有led灯*/
    GPIO_SetBits(GPIOB, GPIO_Pin_0);
    GPIO_SetBits(GPIOG, GPIO_Pin_6|GPIO_Pin_7);	 
}

【stm32f1_bsp_led.h】

#ifndef __STM32F1_BSP_LED_H__
#define __STM32F1_BSP_LED_H__

#ifdef __cplusplus
 extern "C" {
#endif 

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"

/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

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

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

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

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

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

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

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

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


#define ON  1
#define OFF 0

/* 带参宏,可以像内联函数一样使用 */
#define LED1(a)    if (a)    \\
                    GPIO_SetBits(GPIOB,GPIO_Pin_0);\\
                    else        \\
                    GPIO_ResetBits(GPIOB,GPIO_Pin_0)

#define LED2(a)    if (a)    \\
                    GPIO_SetBits(GPIOG,GPIO_Pin_6);\\
                    else        \\
                    GPIO_ResetBits(GPIOG,GPIO_Pin_6)

#define LED3(a)    if (a)    \\
                    GPIO_SetBits(GPIOG,GPIO_Pin_7);\\
                    else        \\
                    GPIO_ResetBits(GPIOG,GPIO_Pin_7)


/* 直接操作寄存器的方法控制IO */
#define    digitalHi(p,i)            {p->BSRR=i;}            //设置为高电平        
#define    digitalLo(p,i)            {p->BRR=i;}             //输出低电平
#define    digitalToggle(p,i)        {p->ODR ^=i;}           //输出反转状态


/* 定义控制IO的宏 */
#define LED1_TOGGLE        digitalToggle(GPIOB,GPIO_Pin_0)
#define LED1_ON            digitalHi(GPIOB,GPIO_Pin_0)
#define LED1_OFF           digitalLo(GPIOB,GPIO_Pin_0)

#define LED2_TOGGLE        digitalToggle(GPIOC,GPIO_Pin_4)
#define LED2_ON            digitalHi(GPIOG,GPIO_Pin_6)
#define LED2_OFF           digitalLo(GPIOG,GPIO_Pin_6)

#define LED3_TOGGLE        digitalToggle(GPIOC,GPIO_Pin_3)
#define LED3_ON            digitalHi(GPIOG,GPIO_Pin_7)
#define LED3_OFF           digitalLo(GPIOG,GPIO_Pin_7)
/* Exported functions ------------------------------------------------------- */
void LED_GPIO_Config(void);

#ifdef cplusplus
}
#endif

#endif /* __STM32F1_BSP_LED_H__ */

不管使用哪种方式,其实验现象都是一样的,但是使用位带操作更方便些,操作者步骤更少,下面举例说明。

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

1682578698223h4kgh19i0c

普通操作和位带操作的汇编对比代码如下:

1682578698688e3821chru8

位带读操作相对简单,普通操作和位带操作的设置过程如下图所示:

1682578699026zwj46gliu8

普通操作和位带操作的汇编对比代码如下:

1682578699376yo56mm73ev

可以看出位带操作的步骤更少,相对普通操作更简洁。

而且位带操作属于原子操作,在多任务系统中,位带操作可以解决共享资源中的紊乱危象,关于该部分内容可以参看《Cortex-M3权威指南》。

__总的来说,位带的主要优点__是数据的一个单独位可以通过一条指令来读或者写,而不需要操作一些利的寄存器。例如,一条从位带别名区域地址进行读操作的LDR指令会将值0或者1加1载入寄存器。类似的,一条STR指令在向位带别名区的地址写入时,只是修改主区域中数据的一位。当然修改需要由硬件来执行读写操作,但是只有一条指令(STR)被取指并执行。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    19265

    浏览量

    229670
  • 单片机
    +关注

    关注

    6035

    文章

    44553

    浏览量

    634724
  • 嵌入式
    +关注

    关注

    5082

    文章

    19111

    浏览量

    304845
  • 寄存器
    +关注

    关注

    31

    文章

    5336

    浏览量

    120244
  • Cortex-M
    +关注

    关注

    2

    文章

    229

    浏览量

    29752
收藏 人收藏

    评论

    相关推荐

    Cortex-M 系列处理特点和区别详解

    3ARM Cortex-M4“8/16 ”应用“16/32 ”应用“32 /DSC”应用低成本和简单性性能效率有效的数字信号控制Cortex-
    发表于 01-14 10:13

    带操作原理详解+LED实验的相关资料分享

    【嵌入式系统】带操作原理详解+LED实验解读1、带操作的实质
    发表于 12-16 07:06

    带操作的相关资料推荐

    注意:本文中关于STM32的带操作原理只适用于Cortex-M3和Cortex-M4(F)内核处理器,Cortex-M系列的其他内核处理器
    发表于 02-07 09:24

    关于Cortex-M 调试应用的介绍

    Cortex-M 调试应用
    的头像 发表于 07-10 00:56 2618次阅读

    快速理解STM32带操作原理

    作者:strongerHuang 说到位带操作,可能很多人比较陌生,但说到控制IO,你肯定不会陌生。有的项目为了最大效率控制IO,使用带操作。下面就来简单说说未带操作的内容。 一、初
    的头像 发表于 09-03 15:40 4723次阅读
    快速理解STM32<b class='flag-5'>位</b><b class='flag-5'>带操作</b>原理

    Cortex-M跑Linux操作系统能行吗

    单片机、Cortex-M、Linux它们和嵌入式有什么区别? 跑 Linux 操作系统需要什么处理器?ARM9、ARM11? Cortex-M比ARM9更新,为什么不能跑Linux? 相信很多小伙伴
    的头像 发表于 07-19 09:35 1933次阅读
    <b class='flag-5'>Cortex-M</b>跑Linux<b class='flag-5'>操作</b>系统能行吗

    Cortex-M可以跑Linux操作系统吗?

    Cortex-M可以跑Linux操作系统吗?
    发表于 12-01 11:36 2次下载
    <b class='flag-5'>Cortex-M</b>可以跑Linux<b class='flag-5'>操作</b>系统吗?

    CortexM3内核学习笔记(二):带操作

    概念Cortex-M3的存储器系统支持所谓的“带”(bit‐band)操作。通过它,实现了对单一比特的原子操作
    发表于 12-01 12:51 6次下载
    <b class='flag-5'>Cortex</b>‐<b class='flag-5'>M</b>3内核学习笔记(二):<b class='flag-5'>位</b><b class='flag-5'>带操作</b>

    STM32学习笔记:带操作(Bit_band Operations)

    注意:本文中关于STM32的带操作原理只适用于Cortex-M3和Cortex-M4(F)内核处理器,Cortex-M系列的其他内核处理器
    发表于 12-04 12:36 0次下载
    STM32学习笔记:<b class='flag-5'>位</b><b class='flag-5'>带操作</b>(Bit_band Operations)

    初识“带操作

    目录初识“带操作”什么是“带操作”?STM32的“带操作”为何会出现?STM32“
    发表于 01-12 17:18 0次下载
    初识“<b class='flag-5'>位</b><b class='flag-5'>带操作</b>”

    STM32带操作-详解-计算过程

    前言这篇文章主要用来讲解STM32中的带操作,学习过51单片机的应改了解,在控制51单片机IO引脚时,只需要向某一个IO口赋值就可以实现,对应IO口的输出高或地。那么STM32可以不可以像51
    发表于 01-17 10:43 5次下载
    STM32<b class='flag-5'>位</b><b class='flag-5'>带操作</b>-<b class='flag-5'>详解</b>-计算过程

    STM32F407入门开发: 带操作

    STM32F407的带操作可以实现类似51单片机中寄存器的操作方法,操作GPIO口代码简洁方便。 关于段的
    的头像 发表于 05-28 13:50 3660次阅读
    STM32F407入门开发: <b class='flag-5'>位</b><b class='flag-5'>带操作</b>

    Cortex-M 内核中断/异常系统、中断优先级/嵌套 详解

    Cortex-M 内核中断/异常系统、中断优先级/嵌套 详解
    的头像 发表于 09-27 15:29 2185次阅读
    <b class='flag-5'>Cortex-M</b> 内核中断/异常系统、中断优先级/嵌套 <b class='flag-5'>详解</b>

    Cortex-M带操作的原理

    Cortex-M带操作的原理
    的头像 发表于 10-24 15:27 881次阅读
    <b class='flag-5'>Cortex-M</b><b class='flag-5'>位</b><b class='flag-5'>带操作</b>的原理

    stm32带操作有什么用

    STM32带操作是一种在ARM Cortex-M微控制器中使用的特殊技术,它允许同时处理多个位,并且可以提高代码效率和性能。在这篇文章中,我将详细介绍STM32
    的头像 发表于 12-22 16:02 1387次阅读