Jump to content
Forum sending old emails Read more... ×
Sign in to follow this  
yyrkoon

Beaglebone black on board ADC

Recommended Posts

Well apparently the userspace driver is dog slow. Using read() and waiting for len != -1 I get a whopping 300-400 samples a second. heh. Introducing usleep() or nanosleep() does not help things either for a blocking wait. I have not looked at the implementation code for either sleeps, but apparently they're using system calls, or are system calls, or perhaps I'm bumping up on PREEMPT RT kernels latency ? *shrug*

 

Not sure what I want to do now. I've been thinking that /dev/mem + mmap() would be fast. And I'm still fairly sure it will be. However . . . I wont be able to use the userspace driver to achieve good speeds . . . so will have to poke, and prod the module directly I suppose ? Need something to eat . . .

Share this post


Link to post
Share on other sites

Here's the current code I've been dorking around with for the last couple hours. Never mind that nanosleep() in there I was just testing various values as I've never used it before. It actually slows things down considerably . .

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

int main()
{
        const char *fname = "/dev/iio:device0";
        int count = 1, fd, len;
        int sample;
        int *adc;

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

        while(count <= 2000000){

                len = read(fd, adc, sizeof(adc));
                if(len == -1){
                        nanosleep((const struct timespec[]){{0, 1L}}, NULL);
                }

                sample = (int)(*adc & 0xFFF);
                
                if(count % 100000 == 0)
                        printf("%d\t%d\n", count, sample);

                count++;
        }
        close(fd);

        return  0;
}

By the way for those of you who would like to experiment with this code. If you remove "| O_NONBLOCK", the reads will change to blocking calls, and use considerably less processor.

Share this post


Link to post
Share on other sites

Code cleanup and compile, output.

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

int main()
{
        const char *fname = "/dev/iio:device0";
        int count = 1, fd, len, sample;
        int *adc;

        fd = open(fname, O_RDONLY | O_NONBLOCK);
        if(fd == -1){
                printf("error: %s %d\n", strerror(errno), errno);
                exit(1);
        }
        while(count <= 2000000){
                /* len should never be -1 on bocking read. */
                len = read(fd, adc, sizeof(adc));
                sample = (int)((*adc) & 0xFFF);
                 if(count % 100000 == 0)
                        printf("%d\t%d\n", count, sample);
                count++;
        }
        close(fd);
        return  0;
}
debian@beaglebone:~$ gcc test.c -o test
debian@beaglebone:~$ sudo su
root@beaglebone:/home/debian# time ./test
100000  3989
200000  3989
300000  3990
400000  3993
500000  4006
600000  3980
700000  3992
800000  4000
900000  4006
1000000 3985
1100000 3998
1200000 3996
1300000 3998
1400000 4008
1500000 4001
1600000 3996
1700000 3996
1800000 4010
1900000 3995
2000000 3995

real    0m2.171s
user    0m0.430s
sys     0m1.720s

~921234.4541685859 Samples a second. And of course many duplicate reads.

 

Information:

http://elinux.org/images/6/65/Spruh73c.pdf- Page 1574 table 12-63

http://processors.wiki.ti.com/index.php/Linux_Core_ADC_User's_Guide#Beaglebone.2FBeaglebone_Black- For setup

http://processors.wiki.ti.com/index.php/Linux_Core_ADC_User's_Guide#Continuous_Mode- Various information: types, file location, etc.

 

Share this post


Link to post
Share on other sites

So, last night I was running into issues related to values not comparing correctly. When implementing continue, the code would just run in an infinite loop.

 

The culprit -> int *adc; /* Should be an int, not a pointer to int. */

 

I'll assume this is why a certain marsupial was seen in this general vicinity once or twice yesterday ;) With the above in mind I refactored this out, and while at it I went ahead and implemented channel tracking too. So with the code I'll paste below, I'll consider this horse beaten to a pulp, and I'll move on to mmap() today I think.

 

Code:

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

int main()
{
        const char *fname = "/dev/iio:device0";
        int count = 0, fd;
        unsigned int adc, sample, index;


        const int iden_mask = 0x00070000;
        const int data_mask = 0x00000FFF;

        fd = open(fname, O_RDONLY | O_NONBLOCK);
        if(fd == -1){
                exit(1);
        }
        index = 0;
        while(count <= 1000){
                read(fd, &adc, sizeof(adc));
                if(index == ((adc & iden_mask) >> 16)){
                        sample = adc & data_mask;
                        printf(" %d:%d ", index, sample);
                        count++;
                        index++;
                }
                if(index > 6){
                        index = 0;
                        printf("\n");
                }                                 
        }
        printf("\n");
        printf("%s %d\n", "Number of samples:", count * 7);
        close(fd);
        return  0;
}

Output:

debian@beaglebone:~$ gcc -Wall test.c -o test
debian@beaglebone:~$ time sudo ./test
 . . .

 0:3950  1:3956  2:3946  3:3951  4:3937  5:3954  6:3939
 0:3963  1:3963  2:3953  3:3953  4:3956  5:3942  6:3950
 0:3949  1:3953  2:3943  3:3957  4:3955  5:3963  6:3953
 0:3941  1:3944  2:3953  3:3948  4:3948  5:3949  6:3952
 0:3957  1:3942  2:3958  3:3950  4:3958  5:3952  6:3944

Number of samples: 7000

real    0m14.651s
user    0m2.620s
sys     0m11.830s

The sample rate is dismal, but at least we know the values output are reasonably unique. Or at minimum, the values should accurate. I've experimented with increasing / decreasing the buffer size, but it does not seem to have much effect. Another thing to perhaps consider at some later date. Sample averaging if likely turned on, and is probably set to 16 sample averages. I *think* that perhaps if sample averaging was turned off, reading the ADC with the above code should be much faster. However,  I am unaware of how one would set this using the iio userspace driver . . . I suppose I could start by digging into the latest documentation for iio, but this may very well not even exist, and possibly the documentation is not up to date. For the kernel I'm using. I've run into this a lot during my experimentation with the ADC's thus far. In fact, there is absolutely zero *full* documentation for userspace continuous mode on the web - That I could find. All this information was pieced together by yours truly from the above previous post( with links ), and using my brain - What's left of it . . .

 

EDIT:

By the way. changing the file descriptor to blocking, from non blocking has a small impact on sample rate, but has a HUGE impact on CPU load. Using much less CPU when blocking mode is reestablished.

Share this post


Link to post
Share on other sites

@@spirilis I've verified that at least ADC FIFO0 works from this git project: https://github.com/ehayon/BeagleBone-GPIO. I've just started testing it, but with the ADC enabled / setup through the iio userspace driver, I'm getting roughly 9413 samples a second from all 7 channels. Not sure if those values are complete or not, as the values are all over the place. However, I have nothing connected to the ADC channels, I'm just measuring the floating voltage on those pins.

 

Maybe I'll go look for an LED to connect to a channel input and then to ADC ground, and place it over the BBB's blinking USR LEDs . . .

 

By the way, currently I'm only using his AM335x.h file.

Share this post


Link to post
Share on other sites

mmap()ing /dev/mem so far is turning out great. I just borrowed that fella's am335x.h file, and went to town. Also for the record we're getting into that area where writing to files is 2x s fast as printing to screen . . .that, and the file I'm writing to is an NFS share . . . with high(figuratively) latency at times. It's noticeable enough when I compile to see the difference when it feels like being slow, or not so slow. Both of these are 200k iterations each, and both skipping AIN7 as an output.

 


. . .
 6:2609 1:0831 6:3612 2:0141 1:3921 3:1488 0:0243 5:2981 3:1685 2:1118
 5:0523 1:3068 5:2318 4:0123 3:0532 3:3336 1:1599 1:2541 0:3731 2:3157

real    0m1.656s
user    0m0.380s
sys     0m0.340s


debian@beaglebone:~$ time sudo ./test > output.txt

real    0m1.059s
user    0m0.370s
sys     0m0.030s

Anyway, even for me, this is early Alpha testing - heh

Share this post


Link to post
Share on other sites

By the way, this works without having to use device tree files, or even loading the TI ADC driver. Just direct prodding of the ADC until it gets off it's butt.

Share this post


Link to post
Share on other sites
To screen:
2000000 total iterations
 channel 0 samples: 296296
 channel 1 samples: 444444
 channel 2 samples: 296296
 channel 3 samples: 370371
 channel 4 samples: 148149
 channel 5 samples: 222222
 channel 6 samples: 222222
real    0m16.247s
user    0m3.850s
sys     0m3.140s

To file:

 2000000 total iterations
 channel 0 samples: 296296
 channel 1 samples: 444445
 channel 2 samples: 296296
 channel 3 samples: 370370
 channel 4 samples: 148148
 channel 5 samples: 222222
 channel 6 samples: 222223
real    0m5.768s
user    0m3.650s
sys     0m0.090s

~25684sps per channel, worst case. Now, I'm experiencing issues with the FIFO, which could take me a few days to iron out, as I've got other things to do  as well.

Share this post


Link to post
Share on other sites

So, I've run into a snag.

 

1 - The FIFO does not seem to be refreshing. What I mean is: between reboots, the data I'm reading seems to be repeating after some amount of values. For now, I'm assuming this is based on the length of the buffer I've set using iio:device0.

 

2 - After googling around a bit, I was reacquainted with devmem2. So while experimenting with it on the address+offset in question, writing to the FIFO buffer, *seems* to reset it. i.e. I get what appears a valid value back out. Which is 12bits== sample value, then something like 7 bits reserved, and 3 bits channel id tag, then the rest out to bit 31 all reserved. So the data seems very likely correct.

 

3 - If I write to the same address using my current iteration of code. Well it clears FIFO completely . . . Until I rerun the executable. The result is I get out one reading, and the rest is all blank data ( address+offset &= ~0xFFF ). However, the code I'm currently using uses the previously mentioned header file I found. Manually doing the math on addressing between the two apps seems different. But I'm using constants from this header file, and many seem odd to me.

 

Example:

#define MMAP_OFFSET (0x44C00000)
#define MMAP_SIZE   (0x481AEFFF-MMAP_OFFSET)
. . .
#define ADC_TSC (0x44E0D000)  // ADC base address
. . .
#define ADC_FIFO0DATA (ADC_TSC+0x100)
. . .
map = (uint32_t*)mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMAP_OFFSET);

So with the above in mind, Why in the hell would anyone use the following method to read out of the ADC?

int analogRead(PIN p) {
    init();
    
    // the clock module is not enabled
    if(map[(CM_WKUP_ADC_TSC_CLKCTRL-MMAP_OFFSET)/4] & CM_WKUP_IDLEST_DISABLED)
        adc_init();
    
    // enable the step sequencer for this pin
    map[(ADC_STEPENABLE-MMAP_OFFSET)/4] |= (0x01<<(p.bank_id+1));

    // return the the FIFO0 data register
    return map[(ADC_FIFO0DATA-MMAP_OFFSET)/4] & ADC_FIFO_MASK;
}

Why wouldn't one just mmap() the ADC base address, pass in the offset to the FIFO0 register, and mask that ?

 

@@oPossum ?

 

EDIT:

 

BY the way I did not think to add ADC_FIFO_MASK = 0xFFF.

I do not use it though as I use a constant of my own, with the same value. Just experimenting with this persons code.

Share this post


Link to post
Share on other sites

Actually the FIFO register is Actually the FIFO is 100h in size, and can store 8 such fields mentioned below.

 

  1. 12bits sample represented in a 16bit field (0-15b)
  2. 3 bits to represent channel id tag (16-19b)
  3. The rest reserved(20-31b)

I'm not 100% positive on #1 it could be 12bit data, and then 4bit reserved, but it's close enough for me to know to mask out 12 bits in either case. Yes, I've read the TRM, but am too flustered to worry about the small details atm.

Share this post


Link to post
Share on other sites

Anyway I've got my FIFO problem sorted, but still curious why anyone would use mmap() as demonstrated above. I get that perhaps some of the registers are outside of the ADC register "domain" so perhaps that is why. Either way, I seem to be back on par . . . well actually, I'm on par x2.

 

I was not setting ADC STEPENABLE bit for each pin.
 

offset 54h STEPENABLE Step Enable Section 12.5.16 Shown here http://elinux.org/images/6/65/Spruh73c.pdf#page=1484&zoom=auto,0,650.3

To screen:
debian@beaglebone:~$ time sudo ./test
. . .
 0:3883 1:3851 2:3969 3:3659 4:3680 5:3756 6:3731 7:3876
Read: 8 channels - 200000 samples per channel for a total of 1600000 samples read.

real    0m13.371s
user    0m3.510s
sys     0m2.230s
 

Piped to file:
debian@beaglebone:~$ time sudo ./test > output.txt

real    0m4.343s
user    0m3.160s
sys     0m0.070s
debian@beaglebone:~$ tail output.txt
 0:3870 1:3831 2:3755 3:3766 4:3769 5:3784 6:3775 7:3873
 0:3844 1:3846 2:3859 3:3804 4:3798 5:3795 6:3788 7:3871
 0:3800 1:3787 2:3828 3:3779 4:3779 5:3780 6:3808 7:3871
 0:3879 1:3829 2:3849 3:3800 4:3788 5:3761 6:3771 7:3871
 0:3832 1:3799 2:3831 3:3817 4:3806 5:3787 6:3791 7:3871
 0:3859 1:3780 2:3818 3:3781 4:3785 5:3779 6:3777 7:3883
 0:3900 1:3835 2:3851 3:3806 4:3802 5:3793 6:3760 7:3875
 0:3821 1:3795 2:3832 3:3785 4:3789 5:3811 6:3795 7:3875
 0:3869 1:3828 2:3840 3:3768 4:3772 5:3782 6:3771 7:3875
Read: 8 channels - 200000 samples per channel for a total of 1600000 samples read.

Share this post


Link to post
Share on other sites

Moving right along, I've decided for best "throughput", reading the ADC's would be best done via using the PRU's, with the PRU's putting the reading into memory. Where the ARM core can then grab that data, and do whatever it likes with it. This has the added benefit of offloading the ARM CPU somewhat. As I can use an interrupt between the PRU core, and the main ARM A8 processor. So the ARM core can block-wait, until the data is ready. So hopefully reducing the main processor load a good bit.

 

The Journey learning the PRU module for me, is not an easy one. Either I brush up on my ASM skills - Which would be a considerable undertaking. Then use CCS as an easy way out for programming, including ripping off example code. Or I learn the hardware better by using the cgt-pru toolchain + gcc( ARM side of things ), and hopefully learning more about the hardware. While in that process learn enough about the hardware to write my own code from scratch. Well, technically, using C for the PRU's means the best / easiest route would be to learn / use the TI app loader API / shared library . . . but I think this is the way I'm going to go with it.

 

*Lots more reading* to do . . .

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×