0️⃣

RISC-Vのソフトウェアを(限りなくゼロから)作る

2024/04/14に公開

作るってどこから?

CH32Vという格安の32ビットRISC-VのMCUシリーズで動くソフトウェアを作ります。今回はCH32V203K8T6という1個120円で買えるMCUを使いました。

こういう組み込み用のMCUにソフトウェアを書く場合、MCUメーカーから配布されるIDE相当のものを使うのがセオリーです。またArduino IDEで使える場合もありますし、最近だとPlatform IOがいろんな組み込みMCUに対して使えたりするらしいですね。でもね…IDEとか使いたくないの、全部コマンドラインでやりたいの、クロスコンパイル環境を自分で構築したいの、そんな気分なの。

なのでツールチェイン、いわゆるCコンパイラとして必須なbinutils, gcc, newlibの3点セットのビルドから初めて、MCUを変換基板にハンダ付けしてブレッドボードに簡易な回路を組んで、LED点滅(通称Lチカ)+シリアル(UART)通信のHello WorldをコンパイルできるMakefileを書いて、それを実際のMCUに書き込んで動作確認取れたところまでを端折って紹介します。

ツールチェインのクロスコンパイル

今どきはC言語のコンパイラといえばLLVMとclangが有名ですが、クロスコンパイルですし安定と実績のgccを使いました。私は遠い過去に32ビットARM向けのクロスコンパイル環境をビルドしてたこともあるので、その手順を思い出す(もしくは現在の知識で理解しなおす)意味合いもあります。

今回はWindows環境を用いました。組み込みの開発環境と言えばなんだかんだWindowsがファーストクラスなところがあります。なのでmsys2のMINGW上のRISC-V向けgccを利用します。とはいえコンパイル済みバイナリにはriscv64の表記がありなんか嫌な予感がしたのでriscv32向けにコンパイルしなおしました。リンク先には私がコンパイルしたパッケージも置いてあります。

やったことはMINGWのパッケージビルドシステムに乗っかり、riscv64向けの設定をriscv32に書き換えてビルドするだけなので難しいことはないでしょう。PKGBUILDファイルの中身を見て、必要な手順を理解するのです。

最大のポイントはビルドの順序です。newlibとgccの間には循環参照があるためブートストラップが複雑になっています。

  1. binutils (リンカ等)をビルド&インストール
  2. gccをminimalな設定でビルド
  3. newlibを2のminimal gccでビルド&インストール
  4. gccをビルド&インストール
  5. (オプション?) newlibを4のgccでビルド&インストール

なおビルドするには時間がかなりかかります。半日から1日を覚悟しましょう。これは./configureに非常に時間がかかるせいであり、コンパイルは並列度を高めれば十分速いのです。msys2の悲しい性(さが)だと甘受しましょう。

ブレッドボードに試験用の回路を組む

CH32V203K8T6はQFPという小さなパッケージなので、そのままではブレッドボードに刺さりません。なので変換基板を使います。0.8mmピッチのQFPのハンダ付けはRP2040の0.4mmを体験した後だと、ぶっちゃけると余裕です。

そのあとはCH32V203のデータシートとにらめっこしつつ、必要最小限の周辺回路を組みました。もともとCH32Vの周辺回路は簡単なのですが、今回はブレッドボードが電源を生成してくれるので、さらに簡単になっています。このブレッドボードは秋月電子で買ったはずだけど、現在は扱ってなさそうですし、円高でえらく値上がりしてますね。(調べなおしたら2400円で売ってました) あとはUSBから書き込めるようにするための信号線とブートモード切替のスイッチを付け、Lチカ+シリアル(UART)printfができるようにするためにいくつかの部品を追加します。以下が完成図です。

試験用回路

RP2040やATMEGA32U4に比べると極めてシンプルなのが見るからにわかるんじゃないでしょうか。またLEDには抵抗内蔵のものを利用しているため、ブレッドボード上にはLED抵抗が省略されています。

Lチカ用のMakefileを書く

このCH32V203用のプログラムとそれをビルドするMakefileを用意します。メーカーがSDKを公開しているので、まずそれをよく眺めます。EVT/EXAMに各種のプログラム例が収録されてます。さらに良く見るとEVT/EXAM/GPIO/GPIO_ToggleはまさにLチカ+UARTによるprintfを使ってそうですね。

使えそうなサンプルが見つかったところで、ビルド方法が記録されてないか引続きSDKを探します。そうしてMakefile他が記録されたobjを見つけました。これがあったおかげで必要なビルドの手続きを知るのに大幅な時間短縮ができました。どうもIDEが生成したファイルみたいですね。

で、これらの情報からすったもんだしてできたのがこのプロジェクト:koron/helloworld-ch32v203です。とりあえずmake一発で最初にビルドしたツールチェインを使って、書き込みに利用できるelf, hexファイルが生成できるようになりました。本当は再利用可能なように綺麗な作りにしたいのですが、まずは動作確認ということでMakefileやプロジェクト構造はやっつけ仕事。でも読めばわかる程度にはすっきり書いたつもりです。

補足: SDKの構造のメモ

SDKを眺めるなかで分かったおおよその構造。必須もしくはほぼ必須のもの

  • Startup - アセンブラで書かれたスタート(エントリ)ポイント。この子が無いとプログラムが動かない
  • Core - コアCPUにアクセスするライブラリ
  • Peripheral - MCUのコア以外の周辺回路(機能)のためのコードライブラリ
  • Debug - デバッグ用のライブラリ。とはいえ短時間スリープ機能がココに実装されているのでほぼ必須といって良い
  • Ld - リンカスクリプトが置いてある。データシートに書かれたメモリマップ通りに配置するためのもの。自前で書くのはしんどいので、含まれてて良かった

MCUへプログラムを書き込んで動作確認

あとはビルドしたプログラムをMCUへ書き込むだけです。SDKによれば専用ハードを使うみたいですが、実はUSBからでも書けるということでやってみました。

書き込みにはch32-rs/wchispを使いました。CH32V203K8T6はBOOT0をプルアップした状態で電源を入れるとブートモードに入りwchispと通信できる状態になります。WindowsではWinUSBというドライバが必須なので、ブートモード状態のMCUを「不明なデバイス」として認識させた状態で、Zadigを起動してWinUSBをインストールしました。ただWinUSBを入れた後でもこんな感じになってます。エラーにも警告にもなってないのでヨシ。

こうなればwchisp infoでチップの情報が見え、またwchisp flash {name}.hexでプログラムを書き込め、意図通りにLチカ+シリアル(UART)printfの動作確認がとれました。

wchisp infoの例
動作してるところ

おことわり

この記事ではストレートにできたかのように書いてますが、実際はそれなりにハマったり試行錯誤したりしています。

参考資料

Discussion