介绍
PWM(Pulse Width Modulation),脉冲宽度调制,是一种数字量控制模拟量的技术,常用于电机驱动、显示屏背光控制、逆变控制等;NXP-MCXA153开发板上有多路CTimer定时器,可以用来生成PWM波形
移植流程
① 在board里边添加相应的外设:配置时钟分频、引脚功能等
② 添加相应的config开关、Kconfig开关,用以指示相应的外设开启与关闭(本质是通过宏定义或者条件编译的方式)
③ 根据SDK_2_14_2_FRDM-MCXA153提供的simple_pwm示例工程编写pwm总线驱动,需要实现几个关键的函数
- mcx_pwm_init
- mcx_drv_pwm_control
- mcx_drv_pwm_enable
- mcx_drv_pwm_disable
④ 添加相应的库文件依赖:fsl_ctimer.c
驱动文件
pin_mux.c
在BOARD_InitPins
函数里加入以下代码:配置CTIMER1外设为复位状态、设置P1_4引脚功能
#ifdef BSP_USING_PWM0
ctimer_config_t config;
CTIMER_Init(CTIMER1, &config);
const port_pin_config_t port1_4_pin62_config = {/* Internal pull-up/down resistor is disabled */
kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
kPORT_LowPullResistor,
/* Fast slew rate is configured */
kPORT_FastSlewRate,
/* Passive input filter is disabled */
kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
kPORT_OpenDrainDisable,
/* Low drive strength is configured */
kPORT_LowDriveStrength,
/* Normal drive strength is configured */
kPORT_NormalDriveStrength,
/* Pin is configured as CT1_MAT2 */
kPORT_MuxAlt4,
/* Digital input enabled */
kPORT_InputBufferEnable,
/* Digital input is not inverted */
kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
kPORT_UnlockRegister};
/* PORT1_4 (pin 62) is configured as CT1_MAT2 */
PORT_SetPinConfig(PORT1, 4U, &port1_4_pin62_config);
#endif
board/Kconfig
加入PWM0相关配置
menuconfig BSP_USING_PWM
config BSP_USING_PWM
bool "Enable PWM"
select RT_USING_PWM
default y
if BSP_USING_PWM
config BSP_USING_PWM0
bool "Enable PWM0 output"
default y
endif
drv_pwm.c
pwm驱动层适配如下
/*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-8-3 hywing Initial version.
*/
#include <rtthread.h>
#include <rtdevice.h>
#include "fsl_ctimer.h"
#ifdef RT_USING_PWM
typedef struct
{
struct rt_device_pwm pwm_device;
CTIMER_Type *ct_instance;
uint32_t timerClock;
const ctimer_match_t pwmPeriodChannel;
ctimer_match_t matchChannel;
char *name;
} mcx_pwm_obj_t;
static mcx_pwm_obj_t mcx_pwm_list[]=
{
#ifndef BSP_USING_PWM0
{
.ct_instance = CTIMER1,
.timerClock = 0,
.pwmPeriodChannel = kCTIMER_Match_3,
.matchChannel = kCTIMER_Match_2,
.name = "pwm0",
}
#endif
};
volatile uint32_t g_pwmPeriod = 0U;
volatile uint32_t g_pulsePeriod = 0U;
static rt_err_t mcx_drv_pwm_get(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
{
return RT_EOK;
}
status_t CTIMER_GetPwmPeriodValue(uint32_t pwmFreqHz, uint8_t dutyCyclePercent, uint32_t timerClock_Hz)
{
g_pwmPeriod = (timerClock_Hz / pwmFreqHz) - 1U;
g_pulsePeriod = (g_pwmPeriod + 1U) * (100 - dutyCyclePercent) / 100;
return kStatus_Success;
}
static rt_err_t mcx_drv_pwm_set(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
{
CTIMER_Type *ct = pwm->ct_instance;
uint32_t pwmFreqHz = 1000000000 / configuration->period;
uint8_t dutyCyclePercent = configuration->pulse * 100 / configuration->period;
CTIMER_GetPwmPeriodValue(pwmFreqHz, dutyCyclePercent, pwm->timerClock);
CTIMER_SetupPwmPeriod(ct, kCTIMER_Match_3, kCTIMER_Match_2, g_pwmPeriod, g_pulsePeriod, false);
return 0;
}
static rt_err_t mcx_drv_pwm_enable(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
{
CTIMER_StartTimer(pwm->ct_instance);
return 0;
}
static rt_err_t mcx_drv_pwm_disable(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
{
CTIMER_StopTimer(pwm->ct_instance);
return 0;
}
static rt_err_t mcx_drv_pwm_control(struct rt_device_pwm *device, int cmd, void *args)
{
mcx_pwm_obj_t *pwm = device->parent.user_data;
struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)args;
switch (cmd)
{
case PWM_CMD_ENABLE:
return mcx_drv_pwm_enable(pwm, configuration);
case PWM_CMD_DISABLE:
return mcx_drv_pwm_disable(pwm, configuration);
case PWM_CMD_SET:
return mcx_drv_pwm_set(pwm, configuration);
case PWM_CMD_GET:
return mcx_drv_pwm_get(pwm, configuration);
default:
return -RT_EINVAL;
}
return RT_EOK;
}
static struct rt_pwm_ops mcx_pwm_ops =
{
.control = mcx_drv_pwm_control,
};
int mcx_pwm_init(void)
{
rt_err_t ret;
char name_buf[8];
ctimer_config_t config;
CTIMER_GetDefaultConfig(&config);
for (uint8_t i = 0; i < ARRAY_SIZE(mcx_pwm_list); i++)
{
mcx_pwm_list[i].timerClock = CLOCK_GetCTimerClkFreq(1U) / (config.prescale + 1);
CTIMER_Init(mcx_pwm_list[i].ct_instance, &config);
ret = rt_device_pwm_register(&mcx_pwm_list[i].pwm_device, mcx_pwm_list[i].name, &mcx_pwm_ops, &mcx_pwm_list[i]);
if (ret != RT_EOK)
{
return ret;
}
}
return RT_EOK;
}
INIT_DEVICE_EXPORT(mcx_pwm_init);
#endif /* RT_USING_PWM */
SConscript
在Libraries/MCXA153/SConscript
文件里边加上以下代码
src += ['MCXA153/drivers/fsl_ctimer.c']
测试用例
打开使能pwm0驱动,保存配置推出menuconfig
导出MDK5工程
scons --target=mdk5
生成频率1KHz,占空比为5%的方波,最后编译并烧录到开发板上去
#include <rtthread.h>
#include <rtdevice.h>
#include <rtthread.h>
#include "board.h"
#include <rtdevice.h>
#define PWM_LED_DEV "pwm0"
#define PWM_CHANNEL 3
int main(void)
{
struct rt_device_pwm *pwm_dev = RT_NULL;
rt_uint32_t period, pulse;
period = 1000000;
pulse = 50000;
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_LED_DEV);
if (pwm_dev == RT_NULL)
{
rt_kprintf("pwm device (%s) not found!\n", PWM_LED_DEV);
return RT_ERROR;
}
rt_pwm_enable(pwm_dev, PWM_CHANNEL);
rt_pwm_set(pwm_dev, PWM_CHANNEL, period, pulse);
while (1)
{
rt_thread_mdelay(1000);
}
return 0;
}
实验效果
使用逻辑分析仪测量P1_4生成的波形,波形刚好频率为1KHz,占空比为5%
总结
其实这个是用NXP定时器配置的PWM,并不是原生的PWM,但也能满足要求