打印
[STM32U5]

【NUCLEO-U5A5ZJ-Q测评】pwm驱动板载LED和自定义引脚PWM输出控制LED

[复制链接]
1113|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

一、板载LED
NUCLEO-U5A5ZJ-Q开发板自身提供了3个可以被用户控制的LED:


默认的引脚定义如下:



上述三个LED,可以在用户程序中进行控制。

二、PWM控制LED源码
在Zephyr的源码中,提供了一个 samples/drivers/led_pwm 可以使用PWM驱动LED:
/*
* Copyright (c) 2020 Seagate Technology LLC
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <errno.h>
#include <zephyr/drivers/led.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);

#define LED_PWM_NODE_ID         DT_COMPAT_GET_ANY_STATUS_OKAY(pwm_leds)

const char *led_label[] = {
        DT_FOREACH_CHILD_SEP_VARGS(LED_PWM_NODE_ID, DT_PROP_OR, (,), label, NULL)
};

const int num_leds = ARRAY_SIZE(led_label);

#define MAX_BRIGHTNESS        100

#define FADE_DELAY_MS        10
#define FADE_DELAY        K_MSEC(FADE_DELAY_MS)

/**
* [url=home.php?mod=space&uid=247401]@brief[/url] Run tests on a single LED using the LED API syscalls.
*
* @param led_pwm LED PWM device.
* @param led Number of the LED to test.
*/
static void run_led_test(const struct device *led_pwm, uint8_t led)
{
        int err;
        uint16_t level;

        LOG_INF("Testing LED %d - %s", led, led_label[led] ? : "no label");

        /* Turn LED on. */
        err = led_on(led_pwm, led);
        if (err < 0) {
                LOG_ERR("err=%d", err);
                return;
        }
        LOG_INF("  Turned on");
        k_sleep(K_MSEC(1000));

        /* Turn LED off. */
        err = led_off(led_pwm, led);
        if (err < 0) {
                LOG_ERR("err=%d", err);
                return;
        }
        LOG_INF("  Turned off");
        k_sleep(K_MSEC(1000));

        /* Increase LED brightness gradually up to the maximum level. */
        LOG_INF("  Increasing brightness gradually");
        for (level = 0; level <= MAX_BRIGHTNESS; level++) {
                err = led_set_brightness(led_pwm, led, level);
                if (err < 0) {
                        LOG_ERR("err=%d brightness=%d\n", err, level);
                        return;
                }
                k_sleep(FADE_DELAY);
        }
        k_sleep(K_MSEC(1000));

        /* Set LED blinking (on: 0.1 sec, off: 0.1 sec) */
        err = led_blink(led_pwm, led, 100, 100);
        if (err < 0) {
                LOG_ERR("err=%d", err);
                return;
        }
        LOG_INF("  Blinking on: 0.1 sec, off: 0.1 sec");
        k_sleep(K_MSEC(5000));

        /* Enable LED blinking (on: 1 sec, off: 1 sec) */
        err = led_blink(led_pwm, led, 1000, 1000);
        if (err < 0) {
                LOG_ERR("err=%d", err);
                LOG_INF("  Cycle period not supported - on: 1 sec, off: 1 sec");
        } else {
                LOG_INF("  Blinking on: 1 sec, off: 1 sec");
        }
        k_sleep(K_MSEC(5000));

        /* Turn LED off. */
        err = led_off(led_pwm, led);
        if (err < 0) {
                LOG_ERR("err=%d", err);
                return;
        }
        LOG_INF("  Turned off, loop end");
}

int main(void)
{
        const struct device *led_pwm;
        uint8_t led;

        led_pwm = DEVICE_DT_GET(LED_PWM_NODE_ID);
        if (!device_is_ready(led_pwm)) {
                LOG_ERR("Device %s is not ready", led_pwm->name);
                return 0;
        }

        if (!num_leds) {
                LOG_ERR("No LEDs found for %s", led_pwm->name);
                return 0;
        }

        do {
                for (led = 0; led < num_leds; led++) {
                        run_led_test(led_pwm, led);
                }
        } while (true);
        return 0;
}


