はじめに
前回,PICマイコンによるポータブル気圧計をプリント基板化し,その際に,普通のLEDの代わりにNeoPixelを用いました。その下準備として,筆者はRaspberry PiとPICマイコンのそれぞれによるNeoPixelの制御方法(光らせ方)をいろいろと試行錯誤しました。本記事ではまずRaspberry Pi(pigpioライブラリとPython)を用いたNeoPixelの制御方法について書いて参ります。PICマイコンについては次回に委ねたいと存じます。
図2に完成イメージとしてRaspberry Pi Zero 2 Wにて12個のNeoPixel (WS2812B)を制御した様子を示します。

目次
NeoPixelとは
普通のフルカラーLEDとNeoPixel
フルカラーLEDとは,1つのパッケージに赤(R),緑(G),青(B)の3色のLEDを集積した発光デバイスです。例えばPWM (pulse-width modulation)によって(R, G, B)の輝度の割合を変えることで,様々な色を表現できます。図2は,筆者がPICマイコンを久しぶりに使ってみた際の練習として作った,フルカラーLEDをPIC12F1840で制御する小さな基板です。

一方,中国のWorldSemi社は2010年代前半あたりからNeoPixelと呼ばれるフルカラーLEDを発売しています。NeoPixelはR, G, Bの3色のLEDと同じパッケージ内に小さなマイコンを内蔵しており,シリアル通信(専用プロトコル)にて(R, G, B)の輝度をそれぞれ8 bitの分解能で設定できるという特長があります。さらに,いくつものNeoPixelをデイジーチェーン接続することが可能です。電飾をはじめとする各種の電子工作,多色表示を活用したインジケータ,そしてもちろんフルカラーLEDパネルなどの様々な製品にも近年広く使われています。しかも,秋月電子通商では2個で50円という安価で入手可能です。また,図2の普通のフルカラーLEDで必要だった電流制限抵抗も不要です。
NeoPixelのシリアル通信とその難しさ
図3にNeoPixelの1種,WS2812B*1のデータシートから引用したピン配置を示します。1番ピンはVDD,2番ピンはDOUT,すなわちデイジーチェーンに用いるデータの出力端子,3番ピンはVSS (=GND),4番ピンはDIN,すなわちデータの入力端子です。

NeoPixelをデイジーチェーンした場合の通信は,次のように説明できます。先頭のNeoPixelが最初のG, R, Bの24 bitを受け取った場合,自分の輝度を変更します。先頭のNeoPixelがもう1組の24 bitのデータを受け取ると,自分の輝度を変更することなく,2番目のNeoPixelにそのデータを渡します。先頭のNeoPixelがさらにもう1組の24 bitのデータを受け取ると,自分の輝度を変更することなく,それをそのまま2番目のNeoPixelに渡し,2番目のNeoPixelはやはり自分の輝度を変更することなく,3番目のNeoPixelにデータを渡します。このように,各NeoPixelは受け取ったデータの最初の24 bitを受け取って,それ以降を隣のNeoPixelに順次流していくように通信します。データが流れてこなくなって一定時間(> 280 μs)が経過すると,変更された輝度が実際にLEDに反映されます。


改めて考えてみると,符号「0」の場合に必要となる220 ns ~ 380 nsというパルス幅はなかなか高速ですね。もちろん,100 MHz超で動いているマイコンをベアメタルで制御している場合は大したことはないかもしれませんが,サブマイクロ秒でRaspberry PiのGPIOを,それもPythonから制御しようとすると難しさが生じます。Raspberry PiのGPIO制御ライブラリの決定版(?)とも言えるpigpioライブラリにおいても,パルス生成機能(waveform機能)は1 μs単位でしか制御できません。いったいどうすれば良いのでしょうか…?
SPIのMOSIを利用したNeoPixelの制御
NeoPixelの1 bitをSPIの1 byteで模擬
ウェブやXをいろいろと検索してみると,図6のようにRaspberry PiのSPI (serial peripheral interface)を利用する方法がありそうだと分かりました(図6はNeoPixelが1個の場合です)。しかし,pigpioライブラリを使用する例を見つけられませんでしたので,自分で作ることにしました。


