Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CH32V003開発ガイドブック Arduino抜粋版

本サイトは技術同人誌『CH32V003開発ガイドブック』の中から、Arduinoについて抜粋し、さらにArduinoの利用に便利なように追記したものです。 とっつきにくいCH32V003にふれるファーストステップとしてArduinoを活用してもらいたく、Arduinoに絞った内容を無料で公開することにしました。 本サイトをきっかけに、CH32V003に興味を持っていただければ幸いです。

CH32V003について、さらに使いこなしたいと思った場合には、元の書籍『CH32V003開発ガイドブック』をお求めいただければ幸いです。 『CH32V003開発ガイドブック』の販売ページはこちらです。

CH32V003開発ガイドブック — スイッチサイエンス
https://www.switch-science.com/products/10547

CH32V003開発ガイドブック[74TH-B018] - 74th Books & Gadgets - BOOTH
https://74th.booth.pm/items/6934072

元本では、公式SDK、ch32fun、直接レジスタを操作する方法など、CH32V003の開発について幅広く解説しています。 公式SDKとレジスタ操作が必要な、『DMAとTimer』『WatchDogTimer』『省エネルギーモード』については、抜粋版には記載がありません。 元の本を参照をお願いします。

まえがき

CH32V003はWCHが開発した非常に安価なRISC-V MCUです。 筆者は、様々な電子工作の製作に、コストを気にせずに組み込んでいます。 CH32V003を使い始めて2年以上が経ちましたが、今や筆者の電子工作に欠かせない存在となっています。

UIAPさんが、CH32V003を使った開発ボードを頒布し、CH32V003での開発の敷居を下げてくれています。 そこでより開発に便利なように、CH32V003について私の書籍から、Arduinoの部分を抜粋した部分が公開されていると活用できると思い、本サイトを作成しました。 もしよりCH32V003を深く開発したいと思っていただいたり、よりCH32V003の開発について知りたいと思っていただけならば、元の書籍を購入していただければ幸いです。

おことわり

本サイトは2025年5月31日に発刊した技術同人誌『CH32V003開発ガイドブック』の内容を元にしています。 掲載内容の多くは、執筆時点のものとなります。

リポジトリ

このドキュメントは、GitHubのリポジトリで公開しております。

74th/ch32v003-guidebook-arduino: CH32V003開発ガイドブック Arduino抜粋版
https://github.com/74th/ch32v003-guidebook-arduino

本書の著作権について

全て Atsushi Morimoto (74th) に帰属します。

引用等は構いませんが、本書全体のWebへの転載はご遠慮ください。 こちらのページを見ていただくようにお願いします。

本サイトの内容を変更しない、本サイト全体のPDF、印刷物等の配布はOKです。 ただし、本サイトのArduino抜粋版のみを許可します。 元の書籍『CH32V003開発ガイドブック』は再配布の許可の範囲外となります。

右上のプリンターアイコンから、印刷ができます。 またWindows/MacOSであればPDFとして保存できます。

2. Arduinoでの開発

Arduinoは、Arduino IDEとArduino APIを実装したArduinoライブラリを使った開発環境です Arduino APIでCH32Vが開発できる環境をWCH社が提供しています。

本章ではArduino IDEでの開発方法と、各機能の使い方を説明します。 各Peripheralの章でArduino APIを使った実装方法とその注意点について解説します。

CH32V003用のArduinoライブラリには、WCH社が提供するArduino Core CH32と、arduino-wch32v003があります。 本書ではArduino Core CH32を利用する場合の説明を記述します。

2.1. Arduinoでの開発の準備

本書ではArduino IDE 2.xを例に説明します。 Arduino IDEのインストールについては、Webで「Arduino IDE」と検索するとArduino公式サイトが見つかり、ダウンロードできますので、それをPCにインストールしてください。

Arduino IDEを起動し、メニューバーから基本設定(Settings)を開きます。 "追加のボードマネージャのURL(Additional board manager URLs)"から横のボタンを開き、URL入力のテキストエリアを開きます。

Arduino基本設定

ここに以下のURLを1行追加します。

一般的なCH32V003の場合

https://raw.githubusercontent.com/robinjanssens/WCH32V_board_manager_files/main/package_ch32v_index.json

UIAPduinoの場合

https://github.com/YuukiUmeta-UIAP/board_manager_files/raw/main/package_uiap.jp_index.json

ここには、公式リポジトリ https://github.com/openwch/arduino_core_ch32 ではなく、サードパーティのリポジトリを指定しています。 これは、公式リポジトリに取り込まれたすべてのPRが2025年3月時点ではボードマネージャから利用できるパッケージにまだ反映されておらず、一部の機能が使えないためです。 UIAPduinoの場合、専用のパッケージが用意されています。 現状、サードパーティが提供する上記のパッケージを使用するようにしましょう。

次に左のアクティビティバーから、ボードマネージャータブを開きます。 タブ内の検索欄に、図の様に入力してパッケージを検索し、インストールを押します。

Board Managerからパッケージのインストール

Windowsの場合、Error: 13 INTERNAL: Cannot install tool WCH:beforeinstall@1.0.0: extracting archive: Create linkというエラーが出ることがあります。 これはインストール時に管理者権限を利用しているために発生することがあります。 このエラーが出た場合には、一度Arduino IDEを終了し、Arduino IDEのアイコンを右クリックして「管理者として実行」を選択して起動してください。 一度インストールを行った後は、再度Arduino IDEを再起動し、通常ユーザで利用できます。

パッケージがインストールされ、CH32V003のボードが選択可能になりました。 次にタイトルメニューバーから、利用する環境に合わせて、以下の通りに選択します。

  • 公式Arduinoの場合: 「ツール」→「ボード」→「CH32 MCU EVT Boards」→「CH32V00x」
  • UIAPduinoの場合: 「ツール」→「ボード」→「UIAPduino」→「Pro Micro CH32V003」

さらに、メニューバーの「ツール」から以下の設定もします。

  • Clock Select: 48MHz Internal(内部クロックを利用します)
  • Upload method:
    • 公式Arduinoの場合: WCH-SWD(WCH-LinkE経由で書き込みます)
    • UIAPduinoの場合: minichlink(UIAPduinoのUSB経由で書き込みます)

2.2. まずLチカを成功させる

ArduinoでのLチカをしてみましょう。 Lチカとは、LEDを点滅する簡単なコードのことですが、これでビルドと書き込みができることで、開発環境が整ったことを確認できます。 タイトルメニューから「ファイル」→「スケッチ例」→「01.Basics」→「Blink」を選択すると、サンプルコードが開きます。 このサンプルコードはLED_BUILTINに定義されたピンに繋がれたLEDを点滅させる内容となっています。 CH32V003ではLED_BUILTINは未定義となっているため、代わりにPC0と置換します。 以下のように書き換えます。

void setup() {
  pinMode(PC0, OUTPUT);
}

void loop() {
  digitalWrite(PC0, HIGH);
  delay(1000);
  digitalWrite(PC0, LOW);
  delay(1000);
}

次にWCH-LinkEとCH32V003において、以下の配線を行います。 なお、UIAPduinoの場合にはWCH-LinkEは不要です。

CH32V003WCH-LinkE
VCC3V3
GNDGND
D1SWDIO

そして、PC0に抵抗(電流制限に利用)とLEDを直列に接続します。 回路図にすると以下のような形になります。

Lチカのテストの配線

配線後に、WCH-LinkEをPCに接続しましょう。 UIAPduinoの場合には、ボード上のボタンを押しながらUSBケーブルをPCに接続します。

さて、Arduino IDEでファームウェアを書き込み、上部のツールバーアイコンの右矢印(→)マーク「書き込み」をクリックします。 このときエラーが表示されても、出力タブに「Verified OK」と表示されていれば成功です。 UIAPduinoの場合には、「Image written.」と表示されれば成功です。

ビルドと書き込み

2.3. Arduinoでのファームウェアの開発

Arduinoでのピンの名前の定義

Arduino IDE内では、ピンの名前としてPC0や、PD2といったようにP<GPIO名><ピン番号>という形で定義されています。

なお、PC_0という定義も存在しますが、こちらはArduinoライブラリの内部定数のため、使用しないようにしてください。

ログ出力

CH32VシリーズにはSDI-PrintというSWIO経由でのログ出力の仕組みがあるのですが、CH32V003のArduinoでは未実装となっています。 UART経由でログ出力するしかありません。

UARTのTX(送信)はPD5が使われます。 PD5と、WCH-LinkEのRXを接続してください。

また、SOP-8パッケージのCH32V003J4M6ではUARTのデフォルトのTX(送信)ピン PD5 がSWDIOピンの PD1 と共用されているため、一度UARTを有効にするとSWIOが無効になりSWIO経由での書き込みができなくなります。 詳しくは「12.6. CH32V003開発で知っておくと良いこと」の「SOP-8のCH32V003J4M6のUSART TXとSWIOのピンの共用」に書かれています。そちらを参照してください。

UARTでログ出力の例を示します。 print命令の詳しい使い方は、Arduinoのドキュメントを参照してください。

