martes, 12 de mayo de 2026

ESP32-C3 con pantalla Oled 0.42" con ejemplos

ESP32 C3 Oled 0.42 logo

Muy buenas a todas y todos!!!

Después de la buena experiencia programando el ESP32-C3 mini junto con el módulo de la pantalla SH1106 y el encoder, y otro proyecto que aún estoy probando.

Este pequeño no comparte los mismos pines que el ESP32C3 mini así que si ya tienes un montaje realizado para el mini no te servirá, pero eso no quita que sus pines GPIO sean igual de versátiles, veamos las características y los pines de esta pequeña placa de desarrollo:



Hardware e interfaces

  • 22 pines GPIO programables.
  • Comunicación vía UART, SPI, I2C, I2S y USB Serie/JTAG.
  • Funciones analógicas: dos SAR de 12 bits Convertidores analógico-digitales (ADC), sensor de temperatura integrado.

Procesador y memoria

ESP32 C3 Oled 0.42 chip
  • Procesador: ESP32-C3FN4/FH4, RISC-V de 32 bits de un solo núcleo, hasta 160 MHz.
  • Memoria flash: 4 MB.
  • Pantalla: OLED de 0,42 pulgadas para información visual directa.
  • Conectividad: Wi-Fi de modo dual (802.11b/g/n) y Bluetooth 5 (LE, malla).
  • Programación: Fácil vía USB, en este caso USB-C.
  • Antena: Antena cerámica integrada.


Funciones de Wi-Fi y Bluetooth

El ESP32 integra conectividad dual Wi-Fi y Bluetooth 5 LE, optimizada para aplicaciones que requieren velocidad y eficiencia. Estas son sus capacidades más relevantes:

  • Wi-Fi con velocidades de datos de hasta 150 Mbps, compatible con ancho de banda de 20/40 MHz.
  • Bluetooth 5 LE con alta potencia de transmisión de hasta 20 dBm y red mallada.
  • Coexistencia interna entre Wi-Fi y Bluetooth para el uso de antena compartida.
  • Compatible con múltiples modos Wi-Fi: Estación, SoftAP y modo promiscuo.

Bajo consumo de energía y seguridad

El ESP32 ha sido diseñado para ofrecer un rendimiento óptimo en aplicaciones IoT sin sacrificar la eficiencia energética ni la protección de los datos.
A continuación, se detallan sus características más destacadas en este ámbito:

  • Cuatro modos de ahorro de energía, incluyendo un modo de suspensión profunda con un consumo de corriente de tan solo 5 µA.
  • Funciones de seguridad como arranque seguro y cifrado de memoria flash.
  • Aceleración criptográfica por hardware: AES, SHA, RSA y más.

Aplicaciones

Gracias a su bajo consumo de energía y su versátil conectividad, esta placa es perfecta para numerosas aplicaciones como:

  • Dispositivos para el hogar inteligente
  • Automatización industrial
  • Atención médica
  • Electrónica de consumo
  • Agricultura inteligente
  • Robots de servicio
  • Sensores IoT de bajo consumo y registradores de datos


La placa ESP32-C3 con pantalla OLED de 0,42".


Esta placa IoT compacta se basa en el chip ESP32-C3 (RISC-V) e incorpora una pantalla OLED de 0,42 pulgadas. Como la distribución de pines y el controlador de pantalla no son iguales que la de los módulos ESP32 estándar

A continuación os dejamos la información necesaria para el correcto funcionamiento de la placa.

Especificaciones del hardware

  • Chip: ESP32-C3FN4 con una Memoria Flash de 4 MB, Wi-Fi y Bluetooth 5.0 BLE.
  • Conexión USB: USB nativo USB-C Para Habilitar la comunicación con el monitor serial active en el IDE de Arduino "USB CDC al arrancar".
  • Pantalla OLED: 0,42 pulgadas con una resolución de 72 x 40 píxeles.
  • Dimensiones: Es muy compacta aproximadamente 20 x 25 mm.


Asignación de pines y conexiones

ESP32 C3 Oled 0.42 cubo

La documentación en línea suele ser confusa. Utilice la siguiente asignación de pines para esta placa específica:

  • I2C OLED SDA: GPIO 5 (aunque generalmente se indica el 8.
  • I2C OLED SCL: GPIO 6.
  • LED Integrado: GPIO 8.
  • Boton de reset: GPIO 9.
  • Tx; GPIO 21.
  • Rx: GPIO 20.

Configurando del IDE de Arduino

Para programar placa correctamente vamos seleccionar la siguiente configuración en el IDE de Arduino:

  • Placa: Módulo de desarrollo ESP32C3 Dev Module.
  • USB CDC al arrancar: Habilitado (Para salida de Serial.print).
  • Modo de flasheo: DIO (predeterminado).

Algunas veces podemos experimentar problemas con la carga del programa, para solucionarlo, mantenga presionado el botón BOOT, presione RESET brevemente y luego suelte BOOT. Esto fuerza a la placa al entrar modo de programación.

Códigos de ejemplo

Comprobación de pines: Parpadeo del LED integrado

Usa este sencillo bucle en el pin 8 que controla el LED integrado.

// GPIO 8 es el LED integrado (LOW = ON)
#define LED_PIN 8 

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, LOW);  // LED On
  delay(500);
  digitalWrite(LED_PIN, HIGH); // LED Off
  delay(500);
}

