Artículos
PiWatch - Hora y fecha en Raspberry Pi con frame buffer en Linux
- Detalles
- Categoría: Artículo
- Publicado el Miércoles, 25 Diciembre 2024 12:01
- Escrito por Santiago Villafuerte
- Visto: 171
Este artículo expone los pasos necesarios para mostrar fecha y hora actuales en una Raspberry Pi con el display Hyperpixel4 de Pimoroni. Se utilizan dos lenguajes de programación para lograrlo:
- C para habilitar el modo gráfico de /dev/console en Linux de la RPi
- C# para dibujar la pantalla Hyperpixel4 haciendo uso del frame buffer de Linux en la RPi
Hardware
- Una Raspberry Pi compatible con Hyperpixel4 de Pimoroni (opcional, ya que puedes usar un monitor normal también).
- Hyperpixel4 de Pimoroni
https://shop.pimoroni.com/products/hyperpixel-4?variant=12569485443155
- Conexión Wifi de la Raspberry Pi (mi RPi2 necesita un dongle externo, pero las modernas ya no lo necesitan).
Software
- Visual Studio 2022 con C# y .NET Framework 4.7.2 corriendo en Windows o Linux
https://visualstudio.microsoft.com/es/vs/
- (Opcional) Una PC corriendo Linux con el cross-compiler gcc para la Raspberry Pi
El prefijo del compilador de 32 bits para la Raspberry Pi 2 es: "arm-linux-gnueabihf". Como mi RPi es antigua aún necesita ser compilada con gcc de 32 bits.
Para Raspberry Pis modernas, puedes buscar "Raspberry Pi cross compiler" e instalarlo en Linux.
Este paso es opcional ya que también puedes compilar el código de piwatchVideoMode directamente en el gcc de la RPi (compilar en ella misma).
- Sistema operativo "Raspberry Pi OS Lite" ya instalado en tu Raspberry Pi
Este artículo utiliza la pantalla Hyperpixel4 como único display y sin aplicaciones GUI en la RPi (sin escritorio, X Desktop).
Por lo tanto solo se necesita instalar la versión Lite del OS.
https://www.raspberrypi.com/software/operating-systems/
- Instalar el display Hyperpixel4 en tu Raspberry Pi de acuerdo a las instrucciones de su página.
Este artículo asume que tu display ya funciona y muestra un prompt de Linux normalmente.
https://shop.pimoroni.com/products/hyperpixel-4?variant=12569485443155
Repositorio con código fuente
El código fuente en C y C# de los binarios se encuentra en GitHub:
https://github.com/migsantiago/piwatch
Configuración
Configura el WiFi de tu RPi
Es importante que tu RPi cuente con conexión a internet para que la fecha y hora que se muestren en el display sean correctas. Para configurar tu RPi sigue los tutoriales que hay en línea. La mayoría te pedirán que corras "sudo raspi-config" y des de alta la configuración WiFi desde ahí.
Configura tu módulo Hyperpixel4 Pimoroni
El display con el que cuento es rectangular de 800x480 pixeles. En mi caso tuve que agregar la siguiente línea al final del archivo "/boot/firmware/config.txt" de la RPi:
dtoverlay=vc4-kms-dpi-hyperpixel4,rotate=270
Esta línea habilita el Hyperpixel4 y además lo rota 270° (modo landscape). Si necesitas otra configuración tendrás que modificar el código fuente de C#.
Si tu display ya funciona, verás algo similar al mío.
Configuración de /dev/console para operar en modo gráfico
Al encender la RPi el display mostrará el shell de Linux para capturar comandos y usar la RPi. Ése es el modo predeterminado de la RPi: modo texto. Para que podamos usar la pantalla en modo gráfico necesitamos configurar el dispositivo /dev/console. Esto se logra con el código en C (usé C++, pero se usan funciones POSIX y syscalls de C).
https://github.com/migsantiago/piwatch/blob/main/piwatchVideoMode/src/piwatchVideoMode.cpp
Este código abre /dev/console y deshabilita el modo TTY (O_NOCTTY). Después habilita el modo gráfico y verifica que haya podido hacerlo. El binario debe correrse con permisos root para que el cambio funcione.
# Habilita el modo ejecución en el binario
sudo chmod +x piwatchVideoMode
sudo ./piwatchVideoMode -g
Una vez hecho esto, el prompt del display dejará de cambiar. La pantalla seguirá mostrando texto, pero ya no funcionará si tecleamos algo en ella. El modo gráfico estará habilitado.
Instalando el Mono .NET runtime en la Raspberry Pi
Para poder correr aplicaciones de C# en la RPi necesitas instalar el Mono runtime.
sudo apt update
sudo apt upgrade
sudo apt install mono-complete
Esto permitirá cargar archivos .exe generados por Visual Studio e interpretarlos para que corran sobre Linux.
Dibujando la fecha y hora con C#
Mediante el .NET Framework 4.7.2 y la clase Bitmap en C# podemos dibujar gráficos y texto en un mapa de bits común. El proyecto piwatch en C# hace lo siguiente:
https://github.com/migsantiago/piwatch/tree/main/PiWatch
- Carga la fecha y hora actual. Determina si es de día o de noche y carga una imagen de fondo correspondiente.
- Crea una instancia de la clase Graphics y dibuja la hora y fecha sobre el fondo cargado
- Una vez que el mapa de bits está completo hay que convertirlo al formato de pixeles que la RPi esté usando.
En el caso de mi RPi la cantidad de bits por pixel que está manejando es de 16 bits por pixel, donde:
RRRRRGGG GGGBBBBB
El color rojo usa 5 bits
El color verde usa 6 bits
El color azul usa 5 bits
En una versión anterior de Linux de mi RPi el modo era de 32 bits: AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB. Al actualizar el sistema operativo, ya funciona como muestro arriba.
C# convierte el modo de 32 bits que la clase Bitmap maneja en modo 16 bits.
Esta conversión ya está lista para ser escrita en el frame buffer de video de Linux. - C# utiliza funciones de la librería dinámica de Linux libc.so para escribir los pixeles sobre el frame buffer de video.
C# debe ser configurado para operar en modo no administrado o inseguro (keyword unsafe) para que pueda utilizar apuntadores primitivos de C. Esto es importante para poder escribir el frame de nuestra imagen generada.
Para mapear el archivo /dev/fb0 (frame buffer de video) a memoria RAM, necesitamos usar las funciones mmap y munmap.
- mmap va a mapear un file descriptor de Linux (/dev/fb0) a un apuntador de valores UInt16 (16 bits por pixel).
- munmap va a remover ese mapeo.
C# necesita importar símbolos de la librería dinámica libc.so (shared object en Linux) para poder usar esos métodos nativos:
// Linux stuff
static int O_RDWR = 2;
static int PROT_READ = 1;
static int PROT_WRITE = 2;
static int MAP_SHARED = 1;
[DllImport("/usr/lib/arm-linux-gnueabihf/libc.so.6")]
extern static int open(string pathname, int flags);
[DllImport("/usr/lib/arm-linux-gnueabihf/libc.so.6")]
extern static void* mmap(void *addr, int length, int prot, int flags, int fd, int offset);
[DllImport("/usr/lib/arm-linux-gnueabihf/libc.so.6")]
extern static int munmap(void *addr, int length);
[DllImport("/usr/lib/arm-linux-gnueabihf/libc.so.6")]
extern static int close(int fd);
En mi RPi la ubicación de libc.so es la mostrada. Deberás buscar la misma librería en tu RPi y actualizar esa ruta.
Al exponer estos símbolos en C# ya podemos escribir el frame buffer de Linux directamente desde C#.
El programa escribe pixel por pixel en su correspondiente ubicación en la RAM de video.
¡C# puede ser usado para operaciones de bajo nivel en Linux! - Espera un segundo y vuelve a dibujar un nuevo frame con una nueva hora y fecha.
El programa de C# debe ser ejecutado después de haber habilitado el modo gráfico de la consola.
Habrás notado que la fuente utilizada para dibujar texto en pantalla es diferente. Puedes descargarla de Google Fonts. El código fuente en C# menciona los pasos de instalación en Linux de la RPi.
Para correr el programa de dibujo de pantalla, ejecuta este comando en consola.
# Habilita permisos de ejecución sobre el binario de C#
sudo chmod +x piwatch.exe
sudo ./piwatch.exe
Cómo mostrar nuestro reloj en pantalla automáticamente al encender la RPi
Una manera elegante de lograrlo es usando systemd. Este binario administra dependencias en Linux y arranca servicios cuando es necesario. Podemos crear un servicio para que cuando arranque Linux, nuestro reloj se muestre automáticamente.
Primero necesitamos crear un shell script que haga 2 cosas:
- Cambiar el modo de la consola a gráfico
- Correr nuestro binario de C# para que dibuje la fecha y hora permanentemente
Puedes instalar el script piwatch.sh en /home/pi (u otra ruta):
https://github.com/migsantiago/piwatch/blob/main/scripts/piwatch.sh
Modifica las rutas de ejecución si son diferentes a las mías.
Y para que el script sea ejecutado debes instalar un servicio nuevo:
https://github.com/migsantiago/piwatch/blob/main/scripts/piwatch.service
En esta ruta: /lib/systemd/system/
No olvides darles permisos de ejecución a ambos.
Una vez que están instalados, se debe pedir a systemctl que dé de alta el nuevo servicio.
# Reinicia systemctl
sudo systemctl daemon-reload
# Da de alta el nuevo servicio
sudo systemctl enable piwatch.service
# Reinicia tu RPi
sudo reboot
# Espera a que inicie y verifica si el servicio está corriendo
systemctl status piwatch
Conclusiones
El código que generé está enfocado en mi RPi y quizás no funcione con otros displays. Lo que quise compartir en este tutorial es que con C# podemos ejecutar funciones de bajo nivel en Linux y que es muy fácil lograrlo.
Adicionalmente he buscado otros relojes para la RPi y pues encontré algunos que no me convencieron del todo :D
Si cargas el OS Lite en tu RPi con un monitor genérico, este método de dibujo ha de funcionar también. Solo debes ajustar la imagen con la resolución de tu monitor y los bits por pixel que la RPi te indique:
fbset -i
Bits por pixel en mi RPi:
Bits por pixel en mi VM con Ubuntu:
Con el poder de la RPi debe ser posible dibujar video a 60FPS, pero ya no tuve tiempo de probar algo así. Además, hay que mejorar la función bmp.GetPixel() del .NET Framework para que sea más rápida ;)
Espero que esta información sea útil y pues si alguien desea colaborar con el proyecto por favor háganlo a través del repo en GitHub.
Gracias por leerme.