最近在搞一个 LED 控制的小项目,需求很简单:在矩阵扫描的基础上,实现呼吸灯效果。听起来有点复杂,但只要你理解了时序图,其实一点都不难。
今天这篇文章,我就结合自己写的代码和一张时序图,来给大家讲讲矩阵扫描中实现呼吸灯的原理和实现方法。
目录
一、先看时序图:理解矩阵扫描的本质
我手上的硬件是一个典型的共阴极 LED 矩阵,时序图大概是这样的:

这个图看起来简单,但其实信息量很大:
-
COM1~COM4:四个公共端,控制 LED 的负极;
-
SEG0~SEG3:段选信号,控制 LED 的正极;
-
每个 COM 口对应一组 LED,通过快速轮流拉低 COM 口,实现动态扫描;
-
每个 SEG 口控制具体的 LED 是否点亮。
说白了,就是通过快速轮流选通每一行(COM),再配合 SEG 信号,点亮对应的 LED。
二、呼吸灯效果的实现思路
呼吸灯的本质是PWM(脉宽调制),也就是通过调节 LED 的亮灭时间比例(占空比),来控制亮度。
在矩阵扫描中实现呼吸灯,核心思想就是:
-
在每个扫描周期内,根据当前 LED 的占空比,决定它是否点亮;
-
通过周期性地调整占空比,实现渐亮渐暗的呼吸效果。
三、代码实现:数据结构定义
首先定义一个结构体,表示一个 LED 的状态:
typedef struct {
unsigned char Led0:1; // 0:常亮, 1:呼吸灯
unsigned char Led1:1;
unsigned char Led2:1;
unsigned char Led3:1;
......
} Typedef_Led8;
再定义一个结构体,保存每个 COM 口的呼吸灯参数:
typedef struct {
Typedef_Led8 Leds; // LED模式
unsigned char Bre_Duty; // 当前占空比
unsigned char Bre_Duty_Cnt; // 当前计数
unsigned char Bre_Duty_Cnt_Max; // 呼吸周期
} Typedef_Led8_Bre;
四、矩阵扫描函数实现
我用一个定时器中断,每 125μs 调用一次扫描函数:
void Leds_Data_Drive2(void) {
if (Show_Dptr_Temp != Show_Dptr) {
Show_Dptr_Temp = Show_Dptr;
// 消隐显示
PORT_LEDS_SEG0 = 0; PORT_LEDS_SEG1 = 0; PORT_LEDS_SEG2 = 0; PORT_LEDS_SEG3 = 0;
PORT_LEDS_COM1 = 1; PORT_LEDS_COM2 = 1; PORT_LEDS_COM3 = 1; PORT_LEDS_COM4 = 1;
// COM口控制
switch (Show_Dptr) {
case 0: PORT_LEDS_COM1 = 0; break;
case 1: PORT_LEDS_COM2 = 0; break;
case 2: PORT_LEDS_COM3 = 0; break;
case 3: PORT_LEDS_COM4 = 0; break;
}
}
// 段选控制
if (Show_Dptr < SHOW_NUMB_LENG) {
//常量显示,一定要放在呼吸灯前面
if ((Leds_Bre[Show_Dptr].Leds.Led0) == 0) { if (Show_Data[Show_Dptr] & 0x01) PORT_LEDS_SEG0 = 1; }
if ((Leds_Bre[Show_Dptr].Leds.Led1) == 0) { if (Show_Data[Show_Dptr] & 0x02) PORT_LEDS_SEG1 = 1; }
if ((Leds_Bre[Show_Dptr].Leds.Led2) == 0) { if (Show_Data[Show_Dptr] & 0x04) PORT_LEDS_SEG2 = 1; }
if ((Leds_Bre[Show_Dptr].Leds.Led3) == 0) { if (Show_Data[Show_Dptr] & 0x08) PORT_LEDS_SEG3 = 1; }
// 呼吸灯控制
if (Leds_Bre[Show_Dptr].Bre_Duty_Cnt >= (Leds_Bre[Show_Dptr].Bre_Duty_Cnt_Max - Leds_Bre[Show_Dptr].Bre_Duty)) {
if ((Leds_Bre[Show_Dptr].Leds.Led0) == 1) { if (Show_Data[Show_Dptr] & 0x01) PORT_LEDS_SEG0 = 1; }
if ((Leds_Bre[Show_Dptr].Leds.Led1) == 1) { if (Show_Data[Show_Dptr] & 0x02) PORT_LEDS_SEG1 = 1; }
if ((Leds_Bre[Show_Dptr].Leds.Led2) == 1) { if (Show_Data[Show_Dptr] & 0x04) PORT_LEDS_SEG2 = 1; }
if ((Leds_Bre[Show_Dptr].Leds.Led3) == 1) { if (Show_Data[Show_Dptr] & 0x08) PORT_LEDS_SEG3 = 1; }
}
// 更新占空比计数
if (++Leds_Bre[Show_Dptr].Bre_Duty_Cnt >= Leds_Bre[Show_Dptr].Bre_Duty_Cnt_Max) {
Leds_Bre[Show_Dptr].Bre_Duty_Cnt = 0;
if (++Show_Dptr >= SHOW_NUMB_LENG) {
Show_Dptr = 0;
}
}
}
}
五、呼吸灯效果控制
我用另一个定时器,每 200ms 调用一次函数,动态调整占空比:
void Leds_Brea_Func() {
if (Tcnt_Disp_Dptr != 0) return;
if (++Show_Bre_Cout <= 50) return;
Show_Bre_Cout = 0;
// COM1 呼吸灯
if (!Show_Bre_Dir0) {
if (++Leds_Bre[0].Bre_Duty >= Leds_Bre[0].Bre_Duty_Cnt_Max) {
Leds_Bre[0].Bre_Duty = Leds_Bre[0].Bre_Duty_Cnt_Max;
Show_Bre_Dir0 = 1;
}
} else {
if (--Leds_Bre[0].Bre_Duty >= Leds_Bre[0].Bre_Duty_Cnt_Max) {
Leds_Bre[0].Bre_Duty = 0;
Show_Bre_Dir0 = 0;
}
}
// 其他 COM 口类似...
}
六、最终实现效果
通过这套代码,我实现了:
-
每个 LED 可以单独设置为常亮或呼吸灯;
-
呼吸灯效果平滑自然;
-
所有 LED 可以同时运行,互不干扰。
七、总结
矩阵扫描 + PWM 实现呼吸灯,其实没你想的那么难。只要掌握了扫描原理和 PWM 控制方法,代码写起来很顺手。希望我的分享能给你带来一些启发。
如果你也在做类似的项目,欢迎一起交流。
八、源码
1.C源文件
#define __LEDS_C__
#include "Leds.H"
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
void Leds_Data_Drive(void)
{
////消隐显示
PORT_LEDS_SEG0 = 0;PORT_LEDS_SEG1 = 0;PORT_LEDS_SEG2 = 0;PORT_LEDS_SEG3 = 0;
PORT_LEDS_COM1 = 1;PORT_LEDS_COM2 = 1;PORT_LEDS_COM3 = 1;PORT_LEDS_COM4 = 1;
////COM口控制
switch (Show_Dptr)
{
case 0:
{
PORT_LEDS_COM1 = 0;
}break;
case 1:
{
PORT_LEDS_COM2 = 0;
}break;
case 2:
{
PORT_LEDS_COM3 = 0;
}break;
case 3:
{
PORT_LEDS_COM4 = 0;
}
default:
break;
}
////段选控制
if(Show_Dptr < SHOW_NUMB_LENG)
{
if(Show_Data[Show_Dptr] & 0x01) PORT_LEDS_SEG0 = 1;
if(Show_Data[Show_Dptr] & 0x02) PORT_LEDS_SEG1 = 1;
if(Show_Data[Show_Dptr] & 0x04) PORT_LEDS_SEG2 = 1;
if(Show_Data[Show_Dptr] & 0x08) PORT_LEDS_SEG3 = 1;
}
////显示周期(时间)控制
if(++Show_Dptr >= SHOW_NUMB_DUTY)
{
Show_Dptr = 0;
}
}
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
void Send_Data_Show(void)
{////4ms执行一次
Show_Data[0] = Disp_Buf[0];
Show_Data[1] = Disp_Buf[1];
Show_Data[2] = Disp_Buf[2];
Show_Data[3] = Disp_Buf[3];
}
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
/////扫描显示实现常亮与呼吸灯效果
/////1.常亮可以实现一直亮与闪烁(闪烁周期不能太短)
/////2.呼吸灯效果,每一LED灯的呼吸效果功能可以独立控制,不影响其他LED灯的常量与闪烁效果
/////3.有四个COM口,每个COM口有四个LED灯,共16个LED灯,每个LED灯的呼吸效果可以独立控制
/////4.每一个COM口执行的时间最大能占到1/4总周期的时间,即1/4的周期T,设T=4ms,可调整的周期为3ms,2ms,1ms,....
/////5.每一个COM口执行的时间最小能占到1/32总周期的时间,即1/32的周期T,即T/32;因为125us的定时器,最大能执行32次
/////6.让实现LED实现常亮与呼吸灯效果
// ////结构体8位定义
// typedef struct
// {
// unsigned char Led0:1; ////0-常亮,1-呼吸灯
// unsigned char Led1:1;
// unsigned char Led2:1;
// unsigned char Led3:1;
// unsigned char Led4:1;
// unsigned char Led5:1;
// unsigned char Led6:1;
// unsigned char Led7:1;
// }Typedef_Led8;
// ////结构体定义
// typedef struct
// {
// Typedef_Led8 Leds; //LED灯状态:位0-常亮,1-呼吸灯
// unsigned char Bre_Duty; //呼吸灯占空比
// unsigned char Bre_Duty_Cnt;//呼吸灯占空比计数
// unsigned char Bre_Duty_Cnt_Max;//呼吸灯占空比计数最大值
// }Typedef_Led8_Bre;////呼吸灯结构体
// Typedef_Led8_Bre Leds_Bre[4];////4个COM口,每个COM口4个LED灯,共16个LED灯
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
void Leds_Data_Drive2(void)
{////125us执行一次
if (Show_Dptr_Temp != Show_Dptr)
{
Show_Dptr_Temp = Show_Dptr;
////消隐显示
PORT_LEDS_SEG0 = 0;PORT_LEDS_SEG1 = 0;PORT_LEDS_SEG2 = 0;PORT_LEDS_SEG3 = 0;
PORT_LEDS_COM1 = 1;PORT_LEDS_COM2 = 1;PORT_LEDS_COM3 = 1;PORT_LEDS_COM4 = 1;
////COM口控制
switch (Show_Dptr)
{
case 0:
{
PORT_LEDS_COM1 = 0;
}break;
case 1:
{
PORT_LEDS_COM2 = 0;
}break;
case 2:
{
PORT_LEDS_COM3 = 0;
}break;
case 3:
{
PORT_LEDS_COM4 = 0;
}
default:
break;
}
}
////段选控制
if(Show_Dptr < SHOW_NUMB_LENG)
{
////常量显示,一定要放在呼吸灯前面
if((Leds_Bre[Show_Dptr].Leds.Led0) == 0){if(Show_Data[Show_Dptr] & 0x01){PORT_LEDS_SEG0 = 1;}}
if((Leds_Bre[Show_Dptr].Leds.Led1) == 0){if(Show_Data[Show_Dptr] & 0x02){PORT_LEDS_SEG1 = 1;}}
if((Leds_Bre[Show_Dptr].Leds.Led2) == 0){if(Show_Data[Show_Dptr] & 0x04){PORT_LEDS_SEG2 = 1;}}
if((Leds_Bre[Show_Dptr].Leds.Led3) == 0){if(Show_Data[Show_Dptr] & 0x08){PORT_LEDS_SEG3 = 1;}}
////呼吸灯控制
if(Leds_Bre[Show_Dptr].Bre_Duty_Cnt >= (Leds_Bre[Show_Dptr].Bre_Duty_Cnt_Max-Leds_Bre[Show_Dptr].Bre_Duty))////减少消隐操作
{
if((Leds_Bre[Show_Dptr].Leds.Led0) == 1) {if(Show_Data[Show_Dptr] & 0x01) PORT_LEDS_SEG0 = 1;}
if((Leds_Bre[Show_Dptr].Leds.Led1) == 1) {if(Show_Data[Show_Dptr] & 0x02) PORT_LEDS_SEG1 = 1;}
if((Leds_Bre[Show_Dptr].Leds.Led2) == 1) {if(Show_Data[Show_Dptr] & 0x04) PORT_LEDS_SEG2 = 1;}
if((Leds_Bre[Show_Dptr].Leds.Led3) == 1) {if(Show_Data[Show_Dptr] & 0x08) PORT_LEDS_SEG3 = 1;}
}
if(++Leds_Bre[Show_Dptr].Bre_Duty_Cnt >= Leds_Bre[Show_Dptr].Bre_Duty_Cnt_Max){Leds_Bre[Show_Dptr].Bre_Duty_Cnt = 0;if(++Show_Dptr >= SHOW_NUMB_LENG) {Show_Dptr = 0;}}
}
}
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
void Leds_xBre_Init(void)
{
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
Leds_Bre[0].Leds.Led0 = 0;
Leds_Bre[0].Leds.Led1 = 0;
Leds_Bre[0].Leds.Led2 = 0;
Leds_Bre[0].Leds.Led3 = 0;
Leds_Bre[1].Leds.Led0 = 0;
Leds_Bre[1].Leds.Led1 = 0;
Leds_Bre[1].Leds.Led2 = 0;
Leds_Bre[1].Leds.Led3 = 0;
Leds_Bre[2].Leds.Led0 = 0;
Leds_Bre[2].Leds.Led1 = 0;
Leds_Bre[2].Leds.Led2 = 0;
Leds_Bre[2].Leds.Led3 = 0;
Leds_Bre[3].Leds.Led0 = 0;
Leds_Bre[3].Leds.Led1 = 0;
Leds_Bre[3].Leds.Led2 = 0;
Leds_Bre[3].Leds.Led3 = 0;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
Show_Dptr = 0;
Show_Dptr_Temp = 1;
Leds_Bre[0].Bre_Duty = 0;
Leds_Bre[0].Bre_Duty_Cnt = 0;
Leds_Bre[0].Bre_Duty_Cnt_Max = 12;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
Leds_Bre[1].Bre_Duty = 0;
Leds_Bre[1].Bre_Duty_Cnt = 0;
Leds_Bre[1].Bre_Duty_Cnt_Max = 12;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
Leds_Bre[2].Bre_Duty = 0;
Leds_Bre[2].Bre_Duty_Cnt = 0;
Leds_Bre[2].Bre_Duty_Cnt_Max = 12;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
Leds_Bre[3].Bre_Duty = 0;
Leds_Bre[3].Bre_Duty_Cnt = 0;
Leds_Bre[3].Bre_Duty_Cnt_Max = 12;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
}
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
void Leds_Brea_Func()
{////调节PWM宽度
if (Tcnt_Disp_Dptr != 0){return;}////4ms执行一次
if(++Show_Bre_Cout<= 50){return;} ////4ms*50=200ms
Show_Bre_Cout = 0;
if(!Show_Bre_Dir0){if(++Leds_Bre[0].Bre_Duty >= Leds_Bre[0].Bre_Duty_Cnt_Max){Leds_Bre[0].Bre_Duty = Leds_Bre[0].Bre_Duty_Cnt_Max;Show_Bre_Dir0 = TRUE;}}
if(Show_Bre_Dir0){if(--Leds_Bre[0].Bre_Duty >= Leds_Bre[0].Bre_Duty_Cnt_Max){Leds_Bre[0].Bre_Duty = 0;Show_Bre_Dir0 = FALSE;}}
if(!Show_Bre_Dir1){if(++Leds_Bre[1].Bre_Duty >= Leds_Bre[1].Bre_Duty_Cnt_Max){Leds_Bre[1].Bre_Duty = Leds_Bre[1].Bre_Duty_Cnt_Max;Show_Bre_Dir1 = TRUE;}}
if(Show_Bre_Dir1){if(--Leds_Bre[1].Bre_Duty >= Leds_Bre[1].Bre_Duty_Cnt_Max){Leds_Bre[1].Bre_Duty = 0;Show_Bre_Dir1 = FALSE;}}
if(!Show_Bre_Dir2){if(++Leds_Bre[2].Bre_Duty >= Leds_Bre[2].Bre_Duty_Cnt_Max){Leds_Bre[2].Bre_Duty = Leds_Bre[2].Bre_Duty_Cnt_Max;Show_Bre_Dir2 = TRUE;}}
if(Show_Bre_Dir2){if(--Leds_Bre[2].Bre_Duty >= Leds_Bre[2].Bre_Duty_Cnt_Max){Leds_Bre[2].Bre_Duty = 0;Show_Bre_Dir2 = FALSE;}}
if(!Show_Bre_Dir3){if(++Leds_Bre[3].Bre_Duty >= Leds_Bre[3].Bre_Duty_Cnt_Max){Leds_Bre[3].Bre_Duty = Leds_Bre[3].Bre_Duty_Cnt_Max;Show_Bre_Dir3 = TRUE;}}
if(Show_Bre_Dir3){if(--Leds_Bre[3].Bre_Duty >= Leds_Bre[3].Bre_Duty_Cnt_Max){Leds_Bre[3].Bre_Duty = 0;Show_Bre_Dir3 = FALSE;}}
}
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
2.C头文件
#ifndef __LEDS_H__
#define __LEDS_H__
#include "Common.H"
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
////共阴极
// #define PORT_LEDS_COM1 PA4
// #define PORT_LEDS_COM2 PA1
// #define PORT_LEDS_COM3 PA0
// #define PORT_LEDS_COM4 PB0
#define PORT_LEDS_COM1 LATA4
#define PORT_LEDS_COM2 LATA1
#define PORT_LEDS_COM3 LATA0
#define PORT_LEDS_COM4 LATB0
////SEG口
// #define PORT_LEDS_SEG0 PC4
// #define PORT_LEDS_SEG1 PB2
// #define PORT_LEDS_SEG2 PB3
// #define PORT_LEDS_SEG3 PB4
#define PORT_LEDS_SEG0 LATC4
#define PORT_LEDS_SEG1 LATB2
#define PORT_LEDS_SEG2 LATB3
#define PORT_LEDS_SEG3 LATB4
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
#define LEDS_DUTY 4
#define LEDS_CYCL 4
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
#define SHOW_NUMB_LENG 4
#define SHOW_NUMB_DUTY 4
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
////结构体8位定义
typedef struct
{
unsigned char Led0:1; ////0-常亮,1-呼吸灯
unsigned char Led1:1;
unsigned char Led2:1;
unsigned char Led3:1;
unsigned char Led4:1;
unsigned char Led5:1;
unsigned char Led6:1;
unsigned char Led7:1;
}Typedef_Led8;
////结构体定义
typedef struct
{
Typedef_Led8 Leds; //LED灯状态:位0-常亮,1-呼吸灯
unsigned char Bre_Duty; //呼吸灯占空比
unsigned char Bre_Duty_Cnt;//呼吸灯占空比计数
unsigned char Bre_Duty_Cnt_Max;//呼吸灯占空比计数最大值
}Typedef_Led8_Bre;////呼吸灯结构体
// Typedef_Led8_Bre Leds_Bre[4];////4个COM口,每个COM口4个LED灯,共16个LED灯
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
#ifdef __LEDS_C__
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
// xchar Tcnt_Disp_Dptr;
//xchar Tcnt_Tuch_Freh;////用于触摸扫描
// vsbit Fdsp_Data_Alon;
// xchar Disp_Buf[6];
xchar Show_Data[SHOW_NUMB_LENG];
xchar Show_Dptr; ////索引
xchar Show_Dptr_Temp; ////索引
Typedef_Led8_Bre Leds_Bre[4];////4个COM口,每个COM口4个LED灯,共16个LED灯
vsbit Show_Bre_Dir0;////呼吸灯方向
vsbit Show_Bre_Dir1;
vsbit Show_Bre_Dir2;
vsbit Show_Bre_Dir3;
uuint Show_Bre_Cout;
// uuint Show_Cout;
// uuint Show_Duty;
// uuint Show_Cycl;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
#else
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
// extern xchar Tcnt_Disp_Dptr;
//extern xchar Tcnt_Tuch_Freh;////用于触摸扫描
// extern vsbit Fdsp_Data_Alon;
// extern xchar Disp_Buf[6];
extern xchar Show_Data[SHOW_NUMB_LENG];
extern xchar Show_Dptr; ////索引
extern xchar Show_Dptr_Temp; ////索引
extern Typedef_Led8_Bre Leds_Bre[4];////4个COM口,每个COM口4个LED灯,共16个LED灯
extern vsbit Show_Bre_Dir0;////呼吸灯方向
extern vsbit Show_Bre_Dir1;
extern vsbit Show_Bre_Dir2;
extern vsbit Show_Bre_Dir3;
extern uuint Show_Bre_Cout;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
extern uuint Show_Cout;
extern uuint Show_Duty;
extern uuint Show_Cycl;
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
#endif
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
extern void Leds_Data_Drive(void);
extern void Send_Data_Show(void);
extern void Leds_Data_Drive2(void);
extern void Leds_xBre_Init(void);
extern void Leds_Brea_Func();
/*------------------------------------------------------------------------------------------------------------------------------------------20250703A*/
#endif

被折叠的 条评论
为什么被折叠?



