本文章探討利用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); } }
沒有留言:
張貼留言