通过RS232串口异步读取条形码扫描器的数据

4

我有一个连接在PC的RS232串口上的条形码阅读器。我正在编写C++代码向条形码扫描仪发送命令并将响应返回给PC。目前程序可以正确地向设备发送数据,但无法从条形码扫描仪读取响应。在这种情况下,一旦我们向条形码阅读器发送命令,它会回复肯定或否定的确认。

e.g:- Send BEEP command.
 1. Host(PC) send a BEEP command to barcode scanner
 2. Barcode scanner make a beep sound and send the acknowledgement back
    to host (PC)
 3. Host (PC) read the acknowledgement

以下代码中的前两步骤正常运行,但我无法正确编写第三步。请有人帮助我纠正源代码,以异步读取来自条形码扫描器的响应。
主.cpp
#include <iostream>
extern "C"
{
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
}
#include "DeviceRS232.h"
#include "Message.h"


int main()
{
    unsigned char recvBuffer[257];
    unsigned char ledOn[] = {0x05, 0xE7, 0x04, 0x00, 0x0D, 0x00};
    unsigned char SSIBuffer[] = {0x00, 0xC6, 0x04, 0x08, 0x11, 0xEE, 0x01};
    unsigned char requestRevision[] = {0x00, 0x04, 0xA3, 0x04, 0x00};
    unsigned char sendBeep[] = {0x00, 0xE6, 0x04, 0x00, 0x05};

    Message beepCommand(sendBeep, sizeof(sendBeep)/sizeof(sendBeep[0]));

    std::cout << "*********************************************************" << std::endl << std::endl;
    DeviceRS232 dev_rs232;
    dev_rs232.setDefaultAttributes();
    dev_rs232.openSerialPort();


    // Send BEEP command several times.
    std::cout << "---Start sending beep---" << std::endl;
    for(int x=0; x<1; x++)
    {
        int sizeSent = dev_rs232.sendDataBuffer(beepCommand.getCommandData(), beepCommand.getLen());
        if( sizeSent > 0)
        {
            std::cout << "Data sent: " <<  sizeSent << std::endl;
        }

        memset(recvBuffer, 0, sizeof(recvBuffer));
        int recvSize = dev_rs232.receiveDataBuffer(recvBuffer, sizeof(recvBuffer));
        std::cout << "Date Received, Data: " <<  recvBuffer << " Size: " << recvSize << std::endl;
        sleep(2);
        /**
        while(true)
        {
            memset(recvBuffer, 0, sizeof(recvBuffer));
            int recvSize = dev_rs232.receiveDataBuffer(recvBuffer, sizeof(recvBuffer));
            if(recvSize > 0)
                std::cout << "Date Received, Data: " <<  recvBuffer << " Size: " << recvSize << std::endl;
            sleep(2);
        }*/
    }
    std::cout << "---End sending beep-----\n" << std::endl;


    dev_rs232.closeSerialPort();
    std::cout << "*********************************************************" << std::endl;


    return 0;
}

Message.h

#ifndef MESSAGE_H
#define MESSAGE_H

#include <iostream>
#include <string>
#include <numeric>
extern "C"
{
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
}

class Message
{
    public:
        Message();              //  default constructor
        virtual ~Message();     //  destructor


        Message(const std::basic_string<unsigned char> msg) : commandMsg(msg)
        {
            printf("msg[0]:%x\n", msg[4]);
            std::cout << "length: " << commandMsg.length() << std::endl;

            //commandMsg[0] = commandMsg.length();
            appendChecksum();
        };

        Message(const unsigned char *msg, int msglen) : commandMsg(msg, msglen)
        {
            commandMsg[0] = commandMsg.length();
            appendChecksum();
        };

        const unsigned char *getCommandData() const
        {
            return commandMsg.c_str();
        }

        int getLen() const
        {
            return commandMsg.length();
        }


    protected:
    private:
        int appendChecksum();
        std::basic_string<unsigned char> commandMsg;
};

#endif // MESSAGE_H

Message.cpp

#include "Message.h"

Message::Message()
{
    //ctor
}

Message::~Message()
{
    //dtor
}

int Message::appendChecksum()
{
    int sum = -std::accumulate(commandMsg.begin(), commandMsg.end(), 0);

    commandMsg.push_back(0xFF & (sum >> 8));
    commandMsg.push_back(0xFF & sum);
}

DeviceRS232.h

#ifndef DEVICERS232_H
#define DEVICERS232_H

extern "C"
{
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdlib.h>
}

#include <string>

#define MAX_SERIAL_PORT_NO  30



class DeviceRS232
{
    public:
        DeviceRS232();
        virtual ~DeviceRS232();

        int fdRS232;                    //  file descriptor for the serial port

        void setSerialPort(std::string sp);
        void setBaudRate(long baud);
        void setDataBits(int dataBit);
        void setStopBits(int stopBit);
        void setNumberOfParityBits(int nparityBits);
        void setDefaultAttributes();
        long getBaudRate();
        std::string getSerialPort();
        int openSerialPort();
        int readUserConfiguration();
        int sendDataBuffer(const unsigned char *dataBuffer, size_t bufferSize);
        int receiveDataBuffer(unsigned char *dataBuffer, size_t bufferSize);
        void closeSerialPort();


    protected:
        std::string serialPort;         //  Serial port like /dev/ttyS0
        long baudRate;                  //  Scanner baud rate
        int dataBits;                   //  data bits
        int stopBits;                   //  stop bits
        int numberOfParityBits;         //  number of parity bits
        termios oldSerialPortSetting;   //  Current values of termios structure for /dev/ttyS0
        termios newSerialPortSetting;   //  new termios attributes for /dev/ttyS0


    private:
};

#endif // DEVICERS232_H

DeviceRS232.cpp

#include "DeviceRS232.h"

DeviceRS232::DeviceRS232()
{
    //ctor
}

DeviceRS232::~DeviceRS232()
{
    //dtor
}

void DeviceRS232::setSerialPort(std::string sp)
{
    serialPort = sp;
}

void DeviceRS232::setBaudRate(long baud)
{
    baudRate = baud;
}

void DeviceRS232::setDataBits(int dataBit)
{
    dataBits = dataBit;
}

void DeviceRS232::setStopBits(int stopBit)
{
    stopBits = stopBit;
}

void DeviceRS232::setNumberOfParityBits(int nparityBits)
{
    numberOfParityBits = nparityBits;
}

void DeviceRS232::setDefaultAttributes()
{
    std::string sp = "/dev/ttyS0";
    long baud = 9600;
    int dataBit = 1;
    int stopBit = 1;
    int nparityBits = 0;

    setSerialPort(sp);
    setBaudRate(baud);
    setDataBits(dataBit);
    setStopBits(stopBit);
    setNumberOfParityBits(nparityBits);
}

long DeviceRS232::getBaudRate()
{
    return baudRate;
}

std::string DeviceRS232::getSerialPort()
{
    return serialPort;
}

