Cómo agregar un RTC a un sistema Linux (ejemplo con Beaglebone)

Introducción
Un Real Time Clock (RTC) es un dispositivo que mantiene la hora actualizada utilizando muy poca energía. Es decir, un RTC necesita de una fuente de energía externa para funcionar. Muchos equipos electrónicos tienen un RTC (los computadores, los celulares, etc).

Normalmente un módulo de RTC (i.e. un chip RTC incluído en una PCB para prototipar) como el que vamos a utilizar de ejemplo, incluye una batería externa.

En ausencia de una fuente de energía externa, la hora del RTC se pierde y vuelve a su valor por defecto, ya que esta se almacena en una memoria volátil. Se almacena en una memoría volátil porque la escritura (la cual sucede cada segundo normalmente) utiliza menos energía que en una memoria no-volátil.

Los RTC son manejados normalmente por I2C, con lo cual se puede setear y leer la hora.
Cómo agregar un RTC a un sistema Linux
Los RTC son un componente electrónico importante en el mundo de la computación. Es por eso que para la gran mayoría de los RTC en el mundo, ya existen los drivers necesario para manejarlos, sobre todo en sistemas Linux (Linux es usado en la gran mayoría, sino todos los microprocesadores).

Dicho lo anterior, si se quiere incluir un RTC a un microprocesador, se puede hacerlo:

  1. Manualmente: untuitivo para principiantes, pero obviamente no recomendado en productos profesionales, ya que no se puede asegurar un correcto funcionamiento.
  2. Automáticamente: requiere conocimiento del kernel para realmente entenderlo. Sin embargo, es la manera profesional y ,por lejos, la manera más confiable y eficiente de agregar un RTC a un sistema.

La manera manual, es en realidad una manera automática. A lo que nos referimos con esto es que se escribe código que corre en el userspace. Por ejemplo, cada vez que se prende el computador, se ejecuta un código escrito por el usuario o algún tercero que lee el RTC y luego se setea la hora del computador. Luego se necesita otro código o programa, para por ejemplo, actualizar la hora del rtc si es que existe internet.

En este tutorial, vamos a proceder de la manera automática. Por manera automática nos referimos que corre en el kernel, no en el userspace. Para que te hagas una idea de lo confiable que es esta manera de agregar un RTC, el driver que vas a estar utilizando podría ser utilizado incluso en computadores y celulares, lo cual es indicativo de lo confiable y eficiente que es este método. En este tutorial vamos a explicar lo mínimo para entender el funcionamiento, y en paralelo, agregar un RTC DS3231 a una BeagleBone (este tutorial sirve en parte para una Raspberry Pi, i.e. sistemas linux en general).

Verificar que existe un driver para tu RTC
Lo primero que debemos hacer al agregar un RTC como el DS3231 es verificar que existen los drivers en el sistema Linux que vamos a utilizar (Así es, lo drivers muchas veces ya están en el sistema operativo). Para ver los drivers relacionados a los RTC podemos hacer en una consola:

ls /lib/modules/$(uname -r)/kernel/drivers/rtc
El resultado en una Raspberry Pi que tenemos acá en Southern L Technologies:

rtc-abx80x.ko
rtc-bq32k.ko
rtc-ds1302.ko
rtc-ds1305.ko
rtc-ds1307.ko
rtc-ds1374.ko
rtc-ds1390.ko
rtc-ds1672.ko
rtc-ds3232.ko
rtc-em3027.ko
rtc-fm3130.ko
rtc-isl12022.ko
rtc-isl1208.ko
rtc-m41t80.ko
rtc-m41t93.ko
rtc-m41t94.ko
rtc-max6900.ko
rtc-max6902.ko
rtc-pcf2123.ko
rtc-pcf2127.ko
rtc-pcf8523.ko
rtc-pcf8563.ko
rtc-pcf8583.ko
rtc-r9701.ko
rtc-rs5c348.ko
rtc-rs5c372.ko
rtc-rv3029c2.ko
rtc-rx4581.ko
rtc-rx8025.ko
rtc-rx8581.ko
rtc-s35390a.ko
rtc-x1205.ko
							
