技術者見習いのメモ書き

電子工作や神社めぐりなどの趣味の話を中心に書いていきます。 Twitter:@hakura_riku

中国製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のコードを参考に記述した物です. プログラムの実装内容は下記の通りです.

  1. GPIO_InitTypeDef, USART_InitTypeDefを定義する.

  2. USART2のRX, TXピンのGPIOクロックとUSART2クロックを有効にする.

  3. GPIOポートの設定とシリアルポートの初期化

  4. NVIC_InitTypeDefの定義とNVICの設定

  5. シリアルポートとUSART受信割り込みを有効化する.

  6. 文字を送信する関数と文字列を送信する関数を定義する

  7. 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.