/**********************************************************************
|
* Copyright: (C)2024 LingYun IoT System Studio
|
* Author: GuoWenxue<guowenxue@gmail.com>
|
*
|
* Description: WS2812B strip lights driver on ISKBoard
|
*
|
* Reference : https://blog.csdn.net/Lennon8_8/article/details/108980808
|
*
|
* COMPILER OPTIMIZATION WARNING:
|
* This driver uses NOP-based delays for precise WS2812B pulse timing. Keil5
|
* Arm Compiler(AC6) default '-O3' optimization breaks this by altering loop
|
* assembly and branch structures, causing timing collapse.
|
* Fix: Options for Target -> C/C++ (AC6) Set Optimization to -O1.
|
* Note: GCC compiler (STM32CubeIDE/VSCode) is NOT affected.
|
*
|
* ChangeLog:
|
* Version Date Author Description
|
* V1.0.0 2024.08.29 GuoWenxue Release initial version
|
*
|
***********************************************************************/
|
#include <string.h>
|
#include <stdint.h>
|
#include "ws2812b.h"
|
#include "miscdev.h"
|
|
#define mdelay(ms) HAL_Delay(ms)
|
|
static color_t g_ColorPanel[WS2812_NUM];
|
|
typedef struct ws_gpio_s
|
{
|
GPIO_TypeDef *group;
|
uint16_t pin;
|
} ws_gpio_t;
|
|
/* 使用 ws_gpio 结构体进行 GPIO 操作,便于移植 */
|
static ws_gpio_t ws_gpio =
|
{
|
.group = GPIOC,
|
.pin = GPIO_PIN_2,
|
};
|
#define ws_set_high() (ws_gpio.group->BSRR = ws_gpio.pin)
|
#define ws_set_low() (ws_gpio.group->BRR = ws_gpio.pin)
|
|
|
/* 配置 WS2812B 灯带的 GPIO 口 */
|
void ws2812b_init(void)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
GPIO_InitStruct.Pin = ws_gpio.pin;
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
|
HAL_GPIO_Init(ws_gpio.group, &GPIO_InitStruct);
|
|
ws2812b_reset();
|
|
return ;
|
}
|
|
/*
|
* 使用内联汇编 NOP 实现纳秒级精确延时 @ 80MHz: 1 NOP = 12.5ns
|
*
|
* 根据数据手册时序:
|
* T0H: 220~380ns (recommend 300ns = 24 cycles)
|
* T0L: 580~1000ns (recommend 800ns = 64 cycles)
|
* T1H: 580~1600ns (recommend 1100ns = 88 cycles)
|
* T1L: 220~420ns (recommend 320ns = 26 cycles)
|
* RES: > 280us
|
*
|
* 实际测试建议:
|
* - 考虑 GPIO 操作本身耗时约 2-3 个时钟周期
|
* - 考虑函数调用、判断等开销
|
* - 以下数值已预留开销,可根据实测微调 ±2~4 个 NOP
|
*/
|
|
/* T0H: 300ns ≈ 24 cycles
|
* 减去 GPIO 和判断开销约 6 cycles,使用 18 NOPs
|
*/
|
#define delay_T0H() __ASM volatile( \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t" \
|
)
|
|
/* T0L: 800ns ≈ 64 cycles
|
* 减去开销约 6 cycles,使用 58 NOPs
|
*/
|
#define delay_T0L() __ASM volatile( \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
)
|
|
/* T1H: 1100ns ≈ 88 cycles
|
* 减去开销约 6 cycles,使用 82 NOPs
|
*/
|
#define delay_T1H() __ASM volatile( \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t" \
|
)
|
|
/* T1L: 320ns ≈ 26 cycles
|
* 减去开销约 6 cycles,使用 20 NOPs
|
*/
|
#define delay_T1L() __ASM volatile( \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
"NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t NOP\n\t" \
|
)
|
|
static inline void ws2812b_WriteByte(uint8_t byte)
|
{
|
int i;
|
|
for(i=0; i<8; i++)
|
{
|
if(byte & 0x80)
|
{
|
/* Sequence Send 1:
|
*
|
* +--------------+ +
|
* |<- T1H ->|<- T1L ->|
|
* | +---------+
|
*/
|
ws_set_high();
|
delay_T1H();
|
|
ws_set_low();
|
delay_T1L();
|
}
|
else
|
{
|
/* Sequence Send 0:
|
*
|
* +---------+ +
|
* |<- T0H ->|<- T0L ->|
|
* | +--------------+
|
*/
|
ws_set_high();
|
delay_T0H();
|
|
ws_set_low();
|
delay_T0L();
|
}
|
|
byte <<= 1;
|
}
|
|
return ;
|
}
|
|
void ws2812b_reset(void)
|
{
|
/* RES: low voltage time > 280us */
|
ws_set_low();
|
udelay(300);
|
|
memset(g_ColorPanel, 0, sizeof(g_ColorPanel));
|
|
return ;
|
}
|
|
static inline void ws2812b_SendColor(color_t color)
|
{
|
ws2812b_WriteByte(color.pixel.green);
|
ws2812b_WriteByte(color.pixel.red);
|
ws2812b_WriteByte(color.pixel.blue);
|
|
return ;
|
}
|
|
static inline void ws2812b_Refresh(void)
|
{
|
int i;
|
uint32_t primask;
|
|
/* 关闭中断保证时序准确 */
|
primask = __get_PRIMASK();
|
__disable_irq();
|
|
for(i=0; i<WS2812_NUM; i++)
|
{
|
ws2812b_SendColor(g_ColorPanel[i]);
|
}
|
|
/* 恢复中断状态 */
|
__set_PRIMASK(primask);
|
|
return ;
|
}
|
|
static inline int ws2812b_SetPixel(int which, color_t color)
|
{
|
if( which<0 || which>=WS2812_NUM )
|
return -1;
|
|
g_ColorPanel[which].pixel.red = color.pixel.red;
|
g_ColorPanel[which].pixel.green = color.pixel.green;
|
g_ColorPanel[which].pixel.blue = color.pixel.blue;
|
|
return 0;
|
}
|
|
int ws2812b_turn(int which, color_t color)
|
{
|
if( which<0 || which>=WS2812_NUM )
|
return -1;
|
|
ws2812b_SetPixel(which, color);
|
ws2812b_Refresh();
|
|
return 0;
|
}
|
|
void ws2812b_blink(void)
|
{
|
int c, i;
|
color_t colors[4] = {[0].data=PIXEL_B, [1].data=PIXEL_R, [2].data=PIXEL_G, [3].data=PIXEL_W};
|
color_t color_off = {.data = PIXEL_OFF};
|
|
for(c=0; c<4; c++) /* color */
|
{
|
for(i=0; i<WS2812_NUM; i++)
|
{
|
ws2812b_turn(i, colors[c]);
|
mdelay(300);
|
ws2812b_turn(i, color_off);
|
mdelay(300);
|
}
|
}
|
}
|