PRUs realtime data acquisition, I2C bus and ADC

Hi all,

I am trying to use the PRUs for real time data acquisition on the Beaglebone black (Linux debian 4.9.45-ti-r57). I have set up the PRU with remoteproc and RPMsg; everything is working fine.
The first time, I successfully captured data with the PRU using the SPI protocol and bit banging, and I have sent them to the ARM with RPMsg.

Now I want to do the same thing using the I2C protocol, which left me with some questions:

  • Is it possible to use the on-board I2C bus with the PRUs?
  • If not, I will enable the pull-up resistor on a GPIO pins using a custom device tree. But after that, I do not really understand how I can read and write the SDA line for I2C? (Contrary to the SPI protocol, I won’t have Data In and Data out lines but only one SDA line).

My final goal will be to use the on-board ADC. There is already a very interesting project PRUADC that captures data using an external ADC. I would like to use the on-board ADC, is there any way to do it ? (I am not afraid of going into complex stuff).

Thanks,

Pierrick

Hi all,

I am trying to use the PRUs for real time data acquisition on the Beaglebone black (Linux debian 4.9.45-ti-r57). I have set up the PRU with remoteproc and RPMsg; everything is working fine.
The first time, I successfully captured data with the PRU using the SPI protocol and bit banging, and I have sent them to the ARM with RPMsg.

Now I want to do the same thing using the I2C protocol, which left me with some questions:

  • Is it possible to use the on-board I2C bus with the PRUs?

Yes, and you can also control the SPI interface with the PRU as well. No need to do bit banging.

  • If not, I will enable the pull-up resistor on a GPIO pins using a custom device tree. But after that, I do not really understand how I can read and write the SDA line for I2C? (Contrary to the SPI protocol, I won’t have Data In and Data out lines but only one SDA line).

If you wish to bit bang the I2C, you need to create a state machine for I2. You start by outputting the “Address” together with “Read/Write” bit and then the “Data”, and then tristate the SDA line and wait for it to go high, which is the “Ack” from the slave device. After that, you simply toggle the clock line and read back the “Data” line.

My final goal will be to use the on-board ADC. There is already a very interesting project PRUADC that captures data using an external ADC. I would like to use the on-board ADC, is there any way to do it ? (I am not afraid of going into complex stuff).

Yes, you can access the ADC with the PRU, but not sure why you would want to do that unless you are doing some closed loop control function. If you are simply reading the ADC and sending that info to the ARM, is is better to use the IIO driver. This driver uses DMA to transfer the samples to memory and the timing between the samples is controlled via the device tree.

Regards,
John

Thank you for your quick answerJohn!

So if I understand well, I can use the PRUs with the on-board SPI and I2C interface ? I have not found any similar project that can help me on this. Is it done using the L2 and L3 interconnect ? I am highly interested by using those on board interface with the PRUs, but it seems that I still need to learn a lot about this.

For the ADC, my future application is going to be time critical, by this I mean that I want to sample data at around 5khz to 10khz. Is the IIO driver able to execute real-time data acquisition while the ARM is processing the data?

Thanks

Pierrick

Thank you for your quick answerJohn!

So if I understand well, I can use the PRUs with the on-board SPI and I2C interface ? I have not found any similar project that can help me on this. Is it done using the L2 and L3 interconnect ? I am highly interested by using those on board interface with the PRUs, but it seems that I still need to learn a lot about this.

The PRU has access these peripherals in the same way the ARM does. There is a github project that took the Starterware code for these peripherals and adapted them for the PRU.

For the ADC, my future application is going to be time critical, by this I mean that I want to sample data at around 5khz to 10khz. Is the IIO driver able to execute real-time data acquisition while the ARM is processing the data?

The IIO driver can sample at 200KHz and higher (don’t remember the upper limit). When you say real-time, you need to explain what you mean. For me, it means deterministic and in the case of the IIO driver, it samples the ADC at a constant sample rate, even if the CPU is under load, so it is deterministic. Now if you are doing close loop control, then you have the Linux Kernel interrupt latency to deal with and that is deterministic to about 1mS.

Regards,
John

Thanks John for you answer, I was quit busy last week so I worked on this during the Weekend.

Unfortunately, I was not able to find a project that is using the SPI and I2C interface with the PRU, I only found this one :
https://github.com/chanakya-vc/PRU-I2C_SPI_master/wiki/SPI-Master-Controller
But it is using bit banging for the SPI part and not using the on-board pull-up resistors for the I2C part.

Concerning the ADC, I’ll have a loook at the UIIO drivers in the coming days it seems that it meets my need in term of real-time acquisition.

Regards,

Pierrick

Like I said, it was based on Starterware, so search Github for starterware and you will see a project starterware_PRU. It does use the mcspi, so it is not a bitbang implementation.

Regards,
John

Thanks John,

I am now working with the starterware_PRU but I did not find examples for using the McSPI with the PRU, do you think it will be hard to adapt the initial code to the PRU ?

