一、前言
由于项目更迭,需要将原4G模块更换为国内的WAPI协议模块,主控芯片NRF52840无需改动其他部分,只需要将串口部分的数据格式稍作更改即可。
编程风格和之前的esp8266一致,同样都是AT指令来配置模块,由于主控涉及到协议栈等内容,这里只展现AT指令集的封装,目的是作为配置参考,让更多人更快上手WAPI模块。
二、WAPI驱动
WAPI_M0804C.c
/** @Author: Li RF* @Date: 2024-01-23 15:27:25* @LastEditTime: 2024-02-20 14:28:48* @Description: WAPI模块驱动API* Email: 1125962926@qq.com* Copyright (c) 2024 by Li RF, All Rights Reserved. */#include "WAPI_M0804C.h"
//#include "delay.h"
#include "nrf_delay.h"/* 新模块配置流程* 1、上传证书,每次上电会预留时间,模块开启热点,通过网页192.168.88.1上传* 2、关闭回显(可选)* 3、关闭所有TCP/UDP连接* 4、断开与AP的连接,防止上一次配置的干扰* 5、设置静态IP或自动获取* 6、关闭自动连接AP功能(便于调试,实际使用可开启)* 7、设置模块频段(可选,默认2.4和5都开启)* 8、证书模式或密码模式连接至AP端* 9、创建TCP或UDP连接(若连接失败尝试关闭防火墙)* 10、开启接收服务器回应(每次连接都需要开启,否则只接收一次)* 11、发送数据*//* 若调试无误则打开自动连接AP功能 *//************************** 全局变量及宏定义 *********************************//* SOCKET连接序号,0~3。* 在建立TCP和UDP后,需要指定连接序号来发送或接收数据。* 建立连接时模块会返回连接序号。第一个连接会默认使用0。* 接收数据时也会返回连接序号,在数据处理函数中会自动更新全局变量。* 该全局变量可用于函数调用时传参,也可以用户自定义传入要处理的连接序号。*/
uint8_t socket_num = 0;/* 指令封装缓冲 */
static char WAPI_command[MAX_MESSAGE];/* UART发送缓冲和标志位 */
uint8_t WAPI_RX_BUF[WAPI_UART_BUF_Length]; //接收缓冲数组
uint16_t WAPI_RX_STA = 0; //接收计数及接收完成标志位/************************** 初始化 *********************************//*** @description: WAPI模块全局初始化,调用后可直接发送数据,初始化耗时大约5秒* 由于参数过多,调用前需要在函数体内调整传参,一般设置后无需改动。 * @return {*}*/
void WAPI_Init(void)
{//printf("Start Init WAPI! \r\n");nrf_delay_ms(2000);/* 重启 */WAPI_Reboot();nrf_delay_ms(2000);//printf("Open Hotpoint! \r\n");/* 等待上传证书,延时时间以30秒为单位,传入倍数。传入2,延时1分钟 */if(Start_upload_Certificate(1) == WAPI_FAL){nrf_delay_ms(500);Close_Hotpoint();//重新关闭}/* 关闭回显 *///printf("Close Echo! \r\n");WAPI_ECHO(0);nrf_delay_ms(500);/* 关闭所有连接 *///printf("Close All Connection! \r\n");Close_All_TCP_UDP();nrf_delay_ms(500);/* 断开AP的连接,防止上一次配置的干扰 *///printf("Disconnect With AP! \r\n");Disconnect_AP();nrf_delay_ms(500);/* 自动获取IP *///printf("Start DHCP! \r\n");Set_WAPI_IP(1, NULL, NULL, NULL);nrf_delay_ms(500);/* 关闭自动连接AP *///printf("Close Auto-Connection! \r\n");Auto_Connect_AP(0);nrf_delay_ms(500);/* 证书模式连接至AP,参数:连接并存储配置,证书连接,WAPI协议 *///printf("Trying Connect to AP... \r\n");while(WAPI_Connect_AP(1, 1, 0) == WAPI_FAL);//printf("Connection Complete! \r\n");/* 建立TCP连接,参数:TCP,服务器端口,本地端口 */WAPI_Establish_Connection(1, 4000, 4000);//printf("TCP Successful! Socket: %d \r\n", socket_num);nrf_delay_ms(500);/* 开启接收,参数:连接序号,文本模式,持续接收 *///printf("Open Reception! \r\n");Open_Reception(socket_num, 0, 1);//printf("WAPI Init Finished! \r\n");
}/************************** 连接API *********************************//*** @description: 连接AP端,连接过程耗时较长* @param {uint8_t} load_config: 0:连接不存储配置;1:连接并存储配置;2:读取配置并连接。* @param {uint8_t} connect_mode: 连接模式。 1: 证书连接 2: 密码连接* @param {uint8_t} use_WIFI: 用WAPI或WIFI连接。 1: WIFI 其余任意值: WAPI * @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret WAPI_Connect_AP(uint8_t load_config, uint8_t connect_mode, uint8_t use_WIFI)
{if(connect_mode == 1)//证书模式{/* 开启WAPI证书模式连接 */sprintf(WAPI_command, "AT+WAPICT,%d,%s", load_config, AP_SSID);}else if(connect_mode == 2){if(use_WIFI == 1)//WIFI{/* 连接 WIFI 热点 */sprintf(WAPI_command, "AT+WAPICT,%d,%s,%s,1", load_config, AP_SSID, AP_PASSWORD);}else{/* 连接 WAPI 热点 */sprintf(WAPI_command, "AT+WAPICT,%d,%s,%s", load_config, AP_SSID, AP_PASSWORD);}}return WAPI_send_cmd(WAPI_command, "already", 8 * WAITTIME);
}/*** @description: 建立TCP/UDP连接* @param {uint8_t} mode: 1:TCP 2:UDP* @param {int} server_port: TCP和UDP服务器端口* @param {int} wapi_port: WAPI模块本地端口* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret WAPI_Establish_Connection(uint8_t mode, int server_port, int wapi_port)
{if(mode == 1)//TCP连接{/* 创建TCP连接 */sprintf(WAPI_command, "AT+NCRECLNT,TCP,%s,%d,%d", SERVER_IP_ADDR, server_port, wapi_port);}else if(mode == 2)//UDP连接{/* 创建UDP连接 */sprintf(WAPI_command, "AT+NCRECLNT,UDP,%s,%d,%d", SERVER_IP_ADDR, server_port, wapi_port);}return WAPI_send_cmd(WAPI_command, "Socket:", WAITTIME);
}/*** @description: 发送数据至服务器* @param {uint8_t} socket: SOCKET连接序号。* @param {uint8_t} formation: 数据格式。0:文本模式; 1:16进制模式* @param {char} *information: 要发送的数据。* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL。*/
WAPI_ret WAPI_Send_Information(uint8_t socket, uint8_t formation, char *information)
{/* 发送数据,指定SOCKET连接序号,数据格式 */sprintf(WAPI_command, "AT+NSEND,%d,%d,%s", socket, formation, information);return WAPI_send_cmd(WAPI_command, "Send", WAITTIME);
}/*** @description: 开启TCP/UDP服务器数据接收* @param {uint8_t} socket_num: socket连接序号,0~3* @param {uint8_t} formation: 数据模式。0:文本模式; 1:16进制模式* @param {uint8_t} get_continue: 0:命令接收数据; 1:持续接收* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Open_Reception(uint8_t socket_num, uint8_t formation, uint8_t get_continue)
{/* 指定SOCKET连接序号,数据格式,持续接收 */sprintf(WAPI_command, "AT+NRECV,%d,%d,%d", socket_num, formation, get_continue);return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*
+RECV[0][5]:hello
2B 52 45 43 56 5B 30(0) 5D 5B 35(5) 5D 3A(:) 68(h) 65(e) 6C(l) 6C(l) 6F(o) 0D 0A
*//*** @description: : 处理串口接收的数据,如果服务器发送数据至WAPI模块,会以“+RECV”开头。* @param {char} *old_data: 串口UART接收的数据。* @param {char} *output: 提取出的信息,传参前需要定义,传入 char* 。* @return {char*} 有数据返回数据地址,没有数据则返回NULL。*/
char* WAPI_Date_Processing(char *old_data)
{ char *output = WAPI_Check_Data("+RECV", old_data);//找到+RECV字符串起始地址if(output){ socket_num = (uint8_t)output[6] - '0'; //取出连接号uint8_t len = (uint8_t)output[9] - '0'; //返回数据长度output = WAPI_Check_Data(":", output) + 1; //跳过RECV等前缀,指向数据output[len - 2] = '\0'; //去除回车换行}return output;
}/*** @description: 关闭TCP或UDP连接* @param {uint8_t} socket_num: socket连接序号,0~3* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Close_TCP_UDP(uint8_t socket_num)
{/* 指定SOCKET连接序号 */sprintf(WAPI_command, "AT+NSTOP,%d", socket_num);return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: 关闭所有的连接* @return {void}*/
void Close_All_TCP_UDP(void)
{for(uint8_t i = 0; i <= 3; i++){Close_TCP_UDP(i);nrf_delay_ms(100);//改为工程中的延时,间隔发送确保都全部关闭}
}/*** @description: 断开与AP端的连接* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Disconnect_AP(void)
{sprintf(WAPI_command, "AT+WSDISCNCT");return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/************************** 可选功能API *********************************//*** @description: UART1转发功能,UART1只支持数据透明转发,可用于没有CPU的传感器* @param {uint8_t} cmd: 0:关闭; 1:开启。* @param {int} boundrate: UART1波特率* @param {uint8_t} server_port: 服务器端口号* @param {uint8_t} wapi_port: 本地端口号,并建立UDP连接; 传入0为TCP连接。* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret UART1_Send(uint8_t cmd, int boundrate, uint8_t server_port, uint8_t wapi_port)
{if(wapi_port == 0)//TCP{sprintf(WAPI_command, "AT+UARTFWD=%d,%d,%s,%d", cmd, boundrate, SERVER_IP_ADDR, server_port);}else if(wapi_port > 0)//UDP{sprintf(WAPI_command, "AT+UARTFWD=%d,%d,%s,%d,%d", cmd, boundrate, SERVER_IP_ADDR, server_port, wapi_port);}else{return WAPI_FAL;//端口地址错误}return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: 设置WAPI模块IP地址,需要断开与AP的连接* @param {uint8_t} dhcp: 1:自动获取(后三个参数任意,可以NULL); 0:静态设置* @param {char} *ipaddr: IP地址* @param {char} *mask: 掩码* @param {char} *gateway: 网关* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Set_WAPI_IP(uint8_t dhcp, char *ipaddr, char *mask, char *gateway)
{switch (dhcp){case 0:sprintf(WAPI_command, "AT+WFIXIP=1,%s,%s,%s", ipaddr, mask, gateway);break;case 1:sprintf(WAPI_command, "AT+WFIXIP=0");break;default:return WAPI_FAL;}return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: 设置WAPI模块工作频段* @param {uint8_t} band: 1:只使用2.4G 2:只使用5G 3:都启用* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Set_WAPI_Band(uint8_t band)
{sprintf(WAPI_command, "AT+BAND=%d", band);return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: 自动连接AP端,需要提前连接过AP端并保存配置* @param {uint8_t} cmd: 0:关闭;1:开启。* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Auto_Connect_AP(uint8_t cmd)
{sprintf(WAPI_command, "AT+WAUTOCNCT=STA,%d", cmd);return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: 重启WAPI模块* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret WAPI_Reboot(void)
{sprintf(WAPI_command, "AT+REBOOT");return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: WAPI模块回显开关* @param {uint8_t} cmd: 1:打开回显 0:关闭* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret WAPI_ECHO(uint8_t cmd)
{sprintf(WAPI_command, "AT+ECHO=%d", cmd);return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: 进入休眠模式。需要连接至AP端才能进入休眠。* @param {uint8_t} dtim: 低功耗模式DTIM值,可选0,1,3,5,10。0为关闭,数值越大,接收间隔越大,平均电流越小。* @param {uint8_t} hib: 休眠深度,可选0~5。数值越大,平均电流越小。* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret WAPI_Sleep(uint8_t dtim, uint8_t hib)
{/* 设置接收间隔 */sprintf(WAPI_command, "AT+SETDP=%d", dtim);memset(WAPI_command, 0, MAX_MESSAGE);/* 设置休眠深度,进入休眠 */sprintf(WAPI_command, "AT+HIB=%d", hib);return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/************************** 开启热点,上传证书 *********************************//*** @description: WAPI模块开启热点,仅支持2.4G* @return {void} */
void Open_Hotpoint(void)
{sprintf(WAPI_command, "AT+WAPSTART=0,%s,%s", WAPI_SSID, WAPI_PASSWORD);WAPI_send_cmd(WAPI_command, "OK", 0);
}/*** @description: WAPI模块关闭热点* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Close_Hotpoint(void)
{sprintf(WAPI_command, "AT+WAPSTOP");return WAPI_send_cmd(WAPI_command, "OK", WAITTIME);
}/*** @description: 开启热点,允许上传证书。调用后会进行长时间延时,根据传参而定,延时时长为 count * 30 秒* @param {int} wapi_port: WAPI模块本地端口* @return {WAPI_ret} 成功返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret Start_upload_Certificate(uint32_t count)
{/* 开启热点 */Open_Hotpoint();//printf("Open_Hotpoint\n");for(int i = 0; i < count; i++){nrf_delay_ms(1000 * 30);printf("%ds\n", (i + 1) * 30);}/* 关闭热点 */return Close_Hotpoint();
}/************************** 内部调用 *********************************//*** @description: 向WAPI模块发送指令,并检查返回值* @param {char} *cmd: 要发送的指令* @param {char} *expect: 期望指令,检查模块返回值,查看指令执行情况,若不检查,调用时传入0(不带引号)或NULL* @param {int} waittime: 等待数据接收时长(ms),等待模块回应的最大时间,传入0则不等待接收* @return {WAPI_ret} 应答正确返回WAPI_OK,失败返回WAPI_FAL*/
WAPI_ret WAPI_send_cmd(char *cmd, char *expect, int waittime)
{WAPI_ret ret = WAPI_FAL;strcat(cmd, "\r\n"); //添加末尾回车,换行WAPI_send_string(cmd); //发送数据#if CHECK_ECHOint waittime_old = waittime; //保存等待时间char *temp_receive; //用于获取目标字符串首地址if(waittime && expect) //等待接收,检查返回值{while(--waittime){nrf_delay_ms(1);if(WAPI_RX_STA) //接收到数据{waittime = waittime_old;//刷新等待时间 temp_receive = WAPI_Check_Data(expect, (char *)WAPI_RX_BUF);//获取到目标字符串首地址WAPI_RX_STA = 0;//printf("%s\n", (char *)WAPI_RX_BUF);if(temp_receive){if(!strcmp("Socket:", expect)){socket_num = (uint8_t)temp_receive[7] - '0';}ret = WAPI_OK;}}}}return ret;
#elsereturn WAPI_OK;
#endif
} /*** @description: 检查要验证的字符串是否为原字符串的子串* @param {char} *str_target: 要查找的子串。* @param {char} *str_rx: 原字符串。* @return {char} 找到返回字符串出现的首地址,没有则返回NULL*/
char* WAPI_Check_Data(char *str_target, char *str_rx)
{return strstr((const char*)str_rx, (const char*)str_target);
}/*** @description: UART发送字符串* @param {uint8_t} *str: 要发送的字符串* @return {*}*/
void WAPI_send_string(char *str)
{while(*str != '\0'){WAPI_SEND_API(*str);str++;}
}
WAPI_M0804C.h
/** @Author: Li RF* @Date: 2024-01-23 16:51:02* @LastEditTime: 2024-02-20 12:37:11* @Description: * * Copyright (c) 2024 by Li RF, 1125962926@qq.com, All Rights Reserved. */
#ifndef __WAPI_M0804C_H
#define __WAPI_M0804C_H#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "app_uart.h"/* 全局宏开关 */
#define CHECK_ECHO 1 //是否开启返回值验证。1:开启/* 返回值定义 */
#define WAPI_ret uint8_t //函数返回值类型定义
#define WAPI_OK 1 //函数返回值
#define WAPI_FAL 0 //函数返回值/* WAPI模块相关配置 */
#define WAPI_SSID "WAPI-module" //WAPI模块热点SSID
#define WAPI_PASSWORD "12345678" //WAPI模块热点密码/* TCP服务器IP地址 */
#define SERVER_IP_ADDR "10.10.10.100" //TCP服务器IP地址/* AP端相关配置 */
#define AP_SSID "WAPI-test2.4" //AP端SSID
#define AP_PASSWORD "abc123456" //AP端密码/* 发送和接收配置 */
#define MAX_MESSAGE 256 //发送缓冲大小
#define WAITTIME 500 //等待模块响应时长,毫秒(ms)
#define DATA_FORMAT 0 //数据模式。0:文本模式; 1:16进制模式
#define WAPI_UART_BUF_Length 256 //UART缓冲大小
extern uint8_t WAPI_RX_BUF[WAPI_UART_BUF_Length];//UART接收缓冲数组
extern uint16_t WAPI_RX_STA; //UART接收计数及接收完成标志位
extern uint8_t socket_num;/* UART串口发送函数 */
#define WAPI_SEND_API app_uart_put //需要更改为工程中的UART发送函数(字节为单位发送(char))/************************** 初始化 *********************************/void WAPI_Init(void);//由于参数过多,需要到函数中修改/************************** 连接API *********************************//********* 建立连接 *********/WAPI_ret WAPI_Connect_AP(uint8_t load_config, uint8_t connect_mode, uint8_t use_WIFI);//连接AP端
WAPI_ret WAPI_Establish_Connection(uint8_t mode, int server_port, int wapi_port); //建立TCP或UDP连接/********* 发送数据 *********/WAPI_ret WAPI_Send_Information(uint8_t socket, uint8_t formation, char *information); //发送数据至服务器/********* 接收数据 *********/WAPI_ret Open_Reception(uint8_t socket_num, uint8_t formation, uint8_t get_continue);//开启服务器数据接收
char* WAPI_Date_Processing(char *old_data); //处理WAPI模块接收的数据/********* 断开连接 *********/WAPI_ret Close_TCP_UDP(uint8_t socket_num); //关闭TCP或UDP连接
void Close_All_TCP_UDP(void); //关闭所有连接
WAPI_ret Disconnect_AP(void); //断开与AP端的连接/************************** 可选功能API *********************************/WAPI_ret UART1_Send(uint8_t cmd, int boundrate, uint8_t server_port, uint8_t wapi_port); //开启或关闭UART1转发功能
WAPI_ret Set_WAPI_IP(uint8_t dhcp, char *ipaddr, char *mask, char *gateway); //设置IP地址
WAPI_ret Set_WAPI_Band(uint8_t band); //设置模块频段(2.4G或5G)
WAPI_ret Auto_Connect_AP(uint8_t cmd); //开启或关闭自动连接AP端
WAPI_ret WAPI_Reboot(void); //重启模块
WAPI_ret WAPI_ECHO(uint8_t cmd); //模块回显
WAPI_ret WAPI_Sleep(uint8_t dtim, uint8_t hib); //低功耗/************************** 开启热点,上传证书 *********************************/void Open_Hotpoint(void); //WAPI模块开启热点,仅支持2.4G
WAPI_ret Close_Hotpoint(void); //WAPI模块关闭热点
WAPI_ret Start_upload_Certificate(uint32_t count); //开启热点,允许上传证书,延时后关闭热点/************************** 内部调用 *********************************/WAPI_ret WAPI_send_cmd(char *cmd, char *expect, int waittime); //向WAPI模块发送指令,并检查返回值
char* WAPI_Check_Data(char *str_target, char *str_rx); //检查子字符串
void WAPI_send_string(char *str); //UART发送字符串#endif
三、结语
代码还有很多需要优化的地方,由于很多代码不知道能不能开源,所以只贴出了AT指令的封装,希望可以给读者提供一个编程思路。