- 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();
- }
附件