ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Embassy nRF91 蜂窝调制解调器网络驱动:embassy-net-nrf91 集成与实践

Embassy nRF91 蜂窝调制解调器网络驱动:embassy-net-nrf91 集成与实践 嵌入式物联网异步编程【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址https://gitcode.com/gh_mirrors/em/embassy点击查看免费下载导读embassy-net-nrf91是 Embassy 生态中面向 Nordic nRF91 系列蜂窝调制解调器如 nRF9160的embassy-net驱动 crate它通过 nRF91 应用处理器Application Core与蜂窝调制解调器之间的 IPCInter-Processor Communication共享内存通道把蜂窝网络能力抽象成标准的embassy-net网络设备让开发者直接使用TcpSocket、UDP、DNS 等高层 API。本文围绕该驱动从快速上手示例、核心 API、IPC 底层原理到蜂窝上下文管理APN 配置、认证、重附着逐层展开读完后你将能够基于当前仓库在 nRF9160 上搭建一个可用的蜂窝 TCP 客户端并理解驱动内部控制块 消息列表的共享内存通信机制。一、驱动概览蜂窝调制解调器如何接入 embassy-netembassy-net-nrf91定位在 embassy-net 的网络设备层。它实现了embassy-net-driver-channel定义的设备接口对外暴露一个ch::Device类型的网络驱动NetDriver可以直接传给embassy_net::Stack创建网络栈进而获得 TCP/UDP 等 socket 能力。该驱动在 embassy-net-nrf91/README.md 中明确说明了两点核心事实它是Nordic nRF91 系列蜂窝调制解调器的embassy-net驱动可运行在任何 executor 上Interoperability: This crate can run on any executor这意味着它不依赖特定的调度器实现与 Embassy 之外的其他 async 运行时也能协作。关于使用示例README 指向仓库内的 examples/nrf9160 目录当前仓库中实际存在examples/nrf9160/Cargo.toml、examples/nrf9160/memory.x以及examples/nrf9160/src/bin/modem_tcp_client.rs示例程序。二、快速上手在 nRF9160 上跑通蜂窝 TCP 客户端仓库中的 modem_tcp_client.rs 是官方提供的完整可运行示例它演示了从驱动创建、后台任务派发、APN 配置到 TCP 连接的完整流程。2.1 中断处理IPC 中断入口驱动依赖 nRF91 的 IPC 外设中断来接收调制解调器的通知示例通过#[interrupt]宏声明中断处理函数并调用驱动的on_ipc_irq()#[interrupt] fn IPC() { embassy_net_nrf91::on_ipc_irq(); }on_ipc_irq()在 lib.rs 中的实现会清除 IPC_NS 的中断使能并唤醒驱动内部注册的 waker驱动后台任务随后轮询 IPC 事件。2.2 创建驱动共享内存与三个句柄驱动通过new_with_trace()或new()创建返回四元组(NetDriver, Control, Runner, TraceReader)let (device, control, runner, tracer) embassy_net_nrf91::new_with_trace(STATE.init(State::new()), ipc_mem, TRACE.init(TraceBuffer::new())).await;参数含义如下参数类型说明statemut State驱动共享状态通过State::new()创建用StaticCell静态存放shmemmut [MaybeUninitu8]应用核与调制解调器共享的 IPC 内存区见下文内存要求trace_buffermut TraceBuffer调制解调器 trace 数据缓冲通过TraceBuffer::new()创建shmem在示例中取自链接脚本定义的 IPC 内存区域let ipc_mem unsafe { let ipc_start __start_ipc as *const u8 as *mut MaybeUninitu8; let ipc_end __end_ipc as *const u8 as *mut MaybeUninitu8; let ipc_len ipc_end.offset_from(ipc_start) as usize; slice::from_raw_parts_mut(ipc_start, ipc_len) };对应的 memory.x 为 nRF9160 定义了三个内存区域MEMORY { FLASH : ORIGIN 0x00000000, LENGTH 1024K RAM : ORIGIN 0x20010000, LENGTH 192K IPC : ORIGIN 0x20000000, LENGTH 64K } PROVIDE(__start_ipc ORIGIN(IPC)); PROVIDE(__end_ipc ORIGIN(IPC) LENGTH(IPC));可见 IPC 共享内存独占 RAM 低端 64KB0x2000_0000 起始。2.3 后台任务modem_task 与 net_task驱动需要两个后台任务持续运行Runner::run()负责驱动后台处理embassy_net::Runner::run()负责网络栈#[embassy_executor::task] async fn modem_task(runner: Runnerstatic) - ! { runner.run().await } #[embassy_executor::task] async fn net_task(mut runner: embassy_net::Runnerstatic) - ! { runner.run().await }Runner::run()的实现在 lib.rs它注册 waker 后调用state.poll()处理 IPC 事件并轮询ch.poll_tx()获取待发送的 IP 包通过消息0x7006_0004IP send发送给调制解调器。2.4 建立网络栈并配置 APN示例随后初始化网络栈、把驱动挂到接口上并使用context::Control配置蜂窝上下文let control CONTROL.init(context::Control::new(control, 0).await); spawner.spawn(unwrap!(control_task( control, context::Config { apn: biot.nat.es, auth_prot: context::AuthProt::Pap, auth: Some((borange, borange)), pin: None, }, iface )));最后iface.wait_config_up().await等待接口配置就绪即可创建TcpSocket发起连接。三、驱动核心 APInew / new_with_trace 与运行时对象驱动在 lib.rs 中提供了两个构造入口和若干运行时对象new(state, shmem)创建(NetDriver, Control, Runner)不带 tracenew_with_trace(state, shmem, trace_buffer)额外返回TraceReader用于读取调制解调器内部 trace 通道数据调试蜂窝协议栈问题时非常有用NetDrivera类型别名ch::Devicea传给embassy_net::StackControla运行时控制句柄可执行 AT 命令、打开/关闭裸 socket、设置链路状态lib.rsRunnera后台运行器必须与所有网络操作并发运行lib.rsState/TraceBuffer均为const fn new()创建的共享状态用StaticCell静态分配。Control::wait_init()会等待调制解调器完成 IPC 初始化调制解调器通过 IPC 事件通道 2 上报modem_info后驱动把init置位并唤醒等待者见 lib.rs。四、底层原理IPC 共享内存与控制块 消息列表协议这是本驱动最核心的机制全部实现在 lib.rs 中理解它能帮助你排查驱动集成问题。4.1 共享内存的布局与分配new_internal()lib.rs在传入的共享内存上通过Allocator顺序分配以下结构ControlBlock控制块crate 内部#[repr(C)]结构lib.rsRX 缓冲RX_SIZE 8 * 1024lib.rsTrace 缓冲TRACE_SIZE 16 * 1024。ControlBlock中包含版本号、RX 基址、两个列表control list 与 data list、调制解调器信息指针、trace 指针、消息槽msgs: [[Message; LIST_LEN]; 2]LIST_LEN 32以及 4 个 1500 字节的发送缓冲tx_bufslib.rs。4.2 内存与安全属性要求new_internal()对共享内存有严格断言lib.rs长度不能为 0长度必须是 8KBSPU_REGION_SIZE的整数倍指针必须 8KB 对齐共享内存必须位于 RAM 低端 128KB 内shmem_ptr shmem_len 0x2002_0000。驱动还会通过SPU_SSystem Protection Unit安全侧把对应 RAM 区域配置为非安全secattr(false)、可读可写可执行并把外设 ID 42IPC 相关设为非安全从而允许非安全侧的调制解调器访问共享内存lib.rs。4.3 消息列表协议应用核与调制解调器之间通过两条列表control list 与 data list交换Message。Message是#[repr(C)]结构lib.rs包含id高 16 位区分命令类型、低 16 位区分子类型如 AT 命令0x0001_0003、打开 socket0x7001_0004、IP 发送0x7006_0004、关闭 socket0x7009_0004channel1 control2 datadata/data_len载荷指针与长度param44 字节参数区。每个ListItemlib.rs由state高 16 位序号低 8 位标记 sent/held/freed与message指针组成。发送方把Message写入消息槽、设置列表项状态并触发对应 IPC 通道的tasks_send接收方轮询 IPC 事件、检查列表项状态与序号匹配后处理消息process()lib.rs。4.4 IPC 事件通道与中断唤醒驱动使用IPC_NS的事件通道 0/2/4/6/7lib.rs通道用途0一般事件清除2调制解调器初始化完成上报modem_info与收发列表4调制解调器投递 control/data 消息数据接收通知、AT 响应等6一般事件清除7trace 数据就绪所有事件统一由on_ipc_irq()触发 wakerRunner::run()中被唤醒后轮询处理。RX 路径上调制解调器通过 IP receive 通知id 28 9、子类型 1把数据包送入驱动驱动复制到PacketBuf并交给ch::Runner::try_rx()lib.rsTX 路径则由Runner从ch.poll_tx()取出待发包经send_message()复制到共享内存的tx_bufs后发送lib.rs。4.5 安全校验PointerChecker由于调制解调器返回的是共享内存中的裸指针驱动使用PointerCheckerlib.rs对每个传入指针做边界与对齐校验越界即 panic防止不可信指针导致内存破坏。五、蜂窝上下文管理context::Control 高层 API底层Control之上context.rs 提供了面向某个 PDN 上下文PDP context的高层封装把 APN 配置、认证、附着、地址获取等流程封装成类型安全的 API。5.1 配置结构Config 与 AuthProtpub struct Configa { pub apn: a [u8], // 目标 APN 地址 pub auth_prot: AuthProt, // 认证协议 pub auth: Option(a [u8], a [u8]), // (用户名, 密码) pub pin: Optiona [u8], // SIM PIN } pub enum AuthProt { None 0, // 无认证 Pap 1, // PAP 认证 Chap 2, // CHAP 认证 }对应 context.rs。Control::new(control, cid)接收底层控制句柄与上下文 IDcid并会先等待调制解调器初始化完成context.rs。5.2 configure()AT 命令序列configure(config)context.rs通过at-commandscrate 的CommandBuilder依次下发CFUN0先关闭射频功能避免配置冲突CGDCONTcid,IP,apn配置 PDP 上下文的数据类型与 APNCGAUTHcid,auth_prot[,username,password]配置认证协议与凭据无凭据时省略用户名/密码参数CPINpin若有 SIM PIN 则下发命令失败返回 ERROR会被忽略——文档注释明确说明忽略表示无需 PIN 的 ERROR。文档注释特别提醒configure()会断开当前 APN 连接仅在配置发生变化时才应调用配置完成后需要调用enable()激活。5.3 附着与状态查询attach()CGATT1附着 PDNdetach()CGATT0分离 PDNstatus()依次执行CGATT查询附着状态、CGPADDR查询 IP 地址、CGCONTRDP查询网关与 DNS返回Status结构context.rspub struct Status { pub attached: bool, // 是否已附着 APN pub ip: OptionIpAddr, // 分配的 IP pub gateway: OptionIpAddr, // 网关 pub dns: VecIpAddr, 2, // DNS 服务器最多 2 个 }CGCONTRDP响应中可解析出网关、主备 DNS 与 MTU 等参数context.rs但因 MTU 字段未使用驱动仍按固定MTU 1500工作lib.rs。5.4 enable() / disable() 与 PDN 保持disable()CFUN0关闭调制解调器enable()CFUN1开启并追加%XPDNCFG1让调制解调器在 PDN 分离后保持存活context.rs。5.5 run()自动重附着控制循环runF: Fn(Status)(reattach)context.rs是示例中实际使用的核心方法流程为enable()激活调制解调器wait_attached()每秒轮询一次直到附着成功context.rsopen_raw_socket()打开 IP 裸 socket 并取得文件描述符底层消息0x7001_0004lib.rs调用set_link_state(LinkState::Up)通知网络栈链路可用并调用用户回调reattach(status)循环中每 10 秒检查附着状态一旦掉线则set_link_state(LinkState::Down)、关闭 socket、等待重新附着后重开 socket 并恢复链路。用户回调reattach在示例中用于把调制解调器分配的 IP 与默认路由写入 embassy-net 接口apply_statusmodem_tcp_client.rsfn apply_status(iface: Ifacestatic, status: Status) { let Some(IpAddr::V4(addr)) status.ip else { panic!(Unexpected IP address); }; unwrap!(iface.set_ip_addrs([IpCidr::V4(Ipv4Cidr::new(addr, 32))])); let stack iface.stack(); if let Some(IpAddr::V4(gateway)) status.gateway { unwrap!(stack.routes().add_default_ipv4_route(gateway, iface.handle())); } else { stack.routes().remove_default_ipv4_route(); } }六、Trace 通道调试蜂窝协议栈的利器new_with_trace()返回的TraceReader读取调制解调器通过共享内存上报的内部 trace。底层通过 IPC 事件通道 7 通知lib.rs调制解调器先上报TraceContext包含最多 3 个TraceChannellib.rs随后驱动轮询各通道的读写指针把新增数据按0xEF 0xBE 长度 通道ID的头部封装写入TraceWriterhandle_tracelib.rs。示例中trace_task把 trace 数据经 UARTBufferedUarteTx1M 波特率转发到调试串口modem_tcp_client.rs在调试蜂窝连接问题时可以直观观察调制解调器协议栈行为。七、特性开关与依赖crate 的 Cargo.toml 提供两个可选特性defmt启用 defmt 日志格式化同时打开heapless、embassy-time的 defmt 支持log启用标准log宏日志。依赖方面驱动基于nrf-paccrate 内部通过nrf_pac as pac访问寄存器、cortex-m、embassy-net-driver-channel设备通道抽象、embassy-syncpipe、waker、embassy-time定时器、heapless定长容器与at-commandsAT 命令构建/解析。示例工程 examples/nrf9160/Cargo.toml 中以features [defmt, nrf9160-s, time-driver-rtc1, gpiote, unstable-pac, time]使用embassy-nrf以[defmt, tcp, ipv4, medium-ip, icmp-ping-reply]使用embassy-net构建目标为thumbv8m.main-none-eabihf。八、互操作性与适用前提按照 README 的说明该驱动可运行在任何 executor上——它不直接绑定 embassy-executor 的特定平台实现仅依赖poll_fn、waker 注册等标准 async 原语。这为在 nRF91 上搭配其他运行时或自行封装的调度器使用提供了灵活性。需要特别说明的适用前提该驱动面向nRF91 系列示例与文档均以 nRF9160 为准依赖其特有的 IPC 外设、SPU 安全属性和 LTE 调制解调器固件共享内存必须满足 8KB 对齐、位于 RAM 低端 128KB 的约束链接脚本需按 memory.x 预留 IPC 区域应用核需运行在 TrustZone 安全侧示例使用nrf9160-s特性因为驱动需要访问SPU_S、POWER_S等安全侧外设来配置调制解调器与共享内存属性。九、小结embassy-net-nrf91通过IPC 共享内存 控制块/消息列表协议把 nRF91 蜂窝调制解调器封装为标准的embassy-net网络设备底层new()/new_with_trace()完成共享内存分配、SPU 安全配置与调制解调器启动Runner在后台轮询 IPC 事件与消息列表context::Control则把 APN 配置、PAP/CHAP 认证、附着与自动重附着封装成高层 API。从 modem_tcp_client.rs 出发配合 memory.x 的内存布局与 Cargo.toml 的特性配置即可快速搭建可复用的蜂窝网络应用。赞分享嵌入式物联网异步编程【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址https://gitcode.com/gh_mirrors/em/embassy点击查看免费下载相关推荐embassy-net-driver-channel 使用指南为 embassy-net 编写通道式网络驱动的完整实践embassy net driver channel 使用指南为 embassy net 编写通道式网络驱动的完整实践 embassy net driver嵌入式物联网异步编程embassy-net-driver-channel 演进全解用通道化接口驱动 embassy-net 异步网络栈embassy net driver channel 演进全解用通道化接口驱动 embassy net 异步网络栈 导读 embassy net driver嵌入式物联网异步编程embassy-net-esp-hosted 集成指南在 Embassy 异步框架中通过 SPI/SDIO 驱动 ESP-Hosted 网络协处理器embassy net esp hosted 集成指南在 Embassy 异步框架中通过 SPI/SDIO 驱动 ESP Hosted 网络协处理器 导读 e嵌入式物联网异步编程上一篇G-Helper 技术解析单文件实现轻量级华硕笔记本硬件控制的架构与原理下一篇如何为PDF添加真实扫描质感3分钟免费在线工具指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表