如何偷改 millis( ) 与 micros( )的值方便测试(教程)(定时器相关)

1. 很多人利用 millis( )micros( )检查是否到了该做事情

用来设定定时做事( Timer 库或 SimpleTimer 库都是这样);

如果时间久了, millis( ) 很大会怎样, 有没有溢出问题 ?
官网说明 millis( ) 返回值是 unsigned long类型, 要Arduino执行49.710 天后才发生溢出。
4294967296ms/1000 /60 /60 / 24 天 = 49.710 天
这时 millis( ) 会由最大值的 4294967295 又加 1 变为 0,
一般称这为溢出(Overflow), 也称 Rollover (归零)!
即使是用 micros( ), 等到接近 unsigned long最大值也要等大约 71.58分钟:
4294967296 /1000/1000 /60 分钟 = 71.58 分钟
也就是说, micros( ) 要开机后大约 71.58分钟才会溢出(Overflow), 或应该说Rollover (归零)!

2. 真正溢出(Overflow)的定义应该是当把 millis( ) 看作 signed 有正负

它从正数再次加 1 后瞬间变成负数之时:
在你 PC 上做个测试:
开启微软的计算器(Calculator):
点 View 选 Programmer, 然后点 Dword
输入 0 - 1 并按 =
此时有 32 个 1, 最右边的编号是 0,用鼠标点一下bit 31, 该 bit 会变为 0,二进制变成0111 1111 1111 1111 1111 1111 1111 1111,十进制数是 2,147,483,647, 这就是有正负之 long 的最大值(即0x7fffFFFF),
这时, 请用鼠标点计算器的 + 再点 1 然后点 =
怪事发生了, 出现答案是负数: -2147483648
也就是说, 把 2147483647 这数加 1 就会变成 -2147483648,二进制变成1000 0000 0000 0000 0000 0000 0000 0000
这才是 CPU 对真正 Overflow 的定义 !但如果看做 unsigned long , 它就是正数 2147483648, 再次加 1, 变成 -2147483647, 它就是 unsigned long 的 2147483649, 一直加 1, 最后会达到 4294967295, 这数看做 signed 就是 -1; 也就是二进制 32 个 1, 此时若不限制只有 32 bit, 则再次加 1 后会 变成 10000… 共 32 个 0 , 这个数其实就是 4294967296

3. 你可以用简单 Arduino 程序测试:

void setup( ) {
  unsigned long k = -1;
  Serial.begin(9600);
  Serial.println( String("k=") + k);
  long gg = k;
  Serial.println( String("gg=") + gg);
  k = k / 2;
  Serial.println( String("k / 2 = ") + k);
}
void loop( ) {
  ;
}

输出:

k=4294967295
gg=-1
k / 2 = 2147483647

4.我们可以在程序中偷改 millis( )

这我在以下这篇有写过: 关于 millis() 溢出(overflow)归零(rollover)有没问题?(教程),现在举例说一下如何改:
(a) 在你程序最前面写:extern unsigned long timer0_millis;这是说我们要直接存取(访问) millis( ) 的变量 timer0_millis
(b)想改为接近快要归零或是接近 49.710 天,
这样写:
timer0_millis = -5678; 就是再过 5.678 秒就会归零;然后你可以立即 Serial.print( millis( ) ); 印出来看看 millis( )` 是多少 !?

©想改为接近 unsigned long 的一半或说大约 24.8 天,
可以这样:
timer0_millis = 2147483647L -5677; 再过 5.678 秒就会过一半 ! 或是不记得这么长的 2147483647L
那简单写这样也一样:
timer0_millis = ((unsigned long) -1 ) /2 -5677;/再过 5.678 秒就会过一半 !

如果你不相信,
那就写个简单的 Arduino 程序测试看看吧

5.那可否偷改 micros( )的答案呢 ?

答案是当然可以 !
不然要开机后约 71.58 分钟 micros( )才会到很大接近归零也是很久:
4294967296 /1000/1000 /60 分钟 = 71.58 分钟
那要如何偷改 micros( )的答案呢 ?
很简单, 只要偷改计算基础的 timer0_overflow_count 即可,
原理就不多说的, 我写了两个函数方便设定为快要溢出或快要到 unsigned long 的一半

void setup() {
  Serial.begin(9600);
  setMicrosEndBackMs(3250);  // 再过大约 3.25秒 micros( )会溢出(相当于71.58分钟)
  unsigned long you = micros();
  Serial.println(you);
  setMicrosHalfBackMs(3388);  //再过大约  3.388 秒 micros( )会过unsigned long的一半(即快要35.79分钟):
  unsigned long her = micros();
  Serial.println(her);
}
void loop() {
  ;
}

两个函数如下, 大家可以直接抄去用即可:

// 71.58分钟倒回 back ms (注意不是 back us)
void setMicrosEndBackMs(unsigned long back) {
  extern unsigned long timer0_overflow_count;
  back = ( ((unsigned long)(-1)) / 4 / 256 - 2) - back;
  cli( ); // 禁止中断
  timer0_overflow_count = back;
  sei( ); // 允许中断
}

// 35.79分钟倒回 back ms (注意不是 back us)
void setMicrosHalfBackMs(unsigned long back) {
  extern unsigned long timer0_overflow_count;
  back = ( ((unsigned long)(-1)) / 4 / 256 - 2) / 2 - back;
  cli( ); // 禁止中断
  timer0_overflow_count = back;
  sei( ); // 允许中断
}

输出:、

4291636232
2144012772

6. 关于 millis( )micros( ) 是如何计算出时间的请参考这里:

不过为了方便, 我把相关的源代码复制在以下:

unsigned long timer0_millis = 0; // 开机到现在几个 millis ?
unsigned char timer0_fract = 0; // 调整误差用
unsigned long timer0_overflow_count; // 给 micros( ) 用

// 以下 ISR( ) 中断处理程序每隔 1.024 ms 会执行一次

SIGNAL(TIMER0_OVF_vect) {
  timer0_millis += 1;
  timer0_fract += 3;
  if (timer0_fract >= 125) {
    timer0_fract -= 125;
    timer0_millis += 1;
  }
  timer0_overflow_count++;
}

// call 要 4 clock, 回传后把答案复制到变数(variable变量)要8clock
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
/// unsigned long ans = millis( );  // total 4+17+8 = 29 clocks


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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值