关于上位机以及arm的串口编程,网上的资料很多,不过两者代码同时给出的资料却很少,本菜鸟经过几天时间的煎熬,终于实现了用自己编写的上位机软件传输文件到arm板上。上位机使用的是C#,arm上使用的是Linux C,使用的文件传输协议为XModem。关于XModem协议的格式可以参考我前面转的一篇博客,这里就不细说了。
下面首先贴出代码:
/******ARM上的应用程序******/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
unsigned char buf[132];
int debug_buf[132];
int i;
//打印出接收到的数据(调试时用)
void print_data_package()
{
printf("received package:
");
for(i=0;i<132;i++)
{
debug_buf[i]=buf[i];
printf("%d ",buf[i]);
}
printf("
");
printf("
");
}
//初始化串口
void uart_init()
{
struct termios tmp_term;
int fd;
if( (fd = open("/dev/ttyS1", O_RDWR)) < 0 ) {
printf("Error: cannot open file /dev/ttyS1");
exit(1);
}
if(isatty(fd) == 0) {
printf("Error: /dev/ttyS1 is not a tty
");
close(fd);
exit(1);
}
if(tcgetattr(fd, &tmp_term) < 0) {
printf("Error: get termios failed
");
close(fd);
exit(1);
}
tmp_term.c_cflag = CS8|CREAD|HUPCL|CLOCAL;
tmp_term.c_oflag = 0;
tmp_term.c_iflag = IXANY|IGNBRK|IGNPAR;
tmp_term.c_lflag = 0;
tmp_term.c_cc[VMIN]=132; //read函数最少读到132字节数据才返回,否则保持阻塞,以此保证数据包的完整性
tmp_term.c_cc[VTIME]=0;
cfsetispeed(&tmp_term,B115200); //设置波特率
cfsetospeed(&tmp_term,B115200);
if(tcsetattr(fd, TCSAFLUSH, &tmp_term) < 0) {
printf("Error: set termions failed
");
close(fd);
exit(1);
}
close(fd);
}
int main(int argc, char** argv)
{
unsigned char signal[1];
unsigned char check_sum=0; //校验和
unsigned char package_number=0; //包号
char file_name[100]; //文件名
int dev_fd;
int file_fd;
uart_init(); //初始化串口
sprintf(file_name,"/mnt/usb/arm_program/received_data");
memset(buf,0,sizeof(buf));
dev_fd=open(argv[1],O_RDWR|O_NOCTTY); //打开串口
tcflush(dev_fd,TCIOFLUSH); //清空串口缓冲区
printf("%s opened
",argv[1]);
printf("
");
if(dev_fd==-1)
{
perror(argv[1]);
exit(1);
}
read(dev_fd,buf,132); //接收第一个数据包
if(buf[0]==0x01) //接收到第一个SOH信号
{
printf("first SOH received
");
printf("
");
signal[0]=0x15;
write(dev_fd,signal,1); //发送NAK信号
printf("NAK sent
");
file_fd=open(file_name,O_RDWR|O_CREAT); //打开文件,此文件用来保存接收到的数据
read(dev_fd,buf,132); //读取数据包
tcflush(dev_fd,TCIFLUSH); //清空串口接收缓冲区
print_data_package();
while(buf[0]!=0x04) //未收到EOT信号
{
if(buf[0]==0x01&&buf[1]==package_number) //包头及包号正确
{
printf("data package structure and package number right
");
printf("my package head: %d, my package number: %d, your package number:%d
",buf[0],package_number,buf[1]);
printf("
");
check_sum=0;
for(i=3;i<131;i++)
{
check_sum+=buf[i]; //计算校验和
}
if(check_sum==buf[131]) //校验和正确
{
printf("check summation right
");
printf("my check summation: %d, your check summation: %d
",check_sum,buf[131]);
printf("
");
lseek(file_fd,0,SEEK_END); //移至文件尾部
write(file_fd,buf+3,128); //将数据写入文件
signal[0]=0x06;
write(dev_fd,signal,1); //发送ACK信号
printf("ACK sent
");
package_number++; //包号加1
memset(buf,0,132); //清空数据包
read(dev_fd,buf,132); //读取数据包
tcflush(dev_fd,TCIFLUSH); //清空串口接收缓冲区
print_data_package();
}
else
{
printf("check summation wrong
");
printf("my check summation: %d, your check summation: %d
",check_sum,buf[131]);
printf("
");
signal[0]=0x15;
write(dev_fd,signal,1); //发送NAK信号
printf("NAK sent
");
memset(buf,0,132); //清空数据包
read(dev_fd,buf,132); //读取数据包
tcflush(dev_fd,TCIFLUSH); //清空串口接收缓冲区
print_data_package();
}
}
else
{
printf("data package sturcture or number wrong
");
printf("my package head: %d, my package number: %d, your package number: %d
",buf[0],package_number,buf[1]);
printf("
");
signal[0]=0x15;
write(dev_fd,signal,1); //发送NAK信号
printf("NAK sent
");
memset(buf,0,132); //清空数据包
read(dev_fd,buf,132); //读取数据包
tcflush(dev_fd,TCIFLUSH); //清空串口接收缓冲区
print_data_package();
}
}
printf("EOT received
");
printf("
");
signal[0]=0x06;
write(dev_fd,signal,1); //发送ACK信号
printf("ACK sent
");
close(dev_fd);
close(file_fd);
return 0;
}
else
{
printf("first SOH not received");
printf("
");
close(dev_fd);
close(file_fd);
exit(1);
}
}
/******上位机软件发送按钮对应的C#代码******/
private void btnSend_Click(object sender, EventArgs e)
{
BinaryReader sr = new BinaryReader(File.Open(this.txtFileName.Text,FileMode.Open)); //打开文件
byte[] dataPackage = new byte[132]; //定义132字节的数据包
Array.Clear(dataPackage, 0, 132); //清空数据包
byte[] signal=new byte[132]; //信号
byte packageNumber=0; //包号
int i;
signal[0]=0x01; //起始信号SOH
sp.Write(signal, 0, 132); //发送起始信号SOH
signal[0] = (byte)sp.ReadByte();
if (signal[0] == 0x15) //第一次收到NAK信号
{
this.rtbReceive.AppendText("开始传输文件
");
while (sr.Read(dataPackage, 3, 128) != 0) //文件没有结束,则从文件中读取128字节数据,并打包发送
{
dataPackage[0] = 0x01; //包头SOH
dataPackage[1] = packageNumber; //包号
dataPackage[2] = (byte)~packageNumber; //包号取反
dataPackage[131] = 0; //清零校验码
for (i = 3; i < 131; i++)
{
dataPackage[131] += dataPackage[i]; //计算检验码
}
sendPackage:
sp.DiscardOutBuffer(); //清空串口输出缓冲区
sp.Write(dataPackage, 0, 132); //发送数据包
this.rtbReceive.AppendText("发送数据包" + packageNumber.ToString() + "
");
signal[0] = (byte)sp.ReadByte();
if (signal[0] == 0x06) //收到ACK信号
{
packageNumber++; //包号加1
}
else if (signal[0] == 0x15) //收到NAK信号
{
this.rtbReceive.AppendText("数据包" + packageNumber.ToString() + "发送失败,重发
");
goto sendPackage; //重发
}
else
this.rtbReceive.AppendText("文件传输错误");
Array.Clear(dataPackage, 0, 132); //清空数据包
}
signal[0] = 0x04; //EOT信号
sp.Write(signal, 0, 132); //发送EOT信号
if (sp.ReadByte() == 0x06) //收到ACK信号
{
this.rtbReceive.AppendText("文件传输成功
");
}
else
this.rtbReceive.AppendText("文件传输错误");
}
else
this.rtbReceive.AppendText("文件传输错误");
}
下面讲讲编程中遇到的问题。遇到的一个主要问题是:一个132字节的数据包有时候会被分两次接收,也就是说第一次buf中接收到的是数据包的前一部分数据,第二次接收到的是后一部分数据,因此两次数据包都不正确,此现象循环往复,导致数据包不断重发,文件永远无法传输成功。
没有经验的我百思不得其解,查阅了众多资料,反复调试,终于找到了原因。原因就出在串口的c_cc[VMIN]和c_cc[VTIME]参数上。关于c_cc[VMIN]和c_cc[VTIME]的设置网上资料很多,这里就不贴了,下面我仅以一个例子说明在c_cc[VTIME]=0时c_cc[VMIN]的作用。
假设c_cc[VMIN]=10,c_cc[VTIME]=0,read函数的第三个参数给的是15,此时程序已经执行到read函数读取串口设备文件,下面分情况讨论:
若串口接收缓冲区中的数据量小于10字节,则read函数阻塞等待,直至缓冲区接收到10字节数据,然后读取并返回;
若串口接收缓冲区中的数据量大于等于10字节,且小于等于15,则read函数读取串口接收缓冲区的全部数据并返回;
若串口接收缓冲区中的数据量大于15字节,则read函数读取串口接收缓冲区的前15字节并返回。