void setup()
{
  Serial.begin(115200);
  Serial.println("start");
}

int count = 0;

void loop()
{
  Serial.print("loop ");
  Serial.println(count++);
  delay(500);
}

Arduino IDEでシリアルモニタを開くには、まずツールバー中のポートの選択から、WCH-LinkEのポートを選択します。 その次のツールバー右側の虫眼鏡のアイコンをクリックします。 すると下部パネル内にSerial.printで送った内容が表示されます。

Serial Monitor

なお、UIAPduinoでは、一般的なArduinoのようにUSB経由でシリアルモニタを開く機能は現状はありません。

2.4. Arduinoのコードからレジスタを操作する

(本節は上級トピックのため、未掲載です。元本を参照ください。)

2.5. まとめ

以上、Arduino IDEでの開発方法を説明しました。 後の各Peripheralの解説では、GPIOやADCなどのArduinoのAPIの使い方について詳しく説明します。

本書ではArduino APIで各Peripheralを動かしています。 予想よりもきちんと実装されていて、動くことを確認できました。 そのため、Arduinoを使った開発は人に勧めるのに有用だと感じました。

3. GPIO

本章では、GPIOとして、デジタル信号の入力出力を行う方法を説明します。

ここでは、以下のようなスイッチとLEDの付いた回路を制御できるようにします。

LEDとスイッチの回路

3.1. Arduino

Arduinoでの利用方法を解説します。 サンプルコードは以下のリポジトリにあります。

https://github.com/74th/ch32v003-book-code/tree/main/blink_and_read-arduino_core_ch32

まず、各GPIOの値を取得する前に、GPIOの各ピンの設定をする必要があります。 ArduinoではpinMode関数を使って、GPIOの設定を行います。

例えば、入力と出力の設定は以下のように記述できます。

void setup()
{
  // 出力
  pinMode(PC0, OUTPUT);

  // 入力
  // INPUT: フローティング
  // INPUT_PULLUP: プルアップ
  // INPUT_PULLDOWN: プルダウン
  pinMode(PA1, INPUT_PULLUP);
}

pinModeの第2引数に、種別を指定します。

  • OUTPUT: 出力
  • INPUT: 入力、フローティング
  • INPUT_PULLUP: プルアップを有効にした入力
  • INPUT_PULLDOWN: プルダウンを有効にした入力

次に値を取得したり、出力するにはdigitalWritedigitalReadを使います。 たとえば、LEDの点滅をしたり、ボタンの入力を取得する実装は以下となります。

void loop() {
  // ボタンが押されるとLOWになる
  bool btn = digitalRead(PA1);
  if (!btn)
  {
    delay(1000);
    return;
  }

  digitalWrite(PC0, HIGH);
  delay(500);
  digitalWrite(PC0, LOW);
  delay(500);
}

digitalWrite()の第2引数には、HIGHまたはLOWを指定、もしくはbool値の値を入力します。 digitalRead()の戻り値は、HIGHまたはLOWのbool値です。

4. PWM

PWM(Pulse Width Modulation)は、デジタル信号を用いてアナログ信号のように振る舞わせる技術です。 主にモーター制御やLEDの明るさ調整に使用されます。

TimerにはPWMのための機能が含まれています。 例えば256カウントでループするカウンタを作ったとして、PWM機能を使うとTimerのカウントの度に設定した値と現在の値を比べ、大きければGPIOをHighにできます。 これで、比較する値をデューティー比として使い、PWMのパルス幅を調整できます。

なお、1つのTimerで複数のチャンネルを利用することで、異なるパルス幅のPWM信号を同時に出力できます。

4.1. PWMが使えるピン

PWMはTimerを使うため、Timerのアウトプットチャンネルに対応したGPIOピンで利用できます。

Timerの各チャンネルのマッピングが以下の表のようになっています。 デフォルトの他にAFIOを使って別にピンにマップすることもできますが、Arduinoでは動作を確認できていません。

TIM1のアウトプットチャンネルのマッピング

TIM1マッピング (TIM1RM)CH1CH2CH3CH4
Default (0b00)PD2PA1PC3PC4

TIM2のアウトプットチャンネルのマッピング

TIM2マッピング (TIM2RM)CH1CH2CH3CH4
Default (0b00)PD4PD3PC0PD7

4.2. Arduino

Arduinoでの利用は簡単です。 サンプルコードは以下のリポジトリにあります。

https://github.com/74th/ch32v003-book-code/tree/main/pwm-arduino_core_ch32

まず、利用するピンは前節のマッピングのリストを参照してください。 "0b00 (Default)"にリストアップされたピンを利用するようにしてください。 TIM1、TIM2のどちらでも利用できます。 Remapでも動作できるように実装されてはいるようなのですが、動作を確認できませんでした。

ピンの向きを設定します。

pinMode(PD2, OUTPUT);

次にanalogWrite関数を使ってPWM信号を出力します。 値は12bit、0-4095の範囲で指定します。

32段階で明るさをフェードイン、フェードアウトする例を示します。

void loop() {
  for(int i=1;i<=32;i++){
    analogWrite(PD2, 128*i-1);
    delay(31);
  }

  for(int i=1;i<=32;i++){
    analogWrite(PD2, 128*(33-i)-1);
    delay(31);
  }
}

5. ADC

本章では、ADCの使い方を説明します。

ADCとはAnalog-Digital Converterの略で、アナログ信号をデジタル信号に変換するためのコンポーネントです。 ADCは、センサーからのアナログ信号をデジタル信号に変換するために使用されます。

5.1. CH32V003のADC

CH32V003には、10bit分解能のADCが8チャンネル搭載されています。 入力電圧範囲は、電源電圧のVSS(GND)からVDD(VCC)までです。

それぞれのADCは以下のポートに接続されています。

  • PA2: ADC_IN0
  • PA1: ADC_IN1
  • PC4: ADC_IN2
  • PD2: ADC_IN3
  • PD3: ADC_IN4
  • PD5: ADC_IN5
  • PD6: ADC_IN6
  • PD4: ADC_IN7

5.2. サンプルコードの回路

ここでは2軸ジョイスティックの読み取りを行います。 回路図に示すと以下のようになります。

2軸ジョイスティック読み取りの回路図

5.3. Arduino

ArduinoでのADCの実装方法を説明します。 サンプルコードは以下のリポジトリにあります。

https://github.com/74th/ch32v003-book-code/tree/main/adc-arduino_core_ch32

Arduinoでは、ADCチャンネル番号(0-7)を指定する必要はありません。 PA1などGPIOのポート名を指定すれば、対応するADCチャンネルが自動的に選択されます。 もちろん、前述の通りADC機能を持つポートでのみ利用可能です。

まず、ピンの初期化をpinMode()を使って行います。

pinMode(PA1, INPUT_ANALOG);
pinMode(PA2, INPUT_ANALOG);

次にanalogRead()を使って、アナログ値を取得します。 引数にはチャンネル名ではなく、ポート名を指定します。

// 0~1023の範囲で値を取得
uint32_t x = analogRead(PA1);
uint32_t y = analogRead(PA2);

6. UART

UARTとは、2つのデバイス間でシリアル通信を行うためのインターフェースです。 送信側と受信側で通信速度を合わせて動くため、同期のためのクロック信号は必要ありません。 そのため非常にシンプルな配線で通信できます。 また、マスターとスレーブといった親子関係はありません。

MCUとしては同期通信もできるとして、データシートにはUSARTと書かれていますが、本サイトではUARTで統一します。

6.1. CH32V003のUART

CH32V003にはUARTが1つ搭載されています。

以下のポートに接続されています。 AFIOのリマップを使うことで、標準のポート以外でも接続できます。 ただし、ArduinoではAFIOのリマップの機能の動作を確認できませんでした。

UART1マッピング(USART1_RM)TXRXCKCTSRTS
Default (0b00)PD5PD6PD4PD3PC2

UARTによるログ出力でも説明しましたが、SOP-8パッケージのCH32V003J4M6のUART TXは、SWDIOと同じピンを利用しています。 そのため、UART TXを有効にするとSWDIOが無効になり、書き込みを受け付けなくなります。 詳しくは「12.6. CH32V003開発で知っておくと良いこと」の「SOP-8のCH32V003J4M6のUSART TXとSWIOのピンの共用」に書かれています。そちらを参照してください。

6.2. サンプル回路

UARTで動かすツールとして、秋月電子通商でも販売されているCO2センサーMH-Z19Cを使います。 筆者の利用したセンサーは壊れているようでしたが、UARTでの通信の確認に用いました。

MH-Z19Cを読み取るサンプル回路

6.3. Arduino

UARTは、ArduinoではSerialオブジェクトを使用して簡単に利用できます。 サンプルコードは以下のリポジトリにあります。

https://github.com/74th/ch32v003-book-code/tree/main/uart-arduino_core_ch32

まず、Serial.begin()で初期化として、ボーレートを指定します。

Serial.begin(9600);

バイト列を書き込むにはSerial.write()を使います。 この関数は、バイト値1つの送信や、複数バイトの送信、文字列の送信ができます。

