Skip to content

Commit 28d3ab4

Browse files
committed
Added multi-pin TDM support.
This allows you use use more than one TX or RX pin in TDM mode, allowing for up to 64 channels of record or playback data This fix goes hand-in-hand with the multi-channel USB support. PaulStoffregen/cores#732 input_tdm: every odd channel had every other sample swapped In every odd channel in TDM input (1, 3, 5, 7, 9, 11, 13, 15), every other word was swapped due to an incorrect copy from 32-bits to 16-bits. This fix corrects the odd channels. The Shift-by zeros and the extraneous logical ands are there for clarity, and I verified they don't end up affecting final code optimization as long as optimization is turned on.
1 parent f377760 commit 28d3ab4

File tree

5 files changed

+94
-42
lines changed

5 files changed

+94
-42
lines changed

input_tdm.cpp

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,15 @@
3131
#include "utility/imxrt_hw.h"
3232

3333
DMAMEM __attribute__((aligned(32)))
34-
static uint32_t tdm_rx_buffer[AUDIO_BLOCK_SAMPLES*16];
35-
audio_block_t * AudioInputTDM::block_incoming[16] = {
36-
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
37-
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
38-
};
34+
static uint32_t tdm_rx_buffer[AUDIO_BLOCK_SAMPLES*TDM_IN_N_AUDIO_BLOCKS];
35+
audio_block_t * AudioInputTDM::block_incoming[TDM_IN_N_AUDIO_BLOCKS];
3936
bool AudioInputTDM::update_responsibility = false;
4037
DMAChannel AudioInputTDM::dma(false);
4138

42-
4339
void AudioInputTDM::begin(void)
4440
{
41+
for (int i = 0; i < TDM_IN_N_AUDIO_BLOCKS; i++)
42+
block_incoming[i] = nullptr;
4543
dma.begin(true); // Allocate the DMA channel first
4644

4745
// TODO: should we set & clear the I2S_RCSR_SR bit here?
@@ -68,7 +66,22 @@ void AudioInputTDM::begin(void)
6866
dma.attachInterrupt(isr);
6967
#elif defined(__IMXRT1062__)
7068
CORE_PIN8_CONFIG = 3; //RX_DATA0
71-
IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2;
69+
IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; // TEENSY P8, GPIO_B1_00_ALT3 as rx 0
70+
// When using combine mode, you *must* use the data pins in order.
71+
switch (AUDIO_N_SAI1_RX_DATAPINS) {
72+
case 4:
73+
CORE_PIN32_CONFIG = 3; //RX_DATA3
74+
IOMUXC_SAI1_RX_DATA3_SELECT_INPUT = 1; // TEENSY P32, GPIO_B0_12_ALT3 as rx 1
75+
case 3:
76+
CORE_PIN9_CONFIG = 3; //RX_DATA2
77+
IOMUXC_SAI1_RX_DATA2_SELECT_INPUT = 1; // TEENSY P9, GPIO_B0_11_ALT3 as rx 2
78+
case 2:
79+
CORE_PIN6_CONFIG = 3; //RX_DATA1
80+
IOMUXC_SAI1_RX_DATA1_SELECT_INPUT = 1; // TEENSY P6, GPIO_B0_10_ALT3 as rx 1
81+
break;
82+
default:
83+
break;
84+
}
7285
dma.TCD->SADDR = &I2S1_RDR0;
7386
dma.TCD->SOFF = 0;
7487
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
@@ -89,33 +102,65 @@ void AudioInputTDM::begin(void)
89102
#endif
90103
}
91104

