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;
}