你好,这是我的第3篇原创分享,内容为实现LED的亮灭切换。
一、文章脉络

二、需求分析
本期博客实现的是一个比较简单的小功能,即通过GPIO引脚来点亮开发板上的三个LED灯,并切换亮灭状态。首先我们需要知道MCU和LED的电路连接:

开发中使用的MCU有着144个引脚,本期博客只截取部分,但也不难看出,其中有一个红灯是电源灯,通电即常亮,其他三个LED正极都接入3.3V,阴极分别接入PA0,PA1,PA8,由此可见,我们可以通过控制MCU向PA0,PA1,PA8发出低电平,使LED1,2,3点亮。
对于第二个需求:切换亮灭状态,则需要结合if()判断,即LED亮时,切换为灭,或者LED灭时,切换为亮,整体上并不难。
三、相关概念
3.1 GPIO
GPIO即 General Purpose Input/Output 的缩写,翻译成中文为通用功能输入输出引脚,本需求中只采用了其中的一种功能:通用推挽输出
3.2 模块化编程
模块化编程即在编写代码时将代码根据不同的层级分为一个个文件,没有统一标准,我在今后的文章中会采用以下的分类方法:分为Driver, Interface, Common, Application, Middle五个层级
Driver即驱动层,MCU内部的所有板块的功能选择都在这个层级中,如Cortex内核,GPIO,UART,IIC等模块,都封装在了MCU芯片内。
Interface即交互层,MCU和开发板上其他元器件之间的信息传输都在这个层级中,比如通过PA0向LED1传递高低电平就在这个层级下。
Common即通用层,所有常用的小工具都在这个层级中,比如延时,宏定义等等
Middle即中间层,主要放置和操作系统有关的代码。
Application即应用层,所有功能模块都放在这个层级中,随着代码的复杂,实现一个功能往往需要开发板上的许多模块一起配合,这些配合而成的功能就放在这个文件中。
四、代码实现
4.1 相关寄存器
实现LED亮灭切换用到了GPIO,并且需要通过寄存器的方式来控制GPIOA三个引脚的输出,下面是一些涉及到的寄存器及其标志位功能:
| 寄存器 | 标志位 |
|---|---|
| RCC-> APB2ENR | IOPAEN:开启GPIOA模块的时钟 |
| GPIOA->CRL | CNF0[1:0],MODE0[1:0] : 设置PA0为通用推挽输出 CNF1[1:0],MODE1[1:0] : 设置PA1为通用推挽输出 |
| GPIOA-> CRH | CNF8[1:0],MODE8[1:0] : 设置PA8为通用推挽输出 |
| GPIOA->ODR | ODR0:设置PA0输出高低电平 ODR1:设置PA1输出高低电平 ODR8:设置PA8输出高低电平 |
| GPIOA->IDR | IDR0:获取PA0引脚的电平值 IDR1:获取PA1引脚的电平值 IDR8:获取PA8引脚的电平值 |
4.2函数分解
在分析完需求之后,现在我们可以开始根据MCU手册编写我们的程序了,要做的第一件事情就是在Interface文件夹下创建Interface_LED.c &.h文件,并在Keil中添加C文件:

接下来在Interface_LED.h文件中声明函数原型:
#ifndef __INTERFACE_LED_H__
#define __INTERFACE_LED_H__
#define LED1 GPIO_ODR_ODR0
#define LED2 GPIO_ODR_ODR1
#define LED3 GPIO_ODR_ODR8
#define LED_GPIO GPIOA
#include <stdint.h>
#include "stm32f10x.h"
//LED初始化
void Interface_LED_Init(void);
//点亮指定的LED
void Interface_LED_On(uint16_t led);
//熄灭指定的LED
void Interface_LED_Off(uint16_t led);
//切换指定LED的亮灭状态
void Interface_LED_Toggle(uint16_t led);
//同时点亮多个LED
void Interface_LED_OnAll(uint16_t *leds,uint8_t len);
//同时熄灭多个LED
void Interface_LED_OffAll(uint16_t *leds,uint8_t len);
#endif /* __INTERFACE_LED_H__ */
第一个功能是时钟使能GPIOA,并将PA0设置为通用推挽输出,在这里将其封装为Interface_LED_Init()。
需要说明一点,GPIO本是MCU内部的模块,但单独设置它没有意义,它的任务是和外界做交互,所以它通常会作为其他功能的一部分,但不会单独设置。
void Interface_LED_Init(void)
{
//1.时钟使能
//*(uint32_t *)(0x40021000 + 0x18) = 0x0004;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//2.设置引脚的模式
//*(uint32_t *)(0x40010800 + 0x00) = 0x44444443;
//全部设置为通用推挽输出
//PA8
LED_GPIO->CRH &= ~GPIO_CRH_CNF8;
LED_GPIO->CRH |= GPIO_CRH_MODE8;
//PA1 + PA0
LED_GPIO->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_CNF0);
LED_GPIO->CRL |= (GPIO_CRL_MODE1 | GPIO_CRL_MODE0);
//3.设置LED对应的引脚电平
//起始时全都不亮
LED_GPIO->ODR |=(LED1 | LED2 | LED3);
}
第二个功能是将某个LED点亮,设置函数的参数为led,在函数内部让对应的引脚输出低电平:
void Interface_LED_On(uint16_t led){
LED_GPIO->ODR &= ~led;
}
第三个功能是将某个LED熄灭,和功能一就一点不同,即输出高电平:
void Interface_LED_Off(uint16_t led){
LED_GPIO->ODR |= led;
}
第四个功能是点亮多个LED,提前将LED1,LED2,LED3这三个宏定义存在一个数组中,在函数内循环调用LED_On()即可:
uint16_t leds[3] = {LED1,LED2,LED3};
void Interface_LED_OnAll(uint16_t *leds,uint8_t len){
for (uint8_t i = 0; i < len; i++)
{
Interface_LED_On(leds[i]);
}
}
第五个功能是熄灭多个LED,在函数内循环调用LED_Off()即可,数组不必重复定义
void Interface_LED_OffAll(uint16_t *leds,uint8_t len){
for(uint8_t i = 0; i < len; i++){
Interface_LED_Off(leds[i]);
}
}
最后一个功能稍难,即切换LED灯的亮灭状态,需要根据当前GPIOA引脚的状态进行if()条件判断:
void Interface_LED_Toggle(uint16_t led){
//引脚如果是1则LED为熄灭状态
if(LED_GPIO->IDR & led){
//点亮LED
Interface_LED_On(led);
}else{
//熄灭LED
Interface_LED_Off(led);
}
}
五、附录
| 编程环境 | 烧录方式 |
|---|---|
| VSCode + Keil MDK | ST-LINK |
往期文章:
9348



被折叠的 条评论
为什么被折叠?



