• そろそろ真面目にイラスト覚えるべきか

    そろそろ真面目にイラストを覚えるときがきたかもしれない。

    作りたいアプリがあるとき、プログラムはかけるサーバーサイドも大丈夫、いわゆるUIUXデザインも行けるし(この言い方嫌いだけどね)チーム開発も慣れてる。

    ただ、絵が描けない。

    イラストレーターは使える。影だの何だの使えるけど、オオカミすら書けない。ペンで書けないんだからパソコンでも書けない。

    配置とかフォントサイズとかそういうのは何とかする手法は勉強したからダメでないものは作れるんだけど、絵が描けない。正確に言うと描けるんだけど、いい絵が描けない。

    ここで思い出すのは「アプリベンチャーやりたいんだよね」と言って「ただ、俺はプログラム出来ないんだよね」って言うの。それを聞くと「いやいや、覚えればいいじゃん」って思うんだけど、それはそっくりそのまま今の僕に当てはまる。

    ただやろうと思ったときに思うのは、絵はセンスが要求される。プログラムは覚えればそれ以上ないし動くものが作れる。動けば100%完成で動かなければ0%完成。

    だけど絵は0%はなくてとりあえず何かはかけるからどんなダメなやつでも1%ぐらいにはなる。しかも完成品形がないから100%じゃなくて150%とかが起きるし評価が人による。プログラムなら誰が見てもボタンが押せるかどうかなんてのは明らかだから誰であっても完成かどうかは評価できるのに。

    だからどんなに勉強しても上手くならないかもしれない。そもそも勉強法があるのかも怪しい。

    そうやって言い訳してきたけど、ただ、プログラムに本当にセンスがいらないのかはちょっと怪しい。

    仮に動きが完璧だったとしてその箱を開けた中身がどうなっているかはセンスじゃなかろうか。1つ思うのは完璧な仕様書は書けないはず。例えば「今日押したボタンが明日の朝も同じ位置にあること」とは仕様書には書いてない。だからテストしないけど違うかもしれないわけだよね。こういうのは例えばボタンの上に日付表示があって10/1はOKだけど10/11だとボタンがずれるってのがあり得る。ずれて欲しくないなら仕様書に書かないといけないけど何パターン必要だろうか。ネットワークがないとき、押した場所の経度がちょうど0だったとき、予期しない性別データがあったとき。書ききれるはずもない。書ききれるというなら例えばそのタイミングで電源に2000vのサージが来てデータバスのの1つだけが破損してビットが1回だけ反転した場合の仕様は?

    完璧な仕様が不可能なら仕様書にない領域の動きはさきい言った内部によって決まるわけだけど、それはセンスなんじゃないだろうか。

    つまり全ての想定が不可能な限り対策を勉強するなんてのは不可能なんだからそれはセンスと呼べる気がする。そしてそれならセンスは磨けられるものな気がする。
    この”センス”は絵の”センス”とは同じ属性なんじゃないかな。絵が上手い人がプログラムが上手いかどうかじゃなくて、勉強不可能なものみたいな意味で。

    そう思うと絵をやってみないわけにはいかなくなってきたので、イラストの勉強を始めることにする。

  • Timerで割り込みを使う

    前回割り込みを使わずにタイマーをつかいました。今回は割り込みを使ってみましょう。

    割り込みって何

    マイコンが他の何かに繋がっている時、例えばロボットを作っているとしてそれを制御するマイコンにロボットの触覚センサーが繋がっている時、触覚に何かがぶつかってからすぐに反応しないと困る時があります。ぶつかっちゃうとか落ちちゃうとか。
    そうならないようにするには頻繁に触覚センサーをチェックしないといけません。
    たとえばwhile(1){}の無限ループの中で
    while(1){
    if(触覚になにかあたった){
    避ける
    }
    }

    と言ったプログラムを入れてとにかくチェックすることになりますが、当然ロボットは他の処理もするので
    while(1){
    if(触覚になにかあたった){
    避ける
    }
    他の処理
    }
    ということになります。
    そうするとこの「他の処理中」、触覚になにかあたっても気づくことが出来ません。その間はif文が実行されないからです。超鈍感。

    それを解決するのが割り込みという仕組みです。
    割り込みというのは何かをきっかけに起きる緊急事態で、今やっているプログラムをいったん止めておいて、その時だけ別のプログラムを実行することが出来ます。そしてそれが終わったら元のプログラムに戻ります。書き方としてはこんな感じ。
    while(1){
    他の処理
    }
    void 触覚になにかあたったら(void)
    {
    避ける
    }
    必要なときだけ関数が呼ばれるので、これなら普通の時は無駄に触覚のチェックを毎回しなくていいし、しかも触覚に触った瞬間にするべき行動をすぐに取ることが出来ます。
    whileの中のプログラムは割り込みのことは全く気にしなくても大丈夫です。途中で中断されても計算がおかしくなることはありません。(ただ、中断されるのでタイミングがズレることはあります)
    これをマイコンがどうやってやっているかは今回は置いておいて、前回やったタイマーでオーバーフローがあるごとに割り込みでLEDをON・OFFさせるってのをやってみましょう。

    NVICとは

    STMマイコンの割り込みはNVICという仕組みが管理しています。
    実はこれARM(あーむ)といわれるマイコン全てで共通の仕組みなのでSTMだけじゃなく他のARMマイコンでも同じです。
    割り込みは同時に2つ発生したり(タイマ6とタイマ2が同じタイミングでオーバーフローしたり)割り込みの作業中に別の割り込みが発生することがあります。そういう時どうするかを管理するのがNVICです。
    難しいことは今回は置いておきますが、NVICが全ての割り込みを管理していて、タイマ6の割り込みを許可するかどうかもNVICが管理しています。それだけ覚えれば大丈夫です。
    割り込みのきっかけはGPIOでボタンが押されたら、とかA/Dの結果がある値を超えたらとか、いろいろきっかけがありますが、NVICは立ち上がり直後は全て割り込み禁止になっています。使いたいのがあれば個別にONにする必要があるのです。
    NVICのISERレジスタの対応するビットを1にすれば割り込みが許可されます。

    呼ばれる関数

    では、許可できたとして、割りこまれた時にどの関数を呼ぶかというのはどうやって設定するのでしょうか。
    実はそれスタートアップスクリプトに書いてありました。
    startup_stm32f407xx.Sをもう一度見てみましょう。

    
    g_pfnVectors:
      .word  _eram
      .word  Reset_Handler
      .word  NMI_Handler
      .word  HardFault_Handler
      .word  MemManage_Handler
      .word  BusFault_Handler
      .word  UsageFault_Handler
      .word  0
      .word  0
      .word  0
      .word  0
      .word  SVC_Handler
      .word  DebugMon_Handler
      .word  0
      .word  PendSV_Handler
      .word  SysTick_Handler
      
      /* External Interrupts */
      .word     WWDG_IRQHandler                   /* Window WatchDog              */                                        
      .word     PVD_IRQHandler                    /* PVD through EXTI Line detection */                        
      .word     TAMP_STAMP_IRQHandler             /* Tamper and TimeStamps through the EXTI line */            
      .word     RTC_WKUP_IRQHandler               /* RTC Wakeup through the EXTI line */                      
      .word     FLASH_IRQHandler                  /* FLASH                        */                                          
      .word     RCC_IRQHandler                    /* RCC                          */                                            
      .word     EXTI0_IRQHandler                  /* EXTI Line0                   */                        
      .word     EXTI1_IRQHandler                  /* EXTI Line1                   */                          
      .word     EXTI2_IRQHandler                  /* EXTI Line2                   */                          
      .word     EXTI3_IRQHandler                  /* EXTI Line3                   */             
    

    こんなのがあったと思います。
    これが割り込みベクタ(の一部)です。
    NVICは割り込まれた時にその割り込みに応じて決められたアドレス時に自動的にジャンプします。
    このアセンブリでは割り込みごとにどのアドレスに飛ぶかが書いてあります。
    タイマ6がどれかというとこれです。

      .word     TIM6_DAC_IRQHandler               /* TIM6 and DAC1&2 underrun errors */

    これはタイマ6のオーバーフローが起きた時に飛ぶ先としてTIM6_DAC_IRQHandlerへ飛べという設定です。
    (このプログラムは”この場所に”書く必要があります。「タイマ6の割り込みジャンプ先は0x64番地に書いてある」というふうに設定してあるからです。だから割り込みベクタ”テーブル”という名前がついています)

    じゃあそのとび先であるTIM6_DAC_IRQHandlerはどこなんだと思ってこのプログラムの下の方を見ると

    
       .weak      TIM6_DAC_IRQHandler                  
       .thumb_set TIM6_DAC_IRQHandler,Default_Handler
    

    こういうのがあります。
    このweakというのは
    「実際にTIM6_DAC_IRQHandlerというのがあれば TIM6_DAC_IRQHandlerはそれにする。だけどもしなければDefault_Handlerにする」という意味です。
    これはつまり void TIM6_DAC_IRQHandler(void)っていう関数がどこかにあればそれを設定するけど、もし無いならDefault_Handlerにするってことです。
    ちなみにDefault_Handlerはこのアセンブリの上の方にあります。見てみて下さい。ただの無限ループです。

    これで設定の仕方がわかりました。
    void TIM6_DAC_IRQHandler(void)
    って言う名前の関数を用意すればそれでいいのです。

    やってみる

    それではやってみましょう。

    
    #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 = 4800 * 2;
        TIM6->CNT = 0;
        TIM6->CR1 |= TIM_CR1_CEN;
    
        TIM6->DIER = TIM_DIER_UIE;
        NVIC_EnableIRQ(TIM6_DAC_IRQn);
    
        while(1);
    }
    
    void TIM6_DAC_IRQHandler(void)
    {
    	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);
    }
    
    

    前回と違うのはここですね

        TIM6->DIER = TIM_DIER_UIE;
        NVIC_EnableIRQ(TIM6_DAC_IRQn);
    
        while(1);

    まずタイマ6のDIERレジスタのUIEビットを1にしています。
    これは”タイマー6ペリフェラルにおける割り込みをするかどうか”の設定になります。
    これが1になっているとオーバーフローが起きた時に割り込み要求がNVICの方へ行きます。(もっと正確に言うとSRレジスタのUIFビットがNVICに割り込みを要求するようになります)
    ただ、それだけだとNVICは受け付けてくれません。こんどは”NVICにおけるタイマー6の割り込みを許可するかどうか”の設定が必要になります。
    NVICのISERレジスタをいじればよいのですが、便利な関数が用意されているのでそれを利用します。
    この2つによって割り込みが使えるようになります。
    割り込み時の流れを確認すると

    1. タイマ6カウンタがARRと同じ値になりその後オーバーフローして0になる
    2. SRレジスタのUIFが1になる
    3. DIERレジスタのUIEが1なのでNVICに対して割り込み要求が行く
    4. NVICではタイマ6の割り込みが許可されているので、今のプログラムの実行をいったん停止してvoid TIM6_DAC_IRQHandler(void)関数を呼び出し実行する
    5. void TIM6_DAC_IRQHandler(void)関数が終わったら何事もなかったかのようにさっき停止した場所に戻リ続ける

    となります。
    その割り込み関数の中では

    void TIM6_DAC_IRQHandler(void)
    {
    	TIM6->SR = 0;
    	GPIOD->ODR = ~(GPIOD->ODR);
    }
    

    といった感じでSRレジスタの0セットとLEDの反転をしています。
    SRレジスタのUIFを0にするのは重要です。
    割り込み要求はSRレジスタが1だからこそ起きるので、これを0にしないとこの関数が終わった時に「お、SRレジスタが1だ。じゃあ割り込もう」ということでもう一度この関数に戻ってしまいます。なので、すぐさま0に戻します。

    どうでしょうか。意外とあっさり割り込みに出来たと思います。
    無限ループを見てみて下さい。while(1);となっていて、何もしていません。
    必要なときに(タイマ割り込みで)LEDのON・OFFが切り替わる関数が呼ばれるので非常に便利ですね。

    まとめ

    • 割り込みというプログラムを停止して特別に今だけ関数を呼び出す仕組みがある
    • 割り込みはNVICで管理されている
    • 割り込みするにはペリフェラルの割り込み許可とNVICの割り込み許可が必要
    • 割り込みで呼ばれる関数の名前は決まっている(スタートアップスクリプトによって)
    • 割りこまれたら割り込みの元になるフラグは0にすぐ戻す
  • 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とは別に動かしてクロックを数えさせることが出来る
    • 長い時間を測るにはプリスケーラを使うと良い
  • デートとAppleWatch

    Apple Watchの話題は最近は聞かなくなったしつけている人もほとんど見なくなったね。僕は最近仕事で移動するときはしてるんだけど一時期完全に引き出しにしまってた。使い道はあまりないんだけど、ただ通知を受け取るのが便利で届くと腕が震えるからすぐわかる。だから知らないうちにLineのメッセージが10分前に来てたなんてことはなくなる。違う作業をしてても腕が震えれば「なんだろ」と気になるし、時計を見るようにWatchを見るだけで送られてきたお知らせにすぐ気付くことが出来る。

    時間が分かるので仕事の移動中はもちろんうちの奥さんとデートに出かける時もしてたんだけどあるとき普通に楽しくデートしてる時に奥さんに「時間が気になるの?」と言われてびっくりした。どうも1つ1つ通知があるごとにWatchに表示される通知が重要なものか確認してたみたいでチラチラ時計を見てたらしい。向こうからしてみれば僕が時計を見るのは通知がきたからなのか時間の確認なのか分からないもんね。僕はそのとき赤いスイートピーの歌詞が頭に浮かんだ。(♪なぜあなたが時計をチラッと見るたび泣きそうな気分になるの)。というか仮に全部通知だったのが知れてたとしてもダメだよね。デートに集中してないんだから。じゃあ通知を無視すればいい?腕が震えるのに?それは無理だ。設定でオフにする?それじゃあただの腕時計だし、欲しくなったときに設定変えるなんて面倒すぎる。ということでその日から最近存在を思い出すまでは全くしなくなってしまった。

  • 動作周波数を168Mhzに変更する

    さて、前回実はクロックが内部クロックになっていて、しかもどうやら168Mhzではなさそうということがわかりました。
    今回は168Mhzで動かすための設定です。

    クロック図

    何回か前に紹介したこのマイコンのクロックですがもう一度見てみましょう

    stm32f4_clock0
    出典
    : ReferenceManual

    これを見ても何が何やらですが、スタートアップスクリプトからどういう設定になっているのかというと。

    stm32f4_clock1
    出典
    : ReferenceManual

    こうなっています。
    基本的に左が源で右がどこに使われているかなのですが、
    この「HCL to AHB bus,core,memory and DMA」というのがマイコンのベースとなるクロックです。1+1がどのぐらいの速度で計算されるかはここで決まるわけです。
    そしてスタートアップスクリプトの設定を見るに、ここのクリックが何から来ているかというと16MhzのHSIRCというマイコン内蔵の16Mhzのクロックになっています。
    もちろん使えるクロックですが、精度が微妙で正確な何かを作るときにはちょっと使えません。
    では、せっかくつながっている制度の良い外部の8Mhzをつかって168Mhzで動かしてみましょう。

    じゃあPLLを使わなければ

    168Mhzで動かすのに一番簡単なのは168Mhzのクリスタルを買ってきてマイコンにつなぐことです。ただ、168Mhzというクリスタルクロックはありません(あっても入手が難しいし、つなぐのも大変)
    そこでこのマイコンに内蔵されているPLLという周波数を増やす回路を使います。つまりプログラム絵設定しなきゃいけない手順としてはこんな感じになるんです

    1. 外部クロックにクロックを切り替える
    2. PLLをONにする
    3. PLLで正しい周波数を設定する
    4. その他プリスケーラなどを設定する

    さて、正解を先に見てみましょう。

    [c]

    #include "stm32f4xx.h"

    static void SetSysClock(void);

    int main(void)
    {
    SetSysClock();
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

    GPIOA->MODER = 0;
    GPIOD->MODER = 0x40000000;
    while(1) {
    GPIOD->ODR = GPIOA->IDR << 15;
    };
    }

    /************************* PLL Parameters *************************************/
    /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
    #define PLL_M 8
    #define PLL_N 336

    /* SYSCLK = PLL_VCO / PLL_P */
    #define PLL_P 2

    /* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */
    #define PLL_Q 7

    /* PLLI2S_VCO = (HSE_VALUE Or HSI_VALUE / PLL_M) * PLLI2S_N
    I2SCLK = PLLI2S_VCO / PLLI2S_R */
    #define PLLI2S_N 258
    #define PLLI2S_R 3

    static void SetSysClock(void)
    {
    /******************************************************************************/
    /* PLL (clocked by HSE) used as System clock source */
    /******************************************************************************/
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    /* Enable HSE */
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    /* Wait till HSE is ready and if Time out is reached exit */
    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)
    {
    /* Select regulator voltage output Scale 1 mode, System frequency up to 168 MHz */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;

    /* HCLK = SYSCLK / 1*/
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

    /* PCLK2 = HCLK / 2*/
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz

    /* PCLK1 = HCLK / 4*/
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; // APB1は 42Mhz

    /* Configure the main PLL */
    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
    (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

    /* Enable the main PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till the main PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }

    /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
    FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

    /* Select the main PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    /* Wait till the main PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
    {
    }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock
    configuration. User can add here some code to deal with this error */
    GPIOD->ODR |= 1<<14;
    }

    /******************************************************************************/
    /* I2S clock configuration */
    /******************************************************************************/
    /* PLLI2S clock used as I2S clock source */
    RCC->CFGR &= ~RCC_CFGR_I2SSRC;

    /* Configure PLLI2S */
    RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);

    /* Enable PLLI2S */
    RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);

    /* Wait till PLLI2S is ready */
    while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)
    {
    }
    }

    [/c]

    SetSysClock(void)というのはSTMが他の場所で公開していた168Mhzにするための関数です。僕らのプロジェクトには入ってないのですが、他の場所から引っ張ってきました。
    何で先に正解を用意したかというと、さっきのPLLをONにして〜という手順には今はまだ知らなくていいであろういろいろなレジスタが関係してくるのでとりあえず今は168Mhzに出来るようにしておいて、説明を軽くしようと思いました。
    この関数を呼べば168Mhzに切り替わるのでmain関数の最初に書いておけばOKです。このまま便利に使いましょう。

    どういう流れなのか

    とはいえ、ちょっと中身を知っておきましょう。
    今回のこのプログラムで設定されている流れはこうなっています。

    stm32f4_clock2
    出典
    : ReferenceManual

    8Mhzの入力がHCLKに行くまでにどうなっていっているかというと

    1. 最初のプリスケーラ(分周器)でM分の1にして1Mhzを作る
    2. 1MhzをN倍して366MHzを作る
    3. 分周器でN分の1にして168Mhzを作る
    4. AHBプリスケーラをそのまま1倍で通過してHCLKとして使う。

    という感じです。図で表すと

    IMG_0673

    何故こんなややこしいことになっているかというと、いくつか制限があります。
    まず回路的な制限として

    1. VCOの入力は1~2Mhz
    2. VCOの出力は100~432Mhz(100以下も可能ではあるが注意が必要)
    3. Pの出力は180Mhzが上限

    そして、それぞれ M N Pという値がありますが、それらも設定できる値が決まっていて

    1. M = 2〜63
    2. N = 2〜432
    3. P = 2,4,6,8

    この条件で考えると、8Mhzの入力に対して M=8, N=366,P=2の設定で168Mhzを作るのが良さそうとなるわけです。
    ちなみに168Mhzでなく180Mhzでも動くわけですが、168Mhzにしているのはいつか使うであろうUSBの48Mhzを作るためにVCOを336Mhzにしています。(USBのクロックはVCOの出力を別のQという分周器で作っています。Qの値を7にすると48Mhzになります。これはUSBの通信速度です)

    深いことは考えない

    これでPLLでどうやって168Mhzを作っているのかわかりました。
    今回はここまででこの関数やレジスタは深追いはしません、流れだけ書いておきますね。

    1. HSE(外部クロック)を有効にする
    2. HSEが正常に立ち上がって、かつある程度クロックを出すまで待つ(クリスタルの起動後は不アンテナ周波数なため待つのが普通)
    3. VCOの準備
    4. APB2はHCLKの1/2となるようにする
    5. APB1はHCLKの1/4となるようにする
    6. PLLに各値の設定
    7. PLLをONにする
    8. PLLが安定になるまで待機
    9. クロック源をHSIからPLLに切り替える
    10. 切替後正常になるまで待機
    11. I2Cに関する別のPLLの設定

    ただ、1つあなたが気にしなきゃいけないのはAPB1とAPB2のクロックです。
    これらはAHBのクロック(168Mhz)を分周したものになっています。
    前回まで使ってきたGPIOはAPB2のクロックを使っていましたね。
    APB2は1/4分周されているので168/4=42Mhzとなります。
    つまりCPUの実行速度とは違ってだいぶ遅いわけです。
    なんでかというと実はAPB1とAPB2は上限速度があって、それもだいぶ遅くて

    1. APB2は90Mhzが上限
    2. APB1は45Mhzが上限

    となっているからです。

    このプログラムを動かすと前回と何の違いもなくLEDがボタンに合わせてON,OFFします。
    速くなったかどうかわからないですね。。。
    それを確かめるためにも次回はタイマーを使って時間が経ったら何かをするというプログラムを書いてみましょう。
    これで本当に168Mhzになっているかを確認できますね。