STM32外设地图--UART

一、UART(Universal Asynchronous Receiver Transmitter,通用异步收发器)

1、UART发送 1Byte数据
  软件或DMA控制器 把1Byte数据写入UART的发送数据寄存器TDR后,UART清零TXE标志和TC标志,并自动将数据从发送数据寄存器TDR转移到发送移位寄存器TSR,转移完成后置位TXE标志并触发UART中断请求(TXE中断使能时)、DMA请求(UART的DMA发送模式使能时),同时UART发送器根据UART协议,将发送移位寄存器TSR的数据发送到TX引脚,发送完成后,若TXE标志为1,则置位TC标志并触发UART中断请求(TC中断使能时)。

2、UART接收 1Byte数据
  UART接收器 根据UART协议从RX引脚上收到1Byte数据后,将数据保存到接收数据寄存器RDR,然后置位RXNE标志并触发UART中断请求(RXNE中断使能时)、DMA请求(UART的DMA接收模式使能时),后面若在RX引脚上检测到一个空闲帧,则置位IDLE标志并触发UART中断请求(IDLE中断使能时)。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、程序开发流程

1、发送多字节数据
① 轮询发送
  先把1Byte数据写入发送数据寄存器TDR,然后通过TC位判断数据是否已发完,若没发完则持续等待它发完, 等发完了再写下一个字节到发送数据寄存器TDR, 如此循环,直到所有字节全部发完。

/* 函数功能:用UART1发送多字节数据. 此接口供外部模块调用
   返回值:实际发送的字节数
*/
int uart1_write_string(uint8_t *buff,  int len)
{
	ASSERT(buff != NULL && len > 0);
	for(int i = 0; i < len; i++)
	{
		int block_timeout = 2000;	//阻塞超时时间
		
		UART1_RESET_TC_BIT();		//软件对TC bit写0,清零TC bit
		uart1->TDR = buff[i];		//写1Byte数据到UART1的寄存器TDR, UART1将自动发送
		while(uart1的TC位为0 && block_timeout--); //等待数据发完(数据从移位寄存器TSR发送到引脚TX), 并带超时判断
		if(block_timeout == 0)
			return i;	// 数据在超时时间内没发完, 则发送失败, 返回已发送的字节数
	}
	
	return len; //返回已发送的字节数
} 

void uart1_init(void)
{
	初始化uart1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
}  

② TXE中断发送
  先把要发送的多字节数据缓存到一个队列中,然后使能UART的TXE中断,当发送数据寄存器TDR为空时会置位TXE标志并触发UART中断请求,在中断函数中,每次取出缓存队列中的一字节数据并发出去(发出去就是把数据写入发送数据寄存器TDR,注意此操作会导致TXE标志清零),当发出最后1个字节后(注意是发出,此时发送控制器正在把发送移位寄存器TSR的数据发送到TX引脚),禁能TXE中断。此方式的缺点是,不能确定最后一个字节何时发完。

queue_t  g_tx_queue;	//发送队列,用于缓存待发送的数据

/* 函数功能:用UART1发送多字节数据. 此接口供外部模块调用
   返回值:实际发送的字节数
*/
int uart1_write_string(uint8_t *buff,  int len)
{
	ASSERT(buff != NULL && len > 0);
	interrupt_disable();
	int ok_len = queue_write(&g_tx_queue, buff, len);	//把要发送的多字节数据,缓存到发送队列g_tx_queue
	interrupt_enable();
	UART1_INT_TXE_ENABLE();		//使能UART1的TXE中断
	
	return ok_len;				//返回成功写入队列的字节数
}

void uart1_irq_handler(void)
{
	uint8_t data;

	if(TXE bit置1, 且TXE中断已使能)
    {
    	if(queue_read(&g_tx_queue, &data) == FALSE)	//发送队列没有数据, 说明最后一字节已发出
    	{
    		UART1_INT_TXE_DISABLE();	//禁能UART1的TXE中断
    		return;
    	}
    
    	uart1->TDR = data;				//将取出的1Byte数据, 写到UART1的寄存器TDR, UART1将自动发送
    }
}

void uart1_init(void)
{
	初始化uart1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
	在NVIC中,设置UART1中断优先级,并使能UART1中断;
}            

③ TC中断发送
  在初始化时先使能UART的TC中断。先发出第一个字节,然后把剩余字节缓存到一个队列中,UART每发完一字节数据后会置位TC标志并触发UART中断请求,在中断函数中,每次取出缓存队列中的一字节数据并发出去(发出去就是把数据写入发送数据寄存器TDR,注意此操作会导致TC标志清零),当最后1个字节已发完则向TC bit写0以清零TC标志。此方式的缺点是,每次都要等发送数据寄存器TDR和发送移位寄存器TSR都为空的时候,才能发送下一字节,发送效率低。

queue_t  g_tx_queue;	//发送队列,用于缓存待发送的数据

/* 函数功能:用UART1发送多字节数据. 此接口供外部模块调用
   返回值:实际发送的字节数
*/
int uart1_write_string(uint8_t *buff,  int len)
{
	int ok_len = 0;
	ASSERT(buff != NULL && len > 0);
	interrupt_disable();
	if(TXE bit为0) //寄存器TDR非空, 说明uart1正在发数据
	{
		ok_len = queue_write(&g_tx_queue, &buff[0], len);	//把要发送的多字节数据,缓存到发送队列g_tx_queue
	}
	else	      //寄存器TDR为空, 则发出第1字节, 以便触发TC中断, 剩余字节缓存到队列中, 由TC中断负责发送
	{
		uart1->TDR = buff[0];	//将第1字节数据写到寄存器TDR, UART1将自动发送
		ok_len = queue_write(&g_tx_queue, &buff[1], len - 1);	//剩余字节缓存到发送队列g_tx_queue
		ok_len++;
	} 
	interrupt_enable();
	
	return ok_len;	//返回实际发送的字节数
}

void uart1_irq_handler(void)
{
	uint8_t data;
	
	if(TC bit置1, 且TC中断已使能)
    {
    	if(queue_read(&g_tx_queue, &data) == FALSE)	//发送队列没有数据, 说明最后一字节已发完
    	{
    		UART1_RESET_TC_BIT();	//软件对TC bit写0,清零TC bit
    		if(uart1->tx_done_callback)
    	    {
    	    	uart1->tx_done_callback();	//通知任务数据发完了
    	    }
    		return;
    	}
    
    	uart1->TDR = data;	//将取出的1Byte数据, 写到UART1的寄存器TDR, UART1将自动发送
    }
}

void uart1_init(void)
{
	初始化uart1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
	在NVIC中,设置UART1中断优先级,并使能UART1中断;		
	
	使能UART1的TC中断;
}        

④ TXE中断发送、TC中断确定结束
  先把要发送的多字节数据缓存到一个队列中,然后使能UART的TXE中断,当发送数据寄存器TDR为空时会置位TXE标志并触发UART中断请求,在TXE标志触发的UART中断函数中每次取出缓存队列中的一字节数据并发出去(发出去就是把数据写入发送数据寄存器TDR,注意此操作会导致TXE标志清零),当发出最后1个字节后(注意是发出,此时发送控制器正在把发送移位寄存器TSR的数据发送到TX引脚),禁能TXE中断然后使能TC中断当UART把最后1个字节发完后(注意是发完,此时发送控制器已将发送移位寄存器TSR的数据全部发送到了TX引脚),置位TC标志并触发UART中断请求,在TC标志触发的UART中断函数中调用回调函数以通知任务数据发完了,并向TC bit写0以清零TC标志,最后禁能TC中断。这种方式的发送效率高,同时能确定最后一个字节何时发完,是比较推荐的方式。

queue_t  g_tx_queue;	//发送队列,用于缓存待发送的数据

/* 函数功能:用UART1发送多字节数据, 此接口供外部模块调用
   返回值:实际发送的字节数
*/
int uart1_write_string(uint8_t *buff,  int len)
{
	ASSERT(buff != NULL && len > 0);
	interrupt_disable();
	int ok_len = queue_write(&g_tx_queue, buff, len);	//把要发送的多字节数据,缓存到发送队列g_tx_queue
	interrupt_enable();
	UART1_INT_TXE_ENABLE();		//使能UART1的TXE中断
	
	return ok_len;	//返回成功写入队列的字节数
}

void uart1_irq_handler(void)
{
	uint8_t data;

	if(TXE bit置1, 且TXE中断已使能)
    {
    	if(queue_read(&g_tx_queue, &data) == FALSE)	//发送队列没有数据, 说明最后一字节已发出
    	{
    		UART1_INT_TXE_DISABLE();	//禁能UART1的TXE中断
    		UART1_INT_TC_ENABLE();		//使能UART1的TC中断
    		return;
    	}
    
    	uart1->TDR = data;	//将取出的1Byte数据, 写到UART1的寄存器TDR, UART1将自动发送
    }
    else if(TC bit置1, 且TC中断已使能)
    {
    	UART1_RESET_TC_BIT();	//软件对TC bit写0,清零TC bit
    	UART1_INT_TC_DISABLE();	//禁能UART1的TC中断
    	if(uart1->tx_done_callback)
    	{
    		uart1->tx_done_callback();	//通知任务数据发完了
    	}
    }
}

void uart1_init(void)
{
	初始化uart1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
	在NVIC中,设置UART1中断优先级,并使能UART1中断;	
}            

⑤ DMA发送、TC中断确定结束
  把任务A待发送的数据,拷贝到发送环形缓存(超出缓存长度的数据将不拷贝),若UART正在发送,则直接把拷贝的字节数返回给任务A,否则先根据环形缓存状态配置好DMA(配好后DMA会自动发送数据),然后把拷贝的字节数返回任务A。

. 根据环形缓存的当前状态,配置DMA:
DMA为单次模式, 传输方向为memory --> peripheral, 搬运次数为N
peripheral:起始地址为寄存器TDR地址,地址不增加,单数据长度为1字节;
memory: 起始地址为 ADDR_X,地址增加,单数据长度为1字节;
使能DMA channel的传输完成中断(在传输完成中断函数中,使能UART的TC中断);
使能DMA channel;
最后使能UART的DMA发送模式(当寄存器TDR为空时,UART自动向DMA控制器发送DMA请求,DMA收到请求后会搬运数据)。

PS:因为DMA只能搬运线性地址空间的数据,而发送环形缓存中的数据在内存中的地址可能是非线性的,因此得把环形缓存中的数据分成线性的两部分,这样就得让DMA分两次发,第一次在任务A的上下文环境中发,第二次当DMA发完后在TC触发的UART中断函数中发。

举例:假设把任务A待发送的数据拷贝到发送环形缓存后,缓存中有5字节数据(如下图的图A),此时UART没在发数据,则需配置DMA以便启动第一次发送,此时配置DMA时设置搬运次数为2(addr_05 ~ addr_06之间的2个数据),起始地址为 addr_05。当配置好后,发送寄存器TDR为空时UART会向DMA控制器发送DMA请求,DMA开始把数据从环形缓存搬到UART寄存器TDR中,触发UART开始发送。当DMA搬运完最后一个数据(即addr_06对应的数据)后,触发DMA传输完成中断,在其中断函数中,使能UART的TC中断,后面当UART把最后一个数据(即addr_06对应的数据)发完后,触发TC中断,在其中断函数中,禁能TC中断,并更新发送环形缓存的状态(如下图的图B,此时缓存中还有3字节数据),然后重新配置DMA启动第二次发送,此时设置DMA搬运次数为3(addr_00 ~ addr_02之间的3个数据),起始地址为 addr_00。当DMA第二次搬运完最后一个字节后(即addr_02对应的数据),同样的,最终又会进入TC标志触发的UART中断,在其中断函数中,再次更新发送环形缓存的状态(如下图的图C,此时缓存为空),此时发送环形缓存为空,即发送结束。

在这里插入图片描述

queue_t  g_tx_queue;	//发送队列,用于缓存待发送的数据

/* 函数功能:用UART1发送多字节数据. 此接口供外部模块调用
   返回值:实际发送的字节数
*/
int uart1_write_string(uint8_t *buff,  int len)
{
	int ok_len = 0;
	ASSERT(buff != NULL && len > 0);
	
	interrupt_disable();
	ok_len = queue_write(&g_tx_queue, &buff[0], len);	//把要发送的多字节数据,缓存到发送队列g_tx_queue
	interrupt_enable();
	
	if(TXE bit为0) //寄存器TDR非空, 说明uart1正在发数据
		return ok_len;

	根据环形缓存状态配置DMA;	//配好后DMA会自动发送数据
	
	return ok_len;	//返回实际发送的字节数
}

void dma_uart1_tx_channel_irq_handler(void)
{
	if(TCIFx标志置位)	//transfer complete flag
	{
		清零TCIFx标志;
		使能UART1的TC中断;
	}
}

void uart1_irq_handler(void)
{	
	if(TC bit置1, 且TC中断已使能)
    {
    	清零TC标志;
    	禁能UART1的TC中断;
    	更新发送环形缓存的状态;	//read_idx, 缓存剩余数据个数
    	if(环形缓存还有数据)
    	{
    		根据环形缓存状态配置DMA;	//配好后DMA会自动发送数据
    	}
    }
}

void uart1_init(void)
{
	初始化UART1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
	使能UART1的DMA发送模式;
	
	在NVIC中,设置UART1中断优先级,并使能UART1中断;			//TC标志置位时触发UART中断
	在NVIC中,设置UART1 TX对应的DMA channel中断的优先级并使能;	//DMA传输完成时触发中断
}           

2、接收不定长数据
① RXNE中断接收、定时器超时确定帧结束
  在UART初始化时使能UART的RXNE中断,创建一个定时器并设置好超时时间,然后创建一个接收队列用于缓存接收的数据。当UART从RX引脚上接收到1Byte数据后,置位RXNE标志并触发中断,在中断函数中,将寄存器RDR的值读出并缓存到接收队列中(读寄存器RDR会导致RXNE标志清零),然后复位定时器(若定时器未启动则启动定时器)。当定时器超时后,调用回调函数通知任务已接收到一帧数据

queue_t  g_rx_queue;			//接收队列,用于缓存接收的数据
bool g_rx_ok_flag = FALSE;		//接收完成标志
timer_t g_timer;	    		//软件定时器, 用于一帧接收超时判断

/* 函数功能:读UART1接收的一帧数据. 此接口供外部模块调用
   返回值:实际接收的字节数
*/
int uart1_read_string(uint8_t *buff,  int len)
{
	int rcv_len = 0;
	ASSERT(buff != NULL && len > 0);
	
	interrupt_disable();
	if(g_rx_ok_flag == TRUE)	//已接收到一帧数据
	{
		rcv_len = queue_read(&g_rx_queue, buff, len);	//将接收队列的缓存数据, 拷贝到任务指定buff
		g_rx_ok_flag = FALSE;	//任务已读取接收的数据, 可重新接收新数据
	}
	interrupt_enable();
	
	return rcv_len;	//返回实际接收的字节数
}

void uart1_rx_timer_timeout_callback(void)
{
	interrupt_disable();
	g_rx_ok_flag = TRUE;	//设置接收完成标志
	if(uart1->rx_done_callback)
	{
		int rcv_len = queue_len(&g_rx_queue);
		interrupt_enable();
		uart1->rx_done_callback(rcv_len);	//通知任务已接收到一帧数据
	}
	interrupt_enable();
}

void uart1_irq_handler(void)
{
	if(RXNE bit置1, 且RXNE中断已使能)
    {
    	uint8_t data = uart1->RDR;	//从寄存器RDR中读出接收的数据(读RDR导致RXNE bit清零)
    	if(g_rx_ok_flag == TRUE)	//上一帧数据未被任务读取, 则不再接收新数据	
    		return;
    		
    	queue_write(&g_rx_queue, data);	//将接收的数据缓存到接收队列中
    	timer_reset(&g_timer);		    //复位定时器
    }
    else
    {
    	清零各种能触发中断的error标志;
    }
}

void uart1_init(void)
{
	初始化uart1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
	在NVIC中,设置UART1中断优先级,并使能UART1中断;		//在NVIC中, UART1中断是 IRQ#37
	
	使能UART1的RXNE中断;
	timer_set_timeout(&g_timer, TIMEOUT_5_MS);	//设置定时器超时时间
	timer_set_call_back(&g_timer, uart1_rx_timer_timeout_callback);	//设置定时器回调函数
}            

② RXNE中断接收、IDLE中断确定帧结束
  在UART初始化时使能UART的RXNE中断、IDLE中断,并创建一个接收队列用于缓存接收的数据。当UART从RX引脚上接收到1Byte数据后,置位RXNE标志并触发中断,在RXNE标志触发的中断函数中将寄存器RDR的值读出并缓存到接收队列中(读寄存器RDR会导致RXNE标志清零)。当UART在RX引脚检测到一个idle frame时,置位IDLE标志并触发中断,在IDLE标志触发的中断函数中调用回调函数通知任务已接收到一帧数据,并清零IDLE标志。

queue_t  g_rx_queue;			//接收队列,用于缓存接收的数据
bool g_rx_ok_flag = FALSE;		//接收完成标志

/* 函数功能:读UART1接收的一帧数据. 此接口供外部模块调用
   返回值:实际接收的字节数
*/
int uart1_read_string(uint8_t *buff,  int len)
{
	int rcv_len = 0;
	ASSERT(buff != NULL && len > 0);
	
	interrupt_disable();
	if(g_rx_ok_flag == TRUE)	//已接收到一帧数据
	{
		rcv_len = queue_read(&g_rx_queue, buff, len);	//将接收队列的缓存数据, 拷贝到任务指定buff
		g_rx_ok_flag = FALSE;	//任务已读取接收的数据, 可重新接收新数据
	}
	interrupt_enable();
	
	return rcv_len;	//返回实际接收的字节数
}

void uart1_irq_handler(void)
{
	if(RXNE bit置1, 且RXNE中断已使能)
    {
    	uint8_t data = uart1->RDR;	//从寄存器RDR中读出接收的数据(读RDR导致RXNE bit清零)
    	if(g_rx_ok_flag == TRUE)	//上一帧数据未被任务读取, 则不再接收新数据	
    		return;
    		
    	queue_write(&g_rx_queue, data);	//将接收的数据缓存到接收队列中
    }
    else if(IDLE bit置1, 且IDLE中断已使能)
    {
    	uint8_t data = uart1->RDR;	//读寄存器RDR可清零IDLE标志
    	if(g_rx_ok_flag == TRUE)	//上一帧数据未被任务读取, 则不再接收新数据	
    		return;
    	
    	g_rx_ok_flag = TRUE;		//设置接收完成标志
		if(uart1->rx_done_callback)
		{
			int rcv_len = queue_len(&g_rx_queue);
			uart1->rx_done_callback(rcv_len);		//通知任务已接收到一帧数据
		}
    }
    else
    {
    	清零各种能触发中断的error标志;
    }
}

void uart1_init(void)
{
	初始化uart1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
	在NVIC中,设置UART1中断优先级,并使能UART1中断;		//在NVIC中, UART1中断是 IRQ#37
	
	使能UART1的RXNE中断;
	使能UART1的IDLE中断;
}            

③ DMA接收、IDLE中断确定帧结束
Ⅰ、配置DMA:
循环模式,传输方向为 peripheral–> memory,搬运次数为 接收环形缓存总字节长度;
peripheral:起始地址为 寄存器RDR地址,地址不增加,单数据长度为1字节;
memory:起始地址为 接收环形缓存起始地址,地址增加,单数据长度为1字节;
使能DMA channel 的传输过半中断、传输完成中断;
使能DMA channel;
最后使能UART的DMA接收模式。

Ⅱ、使能UART的IDLE中断。

Ⅲ、在DMA传输过半中断、DMA传输完成中断、IDLE中断的中断函数中,计算DMA写入接收环形缓存的数据字节数并更新环形缓存的状态。在IDLE中断的中断函数中,调用接收回调函数把收到的数据长度告诉任务A。

queue_t  g_rx_queue;	//接收队列,用于缓存接收的数据
uint16_t remain_cnt;	//上一次保存的寄存器DMA_CNDTRx的值(DMA剩余搬运次数), 用于计算DMA写入接收缓存的数据个数

/* 函数功能:读UART1接收的一帧数据. 此接口供外部模块调用
   返回值:实际接收的字节数
*/
int uart1_read_string(uint8_t *buff,  int len)
{
	int rcv_len = 0;
	ASSERT(buff != NULL && len > 0);
	
	interrupt_disable();
	rcv_len = queue_read(&g_rx_queue, buff, len);	//将接收队列的缓存数据, 拷贝到任务指定buff
	interrupt_enable();
	
	return rcv_len;	//返回实际接收的字节数
}

void dma_uart1_rx_channel_irq_handler(void)
{
	if(HTIFx标志置位)		//half transfer flag
	{
		清零TCIFx标志;
	}
	else if(TCIFx标志置位)	//transfer complete flag
	{
		清零TCIFx标志;
	}
	else
	{
		清零各种能触发中断的error标志;
		return;
	}

	/* 计算DMA写入环形缓存的数据个数,并更新环形缓存的write_idx */
	uint16_t rcv_len = 0;
	uint16_t counter = __HAL_DMA_GET_COUNTER(&(uart1->dma_rx.handle));
	if (counter <= remain_cnt)
	{
		recv_len = remain_cnt - counter;
	}   
    else
    {
    	recv_len = g_rx_queue.config.rx_bufsz + remain_cnt - counter;
    }
    
    remain_cnt = counter;
    更新g_rx_queue的write_idx(DMA写入的字节长度为recv_len);
}

void uart1_irq_handler(void)
{
    if(IDLE bit置1, 且IDLE中断已使能)
    {
    	清零IDLE标志;
    	
    	/* 计算DMA写入环形缓存的数据个数,并更新环形缓存的write_idx */
    	uint16_t rcv_len = 0;
		uint16_t counter = __HAL_DMA_GET_COUNTER(&(uart1->dma_rx.handle));
		if (counter <= remain_cnt)
		{
			recv_len = remain_cnt - counter;
		}   
    	else
    	{
    		recv_len = g_rx_queue.config.rx_bufsz + remain_cnt - counter;
    	}
    
    	remain_cnt = counter;
    	更新g_rx_queue的write_idx(DMA写入的字节长度为recv_len);
    	
    	/* 通知任务已接收到一帧数据 */
    	if(uart1->rx_done_callback)
		{
			uart1->rx_done_callback(queue_len(&g_rx_queue));	
		}
		return;
    }
    
    清零各种能触发中断的error标志;
}

void uart1_init(void)
{
	初始化UART1(uart1引脚, uart1波特率、数据位个数、奇偶校验、停止位个数, ...);
	使能UART1的DMA接收模式;
	
	配置DMA:循环模式, 传输方向为 peripheral--> memory, 搬运次数为 g_rx_queue.config.rx_bufsz;
              peripheral:起始地址为寄存器RDR地址,地址不增加,单数据长度为1字节;
              memory: 起始地址为 &g_rx_queue.buff[0],地址增加,单数据长度为1字节;
              使能DMA channel的传输过半中断、传输完成中断;//用于准确计算DMA写入接收缓存的数据个数
              使能DMA channel;							//UART1 RX对应的DMA channel
              最后使能UART的DMA接收模式。   				//UART收到1Byte数据后自动向DMA控制器发送DMA请求
              
    remain_cnt = g_rx_queue.config.rx_bufsz;			//remain_cnt初值和寄存器DMA_CNDTRx保持一致
              
    使能UART1的IDLE中断;
    在NVIC中,设置UART1中断优先级,并使能UART1中断;		
    在NVIC中,设置UART1 RX对应的DMA channel中断优先级并使能; //UART中断优先级 < DMA_channel中断优先级
}            

三、注意事项

1、UART接收时,使能RXNE中断后,会同时使能ORE中断。如果在接收数据时,软件没有在1Byte时间内读完数据寄存器DR,ORE标志会置位并触发UART中断,如果此时出现ORE =1、RXNE = 0 的情况,而在中断函数中软件因为RXNE为0没有去读数据寄存器DR,导致ORE标志没能清零,则ORE标志会一直触发中断。
  PS:出现ORE =1、RXNE = 0的场景:软件正在读寄存器SR和DR期间,此时移位寄存器RSR接收完了1Byte数据,则ORE置1(此时因为RXNE标志为1,寄存器RSR的数据不会转移到寄存器DR,此数据最终命运是被后面接收的数据所覆盖),当软件读完寄存器DR后,RXNE = 0。

参考资料
[1] STM32F103xx datasheet.
[2] STM32F10xxx reference manual.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值