STM32、C ++およびFreeRTOS。 最初から開発。 パート1

はじめに



少し前まで、私の部門では、ファームウェアを開発する新しいソフトウェアエンジニアを見つけるのが難しいと感じていました。 経験豊富で賢い人は給料のレベルが好きではなく、私たちの街には若者はいません。 そのため、セントルイスのどこかに本社を置く勇敢なグローバル企業の後援の下、最初にインターンシップのために学生を募集し始め、その後、ソフトウェア開発に関する2つのコースを受講し、自分で最も「賢明な」コースを選択することにしました突然新しい従業員が必要になった場合。 これははるかに安く、最大数の応募者をカバーできます。

トピックから少し脱線します。前回は非常に長い時間プログラムしましたが、通常はC#でプログラムし、マイクロコントローラー(C言語のPIC16)での最後のグローバルプロジェクトは2007年に行われました。

そのため、最新のマイクロコントローラー、C ++言語、およびリアルタイムオペレーティングシステムを扱う必要がありました。

もちろん、すべてのプロジェクトはすでにRTOSを使用しており、C ++で記述されていますが、開発者としては参加しませんが、そのようなソフトウェアの開発プロジェクトを管理することで寄生に関与しています。



選択肢



私はすべてのことについて1か月を費やしました。 2015年6月の初めから2015年7月の初めまで、私は休暇をとっていたので、休暇の後は通常多くの仕事があります。 すべてを迅速かつ明確に行う必要がありました。

同僚と少し相談したところ、ARM Cortexのファッショナブルな方向が異なるコアであり、利用可能なデバッグボードから25ドルのOlimex STM32P152を注文できることがわかりました。 彼らは非常に早く到着しました-約2000ルーブルの価格で6枚のボード。 これらの料金は、このコースが実際に行われる大学のために私たちが購入したことに注意する価値があります。

画像



ベースライン:目的



まず第一に、どの情報をどのように学生に提供するかを決定する必要がありました。 私自身、主な目標は、STM32マイクロプロセッサ、C ++プログラミング言語、FreeRTOSリアルタイムオペレーティングシステムを使用して、要件やアーキテクチャの開発からコーディングまで、デバイス用のソフトウェア開発プロセス全体を示すことだと判断しました。



ベースライン:制限



したがって、このプロジェクトの目標は、FreeRTOSを使用してC ++で記述されたコーディング標準(ここでは説明しません)により、Olimex STM32P152デバッグボード用のデモアプリケーションを作成することです。

アプリケーションは、C ++言語に固有の複雑な構造を持たない、明確でシンプルで目立たないものでなければなりません。 アーキテクチャはUMLで記述する必要があります。



ベースライン:機能要件



SR0:デバイスは、3つのパラメーターを測定する必要があります(3つの変数があります): マイクロプロセッサー温度VDDA 電圧可変抵抗器による電圧

SR1:デバイスは、これらの変数の値をインジケーターに表示する必要があります。

SR2: マイクロプロセッサーの温度の単位は摂氏で、その他のパラメーターはボルトです。

SR3: ボタン1を押すと、 ディスプレイに次の測定変数の画面が表示されます。

SR4: ボタン1が押されると、 LED 1は状態を変更する必要があります

SR5: ボタン2を押すと、インディケーターは変数の表示モードを変数の常時表示から順次に変更する必要があります(1.5秒ごとに画面を変更します)。

SR6: ボタン2を押すと、 LED 2の状態が変わるはずです。

SR7: LED 3は1秒ごとに1回点滅します。



つまり、これらはすべてこのタスクの要件です。

タスクがそれほど難しくないことは明らかですが、指定された要件に従って、デバイスの開発プロセス全体をほぼ完全に表示できます。



開発:はじめに



そして、要件の準備ができたら、先へ進むことができます。 インフラストラクチャから始めましょう。 まず、IARでC ++のプロジェクトを作成しました。ここには新しいものはありません。 この記事では、すべてIARでのSTM32用C ++プロジェクトの作成について説明します。 ここでやめません。



開発:FreeRTOSのラッパー



C ++を使用する予定で、オペレーティングシステムがCで記述されているため、FreeRTOS用のC ++ラッパーが必要です。 理想的には、RTOSのほとんどに適合するようにラッパーを作成でき、プロジェクトではRTOSのタイプに依存できませんでしたが、これを気にせず、この状況では必要なオペレーティングシステム機能のみを選択しました、そして彼だけがそれらをラップし、メソッドの署名を愚かにコピーしました。

