- 描述
- 需要用到的相关寄存器
- GIPIO
- GPIO的工作模式
- CRL寄存器
- CRH
- IDR
- ODR
- BSRR
- BRR
- LCKR
- RCC
- APB2ENR
- 实验过程
- 确定LED灯的位置
- 库函数版本的程序
- 寄存器版本的程序
- 位带操作版本的程序
- 结果展示
- 总结
这是本喵的第一个实验,是第一次操作STM32实验两个LED灯间隔0.5秒轮流闪烁。使用的是STM32F103ZET6型号的单片机,下面本喵将详细说明下如何实现这个实验,虽然实验很简单,但是因为是第一个实验,所以本喵很想和大家分享一下。
需要用到的相关寄存器 GIPIOGIPIO是STM32里非常重要的外设,很多功能都是通过IO口来实现的,STM32F103ZET6的IO口有7组,分别是PA到AG,每一组IO口又16个IO口,编号是从0到16。不同型号的STM32中GPIO的数量也不一样,但是使用方法是一样的,而且每组GPIO中都有7个寄存器来控制IO的工作。
GPIO的工作模式IO口的工作模式有8种,有4种输入模式,4种输出模式,分别是
- 模拟输入模式(GPIO_Mode_AIN)
- 浮空输入模式(GPIO_Mode_IN_FLOATING)
- 上拉输入模式(GPIO_Mode_IPU)
- 下拉输入模式(GPIO_Mode_IPD)
- 开漏输出模式(GPIO_Mode_Out_OD)
- 复用开漏输出模式(GPIO_Mode_AF_OD)
- 推挽输出模式(GPIO_Mode_Out_PP)
- 复用推挽输出模式(GPIO_Mode_AF_OD)
本实验中我们使用的是推挽输出模式,至于这8中模式的区别以及应用场景本喵在次文中便不再介绍。
CRL寄存器叫做端口配置低寄存器(GPIOX_CRL),这个寄存器是一个32位的寄存器,每4位控制一个IO口,所以CRL控制的是一组IO口中的低8个IO口,每一组GPIO中都会有它。它用来配置IO的工作模式,决定IO是输入口还是输出口,当作输入口时是哪个输入模式,当作输出口时是哪个输出模式,并且输出速度是多少。
这是它的32个位,可以看到,每个MODE有两位,每个CNF有两位,一共就是4位,控制的一个IO口,后面的标号0到7是指从第1个IO口到第8个IO口。
先配置好MODE,这个IO口是输入模式,那就将MODE的俩位写成00,如果是输出模式,就将MODE的俩位写成其它三种情况,每种情况的输出速度是不一样的,参照上图来设置。
然后再来设置IO口的工作模式,通过对CNF写数字来确定IO的工作模式,参照上图来设置。
要用到哪个IO口就将对应的标号下的MODE和CNF写入相应的0或者1就配置了IO口。
叫做端口配置高寄存器(GPIOX_CRH),它也是32位的一个寄存器,与CRL一样也是控制着IO的工作方式,不同的是,CRH控制的是一组IO口中剩下的8个IO口,也就是从标号从8到15的IO口,配置方法和CRL一样,在对应标号下的MODE和CNF写1或者0即可,同样也是每一组GPIO中都有一个这样的寄存器。
IDR叫做端口输入数据寄存器(GPIOX_IDR),它同样是一个32位的寄存器,每组GPIO中都有一个,它的作用是读取IO口中的数据,送到CPU。
虽然IDR有32位,但是它只使用低16位,每一位对应的一个IO口,标号是从0到15的,想读取哪一个IO口,就读取对应标号的那一位即可,但是只能读低16位的,读取的结果是0或者1,如果读高16位的话,结果是0。IDR寄存器是只读类型的,不能写。
本喵此次的实验不会使用到这个寄存器,故不作太详细的介绍,后面用到的时候会详细介绍。
叫做端口输出数据寄存器(GPIOX_ODR),是一个32位的寄存器,每组GPIO中都有一个,它的作用是使IO口输出数据。
同样,它虽然是32位的,但是只使用低16位,而且是可读可写的,也就是即可以获得IO的数据也可以让IO口的输出数据。低16位控制着16个IO口,标号是从0到15的,想让哪个口输出0或者1,就对哪一位写0或者1就行,但是注意一点,写的时候只能以16位的形式写,例如让第3个IO口输出1,其他口输出0,就要对ODR寄存器写0000000000000100,着是二进制序列,为了方便,我们通常写16进制,就是0x0004。
本喵的此次实验中这个寄存器会使用。
叫做端口位设置/清除寄存器(GPIOX_BSRR),也是一个32位的寄存器,每一组GPIO中都有一个,该寄存器也是控制着IO的数据输出。
这是它的32个位,低16位和高16位都是控制着16个IO口。
将低16位中对应标号的位写1的话,对应的IO口就会输出1,将高16位对应标号的位写的话,对应的IO口就会输出0。对这32个位写0都不会有任何的效果。
虽然这个寄存器可以用来做此次实验,但是本喵没有使用它,在后面用到它的时候再做详细介绍。
叫做端口位清除寄存器(GPIOX_BRR),它也是一个32位的寄存器,每组GPIO中都有一个,但是它的作用只有清除,也就是写0。而且只能写。
这是它的32个位,只有低16位在使用,控制着16个IO口,需要对哪个IO口清零就在对应标号的位写1,并且只能以16位的形式写入。
这个寄存器同样没有使用到,后面用到的时候本喵会详细介绍。
叫做端口配置锁定寄存器(GPIOX_LCKR),同样是一个32位的寄存器,每组GPIO中都有一个,因为这个寄存器使用的情况不多,便不再介绍,后面如果有用到的话本喵再详细介绍。
以上便是每一组GPIO中都有的7个寄存器的介绍。
RCC它是控制时钟的,STM32在使用时必须先配置时钟,也就是给单片机一个节拍,让单片机按照这个节拍规律来工作,是非常重要的外设。
APB2ENR叫做外设时钟使能寄存器,本喵的此次实验仅使用到这一个时钟寄存器,所以就仅对它进行介绍,与时钟相关的寄存器还有其他,在后面使用到的时候会进行详细介绍。
它同样是一个32位的寄存器,但是只使用低16位,高16位保留。它控制着GPIO以及其他挂靠在APB2总线下的外设是否使能。
本喵仅使用GPIO,所以就对相应标号A到G的GPIO位下写1,使这一组IO口使能。它可以以字,半字,字节的形成进行访问。
通过看开发板原理图,我们可以看到,LED0是与PB5连接的,LED1是与PE5连接的,而且是共阳极接法,确定了哪个IO在工作我们就可以开始编程序了。
库函数版本的程序STM32官方提供了大量的库函数,为了用户更加方便的实验STM32的各种外设,下面先使用库函数来实现这个实验。
我们先要创建好这样一个文件假,并且将各种需要的库函数等内容放进相应的文件夹内,具体操作本喵就不说了。
下面我们来写程序
led.h中的代码
#ifndef __LED_H//防止头文件重复引用 #define __LED_H//当检测到有这个定义的时候便不再复制 void LED_Init(void);//LED初始化函数声明 #endif
led.c中的代码
#include "led.h"//引用led头文件,实质就是复制声明
#include "stm32f10x.h"//顶级头文件引用,只有使用到stm32的固件库就要引用
void LED_Init(void)
{
//IO口工作方式的确定
GPIO_InitTypeDef GPIO_InitStruct;//LED初始化结构体变量命名
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//IO口使用推挽输出模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//IO口中标号为5的引脚工作
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//IO的速度设置为50HZ
//LED初始化
GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化PB5口
GPIO_Init(GPIOE,&GPIO_InitStruct);//初始化PE5口
//LED灯初始状态设定
GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB5口初始状态是高电平,也就是灭
GPIO_ResetBits(GPIOE,GPIO_Pin_5);//PE5口初始状态是低电平,也就是亮
}
main.c中的代码
#include "stm32f10x.h"
#include "delay.h"//延时函数头文件
#include "led.h"
int main()
{
//配置时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//PB口时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);//PE口时钟使能
//延时初始化
delay_init();
//LED初始化
LED_Init();
//点灯循环
while(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_5);//PB5口低电平,LED0亮
GPIO_SetBits(GPIOE,GPIO_Pin_5);//PE5口高电平,LED1灭
delay_ms(500);//延时500ms
GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB5口高电平,LED0灭
GPIO_ResetBits(GPIOE,GPIO_Pin_5);//PE5口低电平,LED1亮
delay_ms(500);//延时500ms
}
return 0;
}
以上便是库函数版本的代码,库函直接将GPIO的各种寄存器配置都封装成了函数,我们只需要确定哪个IO口,什么工作模式,以及输出高低电平等参数赋给库函数即可。
寄存器版本的程序寄存器版本与库函数版本的区别是,寄存器版本是我们直接操作寄存器来达到目的,需要我们具体一步步来操作。
创建工程是与库函数版本相同的。
led.h中的代码
#ifndef __LED_H #define __LED_H void LED_Init(void); #endif
led.c中的代码
#include "led.h"//引用头文件,复制函数声明
#include "stm32f10x.h"//引用顶级头文件,只要使用到stm32的固件库就需要引用
void LED_Init(void)
{
//设置PB5为推挽输出,速度为50MHZ
//必须先清0再写1
GPIOB->CRL&=0xFF0FFFFF;//将MODE5和CNF5清零,并且不影响其他位
GPIOB->CRL|=0x00300000;//将MODE5和CNF5设置为0011,并且不影响其他位
//设置PE5为推挽输出,速度为50MHZ
GPIOE->CRL&=0xFF0FFFFF;//将MODE5和CNF5清零,并且不影响其他位
GPIOE->CRL|=0x00300000;//将MODE5和CNF5设置为0011,并且不影响其他位
//LED0初始化
GPIOB->ODR|=1<<5;//为了不印象其他IO口,使用左移的方式将1写到ODR5,输出高电平,灯灭
//LED1初始化
GPIOE->ODR&=(~(1<<5));//为了不印象其他IO口,使用左移取反的方式将0写到ODR5,输出低电平,灯亮
}
main.c中的代码
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
int main()
{
//配置时钟使能
RCC->APB2ENR|=1<<3;//为了不影响其他外设,使用左移的方式,将PB口使能
RCC->APB2ENR|=1<<6;//为了不影响其他外设,使用左移的方式,将PE口使能
//延时初始化
delay_init();
//LED初始化
LED_Init();
//点灯
while(1)
{
GPIOB->ODR&=(~(1<<5));//将PB5输出低电平,LED0亮
GPIOE->ODR|=1<<5;//将PE5输出高电平,LED1灭
delay_ms(500);//延时500ms
GPIOB->ODR|=1<<5;//将PB5输出高电平,LED0灭
GPIOE->ODR&=(~(1<<5));//将PE5输出低电平,LED1亮
delay_ms(500);//延时500ms
}
return 0;
}
以上便是寄存器版本的程序,可以看到,它主要是需要我们对用到的寄存器进行具体的操作,具体到对每一位的操作,没有库函数那样来的方便,但是需要了解,方便我们后续在出现问题时从库函数中找到寄存器的配置问题。
位带操作版本的程序位带是指,寄存器中的每个比特位都膨胀为一个32为的地址,我们对这个地址写0或者1就相当于给该寄存器的这一位写了0或者1,当然,读该地址时也相当于读了该寄存器这一位的数据。
可以看到,位操作是地址范围限制的,至于它是怎么膨胀的以及一个比特位与一个32位地址的对应关系本喵就不详述了。
它的方便之处就在于可以直接对寄存器的某一位进行操作,例如ODR5,我们直接使用库函数封装的位带操作PAout(5)=1,就可以将ODR5写1,不用通过左移和按位或等操作来实现写1,可以说非常的方便。
下面我们来看程序
当然,工程的创建和上面是一样的。
led.h中的代码
#ifndef __LED_H//防止头文件重复引用 #define __LED_H//当检测到有这个定义的时候便不再复制 void LED_Init(void);//LED初始化函数声明 #endif
led.c中的代码
#include "led.h"//引用led头文件,实质就是复制声明
#include "stm32f10x.h"//顶级头文件引用,只有使用到stm32的固件库就要引用
#include "sys.h"//位带操作库函数头文件引用
void LED_Init(void)
{
//IO口工作方式的确定
GPIO_InitTypeDef GPIO_InitStruct;//LED初始化结构体变量命名
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//IO口使用推挽输出模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//IO口中标号为5的引脚工作
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//IO的速度设置为50HZ
//LED初始化
GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化PB5口
GPIO_Init(GPIOE,&GPIO_InitStruct);//初始化PE5口
//LED灯初始状态设定
PBout(5)=1;//将LED0初始化为高电平,灯灭
PEout(5)=0;//将LED1初始化为低电平,灯亮
}
main.c中的代码
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
int main()
{
//配置时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//PB口时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);//PE口时钟使能
//延时初始化
delay_init();
//LED初始化
LED_Init();
//点灯
while(1)
{
PBout(5)=0;//PB5输出低电平,LED0亮
PEout(5)=1;//PE5输出高电平,LED1灭
delay_ms(500);
PBout(5)=1;//PB5输出高电平,LED0灭
PEout(5)=0;//PE5输出低电平,LED1亮
delay_ms(500);
}
return 0;
}
可以看到,位带操作与库函数以及寄存器版本的不同点主要就是控制IO口高低电平的输出的方式不同,而且位带操作更加的方便快捷。
结果展示将以上三种版本中的任何一种烧录的开发板上就可以看到下面这样的实验结果
当然,红绿灯是不停的交替闪烁的,这里仅以图片的形式给大家展示。
以上便是跑马灯实验的三个版本,库函数版本,寄存器版本,以及位带操作的版本,它们各有千秋,但是库函数版本是使用最多的,也是在使用STM32过程中使用最多的。



