libpruio (fast and easy D/A - I/O)

A new library called libpruio is availble to support digital input and output as well as analog input on Beaglebone (black) hardware. It uses software running on a PRUSS to configure and control the devices

  • Control Module (pinmuxing)

  • GPIO 0 to 3 (digital IO)

  • TSC_ADC_SS (analog input)

The API is designed for easy usage, but also for fast execution speed. No need for root privilegues or further device tree overlays (just a single overlay to start the PRUSS).

It’s compiled by the FreeBASIC compiler, but also includes a wrapper to be used with C compilers. The package contains example code in both languages. Development and testing has been done on a BeagleboneBlack under Ubuntu 13.10.

Find more informations at

This looks pretty awesome from the surface. You should register at http://beagleboard.org/project. Do you want this included the default image?

Thanks for the registration tip / link, done.

And thanks for your positive statement. I hope you find some time to look under the hood. Would be nice if you can run some of the pre-compiled examples and tell us your thoughts.

The licences are LGPLv2 / GPLv3, so it could get in to the default image. I’d like to get some feedback and fix the major bugs, first. Is there a deadline?

No particular deadline as we already shipped the Rev C image. We will push a bug fix version over the next 2-3 weeks as reports come in. If we don’t squeeze in there, there’s always another release coming.

No set deadline. We should be doing new releases multiple times this
summer as issue reports and feature requests start coming in on Rev C
as we get more users.

For the default image we need the binary libpruio.so (not sure about libpruio.a). And we need the headers. Currently there’re 4 headers, but I think I should create an all-in-one version called pruio.h and drop the documentation comments to save some memory.

Where to send the fine-tuned files? (Please be patient, that’s my first project here.)

Who cares about the dependencies (libprussdrv)?

Version 0.0.2 is out now:

  • added: new example button.bas (.c)
  • bugfix: gpio_get() returns correct value now
  • cosmetic: pruio_c_wrapper.h contains headers pruio.h and pruio.hp (all-in-one)
  • cosmetic: minor changes in documentation

Hi TJF,

I just managed to compile and run the libpruio example apps on beaglebone white PRU.

I was experimenting with the button.c, trying to set the pin 8_07 as active high, using →

if (pruio_gpio_set(io, PIN, PRUIO_IN_0, PRUIO_LOCK_CHECK)) { 
    printf("failed setting PIN (%s)\n", io->Errr); break;}

But, when I compile and run it, I still see its ‘1’.

./analyse reveals->

P8_07, GPIO 2/02: input, pullup

Am I missing something?

Forgive me if this sounds like a stupid question. Please help.

Thanks and regards,
Shoaib Ahmed.

Hi Shoaib Ahmed,

thanks for your interrests in libpruio.

Hey TJF,

I’m currently working on underwater ultrasonic sound and wonder if you think that with your library can produce constant and reliant ADC input at sample rates of 200Khz per pin. On top of that I’m interested to see version 0.2 and the ring buffer you’re been talking about.

Since it seems that you’ve already been able to produce results with the ring buffer, I only need to read ADC input right now and I’m on a tight schedule right now I would like to ask you if you want to provide a test version of said feature. I can provide the results of the tests.

Greetings,
Nils Kohrs

Hello Nils!

Hey TJF,

I’m currently working on underwater ultrasonic sound and wonder if you think that with your library can produce constant and reliant ADC input at sample rates of 200Khz per pin

The PRUSS code is ready to work at that speed. Unfortunately the BBB hardware (TSC_ADC_SS) is limited to a maximum total sampling rate of 160 kHz (one channel, software triggered start). If you need more than one channel, you’ve to add some delay for switching (multiplexing) the input.

On top of that I’m interested to see version 0.2 and the ring buffer you’re been talking about.

The code is ready (95 %). I’m working on the documentation (any help is welcome).

Since it seems that you’ve already been able to produce results with the ring buffer, I only need to read ADC input right now and I’m on a tight schedule right now I would like to ask you if you want to provide a test version of said feature. I can provide the results of the tests.

Beta testing is welcome. Please tell me if you’re still interested (in the light of the hardware limitations).

BR

libpruio-0.2 is out now:

New:

  • Ring buffer (RB) run mode (samples analog input continously).
  • PWM output in IO and RB mode (variable frequency and duty cycles).
  • CAP input in IO and RB mode (analyses frequency and duty cycles of a pulse train).
  • New examples pwm_adc, pwm_cap, rb_oszi.
  • Subsystem control (enable or disable single subsystems at run-time).
  • Device tree overlay included (universal pinmuxing at run-time).
  • Tools included to create, compile and install universal or customized device tree overlays.
  • Advanced error messages from constructor.

Changes:

  • Completely renewed source code (modular now, for easier expansions).
  • Completely renewed documentation (interferences between C and FB source solved).
  • API adapted to modular structure (see file migration.txt.
  • Version 0.0 examples adapted (1, analyse, button, io_input, sos, stepper, oszi, triggers).
  • Adaptions for new FreeBASIC compiler fbc-1.00.
  • Access to all subsystem registers supported.

Bugfixes:

  • Pinmuxing now available.
  • GPIO output fixed (former gpio_set sometimes skipped a setting).

Download (as in first post)

http://www.freebasic-portal.de/downloads/fb-on-arm/libpruio-325.html

TJF, It would be nice if you’re going to link to a site to a majority of English speakers, that the link you give to us links to a written English page.

At minimum you’re posting in English so yeah, be nice to us.

William, sorry, my fault!

“us” can find the ‘brand new’ English project page at

http://beagleboard.org/project/libpruio/

Don’t hesitate to ask if you need further assistance on downloading.

Hey TJF,

Nice to see the update, I really happy with the new features :slight_smile:

What would be the highest consistent samplerate possible with the c wrapper with 1 channel. Using the RB would be the nicest but using MM is also an option if the samplerate is higher with that.

I think if I know that I could figure out the rest myself, however it would be nice to have sample code of config&start rb/mm(possibly on max samplerate) and data retrieving in c.

Greetings,
Nils Kohrs

Hello Nils,

thanks for feedback!

The maximum sample rate is limited by the ADC subsystem. It’s 159 kS/s (15 cycles @ 2.4 MHz + safety buffer) for a single step (channel). It neither matters if you operate in RB or MM mode, nor if you use FB or C compilers.

For me, writing C examples is exhausting and time consuming. For an experianced C programmer it should be easy to translate the FB examples rb_oszi and triggers, where you can find the code you’re loocking for. (I’d appreciate it if I can include such a translation in to the documentation.)

For maximum sample rate you’ve to configure a customized step with no delays and no avaraging. And you’ve to activate this step only, like

`

pruio_adc_setStep(Io, 9, 4, 0, 0, 0); // step 9 for AIN-4

pruio_config(Io, Samples, 1 << 9, 6285, 4); // ‘1 << 9’ → step 9, ‘6285’ ns → 159.1 kHz

pruio_rb_start(Io);

`

BR

Hey TJF,

with 6285 ns I was only able to set the amount of samples to 1 without getting an error. With 6290 works everything fine.

Here is some c samplecode for you, it keeps writing all the samples to a file in burst of 1000000/4 samples per writing burst.

`
#include “unistd.h”
#include “stdio.h”
#include “…/c_wrapper/pruio.h”

//! The main function.
int main(int argc, char *argv)
{
FILE
oFile;
uint8 bDiv = 4, bStep;
uint32 bSize = 1e6;
uint32 bsSize = bSize/bDiv;
oFile = fopen(“output”,“wb”);
pruIo io = pruio_new(PRUIO_DEF_ACTIVE, 0x98, 0, 1); //! create new driver structure
pruio_adc_setStep(io, 9, 4, 0, 0, 0); // step 9 for AIN-4
if (pruio_config(io, bSize, 1 << 9, 6290, 0)){ // ‘1 << 9’ → step 9, ‘6285’ ns → 159.1 kHz
printf(“config failed (%s)\n”, io->Errr);}
else{
pruio_rb_start(io);
sleep(1);
while(1){
while(io->DRam[0] < (bStep+1) * bsSize && io->DRam[0] > bStep * bsSize){
sleep(1);
}
printf(“start writing %u\n”,bStep
bsSize);
fwrite(io->Adc->Value, sizeof(uint16), 16000, oFile);
printf(“end writing %u\n”,(bStep+1)*bsSize);
bStep = bStep+1 < bDiv ? bStep+1 : 0;
}
}
pruio_destroy(io);
return 0;
}
`

Greetings,
Nils Kohrs

Hello Nils,

thanks for the code. I think about including it in the libpruio examples folder, but your main loop is endless and the user cannot abort the program. (Shouldn’t the file get closed?) Perhaps I can adapt it a bit.

Regarding the ADC speed I made some further testing and it seems that I mis-interpreted the TRM. The ADC subsystem can sample at least at 200 kS/s. This speed also works for multiple channels. Find an example of four channels at 44.1 kHz in this post. An overall sampling rate of 200 kHz was also possible (four channels).

So the limiting in the current libpruio-0.2 is too much on the safe site. If you don’t want to wait for the next version, you can adapt the code by yourself (FreeBASIC compiler required). Replace in file pruio_adc.bas in function PruIo.configure(…) the lines

`
d *= (Conf->ADC_CLKDIV + 1) * 417 '417 ≈ 1000000 / 2400 (= 1 GHz / 2.4 MHz)
d += 30 ’ PRU cycles for restart [GHz]
IF Tmr <= d THEN .Errr = @“sample rate too big” : RETURN .Errr

`

by the following code

`
d = (d * (Conf->ADC_CLKDIV + 1) * 1000) \ 24
IF Tmr <= d ORELSE Tmr < 5000 THEN _
.Errr = @“sample rate too big” : RETURN .Errr

`

You may play a bit with the absolute value 5000. On my BBB the timing is OK up to a frequency of 240 kHz (4165). But this may vary from board to board.

Hey TJF,

I’ve got it compiled and working. I can’t yet test if the adc keeps up since I the function generator we’ve ordered got out of stock… However I changed my code a bit so it would close the files and the whole program has a end statement. It’s currently 1 channel at ~220 kS/s. I haven’t pushed it further because I don’t know what will happen. With my understanding of the PRU I guess the PRU can’t break anything on the BBB while doing that, but I don’t know so I don’t want to push my luck.

`
#include “unistd.h”
#include “stdio.h”
#include “…/c_wrapper/pruio.h”

//! The main function.
int main(int argc, char *argv)
{
FILE
oFile;
uint8 bDiv = 4, bStep;
uint32 bSize = 1e6;
uint32 bsSize = bSize/bDiv;
uint8 cycles = 2;
char fName[12];
int i = 0;

pruIo io = pruio_new(PRUIO_DEF_ACTIVE, 0x98, 0, 1); //! create new driver structure
pruio_adc_setStep(io, 9, 4, 0, 0, 0); // step 9 for AIN-4
if (pruio_config(io, bSize, 1 << 9, 4545, 0)){ // ‘1 << 9’ → step 9, ‘6285’ ns → 159.1 kHz
printf(“config failed (%s)\n”, io->Errr);}
else{
pruio_rb_start(io);
sleep(1);
for(i=0; i<cycles; i++){
sprintf(fName, “output.%u”,i);
oFile = fopen(fName,“wb”);
while(bStep<bDiv){
while(io->DRam[0] < (bStep+1) * bsSize && io->DRam[0] > bStep * bsSize){
sleep(1);
}
printf(“writing samples %u-%u\n”,bStep
bsSize, (bStep+1)*bsSize);
fwrite(io->Adc->Value, sizeof(uint16), 16000, oFile);
bStep++;
}
bStep=0;
fclose(oFile);
}
}
pruio_destroy(io);
return 0;
}

`

Greetings,
Nils Kohrs

Whew ! If you’re looking for “fast”, you’re not going about it the right way.

  1. Nested loops are bad.

  2. A file open, and close every cycle on the outer loop ? Is that really necessary ? Big time performance hit.

  3. spintf() as I recall is a fairly slow function.

A few things you can do to fix these issues.

Remove the nested loops and replace with a, if/else control block. Make the conditions checks that are most likely to pass more often - first. Also, simplify your math. If the numbers are going to be consistent, and non changing. Hard code the values. Multiplication and division on fast moving code will slow you down. Especially if done on floating point numbers. So unless I missed an assignment somewhere . . .

``while(io->DRam[0] < (bStep+1) * bsSize && io->DRam[0] > bStep * bsSize) {

}

if bsStep which seems to be left unassigned ( 0 ) + 1 = 1

1 * is going to be that . . . Some compilers may optimize this out, but why ? It also makes the code less readable.

Also . . .

fwrite(io->Adc->Value, sizeof(uint16), 16000, oFile);

Is a potential performance hit. Quite honestly I am not sure if sizeof(uint16) would be checked once at compile time, or if it would be checked every cycle at run time. But you can “fix” this simply by hard coding the value. Make it a constant if you feel it makes the code more readable.

Open the file once before the loop, and close it after the loop is done. That is, if at all possible. Also there is a “trap for new players”, in that if the application exits abnormally, the file may still be left opened. So a check when the application first starts may be in order.

As an aside, you can simplify the whole application by just gathering raw data, and shifting it out to the the general purpose processor ( ARM ). Which then the main processor can be made to do all the heavy math “lifting”.

Anyway I hope this is useful information, and my posting is not meant to demean, or otherwise come across as “smart-ass”.