GD32F103 debugging notes (2) USART (receive interrupt, receive idle interrupt + DMA, send DMA)

Preface

In the last article, I finished touching the ADC+DMA of GD32F103 debugging notes (1) , and next I touched on the USART of GD32.

DMA

A data porter and a good assistant for the CPU.

USE

A serial communication protocol, to put it bluntly, allows two lines to switch high and low levels according to certain rules. Based on the duration of the high and low levels within a unit time, we determine whether the start signal is data 0 or 1, parity bit, etc. In fact, whether it is a single bus (1-wire), two wires (USART, IIC), or even multiple wires (SPI3 wires or 4 wires, Modbus), they are all based on this, and then form their own protocols based on different definitions. For specific agreement provisions, you can check the relevant information on the Internet, and I will not introduce them here. So everyone must understand that these protocols can theoretically be simulated by software through several IO ports, but this will consume CPU resources. Therefore, many microcontroller manufacturers will have hardware modules that support these protocols. We only need to simply configure a few registers to use these protocols to communicate with peripheral devices and complete the product functions we want. The hardware USART of GD32 is also used today.

Programming of each module

I use GD32F103RC ( Note: Please make sure you have a GD32F103 project that can be compiled and it already contains the standard library ).

  • 1. First configure the clocks of each hardware peripheral (DMA clock, GPIO clock, USART clock, AF clock).

void SystemClock_Reconfig(void)
{
    
    
		/* Initializes the CPU, AHB and APB busses clocks */
		rcu_pll_config(RCU_PLLSRC_HXTAL,RCU_PLL_MUL9);				//set PLL's Source and multiple number				
		rcu_system_clock_source_config(RCU_CKSYSSRC_PLL);			//select the PLL to the system clock source			Fsys = 8M * 9 = 72MHz 
		
		/* Due to AHB,APB2,APB1 has been initialized in SystemInit();(AHB = SYSCLK,APB2 = AHB/1,APB1 = AHB/2)
		 * We don't need to configure it again if unnecessary.
		 *
		 * CK_APB1 Max is 54MHz and CK_APB2 Max is 108MHz in GD32F10X,
		 * Please ensure your prescaler is not above Max Frequency.
	   */
//		rcu_ahb_clock_config(RCU_AHB_CKSYS_DIV1);							//set AHB Clock is equal to system clock		
//		rcu_apb1_clock_config(RCU_APB1_CKAHB_DIV2);						//set CK_APB1 is equal to half AHB clock			Fck_apb1 = 72M / 2 = 36MHz
//		rcu_apb2_clock_config(RCU_APB2_CKAHB_DIV1);						//set CK_APB1 is equal to AHB Clock						Fck_apb2 = 72M / 1 = 72MHz

		rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);					//set CK_ADCx is equal to CK_APB2/6						Fck_adcx = 72M / 6 = 12MHz
//		rcu_usb_clock_config(RCU_CKUSB_CKPLL_DIV2);						//set CK_USBD is equal to CK_PLL/2						Fck_usbd = 72M / 2 = 36MHz
//		rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_128);				//set CK_RTC is equal to CK_HXTAL/128					Fck_rtc = 8M / 128 = 62.5KHz		
	
		/* Enable all peripherals clocks you need*/
		rcu_periph_clock_enable(RCU_GPIOA);
		rcu_periph_clock_enable(RCU_GPIOB);
		rcu_periph_clock_enable(RCU_GPIOC);
		rcu_periph_clock_enable(RCU_GPIOD);
		rcu_periph_clock_enable(RCU_GPIOE);
		rcu_periph_clock_enable(RCU_GPIOF);
		rcu_periph_clock_enable(RCU_GPIOG);
		
		rcu_periph_clock_enable(RCU_AF);
		rcu_periph_clock_enable(RCU_DMA0);
		rcu_periph_clock_enable(RCU_DMA1);
		rcu_periph_clock_enable(RCU_I2C1);
		rcu_periph_clock_enable(RCU_ADC0);
		rcu_periph_clock_enable(RCU_ADC2);
		rcu_periph_clock_enable(RCU_USART1);
		rcu_periph_clock_enable(RCU_USART2);
		
		/* Timer1,2,3,4,5,6,11,12,13 are hanged on APB1,
		 * Timer0,7,8,9,10 					 are hanged on APB2
		 */
		rcu_periph_clock_enable(RCU_TIMER1);									
}

