NanoPi NEO に SPI で 3.2 インチ LCD を繋げる

用意したもの

NanoPi NEO
3.2 インチ LCD:ILI9341 SPI接続 320×240

Amazon.co.jp: DIANN 3.2" ILI9341 SPI TFT LCD Display Touch Panel 320×240 TFT LCD Touch Screen Shield 5V/3.3V STM32 Display Module SPI Serial with Touch Pen : 産業・研究開発用品
Amazon.co.jp: DIANN 3.2" ILI9341 SPI TFT LCD Display Touch Panel 320×240 TFT LCD Touch Screen Shield 5V/3.3V STM32 Displ…

配線

SPI:4本   その他制御用:2本   電源:2本  LEDバックライト:1本
とりあえず、この9本配線されてればOK(削ろうと思えば削れるけど…)

NanoPi NEO の準備

まずは SPI の有効化。

$ sudo vi /boot/armbianEnv.txt

として設定ファイルを書き換える。
次の2行を末尾に付け加えて、保存し、再起動。

overlays=spi-spidev i2c0 usbhost1 usbhost2
param_spidev_spi_bus=0

※I2Cと同時に使おうと、 overlays=spi-spidev i2c0 としたら SPI が動かない…
 何やってもダメなので、とりあえず、SPI 一本に絞って使うことにした。

で、

$ sudo reboot

として再起動後、SPIがちゃんと有効化されて起動してきたかを確認する。

$ ls -al /dev/spidev*
crw------- 1 root root 153, 0  6月 20 00:04 /dev/spidev0.0

$ lsmod | grep spi
spidev                 20480  0

こんな感じで表示されればOK

続いて、Python の仮想環境をホームディレクトリは配下に作る。

$ cd ~
$ mkdir lcd_con
$ python3 -m venv ./lcd_con
$ cd lcd_con
$ source ./bin/activate

続いてライブラリの導入。
あ、ついでに pip も更新しておく。

(lcd_con)$ sudo apt update
(lcd_con)$ ./bin/pip install --upgrade pip
(lcd_con)$ ./bin/pip install spidev OPi.GPIO

また、NanoPi NEO の GPIO をどうやって使えるようにするかは別記事にてまとめてある。

NanoPi NEO の GPIO が使えるようになるまで
NanoPi NEO って?ちょっと今更感はある。(2025年11月)性能的にはAIの推論もほぼほぼできないし、WiFi も Bluetooth もない。だが、そこがいい。小さいのにGPIO豊富だし、UARTも複数持ってるので。そして、Ar…

準備完了。あとはコードを書いていくだけ。

ffmpeg で映像を流し込む

非力な NanoPi NEO に ffmpeg でエンコさせるなんて…サディスティック…
けどやってみた。

import OPi.GPIO as GPIO
import spidev, time, subprocess
 
DC = 18
RST = 16
 
GPIO.setmode(GPIO.CUSTOM)
GPIO.setup(DC, GPIO.OUT)
GPIO.setup(RST, GPIO.OUT)
 
spi = spidev.SpiDev()
spi.open(0, 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, [0x98])
    time.sleep(0.01)
    cmd(0x29)
    time.sleep(0.02)
 
init_lcd()
 
# --- 配信・画面設定 ---
RTSP_URL = "rtsp://[ID]:[PW]@[Camera IP]/"
WIDTH = 240
HEIGHT = 320
FRAME_RATE = 5
FRAME_SIZE = WIDTH * HEIGHT * 3
 
# ffmpegのコマンド作成
ffmpeg_cmd = [
    'ffmpeg',
    '-rtsp_transport', 'tcp',
    '-i', RTSP_URL,
    '-vf', f'transpose=2,scale={WIDTH}:{HEIGHT}',
    '-f', 'rawvideo',
    '-pix_fmt', 'rgb24',
    '-r', f'{FRAME_RATE}',
    '-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()

まぁ 30fps とか 20fps なんてのは到底無理よね。
せいぜい 5fps くらいかねぇ…

とはいえ、目的は達した。

3000円の NanoPi NEO に、2000円の LCD、それだけで超ポータブルな RTSP ビューアができた。
あとはモバイルバッテリーでどれだけ動くか、かな。

この記事にコメントしてみる