Trying to run the ADC from the PRU

I am not having success in running the ADC from the PRU, and wondering if anyone can offer some guidance. I suspect my device tree overlay, but I am also uncertain about my PRU code. The overlay compiles and installs without error. The PRU code hangs on the loop that is waiting for FIFO0COUNT to be nonzero.

Both my PRU code and the device tree overlay are shown below. I am running kernel 4.4.19-bone-rt-r13.

Thanks,
Phil

(BTW, this message is cross-posted here. It is not clear to me which forum is the right place for this. Hopefully this won’t irritate anyone too badly.)

PRU Code
// Interrupt strobe bit
#define PRU0_R31_VEC_VALID (1<<5)

// Interrupt signal number
// SIGNUM corresponds to pr1_pru_mst_intr[3]_intr_req
// which is named PRU0_ARM_INTERRUPT (19) in pruss_intc_mapping.h
// This interrupt is in turn mapped to PRU Interrupt channel 2,
// and then mapped to host interrupt 2 as well.
#define SIGNUM 3

#define ADC_BASE 0x44e0d000

// ADC registers, offset from ADC_BASE
#define CTRL 0x0040
#define ADCSTAT 0x0044
#define ADC_CLKDIV 0x004c
#define STEPENABLE 0x0054

#define STEPCONFIG1 0x0064
#define STEPDELAY1 0x0068
#define STEPCONFIG2 0x006C
#define STEPDELAY2 0x0070
#define STEPCONFIG3 0x0074
#define STEPDELAY3 0x0078
#define STEPCONFIG4 0x007C
#define STEPDELAY4 0x0080
#define STEPCONFIG5 0x0084
#define STEPDELAY5 0x0088
#define STEPCONFIG6 0x008C
#define STEPDELAY6 0x0090
#define STEPCONFIG7 0x0094
#define STEPDELAY7 0x0098
#define STEPCONFIG8 0x009C
#define STEPDELAY8 0x00A0

#define STEPCONFIG9 0x00A4
#define STEPDELAY9 0x00A8
#define STEPCONFIG10 0x00AC
#define STEPDELAY10 0x00B0
#define STEPCONFIG11 0x00B4
#define STEPDELAY11 0x00B8
#define STEPCONFIG12 0x00BC
#define STEPDELAY12 0x00C0
#define STEPCONFIG13 0x00C4
#define STEPDELAY13 0x00C8
#define STEPCONFIG14 0x00CC
#define STEPDELAY14 0x00D0
#define STEPCONFIG15 0x00D4
#define STEPDELAY15 0x00D8
#define STEPCONFIG16 0x00DC
#define STEPDELAY16 0x00E0

#define FIFO0COUNT 0x00e4
#define FIFO0DATA 0x0100

#define FIFO1COUNT 0x00f0
#define FIFO1DATA 0x0200
// Register Group base addresses/pointers
#define PRU0_CTRL_addr 0x00022000
#define PRU1_CTRL_addr 0x00024000
#define PRU_CFG_ptr c4

// PRUx_Ctrl Registers: Register offsets
#define PRUx_CTRL_CTRL 0x00
#define PRUx_CTRL_STS 0x04
#define PRUx_CTRL_WAKEUP_EN 0x08
#define PRUx_CTRL_CYCLE 0x0c
#define PRUx_CTRL_STALL 0x10
#define PRUx_Ctrl_CTBIR0 0x20
#define PRUx_Ctrl_CTBIR1 0x24
#define PRUx_Ctrl_CTPPR0 0x28
#define PRUx_Ctrl_CTPPR1 0x2c

// Register allocations
// Registers r0-r3 are to be used for general purpose operations
#define adc_base r4
#define fifo0data r5
#define local c24
#define remote c25
#define shared c28
#define PRU0_CTRL_ptr r22

.origin 0
.entrypoint INIT

INIT:
// constant table offsets
// This sets the following:
// c24 = 0x00000000 local PRU RAM
// c25 = 0x00002000 remote PRU RAM
// c28 = 0x00010000 shared RAM
mov PRU0_CTRL_ptr, PRU0_CTRL_addr
mov r0, 0
sbbo r0, PRU0_CTRL_ptr, PRUx_Ctrl_CTBIR0, 4
mov r0, 0x00000100
sbbo r0, PRU0_CTRL_ptr, PRUx_Ctrl_CTPPR0, 4

// local constants
mov adc_base, ADC_BASE
mov fifo0data, FIFO0DATA

