Raspberry Pi PicoとArduino IDEでお手軽PWM割り込み

Featured image of the post

目次

概要

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

を入れると表示されるようになります。

導入方法は下記ドキュメント参照。

例1(割り込みのみ)

10kHzのPWM信号を出力し、エッジの立ち上がりの際に割り込みを実行します。

Image in a image block
黄色:基準PWM信号(10kHz) Pin5 青色:PWM割り込み実行パルス Pin0
#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の遅延を設けているのは、遅延無しだと早すぎてオシロスコープでもほとんど波形を読み取れないためです。

Image in a image block
こうなる

例2(PWM割り込みで次のパルスのDuty比変更)

#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比を変更しています。

Image in a image block
黄色:Duty比可変の基準PWM信号(10kHz) Pin5 青色:PWM割り込みのcounter(本数で表現) Pin0

4本(4回で1周)でもやってみましたが、やはり次のパルスに対してDuty比を変更できているようです(反映が遅すぎて1周回った3本後にたまたま反映されているわけではないという意)。

Image in a image block
黄色:基準PWM信号(10kHz) 青色:PWM割り込みのcounter(本数で表現)

ここから更に周波数を上げてみます。

Image in a image block
50kHz
Image in a image block
60kHz
Image in a image block
80kHz
Image in a image block
100kHz

100kHzは怪しいですが、80kHzまでは綺麗に動いているようです。この周波数でもanalogWrite()のDuty比変更が間に合うものなんですね。

まとめ

僕は40kHzのPWM信号で割り込んでADCを実行し、その計算結果を次のパルスのDuty比に反映させるという処理をこの方法で行っています。
高いサンプリングレートを正確に出すのは面倒くさいので、このPWM割り込みの方法は雑に使えて便利だと思います。

おすすめです。

所感

オシロスコープを使って行うマイコンのプログラミングが捗りすぎて楽しいです。

2chでここまで便利なら、4chやそれ以上のチャンネル数を持つものなんて一体どれほど便利なんでしょう。

2ch以上のオシロスコープかロジックアナライザが趣味用に欲しいです。