极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 21746|回复: 9

求助 arduino +mpu6050串口输出数据的频率怎么提高

[复制链接]
发表于 2015-4-6 18:08:07 | 显示全部楼层 |阅读模式
先用官网上的例子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);
   
}


回复

使用道具 举报

发表于 2015-4-6 22:52:09 | 显示全部楼层
把前面的Serial.print()屏蔽掉应该会提高数度,串口打印会影响循环速度,或提高串口速率。
回复 支持 反对

使用道具 举报

发表于 2015-4-7 13:17:55 | 显示全部楼层
前面设置ARDUINO的波特率的时候提高点。波特率低了是这样的。。。
串口语句这么写是比较易懂,但是执行多遍效率就是问题了,可以考虑输出一行,用逗号或者其他符号分割。
可以参考 GPS现行的主要数据协议《NMEA-0183》
一个长串口输出语句要比多行反复输出要强不少,另外上位机编写其实也更简单。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-4-7 21:33:15 | 显示全部楼层
darkorigin 发表于 2015-4-7 13:17
前面设置ARDUINO的波特率的时候提高点。波特率低了是这样的。。。
串口语句这么写是比较易懂,但是执行多遍 ...

谢谢,这样的确有用,学习了。
回复 支持 反对

使用道具 举报

发表于 2015-4-8 00:53:24 | 显示全部楼层
darkorigin 发表于 2015-4-7 13:17
前面设置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 传送的慢速几乎可以忽略 :-)

回复 支持 反对

使用道具 举报

发表于 2015-4-8 13:25:21 | 显示全部楼层
tsaiwn 发表于 2015-4-8 00:53
大神你说的"串口语句...执行多遍效率就是问题了"
这句我不太同意 :-)
   因为其实楼主的问题在根本来 ...

第一 。我不是大神。。。毕业接近10年了 之前学的东西都基本还给老师了。 当年学的也只是单片机汇编
第二 串口输出函数 不仅仅是简单的输出。需要按照时钟的时序。
第三 所有的高级语言需要被编译器编译成 汇编语言,再又链接程序完成编译过程。所以很多时序以及堆栈等操作我们是看不见的 会有一堆POP,PUSH JMP JZ JNZ,MOV之类的代码。 反复的输出效率明显是低于一行的。
至于节约多少时间 我没法说 因为我当时学的汇编还是 Z80和MCS51系列单片。 具体问题具体分析。
至于来不来得及输出 可以通过试验测得。通过串口监视器能看到收到的数据即可证明是否缓冲区溢出。
但是很明显 调用函数 用汇编的表达 都需要先对目前的寄存器进行堆栈操作(原理就是用PUSH保存目前工作状态,函数结束之后通过pop继续执行后续代码)这么搞起来 指令周期就长了很多 CPU需要反复的寄存器进出栈。还要进行相应的输出工作。
这个不同于我们常见的PC.因为PC可以形象的认为是一堆单片机,一堆运算器和一个中央处理器的组合体。
常见的PC编程面对的是OS的核心 很多任务 OS会分配给驱动程序。驱动程序指挥各种设备协同运行。(比如传统的猫 就有硬猫和软猫之分 硬猫自带处理芯片直接编码 软猫只提供硬件接口 由CPU来完成通讯的编码工作)
在单片机上,基本所有操作都需要单片机核心自己完成。

PS. 其实我技术也是荒废了多年了  纯属浅见   还有就是欢迎讨论。 技术只有讨论才能提高。
回复 支持 反对

使用道具 举报

发表于 2015-4-8 13:29:41 | 显示全部楼层
tsaiwn 发表于 2015-4-8 00:53
大神你说的"串口语句...执行多遍效率就是问题了"
这句我不太同意 :-)
   因为其实楼主的问题在根本来 ...

我上面的帖子 提出2个改善建议
1. 和楼上一样的意见 就是提高波特率。避免因波特率导致的通讯瓶颈
2. 通过合并很多  Serial.print( ); 减小CPU处理工作量。 CPU对于常规的 数值运算 字符串运算 逻辑运算效率都是很高的 有直接的机器指令完成。很多单片只用几个单片机周期即可完成。但是对于Serial.print( );这类高级语言 需要协调时序,需要堆栈操作,需要实际操作对应寄存器。所以效率自然会低。
不恰当的比方就是能就近走高速或者专线就不走市内路线等红灯。
回复 支持 反对

使用道具 举报

发表于 2015-4-8 16:41:57 | 显示全部楼层
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:}  :-)
回复 支持 反对

使用道具 举报

发表于 2015-4-8 23:25:42 | 显示全部楼层
tsaiwn 发表于 2015-4-8 16:41
元老兄台说的没错
(不叫大神改叫元老兄台  :-) :-)
少调用函数可以省一点点 clock

额avr底层我确实不懂呵呵~~~涨姿势啊~~~
加威望 加金币 只能等版主来弄吧。HOHO
回复 支持 反对

使用道具 举报

发表于 2016-3-18 22:10:14 | 显示全部楼层
您好,请问您解决了采样频率的问题了吗,我现在最多能达到800,我希望加速度计能达到1000
回复 支持 反对

使用道具 举报

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

本版积分规则

Archiver|联系我们|极客工坊

GMT+8, 2026-6-14 21:15 , Processed in 0.040952 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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