Reading ADC from PRU

Hi all,

I’ve been reviving an older BBB project that uses the PRUs to continuously read the ADC and fill FIFO0 before writing the data to DRAM. I can run my assembly code on the PRUs, but data never reaches DRAM. I hoping for a sanity check to help me track down the error in my setup.

I first boot with UIO enabled and set all my pins using config-pin. I then use a C code to initialize the PRUs using UIO, load my assembly code onto PRU0 and PRU1, and then start running the PRUs. I know that my pins are set correctly, that the ADC works (based on pinging the AIN0 values from iio:device0 with BB-ADC loaded in /boot/uEnv.txt), and that my PRUs are able to initialize and run (as I have separate code working on PRU0).

In my PRU1 assembly code I follow these steps:

  1. Turn on OCP
  2. Disable ADC putting zero in control bit 0
  3. Set some ADC settings (Idle mode and clkdiv)
  4. Enable the control bit 0 to turn on ADC
  5. Write to STEPENABLE bit 1 to capture from AIN0
  6. Read FIFO0 and then write to DRAM memory address

I have verified that the memory locations for all these different settings are correct and that this exact PRU code worked in the past under the obsolete $SLOTS framework. My data definitely makes it to the AIN0 pin as well, I can see it when pinging the ADC separately from the command line. I suspect that something is wrong with my Linux environment, but I don’t know what is needed as the PRU handles all the ADC settings.

Would anyone have some suggestions for me? I’ve been stuck at this stage for a bit and have run out of things to check. Everything I have learned on enabling the ADC comes from the TI Technical Manual

Thank you!

  • Joey

you’ve only described the Linux environment as loading and starting the PRU’s… do you not have to specify WHERE in DRAM you intend to write?

no where near enough data on what FIFO0 is … could mean anything.
is it part of an:

  • MII_RT environment
  • I2c
  • UART ???

unreasonable to expect help with such sketchy description

good luck
gomer

I managed to get my code working by following parts of this old discussion:

https://groups.google.com/g/beagleboard/c/0a4tszlq2y0?pli=1

It seems that the critical part was activating the STEPENABLE register before setting the STEPCONFIG settings. It doesn’t seem that this was necessary in the past, and you could just write to STEPENABLE when you wanted to capture (when set in the “one-shot” capture mode).

By “Linux environment” I mean the firmware I load in /boot/uEnv.txt, the linux kernel modules I have available, and the way I configure the pins. The FIFO I am talking about is clearly described as an integrated part of the onboard ADC itself. Check out page 1916 of the TI Technical Manual. I write data out between the following values, 2 bytes at time:

0x0000_0000 Data Start (PRU1 DRAM)
0x0000_1FFC Data End (PRU1 DRAM)

The procedure for starting and controlling the ADC directly with assembly code is poorly documented and I would not recommend doing it this way. I’m sure doing things from libpruio or something similar would have been easier, but I need to be able to handle external interrupts with high (< 2ms) precision and I did not see a way of doing that consistently in C :upside_down_face:

I had so much difficulty early on doing what you are attempting but finally go it running. I do not use libprio but use rpmsg (the TI recommendation at the time.)

We write in C and avoid assembly and we’ve done ok so far. As you know the PRU has limited memory so we’re generally doing everything we can to minimize the code. We aren’t currently using the shared 12k at all.

if you have not thoroughly gone through the TI documentation on the ADC (the touchscreen controller or TSC as TI intended it to be), I suggest you do that. I benefitted greatly from that. Especially pay attention to the relationship and difference between channels and steps. It can be a bit confusing.

TI has a very decent engineering forum with fairly good help from their engineers too.

This may not be what you are after but we initialize the ADC with the PRU code in C. I think I got this from a TI example and modified it. I’m posting it here ‘as-is’ in hopes it might help you.

static void ADCConfigure(void)
{
	unsigned int i, count, data;

	/* Enable ADC module clock */
	HWREG(SOC_CM_WKUP_REGS + CM_WKUP_ADC_TSC_CLKCTRL) = 0x02;

	/* Disable ADC module for configuration */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) &= ~0x01;

	/* fs = 24MHz / ((CLKDIV+1)*2*Channels*(OpenDly+Average*(14+SampleDly)))
	 *    = 53.57kHz
	 * CLKDIV = 0
	 * Channels = 1
	 * Average = 16
	 * OpenDly = 0
	 * SampleDly = 0
	 */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_ADC_CLKDIV) = 0;

	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_ADCRANGE) = 0xFFF << 16;

	/* Disable all steps for now */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPENABLE) &= 0xFF;

	/* Unlock step configuration */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) |= 0x04;

	// Step 1 config: SW mode, one shot mode, fifo 0, channel 0, AIN4 on the Beaglebone Black, 4 samples averaged
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(0)) = 0x00000008;
	// Step 2 config: SW mode, one shot mode, fifo 0, channel 4, AIN4 on the Beaglebone Black, 4 samples averaged
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(1)) = 0x00200008;
	// Step 3 config: SW mode, one shot mode, fifo 0, channel 5, AIN5 on the Beaglebone Black, 4 samples averaged
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(2)) = 0x00280008;
	
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPDELAY(0)) = 0xFF000000;
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPDELAY(1)) = 0xFF000000;
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPDELAY(2)) = 0xFF000000;
	
	/* Enable channel ID tag */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) |= 0x02;

	/* Clear end-of-sequence interrupt */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQSTATUS) = 0x02;

	/* Enable end-of-sequence interrupt */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQENABLE_SET) = 0x02;

	/* Lock step configuration */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) &= ~0x04;

	/* Empty FIFO 0 */
	count = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFOCOUNT(0));
	for (i = 0; i < count; i++) {
		data = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFODATA(0));
	}

	/* Enable ADC module */
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) |= 0x01;
}

Next, here’s a short piece of our code that reads a channel of the ADC. This code is not the complete routine because there is some proprietary computation/handling in the remaining section that would take some time to redact and that would probably introduce some errors. Anyway, I hope this helps. This forum has been very helpful to me and I try to ‘pay it foward’ when I can. Best of luck

static void ReadSensor()
{
//  Reads the sensor and stores the value if it is within the threshold of a possible seed.
//  If a seed is found, stores the cycles to extrude that seed in an Extrustion array.

	int i;
	int j;
	int m;

	// Start step 
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPENABLE) = 0xE; // enables steps 1,2 and 3.  TSC Charge is not enabled (bit 0)
		
	// Wait for interrupt
	while (!(HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQSTATUS)&0x02));
			
	// Clear interrupt 
	HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQSTATUS) = 0x02;
					
	Data = 0xFFFFFFFF;
												
	count = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFOCOUNT(0));

// #PRAGMA MUST_ITERATE	
	for (i = 0; i < count; i++)	// count is the number of readings 
								// in the ADC FIFO buffer
		{
			Data = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFODATA(0));
			StepRead = (Data >> 16) & 0xF;
			RawAnalog = Data & 0xFFF;

1 Like