快速连接蜂窝网络¶
Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.
项目概述¶
本案例基于移远通信pico开发板和UniRTOS实现。通过调用蜂窝网络相关的接口,使开发板快速连接蜂窝网络,获取IP地址。
开发准备¶
硬件要求¶
pico开发板,点此购买。
USB数据线(Type-C),点此购买。
有效SIM卡
软件要求¶
unirtos-toolchain.exe:编译工具链安装程序,点此获取。
Python:用于运行unirtos-cli工具,需使用3.9及更高版本。安装步骤详见 Python环境安装。
Git:unirtos-cli使用该工具拉取SDK、依赖库等,需使用2.20及更高版本。安装步骤详见 Git工具安装。
unirtos-cli:UniRTOS的命令行工具,用于一键拉取SDK、快速创建工程。安装步骤详见 unirtos-cli工具安装。
USB驱动:用于PC识别模块的USB枚举接口,点此获取。
QFlash.exe:模块固件烧录程序,用于烧录UniRTOS编译生成的固件,点此获取。
EPAT工具:芯片厂商提供的日志抓取工具,用于查看模块运行日志以分析应用程序执行情况,点此获取。
QCOM工具:移远通信提供的串口调试工具,用于执行和验证AT命令,点此获取。
快速上手¶
下载项目¶
示例代码位于UniRTOS官方创客仓库,点此访问下载。
添加项目至UniRTOS SDK¶
编译构建
下载项目后,请确保已安装unirtos-cli和unirtos-toolchain工具。随后,在下载的项目根目录中打开命令行窗口(Cmd或PowerShell),依次执行以下命令以拉取编译环境并进行编译:
unirtos-cli env-setup
unirtos-cli build
型号配置
项目配置中默认的编译型号为EG800ZCN_LA。若您使用的模块型号并非EG800ZCN_LA,请修改项目目录下 env_config.json 文件中的build字段,修改之后需重新编译固件以适配您的硬件。
备注
有关详细的编译与烧录流程,请参考 快速上手。
有关unirtos-cli的相关命令介绍,请参考 unirtos-cli使用教程。
硬件连接¶
将SIM卡按卡槽丝印方向正确插入,扣好卡盖。
使用USB数据线连接开发板和电脑。
日志展示¶
正确连接网络后,在 EPAT 工具中可以搜索到如下日志。
功能函数¶
unir_datacall_demo_init¶
功能描述
初始化并启动数据拨号演示功能。用于创建并启动独立任务,使数据拨号在后台运行而不阻塞主程序。关键操作
任务创建:调用 qosa_task_create() 创建名为 QDATACALLDEMO 的任务。
任务配置:调用 datacall_demo_task() 配置线程栈为4 KB,使用普通优先级,确保连网流程稳定运行。
重要性
用户需使用 UNIRTOS_APP_EXPORT 将 unir_datacall_demo_init() 注册到统一的初始化表里,供系统启动时自动调度执行,用以启动自动拨号、连网、重连全套功能。示例代码
void unir_datacall_demo_init(void)
{
int err = 0;
// Create data call dial-up demo task
err = qosa_task_create(&g_datacall_demo_task, 4 * 1024, QOSA_PRIORITY_NORMAL, "QDATACALLDEMO", unir_datacall_demo_task, QOSA_NULL);
if (err != QOSA_OK)
{
QLOGD("[datacall]]datacall_demo task create error");
return;
}
}
unir_datacall_demo_task¶
功能描述
数据拨号演示的核心主处理函数。在独立任务中完成网络附着、PDP配置、拨号建立、IP获取、掉线自动重连的全生命周期逻辑。关键操作:
创建消息队列:调用 qosa_msgq_create() 创建消息队列实例,用于接收网络事件,实现异步事件处理。
等待网络附着:调用 qosa_datacall_wait_attached() 等待注网成功,超时时间固定为300秒。
注册事件回调:调用 qosa_event_notify_register() 注册PDN断开与PDP激活状态变化的回调,用于监听网络状态。回调函数详见 datacall_nw_deact_pdp_cb() 和 datacall_pdp_change_cb()。
配置PDP上下文:调用 qosa_datacall_set_pdp_context() 设置APN、IP类型(IPv4或IPv6)等信息。
创建并启动拨号:调用 qosa_datacall_conn_new() 创建连接,调用 qosa_datacall_start() 执行同步拨号。
获取并打印IP信息:调用 qosa_ip_addr_inet_ntop() 解析IPv4或IPv6地址,调用 QLOGI() 输出解析的日志。
无限循环监听事件:等待消息队列,处理PDP断开事件并自动重拨。
掉线重连机制:最多重试10次,每次间隔20秒,重连成功后重新获取IP地址。
重要性
该函数完整封装了蜂窝数据拨号从连网到掉线重连的全套流程,是物联网设备连接蜂窝网络的核心逻辑。示例代码
static void unir_datacall_demo_task(void *arg)
{
int ret = 0;
int retry_count = 0;
qosa_uint8_t simid = 0;
int profile_idx = 1;
qosa_datacall_conn_t conn;
qosa_bool_t datacall_status = QOSA_FALSE;
qosa_datacall_ip_info_t info = {0};
datacall_demo_msg_t datacall_task_msg = {0};
qosa_pdp_context_t pdp_ctx = {0};
qosa_bool_t is_attached = QOSA_FALSE;
char ip4addr_buf[CONFIG_QOSA_INET_ADDRSTRLEN] = {0};
char ip6addr_buf[CONFIG_QOSA_INET6_ADDRSTRLEN] = {0};
QOSA_UNUSED(arg);
// Create message queue
ret = qosa_msgq_create(&g_datacall_demo_msgq, sizeof(datacall_demo_msg_t), 20);
QLOGI("[datacall]create msgq result=%d", ret);
qosa_task_sleep_sec(3);
// If attach is successful before the max time timeout, it will immediately return QOSA_TRUE and enter second while loop
// Otherwise, it will block until timeout and returning QOSA_FALSE and enter first while loop
is_attached = qosa_datacall_wait_attached(simid, DATACALL_DEMO_WAIT_ATTACH_MAX_WAIT_TIME);
if (!is_attached)
{
QLOGI("[datacall]attach fail");
goto exit;
}
// Register network PDN deactivation event callback
qosa_event_notify_register(QOSA_EVENT_NW_PDN_DEACT, datacall_nw_deact_pdp_cb, QOSA_NULL);
// Register the PDP activation status report event callback
qosa_event_notify_register(QOSA_EVENT_NET_PDP_ACT, datacall_pdp_change_cb, QOSA_NULL);
// Configure PDP context: APN, IP type
// If the operator has restrictions on the APN during registration, the APN provided by the operator needs to be set
const char *apn_str = "3gnet";
pdp_ctx.apn_valid = QOSA_TRUE;
pdp_ctx.pdp_type = QOSA_PDP_TYPE_IP; //IPv4
if (pdp_ctx.apn_valid)
{
qosa_memcpy(pdp_ctx.apn, apn_str, qosa_strlen(apn_str));
}
ret = qosa_datacall_set_pdp_context(simid, profile_idx, &pdp_ctx);
QLOGI("[datacall]set pdp context, ret=%d", ret);
// Create data call object
conn = qosa_datacall_conn_new(simid, profile_idx, QOSA_DATACALL_CONN_TCPIP);
// Start to execute the data call (sync)
ret = qosa_datacall_start(conn, DATACALL_DEMO_WAIT_DATACALL_MAX_WAIT_TIME);
if (ret != QOSA_DATACALL_OK)
{
QLOGI("[datacall]datacall fail ,ret=%d", ret);
goto exit;
}
// Get data call status (0: deactivated/inactive, 1: active)
datacall_status = qosa_datacall_get_status(conn);
QLOGI("datacall status=%d", datacall_status);
// Get IP info from the data call
ret = qosa_datacall_get_ip_info(conn, &info);
QLOGI("[datacall]pdpid=%d,simid=%d", info.simcid.pdpid, info.simcid.simid);
QLOGI("[datacall]ip_type=%d", info.ip_type);
if (info.ip_type == QOSA_PDP_IPV4)
{
// IPv4 info
qosa_memset(ip4addr_buf, 0, sizeof(ip4addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET, &info.ipv4_ip.addr.ipv4_addr, ip4addr_buf, sizeof(ip4addr_buf));
QLOGI("[datacall]ipv4 addr:%s", ip4addr_buf);
}
else if (info.ip_type == QOSA_PDP_IPV6)
{
// IPv6 info
qosa_memset(ip6addr_buf, 0, sizeof(ip6addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET6, &info.ipv6_ip.addr.ipv6_addr, ip6addr_buf, sizeof(ip6addr_buf));
QLOGI("[datacall]ipv6 addr:%s", ip6addr_buf);
}
else
{
// IPv4 and IPv6 info
qosa_memset(ip4addr_buf, 0, sizeof(ip4addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET, &info.ipv4_ip.addr.ipv4_addr, ip4addr_buf, sizeof(ip4addr_buf));
QLOGI("[datacall]ipv4 addr:%s", ip4addr_buf);
qosa_memset(ip6addr_buf, 0, sizeof(ip6addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET6, &info.ipv6_ip.addr.ipv6_addr, ip6addr_buf, sizeof(ip6addr_buf));
QLOGI("[datacall]ipv6 addr:%s", ip6addr_buf);
}
while (1)
{
ret = qosa_msgq_wait(g_datacall_demo_msgq, (qosa_uint8_t *)&datacall_task_msg, sizeof(datacall_demo_msg_t), QOSA_WAIT_FOREVER);
if (ret != 0)
continue;
QLOGI("[datacall]enter datacall demo task, msgid=%d", datacall_task_msg.msgid);
switch (datacall_task_msg.msgid)
{
case DATACALL_NW_DEACT_MSG: {
datacall_demo_pdp_deact_ind_t *deact_ptr = (datacall_demo_pdp_deact_ind_t *)datacall_task_msg.argv;
QLOGI("[datacall]simid=%d,deact pdpid=%d", deact_ptr->simid, deact_ptr->pdpid);
// Try to reactive for 10 times, and the time interval is 20 seconds
while (((ret = qosa_datacall_start(conn, DATACALL_DEMO_WAIT_DATACALL_MAX_WAIT_TIME)) != QOSA_DATACALL_OK) && (retry_count < 10))
{
retry_count++;
QLOGI("[datacall]datacall fail, the retry count is %d", retry_count);
qosa_task_sleep_sec(20);
}
if (ret == QOSA_DATACALL_OK)
{
retry_count = 0;
// Get data call status (0: deactivated/inactive, 1: active)
datacall_status = qosa_datacall_get_status(conn);
QLOGI("[datacall]datacall status=%d", datacall_status);
// Get IP info from data call
ret = qosa_datacall_get_ip_info(conn, &info);
QLOGI("[datacall]pdpid=%d,simid=%d", info.simcid.pdpid, info.simcid.simid);
QLOGI("[datacall]ip type=%d", info.ip_type);
char ip4addr_buf[CONFIG_QOSA_INET_ADDRSTRLEN] = {0};
char ip6addr_buf[CONFIG_QOSA_INET6_ADDRSTRLEN] = {0};
if (info.ip_type == QOSA_PDP_IPV4)
{
qosa_memset(ip4addr_buf, 0, sizeof(ip4addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET, &info.ipv4_ip.addr.ipv4_addr, ip4addr_buf, sizeof(ip4addr_buf));
QLOGI("[datacall]ipv4 addr:%s", ip4addr_buf);
}
else if (info.ip_type == QOSA_PDP_IPV6)
{
qosa_memset(ip6addr_buf, 0, sizeof(ip6addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET6, &info.ipv6_ip.addr.ipv6_addr, ip6addr_buf, sizeof(ip6addr_buf));
QLOGI("[datacall]ipv6 addr:%s", ip6addr_buf);
}
else
{
qosa_memset(ip4addr_buf, 0, sizeof(ip4addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET, &info.ipv4_ip.addr.ipv4_addr, ip4addr_buf, sizeof(ip4addr_buf));
QLOGI("[datacall]ipv4 addr:%s", ip4addr_buf);
qosa_memset(ip6addr_buf, 0, sizeof(ip6addr_buf));
qosa_ip_addr_inet_ntop(QOSA_IP_ADDR_AF_INET6, &info.ipv6_ip.addr.ipv6_addr, ip6addr_buf, sizeof(ip6addr_buf));
QLOGI("[datacall]ipv6 addr:%s", ip6addr_buf);
}
}
else
{
QLOGI("[datacall]datacall fail in nw deact pdn event");
}
qosa_free(datacall_task_msg.argv);
}
break;
default:
break;
}
}
exit:
qosa_event_notify_unregister(QOSA_EVENT_NW_PDN_DEACT, datacall_nw_deact_pdp_cb);
qosa_event_notify_unregister(QOSA_EVENT_NET_PDP_ACT, datacall_pdp_change_cb);
qosa_msgq_delete(g_datacall_demo_msgq);
}
datacall_nw_deact_pdp_cb¶
功能描述
PDN网络去激活(掉线)事件的回调函数。当网络/基站主动断开PDP时,该回调函数将被系统自动调用。关键操作:
获取掉线信息:解析回调触发时携带的 qosa_datacall_nw_deact_event_t 类型结构体参数,获取SIM ID和PDP ID。
封装事件消息:通过消息队列发送去激活事件消息给主任务,触发重连流程。
重要性
该函数实现了掉线感知,是自动重连机制的触发入口。示例代码
int datacall_nw_deact_pdp_cb(void *user_argv, void *argv)
{
QOSA_UNUSED(user_argv);
datacall_demo_pdp_deact_ind_t *deact_ptr = QOSA_NULL;
datacall_demo_msg_t datacall_nw_deact_msg = {0};
//Get network deactivation report parameters
qosa_datacall_nw_deact_event_t *pdp_deatch_event = (qosa_datacall_nw_deact_event_t *)argv;
QLOGI("[datacall]enter,simid=%d,pdpid=%d", pdp_deatch_event->simid, pdp_deatch_event->pdpid);
// malloc memory
deact_ptr = (datacall_demo_pdp_deact_ind_t *)qosa_malloc(sizeof(datacall_demo_pdp_deact_ind_t));
if (deact_ptr == QOSA_NULL) // If malloc fails, return 0
{
return 0;
}
deact_ptr->simid = pdp_deatch_event->simid;
deact_ptr->pdpid = pdp_deatch_event->pdpid;
// Prepare to send messages to the message queue
datacall_nw_deact_msg.msgid = DATACALL_NW_DEACT_MSG;
datacall_nw_deact_msg.argv = deact_ptr;
qosa_msgq_release(g_datacall_demo_msgq, sizeof(datacall_demo_msg_t), (qosa_uint8_t *)&datacall_nw_deact_msg, QOSA_NO_WAIT);
return 0;
}
datacall_pdp_change_cb¶
功能描述
PDP激活状态变化的回调函数。该函数将在PDP激活状态变更时被系统自动调用。关键操作
接收PDP激活状态事件,可扩展用于状态监控、日志记录、上层通知等。重要性
该函数用于实时监控PDP激活状态,便于调试与业务联动。示例代码
int datacall_pdp_change_cb(void *user_argv, void *argv)
{
// PDP activation status event report
qosa_datacall_act_event_t *pdp_act_status = (qosa_datacall_act_event_t *)argv;
QLOGI("enter,pdpid=%d,opt=%d",pdp_act_status->pdpid,pdp_act_status->opt);
return 0;
}
应用逻辑流程图¶