My Pi Description

My Experiences With the Raspberry Pi -- Tracking My Learning -- My Pi Projects

Monday, April 20, 2015

Serial Transmission - Gertboard (Like an Arduino) to Raspberry Pi

Introduction

If you read my post of March 14, you know I have an enclosure with temperature sensors, and a Raspberry Pi with a Gertboard attached that connects to a receiver board. The enclosure transmits sensor data to the receiver board. The receiver passes the decoded data to the ATmega328P microcontroller on the Gertboard. I covered the transmission, via 434 MHz transmitter and receiver in my March 14 post. Check it out here.
The last piece of the data chain is to pass the data to the Raspberry Pi. A python program, running on the Raspberry Pi, is in charge of obtaining the measurements from the C++ program running on the Gertboard's ATmega328P. As long as the sensor enclosure is transmitting, the Gertboard receives the measurements.
If you wonder why I pass data to the Raspberry Pi it is because I can get graphs like this:
I can get a log file like this (just the beginning of the file):
I can setup the measurement run with this graphical user interface I created:

Data Captured By the Receiver

ROM codes, and 12 bytes of description for each DS18B20 sensor, are stored in the EEPROM of the enclosure's ATMega328P. One temperature sensor is permanently connected to the enclosure, but I can connect as many other sensors as I wish. However, the ROM codes and descriptions for those other sensors must be stored in the EEPROM. When I apply power to the enclosure, we find out what sensors are attached by attempting to read from the scratchpad of each sensor for which we have a ROM code. If the CRC passes, we know the sensor is connected.
I have a full suite of ATmega328P, C++ sketches, for adding, editing, and removing sensor data from the EEPROM. Check it out here. I also wrote my own library for the DS18B20 temperature sensor, and other libraries. Check out my libraries here.
Let's assume I have data for five sensors stored in the EEPROM and sensor's 1, 3, and 5 are connected to the enclosure. I command each sensor, in turn, to make a temperature measurement. When the measurement is available, we assemble 20 bytes of data (data for one sensor). The first byte is always the number of sensors that are connected to the enclosure. For our example, that number is 3. The next to the last byte is the number of the sensor which corresponds to its position in the EEPROM. In our example, that will be 1, 3, or 5. The other bytes correspond to the temperature measurement, high and low alarm temperature, and the sensor description. The last byte is the CRC. We continually make measurements running through each of the connected sensors.
The receiver, continually, looks for valid data. If it recognizes synchronization pulses (see my March 14 post), it sees that the data is valid. It decodes the 20 bytes of Manchester encoded data for the sensor that happened to be transmitting. It may be sensor 1, 3, or 5. It looks at the first byte of data to find the number of sensors in the measurement run. In this case it will be three. An array is initialized whose length is the number of sensors, three in our case, times the number of data bytes per measurement, which is 20. In our example, the array will be 60 bytes long. The data for the first sensor is placed in the array. Next, the receiver code looks for, and captures, data for the next two measurements. This data is appended to the array. Now we have 60 bytes of data. The order of the data may be for sensors 1, 3, and 5; or 3, 5, and 1; or 5, 1, and 3.
After accumulating the 60 bytes, the receiver code looks to see if the Raspberry Pi's python program is requesting the data. If not, the data in the array is overwritten with the next series of measurements. There will be a minimum of one minute between requests from the Pi to the receiver (selected by the user). Depending upon the resolution of the sensor and how power is applied to the sensor (normal or parasitic) the receiver will have those 60 bytes available about every 300ms. to every 2200ms. Therefore, you can see that most of the measurements are lost.

Receiver Code

