赞
踩
我们讲了GPIO的输出,虽然我们使用的是固件库编程,但是最底层的操作是什么呢?对,我们学习过51单片机的同学肯定学习过 sbit 修改某一位的高低电平,从而实现对于硬件的控制。那么我们现在在STM32中有没有相似的操作呢?答案肯定是有的。那么我们今天就来讲讲位带操作。
创作不易,点个三连不迷路!!!
- P0=0xFE; //总线操作
-
- sbit LED1 = P0^0; //位操作
- LED1 = 0;
如此这般,就是总线操作与位操作的区别(在51单片机中)。那么我们在32中该如何操作呢 位操作就是可以单独的对一个bit位进行读和写的过程。51 单片机中通过关键字 sbit 来实现位定义,而 STM32 是通过访问位带别名区来实现。
在 STM32 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,另一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别
名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些
字时,就可以达到访问位带区某bit位的目的。
如图所示, 我们以ODR寄存器为例,而位带操作就是把寄存器中的每一个位都重新找了个地址。这个地址在位带别名区内,而且在位带别名区里会膨胀成4个字节,但是操作的时候只有最低位有效(ODR0)。
在位带操作中,不止止是片上外设会有位带操作,而且SRAM也会有1MB的位带区,位带区里面的每一个位都可以通过位带别名区的地址来访问(一位为四个字节)。
结合上述例子,我们知道GPIO_ODR的基地址,那么我们怎么知道每一位所对应的地址呢?那么我们就有了位带区与位带别名区地址转换。
接着我就来给大家讲解一下转换的公式的具体含义及代码展示。
前一个呢是位带别名区地址,0x 4200 0000 和 0x 2200 0000.接着呢就是(A-0x 2000 0000),这个算出来的是偏移地址(字节)。一个字节有八个位,而一个位是四个字节。n*4为字节的偏移数,n为位号。从公式来看,我们只需要知道A以及n就可以算出位带地址。再编程上来说,可以统一用一个公式表示:
- // AliasAddr= =0x42000000+ (A-0x40000000)*8*4 +n*4
- // AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4
-
- // 把“位带地址 + 位序号”转换成别名地址的宏
- #define BITBAND(addr, bitnum)
- ((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2))
-
为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址 + 位序号”转换成别名区地址统一成一个宏。
addr & 0xF0000000 是为了区别 SRAM 还是外设,实际效果就是取出 4 或者 2,如果是外设,则取出的是 4,+0X02000000 之后就等于 0X42000000,0X42000000 是外设别名区的起始地址。如果是 SRAM,则取出的是 2,+0X02000000 之后就等于 0X22000000,0X22000000 是 SRAM 别名区的起始地址。addr & 0x00FFFFFF 屏蔽了高三位,相当于减去 0X20000000 或者 0X40000000。
外设的最高地址是:0X20100000,跟起始地址 0X20000000 相减的时候,总是低5位才有效,所以就把高三位屏蔽掉来达到减去起始地址的效果,具体屏蔽掉多少位跟最高地址有关。SRAM 同理分析。«5 相当于 *8*4,«2 相当于 *4。
最后我们就可以通过指针的形式操作这些位带别名区地址,最终实现位带区的bit位操作。
- // 把一个地址转换成一个指针
- #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
-
- // 把位带别名区地址转换成指针
- #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
拷贝一份上节课的代码,并稍作修改,使用条件编译使得上一部分代码编译,下一部分代码不编译。我们看到上一部分代码是使得LED2(即绿色)自动亮灭亮灭,中间稍作迟缓:
- #include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
- #include "bsp_led.h"
- #include "bsp_key.h"
-
- void Delay(uint32_t count)
- {
- for(;count!=0;count--);
- }
-
- int main(void)
- {
- LED_GPIO_Config();
- LED_KEY_Config();
- #if 1
- while(1)
- {
- //GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
- LED2(OFF);
- Delay(0xFFFFF);
- //GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
- LED2(ON);
- Delay(0xFFFFF);
- }
- #else
- while(1)
- {
- if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
- {
- LED1(ON);
- }
- if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
- {
- LED2_TOGGLE;
- }
- }
- #endif
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
那么输出怎么写呢?我们现在开始操作ODR寄存器,从公式上看,我们要先写进去通用的公式,然后用n代替参量写一个带参宏出来,然后强制转换为地址,并使用指针操作加上 * 。然后再定义一个GPIOB_ODR_Addr用来表示(GPIOB_BASE+0x0c)基地址加偏移量:
- #define GPIOB_ODR_Addr (GPIOB_BASE+0x0c)
- #define PBOut(n) *(unsigned int*)((GPIOB_ODR_Addr & 0xF0000000)+0x02000000+((GPIOB_ODR_Addr & 0x00FFFFFF)<<5)+(n<<2))
然后我们就可以在main函数中做修改,如此这般,我们就通过位带操作实现了操作寄存器从而实现LED灯的亮灭:
- while(1)
- {
- PBOut(0) = 1;
- Delay(0xFFFFF);
- PBOut(0) = 0;
- Delay(0xFFFFF);
- }
- #include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
- #include "bsp_led.h"
- #include "bsp_key.h"
-
- #define GPIOB_ODR_Addr (GPIOB_BASE+0x0c)
- #define PBOut(n) *(unsigned int*)((GPIOB_ODR_Addr & 0xF0000000)+0x02000000+((GPIOB_ODR_Addr & 0x00FFFFFF)<<5)+(n<<2))
-
- void Delay(uint32_t count)
- {
- for(;count!=0;count--);
- }
-
- int main(void)
- {
- LED_GPIO_Config();
- LED_KEY_Config();
-
- #if 1
- while(1)
- {
- PBOut(0) = 1;
- Delay(0xFFFFF);
- PBOut(0) = 0;
- Delay(0xFFFFF);
- }
- #else
- while(1)
- {
- if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==KEY_ON)
- LED1(ON);
- if(Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==KEY_ON)
- LED2_TOGGLE;
- }
- #endif
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
从原理图可知,我们要打开BLUE灯,就是打开PB1口,即把PBOut(0)改为PBOut(1)即可,其他的以此类推。
输入的话就该操作下面一部分代码了。首先我们要算出IDR的这个第0位地址,为:
- #define GPIOA_IDR_Addr (GPIOA_BASE+0x08)
- #define PAin(n) *(unsigned int*)((GPIOA_IDR_Addr & 0xF0000000)+0x02000000+((GPIOA_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))
-
- #define GPIOC_IDR_Addr (GPIOA_BASE+0x08)
- #define PCin(n) *(unsigned int*)((GPIOC_IDR_Addr & 0xF0000000)+0x02000000+((GPIOC_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))
那么我们发现我们写的Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN)和Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN)函数就没有用了,我们在这里修改为:
- while(1)
- {
- // if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==KEY_ON)
- // LED1(ON);
- // if(Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==KEY_ON)
- // LED2_TOGGLE;
- if(PAin(0)==KEY_ON)
- {
- while(PAin(0)==KEY_ON);
- LED1(ON);
- }
- if(PCin(13)==KEY_ON)
- {
- while(PCin(13)==KEY_ON);
- LED2_TOGGLE;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里我们使用了两种方式来实现按键控制LED灯的亮灭,分别控制PA0和PC13端口。
- #include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
- #include "bsp_led.h"
- #include "bsp_key.h"
-
- #define GPIOA_IDR_Addr (GPIOA_BASE+0x08)
- #define PAin(n) *(unsigned int*)((GPIOA_IDR_Addr & 0xF0000000)+0x02000000+((GPIOA_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))
-
- #define GPIOC_IDR_Addr (GPIOA_BASE+0x08)
- #define PCin(n) *(unsigned int*)((GPIOC_IDR_Addr & 0xF0000000)+0x02000000+((GPIOC_IDR_Addr & 0x00FFFFFF)<<5)+(n<<2))
-
- int main(void)
- {
- LED_GPIO_Config();
- LED_KEY_Config();
- while(1)
- {
- if(PAin(0)==KEY_ON)
- {
- while(PAin(0)==KEY_ON);
- LED1(ON);
- }
- if(PCin(13)==KEY_ON)
- {
- while(PCin(13)==KEY_ON);
- LED2_TOGGLE;
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
到这里我们的课程就结束啦,从下一节开始就是中级篇的讲解了,我们下次见咯!
创作不易,点个三连不迷路!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。