本帖最后由 tsaiwn 于 2015-4-8 23:09 编辑
(1)到底 millis( ) 的代码是怎么写的 ?
请看以下代码 :
unsigned long millis( ) {
unsigned long m;
uint8_t oldSREG = SREG; //状態寄存器(包括是否允许 Interrupt); 1clock
// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to timer0_millis)
cli( ); // 禁止中断; 1 clock
m = timer0_millis; // 读取记忆体的全域变量 timer0_millis;8 clock
SREG = oldSREG; // 恢復状態寄存器(注意不一定恢復中断喔 !);1 clock
return m; // 6 clocks
} // millis( // total 17 clock cycles
啥?
原来它只是先用 cli( ) 把中断请求禁止,
然后读取 timer0_millis; 放到临时变量 m,
接著还原中断状態, 然后把 m 送回来 !
请注意我是说"还原中断状態",
不是说"恢復中断",
Why?
因为原本在进入 millis( ) 之前有可能已经是禁止中断的状態,
是否禁止中断被记录在 SREG 中的一个 bit,
在送回答案之前做 SREG = oldSREG; 还原中断状態 !
因为在刚进入 millis( ) 时有把 SREG 先复製到 oldSREG 这临时变量中!
注意虽然你在 ISR( ) 內可以调用 millis( ),
但是在 ISR( ) 內因为中断请求被禁止,
所以连续调用 millis( ) 得到的答案都不会变喔 !
因此千万不要在 ISR( ) 中断程序內写如下:
while( millis( ) < timeUP ) {
//.. do nothing 或 do something
}
这样这 while Loop 会陷入永不停止的 LOOP !!!
因为 millis( ) 都不会改变答案 !
(2)关於 timer0 的中断与其处理程序 SIGNAL(TIMER0_OVF_vect)
是谁负责计算 timer0_millis 这个变数(Variable, 变量) ?
问题来了,
既然 millis( ) 的答案来自 timer0_millis 这个变数(Variable;变量),
那 timer0_millis 这是啥东西呢?
原来它是一个全局变量(Global variable),
意思是可被各 function 存取(访问)的 unsigned long 变量。
那又是谁负责计算这 timer0_millis 呢?
是一个中断程序负责, 如下:
unsigned long timer0_millis=0; // 开机到现在几个 millis ?
unsigned char timer0_fract=0; // 调整误差用
unsigned long timer0_overflow_count; // 给 micros( ) 用
SIGNAL(TIMER0_OVF_vect) {
timer0_millis += 1;
timer0_fract += 3;
if (timer0_fract >= 125) {
timer0_fract -= 125;
timer0_millis += 1;
}
timer0_overflow_count++;
}
看到这里, 我们发现 millis( ) 答案来自 timer0_millis;
而 timer0_millis 必须系统发现 TIMER0_OVF_vect 中断才会改变(稍后討论),
所以在 ISR( ) 內连续调用 millis( ) 其答案是不会变的 !
因为在 ISR( ) 內中断是被禁止的,
根本没机会进入SIGNAL(TIMER0_OVF_vect),
所以在 ISR( ) 內连续调用 millis( ) 回传值不会变 !
所以千万不要在 ISR( ) 內企图用 millis( ) 判断过了多久 !
因为在 ISR( ) 內执行期间 millis( ) 在静止状態 !!
(3)何时会执行上述的 SIGNAL(TIMER0_OVF_vect) 这 ISR( ) ? 为什么 ?
好了, 剩下的问题是何时会执行上述中断代码 SIGNAL(TIMER0_OVF_vect) ?
这代码的 TIMER0_OVF_vect 名称就已经说明了是当 timer0 发生 Overflow的中断,
也就是 timer0 的內部计数寄存器 TCNT0 算了一轮迴(0,1,2...254, 255, 0),
从 255 加 1 又变为 0 之时(这时称 Overflow 溢位)会產生中断进入这处理程序 !
那么 timer0 的 TCNT0 每隔多久会加 1 呢?
就是每当 timer0 被 "踢" 一下的时候啦!
被 "踢"一下就是 timer0 的时脉变化一下, 称作一个 tick 或一个 clock cycle;
由於 timer0 的 Prescaler 是被Arduino设定为 64,
Arduino 大都使用 16 MHz 的时脉,除频 64 之后给 timer0 用,
则每个 clock cycle (或称 tick) 时间为:
1 秒 / (16 000 000 / 64) = 1/250000 = 0.000004 sec = 0.004 ms
所以给 timer0 的 tick 是每个 tick 0.004ms = 4 us (micro second)。
意思是每隔 0.004 milli sec 计时器(定时器)的时脉电路会"踢" timer0 一下,
这使得 timer0 会自动把 TCNT0 加 1, (注意不是靠 CPU 喔!)
因为 TCNT0 只有 8 bit, 看作无符號整数 (unsigned char),
既然 TCNT0 每 0.004ms 会自动加 1, 总会加到 255,
然后 255 再加 1 变回 0 (即 Overflow), 共使用256 ticks,
共花了 0.004 ms * 256 = 1.024ms,
这时会对 CPU 產生中断一次,
要求 CPU 进入上述的中断处理程序SIGNAL(TIMER0_OVF_vect) 处理 。
*** 注意给 CPU 的 clock cycle 是 0.0625 us 喔(没有除以 64) !!
(4)每隔 1.024ms 把 millis 加 1 岂不是有误差 0.024ms 那要如何修正 ?
我们看到了在中断处理程序中主要是把 timer0_millis 加 1,
但请注意, 实际上这时是经过了 1.024 ms, 並不是 1ms,
也就是產生了误差, 长此以往, 这误差会越来越大 !
还好, Arduino 的工程师很聪明,
另外用一个变数 timer0_fract 纪录误差, 就是 timer0_fract += 3;
然后你会发现在 (timer0_fract >= 125) 时会做调整:
if (timer0_fract >= 125) {
timer0_fract -= 125;
timer0_millis += 1;
}
这个动作跟闰年(Leap year)原理类似,
因为地球绕太阳一圈的回归年其实是365.2421990741天,
不是 365天也不是 366天, 所以每四年要闰年一次多一天,
可是四年多一天等於算做一年是 365.25 天, 又不准了,
因此每一百年又把多算的一天取消(公元年/100整除不是闰年)做修正 !!
这里的算法是因每次误差 0.024 ms, 用 3 代表,
然后 125 就是代表 0.024ms * 125 = 1.000ms,
因此如果 (timer0_fract >= 125) 就要把 millis 加 1,
並且要做 timer0_fract -= 125;
注意不是设为 0 喔, 是减去 125,
因这时可能是125, 126, 127 这三个之一个, 多出来的误差要累计到下次的计算內。
(5)接著来看看相关的 micros( ) 这 function 是如何写的:
不过以下这程序已经被我简化成比较容易看懂(依据16MHz clock),
它会用到前面提及的 timer0_overflow_count;
unsigned long micros() {
unsigned long m;
uint8_t oldSREG = SREG; // 状態寄存器(包括是否允许 Interrupt)
uint8_t t; // 临时变量
cli(); // 禁止 Interrupt
m = timer0_overflow_count; // timer0 已经 overflow 几次 ?
t = TCNT0; // timer0 目前的值
if ((TIFR0 & _BV(TOV0)) && (t & 255)) m++; // timer0 目前的TCNT0值不是 0且欠一次中断
SREG = oldSREG; // 恢復状態寄存器(注意不一定恢復中断喔 !)
return ((m *256) + t) * 4; // 最大只能代表约 71.58分钟
} // micros(
你可以看到它只是短暂禁止中断, 然后读取两个整数到 m 和 t,
並在恢復中断状態后回传 ((m *256) + t) * 4; 这答案。
(6)为何 micros( ) 回传的值都是 4 的倍数 ?
其实从程序最后回传值就知道一定是 4 的倍数 !
前面说过因为 timer0 的 clock cycle 是每个 tick 0.004ms = 4 us,
在该函数內最后是回传 ((m *256) + t) * 4;
所以你会发现 micros( ) 回传的值都是 4 的倍数 !
回传的 ((m *256) + t) * 4 这答案用白话文说,
就是 ((TCNT0 已Overflow次数) * 256 + TCNT0 ) * 4
注意前面说过该 timer0 的 TCNT0 是每 4 us自动加 1,
这也是为何最后要乘以 4 获得几个 micro seconds的答案 !
因为 micros( ) 答案是用 unsigned long 表示,
所以 micros( ) 大约开机后每70分钟会Overflow 归零,
4294967296 /1000/1000 /60 分钟 = 71.58 分钟
还有, 请注意, 在进入 micros( ) 之前可能已经禁止中断,
所以结束 micros( ) 之前不是用 eni( ); 恢復中断,
是用 SREG = oldSREG; 恢復原先的中断状態!
前面说过,
如果你在 ISR( ) 內连续调用 millis( ) 其答案是不会变的 !
因为在 ISR( ) 內中断是被禁止的, 根本没机会进入SIGNAL(TIMER0_OVF_vect),
所以 millis( ) 回传值不会变,
但是, 在 ISR( ) 內连续调用 micros( ) 则其值是会变的 !
|