Site icon rpine lab Tech Blog

趣味のプログラミング(Web系・バックエンド)や自宅ラボ(Homelab)構築・マイコンを使った電子工作などを雑多に扱った技術ブログです。

🚦 CH32V003マイコンでWS2812B RGB LED (NeoPixel)を動かす

はじめに

CH32V003マイコンを使ってRGB LEDであるWS2812B(通称NeoPixel)を制御してみます。

MounRiver Studio Ⅱ(公式IDE)+公式SDKを利用し、LED制御にはコミュニティライブラリ ch32funのWS2812B制御用ライブラリー(SPI+DMA利用)を利用します。

利用機材

  • マイコン:CH32V003F4P6(SSOP20ピン)(秋月電子AliExpress
  • デバッガー・書き込み機:WCH-LinkE(秋月電子AliExpress
  • RGB LED:WeAct Studio製 WS2812B搭載基板×1
    扱いやすい1チップ搭載の基板タイプを使用

開発環境

  • IDE:MounRiver Studio Ⅱ(VSCodeベースの公式開発環境)
  • SDK:公式SDK(MRS2同梱)

配線

マイコン内臓のSPIペリフェラルを利用してLED制御を行うため、制御信号の出力にはSPIのMOSIピン(PC6)を利用します。

接続方法:

  • 制御信号入力(DIN)→MOSI PC6(16番ピン)
  • 電源→5V電源(WCH-LinkEより)
  • GND→GND
CH32V003F4P6ピン配置(CH32V003データシートより)
CH32V003F4P6ピン配置(CH32V003データシートより)

これをミニブレッドボード上に実装すると次のようになります。右手前にあるのがRGB LEDのモジュールです。

実際の配線の様子
実際の配線の様子

NeoPixelの制御信号について

NeoPixel系LEDは独自のタイミング信号で色データを受信します。データの0/1をHigh/Low信号の長さで表現されます。

0/1/Reset信号タイミング図(WS2812B データシートより)
0/1/Reset信号タイミング図(WS2812B データシートより)
タイミング信号の図に対応する時間表(WS2812B データシートより)
タイミング信号の図に対応する時間表(WS2812B データシートより)

最小で400 ns(2.5 MHz)のHigh信号を送る必要があるためタイミングの制約が厳しく、マイコンでこの信号を出すためには特殊な実装が必要となります。

マイコンでNeoPixel系LEDを制御する方法には大きく2パターンあります。

  1. GPIOの直接制御(BitBang方式):NOP命令でタイミング調整してプログラム的にGPIOをオンオフ操作する
    • 任意のGPIOピンが利用できる
    • 0.1 μs オーダーなのでDelay関数が基本使えず、NOP命令の回数によるタイミング微調整が必要となる(オシロやロジアナの出番)
    • プログラム的に操作するので信号を送っている間はCPUを占有する。タイミングがズレてしまうのでその間割り込みは使えない
  2. マイコンの内蔵ペリフェラル(タイマーやSPI)を利用

    SPIの場合は必要な信号が出力されるようにビットを並べた特殊な出力データを用意し、DMA(Direct Memory Access)機能を利用してペリフェラルのデータレジスターに逐次転送することで信号を生成する。

    • 信号生成をペリフェラルにオフロードするのでCPUを占有しない
    • ペリフェラルの出力に対応する特定のピンのみ利用可能
    • DMAがほぼ必須なため実装が複雑になる。DMAを使わないとプログラムでデータを逐次入れる必要があり利点が薄れる。

今回は後者の、CH32V003マイコンのSPIペリフェラルとDMA機能を利用して制御信号を生成する手法を採用します。

幸いライブラリーとしてCH32VのオープンソースSDKであるch32funWS2812B制御ライブラリーがあるので、これを流用します。

NeoPixel LED制御ライブラリーの準備

このリンクws2812b_dma_spi_led_driver.hをプロジェクトのUserフォルダーに配置します。このライブラリーは単一のヘッダーファイルで構成されており、main.cなどでインクルードして利用します。

公式SDKとch32funでは一部レジスターの定義が異なっているみたいなので、コードの次の箇所を変更する必要があります。

233行目
< 	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*6);
---
> 	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_Mode_AF_PP)<<(4*6);
244行目
< 	SPI1->CTLR1 |= CTLR1_SPE_Set;
---
> 	SPI1->CTLR1 |= SPI_CTLR1_SPE;
User/ws2812b_dma_spi_led_driver.h 変更箇所

プログラム

以下のサンプルプログラムを見つつ、動作確認用のプログラムを作成します。

ライブラリーのインクルード

このライブラリーを使う場所(main関数から使う場合はmain.c)の上の方で次のように書いてライブラリーのヘッダーファイルをインクルードします。

#include "debug.h" // 元のこのインクルード文の下に書く

#define WS2812DMA_IMPLEMENTATION
#define WSGRB // WS2812Bの場合(RGBデータの送信順)

#include "ws2812b_dma_spi_led_driver.h"
User/main.c ライブラリのインクルード

LED色設定用コールバック関数

ライブラリー内の割り込み関数から呼ばれるコールバック関数WS2812BLEDCallbackを追加します。この関数では、LEDの位置lednoを受け取り、RGBの順に下位ビットから8bitずつ色の値が入った24bitの数値を返すように実装します。

今回は複雑なことをせずに全てのLEDで単一のグローバル変数ledValを返すだけにします。このグローバル変数は次に書くmain関数から書き換えるようにします。

static volatile uint32_t ledVal = 0; // 色の設定値(グローバル変数)

// 色設定用のコールバック関数
uint32_t WS2812BLEDCallback(int ledno)
{
    return ledVal; // 24bitのRGB値を返す
}
User/main.c コールバック関数の実装

メイン処理

メイン処理をmain関数に書きます。WS2812BDMAInit関数で初期化して、WS2812BDMAStart関数を呼び出すことでLEDがledValで設定した色に光ります。

デバッグ機能を利用する前提なので、不要であればprintfの記述を消してください。

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
#if (SDI_PRINT == SDI_PR_OPEN)
    SDI_Printf_Enable();
#else
    USART_Printf_Init(115200);
#endif
    printf("SystemClk:%d\r\n",SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    // 初期化関数
    WS2812BDMAInit();

    // 点灯パターン
    static const uint32_t ledPatterns[] = {
        0x00000000, // Off
        0x000000FF, // Red
        0x0000FF00, // Green
        0x00FF0000, // Blue
        0x0000FFFF, // R + G = Yellow
        0x00FFFF00, // G + B = Cyan
        0x00FF00FF, // R + B = Magenta
    };

    // 各色を順番に表示
    for (unsigned int i = 0; i < sizeof(ledPatterns) / sizeof(ledPatterns[0]); i++) {
        // グローバル変数に色の値を代入
        ledVal = ledPatterns[i];
        
        // デバッグ出力
        printf("LED val: %08X\r\n", ledVal);
        
        // DMA転送スタート(1=LED数)
        WS2812BDMAStart(1);

        // 待機
        Delay_Ms(500);
    }
}
User/main.c メイン処理

動作確認

このプログラムを動作した際の様子です。(全部0xFFで最大に光らせているので眩しいです)

プログラムの実行により、赤→緑→青→黄→シアン→マゼンタの順番に点灯しているのが分かると思います。

動作確認プログラムを走らせている様子
動作確認プログラムを走らせている様子

まとめ

CH32V003マイコンを使ってWS2312B LEDを制御することができました。今回は1つしかLEDを繋いでいませんが、このライブラリーを使えばLEDテープなどの複数LEDも制御可能です。

このマイコンで何を作ろうかはまだ思いついていないですが、次はI2Cあたりを使ってみたいと思います。

参考サイト