Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 7 of 7

Thread: Writing Directly to SGTL5000 CODEC DACs

  1. #1
    Junior Member
    Join Date
    Sep 2020
    Posts
    8

    Writing Directly to SGTL5000 CODEC DACs

    I am new to the forum and in my application I am using a Teensy4.1 with the Audio Shield and attempting to do some digital signal processing for a class I am teaching.

    Has anyone come up with a simple way to write to the SGTL500 DAC outputs for testing purposes without using the full Audio Library functionality for passing data blocks? I know how to create an Audio Library elements that will do it, but for my application, I want to be able to update the DAC outputs at specific times that I control.

    What I would like to be able to do is this:

    AudioOutputI2S i2s1;
    AudioControlSGTL5000 sgtl5000_1;
    sgtl5000_1.enable();

    //////////////////////////
    //code to write to DACs
    //////////////////////////

    Any suggestions would be appreciated.

    Neal

  2. #2
    Senior Member
    Join Date
    Apr 2020
    Location
    Tucson
    Posts
    124
    The library isn't that big and the dac can't just be sent data whenever, in order for it to output a signal it has to have a constant flow of data. Why not make a minimal component using an output and two "DC" inputs? You can drive these at 5uS intervals (IOW way above sample rate) with hundreds of lines of code.

  3. #3
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    22,637
    Quote Originally Posted by Neal View Post
    Has anyone come up with a simple way to write to the SGTL500 DAC outputs for testing purposes
    That really depends upon what you consider "simple".

    Just yesterday I answered a question about transmitting a special 20 bit synchronous serial protocol by repurposing the digital audio hardware.

    https://forum.pjrc.com/threads/62819...-on-Teensy-4-1

    Maybe the code on msg #9 and #17 in that thread can help?

    To explain just briefly, the basic idea is to copy all the audio library code which configures the SAI port to transmit & receive in a particular format (and in that thread, bend it to achieve that special 20 bit protocol). But when you set up the SAI hardware, do *not* set the I2S_TCSR_FRDE bit which causes it to request DMA. See the "not using DMA" comment in that code on those 2 messages.

    Then when the SAI hardware is ready to go, instead of configuring a DMA channel, you use this:

    Code:
        attachInterruptVector(IRQ_SAI1, isr);
        NVIC_ENABLE_IRQ(IRQ_SAI1);
        I2S1_TCSR |= 1<<8;  // start generating TX FIFO interrupts
    Setting bit #8 in I2S1_TCSR causes the hardware to request interrupts when it wants more data. Your isr() function gets called, because of attachInterruptVector() and NVIC_ENABLE_IRQ().

    Inside isr(), you must write to I2S1_TDR0. If you've set up the hardware to transmit on multple pins, you must also write to I2S1_TDR1, I2S1_TDR2 or I2S1_TDR3 as needed. If you don't write to these TX FIFO registers as the hardware expects, it will keep asserting the interrupt request and your program will become an infinite loop of running only that interrupt function. For all practical purposes, your program crashes. Expect to need to press the pushbutton on your Teensy to initiate the next code upload, since that infinite looping will block servicing the USB interrupt. (if you're doing this as a class and expect students to implement it, be ready for this pain point....)

    Hopefully that answers your question, but I'm curious if you consider it "simple"?
    Last edited by PaulStoffregen; 09-23-2020 at 09:41 PM. Reason: typos

  4. #4
    Junior Member
    Join Date
    Sep 2020
    Posts
    8
    Thanks for the replies. I think I should have explained better what I am doing better in my original post. I got something working last night that I will include for others to view.

    My goal is to have the DACs running continuously at the 44100kHz (will change later) rate. I want to have the DACs output continuously from a single 32bit memory location containing right/left data. The single output 32bit word in the code below is called myi2s_tx_buffer[0]. The output is continuous, and the value in myi2s_tx_buffer[0] can change asynchronously by other program elements. It needs more work, but it is functioning. The code I am using to control the i2s and DMA is extracted from the Audio Library output_i2s.cpp.

    The following are code excerpts from a simple stereo ramp generator I have running:

    void setup()
    {
    Serial.begin(115200);
    CodecDAC_begin();
    sgtl5000_1.enable(); // sets up the CODEC chip
    sgtl5000_1.volume(.75);
    while(1){
    myi2s_tx_buffer[0] += 0x01000200 ; // triangle ramp right and left; Dac currently outputs at 44.1 kHz update rate
    delayMicroseconds(10);
    }
    }

    void CodecDAC_config_i2s(void)
    {
    CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); //enables SAI1 clock in CCM_CCGR5 register

    //PLL:
    int fs = AUDIO_SAMPLE_RATE_EXACT;
    // PLL between 27*24 = 648MHz und 54*24=1296MHz
    int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
    int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);

    double C = ((double)fs * 256 * n1 * n2) / 24000000;
    int c0 = C;
    int c2 = 10000;
    int c1 = C * c2 - (c0 * c2);
    set_audioClock(c0, c1, c2, false);

    // clear SAI1_CLK register locations
    CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK))
    | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4; first part clears all bits except SAI1_CLK_SEL; second part choosing PLL4 currently
    CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
    | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07
    | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f

    // Select MCLK
    IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 // master clock is an output and something else?
    & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK))
    | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0));

    CORE_PIN23_CONFIG = 3; //1:MCLK
    CORE_PIN21_CONFIG = 3; //1:RX_BCLK
    CORE_PIN20_CONFIG = 3; //1:RX_SYNC

    int rsync = 0;
    int tsync = 1;

    I2S1_TMR = 0; // no masking
    //I2S1_TCSR = (1<<25); //Reset
    I2S1_TCR1 = I2S_TCR1_RFW(1);
    I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async;
    | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1));
    I2S1_TCR3 = I2S_TCR3_TCE;
    I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF
    | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP;
    I2S1_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1));

    I2S1_RMR = 0;
    //I2S1_RCSR = (1<<25); //Reset
    I2S1_RCR1 = I2S_RCR1_RFW(1);
    I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP // sync=0; rx is async;
    | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1));
    I2S1_RCR3 = I2S_RCR3_RCE;
    I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF
    | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;
    I2S1_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1));
    }

    void CodecDAC_begin(void)
    {
    CodecDAC_dma.begin(true); // Allocate the DMA channel first

    CodecDAC_config_i2s();

    CORE_PIN7_CONFIG = 3; //1:TX_DATA0 pin 7 on uP
    CodecDAC_dma.TCD->SADDR = myi2s_tx_buffer; //source address
    CodecDAC_dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes
    CodecDAC_dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // specifies 16 bit source and destination
    CodecDAC_dma.TCD->NBYTES_MLNO = 2; // bytes to transfer for each service request///////////////////////////////////////////////////////////////////
    CodecDAC_dma.TCD->SLAST = -sizeof(myi2s_tx_buffer); // last source address adjustment
    CodecDAC_dma.TCD->DOFF = 0; // increments at destination
    CodecDAC_dma.TCD->CITER_ELINKNO = sizeof(myi2s_tx_buffer) / 2;
    CodecDAC_dma.TCD->DLASTSGA = 0; // destination address offset
    CodecDAC_dma.TCD->BITER_ELINKNO = sizeof(myi2s_tx_buffer) / 2;
    CodecDAC_dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; // enables interrupt when transfers half and full complete
    CodecDAC_dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); // I2S1 register DMA writes to
    CodecDAC_dma.triggerAtHardwareEvent(DMAMUX_SOURCE_ SAI1_TX); // i2s channel that will trigger the DMA transfer when ready for data
    CodecDAC_dma.enable();

    I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
    I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;

    CodecDAC_dma.attachInterrupt(CodecDAC_isr);
    }

    void CodecDAC_isr(void)
    {
    pinMode(3,OUTPUT);
    digitalToggleFast(3);

    CodecDAC_dma.clearInterrupt();
    delayMicroseconds(1); ///////////////////////need this but I don't know why yet, but problem without at least a 100 nsec delay?????
    }

    I need to spend more time digesting what is being proposed in your responses. Hopefully there is a even simpler solution. As I said, my approach needs more work.

    Cheers
    Neal

  5. #5
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    22,637
    Quote Originally Posted by Neal View Post
    I want to have the DACs output continuously from a single 32bit memory location containing right/left data.
    First try a 16 bit location which will output both channels. To do that, just change SOFF and SLAST to zero, so the DMA doesn't ever change the source address. Then it will just read from the same 16 bits every time.

    Maybe also set CSR to zero rather than setting those 2 interrupt enable bits, if you want the DMA to just run without giving you any interrupts.

  6. #6
    Junior Member
    Join Date
    Sep 2020
    Posts
    8
    Paul, both excellent suggestions. I left the SOFF and SLAST that way in case I wanted to use a larger buffer later. I left the completion interrupt in so I could monitor that things were running by toggling pin3 in the isr. That can easily go away now.

    Currently I am globally declaring the myi2s_tx_buffer[] like this:

    static volatile uint32_t myi2s_tx_buffer[1] = {0x00000000};

    I tried to do it the same as it is in the Audio Library like this:

    DMAMEM __attribute__((aligned(32))) static uint32_t myi2s_tx_buffer[1] = {0};

    It compiled OK, but myi2s_tx_buffer[0] never gets modified by myi2s_tx_buffer[0] += 0x01000200 instruction in the while loop.

    Do you have any idea why?

    Thanks for your help!
    Neal

  7. #7
    Junior Member
    Join Date
    Sep 2020
    Posts
    8
    To answer my own question in my last post, I had to add a data cache management functions must be used to flush cached data after data after each output to the CODEC. The function I used I saw in the Audio Library output_i2s.cpp

    arm_dcache_flush_delete(myi2s_tx_buffer, sizeof(myi2s_tx_buffer));

    That allowed me to declare the tx buffer in DMAMEM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •