3. 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
验证方法
- 驱动加载检查:
dmesg | grep "PWM breath"应看到初始化日志。 - LED视觉验证:观察LED亮度应呈现由暗渐亮再渐暗的循环呼吸效果,周期约2秒(1000步×10ms)。
- 波形测量:使用示波器探头连接PWM输出引脚,应看到频率1kHz、占空比周期性变化的方波信号。
- 频率调节验证:修改
PWM_PERIOD_NS为500000(2kHz),重新编译加载后,示波器显示频率变为2kHz,呼吸节奏不变。
通过标准:LED呼吸效果连续稳定运行无中断;示波器测量频率与代码配置一致(误差±5%以内);卸载驱动后LED熄灭,无异常日志。
常见问题
- PWM获取失败:确认设备树中
pwms属性引用的PWM控制器已启用,且通道号正确。 - LED不亮:检查GPIO复用配置,确保PWM引脚未被配置为GPIO功能。
- 呼吸不均匀:步进值
BREATH_STEP_NS过大会导致亮度跳变,建议不超过周期值的2%。