STM32のInput Captureサンプル-自身のPWM出力を自身に入力して簡易計測

---
## 別PinのPWM出力をInput Captureの入力Pinに接続してPWMの時間を簡易計測する

### はじめに - サンプルを作った理由
長音波センサー(PWM出力)を購入した。Arduinoスケッチでは簡単に制御しており、busyループでhightの時間をカウントしてる？と安易に考えてた。
数センチの計測をするにはμsec単位でPWMの時間計測が必要でbusyループでは無理と判った。
以前に作成したPWMサンプルを改造して、PWM出力を入力PINに接続し、Input Captureでμsec単位の計測をしてみることにした。
※ **1CH** のInputCaptureでは **初回しかパルス幅を測定出来ない** のに気づき、一部修正して再投稿(編集するが表示されなかった。。)

### 環境(stm32cubeIDEが動かない非力な環境です)

- ボード STM32VLdiscovery
- ＯＳ Window8.1 32bitメモリ1G
- Cygwin 2.893
- arm社配布のgcc-arm: arm-none-eabi-gcc.exe (GNU Tools for ARM Embedded Processors) 5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496]

### ST Microの資料がザックリ過ぎ-単純なサンプルなし
ST-MicroのサイトでInput Captureのサンプルを探したら、TIM2とTIM3を連携させて16+16=32bitで計測するサンプルはあったがパルス幅を測定していない。
AN4013-STM32 cross Series Timerを読んでも、Input Captureに関しては、ざっくりレジスタの設定を**半ページ**！書いてるだけ。
ST-Microのコミュティの質問を検索して、解答者の誤記で**動かない**サンプルを参考にした。
そのサンプルの1CHのInputCaptureではパルス幅を計測していないのだが、初回の計測値に限りパルス幅相当なので簡易計測とする。stm32cubeMXのサンプルだと、パルス幅測定は、2CHのInputCaptureの差分で算出していた。

### 計測の説明

```TIM_ICPolarity = TIM_ICPolarity_Falling```
立ち下がりエッジでInputCaptureを実行するように設定してる。
割り込みハンドラで取得するCapture値は、InputCapture開始から、パルスの立ち上がりエッジをスルーして、パルスが立ち下がりまでカウントアップされた値になる。
従ってＰＷＭ出力とInputCaptureを同時に開始する初回のみ、パルス幅に等しい値が取得できる。
二回目以降は、[次の立ち上がりまで]+[パルス幅]の間をカウントアップした値がCapture値として取得される。

### Input Capture設定
ST-Microのコミュニティで検索した**動かない**サンプルを参考に作成。
タイマーやADコンバータなどの周辺機能は、ST-Microのマイコンで共通のレジスタ、機能になっている。使用するマイコンと違う型番のサンプルでも基本的に修整なしで動作する。
define、enum名から何を設定する値か解らないのがある。ヘッダのコメントや資料の説明も少なく、その時は資料のblock diagram図(AN4776 Figure 1)から読み解くらしい……

```c:main.c
void Timer1Config() {
  /* Timer for PPM decode */
  TIM_ICInitTypeDef TIM1_ICInitStructure; 
  //TIM_ICInitStructure.TIM_ICMode = TIM_ICMode_ICAP; 
  TIM1_ICInitStructure.TIM_Channel = TIM_Channel_1; //Pin: PA8 
  TIM1_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; 
  TIM1_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  TIM1_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  TIM1_ICInitStructure.TIM_ICFilter = 0x0; 

  TIM_ICInit(TIM1, &TIM1_ICInitStructure);
  TIM_SelectInputTrigger(TIM1,TIM_TS_TI1FP1);
  TIM_InternalClockConfig(TIM1);
  
  TIM_TimeBaseInitTypeDef TIM1_TimeBaseStructure;
  TIM1_TimeBaseStructure.TIM_Period = 0xFFFF;
  TIM1_TimeBaseStructure.TIM_Prescaler = 0;
  TIM1_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
  TIM1_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
  TIM_TimeBaseInit(TIM1, &TIM1_TimeBaseStructure); 
  /* TIM disable counter */
  TIM_Cmd(TIM1, DISABLE);
  /* Disable the CC1 Interrupt Request */ 
  TIM_ITConfig(TIM1, TIM_IT_CC1, DISABLE); 
  
  /* Enable the TIM1 global Interrupt */
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn; 
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
  NVIC_Init(&NVIC_InitStructure); 
}
void RCC_Config(void) {
  ……
  /* TIM1 clock enable */ 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
  ……
}
```

### Input Captureハンドラ

```c:stm32f10x_it.c
extern __IO uint16_t tim1Ch1Value;

void TIM1_CC_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM1, TIM_IT_CC1) == SET) {
    // cler Capture Interrupt pending bit
    TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);
    // TIM_ClearITPendingBit(TIM1,TIM_IT_UPDATE);
    TIM_ClearFlag(TIM1, TIM_FLAG_CC1);
    GPIO_ResetBits(GPIOA, GPIO_Pin_8);
    // Get InputCapture Value
    tim1Ch1Value = TIM_GetCapture1(TIM1);
  }
}
```

### PWM出力の設定
マイコンの周波数は24M。PWMはPB1ピンに出力される。

```main.c
void Timer3Config() {
// 100μsec= x/24M  x = 100*10^-6 * 24 * 10^6= 2400
// 500μsec = 100μsec * 5 = 12000

    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    /* Time base configuration */
    TIM_TimeBaseStructure.TIM_Period = 65535;
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    /* PWM1 Mode configuration: Channel4 */
    // GPIOC Configuration: TIM3 channel4 - PB1
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWMモード
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // High or Low
    TIM_OC4Init(TIM3, &TIM_OCInitStructure);
    TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM3, ENABLE);
    /* TIM3 disable counter */
    TIM_Cmd(TIM3, DISABLE);
}
void RCC_Config(void) {
  /* TIM3 clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  ……
}
void SetCompareVal(int compare) {
  TIM_TypeDef *tim;
  tim=TIM3;
  if(compare){
    // TIM_SetAutoreload(tim,TIM_PERIOD); //PWMのカウンタ上限を変更する時に使う
    TIM_SetCompare4(tim, compare);
    TIM_Cmd(tim,ENABLE);
  }else{
    TIM_Cmd(tim,DISABLE);
  }
}
```

### Systickハンドラ
10msec毎に発火する。

```c:stm32f10x_it.c
extern __IO uint32_t tickCount;

void SysTick_Handler(void) {
  tickCount++;
}
```

### GPIO設定
```c:main.c
void GPIO_Config(void) {
  GPIO_InitTypeDef GPIO_InitStructure;

  // GPIOC Configuration: TIM3 channel4 - PB1
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_1;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // PP-> Push Pull Pin Mode
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &GPIO_InitStructure);

  /* TIM1 channel 1 Pin(PA8) */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Initialize Leds LED3/4 and Button mounted on STM32VLDISCOVERY board */
  STM32vldiscovery_LEDInit(LED3);
  STM32vldiscovery_LEDInit(LED4);
  STM32vldiscovery_PBInit(BUTTON_USER, BUTTON_MODE_GPIO); 
}
```

### main関数、その他

```c:main.c
#include "stm32f10x.h"
#include "STM32vldiscovery.h"

#define TIM3_100_MICRO 2400
#define TIM3_500_MICRO 12000

__IO uint32_t tickCount=0;
__IO uint16_t tim1Ch1Value = 0;
static int PwmValue =0;

void GPIO_Config(void) { // 省略 上記参照 }
void SetCompareVal(int compare) { // 省略 上記参照 }
void Timer3Config() { // 省略 上記参照 }
void Timer1Config() { // 省略 上記参照 }

void RCC_Config(void) {
  /* TIM3 clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  /* TIM1 clock enable */ 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
  /* GPIOA and GPIOB clock enable */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
                         RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
}

void wait(int cnt) {
  tickCount=0;
  while(tickCount < cnt);
}

void WaitStart() {
  while (!STM32vldiscovery_PBGetState(BUTTON_USER));
  wait(10);
  while (STM32vldiscovery_PBGetState(BUTTON_USER));
  wait(10);
}

int IsBreak() {
  if (STM32vldiscovery_PBGetState(BUTTON_USER)) {
    wait(10);  // チャタリング対策
    while (STM32vldiscovery_PBGetState(BUTTON_USER));
    wait(10);
    return 1;
  }
  return 0;
}

void Timer1Enable(void) {
  TIM_Cmd(TIM1, ENABLE);
  TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE); 
}

void Timer1Disable(void) {
  TIM_Cmd(TIM1, DISABLE);
  TIM_ITConfig(TIM1, TIM_IT_CC1, DISABLE);
}

int main(void) {
  RCC_Config();
  GPIO_Config();
  if (SysTick_Config (SystemCoreClock / 100)) { /* Setup SysTick for 10 msec interrupts */
    while (1); // capture Error 
  }
  NVIC_SetPriority(SysTick_IRQn, 0xc0);
  Timer1Config();
  Timer3Config();
  
  PwmValue = TIM3_500_MICRO;
  WaitStart();
  while(1){
    STM32vldiscovery_LEDOn(LED4);
    wait(150);
    STM32vldiscovery_LEDOff(LED4);
    // PWM設定
    SetCompareVal(PwmValue);
    // Input Capture開始
    Timer1Enable();
    while(1){
      // openocd環境が無い時は、tim1Ch1ValueとPwmValueを
      //比較して誤差範囲内ならLED3を光らせる
      //　ただし、初回割り込みのtim1Ch1Valueのみパルス幅の値
      if(IsBreak()){
        SetCompareVal(0);
        Timer1Disable();
        break;
      }
    }
    if (PwmValue == TIM3_500_MICRO) {
      PwmValue = TIM3_100_MICRO;
    } else {
      PwmValue = TIM3_500_MICRO;
    }
  }
}
```

### 動作確認
配線は、Input Capture入力のPA8とＰＷＭ出力のPB1をジャンパー線等で接続する。
openocdでボードに接続する。設定ファイルはopenocdの配布物に含まれている。

```
$ cd /usr/share/openocd-0.10.0-rc1/scripts
$ openocd.exe -f board/stm32vldiscovery.cfg
```


別端末を開き、引数に、objcopy前のシンボル入りの実行ファイルを設定してgdbを起動する。openocdに接続し実行ファイルをロード(load)する。

```
$ arm-none-eabi-gdb Sample
GNU gdb (GNU Tools for ARM Embedded Processors)
  ……
Reading symbols from Sample...done.
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
  ……
(gdb) monitor reset halt
  ……
(gdb) load
Loading section .isr_vector, size 0x1d0 lma 0x8000000
```

mainとInput Captureハンドラにbreakポイントを設定して実行する。

```
(gdb) hbreak main
Hardware assisted breakpoint ……
(gdb) hbreak TIM1_CC_IRQHandler
  ……
(gdb) continue
```

Input Captureハンドラで停止したら、GDBを使い、**初回の割り込み時**のtim1Ch1ValueとPwmValueの値が一致するのを確認する。

```
(gdb) print tim1Ch1Value
   $1 = 11991
(gdb) print PwmValue
   $2 = 12000
```

ボタンを押下しPWMの周期を変更する。上記と同様に値を確認する。

```
(gdb) print tim1Ch1Value
  $3 = 2390
(gdb) print PwmValue
  $4 = 2400
```

同じCPUの同じクロックから信号出力/計測するので誤差は小さくなると言われそうだが、ソフトが妥当な計測値を出力することを確認するのが目的。
超音波センサーのパルス幅測定では、今回の処理を2CHに変更する。

### 参考文献
- ST-Micro Community - Tim1 Input Capture PPM from a RC controller (Posted on November 08, 2009 at 14:38)
- AN4776 General-purpose timer cookbook
- UM0919 stm32vldiscovery User Manual
- トランジスタ技術2011年3月号 電子オルゴール