1
\$\begingroup\$

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!

\$\endgroup\$
3
  • 1
    \$\begingroup\$ The code comment says there is a 0.25 µs delay after CS is pulled low. Is that too long? The datasheet says td1 max is 15/11 ns. Perhaps the ADS7883 remains in power-down mode because of invalid timing and perhaps that pattern you see on SDO bits 8-16 is just crosstalk from the SCLK signal. \$\endgroup\$ Commented Aug 2, 2022 at 21:08
  • \$\begingroup\$ Thank you for taking a look at this, I just tried adjusting that timing, but there was no effect. I thought td1 was the amount of time the chip will take to drive SDO low after CS goes low? tsu1 (Setup time) seems to be saying the the min time from CS low to SCLK low is 7/5ns, but it does not give a max. So i figured .25us was safe. \$\endgroup\$ Commented Aug 3, 2022 at 0:31
  • \$\begingroup\$ Do you think the chip could be going to sleep somehow, so bits 8-16 are Hi-z? \$\endgroup\$ Commented Aug 3, 2022 at 0:32

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.