0

here is the problem I am facing. I have interfaced my ATmega328P with a 6-axis IMU (MPU6050 with the GY521 breakout board). I can read data through the TWI interface (Atmel's I2C) and send it to my PC (running Ubuntu) via the UART. I am using custom-built libraries for both these communication protocols, but they are pretty standard and seem to work just fine. The goal of the project is to compute orientation data from the IMU readings in real-time, say at 100 Hz.

The main problem is that I cannot log data from the device at 100 Hz (not even at 50 Hz). The orientation filter I am using (here) requires a quite high frequency and 100 Hz turned out to work fine (tested offline acquiring data from another device). Right now, I am using the 16-bit timer of the ATmega328P to sample data at 100 Hz and this seem to work, as I have added to the ISR a line to toggle the built-in LED and it looks to me that it is blinking at 100 Hz (I can barely see it turning on and off). In the same ISR, I read the values from the inertial sensor and, just to log them, send these values through the serial port. Every 10 ms (maximum), I send 9 floats (36 bytes) with a baud rate of 115200. If I use the Arduino IDE's Serial Monitor to visualize this data stream, I notice something very weird, as in the following screenshot.

https://i.stack.imgur.com/Bj35Y.jpg

As you notice taking a look at the timestamps, there is a common 33 ms delay every 2 or 3 sets of samples received. Moreover, I get roughly the 60% of the data. For example, an acquisition of 10 seconds only gets me less than 600 samples (per each variable) instead of 1000. Moreover, I tested the same sending only one variable through the UART (i.e. only a single float, 4 bytes) and this results in the same behavior!

By the way, I am exploiting the following to send each byte (char) via the UART interface.

void writeCharUART(char c) {

    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = c;

}

Even though my ISR runs at 100 Hz (LED blinking seem to confirm that), data loss may occur at the level of the TWI transmission. To prove that, I modified the code of the ISR to send just a normal char (T) instead of data from the MPU and I got a similar behavior. Something like this:

00:10:05.203 -> T
00:10:05.203 -> T
00:10:05.236 -> T
00:10:05.236 -> T
00:10:05.236 -> T
00:10:05.236 -> T
00:10:05.269 -> T

So, I guess there is something wrong with the UART library and I actually sample at 100 Hz, but the logging frequency is much lower (and not constant). How can I solve this issue and/or debug the UART library? Do you see other reasons to justify this issue?


EDIT 1

As pointed out in the comments, it seems to be a problem of the receiving software that limits the frequency to ~30 Hz by some sort of buffering. To confirm that, I programmed the ATmega328P with the following code (this time using the IDE).

void loop() {
    Serial.println("T");
}

At first, I thought there was no delay this time, but I could find it after 208 samples. So, there are ~200 samples received at the same timestamp and another bunch of samples after 33 ms. This may be proof that the receiving software introduces this delay.

I also tested a simple serial monitor that I had developed in C and, even though there is no timestamp functionality, I am also loosing samples if I fix the duration of the acquisition sampling at 100 Hz. My serial monitor is based on the termios.h library, but I could not find any documentation about its way of buffering incoming data.

  • How do you get the timestamps? It wouldn't be some timer with a 33ms granularity, by any chance? – Martin James Sep 03 '19 at 06:58
  • They come from the Serial Monitor built-in in the Arduino IDE. I guess you may be right! – Mattia Pesenti Sep 03 '19 at 08:58
  • "it looks to me that it is blinking at 100 Hz" Likely the human eye is too slow. Use a scope - you cannot work with these kind of products without tools. – Lundin Sep 03 '19 at 09:22
  • "I send 9 floats (36 bytes)" The AVR is not only one of the slowest legacy CPUs still in production, it also lacks a FPU. Forcing it to eat floating point numbers will introduce software floating point libs that kill everything that is realtime performance. You _don't need_ floating point for this project! – Lundin Sep 03 '19 at 09:25
  • @Lundin unfortunately I don't have a scope, so I have to debug it via software – Mattia Pesenti Sep 03 '19 at 12:51
  • If you are a hobbyist, I suppose you can buy a cheap pulse counter like [this one](https://www.elfa.se/en/pulse-counter-kuebler-130-012-853/p/13754631), then just clock 10 seconds with a watch and see how many pulses you got. Or build one yourself using a 74HC binary counter IC and some LEDs. 8th LED will blink 128 times slower than the actual frequency. – Lundin Sep 03 '19 at 14:36

2 Answers2

0

There are two issues here:

  1. You are missing messages. You checked the sample rate just with your eyes and told us that you can still see a very fast blinking. Depending on the colour of your LED, the ambient light, your physical state, and your eyes this could mean anything from 30 Hz to 100 Hz.

    • I would not trust my eyes to estimate and rather use an oscilloscope or a frequency counter to measure.
    • You could reduce the frequency of the LED blinking to 1Hz or even lower by dividing in software. Such a low frequency can be measured by hand via a stop watch. For example count 30 blinks and check the time needed for this.
    • Add a counter to the message and increment it with each message. You will see it right away if you're losing data.
  2. The timestamps seem to indicate that the messages are "clustered" at about 30 Hz.

    • I'm guessing that the source of the timestamp in running at 30 Hz. So it can not give you more accurate values.
the busybee
  • 10,755
  • 3
  • 13
  • 30
  • I guess you're right about the frequency, I don't have an oscilloscope unfortunately. **Assuming** I am not loosing data, how can I log data keeping my 100 Hz sampling rate? – Mattia Pesenti Sep 03 '19 at 09:03
  • See point 1: Are you sure you are sampling at 100 Hz? If not, please try the second suggestion. – the busybee Sep 03 '19 at 11:38
  • I tried that, I increment a counter in the ISR to reach a period of 1 second (keeping the 100 Hz execution) and I get the LED blinking at 1 Hz. I tried this both **with and without** the code to read the MPU, with the same result. On the other hand, when I tried to add also the data stream to the ISR, the LED blinking period stretched up to 1.5 seconds (on/off, so 3 seconds total)! Is the baud rate too low? I guess there is a combination of factors both at the source and at the end of the UART tranmission. – Mattia Pesenti Sep 03 '19 at 12:50
0

I kind of solved my issues! First of all, thanks to the comments I have checked that my ISR was correctly running at 100 Hz. Doing so, I could be sure that the problem where somewhere else, namely in the UART communication.

I found this very helpful: Linux, serial port, non-buffering mode Apparently, the Serial Monitor provided by the Arduino IDE uses exploits the termios.h library and uses its default settings. I checked also the user manual and switched to the polling-read mode. Quoting from the user manual

If data is available, read(2) returns immediately, with the lesser of the number of bytes available, or the number of bytes requested. If no data is available, read(2) returns 0.

Hence, I switched back to my serial monitor code and changed the initPort() function adding the following lines of code.

struct termios options;
(...)
options.c_cc[VTIME] = 0;
options.c_cc[VMIN]  = 0;

I noticed right away a much higher data frequency in the terminal. I kept the 1 Hz LED blinking in the ISR and there is no period stretching. Moreover, an acquisition of 10 seconds this time gave me roughly 1000 samples per variable, consistent with a sampling rate of 100 Hz.

On the AVR side, I also changed the way I send data through the UART. Before, I was sending 9 floats like this:

sprintf(buffer, "%f, %f, %f", value1_x, value1_y, value1_z);
serial_print(buffer); // no "\n" sent here
sprintf(buffer, "%f, %f, %f", value2_x, value2_y, value2_z);
serial_print(buffer); // again, no "\n" sent
sprintf(buffer, "%f, %f, %f", roll, pitch, yaw); 
serial_println(buffer); // "\n" is sent here once the last data byte is sent

Now, I replaced all this with a single call to the function serial_println() and I write only 6 floats to the buffer.