灯效应该是整个键盘里比较困难的部分了,尤其在一个8位,cpu只有6MHz的单片机上实现。我也没怎么做过灯效类的项目,所以灯效基本就是随便写写,这里大概记录下设计过程。
一、色彩模型
对于单色灯,亮度只是单纯的增减pwm,但对于RGB而言,调节亮度不能再单纯的增减,在rgb这个色彩模型下,直接对当前颜色的3个pwm值乘除亮度因子就可以实现调节亮度,但是这需要16位pwm,而且意味着pwm频率也大打折扣;而如果使用高级色彩模型意味着运行效率变低,尤其对于这颗mcu,只支持8位乘法,不支持除法;
最终选择了使用hsv色彩模型,天然带有过渡、饱和、亮度控制,有些灯效的做过渡很方便,经过查找资料及优化后,实现了一个灯从hsv转rgb大概需要22us。
/// @brief 快速HSV转RGB,24MHz/4T下,运行时间约为22us
/// @param h 输入,色调值,范围(0-255)
/// @param s 输入,饱和度,范围(0-255)
/// @param v 输入,明度,范围(0-255)
/// @param r 输出,红色,范围(0-255)
/// @param g 输出,绿色,范围(0-255)
/// @param b 输出,蓝色,范围(0-255)
/// @return
void Fast_HSVToRGB(unsigned char h,unsigned char s,unsigned char v,unsigned char *r,unsigned char *g,unsigned char *b);
二、回调
为了解耦灯效和按键的联系,做了个回调函数,按键代码通过回调来通知灯控改变灯效。
typedef enum
{
TurnLED,
IncLumin,
DecLumin,
ShiftEffectAry1,
ShiftEffectAry2,
ShiftEffectAry3,
ShiftEffectAry4,
ShiftColor,
ShiftDir,
IncSpeed,
DecSpeed,
LockGUI,
Ble1,
Ble2,
Ble3,
G24,
USB,
DispBattery,
BtnRelease,
BtnPress,
}custom_usage_t;
/// @brief 按键事件回调
/// @param evt_id 事件
/// @param param1 参数1
/// @param param2 参数2
static void Btn_Evt_Handler(custom_usage_t evt_id,u8 param1,u8 param2)
三、单灯控制
为了可以每个灯都能独立运行写基本灯效,做了个宏大的结构体数组,每个结构体记录了每个灯的一些运行参数,然后在main里定时轮询,这么做优点是每个灯执行基本灯效时集中管理了,外层灯效只需简单调用启动函数即可,缺点就是每次都需要轮询全部灯,在大量灯同时运行的时候很耗时,这里应该还是有很大的优化空间。
.h文件
#ifndef _LED_CTRL_H_
#define _LED_CTRL_H_
#ifdef _LED_CTRL_C_
#define _LED_CTRL_E_
#else
#define _LED_CTRL_E_ extern
#endif
#include"..\include.h"
typedef enum
{
smd_off, //灭灯
smd_on, //亮灯
smd_onoff, //先亮后灭
smd_offon, //先灭后亮
smd_gradient, //渐变
smd_gradientloop, //循环渐变
}led_smd_t;
typedef struct
{
u8 hue;
u8 sat;
u8 val;
union
{
u8 state;
struct
{
u8 mode:3; //动画模式
u8 temp:1; //临时标志
u8 tcolor:4;//预设颜色索引,最多16个
};
};
}sled_s;
_LED_CTRL_E_ sled_s sLed[KEY_NUM];
_LED_CTRL_E_ void LEDApp_EachCtrlInit(u8 checktime,u8 incStep,u8 decStep,u8 preset);
_LED_CTRL_E_ void LEDApp_EachBegin(u8 index,u8 mode,u8 color);
_LED_CTRL_E_ void LEDApp_EachTraverse(void);
#endif
.c文件
#define _LED_CTRL_C_
#include"..\include.h"
typedef struct
{
union
{
u8 Enable;
struct
{
u8 preset:1;
u8 reserve:7;
};
};
u8 decStep;
u8 incStep;
}led_each_t;
static led_each_t LedEachCtrl;
/// @brief 设置单灯自动控制参数
/// @param checktime 运行自动控制函数的间隔时间,单位毫秒
/// @param incStep 向上变化步进
/// @param decStep 向下变化步进
/// @param preset 预设标志,0=预设颜色,1=自定义颜色
void LEDApp_EachCtrlInit(u8 checktime,u8 incStep,u8 decStep,u8 preset)
{
if(preset)
LedEachCtrl.preset = 1;
else
LedEachCtrl.preset = 0;
Task_TimeSet(Task_LedEach, checktime);
LedEachCtrl.incStep = incStep;
LedEachCtrl.decStep = decStep;
}
/// @brief 启动单灯控制
/// @param index 索引号
/// @param mode 灯效模式,传入值详见SINGLE_MODE枚举
/// @param color 预设颜色序号,如果在控制参数中设置了自定义颜色,此处设置将不会生效
void LEDApp_EachBegin(u8 index,u8 mode,u8 color)
{
if(index == 0XFF)
{
for(u8 i=0;i<KEY_NUM;i++)
{
sLed[i].mode = mode;
sLed[i].tcolor = color;
sLed[i].temp = Valid;
}
}
else
{
sLed[index].mode = mode;
sLed[index].tcolor = color;
sLed[index].temp = Valid;
}
if(!Task_Comps[Task_LedEach].run)
{
Task_State(Task_LedEach, 1);
}
}
/// @brief 单灯灯效处理
/// @param index 索引号
static void LEDApp_EachHandle(u8 index)
{
u8 v = sLed[index].val;
u8 h = sLed[index].hue;
u8 s = sLed[index].sat;
u8 t = sLed[index].tcolor;
u8 preh;
u8 pres;
if(LedEachCtrl.preset)
{
preh = PresetHueTab[t];
pres = PresetSatTab[t];
}
else
{
preh = hue;
pres = sat;
}
switch(sLed[index].mode)
{
case smd_off: //灭灯,降低亮度直到0,此过程只涉及val
if(v==0)
{
sLed[index].temp = InValid;
}
else
{
if(v>=LedEachCtrl.decStep)
v -= LedEachCtrl.decStep;
else
v = 0;
}
break;
case smd_on: //亮灯,增加亮度直到设定值,此过程只涉及val
if(v==value)
{
sLed[index].temp = InValid;
sLed[index].mode = smd_off;
}
else
{
if(v<value)
{
u16 temp = (u16)(v + LedEachCtrl.incStep);
if(temp<=value)
v = (u8)temp;
else
v = value;
}
else
{
if(v>=LedEachCtrl.decStep)
{
v -= LedEachCtrl.decStep;
if(v<value)
{
v = value;
}
}
else
v = value;
}
}
h = preh;
s = pres;
break;
case smd_onoff: //先亮后灭,此过程只涉及val
if(sLed[index].temp)
{
if(v>=value)
sLed[index].temp = 0;
else
{
u16 temp = (u16)(v + LedEachCtrl.incStep);
if(temp<=value)
v = temp;
else
v = value;
}
}
else
{
if(v==0)
{
sLed[index].mode = smd_off;
}
else
{
if(v>=LedEachCtrl.decStep)
v -= LedEachCtrl.decStep;
else
v = 0;
}
}
h = preh;
s = pres;
break;
case smd_offon: //先灭后亮,此过程只涉及val
if(sLed[index].temp)
{
if(v==0)
{
sLed[index].temp = 0;
h = preh;
s = pres;
}
else
{
if(v>=LedEachCtrl.decStep)
v -= LedEachCtrl.decStep;
else
v = 0;
}
}
else
{
if(v>=value)
{
sLed[index].mode = smd_off;
}
else
{
u16 temp = (u16)(v + LedEachCtrl.incStep);
if(temp<=value)
v = temp;
else
v = value;
}
}
break;
case smd_gradient: //渐变,此过程只涉及hue和sat
if(h!=preh)
{
h++;
}
if(s!=pres)
{
if(s>pres)
s--;
else
s++;
}
v = value;
if((h==preh)&&(s==pres))
{
sLed[index].mode = smd_off;
sLed[index].temp = InValid;
}
break;
case smd_gradientloop: //光谱循环,此过程只涉及hue
h++;
s = HSV_SAT_MAX;
v = value;
break;
default:break;
}
ConvertFromHSV(index, h, s, v);
sLed[index].hue=h;
sLed[index].sat=s;
sLed[index].val=v;
}
/// @brief 单灯控制运行函数
/// @param
void LEDApp_EachTraverse(void)
{
u8 temp = 0;
for(u8 i=0;i<KEY_NUM;i++)
{
if(sLed[i].state&0X0F)
{
LEDApp_EachHandle(i);
temp |= 0x01;
}
}
if(!temp)
{
Task_State(Task_LedEach, 0); //关闭任务
}
}
四、主灯效
这块代码就很多了,当前做了18个灯效 + 音乐律动,不过18个灯效都很基本,由于只是demo所以也就没过分细究灯效:
然后使用管理函数在main函数里定时调用:
void LEDApp_EffectHandle(void)
{
switch(KeyParam.LedMode)
{
case AlwaysOn:
LEDApp_AlwaysOn();
break;
case Breath:
LEDApp_Breath();
break;
case Snake:
LEDApp_Snake();
break;
case SnakeCircle:
LEDApp_SnakeCircle();
break;
case ColorLoop:
// LEDApp_ColorLoop();
break;
case LRWave:
// LEDApp_LRWave();
break;
case ObliqueWave:
LEDApp_ObWave();
break;
case Rain:
LEDApp_Rain();
break;
case Meteor:
LEDApp_Meteor();
break;
case Explosion:
LEDApp_Explosion();
break;
case TrigSpread:
case TrigSpreadReverse:
LEDApp_TrigSpread();
break;
case TrigRow:
case TrigRowReverse:
LEDApp_TrigRow();
break;
case UserCustom:
LEDApp_UserColor();
default:break;
}
}
切换灯效时调一下初始化函数:
void LEDApp_SwitchEffect(unsigned char reset)
{
if(KeyParam.LedEn)
{
LEDApp_GetHSV();
switch(KeyParam.LedMode)
{
case AlwaysOn:
LEDApp_AlwaysOnInit(reset);
break;
case Breath:
LEDApp_BreathInit(reset);
break;
case Snake:
LEDApp_SnakeInit(reset);
break;
case SnakeCircle:
LEDApp_SnakeCircleInit(reset);
break;
case ColorLoop:
LEDApp_ColorLoopInit(reset);
break;
case LRWave:
LEDApp_LRWaveInit(reset);
break;
case Rain:
LEDApp_RainInit(reset);
break;
case ObliqueWave:
LEDApp_ObWaveInit(reset);
break;
case Meteor:
LEDApp_MeteorInit(reset);
break;
case Explosion:
LEDApp_ExplosionInit(reset);
break;
case TrigSpread:
case TrigSpreadReverse:
LEDApp_TrigSpreadInit(reset);
break;
case TrigSingle:
LEDApp_TrigSingleInit(reset);
break;
case TrigSingleReverse:
LEDApp_TrigSingleReverseInit(reset);
break;
case TrigRow:
LEDApp_TrigRowInit(reset);
break;
case TrigRowReverse:
LEDApp_TrigRowReverseInit(reset);
break;
case Music:
LEDApp_MusicInit(reset);
break;
case UserCustom:
LEDApp_UserColorInit(reset);
break;
case Off:
LEDApp_OffInit();
break;
default:break;
}
}
else
{
LEDApp_OffInit();
}
}