STM32F429第十二篇之位带操作

前言

本文主要介绍以位带操作为代表的F429寄存器的位操作方法。通常的寄存器位操作方法有与或方法,位带方法,还有struct位域方法,本文将一一介绍。本文主要以GPIO的输入输出寄存器为例,他们对应地址为:

//GPIO_ODR寄存器和GPIO_IDR寄存器地址
#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014 
#define GPIOJ_ODR_ADDr    (GPIOJ_BASE+20) //0x40022414
#define GPIOK_ODR_ADDr    (GPIOK_BASE+20) //0x40022814

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
#define GPIOJ_IDR_Addr    (GPIOJ_BASE+16) //0x40022410 
#define GPIOK_IDR_Addr    (GPIOK_BASE+16) //0x40022810 

本文参考文献为:

  • Joesph Yiu.ARM cortex-M3与cortex-M4权威指南(第三版).清华大学出版社
  • 刘火良,杨森.STM32库开发实战指南——基于STM32F4.机械工业出版社
  • ST.STM32 Cortex®-M4 MCUs and MPUs programming manual

与或操作

这是最通用的对寄存器的位操作方式。总共分成两个方式:

  1. 若需要将寄存器目标位置1,则用将该目标位与1进行或运算,其余位与0进行或运算。
  2. 若需要将寄存器目标位清0,则用该目标为与0进行与运算,其余位与1进行与运算。

通过上述操作,虽然是对整个寄存器进行运算,但是只有目标位才有可能发生变化。从而达到位操作的目的。该方式具有通用好的优点,基本上所有的寄存器都可以使用该方式实现位操作。

Struct 位域操作

该方法是C语言自身支持的方式,所以,该方法也具有通用性。

typedef struct
{
    unsigned int bit0 : 1;
    unsigned int bit1 : 1;
    unsigned int bit2 : 1;
    unsigned int bit3 : 1;
    unsigned int bit4 : 1;
    unsigned int bit5 : 1;
    unsigned int bit6 : 1;
    unsigned int bit7 : 1;
    unsigned int bit8 : 1;
    unsigned int bit9 : 1;
    unsigned int bit10 : 1;
    unsigned int bit11 : 1;
    unsigned int bit12 : 1;
    unsigned int bit13 : 1;
    unsigned int bit14 : 1;
    unsigned int bit15 : 1;
    unsigned int bit16 : 1;
    unsigned int bit17 : 1;
    unsigned int bit18 : 1;
    unsigned int bit19 : 1;
    unsigned int bit20 : 1;
    unsigned int bit21 : 1;
    unsigned int bit22 : 1;
    unsigned int bit23 : 1;
    unsigned int bit24 : 1;
    unsigned int bit25 : 1;
    unsigned int bit26 : 1;
    unsigned int bit27 : 1;
    unsigned int bit28 : 1;
    unsigned int bit29 : 1;
    unsigned int bit30 : 1;
    unsigned int bit31 : 1;
} BIT_TypeDef;

通过struct位域可以将32位寄存器每一位都表示出来。

然后,将需要位操作的寄存器地址与强制转换成BIT_TypeDef指针即可。可以参考博客<STM32F429第五篇之HAL库内存映射实现>。以GPIOB_ODR地址为例:

#define  IOB ((BIT_TypeDef*) GPIOB_ODR_Addr)//由于宏定义是直接替换,所以,为了不引起歧义,右边数据应该加括号。

最后,我们在可以如下调用指令,来实现位操作:

IOB->bit1 = 0;
delay_ms(500);                                     
IOB->bit1 = 1;
delay_ms(500);

位带操作

基本概念

利用位带(bit-banding)操作,一次可以访问寄存器的一位。在位带操作中,经常使用的术语为:

  • 位带区域:支持位带操作的存储器地址区域。
  • 位带别名:方位位带别名会对位带区域进行访问。

可以进行位带操作的存储器区域为:

  • 0x20000000-0x200FFFFF
  • 0x40000000-0x400FFFFF

通过博客<STM32F429第五篇之HAL库内存映射实现>,可以知道在外设中,支持位带操作的有:

  • APB1
  • APB2
  • AHB1

他们对应的位带别名分别为:

  • 0x22000000-0x23FFFFFF
  • 0x42000000-0x43FFFFFF

地址转换

位带区域中的一个位经过膨胀之后,在位带别名中,变成了4个字节。虽然看似空间浪费,但是,F429自身的总线系统时32位的,按照4个字节访问的是速度最快的。具体的转换方法为:
b i t _ w o r d _ a d d r = b i t _ b a n d _ b a s e + b y t e _ o f f s e t × 32 + b i t _ n u m b e r × 4 bit\_word\_addr=bit\_band\_base+byte_\_offset\times32+bit\_number\times 4 bit_word_addr=bit_band_base+byte_offset×32+bit_number×4
其中:
b i t _ w o r d _ a d d r bit\_word\_addr bit_word_addr:位带区域在位带别名中的映射地址。
b i t _ b a n d _ b a s e bit\_band\_base bit_band_base:位带别名的起始地址。
b y t e _ o f f s e t byte_\_offset byte_offset:位带区域中目标位所处的字节数。
b i t _ n u m b e r bit\_number bit_number:目标位所在字节中的位数。

如下图所示:
在这里插入图片描述

