状態遷移設計を「動かして」検証する。自作ツール『D+++ Visualizer v51』の開発
はじめに
複雑なシステム開発に欠かせない状態遷移設計。 ホワイトボードや作図ソフトで図を描いても、実装段階で「この条件の時、前の状態のクリーンアップはいつ実行されるんだっけ?」「階層構造の深いところから抜ける時の順序は?」といった矛盾に直面することは珍しくありません。また、最近ではAIにロジックを組ませることも増えましたが、AIは時として「もっともらしいが、論理的に破綻した遷移」を出力する場合があります。
そういった問題をAIを活用しながら解決したいと思いこのツールを作成しました。
自分が感じていた現状の課題
AIを活用して開発をしたいが詳細仕様を検討する工数が大きく、自分でコーディングして試行錯誤した方が速いのではと感じる。
AIと協力して詳細検討をしたいが、状態遷移などの細かな話になってくるとAIが出力する文字だけで理解するのが難しい。
作成した仕様が正しいのか判断が難しい。
ツールの概要
エディタ、シミュレーター、作図、簡易コード生成(C++)機能を1枚のHTMLファイルに凝縮し、インストール不要で手軽に試せるようにしました。
Markdownのテーブルを書くだけで図が生成され、そのまま動かす事ができます。注意点としては自分の設計したツールに状態遷移表を最適化するため、一般的な状態遷移表とは異なる下記の3つの表を使用します。
※一括でのシミュレーションをしない場合はTable Cは作成不要です。
Markdownテーブルの内容
Table A (Status): 状態と不変条件(Invariant)の定義
Table B (Transition): 遷移ルール、ガード条件、アクションの定義
Table C (Global/Test): 変数の初期値やテスト設定
これらを記述すると、Mermaid.jsによって図が描画され、シミュレーション上でステップ実行とTable Cに基づいた一括テストができるようになります。
ツールのダウンロード先
本ツールは下記のGitHubリンクからダウンロードして使用可能です。
とりあえずこのツールを試してみたい場合
このツールの動作を確認したい場合は下記のようにHTMLファイルをGemini3.0に読み込ませて、表示したい状態遷移表を指示してください。 HTMLファイルを読み込ませることで本ツールの詳細な使用方法もAIが解説してくれるため、本ツールのマニュアルは準備していません。


ツールの機能説明
詳細については、HTMLファイルをAIに読ませて解説させるのが最も分かりやすいですが、本ブログ中でも簡単な機能説明を記載します。
状態遷移表A、B、Cと図の作成
先ほどの画面左側に表A、B、Cを記載すると図が作成されます。
ソースコードの parse(text) メソッド内で、各行が以下のいずれかのパターンに一致するかを判定しているので、表の前に必ず「Table A」、「Table B」、「Table C」の文言を入れる必要があります。
- 表A (States): /^#\sTable\s+A/i
行が #(見出し記号)で始まっていてもいなくても、また「Table A」の間に空白があっても、大文字小文字を問わず検知します。
表B (Transitions): /^#\sTable\s+B/i
表C (Test Cases): /^#\sTable\s+C/i
図のクリックによる表の対応部分のハイライト
下記の画像に示すように図をクリックすると対応する表の部分がハイライトされます。