Control de la pantalla OLED

La pantalla utiliza un controlador SSD1306, pero tiene una resolución física es de solo 72x40 píxeles.
Sin embargo, el búfer interno es de 128x64.

Para solucionar este problema tenemos que aplicar un desplazamiento en los ejes X e Y para que el texto sea visible.

Proporción Oled 0.42 con Oled 0.96

Necesitamos tener instalada la biblioteca U8g2 (de olikraus) a través del Administrador de bibliotecas de Arduino o desde el Repositorio de GitHub.


#include <U8g2lib.h>
#include <Wire.h>

#define SCL 6
#define SDA 5

// Iniciando un ESP32-C3 con I2C en los pines 5 SDA y 6 SCL.
// Usaremos el driver estandar de 128x64 pero solo una parte de la pantalla.
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

// Los Offsets se requieren para centrar la salida dentro del buffer 128x64.
const int VISIBLE_X0 = 28;
const int VISIBLE_Y0 = 24;

//configuramos la pantalla de 0.42 pulgadas 
const int VISIBLE_W  = 72;
const int VISIBLE_H  = 40;

void setup(void) {
  delay(1000); // Una pausa para estabilizar el ESP
  
  u8g2.begin();
  u8g2.setContrast(255);      // Máximo brillo
  u8g2.setBusClock(400000);   // 400kHz velocidad del I2C
  u8g2.setFont(u8g2_font_ncenB10_tr); // Elegimos el tipo de letra
}

void loop(void) {
  u8g2.clearBuffer(); // Limpiamos el buffer interno
  
  // Dibuja un marco para probar el área visible
  u8g2.drawFrame(VISIBLE_X0, VISIBLE_Y0, VISIBLE_W, VISIBLE_H);
  
  // Dibujar texto teniendo en cuenta el desplazamiento en x y el desplazamiento en y
  u8g2.setCursor(VISIBLE_X0 + 6, VISIBLE_Y0 + 15); 
  u8g2.print("ESP32-C3");
  u8g2.setCursor(VISIBLE_X0 + 25, VISIBLE_Y0 + 30); 
  u8g2.print("ITB");
  
  u8g2.sendBuffer(); // Enviar datos a la pantalla
  delay(1000);
}


Código: Texto con scroll lateral

El texto se mueve horizontalmente dentro de los 72 píxeles visibles y se dibuja un marco opcional para recordar los límites.
Puedes ajustar delay(30) para cambiar velocidad.

ESP32 C3 Oled 0.42 scroll lateral

#include <U8g2lib.h>

// Configuración de la pantalla (driver SSD1306, 128x64, I2C hardware)
// Pines: SDA = 6, SCL = 5 (ajusta según tu conexión)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 6, 5);

// --- Constantes de área visible real (72x40 con offset 28,24) ---
const int VISIBLE_X0 = 28;
const int VISIBLE_Y0 = 24;
const int VISIBLE_W  = 72;
const int VISIBLE_H  = 40;

// Texto a desplazar
const char* texto = "  Infotronikblog desplazamiento lateral  ";
int desplazamiento = 0;

// Función para dibujar texto teniendo en cuenta el recorte (clipping manual)
void dibujarTextoScroll(int offsetX) {
  // El área de dibujo está limitada a [VISIBLE_X0, VISIBLE_X0+VISIBLE_W]
  // Usamos setFont y drawStr con coordenadas absolutas (incluyendo offset)
  u8g2.setFont(u8g2_font_ncenB08_tr);
  int anchoTexto = u8g2.getStrWidth(texto);
  
  // Dibujamos el texto varias veces para efecto continuo
  int x = VISIBLE_X0 + offsetX;
  int y = VISIBLE_Y0 + 20;  // centrado vertical aprox.
  
  u8g2.drawStr(x, y, texto);
  // Si el texto se sale por la derecha, dibujamos continuación
  if (x + anchoTexto < VISIBLE_X0 + VISIBLE_W) {
    u8g2.drawStr(x + anchoTexto, y, texto);
  }
  // Si se sale por la izquierda, dibujamos la parte que falta
  if (x < VISIBLE_X0) {
    u8g2.drawStr(x + anchoTexto, y, texto);
  }
}

void setup() {
  u8g2.begin();
  u8g2.setFontMode(1);          // Modo transparente
  u8g2.setFontDirection(0);
}

