
やりたいこと
NanoPi NEO では映像を扱うつもりはなかったけど、やってみたらギリギリアウトw
けど、4ドルしか価格が違わないのに性能がおそらく3~4倍はあろう NEO3 Plus なら?
こんなことが割と余裕。(ただし、SPI の帯域がいっぱいいっぱい過ぎてフレームレートは出せないw)
用意したもの
NanoPi NEO3 Plus
3.2 インチ LCD:ILI9341 SPI接続 320×240
注意事項:2026年7月12日時点
Friendly Elec の NanoPi NEO3 Plus の Wiki に間違いがある。
ケアレスミスのようなものなのですぐ直るとは思うけど。
次の画像は Wiki からの抜粋で、Pin#22 と Pin#24 に注目。

Pin#24、GPIO_4_B3_SPI4_CSN0 と書いてあるけど、GPIO1_C1_SPI1_CSN0 の間違え。
最初、「無印NEO から CS だけ変わったのか。SPI4?…ま、その上にSPI1_CSN1があるから、これを使えばいいのか」と接続してみたけれど、全く動かないw
踏襲されたピンアサインが早々変わることはないはずなのに、「SPI1」という文字に釣られるとハマるかも。
下記に回路図があるので念のため見てみた。
https://wiki.friendlyelec.com/wiki/images/a/ae/NanoPi_NEO3_Plus_2601_SCH.pdf
GPIO の項目。

うん、やっぱり GPIO4 でもなければ B3 でもなく SPI4 でもない。
正しくは GPIO1_C1_SPI1_CSN0
なので、CS としてつなげるべきは Pin#22 ではなく Pin#24 で、無印 NEO と全く同じで良い。
そして、結果的に Pin#24 で動いた。
Friendly Elec 公式 Ubuntu ならやることほとんどない
すでに最初から SPI が有効になってる。
$ sudo ls -al /dev/spi*
crw------- 1 root root 153, 0 Jul 12 18:42 /dev/spidev1.0
フレームバッファも存在してはいるけれど、これ、たぶんHDMI用。
$ ls -al /dev/fb*
crw-rw---- 1 root video 29, 0 Jul 12 18:43 /dev/fb0
$ sudo fbset -i -fb /dev/fb0
mode "1920x1080"
geometry 1920 1080 1920 2560 32
timings 0 0 0 0 0 0 0
accel true
rgba 8/16,8/8,8/0,0/0
endmode
Frame buffer device information:
Name : rockchipdrmfb
Address : 0
Size : 19660800
Type : PACKED PIXELS
Visual : TRUECOLOR
XPanStep : 1
YPanStep : 1
YWrapStep : 0
LineLength : 7680
Accelerator : No
fbset はでおフォルトでは入ってないので、フレームバッファの情報を見たい場合は下記のようにインストール。
…でもそんなに使わないからスルーでもいいかな。
$ sudo apt install fbset
もし、ILI9341のドライバ読み込んでフレームバッファ使えれば楽かもしれない。
けど、上記 Wiki の誤植にハメられてちょっと時間を無駄に使ってしまったので、やったのは SPI 直叩き。
OPi.GPIO を使う
以前、下記で書いてるので基本はそっちで。

ただ、設定ファイルに書き足す内容は全く違ってしまうので、その部分だけ以下に差し替え。
# User defined, initialized as empty
CUSTOM: {
7: 147, # GPIO4_C3
8: 134, # GPIO4_A6_UART1_TX_M0
10: 135, # GPIO4_A7_UART1_RX_M0
11: 133, # GPIO4_A5
12: 132, # GPIO4_A4
13: 140, # GPIO4_B4
15: 128, # GPIO4_A0
16: 44, # GPIO1_B4
18: 45, # GPIO1_B5
19: 47, # GPIO1_B7_SPI1_MOSI
21: 48, # GPIO1_C0_SPI1_MISO
22: 50, # GPIO1_C2_SPI1_CSN1
23: 46, # GPIO1_B6_SPI1_CLK
24: 139, # GPIO4_B3_SPI4_CSN0
26: 150 # GPIO4_C6
}
ということで、動かすコード
その前に、一応配線。