静的解析機能
到達不能(幽霊)状態のチェック
初期状態からどのような遷移を辿っても到達することができない「孤立した状態」を検出します。
動作原理: 幅優先探索 (BFS) を使用しています。
表Aの最初に定義された状態を「初期状態」としてキューに入れます。
現在の状態から遷移可能なターゲット(表BのSourceとTargetの関係)を順次走査し、「訪問済み」として記録します。
全走査終了後、「訪問済み」リストに含まれていない状態を「幽霊状態」として報告します。
デッドロック(行き止まり)検知
ある状態に遷移した後、そこから外に出る遷移が一つも定義されていない箇所を特定します。
動作原理: 出次数の確認を行います。
各状態に対し、その状態を Source とする遷移が表Bに存在するかをスキャンします。
ただし、慣習的に End、Final、Stop と名付けられた状態は、意図的な終着点とみなして警告から除外されます。
論理競合(Smart Scan)
同じ状態において、同じトリガーが発生した際、複数のガード条件が同時に「真」になってしまう可能性(決定性の欠如)を解析します。
動作原理: サンプリング評価法を採用しています。
同じ Source と Trigger を持つ遷移ペアを抽出します。
ガード式に含まれる変数を抽出します。
それらの変数に特定のサンプル値(0, 1, -1, 10, 100 など)を代入し、複数のガード式が同時に true になるケースがあるか、簡易評価器でシミュレーションします。
未定義ターゲット参照のチェック
表B(遷移)で指定された遷移先が、表A(状態定義)の中に存在しないという「参照エラー」を検出します。
動作原理: 集合照合です。
表Aに存在するすべての ID を Set(集合)に格納します。
表Bの Target 列を走査し、その ID が Set 内に存在しない場合にエラーを報告します(ワイルドカード * は除外されます)。
解析結果の表示
これらの解析はエディタへの入力時にリアルタイムで実行されます。検出された問題は画面下の「Analysis Panel」にリストアップされ、項目をクリックすると該当する状態遷移表の行をハイライトする仕組みになっています。
動的解析機能
HACE サイクルによる状態遷移解析
シミュレータが1つの遷移を実行する際、単に状態を移すだけでなく、以下の5つのフェーズ(ステップ)を厳密に実行し、論理の破綻を動的に監視します。
Exiting (退出フェーズ): 現在の状態の Exit アクションを実行し、副作用を確認します。
Acting (実行フェーズ): 遷移自体に定義された Action を実行し、変数を更新します。
Updating (更新フェーズ): 内部ポインタをターゲット状態へ移動させます。
Entering (進入フェーズ): ターゲット状態の Entry アクションを実行します。階層構造がある場合は、最深部の子状態に到達するまで自動的に進入を繰り返します(Auto-Entry)。
Checking (不変条件フェーズ): 遷移後の状態で Invariant(不変条件)が満たされているかを動的に評価します。
動作原理: 「トランザクション制御」を用いています。遷移前の変数状態をバックアップし、不変条件エラー(Invariant Violation)や実行エラーが発生した瞬間に、その遷移をなかったことにして安全な状態へロールバックします。
インバリアント(不変条件)の動的監視
各状態に定義された Invariant(表Aの4列目)が、その状態にいる間常に true であるかを監視します。
動作原理: 進入時および待機中に、現在の変数コンテキストを用いて論理式を評価します。例えば depth < 10 という不変条件がある場合、アクションによって depth が 10 になった瞬間に Error_Lock 状態へ移行させ、システムが不正な状態で動き続けるのを防ぎます。
オートチェーン(連鎖遷移)解析
「トリガーが auto である遷移」を検出し、自動的に次の状態へ連鎖させる挙動をシミュレートします。
動作原理: Checking フェーズが完了して Idle(待機)になる直前に、現在の状態で実行可能な auto トリガーがないか再スキャンします。条件(Guard)を満たす auto 遷移がある場合、ユーザーの操作を待たずに即座に次の遷移サイクルを開始します。これにより、初期化フローなどの一連の動作が正しく完了するかを動的に検証できます。
バッチ・テストランナーによる期待値検証
表C(Test Cases)に基づき、複数のシナリオを一括で自動実行し、最終状態が期待通り(Expected)になるかを検証します。
動作原理: 各テストケースごとに独立した仮想コンテキストを作成します。
指定された Initial 状態からシミュレーションを開始し、auto 遷移が止まるまで、またはエラーになるまで高速にステップ実行します。
最終的な currentStateID が Expected と一致するかを判定します(階層状態の場合、前方一致による判定も行われます)。
D+++ Visualizerを使用した開発の実例
「このツールが本当に現場で役に立つのか?」という点についてですが、私個人の意見としては、すでに実用レベルにあると考えています。 実は、今回公開した D+++ Visualizer v51 自体が、Format D+++ 形式の状態遷移表と旧バージョンのD+++ Visualizerがなければ完成しなかったからです。 具体的な開発プロセスとしては、まずツール上で設計図(状態遷移図)をしっかり作り込み、その内容を AI (Gemini 3.0) に渡してプログラムコードを生成させる、という手法をとりました。
GitHub 上に実際のコードを公開しています。この設計から出力されたコードが、皆さんのプロジェクトにおいて実用レベルにあるかどうか、ぜひその目で判断いただければ幸いです。
おわりに
公開しているD+++ VisualizerにはHTML上で表示していないものの、ロジックとしてはカバレッジ算出などの便利機能が他にもあります。使用されたい方はご自分でHTMLファイルを修正して有効化するのも有りだと思います。 将来的にはD+++ Visualizerのバージョンアップを行い、正式に機能対応したいと思いますが現状の完成度でも自分の用途においては十分なので気が向いた際に実装したいと思います。
インドでの生活の実態と感想
はじめに
今年1回もブログを更新していなかった事に気づき、技術的な内容ではないですが約半年ほどインドに滞在して感じた事を記事にします。 正確な内容を求める方には申し訳ないですが、本記事は私の主観が多分に含まれる内容になっております。
生活面
インドでの生活と聞くと非常に過酷な生活をイメージして身構えてしまいますが、日本人が名前を知っている大都市に限っては非常に発展しており生活に困る事は基本的にありません。
各都市に何人の日本人が居住しているかという情報は公開されていないため想像にはなりますが、wataru_indiaさんが言及されている下記順位表の人数は2023年現在において概ね正しいと感じます。
ちなみに、下記の順位表で日本人が1000人を下回る都市は日本人向けの飲食店などが乏しいため、生活に少し困る事が生じる印象があります。一方で日本人が1000人を超えるような大都市は大気汚染が深刻なため健康面での不安があります。
日本人居住者数ランキング(都市別)
1.デリーNCR:4000〜5000人
2.バンガロール:1500人ぐらい
3.ムンバイ:1000人ぐらい
4.チェンナイ:700〜1000人
5.アメダバード:300〜400人
6.プネ:100〜200人
7.コルカタ:50〜100人
8.ハイデラバード:20〜50人
参考文献1より引用
インド人へのお土産
インドに駐在される方やインドへ出張に行かれる方の中にはインド人に対して日本からお菓子等をお土産として持っていかれる方もいらっしゃるかと思います。無難なお土産は卵不使用のチョコレートですが、通常のチョコレートだとインドの気温で溶けてしまう場合もありベイクドチョコレートが良いと思います。
下記の表は私が見聞きした範囲での内容なので誤りがあるかもしれませんが、ベジタリアンのレベルは大きく3つに分類でき、最も厳しい人の場合は卵すら食べません。日本のお菓子には卵が含まれている事が多くお土産として持っていくと受け取って頂けない事があります。
| 名称 | 食べられる物 |
|---|---|
| ピュアベジタリアン | 完全菜食主義者で卵も食べない |
| ベジタリアン | 菜食主義者ではあるが、卵に関しては食べる人もいる |
| ベジタリアン以外 | 基本的に何でも食べるが、宗教的に大切な日だけ肉類や卵などを食べない場合がある |
インド人の時間感覚
外国人と接する機会の少ない純粋なインド人は約束の時間に少し遅れて行く事は常識であり、むしろマナーだと思っている節があるように感じます。インド人と待ち合わせする場合はその点を考慮する必要があります。
また、遅れる場合にインド人から「2分待って」と言われることが頻繁にありますが慣用表現であり、日本人が言う「ちょっと遅れます」ぐらいの意味合なので2分を超えても来ないことがあります。2分という数字に囚われず気長に待ちましょう。
インドでの暮らしで便利なサービス
インドには非常に便利なITサービスが多く、日本を凌駕するような物もあるのでご紹介します。
| サービス名称 | 内容 |
|---|---|
| Blinkit | 食品や日用品の宅配サービス、安価かつ注文から数分で配達されるので非常に便利 |
| Zepto | Blinkitと同じ食品や日用品の宅配サービス |
| Ola | タクシーやオートリキシャの配車サービス、体感ではあるがUberより捕まりやすいと感じる |
| Zomato | Uber Eatsの様なサービスで出前やレストランの席を予約する事ができる |
| MAIN DISH | 日本の食材を購入する事ができるサービス、割高ではあるが日本人にとっては非常に便利なサービス |
食生活
インドの料理は基本的にカレー風味で、北インドの料理ほど辛い傾向にあります。日本人が食べやすい辛さ控え目なインド料理は南インドの料理に多いと感じます。また、ほぼ全てのインド料理にはパクチーが含まれており、パクチーが苦手は方は注文する際にパクチーを入れないよう依頼すると良いです。
日本人が少ない土地に駐在する場合は体調を崩した場合を考え、おかゆやポカリスエットの粉など病人食を作れる食材を持って行った方が無難です。現地の料理は辛い物や油っぽい物が多く体調を崩した際に食べ物で困る事があります。日本から病人食を持ってきていない場合はフルーツのみで耐えしのぐ事になります。
インドでの業務面
業務指示
インド人に業務を依頼する際にメールで指示を出す場合があると思いますが、メール1通で迅速に対応してくれるインド人は少数派と感じます。緊急で対応してほしい内容などはメールに追加して電話や対面で依頼した方が緊急性がインド人に伝わって対応してもらえます。
また、長文のメールは最後まで読んでもらえない場合が多いので、重要な依頼や期日などはメールの最初に記載した方が良いと感じます。メールの文面も1文に一つの指示を記載する事を心掛け、複数の依頼をする場合は複数文に分けて記載した方が良いです。
言語
インドの代表的な言語はヒンディー語ですが、インドにはヒンディー語以外にも複数の言語があります。非ヒンディー語圏の一部地域では過去にヒンディー語公用語化反対運動がされていました[2]。現在でもヒンディー語に対して良くない感情を持っているインド人がいます。そのため、南インドなどの顧客を訪問した際に安易にヒンディー語で挨拶すると不評を買う可能性があります。
インド人からの贈り物
外国人があまり来ない地域の顧客に訪問すると珍しい贈り物を頂く場合があります。お菓子類などであれば頂いたとしても持ち運びが容易ですが、まれに大きな花束や仏像を頂く事があります。そういった贈り物を拒否する事は難しく持ち帰る必要があります。
トラブル対応
インド人は外国人が訪問した際に歓迎する必要があるという考えを持っている人が多く、日本人がトラブル対応でご訪問させて頂いた場合でも歓迎するスイッチが入ってしまう事がごく稀にあります。そういった場合はトラブル対応の本質的な話まで及ばず1回のご訪問では問題が解決しない事があります。
難しい所ではありますが、こちらから話題を切り出して上手く場をコントロールする必要があります。
おわりに
本記事に記載している内容は私が人づてに聞いた内容や体験した事が中心になっています。主観が多く含まれており必ずしも全ての情報が正しいとは限らないので、ご参考程度に留めてください。
参考文献
[1] wataru_india, ‘‘日本人が増えてきた件, ” https://wataru-india.hatenablog.com/entry/2022/05/25/210824.
[2] 吉田 修, ‘‘特集:州政治と連邦政治 インドにおける政治発展の特徴, ” https://www.jstage.jst.go.jp/article/asianstudies/62/4/62_33/_pdf.
中国製RISC-Vマイコン CH32V307でUART通信
はじめに
中国製RISC-Vマイコン CH32V307でUART通信を行う方法を解説します.
本記事は参考文献 1を参考にして, CH32V307V-EVT-R1で動作確認を行っています.
USARTとは
CH32V307はUARTでは無くUSARTのポートを持っています. USARTは非同期のシリアル通信に加えて同期式のシリアル通信にも対応しているポートです. 非同期通信の場合, UARTとUSARTの違いはありません.
UART:非同期式のシリアル通信にのみ対応
USART:同期および非同期式のシリアル通信に対応
サンプルプログラムの解説
CH32V307のUSART1はデバッグポートとして使用されているため, 今回のプログラムではUSART2を使用します[3]. ピンと機能の割り付けについては, 参考文献4の3.2章をご確認ください.
今回紹介するプログラムは参考文献1と参考文献3のコードを参考に記述した物です. プログラムの実装内容は下記の通りです.
GPIO_InitTypeDef, USART_InitTypeDefを定義する.
USART2のRX, TXピンのGPIOクロックとUSART2クロックを有効にする.
GPIOポートの設定とシリアルポートの初期化
NVIC_InitTypeDefの定義とNVICの設定
シリアルポートとUSART受信割り込みを有効化する.
文字を送信する関数と文字列を送信する関数を定義する
USART受信割り込み関数を定義する
USART2初期化関数
下記のコードでUSART2で使用するGPIOの初期化とUSART2の初期化を行っています.
/********************************************************************
* 関数名 : USART2_Init
* 機能 : USART2初期化
* 入力 : 無し
* 戻り値 : 無し
********************************************************************/
void USART2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //USART2のクロックを有効化する
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //GPIOAのクロックを有効化する
/* USART2 TX-->PA2 RX-->PA3 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //TX:PA2をオルタネート機能プッシュプル出力に設定
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX:回路上にプルアップ抵抗が無いので,PA3をプルアップ機能付きの入力に設定
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200; // シリアルポートのボーレートを115200に設定する
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // ワード長を8ビットデータ形式にする
USART_InitStructure.USART_StopBits = USART_StopBits_1; // ストップビット1
USART_InitStructure.USART_Parity = USART_Parity_No; // パリティビットなし
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // ハードウェアフロー制御なし
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //送受信モード
USART_Init(USART2, &USART_InitStructure); //シリアルポートを初期化する
}
割り込みの初期化
下記のコードで受信割り込みの初期化を行っています.
/********************************************************************
* 関数名 : Interrupt_Init
* 機能 : USART2受信割り込み初期化
* 入力 : 無し
* 戻り値 : 無し
********************************************************************/
void Interrupt_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure={0};
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
ASCiiコードの文字送信
ライブラリ関数のUSAT_SendDataを使用して, ASCiiコードの文字を1文字送信します.
/********************************************************************
* 関数名 : USARTx_SendByte
* 機能 : ASCiiコード文字の送信
* 入力 : USART_TypeDef, 8bitの送信データ
* 戻り値 : 無し
********************************************************************/
void USARTx_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
USART_SendData(pUSARTx, data);
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
参考文献3より引用
文字列の送信
USARTx_SendByte関数を使用して文字列を送信します.
/********************************************************************
* 関数名 : USARTx_SendStr
* 機能 : 文字列の送信
* 入力 : USART_TypeDef, 文字列データ
* 戻り値 : 無し
********************************************************************/
void USARTx_SendStr(USART_TypeDef* pUSARTx, char *str)
{
uint8_t i = 0;
do
{
USARTx_SendByte(pUSARTx, *(str+i));
i++;
}while(*(str+i) != '\0');
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}
参考文献3より引用
割り込み関数
受信した文字をそのまま送信するプログラムです.
/********************************************************************
* 関数名 : USART2_IRQHandler(void)
* 機能 : 割り込み関数
* 入力 : 無し
* 戻り値 : 無し
*********************************************************************/
void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART2_IRQHandler(void)
{
uint8_t ucTemp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);//割り込みフラグをリセットする
ucTemp = USART_ReceiveData(USART2); //データを受信する
USART_SendData(USART2, ucTemp); //データを送信する
}
}
参考文献3より引用
作成したプログラムの全体
上記のプログラムとメイン関数を記述した物が下記になります.
#include "debug.h"
/********************************************************************
* 関数名 : USART2_Init
* 機能 : USART2初期化
* 入力 : 無し
* 戻り値 : 無し
********************************************************************/
void USART2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //USART2のクロックを有効化する
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //GPIOAのクロックを有効化する
/* USART2 TX-->PA2 RX-->PA3 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //TX:PA2をオルタネート機能プッシュプル出力に設定
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX:回路上にプルアップ抵抗が無いので,PA3をプルアップ機能付きの入力に設定
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200; // シリアルポートのボーレートを115200に設定する
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // ワード長を8ビットデータ形式にする
USART_InitStructure.USART_StopBits = USART_StopBits_1; // ストップビット1
USART_InitStructure.USART_Parity = USART_Parity_No; // パリティビットなし
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // ハードウェアフロー制御なし
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //送受信モード
USART_Init(USART2, &USART_InitStructure); //シリアルポートを初期化する
}
/********************************************************************
* 関数名 : Interrupt_Init
* 機能 : USART2受信割り込み初期化
* 入力 : 無し
* 戻り値 : 無し
********************************************************************/
void Interrupt_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure={0};
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/********************************************************************
* 関数名 : USARTx_SendByte
* 機能 : ASCiiコードの文字送信
* 入力 : USART_TypeDef, 8bitの送信データ
* 戻り値 : 無し
********************************************************************/
void USARTx_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
USART_SendData(pUSARTx, data);
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/********************************************************************
* 関数名 : USARTx_SendStr
* 機能 : 文字列の送信
* 入力 : USART_TypeDef, 文字列データ
* 戻り値 : 無し
********************************************************************/
void USARTx_SendStr(USART_TypeDef* pUSARTx, char *str)
{
uint8_t i = 0;
do
{
USARTx_SendByte(pUSARTx, *(str+i));
i++;
}while(*(str+i) != '\0');
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
int main(void)
{
USART2_Init(); //USART2を初期化する
Interrupt_Init(); //割り込みを初期化する
USART_Cmd(USART2, ENABLE); //USART2を有効化する
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //割り込みを有効化する
USARTx_SendStr(USART2, "Hello World.\n"); //テストデータの送信
while(1); //無限ループ
}
/********************************************************************
* 関数名 : USART2_IRQHandler(void)
* 機能 : 割り込み関数
* 入力 : 無し
* 戻り値 : 無し
*********************************************************************/
void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART2_IRQHandler(void)
{
uint8_t ucTemp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);//割り込みフラグをリセットする
ucTemp = USART_ReceiveData(USART2); //データを受信する
USART_SendData(USART2, ucTemp); //データを送信する
}
}
動作確認の配線
CH32V307V-EVT-R1に実装されているWCH-LinkをUSBシリアル変換器として使用して動作確認を行いました.
WCH-Linkのジャンパピンを全て外し, RX0をジャンパ線でPA2に接続し, TX0をジャンパ線でPA3に接続することでWCH-LinkをUSBシリアル変換器として使えます.

