> 文档中心 > OpenHarmony HDF PWM开发 基于STM32MP1

OpenHarmony HDF PWM开发 基于STM32MP1

文章目录

      • 1、导入stm32mp1 HAL库文件
      • 2、使能HDF PWM框架
      • 3、编写驱动代码
        • 1、配置文件
        • 2、编写驱动
        • 3、编写构建脚本
      • 4、效果

本文介绍如何在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、编写驱动

earpi-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_tpFLatency;    uint32_tuwTimclock;    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 1int 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);//使能pwmint32_t PwmEnable(DevHandle handle);int32_t PwmDisable(DevHandle handle);//设置pwmint32_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,};//设置pwmstatic 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:

在这里插入图片描述
在这里插入图片描述