RP2040 C SDK PWM功能使用

RP2040 C SDK PWM功能使用


  • 📍参考文档:https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_hardware_pwm

📑RP2040 PWM概述

脉宽调制(PWM)是一种由数字信号提供一个平滑变化的平均电压的方案。这是通过一定的控制宽度的正脉冲实现的。花费在高的时间的比例被称为占空比。这可用于近似模拟输出,或控制开关模式电源电子设备。

RP2040 PWM块有8个相同的切片。每个切片可以驱动两个PWM输出信号,或测量一个输入信号的频率或占空比。这就总共提供了多达16个可控的PWM输出。所有30个GPIO大头针都可以由PWM块驱动。

  • RP2040的PWM只有向上计数法和中心对齐,
  • 图:单个PWM计数。一个16位的计数器从0计数到编程值,然后装载值到零,或再次计数,中心对齐模式。A和B根据当前的计数值和预先编程的A和B的阈值输出高低转换。计数器基于许多事件前进:它可能是自由移动的,或者通过B引脚上的输入信号的电平或边缘进行门控。分数分法器减慢总计数率,以更精细地控制输出频率。
  • 每个PWM通道都配有以下设备:
  • 16位计数器
  • 8.4分数时钟分频器两个独立的输出通道,占空比从0%到100%包括.
  • Dual slope and trailing edge modulation双斜率和后边沿调制。
  • Edge-sensitive input mode for frequency measurement.频率测量
  • Level-sensitive input mode for duty cycle measurement。占空比测量
  • Configurable counter wrap value。可配置计数器的装载值
    • Wrap and level registers are double buffered and can be changed race-free while PWM is running.带双缓冲的,可以在PWM运行时改变更改。
  • Interrupt request and DMA request on counter wrap.计数器带中断请求和DMA请求功能
  • Phase can be precisely advanced or retarded while running (increments of one count)。

Slices can be enabled or disabled simultaneously via a single, global control register. The slices then run in perfect lockstep, so that more complex power circuitry can be switched by the outputs of multiple slices.可以通过单个全局控制寄存器同时启用或禁用通道。然后,这些切片以完美的锁步方式运行,以便通过多个切片的输出,可以对更复杂的电源电路进行切换。

📘PWM编程模式

  • RP2040上的所有30个GPIO引脚都可用于PWM:
  • 将PWM通道映射到RP2040上的GPIO引脚。这也显示在主GPIO函数表中,
  • The 16 PWM channels (8 2-channel slices) appear on GPIO0 to GPIO15, in the order PWM0 A, PWM0 B, PWM1 A...
  • This repeats for GPIO16 to GPIO29. GPIO16 is PWM0 A, GPIO17 is PWM0 B, so on up to PWM6 B on GPIO29
  • The same PWM output can be selected on two GPIO pins; the same signal will appear on each GPIO.
  • If a PWM B pin is used as an input, and is selected on multiple GPIO pins, then the PWM slice will see the logical
    OR of those two GPIO inputs
通道和API函数
  • 🌿uint pwm_gpio_to_slice_num(uint gpio):获取PWM输出引脚对应的通道号

📐PWM频率计算和配置方法

  • RP2040默认时钟频率为125MHz.当然你可以使用set_sys_clock_khz(133000, true); 配置到133MHz.
  • 🌿PWM频率 = 系统时钟频率/分频系数整数倍/计数值.
c 复制代码
pwm_set_clkdiv(slice_num,125);//设置125分频,也就是1MHz(默认系统时钟125MHz)
  • ✨注意:每个通道的整数位分频器为8位,最大值:255,分数位分频器为4位。频率可设置范围: 7.5Hz -125MHz system clock.
  • 📏分频系数带分数分频系数时
  • 🌿PWM占空比
c 复制代码
 pwm_set_clkdiv	(slice_num,125); // Set the clock divider to 125 (125MHz / 125 = 1MHz)
    // Set period of 1000 cycles (0 to 999 inclusive)
    pwm_set_wrap(slice_num, 999);//1000,000/1000=1KHz
    // Set channel A output high for one cycle before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_A, 250);//25% duty cycle
    // Set initial B output high for three cycles before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_B, 750);//75% duty cycle


