一. 关于WS2812
WS2812 内部集成了处理芯片和3颗不同颜色的led灯(红,绿,蓝),通过单总线协议分别控制三个灯的亮度强弱,达到全彩的效果。
二. WS2812灯珠的几种驱动方式
- 使用延时函数
直接翻转IO口产生时序,这种方式最为简单易用,只需要控制延时的时间,就可以从产生0和1码,它需要占用系统资源。
使用 SPI 数据传输产生时序 - 通过SPI控制
只需要控制在合适的波特率,在传输不同数据的时候,可以产生符合要求的0和1码,这种方式需要等同于使用了一个SPI设备 - 使用 DMA+Timer 产生时序
这种方式需要使用一个定时器,其中一个通道固定产生一个周期1.25us
的PWM,占空比2/3,接着需要另一个通道,在周期的1/3处搬运数据到IO口,若为1,PWM不变,若为0,PWM则为0码,这种方式有更大的局限性,由于DMA只能搬运至少一个字节,所以每次会同时改变8个IO口的高低电平,或许使用位带操作可以解决这问题 - 使用 Timer+PWM+DMA 产生时序
本文讨论的实现方案,这种方案有2种驱动的方式,一种是直接建立一个大的数组,存放所有灯珠的数据,然后启动DMA传输,第二种是建立2个灯组数据大小的数组,当DMA传输一个灯珠数据时,改变另一个灯组数据,通过不断改变数组的方式,节约内存,相比较而言,第一种方式较为直观,第二种方式则可以解决灯珠较多的情况,本文讨论第一种的原理和程序的实现。
三. STM32CubeMX 相关配置
基于 STM32F405RGT6
由于项目选择的TIM8定时器, 查询Datasheet得知, TIM8挂载于APB2总线
APB2的时钟基准为168MHz
计算自动重装载数值:
我们要产生一个周期为1.25us
的PWM,
则 自动重装载值 = 0.00000125 * 168000000 = 210
减一不多说
四. 代码实现部分
/**
* @file 在 tim.c 文件增加以下内容
*/
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_PWM_Stop_DMA(&htim8, TIM_CHANNEL_3);
}
/**
* @file ws2812.c
* @brief WS2812 LED driver, use (PC8) TIM8-CH3 PWM mode.
* @author William
* @date 2022.5.20
*/
#include "ws2812.h"
#include "tim.h"
/* WS2812B f=800k, T=1.25us */
#define ONE_PULSE (143) //1 码 (2/3*T)
#define ZERO_PULSE (67) //0 码 (1/3*T)
#define RESET_PULSE (9000) //低电平复位信号50us
#define LED_DATA_LEN (24) //led 颜色数据长度, 一个灯珠需要24bits
#define WS2812_DATA_LEN (RESET_PULSE + LED_NUMS * LED_DATA_LEN) //ws2812灯条需要的总数组长度
/* !! 若嵌套循环使用, 须注意变量(i) !! */
#define LOOP_ALL for(size_t i = 0; i < LED_NUMS; i++) /* 所有灯珠 */
#define DEFAULT_BRIGHTNESS (100) //灯带默认亮度: 0~100
static uint16_t RGB_buffer[WS2812_DATA_LEN] = {0};
static void ws2812_refresh(void);
static void color_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b);
/* ************************************ Public Functions ************************************ */
/**
* @brief WS2812初始化, 全黑
*/
void ws2812_init(void)
{
ws2812_set_dark(MAX);
}
/**
* @brief 设置某个灯珠颜色RGB
*
* @param uint8_t R,G,B: RGB色彩格式 红,绿,蓝通道数据
* uint16_t num, 指定设置颜色的灯珠位号
*/
void ws2812_set_RGB(uint8_t R, uint8_t G, uint8_t B, uint16_t num)
{
if (num > LED_NUMS)
return;
uint16_t *p = (RGB_buffer + RESET_PULSE) + (num * LED_DATA_LEN);
for (uint16_t i = 0; i < 8; i++)
{
p[i] = (G << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
p[i + 8] = (R << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
p[i + 16] = (B << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
}
}
/**
* @brief 设置某个灯珠颜色HSV
*
* @param uint8_t H,S,V: HSV色彩格式
* uint16_t num, 指定设置颜色的灯珠位号
*/
void ws2812_set_HSV(uint16_t H, uint16_t S, uint16_t V, uint16_t num)
{
uint32_t R = 0, G = 0, B = 0;
if (num > LED_NUMS)
return;
uint16_t *p = (RGB_buffer + RESET_PULSE) + (num * LED_DATA_LEN);
color_hsv2rgb(H, S, V, &R, &G, &B);
for (uint16_t i = 0; i < 8; i++)
{
p[i] = (G << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
p[i + 8] = (R << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
p[i + 16] = (B << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
}
}
/**
* @brief 灭灯
*/
void ws2812_set_dark(uint8_t type)
{
switch (type)
{
default:
LOOP_ALL
{
ws2812_set_RGB(0x00, 0x00, 0x00, i);
}
break;
}
ws2812_refresh();
}
/**
* @brief WS2812全彩渐变
* @note 需放置循环体内
*/
void ws2812_colorful_shadow(void)
{
for (uint16_t color = 0; color < 360; color++)
{
for (uint8_t i = 0; i < LED_NUMS; i++)
{
ws2812_set_HSV(color, 100, DEFAULT_BRIGHTNESS, i);
}
ws2812_refresh();
osDelay(50);
}
}
/* ************************************ Static Functions ************************************ */
/**
* @brief WS2812颜色数据刷新, 修改颜色值后调用
*/
static void ws2812_refresh(void)
{
HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_3, (uint32_t *)RGB_buffer, WS2812_DATA_LEN);
}
/**
* @brief 将HSV颜色空间转换为RGB颜色空间
*
* @param h HSV颜色空间的H:色调, 范围0~360
* @param s HSV颜色空间的S:饱和度, 范围0~100
* @param v HSV颜色空间的V:明度, 范围0~100
* @param r 转换后RGB-R值的指针
* @param g 转换后RGB-G值的指针
* @param b 转换后RGB-B值的指针
*
*/
static void color_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
{
h %= 360; // h -> [0,360]
uint32_t rgb_max = v * 2.55f;
uint32_t rgb_min = rgb_max * (100 - s) / 100.0f;
uint32_t i = h / 60;
uint32_t diff = h % 60;
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i)
{
case 0:
*r = rgb_max;
*g = rgb_min + rgb_adj;
*b = rgb_min;
break;
case 1:
*r = rgb_max - rgb_adj;
*g = rgb_max;
*b = rgb_min;
break;
case 2:
*r = rgb_min;
*g = rgb_max;
*b = rgb_min + rgb_adj;
break;
case 3:
*r = rgb_min;
*g = rgb_max - rgb_adj;
*b = rgb_max;
break;
case 4:
*r = rgb_min + rgb_adj;
*g = rgb_min;
*b = rgb_max;
break;
default:
*r = rgb_max;
*g = rgb_min;
*b = rgb_max - rgb_adj;
break;
}
}
/**
* @file ws2812.h
* @brief WS2812 LED driver, use TIM8-CH3 PWM mode.
* @author William
* @date 2022.5.20
*/
#ifndef __WS2812_H
#define __WS2812_H
#include "stdint.h"
#define LED_NUMS (9) //灯珠数量
/* HSV格式常用色值预设 */
#define RED 0
#define YELLOW 60
#define GREEN 120
#define CYAN 180
#define BLUE 240
void ws2812_init(void);
void ws2812_set_RGB(uint8_t R, uint8_t G, uint8_t B, uint16_t num);
void ws2812_set_HSV(uint16_t H, uint16_t S, uint16_t V, uint16_t num);
void ws2812_set_dark(uint8_t type);
void ws2812_colorful_shadow(void);
#endif //__WS2812_H
声明:
本文有参考其他博客, 时间久远, 已忘记原文链接
另外, 本文整合并精简了部分代码, 也增加了常用的颜色格式转换等函数, 更容易使用些
互相交流学习, 不喜勿喷