glc002-firmware/sampleLightCtrl.c

638 lines
14 KiB
C

/********************************************************************************************************
* @file sampleLightCtrl.c
*
* @brief This is the source file for sampleLightCtrl
*
* @author Zigbee Group
* @date 2021
*
* @par Copyright (c) 2021, Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************************************/
#if (__PROJECT_TL_DIMMABLE_LIGHT__)
/**********************************************************************
* INCLUDES
*/
#include "tl_common.h"
#include "zcl_include.h"
#include "sampleLight.h"
#include "sampleLightCtrl.h"
#include <math.h>
/**********************************************************************
* LOCAL CONSTANTS
*/
#define PWM_FREQUENCY 1000 // 1KHz
#define PWM_FULL_DUTYCYCLE 100
#define PMW_MAX_TICK (PWM_CLOCK_SOURCE / PWM_FREQUENCY)
/**********************************************************************
* TYPEDEFS
*/
/**********************************************************************
* GLOBAL VARIABLES
*/
/**********************************************************************
* FUNCTIONS
*/
extern void sampleLight_updateOnOff(void);
extern void sampleLight_updateLevel(void);
extern void sampleLight_updateColor(void);
extern void sampleLight_onOffInit(void);
extern void sampleLight_levelInit(void);
extern void sampleLight_colorInit(void);
/*********************************************************************
* @fn pwmSetDuty
*
* @brief
*
* @param ch - PWM channel
* dutycycle - level * PWM_FULL_DUTYCYCLE
*
* @return None
*/
void pwmSetDuty(u8 ch, u16 dutycycle)
{
#ifdef ZCL_LEVEL_CTRL
u32 cmp_tick = ((u32)dutycycle * PMW_MAX_TICK) / (ZCL_LEVEL_ATTR_MAX_LEVEL * PWM_FULL_DUTYCYCLE);
drv_pwm_cfg(ch, (u16)cmp_tick, PMW_MAX_TICK);
#endif
}
/*********************************************************************
* @fn pwmInit
*
* @brief
*
* @param ch - PWM channel
* dutycycle - level * PWM_FULL_DUTYCYCLE
*
* @return None
*/
void pwmInit(u8 ch, u16 dutycycle)
{
pwmSetDuty(ch, dutycycle);
}
/*********************************************************************
* @fn hwLight_init
*
* @brief
*
* @param None
*
* @return None
*/
void hwLight_init(void)
{
drv_pwm_init();
R_LIGHT_PWM_SET();
G_LIGHT_PWM_SET();
B_LIGHT_PWM_SET();
COOL_LIGHT_PWM_SET();
WARM_LIGHT_PWM_SET();
pwmInit(R_LIGHT_PWM_CHANNEL, 0);
pwmInit(G_LIGHT_PWM_CHANNEL, 0);
pwmInit(B_LIGHT_PWM_CHANNEL, 0);
pwmInit(COOL_LIGHT_PWM_CHANNEL, 0);
pwmInit(WARM_LIGHT_PWM_CHANNEL, 0);
}
/*********************************************************************
* @fn hwLight_onOffUpdate
*
* @brief
*
* @param onOff - onOff attribute value
*
* @return None
*/
void hwLight_onOffUpdate(u8 onOff)
{
if (onOff)
{
drv_pwm_start(R_LIGHT_PWM_CHANNEL);
drv_pwm_start(G_LIGHT_PWM_CHANNEL);
drv_pwm_start(B_LIGHT_PWM_CHANNEL);
drv_pwm_start(COOL_LIGHT_PWM_CHANNEL);
drv_pwm_start(WARM_LIGHT_PWM_CHANNEL);
}
else
{
drv_pwm_stop(R_LIGHT_PWM_CHANNEL);
drv_pwm_stop(G_LIGHT_PWM_CHANNEL);
drv_pwm_stop(B_LIGHT_PWM_CHANNEL);
drv_pwm_stop(COOL_LIGHT_PWM_CHANNEL);
drv_pwm_stop(WARM_LIGHT_PWM_CHANNEL);
}
}
/*********************************************************************
* @fn hwLight_levelUpdate
*
* @brief
*
* @param level - level attribute value
*
* @return None
*/
void hwLight_levelUpdate(u8 level)
{
/* Use this if no rgb support
#if !defined COLOR_RGB_SUPPORT || (COLOR_RGB_SUPPORT == 0)
level = (level < 0x10) ? 0x10 : level;
u16 gammaCorrectLevel = ((u16)level * level) / ZCL_LEVEL_ATTR_MAX_LEVEL;
pwmSetDuty(COOL_LIGHT_PWM_CHANNEL, gammaCorrectLevel * PWM_FULL_DUTYCYCLE);
#endif*/
}
/*********************************************************************
* @fn temperatureToCW
*
* @brief
*
* @param [in]colorTemperatureMireds - colorTemperatureMireds attribute value
* [in]level - level attribute value
* [out]C - cool light PWM
* [out]W - warm light PWM
*
* @return None
*/
void temperatureToCW(u16 temperatureMireds, u8 level, u8 *C, u8 *W)
{
zcl_lightColorCtrlAttr_t *pColor = zcl_colorAttrGet();
*W = (u8)(((temperatureMireds - pColor->colorTempPhysicalMinMireds) * level) / (pColor->colorTempPhysicalMaxMireds - pColor->colorTempPhysicalMinMireds));
*C = level - (*W);
}
/*********************************************************************
* @fn hwLight_colorUpdate_colorTemperature
*
* @brief
*
* @param colorTemperatureMireds - colorTemperatureMireds attribute value
* level - level attribute value
*
* @return None
*/
void hwLight_colorUpdate_colorTemperature(u16 colorTemperatureMireds, u8 level)
{
u8 C = 0;
u8 W = 0;
level = (level < 0x10) ? 0x10 : level;
temperatureToCW(colorTemperatureMireds, level, &C, &W);
u16 gammaCorrectC = ((u16)C * C) / ZCL_LEVEL_ATTR_MAX_LEVEL;
u16 gammaCorrectW = ((u16)W * W) / ZCL_LEVEL_ATTR_MAX_LEVEL;
pwmSetDuty(COOL_LIGHT_PWM_CHANNEL, gammaCorrectC * PWM_FULL_DUTYCYCLE);
pwmSetDuty(WARM_LIGHT_PWM_CHANNEL, gammaCorrectW * PWM_FULL_DUTYCYCLE);
pwmSetDuty(R_LIGHT_PWM_CHANNEL, 0);
pwmSetDuty(G_LIGHT_PWM_CHANNEL, 0);
pwmSetDuty(B_LIGHT_PWM_CHANNEL, 0);
}
/*********************************************************************
* @fn hsvToRGB
*
* @brief
*
* @param [in]hue - hue attribute value
* [in]saturation - saturation attribute value
* [in]level - level attribute value
* [out]R - R light PWM
* [out]G - G light PWM
* [out]B - B light PWM
*
* @return None
*/
void hsvToRGB(u8 hue, u8 saturation, u8 level, u8 *R, u8 *G, u8 *B)
{
u8 region;
u8 remainder;
u8 p, q, t;
u16 rHue = (u16)hue * 360 / ZCL_COLOR_ATTR_HUE_MAX;
u8 rS = saturation;
u8 rV = level;
if (saturation == 0)
{
*R = rV;
*G = rV;
*B = rV;
return;
}
if (rHue < 360)
{
region = rHue / 60;
}
else
{
region = 0;
}
remainder = (rHue - (region * 60)) * 4;
p = (rV * (255 - rS)) >> 8;
q = (rV * (255 - ((rS * remainder) >> 8))) >> 8;
t = (rV * (255 - ((rS * (255 - remainder)) >> 8))) >> 8;
if (region == 0)
{
*R = rV;
*G = t;
*B = p;
}
else if (region == 1)
{
*R = q;
*G = rV;
*B = p;
}
else if (region == 2)
{
*R = p;
*G = rV;
*B = t;
}
else if (region == 3)
{
*R = p;
*G = q;
*B = rV;
}
else if (region == 4)
{
*R = t;
*G = p;
*B = rV;
}
else
{
*R = rV;
*G = p;
*B = q;
}
}
/*********************************************************************
* @fn hwLight_colorUpdate_HSV2RGB
*
* @brief
*
* @param hue - hue attribute value
* saturation - saturation attribute value
* level - level attribute value
*
* @return None
*/
void hwLight_colorUpdate_HSV2RGB(u8 hue, u8 saturation, u8 level)
{
u8 R = 0;
u8 G = 0;
u8 B = 0;
level = (level < 0x10) ? 0x10 : level;
hsvToRGB(hue, saturation, level, &R, &G, &B);
hwLight_colorUpdate_RGB(R, G, B);
}
void hwLight_colorUpdate_RGB(u8 R, u8 G, u8 B)
{
u16 gammaCorrectR = ((u16)R * R) / ZCL_LEVEL_ATTR_MAX_LEVEL;
u16 gammaCorrectG = ((u16)G * G) / ZCL_LEVEL_ATTR_MAX_LEVEL;
u16 gammaCorrectB = ((u16)B * B) / ZCL_LEVEL_ATTR_MAX_LEVEL;
pwmSetDuty(PWM_R_CHANNEL, gammaCorrectR * PWM_FULL_DUTYCYCLE);
pwmSetDuty(PWM_G_CHANNEL, gammaCorrectG * PWM_FULL_DUTYCYCLE);
pwmSetDuty(PWM_B_CHANNEL, gammaCorrectB * PWM_FULL_DUTYCYCLE);
pwmSetDuty(COOL_LIGHT_PWM_CHANNEL, 0);
pwmSetDuty(WARM_LIGHT_PWM_CHANNEL, 0);
}
static float ENFORCE_BOUNDS_FLOAT(float lowerBound, float num, float upperBound)
{
return num < lowerBound ? lowerBound : num > upperBound ? upperBound
: num;
}
float LINEAR_TO_SRGB_GAMMA_CORRECTION(const float part)
{
return part <= 0.0031308 ? 12.92 * part : 1.055 * pow(part, 1.0 / 2.4) - 0.055;
}
void hwLight_colorUpdate_XY2RGB(u16 xI, u16 yI, u8 level)
{
float x = xI / 65536.f;
float y = yI / 65536.f;
// This does not locate the closest point in the gamma spectrum of the lamps. possible #todo
const float z = 1 - x - y;
const float Y = level / ZCL_LEVEL_ATTR_MAX_LEVEL; // This is luminance, but used as brightness
const float X = ((Y) / y) * x;
const float Z = ((Y) / y) * z;
// D65 BT.709 conversion https://en.wikipedia.org/wiki/SRGB
float r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
float g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
float b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
// Enforce the lower and upper bounds
r = ENFORCE_BOUNDS_FLOAT(0.0, r * 255, 255.0);
g = ENFORCE_BOUNDS_FLOAT(0.0, g * 255, 255.0);
b = ENFORCE_BOUNDS_FLOAT(0.0, b * 255, 255.0);
hwLight_colorUpdate_RGB((u8)r, (u8)g, (u8)b);
}
/*********************************************************************
* @fn light_adjust
*
* @brief
*
* @param None
*
* @return None
*/
void light_adjust(void)
{
#ifdef ZCL_LIGHT_COLOR_CONTROL
sampleLight_colorInit();
#else
#ifdef ZCL_LEVEL_CTRL
sampleLight_levelInit();
#endif
#endif
sampleLight_onOffInit();
}
/*********************************************************************
* @fn light_fresh
*
* @brief
*
* @param None
*
* @return None
*/
void light_fresh(void)
{
#ifdef ZCL_LIGHT_COLOR_CONTROL
sampleLight_updateColor();
#else
#ifdef ZCL_LEVEL_CTRL
sampleLight_updateLevel();
#else
pwmSetDuty(COOL_LIGHT_PWM_CHANNEL, ZCL_LEVEL_ATTR_MAX_LEVEL * PWM_FULL_DUTYCYCLE);
#endif
#endif
sampleLight_updateOnOff();
gLightCtx.lightAttrsChanged = TRUE;
}
/*********************************************************************
* @fn light_applyUpdate
*
* @brief
*
* @param
*
* @return None
*/
void light_applyUpdate(u8 *curLevel, u16 *curLevel256, s32 *stepLevel256, u16 *remainingTime, u8 minLevel, u8 maxLevel, bool wrap)
{
if ((*stepLevel256 > 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) > maxLevel))
{
*curLevel256 = (wrap) ? ((u16)minLevel * 256 + ((*curLevel256 + *stepLevel256) - (u16)maxLevel * 256) - 256)
: ((u16)maxLevel * 256);
}
else if ((*stepLevel256 < 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) < minLevel))
{
*curLevel256 = (wrap) ? ((u16)maxLevel * 256 - ((u16)minLevel * 256 - ((s32)*curLevel256 + *stepLevel256)) + 256)
: ((u16)minLevel * 256);
}
else
{
*curLevel256 += *stepLevel256;
}
if (*stepLevel256 > 0)
{
*curLevel = (*curLevel256 + 127) / 256;
}
else
{
*curLevel = *curLevel256 / 256;
}
if (*remainingTime == 0)
{
*curLevel256 = ((u16)*curLevel) * 256;
*stepLevel256 = 0;
}
else if (*remainingTime != 0xFFFF)
{
*remainingTime = *remainingTime - 1;
}
light_fresh();
}
/*********************************************************************
* @fn light_applyUpdate_16
*
* @brief
*
* @param
*
* @return None
*/
void light_applyUpdate_16(u16 *curLevel, u32 *curLevel256, s32 *stepLevel256, u16 *remainingTime, u16 minLevel, u16 maxLevel, bool wrap)
{
if ((*stepLevel256 > 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) > maxLevel))
{
*curLevel256 = (wrap) ? ((u32)minLevel * 256 + ((*curLevel256 + *stepLevel256) - (u32)maxLevel * 256) - 256)
: ((u32)maxLevel * 256);
}
else if ((*stepLevel256 < 0) && ((((s32)*curLevel256 + *stepLevel256) / 256) < minLevel))
{
*curLevel256 = (wrap) ? ((u32)maxLevel * 256 - ((u32)minLevel * 256 - ((s32)*curLevel256 + *stepLevel256)) + 256)
: ((u32)minLevel * 256);
}
else
{
*curLevel256 += *stepLevel256;
}
if (*stepLevel256 > 0)
{
*curLevel = (*curLevel256 + 127) / 256;
}
else
{
*curLevel = *curLevel256 / 256;
}
if (*remainingTime == 0)
{
*curLevel256 = ((u32)*curLevel) * 256;
*stepLevel256 = 0;
}
else if (*remainingTime != 0xFFFF)
{
*remainingTime = *remainingTime - 1;
}
light_fresh();
}
/*********************************************************************
* @fn light_blink_TimerEvtCb
*
* @brief
*
* @param arg
*
* @return 0: timer continue on; -1: timer will be canceled
*/
s32 light_blink_TimerEvtCb(void *arg)
{
u32 interval = 0;
if (gLightCtx.sta == gLightCtx.oriSta)
{
if (gLightCtx.times)
{
gLightCtx.times--;
if (gLightCtx.times <= 0)
{
if (gLightCtx.oriSta)
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
}
else
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
}
gLightCtx.timerLedEvt = NULL;
return -1;
}
}
}
gLightCtx.sta = !gLightCtx.sta;
if (gLightCtx.sta)
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
interval = gLightCtx.ledOnTime;
}
else
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
interval = gLightCtx.ledOffTime;
}
return interval;
}
/*********************************************************************
* @fn light_blink_start
*
* @brief
*
* @param times - counts
* @param ledOnTime - on times, ms
* @param ledOffTime - off times, ms
*
* @return None
*/
void light_blink_start(u8 times, u16 ledOnTime, u16 ledOffTime)
{
u32 interval = 0;
zcl_onOffAttr_t *pOnoff = zcl_onoffAttrGet();
gLightCtx.oriSta = pOnoff->onOff;
gLightCtx.times = times;
if (!gLightCtx.timerLedEvt)
{
if (gLightCtx.oriSta)
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
gLightCtx.sta = 0;
interval = ledOffTime;
}
else
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
gLightCtx.sta = 1;
interval = ledOnTime;
}
gLightCtx.ledOnTime = ledOnTime;
gLightCtx.ledOffTime = ledOffTime;
gLightCtx.timerLedEvt = TL_ZB_TIMER_SCHEDULE(light_blink_TimerEvtCb, NULL, interval);
}
}
/*********************************************************************
* @fn light_blink_stop
*
* @brief
*
* @param None
*
* @return None
*/
void light_blink_stop(void)
{
if (gLightCtx.timerLedEvt)
{
TL_ZB_TIMER_CANCEL(&gLightCtx.timerLedEvt);
gLightCtx.times = 0;
if (gLightCtx.oriSta)
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_ON);
}
else
{
hwLight_onOffUpdate(ZCL_CMD_ONOFF_OFF);
}
}
}
#endif /* __PROJECT_TL_DIMMABLE_LIGHT__ */