📗手册上对PWM例程介绍

  • 📍Pico Examples: https://github.com/raspberrypi/pico-examples/blob/master/pwm/hello_pwm/hello_pwm.c Lines 15 - 29
c 复制代码
// Output PWM signals on pins 0 and 1

#include "pico/stdlib.h"
#include "hardware/pwm.h"

int main() {
    /// \tag::setup_pwm[]

    // Tell GPIO 0 and 1 they are allocated to the PWM
    gpio_set_function(0, GPIO_FUNC_PWM);
    gpio_set_function(1, GPIO_FUNC_PWM);

    // Find out which PWM slice is connected to GPIO 0 (it's slice 0)
    uint slice_num = pwm_gpio_to_slice_num(0);

    // Set period of 4 cycles (0 to 3 inclusive)
    pwm_set_wrap(slice_num, 3);
    // Set channel A output high for one cycle before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_A, 1);
    // Set initial B output high for three cycles before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_B, 3);
    // Set the PWM running
    pwm_set_enabled(slice_num, true);
    /// \end::setup_pwm[]

    // Note we could also use pwm_set_gpio_level(gpio, x) which looks up the
    // correct slice and channel for a given GPIO.
}
  • 🧬波形图

计数器从0到3重复计数,这被配置为TOP值。因此,输出波的周期为4。输出A在4中1周期高,所以平均输出电压为IO电源电压的1/4。输出B高,每4个有3个周期。注意,A和B的上升边总是对齐的。

  • 🌟 频率计算:125MHz/4= 31.25MHz,频率较高时,实际输出的占空比误差较大。

