Windows C下的 “serial”

上文中我们已经介绍了基于python的serial库,而针对法老王——C,自然也有自己的串口库:

  • 实验环境:Win10 x64 C
  • gcc 版本:8.1.0

1. 串口配置

针对以下串口配置有:

#include <stdio.h>
#include <windows.h>
#include "serial.h"

其中:serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include <windows.h>
typedef HANDLE PORT;

PORT OpenPort(int idx);
void ClosePort(PORT com_port);
int SetPortBoudRate(PORT com_port, int rate);
int SetPortDataBits(PORT com_port, int bits);
int SetPortStopBits(PORT com_port, int bits);
int SetPortParity(PORT com_port, int parity);
int GetPortBoudRate(PORT com_port);
int GetPortDataBits(PORT com_port);
int GetPortStopBits(PORT com_port);
int GetPortParity(PORT com_port);
int SendData(PORT com_port, const char * data);
int ReciveData(PORT com_port, char * data,int len);
PORT serial_init(int idx, int rate, int databits, int stopbits, int parity);
int Serial_SendData(PORT com_port, const char *data, int len);
int Serial_ReciveData(PORT com_port, char *data, int len);

#endif

1.1 打开和关闭串口

打开串口:

// 打开串口
PORT OpenPort(int idx)
{
HANDLE hComm; // 串口句柄
TCHAR comname[100]; // 用于保存串口名称

/*
用于格式化串口名称字符串,
\\.\ 表示系统中所有的串口都可以通过这个路径来访问,
串口名称的格式类似于"\\.\COM1"。
*/

wsprintf(comname, TEXT("\\\\.\\COM%d"), idx);
// 打开一个串口设备,并返回一个句柄
hComm = CreateFile(comname, // 串口名
GENERIC_READ | GENERIC_WRITE, // 读写
0, // 0:为专用通讯,关闭共享
NULL, // 安全符
OPEN_EXISTING,// 打开现有串口
0, // 文件属性和标志
NULL); // 指向安全属性的指针

if (hComm == INVALID_HANDLE_VALUE)
return NULL;

COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50; // 读操作之间超时时间间隔,50ms
timeouts.ReadTotalTimeoutConstant = 50; // 读操作总的时间间隔
timeouts.ReadTotalTimeoutMultiplier = 10; // 读操作总时间倍乘,数据达到,超时时间为10*间隔t + 总t
timeouts.WriteTotalTimeoutConstant = 50; // 写操作总超时时间
timeouts.WriteTotalTimeoutMultiplier = 10; // 读操作倍乘

if (SetCommTimeouts(hComm, &timeouts) == FALSE)
return NULL;

if (SetCommMask(hComm, EV_RXCHAR) == FALSE)
return NULL;

printf("open%d ok\n",idx);

return hComm;
}

关闭串口:

void ClosePort(PORT com_port)
{
CloseHandle(com_port);
}

1.2 串口参数配置

  • 对于串口常规需要设置的有:端口号、波特率、数据位、停止位、校验位

1.2.1 设置参数

  • 波特率:
int SetPortBoudRate(PORT com_port, int rate)
{
DCB dcbSerialParams = { 0 }; // DCB 结构体通常用于描述串口的参数信息
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return FALSE;
dcbSerialParams.BaudRate = rate;
Status = SetCommState(com_port, &dcbSerialParams);
return Status;
}
  • 数据位:
int SetPortDataBits(PORT com_port, int bits)
{
DCB dcbSerialParams = { 0 };
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return FALSE;
dcbSerialParams.ByteSize = bits;
Status = SetCommState(com_port, &dcbSerialParams);
return Status;
}
  • 停止位:
int SetPortStopBits(PORT com_port, int bits)
{
DCB dcbSerialParams = { 0 };
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return FALSE;
dcbSerialParams.StopBits = bits;
Status = SetCommState(com_port, &dcbSerialParams);
return Status;
}
  • 校验位:
//默认为无校验。0: NOPARITY 1:ODDPARITY 2:EVENPARITY 3:MARKPARITY 4:SPACEPARITY
int SetPortParity(PORT com_port, int parity)
{
DCB dcbSerialParams = { 0 };
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return FALSE;
dcbSerialParams.Parity = parity;
Status = SetCommState(com_port, &dcbSerialParams);
return Status;
}

1.2.2 获取参数

对应有获取当前设备串口数据信息:

  • 获取波特率:
int GetPortBoudRate(PORT com_port)
{
DCB dcbSerialParams = { 0 };
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return -1;
return dcbSerialParams.BaudRate;
}
  • 获取数据位:
int GetPortDataBits(PORT com_port) 
{
DCB dcbSerialParams = { 0 };
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return -1;
return dcbSerialParams.ByteSize;
}
  • 获取停止位:
int GetPortStopBits(PORT com_port) 
{
DCB dcbSerialParams = { 0 };
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return -1;
return dcbSerialParams.StopBits;
}
  • 获取校验位:
int GetPortParity(PORT com_port)
{
DCB dcbSerialParams = { 0 };
BOOL Status;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
Status = GetCommState(com_port, &dcbSerialParams);
if (Status == FALSE)
return -1;
return dcbSerialParams.Parity;
}

1.3 读写操作

  • 发送数据
// 端口号,以及指向字符数组的指针
int SendData(PORT com_port, const char * data)
{
DWORD dNoOFBytestoWrite = strlen(data);
DWORD dNoOfBytesWritten;
BOOL Status = WriteFile(com_port,
data,
dNoOFBytestoWrite,
&dNoOfBytesWritten,
NULL);
if (Status == FALSE)
{
return -1;
}
else
{
printf("%s\n",data);
}
return dNoOfBytesWritten;
}

// 发送n个字节数据
int Serial_SendData(PORT com_port, const char *data, int len)
{
DWORD dNoOfBytesWritten;
BOOL Status = WriteFile(com_port,
data,
len,
&dNoOfBytesWritten,
NULL);

if (Status == FALSE)
return -1;
else
printf("send ok\n");

return 0;
}
  • 读数据
int ReciveData(PORT com_port, char * data,int len)
{
DWORD dwEventMask;
DWORD NoBytesRead;

BOOL Status = WaitCommEvent(com_port, &dwEventMask, NULL);
if (Status == FALSE)
{
return FALSE;
}
Status = ReadFile(com_port, data, len, &NoBytesRead, NULL);
data[NoBytesRead] = 0;

if (Status == FALSE)
{
return FALSE;
}
else
{
printf("%s\n",data);
}
return TRUE;
}

// 读取n个字节数据
int Serial_ReciveData(PORT com_port, char * data, int len)
{
DWORD dwEventMask;
DWORD NoBytesRead;

BOOL Status = WaitCommEvent(com_port, &dwEventMask, NULL);
if (Status == FALSE)
{
return -1;
}
Status = ReadFile(com_port, data, len, &NoBytesRead, NULL);
data[NoBytesRead] = 0;

if (Status == FALSE)
return -1;
else
printf("rcv ok\n");

return NoBytesRead;
}

1. 4串口初始化

# 形参分别为:端口、波特率、数据位、停止位、校验位
PORT serial_init(int idx, int rate, int databits, int stopbits, int parity)
{
int ret = 0;
PORT com_port; // 句柄转声明为"port"
com_port = OpenPort(idx); // 打开串口idx
if (com_port == INVALID_HANDLE_VALUE)
{
printf("open COM%d fail\n", idx);
return NULL;
}
ret = SetPortBoudRate(com_port, rate);
if(ret == FALSE)
{
printf("set COM%d band fail\n", idx);
return NULL;
}
ret = SetPortDataBits(com_port, databits);
if(ret == FALSE)
{
printf("set COM%d databits fail\n", idx);
return NULL;
}
stopbits = ONESTOPBIT;
printf("stopbits %d\n",stopbits);
ret = SetPortStopBits(com_port, stopbits);
if(ret == FALSE)
{
printf("set COM%d stopbits fail\n", idx);
return NULL;
}
ret = SetPortParity(com_port, parity);
if(ret == FALSE)
{
printf("set COM%d parity fail\n", idx);
return NULL;
}
return com_port;
}

2. 使用

  • 因为serial函数太多可以放于另一个文件中,称为源文件
  • 在主程序中使用include "serial.c",进行调用。
  • 还值得一提的是,当接受数据时,必然要建立缓冲池,有两种方式,char [1024]或者 `memset(buff, 0, 1024)。
#include <stdio.h>	// 标准输入输出
#include <stdint.h> // 整型库
#include <windows.h> // windows c库函数
#include "serial.h" // 自定义串口头文件
#include "serial.c" // 串口函数源文件

// 仅仅测试用
int serial_test()
{
PORT COM1; // 声明端口句柄 com1

char buff[1024] = {0};
// int rcv_len = 0;

printf("open com1\n");
COM1 = serial_init(1, 115200, 8, 1, 0);

Serial_SendData(COM1, "hello guoguo\n", 13); // 发送13个字节
memset(buff, 0, 1024); // C标准库中的用来初始化缓冲池的函数,buff区域指针,0初始化内容,1024数量
// rcv_len = Serial_ReciveData(COM1, buff, 1024);
// printf("rcv:%s\n", buff);
Sleep(1); // 延时
}

int main()
{
serial_test();
return 0;
}

可以使用**==VSPD==**虚拟串口进行仿真。