STM32Fxx位带操作还不会?哲学三问让你实现位带自由(含位带操作核心代码)以LED与键盘为例

简介: STM32Fxx位带操作还不会?哲学三问让你实现位带自由(含位带操作核心代码)以LED与键盘为例

目录


什么是位带操作

什么是位带区

什么是位带别名区

位带操作的实际意义

如何实现位带操作

整体思路

核心代码(以LED与键盘为例)

寄存器封装代码

LED与键盘初始化代码


前言


本文将从以下几点进行讲解:1.什么是位带操作?2.位带操作的实际意义。3.如何实现位带操作。分别对应哲学三问,是什么?为什么?怎么做?


正文


什么是位带操作


位带操作,指的就是单独对一个bit位进行读和写。在51单片机中通过sbit来实现位定义,STM32没有这样的关键字,而是通过访问位带别名区来实现。


什么是位带区


0000.png

我们可以看到图中有两个位带区,分别是SRAM区里的0X20000000-0X200FFFFF地址段和片内外设区里的0X40000000-0X400FFFFF地址段(图中标号①处),它们的地址空间大小都是1M字节,在SRAM和外设地址段内的这1M大小的空间就是位带区,说白了就是支持位带操作的区域就是位带区。我们上面已经说过了,内核的最小寻址单位是字节,那么怎么将寻址单位缩小到bit?要弄明白这个问题,那就要先弄明白什么是位带别名区了。


什么是位带别名区


从位带别名区名字上理解,感觉它像是别人的替身,实际使用上它就是别人的替身(电影里出名的武打明星都有自己的替身,STM32这么出名,肯定也有自己的替身啦,危险的操作都让替身去做,自己躺着就行了),位带别名区就是为位带区服务的,对位带别名区的操作最终都会反映在位带区上,我们操作位带别名区的时候就等价于在操作位带区地址,那么位带别名区与位带区又是什么样的关联关系呢?


从上面映射图上可以看到,SRAM区里的0X22000000-0X23FFFFFF地址段和外设区里0X42000000-0X43FFFFFF地址段都是位带别名区,两个别名区空间大小都是32M。那么,这32M的位带别名区地址空间是怎么与1M的位带区地址空间对应起来的呢?其实,工程师们想出了一个很好的办法,地址映射,将1M字节里面的每一个bit映射到32M字节里面去,那么怎么映射呢?看到这里可能有些小伙伴就亿脸懵逼了,懵逼的话可以看一下下面的演算。


各种单位运算关系


1字节 = 8bit


1字 = 4字节


如果对这些单位没有什么概念,可以看下图

00.png

将1bit映射到1个字空间里(如下图)


映射前的一个字节 = 映射后的8个字


那么就有


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


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

000.png

好奇的小伙伴可能就要问了,为什么要将1bit空间要映射到一个字空间里去呢?我映射到1字节或者2字节的地址空间不行吗?我只能说,STM32是一个32位的机器,内核按字寻址的话寻址速度是最快的,所以别问这么多为什么,如果问了,答案就是为了速度。


位带操作的实际意义


学过51的话,理解起来就简单了,我们都知道使用51时,我们可以直接读写gpio的某一位,像这样就属于位操作,如下:


sbit led0 = P3 ^ 0;
sbit led1 = P3 ^ 1;
led0 = 1;
led1 = 0;

这样操作起来就非常方便,但是STM32没有这样的关键字,只能通过访问位带别名区的位带操作来实现。也就是上面介绍的两个概念,这样一来,STM32的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器位的效果,这比51单片机强大很多。因为51单片机里面并不是所有的寄存器都可以进行位操作,有些寄存器还是得用字节操作,比如SBUF。还有一个就是上面说的,STM32安字寻址速度很快。


如何实现位带操作


整体思路


1.对GPIO的IDR和ODR寄存器位操作进行封装

2.编写对应模块的驱动程序

       2.1.编写头文件:宏定义连接模块的端口、端口引脚、端口时钟、引脚位带,函数声明

       2.2.编写驱动文件

3.主函数使用


核心代码(以LED与键盘为例)


寄存器封装代码


//system.c文件
#include "system.h"   //头文件中把GPIO的IDR和ODR寄存器位操作进行了封装
//system.h文件
#ifndef __system_H__
#define __system_H__
#include "stm32f10x.h"
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//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)  //输入
#endif


LED与键盘初始化代码


void LED0_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  //使能GPIOB/能GPIOE/AFIO时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE|RCC_APB2Periph_AFIO, ENABLE);
  //RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
  //RCC->APB2ENR|=1<<0;         //使能AFIO 时钟
  WRITE_REG(AFIO->MAPR, AFIO_MAPR_SWJ_CFG_JTAGDISABLE); //允许SW-DP调试,禁止JYAG
  //AFIO->MAPR |= 0x02000000;       //JTAG-DP Disabled and SW-DP Enabled
  //配置GPIOB-->推挽输出
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;           //3号引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //快速
  GPIO_Init(GPIOB, &GPIO_InitStructure);              //根据GPIO_InitStructure中指定的参数初始化外设 GPIOB 寄存器
  //配置GPIOE-->推挽输出
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //快速
  GPIO_Init(GPIOE, &GPIO_InitStructure);              //根据GPIO_InitStructure中指定的参数初始化外设 GPIOB 寄存器
  GPIO_ResetBits(GPIOE, GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
  GPIO_SetBits(GPIOB, GPIO_Pin_3);                    //LED_SEL=1
//  GPIOB->ODR |= (1<<3);
//  PBout(3)=1;
//  GPIOE->ODR |= 1;
//  PEout(1) = 1;
}
相关文章
|
6月前
【STM32】基于HAL库的360度编码器、摇杆代码编写
【STM32】基于HAL库的360度编码器、摇杆代码编写
|
芯片
最详细STM32,cubeMX 按键点亮 led
最详细STM32,cubeMX 按键点亮 led
191 0
|
传感器 芯片
最详细STM32,cubeMX 点亮 led
最详细STM32,cubeMX 点亮 led
159 0
|
5月前
使用STM32F103标准库实现定时器控制LED点亮和关闭
通过这篇博客,我们学习了如何使用STM32F103标准库,通过定时器来控制LED的点亮和关闭。我们配置了定时器中断,并在中断处理函数中实现了LED状态的切换。这是一个基础且实用的例子,适合初学者了解STM32定时器和中断的使用。 希望这篇博客对你有所帮助。如果有任何问题或建议,欢迎在评论区留言。
423 2
|
1月前
stm32学习 3-2 LED流水灯
stm32学习 3-2 LED流水灯
69 4
|
1月前
stm32学习3-1 LED闪烁
stm32学习3-1 LED闪烁
34 4
|
4月前
STM32CubeMX FreeRTOS点亮LED
STM32CubeMX FreeRTOS点亮LED
77 10
|
5月前
|
IDE 开发工具
使用STM32F103标准库实现自定义键盘
通过本文,我们学习了如何使用STM32F103标准库实现一个简单的自定义键盘。我们首先初始化了GPIO引脚,然后实现了一个扫描函数来检测按键状态。这个项目不仅能够帮助我们理解STM32的GPIO配置和按键扫描原理,还可以作为进一步学习中断处理和低功耗设计的基础。希望本文对你有所帮助,祝你在嵌入式开发的道路上不断进步!
503 4
|
4月前
STM32CubeMX 按键控制LED
STM32CubeMX 按键控制LED
69 0
|
4月前
STM32CubeMX 点亮LED
STM32CubeMX 点亮LED
59 0