具体来说,外设位带映射地址可以表示为:
b i t _ w o r d _ a d d r = 0 x 42000000 + ( w o r d _ a d d r − 0 x 40000000 ) × 32 + b i t _ n u m b e r × 4 bit\_word\_addr=0x42000000+(word\_addr-0x40000000)\times 32+bit\_number\times 4 bit_word_addr=0x42000000+(word_addr0x40000000)×32+bit_number×4
类似地,可以知道SRAM位带别名区地址为:
b i t _ w o r d _ a d d r = 0 x 22000000 + ( w o r d _ a d d r − 0 x 20000000 ) × 32 + b i t _ n u m b e r × 4 bit\_word\_addr=0x22000000+(word\_addr-0x20000000)\times 32+bit\_number\times 4 bit_word_addr=0x22000000+(word_addr0x20000000)×32+bit_number×4
通过编程,可以将上述公式统一为:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 

编程实现

基本实现方式

//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口位带操作,只对单一的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 PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

#define PJout(n)   BIT_ADDR(GPIOJ_ODR_Addr,n)  //输出 
#define PJin(n)    BIT_ADDR(GPIOJ_IDR_Addr,n)  //输入

#define PKout(n)   BIT_ADDR(GPIOK_ODR_Addr,n)  //输出 
#define PKin(n)    BIT_ADDR(GPIOK_IDR_Addr,n)  //输入

另一种实现方式

在keil MDK-ARM 3.80开始,支持通过__attribute__((bitband))关键字来实现位带操作。具体的实现方式如下:

typedef struct
{
    unsigned int bit0 : 1;
    unsigned int bit1 : 1;
    unsigned int bit2 : 1;
    unsigned int bit3 : 1;
    unsigned int bit4 : 1;
    unsigned int bit5 : 1;
    unsigned int bit6 : 1;
    unsigned int bit7 : 1;
    unsigned int bit8 : 1;
    unsigned int bit9 : 1;
    unsigned int bit10 : 1;
    unsigned int bit11 : 1;
    unsigned int bit12 : 1;
    unsigned int bit13 : 1;
    unsigned int bit14 : 1;
    unsigned int bit15 : 1;
    unsigned int bit16 : 1;
    unsigned int bit17 : 1;
    unsigned int bit18 : 1;
    unsigned int bit19 : 1;
    unsigned int bit20 : 1;
    unsigned int bit21 : 1;
    unsigned int bit22 : 1;
    unsigned int bit23 : 1;
    unsigned int bit24 : 1;
    unsigned int bit25 : 1;
    unsigned int bit26 : 1;
    unsigned int bit27 : 1;
    unsigned int bit28 : 1;
    unsigned int bit29 : 1;
    unsigned int bit30 : 1;
    unsigned int bit31 : 1;    
} BB __attribute__((bitband));

该结构体定义方式和struct位域基本相同,只是最后添加__attribute__((bitband))关键字。

然后,将结构体与位域地址绑定。方式是使用__attribute__((at()))关键字。以GPIOB_ODR地址为例:

BB PBO __attribute__((at(GPIOB_ODR_Addr)));//定义结构体变量,且指定其所在的位带区域地址

同样的,为了提高程序通用性,可以写成:

BitBand PAin __attribute__((at((uint32_t)&(GPIOA->IDR))));
BitBand PBin __attribute__((at((uint32_t)&(GPIOB->IDR))));
BitBand PCin __attribute__((at((uint32_t)&(GPIOC->IDR))));
BitBand PDin __attribute__((at((uint32_t)&(GPIOD->IDR))));
BitBand PEin __attribute__((at((uint32_t)&(GPIOE->IDR))));
BitBand PFin __attribute__((at((uint32_t)&(GPIOF->IDR))));
BitBand PGin __attribute__((at((uint32_t)&(GPIOG->IDR))));

BitBand PAout __attribute__((at((uint32_t)&(GPIOA->ODR))));
BitBand PBout __attribute__((at((uint32_t)&(GPIOB->ODR))));
BitBand PCout __attribute__((at((uint32_t)&(GPIOC->ODR))));
BitBand PDout __attribute__((at((uint32_t)&(GPIOD->ODR))));
BitBand PEout __attribute__((at((uint32_t)&(GPIOE->ODR))));
BitBand PFout __attribute__((at((uint32_t)&(GPIOF->ODR))));
BitBand PGout __attribute__((at((uint32_t)&(GPIOG->ODR))));

最后,调用方式为:

PBO.bit0 = 0;
delay_ms(500);										//延时500ms
PBO.bit0 = 1;
delay_ms(500);                                      //延时500ms

GPIO置位寄存器

在GPIO中,对GPIO每一位都有对应的置位与清零寄存器,所以,通过对改变相应寄存器,即可达到位操作的目的。这种方式,HAL库已经实现,其源代码为:

/**
  * @brief  Sets or clears the selected data port bit.
  *
  * @note   This function uses GPIOx_BSRR register to allow atomic read/modify
  *         accesses. In this way, there is no risk of an IRQ occurring between
  *         the read and the modify access.
  *
  * @param  GPIOx: where x can be (A..K) to select the GPIO peripheral for STM32F429X device or
  *                      x can be (A..I) to select the GPIO peripheral for STM32F40XX and STM32F427X devices.
  * @param  GPIO_Pin: specifies the port bit to be written.
  *          This parameter can be one of GPIO_PIN_x where x can be (0..15).
  * @param  PinState: specifies the value to be written to the selected bit.
  *          This parameter can be one of the GPIO_PinState enum values:
  *            @arg GPIO_PIN_RESET: to clear the port pin
  *            @arg GPIO_PIN_SET: to set the port pin
  * @retval None
  */
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_PIN_ACTION(PinState));

  if(PinState != GPIO_PIN_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
  }
}

通过代码,可以了解,只需要对于GPIOx_BSRR的某一位写1,则将实现GPIO某一位的输出电平的变化。不过此方式只适用于外设GPIO,试用范围比较窄。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值