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_ENABLED
和HAL_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: