PWM使用参考


REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 04/09/2025

    1. 概述

    PWM (Pulse Width Modulation) 模块用于产生脉宽波形,可通过改变频率和占空比来改变输出的电流、电压进而实现控制电机转速、液晶屏调光等功能;SigmaStar的PWM模块同时具备Group功能(细分为sync 、round、hold、stop),即每4个或者6个channel的PWM可绑定成一组,实现4路或6路PWM同步产生或停止。

    设备和硬件组别的关系如下表所示:

    PWM Group bank addr Channel
    HW PWM group0 1119H channel 0 ~ 3
    HW PWM group1 111AH channel 4 ~ 7
    HW PWM group2 111CH channel 8 ~ 11
    HW PWM group3 112BH channel 12 ~ 16
    HW PM PWM group0 1AH pm channel 0 ~ 1

    2. 关键字

    dts/dtsi:

    Linux设备树文件,通常用于描述CPU所支持的外设,外设节点中包含的属性值可用于外设的配置。

    padmux:

    引脚复用,用于将模块功能引脚连接到具体的外部引脚上面,打通信号连接。

    Dead Time:

    PWM 死区时间(Dead Time)是指在PWM信号切换过程中,两个相邻的输出信号之间存在的时间间隔。它用于防止功率开关器件(如MOSFET或IGBT)在切换过程中同时导通,从而避免短路和损坏。

    3. 功能描述

    3.1. 概述

    • 支持PWM的channel数量为20个,其中channel 0~17支持加入各自的Group,channel 18~19不支持Group功能,详见Group与channel对应关系

    • PWM channel 12~17支持最大设定值为(OSCCLK / 0x3FF)的Dead Time设定及刹车功能,例如OSCCLK = 12MHz,则范围是0~85250ns

    • 支持double buffer来防止产生错误波形,即波形会等待当前波形完全生成之后更新

    • 支持shift功能,即波形可以设置相位,相位会影响占空比,实际占空比 = 设置占空比 - 相位

    • 支持sync功能,即Group内的PWM在同一个时刻产生波形,一般一组Group中会包含4个channel的PWM

    • 支持round功能,即同Group内的PWM会在产生特定数量的脉冲后停止

    • 支持hold功能,即同Group内的PWM会在完成当前周期的波形后暂停,常用于改变波形的设定,可设定暂停时间内的高低电平

    • 支持stop功能,即同Group内的PWM会紧急停止

    3.2. 频率和占空比

    • 频率(frequency)

      每秒钟信号从高电平到低电平再回到高电平的次数。

    • 占空比(duty cycle)

      高电平持续时间和低电平持续时间之间的比例。

    • 举例说明

      假设PWM的OSCCLK频率为12M,那么可设置的频率范围为:0.0007Hz~6MHz;

      设定PWM0和PWM1为频率120Hz,占空比25%的波形,且PWM1相对于PWM0有180°的相位偏移,那么各参数配置如下:

      period duty shift
      PWM0 120Hz 25% 0%
      PWM1 120Hz 75% 50%

      对于channel 0~17支持的OSCCLK(Hz)为:86M、24M、12M、6M、3M、1.5M、750K;

      channel 18~19支持的OSCCLK(Hz)为:12M、6M、3M;

      PWM的工作频率范围是(OSCCLK/2)到(OSCCLK/2^34),实际工作频率范围如下:

      clk-freq PWM freq range
      86MHz 0.0050Hz ~ 43MHz
      24MHz 0.0013Hz ~ 12MHz
      12MHz 0.0007Hz ~ 6MHz
      6MHz 0.00035Hz ~ 3MHz
      3MHz 0.00017Hz ~ 1.5Mhz
      1.5MHz 0.000087Hz ~ 750KHz
      750KHz 0.000044Hz ~ 375KHz

      产生的波形如下图:

    3.3. 普通精度模式

    kernel config中不开启Support high precision calculation,则使用普通精度模式,该模式下配置PWM的period的单位为Hz,duty cycle的单位为百分比。

    假设要设置PWM0频率为10000HZ,占空比为50%,则:

    period = 10000

    duty cycle = 50

    以sys/class/pwm/下的接口举例,配置方法如下:

    1.    # 跳转到PWM控制目录
    2.    cd sys/class/pwm/pwmchip0/
    3.
    4.    # 创建PWM0节点
    5.    echo 0 > export
    6.
    7.    # 进入PWM0控制目录
    8.    cd pwm0
    9.
    10.   # 设置频率为10000Hz
    11.   echo 10000 > preiod
    12.
    13.   # 设置占空比为50%
    14.   echo 50 > duty_cycle
    15.
    16.   # 设置极性为反向(不设置反向为echo normal > polarity)
    17.   echo inversed > polarity
    18.
    19.   # 使能PWM0
    20.   echo 1 > enable
    

    3.4. 高精度模式

    kernel config中开启Support high precision calculation选项时,则使用高精度模式,该模式下配置PWM的period和duty_cycle均使用纳秒为单位,所以要先计算周期和占空比的值。

    假设要设置PWM0频率为10000.5HZ,占空比为49.5%,则:

    period = 10^9 ÷ 10000.5 = 99995

    duty cycle = 99995 * 49.5% = 49498

    以sys/class/pwm/下的接口举例,配置方法如下:

    1.    # 跳转到PWM控制路径
    2.    cd sys/class/pwm/pwmchip0/
    3.
    4.    # 创建PWM0节点
    5.    echo 0 > export
    6.
    7.    # 进入PWM0控制路径
    8.    cd pwm0
    9.
    10.   # 设置频率为10000.5Hz
    11.   echo 99995> period
    12.
    13.   # 设置占空比为49.5%
    14.   echo 49498> duty_cycle
    15.
    16.   # 使能PWM0
    17.   echo 1 >enable
    

    由此可见,高精度模式的优势在于参数设定支持到小数。

    3.5. GROUP相关概念

    3.5.1. Sync mode

    sync mode可以将每个pwm channel加入到各自的Group群组中,完成同时对多个PWM进行控制的目的,一个Group有4个pwm channel,且Group与channel对应关系如下:

    Group Group Member
    Group0 PWM0、PWM1、PWM2、PWM3
    Group1 PWM4、PWM5、PWM6、PWM7
    Group2 PWM8、PWM9、PWM10、PWM11
    Group3 PWM12、PWM13、PWM14、PWM15、PWM16、PWM17

    可通过以下方式选择是否将pwm加入到Group中:

    方式一:各channel DTS配置group属性

    group属性决定了/sys/class/sstar/pwm下的各channel layout

    以channel 0 举例,DTS配置group = <0>,选择加入group0

    channel 0的目录在/sys/class/sstar/pwm/group0/目录下:

    以channel 1 举例,DTS不配置group属性,选择不加入group0各channel的目录

    channel 1在/sys/class/sstar/pwm目录下:

    方式二:/sys/class/sstar/group下使用join接口,echo [channel] [enable] > join

    加入: echo 0 1 > join

    退出: echo 0 0 > join

    注意:方式一中DTS不配置group属性,就无法在方式二自由加入group

    3.5.2. Hold mode

    Group的Hold功能会在pwm完成当前周期的波形后停止,并触发中断,此时可以改变各channel波形的配置以保持同步,完成修改后会关闭hold 功能,pwm就会重新产生新的波形,每组group都有自己独立的hold功能。

    举例:高精度模式下,group0下的所有channel已经输出100HZ,50%占空比,极性normal的波形,在此基础上将周期修改成1000HZ。

    1.    #跳转到group路径
    2.    cd sys/class/sstar/pwm/group0
    3.
    4.    #设置group0下所有channal输出100HZ,50%占空比,极性normal的波形
    5.    echo 10000000  > g_period
    6.    echo 7500000   > g_duty
    7.    echo 2500000   > g_shift
    8.    echo 0         > g_polarity
    9.    echo 1         > update // 触发hold mode
    10.   echo 1         > g_enable
    11.
    12.   #将周期修改为1000HZ
    13.   echo 1000000 > g_period
    14.   ehco 1       > update // 触发hold mode
    

    3.5.3. Round mode

    round功能会在同group内的所有channel完成一定数量的脉冲后停止,每组group都有自己独立的round功能。

    举例:group0下的所有channel已经输出1000HZ,50%占空比,极性normal的波形,在此基础上分别触发100,200,300个脉冲后停止。

    1.    #跳转到group路径
    2.    cd sys/class/sstar/pwm/group0
    3.
    4.    #设置group0下所以channal输出1000HZ,50%占空比,极性normal的波形
    5.    echo 1000000 > g_period
    6.    echo 750000 > g_duty
    7.    echo 250000 > g_shift
    8.    echo 0      > g_polarity
    9.    echo 1      > update
    10.   echo 1      > g_enable
    11.
    12.   #触发100个脉冲
    13.   echo 100 > round
    14.
    15.   #触发200个脉冲
    16.   echo 200 > round
    17.
    18.   #触发300个脉冲
    19.   echo 300 > round
    

    3.5.4. Stop mode

    stop功能可以让当前group中的PWM立即停止(不会等当前周期完成)并维持结束时的电平,每组group都有自己独立的stop功能。

    举例:使用stop暂停一段时间后再恢复波形

    1.    #跳转到group路径
    2.    cd sys/class/sstar/pwm/group0
    3.
    4.    #设置group0下所以channel输出1000HZ,50%占空比,极性normal的波形
    5.    echo 1000000 > g_period
    6.    echo 750000 > g_duty
    7.    echo 250000 > g_shift
    8.    echo 0      > g_polarity
    9.    echo 1      > update
    10.   echo 1      > g_enable
    11.
    12.   #急停波形
    13.   echo 1 > stop
    14.
    15.   #恢复波形
    16.   echo 0 > stop
    

    注意:stop 时间不建议太长,尤其是pwm停止后维持的电平为高电平时。

    3.6. 死区时间

    基于两路互补信号产生Dead Time,正向信号基于PWM clock的相对上升沿产生delay,反向信号基于PWM clock的相对下降沿产生delay

    4. Uboot用法介绍

    4.1. Uboot Config配置

    在编译Uboot时需要选择的配置如下:

    SigmaStar drivers->
    
        <*> SigmaStar PWM->
    
            <*> Support dead time generation  // 是否支持Dead Time的设定
    
            <*> Support high precision calculation // 切换普通精度与高精度模式
    

    4.2. 配置DTS

    PWM的DTS配置只需要在对应的chipname.dtsi中配置如下信息(可以根据需求选择性配置PWM channel的数量,最多可以同时配置20个channel):

    pwm0: pwm@0x1F203200{
        compatible = "sstar,pwm";
        reg = <0x1F203200 0x37>;
        channel = <0>;
        clock-freq = <12000000>;
        status = "okay";
    };
    
    pwm12: pwm@0x1F205600 {
        compatible = "sstar,pwm";
        reg = <0x1F205600 0x40>, <0x1F203600 0xC>;
        channel = <12>;
        clock-freq = <12000000>;
        dead-time = <30000 30000>;
        status = "okay";
    };
    

    PWM DTS配置说明:

    属性 描述 设定值 备注
    compatible 匹配驱动进行驱动注册 "sstar,pwm" 禁止修改
    reg 设定寄存器bank地址 / 禁止修改
    channel 匹配channel index 0~19 不需要更改
    clock-freq 设定OSCCLK 详见频率和占空比 可根据需要修改
    dead-time 设定死区时间 0~85250ns,pwm enable后生效 可根据需要修改
    status 选择是否使能PWM驱动 "ok" or "disable" 可根据需要修改

    4.3. Uboot cmd参数说明

    1. 配置PWM 频率及占空比:

      pwm config <pwm_dev_num> <channel> <period_ns> <duty_ns> - config PWM
      
      参数名称 描述
      pwm_dev_num 输入0~19选择想要配置的channel
      channel 输入0
      period_ns 输入频率,高精度模式以ns为单位,低精度模式以hz为单位
      duty_ns 输入占空比,高精度模式以ns为单位,低精度模式以hz为单位
    2. 配置PWM极性:

      pwm invert <pwm_dev_num> <channel> <polarity> - invert polarity
      
      参数名称 描述
      pwm_dev_num 输入0~19选择想要配置的channel
      channel 输入0
      polarity 输入0表示极性正常,输入1表示极性相反
    3. enable PWM:

      pwm enable <pwm_dev_num> <channel> - enable PWM output
      
      参数名称 描述
      pwm_dev_num 输入0~19选择想要配置的channel
      channel 输入0
    4. disable PWM:

      pwm disable <pwm_dev_num> <channel> - eisable PWM output
      
      参数名称 描述
      pwm_dev_num 输入0~19选择想要配置的channel
      channel 输入0

    4.4. Uboot cmd使用实例

    5. Kernel用法介绍

    5.1. Kernel Config配置

    在编译kernel时需要选择的配置如下:

    Device Drivers-->
        [*] SStar SoC platform drivers-->
            [*] Sigmastar PWM driver
                [*] Support dead time generation       // 支持dead time配置
                [*] Support high precision calculation // 切换普通精度与高精度模式
    

    5.2. 配置DTS

    可以通过配置dtsi中pwm项设定pwm driver的基本参数, dtsi的参数展示如下:(可以根据需求选择性配置PWM channel的数量,最多可以同时配置20个channel):

    pwm0: pwm@0x1F203200 {
        compatible = "sstar,pwm";
        reg = <0x0 0x1F203200 0x0 0x40>;
        interrupts=<GIC_SPI INT_IRQ_PWM_GROUP0 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&CLK_pwm>;
        #pwm-cells = <3>;
        channel = <0>;
        group = <0>;
        clk-select = <0>;
        status = "ok";
    };
    
    .......
    
    pwm12: pwm@0x1F205600 {
        compatible = "sstar,pwm";
        reg = <0x0 0x1F205600 0x0 0x40>, <0x0 0x1F203600 0x0 0xC>;
        interrupts=<GIC_SPI INT_IRQ_PWM_GROUP3 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&CLK_pwm>;
        #pwm-cells = <3>;
        channel = <12>;
        group = <3>;
        clk-select = <0>;
        dead-time;
        idle-status = <0 0>;
        status = "ok";
    };
    

    PWM DTS配置说明:

    属性 描述 设定值 备注
    compatible 匹配驱动进行驱动注册,需与代码中一致 "sstar,pwm" 禁止修改
    reg 设定寄存器bank的地址 / 禁止修改
    interrupts 指定使用的硬件中断号及属性 INT_IRQ_PWM_GROUPX 禁止修改
    clocks 指定使用的时钟源 CLK_pwm or CLK_pm_pwm 禁止修改
    channel 匹配channel index 0~19 禁止修改
    group 指定是否加入group 不配置该属性则不加入group,加入group配置0/½/3,详见Group与channel对应关系 可根据需要修改
    clk-select 用于选择时钟档位 pwm017可选06分别对应12M/6M/3M/1.5M/750K/86M/ 24M;pwm1819可选02分别对应12M/6M/3M pwm017选择必须一致;pwm1819选择必须一致
    dead-time 选择是否支持dead time设定 bool 可根据需要修改
    idle-status 选择dead time波形idle时的电平状态 0-低电平;1-高电平 可根据需要修改
    status 选择是否使能PWM驱动 "ok" or "disable" 可根据需要修改

    5.3. Padmux配置

    PWM在Uboot及Kernel环境下的padmux配置方法一致,只需要根据选择的引脚在对应的padmux.dtsi中加入如下所示的代码:

    PWM的PADMUX配置:

    //pwm
    <PAD_GPIOC_00   PINMUX_FOR_PWM0_MODE_1    MDRV_PUSE_PWM0>,
    <PAD_GPIOC_01   PINMUX_FOR_PWM1_MODE_1    MDRV_PUSE_PWM1>,
    <PAD_GPIOC_02   PINMUX_FOR_PWM2_MODE_1    MDRV_PUSE_PWM2>,
    <PAD_GPIOC_03   PINMUX_FOR_PWM3_MODE_1    MDRV_PUSE_PWM3>,
    <PAD_GPIOC_04   PINMUX_FOR_PWM4_MODE_1    MDRV_PUSE_PWM4>,
    <PAD_GPIOC_05   PINMUX_FOR_PWM5_MODE_1    MDRV_PUSE_PWM5>,
    <PAD_GPIOC_06   PINMUX_FOR_PWM6_MODE_1    MDRV_PUSE_PWM6>,
    <PAD_GPIOC_07   PINMUX_FOR_PWM7_MODE_1    MDRV_PUSE_PWM7>,
    <PAD_GPIOC_08   PINMUX_FOR_PWM8_MODE_1    MDRV_PUSE_PWM8>,
    <PAD_OUTP_CH0   PINMUX_FOR_PWM9_MODE_1    MDRV_PUSE_PWM9>,
    <PAD_GPIOA_00   PINMUX_FOR_PWM10_MODE_2   MDRV_PUSE_PWM10>,
    <PAD_GPIOA_01   PINMUX_FOR_PWM11_MODE_2   MDRV_PUSE_PWM11>,
    <PAD_GPIOA_02   PINMUX_FOR_PWM12_MODE_2   MDRV_PUSE_PWM12>,
    <PAD_GPIOA_03   PINMUX_FOR_PWM13_MODE_2   MDRV_PUSE_PWM13>,
    <PAD_GPIOA_04   PINMUX_FOR_PWM14_MODE_2   MDRV_PUSE_PWM14>,
    <PAD_GPIOA_05   PINMUX_FOR_PWM15_MODE_2   MDRV_PUSE_PWM15>,
    <PAD_GPIOA_06   PINMUX_FOR_PWM16_MODE_2   MDRV_PUSE_PWM16>,
    <PAD_GPIOA_07   PINMUX_FOR_PWM17_MODE_2   MDRV_PUSE_PWM17>,
    <PAD_PM_I2C_SDA PINMUX_FOR_PM_PWM0_MODE_2 MDRV_PUSE_PWM18>,
    <PAD_PM_I2C_SCL PINMUX_FOR_PM_PWM1_MODE_2 MDRV_PUSE_PWM19>,
    

    dead time及其break功能的PADMUX配置:

    //dead time
    <PAD_GPIOE_17 PINMUX_FOR_PWM_COMP0_MODE_1 MDRV_PUSE_PWMOUT0_P>,
    <PAD_GPIOE_18 PINMUX_FOR_PWM_COMP0_MODE_1 MDRV_PUSE_PWMOUT0_N>,
    <PAD_GPIOE_23 PINMUX_FOR_PWM_COMP1_MODE_1 MDRV_PUSE_PWMOUT1_P>,
    <PAD_GPIOE_24 PINMUX_FOR_PWM_COMP1_MODE_1 MDRV_PUSE_PWMOUT1_N>,
    <PAD_GPIOA_00 PINMUX_FOR_PWM_COMP2_MODE_1 MDRV_PUSE_PWMOUT2_P>,
    <PAD_GPIOA_01 PINMUX_FOR_PWM_COMP2_MODE_1 MDRV_PUSE_PWMOUT2_N>,
    <PAD_GPIOA_08 PINMUX_FOR_PWM_COMP3_MODE_2 MDRV_PUSE_PWMOUT3_P>,
    <PAD_GPIOA_09 PINMUX_FOR_PWM_COMP3_MODE_2 MDRV_PUSE_PWMOUT3_N>,
    <PAD_EMMC_RST PINMUX_FOR_PWM_COMP4_MODE_1 MDRV_PUSE_PWMOUT4_P>,
    <PAD_EMMC_CLK PINMUX_FOR_PWM_COMP4_MODE_1 MDRV_PUSE_PWMOUT4_N>,
    <PAD_EMMC_D0  PINMUX_FOR_PWM_COMP5_MODE_1 MDRV_PUSE_PWMOUT5_P>,
    <PAD_EMMC_D5  PINMUX_FOR_PWM_COMP5_MODE_1 MDRV_PUSE_PWMOUT5_N>,
    
    //break
    <PAD_GPIOA_12 PINMUX_FOR_PWM_INT_MODE_1 MDRV_PUSE_PWMADC_INT>,
    <PAD_GPIOA_13 PINMUX_FOR_PWM_INT_MODE_1 MDRV_PUSE_PWMOUT_INT>,
    

    第一列为引脚索引号,可以在drivers/sstar/inlcude/{chipname}/gpio.h中查到;

    第二列为模式定义,在drivers/sstar/gpio/{chipname}/hal_pinmux.chal_gpio_st_padmux_info数组里,罗列了所有引脚的复用关系,查询该引脚支持哪些复用功能可以查询该数组;

    第三列为引脚及搭配模式的索引名称,可在drivers/sstar/include/drv_puse.h里查询。

    5.4. 模块使用介绍

    5.4.1. sys/class/pwm

    DTS配置N个pwm channel节点,sys/class/pwm目录下对应生成N个pwmchip0,在pwmchip0目录下执行echo 0 > export,即可生成对应的pwm0目录,(由于每个pwmchip0下的npwm数值均为1,因此export的传参参数只能是0),之后可在pwm0目录下配置pwm相关属性:period、duty_cycle、polarity、enable,详见普通精度模式或者高精度模式

    5.4.2. sys/class/sstar/pwm

    sys/class/sstar/pwm目录结构如下:

    • 一级目录

      group0/½/... → 对同group内的所有channel同步操作

      pwm1/... → 对未加入group内的channel单独操作

    • 二级目录

      以group0举例 → 对同group内的所有channel同时设定period/shift/duty/polarity/enable,并触发hold/stop/round功能

    • 三级目录

      以pwm0举例 → 对同group内的某一个channel进行操作,可单独设定period/shift/duty/polarity/enable

    sys/class/sstar/pwm接口,在二级目录group下可针对所有属于group的channel进行统一设置,也可在该目录下独立设置各channel的属性;在三级目录下可独立设置各channel的属性,二级目录与三级目录设置channel的属性区别在于:二级目录下独立设置channel的属性后,需要echo 1 > update触发hold功能去更新参数,三级目录下独立设置channel的属性后,会立即生效

    举例:group0下的所有channel同步且输出100HZ,50%占空比,极性normal的波形,然后将channel0的period修改为200HZ

    1.    #跳转到group路径
    2.    cd sys/class/sstar/pwm/group0
    3.
    4.    #设置group0下所以channal输出100HZ,50%占空比,极性normal的波形
    5.    echo 10000000  > g_period //group内所有channel的period都设为100HZ
    6.    echo 7500000   > g_duty
    7.    echo 2500000   > g_shift //占空比为(7500000-2500000)/ 10000000 = 50%
    8.    echo 0         > g_polarity
    9.    echo 1         > update //触发hold mode
    10.   echo 1         > g_enable
    11.
    12.   #修改channel0的period为200HZ,方法一:
    13.   echo 0 20000000 > g_period //group内只设定channel 0的period为100HZ
    14.   echo 1 > update //触发hold更新波形,仍然同步
    15.
    16.   #修改channel0的period为200HZ,方法二:
    17.   cd /sys/class/sstar/pwm/group0/pwm0
    18.   echo 20000000 > period
    

    sys/class/sstar/pwm目录结构下设定dead time:

    DTS使能dead-time后,会在sys/class/sstar/pwm下的三级目录生成相关属性文件:

    举例:pwm12输出100HZ,50%占空比,极性normal的波形,同时生成死区时间为5000ns的波形正向波形,和死区时间为40000ns的反向波形

    1.    #跳转到pwm12路径
    2.    cd sys/class/sstar/pwm/group3/pwm12
    3.
    4.    #设置参数输出1000HZ,50%占空比,极性normal的波形
    5.    echo 1000000  > period
    6.    echo 500000   > duty
    7.    echo normal   > polarity
    8.    echo 1        > enable
    9.
    10.   #设定pwm12的dead time,最终产生1000HZ,占空比分别为45%的正向波形和46%的反向波形
    11.   echo 50000 40000 > ddt
    12.   echo 1           > ddt_en
    

    上述设定条件下,如果需要启用刹车功能,需要输入如下指令,且相关PIN脚需要短接高电平或者接地 ,以PAD_GPIOA_13为例:

    echo 0 1 > hw_break //PAD_GPIOA_13接地后,死区波形停止
    
    echo 1 1 > hw_break //PAD_GPIOA_13接高电平后,死区波形停止
    

    5.4.3. IOCTL

    除了上述sysfs接口和linuxPWM框架提供的标准接口外,PWM还提供了ioctl接口,用法如下:

    其中头文件<drv_pwm.h>位于/driver/sstar/pwm目录下,struct pwm_ch_cfg结构体是对channel属性的描述,struct pwm_gp_cfg结构体是对group属性的描述

    • IOCTL_PWM_CHANNEL_CFG:完成channel的参数配置

    • IOCTL_PWM_GET_CHAN_CFG:获取当前channel的参数配置

    • IOCTL_PWM_GROUP_CFG_NR:完成group的参数配置

    • IOCTL_PWM_GET_GROUP_CFG:获取当前group的参数配置

    • IOCTL_PWM_GROUP_STOP:触发stop功能

    • IOCTL_PWM_GROUP_ROUND:触发round功能

    #ifndef __DRV_PWM_H__
    #define __DRV_PWM_H__
    
    #include <cam_os_wrapper.h>
    
    struct pwm_ch_cfg
    {
        u64 duty;     //设定占空比,实际占空比=duty-shift
        u64 shift;    //设定起始相位
        u64 period;   //设定周期
        u8  enable;   //波形使能
        u32 channel;  //指定channel
        u32 polarity; //极性设置:0-正常,1-极性取反
    
    #ifdef CONFIG_SSTAR_PWM_DDT
        u64 p_ddt;   //正向波形的死区时间,以ns为单位
        u64 n_ddt;   //反向波形的死区时间,以ns为单位
        u8  ddt_en;  //死区时间使能
    #endif
    };
    
    struct pwm_gp_cfg
    {
        u64 duty;
        u64 shift;
        u32 group;
        u64 period;
        u8  enable;
        u8  stop_en;   //stop功能使能
        u32 polarity;
        u32 round_num; //设定round mode下的脉冲数量
    };
    
    #define PWM_IOC_MAXNR 6
    
    #define IOCTL_PWM_SET_CHAN_CFG_NR  (0)
    #define IOCTL_PWM_GET_CHAN_CFG_NR  (2)
    #define IOCTL_PWM_SET_GROUP_CFG_NR (1)
    #define IOCTL_PWM_GET_GROUP_CFG_NR (3)
    #define IOCTL_PWM_GROUP_STOP_NR    (4)
    #define IOCTL_PWM_GROUP_ROUND_NR   (6)
    
    #define PWM_IOC_MAGIC           'p'
    #define IOCTL_PWM_SET_CHAN_CFG  _IO(PWM_IOC_MAGIC, IOCTL_PWM_SET_CHAN_CFG_NR)
    #define IOCTL_PWM_GET_CHAN_CFG  _IO(PWM_IOC_MAGIC, IOCTL_PWM_GET_CHAN_CFG_NR)
    #define IOCTL_PWM_SET_GROUP_CFG _IO(PWM_IOC_MAGIC, IOCTL_PWM_SET_GROUP_CFG_NR)
    #define IOCTL_PWM_GET_GROUP_CFG _IO(PWM_IOC_MAGIC, IOCTL_PWM_GET_GROUP_CFG_NR)
    #define IOCTL_PWM_GROUP_STOP    _IO(PWM_IOC_MAGIC, IOCTL_PWM_GROUP_STOP_NR)
    #define IOCTL_PWM_GROUP_ROUND   _IO(PWM_IOC_MAGIC, IOCTL_PWM_GROUP_ROUND_NR)
    
    ......
    
    #endif
    

    5.5. Demo code

    5.5.1 ioctl demo

    上述ioctl接口示例代码如下:

    #include <autoconf.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <drv_pwm.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/ioctl.h>
    
    int main(int argc, char **argv)
    {
        int  ret    = 0;
        int  pwm_fd = -1;
        char path_name[24];
    
        struct pwm_ch_cfg pwm_channel;
        struct pwm_gp_cfg pwm_group;
    
        if (argc != 4)
        {
            printf("format: ut_pwm <channel> [channel_id] [enable]\n");
            printf("format: ut_pwm <group>   [group_id]   [enable]\n");
            printf("format: ut_pwm <stop>    [group_id]   [enable]\n");
            printf("format: ut_pwm <round>   [group_id]   [round_num]\n");
            printf("format: ut_pwm <ddt>     [channel_id] [dead_time]\n");
            return -1;
        }
    
        if (!strcmp(argv[1], "channel"))
        {
            pwm_channel.period   = 1000000;
            pwm_channel.duty     = 500000;
            pwm_channel.shift    = 200000;
            pwm_channel.polarity = 1;
            pwm_channel.channel  = atoi(argv[2]);
            pwm_channel.enable   = atoi(argv[3]);
    
            snprintf(path_name, sizeof(path_name), "/dev/pwm%u", pwm_channel.channel);
            pwm_fd = open((const char *)(char *)path_name, O_RDWR);
            if (pwm_fd < 0)
            {
                printf("open /dev/pwm%u fail errno:[%d]\n", pwm_channel.channel, pwm_fd);
                return -1;
            }
    
            ret = ioctl(pwm_fd, IOCTL_PWM_SET_CHAN_CFG, &pwm_channel);
            if (ret < 0)
            {
                printf("pwm channel[%u] set config fail\n", pwm_channel.channel);
                return ret;
            }
    
            ret = ioctl(pwm_fd, IOCTL_PWM_GET_CHAN_CFG, &pwm_channel);
            if (ret < 0)
            {
                printf("pwm channel[%u] get config fail\n", pwm_channel.channel);
                return ret;
            }
    
            usleep(500000);
            pwm_channel.duty = 700000;
            ret              = ioctl(pwm_fd, IOCTL_PWM_SET_CHAN_CFG, &pwm_channel);
            if (ret < 0)
            {
                printf("pwm channel[%u] set config again fail\n", pwm_channel.channel);
                return ret;
            }
        }
        else if (!strcmp(argv[1], "group"))
        {
            pwm_group.period   = 1000000;
            pwm_group.duty     = 500000;
            pwm_group.shift    = 200000;
            pwm_group.polarity = 0;
            pwm_group.group    = atoi(argv[2]);
            pwm_group.enable   = atoi(argv[3]);
    
            snprintf(path_name, sizeof(path_name), "/dev/pwm_group%u", pwm_group.group);
            pwm_fd = open((const char *)(char *)path_name, O_RDWR);
            if (pwm_fd < 0)
            {
                printf("open /dev/pwm-group%u fail errno:[%d]\n", pwm_group.group, pwm_fd);
                return -1;
            }
    
            ret = ioctl(pwm_fd, IOCTL_PWM_SET_GROUP_CFG, &pwm_group);
            if (ret < 0)
            {
                printf("pwm group[%u] set config fail\n", pwm_group.group);
                return ret;
            }
    
            ret = ioctl(pwm_fd, IOCTL_PWM_GET_GROUP_CFG, &pwm_group);
            if (ret < 0)
            {
                printf("pwm group[%u] get config fail\n", pwm_group.group);
                return ret;
            }
    
            pwm_group.duty = 800000;
            usleep(500000);
            ret = ioctl(pwm_fd, IOCTL_PWM_SET_GROUP_CFG, &pwm_group);
            if (ret < 0)
            {
                printf("pwm group[%u] set config again fail\n", pwm_group.group);
                return ret;
            }
        }
        else if (!strcmp(argv[1], "stop"))
        {
            pwm_group.group = atoi(argv[2]);
            snprintf(path_name, sizeof(path_name), "/dev/pwm_group%u", pwm_group.group);
            pwm_fd = open((const char *)(char *)path_name, O_RDWR);
            if (pwm_fd < 0)
            {
                printf("open /dev/pwm-group%u fail errno:[%d]\n", pwm_group.group, pwm_fd);
                return -1;
            }
    
            pwm_group.stop_en = atoi(argv[3]);
            ret               = ioctl(pwm_fd, IOCTL_PWM_GROUP_STOP, &pwm_group);
            if (ret < 0)
            {
                printf("pwm group[%u] stop config fail\n", pwm_group.group);
                return ret;
            }
        }
        else if (!strcmp(argv[1], "round"))
        {
            pwm_group.group = atoi(argv[2]);
            snprintf(path_name, sizeof(path_name), "/dev/pwm_group%u", pwm_group.group);
            pwm_fd = open((const char *)(char *)path_name, O_RDWR);
            if (pwm_fd < 0)
            {
                printf("open /dev/pwm-group%u fail errno:[%d]\n", pwm_group.group, pwm_fd);
                return -1;
            }
            pwm_group.round_num = atoi(argv[3]);
            ret                 = ioctl(pwm_fd, IOCTL_PWM_GROUP_ROUND, &pwm_group);
            if (ret < 0)
            {
                printf("pwm group[%u] round config fail\n", pwm_group.group);
                return ret;
            }
        }
    #ifdef CONFIG_SSTAR_PWM_DDT
        else if (!strcmp(argv[1], "ddt"))
        {
            pwm_channel.channel = atoi(argv[2]);
            snprintf(path_name, sizeof(path_name), "/dev/pwm%u", pwm_channel.channel);
            pwm_fd = open((const char *)(char *)path_name, O_RDWR);
            if (pwm_fd < 0)
            {
                printf("open /dev/pwm%u fail errno:[%d]\n", pwm_channel.channel, pwm_fd);
                return -1;
            }
    
            ret = ioctl(pwm_fd, IOCTL_PWM_GET_CHAN_CFG, &pwm_channel);
            if (ret < 0)
            {
                printf("pwm channel[%u] get config fail\n", pwm_channel.channel);
                return ret;
            }
    
            pwm_channel.p_ddt  = atoi(argv[3]);
            pwm_channel.n_ddt  = atoi(argv[3]);
            pwm_channel.ddt_en = 1;
    
            ret = ioctl(pwm_fd, IOCTL_PWM_SET_CHAN_CFG, &pwm_channel);
            if (ret < 0)
            {
                printf("pwm channel[%u] set ddt config fail\n", pwm_channel.channel);
                return ret;
            }
        }
    #endif
        else
        {
            printf("erro pwm command\n");
        }
    
        close(pwm_fd);
    
        return 0;
    }
    

    5.5.2 自定义API DEMO

    Demo code 在kernel/drivers/sstar/pwm/ut/ut_pwm_module/ut_pwm_module.c,如下

    module_param(period, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    MODULE_PARM_DESC(period, "Pwm period");
    module_param(duty, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    MODULE_PARM_DESC(duty, "Pwm duty_cycle");
    module_param(polarity, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    MODULE_PARM_DESC(polarity, "Pwm polarity");
    module_param(enable, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    MODULE_PARM_DESC(enable, "Pwm enable");
    
    // Request the pwmdev and check it's state
    static int check_pwm_state(unsigned int channel, struct pwm_state pre_state)
    {
        gpwm_dev = (struct pwm_device *)CamPwmRequest(channel, "pwm_ut");
        if (IS_ERR_OR_NULL(gpwm_dev))
        {
            pr_err("Failed to request pwm dev - %d\n", channel);
            return -1;
        }
        if (pre_state.period != gpwm_dev->state.period || pre_state.duty_cycle != gpwm_dev->state.duty_cycle
            || pre_state.polarity != gpwm_dev->state.polarity || pre_state.enabled != gpwm_dev->state.enabled)
        {
            pr_err("Incorrect init pwm - %d state\n", channel);
            pr_err(
                "pwm->state.period = %llu\n, pwm->state.duty_cycle = %llu\n,pwm->state.polarity = %d\n, pwm->state.enabled "
                "= %d\n",
                gpwm_dev->state.period, gpwm_dev->state.duty_cycle, gpwm_dev->state.polarity, gpwm_dev->state.enabled);
            return -1;
        }
    
        return 0;
    }
    
    static int __init pwm_customer_init(void)
    {
        int                channel = 0;
        struct pwm_state   pre_state;
        struct CamPwmState cam_state;
        pre_state.period     = 500;
        pre_state.duty_cycle = 250;
        pre_state.polarity   = 1;
        pre_state.enabled    = 1;
        if (check_pwm_state(channel, pre_state))
            return -EINVAL;
        cam_state.period   = period;
        cam_state.duty     = duty;
        cam_state.polarity = polarity;
        cam_state.enabled  = enable;
        if (CamPwmConfig(gpwm_dev, CAM_PWM_ALL, &cam_state))
            return -EINVAL;
        pr_info("Pwm test module loaded.\n");
        return 0;
    }
    
    static void __exit pwm_customer_exit(void)
    {
        if (!IS_ERR_OR_NULL(gpwm_dev))
            CamPwmFree(gpwm_dev);
        pr_err("Pwm test module unloaded.\n");
    }
    

    6. API 参考

    除了标准的PWM框架接口外,该功能模块提供以下接口

    API名 功能
    CamPwmConfig 设置pwm参数
    CamPwmRequest 申请pwm句柄
    CamPwmFree 释放pwm句柄

    要使用这些API,需要包含头文件 kernel/drivers/sstar/include/cam_drv_pwm.h

    其中定义的数据结构如下:

    struct CamPwmState
    {
        u64  duty;
        u64  period;
        bool enabled;
        u8   polarity;
    };
    
    enum CamPWMArgs
    {
        CAM_PWM_PERIOD,
        CAM_PWM_DUTY,
        CAM_PWM_POLAR,
        CAM_PWM_ONOFF,
        CAM_PWM_ALL,
    };
    

    6.1. CamPwmConfig

    • 功能

      设置pwm参数

    • 声明

      int CamPwmConfig(struct pwm_device *pwm_dev, enum CamPWMArgs args, struct CamPwmState *cam_state);
      
    • 形参

      参数名称 描述
      pwm_dev PWM设备结构体
      args 设置参数的类型
      cam_state 表示目标PWM状态的结构体
    • 返回值

      返回值 描述
      0 成功
      负数 失败

    6.2. CamPwmRequest

    • 功能

      申请通道对应的PWM 句柄

    • 声明

      struct pwm_device *CamPwmRequest(int channel, const char *label);
      
    • 形参

      参数名称 描述
      channel pwm通道
      label 标识PWM 设备的标签
    • 返回值

      返回值 描述
      pwm_dev * 成功
      NULL 失败

    6.3. CamPwmFree

    • 功能

      释放PWM句柄

    • 声明

      void CamPwmFree(struct pwm_device *pwm_dev);
      
    • 形参

      参数名称 描述
      pwm_dev PWM设备句柄
    • 返回值

      无返回值

    7 FAQ

    7.1 PWM各接口不存在

    • 检查DTS PWM节点的status是否为ok

    • 检查kernel config是否配置,详见Kernel Config配置

    7.2 配置后PWM无波形产生

    Step1: 首先确认测量的引脚是否正确:打开对应的原理图确认即可,如果没有错误的话,则进行下一步。

    Step2: 确认对应的PWM mode是否生效,引脚复用失败主要有两个原因:

    原因一:该引脚没有设置为PWM mode,设置方法详见PADMUX配置

    原因二:有优先级比PWM mode更高级别的padmux mode被开启,可以在编译的时候打开padmux回读机制的CONFIG:CONFIG_MSYS_PADMUX,它的功能是用于确认使用到的padmux是否有被成功设定,开启后在user space输入如下命令查看:

    echo PAD_PWM0 PINMUX_FOR_PWM0_MODE_1 > /sys/class/sstar/msys/mux_verify
    
    cat /sys/class/sstar/msys/mux_verify
    

    Step3:检查相关参数是否设置成功

    以pwm group0为例,输入如下命令查看参数:

    cd /sys/class/sstar/pwm/group0/
    
    cat g_info
    

    如下图所示,channel 0 和channel 2频率和占空比设置成功,channel 0 enable是在正常输出波形,channel 2 disable,无波形产生,而channel 3 没有配置频率和占空比。