15
7

More than 3 years have passed since last update.

組み込み開発における機構制御について(オレ流)

Last updated at Posted at 2020-02-03

RTOSのタスク設計の前に

RTOSのタスク設計の前に私の設計の大前提を書いておきます。
機構制御プログラムは周期タイマーの割り込み処理で行う 事です。
最大理由は一つにメインルーチン、またはタスクのルーチン内で機構制御を行うと 制御時間がいい加減 になる事です。
もう一つはプログラムが煩雑になる事です。

よく見かける方法

私がよく見かけるのはメイン関数のループ内で機構制御を行う人です。
しかも、ポートや複数の機能を持つ特殊レジスタを扱うのに割り込み禁止にせずに特殊レジスタを操作したり
(もちろん割り込みルーチン内でも特殊レジスタを操作してる。)

※”メイン関数のループ内”というのは表現は既に組み込みプログラマーの人には分かるでしょうが下記のようなコードのwhileのループ内のプログラムの事です。

int main(
{
   hw_init_proc();  /* ハードウェア初期化処理 */

   while(1)
   {
        power_up_proc();        /* パワーオン時の処理 */
        while(1)
        {
             main_loop_proc();  /* なんかごちゃっとしたメイン処理 */
        }      
   }
}

何故?ダメ?

何故、機構制御プログラムをメイン関数のループ内にプログラムで記述すると、モーターを正転させて、あるセンサに到達した時にモーターを停止しなければならい時に、メインプログラムの中の状態によっては大きなメモリ領域をクリアなりセットなりするルーチンを呼び出したりしてると、そのプログラムが走ってる時はセンサの監視はしてない為に通り過ぎて数十ミリsec後に行う為、その後に停止するという、その数十ミリsecが致命的な欠陥となりうるからです。

では、サンプルを見て下さい。

unsigned char image_area[1000];

while(1)
{
    if (hoge_sw == 1)
    {
       motor1_cw();      /* モーター1正転 */
       motor_mode = 1;
    }

    if (motor_mode == 1)
    {
        if (sens1_state == ON)
        {
            memset(&image_area[0], 0x0ff, sizeof(image_area));
            motor_mode = 2;
        }
   }
   else if (motor_mode == 2)
   {
        if (sens2_state == ON)
        {
             motor2_cw();    /* モーター2正転 */
             motor_mode = 3;
        }
   }
   else if (motor_mode == 3)
   {
      ・・・・・


   else if (motor_mode == 10)
   {
      if (sens5_state == ON)
      {
          motor1_stop();    /* モーター1停止 */
          motor_mode = 0;
      }
   }
}    

上記の様なサンプルだとループの中でmoror_mode = 1sens1_state = OFFの時とsens1_mode = ONの時でループの時間が変わってきます。

更にはセンサ2がセンサ1の近くにあったとすると、モーター2が正転するまでの時間が変わってきます。
(当然、その間に通信割り込み、その他の割り込みが発生すると仮定して)

これでは正確な時間でアクチュエータ類が制御できない事になります。
最近のCPUでは1000バイトのメモリーセットなんかで、そんなに時間は必要無いと否定してくる人がいますが、この微妙な時間のバラツキが市場では誤動作に繋がる事が往々にしてあります。

ではどうすれば良いのか

先にも記載した通りに周期タイマーの割り込み処理で制御します。私は周期タイマーは最低でも1msの割り込みを使用しています。荒いタイマーだと制御のタイミングも荒くなるからです。
それともう一つはモーターという単位では扱わずに、制御単位でシーケンスを考えます。

シーケンスの例とサンプルコードを下記に記載します。

シーケンス シーケンス1 シーケンス2  シーケンス3 ・・・
状態 モーター1正転
センサ1- ON待ち
センサ2 - ON 待ち モーター1
パルス数 200以上
・・・・
センサ1ON メモリクリアフラグON
シーケンス2へ
モーター2正転
シーケンス3
モーター1停止 ・・・・
void int_timer0_proc()
{
   switch( paper_feed_seq )
   {
   case 1:
        int_paper_feed_seq1_proc();
        break;
   case 2:
        int_paper_feed_seq2_proc();
        break;
   case 3:
        int_paper_feed_seq3_proc();
        break;
             
             
             
   default:
      paper_feed_seq = 0;
      break;
}

ここで注意しなければならない事は、この処理が周期タイマーの周期以内に収まっている事です。
1msの周期なのに、制御処理が1.5msになっていては周期タイマーもまた破綻してしまいます。

この他にも割り込みの優先順位や多重割り込みで他の割り込みが入ってきた事も考える必要があります。
私は大体500ns以下くらいならOKにしていますが、他の割り込みで変な待ち(割り込み内で送信完了待ちのフラグが落ちるの待っている等の処理)が無いかどうか等、テストポートや空きのLEDを使ってトルグさせてオシロスコープ等を見て確認しています。

※モーターのパルス割り込み等では制御は行わずにパルスのカウントのみのプログラムを記載したりしています。

割り込みレジスタのフラグや変数の状態を監視する場合

この場合。当然ながら割り込み内でループで待つような事をしてはダメです。

例として割り込みレジスタの状態を待つ場合は下記のようにして条件が成立しない場合には同じシーケンスが走るようにします。


/* 良い例 */
void int_paper_feed_seq6_proc()
{
    if ((sfc_reg1 & 0x02) == 1)
    {
       paper_feed_seq = 7;   /* シーケンス7へ */
    }
    else
    {
       paper_feed_seq = 6;   /* 同シーケンスへ */
    }
}


/* 悪い例 */

void int_paper_feed_seq6_proc()
{
    while ((sfc_reg1 & 0x02) == 0);
    paper_feed_seq = 7;   /* シーケンス7へ */
}


まとめ

  1. 機構制御プログラムは周期タイマーの割り込み処理で行う。
  2. 機構制御のシーケンス処理プログラムは短くする。
  3. 割り込みレジスタのビットやフラグ変数をループで待つ処理を入れたりはしない。

現状で思いつく事を書いてみました。

次回はこれを踏まえてのタスク設計をテーマにしたいと思います。

15
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
7