以上の説明は1 bitの送出方法のみを述べておりますが,これを各色8 bitの合計24 bit,またそれをデイジーチェーンされたNeoPixelの個数分だけ繰り返すことによって,各NeoPixelの色や輝度を個別に制御可能です。
1個のNeoPixelを光らせるプログラム
以上を踏まえて,まずは1個のNeoPixelを光らせる最小のプログラムを書いてみると,以下のようになりました。pigpiodデーモンのハンドラをpiとして得てから,7行目で所望の(R, G, B)の輝度をcolというリストに格納します。ここではcol = [127, 0, 96],すなわち赤紫としました。
import pigpio # Get pigpio handle pi = pigpio.pi() # Set color of the NeoPixel in [R, G, B] format col = [127, 0, 96] # Build SPI frame frame = [] CODE_0 = 0b10000000 CODE_1 = 0b11100000 # Green for i in range(8): frame.append(CODE_1 if col[1] & (0b10000000 >> i) != 0 else CODE_0) # Red for i in range(8): frame.append(CODE_1 if col[0] & (0b10000000 >> i) != 0 else CODE_0) # Blue for i in range(8): frame.append(CODE_1 if col[2] & (0b10000000 >> i) != 0 else CODE_0) # Send SPI frame h = pi.spi_open(0, 40000000, 3) pi.spi_write(h, frame) pi.spi_close(h) # Stop pigpio pi.stop()
pigpioライブラリのSPIでは,フレームをリストに格納してから,spi_writeメソッドにてMOSIから送出します。10行目でframeという空のリストを作り,11,12行目でCODE_0,CODE_1という変数にそれぞれ前節で述べた0b10000000と0b11100000を入れておきます。
14行目以降で,リストcolの2つ目の要素col[1](= 緑(G)の輝度)の上位ビットから下位ビットに向かって,それが1であるか0であるかを確認しながら,リストframeにCODE_1またはCODE_0を追加していきます。これを同様に赤(R),青(B)に対しても繰り返します。これによって,1個のNeoPixelに対する24 bitのデータを模擬するSPIのフレームが完成します。
26行目でspi_openメソッドにてSPIのハンドラhを得てから(ボーレートを4 Mbaudに設定する),spi_writeメソッドにてリストframeに格納されたデータをMOSI (GPIO10)から送出します。直後にspi_closeメソッドでSPIの通信を閉じます。最後にpigpiodデーモンへのハンドラを停止(stop)します。

図8に示すように,このプログラムを実行すると1個のNeoPixelを光らせることができます。複数のNeoPixelがデイジーチェーンされている場合,先頭のNeoPixelのみが点灯します。
複数のNeoPixelを光らせたい場合,2番目のNeoPixel以降のG, R, Bの輝度をリストframeに順次appendし,SPIのフレームをどんどん長くすることで対応できそうです。
自作Pythonモジュールのソースコード
以上の原理に基づいたpigpioライブラリを用いたPythonモジュールを作りました。短いですので以下にソースコード全文を示します。このプログラムはモジュールとして他のプログラムからimportすることができます。NeoPixelというクラスを定義しましたので,デイジーチェーンされた複数のNeoPixelの列を1つのオブジェクト(インスタンス)として操作できます。
multiRGBメソッドはcolorsという2次元リスト(リストのリスト)に格納されているそれぞれのNeoPixelの(R, G, B)の輝度を確認して順次SPIのフレームを生成し,MOSIから送出します。
turnOffメソッドは変数numに格納された個数分のNeoPixelに輝度(0, 0, 0)を送出して消灯します。
#------------------------------------------------------- # neopixel.py # A module to control NeoPixel full-color LEDs # (c) 2025 @RR_Inyo # Released under the MIT license # https://opensource.org/licenses/mit-license.php #------------------------------------------------------- import time import pigpio class NeoPixel: # Class variables __SPI_CHANNEL = 0 __BAUD_RATE = 4000000 __SPI_MODE = 3 __CODE_0 = 0b10000000 __CODE_1 = 0b11100000 # Constructor def __init__(self, pi): self.__pi = pi # Destructor def __del__(self): self.__pi.stop() # Send SPI frame to multiple daisy-chained NeoPixels def multiRGB(self, colors): # Create frame data frame = [] for color in colors: # Green for i in range(8): frame.append(NeoPixel.__CODE_1 if color[1] & (0b10000000 >> i) != 0 else NeoPixel.__CODE_0) # Red for i in range(8): frame.append(NeoPixel.__CODE_1 if color[0] & (0b10000000 >> i) != 0 else NeoPixel.__CODE_0) # Blue for i in range(8): frame.append(NeoPixel.__CODE_1 if color[2] & (0b10000000 >> i) != 0 else NeoPixel.__CODE_0) # Send to SPI (MOSI) self.__h = self.__pi.spi_open(0, NeoPixel.__BAUD_RATE, NeoPixel.__SPI_MODE) self.__pi.spi_write(self.__h, frame) self.__pi.spi_close(self.__h) time.sleep(0.001) def turnOff(self, num): dark = [[0, 0, 0]] * num self.multiRGB(dark) # Test bench to control a 12-LED NeoPixel ring if __name__ == '__main__': # Triangular wave def triangle(u): if u < 4: y = u else: y = 8 - u if u <= 8 else 0 return y # Get pigpio handle pi = pigpio.pi() # Get NeoPixel handle neopixel = NeoPixel(pi) # Create color pattern colors = [] for i in range(12): colors.append([triangle(i) * 20, triangle((i + 4) % 12) * 20, triangle((i + 8) % 12) * 20]) try: # Send to 12-LED ring and rotating colors for i in range(1000): neopixel.multiRGB(colors) colors = [colors[-1]] + colors[:-1] time.sleep(0.5 - i / 2000) # End test neopixel.turnOff(12) pi.stop() except: # Close pigpio neopixel.turnOff(12) pi.stop()
このプログラムにはif __name__ == '__main__':以降のテストベンチを設けており,単体で実行した場合は12個のNeoPixelにて虹色を表示し,それを順次回転していくようなプログラムとなります。これを実行した様子が図9となります(図1の再掲)。

