【STM32】标准库与HAL库对照学习教程六--位带操作
STM32全部教程
:【STM32】标准库与HAL库对照学习系列教程大全
一、前言
本篇文章讲解位带以及位带的操作,通过位带操作可以极大简化IO的操作,以使用位带操控LED灯为例,通过本篇文章可以加深你对位带的理解。 |
二、准备工作
- STM32开发板(我使用的是普中的STM32F103ZE的Z200系列)
- STM32cubemx软件、keil5(MDK)
- Cortex M3权威指南(中文)(非必要)
使用位带需要了解GPIO的使用与LED的操作,可以看这两篇文章:
【STM32】标准库与HAL库对照学习教程特别篇–GPIO详讲
【STM32】标准库与HAL库对照学习教程三–使用库函数配置GPIO点亮LED灯
三、位带介绍
1、位带操作
在我们学习51单片机的时侯就已经使用过位操作,比如使用sbit对单片机IO口的定义。
但是STM32中并没有这类关键字,控制引脚输入输出是通过控制寄存器IDR与寄存器ODR地址取出32位数据中相应的位实现的。
如果将这32个位数据都取一个地址,当我们要访问32位数据中的某一位数据时,我们访问相应的地址就行了。
那么寄存器所在的地方是位带区,32个地址所在的地方是位带别名区。
比方说引脚输出寄存器ODR有32个位,PA8的控制位是第8位,32个位可以映射到32个地址上,当我们去访问这32个地址的第8个地址时就达到访问ODR寄存器第8个位的目的。
2、STM32位带及位带别名区域
支持位带操作的区域是SRAM区的最低1MB范围(APB1/2,AHB外设)和外设区的最低1MB范围。
四、位带区与位带别名区地址转换
外设位带区与外设位带别名区的地址转换公式:
AliasAddr = 0x42000000+ (A-0x40000000)* 8* 4 +n*4
别名区的地址 = 别名区的基地址+(外设寄存器地址-外设寄存器基地址)*32+要控制寄存器的相应位的序号 *32/8(一个字节8位)
SRAM位带区与SRAM位带别名区的地址转换公式:
AliasAddr = 0x22000000+ (A-0x20000000)* 8* 4 +n*4
别名区的地址 = 别名区的基地址+(SRAM寄存器地址-SRAM寄存器基地址)*32+要控制寄存器相应位的序号 *32/8(一个字节8位)
根据上述两个公式的规律,将其统一为一个公式表示:
AliasAddr = ((A & 0xF0000000)+0x02000000+((A&0x000FFFFF)<<5)+(n<<2))
- A:寄存器地址
- FFFFF=1M
- 左移5位相当于乘以32,左移2位相当于乘以4
五、GPIO的位带操作
#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) //输入
不懂的可以去b站搜索海创电子,在up主的32系列视频中,找到位带操作的视频,里面有详讲,我文字描述实在有限。
六、标准库位带操作
1、工程配置
**(1)**复制一个操控LED的工程,命名为 6、位带操控LED实验。
(2)进入工程文件,进入Public文件,新建System文件夹用来存放位带的文件。
(3)进入工程,新建System.h与System.c文件。
①
文件内容为:
#include "System.h"
②
文件内容为:
#ifndef SYSTEM_H_
#define SYSTEM_H_
#include "stm32f10x.h"
//映射地址公式
#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
//IO口输入寄存器地址映射
#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的值为0~15
#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
(4)添加文件到目录并添加文件路径。
①
②
2、主程序
#include "LED.h"
#include "Delay.h"
//#include "exti.h"
#include "System.h"
/*************************************************
*函数名: main
*函数功能: 主函数
*输入: 无
*返回值: 无
**************************************************/
int main()
{
SysTick_Init(72);
//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //抢占式优先级与响应式优先级的分组
LED_Init();
//KEY_Init();
//My_EXTI_Init();
while(1)
{
PBout(5)=1;
Delay_ms(1000);
PBout(5)=0;
Delay_ms(1000);
}
}
3、实验效果
七、HAL库位带操作
1、配置工程
(1)打开cubemx新建工程,选择芯片(我的是stm32F103ZE)。
(2)配置RCC。
(3)设置LED的引脚为输出模式。
(4) 配置GPIO。
(5) 配置时钟树。
**(6)**配置工程文件,并生成工程。
①
②
(7)在工程文件夹中新建Public文件夹,在Public文件夹内新建System文件夹用来存放位带相关的文件。
①
②
(8)进入工程,新建System.h与System.c文件。
①
内容为;
#include "System.h"
②
内容为:
#ifndef SYSTEM_H_
#define SYSTEM_H_
#include "stm32f1xx_hal.h"
//映射地址公式
#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
//IO口输入寄存器地址映射
#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的值为0~15
#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
可以看到标准库与HAL的寄存器地址是一样的,只是封装函数不一样。
(9)添加文件到目录,并添加文件路径。
①
②
2、主程序
①
②
代码:
PEout(5)=1;
HAL_Delay(1000);
PEout(5)=0;
HAL_Delay(1000);
3、实验效果
八、总结位带操作优点
- 1、控制GPIO口输入输出非常简单。
- 2、操作串行接口芯片非常方便。
- 3、代码简洁,阅读方便。
- 4、在多任务中,用于实现共享资源在任务间的“互锁”访问。
到这里就结束啦!