RISC-VマイコンCH32V003J4M6をGemini Code Assistで開発してみた
はじめに
前回の記事では、フラッシュメモリがわずか2kBしかないAVRマイコン「ATTiny202」でGemini Code Assistを試しました。
今回は予告通り、しばらくジャンク箱の中で眠っていたRISC-Vの激安8ピンマイコンCH32V003J4M6を使って、Arduino IDEでGemini Code Assistの支援を受けながらどこまで開発できるか試してみました。
AVRに比べてネット上に情報が少ないと思われるRISC-Vマイコンで、生成AIはどこまで役に立つでしょうか?
CH32V003とは?
CH32V003は、WCH(南京沁恒微电子)社製のRISC-Vマイコンです。秋月電子で容易に入手できる2つのバリエーションがあります。
- CH32V003F4P6(20ピン):50円、ピッチ0.65mm(要ピッチ変換基板)
- CH32V003J4M6(8ピン):40円、ピッチ1.27mm(手はんだ可能)
10円の違いですが、20ピン版は周辺機能へのピン割り当ての自由度が高く、8ピン版は手はんだでもいけそうなピッチです。
今回は、前回のATTiny202と同様のサイズ感のCH32V003J4M6を使ってみます。
プロジェクト1:電池電圧モニター
要件定義
最初に試したプロジェクトは以下の要件です。
CH32V003J4M6をArduino IDEで開発します。電源はNi-MH単3電池1本(1.2V)とします。
HT7733Aを使用したDC-DCコンバータで1.2V→3.3Vに昇圧し、CH32V003J4M6の電源とします。
CH32V003J4M6は電池電圧値をADCで読み取って、シリアル通信でPCに送信し、PC上のターミナルソフトで表示します。
この要件をGemini Code Assistに指示し、スケッチを出してもらいました。
ハマりポイント:Gemini Code Assistでは解決できなかった問題
しかし、いくつかの問題でハマりました。これらはGemini Code Assistに聞いても解決できず、ネット検索で先人のアドバイスを見つけて解決しました。
(1) 一旦プログラムを書き込むと、再度書き込めなくなる
これは書込みピンがUARTのTXに変更されるためでした。
解決法:WCH-LinkUtilityで「Clear All Code Flash-By Power off」をクリックすると再度書き込めます。
WCH-LinkEの3V3を4番ピンに、GNDを2番ピンに、SWIOを8番ピンに接続
(2) ADCが動かない
Arduino IDEにインストールした直後のWCHボード定義(CH32V EVT Boards Support)ではADC機能がdisableされているためでした。
解決法:定義ファイルを修正してenableに変更します。
修正が必要なファイル(Windows 11の場合)
ファイル1:
C:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\WCH\hardware\ch32v\1.0.4\variants\CH32V00x\CH32V003F4.h
以下の行の//を取って有効化:
#define ADC_MODULE_ENABLED
ファイル2:
C:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\WCH\hardware\ch32v\1.0.4\system\CH32V00x\USER.cpp
クロック設定を外部供給(HSE)から内部供給(HSI)に変更:
//#define SYSCLK_FREQ_8MHz_HSI 8000000
//#define SYSCLK_FREQ_24MHZ_HSI HSI_VALUE
#define SYSCLK_FREQ_48MHZ_HSI 48000000
//#define SYSCLK_FREQ_8MHz_HSE 8000000
//#define SYSCLK_FREQ_24MHz_HSE HSE_VALUE
//#define SYSCLK_FREQ_48MHz_HSE 48000000
(3) Geminiではマニュアルとは異なるピン定義がなされる
誤りを指摘してもさらに誤った情報が出てきました。そこでマニュアルのピン配置を参考にして、実際にピンを総当たりし、シリアルが出力されるピンやADCが機能しているピンを見つけました。
プロジェクト1のハードウェア構成
Ni-MH単3電池の電圧を読み取るハードウェア構成です。
プロジェクト1のスケッチ
Gemini Code Assistで生成し、手直ししたスケッチです。
//CH32V003J4M6のピン定義
//1:NC
//2:GND
//3:ADC IN VccとGND間をボリュームで分圧して加える
//4:Vcc 3.3V
//5:NC
//6:NC
//7:NC
//8:UART TX および SWIO
// アナログ入力ピンを定義 A0は3番ピン
#define ANALOG_PIN A0
// ADCの最大値(CH32V003は10bit ADCなので、0から1023までの値をとります)
const int ADC_MAX_VALUE = 1023;
void setup() {
// シリアル通信を開始 (ボーレート: 115200 bps)
Serial.begin(115200);
// アナログ入力ピンを設定
pinMode(ANALOG_PIN, INPUT_ANALOG);
}
void loop() {
// 1. アナログ値を読み取る
int readValue = analogRead(ANALOG_PIN);
// 2. 物理電圧値(ボルト)に変換する
// CH32V003のVCC(電源電圧)を3.3Vとして計算
float voltage = (float)readValue * (3.3 / ADC_MAX_VALUE);
// 3. 読み取った値と変換した電圧をシリアルポートに送信する
Serial.print(readValue);
Serial.print(", ");
Serial.print(voltage, 3); // 小数点以下3桁まで表示
Serial.println("V");
// 読み取り間隔の調整
delay(1000); //1秒ごとに値を読み取り、送信
}
プロジェクト1の動作確認
PCのターミナルソフトで電圧値が表示されることを確認しました。
プロジェクト2:Ni-MH電池リフレッシュ回路
要件定義
プロジェクト1を応用して、Ni-MH単3電池のリフレッシュを行う回路を作ってみました。メモリ効果を解消するために、Ni-MH電池を1.0Vまで放電させる回路です。
回路の動作
1. リフレッシュ対象のNi-MH単3セルを電池ケースにセット
2. 起動SWを放電中LEDが点灯するまで押す
3. HT7733AでNi-MH電池電圧が3.3Vに昇圧され、CH32V003J4M6が起動
4. 7番ピン(A2)のADC入力で電池電圧を取得、1.0V以上なら5番ピン(PC1)にHighを出力
5. PC1がHighでDMG3414がON、起動SWを離しても動作継続
6. 10Ωの負荷抵抗で電池から約100mA消費させる
7. 1秒毎に電圧を取得し、8番ピン(UART TX)に出力
8. 1.0Vを5回連続で下回るとPC1にLowを出力、放電終了
プロジェクト2のハードウェア構成
プロジェクト2のスケッチ
スケッチ1を手直しして作成しました。
//CH32V003J4M6のピン定義
//1:NC
//2:GND
//3:NC
//4:Vcc 3.3V
//5:LED OUT
//6:NC
//7:ADC IN
//8:UART TX SWIO program時 再書き込みにはClear All Code Flash-By Power off
// アナログ入力ピンを定義 A2は7番ピン
#define ANALOG_PIN A2
// デジタル出力ピンを定義 PC1は5番ピン
#define LED_PIN PC1
// ADCの最大値(CH32V003は10bit ADCなので、0から1023までの値をとります)
const int ADC_MAX_VALUE = 1023;
// 1.0Vを下回った連続回数のcount
int count = 0;
void setup() {
Serial.begin(115200);
pinMode(ANALOG_PIN, INPUT_ANALOG);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// 1. アナログ値を読み取る
int readValue = analogRead(ANALOG_PIN);
// 2. 物理電圧値(ボルト)に変換する
float voltage = (float)readValue * (3.3 / ADC_MAX_VALUE);
// 3. 読み取った値と変換した電圧をシリアルポートに送信する
Serial.print(readValue);
Serial.print(", ");
Serial.print(voltage, 3);
Serial.println("V");
// 4. 5回連続で1.0V未満ならば放電停止
if(voltage >= 1.0){
digitalWrite(LED_PIN, HIGH);
count = 0;
}else{
count++;
if (count > 4){
digitalWrite(LED_PIN, LOW);
while(1);
}
}
delay(1000);
}
1. ADC入力ポートを3番ピン(A0)→7番ピン(A2)に変更
2. 放電中LED・DMG3414制御用に5番ピン(PC1)を追加
3. 1.0Vを5回連続で下回る判定ロジックを追加
プロジェクト2の動作確認
リフレッシュ完了直前に5回1.0Vを切ると電源が切れ、放電が終了します。while(1);でループするようにしていますが、実際にはdigitalWrite(LED_PIN, LOW);でCH32V003J4M6の電源が切れています。
CH32V003F4P6でも動作確認
10円高いCH32V003F4P6(20ピン版)でもプロジェクト2は動作することを確認しました。ピン数が多いので、SWIOの機能を上書きしてしまうこともなく、再書き込みできなくなる問題が発生しません。
- ピン2:UART TX
- ピン7:GND
- ピン9:Vcc
- ピン11:PC1(デジタルIO)
- ピン14:A2(ADC入力)
- ピン18:SWIO(スケッチ書き込み時)
Gemini Code Assistの評価
今回の実験を通じて、RISC-Vマイコン開発におけるGemini Code Assistの有用性がわかりました。
良かった点
- スケッチの骨格となる部分が素早く生成できた
- 単純な文法誤りで無駄なコンパイルを繰り返すことがなくなった
- どんどん新しいプロジェクトを試してみることができるようになった
限界
- ネット上に情報が少ないマイコンでは、ピン定義などで誤った情報が出ることがある
- ボード定義ファイルの設定変更などの深い知識が必要な問題は解決できない
- 問題点のスポット的な検索との組み合わせが必要
まとめ
完璧までとはいきませんでしたが、問題点のスポット的な検索と組み合わせれば、RISC-Vマイコンでもかなり楽にスケッチが作れることがわかりました。
特に、骨格となる部分ができてしまうので、どんどん新しいプロジェクトを試してみることができるようになりました。先人の情報に感謝しつつ、生成AIを活用して組み込み開発を効率化していきたいと思います。