C/C++串口通信(1)-同步操作

转自:

王柏元的博客 http://wangbaiyuan.cn/c-serial-communication-write-reading.html

串口通信方式:调用Windows的API函数

两种操作方式:
1. 同步操作方式
API函数会阻塞直到操作完成以后才能返回(在多线程方式中,虽然不会阻塞主线程,但是仍然会阻塞监听线程);
2. 重叠操作方式(又称为异步操作方式
API函数会立即返回,操作在后台进行,避免线程的阻塞。

通过四个步骤来完成:
(1) 打开串口
(2) 配置串口
(3) 读写串口
(4) 关闭串口

1、打开串口

Win32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用API函数CreateFile来打开或创建的。
该函数的原型为:

HANDLE CreateFile( LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
HANDLE hTemplateFile);

lpFileName:将要打开的串口逻辑名,如”COM1”;
dwDesiredAccess:指定串口访问的类型,可以是读取、写入或二者并列;
dwShareMode:指定共享属性,由于串口不能共享,该参数必须置为0;
lpSecurityAttributes:引用安全性属性结构,缺省值为NULL;
dwCreationDistribution:创建标志,对串口操作该参数必须置为OPEN_EXISTING;
dwFlagsAndAttributes:属性描述,用于指定该串口是否进行异步操作,该值为FILE_FLAG_OVERLAPPED,表示使用异步的I/O;该值为0,表示同步I/O操作;
hTemplateFile:对串口而言该参数必须置为NULL。

同步I/O方式打开串口的示例代码:

HANDLE hCom; //全局变量,串口句柄
hCom=CreateFile("COM1",//COM1口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
0, //同步方式
NULL);
if (hCom== INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "串口号不匹配,打开串口失败!\n");
        Sleep(3000);
        exit(0);
    }

重叠I/O打开串口的示例代码:

HANDLE hCom; //全局变量,串口句柄
hCom =CreateFile("COM1", //COM1口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重叠方式
NULL);
if (hCom== INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "串口号不匹配,打开串口失败!\n");
        Sleep(3000);
        exit(0);
    }

2、配置串口

(1)初始化配置工作。

一般用CreateFile打开串口后,可以调用GetCommState函数来获取串口的初始配置。
要修改串口的配置,应该先修改DCB结构,然后再调用SetCommState函数设置串口

DCB结构包含了诸如波特率、数据位数、奇偶校验和停止位数等信息。 在查询或配置串口的属性时,都要用DCB结构来作为缓冲区。

DCB结构包含了串口的各项参数设置,下面仅介绍几个该结构常用的变量:

typedef struct _DCB{ ………

DWORD BaudRate;//波特率,指定通信设备的传输速率。这个成员可以是实际波特率值或者下面的常量值之一:
CBR_110,CBR_300,CBR_600,CBR_1200,CBR_2400,CBR_4800,CBR_9600,CBR_19200,
CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000,
CBR_14400

DWORD fParity; // 指定奇偶校验使能。若此成员为1,允许奇偶校验检查 …

BYTE ByteSize; // 通信字节位数,4—8

BYTE Parity; //指定奇偶校验方法。此成员可以有下列值: EVENPARITY 偶校验 ;NOPARITY 无校验;MARKPARITY 标记校验; ODDPARITY 奇校验

BYTE StopBits; //指定停止位的位数。此成员可以有下列值: ONESTOPBIT 1位停止位 ;TWOSTOPBITS 2位停止位 ;ON 5STOPBITS 1.5位停止位

GetCommState函数可以获得COM口的设备控制块,从而获得相关参数:

BOOL GetCommState(HANDLE hFile, //标识通讯端口的句柄
LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针
 );
//SetCommState函数设置COM口的设备控制块:
BOOL SetCommState( HANDLE hFile, LPDCB lpDCB );

(2)设置I/O缓冲区的大小和超时:
Windows用I/O缓冲区来暂存串口输入和输出的数据。如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。

BOOL SetupComm( HANDLE hFile, // 通信设备的句柄
DWORD dwInQueue, // 输入缓冲区的大小(字节数)
DWORD dwOutQueue // 输出缓冲区的大小(字节数) 
);

在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。超时的作用是在指定的时间内没有读入或发送指定数量的字符,ReadFile或WriteFile的操作仍然会结束
要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。
调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。
读写串口的超时有两种:

间隔超时和总超时:间隔超时是指在接收时两个字符之间的最大时延。总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读写操作的超时。

COMMTIMEOUTS结构的定义为:

typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //读间隔超时
DWORD ReadTotalTimeoutMultiplier; //读时间系数
DWORD ReadTotalTimeoutConstant; //读时间常量
DWORD WriteTotalTimeoutMultiplier; // 写时间系数
DWORD WriteTotalTimeoutConstant; //写时间常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

COMMTIMEOUTS结构的成员都以毫秒为单位。

总超时的计算公式是:总超时=时间系数×要求读/写的字符数+时间常量

例如,要读入10个字符,那么读操作的总超时的计算公式为:
读总超时=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant
可以看出:间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。

如果所有写超时参数均为0,那么就不使用写超时。
如果ReadIntervalTimeout为0,那么就不使用读间隔超时。
如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为0,则不使用读总超时。
如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0,那么在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符。

在用重叠方式读写串口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。

配置串口的示例代码:

SetupComm(hCom,1024,1024); //输入缓冲区和输出缓冲区的大小都是1024
COMMTIMEOUTS TimeOuts; //设定读超时
TimeOuts.ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000; //设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=500;
TimeOuts.WriteTotalTimeoutConstant=2000;
SetCommTimeouts(hCom,&TimeOuts); //设置超时
DCB dcb;
GetCommState(hCom,&dcb);
dcb.BaudRate=9600; //波特率为9600
dcb.ByteSize=8; //每个字节有8位
dcb.Parity=NOPARITY; //无奇偶校验位
dcb.StopBits=TWOSTOPBITS; //两个停止位
SetCommState(hCom,&dcb);
PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);

在读写串口之前,还要用PurgeComm()函数清空缓冲区,该函数原型:

BOOL PurgeComm( HANDLE hFile, //串口句柄
DWORD dwFlags // 需要完成的操作 
);

参数dwFlags指定要完成的操作,可以是下列值的组合:

PURGE_TXABORT 中断所有写操作并立即返回,即使写操作还没有完成。
PURGE_RXABORT 中断所有读操作并立即返回,即使读操作还没有完成。
PURGE_TXCLEAR 清除输出缓冲区
PURGE_RXCLEAR 清除输入缓冲区

例如:

// Clear buffer清除缓冲区 
PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT);

3、读写串口

使用ReadFile和WriteFile读写串口,下面是两个函数的声明:

//写--发送命令:程序向硬件发送指令
BOOL WriteFile( HANDLE hFile, //串口的句柄
// 写入的数据存储的地址, 即以该指针的值为首地址的一片内存区
LPCVOID lpBuffer,
//要写入的数据的字节数
DWORD nNumberOfBytesToWrite,
// 指向指向一个DWORD数值,该数值返回实际写入的字节数
LPDWORD lpNumberOfBytesWritten,
// 重叠操作时,该参数指向一个OVERLAPPED结构,
// 同步操作时,该参数为NULL。
LPOVERLAPPED lpOverlapped );
//读--接受数据:程序读取硬件返回的数据
BOOL ReadFile( HANDLE hFile, //串口的句柄
// 读入的数据存储的地址,即读入的数据将存储在以该指针的值为首地址的一片内存区
LPVOID lpBuffer,
// 要读入的数据的字节数
DWORD nNumberOfBytesToRead,
// 指向一个DWORD数值,该数值返回读操作实际读入的字节数
LPDWORD lpNumberOfBytesRead,
// 重叠操作时,该参数指向一个OVERLAPPED结构,同步操作时,该参数为NULL。
LPOVERLAPPED lpOverlapped );

lpBuffer:char型数组,数组内容是定义好的指令信息。
nNumberOfBytesToWrite,nNumberOfBytesToRead:lpBuffer数组的元素个数

//lpBuffer:
char send_data[] = { 0x75, 0x57, 0xFF, 0x0D, 0x0A };     //请求数据 16进制
DWORD nNumberOfBytesToWrite= 1; //读操作实际读入的字节数

BYTE Rev_buffer[8]; //接受数据

在用ReadFile和WriteFile读写串口时,既可以同步执行,也可以重叠执行。

在同步执行时,函数直到操作完成后才返回。这意味着同步执行时线程会被阻塞,从而导致效率下降。
在重叠执行时,即使操作还未完成,这两个函数也会立即返回,费时的I/O操作在后台进行。

ReadFile和WriteFile函数是同步还是异步由CreateFile函数决定,

如果在调用CreateFile创建句柄时指定了FILE_FLAG_OVERLAPPED标志,那么调用ReadFile和WriteFile对该句柄进行的操作就应该是重叠的;
如果未指定重叠标志,则读写操作应该是同步的。

ReadFile和WriteFile函数的同步或者异步应该和CreateFile函数相一致

ReadFile函数只要在串口输入缓冲区中读入指定数量的字符,就算完成操作。
而WriteFile函数不但要把指定数量的字符拷入到输出缓冲区,而且要等这些字符从串行口送出去后才算完成操作。
如果操作成功,这两个函数都返回TRUE。

需要注意的是,当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。这说明重叠操作还未完成。

同步方式读写串口的代码:

//同步读串口
 char str[100]; 
 DWORD wCount;//读取的字节数
 BOOL bReadStat;
 bReadStat=ReadFile(hCom,str,100,&wCount,NULL);
 if(!bReadStat) 
 { 
    AfxMessageBox("读串口失败!"); 
    return FALSE; 
 } 
//同步写串口
 char lpOutBuffer[100];
 DWORD dwBytesWrite=100;
 COMSTAT ComStat;
 DWORD dwErrorFlags;
 BOOL bWriteStat;
 ClearCommError(hCom,&dwErrorFlags,&ComStat);
 bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,& dwBytesWrite,NULL);

 if(!bWriteStat) 
 {
    AfxMessageBox("写串口失败!"); 
 }
 PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

