Tag Archives: PCB Design

PSoC – Design and Implementation of a 12 Lead Portable ECG

Fully Assembled Compact, Portable 12 Lead ECG

During the academic year of 2016-2017 at McMaster University, in conjunction with Dr. DeBruin, Christina Riczu, Thomas Phan and Emilie Corcoran, we developed a compact, battery powered, 12-lead electro-cardiogram. The project won 1st place in the biomedical category at the ECE Capstone Poster Day.

The final report we handed in for the course is attached at the end of this post and includes background information, a design overview, schematics and bill of materials for the hardware we developed. This post will introduce the project and serve as a personal account of the considerations and problems associated with the portion of the project that I focused on.

Before we begin I should note that if you decide to replicate this design or develop a derived design that you are doing so at your own risk. Attaching a device with a low impedance connection to a person can be dangerous. We developed this project under a supervisor with experience developing and maintaining such devices and protocols were put into place to ensure safety.

System Design

My goal during the project was to develop the most compact device possible while providing robust mechanical design, usable battery life, convenient connectivity, a clean user interface, and good analog performance. For this to occur components must be selected carefully across electrical, mechanical and software domains. In addition to this I wanted the design to be reusable and extendable in the future, choices were made to allow the circuit boards to be used for different applications beyond this project.


The purpose of the hardware is to perform signal conditioning, analog to digital conversion and transfer the data to a host.

Overview of the hardware and firmware

The software processes the data and displays it in a graphical interface.

Overview of the software

Patient Interface

Contec ECG Cable with Button Snap Connectors

Low cost cables for connecting skin electrodes to a data acquisition system is available on EBay. If you search for Contec ECG cable on EBay you will find them for about $20. They interface with a DB15 connector and come terminated with button snap connectors for connecting with commonly available ECG electrodes such as the 3M Red Dot silver-silver chloride electrodes.

We could not find a pin out for the cable so we used a continuity meter to figure it out, I include it here in case anyone needs it.

7Not Connected
8Not Connected
9Right Arm
10Left Arm
11Left Leg
13Not Connected
14Right Leg
15Not Connected


We begin with electrical component selection as this dictates the direction we must go for mechanical and firmware integration.

Fully Assembled Circuit Boards

Isolated Power

As mentioned before, electrical isolation is required for safety. Batteries can be used to provide a simple isolated supply, however, requiring the replacement of batteries puts an unnecessary burden on the user. We wanted the device to be convenient which meant we wanted the device to be battery powered and rechargeable even during use. This necessitated an isolated DCDC converter between the battery and electronics connected to the patient for safety.

The system was developed to allow the DCDC converter to be sized smaller thus allowing the device to fit into a smaller box. From the top-level schematics, you can see that some components are powered by the battery through a 3.3V linear regulator and only the components with a direct connection to the patient is powered by the isolated DCDC.

Top Level Schematic of the Data Acquisition and Control Board
Power Supply Schematics

The use of a switching converter to power an analog front end meant that we need some noise filtering. The ROE-3.305S isolated DCDC converter we are using comes from RECOM’s Econoline series of switching converters that are designed to be small but it outputs a lot of ripple because the designers did not include a lot of output filtering. A common mode choke will cut down on any common mode noise generated by the isolated DCDC and the LC output filter will filter out much of the switching transients generated by the 80 kHz switching frequency of the ROE-3.305S. The LC backfeed filter is the filter recommended by RECOM to pass some EMC compliance standard test, no calculations were performed by us for backfeed filter.

The result is an analog front end working with signals that have an amplitude of a few millivolts being powered from a switching converter with no noticeable noise issues.

The Bluetooth module and MicroSD card requiring higher currents at 3.3V were powered through the linear regulator by the lithium polymer cell. USB power is provided to a charging circuit on this side of isolation as well. On the other side of isolation, we have the microcontroller with analog to digital converters and connection to the analog front end which contains our operational amplifiers, instrumentation amplifiers, analog multiplexers, and analog buffers all powered at 5V to allow for a larger input range at our analog input. The microcontroller and analog front end do not consume much power compared to the Bluetooth radio and MicroSD card allowing us to size our isolated DCDC converter for up to 200 mA current draw at the output. This was critical as it allowed our system to fit into a smaller box due to height constraints.

