嵌入式开发实战:跑马灯效果实现
跑马灯项目虽简单,却涵盖了嵌入式开发的核心流程:硬件设计→驱动配置→逻辑编程→调试优化。GPIO 接口的输入输出原理与 HAL 库操作。延时函数的使用与程序流程控制(循环、条件判断)。开发工具链的集成使用(IDE、烧录器、调试器)。
一、引言
跑马灯作为嵌入式系统中经典的入门级项目,不仅能帮助开发者快速掌握 GPIO(通用输入输出)接口的控制原理,还能直观展现嵌入式程序的执行逻辑。本文将以 STM32F103C8T6 开发板为例,详细讲解跑马灯功能的硬件设计、软件编程及调试过程,适合嵌入式开发初学者参考
二、硬件设计与搭建
2.1 核心器件选型
- 微控制器(MCU):STM32F103C8T6(Cortex-M3 内核,32KB Flash,6KB SRAM,性价比高,适合入门)。
- LED 灯:普通发光二极管(需串联 220Ω 限流电阻保护)。
-
开发板扩展:若使用 Nucleo-F103RB 等开发板,板载 LED 通常已连接至指定 GPIO 引脚(如 PA5、PC13 等),可直接使用。
2.2 电路连接原理
独立 LED 模式(以 STM32 为例):
器件 |
STM32 引脚 |
说明 |
| LED1 正极 | PA0 | 通过 GPIO 输出高低电平控制亮灭 |
| LED1 负极 | GND | 共阴接法(低电平点亮) |
| LED2 正极 | PA1 | 可扩展多个 LED 至不同引脚 |
| 限流电阻 | 串联在 LED 正极 | 防止电流过大损坏器件 |
板载 LED 快速适配:
多数开发板已将 LED 与特定引脚连接(如 STM32 Nucleo 板的 LD2 对应 PA5),只需查阅原理图确认引脚即可,无需额外接线。
三、软件编程实现(基于 STM32CubeIDE)
3.1 开发环境搭建
- 安装 STM32CubeIDE(集成 HAL 库和图形化配置工具)。
- 创建新项目:选择芯片型号 “STM32F103C8T6”,配置调试工具(如 ST-LINK)。
3.2 GPIO 初始化配置
-
打开 CubeMX 图形配置界面:
- 启用需要控制的 GPIO 引脚(如 PA0、PA1),设置为 “Output” 模式。
- 选择引脚速率(推荐 “Low” 或 “Medium”,满足 LED 闪烁需求)。
- 初始化电平状态:可设置为 “High”(LED 初始熄灭)或 “Low”(初始点亮)。
-
生成代码:点击 “Generate Code”,导出工程文件。
3.3 跑马灯逻辑代码编写
核心思路:
通过循环依次点亮 LED,利用延时函数控制闪烁速度,实现 “流水” 效果。
代码示例:
#include "stm32f10x.h"
uint16_t temp,i,j;
void Delay(unsigned int count)
{
unsigned int i;
for(;count!=0;count--)
{
i=5000;
while(i--);
}
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = 0x03ff;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
while(1)
{
GPIO_SetBits(GPIOB, 0x0FFFF);
temp = 0x0001;
for(i=0;i<10;i++)
{
GPIO_ResetBits(GPIOB, temp);
Delay(100);
temp =( temp<<1)+1;
}
temp = 0x0FE00;
for(j=0;j<10;j++)
{
GPIO_SetBits(GPIOB, temp);
Delay(100);
temp = (temp>>1)+ 0x8000;
}
// GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11);
// GPIO_ResetBits(GPIOB,GPIO_Pin_8);
// Delay(100);
// GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11);
// GPIO_ResetBits(GPIOB,GPIO_Pin_9);
// Delay(100);
// GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11);
// GPIO_ResetBits(GPIOB,GPIO_Pin_10);
// Delay(100);
// GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11);
// GPIO_ResetBits(GPIOB,GPIO_Pin_11);
// Delay(100);
// GPIO_ResetBits(GPIOB,GPIO_Pin_8);
// GPIO_SetBits(GPIOB,GPIO_Pin_9);
// GPIO_SetBits(GPIOB,GPIO_Pin_10);
// GPIO_SetBits(GPIOB,GPIO_Pin_11);
// Delay(100);
// GPIO_SetBits(GPIOB,GPIO_Pin_8);
// GPIO_ResetBits(GPIOB,GPIO_Pin_9);
// GPIO_SetBits(GPIOB,GPIO_Pin_10);
// GPIO_SetBits(GPIOB,GPIO_Pin_11);
// Delay(100);
// GPIO_SetBits(GPIOB,GPIO_Pin_8);
// GPIO_SetBits(GPIOB,GPIO_Pin_9);
// GPIO_ResetBits(GPIOB,GPIO_Pin_10);
// GPIO_SetBits(GPIOB,GPIO_Pin_11);
// Delay(100);
// GPIO_SetBits(GPIOB,GPIO_Pin_8);
// GPIO_SetBits(GPIOB,GPIO_Pin_9);
// GPIO_SetBits(GPIOB,GPIO_Pin_10);
// GPIO_ResetBits(GPIOB,GPIO_Pin_11);
// Delay(100);
}
}
运行结果:

