El Camino Del Conejo

358

description

El Camino Del Conejo

Transcript of El Camino Del Conejo

Page 1: El Camino Del Conejo
Page 2: El Camino Del Conejo
Page 3: El Camino Del Conejo

El camino del conejo

Guía práctica para avanzar en el desarrollo

con procesadores y módulos

Rabbit

Page 4: El Camino Del Conejo
Page 5: El Camino Del Conejo

El camino delconejo

Guía práctica para avanzar en el

desarrollo

con procesadores y módulos

Rabbit

Sergio R. Caprile

Page 6: El Camino Del Conejo

Caprile, Sergio R.El camino del conejo : guía práctica para avanzar en el desarrollo con

procesadores y módulos Rabbit . - 2a ed. - Buenos Aires : Gran AldeaEditores - GAE, 2010. 358 p. ; 23x16 cm.

ISBN 978-987-1301-28-7

1. Electrónica. 2. Procesadores. I. Título CDD 621.39

Fecha de catalogación: 07/04/2010

1a edición: septiembre de 20062a edición: abril de 2010

ISBN 978-987-1301-28-7

2010© by Sergio R. Caprile

Hecho el depósito que establece la ley 11.723.

Se prohibe la reproducción total o parcial, por cualquier medioelectrónico o mecánico incluyendo fotocopias, grabaciónmagnetofónica y cualquier otro sistema de almacenamiento deinformación, sin autorización escrita del editor.

Los nombres de los programas, sistemas operativos, sitios de Internet yhardware, entre otros, mencionados en la presente obra, son propiedadexclusiva de sus registradores legales.

ZigBee® es marca registrada de ZigBee Alliance.

XBee® y XBee-PRO® son marcas registradas de Digi International.

Wi-Fi® es marca registrada de Wi-Fi Alliance.

Page 7: El Camino Del Conejo

Índice de contenido

Prefacio....................................................................................................................xviiAssembler.....................................................................................................................1

Introducción...........................................................................................................1Repaso..............................................................................................................1

Rabbit 2000 y 3000.....................................................................................1

Rabbit 4000 y 5000.....................................................................................2Bloques independientes..........................................................................................3

Depuración........................................................................................................3Rabbit 4000 y 5000.....................................................................................3

Pasaje de parámetros........................................................................................4Bloques de assembler en xmem........................................................................5

Assembler encapsulado en C..................................................................................6Acceso a variables locales y parámetros...........................................................7Depuración........................................................................................................9

Sentencias C dentro de bloques assembler.............................................................9Interrupciones............................................................................................................13

Introducción.........................................................................................................13

Repaso............................................................................................................13Ejemplos...............................................................................................................16

Interrupciones internas: buzzer en Timer B....................................................16Interrupciones externas: lector ABA (Track II)..............................................18

Tiempos de ejecución................................................................................................21Introducción.........................................................................................................21

Conceptos.............................................................................................................21Interrupciones.................................................................................................21Multitarea........................................................................................................23

Multitarea cooperativo..............................................................................23Multitarea de tipo preemptive...................................................................25

Sentencia Slice....................................................................................26

RTOS........................................................................................................29Recursos...............................................................................................................29

Periféricos Internos....................................................................................................31Ports paralelo........................................................................................................31

Shadow registers.............................................................................................31Cambio de pines por hardware.......................................................................35

Ports D y E: bit registers.................................................................................41Ports serie en Rabbit 2000 y 3000........................................................................41

Registros.........................................................................................................41Pines................................................................................................................42

Puertos A y B............................................................................................42Pinout alternativo................................................................................42

vii

Page 8: El Camino Del Conejo

Puertos C y D............................................................................................43Puertos E y F.............................................................................................43

Ports serie en Rabbit 4000 y 5000........................................................................43RS232.lib........................................................................................................43

DMA.........................................................................................................44

Packet.lib........................................................................................................44Timer B................................................................................................................44

Repaso............................................................................................................44Ejemplo (Rabbit 2000 y 3000).......................................................................45El Timer B antes y después del Rabbit 4000 .................................................46

Ejemplo.....................................................................................................47

Reloj de Tiempo Real...........................................................................................47Watchdog Timer...................................................................................................48Uso de encoders o codificadores rotativos...........................................................49

R3000(A) y sucesores.....................................................................................50R2000..............................................................................................................52

Salidas PWM........................................................................................................54

Interrupciones y supresión de pulsos (R3000A+)..........................................56Captura de eventos...............................................................................................58

Repaso............................................................................................................58Ejemplo...........................................................................................................60

Timer C (R4000+)................................................................................................61IO Config..............................................................................................................63

Periféricos..................................................................................................................65Bus de datos.........................................................................................................65

Memorias FRAM paralelo..............................................................................65Controladores de displays LCD color.............................................................70

Breve descripción del display color..........................................................70Breve descripción del controlador............................................................72

Desarrollo..................................................................................................73Algoritmos...........................................................................................77

Displays LCD gráficos....................................................................................78Displays OLED...............................................................................................80

Breve descripción del display...................................................................81Algoritmos.................................................................................................81

Buses serie............................................................................................................82SPI..................................................................................................................83

Rabbit 2000 y 3000...................................................................................84Rabbit 4000 y 5000...................................................................................86Uso y ejemplos ........................................................................................87

Ejemplos..............................................................................................87

Conversores A/D MCP3204 y MCP3208...........................................87Controlador de touchscreen ADS7846................................................90

I2C..................................................................................................................92Ejemplos...................................................................................................95

viii

Page 9: El Camino Del Conejo

Memorias EEPROM y FRAM............................................................95Processor Companion (con FRAM)....................................................98

Microwire.....................................................................................................100Rutinas caseras........................................................................................101Ejemplo...................................................................................................103

BL233B........................................................................................................104Apéndice: Low Voltage blue's............................................................................108

Micro de 3V, periférico de 5V.....................................................................108Micros 5V-tolerant..................................................................................108

Compatibilidad de niveles.................................................................108Incompatibilidad de niveles...............................................................109

Micros que no toleran 5V en sus entradas.........................................109Micro de 5V, periférico de 3V................................................................111

Manejo de memoria extendida.................................................................................113Introducción: R2000/3000 vs. R4000/5000.......................................................113Datos en xmem...................................................................................................113

C....................................................................................................................113

Bloques de datos: display gráfico LCD..................................................113Datos individuales...................................................................................114Arrays y estructuras.................................................................................115

Assembler.....................................................................................................118Datos individuales...................................................................................118Bloques de datos.....................................................................................118

Display color OLED..........................................................................118Display color LCD............................................................................120

Apéndice: La vida después de R4000 y DC10...................................................124Configuración en campo..........................................................................................127

Introducción.......................................................................................................127Repaso..........................................................................................................127

IDblock...................................................................................................127User block...............................................................................................128

Guardando la configuración...............................................................................129Configuración vía web.......................................................................................131

Seguridad......................................................................................................131Simultaneidad..........................................................................................139

Interfaces complejas................................................................................145Conectividad............................................................................................................151

USB....................................................................................................................151Introducción a USB......................................................................................151¿Por qué?......................................................................................................154FT232...........................................................................................................154

Bluetooth............................................................................................................157Introducción a Bluetooth..............................................................................157

Conexión (pairing, bonding)...................................................................159Perfiles (profiles)....................................................................................159

ix

Page 10: El Camino Del Conejo

SPP....................................................................................................159DUN..................................................................................................159

¿Por qué?......................................................................................................159Módulos Bluetooth.......................................................................................160

ZigBee e IEEE 802.15.4.....................................................................................161

Introducción a ZigBee..................................................................................161Routing en una red ZigBee.....................................................................162Comunicación de aplicaciones en una red ZigBee..................................162

Binding..............................................................................................163¿Por qué?......................................................................................................163Módulos XBee..............................................................................................163

XBee 802.15.4........................................................................................164Comunicación punto a punto.............................................................164Red punto a multipunto con coordinador..........................................164

XBee ZB.................................................................................................165xbee_api.lib.......................................................................................165

Equisbí....................................................................................................166

RF multipunto 2,4 Ghz.......................................................................................166Desarrollo de una biblioteca de funciones....................................................168

Encabezado de la biblioteca de funciones...............................................171Sistema de ayuda...............................................................................171Código...............................................................................................172

Encabezados de funciones y variables globales......................................172

Sistema de ayuda...............................................................................172Código...............................................................................................173

Uso de la biblioteca de funciones.................................................................174Ejemplos.......................................................................................................174

Master.....................................................................................................175Slave........................................................................................................175

Comunicación serie asincrónica.........................................................................176Señalización por noveno bit..........................................................................176Señalización por tiempo muerto...................................................................179Señalización por caracter especial................................................................181

IrDA...................................................................................................................182GSM...................................................................................................................183

CSD..............................................................................................................183SMS..............................................................................................................184

Módulos SIMCOM.................................................................................185Selección del formato........................................................................185Envío de mensajes.............................................................................185Recepción de mensajes......................................................................185

Ejemplos............................................................................................186GPRS............................................................................................................189

Módulos SIMCOM.................................................................................189Modo transparente.............................................................................191

x

Page 11: El Camino Del Conejo

File Systems.............................................................................................................193Introducción.......................................................................................................193FS2.....................................................................................................................193

Elección del soporte físico............................................................................194Partición y formateo.....................................................................................195

Utilización normal........................................................................................196Modificación del BIOS.................................................................................197Ejemplos.......................................................................................................198

Creación del FS2 en un módulo con dos chips de flash..........................199Creación del FS2 en un módulo con un chip de flash.............................199Creación del FS2 en la flash principal, en un módulo con dos chips de

flash.........................................................................................................199Creación del FS2 en RAM......................................................................200Escritura de un log..................................................................................201Lectura de un log.....................................................................................202Escritura de un log de longitud fija.........................................................202

Afinación (tuning) y uso avanzado del FS2..................................................204

Tamaño de partición y de sectores..........................................................204Acceso a diferentes particiones...............................................................206Cantidad de archivos y de particiones.....................................................206

FAT....................................................................................................................206Acceso a diferentes dispositivos y particiones.............................................207Partición y formateo.....................................................................................208

Utilización normal........................................................................................209Dispositivos removibles................................................................................211

Tarjetas xD..............................................................................................211Tarjetas SD.............................................................................................212

Ejemplos.......................................................................................................212Creación de FAT.....................................................................................213

Escritura de un log..................................................................................213Lectura de un log.....................................................................................214

Configuración de FAT..................................................................................215Networking...............................................................................................................217

Introducción.......................................................................................................217Switches, bridges, hubs, routers.........................................................................217

Hub...............................................................................................................218Switch...........................................................................................................218Routing, router..............................................................................................219

ISP...........................................................................................................220Dial-up...............................................................................................220xDSL (ADSL y equivalentes)............................................................220

Cable modem.....................................................................................221IP Routing, DNS, direcciones............................................................................221

Analogía cotidiana........................................................................................222DDNS...........................................................................................................223

xi

Page 12: El Camino Del Conejo

Proveedores.............................................................................................223Firewall.........................................................................................................224Proxy.............................................................................................................225Receta para agregar nuevos dispositivos......................................................226

Red con administrador............................................................................226

Red sin administrador.............................................................................226Firewall..............................................................................................227Router + NAT....................................................................................227DNS...................................................................................................227Dispositivos.......................................................................................228

Resolución de problemas (troubleshooting).................................................228

Aplicaciones y particularidades..........................................................................230UDP broadcast..............................................................................................230Desconexiones y sockets abiertos.................................................................230

TCP.........................................................................................................230UDP.........................................................................................................230

Timing, entrega a domicilio, ancho de banda...............................................231

UDP.........................................................................................................231TCP.........................................................................................................231

Wi-Fi..................................................................................................................232Identificación................................................................................................233Seguridad......................................................................................................233

Personal vs. Enterprise............................................................................234

Wireless bridging..........................................................................................235GSM...................................................................................................................235

CSD..............................................................................................................236PPP sobre CSD.......................................................................................236

GPRS............................................................................................................237IP sobre GPRS........................................................................................238

PPP sobre GPRS.....................................................................................238Tethering............................................................................................................239

Networking con Rabbit............................................................................................241Introducción.......................................................................................................241Direcciones IP....................................................................................................241

Resolución por DNS.....................................................................................241

UDP....................................................................................................................243Checksum......................................................................................................244Ejemplos.......................................................................................................245

TCP....................................................................................................................245Detección de interrupción en la conexión....................................................246Cliente TCP..................................................................................................247

Implementación.......................................................................................248Inicio de la conexión.........................................................................248Espera de la aceptación y establecimiento de la comunicación........248Transferencia de datos.......................................................................249

xii

Page 13: El Camino Del Conejo

Fin de la conexión.............................................................................249Espera para reiniciar la conexión (opcional).....................................249

Ejemplos.................................................................................................250Servidor TCP................................................................................................253

Implementación.......................................................................................253

Inicio del servicio..............................................................................253Espera de un pedido de conexión, y establecimiento de ésta............254Transferencia de datos, Fin de la conexión.......................................254

Ejemplos.................................................................................................254Múltiples conexiones en un mismo port.................................................256

Zserver................................................................................................................257

"Archivos" en xmem agregados de forma dinámica.....................................259Archivos en file systems...............................................................................259

Servidor HTTP...................................................................................................260Imágenes generadas en tiempo de ejecución................................................261Imágenes que no aparecen............................................................................263Archivos servidos desde file systems............................................................264

Autenticación................................................................................................265Usuarios..................................................................................................267Archivos y variables................................................................................267

"Archivos" agregados en forma dinámica.........................................268Archivos en file systems, agregados en forma dinámica...................268

HTTP upload................................................................................................269

Actualización dinámica de variables: AJAX................................................271El código JavaScript...............................................................................272El servidor Rabbit...................................................................................272

Gráficos: JavaScript, Flash...........................................................................272Cliente HTTP.....................................................................................................273Cliente FTP........................................................................................................274

Archivos en file systems...............................................................................277Servidor FTP......................................................................................................279

"Archivos" en xmem.....................................................................................280Estáticos..................................................................................................280Dinámicos...............................................................................................281

Archivos en FS2...........................................................................................282

Escritura........................................................................................................286Archivos en FAT..........................................................................................287

Servidores FTP y HTTP combinados................................................................290Autenticación en cliente SMTP..........................................................................291Direcciones IP fijas............................................................................................292

Usando TCPCONFIG...................................................................................293

Direcciones IP dinámicas...................................................................................293DHCP............................................................................................................293

Fallback...................................................................................................294DDNS...........................................................................................................294

xiii

Page 14: El Camino Del Conejo

Cambio de dirección IP................................................................................294Cambio a DHCP......................................................................................295

con fallback.......................................................................................295Sockets abiertos......................................................................................295

PPP...............................................................................................................296

Usando TCPCONFIG.............................................................................298PPP sobre CSD.......................................................................................298PPP sobre GPRS.....................................................................................299

Módulos SIMCOM...........................................................................299PPP sobre GPRS vía Bluetooth (DUN) (tethering)................................299

KCWirefree.......................................................................................300

PPPoE...........................................................................................................302Redirecciones................................................................................................303Cambio de parámetros de red en funcionamiento.........................................303

Ejemplo: cambio desde una página web.................................................303Configuración....................................................................................304Aplicación de los cambios.................................................................304

Programa principal: inicialización de la interfaz...............................305Seguridad...........................................................................................306

Múltiples interfaces............................................................................................310Resolución de problemas (troubleshooting).......................................................311

Herramientas.................................................................................................311Link.........................................................................................................311

Network...................................................................................................311Modo debug.......................................................................................311En la aplicación.................................................................................312

Transporte y aplicación...........................................................................313Modo debug.......................................................................................313En la aplicación.................................................................................313

Wi-Fi..................................................................................................................314Módulos con capacidad Wi-Fi......................................................................314

Configuración..........................................................................................314Conexión abierta................................................................................315Conexión protegida...........................................................................315

Inicio.......................................................................................................316

Cambio de parámetros en campo............................................................316Módulos sin Wi-Fi incorporado ..................................................................318

RabbitWeb...............................................................................................................319Introducción.......................................................................................................319Extensiones a Dynamic C...................................................................................319

Grupos de usuarios.......................................................................................319

Variables.......................................................................................................320Variables con listas de valores................................................................321

Detección de cambios...................................................................................321Lenguaje script...................................................................................................322

xiv

Page 15: El Camino Del Conejo

Funciones......................................................................................................323Selectores................................................................................................324Botones...................................................................................................325

Desarrollo de aplicaciones.......................................................................................327Introducción.......................................................................................................327

Transporte de comunicaciones serie asincrónicas mediante TCP/IP.................327Introducción..................................................................................................327Análisis.........................................................................................................328Desarrollo.....................................................................................................330

Serie a TCP.............................................................................................332TCP a serie..............................................................................................333

Mantenimiento de la conexión................................................................333Cliente...............................................................................................333Servidor.............................................................................................334

Programa principal..................................................................................334Otros protocolos......................................................................................335

Protocol Spoofing..............................................................................335

Gateway.............................................................................................337Conversión de velocidad...................................................................337Latencia: algoritmo de Nagle, flush..................................................338

Tarea para el hogar.......................................................................................339Expandiendo a más puertos simultáneos...........................................339

xv

Page 16: El Camino Del Conejo
Page 17: El Camino Del Conejo

Prefacio

La idea de escribir un segundo libro sobre el tema surge por dos motivosfundamentales. El primero es la necesidad de mantener una referencia rápida de unagran cantidad de temas no cubiertos en el primero, por motivos de tiempo, espacio, y

etc. El segundo motivo es poder analizar algunos de los temas ya presentados desdeotro ángulo. No es muy fácil la expresión cuando el limitante es el lenguaje, y esnecesario construirlo primero para poder utilizarlo después."Desarrollo con procesadores y módulos Rabbit" presenta los temas y da ejemplos,

generalmente simples; la premisa fundamental es la fácil y rápida comprensión delos conceptos. Avanzamos elaborando y construyendo conceptos; donde no hay un

ejemplo, está toda la información como para que el lector pueda seguir solo.Dice el poeta que no lo hay, que se lo hace al andar. Y si de andar se trata, avanzar

es la premisa. Este libro no sólo presenta una mayor cantidad de temas y cuestiones,sino que se mete en cada uno de los temas con una mayor profundidad,aprovechando que ya tenemos una estructura conceptual construida. De este modo,resulta más fácil profundizar y fundamentalmente abordar cada análisis con un

enfoque más práctico. Avanzamos planteando soluciones a problemas comunes quetodo desarrollador se encuentra, con la tranquilidad que nos da el tener la teoría yamedianamente resuelta.

Dice el poeta que no lo hay, que se lo hace al andar. Sea tal vez este libro algunasestelas del mío.

Segunda edición

En esta segunda edición nos encontramos con un camino reestructurado. Ladirección en que ha avanzado el conejo me obligó a descartar contenido de laedición anterior, basado en cuestiones de costo; a modificar parte del material para

adecuarlo a Rabbit 4000 y 5000, los dos últimos a la fecha, y a agregar contenidopara éstos. Particularmente, lo relacionado a Wi-Fi. No se trata de una reescritura, no se trata de un agregado de apéndices, ha sido una

actualización. ¿Qué pasará en el futuro? Vaya la incertidumbre del hombre, eltiempo dirá.

Sergio R. Caprile, marzo de 2010

xvii

Page 18: El Camino Del Conejo
Page 19: El Camino Del Conejo

Assembler

Introducción

Vamos a comenzar analizando las distintas formas de que disponemos para trabajaren assembler. Hemos visto la arquitectura de Rabbit en el libro "Desarrollo conprocesadores y módulos Rabbit", al cual nos referiremos cariñosamente, de aquí en

más, como "el libro introductorio". A modo de introducción, haremos un breverepaso de los modos de direccionamiento de que disponemos, y luego analizaremosla forma de trabajar con assembler en bloques sueltos, independientes, o comoporciones de código embebido dentro de una función en C.Aquellos lectores no interesados en absoluto en el control de la ejecución a este

nivel, pueden saltear este capítulo de forma segura. Sin embargo, está aquí por una

razón, y a menos que haya algo más interesante o remunerativo en que invertir eltiempo, la sugerencia es analizarlo; antes de descartar la información es convenienteconocerla primero...

Repaso

Rabbit 2000 y 3000

El core Rabbit 2000 y 3000 tiene el mismo set de registros que el Z80, con algunasmenores diferencias. Los actores principales son AF, BC, DE, HL, IX, IY y sus

primos alternativos AF', BC', DE', HL'. Con registros de 8-bits (B, C, D, E, H, L),agrupables formando registros de 16-bits (BC, DE, HL) para algunas operaciones de16-bits y funcionamiento como punteros para direccionamiento indirecto. Losregistros índice (IX, IY) son de 16-bits y funcionan como punteros en lasinstrucciones de direccionamiento indexado con offset de 8-bits. Algunas otrasinstrucciones permiten utilizar HL o incluso el stack pointer (SP) como índice para

operaciones de 16-bits como carga indirecta o indexada.Todos los registros de 8-bits pueden rotarse, incrementarse, decrementarse, y

cargarse mediante direccionamiento inmediato, indirecto (utilizando HL comopuntero) e indexado. El acumulador, o registro A, es siempre una de las fuentes ydestino obligado de todas las operaciones aritméticas de 8-bits; puede cargarseademás mediante direccionamiento directo extendido e indirecto (utilizando además

BC o DE como punteros). Los registros de 16-bits pueden cargarse mediantedireccionamiento inmediato extendido, directo extendido, e indirecto (respecto alSP). El par HL puede cargarse mediante direccionamiento indexado respecto a SP

1

Page 20: El Camino Del Conejo

Assembler

(aunque Rabbit llama a este modo relativo1), a él mismo, o a los índices. HL es enefecto una especie de acumulador para operaciones lógicas y aritméticas de 16-bits.

Una característica interesante del set de instrucciones son las instrucciones paramover indirectamente datos y bloques de datos, LDI (LoaD and Increment), LDD(LoaD and Decrement), y sus primas repetitivas LDIR (LoaD Increment and Repeat)y LDDR (LoaD Decrement and Repeat); que transfieren un conjunto de BC datos

apuntado por HL a la posición apuntada por DE, el primero de cabeza a cola y elsegundo cola a cabeza. Otra instrucción de auto repetición es DJNZ (Decrement andJump if Not Zero) que decrementa el registro B y ejecuta el salto relativo mientras Bsea distinto de cero, y se utiliza para repetir un loop tantas veces como indica elregistro B.

El core ejecuta instrucciones en sólo dos clocks por cada byte de opcode y cada

byte de acceso a memoria. Las operaciones de escritura demandan tres clocks, y serequiere un clock adicional si debe computarse una dirección de memoria o se utilizaalguno de los registros índice para direccionar.

Rabbit utiliza un interesante esquema para acceder dispositivos de entrada/salida.Cualquier instrucción que acceda a memoria, puede utilizarse para acceder a uno dedos espacios de I/O: uno interno (periféricos en chip) y otro externo (periféricos

externos). Esto se realiza anteponiendo a la instrucción un prefijo. Mediante estaposibilidad, todas las instrucciones de acceso a memoria en 16 bits están disponiblespara leer y escribir posiciones de I/O. La unidad de mapeo de memoria no se utilizapara I/O, por lo que se dispone de un direccionamiento lineal de 16 bits.

Rabbit 4000 y 5000

Además de lo visto para Rabbit 2000 y 3000, la CPU Rabbit 4000 y 5000

incorpora nuevas instrucciones con soporte para loops largos y números de 16-bits,generando mayor densidad de código. Dispone de instrucciones como DWJNZ(Decrement Word and Jump if Not Zero) y JRE (Jump Relative Extended), versiones16-bits de DJNZ (loop counter por excelencia) y JR, respectivamente.Entre los registros de propósitos generales, encontramos cuatro nuevos registros

índice de 32-bits (PW, PX, PY, PZ), y uno de 16-bits (JK), todos con sus

correspondientes versiones alternativas.El nuevo registro de 16-bits permite concatenarse con el par HL (acumulador de

facto para instrucciones de 16-bits) formando pareja con BCDE, con el fin deincorporar dos registros de 32-bits de propósitos generales, sobre los cuales sepueden hacer algunas operaciones aritméticas, lógicas, y rotaciones.

Los nuevos registros de 32-bits son funcionalmente similares a IX e IY. Su

principal utilidad es la de ser punteros tanto en memoria lógica como física, existeninstrucciones de "carga lejana" (LDF: LoaD Far) que permiten utilizardireccionamiento directo en memoria física, tanto en 8, 16, ó 32-bits.

1 El offset es siempre positivo, por lo que se acerca más a un indexado

2

Page 21: El Camino Del Conejo

Bloques independientes

Bloques independientes

La forma de trabajar en assembler solo, es decir, sin estar vinculado a código en C,es declarar un bloque:

#asm; código assembler aquí...

#endasm

Dentro del bloque, podemos acceder a variables estáticas externas. Nos referimos aellas por su nombre, y el mismo será traducido a su dirección en memoria por elcompilador:

static int pepe;

#asmld HL,(pepe) ; HL = pepe...ld (pepe),HL ; pepe = HL...ld HL,pepe ; HL = &pepe

ld c,(HL)inc HLld b,(HL) ; BC = pepe...ld HL,BC ; valor de retorno en HL

#endasm

Depuración

La forma de depurar un bloque de código assembler independiente, es agregar

debug en la definición del bloque:

#asm debugld a,bld b,c

#endasm

El inconveniente es que esto inserta instrucciones RST282 entre cada líneaassembler, lo cual altera notablemente el tiempo de ejecución y lo limita a depurar

sólo aplicaciones que no requieran de operación a alta velocidad; que suele serjustamente la razón por la cual usaríamos assembler...

ld a,bRST28ld b,cRST28

Rabbit 4000 y 5000

2 El sistema de debugging en circuito se basa en la inserción de instrucciones de restart para generarllamadas a una posición fija en la que el BIOS reporta al entorno de desarrollo y permite el control dela ejecución.

3

Page 22: El Camino Del Conejo

Assembler

Estos micros incluyen siete registros para utilizar como hardware breakpoints. Seisde ellos están disponibles y pueden colocarse en cualquier dirección, incluso paradetectar accesos a memoria.Un hardware breakpoints puede utilizarse para detener la ejecución en un punto,

pero la ejecución paso a paso requiere de software breakpoints (instruccionesRST28).

Pasaje de parámetros

El pasaje de parámetros a una rutina en assembler depende de la inquietud de quienla escribe, y del entorno en que se la utiliza. En Dynamic C, todos los parámetrosque deban pasarse a una función, se pasarán en el stack y el primero además seentregará en el par de registros HL o en el cuarteto BCDE, según se trate de unentero/caracter/puntero o un entero largo/coma flotante, respectivamente. Por ende,si nuestra rutina en assembler será llamada desde una función C, deberemos esperar

los parámetros en el stack, inmediatamente a continuación de la dirección de retorno:

#asm;@sp+2= dirección en xmem (long);@sp+6= longitud de los datos (int)

...

ld hl, (SP+4) ; Obtiene address (long) en BC:DEld c,lld b,hld hl, (SP+2)ex de,hl...ld hl,(sp+6) ; hl=longitud del bloque...

#endasm

También podemos aprovechar y utilizar directamente BCDE (en este caso) para elprimer parámetro.Como sabemos, el pasaje de parámetros se realiza por valor, es decir no se pasa una

referencia sino el valor del elemento. Cuando el parámetro que se pasa es unpuntero, lo que se pasa es el valor del puntero. Cuando el parámetro que se pasa es

una estructura, se reserva el espacio en el stack como para que pueda entrar dichaestructura, y si la función que se llama devuelve como resultado una estructura, antes

4

ultimo parametro

primer parametro

dir. de retorno

SP

Page 23: El Camino Del Conejo

Bloques independientes

de llamarla se reserva espacio en el stack como para que quepa dicha estructura. Elcompilador determina esto en base a las declaraciones de las funciones.

Todo esto deberemos tenerlo presente, ya que en assembler, somos nosotrosquienes debemos operar sobre el stack para obtener los parámetros y eventualmenteentregar el valor devuelto por nuestra función. De igual modo, si desde assemblerllamamos a una rutina que espera ser llamada desde C, deberemos colocar todos los

parámetros en el stack antes de llamarla y volverlo a la normalidad al recibir elcontrol nuevamente.El valor de retorno de una rutina assembler debe encontrarse en el par HL o en el

cuarteto BCDE, según su tipo, a menos, claro está, que se trate de una estructura,como adelantáramos. A fin de que el compilador sepa qué esperar, es menesterdeclarar correctamente la función:

int jacinto(long, int);

#asm

;@sp+2= dirección en xmem (long);@sp+6= longitud de los datos (int)jacinto::

...ld hl,(sp+6) ; hl=segundo parámetro...ld hl,algo ; valor de retornoret

#endasm

Tendremos varios ejemplos reales de funciones en C llamando a una rutina enassembler en el capítulo sobre manejo de memoria extendida.

Bloques de assembler en xmem

A menos que lo indiquemos explícitamente, los bloques de assembler se ubican enel área root. Si agregamos en la declaración del bloque la directiva xmem, entoncesse ubicará en xmem. Como sabemos, el segmento xmem es una ventana de 8KB a la

totalidad de la memoria física, cuyo puntero de segmento es el registro XPC de laCPU. El compilador, cuando genera código en xmem, arma bloques de no más de4KB, de modo que siempre pueda correrse la ventana y realizar saltos dentro delbloque. En el caso de assembler, si bien el control lo tiene quien lo escribe, elcompilador no permite bloques assembler de más de 4K.

Un detalle muy importante cuando se trabaja con código assembler en xmem, es

contemplar la posibilidad de que quien lo llama pueda recuperar el control. Comosabemos, al ejecutar código en xmem se modifica el registro XPC para apuntar a lapágina que contiene el código en cuestión. Si el código que llama estaba corriendoen xmem, deberá salvar el valor de XPC antes de llamar a nuestro código assembler,y se espera que éste lo recupere al realizar el retorno. Ambas operaciones se realizanmediante dos instrucciones:

#asm xmempepe::

5

Page 24: El Camino Del Conejo

Assembler

...LRET ; recupera PC y XPC del stack

#endasm...

#asm xmem...LCALL pepe ; salva XPC y PC en el stack,... ; carga nuevo XPC y salta a 'pepe'LRET ; recupera PC y XPC del stack

#endasm

Un pequeñísimo detalle a tener en cuenta si se pasan parámetros en el stack, es que

la dirección de retorno incluye el registro XPC, por lo que los parámetros estánahora de SP+3 en adelante.

Assembler encapsulado en C

Si necesitamos sólo un pedacito de assembler para resolver un problema que en Ces más complicado, o queremos, por ejemplo, operar sobre la prioridad de ejecución,podemos embeber un trozo de código en assembler dentro de una función en C, de

forma similar a cuando declaramos funciones en assembler:

int maifunction(int par1, int par2){int i,var1;

#asmipset 1

#endasm

for(i=0;i<10;i++) {...}

#asmipres

#endasmreturn(var1);

}

6

ultimo parametro

primer parametro

dir. de retornoSP

Page 25: El Camino Del Conejo

Assembler encapsulado en C

Un ejemplo real de assembler encapsulado en una función C puede observarse másadelante en el código que lee un encoder con R2000 y luego en el que lee el módulode captura de pulsos en R3000; ambos en el capítulo sobre periféricos internos.

Acceso a variables locales y parámetros

Otra ventaja de poder encapsular assembler dentro de una función en C, es quepodemos acceder directamente a las variables locales dinámicas (internas) y a losparámetros de llamada de la función, cosa que en un bloque suelto de assembler nos

obliga a tener que llevar la cuenta del stack, como viéramos en el apartado anterior.Aquí, por ejemplo, podemos acceder fácilmente a estas variables mediante unpseudo direccionamiento relativo al registro SP, con un offset dado por el nombre dela variable. Dicho direccionamiento no existe en la realidad, sino que es elcompilador quien resuelve esto y traduce una versión mnemónica al offset dentro delstack. Así, en el ejemplo anterior, si queremos acceder al parámetro par1, hacemos:

ld hl,(SP+@SP+par1)

El prefijo @SP indica el tamaño de la porción de stack donde están las variables detipo auto locales, y entonces @SP+par1 sería el offset necesario dentro del stackpara que el direccionamiento indexado o relativo respecto de SP acceda a dichavariable, sin tener que calcularlo a mano; es decir, esa instrucción se traducirá alcompilar como:

ld hl,(SP+6)

o el valor al que resuelva @SP+par1, dependiendo de la cantidad de parámetros,variables locales, y a cuál nos referimos; como puede observarse en el diagrama3:

3 El diagrama muestra un espacio para dos bytes en la dirección de retorno, lo cual corresponde a unafunción C en área root. Para una función en xmem, el espacio en el stack ocupado por la dirección deretorno es de tres bytes, como viéramos en un apartado anterior.

7

ultimo parametro

primer parametro

dir. de retorno

SP

primera variable

ultima variable

@SP

Page 26: El Camino Del Conejo

Assembler

De igual modo, podemos acceder a una variable local:

ld hl,(SP+@SP+var1)

Por ejemplo, en la función anterior:

int maifunction(int par1, int par2){int i,var1;

#asmipset 1

#endasm

for(i=0;i<10;i++) {...

#asmld hl,(SP+@SP+par1) ; HL = par1...ld hl,(SP+@SP+par2) ; HL = par2...ld (SP+@SP+var1),hl ; var1 = HL...

#endasm...

}

#asmipres

#endasmreturn(var1);

}

Por supuesto que también podemos acceder a variables estáticas, tanto internas

como externas. En este caso, nos referimos a ellas por su nombre, y el mismo serátraducido a su dirección en memoria por el compilador:

static int pepe;

char maiaderfunction(void){auto int i;static char lalala;

for(i=0;i<10;i++) {...

#asmld hl,(pepe) ; HL = pepe...ld hl,lalala ; HL = &lalala ld a,(HL) ; A = *HL = lalala (ídem)...ld (lalala),a ; lalala = Ald(pepe),hl ; pepe = HL...

#endasm...

}

return(lalala);}

8

Page 27: El Camino Del Conejo

Assembler encapsulado en C

Depuración

Una utilidad adicional de poner assembler encapsulado en C, es la posibilidad deinsertar breakpoints en el código o ejecutar porciones del mismo por pasos. Paralograr esto, lo que hacemos es introducir una sentencia C en medio del bloque deassembler, un simple ; por ejemplo, y entonces el compilador introduce unainstrucción RST28 en ese punto solamente, permitiéndonos ejecutar o poner unbreakpoint en dicho lugar y observar el assembler desde allí abriendo la ventana de

desensamblado.

#class auto

main(){int i;

for(i=0;i<1000;i++){#asm

ld hl,22ld a,(hl)

c ; // esto es una sentencia C vacíald b,ald a,(IX+5)

#endasm

}}

Existe además una opción adicional: IX frame, que permite acceder a losparámetros utilizando el registro IX como índice de una tabla conteniendo losparámetros. Sin embargo, dado que tanto la operatoria como los tiempos de

ejecución no difieren demasiado del uso de SP que hemos descripto, noahondaremos en este tema.

Sentencias C dentro de bloques assembler

Como seguramente habrán notado en el último ejemplo, es posible incluir unasentencia en C dentro de un bloque assembler; sea éste independiente o encapsuladoen C. Sí, es posible tener C encapsulado en assembler encapsulado en C. Por suertela recursividad termina allí.

La sentencia C debe ser algo de una sola línea, dado que se debe terminar conpunto y coma. Sin embargo, el grado de complejidad que un programador de Centonado puede lograr en una sola línea escapa a mi imaginación.

La utilidad principal es mantener la potencia de assembler, sin perder la de C, esdecir, si en un bloque assembler necesitamos leer o modificar el valor de unavariable, o esperar por semáforos, es mucho más sencillo hacerlo en C. De hecho es

lo que hacen algunas de las rutinas de inicialización del sistema.Los siguientes son ejemplos carentes de toda utilidad, cuya única función es dar

una idea de lo que se puede hacer, y demostrar la sintaxis:

9

Page 28: El Camino Del Conejo

Assembler

unsigned char busy;

#asmisr1::

...c busy=1;

ipset 0...ipres

c busy=0;...ret

#endasm

#asmisr2::

push afld a,(busy)and ajr nz,chau...

chau: pop afipresret

#endasm

#asmcode::

c while(busy);c busy=1;

ld hl,......

c busy=0;ret

#endasm

Una utilidad realmente interesante es que es posible resetear un watchdog virtualdesde assembler de forma muy simple:

int ID;

#asm

lup::

...c VdHitWd(ID);

...jr nz lupret

#endasm

main(){

ID = VdGetFreeWd(5); /* inicializa un VWDT */while(1)

lup();}

Como puede observarse, es idéntico a lo que haríamos en C, como veremos másadelante, en el capítulo sobre periféricos internos:

int ID;

10

Page 29: El Camino Del Conejo

Sentencias C dentro de bloques assembler

main(){

ID = VdGetFreeWd(5); /* inicializa un VWDT */

while(1){VdHitWd(ID);...

}}

11

Page 30: El Camino Del Conejo
Page 31: El Camino Del Conejo

Interrupciones

Introducción

Continuamos nuestro análisis antes de volcarnos de forma más decidida en losejemplos y aplicaciones, con las interrupciones, su arquitectura en el core, y algunosejemplos prácticos de como aprovecharlas.

Repaso

Recordemos que el core tiene cuatro niveles de prioridad de ejecución. El nivel másbajo se destina a la ejecución “normal”, y los otros tres niveles suelen adjudicarse deacuerdo a la prioridad requerida por las diversas rutinas de interrupciones, según lacriticidad del periférico o la operación en cuanto a latencia, etc. Una interrupciónsólo se tomará en cuenta si la prioridad de la misma es superior a la prioridad a laque se encuentra funcionando en ese momento la CPU. La latencia máxima queda

establecida por la secuencia más larga de instrucciones privilegiadas1, comoanalizáramos en el libro introductorio.Como ya sabemos, Rabbit emplea offsets fijos para cada interrupción. Según ésta

sea causada por un dispositivo interno o externo, la CPU provee la dirección base enel registro IIR o EIR respectivamente, y el periférico determina el offset. Cadaperiférico interno tiene un offset fijo para cada necesidad, y los periféricos externos

disponen de pines separados para su identificación. Estas tablas de direcciones debenmapear en el área root, debido a que se necesita transferir el control a un área decódigo con una posición fija en el mapa de memoria. Dado que cada offset está aunos dieciséis bytes del anterior y del siguiente, es altamente probable que una rutinade interrupciones no quepa en ese espacio, lo que se subsana transfiriendo laejecución a otra región de memoria. Debe tenerse presente, sin embargo, que si se

realizan saltos a xmem, el registro XPC deberá ser salvado en el stack y luegorecuperado al finalizar la rutina, para permitir el retorno a la dirección de ejecucióninterrumpida sin alterar el mapeo de memoria.Recordemos también que la prioridad de ejecución es establecida al aceptarse una

interrupción, según indica el periférico que interrumpe en uno de sus registros decontrol. También puede ser seteada manualmente con la instrucción IPSET n. En

cualquier caso, prioridades sucesivas van siendo almacenadas en el stack de cuatroposiciones contenido en el registro IP; el cual puede ser desplazado a la derecha,

1 Una interrupción es atendida al final de la instrucción en curso, excepto que ésta sea una de lasinstrucciones privilegiadas, las cuales difieren la atención de las interrupciones a la instrucciónsubsiguiente.

13

Page 32: El Camino Del Conejo

Interrupciones

restableciendo la prioridad anterior, mediante la instrucción IPRES; y puede sersalvado y recuperado del stack mediante PUSH IP y POP IP. La prioridad actual delprocesador se ubica en los dos bits menos significativos del registro IP. Al momentode producirse la interrupción, este registro es desplazado a la izquierda dosposiciones y los dos bits menos significativos se llenan con el valor de la prioridadde la interrupción en curso. Esto resulta en que una rutina de interrupciones sólo

puede ser interrumpida por otra de mayor prioridad (a menos que el programador ladisminuya explícitamente).

Cuando deseamos atender interrupciones de un determinado periférico interno concapacidad de interrupción, indicamos el interrupt handler de la siguiente forma:

SetVectIntern(num, my_isr); // setea la dirección// del handler para num

El parámetro num es el número de offset2 dentro de la tabla apuntada por el registroIIR:

Periférico (o RST) num Offset en la tabla

RST 10 0x02 0x20

RST 38 0x07 0x70

Slave Port 0x08 0x80

Timer A 0x0A 0xA0

Timer B 0x0B 0xB0

Puerto Serie A 0x0C 0xC0

Puerto Serie B 0x0D 0xD0

Puerto Serie C 0x0E 0xE0

Puerto Serie D 0x0F 0xF0

PWM (R3000A+) 0x17 0x0170

Codificador en cuadratura (R3000+) 0x19 0x0190

Captura de eventos (R3000+) 0x1A 0x01A0

Puerto Serie E (R3000+) 0x1C 0x01C0

Puerto Serie F (R3000+) 0x1D 0x01D0

2 Existen además offsets para la interrupción periódica y las instrucciones de restart: RST18, RST20 yRST28. Éstos han sido deliberadamente omitidos dado que son mayormente empleados por DynamicC y no se aconseja interferir, a menos que se sepa claramente lo que se hace.

14

Page 33: El Camino Del Conejo

Introducción

Periférico (o RST) num Offset en la tabla

Network Port (R4000:A, R5000:B/C) 0x1E 0x1E0

Timer C (R4000+) 0x1F 0x1F0

En cuanto a las interrupciones externas, las mismas disponen también de sendosoffset fijos, y dos registros de control: I0CR (Interrupt 0 Control Register) e I1CR(Interrupt 1 Control Register). Ambos determinan la prioridad de trabajo al atender

la interrupción.Cuando deseamos atender interrupciones externas, indicamos el interrupt handler

de la siguiente forma:

SetVectExtern3000(int, my_isr); // int= 0 ó 1 SetVectExtern2000(1, my_isr); // sólo para R2000 original (IQ2T)SetVectExtern(int, my_isr); // R4000/5000

Si compartimos un offset entre diversas rutinas, o por algún otro motivonecesitamos conocer la dirección del interrupt handler que está configurada para undeterminado vector, empleamos la siguiente función:

GetVectIntern(num); // para periféricos internosGetVectExtern3000(); // para interrupciones externasGetVectExtern2000(); // sólo para R2000 IQ2TGetVectExtern(); // R4000/5000

En Dynamic C, indicamos que una función es en realidad un interrupt handler de lasiguiente forma:

nodebug root interrupt void my_isr(){

// interrupt handler}

En particular, la palabra clave (keyword) interrupt ocasiona la inserción de códigoque salva todos los registros en el stack (inclusive los alternativos), por lo que esaltamente probable que un interrupt handler en assembler sea más compacto, ya quepodemos optar por guardar en el stack solamente aquellos registros que utilizamos.

En assembler, definimos un interrupt handler como cualquier otra rutina en

assembler; teniendo en cuenta que deberemos salvar y recuperar todos los registrosutilizados o modificados, y restablecer la prioridad de ejecución (registro IP). Uninterrupt handler típico en área root es el siguiente:

#asm rootmy_isr::

; código del handler propiamente dichoIPRES ; restablece la prioridad del procesador

; antes de la interrupciónRET ; devuelve el control del procesador

15

Page 34: El Camino Del Conejo

Interrupciones

#endasm

Dado que existen cuatro niveles de operación posibles, y una interrupción sólo esaceptada si su prioridad es mayor al nivel de operación, el stack de cuatro posicionesllamado registro IP es suficiente como para guardar una escalada de interrupcionesanidadas. Sin embargo, si en algún interrupt handler el programador explícitamentedisminuye la prioridad para permitir interrupciones de igual jerarquía, podría

producirse (dependiendo del sistema y de si existen fuentes de interrupción conprioridades elevadas) una escalada que ocasione la pérdida de la prioridad deejecución original. En este caso, lo que hacemos es salvar el registro IP en el stackantes de modificar la prioridad de operación, y recuperarlo antes de salir:

#asm rootmy_isr::

PUSH IPipset 0; código del handler propiamente dichoPOP IPIPRES ; restablece la prioridad del procesador

; antes de la interrupciónRET ; devuelve el control del procesador

#endasm

Ejemplos

Interrupciones internas: buzzer en Timer B

En este ejemplo, utilizaremos la interrupción de comparación del Timer B paraconmutar un par de pines, generando dos señales en contrafase que bien pueden ser

transmitidas a un buzzer. Utilizamos los mismos pines que controlan los LEDs de unRCM2200, disponibles en el jumper JP1 de la placa para prototipos que se incluyeen el kit de desarrollo.

void timerb_isr();

unsigned int taimer;

void main(){

WrPortI(PEFR,&PEFRShadow,0);WrPortI(PEDDR,&PEDDRShadow,0x82);WrPortI(PECR,&PECRShadow,0);WrPortI(PEDR,&PEDRShadow,0x80); /* prende un LED, apaga el otro */

SetVectIntern(0x0B, timerb_isr); // setea vector de interrupciónWrPortI(TBCR, &TBCRShadow, 0x09); // clock timer B con (perclk/16)

// prioridad de interrupción: 1WrPortI(TBM1R, NULL, 0x00); // el timer interrumpe cuandoWrPortI(TBL1R, NULL, 0x89); // el contador llega a 0089taimer=0x089;

16

Page 35: El Camino Del Conejo

Ejemplos

WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupción// por match en B1

while(1);}

#asmtimerb_isr::

push af ; salva registrosioi ld a, (TBCSR) ; reconoce interrupciónpush hlld hl,(taimer) ; lee compare shadowpush bcld bc,0x0089add hl,bc ; suma 0x0089ld (taimer),hl ; y guarda en shadowld a,hrrca ; MSbs en bits 7,6rrca ; descarta el resto de los bitsioi ld (TBM1R), a ; carga en match register

ld a,lioi ld (TBL1R), a ; int siguiente: timer+0089hioi ld a,(PEDR) ; lee port Exor 0x82 ; invierte bits de LEDs (alternado)ioi ld (PEDR), a ; escribe port Epop bc

pop hlpop af ; recupera registrosipres ; restablece interrupcionesret ; retorna

#endasm

Como vimos, la función SetVectIntern() es la que define la dirección del interrupthandler para un periférico determinado. Le pasamos como parámetros: el offsetcorrespondiente al Timer B y la dirección del handler. Entonces, al llegar el contador

del Timer B al valor de cuenta esperado y producirse una comparación exitosa, éstegenera una interrupción. El registro IIR apunta a la tabla de offsets3, entonces en ladirección IIR+0xB0 habrá una instrucción de salto (jump) a nuestra rutina4

timerb_isr, donde recibimos el control con el procesador corriendo a la prioridadque especificamos al escribir en el registro TBCR. Necesitamos restablecer laprioridad anterior antes de salir, lo cual hacemos con la instrucción IPRES. En lo

referente al Timer B, no podemos leer el valor de comparación, pero sí guardarlo enun shadow register para tenerlo disponible. Los bits más significativos del registro decaptura están en los bits 7 y 6, por lo que debemos trasladar nuestros bits 1 y 0 adicha posición, ignorando el resto. Con los valores indicados, el buzzer recibe 5KHzen un módulo con clock de 22MHz.

Tendremos otro ejemplo de interrupciones internas más adelante, cuando

analicemos la forma de conexión de un encoder codificador en cuadratura al módulo

3 Esto se maneja de forma transparente por Dynamic C, el usuario no necesita manipular (ni siquieratener conciencia de su existencia...) este registro o la tabla, en forma directa.

4 En realidad, esto actualmente es más complejo debido a que la tabla de offsets puede estar en flash(modo I&D). Sin embargo, existe un esquema que permite modificar las direcciones de interrupthandler como si la tabla estuviera en RAM, haciendo que esto sea transparente, y permitiendosimplificar la operación de forma didáctica como lo hemos hecho.

17

Page 36: El Camino Del Conejo

Interrupciones

decodificador en cuadratura de un R3000; también más información sobre el TimerB, todo en el capítulo sobre periféricos internos.

Interrupciones externas: lector ABA (Track II)

En este ejemplo, tomamos los datos de una lectora de tarjetas RFID con interfazABA (Track II). La interfaz consta de dos líneas: una de clock y una de datos. Losdatos son considerados válidos durante el flanco descendente del reloj, por lo queutilizaremos esta señal para generar una interrupción al procesador y así observar la

señal de datos mediante la lectura de un port de I/O (PE.0 en este caso).Si nuestro procesador es R2000C (IQ5T), lo cual es lo más probable debido a que

hace ya algunos años que está en el mercado, la forma de seleccionar el puerto deinterrupciones es la siguiente:

WrPortI(PEDDR, &PEDDRShadow, 0xEE); // PE0,4=inputWrPortI(PEFR, &PEFRShadow, 0x00);SetVectExtern3000(0, my_isr); // setea la dirección

// del handlerWrPortI(I0CR, &I0CRShadow, 0x13); // habilita INT0 PE4,

// flanco descendente// prioridad 3

Si por el contrario tenemos un R2000 original (IQ2T), deberemos conectar dos de

los pines e ingresar por uno de ellos tal cual indica la nota técnica TN301 en queRabbit describe como resolver el conocido problema de ese micro.

El software se modifica a :

WrPortI(PEDDR, &PEDDRShadow, 0xCE); // PE0,4,5=inputWrPortI(PEFR, &PEFRShadow, 0x00);SetVectExtern2000(3, my_isr); // setea la dirección

18

PE.4

PE.0

Clock

Data

PE.4

PE.0

Clock

Data

PE.5

1K

Page 37: El Camino Del Conejo

Ejemplos

// del handler y su// prioridad

WrPortI(I0CR, &I0CRShadow, 0x13); // habilita INT0 PE4,// flanco descendente

WrPortI(I1CR, &I1CRShadow, 0x13); // habilita INT1 PE5,// flanco descendente

La rutina de interrupciones que es disparada por los flancos de clock descendentesy almacena los bits de datos es la siguiente:

my_isr::push af ; salva registrospush hl ; HL y BC usados por rotaterightpush bcld a,(count) ; counter (cuenta pulsos de clock)inc a ; incrementald (count),a ; counterioi ld a,(PEDR) ; lee port Erra ; pone PE.0 en carrycall rotateright ; ingresa bits por Msb, mueve todo el bufferpop bcpop HLpop af ; recupera registrosipres ; restablece interrupcionesret ; retorna

Para aquellos interesados en este tipo de lectores, pueden consultar en el CD coninformación adicional la nota CAN-050. Tendremos otro ejemplo de uso deinterrupciones externas más adelante, cuando analicemos la forma de conexión de unencoder codificador en cuadratura a un R2000, en el capítulo sobre periféricos

internos.

19

Page 38: El Camino Del Conejo
Page 39: El Camino Del Conejo

Tiempos de ejecución

Introducción

En un sistema que realiza solamente una tarea, generalmente no tenemos mayoresinconvenientes con el manejo de tiempos de ejecución. Llegado el caso en que latarea en sí requiera un manejo delicado del tiempo, siempre está el recurso de contar

ciclos de ejecución y escribir el código específicamente para resolverla. Entre losnumerosos ejemplos podemos citar los generadores de video por software.En cuanto agregamos al video el juego de tenis de mesa, ya hay dos tareas: la

generación del video, y el juego en sí. En aplicaciones en que dos tareas están biendefinidas, y los requerimientos de tiempo de ambas son bien claros, es posibleaprovechar los tiempos improductivos de una de ellas (retrazado vertical) para

realizar la otra. Por supuesto que también es posible escribir un sólo códigoentrelazado que atienda ambas tareas a la vez, de hecho estos juegos con video porsoftware no pueden darse el lujo de andar regalando ciclos de CPU; sin embargo,cuando la cantidad de tareas aumenta, la creciente complejidad e interdependenciahace que deba buscarse una forma de concebir y escribir el código que permitacontrolar y mantener el funcionamiento de forma clara y eficiente, tratando de

reducir el todo a pequeñas partes manejables.

Conceptos

Interrupciones

Como todos sabemos, los microprocesadores permiten interrupciones. Laparticularidad de las interrupciones es que se caracterizan (como su nombre loindica) por interrumpir al procesador en cualquier momento, de forma indistinta,aunque a veces pareciera que lo hacen de forma intencional sobre la tarea que másnos complica. La razón fundamental de su existencia es permitir que el micro pueda

atender eventos que no es conveniente estar esperando o consultando, y/o que debenser atendidos rápidamente y sin demoras.

Cuidadosamente utilizadas, son un poderoso aliado, y hasta es posible armaresquemas de tareas múltiples asignando a cada tarea una interrupción periódicaindependiente. Su uso indiscriminado puede generar más inconvenientes quebeneficios, particularmente no es posible compartir variables o subrutinas sin tomar

las debidas precauciones de accesibilidad, atomicidad y reentrabilidad. Una

21

Page 40: El Camino Del Conejo

Tiempos de ejecución

interrupción puede ocurrir en cualquier momento, y si ocurre en medio de laactualización de una variable multi-byte puede causar (y de hecho lo hace) estragos alas demás tareas, que quedan con valores "parcialmente alterados", que en el mundoreal son valores incorrectos, y probablemente de peligrosa incoherencia para elsoftware. Un ejemplo típico y frecuentemente olvidado, son las variables detemporización, o fecha y hora. El programa principal se pone alegremente a

descomponerlas byte a byte, de forma de mostrar su contenido en el display, sinpensar que son actualizadas por interrupciones, y éstas no tienen por qué ocurrirantes o después de nuestro acceso y no durante1. Si se muestran los valores en undisplay no hay mayor problema, al cabo de un segundo volveremos a la normalidad,pero si esto va a un log o informe vamos a tener que dar muchas explicaciones, comoen el ejemplo siguiente, en el que aparece un registro con casi un año de diferencia:

Para subsanar este inconveniente, Dynamic C incorpora el concepto de variablescompartidas (shared variables). Ante un acceso o modificación de una variable asídefinida, se produce una inhibición de las interrupciones durante el transcurso de la

misma. De hecho, las variables de temporización de Dynamic C están declaradascomo shared.Accesos reiterados a variables de tipo shared, producen inhibiciones reiteradas enlas interrupciones, lo que se traduce como variaciones en la latencia de atención de

1 Una interrupción puede ocurrir en cualquier momento, siempre que esté habilitada. La probabilidadde que suceda justo en el instante en que se está produciendo la lectura es bastante baja,particularmente si ésta es breve y ocurre de forma no sincronizada con las interrupciones, y demanera no frecuente. Sin embargo, es mayor que cero, y por ende, en un número lo suficientementealto de repeticiones, es de esperarse que ocurra. El dejar librado el correcto funcionamiento de unequipo a la función de distribución de Poisson no debería ser considerada buena práctica de diseño...

22

59

59

23

59

59

05

23

59

59

23

05

31

12

01

01

06

00

00

00

05

01

01

interrupt, cambio de segundo

valor del RTC valor tomado

LOG

INT RTC

evento, toma de

fecha y hora,

log

1 3

2

1

2

3

Page 41: El Camino Del Conejo

Conceptos

interrupciones, las cuales sólo pueden ser atendidas cuando está permitido. Rabbitincorpora cuatro niveles de prioridad de ejecución, para que el programador puedadisponer de mayor libertad y minimizar la latencia a interrupciones críticas. Unanálisis del problema de la latencia en generación de señales en puertos de entrada-salida se encuentra en el capítulo sobre periféricos internos. Particularmente,hacemos una demostración del jitter en latencia de interrupciones.

Multitarea

Por multitarea entendemos la ejecución aparentemente simultánea de varias tareas.Es "aparentemente simultánea" porque a menos que tengamos varios procesadores oun procesador con muchos cores y/o unidades de ejecución, una y sólo unainstrucción se ejecuta en un ciclo de máquina. Mediante algún método deconmutación, se administra el tiempo de procesador utilizado, de modo que todas lastareas puedan jugar un rato con él.

El hecho de conmutar o cambiar de tareas (task switching), si pretendemos quecada tarea resulte independiente de las demás, implica salvar y recuperar el contextode operación imperante en cada tarea; de modo de poder suspenderla en undeterminado instante, y poder reanudarla luego, y que dicha tarea no se entere quefue suspendida. Este intercambio de contexto (context switching) impone underroche de tiempo improductivo (aparentemente), y una cierta latencia en los

tiempos asociados a la ejecución de las tareas. A fin de poder seguir los ejemplos acontinuación, vamos a definir tres tareas:

Multitarea cooperativo

En todo sistema multitarea cooperativo, cada tarea es "despachada"

(dispatched)cuando las demás ceden el procesador, lo cual hace que la repetitividadde una tarea en particular no pueda ser fácilmente predicha. Si el sistema escooperativo, debe existir una especie de "acuerdo de buena voluntad" entre laspartes, en este caso las tareas, de ceder el procesador cada cierto tiempo parapermitir el normal desenvolvimiento de las demás. En Rabbit, Dynamic C incluyesoporte para multitarea cooperativo en forma de costates y cofunctions; aunque el

usuario siempre puede escribir su código en forma de handlers y máquinas de

23

procesar config

esperar datos

procesar datos

esperar operador

validar proceso

mostrar valores en display

dormir 1 segundo

esperar comando

procesar comando

entregar resultados

Tarea 1 Tarea 2 Tarea 3

Page 42: El Camino Del Conejo

Tiempos de ejecución

estados. Cada tarea debe detectar los ciclos de espera y ceder el procesador a lasdemás, de modo que todas tengan oportunidad de ejecutarse. En caso de no existirciclos de espera, deberán inventarse y ceder el control en puntos estratégicos, dadoque la única forma de que todas las tareas se ejecuten, es que todas y cada una deellas tengan la buena voluntad de ceder el control periódicamente. Si el sistema estábien planeado, todas las tareas parecen funcionar a máxima velocidad, dado que

operan rápidamente durante un tiempo breve, permaneciendo inactivas mientrasesperan a los eventos que determinan los cambios.El cambio de contexto suele realizarse de una forma simple; si las tareas son

máquinas de estados, hay solamente un elemento que debe salvarse: el estado. Deesta forma, cada tarea cede el control en los estados de espera, voluntariamente, ysabe que las variables compartidas con otras tareas sólo podrán ser alteradas durante

los tiempos en que ella lo permite; excepto, claro está, por las interrupciones.Las sentencias de espera que utilizan el sistema de temporización interno, por

ejemplo:

waitfor(DelayMs(timedelay));

tienen una incertidumbre propia del mismo, dado que como se utiliza un reloj queestá siempre corriendo, el último dígito de la demora solicitada es incierto. Por

ejemplo, la variable MS_TIMER es actualizada a cada milisegundo, pero unallamada a DelayMs(1) puede ocurrir unos microsegundos antes de que suceda laactualización de dicha variable, con lo cual la demora es prácticamente nula.Por ejemplo, las tareas descriptas anteriormente se suceden de la siguiente forma,conmutando de tarea en los tiempos muertos, es decir, aprovechando los ciclos deespera para ceder el control a otras tareas:

Nótese como, al cederse el control una vez terminada una determinada operación,es posible determinar qué es lo que es necesario salvar del contexto (generalmente elestado), y ceder voluntariamente el control. El inconveniente es que el tiempo deejecución depende fuertemente de la buena voluntad de las otras tareas.

24

procesar config

esperar datos

mostrar valores en display

dormir 1 segundoesperar comando

procesar comando

entregar resultados

esperar datos

procesar datos

esperar operadordormir 1 segundo

esperar comando

validar proceso

esperar operador

mostrar valores en display

operador

datos

comando

tiempo

1 segundo

Tarea 1 Tarea 2 Tarea 3eventos

dormir 1 segundo

Page 43: El Camino Del Conejo

Conceptos

Multitarea de tipo preemptive

En un sistema multitarea de tipo preemptive, hay un ejecutor (task scheduler) quetoma la decisión de administrar el tiempo de procesador de forma salomónica.Cuando llega el momento, la tarea que se está ejecutando es suspendida, y se pasa elcontrol a otra. La predictibilidad de los tiempos de ejecución depende de la forma deoperación del ejecutor y de como administra sus recursos.

El cambio de contexto suele realizarse de forma abrupta, por este motivo, lo quedebe salvarse es la totalidad de los registros del procesador, manteniendo un stackseparado para cada tarea. Todo esto suele emplear un tiempo bastante mayor a loque se necesita en un sistema cooperativo. La diferencia fundamental es que cadatarea no debe prestar atención a los ciclos de espera y puede funcionar como siestuviera sola, aunque en realidad su velocidad de ejecución se va reduciendo a

medida que se incorporan nuevas tareas, dado que se comparte el procesador entretodas.La desventaja principal es obvia, se "interrumpe" a la tarea en curso, por lo tanto,tenemos que tomar las mismas precauciones en cuanto a variables compartidas queanalizáramos con las interrupciones.Siguiendo con el ejemplo, en un sistema multitarea de tipo preemptive, las tareas

son interrumpidas (hayan terminado o no) cuando transcurre el tiempo asignado. Eneste caso, asumimos un tiempo uniforme e idéntico para todas las tareas, y además elsistema es lo suficientemente inteligente como para permitir la cesión del control enciclos de espera (lo cual siempre suele depender de la buena voluntad delprogramador)

25

procesar config

mostrar valores en display

dormir 1 segundo

esperar comando

procesar comando

entregar resultados

procesar datos

esperar operador

dormir 1 segundo

validar proceso

mostrar valores en display

operador

datos

comando

tiempo

1 segundo

procesar config

esperar comando

procesar comandoesperar datos

mostrar valores en display esperar comando

procesar comando

procesar config

esperar datos

dormir 1 segundo

procesar datos

dormir 1 segundo procesar comando

dormir 1 segundo

esperar operador dormir 1 segundo esperar comando

dormir 1 segundo

esperar comandovalidar proceso

mostrar valores en display

esperar operador

evento Tarea 1 Tarea 2 Tarea 3

procesar datos

entregar resultados

Page 44: El Camino Del Conejo

Tiempos de ejecución

Nótese como, al poder interrumpirse la tarea en cualquier momento, es preciso salvartodo el contexto y tener especial cuidado con variables compartidas, debido a que lasmismas pueden actualizarse cuando no lo esperamos. El tiempo de ejecución es unavance "lento pero seguro", dado que cada tarea es interrumpida frecuentementepero no demorada por intervalos largos.

Sentencia Slice

En Rabbit, Dynamic C incorpora una facilidad que permite administrar el tiempo deprocesador asignado a una serie de tareas. Cada tarea se define dentro de un slice, alque se le asigna un tamaño de stack y un tiempo máximo de ejecución en ticks2. Si latarea termina de ejecutarse antes de este tiempo, se continúa con el slice siguiente. Sino termina, de forma muy democrática se la suspende y se pasa el procesador al slice

siguiente. Una vez atendidos todos los slices, se retoma la ejecución desde elprimero, resumiéndolo desde donde fue interrumpido, si es que lo fue. Existe unejemplo excelente para visualizar esto, y es una de las samples provistas(Samples\slice\slice02.c). En este ejemplo, tenemos dos tareas importantes, y una notanto. Las tareas importantes tienen un tiempo máximo de procesador asignado (500ticks) cada una, y hay un tiempo de loop (1000 ticks). En condiciones de alta

ocupación, es decir, cuando ambas tareas principales están ocupadas al máximo, latarea en segundo plano (background) no se ejecuta; sólo se le permite correr si lastareas principales no emplean la totalidad del tiempo que tienen asignado:

#class auto

unsigned int looptime, task1slice, task2slice;

int Task1(){

; // first task's code}

int Task2(){

; // second task's code}

BackgroundTask(){

; // background task's code} void main(){

long bgtimer,timeleft;

looptime=1000;task1slice=500;task2slice=500;

for(;;) {

2 1 tick = 1/1024 seg.

26

Page 45: El Camino Del Conejo

Conceptos

bgtimer = TICK_TIMER + looptime;

slice(200,task1slice) {Task1();

}

slice(200,task2slice) {Task2();

}

timeleft = bgtimer-TICK_TIMER;

if(timeleft>=0) {

slice(200,(int)timeleft) {BackgroundTask();

}}

}}

Si ejecutamos esto tal como está, poniendo un breakpoint en donde está resaltado,si miramos el valor de timeleft veremos que es 999, es decir, ambos slices sóloemplearon un tick, nos quedan 999 ticks para ejecutar la tarea en background. Si

corremos paso a paso, veremos el orden en que se ejecutan.Más allá de la paupérrima utilidad de un esquema con tareas vacías, tenemos ennuestras manos un poderoso esqueleto para desarrollar un sistema de multitarea conprioridades y velocidades de ejecución diferentes, incluso alterables en formadinámica, dado que el parámetro que estamos pasando es una variable.

Ejecutemos ahora samples\slice\slice01.c, poniendo breakpoints donde está

resaltado, y veremos que evidentemente cada tarea está siendo interrumpida, dadoque se trata de loops infinitos (aunque la ejecución del printf() ya debería llamarnosla atención por sí sola):

#class auto

shared long x,y; void main(){

x=y=0; // initialize the counters

for(;;) { // outside loop

slice(200,25) { // run this section for 25 ticks

for(;;) {x++;

}}

slice(200,50) { // run this section for 50 ticks

for(;;) {y++;

}}

27

Page 46: El Camino Del Conejo

Tiempos de ejecución

printf("x=%ld, y=%ld\n",x,y); // print the results}

}

También es posible aplicar técnicas de buena vecindad en estos casos, y cedervoluntariamente el control, dado que waitfor y yield pueden utilizarse dentro de unslice. En el ejemplo siguiente, que es una modificación del primer ejemplo,slice02.c, el primer slice tiene un ciclo de espera de 10 segundos (lo cual es

totalmente inútil pero permite ejecutar paso a paso y ver como se distribuye laejecución entre la tareas), pero podría ser una espera a un evento externo; la segundatarea cede voluntariamente el control entre la ejecución de sus dos subtareas (que eneste caso es la misma por simplicidad), que a su vez son cofunctions y tienen ciclosde espera. La ejecución paso a paso de dicho ejemplo permite observar claramentelos puntos donde se cede el control. Debe tenerse en cuenta que el tiempo perdido en

avanzar paso por paso influye en el disponible para cada slice.

#class auto

unsigned int looptime, task1slice, task2slice;

int Task1(){

; // first task's code}

cofunc int Task2(){

waitfor(DelayMs(10)); // second task's code}

BackgroundTask(){

; // background task's code}

void main(){

long bgtimer,timeleft;

looptime=1000;task1slice=500;task2slice=500;

for(;;) {bgtimer = TICK_TIMER + looptime;

slice(200,task1slice) {Task1();waitfor(DelayMs(10000));

}

slice(200,task2slice) {wfd Task2();yield;wfd Task2();

}

timeleft = bgtimer-TICK_TIMER;

28

Page 47: El Camino Del Conejo

Conceptos

if(timeleft>=0) {slice(200,(int)timeleft) {

BackgroundTask();}

}}

}

De esta forma, combinando recursos de uno y otro mundo, es posible lograrcomplejos y poderosos esquemas, sin la complejidad de tener que hacerlo

manualmente, o tener que recurrir a un sistema operativo.

El inconveniente principal es que no es posible utilizar slice si se emplea el stack

TCP/IP.

RTOS

Las aplicaciones que requieren alta predictibilidad en los tiempos de ejecución sedesarrollan directamente sobre el hardware, o empleando RTOS (Real TimeOperating Systems). Los RTOS incluyen complicados sistemas para garantizar unamínima latencia y permitir que las tareas se desarrollen dentro de límites

establecidos, pero para esto es necesario que el sistema sea preemptive.Existen algunos ejemplos muy conocidos de RTOS como uCOS, y existen

excelentes y muy costosos RTOS. Para aquellos interesados, uCOS II se incluyecomo un módulo de Dynamic C.

Recursos

Si una aplicación requiere alta predictibilidad en los tiempos de ejecución y notolera las variaciones de timing introducidas por las interrupciones y la multitarea

cooperativa, se deberá recurrir a las técnicas tradicionales que se emplean para estoscasos, cuando no se dispone o no se quiere disponer de un RTOS:� Aprovechar el hardware para la generación y medición de eventos sensibles altiming. Los puertos D y E del R2000 pueden sincronizar sus cambios con lostimers. El R3000 incorpora contadores de eventos. Analizamos ambas cosas enel capítulo sobre periféricos internos.

� Atender los dispositivos o tareas críticas por interrupciones. Pueden usarse lostimers A y B para generar interrupciones periódicas. Hemos estudiado este temaen el capítulo anterior.� No poner tareas críticas dentro de loops en que otras tareas (costates, máquinasde estados, etc.) puedan alterar sus tiempos de ejecución.� Elevar la prioridad de ejecución del procesador en la tarea crítica para que no

acepte interrupciones. En Rabbit, esto se realiza operando sobre el registro IP(Interrupt Priority) del procesador.

29

Page 48: El Camino Del Conejo

Tiempos de ejecución

� Administrar la prioridad de ejecución del procesador en diversas tareas, deacuerdo a su grado de criticidad, para evitar introducir latencia en la o las tareascríticas. Las tareas de criticidad intermedia pueden interrumpir a las de criticidadbaja, y ser interrumpidas por las de criticidad más alta; pero no al revés.� Para controlar los tiempos de espera o períodos de inactividad, se dispone detimers en forma de shared variables, como TICK_TIMER, MS_TIMER y

SEC_TIMER, las mismas son puestas a cero al arranque del sistema, ySEC_TIMER en particular toma el valor guardado en el RTC. Los procesos quedeban depender de una cierta relación temporal deberán utilizar TICK_TIMER oMS_TIMER, dado que SEC_TIMER podría tener que ser actualizadamanualmente ante un cambio de fecha y hora. Para observar un ejemplo,remitirse al apartado sobre reloj de tiempo real en el capítulo sobre periféricos

internos.

Debe tenerse presente que siempre se tendrá una latencia para atender unainterrupción, correspondiente a la duración de la instrucción en curso al momento degenerarse el pedido de interrupción (asumiendo que las interrupciones esténhabilitadas). Un análisis de esto se encuentra, como dijéramos, en el capítulo sobre

periféricos internos, en el apartado sobre control por hardware de los pines delpuerto paralelo.

Particularmente en Rabbit, el sistema de debugging está siempre activo (auncuando no esté conectado a una PC corriendo Dynamic C), lo cual introducellamadas periódicas al BIOS en el código. Puede inhabilitarse este sistema

anteponiendo nodebug a la definición de las funciones críticas, o directamentedefiniendo #nodebug a la cabeza del archivo que contiene el programa principal

El aumentar la prioridad de ejecución de una tarea sólo garantiza que ésta no seráinterrumpida, para garantizar la periodicidad o el comienzo de la misma se deberáutilizar una interrupción o algún complejo esquema de task scheduling (asignación

de tiempos de ejecución entre tareas).

30

Page 49: El Camino Del Conejo

Periféricos Internos

Ports paralelo

Shadow registers

El concepto de shadow register es bastante viejo, y responde a la necesidad deretener lo que se escribe en una posición de I/O que no puede ser leída. Dado que noes posible leer lo que se escribió, se debe guardar en algún lado para poder saberlo.

Recordemos que las operaciones de seteo y reseteo de bits son en realidad,operaciones de lectura-modificación-escritura (read-modify-write); la gran mayoríade los micros no direccionan bits sino bytes o words, y para poder setear un bit en unregistro, primero hay que leer dicho registro, setear el bit, y luego volverlo a escribir,aunque esto se realice en una sola instrucción assembler, e incluso si esta operaciónse realiza en un ciclo de máquina. Si el registro en cuestión es en realidad un registro

de salida que no nos permite capacidad de leerlo, para poder setear un bitnecesitamos saber cómo están los otros bits del registro, cosa que no podemos hacer.Disponiendo de un shadow register, escribimos en éste el valor que va en el registrode I/O y luego lo transferimos. Cuando queremos setear o resetear un determinadobit, lo hacemos en el shadow register (que no es otra cosa que una posición dememoria con un nombre sombrío) y lo transferimos al registro de I/O.

ld hl, PDDDRShadow

ld de, PDDDR

set 7,(HL) ; pone PD.7 como salida

ioi ldd

ret

Si nuestro micro en cuestión, como es el caso de Rabbit, permite además lectura delos registros de I/O, la utilidad del shadow register parecería haber caducado paraellos. Sin embargo, existe además otra utilidad que no es evidente a primera vista,

pero resulta esencial cuando se trabaja con puertos de entrada-salida bidireccionales.Todos sabemos que un puerto de I/O bidireccional no es en realidad bidireccionalsino que tiene una u otra dirección, según decidamos configurarlo. Por lo general,existe un registro que decide la dirección del mismo, y otro en el que se escriben yleen los datos. Si el micro tiene registros separados para entrada y salida (porejemplo PxIN y PxOUT o PINx y PORTx), no nos vamos a enterar; pero si

solamente existe un único registro PxDR como en Rabbit, podemos llegar aencontrarnos con un pequeño gran problema, que deberemos resolver mediante laasistencia del shadow register.

31

Page 50: El Camino Del Conejo

Periféricos Internos

Supongamos que tenemos una tarea que hace uso de un pin de I/O en modobidireccional de la forma pseudo-open-collector, es decir, tiene conectada unaresistencia de pull-up, ponemos el registro de salida en cero, y lo controlamosmediante el registro de dirección. Cuando queremos un uno, lo ponemos comoentrada, la resistencia de pull-up proveerá el uno. Cuando queremos un cero, loponemos como salida, el puerto provee el cero. Hasta aquí todo bien, no se trata de

nada descabellado1 que no hayamos hecho cientos de veces. Las rutinas acontinuación son a modo de ejemplo, lo más probable es que el código estéembebido dentro de alguna otra rutina:

setup:: ld hl, PDDRioi res 7,(HL) ; pone en cero PD.7ld a,0x00 ; inicializa pines como entradasld (PDDDRShadow),Aioi ld (PDDDR),A ; (PD.7=1)ret

cero: ld hl, PDDDRShadowld de, PDDDRset 7,(HL) ; pone PD.7 como salidaioi lddret

uno: ld hl, PDDDRShadowld de, PDDDRres 7,(HL) ; pone PD.7 como entradaioi lddret

Como PDDDR es un registro que sólo puede escribirse, no tenemos más remedioque utilizar el shadow register.Supongamos ahora que a este equipo que tenemos andando desde hace años, le

agregamos otra tarea y que como los puertos de entrada-salida no son infinitos,resulta que tenemos un pin de I/O en ese mismo port que utilizamos para la nueva

tarea. Obviamente, esta tarea se dedicará a setear y resetear el bit de su interés, y notendría por qué modificar los bits que no le corresponden, ¿no es cierto? Comosolamente la nueva tarea va a trabajar sobre el registro PDDR, no necesitamosmolestarnos con un shadow register, ¿no es así? Entonces, esta nueva tarea, llamada'tarea 2', opera sobre el puerto D, de la siguiente forma:

activa: ld hl, PDDRioi set 6,(HL) ; pone PD.6 en altoret

desactiva:ld hl, PDDRioi res 6,(HL) ; pone PD.6 en bajoret

Asumiendo que modificamos la rutina de setup y configuramos PD.6 como salida:

niusetup::

1 Quienes me conocen personalmente saben que no debería usar esta palabra...

32

Page 51: El Camino Del Conejo

Ports paralelo

ld hl, PDDRioi res 7,(HL) ; pone en cero PD.7ld a,0x40 ; inicializa pines como entradas (PD.6=salida)ld (PDDDRShadow),Aioi ld (PDDDR),A ; (PD.7=1)ret

A simple vista está todo bien, pero misteriosamente, nuestra interfaz bidireccionalcon pseudo-open-collector de repente deja de funcionar. Investigando un poco,logramos llegar a detectar que esto sucede cada vez que la salida de la nueva tarea se

activa o desactiva cuando nuestro viejo y querido PD.7 estaba en uno.La respuesta al enigma es obvia en cuanto nos sacamos de la cabeza el seteo y

reseteo milagroso de bits y recordamos que todo bit set y bit reset es en realidad unaoperación read-modify-write. Cada vez que la nueva tarea decide setear el bit 6 delregistro PDDR, realiza una lectura del mismo, con lo cual lee también el bit 7 (y el 5,4, ...). A continuación, modifica el bit 6, y escribe nuevamente en PDDR,

escribiendo en el bit 7 (y en todos los restantes) el valor que acaba de leer. Si PD.7estaba configurado como entrada en ese momento, y se lee como un uno, se escribirácomo uno; rompiendo lo que asume la tarea 1. El estado de PD.7 no cambia, siguesiendo uno, después de todo era una entrada y tiene un pull-up; pero cuando nuestravieja y querida rutina intenta ponerlo en cero configurándolo como salida, no haceotra cosa que forzarlo en uno rabioso, dado que el bit set escribió un uno en el bit 7

de PDDR.

activa: ld hl, PDDR ; tarea 2 (podría ser 'desactiva' indistintamente)ioi set 6,(HL) ; pone PD.6 en alto y los demás en lo que ve

; que están al leerlos. Si son salidas,... ; no cambian de estado, si son entradas,

; no molesta (mientras son entradas...); vio PD.7=1, ergo escribe PD.7=1

cero: ld hl, PDDDRShadow ; tarea 1ld de, PDDDRset 7,(HL) ; pone PD.7 como salida, pero PDDR.7=1ioi ldd ; oops, falló !

33

PD.7

PD.6

oh, no...

Tarea 1

Tarea 2

Page 52: El Camino Del Conejo

Periféricos Internos

...

La solución, si no podemos darnos el lujo de invertir en un shadow register, esvolver a escribir un cero cada vez que vamos a operar sobre PD.7 para ponerlo enestado bajo, sin importar lo que otras tareas nos hayan hecho:

cero: ld hl, PDDRioi res 7,(HL) ; pone PD.7 = 0ld hl, PDDDRShadowld de, PDDDRset 7,(HL) ; pone PD.7 como salidaioi lddret

uno: ld hl, PDDDRShadowld de, PDDDRres 7,(HL) ; pone PD.7 como entradaioi lddret

No obstante, lo correcto sería utilizar shadow registers y respetar a las demás tareas,

particularmente cuando la asincronicidad entre éstas puede causar que se modifiqueun registro en medio de una operación, cuando no se espera que pueda haber sidomodificado. Supongamos que la tarea 2 se ejecuta por interrupciones (o tal vezdentro de un slice2), y que por obra y gracia de la probabilidad distinta de cero, luegode un largo tiempo de estar funcionando todo sin problemas, la interrupción que pasael control a la tarea 2 ocurre en medio de la tarea 1, justo después de que se pone

PD.7 en cero:

cero: ld hl, PDDRioi res 7,(HL) ; pone PD.7 = 0 (pero sigue siendo entrada)

INTERRUPCIÓN -->...

2 Hemos analizado los bloques slice en el capítulo sobre tiempos de ejecución en el apartado sobremultitarea de tipo preemptive.

34

PD.7

PD.6

oh, no...

Tarea 1

Tarea 2

(IRQ)

Page 53: El Camino Del Conejo

Ports paralelo

activa: ld hl, PDDR ; tarea 2 (podría ser 'desactiva' indistintamente)ioi set 6,(HL) ; pone PD.6 en alto y los demás en lo que ve... ; vió PD.7=1, ergo escribe PD.7=1ipresret

RETORNO <--

ld hl, PDDDRShadowld de, PDDDRset 7,(HL) ; pone PD.7 como salida, pero PDDR.7=1ioi ldd ; oops, falló !...

Por esta razón, no sólo debemos emplear un shadow register, sino que elmovimiento del shadow register al port de I/O lo hacemos de forma atómica, esdecir, mediante una instrucción que no puede ser interrumpida, como analizáramosen el libro introductorio.

activa: ld hl, PDDRShadowld de, PDDRset 6,(HL) ; pone PD.6 en alto en shadowioi ldd ; transfiere a I/O: (HL) -> (DE)ret

desactiva:ld hl, PDDRShadowld de, PDDRres 6,(HL) ; pone PD.6 en bajo en shadowioi ldd ; transfiere a I/O: (HL) -> (DE)ret

La otra opción es inhibir las interrupciones cada vez que se opera sobre el port,

pero generalmente esto se paga en latencia, tema que desarrollaremos en el apartadosiguiente.

Cambio de pines por hardware

Siguiendo con el análisis del último caso presentado en el apartado sobre shadowregisters, tenemos un ejemplo similar. El problema que proponemos es muy parecidoal ejemplo anterior, pero dado que el efecto se produce al revés, es generalmentepasado por alto.

Supongamos que tenemos, en este caso, un ejemplo similar al que hemos visto en elcapítulo sobre interrupciones, con el Timer B; en el cual una rutina de interrupcionesmodifica el estado de uno o varios pines para controlar un buzzer. Si bien en estosejemplos utilizamos un buzzer por su simpleza, el mismo análisis bien puede valerpara cualquier tipo de tarea que presente una periodicidad de interrupciones, en lacual una diferencia notable en la latencia de atención de las mismas es perceptible.

Supongamos también, que nuestro programa principal hace uso del mismo port deI/O que la rutina de interrupciones, supongamos que no podemos partir nuestraoperación en varias elementales (como por ejemplo alterar los bits de a uno), todoslos bits deben cambiar a la vez, pero como no hay ningún pin que se utilice comobidireccional, creemos "estar a salvo". Nuestro programa principal, entonces, no

35

Page 54: El Camino Del Conejo

Periféricos Internos

emplea shadow registers ni maneja el puerto de I/O de forma atómica, de la siguienteforma:

ioi ld A,(PEDR)and 0xEEioi ld (PEDR),A

Supongamos (finalmente), que una interrupción del Timer B cae justo en el mediode las instrucciones de manejo del puerto paralelo en el programa principal:

main...ioi ld A,(PEDR)and 0xEEioi ld (PEDR),A...

int...push af...ioi ld a,(PEDR) ; lee port Exor 0x82 ; invierte bits de LEDs (alternado)ioi ld (PEDR), a ; escribe port E...pop afipresret

Ej:

...ioi ld A,(PEDR)

<- INTERRUPCIÓN...push af...ioi ld a,(PEDR) ; lee port Exor 0x82 ; invierte bits de LEDs (alternado)ioi ld (PEDR), a ; escribe port E...pop afipresret

<- RETORNO DE INTERRUPCIÓNand 0x7Eioi ld (PEDR),A...

Como puede observarse, el contenido del registro A corresponde al valor del puertoparalelo E antes que se produjera la interrupción, la cual modificó el valor de los bitsen que se maneja el buzzer. El programa principal, luego, los altera, interrumpiendoel sonido del buzzer, como muestra el gráfico a continuación:

36

Page 55: El Camino Del Conejo

Ports paralelo

Por más que el tiempo de interrupción del sonido sea muy breve, es probable que seperciba como un pequeño chasquido, pero si esto es frecuente (si el programaprincipal actualiza en loops, aunque no tenga nada nuevo que escribir, por ejemplo),se produce una aparición de componentes propias de la frecuencia de perturbación(la diferencia entre la velocidad de repetición del programa principal y las

interrupciones), que se perciben como suciedad o "gárgaras" en el sonido emitidopor el buzzer3. Ni hablar si el port paralelo en realidad controla un conversor digitala analógico...

Lo primero que se nos suele ocurrir para corregir el defecto es inhibir lasinterrupciones durante la secuencia de actualización, para convertirla (al menos

desde el punto de vista de las interrupciones) en una operación atómica:

main...ipset 1ioi ld A,(PEDR)and 0xEEioi ld (PEDR),Aipres 1...

Sin embargo, esta sencilla operación introduce un período de latencia durante elcual las interrupciones no pueden aceptarse, y por ende se las demora. Dependiendode la duración de este período con respecto a la frecuencia de las interrupciones,aparece una modulación del ancho de pulso de la señal del buzzer que es másmolesta cuando se manifiesta más frecuentemente. El efecto percibido (si es quellega a notarse) es una variación en el timbre4 del buzzer, y es más notoria en micros

de baja velocidad, en los cuales un par de instrucciones representa un intervalo detiempo apreciable frente al período del sonido generado. Por ejemplo, algunos

microsegundos en un buzzer a 5KHz, cuyo semiperíodo es de 100� s .

3 Para quien guste del análisis fanático, este proceso es similar a una heterodinación de ambascomponentes: frecuencia de actualización del programa principal y de las interrupciones (INT), sóloque en el mundo digital. Del producto de ambas aparece fundamentalmente (entre otras cosas) unacomponente de baja frecuencia (la diferencia entre las frecuencias de ambas señales): la periodicidadde la repetición del chasquido. Dejamos el análisis de Fourier para los más desquiciados.

4 Siguiendo con los análisis para fanáticos, este proceso es una modulación de fase.

37

Salidabuzzer

INTstimer

Escrituraen port

(tiempo)

(tiempo)

Page 56: El Camino Del Conejo

Periféricos Internos

Como se aprecia en el gráfico, en el momento en que se demora la atención de lasinterrupciones porque justo se está accediendo al port, el cambio de estado en lospines del buzzer se produce a destiempo.

El segundo intento, recordando lo visto anteriormente, es corregir el defectoempleando shadow registers, y/o realizando las operaciones de I/O de formaverdaderamente atómica. De esta forma, en cualquier instante que se produzca laoperación, siempre que ambas rutinas empleen el mismo esquema, no hay unmovimiento no-atómico que produzca este tipo de glitches, y se minimizan la

latencia y su variación.Sin embargo, la latencia de interrupciones sigue existiendo, y en un sistema muycargado, y/o con interrupciones de mayor prioridad, puede llegar a causar efectoscomo el recientemente analizado. El cambio de estado de los pines se realiza porprograma, y el mismo se demorará tanto como sea necesario demorar la atención dela rutina correspondiente.

En estos casos, la solución es aprovechar el hardware para encargarse del cambiode estado de los pines. La mayoría de los microcontroladores tienen un módulo decomparación que permite operar sobre un pin, cambiándolo de estado cuando eltimer lo indica, sin intervención del software más que para recalcular el nuevoperíodo. Siempre que se opere sobre el último valor calculado y no sobre el valoractual del timer, se minimizará el jitter.

En Rabbit, disponemos de los ports paralelo D y E, más F y G en R3000.Cualquiera de estos puertos puede ser configurado (por nibbles) para que el valorescrito en el registro PxDR sea transferido a los pines cuando lo indica un timerinterno, como por ejemplo uno de los módulos de comparación del Timer B. De estaforma, al ocurrir una comparación exitosa, se transfiere automáticamente el últimovalor calculado a los pines, mientras que al atenderse la correspondiente

interrupción, se calculará el nuevo valor, el cual tendrá efecto en la próximacomparación exitosa. Los números que figuran en el gráfico corresponden al ordendel valor en cuestión; como se observa, el valor 1 es calculado en la primerainterrupción, y pasará a los pines en la siguiente comparación exitosa:

38

Salidabuzzer

INTstimer

Escrituraen port

(tiempo)

(tiempo)

Page 57: El Camino Del Conejo

Ports paralelo

El siguiente ejemplo, muestra el modo de configurar los ports para funcionar deesta forma. Utilizamos los mismos pines que controlan los LEDs de un RCM2200,disponibles en el jumper JP1 de la placa para prototipos que se incluye en el kit dedesarrollo. El uso y configuración del Timer B lo veremos en más detalle en un

apartado siguiente. Este ejemplo es una pequeña modificación del que utilizáramosen el capítulo sobre interrupciones, sólo alteramos lo necesario para producir elcambio de los pines por hardware, y cambiamos los comentarios acorde.

void timerb_isr();

unsigned int taimer;

void main(){

WrPortI(PEFR,&PEFRShadow,0);WrPortI(PEDDR,&PEDDRShadow,0x82);WrPortI(PECR,&PECRShadow,0); // pines cambian inmediatamenteWrPortI(PEDR,&PEDRShadow,0x80); /* prende un LED, apaga el otro */

SetVectIntern(0x0B, timerb_isr); // setea vector de interrupción

WrPortI(TBCR, &TBCRShadow, 0x09); // clock timer B con (perclk/16) // prioridad de interrupción: 1

WrPortI(TBM1R, NULL, 0x00); // el timer dispara el port cuandoWrPortI(TBL1R, NULL, 0x89); // el contador llega a 0089taimer=0x089;

WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupción // por match en B1

WrPortI(PECR,&PECRShadow,0xAA); // pines cambian con match en B1

while(1);}

#asmtimerB_isr::

push af ; salva registros

39

latencia y jitter de latencia

Timer B

Cambioen pines

INTs(tiempo)

timer

0 1 2 3 4 5

1 2 3 4 5 6Escritura

en registro

PxDR

Page 58: El Camino Del Conejo

Periféricos Internos

ioi ld a, (TBCSR) ; reconoce matchpush hlld hl,(taimer) ; lee compare shadowpush bcld bc,0x0089add hl,bc ; suma 0x0089ld (taimer),hl ; y guarda en shadowld a,hrrca ; MSbs en bits 7,6rrca ; descarta el resto de los bitsioi ld (TBM1R), a ; carga en match register

ld a,lioi ld (TBL1R), a ; int siguiente: timer+0089hioi ld a,(PEDR) ; lee port Exor 0x82 ; invierte bits de LEDs (alternado)ioi ld (PEDR), a ; escribe port E (cambiará en nuevo match)pop bc

pop hlpop af ; recupera registrosipres ; restablece interrupcionesret ; retorna

#endasm

Con un simple cambio al ejemplo anterior:

WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupción // por match en B1

WrPortI(PECR,&PECRShadow,0xA0); // 7= hard, 1=soft

while(1);

hacemos trabajar el pin PE.1 por hardware, pero volvemos PE.7 al modo anterior, en

que cambia cuando el software lo escribe. Podemos entonces observar la latencia deinterrupciones y su correspondiente jitter o variación. Colocando un osciloscopiocon un canal y el disparo en PE.7, y otro canal en PE.1, podemos observar como elsoftware "llega después", y dado que las instrucciones son siempre las mismas y ennuestro entorno demoran siempre lo mismo, la variación en ese tiempo sólo puededeberse a una variación en la demora de atención y despacho de las interrupciones,

es decir, la latencia. Lo que se observa es algo similar a la figura siguiente:

La inversión de fase se debe al hecho de que el valor escrito en PE.7 recién pasaráa los pines en la siguiente comparación, mientras que el de PE.1 lo hace al momentode la escritura (recordemos que ya escribíamos valores complementarios en ambospines).

40

latencia minima latencia maxima jitter de latencia

PE.7

PE.1

Page 59: El Camino Del Conejo

Ports paralelo

Ports D y E: bit registers

Una característica sobresaliente de Rabbit que resuelve ambos problemasplanteados, es la existencia de registros individuales para cada bit en los ports D y E.Estos ports permiten, mediante estos bit registers, evitar la existencia de la operaciónread-modify-write al momento de setear un bit en un port. Internamente, existenocho ports de un bit, cada uno en una dirección diferente, por lo que el escribir enuno de estos registros sólo afecta al bit existente, el cual controla directamente el

flip-flop correspondiente del port D o E, según corresponda. Es decir, si escribimosen el registro PDB3R, sólo se afectará el pin PD.3.Cuando nuestro problema se encuentra en alguno de estos ports, podemos

resolverlo aprovechando esta ventaja de hardware.El ejemplo original que vimos con shadow registers, puede resolverse

satisfactoriamente (sólo para los ports D y E) mediante el uso de bit registers, de la

siguiente forma:

activa: ld hl, PDB6Rioi set 6,(HL) ; pone PD.6 en altoret

desactiva:ld hl, PDB6Rioi res 6,(HL) ; pone PD.6 en bajoret

Dado que en PDBxR sólo existe el bit x, en realidad podemos escribir cualquiercosa que tenga ese bit en el estado que nos interesa, con lo cual podemos aprovecharalgún registro que sepamos tiene el valor correcto en caso que los ciclos de clock nosestén quitando el sueño.

Ports serie en Rabbit 2000 y 3000

Veremos algunos ejemplos de uso de los ports serie en el capítulo sobreconectividad, complementarios a los que ya hemos desarrollado en el libro

introductorio. Vamos a recordar aquí la estructura de registros, que la vamos anecesitar, y analizaremos las diferentes distribuciones de pines, causa de confusionesy pérdidas de tiempo.

Registros

La estructura de registros de los ports serie en Rabbit 2000 y 3000 es la siguiente:

SxDR: Serial (A,B,C,D) Data Register, contiene el dato recibido (lectura), o se le

escribe el dato a transmitirSxAR: Alternate Data Register, se utiliza para generar un noveno bit en cero.

41

Page 60: El Camino Del Conejo

Periféricos Internos

SxLR: Long Stop Register, se utiliza para generar un noveno bit en uno, lo cual esequivalente a un bit de stop adicional. Sólo está presente en versionesR2000A y posteriores.

SxSR: Status Register, contiene los flags que indican el estado del port, condición deinterrupción, y errores

SxCR: Control Register, configura el modo de operación, y la prioridad de las

interrupciones.

Pines

Los pines en las interfaces serie requieren un poquito de atención, debido a laexistencia de pinouts alternativos y la carencia de algunos de los pines en algunos delos módulos.

Puertos A y B

Los más conflictivos son los puertos A y B, los cuales comparten los pines con el

port paralelo C, específicamente PC.7, PC.6 para el puerto serie A y PC.5, PC.4para el puerto serie B. La transmisión se realiza por el bit par (PC.6 y PC.4), dadoque es una salida, y la recepción por el bit impar, dado que es una entrada. Para quela salida de los datos de la UART pueda realizarse por los pines mencionados, debensetearse los bits correspondientes (6 y 4, respectivamente) en el PCFR (Port CFunction Register), que es quien decide qué función tiene el pin. Si se utilizan las

funciones serAopen() o pktAopen() para el puerto A y serBopen() o pktBopen() parael puerto B, esto se realiza automáticamente

Pinout alternativo

La salida de datos de la UART puede, alternativamente, tomarse del port D. Paraesto, deben setearse los bits correspondientes (6 y 4, respectivamente) en el PDFR(Port D Function Register), que es quien decide qué función tiene el pin.

La entrada de datos a la UART, también puede alternativamente tomarse del portD. Para esto, es necesario colocar la combinación 01 en los bits 5 y 4 del SxCR(Serial x Control Register) correspondiente.Si se utilizan las funciones serAopen() para el puerto A y serBopen() para el puertoB, en los módulos RCM22xx y 23xx el compilador sabe que los pines "originales"no están disponibles, y automáticamente selecciona el pinout alternativo. Para otras

combinaciones manuales, se puede indicar que se desea el pinout alternativodefiniendo:

#define SERA_USEPORTD#define SERB_USEPORTD

Si se utiliza pktXopen(), se deberá indicar que se desea el pinout alternativodefiniendo:

42

Page 61: El Camino Del Conejo

Ports serie en Rabbit 2000 y 3000

#define PKTA_USEPORTD#define PKTB_USEPORTD

Puertos C y D

Los puertos C y D se encuentran en los pines PC.2, PC.3 y PC.0, PC.1;respectivamente. No existe pinout alternativo, se seleccionan operando sobre PCFRpara habilitar la salida de datos y habilitando el receptor colocando un 0 en el bit 5del SxCR correspondiente. Si se utilizan las funciones serXopen() o pktXopen(), esto

se realiza automáticamente.

Puertos E y F

Los puertos E y F no existen en Rabbit 2000. Se encuentran en los pines PG.6,PG.7 y PG.4, PG.5; respectivamente. No existe pinout alternativo, se seleccionanoperando sobre PGFR para habilitar la salida de datos y habilitando el receptorcolocando un 0 en el bit 5 del SxCR correspondiente. Si se utilizan las funciones

serXopen() o pktXopen(), esto se realiza automáticamente.

Ports serie en Rabbit 4000 y 5000

La versatilidad de asignación de pines en estos micros es muy elevada, y realmentela mejor opción es usar la utilidad de configuración de I/O. Sin embargo, veremosalgunas definiciones que nos ayudarán a elegir manualmente los pines.

RS232.lib

En primera instancia, es costumbre de Rabbit soportar antiguas definiciones, por loque las que hemos visto para Rabbit 2000 y 3000 están soportadas, al menos hasta

DC10.60. La asignación de pines por defecto es la misma que para los modelosanteriores, pero para elegir un pin particular para el puerto serie x, contamos con lassiguientes definiciones en RS232.lib:

#define xDRIVE_TXD 4#define xDRIVE_RXD 5#define SERx_TXPORT PCDR#define SERx_RXPORT PCDR

Existe además una opción de eliminar el código de control de flujo, si no loutilizamos:

#define SER_NO_FLOWCONTROL

La función de inicialización del port utiliza los nuevos BRG (Baud RateGenerators) de estos micros por defecto. Si deseamos usar el Timer A en el port x,debemos definir

43

Page 62: El Camino Del Conejo

Periféricos Internos

#define SPx_USE_TIMERA

DMA

La biblioteca de funciones RS232.lib soporta el uso de DMA mientras no se realicechequeo de paridad. Luego de inicializar el port mediante la llamada a serXopen(),

lo colocamos en modo DMA llamando a la función serXdmaOn(). Podemos salir deeste modo utilizando la función serXdmaOff(). Si queremos utilizar solamente DMA:

#define SER_DMA_ONLY

Si por el contrario no lo vamos a utilizar:

#define SER_DMA_DISABLE

Ambas definiciones indican al compilador que elimine el código que no va autilizar.

Packet.lib

Esta biblioteca de funciones, al menos hasta DC10.60, no presenta agregados encuanto a su funcionalidad, no soporta DMA, y tampoco es posible utilizar algún pinque no sea uno de los que se seleccionan por defecto.

Timer B

En el capítulo sobre interrupciones, y en el apartado sobre cambio de pines porhardware de este capítulo, hemos tenido ejemplos de utilización del Timer B. Ahoravamos a interpretar algunas de las cosas que hemos hecho.

Repaso

Como seguramente recordamos, el Timer B está compuesto por un contador de 10bits (TBCMR-TBCLR) de sólo lectura y dos registros de comparación de 10 bits(TBM1R-TBL1R y TBM2R-TBL2R) que setean un flag de comparación exitosa(match) cuando el valor del contador iguala al del registro. Dado que no disponemosde funciones especiales que nos hagan la vida más fácil, vamos a repasar laestructura de registros:

TBCSR: Timer B Control/Status Register, contiene los flags de comparación exitosade cada uno de los timers, en modo lectura, y controla el estado de lainterrupción de cada registro de comparación, en modo escritura. Cada bit

44

Page 63: El Camino Del Conejo

Timer B

controla el registro respectivo, el bit 0 controla la operación del sistemaTimer B.

TBCR: Timer B Control Register, los bits 1, 0 definen la prioridad de lainterrupción (00 = no habilitada). Los bits 3 y 2 controlan la fuente dereloj del contador:

00 = PCLK/2

01 = Timer A11x = PCLK/16

Los bits 4 y 5 sólo existen en R4000 y superiores, y seteados permitenconfigurar una recarga automática de los registros de comparación de losmódulos 1 y 2, respectivamente.

Los registros de 10 bits (TBCMR-TBCLR, TBM1R-TBL1R y TBM2R-TBL2R)están dispuestos de forma algo caprichosa, que seguramente facilita algunos cálculospero dificulta otros:

MSB

LSB

PCLK, también referido en los textos como PERCLK, o sus equivalentes enminúsculas, es el clock de periféricos (PERipheral CLocK); según estudiáramos enel libro introductorio, que si no se modifica el seteo por defecto equivale enfrecuencia al clock del procesador.

Ejemplo (Rabbit 2000 y 3000)

Analicemos ahora el código del ejemplo anterior, en el apartado sobre cambio depines por hardware; sólo la parte que atañe al Timer B. En este caso, utilizamos elmódulo de comparación B1:

WrPortI(TBCR, &TBCRShadow, 0x09); // clock timer B con (perclk/16) // prioridad de interrupción: 1

WrPortI(TBM1R, NULL, 0x00); // el timer dispara el port cuandoWrPortI(TBL1R, NULL, 0x89); // el contador llega a 0089taimer=0x089;

WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupción // por match en B1

Como puede seguirse en los comentarios, configuramos un clock dividido pordieciséis, a fin de poder lograr frecuencias dentro del rango de audio. Configuramosademás una prioridad de interrupción pues necesitamos volver a cargar un nuevovalor para que el timer divida por una frecuencia de nuestro interés, caso contrario la

comparación exitosa siguiente será luego de que el contador arranque nuevamente decero y vuelva a llegar al valor configurado, lo cual ocurrirá en 1024 cuentas, dado

45

9 89

7 6 5 4 3 2 1 0

Page 64: El Camino Del Conejo

Periféricos Internos

que es un contador de 10-bits. Con estas sentencias, escribimos el valor 0x0089, ycuando el contador llegue a este valor, recibiremos una interrupción. Luego, en larutina de interrupciones, le indicamos al módulo de comparación que ya estamos endicha rutina, que no debe seguir pidiendo una interrupción, y lo hacemos leyendo elregistro TBCSR. Inmediatamente después, sumamos al valor de cuenta anterior, laconstante deseada, es decir, el número de cuentas que deben transcurrir para una

nueva comparación exitosa y su correspondiente interrupción. Dado que los registrosson de sólo lectura, debemos guardar la última cuenta en RAM, lo que hacemos en lavariable taimer, la cual dejamos que recicle tranquilamente, dado que sólo nosinteresan los diez bits menos significativos. Debido a que los dos bits mássignificativos del valor de comparación deben escribirse en los bits mássignificativos del registro TBM1R, hacemos una doble rotación del acumulador, lo

cual efectivamente desplaza los bits 0 y 1 (9 y 8 respectivamente de los diez bits quenos interesan) a las posiciones 6 y 7, respectivamente.

ioi ld a, (TBCSR) ; reconoce match

ld hl,(taimer) ; lee compare shadowpush bcld bc,0x0089add hl,bc ; suma 0x0089ld (taimer),hl ; y guarda en shadowld a,hrrca ; MSbs en bits 7,6rrca ; descarta el resto de los bitsioi ld (TBM1R), a ; carga en match register

ld a,lioi ld (TBL1R), a ; int siguiente: timer+0089h

El valor a colocar en el registro de comparación es la cantidad de clocks de estetimer que necesitamos que cuente. Como tal es el producto entre la frecuencia declock del timer y el período entre interrupciones. En un timer B con clock perclk/16

como es este caso, para generar una señal de 5KHz requerimos una interrupción

cada 100� s , luego, en un RCM2200:

cuentas�perclk

16� period�

22MHz

16�100� s�137,5�0x0089

El Timer B antes y después del Rabbit 4000

Nuestro periférico bajo estudio ha experimentado una pequeña transformación conel advenimiento del Rabbit 4000. Seguramente estudiamos en el libro introductorioque una vez que el Timer B alcanzó la cuenta deseada, para que vuelva ainterrumpirnos, aún en el mismo valor de cuenta, debíamos recargar la pareja de

registros de comparación (TBLxR y TBMxR), con un código como éste:

ioi ld a, (TBCSR) ; reconoce interrupciónxor aioi ld (TBL1R), a ; siguiente: timer=0000h (otra vez)ioi ld (TBM1R), a

46

Page 65: El Camino Del Conejo

Timer B

La encarnación del Timer B en el Rabbit 4000 se ha visto mejorada con elcrecimiento de un nuevo par de registros por módulo, TBSLxR y TBSMxR, Timer Bstep registers. Estos registros permiten que el módulo pueda recargar su cuenta sinintervención de la CPU.

Ejemplo

Analizamos un ejemplo similar al desarrollado en el libro introductorio, donde

hacemos parpadear un LED. Por comodidad, resaltamos las diferencias:

WrPortI(TBCR, &TBCRShadow, 0x19); // clock timer B con (perclk/16)// prioridad de interrupción: 1

// usar step registers para auto-reload

WrPortI(TBL1R, NULL, 0x00); // el timer interrumpe cuandoWrPortI(TBM1R, NULL, 0x00); // el contador llega a 0000

WrPortI(TBSL1R, NULL, 0x00); // y vuelve a recargarseWrPortI(TBSM1R, NULL, 0x00); // con el valor 0000

count=SCALER; // inicializa postscaler

WrPortI(TBCSR, &TBCSRShadow, 0x03);// habilita timer B e interrupción// por match en B1

En la rutina de interrupciones, ya no debemos recargar los registros decomparación. Sin embargo, como los módulos basados en R4000 y R5000 son

mucho más rápidos, hicimos un postscaler de 16-bits para obtener un parpadeovisible:

ioi ld a,(TBCSR) ; reconoce interrupción; auto reload mediante step registers

ld bc,(count) ; postscalerdec bc ; decrementa postscaler (no afecta flag Z)ld a,b ; chequea 0or cjr nz,skip ; si no llega a cero, salteald bc,SCALER ; llegó a cero, recarga postscaler

skip:ld (count),bc ; carga el valor decrementado o la recarga

Reloj de Tiempo Real

El reloj de 32,768KHz (interno en R2000, externo en R3000) alimenta un contadorde 48 bits que oficia de reloj de tiempo real (RTC). Este contador posee un pindiferente para su alimentación, permitiendo el uso de pila de respaldo para el RTCmientras el resto del chip se encuentra sin alimentación.

El contador es leído al inicio del sistema, de la siguiente forma:

SEC_TIMER = read_rtc();

Al mismo tiempo, TICK_TIMER y MS_TIMER son puestos a cero.

47

Page 66: El Camino Del Conejo

Periféricos Internos

En todo momento, fecha y hora del sistema deben determinarse leyendoSEC_TIMER, a menos que sea estrictamente necesario leer directamente el RTC.Recordemos que la información corresponde al número de segundos transcurridosdesde la hora cero (1º de enero de 1980), y puede traducirse a una estructuramediante funciones de Dynamic C:

struct tm thetm;

mktm(&thetm, SEC_TIMER);

La estructura tm, a su vez, tiene los siguientes elementos:

struct tm{

char tm_sec; // segundoschar tm_min; // minutoschar tm_hour; // horaschar tm_mday; // día del meschar tm_mon; // meschar tm_year; // año (1980-2047)char tm_wday; // día de la semana (domingo = 0)

};

Tendremos ejemplos de uso de las funciones de RTC cuando analicemos filesystems, en los que haremos ejemplos de logs de eventos con registro de fecha yhora.

Si el programador altera la hora del RTC:

tm_wr(&thetm); // set clock

esto no alterará SEC_TIMER, que será actualizado sólo luego de un reset, por lo queel programador deberá actualizar dicha variable manualmente, realizando una lecturadel RTC:

tm_wr(&thetm);SEC_TIMER=read_rtc();

Watchdog Timer

Si bien la CPU Rabbit tiene un hardware watchdog, el Virtual Driver lo reseteaperiódicamente y provee diez watchdog timers virtuales por software. Si elprogramador necesita utilizar un watchdog timer para controlar una rutina enparticular, puede hacer uso de los Virtual Watchdog Timers, requiriéndolos y

utilizándolos de la siguiente forma:

ID = VdGetFreeWd(count); /* inicializa un VWDT */

VdHitWd(ID); /* mantiene activo el VWDT, evitando reset */

48

Page 67: El Camino Del Conejo

Watchdog Timer

VdReleaseWd(ID); /* libera el VWDT */

Cada count equivale a un período de 62,5ms; dado por la operación interna delVirtual Driver, controlado a su vez por la interrupción periódica.

Hemos visto un adelanto en el capítulo sobre assembler, respecto a la forma deutilizarlo:

int ID;

main(){

ID = VdGetFreeWd(5); /* inicializa un VWDT, 5 cuentas: 312,5ms while(1){

VdHitWd(ID); // reset del watchdog// tarea// si por algún motivo no llega en el tiempo previsto,// se produce el reset

}}

Si el watchdog timer expira, se produce el reset del micro, pero durante eldebugging, la ejecución del programa se detiene con un código de error 250, yDynamic C muestra una ventana de diálogo que informa de la situación.

Uso de encoders o codificadores rotativos

Un codificador en cuadratura (quadrature encoder) es un dispositivoelectromecánico que se utiliza para seguir la rotación de un eje. Se implementangeneralmente con un disco que alterna franjas opacas y transparentes, excitando dosdetectores ópticos. Las señales de salida son dos ondas cuadradas en cuadratura; lavelocidad de giro se observa en la frecuencia de ambas señales, y la dirección derotación se detecta observando cuál de las dos señales adelanta en fase, es decir,

observando el signo de la diferencia de fase.Una extensión al plano es el viejo y conocido mouse o ratón mecánico con el quehasta hace poco controlábamos nuestra computadora. El mouse mecánico consta dedos codificadores en cuadratura ubicados a su vez espacialmente en cuadratura, yasociados a una esfera. La esfera se encuentra confinada dentro del mouse, y latraslación del mismo ocasiona una rotación de la esfera por fricción. Si movemos el

mouse en el plano en la dirección de un eje x imaginario perpendicular al cable delmouse, la esfera rotará de modo que uno de los codificadores en cuadratura recoja eindique este movimiento. Lo mismo ocurrirá si movemos el mouse en direcciónparalela al cable, con el otro codificador. Leyendo ambos codificadores puedeobtenerse una proyección sobre el eje x y otra sobre el eje y de la trayectoriarecorrida, así como también de la velocidad y aceleración del movimiento.

49

Page 68: El Camino Del Conejo

Periféricos Internos

R3000(A) y sucesores

El Rabbit 3000 y sus sucesores disponen de dos decodificadores de cuadratura,cada uno con dos entradas (normal y cuadratura). Un contador bidireccional de 8-bits (10-bits en el R3000A5 y posteriores) registra los eventos contando en una u otradirección al observar transiciones en el estado de las entradas I y Q, comoanalizáramos en el libro introductorio y reproducimos en el diagrama a continuación:

Las funciones que utilizamos para controlar estos decodificadores de cuadraturason:

qd_init(int); // inicializa con prioridad de interrupciones 'int'qd_zero(mod); // resetea el contador del módulo 'mod'qd_read(mod); // devuelve el valor del contador de 'mod' (long)

La función qd_init() debe llamarse antes del loop de lectura para preparar el vectorde interrupciones; la misma limpia ambos contadores, configura los pines, y setea eltimer A10 con un valor predefinido en la biblioteca de funciones: R3000.lib. o qd.libsegún el micro. Antes de DC9.50, se seleccionaba el nibble bajo del port F en elR3000 por defecto. Actualmente, además, los pines pueden seleccionarse, para el

R3000:

#define QD1_USEPORTFL // QD1 usa PF1 y PF0#define QD1_USEPORTFH // QD1 usa PF5 y PF4#define QD1_DISABLE#define QD2_USEPORTFL // QD2 usa PF3 y PF2#define QD2_USEPORTFH // QD2 usa PF7 y PF6#define QD2_DISABLE

y para R4000 y R5000:

5 El R3000A es la última revisión de R3000, identificada como IL2T en el encapsulado más común.Incluye corrección de algunos defectos del original y agrega nuevas instrucciones y modos deoperación.

50

Page 69: El Camino Del Conejo

Uso de encoders o codificadores rotativos

#define QD1_USEPORTD // usa PD1 y PD0#define QD1_USEPORTEL // usa PE1 y PE0#define QD1_USEPORTEH // usa PE5 y PE4#define QD1_DISABLE

#define QD2_USEPORTD // usa PD3 y PD2#define QD2_USEPORTEL // usa PE3 y PE2#define QD2_USEPORTEH // usa PE7 y PE6#define QD2_DISABLE

En R4000 y R5000 la selección por defecto se no asignar ningún pin.

El siguiente fragmento de código es un ejemplo de un selector para un menú deoperación implementado en base a uno de los decodificadores de cuadratura en unR3000. Utilizamos un tipo de encoder para uso en interfaz de usuario que produce

un pulso en ambas salidas a cada movimiento del eje entre los descansos, es decir,tendremos dos pulsos por paso. El diagrama de conexión precede al ejemplo:

extern void LCD_printat (int font, unsigned int row, unsigned int col, char *ptr, int color, int bcolor); // muestra algo en el display color

static const char *option[]={"Elemento1","Elemento2","Elemento3"};long x,y,encoder;int sel,button;

qd_init(1);sel=button=0;while(!button){ encoder=qd_read(2); if(encoder>2 || encoder <-2) {

qd_zero(2);// borra '>' en posición anteriorLCD_printat (0, 50+10*sel, 30, ">", WHITE, WHITE);// vuelve texto de menú a color normalLCD_printat (0, 50+10*sel, 50, option[sel], BLACK, WHITE);if(encoder<0){

sel++;if(sel>2) sel=2;

}else {

sel--;

51

I

Q

Vdd

PC.5

PF.2

PF.3

push

Page 70: El Camino Del Conejo

Periféricos Internos

if(sel<0) sel=0;

}// dibuja '>' en nueva posiciónLCD_printat (0, 50+10*sel, 30, ">", RED, WHITE);// resalta elemento seleccionado del menúLCD_printat (0, 50+10*sel, 50, option[sel], RED, WHITE);

} if(!BitRdPortI(PCDR,5)) // lee botón de selección

button=1;}// procesa selección, sel=elemento seleccionado

Girando el encoder hacia uno u otro lado, se produce el avance o retroceso dentrode las diferentes opciones del menú. Presionando el botón del encoder, se produce laselección del elemento apuntado.

Elemento1 > Elemento2

Elemento3

R2000

En el Rabbit 2000 no disponemos de decodificadores de cuadratura, por lo cualdeberemos buscar otra forma de leer estos codificadores. Apuntamos entonces el

ejemplo al mismo tipo de encoder que utilizáramos con el R3000; dado que tenemosun pulso en ambas salidas a cada movimiento del eje entre los descansos, podemosutilizar una de las señales como disparador y la otra para detectar el sentido de giro. A grandes rasgos, tendremos dos formas de leer el encoder:� polling, mediante un handler que periódicamente chequea el estado de una señaly ante cambios verifica la otra.

� interrupciones, configurando al micro para ser interrumpido por un flanco de unaseñal y verificando el estado de la otra.

El esquema de interrupciones es bastante más estricto en cuanto a la limpieza de laseñal que genera las mismas, la cual deberá ser filtrada correctamente a fin de evitarfalsos disparos. El esquema de polling es más permisivo, según la frecuencia deoperación del handler en cuestión; también es más propenso a perder pulsos cuando

hay una carga importante del procesador (si no se lo llama con la frecuenciasuficiente).La literatura de Rabbit recomienda reservar las interrupciones para aquellas tareasque tienen un timing crítico (lo cual no es este caso, por lo general), dado que setrata de un sistema con soporte multitarea cooperativo y probablemente la idea de losprogramadores es manejar todo mediante handlers y máquinas de estados. No

obstante, quienes venimos de décadas de exprimir pequeños micros para sacarles unmiserable ciclo de clock extra, estamos más que acostumbrados a reservarinterrupciones para aquellas tareas que consideramos tediosas o su periodicidad y/o

52

Page 71: El Camino Del Conejo

Uso de encoders o codificadores rotativos

asincronicidad nos complican la percepción general de la aplicación. Para justiciacon ambos, analizaremos los dos casos.El siguiente es un handler por polling que lee un encoder conectado como indica eldiagrama que le antecede:

int quadenc_poll(){static int counter;static char oldA;

#GLOBAL_INIT {counter=0;oldA=BitRdPortI(PBDR,0);

}

#asmioi ld A,(PEDR) ; Leeld HL,oldA ; Apunta a estado anteriorand 0x1 ; "change-detect" bitcp (HL) ; Comparald (HL),A ; Almacena nuevo estado (preserva Z)ld HL,(counter) ; lee contador (preserva Z)ret z ; Sale si no hubo cambiosex DE,HLld HL,PBDR ; Cambio, apunta a port Band A ; Flanco ?jr z,fallingioi bit 0,(HL) ; A sube, check Bjr z, AlB

BlA: dec DE ; B antecede a Ajr qpdn

AlB: inc DE ; A antecede a Bjr qpdn

falling: ; descendente, descartarqpdn: ex DE,HL

ld (counter),HL ; vuelve con el valor del contadorret

#endasm}

main(){

WrPortI ( PEDDR,&PEDDRShadow,'\B10001010' ); // PE1,3,7 = outputwhile(1){

printf("%05d \r",quadenc_poll());

53

I

Q

Vdd

PE.0

PB.0

Page 72: El Camino Del Conejo

Periféricos Internos

}

}

A continuación, presentamos una versión de handler por interrupciones externas paraRabbit 2000. El circuito esquemático es el mismo6:

static int counter;

#asmquadenc_isr::

push AF ; salva registrospush HLpush DEld DE,(counter) ; lee contador (16-bits) (preserva Z)ld HL,PBDR ; apunta a port Bioi bit 0,(HL) ; A sube, check Bjr z, AlB

BlA: dec DE ; B antecede a Ajr qidn

AlB: inc DE ; A antecede a Bjr qidn

qidn: ld (counter),DE ; actualiza contadorpop DEpop HLpop afipres ; restablece prioridad e interrupcionesret ; return

#endasm

main(){

WrPortI ( PEDDR,&PEDDRShadow,'\B10001010' ); // PE1,3,7 = outputcounter=0;SetVectExtern3000(0, quadenc_isr); // set up ISR R2000CWrPortI ( I0CR,&I0CRShadow,'\B00001001' ); // ascendente, prioridad 1// SetVectExtern2000(1, quadenc_isr); // set up ISR R2000// WrPortI ( I0CR,&I0CRShadow,'\B00001010' );// ascendente, prioridad 2while(1){

printf("%05d \r",counter);}

}

Salidas PWM

Disponemos de cuatro canales de generación de PWM. El ciclo de trabajo puede

variar en pasos de1

1024de 0%7 a 100%. Esta es una característica de Rabbit que

lo distingue frente a otros micros que utilizan un sistema de comparación entre

6 En realidad esto es válido para las nuevas versiones de R2000. Aquellos dinosaurios (como yo) queaún tienen algún R2000 IQ2T deberán reemplazar las sentencias comentadas y conectar el port Ecomo indica la nota de Rabbit TN301.

7 El 0% en realidad se obtiene desactivando el generador de PWM...

54

Page 73: El Camino Del Conejo

Salidas PWM

registros. No importa cual sea la frecuencia seteada, la resolución es siempre de 10bits. En el R3000, los canales 0 al 3 utilizan los pines PF.4 a PF.7, respectivamente. En R4000 y R5000, podemos elegir el port. Los bits utilizados siguen siendo 4 al 7:

#define PWM_USEPORTC#define PWM_USEPORTD#define PWM_USEPORTE

Las funciones de Dynamic C para operar sobre los generadores de PWM son:

pwm_init((unsigned long)frec); // inicializa el timer// A9, devuelve el valor// seteado (valor más cercano)

pwm_set(ch, pw, NULL); // setea el canal 'ch' con un// ciclo de trabajo 'pw'

El tercer parámetro de pwm_set() puede tomar diversos valores o un OR de ellos.

Por ejemplo, la opción de seleccionar salida open drain en vez de push-pull:

pwm_set(ch, pw, PWM_OPENDRAIN); // open drain

o utilizar el modo spread:

pwm_set(ch, pw, PWM_SPREAD); // modo spread

El siguiente fragmento de código es un ejemplo del uso de los generadores dePWM para controlar un calefactor y un ventilador basado en un motor de corrientecontinua. Tanto la energía eléctrica transformada en calor en la resistencia delcalefactor como la velocidad del motor dependen del valor de la tensión que se lessuministra, la cual controlamos variando su valor medio a través del ciclo de trabajomediante los generadores de PWM, como indica el diagrama.

El código recibe una medición de temperatura en curT, y compara con los límites

configurados (tlo, thi, t2hi). De acuerdo a la diferencia, da más o menos energía alventilador o calefactor, según corresponda, operando sobre el ciclo de trabajo.F_MIN, F_MAX, H_MIN y H_MAX son los valores mínimo y máximo de PWM autilizar para los valores mínimo absoluto y máximo absoluto de tensión aplicable alventilador y el calefactor, respectivamente.

55

+B

logic-level

MOSFET

+B

PF4 logic-level

MOSFET

Calefactor Ventilador

PF5

Page 74: El Camino Del Conejo

Periféricos Internos

int heater,fan,curT,thi,t2hi,tlo,alarm;

if(curT>=thi){ heater=0; fan=(int)(F_MIN+((F_MAX-F_MIN)*(curT-thi))/(t2hi-thi));

if(fan>F_MAX) fan=F_MAX;

if(curT>=t2hi) //alarmaalarm=1;

}else { fan=alarm=0; if(curT<=tlo){

heater=(int)(H_MIN+((H_MAX-H_MIN)*(tlo-curT))/10);if(heater>H_MAX)

heater=H_MAX; } else heater=0;}

pwm_set(0,fan,0);pwm_set(1,heater,0);

Interrupciones y supresión de pulsos (R3000A+)

Los generadores de PWM del R3000A, R4000 y R5000 permiten generar

interrupciones y/o anular la salida cada un número determinado de ciclos, conopciones de 1, 2, 4 u 8. La configuración de estas opciones se realiza utilizandoalgunos de los bits que hasta entonces no tenían aplicación en R3000. Debido a queel soporte en Dynamic C versión 9 no ha sido ampliado al momento de escribir estetexto, deberemos trabajar manualmente sobre dichos bits. A tal fin, mostramos a continuación el diagrama de ubicación de estos bits:

PWL0R

PWL1R

PWL2RPWL3R

Los bits OO controlan la anulación del pulso de salida en 1 de 2, 3 de 4, 7 de 8ciclos, o continuo (no se anula).

Los bits LL establecen el nivel de interrupción, donde 00 significa que el periféricono interrumpe.Los bits II controlan la frecuencia de interrupciones: cada 1 ciclo, 2 ciclos, 4 ciclos,u 8 ciclos de la trama de PWM.

56

O O L L

O O I I

O O

Page 75: El Camino Del Conejo

Salidas PWM

Si el micro es un R4000 o 5000, en Dynamic C versión 10 disponemos además delas siguientes opciones en el tercer parámetro de pwm_set():

� Para suprimir pulsos de salida seleccionaremos una de:

PWM_OUTNORMALPWM_OUTEIGHTHPWM_OUTQUARTERPWM_OUTHALF

� Para seleccionar la prioridad de interrupciones, elegimos entre:

PWM_INTOFFPWM_INTPRI1PWM_INTPRI2PWM_INTPRI3

� Para que las interrupciones sean cada una cantidad de ciclos, usamos:

PWM_INTNORMALPWM_INTEIGHTHPWM_INTQUARTERPWM_INTHALF

La supresión de pulsos tiene aplicaciones en el control de servos, dado que esnecesario generar ciclos de trabajo muy bajos. El anular siete de cada ocho pulsos de

salida nos permite tener toda la resolución (1024 valores) en1

8del rango, es

decir, podemos generar un ciclo de trabajo de entre 0 y 12,5% con precisión de

1

8�1024�0,12� .

Las interrupciones tienen aplicaciones en generación de señales mediante PWM, es

decir, cambios periódicos del valor medio de la señal generada. El interrumpir cadaun determinado número de ciclos de la señal de PWM nos permite trabajar con uncierto oversampling que relaja los requerimientos de filtrado, de modo similar a lafunción spread.

El siguiente es un fragmento8 de código que muestra la actualización periódica del

ancho de pulso tomando muestras de un buffer.

unsigned char buf[BUF_SIZE],*ptr;unsigned int bytes;

ptr=buf;bytes= BUF_SIZE;pwm_init(28800L);SetVectIntern(0x17,PWM_handler);

8 El mismo es parte de un programa que ha sido utilizado para reproducir audio de una tarjeta SDaccediéndola en bajo nivel mediante rutinas ad hoc

57

Page 76: El Camino Del Conejo

Periféricos Internos

pwm_set(2,513,PWM_SPREAD | PWM_INTNORMAL | PWM_USEPORTC | PWM_INTPRI3);

#asm rootPWM_handler::

push afpush hlld hl,(ptr)ld a,(hl)ioi ld (PWM2R),ainc hlld (ptr),hlld hl,(bytes)dec hlld (bytes),hlld a,hor ljr nz,doneld hl,bufld (ptr),hlld hl, BUF_SIZEld (bytes),hl

done: pop hlpop afipres ret

#endasm

Captura de eventos

Las entradas de captura de eventos se utilizan para poder determinar el momento enque se produce un evento externo en particular. Dicho evento es señalizado medianteun flanco cualquiera (o ambos) en alguno de los dieciséis pines que pueden serconfigurados para este propósito, asignados a uno de los dos canales de captura de

que disponemos.La arquitectura utilizada es sumamente flexible, particularmente para medir la

duración de pulsos cortos, en los cuales no podría esperarse atender interrupciones yleer dos cuentas diferentes de un contador para luego obtener la duración restandoambas cuentas.

Repaso

Dado que no disponemos de funciones de Dynamic C para operar sobre este

periférico, vamos a repasar los registros:Para llevar cuenta del tiempo, cada módulo emplea un contador de 16 bits que recibereloj del Timer A8.TAT8R: Timer A8 Time Constant Register, contiene la constante de cuenta para

generar la frecuencia a la que cuenta el módulo, que corresponde a laresolución del mismo.

ICCSR: Input Capture Control/Status Register, en escritura, los bits 3, 2 resetean loscontadores de los módulos 2 y 1 respectivamente, y los bits 7 a 4 controlan

58

Page 77: El Camino Del Conejo

Captura de eventos

la habilitación de la interrupción correspondiente a un evento en particular.En lectura, retorna el estado de ese evento (condición de arranque, dedetención, o de desborde en cada módulo), según el esquema acontinuación (1 => ocurrió esa condición):

bit 7: módulo 2, condición de arranque (start)bit 6: módulo 2, condición de detención (stop)

bit 5: módulo 1, condición de arranque (start)bit 4: módulo 1, condición de detención (stop)bit 3: módulo 2, desborde del contador (rollover)bit 2: módulo 1, desborde del contador (rollover)

ICCR: Input Capture Control Register, controla la prioridad de la interrupción, bits1, 0 (00 = no habilitada)

ICTxR: Input Capture Trigger x Register, controla la o las condiciones deoperación que ocasionan la captura de un evento y el modo defuncionamiento del contador, para cada módulo (1 y 2):

bits 7, 6: 00 = el contador no funciona01 = el contador arranca al ocurrir start y se detiene al

ocurrir stop

10 = el contador avanza de forma continua11 = el contador avanza de forma continua, pero se

detiene al ocurrir stopbits 5, 4: 00 = el módulo no registra el valor del contador al ocurrir

un evento01 = el módulo registra la cuenta ante la condición de

stop10 = el módulo registra la cuenta ante la condición de

start11= el módulo registra la cuenta ante ambas condiciones

bits 3, 2: 00 = no hay condición de start01 = asigna flanco ascendente a condición de start

10 = asigna flanco descendente a condición de start11 = asigna ambos flancos a condición de start

bits 1, 0: 00 = no hay condición de stop01 = asigna flanco ascendente a condición de stop10 = asigna flanco descendente a condición de stop11 = asigna ambos flancos a condición de stop

ICSxR: Input Capture Source x Register, realiza la asignación de un pin de un portparalelo a las condiciones de ese módulo. El nibble superior controla laasignación de la condición de arranque, y el nibble inferior controla laasignación de la condición de detención:

59

Page 78: El Camino Del Conejo

Periféricos Internos

bits 7, 6: seleccionan el port:

R3000 R4000/5000

00 port C port C

01 port D port D

10 port F port E

11 port G

bits 5, 4: seleccionan el bit: 00 = bit 101 = bit 3

10 = bit 511 = bit 7

El nibble inferior es igual, pero con los bits 3, 2 y 1, 0; respectivamenteICLxR: Input Capture LSB x RegisterICMxR: Input Capture MSB x Register, contienen el valor capturado, según la

selección de ICTxR. Al leerse el registro ICLxR, automáticamente se

bloquea ICMxR hasta su posterior lectura, para impedir falsas lecturas.

Ejemplo

El ejemplo siguiente mide el ancho de un pulso generado por el movimiento de unpin de I/O, el cual controlamos de forma precisa inhibiendo las interrupciones yrealizando la operación en assembler.

Configurado el módulo de captura para arrancar el contador en el flanco de subida

y parar en el flanco de bajada, registramos la cuenta en este último flanco y éstacorresponderá al ancho del pulso en las unidades que cuente este módulo. Alclockearlo con perclk/2, contaremos una unidad por cada dos ciclos de clock,aproximadamente 1/22 us. El pulso que vamos a generar es de aproximadamente 11ciclos de clock, unos 0,5us. Como es de esperarse, no es posible medir un ancho depulso de esta magnitud simplemente leyendo el valor de un timer en dos

interrupciones sucesivas, ya que el ancho del pulso generado es menor que lalatencia esperada para atender una sola de las interrupciones.

60

PF.7

PE.0

Page 79: El Camino Del Conejo

Captura de eventos

#class auto

main(){

WrPortI(PEFR, &PEFRShadow, PEFRShadow & ~1);WrPortI(PEDDR, &PEDDRShadow, (PEDDRShadow | 1));WrPortI(PECR, &PECRShadow, 0);WrPortI(PEDR, &PEDRShadow, PEDRShadow & ~1);

WrPortI(ICCSR,&ICCSRShadow,1<<2); // reset módulo 1, no ints

WrPortI(ICCR,&ICCRShadow,0); // no ints

WrPortI(ICT1R,&ICT1RShadow,'\B01010110');/* 01 = el contador arranca al ocurrir start y se detiene al ocurrir stop 01 = el módulo registra la cuenta ante la condición de stop 01 = asigna flanco ascendente a condición de start 10 = asigna flanco descendente a condición de stop */

WrPortI(ICS1R,&ICS1RShadow,'\B10111011'); // PF.7 start y stopWrPortI(TAT8R,&TAT8RShadow,0); // max speedRdPortI(ICCSR);

#asmipset 3 ; 22MHz clock, 1/22 us; 11 cycles = 1/2 usld hl, PEDRioi set 0,(HL)ioi res 0,(HL) ; ~11 cyclesipres

#endasm

if(BitRdPortI(ICCSR,4)) // condición de stopprintf("Cuenta: %d (5 a 22MHz)",RdPortI(ICL1R));

}

Timer C (R4000+)

El timer C, disponible sólo en Rabbit 4000 y 5000 es un contador ascendente de

módulo variable. Incluye cuatro canales con registros de comparación que permitensetear y resetear una salida en determinadas cuentas. Los registros de configuración del Timer C son:

TCCSR:Timer C Control/Status Register, contiene el flag de overflow (bit 1) y el bitde control (bit 0) para habilitar el timer

TCCR: Timer C Control Register, contiene los bits de prioridad de interrupción (0y 1) y los de selección de reloj del contador (bits 3 y 2):

00 = PCLK/201 = Timer A11x = PCLK/16

TCDLR y TCDHR: Timer C Divider Low y High Registers, permiten configurar elmódulo del contador. El timer se resetea al alcanzar esta cuenta.

61

Page 80: El Camino Del Conejo

Periféricos Internos

TCSxR: Timer C Set x Registers, configuran el valor de comparación que causa elseteo del pin

TCRxR: Timer C Reset x Registers, configuran el valor de comparación que causael reset del pin

También incorpora una pareja de registros especiales que le permiten ser controlado

por los controladores DMA.

Disponemos de ejemplos de uso del Timer C en las samples correspondientes,ubicadas en el directorio Samples\TIMERC

62

Page 81: El Camino Del Conejo

IO Config

IO Config

El Rabbit 4000 mantiene los periféricos del Rabbit 3000, incorporando uncontrolador Ethernet y una MMU con mayor poder de direccionamiento, en elmismo encapsulado. Esto, sumado al requerimiento de distribución de los pines dealimentación, hace que se eliminen dos puertos paralelo (respecto a Rabbit 3000,

claro está). La necesidad de arbitrar una gran cantidad de periféricos en una menorcantidad de pines, genera a su vez el requerimiento de tener que contar con variosniveles de asignación, de modo que el usuario tenga mayor versatilidad a la hora deelegir qué pines utilizar, sin que ningún periférico quede sin poder ser utilizadoporque compartía los pines con otro, que ya fue usado. En un micro con lacomplejidad del protagonista de este apéndice, recordar, o incluso compendiar la

cantidad de combinaciones posibles es tarea que aún un obsesivo rehusaría. En unalarde de humanidad y buen criterio, el fabricante nos agasaja con un programadigno de su contemporaneidad, el cual nos permite configurar los periféricos,seleccionar qué vamos a poner en cada pin, y generar el código de inicializaciónapropiado:

63

Page 82: El Camino Del Conejo

Periféricos Internos

Dicho programa se llama IO Config, y resulta instalado de forma automática. Elmismo resulta además una excelente herramienta para visualizar la operación dealgunos de los periféricos. Por ejemplo, la imagen siguiente muestra la operación delos generadores de PWM. Los canales 1 y 4 están en modo "normal", el canal 2 enmodo spread, y el canal 3 suprime 1 de cada 2 pulsos. Las interrupciones se generana 1 de cada 4 ciclos (se suprimen 3 de cada 4).

El Rabbit 5000 complica aún más las cosas en cuanto a pinout, pero mantienebásicamente la misma estructura de periféricos que el Rabbit 4000.

Habiendo introducido al héroe salvador y justificado la razón de su existencia, nos

someteremos a él para todas nuestras necesidades de configuración de periféricos enel Rabbit 4000 y el 5000.

64

Page 83: El Camino Del Conejo

Periféricos

Bus de datos

Si bien existe siempre la posibilidad de comunicarse con memorias o periféricosmediante la técnica de bit-banging, es decir, simular el timing mediante el controlpor software de los pines de I/O del micro; nos referimos en este capítulo a la forma

de aprovechar la presencia del bus de datos (y por qué no del de direcciones) a lahora de conectarnos con memorias o periféricos externos al módulo Rabbit.

Memorias FRAM paralelo

Si bien disponemos de una amplia capacidad de memoria, y la posibilidad derespaldar la RAM mediante una pila, puede llegar a ser interesante emplear unatecnología de memoria no volátil con tiempo de acceso equivalente a una RAM,cantidad de accesos virtualmente ilimitados, y sin consumo de energía para mantener

los datos, como por ejemplo las FRAM (Memorias de acceso aleatorio de tecnologíaferroeléctrica) de Ramtron, como la FM1808.Más allá de las diferencias tecnológicas, este tipo de memorias se comporta, desde

el punto de vista de su interfaz con un micro, de igual forma que una RAM estática.La única diferencia es que debido a timing interno, debe tener pulsos en la señal de

CS , es decir, no podemos dejarla conectada directamente a masa:

Vamos a conectar la memoria al bus de datos dentro del espacio de I/O. Como

disponemos de un espacio de direccionamiento de I/O de 64K, esto no es problema,

65

CS

Address

Data

OE

WE

Page 84: El Camino Del Conejo

Periféricos

dado que la memoria en cuestión es de 32K. El único inconveniente que se nospuede presentar con un módulo, es el hecho de que no todas las líneas de addressestán accesibles en todos los módulos. En este caso, utilizaremos un RCM2100, quetiene accesibles desde A0 a A12, coincidente con el espacio de direccionamiento de8K de los IOSTROBES. Entonces, solamente deberemos generar manualmente dosde las líneas de address (A13 y A14), y lo haremos utilizando el port B para esto:

Como vemos, la FRAM aparece en el mapa de memoria del Rabbit, en el espacio deI/O externo, como cuatro bancos de 8KB situados en el área entre 0x0000 y 0x1FFF.Una pequeña rutina se encarga de extraer el valor de A13 y A14 para colocarlo enPB.6 y PB.7, seleccionando el banco a acceder. Todo el timing es generadoautomáticamente al realizar una operación de I/O; sin embargo, la configuración delIOSTROBE merece un poco más de estudio.Si observamos en la hoja de datos de la FM1808, veremos un par de cosas

interesantes. La primera es que tenemos un tiempo de acceso de 70 ns, y un tiempode precarga de 60ns. Esto significa que el tiempo mínimo de activación de CS

deberá ser de 70ns más lo que la capacidad del bus agregue, y deberemos esperar unmínimo de 60ns entre dos accesos. La segunda es que debido a que en la FRAM seregistra (latch) el valor de las líneas de direcciones al momento de producirse elflanco descendente de CS , las mismas deberán estar estables 4ns antes de estemomento. Si observamos el gráfico de operación de IOSTROBE, veremos que laslíneas de address y el chip select strobe cambian casi simultáneamente, mientras quetanto read strobe como write strobe cambian aproximadamente un ciclo de clockdespués:

66

D0

D1

D2

D3

D4

D5

D6

D7

CS

OE

WE

A0

A1

A2

A3

A4

A5

A6

A7

A8

A9

A10

A11

A12

A13

A14

A0

A1

A2

A3

A4

A5

A6

A7

A8

A9

A10

A11

A12

D0

D1

D2

D3

D4

D5

D6

D7

PE.0

IORD

IOWR

PB.6

PB.7FM1808

Page 85: El Camino Del Conejo

Bus de datos

Si observamos en detalle el timing, en el Manual del Usuario, observaremos quedebido al jitter natural y la diferente capacidad de carga de ambas señales (addressesA[15:0] y /IOCSx), no es posible garantizar que se cumpla esta condición:

67

Page 86: El Camino Del Conejo

Periféricos

Deberemos entonces configurarlo como read strobe y write strobe. En este modode trabajo, IOCS se activa junto con IORD y IOWR , lo cual no esinconveniente. En el RCM2100 tenemos un clock de 22MHz, lo que significa quecada ciclo de clock es de 45,45ns. Sabemos que una operación de I/O demora comomínimo 3 ciclos; pero como vemos en los gráficos, el ancho de pulso de un readstrobe es de dos ciclos (Tw+T2) y un write strobe llega a un ciclo y medio (Tw+1/2T2), más o menos el consabido jitter. En el caso que usáramos una instrucción derepetición como LDIR, nuestro tiempo de precarga (tiempo entre desactivación yactivación siguiente de CS ) sería solamente de un ciclo de clock (T1). Para podercumplir con todos estos requerimientos, deberemos insertar un ciclo de esperaadicional, el mínimo que se nos permite configurar (mayor que 1) es de 3 wait-states.El timing generado, será entonces el siguiente:

68

Page 87: El Camino Del Conejo

Bus de datos

A continuación, la inicialización, configuración, y la pequeña rutina que hace "quetodo esto sea posible".

void init (){// Use Port E bit 0 for Chip Select with 3 wait-states#define DSTROBE 0x01#define DCSREGISTER IB0CR#define DCSSHADOW IB0CRShadow#define DCSCONFIG '\B11111000'

// Inicializa bit en Port E como I/OWrPortI(PEFR, &PEFRShadow, (PEFRShadow|DSTROBE));

// Inicializa bit en Port E bit como salidaWrPortI(PEDDR, &PEDDRShadow, (PEDDRShadow|DSTROBE));

// Inicializa bit en Port E como IOSTROBE con sus wait-states.WrPortI(DCSREGISTER, &DCSSHADOW, DCSCONFIG);

// Port E clockeado por PCLK/2WrPortI(PECR, &PECRShadow, (PECRShadow & ~0xFF));

}

#asm;@sp+2= 1st param, address;@sp+4= 2nd param, data to write;FRAM_Write::

ld hl,(sp+2) ; addresscall doaddress ; pone MSbs en I/Oex de,hlld hl,(sp+4) ; datald a,lex de,hlioe ld (HL),a ; escriberet

69

Address

Data

PE.0

IORD

IOWR

PB.6

PB.7

readstrobe strobewrite

D[0:7]

A[0:12]

Page 88: El Camino Del Conejo

Periféricos

;@sp+2= 1st param, address;FRAM_Read::

ld hl,(sp+2) ; addresscall doaddress ; pone MSbs en I/Oioe ld a,(HL) ; leeld l,ald h,0ret

doaddress:ld a,hand 0xE0 ; limpia bits 7,6,5rla ; bits 7,6 = A14,A13ioi ld (PBDR),a ; A14,A13 en PB.7,PB.6res 7,h ; resto en address bus (A0-A12: 8K)res 6,hres 5,hret

#endasm

Finalmente, un pequeño ejemplo de como realizar lectura y escritura

main(){unsigned int addr;unsigned char dat;

init();addr=0;dat = 0x84;FRAM_Write(addr,dat);dat=FRAM_Read(addr);

}

Controladores de displays LCD color

Un display color LCD generalmente presenta una interfaz relativamente simpledesde el punto de vista del hardware, con un bus de datos y otro de control. Lanomenclatura es algo confusa y varía con el fabricante, pero por lo general aceptageneralizaciones. La cantidad de bits en el bus de datos y la forma de operación delas señales de control y su relación con el bus de datos dependen de lascaracterísticas propias de cada display. Vamos a describir a continuación una seriede displays comúnmente denominados como "genéricos", y que por su precio ydisponibilidad son particularmente atrayentes.

Breve descripción del display color

En este tipo de displays color de 320x240 pixels, las señales de control se encargande indicar los instantes de comienzo de trama vertical (FPFRAME o FRM) y líneahorizontal (FPLINE, LOAD, o CL1), mientras que la información de color paragrupos de pixels es leída a cada intervalo de clock (FPSHIFT, DP, o CL2), 8-bits ala vez (FPDAT0 a FPDAT7). La correspondencia entre estos 8-bits y la informaciónde rojo (R), verde (G) y azul (B) de pantalla es algo compleja, en realidad se

70

Page 89: El Camino Del Conejo

Bus de datos

comienza enviando el contenido RGB de dos pixels adyacentes, más RG de un tercerpixel, y luego se va completando hasta enviar GB del antepenúltimo pixel de laúltima línea, y RGB del penúltimo y último pixels. Esto varía en cuanto al ordensegún el tipo de display, pero guarda una relación similar; pudiendo observarsemejor en el diagrama que sigue a continuación, junto con una idea genérica yaproximada del timing de una trama:

Como puede observarse, la información de color es RGB 1:1:1 (1 bit para cadacolor por cada pixel). Esta información controla el polarizador transmisivovinculado al color correspondiente, que dejará pasar o no la componente de colorrojo (R), verde (G) o azul (B), según se trate, filtrando la luz blanca proveniente deuna iluminación posterior (backlight) que generalmente es en base a CCFL (ColdCathode Fluorescent Light).Sin embargo, esto no significa que solamente podamos tener ocho colores enpantalla, con 0 ó 100% de saturación de cada una de las componentes. Mediante lamodificación de esta información trama a trama, es posible generar una gama muyinteresante de colores. Si la velocidad de actualización de trama es losuficientemente elevada, y el display responde a dicha velocidad, el ojo no percibe elparpadeo y se logra apreciar una gama de colores mucho más amplia.En cada línea, luego de la información correspondiente a los 320 pixels, existe unaserie de pixels sin información; y luego de las 240 líneas horizontales, existe una

71

FPFRAME

FPLINE

FPLINE

FPSHIFT

FPDAT7

FPDAT6

FPDAT0

R1

G1

G3

B3

R4

R6 B320

B318

G318 R1

G1

G3

1 2 3 4 240239

Page 90: El Camino Del Conejo

Periféricos

serie de líneas sin información, conformando una trama. Esto tiene la finalidad deadecuar la frecuencia de actualización a límites manejables por el display.

Si bien la generación de este tipo de timing no es demasiado compleja, para unafrecuencia de actualización de cuadro (trama) de unos 50 Hz o más, la frecuencia dela señal FPCLOCK comienza a ascender al orden de los MHz, lo que empieza a seralgo no tan simple de manejar, y es conveniente utilizar algún controlador que noshaga más liviana la tarea.

Existe además de las vistas, una cuarta línea de control denominada LCDPWR oDISP, que cumple la función de activar el display. La misma debe ponerse en estadoactivo luego de aplicada la alimentación (preferentemente una vez que el hardwarede control de timing está inicializado) y retirarse antes de la desaparición de lamisma, para minimizar inconvenientes en el funcionamiento del display y maximizarsu vida útil.La tensión de alimentación del display puede ser de 3 ó 5V, y la que regula elcontraste se ubica en el orden de los 25V.

Breve descripción del controlador

Luego de mucho evaluar y analizar, elegimos finalmente un controlador inteligente.Entre las diversas opciones que hay en el mercado, seleccionamos uno que nospermita obtener la mejor performance, minimizando el hardware asociado y losproblemas de timing.El elegido es un controlador para displays LCD de alta resolución de Epson, elS1D13706, permitiendo controlar no sólo displays color pasivos sino también blancoy negro y TFT. Si bien la configuración de un dispositivo de esta envergadura esalgo compleja, afortunadamente el fabricante se apiada de nosotros y nos provee unsoftware que nos permite, a partir de los valores operacionales del display, generarlos datos a escribir en los diferentes registros.En el controlador, la imagen a enviar al display se aloja en una RAM interna de80KB (81920 bytes), la cual es direccionada de forma lineal por diecisiete líneas deaddress, y accesible mediante un bus de datos de 16-bits. En un espacio dedireccionamiento de 8-bits adicional al de memoria, se encuentran los registros decontrol que nos permiten modificar el funcionamiento a voluntad. La interfaz entre elcontrolador y el procesador host puede elegirse entre una variedad de procesadoresespecíficos soportados y dos modos genéricos, mediante pines destinados a tal fin. Elarbitraje de la memoria entre la generación de la imagen en el display y el acceso porparte del procesador se resuelve mediante la petición de wait-states al procesador;sin embargo, este controlador de avanzada presenta un tiempo garantizado máximo,lo que permite funcionar sin wait-states si se garantiza una mínima duración al ciclode acceso, razón por la cual lo seleccionamos.

72

Page 91: El Camino Del Conejo

Bus de datos

La tensión de operación para la interfaz con el display y el procesador es de 3,3 V,mientras que el core puede funcionar a 3,3V o a tensiones más bajas

El controlador se encarga de todo lo referente al despliegue de la imagen, la cualreside, como dijéramos, en su memoria interna (80KB). Mediante los registros decontrol, es posible indicar en qué zona de memoria comienza la pantalla y sutamaño, así como también la cantidad de bits asignados a definir el color de cadapixel, lo cual a su vez determinará la distribución de la memoria.El controlador opera asignando una cantidad de bits a cada color primario. En estecaso se trata de seis bits para cada uno. Esto determina una paleta RGB 6:6:6, esdecir, 218=256K (262144) colores posibles. En la memoria de pantalla, según elmodo de resolución seleccionado, se asignará una determinada cantidad de bits paracada pixel. Si asignamos ocho bits tendremos 8 bpp (8 bits por pixel); si asignamoscuatro bits tendremos 4 bpp (4 bits por pixel). Para 8bpp, cada byte representa unpixel y los mismos se distribuyen de arriba a abajo y de izquierda a derecha,conforme avanzan las posiciones de memoria. Para 4bpp es similar, pero seempaquetan dos pixels por byte y el pixel menos significativo (el "de la izquierda")ocupa el nibble más significativo.En el caso de trabajar con 4bpp, se pueden mostrar en pantalla un máximo de 16colores simultáneos, elegidos de una paleta de 262144 colores posibles. En el casode 8bpp, son máximo 256 colores simultáneos, elegidos de una paleta de 262144colores posibles. El controlador maneja todo esto de forma transparente, elprogramador especifica el modo de operación, carga la paleta, y trabaja con losíndices, similar a como se haría con cualquier pantalla o formato de archivo deimagen en modo indexado (indexed-color).Dada la capacidad de RAM y la estructura interna del controlador, es posiblealmacenar varias pantallas en memoria y cambiar la posición de inicio, siempre ycuando, claro está, su tamaño y resolución permitan que quepa. Existen ademásalgunas funciones de soporte adicionales como picture-in-picture y rotación de laimagen 90º, las que lamentablemente no hemos tenido tiempo de explotaroportunamente.

Desarrollo

Enumeramos a continuación los puntos principales que evaluamos para estedesarrollo:� Debido a que el controlador puede funcionar a 3,3 V o menos, hemos elegido unmódulo RCM3300.

� Podemos emplear el bus de Rabbit para agilizar la operación. Dado que Rabbit2000 y 3000 no tienen línea de WAIT , READY, o equivalente, sino quegeneran una inserción automática de wait-states por configuración,configuraremos la cantidad de ciclos de espera necesarios para garantizar untiempo de acceso mínimo que sea mayor al especificado para el controlador;

73

Page 92: El Camino Del Conejo

Periféricos

siempre será más rápido que interrogar manualmente la línea de WAIT .Debido a que no todo el bus de direcciones está disponible en los módulos (sí enel procesador para quien quiere desarrollar su propio hardware), generaremosparte de las direcciones pertinentes mediante I/O.

� En cuanto al bus de datos de 16-bits, lo que haremos es duplicar nuestros 8-bitsconectando parte alta y parte baja del bus entre sí. De este modo, la informaciónsiempre sale del Rabbit por los mismos ocho pines, y el controlador la lee por losocho altos o los ocho bajos, según indique la señal BHE (Bus High-byteEnable), que no es otra cosa que la inversión de A0.

� Para poder acceder al bus de Rabbit, deberemos utilizar el bus auxiliar de I/O

que provee R3000. Para la frecuencia de reloj utilizada en esta nota, en unRCM3300, deberemos insertar unos 15 ciclos de espera en el rango de I/Oexterno a utilizar. Dado que empleamos PE.4, este rango será de 0x8000 a0x9FFF; seis de los bits menos significativos del bus de direcciones estánaccesibles en PB.2 a PB.7, mientras que PA.0 a PA.7 ofician de bus de datos.

� De este modo, la RAM interna del controlador aparece en el mapa de memoriadel Rabbit, en el espacio de I/O externo, como 2048 bancos de 64 bytes que serepiten en el área entre 0x8000 y 0x9FFF (0x8000 a 0x803F, repite de 0x8040 a0x807F, etc.). Los pines de I/O adicionales seleccionan el banco. La razón por laque hacemos esto es porque no disponemos de todo el bus de address en estosmódulos.

� Operando sobre un pin adicional, PC.2, elegimos si accedemos a la RAM o alespacio de registros internos del controlador

Entre las limitaciones de cantidad de pines y posibilidad de operación, elegimosaquéllos que nos agilizaban las rutinas de manejo, sin interferir con los periféricosadicionales presentes en el kit de desarrollo utilizado (RCM3360).

74

DB5DB6DB7DB8DB9DB10DB11DB12DB13DB14DB15

DB0DB1DB2DB3DB4

PA.0PA.1PA.2PA.3PA.4PA.5PA.6PA.7

AB0AB1AB2AB3AB4AB5AB6AB7AB8AB9AB10AB11AB12AB13AB14AB15AB16RD

WE0

WE1 (BHE)

CS

PC.2

PD.6PD.7

PE.4

PC.4

PB.0

AUX I/O

Data bus

M/R

PB.2PB.3PB.4PB.5PB.6PB.7

AUX I/O

address bus

A0A1A2A3A4A5

S1D13706

PG.0PG.1PG.2PG.3PG.4PG.5PG.6PG.7

IORD

IOWR

IOSTROBE

D0D1D2D3D4D5D6D7

Page 93: El Camino Del Conejo

Bus de datos

La alimentación del controlador la hacemos como indica el diagrama siguiente,mediante los pines separados para core e I/O. El gráfico también incluye los pines deconfiguración y el oscilador de reloj:

Finalmente, la conexión con el display se realiza de la siguiente forma:

Para escribir los drivers, debemos hacer las siguientes consideraciones:� Tenemos un bus de 16-bits simulado dentro de uno de 8-bits, con señalizaciónsimilar a la utilizada por el 8086, es decir, cuando operamos sobre el byte "alto"(parte "alta" del bus, D8 a D15), lo indicamos activando la señal BHE .Nuestro bus de direcciones posee una parte baja "real" y una parte alta simuladamediante I/O, por lo que luego de realizar la simulación, procederemos a efectuaruna operación de I/O externo normal.

� Si decidimos utilizar 4bpp, cada pantalla es de 38400 bytes, lo cual cabeperfectamente dentro de 16 líneas de address y nos permite utilizar los punterosdel micro como enteros (16-bits). Podemos alojar solamente una pantallacompleta, pero nos permite prescindir de A16 (PC.4) y simplificar el desarrollo.

� Si decidimos utilizar 8bpp, cada pantalla es de 76800 bytes, lo cual trae lacomplicación adicional de que nuestro bus de direcciones es ahora de 17-bits, ylos punteros de nuestro procesador son de 16-bits. Recibiremos entonces ladirección como un long, es decir, 32-bits.

75

CNF0

CNF1

CNF2

CNF3

BS

RD/WR

IOVDD

COREVDD

CLKI

GND

OSC

S1D13706

CNF4

CNF5

CNF6

CNF7

Vdd

Vdd

(3,3V)

(3,3V)

50MHz

D0D1D2D3D4D5D6D7

CL2, CP

CL1, LOAD

FRM

DISP

FPDAT0FPDAT1FPDAT2FPDAT3FPDAT4FPDAT5FPDAT6FPDAT7

FPSHIFT

FPLINE

FPFRAME

VDD

VLCD, VEE

GND

25V

(contraste)

display

GPO

Vdd (3,3V)

S1D13706

Page 94: El Camino Del Conejo

Periféricos

� A los fines prácticos nos conviene incluir toda la operación de generación delíneas de direcciones en una subrutina. Dado que tendremos bastantes datos paraescribir, necesitamos hacerlo rápido, razón por la cual utilizamos assembler paralas rutinas críticas.

Analizado esto, mostramos a continuación las rutinas de más bajo nivel que nospermiten operar este hardware en 8bpp. Las de 4bpp son algo más simples, y seencuentran en las notas de aplicación que figuran en el CD adjunto. Todo lorelacionado con assembler y pasaje de parámetros lo hemos visto en el primercapítulo.

#asm rootread13706:: call addressbus ; pone addresses y BHE, devuelve address I/O en DE ex de,hl ioe ld a,(hl) ; lee port paralelo ld h,0 ld l,a ret

;sp+2: A0-A15;sp+4: A16;sp+6: datawrite13706:: call addressbus ; pone addresses y BHE, devuelve address I/O en DE ld hl,(sp+6) ; lee dato ld a,l ex de,hl ioe ld (HL),a ; pone dato ret

;sp+2: dirección de retorno a la función que nos llama;sp+4: A0-A15;sp+6: A16;sp+8: datoaddressbus:

ld hl,(sp+4) ; lee addressex de,hl ; en DEld hl,(sp+6) ; lee address A16ld a,l ; en A

ab2: ld hl,PGDR ; apunta a portioi ld (hl),d ; pone parte altald hl,PDDR ; apunta a portioi ld (hl),e ; pone parte baja (7,6)ld hl,PCDR ; apunta a portioi res 4,(HL) ; A16=0rrca ; A16=1 ?jr nc,ok3 ; noioi set 4,(HL) ; sí, A16=1

ok3:ld a,e ; A= A7-A0ld hl,PBDR ; apunta a port paraleloioi set 0,(hl) ; BHE=1rrc e ; debo activar BHE ? (test LSB=1)jr nc,ok1 ; noioi res 0,(HL) ; sí, BHE=0

ok1:and 0x3F ; A6,A7 = 0ld e,a

76

Page 95: El Camino Del Conejo

Bus de datos

ld d,0x80 ; I/O = 0x8000 + A5-A0 = 10000000 00xxxxxxret

#endasm

La inicialización de los pines del micro es la siguiente:

WrPortI ( PGDDR,&PGDDRShadow,'\B11111111' );WrPortI ( PEDDR,&PEDDRShadow,'\B00010000' );WrPortI ( PDDDR,&PDDDRShadow,'\B11000000' );WrPortI ( PBDDR,&PBDDRShadow,'\B11111111' );WrPortI ( PBDR,&PBDRShadow,'\B11000000' );WrPortI ( PCDR,&PCDRShadow,'\B11101111' ); // AB16=0, M/R = M

// Port E bit 4 Chip Select 15 wait-states#define STROBE 0x10#define CSREGISTER IB4CR#define CSSHADOW IB4CRShadow#define CSCONFIG 0x08

// Inicializa bit como I/OWrPortI(PEFR, &PEFRShadow, (PEFRShadow|STROBE));

// Inicializa bit como salidaWrPortI(PEDDR, &PEDDRShadow, (PEDDRShadow|STROBE));

// Inicializa bit como chip select.WrPortI(CSREGISTER, &CSSHADOW, CSCONFIG);

// clockeado por PCLK/2WrPortI(PECR, &PECRShadow, (PECRShadow & ~0xFF));

Algoritmos

A continuación, haremos una somera descripción de los algoritmos involucrados enel desarrollo de funciones para este tipo de displays. Un análisis más exhaustivo,junto con lo relacionado a la inicialización del controlador y el software desarrolladoen base a estos algoritmos, se encuentra en las notas de aplicación que se alojan en elCD que acompaña a esta edición. CAN-035 desarrolla soporte en 4bpp y CAN-036lo hace para 8bpp. Como ambas fueron hechas originalmente con un clock de menorfrecuencia, CAN-037 analiza las diferencias para el clock de 50MHz que finalmenteutilizamos.� Para ubicar un punto en pantalla en 8bpp, calculamos su posición en memoriasabiendo que alojamos un pixel por byte, es decir: mem�x�320�y . Para

hacerlo en 4bpp, la fórmula es, mem�x

2�160�y dado que tenemos dos pixels

por byte; el resto dex

2nos dirá qué nibble utilizar

� Para graficar funciones, debemos tener en cuenta que la coordenada (0;0) se hallaen el extremo superior izquierdo de la pantalla.

� Para mostrar pantallas, deberemos agrupar los datos de modo tal de poderenviarlos de una forma que aproveche de manera eficiente la estructura dememoria. Si comparamos la estructura de memoria del display con la forma de

77

Page 96: El Camino Del Conejo

Periféricos

guardar imágenes en 256 y 16 colores (para 8bpp y 4bpp respectivamente) enformato BMP, veríamos que son muy similares, por ejemplo: BMP va de abajo aarriba y el display de arriba a abajo, por lo que la imagen se ve espejadaverticalmente. Además, BMP incluye un encabezado que contiene la paleta decolores. Por consiguiente, para adaptar una imagen en 4bpp, debemos llevarla ala resolución deseada, reducirla a 16 colores, espejarla verticalmente, salvarla enformato BMP y por último descartar los 118 bytes del comienzo con algún editorhexadecimal. Entre los bytes a descartar tomaremos los bytes 54 a 117, los cualescorresponden a la paleta en formato BGR0 (4 bytes), y la guardaremos comoRGB. Para adaptar una imagen 8bpp, debemos llevarla a la resolución deseada,reducirla a 256 colores, espejarla verticalmente, salvarla en formato BMP y porúltimo descartar los 1078 bytes del comienzo. Entre los bytes a descartartomaremos los bytes 54 a 1077, los cuales corresponden a la paleta en formatoBGR0 (4 bytes), y la guardaremos como RGB. Dado que esto último es algotedioso, se recomienda desarrollar un pequeño programita que lo haga, cosa quequien escribe ya ha hecho y acompaña a este texto dentro del CD coninformación adicional (CAN-038).

� Para desplegar textos, deberemos generar las letras manualmente, "pintando" (ono) los pixels en el display con el formato del caracter. La forma más común (ybastante eficiente) de almacenar tipografías en memoria, consiste en agrupar lospixels "pintados" del caracter en bytes, en el sentido horizontal, es decir, un bytealoja ocho pixels que corresponden a la parte superior del caracter, de izquierda aderecha, de MSb a LSb. Si el ancho del caracter es mayor a dieciséis pixels,entonces se utilizarán grupos de dos bytes. Ésta es la forma en la que se alojan lastipografías provistas con las bibliotecas de funciones de Dynamic C, y con unsimple algoritmo las podemos convertir para su uso con un controlador dedisplays color. Simplemente, chequearemos el estado de cada pixel, y si éste estápintado, lo coloreamos en el display. De igual modo, si no lo está, podemosutilizar un color de fondo, o dejarlo sin modificar.

Un último comentario. Recordemos que una pantalla consiste de 320x240 pixels, locual son 76800 bytes en 8bpp. Semejante cantidad de información requierealmacenamiento en xmem, y una rutina eficiente de copiado hacia el display para queno se note un barrido molesto. Analizamos una de estas rutinas en el capítulo sobremanejo de memoria extendida.

Displays LCD gráficos

En el libro introductorio sobre desarrollo con Rabbit, mientras veíamos algunosejemplos, deslizamos la forma de controlar un display gráfico inteligente LCD de320x240 al bus del procesador. Estos displays, junto con algunos displays OLED,tienen la particularidad de que el controlador está incluido en el módulo que seadquiere, es decir, directamente podemos conectarnos al micro con el display. Por lo

78

Page 97: El Camino Del Conejo

Bus de datos

general, la interfaz es del tipo Intel 8080, o permite configurarse como tal, con locual es compatible con Rabbit, y podemos conectarlo al bus sin problemas. En loscasos en que no, podemos recurrir al bit banging y simular el timing por software.El procedimiento para conectar uno de estos displays al bus es sencillo y muysimilar a lo visto para el controlador del display color, se trata simplemente deconfigurar los IOSTROBES y escribir pequeños segmentos de código a modo dedrivers. Utilizamos el bus de datos; dado que estos displays son mayormente de 5Vempleamos un R2000:

#asm;la función requiere dos parametros:;@sp+2= address;@sp+4= data;LCD_Write::

ld hl,(sp+4) ; dato (LSB)ld a,lld hl,(sp+2) ; address (LSB)ioe ld (HL),a ; escriberet

#endasm

En el caso particular del 320x240, existe una colección de bibliotecas de funcionesde Rabbit que resuelven todo el tema de presentación en pantalla, con un soportegráfico muy poderoso, incluso permitiendo conectar pantallas sensibles al tacto. Elsoftware realiza la lectura de la pantalla y permite dibujar botones con imágenes otextos, y ventanas con texto justificado en su interior. A fin de no ser reiterativos,simplemente recordaremos que toda esta información está bajo la forma de notas deaplicación en el CD que acompañara al libro introductorio, aunque también las

79

D0D1D2D3D4D5D6D7

IORD

IOWR

A0

PE.7

D0D1D2D3D4D5D6D7

A0

CS

RST

+5V

RESET

RD

WR

PG320240

Page 98: El Camino Del Conejo

Periféricos

hemos incluido en el CD que se adjunta con esta edición. CAN-013 y CAN-014analizan el soporte que incluye Rabbit para displays LCD y pantallas sensibles altacto, CAN-015 y CAN-020 estudian estas pantallas y su utilización (veremos unejemplo de un controlador para touchscreen en la sección sobre SPI), y finalmente,CAN-016 y CAN-021 canalizan todo en una biblioteca de funciones: simplementeincluyendo Cika320240FRST.lib tenemos soporte para displays LCD gráficos320x240 con pantalla sensible al tacto:

#use Cika320240FRST.lib

Displays OLED

En cuanto a los displays OLED, si bien son tecnológicamente innovadores, suhardware de interfaz está cubierto por los análisis hechos para los otros displays.Como se observa, utilizamos el bus auxiliar de I/O en un R3000, dados los 3,3V deoperación del display:

#define PORTA_AUX_IO

#asm root;@sp+2= dato/comando a escribir;Write_Com::

ld hl,(sp+2) ; obtiene comando (LSB)ld a,lioe ld (0x8000),a ; lo escriberet

Write_Data::ld hl,(sp+2) ; obtiene dato (LSB)ld a,l

80

IORD

IOWR

D0D1D2D3D4D5D6D7

CS

RST

+5V

RESET

RD

WR

D/CPB.2

PE.4

PA.0PA.1PA.2PA.3PA.4PA.5PA.6PA.7

AUX I/O bus UG9664GFDAF

Page 99: El Camino Del Conejo

Bus de datos

ioe ld (0x8001),a ; lo escriberet

#endasm

#define Read_Data() RdPortE(0x8001)

Breve descripción del display

Estos displays son sumamente versátiles, la memoria puede alojar una pantallacompleta a una resolución de color de 16 bits por pixel (16bpp), y el controladortiene una serie de primitivas gráficas que permiten dibujar líneas y rectángulos con osin relleno, con un simple comando. Esto simplifica enormemente la tarea delprocesador para la presentación de menúes y demás tareas de una interfaz con elusuario, haciéndolos atractivos incluso para procesadores más chicos o con menosmemoria. No poseen generador de caracteres, pero es posible definir el área dememoria en la cual se escribe, lo que relaja notablemente la tarea de dibujar lasletras de forma manual, permitiéndonos disponer de varias tipografías.La estructura de memoria de la pantalla es lineal, similar al controlador para

displays LCD color. Los pixels se agrupan horizontalmente, correspondiendo elprimer byte de memoria al primer pixel de la primera línea de arriba a la izquierda, yel último byte al último pixel de la última línea de abajo a la derecha, para el modode 8bpp. En el modo de 16bpp, deberemos reemplazar byte por word en el textoanterior. De todos modos, la estructura puede ser cambiada alterando los registrosdel controlador que (valga la redundancia) la controlan. Por comodidad y similitudcon los otros displays ya estudiados, la mantendremos así, seteando el modoremapped del controlador.El direccionamiento del byte a leer o escribir en memoria se hace mediante

comandos, especificando la región de pantalla que nos ocupa, en pixels. Elcontrolador tiene además un contador auto-incrementado, el cual apunta a ladirección siguiente luego de una lectura o escritura. Esto resulta óptimo para enviarlos datos byte por byte hasta completar el área elegida; sea para dibujar un caracter oun ícono.Una característica interesante del display, es que puede funcionar a una alta

velocidad de acceso, sin necesidad de contención ni chequeo de flag de ocupado(busy), liberando prontamente al micro para otras tareas.

Algoritmos

El software de uno de los modelos que hemos probado está desarrollado en unaserie de notas de aplicación escritas para la empresa que comercializa estos displaysen Argentina. Las mismas (CAN-029 y CAN-030) se incluyen en el CD queacompaña a este libro. No obstante, enunciamos las características principales atener en cuenta para el desarrollo de algoritmos:

81

Page 100: El Camino Del Conejo

Periféricos

� Como el display ya incorpora primitivas para dibujo de líneas y rectángulos,simplemente utilizaremos estos comandos. Para direccionar un punto, lo queharemos es trazar una línea de un punto de longitud.

� Para graficar funciones, debemos tener en cuenta que la coordenada (0;0) se hallaen el extremo superior izquierdo de la pantalla.

� Para mostrar íconos, caracteres, o pantallas, deberemos definir el área de pantallaigual a la del bitmap en cuestión y enviar los datos, de esta forma se aprovechanlos contadores auto-incrementados y la estructura de memoria; esto se reducesimplemente a enviar todos los bytes corridos.

� En el caso de pantallas, si comparamos la estructura de memoria del display conla forma de guardar imágenes de 24-bits en formato BMP, veríamos que son muysimilares, por ejemplo: BMP va de abajo a arriba y el display de arriba a abajo,por lo que la imagen se ve espejada verticalmente; BMP usa tres bytes (B, G, R)para la información de color y el display utiliza dos bytes o uno, según elformato. Además, BMP incluye un encabezado de 54 bytes. Por consiguiente,para adaptar una imagen, debemos llevarla a la resolución deseada, espejarlaverticalmente, salvarla en formato BMP y luego de descartar los 54 bytes delcomienzo, procesar la paleta al formato del SSD1332 en el modo de color quevayamos a utilizar. Esta tarea está cubierta en CAN-031, nota adjunta en el CDde información adicional.

� Para imprimir textos, indicamos la posición en pantalla mediante suscoordenadas y restringimos el área de escritura al tamaño del caracter. Luego,escribiremos uno o dos bytes (según el modo de color) por cada pixel delcaracter, todos de corrido.

Finalmente, recordemos que una pantalla en 16bpp consiste de 2x96x64=12288bytes. Si bien no es demasiado, si alojamos varias pantallas se requierealmacenamiento en xmem. Una rutina eficiente de copiado hacia el display no es tannecesaria como en el caso del display color LCD, pero no está de más. Analizaremosuna de estas rutinas en el apartado sobre manejo de memoria en otro de los capítulosde este texto.

Buses serie

Existe una gran cantidad de buses serie, es decir, esquemas o normas para quedistintos tipos de periféricos de diversos fabricantes puedan dialogar conmicroprocesadores o microcontroladores, mediante una cantidad reducida deconexiones, en las cuales la información se transmite en serie. De los existentes,tomamos los más comunes, y luego de una breve introducción a la tecnología yoperación, describimos como aprovechar la implementación en Rabbit.

82

Page 101: El Camino Del Conejo

Buses serie

SPI

Se trata de una interfaz serie sincrónica, SPI significa Serial Peripheral Interface(Interfaz Serie para Periféricos). En esencia, existe una conexión en sentido master-slave (MOSI: Master Out, Slave In) y otra en sentido slave-master (MISO: MasterIn, Slave Out). Ambas cambian sus datos al ritmo marcado por la señal de reloj. Elperiférico (slave) es seleccionado mediante una señal de chip select, y es posibleconectar varios dispositivos en daisy-chain1, de modo que los datos enviados por elmaster van ingresando en un dispositivo, saliendo y luego ingresando en otro y asísucesivamente. Cada dispositivo es seleccionado como dijéramos, por sucorrespondiente chip select.Debido a la existencia de dos vías de comunicación, SPI permite operar en fullduplex. Tanto los datos entrantes como los salientes están sincronizados por unamisma señal de reloj. La polaridad de este reloj (CPOL) y la fase del mismo (CPHA)en la cual cambian los datos es tema de varios conflictos, no sólo la nomenclatura yel empleo de éstas cambia según el fabricante, sino que aun los "modos" o "tipos"pueden no coincidir.A fin de poder estudiarlo y manejarlo, observamos que las combinaciones posiblesde CPOL y CPHA definen cuatro modos de trabajo, los cuales enumeraremostomando CPOL como más significativo que CPHA:� Modo 0: Reloj reposa en estado lógico bajo, datos válidos en flanco ascendente:los datos, tanto del master como del slave, cambian de estado en el flancodescendente. Tanto el master como el slave muestrean los datos en el flancoascendente, de modo que siempre sean vistos en el punto en que son más estables(la mitad del tiempo de bit).

� Modo 1: Reloj reposa en estado lógico bajo, datos válidos en flanco descendente(CPHA=1, reloj adelantado 180º).

� Modo 2: Reloj reposa en estado lógico alto, datos válidos en flanco descendente(CPOL=1, reloj invertido)

� Modo 3: Reloj reposa en estado lógico alto, datos válidos en flanco ascendente(CPOL=CPHA=1, reloj invertido y adelantado 180º)

1 La traducción de daisy-chain es cadena de margaritas, lo cual viene a modo alegórico de la formade armar collares o brazaletes con estas simpáticas florecillas, insertando el tallo de una margarita enel centro de la anterior, y así sucesivamente hasta cerrar un círculo del tamaño deseado; claraanalogía de la conexión de dispositivos electrónicos entre sí, la salida de uno con la entrada del otro,hasta cerrar el lazo...

83

Page 102: El Camino Del Conejo

Periféricos

Según el fabricante, los "modos 0 al 3" pueden llamarse "tipos 1 al 4". Laespecificación original es de Motorola (hoy Freescale), y observando hojas de datoscon cierta experiencia (68HC05C4, 68HC11 Reference Manual), no se observa quese hable de modos ni de tipos, simplemente se describen las cuatro combinaciones.Un software gratuito provisto por el mismo fabricante (SPIGen), describe como"tipos 1 al 4" a estas combinaciones... Un detalle que se observa en estasespecificaciones "originales", es que en los modos CPHA=1, el chip select puedepermanecer activo, pero en los modos CPHA=0, se especifica que debe ponerseinactivo luego de cada byte.El descripto como "modo 0" es el "standard de facto", dado que muchos

dispositivos no configurables utilizan este modo de operación. Obviamente master yperiféricos deben trabajar en el mismo "modo".

En muchos casos la salida del dispositivo se coloca en alta impedancia cuando nose lo selecciona, esto permite unir varios dispositivos en un solo cable y obviar lacadena de margaritasDado que si bien hay varias combinaciones, no se trata de otra cosa que de hacercambiar de estado un par de pines de manera consistente; una interfaz SPI puedeimplementarse con una USART, un (par de) registro(s) de desplazamiento, o porsoftware. Debido a que generalmente una USART transmite el bit menossignificativo (LSb) primero, y SPI transmite el bit más significativo (MSb) primero,probablemente deberá utilizarse algún esquema para permutar el orden de los bits sise emplea una USART. De igual modo, el esquema de clocking suele ser diferente.

Rabbit 2000 y 3000

En Rabbit 2000 y 3000, la interfaz SPI puede utilizar cualquiera de las USART, esdecir, puertos serie con capacidad sincrónica, o también puede implementarse sobreun puerto paralelo, conmutando los pines por software.En el R2000, el clock es fijo para una USART, lo cual se corresponde en SPI a loque sería CPOL=CPHA=1, es decir, los datos cambian en el flanco descendente yson muestreados en el flanco ascendente, pero el clock reposa en estado lógico alto.

84

CS

MOSIMISO MSb LSb

CLK CPOL=0CPHA=0

CPOL=0CPHA=1

CPOL=1

CPOL=1

CPHA=0

CPHA=1

Page 103: El Camino Del Conejo

Buses serie

En el R3000, operando sobre los bits 5, 4 del SxER (Serial port x ExtendedRegister), se pueden obtener las cuatro combinaciones. La combinación 01 esequivalente a lo que describimos como "modo 0", que es el más usado.En ambos casos, como la USART transmite el LSb primero, se deberá permutar elorden de los bits tanto en los bytes transmitidos como en los recibidos. La bibliotecade funciones de SPI se ocupa de esto mediante una búsqueda en tabla, de formatransparente.

En Dynamic C, la elección de la interfaz se realiza mediante las macros:

#define SPI_SER_A#define SPI_SER_B

para R3000, disponemos además de:

#define SPI_SER_C#define SPI_SER_D

Para setear el modo de trabajo de la interfaz, utilizamos la macro

#define SPI_CLOCK_MODE 0

Este modo no es otra cosa que el valor de los bits 5, 4 del SxER (recordemos queesto no es válido para R2000), por lo que la correspondencia entreSPI_CLOCK_MODE, CPOL CPHA, y los modos de trabajo enumeradosanteriormente, es la siguiente:

SPI_CLOCK_MODE SxER 5,4 CPOL CPHA modo enumerado

0 00 1 1 3

1 01 0 0 0

2 10 0 1 1

3 11 1 0 2

Finalmente, la velocidad de operación la definimos mediante la siguiente macro,que setea el registro correspondiente del Timer A encargado de determinar elbaudrate del puerto serie en cuestión:

#define SPI_CLK_DIVISOR 5

Si empleamos el port serie B, y queremos utilizar los pines alternativos del portparalelo D, lo indicamos mediante la siguiente macro:

#define SERB_USEPORTD

85

Page 104: El Camino Del Conejo

Periféricos

Si en vez de una de las USART elegimos utilizar un port paralelo para nuestrainterfaz SPI, lo indicamos mediante la siguiente macro:

#define SPI_MODE_PARALLEL

El software de la biblioteca de funciones opera entonces generando un pulsodescendente sobre un clock que reposa en estado alto. Esto puede invertirse (pulsoascendente sobre clock que reposa en estado bajo) mediante la macro:

#define SPI_INVERT_CLOCK

Los bits y registros involucrados pueden dejarse en los valores por defecto (PD.1

para transmisión, PD.0 para clock, y PD.3 para recepción), o bien puedenespecificarse. En este último caso, deben especificarse todos. Las macros son lassiguientes:

// registro para clock y TxD#define SPI_TX_REG PDDR

// bit para TxD#define SPI_TXD_BIT 1

// bit para Clock#define SPI_CLK_BIT 0

// registro para RxD#define SPI_RX_REG PDDR

// máscara para bit usado en RxD (2^bit)#define SPI_RXD_MASK 8

El usuario deberá inicializar los pines de I/O como entrada y salida segúncorresponda.

Rabbit 4000 y 5000

En Rabbit 4000 y 5000, la interfaz SPI sólo puede utilizar cualquiera de lasUSART (puertos serie con capacidad sincrónica)Las USART de estos micros tienen la opción de transmitir el MSb primero, por loque todo transcurre en hardware.

En Dynamic C, la elección de la interfaz se realiza mediante las macros:

#define SPI_SER_A#define SPI_SER_B#define SPI_SER_C#define SPI_SER_D

Dada la complejidad de asignación de pines, se requiere que el usuario lo haga porsu cuenta. Al menos para el pin de recepción disponemos de las siguientes macros:

86

Page 105: El Camino Del Conejo

Buses serie

#define SPI_RX_PORT SPI_RX_PC SPI_RX_PD SPI_RX_PE

Para setear el modo de trabajo de la interfaz, utilizamos la misma macro que hemosvisto:

#define SPI_CLOCK_MODE 0

Finalmente, la velocidad de operación la definimos mediante la misma macro quepara R2000 y R3000, sólo que para R4000 y R5000 no utiliza el Timer A sino elBRG interno que controla el baudrate del puerto serie en cuestión:

#define SPI_CLK_DIVISOR 5

Uso y ejemplos

Para leer y escribir en la interfaz SPI, contamos con las siguientes funciones:

SPIinit(): Inicializa la USARTSPIRead(): lee una determinada cantidad de bitsSPIWrite(): escribe una determinada cantidad de bitsSPIWrRd(): lee y escribe simultáneamente una determinada cantidad de bits

Finalmente, como ya sabemos, para incluir la biblioteca de funciones usamos:

#use SPI.LIB

Ejemplos

La utilidad principal de SPI en un micro como Rabbit se centra en la capacidadpara comunicarse con conversores analógico-digitales, lo que nos permitirá accederal mundo analógico. Sin embargo, también es posible conectar controladores depantallas sensibles al tacto y acceder a memorias no volátiles como EEPROM(25x256, etc.), FRAM como las FM25256 ó FM25L256 de Ramtron, y otrosperiféricos. Los ejemplos a continuación están orientados a R2000 y R3000,debiendo modificarse adecuadamente para R4000 y R5000.

Conversores A/D MCP3204 y MCP3208

Como conversor analógico a digital, emplearemos un MCP3204 de Microchip. Éstees un conversor de 12-bits por aproximaciones sucesivas (SAR). Dispone de cuatroentradas (ocho en el MCP3208) que puede configurar como cuatro canales single-ended o dos canales pseudo-diferenciales (el potencial de la entrada IN- no debealejarse más de unos 100mV del potencial de GND). La referencia de tensión debe

87

Page 106: El Camino Del Conejo

Periféricos

ser externa, funciona a 3 ó 5V y su velocidad de conversión (12 pulsos de clock)ronda las 100.000 muestras por segundo a 5V.Siendo un conversor de 12 bits, su ecuación de funcionamiento es:

D�212�V i

V ref

, donde Vi es la tensión equivalente de entrada, Vref la tensión de

referencia, y D el número entregado por el conversor. Definimos como tensiónequivalente de entrada a la tensión en el pin IN+ en modo single-ended y a ladiferencia IN+ - IN- en modo diferencial.Si observamos la hoja de datos del MCP3204, veremos que formateando

adecuadamente el comando, obtendremos 24 bits de los cuales nuestro resultadoestará justificado a la derecha, es decir, en los 12 bits menos significativos. El"modo de trabajo" en SPI podrá ser cualquiera en el cual los datos cambien en elflanco descendente, para ser leídos en el flanco ascendente, es decir,CPOL=CPHA=0 ó CPOL=CPHA=1 (comúnmente conocidos como "0" y "3"), estonos permite trabajar con SPI_CLOCK_MODE=0, que es el valor por defecto enR3000 y el único en R2000.

Conectaremos el MCP3204 a un port serie sincrónico de un módulo Rabbit. En estecaso utilizamos el port serie B de un RCM2100. Necesitamos además una salidapara oficiar de chip enable, utilizaremos PD.0 para tal fin.De más está decir que la calidad y estabilidad de la referencia de tensión �V ref �

influyen directamente sobre la precisión y estabilidad de la medición, y que laconexión de las masas analógica y digital es fundamental, junto con una buenadisposición de las pistas y la utilización de planos de tierra. Para el ejemplo, comosimplemente nos interesa desarrollar el driver y observar que funciona, usaremos laalimentación del módulo como referencia y conectaremos un potenciómetro entreVdd y masa, con su cursor a la entrada del canal CH0; configurado como single-ended. El valor de salida corresponderá a 0x000 cuando el cursor esté a masa y0xFFF cuando esté a Vdd. De acuerdo al blindaje (o la falta de éste) de la conexión ala entrada, deberá ponerse algún capacitor de filtro.

88

CLK

Do

Di

CSCH0

Vref

AGND DGND

Vdd

CLKB (PB.0)

RXB (PC.5)

TXB (PC.4)

PD.0

.1

10K

MCP3204 .1

Page 107: El Camino Del Conejo

Buses serie

Para el software, comenzamos indicando que vamos a utilizar la interfaz SPI en elport B:

#define SPI_SER_B#define SPI_CLK_DIVISOR 5#use SPI.LIB

Ahora inicializamos el hardware de Rabbit para funcionar con el conversor:

void initAD(){

BitWrPortI ( PDDR, &PDDRShadow, 1, 0 ); // CS = 1 (off)BitWrPortI ( PDDCR, &PDDCRShadow, 0, 0 ); // PD.0 normalWrPortI ( PDCR, &PDCRShadow, 0 );BitWrPortI ( PDDDR, &PDDDRShadow, 1, 0 ); // PD.0 = outputSPIinit(); // inicializa interfaz SPI

}

Seguidamente, un simple driver para realizar la lectura del A/D. Lejos de sereficiente, trata de ser claro, dado que, por experiencia, no es del todo directa lacomprensión del formato del MCP3204. De ser necesaria máxima velocidad, sesugiere la reescritura de esta función, es posible que se deba utilizar assembler.La variable Command es formateada acorde al comando del MCP3204: bit de start,single-ended o diferencial, y 3 bits que definen el canal, aunque en el MCP3204 sólose utilizan los dos menos significativos, el MCP3208 utiliza los tres.La rutina se invoca con el valor del canal como parámetro y devuelve el resultadode la conversión en un entero.

#define START 0x80#define SINGLE 0x40

int ReadAD ( int Channel){int Command, j;struct { // 24 bits

char b;int i;

} Data;

Command=START|SINGLE|((Channel/4)<<5)|((Channel/2)<<4)|((Channel&1)<<3);Command <<= 3; // posición para obtener LSB justif a la derechaCommand=SwapBytes(Command);// pone el MSB primero (Z80 es LSB primero)

BitWrPortI ( PDDR, &PDDRShadow, 0, 0 ); // baja CSSPIWrRd ( &Command, &Data, 3 ); // transmite y recibe 24 bitsBitWrPortI ( PDDR, &PDDRShadow, 1, 0 ); // sube CSj = Data.i; // toma 16 bits útilesj = SwapBytes ( j ) & 0x0FFF; // pone LSB primero, considera 12 bits

return(j);}

// invierte (swap) los bytes de un entero// parámetro y resultado en HL#asmSwapBytes::

ld a, L ; salva LSBld L, H ; MSB -> LSBld H, A ; recupera LSB en MSB

89

Page 108: El Camino Del Conejo

Periféricos

ret#endasm

Un simple programa de ejemplo: leemos el valor del conversor, y sabiendo que suentrada está entre 0 y 5V mostramos en la ventana stdio de Dynamic C el valor dedicha tensión.

void main (){

int Value;float volts, ScaleFactor;

ScaleFactor = 5.0/4096.0;initAD();

while (1) {Value = ReadAD ( 0 );volts = (float) Value * ScaleFactor;printf ( "Value = %5.3f \r", volts );

}}

Controlador de touchscreen ADS7846

El ADS7846 es un conversor analógico-digital de 12-bits, que además dispone deuna matriz de conmutación que permite realizar las dos configuraciones para poderleer una pantalla sensible al tacto de tipo resistivo.La touchscreen que utilizamos corresponde al tipo resistivo de 4-terminales. Setrata de una membrana relativamente rígida, adherida al display, y otra flexible porencima de ésta. La cara interna de ambas membranas recibe un recubrimientoresistivo, pero se impide el contacto directo entre ambas, de modo que la resistenciade contacto entre las mismas es muy alta. Al ejercer presión sobre la membranasuperior, ésta se deforma, disminuyendo la resistencia de contacto entre ambasmembranas. Cada membrana tiene dos de sus extremos opuestos conectados asendos terminales eléctricos, de modo que, cada membrana es en sí misma unaresistencia distribuida longitudinalmente entre los terminales de contacto, una ensentido horizontal, y la otra en sentido vertical. Al presionar sobre la membranaflexible, la resistencia horizontal y la vertical de la figura se unen en una región, quedepende de la posición y área del punto de contacto, es decir, aquel lugar donde serealiza la presión.

90

Page 109: El Camino Del Conejo

Buses serie

Si se hace circular una corriente por una cualquiera de las membranas, puedeestablecerse una diferencia de potencial que es función aproximadamente lineal de laposición entre los extremos de la misma, en los cuales están los terminales.Al ejercer presión sobre la membrana flexible, se produce el contacto entre ambasmembranas, y puede medirse la diferencia de potencial en el punto de presión, encualquiera de los terminales de la otra membrana. Si bien la resistencia de contacto yla de la otra membrana quedan en serie con la medición, su valor es losuficientemente bajo como para poder ser despreciado al efectuar una medición detensión.Esto determina la posición del área de contacto en un sentido (horizontal o

vertical); para determinar la posición en el otro sentido, se realiza la mismaoperación sobre la otra membrana.

La operación del ADS7846 es muy similar a la del MCP3204; con un adecuadoformateo de la palabra de comando se pueden obtener 24-bits, en los cuales el valorconvertido está justificado al LSb. El "modo de trabajo" en SPI podrá también sercualquiera en el cual los datos cambien en el flanco descendente, para ser leídos enel flanco ascendente, es decir, CPOL=CPHA=0 ó CPOL=CPHA=1; es decir quepodemos usar SPI_CLOCK_MODE=0, que es el valor por defecto en R3000 y elúnico en R2000.

91

T1

T2

T3

T4

Punto de contacto

Vdd

Vdd

T4 T1

T3

T2

Coordenada en Y Coordenada en X

Page 110: El Camino Del Conejo

Periféricos

Sin embargo, lo que hicimos en Cika320240FRST.lib fue aprovechar el código delas bibliotecas de funciones, dejando libres los puertos serie. Con un par de simplesmodificaciones, portamos el código para este controlador, obteniendo una bibliotecade funciones con soporte para display LCD 320x240 y touchscreen resistivo. Eldesarrollo completo se encuentra en las notas de aplicación en el CD que seadjuntara al libro introductorio, aunque también las hemos incluido en el queacompaña a esta edición. CAN-015 describe la touch screen, CAN-020 el ADS-7846, y CAN-021 realiza la integración con el display y Dynamic C. El diagrama a continuación muestra el circuito esquemático utilizado:

I2C

El bus I2C (IIC: Inter Integrated Circuit) fue diseñado por Philips en los 80's. Laespecificación original es de dos líneas bidireccionales, una para los datos (SDA) yla otra para la señal de clock (SCL). Sendas resistencias de pull-up establecen unestado inactivo en uno lógico; cuyo valor de tensión depende de la tecnologíautilizada. La velocidad máxima original era de 100Kbps (standard), revisionesposteriores la han llevado primero a 400Kbps (modo fast) y luego en el 2001(revisión 2.1) a 3.4Mbps. Existe también un modo low speed a 10Kbps. Los valorespara las resistencias de pull-up dependen de la velocidad de trabajo elegida.El dispositivo que inicia la comunicación se denomina master, al menos durante eldesarrollo de la misma. Dado que se trata de un bus en el que puede haber más de unmaster, existe un procedimiento de arbitraje mediante el cual se evita que dosmasters puedan tomar el bus al mismo tiempo.El inicio de una comunicación se determina por una secuencia especial denominadaSTART, a partir de ese momento el bus se encuentra siendo utilizado. Para liberarlo,y permitir que otro dispositivo pueda ser master, se realiza una secuencia de STOP.Ambas secuencias corresponden a violaciones de la premisa básica: la línea SDAdebe permanecer estable mientras SCL está en estado alto, los datos son leídos en el

92

T1

T2

T3

T4

+5V

Vdd

DCLK

CS

DIN

DOUT

GND

PENIRQ

47K

.1

X+

Y+

X-

Y-

10n

PB.4

PE.0

PD.3

PE.1

PB.5

Page 111: El Camino Del Conejo

Buses serie

flanco ascendente de SCL. Tanto START como STOP introducen un flanco(descendente y ascendente respectivamente) en SDA mientras SCL está en estadoalto.

La estructura de mensaje es algo compleja. La comunicación se inicia por el mastermediante una secuencia de START, luego se transmite la dirección del dispositivo aseleccionar. A continuación, cada dispositivo en particular requerirá o no unadeterminada secuencia de datos, a fin de identificar unívocamente la dirección de y/olos datos a leer o escribir. Luego, para el caso de una escritura, el master transmiteMSb primero los bits de cada byte. Para el caso de una lectura, el dispositivotransmite MSb primero, uno a uno, los bits de cada byte, mientras el master controlael clock. En ambos casos, luego de cada byte transmitido, el dispositivo que losrecibe, sea éste slave o master, confirma con un ACK, que no es otra cosa que un biten estado lógico bajo. Si el receptor no puede recibir otro byte por el momento, loindica manteniendo SCL en estado bajo. Si por algún motivo no puede recibir más,lo indica negando el ACK, es decir, manteniendo SDA en estado lógico altoterminado el byte. Cuando el que recibe es el master, la finalización de la transacciónse indica negando el último ACK. Una nueva transacción puede iniciarsedirectamente; cuando se ha terminado con ese dispositivo, se señaliza la liberacióndel bus mediante una secuencia de STOP. A partir de entonces, cualquier masterpuede iniciar una nueva comunicación mediante una secuencia de START.

El espacio de direccionamiento es de 7-bits, el octavo bit diferencia lectura deescritura. Algunas de las direcciones se encuentran reservadas para propósitosparticulares (CBUS, expansión, etc.). En algunos dispositivos, los bits mássignificativos identifican el tipo de dispositivo, y los menos significativos identificanuno entre varios posibles de este tipo, correspondiéndose con pines que cada unotiene a tal fin.

93

SCL

SDA

START

ACK

STOP

MSb LSb

Indice DatoSDA

Master

Slave (no ACK)

DispositivoEscritura

SDA

LecturaDispositivo Indice Dato

SCL

Slave (ACK y datos)

Page 112: El Camino Del Conejo

Periféricos

Existe un caso particular, que son las direcciones correspondientes al protocoloCBUS, en el cual no es necesario realizar un ACK luego de cada byte recibido.Existen además otras modificaciones, como por ejemplo la extensión paradirecciones de 10-bits. El lector interesado puede obtener una copia de laespecificación para su regocijo.

La implementación de I2C en Rabbit es por software, y se encuentra en una serie debibliotecas de funciones con soporte para algunos dispositivos específicos y otrosgenéricos. Por lo general, es sumamente simple realizar modificaciones para soportarotros dispositivos, como veremos más adelante.Básicamente, el software soporta cualquier pin bidireccional tanto para SDA comopara SCL. Podemos utilizar los pines por defecto (SCL=PD.6, SDA=PD.7);podemos utilizar cualquier bit del port D mediante las siguientes macros:

#define I2CSCLBit 6#define I2CSDABit 7

o también podemos utilizar cualquier otro pin, operando sobre el registro de controlde dirección (PxDDR), definiendo las siguientes funciones elementales:

i2c_SCL_H(): pone SCL en estado lógico altoi2c_SCL_L(): pone SCL en estado lógico bajoi2c_SDA_H(): pone SDA en estado lógico altoi2c_SDA_L(): pone SDA en estado lógico bajoi2c_SDA(): lee SDAi2c_SCL(): lee SCL

Las funciones básicas de que disponemos están orientadas al funcionamiento comomaster; las mismas son:

i2c_init(): inicializa los pines y sus registros asociados, deberá modificarse si seopera sobre otro port paralelo que no sea el port D.

i2c_start_tx(): genera una secuencia de STARTi2c_startw_tx: genera una secuencia de START, agrega una pequeña demorai2c_send_ack(): envía un ACK (genera un pulso de clock con SDA=0)i2c_send_nak(): niega un ACK (genera un pulso de clock con SDA=1)i2c_read_char(): lee una secuencia de 8 bits, deberá llamarse a una función

adicional como i2c_send_ack() o i2c_send_nak() luego, segúncorresponda

i2c_check_ack(): lee un bit, verifica si es ACKi2c_write_char(): escribe una secuencia de 8 bits, verifica la existencia del ACKi2c_stop_tx(): genera una secuencia de STOP

94

Page 113: El Camino Del Conejo

Buses serie

i2c_wr_wait(): escribe una secuencia de 8 bits, reintenta si el receptor indica que nopuede recibir.

Otras bibliotecas de funciones adicionales incluyen soporte para chips y familias dechips específicos.

Ejemplos

Memorias EEPROM y FRAM

Si bien disponemos de RAM con batería de respaldo, puede llegar a ser útilmanejar una memoria EEPROM como la 24x64 o mejor aún una FRAM comoFM24C64 ó FM24CL64.

Al trabajar con este tipo de memorias, observamos en su hoja de datos que poseenun contador de direcciones interno, el cual se auto-incrementa luego de cadaoperación de lectura o escritura, lo que nos permitirá acceder a bloques de datosingresando solamente la dirección inicial. En el caso de una escritura en una posiciónelegida al azar, el procedimiento es simple y de comprensión directa: secuencia deSTART, dirección del dispositivo, dos bytes de índice (MSB LSB), es decir, ladirección inicial del bloque en memoria, y a continuación el bloque de datos byte abyte, mientras el slave conteste con un ACK luego de cada byte y hasta que el masterdecida finalizar.

Sin embargo, si queremos leer un byte o un bloque de bytes en una determinadaposición de memoria que no es la siguiente a la última que accedimos, deberemosescribir el contador de direcciones interno del dispositivo, esto se realizaespecificando una operación de escritura que luego se aborta, dejando paso a laoperación de lectura propiamente dicha, es decir, habrá una secuencia de START, la

95

SCL

SDA

A2

A1

A0

WP

Vdd

PD.6

PD.7

24x64

2K2

DatoSDA

Master

Slave (ACK)

Slave (no ACK)

DispositivoEscritura

SCL

Indice (MSB) Indice (LSB)

Page 114: El Camino Del Conejo

Periféricos

dirección del dispositivo, dos bytes de índice (MSB LSB) indicando la direccióninicial del bloque en memoria, otra secuencia de START (abortando la escrituraanterior e iniciando una nueva operación), la dirección del dispositivo con el bitR�W�1 indicando una lectura, y a continuación el dispositivo enviará el bloque

de datos, byte a byte, mientras el master conteste con un ACK luego de cada byte,decidiendo éste la finalización de la transferencia negando el ACK.

En este caso, lo que hicimos fue tomar las rutinas de una de las bibliotecas defunciones adicionales que se incluyen, y modificarlas a nuestro gusto. Las funcionesa continuación implementan escritura y lectura en una EEPROM o FRAM de estetipo:

#class auto#define i2cRetries 1#use "i2c.lib"

nodebug int I2CbWrite(unsigned char slave, unsigned int index,char *buf,unsigned char len)

{auto unsigned char cnt;auto short int err;

if (err=i2c_startw_tx()){i2c_stop_tx();return -10+err; // Return too long stretching

}if (err=i2c_wr_wait(slave)){

i2c_stop_tx();return -20+err; // Return no ack on slave (retried)

}if (err=i2c_write_char(index/256)){

i2c_stop_tx();return -30+err; // Return no ack on index

}if (err=i2c_write_char(index%256)){

i2c_stop_tx();return -30+err; // Return no ack on index

}for (cnt=0;cnt<len;cnt++) {

i2c_write_char(buf[cnt]);}i2c_stop_tx();return 0;

}

nodebug int I2CbRead(unsigned char slave, unsigned int index,char *buf,unsigned char len)

{

96

Master

Dispositivo Dato

SCL

Dispositivo Indice (MSB) Indice (LSB)SDA

Lectura al azar Slave

Page 115: El Camino Del Conejo

Buses serie

auto unsigned char cnt;auto short int err;

if (err=i2c_startw_tx()) {i2c_stop_tx();return -10+err; // Return too long stretching

}if (err=i2c_wr_wait(slave)){

i2c_stop_tx();return -20+err; // Return no ack on slave

}if (err=i2c_write_char(index/256)) {

i2c_stop_tx();return -30+err; // Return no ack on index

}if (err=i2c_write_char(index%256)){

i2c_stop_tx();return -30+err; // Return no ack on index

}if (err=i2c_startw_tx()){

i2c_stop_tx();return -40+err; // Return too long stretch on read

}if (err=i2c_wr_wait(slave+1)){

i2c_stop_tx();return -50+err; // Send read to slave - no ack (retried)

}for (cnt=0;cnt<len;cnt++) {

err=i2c_read_char(&buf[cnt]);if (err){

i2c_stop_tx();return -60+err;

}if (cnt==(len-1)){

i2c_send_nak();}else {

i2c_send_ack();}

}i2c_stop_tx();return 0;

}

En caso de querer respetar el hecho de interrumpir la transmisión cuando el slaveniega el ACK, deberemos modificar el código dentro del for() para evaluar estacondición, ya que el valor retornado por i2c_write_char() es el resultado de evaluarla presencia del ACK.A continuación, un ejemplo de programa principal que realiza una escritura y

lectura de la memoria:

#define EEPROM_ADDRESS 0xA0#define WRITE_TIME 5

const char test_string[] = "Hola gente !";

void main(){int return_code,i,a,j;char read_string[20];unsigned char buffer1[9],buffer2[9];

97

Page 116: El Camino Del Conejo

Periféricos

unsigned long t;

i2c_init();

return_code=I2CbWrite(EEPROM_ADDRESS,0,test_string,strlen(test_string));printf("I2CWrite returned:%d\n", return_code);

t = MS_TIMER;while((long)(MS_TIMER - t) < WRITE_TIME);

return_code=I2CbRead(EEPROM_ADDRESS,0,read_string,strlen(test_string));printf("I2Cread returned:%d\n", return_code);

read_string[strlen(test_string)] = 0;

if(return_code != 0) exit(-1);

printf("Read:%s\n", read_string);

}

Processor Companion (con FRAM)

Un processor companion es un interesante dispositivo que reúne una cantidad defunciones adicionales útiles, normalmente dispersas, en un circuito integrado. Entreestas funciones, podemos citar: memoria no-volátil, reloj de tiempo real, reset porbaja tensión, watchdog timer, contador de eventos, número de serie, y comparador.Las funciones de reloj de tiempo real y contador de eventos, son mantenidas enausencia de tensión por una pila de respaldo, mientras que la memoria y el númerode serie son FRAM.Si bien el fabricante (Ramtron) anuncia su interfaz como "2-wire", se trata de unI2C con algunas leves modificaciones de timing que no afectan el normaldesenvolvimiento dentro de un bus. Dentro de la familia FM31xxx encontramosvarias alternativas, con diversos tamaños de memoria FRAM, de 16Kb a 256Kb. Lamisma se comporta como una FM24xyy equivalente, por lo que podemos utilizar loanalizado al estudiar las memorias (24x64) para comunicarnos con la FRAM delprocessor companion. La familia 32xxx es similar, pero sin el reloj de tiempo real.El resto de la circuitería está accesible en una serie de 25 registros que responden aotra dirección de dispositivo. Desde el punto de vista del master I2C, el processorcompanion con FRAM se comporta como dos dispositivos diferentes en el bus.Accederemos a las funciones adicionales mediante el uso de las funciones I2CRead()e I2CWrite(), que figuran en la biblioteca de funciones i2c_devices.lib:

#use i2c_devices.lib

La utilidad de un chip como éste adquiere particular interés cuando diseñamosequipos para aplicaciones en las cuales la seguridad es más que importante. Porejemplo:

98

Page 117: El Camino Del Conejo

Buses serie

� si necesitamos detectar que estamos por perder la alimentación y grabar algo enmemoria no volátil, podemos usar el comparador con referencia para sensar latensión de alimentación antes del regulador.

� el pin de RST oficia como reset por baja tensión, permitiendo evitar brown-outs2.

� si nuestra aplicación es crítica o involucra la seguridad humana, el watchdogdentro del procesador puede no ser suficiente; disponemos entonces de unwatchdog externo al procesador, dentro del processor companion.

� si la precisión del RTC del procesador no es suficiente, disponemos de uno dealta precisión y estabilidad (siempre que utilicemos el tipo de cristalcorrespondiente y realicemos el procedimiento de calibración) dentro delprocessor companion.

� si necesitamos un número de serie para identificar nuestro equipo o validarnuestro software y evitar copias, disponemos de uno en FRAM que puedebloquearse y pasar a ser de lectura solamente.

� si necesitamos contar o detectar algún tipo de eventos, incluso cuando el equipoestá apagado, como por ejemplo la apertura del gabinete, disponemos de uncontador con alimentación de respaldo (la misma del RTC).

A continuación, un ejemplo de trabajo con el processor companion (reproducimossólo la parte que difiere del ejemplo con 24x64):

#define COMPANION_ADDRESS 0xD0

return_code=I2CRead(COMPANION_ADDRESS,0x01,read_string,1);printf("I2Cread returned:%d\n", return_code);

2 Un brown-out es la condición cuando la alimentación disminuye hasta por debajo de la mínimatensión de operación recomendada, sin llegar a ser un "apagón" (black-out). En estas condiciones, elmicro puede llegar a efectuar cualquier clase de cosas no deseadas, y es conveniente mantenerloreseteado mientras la tensión de alimentación no está dentro de los márgenes especificados para lacorrecta operación del procesador y demás chips involucrados.

99

SCL

SDA A1

A0

CNT1

Vcc

FM31xx

SCL

SDA

IRQ

32768

Regulador

PFI

PFO Vback

RST RST

Page 118: El Camino Del Conejo

Periféricos

if(return_code != 0) exit(-1);

printf("Cal/control: %02X\n",read_string[0]);

return_code=I2CRead(COMPANION_ADDRESS,0x0A,read_string,1);printf("I2Cread returned:%d\n", return_code);

if(return_code != 0) exit(-1);

printf("WDT control: %02X\n",read_string[0]);

return_code=I2CRead(COMPANION_ADDRESS,0x11,read_string,8);printf("I2Cread returned:%d\n", return_code);

if(return_code != 0) exit(-1);

printf("Serial number:");for(i=0;i<8;i++)

printf(" %02X",read_string[i]);

Microwire

La interfaz microwire , ��wire , ó 3�wire , es muy similar a SPI.Básicamente, tanto microwire como SPI son sincrónicas y el MSb viaja primero. Eneste caso en particular, nos interesa analizar las memorias EEPROM, que sonsutilmente diferentes.Observando la hoja de datos de una memoria como por ejemplo la 93x46, vemosque la misma valida los datos ingresados en el flanco ascendente del reloj (es decirque el controlador debería ponerlos disponibles en el flanco descendente), el cualpuede reposar en estado lógico bajo o alto. La memoria entrega los datos en elmismo flanco, de modo que el controlador deberá validarlos en el flancodescendente del reloj. Como vemos, siempre en algún sentido se trata de la fasecontraria a la que se utilizaría en SPI.La interfaz consta de cuatro pines: entrada de datos al dispositivo (D, DI), salidade datos del dispositivo (Q, DO), clock (C, CK), y chip select (S, CS). Según elfabricante, la nomenclatura puede variar. El chip select es generalmente activo enalto.

100

S

C

D

Q

Page 119: El Camino Del Conejo

Buses serie

Algunas características distintivas de las memorias con interfaz microwire como la93x46, son:� El comienzo de la transmisión se indica mediante un bit de start, es decir, Dpermanece en estado lógico alto en el primer pulso de reloj.

� La existencia de un bit en estado lógico bajo antes de comenzar el envío de losdatos, es decir, luego de recibido el comando de lectura, la memoria entrega unbit en 0 y luego los datos.

� El chip select debe pulsarse o desactivarse momentáneamente para iniciar el ciclode escritura. La señalización del estado de ocupado se realiza mediante la puestaa cero constante (siempre que el dispositivo esté seleccionado mediante chipselect) durante todo el tiempo que la memoria demora en escribir.

Rutinas caseras

Si bien algunas de las características enunciadas se encuentran en dispositivos SPI,dado el esquema de clock preferimos escribir un set de rutinas adicionales para elmanejo de microwire. En este caso en particular, aprovechamos que nos es fáciltrabajar en 16-bits y lo hacemos, de este modo disminuimos el overhead en cadatransacción. Básicamente, lo que haremos es poner el dato y generar un pulsoascendente de clock para escribir, o generar el pulso y luego leer el dato. Deberemos

101

S

C

D

Q BusyReady

Ciclo de escrituraIngreso de datos

Start

S

C

D

Q 0 Datos

Start

Page 120: El Camino Del Conejo

Periféricos

prestar especial atención a utilizar el número de parte que soporta acceso en 16-bitsy conectar el pin ORG correctamente para formato 16-bits ! 3

// Microwire: drivers en PE, lectura en PB#define EE_S 0#define EE_C 1#define EE_D 3#define EE_Q 1

// SEND CLK#define EE_CLK BitWrPortI(PEDR,&PEDRShadow,1,EE_C);\

BitWrPortI(PEDR,&PEDRShadow,0,EE_C);

// SELECT, SEND START BIT & CLK, Set S+D#define EE_STb BitWrPortI(PEDR,&PEDRShadow,1,EE_S);\

BitWrPortI(PEDR,&PEDRShadow,1,EE_D); EE_CLK;

// DESELECT, Clear S#define EE_CS BitWrPortI(PEDR,&PEDRShadow,0,EE_S);

int EE_RD(int bits){int data;

data=0;while(bits--){

EE_CLK;data=(data<<1)|BitRdPortI(PBDR,EE_Q);

} return(data);}

void EE_WR(int command, int bits){

while(bits--){BitWrPortI(PEDR,&PEDRShadow,command&0x8000,EE_D);

EE_CLK;command=(command<<1);

}

}

// BUSY WAIT AFTER WRITEvoid EE_BYW(void){

3 Lejos de conocer todas las marcas y formatos, podemos adelantar que Microchip denominaM93x46B a la que soporta 16-bits y M93x46C a la que dispone de pin ORG. Holtek es "standard",al igual que Atmel; ST tiene la serie 93C standard y la serie 93S en 16-bits pero sin pin ORG. Enalgunos casos difiere el pinout.

102

S

C

D

Q ORG

Vdd

PE.0

PE.1

PB.1

PE.3 Vdd

93x46

Page 121: El Camino Del Conejo

Buses serie

BitWrPortI(PEDR,&PEDRShadow,1,EE_S); // Set Swhile(!BitRdPortI(PBDR,EE_Q)); // Busy wait hasta que Q=1EE_CS; // deselect

}

Ejemplo

Este programa agrega algunas rutinas básicas para 93x46 y realiza un simpleejemplo del manejo de la memoria en 16-bits.

// EWEN (0011xxxx..), ERAL (0010xxxx..), EWDS (0000xxxx..)#define EWEN 0x3000#define ERAL 0x2000#define EWDS 0x0000

// EWEN/EWDS COMMANDvoid EE_CMD(int command,int bits){

EE_STb; // Start bitEE_WR(command,bits); // Send CMDEE_CS; // Clear S y vuelve

}

void EE_ERAL(int bits){

EE_STb; // Start bitEE_WR(ERAL,bits); // Send CMDEE_CS; // Clear S y vuelveEE_BYW(); // Busy wait

}

// Read dataint EE_93C46_RD(int address,int bits){int data;

address=((address<<8)&0xBF00)|0x8000; // 10xxxxxx CMD RDEE_STb; // Start bitEE_WR(address,8); // Send CMD, 8 bits

// BitRdPortI(PBDR,EE_Q); // check zerodata=EE_RD(bits); // dataEE_CS; // Clear Sreturn(data);

}

// Write datavoid EE_93C46_WR(int address,int data,int bits){

address=((address<<8)&0x7F00)|0x4000; // 01xxxxxx CMD WREE_STb; // Start bitEE_WR(address,8); // Send CMD, 8 bitsEE_WR(data,bits); // Send data, 16 bitsEE_CS; // Clear SEE_BYW(); // Busy wait

}

main(){int i,data;

WrPortI ( PEDR,&PEDRShadow,0x00 ); // Outputs LowWrPortI ( PEDDR,&PEDDRShadow,'\B10001011' ); // PE0,1,3,7 = output

103

Page 122: El Camino Del Conejo

Periféricos

WrPortI ( PEFR, &PEFRShadow, 0 ); // PE: no I/O strobe

EE_CMD(EWEN,8); // Enable write commandsEE_ERAL(8); // Erase allEE_CMD(EWDS,8); // Disable write commandsfor(i=0;i<64;i++){ if((data=EE_93C46_RD(i,16)) != 0xFFFF){

printf("\nError, no en blanco\n"); exit(-1);

}}

EE_CMD(EWEN,8); // Enable write commandsfor(i=0;i<64;i++){

EE_93C46_WR(i,(79*i+11)*53,16);}EE_CMD(EWDS,8); // Disable write commands

for(i=0;i<64;i++){ if((data=EE_93C46_RD(i,16)) != (79*i+11)*53){

printf("Error, no leo lo que escribo en %02X\n",i); exit(-1);

}}printf("OK!\n");

}

BL233B

El BL233B es un chip que provee la capacidad de actuar como interfaz I2C, SPI, ó1-wire4, conectándose con el host mediante una interfaz serie asincrónica. Estádesarrollado en base a un microcontrolador PIC de 18 pines. Mediante el empleo decomandos ASCII simples, es posible operar con memorias o periféricos I2C, SPI ó 1-wire sin necesidad de usar un micro con soporte para ellos, ponerse a desarrollar elprotocolo, o hacer que nuestro procesador principal pierda tiempo en estas tareas.Los pines, además, pueden operarse indistintamente como I/O.Rabbit, como vimos, incluye soporte para I2C y SPI en forma de bibliotecas defunciones. Si bien SPI es manejado por las USART, I2C no lo es, y tal vez elconsumo de recursos no sea compatible con los requerimientos de timing de unsistema complicado. O tal vez, el ponerse a desarrollar el código necesario en formade llamadas a función en lenguaje C nos insuma un tiempo de desarrollo que notenemos, particularmente si no estamos muy acostumbrados a trabajar conperiféricos I2C. Por estos motivos, y otros que seguramente se nos pueden llegar aocurrir al leer estas líneas, puede llegar a ser interesante disponer de uncoprocesador para I2C, SPI ó 1-wire, controlado por simples comandos ASCII desdeuna de las UARTs.

4 1-wire es un bus de una conexión (y masa) en el que el mismo cable transporta la alimentación hacialos periféricos y los datos en ambos sentidos. La señalización se realiza mediante dos ciclos detrabajo diferentes para cada estado, y algunas señales adicionales reconocidas por su duración.

104

Page 123: El Camino Del Conejo

Buses serie

El BL233B tiene además la posibilidad de grabarle "macros" que almacena en sumemoria EEPROM, con lo cual es posible asignarle una tarea que cumple por sísolo, reportando los resultados por la interfaz serie, lo cual lo hace aún másinteresante como coprocesador autónomo, particularmente por el hecho de que yaestá disponible y depurado, acortando nuestros tiempos de desarrollo.

El ejemplo que veremos a continuación está basado en el siguiente circuito:

Enviando un string ASCII por RxD, recibiremos la respuesta por TxD, pines loscuales conectaremos a una de las UARTs de Rabbit. En este caso en particular,utilizamos el puerto serie D.A continuación, damos los ejemplos empleados para probar este circuito, queobservados conjuntamente con la lectura de la hoja de datos del BL233B, darán unaidea rápida de cómo utilizarlo. De todos modos, comentamos lo más importante.

Para leer 64 bytes desde el inicio de la EEPROM:

G1 SA0 00 00 R40P

G1: configura el bus I2C en los pines elegidosS: STARTA0: dirección de dispositivo00 00: dirección de inicio del bloqueR40: lectura de 0x40 bytesP: STOP

105

+5VMCP3204 24x64

A0A1A2

WP

SDA

SCL

P6

IRQ

MOSI

MISO

CLK

CS P5

TxD RxD

ANA

Ins

SCL1

SDA1

SPI2

MOSI

MISO

CLK

2

1

3

comandos

respuestas 57600bps

14.7456MHz

Page 124: El Camino Del Conejo

Periféricos

Para escribir HOLA al inicio de la EEPROM y ver que lo escribió:

SA0 00 00 484F4B41 T0D0A SA0 00 00 R04P

484F4B41: valores hexadecimales para el ASCII de HOLA.T0D0A: hace que el BL233B envíe un retorno de carro y avance de línea, para poderentender mejor lo que observamos

Para leer el pulsador:

?

?: devuelve el byte de estado, el bit 4 es IRQ , que es donde colocamos el botón.

Para encender y apagar el LED, sin activar el CS de SPI, previamente se debeconfigurar los pines correspondientes como salidas:

O609F

O609F: coloca el valor 0x9F en el registro TRIS y 0x60 en el registro de salida, esdecir, configura los bits 5 y 6 como salidas y el resto como entradas, luego colocalos bits 5 y 6 en alto.

Luego, estamos en condiciones de prender y apagar el LED:

O20 O60

O60: pone bits 5 y 6 en alto (todos los demás son entradas), apaga el LED en bit 6O20: pone el bit 6 en bajo y el bit 5 en alto, enciende el LED en bit 6

Para leer la entrada CH0 del MCP3204:

G9P O40 Y W60 R03 O60

G9P: configura el bus SPI en los pines elegidosO40: coloca el bit 5 en bajo, habilita CS del conversorY: indica que la transferencia es bidireccional, se recibe a la vez que se transmiteW60: envía el valor 0x60 por SPI, lo cual configura al conversor para leer CH0R03: lee 0x03 bytesO60: coloca el bit 5 en alto, remueve CS del conversor

106

Page 125: El Camino Del Conejo

Buses serie

Como este conversor es de 12-bits, se debe descartar el último nibble pues leemos elbyte más significativo primero.

El siguiente es el programa utilizado en el Rabbit:

#class auto// Define tamaño de buffers (2^n-1)#define DINBUFSIZE 15#define DOUTBUFSIZE 15

char buffer[1000];

void printresponse(){int n; while(!(n=serDread(buffer,sizeof(buffer),300)));

buffer[n]=0;printf(buffer);

}

void main(){int n;

BitWrPortI(PCDR,&PCDRShadow,1,2); // RTS highserDopen(57600);serDputc('?');printresponse();serDputs("G1 SA0 00 00 R40P");printresponse();serDputs("SA0 00 00 484F4B41 T0D0A SA0 00 00 R04P");printresponse();while(1){

serDputs("O609F G9P O40 Y W60 R03 O60");printresponse();serDputs("T09 O20 ?");printresponse();

}

}

Y la siguiente es la respuesta que obtuvimos al correr el programa:

18 <- el estado484F4B410405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3 <- los 64 bytes pedidos484F4B41 <- HOLA000000 <- valor del A/D (hicimos un corto en la entrada a masa) 19 <- el estado000000 09 <- presionamos el botón000000 09 000000 19 <- liberamos el botón000000 19 FFFFFE <- valor del A/D (hicimos un corto en la entrada a Vref)

107

Page 126: El Camino Del Conejo

Periféricos

Apéndice: Low Voltage blue's

La interconexión de micros y periféricos de 3 y 5V es un tema ambivalente. Por unlado es más sencillo de lo que podría imaginarse, pero por el otro, no es algo trivialcomo para dejarlo librado a la buena voluntad de la providencia. Se trata en esenciade un simple análisis de compatibilidad de niveles lógicos, que en algunos casosresulta más relajado por particularidades de algunos de los componentes.Como seguramente todos los practicantes de la electrónica sabemos, para que dosfamilias lógicas puedan comunicarse entre sí, sus niveles lógicos deben sercompatibles. Además, para que ninguno de los dos dispositivos tome el hábito defumar, no debemos exceder los límites máximos permitidos.

Micro de 3V, periférico de 5V

Para conectar un micro de 3V a un periférico de 5V, en primera instanciadeberemos analizar dos cosas:� que el periférico de 5V tenga el nivel lógico mínimo de entrada losuficientemente bajo como para aceptar la tensión mínima de uno lógicoentregada por el micro. El caso de máxima tensión de cero lógico tambiéndebería ser contemplado, pero esto es mucho más probable de ser cumplido

� que el micro de 3V sea 5V-tolerant, es decir, que soporte la máxima tensión deuno lógico que le entregue el periférico.

Micros 5V-tolerant

Si el micro es capaz de tolerar 5V en sus entradas (como el Rabbit 3000), lacuestión se resuelve analizando la compatibilidad de niveles lógicos.

Compatibilidad de niveles

Si la compatibilidad de niveles lógicos se cumple, podemos realizar una conexióndirecta a los pines analizados. Por supuesto que si el periférico es unidireccional,podemos prescindir del análisis en la dirección no utilizada...PERO, ¡antes de conectar un periférico de 5V a un bus, debemos analizar que nosólo es el 5V-tolerant micro el que está conectado al bus! Cualquier tensión mayor ala tensión de alimentación que pongamos en un bus, puede afectar a cualquiera delos dispositivos conectados que no sea 5V-tolerant, por ejemplo las memorias. Laforma más fácil es utilizar los pines de I/O de forma dedicada, pero la más versátil,aprovechando el bus, en un micro como Rabbit 3000, es emplear el bus auxiliar de

I/O. De esta forma, dejamos el bus principal tranquilo, y conectamos nuestro(s)periférico(s) de 5V en el bus auxiliar de I/O.

108

Page 127: El Camino Del Conejo

Apéndice: Low Voltage blue's

Incompatibilidad de niveles

En caso que el periférico no acepte los niveles que entrega el micro, como porejemplo muchos de los módulos de displays LCD inteligentes (alfanuméricos ygráficos), deberemos intercalar algún dispositivo que nos permita trasladar losniveles lógicos:� Para lograr elevar el nivel lógico de 3V a 5V, podemos utilizar una familia lógicatradicional con niveles bajos, que acepte la entrada del micro de 3V, y entregue5V a la salida. Esta familia es la 74HCT, con niveles de entrada diseñados paratolerar las fluctuaciones de una familia como la TTL5, manteniendo el bajoconsumo y velocidad de HCMOS. Disponemos entonces de los tradicionalesbuffers 74HCT244 (sólo comunicación unidireccional) y los buffersbidireccionales 74HCT245, entre otros. El buffer, obviamente suma un tiempo depropagación, y podemos emplear las señales IORD para DIR y BUFENpara G , como analizaremos más adelante.

� También podemos utilizar alguna alternativa como buffers con open-collector ybuffers bidireccionales que veremos a continuación para los micros que notoleran 5V en sus entradas.

Micros que no toleran 5V en sus entradas

En micros que no son 5V-tolerant, debemos utilizar algún esquema de conversión deniveles. En esta categoría entran el Rabbit 4000 y el 5000.� Se pueden emplear familias lógicas con open-collector y sin protección de salida(HC, HCT y LS no entran dentro de esta categoría), como AHC, que nospermitan "elevar" los 3V a 5V; y lo mismo en el sentido inverso. El requisito deno tener protección a la salida es porque el diodo de clamping hace que la tensiónprovista por el periférico vaya directamente a la fuente de alimentación. Enesquemas de bajo consumo, de reducida capacidad por normas de seguridadintrínseca, o con reguladores serie, esto produce un incremento de la tensión de

5 El nivel de tensión mínima de uno lógico es de 2V

109

+5V

Vdd

buffer buffer

DIRG

DIR=output

G=0

74HCT245

Page 128: El Camino Del Conejo

Periféricos

alimentación que puede llegar a destruir algún circuito. En interfaces de altacapacidad, debido a que el RC que se forma demora el tiempo de transición delas señales, esto puede dificultar la operación.

� También podemos atenuar con divisores resistivos. En interfaces de altacapacidad, el RC que se forma afecta de forma más pronunciada el tiempo detransición de las señales y no suele ser recomendable.

� Otra opción más saludable es emplear buffers bidireccionales diseñadosespecialmente para adaptar niveles lógicos, como por ejemplo el 74LVX3245, elcual se controla desde una interfaz de 3V y permite comunicación en uno u otrosentido entre el lado de 3V y el de 5V. El buffer, obviamente suma un tiempo depropagación en ambos sentidos; la capacidad distribuida sigue existiendo pero suefecto es menos pronunciado, al ser el buffer de baja impedancia de salida. Al serel buffer bidireccional, se deberá controlar la conmutación de dirección y lahabilitación de la salida. En el caso de utilizar el bus auxiliar de I/O, podemosemplear la línea IORD para DIR y BUFEN para G . El timing de estas

110

+5VVdd

+5V

Vdd

+5V

Vdd I

HC, HCT, LS

R

C

pull-up

distribuidapistas, chips

buffer RC buffer ZoC

Vdd

R

C

distribuida

pistas, chips

2R

5V

2RC/3 2RC/3

5Vx2/3=3,3V

Page 129: El Camino Del Conejo

Apéndice: Low Voltage blue's

señales lo hemos visto al principio del capítulo al analizar la conexión de laFRAM paralelo. Se aconseja un cuidadoso análisis del mismo.

Micro de 5V, periférico de 3V

Para conectar un periférico de 3V a un micro de 5V, vale todo el análisis anterior,pero intercambiando periférico y micro en los enunciados, es decir, el micro debetener el nivel lógico mínimo de entrada lo suficientemente bajo como para aceptar latensión mínima de uno lógico entregada por el periférico, y éste debe ser 5V-tolerant. Un micro como el Rabbit 2000, que si bien puede funcionar a 3V seencuentra alimentado a 5V en los módulos, tiene un nivel mínimo de uno lógicoigual al 70% de la tensión de alimentación, es decir: 3,5V. Esto no le permite recibirperiféricos de 3V, al menos no de forma confiable según su hoja de datos. En estecaso, deberemos utilizar alguna alternativa similar a las analizadas, peroconsiderando que es el periférico el de 3V. No estudiaremos demasiado este caso,dado que la tendencia es bajar cada vez más la tensión de alimentación, por lo que espreferible desarrollar con micros de 3V si disponemos de un periférico de interésque no funciona a 5V. En algunos casos, el insertar un buffer de alguna familialógica que funcione a 3V y sea 5V-tolerant es suficiente.

111

+5V

Vdd

buffer buffer

DIRG

DIR=output

G=0

74LVX3245

Page 130: El Camino Del Conejo
Page 131: El Camino Del Conejo

Manejo de memoria extendida

Introducción: R2000/3000 vs. R4000/5000

Los conceptos que estudiamos en este capítulo son válidos para todos los modelosde Rabbit a la fecha de edición de este libro (después de todo, no es otra cosa queálgebra de punteros en bajo nivel).

Sin embargo, en los ejemplos en assembler no consideramos los registros punterosde 32-bits ni el direccionamiento de 24-bits de Rabbit 4000 y 5000, por lo que sibien son aplicables en estos últimos, pueden no resultar la mejor opción. Dado quepara estos micros Dynamic C incorpora el concepto de far pointers, los ejemplos enC tambien pueden no resultar óptimos para ellos. En el apéndice al final del capítulorealizamos un pequeño análisis de esto.

Datos en xmem

C

En C, disponemos de un conjunto de funciones que nos facilitan el manejo de datosen memoria extendida.

Bloques de datos: display gráfico LCD

Si por ejemplo, necesitamos hacer el volcado de pantalla de un display gráficointeligente LCD, cada pantalla de 320x240 pixels es un bloque de 9600 bytes. Sibien podríamos guardar una de ellas en área root, lo más simple a la hora de

compilar es alojarlas en xmem. Trabajando en C con las funciones disponibles, loque podemos hacer es armar un pequeño buffer en área root, e ir copiando de xmema ese buffer, y de allí al display. Esto es así debido a que los punteros que tenemospara realizar el volcado, direccionan en 16-bits. Para acceder directamente desdexmem y escribir en el hardware, necesitamos manipular el esquema de memoria, locual haremos más adelante en assembler con displays que requieren mayor espacio

de memoria.El siguiente ejemplo es una rutina de volcado de memoria para un display gráfico

inteligente LCD de 320x240 pixels:

#define IMGCHUNK 240

#define CHUNKS 40

113

Page 132: El Camino Del Conejo

Manejo de memoria extendida

void LCD_dump(long imgdata){int x,y;unsigned char buffer[IMGCHUNK];

LCD_cursor(1200); // selecciona pantalla gráficaLCD_WriteCmd(0x42); // MWRITEfor(y=0;y<CHUNKS;y++){ xmem2root(&buffer,imgdata+IMGCHUNK*y+sizeof(long),IMGCHUNK);

// trae bloque de xmem for(x=0;x<IMGCHUNK;x++)

LCD_Write(buffer[x]); // copia al display}

}

Por supuesto que esta función necesita urgente una reescritura para eficiencia, perola idea era mostrar el uso de xmem2root() para traer los datos desde xmem al bufferen el segmento de datos, y de allí copiarlos al display.

Datos individuales

Otra alternativa, particularmente cuando son pocos datos aislados, es utilizar las

funciones que para cada tipo se han provisto:

xgetint(var);xsetint(var,valor);xgetlong(var);xsetlong(var,valor);xgetfloat(var);xsetfloat(var,valor);

El ejemplo siguiente muestra la forma de utilizar estas funciones, junto con la

directiva xdata, que define constantes en flash en xmem. Para poder utilizar xsetint()y las otras funciones similares, necesitamos obtener un bloque de memoria alcomienzo del programa, lo cual realizamos con llamadas a xalloc(). Dado que éste esun sistema dedicado que debe cumplir una determinada tarea por sí solo, y noesperamos cambios en la estructura de variables, no necesitamos devolver memoriaal pool, razón por la cual no incluimos llamadas a xrelease().

xdata entero {(int)1234};xdata largo {(long)123456};xdata flota {(float)123.456};

main(){int p;long pp;float ppp;long ptri,ptrl,ptrf;

p=xgetint(entero);pp=xgetlong(largo);ppp=xgetfloat(flota);printf("%d %ld %f\n",p,pp,ppp);ptri=xalloc(sizeof(int));ptrf=xalloc(sizeof(long));ptrl=xalloc(sizeof(float));xsetint(ptri,5678);

114

Page 133: El Camino Del Conejo

Datos en xmem

xsetlong(ptrl,567890);xsetfloat(ptrf,567.89);p=xgetint(ptri);pp=xgetlong(ptrl);ppp=xgetfloat(ptrf);printf("%d %ld %f\n",p,pp,ppp);

}

Arrays y estructuras

Cuando necesitamos trabajar con arrays y estructuras en xmem, dado que no

disponemos de elementos para acceder a la totalidad del array o estructura, sino sóloa uno de sus elementos, una alternativa interesante es pensar el problema como si setratara de un medio externo. De esta forma, accedemos de a uno a los elementos,copiándolos en un espejo en área root, y operando desde allí; de igual modo a comohiciéramos para copiar la imagen al display LCD.Supongamos por ejemplo que necesitamos trabajar con un array de enteros en

xmem. Necesitaremos declarar un entero largo para que sea el nombre del array(puntero al inicio del array); y luego, en tiempo de ejecución, reservamos un bloquede memoria en xmem:

#define SIZE 30000long name;

...name=xalloc(SIZE*sizeof(int));...

dejando así constituido el array.Si nuestro array es una constante en vez de una variable, lo podemos declarar con

xdata o traerlo de disco al momento de compilar con #ximport. En ambos casos, yatenemos el puntero, y si usamos #ximport, deberemos recordar que los primeros 4bytes (un entero largo) tienen la longitud del bloque.

xdata neim1 {(int)1234, 4567};

#ximport "filename" neim2

Para acceder a un elemento determinado del array, necesitamos un lugar dondeguardarlo en forma local, o directamente utilizar el valor retornado por la funciónxgetint():

int saminteguer, mailocalcopi;

mailocalcopi=xgetint(name+sizeof(int)*i); //mailocalcopi=name[i]saminteguer+=mailocalcopi;saminteguer-=xgetint(name+sizeof(int)*32); // saminteguer-=name[32]xsetint(name+sizeof(int)*15233,saminteguer); //name[15233]=samintegermailocalcopi=xgetint(neim2+sizeof(int)*i+sizeof(long));

//mailocalcopi=neim2[i]

115

Page 134: El Camino Del Conejo

Manejo de memoria extendida

Como vemos, es muy similar a trabajar con álgebra de punteros, excepto quedebemos considerar el tamaño del elemento en la ecuación; esto es en realidad loque hace internamente el compilador cuando trabajamos con arrays. Por supuestoque sólo podemos usar xsetint() si el destino es una variable, no una constante.De igual modo, podemos operar con arrays de enteros largos o números en coma

flotante, reemplazando por los tipos y funciones correspondientes.

Para trabajar con estructuras o arrays de estructuras, representando posiblementeuna base de datos, deberemos operar de la misma forma, pero manteniendo siempreuna copia local del registro sobre el que queremos operar. Lo que hacemos es copiarel bloque de memoria xmem del tamaño de la estructura a root, y operar desde allícon el registro en forma local.

Primero necesitamos declarar la estructura:

struct registro {int campo1;int campo2;char campo3[32];float campo4;

};

Luego, necesitamos declarar un entero largo para que sea el nombre del array de

estructuras (puntero al inicio del array). Luego, en tiempo de ejecución, reservamosun bloque de memoria en xmem:

#define SIZE 3000long dibeis;

...dibeis=xalloc(SIZE*sizeof(struct registro));...

Si nuestra base de datos o array de estructuras es una constante en vez de unavariable, la podemos declarar trabajosamente con xdata o traerla de disco con#ximport, de modo similar a como hiciéramos con el array de enteros. En amboscasos, ya tenemos el puntero, y si usamos #ximport, deberemos recordar que losprimeros 4 bytes (un entero largo) tienen la longitud del bloque

xdata dbase1 {(int)1234, 4567," Este texto tiene 32 caracteres ",1234.5678};

#ximport "filename" dbase2

Para acceder a un elemento determinado del array de estructuras, necesitamos tenerun lugar en root donde copiarlo, dado que no disponemos de una función quedevuelva la estructura.

struct registro disreyister;

116

Page 135: El Camino Del Conejo

Datos en xmem

Procedemos entonces a obtener la copia del registro desde la base, y hacemos con éllo que tengamos que hacer. Si, por ejemplo, el problema es la búsqueda de unregistro en la base, vamos trayéndolos de a uno y comparamos contra el modelo:

xmem2root(&disreyister,dibeis+i*sizeof(struct registro)),sizeof(struct(registro))); // trae registro i

memcmp(&modelo,&disreyister,sizeof(struct registro)); // compara

Algo más eficiente tal vez (dependiendo del problema), sería traer solamente el

campo con el que se compara, supongamos que es campo2:

for(i=0;i<SIZE;i++){ if(target==xgetint(dibeis+i*sizeof(struct registro)+sizeof(int)))

printf("found");}

Como se ve, en este caso debemos tener presente el orden de los elementos en laestructura (los campos en el registro), nada más difícil de lo que normalmente

debemos hacer en un sistema dedicado en assembler.

Para grabar un registro, siempre y cuando no esté en flash, obviamente, utilizamosla función complementaria:

root2xmem(dibeis+i*sizeof(struct registro),&disreyister,sizeof(struct registro)); // actualiza registro i

117

Direcciones

de memoria fisica

0x0000

0xFFFF

Direcciones

de memoria logica

0x00000

0xFFFFF

xmem

data

stack

code

dibeis

&disreyistercampo2

dibeis + i * sizeof(struct registro)

dibeis + i * sizeof(struct registro) + sizeof(int)

registro i

array de registros

disreyister

campo1

campo2

campo3

campo4

xmem2root()

Page 136: El Camino Del Conejo

Manejo de memoria extendida

Assembler

En assembler, el problema es otro, dado que disponemos del control total delprocesador.

Datos individuales

Cuando se trata de datos aislados, podemos utilizar las instrucciones de acceso en20-bits; de hecho, las rutinas que hemos visto en C para acceder a datos individuales,son en realidad rutinas en assembler haciendo uso de estas instrucciones, como

muestra esta porción de código extraída de getint():

ld h,dld l,einc d ; Test whether near end of 64k pageld a,cldp hl,(hl) ; Get the value

Las tres primeras instrucciones toman la dirección de BC DE y la colocan en A HL,

como lo requiere la instrucción ldp HL,(HL), luego, dado que esta instrucción nopuede incrementar correctamente si la dirección de la palabra es 0xFFFF, se detectaesta condición y se agrega código para leer el byte siguiente en memoria física.

Bloques de datos

Si bien, como vimos, existen instrucciones para manejo de datos en 20-bits enassembler, cuando se trata de un bloque largo es mucho más simple y eficiente

utilizar los punteros de 16-bits del procesador, que es lo que hacen las funcionesxmem2root() y root2xmem() que utilizamos en C. Para esto, deberemos direccionardentro del segmento xmem, estableciendo una ventana a la zona de memoria físicadonde están nuestros datos.A continuación, veremos un par de ejemplos prometidos, en los cuales hacemos elvolcado de una imagen de su buffer en xmem (donde la guardamos con un simple

#ximport) a un display. Dado que no queremos que se note el barrido de la imagen,escribimos la rutina en assembler.

Display color OLED

Nuestro primer ejemplo es un display OLED, cuyo controlador nos permite mostrarimágenes con 16-bits de resolución de color, en un formato de 96x64 pixels, lo cualson unos 12288 bytes; nada que no podamos transferir velozmente con un R3000.

� Nuestra rutina recibirá la dirección de un bloque de datos en xmem, lo que seráun entero largo con 20-bits significativos, y la longitud del bloque (un entero). Afin de minimizar el problema, limitamos el largo máximo del bloque a 4096bytes, de modo que siempre estamos dentro de la misma ventana en xmem. Estarutina será entonces llamada unas tres veces desde C, para realizar su cometido.

118

Page 137: El Camino Del Conejo

Datos en xmem

� Utilizamos una rutina ya provista para convertir la dirección física aXPC:address, de modo de poder escribir el valor del puntero de segmento yutilizar el registro HL como puntero dentro de xmem.

� A fin de minimizar la cantidad de operaciones, escribimos de a dos pixels.

El diagrama siguiente muestra el mapeo empleado:

Seguramente habrá alguna forma más eficiente, la finalidad de este ejemplo es ladidáctica, con su correspondiente claridad, sin descuidar la eficiencia. El hardwarecorresponde al ejemplo desarrollado en el capítulo sobre periféricos.

#asm root

;@sp+2= dirección en xmem (long);@sp+6= longitud de los datos (int)

xdumpblk::

ld a,xpc ; salva XPC, debemos agregar 2 a los accesospush af ; relativos al SPld hl, (SP+6) ; Obtiene address (long) en BC:DEld c,l ; podría haberse obviado esta parte, perold b,h ; por generalidad la hemos dejadold hl, (SP+4) ; (el primer parámetro ya está en BCDE)ex de,hl

call LongToXaddr ; Convierte BC:DE a XMEM + addressld xpc,a ; DE = address, A = xpc

ld hl,(sp+8) ; hl=longitud del bloqueld c,h ; bc = "ld b,lld a,lex de,hl ; HL= address

119

Direcciones

de memoria fisica

0x0000

0xFFFF

0xE000

Direcciones

de memoria logica

0x00000

HL

XPC

4096xXPC+HL

4096xXPC+0xE000

4096xXPC+0xFFFF

0xFFFFF

HL

imagen

Page 138: El Camino Del Conejo

Manejo de memoria extendida

or ajr nz,xcbf_loop ; corrige si b=0 (djnz es 256 iteraciones)dec c

xcbf_loop:ld a,(hl) ; escribe 2 pixelsioe ld (0x8001),ainc hlld a,(hl)ioe ld (0x8001),a

inc hldjnz xcbf_loop ; loop en bxor adec cjp p,xcbf_loop ; loop en c

xcbf_done:pop af ; restablece XPCld xpc,aret

#endasm

La función en C que llama a esta rutina está basada en parte de xmem2xmem(), a laque se han hecho unas pequeñas modificaciones, y es la siguiente:

void SSD_dump (unsigned long imgdata){unsigned int tocopy,len;

SSD_home(); // address 0,0dest=0;len=96*64;imgdata+=sizeof(long); // apunta a imagenwhile(len) { // manda en bloques de 2KW

tocopy=2048;if(tocopy>len)

tocopy=len;xdumpblk(imgdata,tocopy);imgdata+=(tocopy<<1);len-=tocopy;

}}

Display color LCD

Nuestro segundo ejemplo es un display color LCD, cuyo controlador nos permitemostrar imágenes con una paleta de 256 colores, en un formato de 320x240 pixels,lo cual son unos 76800 bytes; un poco más preocupante, dado que la interfaz es máscompleja.� Nuestra rutina recibirá la dirección de un bloque de datos en xmem, lo que será

un entero largo con 20-bits significativos, y la longitud del bloque (un entero). Afin de minimizar el problema, limitamos el largo máximo del bloque a 4096bytes, de modo que siempre estamos dentro de la misma ventana en xmem. Esta

120

Page 139: El Camino Del Conejo

Datos en xmem

rutina será entonces llamada unas diecinueve veces1 desde C, para realizar sucometido.

� Utilizamos una rutina ya provista para convertir la dirección física aXPC:address, igual que en el ejemplo anterior, de modo de poder escribir elvalor del puntero de segmento y utilizar esta vez el registro IX como índicedentro de xmem.

� A fin de minimizar la cantidad de operaciones en la interfaz, que es mucho máscompleja, escribimos de a dos pixels.

� El ejemplo en sí es muy similar al anterior, la diferencia y su complejidadadicional asociada está en que debemos entregar una dirección de 17-bits alcontrolador de display sobre una interfaz que simula un bus de address de 17-bits, basándonos en el bus auxiliar de I/O.

� Otra diferencia al margen es que dado que éste es un modo indexado de color2,también enviamos la paleta.

Una vez más, seguramente habrá alguna forma más eficiente, la finalidad de esteejemplo es la didáctica, con su correspondiente claridad, y sin descuidar laeficiencia. El hardware corresponde al ejemplo desarrollado en el capítulo sobreperiféricos, del cual recomendamos su observación y análisis para una mejor

comprensión del software.

#asm root

1 Dado que separamos en bloques de 4KB, y 76800 no es divisible por 4096, son 18 bloques de 4096bytes (2048 words) y el resto.

2 Un modo indexado de color es aquél en el cual cada color está representado por un número o índiceque lo ubica en una paleta de n colores. En los modos no indexados, cada color está representado porsus componentes, como en el caso del ejemplo con OLED, los 16-bits son 5B 6G 5R

121

Direcciones

de memoria fisica

0x0000

0xFFFF

0xE000

Direcciones

de memoria logica

0x00000

XPC

4096xXPC+0xE000

4096xXPC+0xFFFF

0xFFFFF

IX

IX

4096xXPC+IX

imagen

Page 140: El Camino Del Conejo

Manejo de memoria extendida

;IY: A0-A15;A: A16;(IX+0): dato pixel izquierdo;(IX+1): dato pixel derechowritew13706:: ld a',a ; save A16 ld hl,iy ; A15-A0 ex de,hl ; en DE call ab2 ; addresses y BHE ld a,(IX+0) ; lee dato ex de,hl ioe ld (HL),a ; pone dato ex af,af' ; restore A16 ld hl,iy ; lee address ld de,0x0001 ; next byte add hl,de ; inc address adc a,d ; (adc a,0x00) propaga carry ex de,hl ; A15-A0 in DE ld a',a ; save A16 call ab2 ; address bus ex de,hl ld a,(IX+1) ; lee dato ioe ld (HL),a ; pone dato ex af,af' ; restore A16 ret

xdumpblk::ld a,xpc ; salva frame pointer, y xpcpush afpush ix

ld hl,(sp+12) ; xmem address (long) en BC:DEld c,lld b,hld hl,(sp+10)ex de,hl

; Convierte BC:DE a XPC y addresscall LongToXaddr ; DE = logical address, A = xpcld xpc,aex de,hl

ld ix,hl ; IX = source

ld iy,(sp+6) ; a:iy=dest, hl=lengthld hl,(sp+8)ld a,lld hl,(sp+14)

ld c,h ; c:b=lengthld b,lex af,af'ld a,lor ajr nz,.xcbf_2dec c

.xcbf_2:ex af,af'

xcbf_loop:call writew13706 ; preserva BC,IX,IY,Ald de,0x0002add ix,deadd iy,de

122

Page 141: El Camino Del Conejo

Datos en xmem

adc a,d ;(adc a,0x00) propaga carrydjnz xcbf_loopdec cjp p,xcbf_loop

xcbf_done:pop ixpop afld xpc,aret

#endasm

La función en C que llama a esta rutina es muy similar a la del ejemplo anterior. Laconstante S1DMEMSIZE es definida en otra sección del programa, y corresponde alos 76800 bytes de que hablábamos.

root useix void LCD_dump(long imgdata, triplet *paldata){

long dest;unsigned int tocopy,len;

len=S1DMEMSIZE/2;dest=0;imgdata+=sizeof(long);writepalette(paldata);while(len) {

tocopy=2048-(int)(dest&2047);if(tocopy>len)

tocopy=len;xdumpblk(dest,imgdata,tocopy);dest+=(tocopy<<1);imgdata+=(tocopy<<1);len-=tocopy;

}}

La palabra clave useix produce el salvado del registro IX en el stack, y su carga conun valor que hace simple la extracción de parámetros. Sin embargo, en esta funcióndecidimos seguir con el uso tradicional, y sólo lo empleamos para salvar dichoregistro.

123

Page 142: El Camino Del Conejo

Manejo de memoria extendida

Apéndice: La vida después de R4000 y DC10

A partir del Rabbit 4000, dado que el procesador dispone de registros de 32-bitsque puede utilizar como punteros en memoria física, es posible acceder a lamemoria de forma lineal, evitando la MMU. Dynamic C versión 10 incorporaentonces el concepto de far pointer, mediante el cual es posible direccionar memoria

extendida de una forma simple, sin tener que utilizar el álgebra de punteros de formaexplícita, a menos que uno desee hacerlo.Por ejemplo, el siguiente programa en C muestra un ejemplo de la utilización de far

pointers:

struct estruct {char nombre[20];

int edad;};

far unsigned char *pepe;far struct estruct *ura;

main(){long i;

pepe=(far unsigned char *) xalloc(100000);ura=(far struct estruct *) xalloc(10000);

for(i=0;i<100000;i++) printf("%c %d",pepe[i],ura->edad);}

Más allá de la carencia de utilidad e inicialización y la reiteración de acceso almismo elemento de la estructura; observemos una parte del código compilado paraver el manejo de punteros. Veremos que éste utiliza los punteros de 32-bits, en estecaso el registro PX, para el acceso a memoria:

printf("%c %d",pepe[i],ura->edad); 1a7c 94E5C7 ld jkhl, (0xC7E5) 15 1a7f A314 ld bcde, 20 4 1a81 EDC6 add jkhl, bcde 4 1a83 FD9D ld px, jkhl 4 1a85 9500 ld hl, (px + 0) 9 1a87 E5 push hl 10 1a88 93E9C7 ld bcde, (0xC7E9) 15 1a8b DDF5 push bcde 18 1a8d DDEE06 ld bcde, (sp + 0x06) 15 1a90 FDF1 pop jkhl 13 1a92 EDC6 add jkhl, bcde 4 1a94 FD9D ld px, jkhl 4 1a96 9500 ld hl, (px + 0) 9 1a98 2600 ld h, 0x00 4 1a9a E5 push hl 10 1a9b 21B11A ld hl, 0x1AB1 6 1a9e E5 push hl 10 1a9f CD131B call printf 12

124

Page 143: El Camino Del Conejo

Apéndice: La vida después de R4000 y DC10

Si recordamos nuestro primer análisis de la arquitectura Rabbit, la MMU está poralgunas razones. Una es mantener el esquema de direccionamiento de 16-bitsoriginal, el cual permite utilizar registros del micro que pueden manejarse en pocosciclos de clock. Estos nuevos registros de 32-bits son del micro, son también másgrandes, y si bien el manejo con estos punteros largos debería tener una penalidad encuanto a velocidad de operación con respecto a punteros de 16-bits, las nuevas

instrucciones del R4000 sorprenden en cuanto a su potencia. Si el acceso a memoriadebe ser de a 8-bits, los punteros de 16-bits (HL en particular) para direccionamientoindirecto siguen siendo más rápidos; para otros casos, se requerirá un análisiscuidadoso si es que el problema a resolver lo amerita. No olvidemos que esto tienesentido si operamos directamente en assembler mapeando la página de interés en elsegmento xmem, si copiamos los datos a root para poder accederlos estamos

introduciendo un overhead adicional y es preferible usar far pointers.

Las funciones que permiten direccionar datos mediante far pointers son diferentes alas tradicionales. En el directorio Samples\Rabbit4000\FAR encontraremosexcelentes ejemplos.

125

Page 144: El Camino Del Conejo
Page 145: El Camino Del Conejo

Configuración en campo

Introducción

A través de los siglos, developers e ingenieros han intentado combatir a un enemigoimplacable: el usuario final. Sin importar cuánto se esmeraban los creadores de unequipo en ajustar cada uno de sus parámetros de operación, el usuario final siempre

querría una aplicación diferente, particularmente alguna que no funcionara con laconfiguración standard. Desolados, ingenieros y developers, intentando satisfacer aeste devastador tirano, crearon toda clase de parámetros configurables, quepermitieran al soberano modificar la operación a su antojo. Este vano intento nologró otra cosa que crear un monstruo aún más devastador e implacable: laconfiguración. Olvidados ya de su enemigo anterior, se debaten ahora entre la vida y

la muerte, intentando resolver qué guardar, dónde guardarlo, cuándo guardarlo,cómo cambiarlo, si volver a una configuración por defecto, etc...

Cada developer tiene sus trucos, y cada micro tiene sus características, quefavorecen uno u otro enfoque. Si bien muchas veces es posible poner una EEPROMexterna, o en el caso de Rabbit utilizar una pila de respaldo para la RAM, estas

alternativas tienen sus bemoles. Una EEPROM externa requiere hardware ajeno almódulo, y puede no aplicar en algunos desarrollos, además de ser un costo adicionalque, según veremos, no es necesario. En cuanto a la pila de respaldo, no sólo puedefallar, sino que si por algún motivo es necesario retirar el módulo de su zócalo, pesea toda nuestra buena voluntad, la RAM dejará de estar alimentada y perderá susdatos. Como alternativa, Rabbit y Dynamic C en particular, tienen un BIOS, el cual

soporta un sistema de identificación que utiliza el sector más alto de la flash paraalmacenar información. Junto a esta información, es posible almacenar datos en laflash, como por ejemplo constantes de calibración y, por qué no, la configuración delsistema. Las palabras mágicas son IDblock y Userblock. Si bien analizamos en parteeste tema en el libro introductorio, repetimos un fragmento aquí, por claridad.

Repaso

IDblock

Como dijéramos, el BIOS soporta un sistema de identificación que utiliza el sectormás alto de la flash para almacenar información; esta información identifica ycaracteriza al hardware en cuestión, e incluye por supuesto a los módulos Rabbit y

127

Page 146: El Camino Del Conejo

Configuración en campo

placas de Z-World. El developer puede utilizarlo para que identifique su propiohardware si así lo desea. Aquí se guarda además, la dirección de hardware Ethernet,mejor conocida como MAC address.

Es de destacar que la integridad de este sector es lo que hace que Dynamic Creconozca al módulo al momento de compilar nuestro software, por lo que siaccidentalmente se lo modifica, puede que Dynamic C no reconozca al módulo, sinpoder saber qué hardware presenta (por ejemplo, si tiene o no Ethernet y quécontrolador), con lo cual no podrá compilar correctamente el código. En este caso,existe una utilidad que puede regenerar el IDblock en base a macros que le proveenla información standard de cada módulo. El único dato faltante es la MAC, la cual seobtiene de la etiqueta del mismo.

Para leer el IDblock, o más correctamente el SystemID block, Dynamic C proveeuna función:

_readIDBlock(bitmap) /* bits 0-3 : cuadrante */

El “cuadrante” es un número de 4 bits que identifica la conexión, es decir, losbloques en los que el chip de flash responde. Por ejemplo, si ocupa los dos bloquesinferiores (512K), será 0x03.

Para escribir el IDblock, es necesario obtener un pequeño programa especial de lapágina de Z-World. Esto es así debido a que la función WriteFlash() no permiteescribir el IDblock, y esto se debe a que el usuario normal no debería tener razónalguna para escribir el IDblock; sin embargo, quien desarrolla su propio hardware,estará más que interesado en hacerlo.

User block

El User Block es un área en la parte alta de la flash, debajo del IDblock (puedeestar en el mismo sector), reservada para que el usuario pueda almacenar constantesde calibración, configuraciones, lo que se le ocurra que necesite guardar y que debasoportar una pérdida total de alimentación. El tamaño del User Block puededeterminarse mediante la función:

GetIDBlockSize();

que devuelve el tamaño en unidades de 4096 bytes, y que como dice en la mismabiblioteca de funciones que la define, tiene un nombre que confunde, dado que unoen realidad lo que quiere es el tamaño del User Block, más que el del IDblock.

/********************************************************************Return in HL the size of one buffered writable flash data block in1000h blocks

This function is badly named, it really has more to do with reportingthe User block size than the ID block size.********************************************************************/

Para leer y escribir el User Block, se dispone de dos funciones:

128

Page 147: El Camino Del Conejo

Introducción

/* lee num bytes del User Block desde offset hacia dest_addr */readUserBlock(dest_addr,offset,num);

/* graba num bytes en el User Block desde src_addr hacia offset */writeUserBlock(offset,src_addr,num): /* graba User Block */

Según qué módulo estemos empleando, es probable que la parte alta del UserBlockya tenga algunas constantes de calibración, por ejemplo para un conversor A/D, conlo cual es buena práctica dejar un espacio libre como para no escribir sobre estasconstantes. En varias notas de aplicación, verán que cuando debimos salvar laconfiguración de la touch screen, dejamos un espacio de 2Kbytes, de la siguienteforma:

#define CALIB_OFFSET (4096*GetIDBlockSize()-0x400)

Damos a continuación un ejemplo de como salvar y recuperar constantes decalibración en el User Block:

#define CALIB_OFFSET (4096*GetIDBlockSize()-0x400)

writeUserBlock(CALIB_OFFSET,src_addr,num_bytes);

readUserBlock(dest_addr,CALIB_OFFSET,num_bytes);

Guardando la configuración

Como para no interferir con las constantes de calibración de la touch screen, y dadoque el User Block es generosamente grande, dejamos otro espacio libre para podersalvar y recuperar nuestra configuración, definiendo:

#define CONFIG_OFFSET (4096*GetIDBlockSize()-0x800)

Como vemos, las funciones que operan sobre el User Block trabajan con bloquesde bytes. Por este motivo, es recomendable agrupar toda la configuración dentro deuna estructura, de modo de poder pasar la dirección de la estructura y su tamañocomo parámetros de llamada a estas funciones:

typedef struct {int release;char name[32];int data1;

} Configureiyon;

Configureiyon config;

// Recupera de flashreadUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));

// Graba en flashwriteUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));

129

Page 148: El Camino Del Conejo

Configuración en campo

De esta forma, la configuración se guarda en flash, y es copiada al inicio en RAM,en donde se la accede normalmente como elementos de la estructura:

printf("Release: %d\nName: %s\n",config.release,&(config.name));

A continuación, un sencillo ejemplo de como obtener la configuración de flash alinicializar, revertir a valores de fábrica ante determinada condición, simbolizada porla presión de un pulsador, y grabar una modificación de configuración, simbolizadapor la presión sobre otro pulsador:

#class auto

typedef struct {int release;char name[32];int data1;

} Configureiyon;

const static Configureiyon info_defaults = {1,"Esta es la primera versión",83};

#define APDEIT 2#define NIUTESTO "Esta es la segunda versión"

#define CONFIG_OFFSET (4096*GetIDBlockSize()-0x800)

void main(){Configureiyon config;

// Carga desde flashreadUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));printf("Release: %d\nName: %s\nData1: %d\n",config.release,

&(config.name),config.data1);

while (1) { costate {

if(!BitRdPortI(PBDR,2)) { // Carga valores de fábrica memcpy(&config,&info_defaults,sizeof(Configureiyon)); writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));

// Graba en flash printf("\n*** Valores por defecto ***\n"); waitfor(DelaySec(2));}if(!BitRdPortI(PBDR,3)) { // Actualiza configuración config.release=APDEIT;

strcpy(config.name,NIUTESTO); config.data1=92;

writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));// Graba en flash

printf("\n*** Nuevos valores ***\n"); waitfor(DelaySec(2));

} }}

}

La primera vez que corramos este ejemplo, en la porción de User Block que alojanuestra configuración, no hay datos coherentes con lo que esperamos, por lo que

130

Page 149: El Camino Del Conejo

Guardando la configuración

deberemos "forzar factory defaults" manteniendo presionado el pulsadorcorrespondiente al inicio del ejemplo. Los ports leídos corresponden a los pulsadoresS2 y S3 de un RCM2100.

Configuración vía web

Si nuestra aplicación tiene display y teclado o equivalente, la forma de configurar elequipo tal vez no difiera demasiado de cualquier otro sistema dedicado con unmicrocontrolador. Aun en ese caso, y particularmente si no los hay, es interesantepoder configurar el equipo de forma remota, mediante una página web. De estaforma, podemos jugar con imágenes, tipografías, y la distribución en pantalla,logrando una interfaz de usuario eficiente y profesional. No obstante, no debemosolvidar que estamos trabajando sobre un sistema dedicado, basado en un micro de 8-bits, y no es conveniente (ni a veces posible) disponer de centenares de kilobytespara alojar hermosas imágenes animadas que no aportan absolutamente nada a laamigabilidad de la interfaz de configuración.

Para poder configurar nuestra aplicación mediante una página web, deberemosincorporar el código básico del servidor web, y publicar las variables necesarias paraque puedan ser accedidas desde el mismo. Hemos visto un ejemplo en el capítulo"Proyecto" del libro introductorio, en el cual nuestro proyecto era configurado víaweb. Las cosas han cambiado un poco y mucha agua corrió bajo el puente desdeentonces. Dada la existencia de RabbitWeb, un poderoso lenguaje script dentro delservidor web, sin costo adicional, realizamos los ejemplos con él. Esta herramientase describe en más detalle en su capítulo correspondiente.

Seguridad

Poder configurar un equipo mediante una interfaz amigable, conectado desde lacomodidad del sillón del living con una PDA, laptop, o equivalente, mientras con laotra mano acariciamos al perro, es muy seductor. Inmediatamente, nuestro paranoideinterior suma dos más dos y se da cuenta que de la misma forma que ingreso yo,podría ingresar cualquier otro, que en el mejor caso puede que no esté capacitadopara operar el equipo, y en el peor de los casos puede estar lo suficientemente malintencionado como para hacer que falle el proceso que el equipo controla.Si la red es cerrada, los usuarios confiables, y las mariposas pululan alegres bajo el

sol de primavera, probablemente podamos dejar las páginas de configuraciónabiertas. En gran cantidad de aplicaciones, tal vez esto no sea conveniente. Lasolución es entonces proteger el acceso a las páginas de configuración mediantealgún método de autenticación, que nos permita que sólo aquellos usuariosautorizados puedan acceder a dichas páginas.

131

Page 150: El Camino Del Conejo

Configuración en campo

La forma de agregar autenticación a una página web, se describe en la secciónhomónima del capítulo sobre networking con Rabbit. Como referencia rápida,agregamos los cambios necesarios:� Definimos el grupo de usuarios autorizados a alterar la configuración, el cual

llamamos muy originalmente ADMIN_GROUP:� En el directorio del server, marcaremos aquellos archivos protegidos, los cuales

resaltamos para esta ocasión:

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),SSPEC_RESOURCE_P_XMEMFILE("/setup.shtml",setup_html,"mi equipo", ADMIN_GROUP,0,SERVER_HTTP,SERVER_AUTH_BASIC | SERVER_AUTH_DIGEST),

SSPEC_RESOURCETABLE_END

� Finalmente, agregamos en el programa principal las funciones necesarias paraingresar un nuevo usuario, llamado (vaya sorpresa) admin, cuyo password seráistrador. Por claridad, reproducimos el programa principal y resaltamos losagregados:

void main(){int uid;

// Carga desde flashif(!BitRdPortI(PBDR,2)) { // valores por defecto

memcpy(&config,&info_defaults,sizeof(Configureiyon));// graba en flashwriteUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));

}else

readUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));

// Crea user IDuid = sauth_adduser("admin", "istrador", SERVER_HTTP); // creasauth_setusermask(uid, ADMIN_GROUP, NULL); // agrega al grupo

sock_init();http_init();tcp_reserveport(80);

while (1) {http_handler();

// aplicación}

}

En este ejemplo asumimos que el usuario se crea sin problemas, dado que es simpley sencillo. Si esto es parte de un programa más complejo, y se agregan usuarios deforma dinámica, tal vez sea conveniente chequear el valor devuelto por la funciónque crea el usuario, el cual alojamos en uid, antes de proseguir con la tarea deagregarlo al grupo.

132

Page 151: El Camino Del Conejo

Configuración vía web

Puede ocurrir, que tengamos algún grupo de usuarios a los cuales queremos darlespermiso para ver la configuración, pero no para cambiarla. Éste podría ser un grupode asistentes o de monitores de red, que deben observar y reportar determinadascondiciones, pero no corresponde, por lo que fuere, que la alteren. En este caso,definimos el grupo de usuarios autorizados a observar pero no alterar laconfiguración, el cual llamamos MON_GROUP: Si bien ambos grupos de usuariostendrán acceso a la página de configuración, resolveremos el tipo de accesomediante los permisos de las variables de configuración.

Finalmente, incluimos en el programa principal las funciones necesarias paraagregar el usuario mon, cuyo password será itor. Por claridad, reproducimos elprograma anterior y resaltamos los agregados:

void main(){int uid;

// Carga desde flashif(!BitRdPortI(PBDR,2)) { // valores por defecto

memcpy(&config,&info_defaults,sizeof(Configureiyon));// graba en flashwriteUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));

}else

readUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));

// Crea user IDif((uid = sauth_adduser("admin", "istrador", SERVER_HTTP))>=0){

sauth_setusermask(uid, ADMIN_GROUP, NULL);}// Crea user IDif((uid = sauth_adduser("mon", "itor", SERVER_HTTP))>=0){

sauth_setusermask(uid, MON_GROUP, NULL);}

sock_init();http_init();tcp_reserveport(80);

while (1) {http_handler();

// aplicación}

}

Para que nuestro programa utilice RabbitWeb, deberemos seguir una serie de pasosmuy simples:

� En primer lugar, indicamos que estamos utilizando RabbitWeb:

#define USE_RABBITWEB 1

lo cual debe hacerse antes de incluir la biblioteca de funciones del web server.� Luego, definimos los dos grupos de usuarios:

133

Page 152: El Camino Del Conejo

Configuración en campo

#web_groups ADMIN_GROUP#web_groups MON_GROUP

� A continuación, registramos las variables a utilizar, las cuales ya deben estardeclaradas y deben ser globales. Por comodidad, registramos la totalidad de laestructura, pero podríamos registrar cada uno de sus elementos de formaindividual:

#web config auth=basic,digest groups=ADMIN_GROUP(rw),MON_GROUP(ro)

Con la definición anterior registramos la variable config (una estructura de tipoConfigureiyon), con permiso de lectura y escritura para el grupo ADMIN_GROUP,permiso de lectura solamente para el grupo MON_GROUP, y la autenticación delusuario podrá hacerse mediante los métodos basic o digest.

Cuando una variable sea actualizada (cambio de configuración), RabbitWeb lodetectará y podrá opcionalmente ejecutar una función. Definimos esa función y laregistramos:

void saveconfig(void){

writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));printf("Saved to flash\n");

}

#web_update config saveconfig

Otra ventaja que tiene el uso de RabbitWeb, es que verifica si realmente hubocambios, es decir; si lo que se envía es igual a lo que existía, no llama a esta funciónporque no hay cambios.

Los tipos MIME los definimos como siempre, pero esta vez incluiremos laextensión zhtml para el script RabbitWeb, y le asignamos el correspondiente handler:

SSPEC_MIMETABLE_STARTSSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),SSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),

SSPEC_MIMETABLE_END

El directorio del server lo definimos como siempre, protegiendo la página deconfiguración:

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),SSPEC_RESOURCE_P_XMEMFILE("/setup.zhtml",setup_html,"mi equipo",

ADMIN_GROUP|MON_GROUP,0,SERVER_HTTP,SERVER_AUTH_BASIC | SERVER_AUTH_DIGEST),SSPEC_RESOURCE_XMEMFILE("/rabbit1.gif", rabbit1_gif),

SSPEC_RESOURCETABLE_END

Los usuarios se generan en el programa principal, como hemos visto.Finalmente, el resto de la tarea la desarrollamos sobre la página ZHTML, la cual

incluye un script que nos permite que se ejecuten ciertas cosas y se modifique el

134

Page 153: El Camino Del Conejo

Configuración vía web

código entregado, de una forma algo más simple y más potente que si utilizáramosSSI. De esta manera, podemos utilizar la misma página tanto para configurar comopara mostrar resultados y errores. En nuestro caso, dado que Rabbit Web revisa sihay o no cambios, y hasta enclava los valores numéricos al máximo del tipoempleado, no tendremos errores en esta página. En un caso real, en el que los valoresa configurar tienen un rango de validez, es posible mostrar una interfaz muyamigable con mucho detalle. A continuación, veremos el script ZHTML, resaltando

las partes importantes:

<html><head><title>Config</title></head><body bgcolor="#FFFFFF" link="#009966" vlink="#FFCC00" alink="#006666"topmargin="0" leftmargin="0" marginwidth="0" marginheight="0"><H1>Configuraci&oacute;n</H1><form ACTION="setup.zhtml" METHOD="POST"><?z if(updating()) { ?> <?z if(!error()) { ?>

<hr><H2><FONT COLOR="#0000FF">Configuración actualizada</FONT></H2><hr> <?z } ?><?z } ?><table><tr><td>Revisi&oacute;n<td><input TYPE="TEXT" NAME="config.release" SIZE=5 VALUE=<?z print$config.release) ?>><tr><td>Descripci&oacute;n<td><input TYPE="TEXT" NAME="config.name" SIZE=64 VALUE="<?z print($config.name)?>"><tr><td>Dato<td><input TYPE="TEXT" NAME="config.data1" SIZE=5 VALUE=<?z print($config.data1)?>></table><?z if(auth($config.release,"rw")) { ?>

<input TYPE="SUBMIT" VALUE="Cambiar"></form><?z } ?></body></html>

Como vemos, es mayormente similar a un SHTML, el script ZHTML estáembebido entre <?z ?>, y con un lenguaje muy simple es posible lograr páginasdinámicas muy complejas.� Si estamos actualizando la página, es decir, el pedido por el cual el server en el

Rabbit está mostrando esta página corresponde a un POST ocasionado por lapresión del botón Cambiar, y si no hay ningún error, informamos que laconfiguración fue actualizada.

� En cada campo a configurar, utilizamos el nombre del elemento dentro de laestructura, como haríamos en cualquier programa C.

� Si el usuario que está viendo la página no tiene permiso de escritura, nomostramos el botón Cambiar. Si éste es ingenioso y de todos modos se lasarregla para intentar hacer una escritura (generando manualmente un POSTdesde otra página), o si simplemente le dejáramos el botón a la vista, recibiría unmensaje 403 Forbidden como respuesta, dado que no tiene permiso para escribiresas variables. Ocultar el botón hace una interfaz más amigable.

135

Page 154: El Camino Del Conejo

Configuración en campo

A continuación, veremos el listado completo del programa:

#class auto

#define USE_RABBITWEB 1

#define CONF_TXTSZ 64

typedef struct {int release;char name[CONF_TXTSZ];int data1;

} Configureiyon;

const static Configureiyon info_defaults = {1,"Esta es la primera versi&oacute;n",83};

#web_groups ADMIN_GROUP#web_groups MON_GROUP

#define CONFIG_OFFSET (4096*GetIDBlockSize()-0x800)

Configureiyon config;

#web config auth=basic,digest groups=ADMIN_GROUP(rw),MON_GROUP(ro)

void saveconfig(void){

writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));printf("Saved to flash\n");

}

#web_update config saveconfig

#define TCPCONFIG 0#define USE_ETHERNET 1

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#use "dcrtcp.lib"#use "http.lib"

#ximport "index.html" index_html#ximport "rabbit1.gif" rabbit1_gif#ximport "setup.zhtml" setup_html

SSPEC_MIMETABLE_STARTSSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),SSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),

SSPEC_MIMETABLE_END

// directorio del server, funciones y variables de SSISSPEC_RESOURCETABLE_START

SSPEC_RESOURCE_XMEMFILE("/", index_html),

136

Page 155: El Camino Del Conejo

Configuración vía web

SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),SSPEC_RESOURCE_P_XMEMFILE("/setup.zhtml",setup_html,"mi equipo",

ADMIN_GROUP|MON_GROUP,0,SERVER_HTTP,SERVER_AUTH_BASIC | SERVER_AUTH_DIGEST),SSPEC_RESOURCE_XMEMFILE("/rabbit1.gif", rabbit1_gif),

SSPEC_RESOURCETABLE_END

void main(){int uid;

// Load from flashif(!BitRdPortI(PBDR,2)) { // Reset to defaults

memcpy(&config,&info_defaults,sizeof(Configureiyon));saveconfig(); // save to flash

}else

readUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));

if((uid = sauth_adduser("admin", "istrador", SERVER_HTTP))>=0){sauth_setusermask(uid, ADMIN_GROUP, NULL);

}if((uid = sauth_adduser("mon", "itor", SERVER_HTTP))>=0){

sauth_setusermask(uid, MON_GROUP, NULL);}

sock_init();http_init();tcp_reserveport(80);while (1) {

http_handler(); // aplicación}

}

Revisar los rangos de validez de los valores que se ingresan es una tarea que nodifiere mucho de la normal programación de cualquier tipo de sistema dedicado. Sinembargo, con RabbitWeb, el chequeo lo hace el servidor, y nuestra tarea se reduce adefinirle los rangos de validez, pudiendo mostrar de forma amigable la existencia deun error, indicando al usuario por donde buscar, sin complicar demasiado nuestravida.

Supongamos que las variables enteras de nuestra configuración tienen un rango devalidez:

0�release�10�9999�data1�9999

Esto en RabbitWeb puede hacerse de varias maneras; siguiendo con la idea anterior,definiremos los rangos al registrar la estructura, de la siguiente forma:

#web config (($config.release > 0)&&($config.release < 10))#web config (($config.data1 <= 9999)?1:WEB_ERROR("muy alto"))#web config (($config.data1 >= -9999)?1:WEB_ERROR("muy bajo"))

El primer caso es más que directo, es fácil ver que se verifica que config.release seamayor que cero y menor que diez. El segundo es algo más complicado, pero lo que

137

Page 156: El Camino Del Conejo

Configuración en campo

se hace es esencialmente lo mismo, la diferencia es que cuando el valor que seintenta introducir (indicado por el signo $ que lo antecede) está fuera de rango,podemos definir un texto que puede luego ser invocado en el script para indicar lanaturaleza del error:

<?z if(error($config.data1)) { ?><FONT COLOR="#FF0000">

<?z print(error($config.data1)) ?></FONT>

<?z } ?>

A continuación, el listado completo del ZHTML con los scripts resaltados, y unaimagen de la pantalla. Lamentablemente, el rojo no será rojo para ustedes, peroespero que esto no sea inconveniente.

<html><head><title>Config</title></head><body bgcolor="#FFFFFF" link="#009966" vlink="#FFCC00" alink="#006666"topmargin="0" leftmargin="0" marginwidth="0" marginheight="0"><H1>Configuraci&oacute;n</H1><form ACTION="setup.zhtml" METHOD="POST"><?z if(updating()) { ?> <?z if(!error()) { ?>

<hr><H2><FONT COLOR="#0000FF">Configuración actualizada</FONT></H2><hr> <?z } ?> <?z if(error()) { ?>

<hr><H3><FONT COLOR="#FF0000">Revise los errores indicados enrojo</FONT></H3><hr> <?z } ?><?z } ?><table><tr><td>

<?z if(error($config.release)) { ?><FONT COLOR="#FF0000">

<?z } ?>Revisi&oacute;n

<?z if(error($config.release)) { ?></FONT>

<?z } ?><td><input TYPE="TEXT" NAME="config.release" SIZE=5 VALUE=<?zprint($config.release) ?>>

<tr><td>Descripci&oacute;n<td><input TYPE="TEXT" NAME="config.name" SIZE=64 VALUE="<?z print($config.name)?>">

<tr><td><?z if(error($config.data1)) { ?>

<FONT COLOR="#FF0000"><?z } ?>

Dato<?z if(error($config.data1)) { ?>

</FONT><?z } ?>

<td><input TYPE="TEXT" NAME="config.data1" SIZE=5 VALUE=<?z print($config.data1)?>>

<?z if(error($config.data1)) { ?><FONT COLOR="#FF0000">

<?z print(error($config.data1)) ?>

138

Page 157: El Camino Del Conejo

Configuración vía web

</FONT><?z } ?>

</table><?z if(auth($config.release,"rw")) { ?>

<input TYPE="SUBMIT" VALUE="Cambiar"></form><?z } ?></body></html>

Simultaneidad

Cuando se trabaja con un display, la situación de configuración es relativamentesimple: el usuario ingresa a un determinado menú, del cual saldrá confirmando,cancelando, o luego de un timeout; por lo que es posible determinar si se estáconfigurando el equipo e impedir modificaciones desde otra interfaz.

En protocolos de control y configuración como por ejemplo Modbus, la situacióntambién es relativamente simple, dado que la alteración de un parámetro se realizamediante un comando de escritura, por lo que si bien puede haber varios cambiosseguidos, el tiempo de configuración queda acotado.

Con una interfaz web, la cosa no es tan simple. En primer lugar, alguien puedeacceder a la página de configuración, y dejar su browser encendido por horas, días,semanas, meses1... Si tenemos sólo un administrador, puede no haber demasiado

1 Bueno, en realidad esto sólo sería posible si el sistema en donde corre el browser no se cuelgadurante todo ese tiempo, supongamos por un momento que se usa un sistema operativo quefunciona.

139

Page 158: El Camino Del Conejo

Configuración en campo

inconveniente, pero ¿qué sucede si tenemos varios, o lo que es peor aún, en varioslugares diferentes? Es posible que dos o más administradores intenten acceder dentrode una misma ventana de tiempo, realizando modificaciones diferentes a losparámetros.Detectar el acceso e identificarlo por la dirección IP es una posibilidad, pero si los

administradores están en la misma conexión a Internet, probablemente tengan lamisma IP pública desde nuestro punto de vista. Otra posibilidad es utilizar cookies,aunque muchos usuarios encuentran bastante desagradable esta opción, y más aún elhecho de manejar ese código manualmente. Además, podrían existir problemasdebido a usuarios que no desean habilitar cookies en su browser, o tal vez conalgunos proxies.

Un recurso para minimizar este problema, es utilizar una variable a modo de tag,oculta, representando la "versión" o "edad" de la información entregada. Lavalidación de los datos enviados incluye el revisar que ambas versiones de estavariable (navegador y equipo) sean correspondientes. De esta manera, de producirseuna actualización (por otro usuario) mientras yo me estoy decidiendo, la secuencia serompe y mis datos son descartados. Veámoslo en detalle:

La secuencia de operaciones en un caso normal es la siguente:

1. El navegador recibe el valor de tag al abrir la página, digamos que es 3.2. Al recibir la actualización, el Rabbit chequea si el valor enviado de tag es igual

al suyo (3), si es así acepta los cambios e incrementa tag (4).

Digamos que yo abro la página de configuración, recibo un llamado telefónico,salgo a apagar un incendio, y luego vuelvo y recuerdo que dejé la configuración porla mitad, e intento ingresar los nuevos valores, pero uno de mis serviciales y siemprebondadosos compañeros de tareas se toma la molestia de modificar otro de losparámetros; sucederá lo siguiente:

1. Mi navegador recibió el valor de tag (3)2. Yo me fui.3. El navegador de mi compañero recibió también el valor de tag (3)4. Mi compañero envió sus cambios5. El Rabbit validó, aceptó los cambios, e incrementó a tag. En el Rabbit, tag es

ahora igual a 46. Yo regreso e intento enviar mi vieja configuración (pisoteando los cambios de mi

compañero)7. Al chequear si el valor de tag que mi navegador envía (3) es igual al valor

interno de tag (4), el Rabbit descubre con asombro que 3 no es igual a 4, yrechaza mis cambios, obligándome a refrescar mi pantalla y observar qué es loque pasó en mi ausencia.

140

Page 159: El Camino Del Conejo

Configuración vía web

La idea original (algo más elaborada) se describe en el manual de RabbitWeb.Como en este caso ya usamos #web_update porque salvamos la configuración enflash, podemos aplicar esta idea. De lo contrario, en un sistema que solamenteactualice variables en tiempo de ejecución, se deberá utilizar el truco descripto en elmanual de RabbitWeb, dado que es necesario forzar una modificación sobre lavariable usada como tag para que se chequee y se dispare la ejecución de la funciónde incremento, definida en #web_update. Para implementar esta solución, realizamos los siguientes pasos:

� En el programa principal:� definimos la variable y la registramos:

int tag;#web tag ($tag == tag)

� luego agregamos el incremento en la función que salva laconfiguración:

tag++;

� En el ZHTML:� agregamos la variable oculta:

<INPUT TYPE="hidden" NAME="tag" VALUE="<?z print($tag) ?>">

A continuación, el listado completo, con los cambios resaltados:

#class auto

#define USE_RABBITWEB 1

#define CONF_TXTSZ 64

typedef struct {int release;char name[CONF_TXTSZ];int data1;

} Configureiyon;

const static Configureiyon info_defaults = {1,"Esta es la primera versi&oacute;n",83};

#web_groups ADMIN_GROUP#web_groups MON_GROUP

#define CONFIG_OFFSET (4096*GetIDBlockSize()-0x800)

Configureiyon config;

#web config auth=basic,digest groups=ADMIN_GROUP(rw),MON_GROUP(ro)#web config (($config.release > 0)&&($config.release < 10))#web config (($config.data1 <= 9999)?1:WEB_ERROR("muy alto"))#web config (($config.data1 >= -9999)?1:WEB_ERROR("muy bajo"))

int tag;

141

Page 160: El Camino Del Conejo

Configuración en campo

#web tag ($tag == tag)

void saveconfig(void){

writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));printf("Saved to flash\n");tag++;

}

#web_update config saveconfig

#define TCPCONFIG 0#define USE_ETHERNET 1

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#use "dcrtcp.lib"#use "http.lib"

#ximport "index.html" index_html#ximport "rabbit1.gif" rabbit1_gif#ximport "setup.zhtml" setup_html

SSPEC_MIMETABLE_STARTSSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),SSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),

SSPEC_MIMETABLE_END

// directorio del server, funciones y variables de SSISSPEC_RESOURCETABLE_START

SSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),SSPEC_RESOURCE_P_XMEMFILE("/setup.zhtml",setup_html,"mi equipo",

ADMIN_GROUP|MON_GROUP,0,SERVER_HTTP,SERVER_AUTH_BASIC | SERVER_AUTH_DIGEST),SSPEC_RESOURCE_XMEMFILE("/rabbit1.gif", rabbit1_gif),

SSPEC_RESOURCETABLE_END

void main(){int uid;

// Load from flashif(!BitRdPortI(PBDR,2)) { // Reset to defaults

memcpy(&config,&info_defaults,sizeof(Configureiyon));saveconfig(); // save to flash

}else

readUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));

if((uid = sauth_adduser("admin", "istrador", SERVER_HTTP))>=0){sauth_setusermask(uid, ADMIN_GROUP, NULL);

}

142

Page 161: El Camino Del Conejo

Configuración vía web

if((uid = sauth_adduser("mon", "itor", SERVER_HTTP))>=0){sauth_setusermask(uid, MON_GROUP, NULL);

}

sock_init();http_init();tcp_reserveport(80);while (1) {

http_handler(); // aplicación}

}

El ZHTML:

<html><head><title>Red</title></head><body bgcolor="#FFFFFF" link="#009966" vlink="#FFCC00" alink="#006666"topmargin="0" leftmargin="0" marginwidth="0" marginheight="0"><H1>Configuraci&oacute;n de par&aacute;metros de red</H1><form ACTION="setup.zhtml" METHOD="POST"><?z if(updating()) { ?> <?z if(!error()) { ?>

<hr><H2><FONT COLOR="#0000FF">Configuración actualizada</FONT></H2><hr> <?z } ?> <?z if(error()) { ?>

<?z if(error($tag)) { ?> <hr><H4><FONT COLOR="#FF0000">Debido a cambios realizados por otro

usuario, recargue la p&aacute;gina y revise sus acciones</FONT></H4><hr><?z } ?><?z if(!error($tag)) { ?> <hr><H3><FONT COLOR="#FF0000">Revise los errores indicados en

rojo</FONT></H3><hr><?z } ?>

<?z } ?><?z } ?><table><tr><td>

<?z if(error($config.release)) { ?><FONT COLOR="#FF0000">

<?z } ?>Revisi&oacute;n

<?z if(error($config.release)) { ?></FONT>

<?z } ?><td><input TYPE="TEXT" NAME="config.release" SIZE=5 VALUE=<?z print($config.release) ?>>

<tr><td>Descripci&oacute;n<td><input TYPE="TEXT" NAME="config.name" SIZE=64 VALUE="<?z print($config.name) ?>">

<tr><td><?z if(error($config.data1)) { ?>

<FONT COLOR="#FF0000"><?z } ?>

Dato<?z if(error($config.data1)) { ?>

</FONT><?z } ?>

143

Page 162: El Camino Del Conejo

Configuración en campo

<td><input TYPE="TEXT" NAME="config.data1" SIZE=5 VALUE=<?z print($config.data1) ?>>

<?z if(error($config.data1)) { ?><FONT COLOR="#FF0000">

<?z print(error($config.data1)) ?></FONT>

<?z } ?></table><?z if(auth($config.release,"rw")) { ?>

<INPUT TYPE="hidden" NAME="tag" VALUE="<?z print($tag) ?>"><input TYPE="SUBMIT" VALUE="Cambiar"></form>

<?z } ?></body></html>

No nos hemos esmerado demasiado en el script, a fin de mantener las diferencias almínimo; pero complicándolo un poco más podríamos evitar que el usuario debarefrescar la página manualmente, agregando alguna redirección, o reemplazando$variable por @variable, que utiliza el valor que tiene la variable, en vez del que sepretende introducir. Si utilizáramos siempre @variable, al haber un error noveríamos lo que enviamos (el valor que queríamos introducir) sino el valor corriente(el configurado), lo que puede ser confuso. Deberíamos entonces modificar el scriptpara que decida qué valor mostrar en función de si se trata de un error en los datos, ode un tag incorrecto:

<td><input TYPE="TEXT" NAME="config.release" SIZE=5 VALUE=<?z if(!error($tag)) { ?>

<?z print($config.release) ?><?z } ?><?z if(error($tag)) { ?>

<?z print(@config.release) ?><?z } ?>>

Finalmente, la última versión del ZHTML:

<html><head><title>Red</title></head><body bgcolor="#FFFFFF" link="#009966" vlink="#FFCC00" alink="#006666"topmargin="0" leftmargin="0" marginwidth="0" marginheight="0"><H1>Configuraci&oacute;n de par&aacute;metros de red</H1><form ACTION="setup.zhtml" METHOD="POST"><?z if(updating()) { ?> <?z if(!error()) { ?>

<hr><H2><FONT COLOR="#0000FF">Configuración actualizada</FONT></H2><hr> <?z } ?> <?z if(error()) { ?>

<?z if(error($tag)) { ?> <hr><H4><FONT COLOR="#FF0000">Debido a cambios realizados por otro

usuario, revise sus acciones</FONT></H4><hr><?z } ?><?z if(!error($tag)) { ?> <hr><H3><FONT COLOR="#FF0000">Revise los errores indicados en

rojo</FONT></H3><hr><?z } ?>

<?z } ?><?z } ?><table>

144

Page 163: El Camino Del Conejo

Configuración vía web

<tr><td><?z if(error($config.release)) { ?>

<FONT COLOR="#FF0000"><?z } ?>

Revisi&oacute;n<?z if(error($config.release)) { ?>

</FONT><?z } ?>

<td><input TYPE="TEXT" NAME="config.release" SIZE=5 VALUE=<?z if(error($tag)) { ?>

<?z print(@config.release) ?><?z } ?><?z if(!error($tag)) { ?>

<?z print($config.release) ?><?z } ?>

>

<tr><td>Descripci&oacute;n<td><input TYPE="TEXT" NAME="config.name" SIZE=64 VALUE="

<?z if(error($tag)) { ?><?z print(@config.name) ?>

<?z } ?><?z if(!error($tag)) { ?>

<?z print($config.name) ?><?z } ?>

">

<tr><td><?z if(error($config.data1)) { ?>

<FONT COLOR="#FF0000"><?z } ?>

Dato<?z if(error($config.data1)) { ?>

</FONT><?z } ?>

<td><input TYPE="TEXT" NAME="config.data1" SIZE=5 VALUE=<?z if(error($tag)) { ?>

<?z print(@config.data1) ?><?z } ?><?z if(!error($tag)) { ?>

<?z print($config.data1) ?><?z } ?>

><?z if(error($config.data1)) { ?>

<FONT COLOR="#FF0000"> <?z print(error($config.data1)) ?>

</FONT><?z } ?>

</table><?z if(auth($config.release,"rw")) { ?>

<INPUT TYPE="hidden" NAME="tag" VALUE="<?z print($tag) ?>"><input TYPE="SUBMIT" VALUE="Cambiar"></form>

<?z } ?></body></html>

Interfaces complejas

Si bien sabemos que con simples sentencias HTML es posible crear selectoresdesplegables, checkboxes y botones de selección, no nos hemos molestado en

145

Page 164: El Camino Del Conejo

Configuración en campo

utilizarlos por dos simples razones. La primera es que si nada más ponemos elHTML estático, la página desplegada no corresponde al verdadero valor de lasvariables en ese momento, sino a los valores por defecto puestos en la página alsalvarla en flash. Si bien esto puede ser conveniente para un formulario que secompleta, o cuando se requiere que el usuario deba pasar por todos los pasos deconfiguración; no responde al concepto de interfaz amigable cuando se intenta que elusuario vea a primera vista el estado actual y modifique sólo lo necesario. Lasegunda razón es que para hacer esto de forma dinámica, la página debería generarsede esta forma.

Con RabbitWeb, es muy simple generar una página dinámica, dado quedisponemos de un lenguaje de scripts que nos permite resolver situaciones de maneraelegante, siempre pensando que estamos trabajando sobre un sistema dedicado conun procesador de 8-bits, cuya tarea principal es resolver un problema de campo.

Vamos entonces a agregar tres nuevas variables a nuestra configuración, una paracada tipo de selector a utilizar. En primer lugar, tendremos una lista de "perfiles deoperación", luego tendremos una opción que puede habilitarse o no, y por último unadecisión con valores mutuamente excluyentes:

typedef struct {int release;char name[CONF_TXTSZ];int data1;int opprof;int turbo;int selfdestruct;

} Configureiyon;

No es necesario registrar las variables en RabbitWeb pues ya registramos laestructura completa, pero sí es necesario definir los valores de selección paraalgunos de los tipos empleados:

#web config.opprof select("Auto"=0,"Lava","Enjuaga","Masajea","Hace panqueques")#web config.selfdestruct select("NO!" = 0, "Bueno", "Tal vez")

Los selectores y botones incluyen "rangos de validez", es decir, solamente aceptanlos valores que han sido definidos en el programa.

El checkbox es esencialmente un "sí/no", y RabbitWeb acepta cualquier valor. Serecomienda que nuestro programa no dependa del valor 1 para considerarlohabilitado, sino que considere cualquier valor diferente de 0 como "sí"; de todosmodos puede definirse un rango de validez si se desea.Finalmente hacemos todo el resto del trabajo dentro del ZHTML; el selector de

modo de operación:

<SELECT NAME="config.opprof"><?z print_select($config.opprof) ?>

</SELECT><br>

la opción es un checkbox:

146

Page 165: El Camino Del Conejo

Configuración vía web

<INPUT TYPE="hidden" name="config.turbo" VALUE="0" ><INPUT TYPE="checkbox"

<?z if($config.turbo!=0) { ?>CHECKED

<?z } ?>NAME="config.turbo" VALUE="1" >

y la decisión es mediante radio buttons:

<?z for ($A = 0; $A < count($config.selfdestruct); $A++){ ?><INPUT TYPE="radio" NAME="config.selfdestruct" OPTION<?z if (selected($config.selfdestruct, $A) ) { ?>

CHECKED<?z } ?>VALUE="<?z print_opt($config.selfdestruct, $A) ?>"><?z print_opt($config.selfdestruct, $A) ?>

<?z } ?>

Nuestro trabajo, entonces, se vería de la siguiente manera:

A continuación, el listado completo del ZHTML, incluyendo soporte para cambiospor otro usuario mientras uno medita sobre la pantalla. El listado del programaprincipal es similar al ejemplo anterior, con las diferencias marcadas, por lo que nolo incluimos.

<html><head><title>Config</title></head><body bgcolor="#FFFFFF" link="#009966" vlink="#FFCC00" alink="#006666"topmargin="0" leftmargin="0" marginwidth="0" marginheight="0"><H1>Configuraci&oacute;n</H1><form ACTION="setup.zhtml" METHOD="POST"><?z if(updating()) { ?> <?z if(!error()) { ?>

<hr><H2><FONT COLOR="#0000FF">Configuración actualizada</FONT></H2><hr>

147

Page 166: El Camino Del Conejo

Configuración en campo

<?z } ?> <?z if(error()) { ?>

<?z if(error($tag)) { ?><hr><H4><FONT COLOR="#FF0000">Debido a cambios realizados por

otro usuario, revise sus acciones</FONT></H4><hr><?z } ?><?z if(!error($tag)) { ?>

<hr><H3><FONT COLOR="#FF0000">Revise los errores indicados enrojo</FONT></H3><hr>

<?z } ?> <?z } ?><?z } ?><table><tr><td>

<?z if(error($config.release)) { ?><FONT COLOR="#FF0000">

<?z } ?>Revisi&oacute;n

<?z if(error($config.release)) { ?></FONT>

<?z } ?><td><input TYPE="TEXT" NAME="config.release" SIZE=5 VALUE=

<?z if(error($tag)) { ?><?z print(@config.release) ?>

<?z } ?><?z if(!error($tag)) { ?>

<?z print($config.release) ?><?z } ?>

>

<tr><td>Descripci&oacute;n<td><input TYPE="TEXT" NAME="config.name" SIZE=64 VALUE="

<?z if(error($tag)) { ?><?z print(@config.name) ?>

<?z } ?><?z if(!error($tag)) { ?>

<?z print($config.name) ?><?z } ?>

">

<tr><td><?z if(error($config.data1)) { ?>

<FONT COLOR="#FF0000"><?z } ?>

Dato<?z if(error($config.data1)) { ?>

</FONT><?z } ?>

<td><input TYPE="TEXT" NAME="config.data1" SIZE=5 VALUE=<?z if(error($tag)) { ?>

<?z print(@config.data1) ?><?z } ?><?z if(!error($tag)) { ?>

<?z print($config.data1) ?><?z } ?>

><?z if(error($config.data1)) { ?>

<FONT COLOR="#FF0000"> <?z print(error($config.data1)) ?>

</FONT><?z } ?>

</table>

148

Page 167: El Camino Del Conejo

Configuración vía web

Perfil de operación:<SELECT NAME="config.opprof"><?z if(!error($tag)) { ?>

<?z print_select($config.opprof) ?><?z } ?><?z if(error($tag)) { ?>

<?z print_select(@config.opprof) ?><?z } ?></SELECT><br>

Turbo:<INPUT TYPE="hidden" name="config.turbo" VALUE="0" ><INPUT TYPE="checkbox"<?z if(!error($tag)) { ?>

<?z if($config.turbo!=0) { ?>CHECKED

<?z } ?><?z } ?><?z if(error($tag)) { ?>

<?z if(@config.turbo!=0) { ?>CHECKED

<?z } ?><?z } ?>NAME="config.turbo" VALUE="1" ><br>

Autodestrucci&oacute;n:<?z for ($A = 0; $A < count($config.selfdestruct); $A++){ ?>

<INPUT TYPE="radio" NAME="config.selfdestruct" OPTION<?z if(!error($tag)) { ?>

<?z if (selected($config.selfdestruct, $A) ) { ?>CHECKED

<?z } ?><?z } ?><?z if(error($tag)) { ?>

<?z if (selected(@config.selfdestruct, $A) ) { ?>CHECKED

<?z } ?><?z } ?>VALUE="<?z print_opt($config.selfdestruct, $A) ?>"><?z print_opt($config.selfdestruct, $A) ?>

<?z } ?><br>

<?z if(auth($config.release,"rw")) { ?><INPUT TYPE="hidden" NAME="tag" VALUE="<?z print($tag) ?>"><input TYPE="SUBMIT" VALUE="Cambiar"></form>

<?z } ?></body></html>

149

Page 168: El Camino Del Conejo
Page 169: El Camino Del Conejo

Conectividad

USB

Las conexiones serie han ido evolucionando, y USB parece ser la elegida de hoy.Casi todos los dispositivos medianamente portátiles que necesitan interconexióntienen un port USB.

Introducción a USB

Seguramente las cosas serían más fáciles si empleáramos la misma terminologíapara las mismas cosas, pero entonces no habría expertos ni gente que escribieralibros explicando algo que todo el mundo sabe pero no conoce por ese nombre...

La cuestión es que USB no difiere mucho de un sistema tradicional decomunicaciones en el que el host interroga periódicamente a los controladores, a lavieja usanza de SDLC1. La terminología es agradable al oído del programador de C+

+, pero el funcionamiento responde a los conceptos tradicionales de polling ycircuito virtual.

USB, como buen sistema polling, es esencialmente master-slave2. Esto significa

que existe un controlador principal, que se encarga de interrogar a los remotos yéstos solamente contestan cuando se les da permiso. De esta forma se evitan lascolisiones en un medio de acceso múltiple y es posible arbitrar el uso del ancho de

banda de acuerdo a la frecuencia con que se interroga a cada remoto.Al conectar un dispositivo al bus USB, el controlador enumera a este dispositivo,

es decir, le asigna una dirección y lee su configuración y características, con lo cuales posible tratarlo de la forma que necesita para operar correctamente. Dado que lasdirecciones son de 7-bits, y todo dispositivo (incluyendo los hubs) debe tener unadirección, el número máximo de dispositivos conectados al bus es de ciento

veintisiete.Físicamente, el enlace no requiere terminación eléctrica, pero la capacidad y

atenuación del cable limitan su longitud máxima a unos pocos metros. El tipo deconector empleado conecta primero los pines de alimentación (2) y luego los de

1 Protocolo desarrollado por IBM, en el cual se inspiraría HDLC, padre de los protocolos de nivel-2usados en X.25, Frame Relay, PPP, y otros.

2 Una reciente "innovación" es USB OTG o USB "On-The-Go", que en resumidas cuentas no es nimás ni menos que un sistema de handshake para que un dispositivo portátil (de ahí el "on the go")como por ejemplo una PDA, pueda decidir si actúa como dispositivo periférico (conectado a unacomputadora para actualización y sincronización) o como controlador (conectado a algún periféricopara su uso). De todos modos, una vez decidido, uno es master (controlador) y el otro slave

(dispositivo)

151

Page 170: El Camino Del Conejo

Conectividad

comunicaciones (2), lo que permite conectar y desconectar un dispositivo "encaliente" (hotswap). Cada dispositivo debe inicializar en un modo de trabajo en elcual no consuma más de 100mA, informando al controlador cuánta corrientenecesita, y cuando éste se lo permita podrá pasar a consumir más. El consumo decorriente máximo permitido por bus es de 500mA. Un dispositivo puede alimentarsedel bus, que provee 5V (en realidad entre 4,375V y 5,25V), o de formaindependiente.

Un hub USB puede partir un bus en varios buses físicos, pero el bus lógico es elmismo, y el máximo total de dispositivos seguirá siendo ciento veintisiete. Lacantidad máxima de hubs que se pueden conectar en cascada es de cinco, y un hubque se alimenta del bus, solamente puede entregar alimentación a otros dispositivos,es decir, no está permitido conectar un hub alimentado del bus a otro hub alimentadodel bus.

Dijimos que hay dos cables de alimentación, y dos de comunicaciones. Por esosdos últimos, el protocolo de comunicaciones permite establecer circuitos virtualesentre el controlador y los dispositivos. Cada dispositivo físico puede tener variasfunctions, y el límite de circuitos virtuales posibles para cada function es de dieciséisen cada sentido (controlador-dispositivo y dispositivo-controlador). Los circuitosvirtuales se establecen desde el controlador, y uno de ellos está reservado para lacomunicación de control entre el dispositivo y el controlador. Estos circuitosvirtuales reciben generalmente el nombre de endpoints.Obviamente, dado que existen circuitos virtuales, la información es transportada en

forma de paquetes, los cuales tienen una longitud en potencias de dos.Todos los datos necesarios para el funcionamiento del dispositivo se obtienen del

diálogo inicial entre el mismo y el controlador. Dichos datos forman una estructurajerárquica llamada device descriptor, que tiene uno o más configuration descriptors,que a su vez tienen uno o más interface descriptors, que a su vez tienen una defaultinterface y probablemente algunas alternate interfaces, que finalmente tienenendpoint descriptors... Simplificando un poco el tema, en esa estructura se definecomo responde el dispositivo a los diversos modos de trabajo (bajo consumo, activo:configuration descriptors), las posibilidades de comunicación (audio y video en unacámara: dos interface descriptors, uno para audio y uno para video), y cómoconectarse a cada interfaz (los endpoint descriptors). Por ejemplo:

� device descriptor� configuration descriptor 1 (activo)

� interface descriptor 1 (audio)� interface descriptor 2 (video)� default interface

� endpoint descriptor 1� endpoint descriptor 2

� configuration descriptor 2 (bajo consumo)

152

Page 171: El Camino Del Conejo

USB

� interface descriptor 1� interface descriptor 2� default interface

� endpoint descriptor 1� endpoint descriptor 2

Cada endpoint pertenece a una categoría, y esto define el tipo de transacción aefectuar:� Control transfers (transferencias de datos de control): se trata de comandos y

respuestas simples, el endpoint 0 (comunicación entre controlador y dispositivo)pertenece a esta categoría.

� Isochronous transfers (transferencias isócronas): corresponde a aplicaciones quenecesitan transferir datos con una periodicidad estable, como por ejemplocomunicaciones de voz o video, requiriendo asignación de un ancho de banda detransferencia fijo.

� Interrupt transfers (transferencias de datos por interrupciones): corresponde aaplicaciones que requieren una baja latencia, usualmente interfaces de usuario. Elnombre tal vez aluda a que este tipo de dispositivos usualmente se maneja porinterrupciones en un micro, pero en un bus USB no hay nada que puedainterrumpir a nadie3. Este tipo de transferencias tal vez debió haberse llamadohigh-priority (de alta prioridad)

� Bulk transfers (transferencias en masa): corresponde a aplicaciones que no tienenrequerimientos de latencia o ancho de banda, y que transmiten gran cantidad dedatos a la mayor velocidad posible de forma esporádica.

Identificado cada tipo de endpoint de cada interfaz de cada dispositivo, en el modode trabajo corriente, el controlador administra la frecuencia y orden en que interrogaa cada uno de éstos para que puedan entregar o recibir los datos que necesitan. Enningún momento un dispositivo puede poner datos en el bus si el controlador no selo indica. Como es de suponer, los endpoints de clase interrupt-transfers soninterrogados más frecuentemente y los de tipo isócrono con una periodicidad estable.

Como podemos inferir de la información anterior, los dispositivos se sientantranquilamente a esperar que el controlador les diga cuando hablar, mientras que esteúltimo transpira desesperado por atender a todos a tiempo. El hardware delcontrolador es entonces bastante complicado y recibe el nombre de HCD: HostController Device, cuya interfaz con el procesador responde (al momento de escribireste texto...) a uno de tres tipos:

� OHCI: Open Host Controller Interface, diseño standard de USB 1.0 y 1.1� UHCI: Universal Host Controller Interface, diseño alternativo de USB 1.0 y 1.1

3 al menos no al momento de escribir este texto...

153

Page 172: El Camino Del Conejo

Conectividad

� EHCI: Enhanced Host Controller Interface, diseño de USB2.0; los controladoresEHCI implementan un OHCI o UHCI virtual (según el fabricante) para acceder adispositivos anteriores de baja velocidad.

Finalmente, los dispositivos conectados a un bus USB se identifican como parte deuna class, lo que permite que el sistema operativo en el host donde reside elcontrolador pueda cargar los drivers necesarios para comunicarse con esedispositivo. Algunas de las clases más comunes son:� Audio device class (tarjetas de sonido, etc.)� Human Interface Device class (HID) (teclados, ratones)� Printer device class (impresoras)� Mass storage device class (discos, tarjetas, cámaras fotográficas digitales)

Las velocidades máximas (al momento de escribir este texto...) corresponden a unade tres:� Low-speed: 1.5 Mbps� Full-Speed: 12 Mbps� High-Speed: 480 Mbps (USB 2.0)

¿Por qué?

Las razones por las cuales incorporar una interfaz USB en un sistema dedicadodependen mayormente del mismo. Si no hay necesidad de tener un acceso a éstedesde otro equipo de mayor jerarquía, no es necesario; incluso en muchos casos sepuede suplir utilizando la interfaz Ethernet. Pese a la cantidad de periféricos USB deque se dispone en el mercado, la implementación de un host USB es demasiadocomplicada para la mayoría de las aplicaciones; no sólo por el controlador sinoporque aun resuelto este tema, queda el de la confección de drivers para atender aestos dispositivos.

Sin embargo, cuando el costo prima y nuestro equipo no dispone de interfazEthernet, una interfaz USB es mucho más interesante y rápida que un port RS-232.Particularmente porque las computadoras modernas traen cada vez menos portsserie, y muchas portátiles no los traen.Equipos de data-logging en general se ven altamente beneficiados con una interfaz

USB, de modo de poder entregar todo su contenido rápidamente a una laptop oequivalente. ¿Hay otras alternativas? Sí, las hay. ¿Mejores o más rápidas? Tal vez,pero una interfaz USB puede ser una alternativa simple, rápida y económica.

FT232

Para tener conectividad USB, no es necesario implementar todas las opciones ni"hablar USB".� La familia de chips FT232 de FTDI resuelve toda la comunicación en el bus

USB, presentándose al usuario como un puerto serie asincrónico.

154

Page 173: El Camino Del Conejo

USB

� Conectando dos pines del chip directamente a los pines de la UART del micro,éste ya está comunicado vía USB.

� El FT232 maneja todos los aspectos USB, incluso la identificación. En el casomás común y económico, en que el usuario no necesita una identificaciónparticular, el FT232 se identifica de forma genérica como un dispositivo deconversión serie a USB. Si el usuario necesita identificar su producto, puedeconectar una EEPROM (la cual será programada por un software provisto por elfabricante), en la cual residirán sus datos de identificación.

� El envío de los datos se realiza bajo la categoría de bulk transfers, agrupandodatos de manera de optimizar latencia y throughput. Esto no presenta ningún tipode inconvenientes para la mayoría de las aplicaciones simples, pero, de sernecesario, es posible controlar varios aspectos de la conversión mediante bits decontrol en la EEPROM; incluso puede utilizarse la categoría isochronoustransfers (transferencias isócronas) si es necesario.

� El FT232 soporta todas las señales de interfaz y varias tensiones y formas deoperación. Puede funcionar tanto a 3,3V como a 5V, puede alimentarse del bus otener su propia fuente, pasando a modo bajo consumo cuando lo indica el hostUSB y comandando por un pin al resto del circuito. Incluso es posible trabajar enhalf-duplex (por ejemplo RS-485), dado que el chip dispone de una salida paracomandar un transceiver.

� Desde el host USB, una vez cargado el driver, el FT232 se ve como un stream dedatos serie. Tanto para los entornos comerciales más usados como para Linux, elfabricante provee unos drivers que permiten ver al FT232 como un port serieadicional, es decir, al conectar el FT232 al bus USB de una "PC común", apareceun nuevo COM, del cual puede configurarse su velocidad, tamaño de palabra, ycontrol de flujo, como cualquier otro port serie standard. Los datos enviados yrecibidos por este port serie virtual se corresponden con las señales en los pinesTD y RD del FT232, respectivamente. Para los más osados, existe una bibliotecade funciones (DLL en la jerga del entorno más conocido) que permite teneracceso directo USB mediante llamadas a función, disponible en la página delfabricante para diversas plataformas.

Veremos a continuación una interfaz USB con el FT232 que se alimenta a 5V de lamisma alimentación que la CPU o microcontrolador del equipo. La misma,conectada a un Rabbit 2000, provee instantáneamente conectividad USB.

155

Page 174: El Camino Del Conejo

Conectividad

Para el caso de un Rabbit 3000, tenemos una serie de opciones:� En kits/módulos con alimentación de 5V y LDO interno, podemos utilizar el

mismo esquema; el R3000 es 5V-tolerant y los niveles lógicos de operación delFT232 permiten el funcionamiento con I/O de 3,3V; alimentado a 5V.

� En kits/módulos con alimentación de 3,3V, o mismo en el caso anterior siproveemos esta tensión de forma externa al FT232, podemos utilizar el esquemamodificado para 3,3V. El inconveniente es que deberemos tomar los 5V dealguna parte.

156

+5V

10u .1 .1

470

27

27

1K5

4K7

10K

27p27p

Vcc-IO

AVccVcc

USBDM

USBDP

RSTOUT

RESET

TxD

RxD

XTin XTout

33n

3V3out

1

2

3

4

USB

CPU

6MHz

GND

AGND

PWRCTL

+5V

10u .1 .1

470

27

27

1K5

4K7

10K

27p27p

Vcc-IO

AVccVcc

USBDM

USBDP

RSTOUT

RESET

TxD

RxD

XTin XTout

33n

3V3out

1

2

3

4

USB

CPU

6MHz

GND

AGND

PWRCTL

+3,3V

Page 175: El Camino Del Conejo

USB

En cualquiera de estos casos, si es necesario, podemos alimentar el FT232 del busUSB, y señalizar al Rabbit que pase a bajo consumo aprovechando las señales decontrol del FT232:

Se recomienda a quienes quieran utilizar esta última opción, leer cuidadosamente lahoja de datos del FT232. Para Rabbit 4000 y 5000, sólo podemos emplear esta última opción.

Bluetooth

Hace ya unos años, comienzan a aparecer algunos standards para interconexióninalámbrica de equipos. Uno de ellos, es Bluetooth.

Introducción a Bluetooth

Bluetooth corresponde a lo que se denomina WPAN (Wireless Personal AreaNetwork), y su objetivo principal es interconectar dispositivos como PDAs,teléfonos celulares, laptops, etc. El intercambio de información se realiza de formainalámbrica, y preferentemente de un modo seguro y económico. Según se puedeleer por allí, el nombre (que bastante llama la atención, por cierto) deriva de unfamoso rey nórdico de la antigüedad, que logró apaciguar y unificar las diversasfacciones que hoy son avanzados países con pujantes empresas de telefonía yfabricantes de teléfonos celulares como la que tuvo la idea de desarrollar esteprotocolo y ponerle ese nombre alegórico; ante la obvia similitud entre unificar ygobernar varios pueblos enemistados e interconectar artefactos disímiles defabricantes diversos.Los dispositivos se dividen en tres grandes clases, de acuerdo a su potencia de RF,

lo que redunda en tres alcances, medidos en decenas de centímetros, metros, y

157

10u .1

.1470

27

27

1K5

27p27p

Vcc-IO

AVccVcc

USBDM

USBDP

RSTOUT

RESET

TxD

RxD

XTin XTout

33n

3V3out

1

2

3

4

USB

CPU

6MHz

GND

AGND

PWRCTL

5VCPU 3VCPU

PWREN LowPowerCtl

Page 176: El Camino Del Conejo

Conectividad

decenas de metros. Las velocidades de operación varían con las revisiones delstandard, generalmente buscando competir con USB y WiFi.

En Bluetooth, los dispositivos se agrupan en pequeñas redes de hasta ochoelementos denominadas piconets. Uno de los dispositivos asume el rol de master,con lo que los restantes asumen el rol de slave. Este rol puede ir cambiando. Laspiconets, a su vez, pueden agruparse en scatternets, pero esto es algo máscomplicado. En una piconet, master y uno de los slaves pueden intercambiar tráficoen cualquier momento, el master va cambiando periódicamente de slave para poderatenderlos a todos.

El protocolo opera en la banda de 2,45 Ghz, la cual divide en 79 canales y vasaltando rápidamente de canal, de modo de no interferir ni ser interferido por otrosdispositivos con otras aplicaciones, dado que se trata de una banda en la que no serequiere licencia para operar.

Un detalle que siempre se toma en consideración al momento de utilizarcomunicaciones inalámbricas, es el de la seguridad. Si bien el standard soportaautenticación, generación de claves y encripción para la protección de los datos(SAFER+, E0), existen artículos donde se describen falencias o ataques. Sinembargo, más allá de la seguridad informática, el verdadero problema de seguridaden los dispositivos Bluetooth es que, debido a la existencia del protocolo de servicediscovery, que permite descubrir y conectarse con otros dispositivos, es muy fácildetectar la presencia de estos dispositivos en vehículos estacionados, bolsos,transeúntes, etc...Según la IEEE, el physical layer de Bluetooth está conformado por dos sublayers,

el protocolo de RF (RF layer) con salto de canales (channel hopping) quebrevemente describimos, y un baseband layer, que se encarga de establecer el enlacefísico entre dos dispositivos, para la existencia del diálogo y por ende de unapiconet. El layer superior (data link layer) está conformado también por dossublayers: el link manager, encargado de negociar seguridad, tamaños de paquete,potencia de RF y estados de conexión; y el L2CAP (Logical Link Control andAdaptation layer Protocol, que es el que se encarga de proveer los servicios deconexión para que puedan comunicarse los protocolos de nivel superior. Entonces,resumiendo, a 2,45 GHz, los dispositivos se comunican en grupos de ochoelementos, de a dos por vez, salteando frecuentemente el canal utilizado, empleandoalgunos protocolos para establecer y mantener la comunicación, negociar opciones, yproveer un enlace transparente sobre el que se puedan correr otros protocolos, queservirán a las aplicaciones que se intenta servir.

Como comentáramos, existe un protocolo denominado SD, service discovery, quepermite que los dispositivos puedan "conocerse" entre sí, mediante el intercambio (apedido) de información relevante a las características de operación y servicios deconexión disponibles y permitidos. Otra posibilidad es directamente utilizar ladirección del dispositivo, si se la conoce. Cada dispositivo posee una dirección de48-bits que es única. Afortunadamente para los humanos, el resultado de SD suele

158

Page 177: El Camino Del Conejo

Bluetooth

incluir nombres "de fantasía", que hacen más fácil identificar el dispositivo al que seintenta conectar. La respuesta a pedidos de SD puede habilitarse, a voluntad.

Conexión (pairing, bonding)

Conocida la dirección, el proceso de conexión entre dos partes se denominapairing, y puede hacerse de forma libre o requiriendo autenticación e inclusoencripción de la información, según la configuración de los dispositivosinvolucrados.

Perfiles (profiles)

Para facilitar la comprensión entre dispositivos, existen determinados perfiles deoperación (Bluetooth profiles) que definen las posibles aplicaciones de los mismos.Esta es un área variada y diversa, por ejemplo existen perfiles para el intercambio deinformación como tarjetas personales, algo muy común entre los usuarios de PDAs;generalmente están basados en OBEX, que es el protocolo de intercambio de objetosempleado desde las primeras Palm PDAs. Existen además perfiles de operación parala conexión de auriculares, controles remotos, etc. Los que más nos interesan sonalgunos basados en un protocolo que permite simular un cable entre dos dispositivos.Este protocolo se denomina RFCOMM, y los perfiles a que hacemos referencia sonSPP (Serial Port Profile) y DUN (Dial-Up Network).

SPP

El perfil SPP simula una conexión mediante un cable, es decir, los dispositivos consoporte Bluetooth que se hallan interconectados mediante el perfil SPP, secomportan como si estuvieran conectados por un cable. Es equivalente a un circuitovirtual, canal lógico, etc., en otras tecnologías.

DUN

El perfil DUN es similar a SPP, pero permite operar con modems, es decir, unteléfono celular que soporta el perfil DUN, permite que se lo use como un modem.

¿Por qué?

Las razones por las cuales implementar Bluetooth en una aplicación o productofinal dependen mayormente del mismo, como ejemplo podemos citar la posibilidadde ser controlado a distancia, sin necesidad de que el operador tenga contacto físicoo siquiera proximidad con el equipo, que puede hallarse en un área restringida. Grancantidad de PDAs y teléfonos celulares incluyen soporte Bluetooth, permitiendo queuna aplicación residente en la PDA pueda controlar de forma gráfica el equipo,simulando, por ejemplo, el frente del mismo. De forma aún más simple, medianteuna aplicación freeware para PDA que permita enviar y recibir caracteres por el

159

Page 178: El Camino Del Conejo

Conectividad

perfil SPP de Bluetooth4, puede realizarse una conexión virtual al puerto serie denuestro equipo, que con un simple intérprete de comandos, ya tiene acceso remoto.Mediante DUN, podríamos utilizar un dispositivo como si fuera un modem, lo que

eventualmente nos permitiría correr PPP; sobre éste TCP/IP, y ganar acceso a unared como la Internet5.

Módulos Bluetooth

Si bien Bluetooth es un stack de protocolo que requiere una considerable cantidadde recursos del procesador, no necesariamente dicho stack debe correrse en elprocesador principal. Existen módulos Bluetooth que permiten a cualquierprocesador con UART acceder al mundo Bluetooth, mediante SPP (Serial PortProfile), es decir, simulando una conexión serie.

Los módulos de KC Wirefree son módulos basados en chipsets de Zeevo, quecontienen un procesador (por lo general ARM7) con todo el stack Bluetooth, el cualse encarga de todas las tareas relacionadas con éste; el procesador principal locontrola mediante comandos AT extendidos, a través de un puerto serie. Según elmódulo, existe además una cantidad de pines de I/O adicionales, que pueden sercontrolados mediante los mismos comandos. Los módulos funcionan a 3,3V eincluyen la RF y una antena integrada.La forma más simple de conectarse a un dispositivo con uno de estos módulos, es

iniciar una conexión desde el dispositivo remoto. Desde una PDA, por ejemplo, seinicia un discovery y el módulo aparecerá en la lista de dispositivos cercanos. Luego,solicitando una conexión, ya estamos conectados.

Iniciar una conexión desde el módulo es igualmente simple, primero deberemosdescubrir la dirección de los demás dispositivos que tengamos cerca, para elloingresamos el comando

AT+ZV Discovery

Una vez elegido el dispositivo, el comando para conectarnos vía SPP es

AT+ZV SPPConnect <dirección>

Una vez establecida la conexión SPP, el módulo se comporta de formatransparente, todo lo que recibe vía Bluetooth (dentro de esa conexión) lo entregapor el puerto serie, y todo lo que recibe por el puerto serie lo transmite al otroextremo, vía la conexión Bluetooth. De igual modo que los modems tradicionalestienen una secuencia de escape para recuperar el control en modo comando y poderdesconectar o modificar parámetros de operación, también estos módulos tienen una

4 Un ejemplo de esto es el programa TriConnect, para PalmOS, disponible en Internet. Este programasimula una terminal asincrónica sobre el puerto serie, un port TCP, o una conexión SPP Bluetooth,permitiendo observar lo que envía el equipo conectado al módulo, y mandar caracteres ingresados enla PDA

5 De hecho, hay un ejemplo de esto en el capítulo sobre networking con Rabbit

160

Page 179: El Camino Del Conejo

Bluetooth

secuencia de escape, diferente, pero igualmente efectiva, y con menor probabilidadde que sea simulada por el stream de datos.

En el caso de DUN, es posible que debamos primero permitir el pairing, tambiénconocido como bonding, lo cual realizamos mediante el comando:

AT+ZV EnableBond <dirección> <clave>

Una vez permitido el bonding, el comando para conectarnos vía DUN es

AT+ZV DUNConnect

El dispositivo remoto deberá proveer la clave requerida para que se establezca laconexión.

El único detalle a tener en cuenta, a la hora de desarrollar software para interactuarcon el módulo, es el hecho de que el mismo envía mensajes al momento deestablecimiento de la conexión y de interrupción de la misma; nada diferente de unmodem convencional. He aquí un pequeño fragmento de programa que espera unaconexión y pasa un string recibido a la función que lo procesa, descartando losmensajes del módulo:

costate { do {

wfd p=cof_serEgets(sentence,sizeof(sentence)-1,10000); } while(p == 0);

if(strstr(sentence,"ConnectionUp")) wfd putprompt(); // muestra prompt "Ingrese comando"

else {if((!strstr(sentence,"AT-ZV")) && (!strstr(sentence,"###NO CARRIER")) && strlen(sentence)){ // ignora mensajes del módulo

wfd cof_serEwrite(sentence, strlen(sentence)); //eco // procesa comando

} }}

ZigBee e IEEE 802.15.4

Comentamos aquí la utilización de dos especificaciones interrelacionadas,destinadas de forma primaria a la intercomunicación de sensores o entidadesindependientes con relativamente bajo tráfico, de forma inalámbrica.

Introducción a ZigBee

El standard 802.15.4 de la IEEE forma parte de un grupo de standards destinados areglamentar la realización de redes personales inalámbricas, o WPANs (WirelessPersonal Area Networks). A grandes rasgos, 802.15.4 se encarga de establecer una

161

Page 180: El Camino Del Conejo

Conectividad

comunicación confiable mediante un enlace de radiofrecuencia, permitiendo ademásel uso de broadcasts para direccionar varios dispositivos a la vez. La confiabilidad seobtiene mediante detección de error, acuse de recibo y retransmisiones; de modo quese pueda determinar si una trama se entrega o no. Si bien esto puede realizarse contecnologías previamente existentes, el énfasis de 802.15.4 se halla en la simpleza dela implementación, requiriendo relativamente poca potencia de procesamiento yfacilitando la operación en bajo consumo, permitiendo que los dispositivos puedan"ausentarse" para dormir por ciertos períodos de tiempo.

El stack de protocolos ZigBee se apoya sobre IEEE 802.15.4-2003 para lacomunicación entre dispositivos cercanos, es decir, el acceso al medio y elintercambio de mensajes por éste. Por sobre esta base, define un nivel de routing(NWK) que permite que los dispositivos con funcionalidad de router puedantransportar mensajes para otro destinatario, extendiendo el alcance total de la red.

Routing en una red ZigBee

Una red ZigBee puede tomar topologías:� Estrella� Árbol� Mesh

En una red estrella, el coordinador atiende a un número de dispositivos, de modosimilar a una red 802.15.4.

En una red árbol tenemos la presencia de routers, y podemos armar pequeñasestrellas (cluster tree). La información se distribuye de forma jerárquica a lo largodel árbol hasta llegar a destino.

En una red mesh, los routers y el coordinador "descubren" la ruta hacia eldestinatario del mensaje mediante una serie de mensajes NWK. Si no haycomunicación directa, los mensajes viajan de router en router hasta llegar aldestinatario. Los dispositivos de bajo consumo siempre entregan los mensajes a sucoordinador, quien tiene la misión de recibir y guardar los mensajes para éstos hastatanto despierten y lo contacten, momento en el cual procede a entregárselos. Si unaruta falla, es posible detectarlo debido a que 802.15.4 posee confirmación derecepción, entonces el router que tiene el mensaje inicia el proceso de descubrir unaruta alternativa y la red converge nuevamente.

Comunicación de aplicaciones en una red ZigBee

Por encima de NWK está el protocolo APS (APplication Support), el cual proveemúltiples circuitos virtuales en la comunicación entre dispositivos. Estos circuitosvirtuales se denominan endpoints. Cada dispositivo tiene un radio y una dirección802.15.4. Esta dirección es única y es la que identifica a cada dispositivo. A su vez,el endpoint identifica a la aplicación. Cada tipo de mensaje en particular tiene uncluster-ID que lo identifica. El conjunto de mensajes que utiliza una aplicación sedenomina profile, y se identifica mediante un profile-ID.

162

Page 181: El Camino Del Conejo

ZigBee e IEEE 802.15.4

Los profiles son definidos por la ZigBee Alliance. Existen profiles públicos yprivados (manufacturer specific), se requiere que los dispositivos utilizando profilespúblicos operen con otros similares, mientras que para los privados sólo se requiereque coexistan con los otros pacíficamente. De este modo, todos los dispositivosutilizando un profile-ID determinado conocen el significado de cada cluster-ID yrelacionan la misma acción con ese código.

Binding

El endpoint 0 está reservado para una aplicación especial denominada ZDO(ZigBee Device Objects), que es la que se encarga de las tareas de configuración ymucho del funcionamiento automático. Qué profiles soporta cada dispositivo y enqué endpoint, se descubre mediante diálogo de ZDOs. De este modo, es posibleauto-configurarse y operar con otros dispositivos. Dicho proceso de descubrir yasociarse se denomina binding.

¿Por qué?

Las razones por las cuales implementar ZigBee u 802.15.4 en una aplicación oproducto son similares a las de cualquier conectividad inalámbrica. Si necesitamoscapacidades de Mbps tenemos Wi-Fi, si el alcance es más limitado y no requerimosmultipunto tenemos Bluetooth, y para menor capacidad tenemos ZigBee/802.15.4;particularmente si el consumo es una variable de importancia.

La cuestión IEEE 802.15.4 versus ZigBee es en realidad un análisis de lo quenuestro proyecto necesita y lo que cada una provee. Algunas aplicaciones puedenresolverse con cualquiera de las dos, mientras que otras requieren una en particularpor algún motivo específico. No es que una sea mejor o peor que la otra, no es queZigBee sea más completa por incluir a 802.15.4, ambas apuntan a resolver unproblema en particular, y ello genera características que les son específicas y quepueden afectar el rendimiento de una aplicación y deben ser tenidas en cuenta. Los puntos fundamentales en donde se separan ambas tecnologías son:

� throughput y latencia� self-healing y routing� complejidad de la red� complejidad del stack

Módulos XBee

Quien más, quien menos, la gran mayoría estamos acostumbrados a usar una UARTen un microcontrolador o un puerto serie en una computadora. La idea de reducirtodo el stack ZigBee a un puerto serie es sumamente tentadora; el utilizar un módulocomo XBee permite escribir la aplicación como cualquier otra aplicación que utilizaun puerto serie, e incluso poder depurar utilizando la computadora sin necesidad desimuladores. Incluso portar el código a ésta, no es necesario disponer del stack, la

163

Page 182: El Camino Del Conejo

Conectividad

interfaz con el mundo ZigBee es el puerto serie; cualquier sistema con capacidad deser programado y un puerto serie, inmediatamente es "ZigBee-compatible". Lomismo ocurre para 802.15.4; con sólo agregar el módulo, un sistema que secomunica por cable pasa inmediatamente a ser inalámbrico, sin necesidad de realizarmodificaciones más allá del reemplazo del o los chips de interfaz por un móduloXBee6.

Como un beneficio adicional, estos módulos cuentan con un conversor A/D devarios canales y pines de I/O, por lo que es posible usarlos como adquisidores dedatos remotos.No vamos a entrar en detalle porque estos módulos tienen muchas prestaciones y

ya disponen de un texto propio. Daremos aquí algunos simples ejemplos de usocomo herramienta de conectividad

XBee 802.15.4

Podemos comunicarnos con estos módulos mediante comandos AT o utilizando unprotocolo documentado denominado “modo API”. En cualquiera de los casos, setrata de una conexión a una UART y un diálogo por el puerto serie.

Comunicación punto a punto

Por defecto, el XBee funciona en modo transparente. En este modo, el móduloenvía al remoto configurado como destinatario los mensajes que recibe por su puertoserie, y presenta en éste los mensajes que recibe del módulo remoto. Asignamos lasdirecciones configurando el parámetro MY=<dirección> para la dirección propia yDL=<dirección> para la dirección del remoto. Esto lo haremos en ambos módulosantes de ponerlos en servicio, por ejemplo:

� módulo 1� MY=1234� DL=4321

� módulo 2� MY=4321� DL=1234

Ambos módulos permanecen con el receptor encendido a la espera de un mensajedel otro.

Red punto a multipunto con coordinador

Los módulos remotos son aquéllos que tienen el parámetro CE con valor 0. Elmódulo central se denomina coordinador, y para que funcione como tal se deberá

6 NdA: permítaseme la licencia poética de asumir que se dispone de 3,3V para alimentar al módulo yhacer la interfaz entre el micro y éste.

164

Page 183: El Camino Del Conejo

ZigBee e IEEE 802.15.4

setear el parámetro CE en el valor 1. Sólo puede haber un coordinador por red(PAN).Cada módulo remoto tendrá como dirección de destino la del módulo central, y su

dirección propia será única. Esto sucede de forma automática al asociarse.Para que un remoto inicie el proceso de asociación, debe setearse el bit 2 en el

parámetro A1. Para que el coordinador asocie remotos que se lo solicitan, debesetearse el bit 2 en el parámetro A2. Por ejemplo A1=4 y A2=4, en uno y otromódulo, respectivamente. Esto puede realizarse manualmente o dejarse guardado enla configuración para que se realice automáticamente al arrancar.

En el Rabbit conectado al módulo coordinador, recibiremos los mensajes que nosenvíen los remotos. Para determinar quién es el que nos envía el mensaje, deberemosinsertar algún tipo de identificador dentro del mismo mensaje, o utilizar el modoAPI.

La información que recibe el coordinador se envía a aquel remoto cuya direccióncoincide con lo que colocamos en los parámetros DH y DL. Para direccionardiferentes módulos, deberemos alterar periódicamente este parámetro (cada vez quedeseemos transmitir a un remoto diferente), por lo que suele ser preferible utilizar elmodo API. Los remotos permanecen en bajo consumo con el receptor apagado porun tiempo que se puede configurar, y al despertar siempre transmiten la informaciónal coordinador.

XBee ZB

En el caso del XBee ZB, también podemos utilizar los comandos AT o el modoAPI. Sin embargo, dado que ya existe una biblioteca de funciones que nos simplificala operatoria, vamos a analizarla.

xbee_api.lib

Mediante la biblioteca de funciones xbee_api.lib se provee una serie de funcionespara el control de estos módulos, con un acercamiento más desde el punto de vistadel programador, que gusta de tener el control del proceso desde su dispositivo, eneste caso el Rabbit.Si utilizamos un módulo con el XBee a bordo, como por ejemplo el RCM4510W, o

una single-board computer, todo ocurre de manera transparente. Sin embargo, sideseamos conectar un XBee a un módulo Rabbit cualquiera, o incluso a una placa denuestro desarrollo, deberemos indicarle a xbee_api.lib en qué puerto serie está elsusodicho; lo cual realizamos mediante la siguiente definición:

#define ZB_SERIAL_PORT B

Como adelantamos no describiremos esto en detalle, pero podemos mencionar queel eje de la operatoria es la función xbee_tick(), que deberemos llamarperiódicamente, y disponemos de funciones para transmitir y recibir:

165

Page 184: El Camino Del Conejo

Conectividad

zb_sendAddress_t addr;

addr.msg=txmsg;addr.msglen=strlen(txmsg);zb_send(&addr);

if(zb_receive(rxmsg,&rxlen)){// dispongo del mensaje en rxmsg y la longitud en rxlen

}

zb_reply("Gracias!",8);

Equisbí

Sin ánimo de incurrir en auto-marketing, el autor se permite destinar un pequeñopárrafo a comentar sobre otro de sus libros, el cual aborda en detalle esta familia demódulos y en particular contiene un capítulo centrado en la interacción entre elXBee ZB y los módulos Rabbit, particularmente la familia RCM4500W, y enfocadoen la utilización de los módulos XBee ZB con micros Rabbit y xbee_api.lib.

Gran parte del contenido de este apartado ha sido tomado de dicho libro, conpermiso del autor. Se invita a los lectores a visitarlo:http://www.ldir.com.ar/libros/equisbi/ (sí, sin acento)

RF multipunto 2,4 Ghz

Con este pomposo título nos referimos a una alternativa económica a Bluetooth yZigBee: los módulos transceptores TRW-2.4G de Wenshing. Se trata de transceiversque operan (como puede inferirse) en la banda de 2,4 GHz, con capacidad dedireccionamiento y selección de canal de comunicaciones. Esto nos permite no sóloelegir el canal a utilizar, sino además manejar todo un esquema de direccionamientode forma transparente, que nos posibilita comunicaciones multipunto. No existesoporte de encripción y/o seguridad.Estos módulos poseen una potencia de salida de 0dBm, lo que permite un alcance

algo más reducido que una red Wi-Fi, más cercano a Bluetooth (metros a decenas demetros). Funcionan a 3,3V, y su consumo es bastante reducido; pero lo realmenteinteresante está en su interfaz. La interfaz del módulo es netamente digital. Si biennada impide su utilización en la forma tradicional (entra bit - sale bit), estos módulospueden trabajar con un sistema denominado ShockBurst, que es algo así como unstore and forward que permite emplear micros sin UART, y/o con relojes de bajafrecuencia y/o poca precisión, comunicándose a una velocidad baja, sin por ellomantener ocupado el canal de comunicaciones, en el cual la comunicación es a altavelocidad, minimizando a la vez consumo y ocupación del canal.

166

Page 185: El Camino Del Conejo

RF multipunto 2,4 Ghz

El micro y el módulo se comunican mediante una interfaz de cinco pines, al ritmoque el primero marca en la señal CLK; esto permite que el micro opere sobre elmódulo cuando dispone de tiempo para hacerlo. Señalizado el fin de paquete atransmitir, el módulo lo transmite a una velocidad mucho mayor (250Kbps),minimizando el riesgo de colisiones con otros módulos y el consumo. El módulo querecibe un mensaje, informa al micro mediante el pin DR1; este último procederáentonces a leer la información del módulo a su propio ritmo.

Las señales restantes sirven para informar al módulo que lo estamos accediendopara configurarlo (CS), o para enviar o recibir datos (CE). Los datos viajan en uno uotro sentido por el pin DATA. Si bien existen algunas alternativas adicionales comola posibilidad de recibir en dos canales, las omitiremos por simplicidad.

Al aplicar alimentación al módulo, éste se encuentra en un estado indefinido ydeberá ingresarse la configuración. Entre estos datos, se encuentra la direcciónpropia del módulo, el canal de operación dentro de la banda, la longitud de losmensajes, y el modo de trabajo, ya que es half-duplex. Tanto recepción comotransmisión se realizan por el mismo canal.

Previo al mensaje a transmitir, el micro comunica al módulo la dirección delmódulo destinatario, como si fuera parte del mensaje (los primeros cinco bytes). Elmódulo agregará luego un CRC a la cola y transmitirá el mensaje. Como el módulo

167

CLK

DATA

CE

Tx

Consumo

CE

DR1

CLK

DATA

tiempo enel aire

(modulo)

(micro)

Transmisor

Receptor

Page 186: El Camino Del Conejo

Conectividad

destinatario conoce la longitud de los mensajes, puede validar el CRC y comunicaral micro la presencia de un mensaje sólo cuando éste es válido. Al entregar elmensaje, se eliminan la propia dirección y el CRC, es decir, se obtiene el mensajesolamente.

Desarrollo de una biblioteca de funciones

Vamos a aprovechar este ejemplo para abordar otro tema interesante: el desarrollode una biblioteca de funciones. Implementaremos una biblioteca de funciones que seocupe de enviar y recibir mensajes utilizando estos módulos. En un primer paso,escribiremos todo como si fuera un simple programa, el cual podemos testearcompilándolo de forma independiente. Más adelante, agregaremos los detalles quehacen que Dynamic C incorpore los textos de ayuda e incluya la biblioteca.

La selección de los pines a emplear para el control del transceptor la realizamosmediante macros. En la biblioteca de funciones podemos definir alguna combinaciónque consideremos típica, pero lo fundamental es que puedan redefinirse dentro delprograma más adelante. Esto lo hacemos mediante macros, como generalmente sehace en cualquier entorno C, de la siguiente forma:

#ifndef TRW_DATA_PORT#define TRW_DATA_PORT PFDR

#endif#ifndef TRW_DATA_SHADOW

#define TRW_DATA_SHADOW PFDRShadow#endif#ifndef TRW_DATA_DIR

#define TRW_DATA_DIR PFDDR#endif#ifndef TRW_DATA_DSHADOW

#define TRW_DATA_DSHADOW PFDDRShadow#endif

El mismo procedimiento lo podemos emplear para la dirección local:

#ifndef TRW_LOCAL_ADDRESS#define TRW_LOCAL_ADDRESS 0X12,0X34,0X56,0X78,0X9A

#endif

y por qué no para otros parámetros.

Comenzaremos por desarrollar rutinas para escribir y leer un byte en el módulo:

nodebug void trw_write_byte(unsigned char value){ unsigned char i; BitWrPortI (TRW_DATA_DIR,&TRW_DATA_DSHADOW,1,TRW_DATA_BIT ); // DATA=salida for (i=0x80;i>0;i/=2){ //desplaza máscara BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,0,TRW_SCK_BIT ); if (i & value)

BitWrPortI (TRW_DATA_PORT,&TRW_DATA_SHADOW,1,TRW_DATA_BIT ); // DATA=1 else

BitWrPortI (TRW_DATA_PORT,&TRW_DATA_SHADOW,0,TRW_DATA_BIT ); // DATA=0 BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,1,TRW_SCK_BIT ); // clk }

168

Page 187: El Camino Del Conejo

RF multipunto 2,4 Ghz

BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,0,TRW_SCK_BIT ); BitWrPortI (TRW_DATA_DIR,&TRW_DATA_DSHADOW,0,TRW_DATA_BIT ); // libera DATA}

nodebug char trw_read_byte(){ unsigned char i,val;

val=0; for (i=0x80;i>0;i/=2){ BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,1,TRW_SCK_BIT ); // CLK if (BitRdPortI(TRW_DATA_PORT,TRW_DATA_BIT)) val=(val | i); // lee bit BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,0,TRW_SCK_BIT ); } return val;}

Luego, seguimos por el proceso de inicialización, en el cual escribiremos laconfiguración deseada.

nodebug void trw_init(){int i;unsigned char *cfgptr;const static unsigned char config[18]={0X8E,0X08,0X1C, // TEST, PLLTRW_DATA_LENb, // DATA2_W longitud de datos 2do receptorTRW_DATA_LENb, // DATA1_W longitud de datosTRW_DUMMY_ADDRESS, // ADDR2 dirección segundo receptorTRW_LOCAL_ADDRESS, // ADDR1 direcciónTRW_ADDR_W, // ADDR_W/CRC tipo de CRCTRW_RF_CONFIG, // RF config(TRW_RF_CHANNEL<<1) // frecuencia del canal, modo Tx/Rx};

BitWrPortI (TRW_CS_PORT,&TRW_CS_SHADOW,1,TRW_CS_BIT); // sube CS i=18; cfgptr=config; while(i--) // envía config trw_write_byte(*cfgptr++); BitWrPortI (TRW_CS_PORT,&TRW_CS_SHADOW,0,TRW_CS_BIT); // baja CS}

A continuación, un par de rutinas para enviar y recibir un mensaje. Nótese comoinsertamos la dirección antes del mensaje, al transmitir.

nodebug void trw_settx(void){ BitWrPortI (TRW_CS_PORT,&TRW_CS_SHADOW,1,TRW_CS_BIT); BitWrPortI (TRW_DATA_DIR,&TRW_DATA_DSHADOW,1,TRW_DATA_BIT ); // DATA=salida BitWrPortI (TRW_DATA_PORT,&TRW_DATA_SHADOW,0,TRW_DATA_BIT ); // DATA=0 BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,1,TRW_SCK_BIT ); // clk BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,0,TRW_SCK_BIT ); BitWrPortI (TRW_CS_PORT,&TRW_CS_SHADOW,0,TRW_CS_BIT); BitWrPortI (TRW_DATA_DIR,&TRW_DATA_DSHADOW,0,TRW_DATA_BIT ); // libera DATA}

nodebug void trw_setrx(void){ BitWrPortI (TRW_CS_PORT,&TRW_CS_SHADOW,1,TRW_CS_BIT); BitWrPortI (TRW_DATA_DIR,&TRW_DATA_DSHADOW,1,TRW_DATA_BIT ); // DATA=salida

169

Page 188: El Camino Del Conejo

Conectividad

BitWrPortI (TRW_DATA_PORT,&TRW_DATA_SHADOW,1,TRW_DATA_BIT ); // DATA=1 BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,1,TRW_SCK_BIT ); // clk BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,0,TRW_SCK_BIT ); BitWrPortI (TRW_CS_PORT,&TRW_CS_SHADOW,0,TRW_CS_BIT); BitWrPortI (TRW_DATA_DIR,&TRW_DATA_DSHADOW,0,TRW_DATA_BIT ); // libera DATA BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,1,TRW_CE_BIT);}

nodebug cofunc void trw_sendpacket(unsigned char *address,unsigned char *buf){auto int len;

trw_settx(); // configura tx BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,1,TRW_CE_BIT); // sube CE len=TRW_ADDRESS_LEN; while(len--){ // manda dirección remota trw_write_byte(*address++); yield; } len=TRW_DATA_LEN; while(len--){ // manda datos trw_write_byte(*buf++); yield; } BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,0,TRW_CE_BIT); // baja CE}

nodebug cofunc int trw_getpacket(unsigned char *buf,int timeout){long taimaut;int len;

trw_setrx(); // configura rx BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,1,TRW_CE_BIT); // habilita Rx (CE=1) waitfor(DelayMs(2)); if (timeout) { // espera indicación

taimaut=MS_TIMER+timeout; while(!BitRdPortI(TRW_DR_PORT,TRW_DR_BIT)){

// de recepción DR1=1 if(MS_TIMER<taimaut)

yield;else { BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,0,TRW_CE_BIT);

return(-1); // o sale por timeout } // si se indica

} } else { while(!BitRdPortI(TRW_DR_PORT,TRW_DR_BIT)) yield; } BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,0,TRW_CE_BIT); // baja CE len=TRW_DATA_LEN; while(len--){

*buf++ = trw_read_byte(); // lee datosyield;

} return(TRW_DATA_LEN);}

Finalmente, una simple rutina adicional que se encarga de retransmitir el mensaje sino se obtiene una respuesta dentro de un cierto tiempo. Dado que se trata de un

170

Page 189: El Camino Del Conejo

RF multipunto 2,4 Ghz

medio de acceso múltiple en el cual no tenemos detección de portadora ni decolisiones, es menester arbitrar de algún modo la comunicación para minimizarretransmisiones y lograr confirmar que la información llega a destino. La forma mássimple, que coincide con la propuesta, suele ser tener un sistema configurado comomaster, el cual interroga a los remotos y obtiene respuesta de ellos. Si la pregunta delmaster no llega a destino, éste la retransmitirá. Si la respuesta del remoto no llega, elmaster retransmitirá la pregunta. Los remotos deberán estar preparados para recibirvarias veces la misma pregunta, sin ofenderse.

nodebug cofunc int trw_poll(unsigned char *raddr,char *msg,int timeout){int i,retries;

retries=0; do {

wfd trw_sendpacket(raddr,msg); // envía mensajewaitfor(DelayMs(TRW_TXGUARDTIME)); // espera transmisiónwfd i=trw_getpacket(msg,timeout); // espera respuesta

} while ((i<0) && (retries++ < TRW_NUM_RETRIES));// repite si no la hay

return(i); // hasta que desiste}

Si bien al desarrollar estas rutinas tuvimos en cuenta las especificaciones delfabricante, aprovechamos los retardos normales de un programa en C para evitarintroducir demoras. Hemos comprobado el funcionamiento correcto aun en coresrápidos como el RCM-3360. Para otras aplicaciones, sugerimos revisar que secumplan los tiempos detallados en el manual del usuario del TRW-2.4G.

Una vez resuelto y depurado (bueno, dentro de lo posible...) el software,incorporamos los detalles característicos de Dynamic C.La primera impresión, para el experimentado programador ANSI C, es que generar

bibliotecas de funciones en Dynamic C es muy complicado. Sin embargo, resultamucho más simple de lo que parece. Existen una serie de reglas, tendientes asimplificar tanto el trabajo de creación como el de uso de la biblioteca de funciones.Dynamic C se encargará de buscar cosas específicas para extraer la información quenecesita, tanto para el sistema de ayuda como para la generación de código.

Encabezado de la biblioteca de funciones

Existe un juego de encabezados para la biblioteca de funciones en sí: uno para elsistema de ayuda, y otro para la inclusión de código.

Sistema de ayuda

Al comienzo del archivo, debe existir un encabezado que explique para qué sirveesta biblioteca de funciones:

/* START LIBRARY DESCRIPTION *********************************************TRW-24G.LIB

171

Page 190: El Camino Del Conejo

Conectividad

DESCRIPTION:Este espacio es para la descripción

END DESCRIPTION **********************************************************/

Esto permite que el lector inquieto pueda conocer los alcances del código, y queDynamic C reconozca la biblioteca de funciones.

Código

Lo que pongamos dentro del siguiente juego de comentarios:

/*** BeginHeader */

/*** EndHeader */

será incorporado al código que "usa" esta biblioteca de funciones. Un ejemplo típicoson las macros y definiciones que usualmente se ponen en los archivos deencabezados (header files), mejor conocidos como .h

Por ejemplo, en nuestra biblioteca de funciones, las definiciones de los pines, ydemás parámetros por defecto:

/*** BeginHeader */// CRC type: 0=none, 1=8-bit, 3=16-bit#ifndef TRW_CRC_TYPE#ifndef TRW_LOCAL_ADDRESS// ALWAYS DEFINE 5 BYTES, use only MSBs

#define TRW_LOCAL_ADDRESS 0X12,0X34,0X56,0X78,0X9A//#define TRW_LOCAL_ADDRESS 0X21,0X43,0X65,0X87,0XA9

#endif#ifndef TRW_SCK_PORT

#define TRW_SCK_PORT PFDR#endif#ifndef TRW_SCK_SHADOW

#define TRW_SCK_SHADOW PFDRShadow#endif/*** EndHeader */

El conjunto de comentarios que define al encabezado puede abrirse y cerrarsevarias veces a lo largo del archivo.

Encabezados de funciones y variables globales

Además del encabezado de la biblioteca de funciones, existe un juego deencabezados del sistema de ayuda para cada función y otro para inclusión de código,para cada variable o grupo de éstas que deba ser accedida por una función o grupode éstas.

Sistema de ayuda

Dentro de un comentario de este tipo, se escribe el texto de ayuda que Dynamic Cmostrará cuando se utilice la ayuda on-line. Este procedimiento se repite para cadafunción que deba ser utilizada desde el exterior

172

Page 191: El Camino Del Conejo

RF multipunto 2,4 Ghz

/* START FUNCTION DESCRIPTION ********************************************nombre de la función <library a que pertenece>

SYNTAX: void mifuction(void)

DESCRIPTION: Descripción de la función

END DESCRIPTION **********************************************************/

Por ejemplo, en nuestro caso:

/* START FUNCTION DESCRIPTION ********************************************trw_init <trw-24g.LIB>

SYNTAX: void trw_init(void)

DESCRIPTION: Initializes TRW-2.4G. The module is set in Tx modeThis function must be called >5ms after startup,before any other access to the module

END DESCRIPTION **********************************************************/

La decisión de qué idioma utilizar corre por cuenta del usuario, aunque el ingléspermite no desentonar con los encabezados y poder publicar el trabajo de formaabierta.

Código

Ahora debemos generar el equivalente a las declaraciones de variables estáticasexternas y funciones externas, es decir, lo que normalmente hace que el linker puedahacer su trabajo. Esta es la información que usualmente se pone en los archivos deencabezados. Lo que pongamos dentro del siguiente juego de comentarios:

/*** BeginHeader nombre */

/*** EndHeader */

será incorporado al código que usa la función o variable nombre. Lo que hacemos esdeclarar todo lo externo dentro de estos comentarios, para que quien usa labiblioteca de funciones no deba hacerlo en su programa, simplemente debe poner#use. Por ejemplo:

/*** Beginheader trw_getpacket, trw_sendpacket, trw_settx, trw_setrx */void trw_settx(void);void trw_setrx(void);cofunc void trw_sendpacket(unsigned char *address,unsigned char *buf);cofunc int trw_getpacket(unsigned char *buf,int timeout);/*** EndHeader */

En este caso no tenemos variables o constantes globales. Si las hubiera,declaramos la variable dentro del cuerpo de la biblioteca, y ponemos lasdeclaraciones de variable externa dentro de los encabezados correspondientes:

173

Page 192: El Camino Del Conejo

Conectividad

/*** BeginHeader pepito */

extern int pepito;

/*** EndHeader */

int pepito;

Uso de la biblioteca de funciones

Lo que hace que Dynamic C pueda incluir todo lo que acabamos de hacer,particularmente el sistema de ayuda, es incorporar nuestra biblioteca de funciones enel archivo que define la ruta de acceso a todas las bibliotecas de funciones: lib.dir.Esto se puede realizar:� de forma global, es decir, para todos nuestros proyectos, modificando el archivo

original que se halla en el directorio base de la instalación de Dynamic C.� de forma local, definiendo en las opciones de proyecto el archivo a utilizar a tal

fin. Lo más fácil es copiar el archivo original en el subdirectorio donde alojamosel proyecto, modificarlo, y decirle a Dynamic C que use ese archivo.

Una vez hecho esto, al arrancar Dynamic C encontrará nuestra biblioteca defunciones e inicializará sus estructuras internas para que podamos utilizar todo.

En el programa principal, entonces, incluimos la biblioteca de funciones:

#use TRW-24g.lib

Toda definición que deba hacerse respecto a la biblioteca de funciones, deberácolocarse antes de "usarla". En nuestro caso, definiremos los pines a emplear,dirección, etc., antes de incluirla:

#define TRW_CS_BIT 6#define TRW_CE_BIT 7#define TRW_DR_BIT 1

#define TRW_LOCAL_ADDRESS 0x21,0x43,0x65,0x87,0xA9

#use TRW-24g.lib

Ejemplos

A continuación, un par de esqueletos de programa de ejemplo; uno para master(pregunta) y otro para slave (responde), respectivamente. La totalidad del listado, enla forma de biblioteca de funciones, se encuentra en el CD adjunto, en la sección denotas de aplicación, como CAN-045. También se incluyen dos programas ejemplode uso, uno para RCM-3360 (master) y otro para RCM-3720 (slave). Amboscomentan la operación por el port serie A a 57600 bps.

174

Page 193: El Camino Del Conejo

RF multipunto 2,4 Ghz

Master

void main()

{unsigned int num;int i;unsigned char msg[TRW_DATA_LEN+1];

trw_init(); // inicializa módulo

num=0;while(1){ costate {

sprintf(msg,"%d:Mensaje",num); // prepara preguntawfd i=trw_poll(raddr1,msg,100); // envía y espera respuestaif(i>0){

// procesa respuesta}num++; // otro mensaje...

}// otras tareas

}}

Slave

void main()

{int i,num;unsigned char msg[TRW_DATA_LEN+1];

trw_init(); // inicializa módulo

while(1){ costate {

wfd i=trw_getpacket(msg,5000);// espera pregunta (5 segundos)

if(i>0){sprintf(msg,"%d: ACK",atoi(msg));

// prepara respuesta// permitir que el master ingrese en recepciónwfd trw_sendpacket(raddr1,msg);

// envía respuestawaitfor(DelayMs(TRW_TXGUARDTIME));

// espera transmisión}else {// no hay preguntas por 5 segundos (me voy a dormir ?)}

}// otras tareas}

}

175

Page 194: El Camino Del Conejo

Conectividad

Comunicación serie asincrónica

Dynamic C incorpora una gran cantidad de funciones que facilitan el trabajoasociado al manejo de puertos serie. Estas funciones están distribuidas en dosbibliotecas de funciones con soporte para comunicaciones vía interfaz serieasincrónica. Nos interesa particularmente una de ellas: packet.lib. Esta biblioteca defunciones está orientada a las comunicaciones half-duplex, brindando un conjunto defunciones que envían y reciben bloques de datos. Para recepción se utiliza unconjunto de buffers, cada buffer puede alojar un paquete de información. Los buffersse ubican sobre xmem, y permiten almacenar los paquetes recibidos hasta tanto elprograma encargado tenga tiempo de procesarlos. Dado el carácter de half-duplex,las funciones de transmisión retornan a recepción una vez finalizado el envío delúltimo byte, más un posible tiempo de guarda definible por el usuario. Esto esconfigurable, pudiendo elegirse otros modos de operación como detección de bit deaddress (señalización por noveno bit) o caracter de sincronismo. Al iniciarse laoperación de recepción, se llama a una función provista por el usuario, y al iniciarsela operación de transmisión se llama a otra función complementaria. Estas llamadasa funciones tienen el objeto de poder configurar el medio para la operación aejecutar, como por ejemplo actuar sobre los transceivers en RS-485, según viéramosen el libro introductorio.

Para un repaso de los registros involucrados y las funciones y opciones de pinesdisponibles, aconsejamos rever la sección correspondiente a los puertos serie en elcapítulo sobre periféricos internos. A continuación, haremos un análisis de lasdiversas formas de comunicación soportadas.

Señalización por noveno bit

Al parecer esto se originó con la familia MCS51 (8031, 8051, etc.). Se trata de unrecurso muy simple para minimizar el consumo y simplificar la operatoria ensistemas multipunto. Uno de los problemas más importantes a resolver a la hora deimplementar un protocolo de comunicaciones, es la forma de detectar el comienzode un mensaje. Si bien quien transmite sabe cuando deja de transmitir y quien recibepuede estar consciente de lo que está haciendo; en un sistema multipunto en el quevarios interlocutores están hablando, es muy difícil discernir entre un byte de datosentre interlocutores y la propia dirección, a menos que constantemente se estésiguiendo el diálogo, lo cual puede no ser posible luego de un reset, o si se pretendeque quienes no están involucrados en la conversación puedan descansar. Uno de losrecursos que resuelven esto es la señalización por noveno bit, en la cual latransmisión de datos se hace mediante 9-bits, uno de los cuales (el noveno...) indicasi se trata de un byte de dirección (a qué equipo se le habla) o de datos (el paquete ensí). De esta forma, todos los dispositivos ajenos al diálogo pueden dormir hastarecibir un byte de dirección, comparar con la propia, y seguir durmiendo si no

176

Page 195: El Camino Del Conejo

Comunicación serie asincrónica

corresponde. Algunas UARTs permiten este funcionamiento dado que pueden serconfiguradas para sólo generar una interrupción ante un byte de dirección (situaciónque debe ser modificada al recibir la propia, para permitir la recepción del resto delmensaje).

Recordemos que los ports serie de Rabbit 2000 y 3000 no soportan bit de paridad ymúltiples bits de stop directamente en el hardware, pero estos bits pueden simularsecon drivers adecuados. En particular, las revisiones posteriores del R2000 (R2000Aen adelante) incorporan un registro denominado “long stop” que permite simplificarlos drivers para estas aplicaciones. Si bien Dynamic C soporta todas estascombinaciones de forma transparente, es interesante tener presente el hardware a lahora de observar los resultados:� Un byte escrito en el SxDR será transmitido en forma normal por la UART.� Un byte escrito en el SxAR será transmitido con el agregado de un noveno bit en

cero.� Para generar un noveno bit en uno, excepto en un R2000 original, se escribe el

byte a transmitir en el SxLR. En un R2000 original, se debe introducir unademora de un bit o más luego del bit de stop, antes de transmitir el siguientecaracter. Esto será interpretado por el receptor como un noveno bit en uno yposibles bits de stop adicionales.

Con esta información presente, observemos la sample Samples\pktdemo.c queutiliza la biblioteca de funciones packet.lib, la cual soporta el modo de señalizaciónpor noveno bit.

La clave para la operación está en la función pktXopen(), donde 'X' representa a lainterfaz que se desee utilizar. Leyendo atentamente el help de esta función, ya sea enel manual de referencia de Dynamic C o haciendo Help->Function lookup (o<Ctrl> H) en Dynamic C, con el cursor sobre la llamada a pktXopen() en la samplerecomendada, observamos lo siguiente (transcribimos la parte más importante):

PARAMETER1: Bits per second of data transfer: min 2400 PARAMETER2: mode - type of packet scheme used, options are:

PKT_GAPMODE PKT_9BITMODE PKT_CHARMODE

PARAMETER3: options - further specification for the packet scheme The value of this depends on the mode used.

9bit mode - type of 9bit protocol PKT_RABBITSTARTBYTE PKT_LOWSTARTBYTE PKT_HIGHSTARTBYTE

PARAMETER4: ptr to function that tests for completeness of a packet. Thefunction should return 1 if the packet is complete, or 0 ifmore data should be read in.

177

Page 196: El Camino Del Conejo

Conectividad

Esto quiere decir, que para poder utilizar noveno bit en modo señalización,debemos elegir el modo de trabajo: � PKT_RABBITSTARTBYTE: corresponde a un modo de trabajo en el cual el

primer elemento del mensaje tiene 9-bits y el resto 8-bits, esto permite señalizarel inicio de trama mediante un elemento de 9-bits con el noveno bit en cero(rompe la trama donde se esperaría un bit de stop), mientras que el resto de loselementos se envía en 8-bits, aumentando la tasa de transferencia, dado que no sedesperdicia un bit.

� PKT_LOWSTARTBYTE: el primer elemento tiene el noveno bit en cero, elresto de los elementos lo tiene en uno.

� PKT_HIGHSTARTBYTE: el primer elemento tiene el noveno bit en uno, elresto de los elementos lo tiene en cero.

Dejemos de lado el cuarto parámetro por el momento.

Reduciendo el problema a su mínima expresión, para poder transmitir tramas con laseñalización deseada y observarlas en un osciloscopio, quedaría:

#use packet.lib

packet_test(void *packet, int length){}

void main() { int i;

pktBopen(19200, PKT_9BITMODE, PKT_HIGHSTARTBYTE,packet_test); while (1) {

pktBsend("AT", 2, 0); for(i=0;i<3200;i++);

} }

nodebug void pktBinit(){} nodebug void pktBrx(){} nodebug void pktBtx(){}

Esto envía reiteradamente la secuencia 'AT'; la 'A' (0x41) con 9-bits, el noveno enestado alto (se ve como un stop bit adicional), y la 'T' (0x54) con 9 bits, el noveno enestado bajo. Observamos a la salida del micro:

178

01 0 0 0 0 01 1

STOPSTART START STOP

0 0 0 0 0 01 1 1

A T

noveno bit noveno bit

Page 197: El Camino Del Conejo

Comunicación serie asincrónica

Las funciones vacías son sólo eso (funciones vacías...), dado que no las usamos paraesta implementación, pero la biblioteca packet.lib() las necesita. En particular, las tres últimas permiten conmutar transceivers en RS-485, segúnviéramos en el libro introductorio.

Deberá tenerse presente que si nuestro procesador es Rabbit 2000 (marcado como"IQ2T"), esto se realiza por software y el noveno bit en uno lógico durará más de loesperado. Al fundirse con el bit de stop, lo que se observa es un espacio intercaractermás largo. Si nuestro procesador es R2000A o superior (marcado como"IQ3T","IQ4T","IQ5T") o R3000, ya dispone del SxLR (Long Stop Register) y latarea mencionada se realiza por hardware.

Para poder recibir, deberemos escribir una función dentro del cuerpo depacket_test() que valide lo recibido y tome la decisión de aceptar el paquete (retornael valor 1 ), o seguir recibiendo datos (retorna el valor 0 ). La sample quemencionamos tiene escrita una función de ese tipo, la cual puede servirnos de guía,para poder analizarla y modificarla según nuestras necesidades. Para detectar ladirección del mensaje y compararla con la nuestra, dentro de esta misma funcióndisponemos del segundo parámetro (en este caso length), el cual nos indica lacantidad de caracteres que hemos recibido hasta el momento, y por ende qué caracterdel mensaje estamos recibiendo.

Señalización por tiempo muerto

Como analizáramos en el apartado anterior, uno de los problemas más importantesa resolver a la hora de implementar un protocolo de comunicaciones, es la forma dedetectar el comienzo de un mensaje. Otro de los recursos que resuelven esto es laseñalización por tiempo muerto, en la cual la transmisión de datos se hace porpaquetes, dejando un tiempo muerto mínimo obligatorio entre paquetes. De estaforma, todos los dispositivos ajenos al diálogo pueden dormir hasta detectar untiempo muerto, esperar a recibir un byte (en lo posible también durmiendo),comparar con la propia dirección, y seguir durmiendo si no corresponde. Esteesquema es el empleado por protocolos como Modbus RTU.

Si bien es factible implementar esto manualmente utilizando timers y/o driversadecuados, la clave para la operación está una vez más en la función pktXopen() yademás en pktXsend() y su compañera cofunction cof_pktXsend(). En el help depktXopen() observamos lo siguiente (transcribimos la parte más importante):

PARAMETER1: Bits per second of data transfer: min 2400 PARAMETER2: mode - type of packet scheme used, options are:

PKT_GAPMODE PKT_9BITMODE PKT_CHARMODE

PARAMETER3: options - further specification for the packet scheme The value of this depends on the mode used.

179

Page 198: El Camino Del Conejo

Conectividad

gap mode - minimum gap size(in byte times)

PARAMETER4: ptr to function that tests for completeness of a packet. Thefunction should return 1 if the packet is complete, or 0 ifmore data should be read in. For gap mode the test function isnot used and should be set to NULL.

En el help de pktXsend() observamos lo siguiente (transcribimos la parte másimportante):

PARAMETER3: The number of byte times to delay before sending thedata (0-255) This is used to implement protocol-specificdelays between packets

La función pktXopen() define el modo de trabajo para la detección de mensajes enrecepción, mientras que pktXsend() es la que define la demora entre mensajesenviados. La demora insertada es siempre algo mayor, por lo menos un tiempo decaracter más, lo cual nos garantiza que el receptor remoto detectará el espacio entretramas.

El ejemplo siguiente muestra como enviar tramas con un tiempo muerto mínimo deal menos tres bytes entre ellas:

#use packet.lib

packet_test(void *packet, int length){}

void main() { int i;

pktBopen(2400, PKT_GAPMODE, 3,NULL); // para recepción, al menos 3 byteswhile (1) {

pktBsend("AT", 2, 3); // para transmisión, al menos 3 bytes}

}

nodebug void pktBinit(){} nodebug void pktBrx(){} nodebug void pktBtx(){}

Las funciones vacías siguen siendo funciones vacías, dado que no las usamos paraesta implementación, pero la biblioteca packet.lib() las necesita. Las tres últimaspermiten conmutar transceivers en RS-485, como comentáramos en el apartadoanterior.En recepción, la biblioteca de funciones identificará el comienzo de un mensaje al

detectar transmisión luego del tiempo muerto configurado. De un modo similardetectará la finalización del mismo y lo colocará en el buffer de recepción. De allípodremos extraerlo mediante la función pktXreceive(). Dado que no intervenimos enel proceso de recolección del paquete, la validación de la dirección la deberemoshacer una vez recibido éste.

180

Page 199: El Camino Del Conejo

Comunicación serie asincrónica

Señalización por caracter especial

Otro de los recursos para detectar el comienzo de un mensaje es la señalización porcaracter especial, en la cual la transmisión de datos se hace utilizando un sistema decodificación tal que sea imposible duplicar este caracter o secuencia de caracteresespeciales dentro del flujo normal de datos. De esta forma, todos los dispositivosajenos al diálogo pueden dormir hasta detectar este caracter especial, recibir ladirección (a quién se manda el mensaje), comparar con la propia, y seguir durmiendosi no corresponde. Este esquema es el empleado por protocolos como ModbusASCII, que envía los datos utilizando dos caracteres por byte ('0' y 'D' para 0x0D) yutiliza caracteres simples para indicar el comienzo y fin de mensaje (':' para elcomienzo y 0x0D 0x0A para el final).

La clave para simplificar la operación de recepción está una vez más en la funciónpktXopen(), en cuyo help observamos lo siguiente (transcribimos la parte másimportante):

PARAMETER1: Bits per second of data transfer: min 2400 PARAMETER2: mode - type of packet scheme used, options are:

PKT_GAPMODE PKT_9BITMODE PKT_CHARMODE

PARAMETER3: options - further specification for the packet scheme The value of this depends on the mode used.

char mode - character marking start of packet

PARAMETER4: ptr to function that tests for completeness of a packet. Thefunction should return 1 if the packet is complete, or 0 ifmore data should be read in. For gap mode the test function isnot used and should be set to NULL.

Para la transmisión, deberemos nosotros introducir el caracter especial al principiode la trama, dado que la presencia de PKT_CHARMODE no realiza ningún tipo demodificación o inserción de lo que se desea transmitir. El receptor, en cambio,comienza a recibir los mensajes cuando detecta el caracter ':' y termina cuando lafunción packet_test() indica fin de paquete (retorna el valor 1 ). El mensaje asíidentificado es colocado en el buffer de recepción, de donde puede extraersemediante la función pktXreceive(). Para detectar la dirección del mensaje ycompararla con la nuestra, dentro de packet_test() disponemos del segundoparámetro, el cual nos indica qué caracter estamos recibiendo.La sample Samples\RCM3000\IR_DEMO.C muestra un esqueleto como para

implementar Modbus ASCII utilizando este esquema.

181

Page 200: El Camino Del Conejo

Conectividad

IrDA

IrDA es el nombre de la asociación que define el stack de protocolos para WPAN(Wireless Personal Area Network) utilizando comunicación por haz infrarrojo.La base, es decir, el enlace físico, es el IrPHY, que define los parámetros ópticos de

la interfaz, velocidades y rango de distancias de operación. Debido a que altransmitir, es altamente probable que el LED transmisor ciegue al fotodiodo (oequivalente) receptor, se opera en half-duplex. La velocidad de operación dependedel modo; por ejemplo SIR (Serial InfraRed) cubre la gama de velocidadesnormalmente encontradas en un enlace serie RS-232, mientras que modos develocidades más altas cubren hasta el orden de varios Mbps. Cada modo codifica losbits en pulsos lumínicos de diferente forma.

El segundo nivel es IrLAP (Infrared Link Access Protocol), que se encarga dedescubrir posibles interlocutores y establecer un enlace confiable. Dentro de esteprotocolo, se establece un master y uno o más slaves, de modo que los slaves sólopueden transmitir cuando el master se lo indica.

En el tercer nivel se ubica IrLMP (Infrared Link Management Protocol), queprovee las diversas conexiones virtuales necesarias. Sobre este stack de tresprotocolos corre generalmente TTP (Tiny Transport Protocol), que sirve de sustentoa protocolos más complejos como IrOBEX (también conocido como OBEX), paraintercambio de objetos, IrLAN, que permite acceder a una LAN, y algunos modosde IrCOMM, que permite simular puertos de comunicaciones.

A partir del Rabbit 3000, es posible configurar las UARTs para poder operar en elmodo de señalización que SIR requiere. Al momento de escribir este texto, Rabbitincluye transceivers con soporte para SIR en algunos de los kits de desarrollo. Sibien nada impide al usuario final implementar todo el stack de protocolos por sucuenta, el material provisto por Rabbit utiliza los transceivers como un cable virtual,enviando los datos por SIR como si los kits estuvieran interconectados por un cable.Para habilitar el SIR encoding en las UARTs del R3000 y superiores, debe setearse

el bit 4 del registro de control extendido de la UART correspondiente. Por ejemplo,para el puerto A:

WrPortI(SAER, NULL, 0x10);

Una alternativa a escribir todo el stack es utilizar un stack de terceros o uncontrolador como el MCP2140, que provee soporte IrComm a 9600bps. Sinembargo, no hemos experimentado sobre este tema.

182

Page 201: El Camino Del Conejo

GSM

GSM

GSM (Global System for Mobile communications) es el standard corriente enmateria de comunicaciones celulares. Si bien existen otras alternativas, laversatilidad y existencia de standards e información hacen sumamente interesante aesta tecnología para la interconexión de dispositivos inteligentes.

Los dispositivos que emplearemos son generalmente modems GSM, los cualesmanejarán la complejidad de la red GSM y serán controlados mediante una interfazrelativamente simple, generalmente extensiones GSM standard y propietarias a loscomandos AT7.

Sin entrar dentro del tema de networking, incluimos aquí una breve descripción delas formas más prácticas y comunes de obtener conectividad en una red GSM.Analizaremos aquí lo que es más apropiado para este entorno; veremos en el capítulosobre networking lo relativo a esta disciplina.

CSD

CSD (Circuit Switched Data) es la forma más antigua de comunicación de datosdentro de GSM.En CSD, se establece una conexión a un determinado ancho de banda, y se reserva

ese ancho de banda por el tiempo de duración de la conexión, más allá de si haytráfico o no. Es prácticamente equivalente a realizar una llamada telefónica con unmodem, y de hecho algunas compañías prestatarias del servicio incluyen la opciónde conectarse a modems en la PSTN (Public Switched Telephone Network, la redpública telefónica) desde dispositivos en la red GSM, además de la conexión entredos dispositivos de la red GSM. En lo que a nosotros como developers nosconcierne, el costo es alto debido a que generalmente se tarifa por tiempo deconexión, y las velocidades de comunicación no suelen ser altas. Los tiempos delatencia son relativamente bajos, dado que existe una conexión virtual establecida.La red reserva una determinada cantidad de timeslots para la comunicación, haya ono tráfico.El modem GSM nos permite establecer una conexión al destino solicitado y cursar

datos por la misma; por lo general, el inicio de la conexión se realiza de formasimilar a un modem telefónico, ya sea originando una conexión saliente o atendiendouna conexión entrante. Por este motivo, cualesquiera de los métodos de conectividadnormalmente empleados en un puerto serie asincrónico (siempre y cuandotrabajemos con 8-bits) puede emplearse con un modem y por ende con un modemGSM en una comunicación CSD.

7 Los comandos AT, también conocidos como comandos Hayes, son un set de comandos paraconfiguración y control de modems. Su nombre común proviene del hecho de que todo comandocomienza con AT, lo cual originalmente servía para que el modem pudiera detectar velocidad, largode palabra y paridad, cuando el usuario llamaba su ATención (attention).

183

Page 202: El Camino Del Conejo

Conectividad

ATD<número remoto>

CONNECTNO CARRIER

RINGATA

CONNECTNO CARRIER

Existe una pequeña salvedad; debido al hecho de que quien provee la funcionalidadde modem telefónico8, al conectarse con la PSTN, es la empresa prestataria delservicio de telefonía celular. Si la llamada se origina en una línea de abonado comúnde la PSTN, ésta no tiene forma de señalizar a la red GSM que la llamada es dedatos, por lo que se cursa como una llamada de voz.

SMS

SMS (Short Message Service) permite enviar y recibir mensajes cortos dentro de lared GSM. De esta forma, es posible enviar un mensaje a un dispositivo remoto sinque éste tenga que estar conectado permanentemente a la Internet, ni esperar a queinicie una conexión, ni llamarlo vía CSD. De igual modo, el dispositivo remotopuede reportar lo que necesite sin necesidad de conectarse. SMS es tal vez la opciónmás económica para sistemas que requieren muy bajo volumen de tráfico, y dadoque todo lo relativo a la transferencia de los mensajes es manejada por el modem y lared GSM, el dispositivo no requiere mayor inteligencia ni stack de protocolos. La

8 Modulación de una portadora de audio con la información digital proveniente del modem GSM ydemodulación de una señal de audio ingresando los datos obtenidos a la red GSM

184

red GSMPSTN

Serie Asinc

Serie Asinc

Serie Asinc

CSD

Módulo GSM

Modem

Funcionalidad de

sólo voz

Módulo GSM

Equipo remoto

Equipo remoto

PCM

Modem telef´onico

SeñalAnalógica

Page 203: El Camino Del Conejo

GSM

desventaja viene de la mano de la carencia de una certeza en el tiempo de entrega (eincluso de la entrega misma) de la información, lo que puede demorar desdemilisegundos hasta horas.

Módulos SIMCOM

La operatoria podrá diferir de acuerdo al modem GSM que se utilice. Resumimoslos pasos esenciales para poder enviar y recibir mensajes de texto SMS, utilizandomódulos de la empresa SIMCOM como por ejemplo SIM200 o SIM340.

Selección del formato

Se realiza mediante el comando AT+CMGF, eligiendo modo texto o modo PDU. Alos fines prácticos, el modo texto es mucho más fácil de entender por un ser humano,y no requiere una elevada comprensión de los formatos involucrados, siendoigualmente simple de procesar por un micro:

AT+CMGF=1

Envío de mensajes

Para enviar un mensaje a un número determinado, ingresamos el comandoAT+CMGS="<phonenumber>", a lo cual recibiremos un prompt (caracter '>'), paraluego ingresar el mensaje, el que terminaremos con un <Ctrl-Z> (ASCII SUB,0x1A). El listado siguiente muestra la secuencia de envío del mensaje, los caracteresresaltados son los que enviamos nosotros, los caracteres ASCII no imprimiblesfiguran con su nombre entre < >. Por claridad, se han omitido los <CR> y <LF>.

AT+CMGS="55555555"

> cuerpo del mensaje<SUB>+CMGS: 5OK

La respuesta al comando es el ID del mensaje, terminado con el clásico OK.

Recepción de mensajes

Al recibir un SMS, el módulo GSM lo informa mediante un mensaje:+CMTI:"SM",<index>, de modo que para leerlo simplemente debemos ingresar el comandoAT+CMGR=<index>. El listado siguiente muestra la secuencia de recepción delmensaje, los caracteres resaltados son los que enviamos nosotros, los caracteresASCII no imprimibles figuran con su nombre entre < >. Por claridad, se han omitidolos <CR> y <LF>.

+CMTI: "SM",1AT+CMGR=1+CMGR: "REC UNREAD","+541155555555",,"06/01/04,14:43:03+00"cuerpo del mensaje

185

Page 204: El Camino Del Conejo

Conectividad

OK

Como se ve, la aparición de la indicación es "no solicitada". Si esto es una molestia,se debe cancelar su aparición mediante el comando AT+CNMI. La forma más simplees AT+CNMI=2,0,0,0,0. De esta forma, debemos interrogar al módulo sobre lapresencia de un SMS, mediante el comando AT+CMGL, que no sólo indica lapresencia de mensajes sino que además los lista (y por consiguiente los marca comoleídos). El listado siguiente muestra la secuencia de recepción del mensaje, loscaracteres resaltados son los que enviamos nosotros, los caracteres ASCII noimprimibles figuran con su nombre entre < >. Por claridad, se han omitido los <CR>y <LF>.

AT+CNMI=2,0,0,0,0

OKAT+CMGL="REC UNREAD"+CMGL: 2,"REC UNREAD","+541155555555",,"06/01/04,14:50:22+00"cuerpo del otro mensajeOK

De no existir ningún mensaje nuevo, la respuesta al comando AT+CMGL essimplemente un OK:

AT+CMGL="REC UNREAD"

OK

Cualquiera fuere el método empleado, el mensaje permanece en memoria despuésde leído, podemos comprobarlo pidiendo los mensajes ya leídos:

AT+CMGL="REC READ"+CMGL: 1,"REC READ","+541155555555",,"06/01/04,14:43:03+00"cuerpo del mensaje+CMGL: 2,"REC READ","+541155555555",,"06/01/04,14:50:22+00"cuerpo del otro mensajeOK

Como puede observarse, ambos mensajes están en memoria. Para borrarlos,utilizamos el comando AT+CMGD, como muestra el ejemplo:

AT+CMGD=1OKAT+CMGD=2

OK

Ejemplos

El ejemplo siguiente muestra la forma de obtener los mensajes SMS con un modemde SIMCOM. A modo académico, luego de cada comando mostramos la respuesta

186

Page 205: El Camino Del Conejo

GSM

del modem, que dado que no hemos inhabilitado el eco local, incluirá el comandomismo:

#define BOUTBUFSIZE 127#define BINBUFSIZE 127

static char buffer[1000];

int main(){int count;char *ptr,*strptr,*auxptr;

serBopen(115200); serBputs("AT+CMGF=1\r\n"); while(serBwrFree()!=BOUTBUFSIZE); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); buffer[count]=0; puts(buffer); serBputs("AT+CNMI=2,0,0,0,0\r\n"); while(serBwrFree()!=BOUTBUFSIZE); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); buffer[count]=0; puts(buffer);

serBputs("AT+CMGL=\"REC UNREAD\"\r\n"); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); buffer[count]=0; printf(buffer); ptr=strstr(buffer,"\n+CMGL:"); while(ptr){ count=atoi(ptr+=7); if(count){

strptr=strchr(ptr,'\n')+1;if(ptr=strstr(strptr,"\n+CMGL:")) *ptr=0;else if(auxptr=strstr(strptr,"\nOK")) *auxptr=0;printf("\nMensaje #%d: %s\n",count,strptr);sprintf(buffer,"AT+CMGD=%d\r\n",count);serBputs(buffer);while(!(count=serBread(buffer,sizeof(buffer)-1,300)));buffer[count]=0;printf(buffer);

} }

serBclose();}

Para evitar los loops y chequear periódicamente los mensajes, es conveniente pasara una forma más compatible con el desarrollo de otras tareas. Aprovechando lasfacilidades de Dynamic C, empleamos costates y cofunctions:

#define BOUTBUFSIZE 127#define BINBUFSIZE 127

static char buffer[1000];

int main()

187

Page 206: El Camino Del Conejo

Conectividad

{int count;char *ptr,*strptr,*auxptr;

serBopen(115200); serBputs("AT+CMGF=1\r\n"); while(serBwrFree()!=BOUTBUFSIZE); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); serBputs("AT+CNMI=2,0,0,0,0\r\n"); while(serBwrFree()!=BOUTBUFSIZE); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); while(1){

costate{ wfd cof_serBputs("AT+CMGL=\"REC UNREAD\"\r\n"); do{

wfd count=cof_serBread(buffer,sizeof(buffer)-1,300); } while(!count); buffer[count]=0; ptr=strstr(buffer,"\n+CMGL:"); while(ptr){ count=atoi(ptr+=7); if(count){

strptr=strchr(ptr,'\n')+1;if(ptr=strstr(strptr,"\n+CMGL:")) *ptr=0;else if(auxptr=strstr(strptr,"\nOK")) *auxptr=0;printf("\nMensaje #%d: %s\n",count,strptr);sprintf(buffer,"AT+CMGD=%d\r\n",count);wfd cof_serBputs(buffer);do{

wfd count=cof_serBread(buffer,sizeof(buffer)-1,300);} while(!count);

} }

waitfor(DelaySec(30));}

} serBclose();}

Más allá de la paupérrima utilidad de este voluminoso y poco portátil sistema derecolección de SMS, la idea general es poder usar el SMS para encapsular comandosde configuración o lo que fuere, de igual forma que como emplearíamos, porejemplo, emails.Para enviar mensajes, la operatoria es muy simple. Una vez configurado el modo de

trabajo con el comando AT+CMGF, simplemente hay que enviarAT+CMGS="<número de destino>" y a continuación el texto del SMS, terminadopor 0x1A (que en octal es 032, si queremos incluirlo dentro del string a enviar):

#define BOUTBUFSIZE 127#define BINBUFSIZE 127

static char buffer[1000];

int main(){int count;char *ptr,*strptr,*auxptr;

188

Page 207: El Camino Del Conejo

GSM

serBopen(115200); serBputs("AT+CMGF=1\r\n"); while(serBwrFree()!=BOUTBUFSIZE); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); buffer[count]=0; puts(buffer);

serBputs("AT+CMGS=\"12345678\"\r\n"); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); buffer[count]=0; printf(buffer); serBputs("Este es un mensaje SMS\032"); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); // eco buffer[count]=0; printf(buffer); while(!(count=serBread(buffer,sizeof(buffer)-1,300))); // net confirm buffer[count]=0; printf(buffer);

serBclose();}

GPRS

GPRS (General Packet Radio Service) es dentro de la arquitectura GSM eltransporte de datos que sucede a CSD, y permite transportar la información por loscanales no utilizados, sin reservar ancho de banda en la red.Lo que nos interesa, desde el punto de vista de la conectividad, es IP sobre GPRS;

en el que el modem GSM provee el stack TCP/IP. Si bien esto no es particularmenteinteresante en dispositivos como Rabbit que tienen su propio stack TCP/IP y soportePPP, el costo total más el de desarrollo (PPP para Rabbit es un módulo que debecomprarse por separado) y/o la ocupación de memoria pueden llegar a favorecer estetipo de implementación, razón por la cual haremos una breve descripción aquí. Lascaracterísticas fundamentales de GPRS y el transporte de éste y otros protocolossobre GPRS, se detallan en el capítulo sobre networking.

Módulos SIMCOM

El ejemplo siguiente muestra un simple sistema en breves y cortos pasos queaprovecha las extensiones de SIMCOM para poder enviar y recibir datos mediantesus módulos SIMxxx, ya sea vía TCP o UDP, de una forma rápida y sencilla, sinpolling ni máquinas de estados:

1. Selección de APN: La selección del APN se realiza mediante el comandoAT+CSTT, según cuál sea nuestro proveedor, deberemos ingresar el APN queéste defina:

AT+CSTT=1,"IP","<APN definido por el proveedor>"

2. Conexión a la red GPRS: mediante el comando AT+CIICR

189

Page 208: El Camino Del Conejo

Conectividad

3. Solicitud de dirección IP: mediante el comando AT+CIFSR, el cual, luego de untiempo, devuelve la dirección IP obtenida.

4. Establecimiento de la conexión con el sitio remoto: sea TCP o UDP elprotocolo a emplear, se debe "realizar una conexión". En el caso de TCP, esnecesario, en el caso de UDP, mantiene un estado de conexión interno paraaceptar datagramas del destino solicitado. La conexión se establece mediante elcomando AT+CIPSTART, que devuelve el mensaje CONNECT OK cuando laconexión se establece (TCP) o inmediatamente (UDP). El comando tiene laforma AT+CIPSTART="protocolo","dirección IP","port", por ejemplo:

AT+CIPSTART="UDP","200.114.232.92","2020"5. Envío de datos: Indicamos al módulo que queremos enviar datos mediante el

comando AT+CIPSEND. Podemos simplemente enviar AT+CIPSEND y recibirun prompt, lo que nos permite enviar los datos y terminarlos con <CTRL-Z>, obien AT+CIPSEND=<longitud> y luego los datos sin terminador. El módulo noscontesta SEND OK al realizar la operación

6. Recepción de datos: cualquier dato que el extremo remoto nos envíe, aparecerápor la interfaz como si fuera una respuesta del SIM200

7. Finalización de la conexión: mediante el comando AT+CIPCLOSE.8. Cesión de la dirección IP: Una vez terminada la sesión, cedemos la dirección IP

para que el sistema la pueda asignar a otro móvil, mediante el comandoAT+CIPSHUT

A continuación, un ejemplo con una prestataria argentina. En el mismo resaltamos

los comandos enviados para diferenciarlos de las respuestas del módulo, y omitimosel eco local, el cual puede eliminarse mediante el comando ATE0, standard del set decomandos Hayes (AT). Los caracteres ASCII no imprimibles figuran con su nombreentre < >:

AT+CSTT="internet.ctimovil.com.ar","gprs","gprs"<CR>

<CR><LF>OK<CR><LF>AT+CIICR<CR>

<CR><LF>OK<CR><LF>AT+CIFSR<CR>

<CR><LF>170.51.251.112<CR><LF>AT+CIPSTART="UDP","200.114.232.92","2020"<CR>

<CR><LF>CONNECT OK<CR><LF><CR><LF><CR><LF>OK<CR><LF>AT+CIPSEND<CR>

<CR><LF>> Este es el cuerpo de mi mensaje UDP<SUB><CR>

190

Page 209: El Camino Del Conejo

GSM

<CR><LF>SEND OK<CR><LF>De este modo aparecería cualquier respuesta del servidor remotoAT+CIPCLOSE<CR>

<CR><LF>OK<CR><LF>AT+CIPSHUT<CR>

<CR><LF>OK<CR><LF>

Para poder realizar esto con un micro, deberemos ir siguiendo paso a paso lacomunicación, verificando que el modem conteste lo que necesitamos que conteste,abortando en caso que no ocurra así.

Como se ve, una vez inicializado el modem, obtenida una dirección IP, yestablecido el socket (UDP) o conexión (TCP), la comunicación se reduce a enviarel mensaje luego del comando AT+CIPSEND, y esperar la respuesta luego delSEND OK del modem GSM.

Modo transparente

En modo transparente, el modem GSM se comporta como cualquier modemtelefónico en cuanto una vez "establecida la comunicación" (abierto el socket), todolo que ingresa por el puerto serie es enviado al socket y lo que se recibe de éste esenviado por el puerto serie. Esto nos permite olvidarnos del empaquetado y dediferenciar respuestas remotas y respuestas del módulo, pero no podemos ingresarcomandos AT sin antes escapar a modo comando, lo cual se realiza mediante latradicional secuencia +++9, o poner DTR inactiva10. Para volver al modotransparente se utiliza el comando ATO, y para cortar ATH0 o simplemente ATH, deigual modo que en una llamada CSD.

Para utilizar uno u otro modo (transparente o normal), lo indicamos mediante elcomando AT+CIPMODE, donde AT+CIPMODE=1 selecciona el modotransparente. La forma de trabajo es similar a lo que estudiaremos en el capítulosobre transporte de datos serie vía TCP/UDP, por lo que permite una ciertaconfiguración para optimizarlo a determinadas particularidades de funcionamiento,lo que se realiza mediante el comando AT+CIPCCFG.

9 Dicha secuencia debe ser precedida y sucedida de un silencio de al menos 500ms. En modotransparente no debe haber un espacio mayor a 20ms entre dichos caracteres.

10 Requiere que se haya habilitado con AT&D1

191

Page 210: El Camino Del Conejo
Page 211: El Camino Del Conejo

File Systems

Introducción

Un file-system es la estructura que nos permite acceder a datos en un medio dealmacenamiento masivo, de una forma ordenada y establecida. Existen muchos deellos, cada uno con sus ventajas y desventajas, sus adeptos y sus críticos. Desde el

punto de vista de los sistemas dedicados, un file-system puede llegar a ser unelefante con pocas probabilidades de caber en nuestro automóvil, a menos que loelijamos cuidadosamente. Dynamic C incorpora el siguiente soporte, al momento deescribir este libro:

� para un file-system propietario destinado a funcionar sobre memorias flashde nombre muy original: Flash File System, o cariñosamente FS, el cual

subsiste en su segunda encarnación como FS2. Esto es sólo para Rabbit2000 y 3000.

� para el archi-conocido FAT.

El FS2 se origina en la necesidad de manejar remotamente servidores FTP y HTTP,por ejemplo, es decir, al incorporar al concepto de archivo el material a servir, es

posible administrarlo como una unidad lógica, concepto de más alto nivel ymanejabilidad que "los 758 bytes que empiezan en la posición 0x1FF32",particularmente cuando el nuevo contenido no tiene 758 bytes y hay que empezar adesplazar el resto...El sistema de archivos conocido como FAT resulta portado a Dynamic C como

consecuencia de la introducción de medios de almacenamiento realmente masivo

(bueno, al menos hoy) como las flash seriales y las flash cards. Si bien FAT distamucho de ser óptimo, es lo suficientemente simple como para una buenaimplementación en sistemas dedicados, dando una mayor flexibilidad de uso queFS2.

FS2

El FS2 es relativamente simple, si bien su utilización puede presentar un ciertogrado de complejidad, debido a lo complicado de la presentación de la información

al respecto, y la nomenclatura empleada. El file-system puede funcionar tanto enflash como en RAM, ya sea con o sin battery back-up (aunque tal vez su utilizaciónsea algo limitada en este último caso). La única condición es que el soporte físico

193

Page 212: El Camino Del Conejo

File Systems

esté mapeado en memoria, es decir, conectado a los buses de direcciones y datos. Setrata de un file-system limitado, sin directorios, pero que permite trabajar sobremúltiples particiones en el mismo o en diversos soportes físicos, cada una condiferentes estructuras lógicas posibles.

Elección del soporte físico

Antes que nada, siquiera de pensar en utilizar el FS2, deberemos elegir el soportefísico. Como dijéramos, FS2 puede funcionar tanto en flash como en RAM. Si

vamos a utilizarlo en RAM, deberemos reservar un área de memoria; si vamos autilizarlo en flash, también, pero según qué tipo de módulo estemos utilizando,puede que nos encontremos con que ya hay un área reservada. En efecto, móduloscomo el RCM2100 se proveen con dos chips de flash de 256KB cada uno, y en estecaso, el compilador asigna uno de los chips para código y el otro para el FS2, por loque ya disponemos de un área de 256K para el FS2, sin siquiera desearlo.

Si por el contrario, el módulo que empleamos tiene solamente un chip de flash,deberemos reservar el espacio en flash, diciéndole al compilador que parte de lamemoria flash está reservada, que no debe utilizarla para poner código allí, yademás, cuánto de esa área reservada se empleará para el FS2. La primera tarea serealiza modificando el BIOS, según puede apreciarse en el párrafo correspondientede este capítulo. La segunda, se realiza definiendo la macro

FS2_USE_PROGRAM_FLASH, con un valor válido en KB, por ejemplo:

#define FS2_USE_PROGRAM_FLASH 16 // para 16KB de FS2 en// flash de programa

Esta macro modifica la operación de la library de FS2, y debe definirse antes deincluir el código de la misma, mediante:

#use "fs2.lib"

Para poder utilizar el FS2 en RAM, deberemos modificar el BIOS reservando unárea para éste, según puede apreciarse en el párrafo correspondiente. De más estádecir que si queremos que el contenido de la RAM se mantenga al sacar laalimentación, deberemos conectar una pila de backup, caso contrario, deberemos

formatear el file-system cada vez que iniciemos el sistema. Un FS2 volátil en RAMpuede servir para alojar archivos temporarios, que serán volcados a flash (copiados aotro "disco") cuando sean considerados definitivos.

Una vez que hayamos decidido el soporte físico a utilizar, emplearemos la llamadaa función que corresponda para el mismo, cuando debamos referirnos a dicho

soporte físico. Resulta que la segunda flash, si existe, es la preferida; mientras que laflash principal, la que aloja el programa, es la "otra" flash, desde el punto de vistadel FS2; a menos que haya sólo una flash, con lo cual no hay "otra". Así, lasllamadas a función correspondientes son:

194

Page 213: El Camino Del Conejo

FS2

fs_get_flash_lx(); // usa la flash "preferida": la flash// secundaria, totalmente asignada para// FS2, o la porción de flash de// programa asignada para FS2 si no hay// una flash secundaria

fs_get_other_lx(); // usa la porción de flash de programa// asignada para FS2 si hay una flash// secundaria, totalmente asignada a FS2

fs_get_ram_lx(); // usa la porción de RAM asignada// para FS2

Es posible tener más de un soporte físico, es decir, podemos tener FS2 en RAM yflash, accediendo a cada uno de ellos mediante la llamada a función correspondiente.Hasta ahora evitamos hacer la analogía del soporte físico con un disco de una

computadora con un sistema tipo CP/M1 o MS-DOS, pero la misma es evidente.Cada medio puede ser empleado por separado, y varios pueden emplearse a la vez,guardando su independencia (no es posible "montarlos" en una raíz común como en

Unix). A su vez, pueden tener divisiones (particiones), siendo cada una de éstas unente lógico independiente. La nomenclatura empleada por Rabbit denomina LX(Logic eXtension) a cada unidad, que en otros ambientes llamamos familiarmenteparticiones, o en el ambiente MS-DOS y derivados, unidades lógicas.

Partición y formateo

Primeramente recordemos que estamos hablando de un microprocesador, y que porlo general estamos desarrollando un sistema dedicado, por lo que a menos que lo que

se esté desarrollando sea una computadora basada en Rabbit con su propio sistemaoperativo (de la cual quisiera recibir una unidad, por curiosidad), la partición y elformateo no son eventos que se realizan tal vez más de una vez, posiblemente elformateo pueda ser requerido alguna vez adicional a la inicialización del equipo. Si,como es de esperar, el formateo no es una actividad necesaria en el equipoterminado, es preferible cargar algún programa aparte al momento de inicializar el

equipo en vez de incluir soporte en el programa de aplicación. Particularmente, unaflash totalmente borrada será reconocida como una partición o unidad lógica (unLX) correctamente formateada.Para partir un LX (incluso uno que ya haya sido partido), emplearemos la función

fs_setup(). Esta función es algo así como una guillotina, que divide el LXespecificado en dos partes. La primera parte conserva el número de identificación,

mientras que la segunda, obviamente, obtiene una identificación nueva, la cual es elvalor devuelto por la función. Es decir, si llamamos a fs_setup() confs_get_flash_lx() como parámetro, partiremos la flash secundaria (destinada en su

1 Para los más jóvenes, CP/M (Control Program for Microprocessors) es un sistema operativo basadoen 8080, luego difundido sobre Z80, con funciones elementales de DOS (Disk Operating System).De hecho booteaba de disco flexible e incorporaba un BIOS que el desarrollador modificaba paraportarlo al hardware de su desarrollo.

195

Page 214: El Camino Del Conejo

File Systems

totalidad para el FS2) en dos, obteniendo el ID de la segunda porción. Si luegovolvemos a llamar a esta función con el ID obtenido, lograremos partir la segundaporción en dos partes, logrando efectivamente tres particiones; procedimiento quepuede ser repetido hasta el hartazgo o hasta que se agote el espacio de memoria,según qué ocurra primero. Como este tema es un poco más complicado, loanalizaremos con más detalle más adelante.

Para poder formatear un LX, deberemos, luego de inicializar el soporte para el file-system (cosa que veremos en el párrafo siguiente), llamar a la funcióncorrespondiente:

fs_init(0,0); // inicializa estructuras de FS2lx_format(fs_get_flash_lx(), 0); // formatea "unidad"

Como puede observarse, el primer parámetro es el identificador de LX, es decir, la

partición o "unidad lógica"; en este caso el resultado de la función que devuelve elLX de la flash destinada al FS2. El segundo parámetro da el grado de optimizaciónsugerido para minimizar el "desgaste" de la flash (wear level).

Utilización normal

Antes que nada deberemos inicializar las estructuras en memoria que operan sobreel file-system, lo cual se realiza mediante el llamado a la función fs_init(). La mismaposee dos parámetros, que deberán pasarse como NULLs, debido a que los mismos

solamente existen por compatibilidad con versiones anteriores. Esta funcióndetermina de cuantos LX dispone y realiza un chequeo de consistencia de lasparticiones, por lo que suele demorar un tiempo. Esto no debería ser crítico ya quesólo debe llamarse al momento de inicializar el sistema.A los fines de una utilización simple del file-system, supongamos que tenemos

solamente un LX, o que vamos a trabajar sobre el primero disponible, por

simplicidad. Digamos algo así como que trabajamos en nuestro home directory enUnix o el directorio raíz en MS-DOS o CP/M. Dentro de este contexto, lasoperaciones de archivos se realizan de forma similar a como se opera normalmenteen C con archivos: especificando un file handle a las diversas llamadas a funciónpara abrir, buscar posición, leer, escribir, y cerrar el archivo. Las funciones retornanlos valores usuales, que permiten detectar errores, fin de archivo, etc.

File file;

Las diferencias principales son debidas al contexto en el cual trabajamos, losnombres de archivo son en realidad números de 1 a 255, y la forma de abrir unarchivo para escritura es más rudimentaria, debiendo crearse si no existe y abrirse

sólo si ya existe:

if(fopen_wr(&file,filenumber) && fcreate(&file,filenumber)) // hubo un error

else

196

Page 215: El Camino Del Conejo

FS2

// 'filenumber' fue abierto o creado para escritura

Una adición interesante es una función que crea un archivo con un nuevo"nombre":

fcreate_unused(&file);

Para escribir en el archivo creado, disponemos de:

fwrite(&file, buffer, longitud); // buffer=puntero al área de datos

Como siempre, debemos cerrar el archivo:

fclose(&file);

Para movernos dentro del archivo, podemos usar:

fseek(&file, offset, SEEK_SET); // 'offset' bytes desde iniciofseek(&file, offset, SEEK_CUR); // 'offset' bytes de posición actualfseek(&file, -offset, SEEK_END); // '-offset' bytes de fin de archivo

Para saber en qué posición estamos:

ftell(&file);

Otra adición interesante es la posibilidad de borrar una cantidad de datos delprincipio de un archivo, de modo de mantener, por ejemplo, un log de una longitudfija, escribiendo al final y borrando al principio (datos más viejos):

fshift(&file, bytes, NULL); // borra 'bytes' bytes a la cabezafshift(&file, bytes, buffer); // copia los datos borrados en buffer

Para leer información de un archivo así abierto, utilizamos la función de lectura. Noes necesario abrirlo como lectura:

fread(&file, buffer, bytes); // buffer=puntero al área de datos

Si deseamos abrir un archivo en modo lectura solamente, la llamada será:

fopen_rd(&file,filenumber);

Finalmente, para deshacernos de un archivo, lo borramos:

fdelete(filenumber);

Modificación del BIOS

Si tenemos un módulo con dos chips de memoria flash, el BIOS reserva el segundochip para alojar el Flash File System, por lo que no es necesario realizarmodificación alguna. Un ejemplo de módulo con dos chips de flash es el RCM2100.Si por el contrario, el módulo tiene una sola flash, y necesitamos utilizar el FS2,

muy probablemente tengamos que modificar el BIOS, reservando un espacio en flash

197

Page 216: El Camino Del Conejo

File Systems

para alojar el File System. La documentación necesaria está en las mismasbibliotecas de funciones, en la ayuda on-line, y en el libro introductorio de estemismo autor. No obstante, haremos aquí un pequeño comentario a modo de ejemplo:como el RCM2200 tiene un solo chip de flash, es necesario dejar reservado unespacio en ese único chip de flash para que Dynamic C no lo use para código ypueda allí alojarse el FS2. Deberemos entonces modificar la macro

XMEM_RESERVE_SIZE en el BIOS, para reservar el espacio necesario en flash

para alojar el FS2:

En Bios\Rabbitbios.c:

//***** File System Information*******************************************#define XMEM_RESERVE_SIZE 0x0000L // Amount of space in the first flash to // reserve. Dynamic C will not use this // much flash for xmemory code space. // This allows the filesystem to be used on // the first flash.

Modificamos entonces esa línea de acuerdo al tamaño deseado, dejando en loposible un poco de más. El valor debe ser, además, múltiplo de 4096; por ejemplo,para 32KB:

#define XMEM_RESERVE_SIZE 0x9000L // Amount of space in the first flash

Para poder utilizar el FS2 en RAM, deberemos modificar el BIOS de forma similar,

y la línea a modificar está a continuación de la anterior:

#define FS2_RAM_RESERVE 0 // Number of 4096-byte blocks to reserve for // filesystem II battery-backed RAM extent. // Leave this at zero if not using RAM // filesystem.

Modificamos entonces esa línea de acuerdo al tamaño deseado, por ejemplo 32KB:

#define FS2_RAM_RESERVE 8 // Number of 4096-byte blocks to reserve for

Ejemplos

Algunos de los siguientes ejemplos utilizan la variable shared SEC_TIMER paralogging, la misma corresponde a fecha y hora correctas si alguna vez fue seteado elRTC y no se removió la alimentación de back-up.En lo posible, trataremos de imprimir los códigos de error retornados. El significadode los mismos puede obtenerse en ERRNO.LIB, ubicada en el directorio

LIB\FILESYSTEM de la instalación de Dynamic C.Los ejemplos deberán correrse de modo tal que las flash mapeen normalmente, porlo general esto corresponde a la opción de "correr programa en flash".

198

Page 217: El Camino Del Conejo

FS2

Creación del FS2 en un módulo con dos chips de flash

Utilizaremos un RCM2100 por el simple hecho de que tenemos uno disponible, ellector puede utilizar el que más le agrade, siempre y cuando éste tenga dos chips deflash.

#class auto#use fs2.lib

main(){int rc;

if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2puts("Formateando...");if((rc=lx_format(fs_get_flash_lx(), 0))==0) // formatea

puts("OK");else printf("No puedo formatear: %d\n",rc);

}else printf("No puedo inicializar: %d\n",rc);

}

Creación del FS2 en un módulo con un chip de flash

Primero deberemos modificar el BIOS con la siguiente línea:

#define XMEM_RESERVE_SIZE 0x9000L

De esta forma reservamos 32KB en la flash para el FS2. Luego sí podemos correr elsiguiente programa en cualquier módulo con una sola flash:

#class auto

// Asignamos 32K para el file system#define FS2_USE_PROGRAM_FLASH 32

#use fs2.lib

main(){int rc;

if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2 puts("Formateando...");

if((rc=lx_format(fs_get_flash_lx(), 0))==0) // formatea puts("OK"); else printf("No puedo formatear: %d\n",rc); } else printf("No puedo inicializar: %d\n",rc);

}

Creación del FS2 en la flash principal, en un módulo con dos chips

de flash

Primero deberemos modificar el BIOS con la siguiente línea:

#define XMEM_RESERVE_SIZE 0x9000L

199

Page 218: El Camino Del Conejo

File Systems

De esta forma reservamos 32KB en la flash principal para el FS2. Luego sí podemoscorrer el siguiente programa en cualquier módulo con dos flash:

#class auto

// Asignamos 32K para el file system#define FS2_USE_PROGRAM_FLASH 32

#use fs2.lib

main(){int rc;

if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2 puts("Formateando...");

if((rc=lx_format(fs_get_other_lx(), 0))==0) // formatea puts("OK"); else printf("No puedo formatear: %d\n",rc); } else printf("No puedo inicializar: %d\n",rc);

}

Nótese que disponemos de dos unidades: este espacio reservado de la flash principal,y la flash secundaria. El ejemplo formatea sólo la primera, si se desea ademásformatear la flash secundaria, debe añadirse la llamada vista en un ejemplo anterior:

if((rc=lx_format(fs_get_other_lx(), 0))==0) // formatea FS2 enputs("OK espacio reservado"); // flash de programa

else printf("No puedo formatear: %d\n",rc);

if((rc=lx_format(fs_get_flash_lx(), 0))==0) // formatea FS2 enputs("OK flash secundaria"); // flash secundaria

else printf("No puedo formatear: %d\n",rc);

Creación del FS2 en RAM

Primero deberemos modificar el BIOS con la siguiente línea:

#define FS2_RAM_RESERVE 8

De esta forma reservamos 32KB en la RAM para el FS2. Luego sí podemos correr elsiguiente programa en cualquier módulo:

#class auto

#use fs2.lib

main(){int rc;

if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2 puts("Formateando...");

if((rc=lx_format(fs_get_ram_lx(), 0))==0) // formatea puts("OK"); else printf("No puedo formatear: %d\n",rc); } else printf("No puedo inicializar: %d\n",rc);

200

Page 219: El Camino Del Conejo

FS2

}

El espacio así asignado se presenta como una unidad que puede coexistir concualesquiera otras definidas; es decir, tendremos uno, dos, o tres soportes físicos,según hallamos definido (además de la RAM), la flash secundaria (si la hubiere) y/oel espacio reservado en flash de programa.

Escritura de un log

Abrimos un archivo con un nombre determinado, si no existe lo creamos, nosvamos hasta el final del mismo, y a continuación actualizamos cada vez que ocurreun evento, simbolizado en este caso por la presión sobre el switch S2 del módulo. Laorden para cerrar el archivo la damos presionando S3. Por comodidad, utilizamos laflash secundaria de un módulo RCM2100, pero el programa debería correr de igualmodo en cualquier FS2, dado que no se especifica LX. El programa no verifica ni

detecta la existencia de espacio para escribir, de ser necesario, esta condición puededetectarse comparando el valor devuelto por fwrite() con la cantidad de datos que seintentaba escribir. A los fines prácticos, el log tendrá la siguiente estructura:

LEN: 1 byte, longitud del texto a continuaciónTXT: LEN bytes

TIMESTAMP: 4 bytes (long)

#class auto#use fs2.lib

File file;const char S2[]="presión de S2";

main(){int rc,filenumber;unsigned char len;char buffer[256],*evento;

filenumber=1;evento=S2;if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2 if(fopen_wr(&file,filenumber) && fcreate(&file,filenumber))

printf("No puedo abrir: %d\n",filenumber); else {

fseek(&file, 0, SEEK_END); // fin de archivo while(BitRdPortI(PBDR,3)){ // mientras no S3

if(!BitRdPortI(PBDR,2)){ // cuando S2

sprintf(buffer,"Evento: %s",evento); // genera stringlen=strlen(buffer);

fwrite(&file, &len, sizeof(char)); // guarda longitud fwrite(&file, buffer, len); // guarda el texto fwrite(&file, &SEC_TIMER, sizeof(long)); // timestamp

for(rc=0;rc<30000;rc++); // anti-rebote while(!BitRdPortI(PBDR,2)); // espera libere S2

for(rc=0;rc<30000;rc++); // anti-rebote }

201

Page 220: El Camino Del Conejo

File Systems

}

fclose(&file); // cierra el archivo }

}else printf("No puedo inicializar: %d\n",rc);

}

Lectura de un log

Abrimos el archivo en modo lectura, y desde el inicio procesamos cada uno de los

registros encontrados, mostrando el contenido en pantalla. A fin de traducir fecha yhora a un formato humanamente entendible, utilizamos las funciones asociadas alRTC.

#class auto#use fs2.lib

File file;

main(){int rc,filenumber;unsigned char len;char buffer[256],*evento;long datetime;struct tm thetm;

filenumber=1; if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2

if(fopen_rd(&file,filenumber)) printf("No puedo abrir: %d\n",filenumber);else { while(fread(&file, &len, 1)){ // mientras haya algo que

// leer, obtiene longitud fread(&file, buffer, len); // lee texto

buffer[len]=0; // formatea como stringfread(&file, &datetime, sizeof(long)); // lee timestampmktm(&thetm, datetime); // convierte a estructura

// para imprimir bonitoprintf("%s %02d/%02d/%04d %02d:%02d:%02d\n",buffer,

thetm.tm_mday,thetm.tm_mon,1900+thetm.tm_year, thetm.tm_hour,thetm.tm_min, thetm.tm_sec);

} fclose(&file); // cierra el archivo}

} else printf("No puedo inicializar: %d\n",rc);

}

Escritura de un log de longitud fija

Haremos esta vez un log de longitud fija, es decir, dados registros de longitud fija,

el log puede albergar hasta un número máximo de registros, y después comienza aborrar los iniciales, manteniendo siempre los más recientes. Para esto, usamos la

202

Page 221: El Camino Del Conejo

FS2

función que permite borrar datos al principio del archivo. De esta forma, el logavanza estirándose al final del archivo y acortándose luego al principio, manteniendosiempre la misma longitud, sin necesidad de manejar un buffer circular.

El programa va insertando registros en el archivo, antes de insertar un nuevoregistro verifica la longitud del archivo, que es función de la cantidad de registros; siexcede un límite prefijado, comienza a borrar del principio del archivo una cantidad

de datos equivalente a un registro, de modo de mantener una longitud de archivomáxima constante al ingresar el nuevo registro.A los fines prácticos, el log tendrá la siguiente estructura:

TXT: LOGSZ bytes (longitud fija)TIMESTAMP: 4 bytes (long)

#class auto#use fs2.lib

File file;const char LOG[]="Texto del log"; //ver longitud abajo// tamaño del texto en un registro del log (longitud fija)#define LOGSZ 13

// cantidad máxima de registros a almacenar en el log#define NUMREC 10

#define MAXLEN (NUMREC * (LOGSZ + sizeof(long)))

main(){int rc,filenumber;long len;char *evento;

filenumber=32;evento=LOG;if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2 if(fopen_wr(&file,filenumber) && fcreate(&file,filenumber))

printf("No puedo abrir: %d\n",filenumber); else {

fseek(&file, 0, SEEK_END); // fin de archivo while(BitRdPortI(PBDR,3)){ // mientras no S3

if(!BitRdPortI(PBDR,2)){ // cuando S2

len=ftell(&file); // tamaño utilizadoif(len>=MAXLEN) // si ya estoy en el máximo, hace fshift(&file, LOGSZ+sizeof(long),NULL); // lugar

fwrite(&file, evento, LOGSZ); // guarda el texto fwrite(&file, &SEC_TIMER, sizeof(long));// timestamp

for(rc=0;rc<20000;rc++); // anti-rebote while(!BitRdPortI(PBDR,2)); // espera libere S2

for(rc=0;rc<20000;rc++); // anti-rebote }}fclose(&file); // cierra el archivo

}}else printf("No puedo inicializar: %d\n",rc);

}

203

Page 222: El Camino Del Conejo

File Systems

De una forma similar al ejemplo anterior, podemos leer el log, verificando elfuncionamiento de nuestro programa de escritura:

#class auto#use fs2.lib

// tamaño de texto en un registro del log (longitud fija)#define LOGSZ 13

File file;

main(){int rc,filenumber;unsigned char len;char buffer[LOGSZ+1],*evento;long datetime;struct tm thetm;

filenumber=32;if ((rc=fs_init(0,0))==0){ // inicializa estructuras de FS2 if(fopen_rd(&file,filenumber))

printf("No puedo abrir: %d\n",filenumber); else {

while(fread(&file, buffer, LOGSZ)){ // mientras haya algo que// leer, lee texto

buffer[LOGSZ]=0; // formatea como string fread(&file, &datetime, sizeof(long)); // lee timestamp mktm(&thetm, datetime); // convierte a estructura

// para imprimir bonito printf("%s %02d/%02d/%04d %02d:%02d:%02d\n",buffer,

thetm.tm_mday,thetm.tm_mon,1900+thetm.tm_year,thetm.tm_hour,thetm.tm_min, thetm.tm_sec);

}fclose(&file); // cierra el archivo

}}else printf("No puedo inicializar: %d\n",rc);

}

Afinación (tuning) y uso avanzado del FS2

Tamaño de partición y de sectores

Como seguramente todos sabemos, la escritura en un file-system se hace en forma

de sectores, llevando estos sectores diversos nombres según el file system de que setrate, y pudiendo o no coincidir en su tamaño con el sector del soporte físico en quese aloja, si lo hubiere. En este caso, el FS2 funciona sobre flash o RAM, y no tienenecesariamente que existir un tamaño de sector físico (si bien la flash puede tenerlo,la RAM no lo tiene). Existe no obstante un tamaño de sector lógico, y dado quecomo recordáramos, los archivos se graban en sectores, el grado de aprovechamiento

que se hace del soporte físico es fuertemente dependiente de la relación entre eltamaño de los archivos y el tamaño de los sectores.

204

Page 223: El Camino Del Conejo

FS2

Si los archivos son pequeños y los sectores son grandes, existe mucho desperdicio,pues la mayoría de los sectores están sin utilizar. Así, diez archivos de 10 bytesocupan diez sectores, o el equivalente a 5120 bytes si el tamaño de sector es de 512bytes. De igual modo, si los archivos son grandes y los sectores son pequeños, sedesperdicia espacio en todo lo que es metadatos, es decir, tabla de asignación (dondese halla el mapa que dice qué sectores ocupa cada archivo) o su equivalente

(punteros en una lista encadenada, como el legendario Disciple DOS2) y demás cosasque incluyen los file-systems más sofisticados.El conflicto se reduce entonces a encontrar la definición de grande y pequeño, de

modo tal de definir un tamaño de sector que sea óptimo para el tamaño de archivos autilizar. En la mayoría de los casos, lo mejor es dejar los parámetros por defecto,particularmente si la flash es sector-oriented y ya tiene un tamaño de sector físico

que limita el mínimo posible para el tamaño de sector lógico.Cuando no es posible ponerse de acuerdo con el tamaño de sector lógico a utilizar,

puede recurrirse a una decisión de convivencia, realizando particiones y asignandodiferentes tamaños de sector para cada partición: particiones grandes con sectoresgrandes para archivos grandes, y particiones chicas con sectores chicos para archivoschicos.

El espacio destinado a cada partición, así como el tamaño de sector lógico, seespecifican como parámetros de la función fs_setup(); cada partición puede tener supropio tamaño de sector lógico. El ejemplo siguiente muestra como es posible creardos particiones, con las características descriptas en el párrafo anterior:

ext2 = fs_setup(ext1, LS1, 0, NULL, FS_PARTITION_FRACTION,LX2F, LS2, 0, NULL);

ext1 es el identificador del LX "base", que puede ser el soporte físico(fs_get_flash_lx(), fs_get_other_lx(), o fs_get_ram_lx()), o una partición que asu vez se va a partir

ext2 es el LX resultante de la nueva partición creadaLS1 es el logaritmo en base 2 del tamaño de sector lógico del LX base, por ejemplo

9 para 512 bytes

LX2F es la fracción de espacio asignada para el nuevo LX, representada dentro deun entero, de modo que el valor 65535 representa la totalidad de la misma

LS2 es el logaritmo en base 2 del tamaño de sector lógico del nuevo LXFS_PARTITION_FRACTION es el comando pasado a fs_setup().

Como habrán observado, esta función es compleja y poderosa, por lo que dejamos

el resto de sus combinaciones posibles para el manual de referencia.

2 El autor ha transcurrido gran parte de su post-adolescencia toqueteando computadoras de algúnmodo emparentadas con Sir Clive Sinclair, es decir, ZX-Spectrum y sus derivados. Disciple fue unsistema de discos sumamente rápido y simple, desarrollado para estas (en su época) poderosascomputadoras personales.

205

Page 224: El Camino Del Conejo

File Systems

La llamada a fs_setup() debe hacerse antes de llamar a fs_init() , y por supuesto, esnecesario formatear ambos LX luego de la partición.

Acceso a diferentes particiones

Si disponemos de varias particiones o LX, para poder cambiar de LX, simplementedebemos llamar a la función que elige sobre cuál LX trabajamos:

fs_set_lx(lxmeta, lxdata);

La razón de la existencia de dos parámetros, es una curiosidad de FS2, que permitealojar la información de metadatos en un LX y los datos en otro, es decir, algo asícomo tener la FAT y directorio en un disco y la información en otro, en lenguajeMS-DOS. Dado que en la implementación de FS2 cada archivo requiere de un sectorpara metadatos del mismo, en una aplicación con muchos archivos relativamentegrandes, que aprovecharían mejor los sectores grandes, la información de metadatos

(metadata) resulta desaprovechando valiosos sectores. Manteniendo dos LX, uno consectores grandes y otro con sectores chicos, puede aprovecharse mejor el espacioutilizando el LX con sectores chicos para metadata y el otro para la información.En una aplicación "tradicional", metadata e información se hallan en el mismo LX,

y entonces, cambiar "de disco" resulta:

fs_set_lx(nuevolx, nuevolx);

Cantidad de archivos y de particiones

A fin de optimizar performance y minimizar recursos en área root y buffers enRAM, existe una serie de macros que definen valores máximos, entre otras cosas,para la cantidad total de archivos y la cantidad de particiones. De ser necesario, elusuario deberá modificar los valores para adecuarlos a sus necesidades. Por ejemplo,

la cantidad máxima de particiones es seteada al momento de compilar, acorde a lacantidad de soportes físicos posibles, por lo que deberá modificarse si alguno deellos tiene particiones.

#define FS_MAX_LX 4 // Máximo número de particiones#define FS_MAX_FILES 12 // Máximo número de archivos en total

FAT

Si bien FAT dista mucho de ser óptimo, es un sistema de archivos lo

suficientemente simple como para poder ser implementado en sistemas dedicados,con una performance razonable, brindando una mayor flexibilidad de uso que FS2.Su principal aplicación es la implementación de file systems en serial flash y NANDflash.

206

Page 225: El Camino Del Conejo

FAT

La implementación de FAT hecha por Rabbit no sólo incluye el MBR (Master BootRecord), sino que necesita de su presencia para su correcto funcionamiento. Existesoporte para FAT12 y FAT16, permitiendo diversos tipos de particiones ocultas. Noobstante, hasta el momento no existe soporte para FAT32 ni nombres largos, elformato interno es solamente LBA, y el tamaño de sector es fijo en 512 bytes. Cadadispositivo o soporte físico puede tener un máximo de cuatro particiones (no existe

soporte de particiones extendidas o unidades lógicas dentro de una partición), y esposible tener hasta cuatro dispositivos como máximo por cada driver. Al presenteexisten drivers para serial flash, que soporta un solo dispositivo, y NAND flash, quesoporta dos dispositivos. El esquema es mayormente abierto, y es posible soportarotro tipo de dispositivos en los cuales utilizar FAT, simplemente escribiendo eldriver correspondiente, que se encarga de transformar la especificación de LBA al

formato requerido por el dispositivo en sí, y demás tareas de bajo nivel.Una característica interesante, es que las rutinas inicialmente son no-bloqueantes,

es decir, retornan inmediatamente. El usuario debe llamarlas reiteradamente hastaque retornen con un código de error diferente a "estoy ocupada". Esto permite larealización de otro tipo de tareas, e incluso operaciones de lectura y escritura"simultánea". No obstante, para los alcances de este texto, optaremos por definir una

macro que automáticamente las vuelve bloqueantes, volviendo luego de terminar sutarea, como estamos acostumbrados a trabajar tradicionalmente en otros entornos.

#define FAT_BLOCK#use "fat.lib"

Acceso a diferentes dispositivos y particiones

FAT es mucho más flexible que FS2, no es necesaria una elección de antemano nimodificación del BIOS; la elección del dispositivo se realiza mediante la selección

de su dirección dentro de una tabla de dispositivos.Dado que cada soporte físico tiene su MBR, un MBR puede definir cuatro

particiones, y la implementación soporta hasta cuatro dispositivos por driver, la tablatiene espacio para dieciséis entradas por cada driver inicializado. La llamada afunción que inicializa las estructuras para FAT, automáticamente inicializa tambiénlos drivers correspondientes a los dispositivos presentes de acuerdo al modelo de

módulo, y monta las particiones presentes en cada dispositivo en su correspondienteubicación. Por lo tanto, referirse a una determinada partición en un dispositivoespecífico se reduce a elegir correctamente el índice dentro de la tabla:

fat_part_mounted[index].

Por lo general, los módulos tienen solamente una memoria NAND-flash, serial-flash, o un zócalo para tarjetas; ya sea NAND-flash removibles de tipo xD o SD, por

lo que el índice será de 0 a 3. En módulos como el RCM3365, que dispone además

207

Page 226: El Camino Del Conejo

File Systems

de la flash soldada de un zócalo para tarjetas de tipo xD, nos referiremos a lasparticiones en esta tarjeta usando índices de 4 a 7:

fat_part_mounted[0] Se refiere a la primera partición del primerdispositivo, usualmente serial o NAND flash enel módulo, o la tarjeta si no hay de aquéllas.

fat_part_mounted[4] Se refiere a la primera partición del segundo dispositivo, usualmente tarjeta xD (RCM3360A)

Si el dispositivo existe y fue montada la partición, en esa posición del arraytendremos un índice a los parámetros de ésta; caso contrario, habrá un NULL. Detodos modos, existen macros que simplifican la operación, y son definidas almomento de compilar por las mismas bibliotecas de funciones. Las mismas soninicializadas al detectar el módulo, al valor correspondiente. Por ejemplo:en un RCM3365:

#define FAT_XD_DEVICE FDDF_MOUNT_DEV_1#define FAT_XD_PART fat_part_mounted[4]

en un RCM4300:

#define FAT_SD_DEVICE FDDF_MOUNT_DEV_0#define FAT_SD_PART fat_part_mounted[0]

Al igual que en FS2, y en MS-DOS, los distintos dispositivos guardan su

independencia, no es posible montarlos en una raíz común como en Unix. Al menosno desde el punto de vista de las funciones de file system, veremos más adelante enel capítulo sobre networking que se provee una especie de file system a la Unixdesde el punto de vista del servidor FTP o HTTP.

Partición y formateo

Como recordáramos al estudiar FS2, estamos hablando de un microprocesador, ypor lo general estamos desarrollando un sistema dedicado, por lo que la partición y

el formateo no son eventos que se realizan tal vez más de una vez. Posiblemente seapreferible cargar algún programa aparte al momento de inicializar el equipo enproducción en vez de incluir soporte en el programa de aplicación. Particularmente,la función que inicializa el soporte FAT permite automáticamente formatear unapartición. El formateo automático puede ser condicional o incondicional:� condicional significa que si encuentra algo no realiza el formateo

� incondicional significa que destruye lo que hubiere e inicializa todo el dispositivocomo una sola partición.

/* Monta la primera partición del primer dispositivo, formatea si no encuentra un file-system válido allí */fat_AutoMount(FDDF_COND_PART_FORMAT | FDDF_MOUNT_DEV_0 |

FDDF_MOUNT_PART_0);

/* Formatea el primer dispositivo como una gran partición, lo monta */fat_AutoMount(FDDF_UNCOND_PART_FORMAT | FDDF_MOUNT_DEV_0 |

208

Page 227: El Camino Del Conejo

FAT

FDDF_MOUNT_PART_0);

Para otras opciones, se recomienda estudiar la documentación y las samples,particularmente Samples\Filesystem\fmt_device.c.

Debido a que, si bien se soporta FAT como file-system, no se implementa un DOS(Disk Operating System, sistema operativo de discos), no existe el concepto de

"directorio corriente" (working directory). Luego, tampoco existen funciones paracambiar de directorio. Los archivos se direccionan desde el directorio raíz con supath correspondiente relativo al mismo, sin posibilidad de especificar el dispositivodentro del path. La especificación de dispositivo se hace sí dentro de la llamada afunción, especificando el índice dentro de la tabla de particiones montadas, comoviéramos en párrafos anteriores. Sin embargo, es posible "abrir" una entrada de

directorio de modo de obtener un file handle, y existen funciones que permiten"leer" un directorio, por lo que es perfectamente posible mantener un estado yemular el concepto de "directorio corriente", almacenando el path en un string yañadiéndolo como prefijo antes de cada operación de acceso a archivos, como puedeobservarse en una de las samples provistas: FAT_shell.c, que emula las operacioneselementales de un shell similar a sh en Unix:

#define MAX_DEVICES 2char path[300];char pwd[MAX_DEVICES * FAT_MAX_PARTITIONS][257];

strcpy(path, pwd[particion]);strcat(path, FAT_SLASH_STR);strcat(path, filename);rc=fat_Open(particion,filename,...); // ... = demás parámetros

Debido a que es muchísimo más simple, para un sistema dedicado, dedicarse a

hacer lo que tiene que hacer; es preferible mantener paths fijos, cargados en elprograma, y referirse siempre a los archivos por el path completo, aún en medio deuna infinidad de directorios, y eso es lo que haremos en este texto. Al iniciar porprimera vez el sistema, al momento de dar formato al soporte físico, es posiblecorrer FAT_shell.c y armar toda la estructura de directorios requerida para laoperación, si la hubiere, y si fuera necesario.

Si la complejidad de la aplicación requiere andar saltando alegremente por losdirectorios, el lector interesado deberá analizar detalladamente dicha sample, quecubre en detalle el correspondiente tema, ya que no encontrará aquí un mayor gradode profundidad que éste. Las funciones provistas permiten hasta incluso escribir unDOS elemental, todo depende de la inquietud y necesidad del usuario.

Utilización normal

Antes que nada deberemos inicializar las estructuras en memoria que operan sobre

el file-system, lo cual se realiza mediante el llamado a la función fat_Automount().La misma posee unos cuantos parámetros, como vislumbramos en el párrafo

209

Page 228: El Camino Del Conejo

File Systems

anterior; lo más simple es inicializar con los parámetros por defecto, o definir quédispositivo queremos montar:

rc = fat_AutoMount(FDDF_USE_DEFAULT); // parámetros por defecto

/* monta todos los dispositivos y todas sus particiones */rc = fat_AutoMount(FDDF_MOUNT_DEV_ALL | FDDF_MOUNT_PART_ALL);

/* monta la segunda partición del segundo dispositivo */rc = fat_AutoMount(FDDF_MOUNT_DEV_1 | FDDF_MOUNT_PART_1);

/* monta primera y segunda partición del primero y segundo dispositivos (NO ES POSIBLE ESPECIFICAR INDEPENDIENTEMENTE EN UNA SOLA LLAMADA) */rc = fat_AutoMount(FDDF_MOUNT_DEV_0 | FDDF_MOUNT_PART_0 |

FDDF_MOUNT_DEV_1 | FDDF_MOUNT_PART_1);

Los parámetros por defecto se encuentran en el código de la biblioteca defunciones, y corresponden a montar todas las particiones de todos los dispositivosreconocidos.

Las operaciones de archivos se realizan de forma similar a como se operanormalmente en C con éstos: especificando un file handle a las diversas llamadas afunción para abrir, buscar posición, leer, escribir, y cerrar el archivo. Las funcionesretornan los valores usuales, que permiten detectar errores, fin de archivo, etc.Recordemos que los archivos se direccionan desde el directorio raíz con su pathcorrespondiente relativo al mismo. La especificación de dispositivo se hace dentro

de la llamada a función, especificando el índice dentro de la tabla de particionesmontadas, y no dentro del path. Una característica interesante adicional, es que esposible hacer una escritura en el soporte físico desde memoria en xmem, según quéfunción se utiliza. A continuación analizaremos las funciones más comunes:

FATfile file;

rc=fat_Open(particion,filename,FAT_FILE,FAT_CREATE,&file,NULL);if(rc<0)

// hubo un errorelse

// 'filename' fue abierto o creado para escritura y/o lectura

Para escribir en el archivo, disponemos de:

fat_Write(&file, buffer, longitud); // buffer=puntero al área// de datos

fat_xWrite(&file, xbuffer, longitud); // xbuffer=puntero al área// de datos, en xmem

Como siempre, debemos cerrar el archivo:

fat_Close(&file);

Para movernos dentro del archivo, podemos usar:

fat_Seek(&file, offset, SEEK_SET); // 'offset' bytes desde iniciofat_Seek(&file, offset, SEEK_CUR); // 'offset' bytes de posición actual

210

Page 229: El Camino Del Conejo

FAT

fat_Seek(&file, -offset, SEEK_END); // '-offset' bytes de fin de archivo

Para saber en qué posición estamos:

fat_Tell(&file, &position); // position = posición actual

Otra adición interesante es la posibilidad de borrar una cantidad de datos del finalde un archivo, es decir, truncarlo. Otra permite "partirlo" en dos archivos, endeterminado punto:

fat_Truncate(&file, bytes); // deja solamente 'bytes' bytes en el// archivo, borra de ahí en adelante

fat_Split(&file, bytes, niuneim); // el archivo abierto se trunca a // 'bytes' bytes, creándose 'niuneim',

// que contiene el resto

Para leer información de un archivo, utilizamos la función de lectura:

fat_Read(&file, buffer, bytes); // buffer=puntero al área de datos

Si deseamos abrir un archivo en modo lectura solamente, la llamada será la misma,pero pasaremos un flag de sólo lectura:

fat_Open(particion,filename,FAT_FILE,FAT_READONLY,&file,0);

Finalmente, para deshacernos de un archivo, lo borramos:

fat_Delete(particion,filename);

Dispositivos removibles

Si el dispositivo utilizado es del tipo removible, deberemos tenerlo insertado alinicializar el programa; caso contrario deberemos montar (mount) la partición

correspondiente antes de poder leer de la misma. Para ello, es necesario detectar supresencia.De igual modo, una vez realizada la actividad sobre el dispositivo, deberemos

desmontar (unmount) todas sus particiones antes de poder retirarlo. La funciónfat_UnmountPartition() se encarga de desmontar una partición, cerrandocualesquiera archivos estuvieren abiertos en la misma. Además, es posible llamar a

otra función, que se encarga de desmontar todas las particiones de un dispositivo:fat_UnmountDevice().Existen además funciones de soporte como para poder implementar hot-swap, el

lector interesado puede consultar la sample correspondiente como ejemplo:Filesystem\FAT_hot_swap.c

Tarjetas xD

A partir de la revisión 2.11 del módulo FAT, es posible detectar la presencia de undispositivo removible basado en NAND-flash, mediante una llamada a

211

Page 230: El Camino Del Conejo

File Systems

nf_XD_Detect(). El parámetro pasado a esta función indica el tipo de anti-rebote aemplear. Para el uso normal de FAT en modo bloqueante, le pasaremos el valor 1como parámetro. El ejemplo siguiente muestra el concepto crudo de la operaciónsobre la tarjeta xD3:

while(nf_XD_Detect(1)<0); rc = fat_AutoMount(FAT_XD_DEVICE | FDDF_MOUNT_PART_ALL );

// opera sobre la tarjetafat_UnmountDevice(FAT_XD_PART->dev); // desmonta y close()

La operación de hot-swap, se describe en una sample de un módulo sólo soportadopor DC9: Filesystem\FAT\FAT_hot_swap_3365_75.c

Tarjetas SD

A partir de la revisión 2.14 del módulo FAT, están soportadas las tarjetas SD ycompatibles. La presencia del dispositivo se detecta mediante una llamada a

sdspi_debounce(). El parámetro pasado a esta función es un puntero a una estructurainterna, que se inicializa al montarla por primera vez. Si la detección la realizamosantes de intentar montar, deberemos realizar manualmente dicha inicialización. Elejemplo siguiente muestra un esquema de la operatoria para una única tarjeta:

sdspi_initDevice(0,&SD_dev0); // SD_dev0 = tabla internawhile(!sdspi_debounce(&SD[0])); // SD[0] = tabla interna

rc = fat_AutoMount(FAT_SD_DEVICE | FDDF_MOUNT_PART_ALL );// opera sobre la tarjetafat_UnmountDevice(FAT_SD_PART->dev); // desmonta y close()

La operación de hot-swap, se describe en la sample correspondiente:Filesystem\FAT\FAT_hot_swap_sd.c

Ejemplos

Algunos de los siguientes ejemplos utilizan la variable shared SEC_TIMER paralogging, la misma corresponde a fecha y hora correctas si alguna vez fue seteado elRTC y no se removió la alimentación de back-up.

En lo posible, trataremos de imprimir los códigos de error retornados. El significadode los mismos puede obtenerse en ERRNO.LIB, ubicada en el directorioLIB\FILESYSTEM de la instalación de Dynamic C.

Por esas cosas de la causalidad, para compilar y ejecutar los ejemplos acontinuación se deberá tener instalado el módulo FAT, el cual no se incluye con ladistribución standard de Dynamic C sino que debe comprarse por separado.

3 Al momento de actualizar este texto, Rabbit aparentemente decidió no implementar el xD Picture

Standard para evitar encarecer los productos con las regalías requeridas. Por lo tanto, la informaciónescrita en una de estas tarjetas por la implementación de FAT en xD de Dynamic C no es legible por

los drivers standard de un lector standard de PC, cámaras digitales o equivalente, y viceversa. Sesugiere utilizar la tarjeta SD si se requiere intercambiar información con otros dispositivos.

212

Page 231: El Camino Del Conejo

FAT

Creación de FAT

Si bien es preferible el empleo de FAT_shell.c , o incluso fmt_device.c, daremosaquí un pequeño ejemplo de creación de un FAT file-system.Utilizaremos un RCM3720 por el simple hecho de que tenemos uno disponible, el

lector puede utilizar el que más le agrade, siempre y cuando éste tenga un dispositivosoportado por los drivers de FAT (serial-flash, NAND-flash). El ejemplo opera

sobre el primer dispositivo, inicializándolo como una gran partición de toda suextensión.

#class auto

#define FAT_BLOCK#use fat.lib

main(){int rc;

rc=fat_AutoMount(FDDF_UNCOND_PART_FORMAT | FDDF_MOUNT_DEV_0 | FDDF_MOUNT_PART_0);

if (rc==0) puts("Formateado");

else printf("No puedo formatear: %d\n",rc);}

Escritura de un log

Abrimos un archivo con un nombre determinado, si no existe lo creamos, nos

vamos hasta el final del mismo, y a continuación actualizamos cada vez que ocurreun evento, simbolizado en este caso por la presión sobre el switch S2 del módulo. Laorden para cerrar el archivo la damos presionando S1. El programa no verifica nidetecta la existencia de espacio para escribir, de ser necesario, esta condición puededetectarse comparando el valor devuelto por fat_Write() con la cantidad de datos quese intentaba escribir.

A los fines prácticos, el log tendrá la siguiente estructura:LEN: 1 byte, longitud del texto a continuaciónTXT: LEN bytesTIMESTAMP: 4 bytes (long)

#class auto

#define FAT_BLOCK#use fat.lib

FATfile file;const char S2[]="presión de S2";

main(){int rc;fat_part *particion;unsigned char len;

213

Page 232: El Camino Del Conejo

File Systems

char buffer[256],*evento,filename[256];

BitWrPortI(PBDDR,&PBDDRShadow,0,7); // PB7 = entradaBitWrPortI(PFDDR,&PFDDRShadow,0,4); // PF4 = entrada

strcpy(filename,"milog.log");evento=S2;rc = fat_AutoMount(FDDF_USE_DEFAULT); // inicializa estructuras de

// FAT y monta dispositivosparticion=fat_part_mounted[0]; // Se refiere a la primera partición

// de serial/NAND flash en el móduloif (particion!=NULL){ if(fat_Open(particion,filename,FAT_FILE,FAT_CREATE,&file,NULL)<0)

printf("No puedo abrir: %s\n",filename); else {

fat_Seek(&file, 0, SEEK_END); // fin de archivo while(BitRdPortI(PFDR,4)){ // mientras no S1

if(!BitRdPortI(PBDR,7)){ // cuando S2

sprintf(buffer,"Evento: %s",evento); // genera stringlen=strlen(buffer);

fat_Write(&file, &len, sizeof(char));// guarda longitud fat_Write(&file, buffer, len); // guarda texto fat_Write(&file, (char *) &SEC_TIMER, sizeof(long));

// timestampfor(rc=0;rc<20000;rc++); // anti-rebote

while(!BitRdPortI(PBDR,7)); // espera libere S2for(rc=0;rc<20000;rc++); // anti-rebote

}}fat_Close(&file); // cierra el archivo

}}else printf("No puedo inicializar (no hay particiones ?): %d\n",rc);

}

Lectura de un log

Abrimos el archivo en modo sólo lectura, y desde el inicio procesamos cada uno delos registros encontrados, mostrando el contenido en pantalla. A fin de traducir fechay hora a un formato humanamente entendible, utilizamos las funciones asociadas alRTC.

#class auto

#define FAT_BLOCK#use fat.lib

FATfile file;

main(){int rc;fat_part *particion;unsigned char len;char buffer[256],*evento,filename[256];long datetime;struct tm thetm;

strcpy(filename,"milog.log");

214

Page 233: El Camino Del Conejo

FAT

rc = fat_AutoMount(FDDF_USE_DEFAULT); // inicializa estructuras de// FAT y monta dispositivos

particion=fat_part_mounted[0]; // Se refiere a la primera partición// de serial/NAND flash en el módulo

if (particion!=0){ if(fat_Open(particion,filename,FAT_FILE,FAT_READONLY,&file,NULL)<0)

printf("No puedo abrir: %s\n",filename); else {

while(fat_Read(&file, &len, 1)>0){// mientras haya qué leer, // obtiene longitud

fat_Read(&file, buffer, len); // lee texto buffer[len]=0; // formatea como string fat_Read(&file, (char *) &datetime, sizeof(long));// lee

// timestamp mktm(&thetm, datetime); // convierte a estructura

// para imprimir bonito printf("%s %02d/%02d/%04d %02d:%02d:%02d\n",buffer, thetm.tm_mday,thetm.tm_mon,1900+thetm.tm_year, thetm.tm_hour,thetm.tm_min, thetm.tm_sec);}fat_Close(&file); // cierra el archivo

}}else printf("No puedo inicializar (no hay particiones ?): %d\n",rc);

}

Configuración de FAT

Existen ciertas macros que pueden modificar la forma en que el file-system opera,permitiendo trocar performance por uso de recursos, o simplemente adecuando elfuncionamiento a la costumbre del programador. por lo general las modificacionesimplican marcar o no como comentarios a algunas macros en el código de labiblioteca de funciones. Los textos mostrados han sido tomados del texto de la

misma, para mejor referencia.

FAT12 soporta particiones de hasta 2MB, mientras que FAT16 las requiere de másde 2MB. Eliminando una de estas dos definiciones, es posible ahorrar espacio dememoria:

#define FAT_FAT12 // comment out to disable FAT12 support#define FAT_FAT16 // comment out to disable FAT16 support

También es posible ahorrar aún más, particularmente código, si no se incluyesoporte para escribir; aunque sus aplicaciones pueden ser más que reducidas.

#define FAT_WRITEACCESS // comment out to disable write operations

Las siguientes macros pueden definirse antes de incluir la biblioteca de funciones.Como comentáramos al comienzo, las funciones son esencialmente no-bloqueantes,

la definición que elige la forma de funcionamiento "standard" es la siguiente:

#define FAT_BLOCK // sets library to 'blocking' mode

215

Page 234: El Camino Del Conejo

File Systems

Finalmente, aquéllos acostumbrados a la forma tradicional y más común de indicarlos paths, podemos utilizarla mediante esta definición, en vez de la más conocida,pero utilizada por sólo un fabricante de sistemas de discos.

#define FAT_USE_FORWARDSLASH // Sets forward slash instead of back slash

En su defensa, dado que los URLs se indican con esta barra, la biblioteca defunciones que se encarga del sistema de archivos, requiere que FAT funcione en esta

modalidad. Analizaremos esto en el capítulo sobre networking con Rabbit.

216

Page 235: El Camino Del Conejo

Networking

Introducción

Es prácticamente imposible intentar resumir todo el networking en un capítulo deun libro, ni siquiera sólo TCP/IP. Por un lado, es tanto pero tanto el contenido quesiempre algo queda afuera. Por otro lado, este campo avanza tan rápidamente que lo

poco que uno aprendió en sus años de networker poco a poco va quedando obsoleto,o pasa a ser una curiosidad. No obstante, los conceptos fundamentales siempreaplican, y ése ha sido el espíritu del capítulo sobre networking en el librointroductorio sobre Rabbit. Sin embargo, la gran cantidad de consultas por parte dealgunos de los usuarios y los consecuentes llantos y colapsos nerviosos originados enlos encargados de responderlas, me han hecho pensar que es necesario profundizar

un poco más en el tema.Si bien el usuario ocasional y el hobbyista pueden ser felices con un "anduvo, listo,

no toques más", quien desarrolla sistemas dedicados con aplicaciones de networkingdebe tener un conocimiento más acabado de lo que está ocurriendo, al punto depoder resolver problemas de networking sin recurrir a pitonisas ni gurúes. Todo essimple y color de rosa cuando funciona, pero ¿cuando no funciona qué? Cuando no

funciona, es cuando hay que analizar por qué no funciona, y una vez que sé por quéno funciona, es más probable que pueda hacer que funcione y siga funcionandodespués, porque si toco todos los parámetros hoy hasta que "funciona, no toquesmás", es muy probable que mañana vuelva a dejar de funcionar.En el libro introductorio hemos intentado un acercamiento de abajo hacia arriba,

estudiando la constitución de las redes y edificando conceptos, analizando los

pormenores de cada protocolo. En este capítulo intento hacer un enfoque de arribahacia abajo, aprovechando el hecho de que algunos de los conceptos ya se conocen.Para un correcto aprovechamiento del contenido de este capítulo, es altamenterecomendable re-leer y/o tener a mano el capítulo homónimo del libro introductorio.Tal vez hasta el leerlo luego de este capítulo sea más provechoso.

Switches, bridges, hubs, routers

Hemos estudiado en el libro introductorio que la vinculación de dispositivos a

nivel-21 se denomina switching o bridging (según el caso), mientras que lavinculación de redes a nivel-3 se denomina routing.

1 Nos referimos al nivel o capa en el modelo por capas de la ISO (modelo OSI)

217

Page 236: El Camino Del Conejo

Networking

Hub

Si tenemos una simple red en nuestro laboratorio o equivalente, sin conectar a nada,lo más probable es que estemos usando un hub o un switch Ethernet. El hub realizala vinculación a nivel-1 (físico), es el que hace la traslación entre los pares trenzadosy el concepto original de Ethernet con un gran cable coaxial. Los equipos conectadosa un hub funcionan esencialmente en half-duplex (hablan de a uno por vez), ysolamente hay una transmisión en la red a la vez. Cada equipo está conectado al hub,pero la conexión total es virtualmente igual al concepto original de Ethernet, escomo si todos los equipos estuvieran conectados a un cable coaxial. Cada uno de losdispositivos conectados al hub puede observar todo el tráfico de la red en todomomento, y es posible intercambiar dispositivos (permutar las puertas a las que estánconectados) sin inconvenientes.

Switch

El switch realiza la vinculación a nivel-2; pese a su nombre de switch, no es másque un bridge con muchas bocas (una por cada equipo), y la topología de una redcon switch pasa a ser algo así como muchas redes (una por cada boca)interconectadas entre sí por dicho switch. Dado que es un bridge, aprende en québoca está cada dispositivo memorizando las direcciones de nivel-2 (MAC addresses)y asociándolas a cada boca, y al observar una trama Ethernet para un dispositivoespecífico, la repite solamente en la boca que corresponde, por lo que es posible queen la red haya varias transmisiones al mismo tiempo, dado que se establecenconexiones virtuales entre las correspondientes bocas, tal vez de ahí venga la idea dellamarlo switch. Por igual motivo, es posible una comunicación full-duplex si tantoel switch como el dispositivo conectado lo permiten; y por el mismo motivo anteriorno es posible intercambiar dispositivos (permutar las puertas a las que estánconectados) sin causar inconvenientes. Una vez que el switch sabe que determinadaMAC está en una boca específica, seguirá mandando las tramas para esa MAC a esaboca, y sólo a ésa. Para que esto deje de ser así, deben ocurrir una de dos cosas: queexpire el tiempo de permanencia en la tabla y el switch re-aprenda la asignación, oque el dispositivo transmita y el switch se dé cuenta que esa MAC ahora está en otraboca.

218

A Bx y

HUB

Page 237: El Camino Del Conejo

Switches, bridges, hubs, routers

Cada uno de los dispositivos conectados al switch puede observar sólo el tráfico dela red destinado para él y el tráfico multicast/broadcast. Para poder observar tráficode y/o hacia otros dispositivos, deberá ponerse un hub y conectar el monitor detráfico y el dispositivo "bajo prueba" a la misma boca del switch, mediante el hub.De esta forma se observa el tráfico de y hacia ese dispositivo solamente, debiendorealizarse una tarea similar para cada dispositivo que se requiera.

Routing, router

En ninguno de estos dos casos (switch y hub) nos es posible salir más allá denuestra red. Todos los dispositivos de una misma red que deban verse entre sídeberán tener la misma dirección de red. En el caso de TCP/IP, deberán tener lamisma dirección de red IP. Cualquier intento de conexión con un dispositivo cuya dirección de red no sea la de

la red a la que se pertenece, necesita indefectiblemente de la intervención de untercer dispositivo que tenga la inteligencia suficiente como para saber llegar a la otrared. Ese dispositivo se denomina router, y no necesariamente debe tener unaexistencia física separada del resto, como veremos más adelante.Un router mantiene comunicación con otros routers y conoce la topología lógica de

la red; es quien deriva los paquetes o datagramas basándose en la información dered, es decir, las direcciones de nivel-3. De acuerdo a la magnitud de la red, existendiversos algoritmos y protocolos por los cuales los routers aprenden como llegar alas diferentes redes que forman la gran red que une a todos los routers queintercambian esta información. El router decide por qué interfaz enviar lainformación en base al contenido de su tabla de ruteo, la cual es un mapa de lalocalización de las diversas redes y puede aprenderse mediante variados protocolos ycontener agregados manuales (rutas estáticas). El agregado manual más común es la"ruta por defecto" (default route), que significa "todo aquello a lo cual no sé comollegar, debe estar del lado de la ruta por defecto". El router al que se apunta medianteuna ruta por defecto, recibe el nombre de "router por defecto" (default gateway).

219

A Bx y

SWITCH

Page 238: El Camino Del Conejo

Networking

ISP

El ejemplo más claro y típico de querer conectarse con dispositivos con unadirección de red diferente a la nuestra es la conexión a Internet. El ente que nosprovee la conexión a Internet se denomina ISP (Internet Service Provider), y amenos que realmente tengamos una red importante (en cuyo caso probablemente noestaríamos leyendo este capítulo), nos proveerá el acceso mediante una conexiónADSL o similar, dial-up, o cable modem.

Dial-up

Una conexión dial-up es una conexión discada, el usuario llama por teléfono al ISPmediante un modem, el cual está conectado al port de comunicaciones de undispositivo que es capaz de dialogar con otro en el nodo del ISP, mediante elprotocolo PPP (Point-to-Point Protocol). Al inicio del diálogo, se recibe unadirección y se establece una ruta por defecto que hace que nuestro dispositivo sepaque todos los demás dispositivos del mundo están más allá de esa conexión. Esedispositivo generalmente es el software de conexión a Internet de nuestracomputadora personal o equivalente.

xDSL (ADSL y equivalentes)

Una conexión ADSL es una conexión física permanente con el proveedor2 de lamisma. Por sobre esta conexión física, existe una conexión virtual bastante compleja3

mediante la cual se establece un enlace con el ISP. Este enlace es invisible paranosotros. La conexión con el ISP se realiza generalmente mediante un dispositivocapaz de dialogar con otro en el nodo del ISP, empleando el protocolo PPPoE(Point-to-Point Protocol over Ethernet). Al inicio del diálogo, se recibe unadirección y se establece una ruta por defecto que hace que nuestro dispositivo sepaque todos los demás dispositivos del mundo están más allá de esa conexión. Esedispositivo generalmente es el software de conexión a Internet por banda ancha denuestra computadora personal o equivalente, y en algunos casos puede ser el mismo

2 cuando éste no la interrumpe...3 por lo general es un circuito virtual en ATM, el puerto Ethernet del modem ADSL se ve conectado al

ISP

220

x y

Hub/Switch

Router

Otras redes

z

Page 239: El Camino Del Conejo

Switches, bridges, hubs, routers

modem ADSL, si éste es uno del tipo que incluye router y se lo configura como tal,cosa que los ISP generalmente no hacen.

Cable modem

Una conexión mediante cable modem es por lo general más parecido a unaconexión virtualmente directa a la Internet. El dispositivo conectado recibe unadirección y se establece una ruta por defecto que hace que nuestro dispositivo sepaque todos los demás dispositivos del mundo están más allá de esa conexión. Esedispositivo generalmente es el stack TCP/IP de nuestra computadora personal oequivalente.

IP Routing, DNS, direcciones

Según vimos, la comunicación entre los dispositivos de una red se hace a nivel-2,mediante elementos que los vinculan como hubs o switches. También vimos quepara poder comunicarnos con dispositivos de otras redes (por definición, aquélloscuya dirección de red es diferente de nuestra dirección de red), necesitamos unrouter.

Como sabemos, la dirección de red es el AND lógico entre la dirección IP y lamáscara de red. Para determinar si una dirección es o no perteneciente a nuestra red,hacemos el AND lógico entre la dirección que nos interesa y nuestra máscara de red,y la comparamos con nuestra dirección de red:

mi dirección IP: 192.168.69.96mi máscara: 255.255.255.0mi dirección de red:

192&255.168&255.69&255.96&0 = 192.168.69.0

dirección IP con que me quiero comunicar: 1.2.3.4algoritmo:

1&255.2&255.3&255.4&0 == 192.168.69.0 ?SI -> misma red

NO -> otra red

Dado que la dirección de red del destino a alcanzar es diferente de nuestradirección de red, necesitamos de un router. La dirección IP la obtenemos medianteun servidor DNS, al cual conocemos por su número, y al cual llegaremos tambiénmediante el router.Por lo general, en nuestra red privada, es decir, la que está en nuestro laboratorio o

empresa, vamos a utilizar direcciones de red privadas, es decir, aquéllas reservadasen las RFC para uso indiscriminado fuera de la Internet. Al conectarnos a Internet,nuestro ISP nos proveerá de una dirección IP pública, para que podamos navegar yhacer todas esas cosas que normalmente hacemos cuando estamos conectados a

221

Page 240: El Camino Del Conejo

Networking

Internet. Quien tiene la dirección IP pública, es el dispositivo que hace de router,cualquier otro dispositivo que quiera conectarse a la Internet, deberá utilizar a esedispositivo (el conectado a la Internet) como router, lo cual se indicará eligiéndolocomo la ruta por defecto. Pero, si en nuestra red tenemos direcciones privadas, losequipos de Internet no pueden saber como volver a las direcciones internas denuestra red, es necesario hacer algún tipo de traducción para que desde la Internetnos vean siempre como la dirección pública que nos asigna el ISP. Esteprocedimiento se llama de forma genérica NAT (Network Address Translation). Eldispositivo que hace la traducción es el mismo que se conecta a la Internet, puesdebe ser el que tiene la dirección IP pública, es decir, el router. Este dispositivotendrá dos interfaces, una para conectarse a nuestra red privada interna, y otra paraconectarse a la Internet. Si este dispositivo oficiando de router es una computadoracon Linux, lo que necesitamos es habilitar IP forwarding para que actúe como routere IP Masquerading para que haga NAT; si es una "PC común", necesitaremos algúntipo de software para "compartir" la conexión a Internet. En ambos casos, puedenexistir aspectos legales, y deberemos consultar a nuestro ISP si esto está permitidoen nuestro contrato.

Analogía cotidiana

El proceso de resolución y routing es sumamente simple, y aunque no lo parezca esla misma operatoria que uno hace para llamar por teléfono, sólo que no se piensa eneso.Supongamos que yo quiero llamar a mi amigo que vive cerca de casa; simplementetomo el teléfono y lo llamo. Esto es así porque yo sé su número de teléfono y sé queestá en mi misma red, aunque nunca me puse a pensarlo.Supongamos ahora que quiero llamar a Egberto Gismonti. Llamo a la embajada deBrasil y le pido el número de teléfono del señor Gismonti y llamo. Ese simpleproceso, implica que sé lo que es un DNS y sé routing.� Sé que para obtener el número de alguien tengo que usar un dispositivo auxiliar,

en el caso de la embajada: la guía telefónica; en el caso del Sr. Gismonti: laembajada (cuyo número obtuve de la guía previamente). El procedimiento esequivalente a la resolución mediante DNS.

� Para llamar a la embajada marco directamente el número, porque sé que está enmi misma red.

� Sé que para llamar a alguien que no está en mi red tengo que usar un dispositivoauxiliar (marcar el código de acceso internacional señaliza al centro deconmutación que establece la conexión internacional y comunica el resto delnúmero); esto es routing

Seguramente el Sr. Gismonti tiene un estudio u oficina, donde hay alguien queatiende la llamada y la deriva a su interno, porque el número de interno de su oficinadentro de su estudio no se publica en la guía. En este caso, la operadora está

222

Page 241: El Camino Del Conejo

IP Routing, DNS, direcciones

haciendo NAT. Seguramente también, el Sr. Gismonti estará muy ocupado y laoperadora nos dirá que no está; en este caso, ella está haciendo de firewall... y si nostoma el mensaje para transmitírselo y luego nos retransmite lo que el Sr. Gismonti lecontestó para nosotros, está haciendo de proxy...

DDNS

Un elemento de relativamente reciente aparición, dada la proliferación deconexiones a la Internet, es el DNS dinámico (Dynamic DNS).Como los ISP necesitan maximizar el uso de las direcciones públicas que poseen, y

como generalmente cobran tarifas extraordinarias para aquéllos que poseenservidores, son generalmente reacios a asignar direcciones IP fijas. Si nuestradirección está constantemente cambiando, no podemos tener un registro en un DNStradicional. Para resolver esta situación, y permitir que los hijos de vecina tengamosacceso a una cyber-vida mejor, DDNS permite que podamos dialogar con el DomainName Server que se encarga de nuestro dominio y actualizar periódicamente lasdirecciones IP de las máquinas que lo habitan. Esto se compone de dos partesfundamentales:

� La RFC-2136, que define el protocolo mediante el cual los DNS puedenaceptar y emitir actualizaciones de sus registros.

� Proveedores de dominios actualizables por el usuario, que proveen a éste deuna interfaz relativamente simple para realizar la actualización por sí mismoy en forma automática

Proveedores

Entre los proveedores más conocidos que proveen servicios gratuitos, sin desmedrode otros, tenemos a ZoneEdit y DynDNS. Ambos utilizan HTTP como transportepara recibir los updates. Todo lo que se necesita para interactuar con ellos es uncliente HTTP (un navegador o un programa como wget). Se informa al cliente losnombres de usuario y password, y se procede a realizar el pedido al proveedor, a lossiguientes URL:

ZoneEdit http://dynamic.zoneedit.com/auth/dynamic.html?

host=mihost.midominioqueregistreenzoneedit

DynDNS http://members.dyndns.org/nic/update?

hostname=mihost&myip=midireccionip&wildcard=NOCHG&mx

=NOCHG&backmx=NOCHG 4

En el caso de ZoneEdit, la dirección es determinada por el proveedor al recibir elpedido (de dónde viene). En cambio DynDNS es un poco más flexible, la direcciónse indica en el pedido. En este caso, dado que si estamos dentro de una red privada

4 Se recomienda al lector verificar esta información con el proveedor antes de invertir en la misma

223

Page 242: El Camino Del Conejo

Networking

es probable que no conozcamos nuestra dirección pública, existe un URL dondepodemos consultarla: http://checkip.dyndns.org

Firewall

Un firewall es un dispositivo destinado a aislar una red de las demás, bajocircunstancias controladas. Como dijéramos en el libro anterior, su nombre derivapor analogía con los “corta fuegos” utilizados para controlar los incendios,impidiendo que éstos se propaguen. Debido a que el firewall se encarga de impedirel paso de aquellos paquetes o datagramas que él y su configurador considerensospechosos o no provechosos para el uso del ancho de banda, en realidad, sunombre debería haber sido "portero de discoteca", "guardia de seguridad desupermercado", o equivalente.Un firewall se pone para proteger las redes privadas y los servidores de los ataques

de esos simpáticos personajes de las películas, que al no tener malvados alienígenasque combatir, se dedican a introducir porquerías en las redes de los demás.Por lo general, un firewall se configura para no permitir conexiones entrantes a la

red privada, y bloquear tráfico considerado no interesante o posiblemente malicioso.La definición de una política de seguridad para un firewall es algo que correspondeal personal de seguridad informática de la empresa, y de no existir dicho personal esrecomendable consultar a gente con amplia experiencia en el tema.El funcionamiento del firewall es sumamente complejo, pero en TCP generalmente

obedece a principios relativamente simples:� impedir conexiones implica bloquear el paso de los paquetes TCP SYN, que son

los que inician la conexión.� impedir tráfico implica bloquear todos los demás paquetes. Generalmente, el

firewall descarta cualquier paquete TCP si no vio un SYN anterior; el mandarsegmentos inválidos es un conocido ataque de DoS (Denial of Service: bloquearla red del prójimo con paquetes sin información).

En UDP es algo más complejo, dado que al no ser orientado a conexión, no hay unamáquina de estados de protocolo que se pueda seguir. Sin embargo, el firewall armainternamente una, observando la condición anterior. Por ejemplo, si no se permitetráfico UDP entrante, un datagrama UDP que ingresa sólo podrá hacerlo si se loconsidera respuesta a uno saliente, para lo cual, cada vez que sale un datagrama, seabre una ventana de tiempo, y si vuelve uno con los mismos números de puerto(obviamente permutados fuente y destino y lo mismo con las direcciones IP...) se lodeja pasar.Si nuestra conexión es detrás de un firewall, deberemos consultar al administrador

para que nos informe qué tenemos autorizado y si puede cambiar algo de laconfiguración para permitir que nuestro dispositivo pueda conectarse a donde debe.Para bajar y subir correo, deberán existir permisos de conexión saliente y tráfico enlos puertos 110 (POP3) y 25 (SMTP). Si tenemos algún otro número de puertoprivado, deberemos informarlo y solicitar se habilite la conexión.

224

Page 243: El Camino Del Conejo

IP Routing, DNS, direcciones

FTP es algo más complicado debido a la existencia de conexiones en ambossentidos; por lo general, en los lugares donde existe un firewall, los clientes FTPnecesitan permiso saliente en el port 21 (conexión de control), y funcionarán enmodo pasivo5, lo cual implica que el servidor indicará a qué dirección y puertodeberán conectarse. Si el firewall tiene impedido el tráfico saliente, puede que estono funcione, si el firewall no detecta que se trata de una conexión de FTP. La otraposibilidad es permitir tráfico entrante por puerto 20, pero muy poca gente permiteconexiones entrantes (y menos aún en los puertos de FTP).

Si nuestro equipo es un servidor (un Rabbit con una página web es un servidor),deberemos solicitar permiso de conexiones entrantes al puerto de la aplicacióncorrespondiente, y el NAT que permita traducir de la dirección pública en que "seve" nuestro servidor en Internet, a la dirección privada que tiene en la red local. ParaHTTP, será el puerto 80. Para servidor FTP, tendremos conexiones entrantes alpuerto 21 y conexiones salientes desde el puerto 20, excepto que se permitaconexiones pasivas, lo cual significa que el servidor recibirá conexiones en lospuertos que él defina; esto último es sumamente complicado si el firewall noentiende el diálogo FTP. Por lo general, se coloca al servidor FTP en un área orango de direcciones donde no se protege, es decir, una DMZ, como veremos acontinuación.

Algunos firewalls tienen lo que se denomina DMZ (DeMilitarized Zone: zona nomilitarizada), que pese a su nombre es bastante simple. Se trata de una boca de reden la que se conectan los equipos a los que el mundo exterior puede acceder, de estaforma se evita que el tráfico externo ande boyando por nuestra red interna, y si algúnhacker logra entrar a uno de estos equipos, aún tiene otra barrera hacia la red interna,donde está la información sensible (servidores de bases de datos, documentaciónservida internamente, etc.). Los firewalls que no tienen un puerto de red especialpara esto, permiten configurar accesos por dirección IP con una función equivalente.

En los lugares más modestos, el firewall probablemente sea un servidor Linux oequivalente, pero las reglas serán más o menos las mismas. El tema es que el firewallva a bloquear principalmente ICMP y todo lo que considere sospechoso, por lo queno va a ser muy simple realizar una puesta en marcha si no tenemos la asistencia dealguien que pueda ver el otro lado de la red a través del firewall e informarnos si haytráfico descartado o si estamos llegando al otro lado.

Proxy

Un proxy es algo así como un intermediario. El más conocido es el proxy HTTP.La configuración típica es permitir conexiones y tráfico saliente/entrante solamenteal proxy, quien será el encargado de ir a buscar todas las páginas que se le pidan, ygeneralmente las almacenará en un buffer local, de modo de tenerlas listas cuandootro usuario (o el mismo) las pida en el futuro. El funcionamiento interno es más que

5 Para más detalles, se recomienda la lectura de la sección sobre FTP en el capítulo de networking enel libro introductorio

225

Page 244: El Camino Del Conejo

Networking

complejo; externamente sólo requiere que el navegador se configure para utilizar elproxy. Un navegador así configurado no requiere resolver nombres, debido a querealiza la petición de la página por su URL al proxy, por lo que no es necesarioacceso al DNS; en una red en la que los usuarios solamente navegan, es la conexiónmás segura. Solamente el proxy y los servidores de correo tienen permiso deconexión saliente/entrante, y generalmente se los coloca en otra red; los usuariossólo conocen lo necesario para llegar a estos servidores.Desde nuestro punto de vista como desarrolladores, si nuestro equipo es accedido

por operadores que tienen su navegador configurado para utilizar un proxy, deberánconfigurar éstos para que (en lo posible) no utilicen el proxy para acceder a nuestroequipo, particularmente si están en la misma red privada. Si los navegadores utilizanel proxy para acceder a nuestro equipo, es posible que el proxy cachee las páginas yel usuario no se dé cuenta de los cambios, por lo que se recomienda instruir a losusuarios a refrescar manualmente las páginas cada vez que acceden a nuestro equipo.

Receta para agregar nuevos dispositivos

Asumimos que el nuevo dispositivo a agregar es algo que utilizamos para eldesarrollo de una aplicación (un Rabbit, por ejemplo), y que deberá tener una ciertaconexión con otros dispositivos dentro del local y fuera del mismo, esto último através de la Internet. Para otros casos, la operatoria es muy similar.

Red con administrador

Si estamos en una red que tiene un administrador, deberemos solicitarleinformación sobre qué nos está permitido y qué no. De igual modo, deberemosinformarle nuestros requerimientos y solicitarle permiso para las conexiones quedebamos hacer. Él nos indicará nuestra dirección IP o nos dará un rango dedirecciones que podemos utilizar y la máscara de red, nos informará la dirección delDNS y del router por defecto (default router), o nos dirá que todo eso se resuelve porDHCP, que configuremos nuestro dispositivo para DHCP. En este último caso, sitenemos un servidor, dado que los demás dispositivos deberán conectarse a él, elservidor DHCP deberá no es la opción recomendable, a menos que esté acompañadopor un DNS interno acoplado, es decir, pueda emitir actualizaciones y el DNS lasentienda, y/o siempre entregue la misma dirección IP. Algo que los demásdispositivos necesitan para conectarse al nuestro es una dirección conocida a dondehacerlo, sea ésta nombre o IP. Afortunadamente, los servidores DHCP y DNSactuales soportan esta interacción, implementando la RFC-2136.

Red sin administrador

Si no hay un administrador de red, deberemos personificarlo. A menos que elnuestro sea el único dispositivo conectado a la red (con lo cual no es una red sinouna conexión a Internet...), es altamente recomendable poner un firewall. Si el

226

Page 245: El Camino Del Conejo

IP Routing, DNS, direcciones

nuestro es el único equipo, todo depende de qué tan confiable sea ese equipo,sistemas dedicados como el Rabbit suelen tener un grado de robustez mucho másalto que una "PC común", y un firewall no sería necesario.Nuestras direcciones de red serán de las reservadas para aplicaciones privadas, se

recomienda utilizar 192.168.x.0 como dirección de red (por ejemplo: 192.168.1.0),los hosts tendrán el último byte desde 1 hasta 254, y la máscara será 255.255.255.0.

En todo caso, si existen otros dispositivos, puede obtenerse esta informaciónobservando su configuración de parámetros de red, en la interfaz correspondiente.En el caso de Linux, el comando es ifconfig, y las interfaces Ethernet suelen ser eth0,eth1, etc.

Firewall

La forma más simple y efectiva de firewall es un pequeño servidor Linux con elfirewall habilitado; amantes de otras soluciones preferirán otras cosas. En cualquiercaso, el firewall debería permitir conexiones salientes (confiamos en los usuariosinternos) e impedir conexiones entrantes, excepto a los servicios que tengamos ennuestro dispositivo y/u otros servidores, si los hay.

Router + NAT

El router será el encargado de conectarnos al mundo exterior, y deberá realizar lafunción de NAT para poder permitir que las respuestas a nuestras peticiones lleguen.Caso contrario, los paquetes que salgan con dirección 192.168.algo.alguien condestino a www.yahoo.com puede que lleguen, pero no las respuestas, porque ningúnrouter de la Internet sabe como llegar a una dirección privada que no le pertenece.La forma más simple y efectiva de router + NAT es un pequeño servidor Linux con

IP Masquerading habilitado, incluso puede ser el mismo que oficia de firewall;amantes de otras soluciones preferirán otras cosas.

La interfaz a la red local será un puerto Ethernet y tendrá una dirección de nuestrared. La interfaz a la red pública (Internet) depende del tipo de conexión, según vimosen el apartado sobre el ISP. En todos los casos (dial-up, cable modem, ADSL yequivalentes), el router obtendrá su dirección IP pública y parámetros de red del ISP,esto incluye máscara, tabla de ruteo (generalmente una ruta por defecto) y DNS.Algunos modems ADSL y WiFi routers permiten funcionar como router + NAT e

incluso hasta firewall.

DNS

Lo más común es utilizar el DNS que nuestro ISP nos provee, pero si éste loinforma por DHCP o PPP/PPPoE, tenemos dos opciones:� Observar en el router qué DNS le ha sido configurado y configurar internamente

en nuestros dispositivos ese DNS.� Instalar un servidor DNS en nuestro firewall/router + NAT, lo cual es

perfectamente factible con un servidor Linux. Algunos modems ADSL y WiFi

227

Page 246: El Camino Del Conejo

Networking

routers permiten funcionar como DNS esclavo, sirviendo a la red interna.Configurar internamente en nuestros dispositivos la dirección del router comoDNS.

Dispositivos

Resuelto el tema de red, los dispositivos internos deberán configurarse con unadirección IP de nuestra red local, con la máscara correspondiente, su default gatewayserá el router + NAT que estemos utilizando, y su DNS será el provisto por el ISP oel que hayamos levantado local, si lo hemos hecho.

Resolución de problemas (troubleshooting)

Una vez conectados, deberemos verificar que tenemos conexión con quienqueremos conectarnos. El punto es que, sin poder ver lo que pasa en la red es muydifícil tratar de determinar una causa. Un analizador de tráfico será nuestros ojos ynos permitirá ver qué es lo que está pasando en la red, y por qué nuestros mensajesno llegan a destino.Algunos dispositivos incluyen opciones de depuración como por ejemplo tcpdumpen Linux y demás Unixes. Si bien es sumamente poderoso si se lo sabe usar, resultaalgo poco práctico para verdaderos problemas de red. Sin embargo, para la mayoríade los inconvenientes cotidianos, puede ser más que suficiente. El listado siguientemuestra el tráfico recibido por el host Ellie en su interfaz eth0, que no proviene delhost 192.168.1.51 (la PC desde la que me conecto por telnet a Ellie):

[root@Ellie /root]# tcpdump -i eth0 not host 192.168.1.51tcpdump: listening on eth013:58:51.219506 Ellie.34126 > ns1.arnet.com.ar.domain: 43124+ PTR?250.255.255.239.in-addr.arpa. (46) (DF)

228

Modem

Red local

Internet

Access server

Modem

Router

Router

ISP

Lab

Firewall

Red

Dial-up

PSTN

Modem telef.

Cable modem

Cable modem

o equivalente

PSTN (cable) ++ ATM + ...

xDSL

Modem xDSL

* El presente es un diagrama generico y conceptual

la implementacion de cada ISP puede ser diferente

PPP IP PPPoE

IP

Red CATV ++ ...

Page 247: El Camino Del Conejo

IP Routing, DNS, direcciones

13:58:51.252637 ns1.arnet.com.ar.domain > Ellie.34126: 43124 NXDomain 0/1/0(103) (DF)13:58:51.253457 Ellie.34126 > ns1.arnet.com.ar.domain: 43125+ PTR?254.1.168.192.in-addr.arpa. (44) (DF)

13:58:51.266351 ns1.arnet.com.ar.domain > Ellie.34126: 43125 NXDomain 0/1/0(121) (DF)13:58:51.267965 Ellie.34126 > ns1.arnet.com.ar.domain: 43126+ PTR?35.191.45.200.in-addr.arpa. (44) (DF)13:58:51.281234 ns1.arnet.com.ar.domain > Ellie.34126: 43126 1/2/2 (147) (DF)

6 packets received by filter0 packets dropped by kernel[root@Ellie /root]#

Incluso Dynamic C incluye algo similar dentro de sus bibliotecas de funciones,como veremos en el capítulo sobre networking con Rabbit. Nos referimos a lasmacros _VERBOSE, por ejemplo DCRTCP_VERBOSE.

Otra opción interesante es proveerse de un analizador de tráfico o analizador deprotocolos, como por ejemplo Ethereal o Wireshark, software que corre bajo Linux(y otras opciones) y es totalmente gratuito. Este tipo de programas permite ver deforma detallada y mucho más amigable el tráfico de la red, pudiendo detallarse entodo momento el tipo de protocolo que se desea observar:

La operatoria genérica de resolución de problemas en networking consiste enobservar el tráfico en ambos sentidos y tratar de determinar, aplicando los conceptos

229

Page 248: El Camino Del Conejo

Networking

aprendidos, dónde está el origen del problema. Por lo general, lo primero que seobservará es que la respuesta no llega, o que la pregunta no sale. El paso siguiente esbuscar un nivel más lejos: si la pregunta no sale, revisar programa/aplicación y sucorrespondiente configuración en nuestro dispositivo. Si la respuesta no llega,observar cuidadosamente si lo que sale es coherente, y luego verificar en el router siel pedido realmente está saliendo al exterior y la respuesta entrando. Debe tenersesiempre presente la topología de la red; donde hay firewalls, es altamente probableque sea éste quien esté descartando la información.

Aplicaciones y particularidades

UDP broadcast

UDP es connection-less, no existe garantía de entrega de la información. Estacarencia de conexión permite que se pueda enviar mensajes a todos los hosts de unared, lo que se conoce como UDP broadcast, y también que se pueda recibirmensajes de cualquier host, sin conocerlo previamente (lo cual se deduce del hechode que es posible mandar mensajes a cualquiera...)

Desconexiones y sockets abiertos

TCP

Como es bien conocido, TCP es un protocolo orientado a conexión, y en un stackTCP/IP, un socket representa una conexión. En el socket, los valores guardados sonaquéllos que lo identifican unívocamente: dirección IP fuente, port fuente, direcciónIP destino, port destino, y tipo de protocolo. Como es de imaginarse, si alguno deestos parámetros cambia, el socket ya no es válido. De igual modo, es de esperarseque un equipo no reciba paquetes que no corresponden a su dirección IP, y un routerno es capaz de alterar la IP de destino, de igual modo que el cartero no tiene por quésaber que Juan Pérez se mudó al 455 de la Avenida de las Acacias Verdes y ya noatiende en Paraje Perdido 322.

Esto significa, que si por algún motivo mi dirección IP o la del otro extremocambian, la aplicación deberá recuperarse del inconveniente; por ejemplo cerrandotodas las conexiones abiertas y reiniciándolas si es necesario.

"Por algún motivo" incluye PPP, DHCP, cambio manual de la dirección IP yequivalentes imaginables.

UDP

Dado que en UDP no existe el concepto de conexión, no hay de qué preocuparse sise interrumpe la conexión de red. No obstante, dado que en el socket, los valores

230

Page 249: El Camino Del Conejo

Aplicaciones y particularidades

guardados son aquéllos que lo identifican unívocamente: dirección IP fuente, portfuente, dirección IP destino, port destino, y tipo de protocolo, si alguno de estosparámetros cambia, el socket ya no es válido. Esto significa, que si por algún motivomi dirección IP o la del otro extremo cambian, deberán modificarse los socketscorrespondientes a comunicaciones UDP en curso, que fueren orientadas a algúnhost en particular, lo que generalmente se traduce en cerrarlos y volverlos a abrir.Los sockets abiertos en modo broadcast no tienen este problema, a menos quecambie la subnet, lo cual es algo menos probable.

Timing, entrega a domicilio, ancho de banda

UDP

Debido a su simpleza y dado que es no-orientado a conexión, todo lo que se entregaresulta enviado cuando uno lo pide; si bien el orden de llegada (y el hecho de llegar)a destino de los datagramas puede ser afectado por la red. El cálculo de ancho debanda ocupado se hace en función de la cantidad adicional de información enviadaen encabezados; cualquier otro tipo de análisis deberá hacerse a un nivel más alto, esdecir, considerando al protocolo de orden superior del que la aplicación se vale; omás bajo, por ejemplo IP TOS6 o QOS.

TCP

En el caso de TCP, se trata de un stream continuo. La entrega de la información enla secuencia prevista está garantizada, pero el timing es sumamente complicado. Enprimer lugar, al ser un protocolo del tipo de ventana deslizable (sliding-window), sibien la ventana se acomoda a las condiciones del entorno, ésta tiene un máximo, ydicho máximo a veces puede no ser adecuado en enlaces de mucha demora y/omucha velocidad. Sin embargo, no es el tipo de problemas con el que nosencontraremos en un sistema dedicado, y podemos dejar su análisis a los expertos denetworking.Otros temas relacionados con la ventana y el timing son el inicio lento (slow start),

el algoritmo de Nagle, el de Van Jacobson, el de Clark... Lo interesante aquí es quees TCP quien decide cuánto ancho de banda usa, demorando la salida y/o elreconocimiento de los datos de modo de aprovechar mejor el ancho de bandadisponible; acomodando el tamaño de la ventana y adaptándose a los cambios. Esdecir, no siempre que le pedimos que transmita, los datos salen inmediatamente; ydesde luego, en el extremo remoto no necesariamente serán entregados en lasmismas fracciones en que fueron escritos.El análisis del comportamiento de un protocolo como TCP a los diferentes entornos

es tema de otro tipo de textos, y de otro tipo de profesionales. Para nuestros

6 El encabezado de IP dispone de un espacio como para establecer la calidad de servicio que se deseao prefiere. Si se respeta esto o no, es algo en lo que muchas redes no están de acuerdo.

231

Page 250: El Camino Del Conejo

Networking

propósitos, lo que necesitamos saber es que se trata de un protocolo que se adapta, yque su inteligencia propia puede interactuar con nuestra determinación, por lo que sirealmente necesitamos tener control del tiempo, tal vez no sea la elección másapropiada; particularmente si no estamos en condiciones de entender lo que sucedeen el intercambio entre ambos TCPs.A modo de introducción y resumen, podemos hacer algunos comentarios sobre lo

que más probablemente puede afectarnos en un sistema dedicado.El algoritmo de Nagle opera sobre la transmisión, demorando el envío de nueva

información si ya existe información anterior que no ha sido confirmada. Es decir, simi aplicación envía algunos bytes cada un tiempo, TCP enviará la primera vez, yluego demorará los subsecuentes hasta tanto llegue el reconocimiento de lo primero,momento en el cual enviará lo que guardó hasta ese instante. Esto logra unaexcelente economía, pero altera el tiempo de entrega. Si nuestra aplicación necesitamínima demora o poca variación de ésta, es conveniente no habilitarlo.

Los demás algoritmos se orientan mayormente al control de congestión,retransmisiones, y manejo de la ventana deslizante; no es de esperarse que nosafecten. Lo que debemos saber, es que la detección de una desconexión, es decir, dela interrupción no deseada de una conexión por un motivo ajeno a ambas partes, enla que no se recibe una indicación de desconexión; se detecta mediante timeouts.Dichos timeouts dependen en cierto modo de estos algoritmos, y es de esperarsetiempos largos, dado que el protocolo realmente intenta que la conexión se mantengaactiva. En algunos sistemas, es posible controlar algunos parámetros que nospermitirán tener una respuesta más rápida a este tipo de eventos.

Finalmente, TCP es un algoritmo adaptativo y converge rápidamente a losparámetros óptimos de funcionamiento. La mayoría de los sistemas dispone de algúnmedio por el cual configurar valores iniciales de estos parámetros, de modo que esposible ayudar a TCP a converger más rápido en el entorno en particular que nosinteresa.

Wi-Fi

Por Wi-Fi se suele denominar a una serie de protocolos englobados en losstandards IEEE 802.11. Particularmente, los más utilizados (al menos mientras estasletras se secan en el papel) son 802.11b y 802.11g. Estos standards definen toda laoperatoria de RF y acceso al medio; 802.11b es el anterior, de velocidad más baja, y802.11g puede funcionar en un modo de compatibilidad 802.11b, permitiendo lacoexistencia en una red de dispositivos 802.11b (generalmente PDAs y dispositivossimples) y 802.11g (generalmente laptops).Dos dispositivos con capacidad Wi-Fi pueden comunicarse entre sí utilizando uno

de los canales permitidos, mediante la modalidad ad-hoc. También pueden,utilizando la modalidad infrastructure (infraestructura), comunicarse con un tercero,que es quien administra y provee interconexión Wi-Fi y con el mundo cableado

232

Page 251: El Camino Del Conejo

Wi-Fi

Ethernet; sea ésta por bridging o por routing. Se los conoce con el nombre genéricode access points (puntos de acceso). En lo particular, los dispositivos que ademásimplementan routing suelen autodenominarse wireless routers (routersinalámbricos).Un dispositivo conectado a un access point puede comunicarse con uno Ethernet o

con otro conectado al mismo access point, de forma indistinta. También puede, porextensión, comunicarse con otro dispositivo wi-fi conectado a otro access point encualquier parte del mundo que esté vinculado por cable a una red, ambas ruteablesentre sí. Desde la Ethernet ambos se ven como estaciones Ethernet detrás de unbridge o router.

Identificación

Una red 802.11 se da a conocer por un identificador de treinta y dos bytesdenominado SSID (Service Set IDentifier). Si bien puede incluir cualquier valorbinario, es común utilizar caracteres ASCII para que los humanos podamosreconocerlo.

Al momento de elegir una red, sea de forma manual o automática, se debeseleccionar un SSID.

Este identificador es normalmente difundido en forma periódica por los accesspoints de modo de permitir que la red sea identificada fácilmente. También esposible suprimir este broadcast periódico y mantener la red en el oscurantismo. Sinembargo, en el momento en que un dispositivo se incorpora a la red, el SSID estransmitido sin cifrar y puede ser visto por pacientes observadores a la pesca denuestro identificador de red.

Seguridad

Dado que los datos que transmitimos por Wi-Fi pueden ser vistos por cualquierindividuo inescrupuloso con una antena de suficiente ganancia y un dispositivocompatible, existen algunas opciones de cifrado y autenticación para intentargarantizar que sólo aquéllos autorizados puedan acceder a la red y a su contenido.El primer intento es WEP (Wired Equivalent Privacy, privacidad equivalente a la

provista por un cable), que posee algunas falencias conocidas mediante las cuales esposible extraer la clave monitoreando algunos millones de mensajes. Hasta se handesarrollado algoritmos para poder estimarla con un 50% de probabilidad conalgunas decenas de miles de mensajes. Digamos pues, que si bien puede seguirsiendo útil para mantener al hijo adolescente del vecino a raya, no debemostransmitir por una red con WEP los datos de nuestra cuenta bancaria; no hace honora su nombre.

WEP puede ser utilizado para autenticar como para cifrar los datos, y se basa enuna clave compartida (shared key). Se recomienda no utilizarlo para autenticación.La introducción de WEP corresponde al standard 802.11 original, actualmentereemplazado por 802.11i.

233

Page 252: El Camino Del Conejo

Networking

Es común encontrar WEP en PDAs y dispositivos que soportan sólo 802.11b.El segundo intento es WPA/TKIP (Wi-Fi Protected Access using Temporal Key

Integrity Protocol, acceso protegido a Wi-Fi usando el protocolo de integridadtemporaria de claves). Esto fue introducido por la Wi-Fi Alliance antes de que seterminara el standard 802.11i, que se ocupa justamente de este problema, y proveeun nivel de seguridad superior comparado con WEP sin modificar el hardwareexistente, lo cual fue justamente su razón de existir. Sin embargo, como todo lo quese hace a las apuradas, tiene algunos pequeños detalles que vienen de arrastre y hansido puestos en evidencia por varios grupos de investigadores. De todos modos, eltiempo y esfuerzo necesarios para romper la seguridad de WPA/TKIP son mayoresque el valor de lo que solemos transportar en una red domiciliaria y el costo de unacceso a Internet; sin mencionar el hecho de que al momento sólo permiten ingresaralgún que otro paquete, no permiten obtener la clave. Por lo tanto, podemos dormirtranquilos si nuestro access point no tiene algo mejor.Es común encontrar WPA/TKIP en dispositivos 802.11 b/g desarrollados antes del

2008.El tercer intento es WPA2/CCMP. CCMP (Counter Mode with Cipher Block

Chaining Message Authentication Code Protocol) utiliza un esquema basado en AES(Advanced Encryption Standard) con clave de 128-bits, según recomienda FIPS-197.Esto corresponde a la implementación completa de 802.11i. Es común encontrar WPA2/CCMP en sistemas más recientes y poderosos, dada la

cantidad de recursos que requiere en comparación con los anteriores. También se lasuele llamar WPA2/AES.

Existen también algunos WPA/CCMP, que consisten en implementaciones sobreWPA/TKIP del componente CCMP, que al ser desarrollado después de WPA esopcional para ésta. Dichas implementaciones proveen una baja compatibilidad entredispositivos y no son muy recomendables.

Personal vs. Enterprise

Hemos visto los métodos de cifrado, pero todos dependen de una clave. Dichaclave puede ser pre-compartida, como en PSK (Pre-Shared Key), con lo cual toda laseguridad depende de qué tan difícil de adivinar sea esta clave, como en cualquiersistema de passwords. Éste es el método más común para aplicaciones personales,residenciales, y de pequeñas y medianas empresas.

En WPA y WPA2, la clave pre-compartida se utiliza para generar clavestransitorias, de modo de generar pequeños cambios de clave con una determinadafrecuencia para mantener mayor seguridad en la totalidad de la sesión.

Para aplicación en ambientes empresariales y/o de alta seguridad, se utiliza EAP(Extensible Authentication Protocol), un grupo de protocolos que implementan elstandard 802.1X y requieren de un servidor de autenticación, generalmente

234

Page 253: El Camino Del Conejo

Wi-Fi

RADIUS. El más conocido es EAP-TLS (Transport Layer Security), pero en 2010ingresan a la colección muchas más alternativas.

Wireless bridging

Para interconectar dos dispositivos wi-fi mediante dos access point diferentes, nointerconectados entre sí por cable, se requiere de un tipo especial de access pointque soporte lo que se conoce como wireless bridging. Si bien esto puede serconfuso, dado que un access point ya está haciendo bridging entre los dispositivosinalámbricos y la red Ethernet, el concepto de wireless bridging al que nos referimoses el bridging de dos redes Ethernet mediante una conexión inalámbrica entre losaccess points:

Este tipo de conexión, permite que dispositivos sin capacidad wi-fi puedan serconectados de forma inalámbrica a una red cableada existente, y dialogar tanto condispositivos inalámbricos como con cualquier otro dispositivo de la red Ethernet. Porejemplo, con cualquier access point, A y B pueden dialogar con C; mientras que Dpuede dialogar con E y cualquier otro host de la Ethernet y cualesquiera otros queestén ruteados desde allí. Si los access points tienen capacidad de wireless bridging,tanto A como B y C pueden dialogar con D, E, la Ethernet y el resto del mundo através de allí.

GSM

GSM (Global System for Mobile communications) es el standard corriente enmateria de comunicaciones celulares. La existencia de algunos protocolos quepermiten una fácil comunicación desde dispositivos inteligentes y la cobertura enzonas donde se dificulta acceder con cable, lo hacen sumamente interesante para

235

Ethernet

E

A

B

C D

Access Point

Access Point

Page 254: El Camino Del Conejo

Networking

desarrollos en diversas áreas. Si bien existen otras alternativas como la red celularTDMA, radioenlaces, satélites, etc; el costo y la versatilidad no acompañan a los dosúltimos, y la complejidad de la interfaz y falta de modems e información oficial, mássu prometida caducidad afectan al primero.

Los dispositivos que emplearemos son generalmente modems GSM, los cualesmanejarán la complejidad de la red GSM y serán controlados mediante una interfazrelativamente simple, generalmente extensiones GSM standard y propietarias a loscomandos AT.

Este es un campo del networking que excede los alcances de intención de estecapítulo, pero dado que cada vez son más los dispositivos que emplean una red GSMpara formar parte de una red TCP/IP, incluimos aquí una breve descripción de lasformas más prácticas y comunes de acceder actualmente a estos beneficios.

CSD

CSD (Circuit Switched Data) es la forma más antigua de comunicación de datosdentro de GSM. Como se describe en el capítulo sobre conectividad, en CSD seestablece una conexión a un determinado ancho de banda, y se reserva ese ancho debanda por el tiempo de duración de la misma, más allá de si hay tráfico o no. Esprácticamente equivalente a realizar una llamada telefónica con un modem. El costode la comunicación es alto debido a que generalmente se tarifa por tiempo deconexión, y las velocidades de comunicación no suelen ser elevadas.El modem GSM nos permite establecer una conexión al destino solicitado y cursar

datos por la misma, es útil cuando queremos conectarnos con un dispositivo remotosimple, no es necesario manejar un stack de protocolo dado que la conexión es puntoa punto una vez que el modem GSM atiende la llamada CSD.Por lo general, el inicio de la conexión se realiza de forma similar a un modemtelefónico, ya sea originando una conexión saliente o atendiendo una conexiónentrante.

PPP sobre CSD

Si la empresa prestataria del servicio GSM lo permite, es posible conectarse acualquier ISP (o uno interno) mediante CSD, estableciendo luego una conexión PPPcomo si se tratara de una llamada dial-up con un modem en una línea telefónicanormal. El dispositivo conectado al modem es quien maneja el stack TCP/IP y elprotocolo PPP, el modem y la red GSM actúan como si se tratara de la redtelefónica.El establecimiento de la conexión es idéntico al caso anterior, dado que la red GSM

solamente establece el vínculo como si fuera la PSTN. Lo único que necesitamossaber es el número asignado por el ISP para la conexión. Algunos prestatarios dan unnúmero abreviado, por ejemplo *638.

236

Page 255: El Camino Del Conejo

GSM

Si se permite conexión con la PSTN, podemos utilizar cualquier ISP como siestuviéramos en la PSTN: simplemente marcando su número de abonado.

GPRS

GPRS (General Packet Radio Service) es dentro de la arquitectura GSM eltransporte de datos que sucede a CSD, y permite transportar la información por loscanales no utilizados, sin reservar ancho de banda en la red. Las características

237

red GSMPSTN

Internet

CSD

PPP

IP

Módulo GSM

Serie Asinc PCM

ISP

Modem

Funcionalidad de

red GSMInternet

CSD

PPP

IP

Módulo GSM

Serie Asinc

Funcionalidad de

Access Server

Page 256: El Camino Del Conejo

Networking

fundamentales desde nuestro punto de vista son su relativo bajo costo, debido a quegeneralmente se tarifa por tráfico, y la posibilidad de obtener altas velocidades detransferencia al adjudicarse grandes anchos de banda por instantes pequeños, para latransferencia de la información. GPRS originalmente se definió para transportar IP,PPP, y X.25.Una característica interesante es la existencia de gateways desde la red GSM hacia

la Internet, por lo que es posible poner "en red" dispositivos en lugares remotos y/ode difícil acceso, o incluso móviles, tal es el caso de los recientemente proliferadossistemas de localización vehicular.

IP sobre GPRS

El dispositivo de la red GSM solicita la asignación de una dirección IP, la cualgeneralmente corresponde a una dirección de red privada, y a continuación puedeiniciar conexiones TCP o mandar datagramas UDP a otros dispositivos. El gatewaydel prestador que mantiene la conexión con la Internet realiza la traducción (NAT) adirecciones públicas.El modem GSM provee toda la inteligencia y el stack TCP/IP, por lo que es posible

utilizar micros sin stack TCP/IP. Esto puede ser una ventaja para lograr un bajocosto, pero limita las aplicaciones, ya que no es posible establecer una comunicacióncon un dispositivo remoto a menos que éste la inicie, dado que sólo obtiene unadirección IP por un tiempo limitado, y ésta corresponde a un grupo de direccionesprivadas, válidas sólo dentro de la red GSM del prestador. Además, dado que lacomunicación se establece solicitándola mediante una serie de comandos AT almodem GSM, todo el manejo de la misma debe hacerse mediante este sistema, locual limita la cantidad de comunicaciones y/o servicios simultáneos que quierandarse.

Hemos visto un ejemplo de aplicación en el capítulo sobre conectividad.

238

red GSM Internet

GPRS

Módulo GSM

Serie Asinc

Gateway

IP

Page 257: El Camino Del Conejo

GSM

PPP sobre GPRS

Esta opción es mucho más interesante para micros con stack de protocolo propio,como el Rabbit, dado que permite que el dispositivo conectado al modem GSMobtenga una dirección IP via PPP, de forma similar a una conexión dial-up, perominimizando el costo, dado que no existe una conexión fija sino un transporte de losdatagramas IP sobre PPP sobre GPRS, es decir, sólo se usa el ancho de bandacuando existe tráfico para cursar.La comunicación se establece solicitándola mediante una serie de comandos AT al

modem GSM, en el mismo procedimiento se indica que es una conexión PPP. Ladirección IP asignada generalmente corresponde a una dirección de red privada. Elgateway del prestador que mantiene la conexión con la Internet realiza la traducción(NAT) a direcciones públicas. Tampoco es posible conectarse con un dispositivoremoto de este tipo a menos que éste inicie la conexión, pero sí es posible tenermuchas conexiones simultáneas a diversos puntos, dado que el stack de protocolocorre en el dispositivo externo a la red.

Tendremos ejemplos de esto en el capítulo sobre networking con Rabbit.

Tethering

Por este nombre se conoce al usar un dispositivo móvil como modem de otro paraacceder a la Internet. Dado que tether como verbo en inglés es la acción de atar a unanimal a algo fijo para limitar su radio de movimiento, tal vez esta terminologíaaluda al hecho de que la conexión queda limitada al radio de conexión ocomunicación con el dispositivo móvil usado para oficiar de modem.

239

red GSM Internet

GPRS

PPP

IP

Módulo GSM

Serie Asinc

Gateway

Page 258: El Camino Del Conejo

Networking

Un caso común es el usar un teléfono celular para conectar una PDA u otrodispositivo con Bluetooth para ganar acceso a la Internet mediante por ejemploGSM. En este caso se trata de una conexión PPP sobre GPRS vía Bluetooth, la cualdesarrollaremos en un ejemplo en el capítulo sobre networking con Rabbit.

240

Page 259: El Camino Del Conejo

Networking con Rabbit

Introducción

La implementación de TCP/IP en Dynamic C es lo suficientemente versátil comopara permitir abrir sockets TCP y UDP, llamando a funciones como tcp_open() yudp_open(). También es posible crear servidores que esperen una conexión TCP en

determinado port llamando a tcp_listen(). Una vez establecido el socket, podemosenviar y recibir información mediante funciones como sock_read() y sock_write()para TCP, y udp_send() y udp_recv() para UDP. Hay muchas más opciones, y elentorno en general es similar al que encuentra alguien familiarizado en programaraplicaciones de networking (Berkeley sockets).Para que el stack TCP/IP se mantenga activo, deberemos llamar periódicamente al

handler que lo atiende: tcp_tick(). Generalmente esta función es llamada al inicio delloop, dentro de un loop infinito que atiende todas las tareas; de esta forma, elprograma principal regula el momento y la periodicidad con que cede el procesadoral stack TCP/IP.

Direcciones IP

Los parámetros de red pasados a las funciones son valores en formato "demáquina", es decir, para ser entendidos y manejados por el stack TCP/IP (una

palabra de 32 bits). La traducción desde el formato humanamente manejable (cuatronúmeros decimales separados por puntos) la realizamos mediante la funcióninet_addr(). En el caso inverso, si queremos visualizar uno de los valoresconfigurados, emplearemos la función inet_ntoa().

longword ipaddress;char my_ip[16];

ipaddress=inet_addr("192.168.1.2");

inet_ntoa(my_ip,ipaddress);

El tipo longword es definido por dcrtcp.lib, la biblioteca de funciones que contieneel stack TCP/IP.

Resolución por DNS

Cuando sólo disponemos del nombre del host con el que nos queremos comunicar,deberemos resolver este nombre a una dirección IP. El proceso de resolución es una

241

Page 260: El Camino Del Conejo

Networking con Rabbit

búsqueda iterativa consultando a los servidores que atienden cada dominioinvolucrado en el nombre; afortunadamente, el servidor DNS que tengamos asignadohará esta tarea por nosotros, solamente deberemos pedirle que lo haga. No obstante,es un proceso que lleva tiempo, desde que se hace la pregunta hasta que se obtiene larespuesta pueden transcurrir algunos segundos. La forma más simple de resolver un nombre es utilizar la función resolve():

resolve("www.ldir.com.ar");

la misma retorna una dirección IP en el formato que puede ser utilizado por lasdemás funciones del stack TCP/IP. Una aplicación adicional es como sustituto deinet_addr(), dado que la llamada a resolve() se deriva a inet_addr() si se observa quelo especificado es una dirección numérica1.En realidad resolve() es un loop que espera una respuesta, sea ésta positiva o de

error (timeout):

if (isaddr(name)) { // si es una dirección, para quéreturn (aton(name)); // resolver ?

}handle=resolve_name_start(name);if(handle<0)

return(0L); // si handle es <0, sale por errorwhile((retval=resolve_name_check(handle,&resolved_ip))==RESOLVE_AGAIN)

tcp_tick(NULL); // espera...if (retval == RESOLVE_SUCCESS) // si resolvió, entrega el valor

return (resolved_ip);else // caso contrario, sale por error

return (0L);

Cuando no nos podemos dar el lujo de esperar sentados a que resolve() resuelva,deberemos partirlo en sus funciones componentes: resolve_name_start() yresolve_name_check(), y escribirlo en forma de handler, utilizar costates, o armaruna pequeña máquina de estados que nos permita atender otras tareas mientrasesperamos. Los fragmentos de código de ejemplo a continuación, asumen quetcp_tick() se llama en el loop principal. El primero puede ser parte del costate queatiende la tarea que necesita comunicarse con la dirección en cuestión. El segundobien puede ser un handler o el código de un estado en la máquina de estados queinicia la comunicación:

handle=resolve_name_start(name); // si handle es <0, errorwaitfor((retval=resolve_name_check(handle,&resolved_ip))!=RESOLVE_AGAIN)if (retval == RESOLVE_SUCCESS)

// puedo usar resolved_ipelse

// hubo un error

handle=resolve_name_start(name); // si handle es <0, errorif((retval=resolve_name_check(handle,&resolved_ip))==RESOLVE_SUCCESS){

// puedo usar resolved_ip, avanzo en el flujo del programa}

1 En realidad se deriva a aton(), que es una macro que internamente es reemplazada por inet_addr().

242

Page 261: El Camino Del Conejo

Direcciones IP

else if (retval != RESOLVE_AGAIN){// manejo el error

}

Para definir quién es nuestro DNS, podemos utilizar la macro

#define MY_NAMESERVER "200.49.156.3"

También, podemos obtenerlo por DHCP, cargarlo en tcpconfig.lib, o ingresarlomediante una llamada a ifconfig(), como veremos más adelante en el apartado sobredirecciones IP.Si pensamos tener varias tareas levantando conexiones a la vez, debemos tener en

cuenta que existe un máximo en el número de pedidos de resolución concurrentes, elcual está fijado por la macro DNS_MAX_RESOLVES, que por defecto es cuatro, lacual deberemos modificar si necesitamos más:

#define DNS_MAX_RESOLVES 4

UDP

Para trabajar con UDP, recordemos que se trata de un protocolo no orientado aconexión, con lo cual, cuando tenemos algo que decir, simplemente lo decimos, loque se traduce en mandar la información cuando está disponible, sin preocuparse siel otro está o no; de eso se encargará la aplicación en particular, si lo requiere. En loque a UDP concierne, se abre el socket, y se escribe.La cantidad de conexiones y el tamaño de los buffers los limitamos operando sobre

algunas macros, previamente a la inclusión de DCRTCP. Es posible aumentar lacapacidad de conexión definiendo el número máximo de sockets (que por defecto escero) mediante una macro. El número de sockets dependerá de la cantidad decomunicaciones salientes y/o entrantes simultáneas que debamos manejar. Debetenerse presente que un mayor número de sockets implica una mayor ocupación ynecesidad de memoria. Por ejemplo, para una simple comunicación UDP:

#define MAX_UDP_SOCKET_BUFFERS 1#use "dcrtcp.lib"

Para desarrollar nuestro programa con UDP, primero debemos definir la variable autilizar para identificar el socket:

udp_Socket s;

Luego, procedemos a abrir el socket. De esta forma, podemos indicar en qué puertovamos a transmitir y/o recibir. En el caso de recepción, podemos indicar siescuchamos a cualquiera que nos hable en ese puerto, al primero que lo haga, osolamente a una dirección en particular. En el caso de transmisión, podemos hablarlea alguien en particular o mandar un broadcast dentro de nuestra subred, con lo cual

243

Page 262: El Camino Del Conejo

Networking con Rabbit

nos escucharán todas las máquinas que estén preparadas para hacerlo. También esposible trabajar sobre multicast, pero resulta algo más complejo. Por ejemplo:Para recibir datagramas en el puerto 0xC1CA, enviados desde cualquier host:

udp_open(&s,0xC1CA,-1L, 0, NULL );

Para recibir datagramas en el puerto 0xC1CA, enviados desde cualquier host; yenviar al puerto 0xC1CA a todos los hosts de la subred (UDP broadcast):

udp_open(&s,0xC1CA, -1L, 0xC1CA, NULL );

Para recibir datagramas en el puerto 0xC1CA del primer host que me envíe. A partirde ese instante, podré mandarle datagramas al puerto del que él me contactó. Esto esasí debido a que el socket se arma al recibir el primer datagrama:

udp_open(&s,0xC1CA,0, 0, NULL );

Para recibir datagramas en el puerto 0xC1CA solamente del host 1.2.3.4, saliendopor puerto 0x1234, y poder enviarle datagramas a ese puerto:

udp_open(&s,0xC1CA,inet_addr("1.2.3.4"),0x1234, NULL );

Para recibir datagramas en el puerto 0xC1CA solamente del host 1.2.3.4, porcualquier puerto. A partir de que éste me envíe un datagrama, podré mandarledatagramas al puerto del que él me contactó. Esto es así debido a que el socket searma al recibir el primer datagrama:

udp_open(&s,0xC1CA,inet_addr("1.2.3.4"),0, NULL );

Para enviar un datagrama:

udp_send(&s,buffer,longitud);

Esta función devuelve valores negativos en caso de error; particularmente, el valor-2 indica que aún no se ha obtenido la MAC de destino. Para recibir un datagrama:

if((len=udp_recv(&s,buffer,maxlen))>=0) // maxlen= tamaño del buffer// tengo un datagrama de len bytes disponible en buffer

Checksum

El header de UDP incluye un checksum, que es algo así como "opcional". Paraignorar el checksum, se debe utilizar la función que setea los parámetros del socket:

sock_mode(&s,UDP_MODE_NOCHK);

244

Page 263: El Camino Del Conejo

UDP

Ejemplos

El ejemplo que desarrollamos a continuación envía un string a un determinado port,mostrando en stdio lo que recibe:

#define TCPCONFIG 0#define USE_ETHERNET 1#define MAX_UDP_SOCKET_BUFFERS 1

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"#define MY_NAMESERVER "200.49.156.3"

#memmap xmem#use "dcrtcp.lib"

#define DEST_IP inet_addr("192.168.1.50")#define DEST_PORT 7766

main(){udp_Socket s;static char buffer[1024];int i;

sock_init(); if(udp_open(&s,0xC1CA,DEST_IP, DEST_PORT, NULL )==0){ printf("No pude abrir el socket"); exit(1); } strcpy(buffer,"Hola"); while((i=udp_send(&s,buffer,strlen(buffer)))==-2) tcp_tick(&s); if(i==-1) printf("No pude enviar"); else {

do{ tcp_tick(&s);

} while((i=udp_recv(&s,buffer,1024))<0);if(i>0){ buffer[i]=0;

puts(buffer);}

} sock_close(&s);}

TCP

La cantidad de conexiones y el tamaño de los buffers los limitamos operando sobrealgunas macros, previamente a la inclusión de DCRTCP. Es posible aumentar lacapacidad de conexión definiendo el número máximo de sockets (que por defecto escuatro) mediante una macro. Deberemos realizar esta modificación si necesitamosmás sockets para las tareas que debamos realizar (conexiones salientes y/o entrantes

245

Page 264: El Camino Del Conejo

Networking con Rabbit

simultáneas). Debe tenerse presente que un mayor número de sockets implica unamayor ocupación y necesidad de memoria. Por ejemplo, si queremos seis sockets:

#define MAX_TCP_SOCKET_BUFFERS 6#use "dcrtcp.lib"

Existe además una función que nos permite demorar el rechazo de una conexiónTCP a la espera de sockets libres, esta función es tcp_reserveport(), y se emplea paraevitar que los pedidos de conexión sean rechazados por ausencia de sockets libres enel tiempo en que se cierra una conexión establecida; por ejemplo cuando unnavegador pide varias imágenes. Este es un tema interesante. La especificación deTCP sugiere un tiempo durante el cual, luego del pedido de cierre de una conexión,debe mantenerse el socket abierto, de modo que ambos extremos estén seguros queel otro accedió al pedido y toda la información fue transferida. Esto es debido a quepodría haber algunos datagramas IP pululando por ahí sin haber llegado a destino.En un sistema donde la memoria es más abundante que la arena en el desierto, estono es problema, pero en un sistema dedicado, la porción de memoria que ocupa unsocket es más que preciada, razón por la cual sistemas como Rabbit limitan estetiempo a un par de segundos, en vez de los varios minutos recomendados. Lo queesto significa para nosotros, es que si no habilitamos tcp_reserveport(), cada vez quese cierra un socket, no se lo podrá volver a abrir por al menos dos segundos, esdecir, cualquier llamada para abrir una conexión saliente o pedido de conexiónentrante serán rechazados si los otros sockets están ocupados, hasta tanto transcurradicho tiempo y se pueda volver a utilizar el socket liberado. Este tiempo se puedecontrolar mediante la macro TCP_TWTIMEOUT.Las funciones por lo general reciben como parámetro un puntero a un socket, el

cual se define en alguna parte del programa:

tcp_Socket socket;

Detección de interrupción en la conexión

Hemos visto que la función tcp_tick() es la encargada de mantener activo el stackTCP/IP, y hemos observado que generalmente la llamamos sin argumentos. Cuandoa la función tcp_tick() se la llama con un argumento (un puntero a un socket),devuelve un valor que nos indica si ese socket está activo o no. De esta forma, esposible detectar interrupciones en la conexión:

if(tcp_tick(&socket)==0) {// se interrumpió mi comunicación

}

Como una conexión TCP tiene una serie de estados, esto nos sirve para resetearnuestra máquina de estados y reiniciar la conexión o indicarlo, si es necesario.Cuando alguno de los extremos cierra la conexión, el otro se da cuenta casi

inmediatamente. No obstante, la detección de una pérdida de conexión por

246

Page 265: El Camino Del Conejo

TCP

interrupción de la conectividad o salida de servicio del extremo remoto puede llevarbastante tiempo, dado el esquema de retransmisiones y timeouts de TCP. Sinembargo, aunque no es lo recomendable, en un caso extremo podemos alterar estosparámetros modificando la biblioteca de funciones de TCP. Lo cierto es que, si nosabemos donde buscar, lo mejor es que no lo modifiquemos...De todos modos, dentro de nuestra aplicación, siempre podemos manejar timers y

detectar si el otro extremo no responde a nuestros mensajes por un determinadotiempo. Sin embargo, cuando no tenemos actividad en cuanto a transferencia deinformación, y no podemos darnos el lujo de detectar que tenemos la conexióninterrumpida recién cuando enviamos algo urgente y no hay respuesta, podemosemplear keepalives. Un keepalive es un paquete que se envía cuando no hay nadaque enviar, de modo de forzar tráfico en la conexión y detectar si el otro extremo"está vivo". Son una relación de compromiso entre evitar desperdiciar ancho debanda y tiempo, ambos expresables como dinero muchas veces. Los configuramosuna vez que nuestro socket está abierto, mediante la siguiente función:

tcp_keepalive(&socket, ktime).

Dicha función permite configurar el tiempo de inactividad a partir del cual se envíaun keepalive. Si pasado un cierto tiempo de espera (configurable mediante unamacro) no se recibe respuesta, el keepalive será retransmitido una determinadacantidad de veces (configurable mediante una macro), momento a partir del cual sino se obtuvo respuesta se da por interrumpida la conexión. Los valores por defecto ylas correspondientes macros son:

#define KEEPALIVE_WAITTIME 60#define KEEPALIVE_NUMRETRYS 4

Con estos valores por defecto, TCP se dará cuenta que se interrumpió la conexiónun mínimo de cuatro minutos después de ocurrida. Si necesitamos algo más rápido,podemos definir nuestros valores antes de incluir dcrtcp.lib:

#define KEEPALIVE_WAITTIME 10#define KEEPALIVE_NUMRETRYS 4

Con estos valores, se envían keepalives cada ktime segundos, si al cabo de 10segundos no se recibió respuesta, se reintenta tres veces más (cuatro en total), con locual se da por cortada la conexión al cabo de 40 segundos de enviado el primerkeepalive. Si el tiempo entre éstos (ktime) es muy largo, influirá en el tiempo dedetección.

Cliente TCP

Para implementar un cliente TCP, deberemos tener presente que una comunicaciónTCP avanza a través de una serie de estados. Los pasos fundamentales quedeberemos realizar son:1. Inicio de la conexión

247

Page 266: El Camino Del Conejo

Networking con Rabbit

2. Espera de la aceptación y establecimiento de la comunicación3. Transferencia de datos4. Fin de la conexión5. Espera para reiniciar la conexión (opcional)

Implementación

Si bien disponemos de funciones que nos permiten inicializar el socket, leerlo, yescribirlo, deberemos desarrollar cada uno de los pasos manualmente.

Inicio de la conexión

Iniciar una conexión es simplemente llamar a la función tcp_open(), pasándolecomo parámetros los datos necesarios para armar el socket: dirección y port dedestino, y port de origen (0 para elegir uno libre). El último parámetro indica ladirección de un data handler para la transferencia de datos, pasándolo como NULLutilizamos el que provee el sistema, sin inconvenientes.

if (tcp_open(&socket,src_port,dest_ip,dest_port,NULL) != 0)//intento establecer la conexión

else // no sé como conectarme a ese destino

Espera de la aceptación y establecimiento de la comunicación

Luego de iniciar la conexión, deberemos esperar a ver si ésta se establece, lo cualse nos puede indicar por la función sock_established(), pero puede que el hostremoto esté muy alegre y ansioso de vernos y nos responda al momento con datos,cerrando inmediatamente la conexión, por lo que es conveniente emplear tambiénsock_bytesready(). Lo anterior podría ocurrir entre dos llamadas a tcp_tick() (segúnla carga del sistema), y nunca llegaríamos a ver el socket activo, aunque tendríamosdatos esperando.Una vez establecida la comunicación, podemos colocar el socket en el modo de

trabajo que vayamos a emplear. Lo más común es dejar el modo por defecto, quecorresponde a un stream de bytes independientes. En algunos casos, como servidoresHTTP, tal vez convenga colocarlo en modo ASCII.

if(sock_established(&socket) || sock_bytesready(&socket) >= 0) {// estoy comunicado !sock_mode(&socket,TCP_MODE_BINARY); // modo binario

}else

// deberé seguir esperando

Existe un tiempo de espera antes de resignarnos a que el otro extremo no recibiónuestro pedido de comunicación, el mismo puede configurarse mediante la siguientemacro, en milisegundos:

248

Page 267: El Camino Del Conejo

TCP

#define TCP_OPENTIMEOUT 31000L

Existe además otro tiempo de espera desde que se detecta la buena voluntad delotro extremo hasta que se cierra el proceso de three-way hansdshake de TCP y seestablece la conexión, también modificable, en milisegundos:

#define TCP_CONNTIMEOUT 13000L

Transferencia de datos

Ya estamos en condiciones de comunicarnos con el extremo remoto, este es elúnico estado que realmente nos interesaba. En este punto, podemos escribir y leer eny del socket para enviar y recibir información, lo cual haremos mediante dosfunciones. Para lectura de datos del socket:

bytes_read=sock_fastread(&socket,buffer,buffer_size);

Para escritura de datos en el socket:

bytes_written=sock_fastwrite(&socket,buffer,len);

No siempre se puede escribir todo lo que se intenta, o hay un error, por lo que esconveniente preguntar cuánto de lo que se intentó escribir fue realmente escrito, yseguir desde allí. Esto se realiza mediante una porción de código similar a ésta:

if (bytes_written < 0) // error, debo cerrar la conexión

if(bytes_written!=len) memcpy(buffer,buffer+bytes_written,len-bytes_written);

len -= bytes_written;

De igual modo, un valor negativo de bytes_written nos estará indicando que ocurrióun error y deberemos reiniciar la conexión.

Fin de la conexión

Para terminar la conexión, simplemente lo indicamos llamando a la funciónsock_close():

sock_close(&socket);

La confirmación del cierre definitivo la podemos hacer de la misma forma quedetectamos una desconexión: observando el valor devuelto por tcp_tick().

Espera para reiniciar la conexión (opcional)

No siempre es necesario, todo depende de la actividad a realizar y nuestradisponibilidad de sockets, pero por ejemplo si la conexión anterior envió datos porun puerto serie, tal vez debamos esperar a que se vacíe el buffer, o si estamosiniciando conexiones a un servidor, tal vez sea amable de nuestra parte esperar unos

249

Page 268: El Camino Del Conejo

Networking con Rabbit

segundos antes de reintentar. De todos modos, si lo último que hicimos antes decerrar el socket fue enviar datos, deberemos seguir llamando a tcp_tick() hasta quelos datos terminen de ser enviados.

Ejemplos

El ejemplo que desarrollaremos a continuación es un simple cliente TCP que seconecta a un determinado port, mostrando en stdio lo que recibe, contestando con unstring, para luego desconectarse:

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"#define MY_NAMESERVER "200.49.156.3"

#define DEST_IP inet_addr("192.168.1.50")#define DEST_PORT 6677

#memmap xmem#use "dcrtcp.lib"

enum{INIT,OPENING,OPEN,CLOSE};

main(){tcp_Socket socket;int state,bytes_written,bytes_read,len;char buffer[1024];

sock_init();

state=INIT;if (tcp_open(&socket,0L,DEST_IP,DEST_PORT,NULL) == 0){

printf("No pude abrir el socket"); exit(1);

}

state=OPENING;while((!sock_established(&socket)) && (sock_bytesready(&socket) < 0)){

if(tcp_tick(&socket)==0)state=CLOSE; // falla en conexión

}

if(state==CLOSE)printf("No pude conectarme");

else {state=OPEN;sock_mode(&socket,TCP_MODE_BINARY);

// inicializa lo que deba inicializar

// espera datoswhile((bytes_read=sock_fastread(&socket,buffer,1024))==0){

if (bytes_read < 0) break;

if(tcp_tick(&socket)==0) // detecta desconexión break;

}// procesa datos leidos, nosotros mostramos en stdiobuffer[bytes_read]=0;printf("%s",buffer);

250

Page 269: El Camino Del Conejo

TCP

// contestamos strcpy(buffer,"Esto es una respuesta\n"); len=strlen(buffer);

do { bytes_written=sock_fastwrite(&socket,buffer,len); if (bytes_written < 0) break; if(bytes_written!=len)

memcpy(buffer,buffer+bytes_written,len-bytes_written); len -= bytes_written; if(tcp_tick(&socket)==0) // detecta desconexión

break;} while (len);

sock_close(&socket);state=CLOSE;while(tcp_tick(&socket)); // espera desconexión

}}

Con un esquema como el desarrollado, ya podemos conectarnos a cualquierservidor de cualquier protocolo de aplicación que corra sobre TCP. Simplementedeberemos implementar el protocolo de aplicación dentro del estado de transferenciade datos de nuestra conexión TCP.Si bien hemos escrito el ejemplo en forma de loops para mayor claridad, hemos

marcado los diferentes estados en que iba avanzando nuestra máquina de estados deconexión. Si nuestro equipo o aplicación debe hacer otra cosa además de abrir unaconexión TCP, es preferible utilizar costates o escribir el código en forma demáquina de estados. La decisión de la forma a utilizar y en qué función hace laescritura sobre el socket es un punto interesante. Cada aplicación tiene suscaracterísticas y según lo que se necesite puede que una u otra cosa resulten más omenos apropiadas o cómodas en cada situación. A fin de generalizar un caso un pocomás simple, hemos desarrollado un pequeño ejemplo, en el cual el programaprincipal llama a un handler que mantiene el estado de la conexión; luego el primeroescribe él mismo sobre el socket que recibe y pide cerrar la conexión. Mientrastanto, otras tareas hacen sus cosas :

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#define DEST_IP inet_addr("192.168.1.50")#define DEST_PORT 6677

#memmap xmem#use "dcrtcp.lib"

enum{IDLE,INIT,OPENING,OPEN,CLOSE,CLOSING};

myTCPtick(tcp_Socket *socket,int *state){

switch(*state){case INIT:

if (tcp_open(socket,0L,DEST_IP,DEST_PORT,NULL) == 0){ printf("No pude abrir el socket\n");

251

Page 270: El Camino Del Conejo

Networking con Rabbit

*state=IDLE; break;

}*state=OPENING;

case OPENING:if((!sock_established(socket))&&(sock_bytesready(socket) < 0)){

if(tcp_tick(socket)==0){printf("No pude conectarme\n");*state=CLOSE; // falla en conexión

}}else {

*state=OPEN;printf("Estoy conectado\n");sock_mode(socket,TCP_MODE_BINARY);// inicializa lo que deba inicializar

}break;

case CLOSE:default:

printf("Cerrando conexión...\n");sock_close(socket);*state=CLOSING;

case OPEN:case CLOSING:

if(tcp_tick(socket)==0) { // detecta/espera desconexión*state=IDLE;printf("Haraganeando\n");

}break;

case IDLE: break;

}return(*state);

}

main(){tcp_Socket socket;int state,bytes_written,bytes_read,len,retrn;char buffer[1024];

sock_init();state=IDLE;while(1) { // el loop infinito representa el llamado periódico a este handler if((retrn=myTCPtick(&socket,&state))==OPEN){

// espera datosif((bytes_read=sock_fastread(&socket,buffer,1024))!=0){

if ((bytes_read < 0)||(tcp_tick(&socket)==0)){// detecta desconexión

state=CLOSE; } // procesa datos leídos, nosotros mostramos en stdio buffer[bytes_read]=0; printf("%s",buffer); // contestamos strcpy(buffer,"Esto es una respuesta\n"); len=strlen(buffer); do {

bytes_written=sock_fastwrite(&socket,buffer,len); if ((bytes_written < 0)||(tcp_tick(&socket)==0)){

// detecta desconexión state=CLOSE;

252

Page 271: El Camino Del Conejo

TCP

}if(bytes_written!=len) memcpy(buffer,buffer+bytes_written,len-bytes_written);

len -= bytes_written; } while (len); state=CLOSE;}

} // otras tareas

costate {waitfor(DelaySec(5));

// inicia conexiónstate=INIT;waitfor(state==IDLE); // espera finalización o errorexit(0);

}}

}

Servidor TCP

Para implementar un servidor TCP, también deberemos tener presente que unacomunicación TCP avanza a través de una serie de estados. Si bien estamostranquilos esperando que se nos llame, deberemos seguir una serie de pasosfundamentales. Éstos son:1. Inicio del servicio (apertura pasiva del socket)2. Espera de un pedido de conexión, aceptación y establecimiento de la misma3. Transferencia de datos4. Fin de la conexión

Implementación

Si bien disponemos de funciones que nos permiten inicializar el socket, leerlo, yescribirlo, una vez más, deberemos desarrollar cada uno de los pasos manualmente.

Inicio del servicio

Iniciar un servicio es informarle al stack TCP/IP que estamos esperando quealguien nos llame en determinado número de port; lo cual realizamos mediante unallamada a la función tcp_listen(), pasándole como parámetros los datos necesariospara armar el socket: el número de port. En un ambiente más restrictivo, tambiénpodemos especificar dirección IP y port aceptados. El penúltimo parámetro indica ladirección de un data handler para la transferencia de datos, pasándolo como NULLutilizamos el que provee el sistema, sin inconvenientes. Cada vez que cerremos unaconexión necesitamos volver a abrir el socket con ese port en escucha.

if (tcp_listen(&socket,port,0,0,NULL,0) != 0)// espero a que me llamen

else // hay un problema

253

Page 272: El Camino Del Conejo

Networking con Rabbit

Espera de un pedido de conexión, y establecimiento de ésta

Luego de iniciar el servicio, deberemos esperar a que alguien se decida a utilizarlo,lo cual se nos va a indicar por la función sock_established(), pero puede que el hostremoto esté muy apurado por decirnos algo y nos ataque inmediatamente con datos,cerrando apuradamente la conexión, sin darnos tiempo a que veamos el socket comoestablecido. Por esto es conveniente emplear también sock_bytesready().

if(sock_established(&socket) || sock_bytesready(&socket) >= 0) {// estoy comunicado !

}else

// podré seguir descansando

Transferencia de datos, Fin de la conexión

De aquí en más todo se comporta de igual modo que para un cliente TCP, dado quepara este protocolo no existen clientes ni servidores, sólo iniciadores y escuchas.

Ejemplos

El ejemplo que desarrollaremos a continuación es un simple servidor TCP queespera una conexión en un determinado port, mostrando en stdio lo que recibe,contestando con un string, y esperando que quien llama corte la conexión. Dado queel servidor probablemente sea sólo una tarea más, y lleva las de ganar: hace lo que lepiden y vuelve a dormir; directamente escribimos el código en forma de máquina deestados, la cual será llamada como handler desde el programa principal, dejandolibre a éste para hacer lo que tenga que hacer. A fin de no complicar el handleragregando más estados, decidimos implementar la "aplicación" (el recibir un texto,mostrarlo y contestar) como un handler, que también escribimos como máquina deestados debido al ciclo de espera en escritura que podría presentarse ante ausencia deespacio en buffers.

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#define MY_PORT 7766

#memmap xmem#use "dcrtcp.lib"

enum {READ=0,WRITE};

int application_handler(tcp_Socket *socket,int *state){int bytes_written,bytes_read,len;char buffer[1024];

switch(*state){case READ:

if((bytes_read=sock_fastread(socket,buffer,1024))!=0){

254

Page 273: El Camino Del Conejo

TCP

if (bytes_read < 0){ // detecta desconexiónreturn(-1);

}// procesa datos leídos, nosotros mostramos en stdiobuffer[bytes_read]=0;printf("%s",buffer);// contestamosstrcpy(buffer,"Esto es una respuesta\n");len=strlen(buffer);*state=WRITE; // sigue ejecutando WRITE a continuación

} else

break; // continúa en este estado si no hay datoscase WRITE:

bytes_written=sock_fastwrite(socket,buffer,len);if (bytes_written < 0){ // detecta desconexión

return(-1);}if(bytes_written!=len)

memcpy(buffer,buffer+bytes_written,len-bytes_written);len -= bytes_written;if(len==0) // sigue en este estado mientras deba escribir

*state=READ; // vuelve a READ cuando termina break;}return(0);

}

enum{IDLE,INIT,LISTEN,OPEN,CLOSE,CLOSING};

void myTCPtick(tcp_Socket *socket,int *state,int *astate){

if((tcp_tick(socket)==0) && (*state!=INIT)){*state=INIT; // falla/cierre de conexión

}switch(*state){case INIT:

if (tcp_listen(socket,MY_PORT,0,0,NULL,0) == 0){ printf("No pude abrir el socket\n");

*state=IDLE;break;

}*state=LISTEN;

case LISTEN:if((sock_established(socket))||(sock_bytesready(socket) > 0)){

*state=OPEN;printf("Estoy conectado\n");sock_mode(socket,TCP_MODE_BINARY);*astate=READ; // reinicia aplicación// inicializa lo que deba inicializar

}break;

case OPEN:if(application_handler(socket,astate))

*state=CLOSE;break;

case CLOSE: default:

printf("Cerrando conexión...\n");sock_close(socket);*state=CLOSING;

case CLOSING:

255

Page 274: El Camino Del Conejo

Networking con Rabbit

case IDLE: break;}

}

main(){tcp_Socket socket;int state,astate;

sock_init(); tcp_reserveport(MY_PORT);

state=INIT;while(1) {// el loop infinito representa el llamado periódico a este handler

myTCPtick(&socket,&state,&astate); // otras tareas}

}

Con un esquema como el desarrollado, ya podemos implementar un servidor decualquier protocolo de aplicación que corra sobre TCP. Simplemente deberemosimplementar el protocolo de aplicación dentro del estado de transferencia de datosde nuestra conexión TCP. De hecho, la sample Samples\tcpip\state.c muestra cómorealizar un simple servidor HTTP.Una última aclaración. En este caso, dada la simpleza de la aplicación propuesta, y

de su total esclavitud con respecto a TCP, particularmente al ser un servidor y notener otra función en la vida al desaparecer la conexión, nos resultó directa laimplementación de la misma como esclava de la conexión. En otras aplicaciones dela vida real, y particularmente cuando se trata de un cliente TCP, es altamenteprobable que sea más conveniente que el handler de la conexión TCP sea esclavo dela aplicación. Como dicen en el norte: your mileage may vary2, y cada developerconcebirá su estructura de acuerdo a lo que crea más conveniente, en función delproblema que deba resolver

Múltiples conexiones en un mismo port

Evidentemente, no estamos corriendo sobre un sistema operativo, y no podemoshacer fork() y generar un nuevo proceso que atienda este requerimiento, mientrasseguimos esperando conexiones en el port que servimos. Estamos en el dominio delos sistemas dedicados, y las cosas se resuelven con una adecuada planificación, yhaciendo el trabajo pesado a la hora de programar. En este caso, lo que tenemos quehacer es definir un número de servidores, crear un socket y una función para cadauno de ellos, y cada función hará su correspondiente llamada a tcp_listen(). Dadoque son varios servicios idénticos, el código es el mismo, sólo cambia el estado de lamáquina que los sirve, por lo que podemos llamar varias veces al mismo handler con

2 Desde mi posición geográfica, Estados Unidos está al norte, puede que si se lee este texto en otrospaíses esto no sea tan así. De todos modos, la referencia es a que su opinión puede diferir de acuerdoa su experiencia en su problema particular.

256

Page 275: El Camino Del Conejo

TCP

diferentes variables de estado y buffers externos (y diferentes sockets), o utilizarindexed cofunctions si preferimos usar las alternativas de Dynamic C.El ejemplo que desarrollaremos a continuación es un simple servidor TCP que

acepta múltiples conexiones en un determinado port, mostrando en stdio lo querecibe3, contestando con un string para luego desconectarse.La primera modificación es en el handler de aplicación, que recibe un parámetro

más: el buffer; y más allá de lo que el compilador tenga como standard, las variableslocales deben ser auto, para poder compartir el código.

int application_handler(tcp_Socket *socket,int *state,char *buffer){auto int bytes_written,bytes_read,len;

La segunda modificación es en el handler de TCP, que recibe el parámetro extrapara el handler de aplicación.

void myTCPtick(tcp_Socket *socket,int *state,int *astate,char *buffer)

La última modificación es en el programa principal, que hacemos los buffersestáticos, los parámetros arrays estáticos (sockets, buffers y variables de estado) ypasamos los parámetros a cada handler antes de llamarlo. Realizamos un simple looppara demostrar la forma de operación, sin demasiadas ambiciones de eficiencia.

#define MY_SERVERS 3

main(){static tcp_Socket socket[MY_SERVERS];static char buffers[MY_SERVERS][1024];static int state[MY_SERVERS],astate[MY_SERVERS],i;

sock_init(); tcp_reserveport(MY_PORT); for(i=0;i<MY_SERVERS;i++)

state[i]=INIT;while(1) { // el loop infinito representa el llamado periódico a este handler for(i=0;i<MY_SERVERS;i++)

myTCPtick(&socket[i],&state[i],&astate[i],buffers[i]); // otras tareas}

}

Como hemos visto, con unas simples modificaciones podemos ya dar servicio aMY_SERVERS conexiones simultáneamente, que en este caso es igual a tres.

Zserver

3 Debido a que printf() es una single user cofunction, cada proceso puede escribir y los diferentespedidos simultáneos serán demorados hasta satisfacer el que está en curso.

257

Page 276: El Camino Del Conejo

Networking con Rabbit

A partir de DC8.5, se incorporan una serie de macros tendientes a facilitar elmanejo de las estructuras de los distintos servidores. Surge entonces la figura deZserver, que es quien tiene el control de estas estructuras y las sirve internamente alos demás servidores, como por ejemplo HTTP server o FTP server. Por ejemplo, para definir los tipos MIME, procederemos de la siguiente forma:

SSPEC_MIMETABLE_STARTSSPEC_MIME_FUNC(".shtml", "text/html", shtml_handler),SSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),SSPEC_MIME(".cgi", "")

SSPEC_MIMETABLE_END

Vemos que:SSPEC_MIME_FUNC nos permite definir el handler a utilizar para este tipo dearchivos.SSPEC_MIME es una entrada normal de la lista de tipos MIME.

Para definir el directorio de un servidor HTTP, procederemos de la siguienteforma:

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),

SSPEC_RESOURCE_ROOTVAR("rilis",&release, INT16,"%d"),

SSPEC_RESOURCE_FUNCTION("/setup.cgi", setup)SSPEC_RESOURCETABLE_END

Los nombres empleados nos van dando una pista de lo que sucede:

SSPEC_RESOURCE_XMEMFILE es un "archivo" en xmemSSPEC_RESOURCE _ROOTVAR identifica a una variable en área rootSSPEC_RESOURCE_FUNCTION identifica a una función SSI ó un CGI4

El campo empleado para el nombre, tiene por supuesto una longitud máxima, y éstaes de veinte caracteres, modificable mediante la macro:

#define SSPEC_MAXNAME 20

Como probablemente hayamos deducido, se trata de una forma más amigable detrabajar, respecto de la definición cruda de las estructuras que utilizábamos enversiones anteriores, y con la que operamos a lo largo del libro introductorio. Si bienel operar directamente sobre la estructura tiene la ventaja de que quien comienza aestudiar puede observar en detalle como se define y se arma toda la estructura detrabajo, es mucho más cómodo para el usuario experimentado el referirse mediante

4 El manejo de CGIs incluye una "nueva forma" mucho más poderosa, pero bastante más compleja,que omitiremos por el momento.

258

Page 277: El Camino Del Conejo

Zserver

estas macros. Debido a que, como comentáramos, es mucho más cómodo trabajarcon esta nueva forma, será la que emplearemos en los ejemplos.

"Archivos" en xmem agregados de forma dinámica

Además de este directorio estático, en flash, existe un directorio dinámico, quepuede modificarse en tiempo de ejecución, agregando nuevos archivos, y cambiandolas propiedades de los mismos. Obviamente, tiene un tamaño por defecto parapreservar RAM, el cual puede modificarse mediante la macro:

#define SSPEC_MAXSPEC 10

y que por defecto es diez, como se indica.La operación de agregado de entradas a esta tabla se realiza mediante funciones

especiales:

sspec_addxmemfile(nombre,dirección,server);

donde nombre corresponde al URL del servidor HTTP, o el nombre de archivo en elservidor FTP, dirección es la dirección en xmem, en el formato que usa #ximport, yserver es una palabra que indica qué servidores ven este "archivo", y puede serSERVER_HTTP, SERVER_FTP, o un or lógico de ambos.El "archivo" puede estar tanto en RAM como en flash, aunque probablemente lo

más útil sea RAM, dado que el contenido de flash puede ser referido mediante latabla estática. Los nombres no pueden incluir prefijos que podrían confundirse conindicaciones de file system, como veremos a continuación.

Archivos en file systems

Para referirnos a archivos en file systems, simplemente utilizamos un prefijo demanera similar a como haríamos en un sistema operativo con el concepto claro defile system, como por ejemplo Linux o cualquier otro UN*X: /fs2/ para referirnos a los archivos en FS2 (el LX corriente)/A/ para referirnos a los archivos en la primera partición FAT del dispositivo/B/ para referirnos a los archivos en la segunda partición, etc.; de acuerdo a las

reglas para montar dispositivos y particiones, que analizamos en el capítulosobre file systems.

También es posible en FS2 referirse a un LX en particular mediante"subdirectorios", por ejemplo: /fs2/ram/ accede a los archivos en RAM, si es que seinicializó un LX allí. No ahondaremos en este tema por el momento.

Antes de poder utilizar los archivos en file systems, deberemos montarlos, lo cualharemos con una llamada a función:

sspec_automount(quemonto, NULL, NULL, NULL);

259

Page 278: El Camino Del Conejo

Networking con Rabbit

donde quemonto puede ser SSPEC_MOUNT_FS, SSPEC_MOUNT_FAT, oSSPEC_MOUNT_ANY; para montar todos los LX de FS2, la primera particióndisponible de FAT, o ambas, respectivamente. Esto efectivamente llama a las rutinasde inicialización para los diversos file systems que viéramos en su capítulocorrespondiente, sin embargo, en el caso de FAT, sólo registra para uso en Zserver ala primera de ellas. Para registrar particiones FAT adicionales, utilizamos la funciónsspec_fatregister(), con información que nos provee sspec_automount() dentro deestructuras que debemos pasarle en los parámetros, previa modificación de la macroque define la cantidad máxima de particiones FAT utilizables(SSPEC_MAX_FATDRIVES). No ahondaremos en este tema, sólo comentamos quees posible y existe muchísima información y muy rica, en los manuales de TCP/IP.Existe un ejemplo en particular sobre este punto en las samples; nos referimos alarchivo Samples\TCPIP\Zserver\filesystem.c.

Zserver es muy rico, y hay mucha información para digerir. Sin embargo, aquí sóloanalizaremos una pequeña parte, aquélla que nos permite resolver las inquietudesque nos plantean los servidores HTTP y FTP, como medio de control o reporte denuestra aplicación.

Veremos ejemplos y muchas opciones más, particularmente permisos de usuario yautenticación, al analizar la forma de utilizar los servicios de Zserver en servidoresHTTP y FTP.

Servidor HTTP

Para compilar las bibliotecas de funciones de HTTP, que proveen un servidorbastante poderoso e interesante, necesitamos incluir:

#use "http.lib"

Los tipos MIME y el directorio del servidor quedan definidos mediante estructurasque apuntan a los datos a servir. El servidor se inicializa llamando a la funciónhttp_init(). Finalmente, el manejo del servidor lo realiza la función http_handler(), lacual deberemos llamar periódicamente. El llamar a http_handler() provee además lafunción de tcp_tick(), por lo que no es necesario llamar a ambas.La cantidad de servidores HTTP dispuestos a atender pedidos (que por defecto es

dos), puede modificarse definiendo una macro que modifica el valor por defecto.Debe tenerse cuidado, ya que un mayor número de servidores implica una mayorocupación y necesidad de memoria. La macro en cuestión se define antes de incluirla biblioteca de funciones de HTTP. Digamos, por ejemplo, que queremos cuatroservidores:

#define HTTP_MAXSERVERS 4#use "http.lib"

260

Page 279: El Camino Del Conejo

Servidor HTTP

Es posible, además, aumentar la capacidad de conexión definiendo el númeromáximo de sockets (que por defecto es cuatro) mediante otra macro. Deberemosrealizar esta modificación si ponemos un número de servidores mayor a la cantidadde sockets por defecto y/o necesitamos más sockets para otras tareas. Nuevamente,un mayor número de sockets implica una mayor ocupación y necesidad de memoria.Esta macro se define antes de incluir la biblioteca de funciones de TCP/IP. Porejemplo, si queremos seis sockets:

#define MAX_TCP_SOCKET_BUFFERS 6#use "dcrtcp.lib"

A partir de DC8.5, se incorporan una serie de macros tendientes a facilitar elmanejo de las estructuras del servidor, particularmente la inclusión de archivos en eldirectorio estático del mismo, sin necesidad de completar demasiados campos de unaestructura y/o definirla. El método utilizado favorece la tradicional técnica decopiado-pegado (copy-paste). La transición es bastante favorable, por ejemplo laestructura HttpSpec que se utiliza tiene su nombre por tradición, dado que muyprobablemente se originó para el HTTP server, siendo luego fagocitada por Zserver.Hemos analizado las nuevas macros en detalle en el apartado correspondiente a

Zserver. Como pudimos observar, las macros no difieren demasiado de la estructuraque empleábamos tradicionalmente (la cual podemos seguir utilizando si es denuestro agrado). Así, tanto tipos MIME como directorio, se definirán con las macrosque hemos visto:

SSPEC_MIMETABLE_STARTSSPEC_MIME_FUNC(".shtml", "text/html", shtml_handler),SSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),SSPEC_MIME(".cgi", "")

SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),SSPEC_RESOURCE_ROOTVAR("rilis",&release, INT16,"%d"),SSPEC_RESOURCE_FUNCTION("/setup.cgi", setup)

SSPEC_RESOURCETABLE_END

Imágenes generadas en tiempo de ejecución

De igual modo, podemos definir archivos en xmem que agregamos en tiempo deejecución, como por ejemplo una imagen en RAM que generamos en base aestadísticas, o por qué no, tomándola de un lector de huellas dactilares.Lo más simple es generar la imagen en formato BMP sin compresión. Si ya

sabemos de antemano el tamaño y resolución de color que vamos a utilizar,simplemente generamos con cualquier software una imagen en dicho formato, yextraemos la cabecera y la paleta, las cuales podemos guardar en flash mediante

261

Page 280: El Camino Del Conejo

Networking con Rabbit

#ximport, y luego copiarlas a RAM, reservando un área al final del tamaño de laimagen. A continuación, modificamos el campo de tamaño de ese pseudo-archivo.Ése será nuestro buffer donde generaremos o alojaremos la imagen:

Si la imagen es en color o en niveles de gris, debemos tener cuidado de elegir unapaleta de colores (o niveles de gris) compatible con lo que vamos a generar.Recordemos que sólo modificamos el contenido de la imagen, no su paleta. Porsupuesto que también podríamos hacer esto si quisiéramos, pero requiere mayortrabajo.El siguiente ejemplo muestra la porción de código que empleamos para generar y

mostrar la imagen obtenida de un sensor de huellas dactilares. El código completo lopodemos observar en la nota de aplicación CAN-052, que se incluye en el CD queacompaña a esta edición.

SSPEC_MIMETABLE_STARTSSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),SSPEC_MIME(".bmp", "image/bmp")

SSPEC_MIMETABLE_END

// 152x200, 8bpp#define IMAGELEN 30400L#define BMPHEADER 1078#define BMPLEN (BMPHEADER+IMAGELEN)

bitmap=xalloc(BMPLEN+4); // reserva RAM para la imagen// agrega la imagen al directorio del web serversspec_addxmemfile("/dedito.bmp",bitmap,SERVER_HTTP );xmem2xmem(bitmap,bmpheader,BMPHEADER+4); // copia encabezado BMPxsetlong(bitmap,BMPLEN); // corrige tamaño (ximport)bitmap+=(BMPHEADER+4); // corrige puntero a imagen

262

#ximport

flash

RAM

long

header

paleta

espacio ocupado por paleta + header

flash

RAM

Al compilar Ejecutando

long

header

paleta

imagen

copiar

modificar

espacio ocupado porpaleta + header + imagen

reservar

Page 281: El Camino Del Conejo

Servidor HTTP

El inconveniente de BMP es que como no es totalmente un standard, puede quealgunos navegadores no lo muestren, o no lo hagan directamente. Una solución eselegir otro formato de archivos, como GIF, PNG, o JPEG (JPG). Sin embargo, GIFemplea un algoritmo de compresión cuyos derechos pertenecen a una conocidaempresa del medio, y quien genere archivos en este formato debe abonar unalicencia; PNG es abierto, pero el algoritmo de compresión no es trivial, al menos nopara su implementación en un sistema dedicado como el nuestro; y JPEG requiere ungrado interesante de procesamiento digital de señales (DSP), el cual escapa anuestras buenas intenciones.En cualquier caso, como vimos, agregamos el "archivo", pseudo-archivo, o como

se nos ocurra llamarlo, al directorio; mediante la función de Zserver para este fin:

sspec_addxmemfile(URL,addr,SERVER_HTTP);

Por supuesto, claro está, que para que la imagen pueda ser vista, deberemos pedirlaal servidor por el nombre con que la definimos en el directorio, lo cual correspondea /dedito.bmp, y se realiza dentro de una referencia <IMG SRC="/dedito.bmp">dentro del archivo HTML que corresponda.

Imágenes que no aparecen

En la gran mayoría de los casos, lo que ocurre es que un navegador ansioso,acostumbrado a hacer gran cantidad de pedidos simultáneos a poderosos servidorescon gigabytes de RAM en la Internet, se encuentra con un micro de 8-bits con unacantidad limitada de servidores (2) y sockets (4), que le dice no a sus pedidos deconexión. Esto se manifiesta particularmente en páginas complejas, con muchasimágenes, y sistemas con pocos servidores disponibles; donde "pocos" podríamosdecir que es el doble de la cantidad de elementos en la página, e incluye por supuestoa los valores por defecto. El navegador intenta traer todas las páginas a la vez, y elservidor (Rabbit) le rechaza las conexiones, a lo cual el primero no reintenta ysimplemente marca las páginas como con error. Al refrescar la página, o pedirindividualmente las imágenes, muy probablemente aparezcan.Como generalmente sucede, la solución es marcar el paso y en vez de decir un no

rotundo al pedido de una conexión, optar por aceptarla sabiendo que en pocossegundos ya tendremos liberados nuestros escasos recursos. Esto se realiza mediantela función tcp_reserveport(), como comentáramos al comenzar a analizar TCP,especificando que el port que reservamos es el que usa el servidor HTTP:

http_init();tcp_reserveport(80);while(1){ http_handler();

263

Page 282: El Camino Del Conejo

Networking con Rabbit

Archivos servidos desde file systems

Un archivo en un file system puede responder por ejemplo a la generación de unlog como los que vimos en el capítulo sobre file systems, una imagen (como elejemplo anterior), o bien archivos que se copian mediante un cliente o servidor FTP,como veremos más adelante.

El ejemplo a continuación hace uso de lo visto al analizar file systems,particularmente FS2. Al detectar la presión del switch S2 de un RCM2100,grabamos un mensaje en un archivo, junto con información de fecha y hora. Dichoarchivo, puede accederse desde el servidor HTTP y observarse de forma remota. Proveemos dos opciones, a fin de mostrar la forma de operar:� En un caso, mapeamos el nombre del archivo con un URL, como puede

apreciarse en la entrada de directorio correspondiente, que se encuentra resaltadajunto con lo pertinente a este ejemplo: la petición de log.txt, produce que elservidor entregue el archivo 1, presentándolo como tipo MIME text/plain alnavegador.

� En el otro, simplemente lo accedemos desde el HTML con el prefijo del filesystem. La petición de /fs2/file1 produce que se entregue el archivo 1 sin indicartipo MIME.

En todos los casos, el LX utilizado es el que se ha definido al modificar (o no) laconfiguración del BIOS, según el módulo que se tiene, tema ya analizado en elcapítulo sobre file systems, en el apartado sobre FS2

SSPEC_MIMETABLE_STARTSSPEC_MIME(".html", "text/html"),SSPEC_MIME(".txt", "text/plain"),SSPEC_MIME(".gif", "image/gif")

SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),SSPEC_RESOURCE_XMEMFILE("/rabbit1.gif", rabbit1_gif),SSPEC_RESOURCE_FSFILE("/log.txt",1)

SSPEC_RESOURCETABLE_END

File file;const char S2[]="presión de S2";

main(){int rc,filenumber;unsigned char len;char buffer[256],*evento;struct tm mtm;

filenumber=1;evento=S2;sock_init();http_init();// inicializa estructuras de FS2

264

Page 283: El Camino Del Conejo

Servidor HTTP

if ((rc=sspec_automount(SSPEC_MOUNT_FS,NULL,NULL,NULL))==0){ while(1) {

http_handler(); costate{

if(!BitRdPortI(PBDR,2)){ // cuando S2if(fopen_wr(&file,filenumber) && fcreate(&file,filenumber))

{ printf("No puedo abrir: %d\n",filenumber);

exit(-1); } else {

fseek(&file, 0, SEEK_END); // fin de archivomktm(&mtm,SEC_TIMER);

sprintf(buffer,"Evento: %s,%02d/%02d/%02d%02d:%02d:%02d\n",evento,mtm.tm_mday,mtm.tm_mon,mtm.tm_year,mtm.tm_hour,mtm.tm_min,mtm.tm_sec)

;len=strlen(buffer);

fwrite(&file, buffer, len); // guarda el textofclose(&file); // cierra el archivowaitfor(DelayMs(100)); // anti-rebote

waitfor(BitRdPortI(PBDR,2)); // espera que libere S2waitfor(DelayMs(100)); // anti-rebote

}}

} }}else printf("No puedo inicializar: %d\n",rc);

}

El HTML correspondiente es el siguiente:

<html><head><title></title></head><body bgcolor="#FFFFFF" link="#009966" vlink="#FFCC00" alink="#006666"topmargin="0" leftmargin="0" marginwidth="0" marginheight="0"><center><img SRC="rabbit1.gif" ></center><h1>Archivos en FS2 para web server</H1><hr><UL><LI><A HREF="log.txt">Log de eventos (mapeado)</A></LI><LI><A HREF="/fs2/file1">Log de eventos (crudo)</A></LI></UL><hr></body></html>

Para trabajar con archivos en FAT es similar, sólo que el prefijo depende de lapartición, como se indicara en el apartado sobre Zserver.

Autenticación

Este es el proceso por el cual es posible identificar a un usuario, de modo que nospermita que sólo aquellos usuarios autorizados puedan acceder a determinadaspáginas.En versiones de DC 8.3 e inferiores, podemos tener algunas de las páginas

protegidas por password de una forma muy simple, definiendo el realm al que dichopassword está asociado. Cuando un web browser solicite una página protegida, le

265

Page 284: El Camino Del Conejo

Networking con Rabbit

aparecerá un cartelito describiéndole la situación e invitándolo a ingresar usuario ypassword para el realm correspondiente:

Para definir el realm, disponemos de la siguiente estructura:

const static HttpRealm myrealm[]={usuario,password,realm};

Por ejemplo, en el caso anterior:

const static HttpRealm myrealm[]={"iuserneim","pasguord","Escrachator"};

y por consiguiente, deberá ingresarse iuserneim como user name y pasguord comopassword.La forma de proteger una página era agregarle el puntero al realm en el último

parámetro de la estructura donde se la asociaba al web server, http_flashspec:

{HTTPSPEC_FILE,"/index.shtml",index_html,NULL,0,NULL,NULL} // no protegida{HTTPSPEC_FILE,"/learn.shtml",learn_html,NULL,0,NULL,myrealm} // protegida

Lo mismo para una función CGI:

{HTTPSPEC_FUNCTION,"/learn.cgi",0,learn,0,NULL,NULL} // no protegida{HTTPSPEC_FUNCTION,"/learn.cgi",0,learn,0,NULL,myrealm} // protegida

En versiones de DC 8.5 y superiores, el manejo es más poderoso pero requieremayor complejidad. En primer lugar, los archivos se asocian a grupos de usuarios, loque puede hacerse de forma estática (en flash). Los grupos de usuarios son dieciséis,uno por cada bit de una palabra; ningún bit seteado corresponde a ningún grupo, elbit 0 seteado corresponde al grupo 0. Luego, se definen usuarios que se asocian aestos grupos, lo cual se realiza de forma dinámica, es decir, es posible agregar unusuario en cualquier momento. El cambio inmediato de uno a otro método, sería:

SSPEC_RESOURCE_P_XMEMFILE(URL,addr,realm,rg,wg,server,aut)SSPEC_RESOURCE_P_FUNCTION(function,addr,realm,rg,wg,server,aut)

Obsérvese la inclusión de _P_ en la macro, lo que indica 'protected'. El URL y sudirección en memoria, la función y su dirección en memoria siguen como siempre,realm es el nombre del realm (el texto a mostrar), rg es la palabra conteniendo losgrupos con permiso de lectura, wg es la palabra conteniendo los grupos con permiso

266

Page 285: El Camino Del Conejo

Servidor HTTP

de escritura, server es el servidor que nos interesa y aut es el tipo de autenticaciónsoportado (a negociar con el browser). Siguiendo con el ejemplo anterior:

SSPEC_RESOURCE_P_XMEMFILE("/setup.shtml",setup_html,"Escrachator",1,0,SERVER_HTTP,SERVER_AUTH_BASIC | SERVER_AUTH_DIGEST),

SSPEC_RESOURCE_P_FUNCTION("/setup.cgi",setup,"Escrachator",1,0,SERVER_HTTP,SERVER_AUTH_BASIC | SERVER_AUTH_DIGEST)

Solamente aquellos usuarios pertenecientes al grupo 0 (bit 0 seteado) podrán accedera /setup.shtml y ejecutar /setup.cgi (permiso de lectura), nadie tiene permiso deescritura sobre ellas (están en flash de todos modos), sólo son visibles por el servidorHTTP5, y se soportan dos métodos de autenticación: básico (clear text) y digest(simple encripción con challenge handshake).

Usuarios

Para asociar un usuario a un grupo, ejecutamos lo siguiente en run time:

uid=sauth_adduser(username,password,server); // crea el usuariosauth_setusermask(uid, groups, NULL); // lo agrega al grupo

donde uid es la identificación asignada al crear el usuario (un entero), groups es el olos grupos a los que queremos que este usuario pertenezca (de acuerdo a lo quequeremos que pueda hacer); esto es simple debido a que cada grupo estárepresentado por un bit, de modo que un simple OR lógico entre dos gruposrepresenta a ambos. Siguiendo con el ejemplo anterior, para asociar el usuarioiuserneim al grupo 0, ejecutamos lo siguiente en run time:

uid=sauth_adduser("iuserneim", "pasguord", SERVER_HTTP);sauth_setusermask(uid, 1, NULL);

En el apartado sobre cambio de dirección IP desde una página web, y en el capítulosobre configuración en campo, encontraremos breves ejemplos sobre como crear ymanejar usuarios.

Archivos y variables

En sistemas con muchas páginas o con páginas a servir en file systems, sería muytedioso marcar cada página con su realm y sus grupos, por lo que existe unaposibilidad adicional que permite proteger un directorio, o un grupo de archivos:protegerlos por prefijo. Esto se realiza mediante una tabla de reglas, que puededefinirse de forma estática y tener agregados de forma dinámica. Para definir la parteestática, deberemos agregar una definición antes de incluir la biblioteca defunciones, y luego definir la tabla de reglas:

#define SSPEC_FLASHRULES

#use "dcrtcp.lib"

5 También podrían definirse visibles para el servidor FTP y ser reemplazadas mediante FTP, pero estoes algo más complicado y lo obviaremos por el momento

267

Page 286: El Camino Del Conejo

Networking con Rabbit

#use "http.lib"

SSPEC_RULETABLE_STARTSSPEC_RULE("/setup", "", ADMIN_GROUP, 0, SERVER_HTTP)

SSPEC_RULETABLE_END

De esta forma, todos los archivos cuyo URL comience con /setup serán protegidos,y se solicitará la autenticación del usuario, que deberá pertenecer en este caso aADMIN_GROUP. Nótese que las funciones SSI y CGI no se incluyen dentro de estacategoría. Para proteger una función, debe emplearse el método visto anteriormentede protección individual.Nuevamente, en el apartado sobre cambio de dirección IP desde una página web, y

en el capítulo sobre configuración en campo, encontraremos breves ejemplos sobrecomo manejar autenticación en archivos y variables.

"Archivos" agregados en forma dinámica

Si necesitamos que un pseudo-archivo como la imagen generada del ejemploanterior, tenga acceso restringido para determinados grupos de usuarios, necesitamosmodificar sus permisos de acceso, lo cual hacemos mediante la siguiente función:

sspec_setpermissions(idx,realm,rg,wg,server,aut,mime)

donde idx es el valor retornado por sspec_addxmemfile() al agregar el "archivo" enforma dinámica, realm es el nombre del realm (el texto a mostrar), rg es la palabraconteniendo los grupos con permiso de lectura, wg es la palabra conteniendo losgrupos con permiso de escritura, server es el servidor que nos interesa; aut es el tipode autenticación soportado (a negociar con el browser). El parámetro mime es unpuntero a una estructura que define el tipo MIME, en caso que éste no figure en latabla estática

Archivos en file systems, agregados en forma dinámica

Así como definimos una tabla estática con reglas para autenticación, tambiénpodemos agregar entradas en forma dinámica, lo que hacemos llamando a la función:

sspec_addrule(url,realm,rg,wg,server,aut,mime);

donde los parámetros son prácticamente los mismos que para la funciónsspec_setpermissions(), excepto por url, que es el nombre del archivo dentro de lajerarquía del servidor HTTP. La cantidad máxima de reglas se define mediante la macro:

#define SSPEC_MAXRULES 10

que por defecto es diez, como se indica. Es preferible utilizar, en lo posible, la tablaestática, y respetar dichas reglas al agregar nuevos archivos; por ejemplo, se protegetodo un directorio, y se agrega el archivo dentro o fuera de ese directorio según debaestar protegido o no. Esto es fundamentalmente porque siguiendo esta sugerencia, se

268

Page 287: El Camino Del Conejo

Servidor HTTP

utilizan sólo una o dos reglas de acceso, mientras que de otro modo existe una reglapara cada archivo, más todo el código para generarlas.Sin embargo, existe un pequeño detalle aquí, el cual veremos más adelante al

analizar el servidor FTP. Lo que podemos comentar, como adelanto, es que esto nose refiere al archivo, sino al prefijo, es decir, la parte del URL que identifica alarchivo dentro de la estructura de directorio del servidor HTTP. Como hemossugerido en el apartado sobre Zserver, existe otra forma de acceder al mismo archivopero por otro camino, lo cual podría utilizarse para burlar la protección. Comocomentáramos, lo analizaremos al estudiar el servidor FTP, dado que resulta másclaro.

HTTP upload

Si necesitamos que el usuario pueda subir información a nuestro equipo de manerasimple, disponemos de un CGI que nos permite realizar esta tarea. Además, existendos excelentes samples que muestran cómo utilizarlo, una de ellas además tiene supropio data handler para manejar la escritura. Nos referimos aSamples\tcpip\http\HTTP_UPLD.c y Samples\tcpip\http\Upld_fat.c. Ambas se basanen FAT.Para este ejemplo, nosotros nos basaremos en FS2, que es menos flexible, pero

fundamentalmente más económico. Todo lo relativo a la inicialización y uso de FS2se encuentra en el capítulo sobre file systems.El CGI que Rabbit provee dentro de la biblioteca de funciones del servidor HTTP,

junto con todo el código que lo soporta, se habilita mediante la siguiente macro:

#define USE_HTTP_UPLOAD

El resto, no difiere demasiado de lo que ya hemos visto. En primer lugar, comovamos a mostrar una foto, hacemos un mapeo del nombre que necesitamos con unaextensión reconocida al nombre simple dentro de FS2; luego, vamos a definir lospermisos de autenticación para que sólo el administrador pueda ejecutar el CGI, yademás, que sólo el administrador pueda escribir en el FS2, mientras que cualquierapuede leer. El listado del código es el siguiente:

#class auto

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#define USE_HTTP_UPLOAD

#define SSPEC_FLASHRULES

#memmap xmem

269

Page 288: El Camino Del Conejo

Networking con Rabbit

#use "dcrtcp.lib"#use "fs2.lib"#use "http.lib"

#ximport "upload.html" index_html

#define ADMIN_GROUP 0x0002

SSPEC_MIMETABLE_STARTSSPEC_MIME(".html", "text/html"),SSPEC_MIME(".jpg", "image/jpeg"),SSPEC_MIME(".cgi", "")

SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.html", index_html),SSPEC_RESOURCE_P_CGI("upload.cgi", http_defaultCGI,"subidas",

ADMIN_GROUP, 0x0000, SERVER_HTTP, SERVER_AUTH_BASIC),SSPEC_RESOURCE_FSFILE("/foto.jpg",3)

SSPEC_RESOURCETABLE_END

SSPEC_RULETABLE_STARTSSPEC_MM_RULE("/fs2", "subidas", 0xFFFF, ADMIN_GROUP, SERVER_HTTP,

SERVER_AUTH_BASIC, NULL)SSPEC_RULETABLE_END

void main(){int rc,uid;

uid = sauth_adduser("admin", "istrador", SERVER_HTTP);sauth_setusermask(uid, ADMIN_GROUP, NULL);sauth_setwriteaccess(uid, SERVER_HTTP);

sock_init();http_init();tcp_reserveport(80);

if ((rc=sspec_automount(SSPEC_MOUNT_FS,NULL,NULL,NULL))==0){ // inicializa estructuras de FS2

while(1) { http_handler();}

}else printf("No puedo inicializar: %d\n",rc);

}

Finalmente, el HTML muestra la foto, y nos ofrece poder elegir un archivo parasubir, el cual se grabará en FS2 como 3, y se verá en el servidor HTTP comofoto.jpg, gracias al mapeo que hicimos. Una vez subida, volvemos a pedir la páginaprincipal (link a home o como más nos agrade)Dado que el tipo MIME que entregamos es image/jpeg, la imagen que subamos

deberá estar en este formato. Una última consideración a tener en cuenta, aunquesimple, es que el dispositivo FS2 que utilicemos deberá tener suficiente espaciocomo para alojar el archivo y la metadata que este necesite, de acuerdo al tamaño desectores empleado.

270

Page 289: El Camino Del Conejo

Servidor HTTP

A continuación, el listado del HTML correspondiente:

<html><head><title>HTTP Upload</title></head><body>

<IMG SRC="foto.jpg"><hr><FORM ACTION="upload.cgi" METHOD="POST" enctype="multipart/form-data"><TABLE BORDER=0 CELLSPACING=2 CELLPADDING=1>

<TR><TD ALIGH=RIGHT>Foto para subir</TD><TD><INPUT TYPE="FILE" NAME="/fs2/file3" SIZE=50></TD>

<TR>

</TABLE><INPUT TYPE="SUBMIT" VALUE="Upload"></FORM>

</body></html>

Otra alternativa, con tal vez menos complicaciones, es emplear un cliente FTP ytraer la información del servidor, como analizamos en un apartado siguiente.

Actualización dinámica de variables: AJAX

La sigla AJAX significa Asynchronous JavaScript And XML, y de su nombrepodemos deducir que no está muy relacionada con Rabbit. Esta vertiginosatecnología que nos recuerda a un conocido fabricante de inviolables cajas deseguridad en entrañables dibujos animados, fue popularizada por Google y estábasada en los siguientes standards:

� JavaScript� XML� HTML� CSS

En esencia, AJAX es algo que se ejecuta en el navegador web, independiente de losservidores, y por ello es realizable en cualquiera de éstos, incluyendo a Rabbit. Noes algo que el servidor provea sino que el cliente realiza.Hemos visto en los ejemplos del libro introductorio que cuando actualizábamos una

variable enviábamos un redireccionamiento o una nueva página completaconteniendo la respuesta. Si tenemos un sistema que debe mostrar información entiempo real, tenemos los siguientes inconvenientes:

� resulta bastante molesto el redibujar constantemente la pantalla� el tráfico de material redundante, ajeno a lo que realmente cambia, es

impropio.

271

Page 290: El Camino Del Conejo

Networking con Rabbit

Con AJAX, un código JavaScript intercambia con el servidor sólo aquellos datosnecesarios a través de un objeto XMLHttpRequest y luego actualiza la página web enbackground, sin necesidad de recargarla. Esto resuelve ambos inconvenientesmencionados.

El código JavaScript

La implementación de AJAX requiere la utilización de conceptos de programaciónde páginas web que escapan a la intención de este libro.En la página web de este texto incorporamos un ejemplo; para la comprensión y

estudio del mismo se sugiere consultar la amplia literatura disponible paraJavaScript.

El servidor Rabbit

En lo propio del servidor Rabbit, sólo debemos ocuparnos de la generación delarchivo XML en el que estarán los valores de las variables que deben seractualizadas. Esta tarea podemos realizarla generando un archivo dinámico omediante un script RabbitWeb, que tal vez sea la opción más simple.En esencia, se trata de un archivo XML en el que los valores son provistos por el

script RabbitWeb en la forma <?z print @variable ?>. Cuando el cliente(navegador) web solicita el archivo XML, el zhtml_handler intercepta el scriptRabbitWeb y completa el texto con los valores de las variables, por ejemplo:

<?xml version='1.0' encoding='ISO-8859-1'?><values>

<analog><ch> <?z printf("%.2f",@ad_inputs[0]) ?> </ch><ch> <?z printf("%.2f",@ad_inputs[1]) ?> </ch>

</analog><datetime> <?z print(@strDateTime) ?> </datetime>

</values>

En la página web de este texto incorporamos un ejemplo completo del fenómenoAJAX.

Gráficos: JavaScript, Flash

Éste es otro terreno donde sucede lo mismo que comentáramos al analizar AJAX.El servidor web de Rabbit puede servir todo lo que sirve un servidor web; es decir, siqueremos que sirva animaciones Flash, las servirá, sólo debemos colocar el URLcorrespondiente en el directorio. A su vez, los objetos Flash pueden realizar gráficostomando datos de archivos XML, mediante el mismo principio de AJAX. Lacomplejidad en este tipo de implementaciones no está en el Rabbit sino en laprogramación de los JavaScripts y Flash correspondientes, todo lo que debemoshacer en el Rabbit es generar un archivo XML con la información de las variables deproceso que queremos transferir para que el navegador haga todo el trabajo.

272

Page 291: El Camino Del Conejo

Servidor HTTP

En la página web de este texto se encuentra un ejemplo de gráficos Flash.

Cliente HTTP

Si utilizamos un Rabbit 4000 o superior, DC10 provee un cliente HTTP.Para utilizar las funciones de la biblioteca que provee el cliente HTTP, necesitamos

obviamente un socket TCP:

tcp_Socket sock;

y una estructura de tipo httpc_Socket:

httpc_Socket hsock;

La inicialización se realiza mediante la siguiente llamada a función:

httpc_init(&hsock,&sock);

Para realizar la petición:

httpc_get_url(&hsock,url);

donde url es un puntero al contenido del GET, por ejemplo:

const char url[]=”/index.html”

Para el caso que necesitemos autenticación, disponemos de una llamada a funciónmás completa:

httpc_get(&hsock,server,port,url,auth);

donde:� server es un puntero al nombre a resolver del servidor� port es un entero conteniendo el número de port, generalmente 80� auth es un puntero a un string conteniendo usuario:password

A continuación, disponemos de funciones para leer los headers, saltearlos, y leer elcuerpo del objeto. Por ejemplo, podemos leer el header y comparar contra uno enparticular:

while (hsock.state == HTTPC_STATE_HEADER){ retval = httpc_read_header(&hsock, url, sizeof(url)); if (retval > 0){

if ((value = httpc_headermatch(url,"Content-Type"))){ // procesamos el tipo }

} else if (retval < 0){ // ERROR }

273

Page 292: El Camino Del Conejo

Networking con Rabbit

}

Podemos obviar los headers mediante la llamada a función:

httpc_skip_headers(&hsock);

Podemos leer el cuerpo del objeto de la siguiente forma:

while (hsock.state == HTTPC_STATE_BODY){ retval = httpc_read_body(&hsock, body, 64); if (retval > 0){

// PROCESAMOS } else if (retval < 0){ // ERROR } }

y finalmente cerramos la operación:

httpc_close (&hsock);

Existe una sample que muestra toda esta operatoria: samples\tcpip\http\http client.c

Cliente FTP

Puede resultar útil disponer de un cliente FTP para depositar determinadainformación en un servidor remoto. Por ejemplo, en el libro introductoriodesarrollamos un control de acceso, el cual reportaba el log de accesos vía FTP, auna hora determinada del día. Otra aplicación también puede ser un log de eventos,estadísticas de proceso, bases de registros para información interna, etc. Si bienhemos visto este tema en dicho libro, ahondamos un poco más los conceptos aquí. Para compilar el cliente FTP, necesitamos incluir la biblioteca de funciones:

#use "ftp_client.lib"

La forma más simple de utilizarlo, sin recurrir a file systems, es pasarledirectamente la dirección en memoria donde están los datos a transmitir, y sulongitud:

ftp_client_setup(ftp_server,0,user,password,FTP_MODE_UPLOAD,filename,NULL,addr,len);

Si en realidad queremos traer la información al Rabbit, haremos un download.Obviamente, addr deberá apuntar a RAM:

ftp_client_setup(ftp_server,0,user,password,FTP_MODE_DOWNLOAD,filename,NULL,addr,len);

La dirección del servidor es un entero largo, el cual obtendremos de forma similar acomo hiciéramos con el cliente POP, llamando a resolve():

274

Page 293: El Camino Del Conejo

Cliente FTP

ftp_client_setup(resolve("samefetepejost.samdomein"),...);// por nombre

ftp_client_setup(resolve("192.168.1.50"),...); // por IP

aunque en este último caso también podríamos usar inet_addr():

ftp_client_setup(inet_addr("192.168.1.50"),...);

Sin embargo, la longitud máxima de archivo que podemos transferir está limitada a32 kilobytes, y el espacio de almacenamiento está limitado por ser addr un punterode 16-bits al espacio root, por lo que es altamente probable y tal vez conveniente quedebamos definir un data handler:

ftp_client_setup(ftp_server,0,user,password,FTP_MODE_UPLOAD,filename,NULL,NULL,0);

ftp_data_handler(ftp_datahandler, NULL, 0);

Por supuesto que en vez de FTP_MODE_UPLOAD también podemos utilizarFTP_MODE_DOWNLOAD y obtener un archivo. Si estamos detrás de un firewall ynecesitamos usar el modo pasivo, haremos un or lógico con FTP_MODE_PASSIVE:

ftp_client_setup(ftp_server,0,user,password,FTP_MODE_UPLOAD | FTP_MODE_PASSIVE,

filename,NULL,addr,len);

A continuación desarrollaremos un data handler simple para subir un archivo a unservidor, lo cual nos puede ser útil para reportar datos de un logger, registros de uncontrol de acceso, etc. El sistema llamará a nuestro data handler proveyéndonos de ladirección de un buffer y su longitud (por defecto FTP_MAXLINE, 256 bytes), másuna variable de estado (dhnd_data) que podemos utilizar para contar los registros aenviar. También es posible operar con un offset (variable offset) respecto al inicio delos datos, de forma similar a como nos desenvolvimos al operar sobre un socketdirectamente, y como haremos más adelante, en otro capítulo, al desarrollar unaaplicación. Todo depende del tipo de tarea a realizar; en un ejemplo como éste, en elcual trabajamos no con un archivo o un flujo continuo de datos sino con una base deregistros o listado de eventos, personalmente prefiero las máquinas de estados. Porúltimo, los flags nos indican qué operación debe realizar nuestro data handler.

int ftp_datahandler(char * data, int len, longword offset, int flags, void * dhnd_data){auto int *printline;

printline = (int *)dhnd_data;

switch (flags) {case FTPDH_IN: // para handlers que hacen "get" (download) return 0; case FTPDH_OUT: // para handlers que hacen "put" (upload) if(*printline >= records)

return 0; // genera registro en data (*printline)++; // Incrementa número de registro return(strlen(data));

275

Page 294: El Camino Del Conejo

Networking con Rabbit

case FTPDH_END:case FTPDH_ABORT: *printline=0; return 0;

} return -1;}

Este data handler es llamado por el cliente ftp en la fase de transferencia de datos, elprograma principal asigna el momento en que éste corre mediante la llamada aftp_client_tick(). El data handler será llamado con los parámetros correspondientespara poder enviar el "archivo" al server, particularmente flags tendrá el valorFTPDH_OUT. En nuestro caso, lo que hacemos es generar un listado de registros delog interno, que quedará en el servidor FTP como un archivo. Cuando el envío delarchivo termine, flags tendrá el valor FTPDH_END, y si la conexión es abortada,flags tendrá el valor FTPDH_ABORT. Como para nuestros fines, es lo mismo,simplemente reseteamos el contador de registros para iniciar de cero un nuevolistado la próxima vez.A continuación, el listado completo de un pequeño ejemplo que lee uno de los

botones de un módulo y genera un listado de una serie de líneas de texto, a modo deregistros de log, que será almacenado en un servidor FTP:

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"#define MY_NAMESERVER "200.49.156.3"

#memmap xmem#use "dcrtcp.lib"#use "ftp_client.lib"

#define USER "scaprile"#define PASSWORD "maipasguord"

#define FTP_SERVER "192.168.1.50"#define FTP_MODE (FTP_MODE_UPLOAD | FTP_MODE_PASSIVE)

#define FILENAME "file00.txt"

#define LINEAS 8

int ftp_datahandler(char * data, int len, longword offset, int flags, void * dhnd_data){auto int *printline;

printline = (int *)dhnd_data;

switch (flags) {case FTPDH_IN:

return 0;case FTPDH_OUT:

if((*printline) >= LINEAS)

276

Page 295: El Camino Del Conejo

Cliente FTP

return 0;sprintf(data, "Linea #%2d\r\n",++(*printline));return(strlen(data));

case FTPDH_END:case FTPDH_ABORT:

return 0;}return -1;

}

main(){int flinenum,ftpret;

sock_init(); while(1){ if(!BitRdPortI(PBDR,2) || !BitRdPortI(PBDR,3)){

printf("Ya te vi\n");if(!ftp_client_setup(resolve(FTP_SERVER),0,USER,PASSWORD,

FTP_MODE,FILENAME,NULL,NULL,0)) { flinenum=0; ftp_data_handler(ftp_datahandler,&flinenum,0); printf("Mandando..."); while((ftpret=ftp_client_tick())== FTPC_AGAIN); if(ftpret==FTPC_OK)

printf("listo\n"); else printf("ERROR\n");

} } }}

Archivos en file systems

Para subir y/o bajar archivos de y/o a un file system mediante el cliente FTP,deberemos escribir un data handler que se encargue de escribir o leer en el archivo;de la misma forma que operamos para hacer los ejemplos de logs, cuandoestudiamos este tema.En lo referente al manejo del data handler, en este caso sí utilizamos los parámetroslen y offset; en el primer caso para saber de cuántos datos disponemos para escribir,y en el segundo, para tener una idea de la cantidad de datos que estamos recibiendo ypoder abortar la operación cuando superamos el tamaño máximo de archivo quepodemos o queremos alojar.El ejemplo siguiente se conecta a un servidor FTP, trae un determinado archivo, y

lo guarda con el nombre 2 en FS2, sobreescribiendo cualquier versión anterior delarchivo. Una vez obtenido, lo abrimos en modo lectura para mostrarlo en pantalla (seaconseja hacer la prueba con archivos de texto...). De igual forma, es posible escribirotro data handler que transfiera un archivo al servidor. Todo lo relativo a lainicialización y uso de FS2 se encuentra en el capítulo sobre file systems; noscentramos aquí en el ejemplo de cliente FTP:

277

Page 296: El Camino Del Conejo

Networking con Rabbit

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#memmap xmem

#use "dcrtcp.lib"#use "ftp_client.lib"

#define USER "maiiuser"#define PASSWORD "maipasguord"

#define FTP_SERVER "192.168.1.50"#define FTP_MODE (FTP_MODE_DOWNLOAD | FTP_MODE_PASSIVE)

#define FILENAME "file00.txt"

#use fs2.lib

File file;

int ftp_datahandler(char * data, int len, longword offset, int flags, void * dhnd_data){

switch (flags) {case FTPDH_OUT:

return 0;case FTPDH_IN: if(offset>100000L) // seguro return(0); fwrite(&file, data, len); // guarda el texto

return(len);

case FTPDH_END:case FTPDH_ABORT:

return 0;}return -1;

}

main(){int rc,ftpret;char buffer[128];

sock_init();if ((rc=fs_init(0,0))!=0) { // inicializa estructuras de FS2

printf("No puedo inicializar: %d\n",rc); exit(-1);}while(BitRdPortI(PBDR,2) && BitRdPortI(PBDR,3));printf("Ya te vi\n");if(!ftp_client_setup(resolve(FTP_SERVER),0,USER,PASSWORD,FTP_MODE, FILENAME,NULL,NULL,0)) {

if(fopen_wr(&file,2) && fcreate(&file,2))printf("No puedo grabar en el archivo\n");

else {ftp_data_handler(ftp_datahandler,NULL,0);printf("Trayendo...");while((ftpret=ftp_client_tick())== FTPC_AGAIN);

278

Page 297: El Camino Del Conejo

Cliente FTP

fclose(&file); // cierra el archivo if(ftpret==FTPC_OK) { printf("listo\n");

if(fopen_rd(&file,2)) printf("No puedo abrir el archivo\n");

else { while(rc=fread(&file,buffer,127)){

buffer[rc]=0; puts(buffer);

}fclose(&file); // cierra el archivo

}}else

printf("ERROR\n");}

}}

Servidor FTP

Tal vez sea necesario disponer de un repositorio de información, donde otrossistemas puedan ingresar y acceder a ésta. Si ese repositorio debe ser nuestro sistemabasado en Rabbit, podemos utilizarlo como servidor FTP. Para compilar el servidor FTP, necesitamos incluir la biblioteca de funciones:

#use "ftp_server.lib"

La inicialización y manejo se realizan mediante dos funciones: ftp_init() yftp_tick(), respectivamente. La función ftp_init() puede utilizarse para definir un setdiferente de data handlers, lo cual escapa a nuestras intenciones. La funciónftp_tick() debe llamarse periódicamente para mantener el server activo.

Una vez más, recordemos que estamos en un sistema dedicado, y debemos cuidar yadministrar los recursos. Podemos definir la cantidad máxima de servidores, es decir,de conexiones simultáneas que permitimos, que por defecto es una:

#define FTP_MAXSERVERS 1

Recordemos que tenemos un límite en la cantidad de sockets disponibles, comoviéramos al comenzar el análisis de TCP y en el servidor HTTP:

#define MAX_TCP_SOCKET_BUFFERS 4

El servidor atiende solamente pedidos de conexión en la interfaz por defecto(IF_DEFAULT). Si deseamos que atienda en cualquiera (IF_ANY), o en una enparticular (IF_ETH0, por ejemplo), lo indicamos mediante otra macro:

#define FTP_INTERFACE IF_ANY

279

Page 298: El Camino Del Conejo

Networking con Rabbit

Finalmente, continuando con la intención de preservar recursos, existe un tiempomáximo de espera en una conexión de control para que el cliente envíe un comando,caso contrario se corta la conexión, liberando el socket:

#define FTP_TIMEOUT 16

algo similar existe en la conexión de datos:

#define FTP_DTPTIMEOUT 16

El manejo de un servidor FTP requiere de un lugar donde almacenar los archivos.Una implementación muy reducida permite servir archivos en xmem, pero si sebusca mayor flexibilidad, se debe emplear algún sistema de archivos, como el FS2.En ambos casos, se requiere del manejo de una base de usuarios y passwords.El manejo de usuarios y sus passwords es similar a lo que hemos visto para

autenticación en el servidor HTTP, lo cual es evidente, dado que es Zserver elencargado de esta tarea. La autenticación se maneja, como vimos, por grupos.El sistema soporta un equivalente al usuario anonymous, muy común en este

entorno, mediante la definición de un usuario como cualquier otro, y luegodeclarando que ese uid es en realidad del usuario anonymous:

uid = sauth_adduser("anonymous", "", SERVER_FTP);ftp_set_anonymous(uid);

Como se observa, el nombre de usuario podría en efecto ser cualquier otro; perodado que el standard utilizado es éste, se recomienda mantenerlo.En cuanto a manejo de usuarios, el sistema permite acceso solamente a aquellos

"archivos" que ese usuario tiene permitidos, al pertenecer a determinado grupo.

"Archivos" en xmem

Estáticos

Como podemos imaginarnos, esto no difiere mucho de lo observado para elservidor HTTP con autenticación, por lo que aconsejamos tener presente dichainformación. Definimos en este caso dos grupos de usuarios6. Ingresamos los"archivos" como "protected", debido a que debemos vincularlos con algún grupopara sus permisos de acceso. El campo realm, pasará a ser el texto a desplegar en laposición en que un servidor FTP muestra el grupo al que pertenece el archivo.Luego, ingresamos los usuarios y los vinculamos a su grupo correspondiente. Aldeclarar al usuario anonymous como lo que es, facilitamos el clásico login, losarchivos que pueden ser accedidos por todos los marcamos como accesibles por

6 Si bien la autenticación es por grupos, no es necesario definir un grupo extra para el usuarioanonymous, si hay archivos que le pertenecen se marcan como pertenecientes a todos los grupos (pordefinición)

280

Page 299: El Camino Del Conejo

Servidor FTP

todos los grupos (0xFFFF). Un pequeño detalle: el campo server debe incluirSERVER_FTP, y el campo aut la única soportada actualmenteSERVER_AUTH_BASIC.

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#use "dcrtcp.lib"#use "ftp_server.lib"

#ximport "rabbit1.gif" rabbit1_gif#ximport "0.jpg" inca_jpg#ximport "oled.jpg" oled_jpg

#define ANON_GROUP 0xFFFF#define ADMIN_GROUP 0x0002#define HACKER_GROUP 0x0004

// directorio del server, funciones y variables de SSISSPEC_RESOURCETABLE_START

SSPEC_RESOURCE_P_XMEMFILE("/rabbit1.gif",rabbit1_gif,"admingroup",ADMIN_GROUP,0,SERVER_FTP,SERVER_AUTH_BASIC),

SSPEC_RESOURCE_P_XMEMFILE("/inca.jpg",inca_jpg,"hacker",HACKER_GROUP,0,SERVER_FTP,SERVER_AUTH_BASIC),

SSPEC_RESOURCE_P_XMEMFILE("/oled.jpg",oled_jpg,"eniuan",0xFFFF,0,SERVER_FTP,SERVER_AUTH_BASIC),

SSPEC_RESOURCETABLE_END

void main(){int uid;

// Create a user IDuid = sauth_adduser("admin", "istrador", SERVER_FTP);sauth_setusermask(uid, ADMIN_GROUP, NULL);

uid = sauth_adduser("elba", "rilete", SERVER_FTP);sauth_setusermask(uid, HACKER_GROUP, NULL);

uid = sauth_adduser("anonymous", "", SERVER_FTP);ftp_set_anonymous(uid);

sock_init();ftp_init(NULL);while (1) {

ftp_tick(); // aplicación}

}

Dinámicos

Cuando el archivo es agregado de forma dinámica, como analizáramos para elservidor HTTP, lo agregamos con sspec_addxmemfile() y controlamos a quiénpertenece con sspec_setpermissions(). Si no tenemos archivos estáticos, lo indicamosmediante la macro:

281

Page 300: El Camino Del Conejo

Networking con Rabbit

#define SSPEC_NO_STATIC

Por ejemplo:

fid=sspec_addxmemfile("/rabbit1.gif",rabbit1_gif,SERVER_FTP);sspec_setpermissions(fid,"admingroup",ADMIN_GROUP,0,SERVER_FTP,

SERVER_AUTH_BASIC,NULL);

fid=sspec_addxmemfile("/oled.jpg",oled_jpg,SERVER_FTP);sspec_setpermissions(fid,"admingroup",0xFFFF,0,SERVER_FTP,

SERVER_AUTH_BASIC,NULL);

Archivos en FS2

Antes de DC8.5, el uso de FS2 requería de un mapa que asocie los nombres de losarchivos en el directorio del servidor, a los nombres permitidos en FS2 (númerosenteros). Dicho mapa se almacenaba en el UserBlock.A partir de DC8.5, se puede emplear Zserver, con lo cual se establece un mapeo

automático entre el nombre en el directorio y el nombre en FS2. Por ejemplo, elarchivo 35 se ve en el servidor como /fs2/file35. Dado que utilizaremos la jerga deFS2, se recomienda la lectura del capítulo correspondiente para tener los conceptosal día. En dicho capítulo, además, analizamos todo lo necesario para configurar einicializar el sistema de archivos.

Por defecto, el file system se monta con permisos de acceso para todos, por lo cualse debe restringir el acceso a lo que se deba. En un primer ejemplo, vamos a utilizaruna regla simple, restringiendo el acceso a todo lo que es el FS2, sólo para eladministrador. Cuando utilizamos una regla de este tipo, el directorio fs2 aparece enel listado, pero al acceder al mismo no se muestra el contenido, cuando el usuario noestá habilitado. Los dos siguientes son ejemplos del listado del directorio de dosusuarios, primero admin, y luego anonymous:

Connected to 192.168.1.54.220 Hello! Welcome to ZWorld TinyFTP!Name (192.168.1.54:scaprile): admin331 Password requiredPassword:230 User logged in.Remote system type is UNIX.ftp> dir227 Entering Passive Mode (192,168,1,54,4,0).150 Opening ASCII mode data connection for /bin/lstotal 1dr-------- 1 admin soloparami 0 Jan 1 1980 fs2226 Transfer complete.ftp> cd fs2250 OKftp> dir227 Entering Passive Mode (192,168,1,54,4,1).150 Opening ASCII mode data connection for /bin/lstotal 1-r-------- 1 admin soloparami 82 Jan 1 1980 file1

282

Page 301: El Camino Del Conejo

Servidor FTP

dr-------- 1 admin soloparami 0 Jan 1 1980 ram226 Transfer complete.

Connected to 192.168.1.54.220 Hello! Welcome to ZWorld TinyFTP!Name (192.168.1.54:scaprile): anonymous331 Anonymous login - send e-mail address as password.Password:230 User logged in.Remote system type is UNIX.ftp> dir227 Entering Passive Mode (192,168,1,54,4,2).150 Opening ASCII mode data connection for /bin/lstotal 1d---r----- 1 anonymous soloparami 0 Jan 1 1980 fs2226 Transfer complete.ftp> cd fs2250 OKftp> dir227 Entering Passive Mode (192,168,69,54,4,3).150 Opening ASCII mode data connection for /bin/lstotal 1226 Transfer complete.

A continuación veremos un ejemplo muy similar al que utilizáramos con el servidorHTTP; generamos un log de eventos en FS2, y tenemos un servidor FTP en el cuales posible ingresar y obtener, entre otras cosas, el mencionado log. Utilizamos eltipo de autenticación que comentamos, de modo que sólo el administrador delsistema pueda acceder a los archivos en FS2. El código resaltado es elcorrespondiente al servidor FTP y la autenticación:

#class auto#memmap xmem

#use fs2.lib

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#define SSPEC_NO_STATIC#define SSPEC_FLASHRULES

#use "dcrtcp.lib"#use "ftp_server.lib"

File file;const char S2[]="presión de S2";

#define ANON_GROUP 0x0001#define ADMIN_GROUP 0x0002

SSPEC_RULETABLE_STARTSSPEC_RULE("/fs2", "soloparami", ADMIN_GROUP, 0, SERVER_FTP)

SSPEC_RULETABLE_END

main(){

283

Page 302: El Camino Del Conejo

Networking con Rabbit

int rc,filenumber;unsigned char len;char buffer[256],*evento;struct tm mtm;

int uid;

// Create a user IDuid = sauth_adduser("admin", "istrador", SERVER_FTP);sauth_setusermask(uid, ADMIN_GROUP, NULL);

uid = sauth_adduser("anonymous", "", SERVER_FTP);sauth_setusermask(uid, ANON_GROUP, NULL);ftp_set_anonymous(uid);

filenumber=1;evento=S2;sock_init();ftp_init(NULL);if((rc=sspec_automount(SSPEC_MOUNT_FS,NULL,NULL,NULL))==0){

// inicializa estructuras de FS2 while(1) {

ftp_tick(); costate{ if(!BitRdPortI(PBDR,2)){ // cuando S2

if(fopen_wr(&file,filenumber)&&fcreate(&file,filenumber)){ printf("No puedo abrir: %d\n",filenumber);

exit(-1);}else {

fseek(&file, 0, SEEK_END); // fin de archivo mktm(&mtm,SEC_TIMER);

sprintf(buffer,"Evento: %s, %02d/%02d/%02d %02d:%02d:%02d\n",evento,mtm.tm_mday,mtm.tm_mon,mtm.tm_year,mtm.tm_hour,mtm.tm_min,mtm.tm_sec);

len=strlen(buffer); fwrite(&file, buffer, len); // guarda el texto

fclose(&file); // cierra el archivowaitfor(DelayMs(100)); // anti-rebote

waitfor(BitRdPortI(PBDR,2)); // espera que libere S2waitfor(DelayMs(100)); // anti-rebote

} } } }}else printf("No puedo inicializar: %d\n",rc);

}

En realidad, estamos limitando el acceso a todo recurso cuyo nombre comience con/fs2, es decir, si existiera un archivo llamado fs256hh.gif, también caería dentro de laregla. Si esto es molestia, y no podemos cambiar el nombre del archivo, podemosescribir la regla de acceso como /fs2/, con lo cual el directorio se presentará deforma diferente, es decir, no aparecerá como perteneciente al realm mencionado,sino que esto sólo ocurrirá al listar su contenido, tema el cual veremos con un pocomás de detalle al analizar archivos sobre FAT.

En caso que se desee permitir acceso al FS2 a todos, y autenticar algunos archivospara algunas personas y otros para otras, podemos operar de la misma forma,

284

Page 303: El Camino Del Conejo

Servidor FTP

ingresando el nombre completo del archivo en la tabla de reglas, o podemos operaren forma dinámica, agregando las reglas para cada archivo en tiempo de ejecución.Todo dependerá de nuestra aplicación y sus requerimientos, en la vida real. Si no hay reglas estáticas, eliminamos la macro:

#define SSPEC_FLASHRULES

y el listado:

SSPEC_RULETABLE_STARTSSPEC_RULE("/fs2", "soloparami", ADMIN_GROUP, 0, SERVER_FTP)

SSPEC_RULETABLE_END

El listado siguiente muestra las diferencias respecto al programa ejemplo anterior(una vez eliminadas la macro y el listado de reglas):

if((rc=sspec_automount(SSPEC_MOUNT_FS,NULL,NULL,NULL))==0){ // inicializa estructuras de FS2sspec_addrule("/fs2/file1","onlimi",ADMIN_GROUP,0,SERVER_FTP,

SERVER_AUTH_BASIC,NULL); while(1) {

Sin embargo, tenemos algo interesante que mencionáramos al estudiar el servidorHTTP con archivos autenticados en file systems. Como viéramos al analizar Zserver,existe otra forma de acceder a archivos en FS2, y es a través del LX particular comosubdirectorio. Tanto en el servidor HTTP como el FTP, el prefijo que identifica alarchivo es en este caso diferente, y un usuario medianamente instruido en FTP o enlas artes del conejo puede burlar las reglas de autenticación y acceder al log, comoobservamos en el listado siguiente, que muestra la forma en que un tal anonymoussale huyendo con el log de admin:

Connected to 192.168.1.54.220 Hello! Welcome to ZWorld TinyFTP!Name (192.168.1.54:scaprile): anonymous331 Anonymous login - send e-mail address as password.Password:230 User logged in.Remote system type is UNIX.ftp> dir227 Entering Passive Mode (192,168,1,54,4,0).150 Opening ASCII mode data connection for /bin/lstotal 1dr--r--r-- 1 anonymous anon 0 Jan 1 1980 fs2226 Transfer complete.ftp> cd fs2250 OKftp> dir227 Entering Passive Mode (192,168,1,54,4,1).150 Opening ASCII mode data connection for /bin/lstotal 1----r----- 1 anonymous onlimi 82 Jan 1 1980 file1dr--r--r-- 1 anonymous anon 0 Jan 1 1980 ram226 Transfer complete.ftp> get file1local: file1 remote: file1227 Entering Passive Mode (192,168,1,54,4,2).

285

Page 304: El Camino Del Conejo

Networking con Rabbit

550 Not authorized

ftp> cd ram250 OKftp> dir227 Entering Passive Mode (192,168,1,54,4,3).150 Opening ASCII mode data connection for /bin/lstotal 1-r--r--r-- 1 anonymous anon 82 Jan 1 1980 file1226 Transfer complete.ftp> get file1local: file1 remote: file1227 Entering Passive Mode (192,168,1,54,4,4).150 Opening BINARY mode data connection (82 bytes)226 Transfer complete.

82 bytes received in 0.00356 secs (23 Kbytes/sec)

Para resolver este inconveniente, lo que hacemos es muy democráticamenteimpedir el acceso a los LX de forma individual. En casos en los que esto no seaposible, debido a que se repite un número de archivo en diferentes LX, simplementedeben agregarse dos reglas por cada archivo, una para cada prefijo posible: /fs2/file1y /fs2/ram/file1, por ejemplo. El siguiente listado muestra estas diferencias respectoal programa anterior; volvemos a colocar la macro:

#define SSPEC_FLASHRULES

y un listado:

SSPEC_RULETABLE_STARTSSPEC_RULE("/fs2/ram", "aca_no", ADMIN_GROUP, 0, SERVER_FTP)

SSPEC_RULETABLE_END

Si deseamos hacerlo dinámico, podemos, por ejemplo, hacerlo de a dos reglas porarchivo:

sspec_addrule("/fs2/file1","onlimi",ADMIN_GROUP,0,SERVER_FTP,SERVER_AUTH_BASIC,NULL);

sspec_addrule("/fs2/ram/file1","onlimi",ADMIN_GROUP,0,SERVER_FTP,SERVER_AUTH_BASIC,NULL);

Escritura

Hasta ahora hemos trabajado sirviendo archivos cargados al momento de compilar,o generados internamente, como un log. Pero ¿qué hacemos si necesitamos permitirescritura, es decir, que alguien se conecte al servidor y coloque o sobreescriba unarchivo?La implementación actual no realiza escrituras por sí sola, el developer deberá

escribir un data handler que maneje escritura, y tener en cuenta algunasparticularidades, por ejemplo:� la macro FTP_CREATE_MASK es el valor que se pasa a sspec_addfsfile(), ésta

deberá incorporar permiso de escritura (por defecto es así).� la macro FTP_EXTENSIONS deberá definirse para habilitar los comandos FTPDELE, SIZE, y MDTM, que por defecto no están soportados, dado que estorequiere mayor cantidad de elementos en las estructuras de los data handlers.

286

Page 305: El Camino Del Conejo

Servidor FTP

� la macro FTP_WRITABLE_FILES deberá definirse a 1 para que el servidorautentique el permiso de escritura de un usuario, al abrir un archivo.

� deberemos incluir los grupos con permisos de escritura en las declaraciones delos archivos con funciones como sspec_addxmemfile(), sspec_setpermissions() ysspec_addrule().

� los usuarios con permiso de escritura se definen con la funciónsauth_setwriteaccess() como se observa en los ejemplos de RabbitWeb en éste yotros capítulos del libro.

En primera instancia, tal vez sería más apropiado implementar un cliente FTP yrealizar la tarea de traer el archivo de forma que nuestra aplicación tenga el controldurante la transferencia, incluso es preferible si la información a traer debe tenercomo destino una estructura o se realiza algún procesamiento, de modo de irconvirtiéndolo a medida que lo traemos, lo que podemos hacer dentro del datahandler. Otra opción es utilizar un servidor HTTP e implementar upload, como hemos visto.

Para aquellos lectores con real interés en un servidor FTP en el cual se puedangrabar archivos, toda la información necesaria para poder escribir el data handler seencuentra en el Dynamic C TCP/IP User's Manual Vol. 2. Otra guía importante es lasample Samples\TCPIP\FTP\ftp_srv2.c.

Archivos en FAT

El módulo FAT nos permite utilizar en alto nivel la memoria flash adicionalincluida en módulos de la serie RCM3700 y 3300, por ejemplo. La operatoria (desdeel punto de vista del servidor FTP) es esencialmente la misma, sólo cambian losprefijos y algún que otro parámetro en alguna llamada a función. Un detalle a teneren cuenta, es que Zserver requiere que el módulo FAT emplee la barra / para separardirectorios, y debe incluirse la biblioteca de funciones de FAT antes que Zserver(que se incluye automáticamente con las de TCP/IP).

#define FAT_USE_FORWARDSLASH#use fat.lib#use "dcrtcp.lib"#use "ftp_server.lib"

A continuación, un ejemplo sobre un RCM3720. Básicamente es idéntico alejemplo anterior sobre FS2, pero esta vez el log lo escribimos y servimos desdeFAT, en la primera (y única) partición que hicimos sobre la serial flash de 1MB queeste módulo incluye.

#class auto#memmap xmem

#define TCPCONFIG 0

287

Page 306: El Camino Del Conejo

Networking con Rabbit

#define USE_ETHERNET 1

#define FAT_BLOCK#define FAT_USE_FORWARDSLASH#use fat.lib

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#define SSPEC_NO_STATIC#define SSPEC_FLASHRULES

#use "dcrtcp.lib"#use "ftp_server.lib"

FATfile file;const char S2[]="presión de S2";

#define ANON_GROUP 0x0001#define ADMIN_GROUP 0x0002

SSPEC_RULETABLE_STARTSSPEC_RULE("/A/", "soloparami", ADMIN_GROUP, 0, SERVER_FTP)

SSPEC_RULETABLE_END

main(){int rc;fat_part *particion;unsigned char len;char buffer[256],*evento,filename[256];struct tm mtm;

int uid;

BitWrPortI(PBDDR,&PBDDRShadow,0,7); // PB7 = entrada

uid = sauth_adduser("admin", "istrador", SERVER_FTP);sauth_setusermask(uid, ADMIN_GROUP, NULL);

uid = sauth_adduser("anonymous", "", SERVER_FTP);sauth_setusermask(uid, ANON_GROUP, NULL);ftp_set_anonymous(uid);

strcpy(filename,"milog.txt");evento=S2;sock_init();ftp_init(NULL);if ((rc=sspec_automount(SSPEC_MOUNT_FAT,NULL,NULL,NULL))==0){

// inicializa estructuras de FATparticion=fat_part_mounted[0];

// Se refiere a la primera partición de// la serial/NAND flash en el módulo

if (particion==NULL) exit(-1); while(1) { ftp_tick(); costate{

if(!BitRdPortI(PBDR,7)){ // cuando S2 if(fat_Open(particion,filename,FAT_FILE,

FAT_CREATE,&file,NULL)<0){

288

Page 307: El Camino Del Conejo

Servidor FTP

printf("No puedo abrir: %s\n",filename); exit(-1); } else {

fat_Seek(&file, 0, SEEK_END); // fin de archivo mktm(&mtm,SEC_TIMER); sprintf(buffer,"Evento: %s, %02d/%02d/%02d %02d:%02d:%02d\n",evento,mtm.tm_mday,mtm.tm_mon,mtm.tm_year,mtm.tm_hour,mtm.tm_min,mtm.tm_sec);

len=strlen(buffer); fat_Write(&file, buffer, len); // guarda el texto

fat_Close(&file); // cierra el archivowaitfor(DelayMs(100)); // anti-rebote

waitfor(BitRdPortI(PBDR,7)); // espera que libere S2waitfor(DelayMs(100)); // anti-rebote

} }}}

}else printf("No puedo inicializar: %d\n",rc);

}

Como se observa, en este caso pusimos la regla de acceso con el prefijo /A/, estoresulta claro dado que es mucho más probable tener un "archivo" en xmem cuyonombre comience con A. A diferencia de cuando servimos con FS2 y no incluimos labarra al final del prefijo, en este caso, el listado del directorio raíz muestra al recursoA como no protegido, dado que no es /A sino /A/ lo que tiene acceso restringido:

220 Hello! Welcome to ZWorld TinyFTP!Name (192.168.1.54:ingenieria): admin331 Password requiredPassword:230 User logged in.Remote system type is UNIX.ftp> dir227 Entering Passive Mode (192,168,1,54,4,0).150 Opening ASCII mode data connection for /bin/lstotal 1dr--r--r-- 1 admin anon 0 Jan 1 1980 A226 Transfer complete.ftp> cd A250 OKftp> dir227 Entering Passive Mode (192,168,1,54,4,1).150 Opening ASCII mode data connection for /bin/lstotal 1-r-------- 1 admin soloparami 123 May 9 1980 milog.txt226 Transfer complete.ftp> get milog.txtlocal: milog.txt remote: milog.txt227 Entering Passive Mode (192,168,1,54,4,2).150 Opening BINARY mode data connection (123 bytes)WARNING! 3 bare linefeeds received in ASCII modeFile may not have transferred correctly.226 Transfer complete.123 bytes received in 0.0102 secs (12 Kbytes/sec)

Por lo demás, la operatoria es exactamente la misma.

289

Page 308: El Camino Del Conejo

Networking con Rabbit

Servidores FTP y HTTP combinados

La información servida por el servidor HTTP, puede compartirse con el servidorFTP, gracias a Zserver. La clave está en definir como visibles por ambos servidores,a aquellos archivos que deseamos compartidos. El siguiente ejemplo sirve tresarchivos, uno sólo en el servidor HTTP, otro sólo en el servidor FTP, y el tercero enambos. Lo mismo es válido para archivos agregados de forma dinámica, o en filesystems, sólo debe tenerse en cuenta de completar correctamente el campo server delas funciones correspondientes.El único inconveniente es que dado que para poder poner archivos estáticos en el

servidor FTP, los definimos como "protected", es necesario disponer de un login deusuario para poder verlos en el servidor HTTP. Una alternativa para resolver esto esdefinirlos dos veces en el directorio, una para cada servidor; aunque obviamente sedesperdicia espacio en el directorio. El siguiente es el listado del ejemplo combinado:

#class auto

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"

#use "dcrtcp.lib"#use "ftp_server.lib"#use "http.lib"

#ximport "rabbit1.gif" rabbit1_gif#ximport "0.jpg" inca_jpg#ximport "oled.jpg" oled_jpg#ximport "http+ftp.html" index_html

#define ADMIN_GROUP 0x0002#define ANON_GROUP 0x0001

SSPEC_MIMETABLE_STARTSSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),SSPEC_MIME(".jpg", "image/jpeg"),

SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/",index_html),SSPEC_RESOURCE_XMEMFILE("/index.html",index_html),SSPEC_RESOURCE_XMEMFILE("/inca.jpg",inca_jpg),SSPEC_RESOURCE_P_XMEMFILE("/oled.jpg",oled_jpg,"admingroup",ADMIN_GROUP,

0,SERVER_FTP,SERVER_AUTH_BASIC),SSPEC_RESOURCE_P_XMEMFILE("/rabbit1.gif",rabbit1_gif,"ol",

0xFFFF,0,SERVER_FTP|SERVER_HTTP,SERVER_AUTH_BASIC)SSPEC_RESOURCETABLE_END

void main(){int uid;

290

Page 309: El Camino Del Conejo

Servidores FTP y HTTP combinados

uid = sauth_adduser("admin", "istrador", SERVER_FTP);sauth_setusermask(uid, ADMIN_GROUP, NULL);

uid = sauth_adduser("anonymous", "", SERVER_FTP|SERVER_HTTP);sauth_setusermask(uid, ANON_GROUP, NULL);ftp_set_anonymous(uid);

sock_init();tcp_reserveport(80);http_init();ftp_init(NULL);while (1) {

http_handler();ftp_tick();

// aplicación}

}

Desde el punto de vista del servidor FTP, lo que sucede es que aparecen todos losarchivos, pero sólo tienen permisos de acceso aquéllos que expresamentedeclaramos, como se observa en este listado, con el login del usuario anonymous:

ftp> dir227 Entering Passive Mode (192,168,1,54,4,0).150 Opening ASCII mode data connection for /bin/lstotal 1---------- 1 anonymous anon 288 Jan 1 1980---------- 1 anonymous anon 288 Jan 1 1980 index.html---------- 1 anonymous anon 39892 Jan 1 1980 inca.jpg----r----- 1 anonymous admingroup 10992 Jan 1 1980 oled.jpg-r--r--r-- 1 anonymous ol 2715 Jan 1 1980 rabbit1.gif226 Transfer complete.ftp>

La entrada con el nombre en blanco se debe a la inclusión del URL "/".

Autenticación en cliente SMTP

Hemos visto en el libro introductorio la forma de enviar emails y la simpleza de usodel cliente SMTP provisto en las bibliotecas de funciones. No obstante, antes deDC9, dicho cliente carecía de autenticación, es decir, era SMTP puro. Más allá delsimple "POP before SMTP", en el que el usuario se autentica haciendo un login alservidor POP antes de enviar el correo mediante SMTP (lo cual puede hacersesimplemente con los ejemplos de POP que viéramos en dicho libro); con DC9aparece la posibilidad de autenticarse mediante algunas de las opciones que poseeESMTP, es decir Enhanced SMTP.Un servidor SMTP comienza el diálogo con un HELO. Un servidor ESMTP, lo

inicia con EHLO, a lo cual se suceden una serie de opciones de autenticación. Sientre ellas están login o plain, aquellos que no posean DC9 o superior y no deseenadquirirlo, deberán parsear la respuesta del servidor, y simplemente iniciar undiálogo de login o enviar un string conteniendo usuario y password, sólo que

291

Page 310: El Camino Del Conejo

Networking con Rabbit

codificado en BASE647. Disponiendo de DC9, la configuración de la autenticaciónse reduce a lo siguiente:

#define USE_SMTP_AUTH#use dcrtcp.lib#use smtp.lib

...smtp_setauth("myusername", "mypassword");...

Los métodos soportados al momento de escribir este texto son LOGIN, PLAIN yCRAM-MD5.

Direcciones IP fijas

Por lo general, en muchos ejemplos vemos que las direcciones IP son fijas y únicasal momento de compilar. Esto puede ser muy útil mientras estamos desarrollando laaplicación, o si la misma no tiene demasiados requisitos de parte del usuario final ypodemos asignarle una IP fija.

#define TCPCONFIG 0#define USE_ETHERNET 1

#define MY_IP_ADDRESS "192.168.1.54"#define MY_NETMASK "255.255.255.0"

#define MY_GATEWAY "192.168.1.1"#define MY_NAMESERVER "200.42.0.108"

Recordemos que el gateway solamente es necesario si el equipo necesita conectarsecon otro fuera de su red local, es decir, debe dialogar con un router. El DNSsolamente es necesario si el equipo debe resolver nombres de hosts, lo cual puedehacerse mediante un simple llamado a resolve(). Las dos primeras macros debendefinirse antes de incluir la biblioteca de funciones de TCP/IP, y definen lautilización de IPs fijas en el programa, y la interfaz Ethernet por defecto8.La ventaja de trabajar de esta forma es que podemos compilar para varios equipos

simplemente cambiando la dirección al momento de compilar, lo cual puede llegar aser una buena solución en una producción pequeña o si los equipos se utilizan endiferentes sitios, con redes homogéneas no interconectadas entre sí. La ventajafundamental es que al ser la dirección IP una constante, podemos referirnos a ellacomo tal en textos y redirecciones en CGIs, como regularmente se muestra en losejemplos:

#define REDIRECTTO "http://" MY_IP_ADDRESS "/index.html"

7 El esquema BASE64 se define en las RFC (como todo lo que tiene que ver con los protocolosrelacionados a la Internet), particularmente las que definen MIME (Multipurpose Internet MailExtensions)

8 situación que ocurría automáticamente en versiones anteriores a DC9.

292

Page 311: El Camino Del Conejo

Direcciones IP fijas

Usando TCPCONFIG

Podemos modificar TCP_CONFIG.LIB para no tener que definir los parámetros dered en cada proyecto:

#define _PRIMARY_STATIC_IP "192.168.1.54"#define _PRIMARY_NETMASK "255.255.255.0"#ifndef MY_NAMESERVER

#define MY_NAMESERVER "200.42.0.108"#endif#ifndef MY_GATEWAY

#define MY_GATEWAY "192.168.1.1"

En el programa principal, simplemente especificamos la configuración de red de lasiguiente forma:

#define TCPCONFIG 1

Debe tenerse en cuenta que si bien la dirección IP sigue siendo fija, la macroMY_IP_ADDRESS no tiene el valor que definimos en TCPCONFIG, y por ende noes posible utilizar una macro para armar el URL de redirección. En este caso,debemos pedir la dirección IP mediante una llamada a función, como veremos alanalizar las direcciones IP dinámicas.

Direcciones IP dinámicas

Con el término "dinámicas" nos referimos a que no son fijas al momento decompilar nuestra aplicación, sino que pueden ser asignadas por un servidor DHCP, ocambiadas en cualquier momento por un usuario o administrador de la red.

DHCP

Si habitamos una red en la cual el administrador ha decidido que las direcciones IPson otorgadas por un servidor DHCP, deberemos especificar esta condiciónmediante:

#define TCPCONFIG 5

Al inicializar la red, la llamada a sock_init() vuelve inmediatamente después deinicializar el controlador de red, pero es altamente probable que aún no tengamosuna dirección IP9. En este caso, se debe monitorear el estado de la interfaz en esperade la resolución del proceso de handshake mediante llamadas a ifpending(), antes delevantar servidores o iniciar conexiones.

9 Antes de DC8.3, sock_init() no retornaba hasta obtener la dirección IP. Actualmente lo hace luego deincializar el controlador y requiere sucesivas llamadas al stack para obtener una dirección IP.

293

Page 312: El Camino Del Conejo

Networking con Rabbit

sock_init();while(ifpending(IF_ETH0)==IF_COMING_UP)

tcp_tick(NULL);

if(ifpending(IF_ETH0)==IF_DOWN){printf("No pude obtener una dirección");

Fallback

En caso que no podamos obtener dirección por DHCP, es conveniente proveer unaopción de fallback a una dirección estática conocida. De este modo, siempredisponemos de una dirección en donde encontrar el módulo. Esto lo indicamosmediante:

#define TCPCONFIG 7

DDNS

Recordemos que para interactuar con, por ejemplo, DynDNS o ZoneEdit,necesitamos un cliente HTTP. Si utilizamos un Rabbit 4000 o superior, DC10 nos loprovee.En el caso particular de DynDNS, existe una sample que muestra la obtención de la

dirección y el manejo en samples\tcpip\http\dyndns.c. Para ZoneEdit, dado que laoperatoria es más simple, se puede resolver el problema simplificando la sample deDynDNS.

Cambio de dirección IP

Cuando la dirección IP debe ser cambiada en el campo, como es el caso de lamayoría de las aplicaciones, deberemos inicializar manualmente la interfaz encuestión, pasándole los parámetros básicos. La función encargada de realizarlo esifconfig():

ifconfig(IF_ETH0,IFS_DOWN,IFS_IPADDR, ipaddress,IFS_NETMASK, src_mask,IFS_ROUTER_SET, def_gwy,IFS_NAMESERVER_SET, def_dns,IFS_UP,IFS_END);

La llamada a ifconfig() se realiza después de llamar a sock_init(), y el progreso deinicialización de la interfaz se realiza mediante sucesivas llamadas al stack TCP/IP,por lo que es necesario monitorear el estado de la interfaz antes de levantarservidores o iniciar conexiones, mediante llamadas a ifpending():

while(ifpending(IF_ETH0)==IF_COMING_UP)tcp_tick(NULL);

294

Page 313: El Camino Del Conejo

Direcciones IP dinámicas

Los parámetros de red pasados a la función ifconfig() son, como estudiáramos alcomienzo de este capítulo, valores en formato "de máquina", es decir, para serentendidos y manejados por el stack TCP/IP. La traducción desde el formato quetodos conocemos la realizamos mediante la función inet_addr(). En el caso inverso,para visualizar uno de los valores configurados, emplearemos la función inet_ntoa().

longword ipaddress,src_mask,def_gwy_def_dns;char my_ip[16],my_mask[16],my_gwy[16],my_dns[16];

ipaddress=inet_addr("192.168.1.2");src_mask=inet_addr("255.255.255.0");def_gwy=inet_addr("192.168.1.1");def_dns=inet_addr("192.168.1.1");

inet_ntoa(my_ip,ipaddress);inet_ntoa(my_mask,src_mask);inet_ntoa(my_gwy,def_gwy);inet_ntoa(my_dns,def_dns);

Recordemos que el tipo longword está definido en dcrtcp.lib.

Cambio a DHCP

Si el cambio en campo es a una opción de DHCP, la operatoria es muy similar:

ifconfig(IF_ETH0,IFS_DOWN,IFS_DHCP, 1,IFS_UP,IFS_END);

con fallback

Si incluimos una opción de fallback, la incluimos en la llamada a función:

ifconfig(IF_ETH0,IFS_DOWN,IFS_DHCP, 1,IFS_DHCP_FB_IPADDR, ipaddress,IFS_NETMASK, src_mask,IFS_UP,IFS_END);

Sockets abiertos

Como estudiáramos en el capítulo sobre networking, si alguno de los parámetroscambia, el socket ya no es válido. Por este motivo, cualquier conexión abierta almomento de cambiar la dirección IP deberá ser cerrada y vuelta a abrir (si esnecesario).

295

Page 314: El Camino Del Conejo

Networking con Rabbit

PPP

Para el análisis de PPP, podemos aplicar lo visto al analizar DHCP y cambio dedirección IP, dado que la dirección es asignada por el servidor PPP, y puede cambiardurante el funcionamiento, dado que la interfaz puede bajarse y subirse con otradirección diferente. El manejo en general es similar a lo visto en el apartado anterior,iniciándose la conexión mediante ifconfig().Para referirnos a una interfaz en particular, lo hacemos en el primer parámetro de

llamada a ifconfig():

ifconfig(IF_PPP1,...

También, si utilizamos solamente una, podemos definirla como

#define USE_PPP_SERIAL 0x02

y luego llamar a ifconfig() como

ifconfig(IF_DEFAULT, ...

La selección de interfaz es simple, IF_PPP0 corresponde al port serie A eIF_PPP5 corresponde al port F de un Rabbit 3000. De igual modo,USE_PPP_SERIAL 0x01 (bit 0) corresponde al port serie A y USE_PPP_SERIAL0x20 (bit 5) corresponde al port F de un Rabbit 3000. En los casos en quenecesitemos utilizar el pinout alternativo (port D), lo indicamos mediante elparámetro IFS_PPP_USEPORTD en la llamada a ifconfig().

Por lo general, el dispositivo que se conecta es un modem, al cual se controlamediante comandos AT para que disque, y luego puede existir un proceso de loginen un servidor. Todo esto se maneja de forma automática por una biblioteca defunciones: chat.lib, que es automáticamente incluida al usar PPP. Desde nuestropunto de vista, deberemos definir todo el proceso de chat con el modem y/o servidoren un string, el cual pasaremos (su puntero) a ifconfig(), mediante el parámetroIFS_PPP_SENDEXPECT. El string referenciado tiene la forma "enviar recibir", ydebe seguir una serie de reglas que permiten desarrollar un simple lenguaje scriptpara automatizar la conexión. Por ejemplo, el siguiente string:

"ATH0 @ ATD1234567 #CONNECT"

significa que se envía ATH0 al modem, se espera un tiempo, luego se envíaATD1234567 y se espera recibir el string CONNECT, sin prestar atención amayúsculas o minúsculas, antes de un determinado tiempo.De igual modo, para hacer que el modem desconecte, podemos pasarle el comando

e indicar que debemos utilizar la secuencia de escape (+ + +) para retomar el controldel modem. Para esto utilizamos los parámetros IFS_PPP_HANGUP eIFS_PPP_MODEMESCAPE, respectivamente.

296

Page 315: El Camino Del Conejo

Direcciones IP dinámicas

Debido a que el módulo PPP no se incluye con la distribución standard de DynamicC sino que debe comprarse por separado, los ejemplos siguientes requieren de lainstalación del mismo para poder compilarse y ejecutarse.

El siguiente listado es un ejemplo de establecimiento de una conexión PPP, luegoenviamos un ping y desconectamos:

#define TCPCONFIG 0

//Usa port B (IF_PPP1)#define USE_PPP_SERIAL 0x02

#define DIALUP_NAME "myname"#define DIALUP_PASSWORD "mypassword"#define DIALUP_SENDEXPECT "ATH0 @ ATDT1234567 #CONNECT"

#memmap xmem#use "dcrtcp.lib"

int main(){auto unsigned long t,ping_address,seq;auto char buffer[20];

sock_init();

ifconfig(IF_DEFAULT,IFS_PPP_SPEED, 115200L,IFS_PPP_RTSPIN, PCDR, NULL, 2,IFS_PPP_CTSPIN, PCDR, 3,IFS_PPP_FLOWCONTROL, 1,IFS_PPP_SENDEXPECT, DIALUP_SENDEXPECT,IFS_PPP_HANGUP, "ATH0 #OK",IFS_PPP_MODEMESCAPE, 1,IFS_PPP_ACCEPTIP, 1,IFS_PPP_ACCEPTDNS, 1,IFS_PPP_REMOTEAUTH, DIALUP_NAME, DIALUP_PASSWORD,IFS_UP,IFS_END);

while(ifpending(IF_DEFAULT) == IF_COMING_UP)tcp_tick(NULL);

if(!ifstatus(IF_DEFAULT)) {printf("No pude inicializar\n");

}else {

printf("PPP OK !\n");ifconfig(IF_DEFAULT, IFG_IPADDR, &t, IFS_END);printf("Mi IP es: %s\n", inet_ntoa( buffer, t));

t=MS_TIMER+5000;if(!_ping(ping_address=resolve("www.yahoo.com"),1)){

while(t>MS_TIMER) {tcp_tick(NULL);if(_chk_ping(ping_address,&seq)!=0xffffffff){

printf("Ping OK: %ld\n", seq); break; }

297

Page 316: El Camino Del Conejo

Networking con Rabbit

}}

ifconfig(IF_DEFAULT, IFS_DOWN, IFS_END);printf("Desconectando...");

while(ifpending(IF_DEFAULT)== IF_COMING_DOWN)tcp_tick(NULL);

printf("listo\n");}

}

Si bien en este ejemplo utilizamos loops infinitos, en aplicaciones reales esconveniente emplear timeouts, particularmente en la desconexión, dado que no todoslos proveedores señalizan correctamente. Particularmente, en algunos casos laasignación de IP falla si el Rabbit comete el error de comentar que antes tenía una IPasignada, por lo que es recomendable "limpiar" la dirección IP forzándola a 0.0.0.0antes de levantar la interfaz, mediante una llamada a if_config():

ifconfig(IF, IFS_IPADDR, 0L, IFS_END); // usar la IF que corresponda

Usando TCPCONFIG

Es posible cargar los parámetros de ifconfig() en tcpconfig.lib, de modo de definirla interfaz mediante una simple selección como:

#define TCPCONFIG 8

la cual realiza lo siguiente (extraído de tcpconfig.lib):

#if TCPCONFIG == 8 #define USE_PPP_SERIAL 0x02 #define IFCONFIG_PPP1 IFS_PPP_SPEED, 115200L, \

IFS_PPP_RTSPIN, PBDR, NULL, 6, \IFS_PPP_CTSPIN, PBDR, 0, \IFS_PPP_FLOWCONTROL, 1, \IFS_PPP_REMOTEAUTH, "isp_logonid", "mY_PAsSwOrD", \IFS_PPP_SENDEXPECT, "ATZ0E0 OK ATDT5555555 #ogin: %0 #word: %1 ~", \IFS_PPP_MODEMESCAPE, 1, \IFS_PPP_HANGUP, "ATH0 #ok", \IFS_PPP_PASSIVE, 0, \IFS_PPP_USEMODEM, 1, \IFS_PPP_REMOTEIP, 0x0A0A0A01, \IFS_UP

#endif

que una vez modificado con los parámetros habituales, puede ser muy útil.

PPP sobre CSD

PPP sobre CSD significa utilizar PPP sobre una conexión CSD, es decir, sobre eltransporte de datos por conmutación de circuitos de GSM, como viéramos en elcapítulo sobre networking. Su configuración no difiere de PPP, más allá del númeroa discar, si se utiliza uno de los abreviados como por ejemplo *638.

298

Page 317: El Camino Del Conejo

Direcciones IP dinámicas

#define DIALUP_SENDEXPECT "ATH0 @ ATD*638 #CONNECT"

Si el prestatario permite conexión con la red pública telefónica (PSTN), podemosutilizar cualquier ISP como si estuviéramos en la PSTN.

#define DIALUP_SENDEXPECT "ATH0 @ ATD1234567 #CONNECT"

PPP sobre GPRS

PPP sobre GPRS significa utilizar PPP sobre el transporte de datos porconmutación de paquetes de GSM, como viéramos en el capítulo sobre networking.Técnicamente, desde el punto de vista del Rabbit, PPP sobre GPRS no difiere enabsoluto de PPP. La única diferencia la tendremos en el script de discado, quedeberá incluir el seteo para que el modem GSM pueda transportar PPP sobre GPRS.Dicho seteo podrá diferir de acuerdo al modem GSM utilizado.

Módulos SIMCOM

Resumimos los pasos esenciales para poder conectarnos a la Internet utilizandomódulos SIMCOM como modem GSM, mediante PPP sobre GPRS.

1. Selección del contexto: Se realiza mediante el comando AT+CGDCONT, segúncuál sea nuestro proveedor, deberemos ingresar el que éste defina:AT+CGDCONT=1,"IP","<contexto definido por el proveedor>"

2. Activación del contexto: mediante el comando AT+CGATT=13. Establecimiento de la conexión PPP: Deberemos iniciar una conexión mediante

un número ficticio, el cual es definido por el proveedor del servicio, ygeneralmente es del tipo *99**#. La autenticación generalmente corresponde aPAP, y tanto usuario como password son definidos por el proveedor del servicio.

El ejemplo a continuación corresponde a una prestataria argentina:

#define DIALUP_NAME "wap"#define DIALUP_PASSWORD "wap"#define DIALUP_SENDEXPECT "ATH0 #OK AT+CGDCONT=1,\"IP\", \\"internet.gprs.unifon.com.ar\" #OK AT+CGATT=1 #OK ATD*99**# #CONNECT"

PPP sobre GPRS vía Bluetooth (DUN) (tethering)

En este caso, la conexión con el módem GPRS la hacemos vía Bluetooth. El perfilutilizado es DUN10 (Dial-Up Networking), y nos permite conectar un Rabbit a unteléfono celular GSM con Bluetooth, empleando un módulo Bluetooth como porejemplo los de KCWirefree.

10 El capítulo sobre conectividad incorpora más información sobre Bluetooth, describiendo éste y otrosprofiles.

299

Page 318: El Camino Del Conejo

Networking con Rabbit

Una vez más, desde el punto de vista del Rabbit, PPP sobre Bluetooth no difiere enabsoluto de PPP. La única diferencia la tendremos en el script de discado, quedeberá incluir los seteos para que el módulo Bluetooth pueda conectarse al teléfonocelular y éste transportar PPP sobre GPRS. Dichos seteos podrán diferir de acuerdoal módulo Bluetooth y el teléfono celular utilizados.

KCWirefree

En el caso en particular que veremos a continuación establecemos unacomunicación con un módulo KCWirefree y un teléfono celular. El teléfono ya tieneingresado el contexto correspondiente, ya sea mediante el comando AT+CGDCONT(que viéramos en el apartado anterior), o manualmente por pantalla, en su memoria. El procedimiento de conexión es:1. Definir parámetros de conexión (bonding): dirección remota y clave: AT+ZVEnableBond <dirección> <clave>

2. Establecer la conexión DUN con el celular: AT+ZV DUNConnect <dirección>3. Aceptar la conexión DUN en el celular, ingresando la clave4. Discar el número ficticio, que activa PPP: ATD*99***<posición>#5. Establecer la conexión PPP

El procedimiento de desconexión es:1. Terminar la conexión PPP2. Interrumpir la conexión del modem GPRS, lo cual se realiza como cualquier

modem convencional, con la secuencia de escape (+++) y la orden de cortar:ATH0

3. Interrumpir la conexión DUN, con la secuencia de escape especial (^#^$^% y dossegundos sin actividad) y la orden de cortar: AT+ZV DUNDisconnect

300

red GSM Internet

Módulo Bluetooth

Serie Asinc Bluetooth GPRS

PPP

IP

Gateway

Page 319: El Camino Del Conejo

Direcciones IP dinámicas

El ejemplo a continuación corresponde a un teléfono T637 (dirección Bluetooth000E07191D58), con una prestataria argentina, cargada en la posición número 4:

#define TCPCONFIG 0

//Usa port E (IF_PPP4)#define USE_PPP_SERIAL 0x10

#define DIALUP_NAME "wap"#define DIALUP_PASSWORD "wap"#define DIALUP_SENDEXPECT "'AT+ZV EnableBond 000e07191d58 1234' \#OK 'AT+ZV DUNConnect 000e07191d58' #-Bypass ATD*99***4# #CONNECT"

#memmap xmem#use "dcrtcp.lib"

int main(){auto unsigned long t,ping_address,seq;auto char buffer[20];

sock_init();

ifconfig(IF_DEFAULT, IFS_PPP_SPEED, 115200L, IFS_PPP_FLOWCONTROL, 0, IFS_PPP_SENDEXPECT, DIALUP_SENDEXPECT, IFS_PPP_HANGUP, "ATH0 #OK #^#^$^% @-Command 'AT+ZV DUNDisconnect'", IFS_PPP_MODEMESCAPE, 1, IFS_PPP_ACCEPTIP, 1, IFS_PPP_ACCEPTDNS, 1, IFS_PPP_REMOTEAUTH, DIALUP_NAME, DIALUP_PASSWORD, IFS_UP, IFS_END);

while(ifpending(IF_DEFAULT) == IF_COMING_UP)tcp_tick(NULL);

if(!ifstatus(IF_DEFAULT)) {printf("No pude inicializar\n");

}else { printf("PPP OK !\n"); ifconfig(IF_DEFAULT, IFG_IPADDR, &t, IFS_END); printf("Mi IP es: %s\n", inet_ntoa( buffer, t));

t=MS_TIMER+5000; if(!_ping(ping_address=resolve("www.yahoo.com"),1)){

while(t>MS_TIMER) {tcp_tick(NULL);if(_chk_ping(ping_address,&seq)!=0xffffffff){

printf("Ping OK: %ld\n", seq); break; } } }

ifconfig(IF_DEFAULT, IFS_DOWN, IFS_END); printf("Desconectando...");

while(ifpending(IF_DEFAULT)== IF_COMING_DOWN)tcp_tick(NULL);

301

Page 320: El Camino Del Conejo

Networking con Rabbit

printf("listo\n");}

}

PPPoE

La selección de una interfaz PPPoE se realiza especificando

#define USE_PPPoE 1#define USE_ETHERNET 1

y luego llamando a ifconfig() como

ifconfig(IF_DEFAULT, ...

También es posible referirse a la interfaz en particular en la llamada a ifconfig().

ifconfig(IF_PPPOE0,...

El siguiente listado es un ejemplo de establecimiento de una conexión PPPoE,luego enviamos un ping y desconectamos. Si bien en este ejemplo utilizamos loopsinfinitos, en aplicaciones reales es conveniente emplear timeouts, particularmente enla desconexión, dado que no todos los proveedores señalizan correctamente.

#define TCPCONFIG 0

#define USE_PPPOE 1#define USE_ETHERNET 1

#define PPPoE_NAME "myname"#define PPPoE_PASSWORD "mypassword"

#memmap xmem#use "dcrtcp.lib"

int main(){auto unsigned long t,ping_address,seq;auto char buffer[20];

sock_init();

ifconfig(IF_DEFAULT,IFS_PPP_ACCEPTIP, 1,IFS_PPP_ACCEPTDNS, 1,IFS_PPP_REMOTEAUTH, PPPoE_NAME, PPPoE_PASSWORD,IFS_UP,IFS_END);

while(ifpending(IF_DEFAULT) == IF_COMING_UP)tcp_tick(NULL);

if(!ifstatus(IF_DEFAULT)) {printf("No pude inicializar\n");

}else {

printf("PPP OK !\n");ifconfig(IF_DEFAULT, IFG_IPADDR, &t, IFS_END);

302

Page 321: El Camino Del Conejo

Direcciones IP dinámicas

printf("Mi IP es: %s\n", inet_ntoa( buffer, t));

t=MS_TIMER+5000;if(!_ping(ping_address=resolve("www.yahoo.com"),1)){

while(t>MS_TIMER) {tcp_tick(NULL);if(_chk_ping(ping_address,&seq)!=0xffffffff){

printf("Ping OK: %ld\n", seq);break;

}}

}

ifconfig(IF_DEFAULT, IFS_DOWN, IFS_END);printf("Desconectando...");

while(ifpending(IF_DEFAULT)== IF_COMING_DOWN)tcp_tick(NULL);

printf("listo\n");}

}

Redirecciones

Debido a que no conocemos la dirección IP al momento de compilar el programa,deberemos obtener este valor mediante una llamada a ifconfig(), convertirlo a untexto utilizando inet_ntoa(), y luego armar el URL de redirección mediante, porejemplo, una llamada a sprintf().

unsigned long t;char my_ip[16],REDIRECTTO[50];

ifconfig(IF_DEFAULT, IFG_IPADDR, &t, IFS_END);inet_ntoa( my_ip, t);sprintf(REDIRECTTO,"http://%s/index.html",my_ip);

Cambio de parámetros de red en funcionamiento

Cuando debamos cambiar los parámetros de red durante el funcionamiento delequipo, deberemos tener en cuenta una serie de aspectos. Primero, si es necesarioque quien realiza el cambio reciba algún tipo de respuesta, deberemos elegir:� entregar la respuesta mediante CGI y luego, una vez terminada, realizar el

cambio solicitado.� usar una redirección, y esperar un cierto tiempo antes de realizar el cambio, para

que la petición de la página y su respuesta puedan realizarse.Al realizar el cambio, deberán ser cerrados todos los sockets, volviendo a abrirse

los necesarios. La operación sobre los parámetros de red en tiempo de ejecución laharemos mediante la función ifconfig(), que ya hemos visto.

Ejemplo: cambio desde una página web

Podemos realizar un pequeño programa ejemplo, que provee la lectura de losparámetros de red desde la flash, y una simple interfaz para poder cambiarlos desde

303

Page 322: El Camino Del Conejo

Networking con Rabbit

una página web, volviendo a guardarlos en flash. Como la primera vez que iniciamosel programa no tenemos nada válido en flash, y tal vez necesitemos en algún otromomento (devolución de un equipo desde campo) resetear los parámetros a un valorseguro y conocido, proveemos un escape a dicha función (factory defaults) mediantela lectura de un pulsador.

El desarrollo en general es muy similar a lo analizado en el capítulo sobreconfiguración en campo; en este caso nos concentraremos más en lo relacionado anetworking.

Configuración

Definimos los parámetros por defecto, declaramos las variables necesarias,agrupamos los parámetros dentro de una estructura, por conveniencia para salvarlosen flash:

#define TCPCONFIG 0#define USE_ETHERNET 1

#define DEFAULT_SOURCEIP "192.168.1.55"#define DEFAULT_NETMASK "255.255.255.0"#define DEFAULT_GATEWAY "192.168.1.1"#define DEFAULT_DNS "192.168.1.1"

#use "dcrtcp.lib"#use "http.lib"

typedef struct { longword src_ip; longword src_mask; longword def_gwy; longword my_dns;} Config;

Config myconfig;char my_ip[16],my_mask[16],my_gwy[16],my_dns[16];char REDIRECTTOK[50],REDIRECTTOERR[50];

#define CONFIG_OFFSET (4096*GetIDBlockSize()-0x800)

Aplicación de los cambios

Lo primero que debemos determinar es si nuestra aplicación soporta o no un reset.Si es así, lo cual no es muy probable, podemos simplemente reiniciar el equipomediante una llamada a exit().

Si el producto final está destinado a un usuario final, muy probablemente nopodremos simplemente resetear el equipo. En este caso, deberemos señalizar elcambio de configuración para que el programa principal cierre todas sus conexionesy proceda a reinicializar la interfaz, lo cual se realizará mediante una tarea, queveremos más adelante, la cual también realizará la grabación en flash.

304

Page 323: El Camino Del Conejo

Direcciones IP dinámicas

Programa principal: inicialización de la interfaz

El programa principal es el encargado de la inicialización de la interfaz:

main(){

if(!BitRdPortI(PBDR,2)) { // factory defaultsmyconfig.src_ip=inet_addr(DEFAULT_SOURCEIP);myconfig.src_mask=inet_addr(DEFAULT_NETMASK);myconfig.def_gwy=inet_addr(DEFAULT_GATEWAY);myconfig.my_dns=inet_addr(DEFAULT_DNS);writeUserBlock(CONFIG_OFFSET,&myconfig,sizeof(Config));

}else { // carga de flash

readUserBlock(&myconfig,CONFIG_OFFSET,sizeof(Config));}

inet_ntoa(my_ip,myconfig.src_ip);inet_ntoa(my_mask,myconfig.src_mask);inet_ntoa(my_gwy,myconfig.def_gwy);inet_ntoa(my_dns,myconfig.my_dns);sprintf(REDIRECTTOK,"http://%s/ok.html",my_ip);sprintf(REDIRECTTOERR,"http://%s/error.html",my_ip);sock_init();ifconfig(IF_ETH0,

IFS_DOWN,IFS_IPADDR, myconfig.src_ip,IFS_NETMASK, myconfig.src_mask,IFS_ROUTER_SET, myconfig.def_gwy,IFS_NAMESERVER_SET, myconfig.my_dns,IFS_UP,IFS_END);

a partir de aquí, deberemos esperar a que la interfaz esté activa antes de iniciaralgunos servicios. En este caso, simplemente esperamos si el cable está conectado,por conveniencia.

if(pd_havelink(IF_ETH0)) while (ifpending(IF_ETH0) == IF_COMING_UP)

tcp_tick(NULL);

luego iniciamos normalmente

http_init();tcp_reserveport(80);for(;;)

http_handler();}

Para el caso en que debamos reiniciar la interfaz sin interrumpir el normalfuncionamiento del sistema (los procesos de networking se verán afectados de todosmodos), el loop principal será el siguiente:

for(;;){http_handler();

costate restartComms { waitfor(DelaySec(5));

sprintf(REDIRECTTOK,"http://%s/ok.html",my_ip);

305

Page 324: El Camino Del Conejo

Networking con Rabbit

sprintf(REDIRECTTOERR,"http://%s/error.html",my_ip);ifconfig(IF_ETH0,

IFS_DOWN, IFS_IPADDR, myconfig.src_ip,

IFS_NETMASK, myconfig.src_mask,IFS_ROUTER_SET, myconfig.def_gwy,IFS_NAMESERVER_SET, myconfig.my_dns,IFS_UP,IFS_END);

writeUserBlock(CONFIG_OFFSET,&myconfig,sizeof(Config));}

}

Nótese que el costate restartComms será inicializado por la función encargada deguardar la nueva configuración, corriendo solamente para realizar su función y luegodetenerse nuevamente. El waitfor nos provee de un tiempo razonable como para queel servidor pueda entregar la respuesta.Finalmente, recordemos que la capacidad de conexión debe definirse de forma tal

de poder tener suficientes sockets para la tarea a realizar y para el web serveradicional que realiza la configuración (si ya no disponíamos de un web server ennuestra aplicación, claro). Una vez más, un mayor número de sockets implica unamayor ocupación y necesidad de memoria. Como viéramos con anterioridad, lamacro correspondiente se define antes de incluir la biblioteca de funciones deTCP/IP. Por ejemplo, si necesitamos seis sockets:

#define MAX_TCP_SOCKET_BUFFERS 6#use "dcrtcp.lib"

Seguridad

En gran cantidad de aplicaciones, tal vez no sea conveniente que cualquiera puedacambiar la dirección IP del equipo. En estos casos, lo conveniente es proteger elacceso a dichas páginas mediante autenticación. Como viéramos algunos apartadosatrás, esto no es muy complicado. Primero definimos el grupo de usuariosautorizados a alterar la configuración, y luego en el directorio del server,marcaremos aquellos archivos que queremos proteger. Finalmente, incorporamos alprograma principal las funciones necesarias para agregar un nuevo usuario, llamado(vaya sorpresa) admin, cuyo password será istreitor:

uid=sauth_adduser("admin", "istreitor", SERVER_HTTP); // creasauth_setusermask(uid, ADMIN_GROUP, NULL); // agrega al grupo

En este ejemplo asumimos que el usuario se crea sin problemas, dado que es simpley sencillo. Si esto es parte de un programa más complejo, y se agregan usuarios deforma dinámica, tal vez sea conveniente chequear el valor devuelto por la funciónque crea al usuario: sauth_adduser(), antes de proseguir con la tarea de agregarlo algrupo.

306

Page 325: El Camino Del Conejo

Direcciones IP dinámicas

A continuación, daremos un ejemplo de una interfaz para cambio de parámetros dered vía web con RabbitWeb. Definimos dos grupos de usuarios: uno con permiso delectura, el otro con permiso de escritura. Quienes no pertenezcan a ninguno de losgrupos no pueden observar la configuración. La interfaz presenta además proteccióncontra acceso simultáneo y detección de errores. Para más detalles se recomiendaconsultar los ejemplos en el capítulo sobre configuración en campo.

#define USE_RABBITWEB 1#define TCPCONFIG 0#define USE_ETHERNET 1

#define DEFAULT_SOURCEIP "192.168.1.55"#define DEFAULT_NETMASK "255.255.255.0"#define DEFAULT_GATEWAY "192.168.1.1"#define DEFAULT_DNS "192.168.1.1"

#use "dcrtcp.lib"#use "http.lib"

typedef struct { longword src_ip; longword src_mask; longword def_gwy; longword my_dns;} Config;

Config myconfig;CoData restartComms;

#web_groups ADMIN_GROUP#web_groups MON_GROUP

char my_ip[16],my_mask[16],my_gwy[16],my_dns[16];#web my_ip (isaddr($my_ip)) auth=basic,digest \groups=ADMIN_GROUP(rw),MON_GROUP(ro)#web my_mask (isaddr($my_mask)) auth=basic,digest \groups=ADMIN_GROUP(rw),MON_GROUP(ro)#web my_gwy (isaddr($my_gwy)) auth=basic,digest \groups=ADMIN_GROUP(rw),MON_GROUP(ro)#web my_dns (isaddr($my_dns)) auth=basic,digest \groups=ADMIN_GROUP(rw),MON_GROUP(ro)

#define CONFIG_OFFSET (4096*GetIDBlockSize()-0x800)

int tag;#web tag ($tag == tag)

void saveconfig(void){

myconfig.src_ip=inet_addr(my_ip);myconfig.src_mask=inet_addr(my_mask);myconfig.def_gwy=inet_addr(my_gwy);myconfig.my_dns=inet_addr(my_dns);CoBegin(&restartComms);

}

#web_update my_ip,my_mask,my_gwy,my_dns saveconfig

#ximport "index.html" index_html#ximport "rabbit1.gif" rabbit1_gif

307

Page 326: El Camino Del Conejo

Networking con Rabbit

#ximport "setup.zhtml" setup_html

SSPEC_MIMETABLE_STARTSSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),SSPEC_MIME(".html", "text/html"),SSPEC_MIME(".gif", "image/gif"),

SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_STARTSSPEC_RESOURCE_XMEMFILE("/", index_html),SSPEC_RESOURCE_XMEMFILE("/index.shtml", index_html),SSPEC_RESOURCE_P_XMEMFILE("/setup.zhtml",setup_html,"super equipo",

ADMIN_GROUP|MON_GROUP,0,SERVER_HTTP,SERVER_AUTH_BASIC | SERVER_AUTH_DIGEST),SSPEC_RESOURCE_XMEMFILE("/rabbit1.gif", rabbit1_gif),

SSPEC_RESOURCETABLE_END

main(){int uid;

if(!BitRdPortI(PBDR,2)) {myconfig.src_ip=inet_addr(DEFAULT_SOURCEIP);myconfig.src_mask=inet_addr(DEFAULT_NETMASK);myconfig.def_gwy=inet_addr(DEFAULT_GATEWAY);myconfig.my_dns=inet_addr(DEFAULT_DNS);writeUserBlock(CONFIG_OFFSET,&myconfig,sizeof(Config));

}else {

readUserBlock(&myconfig,CONFIG_OFFSET,sizeof(Config));}

inet_ntoa(my_ip,myconfig.src_ip);inet_ntoa(my_mask,myconfig.src_mask);inet_ntoa(my_gwy,myconfig.def_gwy);inet_ntoa(my_dns,myconfig.my_dns);if((uid = sauth_adduser("admin", "istrador", SERVER_HTTP))>=0){

sauth_setusermask(uid, ADMIN_GROUP, NULL);}if((uid = sauth_adduser("mon", "itor", SERVER_HTTP))>=0){

sauth_setusermask(uid, MON_GROUP, NULL);}sock_init();ifconfig(IF_ETH0,

IFS_DOWN,IFS_IPADDR, myconfig.src_ip,IFS_NETMASK, myconfig.src_mask,IFS_ROUTER_SET, myconfig.def_gwy,IFS_NAMESERVER_SET, myconfig.my_dns,IFS_UP,IFS_END);

if(pd_havelink(IF_ETH0)) while (ifpending(IF_ETH0) == IF_COMING_UP)

tcp_tick(NULL);

http_init();tcp_reserveport(80);for(;;){ http_handler(); costate restartComms {

waitfor(DelaySec(5));ifconfig(IF_ETH0,

IFS_DOWN,

308

Page 327: El Camino Del Conejo

Direcciones IP dinámicas

IFS_IPADDR, myconfig.src_ip,IFS_NETMASK, myconfig.src_mask,IFS_ROUTER_SET, myconfig.def_gwy,IFS_NAMESERVER_SET, myconfig.my_dns,IFS_UP,IFS_END);

writeUserBlock(CONFIG_OFFSET,&myconfig,sizeof(Config)); }}

}

El ZHTML:

<html><head><title>Red</title></head><body bgcolor="#FFFFFF" link="#009966" vlink="#FFCC00" alink="#006666"topmargin="0" leftmargin="0" marginwidth="0" marginheight="0">Configuraci&oacute;n de par&aacute;metros de red<form ACTION="setup.zhtml" METHOD="POST"><?z if(updating()) { ?> <?z if(!error()) { ?>

<hr><H2><FONT COLOR="#0000FF">Configuración actualizada</FONT></H2><hr> <?z } ?> <?z if(error()) { ?>

<?z if(error($tag)) { ?><hr><H4><FONT COLOR="#FF0000">Debido a cambios realizados por

otro usuario, revise sus acciones</FONT></H4><hr><?z } ?><?z if(!error($tag)) { ?>

<hr><H3><FONT COLOR="#FF0000">Revise los errores indicados enrojo</FONT></H3><hr>

<?z } ?> <?z } ?><?z } ?><table><tr><td>

<?z if(error($my_ip)) { ?><FONT COLOR="#FF0000">

<?z } ?>Direcci&oacute;n IP

<?z if(error($my_ip)) { ?></FONT>

<?z } ?><td><input TYPE="TEXT" NAME="my_ip" SIZE=15 VALUE=

<?z if(error($tag)) { ?><?z print(@my_ip) ?>

<?z } ?><?z if(!error($tag)) { ?>

<?z print($my_ip) ?><?z } ?>

><tr><td>

<?z if(error($my_mask)) { ?><FONT COLOR="#FF0000">

<?z } ?>M&aacute;scara

<?z if(error($my_mask)) { ?></FONT>

<?z } ?><td><input TYPE="TEXT" NAME="my_mask" SIZE=15 VALUE=

<?z if(error($tag)) { ?><?z print(@my_mask) ?>

<?z } ?><?z if(!error($tag)) { ?>

309

Page 328: El Camino Del Conejo

Networking con Rabbit

<?z print($my_mask) ?><?z } ?>

><tr><td>

<?z if(error($my_gwy)) { ?><FONT COLOR="#FF0000">

<?z } ?>Gateway

<?z if(error($my_gwy)) { ?></FONT>

<?z } ?><td><input TYPE="TEXT" NAME="my_gwy" SIZE=15 VALUE=

<?z if(error($tag)) { ?><?z print(@my_gwy) ?>

<?z } ?><?z if(!error($tag)) { ?>

<?z print($my_gwy) ?><?z } ?>

><tr><td>

<?z if(error($my_dns)) { ?><FONT COLOR="#FF0000">

<?z } ?>DNS

<?z if(error($my_dns)) { ?></FONT>

<?z } ?><td><input TYPE="TEXT" NAME="my_dns" SIZE=15 VALUE=

<?z if(error($tag)) { ?><?z print(@my_dns) ?>

<?z } ?><?z if(!error($tag)) { ?>

<?z print($my_dns) ?><?z } ?>

></table><?z if(auth($my_ip,"rw")) { ?>

<INPUT TYPE="hidden" NAME="tag" VALUE="<?z print($tag) ?>"><input TYPE="SUBMIT" VALUE="Configurar"></form>

<?z } ?></body></html>

Múltiples interfaces

El sistema soporta más de una interfaz, por lo que es perfectamente factibleinicializarlas de a una por vez pasando el parámetro correcto en cada llamada aifconfig(), es decir, no es necesario desconectar la interfaz Ethernet para poderutilizar PPP.Como regla general, cada interfaz debería corresponder a una dirección de reddiferente. El routing cuando hay varias interfaces se hace complejo, y es posible quelas respuestas tomen distinto camino que las preguntas, en cuyo caso tal vez debaponerse alguna ruta estática para forzar un camino o garantizar la comunicación. Unadecuado planeamiento de la red, teniendo en cuenta lo estudiado en el capítulosobre networking permitirá anticipar cualquier posible inconveniente.

310

Page 329: El Camino Del Conejo

Múltiples interfaces

Los servidores pueden tener una macro en la que especifican si atienden pedidos deuna interfaz en particular o en todas. Por ejemplo, FTP atiende sólo en la interfaz pordefecto mientras que HTTP lo hace en cualquiera. Esto se modifica buscando yalterando la macro correspondiente en la biblioteca de funciones del servidor

#define HTTP_IFACE IF_ANY#define FTP_INTERFACE IF_DEFAULT

Resolución de problemas (troubleshooting)

Cuando tenemos problemas de conexión o funcionamiento, como comentáramos enel capítulo sobre networking, es muy difícil tratar de determinar una causa sin poderver lo que pasa en la red. Si bien en otros campos de trabajo muchas veces es posible"tocar de oído", cuando hay problemas de networking es necesario utilizar algunaherramienta de visualización o análisis.

Herramientas

Dynamic C incluye algo similar a un analizador de protocolos dentro de susbibliotecas de funciones, y algunas utilidades en tiempo de ejecución para verificarconectividad, las cuales veremos a continuación.

Link

Para detectar el estado del link, es decir, la conexión o no del cable de red,podemos utilizar una función de Dynamic C, sin necesidad de interrogardirectamente al controlador Ethernet:

pd_havelink(interfaz); // Devuelve 1 si el link está bien// y 0 si no lo está

Por ejemplo, para detectar que no estamos conectados a una red Ethernet y evitardemorarnos en la inicialización de la interfaz, podemos hacer:

if(pd_havelink(IF_ETH0)) while (ifpending(IF_ETH0) == IF_COMING_UP)

tcp_tick(NULL);

Network

Modo debug

Al definir DCRTCP_VERBOSE obtenemos un reporte detallado en stdio de lo queestá ocurriendo. Por ejemplo, este es el resultado de ejecutar la sample ping.c:

IP: pkt_init reserved 10/30 buffers at BDFB/00031200 IP: i/f 0 using hwa :00:90:C2:C0:12:26

311

Page 330: El Camino Del Conejo

Networking con Rabbit

DHCP: Sending DISCOVER request on i/f 0 TCP: -2028764ms since last call to tcp_tick! DHCP: incoming on i/f 0 DHCP: offered C0A80164 ARP: who has C0A801A0? Tell C0A80101 i/f 0 DHCP: Sending REQUEST on i/f 0 DHCP: incoming on i/f 0 DHCP: Setting results for i/f 0... lease=604800 sec IP=C0A80164 mask=FFFFFF00 ARP: router_add C0A801FE - new entry ARP: created new entry 0 (for C0A801FE on i/f 0) ARP: loaded entry: 0 1 0 192.168.1.254 00:00:00:00:00:00 0 GW TCP: 109ms since last call to tcp_tick! My IP address is 192.168.1.100 ARP: ...router for i/f 255 is entry ATE=0: 0 D 192.168.1.254 0 0 0 ARP: who has C0A801A0? Tell C0A80101 i/f 0 ARP: arpresolve_start for IP C0A801FE i/f 255... ARP: ...in cache entry 0 ARP: who has C0A801FE? (on i/f 0) ARP: C0A801FE replying to C0A80164 i/f 0 ARP: reloading because his IP address in cache ARP: loaded entry: 0 1 0 192.168.1.254 00:0D:88:BE:F9:11 0 GW OK ICMP: sending echo request received ping: 0 ARP: who has C0A801A0? Tell C0A80101 i/f 0 ARP: arpresolve_start for IP C0A801FE i/f 255... ARP: ...in cache entry 0 ICMP: sending echo request received ping: 1 ARP: arpresolve_start for IP C0A801FE i/f 255... ARP: ...in cache entry 0 ICMP: sending echo request received ping: 2 ARP: arpresolve_start for IP C0A801FE i/f 255... ARP: ...in cache entry 0 ICMP: sending echo request received ping: 3

En la aplicación

Si necesitamos verificar que tenemos conectividad con quien deseamosconectarnos, podemos aprovechar las particularidades del stack. En todo stackTCP/IP, es posible determinar la conectividad a nivel-3 (networking) mediante elenvío de ICMP Echo Request, mejor conocido como ping, y luego detectar larecepción de ICMP Echo Reply. En Dynamic C, la función que envía un ping es justamente:

_ping(dirección,número de secuencia)

la dirección es en el formato de 32bits que utilizan todas las funciones, por lo quedebemos convertirla mediante las funciones ya conocidas, o utilizar resolve():

_ping(resolve("www.yahoo.com"),1);

312

Page 331: El Camino Del Conejo

Resolución de problemas (troubleshooting)

asumiendo que disponemos de un DNS configurado, o

_ping(resolve("192.168.1.1"),1);

Para detectar un ICMP Echo Reply (la respuesta a un ping): longword tmp_seq;

_chk_ping(direccion,&tmp_seq);

la función devuelve en tmp_seq el tiempo transcurrido, ó -1 (0xffffffff) si no serecibió una respuesta luego de un largo rato (timeout). Para más informaciónpodemos consultar como siempre en las samples, particularmente una de ellas:samples\tcpip\ping.c. De todos modos, hemos visto ejemplos de uso al analizar PPPy PPPoE, en este mismo capítulo.

Transporte y aplicación

Modo debug

Si deseamos tener un nivel de conocimiento más profundo de lo que está sucediendoen los servidores que tenemos en nuestro Rabbit, podemos definir HTTP_VERBOSE,SMTP_VERBOSE, etc. (antes de incluir la correspondiente biblioteca de funciones),y podremos observar detalles de la conexión en una ventana stdio en el entorno deDynamic C.

#define HTTP_VERBOSE

De igual modo, si deseamos ejecutar paso a paso dentro de las libraries, necesitamosque sean compiladas para debugging, lo cual generalmente no está habilitado dadoque consume mayor cantidad de recursos, podemos definir HTTP_DEBUG,SMTP_DEBUG, etc. (antes de incluir la correspondiente biblioteca de funciones).

#define HTTP_DEBUG

Por lo general, esto es válido para las demás aplicaciones como FTP, POP, etc.

En la aplicación

Un ping chequea solamente conectividad a nivel-3, no la voluntad del servidor deatender conexiones o datagramas a nivel-4, es decir, transporte (TCP, UDP).Si lo que necesitamos es detectar que la conexión (TCP) está establecida, podemos

hacerlo mediante la función tcp_tick(&socket), como analizáramos en el apartadocorrespondiente al principio de este capítulo. Es decir, la misma función quemantiene activo el stack TCP/IP: tcp_tick(), si se la llama con un puntero a un socketcomo parámetro, devuelve el estado de la conexión. Dado que, como dijéramos, ladetección de una pérdida de conexión puede llevar bastante tiempo, debido alesquema de retransmisiones y timeouts de TCP; o tal vez tenemos tiempos de

313

Page 332: El Camino Del Conejo

Networking con Rabbit

inactividad sin transferencia de información pero necesitamos conocer el estado dela conexión, podemos emplear keepalives, como describiéramos también en dichasección.Si en cambio estamos trabajando con UDP, debemos implementar algún esquema

de pregunta-respuesta que nos permita realizar la detección, como un anexo anuestra aplicación.

Esto mismo también es posible para el caso de una conexión TCP: unacomunicación UDP adicional puede darnos una idea de la conectividad y la voluntaddel servidor de atendernos, aunque lamentablemente no nos dice nada sobre elsocket TCP que nos interesa. Si bien ambos pueden estar muy relacionados, si es elproceso que atiende el socket TCP en el servidor el que se ha ido de vacaciones, laúnica forma de darnos cuenta es mediante información dentro de ese socket, lo cualpodemos implementar como parte de nuestra aplicación.

Wi-Fi

No sólo a partir del procesador R5000, que incorpora la electrónica necesaria, sinoya desde el R4000, algunos módulos Rabbit vienen provistos de hardware paraconectarse a una red Wi-Fi. Los módulos basados en R2000 y R3000 sólo poseenEthernet.

Módulos con capacidad Wi-Fi

La adquisición de Rabbit por parte de Digi International hace que esta últimaincorpore el hardware de Wi-Fi diseñado para sus módulos en los módulos Rabbit.La ventaja fundamental de este esquema es que la compañía es dueña del hardware yno depende de terceros para el abastecimiento, garantizando una cierta estabilidad enun entorno de cambios vertiginosos. El Rabbit 5000, además, incorpora soporte paraWi-Fi dentro mismo del chip, incluyendo WPA2 y autenticación de tipo Enterprise.La configuración de Wi-Fi en bajo nivel puede ser un poco complicada, por lo que

vamos a apoyarnos en la biblioteca de funciones tcp_config.lib para que atienda losvericuetos de la inicialización.

#define TCPCONFIG 1

#define _PRIMARY_STATIC_IP "192.168.1.54"#define _PRIMARY_NETMASK "255.255.255.0"

El resto de los parámetros (name server y gateway) los seguiremos manejandocomo hasta ahora.

Configuración

En el programa principal, simplemente especificamos la configuración de red de lasiguiente forma:

314

Page 333: El Camino Del Conejo

Wi-Fi

#define USE_WIFI 1 #define IFC_WIFI_SSID "Cika"

Conexión abierta

Si no tenemos restricciones en nuestro access point, cosa poco probable en una redoperativa pero deseable en un entorno de desarrollo, simplemente especificamos laconfiguración de la siguiente forma:

#define IFC_WIFI_ENCRYPTION IFPARAM_WIFI_ENCR_NONE

Podemos además especificar que no usamos autenticación, pero tcp_config.lib lotoma por defecto:

#define IFC_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_OPEN

En el programa principal, simplemente especificamos la configuración de red de lasiguiente forma:

#define IFC_WIFI_MODE IFPARAM_WIFI_INFRASTRUCTURE#define IFC_WIFI_REGION IFPARAM_WIFI_REGION_AMERICAS

Conexión protegida

Obviamente existen muchas combinaciones posibles, sólo analizaremos las máscomunes. La información correspondiente se encuentra en la misma biblioteca defunciones tcp_config.lib.Para conectarnos a un access point con, por ejemplo, WPA/TKIP, especificamos la

necesidad de compilar el código de soporte de WPA:

#define WIFI_USE_WPA

y el cifrado:

#define IFC_WIFI_ENCRYPTION IFPARAM_WIFI_ENCR_TKIP

A continuación debemos ingresar la clave. Podemos hacerlo en forma de fraseASCII:

#define IFC_WIFI_WPA_PSK_PASSPHRASE "noteladigo"

o directamente en hexadecimal:

#define IFC_WIFI_WPA_PSK_HEXSTR \ "8125BB6E6DE7937997D978A17932DD9ABC4C4F99E84FE95F8F5989964D8AC970"

315

Page 334: El Camino Del Conejo

Networking con Rabbit

El proveer la clave en ASCII fuerza al módulo a calcular la clave en hexa, lo cualrequiere un tiempo considerable, medido en decenas de segundos, y varias de ellas.Es común observar tiempos de 30 segundos o más. Afortunadamente disponemos deuna opción que nos permite tener información del proceso, particularmente mostraren pantalla la clave en hexadecimal una vez convertida, para poder luego cargarla enel programa de esta forma y no tener que esperar cada vez que reiniciamos elmódulo:

#define WIFI_VERBOSE_PASSPHRASE

Para conectarnos a un access point con, por ejemplo, WPA2/CCMP, especificamosla necesidad de compilar el código de soporte de WPA y el de AES:

#define WIFI_USE_WPA#define WIFI_AES_ENABLED

y el cifrado:

#define IFC_WIFI_ENCRYPTION IFPARAM_WIFI_ENCR_CCMP

El resto es idéntico a lo visto para WPA.La configuración para seguridad en modo enterprise es algo más compleja, y no la

desarrollaremos aquí. Se sugiere al lector la observación de tcp_config.lib y lassamples.

Inicio

Al inicializar la red, la llamada a sock_init() vuelve inmediatamente, sin que existaconexión con el access point. Se debe monitorear el estado de la interfaz mediantellamadas a ifpending(), antes de levantar servidores o iniciar conexiones.

sock_init();while(ifpending(IF_WIFI0)==IF_COMING_UP)

tcp_tick(NULL);

A los fines prácticos, disponemos de una función que espera por nosotros y si se lepasa como parámetro un valor distinto de cero, devuelve información de conexión.Dado que esta función ejecuta exit() en caso de errores, su uso está limitado adesarrollo y depuración.

sock_init_or_exit(1);

Cambio de parámetros en campo

Cuando necesitamos cambiar algún parámetro de operación como el SSID o ladirección IP en campo, deberemos reiniciar manualmente la interfaz. Comohiciéramos para Ethernet, la función encargada de realizarlo es ifconfig(), pero estavez debemos asegurarnos que la interfaz esté desactivada antes de modificar:

316

Page 335: El Camino Del Conejo

Wi-Fi

ifdown(IF_WIFI0);while (ifpending(IF_WIFI0) != IF_DOWN)

tcp_tick(NULL);

ifconfig(IF_WIFI0, IFS_IPADDR, src_ip,

IFS_NETMASK, src_mask,IFS_ROUTER_SET, def_gwy,IFS_NAMESERVER_SET, my_dns,

IFS_WIFI_SSID, strlen(my_ssid), my_ssid, IFS_WIFI_ENCRYPTION, enc,

IFS_WIFI_AUTHENTICATION, auth, IFS_WIFI_WPA_PSK_HEXSTR,my_hexkey,

IFS_END);

ifup(IF_WIFI0);while (ifpending(IF_WIFI0) == IF_COMING_UP)

tcp_tick(NULL);

En caso que estemos trabajando sobre una conexión abierta, los parámetrosrespectivos para cifrado y autenticación serán:

IFS_WIFI_ENCRYPTION, IFPARAM_WIFI_ENCR_NONEIFS_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_OPEN

Para una conexión WPA/TKIP, los parámetros serán:

IFS_WIFI_ENCRYPTION, IFPARAM_WIFI_ENCR_TKIPIFS_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_WPA_PSK

Finalmente, para una conexión WPA2/CCMP, los parámetros serán:

IFS_WIFI_ENCRYPTION, IFPARAM_WIFI_ENCR_CCMPIFS_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_WPA_PSK

De aquí se desprende que podemos fácilmente incorporar estos valores dentro desendos arrays y obtenerlos mediante un índice que indique el modo deseado, porejemplo, desde RabbitWeb:

#web opprof select("Open" = 0,"WPA-TKIP"=1,"WPA2-CCMP"=2) \auth=basic,digest groups=ADMIN_GROUP(rw),MON_GROUP(ro)

const unsigned int enc[]={IFPARAM_WIFI_ENCR_NONE,IFPARAM_WIFI_ENCR_TKIP,IFPARAM_WIFI_ENCR_CCMP};const unsigned int auth[]={IFPARAM_WIFI_AUTH_OPEN,IFPARAM_WIFI_AUTH_WPA_PSK,IFPARAM_WIFI_AUTH_WPA_PSK};

setup_interface(void){

printf("\nConectando...");ifdown(IF_WIFI0);while (ifpending(IF_WIFI0) != IF_DOWN)

tcp_tick(NULL);

ifconfig(IF_WIFI0, IFS_IPADDR, src_ip,

317

Page 336: El Camino Del Conejo

Networking con Rabbit

IFS_NETMASK, src_mask,IFS_ROUTER_SET, def_gwy,IFS_NAMESERVER_SET, my_dns,

IFS_WIFI_SSID, strlen(my_ssid), my_ssid, IFS_WIFI_ENCRYPTION, enc[opprof],

IFS_WIFI_AUTHENTICATION, auth[opprof], IFS_WIFI_WPA_PSK_HEXSTR,my_hexkey,

IFS_END);ifup(IF_WIFI0);while (ifpending(IF_WIFI0) == IF_COMING_UP)

tcp_tick(NULL);printf("Conectado\n");

}

Se sugiere al lector, como tarea para el hogar, el realizar las modificacionespertinentes al programa de ejemplo para cambiar la dirección IP desde una páginaweb y permitir además seteo de parámetros de Wi-Fi. Una pequeña gran ayuda seencuentra en la página web de este libro11.

Módulos sin Wi-Fi incorporado

La forma más simple y sencilla de conectar un módulo Rabbit genérico vía wi-fi eshacerlo mediante un access point con capacidad de wireless bridging. De esta forma,el módulo Rabbit conecta su port Ethernet al access point, y éste se conecta de formainalámbrica con el otro access point. Desde el punto de vista del Rabbit, es como siestuviera conectado directamente a la Ethernet remota, no es necesario ningún tipode conexión ni seteo especial, solamente debe tenerse en cuenta que los accesspoints a utilizar tengan capacidad de wireless bridging y puedan hacerlo entre sí.

11 http://www.ldir.com.ar/libros/camino/

318

Ethernet

Access Point Access Pointde la instalacionadicional

Rabbit

Otros equipos wi-fi

Page 337: El Camino Del Conejo

RabbitWeb

Introducción

RabbitWeb es una extensión a Dynamic C, a partir de la versión 8.50. Sufuncionamiento está totalmente integrado al servidor HTTP y elimina la necesidadde escribir CGIs en C, permitiendo al developer una mayor libertad en el diseño de

las páginas web. RabbitWeb está conformado por dos partes fundamentales:� Un lenguaje tipo script, embebido dentro de tags que son interpretadas por el

servidor HTTP al momento de servir la página.� Extensiones al lenguaje de Dynamic C, que permiten simplificar el trabajo con

variables.

La forma de trabajo en este entorno es:� definir las variables que se van a utilizar para ser accedidas mediante el servidor

HTTP, ya sea para configuración o para mostrar una determinada información.� definir los rangos de validez de las variables a modificar, lo cual probablemente

ya se haya hecho como parte de la definición de la aplicación.� definir los grupos de usuarios que van a acceder (o no) a dicha información.

� definir los métodos de autenticación a emplear para cada variable.� identificar las acciones a realizar cuando existen modificaciones en algunas

variables.� Una vez hecho esto, la presentación de la información se realiza mediante el

lenguaje script, en combinación con HTML, lo que se denomina ZHTML.

Extensiones a Dynamic C

Al utilizar RabbitWeb, lo cual se indica mediante la siguiente macro:

#define USE_RABBITWEB 1

se habilitan una serie de extensiones a Dynamic C que nos permiten trabajar deforma más fácil.

Grupos de usuarios

Por ejemplo, para definir los grupos de usuarios a emplear, se utiliza la directiva#web_groups:

#web_groups ADMIN_GROUP, MON_GROUP

319

Page 338: El Camino Del Conejo

RabbitWeb

Los nombres empleados se agregan al espacio de nombres conocidos por DynamicC, por ejemplo, para agregar un usuario al grupo ADMIN_GROUP procedemoscomo si hubiéramos definido el grupo manualmente, de igual modo a como hicimosen los ejemplos de los capítulos sobre configuración en campo y networking conRabbit:

sauth_setusermask(uid, ADMIN_GROUP, NULL);

Variables

Las variables a ser accedidas vía web deben registrarse, lo cual se realiza aplicandola directiva #web a una variable declarada, de la siguiente forma:

int variable;

#web variable

Al mismo tiempo que se la registra, es posible definir los grupos de usuarios y sus

permisos respectivos, así como también una función de evaluación para chequear siel valor ingresado (al modificarla vía web) está dentro del rango permitido, porejemplo:

#web variable (($variable > -10) && ($variable < 10)) \groups=ADMIN_GROUP(rw),MON_GROUP(ro)

Según vemos, el valor ingresado deberá estar comprendido entre -9 y 9, ambos

inclusive; el grupo ADMIN_GROUP tiene permiso de lectura y escritura y el grupoMON_GROUP tiene sólo permiso de lectura. Cualesquiera otros grupos, no puedenacceder a esta variable. En caso que se quiera dar un determinado tipo de acceso atodos los grupos, se los puede referir como all:

#web variable groups=ADMIN_GROUP(rw),all(ro)

El signo $ delante del nombre de la variable indica que nos estamos refiriendo alvalor que se intenta ingresar, es decir, el que envía el usuario desde su navegador. Siomitimos este signo, entonces nos estaremos refiriendo al valor que tiene la variablepara el resto del sistema, es decir, el último modificado correctamente:

#web variable ($variable > variable)

De igual modo, se puede especificar el tipo de autenticación deseado:

#web variable auth=basic,digest groups=ADMIN_GROUP(rw),all(ro)

Las variables a registrar pueden ser de diversos tipos, RabbitWeb interpreta el tipode la variable, por lo que es posible registrar una estructura y referirse luego a cadauno de sus elementos tanto en la función de chequeo como en el script para supresentación, por ejemplo:

320

Page 339: El Camino Del Conejo

Extensiones a Dynamic C

#web config (($config.release > 0)&&($config.release < 10))

Es posible asignar un texto a mostrar cuando se sale fuera de rango, con unafunción especial del lenguaje script (error($variable)). Los textos los definimos alingresar los rangos de validez de la variable:

#web config (($config.data1 <= 9999)?1:WEB_ERROR("muy alto"))

También es posible registrar un array, y definir la función de chequeo para cada

elemento, de la siguiente forma:

int vector[10];#web vector[@] ((vector[@] > -10) && (vector[@] < 10))

El signo @ indica que nos referimos al índice siendo evaluado en ese momento, esdecir, RabbitWeb comprobará que desde vector[0] a vector[9] estén comprendidosentre -9 y 9 (ambos inclusive) cuando se los modifica.

Los strings se registran por el nombre del array, y el sistema chequeaautomáticamente de no exceder la longitud al actualizar:

char somestring[25];#web somestring

Variables con listas de valores

Si deseamos incluir selectores en nuestra página, disponemos de una función quenos permite asignar valores posibles a tomar por un entero, asociados a los textos delselector. La lista se auto-enumera, y puede ser modificada a voluntad, de formasimilar a enum{} en C (y Dynamic C):

int lista;#web lista select( "uno" = 1, "dos", "tres", "cuatro" )

También podemos definir botones de selección (radio buttons) y casillas paraopciones seleccionables (checkboxes). Los botones se definen de igual forma que unselector, las casillas son variables enteras.Los selectores y botones incluyen un equivalente a los rangos de validez, es decir,

solamente aceptan los valores que han sido definidos en el programa.El checkbox es esencialmente un "sí/no", y RabbitWeb acepta cualquier valor. Se

recomienda que nuestro programa no dependa del valor 1 para considerarlohabilitado, sino que interprete cualquier valor diferente de 0 como un "sí"; de todosmodos puede definirse un rango de validez si se desea.

Detección de cambios

Cuando en el navegador se envía un formulario (submit), éste inicia unrequerimiento de una página al servidor HTTP del Rabbit indicando una operación

321

Page 340: El Camino Del Conejo

RabbitWeb

POST, y envía los datos. RabbitWeb entonces chequea si hubo cambios y validaacorde a las funciones especificadas, actualizando las variables correspondientes. Siel cambio de una de estas variables implica que se debe realizar una actividadadicional, como por ejemplo el caso de un port serie (cerrar y abrir), una direcciónIP (convertirla, bajar y subir la interfaz), o simplemente salvar la configuración enflash, podemos indicarlo mediante la directiva #web_update:

void ejecutame(void){

// tarea relacionada con una modificación de variable}#web_update variable ejecutame

La función ejecutame() se ejecuta entonces cuando RabbitWeb detecta unamodificación válida sobre variable.

Lenguaje script

De forma similar a como SHTML incluye tags que son interpretados por el servidor

al momento de servir la página (SSI: Server Side Includes), ZHTML permitedisponer de un simple y poderoso lenguaje script dentro de la página, que seráinterpretado por el servidor HTTP de Rabbit al momento de servirla. Los tags se incluyen dentro de un envoltorio similar al usado en PHP:

<?z ?>

Dentro del mismo, una sentencia por línea, se coloca el script; por ejemplo, elcódigo a continuación hace que el servidor muestre el valor de la variable variable:

<?z print($variable) ?>

Cada variable a que se haga referencia, deberá haber sido registrada en el programaprincipal, como viéramos en el apartado anterior. Por claridad, utilizaremos para

nuestros ejemplos los mismos nombres de variables que definiéramos en el puntoanterior.Cada variable referida en el script deberá estar precedida por un signo $ o un signo

@. El signo $ indica que estamos haciendo referencia al valor que se intentaintroducir al enviar la página; el signo @ indica que nos referimos al valor que tienela variable en el programa principal.

La sintaxis es similar a C y la gramática es simple, permitiendo ejecucióncondicional mediante el uso de la cláusula if(), aunque sin su else complementario, loque se resuelve invirtiendo la pregunta (la cual debe realizarse nuevamente)mediante el operador de negación: ! (signo de admiración):

<?z if($variable==1) { ?>acción

<?z } ?>

322

Page 341: El Camino Del Conejo

Lenguaje script

o bien

<?z if($variable==1) { ?>acción por 'then'

<?z } ?><?z if($variable!=1) { ?>

acción por 'else'<?z } ?>

La acción encerrada entre llaves puede ser código HTML, el cual forma páginasdiferentes dependiendo de lo que considere el script, o parte del script, incluyendomás condicionales anidadas u otras acciones. Debido a que esto implica un consumode memoria, existe un límite en la cantidad de veces que se puede anidar, que sedefine por programa mediante la macro RWEB_ZHTML_MAXBLOCKS, y por

defecto es 4. Esto aplica también e incluye1 bloques iterativos, representadosmediante la cláusula for( ; ; ). Las variables utilizadas dentro de un for son válidas enel entorno de ejecución del script, y no requieren ser declaradas en el programa enDynamic C:

<?z for($A=0; $A<10;$A++) { ?>acción

<?z } ?>

Funciones

El resto de la operación se realiza mediante funciones que permiten resolversituaciones comunes de un modo simple y efectivo.Para conocer la cantidad de elementos de un array o selector se utiliza la función

count(), por ejemplo:

<?z for ($A = 0; $A < count($lista); $A++){ ?>acción

<?z } ?>

En un array, debemos especificar además a qué dimensión nos referimos, porejemplo, un array unidimensional (vector):

<?z for ($A = 0; $A < count($vector,0); $A++){ ?>acción para vector[$A]

<?z } ?>

Para una matriz, el segundo parámetro será 0 ó 1, según a qué dimensión nosqueramos referir.

Para conocer si la página está siendo mostrada como resultado de una petición(GET) o una introducción de valores (POST):

<?z if(update()) { ?>

1 La cantidad total de bloques iterativos y condicionales que puede ser anidada esRWEB_ZHTML_MAXBLOCKS

323

Page 342: El Camino Del Conejo

RabbitWeb

se trata de un POST<?z } ?><?z if(!update()) { ?>

se trata de un GET<?z } ?>

Para saber si hay algún error en la información enviada:

<?z if(error()) { ?>hay errores

<?z } ?>

Para determinar si el error corresponde a una variable en particular:

<?z if(error($variable)) { ?>hay un error en el valor enviado de variable

<?z } ?>

Para mostrar el mensaje de error asociado a una variable en particular:

<?z print(error($variable)) ?>

En el caso particular en que trabajemos con arrays de enteros, los corchetes soncaracteres no admitidos dentro del nombre de una variable en HTTP. Para resolveresta situación, se incorpora una función adicional, varname():

<INPUT TYPE="text" NAME="varname($variable[$indice])" >

Selectores

Para generar un selector desplegable:

<SELECT NAME="lista"><?z print_select($lista) ?></SELECT><br>

Si se desea reemplazar los nombres o agregar algo, puede generarse manualmentecon la función print_opt(). La función selected() permite determinar si la opcióncorriente es la que corresponde al valor actual de la variable en el programaprincipal:

<?z for ($A = 0; $A < count($lista); $A++){ ?><SELECT NAME="lista"><OPTION<?z if (selected($lista, $A) ) { ?>

SELECTED<?z } ?>> <?z print_opt($lista, $A) ?></SELECTED>

<?z } ?>

324

Page 343: El Camino Del Conejo

Lenguaje script

Botones

Para generar una lista de botones de selección, utilizamos la función print_opt(). Lafunción selected() permite determinar si la opción corriente es la que corresponde alvalor actual de la variable en el programa principal:

<?z for ($A = 0; $A < count($lista); $A++){ ?><INPUT TYPE="radio" NAME="lista" OPTION<?z if (selected($lista, $A) ) { ?>

CHECKED<?z } ?>VALUE="<?z print_opt($lista, $A) ?>"> <?z print_opt($lista, $A) ?>

<?z } ?>

En el capítulo sobre configuración en campo, encontramos una serie de ejemplos deuso en situaciones reales. Para una descripción detallada y precisa de la gramática y

cada una de las funciones y extensiones, se sugiere consultar el manual deRabbitWeb.

325

Page 344: El Camino Del Conejo
Page 345: El Camino Del Conejo

Desarrollo de aplicaciones

Introducción

Sin duda el desarrollo de una aplicación es donde el mundo real y la teoría seconfrontan. No porque uno y otro pertenezcan a mundos distintos, o cada uno tengauna parte de la verdad, sino que muchas veces no conocemos todo el escenario y se

nos escapan algunas cosas al momento teórico, y forzosamente debemos aprenderlasa los golpes en la práctica. No significa esto una falencia de la teoría en sí, sino talvez que la parte que conocíamos de ella no fue suficiente.Lo que intentaremos hacer en este capítulo es plantear una aplicación, hacer un

análisis teórico tratando de anticipar los problemas con los que nos encontraríamos,y luego llevarla a la práctica, aplicando los conceptos elaborados en dicho análisis.

Transporte de comunicaciones serie

asincrónicas mediante TCP/IP

La "conversión serie a Ethernet" es un término seguramente acuñado por algúngenio de marketing, incorrecto desde un punto de vista técnico. Peras son peras, ymanzanas son manzanas. Aunque a veces las peras muy verdes no parezcan peras, nopor ello son manzanas. En primer lugar, no hay ningún tipo de conversión sino untransporte. En segundo lugar, tampoco es por Ethernet, sino por TCP/IP. Hecha lacatarsis correspondiente, vamos a analizar el tema.

Introducción

Suele entenderse por "conversión serie a Ethernet" el transporte de un flujo(stream) serie asincrónico de datos mediante una conexión TCP/IP, quegeneralmente a la salida del dispositivo que realiza la tarea de meter los caracteresobtenidos dentro de un segmento TCP o un datagrama UDP, es transportado porEthernet. La forma de realizarlo no es única, y si bien es algo relativamente simple aprimera vista, dado que las malas implementaciones quedan como muchas veces

enmascaradas por la abundancia de recursos, en cuanto se lo analiza se descubre queno es tan simple como parecía. ¡Y no tendría por qué ser simple! Desde los viejostiempos del X.25, un grupo de genios de lo que por entonces se llamaba CCITT1, y

1 Comité Consultatif Internationale de Télégraphie et Téléphonie, es decir, Comité ConsultorInternacional de Telegrafía y Telefonía, pero en francés.

327

Page 346: El Camino Del Conejo

Desarrollo de aplicaciones

hoy es la ITU-T2, dedicaron varias páginas a la definición de X.28, recomendaciónque establecía la forma en que debía operar un PAD (PacketAssembler/Disassembler) para poder transferir datos provenientes de una terminalasincrónica a través de una red de conmutación de paquetes como X.25 y viceversa.Si bien han pasado las décadas, y X.25 no tiene nada que ver con TCP/IP niEthernet, ambas son en esencia redes de conmutación de paquetes, y los problemas

son esencialmente los mismos. La única diferencia es que en una conexión de 2400bps con 10 usuarios, una implementación errónea se va a notar más que en una redEthernet a 100 Mbps con switches, razón por la cual pueden llegar a proliferaralgunas aberraciones.Lo que quiero decir con todo esto, es que la solución genérica es analizar la

recomendación X.28 e implementar un PAD, aunque probablemente lo más eficiente

sea analizar el stream serie que se intenta transferir, y obrar en consecuencia. A losfines de defender esta mentalidad y en pro de lo académico, haremos un someroanálisis para corroborar nuestra aseveración y descartar posturas simplistas.

Análisis

Lo primero que debemos analizar es la total y absoluta diferencia entre ambosmundos. En el stream serie, los caracteres van y vienen a gusto cuando les place;mientras que en una red Ethernet existe un momento en el que se puede transmitir.

De igual modo, los caracteres serie viajan de a uno o en bloque, a gusto, mientrasque en una red Ethernet viajan tramas de una longitud acotada. Cuando un caracterserie sale, un caracter llega al otro extremo; si salen 2, una pausa y luego 33 juntos,llegan 2, una pausa y luego 33 juntos. En una red Ethernet, se transmite cuando setiene acceso al medio, y esto genera latencias y diferencias de timing.Entonces, podemos decidir aprovecharnos de la diferencia de velocidades y cada

vez que recibimos un caracter, enviamos un paquete. Dado que en una comunicaciónUDP (para ser bondadosos) tenemos un overhead de 28 bytes3, y al mandarlo víaEthernet tenemos un mínimo de 64 bytes por trama, estamos en realidad enviando 84bytes4 por cada caracter, es decir que un enlace de 9600 bps ocupa un ancho debanda de 645120 bps5 en la Ethernet, es decir, 67,2 veces mayor; transmitiendo untotal de 960 tramas por segundo. Si tomamos en cuenta que un sistema de acceso

2 International Telecommunications Union, Telecommunications Standardization Sector, es decir, elsector encargado de la estandarización en las telecomunicaciones de la Unión Internacional deTelecomunicaciones.

3 20 bytes de IP, 8 bytes de UDP.4 8 bytes de preámbulo, 14 de header y 4 de CRC, suman 26 bytes; más un mínimo de 46 bytes de

datos pues la trama más corta debe ser de 64 bytes más el preámbulo, un total de 72 bytes. Siconsideramos además que debemos incluir el espacio de silencio entre tramas que es de 9,6us, esto a10Mbps corresponde a 12 bytes más, por un total de 84 bytes.

5 A 9600 bps, 8 bits por caracter, 1 de stop; son 960 caracteres por segundo. Cada uno de esoscaracteres se envía como una trama de 84 bytes, es decir 672 bits. Esos 960 caracteres por segundoresultan en 960 tramas de 672 bits, es decir 645120bps

328

Page 347: El Camino Del Conejo

Transporte de comunicaciones serie asincrónicas mediante TCP/IP

múltiple con colisiones como es Ethernet funciona bien con una ocupación de hastael 30%, la cantidad máxima de tramas de este tipo que se puede transmitir es de

0,3�

10000000bits

seg

84bytes

trama�8

bits

byte

�4464tramas

seg.

Una red Ethernet tradicional se satura con unas cuatro o cinco de estas conexiones,sin dejar comunicar a ningún otro nodo. No creo que podamos hacernos amigos deladministrador de la red...

Aterrorizados por el hecho de ser odiados por gente tan amistosa como losadministradores de redes, decidimos tomar el camino inverso, y almacenar byteshasta llenar una trama completa y entonces transmitirla. Dado que el tamaño máximo

para los datos en la trama es de 1500 bytes, podemos armar un datagrama UDP dehasta 1472 caracteres. El tiempo requerido para juntar estos 1472 caracteres, sivienen a 9600 bps y son caracteres de 8-bits sin paridad y sólo un bit de stop, es de

1472caracteres�

�8�2�bits

caracter

9600bits

seg

�1,533 seg .

Es decir, que del otro extremo no van a tener noticias nuestras hasta que juntemostodo un gran paquete, lo que no ocurre hasta por lo menos 1,533 segundos. Si lo queviene por la interfaz serie es un protocolo que tiene un tiempo máximo para esperarrespuestas, lo rompemos con nuestra demora. Si es un protocolo de polling quemanda un paquete y espera una respuesta (digamos Modbus6 ASCII), lo rompemos

porque el paquete, a menos que sea de exactamente 1472 bytes, nunca sale7. Si es unprotocolo como Modbus RTU, que depende de un tiempo entre paquetes paradetectar comienzo y fin de los mismos, lo rompemos, porque alteramos el timing.Antes de ser acusado de extremista, me veo obligado a sugerir tomar lo mejor de

ambos mundos y enviar una determinada cantidad de bytes intermedia entre ambosextremos. De esta forma reducimos la latencia y la ocupación de Ethernet. Sin

embargo, si bien estamos en la dirección correcta, ¿cuál es esa cantidad de bytes?Como analizamos en el apartado sobre comunicación serie asincrónica, en el

capítulo sobre conectividad, existen protocolos que delimitan sus mensajes concaracteres únicos, que no se repiten dentro del mensaje. ¿Y si detectamos uno deestos caracteres y enviamos inmediatamente una trama conteniendo el mensaje? Puesmientras que dicho mensaje no supere el tamaño máximo de trama, ¡perfecto! ¿Y si

lo supera? En este caso, al llenarse nuestro buffer, emitiremos un datagrama, y luegoal recibir el caracter final enviaremos el resto. Mientras que en el otro extremo se

6 Protocolo originario de la firma Modicon, hoy standard en el control de PLCs y demás dispositivosde ambiente industrial.

7 En realidad, si el paquete es más grande sale truncado; y la otra parte, al igual que un paquete máspequeño, sale cuando la(s) retransmisión(es), o paquete(s) posterior(es), llena(n) el buffer.

329

Page 348: El Camino Del Conejo

Desarrollo de aplicaciones

almacene todo y se envíe el paquete completo, no habrá inconvenientes (lo cualpuede ocurrir naturalmente dada la diferencia de velocidades).Acabamos de solucionar uno de los casos posibles. ¿Qué pasa ahora si nuestro

protocolo tiene un espacio entre tramas como delimitador? En este caso, lo quepodemos hacer es disponer de un timer, el cual reiniciaremos cada vez que recibimosun caracter. Cuando transcurra un tiempo mayor sin recibir caracteres, el timer

expirará, indicándonos la necesidad de transferir un mensaje. Mantendremos ademásla salvedad de poder enviar una trama si se llena el buffer antes de que expire eltimer.¡Felicidades!, a grandes rasgos acabamos de inventar X.28, sólo que unos veinte

años más tarde...Un PAD X.28 tiene un set de parámetros (X.3) que definen las particularidades que

acabamos de analizar (y otras muchas más), de modo de poder optimizar este tipo decomunicación. Si bien se trata de un dinosaurio de la era X.25, la situación esequivalente.

En los ejemplos hemos usado UDP por una simple razón: no tiene "inteligenciapropia" y es posible hacer este tipo de análisis. Además, no debemos preocuparnos

por mantener una conexión y su latencia es baja. Si se pierde la información en elcamino, todo depende de cómo reacciona el protocolo serie. Si está esperando unaconexión por un cable, es poco probable que le agrade mucho el que se pierda partede la información, o que el segundo mensaje llegue antes que el primero. Lo ciertoes que la mayoría de estos sistemas utilizan TCP en vez de UDP, de modo que todolo que entra tenga certeza de salir por el otro lado en el mismo orden en que se

envía, o al menos poder detectar el establecimiento y la pérdida de la comunicaciónsin tener que armar un protocolo para ello. En este caso, hay muchos factores másque intervienen en el cálculo de ancho de banda, los cuales escapan al tipo deanálisis posible en un texto de estas características8.

Desarrollo

Hecha la disección teórica correspondiente, estamos en condiciones deavalanzarnos sobre el código. Como siempre, debemos ir donde están los ejemplos,

y no es otro lugar que el directorio Samples de la instalación de Dynamic C. Elprimer ejemplo que debemos analizar es Samples\Tcpip\serialexa.c, el cual aplica elconcepto de buffer y timeout para la comunicación, y es lo más utilizado comogenérico. Otro ejemplo a considerar es Samples\Tcpip\Telnet\vserial.c, el cualaprovecha la existencia de una biblioteca de funciones: vserial.lib, encargada detransportar streams serie por conexiones TCP, aprovechando e implementando la

funcionalidad del protocolo Telnet. Lamentablemente no he experimentado con ella

8 Puede obtenerse una idea revisando el apartado sobre timing y ancho de banda en el capítulo sobrenetworking.

330

Page 349: El Camino Del Conejo

Transporte de comunicaciones serie asincrónicas mediante TCP/IP

por razones de tiempo, pero el ejemplo es sumamente claro y se observa que laoperatoria es realmente simple.Si nuestro protocolo no tolera las demoras introducidas al utilizar el método de

timeout que propone serialexa.c, podemos escribir un pequeño handler para detectarfinalización de mensaje; en cuyo caso procedemos a enviarlo al otro extremo. Deigual modo, procesamos lo recibido desde el extremo remoto y lo entregamos por la

interfaz serie. El ejemplo que proponemos a continuación a manera de desarrollo, se basa en lo

que fuimos analizando en el capítulo sobre networking con Rabbit. Desarrollaremosun handler para la aplicación en sí, el cual será llamado desde el handler que manejala conexión TCP. En este ejemplo en particular, elegimos un protocolo serie simple:NMEA0183, que es el utilizado por la gran mayoría de los módulos GPS para

entregar la información. Este protocolo en particular resulta muy fácil de sincronizarcon una máquina de estados. La idea es tomar NMEA0183 por el port serie ytransmitirlo a otro módulo Rabbit en cualquier lugar (en el mismo sería muy obvio yaburrido), y a la vez recibir la información del lugar remoto.

Implementaremos entonces dos handlers, uno para cada sentido; aunque el quetoma la información del segmento TCP y la envía por el puerto serie es sumamentesimple, dado que el procesamiento ya fue hecho del lado remoto.Complementariamente, el que toma la información del puerto serie, deberá detectarinicio y fin del mensaje, y enviar un segmento al extremo remoto. Siguiendo elmismo espíritu, desarrollaremos luego los handlers para mantenimiento de la

conexión TCP; uno local, cliente, que iniciará la conexión, y otro remoto, servidor,que esperará que se lo llame. En el CD que acompaña a esta edición, se encuentra latotalidad del código; un archivo con el cliente, y otro con el servidor, que al sercompilado para un RCM2200 utiliza el pinout alternativo del port B.

En cuanto al código en sí, el pasaje de parámetros a los handlers lo haremos dentro

de una estructura, que definimos a tal fin:

typedef struct {int state;int offset;char buffer[BUFSIZE];

} State;

typedef struct {

331

GPS GPS

Rabbit Rabbit

PC/PDA PC/PDA

TCP/IP

Page 350: El Camino Del Conejo

Desarrollo de aplicaciones

int state;tcp_Socket socket;State serial;

} ConnState;

Las constantes las hemos definido en mayúsculas, para mayor claridad.

Serie a TCP

Aprovechando la simpleza del protocolo, descartamos todo lo que vemos hastaencontrar un caracter '$', momento en el cual comenzamos a almacenar caracteres

hasta encontrar el fin de línea. En este caso utilizamos avance de línea (line feed),que funciona perfectamente con los módulos GPS que teníamos disponibles parahacer la prueba; de encontrarse algún rebelde, se puede reemplazar la comparaciónpor retorno de carro o ambos. Una vez identificado el mensaje, lo enviamos alextremo remoto:

int sertcp_handler(ConnState *hstate){auto int bytes_written;auto unsigned char data;auto State *cstate;

cstate=&hstate->serial; if(cstate->state == SER_SEND) {

bytes_written=sock_fastwrite(&hstate->socket,cstate->buffer,cstate->offset); // cuánto mando ?

if (bytes_written < 0) // hubo un error return(-1);

if(bytes_written!=cstate->offset) { // memcpy tolera written=0memcpy(cstate->buffer,cstate->buffer+bytes_written,

cstate->offset-bytes_written);cstate->offset -= bytes_written; // compenso lo que

} // no pude mandarelse

cstate->state = SER_IDLE; // listo, ya mandé todo}else {

if(serBrdUsed()){ // hay algo esperando ? data=(unsigned char) serBgetc(); // venga !

switch(cstate->state) {case SER_IDLE:

cstate->offset=0; // flushif(data=='$') // comienzo cstate->state=SER_DATA;else return(0); // descartabreak;

case SER_DATA:if(data==0x0A) { // final cstate->state=SER_SEND;

printf("Serial->TCP: %d bytes\n",cstate->offset+1);

}break;

}cstate->buffer[cstate->offset]=data; // guardaif(cstate->offset<(BUFSIZE-1)) // incrementa ptr

cstate->offset++;else

cstate->state=SER_IDLE; // muy largo, ignora

332

Page 351: El Camino Del Conejo

Transporte de comunicaciones serie asincrónicas mediante TCP/IP

}}return(0);

}

TCP a serie

Esto es más que simple, dado que todo el trabajo lo hizo el otro extremo. Sitenemos espacio en el buffer serie, simplemente tomamos lo que haya disponible enel socket y lo entregamos por la interfaz serie:

void tcpser_handler(ConnState* hstate){auto int len,bytes_to_write;auto unsigned char buffer[BOUTBUFSIZE];

if(bytes_to_write=serBwrFree()) { // puedo mandar ? if((len=sock_fastread(&hstate->socket,buffer,

bytes_to_write))>0){ // cuánto tengo (de lo que puedo) ?printf("TCP->serial: %d bytes\n",len);

bytes_to_write=(bytes_to_write>len)?len:bytes_to_write;serBwrite(buffer,bytes_to_write); // afuera !

}}

}

Mantenimiento de la conexión

Tanto la apertura como la espera las hacemos con un esquema muy similar a lovisto en el capítulo sobre networking con Rabbit, con una simple máquina deestados.

Cliente

void conn_handler(ConnState *state){auto tcp_Socket *socket;auto unsigned long timer;

socket=&state->socket;

/* La conexión pudo haberse perdido */

if(state->state!=CONN_INIT && tcp_tick(socket)==0) {state->state=CONN_WINIT; // espera y reintentaprintf("Se cierra la conexion\n");timer=MS_TIMER+WAIT_TMOUT; // restart timeout

}

switch(state->state) { case CONN_WINIT:

if ((serBwrFree() == BOUTBUFSIZE) && (MS_TIMER<timer))state->state=CONN_INIT; // reintenta

break;

case CONN_INIT:if (tcp_open(socket,0,inet_addr(DESTIP),DESTPORT,NULL) != 0) {

333

Page 352: El Camino Del Conejo

Desarrollo de aplicaciones

state->state=CONN_OPENING;printf("\nAbriendo el socket\n");

}else {

state->state=CONN_WINIT;printf("\nError abriendo el socket!\n");timer=MS_TIMER+WAIT_TMOUT; // reinicia timeout

}break;

case CONN_OPENING:if(sock_established(socket)||sock_bytesready(socket)>= 0){

state->state=CONN_OPEN;sock_mode(socket,TCP_MODE_BINARY);state->serial.state=SER_IDLE; // inicializa handlersprintf("Nueva Conexion\n");

}break;

case CONN_OPEN: tcpser_handler(state); if(sertcp_handler(state) == -1){

sock_close(socket);printf("Error, se cierra la conexion\n");state->state=CONN_WCLOSE;

}break;

case CONN_WCLOSE:break;

}}

Servidor

El código del servidor es exactamente igual al del cliente, la única diferencia está

en que en vez de iniciar una conexión, abrimos el socket en modo escucha:

case CONN_INIT:if (tcp_listen(socket,SRVRPORT,0,0,NULL,0) != 0) {

state->state=CONN_OPENING;printf("\nAbriendo el socket\n");

}else {

state->state=CONN_WINIT;printf("\nError abriendo el socket!\n");timer=MS_TIMER+WAIT_TMOUT; // reinicia timeout

}break;

Programa principal

El programa principal es simplemente un loop infinito que llama al handler quemaneja la conexión, permitiéndonos, si fuera necesario, realizar cualesquiera otrastareas:

void main(){ConnState connstate;

334

Page 353: El Camino Del Conejo

Transporte de comunicaciones serie asincrónicas mediante TCP/IP

sock_init();connstate.state=CONN_INIT;serBopen(BAUDRATE);

while (1) { tcp_tick(NULL);

conn_handler(&connstate);}

}

Otros protocolos

Con una muy simple modificación, reemplazando la búsqueda del caracter '$' por elcaracter ':', ya podemos transportar Modbus ASCII en vez de NMEA0183. De igualmodo, haciendo un análisis similar, podemos transportar cualquier tipo de protocoloque delimite inicio y fin de trama por un caracter único o especial. Aquellosprotocolos que utilicen character stuffing9 requerirán algo más de trabajo,complicando un poquitín nuestra máquina de estados.

Otros protocolos que no encajen en este modo de trabajo requerirán que trabajemospor el método de timeout + buffer lleno, lo cual podemos aprender observandoSamples\Tcpip\serialexa.c. En esencia, lo que deberemos hacer es resetear un timercada vez que recibimos un caracter por el puerto serie, y cuando este timer expira ollenamos el buffer, remitimos lo almacenado al otro extremo. La función serXread()realiza esta tarea por sí sola, por lo que la operación puede llegar a ser mucho más

simple de lo que parece en un principio.

Protocol Spoofing

Un poco más complicado, pero igualmente posible, es realizar spoofing. Por estoentendemos la implementación en el Rabbit del protocolo, sea como master o comoslave, evitando transferir a través de la red todo el handshake entre éstos. De este

9 Si el caracter que se usa como delimitador aparece en el cuerpo del mensaje, se lo repite o reemplazapor una secuencia de escape, de modo que una aparición única siempre corresponda a su funcióncomo delimitador. El receptor reconoce la secuencia de escape y es capaz de reconstruir el mensajeoriginal.

335

Centro demonitoreo

Rabbit Rabbit PLC

TCP/IP

RS-232 RS-232

Modbus

Page 354: El Camino Del Conejo

Desarrollo de aplicaciones

modo, se produce un enlace master-slave en un equipo conectado al controladorprincipal, y otro enlace master-slave en un equipo conectado al dispositivocontrolado. Un módulo Rabbit hace de slave, y el otro hace de master, en la interfazserie. Mediante un protocolo que nosotros implementemos, Rabbit master y Rabbitslave intercambian solamente la información, dejando el handshake y el polling parasaber si está vivo en las interfaces serie, con el consecuente ahorro de ancho de

banda, que como bien sabemos muchas veces se traduce a dinero.

Una vez más, no estamos inventando nada, el esquema tradicional que propusimoses similar a lo que realizan STUN10 y BSTUN11, mientras que esta "nueva idea" essimilar a lo que proponen DLSW12 y DLSW+; todos esquemas viejitos del ambienteSNA13. O tal vez un intermedio como lo es XOT14 para X.25.Como seguramente habrán notado una cierta tendencia a lo largo del libro, y

particularmente en este capítulo, lo nuevo no siempre es nuevo, y lo viejo no tiene

por qué ser siempre obsoleto. Así como debemos conocer la historia para entender

10 Serial tunnelling: detección de tramas HDLC/SDLC y su envío por una red, generalmente sobreTCP. Permite intercomunicar sistemas SNA mediante redes modernas.

11 Bi-Sync tunnelling: detección de tramas BSC y su envío por una red "moderna".12 Data Link SWitching, transporta información de sistemas SNA mediante TCP, estableciendo si es

necesario conexiones locales SDLC y ocupando ancho de banda sólo con información. Permiteademás comunicarse con el host SNA mediante otro protocolo (diferente de SDLC), por lo quepodría considerarse como gateway también.

13 Systems Network Architecture, el modelo por capas desarrollado por IBM del que se dice quesurgieron las ideas del modelo OSI (Open Systems Interconnect). De hecho, HDLC, padre de casitodos los protocolos de WAN de nivel-2, se basa en SDLC (Synchronous Data Link Control),protocolo de nivel-2 de SNA.

14 X.25 Over TCP, transporta el nivel-3 de X.25 sobre TCP. En la interfaz serie se recibe LAPB (nivel-2), se extrae la información de packet level, y ésta se transmite encapsulada en TCP. Desde el puntode vista de una interfaz serie, se reduce el tráfico sobre la red TCP al eliminarse la información deLAPB. La RFC que lo define sugiere además la implementación de sistemas con control de flujolocal, lo que permitiría reducir aún más la ocupación de ancho de banda sobre la red TCP alminimizar el tráfico de control sobre ésta.

336

Centro demonitoreo

Rabbit Rabbit PLC

TCP/IP

RS-232 RS-232

Master Slave Master Slave

propietarioProtocolo ’X’ Protocolo ’X’

solo infopolls + info polls + info

Page 355: El Camino Del Conejo

Transporte de comunicaciones serie asincrónicas mediante TCP/IP

los hechos del presente, el conocer el funcionamiento y la evolución de los viejossistemas puede no sólo aportar muchas ideas para los productos de hoy, sino tambiénahorrar mucho tiempo de desarrollo. De hecho, como probablemente habrán notado,muchas cosas ya estaban inventadas antes de que las "descubriéramos"...

Gateway

En algunos casos, el aprender el framing del protocolo nos permite realizar un

gateway, por ejemplo, en el caso de Modbus/TCP, podemos desarrollar una parte deModbus/TCP y hacer un gateway a Modbus; de forma similar a como DLSW+permite conectarse a un host por Ethernet o Token Ring y servir a un controladormediante SDLC por una interfaz serie.

Conversión de velocidad

Otra posibilidad que abre este tipo de transporte, es la conversión de velocidad. Porejemplo, nuestro GPS puede transmitir a 9600bps, y el software que lo recibe ennuestra PC o equivalente recibir los datos en el standard de 4800 bps. Si el caso

fuera a la inversa, no tendríamos de qué preocuparnos, pero cuando la relación de

velocidades es mayor velocidad�menor velocidad , surge el interrogante: ¿a

dónde va a parar lo que sobra? Pues bien, si existen pausas en la transmisión, y lavelocidad efectiva, es decir, la cantidad total de bits transmitidos en el tiempoempleado para hacerlo, no supera a la velocidad de salida, no existe ningúninconveniente.En el caso del GPS, por lo general se transmiten unos tres mensajes por segundo, y

algunos mensajes especiales cada cinco segundos. Los mensajes que se transmiten

cada un segundo son todos menores de 100 caracteres, lo cual totaliza a lo sumounos 300 bytes por segundo, es decir, 3Kbps. De igual modo, podemos estimar quepara el peor caso, no superaremos los 4800bps (por alguna razón el standard fijó estavelocidad... ¿no?), con lo cual nos quedamos tranquilos que podemos realizar laconversión de velocidades. Si el cálculo nos da un poco más, lo que podemos haceres configurar al módulo GPS para que espacie un poco más sus mensajes, o no envíe

aquéllos que no necesitamos. El único detalle al que necesitamos prestarle atención

337

Centro demonitoreo

Rabbit PLC

TCP/IP

RS-232

ModbusModbus/TCP

Page 356: El Camino Del Conejo

Desarrollo de aplicaciones

es que debemos tener un buffer con espacio suficiente para alojar todo lo que eltransmisor envía a la velocidad mayor, menos lo que el receptor logra recibir a lavelocidad menor, durante el tiempo más largo que el transmisor está activotransmitiendo de forma continua. El tamaño calculado se reparte entre buffers serie yTCP remotos, más buffers serie, de protocolo, y TCP locales, asumiendo que eltransmisor de mayor velocidad es local. Todo esto es válido siempre y cuando TCP

pueda transmitir al otro extremo y no se quede esperando por problemas de delayexcesivo o ancho de banda reducido, en cuyo caso el análisis es algo más complejo.Si por el contrario, la velocidad efectiva se acerca peligrosamente o supera la

velocidad del puerto remoto, deberemos implementar algún método de control deflujo, es decir, poder parar al transmisor para que se pueda vaciar el receptor, pues

no hay lugar donde poner lo que sobra. Esto generalmente se hace por tres métodos conocidos:

� control de flujo fuera de banda, es decir, mediante señales ajenas al flujo deinformación como señales de interfaz (RTS, CTS, DTR)� control de flujo en banda, es decir, insertando caracteres especiales

(XON/XOFF, ENQ/ACK) que indican al transmisor el parar y reanudar latransmisión.� Protocolos asincrónicos como por ejemplo BPS15, que ya tienen esta función de

forma intrínseca, dado que el servidor o equivalente va interrogando a losremotos y por ende regulando lo que puede recibir.

Lo que debemos tener en cuenta en los dos primeros casos es si el protocolo y/odispositivo soporta que quien transmite pueda ser instruido o intimado a detenerse;por ejemplo muchos módulos GPS no disponen de señales de interfaz y NMEA0183no establece nada sobre control de flujo. En estos casos, un buffer lo suficientemente

grande como para poder recibir la totalidad de un mensaje en el extremo de mayorvelocidad, mientras no podemos terminar de enviarlo por el extremo de menorvelocidad, suele ser suficiente; lo cual haríamos de todos modos si empleamos elesquema de detección de fin de mensaje por señalización intrínseca del protocolo,pero requiere algo más de análisis en otros métodos.Una vez más, tampoco inventamos nada, control de flujo dentro y fuera de banda

es lo que hacían los multiplexores asincrónicos, y hacen los modems con control deerror (MNP.4, V.42, etc.).

Latencia: algoritmo de Nagle, flush

Si el protocolo a transportar es muy sensible a la latencia, estamos en problemas.No nos referimos por esto al caso de Modbus RTU, dado que éste, una vez detectadoprincipio y fin de trama, es transportable sin mayores inconvenientes, dado que el

receptor dispone de un tiempo para contestar. Los conflictivos son aquellosprotocolos de aplicación que utilizan el tiempo de respuesta para hacer cálculos,sincronizarse, o asumir determinadas cosas sobre su interlocutor, debido a que

15 Burroughs Poll/Select; protocolo de dicha firma para terminales asincrónicas.

338

Page 357: El Camino Del Conejo

Transporte de comunicaciones serie asincrónicas mediante TCP/IP

estamos jugando con el tiempo de propagación de una forma que no fue prevista enel diseño de éstos.Ante este tipo de cosas, probablemente debamos jugar desconectando el algoritmo

de Nagle, el cual comentáramos en el capítulo sobre networking. Afortunadamente,desconectarlo y volverlo a conectar es más simple que entenderlo:

tcp_set_nonagle(&socket);tcp_set_nagle(&socket);

Las opciones para controlar cuándo TCP envía un segmento y cuándo no, una vezdesconectado el algoritmo de Nagle, son las siguientes:

sock_flush(&socket); // envía lo que se haya almacenado

sock_noflush(&socket); // próxima escritura en el socket// no causa un envío

sock_flushnext(&socket); // próxima escritura en el socket// sí causa un envío

Tarea para el hogar

Y hemos llegado al final. Hemos hecho el desarrollo que nos habíamos propuesto,y hemos esbozado algunos conceptos teóricos más avanzados para poder afrontarposibles mejoras y modificaciones.Queda para el lector, como tarea para el hogar, el aplicar éstos y los demás

conceptos vistos en el libro y no sólo implementar diferentes protocolos sinotambién agregar configuración por página web, DHCP, cambio de IP dinámico, logde conexiones en file system, detección de desconexión por keepalives, etc.

Expandiendo a más puertos simultáneos

Tal vez lo más interesante como tarea para el hogar sea la expansión a más puertossimultáneos, es decir, ya que disponemos de entre cuatro y seis puertos serie en el

módulo, por qué no utilizarlos todos. Pues bien, esto es relativamente simple derealizar; utilizando indexed cofunctions o manteniendo el esquema de handlers convariables de estado externas. A fin de reutilizar la mayor parte del código y noescribir cuatro o seis veces el mismo handler, uno para cada puerta, lo que debemoshacer con las funciones del puerto serie, es, igual que como hiciéramos con lasvariables, agregarles un nivel de indirección, es decir reemplazarlas por un puntero.

Como bien sabemos, estamos hablando de un function pointer, un puntero a función,por ejemplo:

typedef int (*fnptr)(); // define el tipo fnptr, puntero a función que devuelve// un entero. Esto es más legible y entendible luego

fnptr serXwrite; // serXwrite es un puntero a una función que devuelve// un entero

serXwrite = serBwrite // serXwrite apunta a serBwrite()

339

Page 358: El Camino Del Conejo

Desarrollo de aplicaciones

(*serXwrite)(buffer,bytes); // se ejecuta serBwrite()

Por ejemplo:

typedef int (*fnptr)();

void somefunction(tcp_Socket *socket, fnptr serXwrite){...

// obtiene vía TCP, procesa, etc.(*serXwrite)(buffer,bytes); // escribe en el puerto// hace otra cosa

}

main(){ConnState connstate[4];

while(1){somefunction(&sockets[0],serAwrite);somefunction(&sockets[1],serBwrite);somefunction(&sockets[2],serCwrite);somefunction(&sockets[3],serDwrite);

}}

Por supuesto que también la podemos incluir dentro de la estructura con la que lepasamos los parámetros a los handlers.

340