本文章探討利用Raspberry Pi Pico來量測輸入PWM方波的頻率與佔空比。以STM32F103C8T6來產生PWM方波讓Pico來量測。
一、RP2040 PWM:
(source: RP2040 datasheet)
RP2040 共有8 PWM slices,每個slice有channel A與B,與GPIO對應表如自如所示。每個slice channel A and B均能輸出PWM方波。但只有channel B能作為輸入PWM用。
RP2040輸出方波的波型主要由下列三個主要參數決定。
- 8.4除瀕(8 bits整數,4 bits小數) :
0: max(256)。整數:1~255,小數:0~(15/16)。
pwm_set_clkdiv(slice_num, div); - 16 bit 計數值(TOP register):
pwm_set_wrap(slice_num, top_wap);
top_wap: 0~65535 - 16 bit 計數比較值(CC register):
pwm_set_chan_level(slice_num, PWM_CHAN_A, cc);
cc: 0~65536
例如:頻率為500k佔空比為40%的方波
系統clock為125M Hz,
pwm_set_clkdiv(slice_num 12.5) --> 125M/12.5=10M
pwm_set_warp(slice_num, 19) --> 10M/(19+1) = 500k
pwm_set_chan_level(slice_num, PWM_CHAN_A, 8) --> 8/(19+1) = 40%
(Source RP2040 datasheet)
- PWM輸入:
僅有每個slice的Channel B能接受輸入。
(source RP2040 datasheet)
將Channel B設為輸入有三種模式,分別為偵測High Leve, rising edge與falling edge。
pwm_config_set_clkdiv_mode(&cfg, mode);
pwm_set_clkdiv_mode(slice_num, mode);
mode:PWM_DIV_B_HIGH, PWM_DIV_B_RISING, PWM_DIV_B_FALLING
- 設定PWM_DIV_B_HIGH時可用來偵測Duty Cycle:
當channel偵測到input PWM pulse level為high時,CC count 加 1,register為16 bits,因此最答值為65535。例如:
slice_num : 3, click div: 100, 偵測時間為50ms。則channel B最大clock數為:
125M Hz/100*(50/1000) = 62500 Hz。
pwm_set_clkdiv_mode(slice_num, PWM_DIV_B_HIGH); pwm_set_clkdiv(slice_num, 100);
pwm_set_enabled(slice_num, true);
sleep_ms(50);
pwm_set_enabled(slice_num, false);
上述為channel B 50ms總clock數。
count=pwm_get_counter(slice_num);
當channel B偵測到input PWM pulse為high時, count+1,因此
count / 625000 * 100%即為Duty cycle。 - 設定PWM_DIV_B_RISING or PWM_DIV_B_FALLING時可用來偵測Frequency:
當channel B偵測到input PWM pulse為RISING or FALLING時count+1。pwm_config cfg = pwm_get_default_config(); pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING); pwm_config_set_clkdiv(&cfg, 1);
pwm_init(slice_num, &cfg, false); gpio_set_function(gpio, GPIO_FUNC_PWM);
以125M Hz偵測。
pwm_set_enabled(slice_num, true);
sleep_ms(delay_ms);
pwm_set_enabled(slice_num, false);
rising_count = pwm_get_counter(slice_num);
經過delay_ms後,偵測到rising_count個RISING EDGE,因此
Freq = rising_count / delay_ms*1000 Hz
但是rising_count最多為65535(16 bit register),因此必須根據input pulse frequence調整delay_ms數。
其他詳細程式碼附於文末。
二、STM32 PWM:
STM32 PWM由timer產生。
如上圖timer 1 clock為72 M Hz。
clock source設為internal clock(72M Hz),prescaler, counter period(ARR)分別為1, 299,因此PWM的Frequency為72M Hz/(1+1)/(299+1) = 120K Hz。
htim1.Instance->ARR = fs_presc;
htim1.Instance->CCR1 = fs_presc/duty;
改變ARR與CCR可以改變frequency 與duty cycle
三、展示影片:
四、程式碼:
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"
#include "string.h"
#include "pico_tft/pico_tft.h"
#include "pico_tft/tft_string/tft_string.h"
#include "pico_tft/fonts/font_ubuntu_mono_24.h"
const uint MEASURE_PIN_1 = 3;
const uint MEASURE_PIN_2 = 5;
bool pwm_slice_warp_1=false;
bool pwm_slice_warp_2=false;
void on_pwm_wrap() {
uint slice_num_1 = pwm_gpio_to_slice_num(MEASURE_PIN_1);
if (pwm_get_irq_status_mask() & (1 << slice_num_1)) {
pwm_clear_irq(slice_num_1);
pwm_slice_warp_1=true;
}
uint slice_num_2 = pwm_gpio_to_slice_num(MEASURE_PIN_2);
if (pwm_get_irq_status_mask() & (1 << slice_num_2)) {
pwm_clear_irq(slice_num_2);
pwm_slice_warp_2=true;
}
}
float measure_frequency(uint gpio) {
uint16_t measure_ms[] = {1,5,10,100,1000};
bool *pwm_slice_warp=false;
// 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);
if (gpio == MEASURE_PIN_1) pwm_slice_warp = &pwm_slice_warp_1;
if (gpio == MEASURE_PIN_2) pwm_slice_warp = &pwm_slice_warp_2;
// Count once for every 1 cycles the PWM B input is high
pwm_config cfg = pwm_get_default_config();
pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING);
pwm_config_set_clkdiv(&cfg, 1);
pwm_init(slice_num, &cfg, false);
gpio_set_function(gpio, GPIO_FUNC_PWM);
pwm_set_irq_enabled(slice_num, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
irq_set_enabled(PWM_IRQ_WRAP, true);
uint32_t ms=0;
uint16_t count=0;
uint8_t meauser_count = sizeof(measure_ms)/sizeof(measure_ms[0]);
*pwm_slice_warp=false;
for (int i=0; i < meauser_count;i++) {
ms = measure_ms[i];
pwm_set_enabled(slice_num, true);
sleep_ms(ms);
pwm_set_enabled(slice_num, false);
count = pwm_get_counter(slice_num);
pwm_set_counter(slice_num,0);
if (*pwm_slice_warp) {
*pwm_slice_warp=false;
if (i > 0) {
ms = measure_ms[i-1];
pwm_set_enabled(slice_num, true);
sleep_ms(ms);
pwm_set_enabled(slice_num, false);
count = pwm_get_counter(slice_num);
pwm_set_counter(slice_num,0);
}
break;
}
}
return ((float)count*1000/(ms));
}
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);
uint16_t count=0;
pwm_set_counter(slice_num,0);
pwm_set_enabled(slice_num, true);
sleep_ms(50);
pwm_set_enabled(slice_num, false);
count=pwm_get_counter(slice_num);
float counting_rate = clock_get_hz(clk_sys) / 100;
float max_possible_count = counting_rate *0.05; // 125M/100*0.05 = 62500 < 65535
return pwm_get_counter(slice_num)/max_possible_count;
}
void test_500k_50() {
gpio_set_function(6, GPIO_FUNC_PWM);
uint slice_num = pwm_gpio_to_slice_num(6);
pwm_config cfg = pwm_get_default_config();
pwm_config_set_clkdiv(&cfg, 12.5);
pwm_config_set_wrap(&cfg, 19);
pwm_init(slice_num, &cfg, true);
pwm_set_chan_level(slice_num, PWM_CHAN_A, 10);
}
int main() {
stdio_init_all();
tft_init();
tft_fill_rect(0,0, TFT_WIDTH-1, TFT_HEIGHT-1, 0xffff);
printf("\nPWM frequency and duty cycle measurement example\n");
uint8_t buffer[120];
float duty, frequency;
while (1) {
//sleep_ms(1000);
frequency = measure_frequency(MEASURE_PIN_1);
duty = measure_duty_cycle(MEASURE_PIN_1)*100;
sprintf(buffer, "F1:%.0f", frequency);
tft_draw_string_withbg(10,10, " ", 0x001f, 0xffff, &font_ubuntu_mono_24);
tft_draw_string_withbg(10,10, buffer, 0x001f, 0xffff, &font_ubuntu_mono_24);
sprintf(buffer, "D1:%02.2f%%", duty);
tft_draw_string_withbg(10,35, buffer, 0xf800, 0xffff, &font_ubuntu_mono_24);
printf("\nMEASURE 1 == Freq:%07.0f, Duty:%02.2f%%\n", frequency, duty);
frequency = measure_frequency(MEASURE_PIN_2);
duty = measure_duty_cycle(MEASURE_PIN_2)*100;
sprintf(buffer, "F2:%.0f", frequency);
tft_draw_string_withbg(10,65, " ", 0x001f, 0xffff, &font_ubuntu_mono_24);
tft_draw_string_withbg(10,65, buffer, 0x001f, 0xffff, &font_ubuntu_mono_24);
sprintf(buffer, "D2:%02.2f%%", duty);
tft_draw_string_withbg(10,90, buffer, 0xf800, 0xffff, &font_ubuntu_mono_24);
printf("\nMEASURE 2 == Freq:%07.0f, Duty:%02.2f%%\n", frequency, duty);
}
}
沒有留言:
張貼留言