一种单片机架构的实现

本文介绍了一种基于单片机的AT命令架构实现方法,包括AT命令的发送与接收机制、异常处理策略及模拟多进程机制。通过封装AT命令发送函数,实现命令的超时判断和结果验证;采用中断接收数据并利用定时器判断帧完整,确保数据正确处理。

一种单片机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;
			}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值