跳至主要内容

Detailed Explanation of PWM Usage and Examples in RP2040

在這篇文章中,我將為大家詳細講解如何在 RP2040 中使用 PWM(Pulse Width Modulation,脈衝寬度調製)功能。我們將通過三個具體的程序示例,深入了解 PWM 的工作原理,以及如何在 RP2040 中配置和使用 PWM。無論你是想要輸出 PWM 信號、控制 LED 亮度,還是測量 PWM 信號的佔空比,這篇文章都將為你提供詳盡的指導。

PWM 基礎知識​

PWM 是一種常用的數位信號處理技術,通過改變脈衝的寬度來調製信號的平均值。在 RP2040 中,PWM 功能由專門的硬體模組提供,可以生成 PWM 信號並控制其參數,如頻率、佔空比等。

PWM 信號由一系列脈衝組成,每個脈衝的寬度決定了信號的佔空比。佔空比是指脈衝的高電平時間與整個週期的比值,通常以百分比表示。通過改變 PWM 信號的佔空比,我們可以調節輸出的平均電壓,從而實現對連接設備的控制。

RP2040 的 PWM 模組提供了多個 PWM 通道,每個通道都可以獨立配置和控制。這使得我們可以同時輸出多個 PWM 信號,並對它們進行精確的控制。

好的,以下是新增的開頭段落,詳細介紹了 PWM 的各種配置模式、作用以及如何設置:

PWM 的配置模式及設置​

在 RP2040 中,PWM 模組提供了多種配置模式,可以根據不同的應用需求進行設置。每種模式都有其特定的作用和配置方法。下面我們來詳細介紹一下這些模式:

自由運行模式(Free-running mode)​

自由運行模式是 PWM 的默認模式。在這種模式下,PWM 計數器會不斷地從 0 計數到設定的最大值(通過 pwm_config_set_wrap 函數設置),然後再從 0 開始重複計數。當計數器的值與比較器的值相等時,PWM 輸出會根據設置的電平(高電平或低電平)進行切換。

适用场景:用於普通的PWM輸出,例如LED亮度調節、電機速度控制等,需要定期的周期性PWM信号。

要將 PWM 配置為自由運行模式,可以使用以下代碼:

pwm_config config = pwm_get_default_config();
pwm_config_set_wrap(&config, 65535);
pwm_init(slice_num, &config, true);

在上面的代碼中,我們首先使用 pwm_get_default_config 函數獲取 PWM 的默認配置,然後使用 pwm_config_set_wrap 函數設置 PWM 計數器的最大值為 65535。最後,使用 pwm_init 函數初始化 PWM 切片,並將配置應用到切片上。

計數模式(Counter mode)​

在計數模式下,PWM 計數器會根據外部輸入的脈衝信號進行計數。這種模式通常用於測量外部信號的頻率或脈衝寬度。 適用場景:用於測量外部脈衝信號的頻率或脈衝寬度,通常與外部輸入信號同步計數。 要將 PWM 配置為計數模式,可以使用以下代碼:

pwm_config config = pwm_get_default_config();
pwm_config_set_clkdiv_mode(&config, PWM_DIV_B_RISING);
pwm_init(slice_num, &config, false);

在上面的代碼中,我們使用 pwm_config_set_clkdiv_mode 函數將 PWM 的時鐘分頻模式設置為 PWM_DIV_B_RISING,表示在輸入信號的上升沿進行計數。然後,使用 pwm_init 函數初始化 PWM 切片,並將配置應用到切片上。

相位校正模式(Phase-correct mode)​

相位校正模式是一種特殊的 PWM 模式,它可以產生對稱的 PWM 信號,並減少輸出信號的諧波失真。在這種模式下,PWM 計數器在達到最大值後會向下計數,而不是直接重置為 0。 适用场景:產生對稱的PWM信号,減少輸出信號的諧波失真,適合對PWM信號質量要求較高的應用。 要將 PWM 配置為相位校正模式,可以使用以下代碼:

pwm_config config = pwm_get_default_config();
pwm_config_set_phase_correct(&config, true);
pwm_init(slice_num, &config, true);