int DeviceRS232::openSerialPort()
{
    int fd, baudr, status, portStatus;
    setDefaultAttributes();

    switch(getBaudRate())
    {
        case      50 : baudr = B50;
                       break;
        case      75 : baudr = B75;
                       break;
        case     110 : baudr = B110;
                       break;
        case     134 : baudr = B134;
                       break;
        case     150 : baudr = B150;
                       break;
        case     200 : baudr = B200;
                       break;
        case     300 : baudr = B300;
                       break;
        case     600 : baudr = B600;
                       break;
        case    1200 : baudr = B1200;
                       break;
        case    1800 : baudr = B1800;
                       break;
        case    2400 : baudr = B2400;
                       break;
        case    4800 : baudr = B4800;
                       break;
        case    9600 : baudr = B9600;
                       break;
        case   19200 : baudr = B19200;
                       break;
        case   38400 : baudr = B38400;
                       break;
        case   57600 : baudr = B57600;
                       break;
        case  115200 : baudr = B115200;
                       break;
        case  230400 : baudr = B230400;
                       break;
        case  460800 : baudr = B460800;
                       break;
        case  500000 : baudr = B500000;
                       break;
        case  576000 : baudr = B576000;
                       break;
        case  921600 : baudr = B921600;
                       break;
        case 1000000 : baudr = B1000000;
                       break;
        default      : printf("invalid baudrate\n");
                       return(1);
                       break;
    }

    //  Open serial port
    fd = open(getSerialPort().c_str(),  O_RDWR | O_NOCTTY | O_NDELAY);
    if(fd == -1)
    {
        printf("Unable to open serial port...\n");
        perror(getSerialPort().c_str());
        return 1;
    }

    fdRS232 = fd;

    fcntl(fdRS232, F_SETFL, FNDELAY);
    status = tcgetattr(fdRS232, &oldSerialPortSetting);
    if(status == -1)
    {
        close(fdRS232);
        printf("Unable to get serial port attributes...\n");
        return 1;
    }

    memset(&newSerialPortSetting, 0, sizeof(newSerialPortSetting));
    newSerialPortSetting.c_cflag = baudr | CS8 | CLOCAL | CREAD; //
    newSerialPortSetting.c_iflag = IGNPAR;
    newSerialPortSetting.c_oflag = 0;
    newSerialPortSetting.c_lflag = 0;
    newSerialPortSetting.c_cc[VMIN] = 0;
    newSerialPortSetting.c_cc[VTIME] = 0;

    status = tcsetattr(fdRS232, TCSANOW, &newSerialPortSetting);
    if(status==-1)
    {
        close(fdRS232);
        perror("unable to adjust portsettings ");
        return 1;
    }

    //  Get the status of opened serial port
    if(ioctl(fdRS232, TIOCMGET, &portStatus) == -1)
    {
        perror("Unable to get port status");
        return 1;
    }

    //  Tern on DTR and RTS
    portStatus |= TIOCM_DTR;
    portStatus |= TIOCM_RTS;

    //  Set the status of the port with new DTR, RTS values
    if(ioctl(fdRS232, TIOCMSET, &portStatus) == -1)
    {
        perror("Unable to set port status...");
        return 1;
    }

  return 0;
}

int DeviceRS232::sendDataBuffer(const unsigned char *dataBuffer, size_t bufferSize)
{
    return write(fdRS232, dataBuffer, bufferSize);
}

int DeviceRS232::receiveDataBuffer(unsigned char *dataBuffer, size_t bufferSize)
{
    /**int recvSize = 0;
    recvSize = read(fdRS232, dataBuffer, bufferSize);
    return recvSize;*/

    unsigned char recvBuffer[255];
    unsigned char *ptrChar;
    int nBytes;

    ptrChar = recvBuffer;
    memset(recvBuffer, 0x00, sizeof(recvBuffer));
    while((nBytes = read(fdRS232, ptrChar, recvBuffer+sizeof(recvBuffer) - ptrChar -1)) > 0)
    {
        ptrChar += nBytes;
        //printf("while - %d\n", nBytes);
    }

    //printf("recvBuffer : %x\n", recvBuffer[0]);
    //printf("recvBuffer : %x\n", recvBuffer[1]);
    //printf("recvBuffer : %x\n", recvBuffer[2]);
    //printf("recvBuffer : %x\n", recvBuffer[3]);
    //printf("recvBuffer : %x\n", recvBuffer[4]);
    dataBuffer = recvBuffer;

    return nBytes;
}

void DeviceRS232::closeSerialPort()
{
    int portStatus;

    if(ioctl(fdRS232, TIOCMGET, &portStatus) == -1)
    {
        perror("Unable to get the port status");
    }

    //  Tern off DTR and RTS
    portStatus &= ~TIOCM_DTR;
    portStatus &= ~TIOCM_RTS;

    //  Set the status of the port with new DTR, RTS values
    if(ioctl(fdRS232, TIOCMSET, &portStatus) == -1)
    {
        perror("Unable to set port status...");
    }

    close(fdRS232);
}

我的错误方法是int DeviceRS232::receiveDataBuffer(unsigned char *dataBuffer, size_t bufferSize)

下面是控制台输出:

*********************************************************

---Start sending beep---
Data sent: 7
Date Received, Data:  Size: 0
---End sending beep-----

*********************************************************

Process returned 0 (0x0)   execution time : 2.004 s
Press ENTER to continue.

你正在向扫描仪发送垃圾数据,蜂鸣命令显然不可能长达257字节。很不清楚为什么你期望得到任何反馈,条形码扫描仪通常只在你按下扫描按钮时才会发送数据。 - Hans Passant
2
+1 表示正确提交格式化的代码。 - Jabberwocky
@HansPassant:实际上,这个条形码设备捆绑了一个串行通信协议。协议规范列出了所有命令(所有与硬件相关的命令、图像扫描命令等)。根据协议规范,在向设备发送命令后,它应该使用确认消息回复。因此,在上述情况下,在向设备发送BEEP命令后,主机期望得到肯定或否定的确认。我仍处于协议实现的起始阶段。首先,我正在尝试建立通信链接。 - Dig The Code
你似乎没有理解Hans的评论重点。当仅有一小部分缓冲区包含命令时,你不应该发送整个缓冲区(sizeof)。在sendBeep消息中添加一个终止nul,这样你就可以使用strlen而不是sizeof。 - ScottMcP-MVP
@ScottMcP-MVP:追加一个NUL是行不通的,因为数据中包含了NUL字符。 - Edward
显示剩余8条评论
2个回答

2
/**
 * Receive responses from the decoder
 */
int DeviceRS232::receiveDecodedData(unsigned char *dataBuffer, size_t bufferSize)
{
    unsigned char recvBuffer[251];
    unsigned char *ptrChar;
    int nBytes, portStatus;
    int inputBufSize = 0;

    ChangeCTS(fdRS232, 0);
    ChangeRTS(fdRS232, 0);

    while(inputBufSize <= 0)
    {
        ioctl(fdRS232, FIONREAD, &inputBufSize);
        usleep(1);
    }


    if(inputBufSize > 0)
    {
        int decodePacketLen = 0;
        //unsigned char
        memset(recvBuffer, 0x00, sizeof(recvBuffer));
        nBytes = 0;

        //usleep(100000);
        while(nBytes < ((int)recvBuffer[0] + 2))
        {
            int index = 0;
            int recvDataLen = 0;
            if(nBytes != 0)
                index = nBytes - 1;

            recvDataLen = read(fdRS232, &recvBuffer[index], 251);
            if(recvDataLen < 0)
            {
                std::cout << "[INFO@DeviceRS232::receiveDecodedData]File read error: " << strerror(errno) << std::endl;
                //sleep(1);
            }

            nBytes += recvDataLen;
            if(nBytes == ((int)recvBuffer[0] + 2))
                break;

        }

        if(recvBuffer[1] == DECODE_DATA)
            sendCommandToDecoder(OPCODE_ACK);

        std::cout << "[INFO @ DeviceRS232::receiveDecodedData]Data Lenght (without CheckSum) : " << (int)recvBuffer[0] << std::endl;

        for(int i=0; i<nBytes; i++)
        {
            std::cout << "recvBuffer[" << i << "]: ";
            printf("%x\n", recvBuffer[i]);
        }
        std::cout << "-----------------------------------" << std::endl;

        ChangeRTS(fdRS232, 1);
        ChangeCTS(fdRS232, 1);
        //sleep(1);
    }

    //strcpy((char *)dataBuffer, (char *)recvBuffer);
    memcpy((char *)dataBuffer, recvBuffer, sizeof(recvBuffer)/sizeof(recvBuffer[0]));
    inputBufSize = 0;

    return nBytes;

}


/**
 * Send commands to the decoder.
 */
int DeviceRS232::sendCommandToDecoder(unsigned int opCode)
{
    unsigned char *commandBuffer;
    int commandLength;

    switch(opCode)
    {
    case OPCODE_ACK:
        {
            unsigned char ackString[] = {0x00, 0xD0, 0x04, 0x00};
            commandLength = sizeof(ackString);
            commandBuffer = ackString;
        }
        break;
    case OPCODE_PARAM_SEND:
        {
            unsigned char paramSendString[] = {0x00, 0xC6, 0x04, 0x08, 0x00, 0xEE, 0x01};
            commandLength = sizeof(paramSendString);
            commandBuffer = paramSendString;
        }
        break;
    default:
        break;
    }

    Message msgCommand(commandBuffer, commandLength);

    return sendDataBuffer(msgCommand.getCommandData(), msgCommand.getLen());
}

在DeviceRS232.h头文件中定义了必需的常量。


2

正如其他人所指出的,一个可疑的地方是你发送的字节数。条形码阅读器可能只期望命令中的字节数而不是总是发送257个字节。

此外,你的代码有很多重复的操作,用于在消息结尾处计算校验和。这表明一个类会有助于简化设计。因此,下面是一个用于此目的的 Message 类:

#include <vector>
#include <numeric>
#include <string>

class Message 
{
public:
    Message(const std::basic_string<unsigned char> msg) : mymsg(msg) { 
        mymsg[0] = mymsg.length(); appendChecksum(); };
    Message(const unsigned char *msg, int msglen) : mymsg(msg, msglen) { 
        mymsg[0] = mymsg.length(); appendChecksum(); };
    const unsigned char *getData() const { return mymsg.c_str(); }
    size_t getLen() const { return mymsg.length(); }

private:
        int appendChecksum();
        std::basic_string<unsigned char> mymsg;
};


int Message::appendChecksum()
{
    int sum = -std::accumulate(mymsg.begin(), mymsg.end(), 0);
    mymsg.push_back(0xff & (sum >> 8));
    mymsg.push_back(0xff & sum);
    return sum;
}

现在,在您的main例程中,如果您使用C++11,您可以消除数十行代码,并改用以下内容:

    Message setparams{{0x00, 0xc6, 0x04, 0x08, 0x11, 0xee, 0x01}};
    Message beep{{0x00, 0xe6, 0x04, 0x00, 0x05}};
    Message getrevision{{0x00, 0xA3, 0x04, 0x00}};
    Message ledOn{{0x00, 0xe7, 0x04, 0x00, 0x0d, 0x00}};

如果您没有使用C++11(这将是一件遗憾的事情!),您可以使用稍微不那么干净的样式:

unsigned char parms[] = {0x00, 0xc6, 0x04, 0x08, 0x11, 0xee, 0x01};
Message setparams(parms,sizeof(parms)/sizeof(parms[0]));

请注意,第一个字节设置为零而不是长度。这是因为构造函数会在计算并附加校验和之前自动计算和设置该字节中的正确长度。当然,还有其他方法可以做到这一点,但我会留给您自己去尝试。
最后,使用您的循环,您现在可以使用以下行:
int sizeSent = dev_rs232.sendDataBuffer(beep.getData(), beep.getLen());

这可能或可能不会真正解决问题,但它将帮助您拥有一个更干净的程序起点。

此外,一些样式和设计建议:

  1. 摆脱使用 using namespace std 的习惯
  2. 使用 iostream 而不是 printf
  3. 在创建设备后不必立即调用 setDefaultAttributes(),而是让构造函数设置合理的默认值
  4. 消除“魔术数字”,例如接收缓冲区大小为 4096。改用命名的 static const。这将使程序更易于理解和维护。
  5. 考虑使用现有库,如 boost::asio,而不是自己编写

祝你好运!

编辑:基于您敏锐(且正确!)地观察到当给出普通的 unsigned char * 时,Message 构造函数无法正常工作,我添加了第二个构造函数并修改了代码的非 C++11 版本。对于任何不便,感到抱歉,并感谢您让我保持诚实。


1
好的发现!那是我的错误——我太习惯使用C++11了。我会立即更新答案并修复它。 - Edward
嗨 Edward:按照指示,我修改了代码,但我的原始问题仍然存在。程序无法从扫描仪接收数据。您能帮我验证一下我在这里使用的从扫描仪接收数据的方式吗?(是否正确) - Dig The Code
@user3458841 - 这种来回评论并不是评论的真正用途。我们应该将它转移到聊天室http://chat.stackoverflow.com/rooms/50406/serial-port-debugging。 - Edward
其实我很想这样做,但是我没有足够的声望去这样做。 - Dig The Code
@DigTheCode 不久之后你就会了解。同时,如果你正在Linux下编写软件,你可以使用strace命令来查看你的应用程序发送和接收的内容,例如:strace -s1024 -o barcode.strace -eread,write,ioctl ./barcode(假设barcode是你的应用程序名称,并且你只需要最多1K的字符串数据来进行故障排除)。或者,如果你有其他成功实现该协议的软件,你也可以使用相同的技术来“监听”并查看实际发生的情况。 - Edward
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接