本文章使用的STM32f103ZET6核心板,下面通过两种方法介绍STM32LED灯的点亮,第一种是通过直接用地址操作寄存器,第二种先将寄存器地址取别名再使用。介绍第一种方法主要是为了让大家了解点亮LED灯的底层逻辑,第二种方法是我们平常使用的,具有更好的可读性。
因为我是用的是野火STM32f103ZET6的板子,上图是LED模块,由上图可以知道这控制这三个LED灯的I/O口,分别是PB0、PB1、PB5。
这些I/O口是由端口输出数据寄存器(GPIOx_ODR(x=A..E))控制的。然后我们打开官方的参考手册找到端口输出数据寄存器
这个寄存器是32位的,但是只有低16位有效。每个I/O口由一个位控制,比如GPIOB有16个I/O,第0位就控制PB0。上图中有一个地址偏移,它是相当于GPIOB基地址的便移,使用这个寄存器是要加上这个地址偏移量接下来我么们就去找这个寄存器的地址 ,同样的在官方的参考手册中去找
由图可以得到GPIOB的起始地址是0x40010C00。然后加上寄存器的偏移地址就得到ODR寄存器的地址,位0x40010C0C。接下来通过代码将PB0置1
* (unsigned int * )0x40010C0C &= ~(1<<0);
由于0x40010C0C是个地址,学过C语言我们知道,不能直接对地址进行操作,所以要将地址强制转换类型位无符号整型然后解引用去操作这个寄存器。&=~(1<<0)这部分是将0x40010C0C与0x40010C0C中将1左移0位相与将ODR寄存器中的PB0置1。这里介绍以下为什么要使用&=~(1<<0)这个操作,因为ODR中还有其他的端口PB1、PB2...PB15,如果直接0x40010C0C=1会使得所有端口都置1,所以这样操作的目的就是为了只将PB0置1。
如上图,没4个位控制一个I/O口,拿PB0位例0和1这两位控制输出模式下最大速度,如果是输入就只能选择00,2和3这两位控制具体的输入和输出模式。这里点亮LED我们选择通用推挽输出模式和10MHZ的最大输出速度。因为这个寄存器的偏移地址是0x00,所以这个寄存器的地址位0x40010C00且0、1、2、3位位0001。
* (unsigned int * )0x40010C00 |= ((1)<<(4*0));
因为在这个寄存器中是4组为一个单位的,所以是((1)<<(4*0))。
通过系统框架图我们可以知道GPIOB挂在在APB2这个高速总线上,然后时钟由RCC控制,接下来通过参考手册找到 APB2外设复位寄存器
可以看到位3是控制GPIOB的 ,将位3置1使时钟打开。由上面系统框架图知道RCC在AHB系统总线上,接下来找到RCC的地址
上图可知RCC起始地址是0x40021000,别忘了加上它的偏移地址0x0C。
* (unsigned int * )0x40021018 |= ((1)<<3);
这样灯就点亮了。下面是完整代码
#include "stm32f10x.h"
int main(){
//打开GPIOB端口的时钟
* (unsigned int * )0x40021018 |= ((1)<<3);
//控制I/O口为输出
* (unsigned int * )0x40010C00 &= ~((0x0f)<<(4*0));
* (unsigned int * )0x40010C00 |= ((1)<<(4*0));
* (unsigned int * )0x40010C00 |= ((1)<<(4*1));
* (unsigned int * )0x40010C00 |= ((1)<<(4*5));
//控制ODR寄存器
* (unsigned int * )0x40010C0C &= ~(1<<0);
* (unsigned int * )0x40010C0C &= ~(1<<1);
* (unsigned int * )0x40010C0C &= ~(1<<5);
}
这样三个LED灯就全部点亮了。解释以下* (unsigned int * )0x40010C00 &= ~((0x0f)<<(4*0)); 这一行代码的作用,是为了将这个寄存器每一位都置0,因为如果在这之前使用过其他位,后面的操作并没有将其关闭,可能会出现错误,同时也是为了代码的严谨性。
使用封装地址点亮LED灯的基本步骤和上面方法一样,只是将各个寄存器的地址封装起来,这样可以提高代码的可读性,不然别人拿着你的代码还要去查地址才知道每个地址的具体功能。为了知道这个底层的逻辑,这里我们直接在stm32f10x.h文件里面进行封装。
封装的思路是选择一个地址为基地址,然后在其地址上加相应的偏移地址,这里我们选择起始地址0x40000000为基地址
stm32f10x.h代码如下
#define PERIPH_BASE ((unsigned int)0x40000000) //基地址
#define APB1PERIPH_BASE PERIPH_BASE //APB1基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) //APB2基地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000) //AHB基地址
#define RCC_BASE (AHBPERIPH_BASE + 0x1000) //RCC基地址
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) //GPIOB基地址
#define RCC_APB2ENR *(unsigned int * )(RCC_BASE + 0x18)
#define GPIOB_CRL *(unsigned int * )(GPIOB_BASE + 0x00)
#define GPIOB_CRH *(unsigned int * )(GPIOB_BASE + 0x04)
#define GPIOB_ODR *(unsigned int * )(GPIOB_BASE + 0x0C)
main.c代码如下
#include "stm32f10x.h"
int main(){
//打开GPIOB端口的时钟
RCC_APB2ENR |=((1)<<3);
//控制I/O口为输出
GPIOB_CRL &= ~((0x0f)<<(4*0));
GPIOB_CRL |=((1)<<(4*0));
GPIOB_CRL |=((1)<<(4*1));
GPIOB_CRL |=((1)<<(4*5));
//控制ODR寄存器
GPIOB_ODR &=~(1<<0);
GPIOB_ODR &=~(1<<1);
GPIOB_ODR &=~(1<<5);
}
上面的方法主要是让大家理解点亮LED灯的底层逻辑,以后我们写代码会使用官方的stm32f10x.h文件,里面都给你将各个器件的地址取了相应的别名,我们使用的时候和上面步骤一样,去原理图中找到所使用的端口,再去参考手册查看相应外设的功能,然后去stm32f10x.h文件找到外设的别名使用就可以了。