以下の内容はhttps://negligible.hatenablog.com/entry/2025/12/25/121703より取得しました。


NeoPixelを光らせる ~ PICマイコン編 ~

はじめに

前回Raspberry PiPythonによるNeoPixelの制御方法を述べました。本記事ではPICマイコンによるNeoPixelの制御方法を書いて参ります。

ウェブやXを検索すると,PICマイコンの中でもCLC (configurable logic cell)を搭載するタイプにて,SPIとCLCを巧く組み合わせる方法が知られていることが分かりました。Microchip社のアプリケーションノートAN1606にもPIC16F1509のCLCを用いたNeoPixelの制御方法が紹介されております*1。この方法では,SPIモジュールが送信している最中にPICマイコンのコアでは別の処理が可能となるというメリットがあります。

本記事ではそのようなスマートな方法ではなく,単純にI/OポートをHigh/Lowに制御するだけという脳みそ筋肉なシンプルな方法でNeoPixelを点灯させるべく試行錯誤した結果をまとめます。また,ボタン電池(CR2032)にてNeoPixel (WS2812B)を点灯できるか否かについても確認した結果を報告します。

完成イメージ

図1にPIC12F1840を用いたNeoPixel実験基板を示します。PIC12F1840はCLCを持っていませんが,1個のNeoPixel (WS2812B)を制御できました。また,WS2812Bのデータシートによると電源電圧(VDD)は+3.7 V ~ +5.3 Vとなっておりますが,それよりも低い電圧(消耗具合や負荷にも拠りますが2.5 ~ 2.8 V程度)しか出ていないボタン電池CR2032でも,さらにはVDDをPICマイコンのI/Oポートから供給しても点灯させることができました。

図1: PIC12F1840を用いたNeoPixel実験基板
PIC12F1840のクロック周波数を8 MHz以上に設定していれば,正常に点灯できるようです。

目次

まずは実験基板を作る

ウェブやXを検索してみると,ボタン電池でNeoPixel (WS2812B)を点灯制御できること自体は見通しがついたので*2,個人的8ピン最強PICマイコンPIC12F1840を用いた実験基板を設計・製作しました。図2に回路図を示します。

図2: PIC12F1840を用いた実験基板の回路図
ボタン電池CR2032にて電源を供給しますが,NeoPixelに対してはJ1のショートピンにて,ボタン電池から直接電源を供給するか,PICマイコンのI/OポートRA2を電源代わりにするかを選択できるようにしました。PICマイコンのI/Oポートの電流駆動能力が比較的大きい(何と25 mAもあります)ためにできる芸当ですね。

PIC12F1840のRA0にはタクトスイッチ,RA4にはデバッグ用の普通のLED(電流制限抵抗R1 = 2 kΩ),RA5はNeoPixelのDIN端子にそれぞれ接続しております。図示を省略していますが,PIC12F1840のICSP (in-circuit serial programming)用のピンをピンヘッダに引き出し,MPLAB SNAPをつなげられるようにしています。なお,タクトスイッチは2025/10/13の記事と同様に,押すとPICマイコンをスリープさせ,もう1度押すと復帰させるために設けております(ただし,本記事ではスリープ・復帰の方法やプログラムについては割愛します)。

シンプルな自作ライブラリ

MPLAB X IDE 6.25にてプログラムを書きましたが,その際に以下のブログ記事を参考にさせて頂きました。 www.soiyasoiya.com MPLABではC言語を用いるため,前回のようにオブジェクト指向とは参りませんが,次のように自作ライブラリを作りました。なお,このライブラリを使用するには,NeoPixelのDIN端子に接続するPICマイコンのI/OポートにMPLAB X IDEにて「NEOPIXEL」と名前(custom name)を付ける必要があります。

NeoPixel.h

ヘッダファイルは下記となっています。これはMPLAB X IDEによって自動生成されたソースコードに加筆したものとなります。

/*
 * NeoPixel.h - Library for controlling NeoPixel by PIC12F microcontroller
 * Make sure that the PIC microcontroller running at 8 MHz or above
 * (c) 2025 @RR_Inyo
 * Released under the MIT license
 * https://opensource.org/licenses/mit-license.php
 */

#ifndef NEOPIXEL_H
#define    NEOPIXEL_H

#ifdef __cplusplus
extern "C" {
#endif

void NEOPIXEL_Send_0(void);
void NEOPIXEL_Send_1(void);
void NEOPIXEL_RGB(uint8_t r, uint8_t g, uint8_t b);

#ifdef __cplusplus
}
#endif

#endif  /* NEOPIXEL_H */

NeoPixel.c

Cソースファイルは下記のように書きました。まだ1個のNeoPixelにしか対応しておりませんが,NEOPIXEL_RGB関数を連続で呼ぶことによってデイジーチェーンされている複数のNeoPixelも点灯制御できるものと考えます(テストした場合は本記事をアップデートします)。

/*
 * NeoPixel.c - Library for controlling NeoPixel by PIC12F microcontroller
 * Make sure that the PIC microcontroller running at 8 MHz or above
 * (c) 2025 @RR_Inyo
 * Released under the MIT license
 * https://opensource.org/licenses/mit-license.php
 */

#include "mcc_generated_files/system/system.h"
#include "NeoPixel.h"

// A function to send symbol 0
void NEOPIXEL_Send_0(void) {
    NEOPIXEL_SetHigh();
    NEOPIXEL_SetLow();
}

// A function to send symbol 1
void NEOPIXEL_Send_1(void) {
    NEOPIXEL_SetHigh();
    NOP();
    NOP();
    NOP();
    NEOPIXEL_SetLow();
}

// A function to send the frame for a single NeoPixel full-color LED
void NEOPIXEL_RGB(uint8_t r, uint8_t g, uint8_t b) {
    // Send green brightness to NeoPixel
    if (g & 0b10000000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (g & 0b01000000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (g & 0b00100000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (g & 0b00010000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (g & 0b00001000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (g & 0b00000100) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (g & 0b00000010) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (g & 0b00000001) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }

    // Send red brightness to NeoPixel
    if (r & 0b10000000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (r & 0b01000000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (r & 0b00100000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (r & 0b00010000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (r & 0b00001000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (r & 0b00000100) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (r & 0b00000010) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (r & 0b00000001) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    
    // Send blue brightness to NeoPixel
    if (b & 0b10000000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (b & 0b01000000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (b & 0b00100000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (b & 0b00010000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (b & 0b00001000) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (b & 0b00000100) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (b & 0b00000010) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
    if (b & 0b00000001) { NEOPIXEL_Send_1(); } else { NEOPIXEL_Send_0(); }
}

前回も述べたように,NeoPixelのシリアル通信ではパルス幅をサブマイクロ秒で制御する必要があります。符号「0」を送るNEOPIXEL_Send_0関数では,NEOPIXEL_SetHigh()とNEOPIXEL_SetLow()を続けて呼び,できるだけ短いパルス幅とします。また,符号「1」を送るNEOPIXEL_Send_1関数では,NEOPIXEL_SetHigh()とNEOPIXEL_SetLow()の呼び出しの間にNOP()を3つ入れ,パルス幅を長くしております。

一方,信号をLowにする時間は明示的にはコントロールしておりません(💧)。これは各ビットを判定するためのif文(NEOPIXEL_RGB関数内)が処理される時間によって確保されることを期待しております。

NEOPIXEL_RGB関数では,G, R, Bの輝度について上位ビットから下位ビットに向かってスキャンしながらNEOPIXEL_Send_0関数またはNEOPIXEL_Send_1関数を呼び出します。if文の処理などにも時間を要するため,パルス幅はWS2812Bのデータシートに記載されている値(前回の図5参照)を逸脱している可能性はありますが,点灯制御はできているようです。

実験結果

実験用プログラム

この自作ライブラリを使って実験用のプログラムを作ります。まずはNeoPixelを赤,緑,青にそれぞれ1秒ずつ点灯するプログラムを書いてみます。まず,MPLAB X IDEのMCCにて,図2のようにPIC12F1840のピンを設定します。また,図3に示すようにクロック周波数を8 MHzとします。

図2: PIC12F1840のピン設定
図3: MPLAB X IDEのMCCによるクロック周波数の設定(8 MHzの例)
ピンとクロックの設定が完了したら,MCCからコードを生成し,main.cを書いていきます。

#include "mcc_generated_files/system/system.h"
#include "NeoPixel.h"

/*
    Main application
*/

int main(void)
{
    SYSTEM_Initialize();
   
    (中略)

    // Give power to NeoPixel through RA2 (POW)
    POW_SetHigh();
    
    while(1)
    {
        // Red
        NEOPIXEL_RGB(0x55, 0, 0);
        __delay_ms(1000);
        
        // Green
        NEOPIXEL_RGB(0, 0x55, 0);
        __delay_ms(1000);
        
        // Blue
        NEOPIXEL_RGB(0, 0, 0x55);
        __delay_ms(1000);
    }    
}

2行目にてNeoPixel.hをインクルードして自作ライブラリを読み込みます。15行目にてRA2にHighを出力し,NeoPixelに電源を供給します。main関数内の無限ループ(17行目のwhile(1)以下)にて,NeoPixelを赤,緑,青に順次点灯させます。輝度は最大(255 = 0xff)ではなく,パルスの波形が分かり易くなるように,85 (= 0x55 = 0b01010101)としました。__delay_ms(1000)にてそれぞれの色を点灯させた後に1秒間待機します。非常にシンプルなプログラムですね。

これをMPLAB SNAPにてPIC12F1840に書き込むと,NeoPixelが赤,緑,青に順次点灯します。

波形の測定

前節で述べたプログラムをICSPでPICマイコンに書き込み,波形を測定してみました。図4に測定風景を示します。オシロスコープは15年ほど前に秋月電子通商にて購入したOWON PDS5022Sですが,今でも活躍しております。USBケーブル(Aオス-Aオス💧)を介して数値データも画面の画像もパソコンに取り込めるので(Windows 11でもOWONのソフトウェアが動作!),まだしばらくは重宝しそうです。

図4: オシロスコープ(OWON PDS5022S)による測定風景

クロック周波数を8 MHzとした場合

図5にPIC12F1840のクロック周波数を8 MHzとした場合の波形を示します。短いパルスと長いパルスがそれぞれ符号「0」と符号「1」のHighに相当します。

図5: PIC12F18340のクロック周波数を8 MHzとした場合におけるNeoPixelに送出する波形
符号「0」のHighは500 ns,Lowは何と6 μsであり,符号「1」のHighは2.5 μs,Lowは何と7.5 μsです。PICマイコンは4クロックで1命令を実行するので,8 MHzでは125 ns × 4 clock = 500 ns毎に命令を実行します。このため,符号「0」のHighの時間は妥当と思われます。符号「1」のHighは理論的には2 μsとなりそうですが,実際は2.5 μsとなりました。この原因についてはアセンブリのコードなどを見ないと確認は難しそうですね。

さて,これらのパルス幅はWS2812Bのデータシートに記載の範囲を大幅に逸脱しております──にも関わらず,正常に点灯制御できておりますので,WS2812Bが「0」または「1」として認識するパルス幅にはかなりの余裕がある,または流れてきた信号のパルス幅を測ってから短パルスと長パルスを判別しているなど,正常に点灯制御できる理由があるのではと推測します。特にLowの時間はとても長く(上記のようにif文が処理される時間となっています)なっていますが,それでもWS2812Bのデータシートに記載のリセット時間280 μsよりは十分に短いために,通信が継続できているものと推測します。いずれにしても,なかなかおもしろい結果が得られました。

クロック周波数を32 MHzとした場合

図7にPIC12F1840のクロック周辺ブロック図を示します(PIC12F1840のデータシートのFigure 5-1より引用・一部改変)。PIC12F1840では内部にクロック周波数を4倍に上げるPLL (phase-locked loop)を内蔵しており,内部クロックを8 MHzに分周した信号に繋がっています。したがって,8 MHzを32 MHzに上げる効果があり,これを利用することで最大で32 MHzで動作させることができます。(16 → 64 MHzにできたら面白そうなのですが,実際は8 → 32 MHzしかできないようです)。

図6: PIC12F1840のクロック周辺ブロック図(PIC12F1840のデータシートより引用・一部改変)
32 MHzに設定すると,より高速にパルスを制御できそうなので,さっそく試してみました。図7に結果を示します。
図7: PIC12F18340のクロック周波数を32 MHzとした場合におけるNeoPixelに送出する波形
符号「0」のHighは150 ns,Lowは1.5 μsであり,符号「1」のHighは650 ns,Lowは1.85 μsです。PICマイコンは4クロックで1命令を実行するので,32 MHzでは31.25 ns × 4 clock = 125 ns毎に命令を実行します。このため,符号「0」のHighの時間は概ね妥当と思われます(オシロスコープの画面上で波形が少しなまっているため,測定誤差とも考えられます。時間軸をもっと拡大すれば分かったかも…)。NOP()をひとつ入れて,300 ns程度に延ばすとデータシートの範囲に収まりそうですね。一方,符号「1」のHighは理論的には500 nsとなりそうですが,実際は650 nsとなりました。やはりこの原因についてはアセンブリのコードなどを見ないと確認は難しそうですね。

パルス幅の比較

表1にWS2812Bのデータシートに記載されているパルス幅の範囲と,上記の実験にて得られたパルス幅をまとめます。パルス幅としてはクロック周波数を32 MHzとした場合の方が8 MHzとした場合に比較してWS2812Bのデータシートに記載の範囲に近くなります。しかし,ボタン電池の消費を抑えるためにはなるべく低いクロック周波数とすることが有効と考えられます。このため,ひとまずこの実験基板は8 MHzで動かすこととしました。

表1: 符号「0」と符号「1」のパルス幅

符号HIGHLOW
データシート8 MHz32 MHzデータシート8 MHz32 MHz
“0”220 ~ 380 ns500 ns150 ns580 ns ~ 1 μs6 μs1.5 μs
“1”580 ns ~ 1 μs2.5 μs650 ns580 ns ~ 1 μs7.5 μs1.85 μs

一応,16 MHzの場合も実験してみましたが,概ね8 MHzと32 MHzから考えられる値となりますので割愛します。

まとめ

本記事では,CLCを持たないPICマイコン(PIC12F1840)にて,直接I/Oポートを制御する方法でNeoPixelを点灯制御する方法について述べました。PIC12F1840を用いた実験基板を作成し,PICマイコンのクロック周波数を8 MHzとした場合と32 MHzとした場合においてNeoPixelのDIN端子に与える信号のパルス幅を測定しました。パルス幅はWS2812Bのデータシートに記載のパルス幅の範囲を逸脱しますが,それでもNeoPixelを正常に点灯制御できることを確認しました。また,WS2812Bのデータシートにおける電源電圧の範囲は+3.7 ~ +5.3 Vですが,3 V出力のボタン電池CR2032(消費具合によりますが実際は2.5 ~ 2.8 V程度)を用いても,またPICマイコンのI/Oポートを電源代わりにしても,正常にNeoPixelを点灯できることが確認できました。

NeoPixelは電流制限抵抗不要,複数のNeoPixelをデイジーチェーン可能で,PICマイコンからは信号1本だけで制御可能,2個で50円と比較的安価であるといったメリットがありますので,今後の電子工作では便利に使える発光デバイスではないかと考えます。

*1:AN1606英文版

*2:電源電圧が高いWS2815BやWS2813BではDC-DCコンバータを要すると考えます。




以上の内容はhttps://negligible.hatenablog.com/entry/2025/12/25/121703より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14