// Multiple input & output objects use the Programmable Delay Block | |||||
// to set their sample rate. They must all configure the same | |||||
// period to avoid chaos. | |||||
#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT) | |||||
#define PDB_PERIOD 1087 // 48e6 / 44100 | |||||
#include "Audio.h" | |||||
#include "arm_math.h" | |||||
DMAMEM static uint16_t analog_rx_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
audio_block_t * AudioInputAnalog::block_left = NULL; | |||||
uint16_t AudioInputAnalog::block_offset = 0; | |||||
bool AudioInputAnalog::update_responsibility = false; | |||||
// #define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT) | |||||
// #define PDB_PERIOD 1087 // 48e6 / 44100 | |||||
void AudioInputAnalog::begin(unsigned int pin) | |||||
{ | |||||
uint32_t i, sum=0; | |||||
// pin must be 0 to 13 (for A0 to A13) | |||||
// or 14 to 23 for digital pin numbers A0-A9 | |||||
// or 34 to 37 corresponding to A10-A13 | |||||
if (pin > 23 && !(pin >= 34 && pin <= 37)) return; | |||||
//pinMode(2, OUTPUT); | |||||
//pinMode(3, OUTPUT); | |||||
//digitalWriteFast(3, HIGH); | |||||
//delayMicroseconds(500); | |||||
//digitalWriteFast(3, LOW); | |||||
// Configure the ADC and run at least one software-triggered | |||||
// conversion. This completes the self calibration stuff and | |||||
// leaves the ADC in a state that's mostly ready to use | |||||
analogReadRes(16); | |||||
analogReference(INTERNAL); // range 0 to 1.2 volts | |||||
//analogReference(DEFAULT); // range 0 to 3.3 volts | |||||
analogReadAveraging(8); | |||||
// Actually, do many normal reads, to start with a nice DC level | |||||
for (i=0; i < 1024; i++) { | |||||
sum += analogRead(pin); | |||||
} | |||||
dc_average = sum >> 10; | |||||
// testing only, enable adc interrupt | |||||
//ADC0_SC1A |= ADC_SC1_AIEN; | |||||
//while ((ADC0_SC1A & ADC_SC1_COCO) == 0) ; // wait | |||||
//NVIC_ENABLE_IRQ(IRQ_ADC0); | |||||
// set the programmable delay block to trigger the ADC at 44.1 kHz | |||||
SIM_SCGC6 |= SIM_SCGC6_PDB; | |||||
PDB0_MOD = PDB_PERIOD; | |||||
PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; | |||||
PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG; | |||||
PDB0_CH0C1 = 0x0101; | |||||
// enable the ADC for hardware trigger and DMA | |||||
ADC0_SC2 |= ADC_SC2_ADTRG | ADC_SC2_DMAEN; | |||||
// set up a DMA channel to store the ADC data | |||||
SIM_SCGC7 |= SIM_SCGC7_DMA; | |||||
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; | |||||
DMA_CR = 0; | |||||
DMA_TCD2_SADDR = &ADC0_RA; | |||||
DMA_TCD2_SOFF = 0; | |||||
DMA_TCD2_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); | |||||
DMA_TCD2_NBYTES_MLNO = 2; | |||||
DMA_TCD2_SLAST = 0; | |||||
DMA_TCD2_DADDR = analog_rx_buffer; | |||||
DMA_TCD2_DOFF = 2; | |||||
DMA_TCD2_CITER_ELINKNO = sizeof(analog_rx_buffer) / 2; | |||||
DMA_TCD2_DLASTSGA = -sizeof(analog_rx_buffer); | |||||
DMA_TCD2_BITER_ELINKNO = sizeof(analog_rx_buffer) / 2; | |||||
DMA_TCD2_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
DMAMUX0_CHCFG2 = DMAMUX_DISABLE; | |||||
DMAMUX0_CHCFG2 = DMAMUX_SOURCE_ADC0 | DMAMUX_ENABLE; | |||||
update_responsibility = update_setup(); | |||||
DMA_SERQ = 2; | |||||
NVIC_ENABLE_IRQ(IRQ_DMA_CH2); | |||||
} | |||||
void dma_ch2_isr(void) | |||||
{ | |||||
uint32_t daddr, offset; | |||||
const uint16_t *src, *end; | |||||
uint16_t *dest_left; | |||||
audio_block_t *left; | |||||
//digitalWriteFast(3, HIGH); | |||||
daddr = (uint32_t)DMA_TCD2_DADDR; | |||||
DMA_CINT = 2; | |||||
if (daddr < (uint32_t)analog_rx_buffer + sizeof(analog_rx_buffer) / 2) { | |||||
// DMA is receiving to the first half of the buffer | |||||
// need to remove data from the second half | |||||
src = (uint16_t *)&analog_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; | |||||
end = (uint16_t *)&analog_rx_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
if (AudioInputAnalog::update_responsibility) AudioStream::update_all(); | |||||
} else { | |||||
// DMA is receiving to the second half of the buffer | |||||
// need to remove data from the first half | |||||
src = (uint16_t *)&analog_rx_buffer[0]; | |||||
end = (uint16_t *)&analog_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; | |||||
} | |||||
left = AudioInputAnalog::block_left; | |||||
if (left != NULL) { | |||||
offset = AudioInputAnalog::block_offset; | |||||
if (offset > AUDIO_BLOCK_SAMPLES/2) offset = AUDIO_BLOCK_SAMPLES/2; | |||||
//if (offset <= AUDIO_BLOCK_SAMPLES/2) { | |||||
dest_left = (uint16_t *)&(left->data[offset]); | |||||
AudioInputAnalog::block_offset = offset + AUDIO_BLOCK_SAMPLES/2; | |||||
do { | |||||
*dest_left++ = *src++; | |||||
} while (src < end); | |||||
//} | |||||
} | |||||
//digitalWriteFast(3, LOW); | |||||
} | |||||
#if 0 | |||||
void adc0_isr(void) | |||||
{ | |||||
uint32_t tmp = ADC0_RA; // read ADC result to clear interrupt | |||||
digitalWriteFast(3, HIGH); | |||||
delayMicroseconds(1); | |||||
digitalWriteFast(3, LOW); | |||||
} | |||||
#endif | |||||
void AudioInputAnalog::update(void) | |||||
{ | |||||
audio_block_t *new_left=NULL, *out_left=NULL; | |||||
unsigned int dc, offset; | |||||
int16_t s, *p, *end; | |||||
// allocate new block (ok if NULL) | |||||
new_left = allocate(); | |||||
__disable_irq(); | |||||
offset = block_offset; | |||||
if (offset < AUDIO_BLOCK_SAMPLES) { | |||||
// the DMA didn't fill a block | |||||
if (new_left != NULL) { | |||||
// but we allocated a block | |||||
if (block_left == NULL) { | |||||
// the DMA doesn't have any blocks to fill, so | |||||
// give it the one we just allocated | |||||
block_left = new_left; | |||||
block_offset = 0; | |||||
__enable_irq(); | |||||
//Serial.println("fail1"); | |||||
} else { | |||||
// the DMA already has blocks, doesn't need this | |||||
__enable_irq(); | |||||
release(new_left); | |||||
//Serial.print("fail2, offset="); | |||||
//Serial.println(offset); | |||||
} | |||||
} else { | |||||
// The DMA didn't fill a block, and we could not allocate | |||||
// memory... the system is likely starving for memory! | |||||
// Sadly, there's nothing we can do. | |||||
__enable_irq(); | |||||
//Serial.println("fail3"); | |||||
} | |||||
return; | |||||
} | |||||
// the DMA filled a block, so grab it and get the | |||||
// new block to the DMA, as quickly as possible | |||||
out_left = block_left; | |||||
block_left = new_left; | |||||
block_offset = 0; | |||||
__enable_irq(); | |||||
// find and subtract DC offset.... | |||||
// TODO: this may not be correct, needs testing with more types of signals | |||||
dc = dc_average; | |||||
p = out_left->data; | |||||
end = p + AUDIO_BLOCK_SAMPLES; | |||||
do { | |||||
s = (uint16_t)(*p) - dc; // TODO: should be saturating subtract | |||||
*p++ = s; | |||||
dc += s >> 13; // approx 5.38 Hz high pass filter | |||||
} while (p < end); | |||||
dc_average = dc; | |||||
// then transmit the AC data | |||||
transmit(out_left); | |||||
release(out_left); | |||||
} | |||||
#include "Audio.h" | |||||
#include "arm_math.h" | |||||
// #define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT) | |||||
// #define PDB_PERIOD 1087 // 48e6 / 44100 | |||||
#if defined(__MK20DX256__) | |||||
DMAMEM static uint16_t dac_buffer[AUDIO_BLOCK_SAMPLES*2]; | |||||
audio_block_t * AudioOutputAnalog::block_left_1st = NULL; | |||||
audio_block_t * AudioOutputAnalog::block_left_2nd = NULL; | |||||
bool AudioOutputAnalog::update_responsibility = false; | |||||
void AudioOutputAnalog::begin(void) | |||||
{ | |||||
SIM_SCGC2 |= SIM_SCGC2_DAC0; | |||||
DAC0_C0 = DAC_C0_DACEN | DAC_C0_DACRFS; // 3.3V VDDA is DACREF_2 | |||||
// slowly ramp up to DC voltage, approx 1/4 second | |||||
for (int16_t i=0; i<128; i++) { | |||||
analogWrite(A14, i); | |||||
delay(2); | |||||
} | |||||
// set the programmable delay block to trigger DMA requests | |||||
SIM_SCGC6 |= SIM_SCGC6_PDB; | |||||
PDB0_IDLY = 1; | |||||
PDB0_MOD = PDB_PERIOD; | |||||
PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; | |||||
PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG | PDB_SC_PDBIE | PDB_SC_DMAEN; | |||||
SIM_SCGC7 |= SIM_SCGC7_DMA; | |||||
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; | |||||
DMA_CR = 0; | |||||
DMA_TCD4_SADDR = dac_buffer; | |||||
DMA_TCD4_SOFF = 2; | |||||
DMA_TCD4_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); | |||||
DMA_TCD4_NBYTES_MLNO = 2; | |||||
DMA_TCD4_SLAST = -sizeof(dac_buffer); | |||||
DMA_TCD4_DADDR = &DAC0_DAT0L; | |||||
DMA_TCD4_DOFF = 0; | |||||
DMA_TCD4_CITER_ELINKNO = sizeof(dac_buffer) / 2; | |||||
DMA_TCD4_DLASTSGA = 0; | |||||
DMA_TCD4_BITER_ELINKNO = sizeof(dac_buffer) / 2; | |||||
DMA_TCD4_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
DMAMUX0_CHCFG4 = DMAMUX_DISABLE; | |||||
DMAMUX0_CHCFG4 = DMAMUX_SOURCE_PDB | DMAMUX_ENABLE; | |||||
update_responsibility = update_setup(); | |||||
DMA_SERQ = 4; | |||||
NVIC_ENABLE_IRQ(IRQ_DMA_CH4); | |||||
} | |||||
void AudioOutputAnalog::analogReference(int ref) | |||||
{ | |||||
// TODO: this should ramp gradually to the new DC level | |||||
if (ref == INTERNAL) { | |||||
DAC0_C0 &= ~DAC_C0_DACRFS; // 1.2V | |||||
} else { | |||||
DAC0_C0 |= DAC_C0_DACRFS; // 3.3V | |||||
} | |||||
} | |||||
void AudioOutputAnalog::update(void) | |||||
{ | |||||
audio_block_t *block; | |||||
block = receiveReadOnly(0); // input 0 | |||||
if (block) { | |||||
__disable_irq(); | |||||
if (block_left_1st == NULL) { | |||||
block_left_1st = block; | |||||
__enable_irq(); | |||||
} else if (block_left_2nd == NULL) { | |||||
block_left_2nd = block; | |||||
__enable_irq(); | |||||
} else { | |||||
audio_block_t *tmp = block_left_1st; | |||||
block_left_1st = block_left_2nd; | |||||
block_left_2nd = block; | |||||
__enable_irq(); | |||||
release(tmp); | |||||
} | |||||
} | |||||
} | |||||
// TODO: the DAC has much higher bandwidth than the datasheet says | |||||
// can we output a 2X oversampled output, for easier filtering? | |||||
void dma_ch4_isr(void) | |||||
{ | |||||
const int16_t *src, *end; | |||||
int16_t *dest; | |||||
audio_block_t *block; | |||||
uint32_t saddr; | |||||
saddr = (uint32_t)DMA_TCD4_SADDR; | |||||
DMA_CINT = 4; | |||||
if (saddr < (uint32_t)dac_buffer + sizeof(dac_buffer) / 2) { | |||||
// DMA is transmitting the first half of the buffer | |||||
// so we must fill the second half | |||||
dest = (int16_t *)&dac_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
end = (int16_t *)&dac_buffer[AUDIO_BLOCK_SAMPLES*2]; | |||||
} else { | |||||
// DMA is transmitting the second half of the buffer | |||||
// so we must fill the first half | |||||
dest = (int16_t *)dac_buffer; | |||||
end = (int16_t *)&dac_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
} | |||||
block = AudioOutputAnalog::block_left_1st; | |||||
if (block) { | |||||
src = block->data; | |||||
do { | |||||
// TODO: this should probably dither | |||||
*dest++ = ((*src++) + 32767) >> 4; | |||||
} while (dest < end); | |||||
AudioStream::release(block); | |||||
AudioOutputAnalog::block_left_1st = AudioOutputAnalog::block_left_2nd; | |||||
AudioOutputAnalog::block_left_2nd = NULL; | |||||
} else { | |||||
do { | |||||
*dest++ = 2047; | |||||
} while (dest < end); | |||||
} | |||||
if (AudioOutputAnalog::update_responsibility) AudioStream::update_all(); | |||||
} | |||||
#else | |||||
void AudioOutputAnalog::begin(void) | |||||
{ | |||||
} | |||||
void AudioOutputAnalog::update(void) | |||||
{ | |||||
audio_block_t *block; | |||||
block = receiveReadOnly(0); // input 0 | |||||
if (block) release(block); | |||||
} | |||||
#endif // defined(__MK20DX256__) | |||||
#include "Audio.h" | |||||
#include "arm_math.h" | |||||
// MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate | |||||
// Possible to create using fractional divider for all USB-compatible Kinetis: | |||||
// MCLK = 16e6 * 12 / 17 | |||||
// MCLK = 24e6 * 8 / 17 | |||||
// MCLK = 48e6 * 4 / 17 | |||||
// MCLK = 72e6 * 8 / 51 | |||||
// MCLK = 96e6 * 2 / 17 | |||||
// MCLK = 120e6 * 8 / 85 | |||||
// TODO: instigate using I2S0_MCR to select the crystal directly instead of the system | |||||
// clock, which has audio band jitter from the PLL | |||||
audio_block_t * AudioOutputI2S::block_left_1st = NULL; | |||||
audio_block_t * AudioOutputI2S::block_right_1st = NULL; | |||||
audio_block_t * AudioOutputI2S::block_left_2nd = NULL; | |||||
audio_block_t * AudioOutputI2S::block_right_2nd = NULL; | |||||
uint16_t AudioOutputI2S::block_left_offset = 0; | |||||
uint16_t AudioOutputI2S::block_right_offset = 0; | |||||
bool AudioOutputI2S::update_responsibility = false; | |||||
DMAMEM static uint32_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
void AudioOutputI2S::begin(void) | |||||
{ | |||||
//pinMode(2, OUTPUT); | |||||
block_left_1st = NULL; | |||||
block_right_1st = NULL; | |||||
config_i2s(); | |||||
CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 | |||||
DMA_CR = 0; | |||||
DMA_TCD0_SADDR = i2s_tx_buffer; | |||||
DMA_TCD0_SOFF = 2; | |||||
DMA_TCD0_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); | |||||
DMA_TCD0_NBYTES_MLNO = 2; | |||||
DMA_TCD0_SLAST = -sizeof(i2s_tx_buffer); | |||||
DMA_TCD0_DADDR = &I2S0_TDR0; | |||||
DMA_TCD0_DOFF = 0; | |||||
DMA_TCD0_CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; | |||||
DMA_TCD0_DLASTSGA = 0; | |||||
DMA_TCD0_BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; | |||||
DMA_TCD0_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
DMAMUX0_CHCFG0 = DMAMUX_DISABLE; | |||||
DMAMUX0_CHCFG0 = DMAMUX_SOURCE_I2S0_TX | DMAMUX_ENABLE; | |||||
update_responsibility = update_setup(); | |||||
DMA_SERQ = 0; | |||||
I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR; | |||||
NVIC_ENABLE_IRQ(IRQ_DMA_CH0); | |||||
} | |||||
void dma_ch0_isr(void) | |||||
{ | |||||
const int16_t *src, *end; | |||||
int16_t *dest; | |||||
audio_block_t *block; | |||||
uint32_t saddr, offset; | |||||
saddr = (uint32_t)DMA_TCD0_SADDR; | |||||
DMA_CINT = 0; | |||||
if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { | |||||
// DMA is transmitting the first half of the buffer | |||||
// so we must fill the second half | |||||
dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; | |||||
end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
if (AudioOutputI2S::update_responsibility) AudioStream::update_all(); | |||||
} else { | |||||
// DMA is transmitting the second half of the buffer | |||||
// so we must fill the first half | |||||
dest = (int16_t *)i2s_tx_buffer; | |||||
end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; | |||||
} | |||||
// TODO: these copy routines could be merged and optimized, maybe in assembly? | |||||
block = AudioOutputI2S::block_left_1st; | |||||
if (block) { | |||||
offset = AudioOutputI2S::block_left_offset; | |||||
src = &block->data[offset]; | |||||
do { | |||||
*dest = *src++; | |||||
dest += 2; | |||||
} while (dest < end); | |||||
offset += AUDIO_BLOCK_SAMPLES/2; | |||||
if (offset < AUDIO_BLOCK_SAMPLES) { | |||||
AudioOutputI2S::block_left_offset = offset; | |||||
} else { | |||||
AudioOutputI2S::block_left_offset = 0; | |||||
AudioStream::release(block); | |||||
AudioOutputI2S::block_left_1st = AudioOutputI2S::block_left_2nd; | |||||
AudioOutputI2S::block_left_2nd = NULL; | |||||
} | |||||
} else { | |||||
do { | |||||
*dest = 0; | |||||
dest += 2; | |||||
} while (dest < end); | |||||
} | |||||
dest -= AUDIO_BLOCK_SAMPLES - 1; | |||||
block = AudioOutputI2S::block_right_1st; | |||||
if (block) { | |||||
offset = AudioOutputI2S::block_right_offset; | |||||
src = &block->data[offset]; | |||||
do { | |||||
*dest = *src++; | |||||
dest += 2; | |||||
} while (dest < end); | |||||
offset += AUDIO_BLOCK_SAMPLES/2; | |||||
if (offset < AUDIO_BLOCK_SAMPLES) { | |||||
AudioOutputI2S::block_right_offset = offset; | |||||
} else { | |||||
AudioOutputI2S::block_right_offset = 0; | |||||
AudioStream::release(block); | |||||
AudioOutputI2S::block_right_1st = AudioOutputI2S::block_right_2nd; | |||||
AudioOutputI2S::block_right_2nd = NULL; | |||||
} | |||||
} else { | |||||
do { | |||||
*dest = 0; | |||||
dest += 2; | |||||
} while (dest < end); | |||||
} | |||||
} | |||||
void AudioOutputI2S::update(void) | |||||
{ | |||||
// null audio device: discard all incoming data | |||||
//if (!active) return; | |||||
//audio_block_t *block = receiveReadOnly(); | |||||
//if (block) release(block); | |||||
audio_block_t *block; | |||||
block = receiveReadOnly(0); // input 0 = left channel | |||||
if (block) { | |||||
__disable_irq(); | |||||
if (block_left_1st == NULL) { | |||||
block_left_1st = block; | |||||
block_left_offset = 0; | |||||
__enable_irq(); | |||||
} else if (block_left_2nd == NULL) { | |||||
block_left_2nd = block; | |||||
__enable_irq(); | |||||
} else { | |||||
audio_block_t *tmp = block_left_1st; | |||||
block_left_1st = block_left_2nd; | |||||
block_left_2nd = block; | |||||
block_left_offset = 0; | |||||
__enable_irq(); | |||||
release(tmp); | |||||
} | |||||
} | |||||
block = receiveReadOnly(1); // input 1 = right channel | |||||
if (block) { | |||||
__disable_irq(); | |||||
if (block_right_1st == NULL) { | |||||
block_right_1st = block; | |||||
block_right_offset = 0; | |||||
__enable_irq(); | |||||
} else if (block_right_2nd == NULL) { | |||||
block_right_2nd = block; | |||||
__enable_irq(); | |||||
} else { | |||||
audio_block_t *tmp = block_right_1st; | |||||
block_right_1st = block_right_2nd; | |||||
block_right_2nd = block; | |||||
block_right_offset = 0; | |||||
__enable_irq(); | |||||
release(tmp); | |||||
} | |||||
} | |||||
} | |||||
void AudioOutputI2S::config_i2s(void) | |||||
{ | |||||
SIM_SCGC6 |= SIM_SCGC6_I2S; | |||||
SIM_SCGC7 |= SIM_SCGC7_DMA; | |||||
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; | |||||
// if either transmitter or receiver is enabled, do nothing | |||||
if (I2S0_TCSR & I2S_TCSR_TE) return; | |||||
if (I2S0_RCSR & I2S_RCSR_RE) return; | |||||
// enable MCLK output | |||||
I2S0_MCR = I2S_MCR_MICS(3) | I2S_MCR_MOE; | |||||
I2S0_MDR = I2S_MDR_FRACT(1) | I2S_MDR_DIVIDE(16); | |||||
// configure transmitter | |||||
I2S0_TMR = 0; | |||||
I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size | |||||
I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) | |||||
| I2S_TCR2_BCD | I2S_TCR2_DIV(3); | |||||
I2S0_TCR3 = I2S_TCR3_TCE; | |||||
I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(15) | I2S_TCR4_MF | |||||
| I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD; | |||||
I2S0_TCR5 = I2S_TCR5_WNW(15) | I2S_TCR5_W0W(15) | I2S_TCR5_FBT(15); | |||||
// configure receiver (sync'd to transmitter clocks) | |||||
I2S0_RMR = 0; | |||||
I2S0_RCR1 = I2S_RCR1_RFW(1); | |||||
I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) | |||||
| I2S_RCR2_BCD | I2S_RCR2_DIV(3); | |||||
I2S0_RCR3 = I2S_RCR3_RCE; | |||||
I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(15) | I2S_RCR4_MF | |||||
| I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; | |||||
I2S0_RCR5 = I2S_RCR5_WNW(15) | I2S_RCR5_W0W(15) | I2S_RCR5_FBT(15); | |||||
// 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 AudioOutputI2Sslave::begin(void) | |||||
{ | |||||
//pinMode(2, OUTPUT); | |||||
block_left_1st = NULL; | |||||
block_right_1st = NULL; | |||||
AudioOutputI2Sslave::config_i2s(); | |||||
CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 | |||||
DMA_CR = 0; | |||||
DMA_TCD0_SADDR = i2s_tx_buffer; | |||||
DMA_TCD0_SOFF = 2; | |||||
DMA_TCD0_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); | |||||
DMA_TCD0_NBYTES_MLNO = 2; | |||||
DMA_TCD0_SLAST = -sizeof(i2s_tx_buffer); | |||||
DMA_TCD0_DADDR = &I2S0_TDR0; | |||||
DMA_TCD0_DOFF = 0; | |||||
DMA_TCD0_CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; | |||||
DMA_TCD0_DLASTSGA = 0; | |||||
DMA_TCD0_BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; | |||||
DMA_TCD0_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
DMAMUX0_CHCFG0 = DMAMUX_DISABLE; | |||||
DMAMUX0_CHCFG0 = DMAMUX_SOURCE_I2S0_TX | DMAMUX_ENABLE; | |||||
update_responsibility = update_setup(); | |||||
DMA_SERQ = 0; | |||||
I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR; | |||||
NVIC_ENABLE_IRQ(IRQ_DMA_CH0); | |||||
} | |||||
void AudioOutputI2Sslave::config_i2s(void) | |||||
{ | |||||
SIM_SCGC6 |= SIM_SCGC6_I2S; | |||||
SIM_SCGC7 |= SIM_SCGC7_DMA; | |||||
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; | |||||
// if either transmitter or receiver is enabled, do nothing | |||||
if (I2S0_TCSR & I2S_TCSR_TE) return; | |||||
if (I2S0_RCSR & I2S_RCSR_RE) return; | |||||
// Select input clock 0 | |||||
// Configure to input the bit-clock from pin, bypasses the MCLK divider | |||||
I2S0_MCR = I2S_MCR_MICS(0); | |||||
I2S0_MDR = 0; | |||||
// configure transmitter | |||||
I2S0_TMR = 0; | |||||
I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size | |||||
I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP; | |||||
I2S0_TCR3 = I2S_TCR3_TCE; | |||||
I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(15) | I2S_TCR4_MF | |||||
| I2S_TCR4_FSE | I2S_TCR4_FSP; | |||||
I2S0_TCR5 = I2S_TCR5_WNW(15) | I2S_TCR5_W0W(15) | I2S_TCR5_FBT(15); | |||||
// configure receiver (sync'd to transmitter clocks) | |||||
I2S0_RMR = 0; | |||||
I2S0_RCR1 = I2S_RCR1_RFW(1); | |||||
I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP; | |||||
I2S0_RCR3 = I2S_RCR3_RCE; | |||||
I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(15) | I2S_RCR4_MF | |||||
| I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; | |||||
I2S0_RCR5 = I2S_RCR5_WNW(15) | I2S_RCR5_W0W(15) | I2S_RCR5_FBT(15); | |||||
// 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 | |||||
} | |||||
/******************************************************************/ | |||||
DMAMEM static uint32_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
audio_block_t * AudioInputI2S::block_left = NULL; | |||||
audio_block_t * AudioInputI2S::block_right = NULL; | |||||
uint16_t AudioInputI2S::block_offset = 0; | |||||
bool AudioInputI2S::update_responsibility = false; | |||||
void AudioInputI2S::begin(void) | |||||
{ | |||||
//block_left_1st = NULL; | |||||
//block_right_1st = NULL; | |||||
//pinMode(3, OUTPUT); | |||||
//digitalWriteFast(3, HIGH); | |||||
//delayMicroseconds(500); | |||||
//digitalWriteFast(3, LOW); | |||||
AudioOutputI2S::config_i2s(); | |||||
CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 | |||||
DMA_CR = 0; | |||||
DMA_TCD1_SADDR = &I2S0_RDR0; | |||||
DMA_TCD1_SOFF = 0; | |||||
DMA_TCD1_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); | |||||
DMA_TCD1_NBYTES_MLNO = 2; | |||||
DMA_TCD1_SLAST = 0; | |||||
DMA_TCD1_DADDR = i2s_rx_buffer; | |||||
DMA_TCD1_DOFF = 2; | |||||
DMA_TCD1_CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; | |||||
DMA_TCD1_DLASTSGA = -sizeof(i2s_rx_buffer); | |||||
DMA_TCD1_BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; | |||||
DMA_TCD1_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
DMAMUX0_CHCFG1 = DMAMUX_DISABLE; | |||||
DMAMUX0_CHCFG1 = DMAMUX_SOURCE_I2S0_RX | DMAMUX_ENABLE; | |||||
update_responsibility = update_setup(); | |||||
DMA_SERQ = 1; | |||||
// TODO: is I2S_RCSR_BCE appropriate if sync'd to transmitter clock? | |||||
//I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; | |||||
I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_FRDE | I2S_RCSR_FR; | |||||
NVIC_ENABLE_IRQ(IRQ_DMA_CH1); | |||||
} | |||||
void dma_ch1_isr(void) | |||||
{ | |||||
uint32_t daddr, offset; | |||||
const int16_t *src, *end; | |||||
int16_t *dest_left, *dest_right; | |||||
audio_block_t *left, *right; | |||||
//digitalWriteFast(3, HIGH); | |||||
daddr = (uint32_t)DMA_TCD1_DADDR; | |||||
DMA_CINT = 1; | |||||
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]; | |||||
if (AudioInputI2S::update_responsibility) AudioStream::update_all(); | |||||
} 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]; | |||||
} | |||||
left = AudioInputI2S::block_left; | |||||
right = AudioInputI2S::block_right; | |||||
if (left != NULL && right != NULL) { | |||||
offset = AudioInputI2S::block_offset; | |||||
if (offset <= AUDIO_BLOCK_SAMPLES/2) { | |||||
dest_left = &(left->data[offset]); | |||||
dest_right = &(right->data[offset]); | |||||
AudioInputI2S::block_offset = offset + AUDIO_BLOCK_SAMPLES/2; | |||||
do { | |||||
//n = *src++; | |||||
//*dest_left++ = (int16_t)n; | |||||
//*dest_right++ = (int16_t)(n >> 16); | |||||
*dest_left++ = *src++; | |||||
*dest_right++ = *src++; | |||||
} while (src < end); | |||||
} | |||||
} | |||||
//digitalWriteFast(3, LOW); | |||||
} | |||||
void AudioInputI2S::update(void) | |||||
{ | |||||
audio_block_t *new_left=NULL, *new_right=NULL, *out_left=NULL, *out_right=NULL; | |||||
// allocate 2 new blocks, but if one fails, allocate neither | |||||
new_left = allocate(); | |||||
if (new_left != NULL) { | |||||
new_right = allocate(); | |||||
if (new_right == NULL) { | |||||
release(new_left); | |||||
new_left = NULL; | |||||
} | |||||
} | |||||
__disable_irq(); | |||||
if (block_offset >= AUDIO_BLOCK_SAMPLES) { | |||||
// the DMA filled 2 blocks, so grab them and get the | |||||
// 2 new blocks to the DMA, as quickly as possible | |||||
out_left = block_left; | |||||
block_left = new_left; | |||||
out_right = block_right; | |||||
block_right = new_right; | |||||
block_offset = 0; | |||||
__enable_irq(); | |||||
// then transmit the DMA's former blocks | |||||
transmit(out_left, 0); | |||||
release(out_left); | |||||
transmit(out_right, 1); | |||||
release(out_right); | |||||
//Serial.print("."); | |||||
} else if (new_left != NULL) { | |||||
// the DMA didn't fill blocks, but we allocated blocks | |||||
if (block_left == NULL) { | |||||
// the DMA doesn't have any blocks to fill, so | |||||
// give it the ones we just allocated | |||||
block_left = new_left; | |||||
block_right = new_right; | |||||
block_offset = 0; | |||||
__enable_irq(); | |||||
} else { | |||||
// the DMA already has blocks, doesn't need these | |||||
__enable_irq(); | |||||
release(new_left); | |||||
release(new_right); | |||||
} | |||||
} else { | |||||
// The DMA didn't fill blocks, and we could not allocate | |||||
// memory... the system is likely starving for memory! | |||||
// Sadly, there's nothing we can do. | |||||
__enable_irq(); | |||||
} | |||||
} | |||||
/******************************************************************/ | |||||
void AudioInputI2Sslave::begin(void) | |||||
{ | |||||
//block_left_1st = NULL; | |||||
//block_right_1st = NULL; | |||||
//pinMode(3, OUTPUT); | |||||
//digitalWriteFast(3, HIGH); | |||||
//delayMicroseconds(500); | |||||
//digitalWriteFast(3, LOW); | |||||
AudioOutputI2Sslave::config_i2s(); | |||||
CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 | |||||
DMA_CR = 0; | |||||
DMA_TCD1_SADDR = &I2S0_RDR0; | |||||
DMA_TCD1_SOFF = 0; | |||||
DMA_TCD1_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); | |||||
DMA_TCD1_NBYTES_MLNO = 2; | |||||
DMA_TCD1_SLAST = 0; | |||||
DMA_TCD1_DADDR = i2s_rx_buffer; | |||||
DMA_TCD1_DOFF = 2; | |||||
DMA_TCD1_CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; | |||||
DMA_TCD1_DLASTSGA = -sizeof(i2s_rx_buffer); | |||||
DMA_TCD1_BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; | |||||
DMA_TCD1_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
DMAMUX0_CHCFG1 = DMAMUX_DISABLE; | |||||
DMAMUX0_CHCFG1 = DMAMUX_SOURCE_I2S0_RX | DMAMUX_ENABLE; | |||||
update_responsibility = update_setup(); | |||||
DMA_SERQ = 1; | |||||
// TODO: is I2S_RCSR_BCE appropriate if sync'd to transmitter clock? | |||||
//I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; | |||||
I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_FRDE | I2S_RCSR_FR; | |||||
NVIC_ENABLE_IRQ(IRQ_DMA_CH1); | |||||
} | |||||
/******************************************************************/ | |||||
#include "Audio.h" | |||||
#include "arm_math.h" | |||||
audio_block_t * AudioOutputPWM::block_1st = NULL; | |||||
audio_block_t * AudioOutputPWM::block_2nd = NULL; | |||||
uint32_t AudioOutputPWM::block_offset = 0; | |||||
bool AudioOutputPWM::update_responsibility = false; | |||||
uint8_t AudioOutputPWM::interrupt_count = 0; | |||||
DMAMEM uint32_t pwm_dma_buffer[AUDIO_BLOCK_SAMPLES*2]; | |||||
void AudioOutputPWM::begin(void) | |||||
{ | |||||
//Serial.println("AudioPwmOutput constructor"); | |||||
block_1st = NULL; | |||||
FTM1_SC = 0; | |||||
FTM1_CNT = 0; | |||||
FTM1_MOD = 543; | |||||
FTM1_C0SC = 0x69; // send DMA request on match | |||||
FTM1_C1SC = 0x28; | |||||
FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); | |||||
CORE_PIN3_CONFIG = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; | |||||
CORE_PIN4_CONFIG = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; | |||||
FTM1_C0V = 120; // range 120 to 375 | |||||
FTM1_C1V = 0; // range 0 to 255 | |||||
for (int i=0; i<256; i+=2) { | |||||
pwm_dma_buffer[i] = 120; // zero must not be used | |||||
pwm_dma_buffer[i+1] = 0; | |||||
} | |||||
SIM_SCGC7 |= SIM_SCGC7_DMA; | |||||
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; | |||||
DMA_CR = 0; | |||||
DMA_TCD3_SADDR = pwm_dma_buffer; | |||||
DMA_TCD3_SOFF = 4; | |||||
DMA_TCD3_ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2) | DMA_TCD_ATTR_DMOD(4); | |||||
DMA_TCD3_NBYTES_MLNO = 8; | |||||
DMA_TCD3_SLAST = -sizeof(pwm_dma_buffer); | |||||
DMA_TCD3_DADDR = &FTM1_C0V; | |||||
DMA_TCD3_DOFF = 8; | |||||
DMA_TCD3_CITER_ELINKNO = sizeof(pwm_dma_buffer) / 8; | |||||
DMA_TCD3_DLASTSGA = 0; | |||||
DMA_TCD3_BITER_ELINKNO = sizeof(pwm_dma_buffer) / 8; | |||||
DMA_TCD3_CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
DMAMUX0_CHCFG3 = DMAMUX_DISABLE; | |||||
DMAMUX0_CHCFG3 = DMAMUX_SOURCE_FTM1_CH0 | DMAMUX_ENABLE; | |||||
DMA_SERQ = 3; | |||||
update_responsibility = update_setup(); | |||||
NVIC_ENABLE_IRQ(IRQ_DMA_CH3); | |||||
} | |||||
void AudioOutputPWM::update(void) | |||||
{ | |||||
audio_block_t *block; | |||||
block = receiveReadOnly(); | |||||
if (!block) return; | |||||
__disable_irq(); | |||||
if (block_1st == NULL) { | |||||
block_1st = block; | |||||
block_offset = 0; | |||||
__enable_irq(); | |||||
} else if (block_2nd == NULL) { | |||||
block_2nd = block; | |||||
__enable_irq(); | |||||
} else { | |||||
audio_block_t *tmp = block_1st; | |||||
block_1st = block_2nd; | |||||
block_2nd = block; | |||||
block_offset = 0; | |||||
__enable_irq(); | |||||
release(tmp); | |||||
} | |||||
} | |||||
void dma_ch3_isr(void) | |||||
{ | |||||
int16_t *src; | |||||
uint32_t *dest; | |||||
audio_block_t *block; | |||||
uint32_t saddr, offset; | |||||
saddr = (uint32_t)DMA_TCD3_SADDR; | |||||
DMA_CINT = 3; | |||||
if (saddr < (uint32_t)pwm_dma_buffer + sizeof(pwm_dma_buffer) / 2) { | |||||
// DMA is transmitting the first half of the buffer | |||||
// so we must fill the second half | |||||
dest = &pwm_dma_buffer[AUDIO_BLOCK_SAMPLES]; | |||||
} else { | |||||
// DMA is transmitting the second half of the buffer | |||||
// so we must fill the first half | |||||
dest = pwm_dma_buffer; | |||||
} | |||||
block = AudioOutputPWM::block_1st; | |||||
offset = AudioOutputPWM::block_offset; | |||||
if (block) { | |||||
src = &block->data[offset]; | |||||
for (int i=0; i < AUDIO_BLOCK_SAMPLES/4; i++) { | |||||
uint16_t sample = *src++ + 0x8000; | |||||
uint32_t msb = ((sample >> 8) & 255) + 120; | |||||
uint32_t lsb = sample & 255; | |||||
*dest++ = msb; | |||||
*dest++ = lsb; | |||||
*dest++ = msb; | |||||
*dest++ = lsb; | |||||
} | |||||
offset += AUDIO_BLOCK_SAMPLES/4; | |||||
if (offset < AUDIO_BLOCK_SAMPLES) { | |||||
AudioOutputPWM::block_offset = offset; | |||||
} else { | |||||
AudioOutputPWM::block_offset = 0; | |||||
AudioStream::release(block); | |||||
AudioOutputPWM::block_1st = AudioOutputPWM::block_2nd; | |||||
AudioOutputPWM::block_2nd = NULL; | |||||
} | |||||
} else { | |||||
// fill with silence when no data available | |||||
for (int i=0; i < AUDIO_BLOCK_SAMPLES/4; i++) { | |||||
*dest++ = 248; | |||||
*dest++ = 0; | |||||
*dest++ = 248; | |||||
*dest++ = 0; | |||||
} | |||||
} | |||||
if (AudioOutputPWM::update_responsibility) { | |||||
if (++AudioOutputPWM::interrupt_count >= 4) { | |||||
AudioOutputPWM::interrupt_count = 0; | |||||
AudioStream::update_all(); | |||||
} | |||||
} | |||||
} | |||||
// DMA target is: (registers require 32 bit writes) | |||||
// 40039010 Channel 0 Value (FTM1_C0V) | |||||
// 40039018 Channel 1 Value (FTM1_C1V) | |||||
// TCD: | |||||
// source address = buffer address | |||||
// source offset = 4 bytes | |||||
// attr = no src mod, ssize = 32 bit, dest mod = 16 bytes (4), dsize = 32 bit | |||||
// minor loop byte count = 8 | |||||
// source last adjust = -sizeof(buffer) | |||||
// dest address = FTM1_C0V | |||||
// dest address offset = 8 | |||||
// citer = sizeof(buffer) / 8 (no minor loop linking) | |||||
// dest last adjust = 0 (dest modulo keeps it ready for more) | |||||
// control: | |||||
// throttling = 0 | |||||
// major link to same channel | |||||
// done = 0 | |||||
// active = 0 | |||||
// majorlink = 1 | |||||
// scatter/gather = 0 | |||||
// disable request = 0 | |||||
// inthalf = 1 | |||||
// intmajor = 1 | |||||
// start = 0 | |||||
// biter = sizeof(buffer) / 8 (no minor loop linking) | |||||