【问题标题】:ReadFile code using Win32 API to read from serial port very slowReadFile 代码使用 Win32 API 从串口读取非常慢
【发布时间】:2021-10-07 23:16:33
【问题描述】:

我正在尝试通过我 PC 上的 COM 端口从 nRF52832 芯片读取 2048 个样本。我已经使用 UART 转 USB 电缆将 nRF 芯片的 UART 引脚连接到计算机。之前我已经能够以 921600 的波特率从中获取 printfNRF_LOG_INFO。现在我想对数据进行实时处理,我需要自己读取它。但是当我运行附加代码(见下文)时,读取这些示例需要 15 秒以上,而这应该在大约 1 秒内完成。

关于如何使这段代码运行得更快的任何想法?

(实时方面稍后会添加,现在我的目标是让程序读取速度足够快)

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <stdint.h>
#include <time.h>

#define SUCCESS 0
#define BUF_SIZE 2048


char* hComm_init_error_modes[7] = {"",             //NOT INTENDED FOR USE
                                   "",             //NOT INTENDED FOR USE
                                  "ERROR OPENING SERIAL PORT\n",
                                  "ERROR RETRIEVING COMM STATE\n",
                                  "ERROR CONFIGURING COMM STATE\n",
                                  "ERROR RETRIEVING COMM TIMEOUTS\n",
                                  "ERROR CONFIGURING COMM TIMEOUTS\n",};



HANDLE hComm;

uint8_t m_buf[BUF_SIZE];
uint8_t single_val_buf[1];
//int m_buf_full = 0;

int hComm_init(char* port){

    hComm = CreateFileA(port,                           //PORT NAME
                        GENERIC_READ | GENERIC_WRITE,   //READ/WRITE
                        0,                              //NO SHARING
                        NULL,                           //NO SECURITY
                        OPEN_EXISTING,                  //OPEN EXISTING PORT ONLY
                        0,                              //NON OVERLAPPED I/O
                        NULL);                          //NULL FOR COMM DEVICES


    if (hComm == INVALID_HANDLE_VALUE){
        return 2;
    }

    DCB commStateConfig;

    if (!GetCommState(hComm, &commStateConfig)){
        return 3;
    }

    commStateConfig.BaudRate = 921600;
    commStateConfig.ByteSize = 8;

    if (!SetCommState(hComm, &commStateConfig)){
        return 4;
    }

    COMMTIMEOUTS comm_timeouts;

    if (!GetCommTimeouts(hComm, &comm_timeouts)){
        return 5;
    }

    comm_timeouts.ReadIntervalTimeout = 0;
    comm_timeouts.ReadTotalTimeoutMultiplier = 0;
    comm_timeouts.ReadTotalTimeoutConstant = 1;
    comm_timeouts.WriteTotalTimeoutMultiplier = 0;
    comm_timeouts.WriteTotalTimeoutConstant = 0;

    if (!SetCommTimeouts(hComm, &comm_timeouts)){
        return 6;
    }

    return 0;
}



int main(int argc, char* argv[]){
    if (argc != 2){
        printf("\nWrong number of inputs! Please provide the com port number and nothing else.\n");
        return 1;
    }
    char portname[11+strlen(argv[1])];
    strcpy(portname, "\\\\.\\COM");
    strcat(portname, argv[1]);

    int err = hComm_init(portname);

    if (err != SUCCESS){
        printf("%s", hComm_init_error_modes[err]);
        goto RUNTIME_ERROR;
    }

    printf("OPENED AND CONFIGURED SERIAL PORT SUCCESSFULLY\n");


    if (!SetCommMask(hComm, EV_RXCHAR | EV_ERR)){
        printf("SetCommMask failed with error code: %ld\n", GetLastError());
        goto RUNTIME_ERROR;
    }

    DWORD dwEvtMask;
    DWORD read;
    int readcount = 0;


    while (1){
        read = 0;
        dwEvtMask = 0;

        if(kbhit()) //Check for key press
        {
            if(27 == getch()) // ESC pressed
            {
                printf("Key ESC pressed, exiting...\n");
                goto CLEAN_EXIT;
            }
        }


        if (WaitCommEvent(hComm, &dwEvtMask, NULL)) 
        {   

            if (dwEvtMask & EV_ERR) 
            {
                printf("Wait failed with error %ld.\n", GetLastError());
                goto RUNTIME_ERROR;
            }


            if (dwEvtMask & EV_RXCHAR) 
            {   
                if(!ReadFile(hComm, single_val_buf, 1, &read, NULL)){
                    printf("\n\nERROR when reading\n\n");
                }
                m_buf[readcount] = *single_val_buf;
                readcount++;
            }

            if (readcount == BUF_SIZE){
                double diff_ms = (clock()-start) * 1000. / CLOCKS_PER_SEC;
                print_m_buf(); //for testing only, remove later
                printf("Time spent reading: %f ms\n", diff_ms);
                goto CLEAN_EXIT;
            }
        } else {
            DWORD dwRet = GetLastError();
            if( ERROR_IO_PENDING == dwRet)
            {
                printf("I/O is pending...\n");

                // To do.
            }
            else 
                printf("Wait failed with error %ld.\n", GetLastError());
        }
    }


CLEAN_EXIT:
    CloseHandle(hComm);
    return 0;

RUNTIME_ERROR:
    printf("Runtime error, program exited.\n");
    CloseHandle(hComm);
    return -1;
}

