LRADC driver for kernel 3.2

Started by dpwhittaker, October 25, 2012, 05:22:23 PM

Previous topic - Next topic


I've spent the last few weeks learning linux device driver programming (thanks to O'Reilly for and free-electrons for, and have come up with what I believe is a respectable driver.  Obviously not the prettiest driver in the world, but it functions and meets my requirements:

1. Easily usable directly from the /dev/lradcN file without additional programs
2. Small memory footprint
3. As fast as possible
4. Able to hold a small buffer of values if the application doesn't get around to reading the samples in time
5. Configurable through the same file that it displays data on
6. Support for delay channels, accumulation, channel selection, current source, and decimal/hex/binary output

The IIO driver for the mx28 didn't meet these requirements, and I didn't want to learn another subsystem alongside the basic character driver subsystem, so this is incompatible with IIO, but is more compatible IMO with python, shell scripts, etc, without having to use an additional program to interact with it.  The goal is to end up with a simple set of drivers for the olinuxino that work consistently in just about any language.  I want to eventually create similar drivers with similar interfaces for GPIO (single-pin and ganged/parallel), I2C, and SPI (bit-bang and internal).

To get it, download it from my dropbox:

To install it, put both files anywhere in your file system, and run:

./lradc.init start (restart, stop also available)

To add more than one lradc channel (8 available), you can edit the init script near the top:

function device_specific_post_load () {
   for i in {0..0}
change to for i in {0..7} if you want to set up all 8 channels.

To interact with it, you write configuration parameters to the file, and read data from it.

echo delay 100 > /dev/lradc0

Sets the delay channel on lradc0 to a 100ms cycle. Values in range 0.5 to 1024 are acceptable.  Delay channels are provided on /dev/lradc0 to /dev/lradc3.  It is possible for one delay channel to drive multiple lradc channels, but I didn't want the channels to be tied together if different programs were using different channels.

echo select 8 > /dev/lradc0

Selects channel 8 (TEMP_SENSE_N) on /dev/lradc0.  channels are listed in the datasheet, but 0 and 1 are available as pins on the board, 6 measures VDDIO, 7 measures Battery voltage, 8 and 9 are used for temperature measurements on die, and 15 measures 5V input.  The rest are marginally to completely not useful.  Check the datasheet if you need more info on them.

echo current 300 > /dev/lradc0

Places a 300 mA current source on /dev/lradc0 as long as channel 0 or 1 are selected.  This allows you to hook up a thermistor or other resistance based sensor without an additional resistor.  Allowed values in range 0-300.

echo samples 10 > /dev/lradc0

Accumulates 10 samples for each sample returned.  The value returned is the average of the samples collected.  If delay is set to 100, and samples are set to 10, you will get 1 sample every 1000 ms that is the average of 10 samples taken at 100 ms intervals.  Maximum value of 32 samples.

echo base 10 > /dev/lradc0

Returns numbers in base 10 (values in range 0 - 4095).  Other available values are base 16 (values in range 0 to fff) or base 256 (raw binary data, 12 bits of information in 16 bit values).

echo config > /dev/lradc0

Returns the configuration data on next read, like so:

[root@micro module]# echo config > /dev/lradc0
[root@micro module]# cat /dev/lradc0
selected channel 8: TEMP_SENSE_N
current source: not available
delay: none
samples: 1
base: 10

echo debug > /dev/lradc0

Returns the debug data (register dump) on next read, like so:

[root@micro module]# echo debug > /dev/lradc0
[root@micro module]# cat /dev/lradc0
register   0 : 00000000 (00000000000000000000000000000000)
register  10 : 00FF0000 (00000000111111110000000000000000)
register  20 : FF008000 (11111111000000001000000000000000)
register  30 : 00000000 (00000000000000000000000000000000)
register  40 : 07FF0000 (00000111111111110000000000000000)
register  50 : 00000000 (00000000000000000000000000000000)
register  60 : 00000000 (00000000000000000000000000000000)
register  70 : 00000000 (00000000000000000000000000000000)
register  80 : 00000000 (00000000000000000000000000000000)
register  90 : 00000000 (00000000000000000000000000000000)
register  A0 : 00000000 (00000000000000000000000000000000)
register  B0 : 00000000 (00000000000000000000000000000000)
register  C0 : 00000000 (00000000000000000000000000000000)
register  D0 : 01010000 (00000001000000010000000000000000)
register  E0 : 02020000 (00000010000000100000000000000000)
register  F0 : 04040000 (00000100000001000000000000000000)
register 100 : 08080000 (00001000000010000000000000000000)
register 110 : 43210000 (01000011001000010000000000000000)
register 120 : 00000000 (00000000000000000000000000000000)
register 130 : 00000080 (00000000000000000000000010000000)
register 140 : 76543218 (01110110010101000011001000011000)
register 150 : 01010000 (00000001000000010000000000000000)

Finally, if you don't send a config or debug command to the file, it will print data from the lradc.  There is a 128-value ring buffer by default.  This can be changed with a recompile currently.  I may make this configurable eventually.  As long as the file is left open, the delay channels will continue to operate and add data to the ring buffer.  When you read the device, it will return all the data stored in the ring buffer.  If delay is set to 0, it will return exactly one sample each time you read the file, sampling immediately and blocking until the sample is read.  If delay is > 0, it will return all the data in the buffer, blocking until a sample is available if the buffer is empty.  A non-blocking read will never block, so if you use non-blocking reads with delay 0, then the first one will schedule a conversion and return nothing, then the next non-blocking read will return that sample and schedule another one, and so on.  A non-blocking read with delay > 0 will return nothing if the buffer is empty.  Multiple non-blocking reads may continue to read nothing, especially with long delays, but will return at least one value in the very next read after the conversion actually happened.

I know there's a lot of data here.  Feel free to play with it, recompile it for your kernel (just edit the kernel source location in the makefile), hack it to pieces and repost it elsewhere, or whatever else you want to do with it.  I would appreciate some recognition, but that is not even necessary.  I would also appreciate critiques of the code if you have time to look over it.

David Whittaker

Fabio Estevam

Cool, it would be nice if you could post this in a patch format to the relevant kernel lists, so that your work is reviewed and possibly applied to the kernel.


Fabio Estevam


Well, I'm not sure I see this driver as belonging to the Linux kernel.  In particular, I expect the mx28 driver to be ported, and it represents a much more "standard" way of doing things - at least I expect that was the idea behind IIO, and I expect utilities will eventually become commonplace to work with IIO devices.  I don't necessarily like configuring my kernel and seeing two different choices for the same functionality, and having to dig into both to figure out which one I really need, and the IIO driver does seem to be "the linux way" to do things.

My intention was more along the lines of creating a simple interface to the hardware that is hacker-friendly and has a low barrier for learning and implementation.  Sort of like a linuxified version of the Wiring libraries for arduino and such.  The only reason it is written as a driver is to give me direct access to interrupts and open the door to more time-sensitive operations.

My next step is to build a software stack on top of this - probably tornado2, tornadIO, and d3.js, to end up with a 2Khz oscilloscope with a web page as a front end (nodejs and socket.IO are in a close second, but I think it just comes down to javascript feeling weird on the server side).  Either way, this will give me a chance to show how to use it in an application, and to test it more thoroughly.

After that, I'm working on a GPIO driver with the same interface, and additional functionality over the built-in sysfs driver.  In specific, I want to allow parallel IO - controlling multiple pins in the same bank from a single character device, and timestamped input - recording the timestamp at the time of the edge interrupt and giving that to the user.  I'll then integrate parts of that driver back into the lradc driver to allow you to control an external analog multiplexer.  This would allow you to connect 4 wires to the mux and the mux to lradc0 and 2 GPIO pins, letting you read 4 analog values at a time (by cycling through them).  This could show up as /dev/lradc00 through /dev/lradc03, or something along those lines.  Of course, if you're reading 4 analog pins from 1 input, you max out at 500hz instead of 2Khz, but it will enable my project, which has 8 "ports", to have an analog pin on each port.

Once those are done, I'll implement my own SPI, and if I find a need for it I2C drivers as well, with the same simple interface, dynamically switching between bit-bang and internal SPI depending on whether the pins support it.  This will allow me to have "smart ports" - devices with microcontrollers (probably msp430 value line) in them that can auto-configure themselves and grant access to more capabilities than the 3-pin ports can provide if needed.

The final goal is to build a simple white box with 8 5-pin ports on the front, a USB, micro-USB, and TV-OUT port on the back.  The USB port on the back can be used for wifi, ethernet, or keyboard connectivity, while the micro-USB will be attached to a USB-RS232 converter into the AUART.  Each port will have GND, VCC, and 3 data pins connected.  Each port can be connected to a "probe" - a generic term for a sensor or controller of some sort.  One of the ports will have hardware SPI as the 3 data pins (all buses are single-slave, so CS will just be hardwired always selected on the probe side), and the others will use bit-bang SPI.  Alternatively, a probe can tie CLK to GND to signify that it is a "dumb" digital probe, or tie CLK to VCC to signify it is a "dumb" analog probe.  This is mostly just to save the $2-3 for a microcontroller, circuit board, and external components, when all you really wanted was a 10 cent thermistor.   Dumb probes will require some manual setup on the olinuxino, but the web front end will simplify this down to just selecting from a dropdown of all known probe types, including 2 generic probe types that allow manual configuration.  Smart probes will identify themselves to the olinuxino, which will then have a list of commands to communicate with it.  All in all, the end goal is to have a simple, easy to extend device for measurement and control, specifically targeted at the education market for use in high school and college labs, but with plenty of room for using as the brains of your robot in the hobby market, all while keeping the cost of the device under $100 fully assembled retail, and under $50 BOM in small to medium quantities (100).

In the end, this driver set will evolve into something fairly application-specific to this particular platform.  I guess I could still be convinced to integrate it into the linux source tree, but I've seen too many comments otherwise to think the linux community would support something so specific in the general kernel.


this is fantastic!

this weekend I read the same books and made my first dummy kernel module and driver


If you are following these, be sure to lean towards free-electrons for 3.2 development.  It seemed to be a little more up to date than the O'Reilly book, though I think the O'Reilly book is better organized and contains a little more information.



Where can we download the LRADC driver ?
The link to the DropBox is dead !

Thanks for developping such driver and ... the next to come  ;)


I've modified the OP, but here's the new link.

Sorry, my wife "cleaned up" the dropbox and moved all my shared links around.


Here is the error that I get when trying to install it.

(loading file ./lradc.ko)insmod: ERROR: could not insert module ./lradc.ko: Invalid module format


Quote from: dpwhittaker on October 25, 2012, 08:35:44 PMthe end goal is to have a simple, easy to extend device for measurement and control, essay typer specifically targeted at the education market for use in high school and college labs, but with plenty of room for using as the brains of your robot in the hobby market

Yes, we are being conditioned as a species to become completely dependent on electronic devices and we are willingly giving in to that inevitability.