WCH-LinkをUSBシリアル変換器として使用する場合の注意点
WCH-LinkをUSBシリアルとして使用すると稀にWCH-LinkがARMモードに切り替わってしまう事があります.
WCH-LinkのCON LED(青色のLED)が常時点灯している場合は, ARMモードになっているので下記の手順でRSIC-Vモードに切り替えてください.
1.WCH-Linkの電源を切りTX0とGNDをジャンパピンでショートする
2.WCH-Linkの電源を投入する. 青色LEDが消灯していればRSIC-VモードになっているのでWCH-Linkの電源を切り元の状態に戻す.
おわりに
UART通信の方法を解説しました. WCH-LinkがARMモードに切り替わってしまった時は故障かと勘違いしましたが, 参考文献5のマニュアルを確認することで対処できました.
書き込みが上手く行かない場合はWCH-Linkのマニュアルを確認すると良さそうです.
参考文献
[1] Xy_, ‘‘CH32V307教程 [第四集] [通信协议], ” https://verimake.com/d/152-ch32v307.
[2] わわわIT用語辞典, ‘‘USARTとは, ” https://wa3.i-3-i.info/word12983.html.
[3] 草帽王子, ‘‘CH32V103应用教程——USART, ” https://www.risc-v1.com/thread-1529-1-1.html.
[4] WCH, ‘‘CH32V20x_30xDS0.PDF, ” http://www.wch-ic.com/downloads/CH32V20x_30xDS0_PDF.html.
[5] WCH, ‘‘WCH-Link使用说明, ” http://www.wch.cn/bbs/thread-71088-1.html.
中国製RISC-Vマイコン CH32V307のタイマ機能
はじめに
中国製RISC-Vマイコン CH32V307のタイマ機能の使用方法を解説します.
本記事は参考文献 1を参考にして, CH32V307V-EVT-R1で動作確認を行っています.
CH32V307のタイマ機能
CH32V307は3種類のタイマーを持っています. 各タイマの概要を下記に記載します.
| タイマの名称 | 詳細 |
|---|---|
| Advanced-control Timer (ADTM) | 最も多機能な16bitタイマで, カスケード接続によりタイマ間で同期できます. (TIM1, 8, 9, 10に機能が割り当たっています. ) 詳細については参考文献2の14章をご確認ください. |
| General-purpose Timer (GPTM) | 16bitの基本的なキャプチャ機能とPWM出力機能などを持つ汎用タイマです. (TIM2, 3, 4, 5に機能が割り当たっています. ) 詳細については参考文献2の15章をご確認ください. |
| Basic Timer (BCTM) | 最も機能の少ない16bitタイマで割り込みなどに使用できます. (TIM6, 7に機能が割り当たっています) 詳細については参考文献2の16章をご確認ください. |
タイマ割り込み機能
タイマ割り込みを使用してLEDを0.5秒ごとに点滅させるプログラムを作成します.
紹介するプログラムではBCTMのTIM6(16bitのカウンタ)を使用して割り込みを行っています.
動作周波数の設定
CH32V307の動作周波数はデフォルトで72MHzに設定されているので144MHzに変更します.
「User」フォルダの直下にある「system_ch32V30x.c」のコードを表示します. この部分でチップの動作周波数が設定されており, 下記の様に72MHzの部分をコメントアウトし, 144MHzの部分を有効化することで144MHzで動作します.

