单片机与AT指令模块之间的通信

市场上有很多的模块,比如蓝牙,WIFI,NB模块通常都是采用AT指令来与之通信,但是每个模块的AT指令不太一样,每个模块的每条指令又不太一样,所以做一个通用的模板,这个模板主要可以实现:

1.无操作系统实时性,处理时不阻塞其他代码的运行
2.可扩展性、移植性、复用性比较好

实时性是因为,可以将需要延时等待的部分分割出来,然后等待超时或者等待到标志才继续运行下面的步骤,有点操作系统里等待信号量的意思
分片
第二点是因为使用表来保存AT指令列表,有点面向对象的意思

下面就用stm32 + hal库 + 蓝牙模块来做具体做一遍,用到的知识有:
C语言:关键字static、const,函数指针,void指针,结构体、联合体,位域,堆内存
数据结构:表,链表,消息队列
查找算法:顺序查找
编程思想:多线程,面向对象

具体过程为

1.建AT指令表
2.写AT指令处理过程
3.写给其他程序调用的接口函数

下面就分步来说

1.建AT指令表

使用结构体或者xmacro来建表,两种方法不太一样,不一样的地方可能就是这部分的写法不一样,但是和后面几点是不耦合的,示例用结构体建表
在 ble.h 创建AT指令所需要的参数

/*
	ble指令的结构体
*/
typedef struct
{
	uint8_t at_num;		/*编号*/
	uint8_t resend;		/*重发次数*/
	uint16_t at_time;	/*指令之间等待时间*/
	uint16_t time_out;	/*超时时间*/
	char *at_str;		/*发送指令*/
	char *recv_str;		/*期待回复值*/
	uint8_t (*at_send_callback)(uint8_t at_index,void *param);	/*回调函数*/
	uint8_t (*at_recv_callback)(uint8_t at_index,void *param);	/*回调函数*/
	
}BLEATStruct;

在ble.c创建结构体数组,也就是AT指令表,并写好相关的函数

BLEDataStruct ble_data = {0};
BLEProcessStruct ble_main = {0};
BLEParamStruct ble_param = {0};

static uint8_t ble_at_test(uint8_t at_index,void *param);
static uint8_t ble_at_test_recv(uint8_t at_index,void *param);
static uint8_t ble_at_broadcast_time(uint8_t at_index,void *param);
static uint8_t ble_at_config_name(uint8_t at_index,void *param);


/*
	指令列表
	需要添加新的指令步骤
	1.在下列的表添加信息
	2.写发送函数
	3.如果有需要,写接收函数
	4.调用
*/
static const BLEATStruct ble_at_table[] = 
{
	/*编号	重发次数	指令间隔	超时时间	发送字符		返回字符			发送回调				接收回调		*/
	{ 0 ,	5,			100,		1000,		"AT",			"OK"	,			ble_at_test,				NULL		},
	{ 1 ,	5,			100,		1000,		"AT+AINT=",		"OK+AINT"	,		ble_at_broadcast_time,	ble_at_test_recv},
	{ 2 ,	5,			100,		1000,		"AT+NAME=",		"OK+NAME"	,		ble_at_config_name,		ble_at_test_recv},
};


/*
	AT指令测试
*/
static uint8_t ble_at_test(uint8_t at_index,void *param)
{
	/*复制字符到待发送区*/
	add_data_to_send_buf((uint8_t *)ble_at_table[at_index].at_str,strlen(ble_at_table[at_index].at_str));
	
	return 1;
}

/*
	AT指令测试回复
*/
static uint8_t ble_at_test_recv(uint8_t at_index,void *param)
{
	printf("配置成功\r\n");
	return 1;
}

/*
	配置广播时间间隔
*/
static uint8_t ble_at_broadcast_time(uint8_t at_index,void *param)
{
	char temp[32] = {0};
	uint16_t *time = (uint16_t *)param;
	
	add_data_to_send_buf((uint8_t *)ble_at_table[at_index].at_str,strlen(ble_at_table[at_index].at_str));
	
	sprintf(temp,"%d",*time);
	
	add_data_to_send_buf((uint8_t *)temp,strlen(temp));
	
	return 1;
	
}

