单片机中的通用LED驱动

这篇具有很好参考价值的文章主要介绍了单片机中的通用LED驱动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

项目中需要用到很多的LED灯,存在不同的闪烁方式,比如单闪,双闪,快闪,慢闪等等,我需要一个有如下特性的LED驱动

  • 方便的增加不同闪烁模式
  • 可以切换闪烁模式
  • 增加LED数目不会有太多的改动
  • 方便移植,要有良好的硬件对接接口

好,那就开整吧。
PS:本文中的程序源码只做演示,可运行的代码文末有链接

数据结构分析

首先考虑一颗LED的相关数据结构。
显然构建LED结构体应该有on,off接口,如下

typedef struct{
	void (*init)(void);	//初始化ED
	void (*on)(void);	//打开LED
	void (*off)(void);	//关闭LED
}led_t;

LED闪烁是亮灭的交替,我们可以关注其中的两个参数,

  • LED亮起时长,标记为ontime
  • LED闪烁周期,标记为cycle
    单片机中的通用LED驱动,单片机,嵌入式硬件,stm32,驱动开发
    将这两个参数抽象为led_mode_t结构体
typedef struct {
	uint16_t cycle;		//LED闪烁周期
	uint16_t ontime;	//LED亮起的时长
}led_mode_t;

一颗LED可能会有很多闪烁模式,不同LED闪烁模式数量不同,所以当我们将led_mode_t集成到led_t的时候,应该采用指针形式,在运行的时候申请该结构的内存。由此,丰富led_t结构体如下

typedef struct{
	void (*init)(void);	//初始化ED
	void (*on)(void);	//打开LED
	void (*off)(void);	//关闭LED
	led_mode_t *mode;		//LED闪烁模式具体的数据
	uint8_t mode_count;		//LED闪烁模式个数
	uint8_t mode_current;	//当前闪烁模式编码
}led_t;

OK,LED结构体初步搭建完毕,接下来假设我们有4颗LED,每一颗闪烁的时间参数如下

  • 快闪:200ms亮起,200ms熄灭,周期400ms
  • 慢闪:500ms亮起,500ms熄灭,周期1000ms
  • 单闪:100ms亮起,900ms熄灭,周期1000ms
  • 双闪:30ms亮起, 70ms熄灭,30ms亮起, 870ms熄灭,周期1000ms

前三个都好说,最后一个需要简单分析一下。双闪可以看作两种闪烁模式的切换。第一种30ms亮起, 70ms熄灭,周期100ms。第二种30ms亮起, 870ms熄灭,周期900ms。
单片机中的通用LED驱动,单片机,嵌入式硬件,stm32,驱动开发

程序框架搭建

所以,初始化该情况下的代码如下

typedef struct {
	uint16_t cycle;		//LED闪烁周期
	uint16_t ontime;	//LED亮起的时长
}led_mode_t;

typedef struct{
	void (*init)(void);	//初始化ED
	void (*on)(void);	//打开LED
	void (*off)(void);	//关闭LED
	led_mode_t *mode;		//LED闪烁模式具体的数据
	uint8_t mode_count;		//LED闪烁模式个数
	uint8_t mode_current;	//当前闪烁模式编码
}led_t;

led_t led_array[4];

void led0_init(void){}
void led1_init(void){}
void led2_init(void){}
void led3_init(void){}
void led0_on(void){}
void led1_on(void){}
void led2_on(void){}
void led3_on(void){}
void led0_off(void){}
void led1_off(void){}
void led2_off(void){}
void led3_off(void){}

void bsp_led_init(void)
{
	//初始化函数指针
	led_array[0].on = led0_on;
	led_array[0].off = led0_off;
	led_array[0].init = led0_init;
	led_array[0].mode_count = 1;
	
	led_array[1].on = led1_on;
	led_array[1].off = led1_off;
	led_array[1].init = led1_init;
	led_array[1].mode_count = 1;	

	led_array[2].on = led2_on;
	led_array[2].off = led2_off;
	led_array[2].init = led2_init;
	led_array[2].mode_count = 1;

	led_array[3].on = led3_on;
	led_array[3].off = led3_off;
	led_array[3].init = led3_init;
	led_array[3].mode_count = 2;
	
	for(uint8_t i = 0; i < sizeof(led_array)/led_array[0]; i++)
	{
		led_array[i].mode = malloc(sizeof(led_mode_t) * led_array[i].mode_count);
		memset(led_array[i].mode, 0, sizeof(led_mode_t) * led_array[i].mode_count);
	}
	//初始化mode时间参数
	led_array[0].mode[0].ontime = 200;	
	led_array[0].mode[0].cycle 	= 400;
	led_array[1].mode[0].ontime = 500;
	led_array[1].mode[0].cycle 	= 1000;
	led_array[2].mode[0].ontime = 100;
	led_array[2].mode[0].cycle 	= 1000;
	led_array[3].mode[0].ontime = 30;
	led_array[3].mode[0].cycle 	= 100;
	led_array[3].mode[1].ontime = 30;
	led_array[3].mode[1].cycle 	= 900;
}