四、调试与优化技巧
4.1 常见问题排查
-
LED 不亮:
- 检查引脚电平是否正确(用万用表测量 GPIO 输出电压)。
- 确认时钟使能是否开启(如忘记调用
__HAL_RCC_GPIOx_CLK_ENABLE())。 - 排查硬件连接:电阻是否漏接、LED 正负极是否接反。
-
闪烁异常:
- 延时函数是否被正确调用(避免被其他代码阻塞)。
- 多任务场景下需考虑线程同步(如使用 RTOS 时的任务调度)。
4.2 功能扩展方向
1.呼吸灯效果:通过 PWM(脉冲宽度调制)调节 LED 亮度,实现渐变效果。
// 需配置TIM定时器和PWM输出(以PA0为例)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 启动PWM
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty_cycle); // 修改占空比// 需配置TIM定时器和PWM输出(以PA0为例)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 启动PWM
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty_cycle); // 修改占空比
-
交互控制:结合按键输入(GPIO 输入模式),实现跑马灯启停或速度切换。
-
多设备协同:通过 I2C/UART 等通信接口,控制多个开发板同步显示跑马灯。
4.3 Proteus 8 Professional接线图

运行结果的示例图

五、进阶实现:使用定时器中断
为了更精确地控制跑马灯效果,我们可以使用定时器中断来实现:
#include "stm32f10x.h"
volatile uint8_t led_pattern = 0x01;
volatile uint8_t direction = 0; // 0:左移 1:右移
void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
if(direction == 0) {
led_pattern <<= 1;
if(led_pattern == 0x00) {
led_pattern = 0x80;
direction = 1;
}
} else {
led_pattern >>= 1;
if(led_pattern == 0x00) {
led_pattern = 0x01;
direction = 0;
}
}
GPIOC->ODR = (GPIOC->ODR & 0xFF00) | (led_pattern & 0x00FF);
}
}
void TIM_Configuration(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 开启TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 定时器配置:1kHz中断频率
TIM_TimeBaseStructure.TIM_Period = 1000 - 1;
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz/72 = 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 使能TIM2中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
int main(void) {
// GPIO初始化(同前)
// ...
// 定时器初始化
TIM_Configuration();
while(1) {
// 主循环可以处理其他任务
}
}
六、总结
通过本项目的实践,我们掌握了:
-
STM32 GPIO的基本配置和使用
-
三种不同的编程方式:寄存器操作、标准库和HAL库
-
简单的延时函数实现
-
定时器中断的使用方法
跑马灯虽然简单,但它是嵌入式开发的"Hello World",通过这个项目可以快速入门STM32开发。后续可以在此基础上扩展更复杂的功能,如PWM调光、外部中断控制等。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)