NuttX for Raspberry Pi PicoでPico Display Packを使う

Raspberry Pi Pico専用の液晶モジュール [Pico Display Pack](https://shop.pimoroni.com/products/pico-display-pack) が秋月電子でも買えるようになりました。

https://akizukidenshi.com/catalog/g/gM-16238/

この液晶モジュール、コントローラのST7789はNuttXにも既にドライバが存在するので、NuttXでも簡単に動かせるのではないかと試してみました(が、ちょっとハマりました)。

# 接続

まず最初の問題が、Display PackとPicoとの接続です。[商品ページ](https://shop.pimoroni.com/products/pico-display-pack)の写真の通り、Pico専用に作られたモジュールなのでピンヘッダを付けたPicoであればそのまま刺さります。が、挿してしまうと他のGPIOピンから信号を取れなくなってしまうため、UARTが繋げなくなってしまいます。現状USB CDCをサポートしていないPicoのNuttXではこれは大問題。
やむなく、Display Packの液晶関連の各ピンとPicoの同じピンをブレッドボード上で繋ぐことに。せっかくの専用モジュールが台無しですが仕方ない…。

https://twitter.com/yunkya2/status/1370398649906122754

以下のピン同士を接続します。

|端子名|ピン番号|
|----:|-------:|
|LCD_DC|21|
|LCD_CS|22|
|GND|18,23|
|LCD_SCLK|24|
|LCD_MOSI|25|
|BL_EN|26|
|LCD_RESET|30|
|3V3|36|

- モジュール上のスイッチやLEDは今回は使わないので、SW_\*とLED_\*はそのままです。
- どうやらモジュール側ではGNDがすべてつながっておらず、Pico側で全GNDピン同士がつながっていることを期待しているようです。最低限、18,23のGNDを繋いでおかないとうまく動きませんでした。

# NuttX側の対応

前述したとおり、コントローラのST7789は既にドライバがあって、接続に使用するSPIも既にサポート済みです。後は、基本的にはPico対応コード側でSPIポートとST7789ドライバ間を繋いでやればよいのですが、以下の対応が必要でした。

#### CMD/DATA信号サポート

通常、SPIではホストからデバイスへのデータ(TX / MOSI)、デバイスからホストへのデータ(RX / MISO)、クロック(SCK)、チップセレクト(CS)の4つの線で制御しますが、ST7789にはRXが出ていない[^1]一方で、CMD/DATA(LCD_DC)という信号で、これから送るのがコントローラへのコマンドなのかそのデータなのかを区別する必要があります。
NuttXのSPIドライバでも`CONFIG_SPI_CMDDATA`というコンフィグレーションでこれをサポートしているので、これを有効にしたうえでLCD_DC信号を制御する関数を登録します。

```c:boards/arm/rp2040/raspberrypi-pico/src/rp2040_spi.c
#ifdef CONFIG_SPI_CMDDATA
int rp2040_spi0cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd)
{
#ifdef CONFIG_LCD_ST7789
  if (devid == SPIDEV_DISPLAY(0))
    {
      /*  This is the Data/Command control pad which determines whether the
       *  data bits are data or a command.
       */

      rp2040_gpio_put(CONFIG_RP2040_SPI0_GPIO, !cmd);

      return OK;
    }
#endif

  return -ENODEV;
}
#endif
#endif
```

[^1]: 正確には、ST7789自体はRX(MISO)の信号が出ていますがDisplay Packがそれを端子側に出していません。基本的にはホスト側からのコマンド/データ送信のみで表示には使えるので不要との判断でしょう。

#### 表示位置オフセット、回転サポート

液晶コントローラの中にはフレームバッファRAMがあって、ホストはSPIのコマンドを使ってこのRAMにデータを書き込む一方で、液晶パネルはこのRAMを読んで表示を行います。
ST7789は240×320ピクセル分のRAMを搭載しているのですが、Display Packの液晶パネルにはなぜか、このRAMの中央の135×240ピクセルが表示されるようになっています。ST7789を使った液晶モジュールは240×240の液晶を搭載しているものがほとんどで、これは素直にフレームバッファの上側をそのまま使っているのですが、何故こんなややこしいことを。

![st7789.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/542e954d-41c0-9b40-c10a-c0daf737a40a.png)

ST7789のコマンドでは、データを書き込む際の位置をフレームバッファ上のX,Y座標で指定するようになっているので、ずれている分だけのオフセットを指定してやればよいのですが、NuttXのドライバにはその機能がなかったので追加が必要でした。

また、ST7789を使った既存の液晶モジュールではこのフレームバッファを横方向にスキャンする使い方をするのですが、Display Packは縦方向にスキャンするようになっていたため、スキャン方向を指定するコマンドをコントローラに送信する対応を追加しています。
コンフィグレーション `CONFIG_LCD_LANDSCAPE` を有効にするとスキャン方向が横に90度傾きます。
ちなみに `CONFIG_LCD_RLANDSCAPE` を有効にすると、表示方向の天地が逆になります。

```c:drivers/lcd/st7789.c
/****************************************************************************
 * Name: st7789_setorientation
 *
 * Description:
 *   Set screen orientation.
 *
 ****************************************************************************/

static void st7789_setorientation(FAR struct st7789_dev_s *dev)
{
  /* No need to change the orientation in PORTRAIT mode */

#if !defined(CONFIG_LCD_PORTRAIT)
  st7789_sendcmd(dev, ST7789_MADCTL);
  st7789_select(dev->spi, 8);

#  if defined(CONFIG_LCD_RLANDSCAPE)
  /* RLANDSCAPE : MY=1 MV=1 */

  SPI_SEND(dev->spi, 0xa0);

#  elif defined(CONFIG_LCD_LANDSCAPE)
  /* LANDSCAPE : MX=1 MV=1 */

  SPI_SEND(dev->spi, 0x70);

#  elif defined(CONFIG_LCD_RPORTRAIT)
  /* RPORTRAIT : MX=1 MY=1 */

  SPI_SEND(dev->spi, 0xc0);
#  endif

  st7789_deselect(dev->spi);
#endif
}
```

# ビルドと起動

NuttXのコンフィグレーション `raspberrypi-pico:displaypack` を指定してビルドします。

```
$ git clone https://github.com/apache/incubator-nuttx.git nuttx
$ git clone https://github.com/apache/incubator-nuttx-apps.git apps
$ cd nuttx
$ ./tools/configure.sh raspberrypi-pico:displaypack
$ make
```

`nxhello`コマンドで、以下のようにHello Worldが表示されます[^2]。

[^2]: twitterのコメントに書いたDMAがおかしい件は、ST7789ドライバで使用している16bit転送が正常に動作しない問題があったのが原因で、既に修正済みです。

https://twitter.com/yunkya2/status/1370735765978714115

# ターミナルエミュレータの実行

NuttXのappsには、ウィンドウシステムやターミナルエミュレータも含まれているので、そのコンフィグレーションも有効にしてみます。

https://twitter.com/yunkya2/status/1370749655026307076

以下のdefconfigを使用しました。

```defconfig
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_FS_PROCFS_EXCLUDE_ENVIRON is not set
# CONFIG_LIBC_LONG_LONG is not set
# CONFIG_NSH_ARGCAT is not set
# CONFIG_NSH_CMDOPT_HEXDUMP is not set
# CONFIG_NSH_DISABLE_DATE is not set
# CONFIG_NSH_DISABLE_LOSMART is not set
# CONFIG_NSH_DISABLE_PRINTF is not set
# CONFIG_NSH_DISABLE_TRUNCATE is not set
# CONFIG_NXFONTS_DISABLE_16BPP is not set
# CONFIG_NX_DISABLE_16BPP is not set
# CONFIG_NX_PACKEDMSFIRST is not set
# CONFIG_NX_WRITEONLY is not set
# CONFIG_STANDARD_SERIAL is not set
CONFIG_ARCH="arm"
CONFIG_ARCH_BOARD="raspberrypi-pico"
CONFIG_ARCH_BOARD_RASPBERRYPI_PICO=y
CONFIG_ARCH_CHIP="rp2040"
CONFIG_ARCH_CHIP_RP2040=y
CONFIG_ARCH_RAMVECTORS=y
CONFIG_ARCH_STACKDUMP=y
CONFIG_BOARDCTL_RESET=y
CONFIG_BOARD_LOOPSPERMSEC=10450
CONFIG_BUILTIN=y
CONFIG_DEBUG_ASSERTIONS=y
CONFIG_DEBUG_ERROR=y
CONFIG_DEBUG_FEATURES=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_WARN=y
CONFIG_DISABLE_POSIX_TIMERS=y
CONFIG_DRIVERS_VIDEO=y
CONFIG_EXAMPLES_FB=y
CONFIG_EXAMPLES_HELLO=y
CONFIG_EXAMPLES_NXDEMO=y
CONFIG_EXAMPLES_NXDEMO_BPP=16
CONFIG_EXAMPLES_NXHELLO=y
CONFIG_EXAMPLES_NXHELLO_BPP=16
CONFIG_EXAMPLES_NXTERM=y
CONFIG_EXAMPLES_NXTEXT=y
CONFIG_EXAMPLES_NXTEXT_BPP=16
CONFIG_FS_PROCFS=y
CONFIG_FS_PROCFS_REGISTER=y
CONFIG_HAVE_CXX=y
CONFIG_I2C=y
CONFIG_LCD=y
CONFIG_LCD_DEV=y
CONFIG_LCD_FRAMEBUFFER=y
CONFIG_LCD_MAXCONTRAST=255
CONFIG_LCD_NOGETRUN=y
CONFIG_LCD_ST7789=y
CONFIG_LCD_ST7789_FREQUENCY=64000000
CONFIG_LCD_ST7789_XOFFSET=53
CONFIG_LCD_ST7789_XRES=135
CONFIG_LCD_ST7789_YOFFSET=40
CONFIG_LCD_ST7789_YRES=240
CONFIG_MAX_TASKS=8
CONFIG_MQ_MAXMSGSIZE=64
CONFIG_NFILE_DESCRIPTORS=6
CONFIG_NSH_ARCHINIT=y
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_READLINE=y
CONFIG_NX=y
CONFIG_NXFONTS_PACKEDMSFIRST=y
CONFIG_NXFONT_SANS17X23B=y
CONFIG_NXTERM=y
CONFIG_NXWIDGETS=y
CONFIG_NXWIDGETS_BPP=16
CONFIG_NXWIDGETS_SIZEOFCHAR=1
CONFIG_NXWM=y
CONFIG_NX_BLOCKING=y
CONFIG_NX_KBD=y
CONFIG_NX_XYINPUT_MOUSE=y
CONFIG_RAM_SIZE=270336
CONFIG_RAM_START=0x20000000
CONFIG_READLINE_CMD_HISTORY=y
CONFIG_RP2040_SPI0=y
CONFIG_RP2040_SPI0_GPIO=16
CONFIG_RP2040_SPI=y
CONFIG_RR_INTERVAL=200
CONFIG_SCHED_HPWORK=y
CONFIG_SCHED_LPWORK=y
CONFIG_SCHED_ONEXIT=y
CONFIG_SCHED_WAITPID=y
CONFIG_SDCLONE_DISABLE=y
CONFIG_SPI_CMDDATA=y
CONFIG_START_DAY=9
CONFIG_START_MONTH=2
CONFIG_START_YEAR=2021
CONFIG_SYSLOG_CONSOLE=y
CONFIG_SYSTEM_I2CTOOL=y
CONFIG_SYSTEM_NSH=y
CONFIG_SYSTEM_SPITOOL=y
CONFIG_TESTING_GETPRIME=y
CONFIG_TESTING_OSTEST=y
CONFIG_UART0_SERIAL_CONSOLE=y
CONFIG_USER_ENTRYPOINT="nsh_main"
CONFIG_VIDEO=y
CONFIG_VIDEO_FB=y

```



# 有線LANとの同時使用

更に、[NuttX for Raspberry Pi Picoで有線LAN接続](https://qiita.com/yunkya2/items/02301a06f4ade334800e)で行ったENC28J60による有線LAN接続と同時に使ってみました。
Display PackがSPI0、ENC28J60がSPI1を使用します。

双方を合体させて、以下のようなコンフィグレーションになります。

```
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_FS_PROCFS_EXCLUDE_ENVIRON is not set
# CONFIG_LIBC_LONG_LONG is not set
# CONFIG_NSH_ARGCAT is not set
# CONFIG_NSH_CMDOPT_HEXDUMP is not set
# CONFIG_NSH_DISABLE_DATE is not set
# CONFIG_NSH_DISABLE_LOSMART is not set
# CONFIG_NSH_DISABLE_PRINTF is not set
# CONFIG_NSH_DISABLE_TRUNCATE is not set
# CONFIG_NXFONTS_DISABLE_16BPP is not set
# CONFIG_NX_DISABLE_16BPP is not set
# CONFIG_NX_PACKEDMSFIRST is not set
# CONFIG_NX_WRITEONLY is not set
# CONFIG_RP2040_DMAC is not set
# CONFIG_STANDARD_SERIAL is not set
CONFIG_ARCH="arm"
CONFIG_ARCH_BOARD="raspberrypi-pico"
CONFIG_ARCH_BOARD_RASPBERRYPI_PICO=y
CONFIG_ARCH_CHIP="rp2040"
CONFIG_ARCH_CHIP_RP2040=y
CONFIG_ARCH_RAMVECTORS=y
CONFIG_ARCH_STACKDUMP=y
CONFIG_BOARDCTL_RESET=y
CONFIG_BOARD_LOOPSPERMSEC=10450
CONFIG_BUILTIN=y
CONFIG_DEV_ZERO=y
CONFIG_DISABLE_POSIX_TIMERS=y
CONFIG_ENC28J60=y
CONFIG_EXAMPLES_HELLO=y
CONFIG_EXAMPLES_NXDEMO=y
CONFIG_EXAMPLES_NXDEMO_BPP=16
CONFIG_EXAMPLES_NXHELLO=y
CONFIG_EXAMPLES_NXHELLO_BPP=16
CONFIG_EXAMPLES_NXTERM=y
CONFIG_EXAMPLES_NXTEXT=y
CONFIG_EXAMPLES_NXTEXT_BPP=16
CONFIG_FS_PROCFS=y
CONFIG_FS_PROCFS_REGISTER=y
CONFIG_HAVE_CXX=y
CONFIG_HAVE_CXXINITIALIZE=y
CONFIG_I2C=y
CONFIG_LCD=y
CONFIG_LCD_DEV=y
CONFIG_LCD_FRAMEBUFFER=y
CONFIG_LCD_MAXCONTRAST=255
CONFIG_LCD_NOGETRUN=y
CONFIG_LCD_ST7789=y
CONFIG_LCD_ST7789_FREQUENCY=64000000
CONFIG_LCD_ST7789_XOFFSET=53
CONFIG_LCD_ST7789_XRES=135
CONFIG_LCD_ST7789_YOFFSET=40
CONFIG_LCD_ST7789_YRES=240
CONFIG_MQ_MAXMSGSIZE=64
CONFIG_NET=y
CONFIG_NETDB_DNSCLIENT=y
CONFIG_NETDB_DNSSERVER_NOADDR=y
CONFIG_NETDEVICES=y
CONFIG_NETINIT_DHCPC=y
CONFIG_NETINIT_DNS=y
CONFIG_NETINIT_DNSIPADDR=0x08080808
CONFIG_NETINIT_NOMAC=y
CONFIG_NETINIT_THREAD=y
CONFIG_NETUTILS_DHCPC=y
CONFIG_NETUTILS_TELNETD=y
CONFIG_NET_BROADCAST=y
CONFIG_NET_ICMP=y
CONFIG_NET_ICMP_SOCKET=y
CONFIG_NET_LOOPBACK=y
CONFIG_NET_ROUTE=y
CONFIG_NET_SOCKOPTS=y
CONFIG_NET_TCP=y
CONFIG_NET_UDP=y
CONFIG_NET_UDP_CHECKSUMS=y
CONFIG_NFILE_DESCRIPTORS=8
CONFIG_NSH_ARCHINIT=y
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_READLINE=y
CONFIG_NX=y
CONFIG_NXFONTS_PACKEDMSFIRST=y
CONFIG_NXFONT_SANS17X23B=y
CONFIG_NXTERM=y
CONFIG_NXWIDGETS=y
CONFIG_NXWIDGETS_BPP=16
CONFIG_NXWIDGETS_SIZEOFCHAR=1
CONFIG_NXWM=y
CONFIG_NX_BLOCKING=y
CONFIG_NX_KBD=y
CONFIG_NX_XYINPUT_MOUSE=y
CONFIG_RAM_SIZE=270336
CONFIG_RAM_START=0x20000000
CONFIG_READLINE_CMD_HISTORY=y
CONFIG_RP2040_ENC28J60_INTR_GPIO=11
CONFIG_RP2040_ENC28J60_RESET_GPIO=10
CONFIG_RP2040_SPI0=y
CONFIG_RP2040_SPI0_GPIO=16
CONFIG_RP2040_SPI1=y
CONFIG_RP2040_SPI1_GPIO=12
CONFIG_RP2040_SPI=y
CONFIG_RR_INTERVAL=200
CONFIG_SCHED_HPWORK=y
CONFIG_SCHED_HPWORKPRIORITY=192
CONFIG_SCHED_ONEXIT=y
CONFIG_SCHED_WAITPID=y
CONFIG_SDCLONE_DISABLE=y
CONFIG_SMP=y
CONFIG_SMP_NCPUS=2
CONFIG_SPI_CMDDATA=y
CONFIG_START_DAY=9
CONFIG_START_MONTH=2
CONFIG_START_YEAR=2021
CONFIG_SYSLOG_CONSOLE=y
CONFIG_SYSTEM_I2CTOOL=y
CONFIG_SYSTEM_NSH=y
CONFIG_SYSTEM_NTPC=y
CONFIG_SYSTEM_PING=y
CONFIG_SYSTEM_SPITOOL=y
CONFIG_TESTING_GETPRIME=y
CONFIG_TESTING_OSTEST=y
CONFIG_UART0_SERIAL_CONSOLE=y
CONFIG_USER_ENTRYPOINT="nsh_main"
CONFIG_WQUEUE_NOTIFIER=y
```

ビルドして起動し、コンソールからnxtermを起動します。
動画の34秒あたりから背景が変わって文字が出ているのは、telnetで接続してnxtextという別のデモを実行させたものです。

https://twitter.com/yunkya2/status/1370750140890324992

実用性はともかく、かなり複雑なことができるようになってきました。
すべてNuttXのappsに入っているデモをそのまま使っています。まだアプリケーションコードはまったく書いていません(笑)。