Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
不穿格子衫的农民
TencentOS Tiny
提交
00e33cb2
T
TencentOS Tiny
项目概览
不穿格子衫的农民
/
TencentOS Tiny
通知
3
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
T
TencentOS Tiny
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
00e33cb2
编写于
8月 11, 2020
作者:
M
mculover666
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
finish AT_SAL_User_Guide document
上级
ad5f75d4
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
323 addition
and
46 deletion
+323
-46
doc/27.AT_Firmware_and_SAL_Firmware_User_Guide.md
doc/27.AT_Firmware_and_SAL_Firmware_User_Guide.md
+323
-46
未找到文件。
doc/27.AT_Firmware_and_SAL_Firmware_User_Guide.md
浏览文件 @
00e33cb2
...
...
@@ -665,11 +665,11 @@ socket_id_1 = tos_sal_module_connect("117.50.111.72", "8001", TOS_SAL_PROTO_TCP)
![
tcp_example_result_uart
](
./image/AT_Firmware/tcp_example_result_uart.png
)
在
socket0
的服务端查看模组发送的消息:
在
socket0
的服务端查看模组发送的消息:
![
tcp_example_result_server1
](
./image/AT_Firmware/tcp_example_result_server1.png
)
在
socket1
的服务端查看模组发送的消息:
在
socket1
的服务端查看模组发送的消息:
![
tcp_example_result_server2
](
./image/AT_Firmware/tcp_example_result_server2.png
)
...
...
@@ -677,23 +677,180 @@ socket_id_1 = tos_sal_module_connect("117.50.111.72", "8001", TOS_SAL_PROTO_TCP)
# 5. 如何适配一个新的通信模组驱动
适配一个新的通信模组,就是实现SAL框架的整套函数指针所定义的函数
。
基于模组的AT指令集,使用 TencentOS-tiny AT 框架与模组交互,实现SAL框架所定义的函数,这个过程称为通信模组适配
。
建议按照以下的流程进行适配:
本文中我以移远通信的4G通信模组 EC20 作为示例,讲述一个全新的通信模组适配流程。
①
**寻找相同类型、相同厂商已有的模组驱动,复制之后开始修改适配**
。
## Step1. 使用串口助手调试,熟悉该模组的AT指令集
比如我要适配 4G Cat.1 模组Air724,肯定是找一份差异不大的模组驱动开始改,比如仓库里已有的4G Cat.4 模组EC20的驱动。
② 普通的AT指令交互流程实现示例(一般为配置指令)
:
适配SAL层需要熟悉模组的三类AT指令
:
-
基本查询配置指令
-
TCP/IP网络协议栈AT指令
-
TCP/IP网络协议栈数据接收机制
比如EC20相关的AT指令如下:
### ① 基本查询配置指令
-
测试AT指令是否正常?
```
AT
OK
```
-
查询SIM卡是否正常?
```
c
AT
+
CPIN
?
+
CPIN
:
READY
OK
```
-
查询模组的信号强度
```
c
AT
+
CSQ
+
CSQ
:
17
,
0
OK
```
-
查询模组是否注册到GSM网络
```
AT+CREG?
+CREG: 0,1
OK
```
-
查询模组是否注册上GPRS网络
```
AT+CGREG?
+CGREG: 0,1
OK
```
-
设置GPRS的APN
```
AT+QICSGP=1,1,"CMNET"
OK
```
-
激活移动场景
```
AT+QIACT=1
OK
```
### ② TCP/IP网络协议栈AT指令
TCP/IP网络协议栈至少需要TCP/UDP socket的通信AT指令,其它上层协议的AT指令暂时不用。
-
建立Socket
```
AT+QIOPEN=1,0,"TCP","117.50.111.72",8902,0,0
OK
+QIOPEN: 0,0
```
>在建立socket的时候需要注意,有的通信模组需要提前使用AT指令配置单链路模式还是多链路模式(eg. ESP8266),而有的通信模组默认直接支持多链路模式,无需配置。
-
发送数据
```
c
AT
+
QISEND
=
0
>
hello
<
0x1a
>
SEND
OK
```
>发送数据的时候需要注意,有的通信模组发送数据使用ASCII字符(eg. ESP8266、EC20等),而有的通信模组发送数据使用十六进制(eg. NB-IoT类模组)
-
接收数据
接下来的一节重点讲述。
-
关闭Socket
```
AT+QICLOSE=0
OK
```
### ③ TCP/IP网络协议栈数据接收机制
通信模组在接收到服务器发来的数据时,会有两种方式上报给MCU:
-
使用固定的IP头上报socket id和数据长度,需要再次去读取数据(缓冲模式接收)
```
c
//模组上报
+
QIURC
:
"recv"
,
0
//MCU发出AT指令去读取数据
AT
+
QIRD
=
0
,
1500
+
QIRD
:
14
Hello
,
client
!
OK
```
-
使用固定的IP头上报socket id和数据长度,同时一起上报数据(直接模式接收)
```
c
+
QIURC
:
"recv"
,
0
,
14
Hello
,
client
!
```
有的模组只支持某一种模式,有的模组两种模式都支持,可以自己配置,对于TencentOS-tiny的AT框架来说,第二种直接上报模式解析起来会更加方便。
## Step2. 三类AT指令,三种实现方式
上面讲述了适配SAL层需要熟悉模组的三类AT指令:基本查询配置指令、TCP/IP网络协议栈AT指令、TCP/IP网络协议栈数据接收机制,这节讲述如何使用AT框架实现这三类AT指令。
① 只需要判断是否返回OK的AT指令
这一类AT指令的实现函数除了指令内容不同,别的都相同,比如关闭回显的AT指令:
```
ATE0
OK
```
对应的实现方法如下:
```
c
static
int
air724
_echo_close
(
void
)
static
int
ec20
_echo_close
(
void
)
{
at_echo_t
echo
;
/* 创建一个echo 对象,缓冲区为NULL,期望字符串为NULL */
tos_at_echo_create
(
&
echo
,
NULL
,
0
,
NULL
);
tos_at_cmd_exec
(
&
echo
,
300
,
"ATE0
\r\n
"
);
/* 执行AT命令,超时时长1000ms */
tos_at_cmd_exec
(
&
echo
,
1000
,
"ATE0
\r\n
"
);
/* 判断执行结果是否为OK */
if
(
echo
.
status
==
AT_ECHO_STATUS_OK
)
{
return
0
;
...
...
@@ -702,26 +859,43 @@ static int air724_echo_close(void)
}
```
③ 需要解析AT指令解析结果的交互流程实现示例(一般为查询指令):
② 需要判断是否OK,也需要解析执行结果的AT指令
这一类AT指令的实现函数中,不同点在于,创建echo对象的时候需要传入一个buffer来存放指令执行的结果,比如查询信号强度的AT指令:
```
AT+CSQ
+CSQ: 17,0
OK
```
对应的实现方法如下:
```
c
static
int
air724
_signal_quality_check
(
void
)
static
int
ec20
_signal_quality_check
(
void
)
{
int
rssi
,
ber
;
at_echo_t
echo
;
char
echo_buffer
[
32
],
*
str
;
int
try
=
0
;
/* 创建echo对象,传入一个缓冲区存放AT命令执行结果 */
tos_at_echo_create
(
&
echo
,
echo_buffer
,
sizeof
(
echo_buffer
),
NULL
);
/* 尝试检测10次,一旦有一次正常,返回 */
while
(
try
++
<
10
)
{
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:"
);
sscanf
(
str
,
"+CSQ:%d,%d"
,
&
rssi
,
&
ber
);
if
(
rssi
!=
99
)
{
...
...
@@ -733,37 +907,40 @@ static int air724_signal_quality_check(void)
}
```
④ 事件处理(一般为模组主动上报的指令):
③ 模组主动上报的数据处理
这一类AT指令对应TCP/IP协议栈接收数据的上报机制,使用AT框架的事件机制进行处理。
首先将固定的ip头和事件处理回调函数注册:
```
c
at_event_t
air724_at_event
[]
=
{
{
"+RECEIVE,"
,
air724_incoming_data_process
},
at_event_t
ec20_at_event
[]
=
{
{
"+QIURC:
\"
recv
\"
,"
,
ec20_incoming_data_process
},
//处理远程服务器发来的数据
{
"+QIURC:
\"
dnsgip
\"
,"
,
ec20_domain_data_process
},
//处理域名解析结果,暂时不管
};
```
回调函数的处理示例如下(添加了处理过程的注释):
事件处理回调函数自己编写,主要作用是提取模组上报的scoketid、数据长度、数据内容,然后将数据内容写入到对应socket id 的channel中。
>需要注意,AT框架一旦读取解析到固定的IP头,则停止解析,拉起对应的回调函数,所以在回调函数中可以继续从缓冲区中一边读取一边解析。
解析示例如下:
```
c
__STATIC__
void
air724
_incoming_data_process
(
void
)
__STATIC__
void
ec20
_incoming_data_process
(
void
)
{
uint8_t
data
;
int
channel_id
=
0
,
data_len
=
0
,
read_len
;
uint8_t
buffer
[
128
];
/*
+RECEIVE, 0,<data_len>:
<data context>
+RECEIVE: prefix
0: scoket id
模组上报的数据格式:
+QIURC: "recv",<sockid>,<datalen>
<data content>
*/
//读走+RECEIVE头之后的一个空格
if
(
tos_at_uart_read
(
&
data
,
1
)
!=
1
)
{
return
;
}
/* 注册的ip头是:[+QIURC: "recv",]回调函数被拉起执行后,接着处理后边的数据即可 */
/
/读取并解析之后的socket id值
/
* 读取解析socket id */
while
(
1
)
{
if
(
tos_at_uart_read
(
&
data
,
1
)
!=
1
)
...
...
@@ -778,43 +955,38 @@ __STATIC__ void air724_incoming_data_process(void)
channel_id
=
channel_id
*
10
+
(
data
-
'0'
);
}
/
/读取并解析之后的data_len值
while
(
1
)
/
* 读取解析数据长度 */
while
(
1
)
{
if
(
tos_at_uart_read
(
&
data
,
1
)
!=
1
)
{
return
;
}
if
(
data
==
'
:
'
)
if
(
data
==
'
\r
'
)
{
break
;
}
data_len
=
data_len
*
10
+
(
data
-
'0'
);
}
/
/读走回车和换行
while
(
1
)
/
* 读取'\r'之后的'\n',不作任何处理 */
if
(
tos_at_uart_read
(
&
data
,
1
)
!=
1
)
{
if
(
tos_at_uart_read
(
&
data
,
1
)
!=
1
)
{
return
;
}
return
;
}
if
(
data
==
'\n'
)
{
break
;
}
}
//根据之前解析到的socket id和data_len,循环读取数据,并写入到socket id对应通道的接收缓冲区
/* 根据解析出的数据长度和缓冲区的长度,循环读取数据内容,写入到对应 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
;
}
/* 写入到对应的channel中 */
if
(
tos_at_channel_write
(
channel_id
,
buffer
,
read_len
)
<=
0
)
{
return
;
}
...
...
@@ -826,7 +998,112 @@ __STATIC__ void air724_incoming_data_process(void)
}
```
## Step3. 整体适配流程
前两步都是细节性的处理,这一步从整体的角度讲述适配流程。
① 模组初始化
```
c
static
int
ec20_init
(
void
)
{
printf
(
"Init ec20 ...
\n
"
);
/* 关闭回显 */
if
(
ec20_echo_close
()
!=
0
)
{
printf
(
"echo close failed,please check your module
\n
"
);
return
-
1
;
}
/* 检测SIM卡是否正常 */
if
(
ec20_sim_card_check
()
!=
0
)
{
printf
(
"sim card check failed,please insert your card
\n
"
);
return
-
1
;
}
/* 检测信号强度是否正常 */
if
(
ec20_signal_quality_check
()
!=
0
)
{
printf
(
"signal quality check status failed
\n
"
);
return
-
1
;
}
/* 检测是否注册到GSM网络 */
if
(
ec20_gsm_network_check
()
!=
0
)
{
printf
(
"GSM network register status check fail
\n
"
);
return
-
1
;
}
/* 检测是否注册到GPRS网络 */
if
(
ec20_gprs_network_check
()
!=
0
)
{
printf
(
"GPRS network register status check fail
\n
"
);
return
-
1
;
}
/* 关闭APN */
if
(
ec20_close_apn
()
!=
0
)
{
printf
(
"close apn failed
\n
"
);
return
-
1
;
}
/* 设置APN,激活移动场景 */
if
(
ec20_set_apn
()
!=
0
)
{
printf
(
"apn set FAILED
\n
"
);
return
-
1
;
}
printf
(
"Init ec20 ok
\n
"
);
return
0
;
}
```
② 将实现的函数映射到SAL框架上:
```
c
sal_module_t
sal_module_ec20
=
{
.
init
=
ec20_init
,
.
connect
=
ec20_connect
,
.
send
=
ec20_send
,
.
recv_timeout
=
ec20_recv_timeout
,
.
recv
=
ec20_recv
,
.
sendto
=
ec20_sendto
,
.
recvfrom
=
ec20_recvfrom
,
.
recvfrom_timeout
=
ec20_recvfrom_timeout
,
.
close
=
ec20_close
,
.
parse_domain
=
ec20_parse_domain
,
};
```
③ 再次封装,留出一个外部调用接口,供上层应用程序调用:
```
c
int
ec20_sal_init
(
hal_uart_port_t
uart_port
)
{
/* 初始化AT框架及其串口 */
if
(
tos_at_init
(
uart_port
,
ec20_at_event
,
sizeof
(
ec20_at_event
)
/
sizeof
(
ec20_at_event
[
0
]))
!=
0
)
{
return
-
1
;
}
/* 将第②步中映射的函数关系,注册到SAL框架 */
if
(
tos_sal_module_register
(
&
sal_module_ec20
)
!=
0
)
{
return
-
1
;
}
/* 调用SAL初始化函数,因为接口和映射的存在,最终调用到ec20_init */
if
(
tos_sal_module_init
()
!=
0
)
{
return
-
1
;
}
return
0
;
}
```
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录