Timerを使う

前回CPUのメインクロックを外部にしてPLLを使い168Mhzに設定しましたね。ただ、本当に168Mhzなのか確認してませんでした。
今回はそれを確認する意味も込めてタイマーを使ってみましょう。

タイマーとは

1秒ごとにLEDを点滅させるにはどうしたら良いでしょうか。
もしタイマーがなければ

unsigned int i;
for(i=0; i<0xFFFFF; i++);

みたいな無駄に何もしないプログラムを挟んでLEDをONOFFするしかないですね。
タイマーというのはCPUとは別に動いて、いつもクロックを数えているペリフェラルです。
これを使って1秒分のクロックを数えたらLEDをON・OFFにするといったプログラムを書けば、プログラム自体は他の作業を指定てもその間にタイマーがクロックを数えてくれます。

早速使ってみる

STM32F407にはタイマーが1から14まで14個あって、別々に動作させることが出来ます。
持っている機能もちょっとずつ違うのですが、今回は最も単純なタイマー6,7を使ってみます。
タイマー6は(7も完全に一緒なので6と呼びます)こんな構造になっています。

stm32ftimer
出典:ReferenceManual

ちょっと見にくいので簡略化すると

stmtimer2

こんな感じです。
クロックがまずプリスケーラというタイマ(カウンタ)に入ります。そしてその結果がタイマに入っています。

プリスケーラとは

プリスケーラは何かというとただのタイマです。
何でこうなってるかというと、クロックは168Mhzなど高速です。
もし1秒のタイマーを作ろうと思ったら168M回クロックが来たことを数えないといけません。
そうすると28bitのタイマが必要になります。(2^28で268,435,456なのでこれなら168,000,000は数えられる。2^27だと足りない!)ちょっとビットが多いですね。1秒ならいいけどもっと増やすと、、、
そこで出てきたアイディアが
「入ってくるクロックを遅くすればいい」。
「じゃあタイマの前にタイマを入れてそれが一杯になったら次のタイマに渡すようにしよう」というもので、それがプリスケーラです。
プリスケーラによって入ってくるクロックを1/2にしたり1/4にしたりできます。しかしやっていることはプリスケーラ自体がタイマで、それが一杯になったら次のタイマにパスしているだけです。
試しにプリスケーラが1/4(あたかもクロックが1/4にったかのように動かすという意味)だった時のそれぞれの動きです。

IMG_0790

いついっぱいになるのか

タイマ6にはAutoReloadRegisterというものがあります。
レジスタなんですが、これは何かというと「タイマがこの値になったらタイマを0に戻して割り込み要求をする」
というものです。
だから「100回数えた時に教えてほしいな」と思ったらこのレジスタに100を入れておけばいいのです。

つかってみる。レジスタは?

TIM6の基本的なレジスタはこれだけです。

  • CR1 コントロールレジスタ。 タイマのON・OFFなど設定
  • SR ステータスレジスタ。 オーバーフローが合ったかどうか
  • CNT カウンタ本体(16bit)
  • PSC プリスケーラ(16bit)
  • ARR 自動リロードレジスタ(16bit)

となっています。CNT,PSC,ARRはもう出てきました。
SRはオーバーフロー(CNTとARRが一致するとオーバーフローとなりCNTが0となる。このことをオーバーフロー(あふれ)という)になったことが記録されるレジスタで

tim6_sr

こんな感じになっています。
たった1ビットのUIFだけあり、これがオーバーフローしたかどうかを示しています。
オーバーフローすると1になるのですが、プログラムから0にしないかぎり1のままです。

CR1が肝心ですね。これがコントロールレジスタです。

tim6_cr1

この中でも特にすぐ使いそうなものは

  • OPEM オーバーフローしたら停止するのか続けるのかを決めます。デフォルトは続けます。
  • CEN これを1にするとタイマーが動き始めます。

さて、これで動かすのに必要なレジスタがわかりました。
あとはARRとプリスケーラにどんな値を入れるかです。

値を決める

CPUは168Mhzですが、タイマー6はAPB1につながっています。APB1は168/4Mhz(=42Mhz)でした。(忘れた人はここ参照)
ただし、タイマーだけAPB1より速いクロックが入ってきます。
クロックダイアログラムを見てみると

tim6clock

となっています。
APBx timer clocksの前に変なのがあります。ちょっと文字が切れていますが、「APBxPRESCが1なら1 そうでないならx2」となっています。
どうやらAPB1が遅い時でもタイマだけは早く動けるようにしてくれているようです。
つまり、タイマ6に入ってくるのは168/4*2で84Mhzとなります。

