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.