Jump to content

Protocol for controlling Arduino pins over serial


ryoh
 Share

Recommended Posts

So far I've used this protocol for a smart fridge and a bilirubin measurement device (for testing Jaundice).  Because popular Arduino Bluetooth interfaces happen to act as media independent serial lines, this will also work with Bluetooth. http://www.electronica60norte.com/mwfls/pdf/newBluetooth.pdf

Responses are returned in the 'value' or 'error' parameters during reads. The 'value' parameter is used as the source during writes.

import serial
import struct
import time

params = ('option', 'pin', 'value', 'error')
ser = serial.Serial('COM3') # opening a serial connection to arduino over USB will reset the device
ser.read(1) # wait for ready signal so we know that arduino came back online.
ser.write(struct.pack('<hhhh', 0, 0, 0, 0))
response = struct.unpack('<hhhh', ser.read(8)) 
print(dict(zip(params, response))

/*
 * ComPacket Data Structure
 * >option: 0 = Analog Read, 1 = Digital Read, 2 = Analog Write, 3 = Digital Write
 * >pin: pin to read/write from
 * >value: Arduino will return pin value to this variable. Leave empty on request
 * >error: Arduino will return an Error code is there's a problem with the request, or reading a pin value
 */
enum ComPacket_option {A_READ, D_READ, A_WRITE, D_WRITE};
enum ComPacket_error {INVALID_OPTION, INVALID_READ_PIN, INVALID_WRITE_PIN};

struct ComPacket {
  enum ComPacket_option option; 
  int pin;
  int value;
  enum ComPacket_error error;
} buf;

void ComPacket_zero(ComPacket *packet) {
  packet->option = (enum ComPacket_option) 0;
  packet->pin = 0;
  packet->value = 0;
  packet->error = (enum ComPacket_error) 0;
}
 
void ComPacket_handleRead(ComPacket *packet) {  
  if (packet->option == A_READ) {
    packet->value = analogRead(packet->pin);
    return;
  }
  if (packet->option == D_READ) {
    packet->value = digitalRead(packet->pin);
    return;
  }
}

void ComPacket_handleWrite(ComPacket *packet) {
  if (packet->option == A_WRITE) {
    analogWrite(packet->pin, packet->value);
    return;
  }
  if (packet->option == D_WRITE) {
    digitalWrite(packet->pin, packet->value);
    return;
  }
}


void ComPacket_handleError(ComPacket *packet, enum ComPacket_error error) {
  ComPacket_zero(packet);
  packet->error = error;
}

void ComPacket_processPacket(ComPacket *packet) {
  switch (packet->option) {
    case A_READ: 
    case D_READ: ComPacket_handleRead(packet); break;
    case A_WRITE:
    case D_WRITE: ComPacket_handleWrite(packet); break;
    
    default: ComPacket_handleError(packet, INVALID_OPTION);
  }
}

/*
 * ******************* END COMPACKET 
 */

// Use Serial1 for TX/RX pins
/*
 * https://www.arduino.cc/reference/tr/language/functions/communication/serial/
 * Serial, Serial1, Serial2 and Serial3 are available. See reference for which you need
 */
auto SerialDevice = Serial;

void setup() {
  // put your setup code here, to run once:
  ComPacket_zero(&buf);
  
  SerialDevice.begin(9600,SERIAL_8N1);
  // wait for serial port to connect. Needed for native USB
  while (!SerialDevice); 
  SerialDevice.write(1);
  
}

void loop() {
  // check if data available
  if (SerialDevice.available() > 0) {
    // if so process packet and send back result
    SerialDevice.readBytes((char*)&buf, sizeof(ComPacket));
    ComPacket_processPacket(&buf);
    SerialDevice.write((char*)&buf, sizeof(ComPacket));
    SerialDevice.flush();
  }
  
}

 

  • I Like This! 1
Link to comment
Share on other sites

  • 2 weeks later...

This is very interesting Ryoh! 

I have a question (which might be stupid) about how this works because I'm very much a noob at this electrical engineering type stuff. I did a bit of reading on the subject from this article though: https://www.arduino.cc/en/pmwiki.php?n=Reference/Serial

I guess what confused me most when I first started looking at this was that I immediately was picturing a serial port, and I was trying to imagine why a connection between Arduino pins and a serial port would uniquely require a program like this in order to function, but I've come to realize that was very far off what's happening here. My new understanding is that serial, in this context, refers to the fact that you're using the TX and RX pins on the Arduino which are pins 2 and 3 on a serial port, and that you could hook up those pins on the Arduino to either pins 2 and 3 on a regular serial port (and use 5 for ground) like this:

Image result for rx tx to serial port

To the TX and RX of another board (like a Raspberry Pi for example) so long as it's like this:

Image result for rx tx to serial port

or even just something like this:

Ymi96.jpg

or Bluetooth as you mentioned.

Is all of that correct?

I'm definitely going to delve deeper into this next Summer when I finally get around to making my drone. I'll have to brush up on my C a bit though I'm sure (for example I'm not quite sure what "buf;" does at the end of your struct definition). I'm really glad you shared this! 