void loop() {
  u8g2.firstPage();
  do {
    // Limpia toda la pantalla (128x64) pero solo se verá el área activa
    u8g2.clearBuffer();
    
    // Dibujar un marco alrededor del área visible (opcional, para referencia)
    u8g2.drawFrame(VISIBLE_X0 - 1, VISIBLE_Y0 - 1, VISIBLE_W + 2, VISIBLE_H + 2);
    
    // Dibujar el texto en scroll
    dibujarTextoScroll(desplazamiento);
    
  } while (u8g2.nextPage());
  
  // Actualizar desplazamiento (velocidad ajustable)
  desplazamiento--;
  if (desplazamiento < -u8g2.getStrWidth(texto)) {
    desplazamiento = VISIBLE_W;
  }
  
  delay(30);  // 30 ms entre frames
}

Código: Cubo 3D + rebotes en los bordes

Un pequeño cubo 3D que se mueve con velocidades vx y vy iniciales semi-aleatorias.
Con rebotes realistas: al tocar cualquier borde del área visible (72×40 con offsets), invierte la componente de velocidad correspondiente, quedando siempre dentro de los límites considerando el medio lado del cubo para que no se salga.

ESP32 C3 Oled 0.42 ejemplo cubo

#include <U8g2lib.h>
#include <math.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 6, 5);

// Área visible real de tu pantalla (offset 28,24)
const int X0 = 28;
const int Y0 = 24;
const int W  = 72;
const int H  = 40;

// Tamaño del cubo (lado en píxeles)
const int LADO = 14;   // Ajustado a 14 como pediste

// Centro del cubo (coordenadas absolutas) - posición inicial en el centro del área visible
float cx = X0 + W/2.0;
float cy = Y0 + H/2.0;

// Velocidades de desplazamiento (píxeles por frame)
float vx = 1.2;
float vy = 0.9;

// Ángulo de rotación (radianes)
float anguloY = 0.0;

// Límites de rebote: el cubo no debe salirse del rectángulo visible
// Como el cubo se dibuja centrado en (cx,cy), el medio lado se extiende a cada lado.
float limiteIzq  = X0 + LADO/2.0;
float limiteDer  = X0 + W - LADO/2.0;
float limiteSup  = Y0 + LADO/2.0;
float limiteInf  = Y0 + H - LADO/2.0;

// Proyección isométrica con rotación alrededor del eje Y
void projectPoint(float x, float y, float z, float angY, int &sx, int &sy) {
  // Rotación en Y
  float x1 = x * cos(angY) + z * sin(angY);
  float z1 = -x * sin(angY) + z * cos(angY);
  // Proyección simple (sin perspectiva, solo desplazamiento)
  sx = (int)(cx + x1);
  sy = (int)(cy - y + z1 * 0.3);   // ligero efecto de profundidad
}

void drawCubo(float angY) {
  // Vértices relativos al centro del cubo (coordenadas modelo)
  int vertices[8][3] = {
    {-LADO/2, -LADO/2, -LADO/2}, // 0
    { LADO/2, -LADO/2, -LADO/2}, // 1
    { LADO/2, -LADO/2,  LADO/2}, // 2
    {-LADO/2, -LADO/2,  LADO/2}, // 3
    {-LADO/2,  LADO/2, -LADO/2}, // 4
    { LADO/2,  LADO/2, -LADO/2}, // 5
    { LADO/2,  LADO/2,  LADO/2}, // 6
    {-LADO/2,  LADO/2,  LADO/2}  // 7
  };
  
  int projX[8], projY[8];
  for (int i = 0; i < 8; i++) {
    projectPoint(vertices[i][0], vertices[i][1], vertices[i][2], angY, projX[i], projY[i]);
  }
  
  // Aristas (12 líneas)
  int edges[12][2] = {
    {0,1},{1,2},{2,3},{3,0}, // base inferior
    {4,5},{5,6},{6,7},{7,4}, // base superior
    {0,4},{1,5},{2,6},{3,7}  // verticales
  };
  
  for (int i = 0; i < 12; i++) {
    u8g2.drawLine(projX[edges[i][0]], projY[edges[i][0]],
                  projX[edges[i][1]], projY[edges[i][1]]);
  }
}

void setup() {
  u8g2.begin();
  randomSeed(analogRead(0));
  
  // Velocidades iniciales aleatorias (para dar variedad)
  vx = (random(80, 160) / 100.0) * 1.5;
  vy = (random(60, 140) / 100.0) * 1.5;
  if (random(2) == 0) vx = -vx;
  if (random(2) == 0) vy = -vy;
  
  // Asegurar que la posición inicial esté dentro de los límites
  if (cx < limiteIzq) cx = limiteIzq;
  if (cx > limiteDer) cx = limiteDer;
  if (cy < limiteSup) cy = limiteSup;
  if (cy > limiteInf) cy = limiteInf;
}

