扇風機のファームウェアを書き換えて潜在能力を引き出した話(その2)

# 初めに
本記事は[扇風機のファームウェアを書き換えて潜在能力を引き出した話(その1)](https://qiita.com/junp007/items/ea25fe152b2fa983f7ce)の続きです。
前回思わず動かなくしてしまった扇風機を生き返らせます。
ところどころ組み込み独特の用語が出てくるので、組み込みになじみのない人はわかりにくいかもしれませんが、できる限りわかりやすく説明しようと思います。


# マイコンの仕様
ソフトの実装の前に、今回の扇風機に搭載されているマイコン(STM8S003F3)のスペックをまとめておきます。

|項目|値|
|:-:|:-:|
|最大クロック|16MHz|
|ピン数|20|
|GPIOの数|16|
|16bitタイマ|2|
|8bitタイマ|1|
|ADC|5|
|FlashROM|8KB|
|RAM|1KB|
|その他|I2C,SPI,UART等|
arduinoとかに使われるマイコンよりはROM、RAMサイズが小さいですが、扇風機のソフトを実装するには十分すぎるくらいかと思います。
詳しくはhttps://www.st.com/resource/en/datasheet/stm8s003f3.pdf
※タイマは指定した時間経過後に処理を行ったりできる機能です。今回の実装で使ってます。
※I2C、SPI、UART等はシリアル通信の方式のことで、今回は使いません。


## ライブラリについて
タイマやGPIO等の各種機能はSTM8S/A Standard peripheral library(https://www.st.com/en/embedded-software/stsw-stm8069.html )を使えば簡単に使用できます。
このライブラリにはサンプルプログラムも豊富に付いてたので、STMマイコンを使うのが初めてでもすぐに使えました。
ただ、ライブラリというのは汎用的に作られているので、どうしてもコード量としては大きくなってしまいます。
なので、今回のマイコンのようにそれほどFlashROMのサイズが大きくない場合は、ライブラリはレジスタの設定方法の参考として使い、直接レジスタをいじるコードを作った方がいい場合があります。
というのも、今回プログラムを作っていると、コードサイズが限界を超えました。
mapファイルを確認するとタイマのライブラリが結構大きなサイズだったので、タイマ部分についてはライブラリを使わずにレジスタを直接操作するようにしました。

※mapファイル・・・コンパイル後の各コードやデータがどう配置されるかが書かれたファイル
　レジスタ・・・マイコンの機能を設定するために用意された特殊なメモリ領域

# 機能
機能はこんな感じにします。

1. 電源が入ると初期風量でモータが回り始める(風量1)
1. 風量設定ボタン(SW2)を押すと風量が変わる(ボタンを押すごとに風量1→2→3→4→1と変わる)
1. タイマ設定ボタン(SW3)を押すとOFFタイマ設定が変わる(ボタンを押すごとに設定0→1→2→3→4→0と変わる)
1. 現在の風量設定とタイマ設定に対応したLEDを点灯させる
1. 電源ON状態から電源ボタン(SW1)を押すと電源OFF状態に遷移する
1. 電源OFF状態から電源ボタン(SW1)を押すと電源ON状態に遷移する
1. 電源OFF状態から電源ON状態になったときは、電源OFFする前に設定していた風量でモータが回り始める
1. 各ボタン操作時と電源ON時には音を鳴らす
1. ONタイマは実装しない

* 風量設定ボタンでの切り替わり

|風量設定|対応するDuty|対応するLED|
|:-:|:-:|:-:|
|1|60%|LED-1-1|
|2|70%|LED-1-2|
|3|85%|LED-1-3|
|4|100%|LED-1-4|

* タイマ設定ボタンでの切り替わり

|OFFタイマ設定|対応するOFFタイマ時間|対応するLED|
|:-:|:-:|:-:|
|0|OFFタイマ無効|LED-2-*消灯|
|1|1時間|LED-2-1|
|2|2時間|LED-2-2|
|3|4時間|LED-2-3|
|4|8時間|LED-2-4|
:::note info
ONタイマはもともとこの扇風機はある機能なのですが、「ONタイマは実装しない」としました。
なぜ実装しないかというと、この機能自体使わないし、実装もめんどくさそうだったからです。
扇風機ってエアコンとは違って、「今、風が欲しい！」ってなって使うもので、「1時間後に風が欲しい！」とはならないと思います。
実は、このONタイマという機能は今回扇風機を分解するまで知りませんでした。
分解して初めて2行目のLEDにアノードが二つあるってことがわかり、なんでやと思ってマニュアルを調べてみると、
電源OFF状態でタイマボタンを押すとONタイマ機能を設定できることがわかりました。
ただ、その機能があるってことを知っても使うことはなかったので、実装もする必要はないと思いました。
:::

# ソフトの実装
## 全体的な処理
全体的な処理をフローチャートで示すと以下の通りです。
![fan_main_flowchart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/38f83b58-8c28-2cca-8584-2f99b2ad4fa9.png)
起動後に初期化フラグを立て、メインループに入ります。
メインループでは初期化フラグが立っていれば初期化処理を行うので、初期化処理が行われます。
それ以後は、「風量ボタン」「タイマボタン」「電源ボタン」をポーリングで監視をし、そのイベント処理を行う。
といった感じです。

## 風量ボタン、タイマボタンの処理
「風量ボタン」「タイマボタン」に関しては、以下のように抵抗が並列に繋がっていて、ADCに入っています。
このマイコンのADCは10bitなので、基準電圧5Vを1024分割した値が取得できます。(例えば700だったら5V*700/1024=3.42V)
![fan_回路図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/1def0d70-1fa0-b206-75bc-aa73d64d3b15.png)
ADCから取得した値を読み取って、その値によってどのボタンが押されたかを判断します。
実際にボタンを押して確かめた結果、「風量ボタン」を押したときは700、「タイマボタン」を押したときは550くらいにだったので、ADCの閾値を600と500に設定し、それぞれの値以上だったら対応するフラグを立て、ボタンを離したときに設定を反映させるように以下のような処理にしました。
![ADCButton_flowchart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/3ba86549-3159-2e36-e22a-348f49172fdd.png)
なお、ボタンを両方押した場合が一番高い値になりますが、「風量変更ボタン」を押したときと同じ挙動になります。

## 電源OFF状態
動いているときに電源ボタンを押すと電源OFF状態になりますが、電源OFF状態と言っても完全に電力を消費しないわけではありません。
では、どういう状態かというと、STM8Sの仕様書の中では「Haltモード」と呼ばれるCpuの機能が停止した状態です。その状態からは外部割込みがあったときに復帰します。HaltモードでのCPUの消費電流は6[uA]程度らしいです。(動作時は2[mA]くらい)
上記フローチャートの「電源OFF処理」で電源OFF状態に入りますが、外部割込みがあるとそこから処理が再開されます。
「電源OFF処理」の中ではモータを止めたり等の処理を行っているので、処理が再開された後は再度初期化処理をしないとモータが回り始めません。
そのため、「電源OFF処理」の際に初期化フラグも一緒に立て、再開したときに初期化処理がされるようにしています。

## 割り込み処理
上記で「割り込み」や「ポーリング」という用語が出ましたが、ここで軽く説明しますと、
「割り込み」とは、上のフローチャートで書いたようなメインループの内ではなく、特定条件になったときに現在行っている処理に割り込んで処理を行う仕組みです。
「割り込み」に対して、メインループ内で都度条件を確認し、条件が合った時に処理を行うことは「ポーリング」と言います。
今回の実装では、GPIOの外部割込み2つ、タイマ割り込み2つの合計4つの割り込みを使用します。

||割り込み要因|条件|用途|
|:-:|:-:|:-:|:-:|
|1|電源ボタン(ポートAの割り込みを有効にしたピン)|立ち上がり、立下り両エッジ|電源ボタンが押されたことを検知する|
|2|ホール素子(ポートDの割り込みを有効にしたピン)|立ち上がり、立下り両エッジ|モータの回転位置を検知する|
|3|タイマ(TIM2)|16MHzの128分周のクロック(=125kHz)を125クロック|ms単位の時間管理、及び状態表示用LED制御|
|4|タイマ(TIM4)|16MHzの256クロック|PWM信号の遅延|

__Q : 「電源ボタン」は割り込みやのになんで「風量ボタン」「OFFタイマボタン」は割り込みじゃないの？__
A : この二つのボタンはそれぞれ抵抗と並列接続されて、それがADCのピンに入っています。([その1](https://qiita.com/junp007/items/ea25fe152b2fa983f7ce)参照)
なので、ポーリングでADCの値を監視して、その出力によってどちらのボタンが押されたかを判断する必要があります。
__Q : 電源ボタンも同じように抵抗と並列にしてADCで見る構成ではだめなの？そしたらIO減らせるやん。__
A : ADCにすると外部割り込みが起きないので電源OFF状態からの復帰ができなくなります。
なので、電源ボタンはGPIOでの入力できる構成にしておく必要があります。
__Q : じゃ、全部GPIOでええやん。PA1空いてるし。__
A : そうですね。私もそう思います。
PA1(5番ピン)は実はなんかに使ってるのか、使えない理由があるのか。謎です。
ただ、その場合は「風量ボタン」「OFFタイマボタン」の外部割込みは無効にしておかないと、「風量ボタン」「OFFタイマボタン」を押したときにも電源OFF状態から復帰することになってしまいます。別にええっちゃええんですけど。

:::note warn
電源OFF状態からの復帰は「外部割込みがあったとき」で、どの外部割込みかは選択できません。
なので、電源OFF状態にする前に電源ボタン以外の割り込みは無効にしておく必要があります。
そのため、ホール素子による外部割込みも例外ではなく、それも無効にしておく必要があります。
そうしないと、電源OFF状態にしても羽の回転によってホール素子が反応すると、その瞬間に電源ON状態になります。
そしてまた回転を始めてしまい、永遠に電源を切れない状態になります。
なお、電源ボタンは電源OFF状態からの復帰のために割り込みを有効にしているだけで、
電源ON状態でのボタン押下の監視はチャタリング防止処理も必要なのでポーリングで行っています。
:::

## 状態表示用LED制御部分
状態表示用LED周辺の回路は前回も載せましたが、こんな感じの回路になってます。
![fan_回路図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/f2438378-5479-6d92-d518-99bf16d47490.png)
1ms毎にタイマ割り込みを起こし、1ms毎に各列を光らせます。
具体的には、16bitの変数に値を入れ、各ビットを以下のように各LEDに割り当てました。
![LED1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/38a76678-0733-a291-8701-4b4c991d3c96.png)
そして、割り込みハンドラが呼ばれるごとに0～3までカウントし、カウントが0ならCOL4をLow, 他のCOLをHighにし、0～2ビット目の1になっているビットに相当するROWをHighし、次に呼ばれた時にはカウントが1になるので、COL3をLow, 他のCOLをHighにし、4～6ビット目を確認し・・・という風に制御します。
例えば、設定値が0x1234の時は割り込みハンドラが呼ばれるごとに以下のように光ります。
![LED2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/d8c896c9-5724-6c80-e634-9e4204c1f776.png)
![LED3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/3379e97f-f868-f3b9-97eb-dcafc4a3a10c.png)
![LED4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/ff177ef3-18bd-0d5f-fe54-9966de84322f.png)
![LED5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/aa5f9797-9a2e-1458-dbc7-86cd2214eed1.png)
1ms毎にこれが切り替わっていくので、人間の目にはこのように見えます。
![LED6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/7e8b2bf5-2717-8fed-340c-a4bb54fefead.png)

## モータ制御部分
やっとメインの目的のモータ制御部分に来ました。
モータの制御方法は前回にも軽く説明しましたが、マイコンからの制御ピンはU+, V+, U-, V-の4ピンです。
U+、V-をHighにし、U-、V+をLowにするとモータにはU→Vの方向で電流が流れ、
逆にU-、V＋をHighにし、U+、V-をLowにするとモータにはV→Uの方向で電流が流れます。
どちらの方向に流すべきかはモータの回転位置で変わり、ホール素子がHighの時にはU→Vの方向、Lowの時にはV→Uの方向、という風に信号を変化させると扇風機が前に風を発生させる方向に回ります。
まとめるとこんな感じです。

|ホール素子の入力|U+|V+|U-|V-|流す方向|
|:-:|:-:|:-:|:-:|:-:|:-:|
|High|High|Low|Low|High|U→V|
|Low|Low|High|High|Low|V→U|

![fan_回路図5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/5d6cf1de-6e6d-e691-6144-d5c115a55006.png)
![fan_回路図6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/2f733532-72ea-8fdf-ec43-dde8fe8cbfb8.png)

また、これらの出力信号をPWM信号にしてDuty比を指定してやればDuty比に応じた速度で回るようになります。
下記の信号はU+、V+をPWM信号にし、Duty比を85%くらいにした信号です。
![motorSignal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/a0d732df-202a-a500-7b14-62095292cb3e.png)
なお、U+とU-が両方High、もしくは、V+とV-が両方Highになるとショートしてしまうので、そうならないようにホール素子の信号の切り替わり時に出力をLowからHighにする時間を遅らせる必要があります。(この時間のことをデッドタイムと言うらしいです)
切り替わり時の信号を拡大するとこんな感じです。
![motorSignal2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/b41bbd74-304d-12b6-8cd0-38d94173c455.png)
このような信号を生成することでモータを回転させられます。

ここから信号の生成の実装の話に入りますが、信号の生成はタイマ(TIM1)のPWMモードを使っています。
ホール素子からの入力の割り込みハンドラで、一旦U+, V+, U-, V-の出力をすべてLowにしてからPWM信号の遅延用タイマ(TIM4)を動かします。
そして、PWM信号の遅延用タイマの割り込みハンドラでホール素子の入力に合わせたDuty比を設定します。
そうすることで、上記のようなデッドタイムを入れたモータのPWM信号が生成できます。

![motor_flowchart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12125/bc905fb8-c5ed-f7ee-53ca-fb637eb5904a.png)

|ホール素子の入力|U+|V+|U-|V-|流す方向|
|:-:|:-:|:-:|:-:|:-:|:-:|
|High|指定したDuty|0%|0%|100%|U→V|
|Low|0%|指定したDuty|100%|0%|V→U|

# 実装した結果
ここまで記載した内容を実装することで、元々あった扇風機の機能はほぼ実装できました。
また、風量4でのDuty比を100%としたので、それで風量に関する潜在能力は引き出し切れたかと思います。
今回のファームウェアの変更前、変更後でどれくらいパワーアップしたのかを
モータの回転速度等で定量的に示すごとができたらよかったのですが、変更前のファームウェアがいつの間にか死んでいたのでその評価ができませんでした。
ただ、体感的には今回作った風量3が以前の風量4と同等くらいの気がしますし、
今回の風量4はそこそこ扇風機から離れても涼しいくらいのパワーが出るようになりましたので、大成功だと思ってます。
やはり死の淵から復活するとパワーアップするんですね。

# 終わりに
今年はもう涼しくなり始めて扇風機もほぼ使わなくなってしまったのですが、
来年に向けて更なる潜在能力を引き出していきたいと思っています。
もうすでに行ったこととしては、今回のマイコンではI2C通信ができますので、
I2Cの小さいOLEDディスプレイを付けて様々な情報を表示させられるようにしました。(LEDは外しました)
また、他に役に立ちそうなことと言えば、リモコン機能を付けるとかですかね。

質問、指摘、要望、こういうことしてみたらどう？等ありましたら教えてください。
細かい部分は説明を省いたりした部分が多いにも関わらず少し長い記事になってしまいましたが、
ここまでお読みいただきありがとうございました。