pwm驱动 pca9685 代码简析

pwm驱动十分常见,这篇博文就以pca9685为例,简单分析一下pwm驱动的常用套路,本文注重重代码逻辑,轻寄存器hack,寄存器hack,请参考datasheet。

 

代码解析

源码路径drivers/pwm/pwm-pca9685.c

首先看驱动的probe函数

static int pca9685_pwm_probe(struct i2c_client *client,
                const struct i2c_device_id *id)
{
    struct pca9685 *pca;//驱动结构体pca
    int ret;
    int mode2;

    pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL);//动态分配
    if (!pca)
        return -ENOMEM;

    pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config);//regmap初始化
    if (IS_ERR(pca->regmap)) {
        ret = PTR_ERR(pca->regmap);
        dev_err(&client->dev, "Failed to initialize register map: %d\n",
            ret);
        return ret;
    }
    pca->duty_ns = 0;
    pca->period_ns = PCA9685_DEFAULT_PERIOD;//默认值 5000000ns

    i2c_set_clientdata(client, pca); // client->dev.drvdata = pca;

    regmap_read(pca->regmap, PCA9685_MODE2, &mode2); //读取mode2寄存器

    if (device_property_read_bool(&client->dev, "invert"))//读取设备树,配置mode2寄存器。
        mode2 |= MODE2_INVRT;
    else
        mode2 &= ~MODE2_INVRT;

    if (device_property_read_bool(&client->dev, "open-drain"))
        mode2 &= ~MODE2_OUTDRV;
    else
        mode2 |= MODE2_OUTDRV;

    regmap_write(pca->regmap, PCA9685_MODE2, mode2);//设置mode2寄存器

    /* clear all "full off" bits */
    regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0);//LED清零
    regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0);

    pca->chip.ops = &pca9685_pwm_ops;        //设置pwm回调函数
    /* add an extra channel for ALL_LED */
    pca->chip.npwm = PCA9685_MAXCHAN + 1;    //设置pwm数量

    pca->chip.dev = &client->dev;
    pca->chip.base = -1;

    ret = pwmchip_add(&pca->chip);           //向pwm子系统注册当前pwm chip
    if (ret < 0)
        return ret;

    ret = pca9685_pwm_gpio_probe(pca);       //gpio初始化相关
    if (ret < 0) {
        pwmchip_remove(&pca->chip);
        return ret;
    }

    /* the chip comes out of power-up in the active state */
    pm_runtime_set_active(&client->dev);    //电源管理pm open
    /*
     * enable will put the chip into suspend, which is what we
     * want as all outputs are disabled at this point
     */
    pm_runtime_enable(&client->dev);        //电源管理pm enable

    return 0;
}

probe函数主要做了:

1.驱动结构体pca的动态分配,初始化。

2.regmap的初始化,用于提供读写i2c寄存器的底层接口。

3.芯片初始化的一些寄存器配置。

4.pwm回调函数注册。

5.gpio回调函数注册。

6.电源管理初始化。

编写pwm驱动,最核心的就是编写pwm_ops回调函数,这些回调函数会在,sys文件系统的相应文件读写时,被pwm子系统调用,让我们来看看:

 

默认echo路径:

/sys/class/pwm/pwmchipX/pwmX
static const struct pwm_ops pca9685_pwm_ops = { //pwm ops回调
    .enable = pca9685_pwm_enable,     //echo 1 >enable时调用
    .disable = pca9685_pwm_disable,   //echo 0 > enable时调用 
    .config = pca9685_pwm_config,     //echo xx >period; echo xx > duty_cycle时调用
    .request = pca9685_pwm_request,   //echo xx > export时调用
    .free = pca9685_pwm_free,         //echo xx >  unexport时调用
    .owner = THIS_MODULE,
};

pca9685_pwm_config回调函数是这个pwm驱动的核心,用于配置pwm的周期,占空比,简单注释一下。

static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,//pwm 配置
                  int duty_ns, int period_ns)
{
    struct pca9685 *pca = to_pca(chip);
    unsigned long long duty;
    unsigned int reg;
    int prescale;

    if (period_ns != pca->period_ns) {//设置新的pwm周期
        prescale = DIV_ROUND_CLOSEST(PCA9685_OSC_CLOCK_MHZ * period_ns,//通过新的周期,算出prescale
                         PCA9685_COUNTER_RANGE * 1000) - 1;

        if (prescale >= PCA9685_PRESCALE_MIN &&
            prescale <= PCA9685_PRESCALE_MAX) {
            /*
             * putting the chip briefly into SLEEP mode
             * at this point won't interfere with the
             * pm_runtime framework, because the pm_runtime
             * state is guaranteed active here.
             */
            /* Put chip into sleep mode */
            pca9685_set_sleep_mode(pca, true);//设置为sleep mode

            /* Change the chip-wide output frequency */
            regmap_write(pca->regmap, PCA9685_PRESCALE, prescale);//设置除数

            /* Wake the chip up */
            pca9685_set_sleep_mode(pca, false);//wakeup

            pca->period_ns = period_ns;//新周期设置完成
        } else {
            dev_err(chip->dev,
                "prescaler not set: period out of bounds!\n");
            return -EINVAL;
        }
    }

    pca->duty_ns = duty_ns;//设置duty时间(一个周期内pwm为高的时间)

    if (duty_ns < 1) {
        if (pwm->hwpwm >= PCA9685_MAXCHAN)
            reg = PCA9685_ALL_LED_OFF_H;
        else
            reg = LED_N_OFF_H(pwm->hwpwm);

        regmap_write(pca->regmap, reg, LED_FULL);//小于1ns,就认为输出一直为低

        return 0;
    }

    if (duty_ns == period_ns) {//如果两个时间相同,就认为是一直为高电平
        /* Clear both OFF registers */
        if (pwm->hwpwm >= PCA9685_MAXCHAN)
            reg = PCA9685_ALL_LED_OFF_L;
        else
            reg = LED_N_OFF_L(pwm->hwpwm);

        regmap_write(pca->regmap, reg, 0x0);//full off 位清零

        if (pwm->hwpwm >= PCA9685_MAXCHAN)
            reg = PCA9685_ALL_LED_OFF_H;
        else
            reg = LED_N_OFF_H(pwm->hwpwm);

        regmap_write(pca->regmap, reg, 0x0);

        /* Set the full ON bit */
        if (pwm->hwpwm >= PCA9685_MAXCHAN)
            reg = PCA9685_ALL_LED_ON_H;
        else
            reg = LED_N_ON_H(pwm->hwpwm);

        regmap_write(pca->regmap, reg, LED_FULL);//full on位 置1

        return 0;
    }

    duty = PCA9685_COUNTER_RANGE * (unsigned long long)duty_ns;
    duty = DIV_ROUND_UP_ULL(duty, period_ns);//计算占空比 duty = (duty/period) * 4096 

    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_OFF_L;
    else
        reg = LED_N_OFF_L(pwm->hwpwm);

    regmap_write(pca->regmap, reg, (int)duty & 0xff);//设置占空比

    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_OFF_H;
    else
        reg = LED_N_OFF_H(pwm->hwpwm);

    regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf);//设置占空比的高4位

    /* Clear the full ON bit, otherwise the set OFF time has no effect */
    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_ON_H;
    else
        reg = LED_N_ON_H(pwm->hwpwm);

    regmap_write(pca->regmap, reg, 0);//full on 位清零,这样pwm才会生效

    return 0;
}

其他pwm回调函数的简单注释:

static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
    struct pca9685 *pca = to_pca(chip);//获得pca驱动结构体
    unsigned int reg;
    
    /*
     * The PWM subsystem does not support a pre-delay.
     * So, set the ON-timeout to 0
     */
    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_ON_L;
    else
        reg = LED_N_ON_L(pwm->hwpwm);
    
    regmap_write(pca->regmap, reg, 0);//清除LED_ON_L寄存器
    
    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_ON_H;
    else
        reg = LED_N_ON_H(pwm->hwpwm);
    
    regmap_write(pca->regmap, reg, 0);//清除LED_ON_H寄存器,led full on
    
    /*
     * Clear the full-off bit.
     * It has precedence over the others and must be off.
     */
    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_OFF_H;
    else
        reg = LED_N_OFF_H(pwm->hwpwm);//清除LED_OFF寄存器full off位
    
    regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0);//
    
    return 0;
}

static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
    struct pca9685 *pca = to_pca(chip);//获得pca驱动结构体
    unsigned int reg;

    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_OFF_H;
    else
        reg = LED_N_OFF_H(pwm->hwpwm);

    regmap_write(pca->regmap, reg, LED_FULL);//led off full位置1,所有输出禁能。

    /* Clear the LED_OFF counter. */
    if (pwm->hwpwm >= PCA9685_MAXCHAN)
        reg = PCA9685_ALL_LED_OFF_L;
    else
        reg = LED_N_OFF_L(pwm->hwpwm);

    regmap_write(pca->regmap, reg, 0x0);//
}

static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
    struct pca9685 *pca = to_pca(chip);//获得pca驱动结构体

    if (pca9685_pwm_is_gpio(pca, pwm))//判断该port是否用作gpio, 如果用作gpio,则不能再申请该port作为pwm。
        return -EBUSY;
    pm_runtime_get_sync(chip->dev);//电源管理引用计数加1

    return 0;
}

static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
    pca9685_pwm_disable(chip, pwm);//pwm禁能
    pm_runtime_put(chip->dev);//电源管理引用计数减1
}

电源管理的回调函数:

static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable)                                                                                 
{
    regmap_update_bits(pca->regmap, PCA9685_MODE1,//设置寄存器位MODE1_SLEEP
               MODE1_SLEEP, enable ? MODE1_SLEEP : 0);
    if (!enable) {
        /* Wait 500us for the oscillator to be back up */
        udelay(500);
    }
}

static int pca9685_pwm_runtime_suspend(struct device *dev)//进入休眠时调用
{
    struct i2c_client *client = to_i2c_client(dev);
    struct pca9685 *pca = i2c_get_clientdata(client);

    pca9685_set_sleep_mode(pca, true);//芯片进入休眠
    return 0;
}

static int pca9685_pwm_runtime_resume(struct device *dev)//从休眠恢复时调用                                                                                                             
{
    struct i2c_client *client = to_i2c_client(dev);
    struct pca9685 *pca = i2c_get_clientdata(client);

    pca9685_set_sleep_mode(pca, false);//芯片唤醒
    return 0;
}

static const struct dev_pm_ops pca9685_pwm_pm = {
    SET_RUNTIME_PM_OPS(pca9685_pwm_runtime_suspend,
               pca9685_pwm_runtime_resume, NULL)
};

gpio相关的配置暂不分析了。

简单总结一下

写pwm驱动的核心套路,那就是pwm_ops回调函数的编写。

主要实现底层功能:配置pwm周期,使能pwm,禁能pwm,即可。其他事情pwm子系统已经帮我们完成了。

 

 

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在STM32中驱动PCA9685进行PWM输出的原理是通过向舵机的信号线发送PWM信号来控制舵机的输出量。PCA9685是一个基于IIC通信的16路PWM输出模块,可以在单片机资源不足的情况下进行扩展使用。通过使用IIC通信,只需要2根IIC线就可以控制16路PWM,周期和占空比都可控。可以多个模块级联,同时可以控制16路输出端的开、关、PWM以及占空比。在STM32中,只需要将PCA9685的驱动文件和对应的头文件放到工程目录中,然后调用相应的函数即可完成对PCA9685的驱动。其中,设置PWM频率的函数为pca_setfreq(float freq),通过计算得到相应的预分频值,并将其写入PCA9685的寄存器中,从而设置PWM的频率。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [stm32 IIC 协议控制PCA9685舵机驱动板](https://blog.csdn.net/richardgann/article/details/119243529)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [stm32中如何使用PCA9685控制舵机(详解)](https://blog.csdn.net/yingyang20010112/article/details/129668789)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值