7.2.5 Molink 蜂窝模组抽象层
7.2.5 Molink 蜂窝模组抽象层
📚 本节导读
学习时长:约 20 分钟
难度级别:⭐⭐⭐☆☆
前置知识:AT 指令基础、蜂窝网络概念、第 7.2.1 节 Socket 基础
🎯 学习目标
- 理解 Molink 蜂窝模组抽象层的架构设计
- 了解 Molink 支持的模组型号分类
- 掌握 Molink 核心 API 的使用(创建、获取、查询模组信息)
- 掌握
mo_get_csq/mo_get_imei/mo_get_reg等查询接口的正确用法
一、概述
Molink(Module Link Kit)是 OneOS 的蜂窝通信模组抽象层,位于 components/net/molink/。它把"模组对象"(mo_object_t)、"操作函数表"(ops_table)和"AT 解析器"(at_parser)组织在一起,为上层应用提供统一的 API,屏蔽不同品牌、不同型号模组的 AT 指令差异。
一句话核心:Molink 用"对象 + 操作函数表"的框架,把每款模组的差异化 AT 指令封装成统一的 mo_xxx 接口(mo_get_imei/mo_get_csq/mo_get_reg...),应用通过 mo_object_get_default()/mo_object_get_by_name() 拿到模组对象后,用同一套接口操作任意型号模组。
典型场景:
- 对接 NB-IoT/4G/5G 蜂窝模组(M5311、BC26、EC20、SIM7600CE 等)做联网通信;
- WiFi 模组(ESP8266/ESP32)、蓝牙模组(YQ8228)也复用同一框架;
- 上层可叠加 Molink 的 Socket/Netconn 层(
MOLINK_USING_SOCKETS_OPS/NETCONN_OPS)直接以 Socket API 方式收发数据。
二、架构设计
┌──────────────────────────────────────────────────┐
│ 应用层(API 宏 / 用户代码) │
│ mo_get_imei / get_csq / mo_at_test / ... │
├──────────────────────────────────────────────────┤
│ API 层 (api/) mo_api.h 统一入口 │
│ mo_general / mo_netserv / mo_socket / mo_wifi │
├──────────────────────────────────────────────────┤
│ 核心层 (core/) 对象与操作表框架 │
│ mo_object_t + ops_table + mo_object_get_default │
├──────────────────────────────────────────────────┤
│ 解析器层 (parser/) AT 收发与解析 │
│ at_parser / at_resp / URC 处理 │
├──────────────────────────────────────────────────┤
│ 模组适配层 (module/) 每款模组一套 ops │
│ EC20 │ BC26 │ ESP8266 │ M5311 │ SIM7600CE │ ... │
└──────────────────────────────────────────────────┘关键理解(分层职责):
- API 层(
api/include/mo_api.h):按功能集(MOLINK_USING_GENERAL_OPS/NETSERV_OPS/SOCKETS_OPS...)条件编译地聚合mo_general.h/mo_netserv.h/mo_socket.h等,并提供用mo_get_default()的便捷宏(如get_imei(...)); - 核心层(
core/include/mo_object.h):定义mo_object_t(含name/type/platform/at_parser/ops_table[]),提供对象管理; - 解析器层(
parser/include/at_parser.h):封装 AT 指令的收发、响应匹配与 URC 处理; - 模组适配层(
module/xxx):每款模组实现各自的mo_xxx_ops_t函数表。
三、核心数据结构
3.1 模组对象(mo_object_t)
完整源码(core/include/mo_object.h):
typedef enum mo_platform
{
MO_PLATFORM_NULL = 0,
#ifdef MOLINK_PLATFORM_MCU
MO_PLATFORM_MCU,
#endif /* MOLINK_PLATFORM_MCU */
#ifdef MOLINK_PLATFORM_OPENCPU
MO_PLATFORM_OPENCPU,
#endif /* MOLINK_PLATFORM_OPENCPU */
} mo_platform_t;
#ifdef MOLINK_PLATFORM_MCU
typedef struct mo_parser_config
{
const char *parser_name;
os_device_t *parser_device;
os_size_t recv_buff_len;
} mo_parser_config_t;
#endif /* MOLINK_PLATFORM_MCU */
typedef struct mo_object
{
os_slist_node_t list; /* module object manage list */
char name[OS_NAME_MAX + 1]; /* module object name */
mo_type_t type; /* module object type */
mo_platform_t platform; /* module object platform */
#ifdef MOLINK_PLATFORM_MCU
at_parser_t parser; /* module object at parser */
#endif /* MOLINK_PLATFORM_MCU */
const void *ops_table[MODULE_OPS_MAX]; /* module object operates table */
} mo_object_t;字段详解:
| 字段 | 含义 |
|---|---|
list | 对象管理链表节点(系统内可同时存在多个模组对象) |
name | 模组对象名称(mo_object_get_by_name 依据它查找) |
type | 模组类型(mo_type_t 枚举,如 MODULE_TYPE_EC20) |
platform | 平台类型(MCU / OpenCPU) |
parser | AT 解析器(仅 MOLINK_PLATFORM_MCU 下存在) |
ops_table[] | 操作函数表数组,按 MODULE_OPS_* 索引存放该模组实现的操作函数集合 |
关键理解:
ops_table是 Molink 抽象的核心——每款模组把mo_general_ops_t/mo_netserv_ops_t/mo_socket_ops_t等函数表挂到ops_table对应索引,mo_get_imei等封装函数内部按MODULE_OPS_GENERAL索引取出get_imei函数指针并调用,从而实现"同一 API、不同模组实现"。
3.2 模组类型枚举(mo_type_t)
mo_type_t(core/include/mo_type.h)按网络类型分类,每类在对应 MOLINK_USING_xxx 宏开启时才编译对应枚举值:
| 网络类型 | 模组类型枚举(MODULE_TYPE_*) |
|---|---|
| NB-IoT | M5310A / M5311 / BC95 / BC28 / BC26 / BC20 / SIM7020 / SIM7070X / N21 / MB26 / GM120 / E7025 / ME3616 |
| 4G Cat1 | EC200X_600S / ML302 / GM190 / A7600X / A7670X / L610 / AIR723UG / CLM920RV3 / N58 / AIR720UH |
| 4G Cat4 | GM510 / EC20 / SIM7600CE / ME3630_W |
| 5G | RG500Q |
| WiFi | ESP8266 / ESP32 |
| 蓝牙 | YQ8228 |
四、支持的模组列表(module/ 目录)
| 品牌 | 型号 | 网络类型 | 枚举 |
|---|---|---|---|
| 移远 Quectel | EC20 | 4G Cat4 | MODULE_TYPE_EC20 |
| EC200X/EC600S | 4G Cat1 | MODULE_TYPE_EC200X_600S | |
| BC20/BC26/BC28/BC95 | NB-IoT | MODULE_TYPE_BC2x/BC95 | |
| RG500Q | 5G | MODULE_TYPE_RG500Q | |
| ME3630_W | 4G Cat4 | MODULE_TYPE_ME3630_W | |
| ME3616 | NB-IoT | MODULE_TYPE_ME3616 | |
| 中移物联 | M5310A/M5311 | NB-IoT | MODULE_TYPE_M5310A/M5311 |
| ML302 | 4G Cat1 | MODULE_TYPE_ML302 | |
| 广和通 Fibocom | N21 | NB-IoT | MODULE_TYPE_N21 |
| N58 | 4G Cat1 | MODULE_TYPE_N58 | |
| 芯讯通 SIMCom | SIM7020/SIM7070X | NB-IoT | MODULE_TYPE_SIM7020/SIM7070X |
| SIM7600CE | 4G Cat4 | MODULE_TYPE_SIM7600CE | |
| 合宙 AirM2M | AIR720UH/AIR723UG | 4G Cat1 | MODULE_TYPE_AIR720UH/AIR723UG |
| 高新兴 Gosuncn | GM120 | NB-IoT | MODULE_TYPE_GM120 |
| GM190 | 4G Cat1 | MODULE_TYPE_GM190 | |
| GM510 | 4G Cat4 | MODULE_TYPE_GM510 | |
| 中兴 ZTE | L610 | 4G Cat1 | MODULE_TYPE_L610 |
| 乐鑫 Espressif | ESP8266/ESP32 | WiFi | MODULE_TYPE_ESP8266/ESP32 |
| 有方 | N58 系列等 | 4G | 见 module/ |
| 紫光展锐 | CLM920RV3 | 4G Cat1 | MODULE_TYPE_CLM920RV3 |
| 中移物联 | MB26 | NB-IoT | MODULE_TYPE_MB26 |
| 宜远 | E7025 | NB-IoT | MODULE_TYPE_E7025 |
五、核心 API 参考
5.1 创建与销毁(mo_factory.h)
完整源码(头文件声明):
typedef mo_object_t *(*mo_create_fn)(const char *name, void *parser_config);
typedef os_err_t (*mo_destory_fn)(mo_object_t *self);
mo_object_t *mo_create(const char *name, mo_type_t type, void *parser_config);
os_err_t mo_destroy(mo_object_t *self, mo_type_t type);关键理解:
mo_create(name, type, parser_config):type是mo_type_t(如MODULE_TYPE_EC20),parser_config是 AT 解析器配置(mo_parser_config_t,含parser_name/parser_device/recv_buff_len);mo_destroy(self, type):销毁对象需同时传回模组类型(用于释放对应 ops 资源);- 一般在板级初始化代码中创建并调用
mo_object_set_default,之后应用层通过mo_object_get_default()获取,无需自己创建。
5.2 获取对象(mo_object.h)
完整源码(头文件声明):
os_err_t mo_object_init(mo_object_t *self, const char *name, void *parser_config);
os_err_t mo_object_deinit(mo_object_t *self);
mo_object_t *mo_object_get_by_name(const char *name);
mo_object_t *mo_object_get_default(void);
void mo_object_set_default(mo_object_t *self);关键理解:
mo_object_get_default():获取系统默认模组对象(最常用,mo_api.h的便捷宏都基于它);mo_object_get_by_name(name):按名称获取指定模组对象(系统可同时挂多款模组);mo_object_set_default(self):把某对象设为默认,通常由板级创建代码调用。
5.3 通用查询(mo_general.h)
完整源码(头文件声明):
#define MO_IMEI_LEN (15) /* International Mobile Equipment Identity */
#define MO_IMSI_LEN (15) /* International Mobile Subscriber Identity */
#define MO_ICCID_LEN (20) /* Integrate Circuit Card Identity */
os_err_t mo_at_test(mo_object_t *self);
os_err_t mo_get_imei(mo_object_t *self, char *value, os_size_t len);
os_err_t mo_get_imsi(mo_object_t *self, char *value, os_size_t len);
os_err_t mo_get_iccid(mo_object_t *self, char *value, os_size_t len);
os_err_t mo_get_firmware_version(mo_object_t *self, mo_firmware_version_t *version);
void mo_get_firmware_version_free(mo_firmware_version_t *version);
os_err_t mo_get_eid(mo_object_t *self, char *eid, os_size_t len);
os_err_t mo_gm_time(mo_object_t *self, struct tm *l_tm);关键理解:
mo_at_test(self):执行AT测试指令(不是文档旧写的mo_at_exec),返回os_err_t;mo_get_imei(self, value, len):查询 IMEI(15 位),结果写入value,len为缓冲大小,返回os_err_t(OS_EOK成功),不是返回字符串;mo_get_iccid(20 位 SIM 卡标识)、mo_get_imsi(15 位)同理;mo_get_firmware_version返回mo_firmware_version_t(行数 + 字符串数组),用完需mo_get_firmware_version_free释放。
5.4 网络服务查询(mo_netserv.h)
完整源码(头文件声明,节选):
typedef enum ps_attach_state
{
DETACHED = 0,
ATTACHED,
UNDEF_ATTACH_STATE
} ps_attach_state_t;
typedef enum pdp_context_state
{
DEACTIVATED = 0,
ACTIVATED,
UNDEF_CGACT_STATE
} pdp_context_state_t;
typedef struct eps_registration_info
{
uint8_t reg_n;
uint8_t reg_stat; /* EPS registration status */
char tac[REG_TAC_MAX_STR_LEN]; /* tracking area code, HEX string */
char cell_id[REG_CELL_ID_MAX_STR_LEN]; /* cell ID, HEX string */
uint8_t act; /* Access technology of the registered network */
uint8_t cause_type; /* indicates the type of <reject_cause> */
uint8_t reject_cause; /* contains the cause of the failed registration */
char active_time[PSM_TIMER_MAX_STR_LEN]; /* GPRS Timer 2 IE in 3GPP TS 24.008 Table 10.5.163 */
char periodic_tau[PSM_TIMER_MAX_STR_LEN]; /* GPRS Timer 3 IE in 3GPP TS 24.008 Table 10.5.163a */
} eps_reg_info_t;
os_err_t mo_set_attach(mo_object_t *self, uint8_t attach_stat);
os_err_t mo_get_attach(mo_object_t *self, uint8_t *attach_stat);
os_err_t mo_set_reg(mo_object_t *self, uint8_t reg_n);
os_err_t mo_get_reg(mo_object_t *self, eps_reg_info_t *info);
os_err_t mo_get_csq(mo_object_t *self, uint8_t *rssi, uint8_t *ber);
os_err_t mo_get_radio(mo_object_t *self, radio_info_t *radio_info);关键理解:
mo_get_csq(self, uint8_t *rssi, uint8_t *ber):查询信号强度,两个输出参数——rssi(0~31,99表示未知)和ber(误码率),返回os_err_t;不是返回单个 int 的旧式接口;mo_get_reg(self, eps_reg_info_t *info):查询 EPS 网络注册状态,结果填到eps_reg_info_t(含reg_stat、tac、cell_id、act等);不是旧写的mo_get_netstat;mo_set_attach/mo_get_attach:PS 附着/去附着(DETACHED=0/ATTACHED=1);mo_get_radio:查询小区无线参数(RSRP/RSRQ/SNR/PCI 等)。
六、Molink 使用示例
#include <string.h>
#include <os_kernel.h>
#include <oneos_config.h>
#include "mo_api.h"
#include "mo_object.h"
void molink_example(void *arg)
{
mo_object_t *mo = NULL;
char imei[MO_IMEI_LEN + 1] = {0};
char iccid[MO_ICCID_LEN + 1] = {0};
uint8_t rssi = 0;
uint8_t ber = 0;
eps_reg_info_t reg_info = {0};
os_err_t ret;
/* 1. 获取系统默认模组对象(板级初始化时已创建并 set_default) */
mo = mo_object_get_default();
if (mo == OS_NULL) {
os_kprintf("[Molink] get default module failed\r\n");
return;
}
os_kprintf("[Molink] module name: %s\r\n", mo->name);
/* 2. AT 通道自检 */
ret = mo_at_test(mo);
if (ret != OS_EOK) {
os_kprintf("[Molink] AT test failed: %d\r\n", ret);
return;
}
/* 3. 查询 IMEI(15 位) */
ret = mo_get_imei(mo, imei, sizeof(imei));
if (ret == OS_EOK) {
os_kprintf("[Molink] IMEI: %s\r\n", imei);
}
/* 4. 查询 ICCID(20 位) */
ret = mo_get_iccid(mo, iccid, sizeof(iccid));
if (ret == OS_EOK) {
os_kprintf("[Molink] ICCID: %s\r\n", iccid);
}
/* 5. 查询信号强度(两个输出参数:rssi + ber) */
ret = mo_get_csq(mo, &rssi, &ber);
if (ret == OS_EOK) {
os_kprintf("[Molink] CSQ: rssi=%u (0-31), ber=%u\r\n", rssi, ber);
}
/* 6. 查询网络注册状态 */
ret = mo_get_reg(mo, ®_info);
if (ret == OS_EOK) {
os_kprintf("[Molink] reg_stat=%u, act=%u, cell_id=%s\r\n",
reg_info.reg_stat, reg_info.act, reg_info.cell_id);
}
}分步详解:
- 获取对象:
mo_object_get_default()拿到板级代码创建并设为默认的模组对象,不需要应用自己mo_create; - AT 自检:
mo_at_test(mo)验证 AT 通道可用; - 查 IMEI/ICCID:
mo_get_imei(mo, buf, len)把结果写入buf(注意buf要留\0空间),成功判ret == OS_EOK; - 查信号:
mo_get_csq(mo, &rssi, &ber)用两个指针输出; - 查注册:
mo_get_reg(mo, ®_info)填eps_reg_info_t。
数值演算(CSQ 信号强度):mo_get_csq 返回的 rssi 按 AT+CSQ 的值映射,rssi = 0 对应约 -113 dBm,每 +1 约 +2 dBm,rssi = 31 对应约 -51 dBm,rssi = 99 表示未知/不可测:
| rssi | 估算信号强度 | 建议 |
|---|---|---|
| 0 | ≤ -113 dBm | 极弱,不适合通信 |
| 10 | 约 -93 dBm | 较弱 |
| 15~20 | 约 -83~-73 dBm | 可用 |
| 25~31 | 约 -63~-51 dBm | 良好 |
📝 本节小结
核心要点回顾
- 对象+操作表框架:Molink 用
mo_object_t+ops_table[]组织模组抽象,mo_xxx封装函数按 ops 索引调用具体模组实现。 - 真实 API:创建/销毁用
mo_create(name, mo_type_t, parser_config)/mo_destroy(self, mo_type_t)(mo_factory.h);获取对象用mo_object_get_default()/mo_object_get_by_name();不是虚构的mo_create(name, parser, user_data)。 - AT 接口:
mo_at_test(self)做 AT 自检,没有mo_at_exec。 - 查询接口返回
os_err_t:mo_get_imei(mo, buf, len)/mo_get_iccid/mo_get_imsi结果写入缓冲、返回OS_EOK表示成功。 - CSQ 双输出:
mo_get_csq(mo, &rssi, &ber)用两个指针输出(rssi 0~31、ber 误码率),不是返回单个 int。 - 网络注册:
mo_get_reg(mo, &eps_reg_info_t)填reg_stat/tac/cell_id/act等,没有mo_get_netstat。 - 功能集条件编译:
MOLINK_USING_GENERAL_OPS/NETSERV_OPS/SOCKETS_OPS/WIFI_OPS等决定启用哪些操作集,mo_api.h据此聚合头文件。 - 模组分类:
mo_type_t枚举按 NB-IoT / 4G Cat1 / Cat4 / 5G / WiFi / 蓝牙分类,每款模组一个MODULE_TYPE_*。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
用 mo_create(name, parser, user_data) | 真实是 mo_create(name, mo_type_t type, void *parser_config) → type 传 MODULE_TYPE_EC20 等枚举 |
mo_destroy(mo) 缺类型参数 | 真实是 mo_destroy(self, mo_type_t type) → 必须传回模组类型 |
用 mo_at_exec(mo, "AT...") | 没有 mo_at_exec → 用 mo_at_test(mo)(只做 AT 自检) |
mo_get_csq(mo) 当返回 int | 真实是 mo_get_csq(mo, &rssi, &ber) 双指针输出 → 准备两个 uint8_t 变量取地址 |
mo_get_imei(mo, ...) 返回值当字符串 | 返回 os_err_t,IMEI 写入缓冲 → 先准备 char imei[16],判 ret == OS_EOK 再读 imei |
用 mo_get_netstat(mo) | 没有 mo_get_netstat → 用 mo_get_reg(mo, &eps_reg_info_t) |
mo_get_imei 缓冲不够 | IMEI 15 位 + \0 = 16 字节 → 用 char imei[MO_IMEI_LEN + 1](或用 MO_ICCID_LEN+1) |
忘记 mo_object_set_default | 应用 mo_object_get_default() 会拿不到对象 → 板级创建后调用 mo_object_set_default |
不判 mo_at_test 返回 | AT 通道异常时后续查询全失败 → 先判 OS_EOK |
包含 <molink.h> | 头文件是 mo_api.h/mo_object.h → 包含 "mo_api.h" 等 |
下一步
- 7.2.6 Kconfig 配置指南:Molink 已提供统一模组抽象,下一节学习 OneOS 网络栈的 Kconfig 配置指南,掌握如何开启 Molink 功能集(
MOLINK_USING_GENERAL_OPS等)、选择具体模组(MOLINK_USING_EC20等)及配置各协议栈参数。