もっと今更、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-12
- Revision:
- 11:79658b6d3f39
- Parent:
- 10:bbf0ab5cc56f
File content as of revision 11:79658b6d3f39:
#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 13 // 32 - 18 -1 #define MASK31_20 0xfff00000 #define MASK23_0 0x00ffffff //#define _DEBUG_CONSOLE // 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; // Serial pc(PA_2, PA_3); 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() { // change to 72MHz SystemClock_Config(); 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) { __WFI(); #ifdef _DEBUG_CONSOLE debugOut(); #endif } } void transferFrames() { // measured load is 18.6% at 72MHz _LED_OFF(); // PC_13 to HIGH for (uint32_t i = 0; i < BUFFER_NUM_FRAMES / 2; ++i) { transferFrame(); } _LED_ON(); // PC_13 to LOW } 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 seems to be 2's complement 18 bit audio data in 20 bit data frame. // // SPIRxBuff, each channel data have 128(=256/2) bits, M:MSB, L:LSB // 127 <--- ---> 0 // 0000 ... 0000000 00000000000M==== ===============L // 1111 ... 1111111 11111111111M==== ===============L // ---- ... ------- ---------------- ---------------- // +0 -> 5 hlf_word +6 +7 // 1111 ... 0000000 1111110000000000 1111110000000000 // 5432 6543210 5432109876543210 5432109876543210 // // Actually, SPI starts some SCKs after LRCK change. // Received data are shifted leftward. // // 127 <--- ---> 0 // |<->| spiDelay // M========= ==========L // ---- ... ------- ---------------- ---------------- // +0 -> 5 hlf_word +6 +7 // 1111 ... 0000000 1111110000000000 1111110000000000 // 5432 6543210 5432109876543210 5432109876543210 // // bit[127:20] is all 0 or all 1. SDO keeps last LSB state. // // bit[31:20] should be 000...00 or 111...11 // bit[19:0] MSB to LSB, 2's complement 18 bit audio data in 20 bit data frame // // measured spiDelay is 2 to 4 // arithmetic right shift int32_t val = (*(rxBuffPtr + 6) << 16) | *(rxBuffPtr + 7); val >>= spiDelay; // determine spiDelay while(true) { int32_t test = val & MASK31_20; if (test == 0 || test == MASK31_20) { 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, adding zero 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 &= MASK23_0; // 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 encode and 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 // ===> BMC encode from LSB to MSB //(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 (!aCh) { // b channel bmc = lastCellZero ? PREAMBLE_Y : ~PREAMBLE_Y; } else if (frameIndex == 0) { // a channel, frame 0 bmc = lastCellZero ? PREAMBLE_Z : ~PREAMBLE_Z; } else { // a channel, frame != 0 bmc = lastCellZero ? PREAMBLE_X : ~PREAMBLE_X; } // lastCellZero = !(bmc & 1); lastCellZero = (bmc & 1) == 0; *txBuffPtr++ = bmc; // Next, 5 ~ 31 bit, audio data and VUCP 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); 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; } #ifdef _DEBUG_CONSOLE void debugOut() { char str[32]; // printf("SystemCoreClock =%d\n", SystemCoreClock); // // dump raw data // uint16_t tmp = spiRxBuff[0].ltCh[6]; // toBinary(&tmp, (char*) &str); // printf("Delay:%02d %s ", spiDelay, str); // tmp = spiRxBuff[0].ltCh[7]; // toBinary(&tmp, (char*) &str); // printf("%s\n", str); // HAL_Delay(200); // dump shifted data int32_t val = (spiRxBuff[0].ltCh[6] << 16) | (spiRxBuff[0].ltCh[7]); val >>= spiDelay; uint16_t tmp = (val & 0xffff0000) >> 16; toBinary(&tmp, (char*) &str); printf("Delay:%02d %s ", spiDelay, str); tmp = val & 0xffff; toBinary(&tmp, (char*) &str); printf("%s\n", str); HAL_Delay(200); // // find loud 18bit data // int32_t val = (spiRxBuff[0].ltCh[6] << 16) | (spiRxBuff[0].ltCh[7]); // val >>= spiDelay; // uint16_t tmp = (val & 0xffff0000) >> 16; // if ((tmp & 0b1111) == 0b1110 || (tmp & 0b1111) == 0b0001) { // toBinary(&tmp, (char*) &str); // printf("Delay:%02d %s ", spiDelay, str); // tmp = val & 0xffff; // toBinary(&tmp, (char*) &str); // printf("%s\n", str); // } } void toBinary(uint16_t *val_ptr, char *str) { uint16_t val = *val_ptr; for (uint32_t j = 0; j < 16; ++j) { *str = val & 0x8000 ? '1' : '0'; val <<= 1; ++str; } *str = '\0'; } #endif /** * @brief System Clock Configuration * The system Clock is configured as follow : * System Clock source = PLL (HSE) * SYSCLK(Hz) = 72000000 * HCLK(Hz) = 72000000 * AHB Prescaler = 1 * APB1 Prescaler = 2 * APB2 Prescaler = 1 * HSE Frequency(Hz) = 8000000 * PLLMUL = 9 * Flash Latency(WS) = 2 * @param None * @retval None */ void SystemClock_Config(void) { HAL_RCC_DeInit(); // LL_RCC_DeInit(); // not implemented yet? // /* Set FLASH latency */ // LL_FLASH_SetLatency(LL_FLASH_LATENCY_2); // // /* Enable HSE oscillator */ // LL_RCC_HSE_Enable(); // while(LL_RCC_HSE_IsReady() != 1) { // }; // // /* Main PLL configuration and activation */ // LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSE_DIV_1, LL_RCC_PLL_MUL_9); // // LL_RCC_PLL_Enable(); // while(LL_RCC_PLL_IsReady() != 1) { // }; // // /* Sysclk activation on the main PLL */ // LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1); // LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL); // while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) { // }; // // /* Set APB1 & APB2 prescaler*/ // LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_2); // LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1); // // /* Set systick to 1ms in using frequency set to 72MHz */ // LL_Init1msTick(72000000); // // /* Update CMSIS variable (which can be updated also through SystemCoreClockUpdate function) */ // LL_SetSystemCoreClock(72000000); // // uint8_t SetSysClock_PLL_HSE(uint8_t bypass); in "system_clock.c", 'bypass = false' means using X'tal. // SetSysClock_PLL_HSE(false); RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_PeriphCLKInitTypeDef RCC_PeriphCLKInit; /* Enable HSE oscillator and activate PLL with HSE as source */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 72 MHz (8 MHz * 9) HAL_RCC_OscConfig(&RCC_OscInitStruct); /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 72 MHz RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // 72 MHz RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // 36 MHz RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // 72 MHz HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); /* USB clock selection */ RCC_PeriphCLKInit.PeriphClockSelection = RCC_PERIPHCLK_USB; RCC_PeriphCLKInit.UsbClockSelection = RCC_USBCLKSOURCE_PLL_DIV1_5; HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInit); // "system_stm32f1xx.h" SystemCoreClockUpdate(); // I don't know HAL_Init() is needed or not. HAL_Init(); // restart uart extern serial_t stdio_uart; serial_baud(&stdio_uart, 9600); } 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); } /* // Use java to create BMC table, because I am a javaer. private void start() { StringBuilder sb = new StringBuilder(); for (char i = 0; i < 16; ++i) { sb.append(String.format("%02d", (int) i)).append(" = 0b"); boolean lastCellZero = true; char c = i; for (int j = 0; j < 4; ++j) { if ((c & 1) == 0) { if (lastCellZero) { sb.append("1111"); lastCellZero = false; } else { sb.append("0000"); lastCellZero = true; } } else { if (lastCellZero) { sb.append("1100"); lastCellZero = true; } else { sb.append("0011"); lastCellZero = false; } } c >>= 1; } sb.append('\n'); } System.out.println(sb.toString()); } */