一、 定义串口收发数据结构体
/*COM Received Data Structure*/
typedef struct
{
uint8_t ubr_EndFlag; //Received data end flag
uint8_t ubr_buffer[300]; //Received data buffer
uint8_t ubr_bufferTemp[300]; //Received data buffer temp
uint16_t ubr_index; //Received data index
uint16_t ubr_len; //Received data len
}ComRevData;
判断ubr_EndFlag标志位就可以得知数据是否接收完成,ubr_bufferTemp这个的作用在于如果接收数据失败就不会把数据存放在buffer数组里面
/*COM Send Data Structure*/
typedef struct
{
uint8_t ubs_Index; //send index
uint8_t ubs_Len; //send len
uint8_t ubs_Buffer[255]; //Send data buffer
}ComSendData;
二、串口发送的两种方式
第一种是重写fputc函数,使用printf函数打印发送。
第二种是在中断中发送数据
usart_interrupt_enable(USART0, USART_INT_TBE);//使能发送中断即可发送数据
void USART0_IRQHandler(void)
{
if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_TBE))
{
usart_interrupt_flag_clear(USART0, USART_INT_FLAG_TBE);
usart_data_transmit(USART0, Usart0SendData.ubs_Buffer[Usart0SendData.ubs_Index++]);
if(Usart0SendData.ubs_Index >= Usart0SendData.ubs_Len)//发送数据完成
{
Usart0SendData.ubs_Index = 0;
usart_interrupt_disable(USART0, USART_INT_TBE);
}
}
}
使用中断函数发送数据的好处?
一般我们采用死等发送数据,此时MCU除了发送字节和while等待外没有处理其他任务,严重影响系统实时性。
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); //等待上一字节发送完成
USART1->DR=txbuf[cnt]; //要发送的字节存入串口数据寄存器
而如果采用中断发送函数,他使用的时间是分散的,在等待数据发送完成的期间MCU可以处理其他任务,系统的实时性高
三、串口常见几种中断
USART的各种中断事件被连接到同一个中断向量(见下图),有以下各种中断事件:
● 发送期间:发送完成、清除发送、发送数据寄存器空。
● 接收期间:空闲总线检测、溢出错误、接收数据寄存器非空、校验错误、LIN断开符号检
测、噪音标志(仅在多缓冲器通信)和帧错误(仅在多缓冲器通信)。
空闲中断:当一帧数据接收完成,也就是接收中断触发,在下一个周期里面接收中断没有被触发,那么空闲中断被触发。即空闲中断触发条件是:使能控件中断,接收寄存器不为空被触发
四、STM32CubeMX 配置串口通信 HAL库
4.1 STM32CubeMX 配置串口
每个外设生成独立的 ’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
4.2 重写fputc函数
#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__*/
/******************************************************************
*@brief Retargets the C library printf function to the USART.
*@param None
*@retval None
******************************************************************/
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1,0xFFFF);
return ch;
}
4.3 配置接收中断函数
#define RXBUFFERSIZE 1 /* 缓存大小 */
uint8_t g_usart_rx_buf[200]; //接收缓冲,最大200个字节
uint16_t g_usart_rx_len; //接收长度
uint8_t g_usart_rx_flag=0; //接收完成标志
uint8_t g_rx_buffer[RXBUFFERSIZE]; /* HAL库USART接收Buffer */
在串口初始化函数中使能接收中断
/* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
g_usart_rx_buf[g_usart_rx_len] = g_rx_buffer[0] ;
g_usart_rx_len++;
if(g_rx_buffer[0]==0x0a)
{
g_usart_rx_flag=1;
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}
}
4.4 串口空闲中断接收数据
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//空闲中断
#define USART1_MAX_RECV_LEN 1000 //最大接收缓存字节数
char USART1_RX_BUF[USART1_MAX_RECV_LEN]; //接收缓冲,最大USART3_MAX_RECV_LEN个字节
unsigned short USART1_RX_STA=0;
/* USER CODE BEGIN 1 */
void USART1_IRQHandler(void)
{
uint8_t res = 0;
//接收中断
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) != RESET)
{
HAL_UART_Receive(&huart1,&res,1,1000);
//将数据放入缓冲区
if( (USART1_RX_STA&0x7fff) < USART1_MAX_RECV_LEN)
{
USART1_RX_BUF[USART1_RX_STA] = res;
USART1_RX_STA++;
}
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}
//空闲中断
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)
{
//一帧数据接收完成
//USART1_IdleCallback(USART1_RX_BUF,USART1_RX_STA&0x7fff);
USART1_RX_BUF[ USART1_RX_STA &0x7fff] = 0;
USART1_RX_STA |= 1 << 15;
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
}
}
if(USART1_RX_STA& 0x8000)
{
printf("%s\r\n",USART1_RX_BUF);
USART1_RX_STA=0;
}
五、串口溢出中断如何处理ORE
5.1 出现的问题和现象
当数据接收区或者FIFO区有数据或者满时,又有新数据进来,会导致发生溢出错误,一旦发生溢出错误,RX 移位寄存区虽然能有新数据不断的覆盖,但是数据不会到达RXR或FIFO(现象是:RXNE在ORE置位时不会被置位),导致程序中不能读到新的数据。只有通过ICR清除ORE才能使得RXNE在接收到新数据时置位。或者增大接收数组的容量,一般采用前者解决。
5.2 什么是ORE中断?为什么会产生?
产生原因如上所述。
ORE标志位在USART_SR寄存器,但值得注意的是
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);使能了接收中断,那么ORE中断也同时被开启了。
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);使能了接收中断,那么ORE中断也同时被开启了。
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);使能了接收中断,那么ORE中断也同时被开启了。
5.3 解决办法
stm32
if(USART_GetFlagStatus(USART1,USART_FLAG_ORE)== SET)//程序中断过多,主机的发送速度又快,很容易会造成溢出错误
{
USART_ClearFlag(USART1, USART_FLAG_ORE); //清除溢出中断
USART_ReceiveData(USART1);//必须要读,不然溢出中断清除不了
}
兆易创新
/*Erro flag*/
if(RESET != usart_interrupt_flag_get(UART7, USART_INT_FLAG_RBNE_ORERR))
{
usart_data_receive(UART7);
usart_interrupt_flag_clear(UART7,USART_INT_FLAG_RBNE_ORERR);
}
if(RESET != usart_interrupt_flag_get(UART7, USART_INT_FLAG_ERR_NERR))
{
usart_data_receive(UART7);
usart_interrupt_flag_clear(UART7,USART_INT_FLAG_ERR_NERR);
}
if(RESET != usart_interrupt_flag_get(UART7, USART_INT_FLAG_ERR_FERR))
{
usart_data_receive(UART7);
usart_interrupt_flag_clear(UART7,USART_INT_FLAG_ERR_FERR);
}
六、RS485
485(一般称作 RS485/EIA-485)是隶属于 OSI 模型物理层的电气特性规定为 2 线,半双工,多点通信的标准。它的电气特性和 RS-232 大不一样。用缆线两端的电压差值来表示传递信号。RS485 仅仅规定了接受端和发送端的电气特性。它没有规定或推荐任何数据协议。
RS485 的特点包括:
1) 接口电平低,不易损坏芯片。RS485 的电气特性:逻辑“1”以两线间的电压差为+(2~6)V表示;逻辑“0”以两线间的电压差为-(2~6)V 表示。接口信号电平比 RS232 降低了,不易损坏接口电路的芯片,且该电平与 TTL 电平兼容,可方便与 TTL 电路连接。
2) 传输速率高。10 米时,RS485 的数据最高传输速率可达 35Mbps,在 1200m 时,传输速度可达 100Kbps。
3) 抗干扰能力强。RS485 接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
4) 传输距离远,支持节点多。RS485 总线最长可以传输 1200m 以上(速率≤100Kbps)一般最大支持 32 个节点,如果使用特制的 485 芯片,可以达到 128 个或者 256 个节点,最大的可以支持到 400 个节点。
七、RS485电气特性
差分信号逻辑1(正)电压为+2~+6V,而逻辑0(负)电压为-2~-6V.接口信号电平比RS-232-C降低了,就不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL电路连接。
SP3485 作为收发器,该芯片支持 3.3V 供电,最大传输速度可达10Mbps,支持多达 32 个节点,并且有输出短路保护。图中 A、B 总线接口,用于连接 485 总线。RO 是接收输出端,DI 是发送数据收入端,RE是接收使能信号(低电平有效),DE 是发送使能信号(高电平有效)。可以将RE和DE用同一个线连接,然后控制该脚的电平信号来确定是发送还是接收模式
八、RS485切换模式需要时间
因为RS485通信是采用半双工通信,有一个引脚作用是使能接收还是发送,但是MCU切换引脚电平需要一定的时间,在这段时间里面MCU的引脚是高阻态。
RS485芯片从接收模式切换到发送模式需要经过3.5us才有驱动能力输出