一、poll() 函数的介绍
poll() 函数用于监控多个文件描述符的变化的函数。它可以用来检查一个或多个文件描述符的状态是否改变,比如是否可读、可写或有错误发生。它常用于处理 I/O 多路复用,这在需要同时处理多个网络连接或文件操作时非常有用。
头文件
#include <poll.h>
函数原型
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:一个指向 pollfd 结构数组的指针,每个 pollfd 结构代表一个要监控的文件描述符。
nfds:fds 数组中的元素数量。
timeout:等待的超时时间(毫秒)。如果设置为 -1,则 poll() 会一直等待,直到某个文件描述符的状态改变或捕获到信号。如果设置为 0,则 poll() 会立即返回,不会等待。
struct pollfd 结构
struct pollfd {
int fd; // 文件描述符
short events; // 感兴趣的事件
short revents; // 返回的事件
};
fd:要监控的文件描述符。
events:用户感兴趣的事件,可以是以下值的组合:
POLLIN:数据可读
POLLOUT:数据可写
POLLPRI:优先级数据可读
POLLERR:发生错误
POLLHUP:挂起(只用于流)
POLLNVAL:请求无效的文件描述符
revents:返回时,这个字段包含了实际发生的事件。
返回值
成功时,返回更改状态的文件描述符数量。
如果超时,返回 0。
失败时,返回 -1 并设置 errno。
示例
下面是一个简单的 poll() 使用示例,它监控标准输入(stdin)是否可读:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
int main() {
struct pollfd fds[1];
int ret;
// 设置要监控的文件描述符和事件
fds[0].fd = STDIN_FILENO; // 标准输入的文件描述符
fds[0].events = POLLIN; // 监控读事件
// 使用 poll 监控文件描述符
ret = poll(fds, 1, -1); // 无限等待,直到有事件发生
if (ret == -1) {
perror("poll");
exit(EXIT_FAILURE);
}
// 检查哪个文件描述符的状态发生了改变
if (fds[0].revents & POLLIN) {
printf("Standard input is readable.\n");
// 这里可以读取标准输入的数据
}
return 0;
}
这个示例程序会等待用户从标准输入(通常是键盘)输入数据。一旦有数据可读,poll() 就会返回,并设置 fds[0].revents 为 POLLIN,表示标准输入现在可读。然后程序会打印一条消息。注意,这个示例并没有实际读取输入数据,只是检测了输入是否可读。如果需要读取数据,你可以使用如 read() 或 fgets() 等函数来从标准输入读取数据。
二、串口使用poll()函数测试代码
测试代码基于文章 <嵌入式Linux开发板测试esp8266模块> 进行修改测试,文章链接如下:
http://t.csdnimg.cn/y31It
修改后代码如下:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <sys/select.h>
#include <poll.h>
#define REV_OK 0 //接收完成标志
#define REV_WAIT 1 //接收未完成标志
#define UART_BUF 128 // 串口缓冲区
#define ESP8266_AP_INFO "AT+CWSAP=\"ATK-8266\",\"12345678\",1,4\r\n"
#define ESP8266_JAP_INFO "AT+CWJAP=\"TP-LINK_xxxx\",\"123456789\"\r\n"
#define ESP8266_TCP_SERVER_INFO "AT+CIPSTART=\"TCP\",\"192.168.1.182\",8080\r\n"
#define u8 unsigned char
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0;
unsigned char buf[UART_BUF] = {0};
/* 实现ms级延时 */
static void delay_xms(unsigned int secs)
{
struct timeval tval;
tval.tv_sec=secs/1000;
tval.tv_usec=(secs*1000)%1000000;
select(0,NULL,NULL,NULL,&tval);
}
typedef struct uart_hardware_cfg {
unsigned int baudrate; /* 波特率 */
unsigned char dbit; /* 数据位 */
char parity; /* 奇偶校验 */
unsigned char sbit; /* 停止位 */
}uart_cfg_t;
static struct termios old_cfg; /* 用于保存终端的配置参数 */
static int fd; /* 串口终端对应的文件描述符 */
/* 串口初始化
打开串口文件描述符,即对应的串口终端的设备节点 */
static int uart_init(const char *device)
{
/* 打开串口 */
/* O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机 */
fd = open(device, O_RDWR | O_NOCTTY);
if (fd < 0)
{
fprintf(stderr, "open error:%s:%s\n", device, strerror(errno));
return -1;
}
/* 获取串口当前的配置参数 */
if (0 > tcgetattr(fd, &old_cfg)) {
fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
close(fd);
return -1;
}
return 0;
}
/**
** 串口配置
** 参数cfg指向一个uart_cfg_t结构体对象
**/
static int uart_cfg(const uart_cfg_t *cfg)
{
struct termios new_cfg = {0}; //将new_cfg对象清零
speed_t speed;
/* 设置为原始模式 */
cfmakeraw(&new_cfg);
/* 使能接收 */
new_cfg.c_cflag |= CREAD;
/* 设置波特率 */
switch (cfg->baudrate) {
case 1200: speed = B1200;
break;
case 1800: speed = B1800;
break;
case 2400: speed = B2400;
break;
case 4800: speed = B4800;
break;
case 9600: speed = B9600;
break;
case 19200: speed = B19200;
break;
case 38400: speed = B38400;
break;
case 57600: speed = B57600;
break;
case 115200: speed = B115200;
break;
case 230400: speed = B230400;
break;
case 460800: speed = B460800;
break;
case 500000: speed = B500000;
break;
default: //默认配置为115200
speed = B115200;
printf("default baud rate: 115200\n");
break;
}
if (0 > cfsetspeed(&new_cfg, speed)) {
fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));
return -1;
}
/* 设置数据位大小 */
new_cfg.c_cflag &= ~CSIZE; //将数据位相关的比特位清零
switch (cfg->dbit) {
case 5:
new_cfg.c_cflag |= CS5;
break;
case 6:
new_cfg.c_cflag |= CS6;
break;
case 7:
new_cfg.c_cflag |= CS7;
break;
case 8:
new_cfg.c_cflag |= CS8;
break;
default: //默认数据位大小为8
new_cfg.c_cflag |= CS8;
// printf("default data bit size: 8\n");
break;
}
/* 设置奇偶校验 */
switch (cfg->parity) {
case 'N': //无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
break;
case 'O': //奇校验
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;
break;
case 'E': //偶校验
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除PARODD标志,配置为偶校验 */
new_cfg.c_iflag |= INPCK;
break;
default: //默认配置为无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
// printf("default parity: N\n");
break;
}
/* 设置停止位 */
switch (cfg->sbit) {
case 1: //1个停止位
new_cfg.c_cflag &= ~CSTOPB;
break;
case 2: //2个停止位
new_cfg.c_cflag |= CSTOPB;
break;
default: //默认配置为1个停止位
new_cfg.c_cflag &= ~CSTOPB;
// printf("default stop bit size: 1\n");
break;
}
/* 将MIN和TIME设置为0 */
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;
/* 清空缓冲区 */
if (0 > tcflush(fd, TCIOFLUSH)) {
fprintf(stderr, "tcflush error: %s\n", strerror(errno));
return -1;
}
/* 写入配置、使配置生效 */
if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {
fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
return -1;
}
/* 配置OK 退出 */
return 0;
}
//==========================================================
// 函数名称: ESP8266_Clear
//
// 函数功能: 清空缓存
//
// 入口参数: 无
//
// 返回参数: 无
//
// 说明:
//==========================================================
void ESP8266_Clear(void)
{
memset(buf, 0, sizeof(buf));
esp8266_cnt = 0;
}
/**
** 信号处理函数,当串口有数据可读时,会跳转到该函数执行
**/
static void io_handler(int sig, siginfo_t *info, void *context)
{
if(SIGRTMIN != sig)
return;
/* 判断串口是否有数据可读 */
if (POLL_IN == info->si_code)
{
if (esp8266_cnt >= sizeof(buf))
{
// esp8266_cnt = 0; //防止串口被刷爆
// memset(buf,0,sizeof(buf));
ESP8266_Clear();
}
esp8266_cnt = read(fd, buf+esp8266_cnt, sizeof(buf)-esp8266_cnt);
printf("esp8266_cnt=%d\r\n", esp8266_cnt);
printf("io_handler=%s\r\n", buf);
}
}
/**
** 异步I/O初始化函数
**/
static void async_io_init(void)
{
struct sigaction sigatn;
int flag;
/* 使能异步I/O */
flag = fcntl(fd, F_GETFL); //使能串口的异步I/O功能
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag);
/* 设置异步I/O的所有者 */
fcntl(fd, F_SETOWN, getpid());
/* 指定实时信号SIGRTMIN作为异步I/O通知信号 */
fcntl(fd, F_SETSIG, SIGRTMIN);
/* 为实时信号SIGRTMIN注册信号处理函数 */
sigatn.sa_sigaction = io_handler; //当串口有数据可读时,会跳转到io_handler函数
sigatn.sa_flags = SA_SIGINFO;
sigemptyset(&sigatn.sa_mask);
sigaction(SIGRTMIN, &sigatn, NULL);
}
void Uart4_Init(unsigned int baud, char *device)
{
uart_cfg_t cfg = {0};
if (NULL == device) {
fprintf(stderr, "Error: the device no found!\n");
exit(EXIT_FAILURE);
}
/* 串口初始化 */
if (uart_init(device))
exit(EXIT_FAILURE);
/* 设置波特率 */
cfg.baudrate = baud;
/* 串口配置 */
if (uart_cfg(&cfg)) {
tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
close(fd);
exit(EXIT_FAILURE);
}
}
void Uart5_Init(unsigned int baud, char *device)
{
uart_cfg_t cfg = {0};
if (NULL == device) {
fprintf(stderr, "Error: the device no found!\n");
exit(EXIT_FAILURE);
}
/* 串口初始化 */
if (uart_init(device))
exit(EXIT_FAILURE);
/* 设置波特率 */
cfg.baudrate = baud;
/* 串口配置 */
if (uart_cfg(&cfg)) {
tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
close(fd);
exit(EXIT_FAILURE);
}
}
//==========================================================
// 函数名称: ESP8266_WaitRecive
//
// 函数功能: 等待接收完成
//
// 入口参数: char *res
//
// 返回参数: REV_OK-接收完成 REV_WAIT-接收超时未完成
//
// 说明: 循环调用检测是否接收完成
//==========================================================
_Bool ESP8266_WaitRecive(char *res)
{
int err=0,nbytes=0,i=0;
struct pollfd fds[] = {
{
.fd = fd,
.events = POLLIN,
},
};
err = poll(fds, 1, 5000);//5s
switch(err)
{
case -1://出错
printf("\n poll read error =%d \n", err);
nbytes=0;
break;
case 0://超时
printf("\n poll read error =%d \n", err);
nbytes=0;
break;
default:
nbytes = read(fd, buf+esp8266_cnt, sizeof(buf)-esp8266_cnt);
break;
}
esp8266_cnt += nbytes;
if(esp8266_cnt == 0) //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
return REV_WAIT;
if( strstr((const char *)buf, res) != NULL || esp8266_cnt == esp8266_cntPre) //如果上一次的值和这次相同,则说明接收完毕
{
// for (i = 0; i < esp8266_cnt; i++)
// printf(" 0x%02x", buf[i]);
// printf("\n");
printf("***********************\n");
printf("%s",buf);
printf("***********************\n");
esp8266_cnt = 0; //清0接收计数
esp8266_cntPre=0;
return REV_OK; //返回接收完成标志
}
esp8266_cntPre = esp8266_cnt; //置为相同
return REV_WAIT; //返回接收未完成标志
}
int ESP8266_Recive(void)
{
int err=0,nbytes=0,i=0;
struct pollfd fds[] = {
{
.fd = fd,
.events = POLLIN,
},
};
memset(buf,0,sizeof(buf));
err = poll(fds, 1, 1000);//1s
switch(err)
{
case -1://出错
printf("\n poll read error =%d \n", err);
nbytes=0;
break;
case 0://超时
printf("\n poll read error =%d \n", err);
nbytes=0;
break;
default:
nbytes = read(fd, buf, sizeof(buf));
break;
}
if(nbytes>0)
{
return nbytes;
}
return -1;
}
//==========================================================
// 函数名称: ESP8266_SendCmd
//
// 函数功能: 发送命令
//
// 入口参数: cmd:命令
// res:需要检查的返回指令
//
// 返回参数: 0-成功 1-失败
//
// 说明:
//==========================================================
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
int ret = 0,err,nbytes;
unsigned int timeOutCnt = 3;
ESP8266_Clear();
ret = write(fd, (unsigned char *)cmd, strlen((const char *)cmd));
if (ret == 0) printf("write err!\r\n");
// printf("UartBuf:%s, Len:%d\r\n", buf, esp8266_cnt);
while(timeOutCnt--)
{
if(ESP8266_WaitRecive(res) == REV_OK) //如果收到数据
{
// printf("ESP8266_SendCmd Function:%s\r\n", buf);
if(strstr((const char *)buf, res) != NULL) //如果接收到的数据是在给定数据的范围内,则不为NULL
{
ESP8266_Clear(); //清空缓存
return 0;
}
}
//delay_xms(10);//10s内让串口2中断函数循环接收到的数据
}
return 1;
}
//==========================================================
// 函数名称: ESP8266_SendData
//
// 函数功能: 发送数据
//
// 入口参数: data:数据
// len:长度
//
// 返回参数: 无
//
// 说明:
//==========================================================
void ESP8266_SendData(unsigned char *data, unsigned short len)
{
char cmdBuf[32];
ESP8266_Clear(); //清空接收缓存
// sprintf(cmdBuf, "AT+CIPSEND=0,%d\r\n", len); //发送命令 多连接
sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len); //发送命令 单连接
if(!ESP8266_SendCmd(cmdBuf, ">")) //收到‘>’时可以发送数据,改不得
{
write(fd, data, len); //发送设备连接请求数据
}
}
_Bool Esp8266_Init()
{
ESP8266_Clear();
printf("1. AT\r\n");
while(ESP8266_SendCmd("AT\r\n", "OK"))
delay_xms(500);
printf("2. CWMODE\r\n");
while(ESP8266_SendCmd("AT+CWMODE=2\r\n", "OK"))
delay_xms(500);
printf("2.1 AT+RST\r\n");
while(ESP8266_SendCmd("AT+RST\r\n", "OK"))
delay_xms(2000);
printf("3. CWSAP\r\n");
while(ESP8266_SendCmd(ESP8266_AP_INFO, "OK"))
{
delay_xms(500);
}
printf("4. AT+CIPMUX\r\n");
// 启动多连接
while(ESP8266_SendCmd("AT+CIPMUX=1\r\n", "OK"))
{
delay_xms(500);
}
printf("5. CIPSERVER\r\n");
while(ESP8266_SendCmd("AT+CIPSERVER=1,8080\r\n", "OK"))
delay_xms(500);
printf("6. CIFSR\r\n");
while(ESP8266_SendCmd("AT+CIFSR\r\n", "OK"))
delay_xms(500);
printf("6. ESP8266 Init OK\r\n");
return 0;
}
_Bool Esp8266_Init_One_TCP_Client()
{
ESP8266_Clear();
printf("1. CWMODE\r\n");
while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
delay_xms(5000);
// printf("1.1 CWDHCP\r\n");
// while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
// delay_xms(5000);
printf("1.1 CIPSTA\r\n");
while(ESP8266_SendCmd("AT+CIPSTA=\"192.168.1.87\",\"192.168.1.1\",\"255.255.255.0\"\r\n", "OK"))
delay_xms(5000);
//
printf("2. CWJAP\r\n");
while(ESP8266_SendCmd(ESP8266_JAP_INFO, "OK"))//WIFI CONNECTED ;// OK
delay_xms(5000);
// printf("2.1 AT+RST\r\n");
// while(ESP8266_SendCmd("AT+RST\r\n", "OK"))
// delay_xms(20000);
printf("3. CIFSR\r\n");
while(ESP8266_SendCmd("AT+CIFSR\r\n", "OK"))
delay_xms(5000);
printf("4. CIPSTART\r\n");
while(ESP8266_SendCmd(ESP8266_TCP_SERVER_INFO, "ERROR")==0)
delay_xms(5000*10);
printf("4. ESP8266 Init OK\r\n");
return 0;
}
int main(int argc, char *argv[])
{
u8 test_buf[] = "esp8266 $$$$$!\r\n";
// Uart4_Init(115200, argv[1]); // 初始化串口4
Uart5_Init(115200, argv[1]); // 初始化串口5
//async_io_init(); // 异步IO初始化,相当于STM32的外部中断配置的初始化,不过STM32下应该是硬件中断,这里是软件中断
// while (Esp8266_Init())
// {
// printf("Esp8266 Init Failed!!!\r\n");
// }
while (Esp8266_Init_One_TCP_Client())
{
printf("Esp8266 Init Failed!!!\r\n");
}
while (1)
{
if(ESP8266_Recive() == -1)
{
sleep(1);
continue;
}
/* 通信测试 */
if (strstr((const char*)buf, "AA"))
{
ESP8266_SendData(test_buf,strlen(test_buf));
ESP8266_Clear();
}
else if(strstr((const char*)buf, "end"))
{
sleep(1);
ESP8266_Clear();
break;
}
}
/* 退出 */
tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
close(fd);
exit(EXIT_SUCCESS);
}
三、测试结果
初始化部分
1. CWMODE
***********************
AT+CWMODE=1
OK
***********************
1.1 CIPSTA
***********************
AT+CIPSTA="192.168.1.87","192.168.1.1","255.255.255.0"
OK
***********************
2. CWJAP
***********************
AT+CWJAP="TP-LINK_xxxx","123456789"
WIFI DISCONNECT
WIFI CONNECTED
WIFI GOT IP
OK
***********************
3. CIFSR
***********************
AT+CIFSR
+CIFSR:STAIP,"192.168.1.87"
+CIFSR:STAMAC,"34:94:54:7f:d4:71"
OK
***********************
4. CIPSTART
poll read error =0
***********************
AT+CIPSTART="TCP","192.168.1.182",8080
CONNECT
OK
***********************
4. ESP8266 Init OK
主函数循环接收,接收到AA,返回 esp8266 $$$$$!,接收到end,则结束。