/*
	配置模块的名字
*/
static uint8_t ble_at_config_name(uint8_t at_index,void *param)
{
	char temp[16] = {0};
	
	char *name = (char *)param;
	
	add_data_to_send_buf((uint8_t *)ble_at_table[at_index].at_str,strlen(ble_at_table[at_index].at_str));
	
	sprintf(temp,"%s",name);
	
	add_data_to_send_buf((uint8_t *)temp,strlen(temp));
	
	return 1;
}

如果需要对指令增删改查的话,可以直接在这个表里面操作

2.写AT指令处理过程

接下来就是需要做整个AT指令的处理过程了,大概的流程就是
处理流程图
这个处理过程主要就是把整个AT指令的发送,等待过程分割开来,就不会阻塞其他任务的运行了,有两种方法做,一种是用switch,一种也是建表,用指针函数来执行对应的步骤,还是用表来做
ble.h,创建两个结构体,一个是步骤表结构体,一个是步骤状态结构体

/*自己的进程状态*/
typedef struct
{
	union{
		uint8_t allbit;
		struct{
			uint8_t ready:1;
			uint8_t running:1;
			uint8_t suspend:1;
			uint8_t lock:1;
		}bits;
	}state;
	uint8_t step;
	uint8_t resend_cnt;		/*重发次数*/
	uint32_t timer;	/*定时器*/
	
}BLEProcessStruct;

/*
	步骤表结构体
*/
typedef struct
{
	uint8_t step;
	void (*process)(void *param);
	
}bleProcessStruct;

在ble.c里建步骤表,然后编写处理过程函数和步骤函数

/*
	查找发送的指令
*/
static int16_t find_at_index_in_table(uint8_t AT)
{
	int16_t index = 0;
	uint8_t table_len = sizeof(ble_at_table) / sizeof(BLEATStruct);
	for(;;)
	{
		if(ble_at_table[index].at_num == AT) break;
		else if(++index > table_len) return -1;
	}
	
	return index;
}




static void ble_process_send_at(void *param);
static void ble_process_wait_recv(void *param);
static void ble_process_recv(void *param);
static void ble_process_fail(void *param);
static void ble_process_end(void *param);

const bleProcessStruct ble_process_table[] = 
{
	{0,		ble_process_send_at	},	/*查表发送数据*/
	{1,		ble_process_wait_recv		},	/*等待信号量或者超时*/
	{2,		ble_process_recv		},	/*成功之后的处理*/
	{3,		ble_process_fail		},	/*失败处理*/
	{4,		ble_process_end	}					/*资源回收*/
};


/*
	步骤0:查找到是哪个AT指令并发送数据
*/
static void ble_process_send_at(void *param)
{
	ble_param.at_index = find_at_index_in_table(ble_param.at_queue->now_at_num);
	memset(ble_data.senddata,0,BLE_SEND_MAX);
	ble_data.sendlen = 0;
	
	/*如果发送回调不为空,则调用*/
	if(ble_at_table[ble_param.at_index].at_send_callback != NULL)
		ble_at_table[ble_param.at_index].at_send_callback(ble_param.at_index,ble_param.at_queue->param);
	
	ble_main.state.bits.suspend = 1;
	HAL_UART_Transmit(&UART_HANDLE,ble_data.senddata,ble_data.sendlen,1000);
	
	ble_main.timer = HAL_GetTick();
	ble_main.step = 1;
	
}

/*
	步骤1:等待回复或者超时
*/
static void ble_process_wait_recv(void *param)
{
	/*如果有回复,则直接跳转到步骤2*/
	if(!ble_main.state.bits.suspend) ble_main.step = 2;
	
	/*等待回复*/
	if(HAL_GetTick() - ble_main.timer > ble_at_table[ble_param.at_index].time_out)
	{
		/*重发*/
		if(++ble_main.resend_cnt >= ble_at_table[ble_param.at_index].resend) ble_main.step = 3;
		ble_main.timer = HAL_GetTick();
		HAL_UART_Transmit(&UART_HANDLE,ble_data.senddata,ble_data.sendlen,1000);
	}
}

/*
	步骤2:收到回复之后的处理
*/
static void ble_process_recv(void *param)
{
	/*对比字符串*/
	if(strstr((char *)ble_data.recvdata,ble_at_table[ble_param.at_index].recv_str) == NULL)
	{
		ble_main.step = 1;
		return ;
	}	
	
	if(ble_at_table[ble_param.at_index].at_recv_callback != NULL)
		ble_at_table[ble_param.at_index].at_recv_callback(ble_param.at_index,param);
	
	ble_main.step = 4;
}

