The personal website of Scott W Harden
December 17th, 2022

Divide 10 MHz to 1PPS with a Microcontroller

I often find it useful to gate frequency counters using a 1 pulse per second (1PPS) signal derived from a 10 MHz precision frequency reference. However, a divide-by-10-million circuit isn't trivial to implement. Counter ICs exist which enable divide-by-100 by combining multiple divide-by-2 and divide-by-5 stages (e.g., MC74HC390A around $0.85 each), but dividing 10 MHz all the way down to 1Hz would require at least 4 of these chips and a lot of wiring.

You can clock a microcontroller at 10 MHz and use its timer and interrupt systems to generate 1PPS. For example, an ATTiny202 in an 8-pin SOIC package is available from Mouser (>50k stocked) for $0.51 each. Note that modern series AVRs require a special UDPI programmer.

ATTiny202 ($0.51) ATTint826 ($0.95)

This page documents a divide-by-10-million circuit achieved with a single microcontroller to scale a 10MHz frequency reference down to 1PPS. I'm using an ATTiny826 because that is what I have on hand, but these concepts apply to any microcontroller with a 16-bit timer.

ATTiny Breakout Board

Some AVRs come in DIP packages but their pin numbers may be different than the same chip in a SMT package. To facilitate prototyping using designs and code that will work identically across a breadboard prototype and a PCB containing SMT chips, I prefer to build DIP breakout boards using whatever SMT package I intend to include in my final builds. In this case it's ATTint826 in a SOIC-20 package, and I can easily use this in a breadboard by soldering them onto SOIC-to-DIP breakout boards.

I assembled the breakout board by hand using a regular soldering iron. When working with small packages it helps so much to coat the pins with a little tack flux to facilitate wetting and prevent solder bridges. I'm presently using Chip Quik NC191. Even if flux is advertized as "no-clean", it's good practice and makes the boards look much nicer to remove remaining flux with acetone and Q-tips or brushes.

Circuit

  • FTDI breakout board for power: To test this design I'm using a FT232 breakout board just to provide easy access to GND and Vcc (5V from the USB rail).

  • 10 MHz can oscillator: It's not ovenized or GPS disciplined, but I'm using this as a stand-in for whatever high-precision 10 MHz frequency standard will eventually be used in this circuit. The important thing is just to know that it outputs 0-5V square waves at 10 MHz going into the EXTCLK pin of the microcontroller

  • UPDI Programmer: I'm using the Atmel-ICE, but a MPLAB Snap would also work here. See Programming Modern AVR Microcontrollers for more information.

  • Output: A LED on an output pin will visualize the 1pps signal

Configuration Change Protection (CCP)

Traditional AVR microcontrollers used fuse bits to set the clock source, but modern series chips can change the clock source from within code. However, modifying the clock source requires temporarily disabling the configuration change protection (CCP) system.

Disabling the CCP only lasts four clock cycles, so the immediate next statement must be assignment of the new value. I use the following function to facilitate this action.

/* Write a value to a CCP-protected register */
void ccp_write(volatile register8_t* address, uint8_t value){
    CCP = CCP_IOREG_gc;
    *address = value;
}
// Use internal 20 MHz clock with CKOUT pin enabled
ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm);

Do not use compound statements when writing to the CCP register. The code below fails to change clock as one may expect by looking at the code, presumably because the combined OR operation with the assignment exceeds four clock cycles. Instead of direct assignment, use the ccp_write function described above.

// WARNING: This code does not actually change the clock source
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLA = CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm;

Configuring the Clock Source

Internal 10 MHz clock

This is the configuration I use to achieve a 10 CPU clock using the built-in 20 MHz oscillator.

void configure_clock_internal_10mhz(){
    ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm); // 20 MHz internal clock, enable CKOUT
    ccp_write(&CLKCTRL.MCLKCTRLB, CLKCTRL_PEN_bm); // enable divide-by-2 clock prescaler
}

External 10 MHz clock

This is the configuration I use to clock the CPU from an external 10 MHz clock source applied to the EXTCLK pin.

void configure_clock_external(){
    ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_EXTCLK_gc | CLKCTRL_CLKOUT_bm); // external clock, enable CKOUT
    ccp_write(&CLKCTRL.MCLKCTRLB, 0); // disable prescaler
}

Configuring the 16-Bit Timer

This is how I configured my ATTiny826's TCA0 16-bit timer to fire an interrupt every 200 ms.

  • Prescale: By enabling a divide-by-64 prescaler, my 10 MHz input becomes 156,250 Hz.

  • Top: By setting the top of my 16-bit counter at 31,250, I achieve exactly 5 overflows per second (once every 200 ms).

  • Interrupt: By enabling an overflow interrupt, I am able to call a function every 200 ms.

void configure_1pps(){
    // 10 MHz system clock with div64 prescaler is 156,250 Hz.
    // Setting a 16-bit timer's top to 31,250 means 5 overflows per second.
    TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; // overflow interrupt
    TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc; // normal mode
    TCA0.SINGLE.PER = 31249UL; // control timer period by setting timer top
    TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV64_gc; // set clock source
    TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm; // start timer
}

Alternatively, multiple timers could be cascaded to achieve a similar effect. Modern AVR series microcontrollers have sections in their datasheet describing considerations for cascading two 16-bit timers to create a single 32-bit timer. Using this strategy one could set the top of the counter to 5 million and arrange an interrupt to toggle an LED, resulting in a 1Hz signal with 50% duty.

Configuring the Interrupt System

This method is called whenever the timer's overflow interrupt is triggered. Since it's called 5 times per second, I just need a global counter to count the number of times it was called, and set an output pin to high on every 5th invocation.

uint8_t overflow_count;

ISR(TCA0_OVF_vect)
{
    overflow_count++;
    if (overflow_count == 5){
        overflow_count = 0;
        PORTB.OUT = PIN1_bm;
    } else {
        PORTB.OUT = 0;
    }

    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // indicate interrupt was handled
}

Do not forget to enable global interrupts in your start-up sequence! This is an easy mistake to make, and without calling this function the overflow function will never be invoked.

sei(); // enable global interrupts

Results

We have achieved a light that blinks exactly once per second with roughly the same precision as the 10 MHz frequency reference used to clock the microcontroller. This output signal is ready to use for precision measurement purposes, such as toggling the gate of a discrete frequency counter.

Resources

Markdown source code last modified on December 23rd, 2022
---
Title: Divide 10 MHz to 1PPS with a Microcontroller
Description: How to use a microcontroller to inexpensively scale down a 10 MHz reference clock into a one pulse per second (1pps) signal
Date: 2022-12-17 1:09PM EST
Tags: circuit, microcontroller
---

# Divide 10 MHz to 1PPS with a Microcontroller

**I often find it useful to gate frequency counters using a 1 pulse per second (1PPS) signal derived from a 10 MHz precision frequency reference.** However, a divide-by-10-million circuit isn't trivial to implement. Counter ICs exist which enable divide-by-100 by combining multiple divide-by-2 and divide-by-5 stages (e.g., [MC74HC390A](https://www.onsemi.com/pdf/datasheet/mc74hc390a-d.pdf) around $0.85 each), but dividing 10 MHz all the way down to 1Hz would require at least 4 of these chips and a lot of wiring.

**You can clock a microcontroller at 10 MHz and use its timer and interrupt systems to generate 1PPS.** For example, an [ATTiny202](https://www.mouser.com/datasheet/2/268/ATtiny202_402_AVR_MCU_with_Core_Independent_Periph-1384964.pdf) in an 8-pin SOIC package is available from Mouser (>50k stocked) for $0.51 each. Note that [modern series AVRs require a special UDPI programmer](https://swharden.com/blog/2022-12-09-avr-programming). 

ATTiny202 ($0.51) | ATTint826 ($0.95)
---|---
<img src="ATTINY202-SOIC-8.png">|<img src="ATTINY826-SOIC-20.png">

**This page documents a divide-by-10-million circuit achieved with a single microcontroller to scale a 10MHz frequency reference down to 1PPS.** I'm using an [ATTiny826](https://www.mouser.com/datasheet/2/268/ATtiny424_426_427_824_826_827_DataSheet_DS40002311-2887739.pdf) because that is what I have on hand, but these concepts apply to any microcontroller with a 16-bit timer.

![](1pps-breadboard2.jpg)

## ATTiny Breakout Board

**Some AVRs come in DIP packages but their pin numbers may be different than the same chip in a SMT package.** To facilitate prototyping using designs and code that will work identically across a breadboard prototype and a PCB containing SMT chips, I prefer to build DIP breakout boards using whatever SMT package I intend to include in my final builds. In this case it's ATTint826 in a SOIC-20 package, and I can easily use this in a breadboard by soldering them onto [SOIC-to-DIP breakout boards](https://www.amazon.com/s?k=soic+dip+breakout).

![](breakout1.jpg)

**I assembled the breakout board by hand using a regular soldering iron.** When working with small packages it helps _so much_ to coat the pins with a little tack flux to facilitate wetting and prevent solder bridges. I'm presently using [Chip Quik NC191](https://www.amazon.com/s?k=NC191). Even if flux is advertized as "no-clean", it's good practice and makes the boards look much nicer to remove remaining flux with acetone and Q-tips or brushes.

![](breakout2.jpg)
![](breakout3.jpg)

## Circuit

* **FTDI breakout board for power:** To test this design I'm using a FT232 breakout board just to provide easy access to `GND` and `Vcc` (5V from the USB rail).

* **10 MHz can oscillator:** It's not ovenized or GPS disciplined, but I'm using this as a stand-in for whatever high-precision 10 MHz frequency standard will eventually be used in this circuit. The important thing is just to know that it outputs 0-5V square waves at 10 MHz going into the `EXTCLK` pin of the microcontroller

* **UPDI Programmer:** I'm using the Atmel-ICE, but a MPLAB Snap would also work here. See [Programming Modern AVR Microcontrollers](https://swharden.com/blog/2022-12-09-avr-programming) for more information.

* **Output:** A LED on an output pin will visualize the 1pps signal

![](1pps-breadboard.jpg)

## Configuration Change Protection (CCP)

**Traditional AVR microcontrollers used fuse bits to set the clock source, but modern series chips can change the clock source from within code.** However, modifying the clock source requires temporarily disabling the configuration change protection (CCP) system. 

Disabling the CCP only lasts four clock cycles, so the immediate next statement must be assignment of the new value. I use the following function to facilitate this action.

```c
/* Write a value to a CCP-protected register */
void ccp_write(volatile register8_t* address, uint8_t value){
	CCP = CCP_IOREG_gc;
	*address = value;
}
```

```c
// Use internal 20 MHz clock with CKOUT pin enabled
ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm);
```

**Do not use compound statements when writing to the CCP register.**  The code below fails to change clock as one may expect by looking at the code, presumably because the combined OR operation with the assignment exceeds four clock cycles. Instead of direct assignment, use the `ccp_write` function described above.

```c
// WARNING: This code does not actually change the clock source
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLA = CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm;
```

## Configuring the Clock Source


### Internal 10 MHz clock

This is the configuration I use to achieve a 10 CPU clock using the built-in 20 MHz oscillator.

```c
void configure_clock_internal_10mhz(){
	ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm); // 20 MHz internal clock, enable CKOUT
	ccp_write(&CLKCTRL.MCLKCTRLB, CLKCTRL_PEN_bm); // enable divide-by-2 clock prescaler
}
```

### External 10 MHz clock

This is the configuration I use to clock the CPU from an external 10 MHz clock source applied to the `EXTCLK` pin.

```c
void configure_clock_external(){
	ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_EXTCLK_gc | CLKCTRL_CLKOUT_bm); // external clock, enable CKOUT
	ccp_write(&CLKCTRL.MCLKCTRLB, 0); // disable prescaler
}
```

## Configuring the 16-Bit Timer

This is how I configured my ATTiny826's TCA0 16-bit timer to fire an interrupt every 200 ms.

* **Prescale:** By enabling a divide-by-64 prescaler, my 10 MHz input becomes 156,250 Hz.

* **Top:** By setting the top of my 16-bit counter at 31,250, I achieve exactly 5 overflows per second (once every 200 ms).

* **Interrupt:** By enabling an overflow interrupt, I am able to call a function every 200 ms.

```c
void configure_1pps(){
	// 10 MHz system clock with div64 prescaler is 156,250 Hz.
	// Setting a 16-bit timer's top to 31,250 means 5 overflows per second.
	TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; // overflow interrupt
	TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc; // normal mode
	TCA0.SINGLE.PER = 31249UL; // control timer period by setting timer top
	TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV64_gc; // set clock source
	TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm; // start timer
}
```

**Alternatively, multiple timers could be cascaded to achieve a similar effect.** Modern AVR series microcontrollers have sections in their datasheet describing considerations for cascading two 16-bit timers to create a single 32-bit timer. Using this strategy one could set the top of the counter to 5 million and arrange an interrupt to toggle an LED, resulting in a 1Hz signal with 50% duty.

## Configuring the Interrupt System

**This method is called whenever the timer's overflow interrupt is triggered.** Since it's called 5 times per second, I just need a global counter to count the number of times it was called, and set an output pin to high on every 5th invocation.

```c
uint8_t overflow_count;

ISR(TCA0_OVF_vect)
{
	overflow_count++;
	if (overflow_count == 5){
		overflow_count = 0;
		PORTB.OUT = PIN1_bm;
    } else {
		PORTB.OUT = 0;
	}
    
	TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // indicate interrupt was handled
}
```

**Do not forget to enable global interrupts in your start-up sequence!** This is an easy mistake to make, and without calling this function the overflow function will never be invoked.

```c
sei(); // enable global interrupts
```

## Results

**We have achieved a light that blinks exactly once per second** with roughly the same precision as the 10 MHz frequency reference used to clock the microcontroller. This output signal is ready to use for precision measurement purposes, such as toggling the gate of a discrete frequency counter.

<video playsinline autoplay muted loop class="border border-dark shadow-sm img-fluid">
  <source src="1pps.webm" type="video/webm">
</video>

## Resources

* Full source code: [ATTiny826 1pps project on GitHub](https://github.com/swharden/AVR-projects/tree/master/ATTiny826%20Timer%201pps) and specifically [main.c](https://github.com/swharden/AVR-projects/blob/master/ATTiny826%20Timer%201pps/ATTiny826%20Clock%20and%20Timer/main.c)

* Inspecting the header file `iotn826.h` in my Program Files / Atmel folder was very useful for identifying named bit masks stored as enums. There is a similarly named file for every supported AVR microcontroller.

* EEVblog forum: [Divide by 10000000](https://www.eevblog.com/forum/projects/divide-by-10000000/)

* EEVblog forum: [Divide by 10 prescaler for frequency counter](https://www.eevblog.com/forum/rf-microwave/divide-by-10-prescaler-for-frequency-counter/)

* EEVblog forum: [10MHz to 1pps divider](https://www.eevblog.com/forum/projects/10mhz-to-1pps-divider/)

* EEVblog forum: [Easiest way to divide 10MHz to 1MHz?](https://www.eevblog.com/forum/projects/easiest-way-to-divide-10mhz-to-1mhz/)

* YouTube: [Build a DIY Frequency Divider](https://www.youtube.com/watch?v=GlKWexGWoXw)

* [picDIV: Single Chip Frequency Divider](http://www.leapsecond.com/pic/picdiv.htm) (2011)

* [PICDIV on GitHub](https://github.com/aewallin/PICDIV)

* All About Circuits thread: [How to convert 10 MHz sine wave to 1Hz TTL (PPS)?](https://forum.allaboutcircuits.com/threads/convert-10-mhz-sine-wave-to-1hz-ttl-pps.54085/)

* [10 MHz to 1 Hz frequency divider using discrete 74HC4017D stages](http://www.perdrix.co.uk/FrequencyDivider/) by David C. Partridge
December 9th, 2022

Programming Modern AVR Microcontrollers

This page describes how to program Microchip's newest series of AVR microcontrollers using official programming gear and software. I spent many years programming the traditional series of Atmel chips, but now several years after Microchip acquired Atmel I am interested in exploring the capabilities of the latest series of AVR microcontrollers (especially the new AVR DD family). Currently the global chip shortage makes it difficult to source traditional ATMega and STM32 chips, but the newest series of AVR microcontrollers feature an impressive set of peripherals for the price and are available from all the major vendors.

TLDR

  • Older AVR microcontrollers are programmed using in-circuit serial programming (ICSP) through the RESET, SCK, MISO, and MOSI pins using cheap programmers like USBtiny. However, serial programming is not supported on newer AVR microcontrollers.

  • New AVR microcontrollers are programmed using the unified program and debug interface (UDPI) exclusively through the UDPI pin. UDPI is a Microchip proprietary interface requiring a UDPI-capable programmer.

  • Official UDPI programmers include Atmel-ICE ($129) and MPLAB Snap ($35). The Atmel-ICE is expensive but it is very well supported. The MPLAB Snap is hacky, requires re-flashing, and has a physical design flaw requiring a hardware modification before it can program AVR series chips.

  • There are notable attempts to create alternative programmers (e.g., jtag2updi and pymcuprog), but this journey into the land of unofficial programmer designs is fraught with abandoned GitHub repositories and a lot of added complexity and brittleness (e.g., SpenceKonde/AVR-Guidance), so to save yourself frustration in the future I highly recommend just buying an officially supported programmer. It's also nice when you can program and debug your microcontroller from within your IDE.

  • UDPI programmers have a Vcc pin that is used to sense supply voltage (but not provide it), so you must power your board yourself while using one of these new programmers.

Blinking a LED is the "Hello, World" of microcontroller programming. Let's take a look at the code necessary to blink a LED on pin 2 of an ATTiny286. It is compiled and programmed onto the chip using Microchip Studio.

#define F_CPU 3333333UL
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    PORTA.DIR = 0xFF;
    while (1)
    {
        PORTA.OUT = 255;
        _delay_ms(500);
        PORTA.OUT = 0;
        _delay_ms(500);
    }
}
  • PORTA.DIR sets the direction of pins on port A (0xFF means all outputs)
  • PORTA.OUT sets output voltage on pins of port A (0xFF means all high)
  • Using _delay_ms() requires including delay.h
  • Including delay.h requires defining F_CPU (the CPU frequency)
  • The ATTiny286 datasheet section 11.3.3 indicates the default clock is 20 MHz with a /6 prescaler, so the default clock is 3333333 Hz (3.3 MHz). This behavior can be customized using the Oscillator Configuration Fuse (FUSE.OSCCFG).

ATTiny826 Pinout

From page 14 of the ATTiny826 datasheet

SMT ATTiny Breakout Board

Many of the newer AVR series microcontrollers are not available in breadboard-friendly DIP packages. I find SOIC-to-DIP breakout boards (available on Amazon and eBay) to be useful for experimenting with chips in SOIC packages. Here I added extra power and PA4 (pin 2) LEDs and 10 kΩ current limiting resistors.

I power the device from the 3.3V or 5V pins on a FT232 USB breakout board. Although the topic is out of scope for this article, I find it convenient to use FTDI chips to exchange small amounts of data or debug messages between a microcontroller and a modern PC over USB without requiring special drivers.

Why is programming modern AVRs so difficult?

I'm surprised how little information there is online about how to reliably program modern AVR series microcontrollers. In late 2022 there is a surprisingly large amount of "advice" on this topic which leads to dead ends and broken or abandoned projects. After looking into it for a while, here is my opinionated assessment. Mouser and Digikey have links to expensive programmers, and Amazon has links to similar items but reviews are littered with comments like "arrived bricked" and "can not program AVR series chips". DIY options typically involve abandoned (or soon-to-be abandoned?) GitHub repositories, or instructions for Arduino-related programming. I seek to consolidate and distill the most useful information onto this page, and I hope others will find it useful.

Atmel-ICE: Expensive but Effective

After using $5 ICSP programmers for the last decade I almost fell out of my chair when I saw Microchip's recommended entry-level programmer is over $180! Digikey sells a "basic" version without cables for $130, but that still seems crazy to me. Also, $50 for a ribbon cable?

I found a kit on Amazon that sells the programmer with a cable for $126. It was hard for me to press that buy button, but I figured the time I would save by having access to modern and exotic chips during the present global chip shortage would make it worth it. After a couple days of free Prime shipping, it arrived. It was smaller than I thought it would be from the product photos.

The cable that came with the device seemed a bit hacky at first, but I'm happy to have it. The female 2.54 mm socket is easy to insert breadboard jumpers into.

I'm glad this thing is idiot proof. The very first thing I did after unboxing this programmer was hook it up to my power supply rails using reverse polarity. I misread the pin diagram and confused the socket with the connector (they are mirror images of one another). This is an easy mistake to make though, so here's a picture of the correct orientation. Note the location of the tab on the side of the connector.

Atmel ICE Pinout Programming Connection
  • Black: GND
  • Red: Vcc - This line is used to sense power and not deliver it, so you are responsible for externally powering your board.
  • Blue: UPDI pin - Although a pull-up resistor on the UPDI pin is recommended, I did not find it was required to program my chip on the breadboard in this configuration.

The AVR Ice was easy to use with Microchip Studio. My programmer was detected immediately, a window popped-up and walked me through updating the firmware, and my LED was blinking in no time.

MPLAB Snap: Cheap and Convoluted

Did I really need to spend $126 for an AVR programmer? Amazon carries the MPLAB Snap for $34, but lots of reviews say it doesn't work. After easily getting the Atmel-ICE programmer up and running I thought it would be a similarly easy experience setting-up the MPLAB Snap for AVR UPDI programming, but boy was I wrong. Now that I know the steps to get this thing working it's not so bad, but the information here was only gathered after hours of frustration.

Here are the steps you can take to program modern AVR microcontrollers with UPDI using a MPLAB Snap:

Step 1: Setup MPLAB

  • The MPLAB Snap ships with obsolete firmware and must be re-flashed immediately upon receipt.

  • Microchip Studio's firmware upgrade tool does not actually work with the MPLAB Snap. It shows the board with version 0.00 software and it hangs (with several USB disconnect/reconnect sounds) if you try to upgrade it.

  • You can only re-flash the MPLAB Snap using the MPLAB X IDE. Download the 1.10 GB MPLAB setup executable and install the MPLAB IDE software which occupies a cool 9.83 GB.

Step 2: Re-Flash the Programmer

  • In the MPLAB IDE select Tools and select Hardware Tool Emergency Boot Firmware Recovery. At least this tool is helpful. It walks you through how to reset the device and program the latest firmware.

Step 3: Acknowledge Your Programmer is Defective

Defective may be a strong word, but let's just say the hardware was not designed to enable programming AVR chips using UPDI. Microchip Studio will detect the programmer but if you try to program an AVR you'll get a pop-up error message that provides surprisingly little useful information.

Atmel Studio was unable to start your debug session. Please verify device selection, interface settings, target power and connections to the target device. Look in the details section for more information. StatusCode: 131107 ModuleName: TCF (TCF command: Processes:launch failed.) An illegal configuration parameter was used. Debugger command Activate physical failed.

Step 4: Fix Your Programmer

The reason MPLAB Snap cannot program AVR microcontrollers is because the UPDI pin should be pulled high, but the MPLAB Snap comes from the factory with its UPDI pin pulled low with a 4.7 kΩ resistor to ground. You can try to overpower this resistor by adding a low value pull-up resistor to Vcc (1 kΩ worked for me on a breadboard), but the actual fix is to fire-up the soldering iron and remove that tiny surface-mount pull-down resistor labeled R48.

Have your glasses? R48 is here:

These photos were taken after I removed the resistor. I didn't use hot air. I just touched it a for a few seconds with a soldering iron and wiped it off then threw it away.

You don't need a microscope, but I'm glad I had one.

Step 5: Reflect

You can now program AVR microcontrollers using UPDI with your MPLAB Snap! Blink, LED, blink.

Can you believe this is the officially recommended action? According to the official Microchip Engineering Technical Note ETN #36: MPLAB Snap AVR Interface Modification

  • Symptom: Programming and debugging fails with AVR microcontroller devices that use the UPDI/PDI/TPI interfaces. MPLAB SNAP, Assembly #02-10381-R1 requires an external pull-up resistor for AVR microcontroller devices that use these interfaces.

  • Problem: AVR microcontroller devices that use the UPDI/PDI/TPI interfaces require the idle state of inactivity to be at a logic high level. Internally, the AVR devices have a weak (50-100K) pull-up resistor that attempts to keep the line high. An external and stronger pull-up resistor may be enough to mitigate this issue and bring voltages to acceptable VDD levels. In some cases, this may not be enough and the pull-down resistor that is part of the ICSP protocol can be removed for these AVR microcontroller applications.

  • Solution: If most of the applications are AVR-centric, consider removing the R48 resistor as shown below. This completely isolates any loading on the programming data line. Additionally, a pull-up resistor to VDD in the range of 1K to 10K should be used for robustness. Pin 4 of J4 is the TPGD data line used for ICSP interfaces and it also doubles as the DAT signal for UPDI/PDI and TPI interfaces. The pull-up resistor can be mounted directly from TVDD (J4-2) to TPGD/DAT (J4-4). Alternatively, the resistor can be mounted on the application side of the circuit for convenience.

I feel genuinely sorry for the Amazon sellers who are getting poor reviews because they sell this product. It really isn't their fault. I hope Google directs people here so that they can get their boards working and leave positive reviews that point more people to this issue.

UPDI Programming with a Serial Adapter

There is no official support for UPDI programming using a serial adapter, but it seems some people have figured out how to do it in some capacity. There was a promising pyupdi project, but it is now deprecated. At the time of writing the leading project aiming to enable UPDI programming without official hardware is pymcuprog, but its repository has a single commit dated two months ago and no activity since. Interestingly, that commit was made by buildmaster@microchip.com (an unverified email address), so it may not be fair to refer to it as an "unofficial" solution. The long term support of the pymcuprog project remains uncertain, but regardless let's take a closer look at how it works.

To build a programmer you just need a TTL USB serial adapter and a 1kΩ resistor. These are the steps I used to program a LED blink program using this strategy:

  • Use a generic FT232 breakout board to achieve a USB-controlled serial port on my breadboard.

  • Connect the programmer as shown with the RX pin directly to the UPDI pin of the microcontroller and the resistor between the RX and TX pins.

  • Ensure a modern version of Python is installed on your system

  • pip install pymcuprog

  • Use the device manager to identify the name of the COM port representing your programmer. In my case it's COM12.

  • I then interacted with the microcontroller by running pymcuprog from a terminal

Ping the Microcontroller

This command returns the device ID (1E9328 for my ATtiny826) indicating the UPDI connection is working successfully

pymcuprog ping -d attiny826 -t uart -u com12
Connecting to SerialUPDI
Pinging device...
Ping response: 1E9328

Write a Hex File

I used Microchip Studio to compile my C code and generate the hex file. Now I can use pymcuprog to load those hex files onto the chip. It's slower to program and inconvenient to drop to a terminal whenever I want to program a chip, but it works.

pymcuprog write -f LedBlink.hex -d attiny826 -t uart -u com12
Connecting to SerialUPDI
Pinging device...
Ping response: 1E9328
Writing from hex file...
Writing flash...

Conclusions

  • The new AVR series microcontrollers have lots of cool peripherals for the price and are available during a chip shortage that threatens availability of the more common traditional microcontrollers.

  • The Atmel-ICE is expensive, but the most convenient and reliable way to program modern AVR microcontrollers using UPDI.

  • The MPLAB Snap can program modern AVRs using UPDI after a software flash and a hardware modification, but its support for AVRs seems like an afterthought rather than its design priority.

  • You can create a makeshift unofficial UPDI programmer from a USB serial adapter, but the added complexity, lack of debugging capabilities, increased friction during the development loop, and large number of abandoned projects in this space make this an unappealing long term solution in my opinion.

Resources

Markdown source code last modified on December 25th, 2022
---
Title: Programming Modern AVR Microcontrollers
Description: Blink a LED on a modern series AVR using Atmel-ICS or MPLAB Snap UPDI programmers.
Date: 2022-12-09 11:45PM EST
Tags: circuit, microcontroller
---

# Programming Modern AVR Microcontrollers

**This page describes how to program Microchip's newest series of AVR microcontrollers using official programming gear and software.** I spent many years programming the traditional series of Atmel chips, but now several years after Microchip acquired Atmel I am interested in exploring the capabilities of the latest series of AVR microcontrollers (especially the new AVR DD family). Currently the global chip shortage makes it difficult to source traditional ATMega and STM32 chips, but the newest series of AVR microcontrollers feature an impressive set of peripherals for the price and are available from all the major vendors.

<div class="w-75 mx-auto">

![](https://www.youtube.com/embed/M-myqg-2c5s)

</div>

## TLDR

* Older AVR microcontrollers are programmed using _in-circuit serial programming_ (ICSP) through the `RESET`, `SCK`, `MISO`, and `MOSI` pins using cheap programmers like [USBtiny](https://learn.adafruit.com/usbtinyisp). However, serial programming is not supported on newer AVR microcontrollers.

* New AVR microcontrollers are programmed using the _unified program and debug interface_ (UDPI) exclusively through the `UDPI` pin. UDPI is a Microchip proprietary interface requiring a UDPI-capable programmer.

* Official UDPI programmers include [Atmel-ICE](https://www.digikey.com/en/products/detail/microchip-technology/ATATMEL-ICE-BASIC/4753381) ($129) and [MPLAB Snap](https://www.digikey.com/en/products/detail/microchip-technology/PG164100/9562532) ($35). The Atmel-ICE is expensive but it is very well supported. The MPLAB Snap is hacky, requires re-flashing, and has a physical design flaw requiring a hardware modification before it can program AVR series chips.

* There are notable attempts to create alternative programmers (e.g., [jtag2updi](https://github.com/ElTangas/jtag2updi) and [pymcuprog](https://github.com/microchip-pic-avr-tools/pymcuprog)), but this journey into the land of unofficial programmer designs is fraught with abandoned GitHub repositories and a lot of added complexity and brittleness (e.g., [SpenceKonde/AVR-Guidance](https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md)), so to save yourself frustration in the future I highly recommend just buying an officially supported programmer. It's also nice when you can program and debug your microcontroller from within your IDE.

* UDPI programmers have a `Vcc` pin that is used to _sense_ supply voltage (but not provide it), so you must power your board yourself while using one of these new programmers.

## ATTiny826 LED Blink

**Blinking a LED is the "Hello, World" of microcontroller programming.** Let's take a look at the code necessary to blink a LED on pin 2 of an [ATTiny286](https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny424-426-427-824-826-827-DataSheet-DS40002311A.pdf). It is compiled and programmed onto the chip using [Microchip Studio](https://www.microchip.com/en-us/tools-resources/develop/microchip-studio).

```c
#define F_CPU 3333333UL
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
	PORTA.DIR = 0xFF;
	while (1)
	{
		PORTA.OUT = 255;
		_delay_ms(500);
		PORTA.OUT = 0;
		_delay_ms(500);
	}
}
```

* `PORTA.DIR` sets the direction of pins on port A (`0xFF` means all outputs)
* `PORTA.OUT` sets output voltage on pins of port A (`0xFF` means all high)
* Using `_delay_ms()` requires including `delay.h`
* Including `delay.h` requires defining `F_CPU` (the CPU frequency)
* The [ATTiny286 datasheet section 11.3.3](https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny424-426-427-824-826-827-DataSheet-DS40002311A.pdf) indicates the default clock is 20 MHz with a /6 prescaler, so the default clock is `3333333 Hz` (3.3 MHz). This behavior can be customized using the Oscillator Configuration Fuse (FUSE.OSCCFG).

## ATTiny826 Pinout

From page 14 of the [ATTiny826 datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny424-426-427-824-826-827-DataSheet-DS40002311A.pdf)

<img src="attiny826-pinout.png" class="img-fluid mx-auto d-block w-75" />

## SMT ATTiny Breakout Board

**Many of the newer AVR series microcontrollers are not available in breadboard-friendly DIP packages.** I find SOIC-to-DIP breakout boards (available on Amazon and eBay) to be useful for experimenting with chips in SOIC packages. Here I added extra power and PA4 (pin 2) LEDs and 10 kΩ current limiting resistors.

<a href="photos/leds2.jpg">
<img src="photos/leds2.jpg" class="img-fluid border shadow my-4" />
</a>

<a href="photos/scope1.jpg">
<img src="photos/scope1.jpg" class="img-fluid border shadow my-4" />
</a>

**I power the device from the 3.3V or 5V pins on a FT232 USB breakout board.** Although the topic is out of scope for this article, I find it convenient to use FTDI chips to exchange small amounts of data or debug messages between a microcontroller and a modern PC over USB without requiring special drivers.

<img src="ft232-breadboard.jpg" class="img-fluid mx-auto d-block w-50" />

## Why is programming modern AVRs so difficult?

**I'm surprised how little information there is online about how to _reliably_ program modern AVR series microcontrollers.** In late 2022 there is a surprisingly large amount of "advice" on this topic which leads to dead ends and broken or abandoned projects. After looking into it for a while, here is my opinionated assessment. Mouser and Digikey have links to expensive programmers, and Amazon has links to similar items but reviews are littered with comments like "arrived bricked" and "can not program AVR series chips". DIY options typically involve abandoned (or soon-to-be abandoned?) GitHub repositories, or instructions for Arduino-related programming. I seek to consolidate and distill the most useful information onto this page, and I hope others will find it useful.

## Atmel-ICE: Expensive but Effective

**After using $5 ICSP programmers for the last decade I almost fell out of my chair when I saw Microchip's recommended entry-level programmer is over $180!** Digikey sells a "basic" version without cables for $130, but that still seems crazy to me. Also, $50 for a ribbon cable?

<a href="avr-ice.webp">
<img src="avr-ice.webp" class="img-fluid w-50 d-block mx-auto my-5" />
</a>

**I found a kit on Amazon that sells the programmer with a cable for $126.** It was hard for me to press that buy button, but I figured the time I would save by having access to modern and exotic chips during the present global chip shortage would make it worth it. After a couple days of free Prime shipping, it arrived. It was smaller than I thought it would be from the product photos.

<a href="photos/atmel-ice-1.jpg">
<img src="photos/atmel-ice-1.jpg" class="img-fluid border shadow my-4" />
</a>

**The cable that came with the device seemed a bit hacky at first, but I'm happy to have it.** The female 2.54 mm socket is easy to insert breadboard jumpers into.

<a href="photos/atmel-ice-2.jpg">
<img src="photos/atmel-ice-2.jpg" class="img-fluid border shadow my-4" />
</a>

**I'm glad this thing is idiot proof.** The very first thing I did after unboxing this programmer was hook it up to my power supply rails using reverse polarity. I misread the pin diagram and confused the _socket_ with the _connector_ (they are mirror images of one another). This is an easy mistake to make though, so here's a picture of the correct orientation. Note the location of the tab on the side of the connector.

Atmel ICE Pinout | Programming Connection
---|---
<a href="atmel-ice-pinout.png"><img src="atmel-ice-pinout.png" class="img-fluid"></a>|<a href="photos/atmel-ice-3.jpg"><img src="photos/atmel-ice-3.jpg" class="img-fluid"></a>

* Black: `GND`
* Red: `Vcc` - This line is used to _sense_ power and not _deliver_ it, so you are responsible for externally powering your board.
* Blue: `UPDI` pin - Although a pull-up resistor on the UPDI pin is recommended, I did not find it was required to program my chip on the breadboard in this configuration.

**The AVR Ice was easy to use with Microchip Studio.** My programmer was detected immediately, a window popped-up and walked me through updating the firmware, and my LED was blinking in no time.

<a href="photos/atmel-ice-4.jpg">
<img src="photos/atmel-ice-4.jpg" class="img-fluid border shadow my-4" />
</a>

## MPLAB Snap: Cheap and Convoluted

**Did I really need to spend $126 for an AVR programmer? Amazon carries the MPLAB Snap for $34, but lots of reviews say it doesn't work.** After easily getting the Atmel-ICE programmer up and running I thought it would be a similarly easy experience setting-up the MPLAB Snap for AVR UPDI programming, but boy was I wrong. Now that I know the steps to get this thing working it's not so bad, but the information here was only gathered after hours of frustration. 

<a href="mplab-snap.webp">
<img src="mplab-snap.webp" class="img-fluid d-block mx-auto w-75" />
</a>

<a href="photos/mplab-snap-1.jpg">
<img src="photos/mplab-snap-1.jpg" class="img-fluid border shadow my-4" />
</a>

Here are the steps you can take to program modern AVR microcontrollers with UPDI using a MPLAB Snap:

### Step 1: Setup MPLAB

* The MPLAB Snap ships with obsolete firmware and must be re-flashed immediately upon receipt.

* Microchip Studio's firmware upgrade tool does not actually work with the MPLAB Snap. It shows the board with version 0.00 software and it hangs (with several USB disconnect/reconnect sounds) if you try to upgrade it.

* You can only re-flash the MPLAB Snap using the MPLAB X IDE. Download the 1.10 GB [MPLAB setup](https://www.microchip.com/en-us/tools-resources/develop/mplab-x-ide) executable and install the MPLAB IDE software which occupies a cool 9.83 GB.

### Step 2: Re-Flash the Programmer

* In the MPLAB IDE select `Tools` and select `Hardware Tool Emergency Boot Firmware Recovery`. At least this tool is helpful. It walks you through how to reset the device and program the latest firmware.

### Step 3: Acknowledge Your Programmer is Defective

Defective may be a strong word, but let's just say the hardware was not designed to enable programming AVR chips using UPDI. Microchip Studio will detect the programmer but if you try to program an AVR you'll get a pop-up error message that provides surprisingly little useful information.

<img src="verify.png" class="mx-auto d-block img-fluid" />

> Atmel Studio was unable to start your debug session. Please verify device selection, interface settings, target power and connections to the target device. Look in the details section for more information.
> StatusCode:	131107
> ModuleName:	TCF (TCF command: Processes:launch failed.)
> An illegal configuration parameter was used. Debugger command Activate physical failed.

### Step 4: Fix Your Programmer

**The reason MPLAB Snap cannot program AVR microcontrollers is because the UPDI pin should be pulled _high_, but the MPLAB Snap comes from the factory with its UPDI pin pulled _low_ with a 4.7 kΩ resistor to ground.** You can try to overpower this resistor by adding a low value pull-up resistor to Vcc (1 kΩ worked for me on a breadboard), but the actual fix is to fire-up the soldering iron and remove that tiny surface-mount pull-down resistor labeled `R48`.

Have your glasses? R48 is here:

<a href="photos/mplab-snap-fix.jpg">
<img src="photos/mplab-snap-fix.jpg" class="img-fluid border shadow my-4" />
</a>


**These photos were taken after I removed the resistor.** I didn't use hot air. I just touched it a for a few seconds with a soldering iron and wiped it off then threw it away.

<a href="photos/scope2.jpg">
<img src="photos/scope2.jpg" class="img-fluid border shadow my-4" />
</a>

You don't need a microscope, but I'm glad I had one.

### Step 5: Reflect

You can now program AVR microcontrollers using UPDI with your MPLAB Snap! Blink, LED, blink.

<a href="photos/mplab-snap-2.jpg">
<img src="photos/mplab-snap-2.jpg" class="img-fluid border shadow my-4" />
</a>

**Can you believe this is the officially recommended action?** According to the official Microchip Engineering Technical Note [ETN #36](http://ww1.microchip.com/downloads/en/DeviceDoc/ETN36_MPLAB%20Snap%20AVR%20Interface%20Modification.pdf): MPLAB Snap AVR Interface Modification

* **Symptom:** Programming and debugging fails with AVR microcontroller devices that use the UPDI/PDI/TPI interfaces. MPLAB SNAP, Assembly #02-10381-R1 requires an external pull-up resistor for AVR microcontroller
devices that use these interfaces.

* **Problem:** AVR microcontroller devices that use the UPDI/PDI/TPI interfaces require the idle state of inactivity to be at a logic high level. Internally, the AVR devices have a weak (50-100K) pull-up resistor that attempts to keep the line high. An external and stronger pull-up resistor may be enough to mitigate this issue and bring voltages to acceptable VDD levels. In some cases, this may not be enough and the pull-down resistor that is part of the ICSP protocol can be removed for these AVR microcontroller applications.

* **Solution:** If most of the applications are AVR-centric, consider removing the R48 resistor as shown below. This completely isolates any loading on the programming data line. Additionally, a pull-up resistor to VDD in the range of 1K to 10K should be used for robustness. Pin 4 of J4 is the TPGD data line used for ICSP interfaces and it also doubles as the DAT signal for UPDI/PDI and TPI interfaces. The pull-up resistor can be mounted directly from TVDD (J4-2) to TPGD/DAT (J4-4). Alternatively, the resistor can be mounted on the application side of the circuit
for convenience.

**I feel genuinely sorry for the Amazon sellers who are getting poor reviews because they sell this product.** It really isn't their fault. I hope Google directs people here so that they can get their boards working and leave positive reviews that point more people to this issue.

## UPDI Programming with a Serial Adapter

There is no official support for UPDI programming using a serial adapter, but it seems some people have figured out how to do it in some capacity. There was a promising [pyupdi](https://github.com/mraardvark/pyupdi) project, but it is now deprecated. At the time of writing the leading project aiming to enable UPDI programming without official hardware is [pymcuprog](https://github.com/microchip-pic-avr-tools/pymcuprog), but its repository has a single commit dated two months ago and no activity since. Interestingly, [that commit](https://github.com/microchip-pic-avr-tools/pymcuprog/commit/593afdc8e089e39a4fed9f4fb19ae81f5f51e9a5.patch) was made by buildmaster@microchip.com (an unverified email address), so it may not be fair to refer to it as an "unofficial" solution. The long term support of the pymcuprog project remains uncertain, but regardless let's take a closer look at how it works.

![](updi-ftdi-serial-programmer.png)

To build a programmer you just need a TTL USB serial adapter and a 1kΩ resistor. These are the steps I used to program a LED blink program using this strategy:

* Use a generic [FT232 breakout board](https://www.amazon.com/s?k=ft232+breakout) to achieve a USB-controlled serial port on my breadboard.

* Connect the programmer as shown with the RX pin directly to the UPDI pin of the microcontroller and the resistor between the RX and TX pins.

* Ensure a [modern version of Python](https://www.python.org/) is installed on your system

* `pip install pymcuprog`

* Use the device manager to identify the name of the COM port representing your programmer. In my case it's `COM12`.

* I then interacted with the microcontroller by running `pymcuprog` from a terminal

### Ping the Microcontroller

This command returns the device ID (1E9328 for my ATtiny826) indicating the UPDI connection is working successfully

```bash
pymcuprog ping -d attiny826 -t uart -u com12
```

```
Connecting to SerialUPDI
Pinging device...
Ping response: 1E9328
```

### Write a Hex File

I used Microchip Studio to compile my C code and generate the hex file. Now I can use `pymcuprog` to load those hex files onto the chip. It's slower to program and inconvenient to drop to a terminal whenever I want to program a chip, but it works.

```
pymcuprog write -f LedBlink.hex -d attiny826 -t uart -u com12
```

```
Connecting to SerialUPDI
Pinging device...
Ping response: 1E9328
Writing from hex file...
Writing flash...
```

## Conclusions

* The new AVR series microcontrollers have lots of cool peripherals for the price and are available during a chip shortage that threatens availability of the more common traditional microcontrollers.

* The Atmel-ICE is expensive, but the most convenient and reliable way to program modern AVR microcontrollers using UPDI.

* The MPLAB Snap can program modern AVRs using UPDI after a software flash and a hardware modification, but its support for AVRs seems like an afterthought rather than its design priority.

* You can create a makeshift unofficial UPDI programmer from a USB serial adapter, but the added complexity, lack of debugging capabilities, increased friction during the development loop, and large number of abandoned projects in this space make this an unappealing long term solution in my opinion.

## Resources

* Atmel-ICE
  * [Atmel-ICE on Mouser](https://www.mouser.com/ProductDetail/Microchip-Technology-Atmel/ATATMEL-ICE?qs=sGAEpiMZZMuRZxwUfDU0miN4udwF8GpUanrVt%252BDSn9Q4SZQ5wSGB4Q%3D%3D) (currently $180.64)
  * [Atmel-ICE on DigiKey](https://www.digikey.com/en/products/detail/microchip-technology/ATATMEL-ICE/4753379) (currently $180.62)
  * [Atmel-ICE on Amazon](https://www.amazon.com/s?k=atmel+ice) (currently $126.49)
  * [Atmel-ICE on eBay](https://www.ebay.com/sch/i.html?_nkw=atmel-ice) (currently $135.00)
  * [Atmel-ICE datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-ICE_UserGuide.pdf)
* MPLAB Snap
  * [MPLAB Snap on Mouser](https://www.mouser.com/ProductDetail/Microchip-Technology-Atmel/PG164100?qs=w%2Fv1CP2dgqoaLDDBjfzhMQ%3D%3D) (currently $34.77)
  * [MPLAB Snap on DigiKey](https://www.digikey.com/en/products/detail/microchip-technology/PG164100/9562532) (currently $34.76)
  * [MPLAB Snap on Amazon](https://www.amazon.com/s?k=mplab+snap) (currently $34.09)
  * [MPLAB Snap datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/50002787C.pdf)
  * [MPLAB Snap AVR UPDI modification](http://ww1.microchip.com/downloads/en/DeviceDoc/ETN36_MPLAB%20Snap%20AVR%20Interface%20Modification.pdf)
* [ATTiny826 datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny424-426-427-824-826-827-DataSheet-DS40002311A.pdf)
* [UPDI Physical Interface](https://onlinedocs.microchip.com/pr/GUID-DDB0017E-84E3-4E77-AAE9-7AC4290E5E8B-en-US-4/index.html)
* [Contact me](https://swharden.com/about/) if you have suggestions or updated information
November 15th, 2022

Interfacing HX710 Differential ADC with Arduino

This page demonstrates how to read differential voltage from a HX710 ADC using Arduino. I recently obtained some pressure sensor boards from Amazon for less than $3 each under names like 6pcs 3.3-5V Digital Barometric Air Pressure Sensor Module Liquid Water Level Controller Board 0-40KPa that use this ADC. Several years ago I worked on a precision pressure meter project based on an I2C temperature and pressure sensor (MS5611), and now that I see new inexpensive SPI pressure sensor modules on the consumer market I'm interested to learn more about their capabilities.

Analog-to-Digital Converter IC

The ADC chip is easily identified as a HX710B 24-Bit Analog-to-Digital Converter (ADC) with Built-in Temperature Sensor. According to the datasheet it can be powered by a 3.3V or 5V supply, and the value it reports is the differential voltage between two input pins.

The datasheet indicates this device can be run from a 3.3V or 5V supply, it uses a built-in fixed-gain (128x) differential amplifier, and it can read up to 40 samples per second. The datasheet provides an example circuit demonstrating how this ADC can be used to measure weight from a scale sensor:

Pressure Sensor

To get a better idea of how this sensor works it would be helpful to locate its product number. I had a hunch it was beneath the part so I desoldered it, and indeed I found part identification information.

The pressure sensor is labeled as a PSG010S but unfortunately I struggled to find a quality datasheet for it. I did find some now-deleted images from an AliExpress listing showing the differences between the base model and the R and S variants. I found this PSG010R datasheet (curiously written in Comic Sans) indicating that maximum voltage is 5V and that the gauge pressure is 0 - 40KPa (0 - 5.8 PSI). This seems to be a fairly standard differential pressure sensor design using a pair of voltage dividers where the pressure is a function of the difference in voltage at the two mid-points (a Wheatstone bridge).

Update (2022-12-23): I received an email from somebody offering additional information about this component:

The PSG010 reports positive and negative pressures and can easily have its range shifted to almost double in one direction with almost none in the other. All that is needed is to lift the +V (2) or ground pin (5) and insert a surface mount 75R ±15R under it. Lifting the ground side by 75R makes it double positive, while pushing the applied +V down makes it double negative (vacuum).
-- bruceg

Read HX710B with Arduino

This code demonstrates how to measure HX710B values using Arduino and display the readings in the serial terminal sufficient to graph in real time using the serial plotter. The animated plot is what it looks like when I blow puffs of air on the sensor.

const int HX_OUT_PIN = 2;
const int HX_SCK_PIN = 3;

enum HX_MODE { NONE, DIFF_10Hz, TEMP_40Hz, DIFF_40Hz};
const byte HX_MODE = DIFF_40Hz;

void setup() {
  pinMode(HX_SCK_PIN, OUTPUT);
  pinMode(HX_OUT_PIN, INPUT);
  Serial.begin(9600);
}

void loop() {
  Serial.println(readHX());
}

unsigned long readHX() {

  // pulse clock line to start a reading
  for (char i = 0; i < HX_MODE; i++) {
    digitalWrite(HX_SCK_PIN, HIGH);
    digitalWrite(HX_SCK_PIN, LOW);
  }

  // wait for the reading to finish
  while (digitalRead(HX_OUT_PIN)) {}

  // read the 24-bit pressure as 3 bytes using SPI
  byte data[3];
  for (byte j = 3; j--;) {
    data[j] = shiftIn(HX_OUT_PIN, HX_SCK_PIN, MSBFIRST);
  }

  data[2] ^= 0x80;  // see note

  // shift the 3 bytes into a large integer
  long result;
  result += (long)data[2] << 16;
  result += (long)data[1] << 8;
  result += (long)data[0];

  return result;
}

Note: This code flips the most significant bit of the sensor reading. The sensor always returns this bit as 1, except for the case of an out-of-range error (see excerpt from datasheet below). By simply flipping the bit our reported values are a continuous range from 0 to 2^14-1, with the edge values representing out-of-range errors.

The output 24 bits of data is in 2’s complement format. When input differential signal goes out of the 24 bit range, the output data will be saturated at 0x800000 (MIN) or 0x7FFFFF (MAX) until the input signal comes back to the input range.
-- HX710 datasheet

Update (2022-12-23): I received an email from someone offering feedback about this code:

This code works in a loop, but perhaps by accident. The strongly worded statements in the HX710 datasheet about 25 - 27 clocks per readout imply that it is risky to rely on this. It may be that hanging clocks induce unwanted sleep modes or over-run into the next read cycle, etc. There is simply no real explanation in what is shown, so best to be safe - always set the next mode immediately AFTER collecting a reading and then always poll for new data ready before attempting a collection. Your 'pulse clock line to start a reading' loop before a reading should be 'add next mode' after a reading to comply with the timing specification. This will ensure that the next conversion will be available rather than the next scheduled conversion AFTER the mode is eventually sent.
-- bruceg

Open-Source HX710B Libraries

Although some libraries are available which facilitate interacting with the HX710, here I engage with it discretely to convey each step of the conversion and measurement process. I found that many libraries use the 10 Hz mode by default, whereas I certainly prefer the 40 Hz mode. More frustratingly, code in many libraries refer to this as gain, which is incorrect. The datasheet indicates gain is fixed at 128 and cannot be changed in software.

Update (2022-12-23): I received an email explaining why people often use "gain" and "mode" when referring to the HX710:

The HX711 is similar to the HX710 but it has user selectable gain AND user selectable sample rates BUT only certain combinations are allowed, so setting mode WILL also select its matched gain value. The HX710 uses most of the same internals, but with just 3 modes - reading the Wheatstone Bridge always using 128 gain at 10 or 40Hz while swapping to Avolt (HX710A) or internal Temperature (HX710B) uses a lower gain and less digits. So for people familiar with the HX711 there is no ambiguity in mixing mode and gain.
-- bruceg

Resources

Markdown source code last modified on December 24th, 2022
---
Title: Interfacing HX710 Differential ADC with Arduino
Description: How to read differential voltage from a HX710 ADC using Arduino
Date: 2022-11-15 12:30AM EST
Tags: circuit, microcontroller
---

# Interfacing HX710 Differential ADC with Arduino

**This page demonstrates how to read differential voltage from a HX710 ADC using Arduino.** I recently obtained some pressure sensor boards from Amazon for less than $3 each under names like _6pcs 3.3-5V Digital Barometric Air Pressure Sensor Module Liquid Water Level Controller Board 0-40KPa_ that use this ADC. Several years ago I worked on a [precision pressure meter project](https://swharden.com/blog/2017-04-29-precision-pressure-meter-project/) based on an [I2C](https://en.wikipedia.org/wiki/I%C2%B2C)  temperature and pressure sensor ([MS5611](https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS5611-01BA03%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS5611-01BA03_B3.pdf%7FCAT-BLPS0036)), and now that I see new inexpensive [SPI](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface) pressure sensor modules on the consumer market I'm interested to learn more about their capabilities.

<img src="hx710b-pressure-board.jpg" class="my-5 border border-dark shadow img-fluid w-75 mx-auto d-block">

## Analog-to-Digital Converter IC

**The ADC chip is easily identified as a [HX710B](https://www.electronicscomp.com/datasheet/hx710b-ic-datasheet.pdf) _24-Bit Analog-to-Digital Converter (ADC) with Built-in Temperature Sensor_.** According to the datasheet it can be powered by a 3.3V or 5V supply, and the value it reports is the differential voltage between two input pins. 

<img src="hx710b-pinout.jpg" class="my-5 img-fluid w-75 mx-auto d-block">

The datasheet indicates this device can be run from a 3.3V or 5V supply, it uses a built-in fixed-gain (128x) differential amplifier, and it can read up to 40 samples per second. The datasheet provides an example circuit demonstrating how this ADC can be used to measure weight from a scale sensor:

<img src="hx710-datasheet.jpg" class="my-5 img-fluid w-75 mx-auto d-block">

## Pressure Sensor

To get a better idea of how this sensor works it would be helpful to locate its product number. I had a hunch it was beneath the part so I desoldered it, and indeed I found part identification information.

<img src="hx710b-pressure-psg010s.jpg" class="my-5 border border-dark shadow img-fluid w-75 mx-auto d-block">

**The pressure sensor is labeled as a PSG010S** but unfortunately I struggled to find a quality datasheet for it. I did find some now-deleted images from an AliExpress listing showing the differences between the base model and the R and S variants. 
I found [this PSG010R datasheet](https://www.katranji.com/tocimages/files/536845-544144.pdf) (curiously written in Comic Sans) indicating that maximum voltage is 5V and that the gauge pressure is 0 - 40KPa (0 - 5.8 PSI). This seems to be a fairly standard [differential pressure sensor](https://www.avnet.com/wps/portal/abacus/solutions/technologies/sensors/pressure-sensors/measurement-types/differential/) design using a pair of voltage dividers where the pressure is a function of the difference in voltage at the two mid-points (a [Wheatstone bridge](https://en.wikipedia.org/wiki/Wheatstone_bridge)).

**Update (2022-12-23):** I received an email from somebody offering additional information about this component:

> The PSG010 reports positive and negative pressures and can easily have its range shifted to almost double in one direction with almost none in the other.  All that is needed is to lift the +V (2) or ground pin (5) and insert a surface mount 75R ±15R under it. 
Lifting the ground side by 75R makes it double positive, while pushing the applied +V down makes it double negative (vacuum).<br>
> -- <cite class="text-end">bruceg</cite>


<img src="psg-pressure-sensor.jpg" class="my-5 border border-dark shadow img-fluid w-75 mx-auto d-block">

## Read HX710B with Arduino

**This code demonstrates how to measure HX710B values using Arduino** and display the readings in the serial terminal sufficient to graph in real time using the serial plotter. The animated plot is what it looks like when I blow puffs of air on the sensor.

<img src="hx710-arduino-plot.gif" class="my-5 img-fluid mx-auto d-block">

```c
const int HX_OUT_PIN = 2;
const int HX_SCK_PIN = 3;

enum HX_MODE { NONE, DIFF_10Hz, TEMP_40Hz, DIFF_40Hz};
const byte HX_MODE = DIFF_40Hz;

void setup() {
  pinMode(HX_SCK_PIN, OUTPUT);
  pinMode(HX_OUT_PIN, INPUT);
  Serial.begin(9600);
}

void loop() {
  Serial.println(readHX());
}

unsigned long readHX() {

  // pulse clock line to start a reading
  for (char i = 0; i < HX_MODE; i++) {
    digitalWrite(HX_SCK_PIN, HIGH);
    digitalWrite(HX_SCK_PIN, LOW);
  }

  // wait for the reading to finish
  while (digitalRead(HX_OUT_PIN)) {}

  // read the 24-bit pressure as 3 bytes using SPI
  byte data[3];
  for (byte j = 3; j--;) {
    data[j] = shiftIn(HX_OUT_PIN, HX_SCK_PIN, MSBFIRST);
  }
  
  data[2] ^= 0x80;  // see note

  // shift the 3 bytes into a large integer
  long result;
  result += (long)data[2] << 16;
  result += (long)data[1] << 8;
  result += (long)data[0];

  return result;
}
```

**Note: This code flips the most significant bit of the sensor reading.** The sensor always returns this bit as `1`, except for the case of an out-of-range error (see excerpt from datasheet below). By simply flipping the bit our reported values are a continuous range from `0` to `2^14-1`, with the edge values representing out-of-range errors.

> The output 24 bits of data is in [2’s complement format](https://en.wikipedia.org/wiki/Two%27s_complement).
> When input differential signal goes out of the 24 bit range, the output data will be saturated at `0x800000` (MIN) or `0x7FFFFF` (MAX)
> until the input signal comes back to the input range.<br>
> -- <cite class="text-end"><a href='https://www.electronicscomp.com/datasheet/hx710b-ic-datasheet.pdf'>HX710 datasheet</a></cite>

**Update (2022-12-23):** I received an email from someone offering feedback about this code:

> This code works in a loop, but perhaps by accident. The strongly worded statements in the [HX710 datasheet](https://www.electronicscomp.com/datasheet/hx710b-ic-datasheet.pdf) about 25 - 27 clocks per readout imply that it is risky to rely on this.  It may be that hanging clocks induce unwanted sleep modes or over-run into the next read cycle, etc.  There is simply no real explanation in what is shown, so best to be safe - always set the next mode immediately AFTER collecting a reading and then always poll for new data ready before attempting a collection. Your 'pulse clock line to start a reading' loop before a reading should be 'add next mode' after a reading to comply with the timing specification.  This will ensure that the next conversion will be available rather than the next scheduled conversion AFTER the mode is eventually sent.<br>
> -- <cite class="text-end">bruceg</cite>

## Open-Source HX710B Libraries

Although some libraries are available which facilitate interacting with the HX710, here I engage with it discretely to convey each step of the conversion and measurement process. I found that many libraries use the 10 Hz mode by default, whereas I certainly prefer the 40 Hz mode. More frustratingly, code in many libraries refer to this as _gain_, which is incorrect. The datasheet indicates gain is fixed at 128 and cannot be changed in software.

**Update (2022-12-23):** I received an email explaining why people often use "gain" and "mode" when referring to the HX710:

> The [HX711](https://www.digikey.com/htmldatasheets/production/1836471/0/0/1/HX711.pdf) is similar to the [HX710](https://www.electronicscomp.com/datasheet/hx710b-ic-datasheet.pdf) but it has user selectable gain AND user selectable sample rates BUT only certain combinations are allowed, so setting mode WILL also select its matched gain value.
The HX710 uses most of the same internals, but with just 3 modes - reading the Wheatstone Bridge always using 128 gain at 10 or 40Hz while swapping to Avolt (HX710A) or internal Temperature (HX710B) uses a lower gain and less digits. So for people familiar with the HX711 there is no ambiguity in mixing mode and gain.<br>
> -- <cite class="text-end">bruceg</cite>

## Resources

* [Pressure Sensor Guide](https://www.electroschematics.com/pressure-sensor-guide/) by T.K. HAREENDRAN - A similar write-up that goes into additional detail. They didn't de-solder the pressure sensor to identify the component name, but there's lots of good information on this page.

* [bogde/HX711 on GitHub](https://github.com/bogde/HX711) - An Arduino library to interface the Avia Semiconductor HX711 24-Bit Analog-to-Digital Converter (ADC) for reading load cells / weight scales. Code on this page does not use this library, but others may find it helpful.

* [HX710 datasheet (English)](https://www.electronicscomp.com/datasheet/hx710b-ic-datasheet.pdf)

* [Differential pressure sensors](https://www.avnet.com/wps/portal/abacus/solutions/technologies/sensors/pressure-sensors/measurement-types/differential/) - Article about the topic which includes a good example of an instrumentation amplifier.

* [Design tips for a resistive-bridge pressure sensor in industrial process-control systems](https://www.ti.com/lit/an/slyt640/slyt640.pdf) - Texas Instruments application note

* [The Wheatstone Bridge](https://meritsensor.com/the-wheatstone-bridge/) by Michael Daily
October 11th, 2020

Exploring the Voltage-Clamp Membrane Test

By modeling a voltage-clamp amplifier, patch pipette, and cell membrane as a circuit using free circuit simulation software, I was able to create a virtual patch-clamp electrophysiology workstation and challenge model neurons with advanced voltage-clamp protocols. By modeling neurons with known properties and simulating experimental membrane test protocols, I can write membrane test analysis software and confirm its accuracy by comparing my calculated membrane measurements to the values in the original model. A strong advantage of this method (compared to using physical model cells) is that I can easily change values of any individual component to assess how it affects the accuracy of my analytical methods.

Instead of modeling a neuron, I modeled the whole patch-clamp system: the amplifier (with feedback and output filtering), pipette (with an imperfect seal, series resistance, and capacitance), and cell (with membrane resistance, capacitance, and a resting potential). After experimenting with this model for a while I realized that advanced topics (like pipette capacitance compensation, series resistance compensation, and amplifier feedback resistance) become much easier to understand when they are represented as components in a circuit with values that can be adjusted to see how the voltage-clamp trace is affected. Many components of the full model can be eliminated to generate ideal traces, and all models, diagrams, and code shown here can be downloaded from my membrane test repository on GitHub.

Circuit Components

Cell

  • Vm (Membrane Potential): Voltage difference across the neuron's membrane. Neurons typically maintain a membrane potential near -70 mV. In our model we can simulate this by connecting Rm to a -70 mV voltage source instead of grounding it as shown in the diagram above.

  • Rm (Membrane Resistance): The resistance across the cell membrane. Resistance is inversely correlated with membrane conductivity (influenced primarily by the number of open channels in the membrane). Membrane resistance is sometimes termed "input resistance" because in combination with cell capacitance it determines the time constant of the voltage response to input currents.

  • Cm (Membrane Capacitance): The capacitance of a neuron describes how much charge is required to change its voltage. Larger cells with more membrane surface area have greater capacitance and require more charge (current times time) to swing their voltage.

  • Tau (Membrane Time Constant, τcell): The membrane time constant describes how fast the cell changes voltage in response to currents across its membrane. This is distinctly different than the voltage clamp time constant which describes how fast the cell changes voltage in response to currents delivered through the patch pipette (dependent on Ra, not Rm). This metric is best thought of with respect to synaptic currents (not currents delivered through the patch pipette). This is a true biological property of the cell, as it exists even when a pipette is not present to measure it. Membrane time constant is membrane capacitance times membrane resistance. If two cells have the same resistance, the larger one (with greater capacitance) will have a slower membrane time constant.

Pipette

  • Ra (Access Resistance): The resistance caused by the small open tip of the patch pipette. If a pipette tip gets clogged this resistance will increase, leading to a failed experiment. Access resistance is the primary contributor to series resistance, but a lesser contributor to input resistance.

  • Rp (Pipette Resistance): Resistance between the amplifier and the tip of the pipette. Resistance of the solution inside the electrode forms a large component of this resistance, but it is such a low resistance is can often be ignored. Its most important consideration is how it combines with Cp to form a low-pass filter inside the pipette (partially overcome by series resistance compensation) to disproportionately degrade fast voltage-clamp transitions.

  • Rs (Seal Resistance): The resistance formed by the seal between the cell surface and the glass pipette. Ideal experiments will have high seal resistances in the GΩ range.

  • Rseries (Series Resistance): Sum of all non-biological resistances. Access resistance is the largest contributor to series resistance, but pipette resistance and reference electrode resistance also influences it. Series resistance is bad for two reasons: it acts as a low-pass filter inside the pipette (reducing magnitude of small transients), and it also acts as a voltage divider in series with membrane resistance (resulting in steady-state voltage error). How impactful each of these are to your experiment is easy to calculate or simulate, and a good experiment will have a membrane / series resistance ratio greater than 10.

  • TauClamp (Voltage Clamp Time Constant, τclamp): The voltage clamp time constant describes how fast the cell changes voltage in response to currents delivered through the patch pipette. This metric is largely determined by access resistance, and it is typically much smaller than the membrane time constant. It describes the relationship between Ra and Cm, and it does not involve Rm. I consider this measurement purely artificial (not biological) because when a pipette is not in a cell this time constant does not exist.

Amplifier

  • Vc (Command Voltage): This is the voltage the experimenter tries to move the cell toward. This isn't always exactly what the cell gets though. First, Cp and Rp form a small low-pass filter delaying measurement of Vm. Similarly, Ra and Cm form a low-pass filter that delays the clamp system from being able to rapidly swing the voltage of the cell. Finally, Ra and Rm combine to form a voltage divider, leading the amplifier to believe the cell's voltage is slightly closer to Vc than it actually is. Many of these issues can be reduced by capacitance compensation and series resistance compensation.

  • Vo (Amplifier Output Voltage): This voltage exiting the amplifier. It is proportional to the current entering the pipette (passing through Rf according to Ohm's law). Divide this value by Rf to determine the current emitted from the amplifier.

  • Rf (Feedback Resistance): Negative feedback for the amplifier. The greater the resistance the smaller the noise but the smaller the range of the output. Large resistances >1GΩ are used for single channel recordings and lower resistances <1GΩ are used for whole-cell experiments.

  • Cf (Feedback Capacitance): This capacitor forms an RC low-pass filter with Rf to prevent ringing or oscillation. This is tangentially related to capacitance compensation which uses variable capacitance to a computer-controlled voltage to reduce the effects of Cp. The main point of this capacitor here is to stabilize our simulation when Cp is added.

  • Io (Clamp Current): Current entering the pipette. This isn't measured directly, but instead calculated from the amplifier's output voltage (measured by an analog-to-digital converter) and calculated as Vo/Rf according to Ohm's law.

Modeling a Patch-Clamp Experiment in LTSpice

LTSpice is a free analog circuit simulator by Analog Devices. I enjoy using this program, but only because I'm used to it. For anyone trying to use it for the first time, I'm sorry. Watch a YouTube tutorial to learn how to get up and running with it. Models used in this project are on GitHub if you wish to simulate them yourself.

This circuit simulates a voltage clamp membrane test (square pulses, ±5mV, 50% duty, 20 Hz) delivered through a patch pipette (with no pipette capacitance), a 1GΩ seal, 15 MΩ access resistance, in whole-cell configuration with a neuron resting at -70 mV with 500 MΩ membrane resistance and 150 pF capacitance. The Bessel filter is hooked-up through a unity gain op-amp so it can be optionally probed without affecting the primary amplifier. It's configured to serve as a low-pass filter with a cut-off frequency of 2 kHz.

Simulating a Membrane Test

The simulated membrane test shows a typical voltage-clamp trace (green) which is interesting to compare to the command voltage (red) and the actual voltage inside the cell (blue). Note that although the hardware low-pass filter is connected, the green trace is the current passing through the feedback resistor (Rf). A benefit of this simulation is that we can probe anywhere, and being able to see how the cell's actual voltage differs from the target voltage is enlightening.

If your clamp voltage does not have sharp transitions, manually define rise and fall times as non-zero values in the voltage pulse configuration options. Not doing this was a huge trap I fell into. If the rise time and fall time is left at 0, LTSpice will invent a time for you which defaults to 10%! This slow rise and fall of the clamp voltage pulses was greatly distorting the peaks of my membrane test, impairing calculation of I0, and throwing off my results. When using the PULSE voltage source set the rise and fall times to 1p (1 picosecond) for ideally sharp edges.

If saving simulation data consider defining the maximum time step. Leaving this blank is typically fine for inspecting the circuit within LTSpice, but if you intend to save .raw simulation files and analyze them later with Python (especially when using interpolation to simulate a regular sample rate) define the time step to be a very small number before running the simulation.

Low-Pass Filtering

Let's compare the output of the amplifier before and after low-pass filtering. You can see that the Bessel filter takes the edge off the sharp transient and changes the shape of the curve for several milliseconds. This is an important consideration for analytical procedures which seek to measure the time constant of the decay slope, but I'll leave that discussion for another article.

Calculate Clamp Current from Amplifier Output Voltage

Patch-clamp systems use a digital-to-analog converter which measures voltage coming out of the amplifier to infer the current being delivered into the pipette. In other words, the magic ability LTSpice gives us to probe current passing through any resistor in the circuit isn't a thing in real life. Instead, we have to use Ohm's law to calculate it as the ratio of voltage and feedback resistance.

Let's calculate the current flowing into the pipette at the start of this trace when the amplifier's output voltage is -192 mV and our command potential is -75 mV:

V = I * R
I = V / R
I = (Vout - Vcmd) / Rf
I = ((-192e-3 V) - (-75e-3 V)) / 500e6 Ω
I = -234 pA

Notice I use math to get the difference of Vout and Vcmd, but in practice this is done at the circuit level using a differential amplifier instead of a unity gain op-amp like I modeled here for simplicity.

Amplifier Feedback Capacitance

Let's further explore this circuit by adding pipette capacitance. I set Cp to 100 pF (I know this is a large value) and observed strong oscillation at clamp voltage transitions. This trace shows voltage probed at the output of the Bessel filter.

A small amount of feedback capacitance reduced this oscillation. The capacitor Cf placed across Rf serves as an RC low-pass filter to tame the amplifier's feedback. Applying too much capacitance slows the amplifier's response unacceptably. It was impressive to see how little feedback capacitance was required to change the shape of the curve. In practice parasitic capacitance likely makes design of patch-clamp amplifier headstages very challenging. Experimenting with different values of Cp and Cf is an interesting experience. Here setting Cp to 1 pF largely solves the oscillation issue, but its low-pass property reduces the peaks of the capacitive transients.

Two-Electrode Giant Squid Axon Model

I created another model to simulate a giant squid axon studied with a two-electrode system. It's not particularly useful other than as a thought exercise. By clamping between two different voltages you can measure the difference in current passing through the stimulation resistor to estimate the neuron's membrane resistance. This model is on GitHub too if you want to change some of the parameters and see how it affects the trace.

Let's calculate the squid axon's membrane resistance from the simulation data just by eyeballing the trace.

ΔV = (-65 mV) - (-75 mV) = 10 mV <-- Δ command voltage
ΔI = (5 µA) - (-5 µA) = 10 µA <-- Δ amplifier current
V = I * R
ΔV = ΔI * Rm
Rm = ΔV / ΔI
Rm = 10e-3 V / 10e-6 A
Rm = 1kΩ <-- calculated membrane resistance

Load LTSpice Simulation Data with Python

LTSpice simulation data is saved in .raw files can be read analyzed with Python allowing you to leverage modern tools like numpy, scipy, and matplotlib to further explore the ins and outs of your circuit. I'll discuss membrane test calculations in a future post. Today let's focus on simply getting these data from LTSpice into Python. Simulation data and full Python code is on GitHub. Here we'll analyze the .raw file generated by the whole-cell circuit model above.

# read data from the LTSpice .raw file
import ltspice
l = ltspice.Ltspice("voltage-clamp-simple.raw")
l.parse()

# obtain data by its identifier and scale it as desired
times = l.getTime() * 1e3 # ms
Vcell = l.getData('V(n003)') * 1e3  # mV
Vcommand = l.getData('V(vcmd)') * 1e3  # mV
Iclamp = l.getData('I(Rf)') * 1e12  # pA

# plot scaled simulation data
import matplotlib.pyplot as plt

ax1 = plt.subplot(211)
plt.grid(ls='--', alpha=.5)
plt.plot(times, Iclamp, 'r-')
plt.ylabel("Current (pA)")

plt.subplot(212, sharex=ax1)
plt.grid(ls='--', alpha=.5)
plt.plot(times, Vcell, label="Cell")
plt.plot(times, Vcommand, label="Clamp")
plt.ylabel("Potential (mV)")
plt.xlabel("Time (milliseconds)")
plt.legend()

plt.margins(0, .1)
plt.tight_layout()
plt.show()

LTSpice simulation data points are not evenly spaced in time and may require interpolation to produce data similar to an actual recording which samples data at a regular rate. This topic will be covered in more detail in a later post.

Membrane Test Analysis

Let's create an ideal circuit, simulate a membrane test, then analyze the data to see if we can derive original values for access resistance (Ra), cell capacitance (Cm), and membrane resistance (Rm). I'll eliminate little tweaks like seal resistance, pipette capacitance, and hardware filtering, and proceed with a simple case voltage clamp mode.

⚠️ WARNING: LTSpice voltage sources have a non-negligible conductance by default, so if you use a voltage source at the base of Rm without a defined resistance you'll have erroneous steady state current readings. Prevent this by defining series resistance to a near infinite value instead of leaving it blank.

Now let's run the simulation and save the output...

I created a diagram to make it easier to refer to components of the membrane test:

Think conceptually about what's happening here: When the command voltage abruptly changes, Vcell and Vcommand are very different, so the voltage-clamp amplifier delivers a large amount of current right after this transition. The peak current (Ipeak) occurs at time zero relative to the transition. The current change between the previous steady-state current (Iprev) and the peak current (Ipeak) is only limited by Ra (since Cm only comes in to play after time passes). Let's call this maximum current change Id. With more time the current charges Cm, raising the Vcell toward (Vcommand) at a rate described by TauClamp. As Vcell approaches Vcommand the amplifier delivers less current. Altogether, amplifier current can be approximated by an exponential decay function:

It = Id * exp(-t / τclamp) + Iss

Analyze the Capacitive Transient

The speed at which Vcell changes in response to current delivered through the pipette is a property of resistance (Ra) and capacitance (Cm). By studying this curve, we can calculate both. Let's start by isolating one curve. We start by isolating individual capacitive transients:

Fit each curve to a single exponential function. I'll gloss over how to do this because it is different for every programming language and analysis software. See my Exponential Fit with Python for details. Basically you'll fit a curve which has 3 parameters: m, tau, and b. You may wish to change the sign of tau depending on the orientation of the curve you are fitting. If your signal is low-pass filtered you may want to fit a portion of the curve avoiding the fastest (most distorted) portion near the peak. If you want to follow along, code for this project is on GitHub.

It = m * exp(-t / tau) + b

These are the values I obtained by fitting the curve above:

m = 667.070
tau = 2.250
b = -129.996

Meaning the curve could be modeled by the equation:

I = 667.070 * exp(-t / 2.250) -129.996

From these values we can calculate the rest:

  • tau is one of the fitted parameters and has the same time units as the input data. Don't confuse this value with the cell's time constant (which describes how current across Rm changes Vm), but instead this value is the time constant of the voltage clamp system (where current across Ra changes Vm). Because Ra is much smaller than Rm, this will be a much faster time constant.

  • State current (Iss) is b from the curve fit

  • The state current before the step will be called Iprev

  • Change in old vs. new steady state current will be Idss

  • Peak current (Ipeak) occurs at time zero (when the exponential term is 1) so this is simply m + b

  • Id is peak transient current (difference between Ipeak and Iprev). Some papers call this I0, but other papers use that abbreviation to refer to Ipeak, so I'll avoid using that term entirely.

We now have:

Iss: -129.996 pA
Iprev: -150.015 pA
Idss: 20.019 pA
Ipeak: 537.074 pA
Id: 687.089 pA
dV: 10 mV
TauClamp: 2.250 ms

Calculate Ra

At time zero, access resistance is the thing limiting our ability to deliver current (Id) to a known ΔV (10 mV). Therefore we can calculate Ra using Ohm's law:

V = I * R
ΔV = ΔI * R
R = ΔV / ΔI
Ra = dV / Id
Ra = 10e-3 V / 687.089e-12 A
Ra = 14.554 MΩ <-- pretty close to our model 15 MΩ

For now let's call this Ra, but note that this is technically Ra mixed with a small leakage conductance due to Rm. Since Ra is so much smaller than Rm this small conductance doesn't affect our measurement much. Accuracy of this value will be improved when we apply leak current correction described later on this page.

Calculate Rm

Now that we know Ra, we can revisit the idea that the difference between this steady state current (Iss) and the last one (Iprev) is limited by the sum of Rm and Ra. let's use this to calculate Rm using Ohm's law:

V = I * R
I = V / R
ΔI = ΔV / R
R * ΔI = ΔV
(Ra + Rm) * ΔI = ΔV
Ra * ΔI + Rm * ΔI = ΔV
Rm * ΔI = ΔV - Ra * ΔI
Rm = (ΔV - Ra * ΔI) / ΔI
Rm = (dV - Ra * Idss) / Idss
Rm = (10e-3 V - (14.554e6 Ω * 20.019e-12 A)) / 20.019e-12 A
Rm = 485 MΩ <-- pretty close to our model 500 MΩ

Accuracy of this value will be improved when we apply leak current correction described later on this page.

Calculate Cm from Ra, Rm, and Tau

When we raise the cell's voltage (Vm) by delivering current through the pipette (Ra), some current escapes through Rm. From the cell's perspective when we charge it though, Ra and Rm are in parallel.

tau = R * C
C = tau / R
Cm = tau / (1/(1/Ra + 1/Rm))
Cm = 2.250e-3 sec / (1/(1/14.554e6 Ω + 1/485e6 Ω))
Cm = 159 pF <-- pretty close to our model 150 pF

Accuracy of this value will be improved when we apply leak current correction described later on this page.

Calculate Cm from the Area Under the Curve

Cell capacitance can alternatively be estimated by measuring the area under the capacitive transient. This method is frequently used historically, and it is simpler and faster than the method described above because it does not require curve fitting. Each method has its pros and cons (e.g., sensitivity to access resistance, hardware filtering, or resilience in the presence of noise or spontaneous synaptic currents). Rather than compare and contrast the two methods, I'll simply describe the theory underlying how to perform this measurement.

After an abrupt voltage transition, all current delivered above the steady state current level goes toward charging the cell, so by integrating this current over time we can calculate how much charge (Q) was delivered. I'll describe this measurement as area under the curve (AUC). When summing these data points yourself be sure to remember to subtract steady state current and divide by the sample rate. Code for this example is on GitHub.

Charge is measured in Coulombs. Area under the curve is 1515.412 pA*ms, but recall that a femtocoulomb is 1pA times 1ms, so it's more reasonable to describe the AUC as 1515.412 fC. This is the charge required to raise cell's capacitance (Cm) by dV. The relationship is described by:

Q = C * ΔV
C = Q / ΔV
Cm = AUC / ΔV
Cm = 1515.412e-15 C / 10e-3 V
Cm = 1515.412e-15 C / 10e-3 V
Cm = 151.541 pF <-- pretty close to our model 150 pF

This value is pretty close to what we expect, and I think its accuracy in this case is largely due to the fact that we simulated an ideal unfiltered voltage clamp trace with no noise. Its under-estimation is probably due to the fact that a longer period wasn't used for the integration (which may have been useful for this noise-free simulation, but would not be useful in real-world data). Additional simulation experiments with different combinations of noise and hardware filtering would be an interesting way to determine which methods are most affected by which conditions. Either way, this quick and dirty estimation of whole-cell capacitance did the trick in our model cell.

Correcting for Leak Current

Why weren't our measurements exact? Rm leaks a small amount of the Id current that passes through Ra to charge Cm. If you calculate the parallel combined resistance of Ra and Rm you get 14.56 MΩ which is pretty much exactly what we measured in our first step and simply called Ra at the time. Now that we know the value of both resistances we can calculate a correction factor as the ratio of Ra to Rm and multiply it by both of our resistances. Cm can be corrected by dividing it by the square of this ratio.

correction = 1 + Ra / Rm
correction = 1 + 14.554 MΩ / 484.96 MΩ
correction = 1.03

Ra = Ra * correction
Rm = Rm * correction
Cm = Cm / (correction^2)
Metric Model Measured Corrected Error
Ra 15 MΩ 14.55 MΩ 14.99 MΩ <1%
Rm 500 MΩ 484.96 MΩ 499.51 MΩ <1%
Cm (fit) 150 pF 159.20 pF 150.06 pF <1%

This correction is simple and works well when Ra/Rm is small. It's worth noting that an alternative to this correction is to solve for Ra and Rm simultaneously. The Membrane Test Algorithms used by pCLAMP calculate Ra this way, solving the following equation iteratively using the Newton-Raphson method:

Ra^2 - Ra * Rt + Rt * (Tau/Cm) = 0

Overall the values I calculated are within a few percent of expectations, and I'm satisfied with the calculation strategy summarized here. I am also impressed with what we were able to achieve by modeling a voltage-clamped neuron using a free circuit simulator!

Use a Voltage-Clamp Ramp to Measure Cm

It's possible to simulate a voltage-clamp ramp and analyze that trace to accurately measure cell capacitance. A strong advantage of this method is that it does not depend on Ra. Let's start by simulating a 10 mV ramp over 100 ms (50 ms down, 50 ms up). When we simulate this with LTSpice and plot it with Python (screenshots, data, and code is on GitHub) we find that cell voltage lags slightly behind the clamp voltage.

During voltage-clamp ramps Vm lags behind the command voltage because charging Cm is limited by Ra. If we measure the difference in this lag between descending and ascending ramps, we can estimate Cm in a way that is insensitive to Ra. Stated another way, Ra only affects abrupt changes in charging rate. Once the cell is charging at a steady rate, that rate of charge is largely unaffected by Ra because the stable charging current is already increased to counteract the previous effect Ra. Stated visually, Ra only affects the rate of charging at the corners of the V. Therefore, let's proceed ignoring the corners of the V and focus on the middle of each slope where the charging rate is stable (and effect of Ra is negligible).

Analysis is achieved by comparing the falling current to the rising current. We start separately isolating the falling and rising traces, then reverse one of them and plot the two on top of each other. The left and right edges of this plot represent edges of ramps where the system is still stabilizing to compensate for Ra, so let's ignore that part and focus on the middle where the charging rate is stable. We can measure the current lag as half of the mean difference of the two traces. Together with the rate of charge (the rate of the command voltage change) we have everything we need to calculate Cm.

dI = dQ / dt
dI = Cm * dV / dt
Cm = dI / (dV / dT)
Cm = (59.997e-12 A / 2) / (10e-3 V / 50e-3 sec) <-- 10 mV over 50 ms
Cm = 149.993 pF <-- Our model is 150 pF

This is a fantastic result! The error we do get is probably the result of a single point of interpolation error while converting the unevenly spaced simulation data to an evenly-spaced array simulating a 20 kHz signal. In this ideal simulation this method of calculating Cm appears perfect, but in practice it is highly sensitive to sporadic noise that is not normally distributed (like synaptic currents). If used in the real world each ramp should be repeated many times, and only the quietest sweeps (with the lowest variance in the difference between rising and falling currents) should be used for analysis. However, this is not too inconvenient because this protocol is so fast (10 repetitions per second).

Summary

This page described how to model voltage-clamp membrane test sweeps and analyze them to calculate Ra, Cm, and Rm. We validated our calculations were accurate by matching our calculated values to the ones used to define the simulation. We also explored measuring the area under the curve and using voltage-clamp ramps as alternative methods for determining Cm. There are a lot of experiments that could be done to characterize the relationship of noise, hardware filtering, and cell properties on the accuracy of these calculations. For now though, I'm satisfied with what we were able to achieve with free circuit simulation software and basic analysis with Python. Code for this project is on GitHub.

Metric Model Calculated Error
Ra 15 MΩ 14.99 MΩ <1%
Rm 500 MΩ 499.51 MΩ <1%
Cm (fit) 150 pF 150.06 pF <1%
Cm (auc) 150 pF 151.541 pF ~1%
Cm (ramp) 150 pF 149.993 pF <.01%

Resources

Markdown source code last modified on September 2nd, 2022
---
title: Exploring the Voltage-Clamp Membrane Test
date: 2020-10-11 22:24:00
tags: science, circuit
---

# Exploring the Voltage-Clamp Membrane Test

**By modeling a voltage-clamp amplifier, patch pipette, and cell membrane as a circuit** using free circuit simulation software, I was able to create a virtual patch-clamp electrophysiology workstation and challenge model neurons with advanced voltage-clamp protocols. By modeling neurons with known properties and simulating experimental membrane test protocols, I can write membrane test analysis software and confirm its accuracy by comparing my calculated membrane measurements to the values in the original model. A strong advantage of this method (compared to using physical model cells) is that I can easily change values of any individual component to assess how it affects the accuracy of my analytical methods.

<div class="text-center">

![](whole-cell-voltage-clamp-diagram.png)

</div>

**Instead of modeling a neuron, I modeled the whole patch-clamp system:** the amplifier (with feedback and output filtering), pipette (with an imperfect seal, series resistance, and capacitance), and cell (with membrane resistance, capacitance, and a resting potential). After experimenting with this model for a while I realized that advanced topics (like pipette capacitance compensation, series resistance compensation, and amplifier feedback resistance) become much easier to understand when they are represented as components in a circuit with values that can be adjusted to see how the voltage-clamp trace is affected. Many components of the full model can be eliminated to generate ideal traces, and all models, diagrams, and code shown here can be downloaded from my [membrane test repository](https://github.com/swharden/memtest) on GitHub.

## Circuit Components

### Cell

* **`Vm` (Membrane Potential):** Voltage difference across the neuron's membrane. _Neurons typically maintain a membrane potential near -70 mV. In our model we can simulate this by connecting `Rm` to a -70 mV voltage source instead of grounding it as shown in the diagram above._

* **`Rm` (Membrane Resistance):** The resistance across the cell membrane. _Resistance is inversely correlated with membrane conductivity (influenced primarily by the number of open channels in the membrane). Membrane resistance is sometimes termed "input resistance" because in combination with cell capacitance it determines the time constant of the voltage response to input currents._

* **`Cm` (Membrane Capacitance):** The capacitance of a neuron describes how much charge is required to change its voltage. _Larger cells with more membrane surface area have greater capacitance and require more charge (current times time) to swing their voltage._

* **`Tau` (Membrane Time Constant, τ<sub>cell</sub>):** The membrane time constant describes how fast the cell changes voltage in response to currents across its membrane. This is distinctly different than the voltage clamp time constant which describes how fast the cell changes voltage in response to currents delivered through the patch pipette (dependent on Ra, not Rm). _This metric is best thought of with respect to synaptic currents (not currents delivered through the patch pipette). This is a true biological property of the cell, as it exists even when a pipette is not present to measure it. Membrane time constant is membrane capacitance times membrane resistance. If two cells have the same resistance, the larger one (with greater capacitance) will have a slower membrane time constant._

### Pipette

* **`Ra` (Access Resistance):** The resistance caused by the small open tip of the patch pipette. _If a pipette tip gets clogged this resistance will increase, leading to a failed experiment. Access resistance is the primary contributor to series resistance, but a lesser contributor to input resistance._

* **`Rp` (Pipette Resistance):** Resistance between the amplifier and the tip of the pipette. _Resistance of the solution inside the electrode forms a large component of this resistance, but it is such a low resistance is can often be ignored. Its most important consideration is how it combines with Cp to form a low-pass filter inside the pipette (partially overcome by series resistance compensation) to disproportionately degrade fast voltage-clamp transitions._

* **`Rs` (Seal Resistance):** The resistance formed by the seal between the cell surface and the glass pipette. _Ideal experiments will have high seal resistances in the GΩ range._

* **`Rseries` (Series Resistance):** Sum of all non-biological resistances. Access resistance is the largest contributor to series resistance, but pipette resistance and reference electrode resistance also influences it. _Series resistance is bad for two reasons: it acts as a low-pass filter inside the pipette (reducing magnitude of small transients), and it also acts as a voltage divider in series with membrane resistance (resulting in steady-state voltage error). How impactful each of these are to your experiment is easy to calculate or simulate, and a good experiment will have a membrane / series resistance ratio greater than 10._

* **`TauClamp` (Voltage Clamp Time Constant, τ<sub>clamp</sub>):** The voltage clamp time constant describes how fast the cell changes voltage in response to currents delivered through the patch pipette. _This metric is largely determined by access resistance, and it is typically much smaller than the membrane time constant. It describes the relationship between Ra and Cm, and it does not involve Rm. I consider this measurement purely artificial (not biological) because when a pipette is not in a cell this time constant does not exist._

### Amplifier

* **`Vc` (Command Voltage):** This is the voltage the experimenter tries to move the cell toward. _This isn't always exactly what the cell gets though. First, `Cp` and `Rp` form a small low-pass filter delaying measurement of `Vm`. Similarly, `Ra` and `Cm` form a low-pass filter that delays the clamp system from being able to rapidly swing the voltage of the cell. Finally, `Ra` and `Rm` combine to form a voltage divider, leading the amplifier to believe the cell's voltage is slightly closer to `Vc` than it actually is. Many of these issues can be reduced by capacitance compensation and series resistance compensation._

* **`Vo` (Amplifier Output Voltage):** This voltage exiting the amplifier. It is proportional to the current entering the pipette (passing through Rf according to Ohm's law). _Divide this value by `Rf` to determine the current emitted from the amplifier._

* **`Rf` (Feedback Resistance):** Negative feedback for the amplifier. _The greater the resistance the smaller the noise but the smaller the range of the output. Large resistances >1GΩ are used for single channel recordings and lower resistances <1GΩ are used for whole-cell experiments._

* **`Cf` (Feedback Capacitance):** This capacitor forms an RC low-pass filter with `Rf` to prevent ringing or oscillation. _This is tangentially related to capacitance compensation which uses variable capacitance to a computer-controlled voltage to reduce the effects of `Cp`. The main point of this capacitor here is to stabilize our simulation when `Cp` is added._

* **`Io` (Clamp Current):** Current entering the pipette. _This isn't measured directly, but instead calculated from the amplifier's output voltage (measured by an analog-to-digital converter) and calculated as `Vo/Rf` according to Ohm's law._

## Modeling a Patch-Clamp Experiment in LTSpice

**[LTSpice](https://en.wikipedia.org/wiki/LTspice) is a free analog circuit simulator by Analog Devices.** I enjoy using this program, but only because I'm used to it. For anyone trying to use it for the first time, I'm sorry. Watch a YouTube tutorial to learn how to get up and running with it. [Models used in this project are on GitHub](https://github.com/swharden/memtest) if you wish to simulate them yourself.

<div class="text-center img-border">

![](voltage-clamp-circuit.png)

</div>

**This circuit simulates a voltage clamp membrane test** (square pulses, ±5mV, 50% duty, 20 Hz) delivered through a patch pipette (with no pipette capacitance), a 1GΩ seal, 15 MΩ access resistance, in whole-cell configuration with a neuron resting at -70 mV with 500 MΩ membrane resistance and 150 pF capacitance. The Bessel filter is hooked-up through a unity gain op-amp so it can be optionally probed without affecting the primary amplifier. It's configured to serve as a low-pass filter with a cut-off frequency of 2 kHz.

## Simulating a Membrane Test

**The simulated membrane test shows a typical voltage-clamp trace (green)** which is interesting to compare to the command voltage (red) and the actual voltage inside the cell (blue). Note that although the hardware low-pass filter is connected, the green trace is the current passing through the feedback resistor (Rf). A benefit of this simulation is that we can probe anywhere, and being able to see how the cell's actual voltage differs from the target voltage is enlightening.

<div class="text-center img-border">

![](voltage-clamp-simulation.png)

</div>

**If your clamp voltage does not have sharp transitions**, manually define rise and fall times as non-zero values in the voltage pulse configuration options. Not doing this was a huge trap I fell into. If the rise time and fall time is left at `0`, LTSpice will invent a time for you which defaults to 10%! This slow rise and fall of the clamp voltage pulses was greatly distorting the peaks of my membrane test, impairing calculation of I0, and throwing off my results. When using the PULSE voltage source set the rise and fall times to `1p` (1 picosecond) for ideally sharp edges.

**If saving simulation data consider defining the maximum time step.** Leaving this blank is typically fine for inspecting the circuit within LTSpice, but if you intend to save .raw simulation files and analyze them later with Python (especially when using interpolation to simulate a regular sample rate) define the time step to be a very small number before running the simulation.

## Low-Pass Filtering

**Let's compare the output of the amplifier before and after low-pass filtering.** You can see that the Bessel filter takes the edge off the sharp transient and changes the shape of the curve for several milliseconds. This is an important consideration for analytical procedures which seek to measure the time constant of the decay slope, but I'll leave that discussion for another article.

<div class="text-center img-border">

![](voltage-clamp-filter.png)

</div>

## Calculate Clamp Current from Amplifier Output Voltage

**Patch-clamp systems use a digital-to-analog converter which measures voltage coming out of the amplifier to infer the current being delivered into the pipette.** In other words, the magic ability LTSpice gives us to probe current passing through any resistor in the circuit isn't a thing in real life. Instead, we have to use Ohm's law to calculate it as the ratio of voltage and feedback resistance. 

**Let's calculate the current** flowing into the pipette at the start of this trace when the amplifier's output voltage is -192 mV and our command potential is -75 mV:

```
V = I * R
I = V / R
I = (Vout - Vcmd) / Rf
I = ((-192e-3 V) - (-75e-3 V)) / 500e6 Ω
I = -234 pA
```

Notice I use math to get the difference of `Vout` and `Vcmd`, but in practice this is done at the circuit level using a differential amplifier instead of a unity gain op-amp like I modeled here for simplicity.



## Amplifier Feedback Capacitance

**Let's further explore this circuit by adding pipette capacitance.** I set `Cp` to 100 pF (I know this is a large value) and observed strong oscillation at clamp voltage transitions. This trace shows voltage probed at the output of the Bessel filter.

<div class="text-center img-border">

![](voltage-clamp-Cp100-Cf0.png)

</div>

**A small amount of feedback capacitance reduced this oscillation**. The capacitor `Cf` placed across `Rf` serves as an RC low-pass filter to tame the amplifier's feedback. Applying too much capacitance slows the amplifier's response unacceptably. It was impressive to see how little feedback capacitance was required to change the shape of the curve. In practice parasitic capacitance likely makes design of patch-clamp amplifier headstages very challenging. Experimenting with different values of `Cp` and `Cf` is an interesting experience. Here setting `Cp` to 1 pF largely solves the oscillation issue, but its low-pass property reduces the peaks of the capacitive transients.

<div class="text-center img-border">

![](voltage-clamp-Cp100-Cf1.png)

</div>

## Two-Electrode Giant Squid Axon Model

**I created another model to simulate a giant squid axon studied with a two-electrode system.** It's not particularly useful other than as a thought exercise. By clamping between two different voltages you can measure the difference in current passing through the stimulation resistor to estimate the neuron's membrane resistance. [This model is on GitHub](https://github.com/swharden/memtest) too if you want to change some of the parameters and see how it affects the trace.

<div class="text-center img-border">

![](two-electrode-circuit.png)
![](two-electrode-simulation.png)

</div>

**Let's calculate the squid axon's membrane resistance** from the simulation data just by eyeballing the trace.

```
ΔV = (-65 mV) - (-75 mV) = 10 mV <-- Δ command voltage
ΔI = (5 µA) - (-5 µA) = 10 µA <-- Δ amplifier current
```

```
V = I * R
ΔV = ΔI * Rm
Rm = ΔV / ΔI
Rm = 10e-3 V / 10e-6 A
Rm = 1kΩ <-- calculated membrane resistance
```

## Load LTSpice Simulation Data with Python

**LTSpice simulation data is saved in .raw files can be read analyzed with Python** allowing you to leverage modern tools like numpy, scipy, and matplotlib to further explore the ins and outs of your circuit. I'll discuss membrane test calculations in a future post. Today let's focus on simply getting these data from LTSpice into Python. [Simulation data and full Python code is on GitHub](https://github.com/swharden/memtest). Here we'll analyze the .raw file generated by the whole-cell circuit model above.

```python
# read data from the LTSpice .raw file
import ltspice
l = ltspice.Ltspice("voltage-clamp-simple.raw")
l.parse()

# obtain data by its identifier and scale it as desired
times = l.getTime() * 1e3 # ms
Vcell = l.getData('V(n003)') * 1e3  # mV
Vcommand = l.getData('V(vcmd)') * 1e3  # mV
Iclamp = l.getData('I(Rf)') * 1e12  # pA
```

<div class="text-center">

![](voltage-clamp-simple-fig1.png)

</div>

```python
# plot scaled simulation data
import matplotlib.pyplot as plt

ax1 = plt.subplot(211)
plt.grid(ls='--', alpha=.5)
plt.plot(times, Iclamp, 'r-')
plt.ylabel("Current (pA)")

plt.subplot(212, sharex=ax1)
plt.grid(ls='--', alpha=.5)
plt.plot(times, Vcell, label="Cell")
plt.plot(times, Vcommand, label="Clamp")
plt.ylabel("Potential (mV)")
plt.xlabel("Time (milliseconds)")
plt.legend()

plt.margins(0, .1)
plt.tight_layout()
plt.show()
```

**LTSpice simulation data points are not evenly spaced** in time and may require interpolation to produce data similar to an actual recording which samples data at a regular rate. This topic will be covered in more detail in a later post.

## Membrane Test Analysis

**Let's create an ideal circuit, simulate a membrane test, then analyze the data to see if we can derive original values for access resistance (Ra), cell capacitance (Cm), and membrane resistance (Rm).** I'll eliminate little tweaks like seal resistance, pipette capacitance, and hardware filtering, and proceed with a simple case voltage clamp mode.

<div class="text-center">

![](voltage-clamp-memtest-circuit.png)

</div>

> **⚠️ WARNING:** LTSpice voltage sources have a non-negligible conductance by default, so if you use a voltage source at the base of Rm without a defined resistance you'll have erroneous steady state current readings. Prevent this by defining series resistance to a near infinite value instead of leaving it blank.

Now let's run the simulation and save the output...

<div class="text-center">

![](voltage-clamp-memtest-simulation.png)

</div>

I created a diagram to make it easier to refer to components of the membrane test:

<div class="text-center">

![](voltage-clamp-square-membrane-test.png)

</div>

**Think conceptually about what's happening here:** When the command voltage abruptly changes, `Vcell` and `Vcommand` are very different, so the voltage-clamp amplifier delivers a large amount of current right after this transition. The peak current (`Ipeak`) occurs at time zero relative to the transition. The current change between the previous steady-state current (`Iprev`) and the peak current (`Ipeak`) is only limited by `Ra` (since `Cm` only comes in to play after time passes). Let's call this maximum current change `Id`. With more time the current charges `Cm`, raising the `Vcell` toward (`Vcommand`) at a rate described by `TauClamp`. As `Vcell` approaches `Vcommand` the amplifier delivers less current. Altogether, amplifier current can be approximated by an exponential decay function:

<div class="text-center">

I<sub>t</sub> = I<sub>d</sub> * exp(-t / τ<sub>clamp</sub>) + I<sub>ss</sub>

</div>

### Analyze the Capacitive Transient

**The speed at which `Vcell` changes in response to current delivered through the pipette** is a property of resistance (`Ra`) and capacitance (`Cm`). By studying this curve, we can calculate both. Let's start by isolating one curve. We start by isolating individual capacitive transients:

<div class="text-center">

![](voltage-clamp-simple-fig5.png)

</div>

**Fit each curve to a single exponential function.** I'll gloss over how to do this because it is different for every programming language and analysis software. See my [Exponential Fit with Python](https://swharden.com/blog/2020-09-24-python-exponential-fit/) for details. Basically you'll fit a curve which has 3 parameters: `m`, `tau`, and `b`. You may wish to change the sign of tau depending on the orientation of the curve you are fitting. If your signal is low-pass filtered you may want to fit a portion of the curve avoiding the fastest (most distorted) portion near the peak. If you want to follow along, [code for this project is on GitHub](https://github.com/swharden/memtest).

<div class="text-center">

I<sub>t</sub> = m \* exp(-t / tau) + b


![](mt1.png)

</div>

These are the values I obtained by fitting the curve above:

```
m = 667.070
tau = 2.250
b = -129.996
```

Meaning the curve could be modeled by the equation:
```
I = 667.070 * exp(-t / 2.250) -129.996
```

From these values we can calculate the rest:

* `tau` is one of the fitted parameters and has the same time units as the input data. Don't confuse this value with the cell's time constant (which describes how current across `Rm` changes `Vm`), but instead this value is the time constant of the voltage clamp system (where current across `Ra` changes `Vm`). Because `Ra` is much smaller than `Rm`, this will be a much faster time constant.

* State current (`Iss`) is `b` from the curve fit

* The state current before the step will be called `Iprev`

* Change in old vs. new steady state current will be `Idss`

* Peak current (`Ipeak`) occurs at time zero (when the exponential term is 1) so this is simply `m + b`

* `Id` is peak transient current (difference between `Ipeak` and `Iprev`). Some papers call this `I0`, but other papers use that abbreviation to refer to `Ipeak`, so I'll avoid using that term entirely.

We now have:

```
Iss: -129.996 pA
Iprev: -150.015 pA
Idss: 20.019 pA
Ipeak: 537.074 pA
Id: 687.089 pA
dV: 10 mV
TauClamp: 2.250 ms
```

### Calculate Ra

At time zero, access resistance is the thing limiting our ability to deliver current (`Id`) to a known `ΔV` (10 mV). Therefore we can calculate `Ra` using Ohm's law:

```
V = I * R
ΔV = ΔI * R
R = ΔV / ΔI
Ra = dV / Id
Ra = 10e-3 V / 687.089e-12 A
Ra = 14.554 MΩ <-- pretty close to our model 15 MΩ
```

For now let's call this `Ra`, but note that this is technically `Ra` mixed with a small leakage conductance due to `Rm`. Since `Ra` is so much smaller than `Rm` this small conductance doesn't affect our measurement much. Accuracy of this value will be improved when we apply leak current correction described later on this page.

### Calculate Rm

Now that we know `Ra`, we can revisit the idea that the difference between this steady state current (`Iss`) and the last one (`Iprev`) is limited by the sum of `Rm` and `Ra`. let's use this to calculate `Rm` using Ohm's law:

```
V = I * R
I = V / R
ΔI = ΔV / R
R * ΔI = ΔV
(Ra + Rm) * ΔI = ΔV
Ra * ΔI + Rm * ΔI = ΔV
Rm * ΔI = ΔV - Ra * ΔI
Rm = (ΔV - Ra * ΔI) / ΔI
Rm = (dV - Ra * Idss) / Idss
Rm = (10e-3 V - (14.554e6 Ω * 20.019e-12 A)) / 20.019e-12 A
Rm = 485 MΩ <-- pretty close to our model 500 MΩ
```

Accuracy of this value will be improved when we apply leak current correction described later on this page.

### Calculate Cm from Ra, Rm, and Tau

When we raise the cell's voltage (`Vm`) by delivering current through the pipette (`Ra`), some current escapes through `Rm`. From the cell's perspective when we charge it though, `Ra` and `Rm` are in parallel.

```
tau = R * C
C = tau / R
Cm = tau / (1/(1/Ra + 1/Rm))
Cm = 2.250e-3 sec / (1/(1/14.554e6 Ω + 1/485e6 Ω))
Cm = 159 pF <-- pretty close to our model 150 pF
```

Accuracy of this value will be improved when we apply leak current correction described later on this page.

### Calculate Cm from the Area Under the Curve

**Cell capacitance can alternatively be estimated by measuring the area under the capacitive transient.** This method is frequently used historically, and it is simpler and faster than the method described above because it does not require curve fitting. Each method has its pros and cons (e.g., sensitivity to access resistance, hardware filtering, or resilience in the presence of noise or spontaneous synaptic currents). Rather than compare and contrast the two methods, I'll simply describe the theory underlying how to perform this measurement.

<div class="text-center">

![](mt2.png)

</div>

**After an abrupt voltage transition, all current delivered above the steady state current level goes toward charging the cell,** so by integrating this current over time we can calculate how much charge (`Q`) was delivered. I'll describe this measurement as area under the curve (AUC). When summing these data points yourself be sure to remember to subtract steady state current and divide by the sample rate. [Code for this example is on GitHub](https://github.com/swharden/memtest).

**Charge is measured in Coulombs.** Area under the curve is `1515.412 pA*ms`, but recall that a _femtocoulomb_ is 1pA times 1ms, so it's more reasonable to describe the AUC as `1515.412 fC`. This is the charge required to raise cell's capacitance (`Cm`) by `dV`. The relationship is described by:

```
Q = C * ΔV
C = Q / ΔV
Cm = AUC / ΔV
Cm = 1515.412e-15 C / 10e-3 V
Cm = 1515.412e-15 C / 10e-3 V
Cm = 151.541 pF <-- pretty close to our model 150 pF
```

**This value is pretty close to what we expect**, and I think its accuracy in this case is largely due to the fact that we simulated an ideal unfiltered voltage clamp trace with no noise. Its under-estimation is probably due to the fact that a longer period wasn't used for the integration (which may have been useful for this noise-free simulation, but would not be useful in real-world data). Additional simulation experiments with different combinations of noise and hardware filtering would be an interesting way to determine which methods are most affected by which conditions. Either way, this quick and dirty estimation of whole-cell capacitance did the trick in our model cell.

## Correcting for Leak Current

**Why weren't our measurements exact?** `Rm` leaks a small amount of the `Id` current that passes through `Ra` to charge `Cm`. If you calculate the parallel combined resistance of `Ra` and `Rm` you get `14.56 MΩ` which is pretty much exactly what we measured in our first step and simply called `Ra` at the time. Now that we know the value of both resistances we can calculate a correction factor as the ratio of `Ra` to `Rm` and multiply it by both of our resistances. `Cm` can be corrected by dividing it by the square of this ratio.

```
correction = 1 + Ra / Rm
correction = 1 + 14.554 MΩ / 484.96 MΩ
correction = 1.03

Ra = Ra * correction
Rm = Rm * correction
Cm = Cm / (correction^2)
```

<div class="text-center">

Metric | Model | Measured | Corrected | Error
---|---|---|---|--
Ra|15 MΩ|14.55 MΩ|14.99 MΩ|<1%
Rm|500 MΩ|484.96 MΩ|499.51 MΩ|<1%
Cm (fit)|150 pF|159.20 pF|150.06 pF|<1%

</div>

**This correction is simple** and works well when `Ra/Rm` is small. It's worth noting that an alternative to this correction is to solve for `Ra` and `Rm` simultaneously. The [Membrane Test Algorithms](https://mdc.custhelp.com/app/answers/detail/a_id/17006/~/membrane-test-algorithms) used by pCLAMP calculate `Ra` this way, solving the following equation iteratively using the Newton-Raphson method:

```
Ra^2 - Ra * Rt + Rt * (Tau/Cm) = 0
```

**Overall the values I calculated are within a few percent of expectations,** and I'm satisfied with the calculation strategy summarized here. I am also impressed with what we were able to achieve by modeling a voltage-clamped neuron using a free circuit simulator!

## Use a Voltage-Clamp Ramp to Measure Cm

**It's possible to simulate a voltage-clamp _ramp_ and analyze that trace to accurately measure cell capacitance.** A strong advantage of this method is that it does not depend on `Ra`. Let's start by simulating a 10 mV ramp over 100 ms (50 ms down, 50 ms up). When we simulate this with LTSpice and plot it with Python ([screenshots, data, and code is on GitHub](https://github.com/swharden/memtest)) we find that cell voltage lags slightly behind the clamp voltage.

<div class="text-center">

![](mtramp1.png)

</div>

**During voltage-clamp ramps `Vm` lags behind the command voltage because charging `Cm` is limited by `Ra`.** If we measure the difference in this lag between descending and ascending ramps, we can estimate `Cm` in a way that is insensitive to `Ra`. Stated another way, `Ra` only affects abrupt changes in charging rate. Once the cell is charging at a steady rate, that rate of charge is largely unaffected by `Ra` because the stable charging current is already increased to counteract the previous effect `Ra`. Stated visually, `Ra` only affects the rate of charging at the corners of the V. Therefore, let's proceed ignoring the corners of the V and focus on the middle of each slope where the charging rate is stable (and effect of `Ra` is negligible).

<div class="text-center">

![](mtramp2.png)

</div>

**Analysis is achieved by comparing the falling current to the rising current.** We start separately isolating the falling and rising traces, then reverse one of them and plot the two on top of each other. The left and right edges of this plot represent edges of ramps where the system is still stabilizing to compensate for `Ra`, so let's ignore that part and focus on the middle where the charging rate is stable. We can measure the current lag as half of the mean difference of the two traces. Together with the rate of charge (the rate of the command voltage change) we have everything we need to calculate `Cm`.

<div class="text-center">

![](mtramp3.png)

</div>

```
dI = dQ / dt
dI = Cm * dV / dt
Cm = dI / (dV / dT)
Cm = (59.997e-12 A / 2) / (10e-3 V / 50e-3 sec) <-- 10 mV over 50 ms
Cm = 149.993 pF <-- Our model is 150 pF
```

**This is a fantastic result!** The error we do get is probably the result of a single point of interpolation error while converting the unevenly spaced simulation data to an evenly-spaced array simulating a 20 kHz signal. In this ideal simulation this method of calculating `Cm` appears perfect, but in practice it is highly sensitive to sporadic noise that is not normally distributed (like synaptic currents). If used in the real world each ramp should be repeated many times, and only the quietest sweeps (with the lowest variance in the difference between rising and falling currents) should be used for analysis. However, this is not too inconvenient because this protocol is so fast (10 repetitions per second).

## Summary

**This page described how to model voltage-clamp membrane test sweeps and analyze them to calculate Ra, Cm, and Rm.** We validated our calculations were accurate by matching our calculated values to the ones used to define the simulation. We also explored measuring the area under the curve and using voltage-clamp ramps as alternative methods for determining `Cm`. There are a lot of experiments that could be done to characterize the relationship of noise, hardware filtering, and cell properties on the accuracy of these calculations. For now though, I'm satisfied with what we were able to achieve with free circuit simulation software and basic analysis with Python. [Code for this project is on GitHub](https://github.com/swharden/memtest).

<div class="text-center">

Metric | Model | Calculated | Error
---|---|---|---
Ra|15 MΩ|14.99 MΩ|<1%
Rm|500 MΩ|499.51 MΩ|<1%
Cm (fit)|150 pF|150.06 pF|<1%
Cm (auc)|150 pF|151.541 pF|~1%
Cm (ramp)|150 pF|149.993 pF|<.01%

</div>

## Resources

* [LTSpice models, simulations, and Python code on GitHub](https://github.com/swharden/memtest)

* [Download LTspice](https://www.analog.com/en/design-center/design-tools-and-calculators/ltspice-simulator.html) - a high performance SPICE simulation software, schematic capture, and waveform viewer with enhancements and models for easing the simulation of analog circuits.

* The [LC Filter Design Tool](https://rf-tools.com/lc-filter/) makes it easy to design filter circuits using common component values

* [The Patch-clamp Technique Explained And Exercised With The Use Of Simple Electrical Equivalent Circuits](https://mdc.custhelp.com/euf/assets/images/KB864_ModelCells.pdf) by Dirk L Ypey and Louis J. DeFelice
 
* [Series Resistance Compensation](Drexel_Gao_Lab_Series_Resistance_Compensation.pdf) by [Wen-Jun Gao](https://drexel.edu/medicine/about/departments/neurobiology-anatomy/research/gao-lab/)

* [How to correct for series resistance (and whole cell capacitance) in real cells](http://www.billconnelly.net/?p=616) by [Bill Connelly](https://www.utas.edu.au/profiles/staff/health/bill-connelly)

* [Series Resistance. Why it’s bad.](http://www.billconnelly.net/?p=310) by [Bill Connelly](https://www.utas.edu.au/profiles/staff/health/bill-connelly)

* [MultiClamp 700B Theory and Operation](https://mdc.custhelp.com/euf/assets/content/MultiClamp_700B_Manual2.pdf)

* [Introduction to Operational Amplifiers with LTSpice](https://learn.sparkfun.com/tutorials/introduction-to-operational-amplifiers-with-ltspice/all)

* [pyABF](https://swharden.com/pyabf/) - A simple Python interface for ABF files

* [What we talk about when we talk about capacitance measured with the voltage-clamp step method](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3273682/pdf/10827_2011_Article_346.pdf) by Adam L. Taylor (2012)

* [Membrane Capacitance Measurements Revisited: Dependence of Capacitance Value on Measurement Method in Nonisopotential Neurons](https://journals.physiology.org/doi/pdf/10.1152/jn.00160.2009) (Golowasch et al., 2009) discusses 3 ways to calculate capacitance: a current-clamp step, a voltage-clamp step, and a V-shaped pair or voltage-clamp ramps.

* [Techniques for Membrane Capacitance Measurements](https://link.springer.com/chapter/10.1007/978-1-4419-1229-9_7) (Single-Channel Recording, chapter 7) describes the membrane test using a simplified circuit similar to what is discussed here. This is the closest text to a step-by-step guide I've found analyzing the traditional voltage-clamp step protocol.

* [Letter to the editor: Accurate cell capacitance determination from a single voltage step: a reminder to avoid unnecessary pitfalls](https://journals.physiology.org/doi/pdf/10.1152/ajpheart.00503.2016) (Platzer and Zorn-Pauly) discusses the advantages of properly fitting the voltage-clamp curve and calculating I0 instead of just measuring the peak or taking the area under the curve to be the charge.

* [Membrane Test Guide](https://mdc.custhelp.com/app/answers/detail/a_id/17005/~/membrane-test-guide) by Axon / Molecular Devices

* [Membrane Test Algorithms](https://mdc.custhelp.com/app/answers/detail/a_id/17006/~/membrane-test-algorithms) by Axon / Molecular Devices

* [Electronics for electrophysiologists](https://www.biologie.ens.fr/perso/barbour/electronics_for_electrophysiologists.pdf) by Boris Barbour
September 27th, 2020

ECG Simulator Circuit

This page describes a simple circuit which produces ECG-like waveform. The waveform is not very detailed, but it contains a sharp depolarizing (rising) component, a slower hyperpolarizing (falling) component, and a repetition rate of approximately one beat per second making it potentially useful for testing heartbeat detection circuitry.

In 2019 I released a YouTube video and blog post showing how to build an ECG machine using an AD8232 interfaced to a computer's sound card. At the end of the video I discussed how to use a 555 timer to create a waveform roughly like an ECG signal, but I didn't post the circuit at the end of that video. I get questions about it from time to time, so I'll share my best guess at what that circuit was here using LTSpice to simulate it.

Design Notes

  • The 555 timer generates pulses about once per second.

  • The diode (D1) causes the 555 to produce very short pulses. The duty of the pulses is controlled by the resistance in series with the diode (R3), with higher resistances resulting in larger duty.

  • The main purpose of the first op-amp is to invert polarity of the signal emitted by the 555. The signal is a square wave at about 1Hz, but it is mostly high with brief low pulses.

  • The second op-amp serves as a voltage buffer to stabilize the output, and the final series capacitor shifts the voltage so it's centered around zero.

  • Unity gain op-amps should have some feedback resistance to improve small-signal stability in production applications, but for messing around here I felt fine omitting them.

Resources

Markdown source code last modified on January 18th, 2021
---
title: ECG Simulator Circuit
date: 2020-09-27 17:11:00
tags: circuit, diyECG
---

# ECG Simulator Circuit

**This page describes a simple circuit which produces ECG-like waveform.** The waveform is not very detailed, but it contains a sharp depolarizing (rising) component, a slower hyperpolarizing (falling) component, and a repetition rate of approximately one beat per second making it potentially useful for testing heartbeat detection circuitry.

**In 2019 I released a [YouTube video](https://www.youtube.com/watch?v=sP_-f5nsOEo) and [blog post](https://swharden.com/blog/2019-03-15-sound-card-ecg-with-ad8232/) showing how to build an ECG machine** using an AD8232 interfaced to a computer's sound card. At the end of the video I discussed how to use a 555 timer to create a waveform roughly like an ECG signal, but I didn't post the circuit at the end of that video. I get questions about it from time to time, so I'll share my best guess at what that circuit was here using LTSpice to simulate it.

<div class="text-center img-border">

![](ltspice-ecg-simulator.png)

</div>

## Design Notes

* The 555 timer generates pulses about once per second.

* The diode (D1) causes the 555 to produce very short pulses. The duty of the pulses is controlled by the resistance in series with the diode (R3), with higher resistances resulting in larger duty.

* The main purpose of the first op-amp is to invert polarity of the signal emitted by the 555. The signal is a square wave at about 1Hz, but it is mostly high with brief low pulses.

* The second op-amp serves as a voltage buffer to stabilize the output, and the final series capacitor shifts the voltage so it's centered around zero.

* Unity gain op-amps should have some feedback resistance to improve small-signal stability in production applications, but for messing around here I felt fine omitting them.

## Resources

* LTSpice file for this project: [ecg.asc](ecg.asc)

* You will need the LM741 model found on the [Using MOD Files in LTSpice](https://swharden.com/blog/2020-09-26-ltspice-mod-files/) page

* My [Action Potential Generator Circuit](https://swharden.com/blog/2017-08-12-analog-action-potential-generator-circuit/) and [Microcontroller Action Potential Generator](https://swharden.com/blog/2017-08-20-microcontroller-action-potential-generator/) articles describe method to produce a similar waveform (designed to look more like what firing neurons produce) using transistors to charge/discharge a capacitor rather than op-amps.
Pages