Arduino에서 일정 단위 시간(프레임)마다 처리 진행

예를 들면 10ms 주기로 처리를 제어하고 싶지만, 인터럽트 처리를 사용할 수 없는 때

할 일



로봇 제어 등으로 밀리초 단위로 처리를 진행하고 싶을 때가 있습니다.
인터럽트 처리를 사용하는 것도 손이지만 I2C와 인터럽트 처리의 동거는 그대로는 할 수 없습니다.
또 delay() 를 사용하면 거기서 처리가 멈추어 버려 이것도 잘 작동하지 않습니다.
그래서 millis()로 취득한 Arduino의 내부 시계 시간을 이용하여 프레임 단위로 처리가 진행되도록 합니다.

개념



예를 들어 1프레임을 10ms(밀리초)로 정해, 프레임 단위로 진행해 가는 시계(sframe)가 있으면 가상합니다.
계산 처리가 1프레임 안에 들어가도록 하고 싶습니다만, 처리가 빨리 종료했을 경우는 남은 시간을 루프로 소화해, 시간내에 끝나지 않으면 다음의 프레임으로 장 엉덩이를 맞추도록 합니다.
millis()로 절대 시각을 취득해, 적산되어 가는 프레임 단위 시계에 대해서, 처리가 예정보다 진행되고 있는지 늦어지고 있는지를 감시합니다.


스케치



※코멘트란에 의해 좋은 코드를 받고 있습니다. (2021.3.12 추가)

처음 게시한 스케치

Arduino 시스템


// 16MHz動作のArduionoを想定 (Arduiono UNO, micro等)

//変数の準備
long frame_ms = 10;// 1フレームあたりの単位時間(ms)
long sframe = (long)millis(); // フレーム管理時計の時刻 schaduledなflame数
long curr= (long)millis(); // 現在時刻を取得


void setup() {
  Serial.begin(115200); //115200bpsでシリアル通信を開始
}


void loop() {
  sframe = sframe + frame_ms;//フレーム管理時計を1フレーム分進める


  // ここから周期処理
  // 内容は何でもよいが、ここでは1秒毎にミリ時刻をシリアル出力。
  for (long i = 0; i <= 200; i++) {// ここ数値(200)で1フレームあたりの負荷を可変。230ぐらいで飽和。
    curr = (long)millis(); // 現在時刻を更新
    if ((curr % 1000) == 0) {//現在時刻が1000msで割り切れたらシリアルに表示する
      Serial.print("millis:");
      Serial.println(curr);
      delayMicroseconds(800);//この数値を減らすと時刻を複数回表示するようになる
    }
    delayMicroseconds(5);//この数値はAdruinoの性質上3以下にしない方がよい
  }
  // 周期処理ここまで


  // この時点で1フレーム内に処理が収まっていない時の処理
  curr= (long)millis(); // 現在時刻を更新
  if (curr > sframe) { // 現在時刻がフレーム管理時計を超えていたら何らかのアラートを出す
    //この例ではシリアルに遅延msを表示
    Serial.print("*** processing delay :");
    Serial.println(curr - sframe);
  }

  // 余剰時間を消化する処理。時間がオーバーしていたらこの処理を自然と飛ばす。
  while (curr < sframe) {
    curr = (long)millis();
  }

}



현재 사용중인 처리 세트

Arduino 시스템
// 周期処理用変数
const int err_led = 2; //処理遅延を識別するLEDのピン設定
const unsigned long frame_ms = 10;// 1フレームあたりの単位時間(ms)
unsigned long merc; // フレーム管理時計用
unsigned long curr; // 現在時刻取をミリ秒で取得する用
unsigned long curr_micro; // 現在時刻をマイクロ秒で取得する用
int framecount; // 現在フレーム何周期目かのカウント用


void setup() {
  Serial.begin(115200);//シリアルモニター設定

  // 周期処理のタイマー初期設定 ////////////////////////////////////////////
  merc = millis(); // フレーム管理時計用の初期時刻
}


void loop() {


// ( 〜この中で周期処理を実施〜 )


  // 周期処理フッタ開始 //////////////////////////////////////////////////
  // この時点で1フレーム内に処理が収まっていない時の処理
  curr = millis(); // 現在時刻を更新
  if (curr > merc) { // 現在時刻がフレーム管理時計を超えていたらアラート
    ////Serial.print("*processing delay : ");
    ////Serial.println(curr - merc);//遅延フレーム数を表示
    digitalWrite(err_led, HIGH);//処理落ちが発生していたらLEDを点灯
  }
  else {
    digitalWrite(err_led, LOW);//処理が時間内に収まっていればLEDを消灯
  }
  framecount = framecount + 2;//フレームカウントを加算(制御などに使う)
  if (framecount > 10000) {
    framecount = 0; //フレームカウントのリセット
  }

  // 余剰時間を消化する処理。時間がオーバーしていたらこの処理を自然と飛ばす
  curr = millis();
  curr_micro = micros(); // 現在時刻を取得
  //Serial.println(merc * 1000 - curr_micro); //余剰時間をμ秒単位で表示
  while (curr < merc) {
    curr = millis();
  }
  merc += frame_ms;//フレーム管理時計を1フレーム分進める
  // 周期処理フッタ終了 //////////////////////////////////////////////////

}

깨달은 것



millis() 는 unsigned 의 변수이므로 감산을 할 수 없습니다.
거기서, 보통의 long형에 캐스트 변환해 수치를 취득하고 있습니다.
그래서 시계는 약 25일에 오버플로우하게 됩니다.
millis() 의 unsigned 형의 변수는 결과가 부가 되지 않는 것은 감산할 수 있습니다. (2021.3.12 추가)

사용법



실제로 사용하는 경우는 주기 처리를 스케치하고, 그 주기 처리가 처리 속도적으로 괜찮은지 어떤지를 경고로 확인하거나 해 사용합니다.
시리얼 출력은 그냥 시간이 걸리므로 경고를 LED 등으로 바꾸어도 좋을지도 모릅니다.
프레임 시계를 사용하여 조건 분기하면 10ms마다, 20ms마다, 500ms마다 등과 처리에 의해 실행 간격을 바꿀 수 있다고 생각합니다.

기타



지연시의 처리나 지연의 전진이 불필요한 경우에 인터럽트 처리를 사용할 수 있는 경우에는 인터럽트 처리가 편리합니다.
또, 처리를 일정 간격으로 실행하는 Metro라고 하는 라이브러리도 편리할 것 같습니다.
htps //w w. pjrc. 이 m/테엔 sy/td_ぃbs_메 t로. HTML
이번 프레임 처리와 함께 사용하면 더욱 편리할 것 같습니다.

말해주세요.



이상한 곳이 있으면 가르쳐주세요.

좋은 웹페이지 즐겨찾기