Nucleo 8S208RB タイマーを使う

# TIM4で `ticks_ms()` のかわり

経過時間を表す `ticks_ms()` のかわりを作ってみます。

割り込みハンドラ
```c:stm8s_it.c（抜粋）
INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
{
  extern uint32_t ticks_ms;

  TIM4_ClearFlag(TIM4_FLAG_UPDATE);
  ticks_ms++;
  return;
}
```
使用例
```c:main.c（抜粋）
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz 動作

  uint32_t ticks_ms = 0;

  TIM4_DeInit();
  TIM4_TimeBaseInit(
    TIM4_PRESCALER_128, // プリスケーラー div 128
    124 // 再ロード値
  ); 
  TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
  TIM4_Cmd(ENABLE);

  enableInterrupts();

  while (1)
  {
    if (! ((GPIOE->IDR) & 1<<4)) {
      printf("Ticks_ms = %lu\n", ticks_ms);
    }
  }

```

Nucleoではボタンを押すたびにリセットからの経過時間がmsec単位で表示されます。

## 補足

RM0016によれば，基本タイマー TIM4 の プリスケーラー 1,2,4,8,16,32,64,128 から選べ，
カウンターリロード値は8ビットという，STM32に慣れているとかなり割り切った，前向きに書けばシンプルでわかりやすい仕様ですね。

TIM4周波数 = f[master] / PSCR / (ARR + 1)

動作周波数は16MHzで譲れないとして，PSCR = 128, ARR=124で

16MHz / 128 / ( 124 + 1 ) = 1kHz

のタイマーとしてみました。

# TIM2 で PWM

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/39061c8f-070c-2222-e611-9bdbcc8648fb.png)

```c:main.c（抜粋）

  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz / 1 = 16MHz

  TIM2_DeInit();
  TIM2_TimeBaseInit( // 16MHz / 1 / (15999+1) = 1kHz
    TIM2_PRESCALER_1, 
    15999
    );

  TIM2_OC2Init(  // for TIM2_CH2 PD3
    TIM2_OCMODE_PWM1, 
    TIM2_OUTPUTSTATE_ENABLE, 
    4800, // 4800 / (15999+1) = 30%
    TIM2_OCPOLARITY_HIGH
    );
  TIM2_Cmd(ENABLE);
  TIM2->CR1 |= TIM2_CR1_CEN;

```

ほぼこちらの抜粋です。

http://stm8sdatasheet.web.fc2.com/STVD-xprod05-OSC-COUNTER/STM8S-OSC-COUNTER.html

- 動作周波数は16MHz
- 1kHz, ON DUTY = 30% の PWM です。コメントのとおり，プリスケーラーは 1 再ロード値は 15999 比較値は 4800 です。
 
16MHz / 1 / (15999+1) = 1kHz
4800/(15999+1) = 30%

- TIM2_CH2に出力するようにします。PD3 に出てきます。

## 補足

最低限ですと，これだけでいいようです。（プリスケーラー 16 リロード値 999）

```c:main.c（抜粋）
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz / 1 = 16MHz

  TIM2->PSCR = TIM2_PRESCALER_16;

  const uint16_t tim2_auto_reload = 999;
  TIM2->ARRH = (tim2_auto_reload >> 8);
  TIM2->ARRL = (tim2_auto_reload & 0xFF);

  const uint16_t tim2_compare_reg1 = 300;
  TIM2->CCR2H = (tim2_compare_reg1 >> 8);
  TIM2->CCR2L = (tim2_compare_reg1 & 0xFF);

  TIM2->CCER1 = TIM2_CCER1_CC2E; // Enable compare channel 2 output
  TIM2->CCMR2 = TIM2_OCMODE_PWM1;

  TIM2->EGR |= TIM2_EGR_UG; // Generate an update event to register new settings
  TIM2->CR1 = TIM2_CR1_CEN; // Enable the counter
```

以下を参考にしました。

