ラベル LV8548 の投稿を表示しています。 すべての投稿を表示
ラベル LV8548 の投稿を表示しています。 すべての投稿を表示

2019/09/08

micro:bit + LV8548MC (ステッピング モーター編)

責任感ゼロ、いじり放題の趣味のプログラミングやブログの書き込みは気楽でイイですよ。

さて、お待たせしました!ステッピング(ステッパ)モーター編です。micro:bit と LV8548MC を使って回してみます。ほとんど新しくなった LV8548MC のパッケージの解説となります。

ハードウェアの準備

モーターを除けば DC モーターと同じです。まずは前の記事を。

モーターはバイポーラ型ステッピングモーター MDP-35A を使いました。ググると仕様書が見つかると思いますが、4端子 A, A(上の線つき), B, B(上の線つき)で、A, B 二つのコイルそれぞれブレッドボードに刺さるようにピンヘッダを半田しました。


これを DC ブラシモーターのときに組んだブレッドボードに刺します。


LV8548MC の OUT1, OUT2, OUT3, OUT4 に先程のピンヘッダを刺します。
ハードの準備はこれでおしまいです。

ソフトウェアの準備

これも DC のソフトウェアで説明した通り。同じパッケージにステッピングモーターのブロックを追加しました。以下の GitHub の URL を拡張機能の追加で指定してください。詳しくは前回の記事で。

https://github.com/ttrsato/pxt-lv8548mc.git

ステッピングモーターブロックの説明

DC からいっぱい追加したので迷うかもしれませんが、最低限必要なものは STEP カテゴリーの最初の三つだけです。


ちなみに DC 用と混在できません。間違わないよう先頭に S: と書かれているのがステッピングモーター用。D: が DC モーター用です。

設定

set stepper で IN1〜IN4 の設定をすれば準備完了です。どのピンでもそれぞれが被らなければ構いません。プルダウンメニューから選んでください。全部のピンが見当たらない場合はマウスのスクロールで残りが見えると思います。
回したいモーターの仕様書をみて、A, B コイルの端子を調べて、それぞれのコイルの両端を A-A', B-B' に割り振れば良いです。

あと、Excitation(励磁)方法を選びますが、よくわからなければ 1/1(Full step, 2相励磁) を選んでおけば良いです。1/2 は(Half step, 1-2相励磁)。LV8548MC はこの2種類の励磁方法のみ選べます。マイクロステップをしたい方は別な IC を選んだ方がいいです。オンセミの Solution Kit なら、LV8702 か LV8714 ですね。
ちなみに 1/1 の場合、48ステップで軸が一回転するステッピングモーターの場合、48ステップで一周。1/2 の場合は倍の 96ステップで軸が一周します。

初めてのステッピングモータープログラム


3つのブロックを使っただけの簡単なプログラム。
ボタン A を押したら Forward 方向に48ステップ回って、Reverse(反対方向)に48ステップ回って、Forward に 24ステップ、Reverse に24ステップ回って終了します。


こんな感じ。


この run stepper ブロックは回転する方向 (Forward/Reverse) と動かすステップ数、速度を指定することができます。ms/step は 1ステップを動かす時間です。ms (1秒の1/1000) で指定します。最短は 1ms/step です。ただこの速度で動くかどうかはモーターと負荷次第です。プログラムの処理速度もどうだか。ちなみに負荷が重いなど期待した速度で回らなくなることを脱調と言います。


最後に指定しているのが stop stepper。ここでは Free(励磁無し) と Hold(励磁保持) を選択できます。run stepper では回し終わった後に Hold(励磁保持)状態になっています。電流が流れたままなので、コイルは磁力を持ち、軸は弱い力では動きません。Free ではコイルの磁力は無くなるので、軸は弱い力で回すことが出来るようになります。Hold 状態は電流を消費しますし、モーターがとても熱くなるので、位置を保持する必要がなければ Free しておいた方が良いかもしれません。

とりあえずこれらを組み合わせれば大体好きなことは出来るのではないかと思います。

