備忘録
概要
高精度タイマ計測であるQueryPerformanceCounterと、Windows APIであるtimeSetEvent/timeKillEventを使って、よくマイコンとかで使われるSystick Timerを疑似的に生成する。
なお、DIOボードを組み合わせて、DIOパルスが生成出来るかどうかも後日実験の予定。
参考記事
若干の工夫点
- クラステンプレートを使用することで、コードレベルでSystick構成を複数用意することが出来るように。
- Attach, Detachをつけて、タイマを動かしてほしい一連のクラスをセットで登録できるように。
注意
- 今回のような処理時間計測において、printfやstd::coutを使うのはよくない。。今回はコマンドプロンプトで試しているため。理由は、printf、std::coutを使うと、標準出力をするという行為に対して、並列化のときにオーバーヘッドが生じてしまう。
- 一番いいのは、割り込みが起きる関数AsNotifiedで現在の時間を蓄えて計測。
- 機器と接続する場合。デバイスドライバで、デバイスレジスタが使える場合は、その変数のアドレスを直接指定して、volatile宣言。使えない場合でAPIが定義されている場合は、なるべく高速に信号が読める関数を使って読み取りすること。
ソースコード
Systick.h
# pragma once
# pragma comment(lib, "winmm")
# include <windows.h>
# include <mmsystem.h>
# include <vector>
class ICallback {
public:
ICallback() { }
virtual ~ICallback() { }
public:
virtual void AsNotified(double elapsed) = 0;
};
template <typename A>
class Systick {
private:
static unsigned int _uTimerID;
static std::vector<ICallback*> _callback;
static LONGLONG _last_time;
static uint32_t _delay;
public:
static void Activate() {
if (_uTimerID == 0) {
_last_time = 0;
_uTimerID = timeSetEvent(_delay, 0, (LPTIMECALLBACK)Systick::TimerCallback, (DWORD)NULL, TIME_PERIODIC);
}
}
static void Deactivate() {
if (_uTimerID != 0) {
timeKillEvent(_uTimerID);
}
}
static void Attach(ICallback* callback) {
_callback.push_back(callback);
}
static void Detach(ICallback* callback) {
auto iter = std::find(_callback.begin(), _callback.end(), callback);
if( iter != _callback.end() ) {
_callback.erase(iter);
}
}
static void CALLBACK TimerCallback(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) {
LONGLONG freq;
LONGLONG end = 0;
QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
if (_last_time > 0) {
do {
QueryPerformanceCounter((LARGE_INTEGER*)&end);
} while (((end - _last_time) * 1000 / freq) < _delay);
// コールバックを呼び出し
// (2回目以降)
for(auto callback: _callback) {
callback->AsNotified(((double)(end - _last_time) * 1000 / freq));
}
} else {
QueryPerformanceCounter((LARGE_INTEGER*)&end);
}
_last_time = end;
}
};
class Tick1;
std::vector<ICallback*> Systick<Tick1>::_callback = std::vector<ICallback*>();
unsigned int Systick<Tick1>::_uTimerID = 0;
LONGLONG Systick<Tick1>::_last_time = 0;
uint32_t Systick<Tick1>::_delay = 50;
class Tick2;
std::vector<ICallback*> Systick<Tick2>::_callback = std::vector<ICallback*>();
unsigned int Systick<Tick2>::_uTimerID = 0;
LONGLONG Systick<Tick2>::_last_time = 0;
uint32_t Systick<Tick2>::_delay = 100;
class Tick3;
std::vector<ICallback*> Systick<Tick3>::_callback = std::vector<ICallback*>();
unsigned int Systick<Tick3>::_uTimerID = 0;
LONGLONG Systick<Tick3>::_last_time = 0;
uint32_t Systick<Tick3>::_delay = 150;
Timer.cpp
# include <cstdio>
# include <iomanip>
# include <iostream>
# include "Systick.h"
class Timer : public ICallback {
private:
std::vector<double> all_elapsed;
public:
void AsNotified(double elapsed) override {
all_elapsed.push_back(elapsed);
}
void PrintTime() {
for(auto t:all_elapsed) {
std::cout << t << std::setprecision(4) << "[ms]" << std::endl;
}
}
};
int main(int argc, char* argv[]) {
Timer* timer1 = new Timer();
Timer* timer2 = new Timer();
Timer* timer3 = new Timer();
Timer* timer4 = new Timer();
Systick<Tick1>::Attach(timer1);
Systick<Tick2>::Attach(timer2);
Systick<Tick3>::Attach(timer3);
Systick<Tick3>::Attach(timer4);
Systick<Tick1>::Activate();
Systick<Tick2>::Activate();
Systick<Tick3>::Activate();
Sleep(1000);
std::cout << "Timer1" << std::endl;
timer1->PrintTime();
std::cout << "Timer2" << std::endl;
timer2->PrintTime();
std::cout << "Timer3" << std::endl;
timer3->PrintTime();
std::cout << "Timer4" << std::endl;
timer4->PrintTime();
return 0;
}
実行結果
割と均一な時間を出してくれていることが分かると思われる。
ただしそれでも+1msの誤差が生じてしまうのは、避けられない。