インラインアセンブラで10μ秒のパルスを出力し、自身のInputCaptureに入力してオシロを使わずに時間測定する
はじめに-サンプル作成の動機
超音波センサーを起動させるには10μsecのパルスを入力する必要があった。
インラインアセンブラのbusy loopで10μsecのパルスを作成した。
オシロも持って無いので、PWM計測用に作成したインプットキャプチャにパルスを入力して時間があっているか測定した。
環境(stm32cubeIDEが動かない非力な環境です)
ボード STM32VLdiscovery
OS 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]
10μsecパルス生成関数
インラインアセンブラではループのみ行うのと、拡張インラインアセンブラでループとhightとlow出力を行う二つの処理を記述する。
拡張インラインアセンブラは処理系、環境による方言、挙動差があるので選択できるようにする。
(拡張)インラインアセンブラで10μsecのbusyループ(79/80回)を記述する。
拡張インラインアセンブラ版は、オーバーヘッドはないがサイクル数を予測する部分があり時間を計測する。
インラインアセンブラ版は前後のレジスタ退避/復帰等のオーバーヘッドがあり、実行すると10μsecを越えることになる。
void puls10usec(void) {
#if 1 // インラインアセンブラ
// レジスタ退避/復帰、構造体アクセスの
// オーバヘッドがあり計測してループ数を補正する
// outout 1 at PA12
GPIOA->BSRR = GPIO_Pin_12;
asm volatile (
"mov r1, #0x50\n\r" // 0x50=80
".L80:\n\t"
"subs r1, r1, #1\n\r"
"bne .L80\n\r"
::: "r1", "cc"
);
// output 0 t PA12
GPIOA->BRR = GPIO_Pin_12;
#else // 拡張インラインアセンブラ
// オーバーヘッドはないがサイクル数を
// 予測する命令があり計測して確認する
asm volatile (
"mov r2, %[c]\n\r"
"mov %[a], r2\n\r"
"mov r1, #0x4f\n\r" // 4f=79
".L80:\n\t"
"subs r1, r1, #1\n\r"
"bne .L80\n\r"
"mov %[b], r2\n\r"
:[a] "=r" ((uint32_t)(GPIOA->BSRR)),
[b] "=r" ((uint32_t)(GPIOA->BRR))
:[c] "r" (GPIO_Pin_12)
: "r1", "r2", "cc"
);
#endif
}
その他の処理も実装済みとして、InputCaptureで計測して10μsecパルスに修整する。
InputCapture設定、ピン設定など、パルス出力関数以外のソースは後述する。
立ち下がりエッジでInputCaptureを実行するように設定してる。
割り込みハンドラで取得するCapture値は、InputCapture開始から、パルスの立ち上がりエッジをスルーして、パルスが立ち下がりまでカウントアップされた値になる。
従ってパルス出力とInputCaptureを同時に開始する初回のみ、パルス幅に等しい値が取得できる。
二回目以降は、[次の立ち上がりまで]+[パルス幅]の間をカウントアップした値がCapture値として取得されので目的の値ではない。
- ループ回数の求め方
処理 | 命令種類 | サイクル数 |
---|---|---|
mov r1,#0x50 | データ処理 | 1 |
subs r1, r1,#1 | データ操作 | 1 |
bne .L80 | 分岐 | 1、2 ※a |
mov %[a],r2 | データ処理 | 4(予想) |
mov %[b],r2 | データ処理 | 4(予想) |
※a 分岐は命令に1サイクル、イミディエート値の分岐に、通常は1サイクル(合計2サイクル)です | ||
※b Cortex-M3 テクニカルリファレンス マニュアル 18.2. プロセッサ命令のタイミング より |
1回のループに3サイクル、クロックが24MHzから10μsecは240サイクル。240÷3=80回のループになる。
拡張インラインアセンブラはhightとlowの出力に5サイクル必要の予想で79回にする。
Input Captureで計測
InputCapture割り込み処理。グローバル変数のtim1Ch1Valueに値を設定する。
extern __IO uint16_t tim1Ch1Value;
void TIM1_CC_IRQHandler(void)
{
if (TIM_GetITStatus(TIM1, TIM_IT_CC1) == SET) {
……省略……
tim1Ch1Value = TIM_GetCapture1(TIM1);
インラインアセンブラ版を計測する。
Input Capture入力のPA8とパルス出力のPA12をジャンパー線等で接続する。
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) break TIM1_CC_IRQHandler
……
(gdb) continue
Input Captureハンドラで停止したら、GDBを使い、キャプチャ値設定後のtim1Ch1Valueを確認する。
(gdb) print tim1Ch1Value
$1 = 258
オーバーヘッドが18サイクルあるが、Input Capture開始-パルス出力関数コールが含まれた計測値なので妥当な値とする。
ファームを焼いて拡張インラインアセンブラも計測する。
(gdb) print tim1Ch1Value
$1 = 259
拡張インラインアセンブラも同様に妥当な値とする。
その他の関数
#include "stm32f10x.h"
#include "STM32vldiscovery.h"
__IO uint32_t tickCount=0;
__IO uint16_t tim1Ch1Value = 0;
void RCC_Config(void);
void GPIO_Config(void);
void RCC_Config(void) {
/* 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 GPIO_Config(void) {
GPIO_InitTypeDef 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);
// LED3-PC9, LED4-PC8, USER button-PA0
// InputCaptureがPA8より、近くのPA12にトリガを出力、
GPIO_InitTypeDef GPIO_InitStructure2;
GPIO_InitStructure2.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure2.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure2.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure2);
GPIO_WriteBit(GPIOA,GPIO_Pin_12, Bit_RESET);
/* Initialize Leds LED3/4 and Button mounted on STM32VLDISCOVERY board */
STM32vldiscovery_LEDInit(LED3);
STM32vldiscovery_LEDInit(LED4);
STM32vldiscovery_PBInit(BUTTON_USER, BUTTON_MODE_GPIO);
}
void puls10usec(void) {
……省略-上記参照……
}
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 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);
// TIM1_InternalClockConfig();
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_Cmd(TIM1, DISABLE);
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 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) {
/* System Clocks Configuration */
RCC_Config();
/* GPIO Configuration */
GPIO_Config();
if (SysTick_Config (SystemCoreClock / 100)) { /* Setup SysTick for 10 msec interrupts */
while (1); // capture Error
}
NVIC_SetPriority(SysTick_IRQn, 0xc0);
Timer1Config();
WaitStart();
while(1){
STM32vldiscovery_LEDOn(LED4);
wait(100);
STM32vldiscovery_LEDOff(LED4);
// Input Capture開始
Timer1Enable();
// トリガ出力
puls10usec();
wait(1);
puls10usec(); // InputCaptureハンドラを二回発生させる為
// ハンドラの二回目のbreak直後に、一回目の計測値を
// GDBのprintで出力する。
while(1){
if(IsBreak()){
Timer1Disable();
break;
}
}
}
}
extern __IO uint32_t tickCount;
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);
}
}
void SysTick_Handler(void) {
tickCount++;
}
参考文献
- Cortex-M3 テクニカルリファレンス マニュアル 18.2. プロセッサ命令のタイミング
- ST-Micro Community - Tim1 Input Capture PPM from a RC controller (Posted on November 08, 2009 at 14:38)
- Linux関係メモ@宇治屋電子 arm-inline asm