[资料] 开发你的第一个nRF Connect SDK(NCS)/Zephy 应用程序
2826 查看
0 回复
 楼主 | 发布于 2022-04-07 | 只看楼主
分享到:

Nordic有2套并存的SDK:老的nRF5 SDK和新的nRF Connect SDK(简称NCS),两套SDK相互独立,大家选择其中一套进行开发即可。一般而言,如果你选择的芯片是nRF51或者nRF52系列,那么推荐使用nRF5 SDK。如果你选择的是Nordic最新产品系列,比如nRF53或者nRF9160,那么请选择nRF Connect SDK。还有一种特殊情况,虽然你选择的是nRF52芯片,但需要使用最新的一些射频技术,比如蓝牙测向,蓝牙Mesh v1.1,低功耗蓝牙音频,那么也需要使用nRF Connect SDK,换句话说,最新的射频技术,Nordic都只会在NCS上进行开发,而nRF5 SDK将进入维护阶段不再增加新的特性(如发现bug,会对其进行修复的)。关于nRF5 SDK的介绍,请参考这篇博文之前的博文,基本上都是基于nRF5 SDK进行阐述的,尤其是这篇:https://www.cnblogs.com/iini/p/9043565.html,详细讲解了nRF5 SDK开发环境的搭建,这里不再赘述。下面将只对NCS SDK进行阐述,以期让大家快速了解这个Nordic最新SDK,并尽快熟悉和上手。

 

 目录

1. nRF Connect SDK介绍... 3

2. nRF Connect SDK安装... 4

2.1 手动安装(直接访问GitHub网站,国内速度比较慢)... 4

2.2 通过nRF Connect桌面版直接GitHub下载... 4

