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

[复制链接]
1707|2
 楼主| HonestQiao 发表于 2023-12-3 13:49 | 显示全部楼层 |阅读模式

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

默认的引脚定义如下:
55667656c06008d825.png
85691656c06167eb1f.png

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

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

  6. #include <zephyr/device.h>
  7. #include <zephyr/devicetree.h>
  8. #include <errno.h>
  9. #include <zephyr/drivers/led.h>
  10. #include <zephyr/sys/util.h>
  11. #include <zephyr/kernel.h>

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

  14. #define LED_PWM_NODE_ID         DT_COMPAT_GET_ANY_STATUS_OKAY(pwm_leds)

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

  18. const int num_leds = ARRAY_SIZE(led_label);

  19. #define MAX_BRIGHTNESS        100

  20. #define FADE_DELAY_MS        10
  21. #define FADE_DELAY        K_MSEC(FADE_DELAY_MS)

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

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

  33.         /* Turn LED on. */
  34.         err = led_on(led_pwm, led);
  35.         if (err < 0) {
  36.                 LOG_ERR("err=%d", err);
  37.                 return;
  38.         }
  39.         LOG_INF("  Turned on");
  40.         k_sleep(K_MSEC(1000));

  41.         /* Turn LED off. */
  42.         err = led_off(led_pwm, led);
  43.         if (err < 0) {
  44.                 LOG_ERR("err=%d", err);
  45.                 return;
  46.         }
  47.         LOG_INF("  Turned off");
  48.         k_sleep(K_MSEC(1000));

  49.         /* Increase LED brightness gradually up to the maximum level. */
  50.         LOG_INF("  Increasing brightness gradually");
  51.         for (level = 0; level <= MAX_BRIGHTNESS; level++) {
  52.                 err = led_set_brightness(led_pwm, led, level);
  53.                 if (err < 0) {
  54.                         LOG_ERR("err=%d brightness=%d\n", err, level);
  55.                         return;
  56.                 }
  57.                 k_sleep(FADE_DELAY);
  58.         }
  59.         k_sleep(K_MSEC(1000));

  60.         /* Set LED blinking (on: 0.1 sec, off: 0.1 sec) */
  61.         err = led_blink(led_pwm, led, 100, 100);
  62.         if (err < 0) {
  63.                 LOG_ERR("err=%d", err);
  64.                 return;
  65.         }
  66.         LOG_INF("  Blinking on: 0.1 sec, off: 0.1 sec");
  67.         k_sleep(K_MSEC(5000));

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

  77.         /* Turn LED off. */
  78.         err = led_off(led_pwm, led);
  79.         if (err < 0) {
  80.                 LOG_ERR("err=%d", err);
  81.                 return;
  82.         }
  83.         LOG_INF("  Turned off, loop end");
  84. }

  85. int main(void)
  86. {
  87.         const struct device *led_pwm;
  88.         uint8_t led;

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

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

  98.         do {
  99.                 for (led = 0; led < num_leds; led++) {
  100.                         run_led_test(led_pwm, led);
  101.                 }
  102.         } while (true);
  103.         return 0;
  104. }


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


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


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


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

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

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


再循环对设置的LED进行测试:
  1. do {
  2.                 for (led = 0; led < num_leds; led++) {
  3.                         run_led_test(led_pwm, led);
  4.                 }
  5.         } 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亮度部分代码:
  1. for (level = 0; level <= MAX_BRIGHTNESS; level++) {
  2.                 err = led_set_brightness(led_pwm, led, level);
  3.                 if (err < 0) {
  4.                         LOG_ERR("err=%d brightness=%d\n", err, level);
  5.                         return;
  6.                 }
  7.                 k_sleep(FADE_DELAY);
  8.         }


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

不过,在Zephyr中,NUCLEO-U5A5ZJ-Q对应的LED默认作为普通GPIO LED控制,所以使用下面的命令编译,会出错:
  1. 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设置:
  1. leds: leds {
  2.                 compatible = "gpio-leds";
  3.                 status = "disabled";
  4.                 green_led_1: led_1 {
  5.                         gpios = <&gpioc 7 GPIO_ACTIVE_HIGH>;
  6.                         label = "User LD1";
  7.                 };
  8.                 blue_led_1: led_2 {
  9.                         gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>;
  10.                         label = "User LD2";
  11.                 };
  12.                 red_led_1: led_3 {
  13.                         gpios = <&gpiog 2 GPIO_ACTIVE_HIGH>;
  14.                         label = "User LD3";
  15.                 };
  16.         };
设置 status = "disabled"; 即可禁用。

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

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

  8.                 pwm_led_2: blue_led_1 {
  9.                         pwms = <&pwm4 2 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
  10.                         label = "blue led";
  11.                 };
  12.         };


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

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

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

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

  4.         pwm3: pwm {
  5.         pinctrl-0 = <&tim3_ch2_pc7>;
  6.                 pinctrl-names = "default";
  7.                 status = "okay";
  8.         };
  9. };

  10. &timers4 {
  11.         st,prescaler = <10000>;
  12.         status = "okay";

  13.         pwm4: pwm {
  14.                 pinctrl-0 = <&tim4_ch2_pb7>;
  15.                 pinctrl-names = "default";
  16.                 status = "okay";
  17.         };
  18. };


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



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


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

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


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

13140656c10e818335.png

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


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

在开发板上,有Arduino兼容接口:
30016656c1215de6bc.png

在CN10上,提供了专门的定时器控制的引脚:
80142656c126752beb.png
91087656c12986bffa.png

在pinctrl中,有PA0对应TIM2_CH1的配置:
89964656c12ccc62b0.png

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

  4.         pwm2: pwm {
  5.                 pinctrl-0 = <&tim2_ch1_pa0>;
  6.                 pinctrl-names = "default";
  7.                 status = "okay";
  8.         };
  9. };


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

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

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

  12.                 pwm_led_3: extra_led_1 {
  13.                         pwms = <&pwm2 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
  14.                         label = "extra led";
  15.                 };
  16.         };


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

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

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

把逻辑分析仪连接上PA0和GND:
84647656c15b840fef.png

在 Testing LED 2 - extra led 的时候采集数据:
6821656c144c7a008.png

采样到的数据,具体部分如下:
58083656c14a194fb3.png

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


放大 亮度控制 部分的数据:
90271656c14f00f388.png

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

四、总结
通过上面的演示,了解了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配起来之后,代码就是很优雅
您需要登录后才可以回帖 登录 | 注册

本版积分规则

42

主题

115

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部