-- https://gist.github.com/stecman/f748abea0332be1e41640fd25b5ca861


# TIM2 で One Pulse Mode

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/97598a9e-8d60-bf46-ecde-be5fdf95fbbc.png)

ロジアナで見やすいように，タイマー開始時にPC5(=Nucleo LED)をH タイマー終了時に L にしています。

```c:main.c（抜粋）
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz 動作

  GPIO_Init(GPIOC, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_FAST);

  GPIO_WriteLow(GPIOC, GPIO_PIN_5);

  TIM2_DeInit();
  TIM2_SelectOnePulseMode(TIM2_OPMODE_SINGLE);
  TIM2_TimeBaseInit( // 16MHz / 1 / (15999+1) = 1kHz
    TIM2_PRESCALER_1, 
    15999
    );

/*
  MODE POLARITY result
    1    HIGH    (start) HHH LLLLLLL (end) HHHH...
    1    LOW     (start) LLL HHHHHHH (end) LLLL...
    1    HIGH    (start) LLL HHHHHHH (end) LLLL...
    2    LOW     (start) HHH LLLLLLL (end) HHHH...
*/

  TIM2_OC2Init(  // for TIM2_CH2 PD3
    TIM2_OCMODE_PWM1, 
    TIM2_OUTPUTSTATE_ENABLE, 
    4800, // 4800 / (15999+1) = 30%
    TIM2_OCPOLARITY_HIGH
    );

  TIM2_Cmd(ENABLE);
  TIM2->CR1 |= TIM2_CR1_CEN;
  GPIO_WriteHigh(GPIOC, GPIO_PIN_5); // for debug

  while (TIM2->CR1 & TIM2_CR1_CEN) {} // タイマー終了を待つ

  GPIO_WriteLow(GPIOC, GPIO_PIN_5); // for debug
```

PWMに `TIM2_SelectOnePulseMode(TIM2_OPMODE_SINGLE);` が加わっただけですね。終了したかどうかはここでは`CEN`レジスタを見ています。

PWMのモード，Polarity設定で信号極性が変わります。コメントに書いてあるとおりでした。

## 再スタートするには

もう一度 `TIM1->CR1 |= TIM1_CR1_CEN;` すればいいようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/568d7b09-f218-9dde-5a3b-c3c4521bf5a6.png)

## 指定時間だけ H にしてタイマーが終了したら L になっていてほしい

1msecだけONにすることを考えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/01c6750a-7d8d-7da1-8102-fe1cda4ed0af.png)

```c:main.c（抜粋）
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz 動作

  TIM2_DeInit();
  TIM2_SelectOnePulseMode(TIM2_OPMODE_SINGLE);
  TIM2_TimeBaseInit(
    TIM2_PRESCALER_1, 
    16000 // 再ロード値
    );

  TIM2_OC2Init(  // for TIM2_CH2 PD3
    TIM2_OCMODE_PWM2, 
    TIM2_OUTPUTSTATE_ENABLE, 
    1, // ON duration 比較値
    TIM2_OCPOLARITY_HIGH
    );

  TIM2_Cmd(ENABLE);
  TIM2->CR1 |= TIM2_CR1_CEN;
```

OnePulseモードを構成すると，
- タイマースタート
- ON状態
- OFF状態
- タイマー終了
- ON状態が続く

となります。なので，ON状態がL OFF状態がHとなるようにして，ON状態を極力短くなるようにすればいいということになります。

比較値は0にすることはできないので，1が最小値となります。正しくは `1 / (F_MASTER / プリスケーラー)` の時間だけ L となりますが，プリスケーラーを小さくすれば実質問題ないでしょう。

上記設定値の例
- 動作周波数 16MHz
- プリスケーラー値 1
- TIM2のカウンター周波数は 16MHz/1 = 16MHz
- 再ロード値 16000  -->> H区間は 16000/16MHz = 1msec
- 比較値 1 -->> 事前のL区間は 1/16MHz = 62.5nsec