// Enable OCP
lbco r0, PRU_CFG_ptr, 4, 4
clr r0, 4
sbco r0, PRU_CFG_ptr, 4, 4

// Disable ADC
lbbo r0, adc_base, CTRL, 4
and r0.b0, r0.b0, 0xff
sbbo r0, adc_base, CTRL, 4

// Run ADC at full speed
mov r0, 0
sbbo r0, adc_base, ADC_CLKDIV, 4

// Configure STEPCONFIGx registers for channels 2-6
// no averaging, SW-enabled, one-shot
mov r0, 0
mov r0.b2, 0x10 // channel 2
sbbo r0, adc_base, STEPCONFIG1, 4
mov r0.b2, 0x18 // channel 3
sbbo r0, adc_base, STEPCONFIG2, 4
mov r0.b2, 0x20 // channel 4
sbbo r0, adc_base, STEPCONFIG3, 4
mov r0.b2, 0x28 // channel 5
sbbo r0, adc_base, STEPCONFIG4, 4
mov r0.b2, 0x30 // chennal 6
sbbo r0, adc_base, STEPCONFIG5, 4

// Configure STEPDELAYx registers for channels 2-6
mov r0, 0
sbbo r0, adc_base, STEPDELAY1, 4
sbbo r0, adc_base, STEPDELAY2, 4
sbbo r0, adc_base, STEPDELAY3, 4
sbbo r0, adc_base, STEPDELAY4, 4
sbbo r0, adc_base, STEPDELAY5, 4

// enable steps 1-5
mov r0, 0x0000003E
sbbo r0, adc_base, STEPENABLE, 4

// Set STEPCONFIG registers protected, use tags
mov r0, 0x00000001
sbbo r0, adc_base, CTRL, 4

MAIN:
// enable the ADC
mov r0, 0x00000003
sbbo r0, adc_base, CTRL, 4

// wait for conversions complete
WAIT_FOR_FIFO0:
lbbo r0, adc_base, FIFO0COUNT, 4
qbne WAIT_FOR_FIFO0, r0, 5 // GETTING STUCK HERE

// signal C code
MOV R31.b0, PRU0_R31_VEC_VALID | SIGNUM

//qba MAIN

halt

Device Tree Overlay
/dts-v1/;
/plugin/;

/{
compatible = “ti,beaglebone”, “ti,beaglebone-black”;
part-number = “pruss_enable”;
version = “00A0”;

/* This overlay uses the following resources */
exclusive-use =
“pru0”,
“pru1”,
“tscadc”,

“P9.29”, // GPIO3[15] fet_off
“P9.30”, // GPIO3[16] fet_on
“P9.31”, // GPIO3[14] triac

“P9.37”, // AIN2 TC_REF
“P9.38”, // AIN3 TC
“P9.33”, // AIN4 VL_SENSE
“P9.36”, // AIN5 VH_SENSE
“P9.35”; // AIN6 IH_SENSE

fragment@0 {
target = <&am33xx_pinmux>;
overlay {
pru_pins: pinmux_pru_pins {
pinctrl-single,pins = <
0x190 0x05 // P9_31 $PINS=100 pr1_pru0_pru_r30_0 mode 5 output pulldown
0x194 0x05 // P9_29 $PINS=101 pr1_pru0_pru_r30_1 mode 5 output pulldown
0x198 0x05 // P9_30 $PINS=102 pr1_pru0_pru_r30_2 mode 5 output pulldown

;
};
};
};

// fragment@1 {
// target = <&tsadc>;
// overlay {
// status = “okay”;
// adc {
// ti,adc-channels = <2 3 4 5 6>;
// };
// };
// };

fragment@2 { // Enable the PRUSS
target = <&pruss>;
overlay {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&pru_pins>;

dig_pru_pins {
pin-names = “FET_OFF”,“FET_ON”,“TRIAC” ;
gpios = <&gpio3 14 0 // P9.31
&gpio3 15 0 // P9.29
&gpio3 16 0>; // P9.30
};
};
};

};

The only thing I can think of is that you're not enabling the ADC control register. Below is a code snippet from another post.
//Init ADC CTRL register
    MOV r2, 0x44E0D040
    MOV r3, 0x00000005
    SBBO r3, r2, 0, 4

