MKのソフトウェアマルチタスクタイマー

MKをプログラミングする際に実装されるアルゴリズムのさまざまな種類の複雑さでは、定期的な循環タスクなどが常に発生します。 高い精度が必要なものもあれば、そのような基準を持つ必要のないものもあります。 MKにはかなりの量のハードウェアタイマー、たとえばSTM32F4があります(14個もあります)。これはSysTick(システム)をカウントしていませんが、幸いなことに2〜3個です。たとえば、同じPIC16です。

このような急ぎのタイムクリティカルなタスクを解決するには、ハードウェアのいずれかに基づいてソフトウェアタイマーを適用することが可能であり、必要です。 しかし、まず最初に...

序文の代わりに


Googleにこれについてどう思うか尋ねてください。

ためらうことなく、検索エンジンは次のようなものを生成します。

  1. ソフトウェアタイマー。 パート1
  2. マルチタスクソフトウェアタイマー。
  3. マルチタスクソフトウェアタイマー、バージョン2.0
  4. ソフトウェアタイマー。 HALアプリケーション

これらの記事は、アルゴリズムの実装の出発点として役立ちました。

まえがき


PCS開発者として、私はしばしばさまざまな企業のPLCをプログラミングしています。 ソフトウェアタイマー用のライブラリは、開発環境のすべてのPLC用に準備されています。 それらのほとんどすべてに同じ機能があります。 PLCプログラムでのタイマーの使用は多くの種類のタスクで必要です。それらすべてを説明するのは意味がないので、ACS TPからいくつかの例を示します。これらの例はこのモジュールに「大まかに移植」されます。

ミニTK


このモジュールではどのようなタイマーが必要ですか? 4つのタイプを選択しました。


最初の2つのタイマーは、産業用制御システムとPLCプログラミングから明らかに移行されました。 最後の2つは、MKのハードウェアタイマーの論理的な拡張です。 タイマーの各タイプを個別に検討してください。

遅延タイマー

このようなタイマーの一般的な例は、時間リレーの実装です。 高(アクティブ)レベル信号がタイマーの開始(制御)入力に到着し、タイマーは時間のカウントを素直に開始します。その後、入力にアクティブレベルがあると、出力もアクティブ状態に移行します。 タイマー入力で信号を削除するとすぐに、出力も非アクティブになります。 以下にタイミングチャートを示します。


MKの世界では、長いキーストロークなどを検出するために「ドライ」コンタクトのアンチバウンスを提供する必要がある場合、このタイマーはアプリケーションを見つけます。 このタイマーの範囲は明らかにこれに限定されません。

オフディレイタイマー

タイマーは基本的に最初のものと同じですが、その逆も論理的です。 アクティブ入力、アクティブおよびタイマー出力中。 入力がロー(アクティブではない)になるとすぐに、タイマーはカウントダウンを開始し、最後に出力もゼロにリセットします。 これが彼のタイミングチャートです。


これは、「親」と同時にではなく、少し後にタスクを停止する必要がある場合に発生します。 MKをプログラミングするとき、このタイプのタイマーは誰かにとっては異様に思えるかもしれませんが、彼らが言うように、そうするように。

サイクルタイマー

まあ、すべてが些細です。 このタイマーの各オーバーフローは、イベントをトリガーする必要があり、それに応じて、このタスクまたはそのルーチンタスクを実行します。 慣性環境センサーへの問い合わせ、LEDの点滅、MCが「順序どおり」であることを示す、さまざまなフィルターの等時間間隔の編成など 下のタイミングチャート。



シングルタイマー

このタイプのタイマーは、トリガーされた後に自動的にオフになる(停止する)ことを除いて、周期的なコピーのほぼ完全なコピーです。 つまり、彼らはそれを開始し、その遅延をカウントし、フラグを設定し(イベントを示します)、それ自体を停止しました。 これが彼のチャートです。



現在、これらはすべてコードに実装されています。

SwTimerモジュール


モジュールの名前はそれ自体を表しています。 このモジュールは、ヘッダーとソートの2つのファイルで構成されています。

ヘッダーを考慮してください:

#ifndef SW_TIMER_H_ #define SW_TIMER_H_ #define SwTimerCount 64 //   /*  */ typedef enum { SWTIMER_MODE_EMPTY, SWTIMER_MODE_WAIT_ON, SWTIMER_MODE_WAIT_OFF, SWTIMER_MODE_CYCLE, SWTIMER_MODE_SINGLE } SwTimerMode; /*  */ typedef struct { unsigned LocalCount:32; //    unsigned Count:24; //    unsigned Mode:3; //  unsigned On:1; //  unsigned Reset:1; //      unsigned Off:1; //  unsigned Out:1; //  unsigned Status:1; //  }SW_TIMER; #if (SwTimerCount>0) volatile SW_TIMER TIM[SwTimerCount]; //   #endif void SwTimerWork(volatile SW_TIMER* TIM, unsigned char Count); //     void OnSwTimer(volatile SW_TIMER* TIM, SwTimerMode Mode, unsigned int SwCount); //     unsigned char GetStatusSwTimer(volatile SW_TIMER* TIM); //   #endif /* SW_TIMER_H_ */ 

このヘッダーには、アレイ内のソフトウェアタイマーの数を示す定義が含まれます。 Enumは、タイマー動作モードの「意識的な」記述のために宣言されています。 ソフトウェアタイマーの構造は次のとおりです。 注目したいのは、タイマー自体が24ビットであることだけです。 この構造では、ソフトウェアタイマーが8バイトのスペースを占有できます。 1 msのハードウェアタイマーオーバーフローを持つ24ビットにより、4.66時間または16,777秒の遅延を達成できます。 それで十分です。

機能はほとんどありません。

モジュール全体の動作を保証する主な機能:

 void SwTimerWork(volatile SW_TIMER* TIM, unsigned char Count); 

