144Labの入江田です。
Adafruit_nRF52_Bootloaderですが、そのままArduino環境構築して利用可能なのを発見しました。
「Adafruit nRF52 by Adafruit(adafruit:nrf52) version:0.14.6」 というボードサポートを追加するだけでarduinoコードの実行とシリアルモニタが利用可能でした。
ここではnRF52840やその互換チップorモジュールでの開発にArduinoを使う方法を解説します。
セットアップ
ターゲットにはAdafruit_nRF52_Bootloaderが書き込み済みであることが前提です。 書き込み済みの製品(例えばSparkfan nRF52840 miniなど)を入手するか、 JLink、DAPLink、OpenOCD、nrfjprogなどのJTAG/SWD系ツールでnRF52840のターゲットにブートローダーを書き込んでおきます。
予めPCにインストールするもの
arduino-cli.yaml(macOS:
proxy_type: auto sketchbook_path: <HOME>/Documents/Arduino arduino_data: <HOME>/Library/Arduino15 board_manager: additional_urls: - https://www.adafruit.com/package_adafruit_index.json
arduino-cli core update-index arduino-cli core install adafruit:nrf52
ターゲットボードに「Nordic nRF52840DK(PCA10056)」を選べば、 とりあえずUSBにつながっているnRF52840互換チップでファーム開発ができます。
Makefile例
NAME := <アプリフォルダ名> FQBN := adafruit:nrf52:pca10056 PORT := /dev/tty.usbmodem####<-USB-CDCデバイス名 depends: pip3 install --user adafruit-nrfutil build: arduino-cli compile -b $(FQBN) $(NAME) adafruit-nrfutil dfu genpkg --dev-type 0x0052 \ --application $(NAME)/$(NAME).$(subst :,.,$(FQBN)).hex \ $(NAME)/$(NAME).$(subst :,.,$(FQBN)).zip upload: arduino-cli upload -b $(FQBN) -p $(PORT) $(NAME)
利用例
> make build ... Zip created at ???/???.adafruit.nrf52.pca10056.ino.zip > make upload ... ######################################## ######################################## ### Activating new firmware Device programmed.
個人的にはIDEよりもarduino-cliとMakefileの方が再現性が高く便利だと思います。 (そのままDockerfileに書いても動作させやすいですし、TravisCIやCircleCIに載せるのも簡単です)
nRF52をArduinoで開発してみて辛かったところ
- メジャーではないターゲットの情報源はソースコードのみ
- コア機能がターゲット別に結構差異がある
- 特に割り込み関連
- 要するに抽象化の仕組みがほとんどない
- SPIまわりがターゲットごとにハードコードされていて、類似ターゲットで動かそうとするとちょっとしたワークアラウンドが必要
- これはArduinoの方針の問題かな。一通りの機能を有効化してユーザーアプリケーションを起動するスタイル。しかしライブラリはそうではなく、活性化用の関数がある。後者で統一してほしいところ。
今のところ把握しているワークアラウンド
ピン割り込みの罠
LOWとかいてるところ、nRF52の場合LOWとHIGHを指定すると黙って失敗します。 ソースコードには対応してるのはRISINGとCHANGEとFALLINGのみと書いていました。
attachInterrupt(GPIO, handler, LOW);
状況に応じて割り込みを保留してあとで割り込み発生させたい事例の場合、 「LOW」を指定する代わりに「FALLING|ISR_DEFERRED」を指定すると良いでしょう。
タイマー割り込み
タイマー割り込みは実質ターゲットアーキテクチャ別のライブラリが必要です。 Cortex-M(ARM)系は決まった名前の割り込みハンドラを上書き宣言すればOKというのは共通です。
extern "C" void TIMER2_IRQHandler(void) { if ((NRF_TIMER2->EVENTS_COMPARE[0] != 0) && ((NRF_TIMER2->INTENSET & TIMER_INTENSET_COMPARE0_Msk) != 0)) { NRF_TIMER2->EVENTS_COMPARE[0] = 0; // Clear compare register 0 event } ...TODO implement } void startTimer(unsigned long us) { NRF_TIMER2->TASKS_STOP = 1; NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer; // Set the timer in Counter Mode NRF_TIMER2->TASKS_CLEAR = 1; // clear the task first to be usable for later NRF_TIMER2->PRESCALER = 4; // Set prescaler. Higher number gives slower // timer. NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos; NRF_TIMER2->CC[0] = us; // Set value for TIMER2 compare register 0 // Enable interrupt on Timer 2, both for CC[0] and CC[1] compare match events NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos; // Clear the timer when COMPARE0 event is triggered NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos; NRF_TIMER2->TASKS_START = 1; // Start TIMER NVIC_EnableIRQ(TIMER2_IRQn); }
I2CをカスタムPINで使う方法
ほとんどのボードではI2Cは一つしか使われないのを利用して、 二つ目のI2Cをセットアップする形で利用可能です。 (一つ目を再定義し直すことは簡単にはできなかった。できる方法がある?) ただし、利用する側は下記コードにある「Wire1」インスタンスを使う必要があります。
TwoWire Wire1(NRF_TWIM1, NRF_TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQn, SDA_PIN, SCL_PIN); extern "C" void SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler(void) { //Serial.println(F("Wire1.onService()")); Wire1.onService(); }
まとめ
- USBデバイスタイプのシリアルポートは利用可能にするまでのハードルが高く、扱いにくい面があったけれど、この手法の場合ブートローダーがセットアップ済みのUSB-CDCデバイスを引き継いでデバッグ用の入出力機構として利用可能です。
- シリアルモニタが最初から使えるのは大きなアドバンテージです。
- Arduinoの豊富なライブラリのうちCPUアーキテクチャに依存のない物が利用可能です。
- 一部標準のArduinoの機能が利用できないがそのうちの一部はここに書いたワークアラウンドで利用可能です。
- とにかくシリアルポートさえ確保できればArduino向けブートローダーが実装可能なのはArduinoの強みですね。