torstai 6. marraskuuta 2014

Serial protocols

I know I promised examples of serial protocols but I've been way too busy to write lately. Sorry about that. I managed to start with the most simple protocols so here is the first part.

For simplicity I am writing these to Arduino using its built-in USB (and yes, I'm aware that this messes with my point of reliability -  but these are simple examples for learning that can be copied and tested easily and quickly so bear with me here).

I'm focusing here on three-wire (ground, transmit, receive) type serial link with parameters 9600 8N1 (9600bps, 8 data bits, no parity, 1 stop bit) since that is the type I use the most. Usually MCUs can also provide hardware flow control and some other control mechanisms (like parity for simple bit-error detection) that can be used to make link more reliable and controllable, depending on your exact situation.

After loading the sketch to Arduino you can use your favorite serial terminal program to send commands to it - I've written custom one that handles some custom protocols I often use but there are many terminals available for free, for example termite that I'm linking here because happened to be on top of quick google search and it seemed actually useful, unlike few other top links.
( http://www.compuphase.com/software_termite.htm ).

Basic Arduino doesn't have much in way of easily visualized I/O so I'm using the "L" led and serial return channel as example of work done. In case of serial return channel I'll make note when data is part of protocol or just example (typically written on paper/screen/whatever and not seen by controller).

So to the code. First basic setup phase; use built-in routines to initialize serial port and set pin 13 (tied to L led) to output. Led will turn on when pin is driven high.

void setup() 
{
   // Init serial communication, 9600 8N1
  Serial.begin(9600);

   // Init L led, set off
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
}

Then the main loop, starting with as simple code as possible.

void loop()
{
  if (Serial.available() > 0) {
    int c = Serial.read();
    
    if (c == '1') {
      digitalWrite(13, HIGH);
    }
    if (c == '0') {
      digitalWrite(13, LOW);
    }
  }
}

Arduino is simply listening to incoming data and acting on it by setting LED on when '1' is received or off when '0' is received. There is no error checking or acknowledgements of any kind, so controller can't know if device actually receives the commands.
For controlling a simple lamp (or relay or really anything that is either on or off) this may actually be "good enough". If link is "lossy" controller can simply send the same command multiple times, hoping the at least one is received successfully.

This protocol works (kinda-sorta) but is quite fragile. What happens if wire is cut? So let's add simple acknowledge mechanism; received send A when command is received and N if command is bad.

void loop()
{
  if (Serial.available() > 0) {
    int c = Serial.read();
    
    if (c == '1') {
      digitalWrite(13, HIGH);
      Serial.write('A');
    } else if (c == '0') {
      digitalWrite(13, LOW);
      Serial.write('A');
    } else {
      Serial.write('N');
    }
  }
}

Now controller can listen to returning data and react on it. If no reply or NAK ("negative acknowledge") is received the command is can be sent again. Now we can detect more error situations and even report such cases to user.

Note that the difference between '1' and '0' commands used above is only single bit. One bit can get corrupted easily, so unless other kind of error detection is used, you'd want to use command bytes with more difference. For example 0x30 ('0') for off, and 0x55 ('U') for on (four-bit difference; transfer errors should usually result NAK)

(excercise: what is the difference between 'A' and 'N', in bits, and is there need to use other bytes for those replies?)

Now, when you want to control LED these can be used, but how about when you want to check if the LED controller is alive? Let's add quick query command;

void loop()
{
  if (Serial.available() > 0) {
    int c = Serial.read();
    
    if (c == '?') {
      Serial.write('A'); 
    } else if (c == '1') {
      digitalWrite(13, HIGH);
      Serial.write('A');
    } else if (c == '0') {
      digitalWrite(13, LOW);
      Serial.write('A');
    } else {
      Serial.write('N');
    }
  }
}

So now controller can probe the device ("are you there?" "yup") when say starting up and report status to user without affecting the system state. Simple addition but so very useful.

On next post I'll be continuing from here, when I find some time of course. That unfortunately might take a while, since I'm (still) quite busy and the protocols introduced next will take some time to write and test too.

Ei kommentteja:

Lähetä kommentti