4、关闭串口

使用CreateFile函数返回的句柄作为参数调用CloseHandle即可:

BOOL CloseHandle(
HANDLE hObject; //handle to object to close
);
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 串口通信是一种通过串行接口进行数据传输的通信方式。异步通信是串口通信中的一种传输方式,即不同步地传输数据。 在C语言中,可以编写一个异步通信的demo程序来演示串口通信。 首先,需要包含相关的头文件,如<termios.h>用于串口配置,<fcntl.h>用于文件控制,<unistd.h>用于文件操作等。 接下来,需要定义串口的文件路径,如"/dev/ttyS0"表示串口设备文件。 然后,可以使用open函数打开串口设备文件,并设置相关的串口参数,如波特率、数据位、校验位、停止位等。 之后,可以使用read函数读取串口接收到的数据,并使用write函数向串口发送数据。 最后,使用close函数关闭串口设备文件。 下面是一个简单的异步通信demo程序的代码示例: #include <stdio.h> #include <fcntl.h> #include <termios.h> #include <unistd.h> int main() { int fd; char buf[100]; fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); if (fd == -1) { perror("open"); return -1; } struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag |= CLOCAL | CREAD; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_cflag &= ~CSTOPB; tcsetattr(fd, TCSANOW, &options); write(fd, "Hello", 5); sleep(1); read(fd, buf, sizeof(buf)); printf("Received: %s\n", buf); close(fd); return 0; } 这段程序实现了通过串口向外发送"Hello",然后从串口接收数据,并打印接收到的数据。 在实际应用中,可以根据具体需求修改程序中的串口设备文件路径和参数设置,并进行相应的错误处理。 ### 回答2: 串口通信是一种用于在计算机和外部设备之间传输数据的标准通信方式。在串口通信中,数据按照位的形式一位一位地传输,而不是以字节的形式。异步通信是串口通信中的一种常见模式,它以不等时间间隔传输数据。 在C语言中实现一个串口通信的异步通信demo,可以使用以下步骤: 1. 引入所需的头文件,如stdio.h、fcntl.h、unistd.h和termios.h。这些头文件提供了访问串口通信所需的函数和数据类型。 2. 打开串口文件。使用open函数打开串口设备文件,如/dev/ttyS0。如果打开成功,该函数将返回文件描述符。 3. 配置串口属性。使用tcgetattr和tcsetattr函数获取和设置串口的属性,包括波特率、数据位、停止位和校验位等。 4. 设置串口为非阻塞模式。使用fcntl函数将串口文件描述符设置为非阻塞模式,这样可以实现异步通信。 5. 读取和写入数据。使用read和write函数从串口中读取和写入数据。可以使用循环来实现连续的数据传输。 6. 关闭串口文件。使用close函数关闭串口文件。 以下是一个简单的异步串口通信demo的示例代码: ```c #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> int main() { int fd; char data; // 打开串口文件 fd = open("/dev/ttyS0", O_RDWR); if (fd == -1) { perror("Error opening serial port"); return 1; } // 配置串口属性 struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~CRTSCTS; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; tcsetattr(fd, TCSANOW, &options); // 设置串口为非阻塞模式 fcntl(fd, F_SETFL, O_NONBLOCK); // 读取和写入数据 while(1) { // 从串口中读取数据 if (read(fd, &data, 1) > 0) { printf("Received: %c\n", data); } // 向串口中写入数据 data = 'A'; write(fd, &data, 1); sleep(1); } // 关闭串口文件 close(fd); return 0; } ``` 上述代码实现了一个简单的异步串口通信demo,其中打开了/dev/ttyS0串口文件,配置了波特率为9600,然后不断读取和写入数据。这只是一个简单的示例,实际应用中需要根据具体需求进行适当的修改和完善。 ### 回答3: 串口通信是指通过串行接口进行数据传输的一种通信方式。在计算机领域中,串口通信通常用于连接计算机与外部设备,如打印机、调制解调器、传感器等。 异步通信demo c是一个使用C语言编写的示例程序,用于演示如何实现串口异步通信。该示例程序通过调用相关的库函数和API,实现了串口的打开、设置波特率、写入数据和读取数据等操作。用户可以根据需要进行修改和扩展,以满足具体的通信需求。 在串口通信中,异步通信是指数据传输的起始和停止时刻不依赖于时钟信号,在传输数据时,发送和接收两端的时钟信号可以有一定的差异。异步通信通过在数据传输中插入起始位和停止位来同步数据的传输。与之相对的是同步通信,同步通信需要在发送和接收两端保持相同的时钟信号,以实现数据的同步传输。 串口通信的优点包括可靠性高、传输距离远、抗干扰能力强等。异步通信demo c通过提供示例代码和相关函数库,简化了程序员对串口通信的开发和调试过程,提高了开发效率和可靠性。 总之,串口通信和异步通信demo c是一种常用的通信方式和相应的示例程序,通过串口连接计算机与外部设备进行数据传输,并通过异步通信方式实现数据的同步和可靠传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值