もっと今更、SC-88ProにS/PDIFを付けよう

Dependencies:   mbed

もっと今更、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

というやつ。

/media/uploads/peu605/frontview.jpg

詳細は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();

}