关于AT模组框架,之前学习TencentOS-tiny是有写过一篇文章的,链接如下:
还在用传统的方式驱动一个通信模组?不如一起来学习下TOS的AT模组框架吧!
最近查看了TencentOS-tiny官方仓库,发现device目录下并没有ML302这个模组的驱动,于是我就利用这个机会学习了模组驱动编写与AT模组框架适配,期间遇到了一些技术疑惑请教了世伟兄(mculover666),最后顺利的将代码提交到了TencentOS-tiny官方仓库;能够得以顺利的完成,得感谢世伟兄提供技术支持。
在此之前,我已经给TencentOS-tiny
和TencentOS-Tiny-IoT-Projects
贡献了一些项目,如下所示,有兴趣可持续关注并提ISSUE。
ML302 4G Cat.1模组资料仓库地址:
https://gitee.com/morixinguan/open-source-of-cloud/tree/master/04.4G_Cat.1
ML302 TencentOS-tiny驱动仓库地址:
https://github.com/Tencent/TencentOS-tiny/tree/master/devices/ml302
ML302 4G Cat.1小熊派测试例程仓库地址:
https://github.com/Tencent/TencentOS-tiny/tree/master/board/BearPi_STM32L431RC/KEIL/tcp_through_ml302_4g_cat_1_module https://github.com/Tencent/TencentOS-tiny/tree/master/examples/tcp_through_ml302_4g_cat_1_module
TencentOS-tiny官方仓库地址:
https://gitee.com/Tencent/TencentOS-tiny https://github.com/Tencent/TencentOS-tiny
TencentOS-Tiny-IoT-Projects官方仓库地址:
https://gitee.com/Tencent/TencentOS-Tiny-IoT-Projects
在使用AT框架适配模组前,需要先用串口终端测试一下,于是我们找到了模组 的通信流程示例手册:
接下来根据手册要求发送以下指令来进行上电初始化,如果初始化流程没有问题的话,接下来就可以和远程服务器进行通信了,这里的测试平台我用的是中国移动OneNet。
1、移动4G Cat.1 ML302上电初始化流程
1.1、测试模块初始化正常
AT OK
1.2、查询SIM卡状态
AT+CPIN? +CPIN: READY OK
1.3、查询协议栈是否打开
AT+CFUN? +CFUN: 1 OK
1.4、查询当前信号质量
AT+CSQ +CSQ: 31,99 OK
1.5、设置 GPRS的APN
AT+CGDCONT=1,"IP","CMIOT" OK
1.6、激活PDP
AT+CGACT=1,1 +CGACT: 1,1 OK
2、建立TCP连接
测试连接移动OneNet云平台==>透传模式
2.1、进入透传模式
AT+MIPMODE=0,1 OK
2.2、连接服务器
AT+MIPOPEN=0,"TCP","183.230.40.33",80 OK CONNECT OK
2.3、发送HTTP透传数据
POST /devices/604618155/datapoints?type=3 HTTP/1.1 api-key:NGSRfMY43gLebqieuaX4UfPgoac= Host:api.heclouds.com Content-Length:11 {"ppm":100} 以下为接收到服务器的信息回复 HTTP/1.1 200 OK Date: Mon, 22 Mar 2021 09:00:30 GMT Content-Type: application/json Content-Length: 26 Connection: keep-alive Server: Apache-Coyote/1.1 Pragma: no-cache {"errno":0,"error":"succ"}
2.4、退出数据模式,此时连接依然保持
+++
2.5、关闭连接,释放资源
AT+MIPCLOSE=0 0,CLOSE OK
2.6、退出透传模式
AT+MIPMODE=0,0 OK
我的串口调试助手显示如下:
为了调试方便,这里我用是透传数据的方式来上传数据到OneNet。
3、TencentOS-tiny AT模组框架适配
AT模组驱动位于TencentOS-tiny根目录下的device目录:
在这个目录下,已经适配了很多市面上常用的AT模组,如下所示,在这个目录下创建一个ML302的文件夹,向里面添加ML302模组驱动文件:
接下来根据其它模组的驱动套路添加对应的代码,完成ML302适配SAL层,这里我们根据官方文档描述,找到SAL
相关适配的结构体:
//枚举===>协议类型,TCP、UDP typedef enum sal_protocol_en { TOS_SAL_PROTO_TCP, TOS_SAL_PROTO_UDP, } sal_proto_t; //需要对接SAL的接口 typedef struct sal_module_st { int (*init)(void); int (*get_local_mac)(char *mac); int (*get_local_ip)(char *ip, char *gw, char *mask); int (*parse_domain)(const char *host_name, char *host_ip, size_t host_ip_len); int (*connect)(const char *ip, const char *port, sal_proto_t proto); int (*send)(int sock, const void *buf, size_t len); int (*recv_timeout)(int sock, void *buf, size_t len, uint32_t timeout); int (*recv)(int sock, void *buf, size_t len); int (*sendto)(int sock, char *ip, char *port, const void *buf, size_t len); int (*recvfrom)(int sock, void *buf, size_t len); int (*recvfrom_timeout)(int sock, void *buf, size_t len, uint32_t timeout); int (*close)(int sock); } sal_module_t;
其中,send、recv、recv_timeout
是TCP协议使用的收发接口,而sendto、recvfrom、recvfrom_timeout
是UDP协议使用的收发接口,按照要求,实现模组对接SAL层常用的一些接口,然后将这个结构体注册到SAL上。
以init接口为例,我们要实现模组上电初始化要做的一些事情,具体指令请参考对应的手册:
static int ml302_init(void) { printf("Init ML302 ...\n" ); //测试模组是否上电 if (ml302_wait_ok() != 0) { printf("ml302 start FAILED\n"); return -1; } //关闭回显 if (ml302_echo_close() != 0) { printf("ml302 echo close FAILED\n"); return -1; } //测试SIM卡是否已经插上 if (ml302_pin_ready() != 0) { printf("ml302 pin not ready\n"); return -1; } //测试是否注网成功 if(ml302_gsm_network_check() != 0) { printf("ml302 GSM network register status check fail\n"); return -1 ; } //测试模组信号强度 if (ml302_signal_quality_check() != 0) { printf("ml302 signal quality check status failed\n"); return -1; } //设置APN if (ml302_set_gprs_apn() != 0) { printf("ml302 set gprs_apn failed\n"); return -1; } //激活PDP if (ml302_activate()) { printf("ml302 activate FAILED\n"); return -1; } printf("Init ML302 Done\n" ); return 0; }
这么多指令里面,其实包含了三类AT指令:
- 1、只需要判断是否返回OK的AT指令
以ml302_wait_ok
函数为例,这是第一类AT指令的实现,如下:
static int ml302_wait_ok(void) { int try = 0; at_echo_t echo; /* 创建一个echo 对象,缓冲区为NULL,期望字符串为NULL */ tos_at_echo_create(&echo, NULL, 0, NULL); /* 尝试检测10次,一旦有一次正常,返回 */ while (try++ < 10) { /* 执行AT命令,超时时长1000ms */ tos_at_cmd_exec(&echo, 1000, "AT\r\n"); /* 判断执行结果是否为OK */ if (echo.status == AT_ECHO_STATUS_OK) { return 0; } } return -1; }
- 2、需要判断是否OK,也需要解析执行结果的AT指令
以ml302_signal_quality_check
函数为例,这是第一类AT指令的实现,如下:
static int ml302_signal_quality_check(void) { int rssi, ber; at_echo_t echo; char echo_buffer[32], *str; int try = 0; /* 尝试检测10次,一旦有一次正常,返回 */ while (try++ < 10) { /* 创建echo对象,传入一个缓冲区存放AT命令执行结果 */ tos_at_echo_create(&echo, echo_buffer, sizeof(echo_buffer), NULL); /* 执行AT命令,超时时长1000ms */ tos_at_cmd_exec(&echo, 1000, "AT+CSQ\r\n"); /* 判断执行结果是否返回了OK */ if (echo.status != AT_ECHO_STATUS_OK) { return -1; } /* 从AT指令的执行结果中解析提取CSQ值进行判断 */ str = strstr(echo.buffer, "+CSQ:"); if (!str) { return -1; } sscanf(str, "+CSQ:%d,%d", &rssi, &ber); if (rssi != 99) { return 0; } } return -1; }
- 3、模组主动上报的数据处理
这一类AT指令对应TCP/IP协议栈接收数据的上报机制,使用AT框架的事件机制进行处理,也就是我们前面提到的这个事件:
at_event_t ml302_at_event[] = { { "+MIPURC: \"recv\",", ml302_incoming_data_process }, };
这个对应下发处理的事件回调函数需要我们自己来实现,如ml302_incoming_data_process
,实现如下:
__STATIC__ void ml302_incoming_data_process(void) { uint8_t data; int channel_id = 0, data_len = 0, read_len; uint8_t buffer[128]; /* //模组上报的数据格式 +MIPURC: "recv",<sockid>,<datalen> <data content> */ /* 读取解析socket id */ while (1) { if (tos_at_uart_read(&data, 1) != 1) { return; } if (data == ',') { break; } channel_id = channel_id * 10 + (data - '0'); } /* 读取解析数据长度 */ while (1) { if (tos_at_uart_read(&data, 1) != 1) { return; } if (data == '\r') { break; } data_len = data_len * 10 + (data - '0'); } if (tos_at_uart_read(&data, 1) != 1) { return; } /* 根据解析出的数据长度和缓冲区的长度,循环读取数据内容,写入到对应 socket id 的channel中 */ do { #define MIN(a, b) ((a) < (b) ? (a) : (b)) read_len = MIN(data_len, sizeof(buffer)); if (tos_at_uart_read(buffer, read_len) != read_len) { return; } if (tos_at_channel_write(channel_id, buffer, read_len) <= 0) { return; } data_len -= read_len; } while (data_len > 0); return; }
模组适配完毕后,编写对接后台例程,由于篇幅限制,详细代码请访问TencentOS-tiny官方仓库获取。
测试例程效果如下:
OneNet平台展示效果: