热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

单片机驱动LED灯:深入探索控制闪烁次数的技术路径

对于大多数单片机的学习者或者是从事单片机行业的工程师来说,单片机驱动LED闪烁起来的时候,基本都是我们入坑的开始,同时当时的那种兴奋与喜悦
56cc42ca44deea035a30c0edb3536948.png

对于大多数单片机的学习者或者是从事单片机行业的工程师来说,单片机驱动LED闪烁起来的时候,基本都是我们入坑的开始,同时当时的那种兴奋与喜悦都是难以忘怀的,从LED灯亮起,到闪烁,再到各种流水灯,能开心好几个晚上。

4cdecaa542fbd58b47f073ecea88a3fd.png

一个LED灯的驱动程序可以说是最简单的,但是随着学习的深入,以及经验的积累,慢慢的会发现,想写一个高效的、普适的、可移植的驱动模块又不是那么容易,每次一般都是根据实际的情况现写一个,当然现写一个也不会花很多时间,但是我们却每次做项目都会要重复着造着这么一个最简单的轮子,因此在这里我想写一个从最原始的到目前我的能力所能达到的最好用的LED驱动模块,当然,我本身也是一个小白,希望能抛砖引玉,欢迎各路大神,提出宝贵意见。

bf9137fdd26b7542e281e2359c99407a.png

基本实际的项目里面LED的数量不会很多,大多数情况不会超过3个(需要单片机控制的),一般只有两种接法(极个别的时候动态扫描的方式除外):一种是拉电流,一种是灌电流,说白了就是高电平亮还是低电平亮的问题,在这里我们主要以单个的LED驱动的方式来说明。

一般的实际项目当中,LED灯的模式主要是以下几种:

常亮/常灭:这个就没什么好说的了

快闪/慢闪:主要是用于不同的状态模块如:正在联网到网络连接成功

触发反转:主要是用于通信状态的指示,当收到数据的时候通过不停的亮灭来指示灯正在通信。

呼吸模式:主要是用于等待用户操作的一个状态

故障码输出:类似摩斯码的当时通过亮的长短来组成一组故障码


以上几种模式不是绝对的,只是相对的,具体怎么用户不同的工程师不同的产品,具体情况具体分析,只要合乎逻辑就可以了。

现在我想要做就是比较好用且相对来说高效的一个LED驱动模块,先定一个小目标,达到以下功能要求:

高电平低电平驱动都可使用(当然这一条算是废话)

非常容易移植,随便哪个工程只需要修改引脚即可,自己配置模式即可

几乎涵盖了所有项目对于一个指示灯功能的需求,什么模式都可以使用

理论上支持无限多个LED指示灯(局限于内存空间)

C语言编写(曾经使用C++写过,但是当LED比较多的时候,效率会比较低,当然也与自己的编程水平有关,所以在这里还是使用C语言来写),但是使用起来类似面向对象的使用方式。

当然在这里,我不是一上来直接就这个LED驱动程序写出来,既然标题是单片机驱动LED灯的进阶之路,其实我更想通过一个简单的LED的驱动程序的不断演变,来传递一点编程的理念与编程思想,以及我们在编程的过程中所不断摸索出的一些技巧,这些东西的价值可能远大于一个LED灯的驱动程序。

入门阶段:

LED_ON;

Delayms(500);

LED_OFF;

Delayms(500);

当我们在写这种驱动程序的时候,相信思想还是比较符合人类的思考,这时候我们还没有被冠以IT男或者程序猿的标签,找对象还是比较好找的,但是这种写法,可能很难满足实际的项目需求了,或者说如果在项目中使用这种代码,那么用户用你开发的产品一定是很难受的。

初级阶段:

Void led_loop()

{

LED_Count++;

If(LED_Count>10)

LED_Count=0;

Switch(LED_Mode)

{

Case 0: //灭

LED_OFF;

Break;

Case 1: //亮

LED_ON;

Break;

Case 2: //闪烁

If(LED_Count<2)LED_ON;

Else LED_OFF;

Break&#xff1b;

}

}

