Watchdog basado en nano de Arduino

Watchdog es un dispositivo diseñado para detectar y solucionar problemas de hardware. Por lo general, se utiliza un temporizador para esto, cuyo reinicio periódico evita que se envíe una señal al reinicio.



Yo utilizo el servidor principal de Gentoo principalmente para experimentos, sin embargo, ejecuta una serie de servicios que, si es posible, deberían estar disponibles sin interrupción. Desafortunadamente, las consecuencias de algunos experimentos conducen al pánico del kernel, el 100% de la carga de la CPU y otros problemas en el momento más inoportuno. Por lo tanto, la idea de agregar watchdog ha requerido atención durante mucho tiempo y finalmente se materializó en este dispositivo.

Después de una inspección minuciosa de lo que estaba disponible y una evaluación del tiempo disponible, el perro guardián montado sobre la base del Arduino Nano fue la mejor opción. Una lista de requisitos apareció más o menos igual:

  1. Iniciar y detener el demonio para que funcione con un temporizador, una herramienta normal del sistema operativo (OpenRC).
  2. Propio watchdog en el dispositivo, en ATmega es necesario usarlo.
  3. El registro de eventos en el dispositivo para arreglar el reinicio y el temporizador.
  4. Sincronice la hora del dispositivo con el host para registrar la hora correcta en el registro.
  5. Recibir y mostrar el estado del dispositivo y sus entradas de registro.
  6. Borrar el registro y restablecer el dispositivo a su estado original.

Por lo tanto, se encontró el "microscopio", se marca el "clavo" ... se puede martillar.

Hardware


La base del dispositivo fue el clon chino Arduino Nano, hecho sobre la base del chip CH340. Los nuevos núcleos de Linux (probados desde 3.16) tienen un controlador adecuado, por lo que el dispositivo se detecta fácilmente como un puerto serie USB.

Arduino reinicio no deseado


Cada vez que se conecta el terminal, el Arduino se reinicia. La razón es que el terminal envía una señal DTR (Data Terminal Ready), lo que hace que el dispositivo se reinicie. Por lo tanto, el IDE de Arduino pone el dispositivo en un modo para cargar bocetos.

Hay varias soluciones al problema, pero solo una funcionó: es necesario instalar un electrolito de 10 µF (C1 en el diagrama a continuación) entre los contactos RST y GND. Desafortunadamente, esto también bloquea la descarga de bocetos al dispositivo.

Como resultado, el esquema es el siguiente:


Dibujado usando KiCad

Explicaciones para el esquema.
  • R1 — , PC817: (5V — 1.2V / 0.02A) = 190Ω, 180Ω.
  • U2 — Arduino PC. , ( USB ), .
  • JP1 — , . .
  • 1 — , DTR.
  • MB_RST, MB_GND — RESET , RST (GND). , .
  • BTN_RST, BTN_GND — , , , , .


Boot-loop (reinicio cíclico) cuando se trabaja con WDT


Los microcontroladores ATmega tienen un mecanismo de reinicio WDT (WatchDog Timer) incorporado. Sin embargo, todos los intentos de usar esta función condujeron a un bucle de arranque, que solo se podía salir apagando la alimentación.

No largas búsquedas revelaron que los cargadores de arranque de la mayoría de los clones de Arduino no son compatibles con WDT. Afortunadamente, este problema se resolvió en el gestor de arranque alternativo Optiboot .

Para actualizar el gestor de arranque, necesita un programador que pueda trabajar en el protocolo SPI, también es deseable que el IDE de Arduino conozca este dispositivo "en persona". En este caso, otro Arduino es ideal.

Si tomamos Arduino UNO, como programador, y la última versión de Arduino IDE v1.6.5, entonces el algoritmo será el siguiente:

  1. boards-1.6.txt optiboot hardware/arduino/avr/boards.txt Arduino IDE.
  2. Arduino Uno, File → Examples → ArduinoISP.
  3. Arduino Nano :
    Arduino Uno ()Arduino Nano (ICSP )
    5V → Vcc
    GND → GND
    D11 → MOSI
    D12 → MISO
    D13 → SCK
    D10 → Reset

    Pin1 (MISO) ← D12Pin2 (Vcc) ← 5V
    Pin3 (SCK) ← D13Pin4 (MOSI) ← D11
    Pin5 (Reset) ← D10Pin6 (GND) ← GND



  4. En el IDE de Arduino, en el menú Herramientas, configure los ajustes como en la captura de pantalla:
  5. Seleccione el elemento de menú Herramientas → Grabar cargador de arranque y asegúrese de que el proceso se complete sin errores.


