一、前言
由于项目更迭,需要将原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_ECHO
int 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;
#else
return 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指令的封装,希望可以给读者提供一个编程思路。