Showing posts with label PIC. Show all posts
Showing posts with label PIC. Show all posts

Monday, February 13, 2012

Getting started with the PIC24FJ64GB002 and PIC32MX110F016B


Microchip's new MPLABX development environment (free download) came out of beta a few weeks ago and the PICkit3 programmer/debugger was on special offer. So I figured this was as good a time as any to pick up a PICkit3 and to get a 'hello world' program running on the PIC24FJ64GB002 and PIC32MX110F016B samples I had in my parts drawer. Both are available in 28 pin DIP packages. (Can't beat DIPs and breadboards for quick prototyping!)

Programming the 8 bit PIC range such as 12F and 16F series with the PICkit2 programmer is simple: just connect the five ICSP pins MCLR, Vdd, GND, PGD, PGC from the programmer to the corresponding pins on the chip. Nothing else is needed. The chip can then be probed and programmed. I prefer to use the command line tool.  For example to verify that a chip was present:
pk2cmd -P
and then I'd upload myprogram.hex firmware with something like
pk2cmd -P -Fmyprogram.hex  -M -R
and apply power at 3.3V for testing (internal oscillator must be configured if there is no external crystal):
pk2cmd -P -T -A3.3

The PIC24FJ64GB002 and PIC32MX110F016B chips are not as straight forward. My naive attempt at treating them like the 8 bit PICs didn't work. I noticed searching Microchip's forums that others were have difficulty also. Like most problems relating to programming MCUs the answer is in the datasheet. You either need to read it cover-to-cover or fumble around and experiment. I'm still not sure which approach gets you there faster, but the latter is more fun :-)



There are a few important differences when compared to the older 12F and 16F series. These chips use a dual power supply. The CPU core runs at a lower voltage (usually 2.2V) and the on-chip peripherals run off a higher voltage (usually 3.3V). The core power can be configured to be supplied from an on-chip source or externally. The on-chip supply is selected by grounding the DISVREG pin and connecting Vcap to ground via a 10µF capacitor [Reference PIC24FJ64GB004 family datasheet, section 2 "Guidelines for getting started with 16-bit microcontrollers"].

Another important difference: the chip must be powered up, even for probing and programming. This is not necessary for the 8 bit PICs that I've been using. This one threw me for a bit: MPLABX with PICkit3 kept telling me no target was connected. Finally I twigged that I should have power applied:
right-click on the project and select 'Properties', select PICkit3, select the Power category from the pull down menu, select the desired voltage (usually something close to 3.3V) and check "Power target circuit from PICkit3" checkbox.



So to summarize the setup for both chips (electrically identical, just different names/functions on some pins).

PIC24FJ64GB002
  • MCLR (pin 1) to programmer MCLR
  • 10k resistor (approx) between MCLR and Vdd
  • Vdd (pin 13) to programmer Vdd
  • Vdd (pin 28) to programmer Vdd
  • Vss (pin 27) to programmer Gnd
  • Vss (pin 8) to programmer Gnd
  • PGED (choose from port 1,2 or 3) to programmer PGD
  • PGEC (chose from port 1,2 or 3) to programmer PGC. Must be the same port as that used for PGED.
  • Vcore (pin 20) to 10µF capacitor to Gnd
  • DISVREG (pin 19) to Gnd
  • Decoupling capacitor between pins 28,27
  • Decoupling capacitor between pins 13,8
PIC32MX110F016B
  • MCLR (pin 1) to programmer MCLR
  • 10k resistor (approx) between MCLR and Vdd
  • Vdd (pin 13) to programmer Vdd
  • AVdd (pin 28) to programmer Vdd
  • AVss (pin 27) to programmer Gnd
  • Vss (pin 8) to programmer Gnd
  • PGED (choose from port 1,2 or 3) to programmer PGD
  • PGEC (chose from port 1,2 or 3) to programmer PGC. Must be same port as that used for PGED
  • Vcap (pin 20) to 10µF capacitor to Gnd
  • Vss (pin 19) to Gnd
  • Decoupling capacitor between pins 28,27
  • Decoupling capacitor between pins 13,8
You should now be able to program the chip with MPLAB and the PICkit3.

Finally the configuration bits must be set just right if you want the PICkit3 in-circuit debugger to work. The syntax used to defining the configuration bits seem seems to vary from compiler to compiler. For the PIC24FJ64GB002  this is what worked for me using the Microchip C30 C compiler version 3.30c:

_CONFIG1(JTAGEN_OFF
        & GCP_OFF
        & GWRP_OFF
        & ICS_PGx2  // NB: set to correct channel
        & FWDTEN_OFF
        & WINDIS_OFF
        & FWPSA_PR32
        & WDTPS_PS8192);
_CONFIG2(IESO_OFF
        & FNOSC_FRCPLL
        & OSCIOFNC_ON
        & POSCMOD_NONE
        & PLL96MHZ_ON
        & PLLDIV_DIV2
        & FCKSM_CSDCMD
        & IOL1WAY_OFF);
_CONFIG3(WPFP_WPFP0
        & SOSCSEL_IO
        & WUTSEL_FST
        & WPDIS_WPDIS
        & WPCFG_WPCFGDIS
        & WPEND_WPENDMEM);
_CONFIG4(DSWDTPS_DSWDTPS3 
        & DSWDTOSC_LPRC
        & RTCOSC_LPRC
        & DSBOREN_OFF
        & DSWDTEN_OFF);



The configuration bits I found that worked for the PIC32MX110F016B (Microchip C32, v2.02) are:

#pragma config FPLLODIV = DIV_1 
#pragma config FPLLMUL = MUL_20 
#pragma config FPLLIDIV = DIV_2 
#pragma config FWDTEN = OFF 
#pragma config FPBDIV = DIV_1 
#pragma config WDTPS = PS1 
#pragma config FCKSM = CSECME 
#pragma config OSCIOFNC = OFF 
#pragma config POSCMOD = HS 
#pragma config IESO = ON 
#pragma config FSOSCEN = OFF 
#pragma config FNOSC = PRIPLL 
#pragma config CP = OFF
#pragma config BWP = OFF 
#pragma config PWP = OFF 
#pragma config ICESEL = ICS_PGx2 // NB: set to correct channel
#pragma config DEBUG = OFF

Note that both of these chips have several programming ports. Any can be used for programming. However for debugging you must specify which port is being used in the configuration bits.




Saturday, April 16, 2011

'Smart' electricity meter based on Efergy Elite monitor, part 2

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.
The PIC can be programmed to trigger INT on a rising edge or falling edge. But not both at the same time. To measure pulse widths I’ll need both. The work around is to alternate the triggering mode.

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
Instead of putting each application into a separate service function as in the template above, I've got blocks of code in the main loop as the code is short and simple.

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)
This hack requires that you solder a wire to the base band pin or test pad of the Efergy Elite receiver unit. See my previous blog post on how to do this.  You will also need a wire for the battery ground. Beware that this will void your warranty -- so only proceed if you know what you are doing.
Schematic of Efergy Elite decoder using a PIC 16F627A or 16F628A (or equivalent).
Once upon a time interfacing a serial IO port to a PC was a straightforward if somewhat laborious task. PCs had a RS232 port which used +/- 13V levels which was not compatible with logic levels (usually 0 and 5V). You needed a level converter. A MAX232 chip + associated capacitors being one of the most popular solutions. Fortunately, nowadays the RS232 is very rarely found on modern equipment. So how do you interface serial IO at logic levels to a PC? There are many options available, and sadly may require that you purchase some hardware. But the good news is that all of these options are useful tools which can be repurposed for other projects. Here are some options:
  • 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.

Tuesday, February 8, 2011

Battery monitoring using a diode/LED and resistor

Summary: A common problem with wireless battery powered sensors is knowing when the battery is about to expire in sufficient time to replace the battery before it fails. This post outlines a method that achieves this with nothing more than one resistor, one diode (or LED), one ADC input and one digital output. A practical application using a PIC 12F675 is described and some results presented.

There are many approaches to the problem of gauging a battery State-of-Charge (SoC) or battery low indicator. Modern "smart batteries" maintain a SoC by use of complex algorithms combining battery/cell voltage, 'coulomb counting' and a battery charge/discharge history.

For small devices based on microcontrollers (MCUs) like the PIC 12F675 the only practical metric is battery voltage under load. But measuring this is not as straight forward as it may first seem.

Many MCU analog to digital converters (ADCs) will measure from 0V up to power supply voltage (Vbat or Vdd). Therefore connecting the ADC to the battery will always report the full scale value (Amax) by definition. Not very useful.

Here is an idea:

Between Vbat and 0V connect a resistor and diode in series. Connect the center point (Vd) to the ADC. Vd is voltage drop across the diode which will be a constant value – about 0.7V for a silicon diode. If A is the ADC reading, and Amax is the full scale value of the ADC then Vbat = Amax * Vd /A.

As the battery voltage dwindles the value of A will increase because the fixed diode voltage drop (Vd) forms a bigger fraction of Vbat.

The design can be further improved: in the above configuration, current (I = (Vbat - Vd) / R) is being continuously drawn which will have a detrimental effect on battery life. Current is only required to flow when a measurement is being taken. So the resistor + diode circuit is connected to digital output line (Dout) instead. When set to logic 0 no current will flow. When set to logic 1 the voltage applied will be almost the same as Vbat.


A further improvement is to replace the silicon diode with a LED.  This way the battery measurement circuit can double as a visual indicator (eg a periodic heartbeat).

One point to note about LEDs: the junction voltage drop (Vd) is significantly higher than that of a silicon diode and varies considerably depending on the color and chemistry (about 1.7V for red and as much as 4V for other 'exotic' colors). You will need to consult the manufacturer's datasheet or measure it yourself (most digital multimeters have this function). Also bear in mind that the diode voltage drop needs to be less than the application's minimum supply voltage, which might rule out use of green or blue LEDs if operating on 2 x NiMH cells in series.

Here is a real application – a wireless environment sensor powered by 2 x AAA NiMH cells in series using a PIC12F675  MCU. The sensor unit periodically transmits environment data to a receiver which is directly connected a server running data acquisition software.

The LED is a typical red LED (Vd = 1.7V) and the series resistor is 3.3kΩ. The radio transmitter is a Holy Stone Enterprise Co. MO-SAWR-A (sold by SparkFun, SKU WRL-08945 @ $4 each). The environment sensor circuitry has been omitted as it's not relevant to this post.

The 12F675 has just 6 IO/analog pins so it's important to maximize use of each pin. In this configuration, pin GP4 is configured as a digital output and serves three functions:
  • Applies a voltage to the resistor-diode network for battery voltage measurement
  • Illuminates a red indicator LED
  • Acts as the baseband signal input to the radio module (a high impedance input, so it will not interfere with the battery measurements)
A desirable side-effect of this arrangement is the LED will illuminate every time the radio is used. This serves as heartbeat / activity indicator.

Another desirable side-effect is that the the transition from logic 0 to logic 1 will energize the radio module, drawing a relatively large current (about 10mA, compared to about 0.5mA consumed by an active 12F675). Battery voltage is best measured under the maximum possible load for the application. (A completely depleted battery can still show a healthy 'open circuit' voltage). Since the radio module requires a 20ms warm up period prior to transmitting data, I use this opportunity to take a battery voltage measurement, ensuring that none of the precious battery charge goes to waste.

The following chart is what the battery voltage vs time look like. I'm assuming Vd is 1.7V.


The chart looks like the classic NiMH/NiCd battery discharge curve (which is similar to alkaline cells too). There is an initial voltage drop at the start of the cycle, followed by a relatively flat curve for most of the discharge cycle and a sudden drop near the end of the charge cycle. If you're curious about the small 'bumps' see footnote [1].

In applications such as laptops and electric cars it's important to know the State-of-Charge at any point in time (as a percentage, kWh, miles or time remaining depending on the application). In my application this is not important. All I require is to get an battery low alert in sufficient time to act on it (a few days will do).

So how to determine the battery low condition? And who's job is figure this out – the hardware constrained sensor/transmitter MCU or the substantially more powerful receiver/data acquisition server. Or both?


Making the battery low determination at the data acquisition end has the advantage of having a powerful CPU with megabytes of RAM available for the task. It makes sense to transmit the battery voltage as part of the data packet for this purpose. But my sensor unit only has a radio transmitter, so it's not possible to communicate the battery low status back to it.

Ideally whatever algorithm is used should be simple enough to run on the limited resources of the sensor MCU. Detecting battery low condition here has advantages. For example it could conserve charge by decreasing the sampling frequency.

I can see two approaches:

  • An absolute battery voltage threshold
  • A rate of change of battery voltage threshold

In theory the absolute voltage threshold is simple to implement. You figure out what is the minimum voltage required for reliable operation (about 2.2V in this application). Then add 100mV or 200mV to that value to arrive at the battery low threshold. The problem with this approach is that the threshold may be in the rapidly decaying part of the battery discharge curve giving little or no useful warning. If set too high the warning kicks in way too early. Getting this right is going to be tricky.

The rate of change approach has its own problems. Here is a chart of the rate of change of ADC values vs time with ADC vs time for comparison. Remember that ADC values increase as the battery voltage decreases.


The problem is the difference between ADC readings from one sample to the next is most likely going to be exactly zero ­– there is insufficient resolution available in the ADC. Some sort of low pass filter (LPF) or sub-sampling needs to be applied to the signal first. The following is mean ADC values in 1 hour bins and the time derivative of that.


Setting a dADC/dt threshold of 2 units / hour will catch the end of battery decay, with perhaps the odd false alarm (eg at day 18). But the capability of performing 1 hour averages is pushing the capability of the 12F675 (considering all the other tasks that need to be performed must be stuffed into its tiny 1Kbyte program memory). Sub-sampling is an alternative. This chart was obtained by sampling one in every 360 samples:


It can be seen to be almost identical to the 1 hour bin chart. Sub-sampling is certainly do-able on a 12F675.

Unfortunately I don't yet have enough data to decide which approach is best. Each battery discharge cycle is over two weeks long and right now I have only one sensor utilizing 2 x AAA cells. It has been running since November 2010. Interestingly a second identical sensor powered by 4 x high capacity AA NiMH cells has been running continuously on the same discharge cycle since November 2010 (it's now February 2011)!

I will post a further analysis when more data has been obtained.

Conclusion:

Measuring the ADC value of the voltage drop across a diode (signal diode or LED) seems to be a feasible way of gauging the battery voltage of a battery powered device. In a hardware constrained MCU like the PIC12F675 the measurement circuit can double as a LED indicator (and other functions) making maximum use of precious IO pins.

However translating this voltage to a reliable State-of-Charge or battery low indicator is a trickier problem and not one that I've fully solved yet.



Footnote 1: It is interesting to note there are small 'bumps' on an otherwise smooth voltage-time curve. It can be seen to correlate with ambient temperature. I'm assuming this is due to the effect of temperature on cell voltage.

Tuesday, June 29, 2010

PIC Serial IO Example

I’ve been working on the PIC firmware for my ABPM project over the past few weeks and I thought it would be useful to combine many of the tips and tricks I learned into a little demo C program. I hope this will help others get up and running fast with similar applications.

The key features of this program are:
  • control and configuration via serial IO cable which can be detached when not required
  • very low power consumption – must run on batteries and last months
  • simple timing functions (eg awake every 20 minutes, take a reading, go back to sleep)
This example C program incorporates elements of all the above requirements. To get this setup up and running you will need the following:
  • a PIC 16F627A, 16F628A or equivalent. Other PICs of with similar functionality may work but a re-compile many be required. If ordering PICs make sure you order it in a plastic DIP package.
  • a prototyping breadboard
  • some way to interface logic voltage level serial IO to a computer (a discussion on this in a moment)
  • a PIC programmer (eg PICkit2), ideally configured to program in-circuit
  • one LED, a few resistors and some suitable jumper wires for the breadboard



Interfacing a PIC to a PC using Serial IO

Serial IO is a theoretically a very simple way of communicating with a PIC. Most PICs have hardware support for this protocol and until a few years ago most PCs had one or more serial ports (in the form of a 9 pin D connector RS232 port). Unfortunately even though the PC and the PIC speak the same serial IO ‘language’, they do so at incompatible voltage levels, so a direct connection is not possible. There are hacks involving resistors, but it doesn’t always work. The easiest ‘correct’ solution is to use the MAX232 chip which converts logic to RS232 voltage levels.

Leaving aside the voltage level problem, a more serious problem is that RS232 ports are increasingly rare having been replaced by the substantially faster and more flexible USB. Interfacing directly with USB is not trivial without the use of a support chipset such as the FTDI FT232RL.

So there are two approaches to achieve PC connectivity:
  1. Use USB <--> RS232 adapter (about €25) at the PC end and a MAX232 for voltage conversion at the PIC end
  2. Bypass the whole RS232 voltage level problem by using a Bus Pirate to act as a bridging device. The Bus Pirate costs about €25 and will avoid the complication of adding a MAX232 to your project.
I opted for approach #2 using a Bus Pirate.

Configuring a Bus Pirate to act as serial IO bridge

Connect to the Bus Pirate with a terminal emulator (eg minicom on Linux). At the Bus Pirate prompt enter:
m(return) (select mode) 
3(return)  (select UART mode) 
6(return)  (select baud rate of 19200)
(return) (select 8 bits, no parity - default)
(return) (select 1 stop bit - default)
(return) (select idle 1 - default)
(return) (select open drain, high = high impedance, low = GND)

Because we have chosen high to be represented by a high impedance state, a pull up resistor (eg 22K) to Vdd is required (see schematic).

Connect the Bus Pirate ground to Vss/GND, MISO to PIC TX and MOSI to PIC RX. Finally select the bridge mode macro: (1) In response to message “UART bridge. Space continues, anything else exits” press the SPACE key. Note: you can only exit this mode by power cycling the device.

Programming the PIC

A socket programmer can be used such as the Velleman K8048. But for development work the constant plugging and unplugging of the chip is a colossal waste of time and will quickly damage the chip’s pins.

Configuring a PICkit 2 as in in-circuit programmer is a far better option. In this configuration the 5 pin ISCP port (Vpp, Vdd, GND, PGD, PGC) is connected to corresponding pins on the PIC. If the Vpp pin is also used in your application (it can double as RA5 or MCLR) a protective diode should be utilized to protect your application circuitry from the ~ 13V programming voltage.

The ISCP port also optionally provides power to the circuit but there are current limits to be aware of: 25mA in the case of a PICkit2. Exceeding that may cause the USB port to switch off (although permanent damage is unlikely). Please refer to chapter 3 of the “PICkit™ 2 Programmer and ICSP™” guide downloadable from http://www.microchip.com for details on the operation of the ICSP port.

Loading the program from Linux

Download the PICkit2 command line Linux utility from http://www.microchip.com/


pk2cmd -PPIC16F627A -FSIODemo.hex -M -T -R

You can power off the PIC at any time with this command:

pk2cmd -PPIC16F627A -W

and power it back up with:

pk2cmd -PPIC16F627A -T -R


Loading the program from Windows

From PICkit 2 Programmer (free download from http://www.microchip.com) choose File -> Import Hex. Then click the “Write” button and if all goes well the status window should turn green with the message “Programming Successful”.


 

Running the program

After successful upload of the firmware to the PIC, all going will you should see a welcome message “Demo PIC application. Version 1.0” on the serial port. You can now enter commands at the prompt. These commands are implemented in the execCmd() function. Additional commands can be added as programming space allows. A command comprises a single letter (upper or lower case) followed by optional arguments and terminated by the RETURN key. 




Command summary:


L no params Query LED state. Will return “ON” or “OFF”.
L ‘0’ or ‘1’ Turn LED on or off respectively.
L ‘t’ Toggle LED state
R no params Reset device
T no params Return toc timer
Z no params Put device to sleep. Will confirm with “SLEEP” message. Hit any key to wake device. Will display “WAKE” message on waking.

Command error codes:

On successful command execution an “OK” message will be displayed. If an error is encountered “ERR” followed by an error number will be displayed.

ERR 2 Unrecognized command
ERR 3 Bad command parameters

Compiling the C code

As it stands this application doesn’t do anything very useful. You will need to edit the C to customize for your application.

You will need the MPLAB IDE from Microchip (a free download at http://www.microchip.com). This is a Windows application, but will run under Wine emulation on Linux (just run the installer in Wine).

Download the ZIP archive from http://wombat.ie/software/electronics/PIC/ and unpack it into a directory.

In MPLAB chooise Project -> Open... from the menu and navigate to the directory into which you unpacked the ZIP archive. Choose the SIODemo.mcp project.

Select Configure -> Select Device... and ensure you have the correct target chip selected.

Key CTRL-F10 (or Project -> Project Rebuild from the menu) to rebuild everything.

If all goes well you should have SIODemo.hex in the same directory. Upload the HEX file to the device with the programmer.


The Code

The code and compiled HEX file can be downloaded from http://wombat.ie/software/electronics/PIC/

/**
 * Example of a serial IO shell for a PIC.
 * Rev 27 June 2010.
 * (c) 2010 Joe Desbonnet, jdesbonnet@gmail.com.
 * Distributed under the "Simplified BSD License".
 *
 * This example is intended as a starting point for writing low power PIC apps
 * that need to be controlled over a serial port.
 *
 * This program will listen on the PIC's serial port for commands sent from a 
 * human user via a terminal emulator or commands issued from another application. 
 * In this example commands are L, L0, L1, Lt (LED status, off, on, toggle 
 * respectively, R (reset), T (get sleep clock 'tocs'), Z (go to sleep). See 
 * comments at execCmd() function for more details.
 *
 * During sleep mode the device uses very little power (micro watts) and can 
 * stay powered on 2 x AAA batteries for months. 
 *
 * Tested with 'Lite' version of HiTech C compiler for PIC on target
 * platform PIC16F627A and 16F628A. This code should be read in together with the
 * PIC datasheet which can be downloaded from http://www.microchip.com
 *
 * This program occupies most of the available space of a 16F627A. However the
 * use of stdio functions and liberal use of printf() accounts for a large portion
 * of the space used. For practical apps long string messages are not required and
 * the use of printf() can often be avoided.
 * 
 * It is assumed that there is a LED on RB3 (pin 9) which must have a limiting
 * resistor of ~ 500 ohms. The serial port RX and TX and connected to pins 
 * 7 and 8 respectively. Also the RX line (pin 7) and INT (pin 6) are tied together
 * to facilitate wake on serial port activity.
 *
 * NB: Do not connect RX and TX directly to RS232 as the voltage levels (typically
 * -15V to -3V for logic 1 and +3V to +15V for logic 0) are not compatible 
 * with PIC logic levels 0/ ~5V.
 *
 * Simplified schematic:
 *
 *          +-----------+
 *          |   PIC     |
 *          |16F627/8(A)|
 *          |           |
 * GND------|Vss     Vdd|------ 2 - 5V
 *          |           |
 *      +---|INT        |
 *      |   |           |
 *  ----+---|RX         |
 *          |           |
 *  --------|TX         |
 *          |           |
 *    +-----|RB3        |
 *    |     +-----------+
 *  500ohm
 *    |
 *   LED
 *    |
 *   GND
 *
 * Not shown: logic <--> RS232 conversion, optional crystal resonator.
 *
 * It is generally recommended that unused input pins are tied either high
 * or low through a ~ 10K resistor. However I have found that if setup correctly
 * (specifically MCLR and LVP are disabled) unused pins can be left floating.
 *
 */

#include <htc.h>
#include "stdio.h"

// Bit manipulation macros
#define BIT(x)   (1 << (x))
#define SETBIT(p,b) (p) |= BIT(b)
#define CLRBIT(p,b) (p) &= ~BIT(b)
#define TOGBIT(p,b) (p) ^= BIT(b)
#define TSTBIT(p,b) (p) & BIT(b) 

/*
Configuration Word:

Oscillator
--------------
HS:    High speed external oscillator
INTIO: Internal oscillator (4MHz)

Remember to set FOSC accordingly.


Watch Dog Timer (WDT)
--------------
WDTEN: Watch dog timer enable
WDDIS: Disable watch dog timer

When enabled a SLEEP operation will resume at the next instruction when the timer 
rolls over.  Otherwise SLEEP can only be interrupted by an external event. It takes 
about 2 seconds for the timer to roll over. A simple low-accuracy sleep clock can 
be made by counting the SLEEP/wake cycles. The device can be awoken after a set 
period of time this way (not implemented in this version). When the WDT is enabled 
a WDT roll over while awake will cause a device reset which we don't want in the
application. So it's important that the WDT is cleared periodically by calling
CLRWDT().

Other features
--------------

Unused features are disabled.

PWRTDIS:   Power up timer disable
BODIS:     Brownout reset disable
UNPROTECT: Code protection disable 
MCLRDIS:   Master Clear disable. Prevents spontaneous resets if MCLR left floating.
LVPDIS:    Low Voltage Programming disable. Prevents problems if RB4/PGM pin left floating.

A note about MCLR and LVP features: if you do enable those features, it is vital 
that you *don't* allow MCRL and PGM pins to float. Tie them high or  low via a 
~ 10K resistor. If you don't you will probably experience spontaneous  resets 
and intermittent failures.

*/

__CONFIG(INTIO & WDTEN & PWRTDIS & BORDIS  & UNPROTECT & MCLRDIS & LVPDIS);

// Oscillator frequency. Set to 4000000UL if using internal oscillator. Else
// set to the external crystal frequency.
//#define FOSC 10000000UL
#define FOSC 4200000UL

// Desired serial port baud rate. In theory any value is possible, but the
// traditional values are 300, 1200, 2400, 4800, 9600, 19200, 38400, 112500
// [Note about max speed, 19200 max with internal 4MHz osc?]
#define BAUD 19200

#define BUF_LEN 8  // Define the length of the command buffer
#define LED_PIN RB3  // Define LED pin.
#define LED_TRIS TRISB3 // Define LED tri-state bit (for configuration to output)


// Function prototypes
unsigned char execCmd();
void prompt();
void crlf();

// Flags shared with ISR. For convenience using an entire 8 bit value to represent
// a single flag. A value 1 being set, 0 being clear. This is wasteful and you may 
// want to use individual bits of a one general flags variable if space becomes tight.
volatile unsigned char sleepMode; // set to 1 if currently SLEEPing as much as possible
volatile unsigned char wakeFlag; // set to 1 by ISR to signal to main loop to wake up
volatile unsigned char cmdFlag;  // set to 1 by ISR to signal to main loop that ENTER was received

volatile unsigned char bufp;  // indicates the position of the next free char in the buffer
char buf[BUF_LEN];     // command buffer

unsigned int toc = 0;    // initialize our simple clock to 0

/**
 * Initialize hardware
 */
void init(void) {

 //
 // Configure serial port
 //

 // Serial Port Baud Rate Generator. Ref sect 12.1
 // "USART Baud Rate Generator (BRG)", page 75 of datasheet.
 SPBRG = (int)( FOSC / (16UL * BAUD) - 1 ); 

 // Receive Status register
 // 1--- ----  SPEN (Serial Port Enable)
 // ---1 ----  CREN (Continuous Receive Enable)  
 RCSTA = 0x90;

 // Transmit Status register
 // --1- ----  TXEN (Transmit Enable)
 // ---- -1--  BRGH (High baurd rate select)
 TXSTA = 0x24;


 //
 // Configure interrupts
 //

 GIE  = 1;      // Enable all unmasked interrupts
 PEIE = 1;      // Enable all unmasked peripheral interrupts
 INTE = 1;      // Enable external interrupt on RB0 (also tied to RX for wake-on-RX)
 RCIE = 1;      // Enable RX interrupt (only works while awake)


 //
 // Configure LED pin as output
 //

 LED_PIN = 0;     // Set to off on latch
 LED_TRIS = 0;     // Set pin as output

}


void main(void){
 unsigned char status;

 init();       // Initialize hardware

 // Display welcome message. NB: use of printf draws in 100s of bytes of library
 // code. Use only if you have to. I recommend not using stdio.h functions and
 // using the using putch() function defined here to output short messages.
 crlf();
 printf ("Demo PIC application. Version 1.0");
 crlf();
 prompt();

 // Main loop
 while (1) {

  // Display wake message if we are awakened from SLEEP
  if ( wakeFlag ) {
   sleepMode = 0;
   wakeFlag = 0;  // reset wakeup flag

   // Display wake message and prompt
   crlf();
   crlf();
   printf ("WAKE");
   crlf();
   prompt();
  }

  // If command entered (chars + ENTER key on serial port) execute command
  if ( cmdFlag ) {

   crlf();

   // Less than 2 chars (including CR) in command buffer is empty commmand.
   if (bufp >= 2) {

    // Execute the command 
    status = execCmd();

    // Display OK  message if successful, error message otherwise.
    if ( status == 0 ) {
     printf ("OK");
    } else {
     printf ("ERR %d", status); // NB: %d pulls in *lots* of lib code
    }
    crlf();

    // If sleepMode set it must mean that we have just been issued
    // a sleep command (as we have to be awake to receive a command).
    // Display a SLEEP message to indicated to the user that the device 
    // is now asleep.
    if (sleepMode) {
     printf ("SLEEP");
     crlf();
    }
   
   }

   // Reset command buffer
   bufp = 0;
   cmdFlag = 0;

   // Display prompt for next command if in wake mode
   if ( ! sleepMode) {
    prompt();
   }

  }

  if (sleepMode) {

   putch ('Z');

   // Can (optionally) disable INT interrupt while awake. 
   // If INT is left running the ISR will be called for every
   // incoming bit transition in the RX line. While this should
   // not cause a problem, it is causing unnecessary CPU activity
   // which generally should be avoided.
   INTE=1;   // Enable INT to facilitate wake on RX
   SLEEP();  // Sleep until INT or Watch Dog Timer expires
   INTE=0;   // INT not needed while awake.

   toc++;   // Update our simple clock

  } else {
   CLRWDT();  // If awake, clear watch dog timer as often as possible
  }

 }

}

/**
 * This function parses the command buffer (buf) after the user hits the 
 * RETURN key and executes whatever command that may have been entered.
 * Commands are letters (upper or lower) followed by optional arguments.
 * 
 * L <RETURN> - Display LED status
 * L0 <RETURN> - LED on
 * L1 <RETURN> - LED off
 * Lt <RETURN> - LED toggle
 * R <RETURN> - Reset device
 * T <RETURN> - Display sleep clock
 * Z <RETURN> - Put device to sleep. Any key will re-awaken.
 * 
 * @return status code. 
 *  0: success
 *  2: unrecognized command
 *  3: bad parameter
 */
unsigned char execCmd() {

 // Get command letter (assume first char is letter) and convert to
 // uppercase using bit manipulation (set bit 5 low)
 char c = buf[0];
 
 CLRBIT (c,5);  // Convert to uppercase by clearing bit 5 to 0.
 

 switch (c) {

  // LED command.
  case 'L':
   if (bufp == 2) {
    printf ("LED is ");
    if (LED_PIN) {
     printf ("ON");
    } else {
     printf ("OFF");
    }
    crlf();
    return 0;
   }
   c = buf[1];
   if (c == '0') {
    LED_PIN = 0;  // LED off
    return 0;   // Return OK
   } 
   if (c == '1') {
    LED_PIN = 1;  // LED on
    return 0;   // Return OK
   }
   if (c == 't') {
    LED_PIN = ! LED_PIN;// LED toggle
    return 0;
   }
   return 3;    // Return bad parameter error
   // break not required

  // Reset command
  case 'R':
   printf ("RESET");  // Indicate to user that device is about to be reset
   crlf();
   asm ("goto 0");   // Jump to addr 0 achieves a reset
   // break not required

  // Sleep timer query
  case 'T':
   printf ("%d",toc);  // WDT takes about 2s to roll over. x2 to get seconds
   crlf();
   return 0;
   // break not required

  case 'Z':
   sleepMode = 1;   // Enable sleep mode. Next iter of main loop will put dev to sleep
   break;

  // Unrecognized command
  default:
   return 2;
 }

 // Return success code
 return 0;
}


/**
 * Interrupt Service Handler (ISR)
 *
 * Any enabled interrupt will cause the CPU to jump to this routine. The C compiler
 * takes care of saving context at the start and restoring it when finished. The 
 * code in the ISR needs to check interrupt status flags to see which interrupt(s)
 * caused the ISR to be invoked. The ISR should be kept as short as possible. In
 * general if work needs to be performed as a result of an interrupt *don't* do it
 * in the ISR. Instead set a flag and have the main loop poll the flag and have it 
 * do the work.
 *
 * Variables modified by ISR:
 *   wakeFlag
 *   bufp
 * 
 * Note variables that are modified here
 * must be declared 'volatile'. This is because an optimising C compiler will
 * make incorrect optimisations based on the false assumption that the
 * value cannot spontaneously change. See this article for a discussion:
 * http://www.embedded.com/story/OEG20010615S0107
 */
interrupt isr () {

 unsigned char c;
 
 // Check if INT interrupt was triggered
 if ( INTF ) {
  if (sleepMode) {
   wakeFlag=1;   // Tell main loop we are awake now
  }
  INTF = 0;    // Clear interrupt flag (else isr will be called again on exit)
 }

 // May have more than one char in the buffer - so need to loop until buffer empty. Else we can have lag (?)
 while (RCIF) {
 
  c = RCREG;    // Read from USART and add to command buffer

  // ESC (ASCII code 27) key causes immediate reset at all times.
  if (c == 27) {
   asm("GOTO 0");  // Jumping to address 0 is how device reset is achieved
  }

  TXREG = c;    // Echo back char if possible

  buf[bufp++] = c;  // Add char to buffer

  // If ENTER or buffer full signal to main loop that we have command
  if (c == '\r' || bufp == BUF_LEN -1) {
   buf[bufp]=0;  // Zero terminate for safety (TODO: this added lots of bytes to code -- can we optimize?)
   cmdFlag = 1;  // tell main loop we have a command entered
   break;    // Exit loop: any further chars in buffer can be ignored
  }

 }

}

/**
 * Send a character to serial port. If buffer is full then wait until space
 * is available. Will wait indefinitely.
 */
void putch(unsigned char c) {
 while ( ! TXIF ) ;   // Loop until TX register available
 TXREG = c;     // Write character to TX register
}


/**
 * Write CR LF (new line) sequence to serial port.
 */
void crlf () {
 putch ('\r');
 putch ('\n');
}
/**
 * Write command prompt to serial port.
 */
void prompt () {
 putch ('>');
 putch (' ');
}


Saturday, June 19, 2010

PIC wake from SLEEP on serial port RX hack

I am writing firmware for my ABPM controller. One important feature I require is to be able to configure and control it via a serial interface (ie RS232 at 0/5V voltage levels). As this will be an always-on battery powered device it will spend most of it's time in SLEEP state to conserve power. The PIC16F627A, my current target of choice, can wake from sleep on almost any peripheral activity. Unfortunately this does not include the USART in asynchronous mode (presumably because the baud rate clock consumes too much power).

Fortunately there is a hack to get around this problem: tie the RX line together with the INT pin of the PIC. Any activity on the bus will trigger an INT interrupt which can be used to tell the device to wake up and go into alert mode to accept commands via the serial bus.

The down side of this approach is that it uses the INT pin (although its use can be shared if necessary) and the first character to arrive while sleeping will be corrupted. This is not a problem: just hit RETURN a few times if  using a terminal emulator, or prefix any command sequence with a few CR characters if issuing the commands from software.

The code (I'm using the free HiTech's PIC C 'Lite' edition) looks something like this:

// variables shared with interrupts must be declared as volatile
volatile unsigned char sleepMode;

main () {

    // setup stuff...

    // main loop
    while (1) {

        // do work...

        if (sleepMode) {
            SLEEP();
        } else {
            // need to clear watchdog timer frequently
            CLRWDT();
        }
    }

}
// interrupt handler
interrupt isr () {

    // check for INT interrupt
    if (INTF) {
        // clear interrupt flag, else it will be retriggered
        INTF = 0;
        // inform main loop that we are now awake
        sleepMode = 0;
    }

    // check other interrupts...
}

Monday, May 17, 2010

How to make an Ambulatory Blood Pressure Monitor (ABPM) for just €20 ($25)


Fig 1: Modified HL168Y wrist cuff blood pressure monitor.
Summary: this post explains how to convert a Health & Life Co. HL168Y blood pressure monitor (sold by Maplin for €17) into an ambulatory blood pressure monitor using nothing but a few components costing no more than €3. Basic knowledge of electronics, simple tools and a PIC microcontroller programmer are required. The modification was successfully tested and a chart of the data is presented at the bottom of this post.

Blood pressure (BP) is notoriously difficult to measure. A single reading at a doctor’s surgery can be misleading as BP varies throughout the day and is often biased due to anxiety ("white coat hypertension"). For a better picture an ambulatory blood pressure monitor (ABPM) is used. This is an unobtrusive device that automatically takes readings at fixed intervals, usually 20 minute intervals during the day and 1 hour intervals at night. The data is stored on the device and is transferred to a computer at the end of the measuring period.

ABPMs are generally made for the medical profession and cost in excess of €1K. As far as I tell there are just a few key features that differentiate ABPMs from the cheap consumer models that can cost as little as €17:
  1. Automatically take measurements
  2. Transfer data to computer
  3. Unobtrusive and robust
  4. Calibrated and certified
So I started thinking: how easy would it be to modify a cheap consumer BP monitor to have ABPM functionality? Maplin sell a HL168Y cuff device for €17. At that price I thought it was worth a shot. The worst that can happen is I’m left with €17 of e-junk.
Disclaimer: Do not try this with a BP monitor that you need for medical purposes. Modifications will almost certainly void your warranty and may affect the accuracy and performance of the device. If you want to tinker with your BP monitor I recommend buying one specifically for this purpose. Also do not use the data obtained for any serious medical decisions: it's of unknown quality.



Fig 2: Innards of the HL168Y BPM
Most electronic BPMs use oscillometric measurement. A cuff which is wrapped around the arm (at wrist or upper arm level) is inflated to about 200mmHg (26kPa) pressure. At this pressure the cuff is sufficiently constrictive to block blood circulation to the arm. A pressure sensor monitors the pressure in the cuff. In addition to measuring the absolute pressure in the cuff, this sensor sufficiently sensitive to ‘hear’ the characteristic pulsations of the heart (which are manifested as small variations in pressure).

At 200mmHg blood flow is constricted -- no heart beat will be detected. The cuff is slowly deflated. At the systolic pressure (around 120mmHg for most people) the heart will be able to overcome the cuff pressure. The microcontroller will be able to detect a small pulsing variations in the cuff pressure. The cuff continues to deflate until a point is reached where no heart beat is detected. This is the diastolic pressure.

It is a very simple device essentially comprising a cuff, air pump, pressure sensor. A microcontroller  coordinates these components, make the calculations and display the result. As the Maplin device has a 40 reading memory, at its simplest all that is required is a means of pressing the button automatically every 20 minutes. The readings will have to be manually transcribed into a spreadsheet or other analysis tool. In theory, this could be done mechanically (using a solenoid actuator) but would would involve a cumbersome attachment to the front face of the device – not very practical. Fortunately simulating a button press electrically is trivial.

The HL168Y device has 4 buttons: Start/Stop, Mode, Set and Memory. These are standard rubber buttons. When depressed, a conductive rubber foot on the underside of the button squeezes against interleaved copper contacts on the PCB.

On the reverse of the PCB are test pads which provide a convenient way to test or tap into important points in the device. TP4 corresponds to the Start/Stop button. Pulling this line to ground (0V) simulates pressing the button.

A microcontroller unit (MCU) like a PIC from Microchip Technology Inc. is perfect for the task. PICs are small, cheap and operate at voltages down to 2V. The HL168Y uses 3V (2 x AAA cells) so the PIC can draw power from the same battery.

My first choice was a tiny 8 pin 12F675. I figured this was small enough to fit in the spare space inside the device housing. Unfortunately I ran into some programming problems so opted for a larger 18 pin 16F627.

The Hardware


Fig 3: schematic of modification
Three points need to be tapped into:
  • Red: Battery + terminal (3V)
  • Black: Battery - terminal (0V)
  • Orange: testpad TP4 (the Start/Stop button)

Solder three wires to the above points. I strongly recommend that the wires are restrained using tape (otherwise it’s all to easy to damage the PCB by accidental tugging on the wires). These three wires connect to pins on the PIC (see fig 4).



Fig 4: The 16F627 PIC MCU mounted on an IC socket. Also underneath (not in view) 2K2 resistor linking Vdd to MCLR pin.

To facilitate easy reprogramming of the PIC I soldered a IC socket into some veroboard. The PIC can be easily mounted and removed as needed.

The red wire is connected to Vdd (pin 14), black to Vss (pin 5) and orange to PB0 (pin 6). I found that unless the master clear pin MCLR (pin 4) is tied high the device is unstable. Therefore under the board (not visible in the photo) is a 2K2 resistor linking pin 4 to pin 14 (any value 1K  - 10K will do).

Test by powering up. If all goes well the BPM should automatically start taking a reading in 5 minutes (enough time to reset the clock).  Tape the attachment to the side of the device using duct tape.  Ensure there are no lose wires that can be snagged on something as you go about your day.



The Software

The requirements for the first iteration of my modification is quite simple: Simulate "Start" being pressed sleep for 20 minutes and repeat.

Delays are often achieved by a tight loop or using internal counters. As this is a battery operated device minimizing power consumption is vital. Delays using loops or timers would push current consumption into the mA region, depleting the battery in days.

Fortunately there is a low power alternative: the PIC SLEEP instruction which puts the chip into a deep hibernate mode. The device can be woken from sleep by interrupt (eg button press) or using the device Watchdog Timer. Using the SLEEP instruction I’ve been able to reduce average power consumption into the micro-amp region.

For the first iteration of this project I've decided to use the C compiler that comes with Microchip's free MPLAB IDE. The free C compiler does not produce the most compact code, but as it's such a short program it is not a problem.

The C program below is compiled with MPLAB and the resulting HEX file uploaded onto the 16F627 chip using a Velleman K8048 PIC programmer.

#include <htc.h>

__CONFIG(INTIO & WDTEN & PWRTDIS & BORDIS & LVPEN  & UNPROTECT);

// Internal crystal frequency 4Mhz
#define _XTAL_FREQ 4000000

int loopDelay (unsigned char n);
int pauseSeconds (unsigned char n);
int pauseMinutes (unsigned char n);

void init(void) {
 PORTB = 0x00000001; // set RB0 on latch high before making pin output
 TRISB = 0b11111110; // set RB0 as output (1=input, 0=output)
}

void main(void) {
 init();
 while (1) {
  pauseMinues (5);

  // Press start button twice (once to wake, once to start)
  pressStartButton(); 
  pauseSeconds(2);
  pressStartButton();

  pauseMinutes(15);
 }
}

/** 
 * Simulate the pressing of the Start button
 */
int pressStartButton () {
 PORTB = 0b0;
 loopDelay(128);
 PORTB = 0b1;
}


 
/** 
 * Seep for one whole cycle of the watchdog timer (about 2s in 16F627)
 */
int wdtSleep () {
 CLRWDT();
 SLEEP();
}

/**
 * Sleep for 'n' seconds by calling wdtSleep() in a loop
 */
int pauseSeconds (unsigned char n) {
 int j;
 n /= 2;
 for (j = 0; j < n; j++) {
  wdtSleep();
 }
}

/**
 * Sleep for 'n' minutes by calling wdtSleep() in a loop
 */
int pauseMinutes (unsigned char n) {
 int i,j;
 for (j = 0; j < n; j++) {
  // about 1 minute
  for (i = 0; i < 30; i++) {
   wdtSleep();
  }
 }
}

/**
 * Cause a delay by looping. Useful for short delays, but uses considerably
 * more power than using SLEEP
 */
int loopDelay (unsigned char n) {
        unsigned char i,j;
        for (i = 0; i < n; i++) {
                CLRWDT();
                for (j = 0; j < 127; j++) {
                        NOP();
                }
        }
}

The Data

I tested this device on myself for a 30 hour continuous stretch over the weekend. It's like wearing a big heavy watch and generally was not a problem. For good readings it is necessary hold still and keep the device at heart level during the 30 seconds or so it takes to measure. It can be easily removed at any time if necessary. I was able to sleep with it (although I think it did affect the quality of my sleep).

The following is a chart of that run. It shows the expected day/night difference, with a noticeable dip during the peak of the sleep cycle at about 04:00.


Limitations

The main problem with this that it starts automatically and continues to trigger a measurement every 20 minutes while batteries are in the device. The only way to turn it off is to remove the battery (which by the way also erases the clock and memory). Also we have to manually key the data from the screen into our spreadsheet / analysis software. This is a major pain point for which I no solution yet. I would also very much like to be able to fit this modification inside the original housing.

Finally a wrist cuff device is not the best for ABPM because readings should be taken while the wrist is at heart height. Any higher or lower will bias the reading. An upper arm cuff is far more preferable as it's always (approx) at heart level and is more comfortable to wear. It may be possible to modify this device into such a configuration, although it will involve tearing it apart.

Phase 2 will attempt to address some of these shortcomings.