Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Sistemas Electronicos Digitales PDF
Sistemas Electronicos Digitales PDF
Autores:
Fernndez Martnez Cesreo
Snchez Miralles lvaro
Captulo 1
Captulo 2
Introduccin ______________________________________________________________ 6
Captulo 3
1
2
19
20
23
24
26
27
27
29
29
Captulo 4
Puertos ______________________________________________________ 48
Captulo 5
Perifricos____________________________________________________ 60
El Timer ________________________________________________________________ 61
4
5
61
62
62
63
63
63
Captulo 6
1
2
Ensamblador__________________________________________________ 71
74
75
75
76
76
77
78
78
79
Saltos ___________________________________________________________________ 81
10
11
12
13
14
100
101
102
103
104
15
Captulo 7
1
2
Captulo 8
1
2
115
116
116
116
118
119
119
119
Bucles__________________________________________________________________ 126
10
11
12
Captulo 9
Captulo 10
Unidad de
Memoria
Unidad de
Entrada
Unidad
Aritmtica
y lgica
(ALU)
Unidad de
salida
Unidad de
Control
CPU
Figura 1: Modelo Von Neumann de un microcontrolador
0x05
127
0x7F
Para manejar nmeros binarios con comodidad se utiliza la base hexadecimal. Los nmeros
binarios agrupados de 4 en 4 bits forman las cifras en hexadecimal.
La memoria est organizada en celdas de 8 bits. A cada celda se asigna una direccin de
memoria, de forma que el micro puede acceder al dato almacenado en dicha celda indicndole
a la memoria (en el bus de direcciones) la direccin de la celda a la que desea acceder, ver
Figura 2.
Direccin
Dato
0000
0001
05
7F
FFFF
A0
R1,R0
0x1010
15
12
1
7
NA
Rs2
Rs1
R0,0x10
Pone lo que hay en la direccin de memoria 0x10 en el registro R0. La codificacin podra
ser:
15
12 11
2
0x2010
8 7
Rs
0
mem
El nmero de bits necesarios para codificar una instruccin depende del tamao del
microprocesador. Un micro ms grande (con ms registros) necesitar ms bits para
codificar una instruccin dada.
Las instrucciones en memoria necesitarn por tanto ms o menos celdas de memoria
para ser almacenadas.
10
b.
c.
d.
e.
11
0000
0002
0004
0006
2080
2182
1010
3841
0080
0082
0007
0003
Instruccin
Instruccin
Instruccin
Instruccin
Dato
Dato
A0
FFFF
Una vez que se manda ejecutar el programa, poniendo PC = 0x0000, se empieza a ejecutar
la primera instruccin, como se muestra en la Figura 5. En la fase de Fetch se coge la
instruccin de la memoria a la que apunta PC y se guarda en IR, quedando IR = 0x2080,
para posteriormente incrementar PC para que apunte a la siguiente instruccin. En la fase de
Execute se ejecuta la instruccin que hay en IR; es decir, se coge el valor que hay en la
direccin de la memoria 0x80 y se pone en R0, quedando R0 = 7. Y as sucesivamente
para las tres siguientes instrucciones, como se puede ver en la Figura 6, Figura 7 y Figura 8.
Execute
Fetch
0000
0002
2080
2182
1010
0002
2080
PC
IR
0080
0082
0007
0003
0000
0007
0000
R0
R1
12
0000
0002
2080
2182
1010
0004
2182
PC
IR
0080
0082
0007
0003
0000
0007
0003
R0
R1
0004
0006
1010
3841
0000
0006
1010
PC
IR
0080
0082
0084
0007
0003
0000
0007
000A
0000
R0
R1
R2
0004
0006
1010
3841
0000
0008
3841
PC
IR
0080
0082
0084
0007
0003
000A
0007
000A
0000
R0
R1
R2
Es importante entender este ejemplo, para entender cmo ejecuta las instrucciones un micro y
por lo tanto comprender mejor los detalles del lenguaje ensamblador para programar un
micro. Este lenguaje es el de ms bajo nivel que se puede programar, el cual tiene una
correspondencia biunvoca entre cdigo mquina e instruccin de ensamblador.
3.6 Distintos niveles de abstraccin de un sistema electrnico digital
Segn al nivel que se trabaje, se puede ver un sistema electrnico de muchas maneras, como
se puede ver en la Figura 9.
El nivel ms bajo o nivel fsico, se corresponde con la interpretacin elctrica y es comn a
todo tipo de sistema electrnico. En este nivel slo hay medidas elctricas de tensin; es el
nivel al que se trabaja cuando se usa el osciloscopio y con las leyes de Kirchhoff.
El segundo nivel o nivel lgico, se corresponde con la interpretacin lgica de las medidas
elctricas del primer nivel. Las medidas de tensin se traducen a ceros y unos, de forma que
por ejemplo un nivel de tensin por debajo de 0.7 Voltios se considera un 0 lgico y un valor
por encima se considera un 1 lgico. Se pueden realizar operaciones en este nivel usando el
lgebra de Bool. A este nivel se sita el cdigo mquina.
13
El tercer nivel o nivel de codificacin, se corresponde con la codificacin de esos ceros y unos
en palabras que puedan ser entendidas mejor por una persona. Este nivel s que depende del
sistema electrnico que se use; es decir, del cdigo que se use, ya que existen cdigos que
interpretan los ceros y unos de distinta manera dependiendo para qu se apliquen. Si se quiere
realizar un programa para un micro, la codificacin se llama ensamblador. En caso de que se
quiera trabajar con nmeros, la codificacin puede ser binaria o hexadecimal, interpretando
los nmeros con signo y sin signo. Por ltimo, si lo que se quiere es programar FPGA o
EPLD (lgica programable), la codificacin que se usa es VHDL. Estas codificaciones
dependen dentro de cada aplicacin del dispositivo que se quiera programar; por ejemplo,
existen distintos cdigos ensamblador para diferentes micros.
Finalmente el cuarto nivel, o nivel ms alto de abstraccin, consiste en realizar una
codificacin ms entendible por una persona, que adems sea independiente del dispositivo
que se quiere programar. En caso de que se quieran programar micros, el lenguaje que se usa
es C, que independiente del micro que se quiere programar; es decir, slo existe un lenguaje
C. En caso de que se quiera trabajar con datos, existen varias codificaciones como son la
ASCII, UNICODE, etc, que son iguales para todos sistemas; es decir, slo existe un cdigo
ASCII.
PROGRAMAS uC
DATOS
Lenguaje C
PROGRAMAS FPGA
Lenguaje VHDL
Nivel fsico
Hardware +5V, 0V
ABSTRACCIN
Figura 9: Niveles de abstraccin de un sistema electrnico digital
14
CPU
(ALU, Registros
y Control)
Memoria
Entrada/Salida
Bus de datos
Bus de direcciones
Bus de control
15
El ciclo de escritura es similar. En este caso la CPU suministra tanto la direccin como el dato
a escribir en memoria (en el bus de direcciones y en el bus de datos respectivamente) y activa
la lnea WR (Write) del bus de control.
Los microcontroladores tienen perifricos y memoria integrados en el chip de CPU, mientras
que los microprocesadores no. Los perifricos sirven para comunicar la CPU con el exterior y
para realizar ciertas tareas sin consumir tiempo de CPU del micro; por ejemplo hay
perifricos que sirven para controlar motores, otros digitalizan seales analgicas, etc. Al
igual que la CPU los perifricos tienen registros que le permiten funcionar. Se dice que un
perifrico est mapeado en memoria si la CPU ve a los registros del perifrico como una
direccin ms de memoria; es decir, el micro accede a los registros del perifrico de la misma
manera que lo hace para acceder a cualquier otra direccin de memoria. El mapa de memoria
describe de forma grfica qu hay en cada rango de direcciones: memoria RAM, ROM o
Perifricos.
Cuando la CPU manda hacer algo a un perifrico se puede quedar a la espera a que ste
termine su labor, preguntndole continuamente si ha terminado, o bien puede configurar al
perifrico de que le avise y le interrumpa cuando termine. En el primer modo de
funcionamiento se dice que la CPU usa polling (es la CPU la que pregunta si ha terminado),
mientras que el segundo modo de funcionamiento se dice que la CPU usa interrupciones (es
el perifrico el que indica a la CPU que ha terminado, interrumpiendo lo que est haciendo en
ese momento). La CPU realiza polling consultado un bit de un registro del perifrico; es decir,
de la misma manera que consulta una direccin de memoria. En cambio las interrupciones
utilizan lneas especficas de comunicacin entre el perifrico y la CPU, las cuales se
encuentran en el bus de control.
16
4 Cuestiones de comprensin
A continuacin se enumeran un conjunto de preguntas que ayudan a comprender lo que se ha
descrito en el captulo.
0) Qu significa modelo del programador?
5) De qu diferentes formas se te ocurren que se pueden interpretar los bits que se almacenan
en la memoria de un micro?
17
18
19
CPU
Memoria
FF FFFF
Registros
00 0001
00 0000
00 FE00
R7
R15
PC
R6
R14
PSW
R5
R13
SP
R4
R12
R3
R11
R2
R10
R1
R9
R0
R8
(SFRs)
(GPRs)
00 FE02
I/O (SFRs)
00 FE0X
R0,0x100
R1,0x102
R1,R0
0x104,R0
20
El tamao del registro R0 es de 2 bytes (16 bits). Como cada direccin de memoria almacena
nicamente 8 bits (1 byte) son necesarios dos bytes (almacenados en direcciones
consecutivas) para llenar el registro. Por este motivo se ha situado el primer dato en la
posicin 0x100 y el segundo dato dos direcciones ms all (posicin 0x102). (El C167 es un
little endian; es decir, almacena el byte menos significativo del dato, parte baja de R0, en la
direccin par. El byte ms significativo va a la direccin impar de memoria).
A la hora de presentar ejemplos ms complejos usaremos como lenguaje de descripcin
en alto nivel el lenguaje C, que se da por conocido (a nivel bsico). El lenguaje en
ensamblador se explicar en detalle en el captulo 6. En el captulo 7 se explicar el detalle de
las particularidades del lenguaje C en la programacin de micros, y lo que es ms importante
la relacin entre el lenguaje C y el ensamblador.
Para empezar, se va a presentar el primer cdigo ensamblador equivalente al programa
siguiente en C, que no es ms que un bucle para incrementar una variable N veces. Es
necesario recordar que en C j += 1 es equivalente a j = j +1.
for (i=1; i <=N; i++)
j += 1;
Direccin de
memoria
E0
E0
48
AD
04
08
0D
10
11
05
05
F1 00 FA
01
FA
MOV
MOV
CMP
JMPR
ADD
ADD
JMPR
R0,#1
R1,#1
R0,#5
cc_sgt,0x510
0xfa00,R1
R0,#1
cc_uc,0x504
;
;
;
;
;
;
;
r0 (i)
auxiliar
if i>N
then goto 512H
j += 1
i += 1
salto sin condicin
Codificacin de
la instruccin
21
De forma concisa cada una de las instrucciones del programa hace lo siguiente, (para ms
informacin y detalles del lenguaje ensamblador ir al captulo 6):
MOV
R0,#1. R0 representa la variable i y se inicializa a 1; i = 1. MOV significa en
ingls move, mueve 1 a R0. El # significa que el valor que le acompaa se trata como
literal y no como una direccin de memoria donde buscar el dato.
R1,#1. R1. R1 es una variable temporal que representa la cantidad a sumar a j,
MOV
que aunque siempre vale 1 se necesita para poder invocar a la instruccin de suma.
CMP
R0,#5. Compara si R0 es 5. CMP en ingls compare.
JMPR cc_sgt,0x510. Si es mayor que 5 salta a la direccin 0x510. La instruccin
JMPR, en ingls jump, salta segn la condicin puesta. En este caso cc_sgt, en ingls
signed greater than, est haciendo una comparacin con signo de mayor que. con
qu? como se ver ms adelante, cada instruccin en ensamblador deja una huella en
la CPU despus de ser ejecutada, en concreto en el registro de estado, y es esa huella
como entrada a la comparacin. En este caso la instruccin CMP anterior, dej una
huella que indicaba si R0 era mayor, menor o igual que 5, que se usa en JMPR para
hacer el salto. Generalmente CMP y JMPR van juntos.
0xfa00,R1. Aade R1 al dato que haya en la direccin de memoria 0xfa00; es
ADD
decir, j=j+1. Como se coment con anterioridad la instruccin ADD 0xfa00, #1 no
existe, de ah que fuera necesario guardar en R1 el 1. Esto significa que no todas las
operaciones soportan todo tipo de operandos. Se puede apreciar que no se ha puesto
#0xfa00, ya que 0xfa00 no es un literal sino una direccin de memoria donde buscar el
dato.
R0,#1. Aade 1 a R0; es decir, i = i+1.
ADD
JMPR cc_uc,0x504. Esta instruccin en un salto sin condicin cc_uc, en ingls
unconditional, a la direccin 0x504, precisamente para que el bucle contine.
Es necesario hacer notar que a lo largo de la ejecucin del programa el PC contiene la
direccin de la siguiente instruccin a ejecutar, para ms detalles ver captulo 2 seccin
3.3. Cada vez que la CPU ejecuta una instruccin, incrementa el PC en dos o cuatro, para
que apunte a la direccin de memoria de la siguiente instruccin. En la terminologa de
programacin cuando una variable o registro contiene como dato una direccin de
memoria, se dice que la variable o el registro es un puntero que apunta a una determinada
direccin de memoria.
22
Para el C167 existen dos tipos de ensambladores, uno de muy bajo nivel llamado ensamblador
de lnea y otro de alto nivel llamado ensamblador de PC. Nada mejor que un ejemplo para
entender la diferencia entre ambos, ver Figura 12
Etiquetas (op)
bucle:
En PC
MOV
MOV
CMP
JMPR
ADD
ADD
JMPR
R0,#1
R1,#1
R0,#5
cc_sgt,fin
0xfa00H,R1
R0,#1
cc_uc, bucle
;
;
;
;
;
;
r0 (i)
auxiliar
if i>N
then goto fin
j += 1
i += 1
fin:
Instruccin
Operandos
Comentarios (opcional)
En lnea
500
502
504
506
508
50C
50E
510
MOV
MOV
CMP
JMPR
ADD
ADD
JMPR
R0,#1
R1,#1
R0,#5
cc_sgt,0x510
0xfa00,R1
R0,#1
cc_uc,0x504
;
;
;
;
r0 (i)
auxiliar
if i>N
then goto 512H
; j += 1
; i += 1
; salto sin condicin
23
Transferencia de datos. Son instrucciones que sirven para mover los datos de un lugar
a otro. La instruccin ms importante es mov.
Aritmticas. Son instrucciones que sirven para realizar operaciones aritmticas. Las
ms importantes son: add (suma), sub (resta), cmp (comparacin, resta operandos y
compara con 0), neg (hace el complemento a dos), mul (multiplica), div (divide).
Lgicas. Realizan operaciones lgicas: and (multiplicacin lgica), or (suma lgica),
cpl (complemento a 1).
Desplazamientos de bits. Desplazan los bits de un registro hacia la derecha o
izquierda. shr (shift right -> derecha), shl (shift left -> izquierda).
Saltos en la ejecucin. Realiza saltos en la ejecucin segn la condicin. jmpr cc_uc
(sin condicin), cc_eq (igual), cc_ne (no igual), cc_ugt (sin signo mayor que), cc_sgt
(con signo mayor que), cc_ule (sin signo menor o igual que, ...). Para entender cmo
funcionan los saltos ver captulo 6 seccin 7.
Los nmero en ensamblador se suelen usar o bien para literales o para referirse a direcciones
de memoria. Por defecto el nmero que se escribe se considera que est en decimal, si se pone
un 0x por delante el nmero est en hexadecimal.
A continuacin se muestran ejemplos de utilizacin de instrucciones para irse familiarizando
con el ensamblador y el C:
MOV R0, #0x4433. Esto hace R0 = 0x4433
MOV R1, R0. Es equivalente a R1 = R0, por lo tanto R1 = 0x4433
AND R1, #0xFF00. Es equivalente a R1 = R1 & 0xFF00. Un and lgico bit a bit,
quedando R1 = 0x4400.
MOV R2, R0. Es equivalente a R2 = R0, por lo tanto R2 = 0x4433.
AND R2, #0x00FF. Es equivalente a R2 = R2 & 0x00FF. Un and lgico bit a bit,
quedando R2 = 0x0033.
SHR R1, #8. Es R1 = R1 >> 8. Un desplazamiento de bits a la derecha, quedando R1 =
0x0044.
ADD R1, R2. Es una suma aritmtica R1 = R1 + R2, quedando R1 = 0x0077.
CPL R1. Realiza el complemento a 1, en C sera R1 = ~R1, quedando R1 = 0xFF88.
XOR R1,#0xFFFF. Realiza un XOR de R1, en C sera R1 = R1 ^0xFFFF. Este ejemplo es
interesante ya que un XOR con 0xFFFF es lo mismo que un complemento a 1, justo
como el ejemplo anterior.
2.2.2 Modos de direccionamiento
El 167 dispone de los siguientes modos de direccionamiento para acceder a los operandos:
24
Direccionamiento
Inmediato
Directo a GPR
Directo a SFR o GPR
Directo a memoria
Indirecto
Indirecto con pre-decremento
Indirecto con post-incremento
Indirecto con desplazamiento
Smbolo
#data
Rw, Rb
reg
mem
[Rw]
[-Rw]
[Rw+]
[Rw+#data16]
R0,R1
R0,#5
RL0,#0
RH0,#3
R1,0xFA00h
R1,[R0]
[-R0],R1
[R0+],R1
R1,[R0+#4]
25
Esto viene a decir, que la interpretacin del contenido de una posicin de memoria
depende del programador, la cul se escenifica en el tipo de instrucciones que use para
decodificarla. Si pone el PC apuntanto a esa direccin de memoria significa que la est
interpretando como una instruccin, si accede a esa direccin de memoria con una instruccin
que tiene en cuenta el signo (MUL, DIV, JMPR cc_sgt,...) entonces la est interpretando
como un nmero con signo y si accede con una instruccin que no tiene en cuenta el signo
(MULU, DIVU, JMPR cc_ugt,...) entonces la est interpretando como un nmero sin signo.
26
MOV
CMP
JMPR
MOV
MOV
R0,a
R0,b
cc_ne,next
R0,#0
a,R0
next:
2.2.4.2 Bucle
i = 0;
while (i<10) {
a[i] = i; i += 1;
}
otro:
MOV
MOV
MOV
CMP
JMPR
MOV
ADD
ADD
JMPR
R0,#0
R1,#1
R2,#0fa00h
R0,#10
cc_sge,next
[R2],R0
R0,R1
R2,#2
cc_uc, otro
next:
27
Bytes. Como la memoria puede llegar a ser de 16MBytes, puede haber 256 segmentos y
256*4=1024 pginas, como se puede ver en la Figura 13. En la tarjeta que se utiliza para las
prcticas de laboratorio todos los segmentos menos el primero tienen memoria ROM. El
primer segmento tiene RAM (externa, esto es, fuera del chip de la CPU). Una pequea parte
del segmento S0 (pgina 3) tiene RAM interna (dentro del chip de la CPU) y los registros, por
ello es el segmento ms importante.
FF FFFF
Pgina 3
(RAM Interna)
FFFF
C000
256
Segmentos
de 64 kB
16 Mb
Pgina 2
8000
Pgina 1
(RAM ext)
4000
S1 64Kb
01 0000
00 0000
S0 64Kb
16 Kb
Pgina 0
(RAM ext)
0000
Como se puede ver en la Figura 13, la pgina 0 y 1 del micro son generalmente RAM externa
(ciertas versiones del micro tienen ROM interna). La pgina 3 contiene la RAM interna y los
registros tal como se muestra en la Figura 14:
Desde la direccin 0xF600 hasta la 0xFC00 se encuentra el STACK, que es memoria
RAM para almacenamiento temporal.
Desde la direccin 0xFC00 hasta la 0xFD00 estn mapeados los GPRs. Como los
GPRs son 15 y caben 256 en esa zona de memoria, significa que los GPRs pueden
estar mapeados en distintas direcciones de memoria. Para ms informacin sobre el
mapeo de los GPRs ver seccin 2.3.2.5.
Desde la direccin 0xFD00 hasta la 0xFE00 se encuentra una zona de memoria RAM
que se puede acceder a nivel de bit, es decir, se pueden usar como operandos con
instrucciones que trabajan a nivel de bit.
Desde la direccin 0xFE00 hasta la 0XFFFF se encuentran mapeados los SFRs.
28
FFFF
SFRs
SFRs
FE00
GPRs
FC00
RAM
STACK
F600
29
Por ejemplo:
Si PC = 01 0000
CSP
IP
01
0000
CSP indica
el segmento
en uso de los
255 posibles
Segmentos
S1 64Kb
01 0000
00 0000
S0 64Kb
IP indica la zona
del segmento
en uso de las
64k posibles
16 bits
Figura 15
30
Apunta a la zona de Stack, que est comprendida entre la direccin 0xF600 y 0xFC00). El
Stack es una zona de la memoria donde el micro guarda de forma temporal informacin que
necesita para la ejecucin de un programa; por ejemplo, cuando hay una llamada a una
funcin guarda la informacin de la direccin desde donde se llama la funcin para poder
posteriormente seguir la ejecucin por donde iba. El funcionamiento del Stack es de tipo
LIFO (last in, first out). Inicialmente SP apunta a la direccin 0xFC00 y segn se van
almacenando datos en el Stack, el SP va decreciendo. De modo contrario, cuando se van
recuperando datos del Stack, el SP va creciendo. Si SP es igual a 0xFC00 quiere decir que el
Stack no tiene datos, se dice que est vaco.
2.3.2.4 DPPs (Data Page Pointer Registers)
De la misma forma que sucede con el PC, cuando se quiere acceder a una zona de la memoria
del micro para coger un dato, es necesario usar 24 bits. Por ejemplo, si se quiere acceder a un
dato en la direccin 0x0A7000, es necesario partir la direccin en dos trozos, usando un
registro de apoyo de 10 bits denominado DPP0. Existen tambin el DPP1, DPP2 y DPP3, que
se pueden usar indistintamente para poder conseguir los 24 bits necesarios. El concepto es
similar al usado con el PC, pero en vez de utilizar un registro para seleccionar el segmento y
otro para direccionar dentro del segmento, se usa un registro para seleccionar una pgina de la
memoria y otro para direccionar dentro de la pgina. Por ello se dice que la memoria del C167
es segmentada para programas y paginada para datos. El registro DPPx se encarga de
seleccionar la pgina dentro de la memoria seleccionada para coger el dato.
A continuacin se va a resolver el problema de intentar de ejecutar la instruccin siguiente,
que es incorrecta:
MOV
La solucin pasara por usar, por ejemplo, el DPP0 que almacenar los 10 bits ms altos de la
zona que queremos direccionar; en este caso y en binario 00 0010 1001 (DPP0 = 0x29). El
resto de la direccin deseada, los 14 bits ms bajos se dejan como estn y se les aade como 2
bits ms altos el nmero de DPP utilizado para resolver la direccin deseada; en este caso el
0.
DPP0 10bits
MOV
MOV
DPP0, #0x29
R0, 0x3000
Pgina seleccionada
31
MOV
MOV
DPP2, #0x29
R0, 0xB000
CP,#0FC00 H
a, R0
Dato
... ...
FC08
4400
R4
FC06
FF00
R3
FC04
FC04
R2
FC02
A050
R1
FC00
1034
R0
...
...
MOV
MOV
...
CP,#0FC04 H
a, R0
Direccin
Dato
R4
...
...
R3
FC08
4400
R2
FC06
FF00
R1
FC04
FC04
R0
FC02
A050
FC00
1034
32
3 Cuestiones de comprensin
Practicando con la memoria del micro
1) Escribir en ensamblador un cdigo que escriba el contenido de la direccin de memoria
0x1F0468 en el registro R1. Usar DPP2.
2) Cuantos bits son necesarios para poder direccionar hasta 8 Mbytes de datos? _____
4 Ejercicios propuestos
1) Hacer un programa en ensamblador que intercambie el contenido de la direccin de
memoria 0x200 por el de la 0x202.
33
4) Hacer un programa en ensamblador que busque el valor mximo (a nivel de byte sin signo)
de los datos que hay entre la direccin de memoria 0x200 y 0x300, y lo guarde en R1.
5) Dadas las condiciones iniciales de los registros y las instrucciones escritas a continuacin,
indicar cmo se modifica la memoria y los registros R0 y R1 en los sucesivos pasos.
34
r0
rh0
FF
r1
rl0
00
rh1
00
rl1
01
PASO 1
r0
MOV R0, #2
rh0
r1
rl0
rh1
rl1
PASO 2
r0
MOV R0, 0x202
rh0
r1
rl0
rh1
rl1
PASO 3
r0
MOV R1, R0
rh0
r1
rl0
rh1
rl1
PASO 4
r0
MOV 0x200, R1
rh0
r1
rl0
rh1
rl1
35
PASO 5
r0
rh0
r1
rl0
rh1
rl1
PASO 6
r0
MOVB rl0, #4
rh0
r1
rl0
rh1
rl1
PASO 7
r0
MOVB rh1, [r0]
rh0
r1
rl0
rh1
rl1
PASO 8
r0
MOV r1, [r0+]
rh0
r1
rl0
rh1
rl1
PASO 9
r0
MOVB rl1, [r0+]
rh0
r1
rl0
rh1
rl1
PASO 10
r0
ADD r1, r0
rh0
r1
rl0
rh1
rl1
36
6) Dada la tabla de cdigos de instrucciones y la situacin inicial de los registros CP, IP,
DPP0 y CSP indicar cmo se modifica la memoria cuando se ejecuta el programa de tres
instrucciones cargado en memoria. Indicar en la tabla de la derecha cmo queda la memoria
despus de la ejecucin de la tercera instruccin del programa.
C digo de instruccin
E 0 31
E 0 10
F0 01
F6 F0 02 02
R egistros
C S P = 00
C P = FC 00
D P P0 =0
IP = 020A
D ireccin de
00
00
00
00
00
00
FC 00
FC 01
FC 02
FC 03
FC 04
FC 05
Instruccin
R 1,#3
R 0,#1
R 0, R 1
0x202, R 0
D ireccin d e
C eld a 8 b its
m em o ria 24
m em oria 24
b its (H ex)
00 0200
00 0201
00 0202
00 0203
00 0204
00 0205
00 0206
00 0207
00 0208
00 0209
00 020A
00 020B
00 020C
00 020D
00 020E
00 020F
00 0210
00 0211
00 0212
00 0213
00 0214
..
m ov
m ov
m ov
m ov
(H ex)
FF
20
20
10
01
11
03
03
11
10
10
E0
31
E0
02
02
F0
F6
00
00
00
01
02
34
45
65
76
bits (H ex)
00 0200
00 0201
00 0202
00 0203
00 0204
00 0205
00 0206
00 0207
00 0208
00 0209
00 020A
00 020B
00 020C
00 020D
00 020E
00 020F
00 0210
00 0211
00 0212
00 0213
00 0214
..
00
00
00
00
00
00
(H ex)
FC 00
FC 01
FC 02
FC 03
FC 04
FC 05
37
7) Dado el programa siguiente, rellenar los registros y la memoria indicada justo antes de que
el programa ejecute la instruccin de la direccin de memoria 0x514 (final del programa)
Direccin memoria HEX
500
502
504
508
50A
50C
510
512
514
Cdigo
E0 10
E0 11
F6 F1 00 FA
48 05
AD 05
04 F1 00 FA
08 01
0D FA
CC 00
Instruccin
mov R0,#1
mov R1,#1
mov 0xfa00,R1
cmp R0,#5
jmpr cc_sgt,0x514
add 0xfa00,R1
add R0,#1
jmpr cc_uc,0x508
nop
Registros
CSP =
CP = FC02
IP =
38
(Hex)
39
40
41
42
43
44
45
46
47
Captulo 4 PUERTOS
1 Objetivos y conceptos a entender en este captulo
El objetivo del captulo es entender y aprender a manejar los puertos paralelo del C167. Este
objetivo se consigue cuando se sepa manejar y diferenciar el registro de direcciones (DPx) y
de datos de un puerto (Px).
2 Puertos paralelo
El C167 tiene 9 puertos paralelo, con un total de 111 lneas. Cada lnea se corresponde con un
pin o patita del chip del micro, que tiene 144 pines en total. Los puertos se nombran Px, de
forma que el puerto 0 se llama P0, el puerto 1 se llama P1, etc. Cada puerto consta de un
conjunto de lneas, como mximo 16, que salen al exterior o entran del exterior por los pines
del micro. Existe una correspondencia biunivoca entre pines y lneas. A continuacin se
enumeran los puertos explicando muy someramente su funcin:
El P0 tiene 16 lneas y es el bus de datos del micro que tanto se ha hablado a lo largo
del captulo 2 y 3. Este bus se conecta a las memorias externas y a los perifricos y se
usa para transmitir los datos que contienen los mismos. No se debe olvidar que tiene
16 bits, cada uno corresponde con una lnea, ya que el C167 es de 16 bits.
El P1 tiene 16 lneas y representa la parte baja del bus de direcciones. Este bus se
conecta a las memorias externas y a los perifricos y se usa para que la CPU indique la
direccin de memoria que se quiere acceder para recuperar o escribir un dato. Este bus
es de 24 bits y por ello este puerto nicamente representa a los 16 bits ms bajos del
mismo. Ser necesario usar el puerto 4 para completar el bus.
El P2 tiene 16 lneas y es de propsito general. En el laboratorio, se tienen conectados
en la parte baja del puerto, 8 diodos LED, y en la parte alta del puerto, 8 interruptores.
Pero es muy importante no olvidar que se podra haber conectado cualquier otra cosa
al puerto.
El P3 tiene 16 lneas. Parte de las lneas se usan para un puerto serie, que en el caso
del laboratorio se usa para comunicarlo con el PC. El resto de las lneas son de
propsito general.
El P4 tiene 8 lneas que completan la parte alta del bus de direcciones.
El P5 tiene 16 lneas que se usan como entradas para el conversor A/D. En cada una
de estas entradas se puede conectar una seal analgica, para poder realizar una
digitalizacin de la misma.
El P6 es de 8 lneas y tiene parte del bus de control.
El P7 tiene 8 lneas que son las salidas correspondientes a la unidad de PWM que tiene
el micro.
48
Cada puerto tiene dos registros que sirven para manejarlo, un registro de control (DPx) y
otro de datos (Px). A continuacin se explica cada uno de ellos particularizado para el puerto
2.
DP2 tiene 16 bits, uno para cada lnea (pin) del puerto. Se utiliza para indicar si el pin hace de
entrada o de salida (el pin no puede hacer de entrada y de salida al mismo tiempo). Con un 0
en DP2 se indica que el pin hace de entrada. Con un 1 el pin hace de salida. Por ejemplo, si en
DP2 cargamos 0xFFFF estamos indicando que todos los pines del puerto hacen de salida. Si
cargamos 0x0000 indicamos que todos hacen de entrada. Con 0x00FF la mitad de los pines
del puerto hacen de entrada y la otra mitad de salida.
Una vez configurado el puerto va DP2, ste se maneja a travs de P2. Cuando se lee P2 se lee
el estado de los pines del micro. Cuando se escribe en P2 se escribe en los pines del micro.
En el sistema usado para las prcticas de laboratorio se han conectado diodos LED en parte
baja de P2 (P2.0 a P2.7) e interruptores en la parte alta (P2.8 a P2.15), de la manera que se
muestra en la Figura 16.
+ 5V
+ 5V
P2.8
P2.0
Figura 16: conexin de los LED e interruptores al puerto 2 del C167 de la tarjeta de laboratorio
Cuando se lea P2 se estar leyendo el estado de los pines que hacen de entrada (parte
alta de P2: interruptores).
Cuando se escriba en P2 se actuar sobre los diodos (parte baja de P2).
Ntese que:
Cuando se lee P2 la nica informacin de inters es la que viene de las entradas (la
informacin de las salidas no tiene inters, se desecha)
49
Cuando se escribe P2 slo se acta sobre las salidas (en las entradas tenemos el valor
fijado en el exterior va interruptores).
DP2 depende nicamente del hardware conectado y por ello slo se configura una
vez dentro de los programas. De forma que aquellos pines del micro que tengan
conectados actuadores, se configurarn como salidas y los que tengan conectados
sensores, se configurarn como entradas.
Por ejemplo:
A continuacin se muestra un programa que suma dos nmeros de 4 bits, uno representado
por los 4 interruptores conectados a la parte ms alta de P2 (P2.12 a P2.15) y otro
representado por los 4 interruptores conectados desde P2.8 hasta P2.11. El resultado de la
suma se muestra por los LEDs.
La solucin en C sera:
void main(void) {
int temp1,temp2;
DP2 = 0x00ff;
while (1) {
temp1 = P2;
temp2 = temp1; /* Mejor que "temp2 = P2 */
temp1 = temp1 & 0x0f00; /* AND bit a bit */
temp1 = temp1 >> 8;
/* SHR rotar 8 a la decha*/
temp2 = temp2 & 0xf000;
temp2 = temp2 >> 12;
P2=~(temp1 + temp2); /* CPL */
}
}
50
bucle:
MOV DP2,#0x00ff
MOV R0, P2
MOV R1, R0
AND R0, #0x0F00
SHR R0, #8
AND R1, #0xF000
SHR R1, #12
ADD R1, R0
CPL R1
MOV P2, R1
JMPR CC_UC, bucle
Ntese que el programa maneja DP2 una nica vez en la fase de inicializacin (configuracin
del puerto, antes del bucle while). El manejo de puerto se realiza a travs de P2.
51
3 Ejercicios propuestos
1) Hacer un programa en ensamblador que encienda el primer LED de la tarjeta del
laboratorio (situados en la parte baja del puerto 2).
2) Hacer un programa en ensamblador que indique en los LED (parte baja de P2) la posicin
de los interruptores (parte alta de P2) de la tarjeta del laboratorio.
52
53
54
55
56
57
58
59
Captulo 5 PERIFRICOS
1 Objetivos y conceptos a entender en este captulo
60
3 El Timer
Es un perifrico que se encarga de contar los pulsos de una seal. La cuenta la almacena en un
registro de 16 bits. En un momento dado la CPU le manda empezar a contar y cuando rebosa
(llega a 0xFFFF+1), avisa de que ha terminado la cuenta. El C167 tiene muchos Timers, pero
todos se usan de forma similar. Esta seccin explica con detalle el funcionamiento del Timer
0, y por similitud queda descrito el funcionamiento del resto de Timers del micro.
Los registros de programacin del mismo son:
T01CON: es un registro (SFR) de configuracin de 16 bits del Timer 0 y 1 como su
nombre indica. La parte baja del registro, primeros 8 bits, configuran el Timer 0 y la
parte alta configura el Timer 1.
T0: es un registro (SFR) de datos del perifrico de 16 bits que se encarga de llevar la
cuenta del nmero de pulsos detectados. El equivalente para el Timer 1 se llama T1.
T0REL: es el registro (SFR) de 16 bits que indica el valor con el que se carga T0 una
vez que rebosa, sobrepasa su mxima cuenta que es 0xFFFF. Dicho de otro modo, una
vez que el Timer 0 a realizado tantas cuentas como para T0 sobrepase 0xFFFF, en vez
de pasar a valer T0=0, pasa a valer T0=T0REL. De esta forma aunque el Timer 0
termina su tarea, puede seguir contando. El registro equivalente para el Timer 1 es
T1REL.
T0IC: es el registro (SFR) de 16 bits tiene un bit (T0IR) que indica cuando el Timer 0
ha rebosado. El resto de bits sirven para configurar su control por interrupcin
A continuacin se explica en detalle cada uno de estos registros.
3.1 Registro de control T01CON
El registro de T01CON tiene 16 bits y consta de dos partes, una parte baja que configura el
Timer 0 y otra parte alta que configura el Timer 1:
15
14
13 12
---
T1R
---
11
T1M
10
8
T1I
---
T0R
5
---
3
T0M
0
T0I
61
tiempo, y en el segundo se dice que funciona en modo counter, por ejemplo el Timer 0
cuenta los pulsos de la seal que entra por el pin T0IN/P3.0.
T0I: es el pre-escalado y lo forman los bits 0-2. El pre-escalado, como su nombre
indica, es un cambio de escala en la cuenta. Indica el nmero de pulsos de la seal de
reloj que tienen que suceder para que el Timer 0 incremente en 1 su cuenta. De esta
forma se logra que el timer cuente ms despacio, ya que el pre-escalado funciona
como un divisor de frecuencia, dividiendo por:
2 ( 3+T 0 I )
Por ejemplo, para el caso de que el Timer 0 funcione en modo timer, y la CPU use un
reloj de 20 MHz, la salida del pre-escalado es:
20 MHz
2 ( 3+ T 0 I )
T0R es el bit 6 y sirve para arrancar y para el Timer. Cuando la CPU quiere que
empiece la tarea, pone este bit a 1, y cuando ha terminado de contar, la CPU puede
poner este bit a 0 y deja de contar o bien le deja continuar recargando de forma
automtica T0=T0REL.
clk = 20MHz
T0I
T0IR T0IE
2 1
ILVL
GLVL
De todos los bits el nico que interesa por ahora es el T0IR, ya que los dems se vern en ms
detalle en el Captulo 8.
T0IR: es un bit que indica que el Timer 0 ha terminado su tarea; es decir, en el
momento en que el registro T0 pasa de 0xFFFF a 0000.
T0IE: habilita el control por interrupciones.
ILVL: indica el nivel de la interrupcin (0 a 15. Nivel 15 nivel ms alto)
GLVL: grupo del nivel de interrupcin.
62
Valor de recarga
despus de un rebose
rebose o final de
cuenta
T0REL
Pre-escalado
clk
T0I
T0
T0IR
Almacena la
cuenta
63
lo mismo que P2
= ~P2,
= complemento a 1 de P2.
T01CON | 0x40,
#include <reg167.h>
main() {
T01CON = 0x06;
T0REL = PERIOD;
T0 = PERIOD;
T0IR = 0;
T01CON |= 0x40;
DP2 = 0x00FF;
P2 = 0;
// timer a 39 kHz
// Qu valores son razonables?
// flag rebose = 0
// Start
while (1) {
while(!T0IR) ;
P2 ^= 0xFFFF;
T0IR = 0;
}
// bucle de espera
// XOR
// flag rebose = 0
El Timer cuenta a una frecuencia de 39kHz y funciona en modo timer. Lo que supone que
dependiendo de la inicializacin de T0 y T0REL el Timer puede rebosar desde
aproximadamente 25us (cuando PERIOD = 0xFFFF) hasta aproximadamente 1.65s (cuando
PERIOD = 0). En un principio los LEDs estn encendidos. Cuando el programa entra en el
bucle infinito, se comprueba si el timer rebosa (T0IR == 1) y si es as entonces cambia el
estado del puerto P2, apagando los LEDs. Luego el programa vuelve a esperar al siguiente
rebose y despus enciende los LEDs. En resumen el programa enciende y apaga los LEDs de
forma peridica con un periodo que depende de la inicializacin de PERIOD. Para que el ojo
humano perciba el parpadeo de los LED este debe hacerse a una frecuencia inferior a 10Hz,
por ejemplo, PERIOD = 0 es razonable.
64
4 Cuestiones de comprensin
1) Deducir la frmula que relaciona el tiempo de rebose del timer (t) con el prescalado (T0I) y
la cuenta inicial (T0); es decir, t = funcin(T0I,T0).
2) Si el tiempo que tarda en rebosar el timer 0 es 2s, cunto vale T0 y T0I (la cuenta inicial y
el preescalado)?. Razona la respuesta
65
5 Ejercicios propuestos
5.1 PWM sencillo (30 min)
Hacer un programa que genere una secuencia de pulsos, por el pin P7.0 del micro, de forma
peridica a frecuencia 256kHz. El ancho de cada pulso viene fijada, en milisegundos, por la
variable de 8 bits DC.
$nonsegmented
S0
ini
ini
S0
section code at 0H
proc NEAR
jmp main
endp
ends
DAT
DC
DAT
ends
SM
main
66
main
SM
endp
ends
end
67
6 Prctica 4: timers
68
69
70
Captulo 6 ENSAMBLADOR
1 Objetivos y conceptos a entender en este captulo
Los objetivos de este captulo son:
Darse cuenta de que cada instruccin ensamblador se codifica de una forma en
hexadecimal.
Entender los nmeros con signo y sin signo, complemento a 2, qu implica el
overflow y el carry.
Entender cmo funcionan los saltos basndose en los flags del PSW.
Cmo se maneja el stack: cuando se usan funciones y con las instrucciones push y pop
2 Introduccin
El objetivo de este captulo es describir las principales instrucciones de ensamblador para el
microcontrolador Siemens C167. Las instrucciones de ensamblador se caracterizan por los
modos de direccionamiento que soportan, los flags de estado a los que afectan y el tamao de
los operandos que usan.
Los modos de direccionamiento son la forma que se tiene en ensamblador para acceder a cada
uno de los operandos de una instruccin. Se explican en el captulo 2 seccin 2.2.2.
Los flags de estado dan informacin cualitativa del resultado despus de ejecutar una
instruccin. Se explican en el captulo 2 seccin 2.3.2.2.
El 167 maneja operandos de distintos tamaos:
Byte: 8 bits
Word: 16 bits
Bit: 1 bit
2.1 Codificacin de instrucciones
La CPU slo entiende de ceros y unos. Por ello en memoria las instrucciones que tiene que
ejecutar se encuentran codificadas de forma binaria, cdigo mquina. El programa encargado
de traducir del lenguaje ensamblador a cdigo mquina se llama ensamblador. Si se trabaja en
alto nivel, el programa encargado de traducir del lenguaje C a cdigo mquina se llama
compilador.
71
F0
Cdigo de
instruccin
x y
Nmero de
GPR
E6
Cdigo de
instruccin
reg
Nmero de
SFR
dato
Nmero de
16 bits
op1,op2
op1,op2
Transfiere el contenido del operando fuente op2, al operando destino op1. En este
ensamblador el operando fuente siempre est a la derecha y el destino a la izquierda. Es la
instruccin que permite el mayor nmero de combinacin de modos de direccionamiento en
sus operandos. Una regla que vale en un 95% de las veces es suponer que puede con todos los
modos de direccionamiento menos MOV mem,mem y MOV mem,#data. Para ms informacin
de los modos de direccionamiento consultar seccin 2.2.2 del captulo 3.
72
op1, op2
16 sin extender
signo
0xFF
-1
255
255 (0x00FF)
-1 (0xFFFF)
0x80
-128
128
128 (0x0080)
-128 (0xFF80)
reg
reg
73
Estas instrucciones se suelen usar para guardar los GPRs que se van a usar en una funcin
como variables locales, ya que en caso contrario la funcin modificara el contenido de los
mismos y no funcionara correctamente el programa que llama a la funcin.
MOV
MOV
ADD
ADD
R0,#0x0001
0xFA00,R0
R0,#0x0009
0xFA00,R0
74
R0,#0x0001
Rh0,#0x01
op1, op2
MOV
MOV
SUB
ADD
R0,#0x01FF
0xFA00,R0
R0,#0x0009
0xFA00,R0
A nivel de byte
MOV
SUBB
R0,#0x01FF
Rh0,#0x01
4.3 NEG
La instruccin NEG sirve para obtener el complemento a dos de un nmero. Es equivalente a
restar el operando op1 de 0 y almacenar el resultado en el mencionado operando (en este caso
el flag de C tambin recoge el borrow).
NEG
Rx
; Rx <- 0 - Rx
75
MOV
NEG
R0,#0x01FF
R0
A nivel de byte
MOV
NEGB
R0,#0x01FF
Rl0
Rx,Ry
Rx,Ry
La instruccin MUL admite dos registros como operandos. El resultado viene dado en 32 bits,
que se almacena en el registro MD (registro Multiply/Divide de la CPU, almacenando en
MDL la parte baja de MD, y en MDH la parte alta de MD). En esta operacin nunca se tiene
rebose (C = 0). El flag V se pone a 1 si el resultado no cabe en 16 bits (til si se pretende
multiplicar el resultado de este primer producto por algn otro factor.) Los flags Z y N se
actualizan de acuerdo con el valor del resultado. La instruccin MULU es la variante para
operandos sin signo.
MOV
MOV
MUL
MOV
MOV
MULU
R0,#0xFFFF
R1,#0xFFFF
R0,R1
R2,MDL
R3,MDH
R0,R1
Figura 26: Ejemplo de MUL y MULU. Finalmente R2 = 1, R3 = 0, MDL = 0x0001 y MDH = 0xFFFE
Rx
Rx
76
El cociente viene dado en 16 bits que se almacenan en la parte baja del registro MD (MDL),
el resto queda en la parte alta (MDH). Caso de que el cociente supere los 16 bits
significativos, o el cociente sea 0, el bit V se pone a 1. La variante DIVU opera con nmeros
sin signo.
MOV
MOV
MOV
DIV
MOV
MOV
MOV
MOV
DIVU
R3,#0xFFFF
MDL,R3
R0,#0x0001
R0
R1,MDL
R2,MDH
R3,#0xFFFF
MDL,R3
R0
Figura 27: Ejemplo de DIV y DIVU. Finalmente R1 = 0xFFFF, R2 = 0, MDL = 0xFFFF y MDH = 0
Rx,op
; Rx <- Rx and op
Admite los mismos modos de direccionamiento que la instruccin ADD. Los flags V y C
quedan a 0 y el resto (A, N) se actualizan de acuerdo con el valor del resultado. La variante
ANDB opera a nivel de byte.
MOV
AND
R0,#0x0001
R0,#0x0009
La instruccin AND sirve como mscara, para seleccionar nicamente ciertos bits de un
registro o para borrar bits de forma selectiva.
77
AND
R0,#0xFFFD
Figura 29: Se quiere poner el bit 1 del registro R0 a 0. O lo que es lo mismo, se seleccionan el resto de bits del
registro R0
5.2 OR
Se usa para efectuar la suma lgica de dos operandos, bit a bit. La instruccin OR admite la
forma:
OR
Rx, op
; Rx <- Rx or op
Admite los mismos modos de direccionamiento que la instruccin ADD. La variante ORB
opera a nivel de byte.
MOV
OR
R0,#0x0001
R0,#0x0009
La instruccin OR sirve como mscara, para seleccionar nicamente ciertos bits de un registro
o para poner a uno ciertos bits de forma selectiva.
OR
R0,#0x0002
Figura 31: Se quiere poner el bit 1 del registro R0 a 1. O lo que es lo mismo, se seleccionan el resto de bits del
registro R0
R0,#0xFFFD
R2,R1
R2,#0x0002
R0,R2
;
;
;
;
se pone a 0 el bit 1 de R0
para no modificar R1
se selecciona el bit 1 de R2
R0.1 = R2.1 = R1.1
Figura 32: Se quiere poner el bit 1 del registro R0 igual que el bit 1 del registro R1.
5.3 XOR
Realiza un OR EXCLUSIVO bit a bit de dos operandos. La instruccin XOR admite la forma:
XOR
Rx, op
; Rx <- Rx xor op
78
MOV
XOR
R0,#0x0101
R0,#0xFFFF
; R0 <- 0xFEFE
5.4 CPL
Realiza el complemento a 1 de un nmero. La instruccin CPL admite la forma:
CPL
Rx
; Rx <- complemento a 1 de Rx
R0,#0x0101
R0
; R0 <- 0xFEFE
Rx,Ry
Rx,#num ; Desplaza num veces a la izda los bits de Rx
Rx 16 bits
MOV
SHL
R0,#0x0001
R0,#9
79
SHR desplaza los bits hacia la derecha. El bit de Carry se pone a uno si el bit 0 que sale del
desplazamiento es 1 (en la ltima iteracin). Las posiciones libres que van quedando a la
izquierda se rellenan con ceros.
Rx 16 bits
Se puede combinar con las operaciones AND y OR para igualar bits de diferentes registros.
AND
MOV
AND
SHL
OR
R0,#0xFFFD
R2,R1
R2,#0x0001
R2,#1
R0,R2
;
;
;
;
;
se pone a 0 el bit 1 de R0
para no modificar R1
se selecciona el bit 0 de R2
el bit 0 pasa a ser el bit 1
R0.1 = R2.1 = R1.1
Figura 36: Se quiere poner el bit 1 del registro R0 igual que el bit 0 del registro R1.
Las instrucciones de rotacin ROL y ROR permiten rotar los bits de un registro un nmero
arbitrario de desplazamientos (a izquierda y derecha respectivamente), de forma que los bits
que salen por una lado del registro entran por el otro.
La forma genrica de utilizacin es:
ROL
ROL
Rx,Ry
Rx,#num ; Rota num veces a la izda los bits de Rx
C
MOV
ROL
Rx 16 bits
R0,#0x8001
R0,#9
ROR rota los bits hacia la derecha. El bit de Carry se pone a uno si el bit 0 que sale del
desplazamiento es 1 (en la ltima iteracin). Las posiciones libres que van quedando a la
izquierda se rellenan con el valor del Carry en cada iteracin.
80
Rx 16 bits
7 Saltos
Como se ha mencionado en las secciones anteriores, el "estado" en el que queda el procesador
despus de ejecutar una instruccin (de transferencia de datos, aritmtica, lgica o de
desplazamiento), queda almacenado en el Registro de Estado, PSW, ver captulo 3 seccin
2.3.2.2. El estado viene definido por los "flags":
N: Negativo.
Z: Cero ("Zero").
V: Rebose ("Overflow").
C: Acarreo o "Carry".
La informacin contenida en estos flags es todo lo que se necesita para realizar el control de
flujo de programa. Por ejemplo, las siguientes condiciones del lenguaje C:
if (a == b)
if (a != b)
81
Sin signo
Con signo
[C] [Z]
[Z] [ ( N V ) ([N] [V] ) ]
C
( N [V] ) ([N] V )
[C]
( N V ) ([N] [V] )
C Z
Z [ ( N [V] ) ([N] V ) ]
La instruccin CMP es similar a la instruccin SUB: resta el operando destino y del operando
fuente y actualiza los flags de acuerdo con el resultado obtenido. Sin embargo, CMP no
actualiza el operando destino.
La operacin admite las siguientes variantes:
CMP
op1,op2
op1 es el operando destino, y op2 es el operando fuente. Esta instruccin es idnea para
comparar dos operandos que no se quieren actualizar, antes de un salto con condicin. Admite
la variante CMPB.
En la Figura 38 se muestra un ejemplo. Primero, la instruccin CMP actualiza los flags del
registro PSW de la misma forma que lo hace la instruccin SUB. Segundo, la instruccin
JMPR se encarga de realizar el salto a la lnea next en caso de que R0 sea mayor que R1
(comparacin sin signo, es necesario hacer notar que -1 es mayor que 100 si se compara sin
signo ya que el nmero -1 es 65535 sin signo en 16 bits).
82
CMP
JMPR
....
R0,R1
cc_ugt,next
;si R0 > R1
next:
Figura 38: Ejemplo de salto condicional
else:
MOV
R0,a
CMP
R0,b
JMPR
cc_ne,else
; condicin if
..
JMPR
cc_uc,endif
; condicin else
..
endif:
if (a == b)
...;
else
...;
83
else:
MOV
R0,a
CMP
R0,b
JMPR
cc_ne,else
; condicin if
..
JMPR
cc_uc,endif
; condicin else
..
endif:
for:
test:
MOV
MOV
SUB
JMPR
MOV
SUB
ADD
CMP
JMPR
R0,#START
R1,#STOP
R1,R0
cc_uc,test
a,R1
R1,#1
R0,#1
R0,#STOP
cc_slt,for
; r0 (i)
; r1 (STOP-START)
; i += 1
; i <= STOP
next:
Figura 43: bucle for en ensamblador
84
i = 0;
while (i<10) {
a[i] = i; i += 1;
}
Figura 44: bucle while en C
otro:
MOV
MOV
MOV
CMP
JMPR
MOV
ADD
ADD
JMPR
R0,#0
; R0 es i
R1,#1
; es el incremento de i
R2,#0xfa00 ;R2 almacena la direccin de memoria de a
R0,#10
cc_sge,next
[R2],R0
R0,R1
R2,#2
cc_uc,otro
next:
SFRs
SFRs
FE00
GPRs
FC00
RAM
STACK
F600
85
Figura 46: Zonas de memoria que permiten usar instrucciones a nivel de bit
9.1 Saltos
Las instrucciones JB y JNB permiten realizar un salto en funcin del estado en el que se
encuentra un bit.
JB
bit, dir
JNB
bit, dir
JB
....
C,next
next:
Figura 47: Ejemplo de manejo de JB.
9.2 Otras
La instruccin BSET pone a 1 el bit especificado en op1:
BSET
op1
Los bits se especifican dando la direccin de la palabra, punto, nmero de bit. Por ejemplo:
BSET
0xFD00.2
pone a 1 el bit 2 de la palabra 0x00FD00. El flag N contiene el estado previo del bit (0 si el bit
estaba a 0), y el flag Z es igual a N complementado.
La instruccin BCLR sirve para poner un bit a 0.
Para mover un bit de un lugar a otro se usa la instruccin
BMOV
bit1, bit2
; bit1 = bit2
86
BAND
bit1, bit2
BOR
bit1, bit2
BXOR
bit1, bit2
87
10 Directivas de ensamblador
Las directivas son unas instrucciones se ponen en el programa y que indican al programa
ensamblador cmo debe ensamblar el cdigo. Estas instrucciones se ejecutan en tiempo de
ensamblado y no en tiempo de ejecucin como el cdigo ensamblador propiamente dicho.
Por eso a veces las directivas de ensamblador se llaman pseudoinstrucciones.
En la Figura 49, se muestra un ejemplo con directivas de ensamblador, que se pasan a explicar
a continuacin.
Procedimiento ej1
maxdata
$nonsegmented
equ
0x10 ; constante
D100
j
h
D100
ej
ej1
section
proc
MOV
MOV
MOV
CMP
JMPR
MOV
ADD
ADD
JMPR
NOP
endp
ends
end
otro:
Seccin de cdigo ej
en direccin 0x300
next:
ej1
ej
code at 300H
NEAR
R0,#0
R1,#1
R2,#j
R0,#maxdata
cc_sge,next
[R2],R0
R0,R1
R2,#2
cc_uc,otro
Final de programa
88
dsw: reserva espacios de 2 byte de memoria para una variable. Por ejemplo, en la
primera lnea de la seccin de datos de la Figura 49, se reserva 20 bytes (10 espacios
de 2 bytes) para la variable j; es quivalente a escribir en lenguaje C int j[10].
Existe la variante dsb que reserva espacios de 1 byte de memoria para la variable.
dw: reserva espacios de 2 byte de memoria para una variable y los inicializa. Por
ejemplo, en la segunda lnea de la seccin de datos de la Figura 49, se reservan dos
bytes que se inicializan a 1; es equivalente a escribir en lenguaje C int h. Por
ejemplo, el equivalente a int h[]={2,4}; sera dw h 2,4. Tambin existe la
variante db que reserva espacios de 1 byte.
equ: define una constante; el equivalente en C es #define. Esta directiva se pone
fuera de las secciones. En la primera lnea de la Figura 49 se muestra un ejemplo de
cmo se define la constante maxdata igual a 10.
end: indica final de programa y se pone al final del fichero.
proc: cada seccin de cdigo se puede dividir en varios procedimientos con esta
directiva. La nomenclatura es:
nombre proc NEAR
....
nombre endp
donde nombre es un etiqueta que indica el nombre del procedimiento y endp indica
el final del procedimiento. Se puede acceder a un procedimiento de la misma manera
que se hace con una lnea de cdigo etiquetada; por ejemplo jmpr nombre.
89
11 Cuestiones de comprensin
1) Cmo se codifica el nmero 16 en hexadecimal usando 8 bits?
R0, #0xFC
;R0 = __________
Movbs
R0, #0xFC
;R0 = _________
3) Indicar el valor de PSW despus de ejecutar las siguientes instrucciones (los bits que no se
usan se pueden poner a cero):
mov
R0, #0
;PSW = ___________
add
R0, #0x6500
;PSW = ___________
add
R0, #0x6500
;PSW = ___________
add
R0, #0x6500
;PSW = ___________
R0, #300
R1, [R0]
equ 0x300
R0,#var
90
R0,#0x01FF
RL0
R0,#0x6FFF
R1,#0xFFFF
R0,R1
R2,MDL
R3,MDH
R3 = ____________
R2 = ____________
MDL,#0xFFFF
R0,#0x0001
R0
R1,MDL
R2,MDH
9) Qu instrucciones de ensamblador hay que programar para hacer que el bit 2 (tercer bit)
del registro R0 sea igual que el de R1 sin llegar a modificar ni el registro R1 ni el resto de bits
del registro R0? (usar mscaras)
91
300
304
306
....
MOV
CALLR
MOV
....
R0, #0x54
400
R1, #0x32
400
402
406
408
PUSH
MOV
POP
RET
R0
R0, #1
R0
R0 = _____________
SP = _____________
Direccin mem
00 FC04
00 FC03
00 FC02
00 FC01
00 FC00
00 FBFF
00 FBFE
00 FBFD
00 FBFC
00 FBFB
00 FBFA
R0 = _____________
SP = _____________
92
Direccin mem
00 FC04
00 FC03
00 FC02
00 FC01
00 FC00
00 FBFF
00 FBFE
00 FBFD
00 FBFC
00 FBFB
00 FBFA
R0 = _____________
SP = _____________
Direccin mem
00 FC04
00 FC03
00 FC02
00 FC01
00 FC00
00 FBFF
00 FBFE
00 FBFD
00 FBFC
00 FBFB
00 FBFA
R0 = _____________
SP = _____________
93
Direccin mem
00 FC04
00 FC03
00 FC02
00 FC01
00 FC00
00 FBFF
00 FBFE
00 FBFD
00 FBFC
00 FBFB
00 FBFA
R0 = _____________
SP = _____________
Direccin mem
00 FC04
00 FC03
00 FC02
00 FC01
00 FC00
00 FBFF
00 FBFE
00 FBFD
00 FBFC
00 FBFB
00 FBFA
R0 = _____________
SP = _____________
94
Direccin mem
00 FC04
00 FC03
00 FC02
00 FC01
00 FC00
00 FBFF
00 FBFE
00 FBFD
00 FBFC
00 FBFB
00 FBFA
R0 = _____________
SP = _____________
Direccin mem
00 FC04
00 FC03
00 FC02
00 FC01
00 FC00
00 FBFF
00 FBFE
00 FBFD
00 FBFC
00 FBFB
00 FBFA
95
T
Con rebotes
Sin rebotes
Cuando se producen rebotes suelen durar un tiempo mximo T, que se puede determinar de
forma experimental. En general, un mismo interruptor puede tener ms rebotes unas veces
que otras, pero s que es cierto que se puede siempre encontrar un cota mxima de T.
En todas las soluciones que se proponen a continuacin se debe apreciar que se usa un bucle
infinito, como toda aplicacin que funciona en un micro. Soluciones ms detalladas que las
que se muestran a continuacin en el contexto de un problema real estn en las secciones 14.2
y 14.3.
En un principio se va a valorar la solucin ms sencilla, sin rebotes. Consiste en esperar a que
la seal se ponga a uno, en ese momento se activa el Timer que se pone a medir tiempo, y
justo cuando se detecta que la seal se vuelve a poner a 0 entonces se apaga el Timer y se
actualizan los LED:
96
DP2 = 0x00FF;
DP7 = 0;
inicializacion(T0I=7,T0=-20000);
while (1){
while (P7.4==0) ;
T0R = 1;
while (P7.4==1);
T0R=0;
t=T0/20;
P2=t;
}
Este ejemplo tiene la pega de que slo puede medir el ancho de pulso de una lnea y sin
rebotes. Desde el punto de vista de la programacin la solucin es poco modular, ya que
mezcla el uso del Timer con el del puerto.
Una solucin ms modular supone usar un sistema muestreado, donde el periodo de muestreo
es un milisegundo. Se detecta cuando la seal se pone a uno y desde ese momento se muestrea
el pulso cada segundo, incrementando un contador que lleva la cuenta de milisegundos que
mide el ancho del pulso. En el momento que se detecta que la seal vuelve a 0, se actualizan
los LED.
DP2 = 0x00FF;
DP7 = 0;
while (1){
while (P7.4==0) ;
contador = 0;
while (P7.4==1){
retraso(1ms);
contador++;
}
P2 = contador;
}
97
Este ejemplo tiene la pega de que slo puede medir el ancho de pulso de una lnea y sin
rebotes. Si se quisiera aumentar la resolucin bastara con llamar a la funcin retraso con un
tiempo inferior a 1ms.
La siguiente solucin propuesta, aunque slo trata con una sola lnea, sera capaz de soportar
tantas como se quisieran simplemente haciendo que las variables anterior, actual, cuenta
y contador fueran vectores de tantos elementos como seales se quieren monitorizar. La idea
es que el sistema es muestreado siempre y no slo cuando se detecta el flanco, como en el
ejemplo anterior, de forma que cada ms se comprueba si hay un flanco en alguna de las
seales que se monitoriza, en este ejemplo slo se mira la lnea P7.4. Adems se ha aadido
una variable cuenta que indica si en una determinada lnea se ha detectado el pulso y por ello
se est midiendo el ancho del mismo en un determinado instante. Por lo dems el primer if se
encarga de detectar el flanco de subida de la seal y el segundo if se encarga de detectar el
flanco de bajada.
DP2 = 0x00FF;
DP7 = 0;
anterior = P7.4;
while (1){
retraso(1/8ms);
actual = P7.4;
if ((anterior != actual) && (actual == 1)) {
cuenta = 1;
}
if ((anterior != actual) && (actual == 0)) {
cuenta = 0;
P2 = contador;
contador = 0;
}
if(cuenta==1)
contador++;
anterior = actual;
}
98
DP2 = 0x00FF;
anterior = P2.8;
while (1){
retraso(1/8ms);
actual = P2.8;
if (anterior != actual)
retraso(100ms);
actual = P2.8;
if ((anterior != actual) && (actual == 1))
cuenta = 1;
else if ((anterior != actual) && (actual == 0)) {
cuenta = 0;
P2 = contador;
contador = 0;
}
if(cuenta==1)
contador++;
anterior = actual;
}
La pega de esta solucin es que se est perdiendo un tiempo de 100ms que no permite
monitorizar otras lneas y que adems es muy superior a lo que los rebotes pueden requerir.
Por ello esta solucin no soporta monitorizar varias seales de forma simultnea.
Por ltimo se presenta una solucin sin ninguna pega, que permite detectar el flanco justo
cuando se terminan los rebotes con independencia de lo que duren. La clave est en suponer
que el tiempo entre cambios de la seal cuando se producen rebotes es inferior a MAX (que
en un sistema real vale con que sea del orden de 3 o 4), ya que se comprueba el flanco en el
momento en el que no ha habido un cambio en la seal durante ms tiempo que MAX; es
decir se ha estabilizado la seal. La variable que comprueba esto es filtrado. Cuando
filtrado llega a 0 quiere decir que la seal se ha estabilizado y por lo tanto, es hora de
determinar si el flanco es de subida o de bajada.
99
DP2 = 0x00FF;
DP7 = 0;
anterior = P2.8;
while (1){
retraso(1/8ms);
actual = P2.8;
if (anterior != actual)
filtrado = MAX;
else if (filtrado >0)
filtrado--;
if (filtrado==0){
filtrado = -1;
if (actual==1) {
cuenta = 1;
}
else {
cuenta = 0;
P2 = contador;
contador = 0;
}
}
if(cuenta==1)
contador++;
anterior = actual;
}
13 Ejercicios
13.1 Acceso a memoria (15 min)
Dado un sistema digital como la tarjeta que se usa en laboratorio, se pide realizar un programa
que tenga las siguientes caractersticas:
Lee un dato de los interruptores conectados a la parte alta del puerto P2.
Accede a la posicin de memoria
Muestra el contenido de la posicin en los LEDs
100
Hacer un programa que encienda o apague un LED conectado al pin P2.0 del
microcontrolador C167, segn la posicin de un interruptor que se encuentra conectador en el
pin P2.1.
101
section code at 0H
proc NEAR
jmp main
endp
ends
DAT
origen
dest
DAT
ends
SM
main
copia:
fin:
ret
main
SM
endp
ends
end
102
Supuesto un sistema digital como el del laboratorio, se ha conectado al pin P7.4 del micro una
seal que consiste en una secuencia de pulsos. Realizar un programa que muestre en los
LEDs, conectados a la parte baja del puerto P2, la cuenta del nmero de pulsos que entran por
el pin P7.4.
103
Escribir un programa en ensamblador de C167 que consiste en una calculadora que opera con
datos de 4 bits (sin signo) y devuelve un resultado de 8 bits. El funcionamiento es el siguiente:
En los cuatro bits ms altos de P2 se proporciona el primer dato. Este dato se valida
cuando la lnea P2.8 pasa de 0 a 1.
A continuacin se introduce el segundo dato (siguiendo el mismo procedimiento que
para el primer dato).
Por ltimo se introduce la operacin (+: P2.12 = 1, -: P2.13=1). La operacin se valida
de la misma forma que los datos. En caso de que no se haya seleccionado una
operacin no se calcula el resultado.
El proceso anterior se repite indefinidamente, mantenindose en la parte baja de P2 el
resultado de la ltima operacin realizada.
$nonsegmented
S0
ini
ini
S0
section code at 0H
proc NEAR
jmp main
endp
ends
DAT
result
dato
DAT
ends
SM
main
104
main
SM
endp
ends
end
105
14 Ejercicios resueltos
14.1 LEDs e interruptores
Dado un sistema digital como la tarjeta que se usa en laboratorio, se pide realizar un programa
en ensamblador que tenga las siguientes caractersticas:
Lee un dato de los interruptores conectados a la parte alta del puerto P2.
Accede a la direccin de memoria indicada en los interruptores (8 bits ms altos de la
direccin todos a 0).
Analiza los ocho bits del dato almacenado en dicha direccin.
Si hay siete bits a 1 y uno a 0, se indica en los LED ms bajos de P2 la posicin del bit
que se encuentra a 0.
Si todos los bits se encuentran a 1 se ilumina el LED conectado a P2.4.
Si hay ms de un bit a 0 se encienden los cinco LED conectados a las lneas menos
significativas de P2.
void main(void) {
int dir;
unsigned char dato, mask, i, n, pos;
DP2 = 0xFF;
while (1) {
dir = (P2 & 0xFF00) >> 8;
dato = *((unsigned char*)dir);
for (i = 0, mask=0x01, n=0; i<8; i++, mask<<=1){
if (!(dato & mask)) {
pos = i;
n++;
}
}
if (n==0) {
P2 = ~0x10;
}
else if (n==1) {
106
SM
main
proc NEAR
bucle:
ini_for:
next_for:
fin_for:
if_1:
MOV
DP2, #0xFF
MOV
R0, P2
AND
R0, #0xFF00
SHR
R0, #8
MOV
R4, #0
MOVB
RL4, [R0]
MOV
R1, #0
; contador de for
MOV
R2, #1
; mscara
MOV
R3, #0
CMP
R1, #8
JMPR
cc_ugt, fin_for
MOV
R0, R4
AND
R0, R2
JMPR
cc_ne, next_for
MOV
ADD
R3, #1
ADD
R1, #1
SHL
R2, #1
JMPR
cc_uc, ini_for
CMP
R3, #0
JMPR
cc_ne, if_1
MOV
P2, #0xEF
JMPR
cc_uc, bucle
CMP
R3, #1
JMPR
cc_ne, if_n
107
Escribir un programa que mida el tiempo en segundos que un interruptor est a uno. Supuesto
un sistema como el del laboratorio, el programa debe medir el tiempo (en segundos) que est
en 1 el pin P2.8 del micro. Cada vez que el pin vuelva a cero se deber esperar de nuevo a que
se ponga a 1 para poder medir el tiempo en el que est a 1. Mostrar el resultado del ancho de
cada pulso en los diodos situados en la parte baja del puerto P2. Es necesario tener en cuenta
los rebotes que producen los interruptores en la seal.
NOTA: Los rebotes se podran eliminar con un filtro analgico paso bajo, pero tiene el
inconveniente de que usa resistencias y condensadores con valores que dependen mucho de la
temperatura. Es mucho ms robusto realizar un filtrado digital con un micro.
void main(void) {
int anterior, actual;
// estado anterior y actual de la seal
int cuenta=0;
// indica si hay que contar o no
int mseg=0;
// ancho del pulso
int filtrado = -1; // contador que mide el tiempo en el mismo
//estado de la seal cuando se trata de un rebote
DP2 = 0xFF;
anterior = P2 & 0x100;
while (1){
retraso(0,-2500); // espera 1ms
actual = P2 & 0x100;
if (anterior != actual)
filtrado = MAX;
else if (filtrado >0)
filtrado--;
if (filtrado==0){ // si la seal es estable
filtrado = -1;
if (actual) {
cuenta = 1;
}
else {
cuenta = 0;
P2 = ~(mseg/1000);
mseg = 0;
}
}
if(cuenta)
mseg++;
anterior = actual;
}
}
108
SM
main
bucle:
filtrado:
flanco:
cero:
seguir:
main
SM
cuenta
mseg
cuenta inicial del timer
preescalado
filtrado
anterior = actual
; actual = P2
109
Escribir en ensamblador un programa que mida el ancho de los pulsos de una seal digital.
Para ello el programa debe medir el tiempo (en milisegundos) que est en 1 el pin P7.4 del
micro. Cada vez que el pin vuelva a cero se deber esperar de nuevo a que se ponga a 1 para
poder medir el tiempo en el que est a 1. Supuesto un sistema como el del laboratorio, mostrar
el resultado del ancho de cada pulso en los diodos situados en la parte baja del puerto P2.
void main(void) {
int anterior, actual;
// estado anterior y actual de la seal
int cuenta=0;
// indica si hay que contar o no
int contador =0; // ancho del pulso
DP2 = 0xFF;
DP7 = 0;
anterior = P7 & 0x10;
while (1){
retraso(0,-2500); // espera 1ms
actual = P7 & 0x10;
if ((anterior != actual) && actual) {
cuenta = 1;
}
if ((anterior != actual) && !actual) {
cuenta = 0;
P2 = contador;
contador = 0;
}
if(cuenta)
contador++;
anterior = actual;
}
}
110
SM
main
bucle:
cero:
seguir:
main
SM
cuenta
contador
cuenta inicial del timer
preescalado
anterior = actual
; actual = P2
111
112
113
114
2 Concepto de driver
El driver es un conjunto de funciones que sirven para independizar al usuario de los detalles
de manejo de un perifrico, de manera que un posible desarrollador que quiera usar el
perifrico lo nico que tiene que hacer es usar el driver y de esa manera usa el perifrico sin
tener que conocer los detalles de implementacin y manejo del mismo.
Hay dos tipos de funciones dentro de un driver:
Funciones de inicializacin: slo se llaman una vez y sirven para inicializar el driver y
el perifrico.
Funciones de manejo: se llaman tantas veces como se necesiten en la ejecucin de un
programa.
Estas funciones tienen que estar perfectamente documentadas indicando Qu hace?, Qu
parmetros necesita?, Qu devuelve?. Esto es as ya que estas funciones son lo nico que
tiene que conocer cualquier desarrollador que quiera usar al perifrico.
Los drivers tienen las siguientes ventajas:
El desarrollador no tiene que conocerse los detalles de implementacin del perifrico.
Si el perifrico cambia su especificacin o versin, slo hace falta cambiar el driver,
pero no las aplicaciones que usan el driver.
Se puede reutilizar en cualquier aplicacin sin modificarlo.
2.1 Ejemplos de driver
Es necesario hacer notar que para un mismo perifrico se pueden hacer varios drivers, que
permiten independizar su manejo de los detalles de implementacin del mismo. Por ello, los
ejemplos que se muestran a continuacin son una posible versin de drivers. Adems los
drivers suelen estar en ficheros distintos del principal, ya que se usan como libreras, gracias a
su gran posibilidad de reutilizacin.
115
Este driver consta de una funcin de manejo y ninguna de inicializacin. Se podra hacer una
funcin de inicializacin que indicara el reloj que tiene el micro y parametrizar la funcin de
manejo en funcin del reloj.
2.3 Driver del convertidor AD
Para ms informacin de qu es un conversor AD y cmo se maneja, leer la seccin 3 de este
captulo. El driver que se muestra a continuacin configura al convertidor AD en modo 0.
116
117
ANALOGICO
ADCH
ADDAT
Convertidor AD
ADCIR
P5.0 (canal 0)
5V
0V
MUX
5V
0V
ADBSY ADST
6
---
ADM
0
ADCH
ADCH bits 0-3: estos 4 bits permiten seleccionar uno de los 16 canales posibles donde
hacer la conversin.
ADM bits 4-5: existen cuatro modos de funcionamiento del convertidor. Si ADM vale
0 se realiza una conversin en el canal seleccionado en ADCH. Existen otros modos
que no se van a usar en este curso, que permiten hacer conversiones sucesivas en el
mismo canal, una conversin secuencial en cada uno de los canales o incluso
conversiones sucesivas siguiendo la secuencia de los canales.
118
12
CANAL
9
---
RESULTADO
while (1) {
while(!ADCIR) ;
P2 = ~(ADDAT >> 2)
ADCIR = 0;
ADCON |= 0x80;
}
//
//
//
//
bucle de espera
coge 8 bits slo
flag fin = 0
Start ADST=1
119
Es necesario darse cuenta de que este programa sera ms modular y entendible si se usase el
driver descrito en la seccin 2.3 de este captulo:
#include <reg167.h>
#include ad.h // lugar donde est el driver
main() {
int resultado;
DP2 = 0x00FF;
P2 = 0xFFFF;
while (1) {
resultado = convad(0);
P2 = ~(resultado >> 2)
}
Como se puede ver es necesario incluir el driver (#include "ad.h"), para decirle al
compilador que la funcin convad existe.
120
121
unsigned int i;
unsigned char c = 0xff;
i = c;
int i;
char c = 0xff;
i = c;
Operador unitarios : cambia de signo al operando. As en: i = -c; el operador toma el valor de la variable c y le cambia el signo. Por tanto si c vale 17, al finalizar
la ejecucin de la instruccin, la variable i contendr el valor -17.
Operador unitario ++ y --: dado que una de las aplicaciones principales de los nmeros
enteros en los programas es la realizacin de contadores, que usualmente se
incrementan o decrementan de uno en uno, los diseadores de C vieron aconsejable
definir unos operadores para este tipo de operaciones. El operador incremento se
representa aadiendo a la variable que se desee incrementar dos smbolos +. La
sintaxis es por tanto: NombreVariable++. As por ejemplo la lnea: Contador++;
sumara uno a la variable Contador. El operador decremento es idntico al de
incremento, sin mas que sustituir los + por -. Siguiendo el ejemplo anterior: Contador-; Le restara uno a la variable Contador.
Operadores binarios: +, -, *: suma, resta y multiplicacin respectivamente.
Operador binario / (op1/op2): da como resultado la parte entera de dividir op1 entre
op2; es decir, el cociente. As por ejemplo 3/2 da como resultado 1, lo que puede
parecer malo, pero an hay cosas peores como 1/2, que da como resultado 0, lo cual
tiene un gran peligro en expresiones como: resultado = (1/2)*3;
Operador binario % (op1%op2): devuelve el resto de la divisin entre op1 y op2. El
operador resto se representa mediante el smbolo %. As por ejemplo 4%2 da como
resultado 0 y 1%2 da como resultado 1. Una utilidad de este operador es la de
122
4580169
Si a
= 1
yb
= 14
es TRUE
Si a
= 0
yb
= 32
es FALSE
123
c = a || b
Si a
Si a
= 0
yb = 0
yb = 0
= 32
es FALSE
es TRUE
c = ! b
Si b = 14
Si b = 0
es FALSE
c es TRUE
c = (a > b) && (a != d)
Si a
Si a
yb = 1
0yb = 1
= 0, d = 0
= 10, d =
es FALSE
c es TRUE
c
124
a
a
a
a
=
=
=
=
P2;
P2;
P2;
P2;
a
a
a
a
&= 0x000F;
|= 0x000F;
>>= 4;
<<= 4;
//
//
//
//
Cuanto
Cuanto
Cuanto
Cuanto
vale
vale
vale
vale
en
en
en
en
a?
a?
a?
a?
Figura 54: Si P2 = 0xFA45, por orden de aparicin a=0x0005, a=0xFA4F, a=0x0FA4 y a=0xA450
4 Instrucciones de control
Es habitual que los programas realicen tareas diferentes en funcin del valor de determinadas
variables. La instruccin if permite definir bloques de cdigo que no son de ejecucin
obligatoria y por lo tanto son bloques de instrucciones que el programa se puede saltar. Esta
decisin depende del valor de una condicin lgica, que a su vez suele depender del valor que
adquieran determinadas variables del programa. Tambin es posible definir dos bloques de
instrucciones alternativos, de manera que slo se ejecute uno u otro en funcin del resultado
de la condicin lgica.
Existen dos formatos bsicos para definir instrucciones que se ejecutan de manera
condicional. Un bloque que se puede ejecutar o no (if normal), y dos bloques que se
ejecutan uno u otro (bloque if-else). El formato en cada caso es el siguiente:
if (condicin) {
...
}
if (condicin) {
// si se cumple a condicin
...
} else {
// si no se cumple la condicin
...
125
A continuacin se describen situaciones que se deben tener en cuenta cuando se manejan este
tipo de condiciones:
if(a == b) {
if(a = b) {
Figura 55: Igualdad versus asignacin. En el caso de la igualdad la condicin se cumple si a es igual a b. En el
caso de la asignacin la condicin se cumple si b es distinto de 0.
if(a && b) {
if(a & b) {
Figura 56: AND lgico versus AND bit a bit. En el caso del AND lgico la condicin se cumple si a y b son
distintos de cero. En el caso del AND bit a bit la condicin puede no cumplirse aunque a y b sean distintos de
cero, por ejemplo si a =0xF0F0 y b = 0x0F0F.
5 Bucles
Es habitual que los programas realicen tareas repetitivas o iteraciones (repetir las mismas
operaciones pero cambiando ligeramente los datos). Esto no supone ningn problema para el
programador novato, que despus de aprender a cortar y pegar puede repetir varias veces el
mismo cdigo, pero dentro de un lmite. Se llama bucle de un programa a un conjunto de
instrucciones que se repite varias veces.
El bucle for queda definido por tres argumentos: sentencia inicial, condicin de salida y
sentencia final de bucle. Estos argumentos se escriben separados por punto y coma y no por
coma como en las funciones.
for(sentencia inicial ; condicin ; sentencia final ){
...
}
Como puede apreciarse, la variable i controla el nmero de veces que se ejecuta el bucle.
Por ello a este tipo de variables se les denomina variables de control. Es muy importante
hacer un buen uso del sangrado para facilitar la lectura del programa.
La sentencia inicial se ejecuta antes de entrar en el bucle y la condicin siempre se
comprueba antes de cada iteracin. Por lo tanto, puede ocurrir que nunca se llegue a entrar en
el bucle si desde el principio no se cumple la condicin. Por ejemplo for(i=0; i<-3;
126
i++) nunca entra en el bucle. La sentencia final se ejecuta al terminar cada iteracin. Por lo
tanto si no se entra en el bucle esta sentencia no se ejecuta nunca.
El funcionamiento del bucle es como sigue: en primer lugar se evala la expresin condicin.
Si el resultado es falso no se ejecutar ninguna de las instrucciones del bucle, el cual est
delimitado, al igual que en el caso del bucle for por dos llaves ({ y }). Por tanto la
ejecucin continuar despus de la llave }. Si por el contrario la condicin es cierta, se
ejecutarn todas las instrucciones del bucle. Despus de ejecutar la ltima instruccin del
bucle se vuelve a comprobar la condicin y al igual que al principio se terminar el bucle si es
falsa o se realizar otra iteracin si es cierta, y as sucesivamente.
do{
...
}while(condicin);
6 Vectores
Un vector es un conjunto de datos del mismo tipo que se almacenan en el ordenador en
posiciones de memoria consecutivas y a los cuales se accede mediante un mismo nombre de
variable. La caracterstica fundamental es que todos los datos de un vector son del mismo
tipo, por ejemplo todos son int o todos son double. La definicin de vectores es parecida
a la definicin de variables, salvo que se debe especificar el tamao del vector entre corchetes
[ ]. Por ejemplo, para definir un vector llamado vec que pueda almacenar 10 valores tipo
double se escribe:
double vec[10];
La manera de acceder a los valores de estas variables es ahora diferente, ya que de todos los
elementos que componen el vector es necesario especificar cul queremos modificar. Esta
especificacin se realiza indicando entre corchetes el nmero de orden del elemento, teniendo
en cuenta que la numeracin de los elementos siempre empieza en cero.
vec[0]=54.23;
vec=4.5;
127
vec[10]=98.5;
/* ERROR */
La segunda asignacin no es correcta porque vec no es una variable tipo double sino un
vector y por lo tanto todas las asignaciones deben indicar el nmero de orden del elemento al
que queremos acceder. El vector vec del ejemplo tiene tamao 10 porque fue declarado
como double vec[10] esto significa que almacena 10 elementos. Los 10 elementos se
numeran desde el 0 hasta el 9 y por lo tanto, el elemento 10 no existe y la tercera asignacin
tampoco es vlida. Es importante destacar que el compilador no dara error en el ltimo caso
ya que no comprueba los rangos de los vectores. Este tipo de asignacin hace que el valor
98.5 se escriba fuera de la zona de memoria correspondiente al vector vec, y probablemente
machaca los valores de otras variables del programa. Hay que prestar especial atencin ya que
slo en algunos casos aparece un error de ejecucin (generalmente cuando el ndice del vector
es muy grande de forma que se accede a zonas en las que no hay memora) pero en otras
ocasiones no aparece ningn mensaje de error y el programa simplemente funciona mal
(OJO: es muy difcil de detectar!!).
Como se puede sospechar, la mejor manera de trabajar con vectores es utilizando bucles for,
para iterar en el mismo y o bien inicializar sus valores o bien obtener sus datos que almacena.
Resulta muy til definir los tamaos de vectores y matrices en funcin de parmetros, ya que
luego se pueden modificar fcilmente sin tener que revisar todo el programa. Estos
parmetros se definen con la instruccin #define (semejante a equ en ensamblador) que
es una directiva del preprocesador, al igual que #include.
Existen dos maneras de inicializar vectores: en las instrucciones del programa o en la propia
definicin:
#define MAX 5
main() {
int Iniciado[]={1,5,8,4,3};
int SinIni[MAX];
int i;
for (i=0; i<MAX; i++)
SinIni[i] = Iniciado[i];
}
Figura 57: Ejemplo de inicializacin en C de un vector en la propia definicin (Iniciado) o por cdigo (SinIni)
128
MAX
equ
D100
Iniciado
SinIni
D100
ej
ej1
ini:
fin:
ej1
ej
;contador
Figura 58: Ejemplo de inicializacin en ensamblador de un vector en la propia definicin (Iniciado) o por
cdigo (SinIni)
7 Punteros
Los punteros son un tipo de variable un poco especial, ya que en lugar de almacenar valores
(como las variables de tipo int o de tipo double) los punteros almacenan direcciones de
memoria. Utilizando variables normales slo pueden modificarse valores, mientras que
utilizando punteros pueden manipularse direcciones de memoria o valores.
Los punteros se definen igual que las variables normales, pero con un asterisco (*) delante del
nombre de la variable. Por ejemplo:
int a;
int *pa;
En este ejemplo la variable a es de tipo entero y puede almacenar valores como 123 o -24,
mientras que la variable p es un puntero y almacena direcciones de memoria. Aunque todos
129
los punteros almacenan direcciones de memoria, existen varios tipos de puntero dependiendo
de la definicin que se haga. Por ejemplo:
int *pti;
char *ptc;
Tanto pti como ptc son punteros y almacenan direcciones de memoria, por lo tanto
almacenan datos del mismo tipo y ocupan la misma cantidad de memoria, es decir, 24 bits
(aunque si se trabaja en el modelo corto de memoria; es decir, slo se trabaja en un segmento,
como es el caso del laboratorio el tamao utilizado es 16 bits). La nica diferencia es que el
dato almacenado en la direccin de memoria contenida en el puntero pti es un entero,
mientras que el dato almacenado en la direccin de memoria contenida en ptc es un char.
Se dice por lo tanto que pti apunta a un entero (puntero a entero) mientras que ptc
apunta a un char (puntero a char). En la Figura 59 aparece un ejemplo en el que pti
apunta a una direccin de memoria (0x200) donde se encuentra almacenado el valor entero 5,
y ptc apunta a otra direccin de memoria (0x202) donde se encuentra almacenado el valor
0x10.
Direccin de
memoria 24
bits (Hex)
00 0200
00 0201
00 0202
00 0203
00 0204
00 0205
00 0206
00 0207
00 0208
00 0209
00 020A
00 020B
Variable en C
i
c
pti
ptc
Valor 8 bits
05
00
10
02
00
02
00
00
02
02
00
00
Figura 59
130
int i;
int *pti;
i=78;
pti=&i;
/*
/*
/*
/*
variable entera */
puntero a entero */
inicializo i */
pti apunta a i */
/*
/*
/*
/*
variable entera */
puntero a entero */
pti apunta a i */
equivale a hacer i=78; */
131
D100
i1
i2
D100
ej
ej1
section
proc
MOV
MOV
MOV
MOV
MOV
endp
ends
ej1
ej
code at 300H
NEAR
R0,#5
; R0 registro auxiliar
i1,R0
R1,#i1
; R1 puntero a i1
R0,[R1] ; equivale a * en C
i2,R0
main() {
int i1,i2;
int *pi;
i1 = 5;
pi = &i1;
i2 = *pi;
// i2?
}
Figura 60: Equivalencia entre C y ensamblador en el manejo de punteros
La primera lnea suma 8 al entero al que apunta pti y por lo tanto el puntero sigue
apuntando al mismo sitio. La segunda lnea incrementa en 8 el puntero, por lo tanto ste pasa
a apuntar a otro sitio.
El operador ++ se utiliza mucho en programacin de micros y permite hacer que el puntero
pase a apuntar al dato que ocupa la siguiente posicin de memoria. La aplicacin fundamental
132
i = *pti++
i = *--pti
133
MAX
equ
D100
VWord
VByte
D100
ej
ej1
section
proc
MOV
MOV
MOV
MOV
MOV
CMP
JMPR
MOVB
ADD
ADD
ADD
JMPR
NOP
endp
ends
suma:
fin:
ej1
ej
code at 300H
NEAR
R0,#VWord; R0
R1,#VByte; R1
R2,#0
; R2
R3,#0
; R3
R4,#0
; R4
R3,#MAX
cc_uge, fin
RL4,[R1+]
R2, R4
R2, [R0+]
R3, #1
cc_uc, suma
puntero
puntero
es la suma
es i
var temporal
#define MAX 5
main() {
int VWord[MAX];
char VByte[MAX];
int *pti;
char *ptc;
int i; suma;
suma = 0;
pti = VWord;
ptc = VByte;
for (i=0; i<MAX; i++){
suma += *pti++;
suma += *ptc++;
}
}
Figura 62: Ejemplo en C de dos vectores que suma 2 vectores de 5 datos
134
/* vector de enteros */
/* puntero a entero */
/* pti apunta a vi */
/*coge el dato 5 enteros adelante */
8 Funciones
Una tcnica muy empleada en la resolucin de problemas complejos es la conocida como
divide y vencers. La manera ms elegante de construir un programa es dividir la tarea a
realizar en otras tareas ms simples. Si estas tareas ms simples no son an lo suficientemente
sencillas, se vuelven a dividir, procediendo as hasta que cada tarea sea lo suficientemente
simple como para resolverse con unas cuantas lneas de cdigo. A esta metodologa de diseo
se le conoce como diseo de arriba-abajo (top-down). Otras ventajas de la divisin de un
problema en mdulos claramente definidos es que facilita el trabajo en equipo y permite
reutilizar mdulos creados con anterioridad si estos se han diseado de una manera
generalista. En C este tipo de mdulos se llaman funciones, que consta de unos argumentos de
entrada, una salida, y un conjunto de instrucciones que definen su comportamiento. Esto
permite aislar la funcin del resto del programa, ya que la funcin puede considerarse como
un programa aparte que toma sus argumentos de entrada, realiza una serie de operaciones
con ellos y genera una salida; todo ello sin interactuar con el resto del programa.
Esta metodologa de diseo presenta numerosas ventajas, entre las que cabe destacar:
La complejidad de cada tarea es mucho menor que la de todo el programa, siendo
abordable.
Se puede repartir el trabajo entre varios programadores, encargndose cada uno de
ellos de una o varias funciones de las que se compone el programa.
Al ir construyendo el programa por mdulos se pueden ir probando estos mdulos
conforme se van terminando, sin necesidad de esperar a que se termine el programa
completo. Esto hace que, tanto la prueba de los mdulos, como la correccin de los
errores cometidos en ellos, sea mucho ms fcil al tener que abarcar solamente unas
cuantas lneas de cdigo, en lugar de las miles que tendr el programa completo.
Una vez construido el programa, tambin el uso de funciones permite depurar los
problemas que aparezcan ms fcilmente, pues una vez identificada la funcin que
falla, slo hay que buscar el fallo dentro de dicha funcin y no por todo el programa.
Si se realizan lo suficientemente generales, las tareas se puede reutilizar en otros
programas. As por ejemplo, si en un programa es necesario convertir una cadena a
135
maysculas y realizamos una funcin que realice dicha tarea, esta funcin se podr
usar en otros programas sin ningn cambio. Si por el contrario la tarea de convertir a
maysculas se incrusta dentro del programa, su reutilizacin ser muchsimo ms
difcil.
En resumen la norma bsica es <<Hacer funciones cortas y que hagan una sola cosa!!
(fciles de desarrollar y de depurar, reutilizables)>>
En C las funciones deben ser declaradas (en los ficheros cabecera *.h) y definidas. La
diferencia entre declaracin y definicin consiste en que siempre que se habla de definicin
implica que existe una reserva de memoria para aquello que se define. En cambio en un
declaracin no.
Un ejemplo de definicin de funcin es el cdigo propiamente dicho:
En la Figura 63 se puede observar como una funcin tiene unos parmetros que entran en la
funcin y se usan como variables locales; es decir, si se modifica su valor en la funcin el
programa principal que llam a la funcin no ve afectados sus valores. Adems la funcin
puede devolver un valor en la instruccin return.
En la Figura 64 se muestra el prototipo o declaracin de la funcin anterior. El prototipo
indica al compilador el nmero y tipo de parmetros de entrada a una funcin y el tipo de
variable de salida de la misma.
int power(int base, int n);
Figura 64: Ejemplo de declaracin de funcin en C
136
En el ejemplo se puede apreciar que el valor de las variables de la funcin main a y b no son
alterados por la llamada a la funcin power. Esto se debe a que en C SIEMPRE se pasan los
parmetros por valor a las funciones; es decir, se hace una copia cada variable y se pasa la
propia copia a la funcin. Es necesario declarar la funcin power antes de la definicin de
main, para que cuando el compilador empiece a compilar (que lo hace de forma secuencial y
de arriba hacia abajo) el cdigo sepa como se ha definido power y compruebe la correcta
sintaxis de la misma. En caso contrario el compilador indicar que la funcin no se ha
declarado.
8.1 Variables globales y locales
Las variables locales son las que slo se pueden acceder dentro de una misma funcin, que se
componen por las variables pasadas por parmetro y las definidas en la propia funcin. La
vida de una variable local se termina en el momento en el que el programa sale de la funcin.
Por otro lado, las variables globales son las que se encuentra fuera de toda funcin y se
pueden usar desde cualquier lugar del cdigo. La vida de una variable global se corresponde
con el tiempo de ejecucin del programa. El buen estilo de programacin recomienda usar lo
menos posible las variables globales ya que se pueden modificar desde cualquier lugar del
cdigo y por tanto son muy complicadas de depurar.
8.2 Paso de parmetros por "referencia"
Como se ha comentado con anterioridad en C siempre se pasan a las funciones los parmetros
por valor. Por otro lado, dado que una funcin slo puede devolver un valor, se restringen
mucho las posibilidades de hacer funciones complejas que puedan devolver ms de un valor.
Para poder salvar este escollo, existe un truco que permite pasar los parmetros por
"referencia" usando punteros. No es un paso por referencia estrictamente hablando, sino que
137
es un paso por valor de un puntero, que equivale a un paso por referencia, ya que aunque no
se puede cambiar la direccin del puntero (ya que se ha pasado por valor y por lo tanto se pasa
una copia), se puede modificar el contenido de la direccin de memoria a la que apunta el
puntero con el operador unario * (derreferenciacin). En la Figura 66 se muestra a la
izquierda un ejemplo de paso de parmetros por valor, en donde a y b quedan inalterados
porque la funcin swap (que pretende cambiar el valor de la variable x por el de la y) est
trabajando con una copia de las variables a y b. En cambio en la derecha se muestra un
ejemplo en el que se pasa por valor los punteros a las variables a y b, y que aunque se trabaja
con las copias de dichos punteros, se puede modificar el valor del contenido de los mismos,
resultando en que a y b intercambian su valor despus de la llamada a la funcin swap.
void swap(int x, int y) {
int temp;
temp = x;
x = y;
y = temp;
}
main () {
int a=5, b=6;
swap(a,b);
}
138
#define MAX 5
int max(int* v, int tam);
main() {
int vector[]={1,5,8,4,3};
int maximo;
maximo = max(vector, MAX);
}
/* funcin que calcula el mximo de un vector */
int max(int* v, int tam){
int maximo=0; i=0;
for (i=0; i<tam; i++){
if (v[i] > maximo)
maximo = vector[i];
}
return (maximo);
}
Figura 67: Ejemplo de paso por parmetro de un vector
Por otro lado, si lo que se quiere es devolver como resultado de una funcin un vector es
imprescindible hacerlo a travs de un parmetro de la misma o no a travs de la instruccin
return. En la Figura 68 se muestra el tpico manejo errneo de un vector, ya que es una
variable local que desaparece al terminar la funcin y por lo tanto aunque el programa
principal recupere la direccin de memoria devuelta, el vector se encuentra vaco. Por otro
lado, en la Figura 69, Figura 70 y Figura 71 se muestran buenas prcticas de programacin, en
donde se reserva la memoria para el vector en el lugar que va a ser utilizado.
#define MAX 50
main() {
int *pti;
pti = get_vector_discreto(1, 4);
}
/* funcin que devuelve un vector con los valores enteros
comprendidos entre min y max*/
int* get_vector_discreto(int min, int max){
int i, vector[MAX];
for (i=min; i<max; i++)
vector[i] = i;
return (vector);
}
Figura 68: Manejo errneo de vuelta de un vector por una funcin
139
#define MAX 50
main() {
int vector[MAX];
get_vector_discreto(1, 4, vector);
}
/* funcin que devuelve un vector con los valores enteros
comprendidos entre min y max*/
void get_vector_discreto(int min, int max, int* v){
int i;
for (i=min; i<max; i++)
*(v+i) = i;
}
Figura 69: Manejo tipo puntero
#define MAX 50
main() {
int vector[MAX];
get_vector_discreto(1, 4, vector);
}
/* funcin que devuelve un vector con los valores enteros
comprendidos entre min y max*/
void get_vector_discreto(int min, int max, int* v){
int i;
for (i=min; i<max; i++)
v[i] = i;
}
Figura 70: Manejo tipo vector
#define MAX 50
main() {
int vector[MAX];
get_vector_discreto(1, 4, vector);
}
/* funcin que devuelve un vector con los valores enteros
comprendidos entre min y max*/
void get_vector_discreto(int min, int max, int* v){
int i;
for (i=min; i<max; i++)
*v++ = i;
}
Figura 71: Uso de post-incremento
140
9 Cuestiones de comprensin
1) Dada la siguiente definicin de variable
char c;
CASO B)
b = 0;
a = 0;
if (a = 0)
b = 1;
CASO C)
b = 0;
a = 0;
if (a = 1)
b = 1;
CASO D)
b = 0;
a = 0;
if (a)
b = 1;
= ______________
= ______________
= ______________
141
equ
D100
i
j
k
D100
ej
ej1
fin:
ej1
ej
-1
;1
;2
;3
;4
;5
;6
;7
;8
;9
;10
;11
;12
;13
;14
142
5) Para cada uno de los programas siguientes: Est correctamente programado? En caso
afirmativo Qu saca por pantalla? En caso negativo, razona la respuesta. Debajo de cada
programa se ha dibujado un recuadro que representa la pantalla donde se deben escribir los
resultados.
double *pdActualiza();
main ()
{
double *pd=NULL;
pd=pdActualiza();
printf("2 %f\n",pd[0]);
return 0;
}
main(){
int i=74;
double *pdActualiza()
{
double md[3]={1.2,-2.3,-1.4};
double *pd=md;
printf("1 %f\n",pd[0]);
return pd;
}
Efecto2000(&i);
printf("%d",i);
}
void Efecto2000(int *pa){
*pa=*pa+1900;
}
143
7) Supuesto un sistema digital como el del laboratorio, explica con tus palabras qu hacen los
programas siguientes:
La lnea 6 de este programa coge el contenido de lo que hay en la direccin de memoria
indicada por el valor de dir y lo guarda en dato.
void main(void) {
int dir, dato;
DP2 = 0xFF;
while (1) {
dir = (P2 & 0xFF00)>>8;
dato = *((char*)dir);
P2 = dato;
}
void main(void) {
unsigned char dato, mask, i, n;
DP2 = 0xFF;
while (1) {
dato = (unsigned char)((P2 & 0xFF00) >> 8);
mask=0x01; n=0;
for (i = 0; i<8; i++){
if (!(dato & mask))
n++;
mask<<=1;
}
if (n==0)
P2 = 0xFF;
else
P2 = 0;
}
144
10 Ejercicios propuestos
10.1 Timer y puertos (40 min)
Dado un sistema como la tarjeta del laboratorio, escribir un programa en C que realice las
siguientes operaciones:
void main(void) {
145
Lee un dato de los interruptores conectados a la parte alta del puerto P2.
Accede a la direccin de memoria indicada en los interruptores (8 bits ms altos de la
direccin todos a 0).
Analiza los ocho bits del dato almacenado en dicha direccin.
Si hay siete bits a 1 y uno a 0, se indica en los LED ms bajos de P2 la posicin del bit
que se encuentra a 0.
Si todos los bits se encuentran a 1 se ilumina el LED conectado a P2.4.
Si hay ms de un bit a 0 se encienden los cinco LED conectados a las lneas menos
significativas de P2.
void main(void) {
146
147
11 Ejercicios resueltos
11.1 La calculadora (30 min)
Partiendo de un sistema como el del laboratorio, escribir un programa en C que consiste en
una calculadora que opera con datos de 4 bits (sin signo) y devuelve un resultado de 8 bits. El
funcionamiento es el siguiente:
En los cuatro bits ms altos de P2 se proporciona el primer dato. Este dato se valida
cuando la lnea P2.8 pasa de 0 a 1.
A continuacin se introduce el segundo dato (siguiendo el mismo procedimiento que
para el primer dato).
Por ltimo se introduce la operacin (+: P2.12 = 1, -: P2.13=1 y *: P2.14=1). La
operacin se valida de la misma forma que los datos. En caso de que no se haya
seleccionado una operacin no se calcula el resultado.
El proceso anterior se repite indefinidamente, mantenindose en la parte baja de P2 el
resultado de la ltima operacin realizada.
La lnea P2.8 llevar un filtrado de rebotes.
148
149
#include <reg167.h>
#define MAX 3
void retraso(int mseg);
void main(void){
int contador=-1, ciclo=0, dato[3], result;
unsigned int anterior,actual, temp;
DP2 = 0x00FF;
P2 = ~0;
anterior = P2 & 0x0100;
while(1) {
retraso(1);
temp = P2;
actual = temp & 0x0100;
if (anterior != actual)
contador = MAX;
else if (contador > 0)
contador--;
else ;
// 1 ms
anterior = actual;
if (contador == 0) {
contador = -1;
if (actual)
dato[ciclo++] = temp >> 12; //recogida de dato
}
if(ciclo == 3) {
ciclo = 0;
// Operaciones
150
151
#include <reg167.h>
void retraso(int prees, int cuenta);
void main(void) {
int sentido = 0, dato = 0x01;
int prees, cuenta, temp;
DP2 = 0x00ff;
P2 = ~dato;
while(1) {
temp = P2;
prees = temp & 0x0700;
prees >>= 8;
cuenta = temp & 0xF800;
retraso(prees, cuenta);
if (!sentido){
P2 = ~(dato <<= 1);
if (dato & 0x80)
sentido = 1;
}
else {
P2 = ~(dato >>= 1);
if (dato & 0x01)
sentido = 0;
}
}
}
152
153
La solucin propuesta utiliza una funcin filtraP2, que no es ms que una funcin genrica de
filtrado de rebotes del puerto P2. Eso significa que se puede usar en cualquier programa que
requiera filtrado de rebotes en P2. Se podra decir que es un driver del puerto P2 cuando se le
conectan interruptores, ya que el usuario no necesita conocer los detalles del puerto ni de los
interruptores y slo se tiene que preocupar del estado de los mismos. Adems es un driver con
autoinicializacin ya que detecta cundo se utiliza por primera vez e inicializa las variables
que necesita.
#include <reg167.h>
#define MAX 3
void retraso(int mseg);
int contador[16],inicializado=0;
unsigned int anterior[16], estado[16];
int filtraP2(int nlinea) {
int actual, temp, mascara, i;
if (!inicializado)
for (i=0; i<16; i++) {
DP2 = 0x00FF;
P2 = ~0;
contador[i] = 0;
anterior[i] = 0;
estado[i] = 0;
inicializado = 1;
}
temp = P2;
mascara = 1;
mascara <<= nlinea;
actual = temp & mascara;
if (anterior[nlinea] != actual)
contador[nlinea] = MAX;
else if (contador[nlinea] > 0)
contador[nlinea]--;
else ;
anterior[nlinea] = actual;
if (contador[nlinea] == 0) {
contador[nlinea] = -1;
if (actual)
estado[nlinea] = 1;
else
estado[nlinea] = 0;
}
return estado[nlinea];
}
void main(void){
int personas=0;
int anterior8,anterior9,actual8,actual9;
anterior8 = 0;
anterior9 = 0;
while(1) {
retraso(1);
actual8 = filtraP2(8);
if (anterior8 != actual8 && actual8)
if (personas < 50)
personas++;
anterior8 = actual8;
actual9 = filtraP2(9);
if (anterior9 != actual9 && !actual9)
if (personas > 0)
personas--;
anterior9 = actual9;
P2 = ~personas;
}
}
// flanco de subida
// flanco de bajada
154
155
156
157
Captulo 9 INTERRUPCIONES
1 Objetivos y conceptos a entender en este captulo
En este captulo se debe aprender qu es una interrupcin y el proceso que se sigue para
atenderla. Se recomienda adems, que se hagan esquemas que permitan analizar el
paralelismo que hay entre el tratamiento de perifricos por polling y por interrupcin.
158
xxIC
15
xxIR xxIE
2 1
ILVL
GLVL
Por otro lado, el registro PSW juega un papel muy importante en las interrupciones:
15
12
ILVL
11
IEN
159
El bit 11, IEN (Global Interrupt Enable), es un bit que permite habilitar o deshabilitar de
forma global las interrupciones. La utilidad radica en que si en algn momento se quiere
ejecutar un trozo de cdigo al cual no se puede interrumpir (zona crtica), se puede poner a
cero este bit, en vez de tener que poner a cero cada uno de los bits xxIE de cada una de las 56
fuentes de interrupcin.
Los 4 bits que van del 12 al 15, indican la prioridad del programa en curso. De forma que una
interrupcin solamente puede interrumpir a la CPU si el ILVL del registro xxIC de la
interrupcin correspondiente, es mayor que el ILVL del PSW. El ILVL del PSW es cero
cuando se ejecuta main.
Por lo tanto para que un perifrico funcione en modo interrupcin es necesario configurarlo
de la siguiente manera:
Habilitar la interrupcin, bit xxIE del registro xxIC.
Poner la prioridad de la interrupcin, ILVL del registro xxIC.
Habilitar de forma global las interrupciones, bit IEN del registro PSW.
4 Ejemplos
A continuacin se muestra un ejemplo que usa el Timer 0 usando interrupciones para llevar a
cabo un calendario. El programa principal muestra en los LEDs conectados en la parte baja de
P2 los minutos transcurridos o las horas dependiendo de un interruptor conectado al pin
P2.15.
Es importante darse cuenta de la sintaxis de la funcin de tratamiento de interrupcin. No se
la pueden pasar parmetros, ya que NO se puede invocar explicitamente. Es la propia CPU
la que hace que se ejecute cuando el Timer rebosa. El nmero 0x20 que aparece en la
definicin de la funcin indica el vector de interrupcin que usa para ser invocada por la
CPU. La CPU sabe que es la funcin que atiende al Timer 0 NO por llamarse timer0 sino
por corresponder al vector de interrupcin 0x20 que es el que atiende las peticiones del
Timer 0. Como no se pueden pasar parmetros a la funcin de interrupcin, la comunicacin
de la misma con el programa principal se debe hacer usando variables globales.
Dado que la funcin de tratamiento de la interrupcin se ejecuta a mayor prioridad que el
resto del cdigo, es necesario que sea corta; es decir, est absolutamente prohibido poner
algn bucle en la misma. En caso contrario podra suceder que el resto de cdigo no tuviera
casi tiempo de CPU para ejecutarse.
160
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int ticks, sec, min, hour;
void main(void) {
ticks = sec = min = hour = 0;
T01CON = 0x00;
T0REL = PERIOD;
/* set reload value */
T0
= PERIOD;
T0IC = 0x44;
/* set T0IE and ILVL = 1 */
IEN
= 1;
/* set global interrupt enable flag */
T0R = 1;
/* start timer 0 */
DP2 = 0x00FF;
while(1){
if (P2 & 0x8000) P2 = ~min;
else P2 = ~hour;
}
}
161
5 Prctica 7: interrupciones en C
162
163
164
Captulo 10
2 Sistemas muestreados
Un sistema muestreado se basa en que cada cierto intervalo de tiempo, llamado periodo de
muestreo, realiza determinadas tareas sncronas. La ventaja de estos sistemas es que se sabe
con bastante precisin en qu momento se ejecutan las tareas, lo que les hace ideales para
llevar un control de tiempo o realizar mediciones. Por otro lado, las tareas muestreadas
dejan ms tiempo libre de CPU, ya que entre intervalos de muestreo la CPU queda libre.
Volviendo al ejemplo del captulo 6 seccin 12, si se quiere medir el ancho de un pulso que
entra por lnea P7.4 la solucin de un sistema sin muestrear es la siguiente:
DP2 = 0x00FF;
DP7 = 0;
inicializacion(T0I=7,T0=-20000);
while (1){
while (P7.4==0) ;
T0R = 1;
while (P7.4==1);
T0R=0;
t=T0/20;
P2=t;
}
Esta solucin no es muestreada porque est mirando continuamente de forma asncrona el
estado del pin P7.4. Para poder determinar el ancho del pulso tiene que usar un Timer, ya que
por la propia ejecucin de lneas de cdigo no sera factible estimar este tiempo.
En cambio en un sistema muestreado la solucin se basa en el uso de un reloj que lleva la
cuenta del periodo de muestreo, y slo se mira el estado de la lnea una vez en cada intervalo.
La ventaja es que el chequeo de la lnea no consume casi CPU y se hace a intervalos
165
regulares, lo que supone que contando el nmero de intervalos se puede tener una medida del
ancho del pulso.
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int cuenta=0, anterior, actual, contador;
void main(void) {
T01CON = 0x00;
T0REL = PERIOD;
T0
= PERIOD;
T0IC = 0x44;
IEN
= 1;
T0R = 1;
DP2 = 0x00FF;
DP7 = 0;
anterior = P7.4;
while(1);
}
166
Por ejemplo, si un sistema tiene que medir el ancho de pulso con una resolucin de 1
milisegundo y adems generar un pulso de 350 microsegundos en algn momento, claramente
el periodo de muestreo es el mximo comn divisor de 350 us y 1 ms, que es 50 us.
3 Fechado
Cuando el funcionamiento de un sistema depende del tiempo, que suele ser casi siempre, es
necesario llevar un reloj calendario. Aunque se puede hacer de muchas maneras, en esta
seccin se pretende dar una metodologa para hacer programas que dependan del tiempo de
forma sencilla y simple.
Lo primero que es necesario saber es la medida mnima que se quiere poder contabilizar; es
decir, la resolucin de la medida de tiempo. En caso de ser un sistema muestreado, esta
resolucin coincide con el periodo de muestreo.
En segundo lugar, cada medida de tiempo que se quiere tener supone la creacin de un nuevo
contador de tiempo. Por ejemplo, si se quiere ejecutar el evento 1 cada segundo y el evento 2
cada 350 milisegundos, para un periodo de muestreo de 1 ms, el cdigo resultante sera:
167
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int mseg_evento1=0, mseg_evento2=0;
void main(void) {
T01CON = 0x00;
T0REL = PERIOD;
T0
= PERIOD;
T0IC = 0x44;
IEN
= 1;
T0R = 1;
while(1){
if (mseg_evento1
mseg_evento1
// programar
}
if (mseg_evento2
mseg_evento2
// programar
}
}
}
168
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int mseg_evento1=0, mseg_evento2=0;
void main(void) {
T01CON = 0x00;
T0REL = PERIOD;
T0
= PERIOD;
T0IC = 0x44;
IEN
= 1;
T0R = 1;
while(1){
retardo(1);
mseg_evento1++;
mseg_evento2++;
if (mseg_evento1
mseg_evento1
// programar
}
if (mseg_evento2
mseg_evento2
// programar
}
}
}
== 1000) {
= 0;
aqu el evento 1
== 350) {
= 0;
aqu el evento 2
En caso de que la medida de tiempo sea asncrona; es decir, se hiciera a partir de un evento
que no se puede predecir cundo sucede, sera el propio evento el que pondra a cero el
contador.
169
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int mseg_evento1=0;
void main(void) {
T01CON = 0x00;
T0REL = PERIOD;
T0
= PERIOD;
T0IC = 0x44;
IEN
= 1;
T0R = 1;
while(1){
if ( ) { // si
mseg_evento1
}
if (mseg_evento1
// programar
}
}
}
170
2. Para cada uno de esos procesos, se deben determinar las etapas de las que consta y
cul es la inicial.
3. Una vez determinadas las etapas, se deben definir las variables que hacen que el
proceso vaya de una etapa a otra.
De forma equivalente se puede definir este mtodo segn una metodologa basada en estados:
1. Determinar las mquinas de estado que tiene el sistema
2. Para cada mquina de estado, se deben determinar cuales son los estados de los que
consta.
3. Una vez determinados los estados, se deben definir las variables que definen los
cambios de estado
Desde el punto de vista software este mtodo quedara:
1. Definir una funcin por cada mquina de estados y definir una variable global que
permita conocer el estado actual en que se encuentra la mquina. Por ejemplo:
int estado_actual=0;
void maquina(){...}
2. Definir una funcin de estado, por cada estado de cada mquina de estados. Estas
funciones sern invocadas desde la funcin que controla cada mquina de estados
segn el valor de la variable estado_actual.
void maquina(){
if (estado_actual == 0)
estado0();
else if (estado_actual == 1)
estado1();
}
Las transiciones de un estado a otro pueden suceder por el paso de tiempo, en ese caso la
variable que se usa en la comparacin ser un contador de tiempo asncrono, tal y como se
explic en la seccin 3 de este captulo.
171
REFERENCIAS
[1] B.W. Kernighan && D.M. Ritchie. The C Programming Language. Prentice Hall.
[2] Instruction set manual for the C166 family of Infineon 16-bit Single Chip Microcontrollers. Infineon
Technologies AG.
172