Please add image descriptionPlease add image description

  • 2. Then configure our GPIO.

  • I am using USART1 (PA2, PA3), USART2 (PC10, PC11), and based on the descriptions in the above two tables, the specific configuration is as follows:
/* the USART port and pins definition */
#define USART1_PORT					GPIOA
#define USART1_TX_PIN				GPIO_PIN_2
#define USART1_RX_PIN				GPIO_PIN_3
#define USART2_PORT					GPIOC
#define USART2_TX_PIN				GPIO_PIN_10
#define USART2_RX_PIN				GPIO_PIN_11

void GPIO_Init(void)
{
    
    
	/* 使用SW下载,不使用JTAG下载,管脚用作其它功能 */
	gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
	
	/* demo board Key1 I/O */
	gpio_init(KEY1_PORT,GPIO_MODE_IPU,GPIO_OSPEED_50MHZ,KEY1_PIN);
	
	/* demo board LED I/O */
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
	gpio_bit_reset(GPIOB,GPIO_PIN_4);
	
	/* demo board OLED I/O*/
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
	gpio_bit_write(GPIOB, GPIO_PIN_12, 0);
	gpio_bit_write(GPIOB, GPIO_PIN_13, 0);	
	
	/* demo board ADC I/O */
	gpio_init(ADC0_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC0_CH4 | ADC0_CH5 | ADC0_CH6);
	gpio_init(ADC2_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC2_CH12 | ADC2_CH13);  
	
	/* demo board USARTx I/O */
	gpio_init(USART1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,USART1_TX_PIN);
	gpio_init(USART1_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,USART1_RX_PIN);
	
	gpio_pin_remap_config(GPIO_USART2_PARTIAL_REMAP, ENABLE);
	gpio_init(USART2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,USART2_TX_PIN);
	gpio_init(USART2_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,USART2_RX_PIN);
}
  • 3. Then configure DMA

  • USAR1 and USART2 both use DMA for transmission . If the reception is an idle interrupt, use DMA , otherwise only use the receive interrupt . The query method is not listed here, because in projects actually developed with 32, the main function query method is rarely used to wait for receiving or sending data. This method only appears in demo instances and is not very practical.

  • Neither sending nor receiving here needs to be configured in DMA loop mode .

  • First of all, sending cannot be looped. This is easy to understand. The data sent each time and when to send it are decided by ourselves. We use it just to send multiple bytes with one send command.

  • It doesn't matter much whether the reception is looped or not. Because we are using the receive idle interrupt, the microcontroller peripheral detects the idle state on the RX pin within a frame time, and this bit is set to 1. This means that when a set of data transmission is completed, an accept idle interrupt will be generated. In this interrupt, we will reset the DMA channel, and it will be reset regardless of whether the loop is enabled, so the loop can be closed by default.

  • After the DMA channel corresponding to TX is initialized, it is closed first, and we then open it when data is to be sent.

/* USARTx Receiving Mode */
#define USARTx_RX_BY_IDLE_DMA	1		//1:使用接收空闲中断+DMA   0:仅使用接收中断

/* USARTx ADrawbuffer definition */
#define USART1_TX_SIZE	8
#define USART1_RX_SIZE	8
#define USART2_TX_SIZE	8
#define USART2_RX_SIZE	8

uint8_t USART1TX_Buffer[USART1_TX_SIZE] = {
    
    0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
uint8_t USART1RX_Buffer[USART1_RX_SIZE] = {
    
    0};
uint8_t USART2TX_Buffer[USART2_TX_SIZE] = {
    
    0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18};
uint8_t USART2RX_Buffer[USART2_RX_SIZE] = {
    
    0};

void DMA_Init(void)
{
    
    
	dma_parameter_struct dma_init_Usart1_TX;
	dma_parameter_struct dma_init_Usart1_RX;
	dma_parameter_struct dma_init_Usart2_TX;
	dma_parameter_struct dma_init_Usart2_RX;

	dma_deinit(DMA0, DMA_CH1);
	dma_deinit(DMA0, DMA_CH2);
	dma_deinit(DMA0, DMA_CH5);
	dma_deinit(DMA0, DMA_CH6);

	/* initialize DMA0 channel1(Usart2_TX) */					
	dma_init_Usart2_TX.direction = DMA_MEMORY_TO_PERIPHERAL;
	dma_init_Usart2_TX.memory_addr = (uint32_t)USART2TX_Buffer;
	dma_init_Usart2_TX.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_Usart2_TX.memory_width = DMA_MEMORY_WIDTH_8BIT;
	dma_init_Usart2_TX.number = (uint32_t)USART2_TX_SIZE;
	dma_init_Usart2_TX.periph_addr = (uint32_t)(&USART_DATA(USART2));
	dma_init_Usart2_TX.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_Usart2_TX.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	dma_init_Usart2_TX.priority = DMA_PRIORITY_LOW;
	dma_init(DMA0, DMA_CH1, &dma_init_Usart2_TX);
	dma_circulation_disable(DMA0, DMA_CH1);
	
	/* initialize DMA0 channel2(Usart2_RX) */					
	dma_init_Usart2_RX.direction = DMA_PERIPHERAL_TO_MEMORY;
	dma_init_Usart2_RX.memory_addr = (uint32_t)USART2RX_Buffer;
	dma_init_Usart2_RX.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_Usart2_RX.memory_width = DMA_MEMORY_WIDTH_8BIT;
	dma_init_Usart2_RX.number = (uint32_t)USART2_RX_SIZE;
	dma_init_Usart2_RX.periph_addr = (uint32_t)(&USART_DATA(USART2));
	dma_init_Usart2_RX.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_Usart2_RX.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	dma_init_Usart2_RX.priority = DMA_PRIORITY_LOW;
	dma_init(DMA0, DMA_CH2, &dma_init_Usart2_RX);	
	dma_circulation_disable(DMA0, DMA_CH2);						
	
	/* initialize DMA0 channel6(Usart1_TX) */					
	dma_init_Usart1_TX.direction = DMA_MEMORY_TO_PERIPHERAL;
	dma_init_Usart1_TX.memory_addr = (uint32_t)USART1TX_Buffer;
	dma_init_Usart1_TX.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_Usart1_TX.memory_width = DMA_MEMORY_WIDTH_8BIT;
	dma_init_Usart1_TX.number = (uint32_t)USART1_TX_SIZE;
	dma_init_Usart1_TX.periph_addr = (uint32_t)(&USART_DATA(USART1));
	dma_init_Usart1_TX.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_Usart1_TX.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	dma_init_Usart1_TX.priority = DMA_PRIORITY_LOW;
	dma_init(DMA0, DMA_CH6, &dma_init_Usart1_TX);	
	dma_circulation_disable(DMA0, DMA_CH6);
	
	/* initialize DMA0 channel5(Usart1_RX) */					
	dma_init_Usart1_RX.direction = DMA_PERIPHERAL_TO_MEMORY;
	dma_init_Usart1_RX.memory_addr = (uint32_t)USART1RX_Buffer;
	dma_init_Usart1_RX.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_Usart1_RX.memory_width = DMA_MEMORY_WIDTH_8BIT;
	dma_init_Usart1_RX.number = (uint32_t)USART1_RX_SIZE;
	dma_init_Usart1_RX.periph_addr = (uint32_t)(&USART_DATA(USART1));
	dma_init_Usart1_RX.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_Usart1_RX.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	dma_init_Usart1_RX.priority = DMA_PRIORITY_LOW;
	dma_init(DMA0, DMA_CH5, &dma_init_Usart1_RX);	
	dma_circulation_disable(DMA0, DMA_CH5);					//circulate or not makes no difference
	
	dma_memory_to_memory_disable(DMA0,DMA_CH1);
	dma_memory_to_memory_disable(DMA0,DMA_CH2);
	dma_memory_to_memory_disable(DMA0,DMA_CH5);
	dma_memory_to_memory_disable(DMA0,DMA_CH6);
	
	/* enable all DMA channels you need */
	dma_channel_disable(DMA0,DMA_CH1);
	dma_channel_enable(DMA0,DMA_CH2);
	dma_channel_enable(DMA0,DMA_CH5);
	dma_channel_disable(DMA0,DMA_CH6);
}
  • 4. Configure USART1 and UASRT2 again

  • The baud rate is 38400, 8 data bits, 1 stop bit, no parity bit, no hardware flow control, receive/transmit enabled, and allow sending using the DMA function. Whether to use the DMA function for reception is determined by the USARTx_RX_BY_IDLE_DMA macro.

void USARTx_Init(void)
{
    
    
	/* USART1 configure */
	usart_deinit(USART1);
	usart_baudrate_set(USART1, 38400U);
	usart_word_length_set(USART1, USART_WL_8BIT);
	usart_stop_bit_set(USART1, USART_STB_1BIT);
	usart_parity_config(USART1, USART_PM_NONE);
	usart_hardware_flow_rts_config(USART1, USART_RTS_DISABLE);
	usart_hardware_flow_cts_config(USART1, USART_CTS_DISABLE);
	usart_receive_config(USART1, USART_RECEIVE_ENABLE);
	usart_transmit_config(USART1, USART_TRANSMIT_ENABLE);
	usart_enable(USART1);
	
	/* USART2 configure */
	usart_deinit(USART2);
	usart_baudrate_set(USART2, 38400U);
	usart_word_length_set(USART2, USART_WL_8BIT);
	usart_stop_bit_set(USART2, USART_STB_1BIT);
	usart_parity_config(USART2, USART_PM_NONE);
	usart_hardware_flow_rts_config(USART2, USART_RTS_DISABLE);
	usart_hardware_flow_cts_config(USART2, USART_CTS_DISABLE);
	usart_receive_config(USART2, USART_RECEIVE_ENABLE);
	usart_transmit_config(USART2, USART_TRANSMIT_ENABLE);
	usart_enable(USART2);
	
	/* config USARTx_TX transmit by DMA */
	usart_dma_transmit_config(USART1,USART_DENT_ENABLE);
	usart_dma_transmit_config(USART2,USART_DENT_ENABLE);

#if USARTx_RX_BY_IDLE_DMA
	usart_dma_receive_config(USART1,USART_DENR_ENABLE);
	usart_dma_receive_config(USART2,USART_DENR_ENABLE);	
#endif

}
  • 5. Interrupt settings

  • Here you choose which reception interrupt to enable based on the reception mode.

void Nvic_init(void)
{
    
    
	/* TIMER1 IRQ set */
	nvic_irq_enable(TIMER1_IRQn, 1, 0);
	timer_interrupt_enable(TIMER1, TIMER_INT_UP);
	
	/* USART1/2 IRQ set */
	nvic_irq_enable(USART1_IRQn, 1, 0);
	nvic_irq_enable(USART2_IRQn, 1, 0);
#if USARTx_RX_BY_IDLE_DMA	
	usart_interrupt_enable(USART1, USART_INT_FLAG_IDLE);
	usart_interrupt_enable(USART2, USART_INT_FLAG_IDLE);
#else	
	usart_interrupt_enable(USART1, USART_INT_RBNE);			
	usart_interrupt_enable(USART2, USART_INT_RBNE);			
#endif

}
  • 6. Interrupt service function

  • One each for USART1 and USART2. We only need to look at one of them, and the other one is the same.

  • If the USARTx_RX_BY_IDLE_DMA macro is true , it chooses to use the receive idle interrupt + DMA method to receive data . We clear this flag bit by reading the corresponding data register (the clearing method described in the manual), and then reset it to the corresponding DMA receiving channel. When the program runs to this point, the DMA channel has acquired one or more bytes of data (depending on how many bytes of data you have sent). Closing and reopening will make the DMA memory buffer start from scratch again (such as the one we defined The USART1RX_Buffer array is the memory buff of USART1_RX. After receiving 3 bytes of data, it moves from USART1RX_Buffer[0] to USART1RX_Buffer[3]. After resetting DMA, it will start from USART1RX_Buffer[0]). In this way, as long as the maximum number of bytes is not overwritten and the flag is set to 1, the data of variable length can be received perfectly and the program will be interrupted the least number of times.

  • If the USARTx_RX_BY_IDLE_DMA macro is false , only the receive interrupt is selected . Then every time a byte of data is received, an interrupt will be generated, and the content of each byte will be moved from the device data register to our own defined memory variable.

void USART1_IRQHandler(void)
{
    
    
#if USARTx_RX_BY_IDLE_DMA	
	if(RESET != usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE))
	{
    
    
		Module.USART1_RX_OK=1;		
		/* clear USART_INT_FLAG_IDLE */
		usart_data_receive(USART1);
		/* disable USART1_RX DMA_Channel */
		dma_channel_disable(DMA0, DMA_CH5);    
		/* reset DMA_Channel CNT */
		dma_transfer_number_config(DMA0, DMA_CH5, USART1_RX_SIZE);
		/* enable USART1_RX DMA_Channel */
		dma_channel_enable(DMA0, DMA_CH5);
	}
#else	
	if(RESET != usart_interrupt_flag_get(USART1, USART_INT_FLAG_RBNE))
	{
    
    
		usart_interrupt_flag_clear(USART1,USART_INT_FLAG_RBNE);
		Module.USART1_RX_OK=1;
		USART1RX_Buffer[0] = usart_data_receive(USART1);
	}
#endif	
}

void USART2_IRQHandler(void)
{
    
    
#if USARTx_RX_BY_IDLE_DMA	
	if(RESET != usart_interrupt_flag_get(USART2, USART_INT_FLAG_IDLE))
	{
    
    
		Module.USART2_RX_OK=1;		
		/* clear USART_INT_FLAG_IDLE */
		usart_data_receive(USART2);
		/* disable USART1_RX DMA_Channel */
		dma_channel_disable(DMA0, DMA_CH2);    
		/* reset DMA_Channel CNT */
		dma_transfer_number_config(DMA0, DMA_CH2, USART2_RX_SIZE);
		/* enable USART1_RX DMA_Channel */
		dma_channel_enable(DMA0, DMA_CH2);
	}
#else	
	if(RESET != usart_interrupt_flag_get(USART2, USART_INT_FLAG_RBNE))
	{
    
    
		usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RBNE);
		Module.USART2_RX_OK=1;
		USART2RX_Buffer[0] = usart_data_receive(USART2);
	}
#endif	
}
  • 7. Send DMA

  • After configuring the DMA channel to USARTx_TX, once the dma_channel_enable() function is enabled, it means that the transmission starts. We only need to configure the data to be sent before sending to use the DMA sending function.

void Usartx_TX_DMA_Send(uint32_t usart_periph,uint8_t* data_buffer,uint8_t length)
{
    
    
	if(usart_periph==USART1)
	{
    
    
		/* Channel disable */
		dma_channel_disable(DMA0, DMA_CH6);
		
		dma_memory_address_config(DMA0, DMA_CH6,(uint32_t)data_buffer);
		dma_transfer_number_config(DMA0,DMA_CH6,length);
		
		/* enable DMA channel to start send */
		dma_channel_enable(DMA0, DMA_CH6);
	}
	else if(usart_periph==USART2)
	{
    
    
		/* Channel disable */
		dma_channel_disable(DMA0, DMA_CH1);
		
		dma_memory_address_config(DMA0, DMA_CH1,(uint32_t)data_buffer);
		dma_transfer_number_config(DMA0,DMA_CH1,length);
		
		/* enable DMA channel to start send */
		dma_channel_enable(DMA0, DMA_CH1);	
	}
}

Main function part

  • Module.OLED_REFRESH is refreshed once per second, that is, DMA transfers once per second.
  • I use OLED to display the received data.
int main(void)
{
    
    
	/* Reset of all peripherals, Initializes the Systick. */
	SystemTick_Init();
	/* Initializes all peripherals clock you need */
	SystemClock_Reconfig();		
	/* Initialize all configured peripherals */

	GPIO_Init();
	DMA_Init();
	OLED_Init();			       
	ADCx_Init();
	USARTx_Init();
	Nvic_init();	
	/* trigger start ADCx_channels conversion */
	adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
	adc_software_trigger_enable(ADC2, ADC_REGULAR_CHANNEL);
		
		
	while(1)
	{
    
    	
		if(Module.LED_REFRESH)
		{
    
    
				gpio_bit_set(GPIOB,GPIO_PIN_4);					
		}
		else
		{
    
    
				gpio_bit_reset(GPIOB,GPIO_PIN_4);
		}		
		if(Module.OLED_REFRESH)
		{
    
    
			Module.OLED_REFRESH = 0;
//			Usartx_TX_DMA_Send(USART1,USART1TX_Buffer,(uint8_t)USART1_TX_SIZE);
//			Usartx_TX_DMA_Send(USART1,(uint8_t*)"hello,C world!\n",15);
//			Usartx_TX_DMA_Send(USART2,(uint8_t*)"hello,C world!\n",15);
			Usartx_TX_DMA_Send(USART2,USART2TX_Buffer,(uint8_t)USART2_TX_SIZE);
			OLED_ShowNum(24,0,USART1RX_Buffer[0],5,16);
			OLED_ShowNum(24,2,USART1RX_Buffer[1],5,16);
			OLED_ShowNum(24,4,USART1RX_Buffer[2],5,16);
		
			OLED_ShowNum(24,6,USART2RX_Buffer[1],5,16);//USART1RX_Buffer[3]   memory_r[4]
		
			OLED_ShowNum(96,0,ADC0_Buffer[1][0],4,16);
			OLED_ShowNum(96,2,ADC2_Buffer[0][0],4,16);
			OLED_ShowNum(96,4,USART2RX_Buffer[0],4,16);
			OLED_ShowNum(96,6,USART2RX_Buffer[2],4,16);	
			
		}
	}
}
  • The display effect is as follows:
  • 使用Usartx_TX_DMA_Send(USART1,USART1TX_Buffer,(uint8_t)USART1_TX_SIZE);
    Please add image description
  • 使用Usartx_TX_DMA_Send(USART1,(uint8_t*)“hello,C world!\n”,15);
    Please add image description

Summarize

  • Needless to say send it here. The two receiving methods depend on the actual situation. Generally, it is recommended to use the IDLE+DMA method. In the case of particularly frequent data, it would be more appropriate to choose to use only receive interrupts.

!!!This article was originally published by Huanxi 6666 on CSDN. Please indicate the source when copying or reprinting:) !!!

Guess you like

Origin blog.csdn.net/qq_37554315/article/details/119762343
Recommended