3 sustituciÓn de ordenador de a bordobibing.us.es/proyectos/abreproy/70354/fichero... · capítulo...
TRANSCRIPT
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
22
3 SUSTITUCIÓN DE ORDENADOR DE A BORDO
3.1 Elección de la nueva placa En un primer momento, tras la avería de la antigua placa Hercules EBX de Diamond Systems, se
pensó en adquirir la nueva versión de la misma placa. No obstante, a pesar de ser una nueva
versión, esta placa estaba a punto de descatalogarse. Además, contaba con una serie de
características (como el número de entradas/salidas) que superaba nuestras necesidades, un
tamaño y consumo elevados y un precio bastante superior al de otras opciones que tanteamos
en el mercado (alrededor de 600 €). Por estos motivos se decidió reemplazar la Hercules EBX
por otro modelo que superase sus prestaciones.
Las necesidades de que partíamos eran función de los sensores ya desarrollados, del modelo
de helicóptero comercial de que se disponía y de los objetivos planteados:
Microprocesador lo bastante potente para realizar todos los cálculos y muestreos
precisos bajo fuertes restricciones de tiempo.
Bajo consumo, puesto que durante el vuelo se emplearán baterías.
Al menos, dos entradas analógicas (para los dos potenciómetros), dos entradas y una
salida digitales (para los sensores de ultrasonidos y óptico) y un puerto serie (para la
IMU).
Conexión Wi-Fi. La antigua placa únicamente contaba con conexión Ethernet, lo que
obligaba a la utilización de un puente Ethernet para las comunicaciones con Tierra que
incrementaba el peso y el consumo.
Salidas PWM. Aunque es posible generar señales PWM con dispositivos adicionales, lo
ideal sería encontrar una placa que disponga, al menos, de 5 salidas PWM (una para
cada servo).
Tras una intensa búsqueda de nuevos modelos, nos quedamos con cuatro placas candidatas a
reemplazar a la Hercules EBX. En la Tabla 1 se muestra una comparativa con las características
de las candidatas que más nos convencieron a priori.
Características Helios (Diamond
Systems) PCM-9361
(Advantech)
TS-7800 (Technologic
Systems)
BL5S220 (Rabbit)
Procesador 300/800 MHz Vortex86DX
Intel Atom N270 1.60 GHz
Marvell 500MHz ARM9
Rabbit® 5000 at 73.73 MHz
Consumo 300MHz: 3.5W
800MHz: 5.4W
5 V-> 0.07 A
12 V-> 1.90 A 4W 9 W (máx.)
Wi-Fi No No No Sí
Dimensiones 90 x 96 mm 146 x 102 mm 90.17 × 95.89 mm 90 x 96 x 15 mm
Peso 70.8 g 0.85 kg ? 70.8 g
Puertos USB 4 5 2 ?
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
23
Puertos Serie 2 2 2 2
E/S digitales y analógicas
40 E/S digitales
16/8 analógicas
8-bit GPIO 110-bit GPIO 40 E/S digitales
8 E, 2 S analóg.
Salidas PWM Ninguna Ninguna Ninguna Ninguna
Precio 966 € 231 € 163 € 199 €
Tabla 1: Placas candidatas a suplir a la Hercules EBX
Lo primero que concluimos al observar la Tabla 1 es la escasez de salidas PWM que
presentaban las placas de los fabricantes más importantes. Esta circunstancia nos llevó a la
decisión de emplear el dispositivo Mini Maestro 12-channel USB Servo Controller de Pololu,
encargado de enviar a los servos señales PWM a partir de las posiciones deseadas indicadas
desde el PC, que veremos en el Apartado 6.2 .
En cuanto a la elección de la placa, aunque en principio la TS-7800 no contaba ni con conexión
Wi-Fi ni con entradas analógicas, el fabricante ofrecía la opción de adquirir un adaptador Wi-Fi
por USB por 25€ más, así como un periférico PC/104 para conversión ADC por otros 42€. Con
estos dos elementos adicionales cubríamos por completo nuestras necesidades iniciales.
Además, el fabricante Technologic Systems ofrecía un kit de desarrollo por 71€ más que nos
pareció de gran interés, puesto que consistía en una tarjeta SD de 2GB con el kernel 2.6 y un
Debian Linux, ambos ya compilados para la placa, lo que seguramente nos ahorraría algunas
semanas de trabajo.
Todos estos motivos, más la constatación de que Technologic Systems contaba con un foro de
desarrolladores al que poder recurrir en un futuro, nos llevaron a inclinarnos finalmente por el
PC embebido TS7800.
Figura 24: TS7800 de Technologic Systems
Conector DB9
Conector USB
Conector
Ethernet
Ranura
tarjeta SD
DIOs
Conector
PC104 Alimentación
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
24
3.2 TS-7800 de Technologic Systems
3.2.1 Características La TS-7800 de Technologic Systems es una placa computadora o SBC (Single Board Computer)
que sigue la directiva RoHS basada en una CPU ARM9 de Marvell a 500MHz con bus PCI
interno, y que provee de un conjunto estándar de periféricos de a bordo como Gigabit
Ethernet, SATA dual y USB 2.0 maestro/esclavo dual de alta velocidad.
La TS-7800 también ofrece una FPGA de Lattice Semiconductor con LUT de 12000 puertas
lógicas programable mediante software Linux y que provee de periféricos extras, tales como
110 líneas GPIO y puertos serie adicionales.
En cuanto al software, la TS-7800 utiliza un kernel 2.6 de Linux mejorado por Technologic
Systems, que permite arrancar en 0.69 s y provee soporte para drivers para todo el hardware
de a bordo. Además, la memoria flash de 512 MB de a bordo permite la instalación de una
distribución Debian completa con un entorno de desarrollo incorporado.
La siguiente lista resume las características más destacables de la TS-7800:
CPU ARM9 a 500Mhz
Bus PCI interno, conector PC/104
FPGA programable con LUT de 12000 puertas lógicas
DDR-RAM de 128MB
Memoria Flash NAND de 512MB, alta velocidad (17MB/s)
Ranura para SD (1 Micro SD, 1 SD de tamaño completo)
Puertos SATA
2 puertos USB 2.0 maestro/esclavo a 480Mbps
Gigabit Ethernet, velocidades 10/100/1000
Canales ADC de 10 bits
10 puertos serie, 2 puertos RS-485 opcionales
110 GPIO (86 asociadas al bus PC/104)
Interfaces para teclado matricial y LCD alfanumérico
Operación entre -20° y +70°C sin ventilador
Bajo consumo: 4W@5V
Consumo de 200 microamperios en modo suspensión
Arranque al prompt del shell de Linux en 0.69 segundos
Ejecuta kernel 2.6 y Linux Debian por defecto
3.2.2 Primeras pruebas
3.2.2.1 Tipos de arranque
El primer aspecto de la nueva placa con el que nos tuvimos que familiarizar fue el de los dos
tipos de arranque que presenta:
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
25
El primero de ellos (por defecto) dura menos de 1.1 segundos, y se realiza desde la
memoria flash de a bordo. Mediante este arranque sólo tenemos acceso a las
utilidades estándares más comunes a través del programa Busybox.
Si tecleamos exit, la placa trata de realizar un arranque desde la partición 4 de la
tarjeta SD o bien desde la Micro SD.
La SD proporcionada por Technologic Systems contiene una versión completa de la
distribución Debian de Linux compilada específicamente para la placa y contiene Apache, SSH,
PPP, servidor FTP y otras utilidades y librerías de uso común, por lo que será el entorno en que
trabajemos habitualmente. Para evitar el arranque rápido por defecto no tenemos más que
escribir el siguiente comando en el shell:
ln -sf /linuxrc-sdroot /linuxrc; save
El archivo /linuxrc es un script del shell, que es lo primero que el kernel ejecuta al
encender la placa. Con la creación de este enlace simbólico entre archivos configuramos la
placa para arrancar directamente la versión de Linux instalada en la SD.
3.2.2.2 Comunicaciones
Conexión mediante medio físico
La TS-7800 no tiene salida de vídeo, teclado o ratón, sino que está pensada para ser utilizada
como una consola a la que se accede desde un PC con un emulador de terminal. La conexión a
la TS-7800 desde el PC remoto puede realizarse bien mediante el cable null-modem incluido en
el Development Kit de la placa o bien mediante Telnet con un cable Ethernet.
Para ambos tipos de conexión podemos hacer uso del programa PuTTY, emulador de terminal
que permite conectar máquinas remotas y ejecutar programas a distancia. PuTTY se conecta
como cliente a múltiples protocolos, como SSH, Telnet o Rlogin.
La configuración inicial de la TS-7800 se hará conectándonos mediante cable Ethernet. Una vez
finalizada dicha configuración, tendremos la Wi-Fi habilitada, por lo que podremos prescindir
del cable.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
26
Figura 25: Emulador de terminal Putty
Configuración Wi-Fi
El driver especificado por el fabricante para poder utilizar el adaptador Wi-Fi por USB, el
ZD1211RW, ya viene instalado en la tarjeta SD, por lo que no hay más que dar los pasos
habituales para conectarse a una red Wi-Fi en Debian:
1. Averiguamos qué unidad está ligada al dispositivo inalámbrico.
>> iwconfig
2. Escaneamos las redes inalámbricas dentro de nuestro alcance.
>> iwconfig eth1 scanning
3. Editamos el fichero /etc/network/interfaces tal como muestra la Tabla 2:
…
iface eth1 inet static
address 192.168.123.52
network 192.168.123.0
netmask 255.255.255.0
broadcast 192.168.123.255
gateway 192.168.123.1
wireless-essid waplpb
wireless-channel 6
wireless-key off
wireless-mode auto
wireless-rate auto
wireless-keymode open
…
Tabla 2: Fichero /etc/network/interfaces
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
27
4. Indicamos el ESSID de la red WiFi a la que queremos que se conecte por defecto
(“waplbp”), así como la dirección IP deseada para la interfaz (192.168.123.52).
>> ifconfig eth1 up
>> iwconfig eth1 essid waplpb
>> ifconfig eth1 192.168.123.52
5. Actualizamos los cambios.
>> /etc/init.d/networking restart
Conexión a Internet
Para completar la configuración de las conexiones de la TS-7800, especificamos el proxy y las
DNS para lograr la conexión a Internet mediante las dos acciones siguientes:
Escribir en línea de comandos la dirección del proxy (172.1.0.15).
>> export http_proxy=http://172.1.0.15
Incluir en el fichero /etc/resolv.conf tantas líneas como DNS queramos
especificar (150.214.186.69, 150.214.186.69).
nameserver 150.214.186.69
nameserver 150.214.186.69
3.2.2.3 Prueba de las entradas y salidas digitales
La comunicación con los sensores de ultrasonido y óptico se realiza a través de las DIOs de la
TS-7800. Para su conexión física debemos atender al conector de las DIOs que ilustra la Tabla
3.
Tabla 3: Conector de las DIOs
Para la lectura y escritura de los pines de las DIOs se dispone de los primeros 16 bits de los
registros de entrada (ubicado en la dirección física 0xe8000004 de la FPGA) y salida (ubicado
en la dirección física 0xe8000008 de la FPGA). Si escribimos un 0 en uno de los bits del registro
0xe8000008, lo ponemos a nivel bajo. Si, por el contrario, escribimos un 1, pondremos el
correspondiente pin en estado de alta impedancia; esto es, estaremos sacando un 1, pero a la
vez podremos utilizar el registro 0xe8000004 para leer los valores que esté recibiendo como
entrada.
Vemos un ejemplo con la DIO 01:
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
28
Si multiplicamos el valor del registro 0xe8000008 por la cifra hexadecimal 0xFFFE,
estaremos sacando un nivel bajo por la DIO 01.
Si sumamos 0x01 al valor del registro 0xe8000008, lo ponemos en estado de alta
impedancia; así, a la vez que generamos un 1 como salida, podemos leer el contenido
del bit 0 del registro 0xe8000004 para saber qué niveles de tensión estamos
recibiendo.
Utilidad peekpoke
La utilidad peekpoke puede utilizarse para acceso directo a las direcciones de memoria. Está
especialmente diseñada para tareas de depuración hardware de bajo nivel.
Si queremos reproducir sobre la placa el ejemplo anterior no tenemos más que escribir las
siguientes órdenes en el prompt:
Sacar un nivel bajo por la DIO 01:
>> poke16 0xe8000008 0x0
Leer el valor digital a la entrada del DIO 01:
>> poke16 0xe8000008 0x1
>> peek16 0xe8000004
Código en C para lectura/escritura de una DIO
El código que aparece en la Tabla 4 muestra la sección de un programa en C que se encarga de
leer el valor de entrada al pin 1 de las DIOs.
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
…
#define BASE7800 0xE8000000
…
volatile unsigned int *DATAIN, *DATAOUT;
unsigned short int state;
unsigned char *start;
…
int fd = open("/dev/mem", O_RDWR|O_SYNC);
start = mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd,
BASE7800);
DATAIN = (unsigned int *)(start + 0x04); //registro de datos de
entrada
DATAOUT= (unsigned int *)(start + 0x08); //registro de datos de salida
*DATAOUT=0xffff; //Configuramos los 16 pines como entrada
state = *DATAIN; // Lectura del estado de las DIOs
printf("%X",state); //Impresión en hexadecimal de la lectura de los 16
bits
close(fd);
…
Tabla 4: Código C para lectura de pin 1 de las DIOs
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
29
En la Tabla 5 aparece un ejemplo de programación en C para sacar un valor digital bajo por el
pin 1 de las DIOs durante un segundo y un valor digital alto durante el siguiente segundo.
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
…
#define BASE7800 0xE8000000
…
volatile unsigned int *DATAIN, *DATAOUT;
unsigned short int state;
unsigned char *start;
…
int fd = open("/dev/mem", O_RDWR|O_SYNC);
start = mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd,
BASE7800);
DATAIN = (unsigned int *)(start + 0x04); //registro de datos de
entrada
DATAOUT= (unsigned int *)(start + 0x08); //registro de datos de salida
*DATAOUT=*DATAOUT & 0xFFFE; //Sacamos nivel bajo por pin 1 del DIO
header
printf("\nNivel bajo en pin 1 del DIO durante 1 segundo...");
sleep(1);
*DATAOUT=*DATAOUT | 0x1; //Sacamos nivel alto por pin 1 del DIO header
printf("\nNivel alto en pin 1 del DIO durante 1 segundo...");
sleep(1);
…
close(fd);
…
Tabla 5: Código C para escritura en pin 1 de las DIOs
3.2.2.4 Pruebas de la comunicación por puerto serie
La IMU se conecta al segundo de los puertos serie que la placa trae habilitados por defecto
(COM2). La única dificultad que tuvimos al principio es que el puerto COM2 tenía activado el
programa getty, que se encarga de manejar el proceso de login cuando accedemos a un PC
Unix. Esto daba lugar a una comunicación nula través del puerto COM2 con la IMU.
Mediante la supresión de la línea del fichero /etc/inittab que activaba el getty logramos
la conexión con la IMU de forma inmediata.
3.3 TS-9700 de Technologic Systems La TS-9700 es una placa periférica con conexión al bus PC/104 que provee de puertos ADC y
DAC para la adquisición de datos en aplicaciones analógicas. Ya explicamos anteriormente que
su uso era necesario para poder leer con la TS-7800 los valores de altitud y paso colectivo
dados por los potenciómetros.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
30
Figura 26: TS-9700 de Technologic Systems
Para el acceso a los periféricos conectados al bus PC/104 de la TS-7800 se toma la dirección de
memoria base del bus (0xEE000000) y se añade el offset correspondiente al periférico en
cuestión. En el caso de la TS-9700, se ubica a partir de las direcciones de memoria resultantes
tras sumar 0x160 a la dirección base del bus. La Tabla 6 muestra el mapa de registros de la TS-
9700.
Tabla 6: Mapa de registros de la TS9700
Cualquier escritura en el A/D Command Register inicia una conversión A/D de 12 bits que se
almacena en los registros A/D LSB y A/D MSB.
Atendiendo, además, a la Tabla 7, observamos que para realizar una conversión A/D nos
tendremos que preocupar de:
1. Seleccionar cuál de los 8 canales de entrada a las TS-9700 queremos convertir,
haciendo uso de los bits 0-2 del A/D Command Register.
Conector
PC104
Entradas
CAD
Jumpers
selección
rango CAD
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
31
2. Consultar el bit 7 de dicho comando para ver si está a 1, lo que significa que la placa
está preparada para realizar la conversión.
3. Leer los registros A/D MSB y A/D LSB para obtener el resultado de la conversión (los
cuatro bits superiores del A/D MSB siempre estarán a 0).
Tabla 7: Significado de los bits del A/D Command Register de la TS-9700
Por último, cabe aclarar que la placa dispone de tres jumpers para configurar el rango de la
señal de entrada en cada canal.
Para seleccionar un rango de entrada 0-10 V, sólo debe instalarse el jumper C.
Para seleccionar un rango de entrada 0-2.5 V, puede o bien instalarse sólo el jumper A
o bien no instalarse ninguno.
Para seleccionar un rango de entrada 0-20 mA, los jumpers A y B deben instalarse.
3.3.1 Código en C para conversión A/D de un canal La sección de código de la Tabla 8 muestra un ejemplo de conversión A/D de la tensión a la
entrada del canal 1 de la TS-9700 en el rango 0-2.5 V.
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
…
#define BASE7800 0xE8000000
#define ADBASE 0x160 //Dirección base registro de control de la
TS-9700
#define ADREADYBIT 7 //7º bit del registro de control indica si se ha
completado una conversión
#define CHANNEL1 0 //Canal de la TS9700 a convertir
#define BASE 0xEE000000 //Dir. base periféricos conectados al bus
PC/104
…
unsigned char *base;
unsigned char *start;
volatile unsigned int *REG1, *REG2, *REG3, *REG4;
unsigned char *adcommand; //Puntero a la dirección base del registro
de control de la TS9700
double voltage; //Tensión obtenida de entrada analógica conectada al
potenciómetro
volatile unsigned char *msb;
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
32
volatile unsigned char *lsb;
…
//====================================================================
//Activación de la funcionalidad ISA del bus PC/104
//====================================================================
fd = open ("/dev/mem" , O_RDWR|O_SYNC);
if (fd < 0){
perror ("open");
exit(1);
}
start = mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd,
BASE7800);
REG1=(unsigned int *)(start + 0x30);
REG2=(unsigned int *)(start + 0x34);
REG3=(unsigned int *)(start + 0x38);
REG4=(unsigned int *)(start + 0x3C);
*REG1 = 0x55555555;
*REG2 = 0x55555555;
*REG3 = 0x55555;
*REG4 = 0x55555;
//Dirección base de la TS-9700 vista desde la TS-7800
base = (unsigned char *)mmap(0, getpagesize(),PROT_READ | PROT_WRITE,
MAP_SHARED, fd, BASE);
if (base == MAP_FAILED){
printf("Error de proyeccion del fichero de configuracion de la
TS9700 \n");
return 0;
}
adcommand = base + ADBASE; //Dirección del registro de control de la
TS9700 conectada al bus PC/104
lsb = base + ADBASE +2; //Dirección de memoria del byte menos
significativo de la conversión A/D
msb = base + ADBASE + 3; //Dirección de memoria del byte más
significativo de la conversión A/D
*adcommand = CHANNEL1; // Los tres primeros bits del registro de
control contienen el nº del canal a convertir
while(!(*adcommand & (1 << ADREADYBIT))) { //Si el bit 6 del registro
de control se pone a "1" se pueden leer lsb y msb
}
//Resultado de la conversión
voltage = (256.0 * *msb + *lsb) * 2.5 / 4096.0;
…
close(fd);
…
Tabla 8: Código en C para conversión A/D del canal 1 de la TS-9700
3.4 Migración de la antigua aplicación El principal problema que se presentó en la fase de migración de la aplicación a la nueva placa
fue la ausencia de un driver específico como el que estaba implantado en la Hercules para el
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
33
uso y control tanto de la placa como de sus puertos y las funciones más útiles. Esta
circunstancia nos obligó a recurrir a funciones estándares de lectura y escritura de registros,
programación de interrupciones temporales, temporización, etc.
A continuación detallamos algunas de las modificaciones y sustituciones que hubo que realizar
en el código para trasladar la aplicación original a la TS-7800.
3.4.1 Funciones de inicialización Como ya hemos comentado, la antigua Hercules EBX disponía del driver DSCud 5.7, que
facilitaba el control de sus funcionalidades. La primera modificación del código de la aplicación
de a bordo está relacionada con las funciones de inicialización del propio driver (dscInit()) y de
la placa (dscInitBoard()). La nueva placa TS-7800 no precisa de ningún tipo de inicialización
para poder trabajar con ella, por lo que bastará con eliminar dichas funciones del programa.
3.4.2 Supresión de tensiones de salida para alimentación de sensores En la antigua aplicación, los potenciómetros se alimentaban a 10 V mediante las salidas
analógicas de la Hercules, lo que requería la gestión de dichas salidas por código. En la fase de
migración a la TS-7800 se pensó que era más conveniente alimentar los potenciómetros
directamente mediante baterías1, por lo que las secciones de código encargadas de la
alimentación fueron suprimidas.
Así, las funciones dscDASetSettings()(destinada a la configuración de la conversión D/A) y
dscDAConvert()(destinada a la conversión D/A de un canal dado) han dejado de utilizarse en
la aplicación de a bordo, sin necesidad de ser sustituidas por ninguna otra. Los potenciómetros
van a pasar a alimentarse a 5 V mediante las baterías encargadas de alimentar la TS-7800.
3.4.3 Funciones de lectura/escritura digital Las funciones de lecturas y escrituras digitales del driver DSCud de la antigua aplicación de a
bordo han sido sustituidas por los nuevos procedimientos explicados en el Apartado 3.2.2.3.
De esta manera hemos prescindido de las funciones dscDIOSetConfig() (para configuración del
puerto DIO correspondiente), dscDIOInputBit() (que recibe un valor de bit desde un puerto de
entrada digital), dscDIOSetBit (que escribe un 1 en un bit especificado de un puerto), y
dscDIOClearBit (que escribe un 0 en un bit especificado de un puerto).
3.4.4 Funciones de lecturas analógicas En la antigua aplicación se empleaban las funciones dscADSetSettings()para la configuración
de los parámetros de conversión A/D de la entrada analógica, dscADSample()para la
conversión A/D y dscADCodeToVoltage()para el paso de unidades de conversión A/D que
utiliza la placa a niveles de tensión.
A partir de ahora, este conjunto de funciones son sustituidas por las que aparecen en el
ejemplo de lectura analógica de la Tabla 8.
1 El diseño del nuevo sistema de alimentación ha corrido a cargo de Javier Galnares Arias, a excepción de
la elección de las nuevas baterías, proceso que se detalla en el Capítulo 5.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
34
3.4.5 Funciones de recuento de tiempo En la antigua aplicación se había recurrido al siguiente procedimiento para realizar recuentos
de tiempo:
Se generaba una señal PWM de frecuencia conocida.
Se contaba el número de pulsos de dicha señal PWM entre el principio y el fin del
periodo de tiempo cuya duración se quería conocer.
En la fase de migración a la TS-7800, se creyó conveniente emplear la función de lectura de
tiempo gettimeofday() de la librería de tiempo <sys/time.h>. Esta función devuelve la hora
actual codificada en una estructura de tipo timeval, que contiene dos campos:
El campo tv_sec contiene los segundos transcurridos desde el Epoch Unix.
El campo tv_usec contiene los microsegundos transcurridos desde el Epoch Unix.
El procedimiento que seguiremos será el mostrado en la Tabla 9, mediante el cual
obtendremos el tiempo transcurrido entre dos puntos de ejecución del código pasando la hora
en que se produce uno y otro a microsegundos y calculando su diferencia.
struct timeval now;
unsigned long t_1, t_2;
…
gettimeofday(&now,NULL);
t_1 = now.tv_sec * 1000000 + now.tv_usec;
…
// Acciones cuya duración se quiere medir
…
gettimeofday(&now,NULL);
t_2 = now.tv_sec * 1000000 + now.tv_usec;
printf("Tiempo invertido %d\n",(t_2-t_1));
…
Tabla 9: Código C para medir tiempo transcurrido entre dos instantes de tiempo
3.4.6 Funciones de programación de interrupciones periódicas Las interrupciones periódicas de usuario se programaban en la Hercules con las funciones
dscUserInt() (programa el comienzo de una interrupción de usuario asociada a una función),
dscClearUserInterruptFunction() (desvincula la función asociada a la interrupción) y
dscCancelOp () (cancela la ejecución de todas las interrupciones periódicas programadas).
Ahora tendremos que recurrir al método especificado por POSIX mediante la utilización de
temporizadores y señales que se activan con su expiración, y el consiguiente manejador de la
alarma asociada a dicho evento. La Tabla 10 muestra un ejemplo de programación de una
interrupción cada segundo.
…
struct itimerval it;
…
int main( void )
{
it.it_value.tv_sec = 1; // start in 1 second
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
35
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 1; // repeat every second
it.it_interval.tv_usec = 0;
arranca();
…
}
...
//Manejador de interrupciones temporales
void sigalrm_handler(int sig)
{
//Función periódica
}
…
arranca (void)//Función que programa el arranque de las interrupciones
temporales
{
if (setitimer(ITIMER_REAL, &it, NULL)!=0)//Definición de los
parámetros del temporizador cuya expiración dará lugar a las
interrupciones
{
printf("Error en la configuracion del timer para las
interrupciones temporales\n");
return 0;
}
if(signal(SIGALRM, sigalrm_handler)==SIG_ERR) //Manejador de las
interrupciones temporales causadas por la finalización del
temporizador
{
printf("Error en la inicialización de las interrupciones
temporales\n");
return 0;
}
…
}
Tabla 10: Código C para programación de interrupciones temporales
3.5 Resolución de conflictos ocasionados por el cambio de placa Aunque han sido varios los problemas que hemos tenido que afrontar para poder conservar la
mayor parte de la aplicación existente y los sensores implementados, en esta sección
trataremos los dos más relevantes.
3.5.1 Modificación del modo de funcionamiento del sensor de
ultrasonidos El sensor de ultrasonidos empleado, el SRF05, consta de los 5 pines mostrados en la Figura 27,
cuyo significado aparece en la Tabla 11.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
36
Figura 27: Pinout del sensor de ultrasonidos modelo SRF05
Pin Función
+5 Vcc Tensión Positiva de Alimentación
ECO Salida del pulso cuya anchura determina el tiempo del recorrido de la señal ultrasónica.
Disparo Entrada de inicio de una nueva medida. Se aplica un pulso con una duración mínima de 10 us.
Modo (N.C.)
Sin conexión se selecciona el modo 1 de compatibilidad con SRF04.
Conectado a GND se selecciona el modo 2 de trabajo.
GND Tierra de alimentación.
Tabla 11: Significado de los pines del sensor de ultrasonidos modelo SRF05
Inicialmente, el SRF05 estaba configurado para funcionar con los pines de Eco y Disparo
unidos. Esta decisión se tomó para ahorrar conexiones en la antigua placa. Esto suponía que la
E/S digital correspondiente debía configurarse como salida, generar un nivel alto durante unos
microsegundos para activar el sensor, y, a continuación, debía funcionar como entrada para
comenzar a recibir el eco.
El problema surge con la utilización de la lógica triestado por la nueva placa. Ya hemos
explicado que para configurar un bit de las DIOs como entrada debemos escribir en él un 1
para que pase a estado de alta impedancia. Esto implica que, aunque estemos tratando dicho
bit como una entrada, en todo momento estamos sacando un nivel alto por él. Así pues, el
sensor de ultrasonidos se reinicia continuamente, y no somos capaces de recibir el eco.
Afortunadamente, el SRF05 consta de un modo de funcionamiento alternativo, con los bits de
Eco y Disparo separados, que nos permitió solventar este problema. Tal como indica la Tabla
11, para seleccionar este modo de funcionamiento no tenemos más que dejar sin conectar el
pin denominado Modo.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
37
3.5.2 Reconfiguración del timer del kernel para obtener mayor
granularidad Otro comportamiento anómalo que se detectó tras la migración de la aplicación a la nueva
placa fue que no se cumplían los periodos configurados para las interrupciones temporales ni
los tiempos especificados para dormir procesos con la función usleep().
Averiguamos que, por definición, la resolución máxima habilitada por defecto para los
temporizadores del kernel de Linux es de 10 milisegundos. Esta circunstancia, agravada por el
hecho de que el microprocesador tiene que atender a tareas más prioritarias que lanzar una
nueva interrupción temporal o despertar un hilo, nos obligó a reconfigurar el kernel que venía
por defecto en la tarjeta SD siguiendo las instrucciones especificadas por el fabricante. A
continuación resumimos los pasos que constituyen el proceso:
1. Loguearnos como root en un PC independiente de la TS-7800.
2. Instalarnos los paquetes libncurses5-dev y libncursesw5-dev para la compilación del
kernel.
>> sudo apt-get install libncurses5-dev libncursesw5-dev
3. Descargarnos la herramienta de desarrollo para compilación cruzada del servidor FTP
de Technologic Systems.
>> wget ftp://ftp.embeddedarm.com/ts-arm-sbc/ts-7800-
linux/cross-toolchains/ts7800-crosstool-linux-gnueabi-
2005q3-2.tar.gz
4. Extraer en el directorio actual y borrar el antiguo tar.
>> tar xvf ts7800-crosstool-linux-gnueabi-2005q3-2.tar.gz
&& rm ts7800-crosstool-linux-gnueabi-2005q3-2.tar.gz
5. Adquirir la fuente del kernel.
>> wget ftp://ftp.embeddedarm.com/ts-arm-sbc/ts-7800-
linux/sources/linux-2.6.21-ts-src-oct102008.tar.gz
6. Extraer la fuente del kernel.
>> mkdir linux-2.6.21-ts && gzip -dc linux-2.6.21-ts-src-
oct102008.tar.gz | tar xf - -C linux-2.6.21-ts/
7. Movernos hasta el nuevo directorio extraído.
>> cd linux-2.6.21-ts
8. Editar el Makefile del directorio raíz del kernel para que apunte a la ruta
correspondiente a la herramienta de compilación cruzada. En nuestro caso, con la
herramienta descomprimida en el mismo directorio que el kernel, cambiamos la línea
186 tal como sigue:
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
38
CROSS_COMPILE ?= ../arm-none-linux-gnueabi/bin/arm-none-
linux-gnueabi-
9. Editar scripts/mod/sumversion.c para incluir una nueva definición de la variable
PATH_MAX.
#define PATH_MAX 1024
10. Editar el archivo arch/arm/kconfig y modificar la frecuencia que aparece por
defecto para el temporizador.
config HZ
…
default 1500
…
11. Escribir "make ts7800_defconfig" para construir el kernel con las opciones que
Technologic Systems usa para la TS-7800.
12. Escribir "make menuconfig" y editar las opciones deseadas (para cambiar
únicamente la resolución de los temporizadores, no hay que cambiar nada).
13. Escribir “make” para construir el kernel.
14. El nuevo kernel aparece en "arch/arm/boot" en un formato comprimido
denominado zImage.
15. Copiar la imagen comprimida en la partición 2 de la tarjeta SD.
>> dd if=arch/arm/boot/zImage of=/dev/sdb2
16. Trasladar la SD a la TS-7800 y arrancar.
La máxima resolución que pudimos configurar fue de 1500MHz (667ns), puesto que todos los
intentos con valores mayores daban un error de compilación. Dedujimos que este error se
debía a las propias limitaciones del kernel, por lo que de momento habrá que conformarse con
este valor máximo permitido. Si en un futuro se detecta la necesidad de aumentar la
resolución de los tiempos, habrá que plantearse la instalación de un parche de tiempo real.
3.6 Mejoras introducidas sobre la antigua aplicación La principal limitación que nos hemos encontrado desde el principio ha sido la de la frecuencia
de trabajo de los servos. Puesto que a los servos se le debe enviar una orden cada 20
milisegundos, ésta será la frecuencia a la que debemos muestrear nuestros sensores, obtener
la acción de control correspondiente y enviarla a los cinco servos.
Es por ello que nuestros esfuerzos se han centrado en mejorar los tiempos de ejecución de las
funciones de lectura de los distintos sensores, así como en tratar de liberar al microprocesador
de la mayor carga posible. Nuestro objetivo final es que seamos capaces de realizar una
interrupción temporal cada 20 ms, que dentro de cada interrupción leamos todos los sensores
y que en función de estas lecturas calculemos las órdenes y las enviemos a los servos.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
39
3.6.1 Minimización de esperas activas del sensor de ultrasonidos Las esperas activas son uno de los principales problemas que encontramos en el código
original de la aplicación. Al principio, no éramos capaces de cumplir los requerimientos
temporales porque el programa se quedaba esperando repetidamente en distintos puntos de
la lectura del sensor de ultrasonidos a que cambiara su valor. Mientras se hacía esta
verificación, la CPU no podía realizar ninguna otra tarea, y como consecuencia la aplicación no
funcionaba como debía.
Para entender mejor el problema al que nos enfrentamos, explicamos con más detalle el
funcionamiento del sensor de ultrasonidos: en primer lugar, el sensor lanza un pulso de ondas
ultrasónicas que rebotan en el objeto que tiene en frente y vuelve; su salida consistirá en una
señal que se mantiene a nivel alto durante el tiempo que tarda el pulso en volver, por lo que lo
único que habrá que hacer para calcular la distancia será tomar esta señal, calcular cuánto
tiempo se mantiene a nivel alto y hacer un sencillo cálculo para obtener la distancia a partir del
tiempo.
El problema radica en el tiempo que pasamos esperando a que dicha señal deje de estar a
nivel alto. Aunque no hemos podido eliminar las esperas activas por completo por la carencia
de interrupciones hardware, hemos adoptado la siguiente estrategia para minimizar su efecto
negativo:
En una primera iteración se realiza una “calibración” del sensor de ultrasonidos. En
este caso, la espera activa es inevitable, pues no tenemos ninguna referencia del
tiempo que la señal permanecerá a nivel alto.
En sucesivas iteraciones, no tenemos que esperar al nivel bajo de la señal durante todo
el tiempo, sino que dormimos el proceso durante un periodo en el cual estamos
seguros de que la señal no va a cambiar de estado. En concreto, se ha decidido que si
en una interrupción temporal la señal se mantiene a nivel alto un cierto tiempo, en la
siguiente dicha señal se mantendrá a nivel alto como mínimo durante un tiempo igual
al 80% del tiempo anterior. Esta idea parece razonable por las propias limitaciones
físicas del helicóptero en el ascenso y el descenso. Así pues, dormiremos el proceso
durante un 80% del tiempo que tenemos como referencia, descargando en gran
medida a la CPU.
DFLOAT medida_us (void)
{
DFLOAT altura=0; // Variable que almacenará la altura
float aux1=0.0; //Variables auxiliares empleadas para el cálculo
de la altura
float aux2=0.0;
volatile unsigned int *DATAIN, *DATAOUT;
struct timeval now; //Estructura que almacena, a la vuelta de la
función gettimeofday(), los
// segundos y microsegundos
transcurridos desde la época Unix
…
gettimeofday(&now,NULL);
t_0= now.tv_sec * 1000000 + now.tv_usec; // Guardamos tiempo
inicial
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
40
printf("ACTIVACIÓN DEL SENSOR\n\n");
*DATAOUT=*DATAOUT & 0xFFFB;//Ponemos un '0' en el bit 3 de DIO
*DATAOUT=*DATAOUT | 0x4; //Sacamos nivel alto por pin 3 del DIO
header durante al menos 12 us
*DATAOUT=*DATAOUT & 0xFFFB;//Ponemos un '0' en el bit 3 de DIO
printf("LECTURA DE LA SALIDA DEL SENSOR\n\n");
do
{
gettimeofday(&now,NULL);
t_ad22 = now.tv_sec * 1000000 + now.tv_usec;
} while(!(*DATAIN & 0x10)&& (t_ad22-t_0) <= 2000);// Cuando se
detecta el nivel alto o sobrepasamos el tiempo establecido,
continuamos
//Aquí está la estrategia del usleep
if ((t_us>0) && (t_us<9000)&& (t_ad22-t_0) <= 2000) // No tiene
sentido t_us>9000 (altura>2m) -> La lectura anterior fue errónea
{
usleep((int)(0.6*t_us)); //Si pongo un porcentaje más alto
corro el riesgo de dormir más de la cuenta
printf("\nt_us= %d\n",t_us);
}
else
{
if(t_us!=0)
{
printf("\nEl valor de t_us no es válido\n");
}
}
do
{
gettimeofday(&now,NULL);
t_ad23 = now.tv_sec * 1000000 + now.tv_usec;
} while((*DATAIN & 0x10)&& (t_ad23 -t_0 <=11000)); // Contamos
mientras la señal del sensor esté a nivel alto y no hayan transcurrido
11ms
printf("\nCALCULO DE LA ALTURA\n\n");
aux1=(331.0+(calibracion)*0.6); // Vsonido(m/s) = 331.0 + T(ºC)*0.6
aux2=((t_ad23-t_ad22)/2000000.0);
altura = aux1 * aux2; // ALTURA(m) = (TIEMPO(s)/2)*Vsonido
(m/s)
…
if((t_ad23-t_0>11000)||(t_ad22-t_0>2000))
{
altura = 0; // Tomaremos la altura nula como dato erróneo
printf("\n ALTURA ERRÓNEA. Timeout.\n");
t_us=0;
}
else
{
t_us=t_ad23-t_ad22; //Guardamos el tiempo que ha tardado en
volver el eco para dormir el hilo la siguiente ejecución
}
printf("Tiempo desde activacion sensor hasta inicio recepcion eco:
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
41
%u \n Duracion del eco: %u\n",t_ad22-t_0,t_ad23-t_0);
return altura;
} // Fin de la función medida_us()
Tabla 12: Código de muestra de la estrategia de minimización de esperas activas del sensor de ultrasonidos
3.6.2 Mejora de tiempos de lectura de la IMU Superado el problema de las esperas activas, la siguiente limitación, que dificultó
considerablemente nuestra tarea, fue la del tiempo que se tardaba en realizar las lecturas de la
IMU.
De los 20 ms disponibles para cada interrupción temporal, la lectura de la IMU consumía un
tiempo comprendido entre 9 y 13 ms. Teniendo en cuenta el resto de tareas que deben
incluirse dentro de una interrupción (lectura del resto de sensores, cálculo y envío de las
señales de control y envío en tiempo real a tierra o escritura en cola de los datos de los
sensores), este tiempo resultaba excesivo.
Nuestra primera idea fue la de emplear un hilo para leer cada uno de los sensores para que las
lecturas pudieran hacerse en paralelo y que dispusiéramos de los datos de todos los sensores
para calcular las órdenes y enviarlas lo antes posible.
Sin embargo, esta estrategia no funcionó porque, aunque dividiéramos el proceso en hilos, el
microprocesador estaba demasiado ocupado comprobando si la lectura de la IMU estaba lista
durante casi todo el tiempo disponible (13 ms apróx.), de manera que el hilo de la IMU
provocaba el bloqueo del resto de los hilos.
Ante esta nueva limitación, decidimos entrar en detalle en las librerías proporcionadas por el
fabricante de la IMU para tratar de mejorar sus tiempos de lectura. Dentro de dichas librerías,
averiguamos que la función empleada para comprobar si la IMU tiene datos disponibles para
ser leídos es select(), cuyo prototipo es el siguiente:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set
*exceptfds, struct timeval *timeout);
En función del valor que se le pase como timeout (tiempo máximo a esperar desde que
select() comienza su ejecución hasta que devuelve el control), la función tiene tres modos de
funcionamiento distintos:
Si timeout es NULL, la función queda en espera indefinida.
Si timeout es una estructura de tiempo de tipo timeval con los segundos o los
microsegundos distintos de cero, espera un tiempo especificado.
Si timeout es una estructura de tiempo de tipo timeval con los segundos y los
microsegundos iguales a cero, no se produce espera (testeo y retorno).
Comprobamos que, originalmente, se le pasaba un cierto valor en el timeout, así que no sólo
se comprobaba si había algún dato disponible en el buffer de la IMU, sino que se bloqueaba
durante un cierto tiempo en el select() realizando otras comprobaciones.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
42
De acuerdo con nuestras necesidades, decidimos que era preferible que en alguna
comprobación aislada la IMU no tuviera datos en su buffer y nos diera errores de lectura a
invertir en cada interrupción más tiempo del estrictamente preciso para ver si había datos
disponibles. Así pues, pasándole a la función select() como parámetro timeout un puntero a
una estructura de tipo timeval con los segundos y microsegundos a cero, los tiempos de
lectura de la IMU se reducen a menos de 2 milisegundos, y apenas se aprecian pérdidas de
datos a lo largo de toda la ejecución.
3.7 Recalibración de los potenciómetros La alimentación de los potenciómetros tras la adquisición de la nueva placa y el rediseño de la
caja aviónica pasa a ser de 5 V en vez de los 10 V originales, por lo que se cree conveniente
realizar una nueva calibración.
Supondremos que la característica de ambos potenciómetros es aproximadamente lineal, por
lo que la calibración consistirá simplemente en asociar el valor de impedancia que presenta el
potenciómetro con la magnitud que se esté midiendo según el sensor: la altura del helicóptero
o el ángulo de paso colectivo.
En ambos casos la estrategia adoptada es la misma:
En primer lugar, medimos la impedancia a la salida del potenciómetro para un
conjunto de valores de altura del helicóptero o de ángulo del colectivo, según proceda.
A continuación, formamos una tabla de valores x e y con el programa Microsoft Excel y
calculamos los coeficientes m y b de la recta de regresión (y=b+m*x).
Realizamos una calibración inicial del potenciómetro para conocer su ángulo de giro al
comienzo de los experimentos con el helicóptero.
o Medimos la tensión a la salida del potenciómetro de altitud en la posición
inicial del helicóptero: 0.16 cm.
o Medimos la tensión a la salida del potenciómetro de paso colectivo en la
posición inicial del rotor principal: 0 grados.
3.7.1 Calibración del potenciómetro para medida de la altura del
helicóptero Los valores de altura para los que se ha medido la impedancia presentada por el
potenciómetro son los mostrados en la Tabla 13. En dichos cálculos se ha tenido en cuenta que
el potenciómetro es de 10 K y está alimentado a 5 V.
Altura (cm) Impedancia (K) Tensión (V)
16 4.8 2,41
21 4.76 2,38
26 4.66 2,33
31 4.58 2,29
36 4.52 2,26
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
43
41 4.44 2,22
46 4.36 2,18
51 4.26 2,13
Tabla 13: Valores de calibración del potenciómetro para medida de la altura
A partir de los valores anteriores, formamos la nube de puntos representada en la Figura 28
con el programa Microsoft Excel, que calcula la recta de regresión trazada en color negro.
Figura 28: Calibración del potenciómetro para medida de la altura
Ahora bien, la recta de regresión obtenida únicamente se cumple cuando el potenciómetro se
coloca con los mismos ángulos por los que ha pasado a lo largo de la calibración para las
alturas correspondientes. Para no tener que preocuparnos de la posición con que coloquemos
el eje de giro del potenciómetro cada vez que realicemos un experimento, nos quedamos con
la pendiente obtenida (m=-1.216), y reescribimos la ecuación para incluir una calibración al
comienzo de cada experimento consistente en medir la tensión que saca el potenciómetro
para la altura inicial de 0.16 m.
) (Ecuación 3.1)
3.7.2 Calibración del potenciómetro para medida del ángulo de paso
colectivo Los valores del colectivo para los que se ha medido la impedancia presentada por el
potenciómetro son los mostrados en la Tabla 14. En este caso, la alimentación también es de 5
V, mientras que el potenciómetro es de 1K.
Capítulo 3: Sustitución de Ordenador de a Bordo Blanca Rubio Díaz
44
Ángulo de paso colectivo () Impedancia () Tensión (V)
12 544 2,72
6 650 3,25
3 674 3,37
0 724 3,62
-3 759 3,795
Tabla 14: Valores de calibración del potenciómetro para medida del paso colectivo
A partir de los valores anteriores, formamos la nube de puntos representada en la Figura 29
con el programa Microsoft Excel, que calcula la recta de regresión trazada en color negro.
Figura 29: Calibración del potenciómetro para medida del colectivo
De la expresión de la recta de regresión, nos quedamos de nuevo con la pendiente calculada:
m=-13.93. Para el cálculo del ángulo del colectivo a partir de la tensión que saca el
potenciómetro en un momento dado, necesitamos conocer tanto m como la tensión que saca
el potenciómetro para el ángulo de referencia, que será de 0.
(Ecuación 3.2)