19:MOSI 21:MISO 23:CLK 24:CS 1:VCC は完全に固定。
GND は適当。
DC と RESET は任意の GPIO で OK。ここではそれぞれ 16 と 18 を使用した。
あと、LEDに5V供給してあげるとバックライトが点く。(バックライトないと真っ暗)
合計9本とちょっと多いかな…
こんな感じでつながっている状態で下記コードを実行すると絵が出る。
import OPi.GPIO as GPIO
import spidev, time, subprocess
DC = 16
RST = 18
GPIO.setmode(GPIO.CUSTOM)
GPIO.setup(DC, GPIO.OUT)
GPIO.setup(RST, GPIO.OUT)
spi = spidev.SpiDev()
spi.open(1, 0)
spi.mode = 0
spi.max_speed_hz = int(32 * 1000 * 1000)
def cmd(c):
GPIO.output(DC, 0)
spi.writebytes([c])
def cmd_with_data(c, data_list):
GPIO.output(DC, 0)
spi.writebytes([c])
GPIO.output(DC, 1)
spi.writebytes(data_list)
# LCD初期化
def init_lcd():
GPIO.output(RST, 0)
time.sleep(0.05)
GPIO.output(RST, 1)
time.sleep(0.05)
cmd(0x11)
time.sleep(0.12)
# 画面転送方向を横向き(320x240)に変更
cmd_with_data(0x36, [0x28])
time.sleep(0.01)
cmd_with_data(0x2A, [0x00, 0x00, 0x01, 0x3F])
cmd_with_data(0x2B, [0x00, 0x00, 0x00, 0xEF])
time.sleep(0.01)
cmd(0x29)
time.sleep(0.02)
init_lcd()
# --- 配信・画面設定 ---
RTSP_URL = "rtsp://[ID]:[PW]@[Camera IP]/"
WIDTH = 320
HEIGHT = 240
FRAME_RATE = 10
FRAME_SIZE = WIDTH * HEIGHT * 3
# ffmpegのコマンド作成
ffmpeg_cmd = [
'ffmpeg',
'-fflags', 'nobuffer',
'-rtsp_transport', 'tcp',
'-r', f'{FRAME_RATE}',
'-i', RTSP_URL,
'-vf', f'scale={WIDTH}:{HEIGHT}:flags=neighbor',
'-f', 'rawvideo',
'-pix_fmt', 'rgb24',
'-an', '-sn',
'-loglevel', 'quiet',
'-'
]
proc = subprocess.Popen(ffmpeg_cmd, stdout=subprocess.PIPE, bufsize=FRAME_SIZE)
try:
while True:
t_start = time.time()
frame_data = proc.stdout.read(FRAME_SIZE)
if len(frame_data) < FRAME_SIZE:
break
# LCDへ書き込み
cmd(0x2C)
GPIO.output(DC, 1)
spi.writebytes2(frame_data)
# ウェイト
t_elasped = time.time() - t_start
t_sleep = 1.0 / FRAME_RATE - t_elasped
if t_sleep > 0:
time.sleep(t_sleep)
except KeyboardInterrupt:
print("\n終了")
finally:
if proc.stdout:
proc.stdout.close()
proc.terminate()
proc.wait()
spi.close()
GPIO.cleanup()
ついでに、ローカルの動画ファイルを再生するなら下のような感じ。
import OPi.GPIO as GPIO
import spidev, time, subprocess
DC = 16
RST = 18
GPIO.setmode(GPIO.CUSTOM)
GPIO.setup(DC, GPIO.OUT)
GPIO.setup(RST, GPIO.OUT)
spi = spidev.SpiDev()
spi.open(1, 0)
spi.mode = 0
spi.max_speed_hz = int(32 * 1000 * 1000)
def cmd(c):
GPIO.output(DC, 0)
spi.writebytes([c])
def cmd_with_data(c, data_list):
GPIO.output(DC, 0)
spi.writebytes([c])
GPIO.output(DC, 1)
spi.writebytes(data_list)
# LCD初期化
def init_lcd():
GPIO.output(RST, 0)
time.sleep(0.05)
GPIO.output(RST, 1)
time.sleep(0.05)
cmd(0x11)
time.sleep(0.12)
# 画面転送方向を横向き(320x240)に変更
cmd_with_data(0x36, [0x28])
time.sleep(0.01)
cmd_with_data(0x2A, [0x00, 0x00, 0x01, 0x3F])
cmd_with_data(0x2B, [0x00, 0x00, 0x00, 0xEF])
time.sleep(0.01)
cmd(0x29)
time.sleep(0.02)
init_lcd()
# --- 配信・画面設定 ---
FILE_PATH = "violet.mp4"
WIDTH = 320
HEIGHT = 240
FRAME_RATE = 10
FRAME_SIZE = WIDTH * HEIGHT * 3
# ffmpegのコマンド作成
ffmpeg_cmd = [
'ffmpeg',
'-re',
'-stream_loop', '-1',
'-r',f'{FRAME_RATE}',
'-i', FILE_PATH,
'-vf', 'scale=320:180:flags=neighbor, pad=320:240:-1:-1:color=black',
'-f', 'rawvideo',
'-pix_fmt', 'rgb24',
'-loglevel', 'quiet',
'-'
]
proc = subprocess.Popen(ffmpeg_cmd, stdout=subprocess.PIPE, bufsize=FRAME_SIZE)
try:
while True:
frame_data = proc.stdout.read(FRAME_SIZE)
if len(frame_data) < FRAME_SIZE:
break
# LCDへ書き込み
cmd(0x2C)
GPIO.output(DC, 1)
spi.writebytes2(frame_data)
except KeyboardInterrupt:
print("\n終了")
finally:
if proc.stdout:
proc.stdout.close()
proc.terminate()
proc.wait()
spi.close()
GPIO.cleanup()
SPI とはいえ 30fps はなかなか厳しい。
そして、個人的にはカメラの RTSP ストリームを受信したいだけなので、ぶっちゃけ 5fps でも良いくらい。
動画を再生しているのは、まぁ、デモ用?w
ということで、NEO3 Plus、いいね。
この記事にコメントしてみる