2018年2月28日水曜日

STM32 Nucleo FreeRTOS SW割り込み on mac memo

[目標]

・スイッチで,LEDをON/OFFする。
  ・最初 : LED OFF
  ・1回目 : LED ON
  ・2回目 : LED Blink(500ms)
  ・3回目 : LED OFF
・スイッチのON/OFFには,IRQ割り込みを用いる
 (ポーリングは面白くないよね)

[前提]

・NucleoL152RE を使用
・NucleoにデフォルトでのってるLED(LD2) をLチカ
・開発環境 SW4STM32(AC6)
・コード生成ツールSTM32CubeMX を使用
http://chuchulabo.blogspot.jp/2018/02/stm32-nucleo-freertos-l-on-mac-memo.html
 で作ったプロジェクトを再利用。
 (めんどくさいから)
 (続きとして作るね)

[手順]

http://chuchulabo.blogspot.jp/2018/02/stm32-nucleo-freertos-l-on-mac-memo.html
の続き。

== Cube MXの設定 ==

CubeMXのプロジェクトを開く。
Nucleoに乗ってる青いスイッチを使用する。
で,それに繋がってるピンは,PC13。
「B1[Blue PushButton]」ってなってる。
ピンをクリックすると,そのピンに設定されている機能を設定/参照できる。
PC13をクリックして,「GPIO_EXTI13」であることを確認する。
さっき設定した「GPIO_EXTI13」は,
このPC13ピンは,GPIOで,割り込み使いますよって意味。
GPIO : 汎用入出力。General-purpose input/output の略。
EXTI : 外部割込み/イベントコントローラ。External Interrupt の略。

次に,GPIOの設定を行う。
「Configulation」タブ->「GPIO」ボタンをクリック。
Pin Configulationダイアログが立ち上がる。
「GPIO」タブを選択すると,設定されているGPIOピンが見れる。
今(Nucleoのデフォルト)は,LEDとSWの設定が見える。
「PC13-WKUP2」をクリックすると,下にどんな設定になっているかが表示される。
プルダウン形式で選択可能。
Nucleoのデフォルトは,
  ・立ち上がりエッジで割り込み
  ・プルアップ/プルダウン なし
になってる。
User Label は,自由に設定できる。
別にデフォルトでいい。
今回は,他の項目は確認しなくていい。
「OK」をクリックして,GPIOの設定は終了。

次に,割り込みの設定を行う。
さっきの立ち上がりエッジ割り込みとか,
割り込み設定じゃないのかというツッコミをしたくなったけど,
立ち上がりエッジ/立ち下がりエッジ/両エッジ とかは,汎用I/Oの割り込みだから必要なもの。
というわけで,やっぱりGPIOの設定になるのかな?

割り込みは,NVICで設定できる。
NVIC : ネスト型ベクタ割り込みコントローラ。Nested Vectored Interrupt Controllerの略。

「Configulation」タブ->「NVIC」ボタンをクリック。
NVIC Configulationダイアログが開く。
「NVIC」タブ->「EXTI line[15:10] interrupts」の「Enabled」にチェックを入れる。
「Preemption Priority」とか,「Sub Priority」とかは,とりあえずデフォルトで。
あと,FreeRTOSを使ってるので,とりあえず「Uses FreeRTOS functions」にチェックを入れておく。

「Enabled」にチェックを入れると,Code Generationの項目が勝手に設定される。
「Code Generation」タブ->「EXTI line[15:10] Interrupt」が勝手にできる。
「Generate IRQ handler」 にチェックが入っていることを確認する。
たぶん,これでNVICの設定は終わり。
「OK」をクリックする。

「Project」->「Generate Code」でコードを生成する。
生成したら,コード(もとい,プロジェクト)を開くか聞かれるので,とりあえず開く。


== 設計? ==


