Category Archives: Programación

Snippets de C++

0
Filed under C++, Programación, Snippets
Tagged as ,

Introducción

Snippets de código en C++ muy simples pero que suelo olvidar con frecuencia y me hacen perder tiempo buscando.

de int a string

  1. #include <sstream>
  2. #include <string>
  3.  
  4. ……………;
  5. ……………;
  6.  
  7. int var = 25;
  8. string varStr;
  9.  
  10. std::ostringstream buffer;
  11. buffer << var;
  12. varStr = var.str();
  13.  
  14. ……………;
  15. ……………;

Generar un fichero (de texto)

  1. #include<fstream>
  2.  
  3. ……………;
  4. ……………;
  5.  
  6.     ofstream fileStream(fileName.c_str());
  7.     fileStream << _dotStream.str();
  8.     fileStream.flush();
  9.     fileStream.close();
  10.  
  11. ……………;
  12. ……………;

Referencias

Snippets de SQLite en python

0
Filed under Python, Snippets
Tagged as ,

Introducción

Algunos snipplets útiles para manejo de bases de datos SQLite desde Python.

Qué instalar

Lo primero, por supuesto, es instalar el wrapper:

  1. javieralso@rigoberto:~$ apt-get install python-pysqlite1.1

Cargando la base de datos

  1. In [1]: import sqlite
  2. In [2]: db = sqlite.Connection("db1")

La base de datos que hemos cargado se llama db1. db es una instancia de la clase sqlite.Connection, con la que podremos acceder a la base de datos.

Obteniendo un cursor y generando una consulta

  1. In [1]: import sqlite
  2. In [2]: db = sqlite.Connection("db1")
  3. In [3]: cursor = db.cursor()
  4. In [4]: cursor.execute("Select * from deviceSamples where Sample = %i", (225))
  5. In [5]: for item in cursor:
  6.    …:     print item
  7.    …:    
  8.    …:    
  9. (1394, ‘-79′, ‘XX:XX:XX:XX:XX:XX’, 225)
  10. (1395, ‘-87′, ‘XX:XX:XX:XX:XX:XX’, 225)
  11. (1396, ‘-89′, ‘XX:XX:XX:XX:XX:XX’, 225)
  12. (1397, ‘-94′, ‘XX:XX:XX:XX:XX:XX’, 225)
  13. (1398, ‘-79′, ‘XX:XX:XX:XX:XX:XX’, 225)
  14. (1399, ‘-92′, ‘XX:XX:XX:XX:XX:XX’, 225)

Obtiene una instancia de Cursor, a través de la cual se pueden realizar consultas. En el ejemplo se realiza una consulta parametrizada y después se muestran todos los resultados obtenidos.

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.

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.

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.

Traceando código C en los AVR “in circuit”: avarice

0
Filed under AVR, Depuración, Embedded
Tagged as ,

Introducción

Bueno, como lo prometido es deuda he conseguido tracear un programa escrito en C para un ATMEGA128 en la propia placa de desarrollo utilizando solamente software libre. Aquí explicaré como lo he hecho y además, como extensión a la receta anterior sobre simulación de código para AVR explicaré como depurar con la herramienta libre ddd, que es básicamente un front-end para gdb muy fácil y cómodo de utilizar.
Así pues, sin mas preámbulos ¡¡¡¡Al turrónnnnn!!!! :-P

Preparando el terreno

Bueno, para nuestros propósitos neceistaremos un AVR JTAG ICE. Se trata de un programador/emulador para AVR y soporta muchísimos de los micros de ésta familia.

En lo referente al software, utilizaremos avarice que no es ni mas ni menos que nuestro interface con el JTAG. También haremos uso del ya conocido avr-gdb y de ddd como front-end de avr-gdb.

Bueno, pues suponiendo que seguisteis con atención la receta anterior en la que hablé de cómo simular los AVR, tan solo tendremos que instalar ddd y avarice en nuestra máquina para empezar…

  1. yo@miMaquina:~$ sudo apt-get install avarice ddd

Compilación del código a simular

Bueno, para la compilación del código necesitamos exactamente las mismas opciones que para la simulación. De hecho, el proceso es el mismo, ya que en ambos casos usamos avr-gdb. La única diferencia es que avarice no necesita para nada el archivo .bin que creamos. En su lugar utilizaremos el .hex, que será el que carguemos en el microcontrolador para tracear. Por lo tanto podemos seguir utilizando el Makefile de la receta anterior y supondré que, de hecho, estamos trabajando con el mismo proyecto.

Simulando con ddd

Bueno, antes de empezar a tracear, voy a contar como utilizar ddd para simular. En realidad es muy sencillo. De hecho, hay que seguir exactamente todos los pasos de la receta anterior, salvo que en lugar de llamar a avr-gdbtui, se llama a ddd diciéndole que use por debajo a avr-gdb. Resumiendo, tenemos que hacer algo como esto:

  1. yo@miMaquina:~$ ddd –debugger avr-gdb -x comandos.gdb

¡¡Y ya ta!! Ale, a simular cosas chulas :-P

Un poco de avarice

Antes de ponernos manos a la obra, explicaré un poco el uso de avarice. Bueno, avarice permite borrar, programar, cambiar los valores de los fusibles, leer el contenido de la memoria del chip, verificar el código del chip y, por supuesto, tracear. Para todo ello necesitaremos, como es de suponer, el JTAG antes mencionado.

Si queremos hacer cosas como por ejemplo borrar un chip, haríamos lo siguiente:

  1. yo@miMaquina:~$ avarice -2 –erase  –jtag usb

la opción -2 se utiliza para decirle a avarice que estamos trabajando con el JTAG mkII, que es algo así como la segunda versión. –jtag usb indica que estamos utilizando la conexión USB del dispositivo. Si quisiéramos utilizar un puerto seríe, habría que sustituir usb por la ruta del archivo de dispositivo del puerto a utilizar.

Para programar un micro:

  1. yo@miMaquina:~$ avarice -2 –erase –program –file archivo.hex –jtag usb

Después de pasar la opción –program, tenemos que decir qué archivo queremos utilizar. Para se utiliza la opción –file seguida del archivo que queremos transferir.

Si se utiliza la opción –P seguido de un modelo de micro podemos decirle a avarice que estaremos utilizando ése micro. Lo normal es que avarice sea capaz de leer la información de identificación del micro sin problemas.

Siempre que queramos programar un micro, deberemos borrarlo antes de manera explícita, ya que el borrado no se hace de forma automática al iniciar la programación, como si pasa con otros entornos.

Al meollo del asunto: TRACEANDO

Ahora ha llegado la hora de la verdad. Con todo listo, nuestro JTAG conectado al usb del ordenador y a la tarjeta a tracear nos disponemos a empezar. La forma en que se ejecutan las aplicaciones es la misma que en la receta anterior. De hecho, para avr-gdb, avarice cumple exactamente el mismo papel que simulavr y lo ve de la misma manera.

Lanzamos primero avarice, diciéndole que programe el micro si esque no lo habíamos hecho nosotros antes:

  1. javieralso@avalon:~$ avarice -2 –erase –program –file rs232toRS485.hex –jtag usb :1212

Ésto es exactamenet lo que hicimos antes, pero con el añadido de que además, le hemos dicho que se quede escuchando conexiones entrantes por el puerto 1212

Ahora tenemos que lanzar ddd, pero antes necesitamos un archivo de comandos para que los ejecute al comienzo. Éste archivo es casi igual que el de la receta anterior, solo que cambian un par de cosillas, así que lo pongo tal y como lo tengo yo:

  1. set debug remote 1
  2. file rs232toRS485.out
  3. target remote 192.168.0.19:1212
  4. break main
  5. continue

Bueno, podemos ver que el archivo es casi igual. Solo se ha eliminado el load, que aquí lo que hacía era cargar el código del micro en el simulador, cosa que no nos interesaba. También activamos la simulación remota (set debug remote 1).

Una vez hecho ésto, solo nos queda llamar al simulador:

  1. yo@miMaquina:~$ ddd –debugger avr-gdb -x comandos.gdb

y ¡¡YA ESTÁ!! a partir de ahora, lo que hagamos en ddd se verá reflejado en nuestra placa de desarrollo. Podremos leer variables, asignarles otros valores, etc etc etc. Eso si, existen algunas limitaciones, como por ejemplo que solo podemos tener 3 breakpoints como mucho, pero vamos, menudencias ;-)

Pues nada més. Espero que lo disfrutéis como lo estoy disfrutando yo y que os sea tan útil para vuestros proyectos como lo está siendo para mi ;-)

Referencias

?

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

Simulación de código C para AVR

0
Filed under AVR, Depuración, Embedded
Tagged as ,

Introducción

Después de haberlo intentado varias veces, habiéndome dado por vencido siempre, he vuelto a las andadas, ésta vez para no parar hasta conseguir depurar un programa que utiliza avr-libc (se puede consultar la receta sobre instalar la toolchain para AVR en éste mismo blog).

Para la depuración he utilizado avr-gdb y simulavr desde distintas máquinas, consiguiendo de ese modo ver en una la ejecución del programa objeto de la prueba y en otra el contenido de los registros y de la memoria del micro.

A partir de ahora, explico cómo lo he hecho todo, desde la compilación (se necesitan parámetros específicos para el enlazador) hasta la puesta en funcionamiento del simulador. Doy por sentando que se conoce el uso de gdb para depurar.

Praparándolo todo

Bueno, para que podamos funcionar tan solo necesitamos simulavr y avr-gdb. En nuestro querido Debian ya sabeis:

  1. yo@miMaquina:~$ sudo apt-get install avr-gdb simulavr

Compilación del código a simular

Para poder simular el código, debemos pasar la opción -g a avr-gcc. Además, de eso, debemos decirle al enlazador que utilice la opción -gstabs. Os paso directamente el Makefile del proyecto que utilicé como prueba para que lo veais mas claro. Está un poco “guarro”, pero servirá para ilustrar el ejemplo ;-) :

  1. MCU=atmega128
  2. CC=avr-gcc
  3. OBJCOPY=avr-objcopy
  4. CFLAGS=-g -mmcu=$(MCU) -Wall -Wstrict-prototypes
  5. #——————-
  6. all: rs232toRS485.hex rs232toRS485.bin
  7. #——————-
  8. %.hex : %.out
  9.         $(OBJCOPY) -j .text -O  ihex rs232toRS485.out rs232toRS485.hex
  10.  
  11. %.bin : %.out
  12.         $(OBJCOPY) -j .text -O binary rs232toRS485.out rs232toRS485.bin
  13.  
  14. rs232toRS485.out : main.o
  15.         $(CC) $(CFLAGS) -o rs232toRS485.out -Wl,-Map,rs232toRS485.map -Wa,-gstabs main.o
  16.         avr-objdump -S rs232toRS485.out > rs232toRS485.lst
  17.  
  18. %.o : %.c
  19.         $(CC) $(CFLAGS) -O0 -c $^

Además de lo comentado anteriormente, también se ha marcado una regla que se encarga de la creación de una imagen binaria del código de salida. Ésta imagen es importante, ya que es la que cargará simulavr para poder llevar a cabo la simulación del micro.

NOTA: Para que se pueda ver el contenido de las variables locales dentro del entorno de simulación, es muy importante que el flag de optimización esté puesto a 0. ($(CC) $(CFLAGS) -O0 -c $^).

Simulando

Bueno, una vez que ya tenemos nuestro programita compilado toca simularlo. Supondremos que vamos a correr simulavr y avr-gdb en la misma máquina, aunque ésto no tiene por qué ser así, de hecho yo lo hago en máquinas separadas para poder ver la traza y el estado de los registros. Pero ésto ya va a gusto del consumidor :-P .

