NodeMCU-V1.0
- 核心板型号:ESP-12-E,4Mbytes(32Mbits)的flash
- 开源官网
- 官网烧录工具使用:
- SPI模式选择:DIO
- FLASH SIZE选择:32Mbit
- 将最新的固件烧写到0x00000处
- 在烧写之前,需要按住FLASH按键不动然后按下RST按键一次
- nodemcu专用烧录工具:只需连接USB串口,选择好烧录文件,单击Flash即可完成烧写
- Pin map
- 其中D0(GPIO16)只能被用作gpio read/write,不支持中断,不支持pwm、i2c、onewire
- Lua core based on eLua project
- cjson based on lua-cjson
- File system based on spiffs
- 事件驱动的编程模型
- 内建的模块:node,json,file,timer,pwm,i2c,spi,onewire,net,mqtt,coap,gpio,wifi,adc,uart,bit,u8g,ucg,ws2801,ws2812,crypto,dht,rtc,sntp,bmp085,tls2561,hx711,system
- Node MCU API
ESP8266介绍
- 内核架构:XtensaLX106(32位),由Tensilica公司开发,是个可配置软核CPU,指令集可扩展,扩展的指令集通过硬件实现。通常扩展指令集有几种方式:
- fusion,就是将多条指令合并为一条指令,从而缩短程序所需要的cycle数
- SIMD,就是单指令多数据
- 默认串口波特率9600。当工作在AP模式时,默认的ip地址为192.168.4.1
- CPU主频支持80MHz和160MHz,支持RTOS。内部三条总线:iBus访问内部,外部存储器;dBus访问数据RAM;AHB访问内部寄存器
- 支持的无线网络类型:STA/AP/STA+AP
- 安全机制:WEP/WPA-PSK/WPA2-PSK
- 加密类型:WEP64/WEP128/TKIP/AES
- 固件升级:本地串口,OTA云端升级
- 网络协议:IPv4,TCP/UDP/FTP/HTTP
- 支持的硬件接口:UART,I2C,PWM,GPIO,ADC(10位,ADC的范围在0~1V)
- 串口数据传输最大传输速率为460800bps
- 系统上电会运行厂商芯片内部的Boot loader,而在bootloader中串口波特率被设置为了74880
- ESP8266的PWM频率100~500Hz
- GPIO输出电压为VDD_IO(比如3.3V),输出电流应该不超过20mA
- ESP8266有两个uart,其中uart0有Tx、Rx,可做数据传输,uart1仅有Tx,可做串口调试信息打印
- ESP8266有两套MAC,因此可以支持softAP+station共存的模式
- ESP8266 softAP可连接4个station
- ESP8266低功耗只针对station模式,对于softAP则没有低功耗模式
- ESP8266的TCP连接最多可以建立5个,UDP连接最多可以建立5个,可同时建立5个TCP连接和5个UDP连接
- ESP8266片上没有ROM,用户程序存放在外部SPI Flash中。最大支持16MByte的容量。支持的SPI模式:Standard SPI,Dual SPI,DIO SPI,QIO SPI以及Quad SPI。注意,在下载固件时需要在下载工具选择对应模式,否则下载后程序将无法得到正确的运行
- ESP8266芯片定义了1个SDIO Slave接口,SDIO由硬件实现,支持4位25MHz SDIOv1.1和4位50MHz SDIO v2.0
最小系统
- Pin11和Pin17两个数字电源管脚,数字电源无需在电路中增加滤波电容。数字电源工作电压范围1.8v~3.3V
- 在模拟电源部分,要注意当ESP8266工作在TX时候,瞬间电流会加大,往往会引起电源的轨道塌陷,所以在设计时在模拟电源电路上增加一个0603或者0805封装的10uF电容,此电容可与0402封装的0.1uF电容搭配
- 在PIN21 SD_CLK管脚上串联一个0402封装的电阻连接到Flash CLK管脚上。此电阻的作用主要为:降低驱动电流,减小串扰和外部干扰,调节时序等。串联电阻大小为200ohm
- RES12K(Pin31)需外接12K对地电阻,该电阻作为芯片bias控制电流的电阻,对精度要求比较高,建议采用12K±1%精度的电阻
- ESP8266模组
- Layout:
- 第一层Top层主要用于走信号线和摆件
- 第二层为GND层,不走信号线,保证一个完整的GND平面
- 第三层为POWER层,尽量走电源线
- 第四层为Bottom层,建议Bottom层不摆件,只走信号线
- 3.3V电源线线宽必须>15mil,走线尽量走第三层(POWER层),到达芯片管脚处时打过孔到达TOP层连接芯片管脚。在过孔处理上,VIA的直径需要大于电源走线的宽度,而且drill应始终,略大于VIA的半径即可
- 晶振位置尽量靠近芯片的XTAL Pins,走线不要太长,同时晶振走线必须用地包起来良好屏蔽;晶振的输入输出走线不能打孔走线,即不能跨层。金正的输入输出走线不能交叉,跨层较差也不行。晶振的输入输出的bypass电容要靠近芯片左右侧摆放,尽量不要放在走线上;晶振下方4层都不能走高频数字信号,最佳情况是晶振下方不走任何信号线,晶振TOP面的铺铜区域越大越好。晶振为敏感器件,晶振周围不能有磁感应器件,比如大电感。
- RF走线必须控制特性阻抗为50Ω,保证第二层完整地平面,周围地孔屏蔽,走线长度尽量短。RF走线尽量保持在10mil以上;RF走线需预留一个π型匹配网络,且π性匹配电路靠近芯片RF Pin脚摆放。芯片到天线的RF走线不能有过孔,即不能跨层走线。RF走线不能走直角或者45°角,如果有需要则使用圆弧走线。RF走线附近不能有高频信号线。RF上的天线必须远离所有传输高频信号的器件,比如晶振,DDR,一些高频时钟
- Layout:
编译
- esp_iot_sdk_v0.9.5及之后版本的软件简化了编译脚本,编译指令:./gen_misc.sh,根据提示按要求输入编译参数
- boot_v1.1与boot_v1.2+:boot_v1.2相对编译时将程序排列的更紧凑,省flash空间;boot_v1.3主要支持增强启动模式可用于产测
- 不支持云端升级:flash.bin+iromtext.bin,支持云端升级:boot.bin+user1.bin
- 注意编译不同大小的bin时,其烧录地址不同
- bin目录存放需要下载到Flash的bin文件
- at文件夹:Espressif提供的支持AT指令的bin文件
- upgrade文件夹,编译生成的支持云端升级的bin文件(user1.bin或user2.bin)
- bin文件下根目录,编译生成的不支持云端升级的bin文件,和其他Espressif提供的bin文件
- 编译生成user1.bin后,先运行make clean清除上次编译生成的临时文件后,再编译生成user2.bin
- 每个bin编译成功后,会提示该bin的烧录位置,典型烧写位置:
- 编译esp_iot_sdk_v0.9.4及之前版本软件
- 指令:./gen_misc.sh
- 支持云端升级(FOTA)的编译步骤如下:
- 运行./gen_misc_plus.sh 1,在esp_iot_sdk/bin/upgrade路径下生成user1.bin
- 运行make clean,清除之前的编译信息
- 运行./gen_misc_plus.sh 2,在esp_iot_sdk/bin/upgrade路径下生成user2.bin
- 针对编译时STEP1和STEP5的选择不同,对应的flash size和flash map不同。
- 系统参数区(system param)始终为flash的最后16KB
- 用户参数区(User param)指Espressif提供的示例软件(IOT_Demo或AT)中设定的用户参数区。如果用户自行实现应用程序,则可以将用户参数存放在flash任意空闲区域
- 用户数据区(UserData),可能空闲,当程序区域未占满flash空间时,剩余空间可供用户存储数据
- none boot-不支持云端升级。编译时Step1选择2,编译生成eagle.flash.bin(简称flash.bin)和eagle.irom0text.bin(简称irom0text.bin),不支持云端升级,则STEP5时选择不同flash size对应的布局如下:
- 512KB flash
- 1024KB flash
\esp_iot_sdk\ld 路径的“eagle.app.v6.ld”文件,其中irom0_0_seg的len即设置irom0text.bin的上限值。对于1024KB flash,此len最大可修改为0xBC000,irom0text.bin 最大支持到752KB
- 2048KB flash
\esp_iot_sdk\ld 路径的“eagle.app.v6.ld”文件,其中irom0_0_seg的len即设置irom0text.bin的上限值。对于2048KB flash,此len最大可修改为0xC0000,irom0text.bin 最大支持到768KB(因为ESP8266目前程序区最大支持1024KB,1024-256=768)
- 4096KB flash
\esp_iot_sdk\ld 路径的“eagle.app.v6.ld”文件,其中irom0_0_seg的len即设置irom0text.bin的上限值。对于2048KB flash,此len最大可修改为0xC0000,irom0text.bin 最大支持到768KB(因为ESP8266目前程序区最大支持1024KB,1024-256=768)
- with boot-支持云端升级。编译时STEP1选择1,便一两次,分别生成user1.bin和user2.bin,支持云端升级功能。STEP5时选择不同的flash size对应的布局:
烧录
- 系统参数区固定为flash的最后四个扇区,每个扇区4KBytes,即flash最后16KB
- master_device_key.bin是ESP8266设备享受Espressif云端服务的身份证明,如果不使用Espressif Cloud可以不少路,否则仅烧录一次。烧录地址在IOT_Demo中设置为用户参数区的第三个扇区
- blank.bin初始化系统参数,烧录地址为flash的倒数第二个扇区
- esp_init_data_default.bin初始化射频相关参数,烧录地址为flash的倒数第四个扇区
- 不支持云端升级
- 支持云端升级(FOTA)。支持云端升级的软件无需烧录user2.bin,可以通过网络升级下载user2.bin到Flash并重启运行。
- 从esp_iot_sdk_v1.4.0版本起,开发者可以通过设置esp_init_data_default.bin(0~128byte)的114byte控制上电时的RF初始化的行为。
SDK二次开发
- 如果函数添加了ICACHE_FLASH_ATTR,该函数会被放在irom中,CPU仅在调用到他们的时候,将他们读到cache中运行;没有条件ICACHE_FLASH_ATTR宏的函数,将在一开始上电运行时,就加载到iram中运行。由于空间有限,我们无法将所有代码都一次性加载到iram中运行,因此在大部分函数前添加ICACHE_FLASH_ATTR宏。注意,不能在GPIO或UART中断处理函数中调用带有“ICACHE_FLASH_ATTR”宏的函数,否则将引起异常。
- wifi_set_ip_info、wifi_set_macaddr仅在user_init中调用才生效
- system_timer_reinit建议在user_init中调用,否则调用后,需要重新arm所有timer
- wifi_station_set_config如果在user_init中调用,底层会自动连接对应的路由,不需要再调用wifi_station_connect来进行连接,否则需要。
- 使能us级定时器
- 在user_config.h中#define USE_US_TIMER,并在user_init中调用system_timer_reinit(),此时可以同时使用os_timer_arm_us和os_timer_arm
- 未定义USE_US_TIMER时,os_timer_arm()的时间参数范围0~6871947ms,os_timer_arm_us不可用
- 定义了USE_US_TIMER时,os_timer_arm()的时间参数范围0~429496ms,os_timer_arm_us的时间参数范围是0~429496729us
- blank.bin,有Espressif提供,烧录到0x7E000地址。不是每次都要烧录,仅当sdk升级版本或需要擦除WIFI配置参数时进行烧录
- eagle.app.v6.flash.bin,用户编译生成,烧录到0x0000地址
- master_device_key.bin,向Espressif服务器申请,烧录到0x3E000地址
- eagle.app.v6.irom0text.bin,用户编译生成,烧录到0x40000地址
- esp_init_data_default.bin有Espressif提供,烧录到0x7c000地址
- system_restore将wifi相关参数复位,即擦出了路由器信息以及恢复了softAP默认名称
- 固件云端升级成功后,需要调用system_upgrade_reboot,否则不切换
- system_timer_reinit需要放在程序最开始,usr_init第一句
重要API介绍
- bool wifi_station_scan (struct scan_config *config, scan_done_cb_t cb);功能:获取AP的信息。注意:不能在user_init中调用此接口,该接口必须在系统初始化完成后,并且ESP8266 station接口使能的情况下调用
- bool wifi_station_set_config (struct station_config *config);功能:设置WiFi station接口的配置参数,并保存到flash。注意:如果wifi_station_set_config在user_init中调用,则ESP8266 station接口在系统初始化完成后,自动连接AP(路由),无需再调用wifi_station_connect;否则,需要调用wifi_station_connect连接AP(路由)。station_config.bssid_set一般设置为0,仅当需要检查AP的MAC地址时(用于有重名AP的情况下)设置为1、本设置如果与原设置不同,会更新保存到flash系统参数区
降低功耗的方法
- Modem-Sleep:CPU一直工作,在保持wifi连接,如果没有数据传输则关闭WiFi Modem电路来省电
- Light-Sleep:CPU暂停工作,保持Wifi连接
- Deep-Sleep:WiFi不需要一直保持连接时采用该模式
ADC应用场景
- 测量VDD3P3管脚3和4的电源电压
- TOUT必须悬空
- RF_init参数:esp_init_data_default.bin(0~127byte)中的低107byte为“vdd33_const”,必须设为0xFF,即255
- RF Calibration工作过程:自测VDD3P3管脚3和管脚4上的电源电压,根据测量结果优化RF电路工作状态
- 用户软件:可使用system_get_vdd33,不可使用system_adc_read
- 测量TOUT管脚6的输入电压
- TOUT管脚接外部电路,输入电压范围限定为0-1.0V
- RF_init参数:esp_init_data_default.bin(0~127byte)中的第107byte为“vdd33_const”,必须设为真实的VDD3P3管脚3和管脚4上的电源电压,ESP8266的工作电压范围1.8V-3.6V,“vdd33_const”单位0.1V,因此“vdd33_const”有效取值18~36.若电源电压不稳定,会动态变化,“vdd33_const”应输入为电源电压变化的最小值0x10.
- RF Calibration工作过程:根据RF_init第107byte“vdd33_const”的值来优化RF电路工作状态,容许误差约为±0.2V
- 用户软件:不可使用system_get_vdd33;可使用system_adc_read
电源管理
- 关闭(OFF):CHIP_PD管脚处于低功耗状态。RTC失效,所有寄存器被清空
- 深度睡眠(DEEP_SLEEP):RTC开着,芯片的其他部分都是关着的。RTC内部recovery memory可保存基本的WiFi连接信息
- 睡眠(SLEEP):只有RTC在运行。晶体振荡器停止工作。任何部位唤醒(MAC、主机、RTC计时器、外部中断)将唤醒整个芯片
- 环形(WAKEUP):在这种状态下,系统从睡眠状态下转为启动(PWR)状态。晶体振荡器和PLL均转化为使能状态
- 开启状态(CPU ON):告诉时钟可以运行,并发送至各个被时钟控制寄存器使能的模块。各个模块,包括CPU在内,执行较低电平的时钟门控。系统运作时,可以通过WAITI指令关闭CPU内部时钟
- 工作状态(RF WORK):在开启状态的基础上打开WiFi功能
2.4GHz接收器
2.4GHz接收器把RF信号降频,编程正交基带信号,用2个高分辨率的高速ADC将后者转为数字信号。为了适应不同的信号频道,无线电接收器集成了RF滤波器、自动增益控制AGC、DC偏移补偿电路和基带滤波器
2.4GHz发射器
2.4GHz发射器将正交基带信号升频到2.4GHz,使用大功率CMOS功率放大器驱动天线。数字校准的使用进一步改善了功率放大器的线性,从而在802.11b传输中达到+17dBm的平均功率,在802.11n中达到了13dBm的平均功率。为了抵消无线电接收器的瑕疵,还另增了校准措施:
1. 载波泄露
2. I/Q相位匹配
3. 基带非线性
SDK_IOT_Demo使用方法
- ESP8266物联网平台的所有网络功能均在库中实现,对用户不透明。用户应用的初始化功能可以在user_main.c中实现。
- void user_init(void)是上层程序的入口函数,给用户提供一个初始化接口,用户可在该函数内增加硬件初始化、网络参数配置、定时器初始化等功能。
- SDK中提供了对json包的处理API,用户也可以采用自定义数据包格式,自行对数据进行处理
- user_config.h,该头文件中可以选择具体的应用示例,仅支持每次打开一个宏定义,使能一个设备,具体支持:
- PLUG_DEVICE(只能插座)
- LIGHT_DEVICE(灯)
- SENSOR_DEVICE(传感器)
- HUMITURE_SUB_DEVICE(温湿度传感器)
- FLAMMABLE_GAS_SUB_DEVICE(可燃气体检测)
- 需要注意,以下头文件中的宏定义只是用户参数区,用户需要根据编译时的flash map自行调整
- user_esp_platform.h中的#define ESP_PARAM_START_SEC 0x3D //or 0x7D, or 0xFD
- user_light.h中的#define PRIV_PARAM_START_SEC 0x3C //or ox7C, or 0xFC
- user_plug.h中的#define PRIV_PARAM_START_SEC 0x3C // or 0x7C, or 0xFC
SDK编程指南
- SDK_v1.1.0及之后版本,请在user_main.c增加void user_rf_pre_init(void),可参考IOT_Demo的user_main.c。用户可在user_rf_pre_init中配置RF初始化,相关RF设置接口为system_phy_set_rfoption,或者在deep-sleep前调用system_deep_sleep_set_option。如果设置为RF不打开,则ESP8266 station及soft-AP均无法使用
- 非OS SDK中,由于是单线程,任何task都不能长期占用CPU
- 如果一个task占用CPU不退出,将导致看门狗的喂狗函数无法执行,系统重启
- 如果关闭中断,请勿占用CPU超过10微妙;如果不关闭中断,建议不超过500毫秒
- 建议使用定时器实现周期性的查询功能,如需在定时器的执行函数中调用os_delay_us或者while、for等函数进行延时或者循环操作,占用时间请勿超过15毫秒
- 非OS SDK在终端处理函数中,请勿使用任何ICACHE_FLASH_ATTR定义的函数
- 内存必须4字节对齐进行读写,请勿直接进行指针转换。例如语句:float temp=((float)data);可能引起异常,建议使用os_memcpy
- 如需在中断处理函数中打印,请使用os_printf_plus,且不能加入太多打印信息,尤其是频繁的中断,中断占用时间过长可能引起底层异常
应用程序接口(APIs)
软件定时器(/esp_iot_sdk/include/osapi.h)
- 该定时器由软件实现,定时器的函数在任务中被执行,因为任务可能被中断,或者被其他高优先级的任务延迟,因此以下os_timer系列的接口并不能保证定时器精确执行
- 如果需要精确的定时,请使用硬件中断定时器,硬件定时器的执行函数在中断里被执行
- 对于同一个timer,os_timer_arm或os_timer_arm_us不能重复调用,必须先os_timer_disarm
- os_timer_setfn必须在timer未使能的情况下调用,在os_timer_arm或os_timer_arm_us之前或者os_timer_disarm之后
硬件中断定时器(esp_iot_sdk/example/driver_lib/hw_timer.c)
- 如果使用NMI中断源,且为自动填装的定时器,调用hw_timer_arm时参数val必须大于100
- 如果使用NMI中断源,那么该定时器将为最高优先级,可打断其他ISR
- 如果使用FRC1中断源,那么该定时器无法打断其他ISR
- hw_timer.c的接口不能跟PWM驱动函数同时使用,因为两者共用了同一个硬件定时器
系统接口
SPI Flash接口
Wi-Fi接口
- wifi_station系列接口以及ESP8266 station相关的设置、查询接口,请在ESP8266 station使能的情况下调用
- wifi_softap系列接口以及ESP8266 soft-AP相关的设置、查询接口,请在ESP8266 soft-AP使能的情况下调用
- 后文的“flash系统参数区”位于flash的最后16KB
Rate Control 接口
强制休眠接口
使用强制休眠功能,必须先设置WiFi工作模式位NULL_MODE,从强制休眠中唤醒ESP8266,或者休眠时间到,进入唤醒回调(wifi_fpm_set_wakeup_cb注册)后,先关闭强制休眠功能,才能再设置WiFi工作模式为station、soft-AP或sta+AP的正常工作模式。示例
ESP-NOW
ESP-NOW软件接口使用时注意:- ESP-NOW目前不支持广播包和组播包
- ESP-NOW现阶段主要为智能灯项目,建议slave角色对应ESP8266 soft-AP模式或者soft-AP+station共存模式;controller角色对应station模式
- 当ESP8266处于soft-AP+station共存模式时,若作为slave角色,将从soft-AP接口通信;若作为controller角色,将从station接口通信
- ESP-NOW不实现休眠环形功能,因此如果通信对方的ESP8266 station正处于休眠状态,ESP-NOW发包将会失败
- ESP8266 station模式下,最多可设置10个加密的ESP-NOW peer,加上不加密的设备,综述不超过20个
- ESP8266 soft-AP模式或者soft-AP+station模式下,最多设置6个加密的ESP-NOW peer,加上不加密的设备,总数不超过20个
云端升级(FOTA)
Sniffer接口
smart config
开启smart config功能前,先要确保AP已经开启SNTP
TCP/UDP接口
- 通用接口
- TCP APIs
- UDP APIs
- mDNS APIs
MESH接口
AT接口
JSON接口
GPIO接口
UART接口
默认情况下,UART0作为系统的打印信息输出接口,当配置为双UART时,UART0作为数据收发接口,UART1作为打印信息输出接口I2C Master接口
ESP8266不能作为I2C从设备,但可以作为I2C主设备,对其他I2C从设备进行控制和读写。每个GPIO管脚内部都可以配置为开漏模式,从而可以灵活地将GPIO口用作I2C data或者clock功能。同时芯片内部提供上拉电阻,以节省外部的上拉电阻PWM接口
PWM驱动接口不能跟hw_timer的接口同时使用,因为二者共用了同一个硬件定时器
##参数结构和宏定义
- 定时器
- WiFi参数
- json相关结构体
- espconn参数
- 中断相关宏定义
ESPCONN编程
- TCP client模式,步骤:
- 依据工作协议初始化espconn参数
- 注册连接成功的回调函数和连接失败重连的回调函数
- 调用espconn_connect建立与TCP Secver的连接
- TCP连接建立成功后,在连接成功的回调函数(espconn_connect_callback)中,注册接收数据的回调函数,发送数据成功的回调函数和断开连接的回调函数
- 在接收数据的回调函数,或者发送数据成功的回调函数中,执行断开连接操作时,建议适当延时一定时间,确保底层函数执行结束
- TCP Server模式,步骤
- 依据工作协议初始化espconn参数
- 注册连接成功的回调函数和连接失败重连的回调函数
- 调用espconn_accept侦听TCP连接
- TCP连接建立成功后,在连接成功的回调函数中,注册接收数据的回调函数,发送数据成功的回调函数和断开连接的回调函数
- espconn callback
- 注意:回调函数中传入的指针arg,对应网络连接的结构体espconn指针。该指针为SDK内部维护的指针,不同回调传入的指针地址可能不一样,请勿依此判断网络连接。可根据espconn结构体中的remote_ip,remote_port判断多连接中的不同网络传输
- 如果espconn_connect(或者espconn_secure_connect)失败,返回非零值,连接未建立,不会进入任何espconn callback
- 请勿在espconn热河回调中调用espconn_disconnect(或者espconn_secure_disconnect)断开连接。如果有需要,可以在espconn回调中使用触发任务的方式(system_os_task和system_os_post)调用espconn_disconnect(或者espconn_secure_disconnect)断开连接
RTC使用实例
以下测试示例,可以验证RTC时间和系统时间,在system_restart时的变化,以及读写RTC memory
##Sniffer结构体说明
- ESP8266可以进入混杂模式,接收空气中的IEEE802.11包,可支持如下HT20的包:
- 802.11b
- 802.11g
- 802.11n(MCS0到MCS7)
- AMPDU
- 尽管有些类型的IEEE802.11包是ESP8266不能完全接收的,但ESP8266可以获得它们的包长。因此,sniffer模式下,ESP8266或者可以接收完整的包,或者可以获得包的长度
- ESP8266可完全接收的包,包含:
- 一定长度的MAC头信息(包含了收发双发的MAC地址和加密方式)
- 整个包的长度
- ESP8266不可完全接收的包,它包含:
- 整个包的长度
- 结构体RxControl和sniffer_buf分别用于via哦是这两种类型的包。其中结构体sniffer_buf包含结构体RxControl。
回调函数wifi_promiscuous_rx含两个参数(buf和len)。len表示buf的长度,分为三种情况:len=128,len为10的整数倍,len=12;
- LEN==128的情况
- buf的数据是结构体sniffer_buf2,该结构体对应的数据包是管理包,含有112字节的数据
- sniffer_buf2.cnt为1
- sniffer_buf2.len为管理包的长度
- LEN为10的整数倍的情况
- buf的数据是结构体sniffer_buf,该结构体是比较可信的,它对应的数据包是通过CRC校验正确的
- sniffer_buf.cnt表示了该buf包含的包的个数,len的值由sniffer_buf.cnt决定
- sniffer_buf.cnt==0表示此buf无效;否则len=5=+cnt*10
- sniffer_buf.buf表示IEEE802.11包的前36字节。从成员sniffer_buf.lenseq[0]开始,每一个lenseq结构体表示一个包长信息
- 当sniffer_buf.cnt>1,由于该包是一个AMPDU,认为每个MPDU的包头基本是相同的,因此没有给出所有的MPDU包头,只给出了每个包的长度(从MAC包头开始到FCS)
- 该结构体中较为游泳的信息有:包长、包的发送者和接收者、包头长度
- LEN==12的情况
- buf的数据是一个结构体RxControl,该结构体是不太可信的,它无法表示包所属的发送和接收者,也无法判断该报的包头长度
- 对于AMPDU包,也无法判断子包的个数和每个子包的长度
- 该结构体中较为有用的信息有:包长,rssi和FEC_CODING
- RSSI和FEC_CODING可以用于评估是否是同一个设备所发
- LEN==128的情况
ESP8266信道的定义
- 虽然ESP8266支持soft-AP和station共存模式,但是ESP8266实际只有一个硬件通道。因此在soft-AP+station模式时,ESP8266 soft-AP会动态调整信道值与ESP8266 station一致
AT 官方指令
- 如果开发板Flash为4Mbit,则无法使用固件升级功能(对应指令AT+CIUPDATE),只能采用non-boot的烧录方式。固件升级功能要求Flash容量为8Mbit或以上,采用boot mode的烧录方式
- AT底层已经占用system_os_task优先级0和1,因此用户如基于AT开发,仅支持建立一个优先级为2的任务
- 波特率为115200
- AT指令必须大写,以回车换行符结尾“\r\n”
- AT Demo仅在ESP8266作为TCP client单连接或者UDP传输时,支持透传
- 目前AT Demo ESP8266仅支持一个TCP服务器,且必须使能多连接,即可连接多个TCP client
GPIO
- ESP8266共有16个通用IO,管脚的位置和管脚的名称分别为:
- 在四线(QUAD)模式flash下,有6个IO用于flash通讯;在两线(DUAL)模式flash下,有四个IO用于与flash通讯
- 与其他IO口不同,GPIO16不属于通用GPIO模块,它属于RTC模块,可以用来在深度睡眠时候唤醒整个芯片,可以配置为输入或者输出模式,但是无法触发IO中断
- 将GPIO16配置为输出模式:gpio16_output_conf(void)
- 从GPIO16输出高/低电平,需要先配置为输出模式:gpio16_output_set(uint8 value)
- 将GPIO16配置为输入模式:gpio16_input_conf(void)
- 读取GPIO16的输入电平状态,需要先配置为输入模式:gpio16_input_get(void)
Free-RTOS
- 编程注意事项:
- 建议使用定时器实现长时间的查询功能,可将定时器设置为循环调用,注意:
- 定时器(freeRTOS timer或os_timer)执行函数内部请勿使用while(1)或其他能阻塞线程的方式延时,例如,不能在定时器回调中进行socket send操作,因为send函数会阻塞线程
- 定时器回调执行请勿超过15毫秒
- os_timer_t建立的变量不能为局部变量,必须为全局变量、静态变量或os_malloc分配的指针
- 从esp_iot_rtos_sdk_v1.2.0起,无需添加宏ICACHE_FLASH_ATTR,函数默认存放在CACHE区,中断函数也可以存放在CACHE区;如需将部分频繁调用的函数定义在RAM中,请在函数前面添加宏IRAM_ATTR
- 网络编程使用通用的socket编程,网络通信时,socket请勿绑定在同一个端口
- RTOS SDK的系统任务最高优先级为14,创建任务的接口xTaskCreate为freeRTOS自带接口,使用下TaskCreate创建任务时,任务堆栈甚至范围为【176,512】
- 在任务内部如需使用长度超过60的大数组,建议使用os_malloc和os_free的方式操作,否则,大数组将占用任务的堆空间
- SDK底层已占用部分优先级:watchdog task优先级14,pp task优先级13,高精度timer(ms)线程优先级12,TCP/IP task优先级10,freeRTOS timer优先级2,idle task优先级为0,pm task优先级1
- 可供用户线程使用的优先级为1~9
- 请勿修改FreeRTOSConfig.h,此处修改头文件并不能生效,设置由SDK库文件决定
- 建议使用定时器实现长时间的查询功能,可将定时器设置为循环调用,注意:
- esp_iot_rtos_sdk默认使用UART0打印调试信息,默认波特率为74880
- esp_iot_rtos_sdk支持多线程,可以建立多个任务。创建任务的接口xTaskCreate为freeRTOS自带接口,使用xTaskCreate创建任务时,任务堆栈设置范围为[176,512]
- RTC使用
非OS SDK与RTOS SDK创建任务的方式对比
1 |
|
1 |
|
强制系统休眠
- 强制休眠接口调用后,并不会立即休眠,而是等到系统idle task执行时才进入休眠
Modem-sleep
1
2
3
4
5
6
7
8
9
10
11
wifi_station_disconnect();
wifi_set_opmode(NULL_MODE); // set WiFi mode to null mode
wifi_fpm_set_sleep_type(MODEM_SLEEP_T); // set modem sleep
wifi_fpm_open(); // enable force sleep
wifi_fpm_do_sleep(FPM_SLEEP_MAX_TIME);
...
wifi_fpm_do_wakeup(); // wake up to use WiFi again
wifi_fpm_close(); // disable force sleep
wifi_set_opmode(STATION_MODE); //set station mode
wifi_station_connect(); //connect to APLight-sleep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void fpm_wakup_cb_func1(void)
{
wifi_fpm_close(); // disable force sleep function
wifi_set_opmode(STATION_MODE); // set station mode
wifi_station_connect(); // connect to AP
}
void user_func(...)
{
wifi_station_disconnect();
wifi_set_opmode(NULL_MODE); // set WiFi mode to null mode.
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); // light sleep
wifi_fpm_open(); // enable force sleep
wifi_fpm_set_wakeup_cb(fpm_wakup_cb_func1); // Set wakeup callback
wifi_fpm_do_sleep(10*1000);
...
}
spiffs文件系统应用
Windows下网络防火墙对TCP Server的屏蔽
- Win+R,输入wf.msc,进入高级安全Windows防火墙
- 在入站规则右击新建规则
- 在规则类型对话框中选择“程序”
- 指名程序的路径
- 允许连接
PWM接口
- ESP8266系统的PWM由FRC1在软件上实现,可实现同频率、不同占空比的多路PWM,可用来控制彩灯、蜂鸣器和电机等设备
- FRC1是一个23bit的硬件定时器
- 使用NMI中断,更加精确
- 可扩展最多8路PWM信号
- 大于14bit的分辨率,最小分辨率45ns
- PWM的时钟源由高速系统时钟提供,其频率高达80MHz。PWM通过预分频器将时钟源16分频,其输入时钟频率为5MHz。PWM通过FRC1来产生粗调定时,结合高速系统时钟的微调,可将分辨率提高到45ns
- PWM时钟周期:100Hz~1KHz
UART接口
- UART0默认情况下会在上电booting期间输出一些打印,此期间打印内容的波特率与所用的外部晶振频率有关。使用40M晶振时,该段打印波特率为115200。使用26M晶振时,该段打印波特率为74880
- UART0和UART1各有一个长度为128Byte的硬件FIFO,读写FIFO都在同一个地址操作
- 如何屏蔽上电打印:使用uart的内部引脚交换功能,在初始化的时候,将U0TXD、U0RXD分别与U0RTS、U0CTS交换
Sleep接口
- 对于Modem-sleep和Light-sleep模式,SDK提供接口来使能睡眠模式,并由系统底层决定何时进入睡眠。在deep-sleep模式下,何时进入睡眠由用户控制,调用接口函数就可立即进入deep-sleep模式,
- Modem-sleep仅工作在Station模式下,连接路由器后生效,ESP8266通过Wi-Fi的DTIM(Delivery Traffic Indication Message)Beacon机制与路由器保持连接。一般路由器的DTIM Beacon间隔为100ms~1000ms。在Modem-sleep模式下,ESP8266会在两次DTIM Beacon间隔时间内,关闭Wi-Fi模块电路,达到省电效果,在下次Beacon到来前自动唤醒。睡眠时间由路由器的DTIM Beacon时间决定。睡眠同时可以保持与路由器的Wi-Fi连接,并通过路由器接受来自手机或者服务器的交互信息。
- 在Modem-sleep模式下,系统可以自动被唤醒,无需配置接口。Modem-sleep一般用于必须打开CPU的应用场景,例如PWM彩灯,需要CPU实时控制
- Light-sleep的工作模式与Modem-sleep相似,不同的是,除了关闭Wi-Fi模块电路以为,在Light-sleep模式下,还会关闭时钟并暂停内部的CPU。在Wi-Fi连接后,并且CPU处于空闲状态时,会自动进入Light-sleep状态
- 在Light-sleep模式下,CPU在暂停状态下不会响应来自外围硬件接口的信号与终端,因此需要配置通过外部GPIO信号将ESP8266唤醒,唤醒过程小于3ms。通过GPIO唤醒只能配置为电平触发模式。接口如下:void gpio_pin_wakeup_enable(uint32 i, GPIO_INT_TYPE intr_state);,其中i为唤醒功能的IO序号,intr_state为唤醒的触发模式,只能是GPIO_PIN_INTR_LOLEVEL或者GPIO_PIN_INTR_HILEVEL。
- Light-sleep模式可用于需要保持与路由器的链接,可以实时响应路由器发来的数据的场合。并且在未收到命令时,CPU可以处于空闲状态。比如Wi-Fi开关的应用,大部分时间CPU都是空闲的,知道收到控制命令,CPU才需要进行GPIO的操作。若系统应用中有小于DTIM Beacon间隔时间的循环定时,系统将不能进入Light-sleep模式。
- Deep-sleep由用户控制,调用接口函数就可立即进入Deep-sleep模式,在该模式下,芯片会断开所有的Wi-Fi连接与数据连接,进入睡眠模式,只有RTC模块仍然工作,负责芯片的定时唤醒。使用Deep-sleep必须将GPIO16与芯片EXT_RSTB管脚连接。
- 配置Deep-sleep
- 在Deep-sleep状态下,可以通过外部IO在芯片EXT_RSTB管脚上产生一个低电平脉冲,芯片即可被唤醒并启动
- Deep-sleep可以用于低功耗的传感器应用,或者大部分时间都不需要进行数据传输的情况。设备可以每隔一段时间从Deep-sleep状态醒来测量数据并上传,之后继续进入Deep-sleep。也可以将多个数据存储于RTC memory(RTC memory在Deep-sleep模式下任然可以保存数据),然后一次发送出去
I2C接口
- 每个GPIO管脚内部都可以配置为开漏模式,从而可以灵活地将GPIO口用作I2C data或clock功能,同时芯片内部提供上拉电阻,以节省外部的上拉电阻。ESP8266作为I2C主机的SDA与SCL线波形由GPIO模拟产生,在SCL的上升沿之后SDA读取数据。SCL高低电平各保持5us,因此I2C时钟频率约为100KHz
OTA升级
- 在支持云端升级的软件中,boot.bin用于选择运行user1还是user2,而主程序由原本的eagle.flash.bin和eagle.iromtext.bin合并为user1.bin或user2.bin
- system param区存了一个flag,标识启动时应当运行user1还是user2
- 启动时先运行boot,boot读取system param区中的flag,判断运行user1还是user2,然后到SPI Flash的对应位置去取
- 上传时,将新版本的 user1.bin 和 user2.bin 均上传⾄至服务器,由设备⾃自⾏行判断应该下载user1.bin 还是 user2.bin
- user1.bin 和 user2.bin 是同样的可执⾏行软件,差别仅在于 flash 的存放位置不同
- 固件升级服务器网址
- 软件接口
- 上传服务器的固件版本命名形如:[v|b]Num1.Num2.Num3.tPTYPE([o|l|a|n])
- v:表示发布版本,b:表示测试版本
- 版本值为:Num110001000 + Num2*1000 + Num3
- Light ptype = 45772
- Switch ptype = 23701
- general ptype = 27388
- o表示支持在线升级
- l表示支持本地升级
- a表示既支持在线升级,也支持本地升级
- n表示不支持升级
红外遥控
- 用于发送的载波可以采用以下几种方式:
- I2S的BCK
- WS脚产生38KHz载波
- 由GPIO中的sigma-delta功能在任意GPIO口产生载波,但sigma-delta产生的载波占空比约为20%,推荐使用MTMS脚(GPIO14),可产生准确的38KHz且占空比为50%的标准方波
- 通过系统FRC2的DSR TIMER接口,产生发送序列并驱动红外发送状态机。由于发送NEC红外码需要精确到us级的定时,所以在IR TX初始化时候,会先调用system_timer_reinit来提高FRC2 timer的精度。
- 红外接收功能主要通过GPIO的边沿中断完成。读取系统时间,将两次时间相减可以得到波形持续时间。由软件状态机ir_intr_handler进行处理
- 红外接收通过GPIO中断实现,而同时,系统只能注册一个IO中断处理程序,如果有其他IO口也需要中断的话,请将这些中断在同一个处理程序中处理(判断中断源并相应处理)
- 在非OS版本的SDK中,进入中断处理(GPIO、UART、FRC等)直到退出中断的整个过程中,不可调用带ICACHE_FLASH_ATTR属性的函数,包括打印函数os_printf
- 硬件电路图
SSL/TLS加密
- SSL运行在TCP/IP层之上、应用层之下,为应用程序提供加密数据通道,它采用了RC4、MD5以及RSA等加密算法,使用40位的密钥。
- HTTPS实际上就是HTTP over SSL,它使用默认端口443,而不是像HTTP那样使用端口80。
- HTTPS协议使用SSL在发送方把原始数据进行加密,然后在接收方进行解密,加密和解密需要发送方和接收方通过交换公知的密钥来实现,因此,所传送的数据不容易被网络何可截获和解密
- 工作流程
- 建立安全能力。SSL捂手的第一阶段启动逻辑连接,建立这个连接的安全能力。首先客户机向服务器发出client hello消息并等待服务器响应,随后服务器向客户机返回server hello消息,对client hello消息中的信息进行确认。
- Client hello消息包括:
- 客户端可以支持的SSL最高版本号
- 一个客户端生成的随机数,稍后用于生成“对话密钥”
- 一个确定会话的会话ID
- 一个客户端可以支持的密码套件列表,每个套件都以SSL开头,紧跟着的是密钥交换算法,用with这个词把密钥交换算法、机密算法、散列算法分开。例如:SSL_DHE_RSA_WITH_DES_CBC_SHA, 表示把DHE_RSA(带有RSA数字签名的暂时Diffie-HellMan)定义为密钥交换算法;把DES_CBC定义为加密算法;把SHA定义为散列算法。
- 一个客户端可以支持的压缩算法列表
- Server Hello消息包括
- 一个SSL版本号。去客户端支持的最高版本号和服务端支持的最高笨笨好中的较低者
- 一个服务器生成的随机数,稍后用于生成“对话密钥”
- 会话ID
- 从客户端的密码条件列表中选择的一个密码套件
- 从客户端的压缩方法的列表中选择的压缩方法
- 这个阶段之后,客户端服务端知道了下列内容:
- SSL版本
- 密钥交换、信息验证和加密算法
- 压缩方法
- 有关密钥生成的两个随机数
- Client hello消息包括:
- 服务器鉴别与密钥交换。服务器启动SSL握手第二阶段,是本阶段所有消息的唯一发送方,客户机是所有消息的唯一接收方。该阶段分为4步
- 证书:服务器将数字证书和到根CA整个链发给客户端,使客户端能用服务器证书中的服务器公钥认证服务器
- 服务器密钥交换:这里视密钥交换算法而定
- 证书请求:服务端可能会要求客户自身进行验证
- 服务器握手完成
- 客户机鉴别与密钥交换。客户机启动SSL握手第三阶段,是本阶段所有消息的唯一发送方,服务器是所有消息的唯一接收方。该阶段分为3歩:
- 证书:为了对服务器证明自身,客户要发送一个整数信息,这是可选的
- 客户机密钥交换:这里客户端将预备主密钥发送给服务端,注意这里会使用服务端的公钥进行加密
- 证书验证:对预备密钥和随机数进行签名
- 完成,客户机启动SSL握手的第四阶段,是服务器结束。该阶段分成4歩
- 建立安全能力。SSL捂手的第一阶段启动逻辑连接,建立这个连接的安全能力。首先客户机向服务器发出client hello消息并等待服务器响应,随后服务器向客户机返回server hello消息,对client hello消息中的信息进行确认。
- SSL协议可分为两层:
- SSL记录协议:它建立在可靠的传输协议之上,为高层协议提供数据封装、压缩、加密等基本功能的支持
- SSL握手协议:它建立在SSL记录协议之上,用于在实际数据传输开始前,通讯双方进行身份认证、协商加密算法、交换密钥等
- ESP8266作为SSL server时,提供加密证书的制作脚本,生成SSL加密所需的头文件cert.h和private_key.h。CA认证功能默认关闭,用户可调用接口espconn_secure_ca_enable使能CA认证
- 证书制作:tool文件夹下,修改makefile.sh文件里面的IP地址为实际SSL 服务器的IP地址;然后./makefile.sh
- 开发者必须调用espconn_secure_set_default_certificate和espconn_secure_set_default_private_key传入证书和密钥
- 证书制作脚本makefile.sh生成默认SSL server证书由Espressif System颁发,并非由CA颁发。如果用户需要CA认证,请将运行脚本Makefile.sh生成的TLS.ca_x509.cer导入SSL client,并使用脚本make_cacert.py将CA文件生成eap_ca_cert.bin烧写到Flash对应的地址
- ESP8266作为SSL client时,可支持双向认证。CA认证功能默认关闭,用户可调用接口espconn_secure_ca_enable使能CA认证
- ESP8266作为SSL client时支持证书认证功能,但此功能默认关闭,开发者可以调用接口espconn_secure_cert_req_enable使能证书认证,证书制作:
- 修改脚本makefile.sh,制作开发者自行签发的CA证书,例如,证书实例中的TLS.ca_x509.cer
- 使用签发的CA制作供SSL client使用的证书,例如,证书示例中的TLS.x509_1024.cer
- 去除制作SSL client使用的证书时所用的密钥,例如证书示例中的TLS.key_1024
- 将证书合成脚本make_cacert.py与CA文件放在同一目录下
- 运行脚本“make_cacert.py”将合成同一目录下的CA文件生成sap_ca_cert.bin,esp_ca_cert.bin的烧录位置由接口espconn_secure_ca_enable设置,用户可以自行定义
- 重命名证书名称(例如TLS.x509_1024.cer);重命名密钥名称,改为private_key.key_1024。
- 将重命名后的文件,与脚本make_cert.py拷贝到同一目下下
- 运行脚本make_cert.py生成esp_cert_private_key.bin,esp_cert_private_key.bin的烧录位置由接口espconn_secure_cert_enable设置,用户可自行定义
- 软件接口
- SSL系列软件接口与普通TCP软件接口,在SDK底层是两套不同的处理流程,因此不能混用两种软件接口。SSL连接时,仅支持使用:
- espconn_secure_XXX系列接口
- espconn_regist_XXX系列注册回调的接口
- espconn_port获得一个空闲的端口
- SSL系列软件接口与普通TCP软件接口,在SDK底层是两套不同的处理流程,因此不能混用两种软件接口。SSL连接时,仅支持使用:
- 在SSL中会使用密钥交换算法交换密钥;使用密钥对数据进行加密;使用散列算法对数据的完整性进行验证,使用数字证书证明自己的身份
- SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器所要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。
- 如何保证公钥不被篡改?
- 将公钥放在数字证书中。只要证书时可信的,公钥就是可信的
- 公钥加密计算量太大,如何减少耗用的时间?
- 每一次对话(session),客户端和服务器都生成一个“对话密钥”,用它来加密信息。由于“对话密钥”是对称加密,所以运算速度非常快,而服务器公钥只用于加密“对话密钥”本身,这样就减少了加密运算的消耗时间
- SSL/TLS协议的基本过程是这样的:
- 客户端向服务端索要并验证公钥
- 双方协商生成“对话密钥”
- 双方采用“对话密钥”进行加密通信
- 为什么要用三个随机数来生成“会话密钥”?
- 不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,三个随机数通过一个密钥导出器最终导出一个对称密钥。pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,那么pre master secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,因此必须引入新的随机因素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,一个伪随机可能完全不随机,可是是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。
- 如何保证公钥不被篡改?
Flash接口
- 一个扇区为4KB,从扇区0开始计数,以下接口可以读写整个Flash的任意区域
- SpiFlashOpResult spi_flash_erase_sector (uint16 sec):擦除Flash的某个扇区
- SpiFlashOpResult spi_flash_write (uint32 des_addr,uint32 *src_addr, uint32 size):将数据写入Flash
- SpiFlashOpResult spi_flash_read(uint32 src_addr,uint32 * des_addr, uint32 size):读取Flash中的数据
- 在IoT_Demo中,将应用级数据存储在了0x3C000开始的4X4KB区域。例如,master_device_key.bin(用户参数,仅在需使用Espressif Cloud的情况需烧录)烧录在0x3E000地址
- Flash擦除的最小单元为一个扇区,当存储在某个扇区的数据需要改写时,流程是先擦掉整个扇区,再将该扇区的数据写回去
- 读写保护的方法:
- 使用三个扇区,提供4KB的可靠存储空间
- 将sector1和sector2作为数据sector,轮流读写,时钟分别存放“本次”数据和“前一次”数据,确保了至少有一份数据存储安全;sector3作为flag sector,标志最新的数据存储sector。
- 初次上电时,数据存储在sector2中,从sector2中将数据读到RAM
- 第一次写数据时,将数据写入sector1.此时若突然掉电,sector1写入失败,sector2&3数据未改变;重新上电时,仍是从sector2中读取数据,不影响使用。
- 改写sector3,将标志置为0,表示数据存于sector1.此时若突然掉电,sector3写入失败,sector1&2均存有一份完整的数据,重新上电时,因为sector3无效,默认从sector2中读取数据,则仍能正常使用,只是未能包含掉电前对sector1写入的数据
- 再一次写数据时,先从sector3读取flag,若flag为0,则上次数据存于sector1,此次应将数据写入sector2;若flag为非0,则认为上次数据存于sector2,此时应将数据写入sector1.
- 写入sector1或者sector2完成后才会写sector3,重置flag
- 使用三个扇区,提供4KB的可靠存储空间
cJSON使用
1 | typedef struct cJSON { |
- cJSON结构体为一个双向链表,并可通过child指针访问下一层
- type变量决定数据类型,数据项可以是字符串可以是整形,也可以是浮点型。如果是整形的话可以从valueint取出,如果是浮点型的话可以从valuedouble取出,以此类推
- 主要函数说明
- 解析
- cJSON_Parse函数负责解析JSON数据包,并按照cJSON结构体的结构序列化整个数据包。使用该函数会通过malloc函数在内存中开辟一个空间,使用完成需要手动释放
- cJSON_GetObjectItem函数可以从cJSON结构体中查找某个子节点名称,如果查找成功,可把该子节点序列化到cJSON结构体中
- 如果需要使用cJSON结构体中的内容,可通过cJSON结构体中的valueint和valuestring取出有价值的内容
- 通过cJSON_Delete释放内存空间
- 组装
- cJSON_CreateObject函数可创建一个根数据项,之后便可向该根数据项中添加string或int等内容
- cJSON_AddNumberToObject向节点中添加子节点
- cJSON_Print函数可以打印跟数据项
- 解析
- 使用例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50cJSON* pRoot = cJSON_CreateObject();
cJSON* pArray = cJSON_CreateArray();
cJSON_AddItemToObject(pRoot, "students_info", pArray);
char* szOut = cJSON_Print(pRoot);
cJSON* pItem = cJSON_CreateObject();
cJSON_AddStringToObject(pItem, "name", "chenzhongjing");
cJSON_AddStringToObject(pItem, "sex", "male");
cJSON_AddNumberToObject(pItem, "age", 28);
cJSON_AddItemToArray(pArray, pItem);
pItem = cJSON_CreateObject();
cJSON_AddStringToObject(pItem, "name", "fengxuan");
cJSON_AddStringToObject(pItem, "sex", "male");
cJSON_AddNumberToObject(pItem, "age", 24);
cJSON_AddItemToArray(pArray, pItem);
pItem = cJSON_CreateObject();
cJSON_AddStringToObject(pItem, "name", "tuhui");
cJSON_AddStringToObject(pItem, "sex", "male");
cJSON_AddNumberToObject(pItem, "age", 22);
cJSON_AddItemToArray(pArray, pItem);
char* szJSON = cJSON_Print(pRoot);
cJSON_Delete(pRoot);
//free(szJSON);
pRoot = cJSON_Parse(szJSON);
pArray = cJSON_GetObjectItem(pRoot, "students_info");
if (NULL == pArray)
{
return -1;
}
int iCount = cJSON_GetArraySize(pArray);
for (int i = 0; i < iCount; ++i)
{
cJSON* pItem = cJSON_GetArrayItem(pArray, i);
if (NULL == pItem)
{
continue;
}
string strName = cJSON_GetObjectItem(pItem, "name")->valuestring;
string strSex = cJSON_GetObjectItem(pItem, "sex")->valuestring;
int iAge = cJSON_GetObjectItem(pItem, "age")->valueint;
}
cJSON_Delete(pRoot);
free(szJSON);