Arduino Mega 2560 LESSON:9 サーボ

提供:yonewiki

Arduino Mega 2560に戻る。

概要

 サーボを使った課題です。


 接続図は以下のとおりです。


 コードは以下のようなものです。

//www.elegoo.com
//2018.12.19
#include <Servo.h>
Servo myservo;

void setup(){
  myservo.attach(9);
  myservo.write(90);// move servos to center position -> 90°
} 
void loop(){
  myservo.write(90);// move servos to center position -> 90°
  delay(500);
  myservo.write(30);// move servos to center position -> 60°
  delay(500);
  myservo.write(90);// move servos to center position -> 90°
  delay(500);
  myservo.write(150);// move servos to center position -> 120°
  delay(500);
}

 サーボライブラリを使った角度の調整です。サーボの位置を瞬時に角度を変えていくという内容のコードですね。サーボの舵の部分を自分に向けて置いた場合の表現で以後、話を進めます。左とか右とか言う表現をしますが、自分に舵を向けた状態から見て左と右とか言ってるんだなぁって思って下さい。


 サーボは赤と黒にあたる線で電源を供給します。赤は+5Vに黒にあたる茶色の線は、GNDへ接続します。サーボの信号線である橙色の線では、パルス波を受け取り角度を調整しています。ライブラリを使わずにまずはパルス波を送ってみましょう。


// SG90サーボ制御
// Servoライブラリ不使用
// タイマー割り込み処理なし

const int servoPin = 9;  // サーボ信号線:D9

void setup() {
  pinMode(servoPin, OUTPUT);
  digitalWrite(servoPin, LOW);
}

void loop() {

  // サーボ信号をHIGHにする
  digitalWrite(servoPin, HIGH);

  // 1500マイクロ秒待つ(中央位置)
  delayMicroseconds(1500);

  // サーボ信号をLOWにする
  digitalWrite(servoPin, LOW);

  // 次のパルスまで待つ
  delayMicroseconds(18500);
}


 このように1500μsecのONと18500μsecのOffを繰り返すパルス波を送ると概ね真ん中の位置でキープしますが、少し不安定であるためサーボがピクピク震えます。1000μsecのONと19000μsecだと左に振れた状態をキープします


  • 1500μsecのONと18500μsecのOff:真ん中
  • 1000μsecのONと19000μsecのOff:左
  • 2000μsecのONと18000μsecのOff:右


 以下のようにすると、左右にサーボがうごきます。左も右も45度くらいしか動かないので、完全に横に向いた状態にはならないですね。


// SG90サーボ制御
// Servoライブラリ不使用
// タイマー割り込み処理なし
// 左右振り

const int servoPin = 9;  // サーボ信号線:D9

void setup() {
  pinMode(servoPin, OUTPUT);
  digitalWrite(servoPin, LOW);
}

void loop() {

  for(int i = 1000; i < 2000; i++){
    // サーボ信号をHIGHにする
    digitalWrite(servoPin, HIGH);

    // 1500マイクロ秒待つ(左位置から右位置へ)
    delayMicroseconds(i);

    // サーボ信号をLOWにする
    digitalWrite(servoPin, LOW);

    // 次のパルスまで待つ
    delayMicroseconds(19000 - i);
  }
  for(int i = 2000; i > 1000; i--){
    // サーボ信号をHIGHにする
    digitalWrite(servoPin, HIGH);

    // 1500マイクロ秒待つ(右位置から左位置へ)
    delayMicroseconds(i);

    // サーボ信号をLOWにする
    digitalWrite(servoPin, LOW);

    // 次のパルスまで待つ
    delayMicroseconds(19000 - i);
  }

}


 サーボライブラリを使わなくても動かすこと自体はできますが、これでは途中で処理を入れる都度、パルス幅が変わって位置が不安定になっていきます。そこで割り込みという技術を使います。


 以下のようにライブラリを使って真ん中に設定するとものすごく安定しています。舵をさわってもほとんど振動を感じません。


#include <Servo.h>

Servo myservo;

void setup() {
  myservo.attach(9);
  myservo.writeMicroseconds(1500);
}

void loop() {
}


 上記のコードは非常に安定します。


void setup() {
  pinMode(servoPin, OUTPUT);
  digitalWrite(servoPin, LOW);
}

void loop() {
  noInterrupts();

  digitalWrite(9, HIGH);
  delayMicroseconds(1500);
  digitalWrite(9, LOW);

  interrupts();

  delayMicroseconds(18500);
}


 上記のようなコードにすると少し改善されます。舵を触るとかすかに振動を感じます。チャットGPTによるとこれは、


 Arduino Mega2560の標準環境では、Timer0のオーバーフロー割り込みが代表例です。Timer0は通常、約1.024msごとにオーバーフローし、millis() や micros() などの時間管理に使われます。とのことでしたので、delayMicrosecondsだけで制御してもTime0のオーバーフローによる割り込みが入っていたのが、noInterrupts();という命令によって割り込みが禁止された期間が作られたことによって少しだけ安定したのではないかという見立てです。なんだかよくわからないことを言われているように感じるでしょうけれど、とにかく小手先の割り込み禁止という手法をもってしてもサーボライブラリの安定性に負けているコードであったということです。


 では、どうすればいいのかという解決編に近づいていきたいと思います。


#include <avr/interrupt.h>

const uint8_t servoPin = 9;

// HIGH期間:1500µs(中央位置)
const uint16_t pulseWidthUs = 1500;

// 信号周期:20000µs(50Hz)
const uint16_t periodUs = 20000;

// 次の割り込みでHIGHにするか、LOWにするか
volatile bool nextHigh = true;

void setup() {
  pinMode(servoPin, OUTPUT);
  digitalWrite(servoPin, LOW);

  noInterrupts();

  // Timer5の設定を初期化
  TCCR5A = 0;
  TCCR5B = 0;
  TCNT5 = 0;

  // 16MHz / 8 = 2MHz
  // 1カウント = 0.5µs
  OCR5A = pulseWidthUs * 2 - 1;

  // CTCモード(OCR5A一致でカウンタをクリア)
  TCCR5B |= (1 << WGM52);

  // 比較一致A割り込みを有効化
  TIMSK5 |= (1 << OCIE5A);

  // プリスケーラ8でタイマー開始
  TCCR5B |= (1 << CS51);

  interrupts();
}

// Timer5比較一致A割り込み
ISR(TIMER5_COMPA_vect) {

  if (nextHigh) {
    digitalWrite(servoPin, HIGH);

    // 次はHIGH期間の終了時に割り込み
    OCR5A = pulseWidthUs * 2 - 1;

    nextHigh = false;
  }
  else {
    digitalWrite(servoPin, LOW);

    // 次はLOW期間の終了時に割り込み
    OCR5A = (periodUs - pulseWidthUs) * 2 - 1;

    nextHigh = true;
  }
}

void loop() {
  // サーボ信号はTimer5割り込みが生成する
  // ここでは何もしない
}


■<avr/interrupt.h>

C:\Users\(UserID)\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include\avr


Arduino Mega 2560 LESSON:9 サーボ interrupt.h


■<avr/io.h>

C:\Users\(UserID)\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include\avr


Arduino Mega 2560 LESSON:9 サーボ io.h


 ながくなってしまいました。

 

Arduino Mega 2560に戻る。