1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【チュートリアル】mruby/cをSTM32マイコンで動かす Chapter03: ハードウェアタイマーの使用

Last updated at Posted at 2024-05-31

しまねソフト研究開発センター(略称 ITOC)にいます、東です。

mruby/cをSTマイクロエレクトロニクス社製の32bitマイコンSTM32で動かす記事
今回はその第3回、ハードウェアタイマーを使うよう設定を追加します。

目標

ハードウェアタイマーを使って sleep の時間を正確にするとともに、sleep 中は CPUを止めて消費電力の削減をはかる。

ビルドシステムの調整

まず、前回 mruby/c のプログラムを書き換えるたびに、コマンドプロンプト画面で mrbc.exe を動かす必要があった作業を自動化します。以下の2種類の方法がありますが、どちらでもかまいません。

方法1 バッチファイルを使う

ITOCのサイトからダウンロードした mruby コンパイル済パッケージには、本家にはないバッチファイル mrbc.bat も一緒に入っています。これを、プロジェクトフォルダの、Core/mrubyc フォルダへコピーします。
STM32Tuto03-スクリーンショット 2024-05-23 15.03.24.png

メニューから、Project > Properties を選び、ダイアログを開きます。

ダイアログ左ペインの C/C++ Build > Settings をクリックし、右ペインの Build Steps タブをクリックします。

Pre-build steps の Command: 欄へ、以下の通り入力します。

cd ..\\Core\\mrubyc; mrbc.bat

STM32Tuto03-スクリーンショット 2024-05-23 15.09.27.png

[Apply and Close] をクリックしてダイアログを閉じます。

この操作により、ビルド前に自動的に mrbc.exe を起動してコンパイルが行われます。

方法2 Makefile を使う

先の方法は、STM32CubeIDE 以外でも、Pre-build 設定さえあれば汎用的に使えますが、.rb ファイルを編集していない時でも必ず .c ファイルが更新されるという動作をします。
速度的には十分早いので問題ないですが、ちょっと不細工かと思う面もあります。
CubeIDE 内部では、gnu make が動いているので、これを利用すると .rb ファイルが更新された時だけ .c ファイルも更新するという動作をさせることもできます。

手順
左ペイン Project Explorer 上、mrubyc の上で右クリックし、New > File を選びます。

Create New File ダイアログで、File name 欄へ、makefile と入力し、[Finish] をクリックし、ファイルを作ります。

STM32Tuto03-スクリーンショット 2024-05-23 15.41.31.png

以下の内容を入力します。
最終行の前空白(インデント)は、スペースではなく TABですので気をつけて入力します。

Core/mrubyc/makefile
MRBC = cmd /C mrbc.exe
RBSRCS = $(wildcard *.rb)
CSRCS = $(RBSRCS:.rb=.c)

.PHONY : all
all: $(CSRCS)

%.c : %.rb
	$(MRBC) -B$(@:.c=) $^

メニューから、Project > Properties を選び、ダイアログを開きます。

ダイアログ左ペインの C/C++ Build > Settings をクリックし、右ペインの Build Steps タブをクリックします。

Pre-build steps の Command: 欄へ、以下の通り入力します。

cd ..\\Core\\mrubyc; make

STM32Tuto03-スクリーンショット 2024-05-23 15.46.56.png

これでビルド時に、.rb が更新されているときだけ、.c ファイルも更新するようになりました。

ハードウェアタイマーを使う

前回は、sleep の時間が指定時間よりもだいぶ長い時間 sleep するようでした。これは、スケジューラが正確に計時する方法が無いからです。これをハードウェアタイマを使って解決します。

以下の手順で行います。

  1. 前回行った タイマー未使用の宣言を取り下げる
  2. SysTick タイマーで、mrbc_tick() をコールする
  3. hal.h へ割り込み許可/禁止を定義

