2016年7月27日
Githubでこの問題への対策がようやく取られたようです。これなら依存部分だけの修正で済みますからmbedライブラリ本体に手を入れる必要がありません。当方の対策よりずっと綺麗な解決策だと思います。
https://github.com/mbedmicro/mbed/pull/2193
https://github.com/mbedmicro/mbed/tree/master/hal/targets/hal/TARGET_NXP/TARGET_LPC15XX
修正したのは有志の方みたいなので、こういう実装の細かいバグは誰かが修正しないといつまでもそのままということみたいですね。
————————-以下古い記事
下記リンク先でも報告されている案件ですが、まとまった解決方法の情報がどこにもないのでこちらでまとめます。
https://developer.mbed.org/questions/53883/Ticker-failingstopping-after-72-minutes-/
https://developer.mbed.org/forum/bugs-suggestions/topic/22102/
今回このせいでmbedの赤外線ライブラリを使ったとき72分後に動かなくなる問題が発生し、対策になかなか大変な思いをしました。
https://developer.mbed.org/cookbook/IR
単発のticker割り込みだけの対策はもうすこし簡単なのですが、こちらのライブラリでは内部でtimer、ticker、timeoutを組み合わせている関係で、安易な対策ではうまく行かず根本の対策を求められる結果となりました。
現状で完璧な対策ができているかは不明なのですがとりあえず安定している対策方法を紹介したいと思います。
72分後で止まる原因
上記のmbed掲示板にこの問題の原因についての記載がありますのでこちらを引用します。
The problem here is the implementation of the ticker based on the LPC1549’s Repetitive Interrupt Timer (RIT). The RIT runs on a fixed clock rate (core clock) and has a 48-bit counter register. The ticker itself uses a 32-bit timestamp in microseconds. There is an overrun in the ticker at 2^32 microsecs (72 minutes). Due to this overrun the newly calculated compare value for the RIT lies below its counter value preventing further compare interrupts. As a result the ticker stops working.
翻訳ではありませんが、概要を説明します。まずLPC15XXシリーズのmbedライブラリではtimerとtickerにRITというハードウェアタイマー機能を使っています(詳しくはデータシート参照)。そしてmbed内部ではtickerもtimerも中身は同じRITを共有して使っています。このRITは48bit精度ですがmbedでは32bit処理に丸めているために起こっている問題と言えそうです。要はLPC1549のRITの仕様と、mbedのtickerの仕様があっていない上に対策が取られていないことが原因です。
では次に具体的に問題が起きているコードを引用して、もっと踏み込んだ原因について書きます。
mbed-src\targets\hal\TARGET_NXP\TARGET_LPC15XX\us_ticker.c
uint32_t us_ticker_read() { if (!us_ticker_inited) us_ticker_init(); uint64_t temp; temp = LPC_RIT->COUNTER | ((uint64_t)LPC_RIT->COUNTER_H << 32); temp /= (SystemCoreClock/1000000); return (uint32_t)temp; }
mbedの内部では結局このコードでRITの値を読み取っているのですが、RITはCOUNTERとCOUNTER_Hを合わせて48bit分カウントします。それを64bitのtemp変数に入力して計算しています。しかし最終的に返り値は32bitで丸められてしまっています。
この32bitに丸められるときに取れる最大値がRITでいうとどの程度になるかを計算しますと、SystemCoreClockが72000000(72Mhz)だとして、0x47FFFFFFB8です。これは丁度32bitの上限値0xFFFFFFFFに72をかけたものです。
32bitの返り値ではRITカウンターが0x47FFFFFFB8を超えた時にオーバーフローが発生、値が0となってしまいそれ以降は正しい動作が出来なくなります。RIT自身の上限は0xFFFFFFFFFFFFですからこの上限値に達するまで次の割り込みは発生しないことになりそうです。
ということはRIT本来の48bitまで正しく動作するような戻り値を確保するには、返り値の精度は現状の32bitではなく64bitの精度が必要ということになります。もしRITスペックの48bitフルで動作出来るようになれば計算上1085時間=45日まで大丈夫です。ということで対策としてはmbedライブラリ内部にあるticker、timer関係の値受け渡しの精度をすべてuint32_tではなくuint64_tへ変更する必要があることがわかりました。
対策編
対策方法としてはまずus_ticker_read()の引数を64bit化することです。ですがそれだけでは当然ながら不十分で、64bit化の対象は引数の受け渡し先すべてになります。ですので改変する箇所は結構あります。
例えば、次のような場所も改変対象になります。
mbed-src\hal\ticker_api.h
typedef uint32_t timestamp_t; /** Ticker's event structure */ typedef struct ticker_event_s { timestamp_t timestamp; /**< Event's timestamp */ uint32_t id; /**< TimerEvent object */ struct ticker_event_s *next; /**< Next event in the queue */ } ticker_event_t; typedef void (*ticker_event_handler)(uint32_t id); /** Ticker's interface structure - required API for a ticker */ typedef struct { void (*init)(void); /**< Init function */ uint32_t (*read)(void); /**< Read function */ void (*disable_interrupt)(void); /**< Disable interrupt function */ void (*clear_interrupt)(void); /**< Clear interrupt function */ void (*set_interrupt)(timestamp_t timestamp); /**< Set interrupt function */ } ticker_interface_t;
typedef uint64_t timestamp_t; へ変更
mbed-src\common\wait_api.c
void wait_us(int us) { uint32_t start = us_ticker_read(); while ((us_ticker_read() - start) < (uint32_t)us); }
intとuint32_tをuint64_tへ変更(ヘッダファイルも)
mbed-src\common\Timer.cpp
void Timer::start() { if (!_running) { _start = ticker_read(_ticker_data); _running = 1; } } void Timer::stop() { _time += slicetime(); _running = 0; } int Timer::read_us() { return _time + slicetime(); } float Timer::read() { return (float)read_us() / 1000000.0f; } int Timer::read_ms() { return read_us() / 1000; } int Timer::slicetime() { if (_running) { return ticker_read(_ticker_data) - _start; } else { return 0; } } void Timer::reset() { _start = ticker_read(_ticker_data); _time = 0; }
_start、_time、slicetime()、read_us()の定義型をuint64_tへ変更(ヘッダファイルも)
対策箇所が多いので、該当部をまとめた改変済ソースを動作安定を確認できた後アップしたいと思います。