【简介】
PWM(Pulse Width Modulation , 脉冲宽度调制) ,在 Rt-thread 把PWM 设备抽象为PWM 设备驱动框架,设备驱动代码为components\drivers\misc\rt_drv_pwm.c ,应用层通过PWM设备驱动层的 rt_pwm_enable,rt_pwm_disable ,rt_pwm_set ,rt_pwm_set_period ,rt_pwm_set_pulse ,rt_pwm_set_dead_time ,rt_pwm_set_phase ,rt_pwm_get 接口调用底层提供的服务接口,rt_device_pwm_register 接口可以将PWM 设备注册到系统中。
rt-thread pwm 设备device 结构体,编写PWM设备驱动基于该结构体派生出新的PWM设备结构体。
struct rt_device_pwm; struct rt_pwm_ops { rt_err_t (*control)(struct rt_device_pwm *device, int cmd, void *arg); }; struct rt_device_pwm { struct rt_device parent; const struct rt_pwm_ops *ops; };
PWM 设备的配置通过以下结构体进行配置
struct rt_pwm_configuration { rt_uint32_t channel; /* 0 ~ n or 0 ~ -n, which depends on specific MCU requirements */ rt_uint32_t period; /* unit:ns 1ns~4.29s:1Ghz~0.23hz */ rt_uint32_t pulse; /* unit:ns (pulse<=period) */ rt_uint32_t dead_time; /* unit:ns */ rt_uint32_t phase; /*unit: degree, 0~360, which is the phase of pwm output, */ /* * RT_TRUE : The channel of pwm is complememtary. * RT_FALSE : The channel of pwm is nomal. */ rt_bool_t complementary; };
PWM 设备驱动的启用在env 的menuconfig 下开启
生成工程后,pwm 驱动框架的已经被加入工程
PWM 驱动适配底层和芯片的接口我们在Kconfig 中添加开启PWM驱动的配置开关
menuconfig BSP_USING_PWM bool "Enable PWM" default n select RT_USING_PWM
并在SConscript 文件中加入编译选项的配置,我们在drv_pwm 文件中适配PWM驱动
# add pwm driver if GetDepend('BSP_USING_PWM'): src += ['drv_pwm.c']
在menuconfig 中开启PWM 驱动
我们添加如下代码添加PWM设备
/** ***************************************************************************************************** * \file drv_pwm.c * *******************************************************************************************************/ /******************************************************************************************************** * Include header files * *******************************************************************************************************/ #include <rtthread.h> #include <rtdevice.h> #include "board.h" /******************************************************************************************************** * Private Macro definition * *******************************************************************************************************/ #define DBG_ENABLE #define DBG_TAG "drv.pwm" #define DBG_LEVEL DBG_LOG #define DBG_COLOR #include <rtdbg.h> /******************************************************************************************************** * Private Type Declarations * *******************************************************************************************************/ struct s32k14x_pwm { struct rt_device_pwm pwm_device; rt_uint8_t instance; ftm_user_config_t * user_cfg; ftm_pwm_param_t * pwm_cfg; ftm_state_t state; char *name; }; /******************************************************************************************************** * Private Variable Definitions * *******************************************************************************************************/ static struct s32k14x_pwm ftm0_pwm5 = { .instance = INST_FLEXTIMER_PWM_1, .user_cfg = &flexTimer_pwm_1_InitConfig, .pwm_cfg = &flexTimer_pwm_1_PwmConfig, .name = "pwm0" }; static FTM_Type * const g_pwm_ftmBase[FTM_INSTANCE_COUNT] = FTM_BASE_PTRS; static rt_err_t drv_pwm_control(struct rt_device_pwm *device, int cmd, void *arg); static struct rt_pwm_ops drv_ops = { drv_pwm_control }; /******************************************************************************************************** * Private Function Declarations * *******************************************************************************************************/ static rt_err_t drv_pwm_enable(rt_uint32_t instance,struct rt_pwm_configuration *configuration, rt_bool_t enable) { status_t ret; if (!configuration->complementary) { ret = FTM_DRV_ControlChannelOutput(instance, configuration->channel,enable); } return ret == STATUS_SUCCESS ? RT_EOK : RT_ERROR; } static rt_err_t drv_pwm_set_period(rt_uint32_t instance, struct rt_pwm_configuration *configuration) { status_t ret; /* Convert nanosecond to frequency and duty cycle. 1s = 1 * 1000 * 1000 * 1000 ns */ ret = FTM_DRV_UpdatePwmPeriod(instance,FTM_PWM_UPDATE_IN_DUTY_CYCLE,1000000000/configuration->period,true); return ret == STATUS_SUCCESS ? RT_EOK : RT_ERROR; } static rt_err_t drv_pwm_set_pulse(rt_uint32_t instanc, ftm_state_t *state,struct rt_pwm_configuration *configuration) { status_t ret; uint16_t firstEdge; uint16_t duty; duty = (configuration->pulse*100)/(1000000000/state->ftmSourceClockFrequency); firstEdge = (uint16_t)(327.68f*((float)duty)); ret = FTM_DRV_UpdatePwmChannel(INST_FLEXTIMER_PWM_1,configuration->channel,FTM_PWM_UPDATE_IN_DUTY_CYCLE,firstEdge,0U,true); return ret == STATUS_SUCCESS ? RT_EOK : RT_ERROR; } static rt_err_t drv_pwm_set(rt_uint32_t instance, ftm_state_t *state ,struct rt_pwm_configuration *configuration) { status_t ret = STATUS_SUCCESS; uint16_t firstEdge; uint16_t duty; /* Convert nanosecond to frequency and duty cycle. 1s = 1 * 1000 * 1000 * 1000 ns */ ret = FTM_DRV_UpdatePwmPeriod(instance,FTM_PWM_UPDATE_IN_DUTY_CYCLE,1000000000/configuration->period,true); if(ret != STATUS_SUCCESS) goto __exit; duty = (configuration->pulse*100)/(configuration->period); firstEdge = (uint16_t)(327.68f*((float)duty)); ret = FTM_DRV_UpdatePwmChannel(INST_FLEXTIMER_PWM_1,configuration->channel,FTM_PWM_UPDATE_IN_DUTY_CYCLE,firstEdge,0U,true); __exit: return ret == STATUS_SUCCESS ? RT_EOK : RT_ERROR; } static rt_err_t drv_pwm_get(rt_uint32_t instance,ftm_state_t *state, struct rt_pwm_configuration *configuration) { uint32_t mod = g_pwm_ftmBase[instance]->MOD + 1u; uint32_t cval = g_pwm_ftmBase[instance]->CONTROLS[configuration->channel].CnV; configuration->period = 1000000000/(state->ftmSourceClockFrequency/state->ftmPeriod); configuration->pulse = configuration->period*cval/mod; return RT_EOK; } static rt_err_t drv_pwm_control(struct rt_device_pwm *device, int cmd, void *arg) { struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)arg; rt_uint32_t instance = ((struct s32k14x_pwm *)(device->parent.user_data))->instance; switch(cmd) { case PWM_CMD_ENABLE: return drv_pwm_enable(instance,configuration,RT_TRUE); case PWM_CMD_DISABLE: return drv_pwm_enable(instance,configuration,RT_FALSE); case PWM_CMD_SET: return drv_pwm_set(instance,&((struct s32k14x_pwm *)(device->parent.user_data))->state,configuration); case PWM_CMD_SET_PERIOD: return drv_pwm_set_period(instance,configuration); case PWM_CMD_SET_PULSE: return drv_pwm_set_pulse(instance,&((struct s32k14x_pwm *)(device->parent.user_data))->state,configuration); case PWM_CMD_GET: return drv_pwm_get(instance,&((struct s32k14x_pwm *)(device->parent.user_data))->state,configuration); default: return -RT_EINVAL; } } /******************************************************************************************************** * Global Function Declarations * *******************************************************************************************************/ static int s32k14x_pwm_init(void) { status_t ret; int result = RT_EOK; /* config pwm timer */ ret = FTM_DRV_Init(ftm0_pwm5.instance, ftm0_pwm5.user_cfg, &ftm0_pwm5.state); if(ret != STATUS_SUCCESS) { LOG_E("Ftm0 init failed %x.",ret); result = -RT_ERROR; goto __exit; } else { LOG_I("Ftm0 init OK."); } ret = FTM_DRV_InitPwm(ftm0_pwm5.instance, ftm0_pwm5.pwm_cfg); if(ret != STATUS_SUCCESS) { LOG_E("Ftm0 pwm init failed %x.",ret); result = -RT_ERROR; goto __exit; } else { LOG_I("Ftm0 pwm init OK."); } if(rt_device_pwm_register(&ftm0_pwm5.pwm_device,ftm0_pwm5.name,&drv_ops,&ftm0_pwm5) == RT_EOK) { LOG_D("%s register success", ftm0_pwm5.name); } else { LOG_E("%s register failed", ftm0_pwm5.name); result = -RT_ERROR; } __exit: return result; } INIT_DEVICE_EXPORT(s32k14x_pwm_init);
以上代码会注册设备到系统中,并会添加pwm 命令对应代码如下
#ifdef RT_USING_FINSH #include <stdlib.h> #include <string.h> #include <finsh.h> static int pwm(int argc, char **argv) { rt_err_t result = -RT_ERROR; char *result_str; static struct rt_device_pwm *pwm_device = RT_NULL; struct rt_pwm_configuration cfg = {0}; if(argc > 1) { if(!strcmp(argv[1], "probe")) { if(argc == 3) { pwm_device = (struct rt_device_pwm *)rt_device_find(argv[2]); result_str = (pwm_device == RT_NULL) ? "failure" : "success"; rt_kprintf("probe %s %s\n", argv[2], result_str); } else { rt_kprintf("pwm probe <device name> - probe pwm by name\n"); } } else { if(pwm_device == RT_NULL) { rt_kprintf("Please using 'pwm probe <device name>' first.\n"); return -RT_ERROR; } if(!strcmp(argv[1], "enable")) { if(argc == 3) { result = rt_pwm_enable(pwm_device, atoi(argv[2])); result_str = (result == RT_EOK) ? "success" : "failure"; rt_kprintf("%s channel %d is enabled %s \n", pwm_device->parent.parent.name, atoi(argv[2]), result_str); } else { rt_kprintf("pwm enable <channel> - enable pwm channel\n"); rt_kprintf(" e.g. MSH >pwm enable 1 - PWM_CH1 nomal\n"); rt_kprintf(" e.g. MSH >pwm enable -1 - PWM_CH1N complememtary\n"); } } else if(!strcmp(argv[1], "disable")) { if(argc == 3) { result = rt_pwm_disable(pwm_device, atoi(argv[2])); } else { rt_kprintf("pwm disable <channel> - disable pwm channel\n"); } } else if(!strcmp(argv[1], "get")) { cfg.channel = atoi(argv[2]); result = rt_pwm_get(pwm_device, &cfg); if(result == RT_EOK) { rt_kprintf("Info of device [%s] channel [%d]:\n",pwm_device, atoi(argv[2])); rt_kprintf("period : %d\n", cfg.period); rt_kprintf("pulse : %d\n", cfg.pulse); rt_kprintf("Duty cycle : %d%%\n",(int)(((double)(cfg.pulse)/(cfg.period)) * 100)); } else { rt_kprintf("Get info of device: [%s] error.\n", pwm_device); } } else if (!strcmp(argv[1], "set")) { if(argc == 5) { result = rt_pwm_set(pwm_device, atoi(argv[2]), atoi(argv[3]), atoi(argv[4])); rt_kprintf("pwm info set on %s at channel %d\n",pwm_device,(rt_base_t)atoi(argv[2])); } else { rt_kprintf("Set info of device: [%s] error\n", pwm_device); rt_kprintf("Usage: pwm set <channel> <period> <pulse>\n"); } } else if(!strcmp(argv[1], "phase")) { if(argc == 4) { result = rt_pwm_set_phase(pwm_device, atoi(argv[2]),atoi(argv[3])); result_str = (result == RT_EOK) ? "success" : "failure"; rt_kprintf("%s phase is set %d \n", pwm_device->parent.parent.name, (rt_base_t)atoi(argv[3])); } } else if(!strcmp(argv[1], "dead_time")) { if(argc == 4) { result = rt_pwm_set_dead_time(pwm_device, atoi(argv[2]),atoi(argv[3])); result_str = (result == RT_EOK) ? "success" : "failure"; rt_kprintf("%s dead_time is set %d \n", pwm_device->parent.parent.name, (rt_base_t)atoi(argv[3])); } } else { rt_kprintf("Usage: \n"); rt_kprintf("pwm probe <device name> - probe pwm by name\n"); rt_kprintf("pwm enable <channel> - enable pwm channel\n"); rt_kprintf("pwm disable <channel> - disable pwm channel\n"); rt_kprintf("pwm get <channel> - get pwm channel info\n"); rt_kprintf("pwm set <channel> <period> <pulse> - set pwm channel info\n"); rt_kprintf("pwm phase <channel> <phase> - set pwm phase\n"); rt_kprintf("pwm dead_time <channel> <dead_time> - set pwm dead time\n"); result = -RT_ERROR; } } } else { rt_kprintf("Usage: \n"); rt_kprintf("pwm probe <device name> - probe pwm by name\n"); rt_kprintf("pwm enable <channel> - enable pwm channel\n"); rt_kprintf("pwm disable <channel> - disable pwm channel\n"); rt_kprintf("pwm get <channel> - get pwm channel info\n"); rt_kprintf("pwm set <channel> <period> <pulse> - set pwm channel info\n"); rt_kprintf("pwm phase <channel> <phase> - set pwm phase\n"); rt_kprintf("pwm dead_time <channel> <dead_time> - set pwm dead time\n"); result = -RT_ERROR; } return RT_EOK; } MSH_CMD_EXPORT(pwm, pwm [option]); #endif /* RT_USING_FINSH */
我们就可以使用pwm 命令来配置PWM 外设
【功能验证】
使用上述的pwm 命令来验证pwm 功能,首先使用pwm probe 命令来查找pwm0 设备
使用pwm set 命令来配置周期和脉冲宽度时长对应单位为ns
使用pwm get 来获取当前pwm 的信息
逻辑分析仪抓取波形和配置的参数保持一致