残りの基本ブロック


run stepper accel と run stepper trap の二つがあります。

run stepper accel
加速、減速を実現するブロック。start の速度から stop の速度まで、段階的に steps で指定したステップ数回転します。

run accel デモ

run stepper trap
台形駆動します。台形駆動は、だんだん速く回転し、一定速度で回った後、止まる前に徐々に速度を遅くする機能です。グラフを書くと台形なのでそんな名前です。

run trap デモ

変換ブロック

残りは変換ブロックです。回す単位や、速度の単位を変えたい時に使います。物によっては STEPPER units ブロックで初期設定を入れる必要があります。
おまじないというわけではないのですが、


set steps/round を設定しておきましょう。今回使用した MDP-35A はステップ数 48 なので、48を設定します。ステップ角は 7.5° (360/48) なので、


set deg/step で設定しても同じです。これらを指定することによって、回転を角度や回転数で指定したり、速度を rpm で指定すると期待通りに動くようになります。つまり以下のブロックがようやく使えるようになるわけです。もちろん 1ステップ以下の動作は出来ないので丸められます。


これらのうち、STEPPER to steps 使うと最初のプログラムはこういう風に書き直せるわけです。


2周、1周、180°、90°など、直感的に値を設定することが出来るようになります。
応用編として、回転するステージがギアを介してステッピングモーターに繋がっていて、1 ステップで 0.1° 動くシステムなら、set 0.1 deg/step を設定すればステージの回転角度を与えて動かすことが出来ます。ちなみに set mm/step を設定しておくと、mm to steps を使えるようになります。これはスライダーなどのボールネジを使った直線駆動する機構に対する設定で、1ステップで移動する距離を mm でセットすると、mm to steps で移動したい距離を与えるとステップ数を返すようになり、上記の角度指定のように直感的な指定が可能になります。

STEPPER to ms/step は、速度の単位を変換するブロックです。
Hz や、rpm でステップの変化速度を与えたい場合はこれらを使ってください。

速度を周波数 Hz で与える

ちなみに上記のコードを実行すると、あれ?となりませんか。1Hz は 1秒間に回転する回数なので、1Hz だったら 1秒で 1回転するはずじゃんと。残念ながら 1ステップ移動する速度が 1Hz(1秒) です。一周するのに 48ステップなので遅いんです。rpm 指定も同様です。仕様です。いやいや、一周単位での Hz やら rpm で設定したいという方は Hz または rpm で与える数を rounds to steps で囲ってから Hz/rpm to ms/step に与えてください。


rounds to steps は一周のステップ数を掛けた答えを返すので、辻褄が合うんですね。

以上、で LV8548MC を使ったステッピングモーター駆動のためのブロックの説明はおしまいです。残念ながら micro:bit が一生懸命駆動パルスを生成しているので、モーターを回している最中は何も出来ませんので悪しからず。

おまけ

あっているかどうかわからないけど、台形駆動のパラメータの計算に苦労したので晒しておきます。面倒臭いのでバグがあると言われないと見直したくないです。間違いがあったら教えてね。

台形駆動は、トータルカウント(ステップ)数 Ca と、トータル時間 Ta、加減速時間 T1 と初期スピード S1 のみを与えて、残りは計算で求めています。

加速期間は速度 S1(ms/step) から S2(ms/step) まで徐々に1ステップの時間が短く(長く)なります。この時間を全部足したのが T1 になります(8)。また、T2 の時間をその時の1ステップ速度 S2 で割ったのが T2 という二つの方程式を解けばいい(4)。なんだけど意外に面倒臭い。答えは (10)。

台形駆動

余談。ステップ時間で見ると、台形じゃなく、上記のように台形の逆さま。じゃぁと速度に直すと 1/S1 とか逆数になるので、直線じゃないです。ただ周波数に変換すると S1 は実際は時間です。1/t=f なので周波数にすると台形になって、しかも直線になります。そういうこと?

