一.前言
串口,相信大家已经很熟悉这个名字了。作为嵌入式最常用的通讯手段,无疑是许多人对于MCU通信梦开始的地方。先前我有介绍过一个关于485的文章485、CAN通讯、LCD段码屏 电动车仪表测试盒_整车通信485-CSDN博客,它完全就是基于串口的通信方式,只是电平标准不一样。一位刚毕业的小亲戚今天问我串口怎么用,乘此机会在此结合我做过的商业性案例一起详细的介绍一下。
二.串口介绍
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了单片机系统的硬件实力。
便于对底层的理解,我们只介绍8位的MCU自带的串口(UART)。(Universal Asynchronous Receiver Transmitter,通用异步收发器)。关于UART和USART的区别及介绍,大佬的UART与USART区别 (串口同步通信和异步通信)_uart同步和异步的区别-CSDN博客介绍的很详细,这里不做过多描述。
三.硬件电路
常见的标准TTl :高5V 低0V RS232: -3~~15V表示高 3~15V表示低 RS485:压差2~6V高 压差-2~-6V表示低(485是差分信号,抗干扰能力强)。
四.参数介绍
- 波特率(Baud Rate)
- 定义:波特率是衡量数据传输速率的一个重要指标,它表示单位时间内传输的码元个数。码元是承载信息量的基本信号单位。例如,在二进制通信系统中,一个码元就代表一个二进制位(0 或 1)。波特率的单位是波特(Bd),1 波特意味着每秒传输 1 个码元。
- 影响因素:较高的波特率可以实现更快的数据传输速度,但对通信线路的质量和设备的性能要求也更高。如果波特率过高,而通信线路存在干扰或者接收设备处理速度跟不上,就可能会导致数据丢失或错误。例如,在一个干扰较大的长距离通信线路中,使用过高的波特率可能会使接收端无法正确解析接收到的信号。
- 常见值:常见的波特率有 9600、19200、38400、115200 等。在一些对数据传输速度要求不高,且通信环境较为复杂的情况下,如简单的传感器数据采集,9600 波特率可能就足够了。而在需要快速传输大量数据的场景,如计算机与高速外部设备之间的通信,可能会使用 115200 波特率甚至更高。
- 数据位(Data Bits)
- 定义:数据位用于指定每个字节的数据长度。在串口通信中,字节是数据的基本单位。数据位可以设置为 7 位或 8 位。
- 应用场景:7 位数据位主要用于 ASCII 码通信。ASCII 码是一种字符编码标准,它用 7 位二进制数来表示 128 个字符,包括英文字母、数字、标点符号等。例如,在早期的电传打字机通信中,就广泛使用 7 位 ASCII 码,因为这样可以有效地传输文本信息。而 8 位数据位则可以传输一个完整的字节,它可以用于传输任意二进制数据,包括自定义的协议数据或者非 ASCII 码字符集的数据。
- 停止位(Stop Bits)
- 定义:停止位用于表示一个数据字节传输结束。它是在每个字节的数据位传输完成后发送的,可以是 1 位、1.5 位或 2 位。
- 作用:停止位的主要作用是让接收设备能够正确识别每个字节的结束位置。在异步串口通信中,接收设备需要通过识别停止位来确定一个字节的数据已经完整接收,从而准备接收下一个字节。例如,当发送端发送一个字节的数据位后,接着发送停止位,接收端会等待停止位接收完成后,才会开始处理这个字节的数据,并准备接收下一个字节的起始位。
- 奇偶校验位(Parity Bit)
- 定义:奇偶校验位是一种简单的错误检测机制。它是在数据位之后添加的一位,用于检查数据在传输过程中是否发生错误。
- 类型:可以设置为奇校验、偶校验或无校验。在奇校验中,数据位和校验位中 “1” 的个数为奇数。例如,发送的数据位是 0110010,为了使 “1” 的个数为奇数,校验位会被设置为 0,这样数据位和校验位一起(01100100)中 “1” 的个数为奇数。在偶校验中,数据位和校验位中 “1” 的个数为偶数。比如,对于数据位 1010101,校验位会被设置为 1,使得(10101011)中 “1” 的个数为偶数。当接收方收到数据后,会根据设置的奇偶校验方式来检查数据的正确性。如果接收端计算出的数据位和校验位中 “1” 的个数不符合设定的奇偶校验规则,就说明数据在传输过程中可能出现了错误。不过,奇偶校验只能检测出奇数个错误位,对于偶数个错误位可能无法检测出来。
五.案例介绍
串口还是蛮好理解的,用户只需要配置好基础参数,他就可以按照你给的数据进行收发,话不多说,我们直接看实例。
由于特殊原因,客户原本使用AD对电池包剩余电量进行采集的方式对结果会有很大的误差。现在采用串口直接和电池包进行通讯,根据电池包返回的SOC值对电量进行校准。
要求:
下面看代码部分
void UART_Init()
{
AFP1|=0B00010100; //复用管脚选择
PCKEN|=0B00100000;//使能USART模块时钟
URLCR|=0B00000001;//8位数据长度,1位停止位,无奇偶校验位
URDLL=104;
URDLH=0; //波特率=Fmaster/(16*{URDLH,URDLL})=9600
URMCR|=0B00011000;//串口发送,接收使能
URIER|=0B00100001; //发送完成,接收buf非空中断
TCF=1;
INTCON|=0B10000000; //开全局中断
INTCON|=0B01000000; //开外设总中断
}
假如你是一个设备,什么时候发送数据完全取决你自己什么时候想发,所以对于发送我们不需要放在中断中去占用资源。相反,我们并不能确定对方什么时候给我们发送东西,假如我们一直在等,就会阻塞单片机的程序,所以接收我们是用中断接收。
void Uart_Send_Byte(unsigned char byte)//串口发送字符
{
URDATAL= byte;
while(TCF==0);
TCF=1;
}
void Uart_Send_Array(unsigned char *str)//发送数据组
{
SendCnt=0;
while(SendCnt<5)//我的待发送数据固定长为5个Byte
{
Uart_Send_Byte(*str);
str++;
SendCnt++;
}
}
void Uart_Receive()
{
if(Uartend==0)
{
if(Uart_Head_Flag==0)//判断头帧
{
SendBuf3[UartCnt]=URDATAL;
if(((UartCnt==0) && (SendBuf3[UartCnt]==0xAA)) || ((UartCnt==1) && (SendBuf3[UartCnt]==0x21)) ||((UartCnt==2) && (SendBuf3[UartCnt]==0x18)))
{
UartCnt++;
}
else
UartCnt=0;
if(UartCnt==3)
{
Uart_Head_Flag=1;
BMS_Check_Value=0;//
}
}
else
{
SendBuf3[UartCnt]=URDATAL;
UartCnt++;
if(UartCnt==31)//接收完全部数据
{
UartCnt=0;
Uart_Head_Flag=0;
Uartend=1;
}
}
}
else
{
UartCnt=0;
}
}
void Uart_Check_OK()//校验方式
{
unsigned char BMS_CheckH=0;
unsigned char BMS_CheckL=0;
unsigned char SUM=0;
if(Uartend)//接收完整数据帧
{
for(SUM=1;SUM<28;SUM++)
{
BMS_Check_Value=BMS_Check_Value+SendBuf3[SUM];
}
BMS_CheckL =BMS_Check_Value%256;
BMS_CheckH =BMS_Check_Value/256;
if(BMS_CheckL==SendBuf3[29] && BMS_CheckH==SendBuf3[30])
{
Soc_Value=SendBuf3[11];
Uartend=0;
}
}
}
以上就是发送以及接收数据的处理了,这样就可以根据接收的报文进行SOC数值的提取了。
这边再强掉一下当开启串口接收中断时,硬件接收Buf就会将接收数据存下,同时将RXNEF置1,读接收Buf里数据时自动清零。
然后给大家补充一个点,就是有可能单片机是5V的,但是电池BMS板那端的信号是3.3V高,这就大概率会通信不成功,导致MCU可以发送数据给BMS板,但是却接收不了信号,需要在RX端口增加电路。
圈起的电容是考虑到实车上环境对信号的影响。
近期再给台铃做仪表时,他有个需求是需要转意,代码我放在这边做个记录,有需要的可以直接用
if(ECU_Meter_Inquiry_F)//信息回复帧发送数组
{
ECU_Meter_Inquiry_F=0;
//数据转意处理部分
Buf3_Length=10;//原先数组长度
for(q=2;q<=8;q++)
{
if((Uart_Send_Buf[q]==0xBB) || (Uart_Send_Buf[q]==0xAA) || (Uart_Send_Buf[q]==0x55) )//0xBB 0xAA 0X55 需要转意
{
Uart_Send_Buf3[q+(Buf3_Length-10)]=0xBB;//0xBB替代原先数据
Buf3_Length++;发送数组长度++
for(j=q+(Buf3_Length-10);j<=Buf3_Length-1;j++)//每一位往后挪
{
Uart_Send_Buf3[j]=Uart_Send_Buf[q];
}
}
else
{
Uart_Send_Buf3[q+(Buf3_Length-10)]=Uart_Send_Buf[q];
}
}
Uart_Send_Buf3[0]=0xAA;
Uart_Send_Buf3[1]=0xAA;
Uart_Send_Buf3[Buf3_Length-1]=0x55;//填充包头包尾
Uart_Send_Array(Uart_Send_Buf3,Buf3_Length);//发送转意后的数组
}