Andrew B. Wright, S. M. ’88, Ph. D.
The analog input system is part of the touchscreen interface. This is not an obvious place to find it. Here is TI’s guide.
As an aside, many sensors are being provided which use the I2C interface or the USB interface. It is becoming less critical to use the analog input interface. But, in embedded applications, having a solid analog interface can get you past obstacles, especially if you cannot find an engineered solution that meets your needs.
The system operates on a separate ground and voltage source, so input conditioning to analog signals is important. Protecting the ADC modules and avoiding saturation are key concerns.
The ADC is a 12 bit successive approximation convertor. The beaglebone user has access to 7 multiplexed channels. The maximum sample rate is 200 ksps (from the 3358 data sheet).
Although the PRU can sample the ADC at a fixed (fast) timing, the ADC can be accessed through the ARM side of the Beaglebone. The ADC can be set up to automatically sample at a fixed rate and put samples into a buffer.
For initial testing, the ARM will be used.
With the universal cape, ADC access is built in.
Connect a 1k resistor in series with a 10k potentiometer or a voltage divider (1k and 5k resistors) between the analog power pin (1.8v, P9-32), the analog ground pin (P9-34), and one of the analog input pins (P9-33 [AIN4], 35, 36, 37, 38, 39 [AIN0], and 40). A circuit diagram can be seen in Figure <insert figure here>.
Varying the potentiometer wiper will vary the code in /sys/bus/iio/devices/iio:device0/in_voltage4_raw. The voltage divider will give a fixed value that is different from the open circuit reading. NOTE: I saw quite a bit of jitter on the readings. All my readings were high relative to my volt meter readings. One-shot mode may be suboptimal (ie, it wasn’t allowing the ADC to settle). Or, something else.
In order to set up a continuous stream of readings (at the default sample rate – 200 kilo samples per second),
echo “1” > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage4_en
echo “10” > /sys/bus/iio/devices/iio:device0/buffer/length
echo “1” > /sys/bus/iio/devices/iio:device0/buffer/enable
This enables a capture of AIN4. I’m not sure what the buffer length affects.
You can access the capture by reading from /dev/iio:device0.
Unfortunately, the device will be sending binary data in a format specified by /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage4_type. So, what appears on the screen will be gibberish.
You can write a code that does this from the ARM side using standard file syntax.
When you use the sysfs interface, a number of design choices have been made for you. If you look at the registers, you will see the following in the stepconfig registers. Registers have been assigned to channels as per the table. All channels are single ended, which means that the INM selection (channel 0 for all steps) is not used. All channels use 16 averages, which (presumably) sets the sample rate at 200 kSPS/16. This has not been verified.
Assigning a stepconfig to a channel is not necessary, but it can be nice. This means that setting the bit in the stepenable register has the same impact as selecting the channel. Therefore, after initial set up, the only register that needs to be modified is the stepenable register.
The sysfs fires an interrupt on FIFO overrun. It is not clear what the result of this interrupt is, but, if you’re going to use the ADC interface from the PRU, you should disable all interrupts. The PRU is not interruptible anyway, so, having an interrupt fire may trigger code on the ARM side that could cause unpredictable results.
If you want to sample at audio rates, you need to set the number of averages to less than 16. Normal audio sample times can go higher than 40 kHz (to achieve a 20 kHz Nyquist frequency). So, fewer than five averages would be desirable for audio sampling.
The ADC interface allows a differential configuration. Consequently, you could have a signal on channel 1 and channel 2 and use the interface to subtract these channels (helps with noise); however, the stepconfig registers would have to be reconfigured.
The ADC allows reference voltages other than analog ground and 1.8Vref. The stepconfig registers allow changes here, but it is probably wise to leave those alone.
The sysfs also chooses FIFO1 for its buffer. This can be set to FIFO0 in the stepconfig registers if that is desired.
ARM Access to ADC The ADCs will be available at /sys/bus/iio/devices/iio:device0 The files, in_voltagex_raw, where x = 0…6, contain the 12 bit binary code for each analog input. Continuous readings from the ARM: echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltagex_en echo num > /sys/bus/iio/devices/iio:device0/buffer/length echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable (this is now handled by the generic_buffer program) There is a utility called iio_generic_buffer which makes this easy. It can be found in the kernel source under tools/iio. It requires the files, iio_utils.c, iio_utils.h, also found in that directory. Download by clicking on the file and selecting “raw”. You can use the browser’s ‘save as’ menu item to download to your local directory. Copy from your host computer to the beagle bone using scp file root@ip address:code/adc. Compile with gcc -o gen generic_buffer.c iio_utils.c. Run with ./gen -n “TI-am335x-adc -g”. The ‘-g’ flag makes this triggerless. In earlier versions of the program, you had to patch out the trigger stuff. This is no longer necessary. NOTE: as of this writing, I get an error when compiling. The error relates to the ‘asprintf’ function. It’s possible that the default version of ADC is better now, so I’m skipping the generic buffer. If all goes well, you should get a stream of floating point numbers, which are actually the 12 bit binary codes read from the ADC4 (if that’s the one you enabled above). NOTE: P9.33 is connected to AIN4 (in_voltage4_raw).
PRU Access to ADC
The PRU side took a bit of work. The TRM’s description of the registers, bits, and what everything does is cryptic. However, by running the ADC driver above and then looking at the registers from the PRU side, a pretty good starting point can be deduced. The ADC driver code (available in the kernel sources) gives a lot more help in getting things going.
The main discovery that I made was that you need to wait a while between setting bits in registers. The PRU is really fast, so you can override your own commands. You can over-sample the ADC, so you need to run the PRU’s sample rate under the ADC’s conversion rate.
I created a directory under root, adc_pru. The archive of the code is here. <- coming soon.
This code samples three channels, including the channel wired above. You can wire the other two channels if you want.
The program looks at the STEPENABLE to determine if conversions are complete. This may not be the only or best flag for this purpose.
It looks at the count in the FIFO buffer to determine how many times to read the FIFO. NOTE: each read of the FIFO reduces the COUNT by 1 and pushes the next value in the FIFO up the stack. I have not done any testing to determine if you can read the FIFO too fast. Reading the FIFO can be done by assigning the FIFO buffer to a variable (e.g. x = FIFO reads the FIFO).
It sets the STEPID bit, so that the ID can be included in the data byte. Even though the register description in the TRM calls this the channel ID, it is the step ID. So, if you connect analog input 4 to step 1, the value in the channel ID in the FIFO DATA will be 0 (step 1 is labeled 0, step 2 is labeled 1, …). Consequently, if you only use one step, and that step is step 1, you cannot determine from the channel ID if anything is going on. It is up to the programmer to remember the mapping between inputs and step.
I ran into some difficulty with the ADC sample rate. What is it? The AM3358 data sheet reports it as 200 kSPS. But, the ADC_CLK is 24 MHz. There is also an ADC_CLKDIV register that divides this clock by 8 (set the value to 111b as the divider value minus 1) which yields a clock of 3 MHz.
There is an ambiguous line in the TRM talking about using clocks faster than 24 MHz. For the Beaglebone, this is irrelevant. The ADC_CLK is fixed and the ADC_CLKDIV value must be 111. These are set up by the OS, so leave them alone.
If you want to see what is going on in BB-ADC, copy /lib/firmware/BB-ADC-00A0.dtbo to your local directory and run
dtc -I dtb -O dts BB-ADC-00A0.dtbo > BB-ADC-00A0.dts
at the command line. The dts file can be viewed in a text editor (gedit or nano).
The registers governing the ADC are located in section xx of the TRM <insert link>. A header file to access the registers has been set up here. <insert link to header page once header file has been created and debugged>
To access the ADC from the PRU, follow the procedure to load code onto the PRU <insert makefile, code snippet for ARM, code snippet for PRU>.
The Touchscreen Controller – Analog to Digital Converter (TSC_ADC) control registers are located at 0x44e0d000 (see Table 2-2. L4_WKUP Peripheral Memory Map in the TRM).
The TSC_ADC DMA section is located at 0x54C0_0000 to 0x54FF_FFFF (see Table 2-1. L3 Memory Map in the TRM).
The CM_WKUP_ADC_TSC_CLKCTRL register (offset 0xbc, reset = 0x30000) needs to be turned on to make the adc work at all (MODULEMODE=2). The CM_WKUP registers start at 0x44E0_0400 (see Table 2-2 in the TRM).
Adding an anti-aliasing filter and input protection:
Aliasing is a problem in analog to digital conversion. An analog low pass filter is necessary no matter what. The order and cut-off frequency require design and testing. See here for some filter information (in progress). See here for some info on FFT on the beaglebone (in progress). What I’d like to try is to use a fairly high sample rate on the ADC with a cheap and dirty input section to provide buffering and filtering. Then, I’d like to implement a digital filter bank to provide multiple cut-off frequencies and downsampling. I’ve read something about this in Oppenheim and Shafer but have not had the opportunity to test it out. FIR low pass filters would be straight-forward to design and implement. The math seems to be workable. Does the PRU have the computational capabilities?
Add to /boot/uEnv.txt …
(Not needed with the PRU)