2

I've started to prototype something using an Arduino Mega and Yún Shield.

It's simple. It reads an ADC at 50Hz and sends data to a server through a socket. But because of that combination, I need to use Bridge to send data through the socket. It's very slow.

Should I change the Arduino Mega for a Leonardo or a Due to get better performance?

Here is my code, maybe I did something wrong.

Arduino code:

#include <Bridge.h> 
#include <Process.h>
#include <Time.h>
#include <TimeLib.h>
#include <stdlib.h>

boolean interruptFlag = 0;
int HD = A0;
int HG = A1;
int CD = A2;
int CG = A3;
int ECG = A5;
String analogSignals = "";
static char outstr[15];
int count = 0;
float x = 0.0;

void setup() {
  // Bridge startup.
  Bridge.begin();  
  // Initialize Bridge values.
  Bridge.put("data", "");  
  noInterrupts(); // Stop interrupts.
  TCCR1A = 0; // Set entire TCCR1A register to 0.
  TCCR1B = 0; // Same for TCCR1B.
  TCNT1  = 0; // Initialize counter value to 0.
  // Set compare match register for 50Hz increments.
  OCR1A = 39999; // = 16000000 / (8 * 50) - 1 (must be <65536)
  // Turn on CTC mode.
  TCCR1B |= (1 << WGM12);
  // Set CS12, CS11 and CS10 bits for 64 prescaler.
  TCCR1B |= (0 << CS12) | (1 << CS11) | (1 << CS10);
  // Enable timer compare interrupt.
  TIMSK1 |= (1 << OCIE1A);
  interrupts(); // Allow interrupts.
  // Launch Python script.
  /*Process p;
  p.begin("python");
  p.addParameter("/root/test/send.py");
  p.run();*/
}

ISR(TIMER1_COMPA_vect) {
  if (!interruptFlag) {
    interruptFlag = 1;    
  }  
}

void loop() {
  if (interruptFlag) {
    writeAnalogSignals();
    interruptFlag = 0;
  }
}

void writeAnalogSignals() {
  String HDReading = convertToVoltage(analogRead(HD));
  String HGReading = convertToVoltage(analogRead(HG));
  String CDReading = convertToVoltage(analogRead(CD));
  String CGReading = convertToVoltage(analogRead(CG));
  String ECGReading = convertToVoltage(analogRead(ECG));
  if (analogSignals.length() == 0) {
    analogSignals =  String(x, 2) + ";" + HDReading + ";" + HGReading + ";" + CDReading + ";" + CGReading + ";0.0;0.0;" + ECGReading;
  } else {
    analogSignals += "|" + String(x, 2) + ";" + HDReading + ";" + HGReading + ";" + CDReading + ";" + CGReading + ";0.0;0.0;" + ECGReading;
  }
  x += 0.02;
  count += 1; 
  if (count >= 50) {
    Bridge.put("data", analogSignals);
    analogSignals = "";
    count = 0;
  }
}
// Convert the analog reading to voltage.
String convertToVoltage(int analogSignal) {
  dtostrf(analogSignal * (5.0 / 1023),8, 6, outstr);
  return outstr;
}

Python code:

import socket
import sys
import getopt
import time

def main(argv):
    try:
        sys.path.insert(0, '/usr/lib/python2.7/bridge/')
        from bridgeclient import BridgeClient as bridgeclient
        # Create a UDP socket.
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        server_address = ('192.168.1.100', 9050)
        bridge = bridgeclient()                              
        previous = ""

        # Send data
        while 1:   
            data = bridge.get("data")   
            if data != previous:                        
                sent = sock.sendto(data, server_address)
                previous = data
            time.sleep(0.5)

    finally:
        sock.close()
  • 1
    I'm not very much into Yùns, but I think that the problem is that time.sleep(0.5) that you put. Try lowering it to e.g. 0.01 to perform a check every 10 milliseconds... If this doesn't work, maybe the problem is the overhead added; to limit this store on the arduino or the Yùn some data (e.g. 50 acquisitions) and then send them all at once. – frarugi87 Jan 19 '17 at 16:16
  • I've changed the time.sleep(0.01) and I stored 50 acquisitions, but It's slow to get 1 sec of datas. – Jonathan Anctil Jan 19 '17 at 16:30
  • You'll have to figure out where the bottleneck is. The Arduino sending the data, the yun receiving the data and/or transmitting the data over UDP. – Gerben Jan 19 '17 at 16:35
  • 1
    What do you mean by "slow"? How much is "slow" for you? And is it just delayed or you are losing packets? – frarugi87 Jan 19 '17 at 20:46
  • 1
    Well, read more. That, or figuring out how to use an ADC directly on a Linux wifi module is your best bet on getting rid of the bridge. But you also need to fix the conceptual problems in your design - data sources time things, data conduits shouldn't have program delays as that will only make timing problems worse. – Chris Stratton Jan 20 '17 at 21:06
4

Both Mega 2560 and Due can handle 4 channels of 1,440 samples per second A-to-D and the least significant byte of the system time stamp sent as a continuous stream of vectors through the USB port to Linux.

In the lab I train at, we see almost no variance in time increment between samples or between receiver read operations, indicating that the bottleneck is our deliberate delays between samples.

The Due is running at full resolution too and communicates via the native port. The receiver process is running in normal user process priority, not boosted for real time.

We use C++ for both endpoints, the embedded Arduino code and the receiver, but the C++ on the Arduino side runs just as fast as the same loop in Arduino sketch language. Python low-level reads are generally able to handle this same level of throughput with ease too, so it is not likely that your hardware or speed goals that are the problem.

I can see a few design issues right away.

  1. Don't use libraries for what one can do in just a few low level lines of code.
  2. Don't place numbers in strings. Keep them as numbers.
  3. Send the numbers as messages placed carefully into byte arrays using bitwise operations.
  4. Don't convert raw samples to voltages on the Arduino. That can be done in the receiver or elsewhere on the Linux side.
  5. Most importantly, find the various code examples that people posted on the Arduino blogs when testing USB baud rates (speed).

The lab engineers started with that speed testing code, replicated the throughput published with it, and then added A-to-D conversion and other features one step at a time.

It is recommended that you place the code under version control and then, with each change, test the speed again. That way you'll notice any bottleneck you introduce immediately and can see the exact changes that must have caused it.

1

The Bridge running in Python in Linux is the biggest problem. Open a terminal (for example with putty) and run top to see the CPU usage. Try to keep it under 50%.

A few parameters at 5 times per second with Bridge.put() is possible, but I don't recommend it, because of the CPU load in Linux. The 50Hz is way too much. Beside that you have also a Python script to send them to a server.

There is about 30 MB of fast RAM storage in /tmp and more storage if you add an SD memory card. You could do a test to write the data to a file, and upload the file every few seconds.

If you really need 50Hz, then I suggest to try the Raspberry Pi (or the ESP8266, as mentioned).

The dtostrf() function does not return a String, but a buffer. You could easily get rid of the Strings, to have more time in the Arduino. A good use ofmillis() is just as accurate as a timer. I think you can do the same without the hardware timer and without the interrupt.

ADDED: Chris Stratton pointed out that the Bridge is not the problem, so I did a small test.

Using 4 values with short names (like: "temp"). Writing them with Bridge.put() every second, and reading them in a webpage via zepto.min.js every second is a CPU load of 6% for python -u bridge.py.

Writing them 50 times per second and reading 1 time per second is a CPU load of 25% for the bridge.

Writing them 50 times per second and reading them 50 times per second does not change a lot, it adds extra load for uhttpd, resulting in a total of about 60 to 70%. That is still okay to run commands from a terminal.

This 'clean' test is different from my previous tests. It is therefore indeed possible to send data through the Bridge at 50 times per second. Sorry for my wrong answer.

  • The original suggestions here are highly mistaken and would be best ignored - following would just trade the problems of one set of fundamental misunderstandings for those of another, equally mistaken set. – Chris Stratton Jan 20 '17 at 22:13
  • 1
    You are right. However, code can be improved. It can be very informative to make changes to the code and see the result for the cpu load. The 50Hz is too much for an Arduino Yún Bridge anyway. – Jot Jan 20 '17 at 22:56
  • This is where you are fundamentally wrong - the issue is not CPU load at all, and 50 Hz is not too much if done right. The issue is with the implementation not taking the bridge into account. Getting rid of the bridge would make things simpler, but it can be done through the bridge by someone who understands communication between systems. What your proposal does is trade the asker's mistakes for your own, different set of mistakes, which put them no closer to a solution. – Chris Stratton Jan 20 '17 at 23:26
  • Thank you for correcting me. I learned something and I have added it to my answer after "ADDED". I hope you still agree with the 30Mbyte ram in /tmp and the unnecessary use of the timer, interrrupt and String object. – Jot Jan 21 '17 at 8:38
  • Hi @Jot, could you provide me the code you did to test it? What did you do different from my implementation? – Jonathan Anctil Jan 23 '17 at 19:15
0

Should I change the Arduino Mega for a Leonardo or a Due to get better performance?

You didn't quite say what kind of performance gains you had in mind. Looking at your code, it doesn't have any particular needs to justify a fancier Arduino.

With that said, I wanted to highlight one particular aspect of ATmega32u / Leonardo that in my view made it a particularly interesting chip for mixed signal application: its ADC unit features a PGA (1x, 10x, 200x) in differential mode with bipolar readings.

it can be quite handy for some applications.

0

