热门标签 | 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);

}

}



推荐阅读
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
  • 本文将深入探讨如何在不依赖第三方库的情况下,使用 React 处理表单输入和验证。我们将介绍一种高效且灵活的方法,涵盖表单提交、输入验证及错误处理等关键功能。 ... [详细]
  • 本文将详细探讨Linux pinctrl子系统的各个关键数据结构,帮助读者深入了解其内部机制。通过分析这些数据结构及其相互关系,我们将进一步理解pinctrl子系统的工作原理和设计思路。 ... [详细]
  • 本文介绍如何使用JPA Criteria API创建带有多个可选参数的动态查询方法。当某些参数为空时,这些参数不会影响最终查询结果。 ... [详细]
  • Scala 实现 UTF-8 编码属性文件读取与克隆
    本文介绍如何使用 Scala 以 UTF-8 编码方式读取属性文件,并实现属性文件的克隆功能。通过这种方式,可以确保配置文件在多线程环境下的一致性和高效性。 ... [详细]
  • Codeforces Round #566 (Div. 2) A~F个人题解
    Dashboard-CodeforcesRound#566(Div.2)-CodeforcesA.FillingShapes题意:给你一个的表格,你 ... [详细]
  • 在多线程编程环境中,线程之间共享全局变量可能导致数据竞争和不一致性。为了解决这一问题,Linux提供了线程局部存储(TLS),使每个线程可以拥有独立的变量副本,确保线程间的数据隔离与安全。 ... [详细]
  • 本次考试于2016年10月25日上午7:50至11:15举行,主要涉及数学专题,特别是斐波那契数列的性质及其在编程中的应用。本文将详细解析考试中的题目,并提供解题思路和代码实现。 ... [详细]
  • 本文介绍了一种解决二元可满足性(2-SAT)问题的方法。通过具体实例,详细解释了如何构建模型、应用算法,并提供了编程实现的细节和优化建议。 ... [详细]
  • 树链问题的优化解法:深度优先搜索与质因数分解
    本文介绍了一种通过深度优先搜索(DFS)和质因数分解来解决最长树链问题的方法。我们通过枚举树链上的最大公约数(GCD),将所有节点按其质因子分类,并计算每个类别的最长链,最终求得全局最长链。 ... [详细]
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社区 版权所有