This is the 13th day of my participation in Gwen Challenge
In an article on the STM32 microcontroller serial port interrupt receiving free variable length data, introduces the use of a serial port interrupt receiving free variable length data, there is a problem with this approach is a serial port for each byte received will enter an interrupt, if send data more frequently, then a serial port interrupt will be constantly interrupting the main program running, affect the system running. So can the serial port in the process of receiving data do not receive a data interruption once, only after the completion of a frame of data interruption?
With the idle interrupt of the serial port and the DMA function, it is possible to interrupt each frame only once after the data is received, and during the data receiving process, every byte received by the DMA serial port is stored.
Idle detection and DMA for serial ports are described in detail in the STM32 reference manual.
Here’s how to initialize serial idle interrupts and DMA.
void uart2_init( u16 baud ) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Push pull multiplexing mode gPIo_initstructure. GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Speed = GPIO_Speed_50MHz; // Float input mode gPIo_initstructure. GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init( &NVIC_InitStructure ); USART_InitStructure.USART_BaudRate = baud; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init( USART2, &USART_InitStructure ); #if (UART2_DMA == 1) USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); // enable DMA to receive uartDMA_Init(); #else USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); #endif USART_Cmd(USART2, ENABLE); // Enable RXNE interrupt and IDLE interrupt. RXNE interrupts when 1 byte is received, and IDLE interrupts when a frame is received. For example, eight RXNE interrupts and one IDLE interrupt are generated when eight bytes are sent to the microcontroller at a time. }Copy the code
To make it easier to contrast idle interrupts and DMA with regular serial port Settings, a macro definition UART2_DMA is used to set whether idle interrupts and DMA need to be enabled, and if the macro definition UART2_DMA has a value of 1, then the idle interrupts and DMA are initialized. To set UART2_DMA to 1, you need to turn on IDLE interrupt for serial port 2, enable DMA for serial port 2, and then initialize DMA.
void uartDMA_Init( void ) { DMA_InitTypeDef DMA_IniStructure; RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); // Enable DMA clock DMA_DeInit(DMA1_Channel6); //DMA1 channel 6 is USART2_RX dMA_inistructure. DMA_PeripheralBaseAddr = (u32) &usart2 ->DR; //DMA peripheral usART base address dma_inistruct. DMA_MemoryBaseAddr = (u32) dMA_rec_buff; //DMA memory base address dma_inistructure. DMA_DIR = DMA_DIR_PeripheralSRC; Dma_inistructure. DMA_BufferSize = DMA_REC_LEN; Dma_inistruct. DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_PeripheralInc_Disable; DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_MemoryInc_Enable; DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Carry DMA_IniStructure in bytes.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // The data buffer is moved into dMA_inistructure. DMA_Mode = DMA_Mode_Normal; // Working in normal cache mode dma_inistructure. DMA_Priority = DMA_Priority_Medium; //DMA channel x has medium priority dMA_inistructure. DMA_M2M = DMA_M2M_Disable; //DMA channel x is not set to memory to memory transfer DMA_Init(DMA1_Channel6, &DMA_IniStructure); DMA_Cmd( DMA1_Channel6, ENABLE ); }Copy the code
Set DMA peripheral address to data register USART2->DR and DMA memory address to DMA data cache group DMA_rec_buff. When there is data in the data register of serial port 2, DMA will automatically store the data into the DMA_REC_buff array. When serial port 2 has an idle interrupt, go directly to the DMA_rec_buff array to read the received data.
Let’s look at the interrupt execution of serial port 2
void USART2_IRQHandler( void ) { u8 tem = 0; If (USART_GetITStatus(USART2, USART_IT_IDLE)! {USART_ReceiveData(USART2); // Read data note: this sentence must be used, otherwise it cannot clear the idle interrupt flag bit. DMA_Cmd( DMA1_Channel6, DISABLE ); Uart2_rec_cnt = dma_rec_len-dMA_getCurrdatacounter (DMA1_Channel6); Copy_data (DMA_rec_buff, uART2_rec_cnT); ReceiveOK_flag = 1; USART_ClearITPendingBit(USART2, USART_IT_IDLE); // Clear the idle interrupt flag myDMA_Enable(DMA1_Channel6); If (USART_GetITStatus(USART2, USART2, USART2, USART2, USART2, USART2) USART_IT_RXNE ) ! = RESET) // Receive interrupt {TEM = USART_ReceiveData(USART2); USART_SendData( USART2, tem ); } #endif }Copy the code
In a serial port interrupt if detected idle interrupt flag, shows a serial port receives a frame data over, this time closes the DMA, and calculate the DMA buffer receives the number of bytes to receive copies of data to a backup in the array, and then setting data reception success marks finally cleared the interrupt, restart the DMA, The next data reception starts.
If the serial data receive frequency is not high, can be in here you don’t have to backup the DMA buffer to receive data, main is to prevent the backup data, data receive frequency is very high, when on a data haven’t processing is completed, and received the new data, the data in the array will be overwritten, may lead to abnormal program. So separating the array that handles the data from the array that receives the data prevents the data from being changed during processing.
Let’s take a look at the complete code
#ifndef __UART2_H #define __UART2_H #include "sys.h" #define DMA_REC_LEN 50; void uartDMA_Init( void ); void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx ); void uart2_Send( u8 *buf, u16 len ); void copy_data( u8 *buf, u16 len ); #endifCopy the code
#include "uart2.h" #define uart2_dma_rec_buff [DMA_REC_LEN] = {0}; u16 uart2_rec_cnt = 0; U8 data_backup[DMA_REC_LEN] = {0}; U16 dataLen_backup = 0; _Bool receiveOK_flag = 0; // Receive completion flag bit /* What does idle interrupt mean? When the bus receives data, once the data flow is interrupted and the bus is IDLE, IDLE will be set to 1, resulting in IDLE interruption. When data is sent, the IDLE bit is set to 0. Note: it will not automatically clear 0 after setting 1, nor will it always interrupt because the status bit is 1. It will only occur when 0 jumps to 1, which can also be understood as rising edge trigger. Therefore, to ensure that the next idle interrupt will work properly, you need to send arbitrary data in the interrupt service function to clear the flag bit. */ void uart2_init( u16 baud ) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Push pull multiplexing mode gPIo_initstructure. GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Speed = GPIO_Speed_50MHz; // Float input mode gPIo_initstructure. GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init( &NVIC_InitStructure ); USART_InitStructure.USART_BaudRate = baud; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init( USART2, &USART_InitStructure ); #if (UART2_DMA == 1) USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); // enable DMA to receive uartDMA_Init(); #else USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); #endif USART_Cmd(USART2, ENABLE); // Enable RXNE interrupt and IDLE interrupt. RXNE interrupts when 1 byte is received, and IDLE interrupts when a frame is received. For example, eight RXNE interrupts and one IDLE interrupt are generated when eight bytes are sent to the microcontroller at a time. } void uartDMA_Init( void ) { DMA_InitTypeDef DMA_IniStructure; RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); // Enable DMA clock DMA_DeInit(DMA1_Channel6); //DMA1 channel 6 is USART2_RX dMA_inistructure. DMA_PeripheralBaseAddr = (u32) &usart2 ->DR; //DMA peripheral usART base address dma_inistruct. DMA_MemoryBaseAddr = (u32) dMA_rec_buff; //DMA memory base address dma_inistructure. DMA_DIR = DMA_DIR_PeripheralSRC; Dma_inistructure. DMA_BufferSize = DMA_REC_LEN; Dma_inistruct. DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_PeripheralInc_Disable; DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_MemoryInc_Enable; DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Carry DMA_IniStructure in bytes.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // The data buffer is moved into dMA_inistructure. DMA_Mode = DMA_Mode_Normal; // Working in normal cache mode dma_inistructure. DMA_Priority = DMA_Priority_Medium; //DMA channel x has medium priority dMA_inistructure. DMA_M2M = DMA_M2M_Disable; //DMA channel x is not set to memory to memory transfer DMA_Init(DMA1_Channel6, &DMA_IniStructure); DMA_Cmd( DMA1_Channel6, ENABLE ); } void myDMA_Enable(DMA_Channel_TypeDef*DMA_CHx) {DMA_Cmd(DMA_CHx, DISABLE); DMA_SetCurrDataCounter(DMA_CHx, DMA_REC_LEN); DMA_Cmd(DMA_CHx, ENABLE); Void uart2_Send(u8 *buf, u16 len) {u16 t; void uart2_Send(u8 *buf, u16 len) {u16 t; for( t = 0; t < len; T++) {while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET); USART_SendData( USART2, buf[t] ); } while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET ); } // Back up the received data void copy_data(u8 *buf, u16 len) {u16t; dataLen_backup = len; // Save data length for(t = 0; t < len; t++ ) { data_backup[t] = buf[t]; // Back up the received data to prevent the old data from being overwritten when new data is received during data processing. Void USART2_IRQHandler(void) {u8 tem = 0; void USART2_IRQHandler(void) {u8 tem = 0; If (USART_GetITStatus(USART2, USART_IT_IDLE)! {USART_ReceiveData(USART2); // Read data note: this sentence must be used, otherwise it cannot clear the idle interrupt flag bit. DMA_Cmd( DMA1_Channel6, DISABLE ); Uart2_rec_cnt = dma_rec_len-dMA_getCurrdatacounter (DMA1_Channel6); //uart2_Send(DMA_rec_buff, uart2_rec_cnT); // Send the received data copy_data(dMA_rec_buff, uart2_rec_cnt); ReceiveOK_flag = 1; USART_ClearITPendingBit(USART2, USART_IT_IDLE); // Clear the idle interrupt flag myDMA_Enable(DMA1_Channel6); If (USART_GetITStatus(USART2, USART2, USART2, USART2, USART2, USART2) USART_IT_RXNE ) ! = RESET) // Receive interrupt {TEM = USART_ReceiveData(USART2); USART_SendData( USART2, tem ); } #endif }Copy the code
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "key.h" #include "uart2.h" #include "string.h" extern u8 data_backup[DMA_REC_LEN]; Extern u16 dataLen_backup; Extern _Bool receiveOK_flag; // Extern _Bool receiveOK_flag; Int main(void) {u8 j = 0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); delay_init(); // the delay function initializes LED_Init(); // Initialize the hardware interface connected to the LED uart2_init(9600); While (1) {if(receiveOK_flag) // Start processing data after receiving a frame of data {receiveOK_flag = 0; uart2_Send( data_backup, dataLen_backup ); Memset (data_backup, 0, sizeof(data_backup)); // empty the backup array} j++; if(j > 50) { j = 0; LED = ! LED; } delay_ms(10); }}Copy the code
Let’s look at the test results