void loop() {
  // 1. Actualizar posición del cubo
  cx += vx;
  cy += vy;
  
  // 2. Rebotes en los bordes
  if (cx < limiteIzq) { cx = limiteIzq; vx = -vx; }
  if (cx > limiteDer) { cx = limiteDer; vx = -vx; }
  if (cy < limiteSup) { cy = limiteSup; vy = -vy; }
  if (cy > limiteInf) { cy = limiteInf; vy = -vy; }
  
  // 3. Rotación continua
  anguloY += 0.06;   // velocidad angular (ajústala a gusto)
  if (anguloY > 2 * M_PI) anguloY -= 2 * M_PI;
  
  // 4. Dibujar en la pantalla
  u8g2.firstPage();
  do {
    u8g2.clearBuffer();
    
    // (Opcional) marco del área visible - puedes comentarlo si prefieres sin marco
    u8g2.drawFrame(X0, Y0, W, H);
    
    // Dibujar el cubo rotando
    drawCubo(anguloY);
    
  } while (u8g2.nextPage());
  
  delay(30);  // controla la velocidad de animación
}

Puedes visitar los siguiente enlaces relacionados con el microcontrolador ESP32 y Arduino:

jueves, 23 de abril de 2026

ePSXe para Windows 98: Cómo emular PlayStation 1 en tu PC retro

Muy buenas a todos y todas!!!

Una vez que hemos probado los juegos más exigentes de finales de los 90's como Quake II, Re-Volt o Half Life empecé a preguntarme si este IBM 300PL con 333MHz sería capaz de hacer funcionar juegos de la mítica PlayStation (la 1).

Entonces recordé uno de los softwares que utilizaba a principios de los 2000 para poder usar los juegos de PlayStation en el PC de la época llamado ePSXe (enhanced PSX emulator).

No voy a mentir pensaba que sería abandonware y que me costaría encontrar este programa sin tener que recurrir a Archive.org, el viejo confiable, pero no, ¡¡¡ePSXe sigue muy vivo!!!

IBM 300PL 6862

Su última versión para Windows es la ePSXe 2.0.18 aunque esta versión no nos servirá para nuestra noventera máquina de 32bits, para Windows 98 la versión que tenemos que usar es la versión 1.6.0.
Cualquier versión superior nos dara el error indicando que necesita una versión superior de windows.

Antes de empezar necesitamos tener la BIOS y un plugin de vídeo, en este caso uno especifico para la voodoo2 3Dfx.

  • BIOS
    Es lo primero que se ejecuta al encender la consola y es la encargada de Inicializar el hardware y Gestionar el arranque además de otras funciones.
    Existieron numerosas versiones de la BIOS para los distintos modelos de PlayStation. Los nombres de los archivos suelen seguir el patrón SCPH-xxxx.bin, donde el número corresponde al modelo de consola. Puedes descargar el archivo SCPH-1001.bin en www.retrostic.com.

  • Plugin vídeo Lewpy's Glide GPU Plugin
    El plugin Lewpy's Glide es un excelente plugin de vídeo para ePSXe que funciona con tarjetas compatibles 3DFX.
    Si la tarjeta no utiliza Glide, este complemento no funciona.
    Descargar driver vídeo Voodoo2: Lewpy's Glide GPU Plugin.

  • Plugin vídeo Pete's PSX GPU plugins
    Este plugin tiene complementos estándar de hardware/aceleración para GPU OpenGL, D3D DX7 y D3D DX6.
    Descargar driver vídeo Pete's: Pete's Windows GPU Plugins.

  • BONUS adaptador de mando PSX a USB
    Para tener una experiencia aún más inmersiva, podemos utilizar un adaptador de mando PS2 a USB que es totalmente compatible con la PS1.
    Adaptador PS2 a USB

    Este adaptador también se puede utilizar con mandos inalámbricos como los que se venden en web chinescas.
    Solo hay que conectar el USB con el mando encendido para que el mando se sincronice correctamente. También puedes probar software como KikyJoyX2 para probar el mando (Lo encontré en Vogons) pero también se puede configurar y calibrar desde el Panel de control de Windows en el apartado "Dispositivos de juegos.

No voy a engañar, no he trasteado mucho con las funciones de configuración de vídeo. Me he limitado a cambiar la resolución y a poner el marcador de FPS.

Salvo por unas pequeñas zonas en las que no pinta bien las texturas, tanto la jugabilidad como la calidad de imagen siguen siendo espectacular.

El primero de mi lista, es Legend of Dragoon, un RPG poco conocido en su momento pero con un sistema de combate atractivo y una historia épica, muy del estilo Final Fantasy VII.


Emulador ePSXe corriendo Legend of Dragoon

Final Fantay VII, por supuesto, también ha sido probado sin problemas ni en lo gráfico ni en el rendimiento.


Emulador ePSXe corriendo Final Fantasy VII

Otros artículos sobre ordenadores retro que te pueden interesar:


jueves, 9 de abril de 2026

