PWM使用参考¶
REVISION HISTORY¶
| Revision No. | Description |
Date |
|---|---|---|
| 1.0 | 11/28/2025 |
1. 概述¶
PWM (Pulse Width Modulation) 模块用于产生脉宽波形,可通过改变频率和占空比来改变输出的电流、电压进而实现控制电机转速、液晶屏调光等功能;Sgs的PWM模块同时具备Group功能(细分为sync 、round、hold、stop),即每4个或者6个channel的PWM可绑定成一组,实现4路或6路PWM同步产生或停止。
不同chip支持PWM channel如下表:
| chipname | pwm channel |
|---|---|
| iford | 0 ~ 11 |
| souffle | 0 ~ 2 |
| ifado | 0 ~ 10 |
| pcupid | 0 ~ 18 |
| ibopper | 0 ~ 2 |
| ifackel | 0 ~ 11 |
Chip间支持的功能略有不同,若IFORD不支持某些功能会在文档中记录。
2. 关键字¶
dts/dtsi:
Linux设备树文件,通常用于描述CPU所支持的外设,外设节点中包含的属性值可用于外设的配置。
padmux:
引脚复用,用于将模块功能引脚连接到具体的外部引脚上面,打通信号连接。
Dead Time:
PWM 死区时间(Dead Time)是指在PWM信号切换过程中,两个相邻的输出信号之间存在的时间间隔。它用于防止功率开关器件(如MOSFET或IGBT)在切换过程中同时导通,从而避免短路和损坏。该功能仅pcupid支持。
3. 功能描述¶
3.1. 概述¶
-
以IFORD为例, 其支持PWM的channel数量为12个,其中channel 0~11 支持加入各自的Group,详见Group与channel对应关系
-
以Pcupid为例, 其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% 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有3个pwm channel,以iford为例,其Group与channel对应关系如下:
| Group | Group Member |
|---|---|
| Group0 | PWM0、PWM1、PWM2、PWM3 |
| Group1 | PWM4、PWM5、PWM6、PWM7 |
| Group2 | PWM8、PWM9、PWM10、PWM11 |
可通过以下方式选择是否将pwm加入到Group中, 注意一个通道即使没有加入任何group,其sysfs接口仍可正常使用。
方式一:各channel DTS配置group属性
group属性决定了/sys/class/sgs/pwm下的各channel layout
以channel 0 举例,DTS配置group = <0>,选择加入group0
channel 0的目录在/sys/class/sgs/pwm/group0/目录下:

以channel 1 举例,DTS不配置group属性,选择不加入group0各channel的目录
channel 1在/sys/class/sgs/pwm目录下:

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

加入: echo 0 1 > join
退出: echo 0 0 > join
注意:方式一中DTS不配置group属性,就无法在方式二自由加入group
该接口也可用于查看各个通道是否加入其对应group,cat join 即可使用。
如下,通道0/½均加入了group 0,通道3未加入。
# cat join
channel : 0, join group
channel : 1, join group
channel : 2, join group
channel : 3, exit 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/sgs/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
15.
16. #单独将通道1修改为2000HZ
17. echo 1 500000 > g_period
18. 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/sgs/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
举例:group0下的所有channel均未有波形产生,在此基础上分别触发100,200,300个输出1000HZ,50%占空比,极性normal的脉冲后停止。
1. #跳转到group路径
2. cd sys/class/sgs/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.
10. #设置100个脉冲
11. echo 100 > round
12. echo 1 > g_enable
13.
14. #更新并触发先前设置的脉冲
15. echo 1 > update
16.
17. #触发200个脉冲
18. echo 200 > round
19.
20. #触发300个脉冲
21. echo 300 > round
3.5.4. Stop mode¶
stop功能可以让当前group中的PWM立即停止(不会等当前周期完成)并维持结束时的电平,每组group都有自己独立的stop功能。

举例:使用stop暂停一段时间后再恢复波形
1. #跳转到group路径
2. cd sys/class/sgs/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时需要选择的配置如下:
Sgs drivers->
<*> Sgs 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 = "sgs,pwm";
reg = <0x1F203200 0x37>;
channel = <0>;
clock-freq = <12000000>;
status = "okay";
};
pwm12: pwm@0x1F205600 {
compatible = "sgs,pwm";
reg = <0x1F205600 0x40>, <0x1F203600 0xC>;
channel = <12>;
clock-freq = <12000000>;
dead-time = <30000 30000>;
status = "okay";
};
PWM DTS配置说明:
| 属性 | 描述 | 设定值 | 备注 |
|---|---|---|---|
| compatible | 匹配驱动进行驱动注册 | "sgs,pwm" | 禁止修改 |
| reg | 设定寄存器bank地址 | / | 禁止修改 |
| channel | 匹配channel index | 详见支持的通道 | 不需要更改 |
| 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-->
<*> SGS SoC platform drivers->
[*] Sgs PWM driver
[*] Support dead time generation // 支持dead time配置
[*] Support high precision calculation // 切换普通精度与高精度模式
5.2. 配置DTS¶
可以通过配置dtsi中pwm项设定pwm driver的基本参数, dtsi的参数展示如下:(可以根据需求选择性配置PWM channel的数量):
pwm0: pwm@0x1F203200 {
compatible = "sgs,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 = "sgs,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 | 匹配驱动进行驱动注册,需与代码中一致 | "sgs,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 | 可根据需要修改 |
| clk-select | 用于选择时钟档位 | 根据速度范围修改 | 可根据需要修改 |
| 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/sgs/inlcude/{chipname}/gpio.h中查到;
第二列为模式定义,在drivers/sgs/gpio/{chipname}/hal_pinmux.c中hal_gpio_st_padmux_info数组里,罗列了所有引脚的复用关系,查询该引脚支持哪些复用功能可以查询该数组;
第三列为引脚及搭配模式的索引名称,可在drivers/sgs/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/sgs/pwm¶
sys/class/sgs/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/sgs/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/sgs/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/sgs/pwm/group0/pwm0
18. echo 20000000 > period
sys/class/sgs/pwm目录结构下设定dead time:
DTS使能dead-time后,会在sys/class/sgs/pwm下的三级目录生成相关属性文件:

举例:pwm12输出100HZ,50%占空比,极性normal的波形,同时生成死区时间为5000ns的波形正向波形,和死区时间为40000ns的反向波形
1. #跳转到pwm12路径
2. cd sys/class/sgs/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/sgs/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_sgs_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_sgs_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/sgs/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/sgs/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/sgs/msys/mux_verify
cat /sys/class/sgs/msys/mux_verify
Step3:检查相关参数是否设置成功
以pwm group0为例,输入如下命令查看参数:
cd /sys/class/sgs/pwm/group0/
cat g_info
如下图所示,channel 0 和channel 2频率和占空比设置成功,channel 0 enable是在正常输出波形,channel 2 disable,无波形产生,而channel 3 没有配置频率和占空比。
