もっと今更、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-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());
    }
*/