Zephyr下SOC驱动修改方法

Creative Commons
本作品采用知识共享署名

本文说明在zephyr支持的SOC驱动无法满足应用需求时的处理方式。

对于SOC驱动,Zephyr是将各个SOC Vender提供的HAL SDK以module的形式导入,然后在driver下按照Zephyr提供的驱动接口调用HAL API进行封装实现。这样就可以达到无论对于哪种SOC来说应用程序的驱动接口都是统一的,可以让应用一次编程多次使用。虽然Zephyr对SOC的驱动支持非常丰富,但不同的SOC驱动类别不尽相同,会存在没有驱动模型或者有驱动模型没有实现的情况。另外出于应用对HAL驱动的要求,可能存在修改HAL接口的需求,这会让现存的Zephyr驱动无法兼容。处理以上问题分为常规做法和快速做法:常规做法的目标是所有的修改最后都能进入zephyr主线,但对应的周期可能会比较长;快速做法是不修改Zephyr主线,在应用程序目录进行修改。本文详细讨论快速做法,并简要说明常规做法。

常规做法 VS 快速做法

使用Zephyr遇到的几种问题和常规做法:

1. 有驱动模型但对应的SOC驱动没有实现

常规做法

一般情况下,这种情况是基于现有的驱动模型对SOC驱动进行实现,这种修改是比较容易merge进入zephyr主线的。例如i2s的驱动接口已有i2s.h进行定义,而Zephyr并没有实现nxp rt系列i2s驱动,那么可以按照i2s.h的接口进行驱动实现。

快速做法

直接在应用中使用HAL API而不使用Zephyr驱动,并不推荐该方法,长远来看更适合用常规方法。

2. 无驱动模型

常规做法

这种方式比较复杂,因为无驱动模型,也就是Zephyr还没有对该类驱动进行抽象定义,因此要先提issue,在社区说明要添加的驱动模型及API,讨论到一定阶段后,按照讨论结果进行驱动模型接口定义,然后再进行驱动实现。由于Zephyr有专门的API管理流程(参考Zephyr API生命周期简介),因此该过程会非常慎重,这也将导致实现周期很长。

快速做法

直接在应用中使用HAL API。

3. SOC驱动已实现但不符合需求

常规做法

一种是SOC Vender HAL 提供的驱动精度不够,需要进行HAL API修改。这种情况会涉及到SOC Vender HAL修改,提交后再进行Zephyr驱动修改,过程也会比较漫长。
另一种是Zephyr驱动模型接口并不适合特殊应用的需求,这就需要对驱动模型API进行修改。一般来说Zephyr的驱动模型都是软件经验积累和常规需求的综合结果,除非有非常充分的理由和普遍的适用范围,一般是不会接受稳定接口的修改。

快速做法

使用out of tree driver方法,按照Zephyr驱动模型另行实现驱动,而Vender HAL API的修改也纳入到Zephyr外部驱动实现中。
直接在应用中使用HAL API。

快速做法实现

1. 直接在应用中使用HAL API

a. API使用

目前Zephyr的构建系统中已经添加了所有module hal的头文件路径,因此只要在应用中include了对应的头文件,就可以直接调用,例如Zephyr并没有实现rt1052的i2s驱动,那么可以通过下面的方法直接访问rt1052 sai来实现i2s功能,下面就是sai初始化的示例代码

1
2
3
4
5
6
#include "fsl_sai.h"
void * i2s_init()
{
...
SAI_TxInit(i2s->i2sdev.base, &config);
}

但只是include头文件最后链接的时候会提示找不到SAI_TxInit,这是因为zephyr并没有将fsl_sai.c加入编译,在应用程序的CMakeList.txt中将其加入就可以链接过

1
zephyr_library_sources(${NXP_HAL_DRV}/fsl_sai.c)

b. 中断的安装

如果使用HAL API实现的驱动需要安装中断处理函数,可以直接使用Zephyr提供的IRQ_CONNECT进行安装,例如

1
2
3
4
5
6
7
8
9
10
static void i2s_isr(void *arg)
{
//isr flow
}

void i2s_init()
{
...
IRQ_CONNECT(SAI1_IRQ, 0, i2s_isr, &i2s_devices[0].i2sdev, 0);
}

2. 使用out of tree driver方法

Zephyr提供了out of tree driver方法,这里我们更进一步在Out of tree driver使用Zephyr的驱动模型接口,让Out of tree driver直接替代掉Zephyr本身的驱动。
这里以PWM为例进行说明,rt1052 HAL API提供的PWM设置API如下

