.. | ||
gprs/sim800 | ||
include | ||
wifi | ||
ip4_addr.c | ||
README.md | ||
sal.c | ||
sal.mk | ||
sal_arch.c | ||
sal_device.c | ||
sal_err.c | ||
sal_sockets.c | ||
ucube.py |
AliOS Things SAL Porting Guide
在AliOS Things移植过程中,如果需要支持外接Wifi/BLE等模,且TCP/IP协议栈运行模组侧;则需要SAL和底层模组控制模块进行对接。SAL功能对上层提供标准socket接口,使上层应用不感知TCP/IP协议栈运行在MCU侧还是通讯模组侧。AliOS Things SAL的接口定义请查看头文件:sal.h。
本文将讲述AliOS Things 中SAL移植要点。
1、SAL模块重要数据结构
首先,先了解一下AliOS Things中跟SAL相关的两个个重要的数据结构: sal_op_t
和sal_conn_t
两个结构体。SAL依赖底层的相关操作和接口封装都在sal_op_t
这个结构体中;sal_conn_t
结构体为建立连接时,SAL传给底层模组控制模块的相关参数。两个结构体相关定义在文件sal.h中。
typedef struct sal_op_s {
char *version;
int (*init)(void);
int (*start)(sal_conn_t *c);
int (*send)(int fd, uint8_t *data, uint32_t len,
char remote_ip[16], int32_t remote_port);
int (*domain_to_ip)(char *domain, char ip[16]);
int (*close)(int fd, int32_t remote_port);
int (*deinit)(void);
int (*register_netconn_data_input_cb)(netconn_data_input_cb_t cb);
} sal_op_t;
typedef struct {
int fd;
CONN_TYPE type;
char *addr;
int32_t r_port;
int32_t l_port;
uint32_t tcp_keep_alive;
} sal_conn_t;
2、SAL接口的实现
在具体的平台移植过程中,用户需要分别实现SAL模块结构体中对应的接口函数。AliOS Things对SAL层接口有一层封装,参见device/sal/sal.c
文件。具体的接口实现一般在device/sal/xxx/
中,其中xxx
代表模组类型。参考实现:mk3060.c。下面对每个接口做一些说明:
init
该接口需要对通信模组进行相关初始化,使通信模组达到可以工作的状态。
deinit
如果有需要,该接口需要提供对通信模组的去初始化操作。
start
该接口需要模组启动一次连接。SAL传给底层的参数为一个结构体指针sal_conn_t
,该结构体参数说明如下:
fd: 每个连接对应SAL socket层的句柄
type: 建立连接的类型,数据定义参见枚举
CONN_TYPE
addr: 对端ip或者域名,例如“192.168.1.1”或者“www.taobao.com”
r_port: 对端端口号
l_port: 本地端口号
tcp_keep_alive: tcp keep alive的时间
因此底层模组控制模块需要维护一套SAL socket 句柄和底层链路的对应关系。可以在发送/关闭连接时可以通过SAL socket句柄查找到对应的底层连接;在接收底层数据时可以根据底层连接找到对应的SAL socket句柄。
close
该接口关闭模组的一个连接。入参说明如下:
fd: 需要关闭的socket句柄,
remote_port: 对端端口号,该参数为可选参数,小于0时为无效参数
send
该接口通过模块发送数据的接口,该接口为阻塞接口,直到模组通知底层控制模块数据发送成功才会返回。入参说明如下:
fd: 发送数据所操作的句柄
data: 待发送数据的指针
len: 待发送数据的长度
remote_ip[16], 对端ip地址,该参数为可选参数,入参为NULL时无效
remote_port: 对端端口号,该参数为可选参数,小于0时为无效参数
domain_to_ip
该接口提供获取对应域名ip地址的功能,注意:1、即使该域名对应多个ip,也只会返回一个Ip地址;2、目前该接口只需要支持ipv4。入参说明如下:
domain: 域名信息
ip[16]: 点格式的ip字符串,目前只支持ipv4,例如:192.168.111.111
register_netconn_data_input_cb
该接口提供一个SAL数据接收函数的回调功能,底层模组控制模块在收到数据后,调用该接口上送到SAL中,SAL会在其中对每个句柄的数据进行管理。SAL提供的数据上送接口实现请见sal_packet_input
。
SAL提供的数据上送接口回调:typedef int (*netconn_data_input_cb_t)(int fd, void *data, size_t len, ip_addr_t *addr, u16_t port);
参数说明如下:
fd: 数据上送需要操作的句柄
data: 接收到的数据(该部分内存由底层自行释放)
len: 接收到的数据长度
addr: 该数据的源地址,为可选参数,可以传入NULL(该部分内存由底层自行释放)
port: 该数据的源端口,为可选参数,可以传入0
3、模组注册和初始化SAL
在完成SAL接口对接实现后,定义一个 sal_op_t
结构体,将各个接口和回调的实现地址赋值给结构体中对应的域。例如:
sal_op_t sal_op = {
.version = "1.0.0",
.init = sal_wifi_init,
.start = sal_wifi_start,
.send = sal_wifi_send,
.domain_to_ip = sal_wifi_domain_to_ip,
.close = sal_wifi_close,
.deinit = sal_wifi_deinit,
.register_netconn_data_input_cb = sal_wifi_packet_input_cb_register,
};
底层模组控制模块需要实现一个函数调用sal_module_register
对SAL进行模块注册。例如:
int mk3060_sal_init(void)
{
return sal_module_register(&sal_op);
}
该模块注册函数需要在device.c的sal_device_init
中进行调用。例如:
int sal_device_init()
{
int ret = 0;
#ifdef DEV_SAL_MK3060
ret = mk3060_sal_init();
#endif
if (ret){
LOGE(TAG, "device init fail ret is %d\n", ret);
}
return ret;
}
4、编译底层模组控制模块
在完成底层模组与SAL接口对接后,该部分代码建议的放置路径为device/sal/xxx/yyy
。其中xxx
为模组类型,例如wifi、ble、lora等;yyy
为模组型号例如:mk3060。例如:mk3060的wifi模组代码放置路径为:device/sal/wifi/mk3060/
。对应模组控制模块代码makefile名称需与模组型号一致为yyy.mk
,例如:mk3060的makefile文件名为:mk3060.mk
。
编译时请注意在正常的编译命令后需要指定编译sal和所使用的通信模组信息,例如:aos make alinkapp@b_l475e sal=1 module=wifi.mk3060
,其中sal=1
表示需要编译sal,module=wifi.mk3060
表示所需要连接的模组类型为wifi.mk3060
,此时AliOS Things的Makefile 体系会自动调用devcie/sal/wifi/mk3060/mk3060.mk 将模组控制模块代码编译进来。