历史上的今天
返回首页

历史上的今天

今天是:2024年12月23日(星期一)

正在发生

2021年12月23日 | STM32F429--位带操作

2021-12-23 来源:eefocus

一、位带操作的原理

51单片机可以直接对某一位IO进行读写操作,而STM32则通过位带操作来控制一个单独的IO口。


概念


位带区:支持位带操作的地址区。

位带别名区:对别名地址的访问最终作用到位带区的访问上。位带别名区对位带区的访问有个地址映射过程。


目的

对位带区的比特位进行独立的读写操作,即单独操作一个位 ,它是通过对位带别名区的操作来实现。


具体过程:

对位带别名区进行读写访问,位带别名区通过地址映射关系映射到相应的位带区,对位带区进行原始比特的读写操作。

   地址映射

对位带别名区进行读且操作  ---------------------->对位带区进行读且操作(目的)


下面是位带操作的一些说明

在这里插入图片描述
在这里插入图片描述

二、计算公式

字节地址为A,比特位序号为n(0<=n<=7)

说明:“*4”表示一个字为4个字节,“*8”表示一个字节中有8个比特。

在这里插入图片描述

将上述的外设位带区和SRAM位带区进行统一,得到下面的公式:


((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))


其中4*8=32=2的5次方,4=2的2次方,相当于左移了5位和2位

公式解析


add &0xF000 0000:目的是取出4和2,用于区分是外设还是SRAM。然后再加上0x2000000就等于外设/SRAM位带别名区的起始地址。

add &0x000FFFFF:屏蔽掉高3位。外设位带区的最高地址为0x400F0000,SRAM位带区的最高地址为0x200F 0000,(0x400F 0000 - 0X4000 0000)与(0x200F 0000-0x2000 0000)在求偏移地址相减的时候只有低五位有效,所以就把剩下的高3位屏蔽掉,剩下的5位和F做与运算即可。


三、举例

假如要操作GPIOH的ODR寄存器的10位,就可以通过位带操作来访问


//1.宏定义ADDR

#define GPIOH_ODR_ADDR          (GPIOH_BASE+0X14)


//2.求出位带别名区的地址    传进两个参数  参数一:ADDR   参数二:位数(即公式里面的n)

#define BITBAND(addr,bitnum)    ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))


//3.把求出来的位带别名区(立即数)强制类型转换为指针,编译器才知道是地址

//即将BITBAND传入MEM_ADDR中

#define MEM_ADDR(addr)           (*(volatile unsigned long *)(addr))


假设LED_GPIO_Config()是LED灯初始化程序

此处重点是说明位带的知识点,详情可以查看我的上一篇文章:

STM32F429--标准库点亮LED灯

https://blog.csdn.net/ABCisCOOL/article/details/106170244


此时在main函数里面直接调用即可,把

void main(void)

{

while(1)

{

  LED_GPIO_Config();

  MEM_ADDR(GPIOH_ODR_ADDR,10) == 0 ;

}

}


上述的操作是不是相对来说,可读性还比较差呢,试试下面改进的写法,是不是有点像51的编程了呢?


//1.宏定义ADDR

#define GPIOH_ODR_ADDR          (GPIOH_BASE+0X14)


//2.求出位带别名区的地址    传进两个参数  参数一:ADDR   参数二:位数(即公式里面的n)

#define BITBAND(addr,bitnum)    ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))


//3.把求出来的位带别名区(立即数)强制类型转换为指针,编译器才知道是地址

//即将BITBAND传入MEM_ADDR中

#define MEM_ADDR(addr)           (*(volatile unsigned long *)(addr))

//4.嵌套宏

#define BIT_ACTION(addr,bitnum)         MEM_ADDR(BITBAND(addr,bitnum))


//相当于操作PH的端口 如 PH10  ,是不是很接近51了呢

#define PHout(n)                        BIT_ACTION(GPIOH_ODR_ADDR,bitnum) 


void main(void)

{

while(1)

{

  LED_GPIO_Config();

  PHout(10) == 0 ;

}

}


