【问题标题】:Serial programming RS485串口编程 RS485
【发布时间】:2012-09-21 13:11:50
【问题描述】:

我的任务是通过 RS485 2 线系统实现 ModBus 协议。 (实际上是三根线,A/B 和 GND)。 ModBus 不是重点,但在此之前的步骤...通过接口进行简单的 I/O。

我正在使用 FTDI USB-RS485 转换器将 Linux 主机(不可互换)连接到 Windows 主机(可与另一台 Linux 主机互换,但我想避免这种情况)

编码应该是 19200, 8, n, 1。 但这似乎不起作用。

我手头没有确切的代码,但在 Linux 上我正在这样做:

 int fd = open("/dev/ttyS3", O_RDWR | O_CTTY);
 if(fd == -1) return "Error while opening the port";

接下来,我配置端口。

struct termios tty;

tcgetattr(fd, &tty);

cfsetispeed(&tty, B19200);
cfsetospeed(&tty, B19200);

tty.c_cflag  = CS8;              //Empties the cflags and sets the character width.
tty.c_cflag |= (CLOCAL | CREAD); //Sets 'recommended' options.

tty.c_lflag  = 0;
tty.c_iflag  = 0;
tty.c_oflag  = 0;

tcgetattr(fd, TCSANOW, &tty);

目前尚未计划奇偶校验和流量控制,因为最终结果将连接到低级电路板,我需要自己处理信号。此外,没有任何电线可以实现“不受限制的通信”。 (毕竟我不希望 XON/XOFF 字符限制我可以传输的字节范围)

所有这些功能都正确执行并设置了数据。

在 Windows 上,我这样打开串口:

DCB SP;
HANDLE hSerial = CreateFile("COM6", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if(hSerial == INVALID_HANDLE_VALUE) return "Error while opening the port";
GetCommState(hSerial, &SP);

奇偶校验和流量控制被禁用。字节大小设置为 8。

编辑: 既然有人问过,这是我在 Windows 上的波特率代码(来自内存) SP.DCBlength=sizeof(SP); SP.BaudRate = 19200; SP.奇偶校验 = 无奇偶校验; SP.StopBits = ONESTOPBIT; SetCommState(hSerial, &SP);

同样,所有这些功能都可以完美运行。

现在,对于让我头疼的测试用例。

在 Linux 主机上,我创建了一个 256 字节大小的字节缓冲区。 这个缓冲区填充了从 0 到 255 的字符值......然后通过网络通过写入发送。 同时,对方正在用'ReadFile'等待数据到达。

使用此配置,对于“其他 Linux 主机”以及 Windows 主机,256 字节到达...但是它不是从 0 到 255 的数字,而是 00 06 等。

当我在设置我真正想要的选项之前将 termios 结构的所有成员设置为 0 时,我可以让 Linuxhost 工作。我猜,这是因为控制字符...但是如果我这样做,Windows 主机要么只接收 256 个字节中的 4 个。

正如我所说,不幸的是我手头没有代码。如果有人知道我可以从什么角度解决这个问题,我将不胜感激。一旦我再次访问它,我将发布更多代码。

我是如何实现读取操作的:

DWORD nBytes = 0;
char Buffer[256], *ptr = Buffer;
int Rem = 256;

while(Rem) {
    ReadFile(hSerial, ptr, Rem, &nBytes, 0);
    Rem -= nBytes;
    ptr += nBytes;
}

//Evaluate Buffer

需要注意的是,我确实设置了超时,但不记得确切的值。

编辑:由于我现在可以再次访问我的工作地点,这里是实际(当前)代码。

const char *InitCOM(const char *TTY) {
    struct termios tty;
    hSerial = open(TTY, O_RDWR | O_NOCTTY | O_NDELAY);
    if(hSerial == -1) return "Opening of the port failed";
    fcntl(hSerial, F_SETFL, 0);
    if(tcgetattr(hSerial, &tty) != 0) return "Getting the parameters failed.";
    if(cfsetispeed(&tty, B19200) != 0 || cfsetospeed(&tty, B19200) != 0) return "Setting the baud rate failed.";


    //CFlags
    //Note: I am full aware, that there's an '=', and that it makes the '&=' obsolete, but they're in there for the sake of completeness.
    tty.c_cflag  = (tty.c_cflag & ~CSIZE) | CS8;    //8-bit characters
    tty.c_cflag |= (CLOCAL | CREAD);und erlaubt 'Lesen'.
    tty.c_cflag &= ~(PARENB | PARODD);          
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;                    

    //Input Flags
    tty.c_iflag     &= ~IGNBRK;             
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);         

    //Local Flags
    tty.c_lflag  = 0;                   

    //Output Flags
    tty.c_oflag  = 0;

    //Control-Characters
    tty.c_cc[VMIN]   = 0;
    tty.c_cc[VTIME]  = 5;
    if(tcsetattr(hSerial, TCSAFLUSH, &tty) != 0) return "Setting the new parameters failed";
return NULL;

}

至于实际发送/接收代码:

int main(int argc, char* argv[]) {
    #if defined FOR_PC
        const char *err = InitCOM("/dev/ttyUSB0");
    #else
        const char *err = InitCOM("/dev/ttyS3");
    #endif

    if(err) printf("Error while initalizing: %s ErrNum: %d\n", err, errno);
    else {
    /*unsigned char C[256];    //Original code with the array
    int nBytes;
    #ifdef FOR_PC
        int Rem = 256, ReqCount = 0;
        unsigned char *ptr = C;
        while(Rem > 0) {    
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(hSerial, &fds);
            select(hSerial+1, &fds, NULL, NULL, NULL);
            nBytes = read(hSerial, ptr, Rem);
            if(nBytes > 0) {
                Rem -= nBytes;
                ptr += nBytes;
                ++ReqCount;
            }
        }
        printf("Number of received Bytes: %d in %d sends.\n\n", 256 - Rem, ReqCount);
        for(int i = 0; i < 256; ++i) {
            printf("%02X ", C[i]);
            if((i%32) == 31) printf("\n");
        }
    #else
        for(int i = 0; i < 256; ++i) C[i] = i;
        nBytes = write(hSerial, C, 256);
        printf("\nWritten Bytes: %d\n", nBytes);
    #endif*/

    //Single-Byte Code
    unsigned char C = 0x55;
    #ifdef FOR_PC
        while(true) {   //Keeps listening
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(hSerial, &fds);
            select(hSerial+1, &fds, NULL, NULL, NULL);
            read(hSerial, &C, 1);
            printf("Received value 0x%02X\n", C);
        }
    #else
        write(hSerial, &C, 1);  //Sends one byte
    #endif
    close(hSerial);
}
return 0;

}

至于示波器:我已经测试了发送的两个方向。两人的工作都非常出色。

0x55 的信号是一个 50 微秒长度的常数 Up/Down(应该如此,所以设置波特率也没有问题)。

那么我的“接收”代码中有什么我做错了吗? “选择”错了吗?

【问题讨论】:

  • 按照 unwind 的建议,检查波特率配置。
  • 请贴出设置波特率和调用SetCommState的Windows代码。
  • '4' 在 32 位系统上始终是一个可疑数字,(sizeof(char*))。
  • 您可以尝试一些事情:添加代码SP.DCBlength = sizeof(DCB);。还可以尝试SP.fParity = 0;,它完全禁用奇偶校验。 SP.ByteSize = 8; 用于 8 个数据位。
  • 问题是,ReadFile() 有一个令人讨厌的提前返回的倾向,因为没有读取到你希望的那么多字节。这就是它具有“lpNumberOfBytesRead”参数的原因,因此您可以了解这次它读取了多少缓冲区,并在必要时发出另一个 ReadFile() 调用。

标签: c++ c serial-communication baud-rate rs485


【解决方案1】:
  1. 您是否也在 Windows 端设置了正确的波特率?
  2. 使用示波器检查电线上的实际数据。调试串行通信是示波器的发明目的。几乎。 :)

【讨论】:

  • 如果您没有示波器,请使用 Windows 中的超级终端应用程序。它可以工作并且易于设置。也许您需要改用 rs232,但这将帮助您解决电缆和连接器上的问题。
  • 是的,我在 Windows 上设置波特率。我也很确定,电缆可以正常工作(否则在设置波特率之前将两个 termios 都设置为 0 的 Linux 示例将无法正常工作,不是吗?)不幸的是,我没有可用的 RS232。跨度>
  • @ATaylor 在进行诸如此类的硬件相关编程时,您确实需要使用示波器。因为现在你不知道是Linux端,Windows端还是硬件造成了麻烦。如果您可以在离开 Linux 机器时以正确的波特率测量正确的 UART 数据信号,那么问题出在接收器上,否则问题出在发送器或硬件上。你不能假设 Linux 版本是正确的,因为它可以“自言自语”,因为你可以将波特率和数据格式设置为任何值,它仍然可以工作。
  • @Lundin 是的,我将在星期一首先尝试示波器......我不是硬件专家,这就是为什么我在这个问题上遇到这么多麻烦的原因。跨度>
  • @ATaylor 我也不是,我认为自己是 99% 的软件专家。但我仍然认为示波器是嵌入式编程的必备工具。
【解决方案2】:

您的阅读功能很容易爆炸。如果您接近缓冲区的末尾,并且您读取的内容超过了填充它的数量,您将复制超出缓冲区的末尾,覆盖堆栈。

在 Linux 发送端,您应该查看“原始模式”,例如 cfmakeraw()。这样你就不会被系统“帮助”你所困扰(比如在你发送换行符时添加 CR——真的搞砸了二进制数据......)。微软有办法做到这一点,但我忘记了。

【讨论】:

    【解决方案3】:

    在 Windows 端,您是否在 DCB 结构中设置了 DCBLength 字段?

    dcb.DCBlength = sizeof(dcb);
    

    【讨论】:

    • 虽然我很欣赏您的意见,但这个问题早已得到解答。事实证明,如果我没记错的话,Linux 嵌入式设备的制造商从一开始就给了我错误的配置信息。
    • 啊好吧,我告诉你设置 DCBlength 只是因为在 Windows 上的串行 I/O 中这是一个非常常见的陷阱 :) 我把 DCB 配置结构搞砸了无数个小时......
    猜你喜欢
    • 1970-01-01
    • 2019-05-09
    • 2023-01-05
    • 1970-01-01
    • 1970-01-01
    • 2011-07-23
    • 1970-01-01
    • 2017-08-13
    • 2010-11-30
    相关资源
    最近更新 更多