虛擬機串口與主機串口通信·小程序(上)

主機串口用到的工具是SSCOM32,虛擬機串口工具是VSPD。即通過VSPD工具,可以將二者的串口相連,可以想象成有一根串口線連接了主機和虛擬機。
<br>第一步 確定端口
打開VSPD,如下圖,點擊“Port pairs”-“create pair“。我們要用到的就是COM1和COM2。此時,二者狀態都是:close。
虛擬機串口與主機串口通信·小程序(上)虛擬機串口與主機串口通信·小程序(上)
虛擬機串口與主機串口通信·小程序(上)
<br>然後,打開虛擬機,“虛擬機”-“設置”-“串口”-選擇端口號,並開啓,確認。
虛擬機串口與主機串口通信·小程序(上)
<br>如下圖,這樣代表該串口已打開。
虛擬機串口與主機串口通信·小程序(上)
<br>再打開SSCOM,選擇“串口2”,打開串口。
虛擬機串口與主機串口通信·小程序(上)
<br>同樣,在VSPD中觀察到串口已經被打開。
虛擬機串口與主機串口通信·小程序(上)
<br>第二步 測試
虛擬機串口向主機發:
虛擬機串口與主機串口通信·小程序(上)
<br>可以看到,主機串口收到了“hello”。
虛擬機串口與主機串口通信·小程序(上)
<br>說到這裏,複習一下“終端”。
1、控制檯終端:tty0~tty6,也叫虛擬終端。(tty0是當前正在使用的虛擬終端的別名)
2、僞終端:pty(圖形終端,遠程控制終端)
3、串口終端:ttyS0~ttyS4
控制終端:tty。即當前正在使用的終端(以上的任何一種都有可能是控制終端)。
這個,自己輸入輸出重定向練習練習就清楚了~

<br>同時,在vspd中也能看到二者傳輸的信息:
虛擬機串口與主機串口通信·小程序(上)
第三步 初始化串口配置信息
一般來說,我們可以手動修改虛擬機和主機的串口配置信息(波特率、校驗位、停止位等等),但是每次都要修改是不是很麻煩呢。所以,可以寫一個初始化串口配置信息的文件,在通信之前執行就可以了。
<br>初始化串口的代碼如下:
port.h

#ifndef _PORT_H_
#define _PORT_H_

#include <stdio.h>
#include < termios.h>
#include < unistd.h>
#include < sys/types.h>
#include < sys/stat.h>
#include < fcntl.h>

/**
 struct termios{
           tcflag_t c_iflag;             // input modes 
           tcflag_t c_oflag;             // output modes 
           tcflag_t c_cflag;             // control modes 
           tcflag_t c_lflag;             // local modes 
           cc_t     c_cc[NCCS];      // control chars 
 };
*/

//設置波特率
void setSpeed(struct termios *ptio,int speed);

//設置數據位
void setData(struct termios *ptio,int data);

//設置奇偶校驗
void setParity(struct termios *ptio,int flag);  //0-忽略奇偶校驗  1-設置奇校驗  2-設置偶校驗

//設置停止位
void setStop(struct termios *ptio, int stop);

//初始化串口 返回值:fd
int portInit(char devname[],int speed,int data,int flag,int stop);

#endif

port.c

#include " port.h"

//設置波特率
void setSpeed(struct termios *ptio,int speed)
{
    switch(speed)
    {
        case 9600:
            cfsetispeed(ptio,B9600);
            cfsetospeed(ptio,B9600);
            break;
        case 14400:
            break;
        case 19200:
            cfsetispeed(ptio,B19200);
            cfsetospeed(ptio,B19200);
            break;
        case 38400:
            cfsetispeed(ptio,B38400);
            cfsetospeed(ptio,B38400);
            break;
        case 115200:
            cfsetispeed(ptio,B115200);
            cfsetospeed(ptio,B115200);
            break;
        default:
            break;
    }
}

//設置數據位
void setData(struct termios *ptio,int data)
{
    ptio->c_cflag &= ~CSIZE;

    switch(data)
    {
        case 5:
            ptio->c_cflag |= CS5;
            break;
        case 6:
            ptio->c_cflag |= CS6;
            break;
        case 7:
            ptio->c_cflag |= CS7;
            break;
        case 8:
            ptio->c_cflag |= CS8;
            break;
        default:
            break;
    }
}

