// Como uso pines analógicos necesito poner resistencias pulldown en cada fotodetector.
// El estado 0 significa que no está midiendo nada.
// El estado 1023 (o casi) significa que el sensor está midiendo algo.
// Como se usa el Timer 1 no se puede usar digitalWrite en los pines: 9 y 10.
// Display: SDA va a A4 y SCL va a A5.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define pin_sensor_1 A1
#define pin_sensor_2 A2
#define pin_sensor_3 A3
#define pin_transistor_leds_IR 2
#define pin_led_indicador 3
#define pin_boton_PAUSA 4
#define pin_Encoder_SW 5
#define pin_Encoder_A 6
#define pin_Encoder_B 7
//Crear el objeto lcd dirección 0x27 (a veces es 0x3F) y 16 columnas x 2 filas
LiquidCrystal_I2C lcd(0x27, 16, 2);
int tercios_totales_de_vuelta = 0;
int vueltas = 0;
int fraccion_de_vuelta = 0;
int umbral_de_deteccion = 500; // Sale de probar con la rueda enebradora.
boolean deteccion_sensor_1 = false;
boolean deteccion_sensor_2 = false;
boolean deteccion_sensor_3 = false;
int estado_sensor_1_actual = 0;
int estado_sensor_1_actual_IR = 0;
int estado_sensor_1_actual_Ruido = 0;
int estado_sensor_1_anterior = 0;
int estado_sensor_2_actual = 0;
int estado_sensor_2_actual_IR = 0;
int estado_sensor_2_actual_Ruido = 0;
int estado_sensor_2_anterior = 0;
int estado_sensor_3_actual = 0;
int estado_sensor_3_actual_IR = 0;
int estado_sensor_3_actual_Ruido = 0;
int estado_sensor_3_anterior = 0;
volatile boolean time_to_blink = false;
boolean blink_on = false;
// Estados:
enum tipo_estado { est_Contador,
est_Modificador,
est_Pausa };
// estado_actual = tipo_estado::menu_inicial;
tipo_estado estado_actual = est_Contador;
tipo_estado estado_anterior = est_Contador;
void setup() {
Serial.begin(9600);
pinMode(pin_led_indicador, OUTPUT);
pinMode(pin_transistor_leds_IR, OUTPUT);
digitalWrite(pin_led_indicador, LOW);
digitalWrite(pin_transistor_leds_IR, LOW);
pinMode(pin_boton_PAUSA, INPUT_PULLUP);
pinMode(pin_Encoder_SW, INPUT_PULLUP);
pinMode(pin_Encoder_A, INPUT_PULLUP);
pinMode(pin_Encoder_B, INPUT_PULLUP);
// Inicializar el LCD
lcd.init();
//Encender la luz de fondo.
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Vueltas: ");
muestra_vueltas(0, 0);
setupTimer1();
}
void loop() {
// Verifica estados:
if (digitalRead(pin_boton_PAUSA) == LOW) { // Si el botón PAUSA está oprimido.
estado_actual = est_Pausa;
delay(50); // Sirve como debounce.
Estado_Pausa(); // Entra en estado Pausa.
}
else if (digitalRead(pin_Encoder_SW) == LOW) { // Si el botón del encoder está oprimido.
estado_actual = est_Modificador;
delay(50);
while (digitalRead(pin_Encoder_SW) == LOW) {} // cuando se suelta el botón se pasa al estado modificador. Tal vez, si se aprieta demasiado rápido, esta linea trae problemas.
delay(50);
Estado_Modificador(); // Entra en estado Modificador.
}
else {
estado_actual = est_Contador;
Estado_Contador(); // Entra en estado Contador.
}
}
void muestra_vueltas(int fila, int tercios_de_vuelta) {
// fila indica la fila del display que debe ser cambiada con el dato de las vueltas.
int digito_centenas;
int digito_decenas;
int digito_unidades;
vueltas = tercios_de_vuelta / 3;
fraccion_de_vuelta = tercios_de_vuelta % 3; // Toma valores 0, 1 o 2.
// opcion para poner números en 3 dígitos:
// char buf[4];
// sprintf( buf, "%03d", num );
// Serial.println( buf );
digito_centenas = ((tercios_de_vuelta / 100) % 10);
lcd.setCursor(9, fila);
lcd.print(digito_centenas);
digito_decenas = ((tercios_de_vuelta / 10) % 10);
lcd.setCursor(10, fila);
lcd.print(digito_decenas);
digito_unidades = ((tercios_de_vuelta) % 10);
lcd.setCursor(11, fila);
lcd.print(digito_unidades);
lcd.setCursor(12, fila);
lcd.print(" /3");
lcd.setCursor(13, fila);
lcd.print(fraccion_de_vuelta);
/*
Serial.print("Vueltas: ");
Serial.print(vueltas);
Serial.print(" ");
Serial.print(fraccion_de_vuelta);
Serial.println("/3");
*/
}
void setupTimer1() {
// Prepara el timer 1 para compararlo con 1 segundo.
// https://www.arduinoslovakia.eu/application/timer-calculator
noInterrupts();
// Clear registers
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// 1 Hz (16000000/((15624+1)*1024))
OCR1A = 15624;
// CTC: Clear Timer on Compare Match Mode
TCCR1B |= (1 << WGM12);
// Prescaler 1024 (también se podría usar un prescaler de 256 y un OCR1A=62496)
TCCR1B |= (1 << CS12) | (1 << CS10);
// Output Compare Match A Interrupt Enable. Se podría comparar otro tiempo como muestra GreatScott usando el March B.
TIMSK1 |= (1 << OCIE1A);
interrupts();
}
ISR(TIMER1_COMPA_vect) {
// Debe ser lo más corta posible, solo para "levantar alguna bandera" y listo.
// Al principio intenté prender y apagar el display desde acá y se me colgaba.
// Se ejecuta cada vez que se dispara la condición del timer 1.
TCNT1 = 0;
time_to_blink = true;
}
void Estado_Pausa() {
// Entra en este estado cuando se detecta que el botón PAUSA está presionado.
// Sale de la función cuando se suelta el botón PAUSA.
while (digitalRead(pin_boton_PAUSA) == LOW) {
if (time_to_blink) {
time_to_blink = false;
lcd.setBacklight(blink_on); // Se podría usar lcd.noDisplay(), que apaga toda la pantalla.
digitalWrite(pin_led_indicador, blink_on);
blink_on = !blink_on; // Si era verdadero, lo cambia por falso y al revés.
}
}
}
void Estado_Modificador() {
// Entra en este estado cuando se detecta que el botón del encoder está presionado.
// Sale de la función cuando se vuelve a presionar el botón del encoder.
// Al salir, siempre cambia el contador por el valor ingresado, así que si no se quiere modificar, hay que volver a elegir el mismo que tenía antes.
uint8_t encoder_A_actual; // uint8_t es equivalente a byte y es lo que está definido como argumento en la función digitalWrite().
uint8_t encoder_A_anterior = digitalRead(pin_Encoder_A);
int tercios_totales_de_vuelta_a_modificar = tercios_totales_de_vuelta;
lcd.setCursor(1, 0);
lcd.print("Cambiar: ");
while (digitalRead(pin_Encoder_SW) == HIGH) {
if (time_to_blink) {
time_to_blink = false;
digitalWrite(pin_led_indicador, blink_on);
blink_on = !blink_on; // Si era verdadero, lo cambia por falso y al revés.
}
encoder_A_actual = digitalRead(pin_Encoder_A);
if (encoder_A_actual != encoder_A_anterior) { // Si se rotó la perilla.
if (digitalRead(pin_Encoder_B) != encoder_A_actual) { // Se rotó en sentido horaio.
tercios_totales_de_vuelta_a_modificar++;
} else {
tercios_totales_de_vuelta_a_modificar--;
}
muestra_vueltas(1, tercios_totales_de_vuelta_a_modificar);
encoder_A_anterior = encoder_A_actual;
}
delay(50); // Es el debouncer más simple que existe.
}
tercios_totales_de_vuelta = tercios_totales_de_vuelta_a_modificar;
muestra_vueltas(0, tercios_totales_de_vuelta);
}
void Estado_Contador() {
// Entra en este estado cada vez que no está pulsado el botón del encoder o de pausa.
// Realiza un par de mediciones (con y sin LED IR) y dsevuelve el control del procesador.
// Esta técnica, sacada de https://youtu.be/-jA5W2S5PeE, permite eliminar el ruido IR de fondo.
// Además, los leds IR solo están prendidos cuando se mide el primer paso, y si no se está en este estado, permanecen apagados.
digitalWrite(pin_transistor_leds_IR, HIGH);
delay(1);
estado_sensor_1_actual_IR = analogRead(pin_sensor_1);
estado_sensor_2_actual_IR = analogRead(pin_sensor_2);
estado_sensor_3_actual_IR = analogRead(pin_sensor_3);
digitalWrite(pin_transistor_leds_IR, LOW);
delay(1);
estado_sensor_1_actual_Ruido = analogRead(pin_sensor_1);
estado_sensor_2_actual_Ruido = analogRead(pin_sensor_2);
estado_sensor_3_actual_Ruido = analogRead(pin_sensor_3);
estado_sensor_1_actual = estado_sensor_1_actual_IR - estado_sensor_1_actual_Ruido;
estado_sensor_2_actual = estado_sensor_2_actual_IR - estado_sensor_2_actual_Ruido;
estado_sensor_3_actual = estado_sensor_3_actual_IR - estado_sensor_3_actual_Ruido;
/*
// Esto es para medir cuánto vale la señal filtrada con la rueda y elegir el umbral.
Serial.print("IR:"); // No tiene que haber espacios después del texto para que se pueda usar el Serial Plotter.
Serial.print(estado_sensor_1_actual_IR);
Serial.print(",");
Serial.print("Ruido:");
Serial.print(estado_sensor_1_actual_Ruido);
Serial.print(",");
Serial.print("Filtrado:");
Serial.println(estado_sensor_1_actual);
*/
if (estado_sensor_1_actual > umbral_de_deteccion && estado_sensor_1_anterior < umbral_de_deteccion) {
// ACÁ OCURRE LA DETECCIÓN.
deteccion_sensor_1 = true;
if (deteccion_sensor_3 == true) {
tercios_totales_de_vuelta++;
deteccion_sensor_3 = false;
muestra_vueltas(0, tercios_totales_de_vuelta);
} else if (deteccion_sensor_2 == true) {
tercios_totales_de_vuelta--;
deteccion_sensor_2 = false;
muestra_vueltas(0, tercios_totales_de_vuelta);
}
// Buzzer...
digitalWrite(pin_led_indicador, HIGH); // se puede mejorar con algún contador para que se mantenga encendido más tiempo.
} else if (estado_sensor_2_actual > umbral_de_deteccion && estado_sensor_2_anterior < umbral_de_deteccion) {
// ACÁ OCURRE LA DETECCIÓN.
deteccion_sensor_2 = true;
if (deteccion_sensor_1 == true) {
tercios_totales_de_vuelta++;
deteccion_sensor_1 = false;
muestra_vueltas(0, tercios_totales_de_vuelta);
} else if (deteccion_sensor_3 == true) {
tercios_totales_de_vuelta--;
deteccion_sensor_3 = false;
muestra_vueltas(0, tercios_totales_de_vuelta);
}
digitalWrite(pin_led_indicador, HIGH); // se puede mejorar con algún contador para que se mantenga encendido más tiempo.
} else if (estado_sensor_3_actual > umbral_de_deteccion && estado_sensor_3_anterior < umbral_de_deteccion) {
// ACÁ OCURRE LA DETECCIÓN.
deteccion_sensor_3 = true;
if (deteccion_sensor_2 == true) {
tercios_totales_de_vuelta++;
deteccion_sensor_2 = false;
muestra_vueltas(0, tercios_totales_de_vuelta);
} else if (deteccion_sensor_1 == true) {
tercios_totales_de_vuelta--;
deteccion_sensor_1 = false;
muestra_vueltas(0, tercios_totales_de_vuelta);
}
digitalWrite(pin_led_indicador, HIGH); // se puede mejorar con algún contador para que se mantenga encendido más tiempo.
}
estado_sensor_1_anterior = estado_sensor_1_actual;
estado_sensor_2_anterior = estado_sensor_2_actual;
estado_sensor_3_anterior = estado_sensor_3_actual;
digitalWrite(pin_led_indicador, LOW); // se puede mejorar con algún contador para que se mantenga encendido más tiempo.
delay(100); // Para que no esté todo el tiempo encendiendo y apagando los leds.
}