Go Down

Topic: Pc to arduino communication (Read 172 times) previous topic - next topic

Topsoil

I've built an led strip controller using arduino mega. On arduino side serial communications code works like this:
  • Wait until Serial.available is > 0
  • Read first byte via Serial.read and save it to command[] byte array
  • Depending on its value, choose one of multiple commands and wait until enough bytes that encode arguments are waiting to be read, then read them into command[] byte array
  • Process them accordingly
  • Return to step one


And while i was sending commands by hand, everything was ok, but when i wrote a program that sends commands for me, two commands sent too quickly cause glitches, it feels like commands "collide"

Simplified arduino code:
Code: [Select]
#include <bitswap.h>
#include <chipsets.h>
#include <color.h>
#include <colorpalettes.h>
#include <colorutils.h>
#include <controller.h>
#include <cpp_compat.h>
#include <dmx.h>
#include <FastLED.h>
#include <fastled_config.h>
#include <fastled_delay.h>
#include <fastled_progmem.h>
#include <fastpin.h>
#include <fastspi.h>
#include <fastspi_bitbang.h>
#include <fastspi_dma.h>
#include <fastspi_nop.h>
#include <fastspi_ref.h>
#include <fastspi_types.h>
#include <hsv2rgb.h>
#include <led_sysdefs.h>
#include <lib8tion.h>
#include <noise.h>
#include <pixelset.h>
#include <pixeltypes.h>
#include <platforms.h>
#include <power_mgt.h>

FASTLED_USING_NAMESPACE

#include <String.h>

#define NUM_LEDS 131
#define DATA_PIN 11

CRGB leds[NUM_LEDS];
byte command[12];
bool manualshow = 1;

void setup() {
 
FastLED.addLeds<WS2812, DATA_PIN, BRG>(leds, NUM_LEDS);
 
randomSeed(analogRead(0));
 
Serial.begin(115200); //Baud rate does not affect glitched behavior
  }

void loop() {
 
    while(Serial.available() == 0){} //Wait until there is something in serial buffer
 
    command[0] = Serial.read();  //Recieve command code
 
switch(command[0])
  {
    default:
      Serial.write(255); //Write 255 back to sender, indicating wrong command code
      break;
    //////////////SET WHOLE STRIP. Format: mode, H, S, V. |OR| mode, R, G, B.
    case 0: 
     
while(Serial.available() < 4){}; //Wait for arguments
     
for(int i = 1; i < 5; i++)
      {
        command[i] = Serial.read(); //Read 4 arguments
      }
     
/////////PUT LED CODE AFTER THIS/////////
/Processing command with FastLED library
     
if(command[1] == 0) // 0 = HSV   1 = RGB
      {
        for(int i = 0; i<=NUM_LEDS; i++)
        {
          leds[i] = CHSV(command[2],command[3],command[4]);
        }
      }
      else
      {
        for(int i = 0; i<=NUM_LEDS; i++)
        {
          leds[i] = CRGB(command[2],command[3],command[4]);
        }
      }
     
      if(manualshow){FastLED.show();}
      Serial.write(0);
      break;
//Other commands follow somilar structure
     
//////////////FILL GRADIENT . Format: mode, StartPoint, (Start color), End Point, (End color), DIR (0 - FWD, 1 = BKWD, 2 - SHRT, 3 = LNG)
    case 1:
      while(Serial.available() < 10){};
     
      for(int i = 1; i <= 10; i++)
      {
        command[i] = Serial.read();
      }
TGradientDirectionCode Dir;
     Dir = command[10];
     
      if(command[1] == 0) // 0 = HSV   1 = RGB
      {
          fill_gradient(leds,command[2],CHSV(command[3],command[4],command[5]),command[6],CHSV(command[7],command[8],command[9]), Dir);
      Serial.write(1);
      }
      else
      {
          fill_gradient_RGB(leds,command[2],CRGB(command[3],command[4],command[5]),command[6],CRGB(command[7],command[8],command[9]), Dir);
      Serial.write(1);
      }
      if(manualshow){FastLED.show();}
     
      break;

    case 2: //FILL SOLID Format: mode, startpos, endpos, (color)

      while(Serial.available() < 6){};
     
      for(int i = 1; i <= 6; i++)
      {
        command[i] = Serial.read();
      }

      if(command[1] == 0)
      {
        for(int i = command[2]; i <= command[3]; i++)
        {
          leds[i] = CHSV(command[4],command[5],command[6]);
        }
      }
      else
      {
        for(int i = command[2]; i <= command[3]; i++)
        {
          leds[i] = CRGB(command[4],command[5],command[6]);
        }
      }
      if(manualshow){FastLED.show();}
      Serial.write(2);
      break;

      case 3: //SET ONE LED Format: mode, pos, (color)

      while(Serial.available() < 5){};
     
      for(int i = 1; i <= 5; i++)
      {
        command[i] = Serial.read();
      }

      if(command[1] == 0)
      {
        leds[command[2]] = CHSV(command[3],command[4],command[5]);
        if(manualshow){FastLED.show();}
      }
      else
      {
        leds[command[2]] = CRGB(command[3],command[4],command[5]);
        if(manualshow){FastLED.show();}
      }
      Serial.write(3);
      break;

      case 4: //SET MODE (AUTO/MANUAL) Format: mode (0 - auto, 1 - manual)

      while(Serial.available() < 1){};
     
      command[1] = Serial.read();
      if(command[1]==0){manualshow = 0;}else{manualshow = 1;}
      break;

      case 5: //SHOW

      FastLED.show();
       
      break;

      case 6: //POLL Format: info

      FastLED.show();

      while(Serial.available() < 1){};
     
      command[1] = Serial.read();

        switch(command[1])
        {
          default:
          Serial.write(0);
          Serial.write(0);
          break;
         
          case 0:
            Serial.write((byte)NUM_LEDS);
            Serial.write(0);
          break;
        }
       
      break;
  }
}