// 1バイトの送信
size_t Serial.write(uint8_t val)
// テキストの送信
size_t Serial.write(char *str)
// バイト列の送信
// 第1引数にバッファ、第2引数にバッファの長さを指定する
size_t Serial.write(uint8_t buf[], size_t len)

MH-Z19CのCO2読み取り命令を送るには、以下のように実装できます。

uint8_t CMD_READ_CO2_CONNECTION[9] =⏎
  { 0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79 };

Serial.write(CMD_READ_CO2_CONNECTION, sizeof(CMD_READ_CO2_CONNECTION));

受信には以下のAPIが利用できます。

// 1バイトの受信
// 受信できるまでブロックする
uint8_t Serial.read()
// 複数バイトの受信
// 第1引数にバッファ、第2引数にバッファの長さを指定する
// 戻り値は受信したバイト数
// タイムアウト機能があり、受信できない場合は0を返す
size_t readBytes(uint8_t *buf, size_t len)
// 複数バイトの受信
// readBytesの機能に加え、第1引数に終了文字を指定できる
size_t readBytesUntil(uint8_t terminator, uint8_t *buf, size_t len)

MH-Z19CのCO2読み取り命令を受信するには、以下のように実装できます。

uint8_t read_buf[9] = { 0 };
uint32_t len = Serial.readBytes(read_buf, sizeof(read_buf));
// MH-Z19Cの応答からデータの抽出
uint16_t co2 = read_buf[2] * 256 + read_buf[3];

7. I2Cマスター

I2Cは、Inter-Integrated Circuitの略で、2本のワイヤを使用してデバイス間でデータを送受信するための通信プロトコルです。 I2Cは主にセンサーやEEPROMなどの低速デバイスとの通信に使用されます。

I2Cではマスターデバイスがスレーブデバイスを操作します。 スレーブにはアドレスを指定して制御を指示するため、複数のデバイスを並列に接続できます。

筆者は自作キーボード用のモジュールとして、I2Cスレーブデバイスを複数作ってきました。 交換可能なデバイスを作るため、I2Cは非常に便利なプロトコルです。

電子工作でよく使われるI2Cデバイスに、SSD1306のOLEDディスプレイがあります。 このSSD1306のライブラリの使い方は第11章で解説します。 本章では基本的なI2Cの実装方法を解説します。

7.1. CH32V003のI2C

CH32V003にはマスターおよびスレーブとして動作可能なI2Cが1つ搭載されています。

ピンのマッピングは以下の通りです。 AFIOを使うと異なるピンにもマップできますが、Arduinoでの動作は確認できませんでした。

マッピング(I2C1REMAP)SCLSDA
Default(0b00)PC2PC1

7.2. サンプル回路

I2Cのプリミティブな機能の例として、SHT31という温湿度センサーを例に取ります。 このセンサーは秋月電子通商でも販売されています。

I2CのSDA、SCLのラインにはプルアップが必要です。 この回路では1kΩの抵抗を接続しています。

接続の回路図は以下となります。

SHT31を使った回路

7.3. Arduino

ArduinoではI2CはWire.hライブラリを使います。 サンプルコードは以下にあります。

https://github.com/74th/ch32v003-book-code/tree/main/i2c_master-arduino_core_ch32

まず、初期化はWire.begin()で行います。

Wire.begin();

書き込みは以下の3つの関数を使います。

// 送信の開始
// 第1引数: スレーブのアドレス
void Wire.beginTransmission(uint8_t address)
// 1バイトのデータ送信
void Wire.write(uint8_t data)
// 送信の終了
void Wire.endTransmission()

SHT31(アドレス0x44)に、温度と湿度の取得のコマンド(0x24、0x00)を送る場合は以下のようになります。

Wire.beginTransmission(0x44);
Wire.write(0x24);
Wire.write(0x00);
Wire.endTransmission();

読み込みは以下の2つの関数を使います。

// スレーブからのデータ送信のリクエスト
Wire.requestFrom(uint8_t address, uint8_t num)
// 1バイトのデータ受信
uint8_t Wire.read()

前述のコマンドの後、受信する処理は以下のように実装できます。

Wire.requestFrom(SHT31_I2C_ADDR, 6);
for (i = 0; i < 6; i++)
{
  dac[i] = Wire.read();
}

// 最初2バイトが温度データ
int t = (dac[0] << 8) | dac[1];
// 温度データを、℃に変換(小数点切り捨て)
uint32_t temperature = (((uint32_t)(t) * 175) >> 16) - 45;
// 4、5バイトが湿度データ
int h = (dac[3] << 8) | dac[4];
// 湿度データを、%に変換(小数点切り捨て)
uint32_t humidity = ((uint32_t)(h)*100) >> 16;

小数点以下の精度がありますが、CPUには浮動小数点演算は機能としてないため、ソフトウェアでの演算が行われプログラムのサイズが大きくなります。 なるべく整数演算かつ、割り算がないように実装すると良いでしょう。

8. I2C スレーブ

前章でI2Cはマスターからスレーブを操作するプロトコルと説明しました。 スレーブデバイスを作れれば、他のMCUからこのMCUを操作することができます。

筆者は、自作キーボード用のジョイスティックポインターデバイスを作るために、CH32V003をI2Cスレーブデバイスとして使いました。 Groveポートを用意すれば、M5Stack等からも操作できるようになります。

本節ではI2Cスレーブデバイスの作成方法を説明します。

I2CスレーブはI2Cマスタと同じ機能を使って実装します。 I2Cマスタとは排他的に使うことになります。

8.1. I2C通信のメッセージボックス実装

I2Cでは、メッセージボックスと呼ばれる、マスターからスレーブに対して、レジスタアドレスを指定して書き込み、読み込みを行う通信方法が良くとられます。

例えば、16バイトのレジスタ配列に対して、レジスタアドレス 0x04 から4バイト、 0x20、0x21、0x22、0x23 と書き込む場合、以下のように通信されます。

  1. マスターからスレーブに、送信を開始を通知
  2. マスターからスレーブに、レジスタアドレスとして、0x04を送信
  3. マスターからスレーブに、レジスタアドレス 0x04 への書き込みとして、0x20 を送信
  4. マスターからスレーブに、レジスタアドレス 0x05 への書き込みとして、0x21 を送信
  5. マスターからスレーブに、レジスタアドレス 0x06 への書き込みとして、0x22 を送信
  6. マスターからスレーブに、レジスタアドレス 0x07 への書き込みとして、0x23 を送信

例えば、レジスタアドレス 0x04 から4バイトスレーブから読み込む場合には以下のように通信されます。

  1. マスターからスレーブに、送信を開始を通知
  2. マスターからスレーブに、レジスタアドレスとして、0x04を送信
  3. マスターからスレーブに、受信の開始を通知
  4. スレーブからマスターに、レジスタアドレス 0x04 の読み込みとして、0x20を送信
  5. スレーブからマスターに、レジスタアドレス 0x05 の読み込みとして、0x21を送信
  6. スレーブからマスターに、レジスタアドレス 0x06 の読み込みとして、0x22を送信
  7. スレーブからマスターに、レジスタアドレス 0x07 の読み込みとして、0x23を送信

このように、送信の最初のバイトをレジスタアドレスとして、次のバイトをデータとして送信することで、スレーブデバイスは、マスターからの要求に応じて、レジスタアドレスを指定して、データを読み書きすることができます。

今回解説するI2Cレジスタの実装はそれを実現するものです。

8.2. Arduino

まずArduinoでの実装を説明します。 サンプルコードは以下のリポジトリにあります。

https://github.com/74th/ch32v003-book-code/tree/main/i2c_slave-arduino_core_ch32

Arduinoでは、マスターからの送信、受信の要求を受け時に実行するコールバック関数を登録して実装できます。 読み書きに使う関数は、マスターの時と同じWire.write()Wire.read()になります。

メッセージボックスの実装を送受信コールバックに実装すると以下のようになります。

// レジスタ
volatile uint8_t i2c_registers[0x30] = { 0x00 };
volatile uint8_t position = 0;

// マスターからスレーブへの送信(受信)
void on_receive(int length) {
  for (int i = 0; i < length; i++) {
    if (i == 0) {
      // 最初のバイトはレジスタアドレスとする
      position = Wire.read();
    } else {
      // 2バイト目以降はレジスタアドレスに書き込む
      if (position + (i - 1) < sizeof(i2c_registers)) {
        i2c_registers[position + (i - 1)] = Wire.read();
      } else {
        Wire.read();
      }
    }
  }
}

// スレーブからマスターへの送信(送信)
void on_request() {
  int i;

  for (i = 0; i < 4; i++) {
    // レジスタアドレスのデータを送信する
    if (position + (i - 1) < sizeof(i2c_registers)) {
      Wire.write(i2c_registers[position + i]);
    } else {
      Wire.write(0x00);
    }
  }
}

I2Cスレーブデバイスのアドレスを指定して、以下のようにセットアップします。

void setup() {
  Serial.begin(115200);

  Wire.onReceive(on_receive);
  Wire.onRequest(on_request);
  Wire.begin(I2C_ADDRESS);
}

