基于Linux的ESP8266应用开发

前言:由于我在学习Linux之前是学习STM32单片机的,为了检验自己的学习成果,故将STM32上的ESP8266程序移植到I.MX6ULL板子上,加深对Linux驱动和应用下串口开发的理解。既是学以致用也是记录生活。

本文中的ESP8266移植分为两部分。一部分是配置串口驱动层,采用Linux4.15内核,使用设备树配置串口。本文所用的内核和根文件系统与正点原子Linux驱动视频教程里一致。故本文内容已适配正点原子的I.MX6ULL开发板。(但道理都是一样的,野火家的、韦东山老师家的、讯为家的稍作修改就可以了)I.MX6ULL设备树配置如下:
1、串口4节点配置

&uart4 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_uart4>;
	status = "okay";
};

2、电气属性配置

pinctrl_uart4: uart4grp {
			fsl,pins = <
				MX6UL_PAD_UART4_TX_DATA__UART4_DCE_TX	0x1b0b1
				MX6UL_PAD_UART4_RX_DATA__UART4_DCE_RX	0x1b0b1
			>;
		};

注意:MX6UL_PAD_UART4_TX_DATA__UART4_DCE_TX和MX6UL_PAD_UART4_RX_DATA__UART4_DCE_RX一定要确保不被别的节点或外设复用
3、编译更新设备树
在Linux内核文件夹目录下执行make dtbs,由于本人是通过tftp网络挂载内核和设备树的,直接将新生成的设备树文件放到指定文件夹目录下(如tftp)即可。
第二部分是应用层代码的编写。实质就是在正点原子的串口应用编程例程加入STM32中对ESP8266的操作的相关函数。我觉得学过STM32和FreeRTOS后再学习Linux的话,多多少少都有一些帮助,就像下面代码一样。应用层的串口框架搭建好以后,其中的异步IO(软件中断中的一种)类似STM32中的外部中断一样,用于接收ESP8266发过来的数据。理清楚这些后,剩下的就是对fd(文件描述符)的读写了(即系统调用write()、read()对fd操作)。
应用层代码ESP8266.c如下:

#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>

#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 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;
}


/**
 ** 信号处理函数,当串口有数据可读时,会跳转到该函数执行
 **/
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; //防止串口被刷爆
        esp8266_cnt = read(fd, buf, sizeof(buf));
        printf("%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);
    }
}

//==========================================================
//	函数名称:	ESP8266_Clear
//
//	函数功能:	清空缓存
//
//	入口参数:	无
//
//	返回参数:	无
//
//	说明:		
//==========================================================
void ESP8266_Clear(void)
{
	memset(buf, 0, sizeof(buf));
	esp8266_cnt = 0;
}

//==========================================================
//	函数名称:	ESP8266_WaitRecive
//
//	函数功能:	等待接收完成
//
//	入口参数:	无
//
//	返回参数:	REV_OK-接收完成		REV_WAIT-接收超时未完成
//
//	说明:		循环调用检测是否接收完成
//==========================================================
_Bool ESP8266_WaitRecive(void)
{

	if(esp8266_cnt == 0) 							//如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
		return REV_WAIT;
		
	if(esp8266_cnt == esp8266_cntPre)				//如果上一次的值和这次相同,则说明接收完毕
	{
		esp8266_cnt = 0;							//清0接收计数
			
		return REV_OK;								//返回接收完成标志
	}
		
	esp8266_cntPre = esp8266_cnt;					//置为相同
	
	return REV_WAIT;								//返回接收未完成标志

}

//==========================================================
//	函数名称:	ESP8266_SendCmd
//
//	函数功能:	发送命令
//
//	入口参数:	cmd:命令
//				res:需要检查的返回指令
//
//	返回参数:	0-成功	1-失败
//
//	说明:		
//==========================================================
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
	int ret = 0;
	unsigned char timeOut = 200;

	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(timeOut--)
	{
		if(ESP8266_WaitRecive() == 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);//2s内让串口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);		//发送命令
	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;
}

int main(int argc, char *argv[])
{
    u8 test_buf[] = "OSS迟早暴富^.^!";
    Uart4_Init(115200, argv[1]); // 初始化串口4
    async_io_init(); // 异步IO初始化,相当于STM32的外部中断配置的初始化,不过STM32下应该是硬件中断,这里是软件中断 
    while (Esp8266_Init())
    {
        printf("Esp8266 Init Failed!!!\r\n");
    }

    while (1)
    {
        /* 通信测试 */
        if (strstr((const char*)buf, "AA"))
        {
            ESP8266_SendData(test_buf,strlen(test_buf));
            ESP8266_Clear();
        }
        sleep(1);     
    }
    
    /* 退出 */
    tcsetattr(fd, TCSANOW, &old_cfg);   //恢复到之前的配置
    close(fd);
    exit(EXIT_SUCCESS);
}

最后将上述的ESP8266.c交叉编译放到单板上运行就可以了,操作命令如下:
1、在Ubuntu下执行arm-linux-gnueabihf-gcc ESP8266.c -o TestAPP
2、在板子的根文件系统上执行./TestAPP /dev/ttymxc3
上述是我的习惯操作,大家可以根据自己的配置自己更换名称。
由于我在程序里设置的是AP模式并设为TCP服务器多连接模式,故想测试通信的话,可以在PC或手机端连接该热点后再通过网络调试助手进行通信测试。

总结:多调多试,阳光总在风雨后!如果我文章中哪里说得不太恰当,烦请大家指出,谢谢!

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值