1.串口通讯介绍
串口通讯(Serial Communications)是一种通过串口进行数据传输的通讯方式,通过串行口每次传输一个字节的数据,按照约定的协议进行数据的传输和接收。串口通讯的原理是利用串行口的发送和接收线路,将需要传输的数据逐位传输到接收端,然后接收端再将这些数据组合成完整的信息。在传输过程中,数据是按照位(bit)进行发送和接收的,而不是按字节(byte)进行并行传输。
串口通讯早期就定义了一套标准的串口规约,常见的接口标准包括RS-232、RS-485等。这些标准规定了接口的物理特性、电气特性、信号功能以及传输过程等。其中,RS-232是最常用的标准之一,它规定了使用数据信号线、地线、控制线等进行传输,适用于外设和计算机间的点对点通信。
在连接方面,串口通讯通常使用DB9(9个引脚)或DB25(25个引脚)等接口进行连接。在这些接口中,GND(地线)、TXD(发送数据)、RXD(接收数据)是最基本的通信线,其他引脚则用于控制信号或握手信号等。
- 通信参数与模式
串口通讯需要双方事先约定好通信参数,这些参数包括波特率、数据位、校验位、停止位等。其中,波特率是指每分钟传输二进制的位数,是衡量资料传送速率的指标。数据位则是指一个通信单元发送的有效信息位,一般可选6、7、8、9位。校验位用于检错,有偶校验、奇校验、高校验和低校验等方式。停止位则表示本通信单元结束的标志。
在通信模式方面,可以是单工、半双工或全双工模式。单工模式的数据传输是单向的,一方固定为发送端,一方固定为接收端。半双工模式则允许数据在两个方向上传输,但在任何时刻只能由其中一方发送数据,另一方接收数据。全双工模式则允许数据同时在两个方向上传输,需要发送设备和接收设备都有独立的接收和发送能力。
- 特点与应用
点对点通信:串口通信是一种点对点的通信方式,稳定性高、传输速度快,适用于一些对实时性要求较高的应用场景。
通信距离短:串口通信的传输距离通常较短,一般在几十米以内,容易受到干扰。不过也有说法认为串口通讯的传输距离较长,可以达到几百米甚至更远(如IEEE488定义的并行通行设备线总长不得超过20米,任意两个设备间的长度不得超过2米,而串口长度可达1200米),这取决于具体的接口标准和传输条件。
通信速率低:串口通信的传输速率通常较低,一般在几百到几千个比特每秒,难以满足一些高速数据传输的需求。
硬件接口简单:串口通信使用的是串行接口,接口简单可靠,不需要复杂的硬件支持。
支持多种通信协议:串口通信可以支持多种通信协议,如RS-232、RS-485、Modbus等,提供了灵活多样的通信方式。
串口通讯在多个领域中得到广泛应用,包括工业自动化(用于控制和监控机器和生产线)、消费电子产品(如打印机、扫描仪等)、计算机网络(较早的计算机网络中用于连接调制解调器)、科研设备(如示波器、频谱分析器等)以及汽车电子(用于诊断系统)等领域。
2.串口通讯更新字库
在STM32之LCD屏GBK字库制作与调用中介绍了字库的制作、烧写以及调用方式。但上述使用方法是将字库文件分割为多个数组直接写入。该方法适用于单个字库且字库文件相对较小的情况下进行烧写。本次将介绍利用串口通讯协议,实现串口方式下载更新字库文件。如下图所示:
2.1 协议介绍
本协议采用发送–应答方式,每包数据固定4096字节(正文数据),带异或校验;
协议格式:协议头 + 数据包大小 + 数据标志 + 数据包内容 + 异或校验值。数据均已16进制方式传输;
协议头:77 62 79 71 --字符串”wbyq”十六进制ASCII值;
数据包大小:固定两字节,高位在前;
数据标志:01 —实体内容;
02 —应答信息;
数据包内容: 要发送的实际内容,单包数据正文最大4096字节;
异或校验值:对发送的内容进行异或;
编码格式:GBK/GB2312
-
示例1
-
发送内容:
-
应答信号:
-
示例2
-
发送内容:
-
应答示例:
2.2 协议数据解析示例
按照文件传输协议格式,解析串口发送的数据内容,通过形参返回解析得到的内容和长度。
/*
串口文件传输数据解析
形参:src --原始内容
count --src的字节数据
buffer --保存解析的内容
buffer_len --接收的实体内容长度
返回值:0成功,其它值--失败
*/
u8 Usart_DataGet(const u8 *src,u16 count,u8 *buffer,u16 *buffer_len)
{
/*
77 62 79 71 0 6 1 31 32 33 34 35 36 7
77 62 79 71 --协议头
0 6 --内容长度
1 --标志符
31 32 33 34 35 36 --实际内容
7 --异或校验值
*/
if(count<8)return 1;//长度错误
if(src[0]!='w' || src[1]!='b' || src[2]!='y' || src[3]!='q')
{
return 2;//协议头错误
}
u16 len=src[4]<<8|src[5];//数据长度
if(len<=0 || len>4096)return 3;//数据内容长度错误
*buffer_len=len;
u8 xor=0;//保存异或校验值
if(src[6]!=0x1)return 4;//数据标志位错误
u16 i=0;
for(i=0;i<len;i++)
{
xor^=src[7+i];
buffer[i]=src[7+i];
}
if(xor!=src[7+i])return 5;//校验出错
return 0;//数据接收正常
}
2.3 应答发送
/*
发送应答
*/
void Usart_SendAck(u8 stat)
{
u8 data[10]={0x77,0x62,0x79,0x71,0x0,0x1,0x02};
u8 xor=0;//保存异或校验值
data[7]=stat;//应答状态
xor^=stat;
data[8]=xor;//异或校验值
Usartx_SendData(USART1,data,9);//发送应答
}
2.4 字库更新
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "timer.h"
#include "infrared.h"
#include "w25q64.h"
#include "at24c08.h"
#include "nt35310.h"
u8 Usart_DataGet(const u8 *src,u16 count,u8 *buffer,u16 *buffer_len);
void Usart_SendAck(u8 stat);
u8 buffer[4096];
u16 buffer_len;
int main()
{
u8 key=0;
Beep_Init();
Led_Init();
Key_Init();
Usartx_Init(USART1,921600,72);
TIMx_Init(TIM2,72,20*1000);
printf("USART1初始化完成\r\n");
u16 id=W25Q64_Init();
printf("id=%#x\r\n",id);
I2C_Init();//初始化硬件
NT35310_Init();
LCD_DisplayStr(100,200,16,"GBK Update:",RED);
u16 i=0;
u8 ret;
u32 addr=0;
char arr[100];
while(1)
{
if(usart1_flag)
{
//usart1_rx_buff[usart1_cnt]='\0';
//printf("usart1:%s\r\n",usart1_rx_buff);
/*
77 62 79 71 0 6 1 31 32 33 34 35 36 7 -->十六进制
77 62 79 71 --协议头
0 6 --内容长度
1 --标志符
31 32 33 34 35 36 --实际内容
7 --异或校验值
*/
ret=Usart_DataGet(usart1_rx_buff,usart1_cnt,buffer,&buffer_len);
if(ret==0)
{
//将字库信息写入到W25Q64
W25Q64_KuaPageProgram_Erase(GBK_24_ADDR+addr,buffer,buffer_len);
addr+=buffer_len;
snprintf(arr,sizeof(arr),"%.1f KB",addr*1.0/1024);
LCD_DisplayStr(100+11*8,200,16,arr,RED);
LED1=!LED1;
}
Usart_SendAck(ret);
if(buffer_len<4096)
{
printf("字库更新完成\r\n");
LCD_DisplayStr(10,240,24,"串口字库更新应用示例",RED);
}
//printf("ret=%d\n",ret);
usart1_flag=0;
usart1_cnt=0;
}
}
}
2.5 字库更新运行效果
3 字库合并
若一次性需要烧写多个字库,则可以将多个字库合并为一个字库文件,代码实现如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
//字库合并
int main(int argc,char **argv)
{
if(argc<3)
{
printf("运行格式:./app <字库文件1>、<字库文件2>、<字库文件3>....\n");
return 0;
}
int i=0;
char *font_file[5];
int file_cnt=0;
unsigned int font_size[5];
unsigned int font_count=0;
int ret;
for(i=1;i<argc;i++)
{
font_file[file_cnt]=argv[i];
struct stat statbuf;
ret=stat(font_file[file_cnt],&statbuf);//获取文件大小
if(statbuf.st_size<=0 || ret)//保证能正常获取到文件大小
{
continue;
}
font_size[file_cnt]=statbuf.st_size;
printf("字库文件%d:%s\t大小:%d byte\n",i,font_file[file_cnt],font_size[file_cnt]);
file_cnt++;
}
if(file_cnt<2)
{
printf("合并文件少于2个,请检查要合并的文件是否正常\n");
return 0;
}
const char *dest_file="font.bin";//合并后的目标文件
//创建目标文件
FILE *dest_fp=fopen(dest_file,"w+b");
if(dest_file==NULL)
{
printf("目标文件%s文件创建失败\n",dest_file);
return 0;
}
char buffer[4096];
int size=0;
int j=0;
//打开文件
for(i=0;i<file_cnt;i++)
{
FILE *fp=fopen(font_file[i],"rb");
if(fp==NULL)
{
printf("%s 文件打开失败,请检查文件是否正常\n",font_file[i]);
return 0;
}
printf("字库文件%d正在写入....\n",i+1);
printf("文件大小:%d\t,写入的起始位置:%d\n",font_size[i],font_count);
while(1)
{
size=fread(buffer,1,sizeof(buffer),fp);//读取源文件内容
fwrite(buffer,size,1,dest_fp);//将内容写入到新文件中
font_count+=size;
if(size!=sizeof(buffer))break;
}
if(size%1024)//保证每次写入为1024的倍数
{
char data=0;
//不足1024用0补齐
for( j=0;j<1024-size%1024;j++)
{
fwrite(&data,1,1,dest_fp);
}
font_count+=j;
}
}
printf("文件合并完成,合并后的文件为:%s,\t文件大小:%.1f MB\n",dest_file,font_count*1.0/1024/1024);
return 0;
}