今回から数式を埋め込めるように TeX のコード解釈できるようにした。TeX なんて学生の頃以来ですよ。でも数式書くならいまだに TeX いいよね。最高です!クヌース先生。

\begin{align}
T_{ams}=2 T1_{ms}+T2_{ms}
\end{align}
\begin{align}
C_{asteps}=2 C1_{steps}+C2_{steps}
\end{align}
\begin{align}
\Delta s=\dfrac {S1_{msps}-S2_{msps}}{C1_{steps}}
\end{align}
\begin{align}
C2_{steps}=\dfrac {T2_{ms}}{S2_{msps}}
\end{align}
\begin{align}
T1_{ms}&=(S2_{msps}+\Delta s)+(S2_{msps}+2\Delta s)+\cdots +(S2_{msps}+C_{asteps}\Delta s)\\
&= \sum^{C_{asteps}}_{i=1} (S2_{msps} + i \Delta s)\\
&= C_{asteps}S2_{msps} + (1 + 2 + \cdots + C_{asteps})\Delta s\\
&= C_{asteps}S2_{msps} + \dfrac {C_{asteps}(C_{asteps} + 1)}{2} \Delta s
\end{align}
\begin{align}
(C_{asteps} - 2)S2_{msps}^{2} + (C_{asteps}S1_{msps} - 4T1_{ms} - T2_{ms})S2_{msps} - T2_{ms}S1_{msps}
\end{align}
\begin{align}
S2_{msps} = \dfrac{-(C_{asteps}S1_{msps} - 4T1_{ms} - T2_{ms}) \pm \sqrt{(C_{asteps}S1_{msps} - 4T1_{ms} - T2_{ms})^{2} +4(C_{asteps} - 2)T2_{ms}S1_{msps}}}{2(C_{asteps} - 2)}
\end{align}



2019/09/01

micro:bit + LV8548MC (DC モーター編:プログラミング)

前回、micro:bit + LV8548MC (DC モーター編:とりあえず ハード)ではハードウェアに関してクドクド書きましたが今回は micro:bit に拡張機能で LV8548MC のパッケージを追加して、LV8548MC のブロックを使ってモーターを回してみます。プログラミングと言いつつ、単なるパッケージのブロック説明ですが。

ステッピングモーターブロックの追加に伴って内容アップデートしました。

拡張機能のインストール


GitHub に上げたパッケージを指定してインストールします(オリジナル)。
ブラウザーで MakeCode を開きます。既存のプロジェクトを開いてもいいですし、新規のプロジェクトを開いてもいいです。ちなみに iPhone 上でもプログラム作れますよ!

ちなみにこの拡張機能は LV8548MCSLDGEVK の機能を参考にはしていますが、完全にゼロから作っていますし、インターフェースも異なります。オシロスコープの私物が無いので、思ったような波形が出ているのかやや不安ですが、動作的にはまぁ、そんなに間違いは無いだろうといった感触です。

プロジェクトを開きます

拡張機能をインストールするために、高度なブロック、拡張機能のタブをクリックします。

拡張機能のインストール

LV8548MC の拡張機能を探します。検索ウィンドウに、
https://github.com/ttrsato/pxt-lv8548mc.git
を入力します。

拡張機能の検索ウィンドウ

検索すると lv8548mc が見つかります。クリックすると一瞬でインストールされます。

lv8548mc が見つかった!

MakeCode のエディターに LV8548MC のタブが増えています。
あとは使うだけ。


ブロックについて


LV8548MC の DC モーター用ブロックは3つあります(減りました)。残念ながらローカライズはやり方がいまいちわからなく(以前トライしたときにうまくいかなかった)、そのうち対応します。誰か知ってたら教えてー。

  • 初期化ブロック

