ADC through PRU and Kernel Driver at the Same Time

When trying to use PRU to control ADC, it is suggested to disable the kernel driver
&tscadc {
// status = “okay”;
status = “disabled”;

}
Is it possible to use channel 0-3 from PRU and channel 4-7 from kernel? Such as set as:
&tscadc {
status = “okay”;
adc {
ti,adc-channels = <4 5 6 7>;
};
};
The reason is that the two sets of ADC usage are quite different, one is much slower and doesn’t care about timing much than the other.

The kernel driver does not support sharing the ADC, nor does the ADC itself support this in any reasonable way…

You program the ADC with a sequence of (up to 16) measurement steps to perform and it will execute these steps in an infinite loop, streaming the measurements into a single fifo which therefore requires a single consumer (either PRU or the linux kernel) to process all of these measurements. This also means it’s not really possible to have different sample rates for different channels, other than by configuring multiple measurement steps for the same ADC channel.

The ADC does have a mechanism for interjecting one or more measurements based on a hardware trigger, whose results use a separate fifo. This is intended for touchscreen pen measurements, with the hardware trigger being the pen down event, but the mechanism might be abusable for other use-cases. But this is likely to be quite complicated and would also require customizing the kernel driver. I wouldn’t suggest going down this road.

Just have PRU process all the measurements and make channels 4-7 available for userspace to read e.g. via shared memory.

