M5NanoC6 をCircuitPythonで動かす

## はじめに

M5NanoC6、小さいのに高機能でとても魅力的なパッケージですよね！M5社製品あるあるですが、使い道が決まる前に反射神経でスイッチサイエンスさんで購入していました（笑）。久しぶりにArduino IDEと格闘するか～と思っていたのですが、ふとCircuitPythonのサイトを見るとesp32c6_devkitのファームウェアがあったので、これで動くかどうか試したところうまく動いたので紹介しておきます。

M5NanoC6のIRからNEC IR信号をM5AtomS3＋IRに送信して表示するの図
![スクリーンショット 2024-04-10 151124.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103775/537d21de-a246-69f0-364b-0bcad25ece6d.png)


## 方針

まずesp32c6のUSBはJTAGのみで、USB Storageにできません。また、Bluetoothは搭載しているもののCircuitPython側がesp32c6のBLEに対応していません。よって接続方法は、**WIFI接続によるWebワークフロー一択**となります。WIFI接続は多少不安定なところがあり、最初は心配になりますが、慣れるとリトライのタイミングとかが分かってきてむしろ超絶便利なので頑張って設定していきましょう（といっても大した手間ではないです）。

## CircuitPythonのダウンロード

ESP32-C6のフラッシュ4MBなので、以下をダウンロードします
https://circuitpython.org/board/espressif_esp32c6_devkitm_1_n4/

9.0.xでも動いていましたが、より新しいESP-IDFに対応している9.1.x系で試しています。

## ファームウェア書き込み

ファームウェア書き込みモードとして起動するには、ボタンを押しながらUSB-Cを接続します。esptoolを使ってダウンロードした.binファイルを書き込みます。

```
.\esptool.exe --chip esp32c6 --port COM6 --baud 115200 --before default_reset --after hard_reset --no-stub  write_flash --flash_mode dio 0x0 .\adafruit-circuitpython-espressif_esp32c6_devkitm_1_n4-en_US-9.1.0-beta0.bin
```

