快速连接蜂窝网络

Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.


项目概述

本案例基于移远通信pico开发板和UniRTOS实现。通过调用蜂窝网络相关的接口,使开发板快速连接蜂窝网络,获取IP地址。

开发准备

硬件要求

../../_images/image_AtUlbR30NoHFuoxkqyNcprLVnud.webp ../../_images/image_Jb5ybZCv0opliDxDZlNc3D9Mnke.webp
  • 有效SIM卡

../../_images/image_VGKsboNviokbIJxyqdMcfY4znOe.webp

软件要求

  • 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字段,修改之后需重新编译固件以适配您的硬件。

备注

  1. 有关详细的编译与烧录流程,请参考 快速上手

  2. 有关unirtos-cli的相关命令介绍,请参考 unirtos-cli使用教程

硬件连接

../../_images/image_LHCwbywsyoIRXHxsAfQcmA9znvd.webp
  1. 将SIM卡按卡槽丝印方向正确插入,扣好卡盖。

  2. 使用USB数据线连接开发板和电脑。

日志展示

正确连接网络后,在 EPAT 工具中可以搜索到如下日志。

../../_images/image_OJbBbmZ2FoLCAKxmIlycchgQnKe.webp

功能函数

unir_datacall_demo_init

  • 功能描述
    初始化并启动数据拨号演示功能。用于创建并启动独立任务,使数据拨号在后台运行而不阻塞主程序。

  • 关键操作

    • 任务创建:调用 qosa_task_create() 创建名为 QDATACALLDEMO 的任务。

    • 任务配置:调用 datacall_demo_task() 配置线程栈为4 KB,使用普通优先级,确保连网流程稳定运行。

  • 重要性
    用户需使用 UNIRTOS_APP_EXPORTunir_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;
}

应用逻辑流程图

image