What is wrong, and how can i properly implement byte "queue" so stuff wont collide

blh64

Read this thread to see how to do it better.

Basically, you should be filling in your command[] until you get an entire line and then process it.

Topsoil

Read this thread to see how to do it better.

Basically, you should be filling in your command[] until you get an entire line and then process it.
That is exactly what i do

Delta_G

That is exactly what i do
Yeah, but read that thread about how to do it right.


Code: [Select]
while(Serial.available() == 0){} //Wait until there is something in serial buffer

Whatever you're building, receiving these commands is all it will ever be able to do if you block like this. Why not just let the loop do nothing if there's nothing on the line?  Why block?  Instead of blocking until you get the whole message, just let the loop run gathering characters until a whole message arrives and then process it.  It's a lot easier to use start and end markers than to try to keep up with how many characters you need to read. 


For one thing, open your code in the IDE and press control-T.  That will autoformat it so it isn't wandering all over the page.  When you see code written by real coders and all the blocks line up all nice and neat, that isn't done just to make it pretty.  It helps you see how your code logic flows and what statements belong in what blocks. 

|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

Topsoil

Yeah, but read that thread about how to do it right.


Code: [Select]
while(Serial.available() == 0){} //Wait until there is something in serial buffer

Whatever you're building, receiving these commands is all it will ever be able to do if you block like this. Why not just let the loop do nothing if there's nothing on the line?  Why block?  Instead of blocking until you get the whole message, just let the loop run gathering characters until a whole message arrives and then process it.  It's a lot easier to use start and end markers than to try to keep up with how many characters you need to read. 


For one thing, open your code in the IDE and press control-T.  That will autoformat it so it isn't wandering all over the page.  When you see code written by real coders and all the blocks line up all nice and neat, that isn't done just to make it pretty.  It helps you see how your code logic flows and what statements belong in what blocks. 


Problem is, i send byte numeric values, representing Hue Saturation and Value of HSV color, and for example 0 might mean end of command, or 0 in Value of some color parameter

Delta_G

Problem is, i send byte numeric values, representing Hue Saturation and Value of HSV color, and for example 0 might mean end of command, or 0 in Value of some color parameter
OK, so I don't see why that means you have to block and freeze your code in place in a while loop waiting for it. 

My robot takes in commands two different ways.  All commands start with a '<' and end with a '>'.  If the first character is an ascii character then it reads until it hits the '>'.  If the first character is a control character from 0x11 through 0x14 then it looks at the next byte which tells it how many bytes are in the message and it reads that many bytes. 

But at no time does it freeze code in a while loop waiting for that stuff to get there.  My robot has lots of other things to do while it waits. 

This is the code I use to read from serial: https://github.com/delta-G/StreamParser

Look at any of the other robot code that uses it to see how it gets used.  RobotMainBrain uses it. 

|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

Topsoil

But at no time does it freeze code in a while loop waiting for that stuff to get there.  My robot has lots of other things to do while it waits. 

My LED controller really doesnt need to do anything while it has no commands: just recieve command, do what it says, and then do nothing. No sensors, no cameras, no motors, just recieve, do, and then nothing

Delta_G

#7
Sep 23, 2019, 01:22 am Last Edit: Sep 23, 2019, 01:22 am by Delta_G
My LED controller really doesnt need to do anything while it has no commands: just recieve command, do what it says, and then do nothing. No sensors, no cameras, no motors, just recieve, do, and then nothing
So you only accept serial input when a led sequence is completely done?  I would think that the ability to receive the next command while you are executing the last one would be pretty useful. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

Blackfin

#8
Sep 23, 2019, 02:51 am Last Edit: Sep 23, 2019, 04:03 am by Blackfin
I think you should consider receiving the entirety of the serial data in a proper state machine, separate the receipt of serial data from the execution of the command and "compartmentalize" (i.e. use functions rather than cramming everything into loop()) have more structure to your messages (e.g. alignment header, fields, checksum etc).

I notice after each command your Arduino writes a single byte response to the PC: Does your PC actually wait for that response before sending the next command?


Power_Broker

Might consider using these libraries (if not, they are at least a good reference on how to deal with UART serial data):

- SerialTransfer (For Arduino)
- ArduSerial (For Windows C++)
"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

Topsoil

I notice after each command your Arduino writes a single byte response to the PC: Does your PC actually wait for that response before sending the next command?


No, it was just a debug thing i added some time ago to make sure that arduino got to this point in program

Topsoil

So you only accept serial input when a led sequence is completely done?  I would think that the ability to receive the next command while you are executing the last one would be pretty useful.  
How do i implement this? I heard of arduino pseudo-parallel execution, is that what i need?

Delta_G

How do i implement this? I heard of arduino pseudo-parallel execution, is that what i need?
No, nothing that complicated. Just stop freezing your code in while loops.  @Robin2 has a great Serial Input Basics thread on this site that shows some good techniques to reading from serial.  But the point is that no program should ever stop and wait.  For anything.  It should continue running and keep coming back to check.  Not freeze in its tracks and wait for something. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

Power_Broker

No, nothing that complicated. Just stop freezing your code in while loops.  @Robin2 has a great Serial Input Basics thread on this site that shows some good techniques to reading from serial.  But the point is that no program should ever stop and wait.  For anything.  It should continue running and keep coming back to check.  Not freeze in its tracks and wait for something.  
That's exactly what this serial library provides - and also offers more robustness than the basic tutorial
"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

Delta_G

If you want to avoid learning to code there are libraries to hide away everything these days.  But at the end of the day this isn't hard at all.  It's a great place to start really learning to code. 

The thing is to stop thinking of your loop function as telling a story from beginning to end like the description in your original post.  It's not wait for this and then do this and then do that and then wait til it's done and finally get to the end.  That's not a good way to program.

Instead think of the loop function like a big high speed checklist that runs over and over and over thousands of times a second.  And most of those times it just checks through and does nothing.  But occasionally one of the checks hits and it's time to take some small step, so we take that small step and go back to running through the checklist at lightning speed.  It takes a little different way of thinking.  Instead of start something and wait 15 seconds it becomes start something and note the time and then on each high speed pass of loop check real quick to see if it has been 15 seconds since that time and if so stop whatever thing or take whatever next step. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

Go Up