TIM4で ticks_ms()
のかわり
経過時間を表す ticks_ms()
のかわりを作ってみます。
割り込みハンドラ
INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
{
extern uint32_t ticks_ms;
TIM4_ClearFlag(TIM4_FLAG_UPDATE);
ticks_ms++;
return;
}
使用例
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
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;
ほぼこちらの抜粋です。
- 動作周波数は16MHz
- 1kHz, ON DUTY = 30% の PWM です。コメントのとおり,プリスケーラーは 1 再ロード値は 15999 比較値は 4800 です。
16MHz / 1 / (15999+1) = 1kHz
4800/(15999+1) = 30%
- TIM2_CH2に出力するようにします。PD3 に出てきます。
補足
最低限ですと,これだけでいいようです。(プリスケーラー 16 リロード値 999)
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
ロジアナで見やすいように,タイマー開始時にPC5(=Nucleo LED)をH タイマー終了時に L にしています。
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;
すればいいようです。
指定時間だけ H にしてタイマーが終了したら L になっていてほしい
1msecだけONにすることを考えます。
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 を使う
参考記事
- 反転して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 で割り込みを作り,グローバルな配列に代入します。
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であれば何もせずに抜けるようにしました。
グローバル変数はヘッダーで宣言します。
extern uint8_t buffer_count;
extern uint8_t buffer[128];
複数のグローバル変数は構造体で持つのが主流のようですが,とりあえず簡単に書きました。
TIM4を作成,サンプリングを開始して,停止したことを判定して書き出します。PD3だけ知りたいので buffer[]
の3ビット目だけを書いています。
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に構成しました。
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 では動きませんでした。