使用 STM32CubeMX 与 HAL 库实现 LED 流水灯及 Proteus 仿真
使用 STM32CubeMX 与 HAL 库实现 LED 流水灯及 Proteus 仿真
引言
在嵌入式开发学习过程中,LED 流水灯是一个经典的入门实验,类似于编程语言中的 "Hello World"。本实验将使用 STM32CubeMX 工具配置 STM32F103C8T6 单片机,通过 HAL 库编写控制程序,并在 Proteus 软件中进行仿真验证。通过这个实验,我们可以掌握 STM32 的 GPIO 配置、基本延时函数使用以及定时器中断的应用,为后续更复杂的项目打下基础。
一、准备工作
在开始实验前,我们需要准备以下工具和软件:
- STM32CubeMX:ST 公司推出的图形化配置工具,用于快速生成 STM32 初始化代码
- Keil MDK-ARM:ARM 架构的微控制器开发工具,用于编写、编译和调试程序
- Proteus:一款电子设计自动化软件,用于电路设计和仿真
- 基本的 C 语言编程知识和嵌入式开发概念
二、使用 STM32CubeMX 创建工程
2.1 新建工程并选择芯片
- 打开 STM32CubeMX 软件,点击 "New Project" 创建新工程
- 在弹出的芯片选择窗口中,搜索 "STM32F103C8",选择 "STM32F103C8T6" 型号
- 点击 "Start Project" 进入工程配置界面


2.2 配置系统时钟和调试接口
-
配置 SYS(系统):
- 在左侧配置栏中找到 "SYS" 选项并点击
- 在 "Debug" 下拉菜单中选择 "Serial Wire",这将启用 SWD 调试接口
- 其他选项保持默认设置
-
配置 RCC(时钟控制):
- 在左侧配置栏中找到 "RCC" 选项并点击
- 在 "HSE Configuration" 中选择 "Crystal/Ceramic Resonator"(外部晶振)
- 此时,Pinout 视图中会显示 HSE 对应的引脚被启用


2.3 配置 GPIO 引脚
本实验将使用三个 LED,分别连接到以下引脚:
- LED1:GPIOB_PIN_9
- LED2:GPIOA_PIN_15
- LED3:GPIOC_PIN_13
配置步骤:
- 在 Pinout 视图中找到上述三个引脚
- 分别点击每个引脚,在弹出的菜单中选择 "GPIO_Output"
- 点击左侧 "GPIO" 选项,进入 GPIO 配置界面
- 对于每个已配置的引脚:
- 在 "GPIO output level" 中选择 "High"(初始状态为高电平,LED 灭)
- 在 "GPIO mode" 中选择 "Output Push-Pull"(推挽输出模式)
- 在 "GPIO Pull-up/Pull-down" 中选择 "No pull-up and no pull-down"(无上下拉)
- 在 "Maximum output speed" 中选择 "Low"(低速即可,节省功耗)

2.4 配置时钟树
- 点击界面上方的 "Clock Configuration" 进入时钟配置界面
- 在 "HSE" 处选择 "Crystal Oscillator"
- 将 PLL 锁相环的输入时钟设置为 HSE
- 配置 PLL 参数,使系统时钟 (SYSCLK) 达到 72MHz:
- PLLMUL 选择 "x9"
- APB1 Prescaler 选择 "/2"
- 确保各个外设时钟都已正确配置
- 点击 "OK" 保存配置

2.5 生成工程文件
-
点击界面上方的 "Project Manager" 进入工程设置界面
-
在 "Project" 选项卡中:
- 设置 "Project Name"(工程名称)
- 选择 "Project Location"(工程保存路径)
- 在 "Toolchain/IDE" 中选择 "MDK-ARM"
- 设置 "Toolchain Version" 为适合你的 Keil 版本
-
在 "Code Generator" 选项卡中:
- 勾选 "Generate peripheral initialization as a pair of '.c/.h' files per peripheral"
- 勾选 "Use micro LIB"
- 其他选项保持默认
-
点击 "Generate Code" 生成工程文件
-
生成完成后,可以选择 "Open Project" 直接在 Keil 中打开工程


三、在 Keil 中编写流水灯代码
3.1 基本循环实现方式
打开生成的工程后,找到并打开 "main.c" 文件,在 main 函数的 while (1) 循环中添加以下代码:
c
运行
// 点亮LED1,其余LED熄灭
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); // LED1亮(低电平有效)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET); // LED2灭
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // LED3灭
HAL_Delay(1000); // 延时1秒
// 点亮LED2,其余LED熄灭
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); // LED1灭
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET); // LED2亮
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // LED3灭
HAL_Delay(1000); // 延时1秒
// 点亮LED3,其余LED熄灭
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); // LED1灭
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET); // LED2灭
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // LED3亮
HAL_Delay(1000); // 延时1秒
代码说明:
- HAL_GPIO_WritePin 函数用于设置 GPIO 引脚的电平状态
- GPIO_PIN_RESET 表示输出低电平,GPIO_PIN_SET 表示输出高电平
- 由于我们在 CubeMX 中设置了 LED 初始状态为高电平(灭),所以当输出低电平时 LED 点亮
- HAL_Delay 函数用于产生毫秒级延时,参数为延时时间(毫秒)
3.2 编译生成 HEX 文件
- 点击 Keil 工具栏中的 "Build" 按钮进行编译
- 编译完成后,确认输出窗口显示 "0 Error (s), 0 Warning (s)"
- 配置生成 HEX 文件:
- 点击 "Options for Target" 按钮(魔术棒图标)
- 切换到 "Output" 选项卡
- 勾选 "Create HEX File"
- 点击 "OK" 保存设置
- 再次编译工程,此时会在工程目录的 "Debug" 文件夹下生成 HEX 文件
四、使用定时器中断实现流水灯
除了基本的循环方式,我们还可以使用定时器中断来实现流水灯效果,这种方式可以释放 CPU 资源,让其在延时期间处理其他任务。
4.1 在 CubeMX 中配置定时器
-
返回 STM32CubeMX 工程
-
在左侧配置栏中找到 "TIM6"(基本定时器)并点击
-
在 "Mode" 选项卡中:
- 勾选 "Activated" 启用定时器
- 设置 "Prescaler"(预分频器)为 71
- 设置 "Counter Period"(自动重装载值)为 999
- 这样配置后,定时器的中断周期为 (71+1)*(999+1)/72000000 = 1ms
-
在 "NVIC Settings" 选项卡中:
- 勾选 "TIM6 global interrupt" 使能定时器中断
- 可以设置适当的中断优先级
-
重新生成代码,更新到 Keil 工程中

4.2 编写中断相关代码
- 在 main.c 文件的顶部定义全局变量:
c
运行
// 全局变量
volatile uint8_t led_state = 0; // LED状态变量,0-2分别代表三个LED
volatile uint32_t led_counter = 0; // 计数器,用于控制LED切换频率
- 在 main 函数中启动定时器中断:
c
运行
int main(void)
{
// 初始化HAL库
HAL_Init();
// 初始化系统时钟
SystemClock_Config();
// 初始化所有配置的外设
MX_GPIO_Init();
MX_TIM6_Init();
// 启动定时器中断
HAL_TIM_Base_Start_IT(&htim6);
// 主循环
while (1)
{
// 主循环中可以添加其他任务
// LED控制逻辑在中断中实现
}
}
- 实现定时器中断回调函数:
c
运行
// 定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6) // 确认是TIM6产生的中断
{
led_counter++; // 每1ms计数器加1
// 每1000ms(1秒)切换一次LED状态
if (led_counter >= 1000)
{
led_counter = 0; // 重置计数器
// 关闭所有LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
// 根据状态变量点亮对应的LED
switch(led_state)
{
case 0:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); // 点亮LED1
break;
case 1:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET); // 点亮LED2
break;
case 2:
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 点亮LED3
break;
}
// 更新状态,循环切换
led_state = (led_state + 1) % 3;
}
}
}
代码说明:
- 定时器每 1ms 产生一次中断,在中断回调函数中计数器加 1
- 当计数器达到 1000 时(即 1 秒后),切换 LED 状态
- 使用 switch-case 语句根据状态变量控制不同的 LED 点亮
- led_state 变量在 0、1、2 之间循环切换,实现流水灯效果
五、Proteus 仿真
5.1 创建 Proteus 仿真电路
-
打开 Proteus 软件,新建一个工程
-
在元件库中添加所需元件:
- STM32F103C8T6:主控制器
- LED-RED:红色 LED(3 个)
- RES:电阻(3 个,220Ω 左右)
- CRYSTAL:晶振(8MHz)
- CAP:电容(若干,用于晶振和复位电路)
- BUTTON:按钮(用于复位)
-
按照以下方式连接电路:
- 将 STM32 的 PA15、PB9、PC13 引脚分别通过限流电阻连接到 LED 的阳极
- LED 的阴极连接到 GND
- 为 STM32 配置 8MHz 外部晶振电路
- 配置复位电路(可以使用按钮实现手动复位)
- 连接 VCC 和 GND,确保电源正常


5.2 加载程序并运行仿真
- 在 Proteus 中双击 STM32F103C8T6 芯片
- 在弹出的对话框中,点击 "Program File" 后的文件夹图标
- 选择之前在 Keil 中生成的 HEX 文件
- 点击 "OK" 完成设置
- 点击 Proteus 工具栏中的 "Run Simulation" 按钮开始仿真
- 观察 LED 的变化,应该看到三个 LED 按照 1 秒的间隔依次点亮和熄灭,形成流水灯效果
六、实验总结
通过本实验,我们学习了:
- 使用 STM32CubeMX 配置 STM32 单片机的基本方法,包括 GPIO、时钟和定时器
- 两种实现 LED 流水灯的编程方法:
- 基本循环 + 延时函数的简单方法
- 定时器中断的高效方法
- 在 Proteus 中搭建 STM32 仿真电路并进行程序验证

对比两种实现方法,基本循环方式代码简单直观,但在延时期间 CPU 处于阻塞状态,无法执行其他任务;而定时器中断方式可以让 CPU 在延时期间处理其他事务,提高了系统的效率和响应性,更适合实际项目应用。
这个实验虽然简单,但涵盖了嵌入式开发的基本流程和重要概念,掌握这些内容对于进一步学习更复杂的 STM32 应用具有重要意义
更多推荐


所有评论(0)