代码很长,主要长度占用在以下三部分

  • 每一个led都有init,on,off函数
  • 初始化函数指针
  • 初始化mode时间参数

第一部分暂时不做优化,这样会方便我按照顺序说下去吧
第二第三部分,显然程序中有很多重复的代码,我们可以使用可变参数宏来优化

#define INIT_PTR(__index, __onptr, __offptr, __initptr, __modecount)	\
	led_array[__index].on = __onptr;									\
	led_array[__index].off = __offptr;									\
	led_array[__index].init = __initptr;								\
	led_array[__index].mode_count = __modecount;						


#define INIT_MODE(__index, __mode, __ontime, __cycle)	\
	led_array[__index].mode[__mode].ontime = __ontime;	\
	led_array[__index].mode[__mode].cycle 	= __cycle;	

使用这两个宏之后,第二第三部分的代码被优化为如下,看起来少了好多

void bsp_led_init(void)
{
	INIT_PTR(0, led0_on, led0_off, led0_init, 1);
	INIT_PTR(1, led1_on, led1_off, led1_init, 1);
	INIT_PTR(2, led2_on, led2_off, led2_init, 1);
	INIT_PTR(3, led3_on, led3_off, led3_init, 2);
	for(uint8_t i = 0; i < sizeof(led_array)/led_array[0]; i++)
	{
		led_array[i].mode = malloc(sizeof(led_mode_t) * led_array[i].mode_count);
		memset(led_array[i].mode, 0, sizeof(led_mode_t) * led_array[i].mode_count);
	}
	INIT_MODE(0, 0, 200, 400);
	INIT_MODE(1, 0, 500, 1000);
	INIT_MODE(2, 0, 100, 100);
	INIT_MODE(3, 0, 30, 100);
	INIT_MODE(3, 1, 30, 900);
}

OK,我们已经设定了各个LED的闪烁模式,对接了初始化,亮起,熄灭的函数,是时候让他跑起来了。
假设我们有一个bsp_led_tick函数,该函数每1ms调用一次。我们在该函数中定义一个递增的变量tick,比较其它和LED灯ontime的大小。tick比ontime小则LED亮起,比ontime大则LED熄灭。为了循环往复的工作,我们采用的比较值应该是tick对周期的求余而不是tick本身。示例如下

static void bsp_led_tick(void){
#define i_CYCLE_LENGTH  (led_array[i].mode[led_array[i].mode_current].cycle)
#define i_ON_TIME (led_array[i].mode[led_array[i].mode_current].ontime)
	static uint64_t tick;
	for(uint8_t i = 0; i <  i < sizeof(led_array)/led_array[0]; i++)
	{
		if(tick % i_CYCLE_LENGTH < i_ON_TIME)
			led_array[i].on();
		else
			led_array[i].off();
	}
	tick++;
}

我们使用了两个宏i_CYCLE_LENGTH ,i_ON_TIME 来减少代码长度,增加可读性。
可以看出,切换闪烁模式的话直接修改led_array[i].mode_current即可。
到此,我们的驱动框架就很清晰了。
接下来我会指出该框架的问题,并逐一修改。

问题修复

问题1:LED无法关闭
上面代码中LED一直会闪烁无法关闭
解决办法
LED关闭可以认为是一种模式。其中ontime为0,周期为任意值(0除外)
所以,我们可以占用mode[0],将其所有LED的mode[0]初始化为ontime=0, cycle=1,用户设置mode_current为0的时候调用该模式,关闭LED。注意,此时用户设置的闪烁模式需要从mode[1]开始

那么,同样的,如果需要LED常亮呢?我认为LED熄灭是大部分项目中必要的,而常亮却不一定,所以在代码中不做常亮模式的预先设置,如果用户需要的话可以另外设置一个mode,其中的ontime大于cycletime(cycletime不可以为0)即可