@JonathanAnctil, @ChrisStratton, I have done some more tests, and the Bridge.put takes an average of 7 milliseconds in the sketch. For 5 values at a rate of 50Hz, that alone takes 1.7 seconds. The Bridge can not keep up with that rate, but the overall cpu load in linux is still under 50%. Therefor the cpu load does not indicate if everything is running okay.

Because I was looking at the cpu load, I took the wrong assumption that it was possible. These tests confirm my previous experiences that the Bridge is too slow.

I have installed extra packages in linux, but I think they will not influence the slow Bridge.put.

With more tests with Bridge.put I learned that Bridge.put takes 4ms, plus 0.3ms per character for the key name and the value text, plus the delay caused by things running at the other side of the Bridge in linux.

The value must be a zero terminated string, it is not possible to send binary data. Suppose turning it into readable hex values, then 20 bytes are needed for the value. With the key name as a single character, and a value of 20 bytes, it takes 10 ms. However, then the values have to be retrieved (and converted) at the other side of the Bridge. I assume that Python will also slow down the Bridge.put, just like Javascript does.

This is my test sketch.

// Showing the webpage on the other side of the Bridge with zepto.min.js 
// and 50 updates per seconds slows down the sketch, because the Bridge.put
// in the sketch will take a lot more time.
// A Bridge.put takes about 7 to 260 ms when the webpage is in a browser 
// with 50Hz updates.
// A Bridge.put takes about 7 ms when the webpage is not shown in a browser.
// A itoa takes about 13 us.
//
// When showing the webpage with 50Hz updates:
//     Only the counter : 43 seconds for 1000 counts = 23 Hz
//     With 6 analog inputs and the free ram : 342 seconds for 1000 counts = 3 Hz
// When showing the webpage with once per 5 seconds updates:
//     Only the counter : 20 seconds for 974 counts = 49 Hz
//     With 6 analog inputs and the free ram : 50 seconds for 985 counts = 20 Hz

#include <Bridge.h>

Process yunProcess;

unsigned long previousMillis;

#define NUM_PINS 6
const int pins[NUM_PINS] = { A0, A1, A2, A3, A4, A5, };
const char * names[NUM_PINS] = { "A0", "A1", "A2", "A3", "A4", "A5" };

void setup() 
{
//  SerialUSB.begin(115200);
//  while(!SerialUSB);
//  SerialUSB.println("Started");
  Bridge.begin();
//  SerialUSB.println("Bridge is running");
}

void loop() 
{
  unsigned long currentMillis = millis();

  // A software timer of 50Hz.
  if(currentMillis - previousMillis >= 20UL)
  {
    previousMillis = currentMillis;

    // The fastest way to turn an integer into text is with itoa.
    // The function itoa takes about 13us.
    // A buffer is used for itoa.
    char buffer[20];

    for(int i=0; i<NUM_PINS; i++)
    {
      int adc = analogRead(pins[i]);
      itoa(adc, buffer, 10);
      Bridge.put(names[i], buffer);
    }

    int freeram = freeRam();
    itoa(freeram, buffer, 10);
    Bridge.put("freeRam", buffer);

    // a counter.
    static int count;
    count++;
    itoa(count, buffer, 10);
//    unsigned long t1 = micros();    
    Bridge.put("counter", buffer);
//    unsigned long t2 = micros();
//    SerialUSB.println(t2 - t1);

//    SerialUSB.println(count);
  }
}


int freeRam () 
{
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

And my test webpage is:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script type="text/javascript" src="zepto.min.js"></script>
<script type="text/javascript">

function refresh() 
{
  $.getJSON("/data/get", function(json) 
  { 
    document.getElementById('myA0').innerHTML=json.value.A0;
    document.getElementById('myA1').innerHTML=json.value.A1;
    document.getElementById('myA2').innerHTML=json.value.A2;
    document.getElementById('myA3').innerHTML=json.value.A3;
    document.getElementById('myA4').innerHTML=json.value.A4;
    document.getElementById('myA5').innerHTML=json.value.A5;
    document.getElementById('myfreeRam').innerHTML=json.value.freeRam;
    document.getElementById('mycounter').innerHTML=json.value.counter;
  });
}

</script>
</head>

<!-- a function to refresh some values -->
<body onload="setInterval(refresh, 1000);">

<br>
<a href="/data/get">Show JSON data</a><br>
<br>

Analog Input A0 = <span id='myA0'>---</span><br>
Analog Input A1 = <span id='myA1'>---</span><br>
Analog Input A2 = <span id='myA2'>---</span><br>
Analog Input A3 = <span id='myA3'>---</span><br>
Analog Input A4 = <span id='myA4'>---</span><br>
Analog Input A5 = <span id='myA5'>---</span><br>
freeRam = <span id='myfreeRam'>---</span><br>
counter = <span id='mycounter'>---</span><br>

</body>
</html>
  • 1
    Great demonstration, thanks a lot. This confirm a lot what I saw about the Bridge! – Jonathan Anctil Jan 24 '17 at 14:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.