12.跑马灯实验:位带操作

跑马灯实验:位带操作

阿波罗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);		//延时
	}
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值