Mono Channel with I2S

Status
Not open for further replies.

JurMa1

Member
Hello everyone,

I want to read out some information from a MEMS Microphone with I2S. I Bought myself a Teensy 3.2 and a MEMS microphone from adafruit (https://learn.adafruit.com/adafruit-i2s-mems-microphone-breakout/). I connected the Pins (3V to +3.3V, GND to GND, BCLK to Pin 9, Dout to Pin 13, LRCL to Pin 23 and SEL ist connected to nothing) and with the Audio Library everything works. The Point is that i want to have less Code as possible and my first goal was to write out every single Sample which has been recorded (so i can see everything in the Arduino IDE Plotter). The full code is under this post.

To do that i looked into the Audio Library and extracted some parts of it. I do not fully understand what every line makes and I don't know where i can research that. Do you have any tips where i could find help about this theme (mainly it is the function config_i2s).
After i copied some parts of the code the program was executing on the Teensy but every second sample was a zero. I think this was because I only used one channel (?) (only used one micro so i got mono sound) and therefore the second channel was left empty. Is there a possibility that I2S is only sending the information of the mono channel or would it be easier just to increase the Pointer by 2 everytime he readout a sample?


Code:
#include <DMAChannel.h>


#ifndef AUDIO_BLOCK_SAMPLES
#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
#define AUDIO_BLOCK_SAMPLES  128
#elif defined(__MKL26Z64__)
#define AUDIO_BLOCK_SAMPLES  64
#endif
#endif


typedef struct audio_block_struct {
	uint8_t  ref_count;
	uint8_t  reserved1;
	uint16_t memory_pool_index;
	int16_t  data[AUDIO_BLOCK_SAMPLES];
} audio_block_t;	// I am going to short this out in the future versions because i only need the audiodata and therefore a struct is not necessary

static DMAChannel dma;
DMAMEM static uint32_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES];
audio_block_t audiodata;
static audio_block_t *mainblock = &audiodata;
static uint16_t block_offset;

#if F_CPU == 96000000 || F_CPU == 48000000 || F_CPU == 24000000
// PLL is at 96 MHz in these modes
#define MCLK_MULT 2
#define MCLK_DIV  17
#elif F_CPU == 72000000
#define MCLK_MULT 8
#define MCLK_DIV  51
#elif F_CPU == 120000000
#define MCLK_MULT 8
#define MCLK_DIV  85
#elif F_CPU == 144000000
#define MCLK_MULT 4
#define MCLK_DIV  51
#elif F_CPU == 168000000
#define MCLK_MULT 8
#define MCLK_DIV  119
#elif F_CPU == 180000000
#define MCLK_MULT 16
#define MCLK_DIV  255
#define MCLK_SRC  0
#elif F_CPU == 192000000
#define MCLK_MULT 1
#define MCLK_DIV  17
#elif F_CPU == 216000000
#define MCLK_MULT 8
#define MCLK_DIV  153
#define MCLK_SRC  0
#elif F_CPU == 240000000
#define MCLK_MULT 4
#define MCLK_DIV  85
#elif F_CPU == 16000000
#define MCLK_MULT 12
#define MCLK_DIV  17
#else
#error "This CPU Clock Speed is not supported by the Audio library";
#endif

#ifndef MCLK_SRC
#if F_CPU >= 20000000
#define MCLK_SRC  3  // the PLL
#else
#define MCLK_SRC  0  // system clock
#endif
#endif

void config_i2s(void)		// what exactly is happening here? Is there any documentation about this?/ what do I have to change to have a mono channel? 
{
	SIM_SCGC6 |= SIM_SCGC6_I2S; // System Clock Gating Control Register 6 -> I2S Clock Gate Control
	SIM_SCGC7 |= SIM_SCGC7_DMA; // System Clock Gating Control Register 7 -> DMA Clock Gate Control
	SIM_SCGC6 |= SIM_SCGC6_DMAMUX; // System Clock Gating Control Register 6 -> DMA Mux Clock Gate Control

	// if either transmitter or receiver is enabled, do nothing
	if (I2S0_TCSR & I2S_TCSR_TE) return; // SAI Transmit Control Register ; Transmitter Enable
	if (I2S0_RCSR & I2S_RCSR_RE) return;// SAI Receive Control Register ; Receiver Enable

	// enable MCLK output
	I2S0_MCR = I2S_MCR_MICS(MCLK_SRC) | I2S_MCR_MOE; // SAI (Serial audio Interface) MCLK Control Register -> MCLK Input Clock Select | MCLK Output Enable
	while (I2S0_MCR & I2S_MCR_DUF); // SAI MCLK Control Register & Divider Update Flag
	I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT - 1)) | I2S_MDR_DIVIDE((MCLK_DIV - 1)); // SAI MCLK Divide Register = MCLK Fraction | MCLK Divide

	// configure transmitter
	I2S0_TMR = 0; // SAI Transmit Mask Register
	I2S0_TCR1 = I2S_TCR1_TFW(1);  // SAI Transmit Configuration 1 Register = Transmit FIFO watermark (watermark at half fifo size)
	I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) // SAI Transmit Configuration 2 Register = async | Bit clock polarity | MCLK select, 0=bus clock, 1=I2S0_MCLK
		| I2S_TCR2_BCD | I2S_TCR2_DIV(1);						//  Bit clock direction | Bit clock divide by (DIV+1)*2
	I2S0_TCR3 = I2S_TCR3_TCE;	// SAI Transmit Configuration 3 Register = transmit channel enable
	I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF // SAI Transmit Configuration 4 Register = Frame Size | Sync Width | MSB First
		| I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD;	//Frame Sync Early | Frame Sync Polarity | Frame Sync Direction
	I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); // SAI Transmit Configuration 5 Register = Word N Width | Word 0 Width | First Bit Shifted

	// configure receiver (sync'd to transmitter clocks)
	I2S0_RMR = 0; // SAI Receive Mask Register
	I2S0_RCR1 = I2S_RCR1_RFW(1); //   SAI Receive Configuration 1 Register =  Receive FIFO watermark
	I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) // SAI Receive Configuration 2 Register | async with receiver | Bit clock polarity|MCLK select, 0=bus clock, 1=I2S0_MCLK
		| I2S_RCR2_BCD | I2S_RCR2_DIV(1);	// Bit clock direction | Bit clock divide by (DIV+1)*2
	I2S0_RCR3 = I2S_RCR3_RCE; // SAI Receive Configuration 3 Register | receive channel enable
	I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF // SAI Receive Configuration 4 Register | Frame Size | Sync Width | MSB First
		| I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD;	// Frame Sync Early | Frame Sync Polarity | Frame Sync Direction
	I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); // SAI Receive Configuration 5 Register | Word N Width | Word 0 Width | 

	// configure pin mux for 3 clock signals
	CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK)
	CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin  9, PTC3, I2S0_TX_BCLK
	CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK
}


void isr()		// inherited from the input_i2s.cpp
{
	uint32_t daddr, offset;
	const int16_t *src, *end;
	int16_t *dest;
	audio_block_t *block;

	//digitalWriteFast(3, HIGH);
#if defined(KINETISK)
	daddr = (uint32_t)(dma.TCD->DADDR);
#endif
	dma.clearInterrupt();   // Set Clear Interrupt Request Register

	if (daddr < (uint32_t)i2s_rx_buffer + sizeof(i2s_rx_buffer) / 2)
	{
		// DMA is receiving to the first half of the buffer
		// need to remove data from the second half
		src = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES / 2];   
		end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES];   
	}
	else
	{
		// DMA is receiving to the second half of the buffer
		// need to remove data from the first half
		src = (int16_t *)&i2s_rx_buffer[0];   
		end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES / 2];   
	}
	block = mainblock;
	if (block != NULL)
	{
		offset = block_offset;
		if (offset <= AUDIO_BLOCK_SAMPLES / 2)
		{
			dest = &(block->data[offset]);
			do
			{
				Serial.println(*src);
				*dest++ = *src++; 	// Or better Change here to	*dest++ = *src
							// 				src += 2
			} while (src < end);
		}
	}
}




void setup()
{
	Serial.begin(9600);
	Serial.print("Number of Samples:");
	Serial.println(AUDIO_BLOCK_SAMPLES);

	dma.begin(true); // Allocate the DMA channel first
	config_i2s();
	CORE_PIN13_CONFIG = PORT_PCR_MUX(4);
#if defined(KINETISK)
	dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0 + 2);     // What is happening here? 
	dma.TCD->SOFF = 0;
	dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1);
	dma.TCD->NBYTES_MLNO = 2;
	dma.TCD->SLAST = 0;
	dma.TCD->DADDR = i2s_rx_buffer;
	dma.TCD->DOFF = 2;
	dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2;
	dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer);
	dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2;
	dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
#endif
	dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX);    
	dma.enable();  

	I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR;  //SAI Receive Control Register
	I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE; // TX clock enable, because sync'd to TX
	dma.attachInterrupt(isr);   
}


void loop()
{
	// put your main code here, to run repeatedly:

}
 
I2S is fundamentally a stereo audio protocol.

I don't see what the point of "have less Code as possible" is. Maybe an academic assignment? In any case, at least you can compare to the working audio library code.
 
So you mean it is not possible to have a mono audio with I2S?

No, you can attach say a single I2S Microphone to I2S and ignore the 2nd channel.
The Audio library handles both channels, but if you write your own code you can certainly do it.

Or, if you use the Audio library GUI, simply do not connect the second channel and the data of the second channel go to Nirwana.
 
The Audio library handles both channels, but if you write your own code you can certainly do it.

Is there a documentation or a example code where i can get information from? Or how can i get the information what parts of the code i have to change to achieve this?

Or, if you use the Audio library GUI, simply do not connect the second channel and the data of the second channel go to Nirwana.

I'm already reading every second example so that i am ignoring the second one but I'm asking myself if there is a good possibility to only have one channel.
 
Is there a documentation or a example code where i can get information from?

Well, all the audio library objects, and this explanation of how they work:

https://www.pjrc.com/teensy/td_libs_AudioNewObjects.html

Or how can i get the information what parts of the code i have to change to achieve this?

By studying the code, and perhaps reading about the I2S protocol (other websites, google is your friend), and of course the chip reference manual chapter on I2S.

Look, I and others have put a tremendous amount of work into the audio library to make all of this as easy as possible for you.

If you *really* want to do things the hard way, you certainly can. But please be realistic about support. You're going to have to figure most of this out on your own. At least there already is good, very well tested code for you to read and use while learning.

Every day I write several very detailed messages on this forum to answer questions. I really do want to help everyone build awesome projects. But there are only so many hours in every day, and *many* software & hardware dev tasks to do. I just can't put much time into helping you redo all the audio library code. The code that's already published works very well and is very efficient. Most of the "support" I can offer revolves around using the already-working library. If you want to rewrite it all, you can, but I can't put more time into assisting you to do so.

I'm already reading every second example so that i am ignoring the second one but I'm asking myself if there is a good possibility to only have one channel.

I2S is fundamentally 2 channels. One is signaled with LRCLK is low, the other when LRCLK is high. If you only want one, it's on you to ignore the other.
 
Status
Not open for further replies.
Back
Top