/*
Programa de control del domo Goethe
Enrique Rubinstein y Osvaldo Ortiz
Versión de prueba V07.7.1 ESP32 Febrero 2 2025
Esta versión incluye :
Como medida de seguridad suenan tres bips antes que el motor se ponga en funcionamiento
Modo de funcionamiento manual:
- Avance a un ángulo determinado por el caminao más corto
- Avance discreto CW y CCW fijado en 10 grados
- Búsqueda del punto Cero. El motor avanza hasta que el sensor de efecto
Hall detecta el imán o una vuelta completa , lo que suceda primero.
Modo de funcionamiento automático (Nina)
**En esta version no se implemento la vuelta ala menu principal**
- Lee la puerta serie y reconoce dos comandos según las especificaciones de Emilio:
Si recibe el comando "S" (Slew) mueve el motor al ángulo especificado por el camino más corto.
Si recibe el comando "K" ejecuta la rutina de búsqueda del Cero.
Esta versión lee directamente la puerta e interpreta el comando, no se incluye un buffer por si arriba algún
comando mientras se esté ejecutando el anterior, además el library utilizado es bloquente por lo que
mientras se mueve el motor se detiene la ejecución del programa
*/
#include <Keypad.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <AccelStepper.h>
const int stepPin = 32; // Pin para el pulso (STEP)
const int dirPin = 33; // Pin para la dirección (DIR)
const int buzzerPin = 25; // Pin Buzzer en el ESP32
// Crea una instancia de AccelStepper para driver MDA860E
AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);
const int buttonPin = 35; // GPIO donde está conectado el interruptor Hall de deteccion de cero
int buttonState = 0; // Variable que almacena el estado del interruptor de punto cero (Hall Switch)
//Definiciones del teclado
// define numero de filas del teclado
const uint8_t ROWS = 4;
// define numero de columnas del teclado
const uint8_t COLS = 4;
// define la distribucion de teclas
char keys[ROWS][COLS] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' }
};
//Teclado chicle
uint8_t colPins[COLS] = { 2, 0, 4, 16 }; // pines correspondientes a las columnas
uint8_t rowPins[ROWS] = { 17, 19, 18, 5}; //Pines correspondientes a las filas
// Teclado Negro
//uint8_t colPins[COLS] = { 23, 19, 18, 5}; // pines correspondientes a las columnas
//uint8_t rowPins[ROWS] = { 17, 16, 4, 15}; //Pines correspondientes a las filas
// crea objeto con los prametros creados previamente
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
LiquidCrystal_I2C lcd(0x27, 20, 4); //Definición del display
enum Screen //Estados posibles de las pantallas
{
IntroScreen,
MainMenu,
MenuAngle,
MenuDiscreet,
SetToZero,
Nina
};
Screen currentScreen = Screen::IntroScreen;
//Variables para la lectura serie
String buffer = ""; // Variable para almacenar los caracteres leídos
bool reading = false; // Indica si estamos en modo de lectura de datos
unsigned long previousMillis = 0; // Almacena la última vez que se leyó
void setup() {
Serial.begin(9600);
Wire.begin();
lcd.init();
lcd.backlight();
// Configura la velocidad máxima y la aceleración
stepper.setMaxSpeed(1500);
stepper.setSpeed(1000);
stepper.setAcceleration(50);
pinMode(buttonPin, INPUT); //inicializa el pin del interruptor Hall como entrada
pinMode(buzzerPin, OUTPUT); //Inicializa el pin del buzzer como salida
//keypad.setDebounceTime(250); // Default debounce is 50 mS. Modoficar solo si es necesario
}
void loop() {
lcd.clear();
switch(currentScreen)
{
case IntroScreen:
renderIntroScreen();
break;
case MainMenu:
renderMainMenu();
break;
case MenuAngle:
renderMenuAngle();
break;
case MenuDiscreet:
renderMenuDiscreet();
break;
case SetToZero:
renderSetToZero();
break;
case Nina:
renderNina();
break;
}
}
void renderIntroScreen()
{
const long interval = 20000UL;
unsigned long currentMillis = millis();
lcd.print("Control de Domo"); // Muestra Presentación
lcd.setCursor(0, 1);
lcd.print("V0.7.2LAB 11-03-2025");
lcd.setCursor(0, 2);
lcd.print("Auto a Nina ");
lcd.print ((interval - currentMillis - previousMillis)/1000UL+1);
lcd.print(" Seg");
lcd.setCursor(0, 3);
lcd.print("A=manual B=Nina");
//delay(2000); //Pausa para ver la versión en el display
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
currentScreen = Screen::MainMenu;
}
/*
char menuSelect = readMenuFromKeypad(); //Espera que se ingrese la opción desde el teclado
switch (menuSelect) {
case 'A':
currentScreen = Screen::MainMenu;
break;
case 'B':
currentScreen = Screen::Nina;
break;
if (currentMillis - previousMillis >= 1000) {
previousMillis = currentMillis;
currentScreen = Screen::IntroScreen;
}
}
*/
}
void renderMainMenu()
{
lcd.print("Ingrese una opcion");
lcd.setCursor(0, 1);
lcd.print("A=Avance a Angulo");
lcd.setCursor(0, 2);
lcd.print("B=Avance Discreto");
lcd.setCursor(0, 3);
lcd.print("C=Set Zero D=NINA");
char menuSelect = readMenuFromKeypad(); //Espera que se ingrese la opción desde el teclado
switch (menuSelect) {
case 'A':
currentScreen = Screen::MenuAngle;
break;
case 'B':
currentScreen = Screen::MenuDiscreet;
break;
case 'C':
// Hace sonar el buzzer tres veces antes que empiece el movimiento
for (int ii = 0; ii < 3; ii++) {
tone(buzzerPin, 1000); // Ajusta la frecuencia del tono
delay(200);
noTone(buzzerPin);
delay(200);
}
currentScreen = Screen::SetToZero;
break;
case 'D':
currentScreen = Screen::Nina;
break;
}
}
String angle;
void renderMenuAngle()
{
lcd.setCursor(0, 0);
lcd.print("Ingrese Angulo");
lcd.setCursor(0, 1);
lcd.print("Ang ingresado:" + angle);
lcd.setCursor(0, 2);
lcd.print("C= Clear #=Enter");
lcd.setCursor(0, 3);
lcd.print("*= Menu Anterior");
char option = readMenuFromKeypad();
int angleDegrees;
switch(option)
{
case 'C':
angle = "";
break;
case '*':
angle = "";
currentScreen = Screen::MainMenu;
break;
case '#':
angleDegrees = angle.toInt();
angleDegrees = angleDegrees % 360;
rotateMotor(angleDegrees);
angle = "";
break;
case 'A':
break;
case 'B':
break;
case 'D':
break;
default:
angle = angle + option;
}
}
void renderNina()
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Modo AUTO - NINA");
lcd.setCursor(0, 1);
lcd.print("Press * for mainMenu");
delay(1000);
/*char menuSelect = readMenuFromKeypad(); //Espera que se ingrese la opción desde el teclado
switch (menuSelect) {
case '*':
currentScreen = Screen::MainMenu;
break;
//}
default:
{
*/
//Comienza lectura puerta serie esperando comando S (Slew) o K (puesta en Cero)
if (Serial.available()) { // Verifica si hay datos disponibles en la puerta serie
char incomingChar = Serial.read(); // Lee un carácter
Serial.println("Iniciando comunicacion...");
if (incomingChar == ':') {
reading = true; // Inicia la captura de caracteres
buffer = ""; // Limpia el buffer
} else if (incomingChar == '#' && reading) {
reading = false; // Finaliza la captura de caracteres
procesarBuffer(buffer); // Procesa el contenido del buffer
} else if (reading) {
buffer += incomingChar; // Almacena los caracteres en el buffer
}
}
//}
//}
}
void procesarBuffer(String data) {
if (data.length() >= 1) {
char secondChar = data[0];
if (secondChar == 'S') {
// Verifica si el primer carácter es 'S' y hay al menos 3 caracteres adicionales
if (data.length() >= 4) {
String angulo = data.substring(1, 4); // Toma los siguientes 3 caracteres
Serial.print("Angulo recibido: ");
Serial.println(angulo); // Imprime el ángulo recibido
lcd.setCursor(0, 2);
lcd.print("Recibido:");
lcd.setCursor(0, 3);
lcd.print("SLEW: ");
lcd.print(angulo);
delay(2000);
int angleDegrees = angulo.toInt();
angleDegrees = angleDegrees % 360;
rotateMotor(angleDegrees);
angle = "";
}
else {
Serial.println("Error: Datos insuficientes después de 'S'.");
}
}
else if (secondChar == 'K') {
// Verifica si el primer carácter es 'K'
Serial.println("Búsqueda de Cero");
lcd.setCursor(0, 3);
lcd.print("Busqueda de cero");
delay(2000);
// Hace sonar el buzzer tres veces antes que empiece el movimiento
for (int ii = 0; ii < 3; ii++) {
tone(buzzerPin, 1000); // Ajusta la frecuencia del tono
delay(200);
noTone(buzzerPin);
delay(200);
}
currentScreen = Screen::SetToZero;
}
else if (secondChar == 'H') { // Si el segundo caracter es 'H'
stepper.stop();
Serial.println("Detener motor"); // Imprimir el mensaje por la puerta serie
lcd.setCursor(0, 3);
lcd.print("DETENCION!!");
delay(2000);
}
else {
Serial.println("Error: Formato de datos incorrecto o no reconocido.");
}
} else {
Serial.println("Error: Datos insuficientes.");
}
}
int stepCount = 0;
void renderSetToZero()
{
digitalWrite(dirPin, HIGH); //define dirección del giro del motor
//*********************************************************************************
//if (stepCount < 7600) //limita a un solo giroMotor NEMA Enrique
//if (stepCount < 400) //limita a un solo giro Motor NEMA 17 400 pasos
//if (stepCount < 107555) //limita a un solo giro Motor Goethe
// if (stepCount < 2200) //Limita una vuelta para motor de 400 pasos colocado en la maqueta
if (stepCount < 200) //limita a un solo giro Motor Wokwi 200 pasos
//*********************************************************************************
{
// las siguientes cuatro lineas avanzan un paso
digitalWrite(stepPin, HIGH);
delayMicroseconds(500);
digitalWrite(stepPin, LOW);
delayMicroseconds(500);
stepCount = stepCount + 1;
// lee el estado del boton
buttonState = digitalRead(buttonPin);
if (buttonState == LOW) currentScreen = Screen::MainMenu;
}
else {
stepCount = 0;
currentScreen = Screen::MainMenu;
}
}
float angleDegrees = stepper.currentPosition();
void renderMenuDiscreet() {
angleDegrees = stepper.currentPosition();
Serial.print("angleDegrees0= ");
Serial.println(angleDegrees);
delay(500);
lcd.setCursor(0, 0);
lcd.print("Ingrese Opcion");
lcd.setCursor(0, 1);
lcd.print("A Avance --->>");
lcd.setCursor(0,2);
lcd.print("B Avance <<---");
lcd.setCursor(0, 3);
lcd.print("*=Menu Ant");
char option = readMenuFromKeypad();
switch(option)
{
case '*':
angle = "";
currentScreen = Screen::MainMenu;
break;
case '#':
break;
case 'A':
Serial.print("angleDegrees1= ");
Serial.println(angleDegrees);
delay(1000);
//*********************************************************************************
angleDegrees = angleDegrees *1.8 + 10.0; //PARA MOTOR 200 pasos Wokwi
//angleDegrees = angleDegrees *0.9 + 10.0; //PARA MOTOR 400
//angleDegrees = angleDegrees *0.1636 + 10.0; //Para motor 400 pasos instaldo en la maqueta
//angleDegrees = angleDegrees *0.04737 + 10.0; //PARA MOTOR NEMA ENRIQUE
// angleDegrees = angleDegrees *0.003347 + 10.0; //PARA GOETHE
//*********************************************************************************
rotateMotor(angleDegrees);
break;
case 'B':
//*********************************************************************************
angleDegrees = angleDegrees *1.8 - 10.0; //PARA MOTOR 200 pasos Wokwi
//angleDegrees = angleDegrees *0.9 - 10.0; //PARA MOTOR 400
//angleDegrees = angleDegrees *0.1636 - 10.0; //Para motor 400 pasos instaldo en la maqueta
//angleDegrees = angleDegrees *0.04737 - 10; //PARA MOTOR NEMA ENRIQUE
//angleDegrees = angleDegrees *0.003347 - 10; //PARA GOETHE
//*********************************************************************************
rotateMotor(angleDegrees);
break;
case 'C':
break;
case 'D':
break;
default:
angle = angle + option;
}
}
void rotateMotor(int angle)
{
// Calcula los pasos necesarios para el ángulo ingresado
long stepsNeeded = 0; // pasos necesarios para girar el angulo ingresado por camino mas corto
long currentsteps = stepper.currentPosition(); // Obtiene la posición actual;
//*********************************************************************************
long totalSteps = 200; //***Para prueba en wokwi ****
//long totalSteps = 400; //Motor 400 paso por vuelta
//long totalSteps = 2200; //Motor 400 pasos instalado en la maqueta
//long totalSteps = 107555; //484/9*5*400 Para telescopio Goethe
//long totalSteps = 7600; //Para motor NEMA Enrique
//*********************************************************************************
long targetsteps = calculateStepsForAngle(angle, totalSteps); // calculo de pasos necesarios para girar el angulo ingresado
// Muestra el resultado en el display
lcd.clear();
lcd.print("Angulo: ");
lcd.print(angle);
lcd.print("\337"); //Imprime el símbolo de grado °
// Hace sonar el buzzer tres veces antes que empiece el movimiento
for (int ii = 0; ii < 3; ii++) {
tone(buzzerPin, 1000); // Ajusta la frecuencia del tono
delay(200);
noTone(buzzerPin);
delay(200);
}
// Mueve la corona al ángulo deseado
if ((targetsteps - currentsteps) > (totalSteps / 2)) {
stepsNeeded = (targetsteps -totalSteps - currentsteps);
} else {
stepsNeeded = targetsteps;
}
stepper.moveTo(stepsNeeded);
// Run to target position with set speed and acceleration/deceleration:
stepper.runToPosition();
lcd.setCursor(0, 1);
lcd.print("Pasos: ");
lcd.print(stepsNeeded);
// Espera un tiempo para mostrar la posición final (opcional)
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pos. final: ");
lcd.print(stepper.currentPosition());
lcd.setCursor(0, 1);
lcd.print("Angulo final: ");
lcd.print(angle);
lcd.print("\337"); //Imprime el símbolo de grado °
delay(2000);
}
// Función para leer desde el teclado la opción de menú
char readMenuFromKeypad() {
String input = "";
char key = 0;
while (input.length() < 1) {
key = keypad.getKey();
if (key) {
input += key;
delay(100); // Pequeña pausa para evitar lecturas duplicadas
}
}
return key;
}
// Función para calcular los pasos necesarios para un ángulo dado
long calculateStepsForAngle(int angulo, long totalSteps) {
// Calcula los pasos necesarios para el ángulo deseado
// (360 grados = totalSteps pasos)
return map(angulo, 0, 360, 0, totalSteps);
}