これで、I2Cスレーブデバイスの実装が完了しました。

9. SPI

SPIとは、Serial Peripheral Interfaceの略で、デバイス間の通信を行うためのプロトコルです。 SPIは、マスターとスレーブデバイス間でデータを同期的に転送するために使用されます。

FLASHなど、I2Cよりも高速な通信が必要なデバイスに使用されます。

CS(Chip Select)信号を使用して、複数のスレーブデバイスを制御できます。 CH32V003では、PC1をCS信号として使用しますが、複数のデバイスを接続する場合には、デバイスごとにCS信号を用意する必要があります。 その場合、GPIOとしてHigh/Low出力を設定し、対象デバイスのCS信号を制御する必要があります。

本章では、SPIデバイスとして、温度センサADT7310をサンプルに、制御の実装方法を紹介します。

9.1. CH32V003のSPI

CH32V003にはSPIが1つ搭載されています。 SPIは、フルデュプレックス通信とデータの送受信を同時に行えます。

リマップで使えるピンは以下の通りです。 MISO、MOSIを入れ替える用途くらいにしか使えません。

マッピング(SPI1RM)NSSSCKMISOMOSI
DefaultPC1PC5PC7PC6
RemappingPC0PC5PC7PC6

あまり利用しないと思われるため、本サイトではマッピング方法の紹介は省略します。

また、SPIではPC5、PC6、PC7のピンを使用しますが、これらのピンはSOP-8パッケージのCH32V003J4M6には割り当てられていません。 したがって、CH32V003J4M6ではSPIを使用できません。

9.2. サンプル回路

温度センサADT7310とCH32V003の接続を回路図にすると以下となります。

サンプル回路

9.3. Arduino

Arduinoでの実装を解説します。 サンプルコードは以下のリポジトリにあります。

https://github.com/74th/ch32v003-book-code/tree/main/spi_master-arduino_core_ch32

ArduinoではSPIオブジェクトからSPIを使えます。 SPIは全二重通信であり、書き込みと読み込みを同時に行えます。 次のように、SPI.beginTransaction()で通信を開始し、SPI.transfer()の引数と戻り値でデータを送受信を実装します。

#include <SPI.h>

void loop() {
  uint16_t raw;
  int32_t raw_int;

  // 連続読み取りモードの開始
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  // コマンドの送信
  SPI.transfer(0x54);
  SPI.endTransaction();

  delay(500);

  // 2バイト読み取り
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  uint8_t b1 = SPI.transfer(0);
  uint8_t b2 = SPI.transfer(0);
  uint16_t raw = b1 << 8 | b2;
  SPI.endTransaction();
}

SPISettingsは、SPIの設定を行うためのクラスです。 dataOrderとdataModeは、使用するデバイスに合わせて変更してください。

// speedMaximum: 通信速度
// dataOrder: データの順序(MSBFIRST or LSBFIRST)
// dataMode: SPIモード(SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3)
SPISettings mySetting(speedMaximum, dataOrder, dataMode)

データの送受信に利用した関数の仕様は次の通りです。

// トランザクションの開始
void beginTransaction(SPISettings settings);
// トランザクションの終了
void endTransaction();
// データの送受信
uint8_t transfer(uint8_t data);

10. NeoPixel

NeoPixelはAdafruit社のRGB LEDテープの商品名ですが、本サイトではWS2812Bチップを使用したRGB LEDの総称として扱います。 このデバイスの使用方法を解説します。

10.1. NeoPixel

NeoPixel (WS2812B) RGB LEDテープ

NeoPixelは、RGB LEDが数珠つなぎに接続されており、1本のデータピンで制御します。

NeoPixelの制御には、0.3μ秒という非常に短いパルス幅の信号を出力する必要があります。 このような高速な信号制御は、Arduino標準のdigitalWrite()関数では困難なようです。

そのため、レジスタを直接操作したり、DMAと組み合わせるなどのPeripheralを活用する必要があります。 SPIで信号を代用する方法も知られています。

10.2. Arduino

Arduino環境でも、レジスタへの直接アクセスは可能です。 レジスタ直接操作や特定のCPU命令を利用して、NeoPixelを制御する関数を作成しました。 この関数をプロジェクトにコピーして使用することで、NeoPixelを制御できます。

https://github.com/74th/ch32v003-book-code/tree/main/neopixel-arduino_core_ch32

// NeoPixelの制御
// digital_pin: NeoPixelのデータピン
// data: NeoPixelのデータ
// led_num: NeoPixelのLEDの数
void write_neopixel(uint32_t digital_pin, uint8_t *data, uint32_t led_num)
{
  uint32_t pin_name = digitalPinToPinName(digital_pin);
  GPIO_TypeDef *gpio = get_GPIO_Port(CH_PORT(pin_name));
  uint32_t pin_mask = CH_GPIO_PIN(pin_name);
  uint32_t h = pin_mask;
  uint32_t l = pin_mask << 16;
  uint32_t data_size = led_num * 3;

  for (int i = 0; i < data_size; i++)
  {
    uint16_t c = data[i];
    for (int j = 0; j < 8; j++)
    {
      if (c & 0x1)
      {
        // 0.7us
        gpio->BSHR = h;
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        // 0.6us
        gpio->BSHR = l;
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
      }
      else
      {
        // 0.35us
        gpio->BSHR = h;
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        // 0.8us
        gpio->BSHR = l;
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
        asm("c.nop");
      }
      c = c >> 1;
    }
  }
}

この関数の使い方を説明します。 まず、setup()関数内でNeoPixelのデータピンを出力モードに設定します。

void setup()
{
  pinMode(PC0, OUTPUT);
}

NeoPixelの色は、Green、Red、Blue(GRB)の順で各色8ビット、合計24ビットのデータで指定します。 この24ビットデータを、接続されているLEDの数だけ連続して送信します。 例えば、LEDを順番に緑、赤、青で点灯させるためのデータは、次のように準備できます。

#define LED_NUM 6

// 各LEDの色データを格納する配列 (GRB)
uint8_t data[LED_NUM * 3];

for (int i = 0; i < LED_NUM; i++) {
  switch (i % 3) {
    case 0: // Green
      data[i * 3] = 0x20;
      data[i * 3 + 1] = 0x00;
      data[i * 3 + 2] = 0x00;
      break;
    case 1: // Red
      data[i * 3] = 0x00;
      data[i * 3 + 1] = 0x20;
      data[i * 3 + 2] = 0x00;
      break;
    case 2: // Blue
      data[i * 3] = 0x00;
      data[i * 3 + 1] = 0x00;
      data[i * 3 + 2] = 0x20;
      break;
  }
}

準備したデータ配列とピン番号、LEDの数を引数として、write_neopixel関数を呼び出します。

write_neopixel(PC0, data, LED_NUM);

11. NeoPixelとOLED SSD1306の制御

SSD1306はOLEDディスプレイ用のドライバICで、I2CやSPIで制御可能な小型ディスプレイです。 非常に安価で、文字、グラフ、画像などを簡単に表示できるため、電子工作で広く利用されています。 このデバイスの使用方法を解説します。

11.1. OLED SSD1306

I2C接続のSSD1306 OLEDモジュール

小型OLEDディスプレイで広く使われているドライバIC、SSD1306の使用方法を説明します。

11.2. Arduino

Arduinoでの開発方法を解説します。 サンプルコードは以下のリポジトリにあります。

https://github.com/74th/ch32v003-book-code/tree/main/i2c_oled-arduino_core_ch32

Arduino Core CH32環境では、Adafruit GFXライブラリと互換性のある「OLED SSD1306 - SH1106」ライブラリが利用可能です。 このライブラリを使用した制御方法を説明します。

OLED SSD1306 - SH1106 - Arduino Reference
https://reference.arduino.cc/reference/en/libraries/oled-ssd1306-sh1106/

まず、Arduino IDEのライブラリマネージャを使用してこのライブラリをインストールします。 ライブラリマネージャで「OLED SSD1306 SH1106」と検索し、表示される「OLED SSD1306 - SH1106」を選択してインストールしてください。

Arduino IDEライブラリマネージャでのSSD1306ライブラリインストール

インストール後、スケッチメニューの「ライブラリをインクルード」から「OLED SSD1306 - SH1106」を選択して、ヘッダーファイルをインクルードします。

OLEDクラスのオブジェクトを、使用するピンやディスプレイ設定と共に定義し、setup()関数内でoled.begin()を呼び出して初期化します。

#include <oled.h>

// OLEDオブジェクトの定義
OLED oled = OLED(
  PC1, // SDAピン
  PC2, // SCLピン
  NO_RESET_PIN, // リセットピン
  OLED::W_128, // 幅
  OLED::H_32, // 高さ
  OLED::CTRL_SSD1306, // コントローラ
  0x3c // I2Cアドレス
);

void setup()
{
  // OLEDの初期化
  oled.begin();
}

コンストラクタの引数PC1, PC2は、それぞれI2CのSDA、SCLピンに対応するGPIOピン番号です。 NO_RESET_PINは、リセットピンを使用しない場合に指定します。 OLED::W_128, OLED::H_32は、ディスプレイの幅と高さをピクセル単位で指定します。 OLED::CTRL_SSD1306は、使用するディスプレイコントローラICの種類を指定します。 最後の0x3cは、OLEDモジュールのI2Cスレーブアドレスです。

筆者が試した128x64ピクセルのOLEDモジュールでは、OLED::H_64を指定すると正常に動作しませんでした。 このライブラリは、ディスプレイの内容を保持するための内部バッファを持っています。 128x64ピクセルの場合、1KB(128 * 64 / 8)のバッファが必要となり、CH32V003の限られたRAM(2KB)ではメモリ不足になる可能性があります。

このライブラリには、文字、画像、図形を描画するための基本的な関数が一通り用意されています。 描画関数を呼び出すと内部バッファが更新され、oled.display()を実行することでバッファの内容が実際のOLEDスクリーンに転送・表示されます。

printfライクな書式指定でテキストを表示する関数も用意されており、便利です。

// 描画バッファをクリア
oled.clear();
// 座標を指定して文字列を描画
oled.draw_string(60, 8, "@74th");
// カーソル位置を設定してprintf形式で文字列を描画
oled.setCursor(60,16);
oled.printf("CH32V003");
// 矩形を描画
oled.draw_rectangle(50, 6, 120, 26);
// 描画バッファの内容をOLEDに転送
oled.display();

ビットマップ画像を表示するには、画像データをC言語の配列形式に変換する必要があります。 筆者は以下のオンラインツールを使用して変換しました。

image2cpp
https://javl.github.io/image2cpp/

このツールでモノクロPNG画像をアップロードし、以下の設定でバイト配列を生成しました。

  • Code output format: Plain bytes
  • Draw mode: Vertical, 1 bit per pixel

生成されたバイト列データを、以下のようにC言語のuint8_t配列として定義します。

// モノクロビットマップデータ (縦方向、1ピクセル1ビット)
static const uint8_t BUNCHO_74TH[] =
  {
    0xff, 0xff, 0xff, 0xff,
    // ... (データ省略) ...
    0xb7, 0xb7, 0xbf, 0xff};

この配列データを引数として、draw_bitmap_P関数を呼び出して描画します。

// ビットマップ画像を描画バッファに描画 (PROGMEMから読み込み)
oled.draw_bitmap_P(0, 0, 32, 32, BUNCHO_74TH);
// 描画バッファの内容をOLEDに転送
oled.display();

12. CH32V003の魅力と基礎知識

最後に、本サイトで取り扱ったCH32V003について解説します。

元の本の、最初の章で解説している内容と同じものです。

12.1. CH32V003とは

CH32V003はWCH社が発売する格安32bit RISC-V MCUです。

CH32V003

WCH社は、STM32に機能を似せた機能を持った32bit RISC-V MCUとして、CH32Vシリーズを発売しています。 その中でもCH32V003は、秋月電子通商でも1個40円で販売される格安MCUです。 公式ショップから購入すると、1個25円で18ものGPIOをもつMCUが購入できます。 格安にもかかわらずSTM32のようにPeripheralが豊富で、UART、I2C、SPI、GPIO、ADC、PWMなどの機能を持っています。 筆者は簡単な機能でも汎用的に組み込むMCUとして使っています。

主な特徴は以下の通りです。

  • とにかく安い
  • 電源が3-5Vで動作
  • 32bit RISC-Vで、命令セットはRV32EC
    • 圧縮命令セット。整数の乗算、除算、浮動小数点はない。
  • RAM 2kB、Code FLASH 16kBととにかく小さい
  • STM32系を模したPeripheralで、機能も豊富
    • Timer、GPIO、UART、I2C、SPI、ADC、PWM、DMA、WDTなど
  • 単線SWIOで書き込みが可能
  • 動作に必要な追加部品が少ない

各パッケージ毎の機能リストは以下の通りです。

ModelCode FlashRAMGPIOTimerADCSPII2CUARTPackage
CH32V003F4P616k2k181+18111TSSOP-20
CH32V003F4U616k2k181+18111QFN-20
CH32V003A4M616k2k141+18-11SOP-16
CH32V003J4M616k2k61+18-11SOP-8

TSSOP-20(0.65mmピッチ20ピン)のパッケージがピン数も多く、手はんだでも難しくはないため電子工作にお勧めです。 TSSOP-20のDIP化PCBは秋月電子通商でも販売されています。 SOP-8(1.27mmピッチ8ピン)のパッケージは、ピッチ幅も広く実装もしやすいですが、後述しますがいくつか注意が必要な点があります。

QFN20(0.4mmピッチ20ピン)のパッケージは、ICサイズも3.0mm×3.0mmととても小さく、小さい(狭い)場所への組み込みに向いています。 裏面パッドがあるためはんだごてでは実装できません。 リフロー機材を使ったり、PCBAサービスを利用する場合には良いでしょう。

12.2. CH32V003のMCU単体の入手方法

WCH社の公式ショップがAliexpressに出店しており、そこから購入することができます。 そこでは50個単位での購入となりますが、2025年4月現在送料を含めて1,200円ほどで購入できます。

50 ピース/ロット CH32V003 工業グレード MCU、RISC-V2A、単線シリアル デバッグ インターフェイス、システム周波数 48MHz - AliExpress
https://ja.aliexpress.com/item/1005005036714708.html

秋月電子通商でも、TSSOP-20(0.65mmピッチ20ピン)のCH32V003F4P6が1個50円、SOP-8(1.27mmピッチ8ピン)のCH32V003J4M6が40円で購入できます。

32ビットRISC-Vマイコン CH32V003F4P6: 半導体 秋月電子通商-電子部品・ネット通販
https://akizukidenshi.com/catalog/g/g118061/

50個単位での購入でも高くないので、ひとまずまとまった数を購入してみると良いでしょう。 Aliexpressで購入した場合でも1〜2週間程度で到着します。

12.3. 最小限の回路

CH32V003を最小限動作させる回路は以下のようになります。

CH32V003の最小限の回路

ポイントは以下となります。

  • VDDに、3.3Vまたは5Vを接続
  • GNDに、GNDを接続
  • VDDに、100nFのコンデンサをデカップリングコンデンサとして接続

このように追加部品は100nFのコンデンサのみとなっています。

12.4. ファームウェア開発の方法

開発の方法を「ファームウェアプログラマ」「ファームウェアライブラリ」「エディタ・IDE」の順に説明します。

ファームウェアプログラマ

ファームウェアの書き込みには、専用のツールWCH-LinkEが必要です。 WCH-Linkという姉妹品も発売されていますが、末尾にEの付いたWCH-LinkEでなければ書き込めません。 WCH-LinkEの代わりになるプログラマが複数開発されています。 ESP32S2を使った「esp32s2-funprog」、STM32F042を使った「NHC-Link042」、RP2040を使った「picorvd」などです。 しかし、日本国内においてはWCH-LinkEが安価に手に入ること、代替ツールでは全ての機能が使えるわけではないことから、WCH-LinkEを購入することをお勧めします。

WCH-LinkE

WCH-LinkEは秋月電子通商で、1,000円ほどで購入することができます。

WCH-LinkEエミュレーター: 開発ツール・ボード 秋月電子通商-電子部品・ネット通販
https://akizukidenshi.com/catalog/g/g118065/

AliexpressでWHC-LinkEと評価ボード、MCUのセットが販売しています。 こちらを購入しても良いでしょう。

メイン周波数48MHz,1本/キット,ch32v003f4p6 qingke RISC-V2A - AliExpress
https://ja.aliexpress.com/item/1005004895791296.html

筆者のBoothショップでもクローン品を作成し販売しております。 CH32V003開発ボードキットを発売した当初、まだ国内でWCH-LinkEが入手できなかったため、作成し、販売していました。 よければこちらもお求めください。

WCH-LinkEクローン

WCH-LinkEクローン USB-C,SWD10Pin(¥2,500) [74TH-G016] - 74th Books & Gadgets - BOOTH https://74th.booth.pm/items/5022813

WCH-LinkEとCH32V003は、SWIOと呼ばれる、1本の線で書き込みができるシリアルインターフェースを使って接続します。 GNDとSWIOの2本の線を接続すれば書き込みができます。

ファームウェアライブラリ

CH32V003のファームウェアを開発できるライブラリが複数あります。

公式SDK (C/C++)

WCH社が公式に提供しているライブラリです。 古いSTM32のライブラリ(Standard Peripheral Library、以下SPL)を模したAPIを提供しています。

後ほど紹介するMounRiver Studioから利用できるようになっていますが、コード自体は以下のリポジトリで公開されています。 OSSとしてApache 2.0ライセンスで公開してくれています。

https://github.com/openwch/ch32v003/

STM32のSPLとほぼ同じAPIを提供しているため、STM32の解説書がCH32を利用する場合でも参考になります。

Arduino Core CH32 (C)

WCH社がWCH SDKをArduinoのAPIでラップし、Arduino IDEで開発できるようにArduinoライブラリを提供しています。

