今回は、Pico(Raspberry Pi Pico)を使って複数ピンから異なる周波数のPWM信号を同時に出力する方法です。
概要
Arduino(UNO R4)やESP32などでは「PWMout」や「LEDC」などの関数が準備されているため、ピン毎に異なる周波数のPWM信号を出力するのは割と簡単に実現できます。ただPicoではそういった便利な関数を見つけられなかったのと、analogWrite関数ではできないので直接C/C++SDKベースで記述してます。ArduinoIDEのearlephilhower版ボードで検証/確認。
サンプルプログラム
さっそくですが作成したサンプルソースです。 GP2ピンから1000Hz(DUTY50%)。GP4ピンから26000Hz(DUTY30%)とピン毎に違う周波数でPWM出力を行うソースです。
#include <hardware/pwm.h>
void setup() {
gpio_set_function(2, GPIO_FUNC_PWM); //GP2
gpio_set_function(4, GPIO_FUNC_PWM); //GP4
uint slice_num1 = pwm_gpio_to_slice_num(2);
uint slice_num2 = pwm_gpio_to_slice_num(4);
//GP2 1000Hz 解像度500
pwm_set_clkdiv(slice_num1, 250.0);
pwm_set_wrap(slice_num1, 499);
//GP4 26000Hz 解像度100
pwm_set_clkdiv(slice_num2, 48.076923);
pwm_set_wrap(slice_num2, 99);
pwm_set_enabled(slice_num1, true);
pwm_set_enabled(slice_num2, true);
//GP2 DUTY50% //GP4 DUTY30%
pwm_set_chan_level(slice_num1, PWM_CHAN_A, (uint16_t)((500) * 0.5));
pwm_set_chan_level(slice_num2, PWM_CHAN_A, (uint16_t)((100) * 0.3));
}
void loop() {
}
ほぼC/C++SDKマニュアルにあるサンプルソースのまま。ピンを2つに修正しているだけです。切り取って順に説明していきます。
ピン割り当て
gpio_set_function(2, GPIO_FUNC_PWM); //GP2
gpio_set_function(4, GPIO_FUNC_PWM); //GP4
GP2ピンとGP4ピンをPWM出力ピンに設定してます。Picoの場合ほとんどのピンにPWMを割り当て可能です。
スライス確認とチャンネル
PWM周波数やDUTYを設定するには、GPIOピンがどのグループ/チャンネルに割り当てられてるかを確認する必要があります。
スライス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
チャンネルA | GP0/GP16 | GP2/GP18 | GP4/GP20 | GP6/GP22 | GP8/GP24 | GP10/GP26 | GP12/GP28 | GP14 |
チャンネルB | GP1/GP17 | GP3/GP19 | GP5/GP21 | GP7/GP23 | GP9/GP25 | GP11/GP27 | GP13/GP29 | GP15 |
ピン毎に8つのスライスと2つのチャンネルでピンがグルーピングされてます。このスライス毎に周波数設定ができるため、異なる周波数を同時に出力できるのは最大8パターンが限界になるかと思います。試したわけではないですが恐らくそういうことかと思います。
▼スライス番号を確認▼
uint slice_num1 = pwm_gpio_to_slice_num(2);
uint slice_num2 = pwm_gpio_to_slice_num(4);
この関数でスライス番号が確認できます。GP2ピンとGP4ピンのスライス番号を確認して変数に格納。GP2⇒1、GP4⇒2となります。番号を確認しているだけなので先ほどの表がわかっていればこの番号確認は不要。マニュアルでこういう使い方をしていたので真似てるだけです。今回は2つの周波数を出力したいのでスライス番号の違うピン(GP2/GP4)を利用します。
PWM周波数の設定(clkdivとwrap)
PWM周波数はスライス毎に設定可能。clkdivとwrap(top)の値で周波数が決まります。clkdiv(分周)は256未満の値で設定。wrapはカウンタで65535以下で設定します。
PWM周波数との関係式
周波数とclkdivとwrapの関係式は以下、
PWM周波数 = クロック周波数/((wrap+1)×clkdiv)
例えばclkdiv=250、wrap=499とした場合、125000000/((499+1)x250)=1000
となり、PWM周波数は1000Hzとなります。
▼clkdivとwrapの設定▼
//GP2 1000Hz
pwm_set_clkdiv(slice_num1, 250.0);
pwm_set_wrap(slice_num1, 499);
//GP4 26000Hz
pwm_set_clkdiv(slice_num2, 48.076923);
pwm_set_wrap(slice_num2, 99);
それぞれこの関数で設定します。スライス番号を指定してclkdivとwrapを設定。GP2(スライス番号1)はでPWM周波数1000Hz。GP4(スライス番号2)はPWM周波数26000Hzになるよう設定してます。この関数ではclkdivはfloat型になります。他にいろいろと設定する関数もあるようですがよく調べてません。
DUTYの設定
DUTY比はμsやms、%で指示することがあるかと思いますがここでは%で行います。wrap+1が1周期のカウント(解像度)となりますのでそのカウント割合がそのままDUTY比となります。今回の例ではDUTY100%の場合、GP2が500、GP4が100になります。
//GP2 DUTY50% //GP4 DUTY30% pwm_set_chan_level(slice_num1, PWM_CHAN_A, (uint16_t)((500) * 0.5)); pwm_set_chan_level(slice_num2, PWM_CHAN_A, (uint16_t)((100) * 0.3));
Dutyはスライス番号とチャンネルで指示。GP2:DUTY50%、GP4:DUTY30%で指示してます。
PWM出力方法はざっとこのような感じ。実際に今回のプログラムを書き込んで出力を確認してみました。
出力結果の確認
GP2/4ピンから出力されている信号をロジアナ測定してます。
▼GP2ピンからの出力波形(1000Hz、DUTY50%)▼
Picoオシロで測定してます。概ね指示通りの出力。
▼GP4ピンからの出力波形(26000Hz、DUTY30%)▼
こちらも指示通り。問題無く出力できてます。
細かい検証は行ってませんが、とりあえずこの方法で複数ピンから異なる周波数でのPWM出力は実現できました。周波数やDUTY設定をする際、都度、clkdiv、wrapなどの値を確認するのも面倒なので簡単な関数作成しておきます。
PWM出力関数
//2024/08/24
//imo Lab. https://garchiving.com
//Raspberry Pi Pico PWM出力関数
#include <hardware/pwm.h>
void setup() {
Serial.begin(115200);
gpio_set_function(2, GPIO_FUNC_PWM);
uint slice_num1 = pwm_gpio_to_slice_num(2);
pwm_set_enabled(slice_num1, true);
}
void loop() {
PWM_OUTPUT(2, 100000, 50); //pin,Hz,DUTY[%]
}
void PWM_OUTPUT(uint8_t _pin, uint32_t _freq, uint8_t _DUTY) {
uint32_t range = 100;
float clkdiv;
uint slice_num = pwm_gpio_to_slice_num(_pin);
clkdiv = (float)(F_CPU) / (float)(range * _freq);
uint16_t wrap = range - 1;
pwm_set_clkdiv(slice_num, clkdiv);
pwm_set_wrap(slice_num, wrap);
pwm_set_chan_level(slice_num, PWM_CHAN_A, (uint16_t)(range * _DUTY * 0.01));
}
setup内、初期設定(ピンアサイン)はそのまま記述。PWM_OUTPUT()
で出力ピン、周波数、DUTYを引数にPWM出力を行います。分解能は100で固定しているので出力できるのは5kHz~100kHz程度。あまり汎用性のある作り方はしていないので実際には改造して利用するパターンが多いかと思います。また指定する周波数でclkdivの有効範囲を超えてしまうので注意が必要。
コメント