Serial data is passed byte by byte from one device to another. It’s up to you to decide how each device (computer or microcontroller) should interpret those bytes, when the beginning of a message is, when the end is, and what to do with the bytes in between.
Before you can pass messages, however, the two devices need to agree on the rate at which they’ll exchange data, the number of bits per byte, etc. Generally, 8 bits, no parity, one stop bit is a good standard, and somewhere between 2400 and 9600 baud is a decent rate for small amounts of data.
Computers use numbers to represent alphanumeric characters (letters and numbers and punctuation) in bytes. There is a standard code, called the ASCII code (it stands for American Standard Code for Information Interchange), that assigns each number or letter a specific byte value from 0 to 255. For example, capital A is ASCII value 65. This chart can be found in many computer manuals’ indexes, and all over the place online. Here’s one online version. ASCII is a very common code (though not the only one), and is used by many devices as part of their serial protocol. As you can tell by the name, ASCII is very much biased to English alphanumeric communication. It’s convenient, in that any ASCII symbol can be represented in one byte, but it’s also limited. Other alphanumeric systems with more characters than the limited number in the Latin (English) are not represented in ASCII. ASCII has been superseded by Unicode, but fortunately the most common form of Unicode, UTF-8, is compatible with ASCII. For more on Unicode, see this informative blog post by Umer Mansoor.
If you’re only sending one number, and that number is less than 255, you know it can fit in a byte. This kind of message is easy. Just send the same byte over and over, and the computer can pick it up at any time. If you’re sending more than that (and you usually are), things are a little more complicated. The receiving computer has to know when the message starts and when it ends.
Different serial devices will use different codes to perform different actions. If a device, like a tape deck, laserdisk player, etc. is serial controllable, there will usually be a section in its manual outlining the messages it expects to receive, and at what baud rates it expects to receive them.
In Arduino, if you want to convert a byte’s raw value to the ASCII characters representing it, use the DEC modifier, like so:
Serial.print(myVar, DEC);
If the value of myVar was 134, this line would return three bytes, containing the characters “1”, “3”, and “4”.
Once you’ve got the microcontroller programmed and hooked to the computer, the first thing you should do is test to see what the microcontroller is sending using the simplest program possible. On the mac, ZTerm does the job well, as does the screen command in the Terminal program; on the PC, HyperTerminal works well. Set the baudrate and settings to the same as what you’ve programmed the microcontroller set to, and connect. If there’s an option for flow control, choose ‘none’. You should see the ASCII representations of whatever bytes you’re sending.
The biggest challenge to serial communication is making sure you get all the bytes, and that you get them in the right order. If the computer (PC or microcontroller) was doing something else at the instant that it should have been listening to the serial port, or it its input buffer has overflowed (i.e. if you’ve received more data than the computer has processed), you may lose some data. There’s no single way of making sure you get the right data, but there are some fundamentals you can check:
1. How many bytes am I sending? Am I receiving the same number of bytes?
For example, if the microcontroller is sending 3 variables, for 3 sensors, the PC needs to receive all three in order to act on them.
2. Did I get the bytes in the right order?
If the sender were sending “ABC” over and over, as “ABCABCABCABC” etc, it’s possible that the receiver might not start receiving at the beginning, and get “BCABCABCABCABCA” instead. This is a problem if, say, A is the right switch, B is the center switch, and C is the left switch. In order to avoid this, it’s sometimes useful to send some value at the start or the end of your data string that’s a constant number, and different from the other values. For example, if A can range from 0 to 100, and B can range from 0 to 100, and C can range from 0 to 100, perhaps you send 101 at the beginning of each string. In BASIC code, that might look like this:
PICBasic Pro:
A var byte B var Byte C var Byte headerByte var byte headerbyte = 101 main: ' generate values for A, B, and C here serout2 portc.6, 16468, [headerByte, A, B, C] goto main
BX-24:
Dim A as byte Dim B as Byte Dim C as Byte dim headerByte as byte headerbyte = 101 ' fill in the code to set up your serial port here do ' generate values for A, B, and C here call putQueue(OutputBuffer, headerByte, 1) call putQueue(OutputBuffer, A, 1) call putQueue(OutputBuffer, B, 1) call putQueue(OutputBuffer, C, 1) loop
Wiring/Arduino:
char A; char B; char C; char headerByte = 101; void setup() { Serial.begin(9600); } void loop() { // generate values for A, B, and C here Serial.print(A, BYTE); Serial.print(B, BYTE); Serial.print(C, BYTE); Serial.print(headerByte, BYTE); }
Another alternative to this method of “punctuating” your serial message is to set up a “call and response” method. For example, the sending device may wait until it receives a request for data, then send one string of data, then wait for another request. This way, the receiver knows that it will only ever have one string of data in its buffer at a time. In this case, you need to make sure that the receiver’s serial buffer can fit as many bytes as the sender sends out in one string, and that the sender can receive a byte and wait before sending.
If the microcontroller is the sender, you might make it wait like this. This example assumes the receiver will send the letter “A” (ASCII 65) when ready for new data:
PicBasic Pro:
main: ' wait for incoming data: serin2 portc.7, 16468, [inputVar] ' If we get a byte from the PC and it's 65 (ASCII "A"), ' send our data out: if inputVar = 65 then serout2 portc.6, 16468, [A, B, C] endif goto main
BX-24:
' Find out if anything is in the queue. gotaByte = statusQueue(inputBuffer) ' If there is data in the input buffer, ' get the first byte of it: if (gotaByte = true) then call getQueue(inputBuffer, inByte, 1) if inByte = 65 then ' send bytes out here: call putQueue(OutputBuffer, A, 1) call putQueue(OutputBuffer, B, 1) call putQueue(OutputBuffer, C, 1) end if else inByte = 0 end If
Arduino:
// Find out if anything is in the queue. if (Serial.available() > 0) { // If there is data in the input buffer, // get the first byte of it: char inByte = Serial.read(); if (inByte == 'A') { // send bytes out here: Serial.print(A, BYTE); Serial.print(B, BYTE); Serial.print(C, BYTE); } }
3. Are my bytes part of a larger variable?
Let’s say you’re sending an integer variable from the BX-24. An integer is two bytes long, so the PC will receive two bytes when you send it an integer. To convert those two bytes to a single number again, use this formula:
integerVar = (byte1Var * 256) + byte2Var
If this confuses you, think of it as analogous to how you convert a number in hexadecimal (base 16) to a decimal (base 10) value. In this case, each byte is a digit in base 256.
Try it by sending a constant number larger than 256 first, to make sure you have the formula right.
4. Is my data getting garbled in transit?
Serial communication is tricky, in that you need to have both sides properly grounded to a common ground, and both receive and transmit wires properly connected and insulated. all kinds of electrical noise can get in the system and interfere with your transmission. To minimize the time you spend troubleshooting, it’s best to start out by sending a constant message. For example, if you know you’re planning to send out three bytes of data to represent sensor data as above, don’t start out by sending the actual values from the sensors. Start out by sending constant values. For example,
PicBasic Pro:
outByte var byte outByte = 65 serout2 portc.6, 16468, [outByte] outByte = 66 serout2 portc.6, 16468, [outByte] outByte = 67 serout2 portc.6, 16468, [outByte]
BX-24:
dim outByte as byte outByte = 65 call putQueue(OutputBuffer, outByte, 1) outByte = 66 call putQueue(OutputBuffer, outByte, 1) outByte = 67 call putQueue(OutputBuffer, outByte, 1)
Arduino:
Serial.print(65, BYTE); Serial.print(66, BYTE); Serial.print(67, BYTE);
When you see that the receiver is getting the same values you’re sending consistently, then you can change your code so that you care sending the actual values of the sensors. the hardware setup alone introduces enough variable conditions and points of possible failure to begin with. Because of this, it’s best to make your code (the thing you can control) constant until you have the hardware under control.