六、灯效处理

灯效应该是整个键盘里比较困难的部分了,尤其在一个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();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值