早速書いてみる

以上からプログラムを書いてみます。


#include "stm32f4xx.h"

static void SetSysClock(void);

int main(void)
{
	SetSysClock();

    RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
    GPIOA->MODER = 0;
    GPIOD->MODER = 0x40000000;

    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    TIM6->PSC = 9999;
    TIM6->ARR = 4200 * 2;
    TIM6->CNT = 0;
    TIM6->CR1 |= TIM_CR1_CEN;

    while(1) {
    	if (TIM6->SR) {
    		TIM6->SR = 0;
    		GPIOD->ODR = ~(GPIOD->ODR);
    	}
    };
}

#define PLL_M      8
#define PLL_N      336
#define PLL_P      2
#define PLL_Q      7
#define PLLI2S_N   258
#define PLLI2S_R   3

static void SetSysClock(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

  RCC->CR |= ((uint32_t)RCC_CR_HSEON);

  do {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
    HSEStatus = (uint32_t)0x01;
  } else {
    HSEStatus = (uint32_t)0x00;
  }

  if (HSEStatus == (uint32_t)0x01) {
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;

    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz

    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;	// APB1は 42Mhz

    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24); RCC->CR |= RCC_CR_PLLON;

    while((RCC->CR & RCC_CR_PLLRDY) == 0);

    FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);

  } else {
  	GPIOD->ODR |= 1<<14; } RCC->CFGR &= ~RCC_CFGR_I2SSRC;
  RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28); RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);
  while((RCC->CR & RCC_CR_PLLI2SRDY) == 0);
}

これをコンパイルして書き込むとLEDが1秒ついて1秒消えてまたついて、、、を繰り返すと思います。

コードを見てみましょう。タイマの設定はここですね


    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    TIM6->PSC = 9999;
    TIM6->ARR = 4200 * 2;
    TIM6->CNT = 0;
    TIM6->CR1 |= TIM_CR1_CEN;

まず、クロックを供給しています。タイマ6はAPB1なので、APB1の設定レジスタからONにしています。
つぎにプリスケーラとARRを設定しています。
84Mhzなので1秒を知るには84,000,000回クロックを数えればいいのですが、16bitですと足りません。そこでプリスケーラを使います。
プリスケーラとARRはどんな組み合わせでもいいのですが、とりあえずキリの良くて、ちゃんと16bitに入る
プリスケーラで10000
タイマで8400
という組み合わせにしました。
プリスケーラで10000数えるためにPSCレジスタに9999を入れます(0も1回カウントされるので1つ減らします。ぜ〜〜ろ、いぃぃち、にぃぃい ってことです)。
そしてCNTを0にしてCR1でタイマを有効化しています。


    while(1) {
    	if (TIM6->SR) {
    		TIM6->SR = 0;
    		GPIOD->ODR = ~(GPIOD->ODR);
    	}
    };

ここがメインループですね。
タイマはCPUとは別に動いています。もし、CNTレジスタがどんどん増えていってARRといっしょになってオーバーフローして0にもどったらSRレジスタの1bit目が1になります。
そしたら0に戻してあげてついでにLEDがあるPORTDのODRレジスタのビットを全部反転させます。
こうすることでオーバーフローがあるごとにLEDをON・OFFできます。

これでタイマ6の動きと設定がわかったと思います。
もちろんタイマ7は全く同じですし、他のタイマもほぼ似た感じで動かせます。

タイマ6には他にもレジスタがありますが、他のタイマと連動させて動かす場合の設定レジスタなどです。
一応全部のレジスタを書いておきます

  • CR1 コントロールレジスタ。 タイマのON・OFFなど設定
  • CR2 コントロールレジスタ。 タイマ同士を連結させるための設定
  • DIER 割り込み設定レジスタ。 オーバーフローした時に割り込むかどうか、またDMA要求するかどうか
  • SR ステータスレジスタ。 オーバーフローが合ったかどうか
  • EGR 更新イベントレジスタ。
  • CNT カウンタ本体(16bit)
  • PSC プリスケーラ(16bit)
  • ARR 自動リロードレジスタ(16bit)

次回はwhile文の中でSRレジスタをチェックするんじゃなくて、時間が来たら別の関数を呼び出す「割り込み」を使ってみましょう。

まとめ

  • タイマというのをCPUとは別に動かしてクロックを数えさせることが出来る
  • 長い時間を測るにはプリスケーラを使うと良い

コメントを残す