FRAPS en Windows 95 y 98: Mide los FPS de tus juegos retro

Muy buenas a todos y todas y bienvenidos a Infotronikblog!!!

Hoy en día es muy fácil saber cuantos FPS(Frames Per Second) nos da una tarjeta gráfica, es decir cuantas imágenes por segundo mostrará el monitor.

Tenemos programas como MSI Afterburne o herramientas integradas como el medidor de FPS de Steam. ¿Pero que pasa cuando probamos un juego en versiones antiguas de sistemas operativos como Windows 95 o Windows 98?

Pues hay varias alternativas pero el que hemos probado se llama FRAPS y funciona perfecrtamente en cualquiera de estos sistemas (y posteriores como Windows XP)

¿Qué es FRAPS?

FRAPS (Frames RAte Per Second), fue desarrollado por la empresa australiana Beepa Pty Ltd. Su primera versión salió al mercado el 25 de agosto de 1999.

FRAPS se convirtió rápidamente en el estándar de la industria para medir el rendimiento en videojuegos.

Durante la era dorada de Windows XP, FRAPS era una herramienta indispensable. Su sencillez y eficacia lo hicieron popular entre jugadores, overclockers y medios de comunicación para realizar benchmarks.

La última versión estable de FRAPS es la 3.5.99 y fue lanzada el 26 de febrero de 2013. A partir de ahí, el desarrollo cesó por completo, quedando como un software "abandonware".


FRAPS es una aplicación universal para Windows que se puede usar con juegos que utilizan la tecnología gráfica DirectX u OpenGL.

Las principales de FRAPS:

  • Software de evaluación comparativa:
    Muestra la cantidad de fotogramas por segundo (FPS) que obtienes en cualquier esquina de la pantalla.
    Realiza pruebas de rendimiento personalizadas y mide la velocidad de fotogramas entre dos puntos cualesquiera y guarda las estadísticas en tu disco duro par poder usarlas para tus propios análisis y aplicaciones.

  • Software de captura de pantalla:
    Con FRAS podemos tomar una captura de pantalla con solo pulsar una tecla.
    Ya tenemos que pegarla en un programa de edición de imágenes cada vez que quieras una nueva captura.
    Las capturas de pantalla se nombran y marcan con la fecha y hora automáticamente.

  • Software de captura de vídeo en tiempo real:
    También nos ofrece la posibilidad de grabar vídeo mientras juegas a tu videojuego favorito!
    Olvídate de la videograbadora, de usar una cámara DV y de grabar tus partidas. ¡Nunca había sido tan fácil!
    FRAPS puede capturar audio y víodeo hasta 7680x4800 con velocidades de fotogramas personalizadas de 1 a 120 fotogramas por segundo.

    El problemas de esta opción es que la captura es en formato AVI con apenas compresión, esto crea archivos muy grandes lo que puede generar algunos problemas sobre todo con los discos duros de la época de Windows 95 y 98 que no suelen tener mucho espacio disponible.

FRAPS Versión 1.9C con QuakeII

Compatibilidad de FRAPS

Antes de ver las compatibilidades tenemos que saber que es una API de sus siglas en inglés Application Programming Interface (Interfaz de Programación de Aplicaciones) y es el conjunto de reglas, protocolos y herramientas que permite que dos programas se comuniquen entre sí.

En el contexto de FRAPS y los videojuegos se trata de APIs de tipo gráfico. Son las encargadas de traducir las órdenes del juego como dibujar un polígono, aplicar una textura, iluminar una escena a comandos que la tarjeta gráfica entiende y ejecuta.

FRAPS es compatible con las API's:

  • DirectX 8, 9, 10, 11

  • OpenGL

Como su última versión salió en 2013 FRAPS no tiene compatibilidad con DirectX 12 y Vulkan.

Otra de las limitaciones, pero no por culpa de FRAPS, es la tasa de refresco del monitor, generalmente funcionan a 60Hz es decir que la imagen se actualiza 60 veces en un segundo. Esto hace que FRAPS no pueda superar los 60FPS y en caso de que se superen, no tendrán un efecto real.

¿Conocias FRAPS?

Descargar la última versión de FRAPS desde su WEB para Windows XP, 7

Descargar versión 1.9C compatible con Windows 95/98 en VOGONS.

Otros artículos sobre ordenadores retro que te pueden interesar:


jueves, 26 de marzo de 2026

Amstrad CPC 464 con Bluetooth: Carga juegos sin cintas – Tutorial DIY

Muy buenas a todos y todas!!!

Si, después de ver el éxito de la instalación en el ZX Spectrum +2 del módulo Bluetooth, me he animado a poner otro en el Amstrad CPC 464 que reparé el año pasado. Bueno pues le ha tocado el turno, ¡¡¡no va a estar en la estantería solo para hacer bonito!!! tiene que funcionar. Así que destornillador en mano quitamos los 6 tornillos de la parte posterior y empezamos con la modificación.

