40. 全彩LED灯实验¶
本章讲解的实验工程目录为:固件库例程\ TIM—全彩LED灯。
学习本章需要TIM定时器章节为基础。
40.1. 全彩LED灯简介¶
全彩LED灯,实质上是一种把红、绿、蓝单色发光体集成到小面积区域中的LED灯,控制时对这三种颜色的灯管输出不同的光照强度, 即可混合得到不同的颜色,其混色原理与光的三原色混合原理一致。
例如,若红绿蓝灯都能控制输出光照强度为[0:255]种等级,那么该灯可混合得到使用RGB888表示的所有颜色(包括以RGB三个灯管都全灭所表示的纯黑色)。
40.2. 全彩LED灯控制原理¶
本教程配套开发板中的RGB灯就是一种全彩LED灯,前面介绍LED基本控制原理的时候,只能控制RGB三色灯的亮灭,即RGB每盏灯有[0:1]两种等级,因此只能组合出8种颜色。
要使用STM32控制LED灯输出多种亮度等级,可以通过控制输出脉冲的占空比来实现,见图 不同占空比的PWM。
示例图中列出了周期相同而占空比分别为100%、80%、50和20%的脉冲波形,假如利用这样的脉冲控制LED灯,即可控制LED灯亮灭时间长度的比例。 若提高脉冲的频率,LED灯将会高频率进行开关切换,由于视觉暂留效应,人眼看不到LED灯的开关导致的闪烁现象, 而是感觉到使用不同占空比的脉冲控制LED灯时的亮度差别。即单个控制周期内,LED灯亮的平均时间越长,亮度就越高,反之越暗。
把脉冲信号占空比分成256个等级,即可用于控制LED灯输出256种亮度,使用三种这样的信号控制RGB灯即可得到256*256*256种颜色混合的效果。 而要控制占空比,直接使用STM32定时器的PWM功能即可。
40.3. 硬件设计¶
本小节讲解的实验工程目录为:固件库例程\ TIM—全彩LED灯。
本实验中使用的RGB灯硬件与前面LED灯章节中的完全相同,见图 LED硬件原理图 ,这是一个RGB灯, 里面由红蓝绿三个小灯构成, 使用PWM控制时可以混合成256不同的颜色。本实验与LED灯基础章节的主要差异是软件中的控制方式。
本开发板中设计的RGB灯控制引脚是经过仔细选择的,因为本实验的软件将使用STM32的定时器控制PWM脉冲的占空比, 然而并不是任意GPIO都具有STM32定时器的输出通道功能,所以在设计硬件时,需要根据《STM32数据手册》中的说明, 选择具有定时器输出通道功能的引脚来控制RGB灯,见图 LED引脚说明。
本实验中的RGB灯使用阴极分别连接到了PF6、PF7及PF8,它们分别是定时器TIM10、11、13的通道1。在本硬件设计方案中三个引脚使用了不同的定时器, 它会给设计程序时会带来不便,若是在硬件资源分配充足的情况下,建议这三个控制引脚使用同一个定时器的不同通道。
40.4. 软件设计¶
本工程中关于RGB灯控制的代码都存储在“bsp_color_led.c”及“bsp_ color_led.h”文件,这些文件也可根据您的喜好命名, 它们不属于STM32标准库的内容,是由我们自己根据应用需要编写的。
40.4.1. 编程要点¶
1) 初始化RGB灯使用的GPIO;
2) 配置定时器输出PWM脉冲;
3) 编写修改PWM脉冲占空比大小的函数;
4) 测试配置的定时器脉冲控制周期是否会导致LED灯明显闪烁;
40.4.2. 代码分析¶
40.4.2.1. LED灯硬件相关宏定义¶
为方便迁移代码适应其它硬件设计,实验中把硬件相关的部分使用宏定义到bsp_color_led.h文件中, 使用不同硬件设计时,修改该文件即可,见代码清单:全彩LED-1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | /*
!!!!!!!!!!!!!!!!!!
本工程的LED灯各通道使用的不是同一个定时器
设计硬件时建议使用同一个定时器来控制三盏灯,简化代码
若使用的硬件是同一个定时器,需要调整源代码和头文件,不能直接修改宏来实现
特别是定时器初始化部分。
!!!!!!!!!!!!!!!!!!
*/
/********************定时器通道**************************/
#define COLOR_TIM_GPIO_CLK (RCC_AHB1Periph_GPIOF)
/************红灯***************/
#define COLOR_RED_TIM TIM10
#define COLOR_RED_TIM_CLK RCC_APB2Periph_TIM10
#define COLOR_RED_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
/*计算说明见c文件*/
/*部分通用定时器的时钟为HCLK/4,部分为HCLK/2,注意要把三个通道的定时器频率配置为一致*/
#define COLOR_RED_TIM_PRESCALER (((SystemCoreClock)/1000000)*30-1)
/************绿灯***************/
#define COLOR_GREEN_TIM TIM11
#define COLOR_GREEN_TIM_CLK RCC_APB2Periph_TIM11
#define COLOR_GREEN_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
/*部分通用定时器的时钟为HCLK/4,部分为HCLK/2,注意要把三个通道的定时器频率配置为一致*/
#define COLOR_GREEN_TIM_PRESCALER (((SystemCoreClock)/1000000)*30-1)
/************蓝灯***************/
#define COLOR_BLUE_TIM TIM13
#define COLOR_BLUE_TIM_CLK RCC_APB1Periph_TIM13
#define COLOR_BLUE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
/*部分通用定时器的时钟为HCLK/4,部分为HCLK/2,注意要把三个通道的定时器频率配置为一致*/
#define COLOR_BLUE_TIM_PRESCALER
SystemCoreClock/2)/1000000)*30-1)
/************红灯***************/
#define COLOR_RED_PIN GPIO_Pin_6
#define COLOR_RED_GPIO_PORT GPIOF
#define COLOR_RED_PINSOURCE GPIO_PinSource6
#define COLOR_RED_AF GPIO_AF_TIM10
#define COLOR_RED_TIM_OCxInit TIM_OC1Init //通道初始化函数
#define COLOR_RED_TIM_OCxPreloadConfig TIM_OC1PreloadConfig //通道重载配置函数
//通道比较寄存器,以TIMx->CCRx方式可访问该寄存器,设置新的比较值,控制占空比
//以宏封装后,使用这种形式:COLOR_TIMx->COLOR_RED_CCRx ,可访问该通道的比较寄存器
#define COLOR_RED_CCRx CCR1
/************绿灯***************/
#define COLOR_GREEN_PIN GPIO_Pin_7
#define COLOR_GREEN_GPIO_PORT GPIOF
#define COLOR_GREEN_PINSOURCE GPIO_PinSource7
#define COLOR_GREEN_AF GPIO_AF_TIM11
#define COLOR_GREEN_TIM_OCxInit TIM_OC1Init //通道初始化函数
#define COLOR_GREEN_TIM_OCxPreloadConfig TIM_OC1PreloadConfig //通道重载配置函数
//通道比较寄存器,以TIMx->CCRx方式可访问该寄存器,设置新的比较值,控制占空比
//以宏封装后,使用这种形式:COLOR_TIMx->COLOR_GREEN_CCRx ,可访问该通道的比较寄存器
#define COLOR_GREEN_CCRx CCR1
/************蓝灯***************/
#define COLOR_BLUE_PIN GPIO_Pin_8
#define COLOR_BLUE_GPIO_PORT GPIOF
#define COLOR_BLUE_PINSOURCE GPIO_PinSource8
#define COLOR_BLUE_AF GPIO_AF_TIM13
#define COLOR_BLUE_TIM_OCxInit TIM_OC1Init //通道初始化函数
#define COLOR_BLUE_TIM_OCxPreloadConfig TIM_OC1PreloadConfig //通道重载配置函数
//通道比较寄存器,以TIMx->CCRx方式可访问该寄存器,设置新的比较值,控制占空比
//以宏封装后,使用这种形式:COLOR_TIMx->COLOR_BLUE_CCRx ,可访问该通道的比较寄存器
#define COLOR_BLUE_CCRx CCR1
|
这些宏定义非常丰富,包括使用的定时器编号COLOR_xxx_TIMx和定时器时钟使能库函数COLOR_xxx_TIM_APBxClock_FUN。
控制各个LED灯时,每个颜色占用一个通道,这些与通道相关的也使用宏封装起来了,如:端口号COLOR_xxx_TIM_LED_PORT、 引脚号COLOR_xxx_TIM_LED_PIN、通道初始化库函数COLOR_xxx_TIM_OCxInit、 通道重载配置库函数COLOR_xxx_TIM_OCxPreloadConfig以及通道对应的比较寄存器名COLOR_xxx_CCRx。其中xxx宏中的xxx指RED、GREEN和BLUE三种颜色。
由于部分操作使用宏封装后不够直观,此处着重强调一下与通道相关的寄存器宏。为方便修改定时器某通道输出脉冲的占空比, 初始化定时器后,可以直接使用形如“TIM10->CCR1=0xFFFFFF”的代码修改定时器TIM10通道1的比较寄存器中的数值,使用本工程中的宏封装后, 形式改为“COLOR_RED_TIMx ->COLOR_RED_CCRx=0xFFFFFF”。
40.4.2.2. 初始化GPIO¶
首先,初始化用于定时器输出通道的GPIO,见 代码清单:全彩LED-2。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /**
* @brief 配置TIM复用输出PWM时用到的I/O
* @param 无
* @retval 无
*/
static void TIMx_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED相关的GPIO外设时钟*/
RCC_AHB1PeriphClockCmd ( COLOR_TIM_GPIO_CLK, ENABLE);
GPIO_PinAFConfig(COLOR_RED_GPIO_PORT,COLOR_RED_PINSOURCE,COLOR_RED_AF);
GPIO_PinAFConfig(COLOR_GREEN_GPIO_PORT,COLOR_GREEN_PINSOURCE,COLOR_GREEN_AF);
GPIO_PinAFConfig(COLOR_BLUE_GPIO_PORT,COLOR_BLUE_PINSOURCE,COLOR_BLUE_AF);
/*COLOR_LED1*/
GPIO_InitStructure.GPIO_Pin = COLOR_RED_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(COLOR_RED_GPIO_PORT, &GPIO_InitStructure);
/*COLOR_LED2*/
GPIO_InitStructure.GPIO_Pin = COLOR_GREEN_PIN;
GPIO_Init(COLOR_GREEN_GPIO_PORT, &GPIO_InitStructure);
/*COLOR_LED3*/
GPIO_InitStructure.GPIO_Pin = COLOR_BLUE_PIN;
GPIO_Init(COLOR_BLUE_GPIO_PORT, &GPIO_InitStructure);
}
|
与LED灯的基本控制不同,由于本实验直接使用定时器输出通道的脉冲信号控制LED灯, 此处代码把GPIO相关的引脚都配置成了复用推挽输出模式,后面将使用定时器控制引脚进行PWM输出。
40.4.2.3. 定时器PWM配置¶
接着配置定时器输出PWM的工作模式,见 代码清单:全彩LED-3。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | /**
* @brief 配置COLOR_TIMx输出的PWM信号的模式,如周期、极性
* @param 无
* @retval 无
*/
static void TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
COLOR_RED_TIM_APBxClock_FUN(COLOR_RED_TIM_CLK,ENABLE);
COLOR_GREEN_TIM_APBxClock_FUN(COLOR_GREEN_TIM_CLK,ENABLE);
COLOR_BLUE_TIM_APBxClock_FUN(COLOR_BLUE_TIM_CLK,ENABLE);
/*注意要把三个通道的定时器频率配置为一致*/
/* 累计 TIM_Period个后产生一个更新或者中断*/
//当定时器从0计数到255,即为256次,为一个定时周期
TIM_TimeBaseStructure.TIM_Period = 256-1;
// APB1定时器时钟源TIMxCLK = 2 * PCLK1
// PCLK1 = HCLK / 4
// => TIMxCLK = HCLK / 2 = SystemCoreClock /2
// 定时器频率为 = TIMxCLK/(TIM_Prescaler+1) = (SystemCoreClock /2)/((SystemCoreClock/2)/1000000)*30 = 1000000/30 = 1/30MHz
//APB2定时器时钟源TIMxCLK = 2 * PCLK2
// PCLK2 = HCLK / 2
// => TIMxCLK = HCLK = SystemCoreClock
// 定时器频率为 = TIMxCLK/(TIM_Prescaler+1) = (SystemCoreClock )/((SystemCoreClock)/1000000)*30 = 1000000/30 = 1/30MHz
/*基本定时器配置TIM_Prescaler根据效果来设置,中断周期小 灯闪烁快,大则闪烁缓慢*/
TIM_TimeBaseStructure.TIM_Prescaler = COLOR_RED_TIM_PRESCALER;
//设置时钟分频系数:不分频(这里用不到)
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
// 初始化定时器TIMx
TIM_TimeBaseInit(COLOR_RED_TIM, &TIM_TimeBaseStructure);
/*基本定时器配置TIM_Prescaler根据效果来设置即可,中断周期小
灯闪烁快,大则闪烁缓慢*/
TIM_TimeBaseStructure.TIM_Prescaler = COLOR_GREEN_TIM_PRESCALER;
// 初始化定时器TIMx
TIM_TimeBaseInit(COLOR_GREEN_TIM, &TIM_TimeBaseStructure);
/*基本定时器配置TIM_Prescaler根据效果来设置即可,中断周期小
灯闪烁快,大则闪烁缓慢*/
TIM_TimeBaseStructure.TIM_Prescaler = COLOR_BLUE_TIM_PRESCALER;
// 初始化定时器TIMx
TIM_TimeBaseInit(COLOR_BLUE_TIM, &TIM_TimeBaseStructure);
/*PWM模式配置*/
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能输出
TIM_OCInitStructure.TIM_Pulse = 0; //设置初始PWM脉冲宽度为0
//当定时器计数值小于CCR_Val时为低电平LED灯亮
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
//使能通道
COLOR_RED_TIM_OCxInit(COLOR_RED_TIM, &TIM_OCInitStructure);
/*使能通道重载*/
COLOR_RED_TIM_OCxPreloadConfig(COLOR_RED_TIM, TIM_OCPreload_Enable);
//使能通道
COLOR_GREEN_TIM_OCxInit(COLOR_GREEN_TIM, &TIM_OCInitStructure);
/*使能通道重载*/
COLOR_GREEN_TIM_OCxPreloadConfig(COLOR_GREEN_TIM, TIM_OCPreload_Enable);
//使能通道
COLOR_BLUE_TIM_OCxInit(COLOR_BLUE_TIM, &TIM_OCInitStructure);
/*使能通道重载*/
COLOR_BLUE_TIM_OCxPreloadConfig(COLOR_BLUE_TIM, TIM_OCPreload_Enable);
//使能TIM重载寄存器ARR
TIM_ARRPreloadConfig(COLOR_RED_TIM, ENABLE);
TIM_ARRPreloadConfig(COLOR_GREEN_TIM, ENABLE);
TIM_ARRPreloadConfig(COLOR_BLUE_TIM, ENABLE);
// 使能计数器
TIM_Cmd(COLOR_RED_TIM, ENABLE);
TIM_Cmd(COLOR_GREEN_TIM, ENABLE);
TIM_Cmd(COLOR_BLUE_TIM, ENABLE);
}
|
定时器配置的主体跟前面《PWM互补输出实验》小节介绍的实验类似,关键代码已用红色字体加粗显示。
本配置初始化了控制RGB灯用的定时器,它被配置为向上计数,TIM_Period被配置为255,即定时器每个时钟周期计数器加1,计数至255时溢出, 从0开始重新计数;而代码中的PWM通道配置,当计数器的值小于输出比较寄存器CCRx的值时,PWM通道输出低电平,点亮LED灯。 所以上述代码配置把输出脉冲的单个周期分成了256份(注意区分定时器的时钟周期和输出脉冲周期), 而输出比较寄存器CCRx配置的值即该脉冲周期内LED灯点亮的时间份数,所以修改CCRx的值,即可控制输出[0:255]种亮度等级。
关于定时器中的TIM_Prescaler分频配置,只要让它设置使得PWM控制脉冲的频率足够高,让人看不出LED灯闪烁即可,您可以亲自修改使用其它参数测试。
40.4.2.4. 颜色混合¶
初始化完定时器和PWM输出通道后,再编写用于设置混合颜色的函数, 见 代码清单:全彩LED-4。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #define COLOR_RED_TIM TIM10
#define COLOR_GREEN_TIM TIM11
#define COLOR_BLUE_TIM TIM13
#define COLOR_RED_CCRx CCR1
#define COLOR_GREEN_CCRx CCR1
#define COLOR_BLUE_CCRx CCR1
/**
* @brief 设置RGB LED的颜色
* @param rgb:要设置LED显示的颜色值格式RGB888
* @retval 无
*/
void SetRGBColor(uint32_t rgb)
{
//根据颜色值修改定时器的比较寄存器值
COLOR_RED_TIM->COLOR_RED_CCRx = (uint8_t)(rgb>>16); //R
COLOR_GREEN_TIM->COLOR_GREEN_CCRx = (uint8_t)(rgb>>8); //G
COLOR_BLUE_TIM->COLOR_BLUE_CCRx = (uint8_t)rgb; //B
}
/**
* @brief 设置RGB LED的颜色
* @param r\g\b:要设置LED显示的颜色值
* @retval 无
*/
void SetColorValue(uint8_t r,uint8_t g,uint8_t b)
{
//根据颜色值修改定时器的比较寄存器值
COLOR_RED_TIM->COLOR_RED_CCRx = r;
COLOR_GREEN_TIM->COLOR_GREEN_CCRx = g;
COLOR_BLUE_TIM->COLOR_BLUE_CCRx = b;
}
|
本工程提供SetRGBColor和SetColorValue这两个函数用于设置RGB灯的颜色值,这两个函数功能和原理都一样,只是输入参数格式不同,方便使用不同格式的颜色值。
在代码层面,控制RGB灯的颜色实质就是控制各个PWM通道输出脉冲的占空比,而占空比可以通过设置定时器相应通道的输出比较寄存器值修改, 又因为定时器已经把单个控制脉冲周期分成[0:255]份,控制时只要把RGB888各通道的颜色值直接赋予给输出比较寄存器即可。
40.4.2.5. 主函数¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint32_t random_color = 0;
/* 初始化呼吸灯 */
ColorLED_Config();
/*初始化串口*/
Debug_USART_Config();
/* 使能RNG时钟 */
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_RNG, ENABLE);
/* 使能RNG外设 */
RNG_Cmd(ENABLE);
printf("\r\n 欢迎使用野火 STM32 F407 开发板。\r\n");
printf("\r\n 全彩LED灯例程\r\n");
printf("\r\n 使用PWM控制RGB灯,可控制输出各种颜色\r\n ");
while (1) {
SetRGBColor(COLOR_YELLOW);
Delay(0xFFFFFF);
/* 等待随机数产生完毕 */
while (RNG_GetFlagStatus(RNG_FLAG_DRDY)== RESET);
/*获取随机数*/
random_color = RNG_GetRandomNumber();
printf("\r\n 随机颜色值:0x%06x",random_color&0xFFFFFF);
/*显示随机颜色*/
SetRGBColor(random_color&0xFFFFFF);
Delay(0x2FFFFFF);
}
}
|
main函数中直接调用了ColorLED_Config函数,而该函数内部又直接调用了前面讲解的GPIO和PWM配置函数:TIMx_GPIO_Config和TIM_Mode_Config。 初始化完定时器后,又初始化了STM32F4芯片的随机数发生器外设RNG,使用它产生随机数,然后调用SetRGBColor和SetColorValue把RGB灯切换成该随机颜色。 实际应用中也可以根据自己的实际需要向函数设置RGB565的颜色值。
40.4.3. 下载验证¶
编译并下载本程序到开发板,给开发板上电复位,可看到RGB彩灯显示不同的色彩。