水晶発振器の起動タイムアウト時間設定
実装されている水晶発振器の起動時間が長くタイムアウトする場合があるので, 水晶発振器の起動タイムアウト閾値を変更します.
水晶発振器の起動タイムアウト閾値を変更は「ch32v30x.h」で行います. 「ch32v30x.h」は「Debug」フォルダ直下にある「debug.h」を表示し, インクルードされている「ch32v30x.h」を右クリックして「Open Declaration」を選択することで表示できます.

HSE_STARTUP_TIMEOUTが水晶発振器の起動タイムアウト閾値です. この値を0x4000に変更します.
ちなみにHSE_VALUEが外部水晶発振器の周波数です. CH32V307V-EVT-R1の水晶発振器は8MHzなので今回は変更する必要ありません.
タイマの初期化
TIM6を初期化するコードは下記の通りです.
設定する機能の名称や関数名がSTM32系のマイコンと同じなので, それらのマイコンを使用した事がある人は簡単に設定できると思います. CH32Vシリーズのタイマ関連のコードを詳しく確認したい方は参考文献4に記載されているのでご確認ください.
/********************************************************************
* 関数名 : TIM6_Init
* 機能 : TIM6タイマ初期化
* 入力 : arr:カウント上限値,psc プリスケーラの値
* 戻り値 : 無し
********************************************************************/
void TIM6_Init( u16 arr, u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM6, ENABLE ); //TIM6クロックを有効化する
TIM_TimeBaseInitStructure.TIM_Period = arr; //カウント上限回数を設定する
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //クロックの分周に使用するプリスケーラの値を指定
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //クロック分周係数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Down; //TIMカウントモード, ダウンカウントモード
TIM_TimeBaseInit( TIM6, &TIM_TimeBaseInitStructure); //設定したパラメータにしたがってTIM6を初期化するコード
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); //TIM6割り込みを有効化し, 割り込み更新を許可する
TIM_ARRPreloadConfig( TIM6, ENABLE ); //TIM6のプリロード機能を有効化
TIM_Cmd( TIM6, ENABLE ); //TIM6を有効にする
}
参考文献1より引用
割り込みの設定
TIM6のNVICを設定します.
設定方法は下記の通り, 前回紹介した外部割込みのコードから一部分を削除した様な感じです.
/********************************************************************
* 関数名 : Interrupt_Init
* 機能 : タイマ割り込み初期化
* 入力 : 無し
* 戻り値 : 無し
********************************************************************/
void Interrupt_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure={0};
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
参考文献1より引用
割り込みプログラム
割り込み関数も外部割込みの際に使用したプログラムと大差ありません.
下記の通りに入力してください.
/********************************************************************
* 関数名 : TIM6_IRQHandler
* 機能 : 割り込み関数
* 入力 : 無し
* 戻り値 : 無し
*********************************************************************/
void TIM6_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
volatile uint16_t LED_Status = 0;
void TIM6_IRQHandler(void)
{
TIM_ClearFlag(TIM6, TIM_FLAG_Update);//割り込みフラグリセット
LED_Status = !LED_Status ;
GPIO_WriteBit(GPIOE, GPIO_Pin_11, LED_Status);
}
参考文献1より引用
プログラム全体
これまでに作成したプログラムにGPIOの初期化とメイン関数を追加した物を以下に記載します.
メイン関数内に記述されている「TIM6_Init」の引数の意味は下記の通りです. (レジスタは0からカウントを開始するため, 設定したい値から-1しています.)
14400-1:分周比を14400に設定して144MHzを10kHzにするためにこの値を入れています.
5000-1:カウント値を5000に設定し, 0.5msので割り込みが発生するようにしています.
#include "debug.h"
/********************************************************************
*関数名 : GPIO_INIT
*機能 : GPIO初期化
*入力 : 無し
*戻り値 : 無し
********************************************************************/
void GPIO_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
/********************************************************************
* 関数名 : TIM6_Init
* 機能 : TIM6タイマ初期化
* 入力 : arr:カウント上限値,psc プリスケーラの値
* 戻り値 : 無し
********************************************************************/
void TIM6_Init( u16 arr, u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM6, ENABLE ); //TIM6クロックを有効化する
TIM_TimeBaseInitStructure.TIM_Period = arr; //カウント上限回数を設定する
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //クロックの分周に使用するプリスケーラの値を指定
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //クロック分周係数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Down; //TIMカウントモード, ダウンカウントモード
TIM_TimeBaseInit( TIM6, &TIM_TimeBaseInitStructure); //設定したパラメータにしたがってTIM6を初期化するコード
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); //TIM6割り込みを有効化し, 割り込み更新を許可する
TIM_ARRPreloadConfig( TIM6, ENABLE ); //TIM6のプリロード機能を有効化
TIM_Cmd( TIM6, ENABLE ); //TIM6を有効にする
}
/********************************************************************
* 関数名 : Interrupt_Init
* 機能 : タイマ割り込み初期化
* 入力 : 無し
* 戻り値 : 無し
********************************************************************/
void Interrupt_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure={0};
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/********************************************************************
* 関数名 : main
* 機能 : main関数
* 入力 : 無し
* 戻り値 : 無し
*********************************************************************/
int main(void)
{
GPIO_INIT(); //GPIO初期化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//割り込み優先度の設定
TIM6_Init( 5000-1, 14400-1 );
Interrupt_Init();//割り込み初期化
while(1); // 無限ループ
}
/********************************************************************
* 関数名 : TIM6_IRQHandler
* 機能 : 割り込み関数
* 入力 : 無し
* 戻り値 : 無し
*********************************************************************/
void TIM6_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
volatile uint16_t LED_Status = 0;
void TIM6_IRQHandler(void)
{
TIM_ClearFlag(TIM6, TIM_FLAG_Update);//割り込みフラグリセット
LED_Status = !LED_Status ;
GPIO_WriteBit(GPIOE, GPIO_Pin_11, LED_Status);
}
参考文献1より引用
動作確認のための配線
動作確認をする際はCH32V307V-EVT-R1のPE11とLED1を配線してください.