在上述代码中,首先通过宏定义,找到设备树中pwm_leds对应的节点:
#define LED_PWM_NODE_ID         DT_COMPAT_GET_ANY_STATUS_OKAY(pwm_leds)


然后,通过宏定义,获取pwm_leds中,各个led设备的名称(label):
const char *led_label[] = {
        DT_FOREACH_CHILD_SEP_VARGS(LED_PWM_NODE_ID, DT_PROP_OR, (,), label, NULL)
};


再根据获得的名称数量,得到有多少个可被PWM控制的LED:
const int num_leds = ARRAY_SIZE(led_label);


另外,代码中还定义了 PWM控制LED的亮度,以及设置每个亮度后的延时时间:
#define MAX_BRIGHTNESS        100

#define FADE_DELAY_MS        10
#define FADE_DELAY        K_MSEC(FADE_DELAY_MS)
在Zephyr中,用 K_MSEC 宏定义来获取具体ms对应的值,然后使用k_sleep()来进行延时。

在main()主逻辑中,先检查设备是否初始化完成:
 led_pwm = DEVICE_DT_GET(LED_PWM_NODE_ID);
        if (!device_is_ready(led_pwm)) {
                LOG_ERR("Device %s is not ready", led_pwm->name);
                return 0;
        }


再循环对设置的LED进行测试:
 do {
                for (led = 0; led < num_leds; led++) {
                        run_led_test(led_pwm, led);
                }
        } while (true);


run_led_test()调用中,几个关键的led控制调用如下:
  • led_on(led_pwm, led); 点亮对应的led
  • led_off(led_pwm, led); 熄灭对应的led
  • led_blink(led_pwm, led, 100, 100); 交替点亮100ms,熄灭100ms
  • led_set_brightness(led_pwm, led, level); 设置对应led的亮度(0-100)


PWM控制LED亮度部分代码:
 for (level = 0; level <= MAX_BRIGHTNESS; level++) {
                err = led_set_brightness(led_pwm, led, level);
                if (err < 0) {
                        LOG_ERR("err=%d brightness=%d\n", err, level);
                        return;
                }
                k_sleep(FADE_DELAY);
        }


明白了以上代码,就能开始编译源码进行测试了。

不过,在Zephyr中,NUCLEO-U5A5ZJ-Q对应的LED默认作为普通GPIO LED控制,所以使用下面的命令编译,会出错:
west build -b nucleo_u5a5zj_q samples/drivers/led_pwm


在编译之前,需要先处理设备树定义。
对应的设备树定义文件为:
  • ./boards/arm/nucleo_u5a5zj_q/nucleo_u5a5zj_q.dts
  • ./boards/arm/nucleo_u5a5zj_q/nucleo_u5a5zj_q-common.dtsi


在nucleo_u5a5zj_q-common.dtsi中, 首先禁用leds设置:
 leds: leds {
                compatible = "gpio-leds";
                status = "disabled";
                green_led_1: led_1 {
                        gpios = <&gpioc 7 GPIO_ACTIVE_HIGH>;
                        label = "User LD1";
                };
                blue_led_1: led_2 {
                        gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>;
                        label = "User LD2";
                };
                red_led_1: led_3 {
                        gpios = <&gpiog 2 GPIO_ACTIVE_HIGH>;
                        label = "User LD3";
                };
        };
设置 status = "disabled"; 即可禁用。

然后,启用pwm_leds的设置:
 pwmleds: pwmleds {
                compatible = "pwm-leds";
                status = "okay";

                pwm_led_1: green_led_1 {
                        pwms = <&pwm3 2 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
                        label = "green led";
                };

                pwm_led_2: blue_led_1 {
                        pwms = <&pwm4 2 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
                        label = "blue led";
                };
        };


设置 status = "okey"; 即可启用。

从上述的配置中,可以看到,只有绿灯和蓝灯,设置了PWM驱动。
红灯对应的是PG2,没有看到对应的PWM调用。

要确定哪些引脚可被PWM控制,可以查看 ./modules/hal/stm32/dts/st/u5/stm32u5a5zjtxq-pinctrl.dtsi:

上面的绿灯和蓝灯的pwm配置:
&timers3 {
        st,prescaler = <10000>;
        status = "okay";

        pwm3: pwm {
        pinctrl-0 = <&tim3_ch2_pc7>;
                pinctrl-names = "default";
                status = "okay";
        };
};

&timers4 {
        st,prescaler = <10000>;
        status = "okay";

        pwm4: pwm {
                pinctrl-0 = <&tim4_ch2_pb7>;
                pinctrl-names = "default";
                status = "okay";
        };
};


分别对应tim3_ch2_pc7、tim4_ch2_pb7,pinctrl定义中,具体如下:
   /omit-if-no-ref/ tim3_ch2_pc7: tim3_ch2_pc7 {
                                pinmux = <STM32_PINMUX('C', 7, AF2)>;
                        };



                        /omit-if-no-ref/ tim4_ch2_pb7: tim4_ch2_pb7 {
                                pinmux = <STM32_PINMUX('B', 7, AF2)>;
                        };


做好上述对应的修改后,再次编译源码,成功通过:
west build -b nucleo_u5a5zj_q samples/drivers/led_pwm


再将生成的固件烧录到开发板:
pyocd flash --erase chip --target stm32u5a5zjtxq ./build/zephyr/zephyr.hex


使用串口监听工具进行监听,就可以查看到实际的运行输出结果:



开发板上的绿色和蓝色LED,也会依次进行:点亮、熄灭、渐亮、0.1秒交替闪烁、1秒交替闪烁


三、添加自定义引脚使用PWM控制输出
在上面的演示代码中,使用PWM驱动了绿色和蓝色LED。
红色LED连接到了PG2,在 pinctrl 的定义中,没有定时器关联到PG2的设置。

在开发板上,有Arduino兼容接口:


在CN10上,提供了专门的定时器控制的引脚:



在pinctrl中,有PA0对应TIM2_CH1的配置:


在 nucleo_u5a5zj_q-common.dtsi 添加对应的配置:
&timers2 {
        st,prescaler = <10000>;
        status = "okay";

        pwm2: pwm {
                pinctrl-0 = <&tim2_ch1_pa0>;
                pinctrl-names = "default";
                status = "okay";
        };
};


然后添加对应的pwd_leds设置:
 pwmleds: pwmleds {
                compatible = "pwm-leds";
                status = "okay";

                pwm_led_1: green_led_1 {
                        pwms = <&pwm3 2 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
                        label = "green led";
                };

                pwm_led_2: blue_led_1 {
                        pwms = <&pwm4 2 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
                        label = "blue led";
                };

                pwm_led_3: extra_led_1 {
                        pwms = <&pwm2 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
                        label = "extra led";
                };
        };


然后重新编译源码并烧录固件,开发板运行后,串口输出如下:


从上述输出可以看到,已经识别到了三个PWM LED,并进行了各项控制操作。

要确定我们自己添加的LED是否有效被PWM控制,可以自己在PA0上接一个LED,也可以用逻辑分析仪进行测试。

把逻辑分析仪连接上PA0和GND:


在 Testing LED 2 - extra led 的时候采集数据:


采样到的数据,具体部分如下:


  • 对应点亮
  • 对应熄灭
  • 对应亮度控制
  • 对应0.1秒闪烁
  • 对应1.0秒闪烁


放大 亮度控制 部分的数据:


可以看到,随着设置的亮度变化,PWM占空比也在对应变化:


四、总结
通过上面的演示,了解了PWM控制LED,如果感兴趣,还可以查看./samples/basic/blinky_pwm 、./samples/basic/servo_motor,进一步了解PWM的控制,实现更多的功能。

使用特权

评论回复
沙发
Tristan_C| | 2023-12-4 22:48 | 只看该作者
高级啊

使用特权

评论回复
板凳
xusiwei1236| | 2023-12-11 10:19 | 只看该作者
Zephyr dts配起来之后,代码就是很优雅

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

37

主题

90

帖子

2

粉丝