技術者見習いのメモ書き

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

中国製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」を選択することで表示できます.

ch32v30x.hの表示方法

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.