おわりに
CH32V307のタイマ機能について紹介しました. 参考文献1ではPWM機能の使い方も紹介しているので, PWMを使用したい方は参考文献1をご参照ください.
CH32系マイコンはSTM32と近い使用感でプログラミングでき, 中国国内ではSTM32を置き換える事の出来る製品としてCH32V103が紹介されています[5]. STM32より安く, 使用感が近いマイコンを探している方は試しに使用してみてもいいかもしれません.
参考文献
[1] Xy_, ‘‘CH32V307教程 [第三集] [时钟], ” https://verimake.com/d/151-ch32v307.
[2] openwch, ‘‘CH32FV2x_V3x Reference Manual, ” https://github.com/openwch/ch32v307/tree/main/Datasheet.
[3] openwch, ‘‘Input_Capture, ” https://github.com/openwch/ch32v307/blob/main/EVT/EXAM/TIM/Input_Capture/User/main.c.
[4] 草帽王子, ‘‘第七章:CH32V103应用教程——定时器中断, ” https://www.risc-v1.com/thread-1533-1-1.html.
[5] gaosmile, ‘‘[分享] 能够替代STM32的国产产品介绍, ” https://www.stmcu.org.cn/module/forum/forum.php?mod=viewthread&tid=628667.
中国製RISC-Vマイコン CH32V307の外部割込み機能
はじめに
中国製RISC-Vマイコン CH32V307の外部割込み機能の使用方法を解説します.
前回と同様に参考文献 1を参考にCH32V307V-EVT-R1を使用して動作確認を行いましたので, その方法をメモとして残します.
CH32V307の外部割込み機能
CH32V307には16個の外部割込みが存在し, EXTI0~EXTI15を全てのIOポート(Px0~Px15(x=A/B/C/D/E))に外部割込み/イベント機能として設定することが出来ます.
しかし, 外部割込みのチャンネルとGPIOのポート番号が一致していなければならないという制約があります. つまり, PA1に対して割り込みを設定している場合, PB1, PC1, PD1, PE1には割り込みを設定できません.
| 割り込みのチャンネル | 対応ポート |
|---|---|
| EXTI0 | PA0, PB0, PC0, PD0, PE0 |
| EXTI1 | PA1, PB1, PC1, PD1, PE1 |
| EXTI2 | PA2, PB2, PC2, PD2, PE2 |
| ⋮ | ⋮ |
| EXTI15 | PA15, PB15, PC15, PD15, PE15 |
外部割込み機能有効化の手順
WCHが公式にアップロードしている外部割込みのサンプルコードに記載されている初期化プログラムは3つの機能で構成されています.
外部入力を入れるGPIOを初期化し, 入力モードとして設定する
EXTernal Interrupt(EXTI)を設定する
ベクタ割込みコントローラ(NVIC)を設定する
コードの中身を確認するとPA0を割り込み用のピンとして設定しており, 信号の立下りで割り込みが入るようになっている事が分かります.
void EXTI0_INT_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
EXTI_InitTypeDef EXTI_InitStructure = {0};
NVIC_InitTypeDef NVIC_InitStructure = {0};
/* 外部入力を入れるGPIOを初期化し, 入力モードとして設定する*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*EXTernal Interrupt(EXTI)を設定する*/
/* GPIOA ----> EXTI_Line0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //割り込みのトリガー条件設定
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* ベクタ割込みコントローラ(NVIC)を設定する*/
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //割り込み優先度
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //サブプライオリティー
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
参考文献2より引用
割り込み発生時の処理
WCHが公式にアップロードしている外部割込みの実行部分はch32v30x_it.cに記載されています.
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
/*********************************************************************
* @fn EXTI0_IRQHandler
*
* @brief This function handles EXTI0 Handler.
*
* @return none
*/
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)!=RESET)
{
#if 0
printf("Run at EXTI\r\n");
#endif
EXTI_ClearITPendingBit(EXTI_Line0); /* Clear Flag */
}
}
参考文献2より引用
動作確認用プログラム
参考文献1に記載されているプログラムを参考に割り込みプログラムの作成を行いました. このプログラムはPA0, PC1に割り込み機能を割り当て, スイッチのON/OFFでLED1, 2を点灯/消灯させます.
#include "debug.h"
/********************************************************************
* 関数名 : GPIO_INIT
* 機能 :LED点灯用GPIOの初期化
********************************************************************/
void GPIO_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
/********************************************************************
* 関数名 : EXTI_INT_INIT
* 機能 : 外部割り込みの初期化
********************************************************************/
void EXTI_INT_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
EXTI_InitTypeDef EXTI_InitStructure={0};
NVIC_InitTypeDef NVIC_InitStructure={0};
/*
* EXTI0の設定
* GPIOAの0ピンに対して割り込み機能を設定する
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line= EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //割り込みのトリガー条件設定
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //割り込み優先度
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //サブプライオリティー
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/*
* EXTI1の設定
* GPIOCの1ピンに対して割り込み機能を設定する
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource1);
EXTI_InitStructure.EXTI_Line= EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //割り込みのトリガー条件設定
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //割り込み優先度
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //サブプライオリティー
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/********************************************************************
* 関数名 : main
* 機能 : メイン関数
*********************************************************************/
int main(void)
{
EXTI_INT_INIT(); // 割り込み機能の初期化
GPIO_INIT(); // GPIOの初期化
while(1); // 無限ループ
}
/********************************************************************
* 関数名 : EXTI0_IRQHandler
* 機能 : EXTI0の割り込み関数
*********************************************************************/
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
volatile uint16_t LED_Status = 0; // volatileを持つ割り込みで使用される変数は、グローバル変数として扱うことができる
void EXTI0_IRQHandler(void)
{
Delay_Ms(500); //チャタリング防止用
EXTI_ClearFlag(EXTI_Line0); // 割り込みフラグをリセット
LED_Status = !LED_Status ; // LEDの状態値の反転
GPIO_WriteBit(GPIOE, GPIO_Pin_11, LED_Status); // PE11(つまりLED1)の状態を設定する
}
/********************************************************************
* 関数名 : EXTI1_IRQHandler
* 機能 : EXTI1の割り込み関数
*********************************************************************/
void EXTI1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
volatile uint16_t LED_Status2 = 0; // volatileを持つ割り込みで使用される変数は、グローバル変数として扱うことができる
void EXTI1_IRQHandler(void)
{
Delay_Ms(500); //チャタリング防止用
EXTI_ClearFlag(EXTI_Line1); // 割り込みフラグをリセット
LED_Status2 = !LED_Status2 ; // LEDの状態値の反転
GPIO_WriteBit(GPIOE, GPIO_Pin_12, LED_Status2); // PE11(つまりLED1)の状態を設定する
}
動作確認時の配線
CH32V307V-EVT-R1には自由に使用できるスイッチが一つしかないので, PA0で動作確認する場合とPC1で動作確認する場合それぞれで配線を変える必要があります.
・PA0とKEYの配線 : スイッチの配線PA0の割り込み機能確認時に配線する
・PC1とKEYの配線 : スイッチの配線PC1の割り込み機能確認時に配線する
・PE10とLED1の配線 : LED1の配線
・PE11とLED2の配線 : LED2の配線

