Huada MCU_ Pits encountered by HC32F460 serial TX send using DMA transport

Keywords: Embedded system Single-Chip Microcomputer MCU dma

DAM Initialization Structure

DMA: One request to transmit one data block, supporting the chain transfer function, can achieve one request to transmit more than one data block.
A data block is a minimum of 1 data block and can be up to 1024 data blocks, each of which can be configured to be 8, 16, or 32 bits wide.

/*  DMA Initialize structure: */
    typedef struct stc_dma_config
    {
        uint16_t                u16BlockSize;       ///<Set the size of the data block, 0~1023 (0 means 1024, that is, 1 has 1024 data)
        uint16_t                u16TransferCnt;     ///<Total number of transfers, each request (triggered by a DMA trigger source once) initiates a block transfer
        uint32_t                u32SrcAddr;         ///<Source Address
        uint32_t                u32DesAddr;         ///<Destination Address
        uint16_t                u16SrcRptSize;      ///<Source Address Duplicate Area Size
        uint16_t                u16DesRptSize;      ///<Target Address Duplicate Area Size
        uint32_t                u32DmaLlp;          ///<Chain Pointer for Chain Transport
        stc_dma_nseq_cfg_t      stcSrcNseqCfg;      ///<Discontinuous Source Address
        stc_dma_nseq_cfg_t      stcDesNseqCfg;      ///<Discontinuous destination address
        stc_dma_ch_cfg_t        stcDmaChCfg;        ///<Channel settings, see the structure below
    }stc_dma_config_t;

    typedef struct stc_dma_ch_cfg
    {
        en_dma_address_mode_t   enSrcInc;       ///< DMA source address mode (self-increasing, self-decreasing, unchanged)
        en_dma_address_mode_t   enDesInc;       ///< DMA target address mode (self-increasing, self-decreasing, unchanged)
        en_functional_state_t   enSrcRptEn;     ///<Source Address Duplication Enabling
        en_functional_state_t   enDesRptEn;     ///<Target Address Duplication Enabling
        en_functional_state_t   enSrcNseqEn;    ///<Discontinuous Source Address Enabling
        en_functional_state_t   enDesNseqEn;    ///<Discontinuous Target Address Enabling
        en_functional_state_t   enLlpEn;        ///<Chain Transport Enabling
        en_dma_llp_mode_t       enLlpMd;        ///<Linked Transport Mode
        en_dma_transfer_width_t enTrnWidth;     ///< 1 data width
        en_functional_state_t   enIntEn;        ///<enable interruption
    }stc_dma_ch_cfg_t;

    typedef struct stc_dma_nseq_cfg
    {
        uint32_t                u32Offset;      ///< DMA no-sequence offset.
        uint16_t                u16Cnt;         ///< DMA no-sequence count.
    }stc_dma_nseq_cfg_t;

Two of these parameters are detailed:
u16BlockSize: Sets the size of the data block to configure up to 1024 data blocks.
The register value is set to 1 data transfer per time and 0 to 1024 data transfer per time.
Since only 10 bits of this register set this value, the maximum is 1023 (0x3FF), not 1024, so 0 represents 1024.

u16TransferCnt: Total number of transfers, starting one data block per request, number of transfers at completion
Counter minus 1, when reduced to 0, transmission completion interrupt occurs. If set to 0, it means unlimited transfers, each time enabled
Move a request to transfer a block of data, and the number of times counter remains unchanged at completion without interruption of transmission completion

Technological process

1. Initialize the serial port normally:

Two send interrupts of the serial port are not opened (INT_USART3_TCI and INT_USART3_TI).
When enabling a serial port, make it acceptable; send UsartTx and UsartTxEmptyInt do not.

USART_FuncCmd(M4_USART3, UsartRx, Enable);     //Enable to receive
USART_FuncCmd(M4_USART3, UsartRxInt, Enable);  //Enable to receive interrupts

2. Normal configuration of DMA

Some things to note:
Initialize the configuration, for example:

stc_dma_config_t DmaInit_config = {
            .u16BlockSize   = 1u,
            .u16TransferCnt = TxDMA_TestBuf_SIZE,
            .u32SrcAddr     = (uint32_t)(TxDMA_TestBuf),
            .u32DesAddr     = (uint32_t)(&(M4_USART3->DR)),
            .stcDmaChCfg.enSrcInc    = AddressIncrease,     ///<Source Address Mode (self-increasing, self-decreasing, unchanged)
            .stcDmaChCfg.enDesInc    = AddressFix,          ///<Target Address Mode (self-increasing, self-decreasing, unchanged)
            .stcDmaChCfg.enIntEn     = Enable,              ///<enable interruption
            .stcDmaChCfg.enTrnWidth  = Dma8Bit,             ///< 1 data width
        };

Set trigger source to serial data register air interrupt event:

DMA_SetTriggerSrc(M4_DMA2, DmaCh1, EVT_USART3_TI);

Just turn on the DMA transport to complete the interrupt (block transfer complete interrupt can be turned on while debugging):

 	stc_irq_regi_conf_t stcIrqRegiCfg; 
    stcIrqRegiCfg.enIRQn      = Int038_IRQn;
    stcIrqRegiCfg.enIntSrc    = INT_DMA2_BTC1;
    stcIrqRegiCfg.pfnCallback = &DMA2_CH1_BtcIrqCallback; //Interrupt function
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DMA_IRQ_PRIORITY);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

Then we need to stop the serial transmission in the end of the DMA transmission interrupt:

//DMA2_CH1 Transmission Complete Interrupt
static void DMA2_CH1_TcIrqCallback(void)
{
    DMA_ClearIrqFlag(M4_DMA2, DmaCh1, TrnCpltIrq);
    USART3_TxDMA_Flag = Tx_Cplt;  /* USART3_DMA Transmission Completion Flag Bit */
    
    while (Set != USART_GetStatus(M4_USART3, UsartTxEmpty))
    {
        /* Don't change, deleting this end will lose another character */
    }
    while (Set != USART_GetStatus(M4_USART3, UsartTxComplete))
    {
        /* Don't change, deleting this will lose the last character */
    }
    
    M4_USART3->CR1 &= ~0x88; /* Direct Write Register, equivalent to 2 sentences of the note below */
    //USART_FuncCmd(M4_USART3, UsartTx, Disable); // Failed to send
    //USART_FuncCmd(M4_USART3, UsartTxEmptyInt, Disable); // Failed to send data register air interrupt, terminating DMA trigger source
}

3. Repeat Transport

When we want to turn on the transmission, we can reset the sending source address and data length first:

DMA_SetTransferCnt(M4_DMA2,DmaCh1,u16TrnCnt);
DMA_SetSrcAddress(M4_DMA2,DmaCh1,u32Address);

Then open the serial port to send, turn on the transmission:

M4_DMA2->CHEN |= 0x01<<DmaCh1;  //Equal to Function DMA_ Channel Cmd()
M4_USART3->CR1 |= 0x88; //Equivalent to function USART_FuncCmd() Open UsartTx and UsartTxEmptyInt

4. Discover problems

There was no problem communicating with the computer, but when trying to communicate with another development board serial port, it was found that it could only be transmitted once, and then it died.
Later in the manual I read the following instructions:
I used to switch (enable&disable) UsartTx repeatedly to disconnect another development board from me, and then only one of the development boards can be reconnected if it restarts.
Baby, why not even PC?

5. Solve problems

So let's leave the UsartTx off and keep it enabled, and then just switch on (enable&disable) the UsartTxEmptyInt interrupt.
Change DMA interrupt to (only failed UsartTxEmptyInt):

//DMA2_CH1 Transmission Complete Interrupt
static void DMA2_CH1_TcIrqCallback(void)
{
    DMA_ClearIrqFlag(M4_DMA2, DmaCh1, TrnCpltIrq);
    USART3_TxDMA_Flag = Tx_Cplt;  /* USART3_DMA Transmission Completion Flag Bit */
    
    while (Set != USART_GetStatus(M4_USART3, UsartTxEmpty))
    {
        /* Don't change, deleting this end will lose another character */
    }
    while (Set != USART_GetStatus(M4_USART3, UsartTxComplete))
    {
        /* Don't change, deleting this will lose the last character */
    }
    
    M4_USART3->CR1 &= ~0x80; /* Direct Write Register, equivalent to 1 sentence of the comment below */
    //USART_FuncCmd(M4_USART3, UsartTxEmptyInt, Disable); // Failed to send data register air interrupt, terminating DMA trigger source
}

Serial port initialization also allows sending:

USART_FuncCmd(M4_USART3, UsartRx, Enable);     //Enable to receive
USART_FuncCmd(M4_USART3, UsartRxInt, Enable);  //Enable to receive interrupts
USART_FuncCmd(M4_USART3, UsartTx, Enable);     //Enable Sending

The same steps are used when you want to transfer:

//Set Source Target
DMA_SetTransferCnt(M4_DMA2,DmaCh1,u16TrnCnt);
DMA_SetSrcAddress(M4_DMA2,DmaCh1,u32Address);
//Open DMA channel and enable serial port interrupt
M4_DMA2->CHEN |= 0x01<<DmaCh1;  //Equal to Function DMA_ Channel Cmd()
M4_USART3->CR1 |= 0x88; //Equivalent to function USART_FuncCmd() Open UsartTx and UsartTxEmptyInt

6. The problem has come again

After step 5, it is found that it can only be sent once, although the connection is not broken, it can only be sent once.
UsartTxEmptyInt does not interrupt INT_USART3_TI generation!
See the official instructions:

This wave is:
You interrupted, I started sending data!
Then you send the data first, so I can start interrupting!
. . . . . .

7. Re-solve the problem

So before we start sending, just write the first character we want to send directly to the send register, which triggers the interrupt.
The other settings are unchanged. Write one more sentence at the end when you want to transfer data:

//Set the source target, starting with the second character
DMA_SetTransferCnt(M4_DMA2, DmaCh1, buf_len-1);
DMA_SetSrcAddress(M4_DMA2, DmaCh1, (uint32_t)(&buf[1])); 
//Open DMA channel and enable serial port interrupt
M4_DMA2->CHEN |= 0x01<<DmaCh1;  //Equal to Function DMA_ Channel Cmd()
M4_USART3->CR1 |= 0x88;         //Equivalent to function USART_FuncCmd() Open UsartTx and UsartTxEmptyInt

USART_SendFrameHeader(M4_USART3,buf[0]); //Sending header triggers Tx register air interrupt to start DMA transmission

USART_ The SendFrameHeader function is implemented as follows:

static void USART_SendFrameHeader(M4_USART_TypeDef *USARTx, uint8_t u8Data)
{
    USARTx->DR_f.TDR = (uint32_t)u8Data;
}

8. Then another small question

After step 7, it can be sent normally, that is, the second character will be missing from the first send every time you turn on the computer.
Think of step 7, which happened before because you could only send it once, that is, the first send without TX can trigger normally, but we do this again, causing the first and second characters to grab resources and the second character to be destroyed.

Let's start with a meaningless character/string.
But it's really good. Shake off an alien sentence when you meet.

I decided to make the DMA carry meaninglessly in its own storage space first when it is initialized, so it will be removed the first time, and then it will be normal!

DMA initialization changed to:

stc_dma_config_t DmaInit_config = {
            .u16BlockSize   = 1u,
            .u16TransferCnt = 1,
            .u32SrcAddr     = (uint32_t)(&TxDMA_TestBuf[0]),
            .u32DesAddr     = (uint32_t)(&TxDMA_TestBuf[1]),
            .stcDmaChCfg.enSrcInc    = AddressIncrease,     ///<Source Address Mode (self-increasing, self-decreasing, unchanged)
            .stcDmaChCfg.enDesInc    = AddressFix,          ///<Target Address Mode (self-increasing, self-decreasing, unchanged)
            .stcDmaChCfg.enIntEn     = Enable,              ///<enable interruption
            .stcDmaChCfg.enTrnWidth  = Dma8Bit,             ///< 1 data width
        };

Serial port initialization then directly enables the interrupt:

   USART_FuncCmd(M4_USART3, UsartRx, Enable);     //Enable to receive
   USART_FuncCmd(M4_USART3, UsartRxInt, Enable);  //Enable to receive interrupts
   USART_FuncCmd(M4_USART3, UsartTx, Enable);     //Enable Sending
   USART_FuncCmd(M4_USART3, UsartTxEmptyInt, Enable); //Send Data Register Air Interrupt

This way, the DMA will carry itself once after initialization is complete, which is a fantastic first time.
Then we can change the destination address back before we start polling:

    /* The first trigger of DMA serial send will lose 1 character, so reconfigure the address after the first trigger of DMA */
    DMA_SetDesAddress(M4_DMA2, DmaCh1, (uint32_t)(&(M4_USART3->DR)));

Broken thoughts

Alas, Rx sending is much simpler to write with DMA transmission, but with less work, just pay attention to setting up a cyclic mode to carry or modifying the destination address of the next move in the block transfer completion interrupt.
It's a trouble for Tx to use DMA thieves.

Posted by CRichardson on Wed, 24 Nov 2021 13:49:47 -0800