Analog inputs with Arduino and EMC

I recently got an arduino board. The AVR microcontroller on it has 6 10-bit ADCs. The version I got has a USB connection, so I decided to write an analog input firmware and associated userspace HAL driver in python. It requires pyserial, available on ubuntu with 'sudo apt-get install python-serial.

The "packet" structure is very simple: each ADC reading consists of two bytes with the following structure:

Byte # Interpretation
0
1PPPxVVV
1
0VVVVVVV
where "P" indicates a bit of the channel number and "V" indicates a bit of the ADC sample. The "x" bit is unused. Since only the first byte of the packet begins with a "1" bit, the protocol is self-synchronizing.

Communication takes place at 9600 baud, and it takes 120 bits (including start and stop bits) to update all analog inputs. This makes the maximum update rate 80Hz. However, some additional time is required for the analogRead() function to execute, and there are no timing guarantees for the HAL userspace component running on the PC.

The program consists of two parts. First, the part that runs on the AVR, "adc6.pde" (see Listing 1). Second, the part that runs on the PC, "arduino-adc6.py" (see Listing 2).

I don't have any analog sources handy, but instead I hooked the several supply voltages available up to the first three analog inputs. Here's a sample session:

$ halrun
halcmd: loadusr -Wn arduino-adc6 python ./arduino-adc6.py
halcmd: show pin
Component Pins:
Owner   Type  Dir         Value  Name
 28114  float OUT             0  arduino-adc6.analog-in-0
 28114  float OUT             5  arduino-adc6.analog-in-1
 28114  float OUT      3.377322  arduino-adc6.analog-in-2
 28114  float OUT      3.333333  arduino-adc6.analog-in-3
 28114  float OUT       3.29912  arduino-adc6.analog-in-4
 28114  float OUT      3.294233  arduino-adc6.analog-in-5
The nominal voltages are 0V, 5V, and 3.3V. It looks like the AVR's "sample and hold" circuitry tends to read near the previous value when the input is actually floating.

There are some obvious next steps: First, there are 6 bidirectional digital I/O pins available. It would be nice to read these. Second, it would be nice to use some of them as outputs, and also use the 6 8-bit PWM generators that are available.

Besides the caveat that this driver is not realtime, it is also important to note that neither USB nor RS232 serial provide isolation, and that only voltages in the range 0..5V may be applied to the AVR's ADC pins.

Listing 1: adc6.pde

void setup() {
  Serial.begin(9600);
}

void loop() {
  for(int i=0; i<6; i++) {
    uint16_t v = analogRead(i) | (i << 10);
    Serial.print((v >> 7) | 0x80, BYTE);
    Serial.print(v & 0x7f, BYTE);
  }
}

Listing 2: arduino-adc6.py

#!/usr/bin/python

import serial
import hal
import sys

PORT = "/dev/ttyUSB0"

if len(sys.argv) > 1:
    PORT = sys.argv[1]

ser = serial.Serial(PORT, 9600, timeout=2)

c = hal.component("arduino-adc6")
for port in range(6):
    c.newpin("analog-in-%d" % port, hal.HAL_FLOAT, hal.HAL_OUT)
    c.newparam("analog-in-%d-offset" % port, hal.HAL_FLOAT, hal.HAL_RW)
    c.newparam("analog-in-%d-gain" % port, hal.HAL_FLOAT, hal.HAL_RW)
    c['analog-in-%d-gain' % port] = 1.0
c.ready()

while 1:
    b1 = ord(ser.read())
    if b1 & 0x80 != 0x80: continue  # First bit of packet has high bit set
    b2 = ord(ser.read())
    if b2 & 0x80 != 0: continue     # Second bit of packet has high bit clear
    v = (b1 << 7) | b2
    port = (v >> 10) & 7
    gain = c['analog-in-%d-gain' % port]
    offset = c['analog-in-%d-offset' % port]
    value = (v & 1023) / 1023. * 5.0 * gain + offset
    c['analog-in-%d' % port] = value


(originally posted on the AXIS blog)

Entry first conceived on 24 December 2007, 19:46 UTC, last modified on 15 January 2012, 3:46 UTC
Website Copyright © 2004-2024 Jeff Epler