为何定时做事的ISR或中断程序內不可用Serial.print(定时器相关)


为何定时做事的ISR或中断程序内不可用Serial.print(定时器相关)
https://www.arduino.cn/thread-12546-1-1.html

问题:中断或定时器中看不到串口打印的内容

常常有人用到定时器的中断(或外部中断),然后想要使用 Serial.print 或 Serial.println 送数据到串口监视器查看,可是却常常有人发现加了一些 Serial.println 之后可能很快就不动了 !?

官方网站也建议在 ISR( ) 内或是在用 attachInterrupt 连接的中断处理程序内, 最好不要使用 Serial.print 或 Serial.println 这类函数 (function) !

那到底在 中断处理程序内 可不可以用 Serial.print 和 Serial.println 呢 ? 其实答案是勉强可以用 Serial.print( ) 与 Serial.println( ) 的 ! 为何说勉强可以, 不是说不可以, 也不说可以 ?

因为, 只要你的中断请求的间隔时间不是太短(即两次中断的时间间隔), 那使用 Serial.print 其实并不会有问题 !
怎样才叫做太短呢? 就是短到 Serial.print 来不及或快要来不及也可能会有问题!如果你的中断一秒才来一次或更久才来一次, 那用 Serial.println应该没问题

如果你的中断请求是 1ms 来一次,那肯定来不及 !!原因很简单, 来不及打印 Serial.println( ) 要求送出的字符串。
奇怪!?

不是说 Arduino CPU 在 16MHz 的频率 (Clock)之下, 最快 1us 可以做 16 个指令,那 1ms 就可以做 16000 个指令了,怎会小小 Serial.print 一下就来不及呢?原因当然就是 Serial.print 很慢很慢 !

其实, 所谓 1us (micro second)可以做 16 个指令是指最简单的机器指令,
对于 C/C++ 写的每句指令因为相当于数个到数十个机器指令,
所以 C/C++ 每句指令大多数要 0.5 us 到 1 us甚至更多的时间(例如调用 millis( ) 或调用 micros( ) 就大约要 2 us)。

聪明的你已经算出那这样 1ms 还是可以做大约数百甚至多到两千个 C/C++ 指令 !
没错, 可是, 在 Baud rate (波特率) 9600 的情况下, 打印一个 char 就要大约 1ms = 1000us 的时间。
这是可以算的, 9600bps 意思就是每秒 9600 bit, 一个 char 要用 10 bit(包括内容 8 bit,开始(Start) 1 bit,结束(STOP) 1 bit), 所以,9600bps 意思就是每秒大约 9600/10 = 960 char (Byte);
这意思就是 1 char 大约要 1ms = 1000 us 的时间 (1秒/960 = 1.04ms) !

Arduino 程序来测试串口输出时间

// measure Serial.print cost
unsigned long begt, endt; // for record micros( )
int baud = 9600;
char ggyy[ ] = "-2-4567890ABCD56";  // 字符串用来测 60..66 char
void setup( ) {
  Serial.begin(baud);
  Serial.println(String("Test Baud Rate = ") + baud);
  Serial.println("----------\nNow test print 50 chars immediate return");
  test1( );
  Serial.println("\n\n==========\nNow test with .flush( )");
  test2( );
  test3( );
} // setup(
void loop( ) {
  ;
}
void test1( ) {
  Serial.flush( );
  delay(258);
  begt = micros( );
  //Serial.print(ggyy);  // 用来测 60 .. 66 char
  Serial.print("123456789x123456789X");
  Serial.print("..34567890-x-4567890F234F678\r\n");
  endt = micros( );
  Serial.println(String("Run time = ") + (endt - begt) + "micro seconds = " +
                 (endt - begt) / 1000.0 + "milli seconds");
}
void test2( ) {
  Serial.flush( );
  delay(258);
  begt = micros( );
  //Serial.print(ggyy);  // 用来测 60 .. 66 char
  Serial.print("123456789x123456789X");
  Serial.print("..34567890-x-4567890F234F678\r\n");
  Serial.flush( );  // 确定都已经打印出去
  endt = micros( );
  Serial.println(String("Run time = ") + (endt - begt) + "micro seconds = " +
                 (endt - begt) / 1000.0 + "milli seconds");
}
void test3( ) {
  Serial.println(String("\n\nPrint 100 char with flush at Baud rate ") + baud);
  Serial.flush( );
  delay(258);
  begt = micros( );
  //Serial.print(ggyy);  // 用来测 60 .. 66 char
  Serial.print("123456789x123456789X");
  Serial.print("..34567890-x-4567890F234F678\r\n");
  ///
  Serial.print("123456789x123456789X");
  Serial.print("..34567890-x-4567890F234F678\r\n");
  Serial.flush( );  // 确定都已经打印出去
  endt = micros( );
  Serial.println(String("Run time = ") + (endt - begt) + "micro seconds = " +
                 (endt - begt) / 1000.0 + "milli seconds");
}

大家可以自己测试看看, 以下我把我测的各种结果跟大家分享:

  1. 测试在 9600 bps 打印 50, 60, 61, … 68 char, 100 char, 200char, 300char, 500char, 600char, 1000char;
    以下两栏结果分别是在 Serial.print 之后立即抓时间, 以及 test2( ) 是用 .flush( ) 之后再抓时间:
打印字符数test1( )test2( )
50 char0.51 ms52.03ms (相差 51.52)
60 char0.61 ms62.43ms (相差 61.82)
61 char0.62 ms63.47ms (相差 62.85)
62 char0.62 ms64.51ms (相差 63.89)
63 char0.64 ms65.55ms (相差 64.91)
64 char0.64 ms66.59ms (相差 65.95)
65 char0.66 ms67.63ms (相差 66.97)
66 char1.08 ms68.67ms (相差 67.59)
67 char2.12 ms69.72ms (相差 67.6)
68 char3.16 ms70.76ms (相差 67.6)
100 char36.44 ms104.03ms (相差 67.59)
200 char140.44 ms208.03ms (相差 67.59)
300 char244.44 ms312.03ms (相差 67.59)
500 char452.44 ms520.03ms (相差 67.59)
600 char556.44 ms624.03ms (相差 67.59)
1000 char972.44 ms1040.03ms (相差 67.59)

请注意, 0.64ms 可能是 0.636ms 或 0.640ms 或 0.644ms, 因印出时自动四舍五入!

  1. 改用 Baud Rate 19200 bps, 发现所需时间就是大约一半, 很合理 !
  2. 改用 Baud Rate 57600 bps, 发现所用时间就是 9600bps 的大约七分之一,
  3. 改用 Baud Rate 115200 bps, 时间又是 57600bps 的大约一半 !
  4. 回头解释刚刚(1)的结果, 以下先讨论 test1( )的方式,
    你会发现, 如果不使用 Serial.flush( ); 把打印数据冲刷出去.
    则印 50char 要 0.51ms (且这答案在各种 Baud Rate 几乎都一样 !!),
    印 60 char 要 0.61ms, 印 64 char 要 0.64ms, 印 65 char 要 0.66ms (因为只印到小数点后两位, 是把小数点后第三位做四舍五入)
    意思是大约每个 char 要大约 0.1ms (不是 1msㄟ, 怎会这么快 !?),
    别高兴, 事实上这时间不是真正打印出去的时间, 是复制字符串到 Serial 的输出缓存区(Buffer)的时间,
    所以, 你会发现, 即使提高 Baud Rate 到 57600, 打印 50 char 也是要 0.51 ms
    那为何 65 char 是 0.66ms, 可是 66 char 却是 1.08ms, 67 char 要 2.12ms, 68 char 要 3.16ms ???
    这是因为输出缓存区(Output buffer)只有 64 bytes (chars),
    当缓存区满的时后, Serial.print 必须等有空位才可以把剩下的字符串复制进去缓存区,
    必须都已经把参数的字符串复制进去缓存区了, 然后 Serial.print 才能返回!
    因此, 你可以发现, 打印 50 char 要 0.51ms, 但是打印 500char, 却要高达 452.44ms, 不是 5.1ms !!!
    如果你把系统 Serial 的输出缓存区改为 500 bytes 则打印 500char 就会是大约 5.1ms, 就是复制到缓存区的时间 !!
  5. 再来讨论(1)的结果, 但重点放在用 test2( ), 也就是 Serial.flush( ); 之后再做 endt = micros( ); 取时间:
    如果没使用 Serial.flush( ); 则表示可能还会有最多 63 bytes 在输出缓存区还没真的被送出去!!
    现在如test2( ) 内的做法, 等 Serial.flush( ); 确定都送出之后再取得 micros( ) 时间, 结果 …
    你会发现 50char 要 52.03ms, 60char 要 62.43ms, 64 char 要 66.59ms, 印 65 char 要 67.64ms, 印 66 char 要 68.67ms, 印 67 char 要 69.72ms,
    印 100 char 要 104.03ms, 印 200 char 要 208.04ms, 印 300 char 要 312.06ms, 印 500 char 要 520.08ms,
    看到没, 几乎就是 1 char 要 1 ms 啦 !!
    那为何是比 1ms 略大呢?
    首先 Serial.print 本身要花时间且传送要做一点准备工作也要一点时间,
    还有, 1秒 / 960 char 这样 1 char 是 1.04ms, 所以印 100 char 要大约 104.03 ms 完全合理 !!!
    另外, 打印 66 char 以上, 不论是 67 char, 100 char, 200char, 300 char, 500 char,甚至1000char,
    你会发现, 没有 Serial.flush( )比有Serial.flush( )少了大约 67.5 ms, 且 66 char 以上后几乎都是这样!
    这是因为缓存区只有 64 bytes, 所以在打印字数小于 65 char 时,
    因第一 char 立即送出, 剩下的字符都可复制到缓存区然后就可从 print 或 println 立即返回!
    但是打印太多字在缓存区满了之后, Serial.print 必须等到有一个 char 被抓走空出一byte的位置才可再把一个 char 填进去缓存区,
    这大约要 1ms 的时间, 必须把要 print( ) 的所有字符都填入缓冲区之后 Serial.print 才会结束;
    而此时缓存区内就是还有 64 char 还没送出, 这是有 .flush( ) 和没有 .flush( ) 差异时间的最主要来源 !!
    所以, 打印 1000 char, 没有 .flush( )时间是 972.44ms, 有 .flush( ) 确定打印完毕则是 1040.03ms, 相差大约67.5ms !
    ,,,。。。,,,。。。,,,。。。!
  6. 补充一下, 虽然 int 只有两个 byte, 但是:
    int k = 12345;
    Serial.println(k); 这句会送出 7 个 char:
    包括 “12345” 以及 “\r\n” 这两个字符;
    但请注意: Serial.print( (char)123); 这样只有送出 1 个 char !!
    还有:
    float ggyy = 1.25612345;
    Serial.println(ggyy); 这句会送出 6 个 char:
    包括 “1.26” 以及 “\r\n” 这两个字符;
    注意 float 和 double 的数值打印时, 小数点之后预设(默认)只印两位(但会从没印出的第一位四舍五入到有印的最后一位):
    http://arduino.cc/en/serial/print

关于 print/println 的源代码请参考:

 (它们也在你Arduino IDE 目录的 hardware\arduino\cores\arduino\ 里面!)

(IDE 1.0.6)
https://github.com/arduino/Arduino/blob/ide-1.0.x/hardware/arduino/cores/arduino/Print.cpp#L59
https://github.com/arduino/Arduino/blob/ide-1.0.x/hardware/arduino/cores/arduino/HardwareSerial.cpp#L61
(IDE 1.5.x / IDE 1.6.x)
https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/Print.cpp#L228
https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/HardwareSerial.cpp#L225

修改串口缓存区大小

如果你想把缓存区改大一些(包括输出缓存区与输入的缓存区),
或只是想看看缓存区大小的定义 SERIAL_BUFFER_SIZE 真的是 64,
IDE 1.0.6 请看 HardwareSerial.cpp
https://github.com/arduino/Arduino/blob/ide-1.0.x/hardware/arduino/cores/arduino/HardwareSerial.cpp#L61
IDE 1.5.x 或 1.6.x 请看 HardwareSerial.h https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/HardwareSerial.h#L42
(它们也在你Arduino IDE 目录的 hardware\arduino\cores\arduino\ 里面!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值