跑马灯实验:位带操作
阿波罗STM32F429开发板:
STM32F429开发指南-库函数版本》- 5.2.1小节IO口的位带操作实现
STM32F4xx官方资料:
《STM32F4xx中文参考手册》-第7章通用IO
《 Cortex-M4权威指南(英文)》-6.7小节 bit band operation
这个只有英文的,但是内容跟M3基本一致,可以参考M3权威指南
笔记基于正点原子官方视频
视频连接https://www.bilibili.com/video/BV1Wx411d7wT?p=71&spm_id_from=333.1007.top_right_bar_window_history.content.click
如有侵权,联系删除
一、位操作基本原理
1.位操作原理:
把每个比特膨胀为一个32位的字,当访问这些字的时候就达到了访问比特的目的,比如说BSRR寄存器有32个位,那么可以映射到32个地址上,我们去访问(读-改-写)这32个地址就达到访问32个比特的目的。
2.哪些区域支持位操作:
其中一个是 SRAM 区的最低 1MB 范围,
0x20000000 ‐ 0x200FFFFF(SRAM 区中的最低 1MB)
第二个则是片内外设区的最低 1MB范围,
0x40000000 ‐ 0x400FFFFF(片上外设区中最低 1MB)
位带区与位带别名区的膨胀关系如下图:
位带区与位带别名区的膨胀对应关系如下图
3.位带操作优越性
位带操作有什么优越性呢?最容易想到的就是通过GPIO的管脚来单独控制每盏LED的点亮与熄灭。另一方面,也对操作串行接口器件提供了很大的方便(典型如74HC165,CD4094)。总之位带操作对于硬件l/o密集型的底层程序最有用处了。
CM3中还有一个称为“bit-bang”的概念,它通常是通过“bit-band”实现的,但是它俩在学术上是两个不同的概念。
位带操作还能用来化简跳转的判断。当跳转依据是某个位时,以前必须这样做:
- 读取整个寄存器
- 掩蔽不需要的位
- 比较并跳转
现在只需: - 从位带别名区读取状态位
- 比较并跳转
使代码更简洁,这只是位带操作优越性的初等体现,位带操作还有一个重要的好处是在多任务中,用于实现共享资源在任务间的“互锁”访问。多任务的共享资源必须满足一次只有一个任务访问它——亦即所谓的“原子操作”。以前的读一改一写需要3条指令,导致这中间留有两个能被中断的空当。于是可能会出现如下图所示的紊乱危象:
4.映射关系:
-
位带区: 支持位带操作的地址区
-
位带别名:对别名地址的访问最终作用到位带区的访问上
(注意:这中间有一个地址映射过程) -
支持位带操作的两个内存区的范围是:
0x2000_oo00-0x200F_FFFF (SRAM区中的最低1MB)
0x4000_o00o-ox400F_FFFF(片上外设区中的最低1MB) -
对于SRAM位带区的某个比特,记它所在字节地址为A,位序号为n(O<=n<=7),则该比特在别名区的地址为:
AliasAddr= 0x22000000+((A-0x20000000)*8+n)*4=0x22000000+(A-0x20000000)*32 + n*4对于片上外设位带区的某个比特,记它所 -
在字节的地址为A,位序号为n(O<=n<=7),则该比特在别名区的地址为:
AliasAddr=0x42000000+((A-0x40000000)*8+n)*4=0x42000000+(A-0x40000000)*32 + n*4
上式中,“*4”表示一个字为4个字节,“*8”表示一个字节中有8个比特。
5.sys.h里面对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 GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
…
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#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 PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
以上都是理论层面,可以不要求看懂,因为我实在是看不懂了
二、跑马灯硬件连接
- GPIO输出方式:推挽输出(上拉)
- LED灯共阳极接法,即端口输出1不亮,输出0亮
- 操作的两引脚为PB0、PB1
三、手把手写跑马灯实验-位操作
- 使能IO口时钟。__HAL_RCC_GPIOX_CLK_ENABLE();
- 初始化IO口模式。 HAL_GPIO_Init();
- 操作IO口,输出高低电平。使用位带操作。
1.工程模板中位带的定义
在工程文件夹-SYSTEM-sys.h文件夹下
//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) //输入
#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) //输入
2.下面介绍怎么调用
不详细解释了,直接看跑马灯程序差不多就能看懂
PBout(0)=1; //PB0口输出高电平,第一个灯亮
PBout(1)=0; //PB1口输出低电平,第二个灯灭
delay_ms(500); //延时
PBout(0)=0; //PB0口输出高电平,第一个灯灭
PBout(1)=1; //PB1口输出低电平,第二个灯亮
delay_ms(500); //延时
3.main函数程序
#include "sys.h"
#include "delay.h"
#include "usart.h"
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义一个变量GPIO_InitStructure
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(360,25,2,8); //初始化系统时钟
delay_init(180); //初始化延时函数
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能PB时钟
GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_InitStructure.Pin=GPIO_PIN_0 | GPIO_PIN_1; //定义PB0 PB1引脚
GPIO_InitStructure.Pull=GPIO_PULLUP; //上拉模式
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //高速模式
HAL_GPIO_Init(GPIOB,&GPIO_InitStructure); //GPIOB:初始化PB口
//&GPIO_InitStructure:结构体指针指向变量GPIO_InitStructure
while(1)
{
PBout(0)=1; //PB0口输出高电平
PBout(1)=0; //PB1口输出低电平
delay_ms(1000); //延时
PBout(0)=0; //PB0口输出高电平
PBout(1)=1; //PB1口输出低电平
delay_ms(1000); //延时
}
}