以下のリポジトリで公開されています。

https://github.com/openwch/arduino_core_ch32

Arduinoは多くのサードパーティライブラリが活用できることも魅力の一つです。 しかし、それらの中にはArduino UNO R3など特定のボードのMCUに向けた仕様になっているものが多く、動作しない場合があります。

本サイトのArduinoでの開発の解説では、このWCH社が提供するArduinoライブラリの利用を前提に説明します。

ch32fun (C)

CH32V003はとてもFlash領域が少ないため、ライブラリでさえも容量を圧迫してしまいます。 容量節約のため、ch32funではレジスタを直接操作することを基本としています。 ファームウェア開発に集中できるように、ビルドや書き込み、ファームウェアのスタートアップ、デバッグプリントなどの開発に必要な機能をフルスタックで提供しています。

Open source minimal stack for the ch32 line of WCH processors, including the ch32v003, a 10¢ 48 MHz RISC-V Microcontroller - as well as many other chips within the ch32v/x line. https://github.com/cnlohr/ch32fun

以前は、ch32funはch32v003funと呼ばれていました。

ch32funでの開発の基本はレジスタを直接操作することですが、一部便利なライブラリが提供されています。 また、リポジトリにはサンプルコードが多数コミットされており、難しいレジスタ操作ながら実際に動くコードを参照できるためたいへん便利です。

元の本では、レジスタを用いた方法も解説しています。 その際には開発環境としてch32funを利用します。

ch32-rs (Rust)

Rustでレジスタ操作でファームウェアを開発できるPeripheral Access Crateがch32-rsというOSSとして公開されています。

Embedded Rust device crates for WCH's RISC-V and Cortex-M microcontrollers https://github.com/ch32-rs/ch32-rs

また、embedded-halとよばれる汎用API仕様でラップしたクレートも公開されています。 クレートは全ての機能を網羅できているわけではないため、必要に応じて利用を選択してください。

ch32-rs/ch32v00x-hal: HAL for the CH32V003 family of microcontrollers https://github.com/ch32-rs/ch32v00x-hal

第16章で、Rustでの環境構築について簡単に紹介します。

arduino-wch32v003 (C)

CH32V003で利用できるArduinoライブラリには、先ほど紹介したArduino Core CH32以外にもarduino-wch32v003があります。 ch32funをArduinoのAPIでラップした、OSSとなっています。

https://github.com/AlexanderMandera/arduino-wch32v003

エディタ・IDE

これまで紹介したファームウェアライブラリで開発できるコードエディタを紹介します。

MounRiver Studio 2

WCH社の公式の開発環境として、MounRiver Studio 2が提供されています。 ファームウェアライブラリにはWCH SDKを利用します。

MounRiver Studio
http://www.mounriver.com/

MounRiver Studio 1では、Eclipseベースであり、対応OSではWindows(x64)のみが提供されていました。 MounRiver Studio 2からは、VS Codeベースに変更され、対応OSもWindows(x64)、macOS(aarch64、ただしコンパイラ等はx64)、Linux(x64)が提供されています。

元の本では、WCH SDKでの開発の章で詳しく使い方を説明しています。

Arduino IDE

Arduino Core CH32は、Arduino IDEで使うことができます。 これも第2章で詳しい使い方を説明しています。

PlatformIO (VS Code)

PlatformIOは、ファームウェア開発に必要なコンパイラやライブラリのセットアップ、書き込みといった作業を、一連の共通の操作で様々なMCUとフレームワークで行えるように整備された開発環境です。 VS Codeの拡張機能として動作します。

開発フレームワークとしては、WCH SDKとch32funに対応しています。 元の本では、WCH SDKでの開発の章で詳しく使い方を説明しています。

ch32funはVS Codeでも開発できる

ch32funは、ビルドはmakeを使って行うため、特にIDEは必要とはしません。 VS Code用の設定ファイルがテンプレートに含まれており、VS Codeでコード補完やデバッグ実行が利用でき、快適に開発できます。 筆者は、ch32funをVS Codeを用いて開発しています。

ライブラリ、開発環境の使い分け

簡易な開発であればArduino Core CH32でも良いでしょう。 しかし、CH32V003の全ての機能を使おうと思うと、WCH SDKかレジスタを直接操作する方が良いです。 また、機能を作り込もうと思うと、Code FLASH容量不足から、Arduino Core CH32では生かしきれないことがあります。

より依存関係が少なくシンプルに作るならば、ch32funが良いでしょう。

書き込みツール

次にプログラマ(WCH-LinkE)をPCで操作するためのツールを紹介します。

公式のファームウェアの書き込みや設定の変更を、WCH-LinkEを介して行う公式GUIツールです。 対応OSはWindowsしかありませんが、GUIでファームウェアの書き込みや、消去、読み込みの他に、フラッシュの保護の解除など様々な操作ができます。 公式ツールなこともあり、最も安心して利用できるツールでしょう。

WCH-LinkEのファームウェアのアップデートもこのツールを介して行えます。

以下からダウンロードできます。

WCH-LinkUtility.ZIP - 南京沁恒微电子股份有限公司 https://www.wch.cn/downloads/WCH-LinkUtility_ZIP.html

OpenOCD

MounRiver StudioではCH32V開発用にフォークしたOpenOCDをメンテナンスしていて、ファームウェアの書き込み時には利用されています。 MounRiver Studio利用時のみ利用すれば良いでしょう。

wlinkは、Rust製のマルチプラットフォームに対応したツールで、WCH-LinkEを介して、ファームウェアの書き込み、消去などができます。 CH32VシリーズのRust用ライブラリのメンテナが製作しています。

ch32-rs/wlink: An open source WCH-Link library/command line tool written in Rust. https://github.com/ch32-rs/wlink

GitHubリポジトリにてビルド済みバイナリも公開されているため、ダウンロードして使うことができます。

Releases · ch32-rs/wlink
https://github.com/ch32-rs/wlink/releases

wlinkを使ってファームウェアを書き込むには以下のように実行します。

wlink flash ch32v003.bin

minichlinkはマルチプラットフォームに対応したツールで、WCH-LinkEを介してファームウェアの書き込み、消去などができます。 ch32funのメンテナが製作しています。

ch32funのGitHubリポジトリからソースコードをチェックアウトして、自分でビルドして利用します。

git clone https://github.com/cnlohr/ch32fun.git
cd ch32fun/minichlink
make

minichlinkは、引数が-bといったフラグになっており、複数のフラグを設定すると設定順に実行される形になります。

例えば、書き込み-w後にリセット-bを行うには以下のようにします。 書き込みは-w <ファイル名> <flash/bootloader>のように、書き込むファイル名と書き込む領域を指定します。

minichlink -w ch32v003.bin flash -b

使い分け

CUIで使えるツールとしては、minichlinkとwlinkのどちらも使えます。 minichlinkはch32funでの活用に特化されていて、wlinkはWCH Link Utilityでできることをそのまま実装しているように見えます。 筆者はシンプルさでいえばminichlinkの方が利用しやすいように思っています。

12.5. ひとまず動く開発ボードが欲しい

MCUが使える基盤を製作する前に、検証に使える開発ボードがあると便利です。 複数のCH32V003開発ボードが発売されています。

WCH社公式評価ボード

WCH社の公式の評価ボードがあります。 先に説明したプログラマWCH-LinkEとセット販売がされています。

公式評価ボード

メイン周波数48MHz,1本/キット,ch32v003f4p6 qingke RISC-V2A - AliExpress https://ja.aliexpress.com/item/1005004895791296.html

まずは1つ持っておくと便利です。

ただし、この開発ボードには水晶発振子が接続されています。 CH32V003を安価に使おうと思うと水晶発振子はコストになり内蔵発振子を使うことが多いです。 そのため、使わない水晶発振子に2ピンが割り当てられてしまうの、使えるピンが減るためもったいないです。

筆者作成のProMicro型ボード

筆者作成のProMicro型ボード

筆者が作成しているCH32V003のProMicro型ボードがあります。 ProMicro型ということで、ブレッドボードにも刺すことができ、便利です。 ProMicroとUART、I2C、SPIのピン配置を同じにしているため、ProMicroに慣れている方であればすぐに配線できると思います。

また、SWD10ピン端子を設けており、筆者の作成するWCH-LinkEクローン、WCH-LinkE Adapterを使うと、1つのケーブルで簡単にWCH-LinkEと配線できます。

こちらはBoothショップで、キットとして販売しています。

CH32V003 ProMicroサイズ開発ボードキット (3個入¥1,500) [74TH-G015] - 74th Books & Gadgets - BOOTH
https://74th.booth.pm/items/4645948

設計ファイル(KiCad)はOSSHWとして公開しています。 ご自身でPCBファブリケータに発注して製造することも可能です。

ch32v-dev-boards/ch32v003-promicro at main · 74th/ch32v-dev-boards
https://github.com/74th/ch32v-dev-boards/tree/main/ch32v003-promicro

UIAPduino Pro Micro CH32V003

UIAPduino Pro Micro CH32V003

