imx6ull PWM实战:从设备树配置到sysfs控制,驱动LED调光与电机调速(基于100ask开发板)
i.MX6ULL PWM实战:从设备树配置到LED调光与电机控制
在嵌入式开发中,PWM(脉冲宽度调制)是一个极其重要的功能模块。无论是LED亮度调节、电机转速控制,还是蜂鸣器发声,PWM都扮演着关键角色。i.MX6ULL作为一款广泛应用于工业控制和物联网设备的处理器,其内置的8路PWM控制器为开发者提供了丰富的硬件资源。本文将带你深入探索如何在实际项目中运用i.MX6ULL的PWM功能,从设备树配置到sysfs控制,最终实现LED调光和电机调速等实用功能。
1. i.MX6ULL PWM硬件基础与引脚配置
i.MX6ULL处理器内置8个PWM控制器(PWM1-PWM8),但实际可用的PWM输出引脚取决于具体的开发板设计。以100ask开发板为例,通过扩展接口引出的PWM引脚主要是PWM7和PWM8,对应的GPIO引脚为GPIO4_IO19和GPIO4_IO20。
引脚复用配置是使用PWM的第一步。i.MX6ULL的引脚通常具有多种功能,需要通过IOMUX控制器进行配置。以下是PWM7和PWM8的典型设备树配置:
&iomuxc { pinctrl_pwm7: pwm7grp { fsl,pins = < MX6UL_PAD_CSI_VSYNC__PWM7_OUT 0x000010B0 >; }; pinctrl_pwm8: pwm8grp { fsl,pins = < MX6UL_PAD_CSI_HSYNC__PWM8_OUT 0x000010B0 >; }; };这段配置将CSI接口的VSYNC和HSYNC引脚复用为PWM7和PWM8输出功能。配置完成后,需要重新编译设备树并更新到开发板:
make dtbs cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/2. PWM sysfs接口详解与基础操作
Linux内核为PWM提供了sysfs接口,使得用户空间程序可以方便地控制PWM参数。在i.MX6ULL上,PWM7和PWM8分别对应/sys/class/pwm/pwmchip7和/sys/class/pwm/pwmchip0目录。
PWM基本操作流程:
- 导出PWM通道
- 设置周期(period)
- 设置占空比(duty_cycle)
- 使能PWM输出
- (可选)禁用PWM输出
以下是一个完整的PWM控制示例:
# 导出PWM7通道0 echo 0 > /sys/class/pwm/pwmchip7/export # 设置周期为10ms(100Hz频率) echo 10000000 > /sys/class/pwm/pwmchip7/pwm0/period # 设置占空比为5ms(50%) echo 5000000 > /sys/class/pwm/pwmchip7/pwm0/duty_cycle # 使能PWM输出 echo 1 > /sys/class/pwm/pwmchip7/pwm0/enable # 禁用PWM输出(完成后) echo 0 > /sys/class/pwm/pwmchip7/pwm0/enable echo 0 > /sys/class/pwm/pwmchip7/unexport注意:PWM周期和占空比的单位都是纳秒(ns)。设置时需确保占空比不大于周期值。
3. LED调光实战:实现呼吸灯效果
利用PWM控制LED亮度是最常见的应用之一。通过改变PWM的占空比,可以线性调节LED的亮度。下面我们实现一个经典的呼吸灯效果。
硬件连接:
- PWM7输出引脚 → LED阳极
- LED阴极 → 限流电阻 → GND
Shell脚本实现:
#!/bin/bash PWM_CHIP="/sys/class/pwm/pwmchip7" PWM_NUM=0 PERIOD=1000000 # 1ms周期,1kHz频率 # 初始化PWM echo $PWM_NUM > $PWM_CHIP/export echo $PERIOD > $PWM_CHIP/pwm$PWM_NUM/period echo 1 > $PWM_CHIP/pwm$PWM_NUM/enable # 呼吸灯效果 while true; do # 渐亮 for ((i=0; i<=$PERIOD; i+=10000)); do echo $i > $PWM_CHIP/pwm$PWM_NUM/duty_cycle usleep 10000 done # 渐暗 for ((i=$PERIOD; i>=0; i-=10000)); do echo $i > $PWM_CHIP/pwm$PWM_NUM/duty_cycle usleep 10000 done doneC语言实现:
对于更精确的控制,可以使用C语言编写PWM控制程序:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #define PWM_PATH "/sys/class/pwm/pwmchip7/pwm0" void write_pwm(const char *file, int value) { char path[256]; sprintf(path, "%s/%s", PWM_PATH, file); FILE *f = fopen(path, "w"); if (f == NULL) { perror("Failed to open PWM file"); exit(1); } fprintf(f, "%d", value); fclose(f); } int main() { // 初始化PWM system("echo 0 > /sys/class/pwm/pwmchip7/export"); write_pwm("period", 1000000); // 1ms周期 write_pwm("duty_cycle", 0); write_pwm("enable", 1); // 呼吸灯效果 while (1) { for (int i = 0; i <= 1000000; i += 10000) { write_pwm("duty_cycle", i); usleep(10000); } for (int i = 1000000; i >= 0; i -= 10000) { write_pwm("duty_cycle", i); usleep(10000); } } return 0; }4. 电机控制实战:PWM调速与方向控制
小型直流电机的控制通常需要PWM调速和方向控制两个信号。我们可以使用i.MX6ULL的一个PWM通道和一个GPIO来实现完整的电机控制。
硬件连接:
- PWM8输出 → 电机驱动模块PWM输入
- GPIO4_IO19 → 电机驱动模块方向控制
- 电机驱动模块输出 → 直流电机
设备树GPIO配置:
首先确保方向控制GPIO已正确配置:
&iomuxc { pinctrl_motor_dir: motordirgrp { fsl,pins = < MX6UL_PAD_CSI_DATA04__GPIO4_IO19 0x000010B0 >; }; };电机控制Shell脚本:
#!/bin/bash # 配置方向控制GPIO echo 115 > /sys/class/gpio/export # GPIO4_IO19对应编号115 echo out > /sys/class/gpio/gpio115/direction # 配置PWM8 echo 0 > /sys/class/pwm/pwmchip0/export echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable # 电机控制函数 set_motor() { local speed=$1 # 0-100% local dir=$2 # 0或1 # 设置方向 echo $dir > /sys/class/gpio/gpio115/value # 计算占空比 local duty=$((1000000 * speed / 100)) echo $duty > /sys/class/pwm/pwmchip0/pwm0/duty_cycle } # 示例:电机正转50%速度 set_motor 50 1 sleep 3 # 示例:电机反转75%速度 set_motor 75 0 sleep 3 # 停止电机 set_motor 0 0PID速度控制:
对于更精确的电机控制,可以实现简单的PID算法:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <math.h> #define PWM_PERIOD 1000000 // 1ms float pid_control(float setpoint, float actual) { static float integral = 0; static float last_error = 0; float error = setpoint - actual; integral += error; float derivative = error - last_error; last_error = error; // 简单PID参数 float Kp = 0.8; float Ki = 0.001; float Kd = 0.01; return Kp * error + Ki * integral + Kd * derivative; } void motor_control(float speed_percent) { static float actual_speed = 0; // 模拟获取实际速度(实际应用中可能是编码器读数) // 这里简化处理,实际速度会有惯性延迟 actual_speed += (speed_percent - actual_speed) * 0.1; // PID计算PWM占空比 float control = pid_control(speed_percent, actual_speed); int duty = (int)(PWM_PERIOD * fmax(0, fmin(1, control / 100.0))); // 应用PWM char cmd[256]; sprintf(cmd, "echo %d > /sys/class/pwm/pwmchip0/pwm0/duty_cycle", duty); system(cmd); } int main() { // 初始化PWM system("echo 0 > /sys/class/pwm/pwmchip0/export"); char cmd[256]; sprintf(cmd, "echo %d > /sys/class/pwm/pwmchip0/pwm0/period", PWM_PERIOD); system(cmd); system("echo 0 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle"); system("echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable"); // 速度控制示例 for (int i = 0; i <= 100; i += 10) { printf("Setting speed to %d%%\n", i); motor_control(i); usleep(500000); } for (int i = 100; i >= 0; i -= 10) { printf("Setting speed to %d%%\n", i); motor_control(i); usleep(500000); } return 0; }5. 进阶应用:PWM控制蜂鸣器与多通道同步
除了LED和电机控制,PWM还可以用于控制有源蜂鸣器产生不同频率的声音。与LED调光不同,蜂鸣器控制通常需要改变PWM的频率而不是占空比。
蜂鸣器控制实现:
#!/bin/bash # 音符频率表 (Hz) declare -A NOTES=( [C4]=261 [D4]=293 [E4]=329 [F4]=349 [G4]=391 [A4]=440 [B4]=493 [C5]=523 [D5]=587 [E5]=659 [F5]=698 [G5]=783 [A5]=880 [B5]=987 ) play_note() { local note=$1 local duration=${2:-500} # 默认500ms if [[ -z "${NOTES[$note]}" ]]; then echo 0 > $PWM_PATH/duty_cycle # 静音 return fi local freq=${NOTES[$note]} local period=$((1000000000 / freq)) # 周期(ns) local duty=$((period / 2)) # 50%占空比 echo $period > $PWM_PATH/period echo $duty > $PWM_PATH/duty_cycle echo 1 > $PWM_PATH/enable usleep $((duration * 1000)) echo 0 > $PWM_PATH/duty_cycle # 静音 } # 初始化PWM PWM_PATH="/sys/class/pwm/pwmchip7/pwm0" echo 0 > /sys/class/pwm/pwmchip7/export echo 1 > $PWM_PATH/enable # 播放简单旋律 play_note C4 300 play_note E4 300 play_note G4 300 play_note C5 600 play_note "" 200 # 休息 play_note G4 300 play_note A4 300 play_note G4 300 play_note F4 600 # 清理 echo 0 > $PWM_PATH/enable echo 0 > /sys/class/pwm/pwmchip7/unexport多通道PWM同步:
在某些应用中,可能需要多个PWM通道保持同步。i.MX6ULL的PWM控制器支持同步功能,可以通过设备树配置:
&pwm7 { compatible = "fsl,imx6ul-pwm", "fsm,imx27-pwm"; #pwm-cells = <2>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_pwm7>; clocks = <&clks IMX6UL_CLK_PWM7>, <&clks IMX6UL_CLK_PWM7>; clock-names = "ipg", "per"; assigned-clocks = <&clks IMX6UL_CLK_PWM7>; assigned-clock-rates = <1000000>; // 1MHz时钟 status = "okay"; }; &pwm8 { compatible = "fsl,imx6ul-pwm", "fsm,imx27-pwm"; #pwm-cells = <2>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_pwm8>; clocks = <&clks IMX6UL_CLK_PWM8>, <&clks IMX6UL_CLK_PWM8>; clock-names = "ipg", "per"; assigned-clocks = <&clks IMX6UL_CLK_PWM8>; assigned-clock-rates = <1000000>; // 与PWM7相同的时钟 status = "okay"; };通过配置相同的时钟源,可以确保多个PWM通道保持同步。在实际应用中,这可以用于RGB LED控制或多相电机驱动等场景。