Edited by Freak
Link to comment
Share on other sites

19 hours ago, Freak said:

This is very interesting Ryoh! 

I have a question (which might be stupid) about how this works because I'm very much a noob at this electrical engineering type stuff. I did a bit of reading on the subject from this article though: https://www.arduino.cc/en/pmwiki.php?n=Reference/Serial

I guess what confused me most when I first started looking at this was that I immediately was picturing a serial port, and I was trying to imagine why a connection between Arduino pins and a serial port would uniquely require a program like this in order to function, but I've come to realize that was very far off what's happening here. My new understanding is that serial, in this context, refers to the fact that you're using the TX and RX pins on the Arduino which are pins 2 and 3 on a serial port, and that you could hook up those pins on the Arduino to either pins 2 and 3 on a regular serial port (and use 5 for ground) like this:

Image result for rx tx to serial port

To the TX and RX of another board (like a Raspberry Pi for example) so long as it's like this:

Image result for rx tx to serial port

or even just something like this:

Ymi96.jpg

or Bluetooth as you mentioned.

Is all of that correct?

I'm definitely going to delve deeper into this next Summer when I finally get around to making my drone. I'll have to brush up on my C a bit though I'm sure (for example I'm not quite sure what "buf;" does at the end of your struct definition). I'm really glad you shared this! 

Thanks for your analysis Freak,

 

Your assumptions are correct about how I was using Serial. To make things more concrete I'll describe the use case that leads to this code.

Originally, when I was making my smart fridge, I planned to only use a raspberry pi. I had both an Arduino nano, and pi, but because controllers were in short supply I wanted to be conservative I tried my best to only use one or the other.  The benefit of the pi was that it had built-in wifi, could provide a Linux shell, let me code in many languages at once; the arduino offered more specialized apis for dealing with hardware out of the box. The pi had more of what I wanted so I attempted to just make that work. Among my supplies I had passive components and some logic gates, so I looked up some methods of making some DIY analog-to-digital (ADC ) converters to use with the pi. After spending a week on this I decided that it was much simpler to use the Arduino as an ADC, because the amount of work to build and document these ghetto, passive ADCs and support them with code were just not worth it. Additionally, the Atmega chip is very power efficient, so I could leave it in the fridge near the temperature sensor without much disturbance (or warming the fridge). 

So in summary, this code was born from the desire to have an ADC hub that I could control with another computer. The 8N1 serial connection I use is just a transport layer, like TCP, underlying my personal protocol for controlling all the pins (this code). In fact, the code above could be retrofitted to work over TCP/IP quite easily instead. Now you should understand the original use case.

The second use case for this code is for rapid development. It took me several tries to balance defrost and cooling in my fridge since one of the sensors was not working, and I could not afford to buy another one. Instead of disassembling the breadboard each time I needed to make a code update, I could ssh into the pi and write some python to control the fridge instead. Whenever possible, I think it's a great idea to keep the minimum amount of code possible on the actual microcontroller, and then interface with a higher-level language. Of course, this is not always ideal. In real-time applications, you could access monitoring stats from a high-level language, but mission-critical processing should probably be done on the chip itself. In something like your drone project, this wouldn't really be an issue since you'd probably be controlling it with wifi, because it's fast, it has built-in encryption, and you're guaranteed to want to do some processing on the laptop instead of the microcontroller itself. 

As for your final question regarding "buf;", Arduino actually uses C++, not C. This is an awkward choice.  Technically I could have made "compacket" into a class, and the natural thing to do would have been to initialized it in "setup()". But once setup returns the object disappears, so you're forced into using global variables, hence "buf;". Of course, there are some tricks you can use to get "buf;" into the namespace that you want, but the added complexity isn't worth it (especially since we're talking about shared memory on a microcontroller). 

Link to comment
Share on other sites

  • 4 weeks later...

I need to get into this. I took a class on Assembly where we simulated a CPU, so the really low level logic is really exciting for me. I have an arduino thats been sitting around collecting dust for a year or two, just not much time to get to play with it. 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...