Arduino(5) 使用Mega2560設計上下位機串口通信系統的下位機

前言

因項目需求,我們要從PC端去控制一些外部設備,比如激光器、光放大器等,這些設備一般使用到的都是低速的串口通信,所以我們需要設計一個上下位機串口通信系統來控制這些設備。這篇文章先講如何使用Arduino Mega2560開發板來作爲下位機控制各個外部設備。

上下位機工作原理

  • 通常來說,上位機指的是PC端的控制軟件,比如可以設置讀取參數的界面軟件,下位機則是指單片機或者帶微處理器的系統,這裏我們使用Mega2560來作爲下位機控制板。下位機可以把一些模擬信號經過AD採集後轉換爲數字信號,經過處理後通過串口發送給上位機;同理上位機也可以給下位機發送一些指令或者信息。
  • 我們這裏需要通過PC端的界面軟件發送命令參數到外部設備上,然後再把外部設備的某些功能參數返回給界面軟件。就以前一篇文章Arduino(4)爲例說明,我們已經把外部設備光開關相關的功能操作寫成了類庫,mega2560作爲下位機控制板直接去調用這個類庫就可以了。

下位機軟件執行流程

在編寫下位機軟件之前,我們可以先自定義一種串口通信的數據幀,來確保上下位機之間的通信不會出現差錯。上下位機串口通信下行協議:PC -> Mega2560,共有8字節構成,採用異或校驗。幀頭可以自定義一字節的數據,例如0xfb;命令字表示自定義一些命令操作,例如我將設置光開關通道號的操作定義爲0x10#define OPTICALSWITCH_CHANNEL_SET 0x10;4個字節的數據值區域表示攜帶本次命令操作設置的參數值;幀尾自定義爲數據幀的末尾,例如0xfe;校驗碼則是對整個數據幀進行左右異或後得到的一個字節的數據。

幀頭 校驗碼 命令 數據值 數據值 幀尾
1 byte 1 byte 1 byte 2 byte 2byte 1 byte

接下來就是在Arduino IDE編寫數據幀的處理流程,代碼如下所示:

#include <OpticalSwitch.h>
#define FRAME 8 // frame length for host command
/*********** Module Command Keyword *************/
#define OPTICALSWITCH_CHANNEL_SET 0x10
#define OPTICALSWITCH_CHANNEL_READ 0x11

/*********** Module Name Code *********************/
#define OpticalSwitch 0x03

// global varibles for com
const int baud1 = 9600;        // inital baud rate for com with host
byte rxData[FRAME];     // bytes for received frames
int comError = 0;       // error count for communications
bool newCMD = false;    // flag for incoming new command
bool response = false;  // flag for outgoing data
int inbyte;             // use it to clear other data in serial buffer

void setup() 
{
    // put your setup code here, to run once:
    Serial.begin(baud1);       //connect with host software
    delay(10);
    modComSetup();  // setup and check communications to modules sequentially
}

void loop() 
{
    // put your main code here, to run repeatedly:
    delay(300);
    int rxLength = Serial.available();  //判斷接收數據的長度
    if(rxLength > 0)
    {
        if (rxLength == FRAME)    //接收數據的長度等於預設值才爲指定命令幀,並將其接收下來;否則清除串口緩衝區
        {
            // get CMD in rxData
            Serial.readBytes(rxData, FRAME); //接收數據,存進指定數組
            newCMD = true;
        }
        else
        {
            // clear incoming buffer
            clearBUF();
            // setup a error flag to notify host to resend command
            comError++;
        }
    }
	
	//當有新的命令幀下發時
    if(newCMD)
    {
        newCMD = false;
         //校驗碼通過後才由Arduino繼續下發命令給外部設備
        if (!checkCMD(rxData))  
        {
            comError = 0;
            //執行命令幀
            parseCMD(rxData);
        }
        else
        {
            comError++;
        }
    }
    clearBUF();
    //有錯則重發命令
    if(comError) resendCMD();
    
    routine();
}

//在此函數內循環讀取外部設備的參數
void routine()
{
}


void modComSetup()
{
    // setup com ports and registers of modules one by one
    if(OpticalSwitch_Setup()!=true)  return;           
}

void resendCMD()
{
    // in case COM error, ask host to resend the last command
    response = true;
    comError = 0;
    // prepare data to be send to host
}

void SendDataToHost(unsigned char ModuleCode)
{
    // frame data to fill up txData and send it to host    
    switch(ModuleCode)
    {
        case OpticalSwitch:
        {
            Serial.write('D');
            Serial.write(OpticalSwitch);
            Serial.write(channel);
            break;   
        } 
        default:
            break;
    }
}

void clearBUF()
{
    delay(50);
    int rxLength = Serial.available();
    // clear incoming buffer
    for (int i = 0; i < rxLength; i++)
    {
        inbyte = Serial.read();
    }
}

bool checkCMD(unsigned char *rxData)
{
    if(rxData[0]==0xfb && rxData[FRAME-1]==0xfe )
    {
        if(rxData[1] == calcBIP8(rxData))
        {
            return 0;
        }
        else
        {
            return 1;
        }
    }
    else
    {
        return 1;
    }
    // calculate to check the correctness of the received command
    // correct--> return 0;
    // uncorrect--> return 1;
}

void parseCMD(unsigned char *rxData)
{
    // parse received command and setup flags accordingly for further processing
    switch(rxData[2])
    {
        case OPTICALSWITCH_CHANNEL_SET:
        {
            OpticalSwitchChannel(rxData[4]);
            break;
        }
        case OPTICALSWITCH_CHANNEL_READ:
        {
            SendDataToHost(OpticalSwitch)
            break;
        }
        default:
            break;
    }
}

//異或校驗,用於校驗下發命令的數據幀是否完整
unsigned char calcBIP8(unsigned char* data)
{
    unsigned char bip16 = data[0]^(data[1]&0x00)^data[2]^data[3]^data[4]^data[5]^data[6]^data[7];
    unsigned char bip8 = ((bip16&0xf0)>>4)^(bip16&0x0f);
    return bip8;
}
  • 首先引用我們之前寫好的類庫#include <OpticalSwitch.h>;然後void setup()函數內初始化Arduino mega2560和PC電腦相連的串口(該串口由mega2560上自帶串口轉USB線和電腦上USB口連接),然後對外部設備進行初始化,這部分程序已經寫在了光開關的類庫裏了。void loop()函數內則是數據幀的整個處理流程
  • 從上位機下發的數據命令幀被與PC相連的串口(也就是Serial)接收後,首先判斷其數據長度是否等於預設值,不然就是傳輸過程出錯了,數據包出現了丟失;接着將接收到的數據存儲到rxData數組內,由異或校驗函數calcBIP8()來檢測數據幀的正確性;然後執行派發函數parseCMD(),這個函數根據數據幀內的命令字來執行相應的命令,調用相應模塊的指定函數;如果這個過程出錯了就重發命令,執行resendCMD()函數,並且清空串口緩衝區,等待下一次數據命令幀;routine()內則循環讀取外部設備的參數,存進相應的數組,等待上位機讀取。整個執行過程放在了loop()函數內,循環執行,有新的命令幀就執行,沒有就等待新的命令到來。
  • 這樣整個控制外部設備的下位機軟件就已經做好了,上位機發送帶有命令字的數據幀到下位機,就可以控制各種外部設備了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章