Sometimes it’s tempting to re-invent the wheel to make a device function exactly the way you want. I am re-visiting the field of homemade electrophysiology equipment, and although I’ve already published a home made electocardiograph (ECG), I wish to revisit that project and make it much more elegant, while also planning for a pulse oximeter, an electroencephalograph (EEG), and an electrogastrogram (EGG). This project is divided into 3 major components: the low-noise microvoltage amplifier, a digital analog to digital converter with PC connectivity, and software to display and analyze the traces. My first challenge is to create that middle step, a device to read voltage (from 0-5V) and send this data to a computer.
This project demonstrates a simple solution for the frustrating problem of sending data from a microcontroller to a PC with a USB connection. My solution utilizes a USB FTDI serial-to-usb cable, allowing me to simply put header pins on my device which I can plug into providing the microcontroller-computer link. This avoids the need for soldering surface-mount FTDI chips (which gets expensive if you put one in every project). FTDI cables are inexpensive (about $11 shipped on eBay) and I’ve gotten a lot of mileage out of mine and know I will continue to use it for future projects. If you are interested in MCU/PC communication, consider one of these cables as a rapid development prototyping tool. I’m certainly enjoying mine!
It is important to me that my design is minimalistic, inexpensive, and functions natively on Linux and Windows without installing special driver-related software, and can be visualized in real-time using native Python libraries, such that the same code can be executed identically on all operating systems with minimal computer-side configuration. I’d say I succeeded in this effort, and while the project could use some small touches to polish it up, it’s already solid and proven in its usefulness and functionality.

This is my final device. It’s reading voltage on a single pin, sending this data to a computer through a USB connection, and custom software (written entirely in Python, designed to be a cross-platform solution) displays the signal in real time. Although it’s capable of recording and displaying 5 channels at the same time, it’s demonstrated displaying only one. Let’s check-out a video of it in action:
This 5-channel realtime USB analog sensor, coupled with custom cross-platform open-source software, will serve as the foundation for a slew of electrophysiological experiments, but can also be easily expanded to serve as an inexpensive multichannel digital oscilloscope. While more advanced solutions exist, this has the advantage of being minimally complex (consisting of a single microchip), inexpensive, and easy to build.

To the right is my working environment during the development of this project. You can see electronics, my computer, microchips, and coffee, but an intriguingly odd array of immunological posters in the background. I spent a couple weeks camping-out in a molecular biology laboratory here at UF and got a lot of work done, part of which involved diving into electronics again. At the time this photo was taken, I hadn’t worked much at my home workstation. It’s a cool picture, so I’m holding onto it.
Below is a simplified description of the circuit schematic that is employed in this project. Note that there are 6 ADC (analog to digital converter) inputs on the ATMega48 IC, but for whatever reason I ended-up only hard-coding 5 into the software. Eventually I’ll go back and re-declare this project a 6-channel sensor, but since I don’t have six things to measure at the moment I’m fine keeping it the way it is. RST, SCK, MISO, and MOSI are used to program the microcontroller and do not need to be connected to anything for operation. The max232 was initially used as a level converter to allow the micro-controller to communicate with a PC via the serial port. However, shortly after this project was devised an upgrade was used to allow it to connect via USB. Continue reading for details…

Below you can see the circuit breadboarded. The potentiometer (small blue box) simulated an analog input signal.

The lower board is my AVR programmer, and is connected to RST, SCK, MISO, MOSI, and GND to allow me to write code on my laptop and program the board. It’s a Fun4DIY.com AVR programmer which can be yours for $11 shipped! I’m not affiliated with their company, but I love that little board. It’s a clone of the AVR ISP MK-II.

As you can see, the USB AVR programmer I’m using is supported in Linux. I did all of my development in Ubuntu Linux, writing AVR-GCC (C) code in my favorite Linux code editor Geany, then loaded the code onto the chip with AVRDude.
I found a simple way to add USB functionality in a standard, reproducible way that works without requiring the soldering of a SMT FTDI chip, and avoids custom libraries like V-USB which don’t easily have drivers that are supported by major operating systems (Windows) without special software. I understand that the simplest long-term and commercially-logical solution would be to use that SMT chip, but I didn’t feel like dealing with it. Instead, I added header pins which allow me to snap-on a pre-made FTDI USB cable. They’re a bit expensive ($12 on ebay) but all I need is 1 and I can use it in all my projects since it’s a sinch to connect and disconnect. Beside, it supplies power to the target board! It’s supported in Linux and in Windows with established drivers that are shipped with the operating system. It’s a bit of a shortcut, but I like this solution. It also eliminates the need for the max232 chip, since it can sense the voltages outputted by the microcontroller directly.
The system works by individually reading the 10-bit ADC pins on the microcontroller (providing values from 0-1024 to represent voltage from 0-5V or 0-1.1V depending on how the code is written), converting these values to text, and sending them as a string via the serial protocol. The FTDI cable reads these values and transmits them to the PC through a USB connection, which looks like “COM5″ on my Windows computer. Values can be seen in any serial terminal program (i.e., hyperterminal), or accessed through Python with the PySerial module.
As you can see, I’m getting quite good at home-brewn PCBs. While it would be fantastic to design a board and have it made professionally, this is expensive and takes some time. In my case, I only have a few hours here or there to work on projects. If I have time to design a board, I want it made immediately! I can make this start to finish in about an hour. I use a classic toner transfer method with ferric chloride, and a dremel drill press to create the holes. I haven’t attacked single-layer SMT designs yet, but I can see its convenience, and look forward to giving it a shot before too long.

Here’s the final board ready for digitally reporting analog voltages. You can see 3 small headers on the far left and 2 at the top of the chip. These are for RST, SCK, MISO, MOSI, and GND for programming the chip. Once it’s programmed, it doesn’t need to be programmed again. Although I wrote the code for an ATMega48, it works fine on a pin-compatible ATMega8 which is pictured here. The connector at the top is that FTDI USB cable, and it supplies power and USB serial connectivity to the board.

If you look closely, you can see that modified code has been loaded on this board with a Linux laptop. This thing is an exciting little board, because it has so many possibilities. It could read voltages of a single channel in extremely high speed and send that data continuously, or it could read from many channels and send it at any rate, or even cooler would be to add some bidirectional serial communication capabilities to allow the computer to tell the microcontroller which channels to read and how often to report the values back. There is a lot of potential for this little design, and I’m glad I have it working.

Unfortunately I lost the schematics to this device because I formatted the computer that had the Eagle files on it. It should be simple and intuitive enough to be able to design again. The code for the microcontroller and code for the real-time visualization software will be posted below shortly. Below are some videos of this board in use in one form or another:
Here is the code that is loaded onto the microcontroller:
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
void readADC(char adcn){
//ADMUX = 0b0100000+adcn; // AVCC ref on ADCn
ADMUX = 0b1100000+adcn; // AVCC ref on ADCn
ADCSRA |= (1<<ADSC); // reset value
while (ADCSRA & (1<<ADSC)) {}; // wait for measurement
}
int main (void){
DDRD=255;
init_usart();
ADCSRA = 0b10000111; //ADC Enable, Manual Trigger, Prescaler
ADCSRB = 0;
int adcs[8]={0,0,0,0,0,0,0,0};
char i=0;
for(;;){
for (i=0;i<8;i++){readADC(i);adcs[i]=ADC>>6;}
for (i=0;i<5;i++){sendNum(adcs[i]);send(44);}
readADC(0);
send(10);// LINE BREAK
send(13); //return
_delay_ms(3);_delay_ms(5);
}
}
void sendNum(unsigned int num){
char theIntAsString[7];
int i;
sprintf(theIntAsString, "%u", num);
for (i=0; i < strlen(theIntAsString); i++){
send(theIntAsString[i]);
}
}
void send (unsigned char c){
while((UCSR0A & (1<<UDRE0)) == 0) {}
UDR0 = c;
}
void init_usart () {
// ATMEGA48 SETTINGS
int BAUD_PRESCALE = 12;
UBRR0L = BAUD_PRESCALE; // Load lower 8-bits
UBRR0H = (BAUD_PRESCALE >> 8); // Load upper 8-bits
UCSR0A = 0;
UCSR0B = (1<<RXEN0)|(1<<TXEN0); //rx and tx
UCSR0C = (1<<UCSZ01) | (1<<UCSZ00); //We want 8 data bits
}
Here is the code that runs on the computer, allowing reading and real-time graphing of the serial data. It’s written in Python and has been tested in both Linux and Windows. It requires *NO* non-standard python libraries, making it very easy to distribute. Graphs are drawn (somewhat inefficiently) using lines in TK. Subsequent development went into improving the visualization, and drastic improvements have been made since this code was written, and updated code will be shared shortly. This is functional, so it’s worth sharing.
import Tkinter, random, time
import socket, sys, serial
class App:
def white(self):
self.lines=[]
self.lastpos=0
self.c.create_rectangle(0, 0, 800, 512, fill="black")
for y in range(0,512,50):
self.c.create_line(0, y, 800, y, fill="#333333",dash=(4, 4))
self.c.create_text(5, y-10, fill="#999999", text=str(y*2), anchor="w")
for x in range(100,800,100):
self.c.create_line(x, 0, x, 512, fill="#333333",dash=(4, 4))
self.c.create_text(x+3, 500-10, fill="#999999", text=str(x/100)+"s", anchor="w")
self.lineRedraw=self.c.create_line(0, 800, 0, 0, fill="red")
self.lines1text=self.c.create_text(800-3, 10, fill="#00FF00", text=str("TEST"), anchor="e")
for x in range(800):
self.lines.append(self.c.create_line(x, 0, x, 0, fill="#00FF00"))
def addPoint(self,val):
self.data[self.xpos]=val
self.line1avg+=val
if self.xpos%10==0:
self.c.itemconfig(self.lines1text,text=str(self.line1avg/10.0))
self.line1avg=0
if self.xpos>0:self.c.coords(self.lines[self.xpos],(self.xpos-1,self.lastpos,self.xpos,val))
if self.xpos<800:self.c.coords(self.lineRedraw,(self.xpos+1,0,self.xpos+1,800))
self.lastpos=val
self.xpos+=1
if self.xpos==800:
self.xpos=0
self.totalPoints+=800
print "FPS:",self.totalPoints/(time.time()-self.timeStart)
t.update()
def __init__(self, t):
self.xpos=0
self.line1avg=0
self.data=[0]*800
self.c = Tkinter.Canvas(t, width=800, height=512)
self.c.pack()
self.totalPoints=0
self.white()
self.timeStart=time.time()
t = Tkinter.Tk()
a = App(t)
#ser = serial.Serial('COM1', 19200, timeout=1)
ser = serial.Serial('/dev/ttyUSB0', 38400, timeout=1)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
while True:
while True: #try to get a reading
#print "LISTENING"
raw=str(ser.readline())
#print raw
raw=raw.replace("\n","").replace("\r","")
raw=raw.split(",")
#print raw
try:
point=(int(raw[0])-200)*2
break
except:
print "FAIL"
pass
point=point/2
a.addPoint(point)
If you re-create this device of a portion of it, let me know! I’d love to share it on my website. Good luck!






20 comments
Ed Sammy
June 18, 2012 at 10:57 AM (UTC -5) Link to this comment
This looks like a great platform for sensor experimentation. And the Python realtime display program is beautifully done. Nice work and thanks for posting.
I was wondering if you were planning on publishing the schematics for the EKG device you demonstrated in the DIY ecg 2 – prototype 1 video that was based on the AD620? I am working on a very similar prototype that would use the AD620 as well and am just looking for some guidance. Thanks in advance.
-Ed
Roberto
June 19, 2012 at 9:09 AM (UTC -5) Link to this comment
Hi, Scott!
Great project, I’d love to recreate this. Do you think you could post the full schematics sometime? Also, I noticed that the microcontroller code is incomplete, could you post the full code? :)
Thanks!
–Rob
Jason Lopez
June 19, 2012 at 11:00 AM (UTC -5) Link to this comment
This looks awesome! Cant wait for you to share the updated code :)
Ill try playing with this for now :)
John
June 20, 2012 at 10:59 AM (UTC -5) Link to this comment
Hi!
Seems so cool! but… there’s a problem in getting the whole code… it shows up uncomplete…
Scott W Harden
June 23, 2012 at 1:27 PM (UTC -5) Link to this comment
Thanks for noticing that! I added the rest of the code. I’m not sure how it got clipped off :)
Bryan
June 20, 2012 at 11:37 AM (UTC -5) Link to this comment
Fascinating stuff!
What sample rate is this setup able to achieve?
Mike
June 21, 2012 at 8:20 PM (UTC -5) Link to this comment
Nice. But why not just use a PIC18F2550 (or 2455 or 4550)? It has way more code space, more SRAM, and it does USB so you don’t need the FTDI cable or the MAX232. Doesn’t get any more minimalist than that.
Keep on with the projects.
Ed Sammy
June 27, 2012 at 9:09 PM (UTC -5) Link to this comment
Cannot get the AVR code to compile. Here is the error I’m getting from the make command in OSX 10.7. Any help?
Thanks in advance
avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=atmega8 -c main.c -o main.o
main.c: In function ‘main’:
main.c:13:9: warning: implicit declaration of function ‘init_usart’
main.c:15:5: error: ‘ADCSRB’ undeclared (first use in this function)
main.c:15:5: note: each undeclared identifier is reported only once for each function it appears in
main.c:21:17: warning: array subscript has type ‘char’
main.c:22:17: warning: implicit declaration of function ‘sendNum’
main.c:22:17: warning: array subscript has type ‘char’
main.c:22:17: warning: implicit declaration of function ‘send’
main.c: At top level:
main.c:30:6: warning: conflicting types for ‘sendNum’
main.c:22:35: note: previous implicit declaration of ‘sendNum’ was here
main.c: In function ‘sendNum’:
main.c:33:9: warning: implicit declaration of function ‘sprintf’
main.c:33:9: warning: incompatible implicit declaration of built-in function ‘sprintf’
main.c:34:9: warning: implicit declaration of function ‘strlen’
main.c:34:23: warning: incompatible implicit declaration of built-in function ‘strlen’
main.c: At top level:
main.c:40:6: warning: conflicting types for ‘send’
main.c:22:52: note: previous implicit declaration of ‘send’ was here
main.c: In function ‘send’:
main.c:41:16: error: ‘UCSR0A’ undeclared (first use in this function)
main.c:41:29: error: ‘UDRE0′ undeclared (first use in this function)
main.c:42:9: error: ‘UDR0′ undeclared (first use in this function)
main.c: At top level:
main.c:45:6: warning: conflicting types for ‘init_usart’
main.c:13:9: note: previous implicit declaration of ‘init_usart’ was here
main.c: In function ‘init_usart’:
main.c:48:9: error: ‘UBRR0L’ undeclared (first use in this function)
main.c:49:9: error: ‘UBRR0H’ undeclared (first use in this function)
main.c:50:9: error: ‘UCSR0A’ undeclared (first use in this function)
main.c:51:9: error: ‘UCSR0B’ undeclared (first use in this function)
main.c:51:22: error: ‘RXEN0′ undeclared (first use in this function)
main.c:51:33: error: ‘TXEN0′ undeclared (first use in this function)
main.c:52:9: error: ‘UCSR0C’ undeclared (first use in this function)
main.c:52:22: error: ‘UCSZ01′ undeclared (first use in this function)
main.c:52:36: error: ‘UCSZ00′ undeclared (first use in this function)
make: *** [main.o] Error 1
Anonymous
June 27, 2012 at 9:15 PM (UTC -5) Link to this comment
Sounds like a library problem. Can you blink a led with a 3 line LED_blink.c program? I think its a compiler configuration problem rather than a code problem.
Ed Sammy
June 27, 2012 at 11:04 PM (UTC -5) Link to this comment
Yes I have gotten the blink program from this tutorial running
http://www.ladyada.net/learn/avr/avrdude.html
I am still attempting to base the ECG circuit off of the Arduino board but am having some troubles so I thought Id give the true AVR a try. Also I am using the AD627 which I am assuming is similar enough to the 620 to yield the same results.
Nishchay
July 3, 2012 at 1:27 AM (UTC -5) Link to this comment
Hi Scott,
I found this post very helpful. I recreated the device, but with an ATmega32 on a small AVR development board that I have. I used the serial port connected via a MAX-232 level converter. This I connected to the USB port using a serial-USB adapter. I modified the code on the micro-controller according to the ATmega32. In the Python code I just uncommented the “print ser” and “print raw”, which runs on the PC.
Now the problem — When I run the device, the python code shows the “FAIL” message. The ‘print’ lines revealed that the line being read from the serial port is blank.
So I guess this means that data are not reaching the serial port.
I have checked that the serial-USB adapter is working and also checked that my TXD pin is transmitting data. Therefore, I think the problem is because of a baud-rate mismatch between the device and the PC.
I noticed that in your code you have declared F_CPU as 8 MHz, but on your device there is no external crystal, nor is there any code to set the internal RC oscillator (which must be 1 MHz) to 8 MHz. So my question is, what clock source is your device using and how did you get an accurate baud rate ?
Thanks in advance.
Nishchay
Scott W Harden
July 3, 2012 at 10:15 AM (UTC -5) Link to this comment
The clock source actually _is_ the internal oscillator. The internal oscillator runs at 8MHz. Often, by default, the DIV/8 fuse is enabled, causing the clock to be divided by 8 (1MHz). I imagine if you disable the DIV/8 fuse, your device will work. You can then use an AVR baud rate calculator reference page like http://www.wormfood.net/avrbaudcalc.php to help you with the rest. Good luck!
Digvijay
November 11, 2012 at 1:24 AM (UTC -5) Link to this comment
I am trying to send out (x,y) coordinates of object in image to microcontroller via USB to Serial cable in Kubuntu version.I am using OpenCV C library for programming purpose.How to interface cable to laptop and make changes in my existing C program.
vidya
January 11, 2013 at 1:17 AM (UTC -5) Link to this comment
Great project..
I am working on designing ecg system using arm processor. i need to write assembly c language to read and display ecg in the pc. can u suggest me how to do this?
Karthik C
January 15, 2013 at 2:25 PM (UTC -5) Link to this comment
Thank You very much , that helped me .
I followed your code and successfully implemented on ATMEGA16/32 . I didn’t known about sprintf function and by this post i came to know about that useful function and also a similar function “itoa” .
I dont know python programming hence i am not able to compile it successfully ,although i tried to compile by following some tutorials but always stuck at “import serial” , hence i used an alternate named “live-graph” written in java.
Avi
February 7, 2013 at 11:18 PM (UTC -5) Link to this comment
The IA-2142-U is a Dual 8bit Analog Output module, with up to 8 Digital Inputs and 3 Digital Outputs. The IA-2142-U structure and software control commands are Series-3000 compatible, including the watchdog protection circuit, and user interface extra Led and Jumper.
USB Analog
Wesley R. Elsberry
April 13, 2013 at 12:12 PM (UTC -5) Link to this comment
The post is very interesting. I think the hardware possibilities have widened now, too. I haven’t had the opportunity to do much beyond demo programs yet with one device I’ve gotten, but I think it might be something you would find useful. This is the Teensy++ 2.0 board from PJRC electronics. It is programmed via USB and offers the capability to use USB in user programs in any of several different modes, including HID. The HID operation is especially interesting since that is supported cross-platform without needing device driver installation.
Teensy++ 2.0 website
It can be programmed with C or in an Arduino mode.
I located this device as something to pair with a Raspberry Pi single-board Linux computer. The Teensy board supplies the few IO and AD capabilities that the Raspberry Pi lacks, and communication can be via USB, simplifying hardware interactions. In terms of your project, I would think this pair of devices would handle the complete set of acquisition, processing, and display tasks, and could be made into a small, portable, battery-driven package. I’ve gotten a UBEC to pair with a 2500MAH lead-acid 12V battery to handle power needs for my work.
Mac Ha Nguyen
April 19, 2013 at 5:06 AM (UTC -5) Link to this comment
Scott, looks like the source code is truncated, the Python programs ends at a.addPoint(point), with the two while clause unfinished…
Scott W Harden
April 19, 2013 at 8:21 AM (UTC -5) Link to this comment
No, that’s accurate. One of the while clauses has already exited by the time that addPoint() is called. The other one loops forever.
Steve Coffman
April 26, 2013 at 11:11 PM (UTC -5) Link to this comment
Dear Scott;
Thank you for posting your multichannel usb device. You certainly did your homework, and what a beautiful job.
I am working on somethig quasi-similiar, and need a little advice. My ThunderVolt device will soon have a voltage
output, and I want to display that voltage on a PC. I am looking for a programmer for hire to build a program to
display my data. I wonder if you would like to make some cash doing this for me.
Basically I want to input +5 volts and a ground from a PC or Mac into my device. There the +5 will be dropped across two
resistances, one of which will be a precision 47 K ohm resistor in series with the user’s body resistance, which is usually 50 K to several million ohms. A third wire from the USB will tap off the voltage across the 47 K ohm and send it back to the user’s computer. There, the computer will read this voltage, compute the amperage by using the 47 K ohm resistance, and calculate the power level in watts. The computer will display this first reading of the resistor on the screen, with print capability.
One of my questions is: Does a PC have sufficient onboard capability so that no additional hardware is needed to determine
the voltage from the third usb line? I do not want to have to make an additional hardware attachment onto my device, since
there isn’t room in my case box to do it, and the cost would be prohibitive. I just want to plug my device’s usb female into a usb-male-to-male cable and connect that to a usb port on a user’s computer. Then, the user will install my software, grab the
two handholds and his/her computer will display a number on the screen. The computer will measure the 47 K ohm resistor’s voltage, calculate the amperage across the resistor, and compute the power across the user’s resistance in micro watts.
The computer will use the first initial reading from the 47 K resistor, because that is the most accurate.
I am a poor Maine resident trying to make a go of a cottage industry, but I believe I have found a very interesting method of
quantifying the vitality of a human being in watts. My pre-zap and post-zap tests show a gain in power of about 6%, depending on how healthy or sick the tester is. I previously used a DVM to do the testing, which is really the same thing but it measures in resistance. Since the body is essentially an electrical charge, I wanted a reading in watts, not ohms.
Dr. Albert Szent Gygorgyi, MD Ph.D, and Nobel prize winner wrote a thesis on this subject. It beacame the backstop for my
ThunderVolt device, along with Tesla’s radiant energy, Royal Raymond Rife work, William Reich, and Hulda Clark, who make the first practical zapper. My device is built on these five outstanding people.
I invite your kind reply.
Sincerely yours,
Steve Coffman
antioxc@gmail.com
I know that there are software programs out there that read cpu voltages on a computer, and they don’t need hardware
accessories or other installs to work.