1. 简介

产品

nRF9151是支持NB-IoT和CAT-M的低功耗蜂窝LTE模组。支持OpenMCU开发,和Nordic其他产品线统一SDK。支持GPS定位。

nRF Cloud是部署在AWS上的物联网云。支持MQTT和HTTPS(REST)API。提供设备注册、设备管理、OTA、定位(基站定位、Wi-Fi定位、AGPS、PGPS)等服务。

nRF7002是一个2.4G/5G低功耗双频Wi-Fi6收发器,可以用来模拟nRF7000。nRF7000是一个只能scan,不能connect的wifi6双频收发器,主要用于做Wi-Fi定位。

本文内容

介绍如何搭建环境、编译nrf_cloud_multi_service工程。这是一个支持多个nRF Cloud服务的设备端工程。让nRF9151连接到nRF Cloud云端,并进行GPS定位、基站定位、Wi-Fi定位。

介绍如何进行Modem Trace抓包分析基站内容。

分析代码框架思路。

2. 硬件准备

  • nRF9151开发板(或其他91系列开发板)
  • NB-IoT物联网卡,这里是中国移动的。左边是开发板送的2张卡,在国内不好用。
  • (可选)nRF7002EK。

Image (2)

Image (3)

关于SIM卡:

  1. 9151DK赠送的两张卡(Onomondo和Wireless Logic)都是预先激活好的,不需要再次激活,并且带有一定的流量和使用期限。如果扫描二维码进行注册,可以获得更多的免费流量。
  2. Onomondo和Wireless Logic是虚拟运营商。他们会和全球的运营商合作,让自己的卡能够漫游到全球大部分地区,支持CAT-M和NB-IoT。具体覆盖范围可以去他们官网查询。国内在上海我试过Onomondo的NB IoT是可用的,Wireless Logic没有漫游成功。
  3. 使用国内的物联网卡,如中国移动的物联网卡时,注意是要支持NB-IoT的,并且注意国内的卡都会锁设备。也就是说新卡一但在一台设备上使用了,就不能换到其他设备上了。

3. 软件准备

安装开发环境与烧录工具

首先按照《nRF Connect SDK安装与入门》装好开发环境NCS v2.9.0,相关工具(nRF Connect for Desktop, nrfutil, Jlink驱动等)。

烧录Modem Firmware

9151官网页面下载最新版Modem Firmware (MFW)。

MFW是Modem专门的固件,是不开源的,以zip包的形式提供。Modem有独立的CPU和存储空间,不占用App Core的Flash和RAM。

可以用nRF Connect for Desktop里面的Programmer烧录。

image-20250424130251452

也可以用nrfutil命令行操作:

nrfutil self-upgrade

# 查看可安装的工具
nrfutil search

# 安装蜂窝系列模组工具
nrfutil install 91

# 烧录modem firmware
nrfutil 91 modem-firmware-upgrade --firmware ./project/mfw_nrf91x1_2.0.2.zip --all-jlinks

配置模拟开关

nRF9160无需此步骤。

目前比较新的开发板都使用Interface MCU来控制板子上的模拟开关,而非物理开关。通过nRF Connect for Desktop上的Board Configurator控制。

nRF9151DK上有两种方式可以连接LEDs和Buttons。一种是GPIO直连,一种是通过I2C接口的IO扩展器连接。

image-20250427124819121

如果你要使用7002EK扩展Wi-Fi功能,那么默认的LED就会占用7002要用的GPIO。因此,7002EK的DeviceTree文件会使能I2C接口的IO扩展器。

zephyr/boards/nordic/nrf9151dk/dts/nrf9151dk_leds_on_io_expander.dtsi

对应地就需要在开发板上把模拟开关切换到IO扩展器。使用nRF Connect for Desktop的Board Configurator来配置:

image-20250424134257883

配置完毕后点击左边写入。这里写入是把配置写入到板子上的Interface MCU(即Jlink DEBUGGER)中。

4. 修改、编译并烧录例程

通过copy a sample的形式复制并创建例程nRF Could Multi Service(v2.9.0/nrf/samples/cellular/nrf_cloud_multi_service)。

修改prj.conf,增加以下内容

# 移动物联网卡需要单独配置PDN
CONFIG_PDN=y
CONFIG_PDN_LEGACY_PCO=y

# 默认是NB+CATM+GPS,国内去掉CATM
CONFIG_LTE_NETWORK_MODE_NBIOT_GPS=y

# 设置连接nRF Cloud时,使用UUID作为本机ID
CONFIG_MODEM_JWT=y
CONFIG_NRF_CLOUD_CLIENT_ID_SRC_INTERNAL_UUID=y

创建编译目标。如果有7002ek,就增加以下参数。如果没有就不加:

image-20250427125146231

也可以用命令行编译:

west build -p \
-b nrf9151dk/nrf9151/ns \
-- \
-DSHIELD=nrf7002ek_nrf7000 \
-DSB_CONF_FILE="sysbuild_nrf700x-wifi-scan.conf" \
-DEXTRA_CONF_FILE="overlay-nrf7002ek-wifi-scan-only.conf"
  1. 这个CMake参数等价于在CMakeLists.txt的开头写set(SHIELD nrf7002ek_nrf7000),也就是会把7002ek这个扩展板的Kconfig配置和设备树overlay附加到当前工程的nrf9151板子上。
  2. 7002ek有很多变体(nrf7002eknrf7002ek_7001nrf7002ek_7000)。硬件上是2.4G/5GHz双频的nRF7002,通过这种变体配置来模拟7001(只有2.4GHz)和7000(只能scan不能连接)
[447/447] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 275888 B 384 KB 70.16%
RAM: 111608 B 215832 B 51.71%
IDT_LIST: 0 GB 32 KB 0.00%
Generating files from /home/jayant/OneDrive_Nordic/Customer/Meari/nrf_cloud_multi_service/build/nrf_cloud_multi_service/zephyr/zephyr.elf for board: nrf9151dk
image.py: sign the payload
image.py: sign the payload
[1/213] Preparing syscall dependency handling

[6/213] Generating include/generated/zephyr/version.h
-- Zephyr version: 3.7.99 (/home/jayant/project/ncs/v2.9.0/zephyr), build: v3.7.99-ncs2
[213/213] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 48296 B 81408 B 59.33%
RAM: 28944 B 32 KB 88.33%
IDT_LIST: 0 GB 32 KB 0.00%
Generating files from /home/jayant/OneDrive_Nordic/Customer/Meari/nrf_cloud_multi_service/build/mcuboot/zephyr/zephyr.elf for board: nrf9151dk
[20/20] Generating ../merged.hex

编译完成后可以看到应用和bootloader的占用。

然后烧录,使用nRF Connect for Desktop中的Serial Monitor查看串口:

image-20250427133846267

能观察到:

  • nRF Cloud的客户端ID是UUID,不是IMEI
  • 设备可以附着基站,可以连网。但是无法连接到nRF Cloud MQTT

5. nRF Cloud设备注册简介

设备注册

nRF Cloud是Nordic的物联网云服务器,设置在AWS上,有设备托管、OTA、位置定位等服务。开放MQTT和HTTP(REST)的接口。

此例程需要把设备注册(Provision)到nRF Cloud上。

所谓的设备注册,就是让云端把这台设备的ID等信息注册在在你的个人账户下。除此之外,设备端和云端还需要各自持有对方的TLS证书(公钥),这样才能进行双向的认证和加密。

这里,云端的证书就是AWS的根证书,而设备端的证书是X.509格式的设备证书。

证书文件

要阅读这部分,首先你需要了解非对称加密和TLS证书。

设备内部需要三个证书文件(credentials):

  • 服务器根证书(Root CA):这里是AWS根证书,用于确保自己连接的云服务器不是冒充的
  • 设备证书(Client certificate):预先给云服务器上传此证书。后续进行访问时,云服务器通过证书来验证设备是否真正是注册的设备。
  • 设备私钥(Client private key):通过密钥加密的数据,可以用设备证书携带的公钥解密。因此持有密钥的设备就是证书代表的设备。

这三个文件都是存储在Modem里面的,不占用Application Core的flash空间。

要管理modem里的证书,需要通过AT%CMNG命令来操作。可以写入云服务器的证书、自动生成证书和私钥等。

%CMNG=<opcode>[,<sec_tag>[,<type>[,<content>[,<passwd>]]]]

这里<opcode>代表写、读等操作。

<sec_tag>很重要,代表的是一组证书的句柄标签,范围是0–2147483647。例如,nRF Cloud的句柄就是16842753。当代码中通过网络访问nRF Cloud时,就通过16842753这个sec tag来获取到对应的证书,进行TLS通讯。

<type>代表这个密钥文件的类型:

  • 0 – Root CA certificate (ASCII text).
  • 1 – Client certificate (ASCII text).
  • 2 – Client private key (ASCII text).
  • ……

同一个sec_tag之下,可以存一组证书文件。这里指的就是不同的<type>。这里我们连接nRF Cloud,就需要0, 1, 2这三种整数文件。

某些云平台会直接把这三个证书文件都生成好,让你把服务器根证书、设备证书和设备私钥直接烧录到设备中。这种方式有暴露设备私钥的风险(毕竟经历了云端,下载到PC,再烧录到设备)。

nRF91系列可以直接在设备上生成私钥,使用AT%KEYGEN命令生成。私钥生成后无法读出,可以调用API使用这个私钥来进行加密、解密,但是绝对无法读取到私钥本身。而设备证书是可以读取的。

