一、源码
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的变量,这个表示在修改显示缓冲区的时候,等待当前操作执行完成,还是立即打断,执行新的配置。
五、其他
一堆干涩的文字,说的又是很抽象的东西。希望读者体谅。博主之后尽量插入一些流程图,或者演示图。
可以呀