Microcontroller Selection

The choice of which microcontroller to use was based on IO mapping flexibility and elimination of external peripherals. The Cypress PSoC5LP was chosen because each pin is remappable making circuit board routing easier resulting in a more compact circuit board and the integrated analog to digital converters supported variable full-scale input range and level shifting. This was key to allow for the use of a multiplexed switched front end to eliminate components in the analog front end. The ease, flexibility and configurability all the digital and analog peripherals implemented using fixed function blocks and universal digital blocks enables the creation of an elegant data acquisition system.

Analog Front End

The analog front end was developed to be compact, we chose to create a multiplexed switched front end to reduce component count, however this means we cannot use high pass filters for removing DC offset, low pass filters to remove out of band noise or notch filters to remove power line noise. This imposed challenges we tackled in the digital domain using the microcontroller we chose earlier. This resulted in a compact analog front end that was not much bigger then the patient lead connector itself.

Analog Front End Schematics, resistor values not specified

All components on the analog front end were chosen to be low noise, support rail to rail operating and be small in size.
An Linear Technology LT1920 is used as the instrumentation amplifier, this is however unnecessary since a high input impedance is not required at this stage. It was used because it uses a single resistor to set the gain and we already had this part on hand from making single lead ECGs using it.

Resistors in this schematic do not have values specified, the resistor values were chosen later.


For communications to the host system we incorporated Bluetooth connectivity and USB connectivity. Having both allows us to communicate both with PCs that do not have Bluetooth connectivity and Android devices which almost all have Bluetooth connectivity.

The Bluetooth module was placed in such a way that the antenna hangs off the board and sits in free air away from the aluminum enclosure to maintain good range. The resulting system demonstrated excellent range of about 25 meters through a cinderblock wall.


Low cost aluminum boxes are available from Digikey. An aluminum box allowed our device to be compact and robust against damage.

The height of the DB15 connector for the Contec ECG electrode cable sets the minimum height of the box

The size of the box was constrained by to a minimum height by the DB15 connector used for the patient lead cable. We used this height as a driving constraint for the selection of the DCDC converter.

The DCDC converter was chosen to match that height constraint

As can be seen, the DCDC converter is the tallest component on the circuit board and has about 1 mm of clearance from the top of box.

Remaining space was filled by a lithium polymer battery

All tall components were placed on one side of the board to make room for the battery and battery tray. The space available became the driving factor for sizing the battery as long as the battery was of high enough capacity, else we would have had to buy a longer box with the same cross-sectional design.

Firmware and Peripheral System

Our firmware was implemented on top of FreeRTOS to allow for future extensibility even though our project did not require an RTOS. A scheduler would allow us to implement on board DSP algorithms in parallel with data sampling and communications easily in the future to allow for processed data to be stored on the MicroSD card which was unused for this project.

The customizable peripheral system with flexible analog components on the PSoC5LP allowed us to use the multiplexed switched analog front end implemented in hardware.


The choice of implementing the user interface and digital signal processing backend in C++ using Qt was made to ensure we could implement both a high-performance backend and fluid user interface.

Qt also provides methods for inter-thread communication and this enables ease of running digital signal processing routines in parallel allowing for the system to be extended in the future.

Touch and mouse friendly, responsive interface

A bonus for using Qt is that the same code and interface can be used for desktop and mobile platforms. The use of Qt Quick allows the creation of touch and mouse friendly interfaces with ease.

Design Challenges

Analog Multiplexer Charge Injection

Initially we wanted to reduce the cost of the analog front end by feeding the electrode signals directly into the analog multiplexers and use the LT1920 instrumentation amplifier similar to the way it is normally used, connected directly to the electrode pair. Unfortunately, it turns out that when analog multiplexers switch, even those designed to inject only a small amount of charge when switching will still result in a significant voltage transient when coupled with the high input impedance of the instrumentation amplifier and the lack of ability of the human body to dissipate even a few nanocoulombs of charge.

