darkorigin 发表于 2015-4-8 13:25 
第一 。我不是大神。。。毕业接近10年了 之前学的东西都基本还给老师了。 当年学的也只是单片机汇编
第 ...
元老兄台说的没错
(不叫大神改叫元老兄台 :-) :-)
少调用函数可以省一点点 clock
但是元老兄台你可能忘了:
你要楼主把信息串在一起用一个 Serial.print( )
结果要串在一起所花的时间却可能大于省下的函数调用时间 !!
(其实不是可能, 而是确实浪费更多倍数的时间 !!)
我这说法是有依据的,
(第一)
因为要串在一起最简单做法如下:
假设 data 在 x, x2, x3, x4, x5, x6, nn (因为楼主有一项为时间 nn), 则:
Serial.println(String("") + x +" "+x2 +" "+x3 +" "+x4 +
" "+x5 +" "+x6 +"," + nn);
这样, 我中间各项故意用" "空白隔开, 最后一项用","隔开, 已经最省啰 :-)
这些串接的动作会跳入 String class, 在 WString.cpp 里面
(这说的程序,
都在 Arduino IDE 的 hardware\arduino\cores\arduino 内,
以下 HardwareSerial.cpp, Print.cpp 也是);
我有实际写 Arduino 程序在 16MHz 的 UNO R3 测试过,
所花时间远比改用 13个 Serial.print( )外加一个 Serial.println( )
还要多 !!
(第二) 进出函数大约用 2 us (以 16MHz UNO 为例)
调用函数 4 clock, 返回也是 4 clock,
进入之后关于对寄存器进行堆栈操作部分,
包括开头 push 寄存器, 返回前 pop 寄存器,
这样总共大约 30 clock,
这就是之前我说的调用函数成本大约 2 micro second
(以 16MHz UNO, 32 clock = 2 us )
所以, 每多一次 Serial.print 浪费 2 us
但是, 每省一个 Serial.print 就必须用一个 + 串接,
这大约浪费额外的 十次函数调用与 10 个函数运行时间 !
至少浪费 20 micro second, 十几倍ㄟ !
(第三)关于每个 + 串接 浪费至少 20 us 的说明
前面的字符串串接动作, 每个 + 串接
则是调用更多次的函数:
(a)先依据 + 右边 type 调用不同类似如下的 String: perator+( )
StringSumHelper & operator + (const StringSumHelper &lhs, float num)
{
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
if (!a.concat(num)) a.invalidate();
return a;
}
(b)里面你看到了, 它调用 a.concat(num)
(c)再来看看 String::concat( )
unsigned char String::concat(float num)
{
char buf[20];
char* string = dtostrf(num, 4, 2, buf);
return concat(string, strlen(string));
}
(d)看到了, 它先调用 dtostrf( ) 处理实数, 把 float 转为小数后两位的字符串,
这处理 float 类似"%.2f" 格式化很耗费时间! 不过也没办法啰 :-)
(e)还有, 又调用一次 concat(string, strlen(string));
(f)请注意, 在这句里面调用一次 strlen(string) 计算字符串长度 !
看过 strlen( ) 源代码你就会发现它有多笨,
它会逐字一直看, 边看边 +1, 直到看到一个 NULL 也就是一个 byte 内容是 0为止!
(g)再来看看那 String::concat(String, len)
unsigned char String::concat(const char *cstr, unsigned int length)
{
unsigned int newlen = len + length;
if (!cstr) return 0;
if (length == 0) return 1;
if (!reserve(newlen)) return 0;
strcpy(buffer + len, cstr);
len = newlen;
return 1;
}
(h)看到没, 它去调用 reserve(newlen)
在该 reserve 内作法我大概说一下(程序码就略去)
(h1)看看 buffer 保留的空间是否 >= newlen,
是则立即返回
(h2)如果不够, 去调用 malloc( ) 要一块新的够大的 RAM buffer
这时如果要不到不会报错, 直接返回!(当然这样有问题, 可是又能怎样呢?)
调用 strcpy( )把原先 buffer 的都逐 char 复制过去
把原先 buffer 位置暂时记在 tmps
把 buffer 改指向新的 RAM buffer
把 tmps 指针指过去, 就是原先字符串占用的 RAM 还给系统
(i)最后再次调用 strcpy 把 cstr 复制到原先字符串的尾巴: buffer + len 处 !
看到没 !?
你省了一次 Serial.print 的调用,
然后却换来多耗费了大约十次的调用, 省了 2 us, 却浪费了至少 20 us !!!
(第四) 每次的 Serial.print 并不是真正送出 char,
所以没有时钟时序 clock 的问题, 那不是 Serial.print 管的 !
(4A)先来看看 Serial.print 如果参数是 C++ 的 String:
(参看 WString.cpp,
在 IDE 的 hardware\arduino\cores\arduino 内)
size_t Print::print(const String &s)
{
return write(s.c_str(), s.length()); //这就是 (4B) 说的函数
}
看到没, 它调用 write( C++ String 的内部, 该字符串长度);
(4B)如果 Serial.print( C_Style 的字符串 ):
size_t Print::print(const char str[])
{
return write(str); // 这就是 (4C) 说的函数
}
看到没, 又多一次函数调用, 调用 write( C_Style 的字符串 );
(4C) 就是以下这 write( charArray[ ], size) 函数
size_t Print::write(const uint8_t *buffer, size_t size)
{
size_t n = 0;
while (size--) {
n += write(*buffer++); // 这就是 (4D) 说的函数
}
return n;
}
最啥呢 ?
原来是用一个 while Loop,
把字符串从头一个 char 一个 char 传给函数 write( 一个 char );
换句话说, 已经接近尾声,
每个 char 都必须调用一次函数 write( char );
(4D)看看这 write(char) 函数
因为 Serial 所用的 HardwareSerial 改写了这函数,
所以这次不可以看 Print::write(char), 要改看 HardwareSerial::write( )
P.S. 其它前面说的都是看 Print:: 的, 因为 HardwareSerial 建立在 Print 上!
size_t HardwareSerial::write(uint8_t c)
{
int i = (_tx_buffer->head + 1) % SERIAL_BUFFER_SIZE;
// If the output buffer is full,
// we can ONLY WAIT ...
while (i == _tx_buffer->tail)
;;; // 如果 tail 都没变化表示 ISR( ) 没送出一个 char 以便空出一个位置
///
_tx_buffer->buffer[_tx_buffer->head] = c;
_tx_buffer->head = i;
///
sbi(*_ucsrb, _udrie);
// clear the TXC bit -- "can be cleared by writing a one to its bit location"
transmitting = true;
sbi(*_ucsra, TXC0);
return 1;
}
看到没, 要印出的每个 char 或 byte 都要到这函数来 !!
它的工作就是把 c 塞入串口的输出缓存区 !
如果缓存区已满了, 就只能这样:
while (i == _tx_buffer->tail)
;;; // 如果 tail 都没变化表示 ISR( ) 没送出一个 char
这个等待会 Loop 到缓存区的 tail 有改变为止!
tail 是因为另一个中断程序 ISR( )把一个 char 送出然后修改了 tail;
所以,
如果在我们写的 ISR( ) 内调用 Serial.print 很危险,
因为如果因我们的 Serial.print( ) 导致 buffer 缓存区满了,
那程序就死了!
原因是在 ISR( ) 内中断自动禁止(除非你有故意打开),
然而缓存区满了之后, 中断如果被禁止,
就没办法把缓存区的送出任何一个 char,
于是我们写在 ISR( ) 内的 Serial.print( ) 会因为该 while( );;; 回不来 !!
既然 Serial.print 回不来, 那我们写的 ISR( ) 就不会结束,
ISR( ) 不结束, 则中断维持在禁止状态, 不会自己打开;
没有中断, 就没有 ISR( ) 能把缓存区的 char 送出 !!!
(4E)在刚刚的 HardwareSerial::write( char )
你会发现唯一与硬件似乎比较有关的只有:
sbi(*_ucsrb, _udrie);
transmitting = true;
sbi(*_ucsra, TXC0);
这三句简单说就是启动(也可能原先已经启动)相关 TX 的中断 ISR( ),
通知真正负责送出的 ISR( ) 一定有char要送出!
(在该 ISR( )内, 如果发现缓存区已经空了,
会把负责送char的 ISR( )自己关闭, 所以这边要打开该中断)
结论,
(1)除非你原先就是一个字符串,
否则为了用一个 Serial.print( )取代原先数个 Serial.print,
使用 + 做串接将耗掉十几倍于原先多个 Serial.print 所浪费函数调用的时间!
(2)报告元老大神兄台,
我写这么多又这么精彩, 给我加威望啦, 因为我积分太低需要加分 {:soso_e128:} :-) |