Set DC Motor は LV8548MC のCH1 (IN1, IN2) または、CH2 (IN3, IN4) に繋ぐ micro:bit の端子を指定します。一つのチャネルに一つの初期化ブロックが必要です。DriveMode は PWM 駆動時の非通電時の動作の指定です。Open または Brake の指定ができます。
ちなみに DC モーター用のブロックの頭には D: が書かれています。ステッピングモーター用ブロック(S: で始まるブロック)とは共用出来ませんのでご注意を。

  • 速度指定ブロック

モーターを回すブロックはこれ一つです。ステッピングモーターブロックの追加の時に見直されました。

モーターの回る方向は Forward または Reverse (反転) で指定。速度は 0〜100 の割合 (duty) で与えることができます。ある一定以上の速度になるとモーターが回転を始めます。ほどほどに。定格外の電圧を加えている時は注意です。

  • 停止ブロック

 モーターを停止させます。
止めるときには Open または Brake を選ぶことができます。Open はモーターは慣性に従い緩やかに停止しますが、Brake はモーターを急激に停止させます。

サンプルプログラム


簡単なサンプルプログラムです。
徐々に速度をあげて、徐々に速度を下げるを繰り返すだけのプログラムです。




次はいよいよ ステッパー (ステッピング)モーターです。



micro:bit + LV8548MC (DC モーター編:とりあえず ハード)

オンセミLV8548MC が秋月で取り扱いが開始されました!

LV8548MC は DC ブラシモーターx2 またはステッピングモーターx1、電源は 4V〜16V のモータードライバーです。

また、そのままでは扱いづかいので、aitendo からピッチ変換基板が2種類(その1その2)が9月上旬から販売されるようです。

オンセミの LV8548MCSLDGEVK ならば付属の Arduino micro を使ってハードウェアの準備をほぼする必要なくモーターを回すことができるのですが、ここではmicro:bit で LV8548MC を動かしてみます。しかも安価に。ではそのハードウェアの準備をします。

まずはピッチ変換基板を使って、モータードライバー IC を使いやすくします。
待ちきれないので、aitendo の既存の SOP-10 ピッチ変換基板 を使いました。できれば新しく販売される基板を使ったほうが良いでしょう。フットパターンが LV8548MC に適しているので半田付けの際に位置決めしやすいし、配線が太めなようなので、大電流を流したいときに安心です。

さ、お試しならばとりあえず既存の変換基盤で大丈夫です。こんな風に半田付けしました。IC をよく見ると横棒が書いてあるのですが、横棒が近い辺が 1 pin (VCC) と 10 pin (OUT1) 側になります。




まぁまぁな感じで半田付けできました。


1 pin (VCC) と 6 pin (GND) の間にはデータシートにしたがって、パスコンを入れました。奮発してセラミックコンデンサ 10uF/50V。これがまた IC より高い。。(150円@千石電商)。データシートでの指定は 0.1uF〜10uFなので、安定性と財布次第で適宜変えてください。パスコンなので、無くても動いちゃうかもしれませんが、電源がプアだと動作が不安定になるので、入れるに越したことはありません。とりあえずセオリー通りに IC のなるべく近くに置きました。

micro:bit と DC モーターとの接続

micro:bit と LV8548MC の GND を一緒にすることを忘れないように。また 10uF のパスコンとは別に、100uF/50V の電解コンデンサーを LV8548MC の VCC - GND 間に入れています。パスコンは動作の安定のためですが、こちらは破壊防止です。壊したくなかったら入れておいたほうが無難です。
モーターが駆動しているときにモーターのコイルにエネルギーがたまります。モーターを急停止すると、このエネルギーをもとに回生電流が流れるのですが、ACアダプターなどの電源がこれを吸収しきれない場合、IC に印可される電圧が急上昇することがあり、IC が破壊に至る場合があるからです。100uF はこの上昇を抑える作用があります。
ちなみに LV8548MC の電源は手持ちの 6V/1A を使いました。LV8548MC は 4V〜16V で動作するので、この範囲の電源を接続してください。4V 以下では正常に動作しません。ちなみに、オンセミは LB1948MC という 2.5V〜16V で動作する LV8548MC とピンコンパチ(ファンクションも同じ)のモータードライバー IC もあります。4V 以下で動かしたい方はこちらを選択してもいいかもしれません。DigiKey などで取り扱いがあります。
モーターは子供達に破壊されたタミヤの2チャンネル リモコン・インセクトを分解して取り出したものです。