## TIM1で One Shot Timer を使う

参考記事

http://stm8sdatasheet.web.fc2.com/STVD-project06-TIMER-PWM/STM8S-TIMER-PWM.html

- 反転してCH1Nにも出す相補出力もできるとのことですが，TIM1_CH1Nをポートに割り当てるためにはオプションバイトの変更が必要です。

# 250kbpsでポートを読んでみる

タイマーを使って定期的にGPIOを読むことを考えます。
25kHz DUTY=30% の矩形波を読むことをPWMでつくり，別のタイマーで250kbpsでサンプリングします。

```実行例
 *  実行するタスク: platformio device monitor 

--- Terminal on /dev/cu.usbmodem14103 | 9600 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
TIM2 PWM started
TIM4 ready for sample
TIM4 finished sample
00001110000000111000000011100000001110000000111000000011100000001110000000111000000011100000001110000000111000000011100000001110
```

今までの組み合わせです。基本タイマー TIM4 で 250kHz で割り込みを作り，グローバルな配列に代入します。

```c:stm8s_it.c（抜粋）
uint8_t buffer_count;
uint8_t buffer[128];

INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
{

  //GPIO_WriteReverse(GPIOC, GPIO_PIN_5);
  if (buffer_count < 128) {
    buffer[buffer_count++]=GPIOD->IDR;
  }
  
  TIM4_ClearFlag(TIM4_FLAG_UPDATE);

  return;
}
```
バッファーは 1ビット * サンプリング数 ですが，スピード優先で GPIOD の8ビット分まるごと読むようにしてみました。
buffer_countは128未満であればサンプリングし，128であれば何もせずに抜けるようにしました。
グローバル変数はヘッダーで宣言します。

```c:stm8s_it.h（抜粋）
extern uint8_t buffer_count;
extern uint8_t buffer[128];
```

複数のグローバル変数は構造体で持つのが主流のようですが，とりあえず簡単に書きました。

TIM4を作成，サンプリングを開始して，停止したことを判定して書き出します。PD3だけ知りたいので `buffer[]` の3ビット目だけを書いています。

```c:main.c（抜粋）
  buffer_count=128; // 念のためサンプリングを停止

  TIM4_DeInit();
  TIM4_TimeBaseInit( // 16MHz / 16 / (3+1) = 250kHz
    TIM4_PRESCALER_16,
    3
  ); 
  TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
  TIM4_Cmd(ENABLE);

  enableInterrupts();

  printf("TIM4 ready for sample\n");

  buffer_count=0; // start sampling

  while( buffer_count<128 ) {
    GPIO_WriteReverse(GPIOC,GPIO_PIN_5);
  } // wait end of sampling

  printf("TIM4 finished sample\n");

  for (int i=0; i<128; i++) {
    printf("%d",(buffer[i]>>3) & 1);
    //printf("%2x\n",buffer[i]);
  }
  printf("\n");
```

サンプリング終了待ちのwhileループ，最適化の影響でしょうか，何もないとなぜか`buffer_count`が更新されません。苦し紛れにLチカさせました。

PWMは上にある通りTIM2_CH2に構成しました。

```c:main.c（抜粋）
  TIM2_DeInit();
  TIM2_TimeBaseInit( // 16MHz / 16 / (39+1) = 25kHz
    TIM2_PRESCALER_16, 
    39
    );

  TIM2_OC2Init(  // for TIM2_CH2 PD3
    TIM2_OCMODE_PWM1, 
    TIM2_OUTPUTSTATE_ENABLE, 
    12, // 5 / (19+1) = 25%
    TIM2_OCPOLARITY_HIGH
    );
  TIM2_Cmd(ENABLE);
  TIM2->CR1 |= TIM2_CR1_CEN; // start TIM2 PWM
```

割り込み処理中は軽くしないとだめですね。今回の例では 500kbps では動きませんでした。