- /*
- * 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的控制,实现更多的功能。