龙芯2k0300走马观碑组PWM驱动移植,如何实现?

摘要:在《龙芯2k0300 - 走马观碑组第21届智能汽车竞赛软硬件设计》中我们使用了DRV8701双路电机驱动,DRV8701双路电机驱动采用门极驱动芯片DRV8701E + N-MOS管TPH1R403NL方案,输入电源5.
在《龙芯2k0300 - 走马观碑组第21届智能汽车竞赛软硬件设计》中我们使用了DRV8701双路电机驱动,DRV8701双路电机驱动采用门极驱动芯片DRV8701E + N-MOS管TPH1R403NL方案,输入电源5.9V ~ 28V均可使用,带过流保护功能。 一、DRV8701双路电机驱动 其中 PH-6P接口用来隔离供电以及信号输入; 红色端子同来提供直流电压输入,输入电压5.9-28V,一般大小为电机可承受的最大电压; 蓝色端子是直流电压输出,输出电压与输入PWM值正相关,连接到电机上。 1.1 电路原理图 电路原理图如下: 1.1.1 DRV8701E DRV8701E是德州仪器 (TI) 的一款单H桥栅极驱动器,专门用来控制外部的4个N沟道MOSFET,关键特性; 控制接口:它采用PH/EN (相位/使能)逻辑接口。这意味着你只需要两个GPIO信号就能控制电机的正转、反转、刹车和滑行,逻辑非常清晰。 自举供电:它内置了一个电荷泵 (Charge Pump),可以产生驱动高侧N-MOS管所需的高于电源电压的栅极驱动电压(通常为 9.5V),这使得它可以高效地驱动高侧开关。 可调驱动强度:它有一个 IDRIVE 引脚,可以通过一个电阻来调节栅极的驱动电流(从6mA到150mA)。在龙邱方案中(5.9V-28V 输入),这个功能非常有用,可以根据电源电压调整开关速度,平衡开关损耗和电磁干扰 (EMI)。 电流检测与保护:它内置了电流检测放大器,配合外部采样电阻,可以实现 PWM电流斩波(限制启动或堵转电流)。同时具备欠压锁定 (UVLO)、过流保护 (OCP) 和热关断 (TSD) 功能。 1.1.2 TPH1R403NL 这是一款Toshiba的N沟道MOSFET,这里会用到4颗(H桥结构),关键特性; 超低导通电阻:它的\(R_{DS(ON)}\)典型值仅为1.7 mΩ (在\(V_{GS}=4.5V\)时)。这是一个非常惊人的低数值,意味着导通损耗极低,发热小,效率极高; 大电流能力:单颗芯片的漏极电流可达60A-150A(取决于测试条件),配合DRV8701E的强驱能力,足以应对大扭矩电机的启动电流。 低门槛电压:它支持4.5V的低电压逻辑驱动。这与DRV8701E在电池供电(如5.9V-12V)时的输出电压完美匹配,确保在电池电压下降时MOS管依然能完全导通,避免烧管。 1.1.3 74HC125PW 74HC125PW是一款非常经典的数字逻辑芯片,它的核心身份是“四路缓冲器/线路驱动器”,并且带有三态输出功能。 可以把它想象成4个独立的电子开关; 缓冲/驱动(增强信号):当开关“打开”时,它会将输入的信号(0或1)原样输出,但输出能力更强。比如,单片机的引脚电流很小,带不动大功率负载,通过74HC125后,就能驱动更重的负载(比如长导线或更多的逻辑门); 三态输出(关键功能): 这是它最特别的地方。普通的芯片输出只有“高电平(1)”和“低电平(0)”。而74HC125有第三种状态高阻态,高阻态相当于开关断开,这根线就像悬空一样,既不输出高也不输出低,对电路没有任何影响。 它还可以用来隔离信号,防止不同模块之间的信号干扰。 1.2 控制原理 DRV8701有两种控制模式:PH/EN模式以及PWM模式,DRV8701芯片表面印的字,最后会有一个字母,决定了它的工作逻辑: DRV8701E(后缀是 E)芯片内部电路被设计为PH/EN模式,此时,15号引脚被定义为PH(方向),14号引脚被定义为EN(使能); DRV8701P(后缀是 P):芯片内部电路被设计为 PWM (IN1/IN2) 模式。此时,15号引脚被定义为IN1,14 号引脚被定义为 IN2。 1.2.1 PH/EN模式 这种模式使用PH(Phase/相位)和EN(Enable/使能)两个信号来控制电机。逻辑非常直观,适合单片机控制。 1.2.2 PWM模式 这种模式使用IN1和IN2两个信号直接控制H桥的上下臂,通常用于直接输入PWM波的场景。 1.2.3 H桥工作原理 更多H桥工作原理内容可以参考《H桥工作原理》。 1.3 电机控制 1.3.1 控制逻辑 经过前面的分析,不难得出左电机控制逻辑; nSLEEP PWM1(EN/IN2) GPIO1(PH/IN1) 电机状态 0 X X 电机停止 1 0 X 刹车(续流) 1 1 0 反转 1 1 1 正转 所以: 给引脚GPIO1为高电平,然后引脚PWM1输入PWM就可以进行左电机正转调速控制; 给引脚GPIO1为低电平,然后引脚PWM1输入PWM就可以进行左电机反转调速控制; 同理,右电机控制逻辑: 给引脚GPIO2为高电平,然后引脚PWM2输入PWM就可以进行右电机正转调速控制。 给引脚GPIO2为低电平,然后引脚PWM2输入PWM就可以进行右电机反转调速控制。 当PWM波的占空比越大时,蓝色端子输出的电压值与红色端子的电压值越接近,反之则与0V越接近。 PWM波的频率不可以随意设置,频率太低会导致电机运转不畅,振动大,噪音大;频率太高会导致驱动器开关损耗较大,甚至有电机会啸叫而不转的情况。一般1k~30k的PWM频率较为普遍,几百Hz的也有,实际上需要根据电机功率在测试时确定合适的PWM频率范围为宜。 1.3.2 硬件接线 久久派底版控制口原理图如下: DRV8701双路电机驱动板与龙芯2K0300开发板的连接关系需严格对应,连接表如下: 电机驱动板 底版引脚 连接的龙芯GPIO 说明 3.3V 3.3V 3.3V 接3.3V PWM1 MT1_P GPIO65(SPI2_MISO) 左电机PWM控制 GPIO1 MT1_N GPIO75(CAN3_TX) 左电机方向引脚 PWM2 MT2_P GPIO66(SPI2_MOSI) 右电机PWM控制 GPIO2 MT2_N GPIO74(CAN3_RX) 右电机方向引脚 GND GND GND 必须可靠接地,否则可能出现显示异常 在《龙芯2k0300 - 久久派开发环境搭建及内核升级(上)》中我们介绍到龙芯2K0300集成4路PWM控制器,支持输入/输出,根据《龙芯2K0300处理器用户手册》手册查询到芯片功能引脚复用关系表; 芯片引脚 GPIO 复用 主功能复用 第一复用 第二复用 SPI2_CLK GPIO64 spi2_clk PWM0 uart0_dcd SPI2_MISO GPIO65 spi2_miso PWM1 uart0_ri SPI2_MOSI GPIO66 spi2_mosi PWM2 uart1_rts SPI2_CS GPIO67 spi2_cs PWM3 uart1_cts 注:除芯片启动相关的引脚(SPI0或SDIO0/eMMC0在相应启动模式下对应引脚为主功能) 外,以上复用引脚上电默认状态都复用为GPIO 功能,其中GPIO0~63默认为输入状态,GPIO64~105默认为输出低电平状态。 因此我们可以知道GPIO65、GPIO66引脚是可以服用为PWM功能的。 二 PWM设备驱动 PWM(脉冲宽度调制)通过改变输出方波的占空比来调节电机两端的平均电压,从而控制转速。结合DRV8701E驱动芯片的PH/EN模式,可以用两个GPIO控制一个电机:一个PWM引脚(调速),一个方向引脚(换向)。 2.1 内核配置 进入内核配置界面: zhengyang@ubuntu:~$ cd /opt/2k0300/build-2k0300/workspace/linux-6.12 zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ source ../set_env.sh && make menuconfig 依次进入以下菜单: Device Drivers → [*] Pulse-Width Modulation (PWM) Support ---> → <*> Loongson PWM support 默认会生成配置: CONFIG_PWM_LOONGSON=y arch/loongarch/configs/loongson_2k300_defconfig文件已经配置了这个,所以我们不用修改任何配置。 2.2 PWM驱动 驱动源码位于drivers/pwd/pwm-loongson.c: 点击查看详情 // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2017-2024 Loongson Technology Corporation Limited. * * Loongson PWM driver * * For Loongson's PWM IP block documentation please refer Chapter 11 of * Reference Manual: https://loongson.github.io/LoongArch-Documentation/Loongson-7A1000-usermanual-EN.pdf * * Author: Juxin Gao <gaojuxin@loongson.cn> * Further cleanup and restructuring by: * Binbin Zhou <zhoubinbin@loongson.cn> * * Limitations: * - If both DUTY and PERIOD are set to 0, the output is a constant low signal. * - When disabled the output is driven to 0 independent of the configured * polarity. */ #include <linux/acpi.h> #include <linux/clk.h> #include <linux/device.h> #include <linux/init.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/units.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/delay.h> /* Loongson PWM registers */ #define LOONGSON_PWM_REG_DUTY 0x4 /* Low Pulse Buffer Register */ #define LOONGSON_PWM_REG_PERIOD 0x8 /* Pulse Period Buffer Register */ #define LOONGSON_PWM_REG_CTRL 0xc /* Control Register */ /* Control register bits */ #define LOONGSON_PWM_CTRL_EN BIT(0) /* Counter Enable Bit */ #define LOONGSON_PWM_CTRL_OE BIT(3) /* Pulse Output Enable Control Bit, Valid Low */ #define LOONGSON_PWM_CTRL_SINGLE BIT(4) /* Single Pulse Control Bit */ #define LOONGSON_PWM_CTRL_INTE BIT(5) /* Interrupt Enable Bit */ #define LOONGSON_PWM_CTRL_INT BIT(6) /* Interrupt Bit */ #define LOONGSON_PWM_CTRL_RST BIT(7) /* Counter Reset Bit */ #define LOONGSON_PWM_CTRL_CAPTE BIT(8) /* Measurement Pulse Enable Bit */ #define LOONGSON_PWM_CTRL_INVERT BIT(9) /* Output flip-flop Enable Bit */ #define LOONGSON_PWM_CTRL_DZONE BIT(10) /* Anti-dead Zone Enable Bit */ /* default input clk frequency for the ACPI case */ #define LOONGSON_PWM_FREQ_DEFAULT 50000000 /* Hz */ struct pwm_loongson_suspend_store { u32 ctrl; u32 duty; u32 period; }; struct pwm_loongson_ddata { struct clk *clk; void __iomem *base; u32 irq; u32 int_count; u64 clk_rate; struct pwm_loongson_suspend_store lss; struct wait_queue_head capture_wait_queue; }; static inline struct pwm_loongson_ddata *to_pwm_loongson_ddata(struct pwm_chip *chip) { return pwmchip_get_drvdata(chip); } static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, u32 offset) { return readl(ddata->base + offset); } static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata, u32 val, u32 offset) { writel(val, ddata->base + offset); } static irqreturn_t pwm_loongson_isr(int irq, void *dev) { u32 val; struct pwm_chip *chip = dev_get_drvdata(dev); struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); if ((val & LOONGSON_PWM_CTRL_INT) == 0) { return IRQ_NONE; } val |= LOONGSON_PWM_CTRL_INT; pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); ddata->int_count++; dev_info(dev, "pwm_loongson_isr count %u\n", ddata->int_count); return IRQ_HANDLED; } static int pwm_loongson_capture(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout) { u32 val; struct device *dev = pwmchip_parent(chip); struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); int ret; pwm_loongson_writel(ddata, 0, LOONGSON_PWM_REG_PERIOD); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); val |= LOONGSON_PWM_CTRL_EN | LOONGSON_PWM_CTRL_CAPTE | LOONGSON_PWM_CTRL_OE; pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); ret = readl_poll_timeout(ddata->base + LOONGSON_PWM_REG_PERIOD, val, val, 10, timeout * 1000); if (ret < 0) return -ETIMEDOUT; dev_dbg(dev, "get first period: %u\n", val); usleep_range(val * 4 / 100 + 1, val * 4 / 100 + 2); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD); result->period = DIV64_U64_ROUND_UP((u64)val * NSEC_PER_SEC, ddata->clk_rate); dev_dbg(dev, "get second period: %u\n", val); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY); result->duty_cycle = DIV64_U64_ROUND_UP((u64)val * NSEC_PER_SEC, ddata->clk_rate); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); val &= ~(LOONGSON_PWM_CTRL_EN | LOONGSON_PWM_CTRL_CAPTE | LOONGSON_PWM_CTRL_OE); pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); return 0; } static int pwm_loongson_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity) { u16 val; struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); if (polarity == PWM_POLARITY_INVERSED) /* Duty cycle defines LOW period of PWM */ val &= ~LOONGSON_PWM_CTRL_INVERT; else /* Duty cycle defines HIGH period of PWM */ val |= LOONGSON_PWM_CTRL_INVERT; pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); return 0; } static void pwm_loongson_disable(struct pwm_chip *chip, struct pwm_device *pwm) { u32 val; u32 duty; u32 period; u32 period_1000ns; struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); duty = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY); period = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD); period_1000ns = mul_u64_u64_div_u64(1000, ddata->clk_rate, NSEC_PER_SEC); pwm_loongson_writel(ddata, 1, LOONGSON_PWM_REG_DUTY); pwm_loongson_writel(ddata, period_1000ns, LOONGSON_PWM_REG_PERIOD); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); val |= LOONGSON_PWM_CTRL_RST; pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); val ^= LOONGSON_PWM_CTRL_RST; pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); ndelay(1000); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); val &= ~LOONGSON_PWM_CTRL_EN; pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); pwm_loongson_writel(ddata, duty, LOONGSON_PWM_REG_DUTY); pwm_loongson_writel(ddata, period, LOONGSON_PWM_REG_PERIOD); } static int pwm_loongson_enable(struct pwm_chip *chip, struct pwm_device *pwm) { u32 val; struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); val |= LOONGSON_PWM_CTRL_EN; pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); return 0; } static int pwm_loongson_config(struct pwm_chip *chip, struct pwm_device *pwm, u64 duty_ns, u64 period_ns) { u64 duty, period; struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */ duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC); pwm_loongson_writel(ddata, duty, LOONGSON_PWM_REG_DUTY); /* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */ period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC); pwm_loongson_writel(ddata, period, LOONGSON_PWM_REG_PERIOD); return 0; } static int pwm_loongson_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { int ret; u64 period, duty_cycle; bool enabled = pwm->state.enabled; if (!state->enabled) { if (enabled) pwm_loongson_disable(chip, pwm); return 0; } ret = pwm_loongson_set_polarity(chip, pwm, state->polarity); if (ret) return ret; period = min(state->period, NSEC_PER_SEC); duty_cycle = min(state->duty_cycle, NSEC_PER_SEC); ret = pwm_loongson_config(chip, pwm, duty_cycle, period); if (ret) return ret; if (!enabled && state->enabled) ret = pwm_loongson_enable(chip, pwm); return ret; } static int pwm_loongson_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { u32 duty, period, ctrl; struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); duty = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY); period = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD); ctrl = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); /* duty & period have a max of 2^32, so we can't overflow */ state->duty_cycle = DIV64_U64_ROUND_UP((u64)duty * NSEC_PER_SEC, ddata->clk_rate); state->period = DIV64_U64_ROUND_UP((u64)period * NSEC_PER_SEC, ddata->clk_rate); state->polarity = (ctrl & LOONGSON_PWM_CTRL_INVERT) ? PWM_POLARITY_NORMAL : PWM_POLARITY_INVERSED; state->enabled = (ctrl & LOONGSON_PWM_CTRL_EN) ? true : false; return 0; } static const struct pwm_ops pwm_loongson_ops = { .capture = pwm_loongson_capture, .apply = pwm_loongson_apply, .get_state = pwm_loongson_get_state, }; static int pwm_loongson_probe(struct platform_device *pdev) { int ret; struct pwm_chip *chip; struct pwm_loongson_ddata *ddata; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; u32 of_clk_freq = 0; u32 irq; u32 init_polarity = 0; irq = platform_get_irq(pdev, 0); dev_info(dev, "irq get:%u\n", irq); if (irq <= 0) { dev_err(&pdev->dev, "no irq resource?\n"); return -ENODEV; } chip = devm_pwmchip_alloc(dev, 1, sizeof(*ddata)); if (IS_ERR(chip)) return PTR_ERR(chip); ddata = to_pwm_loongson_ddata(chip); // 初始化等待队列 init_waitqueue_head(&ddata->capture_wait_queue); ddata->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ddata->base)) return PTR_ERR(ddata->base); ddata->clk = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(ddata->clk)) return dev_err_probe(dev, PTR_ERR(ddata->clk), "failed to get pwm clock\n"); ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT; if (ddata->clk) { ret = devm_clk_rate_exclusive_get(dev, ddata->clk); if (ret) return dev_err_probe(dev, ret, "Failed to get exclusive rate\n"); ddata->clk_rate = clk_get_rate(ddata->clk); if (!ddata->clk_rate) return dev_err_probe(dev, -EINVAL, "Failed to get frequency\n"); } else { #ifdef CONFIG_OF if (!of_property_read_u32(np, "clock-frequency", &of_clk_freq)) ddata->clk_rate = of_clk_freq; of_property_read_u32(np, "init-polarity", &init_polarity); #endif } ddata->irq = irq; ret = devm_request_irq(dev, ddata->irq, pwm_loongson_isr, IRQF_SHARED, dev_name(dev), dev); if (ret) dev_err(dev, "failure requesting irq %d\n", ret); /* Explicitly initialize the CTRL register */ pwm_loongson_writel(ddata, 0, LOONGSON_PWM_REG_CTRL); chip->ops = &pwm_loongson_ops; chip->atomic = true; dev_set_drvdata(dev, chip); pwm_loongson_set_polarity(chip, NULL, init_polarity ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL); if (init_polarity) { pwm_loongson_config(chip, NULL, 0, 100000); pwm_loongson_enable(chip, NULL); pwm_loongson_disable(chip, NULL); } ret = devm_pwmchip_add(dev, chip); if (ret < 0) return dev_err_probe(dev, ret, "failed to add PWM chip\n"); return 0; } static int pwm_loongson_suspend(struct device *dev) { struct pwm_chip *chip = dev_get_drvdata(dev); struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); ddata->lss.ctrl = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); ddata->lss.duty = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY); ddata->lss.period = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD); clk_disable_unprepare(ddata->clk); return 0; } static int pwm_loongson_resume(struct device *dev) { int ret; struct pwm_chip *chip = dev_get_drvdata(dev); struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); ret = clk_prepare_enable(ddata->clk); if (ret) return ret; pwm_loongson_writel(ddata, ddata->lss.ctrl, LOONGSON_PWM_REG_CTRL); pwm_loongson_writel(ddata, ddata->lss.duty, LOONGSON_PWM_REG_DUTY); pwm_loongson_writel(ddata, ddata->lss.period, LOONGSON_PWM_REG_PERIOD); return 0; } static DEFINE_SIMPLE_DEV_PM_OPS(pwm_loongson_pm_ops, pwm_loongson_suspend, pwm_loongson_resume); static const struct of_device_id pwm_loongson_of_ids[] = { { .compatible = "loongson,ls7a-pwm" }, { .compatible = "loongson,ls2k-pwm" }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, pwm_loongson_of_ids); static const struct acpi_device_id pwm_loongson_acpi_ids[] = { { "LOON0006" }, { } }; MODULE_DEVICE_TABLE(acpi, pwm_loongson_acpi_ids); static struct platform_driver pwm_loongson_driver = { .probe = pwm_loongson_probe, .driver = { .name = "loongson-pwm", .pm = pm_ptr(&pwm_loongson_pm_ops), .of_match_table = pwm_loongson_of_ids, .acpi_match_table = pwm_loongson_acpi_ids, }, }; module_platform_driver(pwm_loongson_driver); MODULE_DESCRIPTION("Loongson PWM driver"); MODULE_AUTHOR("Loongson Technology Corporation Limited."); MODULE_LICENSE("GPL"); 内核的PWM驱动pwm-loongson.c已实现标准PWM框架接口,无需编写额外驱动。驱动提供以下功能: 功能 实现方式 周期设置 .apply回调 占空比设置 .apply回调 使能/禁用 .apply回调 捕获输入 .capture回调(部分支持) 驱动加载后,会在/sys/class/pwm/下创建pwmchipX目录。 2.3 新增设备节点 2.3.1 pwm pwm节点定义在arch/loongarch/boot/dts/loongson-2k0300.dtsi: zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ vim arch/loongarch/boot/dts/loongson-2k0300.dtsi 我们可以看到pwm0、pwm1、pwm2、pwm3节点,但是我们只关注pwm1、pwm2节点; pwm1: pwm@0x1611b010 { compatible = "loongson,ls2k-pwm"; reg = <0 0x1611b010 0 0xf>; interrupt-parent = <&liointc0>; interrupts = <16 IRQ_TYPE_LEVEL_HIGH>; #pwm-cells = <2>; pinctrl-0 = <&pwm1_mux_m1>; pinctrl-names = "default"; clock-frequency = <200000000>; status = "disabled"; }; pwm2: pwm@0x1611b020 { compatible = "loongson,ls2k-pwm"; reg = <0 0x1611b020 0 0xf>; interrupt-parent = <&liointc0>; interrupts = <17 IRQ_TYPE_LEVEL_HIGH>; #pwm-cells = <2>; pinctrl-0 = <&pwm2_mux_m1>; pinctrl-names = "default"; clock-frequency = <200000000>; status = "disabled"; }; 其中: pwm1::标签,用于在设备树中其他地方通过&pwm1引用这个节点,方便添加属性或修改状态; pwm@0x1611b010:节点名,格式为 设备名@寄存器基地址。这里pwm是功能名,0x1611b010是该PWM1控制器的物理寄存器基地址。 compatible:驱动匹配字符串。内核通过该属性寻找能驱动此设备的驱动程序; reg:寄存器地址范围。格式为 <地址高位 地址低位 长度高位 长度低位>,由于龙芯采用64位寻址,这里用两个32位数表示64位地址; interrupt-parent:指定该设备的中断路由到哪个中断控制器。&liointc0 是龙芯2K0300内部的中断控制器节点(即龙芯I/O中断控制器)的标签; interrupts:描述中断线的具体信息; <16>:硬件中断编号(对应PWM1控制器在中断控制器中的编号); IRQ_TYPE_LEVEL_HIGH:中断触发类型,这里定义为高电平触发。该宏在 <dt-bindings/interrupt-controller/irq.h> 中定义。 pinctrl-0:指定设备使用的第一组引脚配置,&pwm1_mux_m1 是另一个设备树节点的标签,该节点描述了GPIO87引脚复用为PWM1功能; pinctrl-names:为引脚的配置状态命名,与 pinctrl-0 对应。"default" 是默认状态,驱动在probe时会自动应用 pinctrl-0 的配置,引脚配置设置为&pwm1_mux_m1; clock-frequency:PWM1控制器的输入时钟频率(单位Hz); status:设备状态,此时处于禁用状态。 更多有关设备树相关的内容可以参考:《linux设备树-基础介绍》。 2.3.2 使能 修改arch/loongarch/boot/dts/ls2k300_99pi.dtsi,使能pwm1、pwm2; &pwm1 { status = "okay"; pinctrl-0 = <&pwm1_mux_m0>; pinctrl-names = "default"; }; &pwm2 { status = "okay"; pinctrl-0 = <&pwm2_mux_m0>; pinctrl-names = "default"; }; // PWM1和PWM2(GPIO65/66)与SPI2冲突,需要禁用 &spi2 { status = "disabled"; }; // GPIO75和GPIO74与CAN3,需要禁用 &can3 { status = "disabled"; }; 通过 &pwm1、pwm2 引用这个节点,将 status 改为 "okay",同时根据实际硬件修改引脚配置。 2.3.3 pwm1_mux_m1&pwm2_mux_m1 pwm1_mux_m1、pwm2_mux_m1定义在arch/loongarch/boot/dts/loongson-2k0300.dtsi: pinmux: pinmux@16000490 { ...... pwm1_pins: pwm1-pins { pwm1_mux_m0: pinmux_G65_as_pwm1 { pinctrl-single,bits = <0x10 0x00000004 0x0000000c>; }; pwm1_mux_m1: pinmux_G87_as_pwm1 { pinctrl-single,bits = <0x14 0x00008000 0x0000c000>; }; pwm1_mux_m2: pinmux_G103_as_pwm1 { pinctrl-single,bits = <0x18 0x0000c000 0x0000c000>; }; }; pwm2_pins: pwm2-pins { pwm2_mux_m0: pinmux_G66_as_pwm2 { pinctrl-single,bits = <0x10 0x00000010 0x00000030>; }; pwm2_mux_m1: pinmux_G88_as_pwm2 { pinctrl-single,bits = <0x14 0x00020000 0x00030000>; }; pwm2_mux_m2: pinmux_G104_as_pwm2 { pinctrl-single,bits = <0x18 0x00030000 0x00030000>; }; }; ...... } 这个设备树节点是使用pinctrl-single驱动来配置引脚复用功能的典型写法,pwm1_mux_m1这行代码会在寄存器0x160004a4的位[15:14]位写入 10b,而其它位保持不变,用于将芯片的GPIO87这个引脚设置为PWM1功能。 实际上对于左电机我们使用的是GPIO65引脚,即我们需要使用的是pwm1_mux_m0,pwm1_mux_m0这行代码会在寄存器0x160004a0的位[3:2]位写入 01b,而其它位保持不变,用于将芯片的GPIO65这个引脚设置为PWM1功能。 同理对于右电机我们使用的是GPIO66引脚,即我们需要使用的是pwm2_mux_m0,pwm2_mux_m0这行代码会在寄存器0x160004a0的位[5:4]位写入 01b,而其它位保持不变,用于将芯片的GPIO66这个引脚设置为PWM1功能。 注:pinctrl-single 是一个通用的引脚控制驱动,适用于那些引脚复用寄存器是“单寄存器位域”的芯片。当SoC没有提供复杂的pinctrl框架时,可以直接用这种方式“裸写”寄存器。 三、应用程序 接下来我们在example目录下创建子目录pwm_app; zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example$ mkdir pwm_app zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example$ cd pwm_app 目录结构如下: zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/pwm_app$ tree . . ├── inc │ ├── motor_control.h # 电机控制 │ └── platform.h # 平台抽象层接口 ├── main.c ├── Makefile └── src ├── motor_control.c # 电机控制逻辑 └── platform_linux.c # Linux 平台实现 该案例通过用户层提供的GPIO和PWM接口实现了电机控制。 3.1 源码 3.1.1 platform.h #ifndef PLATFORM_H #define PLATFORM_H #include <stdint.h> #include <stdbool.h> // ==================== 引脚类型定义 ==================== typedef enum { PIN_MODE_INPUT, PIN_MODE_OUTPUT, PIN_MODE_PWM } pin_mode; // ==================== PWM 配置结构体 ==================== typedef struct { int chip; // PWM 控制器编号 int channel; // PWM 通道号 int period_ns; // 周期(纳秒) int duty_ns; // 占空比(纳秒) int direction; // 方向 1:正转 0:未初始化 -1:反转 bool enabled; // 是否使能 void *priv; // 平台私有数据 } pwm_handle; // ==================== GPIO 配置结构体 ==================== typedef struct { int pin; // 引脚号(平台相关) pin_mode mode; // 引脚模式 void *priv; // 平台私有数据 } gpio_handle; // ==================== 平台操作接口 ==================== typedef struct platform_ops { // GPIO 操作 int (*gpio_init)(gpio_handle *gpio); void (*gpio_set)(gpio_handle *gpio, int value); int (*gpio_get)(gpio_handle *gpio); void (*gpio_deinit)(gpio_handle *gpio); // PWM 操作 int (*pwm_init)(pwm_handle *pwm); void (*pwm_set_duty)(pwm_handle *pwm, int duty_ns); void (*pwm_enable)(pwm_handle *pwm, bool enable); void (*pwm_deinit)(pwm_handle *pwm); // 延时操作(毫秒/微秒) void (*delay_ms)(uint32_t ms); void (*delay_us)(uint32_t us); } platform_ops; // 全局平台操作接口(由平台适配层实现) extern const platform_ops platform; #endif // PLATFORM_H 3.1.2 motor_control.h #ifndef MOTOR_CONTROL_H #define MOTOR_CONTROL_H #include "platform.h" // ==================== 电机配置结构体 ==================== typedef struct { pwm_handle pwm; // PWM 配置 gpio_handle dir_gpio; // 方向控制 GPIO const char *name; // 电机名称(调试用) } motor_config; // ==================== 电机控制 API ==================== int motor_init(motor_config *motor); void motor_set_duty(motor_config *motor, int duty); void motor_stop(motor_config *motor); void motor_deinit(motor_config *motor); #endif // MOTOR_CONTROL_H 3.1.3 platform_linux.c 这是一个基于linux平台的实现,其通过用户空间接口实现对GPIO和PWM的控制; GPIO子系统提供了通过sysfs控制GPIO的方式; PWM子系统提供了通过sysfs控制PWM的方式。 更多细节可以参考: 《Rockchip RK3399 - GPIO&PWM风扇调试》。 具体实现如下: 点击查看详情 #include "platform.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #include <errno.h> // [修改] 引入 errno 以获取错误码 #include <syslog.h> // [修改] 引入 syslog 用于更规范的日志输出 // ==================== Linux GPIO 私有数据 ==================== typedef struct { int pin; int fd_value; char path_value[64]; } linux_gpio_priv; // ==================== Linux PWM 私有数据 ==================== typedef struct { int chip; int channel; char base_path[128]; } linux_pwm_priv; // ==================== GPIO 实现 ==================== static int linux_gpio_init(gpio_handle *gpio) { linux_gpio_priv *priv = malloc(sizeof(linux_gpio_priv)); if (!priv) { perror("[GPIO] Failed to allocate memory"); // [修改] return -1; } priv->pin = gpio->pin; // 导出 GPIO char path[64]; int fd = open("/sys/class/gpio/export", O_WRONLY); if (fd >= 0) { char buf[16]; int len = snprintf(buf, sizeof(buf), "%d", gpio->pin); if (write(fd, buf, len) < 0) { // [修改] 导出失败可能是因为它已经存在,这不一定是致命错误,但值得打印 // 如果 errno == EBUSY,说明引脚已被占用 if (errno != EBUSY) { fprintf(stderr, "[GPIO] Failed to export GPIO %d: %s\n", gpio->pin, strerror(errno)); } } close(fd); } else { // [修改] 连 export 文件都打不开,通常是权限问题 fprintf(stderr, "[GPIO] Failed to open /sys/class/gpio/export: %s\n", strerror(errno)); } // [修改] 增加短暂延时,等待 sysfs 节点创建 usleep(10000); // 10ms // 设置方向 snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/direction", gpio->pin); fd = open(path, O_WRONLY); if (fd >= 0) { const char *dir = (gpio->mode == PIN_MODE_OUTPUT) ? "out" : "in"; if (write(fd, dir, strlen(dir)) < 0) { fprintf(stderr, "[GPIO] Failed to set direction for GPIO %d: %s\n", gpio->pin, strerror(errno)); } close(fd); } else { fprintf(stderr, "[GPIO] Failed to open direction file for GPIO %d: %s\n", gpio->pin, strerror(errno)); } // 保存 value 文件路径 snprintf(priv->path_value, sizeof(priv->path_value), "/sys/class/gpio/gpio%d/value", gpio->pin); gpio->priv = priv; return 0; } static void linux_gpio_set(gpio_handle *gpio, int value) { linux_gpio_priv *priv = (linux_gpio_priv*)gpio->priv; int fd = open(priv->path_value, O_WRONLY); if (fd >= 0) { char buf[] = {'0' + (value ? 1 : 0), '\n', '\0'}; if (write(fd, buf, 1) < 0) { fprintf(stderr, "[GPIO] Failed to write value to GPIO %d: %s\n", priv->pin, strerror(errno)); } close(fd); } else { fprintf(stderr, "[GPIO] Failed to open value file for GPIO %d: %s\n", priv->pin, strerror(errno)); } } static int linux_gpio_get(gpio_handle *gpio) { linux_gpio_priv *priv = (linux_gpio_priv*)gpio->priv; int fd = open(priv->path_value, O_RDONLY); if (fd >= 0) { char buf[4]; ssize_t bytes_read = read(fd, buf, sizeof(buf) - 1); // [修改] 留出空间给 '\0' if (bytes_read > 0) { buf[bytes_read] = '\0'; // [修改] 确保字符串结束 close(fd); return (buf[0] == '1') ? 1 : 0; } close(fd); } else { // 读取失败时打印错误,但在某些高频调用场景下可能会刷屏,按需开启 // fprintf(stderr, "[GPIO] Failed to open value file for read: %s\n", strerror(errno)); } return 0; } static void linux_gpio_deinit(gpio_handle *gpio) { linux_gpio_priv *priv = (linux_gpio_priv*)gpio->priv; if (priv) { // 可选的 unexport 操作 int fd = open("/sys/class/gpio/unexport", O_WRONLY); if (fd >= 0) { char buf[16]; snprintf(buf, sizeof(buf), "%d", priv->pin); if (write(fd, buf, strlen(buf)) < 0) { fprintf(stderr, "[GPIO] Failed to unexport GPIO %d: %s\n", priv->pin, strerror(errno)); } close(fd); } free(priv); gpio->priv = NULL; } } // ==================== PWM 实现 ==================== static int linux_pwm_init(pwm_handle *pwm) { linux_pwm_priv *priv = malloc(sizeof(linux_pwm_priv)); if (!priv) { perror("[PWM] Failed to allocate memory"); return -1; } priv->chip = pwm->chip; priv->channel = pwm->channel; // 构建基础路径 snprintf(priv->base_path, sizeof(priv->base_path), "/sys/class/pwm/pwmchip%d/pwm%d", pwm->chip, pwm->channel); // 导出通道 char export_path[64]; snprintf(export_path, sizeof(export_path), "/sys/class/pwm/pwmchip%d/export", pwm->chip); int fd = open(export_path, O_WRONLY); if (fd >= 0) { char buf[16]; int len = snprintf(buf, sizeof(buf), "%d", pwm->channel); // [修改] 同样处理导出错误 if (write(fd, buf, len) < 0) { if (errno != EBUSY) { fprintf(stderr, "[PWM] Failed to export PWM chip %d channel %d: %s\n", pwm->chip, pwm->channel, strerror(errno)); } } close(fd); } else { fprintf(stderr, "[PWM] Failed to open export file for chip %d: %s\n", pwm->chip, strerror(errno)); } // [修改] 增加延时,等待节点创建 usleep(10000); // 10ms // 设置周期 char period_path[128]; int ret = snprintf(period_path, sizeof(period_path), "%s/period", priv->base_path); if (ret < 0 || ret >= (int)sizeof(period_path)) { fprintf(stderr, "[PWM] Period path too long\n"); free(priv); // [修改] 记得释放内存 return -1; } fd = open(period_path, O_WRONLY); if (fd >= 0) { char buf[32]; snprintf(buf, sizeof(buf), "%d", pwm->period_ns); if (write(fd, buf, strlen(buf)) < 0) { fprintf(stderr, "[PWM] Failed to set period: %s\n", strerror(errno)); } close(fd); } else { fprintf(stderr, "[PWM] Failed to open period file: %s\n", strerror(errno)); } pwm->priv = priv; return 0; } static void linux_pwm_set_duty(pwm_handle *pwm, int duty_ns) { linux_pwm_priv *priv = (linux_pwm_priv*)pwm->priv; if (!priv) return; char duty_path[128]; int ret = snprintf(duty_path, sizeof(duty_path), "%s/duty_cycle", priv->base_path); if (ret < 0 || ret >= (int)sizeof(duty_path)) { fprintf(stderr, "[PWM] Duty path too long\n"); return; } int fd = open(duty_path, O_WRONLY); if (fd >= 0) { char buf[32]; snprintf(buf, sizeof(buf), "%d", duty_ns); if (write(fd, buf, strlen(buf)) < 0) { fprintf(stderr, "[PWM] Failed to set duty cycle: %s\n", strerror(errno)); } close(fd); pwm->duty_ns = duty_ns; } else { fprintf(stderr, "[PWM] Failed to open duty_cycle file: %s\n", strerror(errno)); } } static void linux_pwm_enable(pwm_handle *pwm, bool enable) { linux_pwm_priv *priv = (linux_pwm_priv*)pwm->priv; if (!priv) return; char enable_path[128]; int ret = snprintf(enable_path, sizeof(enable_path), "%s/enable", priv->base_path); if (ret < 0 || ret >= (int)sizeof(enable_path)) { fprintf(stderr, "[PWM] Enable path too long\n"); return; } int fd = open(enable_path, O_WRONLY); if (fd >= 0) { if (write(fd, enable ? "1" : "0", 1) < 0) { fprintf(stderr, "[PWM] Failed to enable/disable PWM: %s\n", strerror(errno)); } close(fd); pwm->enabled = enable; } else { fprintf(stderr, "[PWM] Failed to open enable file: %s\n", strerror(errno)); } } static void linux_pwm_deinit(pwm_handle *pwm) { linux_pwm_enable(pwm, false); linux_pwm_priv *priv = (linux_pwm_priv*)pwm->priv; if (priv) { // 可选:unexport char unexport_path[64]; snprintf(unexport_path, sizeof(unexport_path), "/sys/class/pwm/pwmchip%d/unexport", priv->chip); int fd = open(unexport_path, O_WRONLY); if (fd >= 0) { char buf[16]; snprintf(buf, sizeof(buf), "%d", priv->channel); write(fd, buf, strlen(buf)); close(fd); } free(priv); pwm->priv = NULL; } } // ==================== 延时实现 ==================== static void linux_delay_ms(uint32_t ms) { usleep(ms * 1000); } static void linux_delay_us(uint32_t us) { usleep(us); } // ==================== 平台操作接口实例 ==================== const platform_ops platform = { .gpio_init = linux_gpio_init, .gpio_set = linux_gpio_set, .gpio_get = linux_gpio_get, .gpio_deinit = linux_gpio_deinit, .pwm_init = linux_pwm_init, .pwm_set_duty = linux_pwm_set_duty, .pwm_enable = linux_pwm_enable, .pwm_deinit = linux_pwm_deinit, .delay_ms = linux_delay_ms, .delay_us = linux_delay_us }; 3.1.4 motor_control.c #include "motor_control.h" #include <stdlib.h> #include <math.h> extern const platform_ops platform; // 初始化电机 int motor_init(motor_config *motor) { // 初始化方向 GPIO if (platform.gpio_init(&motor->dir_gpio) != 0) { return -1; } platform.gpio_set(&motor->dir_gpio, 0); // 初始化 PWM if (platform.pwm_init(&motor->pwm) != 0) { return -1; } platform.pwm_set_duty(&motor->pwm, 0); platform.pwm_enable(&motor->pwm, true); return 0; } // 设置电机占空比 (-100 ~ 100,负数反转,正数正转) void motor_set_duty(motor_config *motor, int duty) { // 限幅 if (duty > 100) duty = 100; if (duty < -100) duty = -100; // 计算占空比 int duty_ns = (abs(duty) * motor->pwm.period_ns) / 100; // 方向处理 int direction = (duty >= 0) ? 1 : -1; // 更新硬件 if (motor->pwm.duty_ns != duty_ns) { platform.pwm_set_duty(&motor->pwm, duty_ns); } if (motor->pwm.direction != direction) { platform.gpio_set(&motor->dir_gpio, (direction == 1) ? 1 : 0); } } // 停止电机 void motor_stop(motor_config *motor) { platform.pwm_set_duty(&motor->pwm, 0); } // 释放资源 void motor_deinit(motor_config *motor) { platform.pwm_deinit(&motor->pwm); platform.gpio_deinit(&motor->dir_gpio); } 3.1.5 main.c #include <stdio.h> #include "motor_control.h" extern const platform_ops platform; // 全局电机配置实例 motor_config motor_left = { .pwm = { .chip = 0, .channel = 0, .period_ns = 50000, // 20kHz .duty_ns = 0, .direction = 0, .enabled = false, .priv = NULL }, .dir_gpio = { .pin = 75, // GPIO75 .mode = PIN_MODE_OUTPUT, .priv = NULL } }; motor_config motor_right = { .pwm = { .chip = 1, .channel = 0, .period_ns = 50000, // 20kHz .duty_ns = 0, .direction = 0, .enabled = false, .priv = NULL }, .dir_gpio = { .pin = 74, // GPIO74 .mode = PIN_MODE_OUTPUT, .priv = NULL } }; int main() { printf("Initializing motors...\n"); if (motor_init(&motor_left) != 0) { printf("Left motor init failed!\n"); return -1; } if (motor_init(&motor_right) != 0) { printf("Right motor init failed!\n"); return -1; } printf("Motor control ready!\n"); // 前进 printf("Forward...\n"); motor_set_duty(&motor_left, 30); motor_set_duty(&motor_right, 30); platform.delay_ms(3000); // 左转 printf("Turn left...\n"); motor_set_duty(&motor_left, -30); motor_set_duty(&motor_right, 30); platform.delay_ms(2000); // 停止 printf("Stop.\n"); motor_stop(&motor_left); motor_stop(&motor_right); platform.delay_ms(500); // 释放资源 motor_deinit(&motor_left); motor_deinit(&motor_right); return 0; } 这里左电机先正转后反转,如果学过电机调速控制的话,应该知道电机从正转快速切换到反转,会产生一个很大的瞬时电流,具体原理可以参考《直流有刷电机及机械特性》文章中的反接制动环节。 对于使用DRV8701这类专用电机驱动芯片的系统,这种瞬时大电流是在设计中就被考虑到的正常工况,因此很少会损坏芯片。 此外,如果占空比设置的过小,电机可能不转动。 3.2 Makefile # 编译器设置 CC = loongarch64-linux-gnu-gcc CFLAGS = -Wall -Iinc -lm # 目录设置 SRC_DIR = src INC_DIR = inc BUILD_DIR = build TARGET = main # 源文件(src 目录下的 .c 文件 + 根目录的 main.c) SRCS = $(wildcard $(SRC_DIR)/*.c) main.c OBJS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(SRCS))) # 处理路径:将 main.c 和 src/*.c 都放到 build 目录 VPATH = $(SRC_DIR):. # 默认目标 all: $(BUILD_DIR) $(TARGET) # 创建 build 目录 $(BUILD_DIR): mkdir -p $(BUILD_DIR) # 链接 $(TARGET): $(OBJS) $(CC) -o $@ $^ $(CFLAGS) # 编译(适用于 src 目录和根目录的 .c 文件) $(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) $(CC) $(CFLAGS) -c $< -o $@ # 清理 clean: rm -rf $(BUILD_DIR) $(TARGET) # 查看编译信息 info: @echo "SRCS = $(SRCS)" @echo "OBJS = $(OBJS)" @echo "VPATH = $(VPATH)" .PHONY: all clean run info 3.3 编译应用程序 zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/pwm_app$ make mkdir -p build loongarch64-linux-gnu-gcc -Wall -Iinc -lm -c src/motor_control.c -o build/motor_control.o loongarch64-linux-gnu-gcc -Wall -Iinc -lm -c src/platform_linux.c -o build/platform_linux.o loongarch64-linux-gnu-gcc -Wall -Iinc -lm -c main.c -o build/main.o loongarch64-linux-gnu-gcc -o main build/motor_control.o build/platform_linux.o build/main.o -Wall -Iinc -lm zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/pwm_app$ ll drwxrwxr-x 2 zhengyang zhengyang 4096 4月 2 21:16 build/ drwxrwxr-x 2 zhengyang zhengyang 4096 4月 2 20:40 inc/ -rwxrwxr-x 1 zhengyang zhengyang 21960 4月 2 21:16 main* -rw-rw-r-- 1 zhengyang zhengyang 1730 4月 2 21:06 main.c -rw-rw-r-- 1 zhengyang zhengyang 863 4月 2 20:36 Makefile drwxrwxr-x 2 zhengyang zhengyang 4096 4月 2 20:32 src/ 四、测试 4.1 烧录设备树 4.1.1 编译设备树 如果需要单独编译设备树,在linux内核根目录执行如下命令: zhengyang@ubuntu:~$ cd /opt/2k0300/build-2k0300/workspace/linux-6.12 zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ source ../set_env.sh && make arch/loongarch/boot/dts/ls2k300_99pi_wifi.dts dtbs V=1 4.1.2 更新设备树 将设备树拷贝到久久派的/opt目录; zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ scp arch/loongarch/boot/dts/ls2k300_99pi_wifi.dtb root@172.23.34.188:/opt 在久久派使用dd命令烧写设备树到SPI Nor Flash的dtb分区; [root@LS-GD opt]# dd if=/opt/ls2k300_99pi_wifi.dtb of=/dev/mtdblock3 bs=1 22292+0 records in 22292+0 records out 22292 bytes (22 kB, 22 KiB) copied, 0.518064 s, 43.0 kB/s [root@LS-GD opt]# reboot 4.2 sysfs控制PWM PWM提供了用户层的接口,在 /sys/class/pwm/节点下面,PWM驱动加载成功后,会在/sys/class/pwm/目录下产生pwmchipX目录, 更多细节可以参考: 《Rockchip RK3399 - GPIO&PWM风扇调试》。 4.2.1 确认PWM设备 久久派启动系统后,在/sys/class/目录下可以看到pwm类,查看/sys/class/pwm/: [root@LS-GD ~]# ls -l /sys/class/pwm/ lrwxrwxrwx 1 root root 0 Jul 24 20:49 pwmchip0 -> ../../devices/platform/2k300-soc/1611b010.pwm/pwm/pwmchip0 lrwxrwxrwx 1 root root 0 Jul 24 20:49 pwmchip1 -> ../../devices/platform/2k300-soc/1611b020.pwm/pwm/pwmchip1 每个pwmchip对应一个PWM控制器,一个SoC可能有多个控制器(如 pwmchip0, pwmchip1)。 [root@LS-GD ~]# ls -l /sys/class/pwm/pwmchip0/ lrwxrwxrwx 1 root root 0 Jul 24 20:55 device -> ../../../1611b010.pwm --w------- 1 root root 16384 Jul 24 20:55 export -r--r--r-- 1 root root 16384 Jul 24 20:55 npwm drwxr-xr-x 2 root root 0 Jul 24 20:55 power lrwxrwxrwx 1 root root 0 Jul 24 20:49 subsystem -> ../../../../../../class/pwm -rw-r--r-- 1 root root 16384 Jul 24 20:49 uevent --w------- 1 root root 16384 Jul 24 20:55 unexport 注意: pwmchip0、pwmchip1 只是逻辑编号,它们并不直接对应GPIO编号。 其中: npwm:这是一个只读文件,读取它可以知道该控制器下共有多少路PWM通道; export:这是入口。在使用某个PWM通道前,必须向这个文件写入通道编号(如 0),系统才会在 pwmchipX 下生成一个 pwmX 目录,供你操作; unexport:使用完毕后,向这个文件写入编号,撤销对该通道的占用,删除对应的目录。 查看通道数量: [root@LS-GD ~]# cat /sys/class/pwm/pwmchip0/npwm 1 4.2.2 导出PWM通道 向pwmchip0/export文件写入0,导出PWM通道: [root@LS-GD ~]# echo 0 > /sys/class/pwm/pwmchip0/export 会生成 /sys/class/pwm/pwmchip0/pwm0/目录; [root@LS-GD ~]# ls -l /sys/class/pwm/pwmchip0/pwm0/ -r--r--r-- 1 root root 16384 Jul 24 21:01 capture -rw-r--r-- 1 root root 16384 Jul 24 21:01 duty_cycle -rw-r--r-- 1 root root 16384 Jul 24 21:01 enable -rw-r--r-- 1 root root 16384 Jul 24 21:01 period -rw-r--r-- 1 root root 16384 Jul 24 21:01 polarity drwxr-xr-x 2 root root 0 Jul 24 21:01 power -rw-r--r-- 1 root root 16384 Jul 24 21:01 uevent /sys/class/pwm/pwmchip0/pwm0目录下有以下几个文件: 文件名 单位 作用 说明 period 纳秒 (ns) 设置周期 决定 PWM 信号的频率。频率 = 1 / 周期。 duty_cycle 纳秒 (ns) 设置占空比 决定高电平持续的时间。占空比 = duty_cycle / period。 enable 0 或 1 启用/禁用 写入 1 开始输出波形,写入 0 停止输出。 polarity 字符串 设置极性 normal(正常)或 inversed(反转)。 4.2.3 配置PWM参数 设置pwm0输出频率20KHz(如20kHz → 50000ns),占空比50%(50% → 25000ns),极性为正极性: [root@LS-GD ~]# echo 50000 > /sys/class/pwm/pwmchip0/pwm0/period [root@LS-GD ~]# echo 25000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle [root@LS-GD ~]# echo normal > /sys/class/pwm/pwmchip0/pwm0/polarity [root@LS-GD ~]# cat /sys/class/pwm/pwmchip0/pwm0/enable 0 [root@LS-GD ~]# echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable 如果此时有逻辑分析仪或者示波器,可以捕获查看GPIO65引脚的输出信号; 4.3 应用程序测试 接着我们进行测试: [root@LS-GD opt]# scp zhengyang@172.23.34.187:/opt/2k0300/loongson_2k300_lib/example/pwm_app/main /opt [root@LS-GD opt]# ./main Initializing motors... Motor control ready! Forward... Turn left... Stop. 如果这个时候我们接了驱动板,并且连接了小车的电机,我们就可以验证电机是否按照我们的设定运行; 使用逻辑分析仪捕获到的GPIO65、GPIO75、GPIO66、GPIO74引脚信号如下; 可以看到前半段波形是Forward时的,后半段是Turn left时的。