移植 Zephyr 到 Art-Pi

背景

​ 最近工作中接触到了 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 作为调试器 Server
  • board.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_9
						STM32_FMC_SDRAM_NR_13
					    STM32_FMC_SDRAM_MWID_16
                        STM32_FMC_SDRAM_NB_4
					    STM32_FMC_SDRAM_CAS_2
					    STM32_FMC_SDRAM_SDCLK_PERIOD_2
					    STM32_FMC_SDRAM_RBURST_ENABLE
					    STM32_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~~~

相关推荐

  1. socat移植arm+linux

    2024-03-25 08:48:03       13 阅读
  2. Zephyr PM电源管理系统学习笔记

    2024-03-25 08:48:03       13 阅读
  3. 嵌入式Linux中OpenSSH移植ARM开发板

    2024-03-25 08:48:03       8 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-25 08:48:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-25 08:48:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-25 08:48:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-25 08:48:03       20 阅读

热门阅读

  1. Spring Boot 加载配置文件的优先级

    2024-03-25 08:48:03       19 阅读
  2. 网络安全简答题

    2024-03-25 08:48:03       19 阅读
  3. FPGA时钟资源详解——Clock-Capable Inputs

    2024-03-25 08:48:03       18 阅读
  4. 【DevOps云实践】Azure Function中使用发布/订阅模式

    2024-03-25 08:48:03       18 阅读
  5. spring boot常见的面试题

    2024-03-25 08:48:03       17 阅读
  6. 解决 Jupyter Notebook 中没有显示想要的内核的问题

    2024-03-25 08:48:03       18 阅读