beagleboneblack - ¿Cómo agregar dispositivos i2c en el Beaglebone Black usando las superposiciones del árbol de dispositivos?
device-tree (1)
Esta es una información muy útil y valiosa. Escribí un controlador de kernel i2c que puedo cargar dinámicamente para hablar con un chip personalizado en la dirección 0x77. He tenido éxito en comunicarme con el chip en el pasado al crear una instancia del dispositivo manualmente de la siguiente manera: echo act2_chip 0x77> / sys / bus / i2c / devices / i2c-1 / new_device. Una vez que se crea una instancia del dispositivo, puedo verlo utilizando las herramientas i2cdetect y mi controlador de kernel cargable se puede comunicar con el chip.
Ahora estoy tratando de crear una instancia del dispositivo utilizando el método de árbol de dispositivos. Así que siguiendo su ejemplo, cambié algunos parámetros en su archivo dtsi como a continuación:
fragmento @ 1 {target = <& i2c1>;
__overlay__ {
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
act2_chip: act2_chip@77 { /* the real time clock defined as child of the i2c1 bus */
compatible = "xx,act2_chip";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x77>;
};
Conecté el chip en los pines 17 y 18 para scl y sdk. Aquí está la salida de dmesg que obtengo después de echo> slots:
Pero al insertar el controlador en el kernel, veo que se llama a la función de sonda. esto significa que el conductor puede ver el dispositivo por lo que creo.
Y cuando intento escribir en el controlador del kernel, aparece el siguiente mensaje: omap_i2c 4802a000.i2c: el tiempo de espera del controlador se ha agotado.
¿Por qué debería leer esto?
Si tiene un Beaglebone Black (BBB) y desea conectar sus propios dispositivos (no capas), es posible que ya haya escuchado sobre el árbol de dispositivos. En mi caso, quería conectar un dispositivo RTC al bus I2C en el BBB. Hay mucha información diseminada por la web y este artículo pretende ser un resumen de lo que encontré como una guía para hacerlo.
Así que daré un ejemplo completo de activación del bus I2C en el BBB, así como de conectar un chip RTC DS1308 utilizando los controladores de dispositivo incluidos en el núcleo. ¿Suena interesante?
Luego sigue leyendo y por favor deja comentarios si algo no está claro. Si tiene un poco de prisa, también puede agarrar el código de superposición del árbol de dispositivos en Github y volar.
Lo primero es lo primero.
Estoy usando ArchLinux ARM en mi BBB principalmente porque Arch Linux es increíble y posiblemente soy demasiado estúpido para usar las distribuciones debianoid. Aquí hay una screenfetch de screenfetch del sistema ...
Como se puede observar, la versión del kernel ya está por encima de esa versión 3.x. Lo que no puede ver en la captura de pantalla es que el kernel admite superposiciones de árbol de dispositivos utilizando la utilidad Capemgr .
¿Qué es el árbol de dispositivos?
Solo lo haré rápido, puedes encontrar un conocimiento más profundo here , here , here y here . El árbol de dispositivos es una estructura que describe el hardware subyacente en su plataforma. Se usa mucho en dispositivos integrados, ya que los SOC y esas cosas no tienen buses como PCI donde se pueden descubrir dispositivos. Deben definirse de forma estática y se adjuntan a un "bus de plataforma" para dar un identificador a los controladores de dispositivo que se envían con el kernel.
Antes de que el árbol de dispositivos se introdujera en Linux, todo ese trabajo tenía que hacerse con archivos específicos de encabezado C e implementaciones personalizadas que luego debían fusionarse en el núcleo principal. Por lo tanto, al tratarse de una tarea exhaustiva e imaginable, llegó la famosa queja de Linus Torvalds . Aquí vas con aún más fondo de árbol de dispositivos .
Sí bien, pero ¿cómo funciona?
Para describir el árbol de dispositivos, estamos usando .dts
(fuente del árbol de dispositivos), que son legibles por el hombre y compilados por el compilador del árbol de dispositivos ( dtc
) en blobs del árbol de dispositivos ( .dtb
), un formato binario. Cuando el sistema arranca, el gestor de arranque (por ejemplo, u-boot ) entrega esa burbuja al kernel. El kernel lo analiza y crea todos los dispositivos como se indica en el árbol de dispositivos.
Si no me cree, use el compilador del árbol de dispositivos para alcanzar el árbol de dispositivos que su BBB está usando ahora.
Si aún no lo ha instalado, obtenga el paquete apropiado.
pacman -Sy dtc-overlay
dtc -f -I fs /proc/device-tree | less
Se recomienda ese conducto hacia el localizador less
debido a la gran cantidad de salida generada por ese comando. El resultado debe verse algo como esto ..
Todas las partes de su árbol de dispositivos también podrían investigarse dentro de la fuente del kernel, pero como también hay un mecanismo de inclusión, la información se divide entre varios archivos en
<kernel-source>/arch/arm/boot/dts/..
Algunos archivos relevantes son:
-
am335x-bone-common.dtsi
-
am335x-boneblack.dts
-
am33xx.dtsi
Nota: los archivos
.dtsi
son equivalentes a los archivos.h
en C o C ++ porque se incluyen (por lo tanto, la ''i'' al final) por los archivos.dts
Todos describen dispositivos relacionados con el procesador, dispositivos comunes en la plataforma Beaglebone o dispositivos que solo son adecuados para el Beaglebone Black.
Usted mencionó las superposiciones, ¿qué es eso?
Buena pregunta, veo que sigues conmigo. Como dije antes, el blob del árbol de dispositivos se analiza cuando arranca el kernel. Así que cuando tu sistema esté funcionando, toda la magia ya habrá terminado. En una plataforma como la BBB con un montón de placas de expansión ( Capes ), esto requerirá que vuelva a compilar el árbol de dispositivos cada vez que vaya a usar otra capa.
Por lo tanto, tiene el mecanismo de superposición que le permite agregar o modificar dispositivos en su árbol de dispositivos ¡EN RUNTIME! Increíble.
Nota: para poder compilar las superposiciones del árbol de dispositivos, asegúrese de instalar el paquete apropiado como el de arriba (
dtc-overlay
)
¿Y cómo voy a usar todo esto?
Te daré un ejemplo. Como el BBB no tiene un reloj en tiempo real (rtc) que sería útil para generar marcas de tiempo para las mediciones, etc., lo arreglaremos.
Usaremos un chip de reloj en tiempo real ds1307 (de hecho, tengo un ds1308 rtc pero el controlador es compatible) y nos comunicaremos con él a través del bus I2C1 en el BBB. De manera predeterminada, el bus está deshabilitado en el BBB como se puede ver en las fuentes del árbol de dispositivos.
La información importante en ese fragmento es:
- se define un nodo llamado ''i2c1''
- Se define como compatible con el controlador omap4-i2c.
- al dispositivo se le asigna una dirección asignada de memoria (0x4802a000) y un rango de direcciones apropiado (0x1000) de acuerdo con el manual de referencia de los procesadores (página 181)
- el estado de los dispositivos está deshabilitado
Ahora crearemos una superposición para configurar los pines GPIO para el bus i2c1, activaremos ese bus y luego agregaremos el bus rtc-device i2c1 para que se cargue automáticamente el controlador apropiado y se cree el dispositivo rtc en /dev
.
Los pines GPIO en los encabezados P8 y P9 en el BBB tienen múltiples funcionalidades que se combinan entre sí y, por lo tanto, tenemos que ajustar las configuraciones de pinmux para usarlas para la comunicación I2C. Como puede ver en esta tabla para el bus I2C1, tendremos que usar los pines de cabecera 17 y 18 en el modo mux 2. Para obtener más información sobre el manejo de GPIO en BBB, consulte here .
/dts-v1/;
/plugin/;
/{ /* this is our device tree overlay root node */
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "BBB-I2C1"; // you can choose any name here but it should be memorable
version = "00A0";
fragment@0 {
target = <&am33xx_pinmux>; // this is a link to an already defined node in the device tree, so that node is overlayed with our modification
__overlay__ {
i2c1_pins: pinmux_i2c1_pins {
pinctrl-single,pins = <
0x158 0x72 /* spi0_d1.i2c1_sda */
0x15C 0x72 /* spi0_cs0.i2c1_sdl */
>;
};
};
};
}; /* root node end */
Dios mío, ¿qué acaba de pasar?
A primera vista, la sintaxis de superposición parece bastante extraña, pero básicamente está hecha de los llamados fragmentos que se dirigen a un nodo de dispositivo ya existente y modifican ese nodo (y es secundario).
En este caso, nos am33xx_pinmux
nodo del dispositivo am33xx_pinmux
que está definido en el árbol de dispositivos del procesador ( am33xx.dtsi
). Dentro de ese nodo, agregamos un nuevo nodo secundario llamado pinmux_i2c1_pins que no existía antes (mire en am335x-bone-common.dtsi
para verificar) y la etiqueta i2c1_pins.
La siguiente parte es un poco más compleja y si estás interesado lee this . Cada pin GPIO está configurado por un solo registro con varios bits para controlar su comportamiento y todos los registros están controlados por el controlador pinctrl-single
. Para establecer un pin específico, simplemente use su desplazamiento de dirección con respecto a la dirección base (lo encontrará en la tabla de encabezado P9 más arriba) y su configuración de pin como segundo parámetro.
Tomé prestada esta descripción de Derek Molloy para explicar el modo de pin. Ya que 0x72
es equivalente a 01110010b
, ambos pines están configurados como entradas con resistencia de activación habilitada y control de giro activo en modo mux 2.
Y el modo mux 2 para estos pines significa que el pin 17 en el encabezado P9 es la línea de reloj SCL y el pin 18 en el encabezado P9 es la línea de datos SDA.
¿Pero todavía tenemos que habilitar I2C1?
Eso es absolutamente correcto, así que extendamos nuestra superposición de la siguiente manera ...
/dts-v1/;
/plugin/;
/{ /* this is our device tree overlay root node */
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "BBB-I2C1"; // you can choose any name here but it should be memorable
version = "00A0";
fragment@0 {
target = <&am33xx_pinmux>; // this is a link to an already defined node in the device tree, so that node is overlayed with our modification
__overlay__ {
i2c1_pins: pinmux_i2c1_pins {
pinctrl-single,pins = <
0x158 0x72 /* spi0_d1.i2c1_sda */
0x15C 0x72 /* spi0_cs0.i2c1_sdl */
>;
};
};
};
fragment@1 {
target = <&i2c1>;
__overlay__ {
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
rtc: rtc@68 { /* the real time clock defined as child of the i2c1 bus */
compatible = "dallas,ds1307";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x68>;
};
};
};
}; /* root node end */
En el código anterior agregamos un nuevo fragmento que apunta al nodo del dispositivo i2c1 y le decimos que use nuestra configuración de pin definida previamente. Establecemos una frecuencia de reloj I2C de 100kHz y activamos el dispositivo.
Además, el reloj rtc se agregó como un elemento secundario al nodo i2c1. La información importante para el kernel es la declaración compatible, nombrando el controlador a usar ( ds1307
) y la dirección de los dispositivos en el bus I2C ( 0x68
). La dirección I2C del rtc se puede obtener de la hoja de datos.
¿Y cómo consigo ese código en el núcleo?
Al principio, la fuente del árbol de dispositivos tiene que ser compilada. Usa el compilador dtc con la siguiente llamada ..
dtc -O dtb -o <filename>-00A0.dtbo -b 0 -@ <filename>.dts
¡Precaución! El nombre del archivo debe ser una concatenación del nombre que desea más la etiqueta de versión como se ve arriba (-00A0), de lo contrario, tendrá dificultades.
El archivo .dtbo
resultante se debe copiar en /lib/firmware
y realmente no tengo idea de de dónde proviene la convención de nomenclatura "-00A0", pero también hay otros archivos en el directorio de firmware que la usan.
A partir de ahora puedes cargar tu superposición dinámicamente usando Capemgr. Para hacerlo, vaya a /sys/devices/platform/bone_capemgr/
y luego ejecute ..
echo <filename> > slots
Capemgr buscará su archivo .dtbo
en el directorio del firmware y lo cargará si es posible. Al mirar el archivo de ranuras, puede ver si el procedimiento fue exitoso. Debería verse algo como esto ...
Examine el árbol de dispositivos utilizado por el Beaglebone.
dtc -f -I fs /proc/device-tree | less
Encontrarás todas las entradas de la superposición.
Además, debería haber un nuevo dispositivo I2C ( /dev/i2c-1
) y un nuevo dispositivo rtc ( /dev/rtc1
) en su sistema de archivos.
Para echar un vistazo a sus buses i2c, instale el paquete i2c-tools
y use ...
i2cdetect -r 1
La salida debería ser algo como esto ...
Como puede ver, la dirección 0x68 está ocupada por un dispositivo.
Para consultar su uso rtc ..
hwclock -r -f /dev/rtc1
Pero eso no es todo, ¿o no?
No, hay una opción más que tiene, cargar las superposiciones del árbol de dispositivos en el arranque. ¡INCREÍBLE!
Para hacerlo, abra /boot/uEnv.txt
y agregue bone_capemgr.enable_partno=<filename>
a la declaración optargs
. Eso es lo que se ve en mi BBB
optargs=coherent_pool=1M bone_capemgr.enable_partno=bbb-i2c1
Confusamente, el nombre de archivo se usa en optargs, no la etiqueta de part-number
definida en la superposición del árbol de dispositivos.
Puede obtener mi código de ejemplo a un lado de un Makefile útil en github si lo desea.
Lo siento por largo post.