NucleoF103でNeoPixel制御


はじめに

前回の投稿から6年ほど経過した。
https://qiita.com/gkmaro634/items/7b2ce4f72615c96f41d3

その間STM32の開発環境も様変わりしているようで、時代に追いつくために今風の開発環境で何か試そうと思い立った。
前回の記事で取り上げたCubeMXがIDEとして取り込まれたCubeIDEというものがSTMicroからリリースされていたようで、今回こちらを使うこととする。インストール方法はネット上にたくさん情報があり、それらを参考にした。

単なるLチカでは物足りないので色々物色していたところテープLEDなるものが目に止まり、こちらの制御にトライすることにした。一筋縄では行かなかったのでその経緯を記事に残すこととした。

今回使用したもの

NeoPixelの制御

NeoPixelは素のSPIやI2Cでは制御できない独特な制御仕様となっている。
SPIを使ってNeoPixelへのHigh/Lowの制御信号を複数bitで表現する方法が紹介されており、今回はSPIでの制御を採用した。
https://www.newinnovations.nl/post/controlling-ws2812-and-ws2812b-using-only-stm32-spi/
https://blog.handen.net/archives/19087339.html

PWMを使って制御する方法もあるようだが、まだ試せていない。
https://qiita.com/kanehara/items/0e7776c225f83a6b4152

ピンアサイン、クロックの設定

今回用いるNeoPixelは電源(5V)、GND, 信号線をマイコンと接続すれば良い。
信号線はPB15(SPI2_MOSI)に接続することとした。

NeoPixelへのHigh/Lowの信号は1.25usの期間でのDuty比で決定される。
データシートに記載のしきい値に対して実際は多少のマージンを持っているようである。
https://qiita.com/mml/items/770bcf8b0f82a6ec9ad1

ひとまずSPI2へのクロックは8MHzとなるようCubeIDEで設定することとした。
NeoPixelへの制御は
5 / 8MHz = 0.625us
3 / 8MHz = 0.375us
であるので
High: 0b11111000
Low: 0b11100000
とHigh/Lowの制御信号を8bitで表現すればよさそうである。

プログラムの例

メイン関数から呼び出すメソッドを抜粋して記載する。

main.c
#define NEOPIXEL_0      0b11000000
#define NEOPIXEL_1      0b11111100
#define NEOPIXEL_RESET  0b00000000
#define NEOPIXEL_COUNT  30
#define NEOPIXEL_SIGNAL_CLOCK_COUNT 8

typedef struct {
    uint8_t r;
    uint8_t g;
    uint8_t b;
} RGB;

RGB g_neoPixels[NEOPIXEL_COUNT];

void UpdateNeoPixelBuffer(uint32_t pixelIndex, RGB newValue){
    if (pixelIndex >= NEOPIXEL_COUNT){
        return;
    }

    g_neoPixels[pixelIndex].r = newValue.r;
    g_neoPixels[pixelIndex].g = newValue.g;
    g_neoPixels[pixelIndex].b = newValue.b;
}

void TurnOffNeoPixelBuffer(uint32_t pixelIndex){
    if (pixelIndex >= NEOPIXEL_COUNT){
        return;
    }

    g_neoPixels[pixelIndex].r = 0;
    g_neoPixels[pixelIndex].g = 0;
    g_neoPixels[pixelIndex].b = 0;
}

void ResetNeoPixel(){
    uint8_t txValue = NEOPIXEL_RESET;
    uint32_t i = 0;

    // SPI Clock: 8MHz
    // Send Low >50us
    // 8 * 50 / 1e-6 = 400
    for (i = 0; i < 512; i++){
        HAL_SPI_Transmit(&hspi2, &txValue, 1, 100);
    }
}

void UpdateNeoPixel(){
    uint32_t i, j = 0;
    uint8_t txValue;

    ResetNeoPixel();

    for (i=0; i < NEOPIXEL_COUNT; i++){
        for (j=0; j<NEOPIXEL_SIGNAL_CLOCK_COUNT; j++){
            txValue = g_neoPixels[i].g & 0x80 >> j ? NEOPIXEL_1 : NEOPIXEL_0;
            HAL_SPI_Transmit(&hspi2, &txValue, 1, 100);
        }

        for (j=0; j<NEOPIXEL_SIGNAL_CLOCK_COUNT; j++){
            txValue = g_neoPixels[i].r & 0x80 >> j ? NEOPIXEL_1 : NEOPIXEL_0;
            HAL_SPI_Transmit(&hspi2, &txValue, 1, 100);
        }

        for (j=0; j<NEOPIXEL_SIGNAL_CLOCK_COUNT; j++){
            txValue = g_neoPixels[i].b & 0x80 >> j ? NEOPIXEL_1 : NEOPIXEL_0;
            HAL_SPI_Transmit(&hspi2, &txValue, 1, 100);
        }
    }
}