2.3 百度网盘下载(仅适用于Windows... 7

2.4 Gitee镜像安装... 7

2.5 nRF command line tools安装... 11

3. nRF Connect SDK开发环境说明... 12

3.1 SES开发环境搭建... 12

3.1.1 nRF52项目示例... 19

3.1.2 nRF53项目示例... 20

3.1.3 nRF9160项目示例... 23

3.1.4自定义项目装载示例... 24

3.2 命令行方式开发环境搭建... 27

3.2.1 nRF52项目示例... 28

3.2.2 nRF53项目示例... 31

3.2.3 nRF9160项目示例... 32

3.3 Visual Studio Code开发环境搭建... 32

3.4 nRF Connect SDK版本说明... 41

4. nRF Connect SDK项目配置或选项... 42

4.1 nRF Connect SDK项目配置介绍(autoconf.h devicetree_unfixed.h... 42

4.1.1 Kconfig (Kconfig, prj.confautoconf.h) 46

4.1.2 Device Tree( *.dts, *.overlaydevicetree_unfixed.h) 51

4.1.3 配置程序起始地址和大小... 56

4.2 图形化查看Kconfig选项... 57

4.2.1 SES.. 57

4.2.2 west 59

4.2.3 VS Code. 61

5. nRF Connect SDK中几个比较重要的目录... 63

5.1 例子目录... 63

5.2 API目录... 66

5.3 板子定义目录... 69

6. 开发你的第一个NCS程序... 72

6.1 修改hello_world main.c文件... 72

6.2 在项目中添加一个新文件blinky.c. 73

6.3 修改blinky.c文件... 75

6.4 编译和运行你的第一个hello_world程序... 76

6.5 使用prj.conf<board>.overlay配置项目... 77

7. 开发你的第一个multi-image(image)应用... 81

7.1 分区管理(Partition Manager... 81

7.2 imagehello_world程序开发... 89

8. NCS编译系统几个要注意的点... 95

8.1 NCS几个重要的编译系统变量... 95

8.2 conf文件命名规则及编译顺序... 98

8.3 overlay文件命名规则及编译顺序... 99

  

1. nRF Connect SDK介绍

nRF Connect SDK,简称NCS,是Nordic最新的SDK平台,该平台将支持Nordic所有产品线,包括低功耗蓝牙,蜂窝网,WiFi,GPS,2.4G,蓝牙Mesh,Zigbee,Thread,Matter, Homekit, FindMy等,换句话说,由于短距离无线网络和长距离无线网络共用同一个SDK,将使得你同时具备两种网络的开发经验,因为他们的框架是一样的,驱动是一样的,网络协议栈的编写风格也是相仿的。只要你熟悉了其中一种网络的开发,那么相关的经验可以迅速复制到新网络平台上。

nRF Connect SDK内嵌Zephyr RTOS,并沿用了Zephyr project的编译系统。Zephyr Project是Linux基金会推出的一个Apache2.0开源项目,版权非常友好,适合用于商业项目开发。Zephyr Project是一个合作社区,其产品就是Zephyr,具体包括Zephyr RTOS,Zephyr组件以及Zephyr编译系统等。Zephyr很多地方都模拟了Linux,比如使用了DeviceTree和Kconfig,对Linux很熟的同学,阅读Zephyr代码会感到很亲切的。经常有人问:为什么NCS要使用Zephyr RTOS?其实答案就蕴含在Zephyr Project的愿景中:The Zephyr™ Project strives to deliver the best-in-class RTOS for connected resource-constrained devices, built be secure and safe。Zephyr的愿景跟Nordic的产品策略高度吻合,这也是为什么Nordic要选Zephyr的主要原因。NCS SDK和Zephyr Project两者最大的区别有3个:一是owner不同,NCS SDK由Nordic负责,Zephyr SDK由Linux基金会负责。NCS开发中碰到的所有问题,Nordic都将负责解决。二是产品管理不一样,NCS SDK将由Nordic完成所有相关测试和考核,并符合Nordic产品开发,测试,发布和质量控制流程,因此NCS有自己的版本,并跟Zephyr版本控制解耦。三是NCS具有很多增强特性,比如Nordic特有的蓝牙链路层等。所以,从产品角度看,NCS SDK和Zephyr SDK是两套完全不同的SDK。但是,如果从代码角度看,那么NCS SDK和Zephyr SDK又基本上是一样的。在本文章的下面部分,在不引起混淆的情况下,经常会把NCS和Zephyr两个概念换着使用,因为本质上他们是一个东西。

NCS使用了CMake编译系统,并使用了大量Python脚本以辅助生成一些头文件,代码和hex,这些都大大增加开发的可移植性和便利性。

NCS SDK放在GitHub上,里面包含多个仓库,其主仓库(Manifest)是nrf,同时还包含Zephyr,MCUBoot,mbedtls,nrfxlib等其他仓库。NCS SDK可以同时在Windows,macOS和Linux上运行。

由于Zephyr和NCS的build系统是一样的,如果你正在学习Zephyr RTOS,那么也可以参考本篇文章,从NCS SDK开始学习Zephyr。由于NCS SDK新增了很多特性,比如图形化的DEBUG,丰富的例程,你会发现从NCS SDK学习Zephyr是一条便捷通道。

2. nRF Connect SDK安装

NCS开发包比较大,目前大概4G(后续有可能会更大),受限于网络带宽,不能像nRF5 SDK那样,直接提供一个压缩包进行下载,为此我们提供如下几种安装方案,大家根据自己的情况选择一种适合自己的安装方式

2.1 手动安装(直接访问GitHub网站,国内速度比较慢)

这个是Nordic官网的安装方式,需要直接访问GitHub,请大家按照:https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/gs_installing.html里面的说明来安装。这种手动安装的好处是:你可以实时跟踪Nordic的发布,除了第一次安装比较费时费力,后面更新非常方便,速度也很快,大家可以使用如下命令跟GitHub上的资源同步:

git fetch origin
git checkout origin/master 或者 git checkout origin/main west update

注意:如果你没有VPN或者半夜起来安装的话,有可能最后安装会失败。

2.2 通过nRF Connect桌面版直接GitHub下载

首先,下载桌面版nRF connect (同时支持Windows/macOS/Linux平台)。下载链接为https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop/Download#infotabs,选择你的平台和版本。

 

 桌面版nRF connect安装成功后,将如下所示:

  

 在nRF connect桌面版中有2个app都可以完成NCS SDK安装,一个是Toolchain Manager,一个是Getting Started Assistant。Windows和Mac用户使用Toolchain manager方式,Linux用户使用Getting Started Assistant方式(Getting Started Assistant方式其实就是2.1的手动安装方式)。下面将会以Toolchain Manager的方式(支持Windows和macOS)来讲解安装过程。 

首先install Toolchain Manager,然后open,进入settings界面,选择安装目录,如下:

  

 然后重新选择SDK ENVIRONMENTS页面,并选择SDK相应版本进行安装,如下所示:

  

根据网速不同,这个过程持续时间变化很大,我这边网络大概需要20分钟完成所有下载和安装(有的人可能需要一天一夜),安装成功后你将得到如下目录内容:

     

跟2.1相比,这种方案把工具链打包好,不需要你一个一个去下载和安装,节省了不少精力,但是它也访问GitHub网站,所以会碰到跟2.1一样的问题:如果你没有VPN或者半夜起来安装的话,有可能最后安装会失败。 

2.3 百度网盘下载(仅适用于Windows)

我们把2.2安装好的SDK上传到百度网盘,这样大家直接通过百度网盘就可以下载成功了,百度网盘链接:

进入目录“开发你的第一个NCS(Zephyr)应用程序”,选择相应的版本(推荐使用最新版本),直接把相应的压缩包下载下来并解压到本地目录,SDK即算安装完成。注意:这个压缩包只能在Windows系统上运行,不能在Linux和macOS上运行。 

2.4 Gitee镜像安装

如果你对Git毫无概念,建议你参照2.3来安装;反之,如果你有Git相关的常识,那么请往下看,这种方法速度非常快,我们下面会详细介绍它的所有步骤。

工具链安装

NCS会用到这些工具:cmake ninja gperf python git dtc-msys2 west,这些工具都是一些常用的工具,大家可以自己去网上下载安装,下面介绍通过choco第三方安装工具来自动安装上面这些工具

  • 安装Choco
    • 以管理员权限打开Windows PowerShell,然后连续输入如下两个命令
      Set-ExecutionPolicy AllSigned
      Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

      等待几分钟,安装即算完成,你可以输入choco来确认是否安装成功了

  • 通过Choco安装工具
    • 以管理员权限打开CMD,然后输入如下命令:
      choco feature enable -n allowGlobalConfirmation
      choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System'
      choco install ninja gperf python git dtc-msys2
    • 说明一下:这里安装的工具都是最新版,比如Python,肯定是最新版的Python,使用最新版的Python,nRF Connect SDK编译的时候会少一些模块,大家通过PIP安装把他们补上就行了。当然大家也可以指定版本安装,以保证最好的兼容性,choco指定版本安装示例如下所示
      choco install python --version=3.9.6
    • 如果你需要开发Matter应用,你还需要安装GN工具,否则不需要。大家自己上网找一下GN工具,这里就不讲了。
  • 通过Python安装west,在CMD输入如下命令
    pip3 install west
  • 安装GNU ARM工具链
  • 下载IDE,NCS支持三种IDE,三选其一即可。
  • 下载nRF command line tools,见下面的2.5
  • 至此整个工具链准备完成,我们下面进入SDK下载和安装过程

SDK下载和安装

打开CMD,进入到一个你想安装NCS的目录,输入如下命令:

west init -m https://gitee.com/minhua_ai/sdk-nrf --mr gitee_mirror

下载成功后,将会有一个nrf目录,进入:

cd nrf

然后输入如下命令:

west update

返回到根目录:

cd ..

执行一些Python插件脚本,即输入如下指令:

pip3 install -r zephyr/scripts/requirements.txt
pip3 install -r nrf/scripts/requirements.txt
pip3 install -r bootloader/mcuboot/scripts/requirements.txt

至此nRF Connect SDK安装完成。

开发的时候,我们一般选择最新的tag(nrf仓库)进行开发,大家输入如下指令查看目前已发布的tag:

cd nrf
git tag

然后选择最新的tag,比如v1.9.1,执行如下操作:

git checkout v1.9.1
west update

如果要使用其他tag,还是上面两步操作,即先checkout某个tag,然后west update,只有west update成功了,才能说明你切换成功,否则就有问题。

注意:通过这种镜像安装的方式,master或者main分支我没有同步下来,大家只能以某个tag为基准来进行开发,实际上大家也必须以tag为基准来开发,因为只有tag版本才会做考核和认证,它的质量才会有保证。

2.5 nRF command line tools安装

下载完SDK压缩包,再下载“nRF-Command-Line-Tools_x_x_x_Installer.exe”,即nRF command line tools,nRF command line tools包括Jlink驱动以及Nordic自己开发的一些命令行工具,如nrfjprog,nrfutil以及mergehex等,这些工具对开发非常有帮助。nRF command line tools下载链接为:https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Command-Line-Tools/Download#infotabs

   

大家也可以去上面的百度网盘下载nRF command line tools。  

3. nRF Connect SDK开发环境说明

NCS支持的工具链有3套:一套是Visual studio code,一套是SEGGER Embedded Studio,简称SES,图形化的IDE,一套是命令行方式(沿用了Zephyr工具链),其实就是GCC工具链。三套工具链选其一即可,我本人三套工具链都在使用,因为每个都有自己的优势。下面将分别对这三种开发环境进行介绍:

3.1 SES开发环境搭建

通过百度网盘或者Toolchain Manager安装的SDK,必须进入如下目录:install folder\toolchain,并双击SEGGER Embedded Studio.cmd以打开SES,而不能在其他地方直接打开SES,如下所示:

  

SES启动成功后,进入Tools->Options,我们需要先对IDE进行配置。

  

按如下方式进行配置:

  

如果上面方式有问题,请按如下方式进行配置:

  

如果你的GNU工具链是手动安装的,上面的Toolchain目录请使用你自己的安装目录,比如C:\gnuarmemb,Zephyr base就是你Zephyr仓库的目录。

配置好之后,我们就可以打开一个工程进行编译了。进入File->Open nRF Connect SDK Project

  

在NCS中,项目是通过CMakeLists.txt表示的,如下:

  

而开发板一般是在如下目录的:

 

选择开发板,就是选择芯片,除此之外,开发板还规定了芯片的一些外设使能情况,以及一些基本外围电路连接情况,这个跟Keil如下选择Device的操作是异曲同工的,而且NCS这种做法更灵活,功能也更多,扩展性也更好。

  

如下为hello_world项目的装载图:

  

注意,当大家装载一个SES项目的时候,有时候会报错,比如如下界面:

 这个时候,你需要点击上面的“OK”,然后从下面窗口找到错误提示。

 

下面将对nRF52/nRF9160/nRF53项目,以及客户自定义项目,分别进行阐述(其实每个项目的操作基本上一模一样,大家看其中一个例子就可以了),以详细说明如何装载一个项目,编译一个项目,下载一个项目和debug一个项目。

3.1.1 nRF52项目示例

以nrf\applications\nrf_desktop项目为例,开发板选择支持LLPM的gaming mouse:nrf52840gmouse_nrf52840

  

装载成功后,编译,如下所示:

  

然后下载,如下所示:

  

如果要Debug,按如下界面进入:

  

3.1.2 nRF53项目示例

Nordic第一个支持nRF53的量产tag是v1.6.0,如果你需要开发nRF53,请使用v1.6.0及以上版本。

由于nRF53包括两个核:app核和network核,app核用来运行应用程序,network核用来运行射频协议栈,所以每次下载的时候需要同时把两个核的hex都下载进去,但是SES只支持一次下载一个核(west可以同时下载两个核,具体请参考3.2)。在使用SES开发nRF53的时候,项目装载/编译/下载/debug跟其他芯片是一样的,但这些操作只是针对一个核(一般来说都是app核),此时如果还需要network核配合的话,那么需要你手动先把network核的image下载进去,这个可以通过nrfjprog或者west来完成。这里要强调一点的是,当你选择一个蓝牙项目,这个项目默认会同时把app核和network核对应的工程都进行编译,然后生成各自的hex文件,然后你通过nrfjprog或者west把network核的image下载进去,通过SES本身的Target->Download菜单把app核的image下载进去。

以nrf\samples\bluetooth\peripheral_uart蓝牙透传项目为例:

  

编译如下所示:

  

编译成功后,先下载network核里面的image,我们以west flash来下载。进入目录:nrf\samples\bluetooth\peripheral_uart\build_nrf5340dk_nrf5340_cpuapp\hci_rpmsg,然后在cmd输入:

west flash

即可将image下载到network核里面,如下所示:

  

如果west flash在你的环境跑不通,那么你可以直接使用nrfjprog来下载network核的image,进入目录:nrf\samples\bluetooth\peripheral_uart\build_nrf5340dk_nrf5340_cpuapp\hci_rpmsg,然后在cmd输入如下命令:

nrfjprog -f NRF53 --coprocessor CP_NETWORK --sectorerase --program merged_CPUNET.hex --verify

然后下载app核image,app核image可以直接通过SES下载,如下:

  

如需debug,按如下界面进入:

  

3.1.3 nRF9160项目示例

以nrf\samples\nrf9160\mqtt_simple项目为例:

  

编译如下所示:

  

下载如下所示:

  

如需debug,按如下界面进行:

  

3.1.4自定义项目装载示例

如果是你的自定义项目或者Zephyr项目或者不使用toolchain自带的SES,此时就不能使用SES自带的快捷方式来装载项目,而需要自己去相应的目录找到项目工程和开发板,比如我们装载Zephyr的blinky例子。先选择项目工程CMakeLists.txt所在的目录,如下:

  

再选择相应的开发板,如下:

  

然后项目就装载成功了,如下:

  

后面编译,下载和调试,和之前一样,就不再赘述。

3.2 命令行方式开发环境搭建

NCS或者Zephyr项目命令行编译的时候,一般使用west命令,west语法可以通过west –help获得,如下: 

west –help

 

如果你能看到上面的界面,那么你的环境基本上就没问题了。

一个典型的west命令如下所示:

west build -b nrf52840dk_nrf52840

注:-b指定开发板,这里使用nRF52840开发板

通过百度网盘或者Toolchain Manager安装的SDK,必须进入如下目录:install folder\toolchain,并双击git-cmd.cmd以打开命令行,而不能直接打开CMD,如下所示:

  

git-cmd.cmd会自动把环境变量设好,否则后续编译会有问题。

下面分别以nRF52/nRF53/nRF9160开发为例,展示相应的编译和下载命令(其实每个项目的操作基本上一模一样,大家看其中一个例子就可以了)。

3.2.1 nRF52项目示例

下面以编译nrf\applications\nrf_desktop项目为例来讲解。首先进入项目所在的目录:

cd nrf\applications\nrf_desktop

界面将如下所示:

 

由于已经进入了项目所在的目录,编译的时候就无需再指定项目目录了,由于nrf_desktop支持多个开发板,我们还需要指定用哪一个开发板,如下所示:

west build -b nrf52840gmouse_nrf52840

或者

west build -b nrf52840gmouse_nrf52840 -p

注:-p用来清除build目录缓存

由于上面没有指定build目录,上面的命令会自动生成一个build目录(名字就是build),然后所有的编译文件会自动放在该build目录下。

  

build目录如下所示:

  

  

编译成功后,就可以烧写了,使用如下命令进行烧写:

west flash

或者

west flash –erase

  

3.2.2 nRF53项目示例

以nrf\samples\bluetooth\peripheral_uart为例来讲解。首先进入目录:nrf\samples\bluetooth\peripheral_uart,然后输入如下编译命令:

west build -b nrf5340dk_nrf5340_cpuapp -d build_nrf5340dk_nrf5340_cpuapp -p

注:-b指定开发板,-d指定编译目录,-p清除老的编译目录内容

 

编译成功后,就可以烧写了,使用如下命令进行烧写:

west flash -d build_nrf5340dk_nrf5340_cpuapp

  

3.2.3 nRF9160项目示例

以nrf\samples\nrf9160\mqtt_simple为例来讲解。首先进入目录:nrf\samples\nrf9160\mqtt_simple,然后输入如下编译命令:

west build -b nrf9160dk_nrf9160ns -d build_nrf9160dk_nrf9160ns -p

注:-b指定开发板,-d指定编译目录,-p清除老的编译目录内容

 

编译成功后,就可以烧写了,使用如下命令进行烧写:

west flash -d build_nrf9160dk_nrf9160ns

  

3.3 Visual Studio Code开发环境搭建

打开VS Code,首先确认如下nrf扩展都安装好了:

 

 点击如下图标,进入nRF Connect for VS Code,并进入工具配置界面:

把NCS根目录选择好(通过下拉“Browse”选择),然后把工具链所在目录设置好(通过下拉“Browse”选择):

 注意:如果你的GNU工具链是自己手动安装的,那么上面的nRF Connect Toolchain请选择“PATH”,即Windows环境变量。

  然后打开项目

 我们现在打开:nrf\samples\bluetooth\peripheral_uart:

 然后选择板子,比如nrf5340dk_nrf5340_cpuapp,如需debug代码,勾上“Enable debug options”,然后“Build Configuration”。

 点击右下角的“show”,可以看到编译过程日志。(如果编译有问题,请仔细看里面的错误提示)

编译成功后,点击“flash”进行烧写:

点击上面的“Debug”,进入debug模式,如下:

 

 这里介绍另一个扩展件:nRF Terminal,就是Nordic在VS code里面开发的一个终端,该终端可以同时支持串口助手和RTT view功能,这样大家就不需要下载额外的串口助手或者RTT view,而且它又是跨Windows/MacOS/Linux,也算是一个便民工具吧。

nRF Terminal - 串口助手

在“connected devices”窗口,选择一个串口,比如VCOM2,然后选择波特率,比如115200 8n1:

上述操作后,nRF terminal就变成一个串口助手了。如需断开串口,点击左下角的串口名: 

nRF Terminal - RTT Viewer

在“connected devices”窗口,点击“Connect to RTT”按钮,选择“Automatic search”,

在跳出的对话框中选择你的j-link目标设备,如下:

随后nRF Terminal窗口将变成如下这样,此时就相当于RTT Viewer的功能。如需断开RTT,点击左下角的“RTT Connected”。

 

3.4 nRF Connect SDK版本说明

nRF Connect SDK开发包目录如下所示:

  

可以看出,NCS包含nrf,zephyr,bootloader,nrfxlib,modules等多个仓库,其中nrf仓库就是NCS的主仓库(manifest),nrf仓库的版本就是NCS的版本,所以要查看NCS SDK当前版本号,进入nrf目录,输入

git branch

或者

git show

如下:

  

熟悉git的同学都知道,一个SDK如果包含多个仓库,那么每个仓库都有自己的版本,而且仓库版本之间是相互关联的,以NCS SDK为例,当nrf版本切换为v1.4.0时,其他仓库的版本也需要做相应切换,那么对应nrf v1.4.0的Zephyr仓库版本是多少呢?mcuboot仓库版本是多少呢?如果直接使用git工具,那么你需要一个一个记,然后一个一个checkout。在NCS或者Zephyr中,引入了west工具,这样通过管理nrf仓库版本就可以间接管理其他仓库的版本,比如我们现在把NCS SDK版本切换到v1.4.0,指令如下所示:

git checkout v1.4.0

然后通过west update命令,就可以让其他仓库版本自动跟v1.4.0 nrf仓库同步,这样你不需要记住或者管理其他仓库版本,只需管理nrf仓库版本即可。

west update

  

4. nRF Connect SDK项目配置或选项

4.1 nRF Connect SDK项目配置介绍(autoconf.h 和devicetree_unfixed.h)

每一个NCS或者Zephyr项目都包含了非常多的配置项或选项,总数有可能达几千项之多,然而绝大多数配置项我们都不需要去管他们,只需要使用默认值即可,所以开发一个简单的NCS应用程序,有可能我们不需做任何配置,就可以跑起来。当你开发复杂的应用程序的时候,你又会体会到NCS配置的灵活性和方便性了,这其实也是NCS一个很大的优势。本章我们先讲如何查看配置项,后面一章我们再以例子来说明如何更改配置项。NCS或者Zephyr里面主要包含两种配置项:Kconfig和DeviceTree,Kconfig主要负责软件的配置,DeviceTree主要负责板子硬件的配置。两者最终都会生成一个.h文件,其中Kconfig生成的头文件为:autoconf.h,而DeviceTree生成的头文件为devicetree_unfixed.h他们都在如下目录:

  

autoconf.h文件示例如下:

  

devicetree_unfixed.h文件示例如下:

  

如果大家开发过nRF5 SDK的话,一定记得里面有个:sdk_config.h,从机理上,sdk_config.h和上面的autoconf.h/devicetree_unfixed.h并没有本质区别,他们都是完成同样的功能。但是很多人都觉得Kconfig和DeviceTree很复杂,其实他们复杂之处在于如何生成这两个头文件,但是头文件本身并不复杂。在nRF5 SDK中,用户可以直接修改sdk_config.h文件,由于sdk_config.h文件太大,所以Nordic使用了CMSIS_Configuration_Wizard来格式化这个头文件,以形成如下的图形化界面方便大家去修改它:

  

在NCS或者Zephyr里面,由于autoconf.h/devicetree_unfixed.h是由Python脚本自动生成的,所以用户不能直接修改autoconf.h/devicetree_unfixed.h这两个头文件。用户只能去修改生成这两个头文件的输入,以达到间接修改他们的目的。这样做的好处是:更灵活,而且不容易出错(Python会自动帮你找出配置不合理的地方或者语法错误)。可以说,一旦你熟悉了这套配置机制,你就会爱上它。

下面分别对autoconf.h和devicetree_unfixed.h两者的生成过程进行阐述。

4.1.1 Kconfig (Kconfig, prj.conf及autoconf.h)

我们先把生成autoconf.h文件的整体流程图贴出来,后面再对这个图进行解释:

 

autoconf.h文件是由许许多多的Kconfig文件生成的(注:其实Kconfig来源于Linux系统,NCS或者Zephyr对其进行了继承和定制),基本上每个模块都有自己的Kconfig文件,比如蓝牙controller模块:

  

使用文本编辑器打开Kconfig,你将会看到它的内容大概如下所示:

  

除了系统模块可以定义Kconfig文件,你自己的项目模块也可以定义自己的Kconfig文件,如何定义?依葫芦画瓢,仿照例子来即可。记住,在NCS或者Zephyr里面,只要可以用文本编辑器打开的文件,他们的语法都是直接可读的,不需要你另外去学习他们,直接仿照例子,你就可以定制自己的内容。

除了Kconfig,autoconf.h还有一个输入来源:本项目的配置文件。前面说过,Kconfig文件都是模块自带的,模块为每一个选项都设了一个默认值,如果你想修改这个默认值,怎么办?你不需要跑到模块下面直接把Kconfig文件改了(这样不方便,而且也会影响到其他项目工程的运行)。为此NCS或者Zephyr引入了prj.conf文件,prj.conf文件内容大概如下所示:

  

通过prj.conf,大家就可以更改默认的Kconfig选项了,而且这个更改是永久的,并只适用于本项目。

所有的Kconfig和prj.conf结合,先生成一个.conf文件,最后再生成autoconfig.h。.conf文件在如下目录:

 

其内容大概如下所示:

  

可以看出,.config文件格式更接近Kconfig和prj.conf,起到了一个中间桥梁作用。.config和autoconfig.h两者内容是可以一一对应的,因此大部分图形配置系统都是直接调用.config文件来完成图形化配置Kconfig的,后面我们会讲解如何使用SES和menuconfig来图形化配置.config文件。.config是一个临时文件,编译系统默认会以它为基准来生成autoconfig.h,所以一旦你的Kconfig或者prj.conf更新了,记得一定要重新装载项目,以更新.config文件,从而生成新的autoconfig.h文件。

4.1.2 Device Tree( *.dts, *.overlay及devicetree_unfixed.h)

同样我们先把生成devicetree_unfixed.h的整体流程图列出来,后面再对其进行解释:

 

DeviceTree也是Linux里面的概念,NCS或者Zephyr对其进行了继承和定制。在DeviceTree里面,每一种硬件比如芯片或者板子,都可以使用DeviceTree语言进行描述。DeviceTree使用了树形结构,按照层级关系把板子包含的组件一一描述清楚,每块板子都会定义一个dts文件,比如nrf52840dk_nrf52840开发板,它对应的dts文件是nrf52840dk_nrf52840.dts,其内容如下所示:

  

可以看到板子dts文件包含了一个nrf52840_qiaa.dtsi,nrf52840_qiaa.dtsi对应的就是nRF52840这颗芯片本身的dts文件。nrf52840 dtsi里面定义的内容,nRF52840dk开发板直接包含进来,然后在此基础上进行定制,比如dtsi里面将UART0配置为关闭,nRF52840dk可以将其改配为使能;另一种需要修改的情况,nRF52840dk增加了一些其他组件,比如LED/Button/外部Flash等,这些设备都可以成为nrf52840dk_nrf52840.dts里面的一个节点。nrf52840dk_nrf52840.dts是一种比较简单的板子,因此一个dts文件就可以将其表达清楚。我们还会碰到一种情况,几块板子大部分特性都是相同的,只有少数组件不一样,这个时候,我们会把相同的地方拎出来组成一个common.dts,然后这几块板子再引用这个common.dts文件,比如目录:zephyr\boards\arm\nrf9160dk_nrf9160,里面包含两块开发板:nrf9160dk_nrf9160ns和nrf9160dk_nrf9160,两块开发板内容基本上是一样的,所以在这里把相同的内容拎出来组成了一个:nrf9160dk_nrf9160_common.dts,然后nrf9160dk_nrf9160ns.dts和nrf9160dk_nrf9160.dts再引用nrf9160dk_nrf9160_common.dts,这样拆分一下,逻辑关系更清晰,将使系统变得更灵活,扩展性更好。

  

除了<board>.dts,devicetree_unfixed.h还有一个输入来源:本项目的硬件配置文件,即overlay文件。刚才说了,每块板子都有自己的dts文件,里面描述了各个节点的状态,有时候我们会有多个项目共用同一块板子的情况,比如我们的开发板支持很多例子工程,每个例子工程的配置都不一样,有的例子需要打开uart,有的例子需要关闭uart,这种情况怎么办?你不需要跑到开发板定义目录下去更改<board>.dts或者<board>_common.dts,你只需要在本项目下定义一个<board>.overlay文件就可以实现你的目标,<board>.overlay文件内容示例如下所示:

  

通过<board>.overlay,大家就可以更改板子的默认配置,而且这个更改是永久的,并只适用于本项目

<board>.dts和<board>.overlay结合先生成一个zephyr.dts文件,最后再生成devicetree_unfixed.h。zephyr.dts文件在如下目录:

  

其内容大概如下所示:

  

可以看出zephyr.dts就是<board>.dts和<board>.overlay两个文件最终合并的结果,而且zephyr.dts和devicetree_unfixed.h是可以一一对应的,因此大家可以通过查看zephyr.dts来获知自己的硬件配置到底对不对,符不符合预期。

4.1.3 配置程序起始地址和大小

每个应用都需指定其image的ROM起始地址和大小,以及运行时所占RAM的起始地址和大小,这些都需要在工具链中进行配置的,比如Keil,是在如下界面完成相关配置:

  

在NCS中,如果是一个单image应用,程序的起始地址和大小是在Kconfig中配置的(这点NCS跟Zephyr不一样,Zephyr是在DeviceTree中配置),请使用如下两个宏进行配置:

CONFIG_FLASH_LOAD_OFFSET=0x00 CONFIG_FLASH_LOAD_SIZE=0x6f800

一般来说,无线IoT应用都是要求具有固件升级功能,为了升级固件,BootLoader就必不可少,此时一个应用至少有两个image:BootLoader对应的image,以及app对应的image,对于这种多image应用,程序起始地址和大小配置一般不通过Kconfig或者DeviceTree直接来修改,而是交由partition manager(PM)模块来管理,具体请参考7章:开发你的第一个multi-image(多image)应用

4.2 图形化查看Kconfig选项

上面说了,大家可以通过.config文件去查看最终的Kconfig配置,然后通过zephyr.dts文件去查看最终的DeviceTree配置。zephyr.dts文件不是很大,因此推荐使用这种方法去查看。但是.config文件有点长,直接查看它有点累,而且容易搞错,为此NCS三个IDE都有自己的图形化查看工具。

4.2.1 SES

进入Project->Configure nRF Connect SDK Project,如下所示:

  

 

由于一个项目的配置项太多,我们一般在右上角搜索配置项名字,找到它,然后查看它的说明。同时我们可以去尝试修改它,修改成功后,点击“Configure”,配置才能生效。通过图形化界面进行修改,我们可以很快找到合适的配置选项,当大家对系统还不是很熟的时候,推荐使用这种方式去试错。这里强调一下,通过这种方式所做的修改是临时的,一旦项目重启或者缓存刷新了,这里的更改就会失效,所以我们一般推荐使用上面的prj.conf去永久修改配置项。

对于multi-image(多image)应用,点击Project->Configure nRF Connect SDK Project,会同时出现所有image的配置菜单,其中“menuconfig”对应的是主应用的配置项(其他menuconfig对应的是子image的配置项,具体请参考7章:开发你的第一个multi-image(多image)应用),如下:

  

4.2.2 west

命令行方式使用如下命令查看项目配置:

west build -t menuconfig

执行上述命令后,将显示如下界面:

  

注:上述命令需要先安装windows-curses ,即在cmd中执行如下命令:pip install windows-curses –user,此命令的执行需要联网。如果你的电脑无法联网,建议使用guiconfig查看工程配置,其对应的命令为:

west build -t guiconfig

执行上述命令后显示的界面如下所示:

  

由于一个项目的配置项太多,我们一般使用Jump to进行搜索,找到它,然后查看它的说明。同时我们可以去尝试修改它,修改成功后,选择“Save”,配置才能生效。通过图形化界面进行修改,我们可以很快找到合适的配置选项,当大家对系统还不是很熟的时候,推荐使用这种方式去试错。

4.2.3 VS Code

项目编译成功后,就可以通过Kconfig查看配置,默认Kconfig使用nRF Kconfig查看配置项了,如下:

右键单击,你会发现VS Code也支持guiconfig和menuconfig。

 

这里强调一下,上述所有图形化配置方式(三种IDE都一样)所做的修改都是临时的,一旦项目重启或者缓存刷新了,这里的更改就会失效,所以我们一般推荐使用上面的prj.conf去永久修改配置项。

5. nRF Connect SDK中几个比较重要的目录

如前所述,NCS中包括了多个仓库,每个仓库都是相互独立的,而且每个仓库包含的代码都很多,如果一行一行代码读下去,那将是一个无底洞。所以实际开发中,我们都是参考例子,按照例子去做,碰到不懂的API,再去看API说明,循环往复,最终完成自己的开发。

5.1 例子目录

我们先说说例子所在的目录。NCS中商业级的应用程序是放在如下目录:

nrf\applications

  

如果你的应用跟上面的应用相似,那么推荐使用上面的例子,因为他们基本上属于turn-key级的方案,跟成熟的商业应用差不多,你需要的开发工作量最少。

其次是如下例子目录nrf\samples,这个都是Nordic自己开发的一些例程:

  

然后就是Zephyr自带的例子zephyr\samples:

  

大家有时候会觉得NCS或者Zephyr例子还是不够多,比如很多驱动API怎么用,好像没有例子。其实Zephyr所有API的的使用,都可以在zephyr\tests下面找到示例,所以当你找不到例子的时候,不妨在这里找一找:

   

5.2 API目录

NCS里面这么多API,到底该使用哪些API?API说明又在哪里?一般而言,我们只使用仓库里面的include目录下的API,API说明也在那里

比如nrf仓库的include目录:nrf\include

  

Zephyr标准API的include目录zephyr\include:

  

其他仓库也是遵守这个规范的,比如Nordic开发的底层驱动API(与RTOS无关):

modules\hal\nordic\nrfx\drivers\include

  

注:有些人会问,modules\hal\nordic\nrfx\drivers\include和zephyr\include\drivers两个目录里面的驱动API,我到底该使用哪个呢?zephyr\include\drivers这个是Zephyr标准的驱动API,按照Zephyr标准来定义的,它调用了底层API:modules\hal\nordic\nrfx\drivers\include,modules\hal\nordic\nrfx\drivers\include这里面的API都是Nordic自己实现的,跟平台无关。所以说,一般推荐使用zephyr\include\drivers这里面的API,只有这里面没有或者实现不了的功能(比如将同一个引脚动态分配给UART和SPI,Zephyr标准API就无能为力),这个时候才使用modules\hal\nordic\nrfx\drivers\include这里面的API。

5.3 板子定义目录

通过在cmd输入:

west boards

就可以查看目前Zephyr支持哪些标准板子:

  

上面这些板子都是在如下目录定义的:zephyr\boards\arm。由于Cortex-M33内核支持secure和non-secure(ns)两种应用,所以每一个Cortex-M33内核都包含两种硬件定义:安全和非安全。比如nrf9160dk,虽然物理上只有一块板子,逻辑上我们把它划分成两块板子:nrf9160dk_nrf9160和nrf9160dk_nrf9160ns,nrf9160dk_nrf9160是跑安全应用,而nrf9160dk_nrf9160ns是跑非安全应用。同样nRF5340dk,虽然物理上只有一块板子,但是它有两个核都可以供用户使用,其中app核既可以跑安全应用又可以跑非安全应用,而网络核只能跑一种应用类型,所以nrf5340dk在逻辑上就划分成三块板子:nrf5340dk_nrf5340_cpuapp(app核,跑安全应用),nrf5340dk_nrf5340_cpuappns(app核,跑非安全应用)以及nrf5340dk_nrf5340_cpunet(network核,跑非安全应用)。

  

除了这些标准Zephyr板子,NCS还有一些自定义板子,他们在如下目录:nrf\boards\arm

  

如果你要自定义自己的板子,可以参考上面例子来

6. 开发你的第一个NCS程序

现在我们开始我们第一个NCS程序或者Zephyr程序的开发,在NCS中,有如下两个现成的例子:zephyr\samples\hello_world和zephyr\samples\basic\blinky。hello_world例子就是在串口中打印一串字符串,而blinky例子就是让开发板的LED1一闪一闪,这两个程序直接就可以编译和运行,而且应该所有的Zephyr开发板都可以跑这两个程序。现在我们把这两个程序合成一个程序,即既打印字符串给串口助手,又让开发板LED1一闪一闪,同时我们把字符串打印变成循环打印,并将字符串同时输出到串口助手和RTT viewer。下面我们一步一步给大家演示这个开发过程。

本章所有代码可以到如下百度网盘链接获取,进入“开发你的第一个NCS(Zephyr)应用程序”-> “hello_world”,下载hello_world_ncsv140.rar

6.1 修改hello_world main.c文件

首先,我们以hello_world例子为基础,将这个例子拷到一个其他目录下(任何目录都可(不要有中文和空格等),只要你的环境变量都设好了,所有NCS例子的目录可以随意更改,这个真是非常方便!),这里让大家拷贝到其他目录,只是为了演示NCS编译路径的依赖性做得非常好,没有别的意思。如果你不想拷贝,也没关系,咱们可以直接在原始目录上进行修改,NCS自带git管理系统,非常方便你进行版本管理。我们做如下修改,以循环打印同一条日志:

  

复制代码
void main(void)
{ while(1)
    {
        printk("Hello World! %s\n", CONFIG_BOARD);
        k_sleep(K_SECONDS(1));
    }
}
复制代码

 

6.2 在项目中添加一个新文件blinky.c

然后把blinky代码加到hello_world,这里面就会用到添加一个新文件的技能。我们先把zephyr\samples\basic\blinky\src里面的main.c改名为blinky.c,然后拷贝到hello_world\src目录下。如何把blinky.c添加到项目中来呢?推荐的方法是修改CMakelists.txt文件,通过它加入新的编译文件或者库,或者包含新的目录。我们做如下修改,就可以把blinky.c加进来了:

  

target_sources(app PRIVATE src/blinky.c)

这种方式不管是NCS项目还是Zephyr项目,都能工作成功,而且是我们首推的方式。至于CMakeLists的语法怎么理解和使用?还是那句话:参考例子,不要专门去学习。除了修改CMakeLists方法外,SES还引入了一种图形化方法,如下所示:

   

这种方式只有nrf\samples这个目录下的例子才支持,zephyr\samples这个目录下的例子默认不支持这种方式。

上面图形化方式添加文件,本质上跟修改CMakeLists方法一样,它只不过在CMakeLists文件里面预先放入如下两行标识,这样SES就可以把新添加的文件塞到这两行标识之间:

  

我们把这两行标识放在我们的hello_world例子里面,这样我们也可以通过SES添加文件了。blinky.c文件添加成功后,相应的CMakelists文件也更新了,如下所示:

  

6.3 修改blinky.c文件

好了,现在blinky.c文件已经添加成功了,我们再对其进行修改,修改代码如下所示:

  

复制代码
void blinky_thread(void)
{ const struct device *dev; bool led_is_on = true; int ret;

    dev = device_get_binding(LED0); if (dev == NULL) { return;
    }

    ret = gpio_pin_configure(dev, PIN, GPIO_OUTPUT_ACTIVE | FLAGS); if (ret < 0) { return;
    } while (1) {
        gpio_pin_set(dev, PIN, (int)led_is_on);
        led_is_on = !led_is_on;
        k_msleep(SLEEP_TIME_MS);
    }
}

K_THREAD_DEFINE(blinky_thread_id, 1024, blinky_thread, NULL, NULL,
        NULL, 7, 0, 0);
复制代码

如上,我们直接把blinky代码变成另外一个线程以达到我们的目的(当然你也可以把blinky代码变成一个模块,以供hello_world的main调用,这里就不演示这种方式了)。

6.4 编译和运行你的第一个hello_world程序

下面我们以nrf52840dk_nrf52840为例来跑上面的工程,大家也可以用其他开发板来跑,比如nrf5340dk_nrf5340_cpuapp,nrf9160dk_nrf9160ns,这个例子都是支持的

SES工程装载如下所示,然后编译和下载:

  

或者使用west命令进行编译和下载,命令如下所示:

west build -b nrf52840dk_nrf52840 -d build_nrf52840dk_nrf52840 -p
west flash -d build_nrf52840dk_nrf52840

或者使用VS Code,其界面如下所示:

程序下载成功后,串口助手在循环打印“Hello World! nrf52840dk_nrf52840”,然后开发板上LED1在一闪一闪。串口截图如下:

  

我们第一个hello_world程序算是正式大功告成了。

6.5 使用prj.conf和<board>.overlay配置项目

我们现在对hello_world再做一个修改:把log输出到RTT viewer而不是串口助手。传统的nRF5 SDK需要做两件事来达到这个目的:一是修改sdk_config.h文件,二是添加相应文件。但是在NCS里面,你只需在prj.conf里面做如下修改即可达到同样的目的:

  

CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_UART_CONSOLE=n

运行后结果如下所示:

  

按道理说,上面的例子已经把日志输出改为RTT Viewer了,这时去测量nrf52840dk电流,应该很低才对,但实际上此时电流大概为500多微安。这是什么原因呢?我们先看一下Kconfig,如下:

再看一下zephyr.dts文件,如下:

  

可以看出,uart0和uart1都处于使能状态,从而导致功耗偏高。我们通过nrf52840dk_nrf52840.overlay文件,将uart0和uart1关闭,修改如下所示:

  

上面修改会把UART关闭,但是UART驱动还是会加载。如果你确认不需要串口(UART)的话,那么我们可以把相应的Kconfig关掉,即在prj.conf把serial模块关掉(注:serial模块在所有项目默认是打开的),如下

CONFIG_SERIAL=n

通过Kconfig关掉串口,此时即使zephyr.dts文件把UART使能了,即“status = "okay"”,串口也是关掉的,因为一旦Kconfig禁掉串口,就意味着串口驱动模块就不会加载,代码都没有,就无从谈起对UART的操作(上电UART默认是关的),这样就算dts文件里面把UART打开,对工程来说,就是改变了几个宏定义的值,仅此而已。

重新编译和下载,这时我们再去测量nrf52840dk电流,此时电流只有几微安,符合预期。

7. 开发你的第一个multi-image(多image)应用

有时一个应用会包含多个image,最典型的情况有三种:一是BootLoader+app,BootLoader一个image,app一个image,二是spm(tfm)+app,spm(tfm)一个image,app一个image(注:spm/tfm是为Cortex-M33非安全应用设计的一个安全引导程序,像nRF9160/nRF5340这种M33内核,所有非安全应用都需要装载spm或者tfm),三是双核,应用核一个image,网络核一个image(比如nRF5340的应用核和网络核)。在NCS中,编译一个项目,会同时生成多个不同image,这种应用就称为multi-image应用。image应用其生成的hex名为:zephy.hex,multi-image应用其生成的hex名为:merged.hex,merged.hex意味着这个项目会生成多个image,然后将他们合并(merge)成一个hex:merged.hex,因此multi-image应用对用户来说,最终也只有一个image,用户只需下载这个image即可。以nrf\samples\nrf9160\http_application_update为例,这是一个典型的多image应用,它包含3个image:BootLoader image,spm image以及app image,这三个image都是在nrf\samples\nrf9160\http_application_update编译目录下生成的,编译成功后,我们将同时看到三个image的编译目录,如下所示:

  

7.1 分区管理(Partition Manager)

传统的SDK,如果一个产品包含多个image,那么每个image都会对应一个项目,用户需要同时维护多个项目,而且需要同时维护这几个项目的版本关联关系。NCS中引入了partition manager(PM)模块(注:PM和前面的SPM是两个完全不同的模块,二者之间没有任何联系),由PM完成对多个image的管理,以及存储划分。在PM中,主应用称为parent应用,其他应用称为child应用,通过使能parent应用的某些Kconfig,可以自动装载child应用,然后自动编译child应用,然后生成child应用的hex,并将child应用的hex和parent应用的hex合并在一起生成前文所述的merged.hex,这一切都发生在parent应用的build目录中。

PM是如何工作的呢?PM首先假定有一个app image,也就是我们的parent应用,这个应用对应的hex就是前文所述的zephyr.hex,那么app image放在Flash什么地方呢?这个是由PM动态决定的,PM将根据各个image的相对位置,来决定最终的image排列。一般来说,parent应用是默认应用,它不需要特别去指定自己的位置,而child应用则需要告诉PM自己的位置在哪里,这个是通过child应用目录下面的pm.yml文件来实现的,pm.yml会告诉PM本child应用会放在那里,pm.yml文件内容大概如下所示:

  

pm.yml是按照相对位置来决定本child应用的位置的,而且里面会用到Kconfig或者DeviceTree的宏定义,所以前面的<board>.dts文件会定义很多image slot,其实也是为了给PM引用的。pm.yml看起来又是一种新格式文件,让人觉得有点不适应,其实还是那句话,你不需要专门去学习这个文件的原理,它的语法和格式都是你直接可以读懂的,多看看例子,自然就明白了。而且一般开发过程中,大家也不需要关心child应用目录下的文件,大家只需关心parent应用目录下的相关PM文件即可。

那么parent应用目录下有哪些PM文件呢?首先就是build根目录下多image最终布局的partitions.yml文件,以nrf\samples\nrf9160\http_application_update为例,其partitions.yml文件如下所示:

 

partitions.yml文件是由PM模块自动生成的,用户不能直接修改。

然后就是pm.config 和pm_config.h文件,这两个文件一一对应,pm.config和partitions.yml放在同一个目录下,其内容大概如下所示:

  

而pm_config.h是C语言代码直接引用的文件,它在build\zephyr\include\generated目录下,以nrf\samples\nrf9160\http_application_update为例,其pm_config.h文件如下所示:

  

一般来说,使用PM自动生成的存储layout就可以了,只有一个配置有可能需要改:settings_storage的大小,这个可以通过Kconfig选项CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE直接修改就可以了。

如果需要人为指定某个image的位置,也就是说需要对自动生成的partitions.yml进行修改,怎么办呢?其实很简单,把partitions.yml这个文件拷出来,放在项目的根目录下,然后将其重新命名为:pm_static.yml,然后大家就可以按照自己的需求将里面的值进行修改。这里补充一下PM的另一个工作机理,当PM检测到parent应用根目录下面有pm_static.yml文件,它就不会再自动去划分存储空间,而是直接使用这个静态的存储空间layout。

由于大家对PM困惑很多,下面以DFU分区的角度对这个问题进行阐述,大家看下面这几段文字时,最好有一点NCS DFU概念。

所谓分区(Partition),就是对Flash(包括内部Flash和外部flash)或者RAM物理区域进行一个逻辑划分,人为划定哪块区域干什么工作,比如把MCUboot这个image放在0x0000到0xC000这块区域,这种分区是人为的,所以你可以随意调整,比如你把MCUboot放在0x0000到0x10000,当然也是可以的。我们对Flash或者RAM进行分区,目的就是为了把空间利用好,给各个分区一个ID以便后续引用,如果代码里不引用这个分区,那么此分区只是一个占位符而已,比如app和mcuboot这两个分区。

我们看一下https://github.com/aiminhua/ncs_samples/tree/master/smp_dfu/ble_intFlash这个例子生成的partitions.yml:

 

从上面可以看出,这个partitions.yml定义了很多分区,比如app,mcuboot,mcuboot_pad,mcuboot_primary等(冒号前面的就是分区名),而且每一个分区规定了它的起始地址,结束地址,大小,相对位置以及放在什么物理存储器上,比如app这个分区:

 

关于分区名,只有“app”这个名字是必须有,而且是固定的,代表着主应用程序image;其他分区名,比如mcuboot,settings_storage,external_flash等,都是随意定义的,可以修改。比如0x0~0xc000这块内部Flash区,上面取名叫mcuboot,你也可以改成“my_boot”之类的名字,这个也没关系的,取名字主要考虑两点:一是能醒目标识这块区域的功能,二是跟代码里面的引用对起来,比如如下分区定义,经常有人困惑:

 

第一个“external_flash”是分区名,第二个“external_flash”是物理存储器名。作为分区名的“external_flash”,其实我们可以改成其他名字,以消除某些困惑,之所以使用这个名字,是因为老的littlefs例子里面对外部文件系统所在区域就称为“external_flash”,代码如下所示: 

复制代码
复制代码
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(external_flash);
static struct fs_mount_t fs_mnt = {
    .type = FS_LITTLEFS,
    .fs_data = &external_flash,
    .storage_dev = (void *)FLASH_AREA_ID(external_flash),
    .mnt_point = "/lfs",
};
复制代码
复制代码

实际上最新的littlefs例子已经把这块区域重新命名为:littlefs_storage或者storage,所以大家可以把这块分区名改为littlefs_storage,如下:

 

partitions.yml里面使用的region其实是在这个文件:nrf\cmake\partition_manager.cmake定义的,大家可以通过build目录下的regions.yml文件得知目前定义了几个物理存储器:

 

至于partitions.yml里面使用的placement/span等,这个是用来指定各个分区的相对位置的,很多人会疑问,既然指定了分区的起始地址和结束地址,那还有必要去指定各个分区的相对位置吗?这种情况下的确没必要再指定相对位置了,其实这里弄反了一件事情:partitions.yml里面的地址是placement相对位置定下来之后的结果。使用placement相对位置,为编译系统动态确定各个分区的位置提供了便利。如果是我们自己来划分存储器的分区,我们就可以直接使用绝对地址的方式静态指定各个分区的位置(当然使用placement也是可以的)。

如何人为静态指定?答案就是把刚才动态生成的partitions.yml文件拷贝到项目根目录下,然后改名为:pm_static.yml,然后再按照自己的需求去修改,比如smp_dfu/ble_extFlash这个例子,如果由系统动态生成partitions.yml文件,此时mcuboot_secondary分区所在地址为0x0~0xf0000,而文件系统external_flash或者littlefs_storage分区所在地址为0xf0000~0x800000,实际上很多客户喜欢把文件系统放在外部Flash 0x00地址,而把secondary slot放在外部flash最后,据此可以做如下修改:

 

这个pm_static.yml文件没有定义的分区,还是由系统动态分配。有时为了后续升级方便,我们会在pm_static.yml文件里面把所有的分区都按照自己的规划重新定义一遍,这样就不担心某个image突然变大而导致新的partitions.yml跟老的文件不兼容,从而无法升级。在定义pm_static.yml文件时,有如下规则必须遵守:

  • mcuboot_primary大小必须等于mcuboot_secondary,而且CONFIG_BOOT_MAX_IMG_SECTORS最好也等于他们大小/4096
  • 如果使用了一个region(flash_primary这个region除外),那么这个region每一块区域都要属于一个分区名字,不能出现某块区域没有分区名字情况。比如上面重新定义了external_flash region,根据regions.yml文件定义,external_flash总共有8Mbytes,那么这8Mbytes都必须有一个分区名字,而我们定义的littlefs_storage和mcuboot_secondary两个分区的确包含了全部8MB区域。如果我们定义littlefs_storage所在区域为0x0~0x700000,而mcuboot_secondary所在区域为0x710000~0x800000,那么系统就会报错,因为这里还有一个空隙(gap):0x700000~0x710000是没有取分区名字的。解决这个问题有两个办法:一个就是上面的方法把0x700000~0x710000划到littlefs_storage分区,一个就是给这块区域专门取一个名字,比如:my_unused_area(见下面示意),也是可以解决问题的。

 

对于flash_primary这个region,由于系统默认认为必须要有一个“app”分区,所以它可以存在而且只能存在一个空隙(gap),这样系统默认这个gap就是“app”分区。当然你也可以把flash_primary所有区域都分好区,包括“app”分区。

  • regions.yml文件里面各个存储器的物理大小必须符合实际,这个通过修改dts文件来保证的。这里面最容易出错的就是external_flash,external_flash的大小在regions.yml文件里面是以字节为单位(在kconfig文件里面也是以字节为单位的),但是external_flash对应的设备树,比如MX25R64,它在dts文件里面是以bit为单位的,所以当大家使用其他外部Flash的时候,请仔细检查这些size对不对
  • settings_storage,即settings使用的分区,大家可以将分区名改成:storage,这是其一,其二settings系统最终使用的最大flash区域大小是由CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE决定,而不是settings_storage分区本身大小决定,所以建议大家把CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE的值设为settings_storage分区大小。
  • 至于RAM分区,道理也是一样的。这里需要注意的是,RAM各个分区的大小大家可以直接到dts文件里面去调整,而无需在pm_static.yml文件里面调整。当然,大家在pm_static.yml里面调整也是可以的,殊途同归,达到目的就好了。对于nRF52系列,只有一个sram_primary分区,这个没什么好讲的;对于nRF53系列,除了sram_primary这个分区,它还有rpmsg_nrf53_sram分区以及pcd_sram分区,其中rpmsg_nrf53_sram是用来蓝牙协议栈host和controller之间进行双核通讯的,而pcd_sram是用来升级网络核image的。

7.2 多image的hello_world程序开发

我们现在将第6章的hello_world程序跑在多image环境下。现在我们以nrf9160dk_nrf9160ns为例,重新编译一下前述的hello_world程序,由于nrf9160dk_nrf9160ns为非安全应用,前面也说过,所有Cortex-M33非安全应用都会默认使能spm/tfm模块,所以spm/tfm image将会自动加载进来并进行编译。

  

装载成功后,你可以看到build和download的target都变成:merged.hex,而不是以前的zephyr.hex,如下:

  

而且build目录也会多一个spm的build目录,如下所示:

  

程序跑起来后,log如下所示:

  

跟第6章一样,我们再定义一个overlay文件,以将uart0关掉,此时我们去测9160dk的电流,应该只有几微安,但实际上我们测下来还是500多微安,这是为什么呢?因为spm/tfm还使能了serial和uart0,这个可以从spm的.config和zephyr.dts文件得到验证,如下:

  

所以uart0在spm/tfm中打开了,然后程序跳到app,uart0还是处于打开状态,从而导致功耗偏高。那么我们怎么可以便捷的关闭spm/tfm里面的serial模块和uart0?方法一,大家跑到spm/tfm例子里面,然后定义前述的prj.conf以关闭serial模块,但这种方法会影响其他例子;方法二,我们在parent应用中定义spm/tfm的prj.conf,这样spm/tfm只在这个parent应用中关闭了serial模块,对其他应用不产生任何影响。为了将parent应用中的conf和overlay文件(overlay其实对关掉UART没有意义)传给spm/tfm,需要用到NCS编译系统的编译变量,给相应的变量赋值,从而可以将相关文件传递给spm/tfm,具体请参考8

我们对CMakelists文件做如下修改:

  

复制代码
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/spm.conf") set(spm_CONF_FILE
    prj.conf
    ${CMAKE_CURRENT_LIST_DIR}/spm.conf
  )
endif() if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/spm.overlay") set(spm_DTC_OVERLAY_FILE "${CMAKE_CURRENT_SOURCE_DIR}/spm.overlay")
endif()
复制代码

通过设置spm_CONF_FILE和spm_DTC_OVERLAY_FILE两个变量,我们将spm.conf和spm.overlay两个文件传给了spm image项目,从而达到控制spm image编译配置目的。注意:如果要把conf和overlay文件传给tfm image,那么就要使用tfm_CONF_FILE和tfm_DTC_OVERLAY_FILE这两个变量

spm.conf我们定义如下选项:

CONFIG_SERIAL=n
CONFIG_UART_CONSOLE=n

spm.overlay我们定义如下节点:

&uart0 {
    status = "disabled";    
};

 

上述这种通过CMake系统变量去修改子image配置的做法,让很多人觉得有点难,为此从ncs v1.5.0开始引入了一种更简单的方式:用户只需在父应用目录下建一个child_image目录,然后把子image的配置文件放在这个目录,配置文件的名字必须跟子image的名字一模一样(子image的名字见8),这样系统就会自动把该目录下的配置传送给子image,如下所示:

我们再重新装载hello_world程序,可以看到在spm中,serial和uart0都关闭了,如下:

  

此时再去测量9160dk电流,就降到几微安了。

一点额外功能

上面日志要不打印到串口助手,要不打印到RTT viewer,而且都是堵塞式打印。我们现在将打印改成异步的,而且同时打印到串口助手和RTT viewer。为此我们将logging模块打开,同时设置如下Kconfig:

  

复制代码
CONFIG_ASSERT=y
CONFIG_ASSERT_LEVEL=2 CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=2 CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BACKEND_RTT_MODE_DROP=y
CONFIG_LOG_MODE_OVERFLOW=y
CONFIG_LOG_PRINTK=y
CONFIG_LOG_PRINTK_MAX_STRING_LENGTH=256 CONFIG_LOG_BUFFER_SIZE=4096 CONFIG_LOG_BACKEND_RTT_MESSAGE_SIZE=256 CONFIG_LOG_STRDUP_BUF_COUNT=64 CONFIG_LOG_STRDUP_MAX_STRING=64 CONFIG_LOG_BACKEND_SHOW_COLOR=n
CONFIG_LOG_BACKEND_FORMAT_TIMESTAMP=n
CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=1024 CONFIG_CONSOLE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096 CONFIG_RTT_CONSOLE=y
CONFIG_UART_CONSOLE=y
复制代码

只需做上述修改,就可以达到我们前述的目的,非常方便(无需添加文件,无需修改include目录,这就是NCS!)。这次大家可以使用nrf5340dk_nrf5340_cpuapp跑一下试试。

注:上述日志配置直接从nrf_desktop拷贝过来,大家以后也可以参考它来使能自己的log模块。 

8. NCS编译系统几个要注意的点

8.1 NCS几个重要的编译系统变量

在NCS或者Zephyr编译系统中,有几个变量非常重要,每个人最好掌握他们,把他们使用好,会让你的编译变得得心应手,这几个变量是:

ZEPHYR_BASE,用来指示你的Zephyr仓库的绝对目录,比如取值:C:\Nordic\NCS\SDK\tag\v1.4.0\zephyr

BOARD,用来指定编译用的板子,比如取值:nrf52840dk_nrf52840

CONF_FILE,用来指定项目的conf文件,如果没有指定,默认用prj.conf,详细说明见8.2

DTC_OVERLAY_FILE,用来指定项目的overlay文件,如果没有指定,默认用<board>.overlay,详细说明见8.3

PM_STATIC_YML_FILE,用来指定parent应用,即app的pm_static文件,如果没有指定,默认用pm_static.yml,详细说明见7.1

CMAKE_BUILD_TYPE,命令行可以通过这个变量传递一个参数给CMakelists.txt文件或者其他build过程。

上述变量不分大小写,所以CONF_FILE和conf_file是一样的,其他类同。因为这些变量是针对每一个image的,所以每一个image都有自己的board,conf_file,dtc_overlay_file等。对于单image应用,这个好理解也好区分;那如果是多image应用,该如何区分每个image的conf_file和dtc_overlay_file呢?这可以通过使用image专用变量来实现。如前所述,conf_file这个变量本身是作用于app image的,实际上你可以把这个变量看成:app_conf_file,只不过默认都是app image,所以就把app_省略了。当你需要在parent应用中去设置child应用的conf_file,你就不能直接使用conf_file这个变量了(因为它是用来设置parent应用本身的conf文件),而需使用childImageName_conf_file,比如上面的hello_world程序,我们使用了spm_conf_file这个变量,用来设置子image spm的conf_file。跟conf_file变量一样,dtc_overlay_file变量使用了同样的规则。NCS中目前主要有如下4个child image:

  • mcuboot。可升级的第三方开源BootLoader
  • b0(NSIB)。不可升级的Nordic自研BootLoader
  • spm。Cortex-M33非安全应用的安全引导程序,Nordic自己开发的。
  • tfm。trusted-firmware-m,作用跟spm相似,但是符合PSA标准,由第三方开发。

除了上述child image,在编译nRF5340 app核的时候,我们也可以自动包含如下 network核的child image:

  • b0n。nRF5340 网络核的bootloader。(注:b0n跟上面的b0没有任何关系)
  • hci_rpmsg。nRF5340 网络核的蓝牙controller
  • 802154_rpmsg。nRF5340 网络核的802.15.4 controller

通过上面的childImage名字加上前面的编译系统变量,就可以通过parent应用去控制child应用的编译过程,大大方便了多image的开发流程。上述这种通过CMake系统变量去修改子image配置的做法,让很多人觉得有点难,为此从ncs v1.5.0开始引入了一种更简单的方式:用户只需在父应用目录下建一个child_image目录,然后把子image的配置文件放在这个目录,配置文件的名字必须跟子image的名字一模一样,即上面的mcuboot,spm这些特定名字,这样系统就会自动把该目录下的配置传送给子image。

关于上述编译系统变量的使用,大家可以参考:nrf\applications\nrf_desktop和nrf\applications\asset_tracker。

nrf_desktop虽然只有一个CMakeLists.txt,但实际上这个CMakeLists.txt包含了20多个项目,它是怎么做到的呢?它就是通过编译系统变量来实现的。比如要设置某一块板子对应的某一个工程的conf文件,在ncs v1.4.0等老版本NCS中使用了如下语句:

  

set(CONF_FILE "configuration/${BOARD}/app_${CMAKE_BUILD_TYPE}.conf")

比如说,$BOARD=nrf52840dk_nrf52840,$CMAKE_BUILD_TYPE=ZDebug_keyboard,那么它对应的conf文件就是如下这个:

  

在最新版nrf_desktop,比如ncs v1.9.1,conf_file这个变量改为通过命令行方式传进来而不是上面的CMakelists文件,如下所示:

west build -b nrf52840dk_nrf52840 -d build_nrf52840dk_nrf52840 -- -DCONF_FILE=prj_release.conf

 

在ncs v1.4.0老版本NCS中的asset_tracker通过CMakeLists.txt文件定义了子image spm的conf文件,以及定义了一个静态的pm文件,如下所示:

  

set(spm_CONF_FILE ${CMAKE_CURRENT_SOURCE_DIR}/spm.conf) set(PM_STATIC_YML_FILE
  ${CMAKE_CURRENT_SOURCE_DIR}/configuration/${BOARD}/pm_static.yml
  )

在最新版asset_tracker_v2,比如ncs v1.9.1,则通过child_image目录方式实现了上述同样功能,如下:

大家在写自己的多image应用的时候,可以多借鉴上面的例子,上面三种方式:CMakelists,命令行以及child_image目录,都是可行的,大家选择适合自己的就好。目前看起来,child_image目录方式最简单,所以最新的例子基本上都换成这种方式了,但是前两种方式仍然支持,本质上这三种方式原理是一样的,只不过child_image方式封装了一层,实际上编译系统最终也是把child_image目录下的配置文件赋给了上述系统变量。

8.2 conf文件命名规则及编译顺序

对于单image应用或者多image应用的父应用(主应用),我们一般都是通过prj.conf去配置项目的,除了prj.conf,其实符合如下命名标准的conf文件也可以被系统自动加载进来。

  1. 首先读取CONF_FILE变量,我们可以将多个conf文件都赋给这个变量(每个conf文件之间以分号或者空格隔开),这些配置文件最终会合并成一个。我们可以通过三种方式设置CONF_FILE变量
    • 通过命令行方式传递:-DCONF_FILE=<file1.conf;file2.conf>
    • 在CMakeLists.txt中并且必须在调用find_package(Zephyr)之前(也就是包含boilerplate.cmake之前)
    • 通过CMake变量cache
  2. 否则,系统将使用应用目录下的prj_<build>.conf 和boards/<BOARD>_<build>.conf两者的合并结果
  3. 否则,系统将使用应用目录下的prj_<BOARD>.conf 
  4. 否则,系统将使用应用目录下的boards/<BOARD>.conf和prj.conf 的合并结果
  5. 否则,系统将使用应用目录下的prj.conf 
  6. 如果你在应用根目录下或其他目录下放其他名字的conf文件,即不是上面这些情况,那么这个conf文件将被系统忽略,起不到任何作用

记住:如果同一个Kconfig选项或者符号被配置多次,以最后一次配置为准

8.3 overlay文件命名规则及编译顺序

对于单image应用或者多image应用的父应用(主应用),系统按照如下顺序自动装载overlay文件:

  1. 首先读取DTC_OVERLAY_FILE变量,我们可以同时将多个overlay文件赋给这个变量(每个overlay文件之间以分号或者空格隔开),这些overlay文件最终合并为一个文件。我们可以通过如下方式设置DTC_OVERLAY_FILE变量
    • 通过命令行方式传递:-DDTC_OVERLAY_FILE="file1.overlay;file2.overlay"
    • 在CMakeLists.txt中并且必须在调用find_package(Zephyr)之前(也就是包含boilerplate.cmake之前)
  2. 否则,系统将使用应用目录下的boards/<BOARD>.overlay 
  3. 否则,系统将使用应用目录下的<BOARD>.overlay

  1.  如果你在应用根目录下或其他目录下放其他名字的overlay文件,即不是上面这些情况,那么这个overlay文件将被系统忽略,起不到任何作用

以上来源作者Kelvin 胡狼

(0 ) (0 )

智能穿戴 蓝牙耳机

回复 举报
  • 发表回复
    0/3000





    举报

    请选择举报类别

    • 广告垃圾
    • 违规内容
    • 恶意灌水
    • 重复发帖

    全部板块

    返回顶部