Zephyr下SOC驱动修改方法

本文说明在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初始化的示例代码

#include "fsl_sai.h"
void * i2s_init()
{
    ...
    SAI_TxInit(i2s->i2sdev.base, &config);
}

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

zephyr_library_sources(${NXP_HAL_DRV}/fsl_sai.c)

b. 中断的安装 链接到标题

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

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如下

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

 .
 ├── CMakeLists.txt
 ├── LICENSE
 ├── README.md
 ├── SwiftApp
 ├── driver
 ├── export
 ├── export.py
 ├── include
 ├── prj.conf
 ├── src
 └── xip.conf

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

list(APPEND ZEPHYR_EXTRA_MODULES
  ${CMAKE_CURRENT_SOURCE_DIR}/driver
  )

b. 向driver目录中添加驱动代码 链接到标题

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

 .
 └── zephyr
     ├── CMakeLists.txt
     ├── Kconfig
     ├── ext_pwm_mcux.c
     └── module.yml

CMakeList.txt内容如下

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内容如下

config EXT_PWM
	bool "Enable ext PWM"

module.yml内如如下

build:
  cmake: zephyr
  kconfig: zephyr/Kconfig

c. pwm驱动 链接到标题

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

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节点,例如

&flexpwm2_pwm0 {
	status = "okay";
};

&flexpwm2_pwm1 {
	status = "okay";
};

&flexpwm2_pwm2 {
	status = "okay";
};

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

CONFIG_PWM=n
CONFIG_EXT_PWM=y

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

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