当这样来写LED驱动程序的时候&#xff0c;相信基本以及能做很多简单的单片机产品了&#xff0c;甚至很多时候已经完全通过这种方式来满足我们很多的产品需求&#xff0c;这时候我们对单片机也比较了解了&#xff0c;重要的是我们知道了周期与刷新的概念&#xff0c;知道每隔多长时间刷新一次&#xff0c;一个控制周期是多少&#xff0c;也可以通过这种方式来实现多种多样的模式&#xff0c;甚至很多时候&#xff0c;我们会停留在这中方式很久&#xff0c;哪怕是后面&#xff0c;我们可以通过PWM&#xff0c;定时器等方式组合出呼吸灯、闪烁呀&#xff0c;也可以把每个变量变成数组的方式来驱动多个指示灯等等&#xff0c;但是我们的代码依然没法满足模块化的方式。

中级阶段

Typedef struct

{

Char mode;

Char status;

Int count;

Void (*led_set)(char );

}LedOpt_t;

Void led_register(LedOpt_t *led);

Void led_loop(LedOpt_t *led);

这时候&#xff0c;我们已经对与结构体、函数指针、面向对象等已经比较熟悉了&#xff0c;而且我相信&#xff0c;我们也已经做过好多个不大不小的项目了&#xff0c;其实这时候&#xff0c;只要我们善于发现与总结&#xff0c;善于学习更多大神的宝贵经验我们完全有能力写出一套更加实用的驱动程序来&#xff0c;其实这时候比较麻烦的是当有多个指示灯的时候&#xff0c;我们难免还是需要修改一点代码&#xff0c;这让我觉得很不爽&#xff0c;所以还是想写一个驱动部分不想修改的驱动模块。

高级阶段

typedef struct iLed_t

{

uint8_t state; //信号灯当前的状态

uint16_t *mode; //用于指明当前参照闪烁数组中的第几个成员

uint16_t modeLen; //每种模式的长度

uint16_t tickCnt; //tick计数

uint16_t modeIndex; //一个周期内的闪烁次数

int32_t times; //执行次数

void (*on)(void); //led亮

void (*off)(void); //led灭

void (*overCallBack)(void); //led闪烁结束回调函数

struct iLed_t *next; //下一个led灯

} iLed_t;

这个时候&#xff0c;我们使用到了c语言的链表功能&#xff0c;通过一个链表&#xff0c;来实现多个led灯查找&#xff0c;而不用在使用for(i&#61;0;i

比如&#xff1a;

static iLed_t Led1;

static iLed_t Led2;

static iLed_t Led3;

const uint16_t LED_MODE_1[2]&#61;{500,500}; //模式1&#xff1a;亮500 灭500

const uint16_t LED_MODE_2[4]&#61;{200,200,100,800};//模式2&#xff1a;亮200&#xff0c;灭200&#xff0c;亮100&#xff0c;灭800

const uint16_t LED_MODE_3[4]&#61;{200,700,100,800,200,600};//模式3&#xff1a;亮200&#xff0c;灭700&#xff0c;亮100&#xff0c;灭800&#xff0c;亮200&#xff0c;灭600

我们可以根据我们的实际使用需求来随意定义自己的模式即可

led_creat(&Led1,led_1_on, led_1_off);

led_creat(&Led2,led_2_on, led_2_off);

led_creat(&Led3,led_3_on, led_3_off);

led_set_mode(&Led1,mode,modeLen,times);

Mode&#xff1a;选择模式如 LED_MODE_1

modeLen:当前所选模式的所占空间大小&#xff1a;sizeof(LED_MODE_1)&#xff0c;

Times&#xff1a;当前模式执行次数

这样的一个LED驱动程序基本可以实现我们对一个LED指示灯近乎变态的需求&#xff0c;呼吸灯呀&#xff0c;故障码呀什么的都是可以的&#xff0c;另外其实不建议通过这种方式驱动呼吸灯&#xff0c;最好还是使用硬件PWM的方式&#xff0c;当然这个驱动程序也是支持的&#xff0c;下面我就我相对完成的代码全部写出来&#xff0c;另外里面有些思想也是参考了晚上很多大拿的代码&#xff0c;具体我也记不清是谁的了&#xff0c;反正是当时搜索了很多代码&#xff0c;如果有雷同&#xff0c;也是有可能的。