Si el RTC que estás tratando de adaptar a tu dispositivo Linux no está listado, se vuelve un poco más complicado el proceso y para un principante no lo recomendamos. El siguiente paso es revisar el github del Linux Kernel y si es que está debes compilar el linux kernel para tu dispositivo. Si es que no está, lo cual es muy improbable, no vas a poder utilizar el remanente de este tutorial.

Como puedes ver, el DS3231 no aparece textual, lo más parecido es el DS3232, sin embargo, eso no quiere decir que el driver no sirva para controlar el DS3231 (spoiler: sí sirve). Para ver si el driver sirve, lo primero que hay que ver es el código fuente del driver que crees que podría servir (en este ejemplo el DS3232), el cual se encuentra en el github del Linux Kernel, y revisar la estructura of_device_id, si es que en el listado aparece tu dispositivo, entonces claramente el driver sirve para tu dispositivo. El DS3232 está listado en el fuente rtc-ds1307.c.

Imaginando el caso que el DS3231, no está listado en el driver del DS3232. El siguiente paso es buscar el datasheet de ambos RTC y analizar las diferencias. Al hacer este paso, la única diferencia importante entre los dos RTC parece ser que el DS3232 trae una SRAM y el DS3231 no. Afortunadamente, el driver del RS3232 parece no utilizar la funcionalidad de la SRAM, y dado que los registros parecen ser los mismos en ambos RTC, deberíamos esperar un mismo comportamiento si utilizamos el driver del DS3232 con el DS3231.
Escribir, compilar y habilitar un Device Tree
Para que el sistema operativo como una Beaglebone o Raspberry reconozca el dispositivo como parte de su hardware y utilize el driver correspondiente para utilizarlo, es menéster escribir una descripción de hardware (Device Tree). Una descripción de hardware indica al sistema operativo varias cosas:
  • El driver que debe ejecutar para manejar el dispositivo.
  • Parámetros de operación: ejemplos: velocidad del I2C, tipo de interrupciones, etc.
  • Pines o GPIOs a los cuales está conectado.

Es casi imposible escribir un Device Tree de cero y además se considera una mala práctica, se recomienda buscar alguno en internet, aunque en la mayoría de los casos no existe un device tree para tu combinación de hardware en particular. Esto ocurre porque el device tree depende de dos cosas: El dispositivo en el cual va a correr el driver (Beaglebone) y el dispositivo para el cual el driver está diseñado (DS3231) (en este caso, ni siquiera existe el driver específico para nuestro RTC, lo cual lo hace mucho más improbable de encontrar el Device Tree).

Dicho lo anterior, siempre hay que comenzar por una base y buscar otros drivers similares que sirvan de idea para saber cómo se construye un Device Tree (es complicado de entender al comienzo, acá hay muchos ejemplos para la beaglebone). También es necesario ver la documentación del driver (maxim,ds3231.txt) si es que existe.

El Device Tree para el DS3231 en combinación con la BeagleBone fue desarrollado acá en Southern L. Technologies y se distribuye bajo la licencia GPLv3, eso significa, en resumen, que puedes utilizarlo bajo tu propio riesgo, utilizarlo como tu quieras e incluso lucrar con aquello, pero en el código fuente, debe aparecer el copyright intacto, de otra forma, se pueden tomar acciones legales. También es necesario incluir el archivo fuente o de otra manera referir clara y libremente el origen del fuente (esta página web) en el dispositivo que se va a utilizar el código binario:

/*
 * Copyright (C) 2019 Tomas Arturo Herrera Castro 
 *
 * Based on BB-I2C2-RTC-DS1338.dts:
 *
 * Copyright (C) 2018 Tim Small 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 * 
 * This was code was writen by Chilean Software and Hardware developer 
 * company Southern Lake Technologies for a custom-made datalogger. 
 * 
 * http://www.sltech.cl
 *
 * compiled using: dtc -O dtb -o BB-I2C2-RTC-DS3231.dtbo -b 0 -@ BB-I2C2-RTC-DS3231.dts
 *
 * tested on element14 BeagleBone Industrial
 *
 */