Después de este procedimiento, debe cargar bocetos en Arduino Nano eligiendo la misma configuración: placa : Optiboot en cpus de 32 pines, procesador : ATmega328p, velocidad de CPU : 16MHz.

Soldadura


A continuación, debe soldar todo, para que parezca una sola pieza.



Aquí necesitaba un enchufe USB debido al hecho de que tengo una placa base mini-ITX con solo un conector para un par de USB2.0, que se necesitan en el panel frontal, y no había nada que conectar a la almohadilla USB3.0. Si es posible, dichos dispositivos deben conectarse directamente a la placa base para que los cables no sobresalgan.

La soldadura, por regla general, no causa problemas, pero en este caso se utiliza una placa de pruebas, y esto tiene sus propios detalles.

Cómo soldar pistas en una placa de pruebas
( , ). .

:



Resultado:





puede parecer que algunos contactos están mal soldados, pero esto es solo un flujo. El consumo de soldadura en el tablero es bastante grande, por lo que todo lo que es posible está manchado con un flujo aquí. De hecho, este es un buen ejemplo de cómo no necesita dejar el producto después de soldar. El fundente debe lavarse, de lo contrario puede haber problemas con la corrosión de los compuestos. Agregaré e iré a lavar ... Eso es mejor:



 

Parte de software


Hablando objetivamente, el código de este proyecto no es de particular interés. Las introductorias están lejos de ser extremas, y la arquitectura se describe en una frase: envíe un comando - espere una respuesta. En aras del orden, describiré la funcionalidad principal aquí y me detendré brevemente en los puntos más interesantes, desde mi punto de vista.

Todo el código se publica en GitHub, por lo que si está familiarizado con Bash y C / C ++ (en el contexto de los bocetos de Arduino), la lectura en este punto puede finalizar. Si está interesado, el resultado final se puede encontrar aquí .

Conexión de vigilancia


Cuando conecta el perro guardián, se crea un archivo de dispositivo que contiene el número de serie. Si el sistema tiene otros dispositivos ttyUSB (en mi caso, un módem), entonces hay un problema con la numeración. Para identificar de forma exclusiva el dispositivo, debe crear un enlace simbólico con un nombre único. Para esto, se diseñó udev, que probablemente ya existe en el sistema.

Primero necesita encontrar visualmente el perro guardián conectado, por ejemplo, mirando el archivo de registro del sistema. Luego, reemplazando / dev / ttyUSB0 con el dispositivo deseado, escriba en el terminal:

udevadm info -a -p "$(udevadm info -q path -n /dev/ttyUSB0)"

Ejemplo de salida
  looking at device '/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4/1-1.4:1.0/ttyUSB0/tty/ttyUSB0':
    KERNEL=="ttyUSB0"
    SUBSYSTEM=="tty"
    ...
    
  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4/1-1.4:1.0/ttyUSB0':
    KERNELS=="ttyUSB0"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="ch341-uart"
    ...
    
  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4/1-1.4:1.0':
    ...
    
  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4':
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{idVendor}=="1a86"
    ATTRS{idProduct}=="7523"
    ATTRS{product}=="USB2.0-Serial"
    ...


En este caso, la regla se verá así:
ACTION=="add", KERNEL=="ttyUSB[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ttyrst-watchdog"


Debe colocarlo en un archivo separado en el directorio /etc/udev/rules.d , por ejemplo 51-ttyrst-watchdog.rules y decirle a udev que vuelva a cargar las reglas:
udevadm control --reload-rules


A partir de este momento, al conectar watchdog, se creará un enlace / dev / ttyrst- watchdog en el dispositivo deseado, que se utilizará más adelante.

Script Bash (ttyrst-watchdog.sh)


La comunicación con el perro guardián se realiza a una velocidad de 9600 baudios. Arduino funciona sin problemas con terminales a altas velocidades, pero los comandos para trabajar con texto (cat, echo, etc.) reciben y envían solo basura. Es posible que esta sea una característica de solo mi copia del Arduino Nano.

Para el ciclo de reinicio del temporizador principal y para las funciones de línea de comandos, se utiliza un script. La razón es que ambos componentes usan un recurso común: el archivo del dispositivo, y es necesario proporcionarle acceso sincrónico.

La sincronización consiste esencialmente en un ciclo de espera:
while fuser ${DEVICE} >/dev/null 2>&1; do true; done
y capturar el dispositivo durante el tiempo requerido:
cat <${DEVICE}


Obviamente, tal esquema está sujeto a una condición de carrera. Puede lidiar con esto de una manera adulta (por ejemplo, para organizar una cola de mensajes), pero en este caso, es suficiente establecer correctamente los tiempos de espera para garantizar que obtenga el resultado en un tiempo aceptable. De hecho, todo el script está trabajando con tiempos de espera.

La demonización (que se ejecuta en segundo plano) se realiza utilizando el paquete OpenRC. Se supone que este script está en el archivo /usr/local/bin/ttyrst-watchdog.sh , y el script OpenRC está en /etc/init.d/ttyrst-watchdog .

Cuando el demonio se detiene, se requiere la desactivación correcta del watchdog. Para hacer esto, el script establece el controlador de señal que requiere finalización:
trap deactivate SIGINT SIGTERM
Y aquí surge un problema: OpenRC no puede detener al demonio, o mejor dicho, pero no con frecuencia.

El hecho es que el comando kill envía una señal al script, y el programa de suspensión, que se usa para pausar el script, se ejecuta en otro proceso y no recibe la señal. Como resultado, la función de desactivación se inicia solo después de que se completa el sueño, que es demasiado largo.

La solución es comenzar a dormir en segundo plano y esperar a que se complete el proceso en el script:
sleep ${SLEEP_TIME} & wait $!  #  $!  ID   


Constantes básicas:

WATCHDOG_ACTIVE : SÍ o NO, respectivamente, envían una señal para reiniciar cuando el temporizador se activa o no.
WATCHDOG_TIMER : tiempo en segundos para el que está configurado el temporizador.
SLEEP_TIME : tiempo en segundos después del cual se debe reiniciar el temporizador. Debe ser mucho más pequeño que WATCHDOG_TIMER, pero no muy pequeño, para no crear una carga excesiva en el sistema y el dispositivo. En los tiempos de espera actuales, un mínimo razonable es de aproximadamente 5 segundos.
DEFAULT_LOG_LINES : el número de entradas de registro de dispositivo recientes devueltas por el comando de registro predeterminado.

Comandos de script:

inicio- Inicio del ciclo de reinicio del temporizador principal. Puede agregar un código de verificación adicional a la función is_alive, por ejemplo, para verificar la posibilidad de conectarse a través de ssh.
estado : muestra el estado del dispositivo.
reset : restablecer la EEPROM (datos de registro) y reiniciar el dispositivo para restaurar el watchdog a su estado original.
log <número de entradas> : muestra el número especificado de entradas de registro recientes.

Boceto Arduino (ttyrst-watchdog.ino)


Para compilar correctamente el boceto, necesita una biblioteca de tiempo de terceros , que es necesaria para la sincronización de tiempo.

Un boceto consta de dos archivos. Esto se debe al hecho de que Arduino IDE no acepta las estructuras (estructura) declaradas en el archivo principal, se deben mover a un archivo de encabezado externo. Además, la palabra clave typedef no es necesaria para declarar una estructura, probablemente sea incluso dañina ... después de verificar las opciones estándar, no pude encontrar la sintaxis adecuada. El resto es más o menos estándar C ++.

Las funciones wdt_enable y wdt_reset funcionan con el watchdog integrado en el microcontrolador. Después de inicializar el WDT, lo más importante para recordar es restablecerlo en el bucle principal y dentro de los bucles de todas las operaciones largas.

Las entradas de registro se escriben en la memoria EEPROM no volátil, su tamaño disponible se puede especificar en logrecord.h, en este caso es 1024. El registro se realiza en forma de anillo, el separador es una estructura con valores cero. El número máximo de entradas para 1 KiB EEPROM es 203.

Un registro sobre la carga del dispositivo llega al registro solo después de la sincronización horaria. La sincronización se realiza al mismo tiempo que se reinicia el temporizador y antes de que se ejecute cualquier comando durante la inicialización del dispositivo. De otra manera, no será posible comparar la hora correcta con este evento, y la información sobre reinicios del dispositivo, aislada del daemon de trabajo, no es muy interesante.



Eso es todo, ¡gracias por mirar!

Los archivos fuente del proyecto se encuentran en GitHub

Source: https://habr.com/ru/post/es384467/


All Articles