ESPTOOL.EXE en windows XP x32

¿Quien usa ya Windows XP? Es un sistema operativo de 2001, tiene una antigüedad de más de 20 años, y Microsoft dejó de darle soporte hace 10 años, en 2014… Pero para algunos programas o para hardware antiguo sigue siendo necesario.

El caso es que necesito la herramienta esptool de expresiff https://github.com/espressif/esptool/releases que corra en Windows XP de 32 bits. Esta herramienta sirve para programar los chips ESP32 por puerto serie. Suelo proporcionar a mis clientes un script que llama a esptool.exe con los parámetros adecuados para programar los chips con el firmware que proporciono.

En este caso, el cliente tiene en el taller de verificación y control de calidad un antiguo PC con Windows XP x32 que utiliza para probar y programar dispositivos antiguos, es la herramienta que quiere utilizar también con mis programas.

Desgraciadamente esptool.exe sólo existe para Windows 7 y superiores. Expresiff lo proporciona escrito en python https://www.python.org/, aunque existen versiones ejecutables independientes construidas usando el compilador de python a .exe pyinstaller: https://pyinstaller.org/en/stable/.

En principio parece fácil: instalo python y pyinstaller en una máquina de Windows XP, cargo esptool.py y lo compilo. Pero la práctica es algo más dificil por problemas de compatibilidad de las herramientas de compilación con el antiguo Windows XP. Aquí voy a describir cómo instalar las versiones adecuadas de cada herramienta o librería para la compilación bajo Windows XP SP3 de 32 bits:

En primer lugar necesito una versión del intérprete de python que corra en Windows XP… pero que también sea compatible con pyinstaller. Las versiones posibles serían phthon 2.7.9 y la 3.4.4 según comentan en https://stackoverflow.com/questions/47516712/what-versions-of-python-will-work-in-windows-xp. Pero lo mejor sería una versión superior a 3.5 por compatibilidad con pyinstaller, pero no funcionaría en Windows XP, sólo en versiones superiores de Windows. Por suerte python se distribuye en código fuente y es posible modificarlo y recompilarlo para adaptarlo. Eso ya lo ha hecho «Zorba the Geek» y lo ha publicado en este hilo: una versión modificada de python 3.8.13 para Windows XP SP3: https://msfn.org/board/topic/183741-python-3813-for-windows-xp-sp3/page/4/

En la máquina con Windows XP copio «Python 3.8.1350.7z» y lo descomprimo a «c:\python_». A continuación ejecuto el instalador «c:\python_\install c:\». No funciona!. Es necesaria una librería .dll. Es necesario instalar previamente «vc2015_redist.x86.exe», que todavía puede descargarse de la página de microsoft: https://www.microsoft.com/es-es/download/details.aspx?id=53840. Entonces sí funciona la instalación de python, que creará un directorio «c:\python38». Ya puede borrarse el instalador en «c:\Python_».

Ahora hay que configurar el python para compilar esptool, esto se hace desde línea de comandos (abrir un CMD con Win+R y ejecutar CMD).

Instalar PIP (esta línea instalará PIP 22.0.4):
C:\Python38\python -m ensurepip --default-pip

Instalar intelhex (esta línea instalará intelhex 2.30):
C:\Python38\python -m pip install intelhex

Instalar pyserial en la versión compatible con pyinstaller y esptool (3.0.1):
C:\Python38\python -m pip install pyserial==3.0.1

Instalar pyinstaller en la versión compatible con windows XP y esptool 4.10):
C:\Python38\python -m pip install pyinstaller==4.10

Instalar wheel:
C:\Python38\python -m pip install wheel

Actualizar el entorno (actualiza setuptools de 56.0.0 a 69.1.1 y pip de 22.0.4 a 24.0.4):
C:\Python38\python -m pip install --upgrade pip setuptools wheel

A continuación es necesario salir de la consola con EXIT y reiniciar la máquina. Si no no carga algunas variables de entorno que se necesitan para el correcto funcionamiento de python y se producen errores raros de paths no encontrados etc.

Descargar la última versión del código fuente de esptool (esptool-4.7.0.zip) de https://github.com/espressif/esptool/archive/refs/tags/v4.7.0.zip y descomprimirlo en «c:\esptool_». Ahora bastaría con compilarlo con pyinstaller. Para mi sorpresa, una vez compilado correctamente, al ejecutarlo conectado correctamente a un ESP32 falla después de iniciar la comunicación correctamente, al no encontrar unos ficheros .json «esptool/targets/stub_flasher/*.json». Parece que no se han incluido en el proceso de compilación. Después de mucho tiempo buscando el problema sólo encuentro una pista, cómo no en stackoverflow, https://stackoverflow.com/questions/41870727/pyinstaller-adding-data-files

El comando correcto para compilar con TODOS los ficheros necesarios es:
cd c:\esptool_
pyinstaller -F --add-data "c:/esptool_/esptool/targets/stub_flasher/*.json;esptool/targets/stub_flasher/" --onefile esptool.py   

Esto debe dejar el ejecutable «esptool.exe» en «C:\esptool_\dist».

Como internet es efímero, dejo aquí el ejecutable compilado y la versión de python personalizada:

El ejecutable esptool.exe para Windows XP ya compilado:

La versión de «Zorba the Geek» de python3 para Windows XP:

Ingeniería inversa de un convertidor RS232 a ETHERNET parte 3

Ahora que ya tengo terminado el protocolo «discover» puedo hacer que el instalador del driver «NPORT Windows Driver Manager 3.5» reconozca mi ESP32 como un dispositivo MOXA y monte los puertos COM1 y COM2. El siguiente paso es identificar el protocolo de configuración del puerto y el protocolo de transmisión de datos. Aparentemente los puertos TCP/IP 960 y 966 tienen algo que ver:

Para averiguarlos monto el MOXA DE-311como puerto COM1 y analizo con WireShark la comunicación cuando abro el puerto con un programa terminal (Realterm)y envío datos:

Voy cambiando las configuraciones del puerto, baudrate, bits, paridad, control de flujo etc

Los datos de configuración del puerto se envían por el puerto TCP/IP 966. He observado que si hay más puertos activos utiliza los puertos TCP/IP 967, 968 etc hasta 982. El protocolo es algo distinto del protocolo «discover» pero sencillo:

El primer byte es el código de comando, el segundo es la longitud total mensaje y a continuación un grupo de datos variable, con un significado distinto según el comando.

