使用 Win32 API 从串口读取的 ReadFile 代码非常慢
ReadFile code using Win32 API to read from serial port very slow
我正在尝试通过 PC 上的 COM 端口从 nRF52832 芯片读取 2048 个样本。我已经使用 UART 转 USB 电缆将 nRF 芯片的 UART 引脚连接到计算机。我以前能够以 921600 的波特率从它 printf
和 NRF_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;
}
我正在尝试通过 PC 上的 COM 端口从 nRF52832 芯片读取 2048 个样本。我已经使用 UART 转 USB 电缆将 nRF 芯片的 UART 引脚连接到计算机。我以前能够以 921600 的波特率从它 printf
和 NRF_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;
}