ncs: USB-CDCとUARTの両立 (1)
2024/08/28
前回の続き。
DevAcademy Lesson 5 Exercise 1 は、nRF が UART で “1” or “2” or “3” を受信すると該当する LED をトグルさせるという動作を行う。
uart0
だけでやった場合には動作する。
なのに DTSファイルで zephyr_udc0
のプロパティを設定しただけで動かなくなるという現象に悩まされている。
やりたいのは、ログ出力は USBシリアルで吐き出して、UART はそれと関係なく使うというだけ。
再実験
non-secure が関係するかどうかを考えたくないので cpuapp
とcpuapp_usb
だけで考える。
「USBシリアル」は nRF の USB-CDC を指している。
USBを有効にしたボード定義ファイル
cpuapp_usb
をそのまま使う- 結果: NG
- USBシリアルにはログが出力される
uart_callback_set()
で-88
が返る
- 結果: NG
USBを有効にしたボード定義からzephyr_udc0
を無効にする
cpuapp_usb
からzephyr_udc0
の設定をコメントアウトする- それだけだとダメなので
chosen
でzephyr,console = &uart0
を設定 - 結果: OK
- USBシリアルは無効
- UART 側にキー入力をしたら LED がトグルする
- それだけだとダメなので
USB無効のボード定義にzephyr_udc0
だけ追加する
cpuapp
にoverlayでzephyr_udc0
を追加zephyr,console
はそのまま(なのでuart0
を参照している)- 結果: OK
USB無効のボード定義にzephyr_udc0
とCONFIG_USB_DEVICE_STACK=y
を追加
- 1つ上の環境に対して
prj.conf
にCONFIG_USB_DEVICE_STACK=y
を追記するだけ- 結果: NG
- UART 側にログが出ていた
- UART 側に
uart_callback_set()
で-88
が出力されていた
- 結果: NG
再実験の振り返り
USBが有効な状態でzephyr_udc0
だけ削除したら動作したのでzephyr_udc0
に原因があるかと考えていたが、そうではないようだ。
しかしCONFIG_USB_DEVICE_STACKも関係するということは、設定だけではないということだ。
CONFIG_USB_DEVICE_STACK
を有効にすると、CONFIG_SERIAL
も有効でzephyr_udc0
も設定しているので自動的にCONFIG_USB_CDC_ACMも有効になるのだろう。
issue 1
これを読むと、UART2 は CDC ACM に、UART3 は普通に UART に使うということが書かれている。
cdc_acm_uart
のサフィックスに付いた数字が uart
のサフィックスと対応しているということ?
この人の場合、エラーはENOSYS
ではなくENOSETUP
なので前提は異なるものの、割り当てが違うのならエラーが違ってもおかしくはないのか。
試しにこうやると、USBシリアルが 2つになった。
zephyr,console
にcdc_acm_uart0
を指定した場合とcdc_acm_uart1
を指定した場合でちゃんと出力先も切り替わっている。
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
cdc_acm_uart1: cdc_acm_uart1 {
compatible = "zephyr,cdc-acm-uart";
};
};
ではcdc_acm_uart1
だけにするとどうなるかというと、uart_callback_set()
のエラーが-88
になったままである。
そもそも、printk()
でログをUSBシリアルに吐き出していたときは UART の設定はしていないのに使っていた。
zephyr,console
にuart0
でもcdc_acm_uart0
でも設定できるなら、UART のアクセスも uart0
ではなく cdc_acm_uart0
でやればよい。
なのでこの人も、uart2
を CDC ACM に割り当てているわけではなく、単に CDC ACM と UART の両立に失敗しているだけなのかも?
ああ、でもエラー値が違うな。
10ヶ月も前なので、ncs v1 とかで状況が違うのかもしれん。
ここの issue に回答した人によると、CDC ACMドライバは ASYNC APIをサポートしていないのでコールバックを設定しようとするとENOSETUP
を返すと答えているけれども、今の実装を見ると CONFIG_UART_ASYNC_API
が未定義だったら-ENOTSUP
になっている。
うちの-88
はapi->callback_set == NULL
によって-ENOSYS
が返っているのだ。
issue 2
ならば、こちらの issue はどうだ。
nRF52840 だが ncs v2.4 なので最近だし、なにより抜粋してあるuart_callback_set()
が同じ実装だ。
そして戻り値は-ENOSYS
。文句なしだろう。
Cannot get Async UART0 to work with USB CDC - Nordic Q&A - Nordic DevZone - Nordic DevZone
ちょっと疑わしいのは、前の解答にもあった CDC ACM は Async API をサポートしないという件だ。
uart_callback_set()
は非同期のコールバックを設定するAPIなので、非同期に仕様としていないなら#else
のルートで無条件のエラー(-ENOTSUP
)、それ以外はapi->callback_set
を見て対応しているかどうか判定、ということになってるのではなかろうか。
と思ったが、uart_callback_set()
はuart0
に対しての指示だから CDC ACM は関係ないか。
ちなみにこの issue の人は CDC ACM に対して uart_callback_set()
しているので、まあ妥当な結果だろう。
自分のコード
cdc_acm_uart0
はそのままにしてuart0
の設定をuart1
を有効にして振り替えた。
はい、ダメ。
usb_enable()
をuart_callback_set()
より後で呼ぶようにした。
はい、ダメ。なにも出力されない。
それなら次に疑うのは、アプリからDT_NODELABEL(uart0)
で取ってきているけど、実はこれが UART ではなく CDC ACM にすり替わっている、とかか。
あり得るのか?
uart->name
はuart@8000
だった。
これはuart0
の開始アドレスと一致したので、すり替わっているわけではないだろう。
uart->api
がstruct uart_driver_api*
型なのでキャストしてダンプしてみる。
callback_set
を含めこの辺は全部0x00
だった。
それ以降には値が入っていたので、ポーリングなんかはできるんだろう。
ちゃんと動くときはcallback_set
には.text.uarte_nrfx_callback_set
のアドレスが入っていた。
箇所としてはuart_nrfx_uarte.cである。
UARTE_ANY_ASYNC
マクロで ifdef している。
ビルドして焼いてみるとこのインスタンスuart_nrfx_uarte_driver_api
が既に 0x00
になっていた。
grep すると CONFIG_UART_0_ASYNC
の方が出てこなかったので、そのせいで UARTE_ANY_ASYNC
が未定義になり callback_set
に 0x00
が代入されることになったのだろう。
よし、まあまあ進展した。
CONFIG_UART_0_ASYNCについては次回だ。