Beruflich Dokumente
Kultur Dokumente
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
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
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
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.
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
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
Assembler
...
LRET
#endasm
...
#asm xmem
...
LCALL pepe
...
LRET
#endasm
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.
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)
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)
; 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
}
}
los
los
de
no
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
...
c
VdHitWd(ID);
...
jr nz lup
ret
#endasm
main()
{
ID = VdGetFreeWd(5);
while(1)
lup();
/* inicializa un VWDT */
10
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
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
0x0190
0x1A
0x01A0
0x1C
0x01C0
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
0x1E
0x1E0
Timer C (R4000+)
0x1F
0x1F0
// int= 0 1
// slo para R2000 original (IQ2T)
// R4000/5000
//
//
//
//
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();
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
;
;
;
;
; recupera registros
; restablece interrupciones
; retorna
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.
// PE0,4=input
//
//
//
//
//
setea la direccin
del handler
habilita INT0 PE4,
flanco descendente
prioridad 3
Clock
PE.4
Data
PE.0
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
//
//
//
//
//
//
del handler y su
prioridad
habilita INT0 PE4,
flanco descendente
habilita INT1 PE5,
flanco descendente
; salva registros
; HL y BC usados por rotateright
;
;
;
;
;
;
; recupera registros
; restablece interrupciones
; retorna
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
01
01
01
06
05
00
23
00
59
00
59
LOG
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
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
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
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;
for(;;) {
// outside loop
27
Tiempos de ejecucin
printf("x=%ld, y=%ld\n",x,y); // print the results
}
}
28
Conceptos
if(timeleft>=0) {
slice(200,(int)timeleft) {
BackgroundTask();
}
}
}
}
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
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
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:
ld hl, PDDDRShadow
ld de, PDDDR
set 7,(HL)
ioi ldd
ret
ld hl, PDDDRShadow
ld de, PDDDR
res 7,(HL)
ioi ldd
ret
32
Ports paralelo
ld hl, PDDR
ioi res 7,(HL)
ld a,0x40
ld (PDDDRShadow),A
ioi ld (PDDDR),A
ret
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
cero:
33
Perifricos Internos
...
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
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)
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 <--
ld hl, PDDDRShadow
ld de, PDDDR
set 7,(HL)
ioi ldd
...
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
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.
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
...
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)
38
Ports paralelo
Timer B
Cambio
en pines
INTs timer
(tiempo)
latencia y jitter de latencia
Escritura
en registro
PxDR
timerb_isr();
; 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
;
;
;
;
; recupera registros
; restablece interrupciones
; retorna
#endasm
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
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.
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
42
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.
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
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
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:
9 8
7
6 5 4 3
2 1 0
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
46
ld a, (TBCSR)
; reconoce interrupcin
ld (TBL1R), a
ld (TBM1R), a
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);
// y vuelve a recargarse
// con el valor 0000
count=SCALER;
// inicializa postscaler
;
;
;
;
;
reconoce interrupcin
auto reload mediante step registers
postscaler
decrementa postscaler (no afecta flag Z)
chequea 0
skip:
ld (count),bc
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);
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)
// 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 */
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
}
}
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:
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
50
#define
#define
#define
#define
QD1_USEPORTD
QD1_USEPORTEL
QD1_USEPORTEH
QD1_DISABLE
#define
#define
#define
#define
QD2_USEPORTD
QD2_USEPORTEL
QD2_USEPORTEH
QD2_DISABLE
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;
}
// 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
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
ex DE,HL
ld (counter),HL
ret
#endasm
}
main()
{
WrPortI ( PEDDR,&PEDDRShadow,'\B10001010' ); // PE1,3,7 = output
while(1){
printf("%05d \r",quadenc_poll());
53
Perifricos Internos
}
}
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
#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'
// open drain
// modo spread
+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);
O O
L L
PWL1R
O O
PWL2R
PWL3R
O O
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 que las interrupciones sean cada una cantidad de ciclos, usamos:
PWM_INTNORMAL
PWM_INTEIGHTH
PWM_INTQUARTER
PWM_INTHALF
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
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);
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);
#asm
ipset 3
ld hl, PEDR
ioi set 0,(HL)
ioi res 0,(HL)
ipres
#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).
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.
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
66
Bus de datos
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
; 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
;
;
;
;
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.
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
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
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
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
76
Bus de datos
ld d,0x80
ret
#endasm
(
(
(
(
(
(
PGDDR,&PGDDRShadow,'\B11111111' );
PEDDR,&PEDDRShadow,'\B00010000' );
PDDDR,&PDDDRShadow,'\B11000000' );
PBDDR,&PBDDRShadow,'\B11111111' );
PBDR,&PBDRShadow,'\B11000000' );
PCDR,&PCDRShadow,'\B11101111' );
// AB16=0, M/R = M
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).
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
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
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
Bus de datos
ioe ld (0x8001),a
ret
; lo escribe
#endasm
#define Read_Data() RdPortE(0x8001)
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,
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)
83
Perifricos
CS
CPOL=0
CPHA=0
CLK
CPOL=0
CPHA=1
CPOL=1
CPHA=0
CPOL=1
CPHA=1
MOSI
MISO
MSb
LSb
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
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
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
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
El usuario deber inicializar los pines de I/O como entrada y salida segn
corresponda.
SPI_SER_A
SPI_SER_B
SPI_SER_C
SPI_SER_D
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
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.
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
// 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
// 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
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
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
Dispositivo
SDA
Lectura
Dispositivo
Indice
Dato
Indice
Dato
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
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;
}
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);
}
98
Buses serie
si necesitamos detectar que estamos por perder la alimentacin y grabar algo en
SCL
SCL
CNT1
SDA
SDA
A1
PFI
A0
32768
IRQ
PFO Vback
RST
RST
FM31xx
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
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
BitWrPortI(PEDR,&PEDRShadow,1,EE_C);\
BitWrPortI(PEDR,&PEDRShadow,0,EE_C);
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
// 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 );
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);
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
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();
}
}
107
Perifricos
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
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
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
Vdd
R
5Vx2/3=3,3V
C
pistas, chips
distribuida
2R
2RC/3
2RC/3
110
buffer
buffer
DIR=output
DIR
G=0
G
74LVX3245
111
Datos en xmem
C
En C, disponemos de un conjunto de funciones que nos facilitan el manejo de datos
en memoria extendida.
113
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);
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));
...
115
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));
...
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
xmem
0xFFFF
stack
xmem2root()
data
campo2
&disreyister
disreyister
code
0x00000
0x0000
117
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)
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.
Datos en xmem
Utilizamos una rutina ya provista para convertir la direccin fsica a
imagen
4096xXPC+0xFFFF
4096xXPC+HL
Direcciones
de memoria logica
4096xXPC+0xE000
0xFFFF
HL
0xE000
0x00000
0x0000
;
;
;
;
;
;
call LongToXaddr
ld xpc,a
ld
ld
ld
ld
ex
hl,(sp+8)
c,h
b,l
a,l
de,hl
; HL= address
119
; escribe 2 pixels
; loop en b
; loop en c
; restablece XPC
#endasm
// address 0,0
// apunta a imagen
// manda en bloques de 2KW
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
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
; 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
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
124
15
4
4
4
9
10
15
18
15
13
4
4
9
4
10
6
10
12
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)
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.
********************************************************************/
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)
(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));
(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
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
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
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ó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ón
<td><input TYPE="TEXT" NAME="config.release" SIZE=5 VALUE=<?z print
$config.release) ?>>
<tr><td>Descripció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>
Configuracin en campo
#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ó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ó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ó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ó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.
Configuracin va web
tag++;
En el ZHTML:
agregamos la variable oculta:
#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ó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ón de pará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á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ó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ó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>
144
Configuracin va web
<tr><td>
<?z if(error($config.release)) { ?>
<FONT COLOR="#FF0000">
<?z } ?>
Revisió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ó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;
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" >
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ó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ó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ó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
153
Conectividad
EHCI: Enhanced Host Controller Interface, diseo de USB2.0; los controladores
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,
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
+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
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.
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
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>
//eco
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.
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.
mdulo 1
MY=1234
DL=4321
mdulo 2
MY=4321
DL=1234
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
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
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)
166
DATA
(micro)
Transmisor
CE
Tx
tiempo en
el aire
Consumo
CE
DR1
Receptor
CLK
DATA
(modulo)
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.
PFDR
PFDRShadow
PFDDR
PFDDRShadow
168
// CLK
// lee bit
// sube CS
// enva config
// baja CS
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);
}
enva mensaje
espera transmisin
espera respuesta
repite si no la hay
hasta que desiste
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 */
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
void mifuction(void)
DESCRIPTION:
Descripcin de la funcin
void trw_init(void)
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 */
Conectividad
6
7
1
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
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
176
PARAMETER3:
PARAMETER4:
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
PARAMETER3:
179
Conectividad
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
PARAMETER3:
PARAMETER4:
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);
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.
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
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
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();
}
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.
FS2_USE_PROGRAM_FLASH 16
FS2
fs_get_flash_lx();
fs_get_other_lx();
fs_get_ram_lx();
//
//
//
//
//
//
//
//
//
//
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"
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;
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);
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.
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
//
//
//
//
Modificamos entonces esa lnea de acuerdo al tamao deseado, por ejemplo 32KB:
#define FS2_RAM_RESERVE 8
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
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);
}
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
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);
}
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
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);
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.
4
12
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"
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]
fat_part_mounted[4]
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]
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);
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);
//
//
//
//
buffer=puntero al rea
de datos
xbuffer=puntero al rea
de datos, en xmem
210
FAT
fat_Seek(&file, -offset, SEEK_END); // '-offset' bytes de fin de archivo
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);
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()
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()
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.
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
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
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.
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
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.
220
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.
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
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
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
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
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.
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
228
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...)
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.
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.
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.
236
GSM
Mdulo GSM
Funcionalidad de
Access Server
Internet
red GSM
Serie Asinc
CSD
PPP
IP
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
238
GSM
Mdulo GSM
Gateway
red GSM
Serie Asinc
Internet
GPRS
PPP
IP
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
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);
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
}
"200.49.156.3"
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;
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 );
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
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;
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
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
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
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);
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);
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
}
}
"192.168.1.54"
"255.255.255.0"
"192.168.1.1"
251
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);
}
}
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.
253
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
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;
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
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
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.
10
259
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"
Ejecutando
RAM
reservar
RAM
imagen
paleta
header
long
copiar
#ximport
paleta
header
long
flash
flash
262
Servidor 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.
263
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);
}
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
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
// no protegida
// protegida
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)
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
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
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.
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
"192.168.1.54"
"255.255.255.0"
"192.168.1.1"
#define USE_HTTP_UPLOAD
#define SSPEC_FLASHRULES
#memmap xmem
269
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.
271
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>
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;
donde:
D
D
D
273
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"
Cliente FTP
ftp_client_setup(resolve("samefetepejost.samdomein"),...);// por nombre
ftp_client_setup(resolve("192.168.1.50"),...);
// por IP
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);
275
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");
}
}
}
}
277
"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"
IF_ANY
279
16
16
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
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
#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.
"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
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
285
1980 file1
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
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"
287
#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
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)
289
"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
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
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"
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"
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
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
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);
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);
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
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,...
Direcciones IP dinmicas
297
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
que una vez modificado con los parmetros habituales, puede ser muy til.
298
Direcciones IP dinmicas
#define DIALUP_SENDEXPECT "ATH0 @ ATD*638 #CONNECT"
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"
10 El captulo sobre conectividad incorpora ms informacin sobre Bluetooth, describiendo ste y otros
profiles.
299
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
PPPoE
La seleccin de una interfaz PPPoE se realiza especificando
#define USE_PPPoE 1
#define USE_ETHERNET 1
#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);
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)
304
Direcciones IP dinmicas
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);
305
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
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ón de pará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ó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á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
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
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);
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
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
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
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.
"192.168.1.54"
"255.255.255.0"
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
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
o directamente en hexadecimal:
#define IFC_WIFI_WPA_PSK_HEXSTR \
"8125BB6E6DE7937997D978A17932DD9ABC4C4F99E84FE95F8F5989964D8AC970"
315
y el cifrado:
#define IFC_WIFI_ENCRYPTION
IFPARAM_WIFI_ENCR_CCMP
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);
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
317
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
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
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)
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)
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"))
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
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
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 } ?>
323
RabbitWeb
se trata de un POST
<?z } ?>
<?z if(!update()) { ?>
se trata de un GET
<?z } ?>
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>
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 } ?>
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.
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
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
328
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
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;
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
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
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
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.).
338
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);
sock_noflush(&socket);
sock_flushnext(&socket);
fnptr serXwrite;
serXwrite = 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