UIAPが開発するPro Micro型のCH32V003ボードです。 大きな特徴は、USB経由で書き込み可能な回路とブートローダを搭載していることです。

UIAPduino用のArduinoパッケージをインストールすることで、USB経由での書き込みがArduino IDEからできるようになります。

BOOTHショップやスイッチサイエンスで購入できます。

UIAPduino Pro Micro CH32V003 V1.4 - UIAP - BOOTH
https://uiap.booth.pm/items/5845791

その他の開発ボード

ReAct Studioが作成した開発ボードが、国内のビットトレードワンで販売されています。 374円という非常に安価な価格で販売されています。

WA00007 [Weact]CH32V003F4U6マイコンボード — ビット・トレード・ワン 公式オンラインショップ BTOS
https://btoshop.jp/products/wa00007

その他、AliexpressでもCH32V003の開発ボードが販売されています。 CH32V003で検索すると、多数見つかると思います。

12.6. CH32V003開発で知っておくと良いこと

SOP-8のCH32V003J4M6のUSART TXとSWIOのピンの共用

SOP-8のCH32V003J4M6は、USART TXのピンがSWIOのピンと共用になっています。 そのため、USART TXを有効にすると、SWIOによる書き込みができなくなります。

回避方法としては、Remap機能を使って、USART TXをSWIOとは異なるピンで利用することが考えられます。 Remapの方法についてはUARTの第6章で説明します。 なお、Arduinoを利用している場合にはRemapはできません。

USART TXを有効にしてSWIOが使えなくなった場合は、以下に説明する「電源オン時のフラッシュ消去」を行って、ファームウェアを削除してから書き込む必要があります。

電源オン時のフラッシュ消去

CH32シリーズには、電源オン時にフラッシュメモリを消去するコマンドを受け付ける機能があります。

この操作は、複数のツールで対応しています。

タイトルバーメニューに"Target"→"Clear All Code Flash-by Poweroff"を実行します。

wlinkの場合

eraseコマンドに以下のように引数を指定します。

wlink erase --chip CH32V003 --method power-off

minichlinkの場合

フラグ-uを付けます。

minichlink -u

WCH-LinkEには5V、3.3Vの電源供給端子があります。 この端子にはスイッチ機能が付いており、コマンドで電源供給のオンオフを切り替えることができます。 上記のコマンドを使った場合、このスイッチ機能を使って電源供給が一時的に遮断され、オペレーションに成功します。

ただし、WCH-LinkEのクローン品にはこの電源のスイッチ機能が含まれていないことがあります。 実際に、筆者の作成するものや、MuseLabが作成するクローン品にはスイッチ機能は含まれていません。 手動で電源のオンオフをしても行えますが、WCH-LinkE経由で行った方が確実です。

CPU命令セットに整数の乗算・除算がないが、ソフトウェアで使える

CH32V003のCPUの命令セットRV32ECには、整数の乗算・除算の命令がありません。 しかし、ソースコード上で乗算・除算を行った場合、コンパイラによってソフトウェア上で実装され、利用可能になります。

ch32funを使い、uint32_tやfloatの乗算・除算を行った場合のファームウェアサイズを比較しました。

printf未使用(B)printf使用(B)
乗算/除算なし5881,916
uint32_tの乗算6241,948
uint32_tの除算7281,924
uint32_tの乗算/除算7601,956
floatの乗算1,7243,228
floatの除算1,9603,332
floatの乗算/除算2,5923,972

フラッシュの消費容量は、uint32_tの乗算・除算ではそれほど増えませんが、floatの乗算、除算を組み込むと大きくなるのが分かります。

また、printfを使わないことでもフラッシュ容量の削減になります。

WCH-LinkEには、DapLinkモードがある

WCH-LinkEは、ARM MCUのデバッガであるDapLinkとして動かすDAPモードと、WCHのRISC-V MCU用に動かすRVモードがあります。 WCH-LinkE上に青色のLEDが点灯しているときは、DapLinkモードになっています。 一度切り替えると、USBケーブルを抜き差ししてもそのモードが維持されます。

モードを切り替える方法はいくつかあります。 ひとつはModeSと書かれた物理ボタンを押しながら、USBケーブルを挿す方法です。 しかし、WCH-LinkEはプラスチックのケースに覆われており、ケースを取り外さないと挿せません。

wlink、WCH Link Utility、MounRiver StudioのDownload Configurationの機能を使うとケースを取り外さないでも切り替えることができます。 WCH Link Utility、MounRiver StudioではGUIで操作できますが、手軽に行うにはwlinkを使うと良いでしょう。 wlinkでは以下を実行して切り替えることができます。

# CH32V用のRVモードにする
wlink mode-switch --rv

# ARM用のDAPモードにする
wlink mode-switch --dap

12.7. 後継、類似モデル

CH32V002、CH32V006

現在、CH32V002、CH32V006というCH32V003の後継のMCUが発売されています。 機能の比較は以下の通りです。

SeriesCoreCode FlashRAMGPIOTimerADCSPII2CUART
CH32V003RV32EC16k2k181+18111
CH32V002RV32EmC16k4k181+18+3111
CH32V006RV32EmC64k8k311+18+3112

CH32V002はRAMが4kBと倍増しています。 CH32V006は上位モデルで、RAMが8kB、FLASHが64kBと4倍に増加しています。

CH32V002/CH32V006ではCPUコアがCH32VEmCに変更され、整数の乗算がサポートされるようになりました。 また、ADCも3チャンネル追加されたり、タッチセンサーに対応したりしています。 CH32V006は、GPIOの数も増え、UARTも2つに増えています。

また、動作電圧が2~5Vに対応しました。 2Vで動作するため、コイン電池でも動作します。 実際に筆者はコイン電池CR2032で動作するケーブルチェッカをCH32V002で作り、動作しました。

後継のものはグレードアップされていますが、値段はほぼ据え置かれています。 50個単位で購入すれば1000円台前半で入手できます。

ただ、2025年4月現在、まだ発売したばかりなのでいくつか問題もあります。 筆者の検証した限りは、CH32V002ではSWIO経由でのデバッグプリントであるSDI-Printが動作しませんでした。 ch32funは既にこれらのチップに対応していますが、現行のUbuntu 24.04 LTS付属のgccではRV32EmCに対応していないなど、完全に使える状態ではありません。 まだしばらくはCH32V003を使う方が安定して開発に取り組めると思っています。

他のCH32シリーズ

WCHはCH32シリーズとして、32ビットMCUを多数リリースしています。 CH32"V"がRISC-Vコアを搭載したシリーズで、CH32"F"がARMコアを搭載したシリーズです。 名前の通りSTM32シリーズのピンコンパチ品として開発がスタートされたようですが、現在は多様な製品が展開されています。

CH32V203は、CH32シリーズのフラグシップ的なモデルとなっています。 USBデバイス、ホストの機能を持っています。 国内でも秋月電子通商で販売されているため、手に入れやすいでしょう。

CH32X035は、RISC-VコアのMCUですが、いくつか特徴的な機能があります。 USBデバイス、ホストの機能に追加で、USB PDの制御機能も持っています。 さらに、プログラマブルIOコントローラPIOCを搭載しています。 価格もかなり抑えており、USBを使いたいのであれば最も安価なMCUの一つとなっています。

USB、WiFi対応CH570

WCH社はWiFiに対応したMCUもリリースしています。 その中でも安価なCH570を発表しました。 価格は10セントで、CH32V003に近い価格です。

技適を取得していないため、日本国内では電子工作用途でのWiFi利用は難しいでしょう。

一方USBデバイス、ホストの機能も持っています。 Code Flashは240kB、RAMは12kBとかなり大きくなっています。 WiFiを利用しなくても、安価なUSBデバイス対応MCUとしても利用できることを期待しています。

13. その他元書で扱っていること

本サイトArduino抜粋版では扱いませんでしたが、元の本ではCH32V003をさらに活用すべく以下のようなトピックを扱っています。

  • 公式SDKでの開発方法
    • 公式IDE MounRiverStudioの使い方
    • PlatformIOでの開発方法
  • ch32fun、レジスタを直接操作する開発方法
  • 各Peripheralの使い方を、公式SDKやch32fun、レジスタ操作のそれぞれの方法で解説
  • Arduinoからは扱えないPeripheralの使い方
    • Timer
    • DMA
    • WatchDogTimer
    • 省エネルギーモード

CH32V003は非常にプログラムフラッシュ領域が小さいため、ch32funのようにレジスタ操作を中心とすると積み込めるプログラムの幅が広がります。

これらに興味がある方はぜひ元の書籍を購入してみてください。

CH32V003開発ガイドブック[74TH-B018] - 74th Books & Gadgets - BOOTH https://74th.booth.pm/items/6934072

14. 筆者の製作

本サイトではCH32V003のファームウェア開発について解説してきました。 最後に、筆者がCH32V003を組み込んだ作品を紹介します。

CH32V003 ProMicro Like

ProMicro型の開発ボードを最初に作りました。 こちらは13章で紹介していますので、省略します。

キットは以下のページで販売しています。

CH32V003 ProMicro Like Booth販売ページ
https://booth.pm/ja/items/4645948

OSHWとして、KiCadファイルを公開しています。

KiCadファイル
https://github.com/74th/ch32v-dev-boards/tree/main/ch32v003-promicro

StickPointV

自作キーボードで使えるジョイスティックデバイスとしてStickPointVを作りました。 ADCを使ってアナログジョイスティックを読み取り、I2Cスレーブデバイスとして自作キーボードのMCUに接続します。

Stick Point V

このような構造になっています。 ファームウェアはch32funを使って作りました。

Stick Point Vの構造

これを組み込んだ自作キーボードキットSparrow60Cを製作しました。

この製品は以下のページで販売しています。

Booth販売ページ
https://74th.booth.pm/items/5404009

また、このファームウェアはOSSとして公開しています。

firmware(OSS)
https://github.com/74th/stickpoint-firmware/tree/main/stickpointv-ch32v003j4m6

この製作の詳細はブログ記事にまとめています。 ぜひ読んでみてください。

ジョイスティックデバイスをQMK Firmwareに最適化させたStickPointを作った - @74thの制作ログ
https://74th.hateblo.jp/entry/tackpointv

USB切り替え機能付きUSBハブ Relay Switch Hub

Relay Switch Hub

メカニカルリレーを使ってホストの切り替え機能を追加したUSBハブを作りました。 このボタンでリレーを切り替える機能にCH32V003を使いました。

以下のような構造になっています。 本体とは別に、PCの前に配置できるリモートスイッチを作りました。 Groveケーブルを使って接続します。

Relay Switch Hubの構成

キットは以下のページで販売しています。

Booth販売ページ
https://74th.booth.pm/items/5080690

KiCadファイルと、ファームウェアはOSHWとして公開しています。

KiCadファイル、firmware(OSHW)
https://github.com/74th/relay-switch-usbhub

この製作の詳細をブログ記事にまとめています。 ぜひ読んでみてください。

Relayを使って、USB切り替え機能付きUSB Hubを安価に作った - @74thの制作ログ
https://74th.hateblo.jp/entry/2023/09/10/214059

SparrowTV

iPhoneケースに組み込んだTV横PC用の自作キーボードデバイスを作りました。

SparrowTV

SparrowTVはメインのMCUとしてESP32-C3を使っています。 USB HIDキーボード、マウスデバイスとしてIC CH9329を使用しています。 ESP32-C3では、キーボードを実現するほどのIOが足りないため、CH32V003をIOエキスパンダーとして組み込みました。 また、アナログジョイスティックの読み取りも行わせました。 サブMCUとして使うのにCH32V003は丁度良いと思っています。

以下が構成図となります。

SparrowTVの構成

キットは現在以下のページで販売中です。

Booth販売ページ
https://74th.booth.pm/items/5309546

開発したKiCadファイル、ファームウェアはOSHWとして公開しています。

KiCadファイル、firmware(OSHW)
https://github.com/74th/tv-side-keyboard-SparrowTV

USB Rebooter

USB Rebooterは、USBデバイスを定期的に電源オフによる再起動を行うデバイスです。

USB Rebooter

電源スイッチIC CH217をCH32V003で制御することで24時間毎に電源オンオフを行うものです。 CH32V003は安価であるため、簡易Timerとして使用しました。

USB Rebooterの構成

製品は残り少ないですが、以下のページで販売しています。

Booth販売ページ
https://74th.booth.pm/items/5309546

開発したKiCadファイル、ファームウェアはOSHWとして公開しています。

KiCadファイル、firmware(OSHW)
https://github.com/74th/usb-rebooter

この制作についてブログ記事にしています。 ぜひ読んでみてください。

時々ハングするIoT機器を再起動させるUSBアダプタを作った - @74thの制作ログ
https://74th.hateblo.jp/entry/usb-rebooter

USB Switch Adapter

USB Switch Adapter

電子工作をしていてブレッドボード上の回路を組み替えるときに、逐一マイコンボードのUSBを抜き差しするのが面倒です。 これをボタンでできるようにしたのがUSB Switch Adapterです。 CH32V003を組み込んで、長押しでUSBのオフにする機能を追加しました。

構成は以下のようになっています。

USB Switch Adapterの構成

この製品はBoothにて販売しています。

Booth販売ページ
https://74th.booth.pm/items/6291652

また、KiCadファイル、ファームウェアはOSHWとして公開しています。

KiCadファイル、firmware(OSHW)
https://github.com/74th/74th-oshw-projects/tree/main/74TH-G053-usb_switch_adapter

この制作についてブログ記事にしています。 ぜひ読んでみてください。

押しやすいスイッチでUSB電源を遮断するUSB Switch Adapterを作った - @74thの制作ログ
https://74th.hateblo.jp/entry/usb-switch-adapter

自動ダウンロード付きESP32 Writer

ESP32 Writer Pro CH9102

ESP32は、BOOTピンとENピンを制御することでダウンロードモードに入れます。 これをUSBシリアル変換ICからMOSFETでその信号を作るのがよく行われますが、失敗することがあります。 これをCH32V003で余裕を持ったタイミングで制御することで、自動ダウンロード機能を実現するESP32プログラマを作りました。

USBシリアル変換ICにはCH9102を使っています。 構成は以下のようになります。

ESP32 Writer Proの構成

KiCadファイル、ファームウェアはOSHWとして公開しています。

KiCad、firmware(OSHW)
https://github.com/74th/esp32-dev-boards/blob/main/ESP32-Writer-CH9102F

この制作についてブログ記事にしています。 ぜひ読んでみてください。

ESP32の自動ダウンロードブート制御をマイコンで行うESP32 Writer Pro CH9102Fを作った - @74thの制作ログ
https://74th.hateblo.jp/entry/esp32-write-pro-ch9102

7セグLEDデバイス 7 Seg Grove

7Seg Grove

7セグLED4個をGroveで接続できるデバイスにしました。 このI2Cスレーブと7セグLEDの制御にCH32V003を使いました。

筆者の家ではGroveポート付きESP32-C3ボード(『お家IoTリモコン全部作る(技術書典17)』で紹介したESP32-C3-IoT-Server)に繋がれ、リビングの湿度を常に表示するデバイスになっています。

このキットはBoothにて販売しています。

Booth販売ページ
https://74th.booth.pm/items/6412203

また、KiCadファイル、ファームウェアはOSHWとして公開しています。

KiCad、firmware(OSHW)
https://github.com/74th/74th-oshw-projects/blob/main/74TH-G060-7seg-grove

この制作についてブログ記事にしています。 ぜひ読んでみてください。

7セグLEDを176個セット買ったので、Grove I2Cで制御して使うためのデバイスを作った - @74thの制作ログ
https://74th.hateblo.jp/entry/7seg-grove

左右分割型自作キーボードSparrowS v3

SparrowS v3

SparrowS v3は筆者の作成する分割自作キーボードのバージョン3です。 3Dプリンタでケースを出力し、ガスケットマウントというクッションでスイッチ、基板を受けとめる構造になっていて、打鍵感がマイルドになっています。 トラックパッドなどポインターデバイスを右手キーボードの近くに配置するのに効率的な形を目指しました。

RP2040をメインMCUとして左手側に配置し、CH32V003を右手側に配置し、その間はI2Cで接続しています。 その構成は以下のようになっています。

SparrowS v3の構成

このキットはBoothにて販売しています。

Booth販売ページ
https://74th.booth.pm/items/6655442

RP2040上のQMK Firmwareの実装と、CH32V003のファームウェアはOSSとして公開しています。

Sparrow keyboard series QMK Firmware fork
https://github.com/74th/qmk_firmware_sparrow_keyboard/tree/sparrow/keyboards/sparrow62/rev3

Sparrow Series Sub Firmware
https://github.com/74th/sparrow-keyboard-sub-firmware/

この制作についてブログ記事にしています。

ガスケットマウント分割キーボードSparrowS v3を作って、エンドゲームに辿り着いた - @74thの制作ログ
https://74th.hateblo.jp/entry/sparrows-v3

おわりに

本サイトでは、CH32V003のArduinoでの開発について解説してきました。 Arduinoは非常に簡単に扱えるため、簡単なことしかできないと悲観せずに、開発スピード重視で使っていくことをお勧めします。 CH32V003でもっと高度なことをしたいと思ったときに、ぜひ元の書を参考に公式SDKやch32funを使ってみてください。

筆者はこの執筆を通して、CH32V003の使い方を深く知ることができました。 実際に、StickPointVに対して、ch32funのI2C Slaveライブラリの利用に置き換えるリファクタリングをしたり、SparrowSv3の右手キーボードにWatchdog Timerを追加したりしました。 CH32V003の機能を余すことなく使えるようになったように思います。

ぜひ、ご自身のCH32V003の開発に役立てていただければと思います。

著者

Atsushi Morimoto

休みは電子工作ばかりしているAI寄りのフルスタックエンジニア。 VS Codeにわりと詳しく、2024年1月に『改訂新版 Visual Studio Code実践ガイド』を上梓しました。 技術書典3~18に毎回新刊を持って参加しています。