/* ################## Percepduino #################
* Filename: Percepduino1.ino
* Versión: 0.3c (24-01-2025)
* Descripción: Perceptron simple de dos entradas,
* Entrenamiento con puertas logicas
*
* Emulación: https://wokwi.com/projects/420615368379723777
*
* Autor: Jose M Morales
* Web: https://playbyte.es/electronica/20250122_percepduino
* Licencia: Creative Commons Share-Alike 4.0
* https://creativecommons.org/licenses/by-nc-sa/4.0/
* ###################################################*/
/********** Pantalla LCD20x4, protocolo I2C *********/
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR 0x27
#define LCD_COLUMNS 20
#define LCD_LINES 4
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
/*****************************************************/
/***************** Definicion pines *****************/
// Entradas analógicas
#define SENSOR_POT1 A1 // potenciómetro 1
#define SENSOR_POT2 A2 // potenciómetro 2
// Salidas
const int pin_Y_hat1 = 2;// LED rojo
const int pin_Y_hat0 = 3;// LED verde
/************** DATOS DE ENTRENAMIENTO **************/
// valores iniciales, hiperparametros
const float lr = 0.1; // learning rate
const float w1_i = 0.5; // peso inicial
const float w2_i = 0.5;
const float b_i = 0.25;
const int max_epoch =50; // limite de iteraciones
// Entradas comunes para todas las puertas logicas
const int N_samples = 4;
const int X_train[][2] = {
{0, 0},
{0, 1},
{1, 0},
{1, 1}
};
/********* Selecciona la puerta a entrenar ***********/
int y_train[N_samples] = {0, 0, 0, 1}; // puerta AND
//int y_train[N_samples] = {0, 1, 1, 1}; // puerta OR
//int y_train[N_samples] = {0, 1, 1, 0}; // puerta XOR (sin solucion)
/*****************************************************/
//############### Clase PERCEPDUINO ################
class Percepduino {
private:
float w1, w2, b; // Pesos y bias
float lr; // Tasa de aprendizaje
public:
String msg; // Texto para mostrar en el LCD
int total_epocas;
int total_errores;
// === Constructor ====
Percepduino(float learning_rate = 0.1,
float w1_0 = 0,
float w2_0 = 0,
float b_0 = 0) {
lr = learning_rate;
// Inicializa pesos con valores aleatorios
w1 = (w1_0 == 0) ? random(0, 10001) / 10000.0 : w1_0;
w2 = (w2_0 == 0) ? random(0, 10001) / 10000.0 : w2_0;
b = ( b_0 == 0) ? random(0, 10001) / 10000.0 : b_0;
msg = "";
}
int predict(float x1, float x2) {
float z = (x1 * w1 + x2 * w2) + b;
return z >= 1 ? 1 : 0;
}
void train(int X[][2], int y[], int N_samples, int max_epoch) {
bool error; // 0:punto bien clasificado, 1:mal
for (int epoch = 0; epoch < max_epoch; epoch++) {
total_errores = 0;
for (int n = 0; n < N_samples; n++) {
int y_hat = predict(X[n][0], X[n][1]);
error = y[n] - y_hat;
if (error != 0) {
total_errores++;
w1 += lr * error * X[n][0];
w2 += lr * error * X[n][1];
b += lr * error;
}
}// bucle con todos los puntos
/*
Serial.println("epoch= "+ String(epoch) +" -------------------");
Serial.println(" Total errores= "+ String(total_errores));
Serial.println(" w1= "+ String(w1) +"; w2= "+ String(w2)) +"; b= "+ String(b);
*/
total_epocas=epoch;
if (total_errores == 0) {
msg = "** FIN entrenamiento ";
msg+= "\n Epoch: " + String(epoch);
msg+= "\n w1=" + String(w1) + "; w2=" + String(w2);
msg+= "\n b=" + String(b);
return;
}
}// Fin bucle de epoch
msg = "**** ENTRENAMIENTO ";
msg+= "\n NO COMPLETADO ****";
msg+= "\n Alcanzado Limite";
msg+= "\n max_epoch= " + String(max_epoch);
}// FIN funcion train()
float get_lr() { return lr; }
float get_w1() { return w1; }
float get_w2() { return w2; }
float get_b() { return b; }
int get_epoch() { return total_epocas; }
};
//###################################################
// Inicializa perceptron (instancia global)
Percepduino perceptron(lr, w1_i, w2_i, b_i);
//###################################################
void setup() {
pinMode(pin_Y_hat0, OUTPUT);
pinMode(pin_Y_hat1, OUTPUT);
// Presentacion por pantalla y puerto serie
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print(" PERCEPDUINO v0.3 ");
lcd.setCursor(0, 1);
lcd.print("====================");
lcd.setCursor(0, 3);
lcd.print(" Entrenando... ");
Serial.begin(9600);
Serial.print(" PERCEPDUINO v0.3\n");
Serial.print("==================\n");
Serial.print(" Entrenando...\n");
/*
// Muestra tasa de aprendizaje y pesos por defecto
Serial.println(" lr= "+ String(percepduino.get_lr()));
Serial.print(" w1= "+ String(percepduino.getW1()));
Serial.print("; w2= "+ String(percepduino.getW2()));
Serial.print("; b= "+ String(percepduino.getB()));
Serial.println("\n----------------------------------------");
*/
// Inicia entrenamiento
perceptron.train(X_train, y_train, N_samples, max_epoch);
delay(2000);
lcd.clear();
mostrarTextoLCD(perceptron.msg);
if (perceptron.total_errores ==0) {
Serial.println("\n---------------------------------------------");
Serial.println("Entrenamiento completado en "+String(perceptron.get_epoch())+ " iteraciones:");
Serial.println("[w1, w2, b]= ["+ String(perceptron.get_w1())+", "+ String(perceptron.get_w2())+", "+String(perceptron.get_b())+"]");
Serial.println("recta --> x2 = ("+String(-perceptron.get_w1()/perceptron.get_w2())+")x1 + ("+String(-perceptron.get_b()/perceptron.get_w2())+")");
}else{
Serial.println("\n---------------------------------------------");
Serial.println("Entrenamiento FALLIDO ("+String(1+perceptron.get_epoch())+ " iteraciones)");
Serial.println(" Prueba a modificar los hiperparametros");
Serial.println("\n---------------------------------------------");
// error -> LED rojo
digitalWrite(pin_Y_hat1, 1);
digitalWrite(pin_Y_hat0, 0);
while (true) {}// bucle infinito -> FIN DEL PROGRAMA
}
delay(2000);
lcd.clear();
}
void loop() {
// Obtiene valores introducidos
float x1 = 1 - analogRead(SENSOR_POT1) / 1023.0;
float x2 = 1 - analogRead(SENSOR_POT2) / 1023.0;
// Obtiene la prediccion
int y_hat = perceptron.predict(x1, x2);
if (y_hat) {
// y_hat=1 (clase 1) -> LED rojo+verde=amarillo
digitalWrite(pin_Y_hat1, 1);
digitalWrite(pin_Y_hat0, 1);
}else{
// y_hat=0 (clase 0) -> LED verde
digitalWrite(pin_Y_hat1, 0);
digitalWrite(pin_Y_hat0, 1);
}
String texto = "";
texto = " Mueve Pot1 y Pot2 \n";
texto += "====================\n";
texto += "x1=" + String(x1) + " x2=" + String(x2) +"\n";
texto += "Salida: y_hat=" + String(y_hat);
mostrarTextoLCD(texto);
}
//#############################################
void mostrarTextoLCD(String texto) {
// Serial.print(texto);
int line = 0;
while (texto.length() > 0 && line < 4) {
int index = texto.indexOf('\n');
if (index == -1) {
lcd.setCursor(0, line);
lcd.print(texto);
break;
} else {
lcd.setCursor(0, line);
lcd.print(texto.substring(0, index));
texto = texto.substring(index + 1);
line++;
}
}
}
x1
x2
y_hat