目录
引言
前面我们学习了中断的理论知识,其中我们介绍为什么STM32需要中断时给出过一个中断的案例,也就是按键检测的例子,现在呢,我们就来动手操作一下,亲自实现以下这个按键中断案例。
一、案例需求描述
利用外部中断检测按键KEY-3,当按键按下,翻转LED1显示。
二、硬件电路设计
2.1 LED硬件电路
由LED相关原理图容易知道,LED一端接3V3电压,意味着要让LED这段线路导通,则需要在另一端接地或者说给一个低电平,换句话说,要想点亮LED,则需要给连接LED-1的端口输出低电平;反之,关闭LED则输出高电平。同时,我们容易知道控制LED-1的GPIO端口为GPIOA部分的第0个端口PA0,根据以往点亮LED经验,我们这里仍然给控制LED的GPIO端口一个最大翻转速度的推挽输出模式,即设置PA0的工作模式为最高速度的通用推挽输出模式。
2.2 按键硬件电路
由按键相关原理图得知,控制KEY-3按键的GPIO端口为GPIOF部分的第10个端口PF10,联系本次案例需要,我们需要借助外部中断检测按键,所以关于中断的配置也离不开控制按键的端口PF10了,同时我们需要利用按键来点亮或熄灭LED,意味着我们要读取按键对应端口的电平状态,因此其工作模式需要处于输入模式。
其次,我们思考前面学习的STM32的中断体系架构,能够意识到对于GPIO端口我们会进行7合一的复用功能,同时给到外部中断控制器EXTI处理,这个我们后面再说。
然后我们再看看按键的电路原理图,可以发现按键的一端连着3V3电压,意味着当按键按下时输入PF10的端口将处于高电平状态,而按键没有按下时PF10端口默认处于浮空输入,但是我们要借助外部中断进行按键检测,因此需要让控制按键的端口提前默认处于低电平状态,当按键按下时才会出现一个“突发状态”——输入PF10端口电平有低电平突然变为高电平产生的一个上升沿,这个状态刚好可以作为我们开始处理中断的位置,也可以说是断点,所以为了使PF10端口默认处于低电平,这时候我们需要用到前面介绍的GPIO工作模式中的下拉输入模式了,这里我们放一张之前讲述工作模式时看的GPIO的框图中上下拉部分
容易看出,我们只要让PF10接一个下拉就能实现PF10默认处于低电平,但是我们又能想到,配置GPIO的寄存器把输入模式中的上下拉放在了一起,所以我们还将需要手动让端口输出低电平,这样才能让上下拉输入模式默认为下拉输入模式。
这里关于按键我们可能还要考虑一个问题:关于按键抖动的问题,因为我们按键按下时可能出现没有完全按下,也就是没有按实的情况,或者松手也可能出现同样问题。也就是说,按键按下或者松手时可能出现10-15ms的抖动,导致高低电平突变处出现毛刺或者波浪形,可能就会造成此时控制的LED一直闪烁或者似乎不太受控制一样的情况。如下图所示状态
当然可能我们自己感受不到,因为对我们来说那是非常快的一瞬间,但是对于单片机处理时就是一段很长的时间了,因为单片机处理都是在微秒级别,所以对他来说10ms已经是很长的一段时间,然后这里我们的开发板没有做硬件消抖,因此我们在后面进行软件设计时需要在程序中加一段软件消抖的代码。
好,我们对上述分析总结一下:
1. LED-1对应PA0端口,最高速度的推挽输出模式,输出高电平,LED点亮,反之熄灭;
2. 按键KEY-3对应PF10端口,需要下拉输入,同事后续要考虑中断时的复用功能,按键按下则输入高电平,按键未按则输入低电平;
3. 开发板对按键未进行硬件消抖处理,需要后续进行软件消抖。
三、软件设计
由于本次案例将基于寄存器实现,同时本次案例中涉及到中断处理,通过我们前面对中断的学习容易发现,处理中断过程中会涉及到更多的寄存器。因此,创建工程之前,我们先了解一下后面需要用到的寄存器。
3.1 相关寄存器介绍
要了解寄存器,那必然少不了对应的参考手册,因此我们要准备好一份STM32F103xxx的参考手册,我们这里看的是中文版参考手册。
3.1.1 时钟相关寄存器
根据上面对硬件电路的分析,我们需要用到两个GPIO端口,分别是PA0和PF10,因此必然少不了开启时钟的寄存器,同时我们控制按键的端口PF10由于中断处理会使用复用功能,即前面提到的AFIO复用功能选择器,这个他也需要在这开启时钟。关于为什么用的是APB2外设时钟使能寄存器,主要还是和我们用到的端口相关内存映射有关系,这些在参考手册中是可以找到的。
如下图(AFIO也属于APB2总线下,我这里没标出来,大家应该可以看见就在下方)
根据寄存器描述,联系我们使用的是PA0和PF0以及复用功能AFIO,因此我们后续开启GPIOA和F以及AFIO的时钟即可,也就是给这个寄存器中7位和2位置1即可
参考代码如下
// 1. 开启GPIO时钟 这里本来是赋值,但STM32已经提供了相应的宏定义,直接用即可
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // PA0
RCC->APB2ENR |= RCC_APB2ENR_IOPFEN; // PF10
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // AFIO
3.1.2 配置寄存器
然后就是配置GPIO工作模式的寄存器,它分为低和高,低寄存器是配置我们每组GPIO端口的前8个、高寄存器是配置每组GPIO端口的后八个,由于每组GPIO都是16个,,非常类似,所以在STM32中对赋值时设定的宏定义是复用的,代码中会体现出来。
根据前面分析,我们要给PA0最高速度的通用推挽输出模式,而PA0属于GPIOA中的前8个,因此在配置低寄存器中进行。配置如下图,对配置低寄存器的值给mode0处11、cnf0处00即可。
同理,我们要给PF10配置为下拉输入,由于PF10属于GPIOF中后8个,所以要在配置高寄存器中进行。如下图,给8、9、10、11位0001,即mode10给00,cnf10给10即可。
参考配置代码如下
GPIOA->CRL &= ~GPIO_CRL_CNF0; // PA0配置
GPIOA->CRL |= GPIO_CRL_MODE0;
GPIOF->CRH &= ~GPIO_CRH_MODE10; // PF10配置
GPIOF->CRH |= GPIO_CRH_CNF10_1;
GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
3.1.3 输入数据寄存器
关于上下拉输入的上下拉是同时存在的,所以默认哪一个需要我们提前让他输出一个电平来实现,因此我们再介绍一个GPIO的寄存器——输出数据寄存器。
我们让PF10默认下拉,也就是默认是低电平,因此提前输出一个0,让他处于默认下拉
GPIOF->ODR &= ~GPIO_ODR_ODR10; // 默认下拉
3.1.4 AFIO引脚复用相关
处理外部中断时,我们会需要进行7合一的引脚复用,这个时候会涉及到AFIO相关的寄存器,前面介绍中断时说过,经7合一后,也就是7根线连一块进入EXTI时,我们并不知道是7根线的哪一个你要发生中断,所以这里我们就需要在一个寄存器中给他配置。如下图,
这里我们是对控制按键的端口PF10进行配置,因为7合1时,是每组GPIO各自一条线合并为1条线,所以每组GPIO的第0个端口线合成的一条线进入EXTI时被称作了EXTI0,同理,我们这是GPIOF中第10条线,也就是说这是合并在第10根线上的某一条,即叫做EXTI10
相比有朋友会问:等等,为什么我用的是上图这个寄存器呢?实际上,我们发现一个寄存器只能配置四根EXTIx的线,而前面说过,我们进入EXTI的线有10条,其中16条是端口相关的线,同时一个寄存器他只用了低16位,也就是说,我们这16根线使用了4个外部中断寄存器,按顺序数,0-3、4-7、8-11、12-15,我们是10,也就是说,我们使用的寄存器应该是第三个,也就是我们上图所示的这个。
因此,我要配置的位置就是图中外部中断寄存器3中8-11位,然后我们看图下方对每一部分的配置说明可知,如果时PFx引脚,则给寄存器0101即可,因此结合EXTI10和给0101这俩方面,我们就能知道:这个寄存器的配置就是给EXTI10所在位0101即可。
// STM32用数组存放寄存器,我们是在第三个配置寄存器中配置,所以这里是下标2
AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
3.1.5 EXTI配置
根据前面讲中断体系架构来看,AFIO复用以后进入EXTI,接着就是看EXTI内部的处理,而前面我们也详细讲述了EXTI内部的状态,当时也是涉及到了一些寄存器,大家还记得吗?也就是如下图关于EXTI的内部结构框图
所以,首先要配置关于边沿检测的寄存器——上升沿或者下降沿触发选择寄存器,前面分析按键的硬件电路时也发现了,我将借助上升沿来触发中断,所以需要对上升沿触发寄存器进行配置,根据寄存器描述容易知道,即下图9.3.3中寄存器的TR10置1即可
然后,我们还需要配置中断屏蔽寄存器,当然,我们选择不屏蔽该中断,所以根据下图寄存器对位的说明可知,对MR10置1即可
参考代码如下
// 3. EXTI配置 边沿检测类型、中断是否屏蔽
EXTI->RTSR |= EXTI_RTSR_TR10;
EXTI->IMR |= EXTI_IMR_MR10;
3.1.6 清除中断标志位
在详细介绍EXTI内部的寄存器时我们说过:在配置EXTI时,其挂起寄存器时硬件自动产生的挂起,但是最后中断处理开始后需要我们自己手动清除挂起状态,所以这里我们在进入中断处理程序后需要自己给挂起寄存器相应位配个值
如上图,根据挂起寄存器的描述,我们知道给PR10位置1即可清除挂起状态。
参考代码如下
// 进入中断处理程序后清除中断标志位
EXTI->PR |= EXTI_PR_PR10;
3.2 工程创建
沿用前面模块化创建keil工程的四路,我将直接复制之前创建的项目工程,然后更改名字,删除后面产生的文件,最后在Hardware中添加我们后面要用的按键源文件和头文件的空文件即可。然后进入该keil工程进行相关配置就可以了,最后效果如下图
(1)文件目录效果
(2)Keil工程中的效果
新加了Hardware/KEY目录,放的按键文件
点开魔法棒。在C/C++中包含新加文件Hardware/KEY路径
然后,进入Debug,修改一下调试驱动为STLink,并进入settings,如下图效果
由此,我们工程就创建好了,接下来就进入VSCode编写代码即可
3.3 代码编写
进入VSCode,打开我们创建好的工程文件,前面已经讲过多次,这里就不再赘述,有不清楚的朋友可以看看我前面讲述案例实现的文章。
3.3.1 key.h
我们先到我们新创建的key.h中,这是一个按键头文件,所以我们首先要编写一个头文件必要的内容:避免头文件重复编译的内容+必要的头文件包含+我们要做的函数声明
(1)关于必要的头文件
按照前面的分析,首先我们肯定是要对按键相关端口做一个配置,然后由于中断处理也和按键有关,所以这里我们还要对中断处理的相关内容(如AFIO、EXTI、NVIC等)进行配置,做配置必然用到大量宏定义,则是STM32的头文件提供的,所以stm32f10xxx的头文件需要有;
同时还有前面特别提到的关于按键的软件消抖程序,这里我们实现方式就是在按下时延时1ms使程序跳过抖动发生的时间段,然后判断此时按键端口是否还处于高电平,如果处于则继续执行按键按下的程序,反之不执行。所以我们还会需要用到延时函数的头文件,因为关于延时函数的实现在我们起那么编写流水灯案例时就已经实现过,所以这里我们直接用就行。
最后,我们因为是用外部中断检测按键按下嘛,所以那个中断处理程序我们也会放在key.h中编写,当然关于中断处理函数我们不是随便命名,它再STM32中有专门的名字,我们后面再说。所以既然中断处理程序也在key,h中,而该案例需求就是发生中断然后点亮或熄灭LED,因此我们将在中断处理程序中编写LED相关代码,故我们还要包含LED相关的头文件,这个我们前面也已经编写过,所以直接用就行,如果有朋友不清楚的,可以去看看我前面关于流水灯的相关案例的文章,这里就不再赘述。
因此,总结一下:我们需要再key.h中包含三个头文件,分别是stm32f10xxx.h、Delay.h以及LED.h
(2)要用到的函数声明
(1)中我们已经提到,关于按键,我们实际上只需要添加两个东西,一是关于按键的一些配置,包括按键的端口配置以及中断处理需要的配置(如AFIO、EXTI以及最后NVIC相关的库调用),而这些我们都可以作为案件的初始化看待,因此只需要一个按键初始化函数声明即可;二是中断处理程序,因为其函数声明是系统已经规定好了的,所以我们不需要再次声明,只需要对函数进行实现就行,所以在按键头文件不需要加,只需要在源文件编写就行。
根据以上叙述,我们需要在key.h中添加的内容如下:
1. 防止头文件重复编译
2. 三大头文件引入
3. 按键初始化函数声明
参考代码如下
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
// 按键初始化
void Key_Init(void);
#endif
3.3.2 key.c
3.3.2.1 GPIO与中断相关配置
头文件中我们既然已经想到了这个按键初始化,所以我们现在就转到key.c中进行实现吧。其实我们前面的分析已经基本上写的差不多了。
首先是按键对应GPIO端口的配置,也就是PF10端口的配置,包括开启时钟、设置工作模式;然后我们这个端口还要复用功能,所以对AFIO还要开启时钟;
接着就是我们七合一线路进入EXTI时,要配置使得知道EXTI处理的是7个口的哪一个端口的中断,也就是上面我们对AFIO的外部中断寄存器的配置;
然后就是关于EXTI相关寄存器的配置,包括边沿检测方式的选择(上升沿还是下降沿)、中断是否屏蔽的选择;
最后就是把中断交给NVIC大总管进行处理,即按照中断优先级进行调配,打开NVIC使能,把中断交给内核处理。
这就是整个按键初始化要做的操作了,下面时参考代码
void Key_Init(void)
{
// 1. 开启时钟 GPIO时钟和复用功能AFIO的时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPFEN; // 一个系列的芯片存在不同数量的引脚的芯片,所以要手动开启我们这款芯片类型对应的宏定义才能识别
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 2. GPIO工作模式配置 下拉输入 上下拉在一块,所以要给值使得默认为下拉即低电平给0
GPIOF->CRH &= ~GPIO_CRH_MODE10; // mode 00
GPIOF->CRH |= GPIO_CRH_CNF10_1; // cnf 10
GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
GPIOF->ODR &= ~GPIO_ODR_ODR10; // 默认下拉
// 2. AFIO配置引脚复用选择 选PF10 第三个寄存器的EXTI10中PF10 0101
AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
// 3. EXTI配置 边沿检测类型、中断是否屏蔽
EXTI->RTSR |= EXTI_RTSR_TR10;
EXTI->IMR |= EXTI_IMR_MR10;
// 4. NVIC配置 设置优先级的分配模式,给中断单独设置优先级,开启NVIC使能
NVIC_SetPriorityGrouping(3); // 全部是抢占优先级
NVIC_SetPriority(EXTI15_10_IRQn, 3); // 单独设置优先级值
NVIC_EnableIRQ(EXTI15_10_IRQn); // 开启NVIC使能
}
3.3.2.2 NVIC调库代码详解
上面代码关于NVIC调库函数部分进行解释一下:
第一句NVIC_SetProrityGrouping(3); 这是我们NVIC首先给中断优先级进行分配,我们优先级一共是16中,也就是四位表示,然后前三个是固定的不可手动修改的内部异常,所以分配模式是从011开始。通常我们选择全部是抢占优先级,也就是全部是优先级组就行,根据我们前面讲中断所述的也就是第一种分配模式,用011表示,换句话说我们给他置为3即可;
第二句NVIC_SetPrority(EXTI15_10_IRQn, 3); 这句就是我们前面对这次中断进行优先级组和子优先级的分配以后,在单独对我们所进行的中断进行优先级设置。我们看看这个函数形参类型如下图
可以看出第一个参数是我们中断类型,第二个是设置的优先级值。然后我们这个端口PF10是进入EXTI的第10根7合1的线,也就是EXTI10,意味着我们中断类型按理说应该是是EXTI10,但是我们点进这个中断类型看看我们这个EXTI10的时候发现并没有,如下图
我们进入一看,发现这是个枚举类型,里面放的就是全部的中断源,然后对应了序号,可以叫中断号。然后这里有注释说明,这并不是我们这款大容量的芯片,当然芯片不同,但是里面中断基本都是一样的,所以我们直接看即可。然后里面包含了大量的中断,但是我们找一圈都没有发现哦我们的EXTI10,为什么呢?实际上是这个原因:我们进入EXTI的7合一的线中,有几条是7合一以后继续合并了几条才进入EXTI的,所以他有几条EXTI线单独会找不到,毕竟适合在一块了,那么名字也就不同了,我们看一看手册中的中断向量表也能发现:
上图是我从手册中的中断向量表中截取的所有EXTI线的中断,不难发现,EXTI0-4都还是7合一的线,但是到了5开始就连着几根线有合并成了一根线,然后15-10也是一样合并成了一根线,所以大家应该能想到,既然被合并,你们后面底层一定还会经过配置而分开,这里我们大概知道就行。所以呢,我们在回过头来看我们的第10根线,那就不难明白应该是哪一个中断了吧?没错,就是对应中断向量表中的EXTI15-_10,大家还是担心的话,那我们去VSCode中底层代码中找找看有没有:
我们经过查找,很明显就找到了,说明他确实存在。因此第二条NVIC库函数的调用第一个关于中断类型的参数我们就填EXTI15_10即可,然后第二个参数是设置的优先级值,我们这里给一个3就行,因此整条语句就是
然后就是最后一条语句,NVIC_EnableIRQ(EXTI15_10_IRQn);这个就不用多说了,这就是NVIC把中断交给内核处理之前需要打开的中断使能了,里面填上我们对应的中断源(也就是中断号)就行。
3.3.2.3 完善中断处理程序
最后呢,我们再在按键源文件加一个中断处理程序即可。
前面我们说了,中断处理程序的函数声明是系统自己规定好了的,那么我们要去启动文件中的汇编文件.s里面就能看见:
我们打开启动文件中对应的汇编文件,往下翻会看见如上图的部分代码,即便是我们不认识汇编语言,但是看__Vextors向量,然后看下面的内容,我们也能想到这不就是前面看的中断向量表类似的玩意吗?确实,实际上我们中断向量表就是在STM32启动开始工作时就会把整个中断向量表经过汇编文件加载进来,前面说过中断向量表可看作是中断名称与地址的对应关系,也就是说这描述了中断处理程序的入口。然后在汇编文件中这些实际上就可以看做我们中断处理程序的接口,也就是我们完善相应中断处理程序要用的函数声明,并且他们都是不可修改的,也没有形参和返回值。
因此,我们现在要完善EXTI10的中断处理程序,也就是EXTI15_10的中断处理程序,即在里面找到对应的程序入口,函数声明:(直接CTRL+F搜索即可快速找到)
如上图,我们找到了对应的中断处理程序入口,现在直接复制这个名称,同时其无返回值和形参,然后在key.c中填充内容即可
讲述寄存器时说过,当开始执行中断处理程序时,我们需要清除中断标志位,也就是清除挂起状态,所以我们防止忘记,以后中断处理程序第一条语句就是清除中断标志位
// 清除挂起状态(清除中断标志位)
EXTI->PR |= EXTI_PR_PR10;
接着,我们就要进行软件消抖了。因为当我们按下的一瞬间,就会触发中断,所以我们在进入中断处理程序后进行按键消抖,前面我们提到了消抖的大致思路,就是延时嘛,这里我就直接上代码了!也就一条
// 消抖延时
Delay_ms(10);
// 如果还是高电平就翻转LED状态 1则为高电平
if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0)
{
}
然后,就是本次案例继按键中断以后最核心的部分了,上面这代码注释已经暴露了哈哈,每次,就是在按键按下时点亮LED,再按就熄灭LED,也就相当于是我们按下按键就会翻转LED状态的意思,由于我们之前已经写过LED一些功能的函数并且用文件包装了,所以这里就直接拿来用了,关于LED相关代码我后面会一起贴上。因此,加一个翻转LED就行,代码如下
// 如果还是高电平就翻转LED状态 1则为高电平
if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0)
{
LED_Toggle(LED1);
}
好的,关于中断处理程序的代码到这里也就结束了,现在贴上中断处理程序的完整代码:
// 中断处理程序
void EXTI15_10_IRQHandler()
{
// 清除挂起状态
EXTI->PR |= EXTI_PR_PR10;
// 消抖延时
Delay_ms(10);
// 如果还是高电平就翻转LED状态 1则为高电平
if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0)
{
LED_Toggle(LED1);
}
}
OK,最后呢,我再把key.c的完整代码再放一遍哈,大家可以根据需要选择不同的代码段进行参考:
#include "key.h"
void Key_Init(void)
{
// 1. 开启时钟 GPIO时钟和复用功能AFIO的时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPFEN; // 一个系列的芯片存在不同数量的引脚的芯片,所以要手动开启我们这款芯片类型对应的宏定义才能识别
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 2. GPIO工作模式配置 下拉输入 上下拉在一块,所以要给值使得默认为下拉即低电平给0
GPIOF->CRH &= ~GPIO_CRH_MODE10; // mode 00
GPIOF->CRH |= GPIO_CRH_CNF10_1; // cnf 10
GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
GPIOF->ODR &= ~GPIO_ODR_ODR10; // 默认下拉
// 2. AFIO配置引脚复用选择 选PF10 第三个寄存器的EXTI10中PF10 0101
AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
// 3. EXTI配置 边沿检测类型、中断是否屏蔽
EXTI->RTSR |= EXTI_RTSR_TR10;
EXTI->IMR |= EXTI_IMR_MR10;
// 4. NVIC配置 设置优先级的分配模式,给中断单独设置优先级,开启NVIC使能
NVIC_SetPriorityGrouping(3); // 全部是抢占优先级
NVIC_SetPriority(EXTI15_10_IRQn, 3);
NVIC_EnableIRQ(EXTI15_10_IRQn);
}
// 中断处理程序
void EXTI15_10_IRQHandler()
{
// 清除挂起状态
EXTI->PR |= EXTI_PR_PR10;
// 消抖延时
Delay_ms(10);
// 如果还是高电平就翻转LED状态 1则为高电平
if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0)
{
LED_Toggle(LED1);
}
}
3.3.3 主程序main
离成功近在咫尺了!我们已经完整了最主要的代码,现在我们在按照逻辑把代码放进mian函数即可。对于整个按键按下翻转LED状态的逻辑来说,首先我们想想应该就是先做一些初始化,比如LED的初始化、按键的初始化就行;接着......似乎就没有什么了,因为我们就是对按键的控制而已,然后中断处理程序我们不需要动,只要实现就行,LED的代码也是在中断处理程序中。所以我们只需要在main函数中添加LED和按键的初始化,同时不要忘了按键和LED的头文件要在main文件中包含哦!
参考代码如下
#include "LED.h"
#include "key.h"
int main(void)
{
// LED和按键初始化
LED_Init();
Key_Init();
// 4. 死循环保持状态
while(1)
{
}
}
3.3.4 其它代码
最后把我们直接用的LED的代码和Delay的代码都贴上
3.3.4.1 LED代码
1、LED.h
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
// 宏定义
#define LED1 GPIO_ODR_ODR0
#define LED2 GPIO_ODR_ODR1
#define LED3 GPIO_ODR_ODR8
// LED初始化
void LED_Init(void);
// 点亮指定LED灯
void LED_On(uint16_t led);
// 关闭指定LED灯
void LED_Off(uint16_t led);
// 打开一组LED灯
void LED_OnAll(uint16_t leds[], uint8_t size);
// 关闭一组LED灯
void LED_OffAll(uint16_t leds[], uint8_t size);
// 翻转指定LED状态
void LED_Toggle(uint16_t led);
#endif
2、LED.c
#include "LED.h"
// LED初始化
void LED_Init(void)
{
// 1. 开启时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 配置GPIO工作模式
GPIOA->CRL &= ~GPIO_CRL_CNF0;
GPIOA->CRL |= GPIO_CRL_MODE0;
GPIOA->CRL &= ~GPIO_CRL_CNF1;
GPIOA->CRL |= GPIO_CRL_MODE1;
GPIOA->CRH &= ~GPIO_CRH_CNF8;
GPIOA->CRH |= GPIO_CRH_MODE8;
// 3. 设置PA0、PA1、PA8默认高电平
GPIOA->ODR |= LED1;
GPIOA->ODR |= LED2;
GPIOA->ODR |= LED3;
}
// 点亮指定LED灯
void LED_On(uint16_t led)
{
GPIOA->ODR &= ~led;
}
// 关闭指定LED灯
void LED_Off(uint16_t led)
{
GPIOA->ODR |= led;
}
// 打开一组LED灯
void LED_OnAll(uint16_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
LED_On(leds[i]);
}
}
// 关闭一组LED灯
void LED_OffAll(uint16_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
LED_Off(leds[i]);
}
}
// 翻转指定LED状态
void LED_Toggle(uint16_t led)
{
// 读取对应端口IDR的值来判断LED状态
if ((GPIOA->IDR & led) == 0)
{
LED_Off(led);
}
else
{
LED_On(led);
}
}
3.3.4.2 Delay代码
1、Delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
void Delay_us(uint16_t us);
void Delay_ms(uint16_t ms);
void Delay_s(uint16_t s);
#endif
2、Delay.c
#include "Delay.h"
// 延时函数 微秒为单位 利用系统的嘀嗒定时器 系统CPU频率 72MHz 一次嘀嗒 1/72 us
void Delay_us(uint16_t us)
{
// 1. 装载一个计数值 1us 嘀嗒72次
SysTick->LOAD = 72 * us;
// 2. 配置嘀嗒定时器 使用系统时钟(1)、不开启中断(0)、打开使能寄存器(1)
SysTick->CTRL |= 0x05;
// 3. 等待计数值变为0,即判断配置标志位COUNTFLAG是否变为1
while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG) == 0)
{}
// 4. 关闭定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
}
void Delay_ms(uint16_t ms)
{
while(ms--)
{
Delay_us(1000);
}
}
void Delay_s(uint16_t s)
{
while(s--)
{
Delay_ms(1000);
}
}
OK!到这,我们整个案例代码就全部写完了,接下来就到看实验效果的时刻了!
四、案例效果
我们编译一下代码,没有问题
然后我们把程序烧录到开发板上,看看效果
还是挺不错的哈
以上便是本次文章的所有内容,欢迎各位朋友在评论区讨论,本人也是一名初学小白,愿大家共同努力,一起进步吧!
鉴于笔者能力有限,难免出现一些纰漏和不足,望大家在评论区批评指正,谢谢!