小熊派micro HDF PWM驱动开发 原创 精华

杀手来过
发布于 2022-4-9 09:24
浏览
3收藏

小熊派micro HDF PWM驱动开发

本文介绍如何在HDF PWM框架中开发stm32mp1的pwm外设。

stm32mp1的大部分外设可以使用st提供的HAL库来开发。hal库是st官网为所有st芯片提供的sdk包,使开发者可以免去操作寄存器的操作,直接使用库函数完成芯片外设的配置。

STM32MP1 HAL库地址:mirrors_STMicroelectronics/STM32CubeMP1 (gitee.com)

为了使STM32MP1 的PWM驱动适配到HDF框架,就需要了解PWM框架如何开发,可参考官网文档:

zh-cn/device-dev/driver/driver-platform-pwm-develop.md · OpenHarmony/docs - 码云 - 开源中国 (gitee.com)

综上所述,可以将整个开发步骤分三步走:

1、导入STM32HAL库

2、使能HDF PWM驱动

3、编写PWM驱动

1、导入stm32mp1 HAL库文件

下载HAL库完成后,将HAL库中的以下文件添加到bearpi-micro\device\st\drivers\stm32mp1xx_hal\STM32MP1xx_HAL_Driver的Inc目录中。

  • stm32mp1xx_hal_tim.h
  • stm32mp1xx_hal_tim_ex.h
  • stm32mp1xx_hal_dma.h
  • stm32mp1xx_hal_dma_ex.h

将 以下文件添加到bearpi-micro\device\st\drivers\stm32mp1xx_hal\STM32MP1xx_HAL_Driver的Src目录中:

  • stm32mp1xx_hal_tim.c
  • stm32mp1xx_hal_tim_ex.c
  • stm32mp1xx_hal_dma.c
  • stm32mp1xx_hal_dma_ex.c

然后将四个源文件加入编译构建体系:编辑bearpi-micro\device\st\drivers\stm32mp1xx_hal\STM32MP1xx_HAL_Driver\BUILD.gn

在sources添加如下两项:

    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_tim_ex.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_tim.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_dma_ex.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_dma.c",

添加后如下所示,这样编译系统就会编译这两个文件:

import("//drivers/adapter/khdf/liteos/hdf.gni")
module_name = "hdf_stm32mp1xx_hal"
hdf_driver(module_name) {
  sources = [
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_tim_ex.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_tim.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_dma_ex.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_dma.c",
    "STM32MP1xx_HAL_Driver/Src/system_stm32mp1xx.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_gpio.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_i2c.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_exti.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_ltdc.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_rcc.c",
    "STM32MP1xx_HAL_Driver/Src/stm32mp1xx_hal_rcc_ex.c",

  ]
  include_dirs = [
    ".",
    "//device/st/drivers/stm32mp1xx_hal/STM32MP1xx_HAL_Driver/Inc",

  ]
}

最后,还需要配置一个宏定义,来使能TIM,编辑 STM32MP1xx_HAL_Driver\Inc\stm32mp1xx_hal_conf.h,将 HAL_TIM_MODULE_ENABLEDHAL_DMA_MODULE_ENABLED使能 ,如下所示:

#define HAL_TIM_MODULE_ENABLED   
#define HAL_DMA_MODULE_ENABLED   

到此,我们就能使用stm32mp1xx_hal_tim.h 提供的函数来开发PWM驱动。

2、使能HDF PWM框架

由于HDF 框架是在内核中的,需要在内核中使能PWM驱动。

编辑 vendor/bearpi/bearpi_hm_micro/kernel_configs/debug_tee.config,将 LOSCFG_DRIVERS_HDF_PLATFORM_PWM设置为y,如下所示:

LOSCFG_DRIVERS_HDF_PLATFORM_PWM = y

开启该宏之后,就会编译HDF 的PWM框架,就能在驱动中使用PWM框架的pwm_core.h和pwm_if.h。

3、编写驱动代码

按照官网文档的指示进行编写:PWM框架开发

同样分为三步走。

1、配置文件

一共需要修改两个hcs文件,分别是:device_info.hcs和pwm_config.hcs

首先 编辑st\bearpi_hm_micro\liteos_a\hdf_config\device_info\device_info.hcs增加pwm节点,如下所示:

该节点应该是在 platform :: host节点下创建。其中policy=1表示只对内核发布驱动服务,moduleName必须为HDF_PLATFORM_PWM,serviceName必须以HDF_PLATFORM_PWM_开头,后面的数字用来区别不同的pwm外设。

这里我设置为2是因为我使用TIM2作为PWM的源。

            device_pwm :: device{
                device0 :: deviceNode{
                    policy = 1;
                    priority = 12;
                    permission = 0777;
                    moduleName = "HDF_PLATFORM_PWM";
                    serviceName = "HDF_PLATFORM_PWM_2";
                    deviceMatchAttr = "st_stm32mp157_pwm_2";
                }
            }

第二个配置文件就是自己创建的,在\bearpi_hm_micro\liteos_a\hdf_config\录下创建pwm目录,在目录中创建 pwm_config.hcs,并在其中添加以下内容:

其中PWM的计数频率是1MHZ,在代码中写死,可以修改;physics_register表示TIM的寄存器基地址,根据STM32MP1参考手册可知TIM2的寄存器地址是x40000000寄存器地址范围是0x70。

下面的配置表示:使用TIM2 Channel 1作为PWM输出,周期是1ms,占空比是50%

root {
    platform {
        pwm_config {
            //1mhz
            template pwm_device {
                Period = 1000;	//1000*0.1us=100us=10khz
                Pulse = 500; 	//500
                Polarity = 0;   //high
                IdleState = 0;  //reset
                channel = 0;    //channel_1 PA5
                match_attr = "";
                num = 0;
                physics_register = 0X40000000;
                register_size = 0X70;
            }

            device_0X40000000 :: pwm_device {
                match_attr = "st_stm32mp157_pwm_2";
                num = 2;
                physics_register = 0X40000000;
            }
        }
    }
}

将上诉两个文件编辑完成后,在t\bearpi_hm_micro\liteos_a\hdf_config\hdf.hcs添加一行:

#include "pwm/pwm_config.hcs"

2、编写驱动

bearpi-micro\device\st\drivers录下新建pwm目录,并创建驱动源文件stm32mp1_pwm.c,以及BUILD.gn

首先定义驱动入口对象:

// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量
struct HdfDriverEntry g_pwmDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_PLATFORM_PWM",	//必须与device_info.hcs中的字段一样,用于与驱动设备资源匹配
    .Bind = HdfPwmDriverBind,
    .Init = HdfPwmDriverInit,
    .Release = HdfPwnDriverRelease,
};

// 调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_pwmDriverEntry);

以及自定义一个PWM对象:

//私有pwm结构体
struct StmPwm {
    TIM_HandleTypeDef htim;		//HAL TIM必须
    struct PwmDev dev;			//HDF PWM核心层必须
    TIM_OC_InitTypeDef sConfig; //HAL PWM必须 
    uint32_t physics_register;  //TIM基地址
    uint32_t register_size;		//地址范围
    uint32_t channel;			//pwm channel
};

初始化函数逻辑很简单,就是使用 struct StmPwm 对象中的成员去调用各个库的初始化函数。

首先引入PWM所需的头文件:

//stm hal库
#include "stm32mp1xx_hal.h"
#include "stm32mp1xx_hal_tim.h"
//hdf pwm
#include "pwm_core.h"
#include "pwm_if.h"

驱动初始化函数:首先读取上文所配置的信息到StmPwm对象中,然后将PwmDev添加到HDF PWM框架中,这样就能使用HDF PWM框架的功能;最后调用STM32MP1 HAL库函数,初始化TIM2的PWM模式,设置对应的GPIO复用功能,开始输出PWM。

