本文使用开发板为STM32F103ZET6最小系统板和STM32H743VIT6最小系统板,不涉及架构移植。
一、STM32F103ZET6最小系统板
1.查找相近开发板
首先你应该清楚自己开发板的主控属于哪个系列,比如我这里使用的是STM32F103ZET6,它属于F103xE系列。然后在SDK里面搜索:
SOC_STM32F103xE
我这里就搜索到了一块使用相同SOC的开发板waveshare的open103z:
注意,搜索时应该找boards目录下的比如我这里的:
2.修改配置
找到相近开发板后把整个文件夹在相同目录下复制一个,然后修改成你自己开发板的名字,比如我这里命名为stm32f103zet6:
然后修改文件夹里面别人的开发板名字为自己的:
注意如果你要使用west flash烧写程序,必须指定使用哪个烧写工具(-r):
如果使用stlink,这里需要安装openocd,还就是你需要修改support文件夹下的openocd.cfg。这里我复制的文件夹里面已经修改好了,但是因为手上没有stlink,也没有下载openocd,所以也没有试过:
然后就是修改Kconfig.board和Kconfig.defconfig文件中的板子名字:
然后在defconfig里面有一些只要选择了此开发板就会启用的一些默认配置项,根据自己的需求可以选择开启或关闭,我建议基础的都不要修改,如下(不过这会增加代码量,使用这个默认配置编译跑马灯demo,FLASH使用大概13KB):
最重要的就是开发板的设备树文件,它的作用就是完成你这个开发板所使用外设的基础配置。用过ST的标准库的都知道,ST有专门写好一个头文件,里面包含了芯片所有的寄存器名字和地址的映射,我们实际使用的时候,直接用名字就可以访问对应寄存器地址,而不再需要再去查芯片手册一个个的找,现在那个头文件就被替换为了设备树文件:
这些文件都是事先写好的可以直接使用的,我们的设备树文件只需要关注我们的开发板怎么使用这些外设就可以了。 因为我使用的是最小系统板,所以使用外设有限,初始我只配置了LED,按键,串口,USB,和一个SPI flash。注意这里配置好,你还需要到代码里去初始化和配置才能使用,只是使用的时候可以直接用设备树文件中我们设置的名字,这样更加方便,而且如果后续要更改板子,改动起来会更快更方便。而且配置设备树就是依葫芦画瓢,比如你使用了I2C,你完全可以找其他ST系列的板子中有使用I2C的抄过来,只需要稍作修改。比如我这里配置的:
控制台和shell的配置是你启用了zephyr中的相关模块才会有用。 其他模块配置:
这里的RCC就是系统时钟。其他外设:
在包含的头文件里面(stm32f103z(c-d-e)tx-pinctrl.dtsi)有这些引脚的定义,比如串口的PA9引脚:
在头文件stm32f1-pinctrl.h里有这个函数的具体解释:
3.实际测试
修改好之后,我们编译一个zephyr自带的跑马灯demo试一下,使用指令:
west build -p always -b stm32f103zet6 -d zet6 .\samples\basic\blinky\
其中常用的-p表示是否重新编译,常用的是auto和always,auto表示没有文件改动不会重新编译,而always表示总是会重新编译。-b表示所使用的开发板,这里我用的就是刚才自定义的开发板。-d表示生成的构建目录名字,这里我取名zet6。最后跟的是demo的目录。
最终正常编译后,输出log如下:
注意我前面提到的,在设备树文件中我取了一些别名,查看跑马灯demo的main.c可以看到获取设备树文件的宏定义使用的参数正是led0:
所以如果你的名字不是这个可能会编译出错。
编译完成后,可以使用以下指令下载:
west flash -d zet6 -r jlink
如果使用其他工具则修改-r 参数比如:
-r stlink或-r nrfjprog
如果连接正常,则可以看到下载成功:
当然也可以使用以下指令调试:
west debug -d zet6 -r jlink
正常使用指令后会,终端log会停在下图所示:
同时会弹出来一个Jlink的GDB-Server的窗口,此时只需要在终端按照提示输入c即可:
然后GDB串口会显示正常连接:
剩下的就是使用gdb调试了,但是其实我觉得使用Ozone这种带GUI的更方便和直观。
二、STM32H743VIT6最小系统板
为什么在上面的基础上又再讲一遍H743的移植呢?
很简单,H743比F103复杂的多,所以移植的时候需要设置的也多,这里会先把最简单的修改讲一下,后续可能会出该芯片其他外设使用的教程。
首先还是一样的方法,我们搜索SOC_STM32H743,可以搜到相近的开发板:
和上一章一样的方法,复制该文件夹然后修改名字,比如我这里使用的是WeAct的H743VIT6的开发板,所以我把名字都改为了:
其中arduino_r3_connector.dtsi这个文件是为了兼容arduino开发板引脚的配置文件,我这里不使用arduino开发板,所以不需要管它们的引脚匹配这个文件其实删除就行。
然后如果使用STLINK的话,需要修改support文件夹下的openocd.cfg,我这里也没有使用过,所以不做修改。
而board.cmake里面主要支持各种调试器,而调试器里面需要修改目标芯片这里我把H743ZI修改为H743VI:
其他几个文件可以依照上文修改,其实主要就是修改名字,而主要需要注意修改的文件就是.dts文件。
首先原来的.dts所包含的头文件一定要注意,比如我这里复制的源文件:
原来的芯片是H743ZIT6,而我的开发板芯片是H743VIT6,而且为了兼容arduino的设备树文件已经被我删除,所以修改为:
然后就是我上文提到的,标准范例中有使用到一个led和一个按键,想测试这两个demo,需要修改led和按键:
因为前文已经讲过所以不再赘述。这里主要想说的是系统时钟如何配置,因为这直接决定了芯片能不能正常运行。我的建议是使用STM32CubeMX去配置时钟,确定没问题,再回到这个文件中修改,因为那个工具可以检查你的时钟配置是否合理,这很重要。
虽然这个工具有时候用起来会有点问题,但总归是个很有用的工具,上图就是我的时钟配置,然后回到设备树文件,我们完全可以看名字把各个数值填进去:
其实,如果你鼠标放在对应的键上时,可以看到设备树是有提示的,比如我放在d1cpre上:
上面提示到:
D1时钟域,CPU1时钟预分频器,是为了设置HCLK时钟频率(Cortex-M的Systick的时钟源)低于SYSCLK时钟频率(实际核心频率) 。Zephyr目前不影响这两个时钟,当前也不允许设置这个预分频器,除非Zephyr的时钟子系统可以单独使用它们。
这里,Zephyr中这个分频器不允许设置为其他值(默认为1),也就是不分频,因为Zephyr中这两个时钟没有单独使用。假如我在这里设置成了2,可以看到VSCode会提示错误:
这是怎么实现的?这里就涉及到一个问题。.dts文件是如何被解读?更简单一点,为啥pll这个节点里有div-m这个键,而rcc里面没有?
答案是.yaml文件。 这也是一种语言,它在Zephyr里其中一个作用就是书写设备树文件不同节点的解析规则。如果你要进行架构移植,或者是某个新的外设,则必须自己写相对应的设备节点的解析规则。所有的相关文件在目录zephyr/dts/bindings下,比如这里的rcc配置在clock文件夹下的st,stm32h7-rcc.yaml文件中,你可以理解为这是一个dts节点的规则描述文件,它匹配的节点是:
而如果把鼠标放在我们的rcc节点上可以看到节点的完整信息:
这个compatible是何时加上的?
因为我们的节点使用的是&rcc{ },其实是覆盖重写,真正的原始rcc这个节点在头文件中。我们只是在我们这个开发板中的设备树文件重新把这个节点的一些键值给重设了。
回到我们刚才的那个.yaml文件中可以看到:
所以,当你所复制的开发板中没有你所需要使用的键时,可以在这里添加,然后还需要去源码中使用dts的宏解析设备树生成的头文件或是宏定义。另外,当有一些你不清楚的配置或者需要添加一些外设时,可以查看这些文件。
比如我这次所复制的开发板,一开始修改好设备树文件后,直接烧录无法启动,显示卡在LL_RCC_HSE_IsReady函数中,基本初步判定是时钟配置问题。通过单步调试发现会执行LL_RCC_HSE_EnableBypass函数,仔细查看设备树文件,发现所复制设备树文件中HSE时钟中有这么一个配置:
这牵扯到HSE两种模式(HSE晶体模式/HSE旁路模式),这里我是用的晶振使用HSE晶体模式即可,使用旁路模式反而会卡在那,所以把这一句去掉,板子就正常了。
更新 2023-04-21:
一开始只把板子调到可以正常运行了,后面配置两块外挂的flash的时候还没调完就没时间再搞了,有人想看就把dts放上来,仅供参考。
/*
* Copyright (c) 2020 Teslabs Engineering S.L.
*
* SPDX-License-Identifier: Apache-2.0
*/
/dts-v1/;
#include <st/h7/stm32h743Xi.dtsi>
#include <st/h7/stm32h743vitx-pinctrl.dtsi>
/ {
model = "WeAct H743VI";
compatible = "st,stm32h743vi";
chosen {
zephyr,console = &usart1;
zephyr,shell-uart = &usart1;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,dtcm = &dtcm;
zephyr,code-partition = &slot0_partition;
zephyr,display = &st7735r;
};
leds {
compatible = "gpio-leds";
blue_led: led_0 {
gpios = <&gpioe 3 GPIO_ACTIVE_HIGH>;
label = "LED-E3";
};
b_led: led_1 {
gpios = <&gpioe 10 GPIO_ACTIVE_HIGH>;
label = "b-led";
};
};
gpio_keys {
compatible = "gpio-keys";
k1: button_0 {
label = "User";
gpios = <&gpioc 13 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>;
};
};
aliases {
led0 = &blue_led;
led1 = &b_led;
sw0 = &k1;
watchdog0 = &iwdg;
spi-flash0 = &w25q64_1;
};
};
&clk_hse {
clock-frequency = <DT_FREQ_M(25)>;
status = "okay";
};
&pll {
div-m = <5>;
mul-n = <96>;
div-p = <2>;
div-q = <2>;
div-r = <2>;
clocks = <&clk_hse>;
status = "okay";
};
&rcc {
clocks = <&pll>;
clock-frequency = <DT_FREQ_M(240)>;
d1cpre = <1>;
hpre = <2>;
d1ppre = <1>;
d2ppre1 = <1>;
d2ppre2 = <1>;
d3ppre = <1>;
};
/*
&clk_hsi {
hsi-div = < 1 >;
status = "okay";
};
&rcc {
clocks = <&clk_hsi>;
clock-frequency = <DT_FREQ_M(64)>;
d1cpre = <1>;
hpre = <2>;
d1ppre = <2>;
d2ppre1 = <2>;
d2ppre2 = <2>;
d3ppre = <2>;
};
*/
&usart1 {
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
pinctrl-names = "default";
current-speed = <115200>;
status = "okay";
};
zephyr_udc0: &usbotg_fs {
pinctrl-0 = <&usb_otg_fs_dm_pa11 &usb_otg_fs_dp_pa12>;
pinctrl-names = "default";
status = "okay";
};
&rtc {
status = "okay";
};
/*
&i2c1 {
pinctrl-0 = <&i2c1_scl_pb8 &i2c1_sda_pb9>;
pinctrl-names = "default";
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
};
&die_temp {
status = "okay";
};
&rng {
status = "okay";
};
&quadspi {
status = "okay";
pinctrl-0 = <&quadspi_clk_pb2 &quadspi_bk1_ncs_pb6
&quadspi_bk1_io0_pd11 &quadspi_bk1_io1_pd12
&quadspi_bk1_io2_pe2 &quadspi_bk1_io3_pd13>;
pinctrl-names = "default";
w25q64_0: qspi-nor-flash@0 {
status = "okay";
compatible = "st,stm32-qspi-nor";
reg = <0>;
qspi-max-frequency = <80000000>;
size = <0x4000000>;
spi-bus-width = <4>;
};
};
*/
&spi1 {
status = "okay";
pinctrl-0 = <&spi1_sck_pb3 &spi1_mosi_pd7 &spi1_miso_pb4>;
pinctrl-names = "default";
cs-gpios = <&gpiod 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
w25q64_1: w25q64jv@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <8000000>;
jedec-id = [ef 70 17];
size = <0x4000000>;
has-dpd;
t-enter-dpd = <3000>;
t-exit-dpd = <3000>;
};
};
&spi4 {
status = "okay";
pinctrl-0 = <&spi4_sck_pe12 &spi4_mosi_pe14>;
pinctrl-names = "default";
// cs-gpios = <&gpioe 11 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
st7735r: st7735r@0 {
compatible = "sitronix,st7735r";
spi-max-frequency = <8000000>;
reg = <0>;
cmd-data-gpios = <&gpioe 13 GPIO_ACTIVE_LOW>; /* D9 */
reset-gpios = <&gpioa 2 GPIO_ACTIVE_LOW>; /* D8 */
width = <160>;
height = <80>;
caset = [00 00 00 4f];
raset = [00 00 00 9f];
x-offset = <0>;
y-offset = <0>;
madctl = <0x28>;
colmod = <0x55>;
vmctr1 = <0x0e>;
pwctr1 = [a2 02 84];
pwctr2 = [c5];
pwctr3 = [0a 00];
pwctr4 = [8a 2a];
pwctr5 = [8a ee];
frmctr1 = [01 2c 2d];
frmctr2 = [01 2c 2d];
frmctr3 = [01 2c 2d 01 2c 2d];
gamctrp1 = [02 1c 07 12 37 32 29 2d 29 25 2b 39 00 01 03 10];
gamctrn1 = [03 1d 07 06 2e 2c 29 2d 2e 2e 37 3f 00 00 02 10];
};
};
&backup_sram {
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/* 128KB for bootloader */
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 DT_SIZE_K(128)>;
read-only;
};
/* storage: 128KB for settings */
storage_partition: partition@20000 {
label = "storage";
reg = <0x00020000 DT_SIZE_K(128)>;
};
/* application image slot: 256KB */
slot0_partition: partition@40000 {
label = "image-0";
reg = <0x00040000 DT_SIZE_K(256)>;
};
/* backup slot: 256KB */
slot1_partition: partition@80000 {
label = "image-1";
reg = <0x00080000 DT_SIZE_K(256)>;
};
/* swap slot: 128KB */
scratch_partition: partition@c0000 {
label = "image-scratch";
reg = <0x000c0000 DT_SIZE_K(128)>;
};
};
};
&iwdg1 {
status = "okay";
};