ncs: DevAcademy bt-fund Lesson 4 Exer 3 が動かないのはUSB-CDC関係のようだ
2024/08/27
BLE Fundamentals Lesson 4 - Exercise 3をやっているのだが、うまくいかない。
MDBT53ボードには LED が載っているのだが、それが点滅しているのだ。
動画にしたが見えるかな?
正しく動作する場合、デフォルトの LED はステータスLED ということで無限ループの中で点滅させるだけである。 なのでこれはerror()に陥っているのだろう。
いろいろログを出すようにしたり追加したりしたところuart_callback_set()が-ENOSYS
を返していた。
DeviceTree から ncs_nordic_uart
の取得に失敗しているのかも?
USBシリアルからログが出たのでなんとかなるかと思ったが、DevAcademy Fundamentals(BLE ではない) の UART をやっておこう。
Fundamentals Lesson 5 UART
(UART は Lesson 5 だが URL は “lesson-4” なので間違えぬよう)
さて Zephyr には UART にアクセスする API が 3種類ある。
- polling
- read:
uart_poll_in()
(non-blocking) - send:
uart_poll_out()
(blocking)
- read:
- interrupts-driven
- raw interrupts と書いてあるので割り込みレベルで呼ばれるのかも
- asynchronous
- これが一般的らしい。Exercise でやるのもこれだけ。
- EasyDMA を裏で使っている
ちなみにこの Lesson の Exercise は Thingy:53 はサポートしていないらしい。 ブロック図を見るとセンサー類はたくさんあるが UART はなさそうだった。 もしかしたら UART でつないでいるセンサーがあるのかもしれんが、まあ深くは調べなくて良かろう。
ダメだった
で、この Exercise も MDBT53ボードでは動かなかった。
USB を有効にしているとuart_callback_set()
が-ENOSYS
を返す。↑と同じだ。
ボード定義ファイル で USB を使うようにしていない場合は動いている。
DeviceTree の扱い方がよくわかっていないせいか?
その Exercise の中だったと思うが、Lesson 2 へのリンクがあった。 「Reading buttons and controlling LEDs」というタイトルだったので中身も見ずにスルーしたのだが、DeviceTree の説明がここにあるじゃないか。。。 DevAcademy Intermediate の方にボード定義ファイルの Lesson があったから、てっきりそこしかないと思い込んでいたよ。。。
Devicetree Guide(これは ncs v2.6.1)というものはあるが、かなり膨大だ。 Lesson をやることにしよう。
Lesson 2 – Reading buttons and controlling LEDs
Devicetree
Devicetree source、以下 DTSファイルはだいたいこんな感じだ。
/dts-v1/;
#include <なんとか.dtsi>
#include "かんとか.dtsi"
/ {
a-node {
subnode_label: a-sub-node {
foo = <3>;
};
};
};
&gpio0 {
status = "okay";
};
...
/
はルートで、全部この中に含まれるのかと思いきや、上に書いた&gpio0
のようにその外側に書かれるものもある。
#include
に<...>
と"..."
があるのは C/C++ と同じだろう。
拡張子は、DTSファイル本体は.dts
、includeされる方は.dtsi
が一般的なようだが、別に.dts
を includeしてもよいし .h
を include しているファイルもあった。
プリプロセス的なものはそのくらいか。 これがあるせいで、といっては変だが nRF Connect for VScode で Visual Devicetree Editor で編集するのが難しい。 単一ファイルならよいけど、編集した内容をどのファイルに反映させるかなんてツールでは分からんしね。
/
はファイル中に何回出てきても大丈夫- パス指定のようなもので、ここから
root
ですよ、くらいの目印だろう
- パス指定のようなもので、ここから
root
ノードの中には複数のノードを書くことができる- ノードの中にはサブノードを書くことができる
- ラベルは付けられるのか?→ 付けられる
- サブノードにはサブノードラベルを付けることができる
- ラベルはなくてもよいし、複数付けることもできる
- プロパティはサブノードの中にしか置けない?→ ノードの中にも書ける
ラベル
なんか、ノード1つに複数のラベルを付けられるなら配置として ノード: ラベルたち
となりそうなのだけど違うのだ。
最近の言語みたいに変数名: 型
と思っておけば良いか。
ラベルの役割だが、Devicetree 内の他の場所にあるノードを参照するためのものだそうだ(intermediateにも書いてあった気がする)。
何か理由があるのだろうけど、なんでノード名で参照できないのだろうか。
サブノードだと/a-node/a-sub-node
みたいに書くのが長くなるからか?
ラベルとは別に aliases
というノードもあるが、英語そのままなら「別名」だがラベルと何が違うのか。
どうも、別名はノードに付けるのではなくラベルに付けるようだ。
別名の方は Cソースから使うものだそうだ。そうすることで、DTSファイルの変更をすれば Cソースの方はそのままいける、というメリットがあるそうだ。
DTSファイルを見てどれが有効な設定なのか調べるのは大変なので、Visual Devicetree Editor で Alias のところだけ見れば良いというのがよいのかもしれん。
まあ、そのうちわかるだろう。
aliases {
subnode_alias = &subnode_label;
};
サブノードにしかラベルを付けられない、とは書いていないのでノードにも付けられるのだろう(付けてた)。 プロパティも同様。
ノード名が led_0
、そのラベル名が led0
、エイリアスも led0
。
DK 上では LED1~4 だけど定義では led0
~3
なのでエイリアスはそちらにあわせるのか。
なんとなくだが、dk_buttons_and_leds
では LED1~4 を使っているのでエイリアスも 1 から始めた方がわかりやすいような?
まあ、ここら辺の感覚は人それぞれか。
pinctrl を見ているとノード名とラベル名が同じになっていたので、そうなると何の名前を使っているのかさっぱりわからん。。
&
は参照のマーク。
他のノードを参照する場合はラベル名を使うようだし、&ラベル名
というパターンしかないのか?
aliases
は &ラベル名
の形で使うようだから、
Devicetree bindings
ときどき出てくる compatible
。
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
};
“Devicetree bindings”というもので、DTSファイルだけでは足りない情報を補足する機構らしい。 DTSファイルは半分で bindings は残り半分というくらい重要な情報とのこと。
compatible
の例が載っていたが、ボード定義ファイルの中にあるYAMLファイルはcompatible
が入っていないが同類だろう。
プロパティ名やプロパティが取り得る値の説明をひとまとめにして名前を付けている。
定義済みの Devicetree bindings は ここ にある。
すべてのノードについてcompatible
があってもおかしくないと思う。
しかし UART なんかはない。Zephyr の中にもない。
arm,cmsdk-uartだろうか?
とにかく compatible
の指定がない。
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
};
pinctrlには description
に出てきはしているが “default” とか “sleep” とかよくわからん。
Pin control devicetree file
LED とボタン以外のピンマッピングは pinctrl ノードでやるようだ。
それ用に -pinctrl.dtsi
というファイルに分けることが多いようだ。
pinctrlも説明が長い。。。 すまんがスルーだ。
nRF5340DKの pinctrl で uart0
はこうだった。
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 20)>,
<NRF_PSEL(UART_RTS, 0, 19)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 22)>,
<NRF_PSEL(UART_CTS, 0, 21)>;
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 20)>,
<NRF_PSEL(UART_RX, 0, 22)>,
<NRF_PSEL(UART_RTS, 0, 19)>,
<NRF_PSEL(UART_CTS, 0, 21)>;
low-power-enable;
};
};
Raytac MDBT53 ではこうなっていてuart0_default
のグループ数が違った。
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 20)>,
<NRF_PSEL(UART_RX, 0, 22)>,
<NRF_PSEL(UART_RTS, 0, 19)>,
<NRF_PSEL(UART_CTS, 0, 21)>;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 20)>,
<NRF_PSEL(UART_RX, 0, 22)>,
<NRF_PSEL(UART_RTS, 0, 19)>,
<NRF_PSEL(UART_CTS, 0, 21)>;
low-power-enable;
};
};
グループは、グループ単位でプロパティの設定ができるらしい。 なので 2つに分かれていたらそれぞれ細かく設定できるともいえるし、それぞれに設定しないといけないとも言えるしで、深く考えなくても良いのかな。
アクセス
先ほど aliases
は Cソースから使うと書いたが、DT_NODELABEL()
でラベルも使えるらしい。
なんじゃそりゃ。
まあ、ラベルはノードを参照するために使うし、aliases
で元になるのもラベルだからおかしくはないのか。
DT_NODELABEL(ラベル名)
やDT_ALIAS(別名)
で得られるのはノードIDというものらしい。
ちなみにラベル名や別名は"..."
で囲まない。
まあ、間違えたらコンパイルエラーになるだけだ。
DEVICE_DT_GET(ノードID)
で取得したデバイスポインタを汎用APIに与えて実際のデバイスドライバを経由してハードウェアにアクセスする。
つまり、アプリにはあまりハードウェアへのアクセスを直接記述しないということだ。
こういうフレームワークを使う以上、アプリとそれより下の階層を分離するくらいは負荷にならないということだろう。
ただ、こういうマクロやdevice_is_ready()
のような汎用なものだけでなく、各ドライバ(Peripherals)が提供しているものもあるそうだ。
そして推奨は後者の方とのこと。 Devicetree からより多くの情報を取ってくるからという理由だ。
なんとなく「汎用の方を使った方が変更になったときも対応できるので推奨します」になるかと思ったので意外だった。
ダメな点の振り返り
Fundamentals Lesson 5 - Exercise 1を振り返る。
MDBT53のボード定義をそれぞれ試した。
cpuapp
とcpuapp_ns
は OKcpuapp_usb
とcpuapp_usb_ns
はダメ
となると USBシリアルに対応したどこかがよろしくないということだ。
大きな差分はこれくらいだと思う。
/ {
chosen {
zephyr,console = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};
CONFIG_USB_DEVICE_STACK=y
CONFIG_UART_LINE_CTRL=y
&zephyr_udc0
は pinctrl だからそんなに影響ないと思ったのだが、そのブロックをコメントアウトして zephyr,console
をuart0
にすると動作した。
zephyr,console
だけuart0
にしてもダメだった。
まさか pinctrl だけで UART がうまくいかなくなるとは。。。 しかしなぜ?
CDC ACM
CDC ACM は UART driver API を使うとか 4 つまではインスタンスをサポートするなどは書いてあるが UART が使えなくなるようなことは書かれていない。
わからん。。。