Sie sind auf Seite 1von 358

El camino del conejo

Gua prctica para avanzar en el desarrollo


con procesadores y mdulos
Rabbit

El camino del
conejo
Gua prctica para avanzar en el

desarrollo
con procesadores y mdulos

Rabbit

Sergio R. Caprile

Caprile, Sergio R.
El camino del conejo : gua prctica para avanzar en el desarrollo con
procesadores y mdulos Rabbit . - 2a ed. - Buenos Aires : Gran Aldea
Editores - GAE, 2010.
358 p. ; 23x16 cm.
ISBN 978-987-1301-28-7
1. Electrnica. 2. Procesadores. I. Ttulo
CDD 621.39

Fecha de catalogacin: 07/04/2010

1a edicin: septiembre de 2006


2a edicin: abril de 2010
ISBN 978-987-1301-28-7

2010 by Sergio R. Caprile


Hecho el depsito que establece la ley 11.723.

Se prohibe la reproduccin total o parcial, por cualquier medio


electrnico o mecnico incluyendo fotocopias, grabacin
magnetofnica y cualquier otro sistema de almacenamiento de
informacin, sin autorizacin escrita del editor.

Los nombres de los programas, sistemas operativos, sitios de Internet y


hardware, entre otros, mencionados en la presente obra, son propiedad
exclusiva 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.

ndice de contenido
Prefacio....................................................................................................................xvii
Assembler.....................................................................................................................1
Introduccin...........................................................................................................1
Repaso..............................................................................................................1
Rabbit 2000 y 3000.....................................................................................1
Rabbit 4000 y 5000.....................................................................................2
Bloques independientes..........................................................................................3
Depuracin........................................................................................................3
Rabbit 4000 y 5000.....................................................................................3
Pasaje de parmetros........................................................................................4
Bloques de assembler en xmem........................................................................5
Assembler encapsulado en C..................................................................................6
Acceso a variables locales y parmetros...........................................................7
Depuracin........................................................................................................9
Sentencias C dentro de bloques assembler.............................................................9
Interrupciones............................................................................................................13
Introduccin.........................................................................................................13
Repaso............................................................................................................13
Ejemplos...............................................................................................................16
Interrupciones internas: buzzer en Timer B....................................................16
Interrupciones externas: lector ABA (Track II)..............................................18
Tiempos de ejecucin................................................................................................21
Introduccin.........................................................................................................21
Conceptos.............................................................................................................21
Interrupciones.................................................................................................21
Multitarea........................................................................................................23
Multitarea cooperativo..............................................................................23
Multitarea de tipo preemptive...................................................................25
Sentencia Slice....................................................................................26
RTOS........................................................................................................29
Recursos...............................................................................................................29
Perifricos Internos....................................................................................................31
Ports paralelo........................................................................................................31
Shadow registers.............................................................................................31
Cambio de pines por hardware.......................................................................35
Ports D y E: bit registers.................................................................................41
Ports serie en Rabbit 2000 y 3000........................................................................41
Registros.........................................................................................................41
Pines................................................................................................................42
Puertos A y B............................................................................................42
Pinout alternativo................................................................................42

vii

Puertos C y D............................................................................................43
Puertos E y F.............................................................................................43
Ports serie en Rabbit 4000 y 5000........................................................................43
RS232.lib........................................................................................................43
DMA.........................................................................................................44
Packet.lib........................................................................................................44
Timer B................................................................................................................44
Repaso............................................................................................................44
Ejemplo (Rabbit 2000 y 3000).......................................................................45
El Timer B antes y despus del Rabbit 4000 .................................................46
Ejemplo.....................................................................................................47
Reloj de Tiempo Real...........................................................................................47
Watchdog Timer...................................................................................................48
Uso de encoders o codificadores rotativos...........................................................49
R3000(A) y sucesores.....................................................................................50
R2000..............................................................................................................52
Salidas PWM........................................................................................................54
Interrupciones y supresin de pulsos (R3000A+)..........................................56
Captura de eventos...............................................................................................58
Repaso............................................................................................................58
Ejemplo...........................................................................................................60
Timer C (R4000+)................................................................................................61
IO Config..............................................................................................................63
Perifricos..................................................................................................................65
Bus de datos.........................................................................................................65
Memorias FRAM paralelo..............................................................................65
Controladores de displays LCD color.............................................................70
Breve descripcin del display color..........................................................70
Breve descripcin del controlador............................................................72
Desarrollo..................................................................................................73
Algoritmos...........................................................................................77
Displays LCD grficos....................................................................................78
Displays OLED...............................................................................................80
Breve descripcin del display...................................................................81
Algoritmos.................................................................................................81
Buses serie............................................................................................................82
SPI..................................................................................................................83
Rabbit 2000 y 3000...................................................................................84
Rabbit 4000 y 5000...................................................................................86
Uso y ejemplos ........................................................................................87
Ejemplos..............................................................................................87
Conversores A/D MCP3204 y MCP3208...........................................87
Controlador de touchscreen ADS7846................................................90
I2C..................................................................................................................92
Ejemplos...................................................................................................95
viii

Memorias EEPROM y FRAM............................................................95


Processor Companion (con FRAM)....................................................98
Microwire.....................................................................................................100
Rutinas caseras........................................................................................101
Ejemplo...................................................................................................103
BL233B........................................................................................................104
Apndice: Low Voltage blue's............................................................................108
Micro de 3V, perifrico de 5V.....................................................................108
Micros 5V-tolerant..................................................................................108
Compatibilidad de niveles.................................................................108
Incompatibilidad de niveles...............................................................109
Micros que no toleran 5V en sus entradas.........................................109
Micro de 5V, perifrico de 3V................................................................111
Manejo de memoria extendida.................................................................................113
Introduccin: R2000/3000 vs. R4000/5000.......................................................113
Datos en xmem...................................................................................................113
C....................................................................................................................113
Bloques de datos: display grfico LCD..................................................113
Datos individuales...................................................................................114
Arrays y estructuras.................................................................................115
Assembler.....................................................................................................118
Datos individuales...................................................................................118
Bloques de datos.....................................................................................118
Display color OLED..........................................................................118
Display color LCD............................................................................120
Apndice: La vida despus de R4000 y DC10...................................................124
Configuracin en campo..........................................................................................127
Introduccin.......................................................................................................127
Repaso..........................................................................................................127
IDblock...................................................................................................127
User block...............................................................................................128
Guardando la configuracin...............................................................................129
Configuracin va web.......................................................................................131
Seguridad......................................................................................................131
Simultaneidad..........................................................................................139
Interfaces complejas................................................................................145
Conectividad............................................................................................................151
USB....................................................................................................................151
Introduccin a USB......................................................................................151
Por qu?......................................................................................................154
FT232...........................................................................................................154
Bluetooth............................................................................................................157
Introduccin a Bluetooth..............................................................................157
Conexin (pairing, bonding)...................................................................159
Perfiles (profiles)....................................................................................159
ix

SPP....................................................................................................159
DUN..................................................................................................159
Por qu?......................................................................................................159
Mdulos Bluetooth.......................................................................................160
ZigBee e IEEE 802.15.4.....................................................................................161
Introduccin a ZigBee..................................................................................161
Routing en una red ZigBee.....................................................................162
Comunicacin de aplicaciones en una red ZigBee..................................162
Binding..............................................................................................163
Por qu?......................................................................................................163
Mdulos XBee..............................................................................................163
XBee 802.15.4........................................................................................164
Comunicacin punto a punto.............................................................164
Red punto a multipunto con coordinador..........................................164
XBee ZB.................................................................................................165
xbee_api.lib.......................................................................................165
Equisb....................................................................................................166
RF multipunto 2,4 Ghz.......................................................................................166
Desarrollo de una biblioteca de funciones....................................................168
Encabezado de la biblioteca de funciones...............................................171
Sistema de ayuda...............................................................................171
Cdigo...............................................................................................172
Encabezados de funciones y variables globales......................................172
Sistema de ayuda...............................................................................172
Cdigo...............................................................................................173
Uso de la biblioteca de funciones.................................................................174
Ejemplos.......................................................................................................174
Master.....................................................................................................175
Slave........................................................................................................175
Comunicacin serie asincrnica.........................................................................176
Sealizacin por noveno bit..........................................................................176
Sealizacin por tiempo muerto...................................................................179
Sealizacin por caracter especial................................................................181
IrDA...................................................................................................................182
GSM...................................................................................................................183
CSD..............................................................................................................183
SMS..............................................................................................................184
Mdulos SIMCOM.................................................................................185
Seleccin del formato........................................................................185
Envo de mensajes.............................................................................185
Recepcin de mensajes......................................................................185
Ejemplos............................................................................................186
GPRS............................................................................................................189
Mdulos SIMCOM.................................................................................189
Modo transparente.............................................................................191
x

File Systems.............................................................................................................193
Introduccin.......................................................................................................193
FS2.....................................................................................................................193
Eleccin del soporte fsico............................................................................194
Particin y formateo.....................................................................................195
Utilizacin normal........................................................................................196
Modificacin del BIOS.................................................................................197
Ejemplos.......................................................................................................198
Creacin del FS2 en un mdulo con dos chips de flash..........................199
Creacin del FS2 en un mdulo con un chip de flash.............................199
Creacin del FS2 en la flash principal, en un mdulo con dos chips de
flash.........................................................................................................199
Creacin del FS2 en RAM......................................................................200
Escritura de un log..................................................................................201
Lectura de un log.....................................................................................202
Escritura de un log de longitud fija.........................................................202
Afinacin (tuning) y uso avanzado del FS2..................................................204
Tamao de particin y de sectores..........................................................204
Acceso a diferentes particiones...............................................................206
Cantidad de archivos y de particiones.....................................................206
FAT....................................................................................................................206
Acceso a diferentes dispositivos y particiones.............................................207
Particin y formateo.....................................................................................208
Utilizacin normal........................................................................................209
Dispositivos removibles................................................................................211
Tarjetas xD..............................................................................................211
Tarjetas SD.............................................................................................212
Ejemplos.......................................................................................................212
Creacin de FAT.....................................................................................213
Escritura de un log..................................................................................213
Lectura de un log.....................................................................................214
Configuracin de FAT..................................................................................215
Networking...............................................................................................................217
Introduccin.......................................................................................................217
Switches, bridges, hubs, routers.........................................................................217
Hub...............................................................................................................218
Switch...........................................................................................................218
Routing, router..............................................................................................219
ISP...........................................................................................................220
Dial-up...............................................................................................220
xDSL (ADSL y equivalentes)............................................................220
Cable modem.....................................................................................221
IP Routing, DNS, direcciones............................................................................221
Analoga cotidiana........................................................................................222
DDNS...........................................................................................................223
xi

Proveedores.............................................................................................223
Firewall.........................................................................................................224
Proxy.............................................................................................................225
Receta para agregar nuevos dispositivos......................................................226
Red con administrador............................................................................226
Red sin administrador.............................................................................226
Firewall..............................................................................................227
Router + NAT....................................................................................227
DNS...................................................................................................227
Dispositivos.......................................................................................228
Resolucin de problemas (troubleshooting).................................................228
Aplicaciones y particularidades..........................................................................230
UDP broadcast..............................................................................................230
Desconexiones y sockets abiertos.................................................................230
TCP.........................................................................................................230
UDP.........................................................................................................230
Timing, entrega a domicilio, ancho de banda...............................................231
UDP.........................................................................................................231
TCP.........................................................................................................231
Wi-Fi..................................................................................................................232
Identificacin................................................................................................233
Seguridad......................................................................................................233
Personal vs. Enterprise............................................................................234
Wireless bridging..........................................................................................235
GSM...................................................................................................................235
CSD..............................................................................................................236
PPP sobre CSD.......................................................................................236
GPRS............................................................................................................237
IP sobre GPRS........................................................................................238
PPP sobre GPRS.....................................................................................238
Tethering............................................................................................................239
Networking con Rabbit............................................................................................241
Introduccin.......................................................................................................241
Direcciones IP....................................................................................................241
Resolucin por DNS.....................................................................................241
UDP....................................................................................................................243
Checksum......................................................................................................244
Ejemplos.......................................................................................................245
TCP....................................................................................................................245
Deteccin de interrupcin en la conexin....................................................246
Cliente TCP..................................................................................................247
Implementacin.......................................................................................248
Inicio de la conexin.........................................................................248
Espera de la aceptacin y establecimiento de la comunicacin........248
Transferencia de datos.......................................................................249
xii

Fin de la conexin.............................................................................249
Espera para reiniciar la conexin (opcional).....................................249
Ejemplos.................................................................................................250
Servidor TCP................................................................................................253
Implementacin.......................................................................................253
Inicio del servicio..............................................................................253
Espera de un pedido de conexin, y establecimiento de sta............254
Transferencia de datos, Fin de la conexin.......................................254
Ejemplos.................................................................................................254
Mltiples conexiones en un mismo port.................................................256
Zserver................................................................................................................257
"Archivos" en xmem agregados de forma dinmica.....................................259
Archivos en file systems...............................................................................259
Servidor HTTP...................................................................................................260
Imgenes generadas en tiempo de ejecucin................................................261
Imgenes que no aparecen............................................................................263
Archivos servidos desde file systems............................................................264
Autenticacin................................................................................................265
Usuarios..................................................................................................267
Archivos y variables................................................................................267
"Archivos" agregados en forma dinmica.........................................268
Archivos en file systems, agregados en forma dinmica...................268
HTTP upload................................................................................................269
Actualizacin dinmica de variables: AJAX................................................271
El cdigo JavaScript...............................................................................272
El servidor Rabbit...................................................................................272
Grficos: JavaScript, Flash...........................................................................272
Cliente HTTP.....................................................................................................273
Cliente FTP........................................................................................................274
Archivos en file systems...............................................................................277
Servidor FTP......................................................................................................279
"Archivos" en xmem.....................................................................................280
Estticos..................................................................................................280
Dinmicos...............................................................................................281
Archivos en FS2...........................................................................................282
Escritura........................................................................................................286
Archivos en FAT..........................................................................................287
Servidores FTP y HTTP combinados................................................................290
Autenticacin en cliente SMTP..........................................................................291
Direcciones IP fijas............................................................................................292
Usando TCPCONFIG...................................................................................293
Direcciones IP dinmicas...................................................................................293
DHCP............................................................................................................293
Fallback...................................................................................................294
DDNS...........................................................................................................294
xiii

Cambio de direccin IP................................................................................294


Cambio a DHCP......................................................................................295
con fallback.......................................................................................295
Sockets abiertos......................................................................................295
PPP...............................................................................................................296
Usando TCPCONFIG.............................................................................298
PPP sobre CSD.......................................................................................298
PPP sobre GPRS.....................................................................................299
Mdulos SIMCOM...........................................................................299
PPP sobre GPRS va Bluetooth (DUN) (tethering)................................299
KCWirefree.......................................................................................300
PPPoE...........................................................................................................302
Redirecciones................................................................................................303
Cambio de parmetros de red en funcionamiento.........................................303
Ejemplo: cambio desde una pgina web.................................................303
Configuracin....................................................................................304
Aplicacin de los cambios.................................................................304
Programa principal: inicializacin de la interfaz...............................305
Seguridad...........................................................................................306
Mltiples interfaces............................................................................................310
Resolucin de problemas (troubleshooting).......................................................311
Herramientas.................................................................................................311
Link.........................................................................................................311
Network...................................................................................................311
Modo debug.......................................................................................311
En la aplicacin.................................................................................312
Transporte y aplicacin...........................................................................313
Modo debug.......................................................................................313
En la aplicacin.................................................................................313
Wi-Fi..................................................................................................................314
Mdulos con capacidad Wi-Fi......................................................................314
Configuracin..........................................................................................314
Conexin abierta................................................................................315
Conexin protegida...........................................................................315
Inicio.......................................................................................................316
Cambio de parmetros en campo............................................................316
Mdulos sin Wi-Fi incorporado ..................................................................318
RabbitWeb...............................................................................................................319
Introduccin.......................................................................................................319
Extensiones a Dynamic C...................................................................................319
Grupos de usuarios.......................................................................................319
Variables.......................................................................................................320
Variables con listas de valores................................................................321
Deteccin de cambios...................................................................................321
Lenguaje script...................................................................................................322
xiv

Funciones......................................................................................................323
Selectores................................................................................................324
Botones...................................................................................................325
Desarrollo de aplicaciones.......................................................................................327
Introduccin.......................................................................................................327
Transporte de comunicaciones serie asincrnicas mediante TCP/IP.................327
Introduccin..................................................................................................327
Anlisis.........................................................................................................328
Desarrollo.....................................................................................................330
Serie a TCP.............................................................................................332
TCP a serie..............................................................................................333
Mantenimiento de la conexin................................................................333
Cliente...............................................................................................333
Servidor.............................................................................................334
Programa principal..................................................................................334
Otros protocolos......................................................................................335
Protocol Spoofing..............................................................................335
Gateway.............................................................................................337
Conversin de velocidad...................................................................337
Latencia: algoritmo de Nagle, flush..................................................338
Tarea para el hogar.......................................................................................339
Expandiendo a ms puertos simultneos...........................................339

xv

Prefacio

La idea de escribir un segundo libro sobre el tema surge por dos motivos
fundamentales. El primero es la necesidad de mantener una referencia rpida de una
gran 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 desde
otro ngulo. No es muy fcil la expresin cuando el limitante es el lenguaje, y es
necesario construirlo primero para poder utilizarlo despus.
"Desarrollo con procesadores y mdulos Rabbit" presenta los temas y da ejemplos,
generalmente simples; la premisa fundamental es la fcil y rpida comprensin de
los conceptos. Avanzamos elaborando y construyendo conceptos; donde no hay un
ejemplo, est toda la informacin 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 slo 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 ms fcil profundizar y fundamentalmente abordar cada anlisis con un
enfoque ms prctico. Avanzamos planteando soluciones a problemas comunes que
todo desarrollador se encuentra, con la tranquilidad que nos da el tener la teora ya
medianamente resuelta.
Dice el poeta que no lo hay, que se lo hace al andar. Sea tal vez este libro algunas
estelas del mo.

Segunda edicin
En esta segunda edicin nos encontramos con un camino reestructurado. La
direccin en que ha avanzado el conejo me oblig a descartar contenido de la
edicin 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 contenido
para stos. Particularmente, lo relacionado a Wi-Fi.
No se trata de una reescritura, no se trata de un agregado de apndices, ha sido una
actualizacin. Qu pasar en el futuro? Vaya la incertidumbre del hombre, el
tiempo dir.
Sergio R. Caprile, marzo de 2010

xvii

Assembler

Introduccin
Vamos a comenzar analizando las distintas formas de que disponemos para trabajar
en assembler. Hemos visto la arquitectura de Rabbit en el libro "Desarrollo con
procesadores y mdulos Rabbit", al cual nos referiremos cariosamente, de aqu en
ms, como "el libro introductorio". A modo de introduccin, haremos un breve
repaso de los modos de direccionamiento de que disponemos, y luego analizaremos
la forma de trabajar con assembler en bloques sueltos, independientes, o como
porciones de cdigo embebido dentro de una funcin en C.
Aquellos lectores no interesados en absoluto en el control de la ejecucin a este
nivel, pueden saltear este captulo de forma segura. Sin embargo, est aqu por una
razn, y a menos que haya algo ms interesante o remunerativo en que invertir el
tiempo, la sugerencia es analizarlo; antes de descartar la informacin es conveniente
conocerla primero...

Repaso
Rabbit 2000 y 3000
El core Rabbit 2000 y 3000 tiene el mismo set de registros que el Z80, con algunas
menores 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 de
16-bits y funcionamiento como punteros para direccionamiento indirecto. Los
registros ndice (IX, IY) son de 16-bits y funcionan como punteros en las
instrucciones de direccionamiento indexado con offset de 8-bits. Algunas otras
instrucciones 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 como
puntero) e indexado. El acumulador, o registro A, es siempre una de las fuentes y
destino obligado de todas las operaciones aritmticas de 8-bits; puede cargarse
adems mediante direccionamiento directo extendido e indirecto (utilizando adems
BC o DE como punteros). Los registros de 16-bits pueden cargarse mediante
direccionamiento inmediato extendido, directo extendido, e indirecto (respecto al
SP). El par HL puede cargarse mediante direccionamiento indexado respecto a SP
1

Assembler
(aunque Rabbit llama a este modo relativo1), a l mismo, o a los ndices. HL es en
efecto una especie de acumulador para operaciones lgicas y aritmticas de 16-bits.
Una caracterstica interesante del set de instrucciones son las instrucciones para
mover 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 posicin apuntada por DE, el primero de cabeza a cola y el
segundo cola a cabeza. Otra instruccin de auto repeticin es DJNZ (Decrement and
Jump if Not Zero) que decrementa el registro B y ejecuta el salto relativo mientras B
sea distinto de cero, y se utiliza para repetir un loop tantas veces como indica el
registro B.
El core ejecuta instrucciones en slo dos clocks por cada byte de opcode y cada
byte de acceso a memoria. Las operaciones de escritura demandan tres clocks, y se
requiere un clock adicional si debe computarse una direccin de memoria o se utiliza
alguno de los registros ndice para direccionar.
Rabbit utiliza un interesante esquema para acceder dispositivos de entrada/salida.
Cualquier instruccin que acceda a memoria, puede utilizarse para acceder a uno de
dos espacios de I/O: uno interno (perifricos en chip) y otro externo (perifricos
externos). Esto se realiza anteponiendo a la instruccin un prefijo. Mediante esta
posibilidad, todas las instrucciones de acceso a memoria en 16 bits estn disponibles
para leer y escribir posiciones de I/O. La unidad de mapeo de memoria no se utiliza
para I/O, por lo que se dispone de un direccionamiento lineal de 16 bits.

Rabbit 4000 y 5000


Adems de lo visto para Rabbit 2000 y 3000, la CPU Rabbit 4000 y 5000
incorpora nuevas instrucciones con soporte para loops largos y nmeros de 16-bits,
generando mayor densidad de cdigo. Dispone de instrucciones como DWJNZ
(Decrement Word and Jump if Not Zero) y JRE (Jump Relative Extended), versiones
16-bits de DJNZ (loop counter por excelencia) y JR, respectivamente.
Entre los registros de propsitos 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 de
incorporar dos registros de 32-bits de propsitos generales, sobre los cuales se
pueden hacer algunas operaciones aritmticas, lgicas, 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 lgica como fsica, existen
instrucciones de "carga lejana" (LDF: LoaD Far) que permiten utilizar
direccionamiento directo en memoria fsica, tanto en 8, 16, 32-bits.
1

El offset es siempre positivo, por lo que se acerca ms a un indexado

Bloques independientes

Bloques independientes
La forma de trabajar en assembler solo, es decir, sin estar vinculado a cdigo en C,
es declarar un bloque:
#asm
; cdigo assembler aqu...
#endasm

Dentro del bloque, podemos acceder a variables estticas externas. Nos referimos a
ellas por su nombre, y el mismo ser traducido a su direccin en memoria por el
compilador:
static int pepe;
#asm
ld HL,(pepe)
...
ld (pepe),HL
...
ld HL,pepe
ld c,(HL)
inc HL
ld b,(HL)
...
ld HL,BC

; HL = pepe
; pepe = HL
; HL = &pepe
; BC = pepe
; valor de retorno en HL

#endasm

Depuracin
La forma de depurar un bloque de cdigo assembler independiente, es agregar
debug en la definicin del bloque:
#asm debug
ld a,b
ld b,c
#endasm

El inconveniente es que esto inserta instrucciones RST282 entre cada lnea


assembler, lo cual altera notablemente el tiempo de ejecucin y lo limita a depurar
slo aplicaciones que no requieran de operacin a alta velocidad; que suele ser
justamente la razn por la cual usaramos assembler...
ld a,b
RST28
ld b,c
RST28

Rabbit 4000 y 5000


2

El sistema de debugging en circuito se basa en la insercin de instrucciones de restart para generar


llamadas a una posicin fija en la que el BIOS reporta al entorno de desarrollo y permite el control de
la ejecucin.

Assembler
Estos micros incluyen siete registros para utilizar como hardware breakpoints. Seis
de ellos estn disponibles y pueden colocarse en cualquier direccin, incluso para
detectar accesos a memoria.
Un hardware breakpoints puede utilizarse para detener la ejecucin en un punto,
pero la ejecucin paso a paso requiere de software breakpoints (instrucciones
RST28).

Pasaje de parmetros
El pasaje de parmetros a una rutina en assembler depende de la inquietud de quien
la escribe, y del entorno en que se la utiliza. En Dynamic C, todos los parmetros
que deban pasarse a una funcin, se pasarn en el stack y el primero adems se
entregar en el par de registros HL o en el cuarteto BCDE, segn se trate de un
entero/caracter/puntero o un entero largo/coma flotante, respectivamente. Por ende,
si nuestra rutina en assembler ser llamada desde una funcin C, deberemos esperar
los parmetros en el stack, inmediatamente a continuacin de la direccin de retorno:
ultimo parametro
primer parametro
dir. de retorno
SP
#asm
;@sp+2= direccin en xmem (long)
;@sp+6= longitud de los datos (int)
...
ld hl, (SP+4)
; Obtiene address (long) en BC:DE
ld c,l
ld b,h
ld hl, (SP+2)
ex de,hl
...
ld hl,(sp+6)
; hl=longitud del bloque
...
#endasm

Tambin podemos aprovechar y utilizar directamente BCDE (en este caso) para el
primer parmetro.
Como sabemos, el pasaje de parmetros se realiza por valor, es decir no se pasa una
referencia sino el valor del elemento. Cuando el parmetro que se pasa es un
puntero, lo que se pasa es el valor del puntero. Cuando el parmetro que se pasa es
una estructura, se reserva el espacio en el stack como para que pueda entrar dicha
estructura, y si la funcin que se llama devuelve como resultado una estructura, antes

Bloques independientes
de llamarla se reserva espacio en el stack como para que quepa dicha estructura. El
compilador determina esto en base a las declaraciones de las funciones.
Todo esto deberemos tenerlo presente, ya que en assembler, somos nosotros
quienes debemos operar sobre el stack para obtener los parmetros y eventualmente
entregar el valor devuelto por nuestra funcin. De igual modo, si desde assembler
llamamos a una rutina que espera ser llamada desde C, deberemos colocar todos los
parmetros en el stack antes de llamarla y volverlo a la normalidad al recibir el
control nuevamente.
El valor de retorno de una rutina assembler debe encontrarse en el par HL o en el
cuarteto BCDE, segn su tipo, a menos, claro est, que se trate de una estructura,
como adelantramos. A fin de que el compilador sepa qu esperar, es menester
declarar correctamente la funcin:
int jacinto(long, int);
#asm
;@sp+2= direccin en xmem (long)
;@sp+6= longitud de los datos (int)
jacinto::
...
ld hl,(sp+6)
; hl=segundo parmetro
...
ld hl,algo
; valor de retorno
ret
#endasm

Tendremos varios ejemplos reales de funciones en C llamando a una rutina en


assembler en el captulo sobre manejo de memoria extendida.

Bloques de assembler en xmem


A menos que lo indiquemos explcitamente, los bloques de assembler se ubican en
el rea root. Si agregamos en la declaracin del bloque la directiva xmem, entonces
se ubicar en xmem. Como sabemos, el segmento xmem es una ventana de 8KB a la
totalidad de la memoria fsica, cuyo puntero de segmento es el registro XPC de la
CPU. El compilador, cuando genera cdigo en xmem, arma bloques de no ms de
4KB, de modo que siempre pueda correrse la ventana y realizar saltos dentro del
bloque. En el caso de assembler, si bien el control lo tiene quien lo escribe, el
compilador no permite bloques assembler de ms de 4K.
Un detalle muy importante cuando se trabaja con cdigo assembler en xmem, es
contemplar la posibilidad de que quien lo llama pueda recuperar el control. Como
sabemos, al ejecutar cdigo en xmem se modifica el registro XPC para apuntar a la
pgina que contiene el cdigo en cuestin. Si el cdigo que llama estaba corriendo
en xmem, deber salvar el valor de XPC antes de llamar a nuestro cdigo assembler,
y se espera que ste lo recupere al realizar el retorno. Ambas operaciones se realizan
mediante dos instrucciones:
#asm xmem
pepe::

Assembler
...
LRET

; recupera PC y XPC del stack

#endasm
...
#asm xmem
...
LCALL pepe
...
LRET
#endasm

; salva XPC y PC en el stack,


; carga nuevo XPC y salta a 'pepe'
; recupera PC y XPC del stack

Un pequesimo detalle a tener en cuenta si se pasan parmetros en el stack, es que


la direccin de retorno incluye el registro XPC, por lo que los parmetros estn
ahora de SP+3 en adelante.
ultimo parametro
primer parametro
dir. de retorno

SP

Assembler encapsulado en C
Si necesitamos slo un pedacito de assembler para resolver un problema que en C
es ms complicado, o queremos, por ejemplo, operar sobre la prioridad de ejecucin,
podemos embeber un trozo de cdigo en assembler dentro de una funcin en C, de
forma similar a cuando declaramos funciones en assembler:
int maifunction(int par1, int par2)
{
int i,var1;
#asm
ipset 1
#endasm
for(i=0;i<10;i++) {
...
}
#asm
ipres
#endasm
return(var1);
}

Assembler encapsulado en C
Un ejemplo real de assembler encapsulado en una funcin C puede observarse ms
adelante en el cdigo que lee un encoder con R2000 y luego en el que lee el mdulo
de captura de pulsos en R3000; ambos en el captulo sobre perifricos internos.

Acceso a variables locales y parmetros


Otra ventaja de poder encapsular assembler dentro de una funcin en C, es que
podemos acceder directamente a las variables locales dinmicas (internas) y a los
parmetros de llamada de la funcin, cosa que en un bloque suelto de assembler nos
obliga a tener que llevar la cuenta del stack, como viramos en el apartado anterior.
Aqu, por ejemplo, podemos acceder fcilmente a estas variables mediante un
pseudo direccionamiento relativo al registro SP, con un offset dado por el nombre de
la variable. Dicho direccionamiento no existe en la realidad, sino que es el
compilador quien resuelve esto y traduce una versin mnemnica al offset dentro del
stack. As, en el ejemplo anterior, si queremos acceder al parmetro par1, hacemos:
ld hl,(SP+@SP+par1)

El prefijo @SP indica el tamao de la porcin de stack donde estn las variables de
tipo auto locales, y entonces @SP+par1 sera el offset necesario dentro del stack
para que el direccionamiento indexado o relativo respecto de SP acceda a dicha
variable, sin tener que calcularlo a mano; es decir, esa instruccin se traducir al
compilar como:
ld hl,(SP+6)

o el valor al que resuelva @SP+par1, dependiendo de la cantidad de parmetros,


variables locales, y a cul nos referimos; como puede observarse en el diagrama3:

ultimo parametro
primer parametro
dir. de retorno
primera variable
@SP
ultima variable

SP

El diagrama muestra un espacio para dos bytes en la direccin de retorno, lo cual corresponde a una
funcin C en rea root. Para una funcin en xmem, el espacio en el stack ocupado por la direccin de
retorno es de tres bytes, como viramos en un apartado anterior.

Assembler
De igual modo, podemos acceder a una variable local:
ld hl,(SP+@SP+var1)

Por ejemplo, en la funcin anterior:


int maifunction(int par1, int par2)
{
int i,var1;
#asm
ipset 1
#endasm
for(i=0;i<10;i++) {
...
#asm
ld hl,(SP+@SP+par1)
...
ld hl,(SP+@SP+par2)
...
ld (SP+@SP+var1),hl
...

; HL = par1
; HL = par2
; var1 = HL

#endasm
...
}
#asm
ipres
#endasm
return(var1);
}

Por supuesto que tambin podemos acceder a variables estticas, tanto internas
como externas. En este caso, nos referimos a ellas por su nombre, y el mismo ser
traducido a su direccin en memoria por el compilador:
static int pepe;
char maiaderfunction(void)
{
auto int i;
static char lalala;
for(i=0;i<10;i++) {
...
#asm
ld hl,(pepe)
...
ld hl,lalala
ld a,(HL)
...
ld (lalala),a
ld(pepe),hl
...
#endasm
...
}
return(lalala);
}

; HL = pepe
; HL = &lalala
; A = *HL = lalala (dem)
; lalala = A
; pepe = HL

Assembler encapsulado en C

Depuracin
Una utilidad adicional de poner assembler encapsulado en C, es la posibilidad de
insertar breakpoints en el cdigo o ejecutar porciones del mismo por pasos. Para
lograr esto, lo que hacemos es introducir una sentencia C en medio del bloque de
assembler, un simple ; por ejemplo, y entonces el compilador introduce una
instruccin RST28 en ese punto solamente, permitindonos ejecutar o poner un
breakpoint 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
c

ld
ld
;
ld
ld

hl,22
a,(hl)
// esto es una sentencia C vaca
b,a
a,(IX+5)

#endasm
}
}

Existe adems una opcin adicional: IX frame, que permite acceder a


parmetros utilizando el registro IX como ndice de una tabla conteniendo
parmetros. Sin embargo, dado que tanto la operatoria como los tiempos
ejecucin no difieren demasiado del uso de SP que hemos descripto,
ahondaremos en este tema.

los
los
de
no

Sentencias C dentro de bloques assembler


Como seguramente habrn notado en el ltimo ejemplo, es posible incluir una
sentencia en C dentro de un bloque assembler; sea ste independiente o encapsulado
en C. S, es posible tener C encapsulado en assembler encapsulado en C. Por suerte
la recursividad termina all.
La sentencia C debe ser algo de una sola lnea, dado que se debe terminar con
punto y coma. Sin embargo, el grado de complejidad que un programador de C
entonado puede lograr en una sola lnea escapa a mi imaginacin.
La utilidad principal es mantener la potencia de assembler, sin perder la de C, es
decir, si en un bloque assembler necesitamos leer o modificar el valor de una
variable, o esperar por semforos, es mucho ms sencillo hacerlo en C. De hecho es
lo que hacen algunas de las rutinas de inicializacin del sistema.
Los siguientes son ejemplos carentes de toda utilidad, cuya nica funcin es dar
una idea de lo que se puede hacer, y demostrar la sintaxis:

Assembler
unsigned char busy;
#asm
isr1::
c

...
busy=1;
ipset 0
...
ipres
busy=0;
...
ret

#endasm
#asm
isr2::

chau:

push af
ld a,(busy)
and a
jr nz,chau
...
pop af
ipres
ret

#endasm
#asm
code::
c
c
c

while(busy);
busy=1;
ld hl,...
...
busy=0;
ret

#endasm

Una utilidad realmente interesante es que es posible resetear un watchdog virtual


desde assembler de forma muy simple:
int ID;
#asm
lup::

...
c

VdHitWd(ID);
...
jr nz lup
ret

#endasm
main()
{
ID = VdGetFreeWd(5);
while(1)
lup();

/* inicializa un VWDT */

Como puede observarse, es idntico a lo que haramos en C, como veremos ms


adelante, en el captulo sobre perifricos internos:
int ID;

10

Sentencias C dentro de bloques assembler

main()
{
ID = VdGetFreeWd(5);
while(1){
VdHitWd(ID);
...
}

/* inicializa un VWDT */

11

Interrupciones

Introduccin
Continuamos nuestro anlisis antes de volcarnos de forma ms decidida en los
ejemplos y aplicaciones, con las interrupciones, su arquitectura en el core, y algunos
ejemplos prcticos de como aprovecharlas.

Repaso
Recordemos que el core tiene cuatro niveles de prioridad de ejecucin. El nivel ms
bajo se destina a la ejecucin normal, y los otros tres niveles suelen adjudicarse de
acuerdo a la prioridad requerida por las diversas rutinas de interrupciones, segn la
criticidad del perifrico o la operacin en cuanto a latencia, etc. Una interrupcin
slo se tomar en cuenta si la prioridad de la misma es superior a la prioridad a la
que se encuentra funcionando en ese momento la CPU. La latencia mxima queda
establecida por la secuencia ms larga de instrucciones privilegiadas1, como
analizramos en el libro introductorio.
Como ya sabemos, Rabbit emplea offsets fijos para cada interrupcin. Segn sta
sea causada por un dispositivo interno o externo, la CPU provee la direccin base en
el registro IIR o EIR respectivamente, y el perifrico determina el offset. Cada
perifrico interno tiene un offset fijo para cada necesidad, y los perifricos externos
disponen de pines separados para su identificacin. Estas tablas de direcciones deben
mapear en el rea root, debido a que se necesita transferir el control a un rea de
cdigo con una posicin fija en el mapa de memoria. Dado que cada offset est a
unos diecisis bytes del anterior y del siguiente, es altamente probable que una rutina
de interrupciones no quepa en ese espacio, lo que se subsana transfiriendo la
ejecucin a otra regin de memoria. Debe tenerse presente, sin embargo, que si se
realizan saltos a xmem, el registro XPC deber ser salvado en el stack y luego
recuperado al finalizar la rutina, para permitir el retorno a la direccin de ejecucin
interrumpida sin alterar el mapeo de memoria.
Recordemos tambin que la prioridad de ejecucin es establecida al aceptarse una
interrupcin, segn indica el perifrico que interrumpe en uno de sus registros de
control. Tambin puede ser seteada manualmente con la instruccin IPSET n. En
cualquier caso, prioridades sucesivas van siendo almacenadas en el stack de cuatro
posiciones contenido en el registro IP; el cual puede ser desplazado a la derecha,
1

Una interrupcin es atendida al final de la instruccin en curso, excepto que sta sea una de las
instrucciones privilegiadas, las cuales difieren la atencin de las interrupciones a la instruccin
subsiguiente.

13

Interrupciones
restableciendo la prioridad anterior, mediante la instruccin IPRES; y puede ser
salvado y recuperado del stack mediante PUSH IP y POP IP. La prioridad actual del
procesador se ubica en los dos bits menos significativos del registro IP. Al momento
de producirse la interrupcin, este registro es desplazado a la izquierda dos
posiciones y los dos bits menos significativos se llenan con el valor de la prioridad
de la interrupcin en curso. Esto resulta en que una rutina de interrupciones slo
puede ser interrumpida por otra de mayor prioridad (a menos que el programador la
disminuya explcitamente).
Cuando deseamos atender interrupciones de un determinado perifrico interno con
capacidad de interrupcin, indicamos el interrupt handler de la siguiente forma:
SetVectIntern(num, my_isr);

// setea la direccin
// del handler para num

El parmetro num es el nmero de offset2 dentro de la tabla apuntada por el registro


IIR:

14

Perifrico (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

Existen adems offsets para la interrupcin peridica y las instrucciones de restart: RST18, RST20 y
RST28. stos han sido deliberadamente omitidos dado que son mayormente empleados por Dynamic
C y no se aconseja interferir, a menos que se sepa claramente lo que se hace.

Introduccin
Perifrico (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 tambin de sendos


offset 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 interrupcin.
Cuando deseamos atender interrupciones externas, indicamos el interrupt handler
de la siguiente forma:
SetVectExtern3000(int, my_isr);
SetVectExtern2000(1, my_isr);
SetVectExtern(int, my_isr);

// int= 0 1
// slo para R2000 original (IQ2T)
// R4000/5000

Si compartimos un offset entre diversas rutinas, o por algn otro motivo


necesitamos conocer la direccin del interrupt handler que est configurada para un
determinado vector, empleamos la siguiente funcin:
GetVectIntern(num);
GetVectExtern3000();
GetVectExtern2000();
GetVectExtern();

//
//
//
//

para perifricos internos


para interrupciones externas
slo para R2000 IQ2T
R4000/5000

En Dynamic C, indicamos que una funcin es en realidad un interrupt handler de la


siguiente forma:
nodebug root interrupt void my_isr()
{
// interrupt handler
}

En particular, la palabra clave (keyword) interrupt ocasiona la insercin de cdigo


que salva todos los registros en el stack (inclusive los alternativos), por lo que es
altamente probable que un interrupt handler en assembler sea ms compacto, ya que
podemos 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 registros
utilizados o modificados, y restablecer la prioridad de ejecucin (registro IP). Un
interrupt handler tpico en rea root es el siguiente:
#asm root
my_isr::
; cdigo del handler propiamente dicho
IPRES
; restablece la prioridad del procesador
; antes de la interrupcin
RET
; devuelve el control del procesador

15

Interrupciones
#endasm

Dado que existen cuatro niveles de operacin posibles, y una interrupcin slo es
aceptada si su prioridad es mayor al nivel de operacin, el stack de cuatro posiciones
llamado registro IP es suficiente como para guardar una escalada de interrupciones
anidadas. Sin embargo, si en algn interrupt handler el programador explcitamente
disminuye la prioridad para permitir interrupciones de igual jerarqua, podra
producirse (dependiendo del sistema y de si existen fuentes de interrupcin con
prioridades elevadas) una escalada que ocasione la prdida de la prioridad de
ejecucin original. En este caso, lo que hacemos es salvar el registro IP en el stack
antes de modificar la prioridad de operacin, y recuperarlo antes de salir:
#asm root
my_isr::
PUSH IP
ipset 0
; cdigo del handler propiamente dicho
POP IP
IPRES
; restablece la prioridad del procesador
; antes de la interrupcin
RET
; devuelve el control del procesador
#endasm

Ejemplos
Interrupciones internas: buzzer en Timer B
En este ejemplo, utilizaremos la interrupcin de comparacin del Timer B para
conmutar un par de pines, generando dos seales en contrafase que bien pueden ser
transmitidas a un buzzer. 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 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 interrupcin
WrPortI(TBCR, &TBCRShadow, 0x09); // clock timer B con (perclk/16)
// prioridad de interrupcin: 1
WrPortI(TBM1R, NULL, 0x00);
// el timer interrumpe cuando
WrPortI(TBL1R, NULL, 0x89);
// el contador llega a 0089
taimer=0x089;

16

Ejemplos
WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupcin
// por match en B1
while(1);
}
#asm
timerb_isr::
push af
ioi ld a, (TBCSR)
push hl
ld hl,(taimer)
push bc
ld bc,0x0089
add hl,bc
ld (taimer),hl
ld a,h
rrca
rrca
ioi ld (TBM1R), a
ld a,l
ioi ld (TBL1R), a
ioi ld a,(PEDR)
xor 0x82
ioi ld (PEDR), a
pop bc
pop hl
pop af
ipres
ret
#endasm

; salva registros
; reconoce interrupcin
; lee compare shadow
; suma 0x0089
; y guarda en shadow
; MSbs en bits 7,6
; descarta el resto de los bits
; carga en match register
;
;
;
;

int siguiente: timer+0089h


lee port E
invierte bits de LEDs (alternado)
escribe port E

; recupera registros
; restablece interrupciones
; retorna

Como vimos, la funcin SetVectIntern() es la que define la direccin del interrupt


handler para un perifrico determinado. Le pasamos como parmetros: el offset
correspondiente al Timer B y la direccin del handler. Entonces, al llegar el contador
del Timer B al valor de cuenta esperado y producirse una comparacin exitosa, ste
genera una interrupcin. El registro IIR apunta a la tabla de offsets 3, entonces en la
direccin IIR+0xB0 habr una instruccin de salto (jump) a nuestra rutina 4
timerb_isr, donde recibimos el control con el procesador corriendo a la prioridad
que especificamos al escribir en el registro TBCR. Necesitamos restablecer la
prioridad anterior antes de salir, lo cual hacemos con la instruccin IPRES. En lo
referente al Timer B, no podemos leer el valor de comparacin, pero s guardarlo en
un shadow register para tenerlo disponible. Los bits ms significativos del registro de
captura estn en los bits 7 y 6, por lo que debemos trasladar nuestros bits 1 y 0 a
dicha posicin, ignorando el resto. Con los valores indicados, el buzzer recibe 5KHz
en un mdulo con clock de 22MHz.
Tendremos otro ejemplo de interrupciones internas ms adelante, cuando
analicemos la forma de conexin de un encoder codificador en cuadratura al mdulo
3
4

Esto se maneja de forma transparente por Dynamic C, el usuario no necesita manipular (ni siquiera
tener conciencia de su existencia...) este registro o la tabla, en forma directa.
En realidad, esto actualmente es ms 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 interrupt
handler como si la tabla estuviera en RAM, haciendo que esto sea transparente, y permitiendo
simplificar la operacin de forma didctica como lo hemos hecho.

17

Interrupciones
decodificador en cuadratura de un R3000; tambin ms informacin sobre el Timer
B, todo en el captulo sobre perifricos internos.

Interrupciones externas: lector ABA (Track II)


En este ejemplo, tomamos los datos de una lectora de tarjetas RFID con interfaz
ABA (Track II). La interfaz consta de dos lneas: una de clock y una de datos. Los
datos son considerados vlidos durante el flanco descendente del reloj, por lo que
utilizaremos esta seal para generar una interrupcin al procesador y as observar la
seal 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 ms probable debido a que
hace ya algunos aos que est en el mercado, la forma de seleccionar el puerto de
interrupciones es la siguiente:
WrPortI(PEDDR, &PEDDRShadow, 0xEE);
WrPortI(PEFR, &PEFRShadow, 0x00);
SetVectExtern3000(0, my_isr);

// PE0,4=input
//
//
//
//
//

WrPortI(I0CR, &I0CRShadow, 0x13);

setea la direccin
del handler
habilita INT0 PE4,
flanco descendente
prioridad 3

Clock

PE.4

Data

PE.0

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 tcnica TN301 en que
Rabbit describe como resolver el conocido problema de ese micro.
PE.4
1K
Clock

PE.5

Data

PE.0

El software se modifica a :
WrPortI(PEDDR, &PEDDRShadow, 0xCE);
WrPortI(PEFR, &PEFRShadow, 0x00);
SetVectExtern2000(3, my_isr);

18

// PE0,4,5=input
// setea la direccin

Ejemplos

WrPortI(I0CR, &I0CRShadow, 0x13);


WrPortI(I1CR, &I1CRShadow, 0x13);

//
//
//
//
//
//

del handler y su
prioridad
habilita INT0 PE4,
flanco descendente
habilita INT1 PE5,
flanco descendente

La rutina de interrupciones que es disparada por los flancos de clock descendentes


y almacena los bits de datos es la siguiente:
my_isr::
push af
push hl
push bc
ld a,(count)
inc a
ld (count),a
ioi ld a,(PEDR)
rra
call rotateright
pop bc
pop HL
pop af
ipres
ret

; salva registros
; HL y BC usados por rotateright
;
;
;
;
;
;

counter (cuenta pulsos de clock)


incrementa
counter
lee port E
pone PE.0 en carry
ingresa bits por Msb, mueve todo el buffer

; recupera registros
; restablece interrupciones
; retorna

Para aquellos interesados en este tipo de lectores, pueden consultar en el CD con


informacin adicional la nota CAN-050. Tendremos otro ejemplo de uso de
interrupciones externas ms adelante, cuando analicemos la forma de conexin de un
encoder codificador en cuadratura a un R2000, en el captulo sobre perifricos
internos.

19

Tiempos de ejecucin

Introduccin
En un sistema que realiza solamente una tarea, generalmente no tenemos mayores
inconvenientes con el manejo de tiempos de ejecucin. Llegado el caso en que la
tarea en s requiera un manejo delicado del tiempo, siempre est el recurso de contar
ciclos de ejecucin y escribir el cdigo especficamente para resolverla. Entre los
numerosos 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
generacin del video, y el juego en s. En aplicaciones en que dos tareas estn bien
definidas, y los requerimientos de tiempo de ambas son bien claros, es posible
aprovechar los tiempos improductivos de una de ellas (retrazado vertical) para
realizar la otra. Por supuesto que tambin es posible escribir un slo cdigo
entrelazado que atienda ambas tareas a la vez, de hecho estos juegos con video por
software no pueden darse el lujo de andar regalando ciclos de CPU; sin embargo,
cuando la cantidad de tareas aumenta, la creciente complejidad e interdependencia
hace que deba buscarse una forma de concebir y escribir el cdigo que permita
controlar y mantener el funcionamiento de forma clara y eficiente, tratando de
reducir el todo a pequeas partes manejables.

Conceptos
Interrupciones
Como todos sabemos, los microprocesadores permiten interrupciones. La
particularidad de las interrupciones es que se caracterizan (como su nombre lo
indica) por interrumpir al procesador en cualquier momento, de forma indistinta,
aunque a veces pareciera que lo hacen de forma intencional sobre la tarea que ms
nos complica. La razn fundamental de su existencia es permitir que el micro pueda
atender eventos que no es conveniente estar esperando o consultando, y/o que deben
ser atendidos rpidamente y sin demoras.
Cuidadosamente utilizadas, son un poderoso aliado, y hasta es posible armar
esquemas de tareas mltiples asignando a cada tarea una interrupcin peridica
independiente. Su uso indiscriminado puede generar ms inconvenientes que
beneficios, particularmente no es posible compartir variables o subrutinas sin tomar
las debidas precauciones de accesibilidad, atomicidad y reentrabilidad. Una
21

Tiempos de ejecucin
interrupcin puede ocurrir en cualquier momento, y si ocurre en medio de la
actualizacin de una variable multi-byte puede causar (y de hecho lo hace) estragos a
las dems tareas, que quedan con valores "parcialmente alterados", que en el mundo
real son valores incorrectos, y probablemente de peligrosa incoherencia para el
software. Un ejemplo tpico y frecuentemente olvidado, son las variables de
temporizacin, o fecha y hora. El programa principal se pone alegremente a
descomponerlas byte a byte, de forma de mostrar su contenido en el display, sin
pensar que son actualizadas por interrupciones, y stas no tienen por qu ocurrir
antes o despus de nuestro acceso y no durante1. Si se muestran los valores en un
display 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, como
en el ejemplo siguiente, en el que aparece un registro con casi un ao de diferencia:
valor del RTC

valor tomado

31
12
1

05

05

23

23

59

59

59

59

evento, toma de
fecha y hora,
log

INT RTC

interrupt, cambio de segundo


01

01

01

01

06

05

00

23

00

59

00

59

LOG

Para subsanar este inconveniente, Dynamic C incorpora el concepto de variables


compartidas (shared variables). Ante un acceso o modificacin de una variable as
definida, se produce una inhibicin de las interrupciones durante el transcurso de la
misma. De hecho, las variables de temporizacin de Dynamic C estn declaradas
como shared.
Accesos reiterados a variables de tipo shared, producen inhibiciones reiteradas en
las interrupciones, lo que se traduce como variaciones en la latencia de atencin de
1

22

Una interrupcin puede ocurrir en cualquier momento, siempre que est habilitada. La probabilidad
de 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 de
manera no frecuente. Sin embargo, es mayor que cero, y por ende, en un nmero lo suficientemente
alto de repeticiones, es de esperarse que ocurra. El dejar librado el correcto funcionamiento de un
equipo a la funcin de distribucin de Poisson no debera ser considerada buena prctica de diseo...

Conceptos
interrupciones, las cuales slo pueden ser atendidas cuando est permitido. Rabbit
incorpora cuatro niveles de prioridad de ejecucin, para que el programador pueda
disponer de mayor libertad y minimizar la latencia a interrupciones crticas. Un
anlisis del problema de la latencia en generacin de seales en puertos de entradasalida se encuentra en el captulo sobre perifricos internos. Particularmente,
hacemos una demostracin del jitter en latencia de interrupciones.

Multitarea
Por multitarea entendemos la ejecucin aparentemente simultnea de varias tareas.
Es "aparentemente simultnea" porque a menos que tengamos varios procesadores o
un procesador con muchos cores y/o unidades de ejecucin, una y slo una
instruccin se ejecuta en un ciclo de mquina. Mediante algn mtodo de
conmutacin, se administra el tiempo de procesador utilizado, de modo que todas las
tareas puedan jugar un rato con l.
El hecho de conmutar o cambiar de tareas (task switching), si pretendemos que
cada tarea resulte independiente de las dems, implica salvar y recuperar el contexto
de operacin imperante en cada tarea; de modo de poder suspenderla en un
determinado instante, y poder reanudarla luego, y que dicha tarea no se entere que
fue suspendida. Este intercambio de contexto (context switching) impone un
derroche de tiempo improductivo (aparentemente), y una cierta latencia en los
tiempos asociados a la ejecucin de las tareas. A fin de poder seguir los ejemplos a
continuacin, vamos a definir tres tareas:
Tarea 1
procesar config
esperar datos
procesar datos
esperar operador
validar proceso

Tarea 2
mostrar valores en display
dormir 1 segundo

Tarea 3
esperar comando
procesar comando
entregar resultados

Multitarea cooperativo
En todo sistema multitarea cooperativo, cada tarea es "despachada"
(dispatched)cuando las dems ceden el procesador, lo cual hace que la repetitividad
de una tarea en particular no pueda ser fcilmente predicha. Si el sistema es
cooperativo, debe existir una especie de "acuerdo de buena voluntad" entre las
partes, en este caso las tareas, de ceder el procesador cada cierto tiempo para
permitir el normal desenvolvimiento de las dems. En Rabbit, Dynamic C incluye
soporte para multitarea cooperativo en forma de costates y cofunctions; aunque el
usuario siempre puede escribir su cdigo en forma de handlers y mquinas de
23

Tiempos de ejecucin
estados. Cada tarea debe detectar los ciclos de espera y ceder el procesador a las
dems, de modo que todas tengan oportunidad de ejecutarse. En caso de no existir
ciclos de espera, debern inventarse y ceder el control en puntos estratgicos, dado
que la nica forma de que todas las tareas se ejecuten, es que todas y cada una de
ellas tengan la buena voluntad de ceder el control peridicamente. Si el sistema est
bien planeado, todas las tareas parecen funcionar a mxima velocidad, dado que
operan rpidamente durante un tiempo breve, permaneciendo inactivas mientras
esperan a los eventos que determinan los cambios.
El cambio de contexto suele realizarse de una forma simple; si las tareas son
mquinas de estados, hay solamente un elemento que debe salvarse: el estado. De
esta forma, cada tarea cede el control en los estados de espera, voluntariamente, y
sabe que las variables compartidas con otras tareas slo podrn 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 temporizacin interno, por
ejemplo:
waitfor(DelayMs(timedelay));

tienen una incertidumbre propia del mismo, dado que como se utiliza un reloj que
est siempre corriendo, el ltimo dgito de la demora solicitada es incierto. Por
ejemplo, la variable MS_TIMER es actualizada a cada milisegundo, pero una
llamada a DelayMs(1) puede ocurrir unos microsegundos antes de que suceda la
actualizacin de dicha variable, con lo cual la demora es prcticamente 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 de
espera para ceder el control a otras tareas:
eventos

Tarea 1

Tarea 2

Tarea 3

tiempo

procesar config
esperar datos
mostrar valores en display
dormir 1 segundo

comando
datos

operador
1 segundo

esperar datos
procesar datos
esperar operador
esperar operador
validar proceso

dormir 1 segundo

esperar comando
procesar comando
entregar resultados

esperar comando

dormir 1 segundo
mostrar valores en display

Ntese como, al cederse el control una vez terminada una determinada operacin,
es posible determinar qu es lo que es necesario salvar del contexto (generalmente el
estado), y ceder voluntariamente el control. El inconveniente es que el tiempo de
ejecucin depende fuertemente de la buena voluntad de las otras tareas.
24

Conceptos

Multitarea de tipo preemptive


En un sistema multitarea de tipo preemptive, hay un ejecutor (task scheduler) que
toma la decisin de administrar el tiempo de procesador de forma salomnica.
Cuando llega el momento, la tarea que se est ejecutando es suspendida, y se pasa el
control a otra. La predictibilidad de los tiempos de ejecucin depende de la forma de
operacin del ejecutor y de como administra sus recursos.
El cambio de contexto suele realizarse de forma abrupta, por este motivo, lo que
debe salvarse es la totalidad de los registros del procesador, manteniendo un stack
separado para cada tarea. Todo esto suele emplear un tiempo bastante mayor a lo
que se necesita en un sistema cooperativo. La diferencia fundamental es que cada
tarea no debe prestar atencin a los ciclos de espera y puede funcionar como si
estuviera sola, aunque en realidad su velocidad de ejecucin se va reduciendo a
medida que se incorporan nuevas tareas, dado que se comparte el procesador entre
todas.
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 que
analizramos 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. En
este caso, asumimos un tiempo uniforme e idntico para todas las tareas, y adems el
sistema es lo suficientemente inteligente como para permitir la cesin del control en
ciclos de espera (lo cual siempre suele depender de la buena voluntad del
programador)
evento

Tarea 1

Tarea 2

Tarea 3

tiempo

procesar config
mostrar valores en display
comando

esperar comando

procesar config
mostrar valores en display
dormir 1 segundo

datos

procesar config
esperar datos
esperar datos
procesar datos

esperar comando
procesar comando

dormir 1 segundo

procesar comando

dormir 1 segundo

procesar comando

dormir 1 segundo

procesar comando
entregar resultados
entregar resultados
esperar comando
esperar comando

procesar datos

operador

procesar datos
esperar operador
esperar operador

dormir 1 segundo
dormir 1 segundo

esperar operador
validar proceso
1 segundo

dormir 1 segundo
mostrar valores en display
esperar comando

validar proceso
mostrar valores en display

25

Tiempos de ejecucin

Ntese como, al poder interrumpirse la tarea en cualquier momento, es preciso salvar


todo el contexto y tener especial cuidado con variables compartidas, debido a que las
mismas pueden actualizarse cuando no lo esperamos. El tiempo de ejecucin es un
avance "lento pero seguro", dado que cada tarea es interrumpida frecuentemente
pero no demorada por intervalos largos.

Sentencia Slice
En Rabbit, Dynamic C incorpora una facilidad que permite administrar el tiempo de
procesador asignado a una serie de tareas. Cada tarea se define dentro de un slice, al
que se le asigna un tamao de stack y un tiempo mximo de ejecucin en ticks2. Si la
tarea termina de ejecutarse antes de este tiempo, se contina con el slice siguiente. Si
no termina, de forma muy democrtica se la suspende y se pasa el procesador al slice
siguiente. Una vez atendidos todos los slices, se retoma la ejecucin desde el
primero, resumindolo desde donde fue interrumpido, si es que lo fue. Existe un
ejemplo excelente para visualizar esto, y es una de las samples provistas
(Samples\slice\slice02.c). En este ejemplo, tenemos dos tareas importantes, y una no
tanto. Las tareas importantes tienen un tiempo mximo de procesador asignado (500
ticks) cada una, y hay un tiempo de loop (1000 ticks). En condiciones de alta
ocupacin, es decir, cuando ambas tareas principales estn ocupadas al mximo, la
tarea en segundo plano (background) no se ejecuta; slo se le permite correr si las
tareas 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(;;) {

26

1 tick = 1/1024 seg.

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 slo
emplearon un tick, nos quedan 999 ticks para ejecutar la tarea en background. Si
corremos paso a paso, veremos el orden en que se ejecutan.
Ms all de la pauprrima utilidad de un esquema con tareas vacas, tenemos en
nuestras manos un poderoso esqueleto para desarrollar un sistema de multitarea con
prioridades y velocidades de ejecucin diferentes, incluso alterables en forma
dinmica, dado que el parmetro 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, dado
que se trata de loops infinitos (aunque la ejecucin del printf() ya debera llamarnos
la atencin 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

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

Tambin es posible aplicar tcnicas de buena vecindad en estos casos, y ceder


voluntariamente el control, dado que waitfor y yield pueden utilizarse dentro de un
slice. En el ejemplo siguiente, que es una modificacin del primer ejemplo,
slice02.c, el primer slice tiene un ciclo de espera de 10 segundos (lo cual es
totalmente intil pero permite ejecutar paso a paso y ver como se distribuye la
ejecucin entre la tareas), pero podra ser una espera a un evento externo; la segunda
tarea cede voluntariamente el control entre la ejecucin de sus dos subtareas (que en
este caso es la misma por simplicidad), que a su vez son cofunctions y tienen ciclos
de espera. La ejecucin paso a paso de dicho ejemplo permite observar claramente
los 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

Conceptos

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

De esta forma, combinando recursos de uno y otro mundo, es posible lograr


complejos 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 ejecucin se
desarrollan directamente sobre el hardware, o empleando RTOS (Real Time
Operating Systems). Los RTOS incluyen complicados sistemas para garantizar una
mnima latencia y permitir que las tareas se desarrollen dentro de lmites
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 incluye
como un mdulo de Dynamic C.

Recursos
Si una aplicacin requiere alta predictibilidad en los tiempos de ejecucin y no
tolera las variaciones de timing introducidas por las interrupciones y la multitarea
cooperativa, se deber recurrir a las tcnicas tradicionales que se emplean para estos
casos, cuando no se dispone o no se quiere disponer de un RTOS:
Aprovechar el hardware para la generacin y medicin de eventos sensibles al
timing. Los puertos D y E del R2000 pueden sincronizar sus cambios con los
timers. El R3000 incorpora contadores de eventos. Analizamos ambas cosas en
el captulo sobre perifricos internos.
Atender los dispositivos o tareas crticas por interrupciones. Pueden usarse los
timers A y B para generar interrupciones peridicas. Hemos estudiado este tema
en el captulo anterior.
No poner tareas crticas dentro de loops en que otras tareas (costates, mquinas
de estados, etc.) puedan alterar sus tiempos de ejecucin.
Elevar la prioridad de ejecucin del procesador en la tarea crtica para que no
acepte interrupciones. En Rabbit, esto se realiza operando sobre el registro IP
(Interrupt Priority) del procesador.

29

Tiempos de ejecucin
Administrar la prioridad de ejecucin del procesador en diversas tareas, de

acuerdo a su grado de criticidad, para evitar introducir latencia en la o las tareas


crticas. Las tareas de criticidad intermedia pueden interrumpir a las de criticidad
baja, y ser interrumpidas por las de criticidad ms alta; pero no al revs.
Para controlar los tiempos de espera o perodos de inactividad, se dispone de
timers en forma de shared variables, como TICK_TIMER, MS_TIMER y
SEC_TIMER, las mismas son puestas a cero al arranque del sistema, y
SEC_TIMER en particular toma el valor guardado en el RTC. Los procesos que
deban depender de una cierta relacin temporal debern utilizar TICK_TIMER o
MS_TIMER, dado que SEC_TIMER podra tener que ser actualizada
manualmente ante un cambio de fecha y hora. Para observar un ejemplo,
remitirse al apartado sobre reloj de tiempo real en el captulo sobre perifricos
internos.
Debe tenerse presente que siempre se tendr una latencia para atender una
interrupcin, correspondiente a la duracin de la instruccin en curso al momento de
generarse el pedido de interrupcin (asumiendo que las interrupciones estn
habilitadas). Un anlisis de esto se encuentra, como dijramos, en el captulo sobre
perifricos internos, en el apartado sobre control por hardware de los pines del
puerto paralelo.
Particularmente en Rabbit, el sistema de debugging est siempre activo (aun
cuando no est conectado a una PC corriendo Dynamic C), lo cual introduce
llamadas peridicas al BIOS en el cdigo. Puede inhabilitarse este sistema
anteponiendo nodebug a la definicin de las funciones crticas, o directamente
definiendo #nodebug a la cabeza del archivo que contiene el programa principal
El aumentar la prioridad de ejecucin de una tarea slo garantiza que sta no ser
interrumpida, para garantizar la periodicidad o el comienzo de la misma se deber
utilizar una interrupcin o algn complejo esquema de task scheduling (asignacin
de tiempos de ejecucin entre tareas).

30

Perifricos Internos

Ports paralelo
Shadow registers
El concepto de shadow register es bastante viejo, y responde a la necesidad de
retener lo que se escribe en una posicin de I/O que no puede ser leda. Dado que no
es posible leer lo que se escribi, se debe guardar en algn lado para poder saberlo.
Recordemos que las operaciones de seteo y reseteo de bits son en realidad,
operaciones de lectura-modificacin-escritura (read-modify-write); la gran mayora
de los micros no direccionan bits sino bytes o words, y para poder setear un bit en un
registro, primero hay que leer dicho registro, setear el bit, y luego volverlo a escribir,
aunque esto se realice en una sola instruccin assembler, e incluso si esta operacin
se realiza en un ciclo de mquina. Si el registro en cuestin es en realidad un registro
de salida que no nos permite capacidad de leerlo, para poder setear un bit
necesitamos saber cmo estn los otros bits del registro, cosa que no podemos hacer.
Disponiendo de un shadow register, escribimos en ste el valor que va en el registro
de I/O y luego lo transferimos. Cuando queremos setear o resetear un determinado
bit, lo hacemos en el shadow register (que no es otra cosa que una posicin de
memoria con un nombre sombro) y lo transferimos al registro de I/O.
ld hl, PDDDRShadow
ld de, PDDDR
set 7,(HL)
ioi ldd
ret

; pone PD.7 como salida

Si nuestro micro en cuestin, como es el caso de Rabbit, permite adems lectura de


los registros de I/O, la utilidad del shadow register parecera haber caducado para
ellos. Sin embargo, existe adems 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 bidireccional
sino que tiene una u otra direccin, segn decidamos configurarlo. Por lo general,
existe un registro que decide la direccin del mismo, y otro en el que se escriben y
leen los datos. Si el micro tiene registros separados para entrada y salida (por
ejemplo 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 a
encontrarnos con un pequeo gran problema, que deberemos resolver mediante la
asistencia del shadow register.
31

Perifricos Internos
Supongamos que tenemos una tarea que hace uso de un pin de I/O en modo
bidireccional de la forma pseudo-open-collector, es decir, tiene conectada una
resistencia de pull-up, ponemos el registro de salida en cero, y lo controlamos
mediante el registro de direccin. Cuando queremos un uno, lo ponemos como
entrada, la resistencia de pull-up proveer el uno. Cuando queremos un cero, lo
ponemos 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 a
continuacin son a modo de ejemplo, lo ms probable es que el cdigo est
embebido dentro de alguna otra rutina:
setup:: ld hl, PDDR
ioi res 7,(HL)
ld a,0x00
ld (PDDDRShadow),A
ioi ld (PDDDR),A
ret
cero:

uno:

; pone en cero PD.7


; inicializa pines como entradas
; (PD.7=1)

ld hl, PDDDRShadow
ld de, PDDDR
set 7,(HL)
ioi ldd
ret

; pone PD.7 como salida

ld hl, PDDDRShadow
ld de, PDDDR
res 7,(HL)
ioi ldd
ret

; pone PD.7 como entrada

Como PDDDR es un registro que slo puede escribirse, no tenemos ms remedio


que utilizar el shadow register.
Supongamos ahora que a este equipo que tenemos andando desde hace aos, 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 inters, y no
tendra por qu modificar los bits que no le corresponden, no es cierto? Como
solamente la nueva tarea va a trabajar sobre el registro PDDR, no necesitamos
molestarnos 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, PDDR
ioi set 6,(HL)
ret
desactiva:
ld hl, PDDR
ioi res 6,(HL)
ret

; pone PD.6 en alto

; pone PD.6 en bajo

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


niusetup::

32

Quienes me conocen personalmente saben que no debera usar esta palabra...

Ports paralelo
ld hl, PDDR
ioi res 7,(HL)
ld a,0x40
ld (PDDDRShadow),A
ioi ld (PDDDR),A
ret

; pone en cero PD.7


; inicializa pines como entradas (PD.6=salida)
; (PD.7=1)

A simple vista est todo bien, pero misteriosamente, nuestra interfaz bidireccional
con 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 una
operacin read-modify-write. Cada vez que la nueva tarea decide setear el bit 6 del
registro PDDR, realiza una lectura del mismo, con lo cual lee tambin el bit 7 (y el 5,
4, ...). A continuacin, 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.7
estaba 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, sigue
siendo uno, despus de todo era una entrada y tiene un pull-up; pero cuando nuestra
vieja y querida rutina intenta ponerlo en cero configurndolo como salida, no hace
otra cosa que forzarlo en uno rabioso, dado que el bit set escribi un uno en el bit 7
de PDDR.
PD.7

oh, no...

PD.6

Tarea 1
Tarea 2

activa: ld hl, PDDR


ioi set 6,(HL)

cero:

; tarea 2 (podra ser 'desactiva' indistintamente)


; pone PD.6 en alto y los dems en lo que ve
; que estn 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
ld hl, PDDDRShadow ; tarea 1
ld de, PDDDR
set 7,(HL)
; pone PD.7 como salida, pero PDDR.7=1
ioi ldd
; oops, fall !

33

Perifricos Internos
...

La solucin, si no podemos darnos el lujo de invertir en un shadow register, es


volver a escribir un cero cada vez que vamos a operar sobre PD.7 para ponerlo en
estado bajo, sin importar lo que otras tareas nos hayan hecho:
cero:

uno:

ld hl, PDDR
ioi res 7,(HL)
ld hl, PDDDRShadow
ld de, PDDDR
set 7,(HL)
ioi ldd
ret
ld hl, PDDDRShadow
ld de, PDDDR
res 7,(HL)
ioi ldd
ret

; pone PD.7 = 0
; pone PD.7 como salida

; pone PD.7 como entrada

No obstante, lo correcto sera utilizar shadow registers y respetar a las dems tareas,
particularmente cuando la asincronicidad entre stas puede causar que se modifique
un registro en medio de una operacin, cuando no se espera que pueda haber sido
modificado. Supongamos que la tarea 2 se ejecuta por interrupciones (o tal vez
dentro de un slice2), y que por obra y gracia de la probabilidad distinta de cero, luego
de un largo tiempo de estar funcionando todo sin problemas, la interrupcin que pasa
el control a la tarea 2 ocurre en medio de la tarea 1, justo despus de que se pone
PD.7 en cero:
PD.7

oh, no...

PD.6

Tarea 1
Tarea 2
(IRQ)

cero:

ld hl, PDDR
ioi res 7,(HL)

; pone PD.7 = 0 (pero sigue siendo entrada)

INTERRUPCIN -->
...

34

Hemos analizado los bloques slice en el captulo sobre tiempos de ejecucin en el apartado sobre
multitarea de tipo preemptive.

Ports paralelo
activa: ld hl, PDDR
ioi set 6,(HL)
...
ipres
ret
RETORNO <--

; tarea 2 (podra ser 'desactiva' indistintamente)


; pone PD.6 en alto y los dems en lo que ve
; vi PD.7=1, ergo escribe PD.7=1

ld hl, PDDDRShadow
ld de, PDDDR
set 7,(HL)
ioi ldd
...

; pone PD.7 como salida, pero PDDR.7=1


; oops, fall !

Por esta razn, no slo debemos emplear un shadow register, sino que el
movimiento del shadow register al port de I/O lo hacemos de forma atmica, es
decir, mediante una instruccin que no puede ser interrumpida, como analizramos
en el libro introductorio.
activa: ld hl, PDDRShadow
ld de, PDDR
set 6,(HL)
ioi ldd
ret
desactiva:
ld hl, PDDRShadow
ld de, PDDR
res 6,(HL)
ioi ldd
ret

; pone PD.6 en alto en shadow


; transfiere a I/O: (HL) -> (DE)

; pone PD.6 en bajo en shadow


; transfiere a I/O: (HL) -> (DE)

La otra opcin es inhibir las interrupciones cada vez que se opera sobre el port,
pero generalmente esto se paga en latencia, tema que desarrollaremos en el apartado
siguiente.

Cambio de pines por hardware


Siguiendo con el anlisis del ltimo caso presentado en el apartado sobre shadow
registers, tenemos un ejemplo similar. El problema que proponemos es muy parecido
al ejemplo anterior, pero dado que el efecto se produce al revs, es generalmente
pasado por alto.
Supongamos que tenemos, en este caso, un ejemplo similar al que hemos visto en el
captulo sobre interrupciones, con el Timer B; en el cual una rutina de interrupciones
modifica el estado de uno o varios pines para controlar un buzzer. Si bien en estos
ejemplos utilizamos un buzzer por su simpleza, el mismo anlisis bien puede valer
para cualquier tipo de tarea que presente una periodicidad de interrupciones, en la
cual una diferencia notable en la latencia de atencin de las mismas es perceptible.
Supongamos tambin, que nuestro programa principal hace uso del mismo port de
I/O que la rutina de interrupciones, supongamos que no podemos partir nuestra
operacin en varias elementales (como por ejemplo alterar los bits de a uno), todos
los bits deben cambiar a la vez, pero como no hay ningn pin que se utilice como
bidireccional, creemos "estar a salvo". Nuestro programa principal, entonces, no
35

Perifricos Internos
emplea shadow registers ni maneja el puerto de I/O de forma atmica, de la siguiente
forma:
ioi ld A,(PEDR)
and 0xEE
ioi ld (PEDR),A

Supongamos (finalmente), que una interrupcin del Timer B cae justo en el medio
de las instrucciones de manejo del puerto paralelo en el programa principal:
main
...
ioi ld A,(PEDR)
and 0xEE
ioi ld (PEDR),A
...
int
...
push af
...
ioi ld a,(PEDR)
xor 0x82
ioi ld (PEDR), a
...
pop af
ipres
ret

; lee port E
; invierte bits de LEDs (alternado)
; escribe port E

Ej:
...
ioi ld A,(PEDR)
<- INTERRUPCIN
...
push af
...
ioi ld a,(PEDR)
xor 0x82
ioi ld (PEDR), a
...
pop af
ipres
ret
<- RETORNO DE INTERRUPCIN
and 0x7E
ioi ld (PEDR),A
...

; lee port E
; invierte bits de LEDs (alternado)
; escribe port E

Como puede observarse, el contenido del registro A corresponde al valor del puerto
paralelo E antes que se produjera la interrupcin, la cual modific el valor de los bits
en que se maneja el buzzer. El programa principal, luego, los altera, interrumpiendo
el sonido del buzzer, como muestra el grfico a continuacin:

36

Ports paralelo
Salida
buzzer
INTs
timer
(tiempo)
Escritura
en port
(tiempo)

Por ms que el tiempo de interrupcin del sonido sea muy breve, es probable que se
perciba como un pequeo chasquido, pero si esto es frecuente (si el programa
principal actualiza en loops, aunque no tenga nada nuevo que escribir, por ejemplo),
se produce una aparicin de componentes propias de la frecuencia de perturbacin
(la diferencia entre la velocidad de repeticin del programa principal y las
interrupciones), que se perciben como suciedad o "grgaras" en el sonido emitido
por el buzzer3. Ni hablar si el port paralelo en realidad controla un conversor digital
a analgico...
Lo primero que se nos suele ocurrir para corregir el defecto es inhibir las
interrupciones durante la secuencia de actualizacin, para convertirla (al menos
desde el punto de vista de las interrupciones) en una operacin atmica:
main
...
ipset 1
ioi ld A,(PEDR)
and 0xEE
ioi ld (PEDR),A
ipres 1
...

Sin embargo, esta sencilla operacin introduce un perodo de latencia durante el


cual las interrupciones no pueden aceptarse, y por ende se las demora. Dependiendo
de la duracin de este perodo con respecto a la frecuencia de las interrupciones,
aparece una modulacin del ancho de pulso de la seal del buzzer que es ms
molesta cuando se manifiesta ms frecuentemente. El efecto percibido (si es que
llega a notarse) es una variacin en el timbre 4 del buzzer, y es ms notoria en micros
de baja velocidad, en los cuales un par de instrucciones representa un intervalo de
tiempo apreciable frente al perodo del sonido generado. Por ejemplo, algunos
microsegundos en un buzzer a 5KHz, cuyo semiperodo es de 100 s .
3

Para quien guste del anlisis fantico, este proceso es similar a una heterodinacin de ambas
componentes: frecuencia de actualizacin del programa principal y de las interrupciones (INT), slo
que en el mundo digital. Del producto de ambas aparece fundamentalmente (entre otras cosas) una
componente de baja frecuencia (la diferencia entre las frecuencias de ambas seales): la periodicidad
de la repeticin del chasquido. Dejamos el anlisis de Fourier para los ms desquiciados.
Siguiendo con los anlisis para fanticos, este proceso es una modulacin de fase.

37

Perifricos Internos

Salida
buzzer
INTs
timer
(tiempo)
Escritura
en port
(tiempo)

Como se aprecia en el grfico, en el momento en que se demora la atencin de las


interrupciones porque justo se est accediendo al port, el cambio de estado en los
pines del buzzer se produce a destiempo.
El segundo intento, recordando lo visto anteriormente, es corregir el defecto
empleando shadow registers, y/o realizando las operaciones de I/O de forma
verdaderamente atmica. De esta forma, en cualquier instante que se produzca la
operacin, siempre que ambas rutinas empleen el mismo esquema, no hay un
movimiento no-atmico que produzca este tipo de glitches, y se minimizan la
latencia y su variacin.
Sin embargo, la latencia de interrupciones sigue existiendo, y en un sistema muy
cargado, y/o con interrupciones de mayor prioridad, puede llegar a causar efectos
como el recientemente analizado. El cambio de estado de los pines se realiza por
programa, y el mismo se demorar tanto como sea necesario demorar la atencin de
la rutina correspondiente.
En estos casos, la solucin es aprovechar el hardware para encargarse del cambio
de estado de los pines. La mayora de los microcontroladores tienen un mdulo de
comparacin que permite operar sobre un pin, cambindolo de estado cuando el
timer lo indica, sin intervencin del software ms que para recalcular el nuevo
perodo. Siempre que se opere sobre el ltimo valor calculado y no sobre el valor
actual del timer, se minimizar el jitter.
En Rabbit, disponemos de los ports paralelo D y E, ms F y G en R3000.
Cualquiera de estos puertos puede ser configurado (por nibbles) para que el valor
escrito en el registro PxDR sea transferido a los pines cuando lo indica un timer
interno, como por ejemplo uno de los mdulos de comparacin del Timer B. De esta
forma, al ocurrir una comparacin exitosa, se transfiere automticamente el ltimo
valor calculado a los pines, mientras que al atenderse la correspondiente
interrupcin, se calcular el nuevo valor, el cual tendr efecto en la prxima
comparacin exitosa. Los nmeros que figuran en el grfico corresponden al orden
del valor en cuestin; como se observa, el valor 1 es calculado en la primera
interrupcin, y pasar a los pines en la siguiente comparacin exitosa:

38

Ports paralelo
Timer B

Cambio
en pines

INTs timer
(tiempo)
latencia y jitter de latencia
Escritura
en registro
PxDR

El siguiente ejemplo, muestra el modo de configurar los ports para funcionar de


esta 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 de
desarrollo. El uso y configuracin del Timer B lo veremos en ms detalle en un
apartado siguiente. Este ejemplo es una pequea modificacin del que utilizramos
en el captulo sobre interrupciones, slo alteramos lo necesario para producir el
cambio 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 inmediatamente
WrPortI(PEDR,&PEDRShadow,0x80);
/* prende un LED, apaga el otro */
SetVectIntern(0x0B, timerb_isr);

// setea vector de interrupcin

WrPortI(TBCR, &TBCRShadow, 0x09); //


//
WrPortI(TBM1R, NULL, 0x00);
//
WrPortI(TBL1R, NULL, 0x89);
//
taimer=0x089;

clock timer B con (perclk/16)


prioridad de interrupcin: 1
el timer dispara el port cuando
el contador llega a 0089

WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupcin


// por match en B1
WrPortI(PECR,&PECRShadow,0xAA);
// pines cambian con match en B1
while(1);
}
#asm
timerB_isr::
push af

; salva registros

39

Perifricos Internos
ioi ld a, (TBCSR)
push hl
ld hl,(taimer)
push bc
ld bc,0x0089
add hl,bc
ld (taimer),hl
ld a,h
rrca
rrca
ioi ld (TBM1R), a
ld a,l
ioi ld (TBL1R), a
ioi ld a,(PEDR)
xor 0x82
ioi ld (PEDR), a
pop bc
pop hl
pop af
ipres
ret

; reconoce match
; lee compare shadow
; suma 0x0089
; y guarda en shadow
; MSbs en bits 7,6
; descarta el resto de los bits
; carga en match register
;
;
;
;

int siguiente: timer+0089h


lee port E
invierte bits de LEDs (alternado)
escribe port E (cambiar en nuevo match)

; recupera registros
; restablece interrupciones
; retorna

#endasm

Con un simple cambio al ejemplo anterior:


WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupcin
// 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 de
interrupciones y su correspondiente jitter o variacin. Colocando un osciloscopio
con un canal y el disparo en PE.7, y otro canal en PE.1, podemos observar como el
software "llega despus", y dado que las instrucciones son siempre las mismas y en
nuestro entorno demoran siempre lo mismo, la variacin en ese tiempo slo puede
deberse a una variacin en la demora de atencin y despacho de las interrupciones,
es decir, la latencia. Lo que se observa es algo similar a la figura siguiente:
PE.7

PE.1

latencia minima

latencia maxima

jitter de latencia

La inversin de fase se debe al hecho de que el valor escrito en PE.7 recin pasar
a los pines en la siguiente comparacin, mientras que el de PE.1 lo hace al momento
de la escritura (recordemos que ya escribamos valores complementarios en ambos
pines).
40

Ports paralelo

Ports D y E: bit registers


Una caracterstica sobresaliente de Rabbit que resuelve ambos problemas
planteados, 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 operacin
read-modify-write al momento de setear un bit en un port. Internamente, existen
ocho ports de un bit, cada uno en una direccin diferente, por lo que el escribir en
uno de estos registros slo afecta al bit existente, el cual controla directamente el
flip-flop correspondiente del port D o E, segn corresponda. Es decir, si escribimos
en el registro PDB3R, slo 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 (slo para los ports D y E) mediante el uso de bit registers, de la
siguiente forma:
activa: ld hl, PDB6R
ioi set 6,(HL)
ret
desactiva:
ld hl, PDB6R
ioi res 6,(HL)
ret

; pone PD.6 en alto

; pone PD.6 en bajo

Dado que en PDBxR slo existe el bit x, en realidad podemos escribir cualquier
cosa que tenga ese bit en el estado que nos interesa, con lo cual podemos aprovechar
algn registro que sepamos tiene el valor correcto en caso que los ciclos de clock nos
estn quitando el sueo.

Ports serie en Rabbit 2000 y 3000


Veremos algunos ejemplos de uso de los ports serie en el captulo sobre
conectividad, complementarios a los que ya hemos desarrollado en el libro
introductorio. Vamos a recordar aqu la estructura de registros, que la vamos a
necesitar, y analizaremos las diferentes distribuciones de pines, causa de confusiones
y prdidas 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 transmitir
SxAR: Alternate Data Register, se utiliza para generar un noveno bit en cero.

41

Perifricos Internos
SxLR: Long Stop Register, se utiliza para generar un noveno bit en uno, lo cual es
equivalente a un bit de stop adicional. Slo est presente en versiones
R2000A y posteriores.
SxSR: Status Register, contiene los flags que indican el estado del port, condicin de
interrupcin, y errores
SxCR: Control Register, configura el modo de operacin, y la prioridad de las
interrupciones.

Pines
Los pines en las interfaces serie requieren un poquito de atencin, debido a la
existencia de pinouts alternativos y la carencia de algunos de los pines en algunos de
los mdulos.

Puertos A y B
Los ms conflictivos son los puertos A y B, los cuales comparten los pines con el
port paralelo C, especficamente PC.7, PC.6 para el puerto serie A y PC.5, PC.4
para el puerto serie B. La transmisin se realiza por el bit par (PC.6 y PC.4), dado
que es una salida, y la recepcin por el bit impar, dado que es una entrada. Para que
la salida de los datos de la UART pueda realizarse por los pines mencionados, deben
setearse los bits correspondientes (6 y 4, respectivamente) en el PCFR (Port C
Function Register), que es quien decide qu funcin tiene el pin. Si se utilizan las
funciones serAopen() o pktAopen() para el puerto A y serBopen() o pktBopen() para
el puerto B, esto se realiza automticamente

Pinout alternativo
La salida de datos de la UART puede, alternativamente, tomarse del port D. Para
esto, deben setearse los bits correspondientes (6 y 4, respectivamente) en el PDFR
(Port D Function Register), que es quien decide qu funcin tiene el pin.
La entrada de datos a la UART, tambin puede alternativamente tomarse del port
D. Para esto, es necesario colocar la combinacin 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 puerto
B, en los mdulos RCM22xx y 23xx el compilador sabe que los pines "originales"
no estn disponibles, y automticamente selecciona el pinout alternativo. Para otras
combinaciones manuales, se puede indicar que se desea el pinout alternativo
definiendo:
#define SERA_USEPORTD
#define SERB_USEPORTD

Si se utiliza pktXopen(), se deber indicar que se desea el pinout alternativo


definiendo:

42

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 PCFR
para habilitar la salida de datos y habilitando el receptor colocando un 0 en el bit 5
del SxCR correspondiente. Si se utilizan las funciones serXopen() o pktXopen(), esto
se realiza automticamente.

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 seleccionan
operando sobre PGFR para habilitar la salida de datos y habilitando el receptor
colocando un 0 en el bit 5 del SxCR correspondiente. Si se utilizan las funciones
serXopen() o pktXopen(), esto se realiza automticamente.

Ports serie en Rabbit 4000 y 5000


La versatilidad de asignacin de pines en estos micros es muy elevada, y realmente
la mejor opcin es usar la utilidad de configuracin de I/O. Sin embargo, veremos
algunas definiciones que nos ayudarn a elegir manualmente los pines.

RS232.lib
En primera instancia, es costumbre de Rabbit soportar antiguas definiciones, por lo
que las que hemos visto para Rabbit 2000 y 3000 estn soportadas, al menos hasta
DC10.60. La asignacin de pines por defecto es la misma que para los modelos
anteriores, pero para elegir un pin particular para el puerto serie x, contamos con las
siguientes definiciones en RS232.lib:
#define
#define
#define
#define

xDRIVE_TXD
xDRIVE_RXD
SERx_TXPORT
SERx_RXPORT

4
5
PCDR
PCDR

Existe adems una opcin de eliminar el cdigo de control de flujo, si no lo


utilizamos:
#define SER_NO_FLOWCONTROL

La funcin de inicializacin del port utiliza los nuevos BRG (Baud Rate
Generators) de estos micros por defecto. Si deseamos usar el Timer A en el port x,
debemos definir
43

Perifricos Internos

#define SPx_USE_TIMERA

DMA
La biblioteca de funciones RS232.lib soporta el uso de DMA mientras no se realice
chequeo de paridad. Luego de inicializar el port mediante la llamada a serXopen(),
lo colocamos en modo DMA llamando a la funcin serXdmaOn(). Podemos salir de
este modo utilizando la funcin 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 cdigo que no va a


utilizar.

Packet.lib
Esta biblioteca de funciones, al menos hasta DC10.60, no presenta agregados en
cuanto a su funcionalidad, no soporta DMA, y tampoco es posible utilizar algn pin
que no sea uno de los que se seleccionan por defecto.

Timer B
En el captulo sobre interrupciones, y en el apartado sobre cambio de pines por
hardware de este captulo, hemos tenido ejemplos de utilizacin del Timer B. Ahora
vamos a interpretar algunas de las cosas que hemos hecho.

Repaso
Como seguramente recordamos, el Timer B est compuesto por un contador de 10
bits (TBCMR-TBCLR) de slo lectura y dos registros de comparacin de 10 bits
(TBM1R-TBL1R y TBM2R-TBL2R) que setean un flag de comparacin exitosa
(match) cuando el valor del contador iguala al del registro. Dado que no disponemos
de funciones especiales que nos hagan la vida ms fcil, vamos a repasar la
estructura de registros:
TBCSR: Timer B Control/Status Register, contiene los flags de comparacin exitosa
de cada uno de los timers, en modo lectura, y controla el estado de la
interrupcin de cada registro de comparacin, en modo escritura. Cada bit

44

Timer B

TBCR:

controla el registro respectivo, el bit 0 controla la operacin del sistema


Timer B.
Timer B Control Register, los bits 1, 0 definen la prioridad de la
interrupcin (00 = no habilitada). Los bits 3 y 2 controlan la fuente de
reloj del contador:
00 = PCLK/2
01 = Timer A1
1x = PCLK/16
Los bits 4 y 5 slo existen en R4000 y superiores, y seteados permiten
configurar una recarga automtica de los registros de comparacin de los
mdulos 1 y 2, respectivamente.

Los registros de 10 bits (TBCMR-TBCLR, TBM1R-TBL1R y TBM2R-TBL2R)


estn dispuestos de forma algo caprichosa, que seguramente facilita algunos clculos
pero dificulta otros:
MSB
LSB

9 8
7

6 5 4 3

2 1 0

PCLK, tambin referido en los textos como PERCLK, o sus equivalentes en


minsculas, es el clock de perifricos (PERipheral CLocK); segn estudiramos en
el libro introductorio, que si no se modifica el seteo por defecto equivale en
frecuencia al clock del procesador.

Ejemplo (Rabbit 2000 y 3000)


Analicemos ahora el cdigo del ejemplo anterior, en el apartado sobre cambio de
pines por hardware; slo la parte que atae al Timer B. En este caso, utilizamos el
mdulo de comparacin B1:
WrPortI(TBCR, &TBCRShadow, 0x09); //
//
WrPortI(TBM1R, NULL, 0x00);
//
WrPortI(TBL1R, NULL, 0x89);
//
taimer=0x089;

clock timer B con (perclk/16)


prioridad de interrupcin: 1
el timer dispara el port cuando
el contador llega a 0089

WrPortI(TBCSR, &TBCSRShadow, 0x03); // habilita timer e interrupcin


// por match en B1

Como puede seguirse en los comentarios, configuramos un clock dividido por


diecisis, a fin de poder lograr frecuencias dentro del rango de audio. Configuramos
adems una prioridad de interrupcin pues necesitamos volver a cargar un nuevo
valor para que el timer divida por una frecuencia de nuestro inters, caso contrario la
comparacin exitosa siguiente ser luego de que el contador arranque nuevamente de
cero y vuelva a llegar al valor configurado, lo cual ocurrir en 1024 cuentas, dado

45

Perifricos Internos
que es un contador de 10-bits. Con estas sentencias, escribimos el valor 0x0089, y
cuando el contador llegue a este valor, recibiremos una interrupcin. Luego, en la
rutina de interrupciones, le indicamos al mdulo de comparacin que ya estamos en
dicha rutina, que no debe seguir pidiendo una interrupcin, y lo hacemos leyendo el
registro TBCSR. Inmediatamente despus, sumamos al valor de cuenta anterior, la
constante deseada, es decir, el nmero de cuentas que deben transcurrir para una
nueva comparacin exitosa y su correspondiente interrupcin. Dado que los registros
son de slo lectura, debemos guardar la ltima cuenta en RAM, lo que hacemos en la
variable taimer, la cual dejamos que recicle tranquilamente, dado que slo nos
interesan los diez bits menos significativos. Debido a que los dos bits ms
significativos del valor de comparacin deben escribirse en los bits ms
significativos del registro TBM1R, hacemos una doble rotacin del acumulador, lo
cual efectivamente desplaza los bits 0 y 1 (9 y 8 respectivamente de los diez bits que
nos interesan) a las posiciones 6 y 7, respectivamente.
ioi ld a, (TBCSR)

; reconoce match

ld hl,(taimer)
push bc
ld bc,0x0089
add hl,bc
ld (taimer),hl
ld a,h
rrca
rrca
ioi ld (TBM1R), a
ld a,l
ioi ld (TBL1R), a

; lee compare shadow


; suma 0x0089
; y guarda en shadow
; MSbs en bits 7,6
; descarta el resto de los bits
; carga en match register
; int siguiente: timer+0089h

El valor a colocar en el registro de comparacin es la cantidad de clocks de este


timer que necesitamos que cuente. Como tal es el producto entre la frecuencia de
clock del timer y el perodo entre interrupciones. En un timer B con clock perclk/16
como es este caso, para generar una seal de 5KHz requerimos una interrupcin
cada 100 s , luego, en un RCM2200:
22 MHz
perclk
* period 
*100 s137,50x0089
cuentas
16
16

El Timer B antes y despus del Rabbit 4000


Nuestro perifrico bajo estudio ha experimentado una pequea transformacin con
el advenimiento del Rabbit 4000. Seguramente estudiamos en el libro introductorio
que una vez que el Timer B alcanz la cuenta deseada, para que vuelva a
interrumpirnos, an en el mismo valor de cuenta, debamos recargar la pareja de
registros de comparacin (TBLxR y TBMxR), con un cdigo como ste:
ioi
xor a
ioi
ioi

46

ld a, (TBCSR)

; reconoce interrupcin

ld (TBL1R), a
ld (TBM1R), a

; siguiente: timer=0000h (otra vez)

Timer B
La encarnacin del Timer B en el Rabbit 4000 se ha visto mejorada con el
crecimiento de un nuevo par de registros por mdulo, TBSLxR y TBSMxR, Timer B
step registers. Estos registros permiten que el mdulo pueda recargar su cuenta sin
intervencin 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 interrupcin: 1
// usar step registers para auto-reload
WrPortI(TBL1R, NULL, 0x00);
WrPortI(TBM1R, NULL, 0x00);

// el timer interrumpe cuando


// el contador llega a 0000

WrPortI(TBSL1R, NULL, 0x00);


WrPortI(TBSM1R, NULL, 0x00);

// y vuelve a recargarse
// con el valor 0000

count=SCALER;

// inicializa postscaler

WrPortI(TBCSR, &TBCSRShadow, 0x03);// habilita timer B e interrupcin


// por match en B1

En la rutina de interrupciones, ya no debemos recargar los registros de


comparacin. Sin embargo, como los mdulos basados en R4000 y R5000 son
mucho ms rpidos, hicimos un postscaler de 16-bits para obtener un parpadeo
visible:
ioi ld a,(TBCSR)
ld bc,(count)
dec bc
ld a,b
or c
jr nz,skip
ld bc,SCALER

;
;
;
;
;

reconoce interrupcin
auto reload mediante step registers
postscaler
decrementa postscaler (no afecta flag Z)
chequea 0

; si no llega a cero, saltea


; 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 contador
de 48 bits que oficia de reloj de tiempo real (RTC). Este contador posee un pin
diferente para su alimentacin, permitiendo el uso de pila de respaldo para el RTC
mientras el resto del chip se encuentra sin alimentacin.
El contador es ledo 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

Perifricos Internos
En todo momento, fecha y hora del sistema deben determinarse leyendo
SEC_TIMER, a menos que sea estrictamente necesario leer directamente el RTC.
Recordemos que la informacin corresponde al nmero de segundos transcurridos
desde la hora cero (1 de enero de 1980), y puede traducirse a una estructura
mediante funciones de Dynamic C:
struct tm thetm;
mktm(&thetm, SEC_TIMER);

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


struct tm
{
char
char
char
char
char
char
char
};

tm_sec;
tm_min;
tm_hour;
tm_mday;
tm_mon;
tm_year;
tm_wday;

//
//
//
//
//
//
//

segundos
minutos
horas
da del mes
mes
ao (1980-2047)
da de la semana (domingo = 0)

Tendremos ejemplos de uso de las funciones de RTC cuando analicemos file


systems, en los que haremos ejemplos de logs de eventos con registro de fecha y
hora.
Si el programador altera la hora del RTC:
tm_wr(&thetm);

// set clock

esto no alterar SEC_TIMER, que ser actualizado slo luego de un reset, por lo que
el programador deber actualizar dicha variable manualmente, realizando una lectura
del RTC:
tm_wr(&thetm);
SEC_TIMER=read_rtc();

Watchdog Timer
Si bien la CPU Rabbit tiene un hardware watchdog, el Virtual Driver lo resetea
peridicamente y provee diez watchdog timers virtuales por software. Si el
programador necesita utilizar un watchdog timer para controlar una rutina en
particular, puede hacer uso de los Virtual Watchdog Timers, requirindolos y
utilizndolos de la siguiente forma:
ID = VdGetFreeWd(count);
VdHitWd(ID);

48

/* inicializa un VWDT */

/* mantiene activo el VWDT, evitando reset */

Watchdog Timer
VdReleaseWd(ID);

/* libera el VWDT */

Cada count equivale a un perodo de 62,5ms; dado por la operacin interna del
Virtual Driver, controlado a su vez por la interrupcin peridica.
Hemos visto un adelanto en el captulo sobre assembler, respecto a la forma de
utilizarlo:
int ID;
main()
{
ID = VdGetFreeWd(5);
/* inicializa un VWDT, 5 cuentas: 312,5ms
while(1){
VdHitWd(ID);
// reset del watchdog
// tarea
// si por algn motivo no llega en el tiempo previsto,
// se produce el reset
}
}

Si el watchdog timer expira, se produce el reset del micro, pero durante el


debugging, la ejecucin del programa se detiene con un cdigo de error 250, y
Dynamic C muestra una ventana de dilogo que informa de la situacin.

Uso de encoders o codificadores rotativos


Un codificador en cuadratura (quadrature encoder) es un dispositivo
electromecnico que se utiliza para seguir la rotacin de un eje. Se implementan
generalmente con un disco que alterna franjas opacas y transparentes, excitando dos
detectores pticos. Las seales de salida son dos ondas cuadradas en cuadratura; la
velocidad de giro se observa en la frecuencia de ambas seales, y la direccin de
rotacin se detecta observando cul de las dos seales adelanta en fase, es decir,
observando el signo de la diferencia de fase.
Una extensin al plano es el viejo y conocido mouse o ratn mecnico con el que
hasta hace poco controlbamos nuestra computadora. El mouse mecnico consta de
dos codificadores en cuadratura ubicados a su vez espacialmente en cuadratura, y
asociados a una esfera. La esfera se encuentra confinada dentro del mouse, y la
traslacin del mismo ocasiona una rotacin de la esfera por friccin. Si movemos el
mouse en el plano en la direccin de un eje x imaginario perpendicular al cable del
mouse, la esfera rotar de modo que uno de los codificadores en cuadratura recoja e
indique este movimiento. Lo mismo ocurrir si movemos el mouse en direccin
paralela al cable, con el otro codificador. Leyendo ambos codificadores puede
obtenerse una proyeccin sobre el eje x y otra sobre el eje y de la trayectoria
recorrida, as como tambin de la velocidad y aceleracin del movimiento.

49

Perifricos 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 8bits (10-bits en el R3000A5 y posteriores) registra los eventos contando en una u otra
direccin al observar transiciones en el estado de las entradas I y Q, como
analizramos en el libro introductorio y reproducimos en el diagrama a continuacin:

Las funciones que utilizamos para controlar estos decodificadores de cuadratura


son:
qd_init(int);
qd_zero(mod);
qd_read(mod);

// inicializa con prioridad de interrupciones 'int'


// resetea el contador del mdulo 'mod'
// devuelve el valor del contador de 'mod' (long)

La funcin qd_init() debe llamarse antes del loop de lectura para preparar el vector
de interrupciones; la misma limpia ambos contadores, configura los pines, y setea el
timer A10 con un valor predefinido en la biblioteca de funciones: R3000.lib. o qd.lib
segn el micro. Antes de DC9.50, se seleccionaba el nibble bajo del port F en el
R3000 por defecto. Actualmente, adems, los pines pueden seleccionarse, para el
R3000:
#define
#define
#define
#define
#define
#define

QD1_USEPORTFL
QD1_USEPORTFH
QD1_DISABLE
QD2_USEPORTFL
QD2_USEPORTFH
QD2_DISABLE

// QD1 usa PF1 y PF0


// QD1 usa PF5 y PF4
// QD2 usa PF3 y PF2
// QD2 usa PF7 y PF6

y para R4000 y R5000:


5

50

El R3000A es la ltima revisin de R3000, identificada como IL2T en el encapsulado ms comn.


Incluye correccin de algunos defectos del original y agrega nuevas instrucciones y modos de
operacin.

Uso de encoders o codificadores rotativos

#define
#define
#define
#define

QD1_USEPORTD
QD1_USEPORTEL
QD1_USEPORTEH
QD1_DISABLE

// usa PD1 y PD0


// usa PE1 y PE0
// usa PE5 y PE4

#define
#define
#define
#define

QD2_USEPORTD
QD2_USEPORTEL
QD2_USEPORTEH
QD2_DISABLE

// usa PD3 y PD2


// usa PE3 y PE2
// usa PE7 y PE6

En R4000 y R5000 la seleccin por defecto se no asignar ningn pin.


El siguiente fragmento de cdigo es un ejemplo de un selector para un men de
operacin implementado en base a uno de los decodificadores de cuadratura en un
R3000. 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 conexin precede al ejemplo:
Vdd

PF.2

push

PC.5

PF.3

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 posicin anterior
LCD_printat (0, 50+10*sel, 30, ">", WHITE, WHITE);
// vuelve texto de men a color normal
LCD_printat (0, 50+10*sel, 50, option[sel], BLACK, WHITE);
if(encoder<0){
sel++;
if(sel>2)
sel=2;
}
else {
sel--;

51

Perifricos Internos
if(sel<0)
sel=0;
}
// dibuja '>' en nueva posicin
LCD_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))
button=1;

// lee botn de seleccin

}
// procesa seleccin, sel=elemento seleccionado

Girando el encoder hacia uno u otro lado, se produce el avance o retroceso dentro
de las diferentes opciones del men. Presionando el botn del encoder, se produce la
seleccin del elemento apuntado.
>

Elemento1
Elemento2
Elemento3

R2000
En el Rabbit 2000 no disponemos de decodificadores de cuadratura, por lo cual
deberemos buscar otra forma de leer estos codificadores. Apuntamos entonces el
ejemplo al mismo tipo de encoder que utilizramos con el R3000; dado que tenemos
un pulso en ambas salidas a cada movimiento del eje entre los descansos, podemos
utilizar una de las seales 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 peridicamente chequea el estado de una seal
y ante cambios verifica la otra.
interrupciones, configurando al micro para ser interrumpido por un flanco de una
seal y verificando el estado de la otra.
El esquema de interrupciones es bastante ms estricto en cuanto a la limpieza de la
seal que genera las mismas, la cual deber ser filtrada correctamente a fin de evitar
falsos disparos. El esquema de polling es ms permisivo, segn la frecuencia de
operacin del handler en cuestin; tambin es ms propenso a perder pulsos cuando
hay una carga importante del procesador (si no se lo llama con la frecuencia
suficiente).
La literatura de Rabbit recomienda reservar las interrupciones para aquellas tareas
que tienen un timing crtico (lo cual no es este caso, por lo general), dado que se
trata de un sistema con soporte multitarea cooperativo y probablemente la idea de los
programadores es manejar todo mediante handlers y mquinas de estados. No
obstante, quienes venimos de dcadas de exprimir pequeos micros para sacarles un
miserable ciclo de clock extra, estamos ms que acostumbrados a reservar
interrupciones para aquellas tareas que consideramos tediosas o su periodicidad y/o

52

Uso de encoders o codificadores rotativos


asincronicidad nos complican la percepcin general de la aplicacin. Para justicia
con ambos, analizaremos los dos casos.
El siguiente es un handler por polling que lee un encoder conectado como indica el
diagrama que le antecede:
Vdd

PE.0
PB.0

int quadenc_poll()
{
static int counter;
static char oldA;
#GLOBAL_INIT {
counter=0;
oldA=BitRdPortI(PBDR,0);
}
#asm

BlA:
AlB:
falling:
qpdn:

ioi ld A,(PEDR)
ld HL,oldA
and 0x1
cp (HL)
ld (HL),A
ld HL,(counter)
ret z
ex DE,HL
ld HL,PBDR
and A
jr z,falling
ioi bit 0,(HL)
jr z, AlB
dec DE
jr qpdn
inc DE
jr qpdn

;
;
;
;
;
;
;

Lee
Apunta a estado anterior
"change-detect" bit
Compara
Almacena nuevo estado (preserva Z)
lee contador (preserva Z)
Sale si no hubo cambios

; Cambio, apunta a port B


; Flanco ?
; A sube, check B
; B antecede a A
; A antecede a B
; descendente, descartar

ex DE,HL
ld (counter),HL
ret

; vuelve con el valor del contador

#endasm
}
main()
{
WrPortI ( PEDDR,&PEDDRShadow,'\B10001010' ); // PE1,3,7 = output
while(1){
printf("%05d \r",quadenc_poll());

53

Perifricos Internos
}
}

A continuacin, presentamos una versin de handler por interrupciones externas para


Rabbit 2000. El circuito esquemtico es el mismo6:
static int counter;
#asm
quadenc_isr::

BlA:
AlB:
qidn:

push AF
push HL
push DE
ld DE,(counter)
ld HL,PBDR
ioi bit 0,(HL)
jr z, AlB
dec DE
jr qidn
inc DE
jr qidn
ld (counter),DE
pop DE
pop HL
pop af
ipres
ret

; salva registros
; lee contador (16-bits) (preserva Z)
; apunta a port B
; A sube, check B
; B antecede a A
; A antecede a B
; actualiza contador

; restablece prioridad e interrupciones


; return

#endasm
main()
{
WrPortI ( PEDDR,&PEDDRShadow,'\B10001010' );
// PE1,3,7 = output
counter=0;
SetVectExtern3000(0, quadenc_isr);
// set up ISR R2000C
WrPortI ( I0CR,&I0CRShadow,'\B00001001' ); // ascendente, prioridad 1
// SetVectExtern2000(1, quadenc_isr);
// set up ISR R2000
// WrPortI ( I0CR,&I0CRShadow,'\B00001010' );// ascendente, prioridad 2
while(1){
printf("%05d \r",counter);
}
}

Salidas PWM
Disponemos de cuatro canales de generacin de PWM. El ciclo de trabajo puede
1
de 0%7 a 100%. Esta es una caracterstica de Rabbit que
variar en pasos de
1024
lo distingue frente a otros micros que utilizan un sistema de comparacin entre
6
7

54

En realidad esto es vlido para las nuevas versiones de R2000. Aquellos dinosaurios (como yo) que
an tienen algn R2000 IQ2T debern reemplazar las sentencias comentadas y conectar el port E
como indica la nota de Rabbit TN301.
El 0% en realidad se obtiene desactivando el generador de PWM...

Salidas PWM
registros. No importa cual sea la frecuencia seteada, la resolucin es siempre de 10
bits.
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);
pwm_set(ch, pw, NULL);

//
//
//
//
//

inicializa el timer
A9, devuelve el valor
seteado (valor ms cercano)
setea el canal 'ch' con un
ciclo de trabajo 'pw'

El tercer parmetro de pwm_set() puede tomar diversos valores o un OR de ellos.


Por ejemplo, la opcin 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 cdigo es un ejemplo del uso de los generadores de


PWM para controlar un calefactor y un ventilador basado en un motor de corriente
continua. Tanto la energa elctrica transformada en calor en la resistencia del
calefactor como la velocidad del motor dependen del valor de la tensin que se les
suministra, la cual controlamos variando su valor medio a travs del ciclo de trabajo
mediante los generadores de PWM, como indica el diagrama.
+B

+B

Calefactor

PF5

logic-level
MOSFET

Ventilador

PF4

logic-level
MOSFET

El cdigo recibe una medicin de temperatura en curT, y compara con los lmites
configurados (tlo, thi, t2hi). De acuerdo a la diferencia, da ms o menos energa al
ventilador o calefactor, segn corresponda, operando sobre el ciclo de trabajo.
F_MIN, F_MAX, H_MIN y H_MAX son los valores mnimo y mximo de PWM a
utilizar para los valores mnimo absoluto y mximo absoluto de tensin aplicable al
ventilador y el calefactor, respectivamente.
55

Perifricos 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)
//alarma
alarm=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 supresin de pulsos (R3000A+)


Los generadores de PWM del R3000A, R4000 y R5000 permiten generar
interrupciones y/o anular la salida cada un nmero determinado de ciclos, con
opciones de 1, 2, 4 u 8. La configuracin de estas opciones se realiza utilizando
algunos de los bits que hasta entonces no tenan aplicacin en R3000. Debido a que
el soporte en Dynamic C versin 9 no ha sido ampliado al momento de escribir este
texto, deberemos trabajar manualmente sobre dichos bits.
A tal fin, mostramos a continuacin el diagrama de ubicacin de estos bits:
PWL0R

O O

L L

PWL1R

O O

PWL2R
PWL3R

O O

Los bits OO controlan la anulacin del pulso de salida en 1 de 2, 3 de 4, 7 de 8


ciclos, o continuo (no se anula).
Los bits LL establecen el nivel de interrupcin, donde 00 significa que el perifrico
no 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

Salidas PWM
Si el micro es un R4000 o 5000, en Dynamic C versin 10 disponemos adems de
las siguientes opciones en el tercer parmetro de pwm_set():
D
Para suprimir pulsos de salida seleccionaremos una de:
PWM_OUTNORMAL
PWM_OUTEIGHTH
PWM_OUTQUARTER
PWM_OUTHALF

Para seleccionar la prioridad de interrupciones, elegimos entre:


PWM_INTOFF
PWM_INTPRI1
PWM_INTPRI2
PWM_INTPRI3

Para que las interrupciones sean cada una cantidad de ciclos, usamos:
PWM_INTNORMAL
PWM_INTEIGHTH
PWM_INTQUARTER
PWM_INTHALF

La supresin de pulsos tiene aplicaciones en el control de servos, dado que es


necesario generar ciclos de trabajo muy bajos. El anular siete de cada ocho pulsos de
1
salida nos permite tener toda la resolucin (1024 valores) en
del rango, es
8
decir, podemos generar un ciclo de trabajo de entre 0 y 12,5% con precisin de
1
0,12 .
81024
Las interrupciones tienen aplicaciones en generacin de seales mediante PWM, es
decir, cambios peridicos del valor medio de la seal generada. El interrumpir cada
un determinado nmero de ciclos de la seal de PWM nos permite trabajar con un
cierto oversampling que relaja los requerimientos de filtrado, de modo similar a la
funcin spread.
El siguiente es un fragmento8 de cdigo que muestra la actualizacin peridica 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);

El mismo es parte de un programa que ha sido utilizado para reproducir audio de una tarjeta SD
accedindola en bajo nivel mediante rutinas ad hoc

57

Perifricos Internos
pwm_set(2,513,PWM_SPREAD | PWM_INTNORMAL |
PWM_USEPORTC | PWM_INTPRI3);
#asm root
PWM_handler::
push af
push hl
ld hl,(ptr)
ld a,(hl)
ioi ld (PWM2R),a
inc hl
ld (ptr),hl
ld hl,(bytes)
dec hl
ld (bytes),hl
ld a,h
or l
jr nz,done
ld hl,buf
ld (ptr),hl
ld hl, BUF_SIZE
ld (bytes),hl
done:
pop hl
pop af
ipres
ret
#endasm

Captura de eventos
Las entradas de captura de eventos se utilizan para poder determinar el momento en
que se produce un evento externo en particular. Dicho evento es sealizado mediante
un flanco cualquiera (o ambos) en alguno de los diecisis pines que pueden ser
configurados para este propsito, asignados a uno de los dos canales de captura de
que disponemos.
La arquitectura utilizada es sumamente flexible, particularmente para medir la
duracin de pulsos cortos, en los cuales no podra esperarse atender interrupciones y
leer dos cuentas diferentes de un contador para luego obtener la duracin restando
ambas cuentas.

Repaso
Dado que no disponemos de funciones de Dynamic C para operar sobre este
perifrico, vamos a repasar los registros:
Para llevar cuenta del tiempo, cada mdulo emplea un contador de 16 bits que recibe
reloj del Timer A8.
TAT8R: Timer A8 Time Constant Register, contiene la constante de cuenta para
generar la frecuencia a la que cuenta el mdulo, que corresponde a la
resolucin del mismo.
ICCSR: Input Capture Control/Status Register, en escritura, los bits 3, 2 resetean los
contadores de los mdulos 2 y 1 respectivamente, y los bits 7 a 4 controlan
58

Captura de eventos
la habilitacin de la interrupcin correspondiente a un evento en particular.
En lectura, retorna el estado de ese evento (condicin de arranque, de
detencin, o de desborde en cada mdulo), segn el esquema a
continuacin (1 => ocurri esa condicin):
bit 7: mdulo 2, condicin de arranque (start)
bit 6: mdulo 2, condicin de detencin (stop)
bit 5: mdulo 1, condicin de arranque (start)
bit 4: mdulo 1, condicin de detencin (stop)
bit 3: mdulo 2, desborde del contador (rollover)
bit 2: mdulo 1, desborde del contador (rollover)
ICCR: Input Capture Control Register, controla la prioridad de la interrupcin, bits
1, 0 (00 = no habilitada)
ICTxR: Input Capture Trigger x Register, controla la o las condiciones de
operacin que ocasionan la captura de un evento y el modo de
funcionamiento del contador, para cada mdulo (1 y 2):
bits 7, 6: 00 = el contador no funciona
01 = el contador arranca al ocurrir start y se detiene al
ocurrir stop
10 = el contador avanza de forma continua
11 = el contador avanza de forma continua, pero se
detiene al ocurrir stop
bits 5, 4: 00 = el mdulo no registra el valor del contador al ocurrir
un evento
01 = el mdulo registra la cuenta ante la condicin de
stop
10 = el mdulo registra la cuenta ante la condicin de
start
11= el mdulo registra la cuenta ante ambas condiciones
bits 3, 2: 00 = no hay condicin de start
01 = asigna flanco ascendente a condicin de start
10 = asigna flanco descendente a condicin de start
11 = asigna ambos flancos a condicin de start
bits 1, 0: 00 = no hay condicin de stop
01 = asigna flanco ascendente a condicin de stop
10 = asigna flanco descendente a condicin de stop
11 = asigna ambos flancos a condicin de stop
ICSxR: Input Capture Source x Register, realiza la asignacin de un pin de un port
paralelo a las condiciones de ese mdulo. El nibble superior controla la
asignacin de la condicin de arranque, y el nibble inferior controla la
asignacin de la condicin de detencin:

59

Perifricos 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 1
01 = bit 3
10 = bit 5
11 = bit 7
El nibble inferior es igual, pero con los bits 3, 2 y 1, 0; respectivamente
ICLxR: Input Capture LSB x Register
ICMxR: Input Capture MSB x Register, contienen el valor capturado, segn la
seleccin de ICTxR. Al leerse el registro ICLxR, automticamente 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 un
pin de I/O, el cual controlamos de forma precisa inhibiendo las interrupciones y
realizando la operacin en assembler.

PE.0

PF.7

Configurado el mdulo 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 sta
corresponder al ancho del pulso en las unidades que cuente este mdulo. Al
clockearlo 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 11
ciclos de clock, unos 0,5us. Como es de esperarse, no es posible medir un ancho de
pulso de esta magnitud simplemente leyendo el valor de un timer en dos
interrupciones sucesivas, ya que el ancho del pulso generado es menor que la
latencia esperada para atender una sola de las interrupciones.

60

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);

/* 01
01
01
10

=
=
=
=

WrPortI(ICCSR,&ICCSRShadow,1<<2);

// reset mdulo 1, no ints

WrPortI(ICCR,&ICCRShadow,0);

// no ints

WrPortI(ICT1R,&ICT1RShadow,'\B01010110');
el contador arranca al ocurrir start y se detiene al ocurrir stop
el mdulo registra la cuenta ante la condicin de stop
asigna flanco ascendente a condicin de start
asigna flanco descendente a condicin de stop */
WrPortI(ICS1R,&ICS1RShadow,'\B10111011');
WrPortI(TAT8R,&TAT8RShadow,0);
RdPortI(ICCSR);

// PF.7 start y stop


// max speed

#asm
ipset 3
ld hl, PEDR
ioi set 0,(HL)
ioi res 0,(HL)
ipres

; 22MHz clock, 1/22 us; 11 cycles = 1/2 us


; ~11 cycles

#endasm
if(BitRdPortI(ICCSR,4))
// condicin de stop
printf("Cuenta: %d (5 a 22MHz)",RdPortI(ICL1R));
}

Timer C (R4000+)
El timer C, disponible slo en Rabbit 4000 y 5000 es un contador ascendente de
mdulo variable. Incluye cuatro canales con registros de comparacin que permiten
setear y resetear una salida en determinadas cuentas.
Los registros de configuracin del Timer C son:
TCCSR: Timer C Control/Status Register, contiene el flag de overflow (bit 1) y el bit
de control (bit 0) para habilitar el timer
TCCR: Timer C Control Register, contiene los bits de prioridad de interrupcin (0
y 1) y los de seleccin de reloj del contador (bits 3 y 2):
00 = PCLK/2
01 = Timer A1
1x = PCLK/16
TCDLR y TCDHR: Timer C Divider Low y High Registers, permiten configurar el
mdulo del contador. El timer se resetea al alcanzar esta cuenta.

61

Perifricos Internos
TCSxR: Timer C Set x Registers, configuran el valor de comparacin que causa el
seteo del pin
TCRxR: Timer C Reset x Registers, configuran el valor de comparacin que causa
el reset del pin
Tambin 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

IO Config

IO Config
El Rabbit 4000 mantiene los perifricos del Rabbit 3000, incorporando un
controlador Ethernet y una MMU con mayor poder de direccionamiento, en el
mismo encapsulado. Esto, sumado al requerimiento de distribucin de los pines de
alimentacin, hace que se eliminen dos puertos paralelo (respecto a Rabbit 3000,
claro est). La necesidad de arbitrar una gran cantidad de perifricos en una menor
cantidad de pines, genera a su vez el requerimiento de tener que contar con varios
niveles de asignacin, de modo que el usuario tenga mayor versatilidad a la hora de
elegir qu pines utilizar, sin que ningn perifrico quede sin poder ser utilizado
porque comparta los pines con otro, que ya fue usado. En un micro con la
complejidad del protagonista de este apndice, recordar, o incluso compendiar la
cantidad de combinaciones posibles es tarea que an un obsesivo rehusara. En un
alarde de humanidad y buen criterio, el fabricante nos agasaja con un programa
digno de su contemporaneidad, el cual nos permite configurar los perifricos,
seleccionar qu vamos a poner en cada pin, y generar el cdigo de inicializacin
apropiado:

63

Perifricos Internos
Dicho programa se llama IO Config, y resulta instalado de forma automtica. El
mismo resulta adems una excelente herramienta para visualizar la operacin de
algunos de los perifricos. Por ejemplo, la imagen siguiente muestra la operacin de
los generadores de PWM. Los canales 1 y 4 estn en modo "normal", el canal 2 en
modo spread, y el canal 3 suprime 1 de cada 2 pulsos. Las interrupciones se generan
a 1 de cada 4 ciclos (se suprimen 3 de cada 4).

El Rabbit 5000 complica an ms las cosas en cuanto a pinout, pero mantiene


bsicamente la misma estructura de perifricos que el Rabbit 4000.
Habiendo introducido al hroe salvador y justificado la razn de su existencia, nos
someteremos a l para todas nuestras necesidades de configuracin de perifricos en
el Rabbit 4000 y el 5000.

64

Perifricos

Bus de datos
Si bien existe siempre la posibilidad de comunicarse con memorias o perifricos
mediante la tcnica de bit-banging, es decir, simular el timing mediante el control
por software de los pines de I/O del micro; nos referimos en este captulo a la forma
de aprovechar la presencia del bus de datos (y por qu no del de direcciones) a la
hora de conectarnos con memorias o perifricos externos al mdulo Rabbit.

Memorias FRAM paralelo


Si bien disponemos de una amplia capacidad de memoria, y la posibilidad de
respaldar la RAM mediante una pila, puede llegar a ser interesante emplear una
tecnologa de memoria no voltil con tiempo de acceso equivalente a una RAM,
cantidad de accesos virtualmente ilimitados, y sin consumo de energa para mantener
los datos, como por ejemplo las FRAM (Memorias de acceso aleatorio de tecnologa
ferroelctrica) de Ramtron, como la FM1808.
Ms all de las diferencias tecnolgicas, este tipo de memorias se comporta, desde
el punto de vista de su interfaz con un micro, de igual forma que una RAM esttica.
La nica diferencia es que debido a timing interno, debe tener pulsos en la seal de
CS , es decir, no podemos dejarla conectada directamente a masa:
CS
Address
Data

OE
WE

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

Perifricos
dado que la memoria en cuestin es de 32K. El nico inconveniente que se nos
puede presentar con un mdulo, es el hecho de que no todas las lneas de address
estn accesibles en todos los mdulos. En este caso, utilizaremos un RCM2100, que
tiene accesibles desde A0 a A12, coincidente con el espacio de direccionamiento de
8K de los IOSTROBES. Entonces, solamente deberemos generar manualmente dos
de las lneas de address (A13 y A14), y lo haremos utilizando el port B para esto:
D0
D1
D2
D3
D4
D5
D6
D7

D0
D1
D2
D3
D4
D5
D6
D7

PE.0

CS

IORD

OE

IOWR

WE
FM1808

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
PB.6
PB.7

Como vemos, la FRAM aparece en el mapa de memoria del Rabbit, en el espacio de


I/O externo, como cuatro bancos de 8KB situados en el rea entre 0x0000 y 0x1FFF.
Una pequea rutina se encarga de extraer el valor de A13 y A14 para colocarlo en
PB.6 y PB.7, seleccionando el banco a acceder. Todo el timing es generado
automticamente al realizar una operacin de I/O; sin embargo, la configuracin del
IOSTROBE merece un poco ms 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 tiempo
de precarga de 60ns. Esto significa que el tiempo mnimo de activacin de CS
deber ser de 70ns ms lo que la capacidad del bus agregue, y deberemos esperar un
mnimo de 60ns entre dos accesos. La segunda es que debido a que en la FRAM se
registra (latch) el valor de las lneas de direcciones al momento de producirse el
flanco descendente de CS , las mismas debern estar estables 4ns antes de este
momento. Si observamos el grfico de operacin de IOSTROBE, veremos que las
lneas de address y el chip select strobe cambian casi simultneamente, mientras que
tanto read strobe como write strobe cambian aproximadamente un ciclo de clock
despus:

66

Bus de datos

Si observamos en detalle el timing, en el Manual del Usuario, observaremos que


debido al jitter natural y la diferente capacidad de carga de ambas seales (addresses
A[15:0] y /IOCSx), no es posible garantizar que se cumpla esta condicin:

67

Perifricos

Deberemos entonces configurarlo como read strobe y write strobe. En este modo
de trabajo, IOCS se activa junto con IORD y IOWR , lo cual no es
inconveniente. En el RCM2100 tenemos un clock de 22MHz, lo que significa que
cada ciclo de clock es de 45,45ns. Sabemos que una operacin de I/O demora como
mnimo 3 ciclos; pero como vemos en los grficos, el ancho de pulso de un read
strobe es de dos ciclos (Tw+T2) y un write strobe llega a un ciclo y medio (Tw+1/2
T2), ms o menos el consabido jitter. En el caso que usramos una instruccin de
repeticin como LDIR, nuestro tiempo de precarga (tiempo entre desactivacin y
activacin siguiente de CS ) sera solamente de un ciclo de clock (T1). Para poder
cumplir con todos estos requerimientos, deberemos insertar un ciclo de espera
adicional, el mnimo que se nos permite configurar (mayor que 1) es de 3 wait-states.
El timing generado, ser entonces el siguiente:

68

Bus de datos

PB.6
PB.7
PE.0

write
strobe

read
strobe

Address
A[0:12]
Data
D[0:7]
IOWR

IORD

A continuacin, la inicializacin, configuracin, y la pequea rutina que hace "que


todo 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/O
WrPortI(PEFR, &PEFRShadow, (PEFRShadow|DSTROBE));
// Inicializa bit en Port E bit como salida
WrPortI(PEDDR, &PEDDRShadow, (PEDDRShadow|DSTROBE));
// Inicializa bit en Port E como IOSTROBE con sus wait-states.
WrPortI(DCSREGISTER, &DCSSHADOW, DCSCONFIG);
// Port E clockeado por PCLK/2
WrPortI(PECR, &PECRShadow, (PECRShadow & ~0xFF));
}
#asm
;@sp+2= 1st param, address
;@sp+4= 2nd param, data to write
;
FRAM_Write::
ld hl,(sp+2)
call doaddress
ex de,hl
ld hl,(sp+4)
ld a,l
ex de,hl
ioe ld (HL),a
ret

; address
; pone MSbs en I/O
; data
; escribe

69

Perifricos
;@sp+2= 1st param, address
;
FRAM_Read::
ld hl,(sp+2)
call doaddress
ioe ld a,(HL)
ld l,a
ld h,0
ret
doaddress:
ld a,h
and 0xE0
rla
ioi ld (PBDR),a
res 7,h
res 6,h
res 5,h
ret
#endasm

; address
; pone MSbs en I/O
; lee

;
;
;
;

limpia bits 7,6,5


bits 7,6 = A14,A13
A14,A13 en PB.7,PB.6
resto en address bus (A0-A12: 8K)

Finalmente, un pequeo 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 simple
desde el punto de vista del hardware, con un bus de datos y otro de control. La
nomenclatura es algo confusa y vara con el fabricante, pero por lo general acepta
generalizaciones. La cantidad de bits en el bus de datos y la forma de operacin de
las seales de control y su relacin con el bus de datos dependen de las
caractersticas propias de cada display. Vamos a describir a continuacin una serie
de displays comnmente denominados como "genricos", y que por su precio y
disponibilidad son particularmente atrayentes.

Breve descripcin del display color


En este tipo de displays color de 320x240 pixels, las seales de control se encargan
de indicar los instantes de comienzo de trama vertical (FPFRAME o FRM) y lnea
horizontal (FPLINE, LOAD, o CL1), mientras que la informacin de color para
grupos de pixels es leda a cada intervalo de clock (FPSHIFT, DP, o CL2), 8-bits a
la vez (FPDAT0 a FPDAT7). La correspondencia entre estos 8-bits y la informacin
de rojo (R), verde (G) y azul (B) de pantalla es algo compleja, en realidad se
70

Bus de datos
comienza enviando el contenido RGB de dos pixels adyacentes, ms RG de un tercer
pixel, y luego se va completando hasta enviar GB del antepenltimo pixel de la
ltima lnea, y RGB del penltimo y ltimo pixels. Esto vara en cuanto al orden
segn el tipo de display, pero guarda una relacin similar; pudiendo observarse
mejor en el diagrama que sigue a continuacin, junto con una idea genrica y
aproximada del timing de una trama:

FPFRAME
1

FPLINE

239

240

FPLINE

FPSHIFT

FPDAT7

R1 B3

G318

R1

FPDAT6

G1 R4

B318

G1

FPDAT0

G3 R6

B320

G3

Como puede observarse, la informacin de color es RGB 1:1:1 (1 bit para cada
color por cada pixel). Esta informacin controla el polarizador transmisivo
vinculado al color correspondiente, que dejar pasar o no la componente de color
rojo (R), verde (G) o azul (B), segn se trate, filtrando la luz blanca proveniente de
una iluminacin posterior (backlight) que generalmente es en base a CCFL (Cold
Cathode Fluorescent Light).
Sin embargo, esto no significa que solamente podamos tener ocho colores en
pantalla, con 0 100% de saturacin de cada una de las componentes. Mediante la
modificacin de esta informacin trama a trama, es posible generar una gama muy
interesante de colores. Si la velocidad de actualizacin de trama es lo
suficientemente elevada, y el display responde a dicha velocidad, el ojo no percibe el
parpadeo y se logra apreciar una gama de colores mucho ms amplia.
En cada lnea, luego de la informacin correspondiente a los 320 pixels, existe una
serie de pixels sin informacin; y luego de las 240 lneas horizontales, existe una

71

Perifricos
serie de lneas sin informacin, conformando una trama. Esto tiene la finalidad de
adecuar la frecuencia de actualizacin a lmites manejables por el display.
Si bien la generacin de este tipo de timing no es demasiado compleja, para una
frecuencia de actualizacin de cuadro (trama) de unos 50 Hz o ms, la frecuencia de
la seal FPCLOCK comienza a ascender al orden de los MHz, lo que empieza a ser
algo no tan simple de manejar, y es conveniente utilizar algn controlador que nos
haga ms liviana la tarea.
Existe adems de las vistas, una cuarta lnea de control denominada LCDPWR o
DISP, que cumple la funcin de activar el display. La misma debe ponerse en estado
activo luego de aplicada la alimentacin (preferentemente una vez que el hardware
de control de timing est inicializado) y retirarse antes de la desaparicin de la
misma, para minimizar inconvenientes en el funcionamiento del display y maximizar
su vida til.
La tensin de alimentacin del display puede ser de 3 5V, y la que regula el
contraste se ubica en el orden de los 25V.

Breve descripcin 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 nos
permita obtener la mejor performance, minimizando el hardware asociado y los
problemas de timing.
El elegido es un controlador para displays LCD de alta resolucin de Epson, el
S1D13706, permitiendo controlar no slo displays color pasivos sino tambin blanco
y negro y TFT. Si bien la configuracin de un dispositivo de esta envergadura es
algo compleja, afortunadamente el fabricante se apiada de nosotros y nos provee un
software que nos permite, a partir de los valores operacionales del display, generar
los datos a escribir en los diferentes registros.
En el controlador, la imagen a enviar al display se aloja en una RAM interna de
80KB (81920 bytes), la cual es direccionada de forma lineal por diecisiete lneas de
address, y accesible mediante un bus de datos de 16-bits. En un espacio de
direccionamiento de 8-bits adicional al de memoria, se encuentran los registros de
control que nos permiten modificar el funcionamiento a voluntad. La interfaz entre el
controlador y el procesador host puede elegirse entre una variedad de procesadores
especficos soportados y dos modos genricos, mediante pines destinados a tal fin. El
arbitraje de la memoria entre la generacin de la imagen en el display y el acceso por
parte del procesador se resuelve mediante la peticin de wait-states al procesador;
sin embargo, este controlador de avanzada presenta un tiempo garantizado mximo,
lo que permite funcionar sin wait-states si se garantiza una mnima duracin al ciclo
de acceso, razn por la cual lo seleccionamos.

72

Bus de datos
La tensin de operacin 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 ms bajas
El controlador se encarga de todo lo referente al despliegue de la imagen, la cual
reside, como dijramos, en su memoria interna (80KB). Mediante los registros de
control, es posible indicar en qu zona de memoria comienza la pantalla y su
tamao, as como tambin la cantidad de bits asignados a definir el color de cada
pixel, lo cual a su vez determinar la distribucin de la memoria.
El controlador opera asignando una cantidad de bits a cada color primario. En este
caso se trata de seis bits para cada uno. Esto determina una paleta RGB 6:6:6, es
decir, 218=256K (262144) colores posibles. En la memoria de pantalla, segn el
modo de resolucin seleccionado, se asignar una determinada cantidad de bits para
cada pixel. Si asignamos ocho bits tendremos 8 bpp (8 bits por pixel); si asignamos
cuatro bits tendremos 4 bpp (4 bits por pixel). Para 8bpp, cada byte representa un
pixel 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 se
empaquetan dos pixels por byte y el pixel menos significativo (el "de la izquierda")
ocupa el nibble ms significativo.
En el caso de trabajar con 4bpp, se pueden mostrar en pantalla un mximo de 16
colores simultneos, elegidos de una paleta de 262144 colores posibles. En el caso
de 8bpp, son mximo 256 colores simultneos, elegidos de una paleta de 262144
colores posibles. El controlador maneja todo esto de forma transparente, el
programador especifica el modo de operacin, carga la paleta, y trabaja con los
ndices, similar a como se hara con cualquier pantalla o formato de archivo de
imagen en modo indexado (indexed-color).
Dada la capacidad de RAM y la estructura interna del controlador, es posible
almacenar varias pantallas en memoria y cambiar la posicin de inicio, siempre y
cuando, claro est, su tamao y resolucin permitan que quepa. Existen adems
algunas funciones de soporte adicionales como picture-in-picture y rotacin de la
imagen 90, las que lamentablemente no hemos tenido tiempo de explotar
oportunamente.

Desarrollo
Enumeramos a continuacin los puntos principales que evaluamos para este
desarrollo:
Debido a que el controlador puede funcionar a 3,3 V o menos, hemos elegido un
mdulo RCM3300.
Podemos emplear el bus de Rabbit para agilizar la operacin. Dado que Rabbit
2000 y 3000 no tienen lnea de WAIT , READY, o equivalente, sino que
generan una insercin automtica de wait-states por configuracin,
configuraremos la cantidad de ciclos de espera necesarios para garantizar un
tiempo de acceso mnimo que sea mayor al especificado para el controlador;
73

Perifricos

siempre ser ms rpido que interrogar manualmente la lnea de WAIT .


Debido a que no todo el bus de direcciones est disponible en los mdulos (s en
el procesador para quien quiere desarrollar su propio hardware), generaremos
parte de las direcciones pertinentes mediante I/O.
En cuanto al bus de datos de 16-bits, lo que haremos es duplicar nuestros 8-bits
conectando parte alta y parte baja del bus entre s. De este modo, la informacin
siempre sale del Rabbit por los mismos ocho pines, y el controlador la lee por los
ocho altos o los ocho bajos, segn indique la seal BHE (Bus High-byte
Enable), que no es otra cosa que la inversin 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 un
RCM3300, deberemos insertar unos 15 ciclos de espera en el rango de I/O
externo a utilizar. Dado que empleamos PE.4, este rango ser de 0x8000 a
0x9FFF; seis de los bits menos significativos del bus de direcciones estn
accesibles 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 memoria
del Rabbit, en el espacio de I/O externo, como 2048 bancos de 64 bytes que se
repiten en el rea entre 0x8000 y 0x9FFF (0x8000 a 0x803F, repite de 0x8040 a
0x807F, etc.). Los pines de I/O adicionales seleccionan el banco. La razn por la
que hacemos esto es porque no disponemos de todo el bus de address en estos
mdulos.
Operando sobre un pin adicional, PC.2, elegimos si accedemos a la RAM o al
espacio de registros internos del controlador

Entre las limitaciones de cantidad de pines y posibilidad de operacin, elegimos


aqullos que nos agilizaban las rutinas de manejo, sin interferir con los perifricos
adicionales presentes en el kit de desarrollo utilizado (RCM3360).
S1D13706
D0
D1
D2
D3 AUX I/O
D4
D5 Data bus
D6
D7

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

IORD
IOWR
PB.0
IOSTROBE PE.4
PC.2

74

AB0
DB0
AB1
DB1
AB2
DB2
AB3
DB3
AB4
DB4
AB5
DB5
AB6
DB6
AB7
DB7
AB8
DB8
AB9
DB9
AB10
DB10
AB11
DB11
AB12
DB12
AB13
DB13
AB14
DB14
AB15
DB15
AB16
RD
WE0
WE1 (BHE)
CS
M/R

PB.2
PB.3
PB.4
PB.5
PB.6
PB.7
PD.6
PD.7
PG.0
PG.1
PG.2
PG.3
PG.4
PG.5
PG.6
PG.7
PC.4

A0
AUX I/O
A1
A2
address bus A3
A4
A5

Bus de datos
La alimentacin del controlador la hacemos como indica el diagrama siguiente,
mediante los pines separados para core e I/O. El grfico tambin incluye los pines de
configuracin y el oscilador de reloj:
(3,3V)

S1D13706
(3,3V)
Vdd

CNF0
CNF1
CNF2
CNF3
CNF4
CNF5
CNF6
CNF7
RD/WR
BS

Vdd

IOVDD
COREVDD
CLKI

OSC
50MHz

GND

Finalmente, la conexin con el display se realiza de la siguiente forma:


Vdd (3,3V)

FPDAT0
FPDAT1
FPDAT2
FPDAT3
FPDAT4
FPDAT5
FPDAT6
FPDAT7
FPSHIFT
FPLINE
FPFRAME
GPO
S1D13706

D0
D1
D2
D3
D4
D5
D6
D7

VDD
VLCD, VEE

25V
(contraste)

CL2, CP
CL1, LOAD
FRM
GND
DISP
display

Para escribir los drivers, debemos hacer las siguientes consideraciones:


Tenemos un bus de 16-bits simulado dentro de uno de 8-bits, con sealizacin

similar 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 seal BHE .
Nuestro bus de direcciones posee una parte baja "real" y una parte alta simulada
mediante I/O, por lo que luego de realizar la simulacin, procederemos a efectuar
una operacin de I/O externo normal.
Si decidimos utilizar 4bpp, cada pantalla es de 38400 bytes, lo cual cabe
perfectamente dentro de 16 lneas de address y nos permite utilizar los punteros
del micro como enteros (16-bits). Podemos alojar solamente una pantalla
completa, 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 la
complicacin adicional de que nuestro bus de direcciones es ahora de 17-bits, y
los punteros de nuestro procesador son de 16-bits. Recibiremos entonces la
direccin como un long, es decir, 32-bits.
75

Perifricos
A los fines prcticos nos conviene incluir toda la operacin de generacin de

lneas de direcciones en una subrutina. Dado que tendremos bastantes datos para
escribir, necesitamos hacerlo rpido, razn por la cual utilizamos assembler para
las rutinas crticas.
Analizado esto, mostramos a continuacin las rutinas de ms bajo nivel que nos
permiten operar este hardware en 8bpp. Las de 4bpp son algo ms simples, y se
encuentran en las notas de aplicacin que figuran en el CD adjunto. Todo lo
relacionado con assembler y pasaje de parmetros lo hemos visto en el primer
captulo.
#asm root
read13706::
call addressbus
ex de,hl
ioe ld a,(hl)
ld h,0
ld l,a
ret
;sp+2: A0-A15
;sp+4: A16
;sp+6: data
write13706::
call addressbus
ld hl,(sp+6)
ld a,l
ex de,hl
ioe ld (HL),a
ret

; pone addresses y BHE, devuelve address I/O en DE


; lee port paralelo

; pone addresses y BHE, devuelve address I/O en DE


; lee dato
; pone dato

;sp+2: direccin de retorno a la funcin que nos llama


;sp+4: A0-A15
;sp+6: A16
;sp+8: dato
addressbus:
ld hl,(sp+4)
; lee address
ex de,hl
; en DE
ld hl,(sp+6)
; lee address A16
ld a,l
; en A
ab2:
ld hl,PGDR
; apunta a port
ioi ld (hl),d
; pone parte alta
ld hl,PDDR
; apunta a port
ioi ld (hl),e
; pone parte baja (7,6)
ld hl,PCDR
; apunta a port
ioi res 4,(HL)
; A16=0
rrca
; A16=1 ?
jr nc,ok3
; no
ioi set 4,(HL)
; s, A16=1
ok3:
ld a,e
; A= A7-A0
ld hl,PBDR
; apunta a port paralelo
ioi set 0,(hl)
; BHE=1
rrc e
; debo activar BHE ? (test LSB=1)
jr nc,ok1
; no
ioi res 0,(HL)
; s, BHE=0
ok1:
and 0x3F
; A6,A7 = 0
ld e,a

76

Bus de datos
ld d,0x80
ret

; I/O = 0x8000 + A5-A0 = 10000000 00xxxxxx

#endasm

La inicializacin de los pines del micro es la siguiente:


WrPortI
WrPortI
WrPortI
WrPortI
WrPortI
WrPortI
// Port
#define
#define
#define
#define

(
(
(
(
(
(

PGDDR,&PGDDRShadow,'\B11111111' );
PEDDR,&PEDDRShadow,'\B00010000' );
PDDDR,&PDDDRShadow,'\B11000000' );
PBDDR,&PBDDRShadow,'\B11111111' );
PBDR,&PBDRShadow,'\B11000000' );
PCDR,&PCDRShadow,'\B11101111' );

// AB16=0, M/R = M

E bit 4 Chip Select 15 wait-states


STROBE
0x10
CSREGISTER
IB4CR
CSSHADOW
IB4CRShadow
CSCONFIG
0x08
// Inicializa bit como I/O
WrPortI(PEFR, &PEFRShadow, (PEFRShadow|STROBE));
// Inicializa bit como salida
WrPortI(PEDDR, &PEDDRShadow, (PEDDRShadow|STROBE));
// Inicializa bit como chip select.
WrPortI(CSREGISTER, &CSSHADOW, CSCONFIG);
// clockeado por PCLK/2
WrPortI(PECR, &PECRShadow, (PECRShadow & ~0xFF));

Algoritmos
A continuacin, haremos una somera descripcin de los algoritmos involucrados en
el desarrollo de funciones para este tipo de displays. Un anlisis ms exhaustivo,
junto con lo relacionado a la inicializacin del controlador y el software desarrollado
en base a estos algoritmos, se encuentra en las notas de aplicacin que se alojan en el
CD que acompaa a esta edicin. CAN-035 desarrolla soporte en 4bpp y CAN-036
lo hace para 8bpp. Como ambas fueron hechas originalmente con un clock de menor
frecuencia, CAN-037 analiza las diferencias para el clock de 50MHz que finalmente
utilizamos.
Para ubicar un punto en pantalla en 8bpp, calculamos su posicin en memoria
sabiendo que alojamos un pixel por byte, es decir: mem x320y . Para
x
hacerlo en 4bpp, la frmula es, mem 160y dado que tenemos dos pixels
2
x
nos dir qu nibble utilizar
por byte; el resto de
2
Para graficar funciones, debemos tener en cuenta que la coordenada (0;0) se halla
en el extremo superior izquierdo de la pantalla.
Para mostrar pantallas, deberemos agrupar los datos de modo tal de poder
enviarlos de una forma que aproveche de manera eficiente la estructura de
memoria. Si comparamos la estructura de memoria del display con la forma de
77

Perifricos
guardar imgenes en 256 y 16 colores (para 8bpp y 4bpp respectivamente) en
formato BMP, veramos que son muy similares, por ejemplo: BMP va de abajo a
arriba y el display de arriba a abajo, por lo que la imagen se ve espejada
verticalmente. Adems, BMP incluye un encabezado que contiene la paleta de
colores. Por consiguiente, para adaptar una imagen en 4bpp, debemos llevarla a
la resolucin deseada, reducirla a 16 colores, espejarla verticalmente, salvarla en
formato BMP y por ltimo descartar los 118 bytes del comienzo con algn editor
hexadecimal. Entre los bytes a descartar tomaremos los bytes 54 a 117, los cuales
corresponden a la paleta en formato BGR0 (4 bytes), y la guardaremos como
RGB. Para adaptar una imagen 8bpp, debemos llevarla a la resolucin deseada,
reducirla a 256 colores, espejarla verticalmente, salvarla en formato BMP y por
ltimo descartar los 1078 bytes del comienzo. Entre los bytes a descartar
tomaremos los bytes 54 a 1077, los cuales corresponden a la paleta en formato
BGR0 (4 bytes), y la guardaremos como RGB. Dado que esto ltimo es algo
tedioso, se recomienda desarrollar un pequeo programita que lo haga, cosa que
quien escribe ya ha hecho y acompaa a este texto dentro del CD con
informacin adicional (CAN-038).

Para desplegar textos, deberemos generar las letras manualmente, "pintando" (o


no) los pixels en el display con el formato del caracter. La forma ms comn (y
bastante eficiente) de almacenar tipografas en memoria, consiste en agrupar los
pixels "pintados" del caracter en bytes, en el sentido horizontal, es decir, un byte
aloja ocho pixels que corresponden a la parte superior del caracter, de izquierda a
derecha, de MSb a LSb. Si el ancho del caracter es mayor a diecisis pixels,
entonces se utilizarn grupos de dos bytes. sta es la forma en la que se alojan las
tipografas provistas con las bibliotecas de funciones de Dynamic C, y con un
simple algoritmo las podemos convertir para su uso con un controlador de
displays 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, podemos
utilizar un color de fondo, o dejarlo sin modificar.
Un ltimo comentario. Recordemos que una pantalla consiste de 320x240 pixels, lo
cual son 76800 bytes en 8bpp. Semejante cantidad de informacin requiere
almacenamiento en xmem, y una rutina eficiente de copiado hacia el display para que
no se note un barrido molesto. Analizamos una de estas rutinas en el captulo sobre
manejo de memoria extendida.

Displays LCD grficos


En el libro introductorio sobre desarrollo con Rabbit, mientras veamos algunos
ejemplos, deslizamos la forma de controlar un display grfico inteligente LCD de
320x240 al bus del procesador. Estos displays, junto con algunos displays OLED,
tienen la particularidad de que el controlador est incluido en el mdulo que se
adquiere, es decir, directamente podemos conectarnos al micro con el display. Por lo
78

Bus de datos
general, la interfaz es del tipo Intel 8080, o permite configurarse como tal, con lo
cual es compatible con Rabbit, y podemos conectarlo al bus sin problemas. En los
casos 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 muy
similar a lo visto para el controlador del display color, se trata simplemente de
configurar los IOSTROBES y escribir pequeos segmentos de cdigo a modo de
drivers. Utilizamos el bus de datos; dado que estos displays son mayormente de 5V
empleamos un R2000:
+5V

PG320240
D0
D1
D2
D3
D4
D5
D6
D7

D0
D1
D2
D3
D4
D5
D6
D7

IORD
IOWR
A0

RD
WR
A0

PE.7

CS

RESET

RST

#asm
;la funcin requiere dos parametros:
;@sp+2= address
;@sp+4= data
;
LCD_Write::
ld hl,(sp+4)
ld a,l
ld hl,(sp+2)
ioe ld (HL),a
ret

; dato (LSB)
; address (LSB)
; escribe

#endasm

En el caso particular del 320x240, existe una coleccin de bibliotecas de funciones


de Rabbit que resuelven todo el tema de presentacin en pantalla, con un soporte
grfico muy poderoso, incluso permitiendo conectar pantallas sensibles al tacto. El
software realiza la lectura de la pantalla y permite dibujar botones con imgenes o
textos, y ventanas con texto justificado en su interior. A fin de no ser reiterativos,
simplemente recordaremos que toda esta informacin est bajo la forma de notas de
aplicacin en el CD que acompaara al libro introductorio, aunque tambin las
79

Perifricos
hemos incluido en el CD que se adjunta con esta edicin. CAN-013 y CAN-014
analizan el soporte que incluye Rabbit para displays LCD y pantallas sensibles al
tacto, CAN-015 y CAN-020 estudian estas pantallas y su utilizacin (veremos un
ejemplo de un controlador para touchscreen en la seccin sobre SPI), y finalmente,
CAN-016 y CAN-021 canalizan todo en una biblioteca de funciones: simplemente
incluyendo Cika320240FRST.lib tenemos soporte para displays LCD grficos
320x240 con pantalla sensible al tacto:
#use

Cika320240FRST.lib

Displays OLED
En cuanto a los displays OLED, si bien son tecnolgicamente innovadores, su
hardware de interfaz est cubierto por los anlisis hechos para los otros displays.
Como se observa, utilizamos el bus auxiliar de I/O en un R3000, dados los 3,3V de
operacin del display:
+5V

AUX I/O bus

UG9664GFDAF

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

D0
D1
D2
D3
D4
D5
D6
D7

IORD
IOWR
PB.2

RD
WR
D/C

PE.4

CS

RESET

RST

#define PORTA_AUX_IO
#asm root
;@sp+2= dato/comando a escribir
;
Write_Com::
ld hl,(sp+2)
ld a,l
ioe ld (0x8000),a
ret
Write_Data::
ld hl,(sp+2)
ld a,l

80

; obtiene comando (LSB)


; lo escribe

; obtiene dato (LSB)

Bus de datos
ioe ld (0x8001),a
ret

; lo escribe

#endasm
#define Read_Data() RdPortE(0x8001)

Breve descripcin del display


Estos displays son sumamente verstiles, la memoria puede alojar una pantalla
completa a una resolucin de color de 16 bits por pixel (16bpp), y el controlador
tiene una serie de primitivas grficas que permiten dibujar lneas y rectngulos con o
sin relleno, con un simple comando. Esto simplifica enormemente la tarea del
procesador para la presentacin de menes y dems tareas de una interfaz con el
usuario, hacindolos atractivos incluso para procesadores ms chicos o con menos
memoria. No poseen generador de caracteres, pero es posible definir el rea de
memoria en la cual se escribe, lo que relaja notablemente la tarea de dibujar las
letras de forma manual, permitindonos disponer de varias tipografas.
La estructura de memoria de la pantalla es lineal, similar al controlador para
displays LCD color. Los pixels se agrupan horizontalmente, correspondiendo el
primer byte de memoria al primer pixel de la primera lnea de arriba a la izquierda, y
el ltimo byte al ltimo pixel de la ltima lnea de abajo a la derecha, para el modo
de 8bpp. En el modo de 16bpp, deberemos reemplazar byte por word en el texto
anterior. De todos modos, la estructura puede ser cambiada alterando los registros
del controlador que (valga la redundancia) la controlan. Por comodidad y similitud
con los otros displays ya estudiados, la mantendremos as, seteando el modo
remapped del controlador.
El direccionamiento del byte a leer o escribir en memoria se hace mediante
comandos, especificando la regin de pantalla que nos ocupa, en pixels. El
controlador tiene adems un contador auto-incrementado, el cual apunta a la
direccin siguiente luego de una lectura o escritura. Esto resulta ptimo para enviar
los datos byte por byte hasta completar el rea elegida; sea para dibujar un caracter o
un cono.
Una caracterstica interesante del display, es que puede funcionar a una alta
velocidad de acceso, sin necesidad de contencin 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 una
serie de notas de aplicacin escritas para la empresa que comercializa estos displays
en Argentina. Las mismas (CAN-029 y CAN-030) se incluyen en el CD que
acompaa a este libro. No obstante, enunciamos las caractersticas principales a
tener en cuenta para el desarrollo de algoritmos:

81

Perifricos
Como el display ya incorpora primitivas para dibujo de lneas y rectngulos,

simplemente utilizaremos estos comandos. Para direccionar un punto, lo que


haremos es trazar una lnea de un punto de longitud.
Para graficar funciones, debemos tener en cuenta que la coordenada (0;0) se halla
en el extremo superior izquierdo de la pantalla.
Para mostrar conos, caracteres, o pantallas, deberemos definir el rea de pantalla
igual a la del bitmap en cuestin y enviar los datos, de esta forma se aprovechan
los contadores auto-incrementados y la estructura de memoria; esto se reduce
simplemente a enviar todos los bytes corridos.
En el caso de pantallas, si comparamos la estructura de memoria del display con
la forma de guardar imgenes de 24-bits en formato BMP, veramos que son muy
similares, 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 informacin de color y el display utiliza dos bytes o uno, segn el
formato. Adems, BMP incluye un encabezado de 54 bytes. Por consiguiente,
para adaptar una imagen, debemos llevarla a la resolucin deseada, espejarla
verticalmente, salvarla en formato BMP y luego de descartar los 54 bytes del
comienzo, procesar la paleta al formato del SSD1332 en el modo de color que
vayamos a utilizar. Esta tarea est cubierta en CAN-031, nota adjunta en el CD
de informacin adicional.
Para imprimir textos, indicamos la posicin en pantalla mediante sus
coordenadas y restringimos el rea de escritura al tamao del caracter. Luego,
escribiremos uno o dos bytes (segn el modo de color) por cada pixel del
caracter, todos de corrido.

Finalmente, recordemos que una pantalla en 16bpp consiste de 2x96x64=12288


bytes. Si bien no es demasiado, si alojamos varias pantallas se requiere
almacenamiento en xmem. Una rutina eficiente de copiado hacia el display no es tan
necesaria como en el caso del display color LCD, pero no est de ms. Analizaremos
una de estas rutinas en el apartado sobre manejo de memoria en otro de los captulos
de este texto.

Buses serie
Existe una gran cantidad de buses serie, es decir, esquemas o normas para que
distintos tipos de perifricos de diversos fabricantes puedan dialogar con
microprocesadores o microcontroladores, mediante una cantidad reducida de
conexiones, en las cuales la informacin se transmite en serie. De los existentes,
tomamos los ms comunes, y luego de una breve introduccin a la tecnologa y
operacin, describimos como aprovechar la implementacin en Rabbit.

82

Buses serie

SPI
Se trata de una interfaz serie sincrnica, SPI significa Serial Peripheral Interface
(Interfaz Serie para Perifricos). En esencia, existe una conexin en sentido masterslave (MOSI: Master Out, Slave In) y otra en sentido slave-master (MISO: Master
In, Slave Out). Ambas cambian sus datos al ritmo marcado por la seal de reloj. El
perifrico (slave) es seleccionado mediante una seal de chip select, y es posible
conectar varios dispositivos en daisy-chain1, de modo que los datos enviados por el
master van ingresando en un dispositivo, saliendo y luego ingresando en otro y as
sucesivamente. Cada dispositivo es seleccionado como dijramos, por su
correspondiente chip select.
Debido a la existencia de dos vas de comunicacin, SPI permite operar en full
duplex. Tanto los datos entrantes como los salientes estn sincronizados por una
misma seal 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 slo la nomenclatura y
el empleo de stas cambia segn el fabricante, sino que aun los "modos" o "tipos"
pueden no coincidir.
A fin de poder estudiarlo y manejarlo, observamos que las combinaciones posibles
de CPOL y CPHA definen cuatro modos de trabajo, los cuales enumeraremos
tomando CPOL como ms significativo que CPHA:
Modo 0: Reloj reposa en estado lgico bajo, datos vlidos en flanco ascendente:
los datos, tanto del master como del slave, cambian de estado en el flanco
descendente. Tanto el master como el slave muestrean los datos en el flanco
ascendente, de modo que siempre sean vistos en el punto en que son ms estables
(la mitad del tiempo de bit).
Modo 1: Reloj reposa en estado lgico bajo, datos vlidos en flanco descendente
(CPHA=1, reloj adelantado 180).
Modo 2: Reloj reposa en estado lgico alto, datos vlidos en flanco descendente
(CPOL=1, reloj invertido)
Modo 3: Reloj reposa en estado lgico alto, datos vlidos en flanco ascendente
(CPOL=CPHA=1, reloj invertido y adelantado 180)

La traduccin de daisy-chain es cadena de margaritas, lo cual viene a modo alegrico de la forma


de armar collares o brazaletes con estas simpticas florecillas, insertando el tallo de una margarita en
el centro de la anterior, y as sucesivamente hasta cerrar un crculo del tamao deseado; clara
analoga de la conexin de dispositivos electrnicos entre s, la salida de uno con la entrada del otro,
hasta cerrar el lazo...

83

Perifricos
CS
CPOL=0
CPHA=0

CLK

CPOL=0
CPHA=1
CPOL=1
CPHA=0
CPOL=1
CPHA=1
MOSI
MISO

MSb

LSb

Segn el fabricante, los "modos 0 al 3" pueden llamarse "tipos 1 al 4". La


especificacin original es de Motorola (hoy Freescale), y observando hojas de datos
con cierta experiencia (68HC05C4, 68HC11 Reference Manual), no se observa que
se 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 estas
especificaciones "originales", es que en los modos CPHA=1, el chip select puede
permanecer activo, pero en los modos CPHA=0, se especifica que debe ponerse
inactivo luego de cada byte.
El descripto como "modo 0" es el "standard de facto", dado que muchos
dispositivos no configurables utilizan este modo de operacin. Obviamente master y
perifricos deben trabajar en el mismo "modo".
En muchos casos la salida del dispositivo se coloca en alta impedancia cuando no
se lo selecciona, esto permite unir varios dispositivos en un solo cable y obviar la
cadena de margaritas
Dado que si bien hay varias combinaciones, no se trata de otra cosa que de hacer
cambiar de estado un par de pines de manera consistente; una interfaz SPI puede
implementarse con una USART, un (par de) registro(s) de desplazamiento, o por
software. Debido a que generalmente una USART transmite el bit menos
significativo (LSb) primero, y SPI transmite el bit ms significativo (MSb) primero,
probablemente deber utilizarse algn esquema para permutar el orden de los bits si
se 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, es
decir, puertos serie con capacidad sincrnica, o tambin puede implementarse sobre
un puerto paralelo, conmutando los pines por software.
En el R2000, el clock es fijo para una USART, lo cual se corresponde en SPI a lo
que sera CPOL=CPHA=1, es decir, los datos cambian en el flanco descendente y
son muestreados en el flanco ascendente, pero el clock reposa en estado lgico alto.

84

Buses serie
En el R3000, operando sobre los bits 5, 4 del SxER (Serial port x Extended
Register), se pueden obtener las cuatro combinaciones. La combinacin 01 es
equivalente a lo que describimos como "modo 0", que es el ms usado.
En ambos casos, como la USART transmite el LSb primero, se deber permutar el
orden de los bits tanto en los bytes transmitidos como en los recibidos. La biblioteca
de funciones de SPI se ocupa de esto mediante una bsqueda en tabla, de forma
transparente.
En Dynamic C, la eleccin de la interfaz se realiza mediante las macros:
#define SPI_SER_A
#define SPI_SER_B

para R3000, disponemos adems 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 que
esto no es vlido para R2000), por lo que la correspondencia entre
SPI_CLOCK_MODE, CPOL CPHA, y los modos de trabajo enumerados
anteriormente, es la siguiente:
SPI_CLOCK_MODE SxER 5,4 CPOL CPHA modo enumerado
0

00

01

10

11

Finalmente, la velocidad de operacin la definimos mediante la siguiente macro,


que setea el registro correspondiente del Timer A encargado de determinar el
baudrate del puerto serie en cuestin:
#define SPI_CLK_DIVISOR

Si empleamos el port serie B, y queremos utilizar los pines alternativos del port
paralelo D, lo indicamos mediante la siguiente macro:
#define SERB_USEPORTD

85

Perifricos
Si en vez de una de las USART elegimos utilizar un port paralelo para nuestra
interfaz SPI, lo indicamos mediante la siguiente macro:
#define SPI_MODE_PARALLEL

El software de la biblioteca de funciones opera entonces generando un pulso


descendente sobre un clock que reposa en estado alto. Esto puede invertirse (pulso
ascendente 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 transmisin, PD.0 para clock, y PD.3 para recepcin), o bien pueden
especificarse. En este ltimo caso, deben especificarse todos. Las macros son las
siguientes:
// registro para clock y TxD
#define SPI_TX_REG
PDDR
// bit para TxD
#define SPI_TXD_BIT

// bit para Clock


#define SPI_CLK_BIT

// registro para RxD


#define SPI_RX_REG
PDDR
// mscara 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 segn
corresponda.

Rabbit 4000 y 5000


En Rabbit 4000 y 5000, la interfaz SPI slo puede utilizar cualquiera de las
USART (puertos serie con capacidad sincrnica)
Las USART de estos micros tienen la opcin de transmitir el MSb primero, por lo
que todo transcurre en hardware.
En Dynamic C, la eleccin de la interfaz se realiza mediante las macros:
#define
#define
#define
#define

SPI_SER_A
SPI_SER_B
SPI_SER_C
SPI_SER_D

Dada la complejidad de asignacin de pines, se requiere que el usuario lo haga por


su cuenta. Al menos para el pin de recepcin disponemos de las siguientes macros:
86

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 hemos
visto:
#define SPI_CLOCK_MODE 0

Finalmente, la velocidad de operacin la definimos mediante la misma macro que


para R2000 y R3000, slo que para R4000 y R5000 no utiliza el Timer A sino el
BRG interno que controla el baudrate del puerto serie en cuestin:
#define SPI_CLK_DIVISOR

Uso y ejemplos
Para leer y escribir en la interfaz SPI, contamos con las siguientes funciones:
SPIinit(): Inicializa la USART
SPIRead(): lee una determinada cantidad de bits
SPIWrite(): escribe una determinada cantidad de bits
SPIWrRd(): lee y escribe simultneamente 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 capacidad
para comunicarse con conversores analgico-digitales, lo que nos permitir acceder
al mundo analgico. Sin embargo, tambin es posible conectar controladores de
pantallas sensibles al tacto y acceder a memorias no voltiles como EEPROM
(25x256, etc.), FRAM como las FM25256 FM25L256 de Ramtron, y otros
perifricos. Los ejemplos a continuacin estn orientados a R2000 y R3000,
debiendo modificarse adecuadamente para R4000 y R5000.

Conversores A/D MCP3204 y MCP3208


Como conversor analgico a digital, emplearemos un MCP3204 de Microchip. ste
es un conversor de 12-bits por aproximaciones sucesivas (SAR). Dispone de cuatro
entradas (ocho en el MCP3208) que puede configurar como cuatro canales singleended o dos canales pseudo-diferenciales (el potencial de la entrada IN- no debe
alejarse ms de unos 100mV del potencial de GND). La referencia de tensin debe
87

Perifricos
ser externa, funciona a 3 5V y su velocidad de conversin (12 pulsos de clock)
ronda las 100.000 muestras por segundo a 5V.
Siendo un conversor de 12 bits, su ecuacin de funcionamiento es:
Vi
12
D2 *
, donde Vi es la tensin equivalente de entrada, Vref la tensin de
V ref
referencia, y D el nmero entregado por el conversor. Definimos como tensin
equivalente de entrada a la tensin en el pin IN+ en modo single-ended y a la
diferencia 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 resultado
estar 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 el
flanco descendente, para ser ledos en el flanco ascendente, es decir,
CPOL=CPHA=0 CPOL=CPHA=1 (comnmente conocidos como "0" y "3"), esto
nos permite trabajar con SPI_CLOCK_MODE=0, que es el valor por defecto en
R3000 y el nico en R2000.
Conectaremos el MCP3204 a un port serie sincrnico de un mdulo Rabbit. En este
caso utilizamos el port serie B de un RCM2100. Necesitamos adems una salida
para oficiar de chip enable, utilizaremos PD.0 para tal fin.
De ms est decir que la calidad y estabilidad de la referencia de tensin V ref
influyen directamente sobre la precisin y estabilidad de la medicin, y que la
conexin de las masas analgica y digital es fundamental, junto con una buena
disposicin de las pistas y la utilizacin de planos de tierra. Para el ejemplo, como
simplemente nos interesa desarrollar el driver y observar que funciona, usaremos la
alimentacin del mdulo como referencia y conectaremos un potencimetro entre
Vdd y masa, con su cursor a la entrada del canal CH0; configurado como singleended. El valor de salida corresponder a 0x000 cuando el cursor est a masa y
0xFFF cuando est a Vdd. De acuerdo al blindaje (o la falta de ste) de la conexin a
la entrada, deber ponerse algn capacitor de filtro.
Vdd

CLKB (PB.0)

CLK

RXB (PC.5)

Do

TXB (PC.4)

Di

PD.0

Vref
.1
CH0

CS

10K

AGND DGND
MCP3204

88

.1

Buses serie
Para el software, comenzamos indicando que vamos a utilizar la interfaz SPI en el
port B:
#define SPI_SER_B
#define SPI_CLK_DIVISOR
#use SPI.LIB

Ahora inicializamos el hardware de Rabbit para funcionar con el conversor:


void initAD()
{
BitWrPortI ( PDDR, &PDDRShadow, 1, 0 );
BitWrPortI ( PDDCR, &PDDCRShadow, 0, 0 );
WrPortI ( PDCR, &PDCRShadow, 0 );
BitWrPortI ( PDDDR, &PDDDRShadow, 1, 0 );
SPIinit();
}

// CS = 1 (off)
// PD.0 normal
// PD.0 = output
// inicializa interfaz SPI

Seguidamente, un simple driver para realizar la lectura del A/D. Lejos de ser
eficiente, trata de ser claro, dado que, por experiencia, no es del todo directa la
comprensin del formato del MCP3204. De ser necesaria mxima velocidad, se
sugiere la reescritura de esta funcin, 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 slo
se utilizan los dos menos significativos, el MCP3208 utiliza los tres.
La rutina se invoca con el valor del canal como parmetro y devuelve el resultado
de la conversin en un entero.
#define START
#define SINGLE

0x80
0x40

int ReadAD ( int Channel)


{
int Command, j;
struct
{
char
b;
int
i;
} Data;

// 24 bits

Command=START|SINGLE|((Channel/4)<<5)|((Channel/2)<<4)|((Channel&1)<<3);
Command <<= 3;
// posicin para obtener LSB justif a la derecha
Command=SwapBytes(Command);// pone el MSB primero (Z80 es LSB primero)
BitWrPortI ( PDDR, &PDDRShadow, 0, 0 );
SPIWrRd ( &Command, &Data, 3 );
BitWrPortI ( PDDR, &PDDRShadow, 1, 0 );
j = Data.i;
j = SwapBytes ( j ) & 0x0FFF;
// pone
return(j);

//
//
//
//
LSB

baja CS
transmite y recibe 24 bits
sube CS
toma 16 bits tiles
primero, considera 12 bits

}
// invierte (swap) los bytes de un entero
// parmetro y resultado en HL
#asm
SwapBytes::
ld
a, L
ld
L, H
ld
H, A

; salva LSB
; MSB -> LSB
; recupera LSB en MSB

89

Perifricos
ret
#endasm

Un simple programa de ejemplo: leemos el valor del conversor, y sabiendo que su


entrada est entre 0 y 5V mostramos en la ventana stdio de Dynamic C el valor de
dicha tensin.
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 analgico-digital de 12-bits, que adems dispone de
una matriz de conmutacin que permite realizar las dos configuraciones para poder
leer una pantalla sensible al tacto de tipo resistivo.
La touchscreen que utilizamos corresponde al tipo resistivo de 4-terminales. Se
trata de una membrana relativamente rgida, adherida al display, y otra flexible por
encima de sta. La cara interna de ambas membranas recibe un recubrimiento
resistivo, pero se impide el contacto directo entre ambas, de modo que la resistencia
de contacto entre las mismas es muy alta. Al ejercer presin sobre la membrana
superior, sta se deforma, disminuyendo la resistencia de contacto entre ambas
membranas. Cada membrana tiene dos de sus extremos opuestos conectados a
sendos terminales elctricos, de modo que, cada membrana es en s misma una
resistencia distribuida longitudinalmente entre los terminales de contacto, una en
sentido horizontal, y la otra en sentido vertical. Al presionar sobre la membrana
flexible, la resistencia horizontal y la vertical de la figura se unen en una regin, que
depende de la posicin y rea del punto de contacto, es decir, aquel lugar donde se
realiza la presin.

90

Buses serie
Punto de contacto

T3

T4

T1

T2

Si se hace circular una corriente por una cualquiera de las membranas, puede
establecerse una diferencia de potencial que es funcin aproximadamente lineal de la
posicin entre los extremos de la misma, en los cuales estn los terminales.
Al ejercer presin sobre la membrana flexible, se produce el contacto entre ambas
membranas, y puede medirse la diferencia de potencial en el punto de presin, en
cualquiera de los terminales de la otra membrana. Si bien la resistencia de contacto y
la de la otra membrana quedan en serie con la medicin, su valor es lo
suficientemente bajo como para poder ser despreciado al efectuar una medicin de
tensin.
Esto determina la posicin del rea de contacto en un sentido (horizontal o
vertical); para determinar la posicin en el otro sentido, se realiza la misma
operacin sobre la otra membrana.
Vdd

T3
Vdd

T4

T1

T2
Coordenada en Y

Coordenada en X

La operacin del ADS7846 es muy similar a la del MCP3204; con un adecuado


formateo de la palabra de comando se pueden obtener 24-bits, en los cuales el valor
convertido est justificado al LSb. El "modo de trabajo" en SPI podr tambin ser
cualquiera en el cual los datos cambien en el flanco descendente, para ser ledos en
el flanco ascendente, es decir, CPOL=CPHA=0 CPOL=CPHA=1; es decir que
podemos usar SPI_CLOCK_MODE=0, que es el valor por defecto en R3000 y el
nico en R2000.
91

Perifricos
Sin embargo, lo que hicimos en Cika320240FRST.lib fue aprovechar el cdigo de
las bibliotecas de funciones, dejando libres los puertos serie. Con un par de simples
modificaciones, portamos el cdigo para este controlador, obteniendo una biblioteca
de funciones con soporte para display LCD 320x240 y touchscreen resistivo. El
desarrollo completo se encuentra en las notas de aplicacin en el CD que se
adjuntara al libro introductorio, aunque tambin las hemos incluido en el que
acompaa a esta edicin. CAN-015 describe la touch screen, CAN-020 el ADS7846, y CAN-021 realiza la integracin con el display y Dynamic C.
El diagrama a continuacin muestra el circuito esquemtico utilizado:
+5V

.1

Vdd
47K

X+

T1

PB.4

PENIRQ

Y+

T2

PE.0

DCLK

Y-

T3

PD.3

CS

PE.1

DIN

X-

T4

PB.5

DOUT
GND

10n

I2C
El bus I2C (IIC: Inter Integrated Circuit) fue diseado por Philips en los 80's. La
especificacin original es de dos lneas bidireccionales, una para los datos (SDA) y
la otra para la seal de clock (SCL). Sendas resistencias de pull-up establecen un
estado inactivo en uno lgico; cuyo valor de tensin depende de la tecnologa
utilizada. La velocidad mxima original era de 100Kbps (standard), revisiones
posteriores la han llevado primero a 400Kbps (modo fast) y luego en el 2001
(revisin 2.1) a 3.4Mbps. Existe tambin un modo low speed a 10Kbps. Los valores
para las resistencias de pull-up dependen de la velocidad de trabajo elegida.
El dispositivo que inicia la comunicacin se denomina master, al menos durante el
desarrollo de la misma. Dado que se trata de un bus en el que puede haber ms de un
master, existe un procedimiento de arbitraje mediante el cual se evita que dos
masters puedan tomar el bus al mismo tiempo.
El inicio de una comunicacin se determina por una secuencia especial denominada
START, 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 bsica: la lnea SDA
debe permanecer estable mientras SCL est en estado alto, los datos son ledos en el
92

Buses serie
flanco ascendente de SCL. Tanto START como STOP introducen un flanco
(descendente y ascendente respectivamente) en SDA mientras SCL est en estado
alto.
START

STOP

SCL
SDA

MSb

LSb

ACK

La estructura de mensaje es algo compleja. La comunicacin se inicia por el master


mediante una secuencia de START, luego se transmite la direccin del dispositivo a
seleccionar. A continuacin, cada dispositivo en particular requerir o no una
determinada secuencia de datos, a fin de identificar unvocamente la direccin de y/o
los datos a leer o escribir. Luego, para el caso de una escritura, el master transmite
MSb primero los bits de cada byte. Para el caso de una lectura, el dispositivo
transmite MSb primero, uno a uno, los bits de cada byte, mientras el master controla
el clock. En ambos casos, luego de cada byte transmitido, el dispositivo que los
recibe, sea ste slave o master, confirma con un ACK, que no es otra cosa que un bit
en estado lgico bajo. Si el receptor no puede recibir otro byte por el momento, lo
indica manteniendo SCL en estado bajo. Si por algn motivo no puede recibir ms,
lo indica negando el ACK, es decir, manteniendo SDA en estado lgico alto
terminado el byte. Cuando el que recibe es el master, la finalizacin de la transaccin
se indica negando el ltimo ACK. Una nueva transaccin puede iniciarse
directamente; cuando se ha terminado con ese dispositivo, se sealiza la liberacin
del bus mediante una secuencia de STOP. A partir de entonces, cualquier master
puede iniciar una nueva comunicacin mediante una secuencia de START.
SCL
Master
SDA
Escritura

Dispositivo

SDA
Lectura

Dispositivo

Indice

Dato

Slave (ACK y datos)


Slave (no ACK)

Indice

Dato

El espacio de direccionamiento es de 7-bits, el octavo bit diferencia lectura de


escritura. Algunas de las direcciones se encuentran reservadas para propsitos
particulares (CBUS, expansin, etc.). En algunos dispositivos, los bits ms
significativos identifican el tipo de dispositivo, y los menos significativos identifican
uno entre varios posibles de este tipo, correspondindose con pines que cada uno
tiene a tal fin.
93

Perifricos
Existe un caso particular, que son las direcciones correspondientes al protocolo
CBUS, en el cual no es necesario realizar un ACK luego de cada byte recibido.
Existen adems otras modificaciones, como por ejemplo la extensin para
direcciones de 10-bits. El lector interesado puede obtener una copia de la
especificacin para su regocijo.
La implementacin de I2C en Rabbit es por software, y se encuentra en una serie de
bibliotecas de funciones con soporte para algunos dispositivos especficos y otros
genricos. Por lo general, es sumamente simple realizar modificaciones para soportar
otros dispositivos, como veremos ms adelante.
Bsicamente, el software soporta cualquier pin bidireccional tanto para SDA como
para 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 tambin podemos utilizar cualquier otro pin, operando sobre el registro de control
de direccin (PxDDR), definiendo las siguientes funciones elementales:
i2c_SCL_H(): pone SCL en estado lgico alto
i2c_SCL_L(): pone SCL en estado lgico bajo
i2c_SDA_H(): pone SDA en estado lgico alto
i2c_SDA_L(): pone SDA en estado lgico bajo
i2c_SDA(): lee SDA
i2c_SCL(): lee SCL
Las funciones bsicas de que disponemos estn orientadas al funcionamiento como
master; las mismas son:
i2c_init(): inicializa los pines y sus registros asociados, deber modificarse si se
opera sobre otro port paralelo que no sea el port D.
i2c_start_tx(): genera una secuencia de START
i2c_startw_tx: genera una secuencia de START, agrega una pequea demora
i2c_send_ack(): enva 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 funcin
adicional como i2c_send_ack() o i2c_send_nak() luego, segn
corresponda
i2c_check_ack(): lee un bit, verifica si es ACK
i2c_write_char(): escribe una secuencia de 8 bits, verifica la existencia del ACK
i2c_stop_tx(): genera una secuencia de STOP

94

Buses serie
i2c_wr_wait(): escribe una secuencia de 8 bits, reintenta si el receptor indica que no
puede recibir.
Otras bibliotecas de funciones adicionales incluyen soporte para chips y familias de
chips especficos.

Ejemplos
Memorias EEPROM y FRAM
Si bien disponemos de RAM con batera de respaldo, puede llegar a ser til
manejar una memoria EEPROM como la 24x64 o mejor an una FRAM como
FM24C64 FM24CL64.
Vdd

2K2

A2
PD.6

SCL

PD.7

SDA

A1
A0
WP

24x64

Al trabajar con este tipo de memorias, observamos en su hoja de datos que poseen
un contador de direcciones interno, el cual se auto-incrementa luego de cada
operacin de lectura o escritura, lo que nos permitir acceder a bloques de datos
ingresando solamente la direccin inicial. En el caso de una escritura en una posicin
elegida al azar, el procedimiento es simple y de comprensin directa: secuencia de
START, direccin del dispositivo, dos bytes de ndice (MSB LSB), es decir, la
direccin inicial del bloque en memoria, y a continuacin el bloque de datos byte a
byte, mientras el slave conteste con un ACK luego de cada byte y hasta que el master
decida finalizar.
SCL

Master
Slave (ACK)
Slave (no ACK)

SDA
Escritura

Dispositivo

Indice (MSB)

Indice (LSB)

Dato

Sin embargo, si queremos leer un byte o un bloque de bytes en una determinada


posicin de memoria que no es la siguiente a la ltima que accedimos, deberemos
escribir el contador de direcciones interno del dispositivo, esto se realiza
especificando una operacin de escritura que luego se aborta, dejando paso a la
operacin de lectura propiamente dicha, es decir, habr una secuencia de START, la
95

Perifricos
direccin del dispositivo, dos bytes de ndice (MSB LSB) indicando la direccin
inicial del bloque en memoria, otra secuencia de START (abortando la escritura
anterior e iniciando una nueva operacin), la direccin del dispositivo con el bit
RW 1 indicando una lectura, y a continuacin el dispositivo enviar el bloque
de datos, byte a byte, mientras el master conteste con un ACK luego de cada byte,
decidiendo ste la finalizacin de la transferencia negando el ACK.
SCL

SDA

Dispositivo

Indice (MSB)
Lectura al azar

Indice (LSB)
Master

Dispositivo

Dato

Slave

En este caso, lo que hicimos fue tomar las rutinas de una de las bibliotecas de
funciones adicionales que se incluyen, y modificarlas a nuestro gusto. Las funciones
a continuacin implementan escritura y lectura en una EEPROM o FRAM de este
tipo:
#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

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 transmisin cuando el slave


niega el ACK, deberemos modificar el cdigo dentro del for() para evaluar esta
condicin, ya que el valor retornado por i2c_write_char() es el resultado de evaluar
la presencia del ACK.
A continuacin, 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

Perifricos
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 rene una cantidad de
funciones adicionales tiles, normalmente dispersas, en un circuito integrado. Entre
estas funciones, podemos citar: memoria no-voltil, reloj de tiempo real, reset por
baja tensin, watchdog timer, contador de eventos, nmero de serie, y comparador.
Las funciones de reloj de tiempo real y contador de eventos, son mantenidas en
ausencia de tensin por una pila de respaldo, mientras que la memoria y el nmero
de serie son FRAM.
Si bien el fabricante (Ramtron) anuncia su interfaz como "2-wire", se trata de un
I2C con algunas leves modificaciones de timing que no afectan el normal
desenvolvimiento dentro de un bus. Dentro de la familia FM31xxx encontramos
varias alternativas, con diversos tamaos de memoria FRAM, de 16Kb a 256Kb. La
misma se comporta como una FM24xyy equivalente, por lo que podemos utilizar lo
analizado al estudiar las memorias (24x64) para comunicarnos con la FRAM del
processor companion. La familia 32xxx es similar, pero sin el reloj de tiempo real.
El resto de la circuitera est accesible en una serie de 25 registros que responden a
otra direccin de dispositivo. Desde el punto de vista del master I2C, el processor
companion 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 inters cuando diseamos


equipos para aplicaciones en las cuales la seguridad es ms que importante. Por
ejemplo:

98

Buses serie
si necesitamos detectar que estamos por perder la alimentacin y grabar algo en

memoria no voltil, podemos usar el comparador con referencia para sensar la


tensin de alimentacin antes del regulador.
el pin de RST oficia como reset por baja tensin, permitiendo evitar brownouts2.
si nuestra aplicacin es crtica o involucra la seguridad humana, el watchdog
dentro del procesador puede no ser suficiente; disponemos entonces de un
watchdog externo al procesador, dentro del processor companion.
si la precisin del RTC del procesador no es suficiente, disponemos de uno de
alta precisin y estabilidad (siempre que utilicemos el tipo de cristal
correspondiente y realicemos el procedimiento de calibracin) dentro del
processor companion.
si necesitamos un nmero de serie para identificar nuestro equipo o validar
nuestro software y evitar copias, disponemos de uno en FRAM que puede
bloquearse y pasar a ser de lectura solamente.
si necesitamos contar o detectar algn tipo de eventos, incluso cuando el equipo
est apagado, como por ejemplo la apertura del gabinete, disponemos de un
contador con alimentacin de respaldo (la misma del RTC).
Regulador
Vcc

SCL

SCL

CNT1

SDA

SDA

A1

PFI

A0

32768
IRQ

PFO Vback

RST

RST
FM31xx

A continuacin, un ejemplo de trabajo con el processor companion (reproducimos


slo 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);

Un brown-out es la condicin cuando la alimentacin disminuye hasta por debajo de la mnima


tensin de operacin recomendada, sin llegar a ser un "apagn" (black-out). En estas condiciones, el
micro puede llegar a efectuar cualquier clase de cosas no deseadas, y es conveniente mantenerlo
reseteado mientras la tensin de alimentacin no est dentro de los mrgenes especificados para la
correcta operacin del procesador y dems chips involucrados.

99

Perifricos

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 , 3wire , es muy similar a SPI.
Bsicamente, tanto microwire como SPI son sincrnicas y el MSb viaja primero. En
este caso en particular, nos interesa analizar las memorias EEPROM, que son
sutilmente diferentes.
Observando la hoja de datos de una memoria como por ejemplo la 93x46, vemos
que la misma valida los datos ingresados en el flanco ascendente del reloj (es decir
que el controlador debera ponerlos disponibles en el flanco descendente), el cual
puede reposar en estado lgico bajo o alto. La memoria entrega los datos en el
mismo flanco, de modo que el controlador deber validarlos en el flanco
descendente del reloj. Como vemos, siempre en algn sentido se trata de la fase
contraria a la que se utilizara en SPI.
La interfaz consta de cuatro pines: entrada de datos al dispositivo (D, DI), salida
de datos del dispositivo (Q, DO), clock (C, CK), y chip select (S, CS). Segn el
fabricante, la nomenclatura puede variar. El chip select es generalmente activo en
alto.
S
C
D
Q

100

Buses serie
Algunas caractersticas distintivas de las memorias con interfaz microwire como la
93x46, son:
El comienzo de la transmisin se indica mediante un bit de start, es decir, D
permanece en estado lgico alto en el primer pulso de reloj.
La existencia de un bit en estado lgico bajo antes de comenzar el envo de los
datos, es decir, luego de recibido el comando de lectura, la memoria entrega un
bit en 0 y luego los datos.
S
C
D

Start
0

Datos

El chip select debe pulsarse o desactivarse momentneamente para iniciar el ciclo

de escritura. La sealizacin del estado de ocupado se realiza mediante la puesta


a cero constante (siempre que el dispositivo est seleccionado mediante chip
select) durante todo el tiempo que la memoria demora en escribir.

S
C
D

Start
Busy

Q
Ingreso de datos

Ready

Ciclo de escritura

Rutinas caseras
Si bien algunas de las caractersticas enunciadas se encuentran en dispositivos SPI,
dado el esquema de clock preferimos escribir un set de rutinas adicionales para el
manejo de microwire. En este caso en particular, aprovechamos que nos es fcil
trabajar en 16-bits y lo hacemos, de este modo disminuimos el overhead en cada
transaccin. Bsicamente, lo que haremos es poner el dato y generar un pulso
ascendente de clock para escribir, o generar el pulso y luego leer el dato. Deberemos
101

Perifricos
prestar especial atencin a utilizar el nmero de parte que soporta acceso en 16-bits
y conectar el pin ORG correctamente para formato 16-bits ! 3
Vdd

PE.0
PE.1
PE.3
PB.1

S
C
D
Q

Vdd

ORG
93x46

// 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 WRITE
void EE_BYW(void)
{

Lejos de conocer todas las marcas y formatos, podemos adelantar que Microchip denomina
M93x46B 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. En
algunos casos difiere el pinout.

102

Buses serie
BitWrPortI(PEDR,&PEDRShadow,1,EE_S);
while(!BitRdPortI(PBDR,EE_Q));
EE_CS;

// Set S
// Busy wait hasta que Q=1
// deselect

Ejemplo
Este programa agrega algunas rutinas bsicas para 93x46 y realiza un simple
ejemplo del manejo de la memoria en 16-bits.
// EWEN
#define
#define
#define

(0011xxxx..), ERAL (0010xxxx..), EWDS (0000xxxx..)


EWEN 0x3000
ERAL 0x2000
EWDS 0x0000

// EWEN/EWDS COMMAND
void EE_CMD(int command,int bits)
{
EE_STb;
EE_WR(command,bits);
EE_CS;
}
void EE_ERAL(int bits)
{
EE_STb;
EE_WR(ERAL,bits);
EE_CS;
EE_BYW();
}

// Start bit
// Send CMD
// Clear S y vuelve

//
//
//
//

Start bit
Send CMD
Clear S y vuelve
Busy wait

//
//
//
//
//
//

10xxxxxx CMD RD
Start bit
Send CMD, 8 bits
check zero
data
Clear S

//
//
//
//
//
//

01xxxxxx CMD WR
Start bit
Send CMD, 8 bits
Send data, 16 bits
Clear S
Busy wait

// Read data
int EE_93C46_RD(int address,int bits)
{
int data;

//

address=((address<<8)&0xBF00)|0x8000;
EE_STb;
EE_WR(address,8);
BitRdPortI(PBDR,EE_Q);
data=EE_RD(bits);
EE_CS;
return(data);

}
// Write data
void EE_93C46_WR(int address,int data,int bits)
{
address=((address<<8)&0x7F00)|0x4000;
EE_STb;
EE_WR(address,8);
EE_WR(data,bits);
EE_CS;
EE_BYW();
}
main()
{
int i,data;
WrPortI ( PEDR,&PEDRShadow,0x00 );
// Outputs Low
WrPortI ( PEDDR,&PEDDRShadow,'\B10001011' ); // PE0,1,3,7 = output

103

Perifricos
WrPortI ( PEFR, &PEFRShadow, 0 );

// PE: no I/O strobe

EE_CMD(EWEN,8);
// Enable write commands
EE_ERAL(8);
// Erase all
EE_CMD(EWDS,8);
// Disable write commands
for(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);
for(i=0;i<64;i++){
EE_93C46_WR(i,(79*i+11)*53,16);
}
EE_CMD(EWDS,8);

// Enable write commands

// 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, conectndose con el host mediante una interfaz serie asincrnica. Est
desarrollado en base a un microcontrolador PIC de 18 pines. Mediante el empleo de
comandos ASCII simples, es posible operar con memorias o perifricos I 2C, SPI 1wire sin necesidad de usar un micro con soporte para ellos, ponerse a desarrollar el
protocolo, o hacer que nuestro procesador principal pierda tiempo en estas tareas.
Los pines, adems, pueden operarse indistintamente como I/O.
Rabbit, como vimos, incluye soporte para I2C y SPI en forma de bibliotecas de
funciones. Si bien SPI es manejado por las USART, I 2C no lo es, y tal vez el
consumo de recursos no sea compatible con los requerimientos de timing de un
sistema complicado. O tal vez, el ponerse a desarrollar el cdigo necesario en forma
de llamadas a funcin en lenguaje C nos insuma un tiempo de desarrollo que no
tenemos, particularmente si no estamos muy acostumbrados a trabajar con
perifricos I2C. Por estos motivos, y otros que seguramente se nos pueden llegar a
ocurrir al leer estas lneas, puede llegar a ser interesante disponer de un
coprocesador para I2C, SPI 1-wire, controlado por simples comandos ASCII desde
una de las UARTs.

1-wire es un bus de una conexin (y masa) en el que el mismo cable transporta la alimentacin hacia
los perifricos y los datos en ambos sentidos. La sealizacin se realiza mediante dos ciclos de
trabajo diferentes para cada estado, y algunas seales adicionales reconocidas por su duracin.

104

Buses serie
El BL233B tiene adems la posibilidad de grabarle "macros" que almacena en su
memoria 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 an ms
interesante como coprocesador autnomo, particularmente por el hecho de que ya
est disponible y depurado, acortando nuestros tiempos de desarrollo.
El ejemplo que veremos a continuacin est basado en el siguiente circuito:
+5V

MCP3204

ANA
Ins

MOSI
MISO
CLK
CS

SPI2
2 MOSI SCL1
1 MISO SDA1
3 CLK
P6
P5
IRQ

24x64

SCL
SDA

WP
A0
A1
A2

TxD RxD
14.7456MHz
comandos
57600bps
respuestas

Enviando un string ASCII por RxD, recibiremos la respuesta por TxD, pines los
cuales conectaremos a una de las UARTs de Rabbit. En este caso en particular,
utilizamos el puerto serie D.
A continuacin, damos los ejemplos empleados para probar este circuito, que
observados conjuntamente con la lectura de la hoja de datos del BL233B, darn una
idea rpida de cmo utilizarlo. De todos modos, comentamos lo ms importante.
Para leer 64 bytes desde el inicio de la EEPROM:
G1 SA0 00 00 R40P
G1: configura el bus I2C en los pines elegidos
S: START
A0: direccin de dispositivo
00 00: direccin de inicio del bloque
R40: lectura de 0x40 bytes
P: STOP

105

Perifricos

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 enve un retorno de carro y avance de lnea, para poder
entender mejor lo que observamos
Para leer el pulsador:
?
?: devuelve el byte de estado, el bit 4 es IRQ , que es donde colocamos el botn.
Para encender y apagar el LED, sin activar el CS de SPI, previamente se debe
configurar los pines correspondientes como salidas:
O609F
O609F: coloca el valor 0x9F en el registro TRIS y 0x60 en el registro de salida, es
decir, configura los bits 5 y 6 como salidas y el resto como entradas, luego coloca
los 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 dems son entradas), apaga el LED en bit 6
O20: 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 elegidos
O40: coloca el bit 5 en bajo, habilita CS del conversor
Y: indica que la transferencia es bidireccional, se recibe a la vez que se transmite
W60: enva el valor 0x60 por SPI, lo cual configura al conversor para leer CH0
R03: lee 0x03 bytes
O60: coloca el bit 5 en alto, remueve CS del conversor

106

Buses serie
Como este conversor es de 12-bits, se debe descartar el ltimo nibble pues leemos el
byte ms significativo primero.
El siguiente es el programa utilizado en el Rabbit:
#class auto
// Define tamao 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 high
serDopen(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 estado
484F4B410405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627
28292A2B2C2D2E2F303132333435363738393A3B3C3D3E3 <- los 64 bytes pedidos
484F4B41
<- HOLA
000000
<- valor del A/D (hicimos un corto en la entrada a masa)
19
<- el estado
000000
09
<- presionamos el botn
000000
09
000000
19
<- liberamos el botn
000000
19
FFFFFE
<- valor del A/D (hicimos un corto en la entrada a Vref)

107

Perifricos

Apndice: Low Voltage blue's


La interconexin de micros y perifricos de 3 y 5V es un tema ambivalente. Por un
lado es ms sencillo de lo que podra imaginarse, pero por el otro, no es algo trivial
como para dejarlo librado a la buena voluntad de la providencia. Se trata en esencia
de un simple anlisis de compatibilidad de niveles lgicos, que en algunos casos
resulta ms relajado por particularidades de algunos de los componentes.
Como seguramente todos los practicantes de la electrnica sabemos, para que dos
familias lgicas puedan comunicarse entre s, sus niveles lgicos deben ser
compatibles. Adems, para que ninguno de los dos dispositivos tome el hbito de
fumar, no debemos exceder los lmites mximos permitidos.

Micro de 3V, perifrico de 5V


Para conectar un micro de 3V a un perifrico de 5V, en primera instancia
deberemos analizar dos cosas:
que el perifrico de 5V tenga el nivel lgico mnimo de entrada lo
suficientemente bajo como para aceptar la tensin mnima de uno lgico
entregada por el micro. El caso de mxima tensin de cero lgico tambin
debera ser contemplado, pero esto es mucho ms probable de ser cumplido
que el micro de 3V sea 5V-tolerant, es decir, que soporte la mxima tensin de
uno lgico que le entregue el perifrico.

Micros 5V-tolerant
Si el micro es capaz de tolerar 5V en sus entradas (como el Rabbit 3000), la
cuestin se resuelve analizando la compatibilidad de niveles lgicos.

Compatibilidad de niveles
Si la compatibilidad de niveles lgicos se cumple, podemos realizar una conexin
directa a los pines analizados. Por supuesto que si el perifrico es unidireccional,
podemos prescindir del anlisis en la direccin no utilizada...
PERO, antes de conectar un perifrico de 5V a un bus, debemos analizar que no
slo es el 5V-tolerant micro el que est conectado al bus! Cualquier tensin mayor a
la tensin de alimentacin que pongamos en un bus, puede afectar a cualquiera de
los dispositivos conectados que no sea 5V-tolerant, por ejemplo las memorias. La
forma ms fcil es utilizar los pines de I/O de forma dedicada, pero la ms verstil,
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)
perifrico(s) de 5V en el bus auxiliar de I/O.

108

Apndice: Low Voltage blue's

Incompatibilidad de niveles
En caso que el perifrico no acepte los niveles que entrega el micro, como por
ejemplo muchos de los mdulos de displays LCD inteligentes (alfanumricos y
grficos), deberemos intercalar algn dispositivo que nos permita trasladar los
niveles lgicos:
Para lograr elevar el nivel lgico de 3V a 5V, podemos utilizar una familia lgica
tradicional con niveles bajos, que acepte la entrada del micro de 3V, y entregue
5V a la salida. Esta familia es la 74HCT, con niveles de entrada diseados para
tolerar las fluctuaciones de una familia como la TTL5, manteniendo el bajo
consumo y velocidad de HCMOS. Disponemos entonces de los tradicionales
buffers 74HCT244 (slo comunicacin unidireccional) y los buffers
bidireccionales 74HCT245, entre otros. El buffer, obviamente suma un tiempo de
propagacin, y podemos emplear las seales IORD para DIR y BUFEN
para G , como analizaremos ms adelante.
+5V
Vdd

buffer

buffer

DIR=output
DIR

G=0
74HCT245

Tambin podemos utilizar alguna alternativa como buffers con open-collector y

buffers bidireccionales que veremos a continuacin para los micros que no


toleran 5V en sus entradas.

Micros que no toleran 5V en sus entradas


En micros que no son 5V-tolerant, debemos utilizar algn esquema de conversin de
niveles. En esta categora entran el Rabbit 4000 y el 5000.
Se pueden emplear familias lgicas con open-collector y sin proteccin de salida
(HC, HCT y LS no entran dentro de esta categora), como AHC, que nos
permitan "elevar" los 3V a 5V; y lo mismo en el sentido inverso. El requisito de
no tener proteccin a la salida es porque el diodo de clamping hace que la tensin
provista por el perifrico vaya directamente a la fuente de alimentacin. En
esquemas de bajo consumo, de reducida capacidad por normas de seguridad
intrnseca, o con reguladores serie, esto produce un incremento de la tensin de
5

El nivel de tensin mnima de uno lgico es de 2V

109

Perifricos
alimentacin que puede llegar a destruir algn circuito. En interfaces de alta
capacidad, debido a que el RC que se forma demora el tiempo de transicin de
las seales, esto puede dificultar la operacin.
+5V

Vdd

R
pull-up
C
distribuida
pistas, chips
buffer

buffer

RC

ZoC

+5V

+5V

Vdd
Vdd

HC, HCT, LS

Tambin podemos atenuar con divisores resistivos. En interfaces de alta

capacidad, el RC que se forma afecta de forma ms pronunciada el tiempo de


transicin de las seales y no suele ser recomendable.
5V

Vdd

R
5Vx2/3=3,3V
C
pistas, chips
distribuida

2R

2RC/3

2RC/3

Otra opcin ms saludable es emplear buffers bidireccionales diseados

especialmente para adaptar niveles lgicos, como por ejemplo el 74LVX3245, el


cual se controla desde una interfaz de 3V y permite comunicacin en uno u otro
sentido entre el lado de 3V y el de 5V. El buffer, obviamente suma un tiempo de
propagacin en ambos sentidos; la capacidad distribuida sigue existiendo pero su
efecto es menos pronunciado, al ser el buffer de baja impedancia de salida. Al ser
el buffer bidireccional, se deber controlar la conmutacin de direccin y la
habilitacin de la salida. En el caso de utilizar el bus auxiliar de I/O, podemos
emplear la lnea IORD para DIR y BUFEN para G . El timing de estas

110

Apndice: Low Voltage blue's


seales lo hemos visto al principio del captulo al analizar la conexin de la
FRAM paralelo. Se aconseja un cuidadoso anlisis del mismo.
+5V
Vdd

buffer

buffer

DIR=output
DIR

G=0

G
74LVX3245

Micro de 5V, perifrico de 3V


Para conectar un perifrico de 3V a un micro de 5V, vale todo el anlisis anterior,
pero intercambiando perifrico y micro en los enunciados, es decir, el micro debe
tener el nivel lgico mnimo de entrada lo suficientemente bajo como para aceptar la
tensin mnima de uno lgico entregada por el perifrico, y ste debe ser 5Vtolerant. Un micro como el Rabbit 2000, que si bien puede funcionar a 3V se
encuentra alimentado a 5V en los mdulos, tiene un nivel mnimo de uno lgico
igual al 70% de la tensin de alimentacin, es decir: 3,5V. Esto no le permite recibir
perifricos de 3V, al menos no de forma confiable segn su hoja de datos. En este
caso, deberemos utilizar alguna alternativa similar a las analizadas, pero
considerando que es el perifrico el de 3V. No estudiaremos demasiado este caso,
dado que la tendencia es bajar cada vez ms la tensin de alimentacin, por lo que es
preferible desarrollar con micros de 3V si disponemos de un perifrico de inters
que no funciona a 5V. En algunos casos, el insertar un buffer de alguna familia
lgica que funcione a 3V y sea 5V-tolerant es suficiente.

111

Manejo de memoria extendida

Introduccin: R2000/3000 vs. R4000/5000


Los conceptos que estudiamos en este captulo son vlidos para todos los modelos
de Rabbit a la fecha de edicin de este libro (despus de todo, no es otra cosa que
lgebra de punteros en bajo nivel).
Sin embargo, en los ejemplos en assembler no consideramos los registros punteros
de 32-bits ni el direccionamiento de 24-bits de Rabbit 4000 y 5000, por lo que si
bien son aplicables en estos ltimos, pueden no resultar la mejor opcin. Dado que
para estos micros Dynamic C incorpora el concepto de far pointers, los ejemplos en
C tambien pueden no resultar ptimos para ellos. En el apndice al final del captulo
realizamos un pequeo anlisis de esto.

Datos en xmem
C
En C, disponemos de un conjunto de funciones que nos facilitan el manejo de datos
en memoria extendida.

Bloques de datos: display grfico LCD


Si por ejemplo, necesitamos hacer el volcado de pantalla de un display grfico
inteligente LCD, cada pantalla de 320x240 pixels es un bloque de 9600 bytes. Si
bien podramos guardar una de ellas en rea root, lo ms simple a la hora de
compilar es alojarlas en xmem. Trabajando en C con las funciones disponibles, lo
que podemos hacer es armar un pequeo buffer en rea root, e ir copiando de xmem
a ese buffer, y de all al display. Esto es as debido a que los punteros que tenemos
para realizar el volcado, direccionan en 16-bits. Para acceder directamente desde
xmem y escribir en el hardware, necesitamos manipular el esquema de memoria, lo
cual haremos ms adelante en assembler con displays que requieren mayor espacio
de memoria.
El siguiente ejemplo es una rutina de volcado de memoria para un display grfico
inteligente LCD de 320x240 pixels:
#define IMGCHUNK 240
#define CHUNKS
40

113

Manejo de memoria extendida


void LCD_dump(long imgdata)
{
int x,y;
unsigned char buffer[IMGCHUNK];
LCD_cursor(1200);
// selecciona pantalla grfica
LCD_WriteCmd(0x42);
// MWRITE
for(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 funcin necesita urgente una reescritura para eficiencia, pero
la idea era mostrar el uso de xmem2root() para traer los datos desde xmem al buffer
en 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 al
comienzo del programa, lo cual realizamos con llamadas a xalloc(). Dado que ste es
un sistema dedicado que debe cumplir una determinada tarea por s solo, y no
esperamos cambios en la estructura de variables, no necesitamos devolver memoria
al pool, razn 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

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 slo
a uno de sus elementos, una alternativa interesante es pensar el problema como si se
tratara de un medio externo. De esta forma, accedemos de a uno a los elementos,
copindolos en un espejo en rea root, y operando desde all; de igual modo a como
hiciramos 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 ejecucin, reservamos un bloque
de memoria en xmem:
#define SIZE 30000
long 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, ya
tenemos el puntero, y si usamos #ximport, deberemos recordar que los primeros 4
bytes (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 donde


guardarlo en forma local, o directamente utilizar el valor retornado por la funcin
xgetint():
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]=saminteger
mailocalcopi=xgetint(neim2+sizeof(int)*i+sizeof(long));
//mailocalcopi=neim2[i]

115

Manejo de memoria extendida


Como vemos, es muy similar a trabajar con lgebra de punteros, excepto que
debemos considerar el tamao del elemento en la ecuacin; esto es en realidad lo
que hace internamente el compilador cuando trabajamos con arrays. Por supuesto
que slo podemos usar xsetint() si el destino es una variable, no una constante.
De igual modo, podemos operar con arrays de enteros largos o nmeros en coma
flotante, reemplazando por los tipos y funciones correspondientes.
Para trabajar con estructuras o arrays de estructuras, representando posiblemente
una base de datos, deberemos operar de la misma forma, pero manteniendo siempre
una copia local del registro sobre el que queremos operar. Lo que hacemos es copiar
el bloque de memoria xmem del tamao 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 ejecucin, reservamos
un bloque de memoria en xmem:
#define SIZE 3000
long dibeis;
...
dibeis=xalloc(SIZE*sizeof(struct registro));
...

Si nuestra base de datos o array de estructuras es una constante en vez de una


variable, la podemos declarar trabajosamente con xdata o traerla de disco con
#ximport, de modo similar a como hiciramos con el array de enteros. En ambos
casos, ya tenemos el puntero, y si usamos #ximport, deberemos recordar que los
primeros 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 tener


un lugar en root donde copiarlo, dado que no disponemos de una funcin que
devuelva la estructura.
struct registro disreyister;

116

Datos en xmem
Procedemos entonces a obtener la copia del registro desde la base, y hacemos con l
lo que tengamos que hacer. Si, por ejemplo, el problema es la bsqueda de un
registro en la base, vamos trayndolos 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 ms eficiente tal vez (dependiendo del problema), sera 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 la
estructura (los campos en el registro), nada ms difcil de lo que normalmente
debemos hacer en un sistema dedicado en assembler.
Direcciones
de memoria fisica
0xFFFFF

campo4
array de registros

campo3
campo2
campo1
registro i

dibeis + i * sizeof(struct registro) + sizeof(int)


Direcciones
de memoria logica

dibeis + i * sizeof(struct registro)


dibeis

xmem

0xFFFF

stack

xmem2root()

data
campo2

&disreyister
disreyister
code

0x00000

0x0000

Para grabar un registro, siempre y cuando no est en flash, obviamente, utilizamos


la funcin complementaria:
root2xmem(dibeis+i*sizeof(struct registro),&disreyister,
sizeof(struct registro));
// actualiza registro i

117

Manejo de memoria extendida

Assembler
En assembler, el problema es otro, dado que disponemos del control total del
procesador.

Datos individuales
Cuando se trata de datos aislados, podemos utilizar las instrucciones de acceso en
20-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 porcin de cdigo extrada de getint():
ld h,d
ld l,e
inc d
ld a,c
ldp hl,(hl)

; Test whether near end of 64k page


; Get the value

Las tres primeras instrucciones toman la direccin de BC DE y la colocan en A HL,


como lo requiere la instruccin ldp HL,(HL), luego, dado que esta instruccin no
puede incrementar correctamente si la direccin de la palabra es 0xFFFF, se detecta
esta condicin y se agrega cdigo para leer el byte siguiente en memoria fsica.

Bloques de datos
Si bien, como vimos, existen instrucciones para manejo de datos en 20-bits en
assembler, cuando se trata de un bloque largo es mucho ms simple y eficiente
utilizar los punteros de 16-bits del procesador, que es lo que hacen las funciones
xmem2root() y root2xmem() que utilizamos en C. Para esto, deberemos direccionar
dentro del segmento xmem, estableciendo una ventana a la zona de memoria fsica
donde estn nuestros datos.
A continuacin, veremos un par de ejemplos prometidos, en los cuales hacemos el
volcado 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 mostrar
imgenes con 16-bits de resolucin de color, en un formato de 96x64 pixels, lo cual
son unos 12288 bytes; nada que no podamos transferir velozmente con un R3000.
Nuestra rutina recibir la direccin de un bloque de datos en xmem, lo que ser
un entero largo con 20-bits significativos, y la longitud del bloque (un entero). A
fin de minimizar el problema, limitamos el largo mximo del bloque a 4096
bytes, de modo que siempre estamos dentro de la misma ventana en xmem. Esta
rutina ser entonces llamada unas tres veces desde C, para realizar su cometido.
118

Datos en xmem
Utilizamos una rutina ya provista para convertir la direccin fsica a

XPC:address, de modo de poder escribir el valor del puntero de segmento y


utilizar 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:
Direcciones
de memoria fisica
0xFFFFF
HL
XPC

imagen
4096xXPC+0xFFFF
4096xXPC+HL
Direcciones
de memoria logica

4096xXPC+0xE000

0xFFFF
HL
0xE000

0x00000

0x0000

Seguramente habr alguna forma ms eficiente, la finalidad de este ejemplo es la


didctica, con su correspondiente claridad, sin descuidar la eficiencia. El hardware
corresponde al ejemplo desarrollado en el captulo sobre perifricos.
#asm root
;@sp+2= direccin en xmem (long)
;@sp+6= longitud de los datos (int)
xdumpblk::
ld a,xpc
push af
ld hl, (SP+6)
ld c,l
ld b,h
ld hl, (SP+4)
ex de,hl

;
;
;
;
;
;

call LongToXaddr
ld xpc,a

; Convierte BC:DE a XMEM + address


; DE = address, A = xpc

ld
ld
ld
ld
ex

; hl=longitud del bloque


; bc = "

hl,(sp+8)
c,h
b,l
a,l
de,hl

salva XPC, debemos agregar 2 a los accesos


relativos al SP
Obtiene address (long) en BC:DE
podra haberse obviado esta parte, pero
por generalidad la hemos dejado
(el primer parmetro ya est en BCDE)

; HL= address

119

Manejo de memoria extendida


or a
jr nz,xcbf_loop
dec c
xcbf_loop:
ld a,(hl)
ioe ld
(0x8001),a
inc hl
ld a,(hl)
ioe ld
(0x8001),a
inc hl
djnz xcbf_loop
xor a
dec c
jp p,xcbf_loop
xcbf_done:
pop af
ld xpc,a
ret

; corrige si b=0 (djnz es 256 iteraciones)

; escribe 2 pixels

; loop en b
; loop en c
; restablece XPC

#endasm

La funcin en C que llama a esta rutina est basada en parte de xmem2xmem(), a la


que se han hecho unas pequeas modificaciones, y es la siguiente:
void SSD_dump (unsigned long imgdata)
{
unsigned int tocopy,len;
SSD_home();
dest=0;
len=96*64;
imgdata+=sizeof(long);
while(len) {
tocopy=2048;
if(tocopy>len)
tocopy=len;
xdumpblk(imgdata,tocopy);
imgdata+=(tocopy<<1);
len-=tocopy;
}

// address 0,0
// apunta a imagen
// manda en bloques de 2KW

Display color LCD


Nuestro segundo ejemplo es un display color LCD, cuyo controlador nos permite
mostrar imgenes con una paleta de 256 colores, en un formato de 320x240 pixels,
lo cual son unos 76800 bytes; un poco ms preocupante, dado que la interfaz es ms
compleja.
Nuestra rutina recibir la direccin de un bloque de datos en xmem, lo que ser
un entero largo con 20-bits significativos, y la longitud del bloque (un entero). A
fin de minimizar el problema, limitamos el largo mximo del bloque a 4096
bytes, de modo que siempre estamos dentro de la misma ventana en xmem. Esta

120

Datos en xmem

rutina ser entonces llamada unas diecinueve veces1 desde C, para realizar su
cometido.
Utilizamos una rutina ya provista para convertir la direccin fsica a
XPC:address, igual que en el ejemplo anterior, de modo de poder escribir el
valor del puntero de segmento y utilizar esta vez el registro IX como ndice
dentro de xmem.
A fin de minimizar la cantidad de operaciones en la interfaz, que es mucho ms
compleja, escribimos de a dos pixels.
El ejemplo en s es muy similar al anterior, la diferencia y su complejidad
adicional asociada est en que debemos entregar una direccin de 17-bits al
controlador de display sobre una interfaz que simula un bus de address de 17bits, basndonos en el bus auxiliar de I/O.
Otra diferencia al margen es que dado que ste es un modo indexado de color2,
tambin enviamos la paleta.
Direcciones
de memoria fisica
0xFFFFF
IX
imagen

XPC

4096xXPC+0xFFFF
4096xXPC+IX
4096xXPC+0xE000

Direcciones
de memoria logica
0xFFFF
IX
0xE000

0x00000

0x0000

Una vez ms, seguramente habr alguna forma ms eficiente, la finalidad de este
ejemplo es la didctica, con su correspondiente claridad, y sin descuidar la
eficiencia. El hardware corresponde al ejemplo desarrollado en el captulo sobre
perifricos, del cual recomendamos su observacin y anlisis para una mejor
comprensin del software.
#asm root

1
2

Dado que separamos en bloques de 4KB, y 76800 no es divisible por 4096, son 18 bloques de 4096
bytes (2048 words) y el resto.
Un modo indexado de color es aqul en el cual cada color est representado por un nmero o ndice
que lo ubica en una paleta de n colores. En los modos no indexados, cada color est representado por
sus componentes, como en el caso del ejemplo con OLED, los 16-bits son 5B 6G 5R

121

Manejo de memoria extendida


;IY: A0-A15
;A: A16
;(IX+0): dato pixel izquierdo
;(IX+1): dato pixel derecho
writew13706::
ld a',a
ld hl,iy
ex de,hl
call ab2
ld a,(IX+0)
ex de,hl
ioe ld (HL),a
ex af,af'
ld hl,iy
ld de,0x0001
add hl,de
adc a,d
ex de,hl
ld a',a
call ab2
ex de,hl
ld a,(IX+1)
ioe ld (HL),a
ex af,af'
ret
xdumpblk::
ld a,xpc
push
af
push
ix
ld
ld
ld
ld
ex

hl,(sp+12)
c,l
b,h
hl,(sp+10)
de,hl

call LongToXaddr
ld xpc,a
ex de,hl
ld ix,hl
ld
ld
ld
ld

iy,(sp+6)
hl,(sp+8)
a,l
hl,(sp+14)

ld c,h
ld b,l
ex af,af'
ld a,l
or a
jr nz,.xcbf_2
dec c

;
;
;
;
;

save A16
A15-A0
en DE
addresses y BHE
lee dato

;
;
;
;
;
;
;
;
;

pone dato
restore A16
lee address
next byte
inc address
(adc a,0x00) propaga carry
A15-A0 in DE
save A16
address bus

; lee dato
; pone dato
; restore A16

; salva frame pointer, y xpc

; xmem address (long) en BC:DE

; Convierte BC:DE a XPC y address


; DE = logical address, A = xpc
; IX = source
; a:iy=dest, hl=length

; c:b=length

.xcbf_2:
ex af,af'
xcbf_loop:
call writew13706
ld de,0x0002
add ix,de
add iy,de

122

; preserva BC,IX,IY,A

Datos en xmem
adc a,d
djnz xcbf_loop
dec c
jp p,xcbf_loop

;(adc a,0x00) propaga carry

xcbf_done:
pop ix
pop af
ld xpc,a
ret
#endasm

La funcin en C que llama a esta rutina es muy similar a la del ejemplo anterior. La
constante S1DMEMSIZE es definida en otra seccin del programa, y corresponde a
los 76800 bytes de que hablbamos.
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 con
un valor que hace simple la extraccin de parmetros. Sin embargo, en esta funcin
decidimos seguir con el uso tradicional, y slo lo empleamos para salvar dicho
registro.

123

Manejo de memoria extendida

Apndice: La vida despus de R4000 y DC10


A partir del Rabbit 4000, dado que el procesador dispone de registros de 32-bits
que puede utilizar como punteros en memoria fsica, es posible acceder a la
memoria de forma lineal, evitando la MMU. Dynamic C versin 10 incorpora
entonces 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 forma
explcita, a menos que uno desee hacerlo.
Por ejemplo, el siguiente programa en C muestra un ejemplo de la utilizacin 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);
}

Ms all de la carencia de utilidad e inicializacin y la reiteracin de acceso al


mismo elemento de la estructura; observemos una parte del cdigo compilado para
ver el manejo de punteros. Veremos que ste utiliza los punteros de 32-bits, en este
caso el registro PX, para el acceso a memoria:
printf("%c %d",pepe[i],ura->edad);
1a7c
94E5C7
ld jkhl, (0xC7E5)
1a7f
A314
ld bcde, 20
1a81
EDC6
add jkhl, bcde
1a83
FD9D
ld px, jkhl
1a85
9500
ld hl, (px + 0)
1a87
E5
push hl
1a88
93E9C7
ld bcde, (0xC7E9)
1a8b
DDF5
push bcde
1a8d
DDEE06
ld bcde, (sp + 0x06)
1a90
FDF1
pop jkhl
1a92
EDC6
add jkhl, bcde
1a94
FD9D
ld px, jkhl
1a96
9500
ld hl, (px + 0)
1a98
2600
ld h, 0x00
1a9a
E5
push hl
1a9b
21B11A
ld hl, 0x1AB1
1a9e
E5
push hl
1a9f
CD131B
call printf

124

15
4
4
4
9
10
15
18
15
13
4
4
9
4
10
6
10
12

Apndice: La vida despus de R4000 y DC10


Si recordamos nuestro primer anlisis de la arquitectura Rabbit, la MMU est por
algunas razones. Una es mantener el esquema de direccionamiento de 16-bits
original, el cual permite utilizar registros del micro que pueden manejarse en pocos
ciclos de clock. Estos nuevos registros de 32-bits son del micro, son tambin ms
grandes, y si bien el manejo con estos punteros largos debera tener una penalidad en
cuanto a velocidad de operacin con respecto a punteros de 16-bits, las nuevas
instrucciones del R4000 sorprenden en cuanto a su potencia. Si el acceso a memoria
debe ser de a 8-bits, los punteros de 16-bits (HL en particular) para direccionamiento
indirecto siguen siendo ms rpidos; para otros casos, se requerir un anlisis
cuidadoso si es que el problema a resolver lo amerita. No olvidemos que esto tiene
sentido si operamos directamente en assembler mapeando la pgina de inters en el
segmento 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 a
las tradicionales. En el directorio Samples\Rabbit4000\FAR encontraremos
excelentes ejemplos.

125

Configuracin en campo

Introduccin
A travs de los siglos, developers e ingenieros han intentado combatir a un enemigo
implacable: el usuario final. Sin importar cunto se esmeraban los creadores de un
equipo en ajustar cada uno de sus parmetros de operacin, el usuario final siempre
querra una aplicacin diferente, particularmente alguna que no funcionara con la
configuracin standard. Desolados, ingenieros y developers, intentando satisfacer a
este devastador tirano, crearon toda clase de parmetros configurables, que
permitieran al soberano modificar la operacin a su antojo. Este vano intento no
logr otra cosa que crear un monstruo an ms devastador e implacable: la
configuracin. Olvidados ya de su enemigo anterior, se debaten ahora entre la vida y
la muerte, intentando resolver qu guardar, dnde guardarlo, cundo guardarlo,
cmo cambiarlo, si volver a una configuracin por defecto, etc...
Cada developer tiene sus trucos, y cada micro tiene sus caractersticas, que
favorecen uno u otro enfoque. Si bien muchas veces es posible poner una EEPROM
externa, 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 al
mdulo, y puede no aplicar en algunos desarrollos, adems de ser un costo adicional
que, segn veremos, no es necesario. En cuanto a la pila de respaldo, no slo puede
fallar, sino que si por algn motivo es necesario retirar el mdulo de su zcalo, pese
a toda nuestra buena voluntad, la RAM dejar de estar alimentada y perder sus
datos. Como alternativa, Rabbit y Dynamic C en particular, tienen un BIOS, el cual
soporta un sistema de identificacin que utiliza el sector ms alto de la flash para
almacenar informacin. Junto a esta informacin, es posible almacenar datos en la
flash, como por ejemplo constantes de calibracin y, por qu no, la configuracin del
sistema. Las palabras mgicas son IDblock y Userblock. Si bien analizamos en parte
este tema en el libro introductorio, repetimos un fragmento aqu, por claridad.

Repaso
IDblock
Como dijramos, el BIOS soporta un sistema de identificacin que utiliza el sector
ms alto de la flash para almacenar informacin; esta informacin identifica y
caracteriza al hardware en cuestin, e incluye por supuesto a los mdulos Rabbit y
127

Configuracin en campo
placas de Z-World. El developer puede utilizarlo para que identifique su propio
hardware si as lo desea. Aqu se guarda adems, la direccin de hardware Ethernet,
mejor conocida como MAC address.
Es de destacar que la integridad de este sector es lo que hace que Dynamic C
reconozca al mdulo al momento de compilar nuestro software, por lo que si
accidentalmente se lo modifica, puede que Dynamic C no reconozca al mdulo, sin
poder saber qu hardware presenta (por ejemplo, si tiene o no Ethernet y qu
controlador), con lo cual no podr compilar correctamente el cdigo. En este caso,
existe una utilidad que puede regenerar el IDblock en base a macros que le proveen
la informacin standard de cada mdulo. El nico dato faltante es la MAC, la cual se
obtiene de la etiqueta del mismo.
Para leer el IDblock, o ms correctamente el SystemID block, Dynamic C provee
una funcin:
_readIDBlock(bitmap)

/* bits 0-3 : cuadrante */

El cuadrante es un nmero de 4 bits que identifica la conexin, es decir, los


bloques en los que el chip de flash responde. Por ejemplo, si ocupa los dos bloques
inferiores (512K), ser 0x03.
Para escribir el IDblock, es necesario obtener un pequeo programa especial de la
pgina de Z-World. Esto es as debido a que la funcin WriteFlash() no permite
escribir el IDblock, y esto se debe a que el usuario normal no debera tener razn
alguna para escribir el IDblock; sin embargo, quien desarrolla su propio hardware,
estar ms que interesado en hacerlo.

User block
El User Block es un rea en la parte alta de la flash, debajo del IDblock (puede
estar en el mismo sector), reservada para que el usuario pueda almacenar constantes
de calibracin, configuraciones, lo que se le ocurra que necesite guardar y que deba
soportar una prdida total de alimentacin. El tamao del User Block puede
determinarse mediante la funcin:
GetIDBlockSize();

que devuelve el tamao en unidades de 4096 bytes, y que como dice en la misma
biblioteca de funciones que la define, tiene un nombre que confunde, dado que uno
en realidad lo que quiere es el tamao del User Block, ms que el del IDblock.
/********************************************************************
Return in HL the size of one buffered writable flash data block in
1000h blocks
This function is badly named, it really has more to do with reporting
the User block size than the ID block size.
********************************************************************/

Para leer y escribir el User Block, se dispone de dos funciones:


128

Introduccin

/* 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 */

Segn qu mdulo estemos empleando, es probable que la parte alta del UserBlock
ya tenga algunas constantes de calibracin, por ejemplo para un conversor A/D, con
lo cual es buena prctica dejar un espacio libre como para no escribir sobre estas
constantes. En varias notas de aplicacin, vern que cuando debimos salvar la
configuracin de la touch screen, dejamos un espacio de 2Kbytes, de la siguiente
forma:
#define CALIB_OFFSET

(4096*GetIDBlockSize()-0x400)

Damos a continuacin un ejemplo de como salvar y recuperar constantes de


calibracin 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 configuracin
Como para no interferir con las constantes de calibracin de la touch screen, y dado
que el User Block es generosamente grande, dejamos otro espacio libre para poder
salvar y recuperar nuestra configuracin, definiendo:
#define CONFIG_OFFSET

(4096*GetIDBlockSize()-0x800)

Como vemos, las funciones que operan sobre el User Block trabajan con bloques
de bytes. Por este motivo, es recomendable agrupar toda la configuracin dentro de
una estructura, de modo de poder pasar la direccin de la estructura y su tamao
como parmetros de llamada a estas funciones:
typedef struct {
int
release;
char
name[32];
int
data1;
} Configureiyon;
Configureiyon config;
// Recupera de flash
readUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));
// Graba en flash
writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));

129

Configuracin en campo
De esta forma, la configuracin 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 continuacin, un sencillo ejemplo de como obtener la configuracin de flash al


inicializar, revertir a valores de fbrica ante determinada condicin, simbolizada por
la presin de un pulsador, y grabar una modificacin de configuracin, simbolizada
por la presin 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 versin",83
};
#define APDEIT
2
#define NIUTESTO "Esta es la segunda versin"
#define CONFIG_OFFSET

(4096*GetIDBlockSize()-0x800)

void main()
{
Configureiyon config;
// Carga desde flash
readUserBlock(&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 fbrica
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 configuracin
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 porcin de User Block que aloja
nuestra configuracin, no hay datos coherentes con lo que esperamos, por lo que
130

Guardando la configuracin
deberemos "forzar factory defaults" manteniendo presionado el pulsador
correspondiente al inicio del ejemplo. Los ports ledos corresponden a los pulsadores
S2 y S3 de un RCM2100.

Configuracin va web
Si nuestra aplicacin tiene display y teclado o equivalente, la forma de configurar el
equipo tal vez no difiera demasiado de cualquier otro sistema dedicado con un
microcontrolador. Aun en ese caso, y particularmente si no los hay, es interesante
poder configurar el equipo de forma remota, mediante una pgina web. De esta
forma, podemos jugar con imgenes, tipografas, y la distribucin en pantalla,
logrando una interfaz de usuario eficiente y profesional. No obstante, no debemos
olvidar que estamos trabajando sobre un sistema dedicado, basado en un micro de 8bits, y no es conveniente (ni a veces posible) disponer de centenares de kilobytes
para alojar hermosas imgenes animadas que no aportan absolutamente nada a la
amigabilidad de la interfaz de configuracin.
Para poder configurar nuestra aplicacin mediante una pgina web, deberemos
incorporar el cdigo bsico del servidor web, y publicar las variables necesarias para
que puedan ser accedidas desde el mismo. Hemos visto un ejemplo en el captulo
"Proyecto" del libro introductorio, en el cual nuestro proyecto era configurado va
web. Las cosas han cambiado un poco y mucha agua corri bajo el puente desde
entonces. Dada la existencia de RabbitWeb, un poderoso lenguaje script dentro del
servidor web, sin costo adicional, realizamos los ejemplos con l. Esta herramienta
se describe en ms detalle en su captulo correspondiente.

Seguridad
Poder configurar un equipo mediante una interfaz amigable, conectado desde la
comodidad del silln del living con una PDA, laptop, o equivalente, mientras con la
otra mano acariciamos al perro, es muy seductor. Inmediatamente, nuestro paranoide
interior suma dos ms dos y se da cuenta que de la misma forma que ingreso yo,
podra ingresar cualquier otro, que en el mejor caso puede que no est capacitado
para operar el equipo, y en el peor de los casos puede estar lo suficientemente mal
intencionado 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 pginas de configuracin
abiertas. En gran cantidad de aplicaciones, tal vez esto no sea conveniente. La
solucin es entonces proteger el acceso a las pginas de configuracin mediante
algn mtodo de autenticacin, que nos permita que slo aquellos usuarios
autorizados puedan acceder a dichas pginas.

131

Configuracin en campo
La forma de agregar autenticacin a una pgina web, se describe en la seccin
homnima del captulo sobre networking con Rabbit. Como referencia rpida,
agregamos los cambios necesarios:
Definimos el grupo de usuarios autorizados a alterar la configuracin, el cual
llamamos muy originalmente ADMIN_GROUP:
En el directorio del server, marcaremos aquellos archivos protegidos, los cuales
resaltamos para esta ocasin:
SSPEC_RESOURCETABLE_START
SSPEC_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 para

ingresar un nuevo usuario, llamado (vaya sorpresa) admin, cuyo password ser
istrador. Por claridad, reproducimos el programa principal y resaltamos los
agregados:

void main()
{
int uid;
// Carga desde flash
if(!BitRdPortI(PBDR,2)) {
// valores por defecto
memcpy(&config,&info_defaults,sizeof(Configureiyon));
// graba en flash
writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));
}
else
readUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));
// Crea user ID
uid = sauth_adduser("admin", "istrador", SERVER_HTTP);
// crea
sauth_setusermask(uid, ADMIN_GROUP, NULL);
// agrega al grupo
sock_init();
http_init();
tcp_reserveport(80);
while (1) {
http_handler();
// aplicacin
}
}

En este ejemplo asumimos que el usuario se crea sin problemas, dado que es simple
y sencillo. Si esto es parte de un programa ms complejo, y se agregan usuarios de
forma dinmica, tal vez sea conveniente chequear el valor devuelto por la funcin
que crea el usuario, el cual alojamos en uid, antes de proseguir con la tarea de
agregarlo al grupo.

132

Configuracin va web
Puede ocurrir, que tengamos algn grupo de usuarios a los cuales queremos darles
permiso para ver la configuracin, pero no para cambiarla. ste podra ser un grupo
de asistentes o de monitores de red, que deben observar y reportar determinadas
condiciones, pero no corresponde, por lo que fuere, que la alteren. En este caso,
definimos el grupo de usuarios autorizados a observar pero no alterar la
configuracin, el cual llamamos MON_GROUP: Si bien ambos grupos de usuarios
tendrn acceso a la pgina de configuracin, resolveremos el tipo de acceso
mediante los permisos de las variables de configuracin.
Finalmente, incluimos en el programa principal las funciones necesarias para
agregar el usuario mon, cuyo password ser itor. Por claridad, reproducimos el
programa anterior y resaltamos los agregados:
void main()
{
int uid;
// Carga desde flash
if(!BitRdPortI(PBDR,2)) {
// valores por defecto
memcpy(&config,&info_defaults,sizeof(Configureiyon));
// graba en flash
writeUserBlock(CONFIG_OFFSET,&config,sizeof(Configureiyon));
}
else
readUserBlock(&config,CONFIG_OFFSET,sizeof(Configureiyon));
// Crea user ID
if((uid = sauth_adduser("admin", "istrador", SERVER_HTTP))>=0){
sauth_setusermask(uid, ADMIN_GROUP, NULL);
}
// Crea user ID
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();
// aplicacin
}
}

Para que nuestro programa utilice RabbitWeb, deberemos seguir una serie de pasos
muy simples:
En primer lugar, indicamos que estamos utilizando RabbitWeb:
#define USE_RABBITWEB

lo cual debe hacerse antes de incluir la biblioteca de funciones del web server.
Luego, definimos los dos grupos de usuarios:

133

Configuracin en campo

#web_groups ADMIN_GROUP
#web_groups MON_GROUP

A continuacin, registramos las variables a utilizar, las cuales ya deben estar

declaradas y deben ser globales. Por comodidad, registramos la totalidad de la


estructura, pero podramos registrar cada uno de sus elementos de forma
individual:

#web config auth=basic,digest groups=ADMIN_GROUP(rw),MON_GROUP(ro)

Con la definicin anterior registramos la variable config (una estructura de tipo


Configureiyon), con permiso de lectura y escritura para el grupo ADMIN_GROUP,
permiso de lectura solamente para el grupo MON_GROUP, y la autenticacin del
usuario podr hacerse mediante los mtodos basic o digest.
Cuando una variable sea actualizada (cambio de configuracin), RabbitWeb lo
detectar y podr opcionalmente ejecutar una funcin. Definimos esa funcin y la
registramos:
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 hubo
cambios, es decir; si lo que se enva es igual a lo que exista, no llama a esta funcin
porque no hay cambios.
Los tipos MIME los definimos como siempre, pero esta vez incluiremos la
extensin zhtml para el script RabbitWeb, y le asignamos el correspondiente handler:
SSPEC_MIMETABLE_START
SSPEC_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 pgina de


configuracin:
SSPEC_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

Los usuarios se generan en el programa principal, como hemos visto.


Finalmente, el resto de la tarea la desarrollamos sobre la pgina ZHTML, la cual
incluye un script que nos permite que se ejecuten ciertas cosas y se modifique el
134

Configuracin va web
cdigo entregado, de una forma algo ms simple y ms potente que si utilizramos
SSI. De esta manera, podemos utilizar la misma pgina tanto para configurar como
para mostrar resultados y errores. En nuestro caso, dado que Rabbit Web revisa si
hay o no cambios, y hasta enclava los valores numricos al mximo del tipo
empleado, no tendremos errores en esta pgina. En un caso real, en el que los valores
a configurar tienen un rango de validez, es posible mostrar una interfaz muy
amigable con mucho detalle. A continuacin, 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">Configuracin 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 pginas
dinmicas muy complejas.
Si estamos actualizando la pgina, es decir, el pedido por el cual el server en el
Rabbit est mostrando esta pgina corresponde a un POST ocasionado por la
presin del botn Cambiar, y si no hay ningn error, informamos que la
configuracin fue actualizada.
En cada campo a configurar, utilizamos el nombre del elemento dentro de la
estructura, como haramos en cualquier programa C.
Si el usuario que est viendo la pgina no tiene permiso de escritura, no
mostramos el botn Cambiar. Si ste es ingenioso y de todos modos se las
arregla para intentar hacer una escritura (generando manualmente un POST
desde otra pgina), o si simplemente le dejramos el botn a la vista, recibira un
mensaje 403 Forbidden como respuesta, dado que no tiene permiso para escribir
esas variables. Ocultar el botn hace una interfaz ms amigable.
135

Configuracin en campo

A continuacin, veremos el listado completo del programa:


#class auto
#define USE_RABBITWEB

#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
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

1
"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

#use "dcrtcp.lib"
#use "http.lib"
#ximport "index.html"
#ximport "rabbit1.gif"
#ximport "setup.zhtml"

index_html
rabbit1_gif
setup_html

SSPEC_MIMETABLE_START
SSPEC_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 SSI
SSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/", index_html),

136

Configuracin va 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 flash
if(!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();
// aplicacin
}
}

Revisar los rangos de validez de los valores que se ingresan es una tarea que no
difiere mucho de la normal programacin de cualquier tipo de sistema dedicado. Sin
embargo, con RabbitWeb, el chequeo lo hace el servidor, y nuestra tarea se reduce a
definirle los rangos de validez, pudiendo mostrar de forma amigable la existencia de
un error, indicando al usuario por donde buscar, sin complicar demasiado nuestra
vida.
Supongamos que las variables enteras de nuestra configuracin tienen un rango de
validez:
0release10
9999 data19999
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 ms que directo, es fcil ver que se verifica que config.release sea
mayor que cero y menor que diez. El segundo es algo ms complicado, pero lo que
137

Configuracin en campo
se hace es esencialmente lo mismo, la diferencia es que cuando el valor que se
intenta 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 la
naturaleza del error:
<?z if(error($config.data1)) { ?>
<FONT COLOR="#FF0000">
<?z print(error($config.data1)) ?>
</FONT>
<?z } ?>

A continuacin, el listado completo del ZHTML con los scripts resaltados, y una
imagen de la pantalla. Lamentablemente, el rojo no ser rojo para ustedes, pero
espero 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">Configuracin actualizada</FONT></H2><hr>
<?z } ?>
<?z if(error()) { ?>
<hr><H3><FONT COLOR="#FF0000">Revise los errores indicados en
rojo</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=<?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 } ?>
<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

Configuracin va 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 situacin de configuracin es relativamente
simple: 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 configuracin como por ejemplo Modbus, la situacin
tambin es relativamente simple, dado que la alteracin de un parmetro se realiza
mediante un comando de escritura, por lo que si bien puede haber varios cambios
seguidos, el tiempo de configuracin queda acotado.
Con una interfaz web, la cosa no es tan simple. En primer lugar, alguien puede
acceder a la pgina de configuracin, y dejar su browser encendido por horas, das,
semanas, meses1... Si tenemos slo un administrador, puede no haber demasiado
1

Bueno, en realidad esto slo sera posible si el sistema en donde corre el browser no se cuelga
durante todo ese tiempo, supongamos por un momento que se usa un sistema operativo que
funciona.

139

Configuracin en campo
inconveniente, pero qu sucede si tenemos varios, o lo que es peor an, en varios
lugares diferentes? Es posible que dos o ms administradores intenten acceder dentro
de una misma ventana de tiempo, realizando modificaciones diferentes a los
parmetros.
Detectar el acceso e identificarlo por la direccin IP es una posibilidad, pero si los
administradores estn en la misma conexin a Internet, probablemente tengan la
misma IP pblica desde nuestro punto de vista. Otra posibilidad es utilizar cookies,
aunque muchos usuarios encuentran bastante desagradable esta opcin, y ms an el
hecho de manejar ese cdigo manualmente. Adems, podran existir problemas
debido a usuarios que no desean habilitar cookies en su browser, o tal vez con
algunos proxies.
Un recurso para minimizar este problema, es utilizar una variable a modo de tag,
oculta, representando la "versin" o "edad" de la informacin entregada. La
validacin de los datos enviados incluye el revisar que ambas versiones de esta
variable (navegador y equipo) sean correspondientes. De esta manera, de producirse
una actualizacin (por otro usuario) mientras yo me estoy decidiendo, la secuencia se
rompe y mis datos son descartados. Vemoslo en detalle:
La secuencia de operaciones en un caso normal es la siguente:
1. El navegador recibe el valor de tag al abrir la pgina, digamos que es 3.
2. Al recibir la actualizacin, 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 pgina de configuracin, recibo un llamado telefnico,
salgo a apagar un incendio, y luego vuelvo y recuerdo que dej la configuracin por
la mitad, e intento ingresar los nuevos valores, pero uno de mis serviciales y siempre
bondadosos compaeros de tareas se toma la molestia de modificar otro de los
parmetros; suceder lo siguiente:
1.
2.
3.
4.
5.

Mi navegador recibi el valor de tag (3)


Yo me fui.
El navegador de mi compaero recibi tambin el valor de tag (3)
Mi compaero envi sus cambios
El Rabbit valid, acept los cambios, e increment a tag. En el Rabbit, tag es
ahora igual a 4
6. Yo regreso e intento enviar mi vieja configuracin (pisoteando los cambios de mi
compaero)
7. Al chequear si el valor de tag que mi navegador enva (3) es igual al valor
interno de tag (4), el Rabbit descubre con asombro que 3 no es igual a 4, y
rechaza mis cambios, obligndome a refrescar mi pantalla y observar qu es lo
que pas en mi ausencia.
140

Configuracin va web

La idea original (algo ms elaborada) se describe en el manual de RabbitWeb.


Como en este caso ya usamos #web_update porque salvamos la configuracin en
flash, podemos aplicar esta idea. De lo contrario, en un sistema que solamente
actualice variables en tiempo de ejecucin, se deber utilizar el truco descripto en el
manual de RabbitWeb, dado que es necesario forzar una modificacin sobre la
variable usada como tag para que se chequee y se dispare la ejecucin de la funcin
de incremento, definida en #web_update.
Para implementar esta solucin, realizamos los siguientes pasos:
D
En el programa principal:
definimos la variable y la registramos:
int tag;
#web tag ($tag == tag)

luego agregamos el incremento en la funcin que salva la


configuracin:

tag++;

En el ZHTML:
agregamos la variable oculta:

<INPUT TYPE="hidden" NAME="tag" VALUE="<?z print($tag) ?>">

A continuacin, el listado completo, con los cambios resaltados:


#class auto
#define USE_RABBITWEB

#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
#web
#web
#web

config
config
config
config

auth=basic,digest groups=ADMIN_GROUP(rw),MON_GROUP(ro)
(($config.release > 0)&&($config.release < 10))
(($config.data1 <= 9999)?1:WEB_ERROR("muy alto"))
(($config.data1 >= -9999)?1:WEB_ERROR("muy bajo"))

int tag;

141

Configuracin 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
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

1
"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

#use "dcrtcp.lib"
#use "http.lib"
#ximport "index.html"
#ximport "rabbit1.gif"
#ximport "setup.zhtml"

index_html
rabbit1_gif
setup_html

SSPEC_MIMETABLE_START
SSPEC_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 SSI
SSPEC_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 flash
if(!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

Configuracin va 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();
// aplicacin
}
}

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">Configuracin 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

Configuracin 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 al


mnimo; pero complicndolo un poco ms podramos evitar que el usuario deba
refrescar la pgina manualmente, agregando alguna redireccin, o reemplazando
$variable por @variable, que utiliza el valor que tiene la variable, en vez del que se
pretende introducir. Si utilizramos siempre @variable, al haber un error no
veramos lo que enviamos (el valor que queramos introducir) sino el valor corriente
(el configurado), lo que puede ser confuso. Deberamos entonces modificar el script
para que decida qu valor mostrar en funcin de si se trata de un error en los datos, o
de 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 versin 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">Configuracin 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

Configuracin va 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 selectores
desplegables, checkboxes y botones de seleccin, no nos hemos molestado en
145

Configuracin en campo
utilizarlos por dos simples razones. La primera es que si nada ms ponemos el
HTML esttico, la pgina desplegada no corresponde al verdadero valor de las
variables en ese momento, sino a los valores por defecto puestos en la pgina al
salvarla en flash. Si bien esto puede ser conveniente para un formulario que se
completa, o cuando se requiere que el usuario deba pasar por todos los pasos de
configuracin; no responde al concepto de interfaz amigable cuando se intenta que el
usuario vea a primera vista el estado actual y modifique slo lo necesario. La
segunda razn es que para hacer esto de forma dinmica, la pgina debera generarse
de esta forma.
Con RabbitWeb, es muy simple generar una pgina dinmica, dado que
disponemos de un lenguaje de scripts que nos permite resolver situaciones de manera
elegante, siempre pensando que estamos trabajando sobre un sistema dedicado con
un procesador de 8-bits, cuya tarea principal es resolver un problema de campo.
Vamos entonces a agregar tres nuevas variables a nuestra configuracin, una para
cada tipo de selector a utilizar. En primer lugar, tendremos una lista de "perfiles de
operacin", luego tendremos una opcin que puede habilitarse o no, y por ltimo una
decisin 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 la


estructura completa, pero s es necesario definir los valores de seleccin para
algunos 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 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 considerarlo
habilitado, sino que considere cualquier valor diferente de 0 como "s"; de todos
modos puede definirse un rango de validez si se desea.
Finalmente hacemos todo el resto del trabajo dentro del ZHTML; el selector de
modo de operacin:
<SELECT NAME="config.opprof">
<?z print_select($config.opprof) ?>
</SELECT><br>

la opcin es un checkbox:

146

Configuracin va 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 decisin 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 vera de la siguiente manera:

A continuacin, el listado completo del ZHTML, incluyendo soporte para cambios


por otro usuario mientras uno medita sobre la pantalla. El listado del programa
principal es similar al ejemplo anterior, con las diferencias marcadas, por lo que no
lo 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">Configuracin actualizada</FONT></H2><hr>

147

Configuracin 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 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 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

Configuracin va web
Perfil de operacin:
<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

Conectividad

USB
Las conexiones serie han ido evolucionando, y USB parece ser la elegida de hoy.
Casi todos los dispositivos medianamente porttiles que necesitan interconexin
tienen un port USB.

Introduccin a USB
Seguramente las cosas seran ms fciles si empleramos la misma terminologa
para las mismas cosas, pero entonces no habra expertos ni gente que escribiera
libros explicando algo que todo el mundo sabe pero no conoce por ese nombre...
La cuestin es que USB no difiere mucho de un sistema tradicional de
comunicaciones en el que el host interroga peridicamente a los controladores, a la
vieja usanza de SDLC1. La terminologa es agradable al odo del programador de C+
+, pero el funcionamiento responde a los conceptos tradicionales de polling y
circuito 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 las
colisiones en un medio de acceso mltiple 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 direccin y lee su configuracin y caractersticas, con lo cual
es posible tratarlo de la forma que necesita para operar correctamente. Dado que las
direcciones son de 7-bits, y todo dispositivo (incluyendo los hubs) debe tener una
direccin, el nmero mximo de dispositivos conectados al bus es de ciento
veintisiete.
Fsicamente, el enlace no requiere terminacin elctrica, pero la capacidad y
atenuacin del cable limitan su longitud mxima a unos pocos metros. El tipo de
conector empleado conecta primero los pines de alimentacin (2) y luego los de
1
2

Protocolo desarrollado por IBM, en el cual se inspirara HDLC, padre de los protocolos de nivel-2
usados en X.25, Frame Relay, PPP, y otros.
Una reciente "innovacin" es USB OTG o USB "On-The-Go", que en resumidas cuentas no es ni
ms ni menos que un sistema de handshake para que un dispositivo porttil (de ah el "on the go")
como por ejemplo una PDA, pueda decidir si acta como dispositivo perifrico (conectado a una
computadora para actualizacin y sincronizacin) o como controlador (conectado a algn perifrico
para su uso). De todos modos, una vez decidido, uno es master (controlador) y el otro slave
(dispositivo)

151

Conectividad
comunicaciones (2), lo que permite conectar y desconectar un dispositivo "en
caliente" (hotswap). Cada dispositivo debe inicializar en un modo de trabajo en el
cual no consuma ms de 100mA, informando al controlador cunta corriente
necesita, y cuando ste se lo permita podr pasar a consumir ms. El consumo de
corriente mximo permitido por bus es de 500mA. Un dispositivo puede alimentarse
del bus, que provee 5V (en realidad entre 4,375V y 5,25V), o de forma
independiente.
Un hub USB puede partir un bus en varios buses fsicos, pero el bus lgico es el
mismo, y el mximo total de dispositivos seguir siendo ciento veintisiete. La
cantidad mxima de hubs que se pueden conectar en cascada es de cinco, y un hub
que se alimenta del bus, solamente puede entregar alimentacin a otros dispositivos,
es decir, no est permitido conectar un hub alimentado del bus a otro hub alimentado
del bus.
Dijimos que hay dos cables de alimentacin, y dos de comunicaciones. Por esos
dos ltimos, el protocolo de comunicaciones permite establecer circuitos virtuales
entre el controlador y los dispositivos. Cada dispositivo fsico puede tener varias
functions, y el lmite de circuitos virtuales posibles para cada function es de diecisis
en cada sentido (controlador-dispositivo y dispositivo-controlador). Los circuitos
virtuales se establecen desde el controlador, y uno de ellos est reservado para la
comunicacin de control entre el dispositivo y el controlador. Estos circuitos
virtuales reciben generalmente el nombre de endpoints.
Obviamente, dado que existen circuitos virtuales, la informacin 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
dilogo inicial entre el mismo y el controlador. Dichos datos forman una estructura
jerrquica llamada device descriptor, que tiene uno o ms configuration descriptors,
que a su vez tienen uno o ms interface descriptors, que a su vez tienen una default
interface y probablemente algunas alternate interfaces, que finalmente tienen
endpoint descriptors... Simplificando un poco el tema, en esa estructura se define
como responde el dispositivo a los diversos modos de trabajo (bajo consumo, activo:
configuration descriptors), las posibilidades de comunicacin (audio y video en una
cmara: dos interface descriptors, uno para audio y uno para video), y cmo
conectarse 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

USB

interface descriptor 1
interface descriptor 2
default interface
endpoint descriptor 1
endpoint descriptor 2

Cada endpoint pertenece a una categora, y esto define el tipo de transaccin a


efectuar:
Control transfers (transferencias de datos de control): se trata de comandos y
respuestas simples, el endpoint 0 (comunicacin entre controlador y dispositivo)
pertenece a esta categora.
Isochronous transfers (transferencias iscronas): corresponde a aplicaciones que
necesitan transferir datos con una periodicidad estable, como por ejemplo
comunicaciones de voz o video, requiriendo asignacin de un ancho de banda de
transferencia fijo.
Interrupt transfers (transferencias de datos por interrupciones): corresponde a
aplicaciones que requieren una baja latencia, usualmente interfaces de usuario. El
nombre tal vez aluda a que este tipo de dispositivos usualmente se maneja por
interrupciones en un micro, pero en un bus USB no hay nada que pueda
interrumpir a nadie3. Este tipo de transferencias tal vez debi haberse llamado
high-priority (de alta prioridad)
Bulk transfers (transferencias en masa): corresponde a aplicaciones que no tienen
requerimientos de latencia o ancho de banda, y que transmiten gran cantidad de
datos a la mayor velocidad posible de forma espordica.
Identificado cada tipo de endpoint de cada interfaz de cada dispositivo, en el modo
de trabajo corriente, el controlador administra la frecuencia y orden en que interroga
a cada uno de stos para que puedan entregar o recibir los datos que necesitan. En
ningn momento un dispositivo puede poner datos en el bus si el controlador no se
lo indica. Como es de suponer, los endpoints de clase interrupt-transfers son
interrogados ms frecuentemente y los de tipo iscrono con una periodicidad estable.
Como podemos inferir de la informacin anterior, los dispositivos se sientan
tranquilamente a esperar que el controlador les diga cuando hablar, mientras que este
ltimo transpira desesperado por atender a todos a tiempo. El hardware del
controlador es entonces bastante complicado y recibe el nombre de HCD: Host
Controller Device, cuya interfaz con el procesador responde (al momento de escribir
este texto...) a uno de tres tipos:
OHCI: Open Host Controller Interface, diseo standard de USB 1.0 y 1.1
UHCI: Universal Host Controller Interface, diseo alternativo de USB 1.0 y 1.1
3

al menos no al momento de escribir este texto...

153

Conectividad
EHCI: Enhanced Host Controller Interface, diseo de USB2.0; los controladores

EHCI implementan un OHCI o UHCI virtual (segn el fabricante) para acceder a


dispositivos anteriores de baja velocidad.

Finalmente, los dispositivos conectados a un bus USB se identifican como parte de


una class, lo que permite que el sistema operativo en el host donde reside el
controlador pueda cargar los drivers necesarios para comunicarse con ese
dispositivo. Algunas de las clases ms 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, cmaras fotogrficas digitales)
Las velocidades mximas (al momento de escribir este texto...) corresponden a una
de 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 dedicado
dependen mayormente del mismo. Si no hay necesidad de tener un acceso a ste
desde otro equipo de mayor jerarqua, no es necesario; incluso en muchos casos se
puede suplir utilizando la interfaz Ethernet. Pese a la cantidad de perifricos USB de
que se dispone en el mercado, la implementacin de un host USB es demasiado
complicada para la mayora de las aplicaciones; no slo por el controlador sino
porque aun resuelto este tema, queda el de la confeccin de drivers para atender a
estos dispositivos.
Sin embargo, cuando el costo prima y nuestro equipo no dispone de interfaz
Ethernet, una interfaz USB es mucho ms interesante y rpida que un port RS-232.
Particularmente porque las computadoras modernas traen cada vez menos ports
serie, y muchas porttiles 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 rpidamente a una laptop o
equivalente. Hay otras alternativas? S, las hay. Mejores o ms rpidas? Tal vez,
pero una interfaz USB puede ser una alternativa simple, rpida y econmica.

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 comunicacin en el bus
USB, presentndose al usuario como un puerto serie asincrnico.
154

USB
Conectando dos pines del chip directamente a los pines de la UART del micro,

ste ya est comunicado va USB.

El FT232 maneja todos los aspectos USB, incluso la identificacin. En el caso

ms comn y econmico, en que el usuario no necesita una identificacin


particular, el FT232 se identifica de forma genrica como un dispositivo de
conversin serie a USB. Si el usuario necesita identificar su producto, puede
conectar una EEPROM (la cual ser programada por un software provisto por el
fabricante), en la cual residirn sus datos de identificacin.
El envo de los datos se realiza bajo la categora de bulk transfers, agrupando
datos de manera de optimizar latencia y throughput. Esto no presenta ningn tipo
de inconvenientes para la mayora de las aplicaciones simples, pero, de ser
necesario, es posible controlar varios aspectos de la conversin mediante bits de
control en la EEPROM; incluso puede utilizarse la categora isochronous
transfers (transferencias iscronas) si es necesario.
El FT232 soporta todas las seales de interfaz y varias tensiones y formas de
operacin. Puede funcionar tanto a 3,3V como a 5V, puede alimentarse del bus o
tener su propia fuente, pasando a modo bajo consumo cuando lo indica el host
USB y comandando por un pin al resto del circuito. Incluso es posible trabajar en
half-duplex (por ejemplo RS-485), dado que el chip dispone de una salida para
comandar un transceiver.
Desde el host USB, una vez cargado el driver, el FT232 se ve como un stream de
datos serie. Tanto para los entornos comerciales ms usados como para Linux, el
fabricante provee unos drivers que permiten ver al FT232 como un port serie
adicional, es decir, al conectar el FT232 al bus USB de una "PC comn", aparece
un nuevo COM, del cual puede configurarse su velocidad, tamao de palabra, y
control de flujo, como cualquier otro port serie standard. Los datos enviados y
recibidos por este port serie virtual se corresponden con las seales en los pines
TD y RD del FT232, respectivamente. Para los ms osados, existe una biblioteca
de funciones (DLL en la jerga del entorno ms conocido) que permite tener
acceso directo USB mediante llamadas a funcin, disponible en la pgina del
fabricante para diversas plataformas.
Veremos a continuacin una interfaz USB con el FT232 que se alimenta a 5V de la
misma alimentacin que la CPU o microcontrolador del equipo. La misma,
conectada a un Rabbit 2000, provee instantneamente conectividad USB.

155

Conectividad
470

+5V

10u

.1

Vcc-IO

1
USB 2
3
4

.1

27

Vcc AVcc
GND
AGND

USBDM

TxD
USBDP
RxD
PWRCTL 3V3out

27
1K5
4K7

RSTOUT
RESET

10K

CPU

33n

XTin
XTout
6MHz
27p

27p

Para el caso de un Rabbit 3000, tenemos una serie de opciones:


En kits/mdulos con alimentacin de 5V y LDO interno, podemos utilizar el

mismo esquema; el R3000 es 5V-tolerant y los niveles lgicos de operacin del


FT232 permiten el funcionamiento con I/O de 3,3V; alimentado a 5V.
En kits/mdulos con alimentacin de 3,3V, o mismo en el caso anterior si
proveemos esta tensin de forma externa al FT232, podemos utilizar el esquema
modificado para 3,3V. El inconveniente es que deberemos tomar los 5V de
alguna parte.
+3,3V
470

+5V

10u

27

1K5

10K

TxD
USBDP
RxD
PWRCTL 3V3out
RSTOUT
RESET

CPU

33n

XTin
XTout
6MHz
27p

156

Vcc AVcc
GND
AGND

USBDM

27
4K7

.1

Vcc-IO

1
USB 2
3
4

.1

27p

USB
En cualquiera de estos casos, si es necesario, podemos alimentar el FT232 del bus
USB, y sealizar al Rabbit que pase a bajo consumo aprovechando las seales de
control del FT232:
5VCPU 3VCPU
470
10u

Vcc-IO

1
USB 2
3
4

.1

.1

27
27
1K5

Vcc

AVcc
GND
AGND

USBDM

TxD

USBDP
PWRCTL

RxD

RSTOUT
RESET

PWREN

3V3out
XTin
XTout
6MHz

27p

CPU
LowPowerCtl

33n

27p

Se recomienda a quienes quieran utilizar esta ltima opcin, leer cuidadosamente la


hoja de datos del FT232.
Para Rabbit 4000 y 5000, slo podemos emplear esta ltima opcin.

Bluetooth
Hace ya unos aos, comienzan a aparecer algunos standards para interconexin
inalmbrica de equipos. Uno de ellos, es Bluetooth.

Introduccin a Bluetooth
Bluetooth corresponde a lo que se denomina WPAN (Wireless Personal Area
Network), y su objetivo principal es interconectar dispositivos como PDAs,
telfonos celulares, laptops, etc. El intercambio de informacin se realiza de forma
inalmbrica, y preferentemente de un modo seguro y econmico. Segn se puede
leer por all, el nombre (que bastante llama la atencin, por cierto) deriva de un
famoso rey nrdico de la antigedad, que logr apaciguar y unificar las diversas
facciones que hoy son avanzados pases con pujantes empresas de telefona y
fabricantes de telfonos celulares como la que tuvo la idea de desarrollar este
protocolo y ponerle ese nombre alegrico; ante la obvia similitud entre unificar y
gobernar varios pueblos enemistados e interconectar artefactos dismiles de
fabricantes 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 centmetros, metros, y
157

Conectividad
decenas de metros. Las velocidades de operacin varan con las revisiones del
standard, generalmente buscando competir con USB y WiFi.
En Bluetooth, los dispositivos se agrupan en pequeas redes de hasta ocho
elementos 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. Las
piconets, a su vez, pueden agruparse en scatternets, pero esto es algo ms
complicado. En una piconet, master y uno de los slaves pueden intercambiar trfico
en cualquier momento, el master va cambiando peridicamente de slave para poder
atenderlos a todos.
El protocolo opera en la banda de 2,45 Ghz, la cual divide en 79 canales y va
saltando rpidamente de canal, de modo de no interferir ni ser interferido por otros
dispositivos con otras aplicaciones, dado que se trata de una banda en la que no se
requiere licencia para operar.
Un detalle que siempre se toma en consideracin al momento de utilizar
comunicaciones inalmbricas, es el de la seguridad. Si bien el standard soporta
autenticacin, generacin de claves y encripcin para la proteccin de los datos
(SAFER+, E0), existen artculos donde se describen falencias o ataques. Sin
embargo, ms all de la seguridad informtica, el verdadero problema de seguridad
en los dispositivos Bluetooth es que, debido a la existencia del protocolo de service
discovery, que permite descubrir y conectarse con otros dispositivos, es muy fcil
detectar la presencia de estos dispositivos en vehculos estacionados, bolsos,
transentes, etc...
Segn la IEEE, el physical layer de Bluetooth est conformado por dos sublayers,
el protocolo de RF (RF layer) con salto de canales (channel hopping) que
brevemente describimos, y un baseband layer, que se encarga de establecer el enlace
fsico entre dos dispositivos, para la existencia del dilogo y por ende de una
piconet. El layer superior (data link layer) est conformado tambin por dos
sublayers: el link manager, encargado de negociar seguridad, tamaos de paquete,
potencia de RF y estados de conexin; y el L2CAP (Logical Link Control and
Adaptation layer Protocol, que es el que se encarga de proveer los servicios de
conexin para que puedan comunicarse los protocolos de nivel superior. Entonces,
resumiendo, a 2,45 GHz, los dispositivos se comunican en grupos de ocho
elementos, de a dos por vez, salteando frecuentemente el canal utilizado, empleando
algunos protocolos para establecer y mantener la comunicacin, negociar opciones, y
proveer un enlace transparente sobre el que se puedan correr otros protocolos, que
servirn a las aplicaciones que se intenta servir.
Como comentramos, existe un protocolo denominado SD, service discovery, que
permite que los dispositivos puedan "conocerse" entre s, mediante el intercambio (a
pedido) de informacin relevante a las caractersticas de operacin y servicios de
conexin disponibles y permitidos. Otra posibilidad es directamente utilizar la
direccin del dispositivo, si se la conoce. Cada dispositivo posee una direccin de
48-bits que es nica. Afortunadamente para los humanos, el resultado de SD suele
158

Bluetooth
incluir nombres "de fantasa", que hacen ms fcil identificar el dispositivo al que se
intenta conectar. La respuesta a pedidos de SD puede habilitarse, a voluntad.

Conexin (pairing, bonding)


Conocida la direccin, el proceso de conexin entre dos partes se denomina
pairing, y puede hacerse de forma libre o requiriendo autenticacin e incluso
encripcin de la informacin, segn la configuracin de los dispositivos
involucrados.

Perfiles (profiles)
Para facilitar la comprensin entre dispositivos, existen determinados perfiles de
operacin (Bluetooth profiles) que definen las posibles aplicaciones de los mismos.
Esta es un rea variada y diversa, por ejemplo existen perfiles para el intercambio de
informacin como tarjetas personales, algo muy comn entre los usuarios de PDAs;
generalmente estn basados en OBEX, que es el protocolo de intercambio de objetos
empleado desde las primeras Palm PDAs. Existen adems perfiles de operacin para
la conexin de auriculares, controles remotos, etc. Los que ms nos interesan son
algunos basados en un protocolo que permite simular un cable entre dos dispositivos.
Este protocolo se denomina RFCOMM, y los perfiles a que hacemos referencia son
SPP (Serial Port Profile) y DUN (Dial-Up Network).

SPP
El perfil SPP simula una conexin mediante un cable, es decir, los dispositivos con
soporte Bluetooth que se hallan interconectados mediante el perfil SPP, se
comportan como si estuvieran conectados por un cable. Es equivalente a un circuito
virtual, canal lgico, etc., en otras tecnologas.

DUN
El perfil DUN es similar a SPP, pero permite operar con modems, es decir, un
telfono 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 aplicacin o producto
final dependen mayormente del mismo, como ejemplo podemos citar la posibilidad
de ser controlado a distancia, sin necesidad de que el operador tenga contacto fsico
o siquiera proximidad con el equipo, que puede hallarse en un rea restringida. Gran
cantidad de PDAs y telfonos celulares incluyen soporte Bluetooth, permitiendo que
una aplicacin residente en la PDA pueda controlar de forma grfica el equipo,
simulando, por ejemplo, el frente del mismo. De forma an ms simple, mediante
una aplicacin freeware para PDA que permita enviar y recibir caracteres por el

159

Conectividad
perfil SPP de Bluetooth4, puede realizarse una conexin virtual al puerto serie de
nuestro equipo, que con un simple intrprete de comandos, ya tiene acceso remoto.
Mediante DUN, podramos utilizar un dispositivo como si fuera un modem, lo que
eventualmente nos permitira correr PPP; sobre ste TCP/IP, y ganar acceso a una
red como la Internet5.

Mdulos Bluetooth
Si bien Bluetooth es un stack de protocolo que requiere una considerable cantidad
de recursos del procesador, no necesariamente dicho stack debe correrse en el
procesador principal. Existen mdulos Bluetooth que permiten a cualquier
procesador con UART acceder al mundo Bluetooth, mediante SPP (Serial Port
Profile), es decir, simulando una conexin serie.
Los mdulos de KC Wirefree son mdulos basados en chipsets de Zeevo, que
contienen un procesador (por lo general ARM7) con todo el stack Bluetooth, el cual
se encarga de todas las tareas relacionadas con ste; el procesador principal lo
controla mediante comandos AT extendidos, a travs de un puerto serie. Segn el
mdulo, existe adems una cantidad de pines de I/O adicionales, que pueden ser
controlados mediante los mismos comandos. Los mdulos funcionan a 3,3V e
incluyen la RF y una antena integrada.
La forma ms simple de conectarse a un dispositivo con uno de estos mdulos, es
iniciar una conexin desde el dispositivo remoto. Desde una PDA, por ejemplo, se
inicia un discovery y el mdulo aparecer en la lista de dispositivos cercanos. Luego,
solicitando una conexin, ya estamos conectados.
Iniciar una conexin desde el mdulo es igualmente simple, primero deberemos
descubrir la direccin de los dems dispositivos que tengamos cerca, para ello
ingresamos el comando
AT+ZV Discovery

Una vez elegido el dispositivo, el comando para conectarnos va SPP es


AT+ZV SPPConnect <direccin>

Una vez establecida la conexin SPP, el mdulo se comporta de forma


transparente, todo lo que recibe va Bluetooth (dentro de esa conexin) lo entrega
por el puerto serie, y todo lo que recibe por el puerto serie lo transmite al otro
extremo, va la conexin Bluetooth. De igual modo que los modems tradicionales
tienen una secuencia de escape para recuperar el control en modo comando y poder
desconectar o modificar parmetros de operacin, tambin estos mdulos tienen una
4

Un ejemplo de esto es el programa TriConnect, para PalmOS, disponible en Internet. Este programa
simula una terminal asincrnica sobre el puerto serie, un port TCP, o una conexin SPP Bluetooth,
permitiendo observar lo que enva el equipo conectado al mdulo, y mandar caracteres ingresados en
la PDA
De hecho, hay un ejemplo de esto en el captulo sobre networking con Rabbit

160

Bluetooth
secuencia de escape, diferente, pero igualmente efectiva, y con menor probabilidad
de que sea simulada por el stream de datos.
En el caso de DUN, es posible que debamos primero permitir el pairing, tambin
conocido como bonding, lo cual realizamos mediante el comando:
AT+ZV EnableBond <direccin> <clave>

Una vez permitido el bonding, el comando para conectarnos va DUN es


AT+ZV DUNConnect

El dispositivo remoto deber proveer la clave requerida para que se establezca la


conexin.
El nico detalle a tener en cuenta, a la hora de desarrollar software para interactuar
con el mdulo, es el hecho de que el mismo enva mensajes al momento de
establecimiento de la conexin y de interrupcin de la misma; nada diferente de un
modem convencional. He aqu un pequeo fragmento de programa que espera una
conexin y pasa un string recibido a la funcin que lo procesa, descartando los
mensajes del mdulo:
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 mdulo
wfd cof_serEwrite(sentence, strlen(sentence));
// procesa comando
}
}
}

//eco

ZigBee e IEEE 802.15.4


Comentamos aqu la utilizacin de dos especificaciones interrelacionadas,
destinadas de forma primaria a la intercomunicacin de sensores o entidades
independientes con relativamente bajo trfico, de forma inalmbrica.

Introduccin a ZigBee
El standard 802.15.4 de la IEEE forma parte de un grupo de standards destinados a
reglamentar la realizacin de redes personales inalmbricas, o WPANs (Wireless
Personal Area Networks). A grandes rasgos, 802.15.4 se encarga de establecer una
161

Conectividad
comunicacin confiable mediante un enlace de radiofrecuencia, permitiendo adems
el uso de broadcasts para direccionar varios dispositivos a la vez. La confiabilidad se
obtiene mediante deteccin de error, acuse de recibo y retransmisiones; de modo que
se pueda determinar si una trama se entrega o no. Si bien esto puede realizarse con
tecnologas previamente existentes, el nfasis de 802.15.4 se halla en la simpleza de
la implementacin, requiriendo relativamente poca potencia de procesamiento y
facilitando la operacin en bajo consumo, permitiendo que los dispositivos puedan
"ausentarse" para dormir por ciertos perodos de tiempo.
El stack de protocolos ZigBee se apoya sobre IEEE 802.15.4-2003 para la
comunicacin entre dispositivos cercanos, es decir, el acceso al medio y el
intercambio de mensajes por ste. Por sobre esta base, define un nivel de routing
(NWK) que permite que los dispositivos con funcionalidad de router puedan
transportar mensajes para otro destinatario, extendiendo el alcance total de la red.

Routing en una red ZigBee


Una red ZigBee puede tomar topologas:
D
Estrella
D
rbol
D
Mesh
En una red estrella, el coordinador atiende a un nmero de dispositivos, de modo
similar a una red 802.15.4.
En una red rbol tenemos la presencia de routers, y podemos armar pequeas
estrellas (cluster tree). La informacin se distribuye de forma jerrquica a lo largo
del rbol hasta llegar a destino.
En una red mesh, los routers y el coordinador "descubren" la ruta hacia el
destinatario del mensaje mediante una serie de mensajes NWK. Si no hay
comunicacin directa, los mensajes viajan de router en router hasta llegar al
destinatario. Los dispositivos de bajo consumo siempre entregan los mensajes a su
coordinador, quien tiene la misin de recibir y guardar los mensajes para stos hasta
tanto despierten y lo contacten, momento en el cual procede a entregrselos. Si una
ruta falla, es posible detectarlo debido a que 802.15.4 posee confirmacin de
recepcin, entonces el router que tiene el mensaje inicia el proceso de descubrir una
ruta alternativa y la red converge nuevamente.

Comunicacin de aplicaciones en una red ZigBee


Por encima de NWK est el protocolo APS (APplication Support), el cual provee
mltiples circuitos virtuales en la comunicacin entre dispositivos. Estos circuitos
virtuales se denominan endpoints. Cada dispositivo tiene un radio y una direccin
802.15.4. Esta direccin es nica y es la que identifica a cada dispositivo. A su vez,
el endpoint identifica a la aplicacin. Cada tipo de mensaje en particular tiene un
cluster-ID que lo identifica. El conjunto de mensajes que utiliza una aplicacin se
denomina profile, y se identifica mediante un profile-ID.
162

ZigBee e IEEE 802.15.4


Los profiles son definidos por la ZigBee Alliance. Existen profiles pblicos y
privados (manufacturer specific), se requiere que los dispositivos utilizando profiles
pblicos operen con otros similares, mientras que para los privados slo se requiere
que coexistan con los otros pacficamente. De este modo, todos los dispositivos
utilizando un profile-ID determinado conocen el significado de cada cluster-ID y
relacionan la misma accin con ese cdigo.

Binding
El endpoint 0 est reservado para una aplicacin especial denominada ZDO
(ZigBee Device Objects), que es la que se encarga de las tareas de configuracin y
mucho del funcionamiento automtico. Qu profiles soporta cada dispositivo y en
qu endpoint, se descubre mediante dilogo de ZDOs. De este modo, es posible
auto-configurarse y operar con otros dispositivos. Dicho proceso de descubrir y
asociarse se denomina binding.

Por qu?
Las razones por las cuales implementar ZigBee u 802.15.4 en una aplicacin o
producto son similares a las de cualquier conectividad inalmbrica. Si necesitamos
capacidades de Mbps tenemos Wi-Fi, si el alcance es ms limitado y no requerimos
multipunto tenemos Bluetooth, y para menor capacidad tenemos ZigBee/802.15.4;
particularmente si el consumo es una variable de importancia.
La cuestin IEEE 802.15.4 versus ZigBee es en realidad un anlisis de lo que
nuestro proyecto necesita y lo que cada una provee. Algunas aplicaciones pueden
resolverse con cualquiera de las dos, mientras que otras requieren una en particular
por algn motivo especfico. No es que una sea mejor o peor que la otra, no es que
ZigBee sea ms completa por incluir a 802.15.4, ambas apuntan a resolver un
problema en particular, y ello genera caractersticas que les son especficas y que
pueden afectar el rendimiento de una aplicacin y deben ser tenidas en cuenta.
Los puntos fundamentales en donde se separan ambas tecnologas son:
throughput y latencia
self-healing y routing
complejidad de la red
complejidad del stack

Mdulos XBee
Quien ms, quien menos, la gran mayora estamos acostumbrados a usar una UART
en un microcontrolador o un puerto serie en una computadora. La idea de reducir
todo el stack ZigBee a un puerto serie es sumamente tentadora; el utilizar un mdulo
como XBee permite escribir la aplicacin como cualquier otra aplicacin que utiliza
un puerto serie, e incluso poder depurar utilizando la computadora sin necesidad de
simuladores. Incluso portar el cdigo a sta, no es necesario disponer del stack, la

163

Conectividad
interfaz con el mundo ZigBee es el puerto serie; cualquier sistema con capacidad de
ser programado y un puerto serie, inmediatamente es "ZigBee-compatible". Lo
mismo ocurre para 802.15.4; con slo agregar el mdulo, un sistema que se
comunica por cable pasa inmediatamente a ser inalmbrico, sin necesidad de realizar
modificaciones ms all del reemplazo del o los chips de interfaz por un mdulo
XBee6.
Como un beneficio adicional, estos mdulos cuentan con un conversor A/D de
varios canales y pines de I/O, por lo que es posible usarlos como adquisidores de
datos remotos.
No vamos a entrar en detalle porque estos mdulos tienen muchas prestaciones y
ya disponen de un texto propio. Daremos aqu algunos simples ejemplos de uso
como herramienta de conectividad

XBee 802.15.4
Podemos comunicarnos con estos mdulos mediante comandos AT o utilizando un
protocolo documentado denominado modo API. En cualquiera de los casos, se
trata de una conexin a una UART y un dilogo por el puerto serie.

Comunicacin punto a punto


Por defecto, el XBee funciona en modo transparente. En este modo, el mdulo
enva al remoto configurado como destinatario los mensajes que recibe por su puerto
serie, y presenta en ste los mensajes que recibe del mdulo remoto. Asignamos las
direcciones configurando el parmetro MY=<direccin> para la direccin propia y
DL=<direccin> para la direccin del remoto. Esto lo haremos en ambos mdulos
antes de ponerlos en servicio, por ejemplo:
D

mdulo 1
MY=1234
DL=4321
mdulo 2
MY=4321
DL=1234

Ambos mdulos permanecen con el receptor encendido a la espera de un mensaje


del otro.

Red punto a multipunto con coordinador


Los mdulos remotos son aqullos que tienen el parmetro CE con valor 0. El
mdulo central se denomina coordinador, y para que funcione como tal se deber
6

NdA: permtaseme la licencia potica de asumir que se dispone de 3,3V para alimentar al mdulo y
hacer la interfaz entre el micro y ste.

164

ZigBee e IEEE 802.15.4


setear el parmetro CE en el valor 1. Slo puede haber un coordinador por red
(PAN).
Cada mdulo remoto tendr como direccin de destino la del mdulo central, y su
direccin propia ser nica. Esto sucede de forma automtica al asociarse.
Para que un remoto inicie el proceso de asociacin, debe setearse el bit 2 en el
parmetro A1. Para que el coordinador asocie remotos que se lo solicitan, debe
setearse el bit 2 en el parmetro A2. Por ejemplo A1=4 y A2=4, en uno y otro
mdulo, respectivamente. Esto puede realizarse manualmente o dejarse guardado en
la configuracin para que se realice automticamente al arrancar.
En el Rabbit conectado al mdulo coordinador, recibiremos los mensajes que nos
enven los remotos. Para determinar quin es el que nos enva el mensaje, deberemos
insertar algn tipo de identificador dentro del mismo mensaje, o utilizar el modo
API.
La informacin que recibe el coordinador se enva a aquel remoto cuya direccin
coincide con lo que colocamos en los parmetros DH y DL. Para direccionar
diferentes mdulos, deberemos alterar peridicamente este parmetro (cada vez que
deseemos transmitir a un remoto diferente), por lo que suele ser preferible utilizar el
modo API. Los remotos permanecen en bajo consumo con el receptor apagado por
un tiempo que se puede configurar, y al despertar siempre transmiten la informacin
al coordinador.

XBee ZB
En el caso del XBee ZB, tambin podemos utilizar los comandos AT o el modo
API. Sin embargo, dado que ya existe una biblioteca de funciones que nos simplifica
la operatoria, vamos a analizarla.

xbee_api.lib
Mediante la biblioteca de funciones xbee_api.lib se provee una serie de funciones
para el control de estos mdulos, con un acercamiento ms desde el punto de vista
del programador, que gusta de tener el control del proceso desde su dispositivo, en
este caso el Rabbit.
Si utilizamos un mdulo con el XBee a bordo, como por ejemplo el RCM4510W, o
una single-board computer, todo ocurre de manera transparente. Sin embargo, si
deseamos conectar un XBee a un mdulo Rabbit cualquiera, o incluso a una placa de
nuestro desarrollo, deberemos indicarle a xbee_api.lib en qu puerto serie est el
susodicho; lo cual realizamos mediante la siguiente definicin:
#define ZB_SERIAL_PORT B

Como adelantamos no describiremos esto en detalle, pero podemos mencionar que


el eje de la operatoria es la funcin xbee_tick(), que deberemos llamar
peridicamente, y disponemos de funciones para transmitir y recibir:

165

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 pequeo
prrafo a comentar sobre otro de sus libros, el cual aborda en detalle esta familia de
mdulos y en particular contiene un captulo centrado en la interaccin entre el
XBee ZB y los mdulos Rabbit, particularmente la familia RCM4500W, y enfocado
en la utilizacin de los mdulos XBee ZB con micros Rabbit y xbee_api.lib.
Gran parte del contenido de este apartado ha sido tomado de dicho libro, con
permiso 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 ttulo nos referimos a una alternativa econmica a Bluetooth y
ZigBee: los mdulos transceptores TRW-2.4G de Wenshing. Se trata de transceivers
que operan (como puede inferirse) en la banda de 2,4 GHz, con capacidad de
direccionamiento y seleccin de canal de comunicaciones. Esto nos permite no slo
elegir el canal a utilizar, sino adems manejar todo un esquema de direccionamiento
de forma transparente, que nos posibilita comunicaciones multipunto. No existe
soporte de encripcin y/o seguridad.
Estos mdulos poseen una potencia de salida de 0dBm, lo que permite un alcance
algo ms reducido que una red Wi-Fi, ms cercano a Bluetooth (metros a decenas de
metros). Funcionan a 3,3V, y su consumo es bastante reducido; pero lo realmente
interesante est en su interfaz. La interfaz del mdulo es netamente digital. Si bien
nada impide su utilizacin en la forma tradicional (entra bit - sale bit), estos mdulos
pueden trabajar con un sistema denominado ShockBurst, que es algo as como un
store and forward que permite emplear micros sin UART, y/o con relojes de baja
frecuencia y/o poca precisin, comunicndose a una velocidad baja, sin por ello
mantener ocupado el canal de comunicaciones, en el cual la comunicacin es a alta
velocidad, minimizando a la vez consumo y ocupacin del canal.

166

RF multipunto 2,4 Ghz


El micro y el mdulo se comunican mediante una interfaz de cinco pines, al ritmo
que el primero marca en la seal CLK; esto permite que el micro opere sobre el
mdulo cuando dispone de tiempo para hacerlo. Sealizado el fin de paquete a
transmitir, el mdulo lo transmite a una velocidad mucho mayor (250Kbps),
minimizando el riesgo de colisiones con otros mdulos y el consumo. El mdulo que
recibe un mensaje, informa al micro mediante el pin DR1; este ltimo proceder
entonces a leer la informacin del mdulo a su propio ritmo.
Las seales restantes sirven para informar al mdulo que lo estamos accediendo
para configurarlo (CS), o para enviar o recibir datos (CE). Los datos viajan en uno u
otro sentido por el pin DATA. Si bien existen algunas alternativas adicionales como
la posibilidad de recibir en dos canales, las omitiremos por simplicidad.
CLK

DATA
(micro)
Transmisor

CE
Tx
tiempo en
el aire
Consumo

CE
DR1
Receptor
CLK

DATA
(modulo)

Al aplicar alimentacin al mdulo, ste se encuentra en un estado indefinido y


deber ingresarse la configuracin. Entre estos datos, se encuentra la direccin
propia del mdulo, el canal de operacin dentro de la banda, la longitud de los
mensajes, y el modo de trabajo, ya que es half-duplex. Tanto recepcin como
transmisin se realizan por el mismo canal.
Previo al mensaje a transmitir, el micro comunica al mdulo la direccin del
mdulo destinatario, como si fuera parte del mensaje (los primeros cinco bytes). El
mdulo agregar luego un CRC a la cola y transmitir el mensaje. Como el mdulo
167

Conectividad
destinatario conoce la longitud de los mensajes, puede validar el CRC y comunicar
al micro la presencia de un mensaje slo cuando ste es vlido. Al entregar el
mensaje, se eliminan la propia direccin y el CRC, es decir, se obtiene el mensaje
solamente.

Desarrollo de una biblioteca de funciones


Vamos a aprovechar este ejemplo para abordar otro tema interesante: el desarrollo
de una biblioteca de funciones. Implementaremos una biblioteca de funciones que se
ocupe de enviar y recibir mensajes utilizando estos mdulos. En un primer paso,
escribiremos todo como si fuera un simple programa, el cual podemos testear
compilndolo de forma independiente. Ms adelante, agregaremos los detalles que
hacen que Dynamic C incorpore los textos de ayuda e incluya la biblioteca.
La seleccin de los pines a emplear para el control del transceptor la realizamos
mediante macros. En la biblioteca de funciones podemos definir alguna combinacin
que consideremos tpica, pero lo fundamental es que puedan redefinirse dentro del
programa ms adelante. Esto lo hacemos mediante macros, como generalmente se
hace en cualquier entorno C, de la siguiente forma:
#ifndef TRW_DATA_PORT
#define TRW_DATA_PORT
#endif
#ifndef TRW_DATA_SHADOW
#define TRW_DATA_SHADOW
#endif
#ifndef TRW_DATA_DIR
#define TRW_DATA_DIR
#endif
#ifndef TRW_DATA_DSHADOW
#define TRW_DATA_DSHADOW
#endif

PFDR
PFDRShadow
PFDDR
PFDDRShadow

El mismo procedimiento lo podemos emplear para la direccin local:


#ifndef TRW_LOCAL_ADDRESS
#define TRW_LOCAL_ADDRESS 0X12,0X34,0X56,0X78,0X9A
#endif

y por qu no para otros parmetros.


Comenzaremos por desarrollar rutinas para escribir y leer un byte en el mdulo:
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 mscara
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

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 );
if (BitRdPortI(TRW_DATA_PORT,TRW_DATA_BIT))
val=(val | i);
BitWrPortI (TRW_SCK_PORT,&TRW_SCK_SHADOW,0,TRW_SCK_BIT );
}
return val;

// CLK
// lee bit

Luego, seguimos por el proceso de inicializacin, en el cual escribiremos la


configuracin deseada.
nodebug void trw_init()
{
int i;
unsigned char *cfgptr;
const static unsigned char config[18]={
0X8E,0X08,0X1C, // TEST, PLL
TRW_DATA_LENb,
// DATA2_W longitud de datos 2do receptor
TRW_DATA_LENb,
// DATA1_W longitud de datos
TRW_DUMMY_ADDRESS,
// ADDR2 direccin segundo receptor
TRW_LOCAL_ADDRESS,
// ADDR1 direccin
TRW_ADDR_W,
// ADDR_W/CRC tipo de CRC
TRW_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);
i=18;
cfgptr=config;
while(i--)
trw_write_byte(*cfgptr++);
BitWrPortI (TRW_CS_PORT,&TRW_CS_SHADOW,0,TRW_CS_BIT);

// sube CS
// enva config
// baja CS

A continuacin, un par de rutinas para enviar y recibir un mensaje. Ntese como


insertamos la direccin 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

Conectividad
BitWrPortI
BitWrPortI
BitWrPortI
BitWrPortI
BitWrPortI
BitWrPortI

(TRW_DATA_PORT,&TRW_DATA_SHADOW,1,TRW_DATA_BIT );
// DATA=1
(TRW_SCK_PORT,&TRW_SCK_SHADOW,1,TRW_SCK_BIT );
// clk
(TRW_SCK_PORT,&TRW_SCK_SHADOW,0,TRW_SCK_BIT );
(TRW_CS_PORT,&TRW_CS_SHADOW,0,TRW_CS_BIT);
(TRW_DATA_DIR,&TRW_DATA_DSHADOW,0,TRW_DATA_BIT ); // libera DATA
(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();
BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,1,TRW_CE_BIT);
len=TRW_ADDRESS_LEN;
while(len--){
// manda
trw_write_byte(*address++);
yield;
}
len=TRW_DATA_LEN;
while(len--){
// manda
trw_write_byte(*buf++);
yield;
}
BitWrPortI (TRW_CE_PORT,&TRW_CE_SHADOW,0,TRW_CE_BIT);

// configura tx
// sube CE
direccin remota

datos

// 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 indicacin
taimaut=MS_TIMER+timeout;
while(!BitRdPortI(TRW_DR_PORT,TRW_DR_BIT)){
// de recepcin 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 datos
yield;
}
return(TRW_DATA_LEN);
}

Finalmente, una simple rutina adicional que se encarga de retransmitir el mensaje si


no se obtiene una respuesta dentro de un cierto tiempo. Dado que se trata de un
170

RF multipunto 2,4 Ghz


medio de acceso mltiple en el cual no tenemos deteccin de portadora ni de
colisiones, es menester arbitrar de algn modo la comunicacin para minimizar
retransmisiones y lograr confirmar que la informacin llega a destino. La forma ms
simple, que coincide con la propuesta, suele ser tener un sistema configurado como
master, el cual interroga a los remotos y obtiene respuesta de ellos. Si la pregunta del
master no llega a destino, ste la retransmitir. Si la respuesta del remoto no llega, el
master retransmitir la pregunta. Los remotos debern estar preparados para recibir
varias 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);
//
waitfor(DelayMs(TRW_TXGUARDTIME));
//
wfd i=trw_getpacket(msg,timeout);
//
} while ((i<0) && (retries++ < TRW_NUM_RETRIES));
//
return(i);
//

enva mensaje
espera transmisin
espera respuesta
repite si no la hay
hasta que desiste

Si bien al desarrollar estas rutinas tuvimos en cuenta las especificaciones del


fabricante, aprovechamos los retardos normales de un programa en C para evitar
introducir demoras. Hemos comprobado el funcionamiento correcto aun en cores
rpidos como el RCM-3360. Para otras aplicaciones, sugerimos revisar que se
cumplan 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 caractersticos de Dynamic C.
La primera impresin, para el experimentado programador ANSI C, es que generar
bibliotecas de funciones en Dynamic C es muy complicado. Sin embargo, resulta
mucho ms simple de lo que parece. Existen una serie de reglas, tendientes a
simplificar tanto el trabajo de creacin como el de uso de la biblioteca de funciones.
Dynamic C se encargar de buscar cosas especficas para extraer la informacin que
necesita, tanto para el sistema de ayuda como para la generacin de cdigo.

Encabezado de la biblioteca de funciones


Existe un juego de encabezados para la biblioteca de funciones en s: uno para el
sistema de ayuda, y otro para la inclusin de cdigo.

Sistema de ayuda
Al comienzo del archivo, debe existir un encabezado que explique para qu sirve
esta biblioteca de funciones:
/* START LIBRARY DESCRIPTION *********************************************
TRW-24G.LIB

171

Conectividad

DESCRIPTION:
Este espacio es para la descripcin
END DESCRIPTION **********************************************************/

Esto permite que el lector inquieto pueda conocer los alcances del cdigo, y que
Dynamic C reconozca la biblioteca de funciones.

Cdigo
Lo que pongamos dentro del siguiente juego de comentarios:
/*** BeginHeader */
/*** EndHeader */

ser incorporado al cdigo que "usa" esta biblioteca de funciones. Un ejemplo tpico
son las macros y definiciones que usualmente se ponen en los archivos de
encabezados (header files), mejor conocidos como .h
Por ejemplo, en nuestra biblioteca de funciones, las definiciones de los pines, y
dems parmetros 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 cerrarse


varias veces a lo largo del archivo.

Encabezados de funciones y variables globales


Adems del encabezado de la biblioteca de funciones, existe un juego de
encabezados del sistema de ayuda para cada funcin y otro para inclusin de cdigo,
para cada variable o grupo de stas que deba ser accedida por una funcin o grupo
de stas.

Sistema de ayuda
Dentro de un comentario de este tipo, se escribe el texto de ayuda que Dynamic C
mostrar cuando se utilice la ayuda on-line. Este procedimiento se repite para cada
funcin que deba ser utilizada desde el exterior
172

RF multipunto 2,4 Ghz

/* START FUNCTION DESCRIPTION ********************************************


nombre de la funcin
<library a que pertenece>
SYNTAX:

void mifuction(void)

DESCRIPTION:

Descripcin de la funcin

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 mode


This function must be called >5ms after startup,
before any other access to the module

END DESCRIPTION **********************************************************/

La decisin de qu idioma utilizar corre por cuenta del usuario, aunque el ingls
permite no desentonar con los encabezados y poder publicar el trabajo de forma
abierta.

Cdigo
Ahora debemos generar el equivalente a las declaraciones de variables estticas
externas y funciones externas, es decir, lo que normalmente hace que el linker pueda
hacer su trabajo. Esta es la informacin que usualmente se pone en los archivos de
encabezados.
Lo que pongamos dentro del siguiente juego de comentarios:
/*** BeginHeader nombre */
/*** EndHeader */

ser incorporado al cdigo que usa la funcin o variable nombre. Lo que hacemos es
declarar todo lo externo dentro de estos comentarios, para que quien usa la
biblioteca 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 las
declaraciones de variable externa dentro de los encabezados correspondientes:
173

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 en
el 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 instalacin de Dynamic C.
de forma local, definiendo en las opciones de proyecto el archivo a utilizar a tal
fin. Lo ms fcil es copiar el archivo original en el subdirectorio donde alojamos
el proyecto, modificarlo, y decirle a Dynamic C que use ese archivo.
Una vez hecho esto, al arrancar Dynamic C encontrar nuestra biblioteca de
funciones 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 definicin que deba hacerse respecto a la biblioteca de funciones, deber


colocarse antes de "usarla". En nuestro caso, definiremos los pines a emplear,
direccin, etc., antes de incluirla:
#define TRW_CS_BIT
#define TRW_CE_BIT
#define TRW_DR_BIT

6
7
1

#define TRW_LOCAL_ADDRESS 0x21,0x43,0x65,0x87,0xA9


#use TRW-24g.lib

Ejemplos
A continuacin, un par de esqueletos de programa de ejemplo; uno para master
(pregunta) y otro para slave (responde), respectivamente. La totalidad del listado, en
la forma de biblioteca de funciones, se encuentra en el CD adjunto, en la seccin de
notas de aplicacin, como CAN-045. Tambin se incluyen dos programas ejemplo
de uso, uno para RCM-3360 (master) y otro para RCM-3720 (slave). Ambos
comentan la operacin por el port serie A a 57600 bps.

174

RF multipunto 2,4 Ghz

Master
void main()
{
unsigned int num;
int i;
unsigned char msg[TRW_DATA_LEN+1];
trw_init();
num=0;
while(1){
costate {
sprintf(msg,"%d:Mensaje",num);
wfd i=trw_poll(raddr1,msg,100);
if(i>0){
// procesa respuesta
}
num++;
}
// otras tareas
}

// inicializa mdulo

// prepara pregunta
// enva y espera respuesta

// otro mensaje...

Slave
void main()
{
int i,num;
unsigned char msg[TRW_DATA_LEN+1];
trw_init();

// inicializa mdulo

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 recepcin
wfd trw_sendpacket(raddr1,msg);
// enva respuesta
waitfor(DelayMs(TRW_TXGUARDTIME));
// espera transmisin
}
else {
// no hay preguntas por 5 segundos (me voy a dormir ?)
}
}
// otras tareas
}
}

175

Conectividad

Comunicacin serie asincrnica


Dynamic C incorpora una gran cantidad de funciones que facilitan el trabajo
asociado al manejo de puertos serie. Estas funciones estn distribuidas en dos
bibliotecas de funciones con soporte para comunicaciones va interfaz serie
asincrnica. Nos interesa particularmente una de ellas: packet.lib. Esta biblioteca de
funciones est orientada a las comunicaciones half-duplex, brindando un conjunto de
funciones que envan y reciben bloques de datos. Para recepcin se utiliza un
conjunto de buffers, cada buffer puede alojar un paquete de informacin. Los buffers
se ubican sobre xmem, y permiten almacenar los paquetes recibidos hasta tanto el
programa encargado tenga tiempo de procesarlos. Dado el carcter de half-duplex,
las funciones de transmisin retornan a recepcin una vez finalizado el envo del
ltimo byte, ms un posible tiempo de guarda definible por el usuario. Esto es
configurable, pudiendo elegirse otros modos de operacin como deteccin de bit de
address (sealizacin por noveno bit) o caracter de sincronismo. Al iniciarse la
operacin de recepcin, se llama a una funcin provista por el usuario, y al iniciarse
la operacin de transmisin se llama a otra funcin complementaria. Estas llamadas
a funciones tienen el objeto de poder configurar el medio para la operacin a
ejecutar, como por ejemplo actuar sobre los transceivers en RS-485, segn viramos
en el libro introductorio.
Para un repaso de los registros involucrados y las funciones y opciones de pines
disponibles, aconsejamos rever la seccin correspondiente a los puertos serie en el
captulo sobre perifricos internos. A continuacin, haremos un anlisis de las
diversas formas de comunicacin soportadas.

Sealizacin por noveno bit


Al parecer esto se origin con la familia MCS51 (8031, 8051, etc.). Se trata de un
recurso muy simple para minimizar el consumo y simplificar la operatoria en
sistemas multipunto. Uno de los problemas ms importantes a resolver a la hora de
implementar un protocolo de comunicaciones, es la forma de detectar el comienzo
de un mensaje. Si bien quien transmite sabe cuando deja de transmitir y quien recibe
puede estar consciente de lo que est haciendo; en un sistema multipunto en el que
varios interlocutores estn hablando, es muy difcil discernir entre un byte de datos
entre interlocutores y la propia direccin, a menos que constantemente se est
siguiendo el dilogo, lo cual puede no ser posible luego de un reset, o si se pretende
que quienes no estn involucrados en la conversacin puedan descansar. Uno de los
recursos que resuelven esto es la sealizacin por noveno bit, en la cual la
transmisin de datos se hace mediante 9-bits, uno de los cuales (el noveno...) indica
si se trata de un byte de direccin (a qu equipo se le habla) o de datos (el paquete en
s). De esta forma, todos los dispositivos ajenos al dilogo pueden dormir hasta
recibir un byte de direccin, comparar con la propia, y seguir durmiendo si no

176

Comunicacin serie asincrnica


corresponde. Algunas UARTs permiten este funcionamiento dado que pueden ser
configuradas para slo generar una interrupcin ante un byte de direccin (situacin
que debe ser modificada al recibir la propia, para permitir la recepcin del resto del
mensaje).
Recordemos que los ports serie de Rabbit 2000 y 3000 no soportan bit de paridad y
mltiples bits de stop directamente en el hardware, pero estos bits pueden simularse
con drivers adecuados. En particular, las revisiones posteriores del R2000 (R2000A
en adelante) incorporan un registro denominado long stop que permite simplificar
los drivers para estas aplicaciones. Si bien Dynamic C soporta todas estas
combinaciones de forma transparente, es interesante tener presente el hardware a la
hora 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 una
demora de un bit o ms luego del bit de stop, antes de transmitir el siguiente
caracter. Esto ser interpretado por el receptor como un noveno bit en uno y
posibles bits de stop adicionales.
Con esta informacin presente, observemos la sample Samples\pktdemo.c que
utiliza la biblioteca de funciones packet.lib, la cual soporta el modo de sealizacin
por noveno bit.
La clave para la operacin est en la funcin pktXopen(), donde 'X' representa a la
interfaz que se desee utilizar. Leyendo atentamente el help de esta funcin, ya sea en
el 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 sample
recomendada, observamos lo siguiente (transcribimos la parte ms importante):
PARAMETER1:
PARAMETER2:

PARAMETER3:

Bits per second of data transfer: min 2400


mode - type of packet scheme used, options are:
PKT_GAPMODE
PKT_9BITMODE
PKT_CHARMODE
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. The


function should return 1 if the packet is complete, or 0 if
more data should be read in.

177

Conectividad
Esto quiere decir, que para poder utilizar noveno bit en modo sealizacin,
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 sealizar
el inicio de trama mediante un elemento de 9-bits con el noveno bit en cero
(rompe la trama donde se esperara un bit de stop), mientras que el resto de los
elementos se enva en 8-bits, aumentando la tasa de transferencia, dado que no se
desperdicia un bit.
PKT_LOWSTARTBYTE: el primer elemento tiene el noveno bit en cero, el
resto de los elementos lo tiene en uno.
PKT_HIGHSTARTBYTE: el primer elemento tiene el noveno bit en uno, el
resto de los elementos lo tiene en cero.
Dejemos de lado el cuarto parmetro por el momento.
Reduciendo el problema a su mnima expresin, para poder transmitir tramas con la
sealizacin deseada y observarlas en un osciloscopio, quedara:
#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 enva reiteradamente la secuencia 'AT'; la 'A' (0x41) con 9-bits, el noveno en
estado alto (se ve como un stop bit adicional), y la 'T' (0x54) con 9 bits, el noveno en
estado bajo. Observamos a la salida del micro:
noveno bit
A
1 00 00 01 01

START

178

noveno bit
T

0 01 01 01 0 0

STOP START

STOP

Comunicacin serie asincrnica


Las funciones vacas son slo eso (funciones vacas...), dado que no las usamos para
esta implementacin, pero la biblioteca packet.lib() las necesita.
En particular, las tres ltimas permiten conmutar transceivers en RS-485, segn
viramos 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 lgico durar ms de lo
esperado. Al fundirse con el bit de stop, lo que se observa es un espacio intercaracter
ms largo. Si nuestro procesador es R2000A o superior (marcado como
"IQ3T","IQ4T","IQ5T") o R3000, ya dispone del SxLR (Long Stop Register) y la
tarea mencionada se realiza por hardware.
Para poder recibir, deberemos escribir una funcin dentro del cuerpo de
packet_test() que valide lo recibido y tome la decisin de aceptar el paquete (retorna
el valor 1 ), o seguir recibiendo datos (retorna el valor 0 ). La sample que
mencionamos tiene escrita una funcin de ese tipo, la cual puede servirnos de gua,
para poder analizarla y modificarla segn nuestras necesidades. Para detectar la
direccin del mensaje y compararla con la nuestra, dentro de esta misma funcin
disponemos del segundo parmetro (en este caso length), el cual nos indica la
cantidad de caracteres que hemos recibido hasta el momento, y por ende qu caracter
del mensaje estamos recibiendo.

Sealizacin por tiempo muerto


Como analizramos en el apartado anterior, uno de los problemas ms importantes
a resolver a la hora de implementar un protocolo de comunicaciones, es la forma de
detectar el comienzo de un mensaje. Otro de los recursos que resuelven esto es la
sealizacin por tiempo muerto, en la cual la transmisin de datos se hace por
paquetes, dejando un tiempo muerto mnimo obligatorio entre paquetes. De esta
forma, todos los dispositivos ajenos al dilogo pueden dormir hasta detectar un
tiempo muerto, esperar a recibir un byte (en lo posible tambin durmiendo),
comparar con la propia direccin, y seguir durmiendo si no corresponde. Este
esquema es el empleado por protocolos como Modbus RTU.
Si bien es factible implementar esto manualmente utilizando timers y/o drivers
adecuados, la clave para la operacin est una vez ms en la funcin pktXopen() y
adems en pktXsend() y su compaera cofunction cof_pktXsend(). En el help de
pktXopen() observamos lo siguiente (transcribimos la parte ms importante):
PARAMETER1:
PARAMETER2:

PARAMETER3:

Bits per second of data transfer: min 2400


mode - type of packet scheme used, options are:
PKT_GAPMODE
PKT_9BITMODE
PKT_CHARMODE
options - further specification for the packet scheme
The value of this depends on the mode used.

179

Conectividad

gap mode - minimum gap size(in byte times)


PARAMETER4:

ptr to function that tests for completeness of a packet. The


function should return 1 if the packet is complete, or 0 if
more data should be read in. For gap mode the test function is
not used and should be set to NULL.

En el help de pktXsend() observamos lo siguiente (transcribimos la parte ms


importante):
PARAMETER3:

The number of byte times to delay before sending the


data (0-255) This is used to implement protocol-specific
delays between packets

La funcin pktXopen() define el modo de trabajo para la deteccin de mensajes en


recepcin, mientras que pktXsend() es la que define la demora entre mensajes
enviados. La demora insertada es siempre algo mayor, por lo menos un tiempo de
caracter ms, lo cual nos garantiza que el receptor remoto detectar el espacio entre
tramas.
El ejemplo siguiente muestra como enviar tramas con un tiempo muerto mnimo de
al 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 recepcin, al menos 3 bytes
while (1) {
pktBsend("AT", 2, 3);
// para transmisin, al menos 3 bytes
}
}
nodebug void pktBinit(){}
nodebug void pktBrx(){}
nodebug void pktBtx(){}

Las funciones vacas siguen siendo funciones vacas, dado que no las usamos para
esta implementacin, pero la biblioteca packet.lib() las necesita. Las tres ltimas
permiten conmutar transceivers en RS-485, como comentramos en el apartado
anterior.
En recepcin, la biblioteca de funciones identificar el comienzo de un mensaje al
detectar transmisin luego del tiempo muerto configurado. De un modo similar
detectar la finalizacin del mismo y lo colocar en el buffer de recepcin. De all
podremos extraerlo mediante la funcin pktXreceive(). Dado que no intervenimos en
el proceso de recoleccin del paquete, la validacin de la direccin la deberemos
hacer una vez recibido ste.

180

Comunicacin serie asincrnica

Sealizacin por caracter especial


Otro de los recursos para detectar el comienzo de un mensaje es la sealizacin por
caracter especial, en la cual la transmisin de datos se hace utilizando un sistema de
codificacin tal que sea imposible duplicar este caracter o secuencia de caracteres
especiales dentro del flujo normal de datos. De esta forma, todos los dispositivos
ajenos al dilogo pueden dormir hasta detectar este caracter especial, recibir la
direccin (a quin se manda el mensaje), comparar con la propia, y seguir durmiendo
si no corresponde. Este esquema es el empleado por protocolos como Modbus
ASCII, que enva los datos utilizando dos caracteres por byte ('0' y 'D' para 0x0D) y
utiliza caracteres simples para indicar el comienzo y fin de mensaje (':' para el
comienzo y 0x0D 0x0A para el final).
La clave para simplificar la operacin de recepcin est una vez ms en la funcin
pktXopen(), en cuyo help observamos lo siguiente (transcribimos la parte ms
importante):
PARAMETER1:
PARAMETER2:

PARAMETER3:

Bits per second of data transfer: min 2400


mode - type of packet scheme used, options are:
PKT_GAPMODE
PKT_9BITMODE
PKT_CHARMODE
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. The


function should return 1 if the packet is complete, or 0 if
more data should be read in. For gap mode the test function is
not used and should be set to NULL.

Para la transmisin, deberemos nosotros introducir el caracter especial al principio


de la trama, dado que la presencia de PKT_CHARMODE no realiza ningn tipo de
modificacin o insercin de lo que se desea transmitir. El receptor, en cambio,
comienza a recibir los mensajes cuando detecta el caracter ':' y termina cuando la
funcin packet_test() indica fin de paquete (retorna el valor 1 ). El mensaje as
identificado es colocado en el buffer de recepcin, de donde puede extraerse
mediante la funcin pktXreceive(). Para detectar la direccin del mensaje y
compararla con la nuestra, dentro de packet_test() disponemos del segundo
parmetro, 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

Conectividad

IrDA
IrDA es el nombre de la asociacin que define el stack de protocolos para WPAN
(Wireless Personal Area Network) utilizando comunicacin por haz infrarrojo.
La base, es decir, el enlace fsico, es el IrPHY, que define los parmetros pticos de
la interfaz, velocidades y rango de distancias de operacin. Debido a que al
transmitir, es altamente probable que el LED transmisor ciegue al fotodiodo (o
equivalente) receptor, se opera en half-duplex. La velocidad de operacin depende
del modo; por ejemplo SIR (Serial InfraRed) cubre la gama de velocidades
normalmente encontradas en un enlace serie RS-232, mientras que modos de
velocidades ms altas cubren hasta el orden de varios Mbps. Cada modo codifica los
bits en pulsos lumnicos de diferente forma.
El segundo nivel es IrLAP (Infrared Link Access Protocol), que se encarga de
descubrir posibles interlocutores y establecer un enlace confiable. Dentro de este
protocolo, se establece un master y uno o ms slaves, de modo que los slaves slo
pueden transmitir cuando el master se lo indica.
En el tercer nivel se ubica IrLMP (Infrared Link Management Protocol), que
provee las diversas conexiones virtuales necesarias. Sobre este stack de tres
protocolos corre generalmente TTP (Tiny Transport Protocol), que sirve de sustento
a protocolos ms complejos como IrOBEX (tambin conocido como OBEX), para
intercambio de objetos, IrLAN, que permite acceder a una LAN, y algunos modos
de IrCOMM, que permite simular puertos de comunicaciones.
A partir del Rabbit 3000, es posible configurar las UARTs para poder operar en el
modo de sealizacin que SIR requiere. Al momento de escribir este texto, Rabbit
incluye transceivers con soporte para SIR en algunos de los kits de desarrollo. Si
bien nada impide al usuario final implementar todo el stack de protocolos por su
cuenta, 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 un


controlador como el MCP2140, que provee soporte IrComm a 9600bps. Sin
embargo, no hemos experimentado sobre este tema.

182

GSM

GSM
GSM (Global System for Mobile communications) es el standard corriente en
materia de comunicaciones celulares. Si bien existen otras alternativas, la
versatilidad y existencia de standards e informacin hacen sumamente interesante a
esta tecnologa para la interconexin de dispositivos inteligentes.
Los dispositivos que emplearemos son generalmente modems GSM, los cuales
manejarn la complejidad de la red GSM y sern controlados mediante una interfaz
relativamente simple, generalmente extensiones GSM standard y propietarias a los
comandos AT7.
Sin entrar dentro del tema de networking, incluimos aqu una breve descripcin de
las formas ms prcticas y comunes de obtener conectividad en una red GSM.
Analizaremos aqu lo que es ms apropiado para este entorno; veremos en el captulo
sobre networking lo relativo a esta disciplina.

CSD
CSD (Circuit Switched Data) es la forma ms antigua de comunicacin de datos
dentro de GSM.
En CSD, se establece una conexin a un determinado ancho de banda, y se reserva
ese ancho de banda por el tiempo de duracin de la conexin, ms all de si hay
trfico o no. Es prcticamente equivalente a realizar una llamada telefnica con un
modem, y de hecho algunas compaas prestatarias del servicio incluyen la opcin
de conectarse a modems en la PSTN (Public Switched Telephone Network, la red
pblica telefnica) desde dispositivos en la red GSM, adems de la conexin entre
dos dispositivos de la red GSM. En lo que a nosotros como developers nos
concierne, el costo es alto debido a que generalmente se tarifa por tiempo de
conexin, y las velocidades de comunicacin no suelen ser altas. Los tiempos de
latencia son relativamente bajos, dado que existe una conexin virtual establecida.
La red reserva una determinada cantidad de timeslots para la comunicacin, haya o
no trfico.
El modem GSM nos permite establecer una conexin al destino solicitado y cursar
datos por la misma; por lo general, el inicio de la conexin se realiza de forma
similar a un modem telefnico, ya sea originando una conexin saliente o atendiendo
una conexin entrante. Por este motivo, cualesquiera de los mtodos de conectividad
normalmente empleados en un puerto serie asincrnico (siempre y cuando
trabajemos con 8-bits) puede emplearse con un modem y por ende con un modem
GSM en una comunicacin CSD.
7

Los comandos AT, tambin conocidos como comandos Hayes, son un set de comandos para
configuracin y control de modems. Su nombre comn proviene del hecho de que todo comando
comienza con AT, lo cual originalmente serva para que el modem pudiera detectar velocidad, largo
de palabra y paridad, cuando el usuario llamaba su ATencin (attention).

183

Conectividad
ATD<nmero remoto>
CONNECT
NO CARRIER
RING
ATA
CONNECT
NO CARRIER

Existe una pequea salvedad; debido al hecho de que quien provee la funcionalidad
de modem telefnico8, al conectarse con la PSTN, es la empresa prestataria del
servicio de telefona celular. Si la llamada se origina en una lnea de abonado comn
de la PSTN, sta no tiene forma de sealizar a la red GSM que la llamada es de
datos, por lo que se cursa como una llamada de voz.
Mdulo GSM
Equipo remoto

Serie Asinc
Mdulo GSM

Funcionalidad de
Modem

PSTN

red GSM

slo voz

Serie Asinc

CSD

Modem telefonico

PCM

Equipo remoto

Seal
Analgica Serie Asinc

SMS
SMS (Short Message Service) permite enviar y recibir mensajes cortos dentro de la
red GSM. De esta forma, es posible enviar un mensaje a un dispositivo remoto sin
que ste tenga que estar conectado permanentemente a la Internet, ni esperar a que
inicie una conexin, ni llamarlo va CSD. De igual modo, el dispositivo remoto
puede reportar lo que necesite sin necesidad de conectarse. SMS es tal vez la opcin
ms econmica para sistemas que requieren muy bajo volumen de trfico, y dado
que todo lo relativo a la transferencia de los mensajes es manejada por el modem y la
red GSM, el dispositivo no requiere mayor inteligencia ni stack de protocolos. La
8

Modulacin de una portadora de audio con la informacin digital proveniente del modem GSM y
demodulacin de una seal de audio ingresando los datos obtenidos a la red GSM

184

GSM
desventaja viene de la mano de la carencia de una certeza en el tiempo de entrega (e
incluso de la entrega misma) de la informacin, lo que puede demorar desde
milisegundos hasta horas.

Mdulos SIMCOM
La operatoria podr diferir de acuerdo al modem GSM que se utilice. Resumimos
los pasos esenciales para poder enviar y recibir mensajes de texto SMS, utilizando
mdulos de la empresa SIMCOM como por ejemplo SIM200 o SIM340.

Seleccin del formato


Se realiza mediante el comando AT+CMGF, eligiendo modo texto o modo PDU. A
los fines prcticos, el modo texto es mucho ms fcil de entender por un ser humano,
y no requiere una elevada comprensin de los formatos involucrados, siendo
igualmente simple de procesar por un micro:
AT+CMGF=1

Envo de mensajes
Para enviar un mensaje a un nmero determinado, ingresamos el comando
AT+CMGS="<phonenumber>", a lo cual recibiremos un prompt (caracter '>'), para
luego ingresar el mensaje, el que terminaremos con un <Ctrl-Z> (ASCII SUB,
0x1A). El listado siguiente muestra la secuencia de envo del mensaje, los caracteres
resaltados son los que enviamos nosotros, los caracteres ASCII no imprimibles
figuran con su nombre entre < >. Por claridad, se han omitido los <CR> y <LF>.
AT+CMGS="55555555"
> cuerpo del mensaje<SUB>
+CMGS: 5
OK

La respuesta al comando es el ID del mensaje, terminado con el clsico OK.

Recepcin de mensajes
Al recibir un SMS, el mdulo GSM lo informa mediante un mensaje:+CMTI:
"SM",<index>, de modo que para leerlo simplemente debemos ingresar el comando
AT+CMGR=<index>. El listado siguiente muestra la secuencia de recepcin del
mensaje, los caracteres resaltados son los que enviamos nosotros, los caracteres
ASCII no imprimibles figuran con su nombre entre < >. Por claridad, se han omitido
los <CR> y <LF>.
+CMTI: "SM",1
AT+CMGR=1
+CMGR: "REC UNREAD","+541155555555",,"06/01/04,14:43:03+00"
cuerpo del mensaje

185

Conectividad

OK

Como se ve, la aparicin de la indicacin es "no solicitada". Si esto es una molestia,


se debe cancelar su aparicin mediante el comando AT+CNMI. La forma ms simple
es AT+CNMI=2,0,0,0,0. De esta forma, debemos interrogar al mdulo sobre la
presencia de un SMS, mediante el comando AT+CMGL, que no slo indica la
presencia de mensajes sino que adems los lista (y por consiguiente los marca como
ledos). El listado siguiente muestra la secuencia de recepcin del mensaje, los
caracteres resaltados son los que enviamos nosotros, los caracteres ASCII no
imprimibles figuran con su nombre entre < >. Por claridad, se han omitido los <CR>
y <LF>.
AT+CNMI=2,0,0,0,0
OK
AT+CMGL="REC UNREAD"
+CMGL: 2,"REC UNREAD","+541155555555",,"06/01/04,14:50:22+00"
cuerpo del otro mensaje
OK

De no existir ningn mensaje nuevo, la respuesta al comando AT+CMGL es


simplemente un OK:
AT+CMGL="REC UNREAD"
OK

Cualquiera fuere el mtodo empleado, el mensaje permanece en memoria despus


de ledo, podemos comprobarlo pidiendo los mensajes ya ledos:
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 mensaje
OK

Como puede observarse, ambos mensajes estn en memoria. Para borrarlos,


utilizamos el comando AT+CMGD, como muestra el ejemplo:
AT+CMGD=1
OK
AT+CMGD=2
OK

Ejemplos
El ejemplo siguiente muestra la forma de obtener los mensajes SMS con un modem
de SIMCOM. A modo acadmico, luego de cada comando mostramos la respuesta

186

GSM
del modem, que dado que no hemos inhabilitado el eco local, incluir el comando
mismo:
#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 peridicamente los mensajes, es conveniente pasar
a una forma ms compatible con el desarrollo de otras tareas. Aprovechando las
facilidades de Dynamic C, empleamos costates y cofunctions:
#define BOUTBUFSIZE 127
#define BINBUFSIZE 127
static char buffer[1000];
int main()

187

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();
}

Ms all de la pauprrima utilidad de este voluminoso y poco porttil sistema de


recoleccin de SMS, la idea general es poder usar el SMS para encapsular comandos
de configuracin o lo que fuere, de igual forma que como emplearamos, por
ejemplo, emails.
Para enviar mensajes, la operatoria es muy simple. Una vez configurado el modo de
trabajo con el comando AT+CMGF, simplemente hay que enviar
AT+CMGS="<nmero de destino>" y a continuacin el texto del SMS, terminado
por 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

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)));
buffer[count]=0;
printf(buffer);
while(!(count=serBread(buffer,sizeof(buffer)-1,300)));
buffer[count]=0;
printf(buffer);

// eco
// net confirm

serBclose();
}

GPRS
GPRS (General Packet Radio Service) es dentro de la arquitectura GSM el
transporte de datos que sucede a CSD, y permite transportar la informacin por los
canales 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 particularmente
interesante en dispositivos como Rabbit que tienen su propio stack TCP/IP y soporte
PPP, el costo total ms el de desarrollo (PPP para Rabbit es un mdulo que debe
comprarse por separado) y/o la ocupacin de memoria pueden llegar a favorecer este
tipo de implementacin, razn por la cual haremos una breve descripcin aqu. Las
caractersticas fundamentales de GPRS y el transporte de ste y otros protocolos
sobre GPRS, se detallan en el captulo sobre networking.

Mdulos SIMCOM
El ejemplo siguiente muestra un simple sistema en breves y cortos pasos que
aprovecha las extensiones de SIMCOM para poder enviar y recibir datos mediante
sus mdulos SIMxxx, ya sea va TCP o UDP, de una forma rpida y sencilla, sin
polling ni mquinas de estados:
1. Seleccin de APN: La seleccin del APN se realiza mediante el comando
AT+CSTT, segn cul sea nuestro proveedor, deberemos ingresar el APN que
ste defina:
AT+CSTT=1,"IP","<APN definido por el proveedor>"
2. Conexin a la red GPRS: mediante el comando AT+CIICR

189

Conectividad
3. Solicitud de direccin IP: mediante el comando AT+CIFSR, el cual, luego de un
tiempo, devuelve la direccin IP obtenida.
4. Establecimiento de la conexin con el sitio remoto: sea TCP o UDP el
protocolo a emplear, se debe "realizar una conexin". En el caso de TCP, es
necesario, en el caso de UDP, mantiene un estado de conexin interno para
aceptar datagramas del destino solicitado. La conexin se establece mediante el
comando AT+CIPSTART, que devuelve el mensaje CONNECT OK cuando la
conexin se establece (TCP) o inmediatamente (UDP). El comando tiene la
forma AT+CIPSTART="protocolo","direccin IP","port", por ejemplo:
AT+CIPSTART="UDP","200.114.232.92","2020"
5. Envo de datos: Indicamos al mdulo que queremos enviar datos mediante el
comando AT+CIPSEND. Podemos simplemente enviar AT+CIPSEND y recibir
un prompt, lo que nos permite enviar los datos y terminarlos con <CTRL-Z>, o
bien AT+CIPSEND=<longitud> y luego los datos sin terminador. El mdulo nos
contesta SEND OK al realizar la operacin
6. Recepcin de datos: cualquier dato que el extremo remoto nos enve, aparecer
por la interfaz como si fuera una respuesta del SIM200
7. Finalizacin de la conexin: mediante el comando AT+CIPCLOSE.
8. Cesin de la direccin IP: Una vez terminada la sesin, cedemos la direccin IP
para que el sistema la pueda asignar a otro mvil, mediante el comando
AT+CIPSHUT
A continuacin, un ejemplo con una prestataria argentina. En el mismo resaltamos
los comandos enviados para diferenciarlos de las respuestas del mdulo, y omitimos
el eco local, el cual puede eliminarse mediante el comando ATE0, standard del set de
comandos Hayes (AT). Los caracteres ASCII no imprimibles figuran con su nombre
entre < >:
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

GSM
<CR><LF>
SEND OK<CR><LF>
De este modo aparecera cualquier respuesta del servidor remoto
AT+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 la
comunicacin, 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 direccin IP, y
establecido el socket (UDP) o conexin (TCP), la comunicacin se reduce a enviar
el mensaje luego del comando AT+CIPSEND, y esperar la respuesta luego del
SEND OK del modem GSM.

Modo transparente
En modo transparente, el modem GSM se comporta como cualquier modem
telefnico en cuanto una vez "establecida la comunicacin" (abierto el socket), todo
lo que ingresa por el puerto serie es enviado al socket y lo que se recibe de ste es
enviado por el puerto serie. Esto nos permite olvidarnos del empaquetado y de
diferenciar respuestas remotas y respuestas del mdulo, pero no podemos ingresar
comandos AT sin antes escapar a modo comando, lo cual se realiza mediante la
tradicional secuencia +++9, o poner DTR inactiva10. Para volver al modo
transparente se utiliza el comando ATO, y para cortar ATH0 o simplemente ATH, de
igual modo que en una llamada CSD.
Para utilizar uno u otro modo (transparente o normal), lo indicamos mediante el
comando AT+CIPMODE, donde AT+CIPMODE=1 selecciona el modo
transparente. La forma de trabajo es similar a lo que estudiaremos en el captulo
sobre transporte de datos serie va TCP/UDP, por lo que permite una cierta
configuracin para optimizarlo a determinadas particularidades de funcionamiento,
lo que se realiza mediante el comando AT+CIPCCFG.

Dicha secuencia debe ser precedida y sucedida de un silencio de al menos 500ms. En modo
transparente no debe haber un espacio mayor a 20ms entre dichos caracteres.
10 Requiere que se haya habilitado con AT&D1

191

File Systems

Introduccin
Un file-system es la estructura que nos permite acceder a datos en un medio de
almacenamiento masivo, de una forma ordenada y establecida. Existen muchos de
ellos, cada uno con sus ventajas y desventajas, sus adeptos y sus crticos. Desde el
punto de vista de los sistemas dedicados, un file-system puede llegar a ser un
elefante con pocas probabilidades de caber en nuestro automvil, a menos que lo
elijamos cuidadosamente. Dynamic C incorpora el siguiente soporte, al momento de
escribir este libro:
D
para un file-system propietario destinado a funcionar sobre memorias flash
de nombre muy original: Flash File System, o cariosamente FS, el cual
subsiste en su segunda encarnacin como FS2. Esto es slo para Rabbit
2000 y 3000.
D
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 lgica, concepto de ms alto nivel y
manejabilidad que "los 758 bytes que empiezan en la posicin 0x1FF32",
particularmente cuando el nuevo contenido no tiene 758 bytes y hay que empezar a
desplazar el resto...
El sistema de archivos conocido como FAT resulta portado a Dynamic C como
consecuencia de la introduccin de medios de almacenamiento realmente masivo
(bueno, al menos hoy) como las flash seriales y las flash cards. Si bien FAT dista
mucho de ser ptimo, es lo suficientemente simple como para una buena
implementacin en sistemas dedicados, dando una mayor flexibilidad de uso que
FS2.

FS2
El FS2 es relativamente simple, si bien su utilizacin puede presentar un cierto
grado de complejidad, debido a lo complicado de la presentacin de la informacin
al respecto, y la nomenclatura empleada. El file-system puede funcionar tanto en
flash como en RAM, ya sea con o sin battery back-up (aunque tal vez su utilizacin
sea algo limitada en este ltimo caso). La nica condicin es que el soporte fsico
193

File Systems
est mapeado en memoria, es decir, conectado a los buses de direcciones y datos. Se
trata de un file-system limitado, sin directorios, pero que permite trabajar sobre
mltiples particiones en el mismo o en diversos soportes fsicos, cada una con
diferentes estructuras lgicas posibles.

Eleccin del soporte fsico


Antes que nada, siquiera de pensar en utilizar el FS2, deberemos elegir el soporte
fsico. Como dijramos, FS2 puede funcionar tanto en flash como en RAM. Si
vamos a utilizarlo en RAM, deberemos reservar un rea de memoria; si vamos a
utilizarlo en flash, tambin, pero segn qu tipo de mdulo estemos utilizando,
puede que nos encontremos con que ya hay un rea reservada. En efecto, mdulos
como el RCM2100 se proveen con dos chips de flash de 256KB cada uno, y en este
caso, el compilador asigna uno de los chips para cdigo y el otro para el FS2, por lo
que ya disponemos de un rea de 256K para el FS2, sin siquiera desearlo.
Si por el contrario, el mdulo que empleamos tiene solamente un chip de flash,
deberemos reservar el espacio en flash, dicindole al compilador que parte de la
memoria flash est reservada, que no debe utilizarla para poner cdigo all, y
adems, cunto de esa rea reservada se emplear para el FS2. La primera tarea se
realiza modificando el BIOS, segn puede apreciarse en el prrafo correspondiente
de este captulo. La segunda, se realiza definiendo la macro
FS2_USE_PROGRAM_FLASH, con un valor vlido en KB, por ejemplo:
#define

FS2_USE_PROGRAM_FLASH 16

// para 16KB de FS2 en


// flash de programa

Esta macro modifica la operacin de la library de FS2, y debe definirse antes de


incluir el cdigo de la misma, mediante:
#use "fs2.lib"

Para poder utilizar el FS2 en RAM, deberemos modificar el BIOS reservando un


rea para ste, segn puede apreciarse en el prrafo correspondiente. De ms est
decir que si queremos que el contenido de la RAM se mantenga al sacar la
alimentacin, deberemos conectar una pila de backup, caso contrario, deberemos
formatear el file-system cada vez que iniciemos el sistema. Un FS2 voltil en RAM
puede servir para alojar archivos temporarios, que sern volcados a flash (copiados a
otro "disco") cuando sean considerados definitivos.
Una vez que hayamos decidido el soporte fsico a utilizar, emplearemos la llamada
a funcin que corresponda para el mismo, cuando debamos referirnos a dicho
soporte fsico. Resulta que la segunda flash, si existe, es la preferida; mientras que la
flash principal, la que aloja el programa, es la "otra" flash, desde el punto de vista
del FS2; a menos que haya slo una flash, con lo cual no hay "otra". As, las
llamadas a funcin correspondientes son:
194

FS2

fs_get_flash_lx();

fs_get_other_lx();
fs_get_ram_lx();

//
//
//
//
//
//
//
//
//
//

usa la flash "preferida": la flash


secundaria, totalmente asignada para
FS2, o la porcin de flash de
programa asignada para FS2 si no hay
una flash secundaria
usa la porcin de flash de programa
asignada para FS2 si hay una flash
secundaria, totalmente asignada a FS2
usa la porcin de RAM asignada
para FS2

Es posible tener ms de un soporte fsico, es decir, podemos tener FS2 en RAM y


flash, accediendo a cada uno de ellos mediante la llamada a funcin correspondiente.
Hasta ahora evitamos hacer la analoga del soporte fsico 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 raz comn como en
Unix). A su vez, pueden tener divisiones (particiones), siendo cada una de stas un
ente lgico independiente. La nomenclatura empleada por Rabbit denomina LX
(Logic eXtension) a cada unidad, que en otros ambientes llamamos familiarmente
particiones, o en el ambiente MS-DOS y derivados, unidades lgicas.

Particin y formateo
Primeramente recordemos que estamos hablando de un microprocesador, y que por
lo 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 sistema
operativo (de la cual quisiera recibir una unidad, por curiosidad), la particin y el
formateo no son eventos que se realizan tal vez ms de una vez, posiblemente el
formateo pueda ser requerido alguna vez adicional a la inicializacin del equipo. Si,
como es de esperar, el formateo no es una actividad necesaria en el equipo
terminado, es preferible cargar algn programa aparte al momento de inicializar el
equipo en vez de incluir soporte en el programa de aplicacin. Particularmente, una
flash totalmente borrada ser reconocida como una particin o unidad lgica (un
LX) correctamente formateada.
Para partir un LX (incluso uno que ya haya sido partido), emplearemos la funcin
fs_setup(). Esta funcin es algo as como una guillotina, que divide el LX
especificado en dos partes. La primera parte conserva el nmero de identificacin,
mientras que la segunda, obviamente, obtiene una identificacin nueva, la cual es el
valor devuelto por la funcin. Es decir, si llamamos a fs_setup() con
fs_get_flash_lx() como parmetro, partiremos la flash secundaria (destinada en su
1

Para los ms jvenes, CP/M (Control Program for Microprocessors) es un sistema operativo basado
en 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 para
portarlo al hardware de su desarrollo.

195

File Systems
totalidad para el FS2) en dos, obteniendo el ID de la segunda porcin. Si luego
volvemos a llamar a esta funcin con el ID obtenido, lograremos partir la segunda
porcin en dos partes, logrando efectivamente tres particiones; procedimiento que
puede ser repetido hasta el hartazgo o hasta que se agote el espacio de memoria,
segn qu ocurra primero. Como este tema es un poco ms complicado, lo
analizaremos con ms detalle ms adelante.
Para poder formatear un LX, deberemos, luego de inicializar el soporte para el filesystem (cosa que veremos en el prrafo siguiente), llamar a la funcin
correspondiente:
fs_init(0,0);
// inicializa estructuras de FS2
lx_format(fs_get_flash_lx(), 0);
// formatea "unidad"

Como puede observarse, el primer parmetro es el identificador de LX, es decir, la


particin o "unidad lgica"; en este caso el resultado de la funcin que devuelve el
LX de la flash destinada al FS2. El segundo parmetro da el grado de optimizacin
sugerido para minimizar el "desgaste" de la flash (wear level).

Utilizacin 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 funcin fs_init(). La misma
posee dos parmetros, que debern pasarse como NULLs, debido a que los mismos
solamente existen por compatibilidad con versiones anteriores. Esta funcin
determina de cuantos LX dispone y realiza un chequeo de consistencia de las
particiones, por lo que suele demorar un tiempo. Esto no debera ser crtico ya que
slo debe llamarse al momento de inicializar el sistema.
A los fines de una utilizacin 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 en
Unix o el directorio raz en MS-DOS o CP/M. Dentro de este contexto, las
operaciones de archivos se realizan de forma similar a como se opera normalmente
en C con archivos: especificando un file handle a las diversas llamadas a funcin
para abrir, buscar posicin, leer, escribir, y cerrar el archivo. Las funciones retornan
los valores usuales, que permiten detectar errores, fin de archivo, etc.
File file;

Las diferencias principales son debidas al contexto en el cual trabajamos, los


nombres de archivo son en realidad nmeros de 1 a 255, y la forma de abrir un
archivo para escritura es ms rudimentaria, debiendo crearse si no existe y abrirse
slo si ya existe:
if(fopen_wr(&file,filenumber) && fcreate(&file,filenumber))
// hubo un error
else

196

FS2
// 'filenumber' fue abierto o creado para escritura

Una adicin interesante es una funcin 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);
fseek(&file, offset, SEEK_CUR);
fseek(&file, -offset, SEEK_END);

// 'offset' bytes desde inicio


// 'offset' bytes de posicin actual
// '-offset' bytes de fin de archivo

Para saber en qu posicin estamos:


ftell(&file);

Otra adicin interesante es la posibilidad de borrar una cantidad de datos del


principio de un archivo, de modo de mantener, por ejemplo, un log de una longitud
fija, escribiendo al final y borrando al principio (datos ms viejos):
fshift(&file, bytes, NULL);
fshift(&file, bytes, buffer);

// borra 'bytes' bytes a la cabeza


// copia los datos borrados en buffer

Para leer informacin de un archivo as abierto, utilizamos la funcin de lectura. No


es 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);

Modificacin del BIOS


Si tenemos un mdulo con dos chips de memoria flash, el BIOS reserva el segundo
chip para alojar el Flash File System, por lo que no es necesario realizar
modificacin alguna. Un ejemplo de mdulo con dos chips de flash es el RCM2100.
Si por el contrario, el mdulo tiene una sola flash, y necesitamos utilizar el FS2,
muy probablemente tengamos que modificar el BIOS, reservando un espacio en flash
197

File Systems
para alojar el File System. La documentacin necesaria est en las mismas
bibliotecas de funciones, en la ayuda on-line, y en el libro introductorio de este
mismo autor. No obstante, haremos aqu un pequeo comentario a modo de ejemplo:
como el RCM2200 tiene un solo chip de flash, es necesario dejar reservado un
espacio en ese nico chip de flash para que Dynamic C no lo use para cdigo y
pueda 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 lnea de acuerdo al tamao deseado, dejando en lo


posible un poco de ms. El valor debe ser, adems, mltiplo 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 lnea a modificar est a continuacin 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 lnea de acuerdo al tamao 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 para
logging, la misma corresponde a fecha y hora correctas si alguna vez fue seteado el
RTC y no se removi la alimentacin de back-up.
En lo posible, trataremos de imprimir los cdigos de error retornados. El significado
de los mismos puede obtenerse en ERRNO.LIB, ubicada en el directorio
LIB\FILESYSTEM de la instalacin de Dynamic C.
Los ejemplos debern correrse de modo tal que las flash mapeen normalmente, por
lo general esto corresponde a la opcin de "correr programa en flash".

198

FS2

Creacin del FS2 en un mdulo con dos chips de flash


Utilizaremos un RCM2100 por el simple hecho de que tenemos uno disponible, el
lector puede utilizar el que ms le agrade, siempre y cuando ste tenga dos chips de
flash.
#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_flash_lx(), 0))==0)
// formatea
puts("OK");
else printf("No puedo formatear: %d\n",rc);
}
else printf("No puedo inicializar: %d\n",rc);
}

Creacin del FS2 en un mdulo con un chip de flash


Primero deberemos modificar el BIOS con la siguiente lnea:
#define XMEM_RESERVE_SIZE 0x9000L

De esta forma reservamos 32KB en la flash para el FS2. Luego s podemos correr el
siguiente programa en cualquier mdulo 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);
}

Creacin del FS2 en la flash principal, en un mdulo con dos chips


de flash
Primero deberemos modificar el BIOS con la siguiente lnea:
#define XMEM_RESERVE_SIZE 0x9000L

199

File Systems

De esta forma reservamos 32KB en la flash principal para el FS2. Luego s podemos
correr el siguiente programa en cualquier mdulo 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);
}

Ntese que disponemos de dos unidades: este espacio reservado de la flash principal,
y la flash secundaria. El ejemplo formatea slo la primera, si se desea adems
formatear la flash secundaria, debe aadirse la llamada vista en un ejemplo anterior:
if((rc=lx_format(fs_get_other_lx(), 0))==0)
puts("OK espacio reservado");
else printf("No puedo formatear: %d\n",rc);

// formatea FS2 en
// flash de programa

if((rc=lx_format(fs_get_flash_lx(), 0))==0)
puts("OK flash secundaria");
else printf("No puedo formatear: %d\n",rc);

// formatea FS2 en
// flash secundaria

Creacin del FS2 en RAM


Primero deberemos modificar el BIOS con la siguiente lnea:
#define FS2_RAM_RESERVE

De esta forma reservamos 32KB en la RAM para el FS2. Luego s podemos correr el
siguiente programa en cualquier mdulo:
#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

FS2
}

El espacio as asignado se presenta como una unidad que puede coexistir con
cualesquiera otras definidas; es decir, tendremos uno, dos, o tres soportes fsicos,
segn hallamos definido (adems de la RAM), la flash secundaria (si la hubiere) y/o
el espacio reservado en flash de programa.

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 continuacin actualizamos cada vez que ocurre
un evento, simbolizado en este caso por la presin sobre el switch S2 del mdulo. La
orden para cerrar el archivo la damos presionando S3. Por comodidad, utilizamos la
flash secundaria de un mdulo RCM2100, pero el programa debera correr de igual
modo 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 condicin puede
detectarse comparando el valor devuelto por fwrite() con la cantidad de datos que se
intentaba escribir.
A los fines prcticos, el log tendr la siguiente estructura:
LEN: 1 byte, longitud del texto a continuacin
TXT: LEN bytes
TIMESTAMP: 4 bytes (long)
#class auto
#use fs2.lib
File file;
const char S2[]="presin 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 string
len=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++);
while(!BitRdPortI(PBDR,2));
for(rc=0;rc<30000;rc++);

// anti-rebote
// espera libere S2
// anti-rebote

201

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 y
hora a un formato humanamente entendible, utilizamos las funciones asociadas al
RTC.
#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 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);
}

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 nmero mximo de registros, y despus comienza a
borrar los iniciales, manteniendo siempre los ms recientes. Para esto, usamos la
202

FS2
funcin que permite borrar datos al principio del archivo. De esta forma, el log
avanza estirndose al final del archivo y acortndose luego al principio, manteniendo
siempre la misma longitud, sin necesidad de manejar un buffer circular.
El programa va insertando registros en el archivo, antes de insertar un nuevo
registro verifica la longitud del archivo, que es funcin de la cantidad de registros; si
excede un lmite prefijado, comienza a borrar del principio del archivo una cantidad
de datos equivalente a un registro, de modo de mantener una longitud de archivo
mxima constante al ingresar el nuevo registro.
A los fines prcticos, 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
// tamao del texto en un registro del log (longitud fija)
#define LOGSZ 13
// cantidad mxima 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);
// tamao utilizado
if(len>=MAXLEN) // si ya estoy en el mximo, 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++);
while(!BitRdPortI(PBDR,2));
for(rc=0;rc<20000;rc++);
}
}
fclose(&file);

// anti-rebote
// espera libere S2
// anti-rebote

// cierra el archivo

}
}
else printf("No puedo inicializar: %d\n",rc);
}

203

File Systems

De una forma similar al ejemplo anterior, podemos leer el log, verificando el


funcionamiento de nuestro programa de escritura:
#class auto
#use fs2.lib
// tamao 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);
}

Afinacin (tuning) y uso avanzado del FS2


Tamao de particin y de sectores
Como seguramente todos sabemos, la escritura en un file-system se hace en forma
de sectores, llevando estos sectores diversos nombres segn el file system de que se
trate, y pudiendo o no coincidir en su tamao con el sector del soporte fsico en que
se aloja, si lo hubiere. En este caso, el FS2 funciona sobre flash o RAM, y no tiene
necesariamente que existir un tamao de sector fsico (si bien la flash puede tenerlo,
la RAM no lo tiene). Existe no obstante un tamao de sector lgico, y dado que
como recordramos, los archivos se graban en sectores, el grado de aprovechamiento
que se hace del soporte fsico es fuertemente dependiente de la relacin entre el
tamao de los archivos y el tamao de los sectores.
204

FS2
Si los archivos son pequeos y los sectores son grandes, existe mucho desperdicio,
pues la mayora de los sectores estn sin utilizar. As, diez archivos de 10 bytes
ocupan diez sectores, o el equivalente a 5120 bytes si el tamao de sector es de 512
bytes. De igual modo, si los archivos son grandes y los sectores son pequeos, se
desperdicia espacio en todo lo que es metadatos, es decir, tabla de asignacin (donde
se halla el mapa que dice qu sectores ocupa cada archivo) o su equivalente
(punteros en una lista encadenada, como el legendario Disciple DOS2) y dems cosas
que incluyen los file-systems ms sofisticados.
El conflicto se reduce entonces a encontrar la definicin de grande y pequeo, de
modo tal de definir un tamao de sector que sea ptimo para el tamao de archivos a
utilizar. En la mayora de los casos, lo mejor es dejar los parmetros por defecto,
particularmente si la flash es sector-oriented y ya tiene un tamao de sector fsico
que limita el mnimo posible para el tamao de sector lgico.
Cuando no es posible ponerse de acuerdo con el tamao de sector lgico a utilizar,
puede recurrirse a una decisin de convivencia, realizando particiones y asignando
diferentes tamaos de sector para cada particin: particiones grandes con sectores
grandes para archivos grandes, y particiones chicas con sectores chicos para archivos
chicos.
El espacio destinado a cada particin, as como el tamao de sector lgico, se
especifican como parmetros de la funcin fs_setup(); cada particin puede tener su
propio tamao de sector lgico. El ejemplo siguiente muestra como es posible crear
dos particiones, con las caractersticas descriptas en el prrafo 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 fsico


(fs_get_flash_lx(), fs_get_other_lx(), o fs_get_ram_lx()), o una particin que a
su vez se va a partir
ext2 es el LX resultante de la nueva particin creada
LS1 es el logaritmo en base 2 del tamao de sector lgico del LX base, por ejemplo
9 para 512 bytes
LX2F es la fraccin de espacio asignada para el nuevo LX, representada dentro de
un entero, de modo que el valor 65535 representa la totalidad de la misma
LS2 es el logaritmo en base 2 del tamao de sector lgico del nuevo LX
FS_PARTITION_FRACTION es el comando pasado a fs_setup().
Como habrn observado, esta funcin 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 algn


modo emparentadas con Sir Clive Sinclair, es decir, ZX-Spectrum y sus derivados. Disciple fue un
sistema de discos sumamente rpido y simple, desarrollado para estas (en su poca) poderosas
computadoras personales.

205

File Systems
La llamada a fs_setup() debe hacerse antes de llamar a fs_init() , y por supuesto, es
necesario formatear ambos LX luego de la particin.

Acceso a diferentes particiones


Si disponemos de varias particiones o LX, para poder cambiar de LX, simplemente
debemos llamar a la funcin que elige sobre cul LX trabajamos:
fs_set_lx(lxmeta, lxdata);

La razn de la existencia de dos parmetros, es una curiosidad de FS2, que permite


alojar la informacin 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 informacin en otro, en lenguaje
MS-DOS. Dado que en la implementacin de FS2 cada archivo requiere de un sector
para metadatos del mismo, en una aplicacin con muchos archivos relativamente
grandes, que aprovecharan mejor los sectores grandes, la informacin de metadatos
(metadata) resulta desaprovechando valiosos sectores. Manteniendo dos LX, uno con
sectores grandes y otro con sectores chicos, puede aprovecharse mejor el espacio
utilizando el LX con sectores chicos para metadata y el otro para la informacin.
En una aplicacin "tradicional", metadata e informacin 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 en
RAM, existe una serie de macros que definen valores mximos, entre otras cosas,
para la cantidad total de archivos y la cantidad de particiones. De ser necesario, el
usuario deber modificar los valores para adecuarlos a sus necesidades. Por ejemplo,
la cantidad mxima de particiones es seteada al momento de compilar, acorde a la
cantidad de soportes fsicos posibles, por lo que deber modificarse si alguno de
ellos tiene particiones.
#define FS_MAX_LX
#define FS_MAX_FILES

4
12

// Mximo nmero de particiones


// Mximo nmero 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 aplicacin es la implementacin de file systems en serial flash y NAND
flash.

206

FAT
La implementacin de FAT hecha por Rabbit no slo incluye el MBR (Master Boot
Record), sino que necesita de su presencia para su correcto funcionamiento. Existe
soporte para FAT12 y FAT16, permitiendo diversos tipos de particiones ocultas. No
obstante, hasta el momento no existe soporte para FAT32 ni nombres largos, el
formato interno es solamente LBA, y el tamao de sector es fijo en 512 bytes. Cada
dispositivo o soporte fsico puede tener un mximo de cuatro particiones (no existe
soporte de particiones extendidas o unidades lgicas dentro de una particin), y es
posible tener hasta cuatro dispositivos como mximo por cada driver. Al presente
existen drivers para serial flash, que soporta un solo dispositivo, y NAND flash, que
soporta dos dispositivos. El esquema es mayormente abierto, y es posible soportar
otro tipo de dispositivos en los cuales utilizar FAT, simplemente escribiendo el
driver correspondiente, que se encarga de transformar la especificacin de LBA al
formato requerido por el dispositivo en s, y dems tareas de bajo nivel.
Una caracterstica interesante, es que las rutinas inicialmente son no-bloqueantes,
es decir, retornan inmediatamente. El usuario debe llamarlas reiteradamente hasta
que retornen con un cdigo de error diferente a "estoy ocupada". Esto permite la
realizacin de otro tipo de tareas, e incluso operaciones de lectura y escritura
"simultnea". No obstante, para los alcances de este texto, optaremos por definir una
macro que automticamente las vuelve bloqueantes, volviendo luego de terminar su
tarea, como estamos acostumbrados a trabajar tradicionalmente en otros entornos.
#define FAT_BLOCK
#use "fat.lib"

Acceso a diferentes dispositivos y particiones


FAT es mucho ms flexible que FS2, no es necesaria una eleccin de antemano ni
modificacin del BIOS; la eleccin del dispositivo se realiza mediante la seleccin
de su direccin dentro de una tabla de dispositivos.
Dado que cada soporte fsico tiene su MBR, un MBR puede definir cuatro
particiones, y la implementacin soporta hasta cuatro dispositivos por driver, la tabla
tiene espacio para diecisis entradas por cada driver inicializado. La llamada a
funcin que inicializa las estructuras para FAT, automticamente inicializa tambin
los drivers correspondientes a los dispositivos presentes de acuerdo al modelo de
mdulo, y monta las particiones presentes en cada dispositivo en su correspondiente
ubicacin. Por lo tanto, referirse a una determinada particin en un dispositivo
especfico se reduce a elegir correctamente el ndice dentro de la tabla:
fat_part_mounted[index].

Por lo general, los mdulos tienen solamente una memoria NAND-flash, serialflash, o un zcalo para tarjetas; ya sea NAND-flash removibles de tipo xD o SD, por
lo que el ndice ser de 0 a 3. En mdulos como el RCM3365, que dispone adems

207

File Systems
de la flash soldada de un zcalo para tarjetas de tipo xD, nos referiremos a las
particiones en esta tarjeta usando ndices de 4 a 7:
fat_part_mounted[0]

Se refiere a la primera particin del primer


dispositivo, usualmente serial o NAND flash en
el mdulo, o la tarjeta si no hay de aqullas.

fat_part_mounted[4]

Se refiere a la primera particin del segundo


dispositivo, usualmente tarjeta xD (RCM3360A)

Si el dispositivo existe y fue montada la particin, en esa posicin del array


tendremos un ndice a los parmetros de sta; caso contrario, habr un NULL. De
todos modos, existen macros que simplifican la operacin, y son definidas al
momento de compilar por las mismas bibliotecas de funciones. Las mismas son
inicializadas al detectar el mdulo, al valor correspondiente. Por ejemplo:
en un RCM3365:
#define FAT_XD_DEVICE
#define FAT_XD_PART

FDDF_MOUNT_DEV_1
fat_part_mounted[4]

en un RCM4300:
#define FAT_SD_DEVICE
#define FAT_SD_PART

FDDF_MOUNT_DEV_0
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 raz comn como en Unix. Al menos
no desde el punto de vista de las funciones de file system, veremos ms adelante en
el captulo sobre networking que se provee una especie de file system a la Unix
desde el punto de vista del servidor FTP o HTTP.

Particin y formateo
Como recordramos al estudiar FS2, estamos hablando de un microprocesador, y
por lo general estamos desarrollando un sistema dedicado, por lo que la particin y
el formateo no son eventos que se realizan tal vez ms de una vez. Posiblemente sea
preferible cargar algn programa aparte al momento de inicializar el equipo en
produccin en vez de incluir soporte en el programa de aplicacin. Particularmente,
la funcin que inicializa el soporte FAT permite automticamente formatear una
particin. El formateo automtico 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 dispositivo
como una sola particin.
/* Monta la primera particin del primer dispositivo, formatea si no
encuentra un file-system vlido all */
fat_AutoMount(FDDF_COND_PART_FORMAT | FDDF_MOUNT_DEV_0 |
FDDF_MOUNT_PART_0);
/* Formatea el primer dispositivo como una gran particin, lo monta */
fat_AutoMount(FDDF_UNCOND_PART_FORMAT | FDDF_MOUNT_DEV_0 |

208

FAT
FDDF_MOUNT_PART_0);

Para otras opciones, se recomienda estudiar la documentacin 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 para
cambiar de directorio. Los archivos se direccionan desde el directorio raz con su
path correspondiente relativo al mismo, sin posibilidad de especificar el dispositivo
dentro del path. La especificacin de dispositivo se hace s dentro de la llamada a
funcin, especificando el ndice dentro de la tabla de particiones montadas, como
viramos en prrafos 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 y
emular el concepto de "directorio corriente", almacenando el path en un string y
aadindolo como prefijo antes de cada operacin de acceso a archivos, como puede
observarse en una de las samples provistas: FAT_shell.c, que emula las operaciones
elementales de un shell similar a sh en Unix:
#define MAX_DEVICES
2
char 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,...);

// ... = dems parmetros

Debido a que es muchsimo ms simple, para un sistema dedicado, dedicarse a


hacer lo que tiene que hacer; es preferible mantener paths fijos, cargados en el
programa, y referirse siempre a los archivos por el path completo, an en medio de
una infinidad de directorios, y eso es lo que haremos en este texto. Al iniciar por
primera vez el sistema, al momento de dar formato al soporte fsico, es posible
correr FAT_shell.c y armar toda la estructura de directorios requerida para la
operacin, si la hubiere, y si fuera necesario.
Si la complejidad de la aplicacin requiere andar saltando alegremente por los
directorios, el lector interesado deber analizar detalladamente dicha sample, que
cubre en detalle el correspondiente tema, ya que no encontrar aqu un mayor grado
de profundidad que ste. Las funciones provistas permiten hasta incluso escribir un
DOS elemental, todo depende de la inquietud y necesidad del usuario.

Utilizacin 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 funcin fat_Automount().
La misma posee unos cuantos parmetros, como vislumbramos en el prrafo
209

File Systems
anterior; lo ms simple es inicializar con los parmetros por defecto, o definir qu
dispositivo queremos montar:
rc = fat_AutoMount(FDDF_USE_DEFAULT);

// parmetros por defecto

/* monta todos los dispositivos y todas sus particiones */


rc = fat_AutoMount(FDDF_MOUNT_DEV_ALL | FDDF_MOUNT_PART_ALL);
/* monta la segunda particin del segundo dispositivo */
rc = fat_AutoMount(FDDF_MOUNT_DEV_1 | FDDF_MOUNT_PART_1);
/* monta primera y segunda particin 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 parmetros por defecto se encuentran en el cdigo de la biblioteca de


funciones, y corresponden a montar todas las particiones de todos los dispositivos
reconocidos.
Las operaciones de archivos se realizan de forma similar a como se opera
normalmente en C con stos: especificando un file handle a las diversas llamadas a
funcin para abrir, buscar posicin, leer, escribir, y cerrar el archivo. Las funciones
retornan los valores usuales, que permiten detectar errores, fin de archivo, etc.
Recordemos que los archivos se direccionan desde el directorio raz con su path
correspondiente relativo al mismo. La especificacin de dispositivo se hace dentro
de la llamada a funcin, especificando el ndice dentro de la tabla de particiones
montadas, y no dentro del path. Una caracterstica interesante adicional, es que es
posible hacer una escritura en el soporte fsico desde memoria en xmem, segn qu
funcin se utiliza.
A continuacin analizaremos las funciones ms comunes:
FATfile file;
rc=fat_Open(particion,filename,FAT_FILE,FAT_CREATE,&file,NULL);
if(rc<0)
// hubo un error
else
// 'filename' fue abierto o creado para escritura y/o lectura

Para escribir en el archivo, disponemos de:


fat_Write(&file, buffer, longitud);
fat_xWrite(&file, xbuffer, longitud);

//
//
//
//

buffer=puntero al rea
de datos
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);
fat_Seek(&file, offset, SEEK_CUR);

210

// 'offset' bytes desde inicio


// 'offset' bytes de posicin actual

FAT
fat_Seek(&file, -offset, SEEK_END); // '-offset' bytes de fin de archivo

Para saber en qu posicin estamos:


fat_Tell(&file, &position);

// position = posicin actual

Otra adicin interesante es la posibilidad de borrar una cantidad de datos del final
de un archivo, es decir, truncarlo. Otra permite "partirlo" en dos archivos, en
determinado 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, crendose 'niuneim',
// que contiene el resto

Para leer informacin de un archivo, utilizamos la funcin 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 slo 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 al
inicializar el programa; caso contrario deberemos montar (mount) la particin
correspondiente antes de poder leer de la misma. Para ello, es necesario detectar su
presencia.
De igual modo, una vez realizada la actividad sobre el dispositivo, deberemos
desmontar (unmount) todas sus particiones antes de poder retirarlo. La funcin
fat_UnmountPartition() se encarga de desmontar una particin, cerrando
cualesquiera archivos estuvieren abiertos en la misma. Adems, es posible llamar a
otra funcin, que se encarga de desmontar todas las particiones de un dispositivo:
fat_UnmountDevice().
Existen adems 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 revisin 2.11 del mdulo FAT, es posible detectar la presencia de un
dispositivo removible basado en NAND-flash, mediante una llamada a
211

File Systems
nf_XD_Detect(). El parmetro pasado a esta funcin indica el tipo de anti-rebote a
emplear. Para el uso normal de FAT en modo bloqueante, le pasaremos el valor 1
como parmetro. El ejemplo siguiente muestra el concepto crudo de la operacin
sobre la tarjeta xD3:
while(nf_XD_Detect(1)<0);
rc = fat_AutoMount(FAT_XD_DEVICE | FDDF_MOUNT_PART_ALL );
// opera sobre la tarjeta
fat_UnmountDevice(FAT_XD_PART->dev);
// desmonta y close()

La operacin de hot-swap, se describe en una sample de un mdulo slo soportado


por DC9: Filesystem\FAT\FAT_hot_swap_3365_75.c

Tarjetas SD
A partir de la revisin 2.14 del mdulo FAT, estn soportadas las tarjetas SD y
compatibles. La presencia del dispositivo se detecta mediante una llamada a
sdspi_debounce(). El parmetro pasado a esta funcin es un puntero a una estructura
interna, que se inicializa al montarla por primera vez. Si la deteccin la realizamos
antes de intentar montar, deberemos realizar manualmente dicha inicializacin. El
ejemplo siguiente muestra un esquema de la operatoria para una nica tarjeta:
sdspi_initDevice(0,&SD_dev0);
// SD_dev0 = tabla interna
while(!sdspi_debounce(&SD[0]));
// SD[0] = tabla interna
rc = fat_AutoMount(FAT_SD_DEVICE | FDDF_MOUNT_PART_ALL );
// opera sobre la tarjeta
fat_UnmountDevice(FAT_SD_PART->dev);
// desmonta y close()

La operacin 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 para
logging, la misma corresponde a fecha y hora correctas si alguna vez fue seteado el
RTC y no se removi la alimentacin de back-up.
En lo posible, trataremos de imprimir los cdigos de error retornados. El significado
de los mismos puede obtenerse en ERRNO.LIB, ubicada en el directorio
LIB\FILESYSTEM de la instalacin de Dynamic C.
Por esas cosas de la causalidad, para compilar y ejecutar los ejemplos a
continuacin se deber tener instalado el mdulo FAT, el cual no se incluye con la
distribucin standard de Dynamic C sino que debe comprarse por separado.

Al momento de actualizar este texto, Rabbit aparentemente decidi no implementar el xD Picture


Standard para evitar encarecer los productos con las regalas requeridas. Por lo tanto, la informacin
escrita en una de estas tarjetas por la implementacin de FAT en xD de Dynamic C no es legible por
los drivers standard de un lector standard de PC, cmaras digitales o equivalente, y viceversa. Se
sugiere utilizar la tarjeta SD si se requiere intercambiar informacin con otros dispositivos.

212

FAT

Creacin de FAT
Si bien es preferible el empleo de FAT_shell.c , o incluso fmt_device.c, daremos
aqu un pequeo ejemplo de creacin de un FAT file-system.
Utilizaremos un RCM3720 por el simple hecho de que tenemos uno disponible, el
lector puede utilizar el que ms le agrade, siempre y cuando ste tenga un dispositivo
soportado por los drivers de FAT (serial-flash, NAND-flash). El ejemplo opera
sobre el primer dispositivo, inicializndolo como una gran particin de toda su
extensin.
#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 continuacin actualizamos cada vez que ocurre
un evento, simbolizado en este caso por la presin sobre el switch S2 del mdulo. La
orden para cerrar el archivo la damos presionando S1. El programa no verifica ni
detecta la existencia de espacio para escribir, de ser necesario, esta condicin puede
detectarse comparando el valor devuelto por fat_Write() con la cantidad de datos que
se intentaba escribir.
A los fines prcticos, el log tendr la siguiente estructura:
LEN: 1 byte, longitud del texto a continuacin
TXT: LEN bytes
TIMESTAMP: 4 bytes (long)
#class auto
#define FAT_BLOCK
#use fat.lib
FATfile file;
const char S2[]="presin de S2";
main()
{
int rc;
fat_part *particion;
unsigned char len;

213

File Systems
char buffer[256],*evento,filename[256];
BitWrPortI(PBDDR,&PBDDRShadow,0,7);
BitWrPortI(PFDDR,&PFDDRShadow,0,4);

// PB7 = entrada
// PF4 = entrada

strcpy(filename,"milog.log");
evento=S2;
rc = fat_AutoMount(FDDF_USE_DEFAULT);
particion=fat_part_mounted[0];

// inicializa estructuras de
// FAT y monta dispositivos
// Se refiere a la primera particin
// de serial/NAND flash en el mdulo

if (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 string
len=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));
// timestamp
for(rc=0;rc<20000;rc++);
// anti-rebote
while(!BitRdPortI(PBDR,7));
// espera libere S2
for(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 slo lectura, y desde el inicio procesamos cada uno de
los registros encontrados, mostrando el contenido en pantalla. A fin de traducir fecha
y hora a un formato humanamente entendible, utilizamos las funciones asociadas al
RTC.
#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

FAT
rc = fat_AutoMount(FDDF_USE_DEFAULT);
particion=fat_part_mounted[0];

// inicializa estructuras de
// FAT y monta dispositivos
// Se refiere a la primera particin
// de serial/NAND flash en el mdulo

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);
}

Configuracin 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 el
funcionamiento a la costumbre del programador. por lo general las modificaciones
implican marcar o no como comentarios a algunas macros en el cdigo de la
biblioteca 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 ms
de 2MB. Eliminando una de estas dos definiciones, es posible ahorrar espacio de
memoria:
#define FAT_FAT12
#define FAT_FAT16

// comment out to disable FAT12 support


// comment out to disable FAT16 support

Tambin es posible ahorrar an ms, particularmente cdigo, si no se incluye


soporte para escribir; aunque sus aplicaciones pueden ser ms que reducidas.
#define FAT_WRITEACCESS

// comment out to disable write operations

Las siguientes macros pueden definirse antes de incluir la biblioteca de funciones.


Como comentramos al comienzo, las funciones son esencialmente no-bloqueantes,
la definicin que elige la forma de funcionamiento "standard" es la siguiente:
#define FAT_BLOCK

// sets library to 'blocking' mode

215

File Systems
Finalmente, aqullos acostumbrados a la forma tradicional y ms comn de indicar
los paths, podemos utilizarla mediante esta definicin, en vez de la ms conocida,
pero utilizada por slo 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 de
funciones que se encarga del sistema de archivos, requiere que FAT funcione en esta
modalidad. Analizaremos esto en el captulo sobre networking con Rabbit.

216

Networking

Introduccin
Es prcticamente imposible intentar resumir todo el networking en un captulo de
un libro, ni siquiera slo TCP/IP. Por un lado, es tanto pero tanto el contenido que
siempre algo queda afuera. Por otro lado, este campo avanza tan rpidamente que lo
poco que uno aprendi en sus aos de networker poco a poco va quedando obsoleto,
o pasa a ser una curiosidad. No obstante, los conceptos fundamentales siempre
aplican, y se ha sido el espritu del captulo sobre networking en el libro
introductorio sobre Rabbit. Sin embargo, la gran cantidad de consultas por parte de
algunos de los usuarios y los consecuentes llantos y colapsos nerviosos originados en
los encargados de responderlas, me han hecho pensar que es necesario profundizar
un poco ms en el tema.
Si bien el usuario ocasional y el hobbyista pueden ser felices con un "anduvo, listo,
no toques ms", quien desarrolla sistemas dedicados con aplicaciones de networking
debe tener un conocimiento ms acabado de lo que est ocurriendo, al punto de
poder resolver problemas de networking sin recurrir a pitonisas ni gures. Todo es
simple 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 ms probable que pueda hacer que funcione y siga funcionando
despus, porque si toco todos los parmetros hoy hasta que "funciona, no toques
ms", es muy probable que maana vuelva a dejar de funcionar.
En el libro introductorio hemos intentado un acercamiento de abajo hacia arriba,
estudiando la constitucin de las redes y edificando conceptos, analizando los
pormenores de cada protocolo. En este captulo intento hacer un enfoque de arriba
hacia abajo, aprovechando el hecho de que algunos de los conceptos ya se conocen.
Para un correcto aprovechamiento del contenido de este captulo, es altamente
recomendable re-leer y/o tener a mano el captulo homnimo del libro introductorio.
Tal vez hasta el leerlo luego de este captulo sea ms provechoso.

Switches, bridges, hubs, routers


Hemos estudiado en el libro introductorio que la vinculacin de dispositivos a
nivel-21 se denomina switching o bridging (segn el caso), mientras que la
vinculacin 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

Networking

Hub
Si tenemos una simple red en nuestro laboratorio o equivalente, sin conectar a nada,
lo ms probable es que estemos usando un hub o un switch Ethernet. El hub realiza
la vinculacin a nivel-1 (fsico), es el que hace la traslacin entre los pares trenzados
y el concepto original de Ethernet con un gran cable coaxial. Los equipos conectados
a un hub funcionan esencialmente en half-duplex (hablan de a uno por vez), y
solamente hay una transmisin en la red a la vez. Cada equipo est conectado al hub,
pero la conexin total es virtualmente igual al concepto original de Ethernet, es
como si todos los equipos estuvieran conectados a un cable coaxial. Cada uno de los
dispositivos conectados al hub puede observar todo el trfico de la red en todo
momento, y es posible intercambiar dispositivos (permutar las puertas a las que estn
conectados) sin inconvenientes.
HUB

Switch
El switch realiza la vinculacin a nivel-2; pese a su nombre de switch, no es ms
que un bridge con muchas bocas (una por cada equipo), y la topologa de una red
con 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 asocindolas a cada boca, y al observar una trama Ethernet para un dispositivo
especfico, la repite solamente en la boca que corresponde, por lo que es posible que
en la red haya varias transmisiones al mismo tiempo, dado que se establecen
conexiones virtuales entre las correspondientes bocas, tal vez de ah venga la idea de
llamarlo switch. Por igual motivo, es posible una comunicacin full-duplex si tanto
el switch como el dispositivo conectado lo permiten; y por el mismo motivo anterior
no es posible intercambiar dispositivos (permutar las puertas a las que estn
conectados) sin causar inconvenientes. Una vez que el switch sabe que determinada
MAC est en una boca especfica, seguir mandando las tramas para esa MAC a esa
boca, y slo a sa. Para que esto deje de ser as, deben ocurrir una de dos cosas: que
expire el tiempo de permanencia en la tabla y el switch re-aprenda la asignacin, o
que el dispositivo transmita y el switch se d cuenta que esa MAC ahora est en otra
boca.
218

Switches, bridges, hubs, routers


Cada uno de los dispositivos conectados al switch puede observar slo el trfico de
la red destinado para l y el trfico multicast/broadcast. Para poder observar trfico
de y/o hacia otros dispositivos, deber ponerse un hub y conectar el monitor de
trfico y el dispositivo "bajo prueba" a la misma boca del switch, mediante el hub.
De esta forma se observa el trfico de y hacia ese dispositivo solamente, debiendo
realizarse una tarea similar para cada dispositivo que se requiera.
SWITCH

Routing, router
En ninguno de estos dos casos (switch y hub) nos es posible salir ms all de
nuestra red. Todos los dispositivos de una misma red que deban verse entre s
debern tener la misma direccin de red. En el caso de TCP/IP, debern tener la
misma direccin de red IP.
Cualquier intento de conexin con un dispositivo cuya direccin de red no sea la de
la red a la que se pertenece, necesita indefectiblemente de la intervencin de un
tercer dispositivo que tenga la inteligencia suficiente como para saber llegar a la otra
red. Ese dispositivo se denomina router, y no necesariamente debe tener una
existencia fsica separada del resto, como veremos ms adelante.
Un router mantiene comunicacin con otros routers y conoce la topologa lgica de
la red; es quien deriva los paquetes o datagramas basndose en la informacin de
red, es decir, las direcciones de nivel-3. De acuerdo a la magnitud de la red, existen
diversos algoritmos y protocolos por los cuales los routers aprenden como llegar a
las diferentes redes que forman la gran red que une a todos los routers que
intercambian esta informacin. El router decide por qu interfaz enviar la
informacin en base al contenido de su tabla de ruteo, la cual es un mapa de la
localizacin de las diversas redes y puede aprenderse mediante variados protocolos y
contener agregados manuales (rutas estticas). El agregado manual ms comn es la
"ruta por defecto" (default route), que significa "todo aquello a lo cual no s como
llegar, debe estar del lado de la ruta por defecto". El router al que se apunta mediante
una ruta por defecto, recibe el nombre de "router por defecto" (default gateway).

219

Networking
Hub/Switch

Router
Otras redes
z

ISP
El ejemplo ms claro y tpico de querer conectarse con dispositivos con una
direccin de red diferente a la nuestra es la conexin a Internet. El ente que nos
provee la conexin a Internet se denomina ISP (Internet Service Provider), y a
menos que realmente tengamos una red importante (en cuyo caso probablemente no
estaramos leyendo este captulo), nos proveer el acceso mediante una conexin
ADSL o similar, dial-up, o cable modem.

Dial-up
Una conexin dial-up es una conexin discada, el usuario llama por telfono al ISP
mediante un modem, el cual est conectado al port de comunicaciones de un
dispositivo que es capaz de dialogar con otro en el nodo del ISP, mediante el
protocolo PPP (Point-to-Point Protocol). Al inicio del dilogo, se recibe una
direccin y se establece una ruta por defecto que hace que nuestro dispositivo sepa
que todos los dems dispositivos del mundo estn ms all de esa conexin. Ese
dispositivo generalmente es el software de conexin a Internet de nuestra
computadora personal o equivalente.

xDSL (ADSL y equivalentes)


Una conexin ADSL es una conexin fsica permanente con el proveedor 2 de la
misma. Por sobre esta conexin fsica, existe una conexin virtual bastante compleja3
mediante la cual se establece un enlace con el ISP. Este enlace es invisible para
nosotros. La conexin con el ISP se realiza generalmente mediante un dispositivo
capaz de dialogar con otro en el nodo del ISP, empleando el protocolo PPPoE
(Point-to-Point Protocol over Ethernet). Al inicio del dilogo, se recibe una
direccin y se establece una ruta por defecto que hace que nuestro dispositivo sepa
que todos los dems dispositivos del mundo estn ms all de esa conexin. Ese
dispositivo generalmente es el software de conexin a Internet por banda ancha de
nuestra computadora personal o equivalente, y en algunos casos puede ser el mismo
2
3

cuando ste no la interrumpe...


por lo general es un circuito virtual en ATM, el puerto Ethernet del modem ADSL se ve conectado al
ISP

220

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 conexin mediante cable modem es por lo general ms parecido a una
conexin virtualmente directa a la Internet. El dispositivo conectado recibe una
direccin y se establece una ruta por defecto que hace que nuestro dispositivo sepa
que todos los dems dispositivos del mundo estn ms all de esa conexin. Ese
dispositivo generalmente es el stack TCP/IP de nuestra computadora personal o
equivalente.

IP Routing, DNS, direcciones


Segn vimos, la comunicacin entre los dispositivos de una red se hace a nivel-2,
mediante elementos que los vinculan como hubs o switches. Tambin vimos que
para poder comunicarnos con dispositivos de otras redes (por definicin, aqullos
cuya direccin de red es diferente de nuestra direccin de red), necesitamos un
router.
Como sabemos, la direccin de red es el AND lgico entre la direccin IP y la
mscara de red. Para determinar si una direccin es o no perteneciente a nuestra red,
hacemos el AND lgico entre la direccin que nos interesa y nuestra mscara de red,
y la comparamos con nuestra direccin de red:
mi direccin IP: 192.168.69.96
mi mscara: 255.255.255.0
mi direccin de red:
192&255.168&255.69&255.96&0 = 192.168.69.0
direccin IP con que me quiero comunicar: 1.2.3.4
algoritmo:
1&255.2&255.3&255.4&0 == 192.168.69.0 ?
SI -> misma red
NO -> otra red

Dado que la direccin de red del destino a alcanzar es diferente de nuestra


direccin de red, necesitamos de un router. La direccin IP la obtenemos mediante
un servidor DNS, al cual conocemos por su nmero, y al cual llegaremos tambin
mediante 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, aqullas reservadas
en las RFC para uso indiscriminado fuera de la Internet. Al conectarnos a Internet,
nuestro ISP nos proveer de una direccin IP pblica, para que podamos navegar y
hacer todas esas cosas que normalmente hacemos cuando estamos conectados a
221

Networking
Internet. Quien tiene la direccin IP pblica, es el dispositivo que hace de router,
cualquier otro dispositivo que quiera conectarse a la Internet, deber utilizar a ese
dispositivo (el conectado a la Internet) como router, lo cual se indicar eligindolo
como la ruta por defecto. Pero, si en nuestra red tenemos direcciones privadas, los
equipos de Internet no pueden saber como volver a las direcciones internas de
nuestra red, es necesario hacer algn tipo de traduccin para que desde la Internet
nos vean siempre como la direccin pblica que nos asigna el ISP. Este
procedimiento se llama de forma genrica NAT (Network Address Translation). El
dispositivo que hace la traduccin es el mismo que se conecta a la Internet, pues
debe ser el que tiene la direccin IP pblica, es decir, el router. Este dispositivo
tendr dos interfaces, una para conectarse a nuestra red privada interna, y otra para
conectarse a la Internet. Si este dispositivo oficiando de router es una computadora
con Linux, lo que necesitamos es habilitar IP forwarding para que acte como router
e IP Masquerading para que haga NAT; si es una "PC comn", necesitaremos algn
tipo de software para "compartir" la conexin a Internet. En ambos casos, pueden
existir aspectos legales, y deberemos consultar a nuestro ISP si esto est permitido
en nuestro contrato.

Analoga cotidiana
El proceso de resolucin y routing es sumamente simple, y aunque no lo parezca es
la misma operatoria que uno hace para llamar por telfono, slo que no se piensa en
eso.
Supongamos que yo quiero llamar a mi amigo que vive cerca de casa; simplemente
tomo el telfono y lo llamo. Esto es as porque yo s su nmero de telfono y s que
est en mi misma red, aunque nunca me puse a pensarlo.
Supongamos ahora que quiero llamar a Egberto Gismonti. Llamo a la embajada de
Brasil y le pido el nmero de telfono del seor Gismonti y llamo. Ese simple
proceso, implica que s lo que es un DNS y s routing.
S que para obtener el nmero de alguien tengo que usar un dispositivo auxiliar,
en el caso de la embajada: la gua telefnica; en el caso del Sr. Gismonti: la
embajada (cuyo nmero obtuve de la gua previamente). El procedimiento es
equivalente a la resolucin mediante DNS.
Para llamar a la embajada marco directamente el nmero, porque s que est en
mi misma red.
S que para llamar a alguien que no est en mi red tengo que usar un dispositivo
auxiliar (marcar el cdigo de acceso internacional sealiza al centro de
conmutacin que establece la conexin internacional y comunica el resto del
nmero); esto es routing
Seguramente el Sr. Gismonti tiene un estudio u oficina, donde hay alguien que
atiende la llamada y la deriva a su interno, porque el nmero de interno de su oficina
dentro de su estudio no se publica en la gua. En este caso, la operadora est
222

IP Routing, DNS, direcciones


haciendo NAT. Seguramente tambin, el Sr. Gismonti estar muy ocupado y la
operadora nos dir que no est; en este caso, ella est haciendo de firewall... y si nos
toma el mensaje para transmitrselo y luego nos retransmite lo que el Sr. Gismonti le
contest para nosotros, est haciendo de proxy...

DDNS
Un elemento de relativamente reciente aparicin, dada la proliferacin de
conexiones a la Internet, es el DNS dinmico (Dynamic DNS).
Como los ISP necesitan maximizar el uso de las direcciones pblicas que poseen, y
como generalmente cobran tarifas extraordinarias para aqullos que poseen
servidores, son generalmente reacios a asignar direcciones IP fijas. Si nuestra
direccin est constantemente cambiando, no podemos tener un registro en un DNS
tradicional. Para resolver esta situacin, y permitir que los hijos de vecina tengamos
acceso a una cyber-vida mejor, DDNS permite que podamos dialogar con el Domain
Name Server que se encarga de nuestro dominio y actualizar peridicamente las
direcciones IP de las mquinas que lo habitan. Esto se compone de dos partes
fundamentales:
D
La RFC-2136, que define el protocolo mediante el cual los DNS pueden
aceptar y emitir actualizaciones de sus registros.
D
Proveedores de dominios actualizables por el usuario, que proveen a ste de
una interfaz relativamente simple para realizar la actualizacin por s mismo
y en forma automtica

Proveedores
Entre los proveedores ms conocidos que proveen servicios gratuitos, sin desmedro
de otros, tenemos a ZoneEdit y DynDNS. Ambos utilizan HTTP como transporte
para recibir los updates. Todo lo que se necesita para interactuar con ellos es un
cliente HTTP (un navegador o un programa como wget). Se informa al cliente los
nombres de usuario y password, y se procede a realizar el pedido al proveedor, a los
siguientes 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

En el caso de ZoneEdit, la direccin es determinada por el proveedor al recibir el


pedido (de dnde viene). En cambio DynDNS es un poco ms flexible, la direccin
se indica en el pedido. En este caso, dado que si estamos dentro de una red privada
4

Se recomienda al lector verificar esta informacin con el proveedor antes de invertir en la misma

223

Networking
es probable que no conozcamos nuestra direccin pblica, existe un URL donde
podemos consultarla: http://checkip.dyndns.org

Firewall
Un firewall es un dispositivo destinado a aislar una red de las dems, bajo
circunstancias controladas. Como dijramos en el libro anterior, su nombre deriva
por analoga con los corta fuegos utilizados para controlar los incendios,
impidiendo que stos se propaguen. Debido a que el firewall se encarga de impedir
el paso de aquellos paquetes o datagramas que l y su configurador consideren
sospechosos o no provechosos para el uso del ancho de banda, en realidad, su
nombre debera haber sido "portero de discoteca", "guardia de seguridad de
supermercado", o equivalente.
Un firewall se pone para proteger las redes privadas y los servidores de los ataques
de esos simpticos personajes de las pelculas, que al no tener malvados aliengenas
que combatir, se dedican a introducir porqueras en las redes de los dems.
Por lo general, un firewall se configura para no permitir conexiones entrantes a la
red privada, y bloquear trfico considerado no interesante o posiblemente malicioso.
La definicin de una poltica de seguridad para un firewall es algo que corresponde
al personal de seguridad informtica de la empresa, y de no existir dicho personal es
recomendable 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 conexin.
impedir trfico implica bloquear todos los dems paquetes. Generalmente, el
firewall descarta cualquier paquete TCP si no vio un SYN anterior; el mandar
segmentos invlidos es un conocido ataque de DoS (Denial of Service: bloquear
la red del prjimo con paquetes sin informacin).
En UDP es algo ms complejo, dado que al no ser orientado a conexin, no hay una
mquina de estados de protocolo que se pueda seguir. Sin embargo, el firewall arma
internamente una, observando la condicin anterior. Por ejemplo, si no se permite
trfico UDP entrante, un datagrama UDP que ingresa slo podr hacerlo si se lo
considera respuesta a uno saliente, para lo cual, cada vez que sale un datagrama, se
abre una ventana de tiempo, y si vuelve uno con los mismos nmeros de puerto
(obviamente permutados fuente y destino y lo mismo con las direcciones IP...) se lo
deja pasar.
Si nuestra conexin es detrs de un firewall, deberemos consultar al administrador
para que nos informe qu tenemos autorizado y si puede cambiar algo de la
configuracin para permitir que nuestro dispositivo pueda conectarse a donde debe.
Para bajar y subir correo, debern existir permisos de conexin saliente y trfico en
los puertos 110 (POP3) y 25 (SMTP). Si tenemos algn otro nmero de puerto
privado, deberemos informarlo y solicitar se habilite la conexin.
224

IP Routing, DNS, direcciones


FTP es algo ms complicado debido a la existencia de conexiones en ambos
sentidos; por lo general, en los lugares donde existe un firewall, los clientes FTP
necesitan permiso saliente en el port 21 (conexin de control), y funcionarn en
modo pasivo5, lo cual implica que el servidor indicar a qu direccin y puerto
debern conectarse. Si el firewall tiene impedido el trfico saliente, puede que esto
no funcione, si el firewall no detecta que se trata de una conexin de FTP. La otra
posibilidad es permitir trfico entrante por puerto 20, pero muy poca gente permite
conexiones entrantes (y menos an en los puertos de FTP).
Si nuestro equipo es un servidor (un Rabbit con una pgina web es un servidor),
deberemos solicitar permiso de conexiones entrantes al puerto de la aplicacin
correspondiente, y el NAT que permita traducir de la direccin pblica en que "se
ve" nuestro servidor en Internet, a la direccin privada que tiene en la red local. Para
HTTP, ser el puerto 80. Para servidor FTP, tendremos conexiones entrantes al
puerto 21 y conexiones salientes desde el puerto 20, excepto que se permita
conexiones pasivas, lo cual significa que el servidor recibir conexiones en los
puertos que l defina; esto ltimo es sumamente complicado si el firewall no
entiende el dilogo FTP. Por lo general, se coloca al servidor FTP en un rea o
rango de direcciones donde no se protege, es decir, una DMZ, como veremos a
continuacin.
Algunos firewalls tienen lo que se denomina DMZ (DeMilitarized Zone: zona no
militarizada), que pese a su nombre es bastante simple. Se trata de una boca de red
en la que se conectan los equipos a los que el mundo exterior puede acceder, de esta
forma se evita que el trfico externo ande boyando por nuestra red interna, y si algn
hacker logra entrar a uno de estos equipos, an tiene otra barrera hacia la red interna,
donde est la informacin sensible (servidores de bases de datos, documentacin
servida internamente, etc.). Los firewalls que no tienen un puerto de red especial
para esto, permiten configurar accesos por direccin IP con una funcin equivalente.
En los lugares ms modestos, el firewall probablemente sea un servidor Linux o
equivalente, pero las reglas sern ms o menos las mismas. El tema es que el firewall
va a bloquear principalmente ICMP y todo lo que considere sospechoso, por lo que
no va a ser muy simple realizar una puesta en marcha si no tenemos la asistencia de
alguien que pueda ver el otro lado de la red a travs del firewall e informarnos si hay
trfico descartado o si estamos llegando al otro lado.

Proxy
Un proxy es algo as como un intermediario. El ms conocido es el proxy HTTP.
La configuracin tpica es permitir conexiones y trfico saliente/entrante solamente
al proxy, quien ser el encargado de ir a buscar todas las pginas que se le pidan, y
generalmente las almacenar en un buffer local, de modo de tenerlas listas cuando
otro usuario (o el mismo) las pida en el futuro. El funcionamiento interno es ms que
5

Para ms detalles, se recomienda la lectura de la seccin sobre FTP en el captulo de networking en


el libro introductorio

225

Networking
complejo; externamente slo requiere que el navegador se configure para utilizar el
proxy. Un navegador as configurado no requiere resolver nombres, debido a que
realiza la peticin de la pgina por su URL al proxy, por lo que no es necesario
acceso al DNS; en una red en la que los usuarios solamente navegan, es la conexin
ms segura. Solamente el proxy y los servidores de correo tienen permiso de
conexin saliente/entrante, y generalmente se los coloca en otra red; los usuarios
slo 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, debern
configurar stos para que (en lo posible) no utilicen el proxy para acceder a nuestro
equipo, particularmente si estn en la misma red privada. Si los navegadores utilizan
el proxy para acceder a nuestro equipo, es posible que el proxy cachee las pginas y
el usuario no se d cuenta de los cambios, por lo que se recomienda instruir a los
usuarios a refrescar manualmente las pginas cada vez que acceden a nuestro equipo.

Receta para agregar nuevos dispositivos


Asumimos que el nuevo dispositivo a agregar es algo que utilizamos para el
desarrollo de una aplicacin (un Rabbit, por ejemplo), y que deber tener una cierta
conexin con otros dispositivos dentro del local y fuera del mismo, esto ltimo a
travs de la Internet. Para otros casos, la operatoria es muy similar.

Red con administrador


Si estamos en una red que tiene un administrador, deberemos solicitarle
informacin sobre qu nos est permitido y qu no. De igual modo, deberemos
informarle nuestros requerimientos y solicitarle permiso para las conexiones que
debamos hacer. l nos indicar nuestra direccin IP o nos dar un rango de
direcciones que podemos utilizar y la mscara de red, nos informar la direccin del
DNS y del router por defecto (default router), o nos dir que todo eso se resuelve por
DHCP, que configuremos nuestro dispositivo para DHCP. En este ltimo caso, si
tenemos un servidor, dado que los dems dispositivos debern conectarse a l, el
servidor DHCP deber no es la opcin recomendable, a menos que est acompaado
por un DNS interno acoplado, es decir, pueda emitir actualizaciones y el DNS las
entienda, y/o siempre entregue la misma direccin IP. Algo que los dems
dispositivos necesitan para conectarse al nuestro es una direccin conocida a donde
hacerlo, sea sta nombre o IP. Afortunadamente, los servidores DHCP y DNS
actuales soportan esta interaccin, implementando la RFC-2136.

Red sin administrador


Si no hay un administrador de red, deberemos personificarlo. A menos que el
nuestro sea el nico dispositivo conectado a la red (con lo cual no es una red sino
una conexin a Internet...), es altamente recomendable poner un firewall. Si el
226

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 ms
alto que una "PC comn", y un firewall no sera necesario.
Nuestras direcciones de red sern de las reservadas para aplicaciones privadas, se
recomienda utilizar 192.168.x.0 como direccin de red (por ejemplo: 192.168.1.0),
los hosts tendrn el ltimo byte desde 1 hasta 254, y la mscara ser 255.255.255.0.
En todo caso, si existen otros dispositivos, puede obtenerse esta informacin
observando su configuracin de parmetros 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 ms simple y efectiva de firewall es un pequeo servidor Linux con el
firewall habilitado; amantes de otras soluciones preferirn otras cosas. En cualquier
caso, el firewall debera permitir conexiones salientes (confiamos en los usuarios
internos) e impedir conexiones entrantes, excepto a los servicios que tengamos en
nuestro dispositivo y/u otros servidores, si los hay.

Router + NAT
El router ser el encargado de conectarnos al mundo exterior, y deber realizar la
funcin de NAT para poder permitir que las respuestas a nuestras peticiones lleguen.
Caso contrario, los paquetes que salgan con direccin 192.168.algo.alguien con
destino a www.yahoo.com puede que lleguen, pero no las respuestas, porque ningn
router de la Internet sabe como llegar a una direccin privada que no le pertenece.
La forma ms simple y efectiva de router + NAT es un pequeo servidor Linux con
IP Masquerading habilitado, incluso puede ser el mismo que oficia de firewall;
amantes de otras soluciones preferirn otras cosas.
La interfaz a la red local ser un puerto Ethernet y tendr una direccin de nuestra
red. La interfaz a la red pblica (Internet) depende del tipo de conexin, segn vimos
en el apartado sobre el ISP. En todos los casos (dial-up, cable modem, ADSL y
equivalentes), el router obtendr su direccin IP pblica y parmetros de red del ISP,
esto incluye mscara, 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 ms comn es utilizar el DNS que nuestro ISP nos provee, pero si ste lo
informa 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

Networking
routers permiten funcionar como DNS esclavo, sirviendo a la red interna.
Configurar internamente en nuestros dispositivos la direccin del router como
DNS.

Dispositivos
Resuelto el tema de red, los dispositivos internos debern configurarse con una
direccin IP de nuestra red local, con la mscara correspondiente, su default gateway
ser el router + NAT que estemos utilizando, y su DNS ser el provisto por el ISP o
el que hayamos levantado local, si lo hemos hecho.
Dial-up

Internet

ISP

Router

Cable modem

Access server

xDSL

o equivalente

Modem
PSTN

Red

Lab

Modem

Modem telef.

Router

PPP

Firewall
IP

Red local

Red CATV +
+ ...

PSTN (cable) +
+ ATM + ...

Cable modem

Modem xDSL

IP

PPPoE

* El presente es un diagrama generico y conceptual


la implementacion de cada ISP puede ser diferente

Resolucin de problemas (troubleshooting)


Una vez conectados, deberemos verificar que tenemos conexin con quien
queremos conectarnos. El punto es que, sin poder ver lo que pasa en la red es muy
difcil tratar de determinar una causa. Un analizador de trfico ser nuestros ojos y
nos permitir ver qu es lo que est pasando en la red, y por qu nuestros mensajes
no llegan a destino.
Algunos dispositivos incluyen opciones de depuracin como por ejemplo tcpdump
en Linux y dems Unixes. Si bien es sumamente poderoso si se lo sabe usar, resulta
algo poco prctico para verdaderos problemas de red. Sin embargo, para la mayora
de los inconvenientes cotidianos, puede ser ms que suficiente. El listado siguiente
muestra el trfico recibido por el host Ellie en su interfaz eth0, que no proviene del
host 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.51
tcpdump: listening on eth0
13:58:51.219506 Ellie.34126 > ns1.arnet.com.ar.domain: 43124+ PTR?
250.255.255.239.in-addr.arpa. (46) (DF)

228

IP Routing, DNS, direcciones


13:58:51.252637 ns1.arnet.com.ar.domain > Ellie.34126:
(103) (DF)
13:58:51.253457 Ellie.34126 > ns1.arnet.com.ar.domain:
254.1.168.192.in-addr.arpa. (44) (DF)
13:58:51.266351 ns1.arnet.com.ar.domain > Ellie.34126:
(121) (DF)
13:58:51.267965 Ellie.34126 > ns1.arnet.com.ar.domain:
35.191.45.200.in-addr.arpa. (44) (DF)
13:58:51.281234 ns1.arnet.com.ar.domain > Ellie.34126:

43124 NXDomain 0/1/0


43125+ PTR?
43125 NXDomain 0/1/0
43126+ PTR?
43126 1/2/2 (147) (DF)

6 packets received by filter


0 packets dropped by kernel
[root@Ellie /root]#

Incluso Dynamic C incluye algo similar dentro de sus bibliotecas de funciones,


como veremos en el captulo sobre networking con Rabbit. Nos referimos a las
macros _VERBOSE, por ejemplo DCRTCP_VERBOSE.
Otra opcin interesante es proveerse de un analizador de trfico o analizador de
protocolos, como por ejemplo Ethereal o Wireshark, software que corre bajo Linux
(y otras opciones) y es totalmente gratuito. Este tipo de programas permite ver de
forma detallada y mucho ms amigable el trfico de la red, pudiendo detallarse en
todo momento el tipo de protocolo que se desea observar:

La operatoria genrica de resolucin de problemas en networking consiste en


observar el trfico en ambos sentidos y tratar de determinar, aplicando los conceptos
229

Networking
aprendidos, dnde est el origen del problema. Por lo general, lo primero que se
observar es que la respuesta no llega, o que la pregunta no sale. El paso siguiente es
buscar un nivel ms lejos: si la pregunta no sale, revisar programa/aplicacin y su
correspondiente configuracin en nuestro dispositivo. Si la respuesta no llega,
observar cuidadosamente si lo que sale es coherente, y luego verificar en el router si
el pedido realmente est saliendo al exterior y la respuesta entrando. Debe tenerse
siempre presente la topologa de la red; donde hay firewalls, es altamente probable
que sea ste quien est descartando la informacin.

Aplicaciones y particularidades
UDP broadcast
UDP es connection-less, no existe garanta de entrega de la informacin. Esta
carencia de conexin permite que se pueda enviar mensajes a todos los hosts de una
red, lo que se conoce como UDP broadcast, y tambin que se pueda recibir
mensajes de cualquier host, sin conocerlo previamente (lo cual se deduce del hecho
de que es posible mandar mensajes a cualquiera...)

Desconexiones y sockets abiertos


TCP
Como es bien conocido, TCP es un protocolo orientado a conexin, y en un stack
TCP/IP, un socket representa una conexin. En el socket, los valores guardados son
aqullos que lo identifican unvocamente: direccin IP fuente, port fuente, direccin
IP destino, port destino, y tipo de protocolo. Como es de imaginarse, si alguno de
estos parmetros cambia, el socket ya no es vlido. De igual modo, es de esperarse
que un equipo no reciba paquetes que no corresponden a su direccin IP, y un router
no es capaz de alterar la IP de destino, de igual modo que el cartero no tiene por qu
saber que Juan Prez se mud al 455 de la Avenida de las Acacias Verdes y ya no
atiende en Paraje Perdido 322.
Esto significa, que si por algn motivo mi direccin IP o la del otro extremo
cambian, la aplicacin deber recuperarse del inconveniente; por ejemplo cerrando
todas las conexiones abiertas y reinicindolas si es necesario.
"Por algn motivo" incluye PPP, DHCP, cambio manual de la direccin IP y
equivalentes imaginables.

UDP
Dado que en UDP no existe el concepto de conexin, no hay de qu preocuparse si
se interrumpe la conexin de red. No obstante, dado que en el socket, los valores

230

Aplicaciones y particularidades
guardados son aqullos que lo identifican unvocamente: direccin IP fuente, port
fuente, direccin IP destino, port destino, y tipo de protocolo, si alguno de estos
parmetros cambia, el socket ya no es vlido. Esto significa, que si por algn motivo
mi direccin IP o la del otro extremo cambian, debern modificarse los sockets
correspondientes a comunicaciones UDP en curso, que fueren orientadas a algn
host 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 que
cambie 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 conexin, todo lo que se entrega
resulta 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 clculo de ancho de
banda ocupado se hace en funcin de la cantidad adicional de informacin enviada
en encabezados; cualquier otro tipo de anlisis deber hacerse a un nivel ms alto, es
decir, considerando al protocolo de orden superior del que la aplicacin se vale; o
ms bajo, por ejemplo IP TOS6 o QOS.

TCP
En el caso de TCP, se trata de un stream continuo. La entrega de la informacin en
la secuencia prevista est garantizada, pero el timing es sumamente complicado. En
primer lugar, al ser un protocolo del tipo de ventana deslizable (sliding-window), si
bien la ventana se acomoda a las condiciones del entorno, sta tiene un mximo, y
dicho mximo a veces puede no ser adecuado en enlaces de mucha demora y/o
mucha velocidad. Sin embargo, no es el tipo de problemas con el que nos
encontraremos en un sistema dedicado, y podemos dejar su anlisis a los expertos de
networking.
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 que
es TCP quien decide cunto ancho de banda usa, demorando la salida y/o el
reconocimiento de los datos de modo de aprovechar mejor el ancho de banda
disponible; acomodando el tamao de la ventana y adaptndose a los cambios. Es
decir, no siempre que le pedimos que transmita, los datos salen inmediatamente; y
desde luego, en el extremo remoto no necesariamente sern entregados en las
mismas fracciones en que fueron escritos.
El anlisis 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 desea
o prefiere. Si se respeta esto o no, es algo en lo que muchas redes no estn de acuerdo.

231

Networking
propsitos, lo que necesitamos saber es que se trata de un protocolo que se adapta, y
que su inteligencia propia puede interactuar con nuestra determinacin, por lo que si
realmente necesitamos tener control del tiempo, tal vez no sea la eleccin ms
apropiada; particularmente si no estamos en condiciones de entender lo que sucede
en el intercambio entre ambos TCPs.
A modo de introduccin y resumen, podemos hacer algunos comentarios sobre lo
que ms probablemente puede afectarnos en un sistema dedicado.
El algoritmo de Nagle opera sobre la transmisin, demorando el envo de nueva
informacin si ya existe informacin anterior que no ha sido confirmada. Es decir, si
mi aplicacin enva algunos bytes cada un tiempo, TCP enviar la primera vez, y
luego demorar los subsecuentes hasta tanto llegue el reconocimiento de lo primero,
momento en el cual enviar lo que guard hasta ese instante. Esto logra una
excelente economa, pero altera el tiempo de entrega. Si nuestra aplicacin necesita
mnima demora o poca variacin de sta, es conveniente no habilitarlo.
Los dems algoritmos se orientan mayormente al control de congestin,
retransmisiones, y manejo de la ventana deslizante; no es de esperarse que nos
afecten. Lo que debemos saber, es que la deteccin de una desconexin, es decir, de
la interrupcin no deseada de una conexin por un motivo ajeno a ambas partes, en
la que no se recibe una indicacin de desconexin; se detecta mediante timeouts.
Dichos timeouts dependen en cierto modo de estos algoritmos, y es de esperarse
tiempos largos, dado que el protocolo realmente intenta que la conexin se mantenga
activa. En algunos sistemas, es posible controlar algunos parmetros que nos
permitirn tener una respuesta ms rpida a este tipo de eventos.
Finalmente, TCP es un algoritmo adaptativo y converge rpidamente a los
parmetros ptimos de funcionamiento. La mayora de los sistemas dispone de algn
medio por el cual configurar valores iniciales de estos parmetros, de modo que es
posible ayudar a TCP a converger ms rpido en el entorno en particular que nos
interesa.

Wi-Fi
Por Wi-Fi se suele denominar a una serie de protocolos englobados en los
standards IEEE 802.11. Particularmente, los ms utilizados (al menos mientras estas
letras se secan en el papel) son 802.11b y 802.11g. Estos standards definen toda la
operatoria de RF y acceso al medio; 802.11b es el anterior, de velocidad ms baja, y
802.11g puede funcionar en un modo de compatibilidad 802.11b, permitiendo la
coexistencia en una red de dispositivos 802.11b (generalmente PDAs y dispositivos
simples) 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. Tambin pueden,
utilizando la modalidad infrastructure (infraestructura), comunicarse con un tercero,
que es quien administra y provee interconexin Wi-Fi y con el mundo cableado
232

Wi-Fi
Ethernet; sea sta por bridging o por routing. Se los conoce con el nombre genrico
de access points (puntos de acceso). En lo particular, los dispositivos que adems
implementan routing suelen autodenominarse wireless routers (routers
inalmbricos).
Un dispositivo conectado a un access point puede comunicarse con uno Ethernet o
con otro conectado al mismo access point, de forma indistinta. Tambin puede, por
extensin, comunicarse con otro dispositivo wi-fi conectado a otro access point en
cualquier parte del mundo que est vinculado por cable a una red, ambas ruteables
entre s. Desde la Ethernet ambos se ven como estaciones Ethernet detrs de un
bridge o router.

Identificacin
Una red 802.11 se da a conocer por un identificador de treinta y dos bytes
denominado SSID (Service Set IDentifier). Si bien puede incluir cualquier valor
binario, es comn utilizar caracteres ASCII para que los humanos podamos
reconocerlo.
Al momento de elegir una red, sea de forma manual o automtica, se debe
seleccionar un SSID.
Este identificador es normalmente difundido en forma peridica por los access
points de modo de permitir que la red sea identificada fcilmente. Tambin es
posible suprimir este broadcast peridico y mantener la red en el oscurantismo. Sin
embargo, en el momento en que un dispositivo se incorpora a la red, el SSID es
transmitido sin cifrar y puede ser visto por pacientes observadores a la pesca de
nuestro identificador de red.

Seguridad
Dado que los datos que transmitimos por Wi-Fi pueden ser vistos por cualquier
individuo inescrupuloso con una antena de suficiente ganancia y un dispositivo
compatible, existen algunas opciones de cifrado y autenticacin para intentar
garantizar que slo aqullos 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 es
posible extraer la clave monitoreando algunos millones de mensajes. Hasta se han
desarrollado algoritmos para poder estimarla con un 50% de probabilidad con
algunas decenas de miles de mensajes. Digamos pues, que si bien puede seguir
siendo til para mantener al hijo adolescente del vecino a raya, no debemos
transmitir por una red con WEP los datos de nuestra cuenta bancaria; no hace honor
a su nombre.
WEP puede ser utilizado para autenticar como para cifrar los datos, y se basa en
una clave compartida (shared key). Se recomienda no utilizarlo para autenticacin.
La introduccin de WEP corresponde al standard 802.11 original, actualmente
reemplazado por 802.11i.
233

Networking
Es comn encontrar WEP en PDAs y dispositivos que soportan slo 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 integridad
temporaria de claves). Esto fue introducido por la Wi-Fi Alliance antes de que se
terminara el standard 802.11i, que se ocupa justamente de este problema, y provee
un nivel de seguridad superior comparado con WEP sin modificar el hardware
existente, lo cual fue justamente su razn de existir. Sin embargo, como todo lo que
se hace a las apuradas, tiene algunos pequeos detalles que vienen de arrastre y han
sido puestos en evidencia por varios grupos de investigadores. De todos modos, el
tiempo y esfuerzo necesarios para romper la seguridad de WPA/TKIP son mayores
que el valor de lo que solemos transportar en una red domiciliaria y el costo de un
acceso a Internet; sin mencionar el hecho de que al momento slo permiten ingresar
algn que otro paquete, no permiten obtener la clave. Por lo tanto, podemos dormir
tranquilos si nuestro access point no tiene algo mejor.
Es comn 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, segn recomienda FIPS-197.
Esto corresponde a la implementacin completa de 802.11i.
Es comn encontrar WPA2/CCMP en sistemas ms recientes y poderosos, dada la
cantidad de recursos que requiere en comparacin con los anteriores. Tambin se la
suele llamar WPA2/AES.
Existen tambin algunos WPA/CCMP, que consisten en implementaciones sobre
WPA/TKIP del componente CCMP, que al ser desarrollado despus de WPA es
opcional para sta. Dichas implementaciones proveen una baja compatibilidad entre
dispositivos y no son muy recomendables.

Personal vs. Enterprise


Hemos visto los mtodos de cifrado, pero todos dependen de una clave. Dicha
clave puede ser pre-compartida, como en PSK (Pre-Shared Key), con lo cual toda la
seguridad depende de qu tan difcil de adivinar sea esta clave, como en cualquier
sistema de passwords. ste es el mtodo ms comn para aplicaciones personales,
residenciales, y de pequeas y medianas empresas.
En WPA y WPA2, la clave pre-compartida se utiliza para generar claves
transitorias, de modo de generar pequeos cambios de clave con una determinada
frecuencia para mantener mayor seguridad en la totalidad de la sesin.
Para aplicacin en ambientes empresariales y/o de alta seguridad, se utiliza EAP
(Extensible Authentication Protocol), un grupo de protocolos que implementan el
standard 802.1X y requieren de un servidor de autenticacin, generalmente

234

Wi-Fi
RADIUS. El ms conocido es EAP-TLS (Transport Layer Security), pero en 2010
ingresan a la coleccin muchas ms alternativas.

Wireless bridging
Para interconectar dos dispositivos wi-fi mediante dos access point diferentes, no
interconectados entre s por cable, se requiere de un tipo especial de access point
que soporte lo que se conoce como wireless bridging. Si bien esto puede ser
confuso, dado que un access point ya est haciendo bridging entre los dispositivos
inalmbricos y la red Ethernet, el concepto de wireless bridging al que nos referimos
es el bridging de dos redes Ethernet mediante una conexin inalmbrica entre los
access points:

Access Point
A

Ethernet
B
Access Point

E
C

Este tipo de conexin, permite que dispositivos sin capacidad wi-fi puedan ser
conectados de forma inalmbrica a una red cableada existente, y dialogar tanto con
dispositivos inalmbricos como con cualquier otro dispositivo de la red Ethernet. Por
ejemplo, con cualquier access point, A y B pueden dialogar con C; mientras que D
puede dialogar con E y cualquier otro host de la Ethernet y cualesquiera otros que
estn 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 a
travs de all.

GSM
GSM (Global System for Mobile communications) es el standard corriente en
materia de comunicaciones celulares. La existencia de algunos protocolos que
permiten una fcil comunicacin desde dispositivos inteligentes y la cobertura en
zonas donde se dificulta acceder con cable, lo hacen sumamente interesante para
235

Networking
desarrollos en diversas reas. Si bien existen otras alternativas como la red celular
TDMA, radioenlaces, satlites, etc; el costo y la versatilidad no acompaan a los dos
ltimos, y la complejidad de la interfaz y falta de modems e informacin oficial, ms
su prometida caducidad afectan al primero.
Los dispositivos que emplearemos son generalmente modems GSM, los cuales
manejarn la complejidad de la red GSM y sern controlados mediante una interfaz
relativamente simple, generalmente extensiones GSM standard y propietarias a los
comandos AT.
Este es un campo del networking que excede los alcances de intencin de este
captulo, pero dado que cada vez son ms los dispositivos que emplean una red GSM
para formar parte de una red TCP/IP, incluimos aqu una breve descripcin de las
formas ms prcticas y comunes de acceder actualmente a estos beneficios.

CSD
CSD (Circuit Switched Data) es la forma ms antigua de comunicacin de datos
dentro de GSM. Como se describe en el captulo sobre conectividad, en CSD se
establece una conexin a un determinado ancho de banda, y se reserva ese ancho de
banda por el tiempo de duracin de la misma, ms all de si hay trfico o no. Es
prcticamente equivalente a realizar una llamada telefnica con un modem. El costo
de la comunicacin es alto debido a que generalmente se tarifa por tiempo de
conexin, y las velocidades de comunicacin no suelen ser elevadas.
El modem GSM nos permite establecer una conexin al destino solicitado y cursar
datos por la misma, es til cuando queremos conectarnos con un dispositivo remoto
simple, no es necesario manejar un stack de protocolo dado que la conexin es punto
a punto una vez que el modem GSM atiende la llamada CSD.
Por lo general, el inicio de la conexin se realiza de forma similar a un modem
telefnico, ya sea originando una conexin saliente o atendiendo una conexin
entrante.

PPP sobre CSD


Si la empresa prestataria del servicio GSM lo permite, es posible conectarse a
cualquier ISP (o uno interno) mediante CSD, estableciendo luego una conexin PPP
como si se tratara de una llamada dial-up con un modem en una lnea telefnica
normal. El dispositivo conectado al modem es quien maneja el stack TCP/IP y el
protocolo PPP, el modem y la red GSM actan como si se tratara de la red
telefnica.
El establecimiento de la conexin es idntico al caso anterior, dado que la red GSM
solamente establece el vnculo como si fuera la PSTN. Lo nico que necesitamos
saber es el nmero asignado por el ISP para la conexin. Algunos prestatarios dan un
nmero abreviado, por ejemplo *638.

236

GSM

Mdulo GSM
Funcionalidad de
Access Server

Internet
red GSM

Serie Asinc

CSD
PPP
IP

Si se permite conexin con la PSTN, podemos utilizar cualquier ISP como si


estuviramos en la PSTN: simplemente marcando su nmero de abonado.

Internet

ISP
Mdulo GSM

Funcionalidad de
Modem

PSTN

red GSM

Serie Asinc

CSD

PCM

PPP
IP

GPRS
GPRS (General Packet Radio Service) es dentro de la arquitectura GSM el
transporte de datos que sucede a CSD, y permite transportar la informacin por los
canales no utilizados, sin reservar ancho de banda en la red. Las caractersticas
237

Networking
fundamentales desde nuestro punto de vista son su relativo bajo costo, debido a que
generalmente se tarifa por trfico, y la posibilidad de obtener altas velocidades de
transferencia al adjudicarse grandes anchos de banda por instantes pequeos, para la
transferencia de la informacin. GPRS originalmente se defini para transportar IP,
PPP, y X.25.
Una caracterstica 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/o
de difcil acceso, o incluso mviles, tal es el caso de los recientemente proliferados
sistemas de localizacin vehicular.

IP sobre GPRS
El dispositivo de la red GSM solicita la asignacin de una direccin IP, la cual
generalmente corresponde a una direccin de red privada, y a continuacin puede
iniciar conexiones TCP o mandar datagramas UDP a otros dispositivos. El gateway
del prestador que mantiene la conexin con la Internet realiza la traduccin (NAT) a
direcciones pblicas.
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 bajo
costo, pero limita las aplicaciones, ya que no es posible establecer una comunicacin
con un dispositivo remoto a menos que ste la inicie, dado que slo obtiene una
direccin IP por un tiempo limitado, y sta corresponde a un grupo de direcciones
privadas, vlidas slo dentro de la red GSM del prestador. Adems, dado que la
comunicacin se establece solicitndola mediante una serie de comandos AT al
modem GSM, todo el manejo de la misma debe hacerse mediante este sistema, lo
cual limita la cantidad de comunicaciones y/o servicios simultneos que quieran
darse.

Mdulo GSM

Gateway

red GSM

Serie Asinc

Internet

GPRS
IP

Hemos visto un ejemplo de aplicacin en el captulo sobre conectividad.

238

GSM

PPP sobre GPRS


Esta opcin es mucho ms interesante para micros con stack de protocolo propio,
como el Rabbit, dado que permite que el dispositivo conectado al modem GSM
obtenga una direccin IP via PPP, de forma similar a una conexin dial-up, pero
minimizando el costo, dado que no existe una conexin fija sino un transporte de los
datagramas IP sobre PPP sobre GPRS, es decir, slo se usa el ancho de banda
cuando existe trfico para cursar.
La comunicacin se establece solicitndola mediante una serie de comandos AT al
modem GSM, en el mismo procedimiento se indica que es una conexin PPP. La
direccin IP asignada generalmente corresponde a una direccin de red privada. El
gateway del prestador que mantiene la conexin con la Internet realiza la traduccin
(NAT) a direcciones pblicas. Tampoco es posible conectarse con un dispositivo
remoto de este tipo a menos que ste inicie la conexin, pero s es posible tener
muchas conexiones simultneas a diversos puntos, dado que el stack de protocolo
corre en el dispositivo externo a la red.

Mdulo GSM

Gateway

red GSM

Serie Asinc

Internet

GPRS
PPP
IP

Tendremos ejemplos de esto en el captulo sobre networking con Rabbit.

Tethering
Por este nombre se conoce al usar un dispositivo mvil como modem de otro para
acceder a la Internet. Dado que tether como verbo en ingls es la accin de atar a un
animal a algo fijo para limitar su radio de movimiento, tal vez esta terminologa
aluda al hecho de que la conexin queda limitada al radio de conexin o
comunicacin con el dispositivo mvil usado para oficiar de modem.

239

Networking
Un caso comn es el usar un telfono celular para conectar una PDA u otro
dispositivo con Bluetooth para ganar acceso a la Internet mediante por ejemplo
GSM. En este caso se trata de una conexin PPP sobre GPRS va Bluetooth, la cual
desarrollaremos en un ejemplo en el captulo sobre networking con Rabbit.

240

Networking con Rabbit

Introduccin
La implementacin de TCP/IP en Dynamic C es lo suficientemente verstil como
para permitir abrir sockets TCP y UDP, llamando a funciones como tcp_open() y
udp_open(). Tambin es posible crear servidores que esperen una conexin TCP en
determinado port llamando a tcp_listen(). Una vez establecido el socket, podemos
enviar y recibir informacin mediante funciones como sock_read() y sock_write()
para TCP, y udp_send() y udp_recv() para UDP. Hay muchas ms opciones, y el
entorno en general es similar al que encuentra alguien familiarizado en programar
aplicaciones de networking (Berkeley sockets).
Para que el stack TCP/IP se mantenga activo, deberemos llamar peridicamente al
handler que lo atiende: tcp_tick(). Generalmente esta funcin es llamada al inicio del
loop, dentro de un loop infinito que atiende todas las tareas; de esta forma, el
programa principal regula el momento y la periodicidad con que cede el procesador
al stack TCP/IP.

Direcciones IP
Los parmetros de red pasados a las funciones son valores en formato "de
mquina", es decir, para ser entendidos y manejados por el stack TCP/IP (una
palabra de 32 bits). La traduccin desde el formato humanamente manejable (cuatro
nmeros decimales separados por puntos) la realizamos mediante la funcin
inet_addr(). En el caso inverso, si queremos visualizar uno de los valores
configurados, emplearemos la funcin 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 contiene


el stack TCP/IP.

Resolucin por DNS


Cuando slo disponemos del nombre del host con el que nos queremos comunicar,
deberemos resolver este nombre a una direccin IP. El proceso de resolucin es una
241

Networking con Rabbit


bsqueda iterativa consultando a los servidores que atienden cada dominio
involucrado en el nombre; afortunadamente, el servidor DNS que tengamos asignado
har 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 la
respuesta pueden transcurrir algunos segundos.
La forma ms simple de resolver un nombre es utilizar la funcin resolve():
resolve("www.ldir.com.ar");

la misma retorna una direccin IP en el formato que puede ser utilizado por las
dems funciones del stack TCP/IP. Una aplicacin adicional es como sustituto de
inet_addr(), dado que la llamada a resolve() se deriva a inet_addr() si se observa que
lo especificado es una direccin numrica1.
En realidad resolve() es un loop que espera una respuesta, sea sta positiva o de
error (timeout):
if (isaddr(name)) {
// si es una direccin, para qu
return (aton(name));
// resolver ?
}
handle=resolve_name_start(name);
if(handle<0)
return(0L);
// si handle es <0, sale por error
while((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() y
resolve_name_check(), y escribirlo en forma de handler, utilizar costates, o armar
una pequea mquina de estados que nos permita atender otras tareas mientras
esperamos. Los fragmentos de cdigo de ejemplo a continuacin, asumen que
tcp_tick() se llama en el loop principal. El primero puede ser parte del costate que
atiende la tarea que necesita comunicarse con la direccin en cuestin. El segundo
bien puede ser un handler o el cdigo de un estado en la mquina de estados que
inicia la comunicacin:
handle=resolve_name_start(name); // si handle es <0, error
waitfor((retval=resolve_name_check(handle,&resolved_ip))!=RESOLVE_AGAIN)
if (retval == RESOLVE_SUCCESS)
// puedo usar resolved_ip
else
// hubo un error
handle=resolve_name_start(name); // si handle es <0, error
if((retval=resolve_name_check(handle,&resolved_ip))==RESOLVE_SUCCESS){
// puedo usar resolved_ip, avanzo en el flujo del programa
}

En realidad se deriva a aton(), que es una macro que internamente es reemplazada por inet_addr().

242

Direcciones IP
else if (retval != RESOLVE_AGAIN){
// manejo el error
}

Para definir quin es nuestro DNS, podemos utilizar la macro


#define MY_NAMESERVER

"200.49.156.3"

Tambin, podemos obtenerlo por DHCP, cargarlo en tcpconfig.lib, o ingresarlo


mediante una llamada a ifconfig(), como veremos ms adelante en el apartado sobre
direcciones IP.
Si pensamos tener varias tareas levantando conexiones a la vez, debemos tener en
cuenta que existe un mximo en el nmero de pedidos de resolucin concurrentes, el
cual est fijado por la macro DNS_MAX_RESOLVES, que por defecto es cuatro, la
cual deberemos modificar si necesitamos ms:
#define DNS_MAX_RESOLVES

UDP
Para trabajar con UDP, recordemos que se trata de un protocolo no orientado a
conexin, con lo cual, cuando tenemos algo que decir, simplemente lo decimos, lo
que se traduce en mandar la informacin cuando est disponible, sin preocuparse si
el otro est o no; de eso se encargar la aplicacin en particular, si lo requiere. En lo
que a UDP concierne, se abre el socket, y se escribe.
La cantidad de conexiones y el tamao de los buffers los limitamos operando sobre
algunas macros, previamente a la inclusin de DCRTCP. Es posible aumentar la
capacidad de conexin definiendo el nmero mximo de sockets (que por defecto es
cero) mediante una macro. El nmero de sockets depender de la cantidad de
comunicaciones salientes y/o entrantes simultneas que debamos manejar. Debe
tenerse presente que un mayor nmero de sockets implica una mayor ocupacin y
necesidad de memoria. Por ejemplo, para una simple comunicacin UDP:
#define MAX_UDP_SOCKET_BUFFERS 1
#use "dcrtcp.lib"

Para desarrollar nuestro programa con UDP, primero debemos definir la variable a
utilizar para identificar el socket:
udp_Socket s;

Luego, procedemos a abrir el socket. De esta forma, podemos indicar en qu puerto


vamos a transmitir y/o recibir. En el caso de recepcin, podemos indicar si
escuchamos a cualquiera que nos hable en ese puerto, al primero que lo haga, o
solamente a una direccin en particular. En el caso de transmisin, podemos hablarle
a alguien en particular o mandar un broadcast dentro de nuestra subred, con lo cual
243

Networking con Rabbit


nos escucharn todas las mquinas que estn preparadas para hacerlo. Tambin es
posible trabajar sobre multicast, pero resulta algo ms 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; y


enviar 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 enve. A partir
de ese instante, podr mandarle datagramas al puerto del que l me contact. Esto es
as 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, saliendo
por 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, por
cualquier puerto. A partir de que ste me enve un datagrama, podr mandarle
datagramas al puerto del que l me contact. Esto es as debido a que el socket se
arma 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 funcin devuelve valores negativos en caso de error; particularmente, el valor


-2 indica que an no se ha obtenido la MAC de destino.
Para recibir un datagrama:
if((len=udp_recv(&s,buffer,maxlen))>=0) // maxlen= tamao 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". Para
ignorar el checksum, se debe utilizar la funcin que setea los parmetros del socket:
sock_mode(&s,UDP_MODE_NOCHK);

244

UDP

Ejemplos
El ejemplo que desarrollamos a continuacin enva 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
#define MY_NETMASK

"192.168.1.54"
"255.255.255.0"

#define MY_GATEWAY
#define MY_NAMESERVER

"192.168.1.1"
"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 tamao de los buffers los limitamos operando sobre
algunas macros, previamente a la inclusin de DCRTCP. Es posible aumentar la
capacidad de conexin definiendo el nmero mximo de sockets (que por defecto es
cuatro) mediante una macro. Deberemos realizar esta modificacin si necesitamos
ms sockets para las tareas que debamos realizar (conexiones salientes y/o entrantes
245

Networking con Rabbit


simultneas). Debe tenerse presente que un mayor nmero de sockets implica una
mayor ocupacin y necesidad de memoria. Por ejemplo, si queremos seis sockets:
#define MAX_TCP_SOCKET_BUFFERS 6
#use "dcrtcp.lib"

Existe adems una funcin que nos permite demorar el rechazo de una conexin
TCP a la espera de sockets libres, esta funcin es tcp_reserveport(), y se emplea para
evitar que los pedidos de conexin sean rechazados por ausencia de sockets libres en
el tiempo en que se cierra una conexin establecida; por ejemplo cuando un
navegador pide varias imgenes. Este es un tema interesante. La especificacin de
TCP sugiere un tiempo durante el cual, luego del pedido de cierre de una conexin,
debe mantenerse el socket abierto, de modo que ambos extremos estn seguros que
el otro accedi al pedido y toda la informacin fue transferida. Esto es debido a que
podra haber algunos datagramas IP pululando por ah sin haber llegado a destino.
En un sistema donde la memoria es ms abundante que la arena en el desierto, esto
no es problema, pero en un sistema dedicado, la porcin de memoria que ocupa un
socket es ms que preciada, razn por la cual sistemas como Rabbit limitan este
tiempo a un par de segundos, en vez de los varios minutos recomendados. Lo que
esto significa para nosotros, es que si no habilitamos tcp_reserveport(), cada vez que
se cierra un socket, no se lo podr volver a abrir por al menos dos segundos, es
decir, cualquier llamada para abrir una conexin saliente o pedido de conexin
entrante sern rechazados si los otros sockets estn ocupados, hasta tanto transcurra
dicho tiempo y se pueda volver a utilizar el socket liberado. Este tiempo se puede
controlar mediante la macro TCP_TWTIMEOUT.
Las funciones por lo general reciben como parmetro un puntero a un socket, el
cual se define en alguna parte del programa:
tcp_Socket socket;

Deteccin de interrupcin en la conexin


Hemos visto que la funcin tcp_tick() es la encargada de mantener activo el stack
TCP/IP, y hemos observado que generalmente la llamamos sin argumentos. Cuando
a la funcin 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, es
posible detectar interrupciones en la conexin:
if(tcp_tick(&socket)==0) {
// se interrumpi mi comunicacin
}

Como una conexin TCP tiene una serie de estados, esto nos sirve para resetear
nuestra mquina de estados y reiniciar la conexin o indicarlo, si es necesario.
Cuando alguno de los extremos cierra la conexin, el otro se da cuenta casi
inmediatamente. No obstante, la deteccin de una prdida de conexin por
246

TCP
interrupcin de la conectividad o salida de servicio del extremo remoto puede llevar
bastante tiempo, dado el esquema de retransmisiones y timeouts de TCP. Sin
embargo, aunque no es lo recomendable, en un caso extremo podemos alterar estos
parmetros modificando la biblioteca de funciones de TCP. Lo cierto es que, si no
sabemos donde buscar, lo mejor es que no lo modifiquemos...
De todos modos, dentro de nuestra aplicacin, siempre podemos manejar timers y
detectar si el otro extremo no responde a nuestros mensajes por un determinado
tiempo. Sin embargo, cuando no tenemos actividad en cuanto a transferencia de
informacin, y no podemos darnos el lujo de detectar que tenemos la conexin
interrumpida recin cuando enviamos algo urgente y no hay respuesta, podemos
emplear keepalives. Un keepalive es un paquete que se enva cuando no hay nada
que enviar, de modo de forzar trfico en la conexin y detectar si el otro extremo
"est vivo". Son una relacin de compromiso entre evitar desperdiciar ancho de
banda y tiempo, ambos expresables como dinero muchas veces. Los configuramos
una vez que nuestro socket est abierto, mediante la siguiente funcin:
tcp_keepalive(&socket, ktime).

Dicha funcin permite configurar el tiempo de inactividad a partir del cual se enva
un keepalive. Si pasado un cierto tiempo de espera (configurable mediante una
macro) no se recibe respuesta, el keepalive ser retransmitido una determinada
cantidad de veces (configurable mediante una macro), momento a partir del cual si
no se obtuvo respuesta se da por interrumpida la conexin. Los valores por defecto y
las correspondientes macros son:
#define KEEPALIVE_WAITTIME
#define KEEPALIVE_NUMRETRYS

60
4

Con estos valores por defecto, TCP se dar cuenta que se interrumpi la conexin
un mnimo de cuatro minutos despus de ocurrida. Si necesitamos algo ms rpido,
podemos definir nuestros valores antes de incluir dcrtcp.lib:
#define KEEPALIVE_WAITTIME
#define KEEPALIVE_NUMRETRYS

10
4

Con estos valores, se envan keepalives cada ktime segundos, si al cabo de 10


segundos no se recibi respuesta, se reintenta tres veces ms (cuatro en total), con lo
cual se da por cortada la conexin al cabo de 40 segundos de enviado el primer
keepalive. Si el tiempo entre stos (ktime) es muy largo, influir en el tiempo de
deteccin.

Cliente TCP
Para implementar un cliente TCP, deberemos tener presente que una comunicacin
TCP avanza a travs de una serie de estados. Los pasos fundamentales que
deberemos realizar son:
1. Inicio de la conexin
247

Networking con Rabbit


2.
3.
4.
5.

Espera de la aceptacin y establecimiento de la comunicacin


Transferencia de datos
Fin de la conexin
Espera para reiniciar la conexin (opcional)

Implementacin
Si bien disponemos de funciones que nos permiten inicializar el socket, leerlo, y
escribirlo, deberemos desarrollar cada uno de los pasos manualmente.

Inicio de la conexin
Iniciar una conexin es simplemente llamar a la funcin tcp_open(), pasndole
como parmetros los datos necesarios para armar el socket: direccin y port de
destino, y port de origen (0 para elegir uno libre). El ltimo parmetro indica la
direccin de un data handler para la transferencia de datos, pasndolo como NULL
utilizamos el que provee el sistema, sin inconvenientes.
if (tcp_open(&socket,src_port,dest_ip,dest_port,NULL) != 0)
//intento establecer la conexin
else
// no s como conectarme a ese destino

Espera de la aceptacin y establecimiento de la comunicacin


Luego de iniciar la conexin, deberemos esperar a ver si sta se establece, lo cual
se nos puede indicar por la funcin sock_established(), pero puede que el host
remoto est muy alegre y ansioso de vernos y nos responda al momento con datos,
cerrando inmediatamente la conexin, por lo que es conveniente emplear tambin
sock_bytesready(). Lo anterior podra ocurrir entre dos llamadas a tcp_tick() (segn
la carga del sistema), y nunca llegaramos a ver el socket activo, aunque tendramos
datos esperando.
Una vez establecida la comunicacin, podemos colocar el socket en el modo de
trabajo que vayamos a emplear. Lo ms comn es dejar el modo por defecto, que
corresponde a un stream de bytes independientes. En algunos casos, como servidores
HTTP, 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 comunicacin, el mismo puede configurarse mediante la siguiente
macro, en milisegundos:
248

TCP

#define TCP_OPENTIMEOUT

31000L

Existe adems otro tiempo de espera desde que se detecta la buena voluntad del
otro extremo hasta que se cierra el proceso de three-way hansdshake de TCP y se
establece la conexin, tambin 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 en
y del socket para enviar y recibir informacin, lo cual haremos mediante dos
funciones. 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 es
conveniente preguntar cunto de lo que se intent escribir fue realmente escrito, y
seguir desde all. Esto se realiza mediante una porcin de cdigo similar a sta:
if (bytes_written < 0)
// error, debo cerrar la conexin
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 conexin.

Fin de la conexin
Para terminar la conexin, simplemente lo indicamos llamando a la funcin
sock_close():
sock_close(&socket);

La confirmacin del cierre definitivo la podemos hacer de la misma forma que


detectamos una desconexin: observando el valor devuelto por tcp_tick().

Espera para reiniciar la conexin (opcional)


No siempre es necesario, todo depende de la actividad a realizar y nuestra
disponibilidad de sockets, pero por ejemplo si la conexin anterior envi datos por
un puerto serie, tal vez debamos esperar a que se vace el buffer, o si estamos
iniciando conexiones a un servidor, tal vez sea amable de nuestra parte esperar unos
249

Networking con Rabbit


segundos antes de reintentar. De todos modos, si lo ltimo que hicimos antes de
cerrar el socket fue enviar datos, deberemos seguir llamando a tcp_tick() hasta que
los datos terminen de ser enviados.

Ejemplos
El ejemplo que desarrollaremos a continuacin es un simple cliente TCP que se
conecta a un determinado port, mostrando en stdio lo que recibe, contestando con un
string, para luego desconectarse:
#define MY_IP_ADDRESS
#define MY_NETMASK

"192.168.1.54"
"255.255.255.0"

#define MY_GATEWAY
#define MY_NAMESERVER

"192.168.1.1"
"200.49.156.3"

#define DEST_IP
#define DEST_PORT

inet_addr("192.168.1.50")
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 conexin
}
if(state==CLOSE)
printf("No pude conectarme");
else {
state=OPEN;
sock_mode(&socket,TCP_MODE_BINARY);
// inicializa lo que deba inicializar
// espera datos
while((bytes_read=sock_fastread(&socket,buffer,1024))==0){
if (bytes_read < 0)
break;
if(tcp_tick(&socket)==0) // detecta desconexin
break;
}
// procesa datos leidos, nosotros mostramos en stdio
buffer[bytes_read]=0;
printf("%s",buffer);

250

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 desconexin
break;
} while (len);
sock_close(&socket);
state=CLOSE;
while(tcp_tick(&socket)); // espera desconexin
}
}

Con un esquema como el desarrollado, ya podemos conectarnos a cualquier


servidor de cualquier protocolo de aplicacin que corra sobre TCP. Simplemente
deberemos implementar el protocolo de aplicacin dentro del estado de transferencia
de datos de nuestra conexin TCP.
Si bien hemos escrito el ejemplo en forma de loops para mayor claridad, hemos
marcado los diferentes estados en que iba avanzando nuestra mquina de estados de
conexin. Si nuestro equipo o aplicacin debe hacer otra cosa adems de abrir una
conexin TCP, es preferible utilizar costates o escribir el cdigo en forma de
mquina de estados. La decisin de la forma a utilizar y en qu funcin hace la
escritura sobre el socket es un punto interesante. Cada aplicacin tiene sus
caractersticas y segn lo que se necesite puede que una u otra cosa resulten ms o
menos apropiadas o cmodas en cada situacin. A fin de generalizar un caso un poco
ms simple, hemos desarrollado un pequeo ejemplo, en el cual el programa
principal llama a un handler que mantiene el estado de la conexin; luego el primero
escribe l mismo sobre el socket que recibe y pide cerrar la conexin. Mientras
tanto, otras tareas hacen sus cosas :
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"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

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 conexin
}
}
else {
*state=OPEN;
printf("Estoy conectado\n");
sock_mode(socket,TCP_MODE_BINARY);
// inicializa lo que deba inicializar
}
break;
case CLOSE:
default:
printf("Cerrando conexin...\n");
sock_close(socket);
*state=CLOSING;
case OPEN:
case CLOSING:
if(tcp_tick(socket)==0) {
// detecta/espera desconexin
*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 peridico a este handler
if((retrn=myTCPtick(&socket,&state))==OPEN){
// espera datos
if((bytes_read=sock_fastread(&socket,buffer,1024))!=0){
if ((bytes_read < 0)||(tcp_tick(&socket)==0)){
// detecta desconexin
state=CLOSE;
}
// procesa datos ledos, 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 desconexin
state=CLOSE;

252

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 conexin
state=INIT;
waitfor(state==IDLE);
exit(0);
}
}

// espera finalizacin o error

Servidor TCP
Para implementar un servidor TCP, tambin deberemos tener presente que una
comunicacin TCP avanza a travs de una serie de estados. Si bien estamos
tranquilos esperando que se nos llame, deberemos seguir una serie de pasos
fundamentales. stos son:
1. Inicio del servicio (apertura pasiva del socket)
2. Espera de un pedido de conexin, aceptacin y establecimiento de la misma
3. Transferencia de datos
4. Fin de la conexin

Implementacin
Si bien disponemos de funciones que nos permiten inicializar el socket, leerlo, y
escribirlo, una vez ms, deberemos desarrollar cada uno de los pasos manualmente.

Inicio del servicio


Iniciar un servicio es informarle al stack TCP/IP que estamos esperando que
alguien nos llame en determinado nmero de port; lo cual realizamos mediante una
llamada a la funcin tcp_listen(), pasndole como parmetros los datos necesarios
para armar el socket: el nmero de port. En un ambiente ms restrictivo, tambin
podemos especificar direccin IP y port aceptados. El penltimo parmetro indica la
direccin de un data handler para la transferencia de datos, pasndolo como NULL
utilizamos el que provee el sistema, sin inconvenientes. Cada vez que cerremos una
conexin 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

Networking con Rabbit

Espera de un pedido de conexin, 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 funcin sock_established(), pero puede que el host
remoto est muy apurado por decirnos algo y nos ataque inmediatamente con datos,
cerrando apuradamente la conexin, sin darnos tiempo a que veamos el socket como
establecido. Por esto es conveniente emplear tambin sock_bytesready().
if(sock_established(&socket) || sock_bytesready(&socket) >= 0) {
// estoy comunicado !
}
else
// podr seguir descansando

Transferencia de datos, Fin de la conexin


De aqu en ms todo se comporta de igual modo que para un cliente TCP, dado que
para este protocolo no existen clientes ni servidores, slo iniciadores y escuchas.

Ejemplos
El ejemplo que desarrollaremos a continuacin es un simple servidor TCP que
espera una conexin en un determinado port, mostrando en stdio lo que recibe,
contestando con un string, y esperando que quien llama corte la conexin. Dado que
el servidor probablemente sea slo una tarea ms, y lleva las de ganar: hace lo que le
piden y vuelve a dormir; directamente escribimos el cdigo en forma de mquina de
estados, la cual ser llamada como handler desde el programa principal, dejando
libre a ste para hacer lo que tenga que hacer. A fin de no complicar el handler
agregando ms estados, decidimos implementar la "aplicacin" (el recibir un texto,
mostrarlo y contestar) como un handler, que tambin escribimos como mquina de
estados debido al ciclo de espera en escritura que podra presentarse ante ausencia de
espacio en buffers.
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY
#define MY_PORT

"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

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

TCP
if (bytes_read < 0){
// detecta desconexin
return(-1);
}
// procesa datos ledos, nosotros mostramos en stdio
buffer[bytes_read]=0;
printf("%s",buffer);
// contestamos
strcpy(buffer,"Esto es una respuesta\n");
len=strlen(buffer);
*state=WRITE; // sigue ejecutando WRITE a continuacin
}
else
break;
// contina en este estado si no hay datos
case WRITE:
bytes_written=sock_fastwrite(socket,buffer,len);
if (bytes_written < 0){
// detecta desconexin
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 conexin
}
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 aplicacin
// inicializa lo que deba inicializar
}
break;
case OPEN:
if(application_handler(socket,astate))
*state=CLOSE;
break;
case CLOSE:
default:
printf("Cerrando conexin...\n");
sock_close(socket);
*state=CLOSING;
case CLOSING:

255

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 peridico a este handler
myTCPtick(&socket,&state,&astate);
// otras tareas
}
}

Con un esquema como el desarrollado, ya podemos implementar un servidor de


cualquier protocolo de aplicacin que corra sobre TCP. Simplemente deberemos
implementar el protocolo de aplicacin dentro del estado de transferencia de datos
de nuestra conexin TCP. De hecho, la sample Samples\tcpip\state.c muestra cmo
realizar un simple servidor HTTP.
Una ltima aclaracin. En este caso, dada la simpleza de la aplicacin propuesta, y
de su total esclavitud con respecto a TCP, particularmente al ser un servidor y no
tener otra funcin en la vida al desaparecer la conexin, nos result directa la
implementacin de la misma como esclava de la conexin. En otras aplicaciones de
la vida real, y particularmente cuando se trata de un cliente TCP, es altamente
probable que sea ms conveniente que el handler de la conexin TCP sea esclavo de
la aplicacin. Como dicen en el norte: your mileage may vary2, y cada developer
concebir su estructura de acuerdo a lo que crea ms conveniente, en funcin del
problema que deba resolver

Mltiples conexiones en un mismo port


Evidentemente, no estamos corriendo sobre un sistema operativo, y no podemos
hacer fork() y generar un nuevo proceso que atienda este requerimiento, mientras
seguimos esperando conexiones en el port que servimos. Estamos en el dominio de
los sistemas dedicados, y las cosas se resuelven con una adecuada planificacin, y
haciendo el trabajo pesado a la hora de programar. En este caso, lo que tenemos que
hacer es definir un nmero de servidores, crear un socket y una funcin para cada
uno de ellos, y cada funcin har su correspondiente llamada a tcp_listen(). Dado
que son varios servicios idnticos, el cdigo es el mismo, slo cambia el estado de la
mquina que los sirve, por lo que podemos llamar varias veces al mismo handler con
2

Desde mi posicin geogrfica, Estados Unidos est al norte, puede que si se lee este texto en otros
pases esto no sea tan as. De todos modos, la referencia es a que su opinin puede diferir de acuerdo
a su experiencia en su problema particular.

256

TCP
diferentes variables de estado y buffers externos (y diferentes sockets), o utilizar
indexed cofunctions si preferimos usar las alternativas de Dynamic C.
El ejemplo que desarrollaremos a continuacin es un simple servidor TCP que
acepta mltiples conexiones en un determinado port, mostrando en stdio lo que
recibe3, contestando con un string para luego desconectarse.
La primera modificacin es en el handler de aplicacin, que recibe un parmetro
ms: el buffer; y ms all de lo que el compilador tenga como standard, las variables
locales deben ser auto, para poder compartir el cdigo.
int application_handler(tcp_Socket *socket,int *state,char *buffer)
{
auto int bytes_written,bytes_read,len;

La segunda modificacin es en el handler de TCP, que recibe el parmetro extra


para el handler de aplicacin.
void myTCPtick(tcp_Socket *socket,int *state,int *astate,char *buffer)

La ltima modificacin es en el programa principal, que hacemos los buffers


estticos, los parmetros arrays estticos (sockets, buffers y variables de estado) y
pasamos los parmetros a cada handler antes de llamarlo. Realizamos un simple loop
para demostrar la forma de operacin, 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 peridico 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 a
MY_SERVERS conexiones simultneamente, 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 diferentes
pedidos simultneos sern demorados hasta satisfacer el que est en curso.

257

Networking con Rabbit


A partir de DC8.5, se incorporan una serie de macros tendientes a facilitar el
manejo de las estructuras de los distintos servidores. Surge entonces la figura de
Zserver, que es quien tiene el control de estas estructuras y las sirve internamente a
los dems servidores, como por ejemplo HTTP server o FTP server.
Por ejemplo, para definir los tipos MIME, procederemos de la siguiente forma:
SSPEC_MIMETABLE_START
SSPEC_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 de
archivos.
SSPEC_MIME es una entrada normal de la lista de tipos MIME.
Para definir el directorio de un servidor HTTP, procederemos de la siguiente
forma:
SSPEC_RESOURCETABLE_START
SSPEC_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 xmem
SSPEC_RESOURCE _ROOTVAR identifica a una variable en rea root
SSPEC_RESOURCE_FUNCTION identifica a una funcin SSI un CGI4
El campo empleado para el nombre, tiene por supuesto una longitud mxima, y sta
es de veinte caracteres, modificable mediante la macro:
#define SSPEC_MAXNAME

20

Como probablemente hayamos deducido, se trata de una forma ms amigable de


trabajar, respecto de la definicin cruda de las estructuras que utilizbamos en
versiones anteriores, y con la que operamos a lo largo del libro introductorio. Si bien
el operar directamente sobre la estructura tiene la ventaja de que quien comienza a
estudiar puede observar en detalle como se define y se arma toda la estructura de
trabajo, es mucho ms cmodo para el usuario experimentado el referirse mediante
4

El manejo de CGIs incluye una "nueva forma" mucho ms poderosa, pero bastante ms compleja,
que omitiremos por el momento.

258

Zserver
estas macros. Debido a que, como comentramos, es mucho ms cmodo trabajar
con esta nueva forma, ser la que emplearemos en los ejemplos.

"Archivos" en xmem agregados de forma dinmica


Adems de este directorio esttico, en flash, existe un directorio dinmico, que
puede modificarse en tiempo de ejecucin, agregando nuevos archivos, y cambiando
las propiedades de los mismos. Obviamente, tiene un tamao por defecto para
preservar RAM, el cual puede modificarse mediante la macro:
#define SSPEC_MAXSPEC

10

y que por defecto es diez, como se indica.


La operacin de agregado de entradas a esta tabla se realiza mediante funciones
especiales:
sspec_addxmemfile(nombre,direccin,server);

donde nombre corresponde al URL del servidor HTTP, o el nombre de archivo en el


servidor FTP, direccin es la direccin en xmem, en el formato que usa #ximport, y
server es una palabra que indica qu servidores ven este "archivo", y puede ser
SERVER_HTTP, SERVER_FTP, o un or lgico de ambos.
El "archivo" puede estar tanto en RAM como en flash, aunque probablemente lo
ms til sea RAM, dado que el contenido de flash puede ser referido mediante la
tabla esttica. Los nombres no pueden incluir prefijos que podran confundirse con
indicaciones de file system, como veremos a continuacin.

Archivos en file systems


Para referirnos a archivos en file systems, simplemente utilizamos un prefijo de
manera similar a como haramos en un sistema operativo con el concepto claro de
file 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 particin FAT del dispositivo
/B/
para referirnos a los archivos en la segunda particin, etc.; de acuerdo a las
reglas para montar dispositivos y particiones, que analizamos en el captulo
sobre file systems.
Tambin 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 se
inicializ un LX all. No ahondaremos en este tema por el momento.
Antes de poder utilizar los archivos en file systems, deberemos montarlos, lo cual
haremos con una llamada a funcin:
sspec_automount(quemonto, NULL, NULL, NULL);

259

Networking con Rabbit


donde quemonto puede ser SSPEC_MOUNT_FS, SSPEC_MOUNT_FAT, o
SSPEC_MOUNT_ANY; para montar todos los LX de FS2, la primera particin
disponible de FAT, o ambas, respectivamente. Esto efectivamente llama a las rutinas
de inicializacin para los diversos file systems que viramos en su captulo
correspondiente, sin embargo, en el caso de FAT, slo registra para uso en Zserver a
la primera de ellas. Para registrar particiones FAT adicionales, utilizamos la funcin
sspec_fatregister(), con informacin que nos provee sspec_automount() dentro de
estructuras que debemos pasarle en los parmetros, previa modificacin de la macro
que define la cantidad mxima de particiones FAT utilizables
(SSPEC_MAX_FATDRIVES). No ahondaremos en este tema, slo comentamos que
es posible y existe muchsima informacin y muy rica, en los manuales de TCP/IP.
Existe un ejemplo en particular sobre este punto en las samples; nos referimos al
archivo Samples\TCPIP\Zserver\filesystem.c.
Zserver es muy rico, y hay mucha informacin para digerir. Sin embargo, aqu slo
analizaremos una pequea parte, aqulla que nos permite resolver las inquietudes
que nos plantean los servidores HTTP y FTP, como medio de control o reporte de
nuestra aplicacin.
Veremos ejemplos y muchas opciones ms, particularmente permisos de usuario y
autenticacin, al analizar la forma de utilizar los servicios de Zserver en servidores
HTTP y FTP.

Servidor HTTP
Para compilar las bibliotecas de funciones de HTTP, que proveen un servidor
bastante poderoso e interesante, necesitamos incluir:
#use "http.lib"

Los tipos MIME y el directorio del servidor quedan definidos mediante estructuras
que apuntan a los datos a servir. El servidor se inicializa llamando a la funcin
http_init(). Finalmente, el manejo del servidor lo realiza la funcin http_handler(), la
cual deberemos llamar peridicamente. El llamar a http_handler() provee adems la
funcin 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 nmero de servidores implica una mayor
ocupacin y necesidad de memoria. La macro en cuestin se define antes de incluir
la biblioteca de funciones de HTTP. Digamos, por ejemplo, que queremos cuatro
servidores:
#define HTTP_MAXSERVERS 4
#use "http.lib"

260

Servidor HTTP
Es posible, adems, aumentar la capacidad de conexin definiendo el nmero
mximo de sockets (que por defecto es cuatro) mediante otra macro. Deberemos
realizar esta modificacin si ponemos un nmero de servidores mayor a la cantidad
de sockets por defecto y/o necesitamos ms sockets para otras tareas. Nuevamente,
un mayor nmero de sockets implica una mayor ocupacin y necesidad de memoria.
Esta macro se define antes de incluir la biblioteca de funciones de TCP/IP. Por
ejemplo, 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 el


manejo de las estructuras del servidor, particularmente la inclusin de archivos en el
directorio esttico del mismo, sin necesidad de completar demasiados campos de una
estructura y/o definirla. El mtodo utilizado favorece la tradicional tcnica de
copiado-pegado (copy-paste). La transicin es bastante favorable, por ejemplo la
estructura HttpSpec que se utiliza tiene su nombre por tradicin, dado que muy
probablemente 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 estructura
que emplebamos tradicionalmente (la cual podemos seguir utilizando si es de
nuestro agrado). As, tanto tipos MIME como directorio, se definirn con las macros
que hemos visto:
SSPEC_MIMETABLE_START
SSPEC_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_START
SSPEC_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

Imgenes generadas en tiempo de ejecucin


De igual modo, podemos definir archivos en xmem que agregamos en tiempo de
ejecucin, como por ejemplo una imagen en RAM que generamos en base a
estadsticas, o por qu no, tomndola de un lector de huellas dactilares.
Lo ms simple es generar la imagen en formato BMP sin compresin. Si ya
sabemos de antemano el tamao y resolucin de color que vamos a utilizar,
simplemente generamos con cualquier software una imagen en dicho formato, y
extraemos la cabecera y la paleta, las cuales podemos guardar en flash mediante
261

Networking con Rabbit


#ximport, y luego copiarlas a RAM, reservando un rea al final del tamao de la
imagen. A continuacin, modificamos el campo de tamao de ese pseudo-archivo.
se ser nuestro buffer donde generaremos o alojaremos la imagen:
Al compilar

Ejecutando
RAM

reservar

RAM
imagen
paleta
header
long
copiar

espacio ocupado por


paleta + header + imagen
modificar

#ximport

paleta
header
long

flash

flash

espacio ocupado por paleta + header

Si la imagen es en color o en niveles de gris, debemos tener cuidado de elegir una


paleta de colores (o niveles de gris) compatible con lo que vamos a generar.
Recordemos que slo modificamos el contenido de la imagen, no su paleta. Por
supuesto que tambin podramos hacer esto si quisiramos, pero requiere mayor
trabajo.
El siguiente ejemplo muestra la porcin de cdigo que empleamos para generar y
mostrar la imagen obtenida de un sensor de huellas dactilares. El cdigo completo lo
podemos observar en la nota de aplicacin CAN-052, que se incluye en el CD que
acompaa a esta edicin.
SSPEC_MIMETABLE_START
SSPEC_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 server
sspec_addxmemfile("/dedito.bmp",bitmap,SERVER_HTTP );
xmem2xmem(bitmap,bmpheader,BMPHEADER+4);
// copia encabezado BMP
xsetlong(bitmap,BMPLEN);
// corrige tamao (ximport)
bitmap+=(BMPHEADER+4);
// corrige puntero a imagen

262

Servidor HTTP

El inconveniente de BMP es que como no es totalmente un standard, puede que


algunos navegadores no lo muestren, o no lo hagan directamente. Una solucin es
elegir otro formato de archivos, como GIF, PNG, o JPEG (JPG). Sin embargo, GIF
emplea un algoritmo de compresin cuyos derechos pertenecen a una conocida
empresa del medio, y quien genere archivos en este formato debe abonar una
licencia; PNG es abierto, pero el algoritmo de compresin no es trivial, al menos no
para su implementacin en un sistema dedicado como el nuestro; y JPEG requiere un
grado interesante de procesamiento digital de seales (DSP), el cual escapa a
nuestras buenas intenciones.
En cualquier caso, como vimos, agregamos el "archivo", pseudo-archivo, o como
se nos ocurra llamarlo, al directorio; mediante la funcin de Zserver para este fin:
sspec_addxmemfile(URL,addr,SERVER_HTTP);

Por supuesto, claro est, que para que la imagen pueda ser vista, deberemos pedirla
al servidor por el nombre con que la definimos en el directorio, lo cual corresponde
a /dedito.bmp, y se realiza dentro de una referencia <IMG SRC="/dedito.bmp">
dentro del archivo HTML que corresponda.

Imgenes que no aparecen


En la gran mayora de los casos, lo que ocurre es que un navegador ansioso,
acostumbrado a hacer gran cantidad de pedidos simultneos a poderosos servidores
con gigabytes de RAM en la Internet, se encuentra con un micro de 8-bits con una
cantidad limitada de servidores (2) y sockets (4), que le dice no a sus pedidos de
conexin. Esto se manifiesta particularmente en pginas complejas, con muchas
imgenes, y sistemas con pocos servidores disponibles; donde "pocos" podramos
decir que es el doble de la cantidad de elementos en la pgina, e incluye por supuesto
a los valores por defecto. El navegador intenta traer todas las pginas a la vez, y el
servidor (Rabbit) le rechaza las conexiones, a lo cual el primero no reintenta y
simplemente marca las pginas como con error. Al refrescar la pgina, o pedir
individualmente las imgenes, muy probablemente aparezcan.
Como generalmente sucede, la solucin es marcar el paso y en vez de decir un no
rotundo al pedido de una conexin, optar por aceptarla sabiendo que en pocos
segundos ya tendremos liberados nuestros escasos recursos. Esto se realiza mediante
la funcin tcp_reserveport(), como comentramos 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

Networking con Rabbit

Archivos servidos desde file systems


Un archivo en un file system puede responder por ejemplo a la generacin de un
log como los que vimos en el captulo sobre file systems, una imagen (como el
ejemplo anterior), o bien archivos que se copian mediante un cliente o servidor FTP,
como veremos ms adelante.
El ejemplo a continuacin hace uso de lo visto al analizar file systems,
particularmente FS2. Al detectar la presin del switch S2 de un RCM2100,
grabamos un mensaje en un archivo, junto con informacin de fecha y hora. Dicho
archivo, 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 resaltada
junto con lo pertinente a este ejemplo: la peticin de log.txt, produce que el
servidor entregue el archivo 1, presentndolo como tipo MIME text/plain al
navegador.
En el otro, simplemente lo accedemos desde el HTML con el prefijo del file
system. La peticin de /fs2/file1 produce que se entregue el archivo 1 sin indicar
tipo MIME.
En todos los casos, el LX utilizado es el que se ha definido al modificar (o no) la
configuracin del BIOS, segn el mdulo que se tiene, tema ya analizado en el
captulo sobre file systems, en el apartado sobre FS2
SSPEC_MIMETABLE_START
SSPEC_MIME(".html", "text/html"),
SSPEC_MIME(".txt", "text/plain"),
SSPEC_MIME(".gif", "image/gif")
SSPEC_MIMETABLE_END
SSPEC_RESOURCETABLE_START
SSPEC_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[]="presin 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

Servidor HTTP
if ((rc=sspec_automount(SSPEC_MOUNT_FS,NULL,NULL,NULL))==0){
while(1) {
http_handler();
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);
fclose(&file);
waitfor(DelayMs(100));
waitfor(BitRdPortI(PBDR,2));
waitfor(DelayMs(100));

// guarda el texto
// cierra el archivo
// anti-rebote
// espera que libere S2
// 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"
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>

alink="#006666"

Para trabajar con archivos en FAT es similar, slo que el prefijo depende de la
particin, como se indicara en el apartado sobre Zserver.

Autenticacin
Este es el proceso por el cual es posible identificar a un usuario, de modo que nos
permita que slo aquellos usuarios autorizados puedan acceder a determinadas
pginas.
En versiones de DC 8.3 e inferiores, podemos tener algunas de las pginas
protegidas por password de una forma muy simple, definiendo el realm al que dicho
password est asociado. Cuando un web browser solicite una pgina protegida, le

265

Networking con Rabbit


aparecer un cartelito describindole la situacin e invitndolo a ingresar usuario y
password 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 como
password.
La forma de proteger una pgina era agregarle el puntero al realm en el ltimo
parmetro de la estructura donde se la asociaba al web server, http_flashspec:
{HTTPSPEC_FILE,"/index.shtml",index_html,NULL,0,NULL,NULL}
{HTTPSPEC_FILE,"/learn.shtml",learn_html,NULL,0,NULL,myrealm}

// no protegida
// protegida

Lo mismo para una funcin CGI:


{HTTPSPEC_FUNCTION,"/learn.cgi",0,learn,0,NULL,NULL}
{HTTPSPEC_FUNCTION,"/learn.cgi",0,learn,0,NULL,myrealm}

// no protegida
// protegida

En versiones de DC 8.5 y superiores, el manejo es ms poderoso pero requiere


mayor complejidad. En primer lugar, los archivos se asocian a grupos de usuarios, lo
que puede hacerse de forma esttica (en flash). Los grupos de usuarios son diecisis,
uno por cada bit de una palabra; ningn bit seteado corresponde a ningn grupo, el
bit 0 seteado corresponde al grupo 0. Luego, se definen usuarios que se asocian a
estos grupos, lo cual se realiza de forma dinmica, es decir, es posible agregar un
usuario en cualquier momento.
El cambio inmediato de uno a otro mtodo, sera:
SSPEC_RESOURCE_P_XMEMFILE(URL,addr,realm,rg,wg,server,aut)
SSPEC_RESOURCE_P_FUNCTION(function,addr,realm,rg,wg,server,aut)

Obsrvese la inclusin de _P_ en la macro, lo que indica 'protected'. El URL y su


direccin en memoria, la funcin y su direccin en memoria siguen como siempre,
realm es el nombre del realm (el texto a mostrar), rg es la palabra conteniendo los
grupos con permiso de lectura, wg es la palabra conteniendo los grupos con permiso
266

Servidor HTTP
de escritura, server es el servidor que nos interesa y aut es el tipo de autenticacin
soportado (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) podrn acceder


a /setup.shtml y ejecutar /setup.cgi (permiso de lectura), nadie tiene permiso de
escritura sobre ellas (estn en flash de todos modos), slo son visibles por el servidor
HTTP5, y se soportan dos mtodos de autenticacin: bsico (clear text) y digest
(simple encripcin 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 usuario
sauth_setusermask(uid, groups, NULL);
// lo agrega al grupo

donde uid es la identificacin asignada al crear el usuario (un entero), groups es el o


los grupos a los que queremos que este usuario pertenezca (de acuerdo a lo que
queremos que pueda hacer); esto es simple debido a que cada grupo est
representado por un bit, de modo que un simple OR lgico entre dos grupos
representa a ambos. Siguiendo con el ejemplo anterior, para asociar el usuario
iuserneim 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 direccin IP desde una pgina web, y en el captulo


sobre configuracin en campo, encontraremos breves ejemplos sobre como crear y
manejar usuarios.

Archivos y variables
En sistemas con muchas pginas o con pginas a servir en file systems, sera muy
tedioso marcar cada pgina con su realm y sus grupos, por lo que existe una
posibilidad adicional que permite proteger un directorio, o un grupo de archivos:
protegerlos por prefijo. Esto se realiza mediante una tabla de reglas, que puede
definirse de forma esttica y tener agregados de forma dinmica. Para definir la parte
esttica, deberemos agregar una definicin antes de incluir la biblioteca de
funciones, y luego definir la tabla de reglas:
#define SSPEC_FLASHRULES
#use "dcrtcp.lib"

Tambin podran definirse visibles para el servidor FTP y ser reemplazadas mediante FTP, pero esto
es algo ms complicado y lo obviaremos por el momento

267

Networking con Rabbit


#use "http.lib"
SSPEC_RULETABLE_START
SSPEC_RULE("/setup", "", ADMIN_GROUP, 0, SERVER_HTTP)
SSPEC_RULETABLE_END

De esta forma, todos los archivos cuyo URL comience con /setup sern protegidos,
y se solicitar la autenticacin del usuario, que deber pertenecer en este caso a
ADMIN_GROUP. Ntese que las funciones SSI y CGI no se incluyen dentro de esta
categora. Para proteger una funcin, debe emplearse el mtodo visto anteriormente
de proteccin individual.
Nuevamente, en el apartado sobre cambio de direccin IP desde una pgina web, y
en el captulo sobre configuracin en campo, encontraremos breves ejemplos sobre
como manejar autenticacin en archivos y variables.

"Archivos" agregados en forma dinmica


Si necesitamos que un pseudo-archivo como la imagen generada del ejemplo
anterior, tenga acceso restringido para determinados grupos de usuarios, necesitamos
modificar sus permisos de acceso, lo cual hacemos mediante la siguiente funcin:
sspec_setpermissions(idx,realm,rg,wg,server,aut,mime)

donde idx es el valor retornado por sspec_addxmemfile() al agregar el "archivo" en


forma dinmica, realm es el nombre del realm (el texto a mostrar), rg es la palabra
conteniendo los grupos con permiso de lectura, wg es la palabra conteniendo los
grupos con permiso de escritura, server es el servidor que nos interesa; aut es el tipo
de autenticacin soportado (a negociar con el browser). El parmetro mime es un
puntero a una estructura que define el tipo MIME, en caso que ste no figure en la
tabla esttica

Archivos en file systems, agregados en forma dinmica


As como definimos una tabla esttica con reglas para autenticacin, tambin
podemos agregar entradas en forma dinmica, lo que hacemos llamando a la funcin:
sspec_addrule(url,realm,rg,wg,server,aut,mime);

donde los parmetros son prcticamente los mismos que para la funcin
sspec_setpermissions(), excepto por url, que es el nombre del archivo dentro de la
jerarqua del servidor HTTP.
La cantidad mxima 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 tabla
esttica, y respetar dichas reglas al agregar nuevos archivos; por ejemplo, se protege
todo un directorio, y se agrega el archivo dentro o fuera de ese directorio segn deba
estar protegido o no. Esto es fundamentalmente porque siguiendo esta sugerencia, se
268

Servidor HTTP
utilizan slo una o dos reglas de acceso, mientras que de otro modo existe una regla
para cada archivo, ms todo el cdigo para generarlas.
Sin embargo, existe un pequeo detalle aqu, el cual veremos ms adelante al
analizar el servidor FTP. Lo que podemos comentar, como adelanto, es que esto no
se refiere al archivo, sino al prefijo, es decir, la parte del URL que identifica al
archivo dentro de la estructura de directorio del servidor HTTP. Como hemos
sugerido en el apartado sobre Zserver, existe otra forma de acceder al mismo archivo
pero por otro camino, lo cual podra utilizarse para burlar la proteccin. Como
comentramos, lo analizaremos al estudiar el servidor FTP, dado que resulta ms
claro.

HTTP upload
Si necesitamos que el usuario pueda subir informacin a nuestro equipo de manera
simple, disponemos de un CGI que nos permite realizar esta tarea. Adems, existen
dos excelentes samples que muestran cmo utilizarlo, una de ellas adems tiene su
propio data handler para manejar la escritura. Nos referimos a
Samples\tcpip\http\HTTP_UPLD.c y Samples\tcpip\http\Upld_fat.c. Ambas se basan
en FAT.
Para este ejemplo, nosotros nos basaremos en FS2, que es menos flexible, pero
fundamentalmente ms econmico. Todo lo relativo a la inicializacin y uso de FS2
se encuentra en el captulo sobre file systems.
El CGI que Rabbit provee dentro de la biblioteca de funciones del servidor HTTP,
junto con todo el cdigo 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, como


vamos a mostrar una foto, hacemos un mapeo del nombre que necesitamos con una
extensin reconocida al nombre simple dentro de FS2; luego, vamos a definir los
permisos de autenticacin para que slo el administrador pueda ejecutar el CGI, y
adems, que slo el administrador pueda escribir en el FS2, mientras que cualquiera
puede leer.
El listado del cdigo es el siguiente:
#class auto
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

#define USE_HTTP_UPLOAD
#define SSPEC_FLASHRULES
#memmap xmem

269

Networking con Rabbit


#use "dcrtcp.lib"
#use "fs2.lib"
#use "http.lib"
#ximport "upload.html"

index_html

#define ADMIN_GROUP

0x0002

SSPEC_MIMETABLE_START
SSPEC_MIME(".html", "text/html"),
SSPEC_MIME(".jpg", "image/jpeg"),
SSPEC_MIME(".cgi", "")
SSPEC_MIMETABLE_END
SSPEC_RESOURCETABLE_START
SSPEC_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_START
SSPEC_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 para
subir, el cual se grabar en FS2 como 3, y se ver en el servidor HTTP como
foto.jpg, gracias al mapeo que hicimos. Una vez subida, volvemos a pedir la pgina
principal (link a home o como ms nos agrade)
Dado que el tipo MIME que entregamos es image/jpeg, la imagen que subamos
deber estar en este formato. Una ltima consideracin a tener en cuenta, aunque
simple, es que el dispositivo FS2 que utilicemos deber tener suficiente espacio
como para alojar el archivo y la metadata que este necesite, de acuerdo al tamao de
sectores empleado.
270

Servidor HTTP
A continuacin, 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 y
traer la informacin del servidor, como analizamos en un apartado siguiente.

Actualizacin dinmica de variables: AJAX


La sigla AJAX significa Asynchronous JavaScript And XML, y de su nombre
podemos deducir que no est muy relacionada con Rabbit. Esta vertiginosa
tecnologa que nos recuerda a un conocido fabricante de inviolables cajas de
seguridad en entraables dibujos animados, fue popularizada por Google y est
basada en los siguientes standards:
D
JavaScript
D
XML
D
HTML
D
CSS
En esencia, AJAX es algo que se ejecuta en el navegador web, independiente de los
servidores, y por ello es realizable en cualquiera de stos, incluyendo a Rabbit. No
es algo que el servidor provea sino que el cliente realiza.
Hemos visto en los ejemplos del libro introductorio que cuando actualizbamos una
variable envibamos un redireccionamiento o una nueva pgina completa
conteniendo la respuesta. Si tenemos un sistema que debe mostrar informacin en
tiempo real, tenemos los siguientes inconvenientes:
D
resulta bastante molesto el redibujar constantemente la pantalla
D
el trfico de material redundante, ajeno a lo que realmente cambia, es
impropio.

271

Networking con Rabbit


Con AJAX, un cdigo JavaScript intercambia con el servidor slo aquellos datos
necesarios a travs de un objeto XMLHttpRequest y luego actualiza la pgina web en
background, sin necesidad de recargarla. Esto resuelve ambos inconvenientes
mencionados.

El cdigo JavaScript
La implementacin de AJAX requiere la utilizacin de conceptos de programacin
de pginas web que escapan a la intencin de este libro.
En la pgina web de este texto incorporamos un ejemplo; para la comprensin y
estudio del mismo se sugiere consultar la amplia literatura disponible para
JavaScript.

El servidor Rabbit
En lo propio del servidor Rabbit, slo debemos ocuparnos de la generacin del
archivo XML en el que estarn los valores de las variables que deben ser
actualizadas. Esta tarea podemos realizarla generando un archivo dinmico o
mediante un script RabbitWeb, que tal vez sea la opcin ms 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 script
RabbitWeb 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 pgina web de este texto incorporamos un ejemplo completo del fenmeno


AJAX.

Grficos: JavaScript, Flash


ste es otro terreno donde sucede lo mismo que comentramos al analizar AJAX.
El servidor web de Rabbit puede servir todo lo que sirve un servidor web; es decir, si
queremos que sirva animaciones Flash, las servir, slo debemos colocar el URL
correspondiente en el directorio. A su vez, los objetos Flash pueden realizar grficos
tomando datos de archivos XML, mediante el mismo principio de AJAX. La
complejidad en este tipo de implementaciones no est en el Rabbit sino en la
programacin de los JavaScripts y Flash correspondientes, todo lo que debemos
hacer en el Rabbit es generar un archivo XML con la informacin de las variables de
proceso que queremos transferir para que el navegador haga todo el trabajo.
272

Servidor HTTP
En la pgina web de este texto se encuentra un ejemplo de grficos 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 inicializacin se realiza mediante la siguiente llamada a funcin:


httpc_init(&hsock,&sock);

Para realizar la peticin:


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 autenticacin, disponemos de una llamada a funcin


ms completa:
httpc_get(&hsock,server,port,url,auth);

donde:
D
D
D

server es un puntero al nombre a resolver del servidor


port es un entero conteniendo el nmero de port, generalmente 80
auth es un puntero a un string conteniendo usuario:password

A continuacin, disponemos de funciones para leer los headers, saltearlos, y leer el


cuerpo del objeto. Por ejemplo, podemos leer el header y comparar contra uno en
particular:
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

Networking con Rabbit


}

Podemos obviar los headers mediante la llamada a funcin:


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 operacin:


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 determinada
informacin en un servidor remoto. Por ejemplo, en el libro introductorio
desarrollamos un control de acceso, el cual reportaba el log de accesos va FTP, a
una hora determinada del da. Otra aplicacin tambin puede ser un log de eventos,
estadsticas de proceso, bases de registros para informacin interna, etc. Si bien
hemos visto este tema en dicho libro, ahondamos un poco ms los conceptos aqu.
Para compilar el cliente FTP, necesitamos incluir la biblioteca de funciones:
#use "ftp_client.lib"

La forma ms simple de utilizarlo, sin recurrir a file systems, es pasarle


directamente la direccin en memoria donde estn los datos a transmitir, y su
longitud:
ftp_client_setup(ftp_server,0,user,password,FTP_MODE_UPLOAD,
filename,NULL,addr,len);

Si en realidad queremos traer la informacin 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 direccin del servidor es un entero largo, el cual obtendremos de forma similar a


como hiciramos con el cliente POP, llamando a resolve():
274

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 tambin podramos usar inet_addr():


ftp_client_setup(inet_addr("192.168.1.50"),...);

Sin embargo, la longitud mxima de archivo que podemos transferir est limitada a
32 kilobytes, y el espacio de almacenamiento est limitado por ser addr un puntero
de 16-bits al espacio root, por lo que es altamente probable y tal vez conveniente que
debamos 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 tambin podemos utilizar


FTP_MODE_DOWNLOAD y obtener un archivo. Si estamos detrs de un firewall y
necesitamos usar el modo pasivo, haremos un or lgico con FTP_MODE_PASSIVE:
ftp_client_setup(ftp_server,0,user,password,
FTP_MODE_UPLOAD | FTP_MODE_PASSIVE,
filename,NULL,addr,len);

A continuacin desarrollaremos un data handler simple para subir un archivo a un


servidor, lo cual nos puede ser til para reportar datos de un logger, registros de un
control de acceso, etc. El sistema llamar a nuestro data handler proveyndonos de la
direccin de un buffer y su longitud (por defecto FTP_MAXLINE, 256 bytes), ms
una variable de estado (dhnd_data) que podemos utilizar para contar los registros a
enviar. Tambin es posible operar con un offset (variable offset) respecto al inicio de
los datos, de forma similar a como nos desenvolvimos al operar sobre un socket
directamente, y como haremos ms adelante, en otro captulo, al desarrollar una
aplicacin. Todo depende del tipo de tarea a realizar; en un ejemplo como ste, en el
cual trabajamos no con un archivo o un flujo continuo de datos sino con una base de
registros o listado de eventos, personalmente prefiero las mquinas de estados. Por
ltimo, los flags nos indican qu operacin 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 nmero de registro
return(strlen(data));

275

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, el
programa principal asigna el momento en que ste corre mediante la llamada a
ftp_client_tick(). El data handler ser llamado con los parmetros correspondientes
para poder enviar el "archivo" al server, particularmente flags tendr el valor
FTPDH_OUT. En nuestro caso, lo que hacemos es generar un listado de registros de
log interno, que quedar en el servidor FTP como un archivo. Cuando el envo del
archivo termine, flags tendr el valor FTPDH_END, y si la conexin 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 nuevo
listado la prxima vez.
A continuacin, el listado completo de un pequeo ejemplo que lee uno de los
botones de un mdulo y genera un listado de una serie de lneas de texto, a modo de
registros de log, que ser almacenado en un servidor FTP:
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY
#define MY_NAMESERVER

"192.168.1.54"
"255.255.255.0"
"192.168.1.1"
"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

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, cuando
estudiamos este tema.
En lo referente al manejo del data handler, en este caso s utilizamos los parmetros
len y offset; en el primer caso para saber de cuntos datos disponemos para escribir,
y en el segundo, para tener una idea de la cantidad de datos que estamos recibiendo y
poder abortar la operacin cuando superamos el tamao mximo de archivo que
podemos 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 versin anterior del
archivo. Una vez obtenido, lo abrimos en modo lectura para mostrarlo en pantalla (se
aconseja hacer la prueba con archivos de texto...). De igual forma, es posible escribir
otro data handler que transfiera un archivo al servidor. Todo lo relativo a la
inicializacin y uso de FS2 se encuentra en el captulo sobre file systems; nos
centramos aqu en el ejemplo de cliente FTP:

277

Networking con Rabbit


#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"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)
return(0);
fwrite(&file, data, len);
return(len);

// seguro
// guarda el texto

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

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 informacin, donde otros
sistemas puedan ingresar y acceder a sta. Si ese repositorio debe ser nuestro sistema
basado en Rabbit, podemos utilizarlo como servidor FTP.
Para compilar el servidor FTP, necesitamos incluir la biblioteca de funciones:
#use "ftp_server.lib"

La inicializacin y manejo se realizan mediante dos funciones: ftp_init() y


ftp_tick(), respectivamente. La funcin ftp_init() puede utilizarse para definir un set
diferente de data handlers, lo cual escapa a nuestras intenciones. La funcin
ftp_tick() debe llamarse peridicamente para mantener el server activo.
Una vez ms, recordemos que estamos en un sistema dedicado, y debemos cuidar y
administrar los recursos. Podemos definir la cantidad mxima de servidores, es decir,
de conexiones simultneas que permitimos, que por defecto es una:
#define FTP_MAXSERVERS

Recordemos que tenemos un lmite en la cantidad de sockets disponibles, como


viramos al comenzar el anlisis de TCP y en el servidor HTTP:
#define MAX_TCP_SOCKET_BUFFERS 4

El servidor atiende solamente pedidos de conexin en la interfaz por defecto


(IF_DEFAULT). Si deseamos que atienda en cualquiera (IF_ANY), o en una en
particular (IF_ETH0, por ejemplo), lo indicamos mediante otra macro:
#define FTP_INTERFACE

IF_ANY

279

Networking con Rabbit


Finalmente, continuando con la intencin de preservar recursos, existe un tiempo
mximo de espera en una conexin de control para que el cliente enve un comando,
caso contrario se corta la conexin, liberando el socket:
#define FTP_TIMEOUT

16

algo similar existe en la conexin de datos:


#define FTP_DTPTIMEOUT

16

El manejo de un servidor FTP requiere de un lugar donde almacenar los archivos.


Una implementacin muy reducida permite servir archivos en xmem, pero si se
busca mayor flexibilidad, se debe emplear algn 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
autenticacin en el servidor HTTP, lo cual es evidente, dado que es Zserver el
encargado de esta tarea. La autenticacin se maneja, como vimos, por grupos.
El sistema soporta un equivalente al usuario anonymous, muy comn en este
entorno, mediante la definicin de un usuario como cualquier otro, y luego
declarando 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 podra en efecto ser cualquier otro; pero
dado 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
Estticos
Como podemos imaginarnos, esto no difiere mucho de lo observado para el
servidor HTTP con autenticacin, por lo que aconsejamos tener presente dicha
informacin. Definimos en este caso dos grupos de usuarios6. Ingresamos los
"archivos" como "protected", debido a que debemos vincularlos con algn grupo
para sus permisos de acceso. El campo realm, pasar a ser el texto a desplegar en la
posicin en que un servidor FTP muestra el grupo al que pertenece el archivo.
Luego, ingresamos los usuarios y los vinculamos a su grupo correspondiente. Al
declarar al usuario anonymous como lo que es, facilitamos el clsico login, los
archivos que pueden ser accedidos por todos los marcamos como accesibles por
6

Si bien la autenticacin es por grupos, no es necesario definir un grupo extra para el usuario
anonymous, si hay archivos que le pertenecen se marcan como pertenecientes a todos los grupos (por
definicin)

280

Servidor FTP
todos los grupos (0xFFFF). Un pequeo detalle: el campo server debe incluir
SERVER_FTP, y el campo aut la nica soportada actualmente
SERVER_AUTH_BASIC.
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

#use "dcrtcp.lib"
#use "ftp_server.lib"
#ximport "rabbit1.gif"
#ximport "0.jpg"
#ximport "oled.jpg"

rabbit1_gif
inca_jpg
oled_jpg

#define ANON_GROUP
#define ADMIN_GROUP
#define HACKER_GROUP

0xFFFF
0x0002
0x0004

// directorio del server, funciones y variables de SSI


SSPEC_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 ID
uid = 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();
// aplicacin
}
}

Dinmicos
Cuando el archivo es agregado de forma dinmica, como analizramos para el
servidor HTTP, lo agregamos con sspec_addxmemfile() y controlamos a quin
pertenece con sspec_setpermissions(). Si no tenemos archivos estticos, lo indicamos
mediante la macro:
281

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 requera de un mapa que asocie los nombres de los
archivos en el directorio del servidor, a los nombres permitidos en FS2 (nmeros
enteros). Dicho mapa se almacenaba en el UserBlock.
A partir de DC8.5, se puede emplear Zserver, con lo cual se establece un mapeo
automtico entre el nombre en el directorio y el nombre en FS2. Por ejemplo, el
archivo 35 se ve en el servidor como /fs2/file35. Dado que utilizaremos la jerga de
FS2, se recomienda la lectura del captulo correspondiente para tener los conceptos
al da. En dicho captulo, adems, analizamos todo lo necesario para configurar e
inicializar el sistema de archivos.
Por defecto, el file system se monta con permisos de acceso para todos, por lo cual
se debe restringir el acceso a lo que se deba. En un primer ejemplo, vamos a utilizar
una regla simple, restringiendo el acceso a todo lo que es el FS2, slo para el
administrador. Cuando utilizamos una regla de este tipo, el directorio fs2 aparece en
el listado, pero al acceder al mismo no se muestra el contenido, cuando el usuario no
est habilitado. Los dos siguientes son ejemplos del listado del directorio de dos
usuarios, primero admin, y luego anonymous:
Connected to 192.168.1.54.
220 Hello! Welcome to ZWorld TinyFTP!
Name (192.168.1.54:scaprile): admin
331 Password required
Password:
230 User logged in.
Remote system type is UNIX.
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,0).
150 Opening ASCII mode data connection for /bin/ls
total 1
dr-------1 admin
soloparami
0 Jan 1
226 Transfer complete.
ftp> cd fs2
250 OK
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,1).
150 Opening ASCII mode data connection for /bin/ls
total 1
-r-------1 admin
soloparami
82 Jan 1

282

1980 fs2

1980 file1

Servidor FTP
dr-------1 admin
226 Transfer complete.

soloparami

0 Jan 1

1980 ram

Connected to 192.168.1.54.
220 Hello! Welcome to ZWorld TinyFTP!
Name (192.168.1.54:scaprile): anonymous
331 Anonymous login - send e-mail address as password.
Password:
230 User logged in.
Remote system type is UNIX.
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,2).
150 Opening ASCII mode data connection for /bin/ls
total 1
d---r----1 anonymous soloparami
0 Jan 1 1980 fs2
226 Transfer complete.
ftp> cd fs2
250 OK
ftp> dir
227 Entering Passive Mode (192,168,69,54,4,3).
150 Opening ASCII mode data connection for /bin/ls
total 1
226 Transfer complete.

A continuacin veremos un ejemplo muy similar al que utilizramos con el servidor


HTTP; generamos un log de eventos en FS2, y tenemos un servidor FTP en el cual
es posible ingresar y obtener, entre otras cosas, el mencionado log. Utilizamos el
tipo de autenticacin que comentamos, de modo que slo el administrador del
sistema pueda acceder a los archivos en FS2. El cdigo resaltado es el
correspondiente al servidor FTP y la autenticacin:
#class auto
#memmap xmem
#use fs2.lib
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

#define SSPEC_NO_STATIC
#define SSPEC_FLASHRULES
#use "dcrtcp.lib"
#use "ftp_server.lib"
File file;
const char S2[]="presin de S2";
#define ANON_GROUP
#define ADMIN_GROUP

0x0001
0x0002

SSPEC_RULETABLE_START
SSPEC_RULE("/fs2", "soloparami", ADMIN_GROUP, 0, SERVER_FTP)
SSPEC_RULETABLE_END
main()
{

283

Networking con Rabbit


int rc,filenumber;
unsigned char len;
char buffer[256],*evento;
struct tm mtm;
int uid;
// Create a user ID
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);
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 archivo
waitfor(DelayMs(100));
// anti-rebote
waitfor(BitRdPortI(PBDR,2)); // espera que libere S2
waitfor(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, tambin caera dentro de la
regla. Si esto es molestia, y no podemos cambiar el nombre del archivo, podemos
escribir la regla de acceso como /fs2/, con lo cual el directorio se presentar de
forma diferente, es decir, no aparecer como perteneciente al realm mencionado,
sino que esto slo ocurrir al listar su contenido, tema el cual veremos con un poco
ms de detalle al analizar archivos sobre FAT.
En caso que se desee permitir acceso al FS2 a todos, y autenticar algunos archivos
para algunas personas y otros para otras, podemos operar de la misma forma,
284

Servidor FTP
ingresando el nombre completo del archivo en la tabla de reglas, o podemos operar
en forma dinmica, agregando las reglas para cada archivo en tiempo de ejecucin.
Todo depender de nuestra aplicacin y sus requerimientos, en la vida real.
Si no hay reglas estticas, eliminamos la macro:
#define SSPEC_FLASHRULES

y el listado:
SSPEC_RULETABLE_START
SSPEC_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 FS2
sspec_addrule("/fs2/file1","onlimi",ADMIN_GROUP,0,SERVER_FTP,
SERVER_AUTH_BASIC,NULL);
while(1) {

Sin embargo, tenemos algo interesante que mencionramos al estudiar el servidor


HTTP con archivos autenticados en file systems. Como viramos al analizar Zserver,
existe otra forma de acceder a archivos en FS2, y es a travs del LX particular como
subdirectorio. Tanto en el servidor HTTP como el FTP, el prefijo que identifica al
archivo es en este caso diferente, y un usuario medianamente instruido en FTP o en
las artes del conejo puede burlar las reglas de autenticacin y acceder al log, como
observamos en el listado siguiente, que muestra la forma en que un tal anonymous
sale huyendo con el log de admin:
Connected to 192.168.1.54.
220 Hello! Welcome to ZWorld TinyFTP!
Name (192.168.1.54:scaprile): anonymous
331 Anonymous login - send e-mail address as password.
Password:
230 User logged in.
Remote system type is UNIX.
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,0).
150 Opening ASCII mode data connection for /bin/ls
total 1
dr--r--r-1 anonymous anon
0 Jan 1 1980 fs2
226 Transfer complete.
ftp> cd fs2
250 OK
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,1).
150 Opening ASCII mode data connection for /bin/ls
total 1
----r----1 anonymous onlimi
82 Jan 1 1980 file1
dr--r--r-1 anonymous anon
0 Jan 1 1980 ram
226 Transfer complete.
ftp> get file1
local: file1 remote: file1
227 Entering Passive Mode (192,168,1,54,4,2).

285

Networking con Rabbit


550 Not authorized
ftp> cd ram
250 OK
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,3).
150 Opening ASCII mode data connection for /bin/ls
total 1
-r--r--r-1 anonymous anon
82 Jan 1
226 Transfer complete.
ftp> get file1
local: file1 remote: file1
227 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)

1980 file1

Para resolver este inconveniente, lo que hacemos es muy democrticamente


impedir el acceso a los LX de forma individual. En casos en los que esto no sea
posible, debido a que se repite un nmero de archivo en diferentes LX, simplemente
deben agregarse dos reglas por cada archivo, una para cada prefijo posible: /fs2/file1
y /fs2/ram/file1, por ejemplo. El siguiente listado muestra estas diferencias respecto
al programa anterior; volvemos a colocar la macro:
#define SSPEC_FLASHRULES

y un listado:
SSPEC_RULETABLE_START
SSPEC_RULE("/fs2/ram", "aca_no", ADMIN_GROUP, 0, SERVER_FTP)
SSPEC_RULETABLE_END

Si deseamos hacerlo dinmico, podemos, por ejemplo, hacerlo de a dos reglas por
archivo:
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 permitir
escritura, es decir, que alguien se conecte al servidor y coloque o sobreescriba un
archivo?
La implementacin actual no realiza escrituras por s sola, el developer deber
escribir un data handler que maneje escritura, y tener en cuenta algunas
particularidades, 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 FTP
DELE, SIZE, y MDTM, que por defecto no estn soportados, dado que esto
requiere mayor cantidad de elementos en las estructuras de los data handlers.
286

Servidor FTP
la macro FTP_WRITABLE_FILES deber definirse a 1 para que el servidor

autentique el permiso de escritura de un usuario, al abrir un archivo.

deberemos incluir los grupos con permisos de escritura en las declaraciones de

los archivos con funciones como sspec_addxmemfile(), sspec_setpermissions() y


sspec_addrule().
los usuarios con permiso de escritura se definen con la funcin
sauth_setwriteaccess() como se observa en los ejemplos de RabbitWeb en ste y
otros captulos del libro.
En primera instancia, tal vez sera ms apropiado implementar un cliente FTP y
realizar la tarea de traer el archivo de forma que nuestra aplicacin tenga el control
durante la transferencia, incluso es preferible si la informacin a traer debe tener
como destino una estructura o se realiza algn procesamiento, de modo de ir
convirtindolo a medida que lo traemos, lo que podemos hacer dentro del data
handler.
Otra opcin es utilizar un servidor HTTP e implementar upload, como hemos visto.
Para aquellos lectores con real inters en un servidor FTP en el cual se puedan
grabar archivos, toda la informacin necesaria para poder escribir el data handler se
encuentra en el Dynamic C TCP/IP User's Manual Vol. 2. Otra gua importante es la
sample Samples\TCPIP\FTP\ftp_srv2.c.

Archivos en FAT
El mdulo FAT nos permite utilizar en alto nivel la memoria flash adicional
incluida en mdulos de la serie RCM3700 y 3300, por ejemplo. La operatoria (desde
el punto de vista del servidor FTP) es esencialmente la misma, slo cambian los
prefijos y algn que otro parmetro en alguna llamada a funcin. Un detalle a tener
en cuenta, es que Zserver requiere que el mdulo FAT emplee la barra / para separar
directorios, y debe incluirse la biblioteca de funciones de FAT antes que Zserver
(que se incluye automticamente con las de TCP/IP).
#define FAT_USE_FORWARDSLASH
#use fat.lib
#use "dcrtcp.lib"
#use "ftp_server.lib"

A continuacin, un ejemplo sobre un RCM3720. Bsicamente es idntico al


ejemplo anterior sobre FS2, pero esta vez el log lo escribimos y servimos desde
FAT, en la primera (y nica) particin que hicimos sobre la serial flash de 1MB que
este mdulo incluye.
#class auto
#memmap xmem
#define TCPCONFIG 0

287

Networking con Rabbit


#define USE_ETHERNET

#define FAT_BLOCK
#define FAT_USE_FORWARDSLASH
#use fat.lib
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

#define SSPEC_NO_STATIC
#define SSPEC_FLASHRULES
#use "dcrtcp.lib"
#use "ftp_server.lib"
FATfile file;
const char S2[]="presin de S2";
#define ANON_GROUP
#define ADMIN_GROUP

0x0001
0x0002

SSPEC_RULETABLE_START
SSPEC_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 FAT
particion=fat_part_mounted[0];
// Se refiere a la primera particin de
// la serial/NAND flash en el mdulo
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

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 archivo
waitfor(DelayMs(100));
// anti-rebote
waitfor(BitRdPortI(PBDR,7)); // espera que libere S2
waitfor(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/, esto
resulta claro dado que es mucho ms probable tener un "archivo" en xmem cuyo
nombre comience con A. A diferencia de cuando servimos con FS2 y no incluimos la
barra al final del prefijo, en este caso, el listado del directorio raz muestra al recurso
A 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): admin
331 Password required
Password:
230 User logged in.
Remote system type is UNIX.
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,0).
150 Opening ASCII mode data connection for /bin/ls
total 1
dr--r--r-1 admin
anon
0 Jan 1 1980 A
226 Transfer complete.
ftp> cd A
250 OK
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,1).
150 Opening ASCII mode data connection for /bin/ls
total 1
-r-------1 admin
soloparami
123 May 9 1980 milog.txt
226 Transfer complete.
ftp> get milog.txt
local: milog.txt remote: milog.txt
227 Entering Passive Mode (192,168,1,54,4,2).
150 Opening BINARY mode data connection (123 bytes)
WARNING! 3 bare linefeeds received in ASCII mode
File may not have transferred correctly.
226 Transfer complete.
123 bytes received in 0.0102 secs (12 Kbytes/sec)

Por lo dems, la operatoria es exactamente la misma.

289

Networking con Rabbit

Servidores FTP y HTTP combinados


La informacin servida por el servidor HTTP, puede compartirse con el servidor
FTP, gracias a Zserver. La clave est en definir como visibles por ambos servidores,
a aquellos archivos que deseamos compartidos. El siguiente ejemplo sirve tres
archivos, uno slo en el servidor HTTP, otro slo en el servidor FTP, y el tercero en
ambos. Lo mismo es vlido para archivos agregados de forma dinmica, o en file
systems, slo debe tenerse en cuenta de completar correctamente el campo server de
las funciones correspondientes.
El nico inconveniente es que dado que para poder poner archivos estticos en el
servidor FTP, los definimos como "protected", es necesario disponer de un login de
usuario para poder verlos en el servidor HTTP. Una alternativa para resolver esto es
definirlos dos veces en el directorio, una para cada servidor; aunque obviamente se
desperdicia espacio en el directorio.
El siguiente es el listado del ejemplo combinado:
#class auto
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"192.168.1.1"

#use "dcrtcp.lib"
#use "ftp_server.lib"
#use "http.lib"
#ximport
#ximport
#ximport
#ximport

"rabbit1.gif"
rabbit1_gif
"0.jpg"
inca_jpg
"oled.jpg"
oled_jpg
"http+ftp.html" index_html

#define ADMIN_GROUP
#define ANON_GROUP

0x0002
0x0001

SSPEC_MIMETABLE_START
SSPEC_MIME(".html", "text/html"),
SSPEC_MIME(".gif", "image/gif"),
SSPEC_MIME(".jpg", "image/jpeg"),
SSPEC_MIMETABLE_END
SSPEC_RESOURCETABLE_START
SSPEC_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

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();
// aplicacin
}
}

Desde el punto de vista del servidor FTP, lo que sucede es que aparecen todos los
archivos, pero slo tienen permisos de acceso aqullos que expresamente
declaramos, como se observa en este listado, con el login del usuario anonymous:
ftp> dir
227 Entering Passive Mode (192,168,1,54,4,0).
150 Opening ASCII mode data connection for /bin/ls
total 1
---------1 anonymous anon
288 Jan
---------1 anonymous anon
288 Jan
---------1 anonymous anon
39892 Jan
----r----1 anonymous admingroup
10992 Jan
-r--r--r-1 anonymous ol
2715 Jan
226 Transfer complete.
ftp>

1
1
1
1
1

1980
1980
1980
1980
1980

index.html
inca.jpg
oled.jpg
rabbit1.gif

La entrada con el nombre en blanco se debe a la inclusin del URL "/".

Autenticacin en cliente SMTP


Hemos visto en el libro introductorio la forma de enviar emails y la simpleza de uso
del cliente SMTP provisto en las bibliotecas de funciones. No obstante, antes de
DC9, dicho cliente careca de autenticacin, es decir, era SMTP puro. Ms all del
simple "POP before SMTP", en el que el usuario se autentica haciendo un login al
servidor POP antes de enviar el correo mediante SMTP (lo cual puede hacerse
simplemente con los ejemplos de POP que viramos en dicho libro); con DC9
aparece la posibilidad de autenticarse mediante algunas de las opciones que posee
ESMTP, es decir Enhanced SMTP.
Un servidor SMTP comienza el dilogo con un HELO. Un servidor ESMTP, lo
inicia con EHLO, a lo cual se suceden una serie de opciones de autenticacin. Si
entre ellas estn login o plain, aquellos que no posean DC9 o superior y no deseen
adquirirlo, debern parsear la respuesta del servidor, y simplemente iniciar un
dilogo de login o enviar un string conteniendo usuario y password, slo que
291

Networking con Rabbit


codificado en BASE647. Disponiendo de DC9, la configuracin de la autenticacin
se reduce a lo siguiente:
#define USE_SMTP_AUTH
#use dcrtcp.lib
#use smtp.lib
...
smtp_setauth("myusername", "mypassword");
...

Los mtodos soportados al momento de escribir este texto son LOGIN, PLAIN y
CRAM-MD5.

Direcciones IP fijas
Por lo general, en muchos ejemplos vemos que las direcciones IP son fijas y nicas
al momento de compilar. Esto puede ser muy til mientras estamos desarrollando la
aplicacin, o si la misma no tiene demasiados requisitos de parte del usuario final y
podemos asignarle una IP fija.
#define TCPCONFIG
#define USE_ETHERNET
#define MY_IP_ADDRESS
#define MY_NETMASK
#define MY_GATEWAY
#define MY_NAMESERVER

0
1
"192.168.1.54"
"255.255.255.0"
"192.168.1.1"
"200.42.0.108"

Recordemos que el gateway solamente es necesario si el equipo necesita conectarse


con otro fuera de su red local, es decir, debe dialogar con un router. El DNS
solamente es necesario si el equipo debe resolver nombres de hosts, lo cual puede
hacerse mediante un simple llamado a resolve(). Las dos primeras macros deben
definirse antes de incluir la biblioteca de funciones de TCP/IP, y definen la
utilizacin 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 direccin al momento de compilar, lo cual puede llegar a
ser una buena solucin en una produccin pequea o si los equipos se utilizan en
diferentes sitios, con redes homogneas no interconectadas entre s. La ventaja
fundamental es que al ser la direccin IP una constante, podemos referirnos a ella
como tal en textos y redirecciones en CGIs, como regularmente se muestra en los
ejemplos:
#define REDIRECTTO "http://" MY_IP_ADDRESS "/index.html"

7
8

El esquema BASE64 se define en las RFC (como todo lo que tiene que ver con los protocolos
relacionados a la Internet), particularmente las que definen MIME (Multipurpose Internet Mail
Extensions)
situacin que ocurra automticamente en versiones anteriores a DC9.

292

Direcciones IP fijas

Usando TCPCONFIG
Podemos modificar TCP_CONFIG.LIB para no tener que definir los parmetros de
red en cada proyecto:
#define _PRIMARY_STATIC_IP
#define _PRIMARY_NETMASK
#ifndef MY_NAMESERVER
#define MY_NAMESERVER
#endif
#ifndef MY_GATEWAY
#define MY_GATEWAY

"192.168.1.54"
"255.255.255.0"
"200.42.0.108"
"192.168.1.1"

En el programa principal, simplemente especificamos la configuracin de red de la


siguiente forma:
#define TCPCONFIG 1

Debe tenerse en cuenta que si bien la direccin IP sigue siendo fija, la macro
MY_IP_ADDRESS no tiene el valor que definimos en TCPCONFIG, y por ende no
es posible utilizar una macro para armar el URL de redireccin. En este caso,
debemos pedir la direccin IP mediante una llamada a funcin, como veremos al
analizar las direcciones IP dinmicas.

Direcciones IP dinmicas
Con el trmino "dinmicas" nos referimos a que no son fijas al momento de
compilar nuestra aplicacin, sino que pueden ser asignadas por un servidor DHCP, o
cambiadas 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 IP
son otorgadas por un servidor DHCP, deberemos especificar esta condicin
mediante:
#define TCPCONFIG 5

Al inicializar la red, la llamada a sock_init() vuelve inmediatamente despus de


inicializar el controlador de red, pero es altamente probable que an no tengamos
una direccin IP9. En este caso, se debe monitorear el estado de la interfaz en espera
de la resolucin del proceso de handshake mediante llamadas a ifpending(), antes de
levantar servidores o iniciar conexiones.
9

Antes de DC8.3, sock_init() no retornaba hasta obtener la direccin IP. Actualmente lo hace luego de
incializar el controlador y requiere sucesivas llamadas al stack para obtener una direccin IP.

293

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 direccin");

Fallback
En caso que no podamos obtener direccin por DHCP, es conveniente proveer una
opcin de fallback a una direccin esttica conocida. De este modo, siempre
disponemos de una direccin en donde encontrar el mdulo. Esto lo indicamos
mediante:
#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 lo
provee.
En el caso particular de DynDNS, existe una sample que muestra la obtencin de la
direccin y el manejo en samples\tcpip\http\dyndns.c. Para ZoneEdit, dado que la
operatoria es ms simple, se puede resolver el problema simplificando la sample de
DynDNS.

Cambio de direccin IP
Cuando la direccin IP debe ser cambiada en el campo, como es el caso de la
mayora de las aplicaciones, deberemos inicializar manualmente la interfaz en
cuestin, pasndole los parmetros bsicos. La funcin encargada de realizarlo es
ifconfig():
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 despus de llamar a sock_init(), y el progreso de


inicializacin 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 levantar
servidores o iniciar conexiones, mediante llamadas a ifpending():
while(ifpending(IF_ETH0)==IF_COMING_UP)
tcp_tick(NULL);

294

Direcciones IP dinmicas
Los parmetros de red pasados a la funcin ifconfig() son, como estudiramos al
comienzo de este captulo, valores en formato "de mquina", es decir, para ser
entendidos y manejados por el stack TCP/IP. La traduccin desde el formato que
todos conocemos la realizamos mediante la funcin inet_addr(). En el caso inverso,
para visualizar uno de los valores configurados, emplearemos la funcin 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 opcin de DHCP, la operatoria es muy similar:
ifconfig(IF_ETH0,
IFS_DOWN,
IFS_DHCP, 1,
IFS_UP,
IFS_END);

con fallback
Si incluimos una opcin de fallback, la incluimos en la llamada a funcin:
ifconfig(IF_ETH0,
IFS_DOWN,
IFS_DHCP, 1,
IFS_DHCP_FB_IPADDR, ipaddress,
IFS_NETMASK, src_mask,
IFS_UP,
IFS_END);

Sockets abiertos
Como estudiramos en el captulo sobre networking, si alguno de los parmetros
cambia, el socket ya no es vlido. Por este motivo, cualquier conexin abierta al
momento de cambiar la direccin IP deber ser cerrada y vuelta a abrir (si es
necesario).

295

Networking con Rabbit

PPP
Para el anlisis de PPP, podemos aplicar lo visto al analizar DHCP y cambio de
direccin IP, dado que la direccin es asignada por el servidor PPP, y puede cambiar
durante el funcionamiento, dado que la interfaz puede bajarse y subirse con otra
direccin diferente. El manejo en general es similar a lo visto en el apartado anterior,
inicindose la conexin mediante ifconfig().
Para referirnos a una interfaz en particular, lo hacemos en el primer parmetro de
llamada a ifconfig():
ifconfig(IF_PPP1,...

Tambin, si utilizamos solamente una, podemos definirla como


#define USE_PPP_SERIAL 0x02

y luego llamar a ifconfig() como


ifconfig(IF_DEFAULT, ...

La seleccin de interfaz es simple, IF_PPP0 corresponde al port serie A e


IF_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_SERIAL
0x20 (bit 5) corresponde al port F de un Rabbit 3000. En los casos en que
necesitemos utilizar el pinout alternativo (port D), lo indicamos mediante el
parmetro IFS_PPP_USEPORTD en la llamada a ifconfig().
Por lo general, el dispositivo que se conecta es un modem, al cual se controla
mediante comandos AT para que disque, y luego puede existir un proceso de login
en un servidor. Todo esto se maneja de forma automtica por una biblioteca de
funciones: chat.lib, que es automticamente incluida al usar PPP. Desde nuestro
punto de vista, deberemos definir todo el proceso de chat con el modem y/o servidor
en un string, el cual pasaremos (su puntero) a ifconfig(), mediante el parmetro
IFS_PPP_SENDEXPECT. El string referenciado tiene la forma "enviar recibir", y
debe seguir una serie de reglas que permiten desarrollar un simple lenguaje script
para automatizar la conexin. Por ejemplo, el siguiente string:
"ATH0 @ ATD1234567 #CONNECT"

significa que se enva ATH0 al modem, se espera un tiempo, luego se enva


ATD1234567 y se espera recibir el string CONNECT, sin prestar atencin a
maysculas o minsculas, 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 control
del modem. Para esto utilizamos los parmetros IFS_PPP_HANGUP e
IFS_PPP_MODEMESCAPE, respectivamente.
296

Direcciones IP dinmicas

Debido a que el mdulo PPP no se incluye con la distribucin standard de Dynamic


C sino que debe comprarse por separado, los ejemplos siguientes requieren de la
instalacin del mismo para poder compilarse y ejecutarse.
El siguiente listado es un ejemplo de establecimiento de una conexin PPP, luego
enviamos 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

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 es


conveniente emplear timeouts, particularmente en la desconexin, dado que no todos
los proveedores sealizan correctamente. Particularmente, en algunos casos la
asignacin de IP falla si el Rabbit comete el error de comentar que antes tena una IP
asignada, por lo que es recomendable "limpiar" la direccin IP forzndola a 0.0.0.0
antes 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 parmetros de ifconfig() en tcpconfig.lib, de modo de definir
la interfaz mediante una simple seleccin como:
#define TCPCONFIG 8

la cual realiza lo siguiente (extrado 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 parmetros habituales, puede ser muy til.

PPP sobre CSD


PPP sobre CSD significa utilizar PPP sobre una conexin CSD, es decir, sobre el
transporte de datos por conmutacin de circuitos de GSM, como viramos en el
captulo sobre networking. Su configuracin no difiere de PPP, ms all del nmero
a discar, si se utiliza uno de los abreviados como por ejemplo *638.

298

Direcciones IP dinmicas
#define DIALUP_SENDEXPECT "ATH0 @ ATD*638 #CONNECT"

Si el prestatario permite conexin con la red pblica telefnica (PSTN), podemos


utilizar cualquier ISP como si estuviramos en la PSTN.
#define DIALUP_SENDEXPECT "ATH0 @ ATD1234567 #CONNECT"

PPP sobre GPRS


PPP sobre GPRS significa utilizar PPP sobre el transporte de datos por
conmutacin de paquetes de GSM, como viramos en el captulo sobre networking.
Tcnicamente, desde el punto de vista del Rabbit, PPP sobre GPRS no difiere en
absoluto de PPP. La nica diferencia la tendremos en el script de discado, que
deber incluir el seteo para que el modem GSM pueda transportar PPP sobre GPRS.
Dicho seteo podr diferir de acuerdo al modem GSM utilizado.

Mdulos SIMCOM
Resumimos los pasos esenciales para poder conectarnos a la Internet utilizando
mdulos SIMCOM como modem GSM, mediante PPP sobre GPRS.
1. Seleccin del contexto: Se realiza mediante el comando AT+CGDCONT, segn
cul sea nuestro proveedor, deberemos ingresar el que ste defina:
AT+CGDCONT=1,"IP","<contexto definido por el proveedor>"
2. Activacin del contexto: mediante el comando AT+CGATT=1
3. Establecimiento de la conexin PPP: Deberemos iniciar una conexin mediante
un nmero ficticio, el cual es definido por el proveedor del servicio, y
generalmente es del tipo *99**#. La autenticacin generalmente corresponde a
PAP, y tanto usuario como password son definidos por el proveedor del servicio.
El ejemplo a continuacin 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 va Bluetooth (DUN) (tethering)


En este caso, la conexin con el mdem GPRS la hacemos va Bluetooth. El perfil
utilizado es DUN10 (Dial-Up Networking), y nos permite conectar un Rabbit a un
telfono celular GSM con Bluetooth, empleando un mdulo Bluetooth como por
ejemplo los de KCWirefree.

10 El captulo sobre conectividad incorpora ms informacin sobre Bluetooth, describiendo ste y otros
profiles.

299

Networking con Rabbit


Una vez ms, desde el punto de vista del Rabbit, PPP sobre Bluetooth no difiere en
absoluto de PPP. La nica diferencia la tendremos en el script de discado, que
deber incluir los seteos para que el mdulo Bluetooth pueda conectarse al telfono
celular y ste transportar PPP sobre GPRS. Dichos seteos podrn diferir de acuerdo
al mdulo Bluetooth y el telfono celular utilizados.

Mdulo Bluetooth

Gateway

red GSM

Serie Asinc

Bluetooth

Internet

GPRS

PPP
IP

KCWirefree
En el caso en particular que veremos a continuacin establecemos una
comunicacin con un mdulo KCWirefree y un telfono celular. El telfono ya tiene
ingresado el contexto correspondiente, ya sea mediante el comando AT+CGDCONT
(que viramos en el apartado anterior), o manualmente por pantalla, en su memoria.
El procedimiento de conexin es:
1. Definir parmetros de conexin (bonding): direccin remota y clave: AT+ZV
EnableBond <direccin> <clave>
2. Establecer la conexin DUN con el celular: AT+ZV DUNConnect <direccin>
3. Aceptar la conexin DUN en el celular, ingresando la clave
4. Discar el nmero ficticio, que activa PPP: ATD*99***<posicin>#
5. Establecer la conexin PPP
El procedimiento de desconexin es:
1. Terminar la conexin PPP
2. Interrumpir la conexin 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 conexin DUN, con la secuencia de escape especial (^#^$^% y dos
segundos sin actividad) y la orden de cortar: AT+ZV DUNDisconnect

300

Direcciones IP dinmicas
El ejemplo a continuacin corresponde a un telfono T637 (direccin Bluetooth
000E07191D58), con una prestataria argentina, cargada en la posicin nmero 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

Networking con Rabbit


printf("listo\n");
}
}

PPPoE
La seleccin de una interfaz PPPoE se realiza especificando
#define USE_PPPoE 1
#define USE_ETHERNET 1

y luego llamando a ifconfig() como


ifconfig(IF_DEFAULT, ...

Tambin 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 conexin PPPoE,


luego enviamos un ping y desconectamos. Si bien en este ejemplo utilizamos loops
infinitos, en aplicaciones reales es conveniente emplear timeouts, particularmente en
la desconexin, dado que no todos los proveedores sealizan correctamente.
#define TCPCONFIG 0
#define USE_PPPOE 1
#define USE_ETHERNET

#define PPPoE_NAME
#define PPPoE_PASSWORD

"myname"
"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

Direcciones IP dinmicas
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 direccin IP al momento de compilar el programa,
deberemos obtener este valor mediante una llamada a ifconfig(), convertirlo a un
texto utilizando inet_ntoa(), y luego armar el URL de redireccin mediante, por
ejemplo, 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 parmetros de red en funcionamiento


Cuando debamos cambiar los parmetros de red durante el funcionamiento del
equipo, deberemos tener en cuenta una serie de aspectos. Primero, si es necesario
que quien realiza el cambio reciba algn tipo de respuesta, deberemos elegir:
entregar la respuesta mediante CGI y luego, una vez terminada, realizar el
cambio solicitado.
usar una redireccin, y esperar un cierto tiempo antes de realizar el cambio, para
que la peticin de la pgina y su respuesta puedan realizarse.
Al realizar el cambio, debern ser cerrados todos los sockets, volviendo a abrirse
los necesarios. La operacin sobre los parmetros de red en tiempo de ejecucin la
haremos mediante la funcin ifconfig(), que ya hemos visto.

Ejemplo: cambio desde una pgina web


Podemos realizar un pequeo programa ejemplo, que provee la lectura de los
parmetros de red desde la flash, y una simple interfaz para poder cambiarlos desde
303

Networking con Rabbit


una pgina web, volviendo a guardarlos en flash. Como la primera vez que iniciamos
el programa no tenemos nada vlido en flash, y tal vez necesitemos en algn otro
momento (devolucin de un equipo desde campo) resetear los parmetros a un valor
seguro y conocido, proveemos un escape a dicha funcin (factory defaults) mediante
la lectura de un pulsador.
El desarrollo en general es muy similar a lo analizado en el captulo sobre
configuracin en campo; en este caso nos concentraremos ms en lo relacionado a
networking.

Configuracin
Definimos los parmetros por defecto, declaramos las variables necesarias,
agrupamos los parmetros dentro de una estructura, por conveniencia para salvarlos
en flash:
#define TCPCONFIG 0
#define USE_ETHERNET

#define
#define
#define
#define

"192.168.1.55"
"255.255.255.0"
"192.168.1.1"
"192.168.1.1"

DEFAULT_SOURCEIP
DEFAULT_NETMASK
DEFAULT_GATEWAY
DEFAULT_DNS

#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)

Aplicacin de los cambios


Lo primero que debemos determinar es si nuestra aplicacin soporta o no un reset.
Si es as, lo cual no es muy probable, podemos simplemente reiniciar el equipo
mediante una llamada a exit().
Si el producto final est destinado a un usuario final, muy probablemente no
podremos simplemente resetear el equipo. En este caso, deberemos sealizar el
cambio de configuracin para que el programa principal cierre todas sus conexiones
y proceda a reinicializar la interfaz, lo cual se realizar mediante una tarea, que
veremos ms adelante, la cual tambin realizar la grabacin en flash.

304

Direcciones IP dinmicas

Programa principal: inicializacin de la interfaz


El programa principal es el encargado de la inicializacin de la interfaz:
main()
{
if(!BitRdPortI(PBDR,2)) {
// factory defaults
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 {
// 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 iniciar
algunos 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 normal


funcionamiento del sistema (los procesos de networking se vern afectados de todos
modos), el loop principal ser el siguiente:
for(;;){
http_handler();
costate restartComms {
waitfor(DelaySec(5));
sprintf(REDIRECTTOK,"http://%s/ok.html",my_ip);

305

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));
}
}

Ntese que el costate restartComms ser inicializado por la funcin encargada de


guardar la nueva configuracin, corriendo solamente para realizar su funcin y luego
detenerse nuevamente. El waitfor nos provee de un tiempo razonable como para que
el servidor pueda entregar la respuesta.
Finalmente, recordemos que la capacidad de conexin debe definirse de forma tal
de poder tener suficientes sockets para la tarea a realizar y para el web server
adicional que realiza la configuracin (si ya no disponamos de un web server en
nuestra aplicacin, claro). Una vez ms, un mayor nmero de sockets implica una
mayor ocupacin y necesidad de memoria. Como viramos con anterioridad, la
macro correspondiente se define antes de incluir la biblioteca de funciones de
TCP/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 pueda
cambiar la direccin IP del equipo. En estos casos, lo conveniente es proteger el
acceso a dichas pginas mediante autenticacin. Como viramos algunos apartados
atrs, esto no es muy complicado. Primero definimos el grupo de usuarios
autorizados a alterar la configuracin, y luego en el directorio del server,
marcaremos aquellos archivos que queremos proteger. Finalmente, incorporamos al
programa principal las funciones necesarias para agregar un nuevo usuario, llamado
(vaya sorpresa) admin, cuyo password ser istreitor:
uid=sauth_adduser("admin", "istreitor", SERVER_HTTP); // crea
sauth_setusermask(uid, ADMIN_GROUP, NULL);
// agrega al grupo

En este ejemplo asumimos que el usuario se crea sin problemas, dado que es simple
y sencillo. Si esto es parte de un programa ms complejo, y se agregan usuarios de
forma dinmica, tal vez sea conveniente chequear el valor devuelto por la funcin
que crea al usuario: sauth_adduser(), antes de proseguir con la tarea de agregarlo al
grupo.

306

Direcciones IP dinmicas
A continuacin, daremos un ejemplo de una interfaz para cambio de parmetros de
red va web con RabbitWeb. Definimos dos grupos de usuarios: uno con permiso de
lectura, el otro con permiso de escritura. Quienes no pertenezcan a ninguno de los
grupos no pueden observar la configuracin. La interfaz presenta adems proteccin
contra acceso simultneo y deteccin de errores. Para ms detalles se recomienda
consultar los ejemplos en el captulo sobre configuracin en campo.
#define USE_RABBITWEB
#define TCPCONFIG 0
#define USE_ETHERNET
#define
#define
#define
#define

DEFAULT_SOURCEIP
DEFAULT_NETMASK
DEFAULT_GATEWAY
DEFAULT_DNS

1
1
"192.168.1.55"
"255.255.255.0"
"192.168.1.1"
"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"
#ximport "rabbit1.gif"

index_html
rabbit1_gif

307

Networking con Rabbit


#ximport "setup.zhtml"

setup_html

SSPEC_MIMETABLE_START
SSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),
SSPEC_MIME(".html", "text/html"),
SSPEC_MIME(".gif", "image/gif"),
SSPEC_MIMETABLE_END
SSPEC_RESOURCETABLE_START
SSPEC_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

Direcciones IP dinmicas
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">Configuracin 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>
<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

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>

Mltiples interfaces
El sistema soporta ms de una interfaz, por lo que es perfectamente factible
inicializarlas de a una por vez pasando el parmetro correcto en cada llamada a
ifconfig(), es decir, no es necesario desconectar la interfaz Ethernet para poder
utilizar PPP.
Como regla general, cada interfaz debera corresponder a una direccin de red
diferente. El routing cuando hay varias interfaces se hace complejo, y es posible que
las respuestas tomen distinto camino que las preguntas, en cuyo caso tal vez deba
ponerse alguna ruta esttica para forzar un camino o garantizar la comunicacin. Un
adecuado planeamiento de la red, teniendo en cuenta lo estudiado en el captulo
sobre networking permitir anticipar cualquier posible inconveniente.
310

Mltiples interfaces
Los servidores pueden tener una macro en la que especifican si atienden pedidos de
una interfaz en particular o en todas. Por ejemplo, FTP atiende slo en la interfaz por
defecto mientras que HTTP lo hace en cualquiera. Esto se modifica buscando y
alterando la macro correspondiente en la biblioteca de funciones del servidor
#define HTTP_IFACE
IF_ANY
#define FTP_INTERFACE IF_DEFAULT

Resolucin de problemas (troubleshooting)


Cuando tenemos problemas de conexin o funcionamiento, como comentramos en
el captulo sobre networking, es muy difcil tratar de determinar una causa sin poder
ver lo que pasa en la red. Si bien en otros campos de trabajo muchas veces es posible
"tocar de odo", cuando hay problemas de networking es necesario utilizar alguna
herramienta de visualizacin o anlisis.

Herramientas
Dynamic C incluye algo similar a un analizador de protocolos dentro de sus
bibliotecas de funciones, y algunas utilidades en tiempo de ejecucin para verificar
conectividad, las cuales veremos a continuacin.

Link
Para detectar el estado del link, es decir, la conexin o no del cable de red,
podemos utilizar una funcin de Dynamic C, sin necesidad de interrogar
directamente 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 evitar
demorarnos en la inicializacin 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 que
est 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

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
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 aplicacin
Si necesitamos verificar que tenemos conectividad con quien deseamos
conectarnos, podemos aprovechar las particularidades del stack. En todo stack
TCP/IP, es posible determinar la conectividad a nivel-3 (networking) mediante el
envo de ICMP Echo Request, mejor conocido como ping, y luego detectar la
recepcin de ICMP Echo Reply.
En Dynamic C, la funcin que enva un ping es justamente:
_ping(direccin,nmero de secuencia)

la direccin es en el formato de 32bits que utilizan todas las funciones, por lo que
debemos convertirla mediante las funciones ya conocidas, o utilizar resolve():
_ping(resolve("www.yahoo.com"),1);

312

Resolucin 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 funcin devuelve en tmp_seq el tiempo transcurrido, -1 (0xffffffff) si no se


recibi una respuesta luego de un largo rato (timeout). Para ms informacin
podemos consultar como siempre en las samples, particularmente una de ellas:
samples\tcpip\ping.c. De todos modos, hemos visto ejemplos de uso al analizar PPP
y PPPoE, en este mismo captulo.

Transporte y aplicacin
Modo debug
Si deseamos tener un nivel de conocimiento ms profundo de lo que est sucediendo
en 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 conexin en una ventana stdio en el entorno de
Dynamic C.
#define HTTP_VERBOSE

De igual modo, si deseamos ejecutar paso a paso dentro de las libraries, necesitamos
que sean compiladas para debugging, lo cual generalmente no est habilitado dado
que 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 vlido para las dems aplicaciones como FTP, POP, etc.

En la aplicacin
Un ping chequea solamente conectividad a nivel-3, no la voluntad del servidor de
atender conexiones o datagramas a nivel-4, es decir, transporte (TCP, UDP).
Si lo que necesitamos es detectar que la conexin (TCP) est establecida, podemos
hacerlo mediante la funcin tcp_tick(&socket), como analizramos en el apartado
correspondiente al principio de este captulo. Es decir, la misma funcin que
mantiene activo el stack TCP/IP: tcp_tick(), si se la llama con un puntero a un socket
como parmetro, devuelve el estado de la conexin. Dado que, como dijramos, la
deteccin de una prdida de conexin puede llevar bastante tiempo, debido al
esquema de retransmisiones y timeouts de TCP; o tal vez tenemos tiempos de
313

Networking con Rabbit


inactividad sin transferencia de informacin pero necesitamos conocer el estado de
la conexin, podemos emplear keepalives, como describiramos tambin en dicha
seccin.
Si en cambio estamos trabajando con UDP, debemos implementar algn esquema
de pregunta-respuesta que nos permita realizar la deteccin, como un anexo a
nuestra aplicacin.
Esto mismo tambin es posible para el caso de una conexin TCP: una
comunicacin UDP adicional puede darnos una idea de la conectividad y la voluntad
del servidor de atendernos, aunque lamentablemente no nos dice nada sobre el
socket TCP que nos interesa. Si bien ambos pueden estar muy relacionados, si es el
proceso que atiende el socket TCP en el servidor el que se ha ido de vacaciones, la
nica forma de darnos cuenta es mediante informacin dentro de ese socket, lo cual
podemos implementar como parte de nuestra aplicacin.

Wi-Fi
No slo a partir del procesador R5000, que incorpora la electrnica necesaria, sino
ya desde el R4000, algunos mdulos Rabbit vienen provistos de hardware para
conectarse a una red Wi-Fi. Los mdulos basados en R2000 y R3000 slo poseen
Ethernet.

Mdulos con capacidad Wi-Fi


La adquisicin de Rabbit por parte de Digi International hace que esta ltima
incorpore el hardware de Wi-Fi diseado para sus mdulos en los mdulos Rabbit.
La ventaja fundamental de este esquema es que la compaa es duea del hardware y
no depende de terceros para el abastecimiento, garantizando una cierta estabilidad en
un entorno de cambios vertiginosos. El Rabbit 5000, adems, incorpora soporte para
Wi-Fi dentro mismo del chip, incluyendo WPA2 y autenticacin de tipo Enterprise.
La configuracin 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 los
vericuetos de la inicializacin.
#define TCPCONFIG 1
#define _PRIMARY_STATIC_IP
#define _PRIMARY_NETMASK

"192.168.1.54"
"255.255.255.0"

El resto de los parmetros (name server y gateway) los seguiremos manejando


como hasta ahora.

Configuracin
En el programa principal, simplemente especificamos la configuracin de red de la
siguiente forma:
314

Wi-Fi

#define USE_WIFI 1
#define IFC_WIFI_SSID

"Cika"

Conexin abierta
Si no tenemos restricciones en nuestro access point, cosa poco probable en una red
operativa pero deseable en un entorno de desarrollo, simplemente especificamos la
configuracin de la siguiente forma:
#define IFC_WIFI_ENCRYPTION

IFPARAM_WIFI_ENCR_NONE

Podemos adems especificar que no usamos autenticacin, pero tcp_config.lib lo


toma por defecto:
#define IFC_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_OPEN

En el programa principal, simplemente especificamos la configuracin de red de la


siguiente forma:
#define IFC_WIFI_MODE
#define IFC_WIFI_REGION

IFPARAM_WIFI_INFRASTRUCTURE
IFPARAM_WIFI_REGION_AMERICAS

Conexin protegida
Obviamente existen muchas combinaciones posibles, slo analizaremos las ms
comunes. La informacin correspondiente se encuentra en la misma biblioteca de
funciones tcp_config.lib.
Para conectarnos a un access point con, por ejemplo, WPA/TKIP, especificamos la
necesidad de compilar el cdigo de soporte de WPA:
#define WIFI_USE_WPA

y el cifrado:
#define IFC_WIFI_ENCRYPTION

IFPARAM_WIFI_ENCR_TKIP

A continuacin debemos ingresar la clave. Podemos hacerlo en forma de frase


ASCII:
#define IFC_WIFI_WPA_PSK_PASSPHRASE "noteladigo"

o directamente en hexadecimal:
#define IFC_WIFI_WPA_PSK_HEXSTR \
"8125BB6E6DE7937997D978A17932DD9ABC4C4F99E84FE95F8F5989964D8AC970"

315

Networking con Rabbit


El proveer la clave en ASCII fuerza al mdulo a calcular la clave en hexa, lo cual
requiere un tiempo considerable, medido en decenas de segundos, y varias de ellas.
Es comn observar tiempos de 30 segundos o ms. Afortunadamente disponemos de
una opcin que nos permite tener informacin del proceso, particularmente mostrar
en pantalla la clave en hexadecimal una vez convertida, para poder luego cargarla en
el programa de esta forma y no tener que esperar cada vez que reiniciamos el
mdulo:
#define WIFI_VERBOSE_PASSPHRASE

Para conectarnos a un access point con, por ejemplo, WPA2/CCMP, especificamos


la necesidad de compilar el cdigo 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 idntico a lo visto para WPA.


La configuracin para seguridad en modo enterprise es algo ms compleja, y no la
desarrollaremos aqu. Se sugiere al lector la observacin de tcp_config.lib y las
samples.

Inicio
Al inicializar la red, la llamada a sock_init() vuelve inmediatamente, sin que exista
conexin con el access point. Se debe monitorear el estado de la interfaz mediante
llamadas a ifpending(), antes de levantar servidores o iniciar conexiones.
sock_init();
while(ifpending(IF_WIFI0)==IF_COMING_UP)
tcp_tick(NULL);

A los fines prcticos, disponemos de una funcin que espera por nosotros y si se le
pasa como parmetro un valor distinto de cero, devuelve informacin de conexin.
Dado que esta funcin ejecuta exit() en caso de errores, su uso est limitado a
desarrollo y depuracin.
sock_init_or_exit(1);

Cambio de parmetros en campo


Cuando necesitamos cambiar algn parmetro de operacin como el SSID o la
direccin IP en campo, deberemos reiniciar manualmente la interfaz. Como
hiciramos para Ethernet, la funcin encargada de realizarlo es ifconfig(), pero esta
vez debemos asegurarnos que la interfaz est desactivada antes de modificar:
316

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 conexin abierta, los parmetros
respectivos para cifrado y autenticacin sern:
IFS_WIFI_ENCRYPTION, IFPARAM_WIFI_ENCR_NONE
IFS_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_OPEN

Para una conexin WPA/TKIP, los parmetros sern:


IFS_WIFI_ENCRYPTION, IFPARAM_WIFI_ENCR_TKIP
IFS_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_WPA_PSK

Finalmente, para una conexin WPA2/CCMP, los parmetros sern:


IFS_WIFI_ENCRYPTION, IFPARAM_WIFI_ENCR_CCMP
IFS_WIFI_AUTHENTICATION, IFPARAM_WIFI_AUTH_WPA_PSK

De aqu se desprende que podemos fcilmente incorporar estos valores dentro de


sendos arrays y obtenerlos mediante un ndice que indique el modo deseado, por
ejemplo, 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

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 modificaciones


pertinentes al programa de ejemplo para cambiar la direccin IP desde una pgina
web y permitir adems seteo de parmetros de Wi-Fi. Una pequea gran ayuda se
encuentra en la pgina web de este libro11.

Mdulos sin Wi-Fi incorporado


La forma ms simple y sencilla de conectar un mdulo Rabbit genrico va wi-fi es
hacerlo mediante un access point con capacidad de wireless bridging. De esta forma,
el mdulo Rabbit conecta su port Ethernet al access point, y ste se conecta de forma
inalmbrica con el otro access point. Desde el punto de vista del Rabbit, es como si
estuviera conectado directamente a la Ethernet remota, no es necesario ningn tipo
de conexin ni seteo especial, solamente debe tenerse en cuenta que los access
points a utilizar tengan capacidad de wireless bridging y puedan hacerlo entre s.
Access Point
adicional

Access Point
de la instalacion

Rabbit
Ethernet

Otros equipos
wi-fi

11 http://www.ldir.com.ar/libros/camino/

318

RabbitWeb

Introduccin
RabbitWeb es una extensin a Dynamic C, a partir de la versin 8.50. Su
funcionamiento est totalmente integrado al servidor HTTP y elimina la necesidad
de escribir CGIs en C, permitiendo al developer una mayor libertad en el diseo de
las pginas 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 pgina.
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 configuracin o para mostrar una determinada informacin.
definir los rangos de validez de las variables a modificar, lo cual probablemente
ya se haya hecho como parte de la definicin de la aplicacin.
definir los grupos de usuarios que van a acceder (o no) a dicha informacin.
definir los mtodos de autenticacin a emplear para cada variable.
identificar las acciones a realizar cuando existen modificaciones en algunas
variables.
Una vez hecho esto, la presentacin de la informacin se realiza mediante el
lenguaje script, en combinacin 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 de


forma ms fcil.

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

RabbitWeb

Los nombres empleados se agregan al espacio de nombres conocidos por Dynamic


C, por ejemplo, para agregar un usuario al grupo ADMIN_GROUP procedemos
como si hubiramos definido el grupo manualmente, de igual modo a como hicimos
en los ejemplos de los captulos sobre configuracin en campo y networking con
Rabbit:
sauth_setusermask(uid, ADMIN_GROUP, NULL);

Variables
Las variables a ser accedidas va web deben registrarse, lo cual se realiza aplicando
la 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 tambin una funcin de evaluacin para chequear si
el valor ingresado (al modificarla va web) est dentro del rango permitido, por
ejemplo:
#web variable (($variable > -10) && ($variable < 10)) \
groups=ADMIN_GROUP(rw),MON_GROUP(ro)

Segn 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 grupo
MON_GROUP tiene slo permiso de lectura. Cualesquiera otros grupos, no pueden
acceder a esta variable. En caso que se quiera dar un determinado tipo de acceso a
todos 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 al
valor que se intenta ingresar, es decir, el que enva el usuario desde su navegador. Si
omitimos este signo, entonces nos estaremos refiriendo al valor que tiene la variable
para el resto del sistema, es decir, el ltimo modificado correctamente:
#web variable ($variable > variable)

De igual modo, se puede especificar el tipo de autenticacin deseado:


#web variable auth=basic,digest groups=ADMIN_GROUP(rw),all(ro)

Las variables a registrar pueden ser de diversos tipos, RabbitWeb interpreta el tipo
de la variable, por lo que es posible registrar una estructura y referirse luego a cada
uno de sus elementos tanto en la funcin de chequeo como en el script para su
presentacin, por ejemplo:

320

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 una
funcin especial del lenguaje script (error($variable)). Los textos los definimos al
ingresar los rangos de validez de la variable:
#web config (($config.data1 <= 9999)?1:WEB_ERROR("muy alto"))

Tambin es posible registrar un array, y definir la funcin 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, es
decir, RabbitWeb comprobar que desde vector[0] a vector[9] estn comprendidos
entre -9 y 9 (ambos inclusive) cuando se los modifica.
Los strings se registran por el nombre del array, y el sistema chequea
automticamente de no exceder la longitud al actualizar:
char somestring[25];
#web somestring

Variables con listas de valores


Si deseamos incluir selectores en nuestra pgina, disponemos de una funcin que
nos permite asignar valores posibles a tomar por un entero, asociados a los textos del
selector. La lista se auto-enumera, y puede ser modificada a voluntad, de forma
similar a enum{} en C (y Dynamic C):
int lista;
#web lista select( "uno" = 1, "dos", "tres", "cuatro" )

Tambin podemos definir botones de seleccin (radio buttons) y casillas para


opciones seleccionables (checkboxes). Los botones se definen de igual forma que un
selector, 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 considerarlo
habilitado, sino que interprete cualquier valor diferente de 0 como un "s"; de todos
modos puede definirse un rango de validez si se desea.

Deteccin de cambios
Cuando en el navegador se enva un formulario (submit), ste inicia un
requerimiento de una pgina al servidor HTTP del Rabbit indicando una operacin

321

RabbitWeb
POST, y enva los datos. RabbitWeb entonces chequea si hubo cambios y valida
acorde a las funciones especificadas, actualizando las variables correspondientes. Si
el cambio de una de estas variables implica que se debe realizar una actividad
adicional, como por ejemplo el caso de un port serie (cerrar y abrir), una direccin
IP (convertirla, bajar y subir la interfaz), o simplemente salvar la configuracin en
flash, podemos indicarlo mediante la directiva #web_update:
void ejecutame(void)
{
// tarea relacionada con una modificacin de variable
}
#web_update variable ejecutame

La funcin ejecutame() se ejecuta entonces cuando RabbitWeb detecta una


modificacin vlida sobre variable.

Lenguaje script
De forma similar a como SHTML incluye tags que son interpretados por el servidor
al momento de servir la pgina (SSI: Server Side Includes), ZHTML permite
disponer de un simple y poderoso lenguaje script dentro de la pgina, 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 lnea, se coloca el script; por ejemplo, el
cdigo a continuacin 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 programa
principal, como viramos en el apartado anterior. Por claridad, utilizaremos para
nuestros ejemplos los mismos nombres de variables que definiramos en el punto
anterior.
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 intenta
introducir al enviar la pgina; el signo @ indica que nos referimos al valor que tiene
la variable en el programa principal.
La sintaxis es similar a C y la gramtica es simple, permitiendo ejecucin
condicional mediante el uso de la clusula if(), aunque sin su else complementario, lo
que se resuelve invirtiendo la pregunta (la cual debe realizarse nuevamente)
mediante el operador de negacin: ! (signo de admiracin):
<?z if($variable==1) { ?>
accin
<?z } ?>

322

Lenguaje script
o bien
<?z if($variable==1) { ?>
accin por 'then'
<?z } ?>
<?z if($variable!=1) { ?>
accin por 'else'
<?z } ?>

La accin encerrada entre llaves puede ser cdigo HTML, el cual forma pginas
diferentes dependiendo de lo que considere el script, o parte del script, incluyendo
ms condicionales anidadas u otras acciones. Debido a que esto implica un consumo
de memoria, existe un lmite en la cantidad de veces que se puede anidar, que se
define por programa mediante la macro RWEB_ZHTML_MAXBLOCKS, y por
defecto es 4. Esto aplica tambin e incluye1 bloques iterativos, representados
mediante la clusula for( ; ; ). Las variables utilizadas dentro de un for son vlidas en
el entorno de ejecucin del script, y no requieren ser declaradas en el programa en
Dynamic C:
<?z for($A=0; $A<10;$A++) { ?>
accin
<?z } ?>

Funciones
El resto de la operacin se realiza mediante funciones que permiten resolver
situaciones comunes de un modo simple y efectivo.
Para conocer la cantidad de elementos de un array o selector se utiliza la funcin
count(), por ejemplo:
<?z for ($A = 0; $A < count($lista); $A++){ ?>
accin
<?z } ?>

En un array, debemos especificar adems a qu dimensin nos referimos, por


ejemplo, un array unidimensional (vector):
<?z for ($A = 0; $A < count($vector,0); $A++){ ?>
accin para vector[$A]
<?z } ?>

Para una matriz, el segundo parmetro ser 0 1, segn a qu dimensin nos


queramos referir.
Para conocer si la pgina est siendo mostrada como resultado de una peticin
(GET) o una introduccin de valores (POST):
<?z if(update()) { ?>

La cantidad total de bloques iterativos y condicionales que puede ser anidada es


RWEB_ZHTML_MAXBLOCKS

323

RabbitWeb
se trata de un POST
<?z } ?>
<?z if(!update()) { ?>
se trata de un GET
<?z } ?>

Para saber si hay algn error en la informacin 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 son
caracteres no admitidos dentro del nombre de una variable en HTTP. Para resolver
esta situacin, se incorpora una funcin 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 manualmente


con la funcin print_opt(). La funcin selected() permite determinar si la opcin
corriente es la que corresponde al valor actual de la variable en el programa
principal:
<?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

Lenguaje script

Botones
Para generar una lista de botones de seleccin, utilizamos la funcin print_opt(). La
funcin selected() permite determinar si la opcin corriente es la que corresponde al
valor 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 captulo sobre configuracin en campo, encontramos una serie de ejemplos de


uso en situaciones reales. Para una descripcin detallada y precisa de la gramtica y
cada una de las funciones y extensiones, se sugiere consultar el manual de
RabbitWeb.

325

Desarrollo de aplicaciones

Introduccin
Sin duda el desarrollo de una aplicacin es donde el mundo real y la teora se
confrontan. No porque uno y otro pertenezcan a mundos distintos, o cada uno tenga
una parte de la verdad, sino que muchas veces no conocemos todo el escenario y se
nos escapan algunas cosas al momento terico, y forzosamente debemos aprenderlas
a los golpes en la prctica. No significa esto una falencia de la teora en s, sino tal
vez que la parte que conocamos de ella no fue suficiente.
Lo que intentaremos hacer en este captulo es plantear una aplicacin, hacer un
anlisis terico tratando de anticipar los problemas con los que nos encontraramos,
y luego llevarla a la prctica, aplicando los conceptos elaborados en dicho anlisis.

Transporte de comunicaciones serie


asincrnicas mediante TCP/IP
La "conversin serie a Ethernet" es un trmino seguramente acuado por algn
genio de marketing, incorrecto desde un punto de vista tcnico. Peras son peras, y
manzanas son manzanas. Aunque a veces las peras muy verdes no parezcan peras, no
por ello son manzanas. En primer lugar, no hay ningn tipo de conversin sino un
transporte. En segundo lugar, tampoco es por Ethernet, sino por TCP/IP. Hecha la
catarsis correspondiente, vamos a analizar el tema.

Introduccin
Suele entenderse por "conversin serie a Ethernet" el transporte de un flujo
(stream) serie asincrnico de datos mediante una conexin TCP/IP, que
generalmente a la salida del dispositivo que realiza la tarea de meter los caracteres
obtenidos dentro de un segmento TCP o un datagrama UDP, es transportado por
Ethernet. La forma de realizarlo no es nica, y si bien es algo relativamente simple a
primera vista, dado que las malas implementaciones quedan como muchas veces
enmascaradas por la abundancia de recursos, en cuanto se lo analiza se descubre que
no es tan simple como pareca. Y no tendra por qu ser simple! Desde los viejos
tiempos del X.25, un grupo de genios de lo que por entonces se llamaba CCITT 1, y
1

Comit Consultatif Internationale de Tlgraphie et Tlphonie, es decir, Comit Consultor


Internacional de Telegrafa y Telefona, pero en francs.

327

Desarrollo de aplicaciones
hoy es la ITU-T2, dedicaron varias pginas a la definicin de X.28, recomendacin
que estableca la forma en que deba operar un PAD (Packet
Assembler/Disassembler) para poder transferir datos provenientes de una terminal
asincrnica a travs de una red de conmutacin de paquetes como X.25 y viceversa.
Si bien han pasado las dcadas, y X.25 no tiene nada que ver con TCP/IP ni
Ethernet, ambas son en esencia redes de conmutacin de paquetes, y los problemas
son esencialmente los mismos. La nica diferencia es que en una conexin de 2400
bps con 10 usuarios, una implementacin errnea se va a notar ms que en una red
Ethernet a 100 Mbps con switches, razn por la cual pueden llegar a proliferar
algunas aberraciones.
Lo que quiero decir con todo esto, es que la solucin genrica es analizar la
recomendacin X.28 e implementar un PAD, aunque probablemente lo ms eficiente
sea analizar el stream serie que se intenta transferir, y obrar en consecuencia. A los
fines de defender esta mentalidad y en pro de lo acadmico, haremos un somero
anlisis para corroborar nuestra aseveracin y descartar posturas simplistas.

Anlisis
Lo primero que debemos analizar es la total y absoluta diferencia entre ambos
mundos. 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, mientras
que en una red Ethernet viajan tramas de una longitud acotada. Cuando un caracter
serie 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 se
tiene 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 comunicacin
UDP (para ser bondadosos) tenemos un overhead de 28 bytes 3, y al mandarlo va
Ethernet tenemos un mnimo de 64 bytes por trama, estamos en realidad enviando 84
bytes4 por cada caracter, es decir que un enlace de 9600 bps ocupa un ancho de
banda de 645120 bps5 en la Ethernet, es decir, 67,2 veces mayor; transmitiendo un
total de 960 tramas por segundo. Si tomamos en cuenta que un sistema de acceso
2
3
4

International Telecommunications Union, Telecommunications Standardization Sector, es decir, el


sector encargado de la estandarizacin en las telecomunicaciones de la Unin Internacional de
Telecomunicaciones.
20 bytes de IP, 8 bytes de UDP.
8 bytes de prembulo, 14 de header y 4 de CRC, suman 26 bytes; ms un mnimo de 46 bytes de
datos pues la trama ms corta debe ser de 64 bytes ms el prembulo, un total de 72 bytes. Si
consideramos adems que debemos incluir el espacio de silencio entre tramas que es de 9,6us, esto a
10Mbps corresponde a 12 bytes ms, por un total de 84 bytes.
A 9600 bps, 8 bits por caracter, 1 de stop; son 960 caracteres por segundo. Cada uno de esos
caracteres se enva como una trama de 84 bytes, es decir 672 bits. Esos 960 caracteres por segundo
resultan en 960 tramas de 672 bits, es decir 645120bps

328

Transporte de comunicaciones serie asincrnicas mediante TCP/IP


mltiple con colisiones como es Ethernet funciona bien con una ocupacin de hasta
el 30%, la cantidad mxima de tramas de este tipo que se puede transmitir es de
bits
10000000
seg
tramas
0,3*
 4464
.
seg
bytes
bits
84
*8
trama
byte
Una red Ethernet tradicional se satura con unas cuatro o cinco de estas conexiones,
sin dejar comunicar a ningn otro nodo. No creo que podamos hacernos amigos del
administrador de la red...
Aterrorizados por el hecho de ser odiados por gente tan amistosa como los
administradores de redes, decidimos tomar el camino inverso, y almacenar bytes
hasta llenar una trama completa y entonces transmitirla. Dado que el tamao mximo
para los datos en la trama es de 1500 bytes, podemos armar un datagrama UDP de
hasta 1472 caracteres. El tiempo requerido para juntar estos 1472 caracteres, si
vienen a 9600 bps y son caracteres de 8-bits sin paridad y slo un bit de stop, es de
bits
82
caracter
1,533 seg .
1472caracteres*
bits
9600
seg
Es decir, que del otro extremo no van a tener noticias nuestras hasta que juntemos
todo un gran paquete, lo que no ocurre hasta por lo menos 1,533 segundos. Si lo que
viene por la interfaz serie es un protocolo que tiene un tiempo mximo para esperar
respuestas, lo rompemos con nuestra demora. Si es un protocolo de polling que
manda un paquete y espera una respuesta (digamos Modbus6 ASCII), lo rompemos
porque el paquete, a menos que sea de exactamente 1472 bytes, nunca sale 7. Si es un
protocolo como Modbus RTU, que depende de un tiempo entre paquetes para
detectar 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 ambos
extremos. De esta forma reducimos la latencia y la ocupacin de Ethernet. Sin
embargo, si bien estamos en la direccin correcta, cul es esa cantidad de bytes?
Como analizamos en el apartado sobre comunicacin serie asincrnica, en el
captulo sobre conectividad, existen protocolos que delimitan sus mensajes con
caracteres nicos, que no se repiten dentro del mensaje. Y si detectamos uno de
estos caracteres y enviamos inmediatamente una trama conteniendo el mensaje? Pues
mientras que dicho mensaje no supere el tamao mximo de trama, perfecto! Y si
lo supera? En este caso, al llenarse nuestro buffer, emitiremos un datagrama, y luego
al recibir el caracter final enviaremos el resto. Mientras que en el otro extremo se
6
7

Protocolo originario de la firma Modicon, hoy standard en el control de PLCs y dems dispositivos
de ambiente industrial.
En realidad, si el paquete es ms grande sale truncado; y la otra parte, al igual que un paquete ms
pequeo, sale cuando la(s) retransmisin(es), o paquete(s) posterior(es), llena(n) el buffer.

329

Desarrollo de aplicaciones
almacene todo y se enve el paquete completo, no habr inconvenientes (lo cual
puede 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 que
podemos hacer es disponer de un timer, el cual reiniciaremos cada vez que recibimos
un caracter. Cuando transcurra un tiempo mayor sin recibir caracteres, el timer
expirar, indicndonos la necesidad de transferir un mensaje. Mantendremos adems
la salvedad de poder enviar una trama si se llena el buffer antes de que expire el
timer.
Felicidades!, a grandes rasgos acabamos de inventar X.28, slo que unos veinte
aos ms tarde...
Un PAD X.28 tiene un set de parmetros (X.3) que definen las particularidades que
acabamos de analizar (y otras muchas ms), de modo de poder optimizar este tipo de
comunicacin. Si bien se trata de un dinosaurio de la era X.25, la situacin es
equivalente.
En los ejemplos hemos usado UDP por una simple razn: no tiene "inteligencia
propia" y es posible hacer este tipo de anlisis. Adems, no debemos preocuparnos
por mantener una conexin y su latencia es baja. Si se pierde la informacin en el
camino, todo depende de cmo reacciona el protocolo serie. Si est esperando una
conexin por un cable, es poco probable que le agrade mucho el que se pierda parte
de la informacin, o que el segundo mensaje llegue antes que el primero. Lo cierto
es que la mayora de estos sistemas utilizan TCP en vez de UDP, de modo que todo
lo que entra tenga certeza de salir por el otro lado en el mismo orden en que se
enva, o al menos poder detectar el establecimiento y la prdida de la comunicacin
sin tener que armar un protocolo para ello. En este caso, hay muchos factores ms
que intervienen en el clculo de ancho de banda, los cuales escapan al tipo de
anlisis posible en un texto de estas caractersticas8.

Desarrollo
Hecha la diseccin terica correspondiente, estamos en condiciones de
avalanzarnos sobre el cdigo. Como siempre, debemos ir donde estn los ejemplos,
y no es otro lugar que el directorio Samples de la instalacin de Dynamic C. El
primer ejemplo que debemos analizar es Samples\Tcpip\serialexa.c, el cual aplica el
concepto de buffer y timeout para la comunicacin, y es lo ms utilizado como
genrico. Otro ejemplo a considerar es Samples\Tcpip\Telnet\vserial.c, el cual
aprovecha la existencia de una biblioteca de funciones: vserial.lib, encargada de
transportar 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 captulo sobre
networking.

330

Transporte de comunicaciones serie asincrnicas mediante TCP/IP


por razones de tiempo, pero el ejemplo es sumamente claro y se observa que la
operatoria es realmente simple.
Si nuestro protocolo no tolera las demoras introducidas al utilizar el mtodo de
timeout que propone serialexa.c, podemos escribir un pequeo handler para detectar
finalizacin de mensaje; en cuyo caso procedemos a enviarlo al otro extremo. De
igual modo, procesamos lo recibido desde el extremo remoto y lo entregamos por la
interfaz serie.
El ejemplo que proponemos a continuacin a manera de desarrollo, se basa en lo
que fuimos analizando en el captulo sobre networking con Rabbit. Desarrollaremos
un handler para la aplicacin en s, el cual ser llamado desde el handler que maneja
la conexin TCP. En este ejemplo en particular, elegimos un protocolo serie simple:
NMEA0183, que es el utilizado por la gran mayora de los mdulos GPS para
entregar la informacin. Este protocolo en particular resulta muy fcil de sincronizar
con una mquina de estados. La idea es tomar NMEA0183 por el port serie y
transmitirlo a otro mdulo Rabbit en cualquier lugar (en el mismo sera muy obvio y
aburrido), y a la vez recibir la informacin del lugar remoto.
TCP/IP

GPS
Rabbit
PC/PDA

GPS
Rabbit
PC/PDA

Implementaremos entonces dos handlers, uno para cada sentido; aunque el que
toma la informacin del segmento TCP y la enva por el puerto serie es sumamente
simple, dado que el procesamiento ya fue hecho del lado remoto.
Complementariamente, el que toma la informacin del puerto serie, deber detectar
inicio y fin del mensaje, y enviar un segmento al extremo remoto. Siguiendo el
mismo espritu, desarrollaremos luego los handlers para mantenimiento de la
conexin TCP; uno local, cliente, que iniciar la conexin, y otro remoto, servidor,
que esperar que se lo llame. En el CD que acompaa a esta edicin, se encuentra la
totalidad del cdigo; un archivo con el cliente, y otro con el servidor, que al ser
compilado para un RCM2200 utiliza el pinout alternativo del port B.
En cuanto al cdigo en s, el pasaje de parmetros 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

Desarrollo de aplicaciones
int state;
tcp_Socket socket;
State serial;
} ConnState;

Las constantes las hemos definido en maysculas, para mayor claridad.

Serie a TCP
Aprovechando la simpleza del protocolo, descartamos todo lo que vemos hasta
encontrar un caracter '$', momento en el cual comenzamos a almacenar caracteres
hasta encontrar el fin de lnea. En este caso utilizamos avance de lnea (line feed),
que funciona perfectamente con los mdulos GPS que tenamos disponibles para
hacer la prueba; de encontrarse algn rebelde, se puede reemplazar la comparacin
por retorno de carro o ambos. Una vez identificado el mensaje, lo enviamos al
extremo 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); // cunto mando ?
if (bytes_written < 0)
// hubo un error
return(-1);
if(bytes_written!=cstate->offset) { // memcpy tolera written=0
memcpy(cstate->buffer,cstate->buffer+bytes_written,
cstate->offset-bytes_written);
cstate->offset -= bytes_written; // compenso lo que
}
// no pude mandar
else
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;
// flush
if(data=='$')
// comienzo
cstate->state=SER_DATA;
else return(0);
// descarta
break;
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;
// guarda
if(cstate->offset<(BUFSIZE-1))
// incrementa ptr
cstate->offset++;
else
cstate->state=SER_IDLE;
// muy largo, ignora

332

Transporte de comunicaciones serie asincrnicas mediante TCP/IP


}
}
return(0);
}

TCP a serie
Esto es ms que simple, dado que todo el trabajo lo hizo el otro extremo. Si
tenemos espacio en el buffer serie, simplemente tomamos lo que haya disponible en
el 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){ // cunto 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 conexin
Tanto la apertura como la espera las hacemos con un esquema muy similar a lo
visto en el captulo sobre networking con Rabbit, con una simple mquina de
estados.

Cliente
void conn_handler(ConnState *state)
{
auto tcp_Socket *socket;
auto unsigned long timer;
socket=&state->socket;
/* La conexin pudo haberse perdido */
if(state->state!=CONN_INIT && tcp_tick(socket)==0) {
state->state=CONN_WINIT;
// espera y reintenta
printf("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

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 handlers
printf("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 cdigo del servidor es exactamente igual al del cliente, la nica diferencia est
en que en vez de iniciar una conexin, 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 que
maneja la conexin, permitindonos, si fuera necesario, realizar cualesquiera otras
tareas:
void main()
{
ConnState connstate;

334

Transporte de comunicaciones serie asincrnicas 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 modificacin, reemplazando la bsqueda del caracter '$' por el
caracter ':', ya podemos transportar Modbus ASCII en vez de NMEA0183. De igual
modo, haciendo un anlisis similar, podemos transportar cualquier tipo de protocolo
que delimite inicio y fin de trama por un caracter nico o especial. Aquellos
protocolos que utilicen character stuffing9 requerirn algo ms de trabajo,
complicando un poquitn nuestra mquina de estados.
Otros protocolos que no encajen en este modo de trabajo requerirn que trabajemos
por el mtodo de timeout + buffer lleno, lo cual podemos aprender observando
Samples\Tcpip\serialexa.c. En esencia, lo que deberemos hacer es resetear un timer
cada vez que recibimos un caracter por el puerto serie, y cuando este timer expira o
llenamos el buffer, remitimos lo almacenado al otro extremo. La funcin serXread()
realiza esta tarea por s sola, por lo que la operacin puede llegar a ser mucho ms
simple de lo que parece en un principio.
Centro de
monitoreo

RS-232

RS-232
TCP/IP

Rabbit

Rabbit

PLC

Modbus

Protocol Spoofing
Un poco ms complicado, pero igualmente posible, es realizar spoofing. Por esto
entendemos la implementacin en el Rabbit del protocolo, sea como master o como
slave, evitando transferir a travs 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 reemplaza
por una secuencia de escape, de modo que una aparicin nica siempre corresponda a su funcin
como delimitador. El receptor reconoce la secuencia de escape y es capaz de reconstruir el mensaje
original.

335

Desarrollo de aplicaciones
modo, se produce un enlace master-slave en un equipo conectado al controlador
principal, y otro enlace master-slave en un equipo conectado al dispositivo
controlado. Un mdulo Rabbit hace de slave, y el otro hace de master, en la interfaz
serie. Mediante un protocolo que nosotros implementemos, Rabbit master y Rabbit
slave intercambian solamente la informacin, dejando el handshake y el polling para
saber 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.
Centro de
monitoreo

RS-232

RS-232
TCP/IP

Rabbit

Rabbit

Protocolo X
Master Slave

propietario

polls + info

solo info

PLC

Protocolo X
Master Slave
polls + info

Una vez ms, no estamos inventando nada, el esquema tradicional que propusimos
es similar a lo que realizan STUN10 y BSTUN11, mientras que esta "nueva idea" es
similar a lo que proponen DLSW12 y DLSW+; todos esquemas viejitos del ambiente
SNA13. O tal vez un intermedio como lo es XOT14 para X.25.
Como seguramente habrn notado una cierta tendencia a lo largo del libro, y
particularmente en este captulo, 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: deteccin de tramas HDLC/SDLC y su envo por una red, generalmente sobre
TCP. Permite intercomunicar sistemas SNA mediante redes modernas.
11 Bi-Sync tunnelling: deteccin de tramas BSC y su envo por una red "moderna".
12 Data Link SWitching, transporta informacin de sistemas SNA mediante TCP, estableciendo si es
necesario conexiones locales SDLC y ocupando ancho de banda slo con informacin. Permite
adems comunicarse con el host SNA mediante otro protocolo (diferente de SDLC), por lo que
podra considerarse como gateway tambin.
13 Systems Network Architecture, el modelo por capas desarrollado por IBM del que se dice que
surgieron las ideas del modelo OSI (Open Systems Interconnect). De hecho, HDLC, padre de casi
todos 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 (nivel2), se extrae la informacin de packet level, y sta se transmite encapsulada en TCP. Desde el punto
de vista de una interfaz serie, se reduce el trfico sobre la red TCP al eliminarse la informacin de
LAPB. La RFC que lo define sugiere adems la implementacin de sistemas con control de flujo
local, lo que permitira reducir an ms la ocupacin de ancho de banda sobre la red TCP al
minimizar el trfico de control sobre sta.

336

Transporte de comunicaciones serie asincrnicas mediante TCP/IP


los hechos del presente, el conocer el funcionamiento y la evolucin de los viejos
sistemas puede no slo aportar muchas ideas para los productos de hoy, sino tambin
ahorrar mucho tiempo de desarrollo. De hecho, como probablemente habrn notado,
muchas cosas ya estaban inventadas antes de que las "descubriramos"...

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 de
Modbus/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 controlador
mediante SDLC por una interfaz serie.
Centro de
monitoreo

RS-232
TCP/IP
Rabbit
Modbus/TCP

PLC

Modbus

Conversin de velocidad
Otra posibilidad que abre este tipo de transporte, es la conversin de velocidad. Por
ejemplo, nuestro GPS puede transmitir a 9600bps, y el software que lo recibe en
nuestra PC o equivalente recibir los datos en el standard de 4800 bps. Si el caso
fuera a la inversa, no tendramos de qu preocuparnos, pero cuando la relacin de
velocidades es mayor velocidad menor velocidad , surge el interrogante: a
dnde va a parar lo que sobra? Pues bien, si existen pausas en la transmisin, y la
velocidad efectiva, es decir, la cantidad total de bits transmitidos en el tiempo
empleado para hacerlo, no supera a la velocidad de salida, no existe ningn
inconveniente.
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 sumo
unos 300 bytes por segundo, es decir, 3Kbps. De igual modo, podemos estimar que
para el peor caso, no superaremos los 4800bps (por alguna razn el standard fij esta
velocidad... no?), con lo cual nos quedamos tranquilos que podemos realizar la
conversin de velocidades. Si el clculo nos da un poco ms, lo que podemos hacer
es configurar al mdulo GPS para que espacie un poco ms sus mensajes, o no enve
aqullos que no necesitamos. El nico detalle al que necesitamos prestarle atencin
337

Desarrollo de aplicaciones
es que debemos tener un buffer con espacio suficiente para alojar todo lo que el
transmisor enva a la velocidad mayor, menos lo que el receptor logra recibir a la
velocidad menor, durante el tiempo ms largo que el transmisor est activo
transmitiendo de forma continua. El tamao calculado se reparte entre buffers serie y
TCP remotos, ms buffers serie, de protocolo, y TCP locales, asumiendo que el
transmisor de mayor velocidad es local. Todo esto es vlido siempre y cuando TCP
pueda transmitir al otro extremo y no se quede esperando por problemas de delay
excesivo o ancho de banda reducido, en cuyo caso el anlisis es algo ms complejo.
Si por el contrario, la velocidad efectiva se acerca peligrosamente o supera la
velocidad del puerto remoto, deberemos implementar algn mtodo de control de
flujo, 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 mtodos conocidos:
control de flujo fuera de banda, es decir, mediante seales ajenas al flujo de
informacin como seales 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 la
transmisin.
Protocolos asincrnicos como por ejemplo BPS15, que ya tienen esta funcin de
forma intrnseca, dado que el servidor o equivalente va interrogando a los
remotos y por ende regulando lo que puede recibir.
Lo que debemos tener en cuenta en los dos primeros casos es si el protocolo y/o
dispositivo soporta que quien transmite pueda ser instruido o intimado a detenerse;
por ejemplo muchos mdulos GPS no disponen de seales de interfaz y NMEA0183
no 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 mayor
velocidad, mientras no podemos terminar de enviarlo por el extremo de menor
velocidad, suele ser suficiente; lo cual haramos de todos modos si empleamos el
esquema de deteccin de fin de mensaje por sealizacin intrnseca del protocolo,
pero requiere algo ms de anlisis en otros mtodos.
Una vez ms, tampoco inventamos nada, control de flujo dentro y fuera de banda
es lo que hacan los multiplexores asincrnicos, y hacen los modems con control de
error (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 detectado
principio y fin de trama, es transportable sin mayores inconvenientes, dado que el
receptor dispone de un tiempo para contestar. Los conflictivos son aquellos
protocolos de aplicacin que utilizan el tiempo de respuesta para hacer clculos,
sincronizarse, o asumir determinadas cosas sobre su interlocutor, debido a que
15 Burroughs Poll/Select; protocolo de dicha firma para terminales asincrnicas.

338

Transporte de comunicaciones serie asincrnicas mediante TCP/IP


estamos jugando con el tiempo de propagacin de una forma que no fue prevista en
el diseo de stos.
Ante este tipo de cosas, probablemente debamos jugar desconectando el algoritmo
de Nagle, el cual comentramos en el captulo sobre networking. Afortunadamente,
desconectarlo y volverlo a conectar es ms simple que entenderlo:
tcp_set_nonagle(&socket);
tcp_set_nagle(&socket);

Las opciones para controlar cundo TCP enva un segmento y cundo no, una vez
desconectado el algoritmo de Nagle, son las siguientes:
sock_flush(&socket);

// enva lo que se haya almacenado

sock_noflush(&socket);

// prxima escritura en el socket


// no causa un envo

sock_flushnext(&socket);

// prxima escritura en el socket


// s causa un envo

Tarea para el hogar


Y hemos llegado al final. Hemos hecho el desarrollo que nos habamos propuesto,
y hemos esbozado algunos conceptos tericos ms avanzados para poder afrontar
posibles mejoras y modificaciones.
Queda para el lector, como tarea para el hogar, el aplicar stos y los dems
conceptos vistos en el libro y no slo implementar diferentes protocolos sino
tambin agregar configuracin por pgina web, DHCP, cambio de IP dinmico, log
de conexiones en file system, deteccin de desconexin por keepalives, etc.

Expandiendo a ms puertos simultneos


Tal vez lo ms interesante como tarea para el hogar sea la expansin a ms puertos
simultneos, es decir, ya que disponemos de entre cuatro y seis puertos serie en el
mdulo, por qu no utilizarlos todos. Pues bien, esto es relativamente simple de
realizar; utilizando indexed cofunctions o manteniendo el esquema de handlers con
variables de estado externas. A fin de reutilizar la mayor parte del cdigo y no
escribir cuatro o seis veces el mismo handler, uno para cada puerta, lo que debemos
hacer con las funciones del puerto serie, es, igual que como hiciramos con las
variables, agregarles un nivel de indireccin, es decir reemplazarlas por un puntero.
Como bien sabemos, estamos hablando de un function pointer, un puntero a funcin,
por ejemplo:
typedef int (*fnptr)();

// define el tipo fnptr, puntero a funcin que devuelve


// un entero. Esto es ms legible y entendible luego

fnptr serXwrite;

// serXwrite es un puntero a una funcin que devuelve


// un entero

serXwrite = serBwrite

// serXwrite apunta a serBwrite()

339

Desarrollo de aplicaciones
(*serXwrite)(buffer,bytes);

// se ejecuta serBwrite()

Por ejemplo:
typedef int (*fnptr)();
void somefunction(tcp_Socket *socket, fnptr serXwrite)
{
...
// obtiene va 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 tambin la podemos incluir dentro de la estructura con la que le
pasamos los parmetros a los handlers.

340

Das könnte Ihnen auch gefallen