关于esp8266介绍网上挺多资料的,我就不再介绍了,有不懂的地方欢迎讨论交流。主要讲解一下如何配置esp8266获取心知天气数据,并通过穿cJSON库解析其中的关键数据。
背景知识
粗略了解就行,看不懂代码再回去看对应内容。
1.GPIO输入输出(IO口配置)
2.串口通信(通过串口发送AT指令给esp8266)
3.定时器中断(通过定时器中断,将每次接收数据区分开)
4.cJSON(C语言无法直接处理get请求获取的json数据包,使用cJSON库获取关键数据)
关键函数说明
cJSON*cJSON_Parse(const char *value);//解析原始JSON数据包,并按照cJSON结构体的结构序列化整个数据包。
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string)://获得字典元素object中键名为string的元素,key-value对
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);//获得数组元素array第index的元素
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);//释放cJSON_Parse函数内部申请的堆内存。
5.心知天气使用说明
心知天气是免费的,通过get命令获取天气数据时需要自己的密钥,下面展示如何获得密钥
注册以后选择免费试用
查看api使用说明
配置流程
1.配置led和按键:led显示状态,key选择执行命令
2.配置串口1:输出信息到上位机,方便调试
3.配置定时器3:方便区分串口每次接收到的数据包
4.配置串口2:与esp8266进行数据传输
使用流程
硬件连接;
ESP8266-RX->PA2
ESP8266-TX->PA3
ESP8266-3V3->3V3
ESP8266-GND->GND
英文翻译问的gpt方便记忆。AT指令后面必须加空行。使用串口2向esp8266发送AT指令进行配置。
配置esp8266指令流程 | 成功返回值 | 作用 |
1.AT | OK | 返回OK表示识别到esp8266模块 |
2.AT+CWMODE=1 | OK | 设置wifi模块工作为sta模式(connection wifi mode),会记录不用每次配置 |
3.AT+RST | 重启模块并生效 | |
4.AT+CWJAP="WIFI名",“wifi密码” | WIFI CONNECTED WIFI GOT IP | 连接wifi(connection wifi join access point,连接wifi接入点)。会记录不用每次配置。重复配置会先显示WIFI DISCONNECTED,在进行连接。连接完成需要4-6秒(可以自行测试再选择延迟时间),中可以多等待一会。 |
5.AT+CIPMUX=0 | OK | 禁用多连接(Connection IP Multiple Connection) |
6.AT+CIPSTART="TCP","目标ip“,目标端口 | CONNECT OK | 连接服务器指定端口,每次要重新配置(Connection IP Start) |
7.AT+CIPMODE=1 | OK | 开启透传模式(connect ip mode),AT指令失效。每次传输不用设置数据长度,每次要重新配置 |
8.AT+CIPSEND | > | 开始传输数据(connection ip send) |
9.GET https://api.seniverse.com/v3/weather/now.json?key=Ses06roxQh0JI5CRq&location=wuxi&language=zh-Hans&unit=c | 天气数据 | GET请求获取天气数据 |
10.+++(不要加换行) | 无 | 退出透传模式,AT指令生效。透传模式中AT指令会作为数据直接发送到所连接服务器 |
关键代码
实验现象,一次打印初始化信息,并获得一次天气数据
按下key0键,再次获取天气数据
wifista.c
#include "wifista.h"
#include "usart.h"
#include "usart2.h"
#include "string.h"
#include "delay.h"
//#include <stdlib.h>
//WIFI STA模式,设置要去连接的路由器无线参数,请根据你自己的路由器设置,自行修改.
const u8* WIFI_NAME="HL"; //路由器SSID号
const u8* wifista_encryption="WPA_WAP2_PSK"; //wpa/wpa2 aes加密方式
const u8* WIFI_PSW="qq123456"; //连接密码
//将AT指令接收到的应答数据通过串口1返回给电脑
//mode:0 不清零串口2状态标志位
// 1 清零串口2状态标志位
void atk_8266_at_response(mode)
{
if(USART2_RX_STA&1<<15)//串口2接收完成
{
USART2_RX_BUF[USART2_RX_STA]=0;//添加结束符
printf("%s",USART2_RX_BUF);//通过串口1输出串口2接收到的数据
if(mode) USART2_RX_STA=0;
}
}
//检查接收到的应答
//str:期待应答值
//返回值:0 未获得期待应答
// 其他 期待应答结果的位置
u8* atk_8266_check_cmd(u8 *str)
{
u8 *strx=0;//创建指向内存地址为0的位置,避免未定义行为
if(USART2_RX_STA&0x8fff)//接受到一次数据
{
USART2_RX_BUF[USART2_RX_STA]=0;//添加结束符
strx=strstr((const u8*)USART2_RX_BUF,(const u8*)str);//通过strstr函数查找str在USART2_RX_BUF中出现的位置
}
return strx;
}
//向esp8266发送命令
//cmd:发送到命令
//ack:期望应答
//waittime:最大等待应答时间(单位10ms)
//返回值:0 发送成功(得到了期待应答信号)
// 1 发送失败
u8 atk_8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime)
{
u8 res=0;
USART2_RX_STA=0;//将串口2状态标志位清零,使串口2可以接收数据
u2_printf("%s\r\n",cmd);//发送命令,自动添加了换行符,所以传入的AT指令不用加换行符
if(ack&&waittime)//需要应答
{
while(--waittime)//等待倒计数
{
delay_ms(10);//每10ms检查一次是否接收到期望应答信号
if(USART2_RX_STA&0x8000)//串口2接收到数据
{
// printf("%s返回结果为:%s",cmd,USART2_RX_BUF);//调试信息查看
if(atk_8266_check_cmd(ack))//检查接收数据是否为期望应答
{
// printf("%s ask:%s\r\n",cmd,(u8*)ack);//调试信息查看
break;
}
USART2_RX_STA=0;//未接收到期望数据则将接收标志位清零,继续等待接收
}
}
if(waittime==0) res=1;//达到最大等待时间还是没有接收到目标信号,表示发送失败
}
return res;
}
//退出通透模式
//返回值;0 退出成功
// 1 退出失败
u8 atk_8266_quit_trans(void)
{
u2_printf("+++");//发送+++退出透传模式,一定不能将换行符"\r\n"
delay_ms(100);
return atk_8266_send_cmd("AT","OK",20);//退出透传判断.
}
//esp8266连接初始化,配置为sta模式,并连接wifi
u8 atk_8266_wifista_config(void)
{
// u8 *p=malloc(100*sizeof(char));
while(atk_8266_send_cmd("AT","OK",20))//1.检查wifi模块是否在线
{
printf("connect fail\r\n");
printf("try to connect\r\n");
}
atk_8266_send_cmd("AT+CWMODE=1","OK",20);//2.配置es8266工作为sta模式
u2_printf("AT+RST/r/n");//3.重启模块
delay_ms(4000);//延时4s等待模块重启
// sprintf((char*)p,"AT+CWJAP=\"%s\",\"%s\"",WIFI_NAME,WIFI_PSW);
while(atk_8266_send_cmd("AT+CWJAP=\"HL\",\"qq123456\"","WIFI GOT IP",600))//4.连接wifi,连接时间较长,设置最大等待时间为600*10=6000ms
{
printf("正在连接wifi\r\n");
}
printf("wifi连接成功\r\n");
u2_printf("AT+CIPMUX=0");//5.禁用多连接
// free(p);
return 0;
}
wifista.h
#ifndef __WIFISTA_H
#define __WIFISTA_H
#include "sys.h"
#include "usart2.h"
u8 atk_8266_wifista_config(void);//esp8266初始化为sta模式
u8 atk_8266_quit_trans(void);//退出透传
u8 atk_8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime);//发送指令
#endif
weather.c
#include "weather.h"
#include "wifista.h"
#include "usart.h"
#include "usart2.h"
#include "delay.h"
#include "cJSON.h"
#define WEATHER_SERVERIP "www.seniverse.com" //心知天气域名
#define WEATHER_PORTNUM "80"//心知天气端口号
//通过cJSON库提取获得json文件中的温度信息
//返回值:0 提取成功
// 1 提取失败
//一定要修改startup_stm32f10x_hd.s文件中的堆栈空间(扩大为0x800即可),否则无法提取:Heap_Size EQU 0x00000800
u8 parse_now_weather()
{
cJSON *root;
cJSON *results_array;
cJSON *first_result;
cJSON *now_object;
cJSON *temperature_value;
root = cJSON_Parse((const char*)USART2_RX_BUF);//提取最外层字典对象
if (root == NULL) {
printf("Error parsing JSON\n");
return 1;
}
// 获取 "results" 数组
results_array = cJSON_GetObjectItemCaseSensitive(root, "results");
if (!cJSON_IsArray(results_array)) {
printf("Error: 'results' is not an array\n");
cJSON_Delete(root);
return 1;
}
// 获取数组中的第一个元素
first_result = cJSON_GetArrayItem(results_array, 0);
// 获取 "now" 对象
now_object = cJSON_GetObjectItemCaseSensitive(first_result, "now");
// 获取温度值
temperature_value = cJSON_GetObjectItemCaseSensitive(now_object, "temperature");
if (cJSON_IsString(temperature_value)) {
printf("Temperature: %s\n", temperature_value->valuestring);//打印温度信息
} else {
printf("Error: Unable to retrieve temperature\n");
}
// 释放 cJSON 结构
cJSON_Delete(root);
return 0;
}
//获取一次实时天气
u8 get_current_weather(void)
{
u8 res=1;
USART2_RX_STA=0;//开启串口2数据接收
while(atk_8266_send_cmd("AT+CIPSTART=\"TCP\",\"api.seniverse.com\",80\r\n","CONNECT",200))//6.服务器连接会断开,所以最好每次查询天气都重新连接服务器
{
printf("服务器连接失败\r\n");
}
printf("服务器连接成功\r\n");
while(atk_8266_send_cmd("AT+CIPMODE=1\r\n","OK",200));//7.传输模式为透传
printf("透传模式开启\r\n");
while(atk_8266_send_cmd("AT+CIPSEND\r\n",">",20));//8.开始透传
printf("准备发送get请求\r\n");
delay_ms(100);
while(atk_8266_send_cmd("GET https://api.seniverse.com/v3/weather/now.json?key=Ses06roxQh0JI5CRq&location=wuxi&language=zh-Hans&unit=c","results",200))//9.GET请求获取天气数据
{
printf("数据获取失败\r\n");
}
printf("天气数据为:%s\r\n",USART2_RX_BUF);
parse_now_weather();//使用CJSON解析数据
atk_8266_quit_trans();//退出透传等待下一次天气查询命令
return res;
}
weather.h
#ifndef __WEATHER_H
#define __WEATHER_H
#include "sys.h"
u8 get_current_weather(void);//获得当前天气
#endif
main.c
#include "stm32f10x.h"
#include "led.h"
#include "stdarg.h"
#include "timer.h"
#include "delay.h"
#include "key.h"
#include "usart2.h"
#include "usart.h"
#include "wifista.h"
#include "weather.h"
#include "cJSON.h"
int main(void)
{
int k=0;
usart2_init(115200);//串口2初始化,与esp8266进行数据传输
Led_Init();//led初始话
key_init();//按键初始化
delay_init();//延时函数初始化
uart_init(115200);//串口1初始化,打印配置信息
atk_8266_wifista_config();//esp8266初始化
get_current_weather();//获取一次天气信息,并提取温度值
while(1)
{
if(USART2_RX_STA&0x8000)//通过串口1输出串口2接受到esp8266返回数据
{
USART2_RX_BUF[USART2_RX_STA&0x7fff]=0;
printf("res---:%s,USART2_RX_STA=%x\r\n",USART2_RX_BUF,USART2_RX_STA);
USART2_RX_STA=0;
}
if(USART_RX_STA&0x8000)//将串口1接收数据发送给串口2
{
USART_RX_BUF[USART_RX_STA&0x3fff]=0;
u2_printf("%s\r\n",USART_RX_BUF);
printf("usart1:%s,len=%x\r\n",USART_RX_BUF,USART_RX_STA);
USART_RX_STA=0;
}
k=key_scan(0);//获得按键值
if(k==KEY0_PRES)//按键0,获得一次天气数据
{
get_current_weather();
USART2_RX_BUF[USART2_RX_STA&0x7fff]=0;
printf("GET:%s,USART2_RX_STA=%x\r\n",USART2_RX_BUF,USART2_RX_STA);
}
else if(k==KEY1_PRES)//按键1,退出透传模式
{
atk_8266_quit_trans();
}
}
}
问题总结
1.无法通过cJSON解析数据,运行文件不报错,但是一直无法解析到温度数据。
cJSON解析数据占用较大栈空间,一定要修改startup_stm32f10x_hd.s文件中的堆栈空间(扩大为0x800即可),否则无法提取。
2.发送AT指令接收不到返回。
配置接入透传模式以后没有退出,复位单片机esp8266保持上电,依旧没有退出通透模式,无法识别AT指令。
3.连接wifi时,一直无法接收到“WIFI GOT IP”指令,无法退出循环。
wifi连接时间较长,需要4-6s可以多等待一会
4.代码无法退出透传模式
进入透传以后输入“+++”,模块退出透传。注意两点1.“+++”软件发送时后面不要加其他东西包括“\r\n”换行符,透过上位机串口发送时,不要勾选发送新行。2.发送"+++"以后需要延迟一段时间,测试100ms可以。
5.报错。
在usart.c文件中添加如下代码块
_ttywrch(int ch)
{
ch = ch;
}