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については次回だ。