# hekr-esp8266-sdk-ra **Repository Path**: simonliu009/hekr-esp8266-sdk-ra ## Basic Information - **Project Name**: hekr-esp8266-sdk-ra - **Description**: 嵌入式4.x固件开发SDK - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2018-07-15 - **Last Updated**: 2020-12-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #氦氪4.x嵌入式SDK应用手册 **v1.1.4 by [zejun.zhao@hekr.me](zejun.zhao@hekr.me)** 2016/3/30 11:03:38 ##总体描述 - 编写目的: 该文档旨在指导开发者快速上手氦氪4.x嵌入式SDK,进行对WIFI模块ESP8266的二次开发,熟悉目录结构、了解api接口各模块功能,以及编程中需注意的事项。 - SDK开发者:基于esp8266硬件平台,采用Hekr配网模式、连接氦氪云进行开发的开发者群体。同时,开发者也无需关注网络重连及服务器重连机制,便于开发者进行二次应用开发。 - api接口使用说明,请参考文档`document\ra_embedded_api.chm` ## 1. SDK目录 --- ###### 目录结构 ├── app | └─ user ├── bin | └─ upgrade ├── document ├── examples ├── include | └─ ra ├── ld ├── lib └── tools ###### 目录结构说明 - `app`:用户工作区。用户在此目录下执行make编译操作,用户级代码及头文件均放在此目录下 - `user`:用户级代码目录,SDK入口函数为`ra_user_main()`,在`user_main.c`中 - `bin`:二进制文件目录。该目录存放了编译生成的bin文件 - `upgrade`:该子目录存放编译生成的支持云端升级(FOTA)的固件(如`user1.bin`或`user2.bin`) - `document`:文档目录 - `include`:头文件目录,包含了用户可使用的API函数声明以及相关宏定义 - `ra`:RA库头文件目录,开发者进行二次开发所需API函数、宏定义、类型定义均在此目录头文件中 - `examples`:实例项目代码 - `Smart Plug` : 智能插座项目 - `ld`:SDK编译链接时所需文件,用户无需修改 - `lib`:SDK编译链接时所需的库文件。 - `libra.a` - `tools`:包含编译工具和烧录工具等,用户无需修改 ## 2. 固件编译 --- - 在`app`目录下,执行编译命令`make`,在`bin\upgrade`目录下生成用户固件`1.bin`。 注:`make`编译前,可以根据flash大小来进行配置,配置文件为`app\Makefile`。默认为`BOOT=new、APP=1、SPI_SPEED=40、SPI_MODE=QIO、SPI_SIZE_MAP=3`,即`flash map`默认为`3 = 2048KB(512K+512K)`。 固件编译时`SPI_SIZE_MAP`仅支持`2/3/4`,`flash map`对应表如下:
SPI_SIZE_MAP flash map
2 1024KB( 512KB+ 512KB)
3 2048KB( 512KB+ 512KB)
4 4096KB( 512KB+ 512KB)
## 3. 固件烧录 --- - `params_section_blank.bin`用于擦除flash中的配置参数(如`prodKey`等)以及`ra_set_parameter_string/ra_set_parameter_integer`接口存入的参数。 - 固件烧录地址取决于`flash map`,即`SPI_SIZE_MAP`。 固件烧录时需要烧录下表中5个bin文件,烧录地址与`SPI_SIZE_MAP`对应表如下:
bin文件 2 3 4
boot_v1.5.bin 0x0 0x0 0x0
1.bin 0x1000 0x1000 0x1000
params_section_blank.bin 0x7D000 0x7D000 0x7D000
esp_init_data_default.bin 0xFC000 0x1FC000 0x3FC000
blank.bin 0xFE000 0x1FE000 0x3FE000
## 4. 用户代码入口 --- - 用户代码入口函数为`ra_user_main()`,在`app/user/user_main.c`中。 - 在进入函数`ra_user_main()`前,系统已经做了部分初始化,具体有:CPU频率为`80MHz`、wifi mode为`STATION_MODE`、设定uart0和uart1波特率为`9600`且打印终端为`uart0`。 此外,在`ra_user_main()`中,需要执行一些函数完成对配置初始化,请参考以下代码: #include #include #include void ra_user_main(void) { //必选 ra_set_parameter_string("prodKey", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); //prodKey设置 ra_set_current_firmware_version("4.1.1.1"); //固件版本号设置,格式必须是"xx.xx.xx.xx" //可选 ra_uart_set_terminal(RA_UART1); //设置打印终端,可将SDK内部调试信息输出 } ## 5. 功能模块 --- ###5.1 打印终端配置 esp8266有两个串口(uart0和uart1),均可作为打印终端,调用`ra_uart_set_terminal()`。`os_printf`的打印终端默认为uart0。 示例代码:(设置uart1为打印终端) #include ra_uart_set_terminal(RA_UART1); os_printf("hello world\n"); uart1输出: hello world **注:`ra_uart_set_terminal()`设置后,SDK内部调试信息也会打印出来,如云端登陆流程的信息,方便开发者调试** 串口相关属性配置可参考:**5.5.1 UART操作接口** ###5.2 用户参数存取接口 SDK提供K/V格式的存参接口。开发者可自定义Key,并可选择两种Value类型(int和string)。此外还提供了K/V的读取和删除接口。接口说明请参考`ra_parameter.h`。 - Key最大长度为32Byte,string类型的Value最大长度为64Byte。 - 开发者请勿使用这三个Key,分别是`ctrlKey`、`bindKey`和`token`,Hekr云端登录机制有使用到它们。 - 此接口旨在用于存储用户参数,不建议用于存放用户数据。用户数据请使用底层`spi_flash_write()`接口。 示例代码: #include int ret = -1; char *key_1 = "key_1"; char *key_2 = "key_2"; ra_int32_t value_1 = 1000, value_int = 0; char *value_2 = "12345678123456781234567812345678"; char value_str[64]; //store params ra_set_parameter_integer(key_1, value_1); ra_set_parameter_string(key_2, value_2); //load params ra_get_parameter_integer(key_1, &value_int); ret = ra_get_parameter_string(key_2, value_str, 64); os_printf("value_int = %d\n", value_int); os_printf("ret = %d, value_str = %s\n", ret, value_str); //delete key_2 ra_delete_parameter(key_2); os_memset(value_str, 0, 64); ret = ra_get_parameter_string(key_2, value_str, 64); os_printf("ret = %d, value_str = %s\n", ret, value_str); uart1输出: value_int = 1000 ret = 32, value_str = 12345678123456781234567812345678 [log]ra_parameter.c:173: key_2 isn't exist //此为SDK内部打印 ret = -1, value_str = ###5.2 GPIO接口 #####5.2.1 逻辑引脚和物理引脚映射 SDK中gpio逻辑序号与esp8266的gpio物理序号一致。如gpio逻辑序号为12对应GPIO12,而它的引脚名称为`MTDI_U` #####5.2.2 配置GPIO为输出 配置GPIO14引脚为输出,且输出高电平: 示例代码: #include ra_gpio_set_direction(14, RA_GPIO_DIRECTION_OUTPUT); ra_gpio_output(14, 1); #####5.2.3 配置GPIO为输入 配置GPIO13引脚为输入,且输出高电平: 示例代码: #include ra_gpio_set_direction(13, RA_GPIO_DIRECTION_INPUT); ra_gpio_state_t state = ra_gpio_input(13); os_printf("gpio13 = %d\n", state); ###5.3 按键接口 SDK中基于gpio封装了注册按键中断处理回调接口,输入参数包括gpio序号、gpio中断类型、长按时间阈值、短按处理函数指针以及长按处理函数指针。 示例代码: 按键管脚为序号为13的管脚,下降沿触发,长按阈值为3000ms,短按处理函数指针指向`short_press_handler()`,长按处理函数指针指向`long_press_handler()`。 #include void short_press_handler(void) { os_printf("short press\n"); } void long_press_handler(void) { os_printf("long press\n"); } void ra_user_main(void) { ra_register_button_irq_handler(13, RA_GPIO_IRQ_TYPE_NEGEDGE, 3000, short_press_handler, long_press_handler); } gpio13电平拉低3s,则进入长按处理回调函数,打印`long press`;gpio13电平拉低短于3s,则进入短按处理回调函数,打印`short press`。 - 注:中断类型推荐使用下降沿触发`RA_GPIO_IRQ_TYPE_NEGEDGE` ###5.4 定时任务接口 SDK中定时接口是对底层api进行的二次封装,且增加了一些新特性,如查询定时任务剩余时间、查询定时任务是否可重复。 #####5.4.1 定时任务新建和启动 示例代码: 注册一个每隔2s重复执行的定时任务,每次执行打印输出`hello world` #include void timer_callback(ra_timer_t timer, void *arg) { os_printf("hello world\n"); } void ra_user_main(void) { ra_timer_t timer; ra_timer_new(&timer); ra_timer_set_callback(timer, timer_callback, NULL); ra_timer_start(timer, 2000, ra_true); } - 注:`ra_timer_start()`第三个参数值为`ra_false`时,定时任务不可重复 #####5.4.2 定时任务的停止与注销 `ra_timer_stop()`:定时器停止工作,但不释放定时器timer。无需调用`ra_timer_new()`来新建定时器 `ra_timer_delete()`:定时器停止工作,且释放定时器timer。 ###5.5 UART接口 SDK可以设置UART0或者UART1作为串口输出,UART0的TXD、RXD管脚分别对应ESP8266的TXD、RXD引脚,UART1对应的TXD管脚为GPIO2。 - 注:esp8266的串口接收中断仅对UART0有效,即`ra_uart_recv_enable()`只支持`RA_UART0`。 #####5.5.1 UART属性设置 UART可设置的属性包括波特率、数据位、奇偶校验位、停止位。(注:底层esp8266暂不支持奇偶校验)。默认属性为`9600`、`8`、`无`、`1`。 示例代码: 设置UART0波特率`115200`、数据位`7`,并从UART0重复输出`hello world`,同时注册串口接收回调函数。 #include #include void timer_callback(ra_timer_t timer, void *arg) { char *string = "hello world"; ra_uart_send_data(RA_UART0, string, os_strlen(string)); } void uart_recv_cb(char *buf, ra_size_t len) { os_printf("%s, len = %d\n", buf, len); } void ra_user_main(void) { //设置UART0属性 ra_uart_set_rate(RA_UART0, RA_UART_RATE_115200); ra_uart_set_data_bits(RA_UART0, RA_UART_DATA_BITS_7); //定时从UART0发送 ra_timer_t timer; ra_timer_new(&timer); ra_timer_set_callback(timer, timer_callback, NULL); ra_timer_start(timer, 3000, 1); //设置UART0的数据接收回调函数 ra_uart_register_recv_callback(RA_UART0, uart_recv_cb); //UART0接收中断使能 ra_uart_recv_enable(RA_UART0); } #####5.5.2 UART通信配置 UART发送配置: `ra_uart_set_sending_queue_interval()`:用于设置串口数据发送队列间隔,默认为100ms。当发送数据帧太快,数据帧会缓存在队列中,然后以一定的间隔时间依次发送出去。 UART接收配置: `ra_uart_set_recv_timeout()`:设置串口接收中断超时时间,默认为100ms。UART中断接收数据,如果超时时间内没有再接收到数据,则进入串口接收回调函数。 `ra_uart_set_recv_buffer_size_limit()`:设置串口接收回调触发长度,默认为256Byte。UART中断接收数据,当buffer长度达到设定值,则进入串口接收回调函数。 - 进入串口回调函数的触发条件由上面两个函数设置的参数决定,满足其中一个条件即进入回调函数。 ###5.6 设备状态接口 #####5.6.1 设备状态的读写操作 为了方便开发者对设备状态进行监控及控制,SDK中使用了设备状态机制。开发者可以根据设备当前实际状态,将对应的设备状态项`item`的值置为`ra_true`,待实际状态发生改变时,将该`item`置为`ra_false`。同时,开发者可在任意时候通过读取某`item`的值来判断设备是否处于该状态下。 typedef enum { RA_DEVICE_STATE_WLAN_CONNECTED = 0, //路由器连接成功 RA_DEVICE_STATE_WLAN_CONNECT_FAILED, //路由器连接失败 RA_DEVICE_STATE_CLOUD_CONNECTED, //服务器连接成功 RA_DEVICE_STATE_HEKR_CONFIG_RUNNING, //Hekr config模式下 RA_DEVICE_STATE_AIRKISS_CONFIG_RUNNING, //airkiss config模式下 } ra_device_state_type_t; `ra_device_state_type_t`枚举定义中包含了开发者可能用到的设备状态项,可以对它们进行`store`(设置)和`load`(读取)操作。 `ra_device_state_store()`:设置某个设备状态项`item`的值。开发者须根据设备实际状态来设置对应`item`值。 ra_device_state_store(RA_DEVICE_STATE_WLAN_CONNECTED, ra_true); //设备连接路由器成功,设置item "RA_DEVICE_STATE_WLAN_CONNECTED"为ra_true。 `ra_device_state_load()`:读取某个设备状态项`item`的值。根据该`item`值来判断设备当前状态,以便进行逻辑处理。 ra_device_state_load(RA_DEVICE_STATE_WLAN_CONNECTED); //根据返回值判断设备是否连接成功路由器,ra_true表示成功,ra_false表示失败。 - 开发者在实际应用中,`ra_device_state_type_t`中已有的设备状态项的`store`操作已经在SDK实现,开发者只需通过`load`操作来获取设备状态。 - 开发者可在`ra_device_state_type_t`枚举定义中添加其他设备状态项,它的`store`和`load`操作则自行实现。注:设备状态项请添加在已有项的后面。 - 所有设备状态项的初始值都是`ra_false`。 #####5.6.2 设备状态改变时的回调函数 为方便开发者快速接入氦氪云端,SDK中提供了wifi连接及服务器登陆的接口,在此过程中,设备状态会出现多种变化(如路由器连接成功、路由器连接失败、服务器登陆成功、服务器登陆失败等)。SDK中提供了设备状态改变的回调函数,当设备状态改变时(如连接上路由器)开发者可以根据`item`的值来判断设备当前状态,进行分支操作。 示例代码:可参考5.8节的相关示例 为了不影响当前设备状态项,在`ra_device_state_type_t`枚举定义中后面添加一条设备状态 `RA_DEVICE_STATE_DO_ACTION` #include #include void device_state_changed_callback(ra_device_state_type_t item, ra_bool current_state) { if (item == RA_DEVICE_STATE_DO_ACTION) { os_printf("RA_DEVICE_STATE_DO_ACTION. state = %d\n", current_state); } } void ra_user_main(void) { ra_register_device_state_changed_callback(device_state_changed_callback);//注册设备状态改变时的回调函数 if (ra_device_state_load(RA_DEVICE_STATE_DO_ACTION) == ra_false) { ra_device_state_store(RA_DEVICE_STATE_DO_ACTION, ra_true); } } uart1输出: RA_DEVICE_STATE_DO_ACTION. state = 1 ###5.7 wifi配网接口 - 配网方式有:`Hekr config`和`airkiss config`(暂不支持) 调用配网接口`ra_start_wifi_config(RA_WIFI_CONFIG_TYPE_HEKR)`即进入`Hekr config`模式,此时使用Hekr APP对设备进行一键配网,设备获取到ssid和password即进入配网处理回调函数中。 示例代码: #include void wifi_config_finish_callback(char *ssid, char *password) { if ((ssid != NULL) && (password != NULL)) { os_printf("wifi_config successed\n"); os_printf("ssid = %s, password = %s\n", ssid, password); //配网成功,获取到ssid及password,接着连接路由器 } else { os_printf("wifi_config failed\n"); //配网失败 } } void ra_user_main() { os_printf("wifi_config start\n"); ra_register_wifi_config_callback(wifi_config_finish_callback);//注册wifi config配网回调函数 ra_start_wifi_config(RA_WIFI_CONFIG_TYPE_HEKR);//启动wifi config } - 串口打印`********hekr_config run********`即表示进入配网模式。 - 配网超时时间为5min,超时后也进入配网回调函数,此时ssid和password均为NULL。 - 配网获取到的ssid和password未保存,用户可自行调用`wifi_station_set_config()`进行存储,避免重复配网。 - 进行配网前,用户可以通过`wifi_station_get_config(struct station_config *config)`接口的config参数来判断是否需要进入配网模式。 ###5.8 设备连接wifi及登陆服务器 设备须先连接上wifi路由器,然后进行登陆服务器操作。开发者可以使用`hekr config`一键配网获取ssid,也连接一个固定的ssid。以下示例配置固定的ssid。 示例代码: #include #include #include void recv_cloud_msg_callback(char *buf, ra_uint16_t len) { os_printf("buf = %s, len = %d\n", buf, len); //接收到云端数据 } void device_state_changed_callback(ra_device_state_type_t item, ra_bool current_state) { if (item == RA_DEVICE_STATE_WLAN_CONNECTED&& current_state == ra_true) { //路由器连接成功,接着登录服务器 os_printf("wifi connect successed\n"); ra_register_cloud_recv_callback(recv_cloud_msg_callback);//注册登录云端后,数据接收的回调函数 ra_connect_to_cloud();//登录云端服务器 } else if (item == RA_DEVICE_STATE_WLAN_CONNECT_FAILED&& current_state == ra_true) { os_printf("wifi connect failed\n"); } } void ra_user_main(void) { ra_register_device_state_changed_callback(device_state_changed_callback);//注册设备状态改变时的回调函数 ra_connect_wifi("WDD_TEST", "56781234", NULL, 30 * 1000); } - `ra_connect_wifi()`中第四个参数为wifi连接的超时时间,超时后设备停止wifi连接,并进入到设备状态改变的回调函数中,此时的`item == RA_DEVICE_STATE_WLAN_CONNECT_FAILED && current_state == ra_true`。 - 设备登陆服务器后就可以进行开发者自己的业务实现,同时,wifi连接异常及服务器连接异常处理开发者无需自行实现,SDK中集成了wifi重连及服务器重连机制。 - 设备登陆服务器时需要`prodKey`(在HEKR console平台创建产品时生成,长度为32 Bytes),请在`ra_user_main()`中调用`ra_set_parameter_string()`来设置,如`ra_set_parameter_string("prodKey", "12345678123456781234567812345678");`。 ###5.9 UDP局域网接口 SDK局域网通信采用UDP协议,wifi模块端作为Server,监听固定端口,端口号为10000,APP端作为Client。 > Client端的ip和port存在于UDP接收回调函数的参数`ra_remote_info_t *`中。 示例代码: #include //UDP数据接收回调函数中,ra_remote_info_t *remote是client端的ip和port void lan_recv_cb(ra_remote_info_t *remote, char *data, ra_uint16_t size) { os_printf("remote ip:%u:%u:%u:%u port:%u\n", remote->remote_ip[0], remote->remote_ip[1], remote->remote_ip[2], remote->remote_ip[3], remote->remote_port); os_printf("data = %s\n", data); ra_lan_comm_send(remote, data, size); //send data to remote } void ra_user_main() { ra_lan_comm_register_recv_callback(lan_recv_cb); //注册UDP数据接收回调函数 ra_lan_comm_server_start(); //启动UDP server,监听10000端口 //连接wifi ra_connect_wifi("WDD_TEST", "56781234", NULL, 30 * 1000); } ###5.10 OTA升级 esp8266的OTA升级方式将flash map分为A/B两个区,固件默认烧录到A区,OTA升级将新固件下载到B区,升级完成后系统重启从B区启动。再次升级时则固件下载至A分区,并从A分区启动,依次更替。 OTA升级指令(action为`devUpgrade`)是由云端主动下发,用户需要将指令中参数URL、MD5和固件类型提取出来,然后调用`ra_start_dev_upgrade()`启动OTA升级。用户在解析`devUpgrade`指令时可以不校验参数,`ra_start_dev_upgrade()`内部会进行校验,如果参数无误,则返回值为0,向云端回应devUpgradeResp时code为200;如果参数有误,则返回值为非0,向云端回应devUpgradeResp时code为非200(自行定义)。 > 注:如果代码中调用了`ra_enable_cloud_data_parse()`函数,则SDK内部集成了OTA升级,开发者无需调用OTA相关API来实现OTA升级。 示例代码: void OTA_successful_callback(void) { os_printf("OTA_successful!\n"); //升级成功后,要执行ra_system_upgrade_reboot()重启完成升级 ra_timer_t timer; ra_timer_new(&timer); ra_timer_set_callback(timer, ra_system_upgrade_reboot, NULL); ra_timer_start(timer, 2*1000, ra_false); } void OTA_fail_callback(int error_code) { os_printf("OTA_fail!\n"); } void ra_user_main() { int ret; ra_register_dev_upgrade_success_callback(OTA_successful_callback); //注册OTA成功回调函数 ra_register_dev_upgrade_failed_callback(OTA_fail_callback); //注册OTA失败回调函数 ret = ra_start_dev_upgrade(dev_TID, URL, MD5, B); //启动模块OTA升级,OTA的固件类型为B if(ret == 0) { //上报devUpgradeResp,code为200 } else { //上报devUpgradeResp,code为非200 } } - OTA升级成功后进入升级成功回调函数需调用`ra_system_upgrade_reboot()`,此时系统会重启,方可正常运行新版本固件。 - OTA进度上报在SDK中集成了,无需用户单独编程。 ###5.11 浮点打印的解决方案 由于esp8266 SDK并不支持浮点打印(包括`printf`及`sprintf`),氦氪在此基础上移植了一种变通解决方案来支持浮点打印。为了区分原生api,支持浮点的api命名为`c_printf`和`c_sprintf`,在`#include `中。 - SDK中的cJSON也支持浮点。 ## 6. 编码注意事项 --- - `include/ra`目录下提供的api函数均是以`ra_`为前缀命名,是该SDK主推供开发者调用的API接口。由于该SDK本身是基于原生`ESP8266 NON_RTOS SDK`深度开发而来,故ESP8266底层API也对开发者开放可供调用,但我们不推荐开发者使用底层API,以免造成RA接口的失效。`ra_`前缀api使用说明请参考`ra_embedded_api.chm`文档。 - 占用CPU时间超过`500ms`的函数不要在`ra_user_main()`直接调用,推荐在系统初始化完成后。可在入口函数中调用`ra_register_system_init_done_callback()`来注册系统初始化完成的回调函数。然后在回调函数中调用敏感api。 - 敏感API均为网络相关API,有`ra_disconnect_wifi()`。 - 设备接收到云端的数据都是操作指令(如appSend、devUpgrade、devSendResp等)对应的JSON字符串,用户需要自行解析成JSON格式,SDK中已经集成了cJSON接口,请参考头文件cJSON.h。 ###### 修饰符说明 - `FUN_ATTRIBUTE`:函数修饰符。函数前不加它,则上电启动时将函数代码从`flash`加载到`iram`;在函数前加上它,则在上电启动后函数被调用时才被加载到`iram`中。由于芯片`iram`空间有限,编码时请在函数前加上此修饰符,但不能给中断回调函数添加(定时器回调函数除外)。 - `RODATA_ATTRIBUTE`:变量修饰符。在变量(包括数组)定义前添加它,它将被写入flash中,减少系统运行期间堆的占用,只在变量使用时从flash中读取到`ram`中。 ## Change Log `V1.0.2`: SDK更新至v1.5.4.1 ,优化心跳机制