如果我们需要操作多个引脚,可以先将其封装起来,然后利用

PAout,PBout,…,PHout等方式来对单独的位进行操作。


附:

本文还说明的还不够详细,还是不清楚的朋友,可以看一下火哥的讲解视频:


https://www.bilibili.com/video/BV1Ws411c7A8?p=23


作业

1- 如何实现GPIO端口的ODR寄存器的位操作

2- 如何实现其他GPIO口的IDR寄存器的位操作

3- 重新实现GPIO输入–按键检测部分的代码,读取IO电平的哪一部分代码,用位带操作的形式操作

提示:unit16_t temp; temp == PAint(10);

4- 把绿灯和蓝灯也点亮


参考答案:


1-

/*GPIOA~GPIOH的ODR寄存器位带操作,*/

#define GPIOA_ODR_ADDR           (GPIOA_BASE+0X14)

#define GPIOB_ODR_ADDR           (GPIOB_BASE+0X14)

#define GPIOC_ODR_ADDR           (GPIOC_BASE+0X14)

#define GPIOD_ODR_ADDR           (GPIOD_BASE+0X14)

#define GPIOF_ODR_ADDR           (GPIOE_BASE+0X14)

#define GPIOG_ODR_ADDR           (GPIOF_BASE+0X14)

#define GPIOH_ODR_ADDR           (GPIOH_BASE+0X14)


/*中间这三个不需要改动*/

#define BITBAND(addr,bitnum)     ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))

#define MEM_ADDR(addr)           (*(volatile unsigned long *)(addr))

#define BIT_ACTION(addr,bitnum)   MEM_ADDR(BITBAND(addr,bitnum))


#define PAout(n)                  BIT_ACTION(GPIOA_ODR_ADDR,bitnum) 

#define PBout(n)                  BIT_ACTION(GPIOB_ODR_ADDR,bitnum) 

#define PCout(n)                  BIT_ACTION(GPIOC_ODR_ADDR,bitnum) 

#define PDout(n)                  BIT_ACTION(GPIOD_ODR_ADDR,bitnum) 

#define PEout(n)                  BIT_ACTION(GPIOE_ODR_ADDR,bitnum) 

#define PFout(n)                  BIT_ACTION(GPIOF_ODR_ADDR,bitnum) 

#define PGout(n)                  BIT_ACTION(GPIOG_ODR_ADDR,bitnum) 

#define PHout(n)                  BIT_ACTION(GPIOH_ODR_ADDR,bitnum) 


2-

/*GPIOA~GPIOH的IDR寄存器位带操作,*/

#define GPIOA_IDR_ADDR           (GPIOA_BASE+0X10)

#define GPIOB_IDR_ADDR           (GPIOB_BASE+0X10)

#define GPIOC_IDR_ADDR           (GPIOC_BASE+0X10)

#define GPIOD_IDR_ADDR           (GPIOD_BASE+0X10)

#define GPIOE_IDR_ADDR           (GPIOE_BASE+0X10)

#define GPIOF_IDR_ADDR           (GPIOF_BASE+0X10)

#define GPIOG_IDR_ADDR           (GPIOG_BASE+0X10)

#define GPIOH_IDR_ADDR           (GPIOH_BASE+0X10)


#define PAint(n)                  BIT_ACTION(GPIOA_IDR_ADDR,bitnum) 

#define PBint(n)                  BIT_ACTION(GPIOB_IDR_ADDR,bitnum) 

#define PCint(n)                  BIT_ACTION(GPIOC_IDR_ADDR,bitnum) 

#define PDint(n)                  BIT_ACTION(GPIOD_IDR_ADDR,bitnum) 

#define PEint(n)                  BIT_ACTION(GPIOE_IDR_ADDR,bitnum) 

#define PFint(n)                  BIT_ACTION(GPIOF_IDR_ADDR,bitnum) 

#define PGint(n)                  BIT_ACTION(GPIOG_IDR_ADDR,bitnum) 

#define PHint(n)                  BIT_ACTION(GPIOH_IDR_ADDR,bitnum) 