From this post: [https://groups.google.com/forum/#!msg/beagleboard/0a4tszlq2y0/SQ-Vwyr9A_AJ](https://groups.google.com/forum/#!msg/beagleboard/0a4tszlq2y0/SQ-Vwyr9A_AJ)

The second post. I would not bother replying to that post, but who knows maybe someone would answer ? I doubt it though.

Hi Phil!

After boot the ADC subsystem is disabled by default. You have to enable the CM_WKUP_ADC_TSC_CLKCTRL register in the CONTROL MODULE before you can use it:

`
mov r0, 0x44E004BC
mov r1, 2
sbbo r1, r0, 0, 4

`

Place this snippet after OCP enbled and before any ADC register access.

Regards

I gave this a try, with no success. I have found that the PRU cannot access the ADC registers (1) - it is some bus access conflict between the PRU and Linux. I had seen reference to this somewhere a long while ago, and a solution to it, but have been unable to find the information again. Trying your code snippet, adding a pre-read of address 0x44E004BC before writing the value 2, causes the PRU to hang, which I interpret as the PRU waiting for a memory access that Linux isn’t allowing it to have.

For further testing, I wrote some C fragments using mmap() to get ahold of the ADC register space, and I was able to get that to work. I don’t believe that is a great way to go though.

(1) I know this because I tried reading the ADC REVISION register using the PRU and got zero as a result.

Thanks for the suggestion, but that doesn’t fix the problem. I can’t even access the ADC registers from the PRU. See my response to TJF.

The PRU hads to access the ADC through the L3_interconnect bus too . . . so the control register for the L3_interconnect must also be enabled.

It takes some cycles before the ADC is up and running. Check the ID register several times. In libpruio it works similar to this snippet (find original code in file pruio_adc.p):

LDI r2, 0b10 // load clock demand value MOV r1,0x44E004BC // load CM_WKUP_ADC_TSC_CLKCTRL address SBBO r2, r1`, 0, 1 // write clock register
LDI r5, 0 // clear timeout counter
MOV r3, 0x44E0D000 // load ADC address
AdcWait:
LBBO r0, r3, 0, 4 // load ADC REVISION
QBNE AdcCopy, r0, 0 // if ADC is up → copy config
ADD r5, r5, 1 // increase timeout counter
QBGE AdcWait, r5.b1, 16 // if no timeout → wait

AdcCopy:

`

Regards

That makes sense… But the TRM seems not to disclose the location of this register. Some googling indicates that the L3/L4 interconnects are documented elsewhere.

OTOH, I am still wondering if my kernel isn’t the root cause. It seems clear that other people have been able to get ADC control from the PRU, but with 3.8 or 4.1 kernels. I could revert to an older kernel version, but that feels like the wrong approach. I have also been looking at the IIO system, which may be a better approach as it would be more “standard” and portable than using the PRU. (But I like using the PRU - it’s a nice hammer looking for a nail!)

OTOH, I am still wondering if my kernel isn’t the root cause. It seems clear that other people have been able to get ADC control from the PRU, but with 3.8 or 4.1 kernels. I could revert to an older kernel version, but that feels like the wrong approach. I have also been looking at the IIO system, which may be a better approach as it would be more “standard” and portable than using the PRU. (But I like using the PRU - it’s a nice hammer looking for a nail!)

I
it could be that I’m thinking of OCP port, which you’re already enabling.

So, yes . . . it could be your kernel, but I some how doubt that is the issue here. One thing you could do is enable the ADC’s from userspace using cape manager, and set the ADC into continuous mode. Like so: http://processors.wiki.ti.com/index.php/Linux_Core_ADC_User%27s_Guide#Continuous_Mode. Then just read from FIF0DATA using the PRU’s.

Then perhaps you can narrow things down some. This is how I initially used /dev/mem + mmap() for the ADC’s then I found this project: https://github.com/ehayon/BeagleBone-GPIO, examined the code, and customized one of the header files for my own needs. Then just used that.

Alright, I finally got this working, thanks to both William and TJF. In the end, my problem boils down to coding sloppiness on my part, as well as being constantly interrupted with other tasks. After spending a solid block of uninterrupted time doing some careful testing, the following code snippets are what I found to work. The only device tree overlay I needed is what I have below; no need to load the BB-ADC overlay.

PRU Code
//--------------------------------------------------------------------
// Enable and/or turn on hardware
// Code in this section must be ordered as listed for proper
// hardware startup.

// Enable OCP
lbco r0, PRU_ICSS_CFG_base, PRU_CFG_SYSCFG, 4
clr r0, 4
sbco r0, PRU_ICSS_CFG_base, PRU_CFG_SYSCFG, 4

// Enable ADC clock, wait for ADC module to start before proceeding
mov r0, CM_WKUP_base + CM_WKUP_ADC_TSC_CLKCTRL
mov r1, 2
sbbo r1, r0, 0, 4
mov r1, 0 // clear timeout counter
AdcWait:
LBBO r0, adc_base, ADC_REVISION, 4 // load ADC REVISION
QBNE AdcUp, r0, 0 // exit if ADC is running
ADD r1, r1, 1 // increase timeout counter
QBGE AdcWait, r1.b1, 16 // if no timeout → wait
AdcUp:

//--------------------------------------------------------------------

Device Tree Overlay
/dts-v1/;
/plugin/;

/{
compatible = “ti,beaglebone”, “ti,beaglebone-black”;
part-number = “pru_enable”;
version = “00A0”;

/* This overlay uses the following resources */
exclusive-use =
“pru0”,

“P9.29”, // GPIO3[15] fet_off
“P9.30”, // GPIO3[16] fet_on
“P9.31”; // GPIO3[14] triac

fragment@0 {
target = <&am33xx_pinmux>;
overlay {
pru_pins: pinmux_pru_pins {
pinctrl-single,pins = <
0x190 0x05 // P9_31 $PINS=100 pr1_pru0_pru_r30_0 mode 5 output pulldown
0x194 0x05 // P9_29 $PINS=101 pr1_pru0_pru_r30_1 mode 5 output pulldown
0x198 0x05 // P9_30 $PINS=102 pr1_pru0_pru_r30_2 mode 5 output pulldown

;
};
};
};

fragment@2 { // Enable the PRUSS
target = <&pruss>;
overlay {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&pru_pins>;

dig_pru_pins {
pin-names = “FET_OFF”,“FET_ON”,“TRIAC” ;
gpios = <&gpio3 14 0 // P9.31
&gpio3 15 0 // P9.29
&gpio3 16 0>; // P9.30
};
};
};

};

The only device tree overlay I needed is what I have below; no need to load the BB-ADC overlay.

Right, technically, you do not need an overlay, and you do not need any driver modules either. As directly twiddling the ADC module registers, you essentially are creating your own driver.

Now, with that said, it could be eventually you’ll end up using the overlays, and setting up the ADC in userspace using the iio driver. But both ways have it’s pluses and minuses. I’m sure you can make the determination for yourself.

Hi Phil!

Your code is incomplete. The error handling is missing, for the case that the ADC subsystem doesn’t come up. Your code just continues.

PRU Code
//--------------------------------------------------------------------
// Enable and/or turn on hardware
// Code in this section must be ordered as listed for proper
// hardware startup.

// Enable OCP
lbco r0, PRU_ICSS_CFG_base, PRU_CFG_SYSCFG, 4
clr r0, 4
sbco r0, PRU_ICSS_CFG_base, PRU_CFG_SYSCFG, 4

// Enable ADC clock, wait for ADC module to start before proceeding
mov r0, CM_WKUP_base + CM_WKUP_ADC_TSC_CLKCTRL
mov r1, 2
sbbo r1, r0, 0, 4
mov r1, 0 // clear timeout counter
AdcWait:
LBBO r0, adc_base, ADC_REVISION, 4 // load ADC REVISION
QBNE AdcUp, r0, 0 // exit if ADC is running
ADD r1, r1, 1 // increase timeout counter
QBGE AdcWait, r1.b1, 16 // if no timeout → wait
AdcUp:

//--------------------------------------------------------------------

But it needs something like

`

QBGE AdcWait, r1.b1, 16 // if no timeout → wait

SBCO r1, DRam, 0, 4 // store timeout counter in DRam
HALT // stop PRU, since ADC isn’t ready
AdcUp:

`

Regards

Thanks, good catch! A question though: Under what circumstances would the ADC not start? I’ve been running the ADC now and have not had a startup fail.

Thunderstorm, lightning, cosmic rays, … hardware damage. I don’t know.

In any case it’s better to stop with an error message, than configuring a subsystem that isn’t running.

Regards

I am facing some issues in the project you suggested : BeagleBone-GPIO/src/gpio.c at master · ehayon/BeagleBone-GPIO · GitHub

in the last function analogRead(PIN p) it always reads the last 12 bits from the FIFO0DATA register
I am using this code and value is got from reading from this register is somewhat misleading (from misleading i mean they are not the same value that we read from “/sys/bus/iio/devices/iio:device0/in_voltage%d_raw” )
So plz suggest what could be wrong in it and also tell me this if 2 or more pins are configured to FIFO0 then how beaglebone differentiates between which pin’s ADC data is present in it and how can use it correct the output readings.