Category Archives: Embedded

Creación de una imagen Emdebian para tu FriendlyArm

0
Filed under ARM, Debian, Embedded, GNU, mini2440
Tagged as , , ,

Introducción

Emdebian es una distribución Debian especialmente diseñada para dispositivos empotrados. Su principal característica radica en el poco espacio que ocupa la distribución.

Emdebian ofrece una Toolchain, un conjunto de herramientas de compilación cruzadas para crear paquetes para nuestra plataforma y repositorios con paquetes que ofrecen soporte a arquitecturas arm, ia64, m68k, mips, mipsel, powerpc y sparc. La instalación puede realizarse desde una máquina Debian con una arquitectura distinta a la arquitectura destino sin ningún tipo de problema utilizando debootstrap o, como se hará en ésta receta, utilizando multistrap, que permite crear un sistema nuevo utilizando varios repositorios y echando mano de apt y dpkg para solucionar posibles conflictos.

Qué necesitamos

Pues como he dicho anteriormente, utilizaremos multistrap, así que nos lo instalamos en nuestra máquina (ya sabeís, apt-get o aptitude bla, bla, bla…).

Además, supondremos que seguimos las recetas anteriores sobre cómo compilar Linux para mini2440 y sobre cómo configurar u-boot para arrancar el sistema desde una micro-sd.

Deberemos tener una tarjeta microSD con dos particiones, una para el núcleo y otra que dejaremos para el sistema (como se explicó en la receta sobre la compilación de Linux). Tendremos el árbol de directorios resultante de la compilación de Linux, pues lo necesitaremos para instalar los módulos del núcleo.

Creación de la imagen

Como se comentó anteriormente, utilizaremos multistrap para crear la imagen de emdebian. Para ejecutar multistrap antes deberemos crear un archivo de configuración en el que especificaremos los repositorios desde los que descargaremos los paquetes, la arquitectura destino y el directorio que se utilizará como destino para la instalación, entre otras cosas. El archivo que he utilizado yo es el siguiente:

  1. [General]
  2. arch=armel
  3. directory=multistrap
  4.  
  5. #cleanup=true
  6. # same as –no-auth option if set to true
  7. # keyring packages listed in each debootstrap will
  8. # still be installed.
  9. noauth=false
  10. # extract all downloaded archives (default is true)
  11. unpack=true
  12. # aptsources is a list of sections to be used for downloading packages
  13. # and lists and placed in the /etc/apt/sources.list.d/multistrap.sources.list
  14. # of the target. Order is not important
  15. aptsources=Emdebian
  16. # the order of sections is not important.
  17. # the debootstrap option determines which repository
  18. # is used to calculate the list of Priority: required packages.
  19. debootstrap=Debian Emdebian
  20.  
  21. [Emdebian]
  22. packages=ifupdown udev procps netbase vim-tiny module-init-tools wget openssh-server screen apmd
  23. source=http://www.emdebian.org/grip/
  24. keyring=emdebian-archive-keyring
  25. suite=squeeze main
  26.  
  27. [Debian]
  28. packages=
  29. source=http://ftp.uk.debian.org/debian
  30. keyring=debian-archive-keyring
  31. suite=squeeze main

La clave arch especifica la arquitectura destino de la distribución, en éste caso armel y la clave directory especifica el directorio destino en el que se desplegará la instalación. En nuestro caso, lo hará dentro del directorio multistrap, subdirectorio del directorio en el que se encuentra nuestro archivo de configuración.

Es importante que la clave cleanup esté comentada (o puesta a false). Ésto hará que la caché de apt no se limpie tras la instalación, pues necesitaremos los paquetes para reinstalarlos en el último paso desde la plataforma destino. El resto de opciones del archivo se pueden ver en la página sobre emdebian, así que no las comentaré aquí.

Guardamos el fichero con el nombre multistrap.conf (por darle algún nombre). Después, podemos instalar:

  1. javieralso@rigoberto:~/emdebian$ ls
  2. multistrap  multistrap.conf
  3. javieralso@rigoberto:~/emdebian$ sudo multistrap –no-auth -f multistrap.conf

Podemos ver que el directorio multistrap en el que quedará instalado emdebian y el archivo de configuración están bajo el mismo directorio. Tras invocar a multistrap podremos ver como se descargan los paquetes y se desempaquetan dentro del directorio multistrap.

Ahora tendremos un sistema muy básico que todavía no es funcional del todo. Tendremos que reinstalar todos los paquetes en el sistema destino, para lo cual deberemos poder logearnos. Como la distribución que tenemos es muy básica, no podremos hacerlo, puesto que /etc/passwd aún no existe, así que nos lo creamos nosotros:

  1. javieralso@rigoberto:~/emdebian$ cd multistrap
  2. javieralso@rigoberto:~/multistrap$ sudo su
  3. rigoberto:/home/javieralso/emdebian/multistrap# echo "root:Npge08pfz4wuk:0:0:root:/root:/bin/bash" > etc/passwd
  4. rigoberto:/home/javieralso/emdebian/multistrap# echo "root:Npge08pfz4wuk:0:" > etc/group
  5. rigoberto:/home/javieralso/emdebian/multistrap#

Como podemos ver, también hemos creado el archivo /etc/group. Recordad tener cuidado con no sobreescribir los archivos de VUESTRA máquina, no la vayais a liar….

Lo que hemos hecho ha sido asignar la contraseña password al usuario root utilizando el formato de contraseña salt/password, aunque para finalizar, aún debemos hacer mas cosas:

  1. rigoberto:/home/javieralso/emdebian/mutistrap# echo "proc /proc proc none 0 0" >>etc/fstab
  2. rigoberto:/home/javieralso/emdebian/multistrap# echo "mini2440" >etc/hostname
  3. rigoberto:/home/javieralso/emdebian/multistrap# mknod dev/console c 5 1
  4. rigoberto:/home/javieralso/emdebian/multistrap# mknod dev/ttySAC0 c 204 64

Después de ésto, empaquetamos el directorio.

  1. rigoberto:/home/javieralso/emdebian/multistrap# tar jcf ../emdebian-grip-071910-armel-debootstrap-squeeze.tar.bz2 .
  2. rigoberto:/home/javieralso/emdebian/multistrap#

Ahora, suponiendo que hemos montado la partición 2 de la tarjeta microSD (donde se supone que vamos a instalar el sistema) en el directorio /media/rootfs, podemos cargar ahí el sistema recién creado:

  1. rigoberto:/home/javieralso/emdebian/multistrap# cd ..
  2. rigoberto:/home/javieralso/emdebian# unp emdebian-grip-071910-armel-debootstrap-squeeze.tar.bz2 /media/rootfs/
  3. rigoberto:/home/javieralso/emdebian#

Instalamos los módulos del núcleo que compilamos en su momento, para eso, nos vamos al directorio mini2440 que creamos en su día y ejecutamos lo siguiente:

  1. javieralso@rigoberto:~/kernel/mini2440# sudo make INSTALL_MOD_PATH=/media/rootfs/  O=../kernel-bin/ modules_install
  2. …………………………………………………………………………………..
  3. …………………………………………………………………………………..
  4. Lots and lots of INSTALL messages
  5. …………………………………………………………………………………..
  6. …………………………………………………………………………………..

Desmontamos la tarjeta, la insertamos en nuestra MINI2440 y arrancamos (conectando el puerto serie de la tarjeta a nuestro PC y arrancando minicom).

Cuando el sistema termine de arrancar, veremos que se para porque no encuentra el archivo /etc/inittab:

  1. …………………………………………………………………………………..
  2. …………………………………………………………………………………..
  3. …………………………………………………………………………………..
  4. …………………………………………………………………………………..
  5. …………………………………………………………………………………..
  6. mmcblk0: mmc0:b368 MSD   1.85 GiB
  7.  mmcblk0: p1 p2
  8. s3c2410-rtc s3c2410-rtc: setting system clock to 2010-07-20 23:14:47 UTC (1279667687)
  9. usb 1-1: new full speed USB device using s3c2410-ohci and address 2
  10. usb 1-1: configuration #1 chosen from 1 choice
  11. kjournald starting.  Commit interval 5 seconds
  12. EXT3-fs warning: maximal mount count reached, running e2fsck is recommended
  13. EXT3 FS on mmcblk0p2, internal journal
  14. EXT3-fs: recovery complete.
  15. EXT3-fs: mounted filesystem with writeback data mode.
  16. VFS: Mounted root (ext3 filesystem) on device 179:2.
  17. Freeing init memory: 136K
  18. INIT: version 2.88 booting
  19. INIT: No inittab file found
  20. Enter runlevel:

Introducimos ‘s’ (single user) y cuando nos pida contraseña introducimos: “password“. De ésta forma tendremos una Shell con permisos de root muy básica, pero con la que podremos terminar de instalar el sistema.

A continuación remontamos el sistema de archivos como sistema de lectura/escritura:

  1. root@(none):~# mount -n / -o remount,rw

y después montamos el sistema de archivos /proc:

  1. root@(none):~# mount -n /proc

Ahora debemos reinstalar todos los paquetes. Aunque los paquetes ya “están” en el disco, debemos “reinstalarlos” para que se genere la base de datos del sistema:

  1. root@(none):~# cd /var/cache/apt/archives
  2. root@(none):/var/cache/apt/archives# dpkg –force-all -i libc6* libstdc++* libncurses5* dpkg_*
  3. ………………………………………………………………………………
  4. ………………………………………………………………………………
  5. lots and lots of installation messages
  6. ………………………………………………………………………………
  7. ………………………………………………………………………………
  8. root@(none):/var/cache/apt/archives# dpkg –force-all -i apt*.deb
  9. ………………………………………………………………………………
  10. ………………………………………………………………………………
  11. lots and lots of installation messages
  12. ………………………………………………………………………………
  13. ………………………………………………………………………………

