小熊派micro HDF PWM驱动开发 原创 精华
小熊派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_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、编写驱动
在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、编写构建脚本
最后要将我们编写好的驱动文件加入到编译构建系统中:
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: