Im trying to interface my Beaglebone Black with a TI ADS7883 SPI 12-bit ADC. My hardware implementation is the same as this tutorial, except with a slightly different pinout from the BBB, namely:
CS => P9_27
SCLK => P9_30
SDO => P9_41
I am testing with a DC signal from a BKP9205 DC power supply. The ADC itself is being powered from the 3.3V (actually 3.393V) rail on the BBB. BBB ground and PS ground are tied together. SCLK runs at 1Mhz.
Problems
- There is activity on the data line, but the ADC only seems to be driving it for the first half of the frame. In the scope captures below, you can see a change for the last 7ish clock cycles.
- each sample is not the same when the input is 1V. It should consistently be (1/3.393)*4096 == 0x411```.
- But sometimes it works fine. For a 3V test voltage (last pic), it gives
0xE00(~2.968V) which is about right. - Shouldn't the data start with 2 zeros? In the 3V test, it seems the 2nd bit is a 1.
The pictures below should hopefully make all this clearer...
My thoughts/attempts so far
Could this be a input buffering issue? Im just feeding my power supply directly to Vin. I added some different bypass caps, but they did not improve the noise at all. The supply is limited to 200mA, which should be enough to cover inrush???
I tried it with 2 different chips, but the behavior is the same. I could try it again, maybe borrowing a microscope to better solder the SOT23-6, but hopefully that is not necessary
I really appreciate any guidance you can provide. Im a EE student currently, and this is my first post, so if there is something basic Im missing please forgive me.
Regards,
Adrian
Schematic
Data
This is a capture of a 5 sample burst with a 1V input. (yellow=CS, pink=SDO, blue=SCLK, green=Vin). The output from my program for this signal is 0x800 0x0 0x400 0x200 0x0.

And here is a closeup of the first frame.

This is the first frame with a 3V reference. The output is stable (0xE00 for all samples)

Code
Here is the code implementing the SPI protocol on the BBB PRU's. The important part start around line 34, where I start driving the SPI bus. You can probably ignore the memory mgmt stuff, it just allows me to share the data from the real-time peripheral microtrollers (the PRU) to the main processor.
#include <stdint.h>
#include <pru_cfg.h>
#include "resource_table_empty.h"
#include "prugpio.h"
#define PRU0_DRAM 0x00000 // Offset to DRAM
volatile register unsigned int __R30;
volatile register unsigned int __R31;
#define CS P9_27
#define SDO P9_41
#define SCLK P9_30
#define adc_en pru0_dram[0] //ARM writes here to start capture
#define num_samples pru0_dram[2] //ARM writes number of samples
#define sample_done pru0_dram[3] //PRU writes here to tell ARM it's to read smaples
#define sample_start_idx 0x00004 //data starts at pru0_dram[4]
// Skip the first 0x200 byte of DRAM since the Makefile allocates
// 0x100 for the STACK and 0x100 for the HEAP.
volatile unsigned int *pru0_dram = (unsigned int *) (PRU0_DRAM + 0x200);
void main(void) {
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0; // Clear SYSCFG[STANDBY_INIT] to enable OCP master port */
__R30 |= (CS | SCLK);
unsigned int i,s,sample;
while(1){
while(adc_en!=1); //wait for enable flag
/*** IMPORTANT: Implementing SPI in software at 1Mhz ***/
for(s=0; s<num_samples; s++){
sample=0x0000;
__R30 &=~ CS; //drive CS Low
__delay_cycles(50); //delay .25us (5ns per cycle)
for(i=0;i<16;i++){ //read 16 bit frame
sample = (sample << 1); //make room for new bit
__R30 &=~ SCLK; //SCLK Low
__delay_cycles(100); //.5us
__R30 |= SCLK; //SCLK High
if(__R31 & SDO) { //add 1 to sample if SDO is non-zero
sample += 1;
}
__delay_cycles(100); //.5uS
}
__R30 |= CS; //CS High
//write sample to shared dram, shift right twice to drop trailing zeros
pru0_dram[sample_start_idx+s] = sample >> 2;
__delay_cycles(500); //raise CS for 2.5us
}
//finish sampling
sample_done = 1;
adc_en=0;
}
}
And here is the program running in userspace on the main ARM processor, it basically starts the ADC, waits for the ADC to finish sampling, then grabs the samples from the shared memory and prints to the console.
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
//storage locations in shared memory for data and params
#define adc_en pru0_dram[0] //location PRU looks for start signal
#define num_samples pru0_dram[2] //location PRU looks for number of sample to take
#define sample_done pru0_dram[3] //PRU writes 0x01 when done writing
#define SAMPLE_OFFSET 0x00004 //data offset in PRU dram
#define NUM_SAMPLES 5 //number of sample to take
//PRU memory info (Page 184 am335x TRM)
#define PRU_ADDR 0x4A300000 // Start of PRU memory
#define PRU_LEN 0x80000 // Length of PRU memory
#define PRU0_DRAM 0x00000 // Offset to PRU0 DRAM
int main(void) {
unsigned int *pru0_dram; // Points to the start of PRU0 local DRAM
unsigned int *pru; // Points to start of PRU memory.
int fd,i;
fd = open ("/dev/mem", O_RDWR | O_SYNC);
if (fd == -1) {
printf ("ERROR: could not open /dev/mem.\n\n");
return 1;
}
pru = mmap (0, PRU_LEN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PRU_ADDR);
if (pru == MAP_FAILED) {
printf ("ERROR: could not map memory.\n\n");
return 1;
}
close(fd);
pru0_dram = pru + PRU0_DRAM/4 + 0x200/4; // Points to 0x200 of PRU0 memory
adc_en=0; //disable adc (ie. write 0 to pru0_dram[0])
sample_done=0; //clear ready flag
num_samples=NUM_SAMPLES;
while(1){
adc_en=0x01;
while(sample_done==0); //wait for pru0 to finish writing
adc_en=0;
for(i=0;i<NUM_SAMPLES;i++){ //print samples, shifting 2
printf("0x%x ", pru0_dram[SAMPLE_OFFSET+i] >> 2);
}
printf("\n");
sample_done=0; //reset ready flag
adc_en=0x00;
usleep(1000000); //sleep 1 second
}
}
THanks again!