📝250KHz频率PWM输出
c 复制代码
/*
 CMSIS-DAP烧录命令:openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c  "adapter speed 5000"-c "program RP2040_PWM.elf verify reset exit"

 jlink命令: openocd -f interface/jlink.cfg -f target/rp2040.cfg  -c  "adapter speed 2000" -c  "program RP2040_PWM.elf verify reset exit"

// Output PWM signals on pins 0 and 1
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/gpio.h"
#include "hardware/divider.h"
#include "hardware/clocks.h"
#include "hardware/pwm.h"

#define BUILTIN_LED PICO_DEFAULT_LED_PIN // LED is on the same pin as the default LED 25



int main()
{
    //set_sys_clock_khz(133000, true); // 325us
    stdio_init_all();
    sleep_ms(2500);
    printf("PWM test!\n");

    // uart_init(UART_ID, BAUD_RATE);

    // GPIO initialisation.
    gpio_init(BUILTIN_LED);
    gpio_set_dir(BUILTIN_LED, 1);
    gpio_pull_up(BUILTIN_LED);

    // Tell GPIO 6 and 7 they are allocated to the PWM
    gpio_set_function(6, GPIO_FUNC_PWM);
    gpio_set_function(7, GPIO_FUNC_PWM);
    // Find out which PWM slice is connected to GPIO 0 (it's slice 0)
    uint slice_num = pwm_gpio_to_slice_num(6);
  pwm_set_clkdiv(slice_num,125); // Set the clock divider to 125 (125MHz / 125 = 1MHz)
    // Set period of 4 cycles (0 to 3 inclusive)
    pwm_set_wrap(slice_num, 3);//250,000
    // Set channel A output high for one cycle before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_A, 1);//25% duty cycle
    // Set initial B output high for three cycles before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_B, 3);//75% duty cycle
    // Set the PWM running
    pwm_set_enabled(slice_num, true);

    // Note we could also use pwm_set_gpio_level(gpio, x) which looks up the
    // correct slice and channel for a given GPIO.

    while (true)
    {
        sleep_ms(1000);
        gpio_xor_mask(1ul << BUILTIN_LED); // Toggle the LED
        // tight_loop_contents();
    //    measure_freqs();
   }


}

static void measure_freqs(void)
{
    uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);
    uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY);
    uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC);
    uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS);
    uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI);
    uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB);
    uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC);
    uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC);

    printf("pll_sys  = %dkHz\n", f_pll_sys);
    printf("pll_usb  = %dkHz\n", f_pll_usb);
    printf("rosc     = %dkHz\n", f_rosc);
    printf("clk_sys  = %dkHz\n", f_clk_sys);
    printf("clk_peri = %dkHz\n", f_clk_peri);
    printf("clk_usb  = %dkHz\n", f_clk_usb);
    printf("clk_adc  = %dkHz\n", f_clk_adc);
    printf("clk_rtc  = %dkHz\n", f_clk_rtc);

    // Can't measure clk_ref / xosc as it is the ref
}
  • 输出的PWM占空比相当准确。

📙电平检测或边沿信号检测

  • PWM触发事件选择。当计数器的启用输入高时,计数器前进,该启用分两个顺序阶段生成。首先,四种事件类型中的任何一种(总是打开,引脚B高,引脚B上升,引脚B下降)都可以为分数时钟分频器的启用脉冲。分配器可以在将启用脉冲传递到计数器之前降低启用脉冲的速率。
  • 每当启用触发时都将连续计数。还有其他三种选择:
  • 当在B针脚计数上检测到一个高水平时,连续计数一次,
  • 在B针脚上检测到每个上升边计数一次,
  • 在B针脚上检测到每一个下降边一次
  • ✨检测引脚只能安排在B通道引脚上:
📑脉宽占空比测量例程
c 复制代码
/*
 CMSIS-DAP烧录命令:openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c  "adapter speed 5000"-c "program RP2040_PWM.elf verify reset exit"

 jlink命令: openocd -f interface/jlink.cfg -f target/rp2040.cfg  -c  "adapter speed 2000" -c  "program RP2040_PWM.elf verify reset exit"

// Output PWM signals on pins 6 and 7
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/gpio.h"
#include "hardware/divider.h"
#include "hardware/clocks.h"
#include "hardware/pwm.h"

#define UART_ID uart0
#define BAUD_RATE 9600
#define BUILTIN_LED PICO_DEFAULT_LED_PIN // LED is on the same pin as the default LED 25

#define freq_CPU 125000000 // 125MHz


const uint OUTPUT_PIN = 7;
const uint MEASURE_PIN = 3;

float measure_duty_cycle(uint gpio); // Measure duty cycle of PWM signal on specified GPIO
static void measure_freqs(void);


int main()
{
    //set_sys_clock_khz(133000, true); // 325us
    stdio_init_all();
    sleep_ms(2500);
    printf("PWM test!\n");

    // uart_init(UART_ID, BAUD_RATE);

    // GPIO initialisation.
    gpio_init(BUILTIN_LED);
    gpio_set_dir(BUILTIN_LED, 1);
    gpio_pull_up(BUILTIN_LED);

    // Tell GPIO 6 and 7 they are allocated to the PWM
    gpio_set_function(6, GPIO_FUNC_PWM);
    gpio_set_function(7, GPIO_FUNC_PWM);
  
    // Find out which PWM slice is connected to GPIO 6 (it's slice 6)
    uint slice_num = pwm_gpio_to_slice_num(6);

  pwm_set_clkdiv	(	slice_num,125); // Set the clock divider to 125 (125MHz / 125 = 1MHz)
    // Set period of 4 cycles (0 to 3 inclusive)
    pwm_set_wrap(slice_num, 3);//250,000
    // Set channel A output high for one cycle before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_A, 1);//25% duty cycle
    // Set initial B output high for three cycles before dropping
    pwm_set_chan_level(slice_num, PWM_CHAN_B, 3);//75% duty cycle
    // Set the PWM running
    pwm_set_enabled(slice_num, true);

    float output_duty_cycle = (float)3.0f/4; // 75% duty cycle


    while (true)
    {
        sleep_ms(1000);
        gpio_xor_mask(1ul << BUILTIN_LED); // Toggle the LED
         float measured_duty_cycle = measure_duty_cycle(MEASURE_PIN);
        printf("Output duty cycle = %.1f%%, measured input duty cycle = %.1f%%\n",
               output_duty_cycle * 100.f, measured_duty_cycle * 100.f);
        // tight_loop_contents();
    //    measure_freqs();
   }


}

float measure_duty_cycle(uint gpio) {
    // Only the PWM B pins can be used as inputs.
    assert(pwm_gpio_to_channel(gpio) == PWM_CHAN_B);
    uint slice_num = pwm_gpio_to_slice_num(gpio);

    // Count once for every 100 cycles the PWM B input is high
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_HIGH);
    pwm_config_set_clkdiv(&cfg, 100);
    pwm_init(slice_num, &cfg, false);
    gpio_set_function(gpio, GPIO_FUNC_PWM);

    pwm_set_enabled(slice_num, true);
    sleep_ms(10);
    pwm_set_enabled(slice_num, false);
    float counting_rate = clock_get_hz(clk_sys) / 100;
    float max_possible_count = counting_rate * 0.01;
    return pwm_get_counter(slice_num) / max_possible_count;
}

static void measure_freqs(void)
{
    uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);
    uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY);
    uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC);
    uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS);
    uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI);
    uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB);
    uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC);
    uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC);

    printf("pll_sys  = %dkHz\n", f_pll_sys);
    printf("pll_usb  = %dkHz\n", f_pll_usb);
    printf("rosc     = %dkHz\n", f_rosc);
    printf("clk_sys  = %dkHz\n", f_clk_sys);
    printf("clk_peri = %dkHz\n", f_clk_peri);
    printf("clk_usb  = %dkHz\n", f_clk_usb);
    printf("clk_adc  = %dkHz\n", f_clk_adc);
    printf("clk_rtc  = %dkHz\n", f_clk_rtc);

    // Can't measure clk_ref / xosc as it is the ref
}

🎉利用RP2040 PWM Arduino库实现进行移植使用

  • 🥕RP2040 C SDK开发本身是支持C/C++开发方式的,移植对应的Arduino库代码使用修改的地方就不多。
  • 📍RP2040 PWM库:https://github.com/khoih-prog/RP2040_PWM

将相关打印的函数注释掉,包含头文件进来即可使用,使用方法就按照Arduino中PWM例程将相关代码拷贝过来即可。 在 CMakeLists.txt中添加set(PICO_CXX_ENABLE_EXCEPTIONS 1)

  • 🍁工程文件:
c 复制代码
/*
 CMSIS-DAP烧录命令:openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c  "adapter speed 5000"-c "program RP2040_PWM_EXLIB.elf verify reset exit"

 jlink命令: openocd -f interface/jlink.cfg -f target/rp2040.cfg  -c  "adapter speed 2000" -c  "program RP2040_PWM_EXLIB.elf verify reset exit"


*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/gpio.h"
#include "hardware/divider.h"
#include "hardware/clocks.h"
#include "hardware/pwm.h"

#define _PWM_LOGLEVEL_ 3
#include "RP2040_PWM.h"//库头文件导入进来

#define BUILTIN_LED PICO_DEFAULT_LED_PIN // LED is on the same pin as the default LED 25
// creates pwm instance
RP2040_PWM *PWM_Instance;

float frequency = 20000;
float dutyCycle = 60;

#define pinToUse 6

int main()
{
    stdio_init_all();

    // GPIO initialisation.
    gpio_init(BUILTIN_LED);
    gpio_set_dir(BUILTIN_LED, 1);
    gpio_pull_up(BUILTIN_LED);
    // Tell GPIO 6 and 7 they are allocated to the PWM
    //  gpio_set_function(6, GPIO_FUNC_PWM);
    //   gpio_set_function(7, GPIO_FUNC_PWM);
    // assigns pin 6 , with frequency of 20 KHz and a duty cycle of 0%
    PWM_Instance = new RP2040_PWM(pinToUse, 20000, 0);

    while (true)
    {
        PWM_Instance->setPWM(pinToUse, frequency, dutyCycle);
        sleep_ms(1000);
        gpio_xor_mask(1ul << BUILTIN_LED); // Toggle the LED
                                           // sleep_ms(1000);
                                           // frequency = 20000;
                                           // dutyCycle = 90;
                                           // PWM_Instance->setPWM(pinToUse, frequency, dutyCycle);
                                           // sleep_ms(1000);
                                           // dutyCycle = 10;
                                           // PWM_Instance->setPWM(pinToUse, frequency, dutyCycle);
        tight_loop_contents();
    }

    //  return 0;
}