//設置奇偶校驗   0-忽略奇偶校驗  1-設置奇校驗  2-設置偶校驗
void setParity(struct termios *ptio,int flag)   
{
    switch(flag)
    {
        case 0:
            ptio->c_cflag &= ~PARENB;
            break;
        case 1:
            ptio->c_cflag |= PARENB;
            ptio->c_cflag |= PARODD;
            ptio->c_iflag |= (INPCK | ISTRIP);
            break;
        case 2:
            ptio->c_iflag |= (INPCK | ISTRIP);
            ptio->c_cflag |= PARENB;
            ptio->c_cflag &= ~PARODD;
            break;
        default:
            break;
    }
}

//設置停止位 (若停止位爲1,則清除CSTOPB;若停止位爲2,則激活CSTOPB)
void setStop(struct termios *ptio, int stop)
{
    switch(stop)
    {
        case 1:
            ptio->c_cflag &= ~CSTOPB;
            break;
        case 2:
            ptio->c_cflag |= CSTOPB;
            break;
        default:
            break;
    }
}

//初始化串口 返回值:fd
int portInit(char devname[],int speed,int data,int flag,int stop)
{
    int fd;
    struct termios tio = {0};

    //打開串口設備
    fd = open(devname,O_RDWR);
    if(fd == -1)
    {
        printf("open port : %s failed.\n",devname);
        return;
    }

    //獲取原有串口配置
    tcgetattr(fd,&tio);
    //激活選項有CLOCAL和CREAD,用於本地連接和接收使能
    tio.c_cflag |= CLOCAL | CREAD;
    //設置波特率
    setSpeed(&tio,speed);
    //設置數據位,需要使用掩碼設置
    setData(&tio,data);
    //設置奇偶校驗
    setParity(&tio,flag);
    //設置停止位
    setStop(&tio, stop);
    //設置最少字符和等待時間
    tio.c_cc[VTIME] = 0;    
    tio.c_cc[VMIN] = 1; //最小字符,緩衝區裏達到數量時才返回
    //設置不採用流控制
    tio.c_cflag &= ~CRTSCTS;
    //清除(輸入)緩存
    tcflush(fd,TCIFLUSH);
    //設置串口默認爲堵塞模式
    fcntl(fd,F_SETFL,0);
    //激活配置
    tcsetattr(fd,TCSANOW,&tio);

    return fd;
}

<br>如何查看串口配置是否成功?要查看某個串口的波特率等信息,可以在控制檯輸入命令: stty -F /dev/ttyS0 -a #ttyS0爲要查看的串口。
第四步 讀取配置信息
這個,在上一篇文章中已經展示過了,爲了練手,可以從配置文件中讀取配置信息,再利用以上代碼進行初始化,當然也可以直接初始化啦~
<br>以下是從文件中獲取配置信息(基於上一篇文章內容)
配置信息:
虛擬機串口與主機串口通信·小程序(上)
readConfig.h

#ifndef _READCONFIG_H_
#define _READCONFIG_H_

#include < stdio.h>
#include < string.h>
#include < sys/types.h>
#include < sys/stat.h>
#include < fcntl.h>

/*串口屬性結構體*/
struct t_port
{
    char devname[20];
    int speed;
    int data;
    int parity;
    int stop;
};

//去空格 
void rm_space(char *pStr);

//去註釋 
void rm_annotation(char *pStr);

//獲取配置項信息
void getMsg(char filename[],struct t_port *port);

#endif

<br>readConfig.c

#include " readConfig.h"

int icount = 0;

//去空格 
void rm_space(char *pStr)
{
    char *pos = pStr;
    pos = strchr(pStr,' ');
    while(pos != NULL)
    {
        strcpy(pos,pos+1);
        pos = strchr(pos,' ');
    }
}

//去註釋 
void rm_annotation(char *pStr)
{
    icount++;
    char *pos = pStr;
    char *end = NULL;
    if(icount == 1) //第一行有可能頂格
    {
        pos = strchr(pStr,'#');
    }
    else
    {
        pos = strstr(pStr,"\n#");
    }

    while(pos != NULL)
    {
        end = strchr(pos+1,'\n');
        strcpy(pos,end);
        pos = strstr(pStr,"\n#");
    }   

}

//獲取配置項信息
void getMsg(char filename[],struct t_port *port)
{
    int fd;
    int readByte;
    char buf[512] = "";
    char *pos = NULL;
    char *end = NULL;
    char key[20] = " ";
    char value[20] = " ";
    char keys[10][20] = {""};
    char values[10][20] = {""};
    int flag = 0;
    int i = 0;

    //打開配置文件
    fd = open(filename, O_RDWR);
    readByte = read(fd,buf,512);
    //處理數據  
    rm_space(buf);
    rm_annotation(buf);

    pos = strchr(buf,'\n');
    end = strchr(pos,'=');

    while(end != NULL)
    {
        memset(key,0,sizeof(key));
        memset(value,0,sizeof(value));
        memcpy(key,pos+1,end - (pos + 1));

        pos = end;
        end = strchr(pos,'\r');
        if(end == NULL) //if the final data
        {
            flag = 1;
            break;
        }
        memcpy(value,pos+1,end - (pos + 1));

        //存key value
        memcpy(keys[i],key,strlen(key));
        memcpy(values[i],value,strlen(value));
        i++;

        pos = end + 1;
        end = strchr(pos,'=');

    }

    if(flag)    
    {
        end = strchr(pos,'\0');
        memcpy(value,pos+1,end - (pos + 1));
        memcpy(keys[i],key,strlen(key));
        memcpy(values[i],value,strlen(value));
    }

    //進行匹配
    for(i = 0; i < 10 ;i++)
    {
        if(strcmp(keys[i],"dev") == 0)
        {
            strcpy(port->devname,values[i]);
        }
        else if(strcmp(keys[i],"speed") == 0)
        {
            port->speed = atoi(values[i]);
        }
        else if(strcmp(keys[i],"data") == 0)
        {
            port->data = atoi(values[i]);
        }
        else if(strcmp(keys[i],"parity") == 0)
        {
            port->parity = atoi(values[i]);
        }
        else if(strcmp(keys[i],"stop") == 0)
        {
            port->stop = atoi(values[i]);
        }
    }

}

第五步 編寫通信程序
那麼,主函數只要調用以上功能能函數,再調用read\write系統函數,就可以了。

#include < stdio.h>
#include " port.h"
#include " readConfig.h" 
int main()
{
    int fd;
    char filename[20] = "serial.cfg";
    char recbuf[100] = "";
    char sendbuf[100] = "";

    struct t_port port = {0};

    //從文件獲取配置信息
    getMsg(filename,&port);
    //串口初始化
    fd = portInit(port.devname,port.speed,port.data,port.parity,port.stop);

    //主機與虛擬機串口通信
    while(1)
    {
        memset(recbuf,0,100);
        memset(sendbuf,0,100);

        //虛擬機等待主機用戶輸入,接收消息
        printf("please wait...\n");
        read(fd,recbuf,100);
        printf("receive msg: %s\n",recbuf);

        //等待虛擬機用戶輸入,發送消息給主機
        printf("send msg: ");
        scanf("%s",sendbuf);
        if(strcmp(sendbuf,"over") == 0)
        {
            break;
        }
        write(fd,sendbuf,sizeof(sendbuf));
    }

    close(fd);
    return 0;
}

第六步 編譯運行
編譯運行之後,主機(SSCOM32)虛擬機串口之間就可以通信了。如下圖:
虛擬機串口與主機串口通信·小程序(上)
主機:hello
虛擬機:world
主機:12345
虛擬機:over
<br>運行時,如果出現主機發消息,虛擬機接收不到的情況,是因爲消息內容都還在緩衝區。比如,在用write發送數據時沒有鍵入回車,信息就將發送不出去的情況,這主要是因爲我們在輸出輸入時是按照 規範模式接受到回車或者換行才發送,而很多情況我們是不需要回車和換行的,這時,應當切換到行方式輸入,tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);不經處理直接發送。
當然也可以直接在SSCOM的界面上勾選“發送新行”。

<br>但是,可以發現,我們只能實現:主機發一句,虛擬機收到內容後才能發送,然後主機收到內容纔可以繼續發送。不能做到主機和虛擬機同時發送,那麼如何解決呢?多進程就OK了。明天再寫~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章