/*
	步骤3:失败的处理
*/
static void ble_process_fail(void *param)
{
	printf("超时\r\n");
	ble_main.step = 4;
}

/*
	步骤4,资源回收
*/
static void ble_process_end(void *param)
{
	/*删除结点*/
	remove_msg_queue_first_node();
	
	/*清除发送数组*/
	memset(ble_data.recvdata,0,BLE_RECV_MAX);
	ble_data.recvlen = 0;
	
	ble_main.resend_cnt = 0;
	ble_main.step = 0;
	
	/*如果消息队列没有消息了,则退出*/
	if(ble_param.at_queue == NULL) ble_main.state.bits.running = 0;

}


/*
	指令处理过程
*/
void ble_at_process(void)
{
	uint8_t step_index = 0;
	uint8_t ble_process_table_len = sizeof(ble_process_table) / sizeof(bleProcessStruct);
	
	if(!ble_main.state.bits.running) return ;	/*如果没有运行标志,直接退出*/
	
	for(;;)
	{
		if(ble_main.step == ble_process_table[step_index].step) break;
		else if(++step_index > ble_process_table_len) return ;
	}
	
	if(ble_process_table[step_index].process != NULL)
		ble_process_table[step_index].process(&ble_param);
	
}


其中ble_at_process()就放在main()里的while(1)运行

那么在哪里释放信号量呢,信号量就说明是模块有返回字符,一般这种AT指令模块都是串口通信,所以我们在接收到一帧数据的时候释放信号量,因为有些模块,他是分多次回复这个AT指令的数据,所以我们希望有自己的缓冲区来持续存储返回的数据,
在ble.h创建接收缓冲区和发送缓冲区结构体

#define  BLE_SEND_MAX   128  /*发送数据最大长度*/
#define  BLE_RECV_MAX	256	  /*接收数据最大长度*/
/*
	存放发送和接收数据的结构体
*/
typedef struct
{
	uint8_t senddata[BLE_SEND_MAX];
	uint8_t recvdata[BLE_RECV_MAX];
	uint16_t sendlen;
	uint16_t recvlen;
}BLEDataStruct;

在ble.c里写复制到缓冲区的函数和释放信号量的函数

/*
	将需要发送的数据加入到发送数组
*/
static void add_data_to_send_buf(uint8_t *data,uint16_t data_len)
{
	if(ble_data.sendlen + data_len < BLE_SEND_MAX)
	{
		memcpy(ble_data.senddata + ble_data.sendlen,data,data_len);
		ble_data.sendlen += data_len;
	}
	
}

/*
	复制数据到接收缓冲区
*/
static void  add_data_to_bc20_recvbuf(uint8_t *data,uint16_t len)
{
	if(ble_data.recvlen + len< BLE_RECV_MAX)
	{
		memcpy(ble_data.recvdata + ble_data.recvlen,data,len);
		ble_data.recvlen += len;
	}
}


/*
	获取ble回复的信息并打印
	接收到蓝牙发送的数据有两种情况,一种的AT的信息,一种是透传的信息
*/
void printf_at_recv(void)
{
	uint16_t recv_len = 0;
	uint8_t *recv = get_uart_recv_data(&UART_HANDLE,&recv_len);
	printf("%s",recv);
	
	/*如果没有连接则,复制到自己的缓冲区*/
	if(BLE_UNLINK_STATE && ble_main.state.bits.running)
	{
		add_data_to_bc20_recvbuf(recv,recv_len);
		ble_main.state.bits.suspend = 0;/*释放信号量,到主线程进行处理*/
	}
	clear_uart_buf(&UART_HANDLE,0);
}


/*
	蓝牙模块初始化
*/
void  ble_init(void)
{
	add_to_uartx_it_callback(&UART_HANDLE,printf_at_recv);
	ble_init_config(1000,"xm-ble");
}

其中ble_init()是在程序开始运行时初始化调用,把释放回调加到串口接收完成的中断里,这是串口模块提供的函数接口,这样做的好处就是模块化,如果AT指令的模块不是串口的接口,那么就可以调用其他模块的函数接口。

