一、学习背景与进阶目标

在之前的学习中,我们已经掌握了如何点亮 LED 和驱动蜂鸣器,但代码都集中在 main.c 中。随着功能增加,主函数会变得臃肿难读。本实验的目标是实现“按键控制 LED”,更重要的是学习代码的模块化封装。我们将把 LED 初始化和按键检测的逻辑分别封装到独立的 .c 和 .h 文件中,让 main.c 只负责核心逻辑调度,这是嵌入式开发中非常重要的工程化思维。

二、硬件连接说明

LED 模块:通常连接在 GPIO 输出引脚(如 PB1、PB2),低电平点亮;

按键模块:连接在 GPIO 输入引脚(如 PB3、PB4),按下时接地(低电平);

原理简述:通过读取按键引脚的电平变化,判断用户操作,进而翻转对应 LED 的状态。

三、代码块

main主函数

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "LED.h"
#include "Key.h"

int main()
{		
		uint8_t number;
		LED__init();
		Key__init();

		while(1)
		{
		number=Key_GetNum();
		if(number==1)
		{
				LED1_turn();
		}
		if(number==2)
		{
				LED2_turn();
		}
		}
}

LED.c

#include "stm32f10x.h"                  // Device header



void LED__init(void)
{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
		GPIO_InitTypeDef GPIO_InitStruct1;
		GPIO_InitStruct1.GPIO_Mode=GPIO_Mode_Out_PP ;
		GPIO_InitStruct1.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2;
		GPIO_InitStruct1.GPIO_Speed=GPIO_Speed_50MHz;
		GPIO_Init(GPIOA,&GPIO_InitStruct1);
		GPIO_SetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2);
}



void LED1_on(void)
{
		GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}



void LED1_off(void)
{
		GPIO_SetBits(GPIOA,GPIO_Pin_1);
}



void LED2_on(void)
{
		GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}



void LED2_off(void)
{
		GPIO_SetBits(GPIOA,GPIO_Pin_2);
}



void LED1_turn(void)
{		if (GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1)==0)
		{
			GPIO_SetBits(GPIOA,GPIO_Pin_1);
		}
		else
		{
			GPIO_ResetBits(GPIOA,GPIO_Pin_1);
		}
}



void LED2_turn(void)
{		if (GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2)==0)
		{
			GPIO_SetBits(GPIOA,GPIO_Pin_2);
		}
		else
		{
			GPIO_ResetBits(GPIOA,GPIO_Pin_2);
		}
}

Key.c

#include "stm32f10x.h" // Device header
#include "Delay.h"

void Key__init(void)
{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
		GPIO_InitTypeDef GPIO_InitStruct2;
		GPIO_InitStruct2.GPIO_Mode=GPIO_Mode_IPU ;
		GPIO_InitStruct2.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_11;
		GPIO_InitStruct2.GPIO_Speed=GPIO_Speed_50MHz;
		GPIO_Init(GPIOB,&GPIO_InitStruct2);
		GPIO_SetBits(GPIOB,GPIO_Pin_1|GPIO_Pin_11);
}



uint8_t Key_GetNum(void)
{
	uint16_t KeyNum=0;
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
{
	Delay_ms(20);
	while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
	Delay_ms(20);
	KeyNum=1;
}
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)
{
	Delay_ms(20);
	while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0);
	Delay_ms(20);
	KeyNum=2;
}
	return KeyNum;
}

四、个人学习心得与踩坑记录

作为一个刚入门 STM32 的初学者,这次尝试模块化编程让我既兴奋又有点“头大”。虽然代码跑通了,但在编写过程中也遇到了一些小插曲,这里如实记录下来,希望能给同样在摸索的朋友一点参考:

关于文件包含的困惑:刚开始我把 LED.c 和 Key.c 建好了,但在 main.c 里编译总是报错“未定义”。后来查资料才知道,光有 .c 文件不行,还得写对应的 .h 头文件来声明函数,并且在工程目录里要把这些文件都添加进去。这一步卡了我好久,但也让我明白了“声明”和“定义”分离的重要性。

对“封装”的浅显理解:以前我觉得把代码写在一个文件里挺方便的,现在试着拆分后,发现 main 函数确实清爽了很多。虽然目前只是简单的函数搬运,但我隐约感觉到,如果以后项目变大了,这种“各司其职”的写法肯定能救命。

按键消抖的疑惑:目前的 Key_GetNum() 函数里我加了一点延时来做消抖,但我不确定这是否是最高效的方法。如果有前辈看到这篇笔记,欢迎在评论区指点一下有没有更优雅的消抖思路,我会非常感激!

五、存在的不足与改进方向

目前的程序虽然实现了“按键控制 LED”,但我深知这只是最基础的入门水平,代码里还有很多不成熟的地方:

阻塞式延时的局限:现在的 Delay_ms 是死等,这意味着在等待灯闪烁或者按键消抖的时候,CPU 是什么都做不了的。我知道这在复杂项目里是大忌,但目前我还不太会用定时器中断来替代它,这是我接下来的重点攻克目标。

代码的扩展性:现在只有两个按键和两个 LED,如果以后要控制 10 个灯怎么办?难道要写 10 个 if 吗?我听说可以用数组或者结构体来优化,但这超出了我目前的知识范围,只能先挖个坑,等学会了再来填。

缺乏异常处理:如果按键坏了或者接触不良,程序目前没有保护措施。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