Para nuestros propósitos simulavr funcionará como un gdbserver, es decir, le dirá a avr-gdb cómo debe comportarse. Por ello tenemos que arrancarlo primero. Para ello lo lanzamos de la siguiente manera:

  1. yo@miMaquina:~$ simulavr -d atmega128 -P simulavr-disp -g rs232toRS485.bin

¿Qué hemos hecho? pues hemos llamado a simulavr diciéndole que vamos a simular un atmega128. Como queremos ver el contenido de los registros hemos llamado tambíen a simulavr-disp ya que simulavr solo dice a avr-gdb cómo comportarse, pero no muestra información alguna. simualvr-disp es el frontend que nos permite ver el contenido de los registros internos de la máquina que estamos simulando.

Para lanzar avr-gdb crearemos antes un archivo de comandos a ejecutar para “automatizar” un poco el uso. Éste archivo tiene algo como ésto:

  1. file rs232toRS485.out
  2. target remote localhost:1212
  3. load
  4. break main
  5. continue

En él decimos a gdb que cargue el archivo a simular, se conecte con en esta mísma máquina (localhost) y escuchando en el puerto 1212 que es donde por defecto escucha simulavr.
Lanzamos avr-gdb (a mi en especial me gusta mas avr-gdbtui) de la siguiente manera:

  1. yo@miMaquina:~$ avr-gdbtui -x comandos.gdb

siendo comandos.gdb el archivo en el que guardamos los comandos anteriores.
Podremos ver como inmediatamente cambian los valores de los registros mostrados en simulavr y avr-gdb queda parado en el main de nuestro programa. Ésto sucede porque nuestro programa empieza a simularse inmediatamente después de lanzar el depurador y para en el primer breakpoint que encuentra, que se encuentra en main (breakpoint por defecto). Cómo antes de eso se tiene que ejecutar código (arranque del micro, configuración de la pila, registros de configuración iniciales, inicialización de memoria, etc….) vemos los cambios en los registros de la máquina reflejados en simulavr. Ahora ya podremos simular/depurar como se hace usualmente con gdb.

¡¡Ale, a disfrutarlo :-D !!

En el futuro…

Actualmente también me estoy peleando con avarice que permite borrar, programar, leer, verificar y depurar “on board” muchos dispositivos de la familia AVR utilizando un AVR JTAG ICE y avr-gdb o insight. En cuanto sea capaz de terminar de hacerlo funcionar, prometo explicarlo en otra recetilla. Mientras tanto a esperar pacientemente jugueteando con todo lo que ya tenemos sobre AVR….

Referencias

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

openWrt: Cómo compilar aplicaciones para la fonera

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

Hace poco compré una segunda fonera (la primera que compré la uso de router en casa) con el ánimo de poder hacer cosillas medio interesantes con ella. Al fin y al cabo, es un dispositivo empotrado con un GNU/Linux dentro, así que habrá que estrujarlo ¿no?

Bueno, las especificaciones de la fonera se pueden encontrar en el siguiente enlace (no me voy a poner a reescribir lo ya escrito o poner fotitos de la fonera, hay mil por ahí).

Así pues, empecemos. Lo primero que hay que hacer, evidentemente, es flashear nuestra fonera e instalarle una versión de OpenWrt no modificada por los chicos de Fon. En Crysol se puede encontrar un manual muy bueno de como hacerlo, aunque yo personalmente lo hice utilizando un puerto serie (también hay recetazo en Crysol). Puedes utilizar cualquier imagen de OpenWrt en la página de descargas de OpenWrt, aunque yo explicaré como compilar una imagen para OpenWrt en nuestra máquina para cargarla después en la fonera.

Descargando Fuentes

Los fuentes de OpenWrt los podemos descargar del repositorio subversion del proyecto tal que así:

  1. svn co https://svn.openwrt.org/openwrt/trunk