3.写给其他程序调用的接口函数

终于到最后一步了,过程都写好之后,就需要提供接口给其他模块调用,比如说需要配置蓝牙模块的名字,广播时间,是否进入低功耗,这个时候就需要输入参数,然后在指令表中的发送回调里将这些参数加入到发送的缓冲区里,但是又有一个问题,这些参数保存在哪里呢,要是我想要发送多条指令呢,这个就需要用到消息队列,把参数(消息)保存在消息队列里,处理完之后在删除消息,具体为:
在ble.h里创建消息队列结构体

/*结点结构体*/
typedef struct _list_node
{
	uint8_t now_at_num;	/*指令编号*/
	void *param;		/*参数*/
	struct _list_node *next;
}ble_at_nodeStruct;	/*at结点*/

/*当前指令编号*/
typedef struct
{
	int16_t at_index;	/*当前at在表里的步长*/
	ble_at_nodeStruct *at_queue;	/*at指令队列*/
	
}BLEParamStruct;

在ble.c里写几个函数,创建新的消息、将消息加入消息队列、删除消息

/*
	创建at指令结点,输入AT指令和参数,参数长度,并存到结点里
*/
static ble_at_nodeStruct *new_msg_queue_node(uint8_t at,void *param_in, uint16_t param_len)
{
	/*为结点申请空间*/
	ble_at_nodeStruct *node = (ble_at_nodeStruct *)malloc(sizeof(ble_at_nodeStruct));
	
	if(node == NULL) return NULL;
	
	node->next = NULL;
	node->now_at_num = at;
	
	if(param_in == NULL) return node;/*如果没有参数*/
	
	/*为数据部分申请空间*/
	uint8_t *param = (uint8_t *)malloc(sizeof(uint8_t) *param_len);
	
	if(param == NULL)
	{
		free(node);node = NULL;
		return NULL;
	}
	
	/*复制参数部分*/
	node->param = param;
	memcpy(node->param,param_in,param_len);
	
	return node;

}

/*
	2.23 加入到消息队列的最后一个结点
*/
static void add_to_msg_queue_tail(ble_at_nodeStruct *node)
{
	ble_at_nodeStruct **tail_node = &ble_param.at_queue;
	ble_at_nodeStruct *next_node = *tail_node;
	
	if(*tail_node == NULL)
	{
		*tail_node = node;
	}else{
		while(next_node->next != NULL)
		{
			next_node = next_node->next;
		}
		next_node->next = node;
	}
	
}

/*
	2.23 删除消息队列中的第一个结点和结点中的消息
*/
static void remove_msg_queue_first_node(void)
{
	ble_at_nodeStruct *first_node = ble_param.at_queue;
	
	if(first_node == NULL) return ;
	
	if(first_node->param != NULL)
	{
		free(first_node->param); first_node->param = NULL;/*释放消息的内存*/
	}
	
	ble_param.at_queue = ble_param.at_queue->next;
	
	free(first_node); first_node = NULL;	/*释放结点内存*/
	
}

然后写AT指令统一入口函数,就是在这里输入参数的地址和参数的长度,用来申请对应大小的内存来存储参数(消息),并且释放任务开始的信号

/*
	所有AT指令入口函数
	参数:at,编号,param:对应的at的参数,param_len:参数字节数
*/
static void ble_at_handler(uint8_t at,void *param,uint16_t param_len)
{
	ble_at_nodeStruct *node = new_msg_queue_node(at,param,param_len);
	
	if(node == NULL) return ;
	
	add_to_msg_queue_tail(node);
	
	ble_main.state.bits.running = 1;
}


最后就是让其他模块调用的接口,就是具体要实现的功能了,比如说配置模块的名字和时间间隔

/*
	对外的接口,配置ble
*/
void ble_init_config(uint16_t broadcast_time,char *name)
{
	
	ble_at_handler(0,NULL,0);
	ble_at_handler(1,&broadcast_time,sizeof(broadcast_time));
	ble_at_handler(2,name,strlen(name));
	
}


这样其他函数只要调用就可以了

结束

整个过程实现下来还是挺麻烦的,而且这只适用于没有操作系统的,用上操作系统的话就简单多了,后面在做一个加上操作系统的。整个过程还是有比较多能够优化的地方,比如说当指令多了之后,查找指令的方法可以改为二分查找,在处理过程中代码格式的优化,希望各位大佬能够多多指出。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 单片机与4G模块 EC200S 的串口通信程序,一般分为初始化模块发送数据和接收数据三个步骤。 首先,要初始化单片机的串口口和4G模块的串口口。单片机和EC200S的串口口需要配置波特率、数据位、停止位和奇偶校验位等参数,以确保它们之间通信能够正常进行。 其次,要通过单片机的串口口发送数据到EC200S模块。可以使用单片机发送函数,在发送缓冲区中放入待发送的数据,然后通过串口口发送出去。在发送之前,还需要检查发送缓冲区是否为空,以确保没有数据丢失。 最后,要在单片机上接收来自EC200S模块的数据。单片机也需要设置接收缓冲区,并通过中断或轮询方式来读取接收缓冲区中的数据。读取完成后,可以对接收到的数据进行处理,比如打印到显示屏上或进行其他操作。 需要注意的是,在进行单片机和EC200S模块的串口通信时,还需要处理数据的传输格式。比如,可以使用ASCII码或二进制格式进行数据的传输。在发送和接收数据时,需要进行格式的转换和拆装,以确保数据能够正确传输和解析。 以上就是单片机与4G模块EC200S串口通信程序的一般步骤,具体实施时还需根据实际情况进行调整和优化。 ### 回答2: 单片机与4G模块EC200S之间的串口通信程序主要包含以下几个步骤: 1. 硬件连接:将单片机的串口TX(发送)引脚连接到EC200S的串口RX(接收)引脚,同时将单片机的串口RX(接收)引脚连接到EC200S的串口TX(发送)引脚。 2. 初始化串口:在单片机代码中,首先需要设置串口的波特率、数据位、停止位和校验位等参数,并使能串口的接收和发送功能。 3. 发送AT指令:通过串口向EC200S发送AT指令,以进行4G网络的连接、断开、发送短信等操作。可以使用串口发送函数将指令发送给EC200S。 4. 接收和解析响应:通过串口接收函数,单片机可以接收到EC200S返回的响应信息。需要对响应信息进行解析,判断操作是否成功,并根据具体情况作出相应处理。 5. 串口中断处理:可以使用串口中断,当EC200S返回数据时,单片机可以通过中断响应快速处理接收到的数据,提高响应速度。 6. 错误处理:在通信过程中可能出现各种错误,如串口通信错误、AT指令错误等。需要对可能出现的错误进行处理,例如重新发送指令、检查串口连接等。 7. 其他功能扩展:根据具体需求,还可以实现其他功能,例如接收EC200S主动推送的数据、实现双向通信等。 综上所述,单片机与4G模块EC200S之间的串口通信程序主要包括串口初始化、AT指令发送与接收、响应解析、错误处理等步骤。通过这些步骤,可以实现单片机与4G模块之间的双向通信,并实现各种操作和功能。 ### 回答3: 单片机和4G模块EC200S的串口通信可以通过以下步骤实现: 1. 首先,将单片机的串口与4G模块的串口进行连接。确保连接正确,包括连接正确的引脚和设置正确的电平转换电路。 2. 在单片机的程序中,使用相应的串口通信库来配置和初始化单片机的串口通信功能。 3. 在单片机的程序中,设置好与4G模块通信的波特率、数据位、停止位等参数,确保与4G模块的串口通信参数一致。 4. 编写单片机程序,通过串口向4G模块发送指令或数据。可以使用串口发送函数将指令或数据发送到4G模块。 5. 在单片机程序中,通过串口接收函数接收4G模块返回的数据或响应。可以使用中断或轮询的方式进行接收。 6. 解析和处理4G模块返回的数据。根据4G模块通信协议,对接收到的数据进行解析和处理,以获取所需的信息或进行相应的操作。 7. 根据需要,可以设置超时机制或错误处理,以确保通信的稳定性和可靠性。 8. 最后,测试和调试单片机与4G模块的串口通信程序,确保正常通信和数据的正确传输。 总之,单片机与4G模块EC200S之间的串口通信需要进行连接、初始化、设置参数、发送指令和数据、接收返回的数据等步骤,并对返回的数据进行解析和处理,以实现双方之间通信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值