[Updated Jan 25: Correction! There is a 1.8V voltage source on the Beaglebone: Port 9 Pin 32. Thanks to Koen Kooi for the info. I've updated the text with this information]
[Update May 5: In kernel version 3.2.14, the file system path for the analog pin readings was changed from /sys/devices/platform/tsc to /sys/devices/platform/omap/tsc]
This article is my second explaining the fundamentals of coding with the Beaglebone. In the first article I explained some of the mysteries of pin muxing, and gave an example of a very simple usage of a digital pin. This time, I’ll use an analog sensor and serial I/O (just O, actually), to create a time and temperature LCD display.
I have to admit that I’m far from an expert – I’m basically writing about this stuff as I figure it out.
The Beaglebone will hopefully prove to be a ground-breaking product, introducing a lot of electronics hobbyists to embedded Linux programming. Unfortunately, at this point there isn’t much in the way of embedded Linux development tools or tutorials that is geared to newcomers. This should change by the summer of 2012, as bonescript expands and more Beaglebone-based projects pop up on blogs and sites like Instructables. Until then, it will be one baby step at a time…
The first rule of analog input with the Beaglebone is:
NOTE: Maximum voltage is 1.8V. Do not exceed this voltage. Voltage dividers
should be used for voltages higher than 1.8V.
So sayeth the Beaglebone System Reference Manual (Emphasis theirs. )
I haven’t experimented with what happens if you go above 1.8V, but since they’ve made the warning red and underlined, I’m pretty sure it would not go well.
This limitation is a problem, since:
There is no pin on the Beaglebone that provides a 1.8V source
[Correction Jan 25 - as pointed out in the comments, there is a pin that provides 1.8V, Port 9 pin 32 (labelled VDD_ADC in the Beaglebone System Reference Manual). So you can ignore the stuff about voltage dividers and level converters below if you are using an analog sensor that can operate on 1.8V, like a potentiometer.]
- Many analog sensors require a minimum voltage greater than 1.8V
So, until someone has a better idea, voltage dividers will be an important part of any Beaglebone analog circuit.
The second most important thing to know about the Beaglebone’s analog input is that the software support seems to be a work-in-progress. The approach I’m taken is based on a brief blog post by one of the maintainers of the Beaglebone Angstrom demo images. His remarks would suggest that they have a better plan that is under development. As far as I know, the approach taken here is an Angstrom kernel modification that won’t work at all on a Ubuntu image with the vanilla Ubuntu kernel.
Back to the issue of 1.8 max voltage. You have a couple of options in your design:
- Added Jan 25: Use Port 9 Pin 32 as your voltage source, if your analog sensor can work on 1.8V
- Use a couple of high precision resistors that can knock 3.3V or 5V down to exactly 1.8V
- Use a couple of garden variety resistors to knock the voltage below 1.8V, then modify your software to adjust for the difference
Being lazy, I’m going with the latter approach. Being very lazy, I’m not even bothering to hook up the resistors – as shown in the screenshot below, I’m using a tiny breakout board to do the work.
Specifically, this is a Sparkfun Logic Level Converter – BOB-08745. You’ll see this part recommended quite often in Beagleboard/Beaglebone circles, since it is an inexpensive solution that can handle a range of voltages, and supports 2-way conversion (necessary for I2C).
For the purpose of analog input I’m not using the board as intended – I’m taking advantage of a semi-documented hack. The RX pins on this board just use a voltage divider: the voltage going into the “HV” pins comes out as half the amount from the LV pins. The circuit for this, taken from the BOB-08745 schematic, is shown below.
If you don’t have a BOB-08745, you can just use a couple of 10K resistors.
Before actually using this type of circuit, measure what voltage you get from the LV end of the voltage divider if the HV end is connected directly to 3.3V. If it isn’t below 1.8V, figure out what’s wrong before you hook it up to the Beaglebone. (Red and underlined, remember?) Don’t worry if the reading isn’t exactly 50% – we will calibrate the software to handle the variance.
The 2 analog sensors I’m using are a 10K potentiometer and an analog thermistor (temperature sensor), the Microchip MCP9700A.
As you can see from the photos, the connections are:
- Potentiometer and MCP9700A:
- Input pin to 3.3V (which connects to pins 3 or 4 of Port 9 on the Beaglebone),
- Ground pin to ground (connects to pins 1 or 2 of Port 9 of the Beaglebone),
- Output pin to one of the RXI pins on the Sparkfun level converter (or to the HV pin on the voltage divider circuit)
- Added Jan 25: An alternative for the potentiometer is to connect the input pin to Port 9 Pin 32 (VDD_ADC)., and the output pin directly to the Beaglebone AIN0 pin (pin 39 on Port 9)
- Sparkfun level converter:
- HV to 3.3V
- HV GND and LV GND to Ground
- LV RXO for the potentiometer to the Beaglebone AIN0 pin (pin 39 on Port 9, or the outside pin of the 4th row from the “bottom” – the end opposite the Ethernet port)
- LV RXO for the MCP9700A to the Beaglebone AIN1 pin (pin 40 on Port 9, or the inside pin of the 4th row)
Note that the LV pin on the level converter isn’t used, since we are just interested in the voltage divider.
The definitive guide to the Beaglebone pin connections is the System Reference Manual. For this project we are just using Port 9, so you can refer to the table below:
Testing Analog Input
Remember all that stuff about pin muxing from the first article? You can forget about it for analog in.
As you can see by looking at Table 12 of the Beaglebone System Reference Manual (part of which is shown below), the mux table for all of the analog in pins (AIN1 to AIN6) is empty: the pins can only be used for analog in.
There actually are entries for these pins in the file system, /sys/kernel/debug/omap_mux, but they have the correct setting by default, and as far as I can tell the Mode setting has no effect on the pins. (The Python code I wrote sets them anyway, but that’s admittedly more out of ignorance than caution.)
So, we can proceed directly to using the pins.
Let’s start with the potentiometer on analog 0 (pin 39 of Port 9). To get the current reading from the pot, enter the following (yes, it’s ain1 for analog 0 – go figure):
[Update May 5: In kernel version 3.2.14, the path to the analog pins was changed slightly, from /sys/devices/platform/tsc to /sys/devices/platform/omap/tsc. The following examples use the original path]
root@beaglebone:~# cat /sys/devices/platform/tsc/ain1 1807
A number should be displayed. Turn the pot all the way up and repeat. You’ll see the maximum value.
For me, it’s 3779. Or 3775. Or 3776. It drifts around a little. Go figure.
The analog input pins on the Beaglebone are 12-bit, so the maximum possible value is 4096. This represents a value of 1.8V. At 3779, it’s reading 3779/4096 * 1.8V, or 1.66V, which is about what you would expect with a 10K/10K voltage divider with 3.3V input.
Now, check the MCP7200A thermometer on analog 1 (again, the analog index is off by 1, so it’s ain2):
root@beaglebone:~# cat /sys/devices/platform/tsc/ain2 826
You should get a relatively low value: if it’s above 1000, either you are in hell or your thermistor isn’t hooked up correctly.
The formula for converting this reading to a temperature is:
(milliVolts – 500) / 10
The 826 reading I got is 826/4096 * 1.8V, or 362 mV.
Is it cold in here, or is it just me?
It’s just me. The voltage divider knocked the reading from the MCP7200A down by about half, or the actual reading is 362 * 2, or 724 mV. The temperature reading is therefore (724 – 500) / 10, or 22.4. (No, that isn’t cold, it’s just metric).
Actually, neither the MCP9700A nor this approach is very accurate. The MCP7200A’s datasheet says it’ typical accuracy is to within 1 degree C . There isn’t anything we can do to improve that, but we can make our calculation more accurate by allowing for slight variances of the resistors in our voltage divider.
To see what effect the voltage divider is having, temporarily hook the input to 3.3V instead of the MCP9700A’s output pin. Then check the value of /sys/devices/platform/tsc/ain2 again.
And again. And again.
I get 3765. And 3762. And 3759.
Given that each 10mV is a degree, that jumping about is a real problem. We’ll plug the average (call it 3762) into the code, then average a bunch of readings to get the temperature, approximately.
The Beaglebone has 6 serial UARTs. One of those, UART0, is connected to the USB port, but that leaves us with 5 to play with. This is quite a step up from the Arduino’s single UART, or the Netduino’s 2 UARTs.
All of these are 3.3V UARTs. This is a problem if you’re communicating with a 5V device, but only when receiving data. Some 3.3 microcontrollers, like the Netduino’s, have “5V tolerant” pins, so they can communicate directly with 5V serial devices. But, for the Beaglebone:
NOTE: Do not connect 5V logic level signals to these pins or the board will be
So sayeth the Beaglebone System Reference Manual.
I assume this means that connecting an UART RX pin directly to the TX pin of a 5V serial device is a bad idea. I did it for awhile, and my Beaglebone lived to tell the tale, but maybe I got lucky.
Were we receiving data from the Serial LCD, then a level converter would be required, like the Sparkfun BOB-08745 described earlier. (Actually a second level converter would be needed, since we would be going between 3.3Vand 5, not 3.3 and 1.8ish.)
However, it will be serial TX only for this project, and all 5V serial devices that I’ve come across handle 3.3V on the RX pin without problems.
In the demo Angstrom image, the pins are not enabled for UART by default. Yup, time for the black art of pin muxing again.
Working with UART1’s mux mode is relatively straightforward. UART1 is the Mode 0 usage for the pin, and as you may recall from my first article, the pin name used in the file system are taken from the Mode 0 usage. To check the current settings:
root@beaglebone:~# cat /sys/kernel/debug/omap_mux/uart1_rxd name: uart1_rxd.(null) (0x44e10980/0x980 = 0x0037), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE7 signals: uart1_rxd | mmc1_sdwp | d_can1_tx | NA | NA | NA | NA | NA root@beaglebone:~# cat /sys/kernel/debug/omap_mux/uart1_txd name: uart1_txd.(null) (0x44e10984/0x984 = 0x0037), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE7 signals: uart1_txd | mmc2_sdwp | d_can1_rx | NA | NA | NA | NA | NA
As you can see, both are set to 0×37 by default in the Angstrom demo image. This setting means they are in Mode 7 (as the 2nd line of the output confirms) and that the Receive feature is enabled for both pins.
Let’s briefly take a closer look at the meaning of these mux pins. In my last article I explained the Mode settings, but not the other bits. The full list of the bit settings can be found in Table 9-58 of the AM335x Technical Reference Manual – you can find a link to it here. I’ll save you the trouble of searching for the table in that 4500-page behemoth:
The 2 UART pins currently have a mux setting of 0×37, or 0011 0111. So:
slowfast [Corrected - July 5] slew rate is selected – this might concern me if I knew what it meant.
- The Receiver is enabled – we want that for the RX pin, not so much for the TX pin
- The pullup/pulldown is set to pullup – not a concern for serial communication
- The pullup/pulldown is enabled
- The 3 mode bits are all on, to indicate mode 7
We want to set both pins to Mode 0, and we also want the receiver disabled on the TX pin:
root@beaglebone:~# echo 20 > /sys/kernel/debug/omap_mux/uart1_rxd root@beaglebone:~# cat /sys/kernel/debug/omap_mux/uart1_rxd name: uart1_rxd.uart1_rxd (0x44e10980/0x980 = 0x0020), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE0 signals: uart1_rxd | mmc1_sdwp | d_can1_tx | NA | NA | NA | NA | NA root@beaglebone:~# echo 0 > /sys/kernel/debug/omap_mux/uart1_txd root@beaglebone:~# cat /sys/kernel/debug/omap_mux/uart1_txd name: uart1_txd.uart1_txd (0x44e10984/0x984 = 0x0000), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE0 signals: uart1_txd | mmc2_sdwp | d_can1_rx | NA | NA | NA | NA | NA
Having said all that, I’m actually going to use UART2 for this project.
UART2 is a little trickier, since its pins have other names in the file system. Looking at Table 11 of the Beaglebone System Reference Manual, we see that UART2_TXD is pin 21, and UART2_RXD is pin 22. Table 12 (below) tells us that pin 21’s Mode 0 usage (and therefore the name used in the file system) is spi0_d0, and that UART2_RXD is the Mode 1 usage. For Pin 22, it’s spi0_sclk, and we also want to change it to Mode 1.
Here are the commands for checking the current settings, and changing the settings to use UART2:
root@beaglebone:~# cat /sys/kernel/debug/omap_mux/spi0_d0 name: spi0_d0.(null) (0x44e10954/0x954 = 0x0037), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE7 signals: spi0_d0 | NA | NA | NA | NA | NA | NA | NA root@beaglebone:~# echo 1 > /sys/kernel/debug/omap_mux/spi0_d0 root@beaglebone:~# cat /sys/kernel/debug/omap_mux/spi0_d0 name: spi0_d0.(null) (0x44e10954/0x954 = 0x0001), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE1 signals: spi0_d0 | NA | NA | NA | NA | NA | NA | NA root@beaglebone:~# cat /sys/kernel/debug/omap_mux/spi0_sclk name: spi0_sclk.(null) (0x44e10950/0x950 = 0x0037), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE7 signals: spi0_sclk | NA | NA | NA | NA | NA | NA | NA root@beaglebone:~# echo 21 > /sys/kernel/debug/omap_mux/spi0_sclk root@beaglebone:~# cat /sys/kernel/debug/omap_mux/spi0_sclk name: spi0_sclk.(null) (0x44e10950/0x950 = 0x0021), b NA, t NA mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE1 signals: spi0_sclk | NA | NA | NA | NA | NA | NA | NA
A Python Time and Temperature Display for the Beaglebone
I’ve written some Python code to try out the analog and serial pins of the Beaglebone. It gets the temperature from the MCP9700A, and uses the potentiometer to set the backlight of the LCD.
You can download the Python program from my Google Code project page: it’s just a single file, serdisplay.py.
From the Beaglebone command line, you can download the Python program as follows:
root@beaglebone:~# wget http://gigamega-micro.googlecode.com/files/gpiotester.py
The code uses one Python library that isn’t included in the Angstrom demo image, python-pyserial. To install it:
root@beaglebone:~# opkg install python-pyserial
There is no configuration file or command line parameters yet, so any changes to the default settings (the Sparkfun Serial LCD Backpack and a 4-row 20-column LCD) should be made to the code near the top of the program:
# -------------- configurable settings --------- # settings for UART1 #DISPLAYPORT = '/dev/ttyO1' #RX_MUX = 'uart1_rxd' #TX_MUX = 'uart1_txd' #MUX_MODE = 0 # settings for UART2 DISPLAYPORT = '/dev/ttyO2' RX_MUX = 'spi0_sclk' TX_MUX = 'spi0_d0' MUX_MODE = 1 # settings for Serial LCD DISPLAY_TYPE = 'SPARKFUN_KIT' #DISPLAY_TYPE = 'MATRIX_ORBITAL' #DISPLAY_TYPE = 'SPARKFUN' # settings for analog voltage conversion MAX_ANALOG = 3762 # approx 4096 * (1.65/1.8), since voltage divider gives me max of 1.65 # -- actual values of 3762 based on measurements # settings for display updates TIME_UPDATE_INTERVAL = 1 # every second TIME_DISPLAY_ROW = 1 TEMPERATURE_DISPLAY_ROW = 2 LCD_NUM_ROWS = 4 LCD_NUM_COLS = 20 # determines whether debug info is written to the console Debug = True # ---------------------------------------------------------
Then, just run the program as root:
root@beaglebone:~# python serdisplay.py
To stop the program, press Ctrl-Z to put it in the background, then kill the background job:
root@beaglebone:~# kill %1
If you want to keep the program running all the time, I’d recommend using the GNU Screen utility:
root@beaglebone:~# screen <Press enter when prompted> root@beaglebone:~# python serdisplay.py
To return to the main command command prompt (leaving the Python program running in the background) press Ctrl-A D. To go back to the Screen session at any time in the future:
root@beaglebone:~# screen –r –d
For troubleshooting, any error messages are written to serdisplay.log in the same directory as serdisplay.py.
Programmer’s Show And Tell
Since the focus of this article is the Beaglebone, not Python, I’ll just point out some Beaglebone-specific parts of the code.
The code which initializes the UART for the serial display is:
DISPLAYPORT = '/dev/ttyO2' RX_MUX = 'spi0_sclk' TX_MUX = 'spi0_d0' MUX_MODE = 1 BAUDRATE = 9600 TIMEOUT = 3 # serial port timeout is 3 seconds - only used when reading from display # MUX settings RECEIVE_ENABLE = 32 . . . open('/sys/kernel/debug/omap_mux/' + RX_MUX, 'wb').write("%X" % (RECEIVE_ENABLE + MUX_MODE)) # set the TX pin for Mode 0 open('/sys/kernel/debug/omap_mux/' + TX_MUX, 'wb').write("%X" % MUX_MODE) serDisplay = serial.Serial(DISPLAYPORT, BAUDRATE, timeout=TIMEOUT)
There are Linux character devices assigned to the BeagleBone UARTs: UART1 is /dev/ttyO1, UART2 is /dev/ttyO2, and so on.
From the point of view of the Python program, the Beaglebone’s UART behaves like any other serial port. The code I wrote would work unchanged (aside from the /dev name) when communicating with an XBee, or through a USB-based virtual COM port.
Analog input is a little more finicky. The code which reads the analog value is:
def readAnalog(pinIndex): try: # add 1 to pin index to get analog pin sys filename reading = open("/sys/devices/platform/tsc/ain" + str(pinIndex + 1), "r").read() # sometimes string has trailing nulls - delete them val = int(re.sub(r'[^\d]+', '', reading)) return val except: log.exception('Error in readAnalog')
As indicated by the comments, I found that the value read through the file system was sometimes corrupted: it contained nulls, usually after the value, but occasionally imbedded within the value. I’m not sure if this is a Python-specific thing, or other code would also encounter the problem. In Python, you can strip out the null (and any other non-numeric) values using a regular expression:
val = int(re.sub(r'[^\d]+', '', reading))
From time to time, the attempt to read the analog value will throw an exception. So, it’s best to use a try/except handler like the one above.
One other problem is that the analog value tends to jump around by about 5 points from one reading to the next, even when it should be constant (e.g. from the potentiometer). I adjusted for this by using an average reading for the temperature, and by ignoring potentiometer readings (i.e. LCD brightness settings) that are within 20 of the last setting.
Don’t forget that the analog value is from 0-4096 – 16 times more sensitive than on the Arduino — so allow for a greater range of readings.
Based on my testing of the code in this project, I’ve found that the Beaglebone’s UARTs are quite reliable and pretty easy to use. Serial communications should be a relatively easy and trouble-free addition to any Beaglebone project.
Analog in, on the other hand, has room for improvement.
The approach I used for reading analog input in is good enough for things that don’t require precise accuracy, like the potentiometer or a light meter. The readings returned by the MCP9700A thermistor are not as stable as they should be.
Fortunately, analog is not the only game in town when it comes to temperature readings, so I plan to experiment with some I2C sensors in the future.