2017年11月

2017年11月08日

AVR系Arduinoの外部割込みピンを増やしたかった

めもらしきもの

AVR系のArduinoではattachInterruptというステキ関数を使うことで外部割り込みを使うことができます。確かUNOではINT0,INT1の二つのピンで外部割込みを使うことでができますね。

これは二相ロータリーエンコーダ等の処理を行うのには非常に便利なわけです。

しかしattachInterruptは有能関数なのですがINTピン以外では有効にできないのはちょっと不便です。

まあ何が言いたいかっていうと強いエンコーダについてるZ相が読めないわけですね。

こういう時に外部割込みピンが無いと少々トリッキーな処理を書いたり
頭の悪い処理を描く必要性が出てしまいます。

INTピン増やしてぇ!!ってわけです。

実はAVRにはINTピン以外に外部割込み可能なPCINTピンがあります。
ぶっちゃけマイコンから飛び出てるIOポート全部ですね。

つまりIOピンのすべては外部割込みピンとして利用できるってわけです。
これはピン変化割り込み(PCINT)と言うのですが若干INTピンを使う方法と比べ制限が付きます。

それは
論理変化時化時にしか割り込めない
ピン個別ではなくポートごとに割り込んでしまう
という点です。

論理変化時にしか割り込めないと言うのは立ち上がり、立ち下りを判別できないってことですね。
これはハンドラ内でピンの状態を読めば解決できるのでそんなに問題にはならないです。
ピン個別ではなくポートごとに割り込んでしまうというのは欠点と言うより利点かもしれませんね
同ポート上の複数のピンで割り込めるかは知らない。マルチプレクサで束ねられてそうだから無理かもしれん

とまあこんな制限があります。この特性をうまく利用していくといいかもしれません。

実際にPCINT機能を使うには二つのレジスタをいじったりポートによってレジスタが違ったりし、更にそれを直接叩いてやるのは混乱を招きますし何やらArduinoっぽくありません

という訳で僕がArudino UNO用にArduino標準関数ライクなライブラリを書いておきました。

実は探すのが面倒だったから作ったというアホな理由

ちょろっと実験して動作確認した程度なのでちょっとした間違いは許して

では早速ヘッダ(PinChangeInterrupt.h)の方から
#pragma once
#ifndef PinChangeInterrupt_H
#define PinChangeInterrupt_H


#include <Arduino.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#ifndef EXTERNAL_NUM_INTERRUPTS
#define EXTERNAL_NUM_INTERRUPTS 3
#endif

#ifndef digitalPinToPCICR(p)
#define digitalPinToPCICR(p)    (((p) >= 0 && (p) <= 21) ? (&PCICR) : ((uint8_t *)0))
#endif

#ifndef digitalPinToPCICRbit(p)
#define digitalPinToPCICRbit(p) (((p) <= 7) ? 2 : (((p) <= 13) ? 0 : 1))
#endif

#ifndef digitalPinToPCMSK(p)
#define digitalPinToPCMSK(p)    (((p) <= 7) ? (&PCMSK2) : (((p) <= 13) ? (&PCMSK0) : (((p) <= 21) ? (&PCMSK1) : ((uint8_t *)0))))
#endif

typedef void (*void_function_pointer)(void);
static void nop(void) {}
static volatile void_function_pointer interrupt_function[EXTERNAL_NUM_INTERRUPTS] = {nop, nop, nop};

enum { PC_INT_0, PC_INT_1, PC_INT_2 };

class PinChangeInterrupt {
  public:
    PinChangeInterrupt();
    ~PinChangeInterrupt();
    void attachInterrupt(uint8_t pin, void (*userFunc)(void));
    void detachInterrupt();

  private:
    uint8_t interrupt_pin;
    uint8_t interrupt_pin_bit_mask;
    uint8_t *interrupt_pin_port;
};
#endif


ソース(PinChangeInterrupt.cpp)の方
#include "PinChangeInterrupt.h"

#define _ISR(vect, interrupt) ISR(vect) {interrupt_function[interrupt]();}

PinChangeInterrupt::PinChangeInterrupt() {
  interrupt_pin = 0;
  sei();
}

PinChangeInterrupt::~PinChangeInterrupt() {
  PinChangeInterrupt::detachInterrupt();
}

void PinChangeInterrupt::attachInterrupt(uint8_t pin, void (*userFunc)(void)) {
  intterrput pin;
  uint8_t pcicr_bit_mask = digitalPinToPCICRbit(interrupt_pin);
  uint8_t pcmsk_bit_mask = digitalPinToPCMSKbit(interrupt_pin);

  //pinMode(interrupt_pin, INPUT);
  *(digitalPinToPCICR(interrupt_pin)) |= _BV(pcicr_bit_mask);
  *(digitalPinToPCMSK(interrupt_pin)) |= _BV(pcmsk_bit_mask);
  interrupt_function[pcicr_bit_mask] = userFunc;
}

void PinChangeInterrupt::detachInterrupt() {
  uint8_t pcicr_bit_mask = digitalPinToPCICRbit(interrupt_pin);
  uint8_t pcmsk_bit_mask = digitalPinToPCMSKbit(interrupt_pin);

  *(digitalPinToPCMSK(interrupt_pin)) &= ~_BV(pcmsk_bit_mask);
  interrupt_function[pcicr_bit_mask] = nop;
}

_ISR(PCINT0_vect, PC_INT_0)
_ISR(PCINT1_vect, PC_INT_1)
_ISR(PCINT2_vect, PC_INT_2)

使い方はほぼattachInterruptと同じです。違うのは割り込みモードの指定ができないだけ

適当にLEDが0番ピンの立ち上がりで1番ピンがトグルするサンプルコードを作ってみました。
#include <PinChangeInterrupt.h>

#define PCINT16_PIN   0
#define LED_BLINK_PIN 1

PinChangeInterrupt pcint;

void blink(void) {
  volatile static bool blink_flg = true;
  if(digitalRead(PCINT16_PIN))digitalWrite(LED_BLINK_PIN, blink_flg = !blink_flg ); //立ち上がりモード
}

void setup() {
  pinMode(PCINT16_PIN, INPUT); //忘れずに
  pinMode(LED_BLINK_PIN, OUTPUT);
  pcint.attachInterrupt(PCINT16_PIN, blink); //void(void)な関数のポインタをぶち込もう
}

void loop() {

}
立下り立ち上がりの判断は割り込みハンドラ内でピン状態を読めばできますよ。
ただどっちでも割り込んじゃうけどね。

このライブラリはATmega328Pや168Pが実装されたUNOやnanoでしか使えない欠陥品です

マクロをいじればMEGAやらLeonardoにも対応できるのでいつかやってみようと思います。




drttx at 00:01|PermalinkComments(0)弱電 | マイコン