接続拡大

さてLV8548MC と micro:bit、モーターとの接続ですが簡単です。VCC, GND に電源、そして DC モーターを駆動するときは、

CH1: IN1, IN2 (OUT1, OUT2)
CH2: IN3, IN4 (OUT3, OUT4)
をペアとして接続してください。CH1, CH2 の定義は上記の通りとします。micro:bit でプログラムを書くときに指定するときに必要です。

IN1, IN2 および OUT1, OUT2 の極性にナーバスになる必要はないです。逆に回るだけなので、期待通りでなかったら接続を変えるか、ソフトの初期化の時の端子設定を逆にすればいいだけですから。

micro:bit と IN1〜IN4 を接続する必要がありますが micro:bit 側のピンは任意です(全部チェックしたわけではないですが)。また CH1, CH2 の両方のチャネルを接続しなくてもいいです。CH1 のみとか。IN1〜IN4 端子は内部でプルダウンされているので、何も接続していない場合は、例えば CH2 の IN3, IN4 に何も繋がない場合、OUT3, OUT4 はハイインピーダンスになるので問題ありません。

詳しくは LV8548MC のデータシートを参考にしてください。

ちなみに micro:bit に割り当てるピンに迷ったら、エッジコネクタとピンアウトの仕様を見てください。LED 表示やら、ボタン入力、アナログ入力など特別なファンクションに割り当てられていないのは P8 と P16 であることがわかります。自分の使いたい機能を持たないピンに割り当てれば良いと思います。
ただ、P0, P1, P2 以外の端子を利用しようとすると、Sparkfun などのマイクロビットブレイクアウトボード (リンクはマルツオンライン。色々な種類があって秋月などでも扱いがあります)が必要になります。上記写真では P13,14,15,16 に IN1,2,3,4 を割り当てています(これでは SPI 端子が使えません)。この手のボードを持っていない方は、まずは P0, P1, P2 を利用してみるのが良いかもしれません。

とりあえず DC モーターのハードはここまで。
micro:bit の LV8548MC 拡張機能パッケージやプログラミングについては次回









2018/11/03

オンセミの LV8548MCSLDGEVK でエナメル線で作ったモーターを回してみた!

オン・セミコンダクター社のモータドライバモジュールソリューションキットを使った、間違いなく世界初のアホな試みである。

エナメル線自作モーターは単三電池に巻きつけて形を整えたよくあるタイプです。磁石の置き方が変わってるかも。クリップにいい感じで貼り付けられます。磁力が足りないかも知れないと感じて、2つの磁石の同じ局を対向させています。原理的にはこの配置もアリかな?

これでも立派な DC ブラシモーターなので、LV8548MC で回すことができる。最大 1A 流せるドライバ IC なので、快調に回ってくれる。もちろん電源は 5V 4A 流せる秋月の電源から供給しているので十分電流を流せる。無駄にw

エナメル線自作DCブラシモーター


よく回ってます♪

アップした動画では、PWM duty 100% で回し始めて、だんだん duty を下げて、また 100% まで duty を上げて最後に駆動を止める、という一連のシーケンス。。をやってるのだけど、なんとなく分かります?

エナメル線でモーターなんて小学生か中学生以来。
繊細なので綺麗に回せるものを作るのは案外難しかったです。



2018/08/23

メダカ自動餌やり器

夏休みに家族旅行に出ることになりました。

旅行中でも餌あげられるように調べてみると、魚自動給餌器って2000円も出せば手に入るんですね?

でも今年は手元にいいオモチャがあるんで、それで魚自動給餌器作ってみました。去年はメダカを実家に預けたのだけど、今年はうちで面倒見ちゃいます!

その前に、どう作るかですよね。。