He identificado bastantes comandos de configuración, aunque a otros no he sido capaz de encontrarles la utilidad.

  • 39 00 = Abrir el puerto
  • 2F 04 E8 03 00 00 = repetido x3 = Cerrar el puerto
  • 13 00 = KeepAlive o Heartbeat
  • 2C 13 10 03 01 01 00 00 00 00 4C454E4F564F2D54455354 = Configurar baudrate, bits etc
  • 17 04 40380000 = Baudrate arbitrario
  • 10 02 00 03 = Configurar baudrate, bits, paridad y bits stop
  • 11 04 00 00 00 00 = Establecer de flujo RTS/CTS DTR/DSR XON/FOFF
  • 12 02 00 00 = Establecer estado de RTS y DTR
  • 21 00 = Establecer BREAK
  • 22 00 = Cancelar BREAK
  • 30 01 10 = Sin identificar
  • 18 02 11 13 = Sin identificar

El MOXA contesta a casi todos estos comandos con ACK que es siempre COMANDO+4F4B

Los datos viajan bidireccionalmente entre el puerto COM y el MOXA por el puerto TCP/IP 950 sin modificación.

Con estos datos escribo el programa para ESP32 (DeVkit V2 o similar) «MoxaEmulator.ino» que realiza todas las funciones necesarias. El programa responde al «discover» y comunica por los puertos necesarios para emular un MOXA DE-302 de dos puertos. El programa sólo hace ECO de los datos serie que entran, enviándolos a la salida, pero eso permite ver que funciona. En esta captura se ve la detección del MOXA DE-311 auténtico y el DE-302 emulado:

El programa del ESP32 envía gran cantidad de información de depuración por el puerto serie, mostrando el intercambio de mensajes con el driver.

Y eso es todo… al final me ha llevado dos tardes más de lo que pensaba, pero los programas han quedado más depurados y fáciles de entender. Analizando el código puede seguirse con relativa facilidad el protocolo de MOXA y puede usarse en otros proyectos.

Links de descarga de los tres programas, aunque como cada uno es la evolución del anterior el «MoxaEmulator.ino» es el más completo y contiene el código de los anteriores.

MoxaDiscover1.ino

MoxaDiscover2.ino

MoxaEmulator.ino

Ingeniería inversa de un convertidor RS232 a ETHERNET parte 2

Con el primer programa el software DSU ya identifica el ESP32 como un dispositivo MOXA de la serie DE. Ahora sigo analizando paquetes para depurar las respuestas. Usando el software «Nport Windows Driver Manager» observo otro mensaje de descubrimiento, lo incorporo también al programa.

El siguiente paso es analizar el protocolo para el cambio de IP del dispositivo.

Y observo con Wireshark la comunicación:

Con estas captura puedo identificar comandos distintos: 0x45 que parece sirve para habilitar la configuración y seleccionar IP estática o DHCP, 0x33 para cambiar la IP y la máscara de red, 0x34 para cambiar el gateway y 0x35 que parece sirve para salvar los cambios, ya que no contiene información.

Con esta información actualizo el programa y ahora es posible también cambiar la IP del ESP32 usando la aplicación DSU. Se trata de un programa de pruebas, la configuración recibida no se salva en EEPROM y se pierde al reiniciar, pero sirve como prueba de concepto: MoxaDiscover2.ino

Ingeniería inversa de un convertidor RS232 a ETHERNET

Motivación:
Llevo más de 15 años usando los dispositivos del fabricante MOXA para llevar puertos serie RS232/RS485/RS422 a distancias largas a través de ethernet.
He usado principalmente los DE-311
https://www.moxa.com/en/products/industrial-edge-connectivity/serial-device-servers/general-device-servers/nport-express-series/de-311
y los NPort5210
https://www.moxa.com/en/products/industrial-edge-connectivity/serial-device-servers/general-device-servers/nport-5200-series/nport-5210

