このやり方が最良かどうかわかりません。私なりの方法です。
欲しい周波数を$f$、クロック周波数を$C_k$とします。また、ラップ値を$M$とすると、 分周費$D_r$は次式で求めることができます。
\[D_r=\frac{\frac{1}{f}*C_k}{M} = \frac{C_k}{f*M}\]分周費は実数です。Raspberry Pi Picoは分周費を
pwm_set_clkdiv_int_frac(uint slice_number, uint8_t integer, uint8_t fract)という関数で設定します。integerが整数部、fractが少数部です。 fractが4ビットですので少数部は$2^{-4}=0.125$の単位でしか設定することができません。 従って、$D_r$は実際には$RD_r$となり、本当に欲しい値とは少しだけずれてしまいます。 \[RD_r= \frac{INT(D_r*16)}{16}\]
今度はこの$D_r$からラップ値$RM$を求めます。
\[RM= INT(\frac{C_k}{f*RD_r})\]ラップ値$M$と$RD_r$を使ってできる周波数と、ラップ値$RM$と$RD_r$を使った場合の周波数にどれほど差がでるか 計算してみます。 $f=440Hz$、 $C_k=125MHz$、 $M=32768$ とします。
\[D_r = \frac{125,000,000}{440*32768} = 8.669766512784091\]16倍してINTを取り、16で割ると、$RD_r =8.625$となります。$RD_r$から作られる波形の周波数は
\[f = \frac{C_k}{M*RD_r} = \frac{125,000,000}{32768*8.625}=442.283740942028986\]となります。一方$RM$を計算すると、
\[RM= INT(\frac{125,000,000}{440*8.625})= INT(32938.076416337285903) = 32938\]こちらから求まる$f_r$は
\[f_r = \frac{C_k}{RM*RD_r} = \frac{125,000,000}{32938*8.625}=440.001020802368261\]となり$f_r$の方が440Hzにより近い周波数となります。プログラムとしては以下のようになります。
#include "pico/stdlib.h" #include "hardware/pwm.h" int main() { gpio_set_function(0, GPIO_FUNC_PWM); gpio_set_function(1, GPIO_FUNC_PWM); uint slice_num = pwm_gpio_to_slice_num(0); pwm_set_wrap(slice_num, 32938); pwm_set_clkdiv_int_frac(slice_num, 8, 10); pwm_set_chan_level(slice_num, PWM_CHAN_A, 16469); pwm_set_chan_level(slice_num, PWM_CHAN_B, 8234); pwm_set_enabled(slice_num, true); while(1){} }
PicoのPWMの発信器を4系統を使って音源を作ります。USB-MIDIでPCとPico繋いで、PCから音を鳴らします。 PWMを4系統使うには、以下のプログラムのようにスライス番号を、pwm_gpio_to_slice_num、 それぞれのピン番号0,2,4,6を設定して獲得します。
#define CHNUM 4 uint slice_num[CHNUM]; void pwm_set() { for(uint8_t i=0; i<CHNUM*2; i+=2){ gpio_set_function(i, GPIO_FUNC_PWM); gpio_set_function(i+1, GPIO_FUNC_PWM); } for(uint8_t i=0; i<CHNUM; i++){ slice_num[i] = pwm_gpio_to_slice_num(i*2); } }
Note Onの時
pwm_set_enabled(slice_num[ch], true);
Note Offの時
wm_set_enabled(slice_num[ch], false);
とするのとで音を出す、音を消すを行なっています。