このモジュールを外部のプログラムから呼び,3個のNeoPixelをR, G, Bそれぞれの最大輝度で点灯するシンプルなプログラムを作ると下記となります。
import pigpio import neopixel pi = pigpio.pi() led = neopixel.NeoPixel(pi) led.multiRGB([[255, 0, 0], [0, 255, 0], [0, 0, 255]])
これを実行すると,図10のように点灯します。

波形の測定〔追記: 2025-12-25〕
Raspberry Pi Zero 2 WのMOSI (GPIO10)からNeoPixelに流れている波形をオシロスコープ(OWON PDS5022S)にて測定しました。図11に測定波形を示します。

| 符号 | HIGH | LOW | ||
|---|---|---|---|---|
| データシート | 測定結果 | データシート | 測定結果 | |
| “0” | 220 ~ 380 ns | 200 ns | 580 ns ~ 1 μs | 1.1 μs |
| “1” | 580 ns ~ 1 μs | 500 ns | 580 ns ~ 1 μs | 800 ns |
符号「0」については,Highのパルス幅がデータシートより僅かに短く,Lowのパルス幅がデータシートより僅かに長くなっています。符号「1」のついては,Highのパルス幅がデータシートより僅かに短く,Lowのパルス幅がデータシートに合致している状態になっておりました。それぞれの符号に用いたSPIのビット列としては,符号「0」に0b11000000,符号「1」に0b11110000とした方が適切かもしれませんね。もしくはボーレートとセットでの見直しが必要になるかもしれませんね。
まとめ
以上,Raspberry PiのpigpioライブラリとPythonによってNeoPixelを制御(光らせる)方法について述べました。pigpioライブラリでのパルス生成機能(waveform機能)では1 μs単位でしかパルスを制御できませんが,NeoPixelはサブマイクロ秒でのパルス幅制御を必要とします。そこで,ボーレートを適切に設定(本記事では4 Mbaud)したSPIを用いてMOSI (GPIO10)からビット列を送出し,NeoPixelから見て符号「0」や符号「1」に見えるようなパルスを生成することによって,Raspberry PiからNeoPixelを制御できることを説明しました。本記事の内容はpigpioを用いたC言語やC++言語での制御にも応用できると考えます。
NeoPixelと本当のSPIデバイス(例えばカラー液晶モジュールなど)を共存したい場合に問題ないかなど,用途と環境によってはさらなる調査が必要かもしれませんね(チップセレクトがある普通のSPIデバイスであればデバイス側は大丈夫な気がしますが,デバイスに送出したSPIのフレームでNeoPixelが不用意に点灯することがあるかもしれません…)。
次回はPICマイコンにてNeoPixelを点灯制御する方法(SPIやCLC (configurable logic cell)などは用いず,ただI/Oポートを直接叩く方法です💧)について書きたいと思います。