本帖最后由 xld0932 于 2022-3-29 19:55 编辑
#申请原创# @21小跑堂
在上一篇中我们介绍了LED灯,它是一个单色的灯,比如红色、蓝色、黄色、或者绿色等等;而本篇中的RGB灯是一个彩色灯,一个RGB灯包含了红、绿、蓝这三元色,可以通过对红绿蓝亮度的比例调节来实现显示彩色的效果。
在淘宝上买了两款RGB灯:一种是RGB灯自身不带有驱动,而是通过调节RGB三个引脚的供电电压/电流来实现彩色显示的模块;另外一种则是RGB灯本身自带有驱动部分,只需要根据内置驱动输入相应的时序和数据即可驱动显示彩色效果的模块;分别如下图所示:
对于无内置驱动的RGB灯模块,我们可以通过像LED灯的控制方式来控制某一颜色的灯单独点亮,也可以通过单个PWM结合对数的控制方式,实现彩色呼吸灯的效果;当然如果要显示混合的炫彩效果,就需要3路PWM分别来控制RGB这三个通道,但不管如何控制,结合环境光和实际的呈现效果,都不太满意,所以一般这种RGB灯要么是结合专业的驱动芯片一起工作,要么仅仅是当作三色灯来使用。示例程序如下所示: void RGB_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_WriteBit(GPIOB, GPIO_Pin_3, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_4, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_RESET);
TASK_Append(TASK_ID_RGB, RGB_Handler, 250);
}
void RGB_Handler(void)
{
static uint8_t Index = 0;
switch(Index)
{
case 0:
GPIO_WriteBit(GPIOB, GPIO_Pin_3, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_4, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_RESET);
break;
case 1:
GPIO_WriteBit(GPIOB, GPIO_Pin_3, Bit_SET);
GPIO_WriteBit(GPIOB, GPIO_Pin_4, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_RESET);
break;
case 2:
GPIO_WriteBit(GPIOB, GPIO_Pin_3, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_4, Bit_SET);
GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_RESET);
break;
case 3:
GPIO_WriteBit(GPIOB, GPIO_Pin_3, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_4, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_SET);
break;
default:
break;
}
Index = (Index + 1) % 4;
}
void RGB_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 1000 - 1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OC3Init(TIM1, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_6); /* TIM1_CH1 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_6); /* TIM1_CH2 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_6); /* TIM1_CH3 */
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TASK_Append(TASK_ID_RGB, RGB_Handler, 25);
}
double LED_GetLOG(double level, double max)
{
double a = log10(999);
printf("\r\n%d(a) = %f", (uint32_t)level, a);
double b = level / (max / a);
printf("\r\n%d(b) = %f", (uint32_t)level, b);
double c = pow(10, b);
printf("\r\n%d(c) = %f", (uint32_t)level, c);
return c;
}
void RGB_Handler(void)
{
static uint16_t Level = 1, Index = 0, MAX = 100.0;
static uint8_t State = 0;
switch(Index)
{
case 0 : TIM_SetCompare1(TIM1, (uint32_t)LED_GetLOG(Level, MAX)); break;
case 1 : TIM_SetCompare2(TIM1, (uint32_t)LED_GetLOG(Level, MAX)); break;
case 2 : TIM_SetCompare3(TIM1, (uint32_t)LED_GetLOG(Level, MAX)); break;
default: break;
}
if(State == 0)
{
if(Level >= MAX)
{
Level = MAX;
State = 1;
}
else
{
Level++;
}
}
else
{
if(Level <= 1)
{
Level = 1;
State = 0;
Index = (Index + 1) % 3;
}
else
{
Level--;
}
}
}
而对于内置驱动的RGB灯,一般常见的就是WS2812B,由于内置了驱动芯片,显示效果比直接通过PWM控制的效果好太多,能够完全显示出五彩缤纷的颜色;由于驱动芯片是可以级连的,这样就可以通过一根通讯控制线来实现对一串RGB灯珠或者一组灯珠的控制了,既节省了MCU资源,也减少了布线成本。本文主要就是通过MM32F0140芯片不同资源来实现对WS2812B RGB灯的控制及显示: 1、通过GPIO控制WS2812B RGB灯 2、通过PWM控制WS2812B RGB灯,及详述实现原理 3、通过SPI控制WS2812B RGB灯,及详述实现原理
我们使用的WS2812B驱动RGB灯珠是3.5V~5.5V供电的,正好我们MM32F0140芯片正常工作电压在2.0~5.5V之间都可以,但由于一个灯珠的R/G/B驱动电流就有12mA,我们接的RGB灯珠也比较多,这样整体电流功耗就会比较大,此时我们就需要外接一个5V的电源适配器来给整个系统供电了。
WS2812B RGB灯珠参数
WS2812B RGB连接方式
WS2812B RGB通讯协议
通过GPIO模拟时序控制RGB灯 结合WS2812B RGB通讯协议,我们使用GPIO来模拟数据的发送时序,是最简易的实现方式了;但它的缺点就是对芯片的依赖性太强,不兼具程序的移植性和可修改性,在前期调试驱动时序也需要大费功夫;但硬件平台一但确定下来,通过这种方式,也是一次性的投入,大多数也可以理解为51和ARM通用的实现方式了。具体的实现代码如下所示(系统运行在72MHz的时钟频率): void RGB_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TASK_Append(TASK_ID_RGB, RGB_Handler, 500);
}
void WS2812B_Write0(void)
{
GPIOB->BSRR = GPIO_Pin_13; /* T0H 330ns */
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
GPIOB->BRR = GPIO_Pin_13; /* T0L 920ns */
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
}
void WS2812B_Write1(void)
{
GPIOB->BSRR = GPIO_Pin_13; /* T1H 670ns */
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
GPIOB->BRR = GPIO_Pin_13; /* T1L 580ns */
__nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
}
void WS2812B_SendData(uint8_t Data)
{
for(uint8_t i = 0; i < 8; i++)
{
if(Data & (0x80 >> i))
{
WS2812B_Write1();
}
else
{
WS2812B_Write0();
}
}
}
void WS2812B_SendColor(uint8_t R, uint8_t G, uint8_t B)
{
WS2812B_SendData(G);
WS2812B_SendData(R);
WS2812B_SendData(B);
}
通过TIMER PWM来控制RGB灯 结合WS2812B RGB通讯协议,我们看到不管是0码还是1码,一个码元的周期典型值是1.25us,唯一不同的就是0码和1码在这个周期内高低电平的占比不同;这样结合我们定时器的PWM比较输出功能,再结合DMA方式,就可以实现一个RGB数据序列了;在交流参数中指定了数据传输速度典型为800kHz,也就是对应了码元的1.25us的周期,这样我们在初始化定时器的时候,就可以直接将PWM输出频率固定在800kHz,然后通过控制CCR的值结合DMA来调整输出的占空比,实现0码和1码的时序。
我们示例程序中使用的是TIM2,它挂载在APB1总线上,根据系统初始化后APB1总线的时钟频率为72MHz,那需要800kHz的码元周期,则需要将定时器90分频,所以程序中设置的是89(0~89有90个数,代表了90分频);而对于0码在码元周期中高电平时间为0.33us,所对应的CCR值通过计算约为24(0.33 * 90 / 1.25),1码在码元周期中高电平时间为0.66us,所应对的CCR值通过计算约为48(0.66 * 90 / 1.25),有了这些数据,我们就可以进行程序化和实现对WS2812B RGB灯珠的控制啦,示例程序如下: uint32_t RGB_Buffer[100];
void RGB_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
memset(RGB_Buffer, 0, sizeof(RGB_Buffer));
RCC_AHBPeriphClockCmd(RCC_AHBENR_DMA1, ENABLE);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RGB_Buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 24;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel5, DISABLE);
RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 89;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, DISABLE);
TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);
TIM_Cmd(TIM2, DISABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_7);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TASK_Append(TASK_ID_RGB, RGB_Handler, 500);
}
void WS2812B_SendData(uint8_t Data, uint8_t Offset)
{
for(uint8_t i = 0; i < 8; i++)
{
if(Data & (0x80 >> i))
{
RGB_Buffer[i + Offset] = 48;
}
else
{
RGB_Buffer[i + Offset] = 24;
}
}
}
void WS2812B_SendColor(uint8_t R, uint8_t G, uint8_t B)
{
WS2812B_SendData(G, 0x00);
WS2812B_SendData(R, 0x08);
WS2812B_SendData(B, 0x10);
DMA_Cmd(DMA1_Channel5, ENABLE);
TIM_Cmd(TIM2, ENABLE);
while(!DMA_GetFlagStatus(DMA1_FLAG_TC5));
DMA_ClearFlag(DMA1_FLAG_TC5);
TIM_Cmd(TIM2, DISABLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
}
通过SPI的MOSI来控制RGB灯 除了上面的那种实现方式之外,我们还可以结合SPI的MOSI这个输出引脚来达到对WS2812B RGB灯珠时序的控制,我们都知道SPI的数据发送的每一个比特位都是按照SPI SCK这个时钟频率进行了,我们MM32F0140这颗MCU的SPI支持1~32位的用户自定义长度位进行数据发送,正好我们就结合了这一功能;
示例程序中使用的是SPI2的MOSI引脚,SPI2也是工作在APB1总线上;在程序中,我们将SPI总线频率进行了16分频率,即SPI SCK的时候频率为2.25MHz,传输1位数据的时间约为0.45us,每3个位为一组,那一组位时间就为1.33us,那正好8组3BIT的数据组成了R/G/B这样的数据序列。在0码中要求了高电平的时间最大不超过0.47us,此时0码用3BIT二进制表示就是100b;而1码中要求了高电平的时间最大不超过1us,此时1码用3BIT二进制表示就是110b;通过8组0码或者1码,就组成了一个颜色的数据,发送3个24位的SPI数据,就可以驱动一个灯珠显示颜色了。示例程序如下所示: void RGB_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI2, ENABLE);
SPI_StructInit(&SPI_InitStructure);
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_DataWidth = 24;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_Cmd(SPI2, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_4);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
SPI_BiDirectionalLineConfig(SPI2, SPI_Direction_Tx);
TASK_Append(TASK_ID_RGB, RGB_Handler, 250);
}
void WS2812B_SendData(uint8_t Data)
{
uint32_t Value = 0;
for(uint8_t i = 0; i < 8; i++)
{
Value <<= 3;
if((Data >> i) & 0x01)
{
Value |= 0x6;
}
else
{
Value |= 0x4;
}
}
uint32_t TempH = ((Value >> 0) & 0x000000FF) << 16;
uint32_t TempM = ((Value >> 8) & 0x000000FF) << 8;
uint32_t TempL = ((Value >> 16) & 0x000000FF) << 0;
SPI_SendData(SPI2, (TempH + TempM + TempL));
for(uint8_t i = 0; i < 100; i++);
}
void WS2812B_SendColor(uint8_t R, uint8_t G, uint8_t B)
{
WS2812B_SendData(G);
WS2812B_SendData(R);
WS2812B_SendData(B);
}
演示效果 除了整体驱动显示之外,还做了在8*8的RGB矩阵中显示了数字的演示,示例程序和演示效果如下所示: void RGB_Handler(void)
{
static uint8_t Index = 0;
uint8_t Color[8][3] =
{
{255, 0, 0},
{255, 255, 0},
{255, 0, 255},
{255, 255, 255},
{ 0, 255, 0},
{ 0, 0, 255},
{ 0, 255, 255},
{128, 128, 128},
};
__disable_irq();
for(uint8_t i = 0; i < 64; i++)
{
WS2812B_SendColor(Color[Index][0], Color[Index][1], Color[Index][2]);
}
Index = (Index + 1) % 8;
__enable_irq();
}
uint8_t NUM_FONT[10][8] =
{
{0x3E,0x22,0x22,0x22,0x22,0x22,0x3E,0x00},
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00},
{0x1C,0x22,0x02,0x04,0x08,0x10,0x3E,0x00},
{0x1C,0x22,0x02,0x04,0x02,0x22,0x1C,0x00},
{0x04,0x0C,0x14,0x24,0x3E,0x04,0x04,0x00},
{0x3E,0x20,0x20,0x3E,0x02,0x02,0x3E,0x00},
{0x3E,0x20,0x20,0x3E,0x22,0x22,0x3E,0x00},
{0x3E,0x02,0x04,0x08,0x08,0x08,0x08,0x00},
{0x3E,0x22,0x22,0x3E,0x22,0x22,0x3E,0x00},
{0x3E,0x22,0x22,0x3E,0x02,0x02,0x3E,0x00},
};
void RGB_DrawNumber(uint8_t Index, uint8_t Value)
{
uint8_t Color[8][3] =
{
{255, 0, 0},
{255, 255, 0},
{255, 0, 255},
{255, 255, 255},
{ 0, 255, 0},
{ 0, 0, 255},
{ 0, 255, 255},
};
for(uint8_t i = 0; i < 8; i++)
{
for(uint8_t j = 0; j < 8; j++)
{
if(NUM_FONT[Value][i] & (0x80 >> j))
{
WS2812B_SendColor(Color[Index][0], Color[Index][1], Color[Index][2]);
}
else
{
WS2812B_SendColor(0, 0, 0);
}
}
}
}
void RGB_Handler(void)
{
static uint8_t Index = 0, Value = 0;
__disable_irq();
RGB_DrawNumber(Index, Value);
Value = (Value + 1) % 10;
if(Value == 0)
{
Index = (Index + 1) % 7;
}
__enable_irq();
}
附件
|