PWM使用参考
REVISION HISTORY¶
Revision No. | Description |
Date |
---|---|---|
1.0 | 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参数说明¶
-
配置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为单位 -
配置PWM极性:
pwm invert <pwm_dev_num> <channel> <polarity> - invert polarity
参数名称 描述 pwm_dev_num 输入0~19选择想要配置的channel channel 输入0 polarity 输入0表示极性正常,输入1表示极性相反 -
enable PWM:
pwm enable <pwm_dev_num> <channel> - enable PWM output
参数名称 描述 pwm_dev_num 输入0~19选择想要配置的channel channel 输入0 -
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.c
中hal_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 没有配置频率和占空比。