更多参数详见:https://docs.nordicsemi.com/bundle/ref_at_commands_nrf91x1/page/REF/at_commands/security/cmng.html

所有证书文件操作时都必须先关闭网络(AT+CFUN=4

nRF Cloud注册方式

通过前面的介绍,我们知道配网需要完成2件事:

  1. 把服务器的根证书存储到设备上
  2. 把设备的证书上传到服务器,并且确保设备中有该证书对应的私钥

nRF Cloud提供3种注册方式:

1. 连接前注册(Preconnect onboarding)

设备在生产完成后,还没有连网,就注册到云端。

可以用电脑手动给云端上传一个csv表格,里面包含设备的ID和证书公钥等信息。适合设备在生产时阶段就批量注册到云端的情况。

其中,证书的来源又有2种:

  • AT%KEYGEN让设备自动生成证书与私钥,再把证书记录到表格中(安全)
  • 先用电脑生成好设备证书和私钥,然后烧录到设备中(可能不安全)

前者对应文档:Onboarding a device without the nRF Cloud Provisioning Service;

后者对应文档:Onboarding with hard-coded device credentials

2. 部署时注册(Auto-onboarding)

设备在生产完成后不注册到云端。而是等到现场部署时,再通过现场的人员操作进行证书生成、云端注册。

这需要在编译时,就在代码中开启Provisionning Service相关的配置。

本文不介绍这种方式,可以参考官方文档:https://docs.nordicsemi.com/bundle/ncs-2.9.0/page/nrf/samples/cellular/nrf_cloud_multi_service/README.html#provisioning_with_the_nrf_cloud_provisioning_service

3. JITP(Just-in-Time provisioning)

用于开发板评估的快速注册。由于开发板生产时都烧录好了证书,nRF Cloud也已经存储了这些证书。只需要扫描二维码就能把这个开发板对应的证书绑定到你的nRF Cloud账号里。

但是如果你已经重新烧了modem firmware,或者是量产的产品,则不能使用这种方式。

https://docs.nordicsemi.com/bundle/nrf-cloud/page/Devices/Associations/Onboarding.html#jitp

6. 将设备注册到nRF Cloud

注册nRF Cloud账号并获取API Key

image-20221205005305505

注册登录后,右上角进入User Account界面

image-20250427165923803

image-20250427170019787

记录自己的API Key。

注意:API Key非常重要,可以通过API Key控制你账号下的所有设备、所有权限、调用付费API等。千万不能泄露!!!

如果你怀疑API Key泄露,可以点击右边的Regenerate API Key重新生成。

安装nRF Cloud Utils

需要python3环境。

可以直接用pip安装:

pip3 install nrfcloud-utils

也可以直接clone仓库:https://github.com/nRFCloud/utils/

如果是pip安装,则直接执行命令,如create_ca_cert

如果是clone方式安装,则进入src /nrfcloud_utils/,然后执行脚本,如create_ca_cert.py

推荐以pip安装的方式进行

设置临时环境变量

在命令行中设置临时环境变量,方便后面执行脚本

# Linux
export API_KEY=<your_api_key>
# Windows Powershell
$env:API_KEY=<your_api_key>

生成自签CA证书

生成一个你自己签名的根证书,私钥由你持有。后续所有的设备证书都由这个CA证书来签发。

create_ca_cert \
-c CN \
-l Shanghai \
-o "Nordic Semiconductor K.K." \
--ou "Sales" \
--cn nordic.cn \
-e jayant.tang@nordicsemi.no \
-p ./my_ca \
-f "Jayant-"

参数解释可查看create_ca_cert --help。证书会生成到-p参数指定的目录下,这里是/my_ca

image-20250427172427929

文件分别为:证书、私钥、公钥。

其中证书内包含公钥。

给设备签发设备证书

让设备生成公私钥,利用自签CA给设备签发设备证书,并把设备证书保存到csv表格中。

device_credentials_installer \
-d \
-t "jayant-DK" \
--ca ./my_ca/Jayant-0x112b0b2f2db47ba510eb57430f349de6f76d882_ca.pem \
--ca-key ./my_ca/Jayant-0x112b0b2f2db47ba510eb57430f349de6f76d882_prv.pem \
-a \
--devinfo-append \
--csv ./jayant_provision.csv \
--devinfo ./jayant_devinfo.csv \
--term CRLF \
--port /dev/ttyACM0
  • -d:安装前先从Modem中删除sectag
  • -t:用于设备分组管理的标签,是一个字符串
  • -T:设置自定义的子类型,如温湿度传感器等,是一个字符串。此处未设置
  • --ca:CA证书文件的路径
  • --ca-key:CA证书私钥的路径(prv)
  • -a--append:保存设备注册信息到csv表格文件时,向末尾增加新的条目,而不是覆盖csv文件(这个选项是确保你可以重复执行脚本,搜集全部设备信息的基础)
  • --devinfo-append:保存设备信息到csv表格文件时,向末尾增加新的条目,而不是覆盖csv文件(这个选项是确保你可以重复执行脚本,搜集全部设备信息的基础)
  • --csv:用于存储设备注册信息的CSV表格的文件名,若文件不存在则创建。若文件存在,则根据-a选项,向文件中添加新条目。(存储UUID、前缀、固件等信息)
  • --devinfo:用于存储设备信息的CSV表格的文件名,若文件不存在则创建。若文件存在,则根据-a选项,向文件中添加新条目。(存储UUID、Modem固件版本、芯片IMEI等信息)
  • --term:AT指令的结束符(NULL,CRLF,CRLF
  • --port:指定AT指令串口(windows上是COM口,Linux上是/dev/tty*)

image-20250427173557275

公私钥生成后,脚本自动读取了设备信息并将其保存到了csv表格中。

由于添加了-a--devinfo-append,如果你有多个设备要注册,这些信息会追加添加到表格中。

注册时,最大支持一次性注册3000台设备。

这种方式是用AT%KEYGEN自动生成私钥的,比较安全。

如果你想用前面提到的“先用电脑生成设备私钥,再烧录进去”的方式。可以参考:create_device_credentials命令来进行生成。然后用AT%CMNG命令烧录三个证书文件(服务器CA证书、设备证书、设备私钥)。烧录前记得AT+CFUN=4关闭网络。AWS IoT CA证书在这里下载(https://www.amazontrust.com/repository/AmazonRootCA1.pem)

把注册信息上传到云端

可以继续使用命令

nrf_cloud_onboard --api-key $API_KEY --csv jayant_provision.csv

利用前面获得的API_KEY来上传csv表格,注册设备。

也可以直接在网页端操作:

image-20250427175008402

选择批量注册:

image-20250427175032694

然后把jayant_provision.csv表格拖进去就可以注册了。

检查连网情况

由于安装证书时,设备被设置为飞行模式。这里打开串口,再reset一下。

image-20250427175858824

可以看到设备成功连网并访问到nRF Cloud.

7. 查看定位

我们进入nRF Cloud会发现设备已经上线:

image-20250427180107690

点击ID进入设备详情页:

image-20250427180227357

可以看到,设备通过MQTT已经上传了经纬度。同时,网页通过谷歌地图渲染了这个经纬度的地点(需要翻墙代理才能看到)。

并且根据颜色不同,有单基站、多基站、Wi-Fi和GPS定位:

image-20250427180437793

我这里在室内,所以GPS定位失败。但是Wi-Fi定位还是很准的。

8. Modem Trace抓包

本节介绍如何进行Modem Trace抓包。Modem Trace就是让nRF9151和基站之间交互的数据包以二进制格式通过串口等方式输出,方便进行抓包调试。

要使能Modem Trace,需要打开一些config和进行一些设备树配置。Zephyr中支持通过snippets的形式提供一组config和设备树配置,这样就不用一个一个手动添加了。

image-20250430161938500

在Build Configuration中添加nrf91-modem-trace-uart即可。

如果是命令行编译的方法,就添加-D<image_name>_SNIPPET="nrf91-modem-trace-uart"参数,如:

west build -p \
-b nrf9151dk/nrf9151/ns \
-- \
-DSHIELD=nrf7002ek_nrf7000 \
-DSB_CONF_FILE="sysbuild_nrf700x-wifi-scan.conf" \
-DEXTRA_CONF_FILE="overlay-nrf7002ek-wifi-scan-only.conf" \
-Dnrf_cloud_multi_service_SNIPPET="nrf91-modem-trace-uart"

如果你想查看snippets具体添加了哪些配置,可以查看Build信息:

image-20250430163301076

可以看到使用的是串口1,波特率1M:

image-20250430163326698

编译完毕后重新烧录固件

把9151DK通过USB连接到电脑。在nRF Connect for Desktop中打开Cellular Monitor:

image-20250430163535426

image-20250430170545090

nRF9151的UART0和UART1,在板子上直接连到Jink USB的,因此无需再接串口。

这里一个串口是日志输出和AT Commands通道,另一个是Modem Trace,注意不要选错。Modem Trace database选择你下载的Modem Firmware的版本。

nRF Connect下的所有软件可以共享串口,所以你可以同时打开“Open Serial Terminal”,查看日志和AT Commands.

之后点击Start就可以开始抓包:

image-20250430171642316

左下角为抓包文件存储位置。

你也可以在Start之前,打开“Open in Wireshark”:

9. 代码解析