Una vez descargado el repositorio, vamos a crear un paquete para que se compile junto con el resto de la imagen. Después se verá como compilar los paquetes de forma independiente al resto de la imagen. Empecemos.

Paquete holamundo

Como ejemplo he escrito el típico holamundo en C y he hecho un Makefile para su compilación con una sola regla. El código necesario para el ejemplo puede descargarse a través de subversion:

  1. svn checkout http://gn-cillos.googlecode.com/svn/trunk/fon-develop gn-cillos-read-only

Se puede observar que se encuentra el directorio con la aplicación holamundo y un archivador tar.gz con el mismo directorio ya comprimido. Ésto no tiene mucho que comentar, por lo que lo obviaré.

Lo importante aquí es el archivo Makefile que nos encontramos en el nivel superior. Éste Makefile es el utilizado para generar el paquete durante el proceso de compilación de la imagen OpenWrt.

  1. #
  2. # Copyright (C) 2006-2008 OpenWrt.org
  3. #
  4. # This is free software, licensed under the GNU General Public License v2.
  5. # See /LICENSE for more information.
  6. #
  7. # $Id$
  8.  
  9. include $(TOPDIR)/rules.mk
  10.  
  11. PKG_NAME:=holamundo
  12. PKG_VERSION:=0.1.1
  13. PKG_RELEASE:=1
  14. PKG_MD5SUM:=19a4d8bdc9bc7add4b3668969a546b68
  15. PKG_SOURCE_URL:=http://localhost
  16. PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
  17. #PKG_CAT:=zcat
  18.  
  19. PKG_BUILD_DIR:=$(BUILD_DIR)/holamundo-$(PKG_VERSION)
  20.  
  21. include $(TOPDIR)/rules.mk
  22. include $(INCLUDE_DIR)/kernel.mk
  23.  
  24. PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
  25.  
  26. include $(INCLUDE_DIR)/package.mk
  27.  
  28. define Package/holamundo
  29. SECTION:=Toys
  30. CATEGORY:=Misc
  31. TITLE:=A gadget to print greetings. Spanish flavour.
  32. URL:=http://localhost
  33. endef
  34.  
  35. define Package/holamundo/Default
  36. URL:=http://localhost
  37. endef
  38.  
  39. define Package/holamundo/description
  40. A message for help in menuconfig: You can write your help message here.
  41. endef
  42.  
  43. define Build/Prepare
  44. $(call Build/Prepare/Default)
  45. endef
  46.  
  47. define Build/Compile
  48. $(MAKE) -C $(PKG_BUILD_DIR)/src \
  49. $(TARGET_CONFIGURE_OPTS) \
  50. LD="$(TARGET_CC)" \
  51. PROGRAMS="holamundo" \
  52. MULTI=1 SCPPROGRESS=1
  53. endef
  54.  
  55. define Package/holamundo/install
  56. $(INSTALL_DIR) $(1)/usr/sbin
  57. $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/holamundo $(1)/usr/sbin/
  58. endef
  59.  
  60. $(eval $(call BuildPackage,holamundo))

La primera parte del Makefile exporta la variable de entorno TOPDIR, utilizada para poder localizar todos los directorios con librerías y fuentes a partir de ahí. Después viene la declaración de variables de entorno para el Makefile. Las mas importantes son:

  • PKG_NAME: Indica el nombre que tendrá el paquete. En nuestro caso, holamundo.
  • PKG_VERSION: Versión del paquete.
  • PKG_MD5SUM: Suma md5 del archivador con los fuentes del paquete a generar. Si tenemos ésta suma, la ponemos ahí, para comprobar que hemos descargado correctamente los paquetes de fuentes. Si el archivador lo hemos generado nosotros, con el comando md5sum podremos obtener fácilmente dicha suma.
  • PKG_SOURCE_URL: Máquina en la que estará alojado el paquete con los fuentes. Generalmente se utiliza un protocolo http para obtener dichos fuentes (el Makefile los obtiene automáticamente con wget).
  • PKG_SOURCE: Nombre del archivador con los fuentes a descargar. En nuestro caso, el archivador es holamundo-0.1.1.tar.gz, que contiene los fuentes de la aplicación que queremos compilar. Éstos fuentes pueden ser parcheados si lo deseamos, también de forma automática, aunque aquí no se ve dicho caso. Para ello, se puede consultar la documentación sobre OpenWrt que puede verse en las Referencias.
  • PKG_CAT: Tipo de archivador descargado. Por defecto se tratará de un tar.gz.
  • PKG_BUILD_DIR: Directorio temporal el en que se compilarán los fuentes. Es muy aconsejable que dicho directorio se encuentre dentro de $(BUILD_DIR)
  • PKG_SOURCE: Archivador que contiene los fuentes ya descargados.