92-
// TODO: needs optimization...
93-
static void memcpy_tdm_rx(uint32_t *dest1, uint32_t *dest2, const uint32_t *src)
94-
{
95-
uint32_t i, in1, in2;
96-
97-
for (i=0; i < AUDIO_BLOCK_SAMPLES/2; i++) {
98-
in1 = *src;
99-
in2 = *(src+8);
100-
src += 16;
101-
*dest1++ = (in1 >> 16) | (in2 & 0xFFFF0000);
102-
*dest2++ = (in1 << 16) | (in2 & 0x0000FFFF);
103-
}
105+
// Since we're grabbing data out of the fifo at 32-bits, but the words
106+
// are 16-bits and we're sharing fifos in a round-robbin manner, with
107+
//
108+
// 1 FIFO (pin) active, the word/channel order for the first frame is
109+
// C00 C02 C04 C06 C08 C10 C12 C14
110+
// C01 C03 C05 C07 C09 C11 C13 C15
111+
//
112+
// 2 FIFO (pin) active, the word/channel order for the first frame is
113+
// C00 C16 C02 C18 C04 C20 C06 C22 C08 C24 C10 C26 C12 C28 C14 C30
114+
// C01 C17 C03 C19 C05 C21 C07 C23 C09 C25 C11 C27 C13 C29 C15 C31
115+
//
116+
// 3 FIFO (pin) active, the word/channel order for the first frame is
117+
// C00 C16 C32 C02 C18 C34 C04 C20 C36 C06 C22 C38 C08 C24 C40 C10 C26 C42 C12 C28 C44 C14 C30 C46
118+
// C01 C17 C33 C03 C19 C35 C05 C21 C37 C07 C23 C39 C09 C25 C41 C11 C27 C43 C13 C29 C45 C15 C31 C47
119+
//
120+
// 4 FIFO (pin) active, the word/channel order for the first frame is
121+
// C00 C16 C32 C48|C02 C18 C34 C50|C04 C20 C36 C52|C06 C22 C38 C54|C08 C24 C40 C56|C10 C26 C42 C58|C12 C28 C44 C60|C14 C30 C46 C62
122+
// C01 C17 C33 C49|C03 C19 C35 C51|C05 C21 C37 C53|C07 C23 C39 C55|C09 C25 C41 C57|C11 C27 C43 C59|C13 C29 C45 C61|C15 C31 C47 C63
123+
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <--- word index
124+
// where each column is a single 32-bit word popped from the fifo
125+
static void deinterleave(audio_block_t *block_incoming[TDM_IN_N_AUDIO_BLOCKS], const uint32_t *src) {
126+
int l = 0;
127+
for (int i=0; i < TDM_IN_N_AUDIO_BLOCKS/2; i++) {
128+
// ix = 0, 2, 4, 6, ... 1 pin
129+
// ix = 0, 16, 2, 18, 3, 20, ... 2 pins
130+
// ix = 0, 16, 32, 2, 18, 33 ... 3 pins
131+
// ix = 0, 16, 32, 48, 2, 18 ... 4 pins
132+
133+
// i = 0, 1, 2, 3, 4, 5,
134+
// ix = 0, 16, 32, 2, 18, 33 ... 3 pins
135+
// = (0*16 1*16 2*16)+i
136+
int ix = (l*16) + i/AUDIO_N_SAI1_RX_DATAPINS*2;
137+
l = (l + 1) % AUDIO_N_SAI1_RX_DATAPINS;
138+
uint32_t *dest1 = (uint32_t *)(block_incoming[ix]->data);
139+
uint32_t *dest2 = (uint32_t *)(block_incoming[ix+1]->data);
140+
const uint32_t *psrc = src;
141+
for (int j=0; j < AUDIO_BLOCK_SAMPLES/2; j++) {
142+
uint32_t in1 = *psrc;
143+
uint32_t in2 = *(psrc + TDM_IN_N_AUDIO_BLOCKS * AUDIO_N_SAI1_RX_DATAPINS / 2);
144+
psrc += TDM_IN_N_AUDIO_BLOCKS;
145+
*dest1++ = ((in1 >> 16) & 0x0000FFFF) | ((in2 << 0) & 0xFFFF0000);
146+
*dest2++ = ((in1 >> 0) & 0x0000FFFF) | ((in2 << 16) & 0xFFFF0000);
147+
}
148+
src++;
149+
}
104150
}
105151

106152
void AudioInputTDM::isr(void)
107153
{
108154
uint32_t daddr;
109155
const uint32_t *src;
110-
unsigned int i;
111156

112157
daddr = (uint32_t)(dma.TCD->DADDR);
113158
dma.clearInterrupt();
114159

115160
if (daddr < (uint32_t)tdm_rx_buffer + sizeof(tdm_rx_buffer) / 2) {
116161
// DMA is receiving to the first half of the buffer
117162
// need to remove data from the second half
118-
src = &tdm_rx_buffer[AUDIO_BLOCK_SAMPLES*8];
163+
src = &tdm_rx_buffer[AUDIO_BLOCK_SAMPLES * TDM_IN_N_AUDIO_BLOCKS / 2];
119164
} else {
120165
// DMA is receiving to the second half of the buffer
121166
// need to remove data from the first half
@@ -125,25 +170,19 @@ void AudioInputTDM::isr(void)
125170
#if IMXRT_CACHE_ENABLED >=1
126171
arm_dcache_delete((void*)src, sizeof(tdm_rx_buffer) / 2);
127172
#endif
128-
for (i=0; i < 16; i += 2) {
129-
uint32_t *dest1 = (uint32_t *)(block_incoming[i]->data);
130-
uint32_t *dest2 = (uint32_t *)(block_incoming[i+1]->data);
131-
memcpy_tdm_rx(dest1, dest2, src);
132-
src++;
133-
}
173+
deinterleave(block_incoming, src);
134174
}
135175
if (update_responsibility) update_all();
136176
}
137177

138178

139179
void AudioInputTDM::update(void)
140180
{
141-
unsigned int i, j;
142-
audio_block_t *new_block[16];
143-
audio_block_t *out_block[16];
144-
145-
// allocate 16 new blocks. If any fails, allocate none
146-
for (i=0; i < 16; i++) {
181+
unsigned int i, j;
182+
audio_block_t *new_block[TDM_IN_N_AUDIO_BLOCKS];
183+
audio_block_t *out_block[TDM_IN_N_AUDIO_BLOCKS];
184+
// allocate TDM_IN_N_AUDIO_BLOCKS new blocks. If any fails, allocate none
185+
for (i=0; i < TDM_IN_N_AUDIO_BLOCKS; i++) {
147186
new_block[i] = allocate();
148187
if (new_block[i] == nullptr) {
149188
for (j=0; j < i; j++) {
@@ -158,8 +197,8 @@ void AudioInputTDM::update(void)
158197
memcpy(block_incoming, new_block, sizeof(block_incoming));
159198
__enable_irq();
160199
if (out_block[0] != nullptr) {
161-
// if we got 1 block, all 16 are filled
162-
for (i=0; i < 16; i++) {
200+
// if we got 1 block, all TDM_IN_N_AUDIO_BLOCKS are filled
201+
for (i=0; i < TDM_IN_N_AUDIO_BLOCKS; i++) {
163202
transmit(out_block[i], i);
164203
release(out_block[i]);
165204
}

input_tdm.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
#include <Arduino.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/Arduino.h
3131
#include <AudioStream.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/AudioStream.h
3232
#include <DMAChannel.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/DMAChannel.h
33+
#include "AudioRate.h"
34+
35+
#define TDM_IN_N_AUDIO_BLOCKS (TDM_CHANNELS_PER_PIN*AUDIO_N_SAI1_RX_DATAPINS)
3336

3437
class AudioInputTDM : public AudioStream
3538
{
@@ -42,7 +45,7 @@ class AudioInputTDM : public AudioStream
4245
static DMAChannel dma;
4346
static void isr(void);
4447
private:
45-
static audio_block_t *block_incoming[16];
48+
static audio_block_t *block_incoming[TDM_IN_N_AUDIO_BLOCKS];
4649
};
4750

4851
#endif

input_tdm2.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ static void memcpy_tdm_rx(uint32_t *dest1, uint32_t *dest2, const uint32_t *src)
7979
in1 = *src;
8080
in2 = *(src+8);
8181
src += 16;
82-
*dest1++ = (in1 >> 16) | (in2 & 0xFFFF0000);
83-
*dest2++ = (in1 << 16) | (in2 & 0x0000FFFF);
82+
*dest1++ = ((in1 >> 16) & 0x0000FFFF) | ((in2 << 0) & 0xFFFF0000);
83+
*dest2++ = ((in1 >> 0) & 0x0000FFFF) | ((in2 << 16) & 0xFFFF0000);
8484
}
8585
}
8686

output_tdm.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2424
* THE SOFTWARE.
2525
*/
26-
2726
#include <Arduino.h>
2827

2928
#if !defined(KINETISL)
@@ -319,20 +318,30 @@ void AudioOutputTDM::config_tdm(void)
319318
I2S1_TCR1 = I2S_TCR1_RFW(4);
320319
I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1)
321320
| I2S_TCR2_BCD | I2S_TCR2_DIV(0);
322-
I2S1_TCR3 = I2S_TCR3_TCE;
321+
322+
// enable all TX data pins. Channels must be used in order, so
323+
// enabled chanels can (0b0001, 0b0011, 0b0111, or 0b1111) only. This
324+
// limitation is because of the fifo combine mode limitation.
325+
I2S1_TCR3 = ((1 << AUDIO_N_SAI1_TX_DATAPINS)-1) << 16;
326+
323327
I2S1_TCR4 = I2S_TCR4_FRSZ(7) | I2S_TCR4_SYWD(0) | I2S_TCR4_MF
324328
| I2S_TCR4_FSE | I2S_TCR4_FSD;
329+
I2S1_TCR4 |= (AUDIO_N_SAI1_TX_DATAPINS > 1) ? I2S_TCR4_FCOMB_ENABLED_ON_WRITES : 0;
325330
I2S1_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31);
326331

327332
I2S1_RMR = 0;
328333
I2S1_RCR1 = I2S_RCR1_RFW(4);
329334
I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1)
330335
| I2S_RCR2_BCD | I2S_RCR2_DIV(0);
331-
I2S1_RCR3 = I2S_RCR3_RCE;
336+
// enable all RX data pins. Channels must be used in order, so
337+
// enabled chanels can (0b0001, 0b0011, 0b0111, or 0b1111) only. This
338+
// limitation is because of the fifo combine mode limitation.
339+
I2S1_RCR3 = ( (1 << AUDIO_N_SAI1_RX_DATAPINS) - 1 ) << 16;
340+
332341
I2S1_RCR4 = I2S_RCR4_FRSZ(7) | I2S_RCR4_SYWD(0) | I2S_RCR4_MF
333342
| I2S_RCR4_FSE | I2S_RCR4_FSD;
343+
I2S1_RCR4 |= (AUDIO_N_SAI1_RX_DATAPINS > 1) ? I2S_RCR4_FCOMB_ENABLED_ON_READS : 0;
334344
I2S1_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31);
335-
336345
CORE_PIN23_CONFIG = 3; //1:MCLK
337346
CORE_PIN21_CONFIG = 3; //1:RX_BCLK
338347
CORE_PIN20_CONFIG = 3; //1:RX_SYNC

output_tdm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <Arduino.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/Arduino.h
3131
#include <AudioStream.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/AudioStream.h
3232
#include <DMAChannel.h> // github.com/PaulStoffregen/cores/blob/master/teensy4/DMAChannel.h
33+
#include "AudioRate.h"
3334

3435
class AudioOutputTDM : public AudioStream
3536
{

0 commit comments

Comments
 (0)