GD32F103调试小记(二)之USART(接收中断、接收空闲中断+DMA、发送DMA)

前言

上篇文章摸完了GD32F103调试小记(一)之ADC+DMA,接下来摸下GD32的USART。

DMA

数据的搬运工,CPU的好助手。

USART

一种串行通信协议,说白了就是让两根线按照一定的规律去切换高低电平。根据一个单位时间内高低电平的持续时间,我们去判定事起始信号还是数据0或1还是奇偶校验位等等。其实不管是单总线(1-wire)、两根线(USART、IIC)、甚至多跟线(SPI3线或4线、Modbus)都是以此为基础,再根据不同的定义形成自己的协议。具体的协议规定可以查看网上的相关资料,这里不多做介绍。所以大家也要明白,这些协议从理论上来说都是可以通过几个IO口自己软件模拟的,但这会耗费CPU的资源。因此不少单片机厂商都会有支持这些协议的硬件模块,我们只需要简单的配置几个寄存器,就能使用这些协议沟通上外围设备,完成我们想要的产品功能。今天所用到的也是GD32的硬件USART。

各模块程序编写

我用的是GD32F103RC(注:请确保自己有一个可以编译的GD32F103工程且里面已经包含了标准库)。

  • 1、首先先配置各硬件外设的时钟(DMA时钟,GPIO时钟,USART时钟,AF时钟)。

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);									
}

请添加图片描述请添加图片描述

  • 2、接着配置咱们的GPIO。

  • 我是用的是USART1(PA2、PA3)、USART2(PC10、PC11),再根据上面两表的描述,具体配置如下:
/* 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、接着配置DMA

  • USAR1、USART2发送都使用DMA。接收如果是空闲中断的话使用DMA,否则仅使用接收中断。这里不列出查询法,因为在真正用32开发的项目中很少使用主函数查询法去等待接收或者发送数据,这种方法仅在demo实例中出现,实用性不高。

  • 这里发送与接收都不需要配置成DMA循环模式

  • 首先发送不能循环,这很好理解,每次发的数据和什么时候发由我们自己决定,我们使用它只是为了使用一次发送命令发送多个字节。

  • 接收是否循环并没有多大关系。因为我们使用的是接收空闲中断,单片机外设在一个帧时间内,RX引脚检测到空闲状态,该位置1。也就意味着当一组数据传输完成后,才会产生一次接受空闲中断。在该中断里我们会重新置为DMA通道,无论是否开启循环都会被重新置位,所以这里默认关闭循环即可。

  • 对应TX的DMA通道初始化完后先关闭掉,等到要发数据时我们再开启它。

/* 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、再配置USART1、UASRT2

  • 波特率38400,数据位8位,停止位1位,无校验位,不使用硬件流控制,接收/发送使能,允许发送使用DMA功能。接收是否使用DMA功能由USARTx_RX_BY_IDLE_DMA这个宏决定。

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、中断设置

  • 这里根据接收方式选择开哪种接收中断

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、中断服务函数

  • USART1与USART2各一个,我们只用看其中一个即可,另一个与之相同。

  • 如果USARTx_RX_BY_IDLE_DMA这个宏为,即选择使用接收空闲中断+DMA的方式接收数据。我们通过读相应的数据寄存器去清这个标志位(手册描述的清位方式),再重新置为对应的DMA接收通道。程序运行到此处时,DMA通道已经获取了一个或多个字节的数据(看你发了多少字节的数据),关闭再开启则是让DMA的内存buffer再次从头开始(比如我们定义的USART1RX_Buffer数组是USART1_RX的内存buff,在接受了3个字节的数据后,从USART1RX_Buffer[0]移到了USART1RX_Buffer[3],复位DMA后又将从USART1RX_Buffer[0]开始)。这样只要保证最大字节数不被覆盖,在有标志位置1后及时处理,就能完美接收不定长的数据,且程序被中断的次数最少。

  • 如果USARTx_RX_BY_IDLE_DMA这个宏为,即选择仅使用接收中断。那么每接收到一个字节的数据就会产生一次中断,再将每个字节的内容从设备数据寄存器搬运到我们自己定义的内存变量里。

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、发送DMA

  • 把DMA通道配置成USARTx_TX后,一旦使能dma_channel_enable()函数,就意味着发送开始,我们只要在发送前配置好要发送的数据即可使用DMA发送功能。

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);	
	}
}

主函数部分

  • Module.OLED_REFRESH是每秒刷新一次,即每秒DMA传送一次。
  • 我使用OLED显示接收到的数据。
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);	
			
		}
	}
}
  • 显示效果如下:
  • 使用Usartx_TX_DMA_Send(USART1,USART1TX_Buffer,(uint8_t)USART1_TX_SIZE);
    请添加图片描述
  • 使用Usartx_TX_DMA_Send(USART1,(uint8_t*)“hello,C world!\n”,15);
    请添加图片描述

总结

  • 发送这里不用多说。接收的两种方式看实际情况,一般情况下推荐使用IDLE+DMA的方式。而在数据特别频繁的情况下,选择仅使用接收中断会更合适。

!!!本文为欢喜6666在CSDN原创发布,复制或转载请注明出处 :)!!!

猜你喜欢

转载自blog.csdn.net/qq_37554315/article/details/119762343