いくつか考えて見たけど、メカ的機構に凝ると大変なので(というかそういう作り込み苦手なので)簡単な物で済ませました。

原理は簡単♪
香辛料の容器みたいに穴の空いた容器を穴を下にすれば、餌がこぼれ出てくるようなモンです。容器を下にする時間をコントロールすれば、餌の量も調整可能です。


穴を上にすれば止まるし。
そうそう、この筒を回転させれば、それが出来るんですよね。回転速度と回数を変えれば餌の量もコントロールできそうです。一回転とかキッチリ回すにはステッパモータが丁度いいですよね〜。

さて、100均で買ってきた容器に穴を開けて、餌を入れてみて穴から餌が出てくることを確認してみます。3φ程度でいい感じです。

そこまで確認したら早速工作してみます。

材料はこんな感じ

モーターは後述する Solution Kit に付属のステッパモータを使います。この軸が6φ何ですけど、どう接続するか悩んだ挙句、秋葉原のヴィストンロボットセンターに飛び込んで探してみました。ロボット関連のお店なら何かあるだろうと目論んで。そして見つけたのが写真左上に写っているタミヤの AO-8038 ギヤードモーターハブシャフト。これがぴったりハマりました。

イモネジでモータに固定します

付属するネジはナイロン入りで締めるのが大変なので、別途4φのナットを購入です。そして途中まで組み立てたのですが、、水槽固定用に買ってみた部品が入らない!甘かった。これは別途解決しました。

お、おぅ。入らない。。

さて、この機構を早速動かしてみます。

そこで活躍するのが、オン・セミコンダクターモータ・ドライバ・ソリューション・キット。現在、DCブラシモータまたはステッパを回せる LV8548 のキットが MouserDigiKey で入手可能です。

電源が別途必要なんですが(秋月で売ってる電源でOK。LV8548 は 4〜12V が適当)、他はモータ含めてキットに入ってるので、モータと PC をくっつけて、付属の Arduino Micro に Web から落としてきた GUI 用のファームを Arduino IDE を使って書き込んであげて、Windows の GUI を立ち上げると GUI からモータを回せちゃいます。マイナスドライバーも附属してるので、半田付けすらいらないです。

※さりげなく macOS High Sierra 上の VirtualBox から Windows10 走らせて、そこで GUI を立ち上げています。保障外でしょうが、一応動いています。ただし、VirtualBox 上の Windows10 から Arduino IDE を使ってプログラムの書き込みは出来ないので、Mac 上で動かしてる Arduino IDE からファームを書いています

こんな画面でポチポチと

使い方は何となく分かりますね。注意するところはステッパなので、ステップ角(Step Angle) をセットしないと有効にならない設定があるところぐらいですか。ステップ角はモータの仕様を見て、適宜設定してください。

あとは GUI でポチポチとパラメータを変えながら最適な餌の量を落とせる設定を探してみます。回転周波数と餌の量のグラフでもと思ってわざわざディジタルの計りまで買ったのだけど、餌の重さが小さすぎて面倒なのでやめました。さて、いい感じに餌が落ちるようになったら、Generate Program というボタンがあるので、押してみます。

生成されたスケッチ

今までの操作が、API を使った Arduino のスケッチの形で出力されます。

必要なのは初期設定とモータを回す、止めるといった部分のコードだけなので、余計な部分ぽいコードはガシガシ削除。あとは定時に餌をあげられるように時計などのコードを実装したら完成です!

簡単、簡単〜。



こんな風に回ります



実際動かしてみると、ステッパーモータのガタガタした動きが、実はいい感じに瓶をシェイクしてくれます。餌の固まりを防げるし、餌の出が良くなると思います。むしろマイクロステップなんぞいらないですよ。このアプリケーションに関しては。