/dts-v1/;
/plugin/;

/{

	compatible = "ti,beaglebone", "ti,beaglebone-black", "ti,beaglebone-green";

	/* identification */
	part-number = "BB-DS3231";
	version = "00A0";

	#address-cells = <1>;
	#size-cells = <0>;

	fragment@0 {
		target-path="/";
		__overlay__ {
			aliases {
				rtc0 = &extrtc;
				/* The OMAP RTC implementation in the BBB is
				 * buggy, so that it cannot be used as a
				 * battery-backed RTS, so that it loses its
				 * contents when power is removed from the
				 * Beaglebone...
				 *
				 * We move the omap built-in RTC to rtc1, so
				 * that userspace defaults to using the DS1338.
				 *
				 * The omap RTC must remain enabled because it
				 * is also used during the reboot process on the
				 * BBB.
				 */
				rtc1 = "/ocp/rtc@44e3e000";
			};
		};
	};

	fragment@1 {
		target = <&i2c2>;
		__overlay__ {
			status = "okay";
			#address-cells = <1>;
			#size-cells = <0>;

			extrtc: ds3231@68 {
				compatible = "maxim,ds3231";
				reg = <0x68>;
			};
		};
	};
};
							
Lo que debes entender de este código:
  • compatible = "maxim,ds3231"; indica el driver que debe cargar al ejecutarse el OS.
  • reg = <0x68>; indica que la dirección I2C del ADS3231 es 0x68.
  • rtc0 = &extrtc; indica que el OS va a tomar como su reloj principal el DS3231.
Este código fuente lo debes ingresar a la Beaglebone y guardar bajo el nombre de BB-I2C2-RTC-DS3231.dts y luego ejecutar los siguientes comandos para compilar y guardar el compilado en el lugar apropiado, en la Beaglebone eso se hace utilizando los comandos:


dtc -O dtb -o BB-I2C2-RTC-DS3231.dtbo -b 0 -@ BB-I2C2-RTC-DS3231.dts
sudo cp BB-I2C2-RTC-DS3231.dtbo dtb_overlay=/lib/firmware/BB-I2C2-RTC-DS3231.dtbo
						
Luego debes habilitar o bien, decirle al kernel que vas a utilizar este hardware:


nano /boot/uEnv.txt
						
Y allí decomentar o en otras palabras habilitar:


enable_uboot_overlays=1
						
y además agregar una linea:


dtb_overlay=/lib/firmware/BB-I2C2-RTC-DS3231.dtbo
						
Esto va a hacer que cuando comienze el kernel, va a utilizar el hardware DS3231 que se describe en el archivo BB-I2C2-RTC-DS3231.dtbo.

Finalmente, conectamos el DS3231 a la Beaglebone. Los pines son I2C son P9.19 y P9.20. El valor de voltaje de alimentación depende de la batería del módulo que estén usando.

Si todos los pasos fueron realizados correctamente, deberíamos tener listado en la dirección /dev/rtc0 el DS3231 y en /dev/rtc1 el tiempo que llevaba el sistema operativo por defecto.

Notar que en la práctica, solo le dijimos al computador que utilizara el hardware externo para llevar su tiempo. El sistema operativo, basaba su tiempo en el ahora dispositivo /dev/rtc1 mediante timedatectl. Ahora su tiempo lo lleva el dispositivo /dev/rtc0 administrado por el mismo programa timedatectl.

Algunos de ustedes habrán notado que el tiempo se actualizaba automáticamente si es que hay internet antes de agregar el RTC. Ahora el tiempo del RTC y el de sistema se va a actualizar automáticamente si es que tienen internet, porque utiliza el RTC como su reloj principal y el tiempo de sistema está basado en el el reloj principal.

Con esto ya hay más trabajo que realizar. El kernel de linux y su sistema operativo hacen el resto del trabajo.

Ante cualquier duda o corrección. No dude en contactar. Se le darán todos los créditos por una corrección.