基于Linux的ARM与上位机文件传输

2019-07-12 17:27发布

关于上位机以及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字节并返回。