もっと今更、SC-88ProにS/PDIFを付けよう
もっと今更、SC-88ProにS/PDIFを付けよう
STM32F103C8T6 ARM STM32 (blue pill)
- モデル:STM32F103C8T6
- コア:ARM 32 Cortex-M3 CPU
- 72MHz頻度を作動させる
- 64Kフラッシュメモリ、20K SRAM
- 2.0-3.6Vパワー、I/O
というやつ。
詳細はwikiの説明に
https://developer.mbed.org/users/peu605/code/DIT88proSTM32F1/wiki/説明
しまったなぁ、wikiのタイトル、漢字にしてしまったよ…
STM32F103C8T6, Roland, SC-88pro, S/PDIF, SPIDF, デジタル出力
main.cpp
- Committer:
- peu605
- Date:
- 2017-09-01
- Revision:
- 0:b3d998305b9d
- Child:
- 1:d7c7d1651f2e
File content as of revision 0:b3d998305b9d:
#include "dit88prostm32.h" //#include "mbed.h" /** * STM32F103C8T6 Blue Pill, Low Layer drivers test * * Software Digital audio Transmitter * for Roland SC-88pro (32kHz, 18bit, Right justified, 256fs) * * @author masuda, Masuda Naika */ #define _SPI SPI2 #define _SPIRxDMA DMA1 #define _SPIRxDMACh LL_DMA_CHANNEL_4 #define _SPIRxDMAIRQn DMA1_Channel4_IRQn #define _SPITxDMA DMA1 #define _SPITxDMACh LL_DMA_CHANNEL_5 #define _SPI_NSS_SOFTWARE() _SPI->CR1 |= SPI_CR1_SSM // NSS softare management #define _SPI_DESELECT_SLAVE() _SPI->CR1 |= SPI_CR1_SSI // deselect slave #define _SPI_SELECT_SLAVE() _SPI->CR1 &= ~SPI_CR1_SSI // select slave #define _SPI_ENABLE() _SPI->CR1 |= SPI_CR1_SPE // SPI enable #define _SPI_DISABLE() _SPI->CR1 &= ~SPI_CR1_SPE // SPI disable #define _LED_ON() LL_GPIO_ResetOutputPin(GPIOC, LL_GPIO_PIN_13) #define _LED_OFF() LL_GPIO_SetOutputPin(GPIOC, LL_GPIO_PIN_13) #define BUFFER_NUM_FRAMES 4 // LR|LR/LR|LR, IRQ freq is 16kHz #define DMA_TRANSFER_LENGTH (8 * 2 * BUFFER_NUM_FRAMES) // serial audio data length #define SDATA_LENGH 18 #define MAX_SPI_DELAY 12 // 32 - 19 - 1 #define MASK31_19 0xfff80000 #define _LED_LOAD //#define _LED_SPI_DELAY // SPI-DMA buffers volatile SpiRxBuff spiRxBuff[BUFFER_NUM_FRAMES]; volatile SpiTxBuff spiTxBuff[BUFFER_NUM_FRAMES]; // channel status table volatile ChannelStatus channelStatus[24]; // SPI bit delay volatile uint32_t spiDelay = 0; extern "C" void DMA1_Channel4_IRQHandler(void) { // SPI Rx DMA if(LL_DMA_IsActiveFlag_HT4(_SPIRxDMA)) { // half transfer LL_DMA_ClearFlag_HT4(_SPIRxDMA); transferFrames(); } else if (LL_DMA_IsActiveFlag_TC4(_SPIRxDMA)) { // transfer complete LL_DMA_ClearFlag_TC4(_SPIRxDMA); transferFrames(); } } int main() { setupPeripherals(); setChannelStatus(); HAL_Delay(1000); // enable SPI and deselect _SPI_DESELECT_SLAVE(); _SPI_ENABLE(); // LL_SPI_Enable(_SPI); uint16_t spiCR1 = _SPI->CR1 & ~SPI_CR1_SSI; // use pre calculated value // LRCK Hi = Left channel // Wait LRCK rise and start SPI as soon as possible while(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_12) != 0); // wait for falling edge while(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_12) == 0); // wait for rising edge // select slave _SPI->CR1 = spiCR1; // wait for interrupt while (true) { #ifdef _LED_SPI_DELAY // measured spiDelay is 3. for (uint32_t i = 0; i < spiDelay; ++i) { _LED_ON(); HAL_Delay(250); _LED_OFF(); HAL_Delay(250); } HAL_Delay(1000); #endif __WFI(); } } void transferFrames() { #ifdef _LED_LOAD // measured load is 20.8% _LED_OFF(); // PC_13 to HIGH #endif for (uint32_t i = 0; i < BUFFER_NUM_FRAMES / 2; ++i) { transferFrame(); } #ifdef _LED_LOAD _LED_ON(); // PC_13 to LOW #endif } void transferFrame() { static uint32_t frameIndex = 0; static uint32_t buffIndex = 0; uint16_t *rxBuffPtr, *txBuffPtr; // transfer left channel rxBuffPtr = (uint16_t*) &spiRxBuff[buffIndex].ltCh; txBuffPtr = (uint16_t*) &spiTxBuff[buffIndex].aCh; transferSubFrame(frameIndex, true, rxBuffPtr, txBuffPtr); // transfer right channel rxBuffPtr = (uint16_t*) &spiRxBuff[buffIndex].rtCh; txBuffPtr = (uint16_t*) &spiTxBuff[buffIndex].bCh; transferSubFrame(frameIndex, false, rxBuffPtr, txBuffPtr); if (++frameIndex == 192) { frameIndex = 0; } if (++buffIndex == BUFFER_NUM_FRAMES) { buffIndex = 0; } } void transferSubFrame(uint32_t frameIndex, bool aCh, uint16_t *rxBuffPtr, uint16_t *txBuffPtr) { static bool lastCellZero = true; // static uint32_t spiDelay = 0; // ====================================== // Read rx buffer and make raw SPIDF data // ====================================== // Roland SC-88pro, 256fs, 18bit right justified, MSB first // SC-88pro's output format might be 2's complement 18 bit audio data in 19 bit data frame. // // SPIRxBuff, each channel data have 128(=256/2) bits, M:MSB, L:LSB // +0 -> 5 hlf_word +6 +7 // 0000 ... 0000000 0000000000000MM= ===============L // 1111 ... 1111111 1111111111111MM= ===============L // ---- ... ------- ---------------- ---------------- // 1111 ... 0000000 1111110000000000 1111110000000000 // 5432 6543210 5432109876543210 5432109876543210 // // Actually, SPI starts some SCKs after LRCK change. // Received data are shifted leftward. // // +0 -> 5 hlf_word +6 +7 |<->| spiDelay // MM====== ==========L // ---- ... ------- ---------------- ---------------- // 1111 ... 0000000 1111110000000000 1111110000000000 // 5432 6543210 5432109876543210 5432109876543210 // // bit[127:19] is all 0 or all 1. // // bit[31:19] should be 000...00 or 111...11 // bit[18] same as MSB bit // bit[17:0] audio data, MSB to LSB // arithmetic right shift int32_t val = (*(rxBuffPtr + 6) << 16) | *(rxBuffPtr + 7); val >>= spiDelay; // determine spiDelay while(true) { int32_t test = val & MASK31_19; if (test == 0 || test == MASK31_19) { break; } else { if (spiDelay == MAX_SPI_DELAY) { spiDelay = 0; val = 0; // discard data break; } else { ++spiDelay; val >>= 1; } } } // left shift to fit 24 bit width val <<= (24 - SDATA_LENGH); // 4-27 data, 28 V, 29 U, 30 C, 31 P // U = 0 // 'val' here does not have preamble. // So, V position is (28 - 4), C pos is (30 - 4), and P pos is (31 - 4). // mask with 24 bit to clear VUCP bits val &= 0x00ffffff; // set Channel status bit uint32_t bitPos = frameIndex & 7; uint32_t offset = frameIndex >> 3; uint8_t *chStatusPtr = aCh ? (uint8_t*) &channelStatus[offset].ltCh : (uint8_t*) &channelStatus[offset].rtCh; if (*chStatusPtr & _BV(bitPos)) { val |= _BV(30 - 4); } // set parity bit if (oddParity(val)) { val |= _BV(31 - 4); } // ============================================== // Biphase Mark Code encodeand write to Tx buffer // ============================================== // Z:preamble, A:aux, L:LSB, M:MSB, 0:filler(unused bit) // V:validity, U:user, C:ch status, P:parity //(PCUVM================L00AAAAZZZZ) // PCUVM================L00AAAA w/o preamble (= val) // -------------------------------- // 33222222222211111111110000000000 // 10987654321098765432109876543210 // Write Biphase Mark Code data to SPITx-DMA buffer // First, 0 ~ 4 bit is Preamble. Not BMC! uint16_t bmc; if (frameIndex == 0 && aCh) { bmc = lastCellZero ? PREAMBLE_Z : ~PREAMBLE_Z; } else if (aCh) { bmc = lastCellZero ? PREAMBLE_X : ~PREAMBLE_X; } else { bmc = lastCellZero ? PREAMBLE_Y : ~PREAMBLE_Y; } lastCellZero = (bmc & 1) == 0; *txBuffPtr++ = bmc; // Next, 5 ~ 31 bit, audio data and VPCP bits. 28/4 = 7 loops // BMC encode each by lower 4 bits for (uint32_t i = 0; i < 7 ; ++i) { bmc = lastCellZero ? bmcTable[val & 0x0f] : ~bmcTable[val & 0x0f]; lastCellZero = (bmc & 1) == 0; *txBuffPtr++ = bmc; val >>= 4; } } // consumer, copy allowed, 18bit, 32kHz void setChannelStatus() { // Byte 0: General control and mode information // copy enable channelStatus[0].ltCh = _BV(C_COPY); channelStatus[0].rtCh = _BV(C_COPY); // Byte 1: Category code // general channelStatus[1].ltCh = 0; channelStatus[1].rtCh = 0; // Byte 2: Source and channel number channelStatus[2].ltCh = 0b0001 << 4; channelStatus[2].rtCh = 0b0010 << 4; // Byte 3: Sampling frequency and clock accuracy channelStatus[3].ltCh = _BV(C_FS1) | _BV(C_FS0); channelStatus[3].rtCh = _BV(C_FS1) | _BV(C_FS0); // Byte 23: CRC uint8_t *chStatusPtr = (uint8_t*) channelStatus; channelStatus[23].ltCh = calcCRC(chStatusPtr); ++chStatusPtr; channelStatus[23].rtCh = calcCRC(chStatusPtr); } uint32_t oddParity(uint32_t val) { val ^= val >> 16; val ^= val >> 8; val ^= val >> 4; val ^= val >> 2; val ^= val >> 1; return val & 1; } uint8_t calcCRC(uint8_t *chStatusPtr) { uint8_t crc = 255; for (uint32_t i = 0; i < 24; ++i) { uint8_t b = *chStatusPtr; for (uint32_t j = 0; j < 8; ++j) { if ((crc & 1) ^ (b & 1)) { crc >>= 1; crc ^= 0xb8; } else { crc >>= 1; } b >>= 1; } chStatusPtr += 2; } return crc; } void setupPeripherals() { // enable GPIOB/C clock LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB); LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOC); // Configure GPIO pin : LED_Pin, PC13, OUTPUT LL_GPIO_SetPinMode(GPIOC, LL_GPIO_PIN_13, LL_GPIO_MODE_OUTPUT); LL_GPIO_SetPinOutputType(GPIOC, LL_GPIO_PIN_13, LL_GPIO_OUTPUT_PUSHPULL); LL_GPIO_SetPinSpeed(GPIOC, LL_GPIO_PIN_13, LL_GPIO_SPEED_FREQ_MEDIUM); // SPI IO pins // PB13 -> SCK, 5V tolerant LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_13, LL_GPIO_MODE_ALTERNATE); LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_13, LL_GPIO_PULL_DOWN); LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_13, LL_GPIO_SPEED_FREQ_MEDIUM); // PB14 -> MISO, 5V tolerant LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_14, LL_GPIO_MODE_ALTERNATE); LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_14, LL_GPIO_PULL_DOWN); LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_14, LL_GPIO_SPEED_FREQ_MEDIUM); // PB15 -> MOSI, 5V tolerant LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_15, LL_GPIO_MODE_ALTERNATE); LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_15, LL_GPIO_PULL_DOWN); LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_15, LL_GPIO_SPEED_FREQ_MEDIUM); // PB12 -> LRCK, 5V tolerant LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_12, LL_GPIO_MODE_INPUT); // LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_12, LL_GPIO_PULL_DOWN); LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_12, LL_GPIO_SPEED_FREQ_MEDIUM); // SPI Slave full duplex, mode 0, 16bit LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2); // SPI2 LL_SPI_SetMode(_SPI, LL_SPI_MODE_SLAVE); LL_SPI_SetTransferDirection(_SPI,LL_SPI_FULL_DUPLEX); LL_SPI_SetClockPhase(_SPI, LL_SPI_PHASE_1EDGE); LL_SPI_SetClockPolarity(_SPI, LL_SPI_POLARITY_LOW); LL_SPI_SetTransferBitOrder(_SPI, LL_SPI_MSB_FIRST); LL_SPI_SetDataWidth(_SPI, LL_SPI_DATAWIDTH_16BIT); LL_SPI_SetNSSMode(_SPI, LL_SPI_NSS_SOFT); // connect to DMAReq LL_SPI_EnableDMAReq_RX(_SPI); LL_SPI_EnableDMAReq_TX(_SPI); // DMA LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1); // DMA1 // _SPIRxDMA, DMA1 Channel 4 LL_DMA_ConfigTransfer(_SPIRxDMA, _SPIRxDMACh, LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_PRIORITY_HIGH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD); LL_DMA_ConfigAddresses(_SPIRxDMA, _SPIRxDMACh, (uint32_t)&(_SPI->DR), (uint32_t)&spiRxBuff, LL_DMA_DIRECTION_PERIPH_TO_MEMORY); LL_DMA_SetDataLength(_SPIRxDMA, _SPIRxDMACh, DMA_TRANSFER_LENGTH); // _SPITxDMA, DMA1 Channel5 LL_DMA_ConfigTransfer(_SPITxDMA, _SPITxDMACh, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_PRIORITY_MEDIUM | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD); LL_DMA_ConfigAddresses(_SPITxDMA, _SPITxDMACh, (uint32_t)&spiTxBuff, (uint32_t)&(_SPI->DR), LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetDataLength(_SPITxDMA, _SPITxDMACh, DMA_TRANSFER_LENGTH); // Enable SPIRx DMA IRQ, Half Transfer and Transfer Complete NVIC_SetPriority(_SPIRxDMAIRQn, 0); NVIC_EnableIRQ(_SPIRxDMAIRQn); LL_DMA_EnableIT_TC(_SPIRxDMA, _SPIRxDMACh); LL_DMA_EnableIT_HT(_SPIRxDMA, _SPIRxDMACh); // Enable DMA Channel LL_DMA_EnableChannel(_SPIRxDMA, _SPIRxDMACh); LL_DMA_EnableChannel(_SPITxDMA, _SPITxDMACh); // sleep LL_LPM_EnableSleep(); }