通信模块:BC28
主控:HC32F176KATA
背景:
在公司接受相应的通讯模块,发现要频繁的使用AT指令,然而公司使用的AT指令都是一条条的写的,需要逐条维护,十分麻烦。借着写nbiot项目时,便自己写了统一的AT指令框架,便于以后对于通讯模块的统一维护。
思路:
结合状态机原理,建立一个二维数组,即表格样式。里面分别存有at指令当前状态,下一状态,发送指令,接受正确应答指令,指令发送后没有应答的超时时间,重发次数,串口状态,以及特殊处理函数。
特殊处理函数是用来处理非单纯应答正确即可的指令的,比如存储设备的imei号码,我把它放在读取到所有应答数据后的下一步执行。
代码:
定义串口的状态,以及要用到的at指令
typedef enum
{
IDLE = 0,
SUCCESS_REC, //成功
TIME_OUT, //超时
NO_REC //未收到
} rec_state_t;
typedef enum
{
AT = 0, /*发送AT指令测试*/
CFUN0,
NCSEARFCN,
NCONFIG1,
NCONFIG2,
NCONFIG3,
NCONFIG4,
NBAND,
NCDP,
QREGSWT,
CFUN1,
NCCID,
CIMI,
CGSN,
CPSMS,
// CEDRXS,
NPTWEDRXS,
NRB,
CEREG,
CGATT,
NNMI,
CSQ,
QLWULDATA,
FINISH
} comd_state_e;
定义at指令的状态机对象:
typedef struct
{
comd_state_e cur_state; // 当前状态
comd_state_e next_state; // 下一个状态
char AtSendStr[128]; // 发送字符串(AT命令)
char ATRecStr[128]; // 需要返回的正确字符串
int wait_time; // 等待时间,单位为ms
rec_state_t at_status; // 接收状态
int try_cnt; // 重试次数
uint8_t (*recv_deal)(char* data, uint8_t len); //动作:需要对某些返回的数据记录或者错误处理
}fsm_state_t;
定义完整的at指令表格,程序基本按照表格里的at指令顺序执行
fsm_state_t ATCmds[] =
{
//参数分别为
//当前状态 下一个状态 向NB-IOT发送字符串(AT命令)、 模块应该返回的正确指令、 设置超时(毫秒)、AT指令接收状态、设置重发次数
{AT, CFUN0, "AT\r\n", "OK", 10000, IDLE, 10,default_deal},// 发送at指令,确定模块是否正常
{CFUN0, NCSEARFCN, "AT+CFUN=0\r\n", "OK", 5000, IDLE, 3, default_deal},// 关闭射频功能(不进行无线通讯)
{NCSEARFCN, NCONFIG1, "AT+NCSEARFCN\r\n", "OK", 300, IDLE, 3, default_deal},// 清除存储的频点
{NCONFIG1, NCONFIG2, "AT+NCONFIG=CR_0354_0338_SCRAMBLING,TRUE\r\n", "OK", 300, IDLE, 3, default_deal},// 打开扰码控制
{NCONFIG2, NCONFIG3, "AT+NCONFIG=CR_0859_SI_AVOID,TRUE\r\n", "OK", 300, IDLE, 3, default_deal},// 打开扰码控制
{NCONFIG3, NCONFIG4, "AT+NCONFIG=AUTOCONNECT,TRUE\r\n", "OK", 300, IDLE, 3, default_deal},// 配置模块自动连接网络
{NCONFIG4, NBAND, "AT+NCONFIG=CELL_RESELECTION,TRUE\r\n", "OK", 300, IDLE, 3, default_deal},// 小区重选
{NBAND, NCDP, "AT+NBAND=5\r\n", "OK", 300, IDLE, 3, default_deal},// 设置频段为电信的频段
{NCDP, QREGSWT, "AT+NCDP=221.229.214.202,5683\r\n", "OK", 300, IDLE, 3, default_deal},// 云平台接入ip地址及端口设置
{QREGSWT, CFUN1, "AT+QREGSWT=1\r\n", "OK", 300, IDLE, 3, default_deal},// 设置为1,模块在重启并连接到网络后会触发自动注册物联网平台
{CFUN1, NCCID, "AT+CFUN=1\r\n", "OK", 5000, IDLE, 10, default_deal},// 开启射频功能
{NCCID, CIMI, "AT+NCCID\r\n", "OK", 300, IDLE, 3, default_deal},// 确认sim卡是否存在
{CIMI, CGSN, "AT+CIMI\r\n", "OK", 300, IDLE, 3, default_deal},// 返回 IMSI 码
{CGSN, CPSMS, "AT+CGSN=1\r\n", "\r\n+CGSN:", 300, IDLE, 3, default_deal},// 返回 IMEI 码
{CPSMS, NPTWEDRXS, "AT+CPSMS=0\r\n", "OK", 300, IDLE, 3, default_deal},// PSM模式设置
// {CEDRXS, NRB, "AT+CEDRXS=0,5\r\n", "OK", 300, IDLE, 3, default_deal},// eDRX模式设置
{NPTWEDRXS, NRB, "AT+NPTWEDRXS=3,5\r\n", "OK", 300, IDLE, 3, default_deal},// eDRX模式设置
{NRB, CEREG, "AT+NRB\r\n", "+QLWEVTIND:3", 60000, IDLE, 5, default_deal},// 模块重启
{CEREG, CGATT, "AT+CEREG?\r\n", "\r\n+CEREG:0,1", 5000, IDLE, 10,default_deal},// 查询网络注册状态
{CGATT, NNMI, "AT+CGATT=1\r\n", "OK", 300, IDLE, 3, default_deal},// 使能网络附着
{NNMI, CSQ, "AT+NNMI=1\r\n", "OK", 300, IDLE, 3, default_deal},// 接收到一个下行消息后会发送新消息指示
{CSQ, FINISH, "AT+CSQ\r\n", "\r\n+CSQ:", 300, IDLE, 3, CSQ_deal},// 查询信号强度
{QLWULDATA, FINISH, "AT+QLWULDATA=", "OK", 1000, IDLE, 3, QLWULDATA_deal},// 发送数据
};
接下来是AT指令的接收,发送函数:
fsm_state_t cur = {0,0,0,0,0,0,0,0}; // 相当于一个游动指针,表示当前状态,执行完就更新
static void At_send(fsm_state_t cmd)
{
if(cur.at_status == IDLE)
{
Uart_NB.Uart_SendString(cmd.AtSendStr,strlen(cmd.AtSendStr));
at_recv_time = cmd.wait_time;
}
}
static void At_recv(fsm_state_t *cmd)
{
uint8_t i;
if(cur.try_cnt == 0) //发送次数用完处理
{
// 暂定初始化重来
cur.at_status = NO_REC;
}
// 尚有发送次数时
else
{
if(at_recv_time > 0) // 接收时间未超时
{
cur.at_status = NO_REC; // 没收到数据
//while( (Uart_NB.rx_flag != 1) && (at_recv_time > 0) );
if(Uart_NB.rx_flag == 1) // 规定时间内nbiot串口接收到数据
{
Uart_NB.rx_flag = 0;
memset(atbuff, 0, sizeof(atbuff));// 清空at指令接收缓存
for(i=0; i<Uart_NB.rx_cnt;i++) // 接收到的数据复制到缓存
{
atbuff[i] = U1_RxBuffer[i];
}
if( strstr(atbuff, cmd->ATRecStr ) != NULL)
{
cur.at_status = SUCCESS_REC; // 接收状态赋值为成功
cmd->recv_deal(atbuff, Uart_NB.rx_cnt); // 接收nb模块数据处理
// 接收到数据后,更新当前执行状态机状态
cur.cur_state = cur.next_state;
cur.next_state = ATCmds[cur.cur_state].next_state;
cur.try_cnt = ATCmds[cur.cur_state].try_cnt;
cur.at_status = ATCmds[cur.cur_state].at_status;
}
Uart_NB.rx_cnt = 0; //串口接收缓存清零
}
}
else if(at_recv_time == 0) // 超时处理
{
cur.try_cnt--;
cur.at_status = IDLE;
}
}
}
封装相应的初始化函数和任务函数,后续注册给相应的通讯模块,放进主循环即可:
void AT_init()
{
cur.cur_state = ATCmds[AT].cur_state;
cur.next_state = ATCmds[AT].next_state;
cur.try_cnt = ATCmds[AT].try_cnt;
}
void At_task()
{
if(cur.cur_state != FINISH )
{
At_send(ATCmds[cur.cur_state]);
At_recv(&ATCmds[cur.cur_state]);
}
ZD_NB_transfer();
}