#ifndef _ILED_H_

#define _ILED_H_

#include "stdint.h"

#include "string.h"

#define LED_TICK_TIME 50 //心跳函数调用的时间间隔(单位:ms)

typedef struct iLed_t

{

uint8_t state; //信号灯当前的状态

uint16_t *mode; //用于指明当前参照闪烁数组中的第几个成员

uint16_t modeLen; //

uint16_t tickCnt; //

uint16_t modeIndex; //一个周期内的闪烁次数

int32_t times; //执行次数

void (*on)(void);

void (*off)(void);

void (*overCallBack)(void); //led闪烁结束回调函数

struct iLed_t *next;

} iLed_t;

void led_init(iLed_t *handle,void (*on)(void), void (*off)(void));

void led_set_mode(iLed_t *handle,uint16_t *mode,uint16_t modeLen,int16_t times);

int8_t led_start(iLed_t *handle);

void led_stop(iLed_t *handle);

void led_switch(iLed_t *handle,char state);

void led_toggle(iLed_t *handle);

int8_t led_creat(iLed_t *handle,void (*on)(void), void (*off)(void));

void led_reg_call_back(iLed_t *handle,void (*overCallBack)(void));

void led_tick_loop(void);

void led_loop(void);

void led_tick();

#endif

---------------------------------分割线----------------------------------------------------------------

#include "iLed.h"

#include "stddef.h"

static struct iLed_t * head_led &#61; NULL;

static uint32_t _led_ticks &#61; 0;

//初始化&#xff0c;主要是注册led

void led_init(iLed_t *handle,void (*on)(void), void (*off)(void))

{

memset(handle, 0, sizeof(struct iLed_t));

handle->on&#61;on;

handle->off&#61;off;

handle->modeIndex&#61;0;

handle->modeLen&#61;0;

handle->tickCnt&#61;0;

handle->state&#61;0;

handle->off();

}

//执行完成回调&#xff0c;可以不使用&#xff0c;当一个led灯执行完成之后会调用此函数

void led_reg_call_back(iLed_t *handle,void (*overCallBack)(void))

{

handle->overCallBack&#61;overCallBack;

}

//启动led灯

int8_t led_start(iLed_t *handle)

{

struct iLed_t * target &#61; head_led;

while(target)

{

if(target &#61;&#61; handle) return -1;

target &#61; target->next;

}

handle->next &#61; head_led;

head_led &#61; handle;

return 0;

}

//创建led&#xff0c;注册并启动

int8_t led_creat(iLed_t *handle,void (*on)(void), void (*off)(void))

{

led_init(handle,on,off);

return led_start(handle);

}

//设置led模式

void led_set_mode(iLed_t *handle,uint16_t *mode,uint16_t modeLen,int16_t times)

{

struct iLed_t * target &#61; head_led;

while(target)

{

if(target &#61;&#61; handle)

{

target->mode&#61;mode;

target->times&#61;times;

target->modeLen&#61;modeLen/sizeof(mode[0]);

break;

}

else

{

target &#61; target->next;

}

}

}

//led灯停止

void led_stop(iLed_t *handle)

{

struct iLed_t** curr;

for(curr &#61; &head_led; *curr; )

{

struct iLed_t* entry &#61; *curr;

if (entry &#61;&#61; handle)

{

*curr &#61; entry->next;

}

else

curr &#61; &entry->next;

}

}

//led开关

void led_switch(iLed_t *handle,char state)

{

switch(state)

{

case 1:

handle->on();

handle->state&#61;state;

break;

case 0:

handle->off();

handle->state&#61;state;

break;

default:

break;

}

}

//触发反转

void led_toggle(iLed_t *handle)

{

handle->state&#61;!handle->state;

led_switch(handle,handle->state);

}

//led主要的控制逻辑

void led_handle(iLed_t *handle)

{

if((handle->times!&#61;0)&&(handle->modeLen>0))

{

handle->tickCnt&#43;&#43;;

if((handle->tickCnt*LED_TICK_TIME)<&#61;(handle->mode[handle->modeIndex]))

{

led_switch(handle,!(handle->modeIndex%2));

}

else

{

handle->tickCnt&#61;0;

handle->modeIndex&#43;&#43;;

if((handle->modeIndex)>&#61;(handle->modeLen))

{

handle->modeIndex&#61;0;

if(handle->times>0)

{

handle->times--;

if(handle->times&#61;&#61;0)

{

if(handle->overCallBack!&#61;NULL)

{

handle->overCallBack();

}

}

}

}

}

}

}

/*************************使用方法1***********************************/

//可以放在系统循环中

void led_loop(void)

{

struct iLed_t* target;

if(_led_ticks>&#61;LED_TICK_TIME)

{

for(target&#61;head_led; target; target&#61;target->next) //通过链表查询

{

led_handle(target);

}

_led_ticks&#61;0;

}

}

//系统滴答定时器中1ms执行一次

void led_tick()

{

_led_ticks&#43;&#43;;

}

/*************************使用方法2***********************************/

//定时调用该函数 调用周期为 LED_TICK_TIME

void led_tick_loop(void)

{

struct iLed_t* target;

for(target&#61;head_led; target; target&#61;target->next) //通过链表查询

{

led_handle(target);

}

}



推荐阅读
  • 本文将深入探讨 Unreal Engine 4 (UE4) 中的距离场技术,包括其原理、实现细节以及在渲染中的应用。距离场技术在现代游戏引擎中用于提高光照和阴影的效果,尤其是在处理复杂几何形状时。文章将结合具体代码示例,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文通过分析一个具体的案例,探讨了64位Linux系统对32位应用程序的兼容性问题。案例涉及OpenVPN客户端在64位系统上的异常行为,通过逐步排查和代码测试,最终定位到了与TUN/TAP设备相关的系统调用兼容性问题。 ... [详细]
  • 本文探讨了如何高效地计算数组中和为2的幂的偶对数量,提供了从基础到优化的方法。 ... [详细]
  • 编译原理中的语法分析方法探讨
    本文探讨了在编译原理课程中遇到的复杂文法问题,特别是当使用SLR(1)文法时遇到的多重规约与移进冲突。文章讨论了可能的解决策略,包括递归下降解析、运算符优先级解析等,并提供了相关示例。 ... [详细]
  • 问题描述现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能;在实际开发过程中 ... [详细]
  • 本问题涉及在给定的无向图中寻找一个至少包含三个节点的环,该环上的节点不重复,并且环上所有边的长度之和最小。目标是找到并输出这个最小环的具体方案。 ... [详细]
  • 本文详细介绍了JQuery Mobile框架中特有的事件和方法,帮助开发者更好地理解和应用这些特性,提升移动Web开发的效率。 ... [详细]
  • 洛谷 P4009 汽车加油行驶问题 解析
    探讨了经典算法题目——汽车加油行驶问题,通过网络流和费用流的视角,深入解析了该问题的解决方案。本文将详细阐述如何利用最短路径算法解决这一问题,并提供详细的代码实现。 ... [详细]
  • 小编给大家分享一下Vue3中如何提高开发效率,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获, ... [详细]
  • HTML:  将文件拖拽到此区域 ... [详细]
  • 本文详细介绍了如何在循环双链表的指定位置插入新元素的方法,包括必要的步骤和代码示例。 ... [详细]
  • PHP面试题精选及答案解析
    本文精选了新浪PHP笔试题及最新的PHP面试题,并提供了详细的答案解析,帮助求职者更好地准备PHP相关的面试。 ... [详细]
  • 原文地址:https:blog.csdn.netqq_35361471articledetails84715491原文地址:https:blog.cs ... [详细]
  • 实现系统调用
    实现系统调用一、实验环境​本次操作还是基于上次编译Linux0.11内核的实验环境进行操作。环境如下:二、实验目标​通过对上述实验原理的认识,相信 ... [详细]
  • 本文介绍了如何在GTK+2中实现透明背景下的小部件叠加绘制,类似于GTK3中的GtkOverlay功能。 ... [详细]
author-avatar
手机用户2602936275
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有