I like PIC microcontrollers and I like programming in assembly language. In fact, over the past couple of years, I’ve posted about 40 projects on my website based on that combination. Recently I was ordering some parts from one of my favorite US vendors and I spotted an Arduino Nano, with a programming cable, for only $1.20 more than a bare ATMEGA328 processor chip. So I bought a couple of them. Then I downloaded the Arduino IDE and dusted off my memory of ‘C++’ programming.
This project is a mash-up of a clock that uses GPS for timing and an RF receiver that decodes weather messages from a common AcuRite sensor. The result is a small scale time and temperature display. The GPS clock and weather routines are set up as separate include files so it is easy to go into the main routine and configure it to do just the clock function or just the weather function. Just uncomment the appropriate “#define” at the top of the main routine if you only want one of the functions.
If both functions are used, then the top line of the LCD displays the local time and the bottom line of the LCD displays the humidity and temperature in both Celsius and Fahrenheit. If just the clock function is used, then the top line displays local time and the bottom line displays UTC. If just the weather function is used, then the top line displays the first sensor received and the bottom line displays any other sensor received. I added that capability because I have two weather sensors.
Step 1: Weather Sensor
The AcuRite weather sensor used here sends temperature and humidity information every 16 seconds. On the back it shows a model number of 000592TXR but it is typically advertised as model 06002M. This sensor is used by a lot of different weather station models so it is easy to find and I was able to get them on eBay for under $20. AcuRite sells similar looking sensors for some of their weather stations but they may or may not adhere to the same communication protocol. There is some indication on the web that the 00606 temperature-only sensor does use the same message format but with an invalid humidity byte.
As seen in the first waveform shown above, the weather messages are sent out in bursts with a 2ms gap between successive messages. The second waveform shown above expands part of one message in order to see the bit durations and patterns. There are four sync bits that are about 600us high followed by 600us low. The data bits are represented by 400us high followed by 200us low (1) or 200us high followed by 400us low (0).
The message format consists of 7 bytes of data. The first two bytes are the sensor ID and these do not change (i.e.: it does not use a rolling code). The last byte is a simple additive checksum of the first six bytes. The third byte is a battery level indicator and should always be 44 hex if the battery is good. The fourth byte is the humidity and it is an un-scaled value between 0 and 99. It is important to keep in mind that the most significant bit of bytes 4, 5, and 6 is a parity bit and is not part of the measurement values. Bytes 5 and 6 are the scaled temperature (Celsius) with the lower 4 bits of byte 5 being concatenated with the lower 7 bits of byte 6 to form an 11-bit value. The temperature is always represented as a positive number and only becomes negative when the scaling is applied. The scaling is (C / 10) - 100. The divide by 10 is required because the temperature resolution is in tenths of a degree. The subtraction is required because 100 is added by the sensor in order to keep the transmitted value positive.
Step 2: RF Receiver
The RF module I use for this project is the RXB6. It is a super heterodyne receiver as opposed to the less desirable super regenerative receivers. If you look at the cheap RF modules out there you will find that transmitter and receiver boards are often bundled together. Most of those bundled receivers are super regenerative types so they tend to have much lower performance characteristics (including range) than super heterodyne receivers. We only need the receiver module for this project because we will be getting signals from a weather sensor transmitter.
Step 3: RF Antennas
The RXB6 does not come with an antenna. You can buy some helical ones pretty cheaply but it is also easy to make your own antenna. In fact, a breadboard jumper cable could be slipped onto the antenna pin of the module if you don’t want to get too fancy. Ideally, a straight wire antenna would be 1/4 wavelength which works out to about 6.8 inches. I initially did the jumper wire thing and had no problem picking up my outside sensor even though my electronics workshop is in my basement.
Another possibility is to make your own helical antenna. There are a variety of plans for that on the web but the one shown in the picture above is what I made. I used some solid core wire from a scrap piece of Ethernet cable and wound it around the smooth shank of a 5/32 inch drill bit. Leave the insulation on except for the tip that solders to the RF board. You will need 20 turns. You could also use a 7/32 inch drill bit and wrap 17 turns instead. Any of these will probably work just fine for the ranges you are likely to have for your sensors. The real key is having a good RF receiver to begin with. The AcuRite sensors also have pretty strong transmitters.
Step 4: RF Communication Protocol
There are a few different modulation techniques for transmitting data but these sensors use the simplest which is OOK (on-off-keying) or ASK (amplitude-shift-keying). Since we are dealing with 0/1 data bits in this example, the amplitude is full on or full off. So, for our purposes, OOK and ASK are the same because OOK means the RF carrier is either full on or full off. The message format is generally defined by the manufacturer of the transmitting device and they can use pretty much any transmission rate, any bit formatting style, and any message length. The 433-MHz band is jammed full of transmissions for things like smart meters, etc. so the software needs to be tuned to filter for just the message format we want to use.
Step 5: Time Data
I use a cheap GPS unit in order to get accurate time data that will automatically restart after a power outage. I have several GPS units (without displays) that output the standard NMEA sentences but the smallest and cheapest of the units I have is the NEO-6M. The NEO-6M module is easy to interface to the Arduino because it uses a TTL-level serial port. The only real difference is that the NMEA standard specifies a serial baud rate of 4800 but the NEO-6M defaults to 9600 baud. You can run the free “u-center” program to change the baud rate but I just left it at the factory default. There is also a free utility program called GPSInfo (put out by Globalsat) that is very handy for viewing GPS information on the PC. You can hook up the GPS unit to a standard USB to TTL cable for checking it out or for setting it up using a PC. Keep in mind that the GPS chip on the module actually runs at 3.3 volts (via an on-board voltage regulator) so if you want to connect to its RXD port you should level shift down from 5 volts. The TXD port can connect directly to the Arduino or the PC.
Step 6: Time Zones
Displaying GPS time is an easy thing to do as long as you just want to display UTC (Universal Time Coordinated). The NMEA sentences are composed of ASCII characters which can be directly output to the LCD. The time portion is in the format of HHMMSS.FF (hours, minutes, seconds, and fractional seconds). For our clock the fractional part is not useful so all we need to deal with is six characters. The problem is that you then need to convert to your local time and to a 12-hour AM/PM format if you want that. But sometimes problems are what make life interesting so that’s what that portion of the software is really all about.
As for time zones, you might think that there would simply be 24 of them with 12 of them east of the UTC location (+ zones) and 12 of them west of the UTC location (- zones). In fact, there are a few oddball ones that are fractional hours and a couple that exceed the 12 hour “limit”. If you happen to live in one of those areas I apologize because my software only accounts for the 24 whole hour zones. There are also some of us who use Daylight Savings Time part of the year but that is not automatically accounted for in the software. That would require a lookup table of future dates, extra complexity in the software, and the need to update the software if the weeks of the year for the switchover changed. Instead, the hardware uses a momentary contact switch to allow for easy setting of the time zone (UTC offset).
Step 7: Schematic
The schematic is shown above and includes the connections for a 4-bit 1602 LCD interface. The serial data from the RF receiver is at digital logic levels so it is connected directly to one of the Arduino data input pins. The pin is configured in the software to perform an interrupt-on-change function so that we can measure the pulse widths. The GPS TXD output is directly connected to the Arduino RX input.
There are two switches used. As mentioned earlier, a momentary contact switch allows for setting of the UTC offset. The switch can be pressed at any time to enter the set mode. Initially, the display will show an invalid UTC offset of “+77”. Refer to the “Clock Software” section for UTC offset setting instructions.
The second switch is a simple on/off switch. In the “off” position the time will be displayed in 12-hour format (AM/PM) and in the “on” position the time will be displayed in 24-hour format. This switch can be changed at any time to toggle between formats.
If just the clock function is desired, then the RF receiver module does not need to be connected. If just the weather function is desired, then the GPS and the two switches do not need to be connected.
Step 8: LCD Software
I tend to use one of two types of LCD interfaces. One is the standard 4-bit interface and the other is a 3-wire interface that uses a shift register. I designed that interface when I was working with small PIC microcontrollers that had a limited number of I/O pins. I used the 4-bit interface for this project but have my own LCD include file instead of using the generic Arduino LCD library. That reduces memory consumption and code complexity and also allows me to tweak the code for specific projects like this one.
Step 9: Clock Software
The GPS unit outputs standard NMEA-0183 sentences which are ASCII strings that contain a variety of information. For this application I chose the GGA sentence to get the time information because that is the sentence I used for a previous GPS project. Fields of information in NMEA sentences are separated by commas so, after the GGA sentence header is detected, the software would normally count commas and call the appropriate routine for each desired field of GPS information. Only the time information is needed here and that is in the field after the first comma so no counting is needed.
The six time digits (HHMMSS) are buffered and then processed after all of them are received. The GPS may output some incomplete messages early on so the buffering routine verifies that each character is an ASCII numerical value. If a bad character is received, the message is discarded. This may also happen on rare occasions during normal operation, particularly if the serial port communication drops a bit. I’ve only seen this once and all that happened is that the time paused for a second and then jumped two seconds instead of one.
If the software is configured to only do time display, then the first line of the LCD will display local time and the second line will display UTC. For UTC the software just sends the ASCII characters directly to the display routine, with colons (:) inserted appropriately.
In order to convert the UTC to local time, the UTC offset (time zone) must be applied. Because the UTC time from the GPS is in ASCII format, the software converts the ASCII hour characters to decimal and then adds the UTC offset. The UTC offset is stored as a positive BCD value with a sign bit so it is first converted to an integer value and then negated if the sign bit is set. Once the local time hour value is calculated, a lookup table is used to convert it to BCD, and then the BCD is converted back to ASCII for display. The lookup table needs to handle the 24 hour UTC format as well as +/- 12 time zones. In order to do this, the UTC times from 0000 to 2300 occupy the middle 24 entries in the table with 12 entries before and 12 entries after to account for the time zones. One table is in 12-hour format so I also added a lookup table for the AM/PM part of the display. The other table is in 24-hour format. As mentioned earlier, an on/off switch allows for selection of the 12-hour or 24-hour format.
The time zone is retrieved from EEPROM during initialization and briefly displayed. If it has not been set at least once then the setting routine is called. The setting routine can also be called at any time by pressing the momentary contact switch. The setting routine will initialize the display to “UTC OFFSET +77”. A short press of the switch will change the value to “-00”. If a positive time zone is required, then another short press will change the value to “+00”. A long press (> 1 second) will move the setting mode to the next step. At this point each short press will increment the time value up to a maximum of 12. After reaching the desired time zone, press and hold the switch for greater than 1 second and then release it. The software will then save the UTC value in EEPROM and briefly display “OFFSET SAVED”. If you make a mistake during entry, just exit and then press the switch again to reset it.
The NEO-6M doesn’t require a good position fix in order to output the time so it should output messages as soon as it gets one satellite. Until then the display will read “NO DATA”.
Step 10: Weather Software
The PIC microcontroller has the capability to gate a timer on/off using an external pulse. That same input pulse can also be used as an external interrupt to signal a read of the duration of the pulse. The Arduino doesn’t have that exact capability so I used the interrupt-on-change function. On one edge of the RF message pulse the current microsecond time is saved by the interrupt handler. On the opposite edge the elapsed time is calculated to determine the pulse width.
The software has a “DEBUG” define that allows for display of the raw data format of the received messages. There is also a define to specify the Arduino input pin for the serial stream from the RF receiver. The software is set up to calculate the appropriate interrupt-on-change register settings based on this define. The calculation only works for the Arduino digital pins. An analog pin could be used instead but that would require hard coding of the register values.
The interrupt handler determines if the captured count is long enough to be a start pulse. As mentioned earlier, the gap between multiple messages is 2ms so that is what the software looks for. Because of all the 433-MHz traffic, the initial screening in the software makes sure that the measured time is at least 1.8ms but not greater than 2.4ms. After the start is detected the software looks for the sync bits (600us) and counts to make sure that four of them are received. Once these tests are passed, the software looks for the proper bit times of 200us and 400us.
Received bits are formed into bytes and each byte is saved. After seven bytes are received the message checksum is verified before further processing is allowed. If raw bytes are to be output (debug mode), then the bytes are converted to ASCII characters and sent out to the LCD. If humidity and temperature outputs are desired, then the appropriate conversions are performed.
The two bytes of Centigrade data in the RF message are mashed together to form an 11-bit value. The lower portion is shifted left one bit to eliminate the parity bit and to align it with the bits in the upper portion. The two bytes are formed into a 16-bit word variable and then the whole thing is right shifted one bit to get the final bit alignment. The word variable is then converted to a floating point variable for the math calculations.
One big advantage of using C++ on the Arduino versus assembly language on the PIC is that it simplifies the math calculations. As mentioned earlier the Centigrade conversion is (C / 10) -100. The result is converted to a string and sent to the LCD for display. The Fahrenheit calculation is (C * 1.8) + 32. The result is again converted to a string and sent to the LCD for display. In both cases, the String conversion includes the negative sign (if appropriate) and the decimal point. A check is made for the decimal point to ensure that only one character after the decimal is sent to the display. That check is needed because the string can range from 3 to 5 characters in length.
I have two AcuRite sensors so I added a check in the software to ensure that the data for one doesn’t overwrite the data for the other if the software is set to only do the weather function. The first sensor received after power up gets displayed on line 1 and the other is displayed on line 2. By using the debug mode, I can see what the ID is for each sensor so I could make a simple check in the code if I only wanted to process data from one of them.
The software monitors the battery status (byte3) and displays a message if it indicates a low battery. This message overwrites all other data for that sensor.
Step 11: Displays
Here are some example displays for the various functions. I have a few other Instructables but most of my PIC microcontroller projects can be found on my website at: www.boomerrules.wordpress.com
Participated in the
Arduino Contest 2019