Library to control and transfer data from NXP SGTL5000. As used on the Teensy Audio Shield. It uses DMA to transfer I2S FIFO data.

The Library now supports dual codecs. Allowing all 4 channels of the Teensy I2S interface to RX and TX data to separate SGTL5000 devices.

The ISR routines that handles pointer swaps for double buffering has been fully coded in assembler to reduce overhead and now takes < 800nS per FIFO transfer when using all 4 channels.

Support added for all typical sample rates and system Clock speeds of 96Mhz or 120Mhz.

Pause and Resume functions added to allow quick and simple suppression of IRQs and stream halting and restart. This required software triggered IRQ, in order to ensure accurate word sync control.

Revision:
2:a9d8242f76ea
Parent:
1:d48e64f611fb
Child:
3:62c03088f256
--- a/sgtl5000.cpp	Thu Jun 15 09:46:58 2017 +0000
+++ b/sgtl5000.cpp	Thu Jun 15 13:59:26 2017 +0000
@@ -420,13 +420,18 @@
     NVIC_SetVector((IRQn)SGTL5000::RX_DMAch, (uint32_t)&SGTL5000::sync_dma_ISR);                        // Set DMA RX handler vector
     NVIC_SetPriority((IRQn)SGTL5000::TX_DMAch, DMA_irq_pri);                                            // Set irq priorities the same
     NVIC_SetPriority((IRQn)SGTL5000::RX_DMAch, DMA_irq_pri);
-    NVIC_EnableIRQ((IRQn)SGTL5000::TX_DMAch);                                                           // Enable IRQ for chosen TX DMA channel
-    NVIC_EnableIRQ((IRQn)SGTL5000::RX_DMAch);                                                           // Disable IRQ for chosen RX DMA channel
-    NVIC_SetVector(I2S0_Tx_IRQn, (uint32_t)&SGTL5000::sync_I2S_ISR);                                    // Set vector for SYNC word start ISR
-    NVIC_SetPriority(I2S0_Tx_IRQn, 0);                                                                  // Set priority of SYNC word start ISR
-    NVIC_EnableIRQ(I2S0_Tx_IRQn);                                                                       // Enable SYNC word start ISR using TX direction
-    I2S0->TCSR |= I2S_TCSR_WSF_MASK;                                                                    // Clear TX Word Start Flag
-    I2S0->TCSR |= I2S_TCSR_WSIE_MASK;                                                                   // Enable I2S TX word start IRQ
+    if(SGTL5000::TX_DMAch > SGTL5000::RX_DMAch) {
+        NVIC_EnableIRQ((IRQn)SGTL5000::RX_DMAch);                                                       // Enable IRQ for chosen RX DMA channel if using lower priority DMA channel (lower channel number). This assumes fixed priority encoding and pre-emption, (which are default).
+        NVIC_DisableIRQ((IRQn)SGTL5000::TX_DMAch);                                                      // Disable IRQ for chosen TX DMA channel. We must wait for the lowest priority channel to trigger the DMA IRQ.
+    } else {
+        NVIC_EnableIRQ((IRQn)SGTL5000::TX_DMAch);                                                       
+        NVIC_DisableIRQ((IRQn)SGTL5000::RX_DMAch);
+    }
+    NVIC_SetVector(I2S0_Rx_IRQn, (uint32_t)&SGTL5000::sync_I2S_ISR);                                    // Set vector for SYNC word start ISR
+    NVIC_SetPriority(I2S0_Rx_IRQn, 0);                                                                  // Set priority of SYNC word start ISR
+    NVIC_EnableIRQ(I2S0_Rx_IRQn);                                                                       // Enable SYNC word start ISR using RX direction
+    I2S0->RCSR |= I2S_RCSR_WSF_MASK;                                                                    // Clear TX Word Start Flag
+    I2S0->RCSR |= I2S_RCSR_WSIE_MASK;                                                                   // Enable I2S TX word start IRQ
     return 0;
 }
 
@@ -456,7 +461,7 @@
 
 void SGTL5000::sync_I2S_ISR(void)
 {
-    I2S0->TCSR &= ~I2S_TCSR_WSIE_MASK;                                                                  // Disable TX word start IRQs
+    I2S0->RCSR &= ~I2S_RCSR_WSIE_MASK;                                                                  // Disable TX word start IRQs
     I2S0->TCSR |= I2S_TCSR_FR_MASK;                                                                     // Reset TX FIFO pointers
     I2S0->RCSR |= I2S_RCSR_FR_MASK;                                                                     // Reset RX FIFO pointers
     I2S0->TCSR |= I2S_TCSR_FRDE_MASK;                                                                   // Enable DMA request based on TX FIFO watermark
@@ -465,6 +470,9 @@
 
 void SGTL5000::sync_dma_ISR(void)
 {
+    static uint32_t t2;
+    static uint32_t t1;
+
     /*
     The RX and TX buffers are each 16 * 32bit words, which allows the FIFO to be, at a maximum,
     fully emptied or filled in each DMA request. Each buffer is arranged firstly in 2 halves.
@@ -476,10 +484,14 @@
     buffer layout with Block Size = 2               Block Size = 4                          Block Size = 8
     Double Buffer A Double Buffer B                 Double Buffer A Double Buffer B         Double Buffer A Double Buffer B
     |L|R|x|x|x|x|x|x:L|R|x|x|x|x|x|x|               |L|L|R|R|x|x|x|x:L|L|R|R|x|x|x|x|       |L|L|L|L|R|R|R|R:L|L|L|L|R|R|R|R|
+    
+    When running both TX & RX synchronously, only 1 direction has its IRQ enabled in the NVIC, which is enabled is determined by the priority of the DMA channels.  In sync mode TX & RX DMAs transfer
+    the same number of bytes to the FIFO, therefore we should see only 1 FIFO word difference between DMA demands for TX or RX. This measn the DMA transfers will be pre-empting each other,
+    dependant on relative priority. Therefore before the user ISR is called we must be sure that the lowest priority channel has completed its transfer and it is this channel that has its IRQ enabled.
+    We clear both flags here, but the active IRQ is chosen in the start_SYNC function. This avoids servicing an extra IRQ
+    therby saving a few cycles in avoiding an extra stack operation.
     */
     static uint32_t db_sync_phase = 0;
-    static uint32_t TX_DMA_Complete = 0;
-    static uint32_t RX_DMA_Complete = 0;
     static uint32_t dbA_rx_L = (uint32_t)&SGTL5000::I2S_RX_Buffer[0];                                   // Pre-compute buffer offsets etc to save cycles in ISR
     static uint32_t dbA_rx_R = (uint32_t)&SGTL5000::I2S_RX_Buffer[SGTL5000::RX_block_size / 2];
     static uint32_t dbA_tx_L = (uint32_t)&SGTL5000::I2S_TX_Buffer[0];
@@ -491,19 +503,10 @@
     static uint32_t RX_DMA_int_mask = 0x1 << SGTL5000::RX_DMAch;
     static uint32_t TX_DMA_int_mask = 0x1 << SGTL5000::TX_DMAch;
 
-    if(DMA0->INT & RX_DMA_int_mask) {
-        DMA0->CINT = DMA_CINT_CINT(SGTL5000::RX_DMAch);
-        ++RX_DMA_Complete;
-    }
-    if(DMA0->INT & TX_DMA_int_mask) {
-        DMA0->CINT = DMA_CINT_CINT(SGTL5000::TX_DMAch);
-        ++TX_DMA_Complete = true;
-    }
+    DMA0->CINT = DMA_CINT_CINT(SGTL5000::RX_DMAch);                                                 // Clear RX DMA IRQ flag
+    DMA0->CINT = DMA_CINT_CINT(SGTL5000::TX_DMAch);                                                 // Clear TX DMA IRQ flag
+    
 
-    if(TX_DMA_Complete && RX_DMA_Complete) {
-        --TX_DMA_Complete;
-        --RX_DMA_Complete;
-    } else return;
 
     if(db_sync_phase) {                                                                                 // Swap double buffer pointers with pre-computed indecies
         *SGTL5000::BufRX_L_safe  = dbB_rx_L;
@@ -521,6 +524,7 @@
     if(SGTL5000::SYNC_attach_type) {                                                                    // Trigger swIRQ or call Callback
         if(NVIC_GetActive(SGTL5000::SYNC_swIRQ) == 0) NVIC->STIR = SGTL5000::SYNC_swIRQ;
     } else SGTL5000::SYNC_user_func.call();
+    
 }
 
 
@@ -828,7 +832,7 @@
 
 uint32_t SGTL5000::read_debug(uint32_t index)
 {
-    //SGTL5000::debug[0] = 
+    //SGTL5000::debug[0] =
     return SGTL5000::debug[index];
 };
 }
\ No newline at end of file