スイッチのON/OFFの時の割り込み(EXTI line[15:10] Interrupt)で,LEDのON/OFF/BLINKを切り替えるのもいいけど,それでは,FreeRTOS使わなくてもできる。
なので,少し回りくどいことをする。
せっかくLEDタスクを作っているので,LEDの制御は,全てLEDタスクから行うようにする。(と言っても,BLINKはタイマ機能使うけど。)
というわけで,シーケンス図。
スイッチの割り込みがかかったら,メッセージをLEDタスクに送る。
LEDタスクは,メッセージを受け取るごとに,
ON->OFF->BLINK->・・・
とLEDの光り方を変えていく。
ちなみに,これはSeq Diagで作った。
PowerPointで作ろうかとも思ったけど,線を引くのがいやだったので,Seq Diagを使用。
すごい便利。
参考 : http://blockdiag.com/ja/seqdiag/index.html

割り込みとタスクのメッセージのやりとりは,メッセージキューを使用する。
メッセージキューとメールキュー,用途どう違うのかわからんけど,
  ・メッセージキュー : int型/ポインタ のみを転送可能
  ・メールキュー : どんな型でも転送可能
って書いてある気がする。
ただ,メールキューは,メッセージキューのラッパーな気がする。
メッセージキューは,int型/ポインタ 以外の例えば構造体のデータとかを転送したい場合には,メモリプールを取得->メモリプール領域内にデータを保存->メモリプールのポインタを転送 としなければならない。
メールキューは,この一連の流れを,勝手にやってくれるものというイメージ。

メールキューの方が便利そうだが,
なんとなく,基本を知るために,メッセージキューを使う。

== 割り込みの作業準備(ただの説明) ==


今更やけど,main.cから呼ばれてるのは,
  ・HAL_Init() : stm32l1xx_hal.cに記述。
  ・SystemClock_Config() : main.cに記述。
  ・MX_GPIO_Init() : gpio.cに記述。
  ・MX_USART2_UART_Init() : usart.cに記述。
  ・MX_FREERTOS_Init() : freertos.cに記述。
  ・osKernelStart() : cmsis_os.cに記述。
で,このあと,while(1)でループ。

でも,このループにはこないはず。(たぶん)
なぜなら,タスクを起動して,そのタスクにディスパッチされるから。


今回,記述が必要なのは,LedTaskと,スイッチ割り込みがかかった時の処理。
で,これらをどこに書くかというと,
  ・LedTask -> freertos.c
  ・スイッチ割り込み -> stm32l1xx_it.c?

LedTaskに関しては,過去(https://chuchulabo.blogspot.jp/2018/02/stm32-nucleo-freertos-l-on-mac-memo.html)に記述した通り,void StartLedTask(void const * argument) 関数が,タスクの実体。

では,スイッチ割り込み(EXTI line[15:10] Interrupt)の処理の実体はどこにあるかというと,stm32l1xx_it.cにある,void EXTI15_10_IRQHandler(void) という関数がそう。
割り込みがかかると,この関数に飛んでくる。
さらに,中でHAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); を実行している。

P13は,スイッチの繋がってるピン。
つまり,スイッチ押された時に,HAL_GPIO_EXTI_IRQHandler(); 内に書いてる処理が実行される!
じゃあ,そのHAL_GPIO_EXTI_IRQHandler() はどこに書かれているのか?
これは,stm32l1xx_hal_gpio.c に書かれている。

HAL_GPIO_EXTI_IRQHandler() の中で,さらにコールバック関数(HAL_GPIO_EXTI_Callback(GPIO_Pin);)が呼び出されている。

たぶん,自分で記述すべきは,このコールバック関数だと思う。

ちなみに,割り込みかかってから,ずっと,
全部の関数で,uint16_t GPIO_Pinを引数として渡している。
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);  こんな感じで。

EXTI line[15:10] Interruptは,みたらわかるけど,10〜15 の割り込みがかかった時,呼び出される。じゃあ,さらにわけた個別の処理(10なら10の,13なら13の異なる処理)を行いたい場合,どうするか?
これはユーザーがif文とかで分岐させてやらないといけない。
最後のコールバック関数まで分けてくれている様子はないので,コールバック関数内で自分たちで書かないといけないっぽい。

さらに,自動生成のコールバック関数のコメントに,こんなことが書いてある。

/* NOTE : This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/

どういうことかといえば,コールバック関数が必要な場合,ここに定義してる関数は使用するな。別のとこで(ユーザーが作ったファイルで),HAL_GPIO_EXTICallback関数を定義して,その関数を使え。
ってこと。関数の最初に weak ってついてるから,もし,他の場所で同じ名前の関数があった場合には,そっちが使われるようになっている。

長くなったけど,
割り込みでやることは,
  ・ユーザーファイルを作成
  ・ユーザーファイルにHAL_GPIO_EXTICallback関数を定義
  ・定義した関数ないで,GPIO_PIN_13 の時の処理を記述(条件分岐)
  ・処理内容は,メッセージの送信
以上!

== タスクの作業準備(ほぼ割り込み)(ただの説明) ==

タスクは,何も考えずに,割り込みからメッセージが送られてきた時の処理を書けばいいんやけど,一つ問題が。

メッセージキューでメッセージを割り込みハンドラ -> タスク へ送るわけだが,メッセージキューのIDどうすんの?
extern使ったらいいのかもしれんけど,個人的にexternはあんまり好かん。
というわけで,割り込みかかった時の処理と,LEDタスクの処理は同じファイルに記述する。

でも,freertos.cにLEDの割り込みの処理書くのもなぁ・・・と思うので,
LED制御用のファイルを一個作って,そこに,割り込みの処理と,LEDタスクの処理を書くことに。
freertos.cのStartLedTask()関数からは,このLED制御用のファイルに書いた,LEDタスクの処理関数を呼び出す形にする。
図にするとこんな感じ。
気持ち悪いのが,LedCtrl.cの中に,HdrSwitch()関数が書かれていること。
SW制御ようのタスクを作って,タスク間通信をするのが正解なのかもしれない。
でも,今回はこのまま行く。(過去のログを変更するのが大変面倒だから)
次,いつかなおす!(たぶん直さない)

説明はこれくらいにしとこう。力尽きた。

== コーディング ==

書き始めたけど,すっごい書きづらいなぁ。
LED点滅用のタイマーをCubeMXで自動生成してるから,freertos.cにタイマーが書かれてるわけやけど,LedCtrl.cからタイマーの開始/停止をしたい。
freertos.cで生成してるリソースをLedCtrl.cからいじりたい場合,externするしかない気がする。
考え直した方がいいかなぁ。
CubeMXの自動生成,すごい便利やけど,FreeRTOSのリソースに関しては,自分で書いた方がいいかもしれない。タスクくらいは生成してもいいけど,その他のタイマー,メッセージキュー,メモリプールとかは自分で書いた方がいい。
externあまりしたくなければ,ユーザ作成ファイルに結局メッセージキューもメモリプールもタイマーも書くから。

・・・コーディングするのやめよっかな・・・。

コーディングやめる。
大体考えなあかんことはこれまでに書いたから,まぁ大丈夫でしょ。
何より,書きにくいし。

もっと設計ちゃんと考えなあかんなぁ・・・。




2018年2月25日日曜日

STM32 Nucleo FreeRTOS Lチカまで on Mac memo

[目標]
・FreeRTOSを使ってLチカ
  ・作成するタスクは1つ
  ・周期タイマで0.5秒周期で点滅させる

[前提]
・NucleoL152RE を使用
・NucleoにデフォルトでのってるLED(LD2) をLチカ
・開発環境 SW4STM32(AC6)
・コード生成ツールSTM32CubeMX を使用
・開発環境の構築,プロジェクトの作成は,
http://chuchulabo.blogspot.jp/2018/02/stm32-on-mac-memo.html
を参照。

[手順]

== タスクの追加 ==

http://chuchulabo.blogspot.jp/2018/02/stm32-on-mac-memo.html
で作成したプロジェクトをそのまま変更する(めんどいから)。
CubeMXのプロジェクトファイルを開く。
左サイドバーの FREERTOS->Enabled にチェックを入れる。
これだけで,FREERTOSをプロジェクトに入れてくれる。すごい!
タスクの設定をする。
まずは,LEDタスクを作成する。
これも,CubeMXで行う。
「Configuration」タブ -> 「FREERTOS」 をダブルクリック。
FREERTOS Configuration のダイアログが開くので,設定を変更する。
「Tasks and Queues」タブ -> Tasksの方の「Add」ボタンをクリック。


New Task ダイアログが開くので,LEDタスクを設定する。
以下のように設定して,「OK」を押す。
すると,LEDタスクが追加される。
追加されたのを確認したら,「Apply」をクリック -> 「OK」をクリック。

Project->generate Code で,コード生成を開始する。
すると,次のようなダイアログが出る。
よくわからんが,「Yes」を押す。
すると,CubeMXがよしなにコードを生成してくれる。
素晴らしい!

main.cでは,
MX_FREERTOS_Init();
が呼び出されているだけ。
中身は,freertos.c に書いてある。
ちゃんとvoid StartLedTask(void const * argument) ってのが生成されている。



== ちょっとFreeRTOSとCMSISについてメモ ==
使うとチェックを入れたのはFree RTOS。
だが,実際には,Free RTOSのAPIをラッパーしたCMSISのAPIを使用して,
タスクの生成は行われている。

osThreadDef とか,osThreadCreate というのは,CMSISのAPI。

FreeRTOSについて
https://www.freertos.org/index.html

CMSISについて
http://www.keil.com/pack/doc/CMSIS/RTOS/html/index.html

CMSISは,FreeRTOSや,その他のRTOSであっても,共通して使用できるように
策定されたもの。
CMSISのAPIを使っていれば,FreeRTOSから他のRTOS(ThreadXとか?)に変えても,
CMSISの部分が吸収してくれて,移行が簡単になる。

だから,今回は,CMSISのAPIをなるべく使うようにする。
(CubeMXが勝手にやってくれるんだろうけど)

== 周期ハンドラの追加 ==
タイマー機能を使用して,周期ハンドラを作る。
まず,CubeMXで設定を行う。
タイマー機能を使用するため,ソフトウェアタイマを有効にする。
「Configuration」タブ -> 「FREERTOS」 をダブルクリック。
FREERTOS Configuration ダイアログが開く。
「Config parameters」タブ->「Software timer definitions」->「USE_TIMERS」を「Enabled」に変更。
次に,タイマーを作成する設定を行う。
「Timers and Semaphores」タブ->「Timers」の「Add」ボタンをクリック。
NewTimerダイアログが開くので,以下のように設定して,「OK」をクリック。
LEDの点滅用のタイマーなので,名前をLedTimerにした。
Callbackは,タイマーで一定時間たった時に呼ばれるコールバック関数の事。

「Apply」->「OK」をクリックして,FREERTOSの設定終了。
と思ったけど,なんか,「Free RTOS Heap Usage」タブで,「×」が表示されててだめっぽいところがある。読むと,Heapサイズが足りてないっぽい。
タイマーをEnableにしたせいで,使用するHeapサイズが足りなくなったらしい。
とりあえず,Heapサイズを多くとるようにする。
「Config parameters」タブ->「Memory management settings」->「TOTAL_HEAP_SIZE」を3072→4096に変更。
すると,「Free RTOS Heap Usage」タブの「×」が「✔︎」に変わる。

これでOK!多分。
「Apply」->「OK」を押して,FREE RTOSの設定を完了する。

コード生成をしてみる。
「Project」->「Generate Code」で,コードを生成する。

freertos.cをみると,タイマが追加されていることがわかる。




== コーディング ==
MX_FREERTOS_Init(void) でタスクもタイマも作成されている。
タスクに関しては,osThreadCreate() したら,始まるみたい。
ただ,タイマは,osTimerStart()しないと,始まらないっぽい。
というわけで,
LEDタスクからLEDタイマを開始して,
LEDタイマのコールバック関数内でLEDを点滅させる。
まず,タイマをスタートさせる。

以下,コード。
-------------------------------------------------
/* StartLedTask function */
void StartLedTask(void const * argument)
{
  /* USER CODE BEGIN StartLedTask */
  osTimerStart(LedTimerHandle, 500);
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END StartLedTask */
}

/* LedTimerCallback function */
void LedTimerCallback(void const * argument)
{
  /* USER CODE BEGIN LedTimerCallback */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
  
  /* USER CODE END LedTimerCallback */

}
-------------------------------------------------


さらに,freertos.cは,HALライブラリをincludeしてないので,
HAL_GPIO_TogglePin()でエラーになる。
ので,HALをincludeする。
以下,コード。
-------------------------------------------------
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"

/* USER CODE BEGIN Includes */     
#include "stm32l1xx_hal.h"


/* USER CODE END Includes */
-------------------------------------------------

以上,コーディング終わり!
500ms周期でLEDが点滅するはず!

Build -> Debug -> Resumeで,実行すると,
LEDが500ms周期で点滅した!
成功!

2018年2月23日金曜日

STM32 開発環境構築 Nucleo Lチカまで on Mac memo

[目標]
以下のインストール
・統合開発環境 SW4STM32
・コード生成ツールSTM32CubeMX
その後
・Nucleo L152RE でLチカ

[手順]
== 統合開発環境 SW4STM32 ==
  1. http://www.st.com/ja/development-tools/sw4stm32.html
    からOpenSTM32Community に入って,
    http://www.openstm32.org/Downloading%2Bthe%2BSystem%2BWorkbench%2Bfor%2BSTM32%2Binstaller
    からMAC OS/X 用の最新版をダウンロード。
    今回は,"install_sw4stm32_macos_64bits-v2.4.run"をダウンロード。
    OpenSTM32に入るには,アカウント登録が必要。
    注意 : ひたすら遅い。1時間くらいかかる。
  2. ファイルをダウンロードした場所でターミナルで開く。
    以下のコマンドを入力。
    $ chmod 755 install_sw4stm32_macos_64bits-v2.4.run 
    $ ./install_sw4stm32_macos_64bits-v2.4.run
  3. インストールのWindowが開くので,そのままデフォルトの設定で進めていく。
    ライセンスとかは,全部agreeにする。http://www.openstm32.org/Installing%2BSystem%2BWorkbench%2Bfor%2BSTM32%2Bwith%2Binstaller

    インストール始まったなーと思っても,なんか進まない時は,ターミナルを確認する。実はPasswordを求められてる。
  4. 終わり!
