Detailed source code of STM32 USART serial port DMA receiving and sending!

I have never had the opportunity to use STM32 for any project. Fortunately, the company's products need to be upgraded on the platform, and STM32 is used, so I am debugging STM32 recently. Let's talk about USART first, then I2C, etc.

Hardware platform: STM32F103ZET6; 

Development environment: KEIL 4;

Let’s talk about the application communication mode first. The working mode of the serial terminal is similar to that of the Diwen screen. The terminal passively accepts the instructions sent by the MCU, and the terminal will occasionally actively send some data to the MCU (like the touch information upload of the Diwen screen).

Serial DMA sending:

The process of sending data:

If there is data to be sent in the foreground program, you need to do the following things

1. Put the data to be sent in the data sending buffer. Note: The first address of the data buffer must be written into the DMA configuration when the DMA is initialized.

2. Assign the number of data bytes to be sent in the data buffer to the sending DMA channel (the serial port sending DMA and the serial port receiving DAM are not the same DMA channel)

3. Turn on DMA. Once turned on, DMA starts to send data. Explain: when KEIL is debugged, DMA and debug are not synchronized, that is, no matter what state of Keil is, DMA always sends data.       

4. Wait for the send completion flag, which is the flag set by point 3 in the Terminal Services function below. Or according to your actual situation, whether you want to wait for this flag bit all the time, you can also cyclically query through the state machine. or otherwise.

Judging that the data transmission is complete:

After the DMA is started and sent, a DMA send completion interrupt is generated, and the following things are done in the interrupt function:

1. Clear the DMA transmission completion interrupt flag bit

2. Close the serial port to send the DMA channel

3. Set a software flag to the foreground program to indicate that the data has been sent

 

Serial DMA reception:

The process of receiving data:

The serial port receiving DMA is in the open state when it is initialized, and it has been waiting for the arrival of data. There is no need to do anything in the software, as long as the configuration is set when the configuration is initialized.

 

Judging data data reception is complete:

       Here, it is judged that the reception is completed by means of serial port idle interrupt, that is, when the serial port data flow stops, an IDLE interrupt will be generated. This interrupt does the following things:

1. Close the serial port to receive the DMA channel, 2 reasons: 1. To prevent further data from being received later, causing interference. 2. Facilitate the reconfiguration and assignment of DMA , the fourth point below.      

2. Clear all DMA flags

3. Get the number of data bytes received from the DMA register

4. Reset the number of data bytes to be received by the DMA next time. Note that this is to reset the received count value for the DMA register. This number can only be greater than or equal to the number of bytes that may be received. Otherwise, when the DMA receive counter is decremented to When it is 0 , the count value will be reloaded again, and the count down will be cycled again, so the data in the receive buffer will be overwritten and lost.

5. Turn on the DMA channel and wait for the next data reception. Note that writing to the DMA -related register configuration, such as the write count value in Article 4 , must be done with the DMA turned off , otherwise the operation will be invalid.

To explain, the IDLE interrupt of STM32 will not be generated all the time when there is no data received by the serial port. The conditions are as follows. When the IDLE flag is cleared, the first data must be received before it starts. Triggered, once the received data is cut off, and no data is received, an IDLE interrupt is generated.

 

USART and DMA hardware initialization configuration

 

/*--- LumModule Usart Config ---------------------------------------*/

 

#define LUMMOD_UART                      USART3

#define LUMMOD_UART_GPIO                 GPIOC

#define LUMMOD_UART_CLK                  RCC_APB1Periph_USART3

#define LUMMOD_UART_GPIO_CLK        RCC_APB2Periph_GPIOC

#define LUMMOD_UART_RxPin               GPIO_Pin_11

#define LUMMOD_UART_TxPin               GPIO_Pin_10

#define LUMMOD_UART_IRQn                USART3_IRQn

#define LUMMOD_UART_DR_Base                  (USART3_BASE + 0x4)  //0x40013804

 

#define LUMMOD_UART_Tx_DMA_Channel      DMA1_Channel2

#define LUMMOD_UART_Tx_DMA_FLAG         DMA1_FLAG_GL2//DMA1_FLAG_TC2 | DMA1_FLAG_TE2 

#define LUMMOD_UART_Tx_DMA_IRQ          DMA1_Channel2_IRQn

 

#define LUMMOD_UART_Rx_DMA_Channel      DMA1_Channel3

#define LUMMOD_UART_Rx_DMA_FLAG         DMA1_FLAG_GL3//DMA1_FLAG_TC3 | DMA1_FLAG_TE3 

#define LUMMOD_UART_Rx_DMA_IRQ      DMA1_Channel3_IRQn

 

 

void Uart_Init(void)

{

    NVIC_InitTypeDef NVIC_InitStructure;

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

 

    /* System Clocks Configuration */

//= System Clocks Configuration ====================================================================//

   

    /* Enable GPIO clock */

    RCC_APB2PeriphClockCmd(LUMMOD_UART_GPIO_CLK , ENABLE ); // Turn on the clock of the IO port where the serial port is located

    /* Enable USART Clock */

    RCC_APB1PeriphClockCmd(LUMMOD_UART_CLK, ENABLE); // Start serial clock

   

   

//=NVIC_Configuration==============================================================================//

 

    /* Configure the NVIC Preemption Priority Bits */

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);

 

    /* Enable the DMA Interrupt */

    NVIC_InitStructure.NVIC_IRQChannel = LUMMOD_UART_Tx_DMA_IRQ; // Transmit DMA channel interrupt configuration

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // priority setting

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);

 

    /* Enable the USART Interrupt */

    NVIC_InitStructure.NVIC_IRQChannel = LUMMOD_UART_IRQn; // Serial port interrupt configuration

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);

   

//=GPIO_Configuration==============================================================================//

 

    GPIO_PinRemapConfig(GPIO_PartialRemap_USART3, ENABLE); // I did not use the default IO port here, so I remapped it, which can be configured according to your own hardware conditions

   

    /* Configure USART3 Rx as input floating */

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // Serial port receiving IO port settings

    GPIO_InitStructure.GPIO_Pin = LUMMOD_UART_RxPin;

    GPIO_Init(LUMMOD_UART_GPIO, &GPIO_InitStructure);

 

    /* Configure USART3 Tx as alternate function push-pull */

    GPIO_InitStructure.GPIO_Speed ​​= GPIO_Speed_50MHz; // Serial port send IO port settings

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // This is set to multiplexed push-pull output   

    GPIO_InitStructure.GPIO_Pin = LUMMOD_UART_TxPin;

    GPIO_Init(LUMMOD_UART_GPIO, &GPIO_InitStructure);

 

    DMA_Uart_Init(); // Serial DMA configuration

 

    /* USART Format configuration ------------------------------------------------------*/

 

    USART_InitStructure.USART_WordLength = USART_WordLength_8b; // Serial port format configuration

    USART_InitStructure.USART_StopBits = USART_StopBits_1;

    USART_InitStructure.USART_Parity = USART_Parity_No;

    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

 

    /* Configure USART3 */

    USART_InitStructure.USART_BaudRate = 115200; // Baud rate setting

    USART_Init(LUMMOD_UART, &USART_InitStructure);

 

    /* Enable USART3 Receive and Transmit interrupts */

    USART_ITConfig(LUMMOD_UART, USART_IT_IDLE, ENABLE); // Enable serial port idle IDEL interrupt

   

    /* Enable the USART3 */

    USART_Cmd(LUMMOD_UART, ENABLE); // Enable serial port

    /* Enable USARTy DMA TX request */

    USART_DMACmd(LUMMOD_UART, USART_DMAReq_Tx, ENABLE); // Enable serial port DMA transmission

    USART_DMACmd(LUMMOD_UART, USART_DMAReq_Rx, ENABLE); // Enable serial port DMA reception

}

 

 

void DMA_Uart_Init(void)

{

    DMA_InitTypeDef DMA_InitStructure;

   

    /* DMA clock enable */

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Enable DMA1 clock

   

   

//=DMA_Configuration==============================================================================//

 

/*--- LUMMOD_UART_Tx_DMA_Channel DMA Config ---*/

 

    DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // Disable DMA channel

    DMA_DeInit(LUMMOD_UART_Tx_DMA_Channel); // restore default value

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&LUMMOD_UART->DR);// Set serial port send data register

    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LumMod_Tx_Buf; // Set the first address of the send buffer

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // set peripheral bit target, memory buffer -> peripheral register

    DMA_InitStructure.DMA_BufferSize = LUMMOD_TX_BSIZE; // The number of bytes to be sent can actually be set to 0 here, because the secondary value will be reset when it is actually sent

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // The peripheral address does not increase and adjust, and the adjustment is automatically realized by DMA

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // Memory buffer address increase adjustment

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Peripheral data width 8 bits, 1 byte

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // Memory data width 8 bits, 1 byte

    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // single transfer mode

    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // priority setting

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // Disable memory-to-memory DMA mode

    DMA_Init(LUMMOD_UART_Tx_DMA_Channel, &DMA_InitStructure); // write configuration

    DMA_ClearFlag(LUMMOD_UART_Tx_DMA_FLAG); // Clear all DMA flags

DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // 关闭DMA

    DMA_ITConfig(LUMMOD_UART_Tx_DMA_Channel, DMA_IT_TC, ENABLE); // Enable send DMA channel interrupt

   

/*--- LUMMOD_UART_Rx_DMA_Channel DMA Config ---*/

 

    DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, DISABLE); // Disable DMA channel

    DMA_DeInit(LUMMOD_UART_Rx_DMA_Channel); // restore default value

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&LUMMOD_UART->DR);//Set serial port receive data register

    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LumMod_Rx_Buf; // Set the first address of the receive buffer

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // Set peripheral as data source, peripheral register -> memory buffer

    DMA_InitStructure.DMA_BufferSize = LUMMOD_RX_BSIZE; // requires the maximum possible number of bytes received

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // The peripheral address does not increase and adjust, and the adjustment is automatically realized by DMA

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // Memory buffer address increase adjustment

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Peripheral data width 8 bits, 1 byte

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // Memory data width 8 bits, 1 byte

    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // single transfer mode

    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // priority setting

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // Disable memory-to-memory DMA mode

    DMA_Init(LUMMOD_UART_Rx_DMA_Channel, &DMA_InitStructure); // write configuration

    DMA_ClearFlag(LUMMOD_UART_Rx_DMA_FLAG); // Clear all DMA flags

    DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, ENABLE); // Open the receive DMA channel and wait for data to be received

   

}

void BSP_Init(void)

{

    Uart_Init();

}

 

//============================================================//

DMA send application source code

 

void DMA1_Channel2_IRQHandler(void)

{

    if(DMA_GetITStatus(DMA1_FLAG_TC2))

    {

        LumMod_Uart_DAM_Tx_Over();

    }

}

void LumMod_Uart_DAM_Tx_Over(void)

{

    DMA_ClearFlag(LUMMOD_UART_Tx_DMA_FLAG); // clear flag

    DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // close the DMA channel

    OSMboxPost(mbLumModule_Tx, (void*)1); // Set the flag bit, here I use UCOSII, which can be modified according to your own needs

}

 

void LumMod_Cmd_WriteParam( uint8 sample_num, uint8 *psz_param )

{

    uint8 err;

    uint8 LumMod_Tx_Index ;

 

    LumMod_Tx_Index = 0;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 1;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 2;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 3;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 4;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 5;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 6;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 7;

    LumMod_Tx_Buf[LumMod_Tx_Index++] = 8;

   

    LumMod_Uart_Start_DMA_Tx( LumMod_Tx_Index );

    OSMboxPend(mbLumModule_Tx, 0, &err);

}

 

void LumMod_Uart_Start_DMA_Tx(uint16_t size)

{

    LUMMOD_UART_Tx_DMA_Channel->CNDTR = (uint16_t)size; // Set the number of bytes to send

    DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, ENABLE); //Start DMA transmission

}

 

//============================================================//

DMA receive application source code

 

void USART3_IRQHandler(void)

{

    if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) // idle interrupt

    {

        LumMod_Uart_DMA_Rx_Data();

        USART_ReceiveData( USART3 ); // Clear IDLE interrupt flag bit

    }

}

void LumMod_Uart_DMA_Rx_Data(void)

{

    DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, DISABLE); // Turn off DMA to prevent interference

    DMA_ClearFlag( LUMMOD_UART_Rx_DMA_FLAG ); // Clear the DMA flag

    LumMod_Rx_Data.index = LUMMOD_RX_BSIZE - DMA_GetCurrDataCounter(LUMMOD_UART_Rx_DMA_Channel); //Get the number of bytes received

    LUMMOD_UART_Rx_DMA_Channel->CNDTR = LUMMOD_RX_BSIZE; // Reassign the count value, which must be greater than or equal to the maximum possible number of received data frames

    DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, ENABLE); /* DMA is enabled, waiting for data. Note that if the rate of interrupt sending data frames is very fast, the MCU has no time to process the data received this time, and if the interrupt sends data again, it cannot be turned on here, otherwise the data will be overwritten. There are 2 ways to solve it.

    1. Before reopening the receive DMA channel, copy the data in the LumMod_Rx_Buf buffer to another array, then turn on the DMA, and then process the copied data immediately.

    2. Create a double buffer. In the LumMod_Uart_DMA_Rx_Data function, reconfigure the buffer address of DMA_MemoryBaseAddr, then the data received next time will be saved in a new buffer and will not be overwritten. */

    OSMboxPost(mbLumModule_Rx, LumMod_Rx_Buf); // Send and receive the new data flag for the foreground program to query

}


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325987295&siteId=291194637