Multichannel USB Analog Sensor with ATMega48

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 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){
    ADCSRA = 0b10000111; //ADC Enable, Manual Trigger, Prescaler
    ADCSRB = 0;

    int adcs[8]={0,0,0,0,0,0,0,0};

    char i=0;
		for (i=0;i<8;i++){readADC(i);adcs[i]=ADC>>6;}
		for (i=0;i<5;i++){sendNum(adcs[i]);send(44);}
		send(10);// LINE BREAK
		send(13); //return

void sendNum(unsigned int num){
	char theIntAsString[7];
	int i;
	sprintf(theIntAsString, "%u", num);
	for (i=0; i < strlen(theIntAsString); i++){

void send (unsigned char c){
	while((UCSR0A & (1<<UDRE0)) == 0) {}
	UDR0 = c;

void init_usart () {
	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.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.xpos]=val
		if self.xpos%10==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))
		if self.xpos==800:
			print "FPS:",self.totalPoints/(time.time()-self.timeStart)

	def __init__(self, t):
		self.c = Tkinter.Canvas(t, width=800, height=512)

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"
		#print raw
		#print raw
			print "FAIL"

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!


21 thoughts on “Multichannel USB Analog Sensor with ATMega48

  1. 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.


  2. 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? 🙂


  3. 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.

  4. 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

  5. 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.

  6. 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.

  7. 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?

  8. 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.

  9. 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

  10. 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.

      • 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

        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.

  11. Hi Scott,

    This is a great Python code to monitor my arduino uno attach with a geophone~
    And it works awesome !!!!
    Thanks a lot~~

    I am trying to make a wireless geophone, it just getting started