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()函数内,循环执行,有新的命令帧就执行,没有就等待新的命令到来。
  • 这样整个控制外部设备的下位机软件就已经做好了,上位机发送带有命令字的数据帧到下位机,就可以控制各种外部设备了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章