STM32CubeMx驱动WS2812B实现幻彩(超详)
1.创建基于STM32F03C8T6工程
1.1配置时钟
- 选择外部高速时钟源HSE
1.2配置系统时钟树使其达到最大时钟72MHz(最大系统时钟)
由时钟树可以知道APB1上定时器时钟频率是72MHz,实验使用的硬件接的是PA2,用的定时器TIM2_CH3, 查阅数据手册可知
TIM2外设在APB1上所以TIM2的定时器频率是72MHz。
3.理论分析
之前写过一篇文章,文章链接在此:创客实验第一弹之驱动WS2812B彩灯;不了解的可以直接跳转,今天主要讲解如何使用STM32CubeMx驱动WS2812B实现幻彩,这里做部分内容截取,完整可以跳转文章阅读。
3.1WS2812B的逻辑“1”和逻辑“0”
由上图可知“0”码和“1”码都是既有高电平又有低电平不过高电平和低电平的比例不同,这点很好理解,重点是分析一下它的特点,首先直观的特点就是编码“0”的电平高电平时间短一些低电平时间长一些,这也恰好符合我们的逻辑毕竟它还是低电平多一些的,编码“1”的电平高电平时间就长一些,而低电平就短一些。
但是不管是高电平还是低电平他们占用整个时间长度是一样的,这里还有一个很长的低电平这个代表复位信号。
3.2WS2812B控制波形的精准描述如何?
这里涉及到严格的数学描述了,长一点是多长?短一点是多短?这个肯定是有标准或者是约束的
理论上来说,高电平时长和低电平时长加起来应该是0.4us+0.85us或者0.85us+0.4us也就是说总共要占用1.25us的时间才可以编码出来一个“0”或者“1”出来。复位是要求50us以上,显然是要比编码的0或者1占用的时间要多的。
当然既然是电路的高低电平时长就会引入误差这个在误差允许的范围内我们可以接受,这个范围就是上下不超过150ns这里是ns比us还要小的时间,这个其实时间要求还是很严格的。
1.3分析结果
定时器主频是72MHz 要想实现1.25us 周期的波形也就是频率是800KHz波形我们可以采用分频为1分频,计数值为90来实现具体原因如下
- PWM频率计算公式:
Fpwm =Tclk / ((arr+1)*(psc+1))(单位:Hz)
上面提到数据传送频率为800KHz,TCLK为72Mhz,我们这里设置psc = 0,arr= 89,得到频率刚好为800KHz,也就是计数器计数90个数,每个数计数时间是1/72us,1/72us*90 = 1.25us;
通过控制PWM占空比发送0码和1码,额定周期为1.25us,则频率为800Khz
- 0码占空比计算
(0码高电平时间)/(周期)—> 0.4us / 1.25 us = 0.32
用占空比乘以定时器重装值加一就是0码的CCR值(代表PWM高电平计数个数)—>
0.32 * (90) = 28.8(取28,网上网友实测不可以高于28,但23到28都可以,我这里选28)
-
1码占空比计算
同理计算:(1码高电平时间)/ (周期)—> 0.8 / 1.25 = 0.64
(占空比)*(90)= CCR —> 0.64 * 90 = 57.6(取57左右即可这里取58)
4.配置定时器的参数
5.WS2812B灯带的数据是什么样的呢?
在说数据格式之前先来补充一下关于色彩的知识点,就是三原色,红绿蓝,也就是我们常说的RGB,R就是RED,G就是GREEN,B就是BLUE,一个彩色可以用这三种颜色的比例来混合出来。
每一个LED的R、G、B分别由八位数据控制颜色浓度,(每种颜色浓度有0~255档,理论上RGB就可以组成256的3次方中颜色组合)即每个LED需要24BIT数据,那么需要发送数据的总长度则为(要控制LED数量 n)*(24),每个LED保存24BIT将剩余位传给后面LED。全部数据发送完成后要继续发送大于24us的低电平作为RESET_CODE等才可以点亮。
既然是24bit数据代表三种颜色,我们就要首先知道一个bit的意义是什么,我们传统意义上来说1个bit代表一个数据位,但是对于数据位bit的理解好像就是“1”或者“0”在数电里我们很容易把高低电平跟逻辑1和逻辑0对应起来,但是表示灯珠的逻辑电平不是简单的高低电平。在数值上0xFFFFFF就是24bit的1,0x000000就是24bit的0.这里有8个bit代表颜色G分量,G7G6G5G4G3G2G1G0,有8个bit代表R分量R7R6R5R4R3R2R1R0,有8个bit代表B分量B7B6B5B4B3B2B1B0,当不同分量组合时候就会有不同的数据产生,这个数据背后其实是逻辑电平,这里要说明的是彩灯的逻辑“1”并不是简简单单的高电平,彩灯的逻辑“0”也不是简简单单的低电平。而是上面分析的占空比不同的PWM波形。
4.配置定时器的DMA参数
这里解释下为什么要就一颗灯也要用DMA,这里仅仅是拿一个灯做,其实多个灯串起来数据量还是很大的使用DMA才可以充分释放其性能优势。
- 内存向外设传输
- 内存递增递增
- DMA普通模式
- 32为字长传输
4.生产代码修改完善
生成工程配置可参考上篇博客
4.1添加自定义ws2812b.c文件跟ws2812b.h文件
4.2修改自定义ws2812b.c文件跟ws2812b.h文件
- ws2812.c文件内容
#include "ws2812b.h"
#include "tim.h"
/*Some Static Colors------------------------------*/
const RGB_Color_TypeDef RED = {255,0,0}; //显示红色RGB数据
const RGB_Color_TypeDef ORANGE = {127,106,0};
const RGB_Color_TypeDef YELLOW = {127,216,0};
const RGB_Color_TypeDef GREEN = {0,255,0};
const RGB_Color_TypeDef CYAN = {0,255,255};
const RGB_Color_TypeDef BLUE = {0,0,255};
const RGB_Color_TypeDef PURPLE = {238,130,238};
const RGB_Color_TypeDef BLACK = {0,0,0};
const RGB_Color_TypeDef WHITE = {255,255,255};
const RGB_Color_TypeDef MAGENTA = {255,0,220};
/*二维数组存放最终PWM输出数组,每一行24个
数据代表一个LED,最后一行24个0代表RESET码*/
uint32_t Pixel_Buf[LED_NUM+1][24];
/*
功能:设定单个RGB LED的颜色,把结构体中RGB的24BIT转换为0码和1码
参数:LedId为LED序号,Color:定义的颜色结构体
*/
void RGB_SetColor(uint8_t LedId,RGB_Color_TypeDef Color)
{
uint8_t i;
if(LedId > LED_NUM)return; //avoid overflow 防止写入ID大于LED总数
for(i=0;i<8;i++) Pixel_Buf[LedId][i] = ( (Color.G & (1 << (7 -i)))? (CODE_1):CODE_0);//数组某一行0~7转化存放G
for(i=8;i<16;i++) Pixel_Buf[LedId][i] = ( (Color.R & (1 << (15-i)))? (CODE_1):CODE_0);//数组某一行8~15转化存放R
for(i=16;i<24;i++) Pixel_Buf[LedId][i] = ( (Color.B & (1 << (23-i)))? (CODE_1):CODE_0);//数组某一行16~23转化存放B
}
/*
功能:最后一行装在24个0,输出24个周期占空比为0的PWM波,作为最后reset延时,这里总时长为24*1.2=30us > 24us(要求大于24us)
*/
void Reset_Load(void)
{
uint8_t i;
for(i=0;i<24;i++)
{
Pixel_Buf[LED_NUM][i] = 0;
}
}
/*
功能:发送数组
参数:(&htim1)定时器1,(TIM_CHANNEL_2)通道2,((uint32_t *)Pixel_Buf)待发送数组,
(Pixel_NUM+1)*24)发送个数,数组行列相乘
*/
void RGB_SendArray(void)
{
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3, (uint32_t *)Pixel_Buf,(LED_NUM+1)*24);
}
/*
功能:显示红色
参数:Pixel_Len为显示LED个数
*/
void RGB_RED(uint16_t Pixel_Len)
{
uint16_t i;
for(i=0;i<Pixel_Len;i++)//给对应个数LED写入红色
{
RGB_SetColor(i,RED);
}
Reset_Load();
RGB_SendArray();
}
/*
功能:显示绿色
参数:Pixel_Len为显示LED个数
*/
void RGB_GREEN(uint16_t Pixel_Len)
{
uint16_t i;
for(i=0;i<Pixel_Len;i++)//给对应个数LED写入绿色
{
RGB_SetColor(i,GREEN);
}
Reset_Load();
RGB_SendArray();
}
/*
功能:显示蓝色
参数:Pixel_Len为显示LED个数
*/
void RGB_BLUE(uint16_t Pixel_Len)
{
uint16_t i;
for(i=0;i<Pixel_Len;i++)//给对应个数LED写入蓝色
{
RGB_SetColor(i,BLUE);
}
Reset_Load();
RGB_SendArray();
}
/*
功能:显示白色
参数:Pixel_Len为显示LED个数
*/
void RGB_WHITE(uint16_t Pixel_Len)
{
uint16_t i;
for(i=0;i<Pixel_Len;i++)//给对应个数LED写入白色
{
RGB_SetColor(i,WHITE);
}
Reset_Load();
RGB_SendArray();
}
//用户自定义API接口可根据实际拓展
/*******************************************************************************/
/* 添加部分 */
//显示指定颜色
static void rgb_show(uint32_t Pixel_Len, RGB_Color_TypeDef rgb)
{
uint16_t i;
for(i=0;i<Pixel_Len;i++)
{
RGB_SetColor(i,rgb);
}
Reset_Load();
RGB_SendArray();
}
//颜色循环转换
static RGB_Color_TypeDef Wheel(uint8_t WheelPos)
{
RGB_Color_TypeDef rgb;
WheelPos = 255 - WheelPos;
if (WheelPos < 85)
{
rgb.R = 255 - WheelPos * 3;
rgb.G = 0;
rgb.B = WheelPos * 3;
return rgb;
}
if (WheelPos < 170)
{
WheelPos -= 85;
rgb.R = 0;
rgb.G = WheelPos * 3;
rgb.B = 255 - WheelPos * 3;
return rgb;
}
WheelPos -= 170;
rgb.R = WheelPos * 3;
rgb.G = 255 - WheelPos * 3;
rgb.B = 0;
return rgb;
}
//彩虹呼吸灯
static void rainbow(uint8_t wait)
{
uint32_t timestamp = HAL_GetTick();
uint16_t i;
static uint8_t j;
static uint32_t next_time = 0;
uint32_t flag = 0;
if (next_time < wait)
{
if ((uint64_t)timestamp + wait - next_time > 0)
flag = 1;
}
else if (timestamp > next_time)
{
flag = 1;
}
if (flag) // && (timestamp - next_time < wait*5))
{
j++;
next_time = timestamp + wait;
for (i = 0; i < LED_NUM; i++)
{
RGB_SetColor(i, Wheel((i + j) & 255));
}
}
RGB_SendArray();
}
//彩虹灯旋转
static void rainbowCycle(uint8_t wait)
{
uint32_t timestamp = HAL_GetTick();
uint16_t i;
static uint8_t j;
static uint32_t next_time = 0;
static uint8_t loop = 0;
if (loop == 0)
next_time = timestamp;
loop = 1; //首次调用初始化
if ((timestamp > next_time)) // && (timestamp - next_time < wait*5))
{
j++;
next_time = timestamp + wait;
for (i = 0; i < LED_NUM; i++)
{
RGB_SetColor(i, Wheel(((i * 256 / (LED_NUM)) + j) & 255));
}
}
RGB_SendArray();
}
- ws2812.h文件内容
#ifndef _WS2812B_H_
#define _WS2812B_H_
#include "main.h"
/*这里是上文计算所得CCR的宏定义*/
#define CODE_1 (58) //1码定时器计数次数
#define CODE_0 (28) //0码定时器计数次数
/*建立一个定义单个LED三原色值大小的结构体*/
typedef struct
{
uint8_t R;
uint8_t G;
uint8_t B;
}RGB_Color_TypeDef;
#define LED_NUM 1 //LED数量宏定义,这里我使用一个LED
void RGB_SetColor(uint8_t LedId,RGB_Color_TypeDef Color);//给一个LED装载24个颜色数据码(0码和1码)
void Reset_Load(void); //该函数用于将数组最后24个数据变为0,代表RESET_code
void RGB_SendArray(void); //发送最终数组
void RGB_RED(uint16_t Pixel_Len); //显示红灯
void RGB_GREEN(uint16_t Pixel_Len);//显示绿灯
void RGB_BLUE(uint16_t Pixel_Len); //显示蓝灯
void RGB_WHITE(uint16_t Pixel_Len);//显示白灯
#endif
4.3修改主函数实现测试功能
/* USER CODE BEGIN Includes */
#include "ws2812b.h"
/* USER CODE END Includes */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
RGB_RED(1);
HAL_Delay(1000);
RGB_GREEN(1);
HAL_Delay(1000);
RGB_BLUE(1);
HAL_Delay(1000);
RGB_WHITE(1);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
5.下载验证
颜色好像有点色差,基本没啥问题。OK 到这如何使用STM32CubeMx驱动WS2812B实现幻彩基本搞定,后面有机会再更新幻彩灯的实现部分。