はじめに
前回はRaspberry PiのPythonによる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ポートから供給しても点灯させることができました。

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

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とします。


#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のソフトウェアが動作!),まだしばらくは重宝しそうです。

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

さて,これらのパルス幅は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しかできないようです)。


NOP()をひとつ入れて,300 ns程度に延ばすとデータシートの範囲に収まりそうですね。一方,符号「1」のHighは理論的には500 nsとなりそうですが,実際は650 nsとなりました。やはりこの原因についてはアセンブリのコードなどを見ないと確認は難しそうですね。
パルス幅の比較
表1にWS2812Bのデータシートに記載されているパルス幅の範囲と,上記の実験にて得られたパルス幅をまとめます。パルス幅としてはクロック周波数を32 MHzとした場合の方が8 MHzとした場合に比較してWS2812Bのデータシートに記載の範囲に近くなります。しかし,ボタン電池の消費を抑えるためにはなるべく低いクロック周波数とすることが有効と考えられます。このため,ひとまずこの実験基板は8 MHzで動かすこととしました。
| 符号 | HIGH | LOW | ||||
|---|---|---|---|---|---|---|
| データシート | 8 MHz | 32 MHz | データシート | 8 MHz | 32 MHz | |
| “0” | 220 ~ 380 ns | 500 ns | 150 ns | 580 ns ~ 1 μs | 6 μs | 1.5 μs |
| “1” | 580 ns ~ 1 μs | 2.5 μs | 650 ns | 580 ns ~ 1 μs | 7.5 μs | 1.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円と比較的安価であるといったメリットがありますので,今後の電子工作では便利に使える発光デバイスではないかと考えます。