Thanks

Pierrick

The IIO ADC driver can run at 800K samples per second. Here is the patch that made that possible.

https://patchwork.kernel.org/patch/9391487/

I can confirm that I have tested the driver at 800Ksps and it works fine as long as you have a proper low impedance source for each ADC channel. CPU utilization was about 5% if I recall and that was probably used by the iiod daemon, which I used to display the waveform on a remote Linux app.

There is example code in the original Starterware for McSPI, which should work fine if you are using the PRU low level drivers.

Regards,
John

Hi John,

Sorry for the late answer, I had hard time using the PRUs and I am now going to use the IIO ADC driver, I am able to read the sample with the cat command in /sys/bus/iio/devices/iio:device0/in_voltage3_raw
However I am not able to use Libiio in order to read data from a user space application, I am reading (nil) instead of my data. Do you have any idea of where does the problem comes from ?

Here is the code I am using in the user space :

#define _BSD_SOURCE
#define _GNU_SOURCE
#define _DEFAULT_SOURCE

#include <cdk/cdk.h>
#include <locale.h>
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#ifdef APPLE
#include <iio/iio.h>
#else
#include <iio.h>
#endif

struct iio_context *ctx;
struct iio_device *dev;
struct iio_channel *ch;

int main()
{
ctx = iio_create_default_context();
dev = iio_context_get_device(ctx, 0);
ch = iio_device_get_channel(dev, 3);

iio_device_attr_write_longlong(dev, “sample_rate”, 100);
iio_channel_attr_write_double(ch, “scale”, 1);

iio_channel_enable(ch);

char *a = iio_device_get_data(dev);
printf("%p\n", a);

iio_channel_disable(ch);

return 0;
}

Thanks

Pierrick

Look at the kernel source under tools/iio for examples on how to use IIO.

Hi John,
Thanks for the help, I looked into the iio_generic_buffer.c example and i patched it to disable the hardware triggers thanks to the patch presented on this page : https://www.teachmemicro.com/beaglebone-black-adc/ I am now able to reader a buffer from the different channel.
However I have 2 majors questions that remains:

  1. I only want to use on channel, then I do not want the ADC to sample the other one so that i’ll have the maximum sampling rate. What is the best way to disable the channel? If I do not enable them in iio_generic_buffer.c I am not sure that the ADC is not going to sample this channel or not (well, I think it wont sample but I prefer to be sure). Is it preferable to not mention them on the devicetree so that Linux wont know that there are multiple channels on the ADC? This part is not very clear for me.

  2. To change the sample frequency of the ADC you mentioned that it is done using the device tree however I did not find any argument on the ADC devicetree to change the sampling frequency. I read the discussion you had on this post (https://patchwork.kernel.org/patch/9391487/ ) but it is not clear if the frequency setting is done using the kernel module or devicetrees. Could you explain me this please?

Thanks a lot

Pierrick

Hi John,
Thanks for the help, I looked into the iio_generic_buffer.c example and i patched it to disable the hardware triggers thanks to the patch presented on this page : https://www.teachmemicro.com/beaglebone-black-adc/ I am now able to reader a buffer from the different channel.
However I have 2 majors questions that remains:

  1. I only want to use on channel, then I do not want the ADC to sample the other one so that i’ll have the maximum sampling rate. What is the best way to disable the channel? If I do not enable them in iio_generic_buffer.c I am not sure that the ADC is not going to sample this channel or not (well, I think it wont sample but I prefer to be sure). Is it preferable to not mention them on the devicetree so that Linux wont know that there are multiple channels on the ADC? This part is not very clear for me.

First, you have to enable each channel for it to be placed in the buffer. Second, you have to setup the size of the buffer and then enable the buffer to start filling the buffer with samples. When you enable the buffer, tiadc_buffer_postenable() is run which only scans those channels you enabled.

  1. To change the sample frequency of the ADC you mentioned that it is done using the device tree however I did not find any argument on the ADC devicetree to change the sampling frequency. I read the discussion you had on this post (https://patchwork.kernel.org/patch/9391487/ ) but it is not clear if the frequency setting is done using the kernel module or devicetrees. Could you explain me this please?

The sampling frequency is dependent on the Open Delay, Sample Delay and Conversion Time. If you look at the file linux/mfd/ti_am335x_tscadc.h line 145, there is an explanation of this.

Regards,
John

Hi John,
Thanks for the help, I looked into the iio_generic_buffer.c example and i patched it to disable the hardware triggers thanks to the patch presented on this page : https://www.teachmemicro.com/beaglebone-black-adc/ I am now able to reader a buffer from the different channel.
However I have 2 majors questions that remains:

  1. I only want to use on channel, then I do not want the ADC to sample the other one so that i’ll have the maximum sampling rate. What is the best way to disable the channel? If I do not enable them in iio_generic_buffer.c I am not sure that the ADC is not going to sample this channel or not (well, I think it wont sample but I prefer to be sure). Is it preferable to not mention them on the devicetree so that Linux wont know that there are multiple channels on the ADC? This part is not very clear for me.

  2. To change the sample frequency of the ADC you mentioned that it is done using the device tree however I did not find any argument on the ADC devicetree to change the sampling frequency. I read the discussion you had on this post (https://patchwork.kernel.org/patch/9391487/ ) but it is not clear if the frequency setting is done using the kernel module or devicetrees. Could you explain me this please?

Looking at this a little more, there is a mistake in the ADC DT file BB-ADC-0A00.dts. The maximum averaging is 16, not 0x16.

The line
ti,chan-step-avg = <0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16>

should be changed to
ti,chan-step-avg = <16 16 16 16 16 16 16 16>
Fortunately, the driver does a range check and sets the value to 16.

ti,chan-step-avg = <1 1 1 1 1 1 1 1> /* 2 sample average */
ti,chan-step-opendelay = <0 0 0 0 0 0 0 0>
ti,chan-step-sampledelay = <0 0 0 0 0 0 0 0>

To achieve a conversion rate of 800 KS/s

From ti_am335x_tscad.c, 1 + (1 + 13) * 2 = 30 cycles

The ADC uses a 24 MHz clock, so 1/24,000,000 * 15 = 800 KS/s

You could increase the sampling rate to 1.6MS/s by changing the average to 0, which means there is no averaging. To achieve this, the minimum number of cycles for a conversion is 15 (12.3.7 of the AM3358 Technical Reference Manual)

1 + (1 + 13) * 1 = 15 cycles

which will give you 1.6 MS/s

Regards,
John

Thanks John!

Looking at this a little more, there is a mistake in the ADC DT file
BB-ADC-0A00.dts. The maximum averaging is 16, not 0x16.

The line
ti,chan-step-avg = <0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16>

should be changed to
ti,chan-step-avg = <16 16 16 16 16 16 16 16>
Fortunately, the driver does a range check and sets the value to 16.

Fixed: https://github.com/beagleboard/bb.org-overlays/commit/1328a31062041fcc9a3103782eb6b0b86ef76084

Regards,

Hi John,
Thanks a lot for this very complete answer ! I think I understand it now, the last point I am not sure about is:

ti,chan-step-avg = <1 1 1 1 1 1 1 1> /* 2 sample average */

I went through 12.3.3 of the AM3358 Technical Reference Manual and it seems that the setting the averaging value to 1 disable the averaging (instead of setting it to 2) am I right?
Thanks again for your help

Hi John,
Thanks a lot for this very complete answer ! I think I understand it now, the last point I am not sure about is:

ti,chan-step-avg = <1 1 1 1 1 1 1 1> /* 2 sample average */

I went through 12.3.3 of the AM3358 Technical Reference Manual and it seems that the setting the averaging value to 1 disable the averaging (instead of setting it to 2) am I right?
Thanks again for your help

If you look in the AM3358 TRM, it says 0 will disable averaging and 1 will average over two samples.

From the TRM

Number of samplings to average:
000 = No average.
001 = 2 samples average.
010 = 4 samples average.
011 = 8 samples average.
100 = 16 samples average.

Regards,
John

Yes, sorry, my mistake I was looking to section 12.3.3 in the TRM instead on looking to the STEPCONFIG register description sections.

John,
I am still working on this project. I have studied the iio_generic_buffer.c and adapted it to save the result of the sampling in a csv file. I was testing it today with a waveform generator in the following configuration:

  • signal frequency : 1Hz (sin function)
  • signal amplitude: 1V
  • averaging 8 times

I took 1000 samples (buffer length 1000), I then displayed the result and instead of a constant horizontal line, i had a sinus-like curve (attached as 8 average sin 1Hz 1000 samples).

I tried with higher frequencies such as 20khz and i have a similar result but with a “pulse” which, as far as understand corresponds to the signal I want to sample ( attached as 8 average sin20kHz).

In both cases, I have some “waves” that does not seems to be linked to the frequency of the signal…

I guess something is wrong in my iio_generic_buffer.c like i am not reading the data at the right place… but i do not really know what kind of error I should look for, I am also attaching it to this post (maybe it can help).
The changes I made are from the patch on this page : https://www.teachmemicro.com/beaglebone-black-adc/ and I also modified the process_scan(), print2byte() function and the main (~line 650) to save the value in a csv file instead of just printing them.

If you can give me an hint of where my error could be it would be very helpful.

Thanks a lot,

Pierrick

8 average sin1hz 1000sample.png

8 average 20kHz.png

iio_generic_buffer_back.c (17.4 KB)

If you are using the latest kernel, you must use u-boot overlays as the kernel capemanager as discussed in that article is no longer available.

BTW, you can run iiod on your BBB and then use IIO Oscilloscope on you desktop which can connect to your BBB and display the waveforms.

https://wiki.analog.com/resources/tools-software/linux-software/iio_oscilloscope