#include "AlarmTone.h"
#include "StateMachine.h"
#include <SevSeg.h>
const int ColonPin = 13; //!< 7セグコロンピン番号
const int SpeakerPin = A3; //!< スピーカーピン番号
const int MinuteButtonPin = A0; //!< 分ボタンピン番号
const int SecondButtonPin = A1; //!< 秒ボタンピン番号
const int StartButtonPin = A2; //!< 開始ボタンピン番号
const int ClearButtonPin = A4; //!< クリアボタンピン番号
const int BlinkColonCountdownCycle = 500; //!< カウントダウン状態のコロン点滅周期(msec)
const int BlinkColonAlarmOnCycle = 250; //!< アラーム音出力状態のコロン点滅周期(msec)
const int AutomaticCountupStartTime = 2000; //!< 自動カウントアップ開始時間(msec)
const int AutomaticCountupCycle = 200; //!< 自動カウントアップ周期(msec)
bool singleReadButton(int pinNo);
/**
* @brief ボタン情報クラス
*/
class Button
{
public:
/**
* @brief コンストラクタ:メンバーの初期化
* @param [in] pinNo ピン番号
*/
Button(int pinNo)
: state_(false), changed_(false), repeat_(0), lastState_(false), hold_(false), holdTime_(0), pinNo_(pinNo){};
/**
* @brief ボタンの状態を取得する(チャタリング除去あり)
*/
void readButton()
{
changed_ = false;
bool state = singleReadButton(pinNo_);
if (lastState_ == state)
{
if (state_ != state)
{
repeat_++;
if (repeat_ >= 3)
{
state_ = state;
changed_ = true;
}
}
}
else
{
repeat_ = 1;
}
lastState_ = state;
hold_ = false;
if (state_)
{
if (holdTime_ >= AutomaticCountupStartTime)
{
hold_ = true;
}
else
{
holdTime_++;
}
}
else
{
holdTime_ = 0;
}
};
/**
* @brief ボタンを押下されたかどうかを返す
* @retval true 押された
* @retval false 押されてない
*/
bool isPressedButton()
{
return changed_ && state_;
};
/**
* @brief ボタン長押し中かどうかを返す
* @retval true ボタン長押し中
* @retval false ボタンが解放中もしくは2秒未満の押下
*/
bool isHoldButton()
{
return hold_;
};
private:
bool state_; //!< 押下状態 (true:押下, false:解放)
bool changed_; //!< 押下状態の変化有無 (true:変化あり, false:変化なし)
int repeat_; //!< 押下状態繰り返し回数 (チャタリング除去情報)
bool lastState_; //!< 前回の押下状態 (チャタリング除去情報)
bool hold_; //!< 長押し状態(true:長押し中,false:その他)
int holdTime_; //!< ボタン押下時間[ms]
int pinNo_; //!< ピン番号
};
Button minuteButton(MinuteButtonPin); //!< 分ボタン情報
Button secondButton(SecondButtonPin); //!< 秒ボタン情報
Button startButton(StartButtonPin); //!< 開始/停止ボタン情報
Button clearButton(ClearButtonPin); //!< クリアボタン情報
AlarmTone alarmTone; //!< アラーム音制御
SevSeg sevseg; //!< 7セグ制御
bool blinkColon(int cycle);
void setCountDownTime();
class CountDownState :public BaseState {
public:
CountDownState() : countdownComplete_(false), msec_(0) {};
KTState getState() override {
return KTCountdown;
};
void stateEntry(Context* ctx) override{
countdownComplete_ = false;
msec_ = (ctx->displayTime.minute * 60 + ctx->displayTime.second) * 1000;
};
KTState stateDo(Context* ctx) override {
// 開始/停止ボタンで時間設定状態に遷移
if (startButton.isPressedButton())
{
return KTStop;
}
// 0ミリ秒の場合カウントを停止してアラーム音出力状態に遷移
if (msec_ <= 0)
{
return KTAlarm;
}
ctx->displayTime.minute = msec_ / 1000 / 60;
ctx->displayTime.second = msec_/ 1000 % 60;
msec_--;
ctx->colonState = blinkColon(BlinkColonCountdownCycle);
return KTNone;
}
private:
bool countdownComplete_;
uint32_t msec_;
};
class AlarmState : public BaseState{
public:
KTState getState() override {
return KTAlarm;
}
KTState stateDo(Context* ctx) override {
// 開始/停止ボタン押下で時間設定状態に遷移
if (startButton.isPressedButton())
{
ctx->displayTime.minute = ctx->countdownStartTime.minute;
ctx->displayTime.second = ctx->countdownStartTime.second;
return KTStop;
}
ctx->colonState = blinkColon(BlinkColonAlarmOnCycle);
ctx->playAlarm = true;
return KTNone;
}
};
class StopState : public BaseState {
public:
KTState getState() override {
return KTStop;
}
KTState stateDo(Context* ctx) override {
// 開始/停止ボタン押下かつ、分もしくは秒のどちらかが0超過でカウントダウン状態に遷移
if (startButton.isPressedButton() && ((ctx->displayTime.minute > 0) || (ctx->displayTime.second > 0)))
{
ctx->countdownStartTime.minute = ctx->displayTime.minute;
ctx->countdownStartTime.second = ctx->displayTime.second;
return KTCountdown;
}
setCountDownTime();
ctx->colonState = true;
ctx->playAlarm = false;
return KTNone;
}
};
StopState stopState; //!< 停止状態クラス
CountDownState countDownState; //!< カウントダウン状態クラス
AlarmState alarmState; //!< アラーム音出力状態クラス
BaseState *stateTbl[] = {&stopState, &countDownState, &alarmState}; //!< 状態クラス配列
StateMachine stateMachine(stateTbl, 3); //!< 状態制御クラス
/**
* @brief アラーム音出力の開始/停止
* @param [in] on 出力開始(true)/出力停止(false)
*/
bool setAlarm(bool on)
{
if (on)
{
alarmTone.play();
}
else
{
alarmTone.stop();
}
}
/**
* @brief 7セグ表示数値の設定
* @param [in] minute 分 (0~99)
* @param [in] second 秒 (0~99)
*/
void sevsegSetNumber(int minute, int second)
{
sevseg.setNumber(minute * 100 + second);
}
/**
* @brief 7セグのコロン表示
* @param [in] on 表示(true)/非表示(false)
*/
void sevsegSetColon(bool on)
{
digitalWrite(ColonPin, on ? LOW : HIGH);
}
/**
* @brief コロンを点滅
* @param [in] cycle 点滅の周期(msec)
* @retval true 点灯状態
* @retval false 消灯状態
*/
bool blinkColon(int cycle)
{
static bool colon = false;
static int elapsedTime = 0;
elapsedTime++;
if (elapsedTime >= cycle)
{
elapsedTime = 0;
sevsegSetColon(colon);
colon = !colon;
}
return colon;
}
/**
* @brief ボタン状態を取得
* @retval true 押下中
* @retval false 解放中
*/
bool singleReadButton(int pinNo)
{
return digitalRead(pinNo) == LOW;
}
/**
* @brief 入力(ボタン状態)を更新
*/
void updateInput()
{
minuteButton.readButton();
secondButton.readButton();
startButton.readButton();
clearButton.readButton();
}
/**
* @brief 前回実行時から1ms以上経過しているか判断する
* @retval true 1ms以上経過している
* @retval false その他
*/
bool is1msElapsed()
{
static uint32_t lastTime = 0; //前回の実行時間
uint32_t now = millis(); //今回の実行時間
if (lastTime == now)
{
return false;
}
lastTime = now;
return true;
}
/**
* @brief カウントダウン時間を0分0秒にクリアする
*/
void clearCountdownTime()
{
if (clearButton.isPressedButton())
{
context.displayTime.minute = 0;
context.displayTime.second = 0;
}
}
/**
* @brief 時間をカウントアップする
* @param [out] time 表示時間(分もしくは秒)
* @param [in] maxTime 最大時間(分:100、秒:60)
*/
void countUpTime(uint32_t *time, uint32_t maxTime)
{
*time = (*time + 1) % maxTime;
}
/**
* @brief カウントダウン時間を更新
* @param [out] button ボタン情報
* @param [out] time 表示時間(分もしくは秒)
* @param [in] maxTime 最大時間(分:100、秒:60)
*/
void updateCountDownTime(Button &button, uint32_t *time, uint32_t maxTime)
{
static unsigned int incrementCnt = 0;
if (button.isPressedButton())
{
countUpTime(time, maxTime);
}
else if (button.isHoldButton())
{
if (incrementCnt >= AutomaticCountupCycle)
{
incrementCnt = 0;
countUpTime(time, maxTime);
}
incrementCnt++;
}
}
/**
* @brief カウントダウンの時間を設定する
*/
void setCountDownTime()
{
clearCountdownTime();
updateCountDownTime(minuteButton, &context.displayTime.minute, 100);
updateCountDownTime(secondButton, &context.displayTime.second, 60);
}
/**
* @brief 7セグに出力する処理
*/
void updateOutput()
{
setAlarm(context.playAlarm); // アラーム音の出力
sevsegSetColon(context.colonState); // コロンの表示
sevsegSetNumber(context.displayTime.minute, context.displayTime.second); // 時間の表示
sevseg.refreshDisplay(); // ←毎回呼び出す必要あり
}
/**
* @brief 初期化処理
*/
void setup()
{
Serial.begin(115200);
// ピン設定
pinMode(MinuteButtonPin, INPUT_PULLUP);
pinMode(SecondButtonPin, INPUT_PULLUP);
pinMode(StartButtonPin, INPUT_PULLUP);
pinMode(ClearButtonPin, INPUT_PULLUP);
pinMode(ColonPin, OUTPUT);
pinMode(SpeakerPin, OUTPUT);
// アラーム初期化
alarmTone.begin(SpeakerPin);
// 7セグ初期化
byte digits = 4;
byte digitPins[] = {2, 3, 4, 5};
byte segmentPins[] = {6, 7, 8, 9, 10, 11, 12};
bool resistorsOnSegments = false;
bool updateWithDelays = false;
bool leadingZeros = true;
bool disableDecPoint = true;
sevseg.begin(COMMON_ANODE, digits, digitPins, segmentPins, resistorsOnSegments,
updateWithDelays, leadingZeros, disableDecPoint);
sevseg.setBrightness(90);
// 状態遷移初期化
stateMachine.setup();
}
/**
* @brief 繰り返し処理
*/
void loop()
{
// 1ms経過まで繰り返し処理をブロック
if (!is1msElapsed())
{
return;
}
// 入力
updateInput();
// 処理
stateMachine.loop();
// 出力
updateOutput();
}