インストール画面
ターミナル パスワード入力


ついでに,AC6を起動して,アップデートを行う。
Help->Check for Updatesで,アップデートを開始。

== コード生成ツールSTM32CubeMX ==

  1. http://www.st.com/en/development-tools/stm32cubemx.html?sc=stm32cubemx
    から,CubeMXをダウンロード。zipファイルでダウンロードできる。
  2. AC6を立ち上げ,Help->Install New Software を選択。
  3. 無理だった。zipを選択しても,インストールできない。
  4. なので,別の方法を。zipファイルを解凍すると,.Appファイルがある。
  5. SetupSTM32CubeMX-4.24.0.app をダブルクリックする。
  6. インストールのWindowが開くので,デフォルトでインストール。
  7. アプリケーション->STMicroElectronicsにSTM32CubeMX.appがあることを確認。
  8. 終了!




== プロジェクトの作成 ==
デフォルトだと,AC6のワークスペースは,
Documents\workspace になるので,ここにプロジェクトを作成する。

まず,STM32CubeMXを起動する。
NewProjectを選択する。
Board Selectorタブをクリックして,Nucleo-L152RE を選択して,「StartProject」をクリック。

全てのペリフェラルをデフォルトで初期化するか聞かれるので,「Yes」を選択。

すると,こんな感じで,マイコンのピンを選択できる画面が出てくる。


Documents\workspace\NucleoL152RE_Blink フォルダを作って,NucleoL152RE_Blink.iocとして保存する。
次に,コードを生成する。
EclipseのプロジェクトをCubeMXから作成したいので,プロジェクト設定を行う。
Project->Setting を選択。