静的メソッドstatic void run(void * parameters)も追加されました。 これは、タスクの作成時に使用されるポインターの関数です。

タスクでクラスインスタンスメソッドを呼び出せるようにするために、仮想関数virtual void run(void)= 0;でiActiveObjectインターフェイスが作成されました。 タスクへのポインタを保持するグローバル属性。

iActiveObject.h
#include "types.h" //   class iActiveObject { public: virtual void run(void) = 0; void *taskHandle; };
      
      







タスク(アクティブ)になりたいオブジェクトは、このインターフェイスを継承し、run()メソッドを実装する必要があります。 このオブジェクトへのポインターは、パラメーターとしてラッパーのrun()関数に渡されます。

紙の上では、このように見えた:

画像

私はこの写真を実現する仕事を才能のある若い専門家に任せました。若い専門家はそれをうまく処理し、数日後にIAR 6.50でそのような空の作業草案を提出しました。

IAR 6.50のFreeRTOSラッパーを使用した空のプロジェクト

この記事から改善版のラッパーを入手できますCortex M4の「すべての」リアルタイムオペレーティングシステム用のC ++ラッパー



開発:一般的なアーキテクチャ



若いスペシャリストがラッパーをやっていたとき、ソフトウェアアーキテクチャについて疑問に思いました。 私自身、3つのパッケージを強調しました。

AHardware-機器(LED、インジケータ、ADCなど)を操作および制御するためのクラスを含むパッケージ

アプリケーション-アプリケーションレベルのクラスを含むパッケージで、設計上、ハードウェアについて何も知らないため、ラッパーが人間のスノーフレークによって開発されていなければ、変更なしで任意のマイクロコントローラーに移植できます。 この場合、そうではありません:)

画像

FreeRTOS-移植されたOSとそのラッパーを備えたパッケージ。

次の図を少し描いたところ、判明しました。

画像



開発:LED点滅



いつものように、開発は最も面白くて複雑なものから始まります:)-SR7要件の実装: LED 3は1秒に1回点滅するはずです。

最初のステップは、マイクロコントローラーのポートと周波数設定に対処することでした。 そして、最後のマイクロコントローラープロジェクトからの過去8年間で、多くの変化があり、簡単なことではないように思えました。 これらのセクションのデータシートを注意深く読んで、最終的にはすべてが非常に簡単なことを理解するために、(PIC 16よりも)設定がはるかに多いことを理解する必要がありました。

その結果、すべてのポート設定が__low_level_init()にスローされました。 そして一般的に、私はプログラムの作業中に変更されないすべての鉄の設定をこの機能に入れました。 main()の前、すべての変数が初期化され、グローバルクラスインスタンスコンストラクターが実行される前に呼び出されます。

LED用のポートの構成
  //  - //PE.10, PE.11 -  stat3  stat4 //PA.4, PA.5 -  stat1  stat 2 //  PE.10, PE.11, PA.4, PA.5  , c.174 CD00240194.pdf GPIOE->MODER |= GPIO_MODER_MODER10_0; GPIOE->MODER |= GPIO_MODER_MODER11_0; GPIOA->MODER |= GPIO_MODER_MODER4_0; GPIOA->MODER |= GPIO_MODER_MODER5_0;
      
      







さて、ハードウェアがセットアップされ、今度はペイントに座りました。今回はLED制御クラスです。 30分後、これが起こりました。

画像

そして、この奇跡の実装:

LedsDriver.h
 #include "types.h" //   tU8 #define LEDS_NUMBER 4 class cLedsDriver { public: explicit cLedsDriver(void); void ledOn(const tU8 led); void ledOff(const tU8 led); void ledToggle(const tU8 led); private: static tPort ledsPort[LEDS_NUMBER]; static const tU16 ledsPin[LEDS_NUMBER]; };
      
      







ledsdriver.cpp
 #include "ledsdriver.h" //     tLeds #include <stm32l1xx.h> // STM32 #include "susuassert.h" // for ASSERT #include "types.h" //   tPort, tU16, tU8 #include "bitutil.h" //      #define LED1_PIN GPIO_OTYPER_ODR_4 #define LED1_PORT GPIOA #define LED2_PIN GPIO_OTYPER_ODR_5 #define LED2_PORT GPIOA #define LED3_PIN GPIO_OTYPER_ODR_10 #define LED3_PORT GPIOE #define LED4_PIN GPIO_OTYPER_ODR_11 #define LED4_PORT GPIOE tPort cLedsDriver::ledsPort[LEDS_NUMBER] = {LED1_PORT, LED2_PORT, LED3_PORT, LED4_PORT}; const tU16 cLedsDriver::ledsPin[LEDS_NUMBER] = {LED1_PIN, LED2_PIN, LED3_PIN, LED4_PIN}; /******************************************************************************* * Function: constructor * Description: ******************************************************************************/ cLedsDriver::cLedsDriver(void) { } /******************************************************************************* * Function: ledOn * Description:    ******************************************************************************/ void cLedsDriver::ledOn(const tU8 led) { ASSERT(led < LEDS_NUMBER); SETBIT(this->ledsPort[led]->ODR, this->ledsPin[led]); } /******************************************************************************* * Function: ledOff * Description:    ******************************************************************************/ void cLedsDriver::ledOff(const tU8 led) { ASSERT(led < LEDS_NUMBER); CLRBIT(this->ledsPort[led]->ODR, this->ledsPin[led]); } /******************************************************************************* * Function: ledToggle * Description:     ******************************************************************************/ void cLedsDriver::ledToggle(const tU8 led) { ASSERT(led < LEDS_NUMBER); TOGGLEBIT(this->ledsPort[led]->ODR, this->ledsPin[led]); }
      
      







ここでも、LEDのロジックを制御するためのクラス(cLedsDirectorクラス)を作成するために少し描画する必要があります。 これがアクティブなクラス、つまり そのrun()関数はタスクで実行されます。 上で書いたように、すべてのアクティブなクラスはiActiveObjectインターフェイスを継承する必要があります。 したがって、画像も複雑に見えません。

画像

繰り返しますが、実装も簡単で、私のようなスノーフレークで実行できます。

ledsdirector.h
 #include "ledsdriver.h" // cLedsDriver #include "iactiveobject.h" // iActiveObject typedef enum { LD_led1 = 0, LD_led2 = 1, LD_led3 = 2, LD_led4 = 3, LD_none = 4 } tLeds; class cLedsDirector: public iActiveObject { public: explicit cLedsDirector(void); void run(void); private: cLedsDriver* pLedsDriver; };
      
      







ledsdirector.cpp
 #include "ledsdirector.h" //   #include "frtoswrapper.h" //  oRTOS #include "types.h" // l  #define LED_DELAY (tU32)500/portTICK_PERIOD_MS /******************************************************************************* * Function: constructor * Description:    cLedsDriver ******************************************************************************/ cLedsDirector::cLedsDirector(void) { this->pLedsDriver = new cLedsDriver(); } /******************************************************************************* * Function: runTask * Description:   . led3   ******************************************************************************/ void cLedsDirector::run(void) { for(;;) { oRTOS.taskDelay(LED_DELAY); this->pLedsDriver->ledToggle(LD_led3); } }
      
      







まあ、すべてが書かれているので、ささいなことは残っています-cLedsDirectorクラスのインスタンスとmain()でタスクを作成し、すべてがどのように機能するかを確認することです。

関数メイン()
 #define LEDSDIRECTOR_STACK_SIZE configMINIMAL_STACK_SIZE #define LEDSDIRECTOR_PRIORITY (tU32)2 //      ,  oRTOS   //     RTOS ,  -    //  ,     cRTOS oRTOS; .... void main(void) { cLedsDirector *pLedsDirector = new cLedsDirector(); oRTOS.taskCreate(pLedsDirector, LEDSDIRECTOR_STACK_SIZE, LEDSDIRECTOR_PRIORITY, "Leds"); oRTOS.startScheduler(); }
      
      







私たちはボード上で走ります、そして見よ-それは初めて動作します。 だから私はすべてを正しかった。 最初の要件の実装があります。 さらに進んでいきます。 それまでの間、プロジェクトを保存します

IAR 6.50のプロジェクト点滅LED



開発:ボタン



LEDが機能し、SR6の要件を実装できるようになりました。 ボタン2を押すとLED 2の状態を変更し、SR4: ボタン1を押すとLED 1の状態を変更する必要があります。

割り込みなしでボタンを作成することにし、一般にこのプロジェクトでは割り込みを使用しませんが、割り込みの使用を禁止するものはありませんが、それを決定しました。

手順に従って、クラスcButtonsDriverを描画します。

画像

ここで、最初にボタンが機能しなかったということ、または獲得したボタンが1つだけだったことを言わなければなりません。 2番目は機能しませんでした。 理解したので、1つのボタンがゼロに引き寄せられ、2番目のボタンが1つに引き寄せられることに気付きました。 したがって、クリックは異なる方法で決定されます。 うつ病を判断するために、追加の属性-buttonsTriggerを導入しました(元々このアーキテクチャにはありませんでした)。 0または1でボタンが押されたと見なされる値を示します。その後、すべてが時計のように機能し始めました。

