STM32为什么需要位带操作呢?

发布者:HeavenlySunset最新更新时间:2024-03-08 来源: elecfans关键字:STM32  位带操作  GPIO输出 手机看文章 扫描二维码
随时随地手机看文章

为什么需要位带操作?

因为编程需要操作某个bit位来达到我们想要的功能,比如点灯需要操作GPIOA->ODR

的某个bit假设是第2bit,写1就可以让GPIO输出一个高电平

GPIOA- >ODR |= 1< < 2;

这样写其实有三个隐含的操作:

//1.读取ODR寄存器的值到内存//2.改写第2bit的值//3.再把改写后的值写进ODR寄存器

这样的缺点:效率低

位带操作就是为了解决这个问题,前提是硬件支持这么做。

位操作就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。51 单片机中通过关键字 sbit 来实现位定义,STM32没有这样的关键字,而是通过访问位带别名区来实现,例如

sbit LED P1^2LED = 1;//输出高电平LED = 0;//输出低电平

这样的优点:效率高

什么是位带别名区?

STM32本身不支持位操作,它发明了一种位带操作来让32的某些资源支持位操作。

这两个区域一个是 SRAM 区的最低 1MB 空间,令一个是外设区最低 1MB 空间。

这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的 位带别名区 ,位带别名区把这 1MB 的空间的 每一个位膨胀成一个 32 位的字 ,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。

位带别名区就是就是就是本来位的区域,变成了字的区域。

这里有个形象的解释:

打个形象的比方,以某个村,就张村把,该村有3户人家分别为A,B,C,我想给张村的A送礼,但是明文规定,不能给具体的个人送礼,但是可以给村委会送礼,那我该怎么办呢,OK,即日起,A不叫A了,改名叫做村委会1,B和C分别改叫做村委会2和村委会3,哦了,可以给A送礼了,虽然我送礼的对象是村委会1,听起来好像比个人级别高一点,但是最终收到礼物的还是个人A。

同理,STM32不允许对某个端的某一个IO口进行操作,也就是PA.1 = 0或者PA.1 = 1这样的操作是非法的,好了,那我就给PA.1起个别名,将原来PA.1的位地址扩展成一个32位的 字地址 ,对32位的地址进行操作,这个是STM32允许的,肯定是可以的,STM32对所有的寄存器配置,都是对某个32位地址的操作,因此说白了,操作一个32位寄存器来影响某个位的操作叫做位带操作。

什么是位带区?

我们可以看到下面图中有两个位带区,分别是SRAM区里的0x20000000-0x200FFFFF地址段和片内外设区里的0x40000000-0x400FFFFF地址段(图中标号①处),它们的地址空间大小都是1M字节,在SRAM段内和外设地址段内的这1M大小的空间就是位带区,说白了就是支持位带操作的区域就是位带区。

图片

位带区跟位带别名区有怎样的关系?

从上面映射图上可以看到,SRAM区里的0x22000000-0x23FFFFFF地址段和外设区里0x42000000-0x43FFFFFF地址段都是位带别名区,两个别名区空间大小都是32MB。那么,这32MB的位带别名区地址空间是怎么与1MB的位带区地址空间对应起来的呢?

答案:地址映射

那么问题来了?将1M字节里面的每一个bit映射到32M字节里面去,那么怎么映射呢?

首先明确一些概念:

1字节= 8bit1字  = 4字节 = 32bit

看图

图片

将1bit映射到1个字空间(扩大了32倍)

映射前的1个字节 = 映射后的8个字(扩大了32倍 8 * 4 = 32字节)

那么就得出以下结论:

映射前的1个字节 = 映射后的32个字节

映射前的1M字节 = 映射后的32M字节

图片

0x40000000地址处的1个bit变成了0x42000010地址处的32个bit

为什么要将1bit空间要映射到一个字空间里去呢?我映射到1字节或者2字节的地址空间不行吗?我只能说,STM32是一个32位的机器,内核按字寻址的话寻址速度是最快的,所以别问这么多为什么,如果问了,答案就是为了速度。就好比你买个电脑用一个小箱子装着但是顺丰快递发货走的是集装箱,理论上来说装到集装箱里空运是最快的,要不然没办法上飞机啊......各位想想好像是这么个道理哈

位带操作该怎么用?

我们已经知道了位带区就是支持位操作的地址段,位带别名区就是位带区的地址映射,操作位带别名区就等价于操作位带区,并且我们知道了大致的映射过程,那么在STM32实际使用中又是怎么应用的呢?

在《Cortex M3权威指南》中,前人已经整理出了位带别名区与位带区地址对应关系的表达式,使用的时候只要套用公式就可以,如下图

图片

将两个公式合并一下就得到:

AliasAddr = ((A & 0xF0000000)+0x02000000+((A &0x00FFFFFF)<<5)+(n<<2))

式中A为位带区地址,n为位序号

<<5 <<2又是什么鬼

2进制左移5位就相当于乘以2^5次方 就是扩大32倍的意思 为什么不写成*32 问就是效率 <<2同理扩大4倍


使用以下开源代码即可完成映射

// 把“位带地址+位序号”转换成别名地址的宏

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


// 把一个地址转换成一个指针

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


// 把位带别名区地址转换成指针

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



// GPIO ODR 和 IDR 寄存器地址映射

#define GPIOA_ODR_Addr   (GPIOA_BASE+20)

#define GPIOB_ODR_Addr   (GPIOB_BASE+20)  

#define GPIOC_ODR_Addr   (GPIOC_BASE+20)  

#define GPIOD_ODR_Addr   (GPIOD_BASE+20)

#define GPIOE_ODR_Addr   (GPIOE_BASE+20)

#define GPIOF_ODR_Addr   (GPIOF_BASE+20)      

#define GPIOG_ODR_Addr   (GPIOG_BASE+20)

#define GPIOH_ODR_Addr   (GPIOH_BASE+20)      

#define GPIOI_ODR_Addr   (GPIOI_BASE+20)

#define GPIOJ_ODR_Addr   (GPIOJ_BASE+20)      

#define GPIOK_ODR_Addr   (GPIOK_BASE+20)


#define GPIOA_IDR_Addr   (GPIOA_BASE+16)  

#define GPIOB_IDR_Addr   (GPIOB_BASE+16)  

#define GPIOC_IDR_Addr   (GPIOC_BASE+16)  

#define GPIOD_IDR_Addr   (GPIOD_BASE+16)  

#define GPIOE_IDR_Addr   (GPIOE_BASE+16)    

#define GPIOF_IDR_Addr   (GPIOF_BASE+16)    

#define GPIOG_IDR_Addr   (GPIOG_BASE+16)  

#define GPIOH_IDR_Addr   (GPIOH_BASE+16)

#define GPIOI_IDR_Addr   (GPIOI_BASE+16)

#define GPIOJ_IDR_Addr   (GPIOJ_BASE+16)

#define GPIOK_IDR_Addr   (GPIOK_BASE+16)



// 单独操作 GPIO的某一个IO口,n(0,1,2...16),n表示具体是哪一个IO口

#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) //输入

理论上我们不仅可以使用公式对所有GPIO端口进行封装,我们也可以对STM32所有片内外设的寄存器进行封装(FSMC除外)


如图

图片

使用注意事项

  • 使用上面封装好的位带操作之前,要先对IO端口进行配置,否则操作结果不可预期。

  • PAout(n)作为左值使用,PAin(n)作为右值使用。(跟51单片机一样,我想你是懂51的)

  • 最后,使用的过程中要注意一点,强制地址转换的时候一定要使用volatile关键字进行修饰,否则这个操作可能会被编译器优化掉


使用例子

Led.h 增加位带操作代码

#define LED0 PFout(9)

#define LED1 PFout(10)

#define BEEP PFout(8)

Key.h增加位带操作代码

#define KEY0 PEin(4)

#define KEY1 PEin(3)

#define KEY2 PEin(2)

#define KEY_UP PAin(0)

main.c示例代码

#include "stm32f4xx.h"

#include "led.h"

#include "delay.h"

#include "key.h"

#include "usart.h"

#include "bit_band.h"

int main(void)

{

uint8_t i,key;

LED_Init();

KEY_Init();

USART1_Init(115200);

while(1)

{

key=ScanKeyVal(0);

if(key)

{

i=!i;

LED0=!LED0;

LED1=!LED1;

}

}

}

三、DS18B20温度传感器示例-位带控制实现时序

#include "ds18b20.h"

/*

函数功能: 硬件初始化--IO配置

硬件连接: PB15

*/

void DS18B20_Init(void)

{

   /*1. 开时钟*/

   RCC- >APB2ENR|=1< < 3; //PB

   /*2. 配置GPIO口模式*/

   GPIOB- >CRH&=0x0FFFFFFF;

   GPIOB- >CRH|=0x30000000;

   /*3. 上拉*/

   GPIOB- >ODR|=1< < 15;

}


/*

函数功能: 发送复位脉冲检测DS18B20硬件--建立通信过程

返 回 值: 0表示成功 1表示失败  

*/

u8 DS18B20_Check(void)

{

   u8 i;

   DS18B20_OUT_MODE(); //配置IO口为输出模式

   DS18B20_OUT=0;      //拉低

   delay_us(580);      

   DS18B20_OUT=1;      //拉高

   

   DS18B20_IN_MODE();  //配置IO口为输入模式

   for(i=0;i< 100;i++)

  {

       if(DS18B20_IN==0)break;

       delay_us(1);

  }

   if(i==100)return 1;

   

   for(i=0;i< 250;i++)

  {

      if(DS18B20_IN)break;

      delay_us(1);

  }

   if(i==250)return 1;

   return 0;

}


/*

函数功能: DS18B20写一个字节数据

*/

void DS18B20_WriteOnebyte(u8 cmd)

{

   u8 i;

   DS18B20_OUT_MODE(); //输出模式

   for(i=0;i< 8;i++)

  {

       if(cmd&0x01) //发送1

      {

           DS18B20_OUT=0;

           delay_us(15);

           DS18B20_OUT=1;

           delay_us(45);

           DS18B20_OUT=1;

           delay_us(2);

      }

       else //发送0

      {

           DS18B20_OUT=0;

           delay_us(15);

           DS18B20_OUT=0;

           delay_us(45);

           DS18B20_OUT=1;

           delay_us(2);

      }

       cmd >>=1;

  }

}


/*

函数功能: DS18B20读一个字节数据

*/

u8 DS18B20_ReadOnebyte(void)

{

   u8 i;

   u8 data=0;

   for(i=0;i< 8;i++)

  {

       DS18B20_OUT_MODE(); //输出模式

       DS18B20_OUT=0;

       delay_us(2);

       DS18B20_IN_MODE();

       delay_us(8);

       data >>=1; //右移1位

       if(DS18B20_IN)data|=0x80;

       delay_us(50);

       DS18B20_OUT=1;

       delay_us(2);

  }

   return data;

}


/*

函数功能: 读取一次DS18B20的温度数据

返回值: 读取的温度数据高低位

*/

u16 DS18B20_ReadTemp(void)

{

  u16 temp;

  u8 t_L,t_H;

  if(DS18B20_Check())return 1;

  DS18B20_WriteOnebyte(0xCC); //跳跃 ROM 指令 --不验证身份

  DS18B20_WriteOnebyte(0x44); //发送温度转换指令

   

  if(DS18B20_Check())return 2;

  DS18B20_WriteOnebyte(0xCC); //跳跃 ROM 指令 --不验证身份

  DS18B20_WriteOnebyte(0xBE); //读取RAM里的数据

 

  //读取温度

  t_L=DS18B20_ReadOnebyte(); //低字节

  t_H=DS18B20_ReadOnebyte(); //高字节

  temp=t_H< < 8|t_L;

  return temp;

}


关键字:STM32  位带操作  GPIO输出 引用地址:STM32为什么需要位带操作呢?

上一篇:STM32上的SDRAM硬件电路设计
下一篇:STM32系列里RTC的亚秒特性及功能(下)

推荐阅读最新更新时间:2024-11-16 21:47