今回ターゲットにしているマイコン STM32F401 は、ARM Cortex-M4 CPUコアを搭載しており、CPUコア自体にSysTickという簡単なハードウェアタイマーを内蔵しています。
ST製 HAL ライブラリの仕様を調査すると、SysTickタイマーを使って 1ms サイクルで割り込みをかける用途に使っており、割り込みハンドラはユーザにも開放されています。別途ペリファラルのタイマーを使用する方法もありますが、有限のリソースをスケジューラが使用してしまうのはもったいないので、ここでは HALライブラリの SysTick タイマーに同居する方法を採用しました。

1. 前回行った タイマー未使用の宣言を取り下げる

メニューから、Project > Properties を選び、ダイアログを開きます。

ダイアログ左ペインの C/C++ General > Paths and Symbols をクリックし、右ペインの Symbols タブをクリックします。

Languages が GNU C になっていることを確認し、前回追加したシンボル MRBC_NO_TIMER をクリックして選択状態にしてから、[Delete] ボタンをクリックします。
STM32Tuto03-スクリーンショット 2024-05-23 16.25.27.png

2. SysTick タイマーで、mrbc_tick() をコールする

左ペイン Project Explorer の画面から、Core > Src とたどり、stm32f4xx_it.c をダブルクリックして開きます。
STM32Tuto03-スクリーンショット 2024-05-23 16.27.13.png

右ペインに表示された C言語ソースコードから、/* USER CODE BEGIN SysTick_IRQn 0 */ の箇所を探し、その下に以下のコードを追記します。

Core/Src/stm32f4xx_it.c
  /* USER CODE BEGIN SysTick_IRQn 0 */
  void mrbc_tick(void);
  mrbc_tick();
  /* USER CODE END SysTick_IRQn 0 */

ここは、SysTick_Handler関数の中で、1msごとに割り込み処理によってコールされます。mruby/c スケジューラがタイマー割り込みによって必要としている処理は、mrbc_tick() をコールすることだけです。

3. hal.h へ割り込み許可/禁止を定義

前回は、割り込みを使用しない前提でしたので、ほとんどのマクロを無効化していました。今回は必要な箇所をきちんと記述します。

左ペイン Project Explorer の画面から、Core > mrubyc_src とたどり、hal.h をダブルクリックして開きます。

ファイルの内容を、以下の通り書き換えます。

Core/mrubyc_src/hal.h
#ifndef MRBC_SRC_HAL_H_
#define MRBC_SRC_HAL_H_

#include "main.h"

#define MRBC_TICK_UNIT 1
#define MRBC_TIMESLICE_TICK_COUNT 10

#define hal_init()        ((void)0)
#define hal_enable_irq()  __enable_irq()
#define hal_disable_irq() __disable_irq()
#define hal_idle_cpu()    ((void)0)

int hal_write(int fd, const void *buf, int nbytes);
int hal_flush(int fd);
void hal_abort(const char *s);

#endif

変更点は、hal_enable_irq と、hal_disable_irq の実体を記述した事と、それに伴って必要になるヘッダの include、および、NO_TIMER用に仮に設定していた hal_idle_cpu を無効化した点です。
割り込みの enable/disable は、TickTimer の割り込みだけを有効化/無効化でも良いですが、より汎用的に(簡易に)使う事ができる方法を選定しています。

一通り終わりましたら、ビルドしてターゲットへ書き込みます。

ほぼ正確な秒数で、sleep するようになります。
(以下のデモは、Rubyのプログラムは、1秒ごとの sleep に戻しています)

Core/mrubyc/task1.rb
while true
  led_write( 1 )
  sleep 1
  led_write( 0 )
  sleep 1
end

STM32Tuto03-Demo1.gif


(注意)
まれに、書き込みが終わっても正しくユーザプログラムが開始しないことがありました。原因は不明ですが、ターゲットボード上の黒い RESET スイッチを押すと、正しく動き始めます。

プログラムの sleep 時には CPU も sleep させる

