Class 6 – Lab 1 Intro to Serial Communications

This lab helps to better understand serial communication from a microntroller. Soon enough I’ll use what I’ve learned here to write programs in other languages that can interact with my microcontroller, such as p5.js.

For now, I’ll just learn how to send data from multiple sensors through my Arduino to my computer, and learn to format that data in my serial monitor so it’s easier to read.

In general it’s good to know that serial data is sent byte by byte from one device to another, and it’s up to you how to interpret that data. But honestly, a lot of the decisions are already made for us based on common practice (for example, so far in school we’re using 9600 baud for the speed of the communication, and using 5 volt microcontrollers and laptops that can transmit and receive at that same voltage).  From what I understand, what we have to decide is whether to interpret data as raw binary or ASCII, and whether to slow down receiving data so that program doesn’t slow down with too much data. For the most part, we want to use ASCII encoded data so that it’s easier to debug in our serial monitor. To receive ASCII encoded data, we can use the Serial.print() command.

Asynchronous Serial Communication

I found this definition of asychronous serial communication on Sparkfun.com to be helpful.

“Asynchronous means that data is transferred without support from an external clock signal. This transmission method is perfect for minimizing the required wires and I/O pins, but it does mean we need to put some extra effort into reliably transferring and receiving data.” https://learn.sparkfun.com/tutorials/serial-communication 

My take away from this is that by not having to connect two devices to the same external “clock” with a bunch of wires and pins, we save a lot of physical labor. ?? But we still need to write code using pre-determined signaling rules that makes it possible for them to successfully talk to one another.

Initializing Communication Between Two Devices

To me, there are 6 things required to communicate between two devices, and have it be understandable through your serial monitor. Basically, they need to speak the same language at the same time.

  1. The data rate – the speed at which information from device is sampled by another device. The most common data rate is 9600 baud or bits per second. This means every 1/9600th of a second, the voltage’s value is interpreted as a new bit of data! Converted to bytes, this means 1200 bytes can be sent in one second!
  2. The voltage levels representing a 1 or 0 bit – this depends on whether both your devices use the same voltage. If they don’t, you’ll need to map them to each other. For example, you’ll need to map 3.3 volts to a 5 volt device so that their 0s and 1s translate across devices.
  3. The meaning of those voltage levels – is the voltage signal “true”, using a high voltage as “1” and a low voltage as “0”? Or is the signal “inverted”, with the opposite reading of a low voltage as “0” and a high voltage as “1”?
  4. Wires to send and receive data on both the sending and receiving devices. These are also called “transmit” and “receive” and “Tx” and “Rx”
  5. A common ground connection, so that both devices have a common starting point to measure voltage by.
  6. How to interpret the data as incoming bytes // How to print that data to your serial monitor so you can read it – You need to decide when the beginning of the message is, when the end is, and what to do with the bytes in between.

 

ASCII vs. Binary: What “data language” should you use, and when?

In short, my understanding is that sticking to the raw binary values of your sensor reading, such as 101011, is useful because you don’t have to ask your program or serial monitor to spend time translating “data languages”. Raw binary is also more efficient as long as you are sending values below 255, because you can send these small numbers in one raw binary byte. I’m assuming this means your program can run faster. But anything above the value 255 needs more than one byte to be sent — in fact, it needs three bytes to be sent.

However, ASCII “encoded strings” or values are ultimately better because we can actually read them to debug our code with the serial monitor. Who can read straight raw binary code anyway??

From what I understand, the creators of Arduino code decided to create two commands that let you switch between the two data languages.

  • The Serial.write() command sends binary values of sensor readings. It doesn’t format data as ASCII characters. I BELIEVE we never see the Serial.write() command’s return of a binary value in our Arduino IDE app because the serial monitor is set up to only return ASCII Characters. We have to use other serial monitor apps such as Cool Term if we want to see those binary values. But that binary value is still used within our Arduino IDE app to execute whatever code we’ve written.

 

  • The Serial.print() command formats the value it returns as an ASCII-encoded decimal number. And if you use the Serial.println() command, you get the BONUS benefit of a creating a carriage return and new line, which makes reading the data in your serial monitor easier to read.  (ASCII is like a foreign language dictionary within your computer that translates one value language into another as requested.) My understanding is that the Serial.print() command has a built-in ability to translate the raw binary data of a sensor reading into ASCII. You need Serial.print() to send values higher than 255 because they won’t fit into a single byte. Higher values need 3 bytes (one for each digit of a number such as 880), plus any other bytes assigned to the punctuation you might want to see in your serial monitor. In general, Serial.print() is great to use because it returns values that are easier for someone to read than raw binary.

Code to practice sending data values for three sensors to your serial monitor

I was able to print all three sensor readings to my serial monitor with this code.

But I’m amazed that this code works without assigning the A0, A1 and A2 pins. How does it know which pins to read?? Are “0” and analogRead enough of a clue for it to work?

void loop() {
for (int thisSensor = 0; thisSensor < 3; thisSensor++) {
int sensorValue = analogRead(thisSensor);
Serial.print(sensorValue);
Serial.print(“,”);
}
}

Advice on sending multiple sensor’s data to your serial monitor

You’ll want to make it easier to read multiple readings of sensors in your serial monitor. Otherwise, you’ll just get a long list of values and won’t be able to tell which belongs to which sensor.

To start with you, you can use punctuation to format multiple binary values by adding in tabs, commas, line breaks, etc. This is demonstrated in the code above. However, if you’re using Serial.write(), you sacrifice a binary value for each punctuation value you use… you’re out of luck if your sensor has that same reading value! You also risk slowing down your program if data is constantly coming in, because there is nothing in your code to stop it. All this data gets stuck in your “serial buffer,” which is part of your computer that holds incoming information.

Therefore, you can also add code to create a “call and response” or break in the flow of data coming from your sensors. You can require the device sending data to wait for a request from the other device once its ready to start, or done processing the data it already has in its serial buffer.

Punctuation

Using punctuation alone to separate sensor data is simple to use, in that you read each sensor’s pin and add code for a comma or line after each one. But this method doesn’t prevent your program from slowing down while the device’s serial buffer fills up (with information from the other device faster than it can receive).

const int switchPin = 2;      // digital input
 void setup() {
   // configure the serial connection:
   Serial.begin(9600);
   // configure the digital input:
   pinMode(switchPin, INPUT);
 }
void loop() {
   // read the sensor:
   int sensorValue = analogRead(A0);
   // print the results:
   Serial.print(sensorValue);
   Serial.print(",");
   // read the sensor:
   sensorValue = analogRead(A1);
   // print the results:
   Serial.print(sensorValue);
   Serial.print(",");
   // read the sensor:
   sensorValue = digitalRead(switchPin);
   // print the results:
   Serial.println(sensorValue);
}

 

Flow Control, aka Call and Response, aka Handshaking

If you do need to prevent your program from slowing down, with a little more code you can require the device with data to wait until its been asked to send more data. That way the serial buffer of your receiving device can finish what’s already on its plate.

As part of this “call and response” code, you make use of the Serial.available() command to find out how many bytes are available or waiting to be read.  I believe this means its checking the serial buffer to find out what data remains to be read?

I tried the code below, but my loop is not stopping after each data sample to wait for me to enter another input. Why is this? Also, just to clarify, the serial monitor’s text field at the top is used to send data to the microcontroller?

const int switchPin = 2;

void setup() {
Serial.begin(9600);
while (Serial.available() <= 0) {
Serial.println(“hello”); // send a starting message
delay(300); // wait 1/3 second
}
}

void loop() {
// read the sensor:
int sensorValue = analogRead(A0);
// print the results:
Serial.print(sensorValue);
Serial.print(“,”);

// read the sensor:
sensorValue = analogRead(A1);
// print the results:
Serial.print(sensorValue);
Serial.print(“,”);

// read the sensor:
sensorValue = digitalRead(switchPin);
// print the results:
Serial.println(sensorValue);

if (Serial.available() > 0) {
int inByte = Serial.read();
sensorValue = analogRead(A0);
Serial.print(sensorValue);
Serial.print(“,”);

sensorValue = analogRead(A1);
Serial.print(sensorValue);
Serial.print(“,”);

sensorValue = digitalRead(switchPin);
Serial.println(sensorValue);
}
}

 

 

Questions

There was a quick mention about how using println() in the draw() loop of your p5.js sketch will slow it down a lot, because the serial buffer will become too full. Instead, you should switch over to a call-and-response method to only get information when you need it. I was confused, but this might be cleared up in the second lab.

Leave a Reply

Your email address will not be published. Required fields are marked *