この関数は、ハードウェアタイマーがいっぱいになったときに呼び出す必要があります。 モジュールのアルゴリズム全体を整理します。 コードを見てください:

 void SwTimerWork(volatile SW_TIMER* TIM, unsigned char Count){ unsigned short i=0; for (i=0; i<Count; i++){ if (TIM->Mode==SWTIMER_MODE_EMPTY) { TIM++; continue; } if (TIM->Mode==SWTIMER_MODE_WAIT_ON){ //     if (TIM->On){ if (TIM->LocalCount>0) TIM->LocalCount--; else { TIM->Out=1; TIM->Status=1; } } else { TIM->Out=0; TIM->LocalCount=TIM->Count-1; } } if (TIM->Mode==SWTIMER_MODE_WAIT_OFF){ //     if (TIM->On){ TIM->Out=1; TIM->Status=1; TIM->LocalCount=TIM->Count-1; } else { if (TIM->LocalCount>0) TIM->LocalCount--; else TIM->Out=0; } } if (TIM->Mode==SWTIMER_MODE_CYCLE){ if (TIM->Off){ if (TIM->On){ TIM->Off=0; if (TIM->LocalCount>0) TIM->LocalCount--; } } else{ if (TIM->LocalCount>0) { TIM->LocalCount--; TIM->Out=0; } else { TIM->Out=1; TIM->Status=1; TIM->LocalCount=TIM->Count-1; } } if (TIM->Reset){ TIM->LocalCount=TIM->Count-1; TIM->Out=0; TIM->Status=0; } } if (TIM->Mode==SWTIMER_MODE_SINGLE){ if (TIM->Off){ if (TIM->On){ TIM->Off=0; if (TIM->LocalCount>0) TIM->LocalCount--; } } else{ if (TIM->LocalCount>0) { TIM->LocalCount--; TIM->Out=0; } else { TIM->Out=1; TIM->Status=1; TIM->LocalCount=TIM->Count-1; TIM->Off=1; } } if (TIM->Reset){ TIM->LocalCount=TIM->Count-1; TIM->Out=0; TIM->Status=0; } } TIM++; } } 

ループでは、タイマーの配列全体を調べます。 タイマーが空= EMPTYの場合、次のタイマーに進みます。 タイマーの動作モードに応じて、独自のロジックが編成されます。

この関数の呼び出しは、ハードウェアタイマーの割り込みと、フラグによるメインプログラムのループの両方から構成できます。
STM32の割り込みの例を次に示します。

 /* USER CODE BEGIN 0 */ #include "sw_timer.h" /* USER CODE END 0 */ void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); HAL_SYSTICK_IRQHandler(); /* USER CODE BEGIN SysTick_IRQn 1 */ SwTimerWork(TIM,SwTimerCount); /* USER CODE END SysTick_IRQn 1 */ } 

しかし、メインループから:
 /* USER CODE BEGIN 0 */ extern uint8_t FlagSwTimer; /* USER CODE END 0 */ void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); HAL_SYSTICK_IRQHandler(); /* USER CODE BEGIN SysTick_IRQn 1 */ FlagSwTimer=1; /* USER CODE END SysTick_IRQn 1 */ } /* */ while(1){ if (FlagSwTimer){ #if (SwTimerCount>0) //    sw_timer.c SwTimerWork(TIM,SwTimerCount); #endif FlagSwTimer=0; } 

その他の機能:

 void OnSwTimer(volatile SW_TIMER* TIM, SwTimerMode Mode, unsigned int SwCount); 

この関数は、特定のタイマーの動作モードを設定し、必要な遅延を設定します。

 unsigned char GetStatusSwTimer(volatile SW_TIMER* TIM); 

タイマーステータスのステータスを読み取ります。 指定されたタイマーが空の場合、-1を返します。

申込み


 #define I_DI_READ_0() HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) //  OnSwTimer(&TIM[0],SWTIMER_MODE_WAIT_OFF,1000); OnSwTimer(&TIM[1],SWTIMER_MODE_SINGLE,1000); OnSwTimer(&TIM[2],SWTIMER_MODE_WAIT_ON,1000); OnSwTimer(&TIM[3],SWTIMER_MODE_CYCLE,1000); TIM[3].Off=0; TIM[3].On=1; //   TIM[1].Off=0; TIM[1].On=1; //   

いくつかのタイマーを異なるモードで初期化します。 メインループでは、自由裁量で使用します。

  while (1){ TIM[2].On=I_DI_READ_0(); // ,       HAL_GPIO_WritePin(GPIOD,GPIO_PIN_15,TIM[2].Out); //  ,   TIM[0].On=I_DI_READ_0(); //  ,        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_14,TIM[0].Out); // ,   if (GetStatusSwTimer(&TIM[3])){ HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_13); //   ,   } if (GetStatusSwTimer(&TIM[1])){ HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12); //   ,   } TIM[3].Reset=I_DI_READ_0(); //  ,    } 

サイクリックまたはシングルタイマーを停止する必要がある場合は、有効化ビットをリセットし、無効化ビットを設定する必要があります。

  TIM[3].On=0; TIM[3].Off=1; 

Onビットを設定して再度有効にします。

ステータスの読み取りは、関数として、またはビットを読み取ることで直接行うことができます。

  if (GetStatusSwTimer(&TIM[1])) //  if (TIM[3].Status){ //  TIM[3].Status=0; // ,         //   ,       /*UserCode*/ } 

タイマーが不要になった場合、タイマーを停止するだけでなく、削除する、つまりEMPTYモードに切り替えると、タイマーの配列の処理機能の実行時間を短縮できます。 これを行うには、SWTIMER_MODE_EMPTYモードでタイマー準備関数を呼び出します。 または、これを直接示します。

  OnSwTimer(&TIM[3],SWTIMER_MODE_EMPTY,0); //  TIM[3].Mode=SWTIMER_MODE_EMPTY; //  

最初の2つのタイマーと2番目の2つのタイマーは、不要な機能などを生成しないように、1つの構造で意味がわずかに異なります。 ソフトウェアタイマーモジュールは、ここからダウンロードできます

この記事は、 STM32レッスンの改訂資料です。 STM32F4プログラミングレッスン。 レッスン番号4。ソフトウェアマルチタスクタイマーSTM32F4。 私の著者。

このソフトウェアマルチタスクタイマーモジュールの機能を示すビデオ:

それがおそらくすべてです。

Source: https://habr.com/ru/post/J273077/


All Articles