El archivador con los fuentes del paquete que se desea compilar debe encontrarse en una máquina accesible por wget (localhost también vale, evidentemente). Supongo que debe de haber alguna forma de decirle que los fuentes ya se encuentran descomprimidos en algún lugar y no hace falta descargarlos y tal, pero como a mi personalmente, ésta manera me viene bien, no me he preocupado en mirar mas.

Bueno, después de declarar éstas variables, se definen las secciones que componen el Makefile.
define Package/holamundo, define Package/holamundo/Default y define Package/holamundo/description contienen información para el menú de configuración (se verá mas adelante). El hecho de escribir Package/nombre_paquete/opcion en lugar de Package/opcion, como parecería mas lógico, es debido a que en un mismo Makefile podemos compilar varios paquetes, en caso de que existan dependencias (por poner un ejemplo), y de ésta forma se pueden definir distintas acciones para cada uno de ellos.

Build/Prepare se encarga de “preprocesar”, por así decirlo el paquete antes de compilarlo. Aquí en realidad es donde se descarga el paquete, se descomprime y tal. Todo ésto es llevado a cabo por el script Build/Prepare/Default, que es llamado automáticamente. Supongo que aquí es donde si quieremos, podemos decirle que lleve a cabo otras acciones (en lugar de descargar el paquete con wget, descomprimirlo, etc) o incluso dejarlo vacío si no queremos que haga nada.

Build/Compile se encarga de compilar el paquete y generar el ejecutable. Aquí es importante decir que en el Makefile del paquete a compilar (no en éste), debe utilizarse la variable CC para definir al compilador, ya que nuestro Makefile guardará ahí una referencia al compilador cruzado que se utiliza para compilar. En PROGRAMS indicamos qué paquetes (programas) se compilarán. Como en éste caso solo tenemos holamundo, pues lo escribimos y fuera.

Finalmente, en Package/holamundo/Install se definen las reglas para generar el paquete una vez compilado todo el código. En realidad se definen en qué directorio de la máquina destino se descomprimirá el paquete y cada uno de los archivos que forman dicho paquete (podriamos tener varios archivos de configuración o inicialización que irían a distintos subdirectorios dentro de la máquina). Opcionalmente, también se podrían escribir comandos que serían generados tras instalar.

Después de definido todo ésto, es cuando se lleva a cabo el proceso de compilación propiamente dicho, llamando al script BuildPackage. Éste script deberá llamarse una vez por cada paquete que vaya a ser generado.

Bueno, pues básicamente éste es el Makefile necesario para crear un paquete con la toolchain de OpenWrt. Éste makefile habrá que guardarlo dentro de la ruta package/holamundo
y desde ahí se compilará.

Configurando el Núcleo

Bueno, pues ya tenemos el Makefile correspondiente al paquete que queremos generar. Ahora, generaremos una Imagen nueva de OpenWrt y además, también compilaremos el SDK para compilar aplicaciones para ése núcleo (en realidad, el SDK para compilar aplicaciones compatibles con un núcleo determinado no tiene que ser tan específico, basta con elegir uno de la página de descargas que soporte nuestro micro. Por tanto, éste paso te lo puedes saltar si quieres).

ejecutando

  1. make menuconfig

se abre la ventana de configuración del firmware, donde podemos encontrar entre otras opciones, la posibilidad de compilar nuestro paquete, que como configuramos anteriormente, se encuentra en la sección misc, como puede verse aquí:

Opciones de configuración de OpenWrt
y aquí puede verse el menú de ayuda:

Menu de ayuda

Compilando

Una vez preparado todo, ya solo falta compilar:

  1. make

Así de fácil. Ahora ya solo falta esperar a que el proceso concluya. Dependiendo de tu máquina (y de tu velocidad de conexion a internet), ésto puede llevar mas o menos tiempo. Un café y/o un par de capítulos de tu serie favorita te ayudadarán a mitigar la espera…

Hay que decir que la compilación tardará mas o menos tiempo dependiendo de los paquetes que configures para que sean instalados con tu “distribución” de OpenWrt. En la mayoría de los casos, (como es el caso de nuestro paquete holamundo), los fuentes deben ser descargados de la red y sus parches también, por lo que el proceso puede tardar bastante tiempo.

Terminando la compilación
Una vez que ha finalizado la descarga, podemos ir al directorio bin. Allí nos podremos encontrar con lo siguiente:

    • openwrt-atheros-vmlinux.lzma y openwrt-atheros-root.squashfs: Imágenes para cargar en tu fonera con el firmware compilado.
      OpenWrt-SDK-atheros-for-Linux-i686.tar.bz2: Toolchain comprimida para compilar aplicaciones para ésa versión del firmware.
  • En realidad, si lo único que quieres hacer es compilar aplicaciones para tu fonera sin historias raras (historias raras == tener que compilarte tu propia imágen del sistema), todo lo anterior, a excpeción del Makefile no es necesario, pues te puedes bajar la toolchain ya precompilada, no siendo muy crítico el hecho de utilizar la de una versión del firmware para compilar aplicaciones en otra versión.

    Compilando con la toolchain

    Pues una vez que hemos descomprimido nuestra toolchain y hemos cambiado la ruta de TOPDIR a la nueva ruta en la que se encuentra la toolchain, procedemos a compilar nuestro paquete holamundo.
    Lo primero que hacemos, es crearnos, dentro del directorio package un subdirectorio llamado holamundo donde copiaremos nuestro famoso Makefile. Vamos, ésto es igual que cuando compilamos la imagen de firmware. En realidad, para compilar la imágen del firmware no era necesario crear el paquete holamundo, yo lo hice ahí para que fuese un poco mas ilustrativo, pero nos podíamos haber saltado el paso, como bien he dicho antes, y generar (o descargar) una toolchain ya compilada.
    Una vez que ya tenemos el subdirectorio listo, desde el raíz invocamos la compilación:

    1. make package/holamundo/compile

    Et voilá, se pondrá a compilar nuestro holamundo.
    Si queremos ver el proceso con mas detalle:

    1. make package/holamundo/compile V=99

    Y también podemos hacer cosas como

    1. make package/holamundo/clean

    o

    1. make package/holamundo/prepare

    Bueno, pues después de haber compilado nuestro queridísimo holamundo, lo podremos ver en bin/packages/target-mips_uClibc-0.9.29/:

    1. yo@miMaquina:~/toolkits/fonera/OpenWrt-SDK-atheros-for-Linux-i686$ ls -l  bin/packages/target-mips_uClibc-0.9.29/
    2. total 4
    3. -rw-r–r– 1 yo yo 1859 abr 30 03:01 holamundo_0.1.1-1_mips.ipk

    Y ahí está nuestro paquetito ipk listo para ser instalado, aunque éso ya será otra historia, que ahora es tarde…. :-P

    Lo próximo…

    Bueno, pues ahora falta configurar un servidor para que nos sirva, en forma de mirror, los paquetes ipk que creemos y configurar ipkg en nuestra fonera para poder instalar los paquetes de forma automágica….

    No tardaremos ;-)

    Referencias

    Luego las pongo, que ahora ando cansaote….