Raspberry Pi PicoでanalogWrite()
によって生成したPWMを使ったPWM割り込みを行います。手軽に超高速・高精度のタイマー割り込みが実現できます。
setup()
の中の塊になってる5行が要です。
Raspberry Pi Pico SDK DocumentationのPWMのところ
卒業研究でRaspberry Pi Picoを使い、1kHzで実行する処理とそれより遥かに高い周波数で実行する処理(現在は40kHz)を共存させています。
最初はタイマー割り込みを使うつもりだったのですが、どんな動きをしているのか理解できないコードが出現したので諦めました。
PWM割り込みの存在を知って挑戦したところ、割り込みの前段階のPWMの生成部分で周波数とか分周比とか分解能とかいろいろ出てきて面倒になりました。
ここで、面倒な設定とかしないでシンプルにanalogWrite()
のPWMで使えないのか?と試したら使えたのでそのまま使っています。
analogWrite()
ですから非常にお手軽です。
Earle Philhower氏によるRaspberry Pi Pico/RP2040用のArduino coreであるArduino-Picoを使用します。
Arduino IDEの設定で追加のボードマネージャーのURLとして
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
を入れると表示されるようになります。
導入方法は下記ドキュメント参照。
10kHzのPWM信号を出力し、エッジの立ち上がりの際に割り込みを実行します。
#include "hardware/pwm.h"
const uint8_t InterruptPin = 0; // PWM割り込み確認ピン
const uint8_t PWMPin = 5; // PWM出力ピン
const uint PWMFreq = 10000; // 10kHz
uint PWM_slice_num; // PWMスライス番号
// PWM割り込みで実行される関数
void pwm_interrupt()
{
pwm_clear_irq(PWM_slice_num); // PWMスライスの割り込みフラグをクリア(割り込み処理が開始されたことをシステムに通知するためのもの)
gpio_put(InterruptPin, HIGH); // PWM割り込み実行確認
int old_time = micros();
while (micros() - old_time <= 1)
{
/* 1usの遅延 */
}
gpio_put(InterruptPin, LOW); // PWM割り込み実行確認
}
void setup()
{
pinMode(InterruptPin, OUTPUT);
pinMode(PWMPin, OUTPUT);
PWM_slice_num = pwm_gpio_to_slice_num(PWMPin); // PWMスライス番号を取得
pwm_clear_irq(PWM_slice_num); // PWMスライスの割り込みフラグをクリア
pwm_set_irq_enabled(PWM_slice_num, true); // PWMスライスに割り込みを有効化
irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_interrupt); // PWM割り込みを処理するためのコールバック関数を登録
irq_set_enabled(PWM_IRQ_WRAP, true); // PWMスライスを有効化
analogWriteFreq(PWMFreq); // PWM周波数の設定 これが割り込み周波数になる
analogWrite(PWMPin, 127); // PWM信号の生成開始
}
void loop()
{
}
1usの遅延を設けているのは、遅延無しだと早すぎてオシロスコープでもほとんど波形を読み取れないためです。
#include "hardware/pwm.h"
const uint8_t InterruptPin = 0; // PWM割り込み確認ピン
const uint8_t LoopPin = 1; // 周期処理タイミングピン
const uint8_t PWMPin = 5; // PWM出力ピン
const uint PWMFreq = 10000; // 10kHz
uint PWM_slice_num; // PWMスライス番号
volatile int counter = 1;
// PWM割り込みで実行される関数
void pwm_interrupt()
{
pwm_clear_irq(PWM_slice_num); // PWMスライスの割り込みフラグをクリア(割り込み処理が開始されたことをシステムに通知するためのもの)
if (counter > 3)
{
gpio_put(LoopPin, !gpio_get(LoopPin)); // オシロスコープのトリガ信号
counter = 1;
}
for (int i = 0; i < counter; i++)
{
gpio_put(InterruptPin, HIGH); // PWM割り込み実行確認
int old_time = micros();
while (micros() - old_time <= 20)
{
/* 遅延 */
}
gpio_put(InterruptPin, LOW); // PWM割り込み実行確認
while (micros() - old_time <= 10)
{
/* 遅延 */
}
}
switch (counter)
{
case 1:
analogWrite(PWMPin, 100); // 次(counterが2のとき)のDuty比
break;
case 2:
analogWrite(PWMPin, 150); // 次(counterが3のとき)のDuty比
break;
case 3:
analogWrite(PWMPin, 50); // 次(counterが1のとき)のDuty比
break;
default:
analogWrite(PWMPin, 0);
break;
}
counter++;
}
void setup()
{
pinMode(InterruptPin, OUTPUT);
pinMode(LoopPin, OUTPUT);
pinMode(PWMPin, OUTPUT);
PWM_slice_num = pwm_gpio_to_slice_num(PWMPin); // PWMスライス番号を取得
pwm_clear_irq(PWM_slice_num); // PWMスライスの割り込みフラグをクリア
pwm_set_irq_enabled(PWM_slice_num, true); // PWMスライスに割り込みを有効化
irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_interrupt); // PWM割り込みを処理するためのコールバック関数を登録
irq_set_enabled(PWM_IRQ_WRAP, true); // PWMスライスを有効化
analogWriteFreq(PWMFreq); // PWM周波数の設定 これが割り込み周波数になる
analogWrite(PWMPin, 127); // PWM信号の生成開始
}
void loop()
{
}
変数counter
を1,2,3,1,2,3…とループさせて、割り込み時に実行される関数でcounter
の数のパルスを作ります。そして、counter
の数に応じて次のPWM信号のDuty比を変更しています。
4本(4回で1周)でもやってみましたが、やはり次のパルスに対してDuty比を変更できているようです(反映が遅すぎて1周回った3本後にたまたま反映されているわけではないという意)。
ここから更に周波数を上げてみます。
100kHzは怪しいですが、80kHzまでは綺麗に動いているようです。この周波数でもanalogWrite()
のDuty比変更が間に合うものなんですね。
僕は40kHzのPWM信号で割り込んでADCを実行し、その計算結果を次のパルスのDuty比に反映させるという処理をこの方法で行っています。
高いサンプリングレートを正確に出すのは面倒くさいので、このPWM割り込みの方法は雑に使えて便利だと思います。
おすすめです。
オシロスコープを使って行うマイコンのプログラミングが捗りすぎて楽しいです。
2chでここまで便利なら、4chやそれ以上のチャンネル数を持つものなんて一体どれほど便利なんでしょう。
2ch以上のオシロスコープかロジックアナライザが趣味用に欲しいです。