La instalación y los puntos donde se tiene que conectar el módulo Bluetooth ha sido cosa fácil, lo que me ha entretenido más de lo que me gustaría ha sido la soldadura de la alimentación.

La idea es la misma que en el ZX Spectrum, pero en este caso lo hemos conectado directamente al cabezal del lector de cintas.

La alimentación la hemos sacado del molex de cables que se conecta a la placa base del CPC 464.

Podemos localizar primero en el manual de servicio el esquema donde podemos localizar los puntos que necesitamos para el mod que son:

  • La alimentación la conectamos en +B y GND que es donde se conecta el cable que va de la casetera a la placa principal justo donde conecta con la placa de la casetera.
  • El audio lo conectamos en el cabezal que esta marcado como "R/P. HEAD" y conectamos el canal derecho del modulo bluetooth al + del cabezal y el GND al que esta marcado como -.

En este caso al disponer de más espacio en la parte posterior del casete, he quitado el tornillo que sujeta la polea grande de la goma que va al motor.

Como tenía cero material para utilizar me puse a investigar y encontré algunas cosillas interesantes como

  • Recopilación de juegos:
    Creado por Sergi Caparrós y Javy Fernández, la web AMSTRADPOWER.ES en su sección LoadCPC recopila el trabajo de Juanfran con, me atrevería a decir, cientos de Juegos.
    con una organización sencilla pero directa, puedes reproducir el juego directamente desde la web simplemente pulsando "Play". Además incluye las caratulas o covers de cada una de las cintas...
    Investigando un poco sobre ellos descubrí CapaSoft donde también puedes encontrar juegos como Jax The Dog o Amstrad Eterno, este último además colabora donando el 100% de las donaciones que realices por su descarga irán destinadas a la Asociación Española Contra el Cáncer..

  • Diagnostico:
    Una vez que todo enciende y parece funcionar bien es hora de hacer un pequeño diagnostico del sistema para comprobar que todo funciona correctamente.
    Para esta tarea existe el repositorio de Github amstrad-diagnostics. Con el podemos comprobar la RAM baja en el CPC464 y baja y alta en CPC6128, así como el teclado e información diversa del sistema.

  • Convertir CDT a WAV
    Al descargar el diagnostico en formato CDT no podía cargarlo directamente en el Amstrad, primero hay que cambiar el formato a un formato de audio, puede ser WAv o MP3 y para esto encontré CDTMaster una utilidad para editar archivos CDT (Cintas Amstrad) con el que también podemos cambiar a un formato de tipo WAV.
    Lo mejor de todo es que lo pude usar directamente en mi sistema Linux, instalándolo con wine:
    
        wine CDTMaster.exe
        
    Lo ejecutamos y pulsamos sobre "File" y ya nos aparece "convert to WAV", luego podemos pasarlo a MP3, pero como la conversión ocupo solo 4MB lo deje en WAV.

He probado juegos como Green Beret, 007 Con Licencia para Matar, La Abadía del crimen,



Otros artículos sobre ordenadores retro que te pueden interesar:

Saludos a todos y todas y espero que disfruteis de la modificación!!!

domingo, 15 de marzo de 2026

ZX Spectrum con Bluetooth: Olvida las cintas y carga juegos desde el móvil

Muy buenas a todos y todas a este mundo de los 8bits!!!

Desde que compre esta pequeña joya de los 80's, uno de los mayores problemas que presenta el ZX Spectrum es la carga de programas o juegos desde su soporte nativo: las cintas de casete.

Y es que en este tipo de máquinas, como el Amstrad CPC 464, utilizan como soporte magnético las obsoletas cintas de casete.

Si bien hay un buen mercado de segunda mano para este tipo de soporte, no siempre están en las mejores condiciones al ser susceptible a la degradación tanto física como química, deformaciones y atascos e incluso la aparición de moho si se almaceno en un sótano o trastero húmedo.


 

Por suerte las nuevas tecnologías siempre nos echan una mano para para solucionar este tipo de adversidades y no dejar como un gran pisapapeles a nuestro querido ZX Spectrum. 

 

El concepto era almacenar los 1s y 0s de los bits en tonos de audio, para después hacer la operación contraría y transformar esos tonos de audio en información que el ordenador pueda entender.

Realmente era algo más parecido a un módem que a un disquete de la época.

Como resultado tenemos un soporte que esta basado en audio.

Igual que en sus orígenes, el audio siempre se puede copiar y aquí es donde entran las nuevas tecnologías: El formato de audio WAV y el formato MP3.

Como ya explique en la restauración del ZX Spectrum una de las soluciones es saltarse el casete e introducir la señal de audio desde la salida de audio de un teléfono o un ordenador.

Esta opción esta muy bien, pero seguimos con la posibilidad de que el cable esté en mal estado o el conector sucio además de tener otro cable más por encima del escritorio.


