使用 Win32 API 从串口读取的 ReadFile 代码非常慢

ReadFile code using Win32 API to read from serial port very slow

我正在尝试通过 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;
}

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

您正在使用额外的中间缓冲区(例如 single_val_buf)。 Better/easier 直接读入 m_buf 目标缓冲区(即不需要复制字节)。

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

旁注: goto 是 ugly/bad 风格,很容易重构。


这是一些重构代码。注释

我使用 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;
}