一、位带操作的原理
51单片机可以直接对某一位IO进行读写操作,而STM32则通过位带操作来控制一个单独的IO口。
概念
位带区:支持位带操作的地址区。
位带别名区:对别名地址的访问最终作用到位带区的访问上。位带别名区对位带区的访问有个地址映射过程。
目的
对位带区的比特位进行独立的读写操作,即单独操作一个位 ,它是通过对位带别名区的操作来实现。
具体过程:
对位带别名区进行读写访问,位带别名区通过地址映射关系映射到相应的位带区,对位带区进行原始比特的读写操作。
地址映射
对位带别名区进行读且操作 ---------------------->对位带区进行读且操作(目的)
下面是位带操作的一些说明
二、计算公式
字节地址为A,比特位序号为n(0<=n<=7)
说明:“*4”表示一个字为4个字节,“*8”表示一个字节中有8个比特。
将上述的外设位带区和SRAM位带区进行统一,得到下面的公式:
((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))
其中4*8=32=2的5次方,4=2的2次方,相当于左移了5位和2位
公式解析
add &0xF000 0000:目的是取出4和2,用于区分是外设还是SRAM。然后再加上0x2000000就等于外设/SRAM位带别名区的起始地址。
add &0x000FFFFF:屏蔽掉高3位。外设位带区的最高地址为0x400F0000,SRAM位带区的最高地址为0x200F 0000,(0x400F 0000 - 0X4000 0000)与(0x200F 0000-0x2000 0000)在求偏移地址相减的时候只有低五位有效,所以就把剩下的高3位屏蔽掉,剩下的5位和F做与运算即可。
三、举例
假如要操作GPIOH的ODR寄存器的10位,就可以通过位带操作来访问
//1.宏定义ADDR
#define GPIOH_ODR_ADDR (GPIOH_BASE+0X14)
//2.求出位带别名区的地址 传进两个参数 参数一:ADDR 参数二:位数(即公式里面的n)
#define BITBAND(addr,bitnum) ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))
//3.把求出来的位带别名区(立即数)强制类型转换为指针,编译器才知道是地址
//即将BITBAND传入MEM_ADDR中
#define MEM_ADDR(addr) (*(volatile unsigned long *)(addr))
假设LED_GPIO_Config()是LED灯初始化程序
此处重点是说明位带的知识点,详情可以查看我的上一篇文章:
STM32F429--标准库点亮LED灯
https://blog.csdn.net/ABCisCOOL/article/details/106170244
此时在main函数里面直接调用即可,把
void main(void)
{
while(1)
{
LED_GPIO_Config();
MEM_ADDR(GPIOH_ODR_ADDR,10) == 0 ;
}
}
上述的操作是不是相对来说,可读性还比较差呢,试试下面改进的写法,是不是有点像51的编程了呢?
//1.宏定义ADDR
#define GPIOH_ODR_ADDR (GPIOH_BASE+0X14)
//2.求出位带别名区的地址 传进两个参数 参数一:ADDR 参数二:位数(即公式里面的n)
#define BITBAND(addr,bitnum) ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))
//3.把求出来的位带别名区(立即数)强制类型转换为指针,编译器才知道是地址
//即将BITBAND传入MEM_ADDR中
#define MEM_ADDR(addr) (*(volatile unsigned long *)(addr))
//4.嵌套宏
#define BIT_ACTION(addr,bitnum) MEM_ADDR(BITBAND(addr,bitnum))
//相当于操作PH的端口 如 PH10 ,是不是很接近51了呢
#define PHout(n) BIT_ACTION(GPIOH_ODR_ADDR,bitnum)
void main(void)
{
while(1)
{
LED_GPIO_Config();
PHout(10) == 0 ;
}
}
如果我们需要操作多个引脚,可以先将其封装起来,然后利用
PAout,PBout,…,PHout等方式来对单独的位进行操作。
附:
本文还说明的还不够详细,还是不清楚的朋友,可以看一下火哥的讲解视频:
https://www.bilibili.com/video/BV1Ws411c7A8?p=23
作业
1- 如何实现GPIO端口的ODR寄存器的位操作
2- 如何实现其他GPIO口的IDR寄存器的位操作
3- 重新实现GPIO输入–按键检测部分的代码,读取IO电平的哪一部分代码,用位带操作的形式操作
提示:unit16_t temp; temp == PAint(10);
4- 把绿灯和蓝灯也点亮
参考答案:
1-
/*GPIOA~GPIOH的ODR寄存器位带操作,*/
#define GPIOA_ODR_ADDR (GPIOA_BASE+0X14)
#define GPIOB_ODR_ADDR (GPIOB_BASE+0X14)
#define GPIOC_ODR_ADDR (GPIOC_BASE+0X14)
#define GPIOD_ODR_ADDR (GPIOD_BASE+0X14)
#define GPIOF_ODR_ADDR (GPIOE_BASE+0X14)
#define GPIOG_ODR_ADDR (GPIOF_BASE+0X14)
#define GPIOH_ODR_ADDR (GPIOH_BASE+0X14)
/*中间这三个不需要改动*/
#define BITBAND(addr,bitnum) ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) (*(volatile unsigned long *)(addr))
#define BIT_ACTION(addr,bitnum) MEM_ADDR(BITBAND(addr,bitnum))
#define PAout(n) BIT_ACTION(GPIOA_ODR_ADDR,bitnum)
#define PBout(n) BIT_ACTION(GPIOB_ODR_ADDR,bitnum)
#define PCout(n) BIT_ACTION(GPIOC_ODR_ADDR,bitnum)
#define PDout(n) BIT_ACTION(GPIOD_ODR_ADDR,bitnum)
#define PEout(n) BIT_ACTION(GPIOE_ODR_ADDR,bitnum)
#define PFout(n) BIT_ACTION(GPIOF_ODR_ADDR,bitnum)
#define PGout(n) BIT_ACTION(GPIOG_ODR_ADDR,bitnum)
#define PHout(n) BIT_ACTION(GPIOH_ODR_ADDR,bitnum)
2-
/*GPIOA~GPIOH的IDR寄存器位带操作,*/
#define GPIOA_IDR_ADDR (GPIOA_BASE+0X10)
#define GPIOB_IDR_ADDR (GPIOB_BASE+0X10)
#define GPIOC_IDR_ADDR (GPIOC_BASE+0X10)
#define GPIOD_IDR_ADDR (GPIOD_BASE+0X10)
#define GPIOE_IDR_ADDR (GPIOE_BASE+0X10)
#define GPIOF_IDR_ADDR (GPIOF_BASE+0X10)
#define GPIOG_IDR_ADDR (GPIOG_BASE+0X10)
#define GPIOH_IDR_ADDR (GPIOH_BASE+0X10)
#define PAint(n) BIT_ACTION(GPIOA_IDR_ADDR,bitnum)
#define PBint(n) BIT_ACTION(GPIOB_IDR_ADDR,bitnum)
#define PCint(n) BIT_ACTION(GPIOC_IDR_ADDR,bitnum)
#define PDint(n) BIT_ACTION(GPIOD_IDR_ADDR,bitnum)
#define PEint(n) BIT_ACTION(GPIOE_IDR_ADDR,bitnum)
#define PFint(n) BIT_ACTION(GPIOF_IDR_ADDR,bitnum)
#define PGint(n) BIT_ACTION(GPIOG_IDR_ADDR,bitnum)
#define PHint(n) BIT_ACTION(GPIOH_IDR_ADDR,bitnum)
3-按键接的是PA0端口号
uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
if (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON)
{
//如果按下了,KEY_ON 1
while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);
//做相应的动作
return KEY_ON;
}
else return KEY_OFF;
}
重新实现如下
uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
unit16_t temp;
temp = PAint(0);
if (temp== 1)
{
printf("有按键按下,做相应的动作");
}
}
4-
红灯点亮的程序
#define LED_GPIO_PIN GPIO_Pin_10
#define LED_GPIO_PORT GPIOH
#define LED_GPIO_CLK RCC_AHB1Periph_GPIOH
//配置函数
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//0-打开系统时钟
RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);
//1-设置引脚
GPIO_InitStruct.GPIO_Pin = LED_GPIO_PIN;
//2-设置为输出模式
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
//3-设置为推挽输出类型
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
//4-设置为上拉
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
//5-设置速度50MHZ
GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
//把引脚写进寄存器的函数
GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);
}
void LED_R_LIGHT(void)
{
PHout(10)==0;
}
绿灯:
#define LED_GPIO_PIN GPIO_Pin_11
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//0-打开系统时钟
RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);
//1-设置引脚
GPIO_InitStruct.GPIO_Pin = LED_GPIO_PIN;
//2-设置为输出模式
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
//3-设置为推挽输出类型
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
//4-设置为上拉
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
//5-设置速度50MHZ
GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
//把引脚写进寄存器的函数
GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);
}
void LED_G_LIGHT(void)
{
PHout(11)==0;
}
蓝灯:
#define LED_GPIO_PIN GPIO_Pin_12
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//0-打开系统时钟
RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);
//1-设置引脚
GPIO_InitStruct.GPIO_Pin = LED_GPIO_PIN;
//2-设置为输出模式
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
//3-设置为推挽输出类型
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
//4-设置为上拉
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
//5-设置速度50MHZ
GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
//把引脚写进寄存器的函数
GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);
}
void LED_B_LIGHT(void)
{
PHout(12)==0;
}
上一篇:STM32F429--启动文件简单分析
下一篇:STM32F429--按键检测
推荐阅读最新更新时间:2024-11-10 10:58
推荐帖子
- 请教arm的普通内核与Cortex内核有何区别啊!
- 请教arm的普通内核与Cortex内核有何区别啊?编程上有何区别呢?请教arm的普通内核与Cortex内核有何区别啊!1)ARM的普通内核是指哪些?ARM有不下几十种内核,哪些是普通的?2)任何两个内核之间的比较,都可以写出很多篇论文。3)如果使用C编程,在编程方面,内核之间基本无差别。楼主的问题有点大了cortex也属于普通内核。呵呵,我菜鸟了,对不起!我菜鸟了!但是
- f33 stm32/stm8
- 丝印“SAL”,5脚贴片,封装跟SOT23差不多大,是啥型号?
- 图示5个引脚位置,贴片的,封装跟SOT23差不多大。补充:刚才查了一下,这个封装是TSOP-5关键的是上面就丝印了“SAL”,再无其他提示。呵呵这是我在一个LED驱动器上看到的,很是好奇,不知道大家看见过没有? 丝印“SAL”,5脚贴片,封装跟SOT23差不多大,是啥型号?这种封装的升压芯片很多的凭你的经验能猜出来是哪家公司么?猜不出!刚才我查了下,TSOP-5的封装引脚应该是这样子的。按照我看到SAL的位置摆放出来,我觉得是不是引脚号就对应不上了。
- Michael_Fei LED专区
- 哪位大神帮忙分析一下这个电路是干什么的
- WE-VOUT为一个信号输入到LM321,R1,R4可以选择焊或者不焊,这个电路是用来干嘛的哪位大神帮忙分析一下这个电路是干什么的R1,R4可以选择焊或者不焊,这个电路是用来干嘛的电路是用来干嘛的,这可很难看出来。AUX怎么感觉是车上的接口什么的瞎猜的哈:hug:就是普通的运放,虚短虚断,虚短,weout=C2-P,功能就是信号条调理,用运放增强输出驱动能力焊或者不焊?是可以短路还是直接断开?几乎所有涉及运放问题,都可以用虚短,V+=V-;虚断,运放正负输入电流近似为
- li6666 模拟电子
- 在WINCE5.0下,USB不连接,设备上也会跳出ACTIVESYNC的连接窗口,怎么解决呢
- 这种情况在设备启动或者设备睡眠唤醒后并且FFUART未被打开时会出现如果FFUART被打开,就不会有那个窗口弹出我们的FFUART是com1,USB是com2如果USB是插上的,开机和唤醒后可以实现activesync的自动连接貌似设备启动或唤醒后,如果com1未打开,系统会把com1当作USB的com2进行连接很郁闷的问题,有人能帮忙解决吗在WINCE5.0下,USB不连接,设备上也会跳出ACTIVESYNC的连接窗口,怎么解决呢
- PWP1013 WindowsCE
- IMX6开发板
- 有人懂这个开发板的么,想请问点问题。有懂的留个联系方式,急。IMX6开发板
- hy2320020 ARM技术
- 接地与屏蔽技术
- 接地与屏蔽技术非常好的电磁兼容方面的电子资料,清晰度高,谢谢分享。感谢分享,下来看一下,这个原理没去深入的学过,对有电磁兼容问题的,现在我就是把地做的超大就解决了。。。 的确是关键,核心因数你是懂的、、、、👍接地和屏蔽技术这方面的资料还是非常少的,感谢楼主提供的资料,非常有价值,值得下载学习 学习了吸收了更有价值、、、非常好的电磁兼容方面的电子资料,很应该收藏学习非常好的电磁兼容方面的电子资料终于找到能下载的了
- btty038 RF/无线
设计资源 培训 开发板 精华推荐
- LT3091HDE LDO 稳压器在并联器件中的典型应用
- 稳压器(3.5V 至 5V)
- 使用 Microchip Technology 的 MIC4744YML 的参考设计
- AD9154-FMC-EBZ,用于评估 AD9154 四通道、16 位、2.4 GSPS、TxDAC+ 数模转换器的 FMC 评估板
- f103rct6
- DC2402A,基于 LT6237 轨到轨 SAR ADC 驱动器放大器的演示板
- DC988A-A,使用 LT3585EDDB-0、550mA I 输入、具有可调输入电流限制和集成 IGBT 驱动器的闪光灯充电器的演示板
- 使用 NXP Semiconductors 的 PPC5643LF0MMM 的参考设计
- ST1S14 3A降压型开关稳压器典型应用电路
- KA431SL 可编程并联稳压器的典型应用