背景
最近工作中接触到了 Zephyr,不由觉得 Zephyr 是个很强大、全面、优秀的实时操作系统,但同时是有一定的上手难度的,其复杂的构建系统让小编倒吸一口凉气。为了深入研究并完全掌控 Zephyr,小编决定把它移植到手头的开发板上,为后续探究 Zephyr 源码仓库的构建原理、系统启动原理、多种调度机制 打下基础。
基础知识
- 搭建 Zephyr 环境
- 能够使用基本的 west 命令编译、烧录、调试 bsp
可通过 Zephyr 源码调试 章节验证基础知识的掌握程度。具体 west 工具的细节以及整个 elf 文件链接过程暂时不要深究,这是个很深的坑。
移植
Zephyr 仓库下面有很多开发板的基础 bsp,我们可以寻找一个与我们开发板上芯片型号类似的 bsp 作为参考。Art-Pi 主控是 Stm32H750XBH6,主频高达 480Mhz,片上资源非常丰富。
我们可以参考 zephyr/boards/st
目录下的 stm32h750b_dk
,该目录树结构如下:
stm32h750b_dk
├── Kconfig.stm32h750b_dk
├── arduino_r3_connector.dtsi
├── board.cmake
├── board.yml
├── doc
│ ├── img
│ │ └── stm32h750b_dk.png
│ └── index.rst
├── stm32h750b_dk.dts
├── stm32h750b_dk.yaml
├── stm32h750b_dk_defconfig
└── support└── openocd.cfg
各文件描述如下:
Kconfig.stm32h750b_dk
: 板级 Kconfig 定义,用于控制板级资源,如使能 HAL 层的各个驱动arduino_r3_connector.dtsi
: arduino 扩展引脚设备树定义,我们的板子不需要该文件,直接删除board.cmake
:板级 CMake 配置,一般用于配置板子所使用的调试器参数,比如使用 openocd 作为调试器 Serverboard.yml
: 板级描述文件,暂时不清楚有什么用,但没有这个文件,构建系统会报错doc
:板子介绍stm32h750b_dk.dts
: 板子设备树定义stm32h750b_dk.yaml
: 板级测试相关(Zephyr 自动化单元测试系统使用)stm32h750b_dk_defconfig
: 板级 Kconfig 默认值,如果在此处配置了默认值,就无法通过 menuconfig 或者 App 目录下的 pri.conf 文件中进行修改openocd.cfg
: openocd 配置文件,如果 board.cmake 中使用 openocd 作为调试器的 server,该文件将会被传递给 openocd 使用
主要是板子设备树文件比较重要,该文件涉及芯片时钟、外设、内存、flash 配置。其余文件中的内容全部改为自己的开发板,Art-Pi 设备树文件最终配置如下:
/** Copyright (c) 2023 STMicroelectronics** SPDX-License-Identifier: Apache-2.0*//dts-v1/;
#include <st/h7/stm32h750Xb.dtsi>
#include <st/h7/stm32h750xbhx-pinctrl.dtsi>
#include <zephyr/dt-bindings/input/input-event-codes.h>/ {model = "STM32H750XBH6 ART PI";compatible = "st,stm32h750xb-art-pi";chosen {zephyr,console = &uart4; // uart4 作为控制台输出zephyr,shell-uart = &uart4; // uart4 作为控制台输入zephyr,sram = &sram0; // 使用 sram0 作为片内 sram,sram0 的定义在 dtsi 文件中zephyr,flash = &flash0; // 使用 flash0 作为片内 flash,这个并不决定链接时各符号的加载地址,而是由 CONFIG_FLASH_BASE_ADDRESS 这个宏决定,zephyr 构建系统的链接脚本有点复杂,暂时不深究};leds {compatible = "gpio-leds";blue_led: led_1 {gpios = <&gpioi 8 GPIO_ACTIVE_LOW>; // 定义板子蓝灯引脚label = "USER2 LD7";};red_led: led_2 {gpios = <&gpioc 15 GPIO_ACTIVE_LOW>; // 定义板子红灯引脚label = "USER2 LD7";};};gpio_keys {compatible = "gpio-keys"; // 定义板子按键引脚user_button: button {label = "User";gpios = <&gpioh 4 GPIO_ACTIVE_LOW>;zephyr,code = <INPUT_KEY_0>;};};sdram1: sdram@c0000000 { // 定义板子片外 sdram 地址,使用 fmc 驱动compatible = "zephyr,memory-region", "mmio-sram";device_type = "memory";reg = <0xc0000000 DT_SIZE_M(32)>; // 定义 sdram 起始地址、尺寸zephyr,memory-region = "SDRAM1";zephyr,memory-attr = <( DT_MEM_ARM(ATTR_MPU_RAM) )>;};aliases {led0 = &blue_led;led1 = &red_led;sw0 = &user_button;};
};&clk_hse { // 外部 hse 定义clock-frequency = <DT_FREQ_M(25)>;status = "okay";
};&pll { // pll 配置,h750 系列的时钟树比较复杂,可通过 cubemx 简化配置div-m = <5>;mul-n = <192>;div-p = <2>;div-q = <4>;div-r = <4>;clocks = <&clk_hse>;status = "okay";
};&rcc { // 系统各时钟域配置clocks = <&pll>;clock-frequency = <DT_FREQ_M(480)>;d1cpre = <1>;hpre = <2>;d1ppre = <2>;d2ppre1 = <2>;d2ppre2 = <2>;d3ppre = <2>;
};&uart4 { // uart4 作为 zephyr 控制台pinctrl-0 = <&uart4_tx_pa0 &uart4_rx_pi9>;pinctrl-names = "default";current-speed = <115200>;status = "okay";
};&fmc { // fmc 配置,用于驱动 sdram 芯片status = "okay";pinctrl-0 = <&fmc_nbl0_pe0 &fmc_nbl1_pe1&fmc_sdclk_pg8 &fmc_sdnwe_ph5 &fmc_sdcke0_pc3&fmc_sdne0_pc2 &fmc_sdnras_pf11 &fmc_sdncas_pg15&fmc_a0_pf0 &fmc_a1_pf1 &fmc_a2_pf2 &fmc_a3_pf3 &fmc_a4_pf4&fmc_a5_pf5 &fmc_a6_pf12 &fmc_a7_pf13 &fmc_a8_pf14&fmc_a9_pf15 &fmc_a10_pg0 &fmc_a11_pg1 &fmc_a12_pg2&fmc_a14_pg4 &fmc_a15_pg5 &fmc_d0_pd14 &fmc_d1_pd15&fmc_d2_pd0 &fmc_d3_pd1 &fmc_d4_pe7 &fmc_d5_pe8 &fmc_d6_pe9&fmc_d7_pe10 &fmc_d8_pe11 &fmc_d9_pe12 &fmc_d10_pe13&fmc_d11_pe14 &fmc_d12_pe15 &fmc_d13_pd8 &fmc_d14_pd9&fmc_d15_pd10>;pinctrl-names = "default";sdram {status = "okay";power-up-delay = <100>;num-auto-refresh = <8>;mode-register = <0x221>;refresh-rate = <0x02A5>;bank@0 {reg = <0>;st,sdram-control = <STM32_FMC_SDRAM_NC_9STM32_FMC_SDRAM_NR_13STM32_FMC_SDRAM_MWID_16STM32_FMC_SDRAM_NB_4STM32_FMC_SDRAM_CAS_2STM32_FMC_SDRAM_SDCLK_PERIOD_2STM32_FMC_SDRAM_RBURST_ENABLESTM32_FMC_SDRAM_RPIPE_0>;st,sdram-timing = <2 8 6 6 2 2 2>;};};
};
仅通过如上配置就能够编译并链接通过,其背后是 Zephyr 对 stm32 系列芯片 hal 库以及外设驱动的完美适配,以至于用户仅通过简单的设备树描述就能让外部设备正常工作起来。最终 Art-Pi 目录结构如下:
stm32h750_art_pi
├── Kconfig.defconfig
├── board.cmake
├── board.yml
├── stm32h750_art_pi.dts
├── stm32h750_art_pi_defconfig
└── support└── openocd.cfg // openocd 默认会掉用 openocd.cfg 文件,该文件用于描述 openocd 控制的硬件调试器,如 stlink,jlink
板级调试
以上步骤全部通过后会面临一个问题:如何将固件搞到芯片里面?由于我是使用的 stm32 系列,同时拥有 stlink 调试器,可通过 STM32CubeProgrammer
工具擦除并将固件下载到 flash。但会遇到一个问题,固件并没有按照预期执行,甚至没跑到 main 线程,此时连串口都不能打印日志,此时如果拥有调试器,那便是如虎添翼。
Zephyr 的构建系统是支持下载以及调试能力的,这时候也不需要使用 STM32CubeProgrammer
了。一般调试嵌入式设备需要硬件调试器(板子不足以运行 gdb server,但 Zephyr 貌似支持 gdb stub,这样的话可以不需要硬件调试器)、硬件调试器配套的 gdb server(stlink server、jlink server、openocd)、gdb。我们选择使用 openocd 作为 gdb server。可通过使用 west flash
命令验证能否下载固件,如图:
这一命令的背后到底发生了什么?其本质是调用 openocd 来将固件下入芯片内,但这背后是如何调用起 openocd,暂时不深追,这个坑很深,也正是 Zephyr 构建系统的复杂且神秘之处,给人一种知其然不知其所以然的感觉,小编决定后续几章来深究 Zephyr 构建系统原理。
此时一般会发现固件跑不起来,这时候,可以使用 west debugserver
命令来起一个 gdb server,这个 gdb server 就是 openocd,它在某个端口上起一个 tcp 服务,通过 tcp 与 gdb client 交互,双方通信是明文传输,如图:
Openocd 说它监听 3333 端口,此时我们便可以通过 gdb 来连接 3333 端口进行调试了,如图:
使用命令行的方式虽然能进行调试,但是很麻烦,效率不高,这时候可参考 Zephyr 源码调试章节 进行图形化界面调试,这样子能大大提高解决问题的效率,终极调试界面如图:
我的开发环境是 wsl ubuntu,使用 wsl 连接 usb 设备需要简单的配置一下,可网上搜索 wsl usb
关键字查看相关教程。
应用配置
移植完毕后,可通过 Zephyr 仓库 zephyr/samples/basic/blinky
下的应用来验证,比如可在 main 函数中翻转 led 灯来验证。但此时,Zephyr 的 shell 还不能使用,因为 shell 作为 Zephyr 的一个子系统此时还未使能,这也是 Zephyr 构建系统的一大优势,以搭积木的形式构建系统,缺少某一模块,仍然能够编译链接通过,仅仅是该能力缺失而已。可在 zephyr/samples/basic/blinky
下的 prj.conf 文件中添加如下配置:
CONFIG_GPIO=y
CONFIG_UART_CONSOLE=y //使能 uart console
CONFIG_SHELL=y //使能 shell 模块
CONFIG_MEMC=y
CONFIG_MEMC_STM32=y
CONFIG_MEMC_STM32_SDRAM=y
最终就能丝滑的感受 Zephyr 了,这才有点操作系统的味道。
最终附上 项目地址 ,欢迎 Star~~~