おわりに
外部割込み機能の確認を行いました. サンプルコードや情報が充実(中国語ではありますが)しているので, 使用経験のある他の中国製マイコンと比較して使いやすいマイコンだと感じました.
参考文献
[1] Xy_, VeriMake, ‘‘CH32V307教程 [第二集] [GPIO], ” https://verimake.com/d/150-ch32v307-gpio.
[2] WCH, ‘‘EXTI / EXTI0, ” https://github.com/openwch/ch32v307/blob/main/EVT/EXAM/EXTI/EXTI0/User/main.c.
中国製RISC-Vマイコン CH32V307V-EVT-R1 の開発環境構築とサンプルプログラムの実行
はじめに
中国の組み込み業界では西側諸国のプロセッサIP独占を打破するため, 積極的にRISC-VのプロセッサIP開発を行っています[1].
高機能な中国製RISC-Vマイコンを知るためにCH32V307V-EVT-R1の開発環境構築を行ったので, サンプルプログラムの実行までの手順をメモとして残します.
CH32V307V-EVT-R1について
CH32V307Vは32ビット 144MHz動作のRISC-Vマイコンであり, ハードウェアスタック領域と高速割り込みを備えています.
標準のRISC-Vと比較して, 割り込み応答速度が大幅に向上, 単精度浮動小数点命令セットが追加され, スタック領域, UART数, モータータイマー数が拡張されています. そして, USB2.0高速インターフェース(480Mbps)とPHYトランシーバーを内蔵しています[2].

購入方法
CH32V307VはLCSCもしくはAliExpressで購入することが出来ます.
22年5月現在のLCSCでの価格はIC単体で¥562, 開発ボードで¥1510です. 円安の影響もあってか少し割高ですが, マイコンの入手性が悪い現在でも購入できました.
データシートのダウンロード
CH32V307Vの英語版データシートは参考文献3のリンクからダウンロードできます.
電気的特性はCH32V20x_30xDS0に記載されており, 周辺機能の説明, 使用方法, レジスタ構成についてはCH32FV2x_V3xRMに記載されています.
開発環境のダウンロードとインストール
以降の内容は参考文献4の内容をベースに記載しています.
一次情報を確認したい方は参考文献4をご確認ください. また、本記事では開発環境の機能の一部分のみ紹介するので, より詳細な使用方法については参考文献5をご参照ください.
CH32V307Vは無償で利用できるMounRiver Studioで開発できます. 参考文献6のリンクから公式サイトに移動し, 下記の図の赤枠で示すリンクをクリックしてインストーラをダウンロードしてください.

