极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 8371|回复: 3

【教程】为何说定时做事的ISR或中断程序內原则上不可用Serial.print

[复制链接]
发表于 2015-4-5 16:48:50 | 显示全部楼层 |阅读模式


    常常有人用到定时器的中断(或外部中断),
然后想要使用 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 很慢很慢 !


回复

使用道具 举报

 楼主| 发表于 2015-4-5 16:49:57 | 显示全部楼层

补充..
    其实, 所谓 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)  !

回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-4-5 17:04:02 | 显示全部楼层
tsaiwn 发表于 2015-4-5 16:49
补充..
    其实, 所谓 1us (micro second)可以做 16 个指令是指最简单的机器指令,
对於 C/C++ 写的每句 ...


不相信, 写个 Arduino 程序来测试:

  1. // measure Serial.print cost
  2. unsigned long begt, endt; // for record micros( )
  3. int baud = 9600;
  4. char ggyy[ ] = "-2-4567890ABCD56";  // 字符串用来测 60..66 char
  5. void setup( ) {
  6.   Serial.begin(baud);
  7.   Serial.println(String("Test Baud Rate = ") + baud);
  8.   Serial.println("----------\nNow test print 50 chars immediate return");
  9.   test1( );
  10.   Serial.println("\n\n==========\nNow test with .flush( )");
  11.   test2( );
  12.   test3( );
  13. } // setup(
  14. void loop( ) {;}
  15. void test1( ) {
  16.    Serial.flush( );
  17.    delay(258);
  18.    begt = micros( );
  19.    //Serial.print(ggyy);  // 用来测 60 .. 66 char
  20.    Serial.print("123456789x123456789X");
  21.    Serial.print("..34567890-x-4567890F234F678\r\n");
  22.    endt = micros( );
  23.    Serial.println(String("Run time = ")+ (endt-begt)+ "micro seconds = "+
  24.        (endt-begt)/1000.0 + "milli seconds");
  25. }
  26. void test2( ) {
  27.    Serial.flush( );
  28.    delay(258);
  29.    begt = micros( );
  30.    //Serial.print(ggyy);  // 用来测 60 .. 66 char
  31.    Serial.print("123456789x123456789X");
  32.    Serial.print("..34567890-x-4567890F234F678\r\n");
  33.    Serial.flush( );  // 確定都已经打印出去
  34.    endt = micros( );
  35.    Serial.println(String("Run time = ")+ (endt-begt)+ "micro seconds = "+
  36.        (endt-begt)/1000.0 + "milli seconds");
  37. }
  38. void test3( ) {
  39.    Serial.println(String("\n\nPrint 100 char with flush at Baud rate ") +baud);
  40.    Serial.flush( );
  41.    delay(258);
  42.    begt = micros( );
  43.    //Serial.print(ggyy);  // 用来测 60 .. 66 char
  44.    Serial.print("123456789x123456789X");
  45.    Serial.print("..34567890-x-4567890F234F678\r\n");
  46.    ///
  47.    Serial.print("123456789x123456789X");
  48.    Serial.print("..34567890-x-4567890F234F678\r\n");
  49.    Serial.flush( );  // 確定都已经打印出去
  50.    endt = micros( );
  51.    Serial.println(String("Run time = ")+ (endt-begt)+ "micro seconds = "+
  52.        (endt-begt)/1000.0 + "milli seconds");
  53. }
复制代码
大家可以自己测试看看, 以下我把我测的各种结果跟大家分享:
(1)测试在 9600 bps 打印 50, 60, 61, .. 68 char, 100 char, 200char, 300char, 500char, 600char, 1000char;
    以下两栏结果分別是在 Serial.print 之后立即抓时间, 以及 test2( ) 是用 .flush( ) 之后再抓时间:
  打印字符数    test1( )      test2( )
   50 char        0.51 ms      52.03ms   (相差 51.52)
-------------------------------------------------------
   60 char        0.61 ms      62.43ms   (相差 61.82)
   61 char        0.62 ms      63.47ms   (相差 62.85)
   62 char        0.62 ms      64.51ms   (相差 63.89)
   63 char        0.64 ms      65.55ms   (相差 64.91)
   64 char        0.64 ms      66.59ms   (相差 65.95)
   65 char        0.66 ms      67.63ms   (相差 66.97)
   66 char        1.08 ms      68.67ms   (相差 67.59)
   67 char        2.12 ms      69.72ms   (相差 67.6)
   68 char        3.16 ms      70.76ms   (相差 67.6)
---
  100 char      36.44 ms     104.03ms   (相差 67.59)
  200 char    140.44 ms     208.03ms   (相差 67.59)
  300 char    244.44 ms     312.03ms   (相差 67.59)
  500 char    452.44 ms     520.03ms   (相差 67.59)
  600 char    556.44 ms     624.03ms   (相差 67.59)
1000 char   972.44 ms    1040.03ms   (相差 67.59)
========================= 请注意, 0.64ms 可能是 0.636ms 或 0.640ms 或 0.644ms, 因印出时自动四捨五入!
(2)改用 Baud Rate 19200 bps,
   发现所需时间就是大约一半, 很合理 !
(3)改用 Baud Rate 57600 bps,
  发现所用时间就是 9600bps 的大约七分之一,
(4)改用 Baud Rate 115200 bps, 时间又是 57600bps 的大约一半 !
(5)回头解释刚刚(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, 就是复製到缓存区的时间 !!
(6)再来討论(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 !
,,,。。。,,,。。。,,,。。。!
(7)补充一下, 虽然 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


回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-4-5 17:09:19 | 显示全部楼层
tsaiwn 发表于 2015-4-5 17:04
不相信, 写个 Arduino 程序来测试:大家可以自己测试看看, 以下我把我测的各种结果跟大家分享:
(1)测试 ...

输出缓存区(Output buffer)只有 64 bytes (chars),
当缓存区满的时后, Serial.print 必须等有空位才可以把剩下的字符串复製进去缓存区,
必须都已经把参数的字符串复製进去缓存区了, 然后 Serial.print 才能返回!
补充..
如果你想把缓存区改大一些(包括输出缓存区与输入的缓存区),
   或只是想看看缓存区大小的定义 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\ 里面!)
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-4-18 09:26 , Processed in 0.042106 second(s), 17 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表