【问题讨论】:

  • 您正在为每个字节读取(一次一个字节)进行操作系统调用并检查键输入。这是一个很大的自重。您可以读取尽可能多的字符,并在每 1024 个循环中检查按键输入吗?
  • 或者在单独的线程中处理按键输入和端口输入
  • 与其从头试错,不如根据这个示例程序进行修改。 bmo/mttty

标签: c winapi serial-port readfile


【解决方案1】:

代码在每次迭代时读取一个单个字节。这非常很慢。

您正在使用额外的中间缓冲区(例如single_val_buf)。更好/更容易直接读入m_buf 目标缓冲区(即无需复制字节)。

代码还会在每次迭代时轮询键盘。重要的部分是跟上数据,过多的键盘轮询会减慢速度。最好定期轮询键盘(例如,每隔这么多次迭代)。

旁注: goto 是丑陋/糟糕的风格,很容易重构。


这是一些重构的代码。有注释

我使用cpp 条件来表示旧代码和新代码:

#if 0
// old code
#else
// new code
#endif

不管怎样,代码如下:

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <stdint.h>
#include <time.h>

#define SUCCESS 0
#define BUF_SIZE 2048

char *hComm_init_error_modes[7] = {
    "", // NOT INTENDED FOR USE
    "", // NOT INTENDED FOR USE
    "ERROR OPENING SERIAL PORT\n",
    "ERROR RETRIEVING COMM STATE\n",
    "ERROR CONFIGURING COMM STATE\n",
    "ERROR RETRIEVING COMM TIMEOUTS\n",
    "ERROR CONFIGURING COMM TIMEOUTS\n",
};

HANDLE hComm;

uint8_t m_buf[BUF_SIZE];
// NOTE/BUG: not needed -- we can read directly into m_buf
#if 0
uint8_t single_val_buf[1];
#endif

//int m_buf_full = 0;

int
hComm_init(char *port)
{

    hComm = CreateFileA(port,           // PORT NAME
        GENERIC_READ | GENERIC_WRITE,   // READ/WRITE
        0,                              // NO SHARING
        NULL,                           // NO SECURITY
        OPEN_EXISTING,                  // OPEN EXISTING PORT ONLY
        0,                              // NON OVERLAPPED I/O
        NULL);                          // NULL FOR COMM DEVICES

    if (hComm == INVALID_HANDLE_VALUE) {
        return 2;
    }

    DCB commStateConfig;

    if (!GetCommState(hComm, &commStateConfig)) {
        return 3;
    }

    commStateConfig.BaudRate = 921600;
    commStateConfig.ByteSize = 8;

    if (!SetCommState(hComm, &commStateConfig)) {
        return 4;
    }

    COMMTIMEOUTS comm_timeouts;

    if (!GetCommTimeouts(hComm, &comm_timeouts)) {
        return 5;
    }

    comm_timeouts.ReadIntervalTimeout = 0;
    comm_timeouts.ReadTotalTimeoutMultiplier = 0;
    comm_timeouts.ReadTotalTimeoutConstant = 1;
    comm_timeouts.WriteTotalTimeoutMultiplier = 0;
    comm_timeouts.WriteTotalTimeoutConstant = 0;

    if (!SetCommTimeouts(hComm, &comm_timeouts)) {
        return 6;
    }

    return 0;
}