ProjectSettings ダイアログが開く。
Projectタブで,次のように設定する。
ProjectNameとProjectLocationを適当に設定して,「GenerateUnderRoot」のチェックを外す。


CodeGeneratorタブで,次のように設定する。
「Copy all used libraries into the project folder」と,
「Generate peripheral initialization as a pair of ・・・」にチェックを入れる。
(容量は食うかもしれんが,なんとなく,わかりやすくなりそうだから。)

で,OKを押す。
Project->Generate Code をクリックして,コードを生成する。
これで,AC6のプロジェクトファイルも同時に作ってくれる。
コード生成に成功!と言われる。「Open Project」を選択して,プロジェクトを開く。
すると,AC6が立ち上がり,作成されたプロジェクトを編集できる!
インポート成功!って言われたらOK!

workspaceの中を見てみると,以下のような感じになる。
作成されたプロジェクトの中にも,.iocファイルが作成されている。
もしかして,最初の保存はいらなかったかも。まぁいいや。
今後の課題と言うことで。

→ workspace->NucleoL152RE_Blink.ioc は消しても問題ない。
  CubeMXのコード生成で作成した,
  workspace->NucleoL152RE_Blink->NucleoL152RE_Blink.ioc
  を編集していけば良い。


== プログラム実行 ==

プロジェクトも作ったので,プログラムを実行する。
とはいえ,何も起きないけど。
前提として,NUCLEO L152RE をPCと接続しておくこと。

生成したプロジェクトを開いた状態がこんな感じ。
左のサイドバーのNucleoL152RE_Blinkプロジェクトを右クリック
->「Debug As」->「AC6 STM32 C/C+ Application」をクリック

なんかちょっとダイアログが出た末に,次のダイアログが登場。
デバッグ始めるよー的な?
とりあえず,「Yes」をクリック。

すると,AC6の画面が切り替わる。

ここまできたらOK!「Resume」ボタン,もしくは「F8」を押したら,プログラムが実行される!
したところでまだ何も起きないけど。



== Lチカプログラム作成 ==
まず,コード補完機能を使えるようにするため,
コード補完のショートカットを「Ctrl + Space」から変更する。
"https://qiita.com/rotomx/items/784211ea16760eecac83"
→ とりあえず,「Alt+Space」にした。

main.cを開いて,

int main(void) 関数内のwhile(1)の内部にLチカコードを記述する。
以下,コード

--------------------------------------------------------------------

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);
    HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
    HAL_Delay(1000);

  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  }

  /* USER CODE END 3 */

--------------------------------------------------------------------

トンカチ(🔨)みたいなボタンを押して,Buildする。
特にエラーが出なければ,デバッグする。
1回デバッグしているため,
左のサイドバーのNucleoL152RE_Blinkプロジェクトを右クリック
->「Debug As」->「AC6 STM32 C/C+ Application」をクリック
をしなくてもいい。
虫っぽいボタンを押して,デバッグを開始する。
画面が切り替わるので,Breakpointを貼ってみる。

HAL_GPIO_WritePin(・・・) と書いている行の左部分にカーソルを合わせて,
「Add Breakpoint」を選択する。



「Resume」ボタン,もしくは「F8」を押したら,プログラムが実行される。
Breakpointを貼ったところでプログラムが毎回止まる。
もう一度「Resume」ボタン,もしくは「F8」を押したら,プログラムが再開される。
1個目のBrekapointで止まった時は,LEDは消えている。
2個目のBreakpointで止まった時は,LEDはついている。
これは,Breakpointを貼った行のプログラムが実行される手前のところでプログラムが停止されるから。

Breakpointをなくしたら,ずっとLEDが点滅し続ける。
周期は1秒間隔!
Lチカ終了!
できた!

[参考]
http://happytech.jp/wordpress/2017/05/28/ide-for-stm32-mcu-sw4stm32/

[おまけ]
今回諦めたけど,EclipseにCubeMXのプラグイン入れる方法を昔載せてた。
http://chuchulabo.blogspot.jp/2017/05/stm32macos.html