一、引言

跑马灯作为嵌入式系统中经典的入门级项目,不仅能帮助开发者快速掌握 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 开发环境搭建

  1. 安装 STM32CubeIDE(集成 HAL 库和图形化配置工具)。
  2. 创建新项目:选择芯片型号 “STM32F103C8T6”,配置调试工具(如 ST-LINK)。

3.2 GPIO 初始化配置

  1. 打开 CubeMX 图形配置界面

    • 启用需要控制的 GPIO 引脚(如 PA0、PA1),设置为 “Output” 模式。
    • 选择引脚速率(推荐 “Low” 或 “Medium”,满足 LED 闪烁需求)。
    • 初始化电平状态:可设置为 “High”(LED 初始熄灭)或 “Low”(初始点亮)。
  2. 生成代码:点击 “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 常见问题排查

  1. LED 不亮

    • 检查引脚电平是否正确(用万用表测量 GPIO 输出电压)。
    • 确认时钟使能是否开启(如忘记调用__HAL_RCC_GPIOx_CLK_ENABLE())。
    • 排查硬件连接:电阻是否漏接、LED 正负极是否接反。
  2. 闪烁异常

    • 延时函数是否被正确调用(避免被其他代码阻塞)。
    • 多任务场景下需考虑线程同步(如使用 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); // 修改占空比
  1. 交互控制:结合按键输入(GPIO 输入模式),实现跑马灯启停或速度切换。

  2. 多设备协同:通过 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) {
        // 主循环可以处理其他任务
    }
}

六、总结

通过本项目的实践,我们掌握了:

  1. STM32 GPIO的基本配置和使用

  2. 三种不同的编程方式:寄存器操作、标准库和HAL库

  3. 简单的延时函数实现

  4. 定时器中断的使用方法

跑马灯虽然简单,但它是嵌入式开发的"Hello World",通过这个项目可以快速入门STM32开发。后续可以在此基础上扩展更复杂的功能,如PWM调光、外部中断控制等。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