前情提要:由于嵌入式模块需要使用WIFI进行数据交互通信,因此尝试接入亿佰特的E103-W01 WIFI模块编写代码进行串口通信测试,中间遇到了一些问题,于此进行记录。
测试环境:E103-W01模块或E103-W01-IPX模块(可以理解成是一样的模块,区别只是有没有陶瓷天线)、E103-W01测试底板、带有WIFI功能电脑、路由器或带有热点功能的手机。
开发环境:Windows平台、QT+MSVC2019。
将串口通讯的底层逻辑封装成了SerialInterface类(参考文章https://blog.csdn.net/LX520143/article/details/106448817,并在原作基础上做了部分改进),代码提供在文章末尾。
问题一:
串口回复数据乱码
解决方案:
排查串口的打开方式是否正确,使用串口工具打开串口并进行数据交互,查看是否存在乱码,进而比较自己的串口打开方式与串口工具是否不同。
使用XCOM工具打开串口发现能正常交互,因此与自己的代码中串口的打开方式进行比较,发现是代码中奇偶校验方式错误,应当由ODDPARITY修改为NOPARITY
修正后乱码消失,能够正常进行交互。
问题二:
第一次串口通讯收到的回复总是失败,此后的每次消息收到的回复总是上一次交互的回复。
解决方案:
遇到该状况应该首先考虑通信的延时问题,是否是因为接收函数运行过快,总是未能等到WIFI模块返回处理结果就提前返回,导致了第一次交互无法接收到数据,此后的交互总是从数据交互的缓存中取到上次通讯返回的结果。
因此考虑为串口通讯设置延迟,参考如下代码:
其中com是SerialInterface类,是自定义的串口通讯类(下文代码中会提供);COMMTIMEOUTS是windows延迟设定用数据结构。同时需要注意,超时的设定需要在串口建立连接之后,否则会无法生效。
COMMTIMEOUTS TimeOuts;
// 设定读超时
TimeOuts.ReadIntervalTimeout = 100;
TimeOuts.ReadTotalTimeoutMultiplier = 100;
TimeOuts.ReadTotalTimeoutConstant = 100;
// 设定写超时
TimeOuts.WriteTotalTimeoutMultiplier = 1000;
TimeOuts.WriteTotalTimeoutConstant = 50000;
com.setTimeouts(TimeOuts);
值得注意的是: 需要设置足够长的读超时,如果超时时间设置过短,可能会出现如下图的,在一次交互中消息返回不全,会在下次通讯中混合无序返回的情况。
serialinterface.cpp文件如下:
#include "SerialInterface.h"
#include <iostream>
/*******************************************************************
* 名称: openSyn
* 功能: 同步方式打开串口,并配置默认信息
* 参数:
serial_name:串口名称
baud_rate :波特率,取值如下
......
CBR_9600 9600bps
CBR_14400 14400bps
......
parity :校验方式
EVENPARITY 偶校验
MARKPARITY 标号校验
NOPARITY 无校验
ODDPARITY 奇校验
SPACEPARITY 空格校验
byte_size :数据位大小
4,5,6,7,8
stop_bits :停止位
ONESTOPBIT 1个停止位
ONE5STOPBITS 1.5个停止位
TWOSTOPBITS 2个停止位
* 返回: 正确返回为ture,错误返回为false
*******************************************************************/
bool SerialInterface::openSyn(string serial_name, int baud_rate,
unsigned char parity, unsigned char byte_size,
unsigned char stop_bits) {
if (!openSyn(serial_name))
return false;
DCB dcb;
if (false == GetCommState(hCom, &dcb)) // 获得当前串口的配置信息
{
setSerialLastError("SerialInterface::open() : GetCommState Error");
return false;
}
dcb.DCBlength = sizeof(DCB);
dcb.BaudRate = baud_rate;
dcb.Parity = parity;
dcb.ByteSize = byte_size;
dcb.StopBits = stop_bits;
if (false == SetCommState(hCom, &dcb)) // 用DCB结构重新配置串行端口信息
{
setSerialLastError("SerialInterface::open() : SetCommState Error");
return false;
}
// 超时处理
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = MAXDWORD; // 读间隔超时
// 把间隔超时设为最大,把总超时设为0将导致ReadFile立即返回并完成操作
timeouts.ReadTotalTimeoutMultiplier = 0; // 读时间系数
timeouts.ReadTotalTimeoutConstant = 0; // 读时间常量
timeouts.WriteTotalTimeoutMultiplier = 50; // 写时间系数
timeouts.WriteTotalTimeoutConstant = 2000; // 写时间常量
// 总的读/写超时时间 = Read(Write)TotalTimeoutMultiplier x 要读/写的字节数 +
// Read(Write)TotalTimeoutConstant.
if (false == SetCommTimeouts(hCom, &timeouts)) {
setSerialLastError("SerialInterface::open() : SetCommTimeouts Error");
return false;
}
// 清空缓冲区,为读写串口做准备
if (false == PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT |
PURGE_RXABORT)) {
setSerialLastError("SerialInterface::open() : PurgeComm Error");
return false;
}
return true;
}
/*******************************************************************
* 名称: openSyn
* 功能: 同步方式打开串口(需自己配置串口参数)
* 参数:
serial_name:串口名称
* 返回: 正确返回为ture,错误返回为false
*******************************************************************/
bool SerialInterface::openSyn(string serial_name) {
hCom = CreateFileA(
serial_name.data(), // 普通文件名或者设备文件名,这里是串口名
GENERIC_READ | GENERIC_WRITE, // 访问模式,读和写
0, // 共享模式,独占模式
NULL, // 指向安全属性的指针,不使用,传NULL
OPEN_EXISTING, // 如何创建,在串口中必须设置为OPEN_EXISTING。表示不能创建新端口只能打开已有的端口。
FILE_ATTRIBUTE_NORMAL, // 文件属性,使用默认属性FILE_ATTRIBUTE_NORMAL。
NULL // 用于复制文件句柄,通常这个参数设置为NULL,为空表示不使用模板
);
if (INVALID_HANDLE_VALUE == hCom) // 出错判断
{
hCom = NULL;
setSerialLastError("open():CreateFileA Error!");
return false;
}
return true;
}
/*******************************************************************
* 名称: closeComm
* 功能: 关闭串口
* 参数: 无
* 返回: 正确返回为ture,错误返回为false
*******************************************************************/
void SerialInterface::closeComm() {
if (NULL == hCom)
return;
CloseHandle(hCom);
hCom = NULL;
}
/*******************************************************************
* 名称: closeComm
* 功能: 判断串口是否打开
* 参数: 无
* 返回: 正确返回为ture,错误返回为false
*******************************************************************/
bool SerialInterface::isOpened() { return NULL == hCom ? false : true; }
DWORD SerialInterface::readData(char *buffer, int length) {
DWORD writeSize = 0;
bool ret = false;
ret = ReadFile(hCom, buffer, length, &writeSize, NULL);
if (false == ret)
return 0;
return writeSize;
}
DWORD SerialInterface::readStr(string *str) {
DWORD readSize = 1024;
unsigned char buffer[1024];
bool ret = false;
ret = ReadFile(hCom, buffer, readSize, &readSize, NULL);
std::cout << "readSize= " << readSize << std::endl;
if (false == ret || 0 == readSize)
return 0;
for (int i = 0; i < readSize; i++) {
*str += buffer[i];
}
// std::cout << "str= " << *str << std::endl;
return readSize;
}
DWORD SerialInterface::writeData(char *buffer, int length) {
DWORD writeSize = 0;
bool ret = false;
ret = WriteFile(hCom, buffer, length, &writeSize, NULL);
if (false == ret)
return 0;
return writeSize;
}
DWORD SerialInterface::writeStr(string str) {
bool ret = false;
DWORD writeSize = 0;
ret = WriteFile(hCom, str.data(), str.length(), &writeSize, NULL);
if (0 == ret) {
last_error = "Error By writeStr(string str)";
return 0;
}
last_error = "";
return writeSize;
}
/*******************************************************************
* 名称: setTimeouts
* 功能: 设置超时
* 参数:
timeouts:超时配置的COMMTIMEOUTS结构体
* 返回: 正确返回为ture,错误返回为false
*******************************************************************/
bool SerialInterface::setTimeouts(COMMTIMEOUTS &timeouts) {
if (false == SetCommTimeouts(hCom, &timeouts)) {
setSerialLastError(
"SerialInterface::setTimeouts() : SetCommTimeouts Error");
return false;
}
return true;
}
/*******************************************************************
* 名称: setDCB
* 功能: 设置串口信息
* 参数:
dcb:串口信息配置的DCB结构体
* 返回: 正确返回为ture,错误返回为false
*******************************************************************/
bool SerialInterface::setDCB(DCB &dcb) {
if (false == SetCommState(hCom, &dcb)) {
setSerialLastError("SerialInterface::setDCB() : SetCommState Error");
return false;
}
return true;
}
/*******************************************************************
* 名称: purgeBuff
* 功能: 刷新缓冲区
* 参数:
flags:需要完成的操作,取值如下
PURGE_RXABORT
终止所有未完成的重叠读取操作并立即返回,即使读取操作尚未完成。 PURGE_RXCLEAR
清除输入缓冲区(如果设备驱动程序有一个)。 PURGE_TXABORT
终止所有未完成的重叠写操作并立即返回,即使写操作尚未完成。 PURGE_TXCLEAR
清除输出缓冲区(如果设备驱动程序有一个)。
* 返回: 正确返回为ture,错误返回为false
* 提示:PurgeComm()函数可以在读写操作的同时,清空缓冲区。当应用程序在读写操作时
调用PurgeComm()函数,不能保证缓冲区内的所有字符都被发送。
*******************************************************************/
bool SerialInterface::purgeBuff(DWORD flags) {
if (false == PurgeComm(hCom, flags)) {
setSerialLastError("SerialInterface::purgeBuff() : PurgeComm Error");
return false;
}
return true;
}
/*******************************************************************
* 名称:flushBuff
* 功能:刷新缓冲区
* 参数:无
* 返回:正确返回为ture,错误返回为false
* 提示:该函数只受流量控制的支配,不受超时控制的支配,它在所有的写操作完成后才返回。
*******************************************************************/
bool SerialInterface::flushBuff() {
if (FlushFileBuffers(hCom)) {
setSerialLastError("SerialInterface::flushBuff() : FlushFileBuffers Error");
return false;
}
return true;
}
/*******************************************************************
* 名称: setBufferSize
* 功能: 设置推荐的缓冲大小
* 参数:
inputBuff:输入缓冲大小
outBuffer:输出缓冲大小
* 返回: 正确返回为ture,错误返回为false
*******************************************************************/
bool SerialInterface::setBufferSize(DWORD inputBuff, DWORD outBuffer) {
if (inputBuff <= 0 || outBuffer <= 0)
return false;
return SetupComm(hCom, inputBuff, outBuffer);
}
/*******************************************************************
* 名称: getSerialLastError
* 功能: 得到最后一次失败的错误信息
* 参数: 无
* 返回: 数据类型:string,错误信息
*******************************************************************/
string SerialInterface::getSerialLastError() { return last_error; }
void SerialInterface::setSerialLastError(string error_msg) {
last_error = error_msg;
}
void SerialInterface::clearSerialLastError() { last_error = ""; }
SerialInterface::SerialInterface() { hCom = NULL; }
SerialInterface::~SerialInterface() {}
serialinterface.h文件如下:
#ifndef __SerialInterface_H_
#define __SerialInterface_H_
#include <Windows.h>
#include <string>
using namespace std;
class SerialInterface
{
private:
/* data */
HANDLE hCom;
string last_error;
public:
SerialInterface();
~SerialInterface();
public:
//同步方式打开串口,并配置默认信息
bool openSyn(string serial_name,int baud_rate,unsigned char parity, unsigned char byte_size, unsigned char stop_bits);
//同步方式打开串口(需自己配置串口参数)
bool openSyn(string serial_name);
//设置推荐的缓冲大小
bool setBufferSize(DWORD inputBuff,DWORD outBuffer);
//设置超时
bool setTimeouts(COMMTIMEOUTS &timeouts);
//设置串口信息
bool setDCB(DCB& dcb);
//刷新缓冲区
bool purgeBuff(DWORD flags);
//刷新缓冲区
bool flushBuff();
//写数据
DWORD writeData(char *buffer,int length);
//读数据
DWORD readData(char *buffer,int length);
//读字符串
DWORD readStr(string *str);
//写字符串
DWORD writeStr(string str);
//关闭串口
void closeComm();
//判断串口是否打开
bool isOpened();
//得到最后一次失败的错误信息
string getSerialLastError();
private:
//设置最后一次的错误信息
void setSerialLastError(string error_msg);
//清chu最后一次的错误信息
void clearSerialLastError();
};
#endif /*__SerialInterface_H_*/