一,什么是位操作
之前我们已经介绍了库函数和寄存器控制LED跑马灯,其实无论使用哪一种方法(包括操作BSRR,BRR寄存器的方式)最终都是通过操作GPIO_ODR寄存器(32位寄存器只使用低16位)响应的位为该IO口赋值
那么什么是位操作?我们知道GPIO_ODR寄存器的每一位对应一个IO口的电平操作,而每一位实际是一个IO口地址的映射,位操作就是跨越寄存器映射,直接为这个地址进行赋值
在LED跑马灯-位操作的实验中我们将使用位操作的方式控制IO口输出高低电平
二,位与别名映射关系
1,支持位操作的两个内存区范围:
0x2000_0000-0x200F_FFFF // SRAM区中的最低1M
0x4000_0000-0x400F_FFFF // 片上外设区中的最低1M
2,地址映射关系计算:
对于SRAM位带区某比特,所在字节地址为A,位n(0<=n<=7),该比特在别名区的地址为:
AliasAddr=0x22000000+((A-0x20000000) * 8+n)*4=0x22000000+(A-0x20000000)*32+n*4
对于片上外设位带区某比特,所在字节地址为A,位n(0<=n<=7),该比特在别名区的地址为:
AliasAddr=0x42000000+((A-0x40000000) * 8+n)*4=0x42000000+(A-0x40000000)*32+n*4
三,位操作
寄存器每一个bit映射为一个32位地址,修改这个位,可直接修改其映射的地址达到操作位的目的
四,位操作的优越性
以前获取某个位的值:
先获取整个寄存器的值
掩盖不需要的位
位操作获取某个位的值:
从位带别名区读取状态位
位操作对硬件I/O密集型底层程序最有好处
以前的读-改-写需要三条指令,导致中间有两个可能被中断的空档
五,sys.h介绍
介绍一个sys.h文件对位操作进行了封装
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"
//计算寄存器地址addr下,第bitnum位映射的32位地址值
#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))
//GPIOx_ODR寄存器地址
#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
//GPIOx_IDR寄存器地址
#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
//位操作封装
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) // 操作GPIOA_ODR寄存器的第n个位-输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) // 操作GPIOA_IDR寄存器的第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
stm32f10x.h中找到GPIOA基地址GPIOA_BASE
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) // 在APB2总线下+偏移量0x0800
再找到APB2PERIPH基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
最终操作寄存器的相应的位
六,LED硬件连接:
连接方式:
LED0连接PB5引脚
LED1连接PE5引脚
七,LED跑马灯实现流程-位操作
1,使能IO时钟
调用函数RCC_APB2PeriphClockCmd
2,初始化GPIO
调用函数GPIO_Init();
3,使用位操作实现操作IO口输出高低电平
八,LED跑马灯-位操作代码
#include "stm32f10x.h"
#include "led.h"
#include "delay.h" // 此头文件中间接引用了sys.h头文件
int main(void)
{
delay_init();
LED_Init();
while(1){
PBout(5)=1; // PB5设置为高电平,LED熄灭
PEout(5)=1;
delay_ms(500); // 延迟500毫秒
PBout(5)=0; // PB5设置为低电平,LED点亮
PEout(5)=0;
delay_ms(500);
}
}