ちなみに[ブラウザからの書き込みツール](https://adafruit.github.io/Adafruit_WebSerial_ESPTool/)はChip ID未登録でできないようです。

## WIFI APの設定

基本となるドキュメントは[こちら](https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor/overview)になります。

USBシリアルで接続します。もし見つからなかったら、Arduino IDE でUSB CDC on BootをOnにしてみてください。接続できたらREPLから以下のようにPythonでsettings.tomlというファイルを作成するスクリプトを実行します（複数行まとめてコピペして大丈夫です）。

```
f = open('settings.toml', 'w')
f.write('CIRCUITPY_WIFI_SSID = "ssid"\n')
f.write('CIRCUITPY_WIFI_PASSWORD = "password"\n')
f.write('CIRCUITPY_WEB_API_PASSWORD = "webpassword"\n')
f.close()
```

無事書き込んだらリセット（抜き差し）して、APに接続されるか確認しましょう。

## オンラインエディタの起動

http://circuitpython.local/code/ をブラウザで表示すると、ローカルネットワークからマイコンを探し出して接続できます。

ESP32-C6特有の現象かは分かりませんが、以下のケースで何回かリトライが必要なことがあります。

- マイコン検出しない: ブラウザのタブ閉じてリロード
- ファイル保存でヘビぐるぐるのままになる: ブラウザのタブ閉じてリロード(実際は保存が完了していることが多いです)
- ファイル保存後、プログラムの実行が止まったように見える: エディタで止まっているように見えるのか、本当に止まっているのかは切り分けられませんでしたが、このようになったらSave+Runを押してみてください

また、他のマイコンよりコンパイルの時間が少し長い（10秒弱）気がしますので、保存後すぐ動作しなくても、少し待ってみてください。

## PIN定義

espressif_esp32c6_devkitm_1_n4 のピン定義は以下のようになります。M5NanoC6で引っ張られていない端子もありますので、[仕様](https://docs.m5stack.com/ja/core/M5NanoC6)を確認し、使用できる端子をアサインします。

GROVEを使う場合はIO1/IO2になります。

```
BUTTON          IO0             IO1             IO10
IO18            IO19            IO2             IO20
IO21            IO3             IO4             IO5
IO6             IO7             IO8             IO9
MTCK            MTDI            MTDO            MTMS
NEOPIXEL        RX              TX              UART
__dict__        board_id
```
※上記はimport boardして "board."→"タブ"で表示されます。

エイリアスがついている端子は以下のような関係になります。
```

    { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO20) },
    { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO21) },

    { MP_ROM_QSTR(MP_QSTR_MTMS), MP_ROM_PTR(&pin_GPIO4) },
    { MP_ROM_QSTR(MP_QSTR_MTDI), MP_ROM_PTR(&pin_GPIO5) },
    { MP_ROM_QSTR(MP_QSTR_MTCK), MP_ROM_PTR(&pin_GPIO6) },
    { MP_ROM_QSTR(MP_QSTR_MTDO), MP_ROM_PTR(&pin_GPIO7) },

    { MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_PTR(&pin_GPIO9) },
    { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO8) },
```

## Lチカ

ボタンでLチカです。単純なIO7のLEDを使用しています。

```
import time
import board
import digitalio

button = digitalio.DigitalInOut(board.IO9) # デフォルトでは入力
button.pull = digitalio.Pull.UP # 内部のプルアップ抵抗を有効化する


led = DigitalInOut(board.IO7)
led.direction = digitalio.Direction.OUTPUT

while True:
    led.value = not (button.value)  # False == ボタンが押された
    time.sleep(0.2)
```

## Neopixelチカ

neopixel_writeの点灯です。

```
import time
import board
import neopixel_write
import digitalio

ledpwr = digitalio.DigitalInOut(board.IO19)
ledpwr.direction = digitalio.Direction.OUTPUT
ledpwr.value = True

led = digitalio.DigitalInOut(board.IO20)
led.direction = digitalio.Direction.OUTPUT

neopixel_write.neopixel_write(led, bytearray([0, 0, 0]))　#消灯初期化
time.sleep(0.1)
neopixel_write.neopixel_write(led, bytearray([0, 128, 128])) #紫
time.sleep(1)
neopixel_write.neopixel_write(led, bytearray([0, 0, 0])) #消灯
```

## Lチカ＋IR送信

ボタンを押すとLEDが光ったのち東芝のテレビの電源ボタンのIR送信をするコードも試してみました。実はIR送信周りは盛大にハマっていて、Adafruit_CircuitPython_IRRemoteライブラリに色々修正のPRをmergeしてもらいました。

動作を試してみましたが、カタログ値通り(直線で５m)M5Atomの時より圧倒的に遠くまで信号が届くようで、SmartHome向けにはかなり良い選択になりそうです！

```
import time

import board
import gc
import digitalio
import pulseio
import adafruit_irremote

button = digitalio.DigitalInOut(board.IO9)  # Default in
button.pull = digitalio.Pull.UP  # enable pull up resistor

led = digitalio.DigitalInOut(board.IO7)
led.direction = digitalio.Direction.OUTPUT

# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
pulseout = pulseio.PulseOut(board.IO3, frequency=38000, duty_cycle=22000)
# Create an encoder that will take numbers and turn them into NEC IR pulses
low = 560
high = 1700
encoder = adafruit_irremote.GenericTransmit(
    header=[9000, 4500], one=[low, high], zero=[low, low], trail=low
)

while True:
    if not button.value:
        led.value = True
        print(f"{button.value} {gc.mem_free()}")
        encoder.transmit(pulseout, [0x02, 0xFD, 0x48, 0xB7])
        time.sleep(0.5)
        led.value = False

    time.sleep(0.25)
```

メモリが気になっていたので残メモリ出していますが、特にこのコードレベルなら厳しくはなさそうでした。

## さいごに

超小型サイズなのに必要な機能を備えOTAでファームウェア更新できるM5NanoC6は、マイコン選定時に有力なオプションになりそうです。すこしづつCircuitPythonのいろいろな機能をM5NanoC6上で試していきたいと思います。