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:
-
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.
-
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)