代码编织梦想

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

目录

什么是位带操作

什么是位带区

什么是位带别名区

位带操作的实际意义

如何实现位带操作

整体思路

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

寄存器封装代码

LED与键盘初始化代码


什么是位带操作

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

什么是位带区

 我们可以看到图中有两个位带区,分别是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字节

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

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

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

那么就有

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

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

好奇的小伙伴可能就要问了,为什么要将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;
}

如果有帮助的话,欢迎点赞收藏哦~🤩,有不同见解或更好的观点也可以在评论区留言,也可以笔者点点关注,互通有无,互相进步。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_53050357/article/details/127174781

项目笔记: gd32f3x0 与stm32fxx isp总结_鸟啼落满枝的博客-爱代码爱编程_gd32f3x0

1. 前言 近段时间在做的一个产品, 共用到3个MCU: GD32F3x0, GD32E2x0, STM32F3xx. 其中GD32F3x0,作为主MCU, 通过UART与另外两个MCU通信. 同时,主MCU可通过USB与PC连接。 产品需要实现三颗MCU的升级功能,根据电路设计,各个MCU升级实现方式如下。 (1) 通过USB接收上位机的升级程序

【STM32】HAL库 STM32CubeMX教程十二---IIC(读取AT24C02 )-爱代码爱编程

前言: 本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用 在之前的标准库中,STM32的硬件IIC非常复杂,更重要的是它并不稳定,所以都不推荐使用。但是在我们的HAL库中,对硬件IIC做了全新的优化,使得之前软件IIC几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处

修改system_stm32Fxx.c文件,启动内部晶振代码-爱代码爱编程

`从启动文件上分析,该函数在reset复位之后,进入main主函数之前,芯片调用该函数进行系统时钟的初始化和设置,对这段代码进行修改,就能实现启动内部晶体了。`` void SystemInit (void) { /* Set HSION bit */ RCC->CR |= (uint32_t)0x00000001; #if defined (ST

stm32fxx iap 功能应用介绍_μlink的博客-爱代码爱编程

        ST MCU 提供了一个IAP功能,即STM32 in-application programming (IAP),这个程序通过ST 原始方式(比如常见的拉BOOT管脚进BOOT,然后进行升级)烧录至用户代码起始位置(0x8000000),作为第一级用户软件,有了这个第一级用户软件后,可以支持烧录用户第二级用户软件(App)、读取用户第二级

stm32-寄存器编程-使用寄存器点亮led_学c入门到入土的博客-爱代码爱编程

一、工程文件的建立 1、我们建立一个”工程模板“文件夹,在它下面在新建4个文件夹 Listing存放编译器编译适合产生的C/汇编/链接的列表信息Project存放工程Output存放编译产生的调用信息、hex文件、预览信息、封装库等User存放用户编写的驱动文件 2、打开keil5点击Project新建一个工程放到我们刚建的”工程模板“文件夹

中断(stm32fxx系列单片机)_joyerx的博客-爱代码爱编程

中断概述 1.1 什么是中断? 1.2 中断的作用中断的实现 2.1 中断的入口 2.2 中断优先级ARM单片机中断体系 3.1 NVIC优先级说明 3.2 NVIC优先级分配方式 3.3 STM32fxx中断体系STM32

【stm32】:gpio工作原理_w_oilpicture的博客-爱代码爱编程

前言 时不可以苟遇,道不可以虚行。 一、GPIO 基本结构和工作方式 IO口引脚stm32的大部分引脚除了当GPIO使用外,还可以复用为外设功能引脚(比如串口) 1、GPIO 的工作方式 四种