【裸板驱动】灯控模块分享

一、源码

mod_light.c文件

/*
文件功能:
	实现io口(灯控)的亮灭,反转控制,实现io口的高低、时长、循环次数的控制。
用法介绍:
	1.使用宏定义MOD_LIGHT_NUM选择使用多少个灯
	2.根据打开的宏定义分别配置 初始化MOD_LIGHTx_INIT、卸载MOD_LIGHTx_END、开灯MOD_LIGHTx_ON、关灯MOD_LIGHTx_OFF三个宏定义
	3.使用函数Mod_Light_Init进行初始化
	4.将函数Mod_Light_Loop放置于10ms的循环中
作者:
	春风催雪
更新记录:
	2021-11-10:初次使用
	2022-5-13:给所有函数添加返回,添加IO卸载函数适用linux,优化函数Mod_Light_Set_Twinkle
*/
#include "include.h"

#define MOD_LIGHT_NUM     1  //实际使用了几个灯

#define MOD_LIGHT0_INIT     Dev_Led_Init()
#define MOD_LIGHT0_END      Dev_Led_Release()
#define MOD_LIGHT0_ON       Dev_Led_Set(1)
#define MOD_LIGHT0_OFF      Dev_Led_Set(0)
														
#if MOD_LIGHT_NUM > 1
#define MOD_LIGHT1_INIT     Free_Func()
#define MOD_LIGHT1_END      Free_Func()
#define MOD_LIGHT1_ON       Free_Func()
#define MOD_LIGHT1_OFF      Free_Func()
#endif
														
#if MOD_LIGHT_NUM > 2
#define MOD_LIGHT2_INIT     Free_Func()
#define MOD_LIGHT2_END      Free_Func()
#define MOD_LIGHT2_ON       Free_Func()
#define MOD_LIGHT2_OFF      Free_Func()
#endif

#if MOD_LIGHT_NUM > 3
#define MOD_LIGHT3_INIT     Free_Func()
#define MOD_LIGHT3_END      Free_Func()
#define MOD_LIGHT3_ON       Free_Func()
#define MOD_LIGHT3_OFF      Free_Func()
#endif

#if (MOD_LIGHT_NUM == 0) || (MOD_LIGHT_NUM > 8)
	#error "as MOD_LIGHT_NUM. You can only select 1 ~ 4 lights!!!"
#endif

MODLIGHT ModLight[MOD_LIGHT_NUM];  //定义对应灯的结构体数据

/*
	描述:初始化灯io口
	返回:	0--初始化成功
			-1--led0初始化失败
			-2--led1初始化失败
			-3--led2初始化失败
			-4--led3初始化失败
*/
char Mod_Light_Init(void)
{
	unsigned char i,j;
	
	if(MOD_LIGHT0_INIT < 0)
	{
		return -1;
	}
#if MOD_LIGHT_NUM > 1
	if(MOD_LIGHT1_INIT < 0)
	{
		return -2;
	}
#endif
#if MOD_LIGHT_NUM > 2
	if(MOD_LIGHT2_INIT < 0)
	{
		return -3;
	}
#endif
#if MOD_LIGHT_NUM > 3
	if(MOD_LIGHT3_INIT < 0)
	{
		return -4;
	}
#endif
	
	for(j=0; j<MOD_LIGHT_NUM; j++)  //对应灯的结构体数据初始化
	{
		ModLight[j].LightState = 0;
		for(i=0; i<50; i++)
		{
			ModLight[j].LightBuff[i] = 0;
		}
		ModLight[j].LightStartPos = 0;
		ModLight[j].LightEndPos = 0;
		ModLight[j].LightCount = 0;
		ModLight[j].LightLock = 0;
		Mod_Light_Close(j);
	}
	return 0;
}

/*
	描述:释放灯io口
	返回:	0--释放成功
			-1--led0释放失败
			-2--led1释放失败
			-3--led2释放失败
			-4--led3释放失败
*/
char Mod_Light_End(void)
{
	if(MOD_LIGHT0_END < 0)
	{
		return -1;
	}
#if MOD_LIGHT_NUM > 1
	if(MOD_LIGHT1_END < 0)
	{
		return -2;
	}
#endif
#if MOD_LIGHT_NUM > 2
	if(MOD_LIGHT2_END < 0)
	{
		return -3;
	}
#endif
#if MOD_LIGHT_NUM > 3
	if(MOD_LIGHT3_END < 0)
	{
		return -4;
	}
#endif
	return 0;
}

/*
	描述:设置灯的开关
	Light:选择灯,根据实际灯的对应关系选择
	Level:灯的开关状态
	返回:	0--设置成功
			-1--灯编号超出了可用范围
			-2--灯状态超出允许范围
			-3--灯状态设置失败
*/
static char Mod_Light_Switch(unsigned char Light, unsigned char Level)
{
	int ret;
	
	if(Light >= MOD_LIGHT_NUM)
	{
		return -1;
	}
	if(Level > 1)
	{
		return -2;
	}
	
	switch(Light)
	{
		case 0:
			if(Level)
			{
				ret = MOD_LIGHT0_ON;
			}
			else
			{
				ret = MOD_LIGHT0_OFF;
			}
			break;
#if MOD_LIGHT_NUM > 1
		case 1:
			if(Level)
			{
				ret = MOD_LIGHT1_ON;
			}
			else
			{
				ret = MOD_LIGHT1_OFF;
			}
			break;
#endif
#if MOD_LIGHT_NUM > 2
		case 2:
			if(Level)
			{
				ret = MOD_LIGHT2_ON;
			}
			else
			{
				ret = MOD_LIGHT2_OFF;
			}
			break;
#endif
#if MOD_LIGHT_NUM > 3
		case 3:
			if(Level)
			{
				ret = MOD_LIGHT3_ON;
			}
			else
			{
				ret = MOD_LIGHT3_OFF;
			}
			break;
#endif
		default:
			break;
	}
	
	if(ret < 0)
	{
		return -3;
	}
	
	ModLight[Light].LightState = Level;
	return 0;
}

/*
	描述:灯io口翻转
	Light:选择灯,根据实际灯的对应关系选择
	返回:	0--设置成功
			-1--灯编号超出了可用范围
			-2--io口翻转失败
*/
char Mod_Light_Flip(unsigned char Light)
{
	char ret;
	
	if(Light >= MOD_LIGHT_NUM)
	{
		return -1;
	}
	
	if(ModLight[Light].LightState == 0)  //获取存储的灯状态,进行翻转
	{
		ret = Mod_Light_Switch(Light, 1);
	}
	else
	{
		ret = Mod_Light_Switch(Light, 0);
	}
	
	if(ret < 0)
	{
		return -2;
	}
	
	return 0;
}

/*
	描述:打开灯,直接打断当前的闪烁效果
	Light:选择灯,根据实际灯的对应关系选择
	返回:	0--设置成功
			-1--灯编号超出了可用范围
			-2--打开灯失败
*/
char Mod_Light_Open(unsigned char Light)
{
	if(Light >= MOD_LIGHT_NUM)
	{
		return -1;
	}
	
	ModLight[Light].LightLock = 1;  //打开灯控制锁
	if(Mod_Light_Switch(Light, 1) < 0)
	{
		ModLight[Light].LightLock = 0;
		return -2;
	}
	ModLight[Light].LightStartPos = ModLight[Light].LightEndPos;
	ModLight[Light].LightLock = 0;
	
	return 0;
}

/*
	描述:关闭灯,直接打断当前的闪烁效果
	Light:选择灯,根据实际灯的对应关系选择
	返回:	0--设置成功
			-1--灯编号超出了可用范围
			-2--关闭灯失败
*/
char Mod_Light_Close(unsigned char Light)
{
	if(Light >= MOD_LIGHT_NUM)
	{
		return -1;
	}
	
	ModLight[Light].LightLock = 1;
	if(Mod_Light_Switch(Light, 0) < 0)
	{
		ModLight[Light].LightLock = 0;
		return -2;
	}
	ModLight[Light].LightStartPos = ModLight[Light].LightEndPos;
	ModLight[Light].LightLock = 0;
	
	return 0;
}

/*
	描述:循环处理函数,10ms每次
	返回:	0--本次处理正常
			-x--本次处理异常,(x-1)对应灯编号
*/
char Mod_Light_Loop(void)
{
	unsigned char i;
	char ret = 0;
	
	for(i=0; i<MOD_LIGHT_NUM; i++)
	{
		if(ModLight[i].LightLock == 0)  //无锁状态才能进行处理
		{
			ModLight[i].LightCount++;  //计时
			if((ModLight[i].LightCount > 65530) || (ModLight[i].LightCount >= ModLight[i].LightBuff[ModLight[i].LightStartPos]))  //判断时间超长或者到达预定时间
			{
				if(ModLight[i].LightStartPos != ModLight[i].LightEndPos)  //如果有数据尚未读取
				{
					ModLight[i].LightStartPos++;  //跳转下一缓存区
					if(ModLight[i].LightStartPos > 49)
					{
						ModLight[i].LightStartPos = 0;
					}
					if(Mod_Light_Flip(i) < 0)  //灯翻转
					{
						ret = -(i + 1);
					}
				}
				ModLight[i].LightCount = 0;
			}
		}
	}
	
	return ret;
}

/*
	描述:设置灯的亮灭缓冲区域
	Light:选择灯
	Dat:存储灯的亮/灭时间,亮灭状态相互切换,仅能通过顺序控制,dat单位为10ms
	Length:数据长度
	Mode:有抢占和等待两种
	返回:	0--设置成功
			-1--Light灯编号超出了可用范围
			-2--Length设置的数据超长
			-3--剩余数据空间不足
			-4--Mode模式超出可用种类
*/
char Mod_Light_Set_Buff(unsigned char Light, unsigned short *Dat, unsigned char Length, MLSSEIZE Mode)
{
	unsigned short temp;
	
	if(Light >= MOD_LIGHT_NUM)
	{
		return -1;
	}
	if(Length > 50)
	{
		return -2;
	}
	
	ModLight[Light].LightLock = 1;  //首先设置锁
	if(Mode == MLS_SEIZE_WAIT)  //等待模式下
	{
		if(ModLight[Light].LightStartPos >= ModLight[Light].LightEndPos)  //判断是否超长
		{
			temp = 50 - ModLight[Light].LightStartPos + ModLight[Light].LightEndPos;
		}
		else
		{
			temp = ModLight[Light].LightEndPos - ModLight[Light].LightStartPos;
		}
		if(temp < Length)
		{
			ModLight[Light].LightLock = 0;
			return -3;
		}
		
		for(temp=0; temp<Length; temp++)  //写缓存区
		{
			ModLight[Light].LightEndPos++;
			if(ModLight[Light].LightEndPos > 49)
			{
				ModLight[Light].LightEndPos = 0;
			}
			ModLight[Light].LightBuff[ModLight[Light].LightEndPos] = Dat[temp];
		}
	}
	else if(Mode == MLS_SEIZE_BREAK)  //抢占模式下
	{
		ModLight[Light].LightStartPos = ModLight[Light].LightEndPos;  //清空缓冲区
		for(temp=0; temp<Length; temp++)
		{
			ModLight[Light].LightEndPos++;
			if(ModLight[Light].LightEndPos > 49)
			{
				ModLight[Light].LightEndPos = 0;
			}
			ModLight[Light].LightBuff[ModLight[Light].LightEndPos] = Dat[temp];  //赋值
		}
	}
	else
	{
		ModLight[Light].LightLock = 0;
		return -4;
	}
	ModLight[Light].LightLock = 0;
	
	return 0;
}

/*
	设置灯的闪烁
	Light:选择灯
	Tim:灯亮的时间,单位10ms
	Num:灯闪烁的次数,25以内
	Per:灯熄灭的占比,单位0.1,
	返回:	0--设置成功
			-1--灯编号超出了可用范围
			-2--Per熄灭比例数值不能为零
			-3--亮灯时常或者熄灭时间超出最大值65530或者为0
			-4--Num循环次数超出最大范围25
			-5--对应的灯操作失败
			-6--缓存设置失败
*/
char Mod_Light_Set_Twinkle(unsigned char Light, unsigned short Tim, unsigned char Num, unsigned char Per)
{
	unsigned short buff[50];
	unsigned char i;
	
	if(Light >= MOD_LIGHT_NUM)
	{
		return -1;
	}
	if(Per < 1)
	{
		return -2;
	}
	if((Tim > 65530) || (((unsigned int)Tim * Per / 10) > 65530) || (Tim == 0))
	{
		return -3;
	}
	if((Num > 25) || (Num == 0))
	{
		return -4;
	}
	
	for(i=0; i<Num; i++)  //打开时间和熄灭时间同时写入
	{
		buff[i * 2] = Tim;
		buff[i * 2 + 1] = (unsigned int)Tim * Per / 10;
	}
	if(Mod_Light_Close(Light) < 0)  //为了控制亮灭不会反向,设置缓冲前需要先把灯设置关
	{
		return -5;
	}
	if(Mod_Light_Set_Buff(Light, buff, Num * 2, MLS_SEIZE_WAIT))
	{
		return -6;
	}
	return 0;
}





mod_light.h源码

#ifndef __MOD_LIGHT_H__
#define __MOD_LIGHT_H__

typedef enum MLSSEIZEENUM  //灯设置的抢占方式
{
	MLS_SEIZE_WAIT = 0,
	MLS_SEIZE_BREAK,
}MLSSEIZE;


typedef struct MODLIGHTSTRUCT
{
	unsigned char LightState;  //灯状态-开关状态
	unsigned short LightBuff[50];  //灯亮缓存区
	unsigned char LightStartPos;  //缓冲起始位置
	unsigned char LightEndPos;  //缓冲结束位置
	unsigned short LightCount;  //灯缓存移位计数
	unsigned char LightLock;  //灯缓存锁
}MODLIGHT;


extern char Mod_Light_Init(void);
extern char Mod_Light_End(void);
extern char Mod_Light_Flip(unsigned char Light);
extern char Mod_Light_Open(unsigned char Light);
extern char Mod_Light_Close(unsigned char Light);
extern char Mod_Light_Loop(void);
extern char Mod_Light_Set_Buff(unsigned char Light, unsigned short *Dat, unsigned char Length, MLSSEIZE Mode);
extern char Mod_Light_Set_Twinkle(unsigned char Light, unsigned short Tim, unsigned char Num, unsigned char Per);


#endif


二、功能描述

1、我想我们学习单片机的时候,第一个程序就是点灯程序,点灯程序是非常简单的,只需要操作IO口高低电平变化就可以了。

2、随着学习的深入,点灯的功能不再是简单的点亮,开始需要控制灯闪烁,控制灯按照不同的方式闪烁,需要控制灯闪烁的次数。

3、当把这些功能集成到一起的时候,就有了灯控的模块。

三、用法介绍

1、很多时候,我们的产品可能不止一个led灯,所以第一步就是配置宏定义MOD_LIGHT_NUM,定义你使用了几个led,当然是有上限的,最高四个。

2、我们的模块是运行在硬件之上的,所以说,灯的基础控制函数还是需要单独实现。接下来有四个宏定义需要配置。

#define MOD_LIGHT0_INIT     Dev_Led_Init()  //led灯的硬件初始化
#define MOD_LIGHT0_END      Dev_Led_Release()  //led灯的释放
#define MOD_LIGHT0_ON       Dev_Led_Set(1)  //点亮led
#define MOD_LIGHT0_OFF      Dev_Led_Set(0)  //关闭led

注意:每当你开启了一个led灯,就要配置相关的宏定义。

3、配置完宏定义,就需要初始化了,将函数Mod_Light_Init放置在初始化的位置。

4、然后将函数Mod_Light_Loop放置在10ms的循环当中,做循环检测。

5、当你配置完上述部分,就可以函数中使用本模块了,例如

Mod_Light_Open(0);  //关闭零号灯
Mod_Light_Close(1);  //打开一号灯
Mod_Light_Flip(2);  //反转2号灯
Mod_Light_Set_Twinkle(0, 50, 3, 1);  //将零号灯打开500ms,关闭500ms,循环3次

四、实现逻辑

1、实现led的定时开关,最直接的方式就是延时控制。当然这不是我们的初衷,延时会占用大量的cpu资源,也容易和程序中其他的部分产生冲突。很显然,定时控制是更好的选择。

2、首先做一个数组,数组中的每个数值都是定时时间。然后让一个指针按照顺序读取数组中的数据(时间),每当读取到的时间到来时,指针再去读取下一个数据。这样子,我们就完成了一个基础的模型。

3、但是这个模型有一个问题,就是它虽然有时间分段,但是没有高低电平的分辨,而我们的led是有高低(亮灭)之分的。所以我们还要想办法让其能识别高低电平。

4、当然,最直接的办法是再次定义一个数组,用这个新数组表示led的状态,但是这样就会占用较多的内存。我们可以使用更巧妙一点的办法,因为led的状态只有两种(0或1),那是否表示,当定时时间到来时,就表示led的状态要发生变化了呢,如果我们在设置的时候,定义led的初始状态,那我们就定义了led的所有状态。

5、这样,我们的模型就变成了,有一个数组,其中的数据表示led的反转时间,每当这个时间到来时,led反转,并且读取下一次的反转时间。

6、led也不能一直反转吧,数组的长度也不会无限长吧,所以实际应用时,读取时间的指针是两个,一个是当前指针,另一个是结束指针。

a、如果我们想要打开某个灯,只需要把读取指针放置在结束指针位置(表示读取已经结束,led状态将不再自行变化),然后将led状态设置为开,并且操作打开led,这样就实现了开灯的完整操作。

b、当然,关灯,反转,也是同样的流程。

c、如果想要设置灯的闪烁,我们只需要设置好闪烁的配置数据(也就是数组),然后将灯恢复为关灯状态,让模块自行按照数组自行变化,就实现了我们的效果。

7、当然,这中间有一个很重要的点,就是“锁”,定时执行的函数一般在定时器中,设置函数在主循环中,两者肯定会出现“撞车”的现象,也就是两个函数同时操作数组,那可能会造成无法预知的后果,所以,每次在设置的时候都上锁,定时器中的函数检测到锁就跳过处理,这样就避免了上述问题。之所以在设置函数中上锁,那是因为中断会穿插在主函数中,也就是只有设置操作会被打断。

8、细心的小伙伴肯定会发现函数Mod_Light_Set_Buff有一个mode的变量,这个表示在修改显示缓冲区的时候,等待当前操作执行完成,还是立即打断,执行新的配置。

五、其他

一堆干涩的文字,说的又是很抽象的东西。希望读者体谅。博主之后尽量插入一些流程图,或者演示图。

博客内容均系原创,未经允许严禁转载!

评论

  1. wjkddc
    2年前
    2022-9-08 19:10:55

    可以呀

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