PIC16 Heart Rate & Temperature Monitor

by pityukecske in Circuits > Electronics

338 Views, 7 Favorites, 0 Comments

PIC16 Heart Rate & Temperature Monitor

IMG20240923233240.jpg
IMG20240923233338.jpg
image1-45.png
image1-10.png

I was browsing projects one day and came across a heart rate monitor which could be easily assembled using only a handful of components. I decided to give it a go with some added features, most specifically an LM35 thermometer. By the time I am writing these words it's already clear that there will be a V2 of this - mistakes have been made, tweaks have been made and generally speaking there is room for improvement on this project.

The instructable I found and started off with was this one: link

After everything was assembled and I began to debug the unit, I cam across the source of the above instructable, in PDF format. This document helped me a lot in understanding how the circuit should be working and what the principles are.

In short: the changes in the light reflected/absorbed by our fingers can be measured with a very cheap infrared reflective sensor, the TCRT5000. These changes are very subtle but we can work on it and amplify + filter to have some kind of pre-conditioning on the signals before they are fed into the PIC16F1512 microcontroller. Once the signals are there, we can use interrupts and timers to measure the time between two successive pulses and find out exactly how many times does this repeat each minute. We could wait one minute before getting a reading but it just seems too much. I decided to measure ten consecutive heart-beat periods and then save the average to get a smoother reading. This way we will have a reading every ten seconds or so - not very fast but a lot faster than one minute.

To display my measurements I used some old displays I bought a while ago with a bunch of other components. I had to do some diode measurements with a handheld DMM to find out how the diodes were exactly wired. The thermometer function is implemented through analog measurements coming from an LM35. I wanted to do fancy stuff here and support temperature measurements down to -40 degrees but I just ended up realizing how stupid of me was to think that it would actually work. I guess I learned some lessons on the way, again.

As usual, I designed and made some PCBs through the toner transfer method. Let's see what I made and how I made it.

Supplies

image1.png
IMG20240924011215.jpg
IMG20240924011235.jpg
IMG20240924011305.jpg

There are a couple of things one will need to build this project:

  1. Magnifying glass or microscope: to inspect components and/or traces.
  2. Multimeter: for continuity and voltage measurements
  3. Logic probe: for debugging once the PCB is powered up (scope or logic analyzer is also good)
  4. Hand tools: for disassembly and assembly
  5. Microcontroller: PIC16F1512 and PICKIT4
  6. Small parts: wires, LEDs, resistors and other small parts
  7. Soldering tools: soldering station, solder wick, solder
  8. Paper and pencil, copper laminate, laser printer, iron, wrapping wire and more...

Ideas, Designing and Testing the PCB

image1-4.png
NewFile46 - Copy.png
NewFile47 - Copy.png
NewFile48 - Copy.png
NewFile49 - Copy.png
image1-0.png

In order to keep everything in one design I decided to create two PCBs within a single PCB document. I did that by adding two connectors and purposefully ignoring the un-routed net errors Altium threw at me. This allowed me to keep the schematic in one single document as well.

I attached the schematic to this step.

Heart rate monitoring section

The sensor interfacing is very similar to the source document I told you about in the first step, but I tweaked the values a bit to get a better signal in each stage. The filtering is basically the same, the C4 = 4.7uF and R6 = 68K form a high-pass filter of ~0.5Hz while the C4 = 100nF and R3 = 680k. The stages also have some gain, 11x and 310x respectively.

I added some oscilloscope screenshots to the image section of this step, see how the very noisy signal coming from the sensor collector slowly turns into something useful. The measurements were taken on the sensor collector, R6, R8 and finally R10. R10 is routed directly onto the CCP1 pin of the microcontroller and an edge-detection is implemented which starts the measurement cycles.

One thing you must take care of is to keep the infrared LED current down, otherwise the whole thing gets incredibly sensitive and you will not be able to measure any heart rate. I found that with a 220R resistor this thing could be used for distance measurements quite easily. I ended up adding a 2K trimmer resistor, tuning the resistance until I got some good BPM readings, then measuring & replacing the thing with a fixed resistor of 1.5K.

Temperature measurement section

Temperature measurements are taken with the help of an LM35 sensor. The datasheet of this sensor describes the calculation formula as follows: every 10mV measured means 1 degrees (Celsius). This is simple enough and the ADC within the microcontroller can be used to measure how many 10mV-s are there on the output pin. I wanted to get fancy and measure negative temperatures as well so I took the "Full-Range Centigrade Temperature Sensor" snippet from the datasheet and added it to my design.

Split power supply section

The Full-Range temperature measurements required negative PSU as well so without thinking too much I added some operational amplifiers and an NPN BJT to create some sort of split supply for the circuit. I didn't realize that having the PIC16F referenced to ground will result in it not being able to measure anything below that - that was it for the sub-zero degrees. In short, this section turned out to be completely useless and I am pretty sure there is a goof somewhere as well as the buffering NPN is getting hot even at low current draw. I will revisit this problem in the V2 of my project.

Microcontroller section

This is really just the MCU and the filtering capacitors. I routed some GPIOs to control the digits through PNP BJTs and to control the segments by driving them through some 100R resistors. The CCP1 module was routed to the output of the heart-rate measurement section, and I added a header to be able to extend the circuit later with an encoder or two buttons - for temperature/heart-rate selection. There is also an RJ11 programming header, which I accidentally placed mirrored across the X axis, so the freaking thing is in reverse and I had to create this adapter thingy to be able to interface it to the PICKIT4.

I attached the PCB layout for toner transfer method to this step.

Downloads

Firmware Development

image1-6.png

Although I enjoy looking at the datasheet and stripping out every bit of every register, this time I went with the MCC code generator tool as a first step. This is a graphical tool that allows users to configure the MCU from a higher level, editing textboxes and checking checkboxes instead of writing numbers into registers. At the end of the day that is still what actually happens in the background but the users gets it in a form that may make more sense.

On the left side there is the resource explorer which lists all available drivers for the microcontroller. One can select all or none, it's up to the application what gets loaded. I selected the following modules:

  1. CCP1 - used for Capture interrupt to catch every rising edge on the CCP1 (sensor + gain/filter)
  2. ADC - used for the Analog-To-Digital-Conversion
  3. FVR - Fixed Voltage Reference used to have accurate readings and easy math for the ADC
  4. TIMER - TMR1 is used by the CCP1 module while TMR2 is used for the heart-rate measurements

CCP1 - Capture module

This module has three different operating modes assigned and the user can select whichever mode he wants. CCP stands for Capture, Compare, PWM and we will use the first operation mode - Capture mode.

This mode uses the CCP1 pin as an input. Whenever an edge (of configurable polarity or count) appears on the CCP1 input pin, the content of the TMR1 counter registers is "saved" into the CCPR1 register pair and triggers the CCP1 interrupt flag. Now this may not sound much but it actually allows for frequency measurements in several different ways:

  1. set up the CCP1 module to give interrupt each time a rising edge is seen on the CCP1 pin. Then, we use another timer with a known interrupt rate (lets say 10ms) to simply increment a number. Whenever an interrupt is seen on the CCP1 pin, we take this number, and multiply it by that 10ms. This will yield the amount of time that passed between two CCP1 interrupts. Heart-rate is actually beats per minute (1 minute = 60 seconds = 60000ms), so we can check how many beats of the recorded length could we fit into one minute. Result BPM = 60000/number. We can output this onto our display to present results to the user. Those looking into the numbers will see that a multiplier of 10 seems to get lost on the way - I decided to just ignore it and just add a decimal point so the number is right on the display and there are no computationally-intensive routines in the firmware.
  2. there is another, more elaborate way to to BPM count - I have used this method previously to measure the frequency of an unknown square-wave. We set up the TMR1 to advance one step with each millisecond and simply wait for a CCP1IF interrupt. Whenever this happens, the state of the TMR1 counter register pair is automatically saved into the CCPR1 register pair and the CCP1 interrupt is triggered (this feature was ignored in method 1). When handling the interrupt we will take the number that got saved in the CCPR1 pair and run the following math: BPM=60000ms/CCPR1
  3. one could increment a number in the CCP1 interrupt routine and have a software timer run for exactly one minute, then see what the count is. This, however takes a lot of time when compared to the other methods. An external interrupt and any timer can be used with this method.

ADC - Analog to-digital converter module and FVR

I used this module to get the AD conversions from the sensor which could be used to calculate temperature later. I used a single channel so configuration was relatively simple. I used the FVR module from within the MCU in order to have faster calculations. I usually have the reference voltage selected as VDD, but the temperature calculation math ends up complicated that way and includes decimal points - that means long math routines.

Each 10mV measured on the sensor output means 1°C. The ADC returns a number proportional to the reference voltage and since this is a 10 bit ADC, the number it returns can vary between 0 and 1023. If the reading shows 0, it means we are looking at a voltage which is equal (or less) than the negative voltage reference. If we are looking at a voltage which is equal or greater than the positive voltage reference, we will see 1023 as a reading. If the voltage on the analog input is between the negative and positive reference voltages, we will se a number between 0 and 1023. If we used 5V as positive reference and ground as negative reference, we could calculate the voltage on the analog input like this: ADC reading * 5V / 1023.

It is obvious that this ends up using floating point numbers which have impact both on program memory and loop time. If, however, one decides to use the 4.096V internal reference voltage instead, the math becomes simple, as 4.096/1023 is very very close to 4mV and this means that each bit of the result means a 4mV voltage after conversion. This 4 is a rather "round" number: multiplications/divisions with numbers that are power of two can easily be substituted by direct microcontroller shifting instructions = they are very fast. Using 4.096V reference allows us to get the temperature reading by simply having the ADC reading shifted left by two. I didn't account for the tenfold multiplier constant (remember, 10mV = 1°C) but simply output the number on the display and just lit up the decimal point instead. The user can't tell the difference but again, there is no division.

TMR2 - timekeeping and measurements

As mentioned before, this timer was used to generate a 10ms time base to help me with calculating the BPM based on the sensor output.

I attached the HEX file for the PIC16F1512 to this step.

Enclosure & Printing

image1-23.png
image1-24.png
image1-44.png
image1-8.png

For the enclosure design I used Fusion 360. I am actually still very far from being a 3D designer, but I am slowly able to design simpler enclosures and models - I bought a cheap course on Udemy, so far it is very good. I used to use TinkerCAD but wanted to get a bit more serious and try something for the more advanced.

I exported a STEP model from Altium Designer to avoid taking hundreds of measurements - the component outlines can be projected on sketches making cutouts very easy to add on the enclosure extrusions. I don't know if TinkerCAD supports this, I didn't see this so far and it was a very useful feature for this project.

On the last picture you can see the spaghetti I made - level and calibrate your 3D printing bed before you print anything! This was my first print. The second one was scrapped because the filament jammed between the printer and CNC and a couple layers were missing and the model broke. The third came out neat!

I attached the STL files to this step.

Assembly and Tests

image1-10.png
image1-3.png
image1-45.png
image1-2.png
image1-7.png

Assembly is easy. I just dropped in the PCB assembly into the rear enclosure part, and arranged the LM35 + TCRT5000 sensors to slip into their cutouts. Then I added the Front side and pushed it together. I counted on friction as the force that will hold the enclosure together, so far it worked out.

Because of the design mistakes I couldn't supply power through the terminal block - that (and the inclusion of a 9V battery inside the enclosure) will remain for the V2 of this design.

Thanks for reading through my instructable - feel free to ask questions or give feedback, I appreciate your input.