//**************************************************************//
// Name : Timer with 74595
// Author : Luiz Paulo Grafetti Terres
// Date : 21 Mar, 2024
// Modified: Mar, 2024
// Version :
// Notes : Code for using a 74HC595 Shift Register
// : to simulate a timer using 3 seven-segments displays
//****************************************************************
//Pin connected to ST_CP of 74HC595
int latchPin = 8;
//Pin connected to SH_CP of 74HC595
int clockPin = 12;
////Pin connected to DS of 74HC595
int dataPin = 11;
// Pins connected to Set/Up/Down buttons
int setPin = 7;
int upPin = 6;
int downPin = 5;
// Pin connected to buzzer
int buzzer = 9;
void setup() {
//set pins to output so you can control the shift register
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
//set pin to output buzzer
pinMode(buzzer, OUTPUT);
//set pins to input using pullup internal mode
pinMode(setPin, INPUT_PULLUP);
pinMode(upPin, INPUT_PULLUP);
pinMode(downPin, INPUT_PULLUP);
Serial.begin(9600);
}
// display-related utils
#define OFF 0
// sev seg display binaries
byte codes[] = {63, 6, 91, 79, 102, 109, 125, 7, 127, 111};
// states
#define INI_WAIT 0
#define SET_TIME 1
#define RUN_TIME 2
#define PLAY_BUZ 3
// substates
byte SUBSTATE = 0;
// time-related utils
#define MINS_MAX 10
#define DSEC_MAX 6
#define SECS_MAX 10
#define MINS 0
#define DSEC 1
#define SECS 2
/* TIMER = { MINS, DSEC, SECS } */
byte timer[] = {0,0,0};
/* UTILS variables for the program to start well */
byte STATE = INI_WAIT;
byte last_state = HIGH;
byte read_state = HIGH;
unsigned long last_debounce_time = 0;
/* tempo arbitrário de espera com botão pressionado para entrar em RUN_TIME*/
const unsigned long debounce_delay_expected = 2000;
/* usado para controlar a quantia total de segundos setados no timer*/
int seconds = 0;
/* usado para controlar alguns flashes nos displays (piscar)*/
byte pair = 0;
/* ! UTILS variables for the program to start well */
void run_timer() {
for (int second = 0; second <= seconds; second++) {
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, codes[timer[SECS]]);
shiftOut(dataPin, clockPin, MSBFIRST, codes[timer[DSEC]]);
shiftOut(dataPin, clockPin, MSBFIRST, codes[timer[MINS]]);
digitalWrite(latchPin, HIGH);
delay(1000);
/* decrescenta os segundos */
if (timer[SECS] != 0) { timer[SECS]--;}
/* trata de: 10s -> 09 s */
else if (timer[DSEC] != 0) { timer[SECS] = SECS_MAX-1; timer[DSEC]--;}
/* trata de: 1min -> 59 s */
else if (timer[MINS] != 0) { timer[SECS] = SECS_MAX-1; timer[DSEC] = DSEC_MAX-1; timer[MINS]--;}
}
seconds = 0;
}
long long int value = millis();
void setting_timer() {
byte read_state_ = HIGH;
byte last_state_ = HIGH;
byte up_read = HIGH;
byte down_read = HIGH;
byte last_up_read = HIGH;
byte last_down_read = HIGH;
byte max_substate_value[] = {MINS_MAX, DSEC_MAX, SECS_MAX};
value = millis();
while (SUBSTATE < 3) {
up_read = digitalRead(upPin);
down_read = digitalRead(downPin);
/* valores de cada display são atualizados de acordo com as entradas
* capturadas dos botões UP e DOWN.
* NOTA: mesmo que o usuário mantenha o botão pressionado, isso irá contar como uma (1) unidade de pressão
* NOTA2: ao chegar ao máximo esperado de cada display, o valor retorna a 0;
* e ao chegar no mínimo esperado, o valor do display mantém-se no mínimo;
*/
if ((up_read != last_up_read && (up_read == LOW)) || (down_read != last_down_read && (down_read == LOW)))
if (timer[SUBSTATE] == 0) timer[SUBSTATE] = !up_read;
else timer[SUBSTATE] = (timer[SUBSTATE] + !up_read - !down_read) % max_substate_value[SUBSTATE];
delay(50);
last_up_read = up_read;
last_down_read = down_read;
/* atualiza o display, piscando o valor que está sendo atualizado
* utilizando-se do valor de SUBSTATE */
flash_display();
/* a cada vez que o botão SET é acionado, o próximo display será
* selecionado para edição de valor */
read_state_ = digitalRead(setPin);
if ((read_state_ != last_state_) && (read_state_ == LOW))
SUBSTATE++; max_substate_value[SUBSTATE];
delay(50);
last_state_ = read_state_;
/* responsible for led blinking */
if (millis() - value > 150) {
value = millis();
pair = !pair;
}
}
/* a paridade setada para 0 garante que todos os displays de
* sete segmentos permanecerão ativos (mostrando seus valores)
* ao final da configuração do timer */
pair = 0;
flash_display();
}
void flash_display() {
/* essa função pisca o display que está sendo configurado
* na etapa SET_TIMER. (o display apaga em passadas pares). */
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST,
((SUBSTATE == SECS) && pair) ? OFF : codes[timer[SECS]]);
shiftOut(dataPin, clockPin, MSBFIRST,
((SUBSTATE == DSEC) && pair) ? OFF : codes[timer[DSEC]]);
shiftOut(dataPin, clockPin, MSBFIRST,
((SUBSTATE == MINS) && pair) ? OFF : codes[timer[MINS]]);
digitalWrite(latchPin, HIGH);
}
void loop() {
switch (STATE) {
case INI_WAIT: //0
Serial.println("INI_WAIT: aguardando uma entrada SET.");
// INI [captura botão SET]
read_state = digitalRead(setPin);
/* ENTRA SE: o último estado era diferente do atual.
* NOTA: usa borda de subida (read_state==HIGH) para evitar
* erros ao segurar o botão por muito tempo */
if ((read_state != last_state) && read_state == HIGH)
STATE = (STATE + 1) % 4;
last_state = read_state;
delay(50);
// FIM [captura botão SET]
/* correção dos valores úteis para o próximo estado: SET_TIME */
if (STATE == SET_TIME) last_state = HIGH; SUBSTATE = 0;
break;
case SET_TIME: //1
if (!SUBSTATE) {
Serial.println("SET_TIME: escolha a configuração do timer:");
/* somente chama a função setting_timer() se o SUBSTATE for 0 */
setting_timer();
} else
Serial.println("SET_TIME: pressione DOWN por 2s:");
// INI [captura botão DOWN]
read_state = digitalRead(downPin);
/* salva o momento em que o botão for pressionado*/
if (read_state != last_state)
last_debounce_time = millis();
/* ENTRA SE => o botão ainda estiver pressionado,
* & o tempo desde que ele foi inicialmente pressionado > 2000ms */
if ((read_state == LOW) && (millis() - last_debounce_time > debounce_delay_expected))
STATE = (STATE + 1) % 4;
last_state = read_state;
delay(50);
// FIM [captura botão SET]
/* RUN_TIME não possui restrições de last_state ou outras*/
break;
case RUN_TIME: // RUN_TIME = 2
Serial.println("RUN_TIME: aguarde o timer zerar.");
/* captura a quantia de segundos total da operação */
seconds = timer[MINS] * 60 + timer[DSEC] * 10 + timer[SECS] * 1;
if (seconds > 0) run_timer();
/* passa para o próximo estado quando o timer zera */
STATE = (STATE + 1) % 4;
/* envia last_state = LOW para o próximo estado */
last_state = LOW; value = millis();
break;
case PLAY_BUZ: //3
Serial.println("PLAY_BUZ: pressione UP por 2s para parar");
/* responsible for buzzer pausing */
if (millis() - value > 300) {
value = millis();
pair = !pair;
}
/* sinal de som de 1KHz*/
if (pair) tone(buzzer, 1000);
else noTone(buzzer);
// INI [captura botão UP]
read_state = digitalRead(upPin);
/* salva o momento em que o botão for pressionado*/
if (read_state != last_state) {
last_debounce_time = millis();
}
/* ENTRA SE => o botão ainda estiver pressionado,
* & o tempo desde que ele foi inicialmente pressionado > 2000ms */
if ((read_state == LOW) && (millis() - last_debounce_time > debounce_delay_expected)) {
STATE = (STATE + 1) % 4;
}
last_state = read_state;
delay(50);
// FIM [captura botão UP]
/* envio HIGH para o INI_WAIT */
if (STATE == INI_WAIT) {
last_state = HIGH;
/* para o buzzer */
noTone(buzzer);
}
break;
}
}