前回,前々回までは,正確な周期がわからない状態でLチカをしていました.
今回は汎用タイマの割込みを使って正確な2秒周期でのLチカをレジスタ叩きで実装します.
delay関数を使う手もありますが,待ち時間の間他の処理できないですし,折角レジスタ叩いているのでマイコンのハードウェア機能を使いたいところです.
今回もまた,データシートとリファレンスマニュアルを手元に置いておいてください.
環境の準備は前々回あるいは別の方の記事等を参照ください.
STM32C011のタイマ
STM32C011のデータシートの20ページを見るとなんとタイマが5つもあります!やりたい放題できますね
TIM1やTIM3は4チャンネル出せるのでPWMでモータ制御やLED調光したくなります.
ということで,今回はタイマ割込みにTIM14を使います.
他のタイマでやる場合も殆ど同じコードで動くはずですが,若干タイマごとにレジスタ構成が違うようなので,万全を期すならば都度リファレンスマニュアルを引くことをお勧めします.
タイマ割込み
本題に入ります.今回使うコードは以下の通りです.
#include <stm32c0xx.h>
void TIM14_Configuration(void);
void RCC_Configuration(void);
void GPIO_Configuration(void);
int main (void){
__disable_irq();
RCC_Configuration();
GPIO_Configuration();
TIM14_Configuration();
__enable_irq();
while (1){}
return 0;
}
void TIM14_IRQHandler(void){
if (TIM14->SR & TIM_SR_UIF)
{
TIM14->SR &= ~TIM_SR_UIF; // 割り込みフラグをクリア
GPIOA->ODR ^= 0x00000001; // PA0を反転
}
}
void RCC_Configuration(void){
// 48MHz
RCC->CR |= RCC_CR_HSION; // HSI始動
while(!(RCC->CR&RCC_CR_HSIRDY)); // クロック安定化を待機
RCC->CR &= ~RCC_CR_HSIDIV_Msk; // クロック分周設定
RCC->CR |= 0x0UL<<RCC_CR_HSIDIV_Pos; // 分周なし
RCC->CFGR &= ~RCC_CFGR_SW_Msk; // HSIをクロック減に設定
}
void GPIO_Configuration(void){
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // GPIOAにクロックを供給
GPIOA->MODER &= ~GPIO_MODER_MODE0_Msk;
GPIOA->MODER |= (GPIO_MODE_OUTPUT_PP << GPIO_MODER_MODE0_Pos); // A0をOutputに
}
void TIM14_Configuration(void){
RCC->APBENR2 |= RCC_APBENR2_TIM14EN;
TIM14->PSC = 4800-1; // プリスケーラを480に設定して10kHzのクロックを得る
TIM14->ARR = 10000-1; // 周期指定 0.1ms単位
TIM14->DIER |= TIM_DIER_UIE; // update割り込みを有効化
TIM14->CR1 |= TIM_CR1_CEN; // TIM14有効化
// 割り込み設定
NVIC_EnableIRQ(TIM14_IRQn);
NVIC_SetPriority(TIM14_IRQn, 1); // 割り込み優先度を設定
}
前回と違うのはタイマ14の設定を行うTIM14_Configuration関数が追加されたことと,TIM14_IRQHandler関数が追加されたことです.
今回もHSI48から48MHzのクロックを供給しています.
タイマへのクロック供給
まずはTIM14_Configuration関数の中身から見ていきます.
始めにRCC->APBENR
レジスタを操作してTIM14へのクロック供給を開始します.
リセット状態では各機能へのクロック供給がされませんので,これをしないと動きません.
TIM14のクロック供給設定はAPBENR2レジスタから操作します.
TIM14_ENビットをセットします。
RCC->APBENR2 |= RCC_APBENR2_TIM14EN;
続いて,タイマの時間設定を行いますが,その前に念のためSYSCLKからTIMPCLKまでの流れを確認します.
RCC_Configuration関数でSYSCLKには48MHzのクロックを設定しています.
ここからタイマ群TIMxまではAMB_PRESCとAPB_PRESCの二段階のプリスケーラを通過しますが,これらはリセット状態で1分周に設定されています.
注釈によると,その状態ではTIMPCLK前段も1倍とのことです.
結局タイマ(TIMPCLK)にも48MHzが供給されています.これを基にタイマ内のレジスタを操作します.
プリスケーラの設定
今回は2秒周期でLチカさせますので,1秒毎に割り込みを起こしてLED状態を反転させることを考えます.
48MHzから1秒をつくるためには4800万回クロックを数えなければなりませんが,16bitのカウンタで直接数えようとするとオーバーフローしてしまいます.
そのためにまずタイマ内のプリスケーラでカウンタクロックを10kHzまで落とします.
設定するレジスタは TIM14->PSC
レジスタです.
4800-1を設定することで10kHzまで分周します.TIM14_PSCレジスタは16ビットですので,設定値が65535をオーバーフローしないように注意します.
TIM14->PSC = 4800-1; // プリスケーラを4800に設定して10kHzのクロックを得る
詳細は多分PWMの項でお話しますが,この設定でタイマの時間分解能が決まります.
タイマ周期の設定
今回はUpcountingモードと呼ばれる,カウントが一定まで達するたびに繰り返し割り込みイベントが発生する機能を使います.
このカウントの最大値を設定するのが TIM14->ARRレジスタです.
こちらも16bitなので設定値が65535を超えないように注意が必要です.
今回は10kHzを10000回数えることで1秒周期での割り込み発生とします.
TIM14->ARR = 10000-1; // 周期指定 0.1ms単位
割り込みの有効化
担当はTIM14->DIER
レジスタです.
TIM14->DIER |= TIM_DIER_UIE; // update割り込みを有効化
割り込み関数の有効化
また,割り込み関数の有効化も行います.
ちょっとここは私の勉強不足の部分があり,中でどうなっているか把握しきれていません.
NVICはシステム周りの関数を指し,システム側で定義済みのようです.
NVIC_EnableIRQ(TIM14_IRQn);
NVIC_SetPriority(TIM14_IRQn, 1); // 割り込み優先度を設定
NVIC_SetPriority関数で優先度を指定することができます.
タイマ割込み関数
次にTIM14_IRQHandler関数の中身を説明します.
先ほどの有効化により,TIM14で割り込みイベントが発生すると,自動的にTIM14_IRQHandler関数に飛びます.
これも名前が定義済みのようです.
まずは割り込みフラグを確認します.担当はTIM14->SR
レジスタです.
updateイベントで割り込みが起こった場合にハードウェアでセットされます.
このように,フラグを確認することで同一のイベント関数の中で割り込みイベント毎に処理を分岐させることができます.特に外部ピン割り込みでは重要になります.
if (TIM14->SR & TIM_SR_UIF)
{
TIM14->SR &= ~TIM_SR_UIF; // 割り込みフラグをクリア
GPIOA->ODR ^= 0x00000001; // PA0を反転
}
フラグをクリアするのを忘れないようにしてください.
今回はODRレジスタを使ってピン状態を変えています.
PA0のビットをxOR演算を使って反転しています.必要であればxORの真理値表を見て動作を確認してください.
レジスタ叩きに限らず,割り込み関数の中で時間のかかる処理はご法度です.
forループを沢山回したり,ADC変換を行ってはいけません.
割り込み関数から出れないのでその間他の処理ができず色々な問題を引き起こします.
まとめ
Arduinoとか触っているとタイマ割込みやPWMを簡単に設定できますが,これらの機能はタイマを1つ以上使っているはずで,色々やると干渉することがあります.
レジスタ叩いていればどのタイマを使っているのか意識せずにはいられません.
タイマは重要なリソースですので,この価格で5つも汎用タイマがあるSTM32C011は是非活用したいところです.