1. 程式人生 > >基於Linux的ARM與上位機檔案傳輸

基於Linux的ARM與上位機檔案傳輸

關於上位機以及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#程式碼******/

        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("檔案傳輸錯誤");
        }
下面講講程式設計中遇到的問題。遇到的一個主要問題是:一個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位元組並返回。