He conectado PLCs Siemens, Hiachi, Omron mediante estos dispositivos a HMI Proface, Omron, ESA VT100 (https://www.esa-automation.com/en/en-products/hmi/), transmisores de peso AUMAT4100 y DAT500 (https://www.pavonesistemi.com/es/electronica-de-pesaje-transmisores-de-peso-dat500) etc
El proceso es fácil, se conecta un dispositivo MOXA adecuadamente configurado para la red ethernet y con un cable personalizado al PLC y en el otro extremo otro al HMI, sensor etc.
Los datos serie entran por uno junto con las señales de control DTR/DSR y RTS/CTS etc y salen por el otro también con sus señales de control DTR/DSR y RTS/CTS después de viajar enrutadas por la red.

Pero una de las mejores funciones que tienen los dispositivos de MOXA es que permiten la opción «Windows Real COM Driver»!!
Cuando el dispositivo de un extremo es un PC con sistema operativo Windows no es necesario montar un MOXA conectado a un puerto serie del PC. Basta con conectar el PC a la red e instalar un driver que crea un «puerto serie virtual» en el PC conectado con un MOXA remoto.
De esta manera los programas en el PC trabajan con un puerto serie como siempre, mientras los datos se envían por la red al moxa remoto por donde salen al dispositivo.
Estos drivers tienen soporte desde Windows 95 hasta Windos 11.

Los productos de MOXA son excelentes y la calidad es excepcional, incluso indican el MTBF en la hoja de datos (más de 225000 horas!). Nunca me ha fallado uno, por lo que no tengo razones para dejar de usarlos.
Sin embargo, para aplicaciones domésticas y experimentación, siempre he ansiado poder tener un puerto serie virtual en el PC que me conecte por la red con mis micros, por ejemplo con ESP32.
De esta forma podría seguir usando los programas antiguos, que comunican por puerto serie, pero a través de la red.

Entonces… si averiguo el protocolo que usa MOXA en sus dispositivos cuando están en modo «REAL COM» podría escribir un programa para mis micros y usar su driver «Windows Real COM Driver»!

Proceso:
Para llevar a cabo la ingeniería inversa del protocolo necesitaré un sniffer de red, utilizaré WireShark: https://www.wireshark.org/
Como esto va a ser una «chapucilla de fin de semana» utilizaré la última versión bajo Windows 11 (Wireshark 4.2.0) en mi taller y una versión más antigua con soporte (no oficial) para Windows 7 para cuando trabaje en el pueblo con un viejo Packard Bell con Core2Duo T7200 http://ftp.uni-kl.de/pub/wireshark/win64/Wireshark-win64-3.6.18.msi.
Analizaré el protocolo de dos MOXA distintos, un DE-311 y un NPort5210 conectados mediante cable cruzado (aunque no es necesario los moxa tienen conexión de red RJ45 10/100 MDi/MDiX) directamente con el PC.
De esta forma minimizo el tráfico de red y no tengo que usar filtros en WireShark.

En el PC instalaré los dos programas que proporciona MOXA para gestionar estos dispositivos.
El programa que permite descubrir dispositivos en la red y asignarles IP:
DSU 2.7 build 23032110 (moxa-device-search-utility-v2.7.zip /dsu_setup_Ver2.7_Build_23032110.exe)
El instalador del driver COM que desubre dispositivos en la red y permite configurarlos como puerto serie local existe en varias versiones según el sistema operativo:
NPORT Windows Driver Manager 3.5 build 22120118 (moxa-win-driver-manager-v3.5-win-7-to-10-and-win-server-2008-r2-to-2019-whql-certified-driver-v3.5.zip / drvmgr_setup_Ver3.5_Build_22120118_whql.exe): Para Windows 7 a Windows 10 y
NPORT Windows Driver Manager 4.2 build (moxa-win-driver-manager-win-11-and-server-2022-and-later-whql-certified-driver-v4.2.zip / drvmgr_setup_Ver4.2_Build_23120717_whql.exe): Para Windows 11
También existen versiones para Windows 95 a Windows Vista que no necesito usar en este proyecto…

Alimento el DE311 y lo conecto al PC mediante un cable de red.

Arranco el WireShark como administrador para capturar de la tarjeta ethernet donde he conectado los MOXA.

Arranco el DSU como Administrador y pulso search.

Si todo va bien deberé poder ver la IP del MOXA en el DSU.

Cierro el DSU y voy a mirar los paquetes capturados por el WireShark.

Identifico varios paquetes UDP desde la IP del PC a la IP de broadcast 255.255.255.255, repetidos varias veces. Seguro que esta es la forma que tiene DSU de «llamar» a los MOXA:
PC (192.168.0.2) port 63683 envia UDP a broadcast (255.255.255.255) port 29168 con 8 bytes 0100000800000000
PC (192.168.0.2) port 63685 envia UDP a broadcast (255.255.255.255) port 4800 con 8 bytes 0100000800000000
PC (192.168.0.2) port 63687 envia UDP a broadcast (255.255.255.255) port 1029 con 6 bytes 490000060000
PC (IPV6 ) port 63691 envia UDP a multicast (IPV6 FF02::1) port 1029 con 28 bytes 7900001c000000000000000000000000000000000100000000010001

Observo que la IP del MOXA sólo contesta a uno de ellos, el sexto, enviado al port 1029 en IPV4 con la respuesta:
MOXA (192.168.0.31) port 50546 envia UDP a broadcast (255.255.255.255) port 1029 con 24 bytes C900001800007EF94CB81103EB9EC0A8006F0090E8049EEB

Supongo que el resto de paquetes no contestados que envía el PC para «llamar» a los posibles MOXA de la red son debidos a que el software DSU soporta varios modelos de MOXA y estará usando varios protocolos distintos…

A continuación el DSU envía un paquete, no ya de broadcast, sino a la dirección IP del MOXA que ha descubierto:

Posiblemente este paquete sirva para obtener información precisa del dispositivo:

PC (192.168.0.111) port 1028 envia UDP a MOXA (192.168.0.31) port 1029 con 6 bytes 010000060000
MOXA (192.168.0.31) port 1029 envia UDP a PC (192.168.0.111) port 1028 con 86 bytes 810000560000FC70148C1103EB9EC0A8006F000104730000040202014E5034303638332020202020202020202020202020202020202020202020020F0F0F0F0F0F0F0F0F0F0F0F0F0F0FC0A80021FFFFFF00C0A80001
También repito el proceso varias veces y siempre obtengo respuesta por el port 1029, también cambian cambian 4 bytes:
MOXA (192.168.0.31) port 1029 envia UDP a PC (192.168.0.111) port 1028 con 86 bytes 810000560000 E118A3CE 1103EB9EC…

Repito varias capturas cambiando la IP del MOXA y del PC para ver las diferencias en los paquetes. A continuación comparo a información que conozco con los datos obtenidos en las capturas para averiguar el significado de cada paquete:

Ahora compararé los paquetes capturados con la información que conozco para ver en que posición hay informacion reconocible.
MOXA DE-311 HW 2.1C, SW 2.4
production S/N á0BEE1240683
S/N 40683 = 9EEB hex
MAC 0090E8049EEB
IP 192.168.0.31 C0A8001F
mask 255.255.255.0 FFFFFF00
gw 192.168.0.1 C0A80001
PC Win 11 con
MAC F8:E4:3B:AF:97:87
IP 192.168.0.111 C0A8006F
mask 255.255.255.0 FFFFFF00

Estas pruebas veo que el primer paquete (comienza por 0x49) y su respuesta podrían tener esta estructura:

Y el segundo paquete, que comienza por 0x01, podría tener esta otra:

Con esta información recopilada he podido escribir un pequeño programa para ARDUINO ESP32 y probar si es correcto. He confirmado que los campos «Modelo» y «Version SW» son correctos cambiándolos por otros valores y observando en DSU la detección. Por ejemplo, en la siguiente captura hago que el ESP32 se identifique como MOXA DE-302 con un firmware version 4.2.

Esta es la captura de la salida de depuración del programa «MoxaDiscover1.ino«. (he eliminado las líneas repetidas):

Moxa discover responder (c) Heli Tejedor, V0.1, enero 2024
Reporting MOXA DE-302 SW version 4.2
Connecting to: xxxxxxxxxx:yyyyyyyyyy
……
Connected!
Signal %:86
MAC: 24:62:AB:F1:DA:44
IP: 192.168.0.165
NetMask: 255.255.255.0
Gateway: 192.168.0.1
DNS: 100.100.1.1
UDP Listening on PORT: 1029… OK
Running at Mhz: 240, Free RAM: 259432
UDP packet type: broadcast, from: 192.168.0.220:57202, to: 255.255.255.255:1029, length: 6, data: 490000060000
Response -> C900001800001214FAFB0203EB9EC0A800DC2462ABF1DA44
Discover CMD 0x49

UDP packet type: broadcast, from: 192.168.0.220:57202, to: 255.255.255.255:1029, length: 10, data: 4900000A00000203EB9E
Response -> C900001800001214FAFB0203EB9EC0A800DC2462ABF1DA44
Discover long CMD 0x49

UDP packet type: unicast, from: 192.168.0.220:1028, to: 192.168.0.165:1029, length: 6, data: 010000060000
Response -> 8100005600001214FAFB0203EB9EC0A800DC000104730000020402014E5034303638332020202020202020202020202020202020202020202020020F0F0F0F0F0F0F0F0F0F0F0F0F0F0FC0A800A5FFFFFF00C0A80001
Read Config CMD 0x01

La siguiente fase: averiguar el protocolo de configuración para cambiar a IP y los otros parámetros de la red

Un autómata programable (PLC) casero, otra vez.

Viene de aqui: https://heli.xbot.es/?p=33

Han pasado otros diez años (alguno más), y nos hemos metido en 2020. El PLC casero no esta muerto, una tarjeta con un micro H8-500 y la versión 3.8 en su EPROM 27C256 continúa trabajando. Todavía controla, con sus 32 entradas y sus 32 salidas digitales, sus dos puertos serie y sus 32K de RAM con RTC y batería de backup, un filtro prensa destinado a desecar lodos.

Aprovechando el parón por el coronavirus me he puesto a revisar el software de programación ECP. La intención era portarlo a Windows. ECP solo corre en MSDOS debido a que hace acceso directo al hardware. Esto era una necesidad en su momento, ni la BIOS ni MSDOS proporcionaban una API adecuada para manejar el hardware. Por ejemplo, la velocidad más alta que podía programarse en los puertos serie era 19200 bauds, aunque los chip UART 8250 podían trabajar hasta 115200 bauds. Solo escribiendo directamente en los registros adecuados de la UART podían conseguirse todas las velocidades diponibles.

Con la llegada de sistemas operativos más avanzados (XP y posteriores) ya no es posible interactuar directamente con el hardware de la máquina. De eso se encarga el propio sistema operativo mediante los drivers, pero proporciona una API para que los programas de usuario puedan acceder al hardware.

El plan era el siguiente: cambios mínimos en el programa ECP para eliminar todas los accesos al hardware y hacerlo a través de la API de Windows. El programa ECP estaba escrito en ANSI C (Borland Turbo C 2.0, todavía se puede mediante DOSBOX bajo Windows XP). Para la nueva versión usaría GCC 9.30 bajo Msys2 y MinGW en Windows 7 x64 ( y en otros momentos máquinas bajo Windows 10 x64). No uso interfaz gráfico, mantengo el interfaz en texto pero usando la librería ncurses.

El resultado es una vesión (alfa todavía) que corre en desde Windows Vista hasta Windows 10 (incluidos Windows 7 y 8.x) de 32 o 64 bits, y también en Windows XP de 32 bits: https://heli.xbot.es/wp-content/uploads/2020/05/EcDuino4.51.zip.

Del mismo modo he tomado los fuentes en C del firmware del PLC V3.8 y lo he portado a Arduino (uso en entorno arduino 1.8.12, aunque compilará con otros anteriores). Junto con algunas librerías he conseguido que entre en la limitada RAM (2K) del Arduino UNO (Atmega328). Teniendo en cuenta que las versiones antiguas trabajaban en micros con 32K de ram es todo un lograo. Para conseguirlo he tenido que rehacer el mapa de memoria del PLC, menos E/S, menos reles internos etc. El resultado es un PLC con las siguientes características:

256 pasos de programa, 32 temporicadores/contadores, 32 entradas y 32 salidas. Dos entradas analógicas 0 a 5V de 10 bit y una salida analógica de 10 bit de 0 a 5V. 64 + 64 relés internos y reloj (si se usa el módulo RTC DS3231).

Como extra he mantenido el HMI (human-machine interface), con teclado de 6 teclas por I2C mediante PCF8754 y pantalla LCD d e16 caracteres por 2 líneas por I2C. Esto nos permite interactuar con el PLC.

Este es el montaje de prueba:

Este es el software para el Arduino UNO. Yo he usado el entorno 1.8.12, pero funcionará con otros más antiguos. Son necesarias 3 librerías que se encuentran en el directorio «libraries» dentro del ZIP: https://heli.xbot.es/wp-content/uploads/2020/04/EcDuinoUno0.2

El reloj no es imprescindible para el funcionamiento del programa, eliminarse de forma opcional. También es posible eliminar el conjunto del display y teclado dejando sin conectar los dos hilos del I2C amarillo y gris que val a los pines A4 y A5 del arduino. En caso de eliminar todo es recomendable conectar en esos pines un par de resistencias de 10K a positivo, o mejor eliminar la línea 65 de header.h «#define _DISPLAY_» para que no se compile la parte del display.

Algunos programas para el PLC para hacer pruebas: https://heli.xbot.es/wp-content/uploads/2020/04/ProgramasTest.zip

Migrando de Arduino UNO a ESP8266: excepciones

Estoy portando un programa que tengo funcinando en varios Arduino UNO a un ESP8266 y me encuentro que no funciona, provoca una excepción 28 y un volcado de pila en cuanto introduzco datos…

Excepciones del ESP8266: https://arduino-esp8266.readthedocs.io/en/latest/exception_causes.html

Después de mucho revisar compruebo que el programa para Arduino no lo tengo demasiado bien escrito, pero no causa problemas. Eso es debido a que no tiene MMU (unidad de manejo de memoria). El ATMEGA328P es un micro muy sencillo y tiene toda la memoria expuesta. Si tengo un «puntero loco» siempre puedo leer el o escribir en la dirección de memoria a donde apunta.

Sin embargo, el ESP8266, tiene un micro Tensilica Xtensa, bastante más complejo, con MMU https://en.wikipedia.org/wiki/Memory_management_unit y excepciones. Si un puntero apunta a la zona de memoria incorrecta es muy facil que se genere una excepción al intentar leer o escribir datos.

El código mal programado es este:

char *SavePtr;

char * MySerialbuffer;

char *Token = strtok_r(MySerialBuffer, " ", &SavePtr);
if (strcmp(Token, "Read") == 0) // Primera palabra clave
{
Token = strtok_r(NULL, " ", &SavePtr);
while (*SavePtr == ' ') SavePtr++; // Elimina espacios extra
  if (strcmp(Token, "Data") == 0) // Segunda palabra clave
  {
  int Src = atoi(strtok_r(NULL, Space, &SavePtr)); // Primer parametro
  int Len = atoi(strtok_r(NULL, Space, &SavePtr)); // Segundo paametro
// etc...
}
}

En MySerialBuffer estan los comandos que recibo por el puero serie. El trozo de código anterior detecta si he escrito «Read Data 12 23» donde 12 y 23 serían dos parámetros numéricos… Pero ese código es un despropósito.

La función «Token = strtok_r(caden ade entrada, delimitadores, saveptr)» https://linux.die.net/man/3/strtok_r devolverá un puntero nulo (NULL) cuando no encuentre una cadena que terminada por los delimitadores. En un micro sin MMU eso no tiene trascendencia, excepto porque cuando leamos datos de *Token estaremos leyendo de la zona de memoria 0 que puede contener cualquier cosa. En mi caso como son comparaciones con palabras clave es casi imposible que en esa zona de memoria haya algo parecido a lo que busco, la comparación falla y ya esta.

Pero en el ESP8266, con el micro Xtensa y su MMU, leer datos de un puntero nulo genera una excepción y el programa termina en una detención halt.

La forma correcta de programar es ir comprobando que el puntero devuelto por strtok_r no es nulo:

char *Token = strtok_r(MySerialBuffer, " ", &SavePtr);  
if (Token==NULL) return 0; // No continuar
if (strcmp(Token, "Read") == 0) // Primera palabra clave
{
Token = strtok_r(NULL, " ", &SavePtr);
if (Token==NULL) return 0; // No continuar
// Elimina espacios extra, con seguridad
if (SavePtr!=NULL) while (*SavePtr == ' ') SavePtr++;
  if (strcmp(Token, "Data") == 0) // Segunda palabra clave
  {
Token = strtok_r(NULL, Space, &SavePtr);
  if (Token != NULL) Src = atoi(Token);
  else return 0; // Falta un parametro
  Token = strtok_r(NULL, Space, &SavePtr);
  if (Token != NULL) Len = atoi(Token);
  else return 0; // Falta un el otro parametro
// etc...
}
}

Es lo que pasa cuando uno se acostumbra demasiado a programar micros ulrasencillos, luego hay que volver a la realidad de las MMU, la alineaciones a página etc

Cerradura electronica con tarjetas de proximidad Mifare con Arduino UNO

Hace unos meses me pidieron un curso personalizado centrado en el uso de tarjetas de proximidad (contactless cards) con tecnología Mifare desde Arduino. Aunque es una tecnología que ya había usado antes no la conocía lo suficiente como para dar un curso. Entonces pensé que lo mejor sería desarrollar algún proyecto con esta tecnología para empaparme bien de sus peculiaridades. Y lo que empezó como un miniproyecto de autoaprendizaje esta terminando en un monstruo que ya ocupa 30K de los 32K que tiene Arduino UNO!

Esta cerradura es bastante completa, tiene casi todas las funciones que se pueden desear en una cerradura electrónica:
– Cada cerradura puede manejar 100 tarjetas distintas en sus listas, si no se usan listas el número de tarjetas es de 255 para cada cerradura. Se pueden configurar 255 cerraduras distintas.
– Funcionamiento Tarjeta + PIN o solo tarjeta. El PIN puede ser de hasta 8 dígitos.
– Funcionamiento en modo Trust, en el que todas las tarjetas son aceptadas si contienen los datos cifrados correctos, o NO trust en el que las tarjetas deben ser introducidas previamente en una de las listas de cada cerradura.
– Listas: Blanca: tarjeta aceptada, util en el modo NO Trust. Negra: La tarjeta incluida en esta lista es rechazada y activa la alarma. Gris: La tarjeta es aceptada pero activa la alarma silenciosa. Gold: Las tarjetas de esta lista NO necesitan PIN, Es más cómodo operar la cerradura pero menos seguro si un usuario pierde la tarjeta.
– Pin de pánico. Si se introduce el PIN 2 se permite el acceso pero se activa la alarma silenciosa.
– Tarjetas personalizables con un mensaje de 16 caracteres que se muestra al usarlas.
– Dos franjas horarias de permiso en cada tarjeta, la tarjeta se rechaza fuera de esas franjas horarias.
– Tarjetas con fecha de caducidad preprogramable, pasada la fecha son rechazadas por las cerraduras.
-Anti Passback programable. Si se activa esta función es necesario salir antes de volver a entrar. Son necesarias cerraduras distintas para la entrada y la salida.
– Niveles de acceso: cada tarjeta se programa para abrir las cerraduras de igual o menor nivel, y no las de mayor nivel.
– Permisos: cada tarjeta se programa con unos permisos: abrir, administrar, cancelar alarma etc. Es posible crear tarjetas que solo permiten poner en hora la cerradura o cancelar la alarma.
– Cambio de PIN por el usuario. Introduciendo el PIN actual en cualquier cerradura.
– Cambio de PIN administrativo. Si un usuario olvida su PIN es posible cambiarlo sin conocerlo. Se necesita autorizar la operación con una tarjeta que tenga activo el permiso de cambio administrativo de PIN.
– Las tarjetas son compatibles con otros sistemas. Los datos almacenados en la tarjeta ocupan 2 bloques y 7 sectores y es posible configurar 4 zonas distintas. El lector busca datos válidos en cada una de las 4 zonas. Esto permite utilizar los bloques que dejan libres otros sistemas y trabajar conjuntamente. Yo lo he probado con tarjetas de vending de Autobar y funciona perfectamente: la tarjeta sigue siendo válida en las máquinas de vending y además funciona en las cerraduras.
– Datos cifrados. La tecnología mifare cifra los datos en las comunicaciones y tiene unas Keys de 48 bits pero es una tecnología ya rota. Existen distintos tipos de ataques que podrían revelar los datos contenidos en las tarjetas o incluso copiarlas. Esta cerradura cifra los datos con elalgoritmo XXTEA y una key de 128 bits. Aunque un atacante pudiera leerla y copiarla no podría averiguar el PIN ni modificar los datos.
– Registro de acceso en las tarjetas: en cada tarjeta se almacenan los 5 últimos usos, con fecha y hora, número de cerradura y tipo de acceso. Estos datos pueden consultarse desde el menú administrativo.
– Registro de acceso en la cerradura: cada cerradura almacena el último acceso de cada una de las 100 tarjetas distintas.
– Configuración sencilla desde PC mediante un programa terminal serie (o desde el entorno Arduino). Una vez introducidos los datos mínimos: password de comunicaciones y Keys es posible crear y administrar las tarjetas desde la propia derradura sin PC.
– Sin Keys ni password por defecto. Eso suele ser un foco de problemas de seguridad. La cerradura NO funciona si no se programa el password de comunicaciones. Es necesario programar también las Keys y recordarlas si quiere que distintas cerraduras sean compatibles entre si.
– Configuración sencilla mediante tarjeta administrativa ZeroCard que permite el acceso a todos os menús de configuración.
– Pantalla LCD de 2×16 para mostrar estado y configurar la cerradura.
– Realimentación visual con dos LED de estado y sonora con altavoz para un uso más cómodo.
-Salida de relé para alarma silenciosa en caso de que el usuario introduzca el PIN 2 con una tarjeta válida o si la tarjeta esta en la lista negra o gris.
– Interruptor anti tamper para detectar manipulación de la cerradura, con alarma y bloqueo del funcionamiento. Posibilidad de autodestrucción en caso de manipulación (se borran las keys y contraseñas).
– Detección de hardware en runtime, que permite detectar y dar alarma si falla el RTC o el lector MFRC522.
– Alarma retentiva, no se desactiva al quitar alimentación. Es necesario borrarla usando una tarjeta con el permiso de cancelar alarma.
– Posibilidad de desformatear las tarjetas y dejarlas en configuración de fábrica (transport). De esta forma pueden usarse en otros sistemas.

El hardware es bastante sencillo:
– Arduino UNO: https://www.tiendatec.es/arduino/placas/379-placa-uno-r3-atmega328p-cable-usb-compatible-arduino-uno-r3-8403790020005.html
– Reloj RTC por I2C zs-042 (chip DS3231): https://www.tiendatec.es/arduino/modulos/400-modulo-zs-042-reloj-en-tiempo-real-rtc-basado-en-ds3231-at24c32-para-arduino-y-raspberry-pi-8404001180013.html
– Lector RFID MFRC522: https://www.tiendatec.es/arduino/rf-rfid-nfc/394-modulo-rfid-rc522-kit-rfid-nfc-con-tarjeta-y-llavero-para-arduino-y-raspberry-pi-8403941180015.html?search_query=mifare&results=7
– Display LCD de 2 líneas de 16 caracteres por I2C: https://www.tiendatec.es/arduino/pantallas-displays/683-pantalla-lcd-1602a-16×2-bus-i2c-iic-azul-para-arduino-8406831590000.html
– Dos relés de nivel lógico: https://www.tiendatec.es/arduino/reles/521-modulo-ky-019-rele-1-canal-5v-para-arduino-8405211430004.html
– Teclado de matriz de 4×4 (aunque solo uso 3×4): https://www.tiendatec.es/arduino/componentes-de-entrada/384-teclado-membrana-4×4-teclas-con-adhesivo-8403841270014.html
– Algunos componentes sueltos: 2 resistencias de 330 Ohmios, un LED rojo y otro verde, un condensador electrolítico de 10uF y 16V, un altavoz de 8 Ohmios y un microinterruptor o contacto reed. También son necesarias algunas resistencias más para convertir el teclado de matriz a analógico: 2 resistencias de 4K7, dos de 1K5, dos de 15K, y una de 2K2.

El esquema es este:

Y el montaje práctico este:

El programa del Arduino necesita algunas librerías adicionales:
Para gestionar el lector MFRC522: https://github.com/miguelbalboa/rfid
Para gesionar el display LCD por I2C: https://github.com/marcoschwartz/LiquidCrystal_I2C
Para gestionar el reloj RTC DS3231: https://github.com/adafruit/RTClib Esta librería la he modificado para detectar, en cada lectura, si el chip RTC esta presente, de forma parecida a como funciona esta otra: https://github.com/PaulStoffregen/DS1307RTC
Mi librería RTClib: RTClib_heli

Para que el programa funcione correctamente es necesario que la memoria EEPROM del Arduino este vacía (a 0xFF). En esta memoria se graban datos importantes, la contraseña de comunicaciones etc y si tiene valores incorrectos no podremos comunicar con la cerradura, ni podrá leer las tarjetas. Lo mejor es borrar previamente la memoria EEPROM, por ejemplo con este programa: https://heli.xbot.es/?p=415

Aunque no esta totalmente terminado y me faltan por probar de forma exaustiva algunas funciones el programa es completamente funcional. Todos los ficheros del proyecto, con los fuentes para Arduino, librerías y esquemas: Cerrojus 0.7.B

Así se ve el prototipo:, la electrónica con arduino y el teclado cony del display LCD. El lector MFRC522 esta pegado tras el teclado y es capaz de leer las tarjetas a través de él.

Y esta es la vista de los circuitos en la parte trasera:

Para el funcinamiento seguro de la cerraura se usan varias Keys y contraseñas que se almacenan en la EEPROM del arduino. Cuando se carga el programa por primera vez deben configurarse con los valores adecuados. Si queremos que distintas cerraduras sean compatibles entre sí deben configurarse con las mismas keys. Esta configuración inicial se realiza desde el propio entorno arduino de programación, mediante el monitor serie.
En el primer uso el menú de administrador no necesita autorización. La primera tarjeta que se cree será la ZeroCard que es la tarjeta administrativa con todos los permisos activos. Después el acceso a los menús necesitará una autorización usando una tarjeta y el PIN correspondiente, dependiendo de los permisos de la tarjeta estarán activos unos menús u otros.
Usando la ZeroCard es posible gestionar todas las funciones de la cerradura y administrar las demás tarjetas, sin necesidad de PC.

En la siguiente entrada el manual de configuración y el manual de uso!

Arduino UNO con wifi ESP8266 por comandos AT simple

Los módulos ESP8266 vienen programados de fábrica para usarse como módems wifi. Para ello se debe usar una extensión del juego de comandos AT estándar: http://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf

Un problema que he encontrado es que los módulos ESP8266 antiguos (firmware 0.9.5) venían configurados por defecto con el puerto serie a 9600 baudios. Esto es muy lento pero muy adecuado para trabajar con los puertos série de software de Arduino UNO (librería softwareserial).
Los nuevos ESP8266 con el firmware (por ejemplo con firmware 1.5.2 ó 2.0.0) vienen configurados con el puerto serie a 115200 bauds. Esto no es problema para un Arduino MEGA que tiene varios puertos serie de hardware. Pero con un Arduino UNO, que solo tiene un puerto serie de hardware y esta conectado al convertidor USB <-> Serie, no podremos usarlos.
Si conectamos el ESP8266 al puerto serie nativo perdemos la comunicación con el PC y la capacidad de depurar el programa por el puerto serie. Además nos interferirá en la carga de sketchs desde el IDE e Arduino.
Si usamos un puerto serie de software (con la librería softwareserial) no podremos comunicar a más de 38400 bauds (teóricamente a 57600 pero yo no lo he conseguido, incluso a 38400 se producen muchos errores).

La solución que propongo es reconfigurar la velocidad por defecto de los ESP8266 a una velocidad mas baja compatible con la librería softwareserial. Una velocidad razonable para esta librería es 19200 baudios.

El procedimiento es el siguiente:
Cargar un sketch vacío en el IDE de arduino y enviarlo al Arduino UNO. A continuación conectar ESP8266 ESP01 para comunicar PC con ESP8266:
ESP8266 PIN 1 (RXD) -> ARDUINO UNO PIN 0 RX
ESP8266 PIN 8 (TXD) -> ARDUINO UNO PIN 1 TX
Según este esquema:

Es necesario que el Arduino UNO tenga un sketch vacío para que no interfiera en las comunicaciones con el ESP8266. Este esquema nos permite usar la placa Arduino UNO como convertidor USB <-> RS232 para comunicar el PC con la placa ESP8266.

A continuación abrir el monitor serie del IDE Arduino ( Herramientas -> Monitor Serie o pulsando Control + Mayúsculas + M) y configurar enviar «Ambos NL & CR» y la velocidad a 115200 baudios.
Enviar AT y enter y el módulo deberá responder OK si todo es correcto. Si la velocidad por defecto del módulo no es 115200 no responerá y posiblemente se vea «basura» en el monitor serie. Entonces habrá que probar distintas velocidades hasta conseguir que el módulo responda OK al comando AT.
Para configurar la velocidad por defecto a 19200 baudios, 8 bits de datos, uno de stop y sin control de flujo hay que enviar el comando:
AT+UART_DEF=19200,8,1,0,0
y el módulo responderá AT.
A continuación no responderá más a AT porque ya habrá conmutado a la nueva velocidad. Para probarlo seleccionar 19200 baudios en el monitor serie y enviar AT. La respuesta deberá ser OK si todo es correcto. Tras este procedimiento la velocidad por defecto queda fijada a 119200 baudios incluso tras un eset del módulo o tras quitarle alimentación.
En este fichero estan los esquemas e instrucciones para este proceso: Esp8266_Config

Ahora ya puede conectarse el ESP8266 en una par de pines generales y configurarlos para puerto serie de software.
Podemos usar los pines 2 y 3 para el puerto serie y conectar también un LED y un pulsador siguiendo este esquema:

A continuación cargamos en el IDE de arduino el sketch «Esp8266_Simple.ino»: Esp8266_Simple
Para monitorizar el trabajo de este sketch se puede usar el monitor serie configurado a 115200 baudios.
La salida será similar a esta:
Enviando: AT+RST
Respuesta: AT+RST
OK
(algo de basura…)
ready
Enviando: AT+CWSAP_CUR=»MyESP8266″,»Test1234″,5,3
Respuesta: AT+CWSAP_CUR=»MyESP8266″,»Test1234″,5,3
OK
Enviando: AT+CWMODE=2
Respuesta: AT+CWMODE=2
OK
Enviando: AT+CIPMUX=1
Respuesta: AT+CIPMUX=1
OK
Enviando: AT+CIPSERVER=1,80
Respuesta: AT+CIPSERVER=1,80
OK
Enviando: AT+CIFSR
Respuesta: AT+CIFSR
+CIFSR:APIP,»192.168.4.1″
+CIFSR:APMAC,»1a:fe:00:00:00:00″
OK
Trabajando
Trabajando
Trabajando
Trabajando

Este programa crea un punto de acceso WIFI (AP) llamado «MyESP8266» con contraseña WPA2 «Test1234» en el canal 5. Nos conectamos a ese punto de acceso, introducimos la contraseña y abrimos en un navegador la dirección 192.168.4.1.
Podremos ver una página web simple con dos botones rotulados ON y OFF. Pulsándolos podremos cambiar el estado del LED conectado el PIN 13 del Arduino. Más abajo podemos ver el estado del PULSADOR conectado en el pin 4.

Lector, copiador y borrador de tarjetas RFID MIFARE

Estoy liado con varios proyectos que involucran lectores de tarjetas RFID tipo MIFARE de NXP https://www.nxp.com/products/identification-and-security/mifare-ics/mifare-classic:MC_41863 con lectores MFRC522 https://www.prometec.net/producto/rfid-kit-arduino y Arduino UNO

MFRC522

Uno de los probemas que he encontrado es que, cuando trabajas con montones de tarjetas y varios proyectos a la vez, es muy facil confundir tarjetas o incluso programar keys incorrectas. En esas condiciones las tarjetas pueden quedar inútiles (es imprescindible conocer las keys internas para usarlas o borrarlas).

Este programa para Arduino UNO (posiblemente funcione en otros) nos permite averiguar las keys programadas en las tarjetas para luego borrarlas o copiarlas sin preocuparnos de buscar las keys exactas que fueron programadas dentro.
Para ello es necesario compilar el programa con la lista de las keys comunmente usadas en nuestros proyectos. El programa intenta autentificarse en cada sector con cada una de las keys programadas y crea una lista de keys y secores. Esto nos permite luego borrar la tarjeta y dejarla en condiciones de fábrica o copiarla a otra vacía.
Es necesaria la librería de arduino MRFC522 que puede instalarse desde el gestor de librerías del arduino IDE o de aqui: https://github.com/miguelbalboa/rfid

El conexionado necesario es muy sencillo:

Fos ficheros del proyecto: HeliMifareClonner.zip

Para usar el programa se abre con el IDE de arduino y se carga en el arduino. Luego se abre el «monitor serie» y se configura a 115200 baudios y «sin ajuste de linea». Aparecerá un menú con 5 opciones, que es autoexplicativo:
1: PROBAR las Keys conocidas. Intenta averiguar que key, de la lista incluida en el programa, corresponde con cada sector de la tarjeta.
2: LEER Tarjeta: Lee la tarjeta usando la lista de keys averiguada anteriormente.
3: VER DATOS de tarjeta: Muestra el bloque de datos (1024 bytes) que ha podido leer con la opción 2
4: ESCRIBIR nueva tarjeta: Escribe una tarjeta vacía, que este con las keys de fábrica a FFFFFFFFFFFF, con los datos leidos anteriormente y luego instala las keys averiguadas en cada sector. Esto crea una tarjeta clon de la leíada anteriormente. El bloque 0, que contiene el UID y otros datos del fabricante, no se copia. Aunque algunas tarjetas de fabricantes alternativos (No NXP) permiten escribir en el bloque 0 usando una secuencia de comandos especial, este programa no lo hace (la línea de código necearia esta comentada en el programa) porque no he podido probar que funcione correctamente.
5: COMPROBAR Tarjeta con datos Compara los datos leidos previamente con los datos de otra tarjeta. Si las keys no son las mismas en las dos tarjetas los bloques trailer no podrán coincidir y habrá otros sectores que no podrán leerse y quedarán sin comparar «SIN DATOS».
6: BORRAR tarjeta: Borra una tarjeta usando las keys averiguadas para dejarla tal y como vienen de fábrica, con las keys a FFFFFFFFFFFF («transport keys»).

Como optimizar el consumo de memoria en programas de Arduino

El compilador que usa el entorno Arduino es muy óptimo y es dificil escribir el programa de forma que genere menos código: el compilador optimiza automáticamente independientemente de como lo esribamos.
Pero hay algunos detalles que permiten ahorrar bytes de RAM o de FLASH, cambiando un poco el enfoque de los programas!

Uno es usar el macro F() para las variables de cadena que pasamos a algunas funciones, principalmente Serial.print().
Usando como ejemplo el programa https://heli.xbot.es/?p=501 podemos cambiar la línea 61 de:
        Serial.println («Testing AnalogKeyPad»);
a
        Serial.println (F(«Testing AnalogKeyPad»));

Compilando con el IDE de Arduino 1.8.5 para Arduino UNO (Atmega328p),
la primera forma genera un programa de 1958 bytes que usa 269 bytes de RAM y
la segunda forma genera un programa de 1998 bytes que usa 249 bytes de RAM.
Hemos ahorrado 20 bytes de ram (de los 2048 disponibles) pero el programa ha crecido en 40 bytes!
Que no cunda el pánico, ahora optimizamos mas todavía:
cambiamos la línea 70 de:
        Serial.print («Pulsado «); Serial.println (b);
a
        Serial.print (F(«Pulsado «)); Serial.println (b);
Ahora el programa ocupa 2004 bytes y usamos 239 de RAM, el programa solo ha crecido en 6 bytes y hemos ahorrado 10 bytes de RAM.

El programa sobre el que estamos trabajando es muy sencillo y no tiene mas cadenas constantes, no podemos seguir mejorándolo, pero cualquier cadena que convirtamos en constante en el futuro ahorraría aproximadamente su longitud en RAM incrementando solo 6 bytes de código en FLASH.

¿Y por que es así?
Porque el micro ATMEGA328P, y toda la familia aTMEGA, son micros con Arquitectura Harvard y eso significa que los DATOS y las instrucciones de PROGRAMA se almacenan en espacios separados y se acceden usando instrucciones de programa distintas.
Eso representa un problema para el compilador porque tiene que generar un código distinto para manejar datos variables (en RAM) y datos constantes (en memoria de programa FLASH).
El compilador que usa el entorno de Arduino es el AVR-GCC y soluciona este problema copiando en RAM las constantes y usando siempre funciones que trabajan sobre RAM.
Esto tiene el inconveniente de que cada constante que declaramos en nuestro programa ocupa una cantidad igual de memoria de programa y de RAM.
Y esto no tiene facil solución porque, para manejar datos que solo esten en memoria de programa, hacen falta funciones distintas que sepan manejar los datos residentes en ese espacio de memoria.
En arduino existe el macro F() que permite instruir al compilador para que almacene una cadena constante solo en FLASH.
Además la función Serial.print() esta sobrecargada de forma que se compila de forma distinta para cadenas variables que para cadenas constantes.
La primera vez que usamos la función Serial.print(F()) el compilador añade el código de la librería necesario para manejar cadenas constantes y por eso el programa crece un poco. Los siguientes usos de la función solo incrementen un poco el código debido a la diferente gestión de los datos.
Lo importante es que cada uso de F() ahorra una cantidad de RAM igual a la longitud de la cadena usada! Y en RAM es donde mas justo esta el Atmega328p.

Podemos usar esta técnica para otras constantes, pero si la función que las ha de usar no esta adecuadamente sobrecargada (es decir, no tienen definida función para trabajar con datos en espacio de programa) no será posible. Tenemos que escribir nuestro propio código para acceder a esos datos en memoria de programa… pero es sencillo.

Este es un array normal, cada dato (int) ocupa dos bytes de FLASH y dos de RAM.
        const int KeyVals[nKeys+1] = { 693, 665, 624, 573, 544, 491, 463, 432, 393, 353, 308, 249, 188, 157, 117, 79, 0};

La forma de decir al compilador que queremos que almacene un array en memoria de programa es esta:
        const int KeyVals[nKeys+1 ] PROGMEM = { 693, 665, 624, 573, 544, 491, 463, 432, 393, 353, 308, 249, 188, 157, 117, 79, 0};

Pero al usar la segunda forma ya no podemos hacer:
if (Val >= KeyVals[i]) break;

porque el manejo de variables array solo puede trabajar con datos en RAM. ATENCION el compilador no informará de un error!!! pero el programa no funcionará. Los datos estarán almacenados en un espacio de memoria pero el programa intentará usar punteros a ese espacio de memoria en otro espacio de memoria distinto, con lo que apuntará a datos distintos y no funcionará!! Debe usarse la función adecuada para leer los datos almacenados en la memoria de programa, usando los punteros a memoria de programa ha generado el uso de PROGMEM: pgm_read_word(), porque los datos son de tipo int (ocupan 16 bits):
        if (Val >= pgm_read_word(KeyVals+i)) break;

Para el otro array, que es tipo char (8 bits), se debe usar la función
        pgm_read_byte(Keys+i);

NO se puede usar el índice [i] en un array PROGMEM ya que su gestión esta definida para trabajar con variables RAM. Debe usarse +i que sirve para punteros a RAM o a FLASH.

Después de aplicar estas dos optimizaciones al programa (dos líneas con F() y dos arrays cambiados a PROGMEM) el programa que antes ocupaga 1958 bytes de FLASH y usaba 269 de RAM ahora ocupa 1992 bytes de FLASH pero solo usa 189 bytes de RAM.
Ha crecido en 34 bytes de 32768 disponibles (~ 0.1%) pero ha bajado el uso de ram en 80 bytes de 2048, casi un 4% de mejora.

https://www.arduino.cc/en/Reference/PROGMEM

El programa optimizado: AnalogKeyPad1