Una vez que tenemos instalado apt, reinstalamos todos los paquetes configurando la lista selections de dpkg:

  1. root@(none):/var/cache/apt/archives# ls *.deb | sed ‘s/\([^_]*\)_.*/\1 install/’ | dpkg –set-selections
  2. root@(none):/var/cache/apt/archives# apt-get dselect-upgrade
  3. ………………………………………………………………………………
  4. ………………………………………………………………………………
  5. lots and lots of installation messages
  6. ………………………………………………………………………………
  7. ………………………………………………………………………………

Aquí es muy probable que se produzcan errores durante la reinstalación. cuando ésto suceda se vuelve a ejecutar apt-get dselect-upgrade hasta que se termine. Para algunos paquetes es posible incluso que tengas que tocar algo a mano, como por ejemplo para que el paquete base-files pueda instalarse correctamente, que en mi caso me fue necesario eliminar el directorio /var/mail a mano.

En éste punto ya casi hemos terminado. Ahora solo falta activar la consola a través de puerto serie:

  1. root@(none):/var/cache/apt/archives# cd /
  2. root@(none):/# echo ttySAC0 >>etc/securetty
  3. root@(none):/# printf "T0:123:respawn:/sbin/getty 115200 ttySAC0\n" >>etc/inittab

¡Y ya está! Reinicia el sistema y ya tendrás una Debian flamante corriendo en tu MINI2440. ¡¡A disfrutarla!!

  1. …………………………………………………………………………………..
  2. …………………………………………………………………………………..
  3. …………………………………………………………………………………..
  4. …………………………………………………………………………………..
  5. …………………………………………………………………………………..
  6. Mounting local filesystems…done.
  7. Activating swapfile swap…done.
  8. Cleaning up temporary files….
  9. Configuring network interfaces…done.
  10. Cleaning up temporary files….
  11. Setting kernel variables …done.
  12. Running scripts in rcS.d/ took 20 seconds.
  13. INIT: Entering runlevel: 2
  14. Using makefile-style concurrent boot in runlevel 2.
  15. apmd[1110]: apmd 3.2.1 interfacing with apm driver 1.13 and APM BIOS 1.2
  16. Starting Advanced Power Management daemon….
  17. Running scripts in rc2.d/ took 2 seconds.
  18. Debian GNU/Linux squeeze/sid MINI2440 ttySAC0
  19. MINI2440 login: root
  20. Password:
  21. Last login: Wed Jul 21 01:14:00 CEST 2010 on ttySAC0
  22. Linux MINI2440 2.6.32-rc8 #1 Mon Dec 7 16:07:52 CET 2009 armv4tl
  23. The programs included with the Debian GNU/Linux system are free software;
  24. the exact distribution terms for each program are described in the
  25. individual files in /usr/share/doc/*/copyright.
  26. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
  27. permitted by applicable law.
  28. root@MINI2440:~#

Y después…

Lo próximo es la creación de una imagen opie utilizando openembedded.

Referencias

NOTA: Entrada extraída de otra antigua entrada publicada por mi en CrySoL.

Conceptos sobre planificación en Sistemas Críticos

0
Filed under Embedded, Tiempo Real
Tagged as ,

Introducción

Actualmente me estoy preparando el que espero sea el último examen de la carrera (que bien suena, pensé que nunca llegaría a decirlo :-P ), que oficialmente es mañana. El caso es que a éstas alturas del verano y tras haber pasado, pongamos (por poner), 24 meses en los que se puede decir que me ha pasado de todo, ahora estoy que no me entra nada en la cabeza ni soy capaz de estar mas de cinco minutos concentrado con nada, así que me he dicho “pues escribe a ver si así te concentras mas”, y aquí estoy.

Sobre lo que voy a escribir ahora es acerca de algunos conceptos sobre planificación en Sistemas Críticos, que es el tema que me ocupa ahora (y que, para qué engañarnos, el que mas me preocupa). Hay montones de ecuaciones que en principio son sencillas, pero numerosas, y además varios tipos de protocolos y de tipos de asignaciones de prioridad, así que lo mejor que puedo hacer es resumirlas y ya de paso tenerlas a mano, pues me parecen tan útiles como interesantes. Empecemos….

Asignación de prioridades

Bueno, para empezar diré que asumo que todos sabemos lo que es un Ejecutivo Cíclico y en qué consiste un Hiperciclo. También asumo que se tienen claros conceptos como Concurrencia, Período de Activación, Tiempo de Ejecución Máximo, Plazo de Respuesta y un largo etcétera.

Dicho todo lo anterior, prosigamos. Formas de asignar prioridades a las distintas tareas de un ejecutivo cíclico puede haber muchas: según el preriodo de activación de las tareas, el tiempo de cómputo, el número de recursos que se utilizan o la cantidad de procesos con los que se comparten ciertos recursos, por poner unos ejemplos.

Do de éstas formas de asignación son la Asignación de mayor prioridad a las tareas de menor período (Rate monotonic scheduling) y la Asignación de mayor prioridad a las tareas de menor plazo de respuesta (Deadline monotonic sheduling). Las características de éstos sistemas de asignación son las siguientes:

  • Rate monotonic scheduling
    • Asigna mayor prioridad a las tareas cuyo período de activación (T) es menor.
    • Asignación óptima para el modelo de tareas simple: Tareas periódicas, independientes y con plazos iguales a los períodos.
    • Si se pueden garantizar los plazos de un sistema de tareas con otra asignación de prioridades, se pueden garantizar con la asignación monónota en frecuencia.
  • Deadline monotonic scheduling
    • Asigna mayor prioridad a las tareas con menor plazo de ejecución.
    • Es válido para modelos en los que existen tareas esporádicas.
    • Si los plazos son menores o iguales que los períodos, la asignación de mayor prioridad a las tareas de menor plazo de respuesta es óptima.

Factor de utlización

En un sistema crítico una medida muy importante es el Factor de Utilización del procesador. Éste factor muestra la relación existente entre el tiempo de cómputo de una tarea dada y su periodo de activación, dando una idea del uso total del procesador. El factor de utilización (U) siempre tiene que ser menor o igual a 1.

La ecuación para el cálculo de U en un sistema es la siguiente: \sum_{i=1}^N \frac{C_i}{T_i} siendo N el número de tareas en ejecución, C es el tiempo de cómputo de una tarea dada y T es el periodo de activación.

En el modelo simple de ejecución con prioridades monótonas en frecuencia, los plazos se garantizan si U \leq U_0 siendo

U_0 = N * (2^{N^{-1}} - 1)

la Utilización mínima garantizada del sistema para N tareas. Un sistema cumplirá con los plazos siempre que, como se ha comentado anteriormente, U \leq U_0. Aunque ésta no es condición necesaria, si que es suficiente.

Mas cosas

Iré añadiendo mas cosillas poco a poco, ya después del examen… :-P

Referencias

Básicamente mis apuntes de clase

GCC y el padding

0
Filed under Embedded, GCC, General, Programación
Tagged as , , ,

Introducción

Hace poco, en el trabajo, me encontraba realizando la implementación del protocolo descrito en la RFC 908 para usarla en un pequeño microcontrolador de Atmel.

Una vez implementado y compilado todo, me dispuse a realizar las correspondientes pruebas y vi que en ciertas funciones sucedían cosas muy extrañas. En concreto, tenía definidas varias estructuras en las que se almacenaba toda la información de estado y control del protocolo y descubrí que al intentar recorrer todos sus miembros con un puntero a char las cosas no cuadraban. Leía valores que se suponía que no estaban ahí o al calcular el tamaño de los mensajes basándome en el tamaño de las estructuras (calculadas usando sizeof) estos no eran los que tenían que ser….

¿Qué estaba sucediendo?

El problema: Padding

Pues si. Enseguida imaginé cual podría ser el problema: el Padding. La verdad es que es un error un poco de novatos, pero yo caí. Generalmente los programadores en lenguajes como C raramente suelen tener en cuenta el tema del padding a diseñar sus programas, puesto que suponen que el compilador se encargará de optimizar nuestro código y además, rara vez se trabaja a un nivel tan bajo como para que ésto sea un problema.

La cosa cambia si estás diseñando código para correr en un dispositivo empotrado con poca memoria o tienes que hacer manipulaciones a muy bajo nivel sobre las estructuras. En mi caso, se daban las dos situaciones, así que me puse a investigar un poco y ésto fue lo que aprendí.

¿Qué es exactamente el padding? Bueno, empecemos por el principio:

El Alineamiento

¿Y ésto qué es?. Bueno, dependiendo del tipo de máquina que estemos utilizando y del tipo de dato con el que estemos trabajando, el rango de direcciones en las que se puede almacenar dicho dato debe cumplir una u otra característica. Me explico: supongamos que estamos trabajando en una máquina de 32 bits. En éste caso, tendremos una máquina con una alineación de memoria de 4 bytes. Es decir, cualquier lectura de memoria se hace de cuatro en cuatro bytes y todas las posibles direcciones de memoria que se leen serán múltiplo de 4. Por tanto, una lectura, supongamos en la dirección 0x00abcd0034 sería correcta, mientras que si intentásemos leer de la dirección 0x00abcd0031 no podríamos, pues esa dirección no está alineada a 4 bytes (no es múltiplo de 4). Si queremos leer ese byte en concreto, deberemos leer la dirección 0x00abcd0030 y después acceder a los bits <8:15>
Hay que hacer una pequeña aclaración y comentar que ésto es para el caso de que nuestra máquina sea de 32 bits pero use una memoria de 8 bits. También podemos tener máquinas con un bus de memoria de igual longitud que los datos que utiliza, en cuyo caso, el alineamiento de las posiciones de memoria sería 1, es decir, se puede leer y escribir en cualquier dirección, pero se leerán 32 bits (o los que tenga el ancho del bus) de cada vez. Para lo que voy a explicar, me parece mas conveniente el modelo de máquina de 32 bits con un bus de 8, aunque esto es aplicable a cualquier tipo de arquitectura.

Si en la máquina de la que estamos hablando queremos guardar un char, dicha variable la podremos guardar, como podemos imaginar, en cualquier dirección (un char, como todos sabemos, ocupa 8 bits). Decimos que el alineamiento de éste tipo de variables es de un byte. Ésto sucede porque, en nuestra máquina de 32 bits, para leer un char, leeremos 32 bits (4 bytes) de una vez, y entre esos 4 bytes, estará el que buscamos.
La cosa cambia si ahora en lugar de querer almacenar un simple char queremos guardar un short (típicamente 16 bits). En éste caso solo podremos almacenarlo en direcciones múltiplo de 2. ¿Y ésto a cuento de qué?. Pues como algunos habréis imaginado (y si no lo habéis imaginado yo os lo cuento), si en cada lectura que realiza nuestro procesador leemos 4 bytes y un short son 2 bytes, tendremos que colocar dicha variable bien en los dos primeros bytes o bien en los dos últimos. Por supuesto también podríamos colocarlo en los bytes 2 y 3, pero si permitimos ese tipo de cosas nada impediría que el primer byte de un short cayese en el último byte de una palabra leída y el segundo en el primer byte de la palabra siguiente. Mas claramente: nuestra máquina lee las direcciones 0×0000, 0×0004, 0×0008, etc…. Nuestro short correría el riesgo de caer entre las direcciones 0×0003 y 0×0004. ¿Cual es la pega de ésto? Obviamente que el procesador debería realizar dos lecturas a memoria: una a la dirección 0×0000 y otra a la dirección 0×0004 para recuperar los dos “trozos” de short. Ahora se ve claro por qué hace falta definir ésto del alineamiento de los tipos de datos: Ahorra trabajo y tiempo al procesador.
Decimos entonces que un short está alineado a dos bytes.

Como podremos imaginar, para el caso de un int o incluso de un double, la alineación tiene que ser forzosamente a 4 bytes, es decir, deben de estar almacenados en direcciones múltiplos de 4.

Ahora si, el Padding

Muy bien, ahora que ya hemos entendido en qué consiste la alineación, podemos hablar del Padding, que no es ni mas ni menos que bytes de “relleno” que se utilizan para forzar el alineamiento de los miembros de una estructura, en el caso del lenguaje C. Veamoslo con un ejemplo:
En el siguiente código podemos ver que tenemos definida una estructura formada por un char y un int:

  1. #include <stdio.h>
  2.  
  3. int main (int argc, char** argv){
  4.  
  5.   struct {
  6.     char a;
  7.     int b;
  8.   } padding;
  9.  
  10.   char* p = (char *)&padding;
  11.  
  12.   padding.a = ‘z’;
  13.   padding.b = 0x6fabada6;
  14.  
  15.   printf("longitud de padding: %i\n", sizeof(padding));
  16.   printf("Datos originales:\n\ta —> %c\n\tb —> 0x%x\n", padding.a, padding.b);
  17.  
  18.   *p = ‘a’; p++;
  19.   *p = 0xbe; p++;
  20.   *p = 0xba; p++;
  21.   *p = 0×12; p++;
  22.   *p = 0×34;
  23.  
  24.   printf("Datos modificados:\n\ta —> %c\n\tb —> 0x%x\n", padding.a, padding.b);
  25. }

Antes de compilar ni ejecutar nada podemos suponer que el tamaño de nuestra estructura (en una máquina de 32 bits) debería ser de 5 bytes: 4 bytes del int y 1 del char. El código simplemente imprime la longitud de la estructura, los datos originalmente almacenados en ella y después, a través de un puntero char recorre dicha estructura y modifica los datos. Finalmente, vuelve a imprimir los datos guardados en la estructura.
Ahora ya podemos compilarlo y ejecutarlo obteniendo la siguiente salida:

  1. longitud de padding: 8
  2. Datos originales:
  3.         a —> z
  4.         b —> 0x6fabada6
  5. Datos modificados:
  6.         a —> a
  7.         b —> 0x6fabad34

:-| ¿qué ha pasado? ¿y esa longitud 8 en lugar de 5? Sencillo: nuestra máquina es de 32 bits. Para poder forzar el alineamiento correcto de las variables que componen la estructura tiene “rellenar” el hueco ocupado entre el char y el int para que éste último comience en una dirección múltiplo de 4 (o en el caso de un PC de 32 bits, se lea en la siguiente dirección de memoria, porque en éste caso, el bus de la memoria coincide con el del micro). Es decir, es como si hubiésemos declaro padding de ésta otra forma:

  1. struct {
  2.   char a;
  3.   char dummy1;
  4.   char dummy2;
  5.   char dummy3;
  6.   int b;
  7. } padding;

de modo que dummy? son solo para rellenar (padding) y hacer que b empiece donde debe, respetando así el alineamiento. Si no fuese así y quisiéramos acceder a a, seguiríamos usando una lectura, mientras que para acceder a b la cosa cambiaría, ya que necesitaríamos una lectura para acceder a los tres primeros bytes y una segunda lectura mas para acceder al último. Después habría que realizar desplazamientos y ajustes para obtener el valor final.
De ahí que veamos otro efecto colateral a la salida de la ejecución del código: El último valor leído de b no coincide con el esperado. De hecho solo hemos modificado el primer byte. Los otros tres bytes que hemos recorrido han sido los del padding.

Ésto abre la veda para hacer pequeños “experimentos”. Por ejemplo, si cambiamos a y b de posiciones relativas veremos que el tamaño sigue siendo 8. ¿De verdad sigue haciendo falta relleno? Ahora se supone que leemos primero el int y que por tanto b lo podremos leer sin problemas…. Pues la respuesta es si y no. En realidad ahora no hay padding, pero si que es cierto que la estructura sigue ocupando 8 bytes en memoria, pues tendremos que hacer dos lecturas para acceder a todos sus miembros.

Pero podemos hacer mas cosillas: Por ejemplo, podemos declarar otro miembro de tipo short entre a y b y ver qué pasa con el tamaño. ¿Ha cambiado? ¡¡NO!! ¿y eso?. Pues porque al encontrarse el short entre un char y un int, puede utilizar tres de los bytes de padding para almacenarse, por lo que ahora habrá un padding de un solo byte. Si en lugar de eso declaramos el short después de b, veremos que la cosa cambia bastante. Ahora estaremos usando 12 bytes. Queda bastante claro todo ésto ¿no?.

Evitando el padding con GCC

Una pregunta bastante obvia que puede asaltar a cualquier lector es: ¿y si no quiero que haya padding porque me hace desperdiciar memoria o me viene fatal para recorrer mi estructura?. Bueno, GCC permite forzar el padding que nosotros queramos usando pragmas. En concreto se usa el pragma pack. Veámoslo con un ejemplo sobre nuestro código anterior:

  1. #include <stdio.h>
  2.  
  3. int main (int argc, char** argv){
  4.  
  5. #pragma pack(push,1)
  6.   struct {
  7.     char a;
  8.     int b;
  9.   } padding;
  10. #pragma pack(pop)
  11.  
  12.   char* p = (char *)&padding;
  13.  
  14.   padding.a = ‘z’;
  15.   padding.b = 0x6fabada6;
  16.  
  17.   printf("longitud de padding: %i\n", sizeof(padding));
  18.   printf("Datos originales:\n\ta —> %c\n\tb —> 0x%x\n", padding.a, padding.b);
  19.  
  20.   *p = ‘a’; p++;
  21.   *p = 0xbe; p++;
  22.   *p = 0xba; p++;
  23.   *p = 0×12; p++;
  24.   *p = 0×34;
  25.  
  26.   printf("Datos modificados:\n\ta —> %c\n\tb —> 0x%x\n", padding.a, padding.b);
  27. }

El primer pragma establece que se fuerce un padding de 1 byte y lo “apila” en una pila que usa el compilador. De ésta forma podemos ir forzando varios paddings y cuando se use pragma pack(pop) sacaremos el último padding que hayamos usado y volveremos al anterior. En nuestro caso, volveremos al padding por defecto de nuestra máquia, puesto que solo hemos hecho un push.

Si ahora ejecutamos el código veremos que si que sucede lo esperado: padding ocupa 5 bytes y recorriendo la estructura con un puntero a char conseguimos modificar todos los datos como esperábamos.

Yo personalmente opto por no “forzar” las cosas. Un consejo para definir estructuras es ir declarando sus componentes por orden de menor a mayor tamaño. De ésta forma, quedarán organizados de una forma bastante óptima. También habrá que cuidar los recorridos por los miembros de la estructura, utilizando funciones específicas para ello.

Claro que también puede haber situaciones en las que no quede mas remedio. Por ejemplo, otra cosa en la que actualmente ando liado es la implementanción de un pequeño driver para un sistema de archivos FAT32 que deberá correr en microcontroladores AVR con muy pocas capacidades. Para poder interpretar las tablas de asignación de archivos he de leer bloques del medio en el que tengo almacenados los datos y después interpretar esa información acorde a la estructura que define a dicha tabla. El problema me viene, al igual que comenté al comienzo de ésta entrada, en que si bien en el caso del microcontrolador (con un bus de 8 bits) los datos leídos casan perfectamente con las estructuras necesarias, para el caso de los tests en el PC esto no es así, ya que las estructuras utilizan muchos tipos de 8 y 16 bits y el padding generado para forzar el alineamiento hace que los datos leídos no concuerden con los campos a los que tienen que ir a parar. En éste caso, tengo dos opciones: o una vez leído el bloque (y guardado temporalmente en un array de char) voy rellenando todas las estructuras campo a campo (algo muy muy lento para un microcontrolador y que necesita gran cantidad de código) o bien fuerzo el alineamiento a un byte para evitarme el padding y genero un puntero a la estructura que me interesa guardando en él la dirección de comienzo de mi array a char. De ésta manera podré recorrer mas fácilmente la estructura, sabiendo que para el micro no supondrá coste alguno y que para el PC generaré algo de código extra (al tener que forzar el alineamiento), pero realmente como mi intención es usar el PC sólo para validar los algorítmos, no importa que sea algo ineficiente si con ello consigo el efecto deseado.

Bajo mi punto de vista (y por lo que acabamos de ver también), creo que forzar paddings puede suponer que en algunas arquitecturas el procesador tenga que realizar mas trabajo. En un PC puede que sea (o no) despreciable, pero desde luego en un sistema empotrado ello supone pérdida de tiempo, consumo de energía y consumo de memoria de código (necesitaremos mas código para codificar las operaciones, como es lógico suponer). Ya es decisión de cada uno optar por una u otra forma de implementar.

Referencias

Pues como siempre, navegando por Internet uno encuentra muchas cosas. En http://bytes.com/topic/c/answers/543879-what-structure-padding encontré una explicación bastante clara de todo ésto del alineamiento y del padding.

ARM mini2440: Configurando uBoot para arranque desde SD para FriendlyArm

0
Filed under ARM, Embedded, mini2440
Tagged as , ,

Introducción

Después de haber compilado e instalado uBoot en nuestra tarjeta de desarrollo mini2440 y de haberle compilado una imágen Linux ahora nos proponemos configurar uBoot para el arranque de dicho kernel.
uBoot permite mucha flexibilidad a la hora de instalar y arrancar imágenes desde la memoria. Aún así, aquí sólo explicaré el procedimiento que he utilizado yo para poder cargar y ejecutar Linux desde una tarjeta SD, ya que además de parecerme los mas fácil y cómodo a la hora de actualizar (tan solo hay que copiar la nueva imagen a la SD), mantendremos intacta la NAND interna de nuestra tarjeta, que sabemos que tiene vida limitada, y aunque muy larga, no deja de ser eso, limitada.

Qué necesitamos

Pues como dije antes, se supone que debemos de haber leído, comprendido y ejecutado las dos recetas que se mencionan mas arriba.
De la segunda receta obtenemos la imagen del núcleo que vamos a utilizar en nuestra tarjeta, uImage.
A continuación, hay que preparar la tarjeta de memoria. Yo en mi caso tengo una partición ext3 de unos 128MB que utilizo para tal fin. Lo del tamaño no importa mucho (ejem), pero nunca por mucho trigo fue mal año (ejem, ejem), así que si nos sobra espacio, podremos utilizarlo para guardar ahí backups y cosas de esas (es lo que yo hago).
También es aconsejable que dicha partición se la primera particion de la unidad (primaria, claro está), ésto no es muy crítico, pero mi configuración tiene ésto en cuenta, con lo cual si situas la partición en otro sitio, deberás especificarlo en la configuración.
Evidentemente no me voy a poner a explicar cómo hacer dicha partición, ya somos todos muy grandecitos para hacerlo solitos, así que cuando termines, te espero mas abajo…

Configurando uBoot

Una vez que tienes la partición hecha, copias la imagen uImage que compilaste en su día a dicha partición e insertas la tarjeta en el lector de tu mini2440. Conectas el puerto serie, abres minicom con la configuración que usaste para instalar uBoot y arrancas. Cuando salga el mensaje de que pulses una tecla para cancelar el arranque procedes:

  1. U-Boot 1.3.2-mini2440 (Oct 15 200919:42:55)                                                      
  2. I2C:   ready
  3. DRAM:  64 MB
  4. Flash:  2 MB
  5. NAND:  128 MiB
  6. Found Environment offset in OOB..
  7. USB:   S3C2410 USB Deviced
  8. In:    serial
  9. Out:   serial
  10. Err:   serial
  11. MAC: 08:08:11:18:12:27
  12. Hit any key to stop autoboot:  0
  13. MINI2440 #

Lo primero que vamos a hacer, para asegurarnos de que todo va sobre ruedas, es hacer el arranque “a mano”, de modo que sepamos exactamente qué parámetros tenemos que dar a uBoot para que cargue correctamente nuestro núcleo. Una vez que ya sepamos estos parámetros, configuraremos uBoot correctamente para que se haga todo de forma automática cada vez que encendamos nuestra tarjeta. Algunos de los comandos que se mostrarán ahora ya nos sonarán de la receta en la que instalamos uBoot, aún así los volveré a explicar ahora.
Lo primero es inicializar la tarjeta SD:

  1. MINI2440 # mmcinit
  2. trying to detect SD Card…
  3. Manufacturer:       0x1b, OEM "SM"
  4. Product name:       "UD   ", revision 1.0
  5. Serial number:      2697000253
  6. Manufacturing date: 12/2006
  7. CRC:                0×78, b0 = 1
  8. READ_BL_LEN=15, C_SIZE_MULT=7, C_SIZE=3453
  9. size = 2329935872
  10. MINI2440 #

Una vez que la tarjeta ha sido detectada e inicializada correctamente, se carga la imagen:

  1. MINI2440 # ext2load mmc 0:1 0×32000000 uImage
  2. 2148500 bytes read
  3. MINI2440 #

Lo que hemos hecho ha sido decirle a uBoot que cargue el archivo uImage que se encuentra en la partición 1 de la tarjeta 0 (0:1) cuyo tipo es extX (ext2load) a partir de la dirección de memoria 0×32000000. Recordemos que en ésta imagen ya definimos el entrypoint, entre otras cosas, por lo que no necesitaremos definirlo mas.
Ahora ya solo queda arrancar:

  1. MINI2440 # bootm
  2. ## Booting kernel from Legacy Image at 32000000
  3.    Image Name:   Angstrom/2.6.31+git/mini2440
  4.    Created:      2009-12-10   4:50:52 UTC
  5.    Image Type:   ARM Linux Kernel Image (uncompressed)
  6.    Data Size:    2148436 Bytes =  2 MB
  7.    Load Address: 30008000
  8.    Entry Point:  30008000
  9.    Verifying Checksum … OK
  10.    Loading Kernel Image … OK
  11. OK
  12. Starting kernel …
  13. <Lots and lots of logs….>
  14. Failed to execute /sbin/init.  Attempting defaults…
  15. Kernel panic – not syncing: No init found.  Try passing init= option to kernel.
  16. [<c002b4c8>] (unwind_backtrace+0×0/0xdc) from [<c0301dd0>] (panic+0×34/0×118)
  17. [<c0301dd0>] (panic+0×34/0×118) from [<c0026544>] (init_post+0×134/0x16c)
  18. [<c0026544>] (init_post+0×134/0x16c) from [<c00085c4>] (kernel_init+0xd8/0x10c)
  19. [<c00085c4>] (kernel_init+0xd8/0x10c) from [<c00277d8>] (kernel_thread_exit+0×0/0×8)

Como podemos observar, el núcleo ha arrancado correctamente aunque veamos un nunca bienvenido kernel panic. Éste es debido a que no tenemos sistema alguno instalado, con lo cual, cuando el kernel termina de cargar e inicializar el sistema no es capaz de lanzar el proceso Init, con lo que se detiene.
De momento no lo necesitamos, éso será el contenido de una próxima receta. Con que arranque el núcleo nos daremos por satisfechos :-P .
Puesto que ya hemos visto que nuestra configuración funciona, la guardamos:

  1. MINI2440 # setenv bootcmd ‘mmcinit; fat2load mmc 0:1 0×32000000 uImage; bootm’
  2. MINI2440 # saveenv
  3. Saving Environment to NAND…
  4. Erasing Nand…Writing to Nand… done

El comando setenv sirve para asignar valores a las variables de entorno de uBoot. En éste caso configuramos la variable bootcmd que es la que contiene los comandos que se ejecutarán en el arranque. Dichas instrucciones son las que probamos anteriormente para la carga del núcleo.
saveenv como su nombre indica, sirve para hacer persistente el contenido de las variables de entorno, de forma que esten ahí cada vez que arranquemos la tarjeta.
Con ésto ya debería ser suficiente para que tu núcleo arranque correctamente, pero ya que estamos metidos con uBoot vamos a hacer algo mas: Vamos a configurarlo para decirle en qué partición de la tarjeta tenemos instalado el sistema, así como con qué parámetros debe de arrancar el núcleo, ya que con los que arranca por defecto probablemente no tengamos acceso a un terminal desde el puerto serie (cosa que a veces puede ser muy útil).
Para esto deberemos tocar otra variable de entorno, en éste caso bootargs que contiene los parámetros que son pasados al kernel:

  1. MINI2440 # setenv bootargs ‘root=/dev/mmcblk0p2 rootwait=4 rw init=/sbin/init noinitrd console=ttySAC0,115200′
  2. MINI2440 # saveenv
  3. Saving Environment to NAND…
  4. Erasing Nand…Writing to Nand… done

Lo que hemos hecho ha sido decirle que tenemos el sistema instalado en la partición 2 de la tarjeta SD (boot=/dev/mmcblk0p2), la ruta de init, y desde donde se puede acceder a la consola y a qué velocidad.
La partición que se ha indicado como partición del sistema deberá ser forzosamente ext3, así que recomiendo que el espacio que te sobró en tu SD lo dediques a una partición para el sistema (cuando haga la preceptiva receta :-P ).
Ahora podemos ver el contenido de nuestras variables de entorno:

  1. MINI2440 # printenv
  2. bootdelay=3
  3. baudrate=115200
  4. ethaddr=08:08:11:18:12:27
  5. ipaddr=10.0.0.111
  6. serverip=10.0.0.4
  7. netmask=255.255.255.0
  8. usbtty=cdc_acm
  9. mini2440=mini2440=0tb
  10. bootargs_base=console=ttySAC0,115200 noinitrd
  11. bootargs_init=init=/sbin/init
  12. root_nand=root=/dev/mtdblock3 rootfstype=jffs2
  13. root_mmc=root=/dev/mmcblk0p2 rootdelay=2
  14. root_nfs=/mnt/nfs
  15. set_root_nfs=setenv root_nfs root=/dev/nfs rw nfsroot=${serverip}:${root_nfs}
  16. ifconfig_static=run setenv ifconfig ip=${ipaddr}:${serverip}::${netmask}:mini2440:eth0
  17. ifconfig_dhcp=run setenv ifconfig ip=dhcp
  18. ifconfig=ip=dhcp
  19. set_bootargs_mmc=setenv bootargs ${bootargs_base} ${bootargs_init} ${mini2440} ${root_mmc}
  20. set_bootargs_nand=setenv bootargs ${bootargs_base} ${bootargs_init} ${mini2440} ${root_nand}
  21. set_bootargs_nfs=run set_root_nfs; setenv bootargs ${bootargs_base} ${bootargs_init} ${mini2440} ${root_nfs} ${ifconfig}
  22. mtdids=nand0=mini2440-nand
  23. mtdparts=mtdparts=mini2440-nand:256k(u-boot),128k(u-boot_env),5m(kernel),125568k(rootfs)
  24. bootargs=root=/dev/mmcblk0p2 rootwait=4 rw init=/sbin/init noinitrd console=ttySAC0,115200
  25. "ootcmd="mmcinit
  26. filesize=20C8EC
  27. partition=nand0,0
  28. mtddevnum=0
  29. mtddevname=u-boot
  30. bootcmd=mmcinit; fat2load mmc 0:1 0×32000000 uImage; bootm
  31. Environment size: 1209/131068 bytes
  32. MINI2440 #

Ahí podremos ver el contenido de todas las variables de entorno de uBoot. Aparte de las explicadas hay algunas interesantes como por ejemplo ifconfig que sirve para configurar si queremos que ejecute un cliente dhcp al arranque o si queremos darle una ip estática, bootdelay , que sirve para configurar el retardo en el arranque o baudrate, que como podemos imaginar, sirve para configurar la velocidad del puerto serie.
Si reiniciamos ahora nuestra mini2440, podremos ver que arranca el núcleo perfectamente, aunque sin sistema poco podemos hacer… Paciencia…. :-D

Referencias

NOTA: Entrada extraída de otra antigua entrada publicada por mi en CrySoL.

ARM mini2440: Compilando Linux para FriendlyArm

0
Filed under ARM, Embedded, mini2440
Tagged as , ,

Introducción

En una entrada anterior escribí sobre cómo compilar e instalar u-boot en una tarjeta mini2440. Ahora voy a explicar cómo compilar un núcleo Linux para ésta misma plataforma. En una próxima receta tengo intención de explicar los pasos para configurar u-boot y hacer arrancable éste núcleo en nuestra amada tarjeta. De momento, vamos lo que interesa: El núcleo.

Descargando los fuentes

Los fuentes del núcleo ya parcheados para su uso en nuestra arquitectura los descargamos de su repositorio git:

  1. javieralso@rigoberto:~$ mkdir kernel ; cd kernel
  2. javieralso@rigoberto:~$ git clone git://repo.or.cz/linux-2.6/mini2440.git
  3. javieralso@rigoberto:~$ mkdir kernel-bin
  4. javieralso@rigoberto:~$ cd mini2440

Con ésto ya tendremos creada la jerarquía de directorios necesaria y los fuentes descargados.
Para la compilación, usaré la misma toolchain que utilicé en la receta sobre u-boot, así que después de leerla y configurar la toolchain contiuaremos.

Lo primero que necesitaremos es crear el archivo de configuración (.config) con las opciones del núcleo. Suponiendo que estamos en el directorio mini2440 creado anteriormente, ejecutamos

  1. javieralso@rigoberto:~$ CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm make O=../kernel-bin/ mini2440_defconfig

Ésto generará la configuración a partir de una template existente en los propios fuentes y prácticamente no tendremos que tocar nada mas.

Configuración del display

En el código descargado existe un bug que hace que la imagen mostrada en el display aparezca desplazada 13 pixeles hacia la derecha. Preguntándo a Google encontré cómo arreglarlo. La solución pasa por editar en uno de los fuentes los parámetros de configuración del display. Sé que hubiese resultado mas cómodo generar el parche y ponerlo aquí sin mas para que se aplique, pero así explico con un poco mas de detalle que es lo que hay que hacer, no sea que alguien utilice otro tipo de display y tenga que editar de forma distinta los fuentes.

El archivo a editar es mini2440/arch/arm/mach-s3c2440/mach-mini2440.c. Aquí, entre otras cosas, se inicializan muchas de las estructuras utilizadas por el núcleo con parámetros específicos de nuestra arquitectura. En nuestro caso habrá que modificar los parámetros del display TFT de 3’5″ con touchscreen, que es el que tengo yo. En otro caso y dependiendo del display que tengas tu, puede que tengas que modificar o no dicho archivo con distintos parámetros.

Buscamos el siguiente código (a partir de la línea 227):

  1. …………………………………
  2. …………………………………
  3. static struct s3c2410fb_display mini2440_lcd_cfg[] __initdata = {
  4.         [0] = { /* mini2440 + 3.5" TFT + touchscreen – old model "N35" */
  5.                 _LCD_DECLARE(
  6.                         7,                      /* The 3.5 is quite fast */
  7.                         240, 21, 38, 6,         /* x timing */
  8.                         320, 4, 4, 2,           /* y timing */
  9.                         60),                    /* refresh rate */
  10. ………………………………..
  11. ………………………………..

