[STM32+ESP8266] 基于STM32控制ESP8266向API接口发送GET请求并解析返回数据

前文写道在使用STM32控制ESP8266通过TCP方式连接上服务器,然后可以和服务器进行通信,收发数据。本文将以苏宁免费的时间接口为例:(👈可以直接点击查看返回结果)
http://quan.suning.com/getSysTime.do
演示如何向服务器的API接口发送GET请求报文,并且解析数据。(博主对网络很不了解,如有错误的地方还请指正)

GET请求格式

具体GET请求的过程我不做多余解释,以免有错误误导读者,如想了解可以搜索一下请求头内容以及解释。
我们在连接服务器成功后可以向服务器发送以下内容,就可以获得相应的结果。

GET http://quan.suning.com/getSysTime.do HTTP/1.1
Host: quan.suning.com


//结尾需要有一个空行并且回车换行,也就是说要有两个换行回车

实际上GET请求全部内容有九行,这里只要发送请求的资源名称以及使用的协议版请求的服务器地址 就可以了。

使用TCP助手测试

在使用ESP8266连接之前,我们先用TCP助手连接服务器,并发送请求测试一下,以保证这个方案确实可行(TCP助手发送的数据和我们使用ESP8266发送的数据一模一样。所以用它来测试向服务器发送GET请求的数据,可以不用改动内容直接使用)。

查看API接口的服务器地址和端口号

因为TCP助手的局限,我们连接时必须先知道API接口的服务器地址和端口号。但是正式使用ESP8266的时候就不必要了,直接使用域名就可以连接。
简单的查看地址的方法有很多,在这里博主介绍使用浏览器方法(测试IE浏览器好像不行,我是用的是QQ浏览器)。
1.直接在浏览器地址栏输入苏宁时间接口的URL,就会看到返回的数据内容(是一串json数据)
2.点击键盘F12,选择Network页签,然后刷新网页,即可看到Name下有两个文件
在这里插入图片描述
3.点击第一个文件即可查看详情。里面包含了整个过程收发的内容。可以看到请求的URL,请求方式,和请求的地址(你看到的可能和我的不一样,都可以使用)
在这里插入图片描述

使用TCP助手连接

打开TCP调试助手然后输入地址和端口号,点击连接网络即可
在这里插入图片描述
至此,我们的准备工作就完成了。此时就相当于我们的ESP8266连接服务器成功。

使用TCP助手发送GET请求报文

下面将我前文说到的报文黏贴进去(请注意格式),点击发送按钮很快就会收到服务器发送回来的数据。
GET请求返回
数据包含响应报文和消息体。
第一行为状态行:200 OK 即是请求成功
空行后面紧接着的就是消息体,内容应该与浏览器显示的结果一致。

如果返回结果有问题,多数是因为复制的问题。删除干净后,输入请求报文内容至发送区。
至此,我们使用TCP调试助手测试成功。

使用ESP8266连接服务器并发送GET请求

如果你已经实现ESP8266连接服务器,并且能收发数据,难么直接使用发送数据命令将请求报文发送出去就可以了。
如果还没有实现,那么可以按照我的博文基于STM32单片机控制ESP8266连接服务器(包含C源码)中的方法来操作。

下面展示具体实现代码:

//头文件中宏定义内容
//时间地址和服务器
#define ESP_TIME_TCP_ADDRESS "quan.suning.com"
#define ESP_TIME_TCP_POINT "80"
//GET请求报文
#define ESP_TIME_INFO  "GET http://quan.suning.com/getSysTime.do HTTP/1.1\r\nHost: quan.suning.com\r\n\r\n" 

//开启透传模式后,直接通过串口向ESP8266发送报文
USART_Send_String(USART2, ESP_TIME_INFO); //发送GET请求报文,获得返回结果
while(!(ESP_RX_STATE > 0));  //判断接收标志
/*
   此处写收到结果后做的解析处理
*/
    ESP_RX_STATE --;//解析数据置位

如此下来,我们发送了GET请求并且还能接受到返回的结果,是不是超简单。那么如何处理结果呢?