在上面的代碼中,我們使用 pwm_config_set_phase_correct 函數將 PWM 配置為相位校正模式,然後使用 pwm_init 函數初始化 PWM 切片,並將配置應用到切片上。

設置 PWM 頻率和佔空比​

無論使用哪種 PWM 模式,我們都需要設置 PWM 信號的頻率和佔空比。PWM 頻率決定了 PWM 信號的週期,而佔空比決定了 PWM 信號在一個週期內高電平的時間比例。

要設置 PWM 頻率,我們需要配置 PWM 的時鐘分頻器和包裹值。時鐘分頻器決定了 PWM 計數器的計數速度,而包裹值決定了 PWM 計數器的最大值。PWM 頻率的計算公式如下:

PWM 頻率 = 系統時鐘頻率 / (時鐘分頻器 * (包裹值 + 1))

例如,如果系統時鐘頻率為 125 MHz,時鐘分頻器設置為 1.0(不分頻),包裹值設置為 1249,則 PWM 頻率為:

PWM 頻率 = 125 MHz / (1.0 * (1249 + 1)) = 100 kHz

要設置 PWM 佔空比,我們需要配置 PWM 的比較器值。比較器值決定了 PWM 信號在一個週期內高電平的時間。PWM 佔空比的計算公式如下:

PWM 佔空比 = 比較器值 / (包裹值 + 1)

例如,如果包裹值設置為 1249,比較器值設置為 624,則 PWM 佔空比為:

PWM 佔空比 = 624 / (1249 + 1) = 0.4992 ≈ 50%

綜上所述,通過合理配置 PWM 的模式、頻率和佔空比,我們可以產生各種不同特性的 PWM 信號,以滿足不同的應用需求。無論是電機控制、伺服機控制還是信號檢測,都可以通過靈活配置 PWM 來實現。

程序示例 1: 輸出 PWM 信號​

首先,讓我們來看一個簡單的程序示例,演示如何在 RP2040 上輸出 PWM 信號:

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

int main() {
// 將 GPIO 0 和 GPIO 1 分配給 PWM
gpio_set_function(0, GPIO_FUNC_PWM);
gpio_set_function(1, GPIO_FUNC_PWM);

// 找到連接到 GPIO 0 的 PWM 切片(slice)編號
uint slice_num = pwm_gpio_to_slice_num(0);

// 設置 PWM 週期為 4 個循環(0 到 3)
pwm_set_wrap(slice_num, 3);

// 設置通道 A 在下降沿之前輸出高電平一個循環
pwm_set_chan_level(slice_num, PWM_CHAN_A, 1);

// 設置通道 B 在下降沿之前初始輸出高電平三個循環
pwm_set_chan_level(slice_num, PWM_CHAN_B, 3);

// 啟動 PWM
pwm_set_enabled(slice_num, true);
}
備註

在這個示例中,我們將 GPIO 0 和 GPIO 1 分配給 PWM 功能,並找到連接到 GPIO 0 的 PWM 切片(slice)編號。然後,我們設置 PWM 週期為 4 個循環,並配置通道 A 和通道 B 的輸出電平。最後,我們啟動 PWM,開始輸出 PWM 信號。

值得注意的是,我們也可以使用 pwm_set_gpio_level(gpio, x) 函數,它會自動查找給定 GPIO 的正確切片和通道,並設置輸出電平。

程序示例 2: 使用 PWM 控制 LED 亮度​

接下來,讓我們看一個更實用的例子,演示如何用一個切片的兩個通道輸出頻率一致占空比不同的pwm波形:

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

// PWM 參數
#define PWM_FREQ 1000 // PWM 頻率為 1kHz
#define PWM_DUTY_CYCLE 30 // PWM 占空比為 30%

int main() {
// 選擇要使用的GPIO
const uint GPIO0 = 0; // 使用 GPIO 0
const uint GPIO1 = 1; // 使用 GPIO 1

// 將GPIO設置為PWM功能
gpio_set_function(GPIO0, GPIO_FUNC_PWM); // 將 GPIO 0 設置為 PWM 功能
gpio_set_function(GPIO1, GPIO_FUNC_PWM); // 將 GPIO 1 設置為 PWM 功能

// 找到PWM切片
uint slice_num = pwm_gpio_to_slice_num(GPIO0); // 獲取 GPIO 0 對應的 PWM 切片編號

// 配置PWM
pwm_config config = pwm_get_default_config(); // 獲取 PWM 的默認配置
// 設置分頻器,以確定頻率
// 系統時鐘頻率為125MHz,頻率 = 125MHz / 分頻器 / (包絡值 + 1)
uint16_t div = 125000000 / PWM_FREQ / 10000; // 計算分頻器值
pwm_config_set_clkdiv(&config, div); // 設置 PWM 分頻器
// 設置包絡值,也就是計數器的最大值
pwm_config_set_wrap(&config, 9999); // 設置 PWM 計數器的最大值為 9999
// 使配置生效
pwm_init(slice_num, &config, true); // 初始化 PWM 並使配置生效

// 設置PWM占空比
uint16_t level_a = 9999 * 35 / 100; // 設置 GPIO0 的占空比為 35%
uint16_t level_b = 9999 * 75 / 100; // 設置 GPIO1 的占空比為 75%
pwm_set_chan_level(slice_num, PWM_CHAN_A, level_a); // 設置通道A (GPIO0) 的占空比
pwm_set_chan_level(slice_num, PWM_CHAN_B, level_b); // 設置通道B (GPIO1) 的占空比

// 讓主循環空轉,維持PWM輸出
while (1) {
tight_loop_contents(); // 主循環空轉,保持 PWM 輸出
}
}

// 互補輸出
#include "pico/stdlib.h"
#include "hardware/pwm.h"

// PWM 參數
#define PWM_FREQ 1000 // PWM 頻率為 1kHz
#define PWM_DUTY_CYCLE 30 // PWM 占空比為 30%

int main() {
// 選擇要使用的GPIO
const uint GPIO0 = 0;
const uint GPIO1 = 1;

// 將GPIO設置為PWM功能
gpio_set_function(GPIO0, GPIO_FUNC_PWM);
gpio_set_function(GPIO1, GPIO_FUNC_PWM);

// 找到PWM切片
uint slice_num = pwm_gpio_to_slice_num(GPIO0);

// 配置PWM
pwm_config config = pwm_get_default_config();
// 啟用相位校正模式
pwm_config_set_phase_correct(&config, true);
// 設置分頻器,以確定頻率
// 系統時鐘頻率為125MHz,頻率 = 125MHz / 分頻器 / (包絡值 + 1) / 2
// 注意在相位校正模式下,頻率會減半
uint16_t div = 125000000 / PWM_FREQ / 10000 / 2;
pwm_config_set_clkdiv(&config, div);
// 設置包絡值,也就是計數器的最大值
pwm_config_set_wrap(&config, 9999);
// 設置輸出極性
pwm_config_set_output_polarity(&config, false, true); // 通道B反相
// 使配置生效
pwm_init(slice_num, &config, true);

// 設置PWM占空比
uint16_t level = 9999 * PWM_DUTY_CYCLE / 100;
pwm_set_both_levels(slice_num, level, level); // 兩個通道使用相同的占空比

// 讓主循環空轉,維持PWM輸出
while (1) {
tight_loop_contents();
}
}

提示

1個切片 兩個通道 相鄰 GPIO 是一個切片 如 GPIO0&GPIO1 GPIO2&GPIO3

程序示例 3: 測量 PWM 信號的佔空比​

最後,讓我們看一個示例,演示如何使用 RP2040 的 PWM 功能來測量 PWM 信號的佔空比:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"

const uint OUTPUT_PIN = 2;
const uint MEASURE_PIN = 5;

float measure_duty_cycle(uint gpio) {
// 只有 PWM B 引腳可以用作輸入
assert(pwm_gpio_to_channel(gpio) == PWM_CHAN_B);

uint slice_num = pwm_gpio_to_slice_num(gpio);

// 每 100 個循環,PWM B 輸入為高電平時計數一次
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;
}

const float test_duty_cycles[] = {
0.f,
0.1f,
0.5f,
0.9f,
1.f
};