问题2:on off重复调用
满足if(tick % i_cycle < i_ontime )条件的时候,程序会重复调用led_array[i].on();即使此时LED已经打开了。
解决办法
在led_t中增加state成员,标记LED状态,已经打开的时候不要重复调用on,已经关闭的不要重复调用off

	···
	for(uint8_t i = 0; i < sizeof(led_array)/led_array[0]; i++)
	{
		//增加关闭模式,此后mode[0]就被占用了,用户定义的闪烁模式要从mode[1]开始
		led_array[i].mode = malloc(sizeof(led_mode_t) * (led_array[i].mode_count)+1);
		memset(led_array[i].mode, 0, sizeof(led_mode_t) *  (led_array[i].mode_count)+1t);
	}
	INIT_MODE(0, 1, 200, 400);
	INIT_MODE(1, 1, 500, 1000);
	INIT_MODE(2, 1, 100, 100);
	INIT_MODE(3, 1, 30, 100);
	INIT_MODE(3, 2, 30, 900);
	···
static void bsp_led_tick(void){
		······
		//cycletime == 0, LED不再闪烁
		if(i_CYCLE_LENGTH == 0)
		{
			if( (lled_array[i]..state == 1))
			{
				led_array[i].state = 0;
				led_array[i].off();
			}
			continue;
		}
		else
		{
			if(tick % i_CYCLE_LENGTH < i_ON_TIME)
			{
					if( (led_array[i].state == 0))
					{//如果之前是关闭的,那么开启
						led_array[i].state = 1;
						led_array[i].on();
					}
			}
			else
			{
					if( led_array.led[i].state == 1)
					{//如果之前是开启的,那么关闭
						led_array[i].state = 0;
						led_array[i].off();
					}
			}
		}
	tick++;
}

问题3:如何实现双闪
我们之前说过,双闪是两种闪烁模式的交替闪烁,那么如何实现交替切换模式呢?
很简单,我们只要再bsp_led_tick中判断tick是否增加到mode[].cycle即可。

//周期回调函数
if(i_CYCLE_LENGTH-1 == (tick % i_CYCLE_LENGTH))
{
	led_array[i].cycle_func();
}

cycle_func是led_t中增加的新成员,作为周期结束的回调函数。使用前需要初始化指向led3_cyclefun,在该函数中做模式切换

static void led3_cyclefun()
{
	if(led_array[3].mode_current== 1)led_array[3].mode_current = 2;
	else if(led_array[3].mode_current== 2)led_array[3].mode_current = 1;
}

有了周期回调函数,再复杂的LED显示效果我们都可以做出来,只需要再周期结束修改显示模式即可。
但是理想很丰满,现实很骨感,这个地方还有一小片乌云等待解决。
单片机中的通用LED驱动,单片机,嵌入式硬件,stm32,驱动开发
我没有按照led3设置mode时间值,按照图上的会直观一点。
可以看到,在第二个闪烁模式tick=2时间段,此时ontime=1,tick%cycletime=2%5=2,那么第二模式根本不会亮起。此时的流程实际上变成了如下图
单片机中的通用LED驱动,单片机,嵌入式硬件,stm32,驱动开发
为了解决这个问题,我们需要明确,模式切换之后tick起始点是不同的,不可以笼统的写作tick%cycletime,模式切换之后应该有自己的tick起始点tick_start,按照(tick-tick_start)%cycletime求得余数
问题4:同步
为了解决问题三,我们引入了起点tick_start机制,这回带来新的问题。
每一个LED都有自己的起点,在模式切换的时候更新为当前tick。模式切换是使用该驱动的人决定的,这会造成即使相同周期的LED灯闪烁相位的差异,看起来在乱闪。
解决办法
在led_t中引入sync成员,ticks_start只有配置了sync为0的时候才会更新为当前tick,否则设置为0。
这将会明确一个事实,sync配置为1的LED无法实现循环的闪烁模式切换。即类似于双闪这种循环的切换模式是不被允许的,单次的切换可以。
双闪如果要做同步该怎么办?应该可以在周期回调函数中做一些工作,留给大家思考。

优化

这部分主要优化的是代码体积,假设我有十个灯,每一个都需要on,off,cyclefunc,这也太恐怖了。所以,我们应该再led_t结构体之上再做一个结构体led_array_t。内容如下

typedef struct{
	led_t led[LED_COUNT];
	void (*on)(void *parameter);	//打开LED
	void (*off)(void *parameter);	//关闭LED
	void (* cycle_func)(void *parameter);	//cycle结束的回调函数
}led_array_t;

这里设置了parameter参数,调用函数的时候将led_t传入以标识身份,在函数内部判断我是哪一个LED,进行相应的操作
举例如下

static void bsp_led_on(void *parameter)
{
	led_t *p = (led_t *)parameter;
	switch(p-led_array.led)
	{
		case 0:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_9);break;
		case 1:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_25);break;
		case 2:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_14);break;
		case 3:DRV_DIO_ChannelOutSet(DRV_DIO_ID_PIO_15);break;
		default:break;
	};
}