1
2
3
4
5
void PWM_UpdatePwmDutycycle(PWM_Type *base,
pwm_submodule_t subModule,
pwm_channels_t pwmSignal,
pwm_mode_t currPwmMode,
uint8_t dutyCyclePercent)

Zephyr的pwm_mcux.c使用上面API进行pwm占空比设置,参数dutyCyclePercent是占空比,精度是1%,这种情况下用PWM对舵机进行控制精度是不够的。要改善这种方法就需要修改HAL对占空比的设置方法,提供新的API。但新的API短时间是无法进入到Vender HAL中,因此我们使用out of tree driver的方法来替换掉Zephyr的PWM驱动。

a. 建立out of tree 驱动目录

在Zephyr应用程序目录下建立驱动目录driver

1
2
3
4
5
6
7
8
9
10
11
12
.
├── CMakeLists.txt
├── LICENSE
├── README.md
├── SwiftApp
├── driver
├── export
├── export.py
├── include
├── prj.conf
├── src
└── xip.conf

在应用目录的CMakeList中添加如下类容:

1
2
3
list(APPEND ZEPHYR_EXTRA_MODULES
${CMAKE_CURRENT_SOURCE_DIR}/driver
)

b. 向driver目录中添加驱动代码

driver下的目录结构如下,因为是以module的形式加入,因此一定要包含一层zephyr目录

1
2
3
4
5
6
.
└── zephyr
├── CMakeLists.txt
├── Kconfig
├── ext_pwm_mcux.c
└── module.yml

CMakeList.txt内容如下

1
2
3
4
5
6
7
8
9
set(NXP_HAL_DRV ${ZEPHYR_BASE}/../modules/hal/nxp/mcux/drivers/imx)

if(CONFIG_EXT_PWM)
zephyr_library()
zephyr_library_sources(
ext_pwm_mcux.c
${NXP_HAL_DRV}/fsl_pwm.c
)
endif()

因为我们要关闭原本的CONFIG_PWM不使用Zephyr的本身的PWM驱动,因此Zephyr在构建的时候不会将fsl_pwm.c加入,所以这里需要将fsl_pwm.c加入。
Kconfig内容如下

1
2
config EXT_PWM
bool "Enable ext PWM"

module.yml内如如下

1
2
3
build:
cmake: zephyr
kconfig: zephyr/Kconfig

c. pwm驱动

ext_pwm_mcux.c是驱动代码,完全照搬zephyr内的pwm_mcux.c,添加一个新的HAL API用于精细化控制占空比–直接指定高电平的宽度,而实现是直接使用写PWM的寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static status_t PWM_SetupPwmEdgeAlignedCycles(PWM_Type *base,
pwm_submodule_t subModule,
const pwm_signal_param_t *chnlParams,
uint8_t numOfChnls,
uint16_t pulseCnt,
uint16_t pwmHighPulse)
{
...
if (chnlParams->pwmChannel == kPWM_PwmA) {
base->SM[subModule].VAL2 = 0;
base->SM[subModule].VAL3 = pwmHighPulse;
} else {
base->SM[subModule].VAL4 = 0;
base->SM[subModule].VAL5 = pwmHighPulse;
}
}

然后在mcux_pwm_pin_set中调用PWM_SetupPwmEdgeAlignedCycles来设置PWM。

d. 扩展pwm驱动的使用

由于我们只是在驱动代码上替换掉Zephyr的驱动,所以其它的一切都不变,任然是在board的dts中加入pwm节点,例如

1
2
3
4
5
6
7
8
9
10
11
&flexpwm2_pwm0 {
status = "okay";
};

&flexpwm2_pwm1 {
status = "okay";
};

&flexpwm2_pwm2 {
status = "okay";
};

在prj.conf配置启用PWM,一定注意不能配置CONFIG_PWM,而使用CONFIG_EXT_PWM,为了保险起见可以多加一句CONFIG_PWM=n

1
2
CONFIG_PWM=n
CONFIG_EXT_PWM=y

然后就可以应用中按照zephyr的驱动操作方式使用pwm驱动,实际操作的就是后面添加的ext_pwm

1
2
pwmdev = device_get_binding(DT_NODELABEL(flexpwm2_pwm0));
pwm_pin_set_cycles(pwmdev, 0, 20000, 500, 0);

参考

https://docs.zephyrproject.org/latest/samples/application_development/out_of_tree_driver/README.html
https://github.com/zephyrproject-rtos/zephyr/tree/master/samples/application_development/out_of_tree_driver
https://docs.zephyrproject.org/latest/reference/overview.html
https://docs.zephyrproject.org/latest/development_process/api_lifecycle.html