How fast/reliable is ADC sampling from the PRU

Then I was scrolling and he said he still had problems. He proposed a solution and then I saw the “enum pinMixung”.

I got the code you (TFJ) proposed next and I put it in the library pruio.h at /usr/local/include.

once I didn’t understand welI what I’ve changed. Maybe you can tell me, if possible.

The header file pruio.h is generated manualy by translating FB source to C. I just forgot to translate this enumerators (will be fixed in next version). There’s also a copy / paste error in one of the function names (can’t remember which ATM). Just check the mails in the FB thread you mentioned.

I didn’t have the oportunity to do some tests about the sample rate, because I don’t have an oscilloscope this moment. But when I have the oportunity I will report my results to you .

As I said, you can use a CAP pin to measure the frequency (as I did).

That’s wrong. The above snippet is a closed loop control. Its output toggles at maximum 93 kHz. This means the controller runs at twice that speed (186 kHz). This is 200 kHz ADC sampling rate plus some time to check the value and switch the GPIO output.

This additional time varies, depending on the load of the host CPU (interrupts handling network, grafics, keyboard, mouse, …), as you can see in the differences between Minimum and Maximum values. If you’re going to layout a controller, dimension it for the Minimum frequency (in this case 120 kHz = 2 * 60 kHz).

BR

Hey TJF,

I used the CAP pin and I got low values of frequency, using just one ADC, and it varies.

I think it is because of the printf() function as you refered once. How can I get the real frequency without using the printf() or an oscilloscope? And how can I get its minimum, average e maximum?

This is the code I used.

The ADC can definitely sample at 1.6 MHz, that is four channels could go up to 400 kHz each (sequentially). The 1.6 MHz correspond to the minimal 15 steps for one sample when setting the ADC clock to 24 MHz, that is, setting the CLKDIV register to zero, and not to 7 as it is set by default (which explains your 200kHz theoretical sampling rate). I have my own assembler code which works, but as it is a bit older, it is not in line with newer things like libpruio. If you want to have a look at my code to get it working let me know and I’ll send it to you.

Hey Lenny,

At this moment I need fast control of current and tension. That depends of the ADC sample rate. I would be very thankful if you sent it.
Best regards,
Luciano.

Domingo, 21 de Dezembro de 2014 11:19:08 UTC, Lenny escreveu:

Hi Lenny,

which hardware do you refer to?

On my Beaglebone Black the register ADC_CLKDIV is 0 (zero) by default.

And yes, it’s true, that each single step samples at 1.6 MHz. But when I trigger the ADC subsystem by software and restart the step sequence (write to register STEPENABLE) at more than 250 kHz, the timing gets inaccurate. It doesn’t matter how many steps are active. That’s why I limit the sum-sampling rate to 200 kHz in libpruio to be at the save site (200 kHz also mentioned in the TI TRM).

BR

Hi TJF and luciano,

below you can find the assembler code which I used. The critical configuration step is this one
//ADC_CLKDIV
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000 //the 24MHz-clock rate is divided by VALUE+1 to yield ADC_CLOCK
SBBO VALUE,ADDR,0x4C,4 //important to write all 4 bytes even though only the first 2 count - if this is not done, the change doesnt take effect for some reason

For some reason, I struggled for some time believing that the ADC_CLKDIV register was set to zero. Finally, all read and write operations went wrong if I only did a 1 or 2 Byte read/write operation to the register. It only worked properly when I read/wrote a full 4 Bytes to the register.

The main loop of my program arms a single ADC acquisition per cycle, waits for some time, and then retrieves a single value from the FIFO. There are two sources of timing jitter:

  1. The read operation from the FIFO takes on my BBB something between 42 and 44 PRU clock cycles. This is as far as I observed not fully deterministic (some memory bus synchronization issue i guess). The solution for this which I used is to have PRU0 running just as a timer (and performing some well-timed calculations in the meantime), and let the PRU1, after it has retrieved a new ADC value, stall until PRU0 has finished its well-defined number of clock cycles and launch a new acquisition only after the PRU0-PRU1 synchronisation has taken place. If one is careful about the number of clock cycles per loop and the moments when one arms and reads out the ADC, then this method only costs a few (<10) clock cycles but improves control over the sampling time a lot.

  2. As far as I observed, the ADC clock is not synchronized with the PRU clock. So if the loop frequency is not a fraction of 24 MHz, there is a “beating” between the two processes. If the loop frequency is a fraction of 24 MHz, then there is from time to time a “glitch” between the two clocks. I used the code below for a PID controller. When I set it to pure proportional response, I observe random jumps in the group delay by 1/24MHz. For my application this is not a problem. However, after knowing this, if I would do this again, I would use an external ADC triggered by the PRU for exactly this reason. If you should happen to find a way to internally synchronize the ADC and PRU clocks, I would be really grateful for a comment.

With all this working, I can read data from a single ADC channel and output this on the r30 register pins (hooked to a parallel DAC) at 1.4 MHz rate (im not using the full 1.6 MHz because of the random glitches in point 2) with only a few degrees phase jitter, again due to point 2). If the synchronization with two PRUs interests you, you can also check out the full code for both PRU’s in the attached zip file.

//prucode.hp

#define ADC_TSC 0x44E0D000
#define FIFO0COUNT 0x44E0D0E4
#define FIFO0DATA 0x44E0D100

//Register usage for PRU1

#define CALLREG r0.w2
#define ENABLESTEPBUFFER r0.b0
#define DIVISORSHIFT r0.b1

//Registers holding constants
#define ADC_ADDR r1
#define OUTMASK r2

//Temporary values - used only for setup routines
#define ADDR r3
#define VALUE r4

#define X0 r4

.macro PAUSE
QBNE ENDPROGRAM,r0,r0
.endm

.macro PAUSE10
PAUSE
PAUSE
PAUSE
PAUSE
PAUSE

PAUSE
PAUSE
PAUSE
PAUSE
PAUSE
.endm

.macro PAUSE100
PAUSE10
PAUSE10
PAUSE10
PAUSE10
PAUSE10

PAUSE10
PAUSE10
PAUSE10
PAUSE10
PAUSE10
.endm

//FIFO ROUTINES****//
//attention: all offsets (3rd argument) are reduced by one, as ADC_ADDR was increased by one before

//Read a value from the FIFO0 buffer of the ADC
.macro READFIFO
.mparam dst
LBBO dst,ADC_ADDR,0xFF,4
.endm

//Read a how many values are stored in the FIFO0 register of the ADC
.macro READFIFOCNT
.mparam dst
LBBO dst,ADC_ADDR,0xE3,2
.endm

//Read the status register of the ADC IRQ
.macro READFIFOIRQ
.mparam dst
LBBO dst,ADC_ADDR,0x23,2
.endm

//Clear the stats register of the ADC IRQ
.macro CLEARFIFOIRQ
.mparam dst
MOV dst,0x0FFF
SBBO dst,ADC_ADDR,0x27,2
READFIFOIRQ dst
.endm

//Trigger a new ADC single-shot measurement
.macro ENABLESTEP1
//STEPENABLE:
//enable only STEP1 a.k.a. bit 1
MOV ENABLESTEPBUFFER,0x02
SBBO ENABLESTEPBUFFER,ADC_ADDR,0x53,1
.endm

//********* End of FIFO routines *******//

Next file with executable code

// prucode1.p
// Code for PRU1

.setcallreg r0.w2
.origin 0
.entrypoint START

//This is the first instruction that will be executed. For convenience, the START section appears only after several definitions
FIRSTINSTRUCTION:
JMP START

//Many definitions: Register names, Constants, Macros etc.
#include “prucode.hp”

/////////////////////CALL routines////////////

//empty FIFO0 buffer so that zero values are in the ‘pipeline’
EMPTYFIFOBUFFER:
//get IRQ status before emptying
READFIFOIRQ VALUE1.w0

EMPTYFIFOBUFFERLOOP:
//read a value
READFIFO VALUE0.w0
//check buffer count
READFIFOCNT VALUE0.w2
//read more values until the buffer is empty
QBNE EMPTYFIFOBUFFER,VALUE.w2,0x0000

//clear and read out the status of the buffer
CLEARFIFOIRQ VALUE1.w2
RET

//Init the whole program
INIT:
//global config settings
LBCO VALUE, CONST_PRUCFG, 4, 4
CLR VALUE, VALUE, 4 // Clear SYSCFG[STANDBY_INIT] to enable OCP master port
CLR VALUE, VALUE, 3 // set no-standby mode
SET VALUE, VALUE, 2 // set no-standby mode
CLR VALUE, VALUE, 1 // enable no-idle mode
SET VALUE, VALUE, 0 // enable no-idle mode
SBCO VALUE, CONST_PRUCFG, 4, 4

// Configure the programmable pointer registers
//Configs for PRU1 at offset 0x000024000, for PRU0 at offset 0x00022000
MOV ADDR, 0x00024000
//CONST_PRU01DRAM (C24) and CONST_PRU10DRAM (C25) offset (bits 11-8) config with bits 0-7 resp. 16-23
MOV VALUE, 0x00000000
SBBO VALUE,ADDR,0x20,4
//CONST_PRUSHAREDRAM (C28) bits 23-8 are given by bits 15:0 of VALUE
MOV VALUE, 0x00010000>>8
SBBO VALUE,ADDR,0x28,4
//CONST_DDR (C31) bits 23-8 are given by bits 31:16 of VALUE
MOV VALUE, 0x00000000
SBBO VALUE,ADDR,0x2C,4

// Initialize Pru Ram to zero
MOV VALUE,0x00000000
FILLDRAM VALUE
FILLSHAREDRAM VALUE

RET

SETUPADC:
// SETUP ADDRESS REGISTERS
MOV ADC_ADDR,ADC_TSC

//because Immediate value addressing only goes up to 255 and
//because we need an offset of 256 for accessing the FIFO, increase the base address by 1
//this means, that all other offsets need to be reduced by 1 in the following w.r.t. their offsets from the TRM
ADD ADC_ADDR,ADC_ADDR,1

//disable TSC_ADC_SS module,dont write channel id into FIFO
//and disprotect step config registers
MOV ADDR,ADC_TSC
MOV VALUE,0x00000004 //as above said already: disprotect and disable…
SBBO VALUE,ADDR,0x40,4 //write value to CTRL register

//ADC_CLKDIV
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000 //the 24MHz-clock rate is divided by VALUE+1 to yield ADC_CLOCK
SBBO VALUE,ADDR,0x4C,4 //important to write all 4 bytes even though only the first 2 count - if this is not done, the change doesnt take effect for some reason

//TS_CHARGE_STEPCONFIG
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000 //no unnecessary delay, do nothing here
SBBO VALUE,ADDR,0x5C,4

//TS_CHARGE_DELAY
MOV ADDR,ADC_TSC
MOV VALUE,0x00000001 //value must be greater than 0
SBBO VALUE,ADDR,0x60,4

//STEPCONFIG1: SW continuous and no averages
//bits 22-19: AINx; bit1-0 = 01 for continuous
//bits 4-2 averaging: 100=16avg,000no avg
MOV ADDR,ADC_TSC
//MOV VALUE,0x00080001 //continuous mode
MOV VALUE,0x00080000 //sw one-shot mode
SBBO VALUE,ADDR,0x64,4

//STEPDELAY1 - add highest two bytes any value if the sampling need to be slowed down
//integrate signal at analog input over 1 analog sample cycles
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000 //bits 31-24: SampleDelay; bits 17-0: OpenDelay
SBBO VALUE,ADDR,0x68,4 //STEPDELAY1

//for future implementations of averaging etc., we also configure steps 2 and 3 although they are never used here

//STEPCONFIG2: SW continuous and no averages
//bits 22-19: AINx; bit1-0 = 01 for continuous
//bits 4-2 averaging: 100=16avg,000no avg
MOV ADDR,ADC_TSC
//MOV VALUE,0x00080001 //continuous mode
MOV VALUE,0x00080000 //one-shot mode
SBBO VALUE,ADDR,0x6C,4

//STEPDELAY2
//integrate signal at analog input over 1 analog sample cycles
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000 //bits 31-24: SampleDelay; bits 17-0: OpenDelay
SBBO VALUE,ADDR,0x70,4 //STEPDELAY2

//STEPCONFIG3: SW continuous and no averages
//bits 22-19: AINx; bit1-0 = 01 for continuous
//bits 4-2 averaging: 100=16avg,000no avg
MOV ADDR,ADC_TSC
MOV VALUE,0x00080001
SBBO VALUE,ADDR,0x74,4

//STEPDELAY3
//integrate signal at analog input over 1 analog sample cycles
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000 //bits 31-24: SampleDelay; bits 17-0: OpenDelay
SBBO VALUE,ADDR,0x78,4 //STEPDELAY2

//STEPENABLE:
//enable only STEP1 a.k.a. bit 1
//here: disable all steps for now
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000
SBBO VALUE,ADDR,0x54,4

//FIFO0THRESHOLD
MOV ADDR,ADC_TSC
MOV VALUE,0x000000FF //bits 5:0 = Value-1 for FIFO Threshold interrupt generation
SBBO VALUE,ADDR,0xE8,4

//SYSCONFIG
MOV ADDR,ADC_TSC
MOV VALUE,0x00000000 //bits 3:2 00=force idle, 01=no idle, 10=smart idle, 11=smart idle+wakeup
SBBO VALUE,ADDR,0x10,4

//enable TSC_ADC_SS module,dont write channel id
//and write-protect step config registers
MOV ADDR,ADC_TSC
MOV VALUE,0x00000001
SBBO VALUE,ADDR,0x40,4

RET

START:
//Zero all registers to avoid ambiguity
ZERO &r0,124
//Initialise memory and registers
CALL INIT

//Setup ADC
CALL SETUPADC

//empty FIFO0 buffer in case it is not clean
CALL EMPTYFIFOBUFFER
//re-zero the buffer variables used by EMPTYFIFOBUFFER
MOV VALUE0,0x00000000
MOV VALUE1,0x00000000

MAINLOOP:

//ask for a new value from the ADC, whose preparation takes about 190 clock cycles
//(should be only 125 cycles (625ns) and once this worked, but recently there are problems. Maybe due to readout latency from FIFO?)

ENABLESTEP1

//wait the required amount of time, and a bit more to make sure we dont read a value when there is none

PAUSE100

PAUSE100

//do the same as in PID-Loop without sending Enablestep
//retrieve sampled analog data
READFIFO X0

//now we have the last adc sampling value (from AIN1) in the register X0 and can write it to DDR memory or do whatever else we want

//…

JMP MAINLOOP

code.zip (15.9 KB)

Hey TJF,

When possible, can you tell me something about my results when I got the sample rate using the CAP pin?

Thanks for your attention,
Luciano.

Sábado, 20 de Dezembro de 2014 20:50:49 UTC, luciano...@gmail.com escreveu:

Hi Luciano,

sorry, I can’t, because I don’t understand your code. The sequence

`

#define P1 P9_14
#define P2 P9_42
int main(int argc, char **argv)
{
pruIo *Io = pruio_new(PRUIO_DEF_ACTIVE, 0, 0, 0); //! create new driver structure

if (Io->Errr) {
printf(“initialisation failed (%s)\n”, Io->Errr);}

if (pruio_cap_config(Io, P1, 2.)) { // configure input pin
printf(“failed setting input @P_IN (%s)\n”, Io->Errr);}

`

should through an error since P1 (= P9_14) has no CAP capability. Why don’t you stop the program in case of that error? And where is the error message in your output picture? Are you sure you did run that code?

Also

`


if (pruio_cap_Value(Io, P1, &f1, &d1)) { // get current input
printf(“failed reading input @P1 (%s)\n”, Io->Errr); }

printf("\r Frequency: %10f , Duty: %10f ", f1, d1); // info

`

cannot work due to the same reason (missing CAP capability of P1) and should through error messages. You should have used P2 in both cases.

BR

Hey TJF,

You’re right! I copied and pasted the wrong code. I used the P2 (P9_42) for the CAP pin and I got that results.

Hi TJF,

Did you find Lenny code interesting?? maybe libpruio can sample as fast as 1.4-1.6 MHz??

regards,

It can. Just configure the ADC steps accordingly.

Lenny code is not interesting for a multi purpose library like libpruio.

BR

Lenny, I’m sorry for this very late answer, but I found this topic really useful and you guys have given me the right directions.
I got to check all the information you gave here in the SPRUH73 guide (technical ashsssfyiafgygrf) and definitely, if one uses the default 3MHz ADC clock or the CLK_M_OSC 24MHz clock, that wouldn’t perfectly sync with the PRUs as 200MHz is not a multiple of these values.
However, if I understood correctly, the PRCM acts like a prescaler, so maybe if you set PRCM = multiple of 3, do the clocks sync then?
I know, maybe you REALLY need the whole 24MHz… But in case your application accepts 8MHz, there’s a chance of syncing them.

What do you think?

It seems I was wrong, I noticed just the same amount of lost samples when dividing the 24MHz by 1, 3 or 6.