3. SA8155内核配置与编译流程实战

SA8155内核配置与编译流程实战 学习卡片

任务目标

在SA8155平台上,通过PWM定时器输出可调占空比的PWM信号,驱动LED实现呼吸灯效果。核心是掌握PWM控制器配置、频率与占空比调节方法。

硬件连接与原理

SA8155内部PWM控制器基于定时器生成波形,输出引脚连接LED(需串联限流电阻)。呼吸灯效果通过周期性改变PWM占空比实现:占空比从0%渐增至100%再渐减至0%,循环往复。PWM频率固定(如1kHz),仅调节占空比即可改变LED亮度。

驱动实现

基于Linux内核PWM子系统,在设备树中声明PWM节点,驱动中通过标准API配置并启动PWM输出。以下为最小可复现实现:

// pwm_breath_led.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <pwm.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/err.h>

#define PWM_PERIOD_NS      1000000  // 1kHz
#define BREATH_STEP_NS     10000    // 占空比步进10us
#define BREATH_DELAY_MS    10       // 每步延时

static struct pwm_device *pwm_led;
static struct task_struct *breath_task;
static int breath_enabled;

static int breath_loop(void *data)
{
    int duty = 0;
    int direction = 1;

    while (!kthread_should_stop()) {
        if (breath_enabled) {
            pwm_config(pwm_led, duty, PWM_PERIOD_NS);
            pwm_enable(pwm_led);

            duty += direction * BREATH_STEP_NS;
            if (duty >= PWM_PERIOD_NS) {
                duty = PWM_PERIOD_NS;
                direction = -1;
            } else if (duty <= 0) {
                duty = 0;
                direction = 1;
            }
        } else {
            pwm_disable(pwm_led);
        }
        msleep(BREATH_DELAY_MS);
    }
    return 0;
}

static int pwm_probe(struct platform_device *pdev)
{
    int ret;

    pwm_led = devm_pwm_get(&pdev->dev, "led");
    if (IS_ERR(pwm_led)) {
        dev_err(&pdev->dev, "Failed to get PWM: %ld\n", PTR_ERR(pwm_led));
        return PTR_ERR(pwm_led);
    }

    pwm_config(pwm_led, 0, PWM_PERIOD_NS);
    breath_enabled = 1;

    breath_task = kthread_run(breath_loop, NULL, "pwm_breath");
    if (IS_ERR(breath_task)) {
        dev_err(&pdev->dev, "Failed to create thread\n");
        return PTR_ERR(breath_task);
    }

    dev_info(&pdev->dev, "PWM breath LED initialized\n");
    return 0;
}

static int pwm_remove(struct platform_device *pdev)
{
    breath_enabled = 0;
    if (breath_task)
        kthread_stop(breath_task);
    pwm_disable(pwm_led);
    return 0;
}

static const struct of_device_id pwm_match[] = {
    { .compatible = "qcom,sa8155-pwm-led" },
    {}
};

static struct platform_driver pwm_driver = {
    .probe  = pwm_probe,
    .remove = pwm_remove,
    .driver = {
        .name = "pwm_breath_led",
        .of_match_table = pwm_match,
    },
};
module_platform_driver(pwm_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SA8155 PWM Breath LED Driver");

设备树配置

// sa8155-pwm-led.dtsi
&soc {
    pwm_led: pwm-led {
        compatible = "qcom,sa8155-pwm-led";
        pwms = <&tlmm_pwm 0 1000000>;  // 使用tlmm_pwm通道0,周期1ms
        pwm-names = "led";
    };
};

编译与加载

# 编译(内核源码树内)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=$(pwd)/drivers/pwm modules

# 加载
adb push pwm_breath_led.ko /data/local/tmp/
adb shell insmod /data/local/tmp/pwm_breath_led.ko

# 卸载
adb shell rmmod pwm_breath_led

验证方法

  1. 驱动加载检查:dmesg | grep "PWM breath" 应看到初始化日志。
  2. LED视觉验证:观察LED亮度应呈现由暗渐亮再渐暗的循环呼吸效果,周期约2秒(1000步×10ms)。
  3. 波形测量:使用示波器探头连接PWM输出引脚,应看到频率1kHz、占空比周期性变化的方波信号。
  4. 频率调节验证:修改PWM_PERIOD_NS为500000(2kHz),重新编译加载后,示波器显示频率变为2kHz,呼吸节奏不变。

通过标准:LED呼吸效果连续稳定运行无中断;示波器测量频率与代码配置一致(误差±5%以内);卸载驱动后LED熄灭,无异常日志。

常见问题

  • PWM获取失败:确认设备树中pwms属性引用的PWM控制器已启用,且通道号正确。
  • LED不亮:检查GPIO复用配置,确保PWM引脚未被配置为GPIO功能。
  • 呼吸不均匀:步进值BREATH_STEP_NS过大会导致亮度跳变,建议不超过周期值的2%。
PWM呼吸灯波形示意(周期1ms,占空比渐变) t 高电平 占空比最大 0ms ~0.5ms ~1ms ← 占空比渐增 → ← 占空比渐减 → 周期 = 1ms (1kHz)