普冉PY32系列(十三) SPI驅動WS2812全綵LED

2023-12-10 18:02:10

目錄

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);
  }

完整的範例程式碼通過以下連結檢視

注意事項

要注意自己使用的 WS2812 的供電電壓是 5V 還是 12V, 不要和 PY32F0 的供電混在一起. WS2812 數量多了之後電流是很大的, 對 5V 8x8 的矩陣實測工作電流在 500mA 以上, 如果是 16x16 的矩陣, 電流會超過 2A. 這麼大的電流最好單獨供電.

參考