I have taken another fragment of the C++ code running on the Gertboard's ATmega328P. Visit here to see the entire script.
The code fragment includes the function, "transmit_data()" that looks for a request from the Pi and transfers the data to the Pi. I also included some of the code in "loop()" to illustrate the sequence of events.
To initiate a data transfer cycle, we first look for synchronizing pulses by running line 41 (see blog post of March 14 for this function). We stay here until we find valid synchronization. Once synchronization is confirmed, we move to line 42, the "manchester_data()" function. This will return data for one sensor - the sensor that was transmitting at the time. The code for the "manchester_data()" function can also be found at my March 14 post. If the CRC passes, we now read the first byte of the data to see how many sensors are connected to the enclosure. Now, that we know how many sensors, we can declare the array "xmit_data[]". This array will hold all of the data for all of the sensors for one measurement cycle. After populating "xmit_data[]" with the data from the one sensor we have acquired, we repeat "synchronize()" and "manchester_data()" for each of the other sensors in the run. Now, we have all of the data. Next is to see if the Raspberry Pi has called for the data.
We are now down at line 68 assuming there were no CRC errors. Line 68 takes us to the function "transmit_data()". The function looks to see if there is anything on the ATmega's serial bus (line 15). If so, it is read and put into the array "buff[]" by implementing "Serial.readBytes()". We are looking for one character, and that character is "s". If we find the "s", we place all the data, one byte at a time, onto the serial bus (via the print statement in line 19). If there was no request from the Pi for data, the contents of "xmit_data[]" will be overwritten with new data.

Python Code Running In the Raspberry Pi

The following is a fragment from a rather long program running on the Pi. The program handles setting up the log files for storing the data, sets up the graph parameters, obtains sensor information (description, high and low alarm settings, and resolution), calls the GUI program and incorporates the user responses, makes and records the temperatures, and creates the graphs. The entire program is here. This program calls another python program that handles the graphical user interface. That program is here.
The function "number_of_sensors()" is called once to obtain the sensor information: description, alarms, and resolution. Then, it is called every time the program calls for temperature measurements. When the function exists, if there was valid data, all of the data from all of the sensors will be in the global array "recv_data[]". In addition, the function returns the number of sensors in the run.
"number_of_sensors()" calls the function "retrieve_serial()". "retrieve_serial()" does the actual work of requesting data from the ATmega328P on the Gertboard, and retrieving the response from the Gertboard. When requested by "number_of_sensors()", it clears the serial port of lingering data, writes the character "s" to the serial port, and waits for a response from the Gertboard. It will check every second for data. If it sees no data, or if the length of the data does not equal a multiple of 20 bytes, it keeps looking. If it has not found valid data after 10 seconds, it gives up and returns "0". If valid data is seen, it returns the number of bytes received.

Saturday, April 11, 2015

Printing Leading Characters With Arduino IDE Serial Monitor

I have a blog post on calculating and checking the Cyclic Redundancy Check (CRC) byte when transmitting serial data. Here is a link. Near the bottom, is a sketch fragment of my CRC function and a table. The table illustrates how the CRC is calculated as the function progresses, bit, by bit, through the data. Here is the output I received from the Arduino IDE Serial Monitor:
That's really ugly. The serial monitor that is part of the Arduino IDE does not print leading zeros. I wanted to see those zeros. "CRC" is an integer, so has 16 bits. "inByte[i]" is 8 bits long. I wrote a small function to calculate how many leading zeros to print when printing a binary number. Then, I expanded it to to handle numbers of any base, and finally, to add any leading character, not only zeros. This is the output using my leading character function:
My leading character function follows. It is amazingly simple. However, it's not universal. It does not handle minus signs or decimal points:
The "pow" operator raises the value in "base" to the power of "position" - 1. For example, if you wish to print a 16 bit binary number, "factor" will be 215. You can see that "factor" can become a very large number, especially if you are dealing with decimal or hex numbers. Consequently, "factor" is a double, a floating point data type. However, the function may not work if "factor" wants to be too large. Just imagine if you wanted 16 characters of a decimal number. "factor" will be 1,000,000,000,000,000. A double data type for all Arduinos beside the Due is four bytes in length, not enough bytes to express 1015. The Due may be able to handle 1015 - it's double is 8 bytes in length.
Leading spaces can come in handy for printing dates. You can use it to line up dates like this:
  • April   9, 2015
  • April 10, 2015