Módulo audio Bluetooth 5.0
Podemos hacer la misma operación pero de manera inalámbrica con un módulo de audio Bluetooth.
Fáciles de encontrar, y baratos, nos ofrecen una solución muy interesante para implementar este pequeño dispositivo en nuestro ZX Spectrum.

Igual que hicimos con el amplificador y el cable, vamos a necesitar los mismos 4 puntos para conectar el módulo bluetooth, toma de alimentación +5V y la salida de audio del casete.



NOTA: Recuerda revisar la placa de la casetera ya que hay diferentes modelos, revisa el diagrama antes de conectar!!!

Una de sus mejores características es que ocupa muy poco espacio lo he situado en la parte lateral derecha de la máquina y no molesta para poder cerrar y abrir la carcasa sin problemas.

¿Cómo funciona?

Para hacer funcionar el módulo primero necesitamos vincular el módulo bluetooth a nuestro teléfono de igual manera que lo hacemos con unos audífonos o un altavoz.

Una vez vinculado el módulo lo tenemos que "calibrar" para que el sonido no este saturado o la señal sea insuficiente. Esto lo regulamos con la intensidad del sonido, en este caso el subiendo o bajando el volumen del dispositivo.

También aplicaciones como PlayZX tiene un apartado de configuración con apartados como:

  • Wave payback volume: Regulamos el volumen, en mi caso lo tengo al 50%.

  • Stereo: Envía la señal por los canales (L y R), también lo tengo activado.

  • Both channels in sync: Esta opción la tengo desactivada.

  • Natural wave: También desactivada.


Una vez configurado, buscamos el juego que vamos a cargar, seleccionamos la cinta y pulsamos Play...



Si te interesa el micro ordenador de 8 bits ZX Spectrum puedes encontrar más información en los siguientes enlaces:

Saludos a todos y todas!!!

viernes, 20 de febrero de 2026

Ojos animados en OLED con Raspberry Pi: Tutorial Python con expresiones

Muy buenas a todos y todas!!!

Ya hemos visto como configurar una pantalla OLED de 128x64 en nuestra Raspberry Pi junto con la librería Adafruit.

Y como veía mi cluster Pi un poco "triste" decidí animarlo un poco con una pantalla OLED de tipo I2c y algunas animaciones programadas en Python que la hacen más "friendly".


Expresiones y dirección de la mirada

Podemos alternar entre las diferentes expresiones:

  • Normal
  • Contento
  • Triste
  • Sorpresa
  • Dormido

También podemos dirigir la mirada en las siguientes direcciones:

  • Izquierda
  • Centro
  • Derecha



Pero también podemos llamar de manera independiente a cada una de las expresiones con la función "crear_ojos" y "animar_parpadeo"

def crear_ojos(expresion="normal", direccion_mirada="centro", estado_parpadeo=1.0):
    """
    Ojos con sistema de parpadeo realista
    estado_parpadeo: 1.0 = totalmente abierto, 0.0 = totalmente cerrado
    """

Para crear la animación de parpadeo:


def animar_parpadeo(expresion="normal", direccion_mirada="centro"):
    """Animación completa de parpadeo"""
    

Programa completo en python:



# Simulación de ojos con expresiones en OLED 128x64

import time
import random
import board
import adafruit_ssd1306
from PIL import Image, ImageDraw, ImageFont

# Use for I2C.
i2c = board.I2C()  # uses board.SCL and board.SDA

try:
        disp = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
        print("\nAdafruit SSD1306 ok")

except Exception as errors:
        print("\nError Adafruit SSD1306:",str(errors), "\nPlease check Raspi-config\n")
        exit()