Thank you so much for your prompt reply. You explained it clearly. I was also thinking using PRU0 to read channel 0-3 and PRU1 to read channel 4-7 with different speed setup, that won’t work either because of the single fifo as you mentioned, right? I’m using GitHub - pgmmpk/bbb_pru_adc: Stream capture of analog sensor data on BeagleBone (Black) using PRU code, in firmware.c
uint16_t adc_read(adc_t *padc, uint16_t *values) {
case 3: // all 8 channels are ready in fifo0
for (i = 0; i < 8; i++) {
data = ADC_TSC.FIFO0DATA;
channel = (data >> 16) & 0xf;
padc->value[padc->index[channel]] = data & 0xfff;
}
I was thinking why all 8 channels are ready in the same fifo and now you explained it.
More questions:

  1. through kernel there are only 7 channels available but through PRU, there are 8 channels available, why is that and where can I plug in the channel 8 (it does not exist on P8 P9 header)?
  2. TSC cannot be used if using PRU to control ADC. Which TSC does it mean here? There is a 4.3" LCD with touch screen cape BB-CAPE-DISP-CT43, can I use that if I’m using PRU to control ADC?
  3. The above code is sending message back to userspace directly after each read and doesn’t put them in shared memory, will that good enough and keep up? So far for 2khz 3 channels seems fine. Adding more channels that only need 1hz rate are wasting the bandwidth for the fast ones.
  4. Due to the max ADC rate is limited, I cannot achieve 1mhz requirement. Any possible way of achieving it using BB Black, BB AI etc? BB AI cannot even using PRU to control ADC because it’s through I2C. Will it work to use PRU to control SPI chip register directly to read sensor that way (SPI is not on the PRU pin out)?

PRU0 to read channel 0-3 and PRU1 to read channel 4-7 with different speed setup, that won’t work either because of the single fifo as you mentioned, right?

Correct, exact same problem.

through kernel there are only 7 channels available

Ehh no, all 8 channels are available via the kernel driver, and the default configuration enables all 8 channels. See the comments in that file for a description of the eight channels.

TSC cannot be used if using PRU to control ADC. Which TSC does it mean here?

The AM335x ADC is a resistive touchscreen controller in addition to being a general-purpose ADC, hence it’s also called ADC/TSC.

There is a 4.3" LCD with touch screen cape BB-CAPE-DISP-CT43, can I use that if I’m using PRU to control ADC?

Yes since (based on its dts) it doesn’t use the ADC in any way. It uses some capacitive touchscreen controller connected via I²C.

The above code is sending message back to userspace directly after each read and doesn’t put them in shared memory, will that good enough and keep up?

How and when you choose to communicate data to userspace is up to you. Presumably sending the values to userspace is just an example anyway and you’re in fact going to do some sort of processing of the data on PRU? Since if you’re just going to stream the data to userspace then I don’t understand why you’re involving PRU at all, it would be more efficient to use the kernel driver instead.

Adding more channels that only need 1hz rate are wasting the bandwidth for the fast ones.

Nobody is forcing you to send those channels to userspace at a high rate. I suggested shared memory since it means userspace can read the values at whatever pace it wants rather than whatever pace PRU is receiving the measurements, but like I said… How and when you choose to communicate data to userspace is up to you.

Due to the max ADC rate is limited, I cannot achieve 1mhz requirement.

The max sample rate supported by the ADC is 200 kHz divided by the number of channels enabled, e.g. 25 kHz when all 8 channels are enabled. See the comments in BB-ADC-00A0.dts for details.

BB AI cannot even using PRU to control ADC because it’s through I2C.

Correct.

Will it work to use PRU to control SPI chip register directly to read sensor that way (SPI is not on the PRU pin out)?

I’m not sure what you’re asking… are you talking about connecting an external ADC via SPI? PRU can directly use an SPI controller (instead of using the kernel driver for that SPI controller) or you could use PRU assembly to bit-bang the protocol through PRU’s fast direct GPIO. For the first option, keep in mind that the max SPI clock frequency is 48 MHz so you’re probably not going to be able to achieve 1 MHz for 4 channels that way.

Thank you so much for your reply. I tried to reply with the good format but I couldn’t block quote the whole question and answers together, so the first block is my question and the 2nd block is your answer. Hope that’s not too confusing.

through kernel there are only 7 channels available

Ehh no, all 8 channels are available via the kernel driver, and the default configuration enables all 8 channels. See the comments in that file for a description of the eight channels.

I saw that there were 8 channels in the dts as well but somehow only 7 (AI0-AI6) are available on P9 header and it was also mentioned 7 channels in https://beagleboard.org/support/bone101. How do I access channel 8?

There is a 4.3" LCD with touch screen cape BB-CAPE-DISP-CT43, can I use that if I’m using PRU to control ADC?

Yes since (based on its dts) it doesn’t use the ADC in any way. It uses some capacitive touchscreen controller connected via I²C.

Thanks for confirming, I looked at the same dts and didn’t find it use any ADC either. But don’t know what TSC is meant for on the BB Black.

How and when you choose to communicate data to userspace is up to you. Presumably sending the values to userspace is just an example anyway and you’re in fact going to do some sort of processing of the data on PRU? Since if you’re just going to stream the data to userspace then I don’t understand why you’re involving PRU at all, it would be more efficient to use the kernel driver instead.

The reason that I use PRU is because I need to read them deterministically . Using kernel driver is super inconsistent, sometimes it has much bigger interval from one to the next sampling. I’ll do data processing in userspace.

Adding more channels that only need 1hz rate are wasting the bandwidth for the fast ones.

Nobody is forcing you to send those channels to userspace at a high rate. I suggested shared memory since it means userspace can read the values at whatever pace it wants rather than whatever pace PRU is receiving the measurements, but like I said… How and when you choose to communicate data to userspace is up to you.

Are there any good code example of using PRU shared memory? I had trouble using prudebug from the cookbook to see the memory map with “ERROR: could not map memory”.

Will it work to use PRU to control SPI chip register directly to read sensor that way (SPI is not on the PRU pin out)?

I’m not sure what you’re asking… are you talking about connecting an external ADC via SPI? PRU can directly use an SPI controller (instead of using the kernel driver for that SPI controller) or you could use PRU assembly to bit-bang the protocol through PRU’s fast direct GPIO. For the first option, keep in mind that the max SPI clock frequency is 48 MHz so you’re probably not going to be able to achieve 1 MHz for 4 channels that way.

What I’m talking about is to use SPI to read the sensor (the sensor has SPI interface instead of as ADC). I don’t like using PRU GPIO to bit-dang SPI since it’s hard to use all the good feature of SPI controller. It would be nice if I can use PRU to control the SPI controller directly similar as controlling the ADC. But will it be deterministic? All interrupts and inconsistency are still happening on the Linus side, PRU can tell SPI controller to start the transmit data consistently, would data coming back in the right interval though? Are there any code sample doing that? Thanks for reminding about the max SPI speed. I only need to use one channel so there is hope to achieve 1Mhz. But if it’s not deterministic, then it’s useless.