解析返回结果

我们收到的数据格式为json格式,这是一种广泛使用数据交换格式。有专用的cJSON库可以进行解析,具体这里就不解释怎么使用了,博主使用另外的笨方法:暴!力!破!解!

解析数据

因为我们的数据格式非常固定,我可以针对这个格式进行解析。再来回看下接收到的内容:
{“sysTime2”:“2019-11-02 22:38:52”,“sysTime1”:“20191102223852”}
使用了两种时间表示方法,都可以使用。从"sysTime1"后的第三位开始就是我们需要的结果,因此我只要使用函数 strstr()函数定位到"sysTime1"的位置往后移动11位(strstr函数返回的是查找的字符串的首地址)就可以了。

 data_pt = strstr((const char *) ESP_RX_BUF.buf,(const char *)"sysTime1");   //寻找到时间结果的地址,首地址返回给字符类型的指针变量(char*) data_pt 

年的首地址移动11位;

 year_string = data_pt + YERA_ADD_DRES;   //年份地址

月份首地址移动15位;
日期首地址移动17位;
小时首地址移动19位;
分钟首地址移动21位;
秒钟首地址移动23位;
以此类推可以得到每个数据的位置(其实直接可以当一个字符串处理)。

处理数据

我们其实是得到了字符串类型的时间数据,已经可以直接使用了。博主为了方便运算继续进行了数据转换,以便其他位置的使用。

获得年:

//获得年函数(因为以年开始的字符串长度过长,所以使用不同的方法)
//输入值是年位置的地址
//返回值是 整型的10进制四位数
int Get_Year(char *y)
{
   int year_return;
   char *year_temp;
   char year[5] = {0};
   char i;
//年的获取需要提取一次字符串,否则无法读取  
   year_temp = y;
   for(i = 0; i<4; i++)
   {
       year[i] = *year_temp;
       year_temp ++;
   }

   year_return =  atoi(&year[0]); 
   return year_return; 
}

获得月份:

//获得月份函数
//输入值是月份位置的地址
//返回值是 整型的10进制两位数
int Get_Moonth(char *m)
{
     int moonth_return;
     moonth_return = atoi(m)/100000000;   //取月份    
     return moonth_return;
}

获得日期

//获得日期函数
//输入值是日期位置的地址
//返回值是 整型的10进制两位数
int Get_Day(char *d)
{
    int day_return;
    day_return = atoi(d)/1000000;   //取日期
 
    return day_return;
}

获得时间

//获得时间
//输入值是时间的位置的地址
//返回值是 整型的10进制的时间总秒数
int Get_Times(char *h, char *m, char *s)
{
    int time_return;
    int hour_return;
    int min_return;
    int sec_return; 

    hour_return = atoi(h)/10000;  //取小时
    min_return = atoi(m)/100;   //取分钟
    sec_return = atoi(s);   //取秒数
 
    time_return = hour_return*3600 + min_return*60 + sec_return;   //转换成总秒数
 
    return time_return;
}

完整解析过程

我的整个处理时间的代码如下:

//定义时间存储的全局变量(也可以定义成结构体)
__IO uint32_t TIMES = 0;
__IO uint32_t YEARS = 2019; 
__IO uint32_t MOONS = 1; 
__IO uint32_t DAYS = 1;
__IO uint32_t WEEKS = 0;

