Differences using ADC with Linux/ARM/C vs. PRU w/C

We are running our system on a BeagleBone Black Wireless.

model:[TI_AM335x_BeagleBone_Black]
dogtag:[BeagleBoard.org Debian Buster IoT Image 2021-02-15]
bootloader:[eMMC-(default)]:[/dev/mmcblk1]:[U-Boot SPL 2019.04-00002-gc9b3922522 (Aug 24 2020 - 16:42:18 -0500)]:[location: dd MBR]
bootloader:[eMMC-(default)]:[/dev/mmcblk1]:[U-Boot 2019.04-00002-gc9b3922522]:[location: dd MBR]
UBOOT: Booted Device-Tree:[am335x-boneblack-uboot-univ.dts]
UBOOT: Loaded Overlay:[AM335X-PRU-RPROC-4-19-TI-00A0.kernel]
UBOOT: Loaded Overlay:[BB-ADC-00A0.kernel]
UBOOT: Loaded Overlay:[BB-BONE-eMMC1-01-00A0.kernel]
kernel:[4.19.94-ti-r68]

While in development, we capture the sensor analog output (0-1.6V) using a Diligent USB-1408FS data acquisition system. Because of an impedance issue between the sensor output and the Diligent, we have a unity gain amplifier between the point where the BBBW ADC connects at AIN0 and the connection to the USB1408FS.

We have been operating like this for months in our lab but in the past several weeks the sensor signal drifts up from zero to a baseline of around 350mv. It only does this when we read AIN0 using the code running on the PRU0. When we run ARM code and read it with the ADC, there is no DC offset drift upward. We have isolated every other device the Beagle operates - pumps & valves are turned on and off via optoisolated connections, user-operated on/off switches, and LED indicators are directly wired to the GPIO lines. We use I2C for our pressure sensors.

I have monitored the 5V supply to the Beagle and the 3.3 (actually 3.39v) on J9_4 and neither one changes when this drift occurs.

We are puzzled. Any ideas?

Walter

I assume you have checked your PRU code against the kernel driver ?

Are you just reading the same channel, or do you read multiple channels ?

Is the ADC in continuous or one shot mode ?

Some possible causes, although depending on the hardware may or may not be relevant.

  1. you are trying to sample the ADC to fast. Max speed is 15 ADC clocks (no averaging). Whats the clock of the ADC ?
  2. you are kicking off a reading before the previous one has properly finished.
  3. reading the result before the conversion has properly finished.
  4. if reading multiple channels, not allowing enough settling time between switching channels.
  5. Have you accidentally enabled reading multiple channels ?
  6. some weird clash between linux and pru. Is the ADC module disabled in the device tree when using the pru ? If not and it is generating interrupts then maybe the linux driver is also trying to access the adc.

Have you tried adjusting the open delay and sample delay registers ?

On the ARM/Linux side, I am using the following code to read the ADC. This snippet isn’t exactly like the PRU code since it only reads one channel. When running the PRU code, I don’t disable the kernel driver for the TSCADC. I never have and we haven’t had any problems. How do I do that and I’ll try it if it makes a difference.

I have not tried adjusting the open delay, etc. As I said, it’s always worked fine for over a year.

else if (x == 14) 
	      {
	      	
	      	printf("\nSingle channel detector.");
	      	// define file handle to read analog input
    		   
		    FILE* fAIN_0 = fopen("/sys/bus/iio/devices/iio:device0/in_voltage0_raw", "r");
		  
		    while(KeepGoing)
		    {
		     
		       fread(&AIN_0_str, 6, 1, fAIN_0);
		   
		        rewind(fAIN_0);
		      
		        AIN_0_fl = atof(AIN_0_str);
		      
		   
		      mvolts_AIN_0 = AIN_0_fl/4096*1.8;
		      
		      if (x == 1)
		      {
		    	printf("Sensor output 0 is %.1f V + (Toggle the on/off switch to stop.)\n",mvolts_AIN_0);
		   
		    	x = 0;
		      }
		      else
		      {
		    	printf("Sensor output 0 is %.1f V x (Toggle the on/off switch to stop.)\n",mvolts_AIN_0);
		   
		        x = 1;
		      }
		      fread(&value_str, 6, 1, RunStopButton_val);   
		      rewind(RunStopButton_val);
		      
		      if (value_str[0] == '0') 
		        {
		          printf("Stopping on switch interrupt");
		          KeepGoing = 0;
		        }
		      usleep(200000);
		    }
		    fclose(RunStopButton_val);

    		rewind (fAIN_0);	

    		fclose(fAIN_0);

	      }

We read three channels with the PRU code. Here’s the C code for the PRU that sets up the ADC. This has worked on our test platform and in the lab for months.

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, AIN0 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;
}

This is the code that actually reads the values from the ADC. Note - this was a handy copy and doesn’t do anything with the values read from channels 4 & 5.

	// 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;
									
			switch (StepRead)
				{
				
					case 0:
						Voltage = RawAnalog;
						break;
					case 1:
						break;
					case 2:
						break;
					default: 
						break;
				}
}		

Here’s a snapshot from the DAQami software showing the signal as measured by the USB1408-FS. The first strip was captured while operating the system with the ARM/Linux C code HWTest.c.

The second strip was captured while running the PRU code. Do you see how the signal drifts up on the strip captured while operating the system with the PRU code? Puzzling. We don’t change anything else between the two methods.

This was on the BBB industrial yes, and not the standard BBB ?

Also when did the rev C silicon come out ? Is it possible that this issue is only showing on the newer rev C silicon ?

So all of a sudden it has stopped working, or are the problems on a different setup ?

If it is the same system and nothing has changed (hardware or software) I would say that you have a weird corner/timing issue that sometimes shows and at other times doesn’t. Or maybe it is something external that you re not aware of.

Have you got a spare analog input channel free ?

If so I would try an experiment. Tie the spare channel to GND then change your sampling steps, such that you first read the grounded input, then read the channel you are sampling, then repeat the same sequence for the other channels.
See if it changes anything.

This is on the standard BBB.

This is a prototype of a system. We have a similar core technology setup on a test platform in the office. We have not had this problem with it.

The prototype integrates many other components with the core technology. We’ve been working with this integrated prototype since April. We’ve had other noise issues, primarily due to adding stepper motors, and have solved those. This is really the last serious problem we have to tackle. We don’t know why the two different access methods to the ADC result in different behavior. I’m puzzled, too, why an ADC reading an analog input would cause the signal to float up to a steady state. I’ve not run across this before.