この Ruby プログラムでは、ほとんど sleep の待ち時間が占めており、その間 CPU は何もしていないことになります。このような場合、使用電力の削減を狙って、マイコンを低消費電力モードにするのが良案です。
Cortex-M4 にも低消費電力モードがあります。マニュアルによると、いくつかのモードが用意されていますが、

  • SysTick タイマーでウェイクアップが必要
  • 周辺デバイスの動作維持
  • メモリ(RAM)内容の維持

といった条件を満たす必要があり、それを満たすモードに入るには、以下のようにすれば良いことが分かります。

HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI)

この関数を、hal.h の hal_idle_cpu() マクロに設定します。

Core/mrubyc_src/hal.h
#ifndef MRBC_SRC_HAL_H_
#define MRBC_SRC_HAL_H_

#include "main.h"

#define MRBC_TICK_UNIT 1
#define MRBC_TIMESLICE_TICK_COUNT 10

#define hal_init()        ((void)0)
#define hal_enable_irq()  __enable_irq()
#define hal_disable_irq() __disable_irq()
#define hal_idle_cpu()    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI)

int hal_write(int fd, const void *buf, int nbytes);
int hal_flush(int fd);
void hal_abort(const char *s);

#endif

これで、ビルドしてターゲットへ書き込みます。
書き込みが終わったタイミング(=実行が始まったタイミング)で、CubeIDE がターゲットに接続できないと文句を言いますが、これは CPUがきちんんとスリープモードに入っている証拠ですので問題ありません。

このボードは、CPUに流れる電流を、JP6に電流計を接続することによって測定することができますので、シャント抵抗とオシロスコープを使って観測してみます。

スリープモードにしない場合 スリープモードにする場合
scope_1.png scope_2.png
約15mA 約6mA

測定結果の値が高い部分はLEDの電流を含んでいるので、低い部分を比較すると、約15mAから6mAと、半分以下の電流に低減できていることがわかります。

おまけ - オシレータを内蔵から外部へ変更

ここからは蛇足ですので、設定してもしなくても良いです。

STM32CubeIDE で自動生成したコードは、デフォルトのクロックソースに内蔵RCオシレータを使うよう設定されます。メーカーによると内蔵RCオシレータは、工場出荷時に25℃の条件下にて±1%の誤差内に調整されています。しかしながら、AN5067 で述べられるとおり、実装条件や周囲温度によって影響をうけ、正確さはそれほど期待できません。

Nucleo-F401RE ボードの回路図を見ると、ST-Link プログラマ側にあるマイコンチップは、8MHz の水晶がクロックソースになっており、さらにその出力は、メイン側の STM32F401RE に接続されているようです。こういった使い方は、恐らく保証対象外だとは思いますが、メーカー製のボードですし、試しに使用する設定すると確かに動きます。

方法

CubeIDEの Clock Configration 画面を開きます。
クロックソースを HSI から HSE に変更し、以下の通り各プリスケーラやPLLの逓倍率を変更します。
たぶん、HCLK 欄へ84と入力すると、よしなに設定してくれると思います。
STM32Tuto03-スクリーンショット 2024-05-24 17.59.35.png

ちなみに、初期値(内蔵RC使用時)は以下の通り。
STM32Tuto03-スクリーンショット 2024-05-24 18.08.55.png

効果

効果はてきめんです。

SysTick実測値(kHz) 誤差 (%)
内蔵 RC 1.0176 1.76
外部 クリスタル 1.0000 0.00

測定は、いま手元に周波数カウンタが無いので、オシロスコープ (キーサイト DSOX2002A)で簡易的に測定しました。ですが、時間(およびその逆数の周波数)は、身近な物理量のなかで最も有効桁数を確保しやすい物理量なので、信じても間違いないでしょう。

おわりに

ファイル全体は、github リポジトリにありますので、そちらをご覧ください。

今回は、ハードウェアタイマーを使うことを目標に、mruby/c の hal を整備しました。
次回は halの残り(コンソール関係)の整備をしていきます。

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?