Summary: In part 1, I documented the Efergy Elite’s radio protocol and wrote a PIC program that ran in a tight loop decoding the data and sending it to a serial port. This post improves upon the decoding technique used allowing for other applications (eg temperature monitoring) to multi-task on the same PIC. A simple control loop multitasking framework using interrupts to capture time critical events is explained.
The ultimate goal of this project is a electricity consumption monitor that provides real time feed back and also logs historical power use for analysis and report generation. For the moment my focus is feeding the data to a PC/laptop. In reality a more power efficient logging device will be needed. But that's for a future post.
Recently I wanted to update this receiver to include data from temperature sensors and output data from both the Efergy Elite and the temperature sensors to a single serial port.
However the tight timing loops which are continuously running make it near impossible to perform any other task. Fortunately the PIC has lots of useful hardware that can be brought to bear on this problem. Timing pulses with a loop is rather clumsy, inaccurate and wasteful of system resources. Almost all PICs have timers and interrupts which can be used for this task.
By using this peripheral hardware a lot of the decoding grunt work can be happen silently in the background, freeing up the CPU for other stuff.
Simple Multitasking with a PIC
Although not a proper multitasking processor such as a Pentium, a very simple form of multitasking can be easily achieved with a PIC. This comprises a master loop which calls application service routines for each application in turn. This is often called “simple control loop multitasking”.
The application service routines check to see if there is any work to be done. If not it immediately exits and the control loop then pass control the the next application service routine. If there is work to be done, the application service routine attempts to tackle as much as can be achieved in it’s allocated time. If all the work cannot be completed in time, it must relinquish control prematurely and continue the task on the next call.
void main (void) { // Initialize apps initAppA(); initAppB(); initAppC(); initAppD(); // Main loop while (1) { appA(); appB(); appC(); appD(); // Clear watchdog timer CLRWTD(); } }
Architecture diagram of simple control loop multitasking with interrupts used to capture time critical events. |
In order to be responsive the main loop must iterate many times a second. The iteration frequency will depend on the nature of the application(s). 1kHz is a common choice. At 1kHz the entire loop must complete in 1ms and each app service routine must take no more than 250µs (assuming there are 4 applications requiring equal CPU time).
These routines must be written so as not to use more than their allocated time slice. If a task cannot be accomplished in that time frame it must be broken into smaller work units.
Unlike a modern preemptive multitasking operating system such as Linux these constraints are not enforced. A badly written service routine will affect the performance of the entire system. For example a deadlock in one application will cause the entire system to fail. One safeguard against rogue applications is to enable the Watchdog Timer and clear it on each iteration of the loop. Should one application lock up the system will reset.
Interrupts can be used to capture time critical events. The ISR (Interrupt Service Routine) is called (almost) the instant the event occurs. Its job is to store the event for the attention of the application service routine whenever it gets control of the processor. It’s often a good idea to buffer these events in case more than one arrives before the service routine gets its turn to run.
Efergy Elite decoding
A quick recap on the Efergy Elite setup. A sensor mounted in the utility meter box transmits by 433MHz radio a power reading to the display unit every 6, 12 or 18s (configurable). On the display unit PCB I tapped into the radio base band output and connected this to a low cost PIC MCU. It does the job of decoding the signal and sending a nicely formatted record to the PC via serial IO.
Bits are encoded on the base band signal using digital FSK (Frequency Shift Keying). Three square wave cycles of 2ms duration in total (ie 1500Hz) is a logic 0 and 4 square wave cycles of 2ms duration (ie 2000Hz) is a logic 1.
Each packet is prefixed by a synchronization header of 0xAB 0xAB and 0x2D. This is followed by 8 octets (bytes) of data and one octet checksum. See previous post [link] for details.
The decoding process starts with the radio hardware which outputs the base band signal. Using pulse width measurements the symbols (bits) are decoded. First the bits are are shoved onto a 16 bit shift register until the end sync header is identified (ie the contents of the shift register will read 0xAB2D). Then 8 octets of data is read followed by one checksum octet (the checksum is the arithmetic sum of the 8 data octets).
The choice of what to implement in the ISR and what to implement in the application service routines is sometimes not clear. The rule of thumb is to keep the ISR as small and fast as possible. I had originally considered using the ISR to time pulses and send pulse durations to the application layer. As there can be as little as 250µs between pulses the entire main loop would have to execute in under that time. That’s only 250 instructions on a 4MHz clocked PIC. Not enough!
For just a few more instructions the pulse to symbol decoding can happen in the ISR thus allowing for up to 2000µs between application service routine calls.
To improve matters further, instead of storing one symbol at a time, I push the decoded symbols into a buffer (implemented as a 8 bit shift register) allowing for up to 16000µs between calls.
For the first attempt I connected the radio base band signal to the INT pin (RB0), traped INT interrupts and used Timer1 to measure the pulse widths. Timer1 is a 16 bit timer which can be clocked using the system clock divided by 4. If you’re using the 4MHz internal clock that’s a convenient 1µs per clock tick.
There are a few gotchas when using interrupts. For example: variables shared between the ISR and main program must be declared with the “volatile” modifier. When accessing state information which can be manipulated by the ISR, it is often necessary to briefly disable the interrupt while reading to ensure that an interrupt does not case the state to appear corrupted.
Measuring pulse width with INT interrupt. |
I’m measuring inverse pulses (ie the duration of time when the signal is at low voltage). So when waiting for a pulse to arrive, set to trigger on a falling edge. In the ISR, record the clock and set trigger mode to rising edge. The next invocation of the ISR will occur at the end of the pulse. Subtract the clock from the previously recorded value -- that is the pulse width. Now reset INT to trigger on a falling edge again for the next pulse.
An alternative implementation using Capture/Compare
Microchip PICs have so much peripheral hardware that there is often several ways of solving the same problem. The Capture/Compare module is probably better suited for this problem. The INT approach does have one advantage though: if there is nothing else to do the device can be put in low power mode with the SLEEP instruction. Activity on INT will automatically wake the device up. I’ll post that implementation in a separate blog post.
The main control loop
My main loop looks like this:
// Main control loop. while (1) { // AppA: Decode Efergy Elite signal. if (efergy_elite_interrupt_decode()) { // Have a complete record // Extract single phase power reading (12 bits) power = ((ee_buf[3]&0x0f) << 8) | ee_buf[4]; // Display full record on the serial port for (i = 0; i < 8; i++) { writeHex(ee_buf[i]); putch (' '); } crlf(); } // AppB: Blink LED if power use above threshold if (power > POWER_THRESHOLD) { // Cause LED to blink every 4096 iterations if ( (t&0x0fff) == 0) { DEBUG_PIN = !DEBUG_PIN; } } else { // LED off if below threshold DEBUG_PIN=0; } // Increment iteration counter t++; // Clear watchdog timer CLRWDT(); } }
This has just two simultaneously running tasks:
- One to decode telemetry from the Efergy Elite and write the data to the serial port
- One to blink a LED if power levels are above some threshold
The LED blinker (AppB) illustrates how to write these application service routines. A simplistic approach would be to compare power consumption against the threshold then turn on the LED, execute a delay loop for 0.5s, turn the LED off and delay again for another 0.5s. That will work, but violates the principle that the code should execute as quickly as possible (taking a whole second to complete a blink).
A smarter approach is implement LED blinking with a timer. I added an iteration counter to the main loop which acts as a low accuracy timer (it seemed easier than using the PICs built in timers). This way the code only takes a few instructions to execute.
The code which writes the record data to the serial port could be optimized in a similar way, writing just one character to the serial port per iteration. But that's an exercise for another day.
The Hardware
You will need the following:- Efergy Elite electricity monitor (obviously)
- A PIC 16F627A or 16F628A (or any other PIC, but you may need to modify the code a little).
- 1 LED
- 1 resistor
- A suitable PIC programmer. I use a PICKit2 from Microchip (about $30)
- Breadboard (or other prototyping system)
- Logic level Serial IO to PC interface (see text below)
Schematic of Efergy Elite decoder using a PIC 16F627A or 16F628A (or equivalent). |
- FTDI cable available from SparkFun.com (DEV-09718), CoolComponents.co.uk and many others. Cost about $18.
- The Dangerous Prototypes Bus Pirate available from SeeedStudio.com ($30 -- but you get a whole load of extra functionality with that. Well worth it.)
- An old Nokia phone cable
- Any development board that features a USB/Serial IO chip can probably be easily hacked for this purpose, including the Arduino, TI MSP LaunchPad etc. (Although you'd probably want to ask yourself if you are better off using the MCU on the development board instead of a separate PIC. If you want to see this decoder implemented for other MCUs leave a comment at the end of the blog post and I'll see what I can do.)
Annotated photograph of setup required to decode telemetry from a Efergy Elite electricity monitor and relay to PC. |
A note on powering the PIC: as this is currently very much a prototype I haven't considered how I might power this device. Right now I'm using the PICKit2 to supply power via the ICSP (3.3 - 5V should work). Practical options are to pull the battery positive terminal out of the Efergy Elite display unit and use that to power the device. If connected to a PC via a FTDI cable, these cables usually have a +5V line which will deliver plenty of current. Or use a separate battery or wallwart power supply.
If running successfully this program will output to the serial port each record from the power sensor as it arrives (every 6, 12 or 18s). The output looks something like this:
EM 0.2.0 00 0D 5A 40 6E 00 01 00 00 0D 5A 40 6E 00 01 00 00 0D 5A 40 73 00 01 00 00 0D 5A 40 6E 00 01 00 00 0D 5A 40 6E 00 01 00 00 0D 5A 41 BF 00 01 00 00 0D 5A 41 BE 00 01 00
As for implementing the temperature sensors I mentioned at the start -- that's for another day. So right now it’s not a terribly exciting application, but could form the basis of something more useful.
The code can be downloaded here. It’s written for the HI-TECH PICC compiler, and has been tested on PICs 16F627A and 16F628A. It should be easy to adapt to other PIC compilers and PIC devices, or indeed other MCU families.