島根県産業技術センター の東です。
mruby/c ver4 をSTM32マイコンで動かす記事 、今回はその第3回、ハードウェアタイマーを使うよう設定を追加します。
目標
ハードウェアタイマーを使って sleep の時間を正確にするとともに、sleep 中は CPUを止めて消費電力の削減をはかる。
ビルドシステムの調整
まず、前回 mruby/c のプログラムを書き換えるたびに、コマンドプロンプト画面で mrbc.exe を動かす必要があった作業を自動化します。
CubeIDE 内部では、gnu make が動いているので、これを利用して .rb ファイルが更新された時だけ .c ファイルも更新するという動作をさせます。
手順
左ペイン Project Explorer 上、mrubyc の上で右クリックし、New > File を選びます。
Create New File ダイアログで、File name 欄へ、makefile と入力し、[Finish] をクリックし、ファイルを作ります。
Makefile ができますので、以下の内容を入力し保存します。
最終行の前空白(インデント)は、スペースではなく TABですので気をつけて入力します。
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
これで、ビルド時に mruby/c ファイルの自動コンパイルも行ってくれるようになりました。
ハードウェアタイマーを使う
前回は、sleep の時間が指定した時間よりもかなり長くスリープしていました。これは、スケジューラが正確に計時する方法が無いからです。これをハードウェアタイマーを使って解決します。
以下の手順で行います。
- 前回行った タイマー未使用の宣言を取り下げる
- SysTick タイマーで、mrbc_tick() をコールする
- 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] ボタンをクリックして消去します - [Apply and Close] ボタンでダイアログをクローズします
2. SysTick タイマーで、mrbc_tick() をコールする
左ペイン Project Explorer の画面から、Core > Src とたどり、stm32f4xx_it.c をダブルクリックして開きます。
右ペインに表示された C言語ソースコードから、
/* USER CODE BEGIN SysTick_IRQn 0 */
の箇所を探し、その下に以下のコードを追記します。
/* 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 をダブルクリックして開きます。
ファイルの内容を、以下の通り書き換えます。
#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 mrbc_hal_init() ((void)0)
#define mrbc_hal_enable_irq() __enable_irq()
#define mrbc_hal_disable_irq() __disable_irq()
#define mrbc_hal_idle_cpu() ((void)0)
int mrbc_hal_write(int fd, const void *buf, int nbytes);
int mrbc_hal_flush(int fd);
void mrbc_hal_abort(const char *s);
#endif
変更点は、mrbc_hal_enable_irq, mrbc_hal_disable_irq の実体を記述した事と、NO_TIMER用に仮に設定していた mrbc_hal_idle_cpu を無効化した点です。
割り込みの enable/disable は、TickTimer の割り込みだけを有効化/無効化でも良いですが、より汎用的に(簡易に)使う事ができる方法を選定しています。
一通り終わりましたら、ビルドしてターゲットへ書き込みます。
ほぼ正確な秒数で、sleep するようになります。
(以下のデモは、Rubyのプログラムを、1秒ごとの sleep に戻しています)
while true
led_write( 1 )
sleep 1
led_write( 0 )
sleep 1
end
プログラムの sleep 時には CPU も sleep させる
この Ruby プログラムでは、ほとんど sleep の待ち時間が占めており、その間 CPU は何もしていないことになります。このような場合、使用電力の削減を狙って、マイコンを低消費電力モードにするのが良案です。
Cortex-M4 にも低消費電力モードがあります。マニュアルによると、いくつかのモードが用意されていますが、
- SysTick タイマーでウェイクアップが必要
- 周辺デバイスの動作維持
- メモリ(RAM)内容の維持
といった条件を満たす必要があり、それを満たすモードに入るには、以下のようにすれば良いことが分かります。
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI)
この関数を、hal.h の mrbc_hal_idle_cpu() マクロに設定します。
#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 mrbc_hal_init() ((void)0)
#define mrbc_hal_enable_irq() __enable_irq()
#define mrbc_hal_disable_irq() __disable_irq()
#define mrbc_hal_idle_cpu() HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI)
int mrbc_hal_write(int fd, const void *buf, int nbytes);
int mrbc_hal_flush(int fd);
void mrbc_hal_abort(const char *s);
#endif
これで、ビルドしてターゲットへ書き込みます。
書き込みが終わったタイミング(= 実行が始まったタイミング)で、CubeIDE がターゲットに接続できないエラーを表示しますが、これは CPUがきちんとスリープモードに入っている証拠ですので問題ありません。
このボードは、CPUに流れる電流を、JP6に電流計を接続することによって測定することができますので、シャント抵抗とオシロスコープを使って観測してみます。
| スリープモードにしない場合 | スリープモードにする場合 |
|---|---|
![]() |
![]() |
| 約15mA | 約6mA |
測定結果の値が高い部分はLEDの電流を含んでいるので、低い部分を比較すると、約15mAから6mAと、半分以下の電流に低減できていることがわかります。
おまけ - オシレータを内蔵から外部へ変更
ここからはおまけですので、実施しなくても続けられます。
CubeIDE で自動生成したコードは、デフォルトのクロックソースに内蔵RCオシレータを使うよう設定されます。メーカーによると内蔵RCオシレータは、工場出荷時に25℃の条件下にて±1%の誤差内に調整されています。しかしながら、AN5067 で述べられるとおり、実装条件や周囲温度によって影響をうけ、正確さはそれほど期待できません。
Nucleo-F401RE ボードの回路図を見ると、ST-Link プログラマ側にあるマイコンチップは、8MHz の水晶がクロックソースになっており、さらにその出力は、メイン側の STM32F401RE に接続されているようです。こういった使い方は、恐らく保証対象外だとは思いますが、メーカー製のボードですし、試しに使用する設定にしてみると確かに動きます。
方法
- CubeIDEを一旦終了します
- エクスプローラでプロジェクトのフォルダを開き、
mrubyc_v4.iocをダブルクリックして CubeMX を開きます
参考
CubeIDEとCubeMXは連携するように作られているので、CubeIDEの左ペインにある mrubyc_v4.ioc をダブルクリックして CubeMX を起動する方法もあります。
しかし私がこのバージョンの組合せで試す限り、意図通り動作しない事が多く発生したので、全く別々のツールとして操作するほうが安全のようです。
CubeMXが起動したら、以下の設定を行います。
- Clock Configuration をクリックし、クロック設定画面を開きます
- 左、中程の
Input frequency欄が、8(MHz) になっていることを確認します -
PLL Source MuxセレクタをHSIからHSEに変更します -
HCLK (MHz)欄に、84を入力します - 画像の通り、各プリスケーラやPLLの逓倍率が変更されたことを確認します
- 右上の
GENERATE CODEボタンをクリックします
効果
効果はてきめんです。
| SysTick実測値(kHz) | 誤差 (%) | |
|---|---|---|
| 内蔵 RC | 1.0176 | 1.76 |
| 外部 クリスタル | 1.0000 | 0.00 |
測定は、いま手元に周波数カウンタが無いので、オシロスコープ (キーサイト DSOX2002A)で簡易的に測定しました。ですが、時間(およびその逆数の周波数)は、身近な物理量のなかで最も有効桁数を確保しやすい物理量なので、十分信頼できる値と言えます。
ダウンロード
ファイル全体は、以下のリンクからダウンロード出来ます。
おわりに
今回は、ハードウェアタイマーを使うことを目標に、mruby/c の hal を整備しました。
次回は halの残り(コンソール関係)の整備をしていきます。










