@@ -266,10 +266,12 @@ private: | |||
// 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 | |||
@@ -0,0 +1,192 @@ | |||
#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); | |||
} | |||
@@ -0,0 +1,144 @@ | |||
#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__) | |||
@@ -0,0 +1,497 @@ | |||
#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); | |||
} | |||
/******************************************************************/ | |||
@@ -0,0 +1,166 @@ | |||
#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) | |||