int main() {
stdio_init_all();
printf("\nPWM duty cycle measurement example\n");

// 配置 PWM 切片並設置為運行狀態
const uint count_top = 1000;
pwm_config cfg = pwm_get_default_config();
pwm_config_set_wrap(&cfg, count_top);
pwm_init(pwm_gpio_to_slice_num(OUTPUT_PIN), &cfg, true);

// 注意,我們還沒有觸碰另一個引腳 -- PWM 引腳預設為輸出,
// 但一旦分頻器模式從自由運行改變,就會變為輸入。
// 將兩個輸出直接連接在一起是不明智的!
gpio_set_function(OUTPUT_PIN, GPIO_FUNC_PWM);

// 對於每個測試佔空比,在輸出引腳上驅動該電平,
// 然後使用另一個引腳讀回實際的輸出佔空比。這兩個值應該非常接近!
for (int i = 0; i < count_of(test_duty_cycles); ++i) {
float output_duty_cycle = test_duty_cycles[i];
pwm_set_gpio_level(OUTPUT_PIN, (uint16_t)(output_duty_cycle * (count_top + 1)));

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

在這個示例中,我們演示了如何使用 RP2040 的 PWM 功能來測量 PWM 信號的佔空比。程序的主要步驟如下:

  1. 定義輸出引腳 OUTPUT_PIN 和測量引腳 MEASURE_PIN。
  2. 實現 measure_duty_cycle() 函數,用於測量給定 GPIO 引腳上的 PWM 信號佔空比。
    • 配置 PWM 切片,設置分頻器模式為 PWM_DIV_B_HIGH,即每 100 個循環計數一次。
    • 啟動 PWM,等待一段時間,然後停止 PWM。
    • 計算最大可能計數值,並根據實際計數值計算佔空比。
  3. 在 main() 函數中,配置輸出引腳的 PWM 切片,設置包裹值為 count_top。
  4. 對於每個測試佔空比,在輸出引腳上設置相應的電平,然後使用測量引腳讀回實際的輸出佔空比。
  5. 將輸出佔空比和測量佔空比打印出來,檢查它們是否接近。
警告

在將兩個引腳連接在一起之前,需要注意 PWM 引腳的默認狀態。PWM 引腳預設為輸出,但一旦分頻器模式從自由運行改變,就會變為輸入。將兩個輸出直接連接在一起是不明智的!

通過這個示例,我們可以看到如何使用 RP2040 的 PWM 功能來精確測量 PWM 信號的佔空比。這在需要對 PWM 信號進行分析和控制的場景中非常有用。

單獨使用 PWM​

除了上述示例中的用法,我們還可以單獨使用 PWM 來控制各種設備,如直流電機、步進電機、伺服機等。下面我們將詳細介紹如何使用 PWM 來控制這些設備,以及如何使用 PWM 作為輸入捕獲來檢測輸入信號的頻率、脈寬和佔空比。

控制直流電機​

使用 PWM 可以方便地控制直流電機的轉速。通過改變 PWM 信號的佔空比,我們可以調節輸出到電機的平均電壓,從而控制電機的轉速。以下是一個使用 PWM 控制直流電機的示例:

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

const uint MOTOR_PIN = 2;

int main() {
// 將電機引腳設置為 PWM 功能
gpio_set_function(MOTOR_PIN, GPIO_FUNC_PWM);

// 獲取 PWM 切片編號和通道
uint slice_num = pwm_gpio_to_slice_num(MOTOR_PIN);
uint channel = pwm_gpio_to_channel(MOTOR_PIN);

// 配置 PWM 切片
pwm_config config = pwm_get_default_config();
pwm_config_set_wrap(&config, 65535);
pwm_init(slice_num, &config, true);

while (1) {
// 設置佔空比,控制電機轉速
pwm_set_chan_level(slice_num, channel, 32768); // 50% 佔空比
sleep_ms(2000);
pwm_set_chan_level(slice_num, channel, 16384); // 25% 佔空比
sleep_ms(2000);
pwm_set_chan_level(slice_num, channel, 49152); // 75% 佔空比
sleep_ms(2000);
}
}

在這個示例中,我們將電機連接到 GPIO2 引腳,並將其設置為 PWM 功能。然後,我們配置 PWM 切片,設置包裹值為 65535,以獲得更高的解析度。在主迴圈中,我們通過設置不同的佔空比來控制電機轉速。佔空比的取值範圍為 0 到 65535,分別對應 0% 到 100% 的佔空比。

提示

根據電機的規格和電源電壓,你可能需要使用 H 橋或電機驅動器來安全地驅動電機。PWM 信號可以用來控制 H 橋或電機驅動器的輸入,從而調節電機轉速。

控制步進電機​

步進電機是一種常用的精密定位設備,可以通過控制脈衝信號來實現精確的角度控制。使用 PWM,我們可以生成控制步進電機所需的脈衝信號。以下是一個使用 PWM 控制步進電機的示例:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/irq.h"

#include "stdlib.h"

#define STEP_PIN 0
#define DIR_PIN 1
#define PWM_SLICE_NUM 0
#define PULSES_PER_REV 1600 // 每圈脈衝數
#define PWM_FREQ (10000) // PWM 頻率 (Hz)

volatile int32_t pulse_count = 0;
volatile int32_t target_pulses = 0;
volatile bool motor_running = false;
volatile float current_angle = 0.0f;

void pwm_irq_handler() {
pwm_clear_irq(PWM_SLICE_NUM);
if (motor_running) {
pulse_count++;
if (pulse_count >= target_pulses) {
pwm_set_enabled(PWM_SLICE_NUM, false);
gpio_set_function(STEP_PIN, GPIO_FUNC_SIO);
gpio_set_dir(STEP_PIN, GPIO_OUT);
gpio_put(STEP_PIN, 0); // 確保引腳為低電平
motor_running = false;
}
}
}

void configure_pwm() {
gpio_set_function(STEP_PIN, GPIO_FUNC_PWM);

pwm_config config = pwm_get_default_config();

// 計算分頻器值和包絡值
uint32_t clock_freq = clock_get_hz(clk_sys); // 獲取系統時鐘頻率
uint32_t desired_freq = 1000; // 目標頻率 1kHz
uint32_t divider = 1;
uint32_t wrap = clock_freq / (desired_freq * divider) - 1;

// 調整分頻器和包絡值,以獲得最接近目標頻率的設置
while (wrap > 65535 && divider < 256) {
divider++;
wrap = clock_freq / (desired_freq * divider) - 1;
}

pwm_config_set_clkdiv_int(&config, divider);
pwm_config_set_wrap(&config, wrap);

// 設置 50% 占空比
uint16_t level = (wrap + 1) / 2;

pwm_init(PWM_SLICE_NUM, &config, false);
pwm_set_chan_level(PWM_SLICE_NUM, PWM_CHAN_A, level);

pwm_clear_irq(PWM_SLICE_NUM);
pwm_set_irq_enabled(PWM_SLICE_NUM, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_irq_handler);
irq_set_enabled(PWM_IRQ_WRAP, true);
}


void rotate_motor(float angle) {
if (motor_running) return; // 如果電機正在運轉,忽略新的命令

bool direction = angle >= 0;
target_pulses = abs((int)(angle / 360.0f * PULSES_PER_REV));
pulse_count = 0;
motor_running = true;

gpio_put(DIR_PIN, direction);
gpio_set_function(STEP_PIN, GPIO_FUNC_PWM);
pwm_set_enabled(PWM_SLICE_NUM, true);

current_angle += angle;
while (current_angle >= 360.0f) current_angle -= 360.0f;
while (current_angle < 0.0f) current_angle += 360.0f;
}

int main() {
stdio_init_all();

gpio_init(DIR_PIN);
gpio_set_dir(DIR_PIN, GPIO_OUT);
configure_pwm();

// printf("Stepper Motor Control\n");
// printf("Step Angle: 1.8 degrees, Microstepping: 8, Pulses per Revolution: 1600\n");

while (true) {

rotate_motor(80);
sleep_ms(1000);
rotate_motor(-80);
sleep_ms(1000);
}

return 0;
}

在這個示例中,我們使用兩個 PWM 通道來控制步進電機。一個通道用於生成步進脈衝,另一個通道用於控制步進電機的方向。我們通過設置脈衝的佔空比和頻率來生成所需的步進脈衝,並通過改變方向通道的電平來控制步進電機的旋轉方向。

備註

步進電機通常需要較高的電流驅動,因此建議使用專用的步進電機驅動器來控制步進電機。PWM 信號可以作為步進電機驅動器的輸入,用於控制步進脈衝和方向。

備註

生成步進脈衝的過程可以分為以下幾個步驟:

  1. 設置步進引腳的 PWM 輸出電平為高電平(50% 佔空比)。
  2. 延遲一段時間(脈衝的高電平持續時間)。
  3. 設置步進引腳的 PWM 輸出電平為低電平。
  4. 延遲一段時間(脈衝的低電平持續時間)。
  5. 重複步驟 1-4,直到生成足夠數量的步進脈衝。
改變步進電機旋轉方向​
pwm_set_chan_level(slice_num_dir, channel_dir, 65535);

生成完 200 個步進脈衝後,我們延遲 1 秒鐘,然後將方向引腳的 PWM 輸出電平設置為 65535,表示步進電機的反向旋轉。

接下來,我們再次生成 200 個步進脈衝,但這次步進電機將向相反方向旋轉。最後,我們再次延遲 1 秒鐘,然後重複整個過程。

程式碼中的數字參數​
備註

這段程式碼中的數字參數的含義如下:

  • 32768: 表示 50% 的 PWM 佔空比。因為我們將 PWM 計數器的最大值設置為 65535,所以 32768 大約是最大值的一半,對應 50% 的佔空比。
  • 500: 表示步進脈衝的高電平和低電平持續時間,單位為微秒。這個值決定了步進電機的速度。增大這個值會降低步進電機的速度,減小這個值會提高步進電機的速度。
  • 200: 表示生成的步進脈衝數量。這個值決定了步進電機旋轉的角度。增大這個值會增加步進電機旋轉的角度,減小這個值會減小步進電機旋轉的角度。
  • 65535: 表示 PWM 計數器的最大值,也表示 100% 的 PWM 佔空比。當方向引腳的 PWM 輸出電平設置為 65535 時,表示步進電機的反向旋轉。
其他 PWM 配置​

除了上述參數,我們還可以設置其他的 PWM 配置,例如:

  • pwm_config_set_clkdiv: 設置 PWM 時鐘分頻器,可以改變 PWM 的頻率。增大分頻值會降低 PWM 頻率,減小分頻值會提高 PWM 頻率。
  • pwm_config_set_phase_correct: 設置是否使用相位校正模式。在相位校正模式下,PWM 計數器在達到最大值後會向下計數,而不是直接重置為 0。這可以減少 PWM 輸出的尖峰噪聲。
  • pwm_config_set_output_polarity: 設置 PWM 輸出的極性。可以選擇在 PWM 計數器達到比較值時輸出高電平還是低電平。

調整這些 PWM 配置可以改變 PWM 輸出的特性,從而影響步進電機的運行方式,如速度、扭矩、噪聲等。

警告

需要注意的是,這段程式碼只是一個基本的示例,用於演示如何使用 PWM 控制步進電機。在實際應用中,你可能需要根據具體的步進電機型號和驅動器來調整參數和設置,以獲得最佳的性能和可靠性。此外,步進電機通常需要較大的電流,因此建議使用專用的步進電機驅動器,而不是直接通過 PWM 引腳驅動。

控制伺服機​

伺服機是一種常用的位置控制設備,通過改變 PWM 信號的脈寬來控制伺服機的角度。以下是一個使用 PWM 控制伺服機的示例:

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

const uint SERVO_PIN = 2;

int main() {
// 將伺服機引腳設置為 PWM 功能
gpio_set_function(SERVO_PIN, GPIO_FUNC_PWM);

// 獲取 PWM 切片編號和通道
uint slice_num = pwm_gpio_to_slice_num(SERVO_PIN);
uint channel = pwm_gpio_to_channel(SERVO_PIN);

// 配置 PWM 切片
pwm_config config = pwm_get_default_config();
pwm_config_set_wrap(&config, 19999);
pwm_config_set_clkdiv(&config, 9.6f);
pwm_init(slice_num, &config, true);

while (1) {
// 設置脈寬,控制伺服機角度
pwm_set_chan_level(slice_num, channel, 1000); // 0 度
sleep_ms(2000);
pwm_set_chan_level(slice_num, channel, 1500); // 90 度
sleep_ms(2000);
pwm_set_chan_level(slice_num, channel, 2000); // 180 度
sleep_ms(2000);
}
}

在這個示例中,我們將伺服機連接到 GPIO2 引腳,並將其設置為 PWM 功能。我們配置 PWM 切片,設置包裹值為 19999,時鐘分頻器為 9.6,以獲得 50Hz 的 PWM 頻率。在主迴圈中,我們通過設置不同的脈寬來控制伺服機的角度。脈寬的典型值為 1000us(0 度)、1500us(90 度)和 2000us(180 度),但具體值可能因伺服機型號而有所不同。

警告

伺服機的控制信號需要精確的脈寬和頻率,因此在配置 PWM 時需要仔細計算包裹值和時鐘分頻器,以獲得所需的脈寬和頻率。此外,伺服機通常需要單獨的電源供電,以提供足夠的電流。

使用 PWM 作為輸入捕獲​

除了用於輸出控制信號,PWM 還可以作為輸入捕獲來檢測輸入信號的頻率、脈寬和佔空比。以下是一個使用 PWM 作為輸入捕獲的示例:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"

const uint INPUT_PIN = 2;

int main() {
// 將輸入引腳設置為 PWM 功能
gpio_set_function(INPUT_PIN, GPIO_FUNC_PWM);

// 獲取 PWM 切片編號
uint slice_num = pwm_gpio_to_slice_num(INPUT_PIN);

// 配置 PWM 切片作為輸入捕獲模式
pwm_config config = pwm_get_default_config();
pwm_config_set_clkdiv_mode(&config, PWM_DIV_B_RISING);
pwm_init(slice_num, &config, false);

// 使能輸入捕獲
pwm_set_enabled(slice_num, true);

while (1) {
// 等待輸入信號
while (!(pwm_get_status(slice_num) & PWM_CH0_CSR_RCIF_BITS));

// 讀取輸入信號的頻率、脈寬和佔空比
uint32_t period = pwm_get_counter(slice_num);
uint32_t pulse_width = pwm_get_compare_counter(slice_num, 0);
float frequency = clock_get_hz(clk_sys) / period;
float duty_cycle = (float)pulse_width / period;

printf("Frequency: %.2f Hz, Pulse Width: %u us, Duty Cycle: %.2f%%\n",
frequency, pulse_width, duty_cycle * 100);

// 清除輸入捕獲中斷標誌
pwm_clear_irq(slice_num);
}
}

在這個示例中,我們將輸入信號連接到 GPIO2 引腳,並將其設置為 PWM 功能。我們配置 PWM 切片作為輸入捕獲模式,並使能輸入捕獲。在主迴圈中,我們等待輸入信號觸發輸入捕獲中斷,然後讀取輸入信號的頻率、脈寬和佔空比。頻率可以通過時鐘頻率除以週期計數器值來計算,脈寬可以直接從捕獲比較計數器中讀取,佔空比可以通過脈寬除以週期來計算。

備註

輸入捕獲模式下,PWM 切片會在輸入信號的上升沿或下降沿觸發輸入捕獲中斷,並記錄當前的計數器值。通過比較連續兩次捕獲的計數器值,可以計算出輸入信號的頻率、脈寬和佔空比。

總結​

在本文中,我們詳細探討了如何在 RP2040 中使用 PWM 功能。除了之前介紹的基本用法,我們還深入講解了如何使用 PWM 控制直流電機、步進電機和伺服機,以及如何使用 PWM 作為輸入捕獲來檢測輸入信號的頻率、脈寬和佔空比。

通過學習本文提供的詳細解釋和示例代碼,你應該能夠掌握 RP2040 中 PWM 的各種用法,並將其應用到自己的嵌入式項目中。無論是電機控制、伺服機控制還是信號檢測,PWM 都是一個強大而靈活的工具。

希望本文能夠幫助你更好地理解和應用 RP2040 中的 PWM 功能。如果你有任何問題或建議,歡迎在評論區留言討論。