Zephyr 之创建 Custom SoC - 1
Intro
最开始看到 Zephyr 能够在 app 层添加 Custom SoC,于是兴奋地搞了两天,无果,网上找资料也找不到,只好向 Zephyr 源代码下手,这才发现在 app 层搞 Custom SoC 是不现实的,像什么 DeviceTree 文件,外设驱动层代码,头文件等等,根本不是一个在 app 层开一个 soc 文件夹那么简单,于是吭哧吭哧搞了快两周,终于在 20230901 周五晚上在基于 Custom SoC 的 Custom Board 上跑通了 hello_world 和 blinky 两个 Zephyr 的 Samples。
Zephyr 本身是支持很多家的 SoC 的,不管是 stm32,nxp_s32,gd32,esp32 等等,但总会出现你的项目所需要的 SoC 不在支持的列表中的情况,况且 Zephyr 支持的 “国产 MCU” 并不多,因此适配 Custom SoC 的需求还是很大的。
Preparation
适配 Custom SoC 是一个比较复杂的活,为了方便管理代码,建议在 github 或 gitee 上 fork 或者导入一下 Zephyr 的代码,在 github 或 gitee 上切换分支到 main 上,然后用下面的方式初始化 zephyr 的工作空间 (假设 Zephyr SDK 已经准备好了):
west init ./zephyrproject -m <git-url>
记得将 <git-url>
换成你的 git 链接。
然后 cd 到 zephyr 目录下,创建一个属于自己的 git 分支:
cd zephyr/
git checkout -b dev-custom-soc
在执行 west update
指令之前,先看下 west.yml 文件,里面记录了要 git clone 的 非 Zephyr 的代码,如果嫌 update 的时间过长,可以在这个文件中,将不需要的 hal_xxx 内容注释掉,这样可以减少 git clone 的时间,毕竟都是从 github 上下载文件,国内的网络环境笔者都花费了两个小时,经过多次 update 才搞定。
退回到 zephyrproject 文件夹内,执行 west update
指令:
cd ../
west update
如果 update 失败,多执行几次 west update
指令试试。
试试在 qemu_x86 上跑个 hello_world 试试,证明环境搭建完成:
cd zephyr/
west build samples/hello_world -b qemu_x86
west build -t run
如果能跑通,就说明环境 ok 了。
接下来说一个跑不通的情况,如果你是在虚拟机上跑 ubuntu,然后将一个实体的文件夹通过共享的挂载到 ubuntu 上,在这个共享文件夹下创建的 zephyrproject,则会出现权限问题,无法成功编译,因此,还是老老实实在虚拟机的虚拟硬盘上搞事情吧,当然有其他解法,暂时懒得去找。
接下来看下要改动的代码吧,看下 Zephyr 的文件结构,这里面要分别在下面的文件夹内添加个改动代码:
- boards/
- drivers/
- dts/
- include/zephyr/
- soc/
boards 是为了给 Custom SoC 适配板子,drivers 适配驱动,第一步可以先不搞定这个,dts 是要放 soc 需要的 DeviceTree 文件,这个文件中定义了 flash 和 sram 的起始地址和大小,是的,这个信息不是在 linker 中定义的,include要添加相关的头文件,第一步也可以先不搞定这个,soc 要存放 soc 的定义文件。前期,我们要先搞定板子,设备树,还有 SoC 才行。
在开始搞定这三个文件夹之前,得先说明下我手上的 SoC,是一颗 Arm Cortex-M3 内核的微控制器,512KB Flash,128KB SRAM。
soc
按照 Zephyr 的目录结构,我需要将 SoC 相关的东西放在 soc/arm 文件夹下,因此,在 soc/arm 下创建 SoC 厂商名称,我这里称为 xx32,可根据
cd soc/arm
mkdir xx32
cd xx32
按照其他厂商芯片的样子,创建一个 common 文件夹,在common 文件夹下创建一个 CMakeLists.txt 文件,这个 CMakeLists.txt 中可以什么都不用写
mkdir common
cd common
touch CMakeLists.txt
在 custom32 文件夹下创建 Kconfig 文件,注意所有 Kconfig 名称的 K 一定要大写,Linux 下文件和文件夹是区分大小写的,一不注意这个 K 会让你找 bug 找好久的。
cd ../
vim Kconfig
填入下面的内容:
config SOC_FAMILY_XX32
bool
select HAS_SEGGER_RTT if ZEPHYR_SEGGER_MODULE
select BUILD_OUTPUT_HEX
if SOC_FAMILY_XX32
config SOC_FAMILY
string
default "xx32"
source "soc/arm/xx32/*/Kconfig.soc"
endif # SOC_FAMILY_XX32
为什么写这些内容,别问,问就是别的 SoC 都这样写,其实仔细读也是能读懂的。
创建 Kconfig.defconfig,并填入下面的内容:
source "soc/arm/xx32/*/Kconfig.defconfig.series"
创建 Kconfig.soc,并填入下面的内容:
source "soc/arm/xx32/*/Kconfig.series"
上面这三个 Kconfig 文件,如果没有意外,到最后都不用改动,如果需要的话,可以在文件前面写上版权信息。
这样只是把厂商的内容创建好了,接下来该创建芯片系列了:
创建 xx32f0 文件夹,代表某一个系列的芯片,并进入:
mkdir xx32f0
cd xx32f0
在 xx32f0 文件夹下创建 CMakeLists.txt 文件,并填入以下内容:
zephyr_include_directories(${ZEPHYR_BASE}/drivers)
zephyr_sources(soc.c)
创建 Kconfig.sreies 文件,并填入以下内容:
config SOC_SERIES_XX32F0
bool "Custom32 Series SoC"
select ARM
select CPU_CORTEX_M3
select SOC_FAMILY_XX32
help
Enable support.
创建 Kconfig.defconfig.series文件,并填入下面内容:
if SOC_SERIES_XX32F0
source "soc/arm/xx32/xx32f0/Kconfig.defconfig.xx32f0*"
config SOC_SERIES
default "xx32f0"
config SYS_CLOCK_HW_CYCLES_PER_SEC
default $(dt_node_int_prop_int,/clocks/clk_sys,clock-frequency)
endif # SOC_SERIES_XX32F0
创建 linker.ld 文件,填入以下内容:
#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>
创建 soc.h 文件,里面填入以下内容:
#ifndef _XX32F0_H_
#define _XX32F0_H_
#ifndef _ASMLANGUAGE
#endif /* !_ASMLANGUAGE */
#endif /* _XX32F0_H_*/
创建 soc.c 文件,里面填入以下内容:
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <soc.h>
static int xx32_init(void)
{
return 0;
}
SYS_INIT(xx32_init, PRE_KERNEL_1, 0);
创建 Kconfig.defconfig.xx32f0x,代表具体的一颗 soc 型号,并填入以下内容:
if SOC_XX32F0X
config SOC
default "SOC_XX32F0X"
config NUM_IRQS
default 128
endif # SOC_XX32F0X
最后创建 Kconfig.soc 文件,前面定义了这个系列的 SoC 中有一个型号,因此在这个文件中填入下面内容:
choice
prompt "xx32f0 MCU Selection"
depends on SOC_SERIES_XX32F0
config SOC_XX32F0X
bool "SOC_XX32F0X"
endchoice
到此为止,soc 下面的内容就写好了,当然,后面还会逐步添加东西,现在,回到 zephyr 目录下
cd ../../../../
dts
接下来填写 dts 中的内容,到 dts/arm 文件夹下,创建 xx32 文件夹,在 xx32 文件夹下创建 xx32f0 文件夹
cd dts/arm
mkdir xx32
cd xx32
mkdir xx32f0
在 xx32f0 下创建 xx32f0.dtsi 文件,并填入以下内容:
#include <mem.h>
#include <freq.h>
#include <arm/armv7-m.dtsi>
/ {
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-m3";
reg = <0>;
};
};
clocks {
clk_sys: clk_sys {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <DT_FREQ_M(96)>;
};
};
soc {
sram0: memory@20000000 {
compatible = "mmio-sram";
reg = <0x20000000 0x0001C000>;
};
flash0: flash@8000000 {
compatible = "soc-nv-flash";
};
};
};
&nvic {
arm,num-irq-priority-bits = <3>;
};
Flash 的起始地址是 0x08000000,大小会在下一个文件中定义,SRAM 的起始地址在 0x20000000,大小128KB。
创建 xx32f0x.dtsi 文件,填入以下内容:
#include <xx32/xx32f0/xx32f0.dtsi>
&flash0 {
reg = <0x08000000 DT_SIZE_K(512)>;
};
定义 Flash 的大小为 256KB。
dts 的内容创建完毕,后面会在这两个文件中填入大量的内容。回到 zephyr 目录下:
cd ../../../..
boards
按照之前提到的创建 Custom Board 的方法,在 zephyr/boards/arm 下创建 Custom Board,并 cd 到 Custom Board 中,这里我定义 Custom Board 的名字为 xx32f0board
在 xx32f0board.dts 中填入以下内容:
/dts-v1/;
#include <xx32/xx32f0/xx32f0x.dtsi>
/ {
model = "xx32f0board";
compatible = "xx32f0";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
};
};
xx32f0board_defconfig 中填入以下内容:
CONFIG_SOC_SERIES_XX32F0=y
CONFIG_SOC_XX32F0X=y
其它的文件照着别的板子一个个添加就行。
test
按照前文中提到的创建应用的方法,编写一个简单的main()函数:
int main(void)
{
while(1)
{}
}
然后测试下编译:
cd app
west build -b custom_board
如果能够成功编译通过,就差不多可以说明前面做的工作已经完成,接下来可以试着在芯片上跑下,由于还没有适配 west flash
指令,因此需要使用第三方工具来实现下载和调试的功能,我这里使用的是 SEGGER 的 OZONE 工具,只能使用 J-Link 下载调试程序,具体用法网上很多资料,这里不再展开讲述。
至于下载调试所需的 elf 文件,则是在 build/zephyr 文件夹下,找到这个 elf 文件,然后下载,运行,在 OZONE 上可以看到,程序能够成功执行,能够打断点,甚至有时候暂停能够看到切换上下文的操作,由此证明内核的移植已经成功。
至于板子,后续开发完成之后,可以将这个板子的文件放到 zephyr 的 boards 文件夹中。
Summary
本文讲述了如何在 Zephyr 中创建一个 Custom SoC,但仅仅讲述了内核的适配,驱动等代买还没有添加,将在后面的文章中继续讲解。
本文在 zephyr 中添加的代码以及 app 代码可见本文绑定的资源。