// 驱动自身业务初始的接口(设置IO口为输出) HDF框架在加载驱动的时候,会将私有配置信息保存在HdfDeviceObject 中的property里面
int32_t HdfPwmDriverInit(struct HdfDeviceObject *device)
{
    dprintf("%s enter\r\n",__func__);

    RCC_ClkInitTypeDef    clkconfig;
    uint32_t              pFLatency;
    uint32_t              uwTimclock;

    sp = (struct StmPwm *)OsalMemCalloc(sizeof(*sp));

    //读取配置文件
    readHcs(device);

    //获取时钟频率
    HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);   
    __HAL_RCC_TIM2_CLK_ENABLE();
    uwTimclock = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_TIMG1);
    //dprintf(" uwTimclock = %d\r\n",uwTimclock);
	//配置TIM的计数频率为10MHZ
    sp->htim.Init.Prescaler = (uint32_t) ((uwTimclock / 10000000U) - 1U);    
    sp->htim.Init.CounterMode = TIM_COUNTERMODE_UP;
    sp->htim.Init.ClockDivision = 0U;
    sp->htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    sp->sConfig.OCMode = TIM_OCMODE_PWM1;
    sp->sConfig.OCFastMode = TIM_OCFAST_DISABLE;

    sp->dev.method = &g_pwmOps;
    sp->dev.cfg.duty = sp->sConfig.Pulse;  
    sp->dev.cfg.period = sp->htim.Init.Period;    
    sp->dev.cfg.polarity = sp->sConfig.OCPolarity;    
    sp->dev.cfg.status = PWM_ENABLE_STATUS;
    sp->dev.cfg.number = 10000; 

    sp->dev.busy = false;

    //添加到核心层
    if (PwmDeviceAdd(device, &(sp->dev)) != HDF_SUCCESS) {
        
        return HDF_FAILURE;
    }
	//对TIM2寄存器基地址进行映射
    sp->htim.Instance = (TIM_TypeDef *)OsalIoRemap(sp->physics_register, sp->register_size);
    if (sp->htim.Instance == NULL) {
        dprintf("error OsalIoRemap for htim \r\n");
        return -1;
    }
    //初始化PWM寄存器
    if (HAL_TIM_PWM_Init(&sp->htim) == HAL_OK)
    {
        dprintf("pwm init ok config channel \r\n");

        HAL_TIM_PWM_ConfigChannel(&sp->htim, &sp->sConfig, sp->channel);
		//初始化gpio
        HAL_TIM_MspPostInit(&sp->htim);
        //开始输出PWM
        HAL_TIM_PWM_Start(&sp->htim,sp->channel);
    }else{
        return -1;
    }
    
    
    return HDF_SUCCESS;
}

读取上诉配置文件到StmPwm对象:

//读取配置文件   pa5 tim2 channel 1
int readHcs(struct HdfDeviceObject *obj)
{
    dprintf("%s enter\r\n",__func__);


    struct DeviceResourceIface *iface = NULL;

    iface = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
    if (iface == NULL || iface->GetUint32 == NULL) {
        HDF_LOGE("%s: face is invalid", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "Period", &sp->htim.Init.Period, 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "Pulse", &sp->sConfig.Pulse, 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "physics_register", &sp->physics_register, 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "register_size", &sp->register_size, 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "channel", &sp->channel, 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "Polarity", &sp->sConfig.OCPolarity , 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "IdleState", &sp->sConfig.OCIdleState , 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }
    if (iface->GetUint32(obj->property, "IdleState", &sp->dev.num , 0) != HDF_SUCCESS) {
        HDF_LOGE("%s: read num fail", __func__);
        return HDF_FAILURE;
    }

    return 0;
}

GPIOA_5 复用为PWM模式

//初始化gpio口
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    dprintf("%s enter\r\n",__func__);
    GPIO_InitTypeDef GPIO_InitStruct;

    //gpioa addr
    unsigned char *gpioa= (unsigned char *)OsalIoRemap(GPIOA_PHYADDR, GPIOA_SIZE);
    /**TIM2 GPIO Configuration    
    PA5     ------> TIM2_CH1 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init((GPIO_TypeDef *)gpioa, &GPIO_InitStruct);
  
}

到此初始化任务就基本完成,那么HDF PWM框架如何来使用我们的PWM驱动呢?这就要提到 PwmMethod,通过设置PwmMethod的函数,提供接口给PWM框架,主要是提供StmPwmSetConfig。

struct PwmMethod g_pwmOps = {
    .setConfig = StmPwmSetConfig,
};

HDF_PWM框架是通过pwm_if.h文件给内核提供PWM功能的,我们来看pwm_if.h给内核提供了什么。

//获取pwm句柄
DevHandle PwmOpen(uint32_t num);
void PwmClose(DevHandle handle);
//设置pwm周期
int32_t PwmSetPeriod(DevHandle handle, uint32_t period);
//设置占空比
int32_t PwmSetDuty(DevHandle handle, uint32_t duty);
//设置极性
int32_t PwmSetPolarity(DevHandle handle, uint8_t polarity);
//使能pwm
int32_t PwmEnable(DevHandle handle);
int32_t PwmDisable(DevHandle handle);
//设置pwm
int32_t PwmSetConfig(DevHandle handle, struct PwmConfig *config);
int32_t PwmGetConfig(DevHandle handle, struct PwmConfig *config);

可以看到PWM框架提供给内核设置PWM参数的接口,这些接口最终会调用我们编写的驱动,那么我们的驱动应该如何实现上述的功能?答案就在:StmPwmSetConfig,所有的接口最终都会调用PwmSetConfig()而间接调用我们编写的StmPwmSetConfig()。

在StmPwmSetConfig()中会对config参数进行检查,然后根据config参数去操作PWM外设,具体而言就是调用HAL库的PWM函数去实现:

//给pwm框架注册的函数
struct PwmMethod g_pwmOps = {
    .setConfig = StmPwmSetConfig,
};
//设置pwm
static int32_t StmPwmSetConfig(struct PwmDev *pwm, struct PwmConfig *config)
{

    if (pwm->cfg.polarity != config->polarity ) {
        HDF_LOGE("%s: not support set pwm polarity", __func__);
        return HDF_ERR_NOT_SUPPORT;
    }

    if (config->status == PWM_DISABLE_STATUS) {

        StmPwmDisable();
        return HDF_SUCCESS;
    }

    if (config->polarity != PWM_NORMAL_POLARITY && config->polarity != PWM_INVERTED_POLARITY) {
        HDF_LOGE("%s: polarity %u is invalid", __func__, config->polarity);
        return HDF_ERR_INVALID_PARAM;
    }

    if (config->period < 0) {
        HDF_LOGE("%s: period %u is not support, min period %u", __func__, config->period, 0);
        return HDF_ERR_INVALID_PARAM;
    }
    if (config->duty < 1 || config->duty > config->period) {
        HDF_LOGE("%s: duty %u is not support, min dutyCycle 1 max dutyCycle %u",
            __func__, config->duty, config->period);
        return HDF_ERR_INVALID_PARAM;
    }
    //暂停pwm,更新配置
    StmPwmDisable();

    if (pwm->cfg.polarity != config->polarity) {
        StmPwmSetPolarity( config->polarity);
    }
    StmPwmSetPeriod( config->period);
    StmPwmSetDuty( config->duty);
    //继续输出

    if (config->number == 0) {
        StmPwmAlwaysOutput();
    } else {
        StmPwmOutputNumberSquareWaves( config->number);
    }
    return HDF_SUCCESS;
}

HAL库实现配置PWM的方法:

static inline void StmPwmDisable()
{
    HAL_TIM_PWM_Stop(&sp->htim, sp->channel);
}

static inline void StmPwmSetPeriod(uint32_t us)
{
    sp->htim.Init.Period = us;
    TIM_Base_SetConfig(sp->htim.Instance, &sp->htim.Init);
}
static inline void StmPwmSetDuty(uint32_t us)
{
    sp->sConfig.Pulse = us;
    HAL_TIM_PWM_ConfigChannel(&sp->htim, &sp->sConfig, sp->channel);
}
static inline void StmPwmSetPolarity(uint32_t polarity)
{
    sp->sConfig.OCPolarity = polarity;
    HAL_TIM_PWM_ConfigChannel(&sp->htim, &sp->sConfig, sp->channel);    
}
static inline void StmPwmAlwaysOutput()
{
    HAL_TIM_PWM_Start(&sp->htim, sp->channel);
}

static inline void StmPwmOutputNumberSquareWaves(uint32_t num)
{

    HAL_TIM_PWM_Start(&sp->htim, sp->channel);
}

3、编写构建脚本

最后要将我们编写好的驱动文件加入到编译构建系统中:

编辑BUILD.gn

import("//drivers/adapter/khdf/liteos/hdf.gni")
module_switch = defined(LOSCFG_DRIVERS_HDF_PLATFORM_PWM)
hdf_driver("hdf_pwm") {
    sources = [
    "stm32mp1_pwm.c",
    ]
    include_dirs = [
     "." ,
     "//device/st/drivers/stm32mp1xx_hal/STM32MP1xx_HAL_Driver/Inc",
  ]
}

并修改device/st/drivers/BUILD.gn,在dep添加pwm:

group("drivers") {
  deps = [
    "pwm",
    "uart",
    "iwdg",
    "i2c",
    "gpio",
    "led",
    "button",
    "sample",
    "mem",
    "stm32mp1xx_hal",
    "wifi/driver/hi3881",
    "wifi/driver:hdf_vendor_wifi",
  ]
}

4、效果

完成PWM驱动的编写后,就可以使用bearpi-micro\drivers\framework\include\platform\pwm_if.h里的函数来控制PWM波。如图是使用逻辑分析测量到的GPIOA_5引脚上的PWM信号:

计数频率10MHZ,计数值1000,那么PWM的频率就是10MHZ/1000=10KHZ:

小熊派micro HDF PWM驱动开发 -鸿蒙开发者社区小熊派micro HDF PWM驱动开发 -鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
3
收藏 3
回复
举报
回复
    相关推荐