解凍したフォルダに格納されているexeファイルを起動してインストールを行います.
インストールフォルダのパスにスペースが入っているとインストールに失敗するので, 特にこだわりが無い場合はインストール先はデフォルトのままにしておく事をお勧めします.

プロジェクトファイルの作成
インストール後の最初の起動時は下記の画面が表示されるので, 赤枠で示す「New MounRiver Project」を選択します.

(1)使用するマイコンのシリーズ(CH32V307)を選択します.
(2)詳細な形名(CH32V307VCT6)を選択します.
(3)Project Nameを入力します.
(4)保存するフォルダのパスを変更する場合はチェックを外し, フォルダを選択する

プログラムの作成
Lチカのサンプルプログラムを作成します.
Project ExplorerからUser→main.cを選択し, メインプログラムを表示します. 初期状態のプログラムはprintfのサンプルが記述されています.

初期状態のプログラムにGPIOポートを初期化するコードを追加します. 下記のプログラムをint main(void)より前にコピぺして下さい.
/*******************************************************************************
* Function Name : GPIO_INIT
* Description : Initializes GPIOA.0
* Input : None
* Return : None
*******************************************************************************/
void GPIO_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
参考文献4より引用
main関数の中にIOポートを制御するコードを追加します. 初期状態で入力されているmain関数を削除して, 下記のコードをコピペしてください.
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main(void)
{
u16 i=0;
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
GPIO_INIT();
while(1)
{
Delay_Ms(250);
GPIO_WriteBit(GPIOE, GPIO_Pin_11, (i==0) ? (i=1) : (i=0));
GPIO_WriteBit(GPIOE, GPIO_Pin_12, (i==1) ? 0 : 1);
}
}
参考文献4より引用

プログラムのビルド
ビルドは下記の画像に示す部分をクリックすることで実行されます.

ビルドを実行すると下記のエラーが発生する事があります. 「Save as UTF-8」を選択してください.

ビルドが成功するとConsoleに下記の表示され, プロジェクトフォルダの下のOBJフォルダにプロジェクトファイル名のHEXファイルが生成されます.

マイコンへの書き込み
CH32V307-EVT-R1のWCH-LinkインターフェースをUSBケーブルでPCと接続し, CH32V307-EVT-R1の電源をONします.

下記の赤枠部分に示すDownloadボタンをクリックすると書き込みが開始します.

書き込みが完了するとConsoleに下記の表示されます.

動作確認のための配線
WCH社が公開している下記の回路図に記載されている通り, CH32V307V-EVT-R1にはユーザーが動作確認に使用できるLEDが2個実装されています.
LEDを使用するにはジャンパ線などを使用してLEDのピンとマイコンピンを接続する必要があります.

作成したサンプルプログラムではPE11, PE12をON/OFFさせているので, 開発ボードのPE11とLED1, PE12とLED2をジャンパ線で接続します.

配線完了後にWCH-LinkインターフェースをUSBケーブルでPCと接続し, CH32V307-EVT-R1の電源をONして動作を確認します.

補足情報
書き込み設定の変更
Download設定を変更する場合はDownloadボタン横の三角マークを選択してConfigrationをクリックしてください.

設定画面ではプログラムの削除, Verifyなどが選択できます.

おわりに
開発環境の構築が容易かつEclipseベースの統合開発環境で開発できるので非常に使い製品でした. 本記事で紹介したCH32V307は高機能な製品でしたが, 低価格版のCH32V203であればIC単体を¥100程度で購入できるので価格を重視する趣味用途にもお勧めです.
参考文献
[1] 电子电路开发学习, ‘‘RISC-V当真是中国处理器产业的最后一次机会?, ” https://mp.weixin.qq.com/s?__biz=MzUzNzk2NTMxMw==&mid=2247484055&idx=1&sn=149af2be86e491c7e0fbfb139834aa84&chksm=fadfa4f9cda82defc37ed5f51b5b5ef7d944296201e104e668cb6402e09a44a8250eb4eda10d&scene=21#wechat_redirect.
[2] openwch, ‘‘32-bit Interconnected RISC-V MCU CH32V307, ” https://github.com/openwch/ch32v307/blob/main/README.md.
[3] openwch, ‘‘Datasheet and Reference Manual, ” https://github.com/openwch/ch32v307/tree/main/Datasheet.
[4] Xy_, VeriMake, ‘‘CH32V307教程 [第一集] [开发环境-MRS], ” https://verimake.com/d/12-ch32v307-mrs.
[5] DengCJ96, ‘‘MounRiver Studio 笔记(1.IDE安装), ” https://www.bilibili.com/read/cv10971816.
[6] MounRiver Studio, ‘‘Embedded RISC-V IDE, ” http://mounriver.com/download.
EAC規格と安全規則(TRCU)について
はじめに
EACマークは電子機器などによく表示されており目にする事の多いマークですが, CEマークなどと比べると馴染みが薄く日本語の情報が少ない規格です. そこで, EACマークについて調べる際に役立つ情報を残します.
EACマークとは
EEU加盟国間(ロシア、ベラルーシ、カザフスタン、アルメニア、キルギス)で合意された「強制認証」としての安全規則(Technical Regulations of Customs Union: TRCU)に適合していることを示すマークで, 欧州連合(EU)のCEマークと異なりいまだ「自己宣言制度」は導入されておらず, 第三者認証機関(認証業務の代行機関:notified body)による試験, もしくは, 認証が求められています[1].
EACマークの規定
EACマークの形状やサイズについての法令は2つ有り, 参考文献[2]から原文を閲覧する事が出来ます.
EACマークには2種類のカラーバリエーションがありますが, 色による意味の違いは無く, 製品に印字した際に見やすい方を製造者が選択します.


また, EACマークのサイズは5㎜以上とする事が求められており, 下記の図の様に同じ高さと幅にする必要があります[3].

