PWM 实现调光
一、项目效果
预期(没有相关经验)
达到的目的(是实际能做到的)
通过 PWM 驱动扩展板上的冷光 LED 和暖光 LED,
并通过红外遥控器调节占空比来实现调节色温和调节亮度的功能。
这里也可以使用普通的,调节普通的led灯珠的亮度
- 使用 CH+/-键设置灯光的色温
- 使用+/-键设置灯光亮度
- 使用 EQ 键控制灯光开启或者关闭
(是基于成功做出,遥控器的实验)
- 通过串口打印此时的亮度和色温百分比
二、原理说明
调光原理
学习物联网最经典的案例就是实现一款智能灯,
用户可以对其进行色温、亮度、颜色(下一章讲解)的控制,
色温就是指灯光的颜色是偏白(冷光源) 还是偏黄(暖光源) ,
单位是K(开尔文),色温值越高光源颜色就越白反之色温越低,光源颜色就越黄。
通常使用冷光灯和暖光灯配合的方式,通过调节各自的亮度来实现色温的任意调节。
调节灯光亮度的原理就是通过 PWM 的占空比来设置灯光亮暗,
调节灯光色温则是在当前亮度值(占空比)基础上再次进行比例的分配
这便是调光原理
硬件连接
开发板上使用的冷光灯色温为 6000K,暖光灯为 3000K,
上图这种情况下对应的色温值为 3900K,另外为了保证色温恒定在固定值,通常亮度不能设置为 0,
而是有一个保底的最小值,比如 10%。
还需要注意的是,为了大家方便理解今后不使用 K 作为色温单位,
而是像亮度那样使用百分比值,范围 0-100,值越大,灯光越白
开发板上的两颗高亮 LED 分别是暖光 LED 和冷光 LED
其中使用这两个引脚(PA15和PB5)的注意点
PA15上电后,该引脚默认是 JTAG 的 JTDI, 因此需要将其引脚重映射为 TIM2 的通道 1,
PB3 上电默认功能是 JTAG 的 JTDO,所以也需要 PB3 重映射为 TIM2 的通道 1,
这两个引脚的功能映射图
配置定时器 2 的通道 1/2 作为 PWM 输出的步骤
- 重映射 TIM2 的 1/2 通道到 PA15 和 PB3 引脚,并初始化为复用输出
- 初始化定时器,主要是设置定时器周期和重装载寄存器
- 分别初始化定时器 2 的输出比较通道 1/2
总结
难点就在于将亮度和色温值最终转换为占空比的计算公式上。
程序说明
灯光初始化函数
/**
* 功能:初始灯光
* 参数:
* brightness:初始化亮度 10-100
* colortemp:初始化色温 0-100
* 返回值:None
*/
void initLight(u8 brightness,u8 colortemp)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //禁止JTAG保留SWD
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //设置JTAG为定时器2部分映射,只使用SWD模式
/*设置冷光灯*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*设置暖光灯*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
setLight(brightness,colortemp);
}
结合代码来理解这句话
灯光初始化函数实现了复用功能重映射,
并初始化冷光灯和暖光灯的控制引脚为复用输出。
设置灯光函数
/**
* 功能:设置灯光亮度和色温
* 参数:
* brightness:亮度 10-100
* colortemp:色温 0-100
* 返回值:None
*/
void setLight(u8 brightness,u8 colortemp)
{
/**
* 无论是亮度还是色温,最终都体现在LED的亮度上
* LED最终的亮度计算公式为:满占空比(重装载值) * 亮度百分比 * 色温百分比
* 并且要保证冷光和暖光的色温比值之和为100%
* */
TIM_SetCompare1(TIM2,getPeriod(TIM2)*brightness/100*colortemp/100); //设置冷光 对应PA15 TIM2_CH1
TIM_SetCompare2(TIM2,getPeriod(TIM2)*brightness/100*(100-colortemp)/100); //设置暖光 对应PB3 TIM2_CH2
}
为了方便理解, 没有把占空比计算公式进行合并。
我们通过 TIM_setCompare1()和 TIM_setCompare2()函数
对定时器 2 的捕获/比较寄存器 1/2 赋值来改变 PWM 占空比,
传入函数的第二个参数就是亮度-色温的计算公式,
在公式中先获取了重装载寄存器中的值(满占空比对应的计数值),
然后乘上亮度的百分比得到我们想要的亮度,最后在此基础上又将亮度值乘上色温百分比,
这里要注意,冷/暖光的色温比值之和是 100%,
这就保证了无论亮度值是多少,最终呈现出来的色温是不受影响的。
PWM 初始化函数
初始化定时器2输出比较通道1来生成PWM 对应冷光
/**
* 功能:初始化定时器2输出比较通道1来生成PWM 对应冷光
* 参数:
* duty:设置PWM输出的占空比
* 返回值:None
*/
void initTIM2OC1(u16 duty)
{
TIM_OCInitTypeDef TIM_OCInitStructure; //定义输出比较初始化结构体
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //设置PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = duty; //设置捕获比较寄存器的值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置有效电平为高电平
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //生效初始化设置
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能输出比较预装载,即更改完输入捕获寄存器的值要等到更新事件发生才生效,该行加不加无所谓
}
初始化定时器2输出比较通道2来生成PWM 对应暖光
/**
* 功能:初始化定时器2输出比较通道2来生成PWM 对应暖光
* 参数:
* duty:设置PWM输出的占空比
* 返回值:None
*/
void initTIM2OC2(u16 duty)
{
TIM_OCInitTypeDef TIM_OCInitStructure; //定义输出比较初始化结构体
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //设置PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = duty; //设置捕获比较寄存器的值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置有效电平为高电平
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //生效初始化设置
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能输出比较预装载,即更改完输入捕获寄存器的值要等到更新事件发生才生效,该行加不加无所谓
}
主函数
int main(void)
{
u8 light_power = 1; //默认开机
s8 brightness = 100; //亮度
s8 colortemp = 50; //色温
/*初始化各外设*/
initSysTick();
initLED();
initUART();
initNVIC(NVIC_PriorityGroup_2);
/*初始化NEC*/
initNEC();
/*定时器4时钟周期10us,中断周期200ms,使能更新中断和捕获通道3中断*/
initTIMx(TIM4,719,19999,TIM_IT_Update|TIM_IT_CC3,ENABLE);
/*设置下降沿触发捕获*/
initTIM4IC3(TIM_ICPolarity_Falling);
/*设置定时器2时钟为10us,1KHz*/
initTIMx(TIM2,719,99,TIM_IT_Update,DISABLE);
initTIM2OC1(50);
initTIM2OC2(50);
/*亮度10%,色温50%*/
initLight(100,50);
while(1)
{
if(NEC_DataStructure.CmdCode_H == 0x45) //CH-键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
colortemp -= 10;
if(colortemp<0)
{
colortemp = 0;
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x47) //CH+键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
colortemp += 10;
if(colortemp>100)
{
colortemp = 100;
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x07) //-键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
brightness -= 10;
if(brightness<10)
{
brightness = 10; //保底值
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x15) //+键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
brightness += 10;
if(brightness>100)
{
brightness = 100;
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x09) //EQ键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
light_power = !light_power; //开关机状态取反
if(light_power == 0)
{
setLight(0,0); //关灯
}else
{
setLight(brightness,colortemp); //开灯
}
}else
{
}
printf("brightness is :%d%%n",brightness);
printf("color temperature is :%d%%n",colortemp);
toggleLED();
Delay_ms(100);
}
}
代码很长,主要实现的是
定时器 2 设置的时钟周期为 10us,则 1KHz(1ms)需要计数 100 个。
然后初始化 PWM 并设置灯光开机默认最大亮度,色温 50%。
在用户逻辑循环中不断的判断此时红外键值,并根据键值做出相应的灯光控制动作。
最终效果
PWM 实现调光
一、项目效果
预期(没有相关经验)
达到的目的(是实际能做到的)
通过 PWM 驱动扩展板上的冷光 LED 和暖光 LED,
并通过红外遥控器调节占空比来实现调节色温和调节亮度的功能。
这里也可以使用普通的,调节普通的led灯珠的亮度
- 使用 CH+/-键设置灯光的色温
- 使用+/-键设置灯光亮度
- 使用 EQ 键控制灯光开启或者关闭
(是基于成功做出,遥控器的实验)
- 通过串口打印此时的亮度和色温百分比
二、原理说明
调光原理
学习物联网最经典的案例就是实现一款智能灯,
用户可以对其进行色温、亮度、颜色(下一章讲解)的控制,
色温就是指灯光的颜色是偏白(冷光源) 还是偏黄(暖光源) ,
单位是K(开尔文),色温值越高光源颜色就越白反之色温越低,光源颜色就越黄。
通常使用冷光灯和暖光灯配合的方式,通过调节各自的亮度来实现色温的任意调节。
调节灯光亮度的原理就是通过 PWM 的占空比来设置灯光亮暗,
调节灯光色温则是在当前亮度值(占空比)基础上再次进行比例的分配
这便是调光原理
硬件连接
开发板上使用的冷光灯色温为 6000K,暖光灯为 3000K,
上图这种情况下对应的色温值为 3900K,另外为了保证色温恒定在固定值,通常亮度不能设置为 0,
而是有一个保底的最小值,比如 10%。
还需要注意的是,为了大家方便理解今后不使用 K 作为色温单位,
而是像亮度那样使用百分比值,范围 0-100,值越大,灯光越白
开发板上的两颗高亮 LED 分别是暖光 LED 和冷光 LED
其中使用这两个引脚(PA15和PB5)的注意点
PA15上电后,该引脚默认是 JTAG 的 JTDI, 因此需要将其引脚重映射为 TIM2 的通道 1,
PB3 上电默认功能是 JTAG 的 JTDO,所以也需要 PB3 重映射为 TIM2 的通道 1,
这两个引脚的功能映射图
配置定时器 2 的通道 1/2 作为 PWM 输出的步骤
- 重映射 TIM2 的 1/2 通道到 PA15 和 PB3 引脚,并初始化为复用输出
- 初始化定时器,主要是设置定时器周期和重装载寄存器
- 分别初始化定时器 2 的输出比较通道 1/2
总结
难点就在于将亮度和色温值最终转换为占空比的计算公式上。
程序说明
灯光初始化函数
/**
* 功能:初始灯光
* 参数:
* brightness:初始化亮度 10-100
* colortemp:初始化色温 0-100
* 返回值:None
*/
void initLight(u8 brightness,u8 colortemp)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //禁止JTAG保留SWD
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //设置JTAG为定时器2部分映射,只使用SWD模式
/*设置冷光灯*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*设置暖光灯*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
setLight(brightness,colortemp);
}
结合代码来理解这句话
灯光初始化函数实现了复用功能重映射,
并初始化冷光灯和暖光灯的控制引脚为复用输出。
设置灯光函数
/**
* 功能:设置灯光亮度和色温
* 参数:
* brightness:亮度 10-100
* colortemp:色温 0-100
* 返回值:None
*/
void setLight(u8 brightness,u8 colortemp)
{
/**
* 无论是亮度还是色温,最终都体现在LED的亮度上
* LED最终的亮度计算公式为:满占空比(重装载值) * 亮度百分比 * 色温百分比
* 并且要保证冷光和暖光的色温比值之和为100%
* */
TIM_SetCompare1(TIM2,getPeriod(TIM2)*brightness/100*colortemp/100); //设置冷光 对应PA15 TIM2_CH1
TIM_SetCompare2(TIM2,getPeriod(TIM2)*brightness/100*(100-colortemp)/100); //设置暖光 对应PB3 TIM2_CH2
}
为了方便理解, 没有把占空比计算公式进行合并。
我们通过 TIM_setCompare1()和 TIM_setCompare2()函数
对定时器 2 的捕获/比较寄存器 1/2 赋值来改变 PWM 占空比,
传入函数的第二个参数就是亮度-色温的计算公式,
在公式中先获取了重装载寄存器中的值(满占空比对应的计数值),
然后乘上亮度的百分比得到我们想要的亮度,最后在此基础上又将亮度值乘上色温百分比,
这里要注意,冷/暖光的色温比值之和是 100%,
这就保证了无论亮度值是多少,最终呈现出来的色温是不受影响的。
PWM 初始化函数
初始化定时器2输出比较通道1来生成PWM 对应冷光
/**
* 功能:初始化定时器2输出比较通道1来生成PWM 对应冷光
* 参数:
* duty:设置PWM输出的占空比
* 返回值:None
*/
void initTIM2OC1(u16 duty)
{
TIM_OCInitTypeDef TIM_OCInitStructure; //定义输出比较初始化结构体
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //设置PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = duty; //设置捕获比较寄存器的值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置有效电平为高电平
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //生效初始化设置
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能输出比较预装载,即更改完输入捕获寄存器的值要等到更新事件发生才生效,该行加不加无所谓
}
初始化定时器2输出比较通道2来生成PWM 对应暖光
/**
* 功能:初始化定时器2输出比较通道2来生成PWM 对应暖光
* 参数:
* duty:设置PWM输出的占空比
* 返回值:None
*/
void initTIM2OC2(u16 duty)
{
TIM_OCInitTypeDef TIM_OCInitStructure; //定义输出比较初始化结构体
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //设置PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = duty; //设置捕获比较寄存器的值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置有效电平为高电平
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //生效初始化设置
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能输出比较预装载,即更改完输入捕获寄存器的值要等到更新事件发生才生效,该行加不加无所谓
}
主函数
int main(void)
{
u8 light_power = 1; //默认开机
s8 brightness = 100; //亮度
s8 colortemp = 50; //色温
/*初始化各外设*/
initSysTick();
initLED();
initUART();
initNVIC(NVIC_PriorityGroup_2);
/*初始化NEC*/
initNEC();
/*定时器4时钟周期10us,中断周期200ms,使能更新中断和捕获通道3中断*/
initTIMx(TIM4,719,19999,TIM_IT_Update|TIM_IT_CC3,ENABLE);
/*设置下降沿触发捕获*/
initTIM4IC3(TIM_ICPolarity_Falling);
/*设置定时器2时钟为10us,1KHz*/
initTIMx(TIM2,719,99,TIM_IT_Update,DISABLE);
initTIM2OC1(50);
initTIM2OC2(50);
/*亮度10%,色温50%*/
initLight(100,50);
while(1)
{
if(NEC_DataStructure.CmdCode_H == 0x45) //CH-键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
colortemp -= 10;
if(colortemp<0)
{
colortemp = 0;
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x47) //CH+键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
colortemp += 10;
if(colortemp>100)
{
colortemp = 100;
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x07) //-键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
brightness -= 10;
if(brightness<10)
{
brightness = 10; //保底值
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x15) //+键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
brightness += 10;
if(brightness>100)
{
brightness = 100;
}
setLight(brightness,colortemp);
}else if(NEC_DataStructure.CmdCode_H == 0x09) //EQ键按下
{
NEC_DataStructure.CmdCode_H = 0; //清除键值
light_power = !light_power; //开关机状态取反
if(light_power == 0)
{
setLight(0,0); //关灯
}else
{
setLight(brightness,colortemp); //开灯
}
}else
{
}
printf("brightness is :%d%%n",brightness);
printf("color temperature is :%d%%n",colortemp);
toggleLED();
Delay_ms(100);
}
}
代码很长,主要实现的是
定时器 2 设置的时钟周期为 10us,则 1KHz(1ms)需要计数 100 个。
然后初始化 PWM 并设置灯光开机默认最大亮度,色温 50%。
在用户逻辑循环中不断的判断此时红外键值,并根据键值做出相应的灯光控制动作。
最终效果
举报