一种单片机AT命令架构实现
简介
不带操作系统的单片机只能有一个进程,但是单片机中既有发送,又有接收的各种需求,如何设置程序的结构呢。
AT命令的发送
直接封装成函数,向串口发送命令即可,但是需要判断命令的返回值,要设定一个超时时间,在这个超时时间内,是否收到了期望的结果。
如下代码例
uint8_t BC_SendCmd(char *cmd, char *res, unsigned short timeOut)
{
_Bool bRet = 1;
snprintf(Debug_Info, sizeof(Debug_Info), "AT CMD:%s", cmd); //Debug only
#ifdef DEBUG
printf("%s",Debug_Info); memset(Debug_Info,0,256);
#endif
//往串口里发送命令
NET_IO_Send((unsigned char *)cmd, strlen(cmd)); //写命令到网络设备
if(res == NULL) //如果为空,则只是发送
return 0;
net_device_info.cmd_hdl = res; //需要所搜的关键词
//等待串口的返回值,指导超时时间,如果没返回就返回失败了。
while((net_device_info.cmd_hdl != NULL) && --timeOut) //等待
HAL_Delay(10);
if(0 != timeOut){
bRet = 0;
} else {
bRet = 1;
}
return bRet; //timeOut ? 0 : 1;
}
这个函数的调用
/* 这个AT命令期待返回IPV4,如果返回的不是IPV4,改函数会返回
* 非0值,即是错误码。
*/
if(!BC_SendCmd("AT+CGDCONT?\r\n", "IPV4", 200)){
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
debug("%s\r\n", net_device_info.cmd_resp);
}
初始化
初始化按着一个模块初始化的时候需要判断的一些命令,一条一条的执行,如果不成功就反复尝试,如下的代码详细提供解释。判断驻网是否成功,判断信号,判断是否注册成功,open mqtt服务,连接mqtt服务器等等。详细看如下的代码实现。
uint8_t BC_Tcp_Init(unsigned char protocol, char *ip, char *port)
{
net_device_info.net_work = 0;
uint8_t status = 1;
char cmd[40] = {0};
printf("BC_init\r\n");
switch(net_device_info.init_step)
{
case 0:
if(!BC_SendCmd("AT\r\n", "OK", 200)){
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
debug("ERROR\r\n");
}
break;
case 1:
if(!BC_SendCmd("AT+CIMI\r\n", "OK", 200)){
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
debug("ERROR\r\n");
}
break;
case 2:
if(!BC_SendCmd("AT+CSQ\r\n", "OK", 200)){
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
debug("ERROR\r\n");
}
break;
case 3:
//查询网络注册状态
if(!BC_SendCmd("AT+CEREG?\r\n", ",1", 200)){
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
if(strstr(net_device_info.cmd_resp, ",5")) {
net_device_info.init_step++;
} else {
debug("%s\r\n", net_device_info.cmd_resp);
}
}
break;
case 4:
//查询pdp的类型
if(!BC_SendCmd("AT+CGDCONT?\r\n", "IPV4", 200)){
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
debug("%s\r\n", net_device_info.cmd_resp);
}
break;
case 5:
//创建socket
if(!BC_SendCmd("AT+NSOCR=\"STREAM\",6,6003,1\r\n", "OK", 200)){ //创建tcp socket
char * resp = net_device_info.cmd_resp;
//char *p;
//提取socket number
//p=strstr(resp, ":");
//p++;
resp++; //越过换行符
socketNum = *resp;
//printf("socketNum:%c\r\n",socketNum);
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
debug("%s\r\n", net_device_info.cmd_resp);
}
break;
case 6:
//建立连接
debug("tcp connecting socket:%c,ip:%s,port:%s\r\n",socketNum,ip,port);
snprintf((char *)cmd, sizeof(cmd), "AT+NSOCO=%c,%s,%s\r\n", socketNum,ip,port);
if(!BC_SendCmd(cmd, "CONNECT OK", 200)){ //创建连接远端服务器,1是表示socket,就是上边出创建的socket
debug("%s\r\n", net_device_info.cmd_resp);
net_device_info.init_step++;
} else {
debug("%s\r\n", net_device_info.cmd_resp);
}
break;
default:
status = 0;
net_device_info.send_ok = 1;
net_device_info.net_work = 1;
break;
}
return status;
}
发送
直接调用AT命令进行发送
uint8_t BC_SendData(uint8_t *data, uint16_t len)
{
char cmdBuf[40] = {'\0'};
uint8_t tmp_data[40] = {'\0'};
unsigned char time_out = 200;
unsigned short count;
while(!net_device_info.send_ok && --time_out) //等待上一次数据发送成功
HAL_Delay(10);
net_device_info.send_ok = 0;
string_to_hexString(data,tmp_data,len);
sprintf(cmdBuf, "AT+NSOSD=%c,%d,%s\r\n",socketNum,len,tmp_data);
while(BC_SendCmd(cmdBuf,"OK",300)) {
count++;
if(count > 10) {
break;
}
HAL_Delay(200);
}
if(count>10) {
return 1; //发送失败
}
HAL_Delay(50);
return 0; //成功
}
模拟多进程机制
一个一毫秒定时器中断,每隔一毫秒调用一次,需要不断执行的操作都放在这里。如果需要一秒钟执行一次的,就可以在这个处理函数里累加,知道累加到1000了,就设置个标志,在主循环中检测到该标志,就执行相应的操作,再隔1000ms在执行一次。
void HAL_SYSTICK_Callback(void)
{
uint16_t i;
if(1U == bCPU_Initialized)
{
if(1U == bUartRcvActing)
{
if(0U != u16UartRcvTimer)
{
u16UartRcvTimer --;
}
else
{
if(netIOInfo.data_count > 1) //">" pub提示符就是一个字符,所以这里这样修改。
{
NET_IO_AddTag2End(); //收到一帧完整数据,在数据尾添加结束符
}
HAL_UART_TxCpltCallback(&hlpuart1); //持续接收
}
}
u16ADSampTimer ++;
if(u16_ADC_SAMP_CYCLE_MS <= u16ADSampTimer)
{
bAD_Samp_Trig = 1U;
u16ADSampTimer = 0U;
}
u16NetTaskTimer ++;
if(u16_GPRS_CONNECT_INTERVAL_MS <= u16NetTaskTimer)
{
bNet_Task_Trig = 1U;
u16NetTaskTimer = 0U;
}
u16NetCmdHandleTimer ++;
if(10U <= u16NetCmdHandleTimer)
{
//10ms
BC_ATCmdHandle();
u16NetCmdHandleTimer = 0U;
}
u16NetTxDataTimer ++;
//提前2S苏醒
if((38*1000) <= u16NetTxDataTimer) {
//唤醒模组
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}
if((5*1000) <= u16NetTxDataTimer) //毫秒为单位 40秒。
{
bNet_TxData_Trig = 1U;
u16NetTxDataTimer = 0U;
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
}
u16IOTestTimer++;
if(0U!=u16LockCtrlTestTimer)
{
u16LockCtrlTestTimer --;
}
u16TcpRcvDataTimer++;
if(20 <= u16TcpRcvDataTimer) {
}
for(i=0; i<u16_LOCK_NUMBER; i++)
{
if(0U != u16Lock_ON_Timer[i])
{
u16Lock_ON_Timer[i] --;
}
else
{
if(LOCK_ON_STATUS == bLock_Read_Status[i] || LOCK_ON == bLock_Ctrl_Status[i])
{
bLock_Ctrl_Status[i] = LOCK_OFF;
//GPIO_DRV_LockControl(i, LOCK_OFF);
}
}
}
}
}
接收
接收时会发生中断的,中断将数据加到一个循环队列中。一旦开始收数据就起一个100ms的定时器,如果100ms的定时器内没有再收到信的字符,这个定时器会被重置为100ms,如果100ms的定时器超时,证明这一帧的数据已经收完,在数据结尾添加结束符,并告知应用成队列里有了一个完整的包,你可以取数据处理了。app收到信号好,从队列中取数据处理。
//在1ms中断中做如下的处理。
if(1U == bUartRcvActing)
{
if(0U != u16UartRcvTimer)
{
u16UartRcvTimer --;
}
else
{
if(netIOInfo.data_count > 1) //">" pub提示符就是一个字符,所以这里这样修改。
{
NET_IO_AddTag2End(); //收到一帧完整数据,在数据尾添加结束符
}
HAL_UART_TxCpltCallback(&hlpuart1); //持续接收
}
}
//如果收到了一帧完整的报文,在1ms中断的下边,收到完整的一帧报文,BC_ATCmdHandle函数才能执行下去,否则dataPtr会返回NULL。
void BC_ATCmdHandle(void)
{
unsigned char *dataPtr = NULL;// *ipdPtr = NULL; //数据指针
dataPtr = NET_IO_Read(); //等待数据
if(dataPtr != 0) {
#ifdef DEBUG
printf("\r\n-----------rcv start---------\r\n");
printf("%s\r\n",dataPtr);
printf("-----------rcv end-----------\r\n");
#endif
}
if(dataPtr != NULL) //数据有效
{
memcpy(Resp_Info, dataPtr, sizeof(Resp_Info)); //Debug only
//检查接收提示,如果提示有接收到数据把数据收进来
if(strstr((char *)dataPtr,"+NSONMI:")){ //表示有数据收到了,并且冒号后边包含socket 和 datalen.
//解析出socket和数据个数
char socket;
char data_len[10] = {'\0'};
char * p = NULL;
//char cmd[30] = {'\0'};
int k=0;
p = strstr((char *)dataPtr,":");
if(p != NULL) {
p++;
} else {
printf("ERROR:+NSONMI parse :\r\n");
}
socket = *p;
//printf("socket:%c",socket);
p = strstr((char *)dataPtr,",");
if(p != NULL) {
p++;
} else {
printf("ERROR:+NSONMI parse ,\r\n");
}
while(1){
if(p[k] >= '0' && p[k] <= '9') {
data_len[k] = p[k];
k++;
} else {
break;
}
}
//收数据进来
//设置一个全局变量,在main_loop中收,不能在这里收的原因是因为,在中断中不能加delay函数,在sendcmd中有delay函数。
data_wait_receive = 1;
rcv_socket = socket;
memcpy(rcv_data_len,data_len,sizeof(rcv_data_len));
} else if(strstr((char *)dataPtr,"+NSOCLI:")){
char * p;
//debug("rcved +NSOCLI:\r\n");
//进入该分支,表示tcp链接server端中断,程序要做重新连接。
p = strstr((char *)dataPtr,":");
while(*p<'0' || *p>'9') {
p++;
}
debug("rcv_socket:%c,%c\r\n",rcv_socket,*p);
if(rcv_socket == *p) {
debug("set tcp_need_reconnect as 1.\r\n");
//net_fault_info.net_fault_level = net_fault_info.net_fault_level_r = NET_FAULT_LEVEL_1;
tcp_need_reconnect = 1;
}
}else if(strstr((char *)dataPtr,"+QMTRECV:")){
//int i;
//有订阅的数据收到了
//char * p = NULL;
char conn_id[3]={0},msg_id[3] = {0},topic[100]={0},data[256]={0};
get_sub_str((char *)dataPtr,": ",",",0,conn_id);
get_sub_str((char *)dataPtr,",",",",0,msg_id);
get_sub_str((char *)dataPtr,",",",",1,topic);
get_sub_str((char *)dataPtr,",","\r\n",2,data);
printf("conn_id:%s,msg_id:%s,topic:%s,data:%s\r\n",conn_id,msg_id,topic,data);
//可以将这些数据加入到缓冲区中,主循环中在到缓冲区中取。
}else if(strstr((char *)dataPtr,"+QMTSTAT")){ //mqtt 重连判断
char * p;
char conn_id;
char err_code;
//取connectID
p = strstr((char *)dataPtr,":");
while(*p<'0' || *p>'9') {
p++;
}
conn_id = *p;
debug("rcv_socket:%c,%c\r\n",rcv_socket,conn_id);
p = strstr((char *)dataPtr,",");
while(*p<'0' || *p>'9') {
p++;
}
//取err_code, 如果不err_code 不等于5 就去重连。
err_code = *p;
if((rcv_socket == conn_id) && (err_code != 5)) {
debug("set mqtt_need_reconnect as 1.\r\n");
//net_fault_info.net_fault_level = net_fault_info.net_fault_level_r = NET_FAULT_LEVEL_1;
mqtt_need_reconnect = 1;
}
} else {
NET_DEVICE_CmdHandle((char *)dataPtr);
}
//检查tcp断开连接的提示符做相应的处理
}
}
在上边函数中有如下一段,判断如果收到的一帧数据包含+QMTRECV: 就是收到的mqtt订阅的数据,将收到的数据按如下方式解析,根据应用想去哪些数据就取哪些数据。
}else if(strstr((char *)dataPtr,"+QMTRECV:")){
//int i;
//有订阅的数据收到了
//char * p = NULL;
char conn_id[3]={0},msg_id[3] = {0},topic[100]={0},data[256]={0};
get_sub_str((char *)dataPtr,": ",",",0,conn_id);
get_sub_str((char *)dataPtr,",",",",0,msg_id);
get_sub_str((char *)dataPtr,",",",",1,topic);
get_sub_str((char *)dataPtr,",","\r\n",2,data);
printf("conn_id:%s,msg_id:%s,topic:%s,data:%s\r\n",conn_id,msg_id,topic,data);
//可以将这些数据加入到缓冲区中,主循环中在到缓冲区中取。
异常处理机制
上边函数有如下一段,如果收到+QMTSTA字符串,进入异常处理分支,根据异常的提示数字,选在是重新链接mqtt服务器,还是做一些什么初始化工作,来恢复链接。
}else if(strstr((char *)dataPtr,"+QMTSTAT")){ //mqtt 重连判断
char * p;
char conn_id;
char err_code;
//取connectID
p = strstr((char *)dataPtr,":");
while(*p<'0' || *p>'9') {
p++;
}
conn_id = *p;
debug("rcv_socket:%c,%c\r\n",rcv_socket,conn_id);
p = strstr((char *)dataPtr,",");
while(*p<'0' || *p>'9') {
p++;
}
//取err_code, 如果不err_code 不等于5 就去重连。
err_code = *p;
if((rcv_socket == conn_id) && (err_code != 5)) {
debug("set mqtt_need_reconnect as 1.\r\n");
//net_fault_info.net_fault_level = net_fault_info.net_fault_level_r = NET_FAULT_LEVEL_1;
mqtt_need_reconnect = 1;
}
本文介绍了一种基于单片机的AT命令架构实现方法,包括AT命令的发送与接收机制、异常处理策略及模拟多进程机制。通过封装AT命令发送函数,实现命令的超时判断和结果验证;采用中断接收数据并利用定时器判断帧完整,确保数据正确处理。
4581





