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
}