Cómo podemos observar, este fragmento corresponde al display de 3’5″ con touchscreen del mini2440 (seleccionado por defecto en la configuración que creamos anteriormente). Dentro de los parámetros x timing, cambiamos el valor 38 por 25, restando así el offset de 13 pixels que hay en el eje X del display.
El código entonces deberá quedar tal que así:

  1. …………………………………
  2. …………………………………
  3. static struct s3c2410fb_display mini2440_lcd_cfg[] __initdata = {
  4.         [0] = { /* mini2440 + 3.5" TFT + touchscreen – old model "N35" */
  5.                 _LCD_DECLARE(
  6.                         7,                      /* The 3.5 is quite fast */
  7.                         240, 21, 25, 6,         /* x timing */
  8.                         320, 4, 4, 2,           /* y timing */
  9.                         60),                    /* refresh rate */
  10. ………………………………..
  11. ………………………………..

Repito, ésto está probado para mi display, si tu display es distinto, deberás buscar la sección correspondiente en el archivo y “ajustarlo” según tus necesidades.

Una vez editado, proseguimos con la compilación.

Compilando

Después de generada la configuración y arreglado el pequeño bug del que hablé antes, ya podemos compilar:

  1. javieralso@rigoberto:~$ CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm make O=../kernel-bin/

Y a esperar (no mucho, un café mientras ves el youtube :D ).

Suponiendo que ya has terminado de compilar y todo ha ido a las mil maravillas, ahora toca crear la imagen que u-boot cargará y arrancará. Para ello utilizamos la herramienta mkimage que podemos encontrar en el directorio tools de la distribución u-boot que compilamos en su momento.

el comando a introducir será el siguiente:

  1. avieralso@rigoberto:~$ mkimage -A arm -O linux -T kernel -C none -a 0×30008000 -e 0×30008000 -d ../kernel-bin/arch/arm/boot/zImage ./uImage

Deberemos usar el path completo a mkimage si no lo tenemos en nuestro path.

Éste comando crea una imagen para arm (-A arm), sistema operativo linux y tipo de imagen un núcleo. Además, no se usará compresión (-O linux -T kernel -C none). La imagen creada será cargada por u-boot a partir de la dirección 0×30008000 y el punto de entrada (dirección a partir de la cual arranca la imagen) será la misma dirección (-a 0×30008000 -e 0×30008000).
La imagen se llamará uImage y quedará almacenada en el directorio actual.

Lo próximo…

En una próxima receta explicaré la configuración de u-boot poniendo como ejemplo la instalación de nuestra imagen para que sea cargada desde una tarjeta microSD (o desde donde queramos).
Después explicaré como compilar desde cero una imagen de opie utilizando la toolchain de openembedded e instalarla, junto con las librerías generadas durante la compilación de éste núcleo.
Finalmente hablaré sobre como compilar una aplicación para opie utilizando bitbake, el script de compilación automatizado de openembedded.

Referencias

Como siempre muchísimas, pero la principal, de la que lo he seguido todo, ha sido el siguiente enlace:

Un saludo

NOTA: Entrada extraída de otra antigua entrada publicada por mi en CrySoL.

Indentación de código

0
Filed under Programación, tinyOS
Tagged as
He aquí un enlace que encontré acerca de estilos de indentación.

Cuando tenga algo de tiempo haré un “copy&paste” aquí (citando autor, claro :P ) por si acaso se perdiese, que me ha parecido muy curioso :-D

Indentación de código.

Instalando uBoot en una mini2440 (Friendlyarm)

2
Filed under ARM, Embedded, GNU, mini2440
Tagged as , , ,

Introducción

Como he comentado anteriormente, u-boot es un bootloader totalmente libre para dispositivos empotrados de bajo poder computacional. Entre sus múltiples posibilidades, están las de particionar la memoría flash del dispostivo en el que se encuentre corriendo, asignar nombre a dichas particiones, cargar imágenes en RAM desde distintas fuentes, volcarlas a flash, y un largo etcétera….
Aquí hablaré de las opciones que a mi juicio pueden ser mas socorridas para la instalación de un Sistema Operativo en un dispositivo empotrado, en mi caso, tomando como ejemplo la tarjeta de desarrollo mini2440 comentada anteriormente, aunque como es de suponer, las mismas operaciones pueden ser realizadas en distintas plataformas hardware.
Así pues vamos a lo divertido del asunto ¡¡Empezamos!!

Instalando

El proceso de instalación de u-boot consiste básicamente en cargar una imagen de u-boot en la RAM de nuestrá máquina, bien a través de su propio bootloader, bien a través de un JTAG. Una vez cargado en RAM, se lanza y se le dice que se (auto)guarde en la flash de nuestra tarjeta. En este caso y dependiendo de nuestro hardware, puede sobreescribir a su bootloader original o bien coexistir con él. En cualquier caso, deberás consultar el manual de tu tarjeta para comprobarlo.

Para el caso de la MINI2440, ésta viene con dos memorias Flash distintas: por un lado hay una flash NOR de 2MB en la que se encuentra almacenado el bootloader específico para los micros de Samsung (mirco de mi tarjeta de desarrollo): supervivi. Por otro lado, tenemos una memoria flash NAND de 128MB en la que se supone que irá nuestro sistema. En ésta memoria será en la que instalaremos u-boot, configurando posteriormente nuestra mini2440 para que arranque desde ahí (utilizando un conmutador que a tal efecto lleva instalado).

Para comenzar, necesitaremos descargarnos tanto el código fuente de u-boot como el software necesario para poder comunicarnos con supervivi y un compilador cruzado para ARM:

* u-boot:

  1. javieralso@rigoberto:~$ mkdir uboot; cd uboot
  2. javierlaso@rigoberto:~$ git clone git://repo.or.cz/u-boot-openmoko/mini2440.git
  3.  

* toolchain: Compiladores cruzados para ARM los hay muchos en esto de GNU. Yo, que seguí al dedillo todo lo que leí en montónes de blogs, usé el siguiente:

  1. javieralso@rigoberto:~$ wget http://www.codesourcery.com/sgpp/lite/arm/portal/package3696/public/arm-none-linux-gnueabi/arm-2008q3-72-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2
  2. javieralso@rigoberto:~$ unp  arm-none-linux-gnueabi/arm-2008q3-72-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2
  3. javieralso@rigoberto:~$ export PATH=$PATH:/home/rigoberto/arm-2008q3/bin

* Friendlyarm package: Se trata de un paquete con un uploader ya compilado para comunicarse con supervivi y de una imagen ya compilada de u-boot, que deberemos utilizar para cargar la imagen que compilemos nosotros. El por qué de usar dos imágenes de u-boot se explicará mas adelante.

  1. javieralso@rigoberto:~$ wget http://www.kernelconcepts.de/~fuchs/micro2440/friendlyarm2440-package.tar.bz2
  2. javieralso@rigoberto:~$ unp friendlyarm2440-package.tar.bz2

Una vez que lo tenemos todo descargado, empezaremos por compilar el bootloader, para ello no debemos olvidar añadir a nuestro PATH la ruta en la que se encuentran los binarios de la toolchain que usemos (como hicimos anteriormente). Una vez hecho esto, podemos pasar a compilar:

  1. javieralso@rigoberto:~$ cd uboot/mini2440
  2. javieralso@rigoberto:uboot/mini2440$ export CROSS_COMPILE=arm-none-linux-gnueabi-
  3. javieralso@rigoberto:uboot/mini2440$ make mini2440_config; make

Después de no mucho tiempo y si todo ha ido bien, deberemos tener un archivo llamado u-boot.bin en la carpeta donde hemos llevado a cabo la compilación. Esa es la imagen de u-boot que se almacenará en la flash de nuestra tarjeta, pero para poder llevar a cabo dicho proceso, necesitaremos otra versión de u-boot (sé que suena bastante raro, y de hecho lo es. Mas adelante explicaré el por qué de ello).

Bueno, llegados a este punto, cogemos nuestra mini2440, deslizamos el conmutador superior hacia la izquierda (para configurar el arranque desde la NOR), conectamos un módem nulo desde su puerto serie a un puerto serie de nuestra máquina y el device USB a un puerto USB libre. Tras ésto, arramcamos minicom y configuramos el puerto serie a 115200 Baudios, 8N1 y sin ningún tipo de contol de flujo.
Hecho ésto, solo queda encender nuestra tarjeta y comprobar que supervivi arranca:

  1. ##### FriendlyARM BIOS for 2440 #####
  2. [x] bon part 0 320k 2368k
  3. [v] Download vivi
  4. [k] Download linux kernel
  5. [y] Download root_yaffs image
  6. [c] Download root_cramfs image
  7. [a] Absolute User Application
  8. [n] Download Nboot
  9. [e] Download Eboot
  10. [i] Download WinCE NK.nb0
  11. [w] Download WinCE NK.bin
  12. [d] Download & Run
  13. [z] Download zImage into RAM
  14. [g] Boot linux from RAM
  15. [f] Format the nand flash
  16. [p] Partition for Linux
  17. [b] Boot the system
  18. [s] Set the boot parameters
  19. [t] Print the TOC struct of wince
  20. [u] Backup NAND Flash to HOST through USB(upload)
  21. [r] Restore NAND Flash from HOST through USB
  22. [q] Goto shell of vivi
  23. Enter your selection:

Seleccionamos la opción q para acceder a la shell de superivi y después le diremos que queremos cargar una imagen en la memoria y arrancar, pero para ello, antes deberemos saber cuando ocupa el bootloader que vamos a cargar (el que ya viene compilado), así que volvemos a la carpeta en la que lo descomprimimos y lo comprobamos:

  1. javieralso@rigoberto:friendlyarm2440-package$ ls -l
  2. …………………..
  3. …………………..
  4. -rwxr-xr-x   1 javieralso javieralso 239016 oct 24 11:38 u-boot.bin
  5. …………………..
  6. …………………..

Podemos ver que ocupa 239016 bytes. Guardamos este dato, nos será util para lo que comentamos anteriormente.

  1. Enter your selection:q
  2. Supervivi> load flash 0 239016 u
  3. USB host is connected. Waiting a download.

Con esto le hemos dicho a supervivi que queremos cargar 239016 bytes de datos (observad que es la misma cantidad de bytes que ocupa la imagen de u-boot) en la RAM de la tarjeta. Ahora supervivi se ha quedado esperando los datos, que deberemos envíarselos vía USB. Habiendo conectado el cable USB como se indicó antes ejecutamos el uploader:

  1. javieralso@rigoberto: friendlyarm2440-package$ sudo ./s3c2410_boot_usb u-boot.bin

que deberá ser llamado desde la carpeta friendlyarm2440-package y con sudo para poder tener acceso al puerto USB.

Si todo ha ido bien, supervivi debería mostar algo como esto:

  1. Now, Downloading [ADDRESS:30000000h,TOTAL:232706]
  2. RECEIVED FILE SIZE:  239016 (28KB/S, 8S)
  3. Downloaded file at 0×30000000, size = 232696 bytes
  4. Found block size = 0x0003c000
  5. Erasing…    … done
  6. Writing…    … done
  7. Written 239016 bytes
  8. Supervivi>

Ahora, arrancamos u-boot:

  1. Supervivi> go 0×30000000

y ya está, nuestra tarjeta arrancará u-boot:

  1. U-Boot 1.3.2-mini2440 (May 15 200921:24:49)
  2. I2C:   ready
  3. DRAM:  64 MB
  4. Flash:  2 MB
  5. NAND:  Bad block table not found for chip 0
  6. Bad block table not found for chip 0
  7. 64 MiB
  8. *** Warning – bad CRC or NAND, using default environment
  9. USB:   S3C2410 USB Deviced
  10. In:    serial
  11. Out:   serial
  12. Err:   serial
  13. MAC: 08:08:11:18:12:27
  14. Hit any key to stop autoboot:  0

Lo primero que ha hecho u-boot al arranchar ha sido darnos un mensaje de alerta indicando que el contenido de las variables de entorno no está fijado y de ahí el error CRC que se ve. Éstas variables de entorno son necesarias para que u-boot pueda configurarse al arrancar, pero antes de crearlas debemos de preparar la memoria NAND.

Para preparar la NAND, lo primero que haremos será borrar el listado de sectores defectuosos y volver a crearlo nosotros. Los sectores defectuosos son una lista, generalmente almacenada en fábrica, que indica qué sectores de la NAND son defectuosos. Lo normal es que haya alguno y con el tiempo y el uso se irán incrementando.

  1. MINI2440 # nand scrub
  2. NAND scrub: device 0 whole chip
  3. Warning: scrub option will erase all factory set bad blocks!
  4.          There is no reliable way to recover them.
  5.          Use this command only for testing purposes if you
  6.          are sure of what you are doing!
  7. Really scrub this NAND flash? <y/N>
  8. Erasing at 0x33d4000 —  81% complete.
  9. NAND 64MiB 3,3V 8-bit: MTD Erase failure: -5
  10. Erasing at 0x3ffc000 — 100% complete.
  11. Bad block table not found for chip 0
  12. Bad block table not found for chip 0
  13. OK
  14. MINI2440 #

Con el comando anterior borramos la lista de sectores defectuosos antes mencionada. Después de haber hecho la limpieza, creamos nuestra propia tabla (también conocida como BBT):

  1. MINI2440 # nand createbbt
  2. Create BBT and erase everything ? <y/N>
  3. Skipping bad block at  0×03410000                                            
  4. Skipping bad block at  0x03ff0000                                            
  5. Skipping bad block at  0x03ff4000                                            
  6. Skipping bad block at  0x03ff8000                                            
  7. Skipping bad block at  0x03ffc000                                            
  8. Creating BBT. Please wait …Bad block table not found for chip 0
  9. Bad block table not found for chip 0
  10. Bad block table written to 0x03ffc000, version 0×01
  11. Bad block table written to 0x03ff8000, version 0×01

Este proceso es relativamente lento, por lo que habrá que esperar que esperar (3-4 minutillos), así que mientras tanto, busca una tarjeta SD y copia el binario u-boot.bin QUE COMPILASTE en dicha tarjeta. La cargaremos desde ahí.

Una vez que tenemos nuestra SD con el binario cargado y que ya se ha generado la nueva BBT, introducimos la tarjeta en el lector de la mini2440 y le decimos a u-boot que la detecte:

  1. MINI2440 # mmcinit
  2. trying to detect SD Card…
  3. Manufacturer:       0×30, OEM "SD"
  4. Product name:       "SU02G", revision 8.0
  5. Serial number:      3489032
  6. Manufacturing date: 9/2010
  7. CRC:                0×27, b0 = 1
  8. READ_BL_LEN=15, C_SIZE_MULT=7, C_SIZE=365
  9. size = 1642070016

Con la tarjeta ya detectada, cargamos u-boot.bin en la RAM:

  1. MINI2440 # fatload mmc 0:1 0×32000000 u-boot.bin
  2.  
  3. 233412 bytes read

¿Qué ha pasado? pues que le hemos dicho a u-boot que cargue el archivo binario u-boot.bin desde un sistema de archivos FAT (fatload), ubicado en la SD (mmc), dispositivo 1, primera partición (0:1), a partir de la dirección de memoria 0×32000000.

Una vez que tenemos el archivo en RAM, lo volcamos a la NAND:

  1. MINI2440 # nand write.e 0×32000000 0×0 0x38fc4
  2. NAND write: device 0 offset 0×0, size 0x38fc4
  3. Writing data at 0x38c00 — 100% complete.
  4.  233412 bytes written: OK

Bueno, en este caso, hemos escrito 0x38fc4 bytes (233412, para que nos entendamos), al comienzo de la NAND. El numerajo hexadecimal, como hemos visto corresponde al peso de u-boot.bin pero ésta vez del que hemos compilado NOSOTROS, no del que cargamos en RAM al principio. Importante no confundir ésto. Un apunte importante es que en ésta parte del proceso, la longitud deberá darse en hexadecimal, no en decimal, ya que ello puede dar lugar a errores.

Por lo que he leído por ahí, el modificador .e del comando write se utiliza para generar códigos de correción de errores y evitar así lo posibles bloques defectuosos que pueda haber en la NAND (recordad el comando createbbt de antes).

Una vez hecho esto, podemos reiniciar tranquilos nuestra mini2440: u-boot la hará arrancar. Aunque no hará mucho mas ya que la NAND está completamente borrada y no hay sistema.

Para finalizar debemos indicar a u-boot donde está la partición dedicada a las variables de entorno. Para ello primero debemos de conocer la tabla de particiones de nuestra memoria NAND, que deberá estar formateada de fábrica (si no es así puede hacerse desde el menú de supervivi). El comando mtdparts nos proporcionará dicha información:

  1. MINI2440 # mtdparts
  2.  
  3. device nand0 <mini2440-nand>, # parts = 4
  4.  #: name                        size            offset          mask_flags
  5.  0: u-boot              0×00040000      0×00000000      0
  6.  1: env                 0×00020000      0×00040000      0
  7.  2: kernel              0×00500000      0×00060000      0
  8.  3: root                0x07aa0000      0×00560000      0
  9.  
  10. active partition: nand0,0(u-boot) 0×00040000 @ 0×00000000
  11.  
  12. defaults:
  13. mtdids  : nand0=mini2440-nand
  14. mtdparts: <NULL>

Podemos ver que tenemos cuatro particiones cuyos nombres son bastante descriptivos. La partición 1, env será la utilizada para las variables de entorno de u-boot, así que con dynenv lo configuramos:

  1. MINI2440 # dynenv set 40000
  2. device 0 offset 0×40000, size 0x7fc0000
  3. 45 4e 56 3000 00 04 00

El comando introducido ha configurado la ubicación de las variables de entorno a partir de la dirección 0×40000, que como se puede ver en la tabla de particiones mostrada anteriormente, es la dirección offset (de comienzo) de la partición asignada a tal efecto.

Una vez configurada la ubicación de las variables de entorno, almacenamos la configuración con el comando saveenv:

  1. MINI2440 # saveenv
  2. Saving Environment to NAND…
  3. Erasing Nand…Writing to Nand… done

Y listo, ya tenemos u-boot instalado.

Para finalizar os contaré por qué antes utilicé dos versiones distintas de uboot. El motivo es que para instalar uboot, primero debe residir en la RAM y poderse ejecutar desde ahí, ya que hay que hacer una limpieza de la NAND antes de llevar a cabo la instalación propiamente dicha. Lo que observé entonces fue que las versiones de uboot que conseguían arrancar desde la RAM, no eran capaces de hacerlo luego una vez que se instalaban en NAND (en efecto, según los manuales que leí, una vez creada la BBT, se escribía el contenido de la RAM a la NAND, ya que allí es donde está uboot) y viceversa, las imágenes que si que eran capaces de arrancar desde la NAND no lo podían hacer desde la RAM. No sé si esto es por algún bug en uboot (que deberían ser entonces bugs distintos en cada una de las versiones) o porque yo hice algo mal (repetí montones de veces lo que vi en tutos, blogs y demás paso por paso). El caso es que después de varios intentos sin mucho éxito, decidí hacerlo tal y como lo he explicado aquí.

En el futuro….

En próximas recetas, explicaré qué comandos básicos utilizar en u-boot para cargar imágenes en RAM o NAND, configurar el arranque automático desde distintas fuentes, particionar la NAND, configurar opciones de arranque para el S.O. que tengamos y alguna cosilla mas. Evito poner esa información aquí para no sobrecargar demasíado esta receta, que se supone que tienen que ser breves y concisas.

Referencias

Muchas y muy variadas, aunque las mas importantes son éstas.

NOTA: Entrada extraída de otra antigua entrada publicada por mi en CrySoL.

Programador para ChipCom CC2430 y sucedáneos bajo GNU/Linux

0
Filed under Electrónica, Embedded, GNU, Zigbee
Tagged as

CC2430/CC2510/CC1110 – Flasher – Modula d.o.o..

GLib IO Channels con C

0
Filed under GNU, Programación, tinyOS
Tagged as , ,

Introducción

Un patrón reactor (o también conocido como Distpatcher), es un patrón de programación utilizado cuando queremos atender varios eventos que pueden suceder de forma asíncrona y en ocasiones simultáneamente, pero queremos que ésos eventos sean atendidos sínconamente y, a ser posible, ordenadamente según el orden en que llegaron los eventos.
Posibles aplicaciones de éste patrón las podemos ver, por ejemplo, en cualquier servidor que queda a la escucha de un socket (de Internet o de cualquier otro tipo, evidentemente) y los va atendiendo según se vayan produciendo conexiones entrantes.
También podemos tener una aplicación en la que si no se produce ninguna entrada durante un tiempo determinado, la aplicación cierre, y en caso contrario, ejecute distintas operaciones según la fuente de entrada.
En éste último caso, podríamos hacer uso de la llamada al sistema select(), pero aquí explicaré como implementar dicho patrón utilizando GLib.

Patrón reactor en C.

El código de ejemplo para ésta receta es el que se puede ver a continuación:

  1. #include <stdio.h>
  2. #include <glib.h>
  3.  
  4. gboolean handler1(gpointer data) {
  5.   printf ("Manejador 1\n");
  6.   return TRUE;
  7. }
  8.  
  9. gboolean handler2(gpointer data) {
  10.   printf ("Manejador 2\n");
  11.   return TRUE;
  12. }
  13.  
  14. gboolean handler3(GIOChannel *source, GIOCondition condition, gpointer data) {
  15.   int fd;
  16.   char buff[100];
  17.  
  18.   for (fd = 0; fd < 100; fd++) {
  19.     buff[fd] = 0;
  20.   }
  21.  
  22.   printf ("Manejador 3\n");
  23.   fd = g_io_channel_unix_get_fd (source);
  24.   read(fd, buff, 100);
  25.   printf ("Buffer: %s", buff);
  26.   return TRUE;
  27. }
  28.  
  29. int main (void) {
  30.   gpointer data;
  31.  
  32.   g_timeout_add(1000, handler1, data);
  33.   g_timeout_add(500, handler2, data);
  34.  
  35.   g_io_add_watch(g_io_channel_unix_new(0), G_IO_IN, handler3, NULL);
  36.   g_main_loop_run(g_main_loop_new(NULL, FALSE));
  37.   return 0;
  38. }

El código que se muestra es bastante simple y fácil de entender. Al principio, después de las inclusiones de las cabeceras necesarias, se declaran tres funciones que consisten en los manejadores de los eventos capturados. Los dos primeros son exactamente iguales y lo único que hacen es escribir un mensaje por pantalla. El tercer manejador se llama cuando se introducen datos por teclado. Lo único que hace es inicializar un buffer en el que después copiaremos los datos introducidos y finalmente los imprimiremos. Podemos ver que éstas tres funciones devuelven TRUE, mas adelante se verá el porqué.

La “chicha” del asunto está en la función main, que es la que se encarga de asignar los manejadores anteriores a cada una de las señales y de inicializar el bucle de eventos.
Las llamadas a g_timeout_add asignan los dos primeros manejadores a un evento de tiempo, que se generará, respectivamente, cada segundo y medio segundo. Por su parte, la llamada a g_io_channel_unix_new asigna el tercer manejador a los eventos de escritura generados a través de la entrada estándar (el teclado).
Finalmente, la penúltima línea se encarga de poner en funcionamiento el bucle principal, creado por la función que se llama como primer parámetro. En ese momento, el programa se queda “congelado” ahí y lo único que hace es esperar a que se produzcan los eventos programados (los dos de temporización y la entrada por el teclado).
Antes comenté que los manejadores siempre devolvian TRUE. Ésto se debe a que si devolviesen FALSE, no volverían a ser llamados. Así conseguimos que los manejadores vuelvan a ser ejecutados cuando se producen de nuevo los eventos.

Referencias

NOTA: Entrada extraída de otra antigua entrada publicada por mi en CrySoL.

Creación de módulos e interfaces en nesC para TinyOS 2

0
Filed under Embedded, Programación, tinyOS
Tagged as , ,

Introducción

tinyOS se basa en el uso de módulos que se “cablean” (wiring) entre sí para dar lugar a la aplicación en sí. Este cableado se lleva a cabo en lo que se conoce como configuraciones y podemos tener montones de configuraciones distintas para una misma aplicación. De este modo, podemos conseguir que una misma aplicación pueda correr en distintas arquitecturas hardware sin apenas tocar código, solo modificando las configuraciones, lo que hará que se utilicen unos módulos u otros que se encargan del control a mas bajo nivel.

Básicamente, aquí hablaré de:

  • Módulos
  • Interfaces
  • Configuraciones

Todo el código de ejemplo que se usa en esta receta puede descargarse, vía subversión, del repositorio público de GoogleCode de la siguiente forma:

  1. yo@miMaquina:~$ svn checkout http://gn-cillos.googlecode.com/svn/trunk/tinyOS

y con ésto ya podremos seguir todos los ejemplos dados a continuación.

Interfaces

Las interfaces se pueden definir como la parte “visible” de los módulos. No se puede acceder a ninguna función de un módulo si antes no ha sido definida en una interfaz.
Una interfaz puede ser provista por varios módulos distintos. Por ejemplo, podemos tener una interfaz de comunicaciones que abstraiga los procedimientos de envío y recepción de mensajes y varios módulos que hagan uso de esa interfaz, uno para cada tipo de hardware subyacente (por ejemplo no se maneja igual el hardware de comunicaciones de una mica2 que el de una telos). Los métodos serán los mismos y se llamarán igual (la interfaz es la misma), pero la implementación interna puede ser totalmente distinta. Nosotros lo veremos como una caja negra.

En nuestro ejemplo se utilizan dos interfaces: interfaz2, en el archivo interfaz2.nc e interfazPRB en el archivo interfazPRB.nc. Las implementaciones son las que siguen:

  1. interface interfaz2 {
  2.   command void i2c1();
  3.   command uint16_t i2c2(int c);
  4.   command uint16_t i2c3(char d);
  5. }

para interfaz2.nc y

  1. interface interfazPRB {
  2.   command void comando1();
  3.   command int comando2(int c);
  4. }

para interfazPRB.nc

Bueno. Se puedes ver, escribir una un interfaz es bastante simple. Lo único que hay que hacer es especificar el nombre de la y los comandos de que consta. Estos comandos son los prototipos (como se llama en ANSI C) de las funciones precedidos de la palabra reservada command. Si quisiesesmos declarar señales, utilizariamos el modificador signal. El nombre del archivo debe de ser el mismo que el del interfaz, respetando las mayúsculas y con extensión .nc.

Módulos

Los módulos implementan bloques funcionales generalmente de un nivel de abstracción más bajo que el bloque que los referencia. Un mismo módulo puede proveer varias interfaces y por lo tanto puede implementar distintas funcionalidades (Por ejemplo el módulo AMSender provee las interfaces Packet, AMPacket, AMSend, etc…). Lo lógico es que todos los conjuntos de funciones (interfaces) que implementa un módulo guarden algún tipo de relación.

En nuestro caso, utilizamos dos módulos: modulo2C, implementado en modulo2C.nc y moduloC implementado en moduloC.nc.
Éste es el contenido de los archivos:

  1. module modulo2C {
  2.   provides {
  3.     interface interfaz2;
  4.     /*
  5.       Única interfaz proporcionada por el módulo
  6.     */
  7.   }
  8. }
  9.  
  10. implementation
  11. {
  12.   command void interfaz2.i2c1() {
  13.     int v1 = 5;
  14.     int v2;
  15.     v2 = v1;
  16.   }
  17.  
  18.   command uint16_t interfaz2.i2c2(int c) {
  19.     return (uint16_t)2*c;
  20.   }
  21.  
  22.   command uint16_t interfaz2.i2c3(char d) {
  23.     return (uint16_t)d*d*d;
  24.   }
  25. }

para modulo2C y

  1.  
  2. module moduloC {
  3.   provides interface interfazPRB;
  4.   uses {
  5.     interface interfaz2;
  6.     interface Leds;
  7.   }
  8. }
  9.  
  10. implementation {
  11.   command void interfazPRB.comando1() {
  12.     call Leds.led0Toggle();
  13.     call Leds.led1Toggle();
  14.     call Leds.led2Toggle();
  15.   }
  16.  
  17.   command int interfazPRB.comando2(int c) {
  18.     call Leds.set((uint8_t)c);
  19.     return 0;
  20.   }
  21. }

Cada uno de los módulos se compone de dos partes. La primera es la declaración. En ella se especifica el nombre del módulo, así como las interfaces que provee y las interfaces que usa. En el caso de moduloC, por ejemplo, podemos ver que la interfaz que proporciona es la interfaz interfacePRB y utiliza las interfaces interfaz2 y Leds.
A continuación sigue la implementación. Aquí debe hacerse la implementación de cada uno de los comandos especificados en la interfaz. En éste caso no hay señales. Si las hubiese, la forma de lanzarlas es llamarlas como funciones normales (precedidas de la palabra reservada signal) cuando sea necesario. En éste caso, como se trata solo de un ejemplo, las funciones son totalmente triviales.

No queda mucho mas que comentar acerca de los módulos. Básicamente, después de ésto solo faltaría escribir la configuración con el wiring entre módulos e interfaces. ¿Y qué es eso?, pues ahora mismo lo explico.

Configuraciones

Como se ha dicho anteriormente, una aplicación escrita para tinyOS se compone de implementación de módulos y de configuraciones. En algunos casos, pueden hacerse aplicaciones solo con archivos de configuraciones. Un archivo de configuración consiste básicamente en un archivo formado por los módulos (o componentes) a utilizar en la aplicación y la relación que hay entre dichos módulos y las interfaces utilizadas por otros módulos.

En un principio esto puede parecer bastante lioso, así que nada mejor que verlo con algún ejemplo. Veamos primero el archivo de configuración moduloEjm.nc:

  1. configuration moduloEjmC {
  2.   provides interface interfazPRB;
  3. }
  4.  
  5. implementation  {
  6.  
  7.   components LedsC;
  8.   components modulo2C;
  9.   components moduloC;
  10.  
  11.   moduloC.Leds -> LedsC;
  12.   moduloC.interfaz2 -> modulo2C;
  13.  
  14.   interfazPRB = moduloC;
  15. }

Paso a paso para no liarnos:

Lo primero que tenemos que hacer es indicar que estamos hablando de una configuración, en éste caso moduloEjmC. Como siempre, dicho nombre coincide con el del archivo en el que se aloja. Ésta configuración, provee una interfaz (aunque podrian ser mas), la interfaz interfazPRB.
En la parte de implementación vemos que utilizamos tres módulos o componentes: LedsC, modulo2C y moduloC. Éstos son los módulos cuya funcionalidad necesitaremos para llevar a cabo las tareas de la configuración en la que estamos trabajando.

Después nos encontramos con la parte de wiring. Aquí tenemos dos conexiones:

  • moduloC.Leds -> LedsC;
  • moduloC.interfaz2 -> modulo2C;

¿Qué hace ésto? fácil, hemos conectado la interfaz Leds utilizada en la implementación del módulo moduloC con el componente (o módulo) LedsC que provee dicha interfaz y que hemos declarado anteriormente en la sección components. Con ésto, cada vez que invoquemos a alguno de los comando de ésta interfaz, lo que en realidad estaremos haciendo será llamar a los métodos con dicho nombre que haya implementados en el módulo LedsC. Si tuviésemos otro módulo con la misma interfaz, podriamos haberlo utilizado indistintamente, con la única diferencia de la implementación que esconda detrás, claro.

La segunda conexión ya queda algo mas clara, conecta el interfaz interfaz2 de moduloC con el módulo modulo2C que provee dicha interfaz y que nosotros mismos escribimos.

Finalmente, en la última línea, podemos ver una asignación distinta a todas las vistas anteriormente. Ésta asignación lo único que hace es conectar la interfaz que se provee (interfazPRB) con el módulo que proveerá la funcionalidad a dicha interfaz, que en éste caso es moduloC.

Ahora, lo único que falta es utilizar todo ésto:

  1. configuration ejmAppC{}
  2.  
  3. implementation
  4. {
  5.   components MainC, ejmC;
  6.   components moduloEjmC;
  7.  
  8.   ejmC -> MainC.Boot;
  9.   ejmC.interfazPRB -> moduloEjmC;
  10. }

Aquí tenemos el contenido de ejmAppC.nc, que tiene la configuración ejmAppC.

Esta configuración podemos ver que no provee interfaz alguna, pero que sí usa componentes, en concreto MainC, ejmC y moduloEjmC. Los dos últimos son familiares ;-)

Después tenemos los wirings: uno que conecta la interfaz Boot del módulo MainC (utilizado para informar de que tinyOS ha arrancado y demás) con el módulo de mayor nivel de nuestra aplicación (la aplicación en si), ejmC y otro que conecta la interfaz interfazPRB que utiliza la aplicación, con el módulo (aunque lo hayamos “implementado” en un archivo de configuración, lo estamos tratando como un módulo, ya que esa configuración corresponde a la de un módulo) moduloEjmC.

Con ésto, ya solo nos falta implementar las funciones de nuestro programita propiamente dicho:

  1. module ejmC {
  2.   uses interface Boot;
  3.   uses interface interfazPRB;
  4. }
  5.  
  6. implementation {
  7.   event void Boot.booted() {
  8.     call interfazPrb.comando1();
  9.   }
  10. }

Aquí puedes ver cómo se define un módulo llamado ejmC y que utiliza las interfaces Boot e interfazPRB que ya conectamos anteriormente a cada uno de los módulos que queríamos que nos diesen la funcionalidad.

A continuación, en la parte de la implementación tan solo tenemos que implementar las funciones y comandos que necesitemos para dar la funcionalidad que queramos a nuestro programita.

¿Qué por qué este es el módulo de mayor nivel? Bueno, por nada en especial. Simplemente que implementa la interfaz Boot del módulo MainC, quien lanzará el evento Boot.booted cuando tinyOS termine de arrancar y por tanto entregará el control del hilo principal a dicho evento, comenzando la ejecución del programa por ahí.

Referencias

NOTA: Entrada extraída de otra antigua entrada publicada por mi en CrySoL.