Full Step と Half Step の違いも試してみましたが、どっちもどっちな感じです。LV8548 は単純な構成なので、Half Step だとステップが細かくなって滑らかになるはずが、トルクが一定でないので味のある動きをします。LV8702 だと Half Step でもトルクが一定になるよう電流のコントロールをするモードがあるのですが(詳しくはデータシート参照)、滑らかになって餌やり器には多分都合が悪いです。

ちなみに LV8548 を搭載した場合はベースボードにある CN8 の A2-A5 端子が全部使えます(Arduino Micro の A2-A5 に直結されてる)。温度センサーをつけて、水温次第で餌をあげる回数や量をコントロールするのも楽しそう!

あと、プチ改造ですが、


ベースボードの VCC と VIN をショート


ベースボードの VCC と Arduino の VIN を直結してあげると、USB から Arduino に給電する必要がなくなるので、モータ用の電源だけでボードを動作させることができます。
Arduino の VIN は 7V〜12V なので、モータに給電する電源次第では Arduino がうまく動かなくなったり、壊したりするので要注意です。

さ、これで安心して旅行に出かけられます!じゃ!

ソースコードはこちら↓
#include <LV8548_STEP_Lib.h>
#include <TimerOne.h>
#include <TimerThree.h>

// LV8548 Stepper library のインスタンス化
Lib_LV8548Step Lib;

/**
 * @brief モーターの制御を表す値
 *
 * hms_def 時間構造体の変数 r に格納される
 */
enum {MT_FEED = 1, MT_FREE = 0};

/**
 * @brief 時間格納構造体
 *
 * モーター制御用の変数を含む。RTC またはシーケンサーに使われる。
 */
typedef struct {
  uint8_t h; ///< 時
  uint8_t m; ///< 分
  uint8_t s; ///< 秒
  uint8_t r; ///< モーター制御
} hms_def;

// RTC 用、時間変数
hms_def rtc_time = {0, 0, 0, MT_FREE};

// シーケンサー用時間変数の配列
// 好きなように設定してして下さい
// ここでは毎回励磁を停止していますが、止めなくても大丈夫です。
// むしろ励磁しておくと止まっている位置から瓶が回転しにくくなるので、
// 不用意に穴の位置が変わってしまうことを低減できます。モーターは熱くなりますが。
hms_def timer_list[] = {
  { 7, 30,  0, MT_FEED}, ///< 朝の餌やり開始
  { 7, 30, 15, MT_FREE}, ///< 餌やり終了、360度回転後少ししてからモーターの励磁を停止
  {12,  0,  0, MT_FEED}, ///< 昼の餌やり開始
  {12,  0, 15, MT_FREE}, ///< 餌やり終了、360度回転後少ししてからモーターの励磁を停止
  {16, 30,  0, MT_FEED}, ///< 夜の餌やり開始
  {16, 30, 15, MT_FREE}, ///< 餌やり終了、360度回転後少ししてからモーターの励磁を停止
};

// シリアル通信用の文字列バッファと Index 用変数
char read_buff[5]; // FMT: x:dd + \0
int buff_ptr = 0;

/**
 * @brief setup()
 */
void setup()
{
  // コマンド入出力用
 Serial.begin(19200);
  
  // LV8548 のための初期化
 Lib.initLib();
 Timer1.initialize(65);
 Timer1.attachInterrupt(interrupt);
  
  // RTC 用の 1秒割り込みを Timer3 で作る
  Timer3.initialize(1000000); // 1sec
  Timer3.attachInterrupt(interrupt3);
 delay(100);
 
  // ステッパーの1ステップの角度設定
 Lib.setStepAngle(7.5);  
}

/**
 * @brief Timer1 割り込みハンドラ
 *
 * LV8548 Stepper library が使用している。
 */
void interrupt()
{
 Lib.timerFire(100);
}

/**
 * @brief 24時間時計
 *
 * リアルタイム更新される時計で、時、分、秒の値を保存する。一秒に一度呼び出されることを期待しています。
 */
void updateRtc()
{
  // Inc 秒
  rtc_time.s += 1;
  if (rtc_time.s == 60) {
    rtc_time.m += 1;
    rtc_time.s = 0;
  }
  // Inc 分
  if (rtc_time.m == 60) {
    rtc_time.h += 1;
    rtc_time.m = 0;
  }
  // Inc 時
  if (rtc_time.h == 24) {
    rtc_time.h = 0;
  }
}

/**
 * @brief hms_def 時間構造体の時間要素の比較
 *
 * 時、分、秒の値を比較する
 *
 * @param  st      ソース時間
 * @param  dt      ディスティネーション時間
 * @retval true    時間一致。
 * @retval false   時間不一致。
 */
 bool timeIsEqualTo(hms_def *st, hms_def *dt)
{
  return ((st->h == dt->h) &&
          (st->m == dt->m) &&
          (st->s == dt->s));
}

/**
 * @brief hms_def 時間を表示する
 *
 * %H:%M:%D のフォーマットでシリアルに出力。
 *
 * @param  t 表示したい時間構造体
 */
void printTime(hms_def *t)
{
  Serial.print(t->h);
  Serial.print(":");
  Serial.print(t->m);
  Serial.print(":");
  Serial.print(t->s);
}

/**
 * @brief hms_def 時間を表示する
 *
 * Timer3 の割り込みハンドラ
 */
void interrupt3()
{
  // RTC の時間を更新する
  updateRtc();

  // RTC の時間をシリアルに出力
  printTime(&rtc_time);

  // シーケンサーの時間を1つずつ確認する
  for (int i = 0; i < sizeof(timer_list) / sizeof(hms_def); i++) {
    // 現在時刻と一致するかどうか
    if (timeIsEqualTo(&rtc_time, (timer_list + i))) {
      if (timer_list[i].r == MT_FEED) {
        // 餌を与える時間
        // モーターの回転速度次第で餌の量が変わりますので
        // 適宜第一引数を調整して下さい。
        // Half step はステップごとにトルクの変動があります。
        // Full step との振動の違いも比較すると面白いです。
        Serial.print(" <- FEED");
        Lib.motorRotationDeg(12, 360.0, 0, 1);
        
      } else if (timer_list[i].r == MT_FREE) {
        // 餌を与えた後、モーターが回り切ってから低消費電力のため励磁を停止する。
        Serial.print(" <- free");
        Lib.motorRotationFree();
        
      }
    }
  }
  Serial.println("");
  
}

/**
 * @brief シリアルで受信した文字列をパースする
 *
 * RTC の時、分、秒を更新することができる。
 * 設定範囲外の場合は無視される。
 *
 * @param  t 受信文字列
 */
void parseBuff(char* s)
{
  int para = atoi(s + 2);
  switch (s[0]) {
    case 'h':
      if (para >= 0 && para < 24) {
        rtc_time.h = para;
        Serial.print("Set h = ");
        Serial.println(para);
      }
      break;
    case 'm':
      if (para >= 0 && para < 60) {
        rtc_time.m = para;
        Serial.print("Set m = ");
        Serial.println(para);
      }
      break;
    case 's':
      if (para >= 0 && para < 60) {
        rtc_time.s = para;
        Serial.print("Set s = ");
        Serial.println(para);
      }
      break;
  }
}

/**
 * @brief シリアルで文字列を受信する
 *
 * 文字列バッファー read_buff のサイズに達した場合、もしくは '\n' (LF) を
 * 受信した場合は文字列の最後に '\0' (NULL) を埋め込み、parseBuff() をコールする。
 *
 * @param  t 受信文字列
 */
void serialRead()
{
  if (Serial.available() > 0) { // 受信したデータが存在する
    char c = Serial.read(); // 受信データを読み込む
    read_buff[buff_ptr++] = c;
    if (buff_ptr == (sizeof(read_buff) - 1) || c == '\n') {
      read_buff[buff_ptr] = '\0';
      buff_ptr = 0;
      parseBuff(read_buff);
    }
  }
}

/**
 * @brief loop()
 */
void loop()
{
  serialRead();
  delay(100);
}