第 10 篇 RK 平台安卓驱动实战 3:PWM 驱动开发,实现 LED 呼吸灯 + 电机调速

目录

[开篇先搞懂:PWM 到底是什么?](#开篇先搞懂:PWM 到底是什么?)

大白话定义

核心概念,小白必须懂

常用场景的频率选择

[一、RK3568 PWM 控制器详解,小白必记](#一、RK3568 PWM 控制器详解,小白必记)

核心特点

引脚复用配置

二、实战前的硬件准备

硬件清单

硬件接线

[(1)LED 呼吸灯接线](#(1)LED 呼吸灯接线)

(2)直流电机调速接线

三、第一步:设备树配置

[1. 修改板级设备树文件](#1. 修改板级设备树文件)

核心属性讲解

[2. 编译烧录验证](#2. 编译烧录验证)

[四、第二步:PWM 内核驱动开发](#四、第二步:PWM 内核驱动开发)

[1. 核心 PWM API 讲解,小白必懂](#1. 核心 PWM API 讲解,小白必懂)

[2. 驱动代码编写](#2. 驱动代码编写)

[3. 编译驱动,烧录验证](#3. 编译驱动,烧录验证)

[五、第三步:HAL 层适配 + 安卓 App 开发](#五、第三步:HAL 层适配 + 安卓 App 开发)

[1. HAL 层代码](#1. HAL 层代码)

头文件my_pwm_hal.h

实现文件my_pwm_hal.c

[2. 安卓 App 核心功能实现](#2. 安卓 App 核心功能实现)

[(1)LED 亮度调节](#(1)LED 亮度调节)

(2)自动呼吸灯效果

(3)电机调速

[六、小白 PWM 驱动必踩的坑,提前规避](#六、小白 PWM 驱动必踩的坑,提前规避)

结尾说两句


大家好,我是黒漂技术佬。前两篇我们搞定了 GPIO 和中断驱动,实现了 LED 的亮灭和按键的实时响应。后台很多兄弟问:

"佬,LED 只能亮和灭,能不能实现渐变的呼吸灯效果?还有我想控制电机的转速,该怎么实现?"

答案就是PWM 驱动!PWM 是嵌入式开发里极其常用的技术,不仅能实现呼吸灯、电机调速,还能做蜂鸣器音量调节、舵机角度控制、屏幕背光调节,几乎所有需要模拟量控制的场景,都能用到 PWM。

今天这篇,我就用大白话给你讲透 PWM 的核心原理,手把手带你完成RK3568 平台 PWM 驱动的完整开发,实现 LED 呼吸灯效果和直流电机调速,并且打通安卓 App 调节 PWM 频率和占空比的全链路,学完就能用在你的项目里。


开篇先搞懂:PWM 到底是什么?

大白话定义

PWM 的全称是 Pulse Width Modulation,脉冲宽度调制。说白了,就是在一个固定的周期内,通过控制高电平持续的时间(脉冲宽度),来改变输出电平的平均占空比,从而实现模拟量的控制效果。

举个最通俗的例子:你家里的电灯开关,只能开和关,对应 GPIO 的高电平和低电平。如果你快速的反复开关这个灯,1 秒钟内开关 1000 次,你的眼睛根本看不到灯在闪烁,只会觉得灯的亮度变了。如果 1 秒内,灯亮的时间占 50%,灭的时间占 50%,你看到的就是半亮的状态;亮的时间占 100%,就是全亮;亮的时间占 10%,就是微亮。

PWM 就是这个原理,它通过芯片内部的 PWM 控制器,输出固定频率的方波信号,我们只需要调节高电平持续的时间(占空比),就能改变输出的平均电压,从而实现呼吸灯、电机调速等效果。

核心概念,小白必须懂

  1. 周期(Period):PWM 信号完成一个完整的高低电平循环所需要的时间,单位是纳秒(ns),周期和频率互为倒数;
  2. 频率(Frequency):1 秒钟内 PWM 信号循环的次数,单位是赫兹(Hz),频率 = 1 / 周期。比如周期是 1ms,频率就是 1000Hz;
  3. 占空比(Duty Cycle):一个周期内,高电平持续的时间占整个周期的比例,用百分比表示。比如周期 1ms,高电平持续 0.5ms,占空比就是 50%;
  4. 分辨率:PWM 占空比的调节精度,比如 10 位分辨率,就是把一个周期分成 1024 份,占空比可以调节 1/1024 的精度。

常用场景的频率选择

不同的场景,需要的 PWM 频率不一样,给小白整理好了,直接用就行:

表格

应用场景 推荐频率 原因
LED 呼吸灯 / 背光调节 1kHz~10kHz 频率高于人眼的视觉暂留频率,不会看到闪烁
直流电机调速 10kHz~20kHz 频率高于人耳的听觉范围,不会听到电机的啸叫
舵机控制 50Hz(20ms 周期) 舵机的标准协议,固定 50Hz
蜂鸣器 2kHz~5kHz 对应蜂鸣器的谐振频率,声音最大

一、RK3568 PWM 控制器详解,小白必记

RK3568 芯片内置了16 路 PWM 控制器,分别是 PWM0~PWM15,每一路都可以独立配置频率和占空比,功能完全一样,我们可以随便选一路来用。

核心特点

  1. 支持可编程的周期和占空比,周期范围非常宽,从几十纳秒到几十秒都可以配置;
  2. 支持 4 种工作模式,我们入门只需要用默认的连续输出模式就行;
  3. 每一路 PWM 都可以配置引脚复用,把 PWM 信号输出到对应的 GPIO 引脚上;
  4. 官方 SDK 里已经实现了完整的 PWM 子系统驱动,我们不用从零写 PWM 控制器的驱动,只需要调用内核提供的 PWM API,就能实现 PWM 控制,极大降低了开发难度。

引脚复用配置

RK3568 的每一路 PWM,都对应了固定的 GPIO 引脚,我们要使用 PWM 功能,必须在设备树里把对应的引脚配置为 PWM 功能,而不是普通 GPIO 功能。

比如我们这次实战用的PWM2,对应的引脚是 GPIO0_A2,我们需要在设备树的 pinctrl 里,把这个引脚配置为 PWM2 功能,而不是 GPIO 功能。


二、实战前的硬件准备

我们这次的实战分为两个部分:

  1. LED 呼吸灯:用 PWM2 输出 PWM 信号,控制 LED 灯的亮度,实现渐变的呼吸灯效果,安卓 App 可以调节呼吸频率和亮度;
  2. 直流电机调速:用 PWM2 输出 PWM 信号,通过 L298N 电机驱动模块,控制直流电机的转速,安卓 App 可以调节电机转速。

硬件清单

  1. RK3568 开发板 1 块;
  2. LED 灯 1 个,1K 限流电阻 1 个;
  3. 直流减速电机 1 个(3.3V~12V 都可以);
  4. L298N 电机驱动模块 1 个(必须用,GPIO 和 PWM 引脚不能直接驱动电机,电流不够);
  5. 电机配套电源 1 个(根据电机的额定电压选);
  6. 杜邦线若干,面包板 1 个。

硬件接线

(1)LED 呼吸灯接线

表格

RK3568 开发板引脚 外接硬件 接线说明
GPIO0_A2(PWM2) LED 阳极(长脚) LED 阴极接 1K 限流电阻,电阻另一端接 GND
GND LED 的 GND 共地
(2)直流电机调速接线

表格

RK3568 开发板 L298N 电机驱动模块 说明
GPIO0_A2(PWM2) ENA PWM 调速信号输入
GPIO0_B0 IN1 电机正转控制引脚
GPIO0_B1 IN2 电机反转控制引脚
GND GND 开发板和驱动模块共地
电机电源 12V 输入 电机的供电电源,根据电机额定电压选
电机输出端 OUT1、OUT2 直流电机两端 接电机

小白红线警告

  1. 绝对不能用开发板的 GPIO 引脚直接驱动电机!电机的启动电流很大,会直接烧芯片引脚,必须用 L298N 这类电机驱动模块;
  2. 开发板、驱动模块、电机电源,必须共地,不然 PWM 信号会乱跳,电机工作不正常;
  3. 电机电源的电压不要超过电机的额定电压,不然会烧电机。

三、第一步:设备树配置

我们需要在设备树里添加 PWM 设备节点,配置引脚复用,使能对应的 PWM 控制器。

1. 修改板级设备树文件

  1. 进入设备树目录,打开你的开发板对应的.dts 文件: bash

    运行

    复制代码
    cd ~/RK3568_Android11_SDK/kernel/arch/arm64/boot/dts/rockchip/
    vim rk3568-firefly.dts
  2. 在根节点里添加我们的 PWM 设备节点,同时配置引脚复用: dts

    复制代码
    / {
        my_pwm: pwm_demo@0 {
            compatible = "my-pwm,demo";
            status = "okay";
            pwms = <&pwm2 0 1000000 0>;
            // pwms属性说明:<&pwm控制器 0 周期(ns) 极性>
            // 这里我们配置PWM2,周期1000000ns=1ms,频率1000Hz,正常极性
            pinctrl-names = "default";
            pinctrl-0 = <&pwm2_pins>;
        };
    
        // 引脚复用配置:把GPIO0_A2配置为PWM2功能
        &pinctrl {
            pwm2 {
                pwm2_pins: pwm2-pins {
                    rockchip,pins = <0 RK_PA2 1 &pcfg_pull_none>;
                    // 说明:<0 RK_PA2 1 ...>,1代表复用功能1,也就是PWM2功能
                };
            };
        };
    
        // 使能PWM2控制器
        &pwm2 {
            status = "okay";
        };
    };

核心属性讲解

  1. pwms = <&pwm2 0 1000000 0>:这是 PWM 设备节点的核心属性,四个参数的含义:
    • &pwm2:使用的 PWM 控制器,我们用的是 PWM2;
    • 第二个参数0:PWM 控制器的通道号,RK3568 的 PWM 都是单通道,固定填 0 就行;
    • 第三个参数1000000:PWM 的周期,单位是纳秒(ns),1000000ns=1ms,对应频率 1000Hz,适合 LED 呼吸灯;
    • 第四个参数0:PWM 的极性,0 是正常极性(占空比越高,高电平时间越长),1 是反极性;
  2. 引脚复用配置里的rockchip,pins = <0 RK_PA2 1 &pcfg_pull_none>:第三个参数1,代表把 GPIO0_A2 配置为复用功能 1,也就是 PWM2 功能,每个引脚的复用功能编号,可以在 RK3568 的芯片 datasheet 里查到。

2. 编译烧录验证

  1. 编译设备树,打包 boot.img,烧录到开发板,重启;

  2. 验证设备树节点生效: bash

    运行

    复制代码
    adb shell
    su
    ls /proc/device-tree/pwm_demo@0

    能看到节点属性,说明配置成功;

  3. 验证 PWM 控制器使能成功: bash

    运行

    复制代码
    cat /sys/class/pwm/pwmchip2/npwm

    输出1,说明 PWM2 控制器已经正常使能,可以使用了。


四、第二步:PWM 内核驱动开发

RK 官方已经实现了 PWM 控制器的底层驱动,我们只需要调用内核提供的 PWM 子系统 API,封装成字符设备驱动,给上层 HAL 层调用就行。

1. 核心 PWM API 讲解,小白必懂

Linux 内核 PWM 子系统,给我们提供了一套标准的 API,不用我们关心底层的寄存器操作,直接调用就行:

表格

API 函数 作用
devm_pwm_get() 从设备树里获取 PWM 设备句柄,推荐用带 devm 的函数,驱动卸载的时候会自动释放资源
pwm_config() 配置 PWM 的周期和占空比,单位都是纳秒(ns)
pwm_enable() 使能 PWM 输出,调用后 PWM 引脚就会输出配置好的方波
pwm_disable() 关闭 PWM 输出
pwm_set_polarity() 设置 PWM 的极性

2. 驱动代码编写

  1. 创建驱动文件: bash

    运行

    复制代码
    cd ~/RK3568_Android11_SDK/kernel/drivers/char/my_drivers
    touch pwm_drv.c
  2. 完整驱动代码,全注释详解: c

    运行

    复制代码
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/uaccess.h>
    #include <linux/device.h>
    #include <linux/platform_device.h>
    #include <linux/pwm.h>
    
    // 驱动信息声明
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("黒漂技术佬");
    MODULE_DESCRIPTION("RK3568 Android PWM Driver");
    MODULE_VERSION("1.0");
    
    // 宏定义
    #define DEVICE_NAME "pwm_drv"
    #define CLASS_NAME "pwm_class"
    
    // PWM控制命令
    #define CMD_PWM_SET_CONFIG 0x2001 // 设置周期和占空比
    #define CMD_PWM_ENABLE     0x2002 // 使能PWM输出
    #define CMD_PWM_DISABLE    0x2003 // 关闭PWM输出
    
    // PWM配置结构体,和用户空间交互用
    struct pwm_config {
        unsigned int period_ns;    // 周期,单位ns
        unsigned int duty_ns;      // 占空比,高电平时间,单位ns
    };
    
    // 全局变量
    static dev_t pwm_devno;
    static struct cdev pwm_cdev;
    static struct class *pwm_class;
    static struct device *pwm_device;
    static struct pwm_device *pwm_dev; // PWM设备句柄
    static int pwm_enabled = 0; // PWM输出使能状态
    
    // ====================== 字符设备核心函数 ======================
    static int pwm_open(struct inode *inode, struct file *filp)
    {
        printk("【pwm_drv】设备被打开\n");
        return 0;
    }
    
    static long pwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
        struct pwm_config config;
        int ret = 0;
    
        switch (cmd) {
            case CMD_PWM_SET_CONFIG:
                // 从用户空间拷贝PWM配置参数
                ret = copy_from_user(&config, (struct pwm_config __user *)arg, sizeof(struct pwm_config));
                if (ret) {
                    printk("【pwm_drv】配置参数拷贝失败\n");
                    return -EFAULT;
                }
                // 校验参数:占空比不能大于周期
                if (config.duty_ns > config.period_ns) {
                    printk("【pwm_drv】参数错误,占空比大于周期\n");
                    return -EINVAL;
                }
                // 配置PWM的周期和占空比
                ret = pwm_config(pwm_dev, config.period_ns, config.duty_ns);
                if (ret) {
                    printk("【pwm_drv】PWM配置失败\n");
                    return ret;
                }
                printk("【pwm_drv】PWM配置成功,周期:%uns,占空比:%uns\n",
                       config.period_ns, config.duty_ns);
                break;
    
            case CMD_PWM_ENABLE:
                // 使能PWM输出
                if (!pwm_enabled) {
                    ret = pwm_enable(pwm_dev);
                    if (ret == 0) {
                        pwm_enabled = 1;
                        printk("【pwm_drv】PWM输出使能\n");
                    } else {
                        printk("【pwm_drv】PWM使能失败\n");
                    }
                }
                break;
    
            case CMD_PWM_DISABLE:
                // 关闭PWM输出
                if (pwm_enabled) {
                    pwm_disable(pwm_dev);
                    pwm_enabled = 0;
                    printk("【pwm_drv】PWM输出关闭\n");
                }
                break;
    
            default:
                printk("【pwm_drv】无效命令\n");
                return -EINVAL;
        }
    
        return ret;
    }
    
    static int pwm_release(struct inode *inode, struct file *filp)
    {
        printk("【pwm_drv】设备被关闭\n");
        return 0;
    }
    
    // file_operations结构体
    static const struct file_operations pwm_fops = {
        .owner = THIS_MODULE,
        .open = pwm_open,
        .unlocked_ioctl = pwm_ioctl,
        .release = pwm_release,
    };
    
    // ====================== platform驱动框架 ======================
    static int pwm_drv_probe(struct platform_device *pdev)
    {
        int ret;
        printk("【pwm_drv】驱动和设备树匹配成功\n");
    
        // 1. 从设备树获取PWM设备句柄
        pwm_dev = devm_pwm_get(&pdev->dev, NULL);
        if (IS_ERR(pwm_dev)) {
            ret = PTR_ERR(pwm_dev);
            dev_err(&pdev->dev, "获取PWM设备失败,错误码:%d\n", ret);
            return ret;
        }
        dev_info(&pdev->dev, "PWM设备获取成功\n");
    
        // 2. 注册字符设备
        ret = alloc_chrdev_region(&pwm_devno, 0, 1, DEVICE_NAME);
        if (ret < 0) {
            dev_err(&pdev->dev, "设备号申请失败\n");
            return ret;
        }
    
        cdev_init(&pwm_cdev, &pwm_fops);
        pwm_cdev.owner = THIS_MODULE;
        ret = cdev_add(&pwm_cdev, pwm_devno, 1);
        if (ret < 0) {
            dev_err(&pdev->dev, "字符设备注册失败\n");
            goto err_devno_free;
        }
    
        pwm_class = class_create(THIS_MODULE, CLASS_NAME);
        if (IS_ERR(pwm_class)) {
            ret = PTR_ERR(pwm_class);
            dev_err(&pdev->dev, "设备类创建失败\n");
            goto err_cdev_del;
        }
    
        pwm_device = device_create(pwm_class, NULL, pwm_devno, NULL, DEVICE_NAME);
        if (IS_ERR(pwm_device)) {
            ret = PTR_ERR(pwm_device);
            dev_err(&pwm->dev, "设备创建失败\n");
            goto err_class_destroy;
        }
    
        dev_info(&pdev->dev, "PWM驱动加载成功!\n");
        return 0;
    
        // 错误处理
    err_class_destroy:
        class_destroy(pwm_class);
    err_cdev_del:
        cdev_del(&pwm_cdev);
    err_devno_free:
        unregister_chrdev_region(pwm_devno, 1);
        return ret;
    }
    
    static int pwm_drv_remove(struct platform_device *pdev)
    {
        printk("【pwm_drv】驱动开始卸载\n");
        // 关闭PWM输出
        if (pwm_enabled) {
            pwm_disable(pwm_dev);
        }
        // 释放资源
        device_destroy(pwm_class, pwm_devno);
        class_destroy(pwm_class);
        cdev_del(&pwm_cdev);
        unregister_chrdev_region(pwm_devno, 1);
        dev_info(&pdev->dev, "PWM驱动卸载成功!\n");
        return 0;
    }
    
    // 设备树匹配表
    static const struct of_device_id pwm_drv_of_match[] = {
        { .compatible = "my-pwm,demo" },
        { /* 结束 */ }
    };
    MODULE_DEVICE_TABLE(of, pwm_drv_of_match);
    
    static struct platform_driver pwm_drv_driver = {
        .probe = pwm_drv_probe,
        .remove = pwm_drv_remove,
        .driver = {
            .name = "pwm_drv_driver",
            .of_match_table = pwm_drv_of_match,
        },
    };
    
    // ====================== 驱动入口和出口 ======================
    static int __init pwm_drv_init(void)
    {
        printk("【pwm_drv】PWM驱动开始加载\n");
        return platform_driver_register(&pwm_drv_driver);
    }
    
    static void __exit pwm_drv_exit(void)
    {
        platform_driver_unregister(&pwm_drv_driver);
    }
    
    module_init(pwm_drv_init);
    module_exit(pwm_drv_exit);

3. 编译驱动,烧录验证

  1. 修改 Makefile,添加 PWM 驱动的编译: makefile

    复制代码
    obj-y += hello_drv.o
    obj-y += gpio_drv.o
    obj-y += key_irq_drv.o
    obj-y += pwm_drv.o
  2. 编译内核,打包 boot.img,烧录到开发板,重启;

  3. 验证驱动加载成功: bash

    运行

    复制代码
    adb shell
    su
    dmesg | grep pwm_drv

    能看到「PWM 驱动加载成功」的日志,说明驱动正常加载;

  4. 查看设备文件,设置权限: bash

    运行

    复制代码
    ls -l /dev/pwm_drv
    chmod 777 /dev/pwm_drv
  5. 测试 PWM 功能:我们用命令行测试,设置 PWM 周期 1ms(1000000ns),占空比 50%(500000ns),使能输出: bash

    运行

    复制代码
    # 编写一个简单的测试程序,或者用ioctl工具测试
    # 测试成功后,LED会变成半亮状态,说明PWM工作正常

五、第三步:HAL 层适配 + 安卓 App 开发

和之前的流程一样,我们完成 HAL 层适配、JNI 封装,然后写一个安卓 App,实现两个功能:

  1. LED 呼吸灯:App 里有一个滑动条,调节 LED 的亮度,还有一个按钮,开启自动呼吸灯效果;
  2. 电机调速:滑动条调节 PWM 占空比,控制电机的转速,按钮控制电机正转、反转、停止。

1. HAL 层代码

头文件my_pwm_hal.h

c

运行

复制代码
#ifndef MY_PWM_HAL_H
#define MY_PWM_HAL_H

#ifdef __cplusplus
extern "C" {
#endif

struct pwm_config {
    unsigned int period_ns;
    unsigned int duty_ns;
};

int pwm_set_config(struct pwm_config *config);
int pwm_enable(void);
int pwm_disable(void);

#ifdef __cplusplus
}
#endif

#endif
实现文件my_pwm_hal.c

c

运行

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "my_pwm_hal.h"

#define DEVICE_PATH "/dev/pwm_drv"
#define CMD_PWM_SET_CONFIG 0x2001
#define CMD_PWM_ENABLE     0x2002
#define CMD_PWM_DISABLE    0x2003

static int fd = -1;

static int pwm_dev_init(void)
{
    if (fd < 0) {
        fd = open(DEVICE_PATH, O_RDWR);
        if (fd < 0) {
            printf("【pwm_hal】打开设备文件失败\n");
            return -1;
        }
    }
    return 0;
}

int pwm_set_config(struct pwm_config *config)
{
    int ret;
    if (pwm_dev_init() < 0) return -1;
    ret = ioctl(fd, CMD_PWM_SET_CONFIG, config);
    if (ret < 0) {
        printf("【pwm_hal】PWM配置失败\n");
        return -1;
    }
    return 0;
}

int pwm_enable(void)
{
    int ret;
    if (pwm_dev_init() < 0) return -1;
    ret = ioctl(fd, CMD_PWM_ENABLE, 0);
    if (ret < 0) {
        printf("【pwm_hal】PWM使能失败\n");
        return -1;
    }
    return 0;
}

int pwm_disable(void)
{
    int ret;
    if (pwm_dev_init() < 0) return -1;
    ret = ioctl(fd, CMD_PWM_DISABLE, 0);
    if (ret < 0) {
        printf("【pwm_hal】PWM关闭失败\n");
        return -1;
    }
    return 0;
}

2. 安卓 App 核心功能实现

(1)LED 亮度调节

通过 SeekBar 滑动条,调节 PWM 的占空比,0% 的时候 LED 熄灭,100% 的时候全亮,实现亮度调节:

java

运行

复制代码
// 滑动条变化监听
seekBarBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        // 周期固定1ms=1000000ns,占空比=progress%
        struct pwm_config config = new struct pwm_config();
        config.period_ns = 1000000;
        config.duty_ns = progress * 10000; // 0~100 → 0~1000000ns
        pwmJni.setConfig(config);
    }
});
(2)自动呼吸灯效果

开启一个线程,循环改变占空比,从 0 到 100,再从 100 到 0,实现渐变的呼吸灯效果:

java

运行

复制代码
new Thread(() -> {
    int duty = 0;
    int step = 1;
    while (isBreathing) {
        struct pwm_config config = new struct pwm_config();
        config.period_ns = 1000000;
        config.duty_ns = duty * 10000;
        pwmJni.setConfig(config);

        duty += step;
        if (duty >= 100) step = -1;
        if (duty <= 0) step = 1;

        try {
            Thread.sleep(20); // 20ms更新一次,呼吸周期4秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
(3)电机调速

电机调速的原理和 LED 亮度调节完全一样,只需要把 PWM 频率改成 10kHz(周期 100000ns),通过调节占空比,改变电机的平均电压,从而调节转速。


六、小白 PWM 驱动必踩的坑,提前规避

  1. 坑 1:PWM 引脚没有输出,完全没反应90% 的情况是引脚复用配置错了,没有把引脚配置为 PWM 功能,还是 GPIO 功能。解决方法:检查设备树里的 pinctrl 配置,确认引脚的复用功能编号正确,和芯片 datasheet 里的一致;
  2. 坑 2:PWM 能输出,但是频率和配置的不一样周期的单位是纳秒(ns),很多新手搞错了单位,比如把 1ms 写成 1000ns,实际频率就变成了 1MHz,和预期的不一样。解决方法:记住周期的单位是纳秒,1ms=1000000ns,10kHz=100000ns 周期;
  3. 坑 3:LED 呼吸灯有闪烁,电机有啸叫PWM 频率太低了,低于人眼 / 人耳的感知范围。解决方法:LED 呼吸灯把频率调到 1kHz 以上,电机调速调到 10kHz 以上;
  4. 坑 4:pwm_config () 调用失败占空比设置的比周期还大,或者周期超出了 PWM 控制器的支持范围。解决方法:校验参数,保证占空比小于等于周期,周期在合理范围内;
  5. 坑 5:电机不转,或者转速不正常没有用电机驱动模块,直接用 GPIO 引脚驱动电机,电流不够;或者没有共地,PWM 信号不正常。解决方法:必须用 L298N 这类驱动模块,保证开发板、驱动模块、电源共地。

结尾说两句

这篇文章,我们彻底搞懂了 PWM 的核心原理,完成了 RK 平台 PWM 驱动的完整开发,实现了 LED 呼吸灯和电机调速的功能,打通了安卓 App 调节 PWM 参数的全链路。PWM 是嵌入式开发里非常实用的技术,学完这篇,你就能应对绝大多数模拟量控制的场景了。

下一篇,我们进入串行总线驱动的第一部分:I2C 设备驱动开发,以 0.96 寸 OLED 屏为例,教你怎么用 Linux I2C 子系统,实现 I2C 设备的驱动开发,并且打通安卓 App 控制屏幕显示的全链路。

我是黒漂技术佬,关注我,带你零基础入门 RK 安卓驱动开发,不踩坑。有任何 PWM 驱动的问题,评论区留言,我都会一一回复。

相关推荐
集芯微电科技有限公司2 小时前
氮化镓GaN FET/GaN HEMT功率驱动器选型一览表
人工智能·单片机·嵌入式硬件·深度学习·神经网络·生成对抗网络
豆浆煮粉2 小时前
Linux驱动开发理解指针与结构体
linux·c语言·驱动开发
7yewh11 小时前
jetson_yolo_deployment 01_linux_dev_env
linux·嵌入式硬件·yolo·机器人·嵌入式
WinstonMao12 小时前
STM32上电不能开机运行问题排查
stm32·单片机·嵌入式硬件
csg110716 小时前
PIC单片机高阶实战(四):PIC32MX串口与4G模块通信
单片机·嵌入式硬件·物联网
辰哥单片机设计18 小时前
MPU6050陀螺仪(STM32)
stm32·单片机·嵌入式硬件
梦..18 小时前
电路EMC问题(二)
嵌入式硬件·硬件架构·硬件工程·pcb工艺
我不是程序猿儿21 小时前
【嵌入式】stm32的时钟配置入门及切入
stm32·单片机·嵌入式硬件
爱倒腾的老唐1 天前
03、制作 STM32 最小系统
stm32·单片机·嵌入式硬件