安全規則(TRCU)の種類
安全規則とはEACマークの適合性を証明するために達成が求められる規則で, 第三者機関による確認を受けます.
2021年現在発行されている規則を下記の表に示します. あくまで21年現在の情報ですので正確な情報を確認した方は参考文献[4]から最新情報をご確認ください.
| 分類 | 規格名称 | 規格番号(ロシア語表記) |
|---|---|---|
| 鉄道関連 | 鉄道車両の安全性について | ТР ТС 001/2011 |
| 鉄道関連 | 高速鉄道の安全性について | ТР ТС 002/2011 |
| 鉄道関連 | 鉄道輸送インフラの安全性について | ТР ТС 003/2011 |
| 自動車関連 | 交通安全 | ТР ТС 014/2011 |
| 自動車関連 | 車輪付き車両の安全について | ТР ТС 018/2011 |
| 自動車関連 | 農業トラクター、林業トラクター、及び同牽引車の安全 | ТР ТС 031/2012 |
| 船舶関連 | 小型船舶の安全 | ТР ТС 026/2012 |
| 設備・プラント | 低電圧機器の安全性について(LVD) | ТР ТС 004/2011 |
| 設備・プラント | 機械設備の安全性について | ТР ТС 010/2011 |
| 設備・プラント | エレベータの安全性 | ТР ТС 011/2011 |
| 設備・プラント | 技術的手段の電磁適合性(EMC) | ТР ТС 020/2011 |
| 設備・プラント | エネルギー消費装置のエネルギー効率に関する要件について | ТР ЕАЭС 048/2019 |
| 防爆規制 | 花火の安全性について | ТР ТС 006/2011 |
| 防爆規制 | 爆発危険環境下で稼働する設備の安全 | ТР ТС 012/2011 |
| 防爆規制 | 自動車燃料、航空燃料、軽油、船舶燃料、ケロシン、重油の安全性 | ТР ТС 013/2011 |
| 防爆規制 | 気体燃料で駆動する設備の安全 | ТР ТС 016/2011 |
| 防爆規制 | 爆発物とその製品の安全 | ТР ТС 028/2012 |
| 防爆規制 | 潤滑剤、潤滑油、及び特殊溶液の安全 | ТР ТС 030/2012 |
| 防爆規制 | 過度の圧力下で動作する機器の安全性 | ТР ТС 032/2013 |
| 防爆規制 | 液化炭化水素ガス燃料の安全 | ТР ЕАЭС 036/2016 |
| 防爆規制 | 液化炭化水素ガス燃料の安全 | ТР ЕАЭС 039/2016 |
| 防爆規制 | 火災安全および消火を確保する手段の要件 | ТР ЕАЭС 043/2017 |
| 防爆規制 | 石油の輸送及び利用に関する安全 | ТР ЕАЭС 045/2017 |
| 防爆規制 | 輸送および(または)使用のために準備された天然燃料ガスの安全性 | ТР ЕАЭС 046/2018 |
| 化学全般 | 汎用樹脂等の梱包材料の安全 | ТР ТС 005/2011 |
| 化学全般 | 香水、化粧品の安全 | ТР ТС 009/2011 |
| 化学全般 | 軽工業品の安全 | ТР ТС 017/2011 |
| 化学全般 | 電気・無線電子製品における有害物質使用制限電気製品、及び無線製品に組み込まれる危険物質に関わる制限規定(RoHS) | ТР ЕАЭС 037/2016 |
| 化学全般 | 化学品の安全(REACH) | ТР ЕАЭС 041/2017 |
| 食品, 他 | 穀物の安全 | ТР ТС 015/2011 |
| 食品, 他 | 食品の安全 | ТР ТС 021/2011 |
| 食品, 他 | 食品表示 | ТР ТС 022/2011 |
| 食品, 他 | フルーツ、野菜ジュースの安全 | ТР ТС 023/2011 |
| 食品, 他 | 油脂製品の安全 | ТР ТС 024/2011 |
| 食品, 他 | 個別特定食品(ダイエット治療、ダイエット予防等)の安全 | ТР ТС 027/2012 |
| 食品, 他 | 食品添加物、香料、機能補助剤の安全 | ТР ТС 029/2012 |
| 食品, 他 | 食肉、肉加工製品の安全 | ТР ТС 034/2013 |
| 食品, 他 | 牛乳、及び乳製品の安全 | ТР ТС 033/2013 |
| 食品, 他 | タバコ製品の安全 | ТР ТС 035/2014 |
| 食品, 他 | 魚、及び水産物の安全 | ТР ЕАЭС 040/2016 |
| 食品, 他 | パッケージ飲料水(ミネラルウオーター等)の安全 | ТР ЕАЭС 044/2017 |
| 食品, 他 | アルコール製品の安全 | ТР ЕАЭС 047/2018 |
| その他 | 未成年者・児童向け製品の安全 | ТР ТС 007/2011 |
| その他 | 玩具の安全 | ТР ТС 008/2011 |
| その他 | 個人保護用具の安全性 | ТР ТС 019/2011 |
| その他 | 家具の安全 | ТР ТС 025/2012 |
| その他 | アトラクションの安全 | ТР ЕАЭС 038/2016 |
| その他 | 児童公園の遊具・設備類の安全 | ТР ЕАЭС 042/2017 |
安全規則(TRCU)原文の確認方法
安全規則(TRCU)は参考文献[4]から取得する事も出来ますが, PDF化された文書(ロシア語)のみの提供で, 日本人が読み解くのは困難です.
しかし, コーデックス(参考文献[5]のサイト)ではロシア国内の法律をWebページ上で公開しているので, ブラウザの翻訳機能を使用することで日本語で閲覧する事が出来ます.
コーデックスの利用方法
コーデックスの利用方法は簡単でWebページ上の検索バーに法律の名称もしくは番号を入力して検索するだけです. 注意点としてはロシア語でしか検索する事ができないので, 「TRCU」を検索する際には「 ТР ТС 」などロシア語表記で検索する必要があります.
「安全規則(TRCU)の種類」の章に記載した表の右側にロシア語表記の規格名称を記入したので検索する際はその部分をコピペして検索すると効率的に探すことが出来ると思います.

おわりに
EACマークの概要と安全規則(TRCU)の情報についてまとめました. しかし, これらの情報は21年時点での物なので, 将来的にEACマークの要件が変わったり, TRCUに追加の規則が加わる可能性も十分にあります. 本ブログに記載されている情報を鵜呑みにせず, 参考文献として記載させて頂いているページなどから正確な情報を確認して下さい.
参考文献
[1] 独立行政法人日本貿易振興機構 , ‘‘GOST-R(ロシア国家標準規格)認証取得:ロシア向け輸出, ” https://www.jetro.go.jp/world/qa/04J-110101.html.
[2] ユーラシア経済連合 , ‘‘Единый знак обращения продукции на рынке, ” http://www.eurasiancommission.org/ru/act/texnreg/deptexreg/coordination/Pages/EAC.aspx.
[3] ユーラシア経済連合 , ‘‘Положение о едином знаке обращения продукции на рынке государств - членов Таможенного союза, ” http://www.eurasiancommission.org/ru/act/texnreg/deptexreg/coordination/Documents/PologenieEAC.pdf.
[4] ユーラシア経済連合 , ‘‘Технические регламенты, вступившие в силу, ” http://www.eurasiancommission.org/ru/act/texnreg/deptexreg/tr/Pages/TRVsily.aspx.
[5]Акционерное общество"Информационная компания "Кодекс" , ‘‘Кодекс , ” https://docs.cntd.ru/.
