audio DAC for Teensy 4 - what one? MCP4725?

Status
Not open for further replies.

1of9

Member
I have a MCP4725 wired up to a T4 on pins 16 and 17 and it "works" but I may have chosen the wrong thing. What I want to do is have non-blocking way to play short (1 sec or so) audio samples. I was thinking I could use DMA over I2C but apparently there is no library to do that on the T4 (did not see anything dma related in wire). Do I need a I2S device? The amp the dac would be driving has an input range of +/-300mv.
 
The Teensy 4.0 does not have a real DAC. I was investigating how to play sounds without using an audio adapter, and came up with two simple solutions:

If you aren't using SPI, you can use the MQS pins (10 and 12). Here is a sketch I wrote to test MQS:

Here is a test I wrote to try out an I2S to mono adapter I bought:

In the future, you might be able to use pins 14 & 15 for S/PDIF output, but the Teensy 4.0 support is not yet written.
 
For audio, I2S is the preferred digital protocol, and many DACs exist on the market which support it. So, I’d say this is the way to go.
 
My goal here is just to be able to do non-blocking playing of short audio clips on the T4 and I can't use the Audio Shield because it is far too large to fit in my project. The MCP4725 board is about 15mm square and I am looking for a board 20mm square at max if I can't use the MCP4725. Before I go buy another board to try I was looking for a confirmation from someone that whatever board I got would actually work and do what I needed it to do on the T4. I see that you can do I2c inside an ISR on the i2c_t3 library but I think that library does not work with the T4. Has anyone used any DAC in a non-blocking way on the T4 other than the Audio Shield?
 
Ok, here are some ways to do what I wanted.

For both methods below we want to use the 1mhz I2C clock and send data to the 4725 in the three byte mode (address + 2 data bytes) and not the slower 4 byte mode. IntervalTimer at whatever sample rate you want (*see note at end) .

The three byte mode uses [0] = address, [1] = 0000xxxx where xxxx = the upper 4 bits of the 12 bit data and the four zeros set the dac speed and power registers [2] = lower 8 bits of the 12bit data

The safe way: (IntervalTimer at 125 microseconds - 8k samples sent per second)
Push the address and two data bytes in.
call endTransmission() - 23% cpu time spent in ISR
slowest as it waits for each byte to be sent and all the error checking to be ok.

call endTransmission(false) - 15.5% cpu time spent in ISR
- faster - the code does not wait to see what happened to the last byte.

Either way of doing it means you will lose quite a bit of cpu time waiting for everything to transmit and be checked.
The shorter your IntervalTimer/higher the sample rate, the more CPU you waste waiting.

The fastest and dirty way (works for me):

endTransmissionFIFO() < 1% time spent in ISR at 125 microsecond interval

Code:
uint8_t TwoWire::endTransmissionFIFO(void)
{
	/*
		1of9's lossy two wire v0.1 - the goal of this function is to shove data into the fifo and return quickly
		assumes only one master and slave and that we have control of the bus - no Arbitration checking
		clears the fifo and shoves up to 4 bytes into it - minimal error checking - use at your own risk
		seriously - this method just does not care about anything other than too much or no data available 
	*/

	if (!txBufferLength) return 4; // no data to transmit
	if (txBufferLength > 4) return 1; // data too large for FIFO

	port->MCR = LPI2C_MCR_MEN | LPI2C_MCR_RTF;  // clear the FIFO
	port->MSR = LPI2C_MSR_PLTF | LPI2C_MSR_ALF | LPI2C_MSR_NDF | LPI2C_MSR_SDF | LPI2C_MSR_FEF; // clear flags

	port->MTDR = LPI2C_MTDR_CMD_START | txBuffer[0];
	if (txBufferLength > 1) port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | txBuffer[1];
	if (txBufferLength > 2) port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | txBuffer[2];
	if (txBufferLength > 3) port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | txBuffer[3];	

	return 0;
}

endTransmissionFIFO at 32 microsecond interval - 3% cpu time spent in ISR

* Theortical maximum send rate is about 30k'ish samples per second (24 bits data + control bits + misc dead time) and I get a clean output at a 32 microsecond interval on the timer.

There is jitter in the output and this is far from being "HiFi" but it does what I wanted.
 
Last edited:
Just use an I2S DAC like a PCM5102. DMA will take care of the transfers for you, and it'll sound great.
 
I needed something very small and the 4725 fit the bill, the sound quality is fine for what I am doing with it. If I had the room the PCM5102 would be a good choice. I am glad I went this route because I learned some things about how the twowire library works on T4 and how to do some interesting things. The Teensy 4 hardware specs say there is a 4 byte fifo for i2c but what it does not say is that the wire library does not really use that fifo, at least not in the way I would want it to. The way it is implimented in the library, it may as well not even have a fifo beacuse it just sends one byte at a time and blocks waiting to check the status of each sent byte except for the last byte if you call endtransmission with false. Not saying there is anything wrong with the way the library works, it is geared towards being reliable and I understand that but there are some intersting and useful things that can be done with the fifo and this is one of them.
 
Status
Not open for further replies.
Back
Top