目地:掌握STM32基本的IO口使用。
内容:通过寄存器方式实现跑马灯效果。
STM32的IO口可以由软件配置为如下8种模式,但必须是以32位字被访问。如:0XFFFF FFFF;
8种模式分别为: 1:输入浮空。浮空悬空,特点电压的不特定性,可能是VCC,也可能是0V,易受干扰,呈高阻态,做
2:输入上拉。通过弱上拉将电平拉至VCC,将不确定的信号通过一个电阻嵌位在高电平.
3:输入下拉。通过弱下拉将电平拉至GND,将不确定的信号通过一个电阻嵌位在低电平.
4:模拟输入。做ADC时使用。
5:开漏输出。不输出电压,低电平时接地,如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻的
电源电压,这种方式适合在连接的外设电压比单片机电压低的时候。
6:推挽输出。单片机引脚可以直接输出高电平电压。低电平时接地。
7:开漏复用功能。可以理解为GPIO口被用作第二功能时的配置情况
8:推挽复用功能。可以理解为GPIO口被用作第二功能时的配置情况
每个IO口 由STM32 7个寄存器控制:
1:GPIOx_CRL
2 : GPIOx_CRH
CRL CRH为配置模式的寄存器,低8位与高8位区别以32位字被访问,举例GPIOx_CRL
从上图可以看出,CRL控制每组IO口(A-G)低8位模式,每个IO端口占用CRL的4个位,高两位为CNF,配置输出或者输入的模式,低两位MODE,选择输入或者输出。
举例,如果我们要配置PORTA.11为上拉输入
GPIOA->CRH&=0XFFFF 0FFF //清除之前的设置,同时不影响其它位
GPIOA->CRH|=0X0000 8000 //第11位 1000 CNF:10:上拉或者下拉输入。MODE:00:输入模式
GPIOA->ODR|=1<<11; //第11位配置为上拉输入
3:GPIOx_ODR 能控制管脚为高电平,也能控制管脚为低电平。管脚对于位写1 gpio 管脚为高电平,写 0 为低电平
4:GPIOx_IDR 只读寄存器,读状态
5:GPIOx_BSRR 只写寄存器:既能控制管脚为高电平,也能控制管脚为低电平。对寄存器高 16bit 写1清除对应的ODRy位为0
对寄存器低16bit 写1设置对应的ODRy位为1。写 0 ,无动作
6:GPIOx_BRR 只能改变管脚状态为低电平,对寄存器 管脚对于位写 1 相应管脚会为低电平。写 0 无动作
7:GPIOx_LCKR 端口配置锁定寄存器,具体不了解。
到此,我们可以写出LED灯的初始化
通过上图了解到 LED0,LED1是分别连到PB5 PE5,只能配置这两个IO为推挽输出高即可,注 在配置 STM32 外设的时候,任何时候都要先使能该外设的时钟!
#include"led.h"
void LED_Init(void)
{
RCC->APB2ENR|=1<<3; //使能PORTB时钟
RCC->APB2ENR|=1<<6; //使能PORTE时钟
GPIOB->CRL&=0XFF0F FFFF ;
GPIOB->CRL|=0X0030 0000;
GPIOB->ODR|=1<<5;
GPIOE->CRL&=0XFF0F FFFF;
GPIOE->CRL|=0X0030 0000;
GPIOE->ODR|=1<<5;
}
APB2ENR是APB2总线上的外设时钟使能寄存器,使能哪组IO口需将其对应BIT位写1:
下面写LED.H
#ifndef __LED_H
#define __LED_H
#include"sys.h"
#define LED0 PBout(5)
#define LED1 PEout(5)
void LED_Init();
#endif
这个头文件,有两个需要注意的地方,一是位带操作,二是预编译
#define LED0 PBout(5) 关于这一句,要复习一下位带操作
位带操作:顾名思义,就是开发人员可以单独对CPU寄存器的某一位进行读写操作
我们在以前学习51单片机时,对某一位IO位进行操作时 sbit LED=PX^n; LED=1,然而STM32不允许这样操作,那么为了方便能像51单片机那样对某一位IO口进行操作,就引入了位带这个概念。
在CM3中有两个区域可以进行位带操作,其一是SRAM中最低1MB范围,其二是片内外设区最低1MB范围,这两个区中的地址除了可以像普通RAM一样使用外,它们都有自己的位带别外区,
位带别名区是把每个比特膨胀位32位的字,当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。
公式:AliasAddr=0x2200 0000+(A-0x2000 0000)*32+n*4
例1:位带区0x2000 0004.1,求出他的位带别名区地址
AliasAddr=0x2200 0000+(0x2000 0004-0x2000 0000)*32+1*4
=0x2200 0000+80+4
=0x2200 0084
和上面图表中一致
再来看看LED.H头文件中对LED0的定义 #define LED0 PAout(8) // 用LED0代替PA8
看一下对PAout(n)的定义:#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
{ #define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)
} 从上面的定义可以看出,GPIOA_ODR_Addr的地址
=0x4000 0000+0x10000+0x0800+12=0x4001080C
这个GPIOA_ODR_Addr位带区的地址为0x4001080C
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
把这句话层层分解:1: 定义 BIT_ADDR(addr,bitnum) 代替 MEM_ADDR(BITBAND(addr,bitnum))
MEM_ADDR(BITBAND(addr,bitnum))
可以分解为两条语句 1: MEM_ADDR(addr) *((volatile unsigned long *)(addr))
*((volatile unsigned long *)(addr)) 这句话的意思是:是把这个地址常量addr强制转化为volatile unsigned long 类型指针,
再取地址addr里的数据。 {这和我们之前学的指针一样,int a =10,int *p, p=&a,如果要对指针变量P所指的地址赋值,或者要读取指针变量P所指地址中的数据时 int b=*p, *p=20,或者是 int b=*(&a), *(&a)=20}.
2: BITBAND(addr,bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
前面已经讲过 支持位带操作的两个区域:1:0X2000 0000--0X200F FFFF(SRAM最低1MB)
2: 0X4000 0000--0X400F FFFF(片上外设区最低1MB)
1: AliasAddr = 0x22000000+((A-0x20000000)*8+n)*4=0x22000000+(A-0x20000000)*32+n*4
2: AliasAddr = 0x42000000+((A-0x40000000)*8+n)*4=0x42000000+(A-0x40000000)*32+n*4
((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
1)addr & 0xF0000000 意思是取出最高的4位,其实就是用于区别SRAM(0x20000000)还是片上外设(0x40000000)
2)++0x2000000 对于SRAM位带区则得到 0x22000000,对于片上外设位带区则得到0x42000000
3)addr &0xFFFFF 就是addr&0x000F FFF ,屏蔽高12位,对于SRAM就是0X200F FFFF 对片上外设是0X400F FFFF
4)(addr &0xFFFFF)<<5) 相当于(A-0x40000000)*32 1<<5=1*2^5;
5) (bitnum<<2) 相当于 n*4 1<<2=1*2^2;