3-按键接的是PA0端口号


uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)

{

  if (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON)

{

//如果按下了,KEY_ON 1

while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);

//做相应的动作

return KEY_ON;

}

else return KEY_OFF;


}


重新实现如下

uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)

{

  unit16_t temp;

  temp = PAint(0);

  if (temp== 1)

  {

    printf("有按键按下,做相应的动作");

  }


}

4-

红灯点亮的程序

#define LED_GPIO_PIN   GPIO_Pin_10

#define LED_GPIO_PORT  GPIOH

#define LED_GPIO_CLK   RCC_AHB1Periph_GPIOH


//配置函数

void LED_GPIO_Config(void)

{


GPIO_InitTypeDef GPIO_InitStruct;

//0-打开系统时钟

RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);

//1-设置引脚

GPIO_InitStruct.GPIO_Pin   = LED_GPIO_PIN;

//2-设置为输出模式

GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_OUT;

//3-设置为推挽输出类型

GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;

//4-设置为上拉

GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP;

//5-设置速度50MHZ

GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;

//把引脚写进寄存器的函数

GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);

}


void  LED_R_LIGHT(void)

{

    PHout(10)==0;

}


绿灯:

#define LED_GPIO_PIN   GPIO_Pin_11


void LED_GPIO_Config(void)

{


GPIO_InitTypeDef GPIO_InitStruct;

//0-打开系统时钟

RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);

//1-设置引脚

GPIO_InitStruct.GPIO_Pin   = LED_GPIO_PIN;

//2-设置为输出模式

GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_OUT;

//3-设置为推挽输出类型

GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;

//4-设置为上拉

GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP;

//5-设置速度50MHZ

GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;

//把引脚写进寄存器的函数

GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);

}

void  LED_G_LIGHT(void)

{

    PHout(11)==0;

}

蓝灯:

#define LED_GPIO_PIN   GPIO_Pin_12


void LED_GPIO_Config(void)

{


GPIO_InitTypeDef GPIO_InitStruct;

//0-打开系统时钟

RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);

//1-设置引脚

GPIO_InitStruct.GPIO_Pin   = LED_GPIO_PIN;

//2-设置为输出模式

GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_OUT;

//3-设置为推挽输出类型

GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;

//4-设置为上拉

GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP;

//5-设置速度50MHZ

GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;

//把引脚写进寄存器的函数

GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);

}


void  LED_B_LIGHT(void)

{

    PHout(12)==0;

}

推荐阅读

史海拾趣

Hitachi Metals公司的发展小趣事

自1956年成立以来,Hitachi Metals便逐步在电子材料领域建立了坚实的基础。公司早期便专注于研发和生产用于电子设备的核心部件,如磁性材料。随着电子行业的蓬勃发展,日立金属迅速抓住了市场机遇,推出了高性能的Nd-Fe-B系列烧结磁石和高性能铁氧体烧结磁石,这些材料广泛应用于计算机、半导体集成电路器件及平板显示屏等高科技产品中。凭借其卓越的产品质量和稳定的市场供应,Hitachi Metals在电子材料市场上赢得了广泛的认可。

DURACELL公司的发展小趣事

在开拓重庆市场时,DURACELL公司面临了巨大的挑战。当地电池市场竞争激烈,价格参差不齐。然而,DURACELL公司凭借其独特的营销策略和高质量的产品,迅速打开了市场。他们选择了与当地贸易代理商合作的方式进入市场,并通过“三步走式”的营销策略迅速占领了市场份额。这一成功案例展示了DURACELL公司灵活的市场策略和对品质的坚持。

ATOP_Technologies公司的发展小趣事

ATOP Technologies自1989年创立以来,以创新、智能、整合、坚持为核心价值观,初期主要专注于工业自动化领域的产品研发。在创立初期,公司面临着资金紧张、市场竞争激烈等挑战。然而,凭借着对技术的深刻理解和对市场的敏锐洞察,ATOP Technologies逐步在工业自动化领域站稳脚跟,并开始积累起一批忠实的客户。

