Jump to content
43oh

Beaglebone black on board ADC


Recommended Posts

So like I said in another recent post early this morning. I was dorking around with the on board ADC last night, and got some fairly surprising results. What i did was in fact not all that special, but I was opening, reading, and closing a file descriptor to the pseudo sysfs file to channel 0 to the ADC many times. The surprising part, was how fast it actually was.

 


debian@beaglebone:~$ uname -a
Linux beaglebone 4.1.5-ti-rt-r10 #1 SMP PREEMPT RT Fri Aug 14 00:02:42 UTC 2015 armv7l GNU/Linux

debian@beaglebone:~$ cat /etc/dogtag
BeagleBoard.org Debian Image 2015-03-01

debian@beaglebone:~$ time ./test
4005 3998 4001 4004 3991 4000 4008 4007 4006 4002 4004 4009 4012 4006
4010 4005 4008 4016 4011 4014 4013 3996 4008 4011 4013 4009 4005 4009 4009
4002 4006 4012 4014 4008 3994 3997 4003 4005 3999 4000 3999 4004 4006 4002
4004 4004 4007 4011 4010 3997 4004 4009 4007 4001 3997 3993 4006 4010 4003
4006 3998 4009 4011 4008 4007 4005 4008 4015 4009 4008 4011 4013 4015 4013
4009 4011 4007 4012 4010 4006 4002 4005 4007 4011 4009 4006 4003 4013 4010
3994 3995 3997 3983 3997 3997 3991 3995 4008 3997 3999 4006 4007 4003 4002
4009 4000 3992 4006 4004 3993 4004 3995 3999 3999 4007 4009 4000 4009 4010
4015 4017 4011 4013 4011 4006 3995 4008 4010 4010 4012 4006 4004 3998 4013
4012 4016 3999 3996 4010 4012 3993 3993 3996 4005 4000 4009 4005 3992 4002
4008 4007 4007 4008 4010 4005 3989 4005 4005 4001 4003 3996 4002 4002 4006
4009 4008 4004 4009 4008 4006 4004 4013 4010 4004 3998 4001 4014 4005 4012
4005 4013 4003 4011 4007 4006 4002 3999 4011 4015 4008 4005 4003 4012 4010
4006 4010 3990 3999 3993 3989 4000 4001 3999 3998 4012 4006 4000 3994 4005
4008 4007 4005 4005 4009 4009 4003 3998 4002 4000 4010 4007 3994 4008 4011
4011 4013 4004 3996 4004 4013 4011 4003 4001 4012 4014 4010 4009 3995 4005
4011 3999 4006 3999 4013 4008 3996 4002 4009 4000 3993 4003 4002 3992 3989
3999 4003 4008 4004 4011 4009 4001 4003 4001 4014 4004 4004 3999 4008 4009
4009 4006 4009 4008 4006 3999 4000 4013 4015 4009 4011 4013 4013 4002 4009
4011 4013 4014 4015 4014 4008 4006 3996 4000 4004 4012 4004 3999 4011 4007
3993 4008 4001 3996 4005 4008 4006 4002 3999 4000 4008 4003 3991 3999 4012
4006 4012 4005 4005 4010 4010 4010 3992 4006 4008 4001 3997 4010 4005 3997
4003 4016 4007 4011 4009 4011 4014 4011 4001 4009 4009 4010 4005 4002 4012
4009 4019 4009 4007 4009 4013 4009 4009 4003 4010 4006 4003 3999 4006 4003
4001 4005 4006 4004 4007 4010 4009 3997 3996 4008 4005 4003 4000 4005 3996
4006 4004 4004 4010 4005 4005 4002 4012 4013 4011 4010 4010 4013 4009 4014
4012 4007 4013 4013 4017 4009 4015 4013 4012 4013 3994 4009 4014 4001 4000
4004 4007 3997 4003 4007 4007 4000 4001 3996 3991 3999 4009 3997 4002 4009
4010 4008 4008 3990 3996 4009 4007 4011 4001 4004 4010 4014 4007 4003 4006
4013 4014 4012 4012 4016 4009 4008 4012 4014 4012 4008 4006 4014 4008 4009
4005 4011 4013 4013 4011 4008 3999 4013 4011 4007 4014 4010 4008 4005 3994
4002 4006 4003 4002 4006 4008 4003 4004 3996 4006 4005 4008 3996 4007 4009
4005 4009 4012 4014 4009 4009 4007 3996 4012 4012 4008 4004 4011 4003 4011
4017 4016 4008 4007 4009 4014 4010 4007 4004 4010 4013 4007 3999 4013 4011
4011 4010 4013 4010 4004 4004 4003 3999 4005 4004 3992 4002 4000 4007 4004
3993 4002 3995 4001 4007 4011 4005 4004 4005 3996 4009 4010 4004 4002 4001
4011 4012 4011 4012 4015 4015 4000 4010 4015 4013 4008 4010 4016 4011 4003
4011 4003 3998 4001 4003 4002 4014 4009 4006 4010 4005 4007 4006 4001 4007
4007 4002 3993 3991 4002 3999 4007 4004 4000 4008 4005 3992 4003 4008 4008
4006 4011 4014 4010 4012 4004 4001 3999 4012 4010 4006 4002 4015 4012 4002
4014 4016 4013 4016 4005 4009 4011 3999 4009 4010 4012 4006 4006 4014 4007
4007 4009 4006 4000 4014 4005 4002 4003 4006 4007 3999 4003 4004 4003 3991
4005 4003 4005 4002 3995 4008 4003 4003 3997 4009 4008 4008 4003 3997 4011
4010 4006 4012 4012 4012 4009 4015 4016 4015 4015 4012 4008 4011 4010 4014
4011 4001 4014 4003 4009 4012 4010 4000 4008 4010 4010 4007 4000 4012 4009
4005 3999 4003 4004 3997 4002 4006 4009 4001 4003 3996 3999 4005 4013 4000
4011 4001 3997 4006 4005 4002 4005 3995 4010 4009 4004 4005 4009 4012 4013
4004 4011 4003 4007 4011 4014 4015 4012 3998 4009 4013 4010 4015 4013 4013
4013 4010 4011 4013 4011 3998 4006 4012 4009 4003 3999 4006 4000 3996 4001
4009 4006 4006 4003 4013 4007 3994 4005 4008 4008 4012 4009 4003 4001 4001
4007 4005 4010 4012 4012 4013 4010 4002 4001 4015 4017 4009 4002 4014 4012
4016 4005 4001 4002 4009 4009 4006 4008 4000 4012 4011 4010 4004 4005 4011
4006 4012 4006 4007 4004 4003 4003 3996 3996 4005 4009 4005 4000 4001 4006
4012 4005 4002 4007 4001 4009 4008 4007 3995 4009 4008 3998 4005 4010 4003
4016 4017 4015 4015 4013 4016 4015 4007 4002 4001 4014 4016 4004 4006 4009
4014 4007 4011 4007 4006 4006 4009 4012 4012 4006 3997 4007 4012 4000 4000
4008 4008 4002 4004 4008 4007 3997 4002 4001 4002 4011 4006 4003 4006 4006
4008 4010 4013 4005 4003 4002 4012 4008 4013 4008 4004 4016 4013 4009 4012
4011 4011 4012 4014 4013 4001 4015 4012 4015 4008 4006 4007 3999 4001 4004
4000 4010 3995 4001 4002 3998 4005 4007 3996 3994 4003 4010 4003 4000 4004
4012 4008 4009 4010 4006 4008 4005 4005 3999 3994 4010 4012 4002 4002 4004
4006 4004 4014 4009 4013 4010 4009 4005 4015 4011 4008 4006 4005 4014 4011
4008 4009 4016 4012 4011 4007 4003 4012 4008 4006 4012 4007 4004 4005 4002
4003 4006 4008 4009 4010 4008 4004 3994 4004 4005 4004 4003 4010 4006 4005
4005 4006 3998 3997 4009 4009 4008 3998 4007 4015 4006 4007 4015 4016 4019
4013 4012 4007 4006 4004 4010 4010 3994 4006 4014 4007 4001 4012 4010 4006
4010 4006 4013 4012 4007 4002 4003 4006 4007 4000 3997 3998

real    0m0.322s
user    0m0.010s
sys     0m0.250s

The code that I wrote, which again is nothing all that special.

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

void read_adc(int fd)
{
        char adc[5] = {0};
        int len = read(fd, adc, sizeof(adc - 1));
        if(len == -1){
            printf("error: %s\n", strerror(errno));
                exit(1);
        }
        else if(len == 0){
            printf("%s\n", "buffer is empty");
        }
        else{
            adc[len] ='\0';
            printf("%s ", adc);
        }
}

int main()
{
        const char *fname = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
        int count = 0;

        while( count++ <= 1000){
                int fd = open(fname, O_RDONLY);
                if(fd == -1){
                        printf("error: %s\n", strerror(errno));
                        exit(1);
                }

                if(count % 15 == 0)
                        printf("\n");

                read_adc(fd);

                close(fd);
        }
        printf("\n");

        return  0;
}

Is it my imagination, or is really fast for what I'm doing? I did try to use mmap() on the pseudo file, but apparently this is either unsupported, or I tried using an improper mode. As I get an error similar to "Device does not exist".

 

Would anyone have any hands on using mmap() and peripherals in this manner ? Also, I'm thinking I might have to use /dev/mem/ but honestly do not know. This is kind of new territory to me.

 

EDIT:

 

err . . . I should also mention this is using one-shot mode. Not continuous mode. Hence why I was opening / closing the file descriptor every iteration. Plus I think continuous mode uses a buffer file somewhat different to this specific pseudo file. I've only read about it, and haven't made an attempt at using it yet.

Link to post
Share on other sites

My guess is mmap wouldn't be supported because the data stream is not inherently something you can "skip forward" or "rewind backward" through, like a file or truly memory mapped interface. It's a point-in-time and non-idempotent stream of data generated on the fly.

 

I would imagine a continuous mode with ability to stream data forever from a single open filedes. I'd be very interested to see how to do that.

Link to post
Share on other sites

My guess is mmap wouldn't be supported because the data stream is not inherently something you can "skip forward" or "rewind backward" through, like a file or truly memory mapped interface. It's a point-in-time and non-idempotent stream of data generated on the fly.

 

I would imagine a continuous mode with ability to stream data forever from a single open filedes. I'd be very interested to see how to do that.

Yeah, which is exactly why I have not done it yet. I've been reading a lot lately about file descriptors, O_NONBLOCK, and epoll(). At first look, it seems to be semi trivial, but when you start digging deeper . . . you find out how complicated it really can be. So, in this context, I'm wondering if this approach would work with the ADC buffer. I've read that epoll() does not work on "regular files", but only file descriptors of things similar to sockets. But, I'm going to do some more reading, and see what I can figure out.

 

This link, has some fairly good information on setup: http://processors.wiki.ti.com/index.php/Linux_Core_ADC_User's_Guide#Usage

Link to post
Share on other sites

@@spirilis

 

How many samples a second are you wanting to achieve ? I've been reading a bit, and apparently sysfs is going to limit the ADC to ~10Khz. Passed that, it does not seem using the PRU to sample the ADC will make a huge difference either. Maybe ~20Khz. . . still reading . . .

Isn't the hardware capable of 1-2MSPS or something?  Truthfully I don't have anything that would need even 10KHz... but digital power type of apps could want more than that.

Link to post
Share on other sites

So just doing a bit of experimentation. I ditched the while check and made it an infinite loop. The program ran for ~30 seconds, then exited with the error "resource temporarily unavailable" - heh. So then I added a usleep(100) in the loop. The program ran a little longer, then exited with the same error. So I increased to usleep(1000), and the executable ran for 5 + minutes. Until I hit ctrl + c at the terminal. So looks like ~1k is max using one-shot mode. At least on a single channel, and the way I was doing things. CPU was up around ~20% as well, so if one wanted to sample all channels, I figure (roughly ) that this could only be done every 2-3 milliseconds at most. But that's speculation on my part, as for the moment, I do not really care enough, to test that theory.

 

Also, for now, I have little interest in continuous mode. I mean seriously, what I have now is more than enough for anything I'd want to do - For now. But it looks like, continuous mode is very similar to one-shot with a little extra setup, and reading the value(s) stored from a different location. You can actually set the sample buffer to store as many samples in one go that you want. How to read all that out though . . . I do not know yet.

 

@@spirilis

Check this out: http://exploringbeaglebone.com/chapter13/DR Molloy's companion site for his book. But it looks like he covers using an external ADC through the PRU. Actually two different ADCs. One at 100k, the other at just above 1msps. Looks like pretty awesome stuff.

Link to post
Share on other sites

So with the code below . . .


. . .
real    1m41.256s
user    0m1.840s
sys     1m16.360s

So roughly 2960 samples a second. Now I think I've beat this horse to death.

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
        const char *fname = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
        int count = 0;
        int fd;
        char adc[5] = {0};
        int len;

        while(count < 300000){
                fd = open(fname, O_RDONLY);
                if(fd == -1){
                        printf("error: %s %d\n", strerror(errno), errno);
                        exit(1);
                }

                if(count % 10 == 0 && count != 0)
                        printf("\n");

                len = read(fd, adc, sizeof(adc - 1));

                if(len == -1){
                        close(fd);
                        continue;               
                }
                else if(len == 0){
                        printf("%s\n", "buffer is empty");
                }
                else{
                        adc[len] ='\0';
                        printf("%s ", adc);
                }

                close(fd);

                count++;
        }

        return  0;
}
Link to post
Share on other sites

err, my math was way off . . . testing for 300,000 iterations actually gave me results in the 5k-6k samples a second ballpark.

 

With printf()'s commented out I get around 5890.55351567869 samples a second

 

With printf()'s in place I get around 5235.419357090503 samples a second.

Link to post
Share on other sites
ADC Driver Limitations

This driver is based on the IIO (Industrial I/O subsystem), however this is the first release of this driver and it has limited functionality:

  1. No HW trigger Support. Currently only supporting software trigger.
  2. Limited number of samples in continuous capture mode. (Only 1528 samples per capture)
  3. Limited maximum sample rate in continuous mode: 8K samples / second.
  4. Simultaneous capture on multiple ADC channels is not supported. Currently only supports continuous capture on a single ADC input channel at a time.
  5. "Out of Range" not supported by ADC driver.

http://processors.wiki.ti.com/index.php/AM335x_ADC_Driver's_Guide

Link to post
Share on other sites

@@spirilis

 

So, I could use your help if you're able to offer it. I want to use /dev/mem/ to access the Beaglebone's ADC. So I need to know how to find out the location in memory for the ADC peripheral. The start address I believe I have found, through the Linux ocp file system.

debian@beaglebone:~$ ls -al /sys/firmware/devicetree/base/ocp/ | grep tscadc
drwxr-xr-x  4 root root  0 Oct  4 19:20 tscadc@44e0d000

I'm 99% sure that is the start address for the peripheral. Ok, so assuming I'm right, is there a way you know of that I can use to find the address size in memory for the ADC ? I've read through the AM335x TRM, under the ADC section ( chapter 12 ), and do not see mention of it there.

 

The rest of this is what I've done so far.

 

So I've literally searched hundreds of places on the web. Including many git projects that I've happened across while searching. Many different projects, blogs, and stackoverflow posts do not *even* jibe. Anyway, now I'm looking through the Table 12-4. TSC_ADC_SS REGISTERS on pages 1490-1492. The last entry is for FIFO1 at offset 200h. Based on FIFO0's offset ( 100h ) should it be safe to assume that size in memory of the whole peripheral would be 300h ? If not, I think it would be safe to say that it would work for my own case?

 

My idea: Set up the ADC using sysfs. set continuous mode, set the separate channels, etc. All this is easy. After that, map the device FIFO(s) through /dev/mem/ using mmap(), and just read out the data . . . One thing I am still a little unclear of however. Is that I may have to set a single bit to clear the buffer when I'm done reading. I think I read something regarding this, but as I said, I'm not clear on that point.

 

Anyway, this should be doable. In fact, I would bet on it. Also not really related to all the above, except I experienced this in all my searching today. It is funny, how all these so called Beaglebone experts who write books, teach classes and all that. Are copying code from other people who did all the hard work, very blatantly . . . I think Derrek Molloy, and one other I can not think of are the only ones I have not seen do this . . .

 

EDIT:

 

By the way the reason I'm 99% sure this is the start address of memory for the ADC is this:

debian@beaglebone:~$ ls -al /sys/firmware/devicetree/base/ocp/ | grep gpio
drwxr-xr-x  2 root root  0 Oct  4 19:20 gpio@44e07000
drwxr-xr-x  2 root root  0 Oct  4 19:20 gpio@4804c000
drwxr-xr-x  2 root root  0 Oct  4 19:20 gpio@481ac000
drwxr-xr-x  2 root root  0 Oct  4 19:20 gpio@481ae000

I've seen a LOT of examples covering gpio, and address 0x4804c000 is one used in many examples. Also, from this output we can assume these are in relation to the 4 separate GPIO banks.

 

EDIT #2:

 

So page 1574 Table 12-63.FIFO1DATA seems to indicate that FIFO1DATA is 32bits in length. Hmm, still not positive how I should map this.

 

EDIT #3:

 . . . . . heh, so revising a git project I found earlier today I found the code pasted below in a header file. Which seems to corroborate everything I've been assuming up to this point. So @@spirilis does all this seem reasonable to you ? How does balls to the walls max ADC speed seem to you ? Now, I'm wondering. If somehow all 8 channels could be used in a way to achieve 8x 200k samples a second. I know vaguely what a SAR module is, but do not know if that module can sample 200k per channel - Or not. By the way his ADC mask would not be what I'd want - Especially if using multiple channels separate. But it would be a good start for just pulling the data out of the first 12 bits.

* Analog Digital Converter Memory Registers */
#define ADC_TSC (0x44E0D000)

#define ADC_CTRL (ADC_TSC+0x40)
#define ADC_STEPCONFIG_WRITE_PROTECT_OFF (0x01<<2)
#define ADC_STEPENABLE (ADC_TSC+0x54)

#define ADCSTEPCONFIG1 (ADC_TSC+0x64)
#define ADCSTEPDELAY1  (ADC_TSC+0x68)
#define ADCSTEPCONFIG2 (ADC_TSC+0x6C)
#define ADCSTEPDELAY2  (ADC_TSC+0x70)
#define ADCSTEPCONFIG3 (ADC_TSC+0x74)
#define ADCSTEPDELAY3  (ADC_TSC+0x78)
#define ADCSTEPCONFIG4 (ADC_TSC+0x7C)
#define ADCSTEPDELAY4  (ADC_TSC+0x80)
#define ADCSTEPCONFIG5 (ADC_TSC+0x84)
#define ADCSTEPDELAY5  (ADC_TSC+0x88)
#define ADCSTEPCONFIG6 (ADC_TSC+0x8C)
#define ADCSTEPDELAY6  (ADC_TSC+0x90)
#define ADCSTEPCONFIG7 (ADC_TSC+0x94)
#define ADCSTEPDELAY7  (ADC_TSC+0x98)
#define ADCSTEPCONFIG8 (ADC_TSC+0x9C)
#define ADCSTEPDELAY8  (ADC_TSC+0xA0)

#define ADC_AVG0  (0x000)
#define ADC_AVG2  (0x001)
#define ADC_AVG4  (0x010)
#define ADC_AVG8  (0x011)
#define ADC_AVG16 (0x100)

#define ADC_FIFO0DATA (ADC_TSC+0x100)
#define ADC_FIFO_MASK (0xFFF)
Link to post
Share on other sites

ADC Driver Limitations

This driver is based on the IIO (Industrial I/O subsystem), however this is the first release of this driver and it has limited functionality:

  1. No HW trigger Support. Currently only supporting software trigger.
  2. Limited number of samples in continuous capture mode. (Only 1528 samples per capture)
  3. Limited maximum sample rate in continuous mode: 8K samples / second.
  4. Simultaneous capture on multiple ADC channels is not supported. Currently only supports continuous capture on a single ADC input channel at a time.
  5. "Out of Range" not supported by ADC driver.

http://processors.wiki.ti.com/index.php/AM335x_ADC_Driver's_Guide

 

This by the way, I've learned is outdated information. The only ADC driver limitation right now is that I believe it does not support "out of range values". Whatever that means - heh.

Link to post
Share on other sites

So, I got continuous mode working from userspace, without using mmap(). I have confirmed 15k samples a second so far, and am working on improving that. In fact, I can actually read out of the ADC at like 800Khz, but obviously many of those samples are redundant. Seems to me so far, that max *correct* Sample rate from userspace the way I have things setup is around ~35Khz. Thats read, printf() to screen every 100,000 samples, for 2 million iterations.

 

Still testing . . .

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...