prettyprint

2023年8月22日 星期二

PWM Ep. 1 : Square Wave Frequency and Duty Cycle Measurement -- Raspberry Pi Pico and STM32F103C8T6

本文章探討利用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輸出方波的波型主要由下列三個主要參數決定。
  1. 8.4除瀕(8 bits整數,4 bits小數) :
    0: max(256)。整數:1~255,小數:0~(15/16)。
    pwm_set_clkdiv(slice_num, div);
  2. 16 bit 計數值(TOP register):
    pwm_set_wrap(slice_num, top_wap);
    top_wap: 0~65535
  3. 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

  1. 設定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。
  2. 設定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

Duty cycle=150/(299+1)*100% = 50% 

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

沒有留言:

張貼留言