PWM(脉冲宽度调制)是控制电机、LED等设备的常用技术。本文将详细介绍如何在树莓派 Pico 上使用 C/C++ SDK 实现 PWM 控制。

前言

PWM 基础知识

PWM 通过调节信号的占空比(Duty Cycle)来控制输出功率。占空比是指信号在一个周期内为高电平的时间比例。通过改变占空比,可以控制负载的功率。

使用 PWM 实现 LED 呼吸灯 - 图1

PWM 功能函数

树莓派 Pico 的 C/C++ SDK 提供了丰富的 PWM 函数:

  • pwm_set_gpio_level(gpio, level): 设置指定 GPIO 引脚的 PWM 输出电平。
  • pwm_set_wrap(slice_num, wrap): 设置 PWM 切片的最大计数值。
  • pwm_set_clkdiv(slice_num, div): 设置 PWM 时钟分频值。

示例

基础功能使用

一般使用 PWM 的基本流程如下:

(1) 将 GPIO 引脚设置为 PWM 功能
(2) 编辑 PWM 配置
(3) 初始化 PWM 配置
(4) 设置电平值(占空比值)
  1. #include "pico/stdlib.h"
  2. #include "hardware/pwm.h"
  3. #define PIN_PWM0 ( 12 )
  4. int main() {
  5. static pwm_config pwm0_slice_config;
  6. uint pwm0_slice_num;
  7. { /* 将指定的 GPIO 引脚设置为 PWM 功能 */
  8. gpio_set_function( PIN_PWM0, GPIO_FUNC_PWM );
  9. pwm0_slice_num = pwm_gpio_to_slice_num( PIN_PWM0 );
  10. }
  11. {
  12. /* 获取 PWM 默认配置 */
  13. pwm0_slice_config = pwm_get_default_config();
  14. }
  15. {
  16. pwm_init( pwm0_slice_num, &pwm0_slice_config, true );
  17. /* PWM计时器、计数器、电平值(占空比计数值)被清除0 */
  18. }
  19. { /* 通过 pwm_set_gpio_level 函数实时调整 PWM 输出的占空比 */
  20. pwm_set_gpio_level( PIN_PWM0, ( pwm0_slice_config.top * 0.20 ) );
  21. /* 占空比20% */
  22. }
  23. while ( true )
  24. {
  25. }
  26. }

以上代码将在 GPIO 为 12 的引脚输出占空比为 20% 的 PWM。从下图的方波图我们可以看到,在一个周期内高电平占了1/5 。

使用 PWM 实现 LED 呼吸灯 - 图2

设置周期

周期可以通过分频比和最大计数值进行调整。
时钟频率越高,精度越好,因此请调整最大计数值,使其尽可能大。
以下代码将其调整为 1ms 和 2ms。
  1. #include "pico/stdlib.h"
  2. #include "hardware/pwm.h"
  3. #define PIN_PWM0 ( 0 )
  4. #define PIN_PWM1 ( 2 )
  5. void main( void )
  6. {
  7. static pwm_config pwm0_slice_config;
  8. uint pwm0_slice_num;
  9. static pwm_config pwm1_slice_config;
  10. uint pwm1_slice_num;
  11. {
  12. gpio_set_function( PIN_PWM0, GPIO_FUNC_PWM );
  13. pwm0_slice_num = pwm_gpio_to_slice_num( PIN_PWM0 );
  14. gpio_set_function( PIN_PWM1, GPIO_FUNC_PWM );
  15. pwm1_slice_num = pwm_gpio_to_slice_num( PIN_PWM1 );
  16. }
  17. {
  18. pwm0_slice_config = pwm_get_default_config();
  19. // 设置最大计数值
  20. pwm_config_set_wrap( &pwm0_slice_config, 62500 );
  21. // 设置时钟分频值
  22. pwm_config_set_clkdiv( &pwm0_slice_config, 2 );
  23. pwm1_slice_config = pwm_get_default_config();
  24. // 设置最大计数值
  25. pwm_config_set_wrap( &pwm1_slice_config, 62500 );
  26. // 设置时钟分频值
  27. pwm_config_set_clkdiv( &pwm1_slice_config, 4 );
  28. }
  29. {
  30. pwm_init( pwm0_slice_num, &pwm0_slice_config, true );
  31. pwm_init( pwm1_slice_num, &pwm1_slice_config, true );
  32. }
  33. { // 设置初始占空比为 20%
  34. pwm_set_gpio_level( PIN_PWM0, ( pwm0_slice_config.top * 0.20 ) );
  35. pwm_set_gpio_level( PIN_PWM1, ( pwm1_slice_config.top * 0.20 ) );
  36. }
  37. while ( true )
  38. {
  39. }
  40. }

使用 PWM 实现 LED 呼吸灯 - 图3

除了基本的 PWM 设置,还可以使用以下功能:

  • 相位校正:通过 pwm_set_phase_correct 函数设置相位校正模式。
  • 输出极性:通过 pwm_set_output_polarity 函数设置输出极性。
  • 计数器:通过 pwm_set_counter 函数设置计数器的初始值。

实现 LED 呼吸灯

  1. #include "pico/stdlib.h"
  2. #include <stdio.h>
  3. #include "pico/time.h"
  4. #include "hardware/irq.h"
  5. #include "hardware/pwm.h"
  6. #define PICO_DEFAULT_LED_PIN 4
  7. #ifdef PICO_DEFAULT_LED_PIN
  8. void on_pwm_wrap() {
  9. static int fade = 0;
  10. static bool going_up = true;
  11. // Clear the interrupt flag that brought us here
  12. pwm_clear_irq(pwm_gpio_to_slice_num(PICO_DEFAULT_LED_PIN));
  13. if (going_up) {
  14. ++fade;
  15. printf("%d\n", fade);
  16. if (fade > 255) {
  17. fade = 255;
  18. going_up = false;
  19. }
  20. } else {
  21. --fade;
  22. printf("%d\n", fade);
  23. if (fade < 0) {
  24. fade = 0;
  25. going_up = true;
  26. }
  27. }
  28. // Square the fade value to make the LED's brightness appear more linear
  29. // Note this range matches with the wrap value
  30. pwm_set_gpio_level(PICO_DEFAULT_LED_PIN, fade * fade);
  31. }
  32. #endif
  33. int main() {
  34. #ifndef PICO_DEFAULT_LED_PIN
  35. #warning pwm/led_fade example requires a board with a regular LED
  36. #else
  37. // Tell the LED pin that the PWM is in charge of its value.
  38. gpio_set_function(PICO_DEFAULT_LED_PIN, GPIO_FUNC_PWM);
  39. // Figure out which slice we just connected to the LED pin
  40. uint slice_num = pwm_gpio_to_slice_num(PICO_DEFAULT_LED_PIN);
  41. // Mask our slice's IRQ output into the PWM block's single interrupt line,
  42. // and register our interrupt handler
  43. pwm_clear_irq(slice_num);
  44. pwm_set_irq_enabled(slice_num, true);
  45. irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
  46. irq_set_enabled(PWM_IRQ_WRAP, true);
  47. // Get some sensible defaults for the slice configuration. By default, the
  48. // counter is allowed to wrap over its maximum range (0 to 2**16-1)
  49. pwm_config config = pwm_get_default_config();
  50. // Set divider, reduces counter clock to sysclock/this value
  51. pwm_config_set_clkdiv(&config, 4.f);
  52. // Load the configuration into our PWM slice, and set it running.
  53. pwm_init(slice_num, &config, true);
  54. // Everything after this point happens in the PWM interrupt handler, so we
  55. // can twiddle our thumbs
  56. while (1)
  57. tight_loop_contents();
  58. #endif
  59. }

总结

通过本文,您应该能够在树莓派 Pico 上使用 C/C++ SDK 实现基本的 PWM 控制。PWM 是一种强大的控制技术,广泛应用于电机控制、LED 调光等领域。希望本文能帮助您更好地理解和使用树莓派 Pico 的 PWM 功能。

参考资料

通过这些参考资料,您可以进一步深入了解树莓派 Pico 和 C/C++ SDK 的更多高级功能。