Charge injection transient captured with the analog front end hooked up to a patient simulator

This waveform of a switching transient with a 5 uS settling time was captured with a breadboard prototype hooked up to a patient simulator. A 5 uS settling time would not be a problem as it would limit our sample rate to 200 kHz, however it turned out that the patient simulator had much lower output impedance and ability to dissipate charge verses an actual human body. We never captured the waveform of a switching transient with the multiplexer hooked up to one of us but it was awful.

To fix this we added unity gain buffers before the analog multiplexers and that fixed our problems.

Analog Sampling System with DC Offset Cancellation

Our design challenges for the analog sampling system begin at the analog front end, the decision to design a switched front end imposes some requirements on the data acquisition system.

The skin/electrode interface creates a half cell potential, or a DC offset that can be up to a few hundred millivolts and is unmatched between electrode pairs. Since we are unable to use analog filters in the analog front end as their settling times will be much too long when switching we are unable to remove this DC component using high pass filters. This means the instrumentation amplifier which amplifies differential voltages will apply gain to the DC offset. Resulting in a signal that can easily clip effectively removing all the interesting AC components in the signal. To avoid this, we must limit the gain on the instrumentation amplifier necessitating a small full-scale range on the ADC performing the sampling.

ADC configured for a +/- 128 mV differential input range. Sampling is triggered by an external signal and occurs at the conversion rate.

We configured the instrumentation amplifier for a gain of 25x, with an input amplitude of up to +/- 5mV this resulted in a signal of +/- 125 mV. The ADC on the Cypress PSoC5LP was configured for an input range of +/- 128 mV. Unfortunately, this presents another problem, because the DC offset in the incoming signal is unknown the ADC can now easily clip. We decided to remedy this situation with the use of another ADC with a low resolution and high input range allowing us to sample the DC offset of the incoming signal. We then set the negative reference terminal of the high resolution low input range ADC to the DC offset using a DAC. The DC offset is sampled and the DAC is driven with a new value in sync with the switching of the analog multiplexer.

Analog multiplexer switching logic and analog data acquisition system

We have 8 channels to sample so the Sample_Clock running at 8 kHz will result in a 1 kHz sample rate per channel, the ECG signal has a bandwidth of about 200 Hz, we over-sample here because we have no low pass filters to prevent aliasing.

The ECG_ADC_REF_DAC_SET_ISR will fire at the same time the ECG_MuxControl_Counter is triggered. This sets the ECG_ADC_REF_DAC to the time-averaged DC offset measured previously by the ECG_REF_ADC which has an input range of 0 to 4.096V at 8 bits of resolution and changes the channel being fed into the ECG_ADC_In analog input pin connected to the output of the analog multiplexers on the analog front end. The ECG_ADC_REF_DAC voltage is fed into the negative input of the ECG_ADC effectively shifting its operating voltage range to the expected DC offset for the channel.

The ECG_ADC_Delay_Timer is used to delay sampling by the two ADCs until the DAC and any remaining switching transients have settled. Values from the ECG_REF_ADC is read out by an interrupt service routine, the rolling average of voltages for each channel is stored for use by the ECG_ADC_REF_DAC. The ECG_ADC uses a DMA for data transfer to keep CPU load low.

This system effectively performs DC offset cancellation and analog data acquisition, since the DAC is only 8 bits when the DC offset changes a step can be seen in the output waveform but this could be compensated in software and the data can then be passed through a digital high pass filter to remove all DC offset but this was not implemented in our code.

USB Data Transfer

Bluetooth data transfer worked without a hitch with the Bluetooth module in Serial Port Protocol mode where it emulates a COM port on the host system. USB data transfer did not work quite as well at first.

It turns out that the USB CDC driver only fires a single event in Qt for each USB bulk transfer that occurs to reduce the number of event handlers being invoked. To maximize our data transfer rate 64 1 byte values are transferred per bulk transfer. This means we must handle the data in blocks. We decided to frame our data into 515 byte frames, 512 bytes of data or 64 samples per channel and a 3 byte header. The header is 0xFF, 0xFF, 0xDA. 0xFFFF is an impossible ADC value because our ADC is configured for 15 bits so it acts as our sync word, 0xDA stands for DATA allowing us to use other headers for commands from the device to host or side channel data for DC offset reporting that would allow us to remove the artifacts in the data introduced by the DAC’s large voltage steps.

Another necessary routine in the data receiver in the host application was detection of loss of sync and a fast method of finding the sync word again if any data is dropped. A loop is used to consume data quickly and find the header without exiting the event handler and synchronization is regained resulting in a sampling artifact too small to see in the output data.

Final Report

Attached below is a copy of our final report and appendices containing background information, a design overview, our presentation materials, schematics, bill of materials, pictures and connector pin-outs.

Little Heart Analyzer – Final Report

Sources and Design Files

Circuit board designs were created in CircuitMaker and is available here.

Software sources were created for Qt Creator and PSoC Creator and is available here.

PIC32 – Dragon Flame

As a gift to my sister and her husband for their wedding I developed the electronics and firmware for a table centerpiece. The product was developed using tools and materials already familiar to me to tighten the development cycle so some component choices were not ideal but were chosen because I already had programmers on hand, code already written, or schematic and footprint libraries already created for past projects. This significantly de-risked manufacturing as many components of the product were already verified to be working giving me confidence to order boards and parts for twenty of these lamps all at once. Developed in about two weeks and all twenty assembled in a weekend this project was completed quickly as the big day was rapidly approaching when I returned to Canada after my internship in California summer of 2016.

Electrical Schematic Design


A PIC32 MX120 in an SO-28 package was chosen to run the firmware to drive the LEDs. The PIC32 is overkill for this application but I already had a PICKit3 on hand and code written to get the SPI peripheral up and running. The SPI peripheral was exploited to avoid the need for more complex timer interrupts to drive the one wire serial interface of the LEDs. This is a well-known technique used to drive WS2812b like LEDs using DMA, no DMA was used for this project as the number of LEDs driven is so low but the use of SPI still reduces complexity in the firmware. To ease hand soldering the SO-28 package was chosen. Though not originally chosen for this feature, the PICKit3 Programmer-to-Go functionality proved to be a time saver when flashing firmware to all twenty boards.

A 6 pin 0.1″ header was used for programming, during board assembly no header was soldered on. I simply put male header pins into the PICKit3 and pressed it up against every board to program them using Programmer-to-Go and an external power supply plugged into each board.


APA106 LEDs in a 5mm frosted package was chosen to hopefully avoid ugly bright spots in the lamp shade. The APA106 LEDs can be purchased for a low cost on AliExpress. They use a one wire protocol that is very similar to WS2812b based LEDs sometimes known as “NeoPixels”. The APA106’s simply daisy chain acting like a shift register. I added a 0.1 uF capacitor on the 3.3V line powering every other LED for good measure but they were probably unnecessary.

Power Supply

Power was supplied by four AA batteries in series wired to the board at the wedding. When the lamps were given away the battery box was cut out and a 5V AC power adapter was included to allow battery-less operation using the 2.1mm jack making this lamp a nice decorative night light that can be left on all the time.

An STM LDFPT-TR adjustable voltage regulator was chosen for its low dropout voltage of only 200 mV at full current allowing long operation on batteries to avoid having the lights die during the evening. This was probably not needed as the power draw by the system was very low and the AA’s were barely drained by the end of the day. A surface mount fuse was added for short circuit protection and a voltage divider added to allow input voltage sensing by the PIC32 however this was never implemented.

Mode Switch

A small push button with a pullup resistor was added to allow cycling of the color of the lamp if desired.

Expansion Header

An expansion header with an input for a CdS light sensor, I2C with associated pull up resistors, and UART was added to allow future expansion and hacking.

Circuit Board Design and Assembly

The board layout was created in Altium. Dividing lines were created on a mechanical layer to allow me to align the LEDs at 45 degree angles to each other. Silkscreen graphics were vectorized by one of my sisters good friends from artwork my sister had created back in her undergraduate days. Conversion to silkscreen lines from the PNG file exported from Adobe Illustrator was done with the PCB logo script included with Altium Designer. Instructions on how to use this script are at http://techdocs.altium.com/printpdf/23168.

The board designed was 10cm in diameter and manufacturing was done by Elecrow using their 20 piece 10cm x 10cm prototyping service. The final cost of each bare board was about $2 USD. The boards were of reasonable quality and arrived in Canada in about 2 weeks from order. I saw small solder mask flaws where a few 0.5 mm diameter spots of copper were not covered. This was alright for this board but may not be acceptable for boards that contain finer pitched components.

Electrical assembly was done in about a day with one person sorting through components and one soldering. A small amount of pen flux was used to assist with the denser joints but overall minimal flux was used to reduce the amount of board cleaning that was required to make the boards presentable.


Time was running short so the firmware was hastily written using some old code developed for the Noiseblaster32 digital audio player I developed with Devon Andrade during the Summer of 2015.  (https://github.com/andrade824/NoiseBlaster32)

As I mentioned before the SPI peripheral is used to generate the 1 wire signal used to drive data into the LEDs. According to the datasheet a high time of 0.35us and a low time of 1.36 us represents a 0 and a high time of 1.36 us and a low time of 0.35 us represents a 1. A low time of 50 us will trigger a reset of the bus and the data shifted in will be latched.  The datasheet says the timing must be within 150 ns but I found elsewhere online showed that the timing does not need to be that close.

uint8_t SPI_Write(uint8_t data) {
    SPI1BUF = data;                 // write to buffer for TX
    while(!SPI1STATbits.SPIRBF);    // wait for transfer to complete
    SPI1STATbits.SPIROV = 0;        // clear any overflow.

    return SPI1BUF;                 // read the received value

Since the timings do not need to be exact I decided to play around. I found an SPI bus clock frequency of 5 MHz works well. A 5 MHz clock has a period of 200 ns, so an SPI byte write will generate a 1.6 us cycle. Writing 0xE0 (1110 0000) to the SPI peripheral generates a high time of 0.6 us effectively writing a 0 to the 1 wire bus. Writing a 0xF8 (1111 1000) to the SPI peripheral generates a high time of 1 us. In hindsight using 0xC0 for 0 and 0xFC for 1 would have generated closer timings but it didn’t seem to matter as the lamp works fine.

void LED_Write_Array(uint8_t data[][3], uint8_t NumLED) {
    uint8_t i;
    uint8_t j;
    uint8_t k;
    for (i = 0; i < NumLED; i++){
        for (j = 0; j <= 2; j++){
            for (k = 8; k > 0; k--){
                if((data[i][j] & 1<<(k-1)) == 0 ) {
                else {

To perform a bus reset to latch the data I write 0x00 to the SPI peripheral 8 times which generates a low time of 12.8 us, much less then the 50 us the datasheet says is required but again this worked fine.

void LED_Latch() {
    uint8_t i;
    for (i = 8; i > 1; i--){

Timer 1 on the PIC32 with a 256x pre-scaler is used to generate a 1 kHz refresh trigger. This 1 kHz trigger is used to update the LED data.

void InitTimer1000Hz() {
    OpenTimer1(T1_ON | T1_SOURCE_INT | T1_PS_1_256, (GetSystemClock()/256)/1000);

    // Set up the timer interrupt with a priority of 2

I decided to smooth the brightness transition by implementing what I call the LED Framebuffer and LED Setpoint. LED Framebuffer array stores the RGB values currently being displayed on the LEDs, the LED Setpoint array stores the target value. A fraction of the difference between the LED Framebuffer and LED Setpoint is taken and added to the current LED Framebuffer value. Updating the LED Framebuffer value is performed every cycle at 1 kHz, a random value is generated for the LED Setpoint every 50 cycles, a button is also sampled to shift the base colour of the light.

void main() {
    SYSTEMConfig(GetSystemClock(), SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE); //Configure for maximum speed
    // Enable multi vectored interrupts
    INTEnableSystemMultiVectoredInt(); //Do not call after setting up interrupts

    TRISBbits.TRISB12 = 1;   // set RB3 as an input

    CNPDBbits.CNPDB3 = 1; // Enables internal pull-down resistor on pin RB3
     PPSUnLock; // Allow PIN Mapping
    PPSOutput(2, RPB5, SDO1);
     PPSLock; // Prevent Accidental Mapping
    // Init the spi module for a slow (init) clock speed, 8 bit byte mode
    SPI1CONbits.ON = 0; // Disable SPI for configuration
    SPI1BUF;    // Clear the receive buffer
    SPI1STATbits.SPIROV = 0;    // Clear the receive overflow bit
    SPI1CON = 0x260;    // Master, CKE=0; CKP=1, sample end
    SPI1BRG = 3;  // Divide by 8. SCK = Fpb/(2 * (BRG + 1))
    SPI1CONbits.ON = 1; // enable

    uint8_t led_framebuffer[8][3] = {  
    uint8_t led_setpoint[8][3] = {  
    uint8_t random;
    uint8_t i;
    uint8_t j;
    uint8_t new_setpoint_countdown = 50;

    uint8_t color[3] = {255,70,10};
    uint32_t total_color = 0xffffff; //B,G,R
    int8_t delta_color[3] = {5,0,0};
    while (1){
        if (refresh_LEDs) {
            new_setpoint_countdown = new_setpoint_countdown - 1;
            for (i = 0; i < 8; i++){
                led_framebuffer[i][0] = led_framebuffer[i][0] + (1.0/15)*(led_setpoint[i][0] - led_framebuffer[i][0]); 
                led_framebuffer[i][1] = led_framebuffer[i][1] + (1.0/15)*(led_setpoint[i][1] - led_framebuffer[i][1]);
                led_framebuffer[i][2] = led_framebuffer[i][2] + (1.0/15)*(led_setpoint[i][2] - led_framebuffer[i][2]);   
            if(new_setpoint_countdown == 0){
                new_setpoint_countdown = 50;
                for (i = 0; i < 8; i++){
                    //random = analogRead(12);
                    for (j=0; j<3; j++) {
                        if (color[j] == 0) {
                            color[j] = 5;
                    random = rand() % color[0]/1.5;
                    led_setpoint[i][0] = color[0] - random;
                    random = rand() % color[1]/2.5;
                    led_setpoint[i][1] = color[1] - random;
                    random = rand() % color[2]/1.5;
                    led_setpoint[i][2] = color[2] - random;   
                if (PORTReadBits(IOPORT_B, BIT_3)) {
                    for (i=0; i<3; i++) {
                        if (color[i] == 255) {
                            delta_color[i] = -5;
                            delta_color[((i+1)%3)] = 5;
                        if(color[i] == 0) {
                            delta_color[i] = 0;
                    for (i=0; i<3; i++) {
                        color[i] += delta_color[i];
            refresh_LEDs = false;
    }; //Avoid Allowing PIC32 to continue executing code


Lamp Shade

Each lamp shades was made from two right angle triangles cut from sheets of Dura-Lar polyester film. Unfortunately, I never wrote down dimensions for the lamp shade but the dimensions are not too critical. The ruler in the picture is 24” long.

Wooden Base

The wooden base is made from a 1-¾” x 5.5” x 5.5” block of wood. A 3” diameter hole was cut through the center of the wooden base to allow space for the battery pack and clearance for the bottom of the through hole solder joints. A 4” hole saw was used to create a slot for the inner lamp shade and a 5” hole saw was used to cut a slot for the outer lamp shade. The blocks were then stained with some random stuff we found in the garage.

On the bottom, a 1” Forstner bit was used to cut out the corners of the circular through hole to allow the battery pack to fit snugly. A few extra holes were cut on the first base as we tried to figure out what would work best.

A hole was then drilled for the 2.1mm DC jack which was press fit into place.

The boards were then fastened with 2 small wood screws.

Final Assembly

Two triangular pieces of Dura-Lar were then formed into cylinders with the long side of the triangle in the slot. The cylinders were then rotated so the sharp point was on the left and right sides with the DC jack facing the rear. Then a piece of cardboard was jammed into the slot to keep the Dura-Lar in place.