详解Cortex-M位带操作

发布时间:2024-05-06  

我相信很多朋友在学习单片机之前都学习过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:UsersBruceOuDesktop20191203162525194.png

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

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

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

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

C:UsersBruceOuDesktop�05xOcwJzy7gW7umetS44.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)

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>