def crear_ojos(expresion="normal", direccion_mirada="centro", estado_parpadeo=1.0):
    """
    Ojos con sistema de parpadeo realista
    estado_parpadeo: 1.0 = totalmente abierto, 0.0 = totalmente cerrado
    """
    # Crear imagen evitando la franja amarilla superior
    img = Image.new('1', (128, 64), 0)
    draw = ImageDraw.Draw(img)
    
    # Posiciones fijas
    centro_y = 40
    ojo_izq_x = 45
    ojo_der_x = 83
    
    # Tamaño máximo reducido a 40
    max_tam = 40
    
    # Radio de redondeo
    radio = 15
    
    # Determinar tamaños según dirección de mirada
    if direccion_mirada == "izquierda":
        ancho_izq, alto_izq = max_tam, max_tam
        ancho_der, alto_der = 30, 30
    elif direccion_mirada == "derecha":
        ancho_izq, alto_izq = 30, 30
        ancho_der, alto_der = max_tam, max_tam
    else:  # centro
        ancho_izq, alto_izq = 35, 40
        ancho_der, alto_der = 35, 40
    
    def dibujar_ojo(x, y, ancho, alto, radio, apertura=1.0):
        """Dibuja un ojo con sistema de parpadeo"""
        if apertura >= 1.0:
            # Ojo totalmente abierto
            draw.rounded_rectangle([
                (x - ancho//2, y - alto//2),
                (x + ancho//2, y + alto//2)
            ], radius=radio, outline=1, fill=1)
        elif apertura <= 0.0:
            # Ojo totalmente cerrado (línea)
            draw.line([
                (x - ancho//2, y),
                (x + ancho//2, y)
            ], fill=1, width=3)
        else:
            # Ojo parcialmente cerrado - calcular altura visible
            alto_visible = int(alto * apertura)
            if alto_visible < 2:
                alto_visible = 2
            
            # Dibujar el ojo recortado según la apertura
            draw.rounded_rectangle([
                (x - ancho//2, y - alto_visible//2),
                (x + ancho//2, y + alto_visible//2)
            ], radius=min(radio, alto_visible//2), outline=1, fill=1)
    
    # Aplicar expresiones (modifican la apertura base)
    apertura_base = estado_parpadeo
    
    if expresion == "normal":
        apertura = apertura_base
    elif expresion == "contento":
        apertura = apertura_base * 0.7  # Ojos más cerrados
    elif expresion == "triste":
        apertura = apertura_base * 0.9  # Ojos casi normales pero caídos
    elif expresion == "sorpresa":
        apertura = min(apertura_base * 1.2, 1.0)  # Ojos más abiertos
    elif expresion == "dormido":
        apertura = apertura_base * 0.3  # Ojos casi cerrados
    
    # Dibujar ojos con la apertura calculada
    dibujar_ojo(ojo_izq_x, centro_y, ancho_izq, alto_izq, radio, apertura)
    dibujar_ojo(ojo_der_x, centro_y, ancho_der, alto_der, radio, apertura)
    
    return img

def animar_parpadeo(expresion="normal", direccion_mirada="centro"):
    """Animación completa de parpadeo"""
    frames = []
    
    # Fases del parpadeo (apertura de 1.0 a 0.0 y vuelta)
    fases = [1.0, 0.8, 0.5, 0.2, 0.0, 0.2, 0.5, 0.8, 1.0]
    
    for apertura in fases:
        frame = crear_ojos(expresion, direccion_mirada, apertura)
        frames.append(frame)
    
    return frames


# Bucle principal 
if __name__ == "__main__":
    expresiones = ["normal", "contento", "triste", "sorpresa", "dormido"]
    direcciones = ["izquierda", "centro", "derecha"]

    print("Mostrando expresiones sencillas...")
    print("Presiona Ctrl+C para detener")

    try:
        while True:
            for expresion in expresiones:
                for direccion in direcciones:

                    print(f"Expresión: {expresion} dirección: {direccion}")

                    img = crear_ojos(expresion, direccion)
                    disp.image(img)
                    disp.show()
                    time.sleep(5)
                                        # 30% de probabilidad de guiño entre animaciones
                    if random.random() < 0.2:
                        #lado_guiño = random.choice(["izquierdo", "derecho"])
                        print(f"¡papadeo {expresion}!")
                        frames_parpadeo = animar_parpadeo(expresion, direccion)

                        for frame in frames_parpadeo:
                            disp.image(frame)
                            disp.show()

    except KeyboardInterrupt:
        print("\nBucle detenido")

Es un programa que podemos ampliar con otras expresiones o incluso hacer llamadas desde otros programas por ejemplo conectandolo a una Raspi cam para simular que la mirada nos sigue.

Otro idea que se me ocurre es que las llamadas sean según diferentes eventos, como que despierte con el acceso por ssh o utilizar el sensor de temperatura para que nos "mire mal" si nuestra Raspberry Pi se pone lujuriosa.

Añadir programa al iniciar Raspberry PI

Lo ideal es que este programa se inicie al encender la Raspberry pi. Es un buen indicador para saber que la Raspberry Pi arranco sin problemas.

esto lo podemos hacer editando el crontab con el comando "crontab -e" (no hace falta sudo).


@reboot sleep 30 && python3 ~/Eyes_simulation/eyes_simulation.py > ~/Eyes_simulation/eyes.log 2>&1

Recuerda poner "@reboot sleep 30" para darle un tiempo prudente a la Raspberry para inicializar el puerto I2c.

Y siempre puedes ver si todo esta bien o hay algún fallo consultando el archivo que se genera en el inicio llamado "eyes.log".


Conclusión:

Es un programa sencillo que le dará a nuestra maquina un poco más de carisma (por si le faltaba un poco) y que deje de ser un puñado de lucecitas encima del escritorio.

Antes de montar el cluster con las 3 Raspberry Pi, la que máquina que usaba para estas cosas empezó a dar problemas con la tarjeta y lo pude detectar ya que no me miraba igual que siempre...

¿Le puedo poner nombre?


Aquí tienes otros enlaces de este blog relacionados con Raspberry Pi: