hiro99ma blog

何か技術的なこと

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 が関係するかどうかを考えたくないので cpuappcpuapp_usbだけで考える。
「USBシリアル」は nRF の USB-CDC を指している。

USBを有効にしたボード定義ファイル

USBを有効にしたボード定義からzephyr_udc0を無効にする

USB無効のボード定義にzephyr_udc0だけ追加する

USB無効のボード定義にzephyr_udc0CONFIG_USB_DEVICE_STACK=yを追加

再実験の振り返り

USBが有効な状態でzephyr_udc0だけ削除したら動作したのでzephyr_udc0に原因があるかと考えていたが、そうではないようだ。 しかしCONFIG_USB_DEVICE_STACKも関係するということは、設定だけではないということだ。

CONFIG_USB_DEVICE_STACKを有効にすると、CONFIG_SERIALも有効でzephyr_udc0も設定しているので自動的にCONFIG_USB_CDC_ACMも有効になるのだろう。

image

issue 1

これを読むと、UART2 は CDC ACM に、UART3 は普通に UART に使うということが書かれている。

nRF5340: issue with USB CDC ACM and UART driver running in parallel - Nordic Q&A - Nordic DevZone - Nordic DevZone

cdc_acm_uartのサフィックスに付いた数字が uart のサフィックスと対応しているということ? この人の場合、エラーはENOSYSではなくENOSETUPなので前提は異なるものの、割り当てが違うのならエラーが違ってもおかしくはないのか。

試しにこうやると、USBシリアルが 2つになった。 zephyr,consolecdc_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,consoleuart0でもcdc_acm_uart0でも設定できるなら、UART のアクセスも uart0 ではなく cdc_acm_uart0 でやればよい。

なのでこの人も、uart2 を CDC ACM に割り当てているわけではなく、単に CDC ACM と UART の両立に失敗しているだけなのかも? ああ、でもエラー値が違うな。 10ヶ月も前なので、ncs v1 とかで状況が違うのかもしれん。

uart_callback_set() v2.6.1

ここの issue に回答した人によると、CDC ACMドライバは ASYNC APIをサポートしていないのでコールバックを設定しようとするとENOSETUPを返すと答えているけれども、今の実装を見ると CONFIG_UART_ASYNC_APIが未定義だったら-ENOTSUPになっている。 うちの-88api->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->nameuart@8000だった。 これはuart0の開始アドレスと一致したので、すり替わっているわけではないだろう。

uart->apistruct 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_set0x00 が代入されることになったのだろう。

image

よし、まあまあ進展した。
CONFIG_UART_0_ASYNCについては次回だ。

< Top page