Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Autores:
1
Sistemas Electrónicos Digitales.
2
Sistemas Electrónicos Digitales.
3
Sistemas Electrónicos Digitales.
4
Sistemas Electrónicos Digitales.
Para optimizar el aprovechamiento del libro se recomienda seguir los siguientes pasos:
• Ir leyendo capítulo a capítulo, en orden secuencial según el ritmo de las clases
presenciales y del laboratorio.
• En cada capítulo prestar especial atención a la sección de "Objetivos y conceptos a
entender", de forma que se debe tener claro cuando se considera que se han cumplido
esos objetivos.
• Una vez que se tenga claro el punto anterior se puede proceder a leer las siguientes
secciones descriptivas y los ejemplos.
• El lector puede cerciorarse del entendimiento de los conceptos, haciendo las
cuestiones de comprensión que hay en cada capítulo.
• Comprobada la comprensión del capítulo, el lector debe hacer los ejercicios que se
propongan.
• Finalmente existen problemas al final de cada capítulo que sirven de material
complementario para adquirir habilidad y destreza en el planteamiento y solución
de problemas de sistemas digitales.
Por último, cabe reseñar que es importante reflexionar y pararse a pensar sobre los conceptos
que aparecen en negrita en el texto, ya que aunque no tienen por qué ser conceptos más
importantes que otros, si es cierto que suelen olvidarse con mayor facilidad y son claves para
entender otros conceptos.
5
Sistemas Electrónicos Digitales.
1 Introducción
Para poder utilizar un microcontrolador es necesario conocer su arquitectura; es decir en qué
consiste por dentro desde el punto de vista de un programador, enfocándose en conocer cuáles
son sus recursos, como son qué instrucciones y modos de direccionamiento soporta, cuáles
son los registros y su tamaño, cómo es el mapa de memoria y cuánto tarda una instrucción en
ejecutarse. Cosa muy distinta a lo que es la organización de un computador, que consiste en
conocer las tripas del mismo, el hardware, cuantos módulos tiene y cómo están conectados
(punto de vista del diseñador); nada más lejos de los objetivos de esta asignatura.
6
Sistemas Electrónicos Digitales.
Unidad de
Memoria
Unidad
Unidad de Aritmética Unidad de
Entrada y lógica salida
(ALU)
Unidad de
Control
CPU
Figura 1: Modelo Von Neumann de un microcontrolador
A continuación se pasa a explicar cada una de estas unidades más en detalle.
3.1 La CPU
La CPU además de incluir la unidad de control y de la unidad aritmético lógica, contiene los
registros, que es un banco de memoria acceso de acceso rápido para el almacenamiento de
datos. Se dice que un micro es de 8 bits si estos registros son de 8 bits, es de 16 bits si estos
registros son de 16 bits, etc. De todos los registros que tiene una CPU, desde el punto de vista
de un programador interesa conocer los siguientes:
• Rx o registros de proposito general: registros que se usan como lugar de
almacenamiento temporal de un dato. Son básicos para operaciones en la ALU, ya que
sirven como punto de entrada y salida de la misma, sirven de apoyo para transferir
información entre dos posiciones de memoria, etc. En el C167 estos registros son 15 y
se notan por Rx (siendo x un número del 0 al 15).
• PC o program counter: contiene la dirección de la próxima instrucción a ejecutar. En
el C167 esté registro está formado por dos el IP y el CSP.
• IR o instruction register: (único registro que almacena instrucciones en lugar de datos)
registro que contiene la instrucción que se está procesando. Este registro no se puede
usar por un programador, simplemente es parte del hardware necesario para que la
CPU procese instrucciones.
• SR o state register: contiene el estado del micro después de haber ejecutado una
instrucción. Por ejemplo, contiene información de si una operación ha dado un
resultado negativo, si en una suma ha generado un acarreo, etc. En el C167 este
registro se denomina PSW.
3.2 La memoria
La memoria es la encargada de almacenar las instrucciones a ejecutar o programa y los datos
que usa ese programa.
7
Sistemas Electrónicos Digitales.
Para manejar números binarios con comodidad se utiliza la base hexadecimal. Los números
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 dirección de
memoria, de forma que el micro puede acceder al dato almacenado en dicha celda indicándole
a la memoria (en el bus de direcciones) la dirección de la celda a la que desea acceder, ver
Figura 2.
Dirección Dato
0000 05
0001 7F
FFFF A0
8
Sistemas Electrónicos Digitales.
add R1,R0
1 NA Rs2 Rs1
Los micros reales, en particular el C167, tienen más registros y soportan en amplio conjunto
de operaciones aritmético/lógicas.
move R0,0x10
9
Sistemas Electrónicos Digitales.
Pone lo que hay en la dirección de memoria 0x10 en el registro R0. La codificación podría
ser:
15 12 11 8 7 0
2 Rs mem
• El número de bits necesarios para codificar una instrucción depende del “tamaño” del
microprocesador. Un micro más grande (con más registros) necesitará más bits para
codificar una instrucción dada.
• Las instrucciones en memoria necesitarán por tanto más o menos celdas de memoria
para ser almacenadas.
10
Sistemas Electrónicos Digitales.
En lenguaje de alto nivel, el ejercicio consiste en sumar los dos números que se encuentran las
direcciones de memoria 0x80 y 0x82, para posteriormente guardar el resultado en la dirección
0x84. Algo similar a la instrucción:
En lenguaje simbólico código máquina, que es el que entiende el micro, esta operación
requiere de tres instrucciones que se apoyan en los registros de proposito general para realizar
la operación anterior:
move R0,0x80 que en código máquina se representa por 2080(H)
move R1,0x82 que en código máquina se representa por 2182(H)
add R1,R0 que en código máquina se representa por 1010(H)
move 0x84,R1 que en código máquina se representa por 3841(H)
Si se analiza con detalle la codificación máquina, cada una de las instrucciones (codificadas
en ensamblador, que es el lenguaje más cercano al código máquina que un programador
conoce) consiste de cuatro dígitos, el primero de ellos representa la operación a realizar según
el tipo de parámetros que usa, y los últimos tres dígitos representan los operandos de la
misma. Es necesario hacer notar que las tres instrucciones son las más sencillas en las que se
puede descomponer el ejemplo, desde el punto de vista de una máquina, ya cada una de ellas
sólo realiza una acción, o bien una transferencia de información o bien una operación con la
ALU.
11
Sistemas Electrónicos Digitales.
FFFF A0
Una vez que se manda ejecutar el programa, poniendo PC = 0x0000, se empieza a ejecutar
la primera instrucción, como se muestra en la Figura 5. En la fase de Fetch se coge la
instrucción 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 instrucción. En la fase de
Execute se ejecuta la instrucción que hay en IR; es decir, se coge el valor que hay en la
dirección 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.
Fetch Execute
12
Sistemas Electrónicos Digitales.
Es importante entender este ejemplo, para entender cómo 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 más bajo nivel que se puede programar, el cual tiene una
correspondencia biunívoca entre código máquina e instrucción de ensamblador.
El nivel más bajo o nivel físico, se corresponde con la interpretación eléctrica y es común a
todo tipo de sistema electrónico. En este nivel sólo hay medidas eléctricas de tensión; es el
nivel al que se trabaja cuando se usa el osciloscopio y con las leyes de Kirchhoff.
El segundo nivel o nivel lógico, se corresponde con la interpretación lógica de las medidas
eléctricas del primer nivel. Las medidas de tensión se traducen a ceros y unos, de forma que
por ejemplo un nivel de tensión por debajo de 0.7 Voltios se considera un 0 lógico y un valor
por encima se considera un 1 lógico. Se pueden realizar operaciones en este nivel usando el
álgebra de Bool. A este nivel se sitúa el código máquina.
13
Sistemas Electrónicos Digitales.
El tercer nivel o nivel de codificación, se corresponde con la codificación de esos ceros y unos
en palabras que puedan ser entendidas mejor por una persona. Este nivel sí que depende del
sistema electrónico que se use; es decir, del código que se use, ya que existen códigos que
interpretan los ceros y unos de distinta manera dependiendo para qué se apliquen. Si se quiere
realizar un programa para un micro, la codificación se llama ensamblador. En caso de que se
quiera trabajar con números, la codificación puede ser binaria o hexadecimal, interpretando
los números con signo y sin signo. Por último, si lo que se quiere es programar FPGA o
EPLD (lógica programable), la codificación que se usa es VHDL. Estas codificaciones
dependen dentro de cada aplicación del dispositivo que se quiera programar; por ejemplo,
existen distintos códigos ensamblador para diferentes micros.
Finalmente el cuarto nivel, o nivel más alto de abstracción, consiste en realizar una
codificación más entendible por una persona, que además 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, sólo 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, sólo existe un código
ASCII.
Nivel físico
Hardware +5V, 0V
ABSTRACCIÓN
Figura 9: Niveles de abstracción de un sistema electrónico digital
Existen niveles de abstracción superiores, pero que no se usan en la programación de sistemas
electrónicos digitales.
14
Sistemas Electrónicos Digitales.
CPU
(ALU, Registros Memoria Entrada/Salida
y Control)
Bus del sistema
Bus de datos
Bus de direcciones
Bus de control
0x82 (CPU) Bus dir -> RD (CPU) Bus control -> 0003 (MEM) Bus datos
Esto significa: la CPU pone en el bus de direcciones la dirección del dato a leer (0x82), a
continuación activa una línea del bus de control que indica operación de lectura (RD: Read),
la memoria suministra el dato almacenado en dicha posición de memoria (3) en el bus de
datos. Por último la CPU recoge el dato del bus de datos y lo almacena en un registro de
proposito general.
15
Sistemas Electrónicos Digitales.
El ciclo de escritura es similar. En este caso la CPU suministra tanto la dirección como el dato
a escribir en memoria (en el bus de direcciones y en el bus de datos respectivamente) y activa
la línea WR (Write) del bus de control.
Cuando la CPU manda hacer algo a un periférico se puede quedar a la espera a que éste
termine su labor, preguntándole continuamente si ha terminado, o bien puede configurar al
periférico 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 periférico 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 periférico; es decir,
de la misma manera que consulta una dirección de memoria. En cambio las interrupciones
utilizan líneas específicas de comunicación entre el periférico y la CPU, las cuales se
encuentran en el bus de control.
16
Sistemas Electrónicos Digitales.
4 Cuestiones de comprensión
A continuación se enumeran un conjunto de preguntas que ayudan a comprender lo que se ha
descrito en el capítulo.
5) ¿De qué diferentes formas se te ocurren que se pueden interpretar los bits que se almacenan
en la memoria de un micro?
17
Sistemas Electrónicos Digitales.
C167
18
Sistemas Electrónicos Digitales.
El modelo del programador del C167 se muestra en la Figura 11. Tiene una CPU con registros
clasificados en dos tipos: registros de propósito específico (SFR, tienen una función muy
concreta) y registros de propósito general (GPR, se pueden usar para cualquier cosa). Los
GPR son equivalentes a los registros de proposito general que se presentaron en la
arquitectura general de un micro en la sección 3.1, y se usan como posiciones de memoria de
acceso rápido. Como registros de propósito específico tenemos, entre otros, el PC (contador
de programa), el PSW (registro de estado) y el SP (Stack Pointer).
Por otro lado, tenemos el modelo de memoria. La memoria está organizada en "celdas" de 1
Byte (8bits). Cada byte tiene una dirección asociada. Las direcciones van desde al 0 hasta la
0xFFFFFF; es decir, se puede direccionar con 24 bits. Para acceder a una celda de memoria
se usa su dirección:
• Para la lectura: la CPU pone la dirección en el bus de direcciones de la cual quiere el
dato, mientras indica por el bus de control que la operación es de lectura. La memoria
devuelve el dato almacenado en la celda en el bus de datos;
dato = READ (dirección).
• Para la escritura: la CPU pone el dato en el bus de datos, mientras indica por el bus de
control que la operación es escritura, y la dirección en el bus de direcciones. La
memoria escribe en la celda direccionada el dato suministrado por la CPU.
WRITE(dato, dirección).
La unidad de entrada y salida se controla a través de sus SFRs. El acceso a estos es similar al
acceso a memoria; es decir, los SFRs están mapeados en memoria.
19
Sistemas Electrónicos Digitales.
Memoria CPU
FF FFFF Registros
R7 R15 PC
R6 R14 PSW
R5 R13 SP
R4 R12
(SFR’s)
R3 R11
R2 R10
R1 R9
00 0001
R0 R8
00 0000
(GPR’s)
El programa sencillo presentado en el capítulo anterior para una máquina de propósito general
(esta vez en la dirección 0x100, ya que en el C167 no se pueden usar las 0x100 primeras
direcciones)
MOV R0,0x100
MOV R1,0x102
ADD R1,R0
MOV 0x104,R0
20
Sistemas Electrónicos Digitales.
El tamaño del registro R0 es de 2 bytes (16 bits). Como cada dirección 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
posición 0x100 y el segundo dato dos direcciones más allá (posición 0x102). (El C167 es un
little endian; es decir, almacena el byte menos significativo del dato, parte baja de R0, en la
dirección par. El byte más significativo va a la dirección impar de memoria).
Dirección inicial
del programa
Dirección de Codificación de
memoria la instrucción
21
Sistemas Electrónicos Digitales.
De forma concisa cada una de las instrucciones del programa hace lo siguiente, (para más
información y detalles del lenguaje ensamblador ir al capítulo 6):
• MOV R0,#1. R0 representa la variable i y se inicializa a 1; i = 1. MOV significa en
inglés move, mueve 1 a R0. El # significa que el valor que le acompaña se trata como
literal y no como una dirección de memoria donde buscar el dato.
• MOV R1,#1. R1. R1 es una variable temporal que representa la cantidad a sumar a j,
que aunque siempre vale 1 se necesita para poder invocar a la instrucción de suma.
• CMP R0,#5. Compara si R0 es 5. CMP en inglés compare.
• JMPR cc_sgt,0x510. Si es mayor que 5 salta a la dirección 0x510. La instrucción
JMPR, en inglés jump, salta según la condición puesta. En este caso cc_sgt, en inglés
signed greater than, está haciendo una comparación con signo de mayor que. ¿con
qué? como se verá más adelante, cada instrucción en ensamblador deja una huella en
la CPU después de ser ejecutada, en concreto en el registro de estado, y es esa huella
como entrada a la comparación. En este caso la instrucción 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.
• ADD 0xfa00,R1. Añade R1 al dato que haya en la dirección de memoria 0xfa00; es
decir, j=j+1. Como se comentó con anterioridad la instrucción 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 dirección de memoria donde buscar el
dato.
• ADD R0,#1. Añade 1 a R0; es decir, i = i+1.
• JMPR cc_uc,0x504. Esta instrucción en un salto sin condición cc_uc, en inglés
unconditional, a la dirección 0x504, precisamente para que el bucle continúe.
22
Sistemas Electrónicos Digitales.
Para el C167 existen dos tipos de ensambladores, uno de muy bajo nivel llamado ensamblador
de línea y otro de alto nivel llamado ensamblador de PC. Nada mejor que un ejemplo para
entender la diferencia entre ambos, ver Figura 12
En PC
Etiquetas (op) MOV R0,#1 ; r0 (i)
MOV R1,#1 ; auxiliar
bucle: CMP R0,#5 ; if i>N
JMPR cc_sgt,fin ; then goto ´fin’
ADD 0xfa00H,R1 ; j += 1
ADD R0,#1 ; i += 1
JMPR cc_uc, bucle
fin:
Instrucción Operandos Comentarios (opcional)
En línea
500 MOV R0,#1 ; r0 (i)
502 MOV R1,#1 ; auxiliar
504 CMP R0,#5 ; if i>N
506 JMPR cc_sgt,0x510 ; then goto 512H
508 ADD 0xfa00,R1 ; j += 1
50C ADD R0,#1 ; i += 1
50E JMPR cc_uc,0x504 ; salto sin condición
510
En cambio el ensamblador de PC admite lo que se llaman etiquetas, que no son más que
nombres que representan una dirección de memoria, que no se conoce a priori y por lo tanto
se usa la etiqueta en su lugar. Esas direcciones de memoria se resuelven o se conocen cuando
el programa se termina y se ensambla. El programa que ensambla se llama ensamblador y se
ejecuta en un PC, y lo único que hace es traducir las etiquetas en direcciones de memoria,
traducir cada instrucción a código máquina y situar cada instrucción en una dirección de
memoria. Como resultado se genera un fichero que se puede cargar en la memoria del micro
directamente.
23
Sistemas Electrónicos Digitales.
• Transferencia de datos. Son instrucciones que sirven para mover los datos de un lugar
a otro. La instrucción más importante es mov.
• Aritméticas. Son instrucciones que sirven para realizar operaciones aritméticas. Las
más importantes son: add (suma), sub (resta), cmp (comparación, resta operandos y
compara con 0), neg (hace el complemento a dos), mul (multiplica), div (divide).
• Lógicas. Realizan operaciones lógicas: and (multiplicación lógica), or (suma lógica),
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 ejecución. Realiza saltos en la ejecución según la condición. jmpr cc_uc
(sin condición), 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 cómo
funcionan los saltos ver capítulo 6 sección 7.
Los número en ensamblador se suelen usar o bien para literales o para referirse a direcciones
de memoria. Por defecto el número que se escribe se considera que está en decimal, si se pone
un 0x por delante el número está en hexadecimal.
24
Sistemas Electrónicos Digitales.
Direccionamiento Símbolo
Inmediato #data
Directo a GPR Rw, Rb
Directo a SFR o GPR reg
Directo a memoria mem
Indirecto [Rw]
Indirecto con pre-decremento [-Rw]
Indirecto con post-incremento [Rw+]
Indirecto con desplazamiento [Rw+#data16]
25
Sistemas Electrónicos Digitales.
En definitiva los números que tienen un uno como bit más significativo son negativos y los
que no son positivos.
Por lo tanto si se mira en una dirección de memoria del micro y se ve que está almacenado el
número 0xFFFE, ¿qué significa?. Puede tener muchos significados:
Esto viene a decir, que la interpretación del contenido de una posición de memoria
depende del programador, la cuál se escenifica en el tipo de instrucciones que use para
decodificarla. Si pone el PC apuntanto a esa dirección de memoria significa que la está
interpretando como una instrucción, si accede a esa dirección de memoria con una instrucción
que tiene en cuenta el signo (MUL, DIV, JMPR cc_sgt,...) entonces la está interpretando
como un número con signo y si accede con una instrucción que no tiene en cuenta el signo
(MULU, DIVU, JMPR cc_ugt,...) entonces la está interpretando como un número sin signo.
26
Sistemas Electrónicos Digitales.
2.2.4.1 Condición
if (a == b)
a = 0;
MOV R0,a
CMP R0,b
JMPR cc_ne,next
MOV R0,#0
MOV a,R0
next:
2.2.4.2 Bucle
i = 0;
while (i<10) {
a[i] = i; i += 1;
}
MOV R0,#0
MOV R1,#1
MOV R2,#0fa00h
otro: CMP R0,#10
JMPR cc_sge,next
MOV [R2],R0
ADD R0,R1
ADD R2,#2
JMPR cc_uc, otro
next:
27
Sistemas Electrónicos Digitales.
Bytes. Como la memoria puede llegar a ser de 16MBytes, puede haber 256 segmentos y
256*4=1024 páginas, como se puede ver en la Figura 13. En la tarjeta que se utiliza para las
prácticas 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 pequeña parte
del segmento S0 (página 3) tiene RAM interna (dentro del chip de la CPU) y los registros, por
ello es el segmento más importante.
FF FFFF FFFF
Página 3
(RAM Interna)
C000
256
Segmentos Página 2
16 Mb de 64 kB 8000
Página 1
(RAM ext)
4000
S1 64Kb
01 0000 Página 0
16 Kb
S0 64Kb (RAM ext)
00 0000 0000
Como se puede ver en la Figura 13, la página 0 y 1 del micro son generalmente RAM externa
(ciertas versiones del micro tienen ROM interna). La página 3 contiene la RAM interna y los
registros tal como se muestra en la Figura 14:
• Desde la dirección 0xF600 hasta la 0xFC00 se encuentra el STACK, que es memoria
RAM para almacenamiento temporal.
• Desde la dirección 0xFC00 hasta la 0xFD00 están 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 más información sobre el
mapeo de los GPRs ver sección 2.3.2.5.
• Desde la dirección 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 dirección 0xFE00 hasta la 0XFFFF se encuentran mapeados los SFRs.
28
Sistemas Electrónicos Digitales.
FFFF
SFRs acceso bit a bit
SFR’s FF00
SFRs
FE00
Acceso bit a bit
FD00
GPRs
RAM FC00
STACK
F600
29
Sistemas Electrónicos Digitales.
Por ejemplo:
Si PC = 01 0000
CSP IP
01 0000
IP indica la zona
del segmento
S1 64Kb
01 0000 en uso de las
S0 64Kb 64k posibles
00 0000
16 bits
Figura 15
30
Sistemas Electrónicos Digitales.
Apunta a la zona de Stack, que está comprendida entre la dirección 0xF600 y 0xFC00). El
Stack es una zona de la memoria donde el micro guarda de forma temporal información que
necesita para la ejecución de un programa; por ejemplo, cuando hay una llamada a una
función guarda la información de la dirección desde donde se llama la función para poder
posteriormente seguir la ejecución por donde iba. El funcionamiento del Stack es de tipo
LIFO (last in, first out). Inicialmente SP apunta a la dirección 0xFC00 y según 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á vacío.
La solución pasaría por usar, por ejemplo, el DPP0 que almacenará los 10 bits más altos de la
zona que queremos direccionar; en este caso y en binario 00 0010 1001 (DPP0 = 0x29). El
resto de la dirección deseada, los 14 bits más bajos se dejan como están y se les añade como 2
bits más altos el número de DPP utilizado para resolver la dirección deseada; en este caso el
0.
DPP0 10bits
31
Sistemas Electrónicos Digitales.
DPP2 10bits
0000 1010 01 11 0000 0000 0000 MOV DPP2, #0x29
MOV R0, 0xB000
Dirección Dato
... ... ...
FC08 4400 R4
...
MOV CP,#0FC00 H FC06 FF00 R3
MOV a, R0
FC04 FC04 R2
...
FC02 A050 R1
FC00 1034 R0
32
Sistemas Electrónicos Digitales.
3 Cuestiones de comprensión
2) ¿Cuantos bits son necesarios para poder direccionar hasta 8 Mbytes de datos? _____
IP =
CSP =
4) ¿Qué hay que hacer para que el registro R1 sea mapeado en la dirección de memoria FC06?
4 Ejercicios propuestos
33
Sistemas Electrónicos Digitales.
4) Hacer un programa en ensamblador que busque el valor máximo (a nivel de byte sin signo)
de los datos que hay entre la dirección de memoria 0x200 y 0x300, y lo guarde en R1.
5) Dadas las condiciones iniciales de los registros y las instrucciones escritas a continuación,
indicar cómo se modifica la memoria y los registros R0 y R1 en los sucesivos pasos.
34
Sistemas Electrónicos Digitales.
35
Sistemas Electrónicos Digitales.
36
Sistemas Electrónicos Digitales.
6) Dada la tabla de códigos de instrucciones y la situación inicial de los registros CP, IP,
DPP0 y CSP indicar cómo se modifica la memoria cuando se ejecuta el programa de tres
instrucciones cargado en memoria. Indicar en la tabla de la derecha cómo queda la memoria
después de la ejecución de la tercera instrucción del programa.
R egistros
C S P = 00
C P = FC 00
D P P0 =0
IP = 020A E scribir aquí la solución final
m em oria 24 m em o ria 24
37
Sistemas Electrónicos Digitales.
7) Dado el programa siguiente, rellenar los registros y la memoria indicada justo antes de que
el programa ejecute la instrucción de la dirección de memoria 0x514 (final del programa)
Registros
CSP =
CP = FC02
IP =
38
Sistemas Electrónicos Digitales.
memoria 24
39
Sistemas Electrónicos Digitales.
40
Sistemas Electrónicos Digitales.
41
Sistemas Electrónicos Digitales.
42
Sistemas Electrónicos Digitales.
43
Sistemas Electrónicos Digitales.
44
Sistemas Electrónicos Digitales.
45
Sistemas Electrónicos Digitales.
46
Sistemas Electrónicos Digitales.
47
Sistemas Electrónicos Digitales.
Capítulo 4 PUERTOS
2 Puertos paralelo
El C167 tiene 9 puertos paralelo, con un total de 111 líneas. Cada línea 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 líneas, como máximo 16, que salen al exterior o entran del exterior por los pines
del micro. Existe una correspondencia biunivoca entre pines y líneas. A continuación se
enumeran los puertos explicando muy someramente su función:
• El P0 tiene 16 líneas y es el bus de datos del micro que tanto se ha hablado a lo largo
del capítulo 2 y 3. Este bus se conecta a las memorias externas y a los periféricos y se
usa para transmitir los datos que contienen los mismos. No se debe olvidar que tiene
16 bits, cada uno corresponde con una línea, ya que el C167 es de 16 bits.
• El P1 tiene 16 líneas y representa la parte baja del bus de direcciones. Este bus se
conecta a las memorias externas y a los periféricos y se usa para que la CPU indique la
dirección 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 más bajos del
mismo. Será necesario usar el puerto 4 para completar el bus.
• El P2 tiene 16 líneas y es de propósito 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 podría haber conectado cualquier otra cosa
al puerto.
• El P3 tiene 16 líneas. Parte de las líneas se usan para un puerto serie, que en el caso
del laboratorio se usa para comunicarlo con el PC. El resto de las líneas son de
propósito general.
• El P4 tiene 8 líneas que completan la parte alta del bus de direcciones.
• El P5 tiene 16 líneas que se usan como entradas para el conversor A/D. En cada una
de estas entradas se puede conectar una señal analógica, para poder realizar una
digitalización de la misma.
• El P6 es de 8 líneas y tiene parte del bus de control.
• El P7 tiene 8 líneas que son las salidas correspondientes a la unidad de PWM que tiene
el micro.
48
Sistemas Electrónicos Digitales.
Cada puerto tiene dos registros que sirven para manejarlo, un registro de control (DPx) y
otro de datos (Px). A continuación se explica cada uno de ellos particularizado para el puerto
2.
DP2 tiene 16 bits, uno para cada línea (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 vía DP2, éste se maneja a través 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 prácticas 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: conexión de los LED e interruptores al puerto 2 del C167 de la tarjeta de laboratorio
Con este hardware en DP2 se tiene que escribir 0x00FF.
• 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).
Nótese que:
• Cuando se lee P2 la única información de interés es la que viene de las entradas (la
información de las salidas no tiene interés, se desecha)
49
Sistemas Electrónicos Digitales.
• Cuando se escribe P2 sólo se actúa sobre las salidas (en las entradas tenemos el valor
fijado en el exterior vía interruptores).
• DP2 depende únicamente del hardware conectado y por ello sólo se configura una
vez dentro de los programas. De forma que aquellos pines del micro que tengan
conectados actuadores, se configurarán como salidas y los que tengan conectados
sensores, se configurarán como entradas.
Por ejemplo:
A continuación se muestra un programa que suma dos números de 4 bits, uno representado
por los 4 interruptores conectados a la parte más 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 solución en C sería:
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
Sistemas Electrónicos Digitales.
MOV DP2,#0x00ff
bucle: 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
Nótese que el programa maneja DP2 una única vez en la fase de inicialización (configuración
del puerto, antes del bucle while). El manejo de puerto se realiza a través de P2.
51
Sistemas Electrónicos Digitales.
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 posición
de los interruptores (parte alta de P2) de la tarjeta del laboratorio.
52
Sistemas Electrónicos Digitales.
53
Sistemas Electrónicos Digitales.
54
Sistemas Electrónicos Digitales.
55
Sistemas Electrónicos Digitales.
56
Sistemas Electrónicos Digitales.
57
Sistemas Electrónicos Digitales.
58
Sistemas Electrónicos Digitales.
59
Sistemas Electrónicos Digitales.
Capítulo 5 PERIFÉRICOS
Generalmente los periféricos suelen tener contacto con el exterior y por ello pueden además
utilizar un puerto del micro.
El micro tiene muchos periféricos, de los cuales los más importantes son:
• Temporizadores (timers): para contar eventos, para llevar un computo del tiempo
transcurrido, etc.
• Convertidor analógico/digital (A/D): sirve para pasar una señal del dominio analógico
al digital, formato que puede ya procesar el micro.
60
Sistemas Electrónicos Digitales.
3 El Timer
Es un periférico que se encarga de contar los pulsos de una señal. 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 sección explica con detalle el funcionamiento del Timer
0, y por similitud queda descrito el funcionamiento del resto de Timers del micro.
15 14 13 12 11 10 8 7 6 5 4 3 2 0
--- T1R --- T1M T1I --- T0R --- T0M T0I
61
Sistemas Electrónicos Digitales.
tiempo, y en el segundo se dice que funciona en modo counter, por ejemplo el Timer 0
cuenta los pulsos de la señal 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 número de pulsos de la señal 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 más 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
clk = 20MHz T0I
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
automática T0=T0REL.
De todos los bits el único que interesa por ahora es el T0IR, ya que los demás se verán en más
detalle en el Capítulo 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 interrupción (0 a 15. Nivel 15 nivel más alto)
• GLVL: grupo del nivel de interrupción.
62
Sistemas Electrónicos Digitales.
Valor de recarga
después de un rebose rebose o final de
T0REL
cuenta
Pre-escalado
clk
T0I T0 T0IR
Almacena la
cuenta
63
Sistemas Electrónicos Digitales.
lo mismo que P2 = ~P2, que es lo mismo que en ensamblador CPL P2, que es lo mismo que P2
= complemento a 1 de P2. De la misma manera T01CON |= 0x40 es lo mismo que T01CON =
T01CON | 0x40, que es en ensamblador OR T01CON,#0x40.
#include <reg167.h>
main() {
T01CON = 0x06; // timer a 39 kHz
T0REL = PERIOD; // ¿Qué valores son razonables?
T0 = PERIOD;
T0IR = 0; // flag rebose = 0
T01CON |= 0x40; // Start
DP2 = 0x00FF;
P2 = 0;
while (1) {
while(!T0IR) ; // bucle de espera
P2 ^= 0xFFFF; // XOR
T0IR = 0; // flag rebose = 0
}
}
El Timer cuenta a una frecuencia de 39kHz y funciona en modo timer. Lo que supone que
dependiendo de la inicialización 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 están 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 después enciende los LEDs. En resumen el programa enciende y apaga los LEDs de
forma periódica con un periodo que depende de la inicialización 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
Sistemas Electrónicos Digitales.
4 Cuestiones de comprensión
1) Deducir la fórmula que relaciona el tiempo de rebose del timer (t) con el prescalado (T0I) y
la cuenta inicial (T0); es decir, t = función(T0I,T0).
2) Si el tiempo que tarda en rebosar el timer 0 es 2s, ¿cuánto vale T0 y T0I (la cuenta inicial y
el preescalado)?. Razona la respuesta
65
Sistemas Electrónicos Digitales.
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
periódica a frecuencia 256kHz. El ancho de cada pulso viene fijada, en milisegundos, por la
variable de 8 bits DC.
$nonsegmented
S0 section code at 0H
ini proc NEAR
jmp main
ini endp
S0 ends
DAT ends
66
Sistemas Electrónicos Digitales.
main endp
SM ends
end
67
Sistemas Electrónicos Digitales.
6 Práctica 4: timers
68
Sistemas Electrónicos Digitales.
69
Sistemas Electrónicos Digitales.
70
Sistemas Electrónicos Digitales.
Capítulo 6 ENSAMBLADOR
2 Introducción
El objetivo de este capítulo 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 tamaño 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 instrucción. Se explican en el capítulo 2 sección 2.2.2.
Los flags de estado dan información cualitativa del resultado después de ejecutar una
instrucción. Se explican en el capítulo 2 sección 2.3.2.2.
71
Sistemas Electrónicos Digitales.
F0 x y
Código de Número de
instrucción GPR
Figura 17: Codificación de la instrucción MOV Rx, Ry
E6 reg dato
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
instrucción que permite el mayor número de combinación 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 más información
de los modos de direccionamiento consultar sección 2.2.2 del capítulo 3.
72
Sistemas Electrónicos Digitales.
Las instrucciones de que dispone el ensamblador de 167 para este propósito son:
MOVBZ op1, op2
MOVBS op1,op2
8 bits con signo 8 bits sin signo 16 sin extender 16 bits exten-
signo diendo signo
0xFF -1 255 255 (0x00FF) -1 (0xFFFF)
0x80 -128 128 128 (0x0080) -128 (0xFF80)
73
Sistemas Electrónicos Digitales.
Estas instrucciones se suelen usar para guardar los GPR’s que se van a usar en una función
como variables locales, ya que en caso contrario la función modificaría el contenido de los
mismos y no funcionaría correctamente el programa que llama a la función.
MOV R0,#0x0001
MOV 0xFA00,R0
ADD R0,#0x0009
ADD 0xFA00,R0
ADD Rx, Ry
ADD Rx, [Ry] ; Ry con y = 0..3
ADD Rx, [Ry+] ; Ry con y = 0..3
74
Sistemas Electrónicos Digitales.
MOV R0,#0x0001
ADDB Rh0,#0x01
MOV R0,#0x01FF
MOV 0xFA00,R0
SUB R0,#0x0009
ADD 0xFA00,R0
A nivel de byte
MOV R0,#0x01FF
SUBB Rh0,#0x01
4.3 NEG
La instrucción NEG sirve para obtener el complemento a dos de un número. Es equivalente a
restar el operando op1 de 0 y almacenar el resultado en el mencionado operando (en este caso
el flag de C también recoge el borrow).
NEG Rx ; Rx <- 0 - Rx
75
Sistemas Electrónicos Digitales.
MOV R0,#0x01FF
NEG R0
MOV R0,#0x01FF
NEGB Rl0
La instrucción 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 operación 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 algún otro factor.) Los flags Z y N se
actualizan de acuerdo con el valor del resultado. La instrucción MULU es la variante para
operandos sin signo.
MOV R0,#0xFFFF
MOV R1,#0xFFFF
MUL R0,R1
MOV R2,MDL
MOV R3,MDH
MULU R0,R1
Figura 26: Ejemplo de MUL y MULU. Finalmente R2 = 1, R3 = 0, MDL = 0x0001 y MDH = 0xFFFE
76
Sistemas Electrónicos Digitales.
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 números
sin signo.
MOV R3,#0xFFFF
MOV MDL,R3
MOV R0,#0x0001
DIV R0
MOV R1,MDL
MOV R2,MDH
MOV R3,#0xFFFF
MOV MDL,R3
DIVU R0
Figura 27: Ejemplo de DIV y DIVU. Finalmente R1 = 0xFFFF, R2 = 0, MDL = 0xFFFF y MDH = 0
Todas ellas actualizan únicamente los flags N y Z. Si el resultado es cero se pone a uno el flag
Z y si el resultado es negativo se pone a uno el flag N.
5.1 AND
Se utiliza para efectuar el producto lógico de dos operandos, bit a bit. La instrucción AND
admite la forma:
Admite los mismos modos de direccionamiento que la instrucción 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 R0,#0x0001
AND R0,#0x0009
77
Sistemas Electrónicos Digitales.
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 lógica de dos operandos, bit a bit. La instrucción OR admite la
forma:
OR Rx, op ; Rx <- Rx or op
Admite los mismos modos de direccionamiento que la instrucción ADD. La variante ORB
opera a nivel de byte.
MOV R0,#0x0001
OR R0,#0x0009
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
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 instrucción XOR admite la forma:
78
Sistemas Electrónicos Digitales.
MOV R0,#0x0101
XOR R0,#0xFFFF ; R0 <- 0xFEFE
5.4 CPL
Realiza el complemento a 1 de un número. La instrucción CPL admite la forma:
MOV R0,#0x0101
CPL R0 ; R0 <- 0xFEFE
Las instrucciones de desplazamiento SHL y SHR permiten desplazar los bits de un registro un
número arbitrario de desplazamientos (a izquierda y derecha respectivamente).
SHL Rx,Ry
SHL Rx,#num ; Desplaza num veces a la izda los bits de Rx
SHL desplaza los bits hacia la izquierda. El bit de Carry se pone a uno si el bit 15 que sale del
desplazamiento es 1 (en la última iteración). Las posiciones libres que van quedando a la
derecha se rellenan con ceros.
C Rx 16 bits 0
MOV R0,#0x0001
SHL R0,#9
79
Sistemas Electrónicos Digitales.
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 iteración). Las posiciones libres que van quedando a la
izquierda se rellenan con ceros.
0 Rx 16 bits C
Se puede combinar con las operaciones AND y OR para igualar bits de diferentes registros.
Figura 36: Se quiere poner el bit 1 del registro R0 igual que el bit 0 del registro R1.
Las instrucciones de rotación ROL y ROR permiten rotar los bits de un registro un número
arbitrario de desplazamientos (a izquierda y derecha respectivamente), de forma que los bits
que salen por una lado del registro entran por el otro.
ROL rota los bits hacia la izquierda. El bit de Carry se pone a uno si el bit 15 que sale del
desplazamiento es 1 (en la última iteración). Las posiciones libres que van quedando a la
derecha se rellenan con el valor del Carry en cada iteración.
C Rx 16 bits
MOV R0,#0x8001
ROL R0,#9
80
Sistemas Electrónicos Digitales.
Rx 16 bits C
7 Saltos
• N: Negativo.
• Z: Cero ("Zero").
• V: Rebose ("Overflow").
• C: Acarreo o "Carry".
La información 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
Sistemas Electrónicos Digitales.
La instrucción CMP es similar a la instrucción 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.
CMP op1,op2
op1 es el operando destino, y op2 es el operando fuente. Esta instrucción es idónea para
comparar dos operandos que no se quieren actualizar, antes de un salto con condición. Admite
la variante CMPB.
En la Figura 38 se muestra un ejemplo. Primero, la instrucción CMP actualiza los flags del
registro PSW de la misma forma que lo hace la instrucción SUB. Segundo, la instrucción
JMPR se encarga de realizar el salto a la línea next en caso de que R0 sea mayor que R1
(comparación sin signo, es necesario hacer notar que -1 es mayor que 100 si se compara sin
signo ya que el número -1 es 65535 sin signo en 16 bits).
82
Sistemas Electrónicos Digitales.
CMP R0,R1
JMPR cc_ugt,next ;si R0 > R1
....
next:
MOV R0,a
CMP R0,b
JMPR cc_ne,else
; condición if
..
JMPR cc_uc,endif
else: ; condición else
..
endif:
8.1 Condición if
A continuación se muestra un ejemplo de condición en C y ensamblador
if (a == b)
...;
else
...;
83
Sistemas Electrónicos Digitales.
MOV R0,a
CMP R0,b
JMPR cc_ne,else
; condición if
..
JMPR cc_uc,endif
else: ; condición else
..
endif:
84
Sistemas Electrónicos Digitales.
i = 0;
while (i<10) {
a[i] = i; i += 1;
}
MOV R0,#0 ; R0 es i
MOV R1,#1 ; es el incremento de i
MOV R2,#0xfa00 ;R2 almacena la dirección de memoria de a
otro: CMP R0,#10
JMPR cc_sge,next
MOV [R2],R0
ADD R0,R1
ADD R2,#2
JMPR cc_uc,otro
next:
Las instrucciones que operan con bits de forma individual sólo se pueden usar contra zonas de
memoria preparadas ello, ver Figura 46
FFFF
SFRs acceso bit a bit
SFR’s FF00
SFRs
FE00
Acceso bit a bit
FD00
GPRs
RAM FC00
STACK
F600
85
Sistemas Electrónicos Digitales.
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 función del estado en el que se
encuentra un bit.
JB bit, dir ; si bit = 1 entonces salta a dir
JNB bit, dir ; si bit = 0 entonces salta a dir
9.2 Otras
La instrucción BSET pone a 1 el bit especificado en op1:
BSET op1
Los bits se especifican dando la dirección de la palabra, punto, número 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.
86
Sistemas Electrónicos Digitales.
87
Sistemas Electrónicos Digitales.
10 Directivas de ensamblador
Las directivas son unas instrucciones se ponen en el programa y que indican al programa
ensamblador cómo debe ensamblar el código. Estas instrucciones se ejecutan en tiempo de
ensamblado y no en tiempo de ejecución como el código 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 continuación.
$nonsegmented
maxdata equ 0x10 ; constante
Sección de datos D100
D100 section data at 200H
en dirección 0x200 j dsw 10 ; int j[10];
h dw 1 ; int h =1;
D100 ends
88
Sistemas Electrónicos Digitales.
• dsw: reserva espacios de 2 byte de memoria para una variable. Por ejemplo, en la
primera línea de la sección 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 línea de la sección 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}; sería dw h 2,4. También 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 línea de la Figura 49 se muestra un ejemplo de
cómo se define la constante maxdata igual a 10.
• end: indica final de programa y se pone al final del fichero.
• proc: cada sección de código se puede dividir en varios procedimientos con esta
directiva. La nomenclatura es:
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 línea de código etiquetada; por ejemplo jmpr nombre.
89
Sistemas Electrónicos Digitales.
11 Cuestiones de comprensión
1) ¿Cómo se codifica el número –16 en hexadecimal usando 8 bits?
3) Indicar el valor de PSW después de ejecutar las siguientes instrucciones (los bits que no se
usan se pueden poner a cero):
90
Sistemas Electrónicos Digitales.
MOV R0,#0x01FF
NEGB RL0
MOV R0,#0x6FFF
MOV R1,#0xFFFF
MUL R0,R1
MOV R2,MDL
MOV R3,MDH
MOV MDL,#0xFFFF
MOV R0,#0x0001
DIV R0
MOV R1,MDL
MOV 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 máscaras)
91
Sistemas Electrónicos Digitales.
....
300 MOV R0, #0x54
304 CALLR 400
306 MOV R1, #0x32
....
400 PUSH R0
402 MOV R0, #1
406 POP R0
408 RET
R0 = _____________
SP = _____________
R0 = _____________
SP = _____________
92
Sistemas Electrónicos Digitales.
R0 = _____________
SP = _____________
R0 = _____________
SP = _____________
93
Sistemas Electrónicos Digitales.
R0 = _____________
SP = _____________
R0 = _____________
SP = _____________
94
Sistemas Electrónicos Digitales.
R0 = _____________
SP = _____________
95
Sistemas Electrónicos Digitales.
Se quiere medir el ancho de un pulso que entra por un pin del micro. Desarrollar un programa
en C que mida el tiempo (en milisegundos) que está en 1 un pin de un puerto del micro (cada
vez que el pin vuelve a cero se debe 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 del pulso en los
diodos situados en la parte baja del puerto P2. Aportar varias soluciones y discutir las ventajas
y desventajas de cada una.
Suponiendo que el pulso lo genera un sistema electrónico (entrada por P7.4) se puede suponer
que los flancos son limpios, no tienen rebotes. Los rebotes surgen cuando es un sistema
mecánico el que provoca los flancos, como por ejemplo un interruptor (entrada por P2.8). En
la siguiente figura se ilustra un flanco sin rebotes y con rebotes.
Con rebotes
Sin rebotes
Cuando se producen rebotes suelen durar un tiempo máximo T, que se puede determinar de
forma experimental. En general, un mismo interruptor puede tener más rebotes unas veces
que otras, pero sí que es cierto que se puede siempre encontrar un cota máxima de T.
En todas las soluciones que se proponen a continuación se debe apreciar que se usa un bucle
infinito, como toda aplicación que funciona en un micro. Soluciones más detalladas que las
que se muestran a continuación en el contexto de un problema real están en las secciones 14.2
y 14.3.
En un principio se va a valorar la solución más sencilla, sin rebotes. Consiste en esperar a que
la señal se ponga a uno, en ese momento se activa el Timer que se pone a medir tiempo, y
justo cuando se detecta que la señal se vuelve a poner a 0 entonces se apaga el Timer y se
actualizan los LED:
96
Sistemas Electrónicos Digitales.
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 sólo puede medir el ancho de pulso de una línea y sin
rebotes. Desde el punto de vista de la programación la solución es poco modular, ya que
mezcla el uso del Timer con el del puerto.
Una solución más modular supone usar un sistema muestreado, donde el periodo de muestreo
es un milisegundo. Se detecta cuando la señal 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 señal 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
Sistemas Electrónicos Digitales.
Este ejemplo tiene la pega de que sólo puede medir el ancho de pulso de una línea y sin
rebotes. Si se quisiera aumentar la resolución bastaría con llamar a la función retraso con un
tiempo inferior a 1ms.
La siguiente solución propuesta, aunque sólo trata con una sola línea, sería capaz de soportar
tantas como se quisieran simplemente haciendo que las variables anterior, actual, cuenta
y contador fueran vectores de tantos elementos como señales se quieren monitorizar. La idea
es que el sistema es muestreado siempre y no sólo 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
señales que se monitoriza, en este ejemplo sólo se mira la línea P7.4. Además se ha añadido
una variable cuenta que indica si en una determinada línea se ha detectado el pulso y por ello
se está midiendo el ancho del mismo en un determinado instante. Por lo demás el primer if se
encarga de detectar el flanco de subida de la señal 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
Sistemas Electrónicos Digitales.
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 solución es que se está perdiendo un tiempo de 100ms que no permite
monitorizar otras líneas y que además es muy superior a lo que los rebotes pueden requerir.
Por ello esta solución no soporta monitorizar varias señales de forma simultánea.
Por último se presenta una solución 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 señal 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 señal durante más tiempo que MAX; es
decir se ha estabilizado la señal. La variable que comprueba esto es filtrado. Cuando
filtrado llega a 0 quiere decir que la señal se ha estabilizado y por lo tanto, es hora de
determinar si el flanco es de subida o de bajada.
99
Sistemas Electrónicos Digitales.
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 características:
• Lee un dato de los interruptores conectados a la parte alta del puerto P2.
• Accede a la posición de memoria
• Muestra el contenido de la posición en los LEDs
100
Sistemas Electrónicos Digitales.
Hacer un programa que encienda o apague un LED conectado al pin P2.0 del
microcontrolador C167, según la posición de un interruptor que se encuentra conectador en el
pin P2.1.
101
Sistemas Electrónicos Digitales.
$nonsegmented
S0 section code at 0H
ini proc NEAR
jmp main
ini endp
S0 ends
DAT ends
copia:
fin: ret
main endp
SM ends
end
102
Sistemas Electrónicos Digitales.
Supuesto un sistema digital como el del laboratorio, se ha conectado al pin P7.4 del micro una
señal 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 número de pulsos que entran por
el pin P7.4.
103
Sistemas Electrónicos Digitales.
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 más altos de P2 se proporciona el primer dato. Este dato se valida
cuando la línea P2.8 pasa de 0 a 1.
• A continuación se introduce el segundo dato (siguiendo el mismo procedimiento que
para el primer dato).
• Por último se introduce la operación (+: P2.12 = 1, -: P2.13=1). La operación se valida
de la misma forma que los datos. En caso de que no se haya seleccionado una
operación no se calcula el resultado.
• El proceso anterior se repite indefinidamente, manteniéndose en la parte baja de P2 el
resultado de la última operación realizada.
$nonsegmented
S0 section code at 0H
ini proc NEAR
jmp main
ini endp
S0 ends
DAT ends
104
Sistemas Electrónicos Digitales.
main endp
SM ends
end
105
Sistemas Electrónicos Digitales.
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 características:
• Lee un dato de los interruptores conectados a la parte alta del puerto P2.
• Accede a la dirección de memoria indicada en los interruptores (8 bits más altos de la
dirección todos a 0).
• Analiza los ocho bits del dato almacenado en dicha dirección.
• Si hay siete bits a 1 y uno a 0, se indica en los LED más bajos de P2 la posición 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 más de un bit a 0 se encienden los cinco LED conectados a las líneas 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
Sistemas Electrónicos Digitales.
107
Sistemas Electrónicos Digitales.
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 señal.
NOTA: Los rebotes se podrían eliminar con un filtro analógico paso bajo, pero tiene el
inconveniente de que usa resistencias y condensadores con valores que dependen mucho de la
temperatura. Es mucho más robusto realizar un filtrado digital con un micro.
void main(void) {
int anterior, actual; // estado anterior y actual de la señal
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 señal 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 señal es estable
filtrado = -1;
if (actual) {
cuenta = 1;
}
else {
cuenta = 0;
P2 = ~(mseg/1000);
mseg = 0;
}
}
if(cuenta)
mseg++;
anterior = actual;
}
}
108
Sistemas Electrónicos Digitales.
109
Sistemas Electrónicos Digitales.
Escribir en ensamblador un programa que mida el ancho de los pulsos de una señal 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 señal
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
Sistemas Electrónicos Digitales.
111
Sistemas Electrónicos Digitales.
112
Sistemas Electrónicos Digitales.
113
Sistemas Electrónicos Digitales.
114
Sistemas Electrónicos Digitales.
2 Concepto de driver
El driver es un conjunto de funciones que sirven para independizar al usuario de los detalles
de manejo de un periférico, de manera que un posible desarrollador que quiera usar el
periférico lo único que tiene que hacer es usar el driver y de esa manera usa el periférico sin
tener que conocer los detalles de implementación y manejo del mismo.
Estas funciones tienen que estar perfectamente documentadas indicando ¿Qué hace?, ¿Qué
parámetros necesita?, ¿Qué devuelve?. Esto es así ya que estas funciones son lo único que
tiene que conocer cualquier desarrollador que quiera usar al periférico.
115
Sistemas Electrónicos Digitales.
Este driver consta de una función de manejo y ninguna de inicialización. Se podría hacer una
función de inicialización que indicara el reloj que tiene el micro y parametrizar la función de
manejo en función del reloj.
116
Sistemas Electrónicos Digitales.
El convertidor AD se encarga de transformar una tensión analógica del exterior en una digital
de 10 bits:
• 5V analógicos se corresponden con 0x3FF
• 0V analógicos se corresponden con 0x000
Para ello usa el puerto P5 del micro. Este puerto tiene 16 bits y se corresponde con 16 pines
del micro que se usan como entradas donde se pueden conectar tensiones analógicas de 0 a
5V. Como existen 16 posibles entradas, se dice que el convertidor AD tiene 16 canales. En
cada instante sólo se puede hacer la conversión de un sólo canal, pero éste es configurable por
el usuario.
117
Sistemas Electrónicos Digitales.
ADCH ANALOGICO
5V
ADCIR Convertidor AD P5.0 (canal 0)
0V
…
ADDAT
MUX
5V
P5.15 (canal 15)
0V
8 7 6 5 4 3 0
• ADCH bits 0-3: estos 4 bits permiten seleccionar uno de los 16 canales posibles donde
hacer la conversión.
• ADM bits 4-5: existen cuatro modos de funcionamiento del convertidor. Si ADM vale
0 se realiza una conversión 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 conversión secuencial en cada uno de los canales o incluso
conversiones sucesivas siguiendo la secuencia de los canales.
118
Sistemas Electrónicos Digitales.
15 12 9 0
#include <reg167.h>
main() {
while (1) {
while(!ADCIR) ; // bucle de espera
P2 = ~(ADDAT >> 2) // coge 8 bits sólo
ADCIR = 0; // flag fin = 0
ADCON |= 0x80; // Start ADST=1
}
}
119
Sistemas Electrónicos Digitales.
Es necesario darse cuenta de que este programa sería más modular y entendible si se usase el
driver descrito en la sección 2.3 de este capítulo:
#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) // coge 8 bits sólo
}
}
Como se puede ver es necesario incluir el driver (#include "ad.h"), para decirle al
compilador que la función convad existe.
120
Sistemas Electrónicos Digitales.
Aunque la codificación interna de los números en memoria física siempre se hace con ceros y
unos, ya que el micro no entiende otra cosa, desde el punto de vista de la programación se
soportan diferentes bases de codificación para expresar un número:
• En base 16, hexadecimal, por ejemplo: char i = 0xff; equivale al -1 decimal.
• En base 10, decimal, por ejemplo: char i = -1;
121
Sistemas Electrónicos Digitales.
unsigned int i;
unsigned char c = 0xff;
i = c;
int i;
char c = 0xff;
i = c;
122
Sistemas Electrónicos Digitales.
int i = 5;
int suma;
while (i) {
suma = suma + i;
-- i;
}
Figura 53: Ejemplo de variable lógica. Sale del bucle cuando i es igual a cero
c = a && b
123
Sistemas Electrónicos Digitales.
c = a || b
c = ! b
c = (a > b) && (a != d)
124
Sistemas Electrónicos Digitales.
Figura 54: Si P2 = 0xFA45, por orden de aparición a=0x0005, a=0xFA4F, a=0x0FA4 y a=0xA450
4 Instrucciones de control
Es habitual que los programas realicen tareas diferentes en función del valor de determinadas
variables. La instrucción if permite definir bloques de código que no son de ejecución
obligatoria y por lo tanto son bloques de instrucciones que el programa se puede saltar. Esta
decisión depende del valor de una condición lógica, que a su vez suele depender del valor que
adquieran determinadas variables del programa. También es posible definir dos bloques de
instrucciones alternativos, de manera que sólo se ejecute uno u otro en función del resultado
de la condición lógica.
Existen dos formatos básicos 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 (condición) {
...
}
if (condición) {
// si se cumple a condición
...
} else {
// si no se cumple la condición
...
125
Sistemas Electrónicos Digitales.
A continuación 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 asignación. En el caso de la igualdad la condición se cumple si a es igual a b. En el
caso de la asignación la condición se cumple si b es distinto de 0.
} }
Figura 56: AND lógico versus AND bit a bit. En el caso del AND lógico la condición se cumple si a y b son
distintos de cero. En el caso del AND bit a bit la condición 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 ningún problema para el
programador novato, que después de aprender a cortar y pegar puede repetir varias veces el
mismo código, pero dentro de un límite. 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, condición de salida y
sentencia final de bucle. Estos argumentos se escriben separados por punto y coma y no por
coma como en las funciones.
Como puede apreciarse, la variable i controla el número 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.
126
Sistemas Electrónicos Digitales.
i++) nunca entra en el bucle. La sentencia final se ejecuta al terminar cada iteración. Por lo
tanto si no se entra en el bucle esta sentencia no se ejecuta nunca.
while(condición){
...
}
El funcionamiento del bucle es como sigue: en primer lugar se evalúa la expresión condición.
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
ejecución continuará después de la llave }. Si por el contrario la condición es cierta, se
ejecutarán todas las instrucciones del bucle. Después de ejecutar la última instrucción del
bucle se vuelve a comprobar la condición y al igual que al principio se terminará el bucle si es
falsa o se realizará otra iteración si es cierta, y así sucesivamente.
do{
...
}while(condición);
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 característica fundamental es que todos los datos de un vector son del mismo
tipo, por ejemplo todos son int o todos son double. La definición de vectores es parecida
a la definición de variables, salvo que se debe especificar el tamaño 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 cuál queremos modificar. Esta
especificación se realiza indicando entre corchetes el número de orden del elemento, teniendo
en cuenta que la numeración de los elementos siempre empieza en cero.
127
Sistemas Electrónicos Digitales.
vec[10]=98.5; /* ERROR */
La segunda asignación no es correcta porque vec no es una variable tipo double sino un
vector y por lo tanto todas las asignaciones deben indicar el número de orden del elemento al
que queremos acceder. El vector vec del ejemplo tiene tamaño 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 asignación
tampoco es válida. Es importante destacar que el compilador no daría error en el último caso
ya que no comprueba los rangos de los vectores. Este tipo de asignación 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 atención ya que
sólo en algunos casos aparece un error de ejecución (generalmente cuando el índice del vector
es muy grande de forma que se accede a zonas en las que no hay memoría) pero en otras
ocasiones no aparece ningún mensaje de error y el programa simplemente funciona mal
(¡¡OJO: es muy difícil 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 tamaños de vectores y matrices en función de parámetros, ya que
luego se pueden modificar fácilmente sin tener que revisar todo el programa. Estos
parámetros se definen con la instrucción #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
definición:
#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 inicialización en C de un vector en la propia definición (Iniciado) o por código (SinIni)
128
Sistemas Electrónicos Digitales.
MAX equ 5
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 sólo 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
Sistemas Electrónicos Digitales.
los punteros almacenan direcciones de memoria, existen varios tipos de puntero dependiendo
de la definición 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, sólo se trabaja en un segmento,
como es el caso del laboratorio el tamaño utilizado es 16 bits). La única diferencia es que el
dato almacenado en la dirección de memoria contenida en el puntero pti es un entero,
mientras que el dato almacenado en la dirección 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 dirección de memoria (0x200) donde se encuentra almacenado el valor entero 5,
y ptc apunta a otra dirección de memoria (0x202) donde se encuentra almacenado el valor
0x10.
130
Sistemas Electrónicos Digitales.
7.2 El operador *
El operador unitario *, llamado operador de indirección, permite acceder al valor por medio
del puntero.
En ensamblador se usan los corchetes [ ] para acceder al valor que apunta el puntero. En la
Figura 60 se muestra la equivalencia entre C y ensamblador en el manejo de punteros. Es
importante hacer notar que en C es equivalente escribir *(pti+i) a escribir pti[i], por
definición; ya que ambos acceden al elemento que se encuentra desplazado i unidades de la
dirección de memoria que representa el puntero pti.
131
Sistemas Electrónicos Digitales.
main() {
int i1,i2;
int *pi;
i1 = 5;
pi = &i1;
i2 = *pi; // ¿i2?
}
*pti+=8;
pti+=8;
La primera línea suma 8 al entero al que apunta pti y por lo tanto el puntero sigue
apuntando al mismo sitio. La segunda línea incrementa en 8 el puntero, por lo tanto éste pasa
a apuntar a otro sitio.
132
Sistemas Electrónicos Digitales.
Cada posición de memoria del micro almacena un byte. Dado que cada tipo de dato ocupa un
número de bytes diferente (un char sí ocupan 1 byte, un int ocupa 2 bytes), los punteros
de cualquier tipo siempre apuntan al primer byte de la codificación de cada dato. Cuando se
accede al dato mediante el operador *, el compilador se encarga de acceder a los bytes
necesarios a partir de la dirección almacenada en el puntero. En el caso de punteros a char
sólo se accede al byte almacenado en la dirección de memoria del puntero, mientras que el
caso de punteros a int se accede a la posición de memoria indicada en el puntero y al
siguiente byte. Todo este mecanismo ocurre de manera automática y el programador sólo
debe preocuparse de definir punteros a char cuando quiere trabajar con valores char o
punteros a int cuando quiere trabajar con valores int.
También para facilitar las cosas, la operación de sumar 1 a un puntero hace que su dirección
se incremente la cantidad necesaria para pasar a apuntar al siguiente dato del mismo tipo. Es
decir sólo en el caso de variables que ocupan 1 byte en memoria (variables char) la
operación de incremento aumenta en 1 la dirección de memoria, en los demás casos la
aumenta más.
Lo que hay que recordar es que siempre se incrementa un dato completo y no hace falta
recordar cuánto ocupa cada dato en la memoria. Por ejemplo, si ptc es un puntero a char
que vale 0x202, la operación ptc++ hará que pase a valer 0x203. Por otro lado si pti es
un puntero a int que vale 0x200, la operación pti++ hará que pase a valer 0x202.
133
Sistemas Electrónicos Digitales.
MAX equ 5
#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++;
}
}
134
Sistemas Electrónicos Digitales.
8 Funciones
Una técnica muy empleada en la resolución de problemas complejos es la conocida como
“divide y vencerás”. La manera más elegante de construir un programa es dividir la tarea a
realizar en otras tareas más simples. Si estas tareas más simples no son aún lo suficientemente
sencillas, se vuelven a dividir, procediendo así hasta que cada tarea sea lo suficientemente
simple como para resolverse con unas cuantas líneas de código. A esta metodología de diseño
se le conoce como diseño de arriba-abajo (top-down). Otras ventajas de la división de un
problema en módulos claramente definidos es que facilita el trabajo en equipo y permite
reutilizar módulos creados con anterioridad si estos se han diseñado de una manera
generalista. En C este tipo de módulos 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 función del resto del programa, ya que la función 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 metodología de diseño 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, encargándose cada uno de
ellos de una o varias funciones de las que se compone el programa.
• Al ir construyendo el programa por módulos se pueden ir probando estos módulos
conforme se van terminando, sin necesidad de esperar a que se termine el programa
completo. Esto hace que, tanto la prueba de los módulos, como la corrección de los
errores cometidos en ellos, sea mucho más fácil al tener que abarcar solamente unas
cuantas líneas de código, en lugar de las miles que tendrá el programa completo.
• Una vez construido el programa, también el uso de funciones permite depurar los
problemas que aparezcan más fácilmente, pues una vez identificada la función que
falla, sólo hay que buscar el fallo dentro de dicha función 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
Sistemas Electrónicos Digitales.
mayúsculas y realizamos una función que realice dicha tarea, esta función se podrá
usar en otros programas sin ningún cambio. Si por el contrario la tarea de convertir a
mayúsculas se “incrusta” dentro del programa, su reutilización será muchísimo más
difícil.
En resumen la norma básica es <<¡¡Hacer funciones cortas y que hagan una sola cosa!!
(fáciles de desarrollar y de depurar, reutilizables)>>
En C las funciones deben ser declaradas (en los ficheros cabecera *.h) y definidas. La
diferencia entre declaración y definición consiste en que siempre que se habla de definición
implica que existe una reserva de memoria para aquello que se define. En cambio en un
declaración no.
p = 1;
for (i=1; i<=n; i++)
p = p * base;
return p;
}
136
Sistemas Electrónicos Digitales.
main () {
int i, a, b;
a = 2; b =10;
i = power(b,a);
}
p = 1;
for (i=1; i<=n; i++)
p = p * base;
return p;
}
137
Sistemas Electrónicos Digitales.
es un paso por valor de un puntero, que equivale a un paso por referencia, ya que aunque no
se puede cambiar la dirección del puntero (ya que se ha pasado por valor y por lo tanto se pasa
una copia), se puede modificar el contenido de la dirección de memoria a la que apunta el
puntero con el operador unario * (derreferenciación). En la Figura 66 se muestra a la
izquierda un ejemplo de paso de parámetros por valor, en donde a y b quedan inalterados
porque la función 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 después de la llamada a la función swap.
138
Sistemas Electrónicos Digitales.
#define MAX 5
main() {
int vector[]={1,5,8,4,3};
int maximo;
maximo = max(vector, MAX);
}
#define MAX 50
main() {
int *pti;
pti = get_vector_discreto(1, 4);
}
139
Sistemas Electrónicos Digitales.
#define MAX 50
main() {
int vector[MAX];
get_vector_discreto(1, 4, vector);
}
#define MAX 50
main() {
int vector[MAX];
get_vector_discreto(1, 4, vector);
}
#define MAX 50
main() {
int vector[MAX];
get_vector_discreto(1, 4, vector);
}
140
Sistemas Electrónicos Digitales.
9 Cuestiones de comprensión
1) Dada la siguiente definición de variable
char c;
2) Señala con un círculo los casos en los que b es igual a 1; es decir, en los que se cumple la
condición.
CASO A)
int a;
b = 0;
a = -1;
if (a)
b = 1;
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
Sistemas Electrónicos Digitales.
MAX equ -1 ;1
142
Sistemas Electrónicos Digitales.
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.
143
Sistemas Electrónicos Digitales.
7) Supuesto un sistema digital como el del laboratorio, explica con tus palabras qué hacen los
programas siguientes:
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
Sistemas Electrónicos Digitales.
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:
#include <reg167.h>
void main(void) {
145
Sistemas Electrónicos Digitales.
• Lee un dato de los interruptores conectados a la parte alta del puerto P2.
• Accede a la dirección de memoria indicada en los interruptores (8 bits más altos de la
dirección todos a 0).
• Analiza los ocho bits del dato almacenado en dicha dirección.
• Si hay siete bits a 1 y uno a 0, se indica en los LED más bajos de P2 la posición 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 más de un bit a 0 se encienden los cinco LED conectados a las líneas menos
significativas de P2.
#include <reg167.h>
void main(void) {
146
Sistemas Electrónicos Digitales.
147
Sistemas Electrónicos Digitales.
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 más altos de P2 se proporciona el primer dato. Este dato se valida
cuando la línea P2.8 pasa de 0 a 1.
• A continuación se introduce el segundo dato (siguiendo el mismo procedimiento que
para el primer dato).
• Por último se introduce la operación (+: P2.12 = 1, -: P2.13=1 y *: P2.14=1). La
operación se valida de la misma forma que los datos. En caso de que no se haya
seleccionado una operación no se calcula el resultado.
• El proceso anterior se repite indefinidamente, manteniéndose en la parte baja de P2 el
resultado de la última operación realizada.
• La línea P2.8 llevará un filtrado de rebotes.
148
Sistemas Electrónicos Digitales.
149
Sistemas Electrónicos Digitales.
#include <reg167.h>
#define MAX 3
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); // 1 ms
temp = P2;
actual = temp & 0x0100;
if (anterior != actual)
contador = MAX;
else if (contador > 0)
contador--;
else ;
anterior = actual;
if (contador == 0) {
contador = -1;
if (actual)
dato[ciclo++] = temp >> 12; //recogida de dato
}
if(ciclo == 3) { // Operaciones
ciclo = 0;
P2 = ~result;
}
}
}
150
Sistemas Electrónicos Digitales.
151
Sistemas Electrónicos Digitales.
#include <reg167.h>
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
Sistemas Electrónicos Digitales.
153
Sistemas Electrónicos Digitales.
La solución propuesta utiliza una función filtraP2, que no es más que una función genérica de
filtrado de rebotes del puerto P2. Eso significa que se puede usar en cualquier programa que
requiera filtrado de rebotes en P2. Se podría 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 sólo se tiene que preocupar del estado de los mismos. Además es un driver con
autoinicialización ya que detecta cuándo se utiliza por primera vez e inicializa las variables
que necesita.
#include <reg167.h>
#define MAX 3
int contador[16],inicializado=0;
unsigned int anterior[16], estado[16];
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) // flanco de subida
if (personas < 50)
personas++;
anterior8 = actual8;
actual9 = filtraP2(9);
if (anterior9 != actual9 && !actual9) // flanco de bajada
if (personas > 0)
personas--;
anterior9 = actual9;
P2 = ~personas;
} 154
}
Sistemas Electrónicos Digitales.
155
Sistemas Electrónicos Digitales.
156
Sistemas Electrónicos Digitales.
157
Sistemas Electrónicos Digitales.
Capítulo 9 INTERRUPCIONES
Para que un periférico funcione en modo interrupción, es necesario configurarle para ello.
Una petición de interrupción de un periférico sólo interrumpe a la CPU
158
Sistemas Electrónicos Digitales.
xxIC
15 8 7 6 5 2 1 0
Por otro lado, el registro PSW juega un papel muy importante en las interrupciones:
15 12 11 3 2 1 0
ILVL IEN - Z V C N
159
Sistemas Electrónicos Digitales.
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 algún momento se quiere
ejecutar un trozo de código al cual no se puede interrumpir (zona crítica), 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 interrupción.
Los 4 bits que van del 12 al 15, indican la prioridad del programa en curso. De forma que una
interrupción solamente puede interrumpir a la CPU si el ILVL del registro xxIC de la
interrupción correspondiente, es mayor que el ILVL del PSW. El ILVL del PSW es cero
cuando se ejecuta main.
Por lo tanto para que un periférico funcione en modo interrupción es necesario configurarlo
de la siguiente manera:
• Habilitar la interrupción, bit xxIE del registro xxIC.
• Poner la prioridad de la interrupción, ILVL del registro xxIC.
• Habilitar de forma global las interrupciones, bit IEN del registro PSW.
4 Ejemplos
A continuación 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.
160
Sistemas Electrónicos Digitales.
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
void main(void) {
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;
}
}
ticks++;
if (ticks == 1000) {
ticks = 0; sec++;
if (sec == 60) {
sec = 0; min++;
if (min == 60) {
min = 0; hour++;
if (hour == 24)
hour = 0;
}
}
}
}
161
Sistemas Electrónicos Digitales.
5 Práctica 7: interrupciones en C
162
Sistemas Electrónicos Digitales.
163
Sistemas Electrónicos Digitales.
164
Sistemas Electrónicos Digitales.
2 Sistemas muestreados
Un sistema muestreado se basa en que cada cierto intervalo de tiempo, llamado periodo de
muestreo, realiza determinadas tareas síncronas. La ventaja de estos sistemas es que se sabe
con bastante precisión 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 más tiempo libre de CPU, ya que entre intervalos de muestreo la CPU queda libre.
Volviendo al ejemplo del capítulo 6 sección 12, si se quiere medir el ancho de un pulso que
entra por línea P7.4 la solución 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 solución no es muestreada porque está mirando continuamente de forma asíncrona el
estado del pin P7.4. Para poder determinar el ancho del pulso tiene que usar un Timer, ya que
por la propia ejecución de líneas de código no sería factible estimar este tiempo.
165
Sistemas Electrónicos Digitales.
regulares, lo que supone que contando el número 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; /* 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;
DP7 = 0;
anterior = P7.4;
while(1);
}
166
Sistemas Electrónicos Digitales.
Por ejemplo, si un sistema tiene que medir el ancho de pulso con una resolución de 1
milisegundo y además generar un pulso de 350 microsegundos en algún momento, claramente
el periodo de muestreo es el máximo común 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
sección se pretende dar una metodología para hacer programas que dependan del tiempo de
forma sencilla y simple.
Lo primero que es necesario saber es la medida mínima que se quiere poder contabilizar; es
decir, la resolución de la medida de tiempo. En caso de ser un sistema muestreado, esta
resolución coincide con el periodo de muestreo.
En segundo lugar, cada medida de tiempo que se quiere tener supone la creación 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 código resultante sería:
167
Sistemas Electrónicos Digitales.
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int mseg_evento1=0, mseg_evento2=0;
void main(void) {
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 */
while(1){
if (mseg_evento1 == 1000) {
mseg_evento1 = 0;
// programar aquí el evento 1
}
if (mseg_evento2 == 350) {
mseg_evento2 = 0;
// programar aquí el evento 2
}
}
}
168
Sistemas Electrónicos Digitales.
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int mseg_evento1=0, mseg_evento2=0;
void main(void) {
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 */
while(1){
retardo(1); /* 1 ms*/
mseg_evento1++;
mseg_evento2++;
if (mseg_evento1 == 1000) {
mseg_evento1 = 0;
// programar aquí el evento 1
}
if (mseg_evento2 == 350) {
mseg_evento2 = 0;
// programar aquí el evento 2
}
}
}
En caso de que la medida de tiempo sea asíncrona; es decir, se hiciera a partir de un evento
que no se puede predecir cuándo sucede, sería el propio evento el que pondría a cero el
contador.
169
Sistemas Electrónicos Digitales.
#include <reg167.h>
#include <stdio.h>
#define PERIOD -2500
int mseg_evento1=0;
void main(void) {
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 */
while(1){
if ( … ) { // si sucede evento 1
mseg_evento1 = 0;
}
if (mseg_evento1 == 1000) {
// programar aquí el evento 2
}
}
}
170
Sistemas Electrónicos Digitales.
2. Para cada uno de esos procesos, se deben determinar las etapas de las que consta y
cuál 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 método según una metodología basada en estados:
1. Determinar las máquinas de estado que tiene el sistema
2. Para cada máquina 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
int estado_actual=0;
void maquina(){...}
2. Definir una función de estado, por cada estado de cada máquina de estados. Estas
funciones serán invocadas desde la función que controla cada máquina de estados
según el valor de la variable estado_actual.
void maquina(){
if (estado_actual == 0)
estado0();
else if (estado_actual == 1)
estado1();
}
void estado0(){
if (var > 0)
estado_actual = 1
}
Las transiciones de un estado a otro pueden suceder por el paso de tiempo, en ese caso la
variable que se usa en la comparación será un contador de tiempo asíncrono, tal y como se
explicó en la sección 3 de este capítulo.
171
Sistemas Electrónicos Digitales.
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