WS2812 是一種集成了控制器的全彩LED, 常見單體尺寸為50mm * 50mm, 4個PIN, 分別是 VCC, GND, DIN, DOUT, 工作電壓3.7V-5.3V, 電流16mA. 市面上出售的大都是製作成條狀, 環狀或矩陣的成品. 供電電壓有5V和12V兩種, 前者因為電壓低, 如... ...
目錄
- 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU簡介
- 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode開發環境
- 普冉PY32系列(三) PY32F002A資源實測 - 這個型號不簡單
- 普冉PY32系列(四) PY32F002A/003/030的時鐘設置
- 普冉PY32系列(五) 使用JLink RTT代替串口輸出日誌
- 普冉PY32系列(六) 通過I2C介面驅動PCF8574擴展的1602LCD
- 普冉PY32系列(七) SOP8,SOP10,SOP16封裝的PY32F002A/PY32F003管腳復用
- 普冉PY32系列(八) GPIO模擬和硬體SPI方式驅動無線收發晶元XN297LBW
- 普冉PY32系列(九) GPIO模擬和硬體SPI方式驅動無線收發晶元XL2400
- 普冉PY32系列(十) 基於PY32F002A的6+1通道遙控小車I - 綜述篇
- 普冉PY32系列(十一) 基於PY32F002A的6+1通道遙控小車II - 控制篇
- 普冉PY32系列(十二) 基於PY32F002A的6+1通道遙控小車III - 驅動篇
- 普冉PY32系列(十三) SPI驅動WS2812全彩LED
WS2812/WS2812B
WS2812 是一種集成了控制器的全彩LED, 常見單體尺寸為50mm * 50mm, 4個PIN, 分別是 VCC, GND, DIN, DOUT, 工作電壓3.7V-5.3V, 電流16mA. 市面上出售的大都是製作成帶狀, 環狀或矩陣的成品. 供電電壓有5V和12V兩種, 前者因為電壓低, 如果長度較長, 每隔兩三百顆需要外接電源補電.
WS2812的特點就是全彩並且是單線串列介面, 只需要一個IO就可以對彩燈實現全部控制.
介面通信格式
WS2812/WS2812B LED 使用 24 bit 數據調節 RGB 色彩, 每個 bit 都是通過(一個高電平 + 一個低電平)表示的.
根據手冊
- 0 表示為一個短的(0.35 µs)高電平加一個長的(0.90 µs)低電平
- 1 表示為一個長的(0.90 µs)高電平加一個短的(0.35 µs)低電平
- 單個 bit 信號周期, 高低電平時長合計為 1.25 µs
- 發送超過 24 bit 信號後, 之前輸入的信號會依次傳遞給串列的下一個 WS2812 LED
- 控制器發送數據前需要保持低電平超過 50 µs(又稱為 RESET), 用於通知 WS2812 開始接收數據.
根據上面的信息, 對單顆LED發送數據, 需要的時間為
\(24 × 1.25 µs + 50 µs = 80 µs\)
對於8顆LED, 需要的時間為
\(8 × 24 × 1.25 µs + 50 µs = 290 µs\)
實際的通信時間間隔要求
當傳輸信號時, 高低電平時間間隔如果不符合手冊要求, 差距較大時LED會不工作(不亮), 在間隔接近但是不完全滿足時, LED會出現顯示錯亂, 色彩亂跳等.
Tim “cpldcpu” 做過一系列實驗 https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ 驗證過時間間隔的邊界, 發現這些要求實際上相當寬鬆:
- 觸發RESET只需要 9 µs (比手冊要求的 50 µs 小很多)
- 一個 bit 的周期至少需要 1.25 µs, 但是不能超過 9 µs, 因為這樣容易觸發RESET
- 0 的高電平時間, 手冊要求是 0.35 µs, 實際上可以短至 62.5 ns , 但是不能長於 0.50 µs
- 1 的高電平時間, 手冊要求是 0.65 µs, 實際上長度可以幾乎跨越整個 bit 周期, 但是不能短於 0.625 µs
SPI驅動時的bit數選擇
對於輸出固定長度的電平組合, SPI是最簡單的方式. 可以使用 SPI, 通過控制其中的數據值與 WS2812 通信, 而時間間隔控制則需要通過控制 SPI 的時鐘以及每次發送的 bit 數量實現, 根據Controlling WS2812(B) leds using STM32 HAL SPI 的計算, 通過對比多種 bit 數的時間要求, 發現使用 bit 數越多, 相容性越好, MCU越容易實現. 因此可以使用預設的 8bit SPI 通信.
對於 PY32F002A/PY32F003/PY32F030, 因為最高頻率是48MHz, 所以當SPI分頻為8, 16時, 分別對應 6MHz, 3MHz, 在工作範圍內; 對於 PY32F040/PY32F071/PY32F072, 最高頻率是72MHz, 當SPI分頻為8, 16, 32時, 分別對應 9MHz, 4.5MHz, 2.25MHz, 都在工作範圍內.
SPI 驅動 WS2812
對應不同的LED數量, 需要調整下麵代碼中WS2812_NUM_LEDS
的值, 這裡使用的是一個8x8的點陣, 因此設為64. 註意這個ws2812_buffer
實際上非常占記憶體, 對於數量超過64的LED燈帶(矩陣), 需要考慮其它的實現.
頭文件 ws2812_spi.h
#include "main.h"
#define WS2812_NUM_LEDS 64
#define WS2812_SPI_HANDLE Spi1Handle
#define WS2812_RESET_PULSE 60
#define WS2812_BUFFER_SIZE (WS2812_NUM_LEDS * 24 + WS2812_RESET_PULSE)
extern SPI_HandleTypeDef WS2812_SPI_HANDLE;
extern uint8_t ws2812_buffer[];
void ws2812_init(void);
void ws2812_send_spi(void);
void ws2812_pixel(uint16_t led_no, uint8_t r, uint8_t g, uint8_t b);
void ws2812_pixel_all(uint8_t r, uint8_t g, uint8_t b);
函數實現 ws2812_spi.c
#include <string.h>
#include "ws2812_spi.h"
#define WS2812_FILL_BUFFER(COLOR) \
for( uint8_t mask = 0x80; mask; mask >>= 1 ) { \
if( COLOR & mask ) { *ptr++ = 0xfc; } \
else { *ptr++ = 0x80; }}
uint8_t ws2812_buffer[WS2812_BUFFER_SIZE];
void ws2812_init(void) {
memset(ws2812_buffer, 0, WS2812_BUFFER_SIZE);
ws2812_send_spi();
}
void ws2812_send_spi(void) {
HAL_SPI_Transmit(&WS2812_SPI_HANDLE, ws2812_buffer, WS2812_BUFFER_SIZE, HAL_MAX_DELAY);
}
void ws2812_pixel(uint16_t led_no, uint8_t r, uint8_t g, uint8_t b) {
uint8_t * ptr = &ws2812_buffer[24 * led_no];
WS2812_FILL_BUFFER(g);
WS2812_FILL_BUFFER(r);
WS2812_FILL_BUFFER(b);
}
void ws2812_pixel_all(uint8_t r, uint8_t g, uint8_t b) {
uint8_t * ptr = ws2812_buffer;
for( uint16_t i = 0; i < WS2812_NUM_LEDS; ++i) {
WS2812_FILL_BUFFER(g);
WS2812_FILL_BUFFER(r);
WS2812_FILL_BUFFER(b);
}
}
使用 PY32F0 驅動 WS2812
具體的SPI初始化可以參考文章結尾的示例代碼, 根據各自環境的工作頻率不同, 需要控制SPI的時鐘周期在工作範圍之內
對於開啟PLL運行在48MHz的PY32F002A/003/030, 使用8分頻
static void APP_SPIConfig(void)
{
LL_SPI_InitTypeDef SPI_InitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
//...
/* The frequency after prescaler should be below 8.25MHz */
SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8;
//...
}
對於 PY32F040/071/072, 工作在HSI 24MHz, 使用4分頻
static void APP_SPI_Config(void)
{
Spi1Handle.Instance = SPI1;
/* The frequency after prescale should be below 8.25MHz */
Spi1Handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
//...
}
示例代碼中, 通過迴圈依次修改像素點的RGB值演示LED全彩效果
ws2812_pixel_all(r, g, b);
ws2812_send_spi();
while (1)
{
i = (i + 1) % WS2812_NUM_LEDS;
ws2812_pixel(i, r++, g++, b++);
ws2812_send_spi();
LL_mDelay(20);
}
完整的示例代碼通過以下鏈接查看
- PY32F002A/PY32F003/PY32F030 https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F0xx/LL/SPI/WS2812_LED
- PY32F040/PY32F071/PY32F072 https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F07x/HAL/SPI/WS2812_LED
註意事項
要註意自己使用的 WS2812 的供電電壓是 5V 還是 12V, 不要和 PY32F0 的供電混在一起. WS2812 數量多了之後電流是很大的, 對 5V 8x8 的矩陣實測工作電流在 500mA 以上, 如果是 16x16 的矩陣, 電流會超過 2A. 這麼大的電流最好單獨供電.