假設有一個 16bit 硬體計時/計數器 (Timer/Counter) 負責計數外部的訊號. 所以我們直覺的反應是某一段時間之內的訊號個數是本次讀取的計數值減掉上次讀取的計數值, 像這樣 Count = ValCurr - ValPrev. 但是如果上次讀取的內容已經接近上限了 (例如: 65530), 而這一次讀到的內容已經溢位變成一個很小的數值 (例如: 5) 呢?
當下你可能會想到下列幾個:
- 改用 32bit 來處理.
- 65536 - (大值 - 小值)
- ...
其實沒那麼麻煩, 答案很簡單依然是 Count = ValCurr - ValPrev, 只不過記得要用 uint16_t (或者 unsigned short) 來定義這些變數.
#include <stdint.h>
#include <stdio.h>
void main(void)
{
uint16_t Count, ValCurr, ValPrev;
// Init. previous counter value
ValPrev = readCounter();
while(1) {
delay_ms(5);
// Get current counter value
ValCurr = readCounter();
Count = ValCurr - ValPrev;
// Save current value for next calc.
ValPrev = ValCurr;
printf("Count in 5ms: %u\n", Count);
}
}
只要把要處理數值的變數依照硬體的位元數定義為不帶符號的變數. 這個方法對 8bit, 16bit, 32bit 甚至是 64bit 的硬體計時/計數器都適用. 可是如果是 10bit 或者 24bit 這類長度不是剛好可以用 8 × 2n 表示的硬體計時/計數器要怎麼辦呢?
答案是稍稍再修改下: 變數一樣是用帶 unsigned 的 (short, int...), 再多作一次位元遮罩就好了. 例如: 硬體是 12bit, 就把運算式改為 Count = (ValCurr - ValPrev) & 0x0FFF(註一), 硬體是 24bit, 就把運算式改為 Count = (ValCurr - ValPrev) & 0x00FFFFFFL(註一) 這樣就可以了.
不過前面說的都要在一個重要的前提之下: 那就是你用硬體計時/計數器長度要夠用. 什麼意思呢? 就是在一個取樣讀取週期 (上一次讀取資料和這一次讀取資料之間) 中最多只能溢位一次. 多次溢位代表你要計數的訊號太多了, 你應該要縮短取樣讀取週期時間或者加長硬體計時/計數器的長度 (位元數).
總結
條件 | 狀況 | 數學公式 |
---|---|---|
沒有發生溢位 | ValCurr = ValPrev | Count = 0 |
沒有發生溢位 | ValCurr > ValPrev | Count = ValCurr-ValPrev |
發生一次溢位 | ValCurr | Count = (ValCurr-ValPrev) + MAX_COUNT*1(註二) |
發生一次溢位 | ValCurr = ValPrev | Count = (ValCurr-ValPrev) + MAX_COUNT*1(註二) Count 已經比可計數範圍多1 |
發生一次溢位 | ValCurr > ValPrev | Count = (ValCurr-ValPrev) + MAX_COUNT*1(註二) |
註: MAX_COUNT = 2^(bit_length_of_timer) |
如果你有辦法準確的抓到溢位的次數 (OV_COUNT) (有溢位中斷可用即可), 那麼計算式應該變成 Count = (ValCurr-ValPrev) + (MAX_COUNT)*OV_COUNT, 算完之後 OV_COUNT 要記得清除 (值設為 0).
註一: 變數 Count, ValCurr, ValPrev 必需要能容納硬體計時/計數器的長度. 12bit 的硬體變數至少是 uint16_t, 若是 24bit 的硬體則至少是 uint32_t.
註二: 這個式子是數學式不是程式, 實作時一樣是要注意數學運算溢位的問題和注意適當的加上 type casting.
註三: 此法不適用於雙向 (即可以倒數的) 硬體計數器.