実装は非常に簡単です。

buttonsdriver.h
 #include "types.h" //    tU16  tU8 #define BUTTONS_NUMBER 2 typedef enum { BS_buttonNotPressed = 0, BS_buttonPressed = 1 } tButtonState; class cButtonsDriver { public: explicit cButtonsDriver(); tButtonState getButtonState(const tU8 button); private: static tPort buttonsPort[BUTTONS_NUMBER]; static const tU16 buttonsPin[BUTTONS_NUMBER]; static const tBoolean buttonsTrigger[BUTTONS_NUMBER]; };
      
      







buttonsdriver.cpp
 #include "buttonsdriver.h" //     tLeds #include <stm32l1xx.h> // STM32 #include "susuassert.h" //for ASSERT #include "types.h" //  tPort, tU16, tU8 #define BUTTON1_PIN GPIO_OTYPER_IDR_13 #define BUTTON1_PORT GPIOC #define BUTTON2_PIN GPIO_OTYPER_IDR_0 #define BUTTON2_PORT GPIOA tPort cButtonsDriver::buttonsPort[BUTTONS_NUMBER] = {BUTTON1_PORT, BUTTON2_PORT}; const tU16 cButtonsDriver::buttonsPin[BUTTONS_NUMBER] = {BUTTON1_PIN, BUTTON2_PIN}; //   ,     0,   1 const tBoolean cButtonsDriver::buttonsTrigger[BUTTONS_NUMBER] = {FALSE, TRUE}; /******************************************************************************* * Function: constructor * Description: ******************************************************************************/ cButtonsDriver::cButtonsDriver() { } /******************************************************************************* * Function: getButtonState * Description:   ,    ******************************************************************************/ tButtonState cButtonsDriver::getButtonState(const tU8 button) { tButtonState eState = BS_buttonNotPressed; ASSERT(button < BUTTONS_NUMBER); //     ,    1,   0, //    ,    ,   //            //      . tBoolean isLogicalZero = !(this->buttonsPort[button]->IDR & this->buttonsPin[button]); if(isLogicalZero ^ this->buttonsTrigger[button]) { eState = BS_buttonPressed; } return eState; }
      
      







さて、ドライバーで行われていることは、今、定期的にボタンをポーリングするタスクについて考える必要があります。

写真はLEDのアクティブクラスに非常によく似ています。車輪を再発明するものは本当です。

画像



ボタンを押すと、それについて知る必要があるタスクに何らかの形で通知する必要があります。 これは、いくつかの方法で実行できます。イベントを介して、キューを使用するか、最新のfreeRTOSプロパティNotificationを使用します。

要件を分析します。 ボタンに触れると、2つのことを行う必要があります。まず、LED 1とLED 2の状態を変更し、次に、インジケーターの表示モードと画面を変更する必要があります。 問題は、キューとイベントは1つのタスクでしか受け入れられず、2番目のタスクはそれを認識できず、アーキテクチャが複雑になることです。 イベントをリダイレクトする必要があります。たとえば、LEDのタスクはイベントまたはキューをボタンからインジケータータスクにリダイレクトする必要があります。 これに関する何かが私に合わなかったので、通知を特定のタスクにすることにしました。 つまり、クリックを決定した後にボタンをポーリングするタスクは、これらのイベントを必要とするタスクのみに通知します。

このため、通知が必要なタスクへのpTaskToNotifyポインターの配列が開始されました。

今の実装:

buttonscontroller.h
 #include "types.h" //   tU32 #include "iactiveobject.h" // iActiveObject #include "buttonsdriver.h" // cButtonsDriver #include "frtosWrapper.h" //  tTaskHandle typedef enum { BT_button1 = 0, BT_button2 = 1, BT_none = 2 } tButtons; class cButtonsController: public iActiveObject { public: explicit cButtonsController(const tTaskHandle *pTaskToNotify, const tU32 countOfNotifiedTask); tButtons getPressedButton(void) const { return pressedButton; }; void run(void); private: cButtonsDriver* pButtonsDriver; tButtons getButton(void); tButtons pressedButton; const tTaskHandle *pTaskToNotify; tU32 countOfNotifiedTask; };
      
      







