一. 关于WS2812
WS2812 内部集成了处理芯片和3颗不同颜色的led灯(红,绿,蓝),通过单总线协议分别控制三个灯的亮度强弱,达到全彩的效果。
WS2812B Datasheet
二. 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 减一不多说
四. 代码实现部分
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_PWM_Stop_DMA(&htim8, TIM_CHANNEL_3);
}
#include "ws2812.h"
#include "tim.h"
#define ONE_PULSE (143)
#define ZERO_PULSE (67)
#define RESET_PULSE (9000)
#define LED_DATA_LEN (24)
#define WS2812_DATA_LEN (RESET_PULSE + LED_NUMS * LED_DATA_LEN)
#define LOOP_ALL for(size_t i = 0; i < LED_NUMS; i++)
#define DEFAULT_BRIGHTNESS (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);
void ws2812_init(void)
{
ws2812_set_dark(MAX);
}
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;
}
}
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;
}
}
void ws2812_set_dark(uint8_t type)
{
switch (type)
{
default:
LOOP_ALL
{
ws2812_set_RGB(0x00, 0x00, 0x00, i);
}
break;
}
ws2812_refresh();
}
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 void ws2812_refresh(void)
{
HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_3, (uint32_t *)RGB_buffer, WS2812_DATA_LEN);
}
static void color_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
{
h %= 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;
}
}
#ifndef __WS2812_H
#define __WS2812_H
#include "stdint.h"
#define LED_NUMS (9)
#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
声明: 本文有参考其他博客, 时间久远, 已忘记原文链接 另外, 本文整合并精简了部分代码, 也增加了常用的颜色格式转换等函数, 更容易使用些 互相交流学习, 不喜勿喷
|