London, UK
Offline
Newbie
Karma: 1
Posts: 41
Arduino rocks
|
 |
« on: October 13, 2012, 06:45:35 pm » |
Hello, I wondered if anyone could give me some help on why my serial performance is very slow. I have an Arduino driving an LED matrix, receiving data from a Raspberry Pi using Python and pyserial. The serial data is sent over a USB cable plugged between the Pi and Arduino. A sketch on the Arduino listens for bytes, one control byte, then 1536 pixel data bytes. (The LED matrix is 1024 RGB pixels at 4 bit colour. I pack two 4 bit values into each byte, so each RGB pixel is 1.5 bytes. Therefore an entire frame is sent in 1024 pixels x 1.5 bytes per pixel = 1536 bytes.) If my maths is right... Running at 115200 baud, I make that roughly 14400bytes/s - enough that I should see a maximum of 9 frames per second. However I'm only seeing roughly two frames per second. Even at 230400 baud things are 2 or 3 fps. Any ideas appreciated! Here is the Python send code: import serial from time import sleep
#setup serial port port = "/dev/ttyACM0" ser = serial.Serial(port, 230400, timeout=1) sleep(1) #wait for serial
#fill screen with white then black
loop=0 while (loop < 100):
#draw totally white frame (all bytes value 11111111) #send control byte: 1 = draw frame ser.write(chr(1)) #send screen data - 2 pixels sent in 3 bytes. 1024 pixels in total = 512 loops of 3 bytes (1536 bytes total) c=0 while (c < 512): ser.write(chr(255)) ser.write(chr(255)) ser.write(chr(255)) c=c+1
#draw totally black frame (all bytes value 00000000) #send control byte: 1 = draw frame ser.write(chr(1)) #send screen data - 2 pixels sent over 3 bytes. 1024 pixels in total = 512 loops of 3 bytes (1536 bytes total) c=0 while (c < 512): ser.write(chr(255)) ser.write(chr(255)) ser.write(chr(255)) c=c+1
loop=loop+1
And here is the Arduino receive code. #include "RGBmatrixPanel.h" //3rd party matrix driver
//define pins #define A 5 #define B 4 #define C 3 #define D 2 #define CLK 10 #define LAT 9 #define OE 7 RGBmatrixPanel matrix(A, B, C, D, CLK, LAT, OE, true);
void setup() { Serial.begin(230400); matrix.begin(); }
void loop() {
byte incomingByte = 0; if (Serial.available() > 0) { //if > 1 get 1st byte which is control byte
incomingByte = Serial.read();
//if control byte is 1, draw frame switch ( int(incomingByte) ) { case 1: drawScreen(); break; default: Serial.println("unknown cmd"); } } }
void drawScreen() {
//Total bytes to receive is 1536 byte x = 0; byte y = 0; byte r = 0; byte g = 0; byte b = 0; byte data = 0;
while (y < 32 ) {
if (Serial.available() >= 3) { //read 3 bytes (2 pixels) //read 1st byte and split into nibbles data = byte (Serial.read()); r = data & B00001111; g = data >> 4;
//read 2nd byte and split into nibbles data = byte (Serial.read()); b = data & B00001111;
//draw pixel1 matrix.drawPixel(x, y, matrix.Color444(r,g,b)); //next pixel x++;
r = data >> 4; //read 3rd byte and split into nibbles data = byte (Serial.read()); g = data & B00001111; b = data >> 4;
//draw pixel2 matrix.drawPixel(x, y, matrix.Color444(r,g,b));
//next pixel if (x == 31){ x=0; y++; } else { x++; } } } matrix.swapBuffers(false); }
|
|
|
|
« Last Edit: October 13, 2012, 06:47:26 pm by mrnick1234567 »
|
Logged
|
|
|
|
|
Seattle, WA USA
Online
Brattain Member
Karma: 315
Posts: 35519
Seattle, WA USA
|
 |
« Reply #1 on: October 13, 2012, 06:55:25 pm » |
If my maths is right... Running at 115200 baud, I make that roughly 14400bytes/s It isn't. Each bytes requires sending 10 bits, not 8. switch ( int(incomingByte) ) { Why are you performing this cast? The switch function is perfectly happy with a byte as the argument. You're receiver is not waiting for three bytes. It is looping through the while loop many times before the third byte arrives. Are you monitoring what the Arduino sends back? I'm guessing that there are a lot of messages being sent back. That is why your receive rate is so low.
|
|
|
|
|
Logged
|
|
|
|
|
London, UK
Offline
Newbie
Karma: 1
Posts: 41
Arduino rocks
|
 |
« Reply #2 on: October 14, 2012, 04:22:36 am » |
Hi Paul, thanks for taking the time to reply.
I was unaware serial required 10 bits per character, OK so that knocks the maximum frame rate down a little. The cast to Int in the switch statement was there as I didn't know switch could handle bytes, I'll take it out.
How would you suggest the receiver code waits for the 3 bytes, I wasn't sure what else it could do other than loop round waiting for data. Is that very inefficient?
As for monitoring what the receiver sends back, there is actually nothing being sent back. I stripped out all serialprint lines I had in there for debugging as I thought that might be the cause of the issue.
Cheers Nick
|
|
|
|
|
Logged
|
|
|
|
|
Seattle, WA USA
Online
Brattain Member
Karma: 315
Posts: 35519
Seattle, WA USA
|
 |
« Reply #3 on: October 14, 2012, 07:50:03 am » |
How would you suggest the receiver code waits for the 3 bytes while(Serial.available() < 3) { // Do nothing } Is that very inefficient? Yes, very. The alternative is to simply deal with data as it arrives, storing each piece until you have enough to proceed. Then use that data, and reset the array index to start storing again.
|
|
|
|
|
Logged
|
|
|
|
|
London, UK
Offline
Newbie
Karma: 1
Posts: 41
Arduino rocks
|
 |
« Reply #4 on: October 20, 2012, 09:36:17 am » |
Thanks again for the pointers Paul, I've tweaked my Arduino code (see below) I've been experimenting with different baud rates and timing the Arduino function that receives the serial data from the Raspberry Pi. At both 115200 and 230400 baud there is no difference in the time it takes to receive and process a frame of data (1536 bytes). It's about 390 milliseconds either way. This seems strange, so I wonder if something other than the baud rate is slowing things down. Commenting out anything to do with the LED matrix drawing routines in the Arduino code made very little difference (10-20 milliseconds) so I know they're not the issue. The idle python editor on the RaspberryPi also seemed to slow things down. Running the python sender program from the command prompt was quicker and got the 390ms results above. I'd be interested to know if there any other ways I could squeeze some more performance out of the Arduino code? Is there anything obvious I'm missing ? Thanks Nick Tweaked Arduino code: #include "RGBmatrixPanel.h" //3rd party matrix driver
//define pins #define A 5 #define B 4 #define C 3 #define D 2 #define CLK 10 #define LAT 9 #define OE 7 RGBmatrixPanel matrix(A, B, C, D, CLK, LAT, OE, true);
void setup() { Serial.begin(230400); matrix.begin(); }
void loop() {
byte incomingByte = 0; if (Serial.available() > 0) { //if data, get 1st byte which is a control byte that sets what to do
incomingByte = Serial.read();
//if control byte is 1, draw frame switch ( int(incomingByte) ) { case 1: drawScreen(); break; default: Serial.println("unknown control command"); } } }
void drawScreen() {
//Receiving 32x32 pixels = 1024 pixels. Each pixel is 3x4 bit values, so each pixel = 1.5 bytes. Total bytes to receive is 1536
byte x = 0; //screen x byte y = 0; //screen y byte r = 0; //red byte g = 0; //green byte b = 0; //blue byte data = 0; //incoming byte
unsigned startTime= millis(); while (y < 32 ) { //wait for 3 bytes of data while (Serial.available() < 3) { //do nothing } //read 1st byte and split into 2 lots of 4 bits, (red and green) data = byte (Serial.read()); r = data & B00001111; g = data >> 4;
//read 2nd byte - b and r of next pixel data = byte (Serial.read()); b = data & B00001111;
//draw pixel1 matrix.drawPixel(x, y, matrix.Color444(r,g,b)); //inc for next pixel x++; r = data >> 4;
//read 3rd byte, g and b data = byte (Serial.read()); g = data & B00001111; b = data >> 4;
//draw pixel2 matrix.drawPixel(x, y, matrix.Color444(r,g,b));
//inc for next pixel or reset if x at end of row if (x == 31){ x=0; y++; } else { x++; } } //print time it took Serial.println(millis() - startTime); //when all data received, swap buffers and show screen matrix.swapBuffers(false); }
|
|
|
|
|
Logged
|
|
|
|
|
Netherlands
Offline
Tesla Member
Karma: 90
Posts: 9403
In theory there is no difference between theory and practice, however in practice there are many...
|
 |
« Reply #5 on: October 20, 2012, 10:10:57 am » |
At both 115200 and 230400 baud there is no difference in the time it takes to receive and process a frame of data (1536 bytes). It's about 390 milliseconds either way. That means that the other code - on arduino or on raspberry side - is blocking performance. Be aware that python is an interpreted language, small bug in arduino code: unsigned startTime = millis(); should be unsigned long Can you post the Raspberry code?
|
|
|
|
« Last Edit: October 20, 2012, 10:13:42 am by robtillaart »
|
Logged
|
|
|
|
|
London, UK
Offline
Newbie
Karma: 1
Posts: 41
Arduino rocks
|
 |
« Reply #6 on: October 21, 2012, 05:31:54 am » |
Hi Rob, yes that was what I was thinking. The Raspberry Pi code is in first post if you want to take a look. I\ve noticed it slows down considerably with some operations - e.g. using reading numpy arrays for screen data.
Let me know what you think.
|
|
|
|
|
Logged
|
|
|
|
|
Netherlands
Offline
Tesla Member
Karma: 90
Posts: 9403
In theory there is no difference between theory and practice, however in practice there are many...
|
 |
« Reply #7 on: October 21, 2012, 06:20:29 am » |
I missed the first post, sorry.
Looking at the python code it just pumps in all data and the arduino is probably overflown with data.
You should add some sort of handshake so that the arduino confirms the receive after every 3 bytes (== 2 RGB pixels). If that ACKnowledge is received the python program spits the next 3 bytes .
That way you at least synchronize the producer and consumer. Furthermore there should be some kind of synchronization at the start of the stream.
<idea> why not make a tuple <x,y,r,g,b> to allow you to set a random pixel on the Arduino screen?
protocol would look like RP -> A: <x,y,r,g,b> A -> RP: <ACK> </idea>
|
|
|
|
|
Logged
|
|
|
|
|
London, UK
Offline
Newbie
Karma: 1
Posts: 41
Arduino rocks
|
 |
« Reply #8 on: October 21, 2012, 07:00:47 am » |
Hey Rob,
Thanks for the ideas. Yeah it does just pump a full frame of data out, which is what I need to draw a whole screen of pixels at once (e.g. for example to display a series of frames).
When you say the Arduino is possibly being overwhelmed with data... I don't see any data missing on the frames, so it's not like bytes are being lost. I'd be interested to know how some kind of data overload would manifest itself as slow performance and no data get lost in the process.
I didn't write in in any ACK logic in as yet, as I thought that by adding more traffic things would slow down even more.
I do already have another routine as you suggest that just sets certain pixels and this does run much faster. It's just these full frames I'm having difficulty with.
|
|
|
|
|
Logged
|
|
|
|
|
Netherlands
Offline
Tesla Member
Karma: 90
Posts: 9403
In theory there is no difference between theory and practice, however in practice there are many...
|
 |
« Reply #9 on: October 21, 2012, 07:48:45 am » |
You could also implement a horizontal line of the same color to speed things up e.g. <x,y, len, r,g,b> it is a sort of run length compression.
as this introduces new types of datapackets / tuples there would be a need for an id as first byte in the packet <1,x,y,r,g,b> = setpixel <2,x1,y1,x2,y2,r,g,b> = draw any line => look for bresenham <3,x,y,radius,r,g, b> = circle <4,x,y,x,y,r,g,b> = square <5,r,g,b> = floodfill whole screen etc
|
|
|
|
|
Logged
|
|
|
|
|
Dallas, TX
Offline
Sr. Member
Karma: 10
Posts: 315
|
 |
« Reply #10 on: October 21, 2012, 09:35:16 am » |
You didn't mention it so I want to make sure, did you modify /etc/inittab and /boot/cmdline.txt to prevent other processes using /dev/ttyAMA0? edit: sorry, I see you're using a USB port. Never mind.
|
|
|
|
« Last Edit: October 21, 2012, 10:51:32 am by PapaG »
|
Logged
|
|
|
|
|
Dallas, TX
Offline
Sr. Member
Karma: 10
Posts: 315
|
 |
« Reply #11 on: October 21, 2012, 12:40:46 pm » |
robtillaart's suggestions are good, but may I make a few more? The Serial class only takes strings and buffers as input. Strings, like tuples, are immutable, so building arbitrary data packets would be done by concatenation rather than insertion. A bytearray might make more sense as you can write to indices and slices of a bytearray and Serial treats it like a buffer. I added the concept of bytearrays to your code: import serial from time import sleep
#setup serial port port = "/dev/ttyACM0" ser = serial.Serial(port, 230400, timeout=1) sleep(1) #wait for serial
#fill screen with white then black
for l in range(100): #draw totally white frame (all bytes value 11111111) ser.write("\x01") #send control byte: 1 = draw frame #send screen data - 2 pixels sent in 3 bytes. #1024 pixels in total = 512 loops of 3 bytes (1536 bytes total) white = bytearray([255, 255, 255]) for c in range(512): ser.write(white)
#draw totally black frame (all bytes value 00000000) ser.write("\x01") #send control byte: 1 = draw frame #send screen data - 2 pixels sent over 3 bytes. #1024 pixels in total = 512 loops of 3 bytes (1536 bytes total) black = bytearray([0, 0, 0]) for c in range(512): ser.write(black)
Then, to create a custom packet like robtillaart suggested: h = 1 #packet header x = 0x13 #x coordinate y = 0x2f #y coordinate r = 0xc #red value g = 0x4 #green value b = 0xe #blue value packet = bytearray([h, x, y, r, g, b]) #build packet
packet[3:6] = [12, 10, 3] #write new r, g, b values
|
|
|
|
« Last Edit: October 21, 2012, 01:09:05 pm by PapaG »
|
Logged
|
|
|
|
|
Manchester (England England)
Offline
Brattain Member
Karma: 277
Posts: 25508
Solder is electric glue
|
 |
« Reply #12 on: October 22, 2012, 02:33:31 am » |
The serial data rate is not maintained when going through a USB converter. The USB transfers data in batches or frames. Also try upping the thread priority in the python code to give it more time before Linux steals some.
|
|
|
|
|
Logged
|
|
|
|
|
London, UK
Offline
Newbie
Karma: 1
Posts: 41
Arduino rocks
|
 |
« Reply #13 on: October 22, 2012, 05:08:37 pm » |
Thanks for all the suggestions Rob and PapaG... implementing a packet structure seems like a nice way to go.
Mike, I'll try upping the priority and see how it goes.
At the moment running functions that plot LED by LED in a packet of (x,y,r,g,b) seem to overwhelm the Arduino's serial buffer more often than not. Sending an ACK per frame slows things down too much. E.g. when plotting 100 LED's (less than 1/4 the screen) with an ACK per packet it drops the frame rate to 3 or 4 frames per second. Not sure the best way around this. I think I might try and set up something that sends a 'pause' command if the buffer get's near 128bytes full.
|
|
|
|
|
Logged
|
|
|
|
|
Manchester (England England)
Offline
Brattain Member
Karma: 277
Posts: 25508
Solder is electric glue
|
 |
« Reply #14 on: October 22, 2012, 06:10:25 pm » |
I think I might try and set up something that sends a 'pause' command if the buffer get's near 128bytes full. That is known as Xon / Xoff handshaking.
|
|
|
|
|
Logged
|
|
|
|
|
|