buttonscontroller.cpp
 #include "buttonscontroller.h" //   #include "susuassert.h" //  ASSERT #include "types.h" //   tPort, tU16, tU8 #include "bitutil.h" //      #define BUTTON_TASK_DELAY (tU32) 50/portTICK_PERIOD_MS #define NEXT_PRESS_DELAY (tU32) 500/portTICK_PERIOD_MS /******************************************************************************* * Function: constructor * Description:         *  .     . ******************************************************************************/ cButtonsController::cButtonsController(const tTaskHandle *pTaskToNotify, const tU32 countOfNotifiedTask) { ASSERT(pTaskToNotify != NULL); this->pButtonsDriver = new cButtonsDriver(); this->pTaskToNotify = pTaskToNotify; this->countOfNotifiedTask = countOfNotifiedTask; } /******************************************************************************* * Function: run * Description:        *   ******************************************************************************/ void cButtonsController::run(void) { tRtosStatus eStatus = RS_fail; tButtons eButtonPreviousState = BT_none; tButtons eButtonCurrentState = BT_none; const tTaskHandle *pTaskHandle; tU32 i = 0; for(;;) { eButtonPreviousState = this->getButton(); if (eButtonPreviousState != BT_none) { //     oRTOS.taskDelay(BUTTON_TASK_DELAY); eButtonCurrentState = this->getButton(); if (eButtonPreviousState == eButtonCurrentState) { pTaskHandle = this->pTaskToNotify; i = 0; //   ,       while ((pTaskHandle != NULL) && (i != countOfNotifiedTask)) { eStatus = oRTOS.taskNotify(*(pTaskHandle), (tU32)eButtonCurrentState, eSetValueWithOverwrite); if(eStatus == RS_fail) { ;//  } pTaskHandle++; i++; } //        0.5  oRTOS.taskDelay(NEXT_PRESS_DELAY); } } oRTOS.taskDelay(BUTTON_TASK_DELAY); } } /******************************************************************************* * Function: getPressedButton * Description:      ******************************************************************************/ tButtons cButtonsController::getButton(void) { tButtons eButton = BT_none; if (BS_buttonPressed == this->pButtonsDriver->getButtonState(BT_button1)) { eButton = BT_button1; } else if (BS_buttonPressed == this->pButtonsDriver->getButtonState(BT_button2)) { eButton = BT_button2; } this->pressedButton = eButton; return eButton; }
      
      







次に、通知用のタスクのリストと、ポーリングボタン用の新しいタスクを作成する必要があります。

main.cpp
 #include <stm32l1xx.h> //  STM2 #include "ledsdirector.h" //   cLedsDirector #include "buttonscontroller.h" //   cButtonsController #include "types.h" //    #include "frtoswrapper.h" //  cRtos #define LEDS_TASK_HANDLE_INDEX 0 #define BUTTON_TASKS_NOTYFIED_NUM 1 #define LEDSDIRECTOR_STACK_SIZE configMINIMAL_STACK_SIZE #define LEDSDIRECTOR_PRIORITY (tU32)2 #define BUTTONSCONTROLLER_STACK_SIZE 256//configMINIMAL_STACK_SIZE #define BUTTONSCONTROLLER_PRIORITY (tU32)3 //      ,  oRTOS   //     RTOS ,  -    //  ,     :) cRTOS oRTOS; .. void main( void ) { // ButtonControllera       // ,    .       //,    static tTaskHandle tasksToNotifyFromButton[BUTTON_TASKS_NOTYFIED_NUM]; cLedsDirector *pLedsDirector = new cLedsDirector(); oRTOS.taskCreate(pLedsDirector, LEDSDIRECTOR_STACK_SIZE, LEDSDIRECTOR_PRIORITY, "Leds"); tasksToNotifyFromButton[LEDS_TASK_HANDLE_INDEX] = pLedsDirector->taskHandle; cButtonsController *pButtonsController = new cButtonsController(tasksToNotifyFromButton, BUTTON_TASKS_NOTYFIED_NUM); oRTOS.taskCreate(pButtonsController, BUTTONSCONTROLLER_STACK_SIZE, BUTTONSCONTROLLER_PRIORITY, "Buttons"); oRTOS.startScheduler(); }
      
      







開始し、確認し、すべてが機能しますが、驚くばかりです:)それが最初に描くことの意味です。

通常どおり、プロジェクトを保存します。

IAR 6.50のプロジェクトボタンとLED

新しいラッパーの例を含む改善されたプロジェクトは、次の場所にあります。

IAR 8.30でのプロジェクト

そして、プロジェクトは次のようになり始めました。

画像



だから、約3日間、3つの要件を実現しました-合計7を持っていることを考えると、悪い速度ではありませんでした。

結局のところ、私は物事についてとても楽観的でしたが、それについては第2部でさらに詳しく説明しました。



All Articles