int
main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("\nWrong number of inputs!"
            " Please provide the com port number and nothing else.\n");
        return 1;
    }
    char portname[11 + strlen(argv[1])];

    strcpy(portname, "\\\\.\\COM");
    strcat(portname, argv[1]);

    int err = hComm_init(portname);

    if (err != SUCCESS) {
        printf("%s", hComm_init_error_modes[err]);
        goto RUNTIME_ERROR;
    }

    printf("OPENED AND CONFIGURED SERIAL PORT SUCCESSFULLY\n");

    if (!SetCommMask(hComm, EV_RXCHAR | EV_ERR)) {
        printf("SetCommMask failed with error code: %ld\n", GetLastError());
        goto RUNTIME_ERROR;
    }

    DWORD dwEvtMask;
    DWORD read;
    int readcount = 0;

#if 1
    int ret = 0;
    unsigned int iter = 0;
#endif

    while (1) {
        ++iter;
        read = 0;
        dwEvtMask = 0;

        // Check for key press
        if (((iter % 1024) == 1) && kbhit()) {
            // ESC pressed
            if (27 == getch()) {
                printf("Key ESC pressed, exiting...\n");
                break;
            }
        }

        if (WaitCommEvent(hComm, &dwEvtMask, NULL)) {
            if (dwEvtMask & EV_ERR) {
                printf("Wait failed with error %ld.\n", GetLastError());
                ret = -1;
                break;
            }

// NOTE/BUG: this only reads _one_ char at a time and uses an extra buffer
#if 0
            if (dwEvtMask & EV_RXCHAR) {
                if (!ReadFile(hComm, single_val_buf, 1, &read, NULL)) {
                    printf("\n\nERROR when reading\n\n");
                }
                m_buf[readcount] = *single_val_buf;
                readcount++;
            }
#else
// NOTE/FIX: read many bytes at once -- put them directly into the final buffer
            if (dwEvtMask & EV_RXCHAR) {
                if (! ReadFile(hComm,
                    &m_buf[readcount], sizeof(m_buf) - readcount,
                    &read, NULL)) {
                    printf("\n\nERROR when reading\n\n");
                    ret = -2;
                    break;
                }

                // increase total accumulated count
                readcount += read;

                // a nicety: force immediate repoll of keyboard
                iter = 0;
            }
#endif

// NOTE/BUG: better to use ">=" rather than "=="
#if 0
            if (readcount == BUF_SIZE) {
#else
            if (readcount >= BUF_SIZE) {
#endif
                double diff_ms = (clock() - start) * 1000. / CLOCKS_PER_SEC;

                print_m_buf();          // for testing only, remove later
                printf("Time spent reading: %f ms\n", diff_ms);
                break;
            }
        }

        // handle WaitCommEvent error?
        else {
            DWORD dwRet = GetLastError();
            if (ERROR_IO_PENDING == dwRet) {
                printf("I/O is pending...\n");
                // To do.
            }
            else {
// NOTE: no need to call GetLastError twice
#if 0
                printf("Wait failed with error %ld.\n", GetLastError());
#else
                printf("Wait failed with error %ld.\n", dwRet);
#endif
            }
        }
    }

    if (ret < 0)
        printf("Runtime error, program exited.\n");
    CloseHandle(hComm);

    return ret;
}

【讨论】:

  • 非常感谢!这就像一个魅力:D
  • 不客气。请考虑投票并接受答案。见:stackoverflow.com/help/someone-answers
  • 你知道,我很惊讶现代 PC 无法以 96kbps 的速度每次系统调用读取 1 个字节的串行端口。
  • @Joshua 如果我没看错问题,波特率为 921600,即约 92,000 字节/秒(即快 10 倍)。因此,这将是 92,000 个系统调用。这意味着每个系统调用必须在 10.8 us 或更短的时间内执行。而且,这些系统调用中的一些会出现 0,因此压力会更大。因此,系统调用开销似乎超过 10.8 us [在 windows 下]。另外,键盘检查可能涉及通过 USB 来轮询它的往返消息,因此它可能会减少 kbhit 调用。
  • @CraigEstey:键盘检查不会往返于键盘,波特率与每秒位数(而不是字节)大致相同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-30
  • 1970-01-01
  • 1970-01-01
  • 2018-10-27
  • 1970-01-01
  • 2016-11-09
相关资源
最近更新 更多