//获取网络时间并解析给系统
void GET_Net_Time(void)
{
    char *data_pt;
    char *day_string;
    char *moon_string;
    char *year_string;
    char *hour_string;
    char *minute_string;
    char *second_string;
    
    RT_Send_String(USART2, ESP_TIME_INFO);  //发送GET请求
    
    while(!(ESP_RX_STATE > 0));  //判断接收标志
    data_pt = strstr((const char *) ESP_RX_BUF.buf,(const char *)"sysTime1");   //寻找到时间结果的地址
    ESP_RX_STATE --;
    
   if(data_pt != NULL)
   {
         day_string = data_pt + DAYS_ADD_DRES;    //日期地址
         moon_string = data_pt + MOON_ADD_DRES;   //月份地址
         year_string = data_pt + YERA_ADD_DRES;   //年份地址
         hour_string = data_pt + HOURS_ADD_DRES;  //小时地址
         minute_string = data_pt + MINUTES_ADD_DRES;  //分钟地址
         second_string = data_pt + SECONDS_ADD_DRES;   //秒中地址
   
    
   
//将时间信息传递给全局变量   
        DAYS = Get_Day(day_string);
        MOONS = Get_Moonth(moon_string);
        YEARS = Get_Year(year_string);
        TIMES = Get_Times(hour_string, minute_string, second_string);
   }
   else
   {
        printf("get net time failed!\r\n");
   }
}

//获得年函数(以年开始的字符串长度过长,所以使用不同的方法)
//输入值是年位置的地址
//返回值是 整型的10进制四位数
int Get_Year(char *y)
{
   int year_return;
   char *year_temp;
   char year[5] = {0};
   char i;
//年的获取需要提取一次字符串,否则无法读取  
   year_temp = y;
   for(i = 0; i<4; i++)
   {
       year[i] = *year_temp;
       year_temp ++;
   }

   year_return =  atoi(&year[0]); 
   return year_return; 
}

//获得月份函数
//输入值是月份位置的地址
//返回值是 整型的10进制两位数
int Get_Moonth(char *m)
{
     int moonth_return;
     moonth_return = atoi(m)/100000000;   //取月份    
     return moonth_return;
}

//获得日期函数
//输入值是日期位置的地址
//返回值是 整型的10进制两位数
int Get_Day(char *d)
{
    int day_return;
    day_return = atoi(d)/1000000;   //取日期
 
    return day_return;
}

//获得时间
//输入值是时间的位置的地址
//返回值是 整型的10进制的时间总秒数
int Get_Times(char *h, char *m, char *s)
{
    int time_return;
    int hour_return;
    int min_return;
    int sec_return; 

    hour_return = atoi(h)/10000;  //取小时
    min_return = atoi(m)/100;   //取分钟
    sec_return = atoi(s);   //取秒数
 
    time_return = hour_return*3600 + min_return*60 + sec_return;   //转换成总秒数
 
    return time_return;
}

如此,所有的数据解析完毕,剩下的就是使用啦,具体怎么显示根据自己的方法实现。

要实现微信小程序和STM32+ESP8266之间的TCP通信,需要在STM32上使用TCP协议栈来实现TCP连接和数据传输。在这里,我以STM32F103为例,使用标准库进行开发。 首先,需要在STM32上初始化ESP8266模块并建立TCP连接。下面是一个简单的示例代码: ```c #include "stm32f10x.h" #include "stdio.h" #include "string.h" // ESP8266连接信息 #define WIFI_SSID "your_ssid" #define WIFI_PASSWORD "your_password" #define TCP_SERVER_IP "192.168.1.100" #define TCP_SERVER_PORT 1234 // 接收缓冲区大小 #define RX_BUF_SIZE 1024 // 接收缓冲区 uint8_t rx_buf[RX_BUF_SIZE]; uint16_t rx_index = 0; // USART1初始化函数 void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能USART1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置PA9为推挽输出,TXD GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA10为浮空输入,RXD GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // USART1初始化 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStructure); // 使能USART1 USART_Cmd(USART1, ENABLE); } // 发送数据到USART1 void USART1_SendData(uint8_t *data, uint16_t len) { for(uint16_t i = 0; i < len; i++) { USART_SendData(USART1, data[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } } // 接收数据 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { rx_buf[rx_index++] = USART_ReceiveData(USART1); } } // 初始化ESP8266 void ESP8266_Init(void) { // 等待模块启动完成 DelayMs(500); // 发送AT指令,检查模块是否正常 ESP8266_SendCmd("AT\r\n"); if(!ESP8266_WaitResponse("OK", 1000)) { printf("ESP8266 init failed!\r\n"); while(1); } // 设置WIFI模式为STA ESP8266_SendCmd("AT+CWMODE=1\r\n"); if(!ESP8266_WaitResponse("OK", 1000)) { printf("ESP8266 set mode failed!\r\n"); while(1); } // 连接WIFI ESP8266_SendCmd("AT+CWJAP=\"" WIFI_SSID "\",\"" WIFI_PASSWORD "\"\r\n"); if(!ESP8266_WaitResponse("OK", 5000)) { printf("ESP8266 connect wifi failed!\r\n"); while(1); } // 获取本地IP地址 ESP8266_SendCmd("AT+CIFSR\r\n"); if(!ESP8266_WaitResponse("+CIFSR:STAIP,\"", 1000)) { printf("ESP8266 get ip failed!\r\n"); while(1); } // 解析IP地址 uint8_t *ip_str = strstr((char *)rx_buf, "+CIFSR:STAIP,\""); uint8_t *ip_end = strstr((char *)ip_str + 15, "\""); if(ip_str && ip_end) { *ip_end = 0; printf("ESP8266 IP: %s\r\n", ip_str + 15); } else { printf("ESP8266 parse ip failed!\r\n"); while(1); } } // 建立TCP连接 void TCP_Connect(void) { // 发送AT指令,建立TCP连接 char cmd[128]; sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", TCP_SERVER_IP, TCP_SERVER_PORT); ESP8266_SendCmd(cmd); if(!ESP8266_WaitResponse("CONNECT", 5000)) { printf("TCP connect failed!\r\n"); while(1); } } int main(void) { // 初始化USART1 USART1_Init(); // 初始化ESP8266 ESP8266_Init(); // 建立TCP连接 TCP_Connect(); while(1) { // 发送数据到服务器 uint8_t data[] = "Hello, world!"; uint16_t len = sizeof(data) - 1; char cmd[128]; sprintf(cmd, "AT+CIPSEND=%d\r\n", len); ESP8266_SendCmd(cmd); if(!ESP8266_WaitResponse(">", 1000)) { printf("TCP send data failed 1!\r\n"); continue; } USART1_SendData(data, len); if(!ESP8266_WaitResponse("SEND OK", 1000)) { printf("TCP send data failed 2!\r\n"); continue; } } } ``` 在上面的代码中,我们使用USART1与ESP8266模块进行通信,并在USART1的中断服务函数中接收数据。在初始化ESP8266模块后,我们首先连接WIFI并获取本地IP地址,然后通过发送AT指令来建立TCP连接。在建立连接后,我们可以通过发送AT指令和USART1将数据发送到服务器。 接下来,我们需要通过TCP协议栈来实现TCP连接和数据传输。使用TCP协议栈可以使我们更方便地控制TCP连接,并提供更高效的数据传输。下面是一个简单的TCP客户端示例代码: ```c #include "stm32f10x.h" #include "stdio.h" #include "string.h" #include "lwip/opt.h" #include "lwip/arch.h" #include "lwip/api.h" #include "lwip/tcp.h" #include "lwip/tcpip.h" #include "netif/etharp.h" // ESP8266连接信息 #define WIFI_SSID "your_ssid" #define WIFI_PASSWORD "your_password" #define TCP_SERVER_IP "192.168.1.100" #define TCP_SERVER_PORT 1234 // 接收缓冲区大小 #define RX_BUF_SIZE 1024 // 接收缓冲区 uint8_t rx_buf[RX_BUF_SIZE]; uint16_t rx_index = 0; // TCP协议栈任务 void tcp_task(void *arg) { struct netconn *conn = NULL; err_t err; struct netbuf *buf; // 创建TCP连接 conn = netconn_new(NETCONN_TCP); if(conn == NULL) { printf("TCP connect failed!\r\n"); return; } err = netconn_connect(conn, IP_ADDR_ANY, TCP_SERVER_PORT); if(err != ERR_OK) { printf("TCP connect failed!\r\n"); return; } while(1) { // 接收数据 err = netconn_recv(conn, &buf); if(err != ERR_OK) { continue; } // 处理数据 uint16_t len = buf->p->tot_len; uint8_t *data = malloc(len); if(data != NULL) { uint16_t index = 0; struct pbuf *q; for(q = buf->p; q != NULL; q = q->next) { memcpy(data + index, q->payload, q->len); index += q->len; } printf("TCP recv data: %s\r\n", data); free(data); } // 释放缓冲区 netbuf_delete(buf); } } // USART1初始化函数 void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能USART1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置PA9为推挽输出,TXD GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA10为浮空输入,RXD GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // USART1初始化 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStructure); // 使能USART1 USART_Cmd(USART1, ENABLE); // USART1中断配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 使能USART1接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } // 发送数据到USART1 void USART1_SendData(uint8_t *data, uint16_t len) { for(uint16_t i = 0; i < len; i++) { USART_SendData(USART1, data[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } } // 接收数据 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { rx_buf[rx_index++] = USART_ReceiveData(USART1); } } // 初始化ESP8266 void ESP8266_Init(void) { // 等待模块启动完成 DelayMs(500); // 发送AT指令,检查模块是否正常 ESP8266_SendCmd("AT\r\n"); if(!ESP8266_WaitResponse("OK", 1000)) { printf("ESP8266 init failed!\r\n"); while(1); } // 设置WIFI模式为STA ESP8266_SendCmd("AT+CWMODE=1\r\n"); if(!ESP8266_WaitResponse("OK", 1000)) { printf("ESP8266 set mode failed!\r\n"); while(1); } // 连接WIFI ESP8266_SendCmd("AT+CWJAP=\"" WIFI_SSID "\",\"" WIFI_PASSWORD "\"\r\n"); if(!ESP8266_WaitResponse("OK", 5000)) { printf("ESP8266 connect wifi failed!\r\n"); while(1); } // 获取本地IP地址 ESP8266_SendCmd("AT+CIFSR\r\n"); if(!ESP8266_WaitResponse("+CIFSR:STAIP,\"", 1000)) { printf("ESP8266 get ip failed!\r\n"); while(1); } // 解析IP地址 uint8_t *ip_str = strstr((char *)rx_buf, "+CIFSR:STAIP,\""); uint8_t *ip_end = strstr((char *)ip_str + 15, "\""); if(ip_str && ip_end) { *ip_end = 0; printf("ESP8266 IP: %s\r\n", ip_str + 15); } else { printf("ESP8266 parse ip failed!\r\n"); while(1); } } int main(void) { // 初始化USART1 USART1_Init(); // 初始化ESP8266 ESP8266_Init(); // 初始化TCP协议栈 tcpip_init(NULL, NULL); // 创建TCP协议栈任务 sys_thread_new("tcp_task", tcp_task, NULL, 512, 2); while(1) { // 发送数据到服务器 uint8_t data[] = "Hello, world!"; uint16_t len = sizeof(data) - 1; char cmd[128]; sprintf(cmd, "AT+CIPSEND=%d\r\n", len); ESP8266_SendCmd(cmd); if(!ESP8266_WaitResponse(">", 1000)) { printf("TCP send data failed 1!\r\n"); continue; } USART1_SendData(data, len); if(!ESP8266_WaitResponse("SEND OK", 1000)) { printf("TCP send data failed 2!\r\n"); continue; } } } ``` 在上面的代码中,我们使用lwIP协议栈来实现TCP连接和数据传输。首先,我们需要在主函数中调用tcpip_init函数来初始化lwIP协议栈。然后,我们创建一个TCP协议栈任务,并在任务中使用netconn_new函数创建一个TCP连接。在循环中,我们使用netconn_recv函数接收数据,并使用netbuf_delete函数释放缓冲区。 在主函数中,我们使用USART1与ESP8266模块进行通信,并在USART1的中断服务函数中接收数据。在初始化ESP8266模块后,我们首先连接WIFI并获取本地IP地址,然后通过发送AT指令和USART1将数据发送到服务器。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值