OK,内容到此结束了,这样看来写好一个LED并不容易。
下面提供了一个基于RTOS的源码,如果要修改为裸机的并不需要耗费很大功夫,相信你可以做到
程序源码在此:https://gitee.com/nwwhhh/led_flash_driver文章来源地址https://www.toymoban.com/news/detail-650988.html

到了这里,关于单片机中的通用LED驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 7. 嵌入式与单片机

    常见面试题 请说说你对嵌入式的理解。⭐⭐⭐⭐ 嵌入式系统是指以应用为中心,以计算机技术为基础,软件硬件可剪裁,适应应用系统对功能、成本、体积、功耗严格要求的专用计算机系统。 嵌入式系统 主要由 嵌入式微处理器 、 外围硬件设备 、 嵌入式操作系统以及用户

    2024年02月02日
    浏览(89)
  • 嵌入式开发:单片机嵌入式Linux学习路径

    SOC(System on a Chip)的本质区别在于架构和功能。低端SOC如基于Cortex-M架构的芯片,如STM32和NXP LPC1xxx系列,不具备MMU(Memory Management Unit),适用于轻量级实时操作系统如uCOS和FreeRTOS。高端SOC如基于Cortex-A架构的芯片,如三星、高通、飞卡、全志和瑞芯微等,具备MMU,支持虚拟内

    2024年02月15日
    浏览(57)
  • 嵌入式(二)单片机基础 | 单片机特点 内部结构 最小系统 电源 晶振 复位

    上一篇文章我们介绍了嵌入式系统 嵌入式系统(Embedded System)是一种特定用途的计算机系统,它通常嵌入在更大的产品或系统中,用于控制、监测或执行特定的任务。这些系统通常由硬件和软件组成,旨在满足特定的需求,如嵌入在家电、汽车、医疗设备、工业自动化、消费

    2024年02月02日
    浏览(108)
  • 单片机(嵌入式)程序分层架构

    目录 前言 嵌入式3层软件架构 嵌入式4层软件架构-1 驱动层 操作系统层 中间件层 应用层 嵌入式4层软件架构-2 硬件层 嵌入式微处理芯片 嵌入式存储器系统 嵌入式I/O接口 中间层 系统软件层 RTOS 文件系统 GUI 应用层 嵌入式5层软件架构1 嵌入式5层软件架构2 嵌入式6层软件架构

    2023年04月19日
    浏览(80)
  • 单片机和Linux嵌入式区别

    单片机是一种集成电路,它能够在一个芯片上完成各种计算、控制和管理任务。单片机没有明确的分层,这是因为它通常被用来设计嵌入式系统,其程序结构和功能要根据具体的应用需求来设计。 在单片机的程序设计中,可以通过一些方法将程序分为不同的层次,比如可以将

    2024年01月17日
    浏览(64)
  • 【单片机/嵌入式】最完整学习路线

            关于这个问题我在网上寻找到 了 相关文章解释得很详细,不了解的同学可以参考一下这篇文章: 到底什么是嵌入式?什么是单片机? - 知乎         一个人学习是孤独的,如果你是一名学生,建议加入学校的电子社团或者实验室,有老师的帮助和同学的互助

    2024年02月03日
    浏览(54)
  • 嵌入式51单片机04-矩阵按键系列

    一、矩阵按键基础知识 矩阵按键工作原理 : 逐行扫描 :通过高四位轮流输出低电平来对矩阵键盘进行扫描,当低四位接收到的数据不全为1的时候,说明有按键按下,然后通过判断低四位数据中哪一位为零来判断哪一个按键被按下。 逐列扫描 :通过低四位轮流输出低电平来

    2024年02月07日
    浏览(65)
  • 【唠唠嵌入式】__如何学习单片机?

    目录 前言 个人定位,从事软件还是硬件? 学习内容 (* ̄︶ ̄)创作不易!期待你们的 点赞、收藏和评论喔。 作为一个老司机,多年来跟 单片机 、 Keil 、 C语言 、 AD 、 烙铁 、 风枪 、 示波器 、 电子元器件 纠缠不清。期间经历了不少磨练,熬过了很多夜晚,也掉了大把头

    2024年02月06日
    浏览(71)
  • 【单片机毕设选题】stm32实现车牌识别系统 -物联网 嵌入式 单片机

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月20日
    浏览(60)
  • 单片机项目分享 stm32机器视觉的人脸识别系统 - 单片机 物联网 嵌入式

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年01月22日
    浏览(84)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包