Beta Dyne Inc公司的发展小趣事

近年来,电子行业面临着技术更新换代迅速、市场竞争激烈的挑战。Beta Dyne也不例外,公司面临着来自同行的竞争压力以及客户需求不断变化的挑战。为了应对这些挑战,Beta Dyne不断调整产品策略,加大研发投入,推出了一系列创新产品,并积极拓展新的应用领域。

ADLINK公司的发展小趣事

近年来,电子行业面临着技术更新换代迅速、市场竞争激烈的挑战。Beta Dyne也不例外,公司面临着来自同行的竞争压力以及客户需求不断变化的挑战。为了应对这些挑战,Beta Dyne不断调整产品策略,加大研发投入,推出了一系列创新产品,并积极拓展新的应用领域。

GTE Microcircuits公司的发展小趣事
学习高性能复合放大器电路的设计需要掌握模拟电路的基础知识,包括放大器的工作原理、电路分析方法、以及元器件的选择和使用等。此外,还需要通过阅读专业书籍、参加培训课程、以及实践项目等方式来不断积累经验和提高技能。

问答坊 | AI 解惑

基于FPGA和DSP的高速瞬态信号检测系统

引 言 目前国内急需一种能够对电火工品的发火过程进行实时无损耗监测的方法和手段,并根据监测结果对火工品的可靠性进行准确的判决和认证,解决科研和生产过程中的具体问题。本系统采用感应式线圈作为非接触式启爆电流的启爆装置,并采用高速A/D ...…

查看全部问答>

cycloneIII开发入门

由于我们应用板还在设计,开发板上的外围资源不是很多,主要是些存储器,所以暂时还不能开展FPGA的HDL和NIOS的开发,就给大家介绍一些基本的知识吧。 首先是QUARTUS II的安装:如果有安装软件,直接点NEXT就可以了,然后是注册,我现在装的的是开 ...…

查看全部问答>

请教一下大家关于继电器输出

什么是继电器输出,其是否可以作为单片机的输入,如果可以需要加转接电路吗?谢谢…

查看全部问答>

有重庆的想做兼职的吗?

有重庆的想做兼职的吗?QQ512440957…

查看全部问答>

请问谁了解 龙旗科技的 手机软件研发待遇如何 谢谢

最近收到龙旗的面试通知,对这个公司不太了解,请教各位.知道的告诉下,工作环境和待遇问题.做3G手机软件研发…

查看全部问答>

51串口通信问题!

真不知道哪里出问题了。。。。。。。 大侠帮忙看看: 我的程序:              MOV TMOD,#20H        ;置定时器T1工作于方式2定时           ...…

查看全部问答>

说说锂电池保护电路综述

锂离子电池保护电路包括过度充电保护、过电流/短路保护和过放电保护,要求过充电保护高精密度、保护IC功耗低、高耐压以及零伏可充电等特性。本文详细介绍了这三种保护电路的原理、新功能和特性要求。近年来,PDA、数字相机、手机、可携式音讯 ...…

查看全部问答>

AT89C51ED2 有没有替换型号呀

请各位高手帮忙,AT89C51ED2市场价格上涨了近50%,此芯片有没有不同厂家的替代型号呀!…

查看全部问答>

问几个关于ColdFire的问题

最近用ColdFire V2(MCF52259)做项目,遇上些问题,咨询下老鸟。 1.因为RAM空间限制,我想把程序中一块比较大的查表数据移到内部Flash中,即在线调试时也可以访问Flash取数据,能否通过修改LCF文件做到?我用的是龙丘的某款在线调试工具。 2.我看到 ...…

查看全部问答>

【转】硬件失效原因之:PCB焊接

有数据显示,78%的硬件失效原因是由于不良的PCB焊接加工造成的。深表赞同        遇到硬件失效的情况,工程师愿意花费大量时间和精力在样板调试和分析中,耽误了项目进度。如果找不出不良原因,他们也会习惯性的认为问题出 ...…

查看全部问答>