# 快速连接蜂窝网络 ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- # 项目概述 本案例基于移远通信pico开发板和UniRTOS实现。通过调用蜂窝网络相关的接口,使开发板快速连接蜂窝网络,获取IP地址。 # 开发准备 ## 硬件要求 - pico开发板,[点此购买]()。 ```{image} images/image_AtUlbR30NoHFuoxkqyNcprLVnud.webp :width: 600px :height: 600px ``` - USB数据线(Type-C),[点此购买]()。 ```{image} images/image_Jb5ybZCv0opliDxDZlNc3D9Mnke.webp :width: 615px :height: 615px ``` - 有效SIM卡 ```{image} images/image_VGKsboNviokbIJxyqdMcfY4znOe.webp :width: 500px :height: 500px ``` ## 软件要求 - **unirtos-toolchain.exe**:编译工具链安装程序,[点此获取]()。 - **Python**:用于运行unirtos-cli工具,需使用3.9及更高版本。安装步骤详见 [Python环境安装](<../../快速上手/环境搭建/环境搭建.md#python环境安装>)。 - **Git**:unirtos-cli使用该工具拉取SDK、依赖库等,需使用2.20及更高版本。安装步骤详见 [Git工具安装](<../../快速上手/环境搭建/环境搭建.md#git工具安装>)。 - **unirtos-cli**:UniRTOS的命令行工具,用于一键拉取SDK、快速创建工程。安装步骤详见 [unirtos-cli工具安装](<../../快速上手/环境搭建/环境搭建.md#unirtos-cli工具安装>)。 - **USB驱动**:用于PC识别模块的USB枚举接口,[点此获取]()。 - **QFlash.exe**:模块固件烧录程序,用于烧录UniRTOS编译生成的固件,[点此获取]()。 - **EPAT工具**:芯片厂商提供的日志抓取工具,用于查看模块运行日志以分析应用程序执行情况,[点此获取]()。 - **QCOM工具**:移远通信提供的串口调试工具,用于执行和验证AT命令,[点此获取]()。 # 快速上手 ## 下载项目 示例代码位于UniRTOS官方创客仓库,[点此访问下载]()。 ## 添加项目至UniRTOS SDK **编译构建** 下载项目后,请确保已安装unirtos-cli和unirtos-toolchain工具。随后,在下载的项目根目录中打开命令行窗口(Cmd或PowerShell),依次执行以下命令以拉取编译环境并进行编译: ```plaintext unirtos-cli env-setup unirtos-cli build ``` **型号配置** 项目配置中默认的编译型号为EG800ZCN_LA。若您使用的模块型号并非EG800ZCN_LA,请修改项目目录下 *env_config.json* 文件中的build字段,修改之后需重新编译固件以适配您的硬件。 ```{note} 1. 有关详细的编译与烧录流程,请参考 [快速上手](<../../快速上手/快速上手.md>)。 2. 有关unirtos-cli的相关命令介绍,请参考 [unirtos-cli使用教程](<../../开发工具/unirtos-cli使用教程/unirtos-cli使用教程.md#unirtos-cli使用教程>)。 ``` ## 硬件连接 ```{image} images/image_LHCwbywsyoIRXHxsAfQcmA9znvd.webp :width: 860px :height: 641px :align: center ``` 1. 将SIM卡按卡槽丝印方向正确插入,扣好卡盖。 2. 使用USB数据线连接开发板和电脑。 ## 日志展示 正确连接网络后,在 **EPAT** 工具中可以搜索到如下日志。 ```{image} images/image_OJbBbmZ2FoLCAKxmIlycchgQnKe.webp :width: 1120px :height: 528px :align: center ``` # 功能函数 ## unir_datacall_demo_init - **功能描述** 初始化并启动数据拨号演示功能。用于创建并启动独立任务,使数据拨号在后台运行而不阻塞主程序。 - **关键操作** - 任务创建:调用 [*qosa_task_create()*](<../../系统功能/多线程/多线程.md#>) 创建名为 *QDATACALLDEMO* 的任务。 - 任务配置:调用 ***datacall_demo_task()*** 配置线程栈为4 KB,使用普通优先级,确保连网流程稳定运行。 - **重要性** 用户需使用 *UNIRTOS_APP_EXPORT* 将 *unir_datacall_demo_init()* 注册到统一的初始化表里,供系统启动时自动调度执行,用以启动自动拨号、连网、重连全套功能。 - **示例代码** ```c 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()*](<../../系统功能/同步与通信/消息队列/消息队列.md#>) 创建消息队列实例,用于接收网络事件,实现异步事件处理。 - 等待网络附着:调用 [*qosa_datacall_wait_attached()*](<../../网络与通信/网卡/蜂窝无线网卡/DataCall拨号/DataCall拨号.md#>) 等待注网成功,超时时间固定为300秒。 - 注册事件回调:调用 *qosa_event_notify_register()* 注册PDN断开与PDP激活状态变化的回调,用于监听网络状态。回调函数详见 ***datacall_nw_deact_pdp_cb()*** 和 ***datacall_pdp_change_cb()***。 - 配置PDP上下文:调用 [*qosa_datacall_set_pdp_context()*](<../../网络与通信/网卡/蜂窝无线网卡/DataCall拨号/DataCall拨号.md#>) 设置APN、IP类型(IPv4或IPv6)等信息。 - 创建并启动拨号:调用 [*qosa_datacall_conn_new()*](<../../网络与通信/网卡/蜂窝无线网卡/DataCall拨号/DataCall拨号.md#>) 创建连接,调用 [*qosa_datacall_start()*](<../../网络与通信/网卡/蜂窝无线网卡/DataCall拨号/DataCall拨号.md#>) 执行同步拨号。 - 获取并打印IP信息:调用 *qosa_ip_addr_inet_ntop()* 解析IPv4或IPv6地址,调用 *QLOGI()* 输出解析的日志。 - 无限循环监听事件:等待消息队列,处理PDP断开事件并自动重拨。 - 掉线重连机制:最多重试10次,每次间隔20秒,重连成功后重新获取IP地址。 - **重要性** 该函数完整封装了蜂窝数据拨号从连网到掉线重连的全套流程,是物联网设备连接蜂窝网络的核心逻辑。 - **示例代码** ```c 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。 - 封装事件消息:通过消息队列发送去激活事件消息给主任务,触发重连流程。 - **重要性** 该函数实现了掉线感知,是自动重连机制的触发入口。 - **示例代码** ```c 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激活状态,便于调试与业务联动。 - **示例代码** ```c 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; } ``` # 应用逻辑流程图 ```{figure} images/board_WjoUwQVlKhBLSgbZTfXcFUfOn2f.jpg :align: center :alt: image ```