Este proyecto tiene por objeto la instalación de una pantalla LCD en cualquier router OpenWrt que disponga de dos GPIO libres.
A) EL HARDWAREA.1) Pantalla.
Para este proyecto utilizo una pantalla 1602 comprada en una tienda china y un router AR-5387un.
Por la parte de detrás de la pantalla hay un expansor de bus I²C, un Philips PCF8574T, que permite su conexión con el router con sólo cuatro cables (GND, VCC, SDA y SCL).
A.2) Cables y soldaduras.
Se le quita la tapa al router. Se suelda el cable SDA (naranja) en el punto R107 (GPIO9) y el cable SCL (verde) en el punto R109 (GPIO10).
El cable GND (negro) se puede soldar al GND del puerto serie (0 voltios). El cable VCC (rojo) lo he soldado en el punto R66, que da +5 Voltios. Igual se puede alimentar a +3'3 voltios, pero no lo he probado.
A.3) Ajuste del contraste.
Ya se puede conectar la pantalla al router. Lo normal es que la pantalla se ilumine y no se vea nada. Hay que ajustar el contraste con el potenciómetro azul hasta que se vean nítidamente los 16 cuadritos de la primera fila.
B) EL FIRMWAREB.1) GPIO.
Para poner en marcha los GPIO hay que averiguar su "base". Desde una terminal de OpenWrt.
cat /sys/class/gpio/gpiochip*/base | head -n1
480
El valor devuelto (480) es la base, un valor que se suma al GPIO para obtener su valore real. El GPIO9 es en realidad el 489 y el GPIO10, el 490.
Para poder utilizar los GPIO se exportan y se declaran "de salida".
echo "489" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio489/direction
echo "490" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio490/direction
Se puede comprobar que los GPIO funcionan correctamente con un polímetro o con un led y una resistencia de 330 Ohmios en serie.
#SDA (GPIO9) a 3.3V y a 0V.
echo "1" > /sys/class/gpio/gpio489/value
echo "0" > /sys/class/gpio/gpio489/value
#SCL (GPIO10) a 3.3V y a 0V.
echo "1" > /sys/class/gpio/gpio490/value
echo "0" > /sys/class/gpio/gpio490/value
B.2) Bus I²C.
Para que el par de GPIO funcionen como bus I²C hay que instalar un par de paquetes, "montar" el bus y reiniciar el router.
opkg update
opkg install kmod-i2c-gpio-custom
opkg install kmod-gpio-pcf857x
echo "i2c-gpio-custom bus0=0,489,490" > /etc/modules.d/59-i2c-gpio-custom
echo "i2c-gpio" > /etc/modules.d/60-i2c-gpio
reboot
Una vez reiniciado el router, se puede comprobar que OpenWrt detecta el bus.
dmesg | grep -E 'i2c|I2C'
[ 14.092000] i2c /dev entries driver
[ 14.112000] Custom GPIO-based I2C driver version 0.1.1
[ 14.120000] i2c-gpio i2c-gpio.0: using pins 489 (SDA) and 490 (SCL)
Una comprobación adicional devuelve el punto de montaje del bus.
ls /dev/i2c*
/dev/i2c-0
En la nuevas compilaciones de OpenWrt no viene el paquete i2c-tools, que incluye las herramientas i2cdetect, i2cdump, i2cget e i2cset. Afortunadamente, sirven las BB v14.07. Para descargar e instalar.
cd /tmp
wget http://downloads.openwrt.org/barrier_breaker/14.07/brcm63xx/generic/packages/oldpackages/libi2c_2013-12-15-1_brcm63xx.ipk
wget http://downloads.openwrt.org/barrier_breaker/14.07/brcm63xx/generic/packages/oldpackages/i2c-tools_2013-12-15-1_brcm63xx.ipk
opkg install libi2c_2013-12-15-1_brcm63xx.ipk i2c-tools_2013-12-15-1_brcm63xx.ipk
Para enviar datos a un dispositivo hay que saber primero en qué dirección está. Se utiliza la herramienta i2cdetect. El parámetro "-y" es para evitar que el comando dé un mensaje de aviso y "0" es el bus (que se obtiene con el comando ls /dev/i2c*).
i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3f
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
OpenWrt detecta un dispositivo en la dirección hexadecimal 0x3F.
Puenteando los contactos A0, A1 y A2, se le puede asignar a la expansora cualquier dirección de entre ocho disponibles. El bus I²C es realmente potente, porque permite conectar un gran número de dispositivos con sólo cuatro cables.
(Foto de Jesús Echevarría.
http://www.jechavarria.com/2013/03/11/how-to-interface-8-lcd-displays-with-two-wires/)
NOTA AL MARGEN.-Lo explicado hasta ahora bien podría servir como base para otros proyectos. Las salidas P0 a P7 del chip PCF8574 se pueden hacer conmutar a voluntad, lo que permitiría controlar, por ejemplo, 8 relés con sólo 2 GPIO. Y como se pueden apilar en paralelo hasta 8 expansoras, se podrían controlar hasta 64 relés con sólo 2 GPIO. Cada módulo expansor cuesta en ebay algo menos de 1€.
NOTA ACLARATORIA.-Volviendo a las pantallas LCD, es de observar que las placas expansoras de bus I²C no están normalizadas. Las patillas P0 a P7 del PCF8574 pueden estar conectadas de muy distintas formas a los puntos 1 a 16 de la pantalla LCD. Esto es importante, porque un mismo programa podría funcionar correctamente con una pantalla determinada y con otra no. En este proyecto la correspondencia es la de la foto. La patilla P3 enciende o apaga el led que ilumina el fondo de la pantalla.
C) EL SOFTWAREPara enviar texto al LCD utilizo un script. Hay que activarle el atributo "ejecutable" para poder utilizarlo como si fuese un comando.
#!/bin/sh
# 2016 :p raphik
# Comando para LCD HD44780 + expansor I2C PCF8574T
# Sintaxis 1: se utiliza para inicializar el LCD una sola vez, antes de enviar cualquier texto
# lcd_write <init>
# Sintaxis 2: se utiliza para enviar texto. La primera fila es la 0. La primera columna es la 0.
# lcd_write <fila> <columna> <texto>
#
func_init()
{
# Inicializa el display para modo 4-bit, 16x2 caracteres:
func_LCD I 0x33; func_LCD I 0x32; func_LCD I 0x28; func_LCD I 0x0C; func_LCD I 0x01
}
func_LCD()
{
local nibb; local data
if [ $1 == "I" ]; then
data=$2
nibb=$(($data/0x10*0x10)); i2cset -y $BUS $ADDRESS $(($nibb+$INST_SET)) $(($nibb+$INST_SEND))
nibb=$(($data%0x10*0x10)); i2cset -y $BUS $ADDRESS $(($nibb+$INST_SET)) $(($nibb+$INST_SEND))
fi
if [ $1 == "C" ]; then
data=$(printf "0x%02x" "'$2")
nibb=$(($data/0x10*0x10)); i2cset -y $BUS $ADDRESS $(($nibb+$CHAR_SET)) $(($nibb+$CHAR_SEND))
nibb=$(($data%0x10*0x10)); i2cset -y $BUS $ADDRESS $(($nibb+$CHAR_SET)) $(($nibb+$CHAR_SEND))
fi
}
func_row_col() # esta función posiciona el cursor tanto en pantallas de 2x16 como de 4x20
{
local base ;
if [ $1 == 0 ]; then base=0x00; fi
if [ $1 == 1 ]; then base=0x40; fi
if [ $1 == 2 ]; then base=0x14; fi
if [ $1 == 3 ]; then base=0x54; fi
func_LCD I $((0x80+$base+$2))
}
func_print_string() # escribir una cadena
{
local i=0
while [ $i -lt ${#1} ]; do
func_LCD C "${1:$i:1}"
let i++
done
}
# SCRIPT
if [ "$#" -eq 0 ]; then
printf "\n lcd_write ver. 1.01 2016 :p raphik\n"
printf "Sintaxis 1: lcd_write <init>\n"
printf "Sintaxis 2: lcd_write <fila> <columna> <texto>\n\n"
return
fi
BUS=0
ADDRESS=0x3F
INST_SET=0x0C
INST_SEND=0x08
CHAR_SET=0x0D
CHAR_SEND=0x09
if [ $1 == "init" ]; then
func_init
return
fi
func_row_col $1 $2
func_print_string "$3"
return
# SCRIPT
Un ejemplo de uso del script: visualizar el porcentaje de calidad del enlace wifi.
./lcd_write.sh init
./lcd_write.sh 0 2 "Quality Link"
./lcd_write.sh 1 5 "$(cat /proc/net/wireless | awk 'NR==3 {printf "%0.2f %% \n", $3/70*100}')"
Saludos.