求助 arduino +mpu6050串口输出数据的频率怎么提高
先用官网上的例子MPU6050_raw测试了下,发现大概11毫秒输出一次结果,所以采样频率只有100Hz。但是我用它来测振动,所以采样频率要尽量高。我把DLPF_CFG设置为0,SMPLRT_DIV设置为0,所以按照手册上的公式,mpu6050的采样频率是8kHz。但是执行了下程序,发现主程序loop()还是要11毫秒左右才输出一次结果。所以还是受限于arduino执行loop()的时间,有什么办法能够让他更快的输出结果吗?用中断可以吗?不太懂中断,所以求教各位。loop()里的代码如下:
void loop() {
// read raw accel/gyro measurements from device
t1=millis();
accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
// these methods (and a few others) are also available
//accelgyro.getAcceleration(&ax, &ay, &az);
//accelgyro.getRotation(&gx, &gy, &gz);
#ifdef OUTPUT_READABLE_ACCELGYRO
// display tab-separated accel/gyro x/y/z values
Serial.print("a/g:\t");
Serial.print(ax/2048.0+0.09); Serial.print("\t");
Serial.print(ay/2048.0+0.02); Serial.print("\t");
Serial.print(az/2048.0-0.01); Serial.print("\t");
Serial.print(gx/131.0); Serial.print("\t");
Serial.print(gy/131.0); Serial.print("\t");
Serial.println(gz/131.0);
#endif
t2=millis();
e=t2-t1;
Serial.print("e=" );Serial.println(e);
}
把前面的Serial.print()屏蔽掉应该会提高数度,串口打印会影响循环速度,或提高串口速率。 前面设置ARDUINO的波特率的时候提高点。波特率低了是这样的。。。
串口语句这么写是比较易懂,但是执行多遍效率就是问题了,可以考虑输出一行,用逗号或者其他符号分割。
可以参考 GPS现行的主要数据协议《NMEA-0183》
一个长串口输出语句要比多行反复输出要强不少,另外上位机编写其实也更简单。 darkorigin 发表于 2015-4-7 13:17 static/image/common/back.gif
前面设置ARDUINO的波特率的时候提高点。波特率低了是这样的。。。
串口语句这么写是比较易懂,但是执行多遍 ...
谢谢,这样的确有用,学习了。 darkorigin 发表于 2015-4-7 13:17 static/image/common/back.gif
前面设置ARDUINO的波特率的时候提高点。波特率低了是这样的。。。
串口语句这么写是比较易懂,但是执行多遍 ...
大神你说的"串口语句...执行多遍效率就是问题了"
这句我不太同意 :-)
因为其实楼主的问题在根本来不及打印 !
即使波特率调高到 115200,
每秒理论上只可打印 11520 char,
(115200 / (1 start + 8 data bit + 1 stop) = 115200/10 = 11520 char)
但实际因 code 执行也要时间, 大约只有10000 char,
也就是 1ms 最多只能印出大约 10 char,
可是楼主想要以 8KHz 的频率打印,
意思是每 1 /8K 秒 = 大约0.125 ms 就想要印出七个 long/float,
这至少大约 30 char,
也就是说 1ms 想要印出 240 char, 这怎可能呢?
那就每 1ms 就积欠了 230 char 来不及印出 !!
Serial.print( ) 在 Output buffer 未满(默认 64 char)之时,
每次调用 Serial.print( ) 几乎会立即返回(因为只是做 copy 到 buffer),
但是一旦 Buffer 满了(尚未真正印出的超过 64 char之时),
Serial.print( ) 就不会返回,
它会一直等待到 Output Buffer 有空出的位置才逐一 copy 过去,
等到全部 print("...")的 cahr 都 copy 进去 Buffer 才会返回!
至于 Output Buffer 的char则会由中断处理检查寄存器,
逐字的送出并逐渐清出 Output Buffer (是一个环状的 Ring Buffer),
也就是说
用一句 Serial.println("ABC123gggyyy");
与写成四句:
Serial.print("ABC");
Serial.print("123");
Serial.print("ggg");
Serial.println("yyy");
这样差别只有函数调用与返回的时间,
以这来说总共大约只是多 3 *( 2 去 + 2回) = 12 micro seconds
相对于 Serial 传送的慢速几乎可以忽略 :-)
tsaiwn 发表于 2015-4-8 00:53 static/image/common/back.gif
大神你说的"串口语句...执行多遍效率就是问题了"
这句我不太同意 :-)
因为其实楼主的问题在根本来 ...
第一 。我不是大神。。。毕业接近10年了 之前学的东西都基本还给老师了。 当年学的也只是单片机汇编
第二 串口输出函数 不仅仅是简单的输出。需要按照时钟的时序。
第三 所有的高级语言需要被编译器编译成 汇编语言,再又链接程序完成编译过程。所以很多时序以及堆栈等操作我们是看不见的 会有一堆POP,PUSH JMP JZ JNZ,MOV之类的代码。 反复的输出效率明显是低于一行的。
至于节约多少时间 我没法说 因为我当时学的汇编还是 Z80和MCS51系列单片。 具体问题具体分析。
至于来不来得及输出 可以通过试验测得。通过串口监视器能看到收到的数据即可证明是否缓冲区溢出。
但是很明显 调用函数 用汇编的表达 都需要先对目前的寄存器进行堆栈操作(原理就是用PUSH保存目前工作状态,函数结束之后通过pop继续执行后续代码)这么搞起来 指令周期就长了很多 CPU需要反复的寄存器进出栈。还要进行相应的输出工作。
这个不同于我们常见的PC.因为PC可以形象的认为是一堆单片机,一堆运算器和一个中央处理器的组合体。
常见的PC编程面对的是OS的核心 很多任务 OS会分配给驱动程序。驱动程序指挥各种设备协同运行。(比如传统的猫 就有硬猫和软猫之分 硬猫自带处理芯片直接编码 软猫只提供硬件接口 由CPU来完成通讯的编码工作)
在单片机上,基本所有操作都需要单片机核心自己完成。
PS. 其实我技术也是荒废了多年了纯属浅见 还有就是欢迎讨论。 技术只有讨论才能提高。 tsaiwn 发表于 2015-4-8 00:53 static/image/common/back.gif
大神你说的"串口语句...执行多遍效率就是问题了"
这句我不太同意 :-)
因为其实楼主的问题在根本来 ...
我上面的帖子 提出2个改善建议
1. 和楼上一样的意见 就是提高波特率。避免因波特率导致的通讯瓶颈
2. 通过合并很多Serial.print( ); 减小CPU处理工作量。 CPU对于常规的 数值运算 字符串运算 逻辑运算效率都是很高的 有直接的机器指令完成。很多单片只用几个单片机周期即可完成。但是对于Serial.print( );这类高级语言 需要协调时序,需要堆栈操作,需要实际操作对应寄存器。所以效率自然会低。
不恰当的比方就是能就近走高速或者专线就不走市内路线等红灯。 darkorigin 发表于 2015-4-8 13:25 static/image/common/back.gif
第一 。我不是大神。。。毕业接近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::operator+( )
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;
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 = 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:}:-) tsaiwn 发表于 2015-4-8 16:41 static/image/common/back.gif
元老兄台说的没错
(不叫大神改叫元老兄台:-) :-)
少调用函数可以省一点点 clock
额avr底层我确实不懂呵呵~~~涨姿势啊~~~:lol
加威望 加金币 只能等版主来弄吧。HOHO 您好,请问您解决了采样频率的问题了吗,我现在最多能达到800,我希望加速度计能达到1000
页:
[1]