基於Linux的ARM與上位機檔案傳輸
阿新 • • 發佈:2018-12-30
關於上位機以及arm的串列埠程式設計,網上的資料很多,不過兩者程式碼同時給出的資料卻很少,本菜鳥經過幾天時間的煎熬,終於實現了用自己編寫的上位機軟體傳輸檔案到arm板上。上位機使用的是C#,arm上使用的是Linux C,使用的檔案傳輸協議為XModem。關於XModem協議的格式可以參考我前面轉的一篇部落格,這裡就不細說了。
下面首先貼出程式碼:
/******ARM上的應用程式******/
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <termios.h> #include <signal.h> #include <dirent.h> #include <sys/stat.h> #include <sys/types.h> unsigned char buf[132]; int debug_buf[132]; int i; //打印出接收到的資料(除錯時用) void print_data_package() { printf("received package:\n"); for(i=0;i<132;i++) { debug_buf[i]=buf[i]; printf("%d ",buf[i]); } printf("\n"); printf("\n"); } //初始化串列埠 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\n"); close(fd); exit(1); } if(tcgetattr(fd, &tmp_term) < 0) { printf("Error: get termios failed\n"); 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\n"); 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\n",argv[1]); printf("\n"); if(dev_fd==-1) { perror(argv[1]); exit(1); } read(dev_fd,buf,132); //接收第一個資料包 if(buf[0]==0x01) //接收到第一個SOH訊號 { printf("first SOH received\n"); printf("\n"); signal[0]=0x15; write(dev_fd,signal,1); //傳送NAK訊號 printf("NAK sent\n"); 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\n"); printf("my package head: %d, my package number: %d, your package number:%d\n",buf[0],package_number,buf[1]); printf("\n"); check_sum=0; for(i=3;i<131;i++) { check_sum+=buf[i]; //計算校驗和 } if(check_sum==buf[131]) //校驗和正確 { printf("check summation right\n"); printf("my check summation: %d, your check summation: %d\n",check_sum,buf[131]); printf("\n"); lseek(file_fd,0,SEEK_END); //移至檔案尾部 write(file_fd,buf+3,128); //將資料寫入檔案 signal[0]=0x06; write(dev_fd,signal,1); //傳送ACK訊號 printf("ACK sent\n"); package_number++; //包號加1 memset(buf,0,132); //清空資料包 read(dev_fd,buf,132); //讀取資料包 tcflush(dev_fd,TCIFLUSH); //清空串列埠接收緩衝區 print_data_package(); } else { printf("check summation wrong\n"); printf("my check summation: %d, your check summation: %d\n",check_sum,buf[131]); printf("\n"); signal[0]=0x15; write(dev_fd,signal,1); //傳送NAK訊號 printf("NAK sent\n"); memset(buf,0,132); //清空資料包 read(dev_fd,buf,132); //讀取資料包 tcflush(dev_fd,TCIFLUSH); //清空串列埠接收緩衝區 print_data_package(); } } else { printf("data package sturcture or number wrong\n"); printf("my package head: %d, my package number: %d, your package number: %d\n",buf[0],package_number,buf[1]); printf("\n"); signal[0]=0x15; write(dev_fd,signal,1); //傳送NAK訊號 printf("NAK sent\n"); memset(buf,0,132); //清空資料包 read(dev_fd,buf,132); //讀取資料包 tcflush(dev_fd,TCIFLUSH); //清空串列埠接收緩衝區 print_data_package(); } } printf("EOT received\n"); printf("\n"); signal[0]=0x06; write(dev_fd,signal,1); //傳送ACK訊號 printf("ACK sent\n"); close(dev_fd); close(file_fd); return 0; } else { printf("first SOH not received"); printf("\n"); close(dev_fd); close(file_fd); exit(1); } }
/******上位機軟體傳送按鈕對應的C#程式碼******/
下面講講程式設計中遇到的問題。遇到的一個主要問題是:一個132位元組的資料包有時候會被分兩次接收,也就是說第一次buf中接收到的是資料包的前一部分資料,第二次接收到的是後一部分資料,因此兩次資料包都不正確,此現象迴圈往復,導致資料包不斷重發,檔案永遠無法傳輸成功。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("開始傳輸檔案\n"); 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() + "\n"); signal[0] = (byte)sp.ReadByte(); if (signal[0] == 0x06) //收到ACK訊號 { packageNumber++; //包號加1 } else if (signal[0] == 0x15) //收到NAK訊號 { this.rtbReceive.AppendText("資料包" + packageNumber.ToString() + "傳送失敗,重發\n"); 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("檔案傳輸成功\n"); } else this.rtbReceive.AppendText("檔案傳輸錯誤"); } else this.rtbReceive.AppendText("檔案傳輸錯誤"); }
沒有經驗的我百思不得其解,查閱了眾多資料,反覆除錯,終於找到了原因。原因就出在串列埠的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位元組並返回。