| #include "mixer.h" | #include "mixer.h" | ||||
| #include "output_dac.h" | #include "output_dac.h" | ||||
| #include "output_i2s.h" | #include "output_i2s.h" | ||||
| #include "output_i2s_quad.h" | |||||
| #include "output_pwm.h" | #include "output_pwm.h" | ||||
| #include "output_spdif.h" | #include "output_spdif.h" | ||||
| #include "play_memory.h" | #include "play_memory.h" |
| max = max_sample; | max = max_sample; | ||||
| do { | do { | ||||
| int16_t d=*p++; | int16_t d=*p++; | ||||
| // TODO: can we speed this up with SSUB16 and SEL | |||||
| // http://www.m4-unleashed.com/parallel-comparison/ | |||||
| if (d<min) min=d; | if (d<min) min=d; | ||||
| if (d>max) max=d; | if (d>max) max=d; | ||||
| } while (p < end); | } while (p < end); |
| #define DAP_COEF_WR_A2_MSB 0x0138 | #define DAP_COEF_WR_A2_MSB 0x0138 | ||||
| #define DAP_COEF_WR_A2_LSB 0x013A | #define DAP_COEF_WR_A2_LSB 0x013A | ||||
| #define SGTL5000_I2C_ADDR 0x0A // CTRL_ADR0_CS pin low (normal configuration) | |||||
| //#define SGTL5000_I2C_ADDR 0x2A // CTRL_ADR0_CS pin high | |||||
| #define SGTL5000_I2C_ADDR_CS_LOW 0x0A // CTRL_ADR0_CS pin low (normal configuration) | |||||
| #define SGTL5000_I2C_ADDR_CS_HIGH 0x2A // CTRL_ADR0_CS pin high | |||||
| void AudioControlSGTL5000::setAddress(uint8_t level) | |||||
| { | |||||
| if (level == LOW) { | |||||
| i2c_addr = SGTL5000_I2C_ADDR_CS_LOW; | |||||
| } else { | |||||
| i2c_addr = SGTL5000_I2C_ADDR_CS_HIGH; | |||||
| } | |||||
| } | |||||
| bool AudioControlSGTL5000::enable(void) | bool AudioControlSGTL5000::enable(void) | ||||
| { | { | ||||
| unsigned int AudioControlSGTL5000::read(unsigned int reg) | unsigned int AudioControlSGTL5000::read(unsigned int reg) | ||||
| { | { | ||||
| unsigned int val; | unsigned int val; | ||||
| Wire.beginTransmission(SGTL5000_I2C_ADDR); | |||||
| Wire.beginTransmission(i2c_addr); | |||||
| Wire.write(reg >> 8); | Wire.write(reg >> 8); | ||||
| Wire.write(reg); | Wire.write(reg); | ||||
| if (Wire.endTransmission(false) != 0) return 0; | if (Wire.endTransmission(false) != 0) return 0; | ||||
| if (Wire.requestFrom(SGTL5000_I2C_ADDR, 2) < 2) return 0; | |||||
| if (Wire.requestFrom((int)i2c_addr, 2) < 2) return 0; | |||||
| val = Wire.read() << 8; | val = Wire.read() << 8; | ||||
| val |= Wire.read(); | val |= Wire.read(); | ||||
| return val; | return val; | ||||
| bool AudioControlSGTL5000::write(unsigned int reg, unsigned int val) | bool AudioControlSGTL5000::write(unsigned int reg, unsigned int val) | ||||
| { | { | ||||
| if (reg == CHIP_ANA_CTRL) ana_ctrl = val; | if (reg == CHIP_ANA_CTRL) ana_ctrl = val; | ||||
| Wire.beginTransmission(SGTL5000_I2C_ADDR); | |||||
| Wire.beginTransmission(i2c_addr); | |||||
| Wire.write(reg >> 8); | Wire.write(reg >> 8); | ||||
| Wire.write(reg); | Wire.write(reg); | ||||
| Wire.write(val >> 8); | Wire.write(val >> 8); |
| class AudioControlSGTL5000 : public AudioControl | class AudioControlSGTL5000 : public AudioControl | ||||
| { | { | ||||
| public: | public: | ||||
| AudioControlSGTL5000(void) : i2c_addr(0x0A) { } | |||||
| void setAddress(uint8_t level); | |||||
| bool enable(void); | bool enable(void); | ||||
| bool disable(void) { return false; } | bool disable(void) { return false; } | ||||
| bool volume(float n) { return volumeInteger(n * 129 + 0.499); } | bool volume(float n) { return volumeInteger(n * 129 + 0.499); } | ||||
| bool muted; | bool muted; | ||||
| bool volumeInteger(unsigned int n); // range: 0x00 to 0x80 | bool volumeInteger(unsigned int n); // range: 0x00 to 0x80 | ||||
| uint16_t ana_ctrl; | uint16_t ana_ctrl; | ||||
| uint8_t i2c_addr; | |||||
| unsigned char calcVol(float n, unsigned char range); | unsigned char calcVol(float n, unsigned char range); | ||||
| unsigned int read(unsigned int reg); | unsigned int read(unsigned int reg); | ||||
| bool write(unsigned int reg, unsigned int val); | bool write(unsigned int reg, unsigned int val); |
| // Quad channel output test | |||||
| // Play two WAV files on two audio shields. | |||||
| // | |||||
| // TODO: add info about required hardware connections here.... | |||||
| // | |||||
| // Data files to put on your SD card can be downloaded here: | |||||
| // http://www.pjrc.com/teensy/td_libs_AudioDataFiles.html | |||||
| // | |||||
| // This example code is in the public domain. | |||||
| #include <Audio.h> | |||||
| #include <Wire.h> | |||||
| #include <SPI.h> | |||||
| #include <SD.h> | |||||
| #include <SerialFlash.h> | |||||
| AudioPlaySdWav playSdWav1; | |||||
| AudioPlaySdWav playSdWav2; | |||||
| AudioOutputI2SQuad audioOutput; | |||||
| AudioConnection patchCord1(playSdWav1, 0, audioOutput, 0); | |||||
| AudioConnection patchCord2(playSdWav1, 1, audioOutput, 1); | |||||
| AudioConnection patchCord3(playSdWav2, 0, audioOutput, 2); | |||||
| AudioConnection patchCord4(playSdWav2, 1, audioOutput, 3); | |||||
| AudioControlSGTL5000 sgtl5000_1; | |||||
| AudioControlSGTL5000 sgtl5000_2; | |||||
| // Use these with the audio adaptor board | |||||
| #define SDCARD_CS_PIN 10 | |||||
| #define SDCARD_MOSI_PIN 7 | |||||
| #define SDCARD_SCK_PIN 14 | |||||
| void setup() { | |||||
| Serial.begin(9600); | |||||
| AudioMemory(10); | |||||
| sgtl5000_1.setAddress(LOW); | |||||
| sgtl5000_1.enable(); | |||||
| sgtl5000_1.volume(0.5); | |||||
| sgtl5000_2.setAddress(HIGH); | |||||
| sgtl5000_2.enable(); | |||||
| sgtl5000_2.volume(0.5); | |||||
| SPI.setMOSI(SDCARD_MOSI_PIN); | |||||
| SPI.setSCK(SDCARD_SCK_PIN); | |||||
| if (!(SD.begin(SDCARD_CS_PIN))) { | |||||
| // stop here, but print a message repetitively | |||||
| while (1) { | |||||
| Serial.println("Unable to access the SD card"); | |||||
| delay(500); | |||||
| } | |||||
| } | |||||
| } | |||||
| void loop() { | |||||
| if (playSdWav1.isPlaying() == false) { | |||||
| Serial.println("Start playing 1"); | |||||
| playSdWav1.play("SDTEST2.WAV"); | |||||
| delay(10); // wait for library to parse WAV info | |||||
| } | |||||
| if (playSdWav2.isPlaying() == false) { | |||||
| Serial.println("Start playing 2"); | |||||
| playSdWav2.play("SDTEST4.WAV"); | |||||
| delay(10); // wait for library to parse WAV info | |||||
| } | |||||
| } | |||||
| {"type":"AudioInputAnalog","data":{"defaults":{"name":{"value":"new"}},"shortName":"adc","inputs":0,"outputs":1,"category":"input-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | {"type":"AudioInputAnalog","data":{"defaults":{"name":{"value":"new"}},"shortName":"adc","inputs":0,"outputs":1,"category":"input-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | ||||
| {"type":"AudioInputI2Sslave","data":{"defaults":{"name":{"value":"new"}},"shortName":"i2ss","inputs":0,"outputs":2,"category":"input-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | {"type":"AudioInputI2Sslave","data":{"defaults":{"name":{"value":"new"}},"shortName":"i2ss","inputs":0,"outputs":2,"category":"input-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | ||||
| {"type":"AudioOutputI2S","data":{"defaults":{"name":{"value":"new"}},"shortName":"i2s","inputs":2,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | {"type":"AudioOutputI2S","data":{"defaults":{"name":{"value":"new"}},"shortName":"i2s","inputs":2,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | ||||
| {"type":"AudioOutputI2SQuad","data":{"defaults":{"name":{"value":"new"}},"shortName":"i2s_quad","inputs":4,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | |||||
| {"type":"AudioOutputSPDIF","data":{"defaults":{"name":{"value":"new"}},"shortName":"spdif","inputs":2,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | {"type":"AudioOutputSPDIF","data":{"defaults":{"name":{"value":"new"}},"shortName":"spdif","inputs":2,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | ||||
| {"type":"AudioOutputAnalog","data":{"defaults":{"name":{"value":"new"}},"shortName":"dac","inputs":1,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | {"type":"AudioOutputAnalog","data":{"defaults":{"name":{"value":"new"}},"shortName":"dac","inputs":1,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | ||||
| {"type":"AudioOutputPWM","data":{"defaults":{"name":{"value":"new"}},"shortName":"pwm","inputs":1,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | {"type":"AudioOutputPWM","data":{"defaults":{"name":{"value":"new"}},"shortName":"pwm","inputs":1,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, | ||||
| </div> | </div> | ||||
| </script> | </script> | ||||
| <script type="text/x-red" data-help-name="AudioOutputI2SQuad"> | |||||
| <h3>Summary</h3> | |||||
| <div class=tooltipinfo> | |||||
| <p>Transmit quad (4) channel 16 bit audio, using I2S master mode.</p> | |||||
| </div> | |||||
| <h3>Audio Connections</h3> | |||||
| <table class=doc align=center cellpadding=3> | |||||
| <tr class=top><th>Port</th><th>Purpose</th></tr> | |||||
| <tr class=odd><td align=center>In 0</td><td>Channel #1</td></tr> | |||||
| <tr class=odd><td align=center>In 1</td><td>Channel #2</td></tr> | |||||
| <tr class=odd><td align=center>In 2</td><td>Channel #3</td></tr> | |||||
| <tr class=odd><td align=center>In 3</td><td>Channel #4</td></tr> | |||||
| </table> | |||||
| <h3>Functions</h3> | |||||
| <p>This object has no functions to call from the Arduino sketch. It | |||||
| simply streams data from its 4 input ports to the I2S hardware.</p> | |||||
| <h3>Hardware</h3> | |||||
| <p>TODO: <a href="https://forum.pjrc.com/threads/29373-Bit-bang-multiple-I2S-inputs-simultaneously?p=79606#post79606" target="_blank">details</a> for how to connect 2 Teensy audio shields</p> | |||||
| <p>The I2S signals are used in "master" mode, where Teensy creates | |||||
| all 3 clock signals and controls all data timing.</p> | |||||
| <table class=doc align=center cellpadding=3> | |||||
| <tr class=top><th>Pin</th><th>Signal</th><th>Direction</th></tr> | |||||
| <tr class=odd><td align=center>9</td><td>BCLK</td><td>Output</td></tr> | |||||
| <tr class=odd><td align=center>11</td><td>MCLK</td><td>Output</td></tr> | |||||
| <tr class=odd><td align=center>22</td><td>TX (ch 1+2)</td><td>Output</td></tr> | |||||
| <tr class=odd><td align=center>15</td><td>TX (ch 3+4)</td><td>Output</td></tr> | |||||
| <tr class=odd><td align=center>23</td><td>LRCLK</td><td>Output</td></tr> | |||||
| </table> | |||||
| <p>Audio from | |||||
| master mode I2S may be used in the same project as ADC, DAC and | |||||
| PWM signals, because all remain in sync to Teensy's timing</p> | |||||
| <h3>Examples</h3> | |||||
| <p class=exam>File > Examples > Audio > HardwareTesting > SGTL5000 > QuadChannelOutput | |||||
| </p> | |||||
| <h3>Notes</h3> | |||||
| <p>Normally, this object is used with two Audio Shields, which | |||||
| are controlled separately by a pair of "sgtl5000" objects.</p> | |||||
| </script> | |||||
| <script type="text/x-red" data-template-name="AudioOutputI2SQuad"> | |||||
| <div class="form-row"> | |||||
| <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> | |||||
| <input type="text" id="node-input-name" placeholder="Name"> | |||||
| </div> | |||||
| </script> | |||||
| <script type="text/x-red" data-help-name="AudioOutputSPDIF"> | <script type="text/x-red" data-help-name="AudioOutputSPDIF"> | ||||
| <h3>Summary</h3> | <h3>Summary</h3> | ||||
| <div class=tooltipinfo> | <div class=tooltipinfo> |
| AudioInputI2S KEYWORD2 | AudioInputI2S KEYWORD2 | ||||
| AudioInputI2Sslave KEYWORD2 | AudioInputI2Sslave KEYWORD2 | ||||
| AudioOutputI2S KEYWORD2 | AudioOutputI2S KEYWORD2 | ||||
| AudioOutputI2SQuad KEYWORD2 | |||||
| AudioOutputI2Sslave KEYWORD2 | AudioOutputI2Sslave KEYWORD2 | ||||
| AudioOutputSPDIF KEYWORD2 | AudioOutputSPDIF KEYWORD2 | ||||
| AudioOutputPWM KEYWORD2 | AudioOutputPWM KEYWORD2 | ||||
| trigger KEYWORD2 | trigger KEYWORD2 | ||||
| length KEYWORD2 | length KEYWORD2 | ||||
| threshold KEYWORD2 | threshold KEYWORD2 | ||||
| setAddress KEYWORD2 | |||||
| enable KEYWORD2 | enable KEYWORD2 | ||||
| enableIn KEYWORD2 | enableIn KEYWORD2 | ||||
| enableOut KEYWORD2 | enableOut KEYWORD2 |
| BX lr | BX lr | ||||
| /* void memcpy_tointerleaveQuad(int16_t *dst, const int16_t *src1, const int16_t *src2, const int16_t *src3, const int16_t *src4) */ | |||||
| .global memcpy_tointerleaveQuad | |||||
| .thumb_func | |||||
| memcpy_tointerleaveQuad: | |||||
| @ r0: dst | |||||
| @ r1: src1 | |||||
| @ r2: src2 | |||||
| @ r3: src3 | |||||
| @ r4: src4 | |||||
| push {r4-r11} | |||||
| ldr r4, [sp, #(0+32)] //4th parameter is saved on the stack | |||||
| add r11,r0,#512 // TODO: 512 = AUDIO_BLOCK_SAMPLES*4 | |||||
| .align 2 | |||||
| .loopQuad: | |||||
| .irp offset, 1,2 | |||||
| ldr r5, [r1],4 | |||||
| ldr r6, [r3],4 | |||||
| pkhbt r7,r5,r6,LSL #16 | |||||
| pkhtb r9,r6,r5,ASR #16 | |||||
| ldr r5, [r2],4 | |||||
| ldr r6, [r4],4 | |||||
| pkhbt r8,r5,r6,LSL #16 | |||||
| pkhtb r10,r6,r5,ASR #16 | |||||
| stmia r0!, {r7-r10} | |||||
| .endr | |||||
| cmp r11, r0 | |||||
| bne .loopQuad | |||||
| pop {r4-r11} | |||||
| BX lr | |||||
| .END | .END | ||||
| #endif | #endif |
| void memcpy_tointerleaveLR(int16_t *dst, const int16_t *srcL, const int16_t *srcR); | void memcpy_tointerleaveLR(int16_t *dst, const int16_t *srcL, const int16_t *srcR); | ||||
| void memcpy_tointerleaveL(int16_t *dst, const int16_t *srcL); | void memcpy_tointerleaveL(int16_t *dst, const int16_t *srcL); | ||||
| void memcpy_tointerleaveR(int16_t *dst, const int16_t *srcR); | void memcpy_tointerleaveR(int16_t *dst, const int16_t *srcR); | ||||
| void memcpy_tointerleaveQuad(int16_t *dst, const int16_t *src1, const int16_t *src2, | |||||
| const int16_t *src3, const int16_t *src4); | |||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||
| } | } | ||||
| #endif | #endif |
| /* Audio Library for Teensy 3.X | |||||
| * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com | |||||
| * | |||||
| * Development of this audio library was funded by PJRC.COM, LLC by sales of | |||||
| * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop | |||||
| * open source software by purchasing Teensy or other PJRC products. | |||||
| * | |||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| * of this software and associated documentation files (the "Software"), to deal | |||||
| * in the Software without restriction, including without limitation the rights | |||||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| * copies of the Software, and to permit persons to whom the Software is | |||||
| * furnished to do so, subject to the following conditions: | |||||
| * | |||||
| * The above copyright notice, development funding notice, and this permission | |||||
| * notice shall be included in all copies or substantial portions of the Software. | |||||
| * | |||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| * THE SOFTWARE. | |||||
| */ | |||||
| #include "output_i2s_quad.h" | |||||
| #include "memcpy_audio.h" | |||||
| #if defined(__MK20DX256__) | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch1_1st = NULL; | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch2_1st = NULL; | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch3_1st = NULL; | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch4_1st = NULL; | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch1_2nd = NULL; | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch2_2nd = NULL; | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch3_2nd = NULL; | |||||
| audio_block_t * AudioOutputI2SQuad::block_ch4_2nd = NULL; | |||||
| uint16_t AudioOutputI2SQuad::ch1_offset = 0; | |||||
| uint16_t AudioOutputI2SQuad::ch2_offset = 0; | |||||
| uint16_t AudioOutputI2SQuad::ch3_offset = 0; | |||||
| uint16_t AudioOutputI2SQuad::ch4_offset = 0; | |||||
| //audio_block_t * AudioOutputI2SQuad::inputQueueArray[4]; | |||||
| bool AudioOutputI2SQuad::update_responsibility = false; | |||||
| DMAMEM static uint32_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES*2]; | |||||
| DMAChannel AudioOutputI2SQuad::dma(false); | |||||
| static const uint32_t zerodata[AUDIO_BLOCK_SAMPLES/4] = {0}; | |||||
| void AudioOutputI2SQuad::begin(void) | |||||
| { | |||||
| #if 1 | |||||
| dma.begin(true); // Allocate the DMA channel first | |||||
| block_ch1_1st = NULL; | |||||
| block_ch2_1st = NULL; | |||||
| block_ch3_1st = NULL; | |||||
| block_ch4_1st = NULL; | |||||
| // TODO: can we call normal config_i2s, and then just enable the extra output? | |||||
| config_i2s(); | |||||
| CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 -> ch1 & ch2 | |||||
| CORE_PIN15_CONFIG = PORT_PCR_MUX(6); // pin 15, PTC0, I2S0_TXD1 -> ch3 & ch4 | |||||
| dma.TCD->SADDR = i2s_tx_buffer; | |||||
| dma.TCD->SOFF = 2; | |||||
| dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1) | DMA_TCD_ATTR_DMOD(3); | |||||
| dma.TCD->NBYTES_MLNO = 4; | |||||
| dma.TCD->SLAST = -sizeof(i2s_tx_buffer); | |||||
| dma.TCD->DADDR = &I2S0_TDR0; | |||||
| dma.TCD->DOFF = 4; | |||||
| dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 4; | |||||
| dma.TCD->DLASTSGA = 0; | |||||
| dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 4; | |||||
| dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
| dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); | |||||
| update_responsibility = update_setup(); | |||||
| dma.enable(); | |||||
| I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR; | |||||
| dma.attachInterrupt(isr); | |||||
| #endif | |||||
| } | |||||
| void AudioOutputI2SQuad::isr(void) | |||||
| { | |||||
| uint32_t saddr; | |||||
| const int16_t *src1, *src2, *src3, *src4; | |||||
| const int16_t *zeros = (const int16_t *)zerodata; | |||||
| int16_t *dest; | |||||
| saddr = (uint32_t)(dma.TCD->SADDR); | |||||
| dma.clearInterrupt(); | |||||
| 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]; | |||||
| if (update_responsibility) update_all(); | |||||
| } else { | |||||
| dest = (int16_t *)i2s_tx_buffer; | |||||
| } | |||||
| src1 = (block_ch1_1st) ? block_ch1_1st->data + ch1_offset : zeros; | |||||
| src2 = (block_ch2_1st) ? block_ch2_1st->data + ch2_offset : zeros; | |||||
| src3 = (block_ch3_1st) ? block_ch3_1st->data + ch3_offset : zeros; | |||||
| src4 = (block_ch4_1st) ? block_ch4_1st->data + ch4_offset : zeros; | |||||
| // TODO: fast 4-way interleaved memcpy... | |||||
| #if 1 | |||||
| memcpy_tointerleaveQuad(dest, src1, src2, src3, src4); | |||||
| #else | |||||
| for (int i=0; i < AUDIO_BLOCK_SAMPLES/2; i++) { | |||||
| *dest++ = *src1++; | |||||
| *dest++ = *src3++; | |||||
| *dest++ = *src2++; | |||||
| *dest++ = *src4++; | |||||
| } | |||||
| #endif | |||||
| if (block_ch1_1st) { | |||||
| if (ch1_offset == 0) { | |||||
| ch1_offset = AUDIO_BLOCK_SAMPLES/2; | |||||
| } else { | |||||
| ch1_offset = 0; | |||||
| release(block_ch1_1st); | |||||
| block_ch1_1st = block_ch1_2nd; | |||||
| block_ch1_2nd = NULL; | |||||
| } | |||||
| } | |||||
| if (block_ch2_1st) { | |||||
| if (ch2_offset == 0) { | |||||
| ch2_offset = AUDIO_BLOCK_SAMPLES/2; | |||||
| } else { | |||||
| ch2_offset = 0; | |||||
| release(block_ch2_1st); | |||||
| block_ch2_1st = block_ch2_2nd; | |||||
| block_ch2_2nd = NULL; | |||||
| } | |||||
| } | |||||
| if (block_ch3_1st) { | |||||
| if (ch3_offset == 0) { | |||||
| ch3_offset = AUDIO_BLOCK_SAMPLES/2; | |||||
| } else { | |||||
| ch3_offset = 0; | |||||
| release(block_ch3_1st); | |||||
| block_ch3_1st = block_ch3_2nd; | |||||
| block_ch3_2nd = NULL; | |||||
| } | |||||
| } | |||||
| if (block_ch4_1st) { | |||||
| if (ch4_offset == 0) { | |||||
| ch4_offset = AUDIO_BLOCK_SAMPLES/2; | |||||
| } else { | |||||
| ch4_offset = 0; | |||||
| release(block_ch4_1st); | |||||
| block_ch4_1st = block_ch4_2nd; | |||||
| block_ch4_2nd = NULL; | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioOutputI2SQuad::update(void) | |||||
| { | |||||
| audio_block_t *block, *tmp; | |||||
| block = receiveReadOnly(0); // channel 1 | |||||
| if (block) { | |||||
| __disable_irq(); | |||||
| if (block_ch1_1st == NULL) { | |||||
| block_ch1_1st = block; | |||||
| ch1_offset = 0; | |||||
| __enable_irq(); | |||||
| } else if (block_ch1_2nd == NULL) { | |||||
| block_ch1_2nd = block; | |||||
| __enable_irq(); | |||||
| } else { | |||||
| tmp = block_ch1_1st; | |||||
| block_ch1_1st = block_ch1_2nd; | |||||
| block_ch1_2nd = block; | |||||
| ch1_offset = 0; | |||||
| __enable_irq(); | |||||
| release(tmp); | |||||
| } | |||||
| } | |||||
| block = receiveReadOnly(1); // channel 2 | |||||
| if (block) { | |||||
| __disable_irq(); | |||||
| if (block_ch2_1st == NULL) { | |||||
| block_ch2_1st = block; | |||||
| ch2_offset = 0; | |||||
| __enable_irq(); | |||||
| } else if (block_ch2_2nd == NULL) { | |||||
| block_ch2_2nd = block; | |||||
| __enable_irq(); | |||||
| } else { | |||||
| tmp = block_ch2_1st; | |||||
| block_ch2_1st = block_ch2_2nd; | |||||
| block_ch2_2nd = block; | |||||
| ch2_offset = 0; | |||||
| __enable_irq(); | |||||
| release(tmp); | |||||
| } | |||||
| } | |||||
| block = receiveReadOnly(2); // channel 3 | |||||
| if (block) { | |||||
| __disable_irq(); | |||||
| if (block_ch3_1st == NULL) { | |||||
| block_ch3_1st = block; | |||||
| ch3_offset = 0; | |||||
| __enable_irq(); | |||||
| } else if (block_ch3_2nd == NULL) { | |||||
| block_ch3_2nd = block; | |||||
| __enable_irq(); | |||||
| } else { | |||||
| tmp = block_ch3_1st; | |||||
| block_ch3_1st = block_ch3_2nd; | |||||
| block_ch3_2nd = block; | |||||
| ch3_offset = 0; | |||||
| __enable_irq(); | |||||
| release(tmp); | |||||
| } | |||||
| } | |||||
| block = receiveReadOnly(3); // channel 4 | |||||
| if (block) { | |||||
| __disable_irq(); | |||||
| if (block_ch4_1st == NULL) { | |||||
| block_ch4_1st = block; | |||||
| ch4_offset = 0; | |||||
| __enable_irq(); | |||||
| } else if (block_ch4_2nd == NULL) { | |||||
| block_ch4_2nd = block; | |||||
| __enable_irq(); | |||||
| } else { | |||||
| tmp = block_ch4_1st; | |||||
| block_ch4_1st = block_ch4_2nd; | |||||
| block_ch4_2nd = block; | |||||
| ch4_offset = 0; | |||||
| __enable_irq(); | |||||
| release(tmp); | |||||
| } | |||||
| } | |||||
| } | |||||
| // MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate | |||||
| // | |||||
| #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 == 16000000 | |||||
| #define MCLK_MULT 12 | |||||
| #define MCLK_DIV 17 | |||||
| #else | |||||
| #error "This CPU Clock Speed is not supported by the Audio library"; | |||||
| #endif | |||||
| #if F_CPU >= 20000000 | |||||
| #define MCLK_SRC 3 // the PLL | |||||
| #else | |||||
| #define MCLK_SRC 0 // system clock | |||||
| #endif | |||||
| void AudioOutputI2SQuad::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(MCLK_SRC) | I2S_MCR_MOE; | |||||
| I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV-1)); | |||||
| // 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_2CH; | |||||
| 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_2CH; | |||||
| 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 | |||||
| } | |||||
| #else // not __MK20DX256__ | |||||
| void AudioOutputI2SQuad::begin(void) | |||||
| { | |||||
| } | |||||
| void AudioOutputI2SQuad::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| block = receiveReadOnly(0); | |||||
| if (block) release(block); | |||||
| block = receiveReadOnly(1); | |||||
| if (block) release(block); | |||||
| block = receiveReadOnly(2); | |||||
| if (block) release(block); | |||||
| block = receiveReadOnly(3); | |||||
| if (block) release(block); | |||||
| } | |||||
| #endif |
| /* Audio Library for Teensy 3.X | |||||
| * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com | |||||
| * | |||||
| * Development of this audio library was funded by PJRC.COM, LLC by sales of | |||||
| * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop | |||||
| * open source software by purchasing Teensy or other PJRC products. | |||||
| * | |||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| * of this software and associated documentation files (the "Software"), to deal | |||||
| * in the Software without restriction, including without limitation the rights | |||||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| * copies of the Software, and to permit persons to whom the Software is | |||||
| * furnished to do so, subject to the following conditions: | |||||
| * | |||||
| * The above copyright notice, development funding notice, and this permission | |||||
| * notice shall be included in all copies or substantial portions of the Software. | |||||
| * | |||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| * THE SOFTWARE. | |||||
| */ | |||||
| #ifndef output_i2s_quad_h_ | |||||
| #define output_i2s_quad_h_ | |||||
| #include "AudioStream.h" | |||||
| #include "DMAChannel.h" | |||||
| class AudioOutputI2SQuad : public AudioStream | |||||
| { | |||||
| public: | |||||
| AudioOutputI2SQuad(void) : AudioStream(4, inputQueueArray) { begin(); } | |||||
| virtual void update(void); | |||||
| void begin(void); | |||||
| private: | |||||
| static void config_i2s(void); | |||||
| static audio_block_t *block_ch1_1st; | |||||
| static audio_block_t *block_ch2_1st; | |||||
| static audio_block_t *block_ch3_1st; | |||||
| static audio_block_t *block_ch4_1st; | |||||
| static bool update_responsibility; | |||||
| static DMAChannel dma; | |||||
| static void isr(void); | |||||
| static audio_block_t *block_ch1_2nd; | |||||
| static audio_block_t *block_ch2_2nd; | |||||
| static audio_block_t *block_ch3_2nd; | |||||
| static audio_block_t *block_ch4_2nd; | |||||
| static uint16_t ch1_offset; | |||||
| static uint16_t ch2_offset; | |||||
| static uint16_t ch3_offset; | |||||
| static uint16_t ch4_offset; | |||||
| audio_block_t *inputQueueArray[4]; | |||||
| }; | |||||
| #endif |