手把手教你用Keil5烧录STM32,点亮第一个继电器!
你是不是也曾在网上翻遍资料,却还是搞不懂怎么把代码“灌”进STM32?
想做个智能开关、远程控制灯,但连最基础的继电器驱动都卡住了?
别急。今天我们就从零开始,手把手带你完成一个完整的嵌入式项目:使用Keil MDK(Keil5)编写程序,烧录到STM32单片机,控制一个继电器模块通断负载。
整个过程不讲虚的,只说你能听懂、能复现、能上手的操作。哪怕你是第一次接触STM32,也能照着做出来。
一、为什么选这个组合?Keil + STM32 + 继电器
在嵌入式开发的世界里,STM32就像“单片机界的安卓机”——型号多、资料全、社区大,尤其适合新手入门。而Keil MDK(俗称Keil5)虽然界面看起来有点“复古”,但它稳定、高效、调试功能强大,至今仍是很多工程师和高校教学的首选工具。
至于继电器,它就是一个“电子开关”。你可以用3.3V的MCU信号去控制220V的灯泡或风扇,实现真正的“弱电控强电”。
三者结合,就是一条通往真实世界的桥梁——写几行代码,就能让物理设备动起来。
二、你需要准备什么?
硬件清单
| 名称 | 示例型号 | 备注 |
|---|---|---|
| STM32最小系统板 | Blue Pill (STM32F103C8T6) | 成本低,资料多 |
| ST-Link V2 下载器 | 国产仿真器 | 支持SWD下载与调试 |
| 继电器模块 | 5V光耦隔离单路继电器 | 带LED指示灯更直观 |
| 杜邦线若干 | 公对公 / 公对母 | 连接用 |
| 外部电源 & 负载 | 如12V适配器 + LED灯带 | 可选小灯泡测试 |
✅ 提示:Blue Pill板子便宜(十几块),但注意有些版本晶振不稳定,建议选有8MHz主晶振+32.768kHz RTC晶振的版本。
软件环境
- Keil MDK 5(推荐版本 uVision5)
- STM32F1xx标准外设库(StdPeriph Library)
- 驱动已安装(ST-Link固件更新至最新)
📌 安装Keil时记得勾选“Install Device Family Pack for STM32F1”,否则找不到芯片支持包。
三、第一步:搭建Keil工程,跑通第一个main函数
打开Keil,新建工程 → 选择Project -> New uVision Project。
1. 选择目标芯片
搜索并选择:
STM32F103C8这是Blue Pill的核心MCU,属于中密度产品线。
2. 添加启动文件
Keil会自动提示是否添加启动代码,点“是”。你会看到一个Startup分组,里面有个.s文件(比如startup_stm32f10x_md.s),这就是启动汇编文件,负责初始化堆栈、跳转main等。
3. 添加核心文件
我们需要加入以下内容才能操作GPIO:
stm32f10x.h—— 芯片寄存器映射头文件- 标准外设库
.c和.h文件(主要是src/stm32f10x_gpio.c,src/misc.c) - 用户自己的
main.c
建议建立如下目录结构:
Project/ ├── CMSIS/ // 内核相关(可选) ├── StdPeriph_Driver/ // 外设库源码 ├── User/ │ ├── main.c │ └── stm32f10x_it.c └── Startup/ └── startup_stm32f10x_md.s然后在Keil中右键Source Group 1→ Add Existing Files… 把这些.c文件加进去。
4. 配置魔术参数:时钟 & 编译选项
设置晶振频率
虽然我们还没用到精确延时,但最好先定义外部晶振为8MHz,在main.c开头加上:
#define HSE_VALUE ((uint32_t)8000000) // 外部晶振8MHz或者修改system_stm32f10x.c中的宏定义。
包含头文件路径
进入Options for Target -> C/C++ -> Include Paths,添加:
.\User .\StdPeriph_Driver\inc .\CMSIS\core_cm3.h 所在路径这样编译器才能找到所有头文件。
四、第二步:配置PA1控制继电器——GPIO怎么玩?
现在我们来让STM32的某个引脚输出高低电平,从而控制继电器动作。
以最常见的接法为例:
继电器模块的IN 引脚接到PA1,当PA1输出低电平时,继电器吸合;高电平则断开。
为什么是“低电平触发”?因为模块内部用了NPN三极管或光耦下拉设计,低电平导通驱动回路。
GPIO初始化四步走
要让PA1工作,必须经过四个步骤:
- 开启GPIOA时钟—— 没有时钟,IO口就是“死”的;
- 配置PA1为推挽输出模式;
- 设置输出速度为50MHz(其实这里无所谓快慢);
- 初始状态设为高电平(断开继电器)
代码如下:
#include "stm32f10x.h" void GPIO_Init(void) { // 第一步:使能GPIOA时钟(APB2总线) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 第二步:定义GPIO初始化结构体 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1; // PA1 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度50MHz // 第三步:应用配置 GPIO_Init(GPIOA, &GPIO_InitStruct); // 第四步:默认断开继电器(输出高电平) GPIO_SetBits(GPIOA, GPIO_Pin_1); }💡 小知识:STM32的GPIO不是直接写
P1=0那种51风格,而是通过寄存器+库函数方式操作,更规范也更安全。
五、第三步:写个简单逻辑,让继电器自己“呼吸”
我们现在写个主循环,让继电器每隔2秒切换一次状态,就像“心跳”一样。
// 简易延时函数(基于循环) void Delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) for (j = 0; j < 7210; j++); // 数值来自实测调整(基于8MHz HSE) } int main(void) { // 初始化GPIO GPIO_Init(); while (1) { // 吸合继电器(低电平) GPIO_ResetBits(GPIOA, GPIO_Pin_1); Delay_ms(2000); // 断开继电器(高电平) GPIO_SetBits(GPIOA, GPIO_Pin_1); Delay_ms(2000); } }⚠️ 注意:这里的延时非常粗糙,仅用于演示。后续可以用SysTick定时器实现精准延时。
六、第四步:连接硬件,烧录程序!
终于到了激动人心的时刻:把代码下载到板子上!
接线图(ST-Link → STM32)
| ST-Link V2 | STM32 Blue Pill |
|---|---|
| SWCLK | PA14 (SWCLK) |
| SWDIO | PA13 (SWDIO) |
| GND | GND |
| 3.3V | 3.3V |
🔌 注意:不要同时接USB给Blue Pill供电!避免电源冲突。建议只通过ST-Link供电即可。
在Keil中下载程序
- 点击菜单
Flash -> Configure Flash Tools - 切换到
Debug标签页 - 选择右侧的
ST-Link Debugger - 点击
Settings
- 在Debug选项卡中确认识别到设备(如ID: 0xXXXXXXX)
- 在Flash Download中勾选 “Program” 和 “Reset and Run” - 回到主界面,点击Load按钮(或按 F8)
如果一切正常,你会看到底部输出:
Programming... Erase Done. Program Done. Verify OK. Reset and Run...板子立刻重启运行程序!
七、听到“咔哒”一声?恭喜你,成功了!
如果你听到继电器每两秒发出“咔哒”声,并且模块上的LED同步闪烁——那你已经完成了人生第一个嵌入式控制项目!
👏 恭喜你正式踏入STM32的大门!
八、常见问题排查清单(亲测有效)
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| Keil无法识别ST-Link | 驱动未安装 | 使用ST-Link Utility检查连接 |
| 下载时报错“No target connected” | 接线错误或供电不足 | 重插杜邦线,确保GND共地 |
| 继电器不动作 | IN脚没收到低电平 | 用万用表测PA1电压变化 |
| 板子反复重启 | 电源不稳或复位异常 | 加100nF陶瓷电容 + 10μF电解电容滤波 |
| 烧录成功但不运行 | Option Bytes设置了读保护 | 使用ST-Link Utility清除保护 |
| 延时不准确 | HSE未启用或系统时钟未配置 | 后续学习RCC时钟树配置 |
🔍 调试技巧:可以在关键位置加一个LED指示灯(比如PC13),用来判断程序是否进入主循环。
九、背后的技术原理:为什么这样就能控制继电器?
我们再来拆解一下整个系统的协作机制。
1. 光耦隔离是怎么起作用的?
大多数继电器模块都带有光耦隔离单元。它的原理很简单:
- 输入侧是一个发光二极管(LED);
- 输出侧是一个光敏三极管;
- 当MCU输出低电平时,电流流过光耦LED,使其发光;
- 光照导致光敏三极管导通,进而驱动继电器线圈得电。
由于输入和输出之间没有电气连接,只有光传递信号,所以叫“隔离”。这能有效防止高压侧反冲损坏你的STM32。
2. 为什么要用三极管放大?
STM32 IO口最大输出电流约25mA,而继电器线圈通常需要40~70mA电流才能可靠吸合。因此模块内部会用一个NPN三极管进行电流放大。
这也是为什么你不能直接拿IO口去驱动大功率继电器本体——必须借助驱动电路。
十、下一步可以怎么玩?
你现在掌握了最基本的“输出控制”能力,接下来可以尝试升级玩法:
✅ 加个按键,手动控制通断
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == Bit_RESET) { Relay_Control(0); // 按下PA0按键 → 吸合 }✅ 串口发指令控制
通过USART接收PC命令,比如发送“ON”就闭合,“OFF”就断开。
✅ 接入DHT11温湿度传感器
温度超过阈值 → 自动打开风扇(由继电器控制)
✅ 结合ESP8266做Wi-Fi遥控
手机APP远程控制家里的台灯、插座……
甚至未来还可以接入FreeRTOS做多任务调度,或者用RTC实现定时开关机。
写在最后:每一个高手,都是从点灯开始的
你可能觉得,“不就是控制一个继电器嘛,有什么难的?”
但你知道吗?当年ARM工程师面试新人,第一题就是:“让你用STM32点个LED,你怎么做?”
这个问题看似简单,却能考察出候选人对时钟、GPIO、编译环境、下载调试的整体理解程度。
而你现在已经走完了这条完整的技术链路:
写代码 → 编译 → 下载 → 硬件响应 → 观察结果 → 调试优化这才是真正意义上的“入门”。
别小看这个“咔哒”声,它是数字世界向现实世界发出的第一声呼唤。
如果你动手实现了这个项目,欢迎在评论区晒出你的接线照片或视频!
也欢迎提问遇到的问题,我会一一回复。
下一期我们讲:如何用SysTick实现精准延时,告别土味Delay()函数?
Keep hacking, keep building.
你的下一个作品,已经在路上了。