STM32实现对RTC闹钟唤醒的设计
工程中用到低功耗的控制,本来想使用待机模式,后来发现待机后所有IO口为高阻态,这样对于一些IO口控制的外设有些不妥,想过外部上拉一个电阻可是功耗不好控制放弃该方案选用停止模式。停止模式后IO口保持停止前的状态,但是不像待机模式那样可以轻松通过闹钟唤醒,只能通过中断线实现唤醒。为了实现RTC闹钟唤醒搜得一段代码,现贴过来分析一下 void RTC_EXTI_INITIAL(FuncTIonalState interrupt_en_or_dis) { NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; //------------EXTI1
[单片机]
<font color='red'>STM32</font>实现对RTC闹钟唤醒的设计
STM32中的SAR ADC是怎么一回事
STM32中的ADC是逐次逼近型ADC(Successive Approximation ADC),是逐个产生比较电压Vref,并逐次与输入电压分别比较,以逐渐逼近的方式进行A/D转换的。而其中的用来产生Vref的电路就是DAC电路。因此一般DAC电路比较容易设计,而DAC在采样速度和精度的权衡上会比较复杂。 SAR ADC的转换原理是把输入的模拟信号按规定的时间间隔采样(采样),并与一系列标准的数字信号相比较,数字信号逐次收敛,直至两种信号相等为止(量化完成),最后输出代表此信号的二进制数(编码)。 SAR ADC结构 结构上主要包括采样保持电路(S/H),比较器(COMPARATOR,COMP),SAR逻辑控制电路、时
[单片机]
<font color='red'>STM32</font>中的SAR ADC是怎么一回事
STM32开发笔记70: 传递参数时,为何要对套接字地址进行强制
在进行IPV6的UDP设计时,偶然发现一个问题,就是大部分套接字函数都需对地址进行强制转换,先看一下程序: 这是bind函数: bind(sockIPV6, (struct sockaddr*)&sockAddr, sizeof(sockAddr)) 这是recvfrom函数: recvfrom(sockIPV6, UdpBuffer, 100, 0, (struct sockaddr*)&sockAddr, &slen) 这是sendto函数: sendto(sockIPV6, UdpBuffer, len, 0, (const struct sockaddr*)&sockAddr, sizeof(sockAdd
[单片机]
STM32生态系统—SBSFU初体验
准备工作 下载SBSFU固件 启动SBSFU固件的运行 首次运行SBSFU 首次下载用户固件 用户应用运行 生成新版本用户应用代码 从用户程序(版本A)下载新用户程序(版本B) 重启后运行新版本用户程序 (版本B) 同时下载sbsfu和初始用户代码 下载SBSFU_UserApp.bin 启动SBSFU_UserApp.bin的运行 下载SBSFU_UserApp.bin后,启动 从SBSFU下载新用户程序(版本D)
[单片机]
STM32库开发理解
STM32F103的开发使用库开发很简单,只需要你设计适合自己的结构框架,就很快的复制、粘贴一样的来编程序,配置好需要的功能,所谓库函数,就是 STM32 固件库文件中为我们编写好的函数接口,我们只要调用这 些库函数,就可以对 STM32 进行配置,达到控制目的。我们可以不知道库函数是如何实 现的,但我们调用函数必须要知道函数的功能、可传入的参数及其意义和函数的返回值。 于是,有读者就问那么多函数我怎么记呀?我们的回答是 :会查就行!所以我们学会 查阅库帮助文档是很有必要的。
[单片机]
<font color='red'>STM32</font>库开发理解
STM32之内部FLASH原理
不同型号的 STM32,其 FLASH 容量也有所不同,最小的只有 16K 字节,最大的则达到了 1024K 字节。市面上 STM32F1 开发板使用的芯片是 STM32F103系列,其 FLASH 容量一般为 512K 字节,属于大容量芯片。大容量产品的 Flash 模块组织结构如图 40.1.1 所示: STM32F1 的闪存(Flash)模块由:主存储器、信息块和闪存存储器接口寄存器等 3 部分组成。下面我们就来介绍下这些组成部分: ①主存储器。该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2K 字节。注意,小容量和中容量产品则每页只有 1K 字节。从
[单片机]
<font color='red'>STM32</font>之内部FLASH原理
STM32之FSMC
STM32 FSMC总线深入研究 由于CPU与FPGA通信的需要,以及对8080总线的熟悉,首选采用了STM32的FSMC总线,作为片间通信接口。FSMC能达到16MHz的写入速度,理论上能写20fps的1024*768的图片哈哈。(当然实际上是不可能的,就算是DMA传输,数据源也跟不上,实际上刷模拟的图片每秒10fps,刷的很high)当然这不是本篇的要点,这里主要研究STM32的FSMC接口,将速度提升到极限。 1. FSMC协议分析 如下为ILI9325的8080接口的协议 CS(片选信号):低电平片选有效,高电平失能(默认为高:失能) RS(数据寄存器):低电平写寄存器,高电平写数据(默认为高:写数据
[单片机]
<font color='red'>STM32</font>之FSMC
基于STM32的红光治疗仪控制系统
一 STM32 ADC 采样 频率的确定 先看一些资料,确定一下STM32 ADC 的时钟: (1),由时钟控制器提供的ADCCLK 时钟和PCLK2(APB2 时钟)同步。CLK 控制器为ADC 时钟提供一个专用的可编程预分频器。 (2)一般情况下在程序 中将 PCLK2 时钟设为 与系统时钟 相同 RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK2Config(RCC_HCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); (3)在时钟配置寄存器(RCC_CFGR) 中 有 为ADC 时钟提供一个专用的可编程预分器 位15:14 ADCPRE:A
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved