在 C++ 中撰寫 rs232 程式 [論壇 - Ubuntu 程式設計]


正在瀏覽:   1 名遊客


 到底部   前一個主題   下一個主題  [無發表權] 請登錄或者註冊



在 C++ 中撰寫 rs232 程式
會員一級
註冊日期:
2013/9/12 9:18
所屬群組:
已註冊使用者
等級: 2
HP : 0 / 46
MP : 5 / 628
EXP: 86
離線
請教各位前輩,
我想要在 linux 中用 c++ 撰寫程式,用 rs232 跟裝置通訊(9600, 8N1),該裝置在 windows 下測試是沒問題的,但在 linux 中就會發生資料收不完整,或是收太多的情況。

每次發送資料的長度不一定,但每筆會有一點時間差(windows 下設約 20ms 就夠了),目前發送的資料大概是 11 bytes, 109 bytes, 11 bytes, 109 bytes...

以下是我的程式:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>

int main()
{
    int fd;
    struct termios options;

    fd = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY);
    if(fd == -1)
    {
        perror("open_port: Unable to open /dev/ttyAMA0");
        return 1;
    }
  
    tcgetattr(fd, &options);

    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);

    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    options.c_cflag &= ~CRTSCTS;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_iflag &= ~INPCK;
    options.c_iflag &= ~(IXON | IXOFF | IXANY);
    options.c_oflag &= ~OPOST;

    options.c_cc[VMIN] = 200;
    options.c_cc[VTIME] = 1;    // time base 0.1s

    tcsetattr(fd, TCSANOW, &options);

    char buf[255];
    int res;

    int maxfd = fd + 1;

    struct timeval timeout = {1, 20000};    //20 ms
    fd_set readSet;

    while(1)
    {
        FD_ZERO(&readSet);
        FD_SET(fd, &readSet);

        if(select(maxfd, &readSet, NULL, NULL, &timeout) > 0)
        {
            if (FD_ISSET(fd, &readSet))
            {
                res = read(fd, buf, sizeof(buf));
                if(res > 0)
                {
                    //Bytes received
                    printf("read %d bytes:\n", res);
                    for(int i=0; i<res; i++)
                        printf("0x%02X, ", buf[i]);
                    printf("\n");
                    printf("--------------------------------\n");
                }
            }
        }
    }

    close(fd);
    return 0;
}

還請各位前輩指導還有什麼地方可以改進的,謝謝

2015/3/20 22:59
應用擴展 工具箱
回覆: 在 C++ 中撰寫 rs232 程式
會員二級
註冊日期:
2013/11/7 9:24
所屬群組:
已註冊使用者
等級: 8
HP : 0 / 182
MP : 29 / 2381
EXP: 29
離線
分成兩個部份:
1/RS232 initial
以下為可正常動作的initial:(宣告部份未列出,主要供你比對差異)
if (0 > (miHandle = open(ptParam->caDeviceName, O_RDWR))) {
D_PRINT("open device fail-%s-[%d]", ptParam->caDeviceName, miHandle);SHOW_LAST_ERR(1001);
return -ERR_OPEN_FILE;
}

bzero(&termOptions, sizeof(termOptions));
// Get the current options:
tcgetattr(miHandle, &save_term);
termOptions = save_term;
termOptions.c_cflag &= ~CSIZE;
// 8bit
termOptions.c_cflag |= ptParam->tDataBit; //CS8;
// N
termOptions.c_cflag &= ptParam->tParityCheck; //~PARENB; /* Clear parity enable */
termOptions.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
termOptions.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
// stop 1
termOptions.c_cflag &= ptParam->tStopBit; //~CSTOPB;

termOptions.c_oflag = 0;
termOptions.c_oflag &= ~OPOST;
termOptions.c_cc[VTIME] = 0;
termOptions.c_cc[VMIN] = 0;
/*Baud Rate Setup for Both Input and Output*/
cfsetispeed(&termOptions, ptParam->tBaudRate);
cfsetospeed(&termOptions, ptParam->tBaudRate);

// Now set the term options (set immediately)
if (0 > tcsetattr(miHandle, TCSANOW, &termOptions)) {
D_PRINT("Setup UART fail.\n");SHOW_LAST_ERR(1002);
return -ERR_UART_SETUP;
}
tcflush(miHandle, TCIOFLUSH);

2/接收資料
這一行 printf("0x%02X, ", cBuf[i]);
中的cBuf,沒有看到宣告,是否筆誤還是除錯的程式碼真的有錯,但是接收是對的?

基本上就算用115200 bps傳送,也不會有有20ms這樣的限制,你的程程中timeout = {1, 20000};這個東西只是在20ms內沒有讀到資料會返回,並不是20ms讀一次。除非你的主程式忙其它的事沒去讀,導致input buffer真的被塞爆了,不然應該沒有這樣的機會。

2015/3/23 18:55
應用擴展 工具箱
回覆: 在 C++ 中撰寫 rs232 程式
會員二級
註冊日期:
2013/11/7 9:24
所屬群組:
已註冊使用者
等級: 8
HP : 0 / 182
MP : 29 / 2381
EXP: 29
離線
忘了提
資料傳送/接收的概念並不是全部傳完,再全部接收,因為你永遠不會知道要多久可以傳得完,收得完,所以用delay的方式是錯的。

正確的作法是在資料封包中加入封包頭/尾/長度/checksum等結構,才能依不同長度進行接收,不必每收完一筆才能再傳下一筆,也不需要一次就要全部收完。透過合理的封包定義,可以很有彈性的分次收,再加以組合成完整封包即可。

不知這樣的說明,你是不是能了解。

2015/3/23 21:24
應用擴展 工具箱
回覆: 在 C++ 中撰寫 rs232 程式
會員一級
註冊日期:
2013/9/12 9:18
所屬群組:
已註冊使用者
等級: 2
HP : 0 / 46
MP : 5 / 628
EXP: 86
離線
感謝您的回覆!!

1. 關於 cBuf 的問題的確是貼的時候筆誤,已修正

2. 您貼的程式似乎是終端機用的程式?我的裝置是純粹丟資料而已

3. 關於 20ms 的部份,因為在 windows 上寫程式時,並不需要特別去設定什麼參數,只要把 timeout 設好,不用特別寫程式,抓進來的資料就會是正確的一筆一筆的樣子。我想應該是利用每筆資料傳送時有個時間差,來判斷什麼時候是一筆完整的資料(這邊假設傳輸都沒問題,資料沒有遺失的情況下,如果有遺失,那的確是要將資料全部收下來,再根據封包結構去檢查、切割資料)

所以我想說既然 windows 可以,linux 應該也可以才是,不過還沒試出來...

想用 c_cc[VTIME] 去控制,但這個最小單位是 100ms,似乎無法再小了

2015/3/24 22:35
應用擴展 工具箱
回覆: 在 C++ 中撰寫 rs232 程式
會員二級
註冊日期:
2013/11/7 9:24
所屬群組:
已註冊使用者
等級: 8
HP : 0 / 182
MP : 29 / 2381
EXP: 29
離線
2/ 您貼的程式似乎是終端機用的程式?我的裝置是純粹丟資料而已
-> 坦白說,也沒有所謂的終端機或其它,純粹就是一個可以收、發的小程式,我拿它來對MCU、Linux、Windows都是一樣的東西。

3/ 由於永遠不會知道資料長度變動的情況是如何,所以用delay真的不是好的方法。由你的程式來,你每20ms收下來的資料可能不夠長,那只是因為在那當下資料還沒送完,下一次再讀,就可以把剩下的讀回來,最終再把封包組合分析即可。你只提到分析封包,但是沒有考慮到組合封包,在兩邊非同步的情況下,很難保證每次讀都是完整的內容。
舉個例來說,A送B收,若先啟動B,再執行A,我想你的夢想可能有機會成真,但只是有可能;若今天先啟動A並開始送,在執行B,那結果如何?你根本不會有完整資料的一天,因為你無從分辨那個地方開始,那個地方結束。甚至只要任何一個地方發生干擾,少了1Byte或多了1Byte,接下來你所有的內容就不可能會是正確的了,因為你找不到下一個開始點在什麼地方?不知這樣是否對你有幫助!

2015/3/25 13:05
應用擴展 工具箱
回覆: 在 C++ 中撰寫 rs232 程式
會員一級
註冊日期:
2013/9/12 9:18
所屬群組:
已註冊使用者
等級: 2
HP : 0 / 46
MP : 5 / 628
EXP: 86
離線
嗯,您說的也是有道理,那我瞭解了,會朝這方面去試試的,謝謝您撥空回覆!!

2015/3/25 21:58
應用擴展 工具箱
回覆: 在 C++ 中撰寫 rs232 程式
會員二級
註冊日期:
2016/4/18 12:29
所屬群組:
已註冊使用者
等級: 8
HP : 0 / 189
MP : 30 / 1119
EXP: 57
離線
偶然看到這個主題
請問裝置為什麼是AMA,一般不是ttyS0嗎?
fd = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY);

謝謝

3/20 16:40:19
應用擴展 工具箱
回覆: 在 C++ 中撰寫 rs232 程式
會員二級
註冊日期:
2013/11/7 9:24
所屬群組:
已註冊使用者
等級: 8
HP : 0 / 182
MP : 29 / 2381
EXP: 29
離線
ubuntu_net2016 寫到:
偶然看到這個主題
請問裝置為什麼是AMA,一般不是ttyS0嗎?
fd = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY);

謝謝


這個名稱是由driver決定的,不是固定的。簡單的說,該設備的driver中會指定此設備的名稱,而ttySX是內鍵driver的名稱,其中的X可以看作是流水號。

3/22 9:34:41
應用擴展 工具箱


 [無發表權] 請登錄或者註冊


可以查看帖子.
不可發帖.
不可回覆.
不可編輯自己的帖子.
不可刪除自己的帖子.
不可發起投票調查.
不可在投票調查中投票.
不可上傳附件.
不可不經審核直接發帖.