SISTEMAS ELECTRÓNICOS DIGITALES
|
|
|
- Milagros Valdéz Castellanos
- hace 10 años
- Vistas:
Transcripción
1 SISTEMAS ELECTRÓNICOS DIGITALES Autores: Fernández Martínez Cesáreo Sánchez Miralles Álvaro
2 Capítulo 1 Filosofía del libro 5 Capítulo 2 Arquitectura de un micro 6 1 Introducción 6 2 Objetivos y conceptos a entender en este capítulo 6 3 Modelo del programador de un micro La CPU La memoria Codificación de las instrucciones Ciclos de ejecución de una instrucción Ejemplo de funcionamiento de la ejecución de un programa Distintos niveles de abstracción de un sistema electrónico digital Organización de un micro a nivel de bloques y buses 15 4 Cuestiones de comprensión 17 Capítulo 3 Arquitectura y Mapa de memoria del C Objetivos y conceptos a entender en este capítulo 18 2 Arquitectura del C Modelo del programador del C Ensamblador de C Resumen de instrucciones en ensamblador Modos de direccionamiento Números con signo y sin signo Ejemplos básicos de codificación en ensamblador Mapa de memoria del C Los registros de propósito general GPRs Principales SFRs de la CPU 29 3 Cuestiones de comprensión 33 4 Ejercicios propuestos 33 5 Práctica 1: Introducción al Siemens C Capítulo 4 Puertos 48 1 Objetivos y conceptos a entender en este capítulo 48 2 Puertos paralelo 48 3 Ejercicios propuestos 52 4 Práctica 2: entradas y salidas digitales 53 5 Práctica 3: ensamblar y depurar 55 Capítulo 5 Periféricos 60 1 Objetivos y conceptos a entender en este capítulo 60 2 Periféricos del C El Timer 61 1
3 3.1 Registro de control T01CON Registros de datos Registro de control de interrupciones T0IC Resumen de funcionamiento Ajuste del pre-escalado Ejemplo de programación: LEDs a ritmo de reloj 63 4 Cuestiones de comprensión 65 5 Ejercicios propuestos PWM sencillo (30 min) 66 6 Práctica 4: timers 68 Capítulo 6 Ensamblador 71 1 Objetivos y conceptos a entender en este capítulo 71 2 Introducción Codificación de instrucciones 71 3 Operaciones de transferencia de datos MOV y MOVB MOVBZ y MOVBS PUSH y POP 73 4 Instrucciones para realizar operaciones aritméticas ADD y ADDB SUB y SUBB NEG MUL y MULU DIV y DIVU 76 5 Instrucciones para realizar operaciones lógicas AND OR XOR CPL 79 6 Instrucciones para realizar desplazamientos de bits 79 7 Saltos 81 8 Ejemplos de equivalencias de C y ensamblador Condición if Bucle for Bucle while 84 9 Instrucciones a nivel de bit Saltos Otras Directivas de ensamblador Cuestiones de comprensión Ejemplo de discusión: medida de ancho de pulso, sin/con filtrado de rebotes Ejercicios 100 2
4 13.1 Acceso a memoria (15 min) Encendido apagado de LED (10 min) Volcado de memoria (20 min) Cuenta de pulsos (30 min) Calculadora (40 min) Ejercicios resueltos LEDs e interruptores Medida de ancho de pulso con rebotes (20 min) Medida de ancho de pulso (20 min) Práctica 5: ejercicios en ensamblador, control de un servo 112 Capítulo 7 Drivers. El convertidor AD Objetivos y conceptos a entender en este capítulo Concepto de driver Ejemplos de driver Driver sencillo del puerto P Driver del Timer Driver del convertidor AD El convertidor analógico digital (AD) Registro de control ADCON Registro de datos ADDAT Registro de control de interrupciones ADCIC Ejemplo de programación 119 Capítulo 8 Programación en C para micros Objetivos y conceptos a entender en este capítulo Tipos de datos para el C Números enteros Números reales Variables lógicas Operadores bit a bit Instrucciones de control Bucles Vectores Punteros El operador & El operador * Operaciones con punteros Funciones Variables globales y locales Paso de parámetros por "referencia" Paso de vectores como parámetros Cuestiones de comprensión Ejercicios propuestos 145 3
5 10.1 Timer y puertos (40 min) Acceso a memoria (40 min) Ejercicios resueltos La calculadora (30 min) El coche fantástico (20 min) El autobus (30min) Práctica 6: ejercicios en lenguaje C 155 Capítulo 9 Interrupciones Objetivos y conceptos a entender en este capítulo Nociones básicas de interrupciones Recursos utilizados en una interrupción Ejemplos Práctica 7: interrupciones en C 162 Capítulo 10 Sistemas digitales complejos Objetivos y conceptos a entender en este capítulo Sistemas muestreados Fechado Programación basada en estados 170 Referencias 172 4
6 Capítulo 1 FILOSOFÍA DEL LIBRO El objetivo de este libro es optimizar el aprendizaje del lector, presentándole un material autocontenido que incluye información teórica, ejemplos, cuestiones de comprensión, ejercicios propuestos y resueltos, problemas propuestos y resueltos, y finalmente las prácticas de laboratorio. Adicionalmente, cada capítulo tiene una sección que informa de los conceptos prioritarios que deben quedar claros en el mismo. La organización se ha hecho para conseguir una sincronización perfecta entre los contenidos teóricos y de laboratorio, de forma que el lector pueda conocer qué es lo que tiene que saber para hacer una práctica. Además todo el libro sigue un orden que se corresponde con el orden cronológico de las clases presenciales. 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
7 Capítulo 2 ARQUITECTURA DE UN MICRO 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. 2 Objetivos y conceptos a entender en este capítulo Entender por qué es importante conocer el modelo del programador de un micro. Entender el modelo del programador, conociendo las unidades de las que consta y cómo se comunican entre sí. para qué sirve el PC? Entender cómo la CPU ejecuta una instrucción Entender qué almacena físicamente una memoria y los niveles de abstracción que permiten interpretar esa información. 3 Modelo del programador de un micro Desde el punto de vista del programador, según el modelo Von Neumann, un micro se ve como una máquina con los bloques mostrados en la Figura 1: La unidad de control y la unidad aritmético lógica (ALU) que junto con los registros forman la CPU. La ALU es la encargada de realizar las operaciones aritméticas que requieran cada una de las instrucciones, los registros son celdas de memoria de acceso rápido y la unidad de control reparte trabajo y coordina el resto de bloques. La memoria principal, que es la encargada de almacenar datos, programas y resultados intermedios (más grande pero más lenta que el banco de registros). La unidad de entrada y salida de datos (I/O). Elemento imprescindible para que el microcontrolador se pueda comunicar con el exterior, de otra forma sería inútil. Gracias a esta unidad se pueden conectar sensores y actuadores al micro, además de poderse comunicar con otros micros y sistemas digitales. 6
8 Unidad de Memoria Unidad de Entrada Unidad Aritmética y lógica (ALU) Unidad de salida 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
9 Los datos están almacenados en formato binario en celdas de 8 bits. Por ejemplo el número 5 está codificado de la siguiente forma: x05 Números más grandes, por ejemplo el 127: x7F 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 05 7F FFFF A0 Figura 2: Organización de la memoria El micro sabe qué direcciones de memoria contienen instrucciones y qué direcciones de memoria contienen datos: Toda dirección de memoria que se acceda a través del registro PC, el micro interpreta su contenido como una instrucción de programa. Toda dirección de memoria que se acceda de otra manera se considera como que contiene un dato; por ejemplo cuando el micro acceda para coger un dato y guardarlo en un registro de proposito general. 8
10 3.3 Codificación de las instrucciones Las instrucciones también están almacenadas en memoria en formato binario. Por ejemplo, la siguiente instrucción: add R1,R0 Significa: Suma el dato almacenado en el registro R0 con el dato almacenado en el registro R1 y deja el resultado en el registro R1. Esta instrucción podría estar codificada de la siguiente forma x1010 Los 16 bits del código de instrucción indican: Los 4 bits más significativos almacenan el código de la instrucción (0001 para la instrucción ADD) Los 12 bits menos significativos indican cuáles son los operandos o parámetros de la instrucción. Para el caso de la instrucción anterior, los 4 bits menos significativos codifican el número de registro que se usa como sumando primero (0000 significa R0), los 4 siguientes bits codifican el número de registro que se usa como sumando segundo (0001 significa R1) y por último los siguientes bits no se usan NA Rs2 Rs1 Utilizando esta codificación el microprocesador en cuestión podría hacer operaciones de suma de: Un máximo de 16 registros de propósito general (dado que sólo se utilizan cuatro bits para codificar el número de registros. Un máximo de 16 instrucciones (cuatro bits para el código de instrucción). Los micros reales, en particular el C167, tienen más registros y soportan en amplio conjunto de operaciones aritmético/lógicas. Otro ejemplo posible de codificación siguiendo este esquema es el siguiente: move R0,0x10 9
11 Pone lo que hay en la dirección de memoria 0x10 en el registro R0. La codificación podría ser: x Rs mem Los 4 bits más significativos almacenan el código de la instrucción (0002 para la instrucción MOV Rx, mem) Los 8 bits menos significativos codifican la dirección de la memoria de donde se coge el dato (0x10), los 4 siguientes bits codifican el número de registro que se usa como destino (0000 significa R0). Estos ejemplos de codificación indican que: 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. 3.4 Ciclos de ejecución de una instrucción La CPU es la encargada de ejecutar las instrucciones que están en la memoria a partir de la posición de la misma que indique el PC. La ejecución de una instrucción supone la ejecución de dos ciclos, ver Figura 3: 1. Ciclo de Fetch: en este ciclo se busca la instrucción que se tiene que ejecutar y se interpreta para saber qué se tiene que ejecutar. Además incrementa PC para que apunte a la siguiente instrucción. Los pasos de este ciclo son: a. El Contador de Programa (PC) contiene la dirección de la próxima instrucción a ejecutar b. El procesador captura la instrucción de memoria c. La instrucción se carga en el Registro de Instrucciones (IR) d. El PC se incrementa (salvo en las instrucciones de salto, que el PC será el valor de la dirección de salto). e. Se interpreta la instrucción y se generan las señales de control (decodificación instrucción) 2. Ciclo de ejecución: en este ciclo se ejecuta propiamente lo que indica la instrucción. La CPU puede ejecutar diferentes instrucciones: a. Transferencia de procesador a memoria 10
12 b. Transferencia de procesador a I/O c. Procesado de datos. La ALU efectúa una operación sobre los datos d. Instrucciones de control. Alteran la secuencia de programa; p.e. Jump e. Combinación de las anteriores Figura 3: ciclos de ejecución de una instrucción 3.5 Ejemplo de funcionamiento de la ejecución de un programa A continuación se presenta un ejemplo muy importante desde el punto de vista conceptual, que ilustra cómo un micro ejecuta un conjunto de instrucciones, poniendo de manifiesto los conceptos explicados en anteriores secciones. 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: (0x84) = (0x80) + (0x82) los paréntesis indican "lo que hay en la dirección de memoria". 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
13 Una vez cargado el programa en la posición 0, la memoria queda como se indica en la Instrucción Instrucción Instrucción Instrucción Dato Dato FFFF A0 Figura 4: Memoria después de cargar el programa ejemplo 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 PC IR R R1 Figura 5: ejecución de la instrucción mov R0, 0x80 12
14 PC IR R R1 Figura 6: ejecución de la instrucción mov R1, 0x82 ALU: R1 <- R0+R PC R IR A R R2 Figura 7: ejecución de la instrucción add R1,R PC R IR A R A 0000 R2 Figura 8: ejecución de la instrucción move 0x84, R1 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. 3.6 Distintos niveles de abstracción de un sistema electrónico digital Según al nivel que se trabaje, se puede ver un sistema electrónico de muchas maneras, como se puede ver en la Figura 9. 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
15 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. PROGRAMAS uc DATOS PROGRAMAS FPGA Lenguaje C Códigos de alto nivel ASCII Interpretación 0's y 1's Ensamblador Interpretación 0's y 1's Números con y sin signo Lenguaje VHDL Nivel lógico 0's y 1's 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
16 3.7 Organización de un micro a nivel de bloques y buses Aunque la organización de un micro no es el objetivo de la asignatura, es entender qué es un Sistema Electrónico Digital (SED) es necesario introducir algunos aspectos de organización, como es la composición a nivel de bloques físicos y la conexión entre bloques mediante buses. En la Figura 10 se muestra el modelo Von Neumann a nivel de bloques y de buses. Un bus no es más que un conjunto de líneas común a varios bloques que permite la comunicación entre ellos. En un SED típico tenemos tres buses: Bus de direcciones. Bus de datos. Bus de control. CPU (ALU, Registros y Control) Memoria Entrada/Salida Bus del sistema Bus de datos Bus de direcciones Bus de control Figura 10: Modelo Von Neumann a nivel de hardware Desde la CPU el exterior se ve como direcciones. Cuando se quiere acceder a un dato en la memoria, la CPU pone en el bus de direcciones la dirección de la memoria donde se encuentra el dato y la memoria le da el dato en el bus de datos. El bus de control sirve para organizar la transferencia del dato entre memoria y CPU (o entre I/O y CPU). Por ejemplo, para leer el dato de la posición 0x82: 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
17 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. Los microcontroladores tienen periféricos y memoria integrados en el chip de CPU, mientras que los microprocesadores no. Los periféricos sirven para comunicar la CPU con el exterior y para realizar ciertas tareas sin consumir tiempo de CPU del micro; por ejemplo hay periféricos que sirven para controlar motores, otros digitalizan señales analógicas, etc. Al igual que la CPU los periféricos tienen registros que le permiten funcionar. Se dice que un periférico está mapeado en memoria si la CPU ve a los registros del periférico como una dirección más de memoria; es decir, el micro accede a los registros del periférico de la misma manera que lo hace para acceder a cualquier otra dirección de memoria. El mapa de memoria describe de forma gráfica qué hay en cada rango de direcciones: memoria RAM, ROM o Periféricos. 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
18 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. 0) Qué significa modelo del programador? 1) De qué partes consta un microprocesador según el modelo del programador? 2) Qué significa que un microprocesador sea de 16 bits? 3) Qué diferencia un microprocesador de un microcontrolador? 4) Qué es el PC? Para qué sirve? 5) De qué diferentes formas se te ocurren que se pueden interpretar los bits que se almacenan en la memoria de un micro? 17
19 Capítulo 3 ARQUITECTURA Y MAPA DE MEMORIA DEL C167 1 Objetivos y conceptos a entender en este capítulo Por orden de importancia: Entender cómo se almacenan los datos y los programas, así como la ejecución de los mismos que permite relacionar ambos. Entender los modos de direccionamiento Entender la arquitectura y, sumamente importante, ver la equivalencia entre el modelo del programador presentado en este capítulo y el presentado de forma general en el capítulo anterior. Empezar a familiarizarse con el ensamblador y la equivalencia que tiene con el C. Por ello en este capítulo se recomienda empezar un esquema, que se seguirá completando en sucesivos capítulos, con las equivalencias entre el ensamblador y el C. Entender cómo se organiza la memoria del C167 Hacerse con la terminología "direccionar", "modo de direccionamiento", "puntero", etc. 2 Arquitectura del C167 Es un microcontrolador de 16 bits, lo que implica que la ALU, el bus de datos y los registros son de 16 bits. Es un micro muy robusto, diseñado para tareas de control industrial. Es capaz de direccionar (pedir direcciones) 16 Mbytes de memoria, es decir, su bus de direcciones es de 24 bits. Como microcontrolador que es lleva incorporados muchos periféricos integrados en el chip: Controladores de comunicaciones serie: para comunicarse con el exterior en serie; por ejemplo un PC. Puertos paralelo: para comunicarse con el exterior; por ejemplo para conectar LEDs, interruptores, un PC, etc. 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. Moduladores PWM: muy usados en electrónica de potencia para controlar motores, etc 18
20 2.1 Modelo del programador del C167 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
21 Memoria CPU FF FFFF Registros R7 R15 PC R6 R14 PSW R5 R13 SP R4 R3 R12 R11 (SFR s) R2 R R1 R0 (GPR s) R9 R8 00 FE00 00 FE02 I/O (SFR s) 00 FE0X Figura 11: modelo del programador del C Ensamblador de C167 Como se comentó en el capítulo 2 sección 3.6, el lenguaje ensamblador es el lenguaje de más bajo nivel que entienden las personas, ya que cada instrucción ensamblador se corresponde con una instrucción código máquina que entiende el micro, es decir, hay una correspondencia biunívoca entre el ensamblador y el código máquina, entre lo que entienden las personas y lo que entienden las máquinas. 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) (0x104) = (0x100) + (0x102) En lenguaje ensamblador de C167 queda de la siguiente forma: MOV MOV ADD MOV R0,0x100 R1,0x102 R1,R0 0x104,R0 20
22 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). A la hora de presentar ejemplos más complejos usaremos como lenguaje de descripción en alto nivel el lenguaje C, que se da por conocido (a nivel básico). El lenguaje en ensamblador se explicará en detalle en el capítulo 6. En el capítulo 7 se explicará el detalle de las particularidades del lenguaje C en la programación de micros, y lo que es más importante la relación entre el lenguaje C y el ensamblador. Para empezar, se va a presentar el primer código ensamblador equivalente al programa siguiente en C, que no es más que un bucle para incrementar una variable N veces. Es necesario recordar que en C j += 1 es equivalente a j = j +1. for (i=1; i <=N; i++) j += 1; Suponiendo que N=5 y que el dato al que representa j se encuentra en la dirección de memoria 0xfa00, el programa en ensamblador equivalente sería: Dirección inicial del programa 500 E0 10 MOV R0,#1 ; r0 (i) 502 E0 11 MOV R1,#1 ; auxiliar CMP R0,#5 ; if i>n 506 AD 05 JMPR cc_sgt,0x510 ; then goto 512H F1 00 FA ADD 0xfa00,R1 ; j += 1 50C ADD R0,#1 ; i += 1 50E 0D FA JMPR cc_uc,0x504 ; salto sin condición 510 Dirección de memoria Codificación de la instrucción A simple vista se pueden observar varias cosas del programa: En lenguaje ensamblador más largo que en C Una línea de ensamblador se corresponde con una instrucción en código máquina que almacena en una dirección de memoria. Cada instrucción de ensamblador se corresponde con una operación elemental, donde casi siempre están involucrados los GPRs 21
23 Las instrucciones se almacenan en memoria en formato binario (1 s y 0 s), aunque en se hayan mostrado en hexadecimal por simplificar la notación. Las instrucciones ocupan 2 ó 4 bytes. Por ejemplo la instrucción 0x0801 situada en la dirección 0x50C ocupa 2 bytes, muestras que la instrucción 0x04F100FA situada en la dirección 0x508 ocupa 4 bytes. Las instrucciones se almacenan en posiciones de memoria consecutivas. Se ejecutan de forma secuencial, salvo el los saltos. 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. Es necesario hacer notar que a lo largo de la ejecución del programa el PC contiene la dirección de la siguiente instrucción a ejecutar, para más detalles ver capítulo 2 sección 3.3. Cada vez que la CPU ejecuta una instrucción, incrementa el PC en dos o cuatro, para que apunte a la dirección de memoria de la siguiente instrucción. En la terminología de programación cuando una variable o registro contiene como dato una dirección de memoria, se dice que la variable o el registro es un puntero que apunta a una determinada dirección de memoria. 22
24 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 Etiquetas (op) En PC 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 Figura 12: comparación entre ensamblador de línea y de PC El ensamblador de línea es lo más parecido al código máquina ya que cuando se escribe se debe tener muy claro en qué dirección de memoria se encuentra cada instrucción, de forma que cuando se hacen saltos se tiene que poner la dirección de memoria donde se salta. Esto es así porque cuando se escribe ensamblador en línea se está escribiendo código máquina directamente en la memoria, gracias a un programa que está cargado en la pastilla del micro que se llama monitor y que es capaz de comunicarse una consola del PC y escribir en memoria el código ensamblador que se quiera. 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 Resumen de instrucciones en ensamblador Se pueden clasificar las instrucciones de ensamblador en diferentes tipos: 23
25 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. A continuación se muestran ejemplos de utilización de instrucciones para irse familiarizando con el ensamblador y el C: MOV R0, #0x4433. Esto hace R0 = 0x4433 MOV R1, R0. Es equivalente a R1 = R0, por lo tanto R1 = 0x4433 AND R1, #0xFF00. Es equivalente a R1 = R1 & 0xFF00. Un and lógico bit a bit, quedando R1 = 0x4400. MOV R2, R0. Es equivalente a R2 = R0, por lo tanto R2 = 0x4433. AND R2, #0x00FF. Es equivalente a R2 = R2 & 0x00FF. Un and lógico bit a bit, quedando R2 = 0x0033. SHR R1, #8. Es R1 = R1 >> 8. Un desplazamiento de bits a la derecha, quedando R1 = 0x0044. ADD R1, R2. Es una suma aritmética R1 = R1 + R2, quedando R1 = 0x0077. CPL R1. Realiza el complemento a 1, en C sería R1 = ~R1, quedando R1 = 0xFF88. XOR R1,#0xFFFF. Realiza un XOR de R1, en C sería R1 = R1 ^0xFFFF. Este ejemplo es interesante ya que un XOR con 0xFFFF es lo mismo que un complemento a 1, justo como el ejemplo anterior Modos de direccionamiento El 167 dispone de los siguientes modos de direccionamiento para acceder a los operandos: 24
26 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] A continuación se describen diferentes ejemplos de utilización con la instrucción MOV: MOV R0,R1 ; Directo a registro (R1 -> R0) MOV R0,#5 ; Inmediato (5 -> R0) MOVB RL0,#0 ; Inmediato al byte bajo (0 -> R0) MOVB RH0,#3 ; Inmediato al byte alto (3 -> R0) MOV R1,0xFA00h ; Directo desde memoria MOV R1,[R0] ; Indirecto ( (R0) -> R1) MOV [-R0],R1 ; Indirecto con pre-decremento MOV [R0+],R1 ; Indirecto con post incremento MOV R1,[R0+#4] ; Indirecto con desplazamiento La primera instrucción utiliza direccionamiento directo a registro en ambos operandos. La instrucción carga el contenido del registro R1 en el registro R0, a nivel de word (16 bits). La segunda instrucción utiliza direccionamiento inmediato (símbolo #) en el segundo operando. La instrucción carga el número 5 en el byte (8 bits) bajo de R0. La siguiente instrucción carga 0 en el byte alto de R0. La parte baja R0 recibe el nombre de RL0, mientras que la parte alta recibe el nombre de RH0. No olvidar especificar parte alta o baja del registro. De lo contrario el ensamblador da el error 74: "Illegal Operand Type". La instrucción MOV R1,0xFA00h carga el valor almacenado en la posición de memoria 0xFA00h en R1, utilizando direccionamiento a memoria (16 bits para la dirección). En el direccionamiento largo se utilizan los registros DPP's (Data Page Pointers). Nótese que si en la segunda instrucción olvidáramos el símbolo #, en R0 se cargaría el contenido de la posición 5 de memoria, en lugar del número 5. La instrucción MOV R1,[R0] carga el contenido de la posición de memoria apuntada por R0, en R1. El modo de direccionamiento se conoce con el nombre de "indirecto". (Nótense los corchetes en R0 para indicar contenido de la posición de memoria). Si en R0 tenemos almacenado el valor 0x200, se carga en R1 el contenido de la posición de memoria 0x200. En el acceso a memoria se utilizan los DPP's. Las dos instrucciones siguientes utilizan variantes del modo de direccionamiento indirecto. Variantes con pre-decremento y con post-incremento. Estas variantes, a parte de obtener el operando, actualizan el valor del puntero (registro de direcciones). En las instrucciones con pre-decremento el puntero se decrementa antes de obtener el operando. 25
27 Luego si en R0 se tiene el valor 0x200, la instrucción MOV [-R0],R1 decrementa en dos unidades (R0 = 0x1FE). A continuación carga el valor almacenado en R1 (2 bytes) en la posición de memoria apuntada por R0. En las instrucciones con post-incremento, el puntero se incrementa después de obtener el operando. Luego si en R0 tiene el valor 0x200, la instrucción MOV [R0+],R1 carga R1 en la posición de memoria apuntada por R0 (2 bytes), e incrementa (R0 = 0x202) en dos unidades. Las instrucciones con predecremento y con post-incremento actualizan el valor del puntero de acuerdo con el tamaño de operando. Es decir, si el operando es a nivel de byte se suma/resta 1. Si es a nivel de word, se suma/resta 2 (dos bytes). (Esta es una forma rudimentaria de manejo automático de tamaños. Los operadores ++ y - del lenguaje C hacen esto mismo con cualquier tipo de operandos). La última instrucción MOV R1,[R0+#4] utiliza un cierto desplazamiento 4. Estos direccionamientos (indirectos con desplazamiento) no actualizan el valor del puntero. El desplazamiento sirve únicamente para obtener la dirección del operando Números con signo y sin signo Un número negativo es aquel que sumado al mismo positivo da cero. Por ejemplo si se suma en 16 bits el número 0xFFFF y 0x0001 da como resultado 0x10000 que en 16 bits es el 0x0000. Esta aritmética se llama aritmética en como fija complemento a 2 y permite deducir el negativo de un número con la ecuación: número _ negativo = 2 número _ de_ bits número _ positivo Por ejemplo, en 16 bits, el negativo de 2 es 0xFFFE en hexadecimal o en decimal: 16 0xFFFE = 2 0x0002 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: Puede representar a una instrucción Puede representar al número Puede representar al número -2 Puede representar cualquier tipo de códificación que se el usuario quiera 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
28 2.2.4 Ejemplos básicos de codificación en ensamblador A continuación se muestran dos ejemplos de programación estructurada en C y su equivalencia en ensamblador Condición if (a == b) a = 0; next: MOV CMP JMPR MOV MOV R0,a R0,b cc_ne,next R0,#0 a,r Bucle i = 0; while (i<10) { a[i] = i; i += 1; Es necesario hacer notar que i += 1 es lo mismo que 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: 2.3 Mapa de memoria del C167 El mapa de memoria describe de forma gráfica qué hay en cada rango de direcciones: memoria RAM, ROM o Periféricos. El mapa de memoria del C167 está dividido en segmentos de Bytes, cada uno de los cuales se divide a su vez en 4 páginas de
29 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 Página 3 (RAM Interna) FFFF 16 Mb 256 Segmentos de 64 kb Página 2 Página 1 (RAM ext) C S1 64Kb S0 64Kb 16 Kb Página 0 (RAM ext) Figura 13: mapa de memoria del C167 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 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
30 SFR s RAM SFRs acceso bit a bit SFRs Acceso bit a bit GPRs FFFF FF00 FE00 FD00 FC00 STACK F600 Figura 14: Organización de la página 3 del C Los registros de propósito general GPRs Los registros de propósito general son 16 y se van de R0 hasta R15. Los 8 primeros registros (R0 a R7) se pueden acceder a nivel de byte (8 bits). En este caso reciben los nombres, por ejemplo para R0, de RL0 y RH0, y se pueden usar con instrucciones que manejan datos a nivel de byte. Solamente los 4 primeros registros (R0 a R3) pueden almacenar direcciones (punteros), para ser usados con modos de direccionamiento indirecto. Para más información sobre modos de direccionamiento ver sección de este capítulo. Se encuentran ubicados en la zona de la memoria que comprende 0xFC00 a 0xFD00, en concreto donde indique el registro CP Principales SFRs de la CPU En las siguientes subsecciones se describen los principales registros de propósito específico que tiene la CPU. En posteriores capítulos se tratarán otros SFRs que permiten gestionar los periféricos del C PC (Program Counter) Como la memoria del C167 puede llegar a ser de 16MBytes, significa que podría haber partes de un programa que estuvieran situadas en los últimos segmentos, lo que implica que el PC debería tener la capacidad de direccionar hasta 16MBytes. Por otro lado, dado que el C167 es un micro de 16 bits, sus registros son de a lo sumo 16 bits. Por lo tanto para poder direccionar hasta 16 MBytes (24 bits) es necesario usar dos registros. Precisamente por esa razón el PC del C167 se desdobla en dos registros: CSP (Code Segment Pointer): registro de 8 bits que almacena el Byte más alto de PC; lo que es lo mismo que decir que apunta al segmento en uso por PC. IP (Instruction Pointer): registro de 16 bits que almacena los 2 Bytes más bajos de PC; lo que es lo mismo que decir que apunta a cualquier dirección dentro del segmento seleccionado por CSP. 29
31 Por ejemplo: Si PC = CSP IP Esto mismo se explica de forma ilustrada en la Figura bits FF FFFF CSP indica el segmento en uso de los 255 posibles Segmentos bits S1 64Kb S0 64Kb IP indica la zona del segmento en uso de las 64k posibles Figura PSW (Processor Status Word) El registro de estado PSW, se encarga de almacenar el estado en el que ha quedado el micro después de que se ejecuta una instrucción. Es un registro de 16 bits, del que por ahora sólo se verán los flags de estado, que son los siguientes: C (flag de acarreo). Para números sin signo, se sabe si ha existido rebose al efectuar la suma, comprobando el flag de "carry" (flag C) del registro de estado de µp. (Este bit también sirve para indicar si ha existido acarreo "negativo" (borrow), que se produce cuando el substraendo es mayor que el minuendo en una operación de resta). V (flag de rebose). Cuando se opera con números con signo se debe comprobar el estado del flag de "overflow" (flag V). Este flag se pone a 1 si al sumar dos números positivos se obtiene uno negativo (bit más significativo a 1), y viceversa. Al sumar dos números de distinto signo nunca se tiene rebose. N (flag de número negativo). Indica que una operación ha generado un resultado negativo. En 8 bits significa que el bit 7 es 1 y en 16 bits significa que el bit 15 es 1. Z (flag de cero). Indica si una operación ha obtenido como resultado un cero SP (Stack pointer) 30
32 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 DPPs (Data Page Pointer Registers) De la misma forma que sucede con el PC, cuando se quiere acceder a una zona de la memoria del micro para coger un dato, es necesario usar 24 bits. Por ejemplo, si se quiere acceder a un dato en la dirección 0x0A7000, es necesario partir la dirección en dos trozos, usando un registro de apoyo de 10 bits denominado DPP0. Existen también el DPP1, DPP2 y DPP3, que se pueden usar indistintamente para poder conseguir los 24 bits necesarios. El concepto es similar al usado con el PC, pero en vez de utilizar un registro para seleccionar el segmento y otro para direccionar dentro del segmento, se usa un registro para seleccionar una página de la memoria y otro para direccionar dentro de la página. Por ello se dice que la memoria del C167 es segmentada para programas y paginada para datos. El registro DPPx se encarga de seleccionar la página dentro de la memoria seleccionada para coger el dato. A continuación se va a resolver el problema de intentar de ejecutar la instrucción siguiente, que es incorrecta: MOV R0, 0x0A7000 ; INCORRECTA 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 (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 Segmento seleccionado Número de DPP seleccionado Página seleccionada MOV DPP0, #0x29 MOV R0, 0x
33 Otra solución podría haber usado el DPP2, que se muestra a continuación: DPP2 10bits MOV DPP2, #0x29 MOV R0, 0xB CP (Context Pointer) Como se comentó con anterioridad los GPRs se pueden mapear desde la dirección 0xFC00 hasta la 0xFD00. El registro CP indica donde están mapeados los GPRs en todo momento, ya que almacena la dirección de memoria donde se encuentra R0. El resto de registros se suponen a continuación. Por defecto el valor de CP es 0xFC00, pero se puede modificar desde un programa. En el ejemplo siguiente se muestra como se modifica CP de forma que R0 se sitúa en la dirección 0xFC00, haciendo que valga a = 0x1034. Dirección Dato MOV MOV... CP,#0FC00 H a, R0 FC08 FC06 FC04 FC FF00 FC04 A050 R4 R3 R2 R1 FC R0 En el siguiente ejemplo, en cambio R0 se sitúa en la dirección 0xFC04, haciendo que valga a = 0xFC MOV MOV... CP,#0FC04 H a, R0 Dirección... FC08 FC06 FC04 FC02 FC00 Dato FF00 FC04 A R4 R3 R2 R1 R0 32
34 3 Cuestiones de comprensión Practicando con la memoria del micro 1) Escribir en ensamblador un código que escriba el contenido de la dirección de memoria 0x1F0468 en el registro R1. Usar DPP2. 2) Cuantos bits son necesarios para poder direccionar hasta 8 Mbytes de datos? Practicando con la arquitectura del micro 3) Si el microcontrolador está ejecutando la línea de código de programa 0x1F0468 Cuál es el valor del registro IP y CSP? IP = CSP = 4) Qué hay que hacer para que el registro R1 sea mapeado en la dirección de memoria FC06? 4 Ejercicios propuestos 1) Hacer un programa en ensamblador que intercambie el contenido de la dirección de memoria 0x200 por el de la 0x
35 2) Hacer un programa en ensamblador que intercambie el contenido de la dirección de memoria 0x200 por el de la 0x201. 3) Hacer un programa en ensamblador que intercambie el contenido de la dirección de memoria indicada en el registro R0, por el contenido de la dirección de memoria indicada en el registro R1. 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
36 Dirección de Celda 8 bits SITUACIÓN INICIAL DE LA MEMORIA Y REGISTROS memoria 24 bits (Hex) (Hex) r0 r rh0 rl0 rh1 rl FF FF A0 Dirección de PASO 1 memoria 24 bits (Hex) r0 r MOV R0, #2 rh0 rl0 rh1 rl Dirección de PASO 2 memoria 24 bits (Hex) r0 r MOV R0, 0x202 rh0 rl0 rh1 rl Dirección de PASO 3 memoria 24 bits (Hex) r0 r MOV R1, R0 rh0 rl0 rh1 rl Dirección de PASO 4 memoria 24 bits (Hex) r0 r MOV 0x200, R1 rh0 rl0 rh1 rl Celda 8 bits (Hex) Celda 8 bits (Hex) Celda 8 bits (Hex) Celda 8 bits (Hex) 35
37 Dirección de PASO 5 memoria 24 bits (Hex) MOV R2, #0x4433 r0 r MOV 0x202, R2 rh0 rl0 rh1 rl Dirección de PASO 6 memoria 24 bits (Hex) r0 r MOVB rl0, #4 rh0 rl0 rh1 rl Dirección de PASO 7 memoria 24 bits (Hex) r0 r MOVB rh1, [r0] rh0 rl0 rh1 rl Dirección de PASO 8 memoria 24 bits (Hex) r0 r MOV r1, [r0+] rh0 rl0 rh1 rl Dirección de PASO 9 memoria 24 bits (Hex) r0 r MOVB rl1, [r0+] rh0 rl0 rh1 rl Dirección de PASO 10 memoria 24 bits (Hex) r0 r ADD r1, r0 rh0 rl0 rh1 rl Celda 8 bits (Hex) Celda 8 bits (Hex) Celda 8 bits (Hex) Celda 8 bits (Hex) Celda 8 bits (Hex) Celda 8 bits (Hex) 36
38 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. Código de instrucción E0 31 E0 10 F0 01 F6 F Registros CSP = 00 CP = FC00 DPP0 =0 IP = 020A Instrucción mov R1,#3 mov R0,#1 mov R0, R1 mov 0x202, R0 Escribir aquí la solución final Dirección de Celda 8 bits Dirección de Celda 8 bits memoria 24 memoria 24 bits (Hex) (Hex) bits (Hex) FF A A B E B C C D E D E E F F F F FC FC00 00 FC FC01 00 FC FC02 00 FC FC03 00 FC FC04 00 FC FC05 (Hex) 37
39 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) Dirección memoria HEX Código Instrucción E0 10 E0 11 mov R0,#1 mov R1,#1 504 F6 F1 00 FA mov 0xfa00,R cmp R0,#5 50A AD 05 jmpr cc_sgt,0x514 50C 04 F1 00 FA add 0xfa00,R add R0,# D FA jmpr cc_uc,0x CC 00 nop Registros CSP = CP = FC02 IP = 38
40 Dirección de Celda 8 bits memoria 24 bits (Hex) (Hex) 39
41 5 Práctica 1: Introducción al Siemens C167 40
42 41
43 42
44 43
45 44
46 45
47 46
48 47
49 Capítulo 4 PUERTOS 1 Objetivos y conceptos a entender en este capítulo El objetivo del capítulo es entender y aprender a manejar los puertos paralelo del C167. Este objetivo se consigue cuando se sepa manejar y diferenciar el registro de direcciones (DPx) y de datos de un puerto (Px). 2 Puertos paralelo El C167 tiene 9 puertos paralelo, con un total de 111 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
50 El P8 tiene 8 líneas que son las entradas correspondientes a la unidad de "Capture Compare del micro. 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 V + 5V P2.0 P2.8 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
51 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: Si el interruptor conectado a P2.8 lo ponemos a 5V (actuando manualmente sobre él) y el resto de interruptores los ponemos a 0V y además la CPU tiene encendidos todos los LED, entonces al leer P2 obtenemos 0x0100. Si en ese momento alguien mueve el interruptor conectado a P2.9, entonces P2 vale 0x0300, lo que permite a la CPU darse cuenta de lo sucedido simplemente leyendo P2. Si la CPU decide apagar el diodo menos significativo escribirá 0x0001 en el puerto. Si a continuación se lee el puerto, sin cambiar ninguno de los interruptores, se obtendrá 0x0301 (obtenemos en valor que hemos escrito previamente). 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 */ En el ejemplo se puede apreciar que la operación AND en C es &, que la operación desplazamiento de bits a la derecha SHR en C es >> y que el complemento a 1 CPL en C es ~. El siguiente código presenta la solución en ensamblador para irse familiarizando con el mismo y así poder compararlo con el lenguaje C. 50
52 bucle: MOV DP2,#0x00ff MOV R0, P2 MOV R1, R0 AND R0, #0x0F00 SHR R0, #8 AND R1, #0xF000 SHR R1, #12 ADD R1, R0 CPL R1 MOV P2, R1 JMPR CC_UC, bucle 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
53 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
54 4 Práctica 2: entradas y salidas digitales 53
55 54
56 5 Práctica 3: ensamblar y depurar 55
57 56
58 57
59 58
60 59
61 Capítulo 5 PERIFÉRICOS 1 Objetivos y conceptos a entender en este capítulo Entender cómo se maneja de forma general cualquier periférico del micro. Entender para qué sirve, cómo se maneja y configura el Timer. Ser capaz de establecer una correspondencia entre el funcionamiento general de un periférico y el funcionamiento del Timer. 2 Periféricos del C167 Un periférico es un dispositivo que tiene todo SED para realizar tareas específicas sin consumir tiempo de CPU. En el C167 los registros de programación de los periféricos se encuentran mapeados en ciertas direcciones de memoria; es decir, se manejan igual que una posición cualquiera de memoria, haciendo operaciones de lectura/escritura, ver sección 3.7. Estas posiciones de memoria se corresponden con los registros de propósito específico (SFR) que tiene el periférico. Hay tres tipos de SFRs que permiten programar el periférico: Los registros de control o de configuración: permiten configurar el modo de funcionamiento del periférico. Indican cuando el periférico debe realizar su tarea y cuando debe parar Start/Stop. La nomenclatura que se usa es xxxcon; es decir, el nombre de los registros de control siempre acaban en CON. Registros de datos: almacenan información relevante de los resultados o parámetros necesarios para el funcionamiento del periférico. Registros de control de interrupción: este registro informa de cuándo el periférico termina la tarea que desempeña. El programa que se ejecuta en la CPU se entera de que el periférico ha terminado su trabajo haciendo polling (de un bit) de este registro, o vía interrupción, ver sección 3.7 para un resumen o el capítulo 8 para los detalles. La nomenclatura que se usa para el nombre de estos registros es xxxic (Interrupt Control register). 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
62 Controladores de comunicaciones serie: para comunicarse con el exterior en serie; por ejemplo un PC. Moduladores PWM: muy usados en electrónica de potencia para controlar motores. En este capítulo sólo se describirá el Timer. 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. Los registros de programación del mismo son: T01CON: es un registro (SFR) de configuración de 16 bits del Timer 0 y 1 como su nombre indica. La parte baja del registro, primeros 8 bits, configuran el Timer 0 y la parte alta configura el Timer 1. T0: es un registro (SFR) de datos del periférico de 16 bits que se encarga de llevar la cuenta del número de pulsos detectados. El equivalente para el Timer 1 se llama T1. T0REL: es el registro (SFR) de 16 bits que indica el valor con el que se carga T0 una vez que rebosa, sobrepasa su máxima cuenta que es 0xFFFF. Dicho de otro modo, una vez que el Timer 0 a realizado tantas cuentas como para T0 sobrepase 0xFFFF, en vez de pasar a valer T0=0, pasa a valer T0=T0REL. De esta forma aunque el Timer 0 termina su tarea, puede seguir contando. El registro equivalente para el Timer 1 es T1REL. T0IC: es el registro (SFR) de 16 bits tiene un bit (T0IR) que indica cuando el Timer 0 ha rebosado. El resto de bits sirven para configurar su control por interrupción A continuación se explica en detalle cada uno de estos registros. 3.1 Registro de control T01CON El registro de T01CON tiene 16 bits y consta de dos partes, una parte baja que configura el Timer 0 y otra parte alta que configura el Timer 1: T1R --- T1M T1I --- T0R --- T0M T0I T0M: es el bit 3 e indica el modo de funcionamiento del Timer. Como se ha comentado el Timer cuenta pulsos, pero no se ha especificado de dónde provienen. Pues bien, el bit de modo indica si los pulsos provienen del reloj interno que usa la CPU o no. En el primer caso se dice que funciona en modo timer ya que el origen es una señal periódica y de frecuencia constante que equivale a un reloj de medida de 61
63 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: (3+T 0I ) 2 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: 20MHz clk = 20MHz T0I (3+ T 0I ) 2 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. 3.2 Registros de datos Existen dos registros de datos de 16 bits cada uno: T0: es un SFR que almacena la cuenta del Timer 0 T0REL: es el valor al que se inicializa el registro T0 cuando éste rebosa 3.3 Registro de control de interrupciones T0IC Es un registro de 16 bits que tiene los siguientes bits: T0IR T0IE 5 2 ILVL 1 GLVL 0 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 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
64 3.4 Resumen de funcionamiento El Timer cuenta hacia arriba desde el valor inicial de T0, hasta 0xFFFF. En el momento que supera este último valor se dice que ha rebosado y entonces T0=T0REL. Posteriormente procede a seguir su cuenta. Es necesario caer en la cuenta que la primera cuenta va desde el valor inicial de T0 a 0xFFFF. Las siguientes cuentas van desde T0REL a 0xFFFF. El rebose queda registrado en el bit T0IR del registro T0IC. Valor de recarga después de un rebose Pre-escalado T0REL rebose o final de cuenta clk T0I T0 T0IR Almacena la cuenta 3.5 Ajuste del pre-escalado El pre-escalado permite al Timer cambiar de escala el ritmo de la cuenta, de forma que en modo timer puede llegar a contar con únicamente 16 bits durante 3.36s sin rebosar. Como T0I tiene tres bits, significa que existen 8 escalas o velocidades de cuenta. Por ejemplo, en modo timer, con un reloj de CPU de 20 MHz, se puede deducir que: Escala 0 (T0I=0): la velocidad de la cuenta es de 2.5MHz; es decir, incrementa la cuenta cada 400ns. A esta velocidad el tiempo máximo que puede pasar hasta el rebose es 26ms, que se corresponde a una cuenta que va desde 0 hasta 0xFFFF, momento en que rebosa. Por lo tanto, en este último caso el valor inicial de T0 para la primera cuenta debe ser 0 y para las siguientes T0REL = 0. Escala 1 (T0I=1): la velocidad de cuenta es la mitad de rápida, 1.25 MHz; es decir, incrementa la cuenta cada 800ns. A esta velocidad el tiempo máximo de rebose es de 52ms (el doble que el caso anterior). 20MHz Escalas 2-7: el razonamiento es análogo, aplicando la ecuación. 2+ T 0I Ejemplo de programación: LEDs a ritmo de reloj Antes de leer la explicación del ejercicio, sería conveniente intentar entenderlo por uno mismo, qué Timer usa?, a qué frecuencia cuenta?, cuanto tiempo tarda en rebosar? qué pasa con el puerto?. Es necesario suponer un sistema como el del laboratorio donde hay LEDs conectados a la parte baja del puerto P2. Por otro lado, la operación ^ es un XOR; es decir, P2 ^= 0xFFFF, es lo mismo que P2 = P2 ^0xFFFF, que es en ensamblador XOR P2, #0xFFFF, que es 63
65 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
66 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 3) Responder a las siguientes preguntas: a. Cuál es el mínimo número negativo (aproximadamente) que se puede conseguir con 16 bits? b. Cuál es el mayor número positivo (aproximadamente) que se puede conseguir con 16 bits con signo? c. Ves alguna incoherencia entre las respuestas que has dado a los apartados 3.a) y 3.b) con la solución obtenida en el ejercicio 2? Razona la respuesta. 65
67 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 ini ini S0 section code at 0H proc NEAR jmp main endp ends DAT section data at 200H DC db 13 DAT SM main ends section code at 300H proc NEAR 66
68 main SM endp ends end 67
69 6 Práctica 4: timers 68
70 69
71 70
72 Capítulo 6 ENSAMBLADOR 1 Objetivos y conceptos a entender en este capítulo Los objetivos de este capítulo son: Darse cuenta de que cada instrucción ensamblador se codifica de una forma en hexadecimal. Entender los números con signo y sin signo, complemento a 2, qué implica el overflow y el carry. Entender cómo funcionan los saltos basándose en los flags del PSW. Cómo se maneja el stack: cuando se usan funciones y con las instrucciones push y pop 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 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 El 167 maneja operandos de distintos tamaños: Byte: 8 bits Word: 16 bits Bit: 1 bit 2.1 Codificación de instrucciones La CPU sólo entiende de ceros y unos. Por ello en memoria las instrucciones que tiene que ejecutar se encuentran codificadas de forma binaria, código máquina. El programa encargado de traducir del lenguaje ensamblador a código máquina se llama ensamblador. Si se trabaja en alto nivel, el programa encargado de traducir del lenguaje C a código máquina se llama compilador. 71
73 La codificación interna de una instrucción se realiza en 16 o en 32 bits, de los cuales 8 son de código de operación o tipo de instrucción, y los demás se usan para codificar los operandos de la instrucción. En la Figura 17 y en la Figura 18 se muestran dos ejemplos de cómo se codifica una instrucción de 16 bits y una de 32 bits respectivamente. En el primer ejemplo la instrucción ocupa 16 bits porque la codificación de los operandos se puede hacer en 8 bits, 4 bits para indicar el primer GPR (que sólo hay 16) y 4 bits para indicar el segundo GPR. En el segundo ejemplo la instrucción ocupa 32 bits porque el primer operando ocupa 8 bits y el segundo 16 bits. F0 x y Código de instrucción Número de GPR Figura 17: Codificación de la instrucción MOV Rx, Ry E6 reg dato Código de instrucción Número de SFR Número de 16 bits Figura 18: Codificación de la instrucción MOV reg, #data 3 Operaciones de transferencia de datos 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. 3.1 MOV y MOVB La instrucción más utilizada en transferencia de datos es MOV. Equivale a un = en C. La sintaxis es la siguiente: MOV op1,op2 ; op1 = op2 a nivel de Word MOVB op1,op2 ; op1 = op2 a nivel de Byte 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 del capítulo 3. 72
74 3.2 MOVBZ y MOVBS Si un dato a nivel de byte se va a utilizar a continuación como dato a nivel de word, es necesario tener en cuenta si el número tiene o no tiene signo. Si el número no tiene signo, los 8 bits más significativos deberán ser igual a cero. Si el número tiene signo (complemento a dos) los 8 bits más significativos deberán estar a 0 si el bit 7 está a 0, y estarán a 1 si el bit 7 está a uno. A esta operación se le conoce como extensión de signo. Las instrucciones de que dispone el ensamblador de 167 para este propósito son: MOVBZ op1, op2 Carga el byte en parte baja, extiende (a los 16 bits) con ceros MOVBZ R0,#0xFF ; r0 <- 0x00FF MOVBZ R0,#-1 ; r0 <- 255 no respeta el signo MOVBS op1,op2 Carga el byte en la parte baja, extiende (a los 16 bits) con ceros MOVBS R0,#0xFF ; r0 <- 0xFFFF MOVBS R0,#-1 ; r0 <- -1 respeta el signo En la tabla siguiente se exponen dos ejemplos: 8 bits con signo 8 bits sin signo 16 sin extender signo 16 bits extendiendo signo 0xFF (0x00FF) -1 (0xFFFF) 0x (0x0080) -128 (0xFF80) 3.3 PUSH y POP Otras instrucciones de transferencia de datos son: PUSH POP reg reg La operación PUSH decrementa el Stack Pointer y carga el operando en la posición de memoria apuntada por el mismo. La operación POP efectúa la operación inversa. El operando debe ser uno de los GPR's o SFR's. 73
75 Figura 19: Ejemplo de uso de PUSH 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. 4 Instrucciones para realizar operaciones aritméticas Los flags se actualizan de acuerdo con el resultado de la operación: N y Z para negativo y cero, C para el acarreo y V para cambios en el bit de signo. 4.1 ADD y ADDB Sirve para sumar dos números en, guardando el resultado en el primero. La forma estándar de la instrucción ADD es: ADD op1, op2 ; op1 <- op1 + op2 MOV MOV ADD ADD R0,#0x0001 0xFA00,R0 R0,#0x0009 0xFA00,R0 Figura 20: Ejemplo de ADD. Finalmente R0 = 0x000A y 0xFA00 = 0x000B El conjunto de modos de direccionamiento queda bastante restringido con respecto a la instrucción MOV. Los modos que se pueden usar son: ADD Rx, Ry ADD Rx, [Ry] ; Ry con y = 0..3 ADD Rx, [Ry+]; Ry con y =
76 ADD Rx, #data ADD reg, #data ADD reg, mem ADD mem, reg La instrucción ADD admite la variante ADDB para operandos a nivel de byte. MOV ADDB R0,#0x0001 Rh0,#0x01 Figura 21: Ejemplo de ADDB. Finalmente R0 = 0x SUB y SUBB La instrucción SUB sirve para restar dos números. Admite las mismas variantes que la instrucción ADD. (En este caso el flag de carry C, recoge el borrow. El borrow está a 1 cuando el minuendo es menor que el substraendo). SUB op1, op2 ; op1 <- op1 op2 MOV MOV SUB ADD R0,#0x01FF 0xFA00,R0 R0,#0x0009 0xFA00,R0 Figura 22: Ejemplo de SUB. Finalmente R0 = 0x1F6 y (0xFA00) =0x3F5 A nivel de byte MOV SUBB R0,#0x01FF Rh0,#0x01 Figura 23: Ejemplo de SUB. Finalmente R0 = 0xFF 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
77 MOV NEG R0,#0x01FF R0 A nivel de byte Figura 24: Ejemplo de NEG. Finalmente R0 = 0xFE01 MOV NEGB R0,#0x01FF Rl0 Figura 25: Ejemplo de NEGB. Finalmente R0 =0x MUL y MULU Son instrucciones para realizar multiplicaciones de dos números. Admiten dos variantes: con y sin signo. MUL Rx,Ry ; [MDH, MDL] <- Rx * Ry MULU Rx,Ry 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 MOV MUL MOV MOV MULU R0,#0xFFFF R1,#0xFFFF R0,R1 R2,MDL R3,MDH R0,R1 Figura 26: Ejemplo de MUL y MULU. Finalmente R2 = 1, R3 = 0, MDL = 0x0001 y MDH = 0xFFFE 4.5 DIV y DIVU La instrucción DIV divide la parte baja del registro MD (MDL: 16 bits) entre el operando Rx (también de 16 bits). DIV Rx ;MDL <- MDL / Rx MDH <- MDL mod Rx DIVU Rx 76
78 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 MOV MOV DIV MOV MOV MOV MOV DIVU R3,#0xFFFF MDL,R3 R0,#0x0001 R0 R1,MDL R2,MDH R3,#0xFFFF MDL,R3 R0 Figura 27: Ejemplo de DIV y DIVU. Finalmente R1 = 0xFFFF, R2 = 0, MDL = 0xFFFF y MDH = 0 5 Instrucciones para realizar operaciones lógicas Las operaciones lógicas trabajan bit a bit dentro de un registro. Se suelen usar para aplicar "máscaras", una especie de filtro que permite seleccionar ciertos bits de un registro. 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: AND Rx,op ; Rx <- Rx and op 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 AND R0,#0x0001 R0,#0x0009 Figura 28: Ejemplo de AND. Finalmente R0 = 1 La instrucción AND sirve como máscara, para seleccionar únicamente ciertos bits de un registro o para borrar bits de forma selectiva. 77
79 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 Figura 30: Ejemplo de OR. Finalmente R0 =9 La instrucción OR sirve como máscara, para seleccionar únicamente ciertos bits de un registro o para poner a uno ciertos bits de forma selectiva. OR R0,#0x0002 Figura 31: Se quiere poner el bit 1 del registro R0 a 1. O lo que es lo mismo, se seleccionan el resto de bits del registro R0 La combinación de AND con OR permite igualar dos bits de dos registros. AND R0,#0xFFFD ; se pone a 0 el bit 1 de R0 MOV R2,R1 ; para no modificar R1 AND R2,#0x0002 ; se selecciona el bit 1 de R2 OR R0,R2 ; R0.1 = R2.1 = R1.1 Figura 32: Se quiere poner el bit 1 del registro R0 igual que el bit 1 del registro R XOR Realiza un OR EXCLUSIVO bit a bit de dos operandos. La instrucción XOR admite la forma: XOR Rx, op ; Rx <- Rx xor op La variante XORB opera a nivel de byte. 78
80 MOV XOR R0,#0x0101 R0,#0xFFFF ; R0 <- 0xFEFE Figura 33: Ejemplo de XOR. Finalmente R0 = 0xFEFE 5.4 CPL Realiza el complemento a 1 de un número. La instrucción CPL admite la forma: CPL Rx ; Rx <- complemento a 1 de Rx La variante CPLB opera a nivel de byte. MOV R0,#0x0101 CPL R0 ; R0 <- 0xFEFE Figura 34: Ejemplo de CPL. Finalmente R0 = 0xFEFE 6 Instrucciones para realizar desplazamientos de bits 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). La forma genérica de utilización es: SHL SHL Rx,Ry Rx,#num ; Desplaza num veces a la izda los bits de Rx donde Rx es el operando a desplazar, y Ry da el número de desplazamientos. 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 SHL R0,#0x0001 R0,#9 Figura 35: Ejemplo de SHL. Finalmente R0 = 0x
81 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. AND R0,#0xFFFD ; se pone a 0 el bit 1 de R0 MOV R2,R1 ; para no modificar R1 AND R2,#0x0001 ; se selecciona el bit 0 de R2 SHL R2,#1 ; el bit 0 pasa a ser el bit 1 OR R0,R2 ; R0.1 = R2.1 = R1.1 Figura 36: Se quiere poner el bit 1 del registro R0 igual que el bit 0 del registro R1. Las instrucciones de 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. La forma genérica de utilización es: ROL ROL Rx,Ry Rx,#num ; Rota num veces a la izda los bits de Rx donde Rx es el operando a rotar, y Ry da el número de desplazamientos. 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 ROL R0,#0x8001 R0,#9 Figura 37: Ejemplo de ROL. Finalmente R0 = 0x0280 si carry 0 y R0 =0x0380 si carry 1. ROR rota los bits hacia la derecha. El bit de Carry se pone a uno si el bit 0 que sale del desplazamiento es 1 (en la última iteración). Las posiciones libres que van quedando a la izquierda se rellenan con el valor del Carry en cada iteración. 80
82 Rx 16 bits C 7 Saltos Como se ha mencionado en las secciones anteriores, el "estado" en el que queda el procesador después de ejecutar una instrucción (de transferencia de datos, aritmética, lógica o de desplazamiento), queda almacenado en el Registro de Estado, PSW, ver capítulo 3 sección El estado viene definido por los "flags": 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) se efectúan en ensamblador comparando (instrucción CMP, ver apartado siguiente) el valor de la variable b con el valor de la variable a. Si las dos variables son iguales el procesador pone el flag de "Zero" a uno (Z=1). Si son distintas el flag permanece a cero. Los saltos condicionales, JMPR cc, permiten la ejecución del bloque if o del bloque else, de acuerdo con el resultado de la comparación. Cuando la condición es del tipo desigualdad los flags que se deben comprobar (después de la operación de comparación) son el Acarreo (Carry, C) para operandos sin signo, y el Rebose (Overflow, V) para operandos con signo (complemento a dos). Para facilitar la escritura de programas, el ensamblador de 167 dispone de las siguientes equivalencias entre alto nivel y ensamblador (ordenadas con criterio de alto nivel): Comparaciones de igualdad y desigualdad: Alto nivel Ensamblador Flag == cc_eq (equal) Z!= cc_ne (not equal) [ Z] 81
83 Comparaciones de desigualdad (estricta y no estricta): Alto nivel > < > = < = Ensamblador Sin signo Con signo cc_ugt (unsigned greater [ C] [ Z] than) cc_sgt (signed greater [ Z] [ ( N V ) ([ N] [ V] ) ] than) cc_ult (unsigned less C than) cc_slt (signed less than) ( N [ V] ) ([ N] V ) cc_uge (unsigned greater [ C] than or equal) cc_sge (signed greater than ( N V ) ([ N] [ V] ) or equal) cc_ule (unsigned less than C Z or equal) cc_sle (unsigned less than Z [ ( N [ V] ) ([ N] V ) ] or equal) 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. La operación admite las siguientes variantes: 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 sin signo en 16 bits). 82
84 next: CMP R0,R1 JMPR cc_ugt,next ;si R0 > R1... Figura 38: Ejemplo de salto condicional Una condición completa se muestra en la Figura 39. else: endif: MOV R0,a CMP R0,b JMPR cc_ne,else ; condición if.. JMPR cc_uc,endif ; condición else.. Figura 39: Ejemplo de condición en ensamblador de C167 8 Ejemplos de equivalencias de C y ensamblador 8.1 Condición if A continuación se muestra un ejemplo de condición en C y ensamblador if (a == b)...; else...; Figura 40: Condición en C 83
85 else: endif: MOV R0,a CMP R0,b JMPR cc_ne,else ; condición if.. JMPR cc_uc,endif ; condición else.. Figura 41: Condición en ensamblador 8.2 Bucle for A continuación se muestra un ejemplo de bucle for en C y ensamblador for (i=start; i <= STOP; i++){ a = STOP - i; Figura 42: bucle for en C MOV R0,#START ; r0 (i) MOV R1,#STOP SUB R1,R0 ; r1 (STOP-START) JMPR cc_uc,test for: MOV a,r1 SUB R1,#1 ADD R0,#1 ; i += 1 test: CMP R0,#STOP JMPR cc_slt,for ; i <= STOP next: Figura 43: bucle for en ensamblador 8.3 Bucle while A continuación se muestra un ejemplo de bucle while en C y ensamblador 84
86 i = 0; while (i<10) { a[i] = i; i += 1; Figura 44: bucle while en C 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: Figura 45: bucle while en ensamblador 9 Instrucciones a nivel de bit Las instrucciones que operan con bits de forma individual sólo se pueden usar contra zonas de memoria preparadas ello, ver Figura 46 SFR s RAM SFRs acceso bit a bit SFRs Acceso bit a bit GPRs STACK FFFF FF00 FE00 FD00 FC00 F600 85
87 Figura 46: Zonas de memoria que permiten usar instrucciones a nivel de bit Las zonas de RAM interna accesibles bit a bit son: De la 00 FD00 a la 00 FDFF zona bit a bit. De la 00 FF00 a la 00 FFFF SFR's accesibles bit a 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 next: JB C,next ;si C = 1 salta next... Figura 47: Ejemplo de manejo de JB. 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. La instrucción BCLR sirve para poner un bit a 0. Para mover un bit de un lugar a otro se usa la instrucción BMOV bit1, bit2 ; bit1 = bit2 Las siguientes instrucciones realizan operaciones lógicas con un sólo bit 86
88 BAND bit1, bit2 ; bit1 <- bit1 and bit2 BOR bit1, bit2 ; bit1 <- bit1 or bit2 BXOR bit1, bit2 ; bit1 <- bit1 xor bit2 En la Figura 48 se muestra algunos ejemplos de uso de estas instrucciones. ;activa bit 1 del P2 BSET P2.1 ; BCLR C ; Carry = 0 BMOV V,C ; V=C Figura 48: Ejemplos de instrucciones "a nivel de bit" 87
89 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. Sección de datos D100 en dirección 0x200 $nonsegmented maxdata equ 0x10 ; constante D100 section data at 200H j dsw 10 ; int j[10]; h dw 1 ; int h =1; D100 ends Procedimiento ej1 Sección de código ej en dirección 0x300 ej section code at 300H ej1 proc NEAR MOV R0,#0 MOV R1,#1 MOV R2,#j otro: CMP R0,#maxdata JMPR cc_sge,next MOV [R2],R0 ADD R0,R1 ADD R2,#2 JMPR cc_uc,otro next: NOP ej1 endp ej ends end Figura 49: Directivas de ensamblador Final de programa section y ends: delimita las secciones del programa. El programa se puede dividir en dos tipos de secciones, las secciones de datos y de código: o Las secciones de datos sirven para reservar memoria para variables. La nomenclatura es: nombre section data at xxxh... nombre ends donde nombre es una etiqueta que indica el nombre de la sección y xxxh es la dirección de memoria donde se guarda la primera variable de la sección. Las demas se guardan a continuación. Por ejemplo, en la Figura 49, la variable j se 88
90 guarda en la dirección 0x200 y la variable h se guarda en la dirección 0x20A, ya que j ocupa 10 posiciones. o Las secciones de código sirve para reservar memoria para instrucciones de programa. La nomenclatura es: nombre section code at xxxh... nombre ends 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: nombre proc NEAR... nombre endp donde nombre es un etiqueta que indica el nombre del procedimiento y endp indica el final del procedimiento. Se puede acceder a un procedimiento de la misma manera que se hace con una línea de código etiquetada; por ejemplo jmpr nombre. 89
91 11 Cuestiones de comprensión 1) Cómo se codifica el número 16 en hexadecimal usando 8 bits? 2) Qué vale R0 después de ejecutar las instrucciones siguientes? Movbz R0, #0xFC ;R0 = Movbs R0, #0xFC ;R0 = 3) Indicar el valor de PSW después de ejecutar las siguientes instrucciones (los bits que no se usan se pueden poner a cero): mov R0, #0 ;PSW = add R0, #0x6500 ;PSW = add R0, #0x6500 ;PSW = add R0, #0x6500 ;PSW = 4) Qué representa la información que almacena R0? (responder a,b ó c) mov R0, #300 mov R1, [R0] a) Un dato que podría variar b) Una dirección de memoria c) Una constante genérica 5) Qué representa var? (responder a,b ó c) var equ 0x300 MOV R0,#var a) Un dato que podría variar b) Una dirección de memoria c) Una constante genérica 90
92 6) Cuál es el valor de R0? MOV NEGB R0,#0x01FF RL0 7) Cual es el valor de R2 y R3? R2 = R3 = MOV MOV MUL MOV MOV R0,#0x6FFF R1,#0xFFFF R0,R1 R2,MDL R3,MDH 8) Cual es el valor de R1 y R2? R1 = R2 = MOV MOV DIV MOV MOV MDL,#0xFFFF R0,#0x0001 R0 R1,MDL R2,MDH 9) Qué instrucciones de ensamblador hay que programar para hacer que el bit 2 (tercer bit) del registro R0 sea igual que el de R1 sin llegar a modificar ni el registro R1 ni el resto de bits del registro R0? (usar máscaras) 91
93 10) Indicar el valor de SP, el de R0 y el valor de la memoria en hexadecimal? La respuesta debe ir en la secuencia de tablas que se encuentran después del ejemplo y que cada una se corresponde con la ejecución de cada una de las instrucciones del ejemplo MOV R0, #0x CALLR MOV R1, #0x PUSH R0 402 MOV R0, #1 406 POP R0 408 RET a) Justo después de ejecutar la instrucción en la dirección de memoria 300 R0 = SP = Dirección mem 00 FC04 00 FC03 00 FC02 00 FC01 00 FC00 00 FBFF 00 FBFE 00 FBFD 00 FBFC 00 FBFB 00 FBFA Dato mem (HEX) b) Justo después de ejecutar la instrucción en la dirección de memoria 304 R0 = SP = 92
94 Dirección mem 00 FC04 00 FC03 00 FC02 00 FC01 00 FC00 00 FBFF 00 FBFE 00 FBFD 00 FBFC 00 FBFB 00 FBFA Dato mem (HEX) c) Justo después de ejecutar la instrucción en la dirección de memoria 400 R0 = SP = Dirección mem 00 FC04 00 FC03 00 FC02 00 FC01 00 FC00 00 FBFF 00 FBFE 00 FBFD 00 FBFC 00 FBFB 00 FBFA Dato mem (HEX) d) Justo después de ejecutar la instrucción en la dirección de memoria 402 R0 = SP = 93
95 Dirección mem 00 FC04 00 FC03 00 FC02 00 FC01 00 FC00 00 FBFF 00 FBFE 00 FBFD 00 FBFC 00 FBFB 00 FBFA Dato mem (HEX) e) Justo después de ejecutar la instrucción en la dirección de memoria 406 R0 = SP = Dirección mem 00 FC04 00 FC03 00 FC02 00 FC01 00 FC00 00 FBFF 00 FBFE 00 FBFD 00 FBFC 00 FBFB 00 FBFA Dato mem (HEX) f) Justo después de ejecutar la instrucción en la dirección de memoria 408 R0 = SP = 94
96 Dirección mem 00 FC04 00 FC03 00 FC02 00 FC01 00 FC00 00 FBFF 00 FBFE 00 FBFD 00 FBFC 00 FBFB 00 FBFA Dato mem (HEX) g) Justo después de ejecutar la instrucción en la dirección de memoria 306 R0 = SP = Dirección mem 00 FC04 00 FC03 00 FC02 00 FC01 00 FC00 00 FBFF 00 FBFE 00 FBFD 00 FBFC 00 FBFB 00 FBFA Dato mem (HEX) 95
97 12 Ejemplo de discusión: medida de ancho de pulso, sin/con filtrado de rebotes 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. T 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 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
98 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
99 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; La única pega de esta solución es que no tiene en cuenta los rebotes. A continuación se muestra una solución que permite conectar a una determinada línea una señal con rebotes. El tratamiento de rebotes es el más simple que se puede hacer, que consiste en que una vez que se detecta un flanco, se espera un tiempo T (tiempo máximo que duran los rebotes) para comprobar si el flanco es de subida o de bajada. Por lo demás es igual que el ejemplo anterior. 98
100 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
101 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
102 13.2 Encendido apagado de LED (10 min) 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 P
103 13.3 Volcado de memoria (20 min) Realizar una subrutina en ensamblador que haga una copia de la zona de memoria comprendida entre la dirección 200H y la 20FH hasta la zona de memoria comprendida entre la dirección 210H y 21FH. $nonsegmented S0 ini ini S0 section code at 0H proc NEAR jmp main endp ends DAT section data at 200H origen dsb 16 dest dsb 16 DAT SM main ends section code at 300H proc NEAR. copia: fin: main SM ret endp ends end 102
104 13.4 Cuenta de pulsos (30 min) 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 P
105 13.5 Calculadora (40 min) 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 ini ini S0 section code at 0H proc NEAR jmp main endp ends DAT section data at 200H result dsb 1 dato dsb 1 DAT SM main ends section code at 300H proc NEAR 104
106 main SM endp ends end 105
107 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
108 SM section code at 300H main proc NEAR MOV DP2, #0xFF bucle: MOV R0, P2 AND R0, #0xFF00 SHR R0, #8 MOV R4, #0 MOVB RL4, [R0] ; dato de la dirección de memoria MOV R1, #0 ; contador de for MOV R2, #1 ; máscara MOV R3, #0 ; contador de número de bits a 0 ini_for: CMP R1, #8 JMPR cc_ugt, fin_for MOV R0, R4 AND R0, R2 JMPR cc_ne, next_for MOV R5, R1 ; almacena la posición del bit a 0 ADD R3, #1 next_for: ADD R1, #1 SHL R2, #1 JMPR cc_uc, ini_for fin_for: CMP R3, #0 JMPR cc_ne, if_1 MOV P2, #0xEF ; enciende LED P2.4 JMPR cc_uc, bucle if_1: CMP R3, #1 JMPR cc_ne, if_n 107
109 14.2 Medida de ancho de pulso con rebotes (20 min) 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
110 SM section code at 300H main proc NEAR MOV DP2, #0xFF MOV R0, P2 ; almacena el estado actual AND R0, #0x100 MOV R2, #0 ; cuenta MOV R3, #0 ; mseg MOV R4, #-2500 ; cuenta inicial del timer MOV R5, #0 ; preescalado MOV R6, #-1 ; filtrado bucle: MOV R1, R0 ; anterior = actual call retraso MOV R0, P2 ; actual = P2 AND R0, #0x100 CMP R0, R1 JMPR cc_eq, filtrado MOV R6, #MAX JMPR cc_uc, flanco filtrado: CMP R6, #0 JMPR cc_sle, flanco SUB R6, #1 flanco: CMP R6, #0 JMPR cc_ne, seguir MOV R6, #-1 CMP R0, #0 JMPR cc_eq, cero MOV R2, #1 JMPR cc_uc, seguir cero: MOV R2, #0 MOV MDL, R3 MOV R3, #1000 DIV R3 MOV R3, MDL CPL R3 MOV P2, R3 MOV R3, #0 seguir: CMP R2, #0 JMPR cc_eq, bucle ADD R3, #1 JMPR cc_uc, bucle main endp SM ends end 109
111 14.3 Medida de ancho de pulso (20 min) 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
112 SM section code at 300H main proc NEAR MOV DP2, #0xFF MOV DP7, #0 MOV R0, P7 ; almacena el estado actual AND R0, #0x10 MOV R2, #0 ; cuenta MOV R3, #0 ; contador MOV R4, #-2500 ; cuenta inicial del timer MOV R5, #0 ; preescalado bucle: MOV R1, R0 ; anterior = actual call retraso MOV R0, P7 ; actual = P2 AND R0, #0x10 CMP R0, R1 JMPR cc_eq, seguir CMP R0, #0 JMPR cc_eq, cero MOV R2, #1 JMPR cc_uc, seguir cero: MOV R2, #0 CPL R3 MOV P2, R3 MOV R3, #0 seguir: CMP R2, #0 JMPR cc_eq, bucle ADD R3, #1 JMPR cc_uc, bucle main endp SM ends end 111
113 15 Práctica 5: ejercicios en ensamblador, control de un servo 112
114 113
115 114
116 Capítulo 7 DRIVERS. EL CONVERTIDOR AD 1 Objetivos y conceptos a entender en este capítulo Entender qué es el convertidor AD y su funcionamiento. Entender la similitud en el manejo del conversor AD y el Timer. Se aconseja esquematizar ambos manejos, para una mayor claridad. Entender el concepto de driver 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. Hay dos tipos de funciones dentro de un driver: Funciones de inicialización: sólo se llaman una vez y sirven para inicializar el driver y el periférico. Funciones de manejo: se llaman tantas veces como se necesiten en la ejecución de un programa. 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. Los drivers tienen las siguientes ventajas: El desarrollador no tiene que conocerse los detalles de implementación del periférico. Si el periférico cambia su especificación o versión, sólo hace falta cambiar el driver, pero no las aplicaciones que usan el driver. Se puede reutilizar en cualquier aplicación sin modificarlo. 2.1 Ejemplos de driver Es necesario hacer notar que para un mismo periférico se pueden hacer varios drivers, que permiten independizar su manejo de los detalles de implementación del mismo. Por ello, los ejemplos que se muestran a continuación son una posible versión de drivers. Además los drivers suelen estar en ficheros distintos del principal, ya que se usan como librerías, gracias a su gran posibilidad de reutilización. 115
117 2.1.1 Driver sencillo del puerto P2 Aunque el manejo del puerto P2 es tan simple que no se necesita realizar un driver que ayude a su manejo, se presenta como primer ejemplo ya que ilustra el concepto como tal. El driver consta de dos funciones de manejo y una de inicialización. /* inicialización del puerto dir: dirección de los pines del puerto*/ void ini_p2 (int dir){ DP2 = dir; /* función que devuelve los datos del puerto P2*/ int get_p2(){ return(p2); /* función modifica los datos del puerto P2*/ int set_p2(int datos){ P2 = datos; 2.2 Driver del Timer 0 Un posible driver para el Timer 0 es la función retarso que se explicó en???, para un micro con un reloj de 20MHz. /** función que espera miliseg milisegundos. El máximo tiempo de espera es 26ms*/ void retraso(int miliseg) { T01CON = 0; T0REL = -miliseg*2500; T0 = T0REL; T0IC = T0IC & 0x7f; // borra flag de rebose T01CON = T01CON 0x40; // arranca timer while (!(T0IC & 0x80)); // bucle de espera de rebose T01CON = T01CON & 0xbf; // para el timer 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. 2.3 Driver del convertidor AD Para más información de qué es un conversor AD y cómo se maneja, leer la sección 3 de este capítulo. El driver que se muestra a continuación configura al convertidor AD en modo
118 /* función que devuelve la conversión AD de un canal canal: canal objeto de la conversión (4 bits) devuelve: resultado en 10 bits */ int convad(int canal){ ADCON = canal & 0xF; ADCIR = 0; ADST=1; while(!adcir); return(addat&0x03ff); Este driver consta de una función de manejo y ninguna de inicialización. Un ejemplo de manejo de este driver se muestra en la sección 3.4 de este capítulo. 3 El convertidor analógico digital (AD) En esta sección se va a ver como funciona el convertidor analógico digital, que es un periférico más del micro. Para más información sobre periféricos ver capítulo 5. 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. El esquema del convertidor se muestra en la Figura 50. El mundo exterior, analógico, se conecta a cada pin del puerto P5. Estos pines llegan a un multiplexor que elige uno de los mismos para realizar la conversión. La elección se realiza programando los bits ADCH. Posteriormente se encuentra el convertidor AD que realiza la conversión a digital del canal seleccionado. El resultado de esta conversión lo deja en ADDAT (10 bits), e indica a través del bit ADCIR que ha terminado de realizar la conversión. De esta manera un programa podría aprovechar la misma para alguna labor. 117
119 ADCH ANALOGICO ADCIR ADDAT Convertidor AD MUX P5.0 (canal 0) 5V 0V P5.15 (canal 15) 5V 0V Figura 50:Esquema del convertidor AD La tarea del periférico finaliza cuando termina la conversión Los registros de programación del mismo son: ADCON: es un registro (SFR) de configuración de 8 bits. ADDAT: es un registro (SFR) de datos del periférico de 16 bits que se encarga de almacenar el resultado de la conversión. ADCIC: es el registro (SFR) de 16 bits que indica cuando el convertidor termina su tarea, y sirve para configurar su control por interrupción o polling. A continuación se explica en detalle cada uno de estos registros. 3.1 Registro de control ADCON El registro de control consta de 8 bits, como se muestra a continuación: ADBSY ADST --- ADM ADCH 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
120 ADST bit 7: es el encargado de controlar el arranque y paro del convertidor. Si se pone comienza la conversión. ADBSY bit 8: bit que indica que el conversor AD está ocupado haciendo una conversión. 3.2 Registro de datos ADDAT El registro de datos ADDAT tiene 16 bits y almacena el resultado en los 10 bits más bajos y en el canal convertido (al que corresponde el resultado) en los 4 bits más altos CANAL --- RESULTADO 3.3 Registro de control de interrupciones ADCIC Sólo merece la pena resaltar que el registro ADCIC tiene un bit ADCIR, que indica que el conversor AD ha terminado su tarea; es decir, que tiene un resultado en ADDAT. Es equivalente al T0IR del Timer Ejemplo de programación A continuación se describe cómo se programa el convertidor AD. Es importante comparar este programa con el correspondiente del Timer en el capítulo 5 sección 3.6. El objetivo del programa es hacer una conversión en el pin P5.0 del micro y mostrar la parte alta del resultado en los 8 LEDs conectados a la parte baja del puerto P2. #include <reg167.h> main() { ADCON = 0x00; // ADM=0, ADST=0, ADCH=0 ADCIR = 0; // flag final = 0 ADCON = 0x80; // Start ADST=1 DP2 = 0x00FF; P2 = 0xFFFF; 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
121 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
122 Capítulo 8 PROGRAMACIÓN EN C PARA MICROS 1 Objetivos y conceptos a entender en este capítulo El objetivo de este capítulo es resumir los conceptos de programación en C que se usan en programación de microprocesadores. Se supone que el lector ha recibido nociones básicas de programación en C antes de leer este capítulo. Es muy importante prestar atención y esquematizar la relación que existe entre el C y el ensamblador según se va leyendo el capítulo. 2 Tipos de datos para el C Números enteros El C167 admite 3 tipos de enteros que se diferencian en el tamaño que tienen. Si se habla de tipos de datos con signo: int (16 bits): desde a char (8 bits): desde -128 a 127 long (32 bits): desde a En caso de que no tengan signo: unsigned int (16 bits): desde 0 a unsigned char (8 bits): desde 0 a 255 unsigned long (32 bits): desde 0 a 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; La promoción de números enteros es directa cuando se trata de pasar de un número de menor tamaño a uno de mayor tamaño: 121
123 unsigned int i; unsigned char c = 0xff; i = c; Figura 51: Promoción de 8 bits sin signo a 16 bits. El resultado en i = 0x00FF int i; char c = 0xff; i = c; Figura 52: Promoción de 8 bits con signo a 16 bits. El resultado en i=0xffff La promoción de números enteros en ensamblador tiene instrucciones especiales, como lo son MOVBZ (para la Figura 51) y MOVBS (para la Figura 52). Los operadores más usados con número enteros son: Operador unitarios : cambia de signo al operando. Así en: i = -c; el operador - toma el valor de la variable c y le cambia el signo. Por tanto si c vale 17, al finalizar la ejecución de la instrucción, la variable i contendrá el valor -17. Operador unitario ++ y --: dado que una de las aplicaciones principales de los números enteros en los programas es la realización de contadores, que usualmente se incrementan o decrementan de uno en uno, los diseñadores de C vieron aconsejable definir unos operadores para este tipo de operaciones. El operador incremento se representa añadiendo a la variable que se desee incrementar dos símbolos +. La sintaxis es por tanto: NombreVariable++. Así por ejemplo la línea: Contador++; sumaría uno a la variable Contador. El operador decremento es idéntico al de incremento, sin mas que sustituir los + por -. Siguiendo el ejemplo anterior: Contador- -; Le restaría uno a la variable Contador. Operadores binarios: +, -, *: suma, resta y multiplicación respectivamente. Operador binario / (op1/op2): da como resultado la parte entera de dividir op1 entre op2; es decir, el cociente. Así por ejemplo 3/2 da como resultado 1, lo que puede parecer malo, pero aún hay cosas peores como 1/2, que da como resultado 0, lo cual tiene un gran peligro en expresiones como: resultado = (1/2)*3; Operador binario % (op1%op2): devuelve el resto de la división entre op1 y op2. El operador resto se representa mediante el símbolo %. Así por ejemplo 4%2 da como resultado 0 y 1%2 da como resultado 1. Una utilidad de este operador es la de 122
124 averiguar si un determinado número es múltiplo de otro; por ejemplo el número es múltiplo de 33 porque %33 da cero. NOTA: en C se pueden mezclar asignaciones y operaciones entre dos operandos en un sólo operador, situando el = entre los dos operandos de una operación; por ejemplo: op1 += op2; // es equivalente a op1 = op1 + op2; op1 %= op2; // es equivalente a op1 = op1 % op2; 2.2 Números reales Al igual que con los enteros, el C167 admite 3 tipos de número reales dependiendo de la precisión que se requiera: float (16 bits) double (32 bits) long double (64 bits) 2.3 Variables lógicas En C no existen las variables lógicas. Cualquier variable entera se puede usar como variable lógica, de forma que si su valor es 0 entonces equivale a un FALSE y si es distinto de 0 entonces equivale a un TRUE. Por ejemplo: 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 Los operadores usados con variables lógicas son los siguientes: &&: es un AND lógico. Ojo no trabaja bit a bit (no confundir con &) c = a && b Si a = 1 y b = 14 c es TRUE Ya que a y b son distintos de cero Si a = 0 y b = 32 c es FALSE Ya que a es igual a cero Tabla 1: Ejemplo de AND lógico 123
125 : es un OR lógico. Ojo no trabaja bit a bit (no confundir con ) c = a b Si a = 0 y b = 0 c es FALSE Ya que a y b son cero Si a = 32 y b = 0 c es TRUE Ya que a es distinto de cero Tabla 2: Ejemplo de OR lógico! : es un NOT lógico. No confundir con ~. c =! b Si b = 14 c es FALSE Ya que b es distinto de cero Si b = 0 c es TRUE Ya que b es igual a cero Tabla 3: Ejemplo de NOT lógico Las posibles condiciones lógicas son: ==: comparación de igualdad. Ojo no es lo mismo que = (asignación). No debe confundirse el operador de asignación = con el operador relacional de igualdad == que no modifica el valor de las variables. Es decir, es totalmente distinto escribir i=7, que introduce el valor 7 en la variable i independientemente del valor que tuviese ésta, que escribir i==7, que compara el valor de la variable i con la constante 7.!= : comparación de desigualdad >, >= : comparación mayor que y mayor igual que respectivamente. <, <= : comparación menor que y menor o igual que respectivamente. c = (a > b) && (a!= d) Si a = 0, d = 0 y b = 1 c es FALSE Ya que a y d son iguales Si a = 10, d = 0 y b = 1 c es TRUE Ya que a y d son distintos y a mayor que b 3 Operadores bit a bit Cuando se programa un micro es muy habitual realizar operaciones a nivel de bit. En C existen los siguientes operandos que trabajan a nivel de bit: & (op1&op2): realiza un AND bit a bit entre el operando op1 y op2. El equivalente en ensamblador es AND op1,op2. (op1 op2): realiza un OR bit a bit entre el operando op1 y op2. El equivalente en ensamblador es OR op1,op2. 124
126 >> (op1>>op2): desplaza op2 bits de op1 hacia la derecha. El equivalente en ensamblador es SHR op1,op2. << (op1<<op2): desplaza op2 bits de op1 hacia la izquierda. El equivalente en ensamblador es SHL op1,op2. ^ (op1^op2): realiza un XOR bit a bit entre el operando op1 y op2. El equivalente en ensamblador es XOR op1,op2. ~ (~op1): realiza un complemento a uno del operando op1. El equivalente en ensamblador es CPL op1. a = P2; a &= 0x000F; // Cuanto vale a? a = P2; a = 0x000F; // Cuanto vale a? a = P2; a >>= 4; // Cuanto vale a? a = P2; a <<= 4; // Cuanto vale a? 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
127 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. if(a && b) { if(a & b) { 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. for(sentencia inicial ; condición ; sentencia final ){... 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. La sentencia inicial se ejecuta antes de entrar en el bucle y la condición siempre se comprueba antes de cada iteración. Por lo tanto, puede ocurrir que nunca se llegue a entrar en el bucle si desde el principio no se cumple la condición. Por ejemplo for(i=0; i<-3; 126
128 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. Si no se conoce a priori el número de iteraciones que deseamos realizar en un bucle se debe utilizar el bucle while. Este tipo de bucles repiten una serie de instrucciones mientras una condición es cierta. La sintaxis es: 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); En este caso se ejecutarán las instrucciones y después se evaluará la condición. Si es falsa se continúa con la instrucción que sigue al bucle y si es cierta se vuelve a repetir el bucle, se evalúa la condición y así sucesivamente. 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. vec[0]=54.23; /*Primer elemento del vector*/ vec=4.5; /* ERROR */ 127
129 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
130 MAX equ 5 D100 section data at 200H Iniciado dw 1,5,8,4,3 SinIni dsw 5 D100 ends ej section code at 300H ej1 proc NEAR MOV R1,#SinIni MOV R0,#Iniciado MOV R3,#0 ;contador ; Inicializa el vector ini: CMP R3,#MAX JMPR cc_uge, fin MOV [R1+],[R0+] ADD R3, #1 JMPR cc_uc, ini fin: NOP ej1 endp ej ends Figura 58: Ejemplo de inicialización en ensamblador de un vector en la propia definición (Iniciado) o por código (SinIni) 7 Punteros Los punteros son un tipo de variable un poco especial, ya que en lugar de almacenar valores (como las variables de tipo int o de tipo double) los punteros almacenan direcciones de memoria. Utilizando variables normales 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
131 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. Dirección de Variable en C Valor 8 bits memoria 24 bits (Hex) i c pti ptc A B 00 Figura 59 En ensamblador se puede trabajar con punteros usando el direccionamiento indirecto. De forma que los cuatro primeros registros de propósito general pueden almacenar direcciones de memoria. 7.1 El operador & El operador & se utiliza para obtener la dirección de memoria de cualquier variable del programa. Si nuestro programa tiene una variable entera que se llama i, entonces podemos obtener la dirección de memoria que el compilador ha preparado para almacenar su valor escribiendo &i. 130
132 int i; /* variable entera */ int *pti; /* puntero a entero */ i=78; /* inicializo i */ pti=&i; /* pti apunta a i */ Al aplicar el operador & a la variable i no se obtiene el valor entero almacenado en ella sino la dirección de memoria en donde se encuentra dicho valor. La dirección de la variable i (&i) se almacena en un puntero a entero, porque es una dirección de memoria donde se halla un entero. El compilador dará un aviso si se intenta realizar una asignación en la que no corresponden los tipos, por ejemplo al asignar &i a un puntero tipo char *, ya que &i devuelve un puntero a int. Este tipo de comprobaciones del compilador evita muchos errores de programación. Por tanto, hay que estar muy atento a los mensajes del compilador. Asignaciones entre dos punteros también son válidas siempre que los dos punteros sean del mismo tipo. En ensamblador se usa el inmediato # para acceder a la dirección de memoria de un variable, ver Figura El operador * El operador unitario *, llamado operador de indirección, permite acceder al valor por medio del puntero. int i; /* variable entera */ int *pti; /* puntero a entero */ pti=&i; /* pti apunta a i */ *pti=78; /* equivale a hacer i=78; */ La asignación *pti=78 introduce el valor entero 78 en la posición de memoria apuntada por el puntero pti. Como previamente se ha almacenado en pti la dirección de memoria de la variable i, la asignación equivale a dar el valor 78 directamente a la variable i. Es decir, tras la asignación pti=&i disponemos de dos maneras de manipular los valores enteros almacenados en la variable i: directamente mediante i,o indirectamente mediante el puntero pti. 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
133 main() { D100 section data at 200H i1 dsw 1 i2 dsw 1 D100 ends ej section code at 300H ej1 proc NEAR MOV R0,#5 ; R0 registro auxiliar MOV i1,r0 MOV R1,#i1 ; R1 puntero a i1 MOV R0,[R1] ; equivale a * en C MOV i2,r0 ej1 endp ej ends int i1,i2; int *pi; i1 = 5; pi = &i1; i2 = *pi; // i2? Figura 60: Equivalencia entre C y ensamblador en el manejo de punteros 7.3 Operaciones con punteros Una vez entendido lo que es un puntero veamos las operaciones que pueden realizarse con estas variables tan especiales. En primer lugar ya se ha visto que admiten la operación de asignación para hacer que apunten a un lugar coherente con su tipo. Pero también admite operaciones de suma y diferencia que deben entenderse como cambios en la dirección a la que apunta el puntero. Es decir, si tenemos un puntero a entero que almacena una determinada dirección de memoria y le sumamos una cantidad, entonces cambiaría la dirección de memoria y el puntero quedaría apuntando a otro sitio. Es importante diferenciar claramente las dos formas de manipular el puntero: *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. El operador ++ se utiliza mucho en programación de micros y permite hacer que el puntero pase a apuntar al dato que ocupa la siguiente posición de memoria. La aplicación fundamental 132
134 de esta operación es recorrer posiciones de memoria consecutivas. Además de valer para fisgonear la memoria del microcontrolador, esto se utiliza para recorrer vectores (cuyos datos siempre se almacenan en posiciones de memoria consecutivas). 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. La equivalencia con ensamblador es la siguiente: MOV R1, [R2+] i = *pti++ MOV R1, [-R2] i = *--pti En la Figura 61 y Figura 62 se muestra un ejemplo de esta equivalencia. 133
135 MAX equ 5 D100 section data at 200H VWord dsw 5 VByte dsb 5 D100 ends ej section code at 300H ej1 proc NEAR MOV R0,#VWord; R0 puntero MOV R1,#VByte; R1 puntero MOV R2,#0 ; R2 es la suma MOV R3,#0 ; R3 es i MOV R4,#0 ; R4 var temporal suma: CMP R3,#MAX JMPR cc_uge, fin MOVB RL4,[R1+] ADD R2, R4 ADD R2, [R0+] ADD R3, #1 JMPR cc_uc, suma fin: NOP ej1 endp ej ends Figura 61: Ejemplo en ensamblador que suma dos vectores de 5 datos #define MAX 5 main() { int VWord[MAX]; char VByte[MAX]; int *pti; char *ptc; int i; suma; suma = 0; pti = VWord; ptc = VByte; for (i=0; i<max; i++){ suma += *pti++; suma += *ptc++; Figura 62: Ejemplo en C de dos vectores que suma 2 vectores de 5 datos 134
136 Si se quiere incrementar un puntero en más de una unidad, se puede usar un desplazamiento usando el operador +: int vi[10]; /* vector de enteros */ int *pti, i; /* puntero a entero */ pti=vi; /* pti apunta a vi */ i = *(pti+5); /*coge el dato 5 enteros adelante */ El equivalente en ensamblador es: MOV R1, [R0+#10] MOVB RL1, [R0+#5] i = *(pti+5); // en caso de int c = *(ptc+5); // en caso de char 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
137 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. Un ejemplo de definición de función es el código propiamente dicho: int power(int base, int n) { int i, p; p = 1; for (i=1; i<=n; i++) p = p * base; return p; Figura 63: Ejemplo de definición de función en C En la Figura 63 se puede observar como una función tiene unos parámetros que entran en la función y se usan como variables locales; es decir, si se modifica su valor en la función el programa principal que llamó a la función no ve afectados sus valores. Además la función puede devolver un valor en la instrucción return. En la Figura 64 se muestra el prototipo o declaración de la función anterior. El prototipo indica al compilador el número y tipo de parámetros de entrada a una función y el tipo de variable de salida de la misma. int power(int base, int n); Figura 64: Ejemplo de declaración de función en C Un ejemplo de manejo de la definición y declaración de función se muestra en la Figura
138 int power(int base, int n); main () { int i, a, b; a = 2; b =10; i = power(b,a); int power(int base, int n) { int i, p; p = 1; for (i=1; i<=n; i++) p = p * base; return p; Figura 65: Ejemplo de uso de función En el ejemplo se puede apreciar que el valor de las variables de la función main a y b no son alterados por la llamada a la función power. Esto se debe a que en C SIEMPRE se pasan los parámetros por valor a las funciones; es decir, se hace una copia cada variable y se pasa la propia copia a la función. Es necesario declarar la función power antes de la definición de main, para que cuando el compilador empiece a compilar (que lo hace de forma secuencial y de arriba hacia abajo) el código sepa como se ha definido power y compruebe la correcta sintaxis de la misma. En caso contrario el compilador indicará que la función no se ha declarado. 8.1 Variables globales y locales Las variables locales son las que sólo se pueden acceder dentro de una misma función, que se componen por las variables pasadas por parámetro y las definidas en la propia función. La vida de una variable local se termina en el momento en el que el programa sale de la función. Por otro lado, las variables globales son las que se encuentra fuera de toda función y se pueden usar desde cualquier lugar del código. La vida de una variable global se corresponde con el tiempo de ejecución del programa. El buen estilo de programación recomienda usar lo menos posible las variables globales ya que se pueden modificar desde cualquier lugar del código y por tanto son muy complicadas de depurar. 8.2 Paso de parámetros por "referencia" Como se ha comentado con anterioridad en C siempre se pasan a las funciones los parámetros por valor. Por otro lado, dado que una función sólo puede devolver un valor, se restringen mucho las posibilidades de hacer funciones complejas que puedan devolver más de un valor. Para poder salvar este escollo, existe un truco que permite pasar los parámetros por "referencia" usando punteros. No es un paso por referencia estrictamente hablando, sino que 137
139 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. void swap(int x, int y) { int temp; temp = x; x = y; y = temp; main () { int a=5, b=6; swap(a,b); void swap(int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; main () { int a=5, b=6; swap(&a,&b); Figura 66: Ejemplo de paso por valor y paso por "referencia" en C 8.3 Paso de vectores como parámetros Los vectores se pasan SIEMPRE por "referencia"; es decir, cualquier función puede modificar el valor de cada componente de un vector. Esto es así ya que para pasar un vector por valor hay que usar su nombre, que no es más que una constante que indica la dirección de memoria del primer elemento del vector, ver ejemplo en Figura
140 #define MAX 5 int max(int* v, int tam); main() { int vector[]={1,5,8,4,3; int maximo; maximo = max(vector, MAX); /* función que calcula el máximo de un vector */ int max(int* v, int tam){ int maximo=0; i=0; for (i=0; i<tam; i++){ if (v[i] > maximo) maximo = vector[i]; return (maximo); Figura 67: Ejemplo de paso por parámetro de un vector Por otro lado, si lo que se quiere es devolver como resultado de una función un vector es imprescindible hacerlo a través de un parámetro de la misma o no a través de la instrucción return. En la Figura 68 se muestra el típico manejo erróneo de un vector, ya que es una variable local que desaparece al terminar la función y por lo tanto aunque el programa principal recupere la dirección de memoria devuelta, el vector se encuentra vacío. Por otro lado, en la Figura 69, Figura 70 y Figura 71 se muestran buenas prácticas de programación, en donde se reserva la memoria para el vector en el lugar que va a ser utilizado. #define MAX 50 main() { int *pti; pti = get_vector_discreto(1, 4); /* función que devuelve un vector con los valores enteros comprendidos entre min y max*/ int* get_vector_discreto(int min, int max){ int i, vector[max]; for (i=min; i<max; i++) vector[i] = i; return (vector); Figura 68: Manejo erróneo de vuelta de un vector por una función 139
141 #define MAX 50 main() { int vector[max]; get_vector_discreto(1, 4, vector); /* función que devuelve un vector con los valores enteros comprendidos entre min y max*/ void get_vector_discreto(int min, int max, int* v){ int i; for (i=min; i<max; i++) *(v+i) = i; #define MAX 50 main() { Figura 69: Manejo tipo puntero int vector[max]; get_vector_discreto(1, 4, vector); /* función que devuelve un vector con los valores enteros comprendidos entre min y max*/ void get_vector_discreto(int min, int max, int* v){ int i; for (i=min; i<max; i++) v[i] = i; #define MAX 50 main() { Figura 70: Manejo tipo vector int vector[max]; get_vector_discreto(1, 4, vector); /* función que devuelve un vector con los valores enteros comprendidos entre min y max*/ void get_vector_discreto(int min, int max, int* v){ int i; for (i=min; i<max; i++) *v++ = i; Figura 71: Uso de post-incremento 140
142 9 Cuestiones de comprensión 1) Dada la siguiente definición de variable char c; Señala con un círculo las opciones correctas: a) c es un número de 8 bits siempre b) c es un carácter siempre c) c es un número sin signo a veces 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) CASO B) CASO C) CASO D) int a; b = 0; a = -1; if (a) b = 1; b = 0; a = 0; if (a = 0) b = 1; b = 0; a = 0; if (a = 1) b = 1; b = 0; a = 0; if (a) b = 1; 3) Indica el valor de la variable entera resultado en cada caso a) resultado = 0x44 && 0x1 = b) resultado = 0x44 0x1 = c) resultado = ~(0x44 >> 2) = 141
143 4) Traducir a C el siguiente código en ensamblador. Cada línea de código ensamblador que se debe traducir tiene un comentario a su derecha indicando el número de línea que le representa. Se debe indicar en cada línea de C la correspondencia con las líneas ensamblador, escribiendo a la derecha de las líneas en C los números de las líneas ensamblador que correspondan. No se puede codificar de forma que una línea en ensamblador tenga asociadas varias en C; es decir, cada línea de C tiene al menos una línea de ensamblador asociada. MAX equ -1 ;1 D100 section data at 200H i dsb 1 ;2 j dsw 1 ;3 k dw 1 ;4 D100 ends ej section code at 300H ej1 proc NEAR MOV R0,#MAX ;5 MOVB i, RL0 ;6 MOVBZ R2, i ;7 MOV j, R2 ;8 MOV R1, k ;9 CMP R1, j ;10 JMPR cc_ugt, fin ;11 MOV R0,#0 ;12 MOV j, R0 ;13 fin: JMPR cc_uc, fin ;14 ej1 endp ej ends 142
144 5) Para cada uno de los programas siguientes: Está correctamente programado? En caso afirmativo Qué saca por pantalla? En caso negativo, razona la respuesta. Debajo de cada programa se ha dibujado un recuadro que representa la pantalla donde se deben escribir los resultados. double *pdactualiza(); main () { double *pd=null; pd=pdactualiza(); printf("2 %f\n",pd[0]); return 0; double *pdactualiza() { double md[3]={1.2,-2.3,-1.4; double *pd=md; printf("1 %f\n",pd[0]); return pd; void Efecto2000(int *pa); main(){ int i=74; Efecto2000(&i); printf("%d",i); void Efecto2000(int *pa){ *pa=*pa+1900; 6) Hacer un programa en C que sume los elementos de un vector de 5 elementos a) Usando un bucle for b) Usando un bucle while. 143
145 7) Supuesto un sistema digital como el del laboratorio, explica con tus palabras qué hacen los programas siguientes: La línea 6 de este programa coge el contenido de lo que hay en la dirección de memoria indicada por el valor de dir y lo guarda en dato. void main(void) { int dir, dato; DP2 = 0xFF; while (1) { dir = (P2 & 0xFF00)>>8; dato = *((char*)dir); P2 = dato; void main(void) { unsigned char dato, mask, i, n; DP2 = 0xFF; while (1) { dato = (unsigned char)((p2 & 0xFF00) >> 8); mask=0x01; n=0; for (i = 0; i<8; i++){ if (!(dato & mask)) n++; mask<<=1; if (n==0) P2 = 0xFF; else P2 = 0; 144
146 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: Lee los interruptores conectados a la parte alta del puerto P2. Extrae el valor de los dos interruptores más significativos, y los utiliza para definir los bits más altos del factor de preescalado del timer 0. El bit menos significativo del preescalado del timer queda a 0. El valor inicial de la cuenta y el valor del rebose del timer quedan igualmente a 0. Los 6 interruptores restantes definen dos operandos, de tres bits cada uno de ellos. El programa realiza la suma y el producto de los dos operandos. En los diodos conectados a la parte baja del puerto P2 aparecen ambos resultados, de forma alterna, con sucesivos reboses de timer (con el primer rebose aparece el resultado de la suma, con el segundo rebose aparece el resultado del producto, y así sucesivamente). Es necesario tener en cuenta los rebotes de los interruptores. #include <reg167.h> void main(void) { 145
147 10.2 Acceso a memoria (40 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 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. Es necesario tener en cuenta los rebotes de los interruptores. #include <reg167.h> void main(void) { 146
148 147
149 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
150 149
151 #include <reg167.h> #define MAX 3 void retraso(int mseg); void main(void){ int contador=-1, ciclo=0, dato[3], result; unsigned int anterior,actual, temp; DP2 = 0x00FF; P2 = ~0; anterior = P2 & 0x0100; while(1) { retraso(1); temp = P2; actual = temp & 0x0100; if (anterior!= actual) contador = MAX; else if (contador > 0) contador--; else ; // 1 ms anterior = actual; if (contador == 0) { contador = -1; if (actual) dato[ciclo++] = temp >> 12; //recogida de dato if(ciclo == 3) { ciclo = 0; // Operaciones if(dato[2] & 0x0001) result = dato[0] + dato[1]; else if(dato[2] & 0x0002) result = dato[0] - dato[1]; else if(dato[2] & 0x0004) result = dato[0] * dato[1]; else ; P2 = ~result; 150
152 11.2 El coche fantástico (20 min) Se quiere hacer un sistema de luces rojas similar al del coche fantástico, que consiste de 8 luces que se encienden de una en una de forma consecutiva, de izquierda a derecha y de derecha a izquierda. Para ello se dispone de un microcontrolador C167, de 8 LEDs y de 8 interruptores. Diseñar un sistema que encienda una luz roja que se vaya moviendo a la derecha y que cuando llegue al final rebote y vuelva hacia la izquierda, y así sucesivamente. Dado que el sistema está diseñado para venderse y la velocidad de movimiento de la luz depende del gusto del consumidor, se tiene pensado que se pueda regular dicha velocidad con 8 interruptores. 151
153 #include <reg167.h> void retraso(int prees, int cuenta); void main(void) { int sentido = 0, dato = 0x01; int prees, cuenta, temp; DP2 = 0x00ff; P2 = ~dato; while(1) { temp = P2; prees = temp & 0x0700; prees >>= 8; cuenta = temp & 0xF800; retraso(prees, cuenta); if (!sentido){ P2 = ~(dato <<= 1); if (dato & 0x80) sentido = 1; else { P2 = ~(dato >>= 1); if (dato & 0x01) sentido = 0; 152
154 11.3 El autobus (30min) Se quiere controlar el número de personas que hay en un autobús. Para ello se ha equipado al autobús con dos sensores de contacto (interruptores con rebotes), uno en la puerta de entrada delantera y otro en la puerta de salida trasera, que detectan el paso de pasajeros. El sensor de la puerta delantera se ha conectado al pin P2.8 de un micro C167 y cada vez que pasa una persona produce una transición de 0 a 1 en el pin. El sensor de la puerta trasera se ha conectado al pin P2.9 y cada vez que pasa una persona produce una transición de 1 a 0 en el pin. Por otro lado, se han instalado 8 LEDs, conectados a la parte baja del puerto P2, en la cabina del conductor para indicarle el número de personas que viajan en el autobús. Si el número de viajeros es mayor que 50, aunque una persona intente subir no se le dejará entrar ya que el autobús se considera lleno. 153
155 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 void retraso(int mseg); int contador[16],inicializado=0; unsigned int anterior[16], estado[16]; int filtrap2(int nlinea) { int actual, temp, mascara, i; // función de filtrado genérica if (!inicializado) for (i=0; i<16; i++) { DP2 = 0x00FF; P2 = ~0; contador[i] = 0; anterior[i] = 0; estado[i] = 0; inicializado = 1; temp = P2; mascara = 1; mascara <<= nlinea; actual = temp & mascara; if (anterior[nlinea]!= actual) contador[nlinea] = MAX; else if (contador[nlinea] > 0) contador[nlinea]--; else ; anterior[nlinea] = actual; if (contador[nlinea] == 0) { contador[nlinea] = -1; if (actual) estado[nlinea] = 1; else estado[nlinea] = 0; return estado[nlinea]; void main(void){ int personas=0; int anterior8,anterior9,actual8,actual9; anterior8 = 0; anterior9 = 0; while(1) { retraso(1); actual8 = filtrap2(8); if (anterior8!= actual8 && actual8) if (personas < 50) personas++; anterior8 = actual8; actual9 = filtrap2(9); if (anterior9!= actual9 &&!actual9) if (personas > 0) personas--; anterior9 = actual9; P2 = ~personas; // flanco de subida // flanco de bajada 154
156 12 Práctica 6: ejercicios en lenguaje C 155
157 156
158 157
159 Capítulo 9 INTERRUPCIONES 1 Objetivos y conceptos a entender en este capítulo En este capítulo se debe aprender qué es una interrupción y el proceso que se sigue para atenderla. Se recomienda además, que se hagan esquemas que permitan analizar el paralelismo que hay entre el tratamiento de periféricos por polling y por interrupción. 2 Nociones básicas de interrupciones Cuando la CPU pide realizar una tarea a un periférico, tiene dos posibilidades para determinar cuando termina el periférico: Estar continuamente preguntando al periférico si ha terminado, de forma que cuando el periférico responda que sí, ejecute el código que corresponda aprovechando la tarea realizada por el periférico. Este método se llama polling y es el que se ha estado usando hasta el momento. Decir al periférico que avise a la CPU de que ha terminado su tarea. En ese momento la CPU deja de hacer lo que estaba haciendo (sólo en caso de que lo que esté haciendo sea de menor prioridad que la petición del periférico) y se pone a ejecutar el código que corresponda aprovechando la tarea realizada por el periférico. Este método se llama interrupción y es el que se va a explicar en este capítulo. 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 El proceso detallado que se sigue funcionando en modo interrupción es el siguiente: 1. La CPU inicializa el periférico, para funcionar en modo interrupción y se indica el grado de prioridad de esa interrupción (prioridad mayor que 0). 2. La CPU se pone a hacer su trabajo normal que tiene la menor prioridad (prioridad 0). 3. Cuando el periférico termina su tarea, realiza una petición de interrupción a la CPU a través de una línea del bus de control denominada IRQ (interrupt request). Para saber más sobre los buses de la CPU ver el capítulo 2 sección La CPU termina de ejecutar la instrucción que estaba ejecutando. 5. La CPU guarda el PSW y el PC (CSP+IP) en el Stack de forma temporal. Esto es necesario para saber lo que estaba ejecutando en el momento de la interrupción (donde apuntaba PC) y para saber cuál es el estado de la CPU en ese instante (PSW). 6. La CPU lanza un ciclo de reconocimiento de interrupción, para determinar los periféricos que han solicitado la misma. De entre todos los posibles (56 en total), elige la interrupción que tenga mayor prioridad (16 niveles distintos). 7. La CPU lanza la función de tratamiento de la interrupción, que no es otra cosa que el código que se tiene que ejecutar para aprovechar la tarea realizada por el periférico 158
160 en cuestión. La dirección de comienzo (denominada vector) de cada función que atiende interrupciones (56 posibles, una por cada tipo de interrupción) se encuentra en la tabla de vectores de interrupción. Por lo tanto la CPU inicializa PC al valor del vector correspondiente. Durante el tratamiento en PSW se pone el nivel de la interrupción. 8. Una vez terminada la ejecución de la función de tratamiento de interrupción, la CPU recupera el PC del Stack para seguir ejecutando lo que estaba haciendo antes de la interrupción y el PSW del Stack para dejar la CPU en el estado que estaba antes de la interrupción, tanto los flags, como el nivel de interrupción. 3 Recursos utilizados en una interrupción Hay 56 posibles fuentes de interrupción, por ejemplo el Timer 0, el Timer 1, el convertidor AD,... Cada fuente de interrupción tiene: Un vector de interrupción, que indica la dirección de memoria de la función que hay que ejecutar cuando se produzca la interrupción. Un registro de control de interrupción, que tiene un nombre xxic. Por ejemplo el del Timer 0 es T0IC, el del Timer 1 es T1IC y el del convertidor AD es ADCIC. El registro de control de interrupciones consta de: ILVL: 4 bits que indican el nivel de la interrupción (0 a 15. Nivel 15 nivel más alto) (Interrupt Level) xxie: bit de habilitación de la interrupción (Interrupt Enable). Este bit indica al periférico que tiene que funcionar en modo interrupción. Si vale 0 el periférico funciona en modo polling. Por ejemplo para el Timer 0 el bit se llama T0IE, para el Timer 1 se llama T1IE. xxir: bit de petición de interrupción (Interrupt Request). Si se pone a 1 el periférico está solicitando una interrupción. Es el bit que se ha usado hasta ahora para saber si el periférico había terminado su tarea. Por ejemplo para el Timer 0 el bit se llama T0IR, para el Timer 1 se llama T1IR. xxic xxir xxie ILVL GLVL Por otro lado, el registro PSW juega un papel muy importante en las interrupciones: ILVL IEN - Z V C N 159
161 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. Es importante darse cuenta de la sintaxis de la función de tratamiento de interrupción. No se la pueden pasar parámetros, ya que NO se puede invocar explicitamente. Es la propia CPU la que hace que se ejecute cuando el Timer rebosa. El número 0x20 que aparece en la definición de la función indica el vector de interrupción que usa para ser invocada por la CPU. La CPU sabe que es la función que atiende al Timer 0 NO por llamarse timer0 sino por corresponder al vector de interrupción 0x20 que es el que atiende las peticiones del Timer 0. Como no se pueden pasar parámetros a la función de interrupción, la comunicación de la misma con el programa principal se debe hacer usando variables globales. Dado que la función de tratamiento de la interrupción se ejecuta a mayor prioridad que el resto del código, es necesario que sea corta; es decir, está absolutamente prohibido poner algún bucle en la misma. En caso contrario podría suceder que el resto de código no tuviera casi tiempo de CPU para ejecutarse. 160
162 #include <reg167.h> #include <stdio.h> #define PERIOD int ticks, sec, min, hour; void main(void) { ticks = sec = min = hour = 0; T01CON = 0x00; T0REL = PERIOD; /* set reload value */ T0 = PERIOD; T0IC = 0x44; /* set T0IE and ILVL = 1 */ IEN = 1; /* set global interrupt enable flag */ T0R = 1; /* start timer 0 */ DP2 = 0x00FF; while(1){ if (P2 & 0x8000) P2 = ~min; else P2 = ~hour; void timer0(void) interrupt 0x20 { 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
163 5 Práctica 7: interrupciones en C 162
164 163
165 164
166 Capítulo 10 SISTEMAS DIGITALES COMPLEJOS 1 Objetivos y conceptos a entender en este capítulo Este capítulo únicamente pretende dar metodologías que faciliten resolver sistemas digitales complejos. No son los únicos métodos que se pueden usar, pero sí es cierto que siguen un estilo de programación y de resolución de problemas bueno; es decir, buscan una buena mantenibilidad, flexibilidad y escalabilidad del sistema. 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. En cambio en un sistema muestreado la solución se basa en el uso de un reloj que lleva la cuenta del periodo de muestreo, y sólo se mira el estado de la línea una vez en cada intervalo. La ventaja es que el chequeo de la línea no consume casi CPU y se hace a intervalos 165
167 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 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); void timer0(void) interrupt 0x20 { 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; La clave para un buen funcionamiento consiste en estimar correctamente el periodo de muestreo, ya que si es demasiado pequeño, la CPU no queda libre, y si es demasiado grande puede haber problemas de resolución y sensibilidad. La regla óptima a seguir para calcular el periodo de muestreo es la siguiente: 1. Determinar el periodo de muestreo de todas las tareas que tenga que realizar la CPU. 2. Escoger el máximo común divisor de todos ellos. 166
168 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
169 #include <reg167.h> #include <stdio.h> #define PERIOD 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 void timer0(void) interrupt 0x20 { mseg_evento1++; mseg_evento2++; En caso de trabajar con polling el código sería: 168
170 #include <reg167.h> #include <stdio.h> #define PERIOD 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
171 #include <reg167.h> #include <stdio.h> #define PERIOD 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 void timer0(void) interrupt 0x20 { mseg_evento1++; 4 Programación basada en estados Cuando se quiere resolver un problema complejo siempre existe una frase que se debe aplicar "divide y vencerás". Pues bien, existen muchas maneras de llevar esto a cabo, la modularización en funciones, el diseño top-down (diseñar primero a alto nivel con grandes bloques y luego ir diseñando dentro de cada bloque los detalles), etc. En esta sección se va a explicar cómo abordar un problema complejo usando estados. Es un diseño top-down con gran modularización. Consiste en modelar cada uno de los posibles procesos de un sistema en lo que se llama una máquina de estados. En esta sección no se pretende exponer una teoría rigorosa sobre las máquinas de estados, ya que no es objetivo del libro, sino de dar ideas de cómo se pueden resolver problemas software usando un método que se le puede ocurrir a cualquiera. A cualquier desarrollador se le puede ocurrir que los pasos a seguir para resolver un problema complejo según un diseño top-down y modular son: 1. Determinar qué procesos independientes existen en el sistema que se quiere desarrollar 170
172 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 Desde el punto de vista software este método quedaría: 1. Definir una función por cada máquina de estados y definir una variable global que permita conocer el estado actual en que se encuentra la máquina. Por ejemplo: int estado_actual=0; void maquina(){ 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(); 3. Dentro de cada función de estado, se deben programar las condiciones de paso de un estado a otro en función de variables. 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
173 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
Arquitectura y mapa de memoria del 80C167. Cesáreo Fernández Martínez Álvaro Sánchez Miralles
Arquitectura y mapa de memoria del 80C167 Cesáreo Fernández Martínez Álvaro Sánchez Miralles Introducción Microcontrolador (Embedded Processor) de 16 bits ALU y Registros de 16 bits Diseñado para tareas
UNIDADES FUNCIONALES DEL ORDENADOR TEMA 3
UNIDADES FUNCIONALES DEL ORDENADOR TEMA 3 INTRODUCCIÓN El elemento hardware de un sistema básico de proceso de datos se puede estructurar en tres partes claramente diferenciadas en cuanto a sus funciones:
Unidad I. 1.1 Sistemas numéricos (Binario, Octal, Decimal, Hexadecimal)
Unidad I Sistemas numéricos 1.1 Sistemas numéricos (Binario, Octal, Decimal, Hexadecimal) Los computadores manipulan y almacenan los datos usando interruptores electrónicos que están ENCENDIDOS o APAGADOS.
Lo que definimos como CPU (Central Process Unit) o Unidad Central de Proceso, está estructurado por tres unidades operativamente diferentes:
Facultad de Ciencias Exactas y Naturales y Agrimensura Departamento de Ingeniería Cátedra : Proyecto Final Apuntes : Microprocesadores Tema 6-1 : Esquema de un µp. La CPU Lo que definimos como CPU (Central
Entorno de Ejecución del Procesador Intel Pentium
Arquitectura de Ordenadores Arquitectura del Procesador Intel Pentium Abelardo Pardo [email protected] Universidad Carlos III de Madrid Departamento de Ingeniería Telemática Entorno de Ejecución del Procesador
UNIDAD 2 Configuración y operación de un sistema de cómputo Representación de datos Conceptos El concepto de bit (abreviatura de binary digit) es fundamental para el almacenamiento de datos Puede representarse
Arquitectura Von Neumann
Arquitectura Von Neumann Arquitectura Von Neumann Establecida en 1945 por Von Neumann Modelo básico de arquitectura utilizado en la mayoría de los computadores Su idea es la de conectar permanentemente
UNIDADES DE ALMACENAMIENTO DE DATOS
1.2 MATÉMATICAS DE REDES 1.2.1 REPRESENTACIÓN BINARIA DE DATOS Los computadores manipulan y almacenan los datos usando interruptores electrónicos que están ENCENDIDOS o APAGADOS. Los computadores sólo
6-REGISTROS DEL 8086 Y DEL 80286
ESTRUCTURA DE COMPUTADORES I (Capítulo 6: Los registros del microprocesador 8086) 1/7 6-REGISTROS DEL 8086 Y DEL 80286 6.1 INTRODUCCIÓN: Dentro del procesador existen unos contenedores especiales de 16
SISTEMAS DE NUMERACIÓN. Sistema decimal
SISTEMAS DE NUMERACIÓN Sistema decimal Desde antiguo el Hombre ha ideado sistemas para numerar objetos, algunos sistemas primitivos han llegado hasta nuestros días, tal es el caso de los "números romanos",
by Tim Tran: https://picasaweb.google.com/lh/photo/sdo00o8wa-czfov3nd0eoa?full-exif=true
by Tim Tran: https://picasaweb.google.com/lh/photo/sdo00o8wa-czfov3nd0eoa?full-exif=true I. FUNDAMENTOS 3. Representación de la información Introducción a la Informática Curso de Acceso a la Universidad
Capítulo 0. Introducción.
Capítulo 0. Introducción. Bueno, por fin está aquí el esperado (espero!!) Capítulo Cero del Tutorial de Assembler. En él estableceremos algunos conceptos que nos serán de utilidad a lo largo del Tutorial.
Capítulo 1: Sistemas de representación numérica Introducción. Dpto. de ATC, Universidad de Sevilla - Página 1 de 8
Dpto. de ATC, Universidad de Sevilla - Página de Capítulo : INTRODUCCIÓN SISTEMAS DE REPRESENTACIÓN NUMÉRICA Introducción Bases de numeración Sistema decimal Sistema binario Sistema hexadecimal REPRESENTACIÓN
Clase 20: Arquitectura Von Neuman
http://computacion.cs.cinvestav.mx/~efranco @efranco_escom [email protected] Estructuras de datos (Prof. Edgardo A. Franco) 1 Contenido Arquitectura de una computadora Elementos básicos de una
Tema 2. Diseño del repertorio de instrucciones
Soluciones a los problemas impares Tema 2. Diseño del repertorio de instrucciones Arquitectura de Computadores Curso 2009-2010 Tema 2: Hoja: 2 / 16 Tema 2: Hoja: 3 / 16 Base teórica Al diseñar un computador,
Tema 1 Introducción. Arquitectura básica y Sistemas Operativos. Fundamentos de Informática
Tema 1 Introducción. Arquitectura básica y Sistemas Operativos Fundamentos de Informática Índice Descripción de un ordenador Concepto básico de Sistema Operativo Codificación de la información 2 1 Descripción
Los sistemas de numeración se clasifican en: posicionales y no posicionales.
SISTEMAS NUMERICOS Un sistema numérico es un conjunto de números que se relacionan para expresar la relación existente entre la cantidad y la unidad. Debido a que un número es un símbolo, podemos encontrar
Unidad de trabajo 2: INFORMÁTICA BÁSICA (primera parte)
Unidad de trabajo 2: INFORMÁTICA BÁSICA (primera parte) Unidad de trabajo 2: INFORMÁTICA BÁSICA... 1 1. Representación interna de datos.... 1 1.2. Sistemas de numeración.... 2 1.3. Aritmética binaria...
DESCRIPCION DEL SITEMA MASTER.
DESCRIPCION DEL SITEMA MASTER. ESTRUCTURA. El sistema MASTER (Sistema Modular para Control Adaptativo en Tiempo Real) se ha implementado en base a un computador compatible PC-AT, dotado de una tarjeta
TEMA 4. Unidades Funcionales del Computador
TEMA 4 Unidades Funcionales del Computador Álvarez, S., Bravo, S., Departamento de Informática y automática Universidad de Salamanca Introducción El elemento físico, electrónico o hardware de un sistema
Curso Completo de Electrónica Digital
CURSO Curso Completo de Electrónica Digital Este curso de larga duración tiene la intención de introducir a los lectores más jovenes o con poca experiencia a la Electrónica Digital, base para otras ramas
Comparadores UNIDAD V
Comparadores UNIDAD V Tecsup Virtu@l Automatización Lógica Programable Índice MÓDULO 2: PROGRAMACIÓN AVANZADA Unidad V: COMPARADORES 1. Comparadores... 1 1.1 Introducción... 1 1.2 Objetivos... 1 1.3 Contenido...
INFORMÁTICA. Matemáticas aplicadas a la Informática
ACCESO A CICLO SUPERIOR INFORMÁTICA Matemáticas aplicadas a la Informática http://trasteandoencontre.km6.net/ 1 Acceso a grado Superior. Informática 1. Unidades de medida en informática Como sabemos, el
Lógica Binaria. Arquitectura de Ordenadores. Codificación de la Información. Abelardo Pardo [email protected]. Universidad Carlos III de Madrid
Arquitectura de Ordenadores Codificación de la Información Abelardo Pardo [email protected] Universidad Carlos III de Madrid Departamento de Ingeniería Telemática Lógica Binaria COD-1 Internamente el ordenador
Memoria La memoria es la parte del ordenador en la que se guardan o almacenan los programas (las instrucciones y los datos).
Memoria La memoria es la parte del ordenador en la que se guardan o almacenan los programas (las instrucciones y los datos). Memoria Típica. Los datos almacenados en memoria tienen que pasar, en un momento
Tema 7: Esquema del Funcionamiento de una Computadora. Escuela Politécnica Superior Ingeniería Informática Universidad Autónoma de Madrid
Tema 7: Esquema del Funcionamiento de una Computadora Ingeniería Informática Universidad Autónoma de Madrid Esquema del Funcionamiento de una Computadora O B J E T I V O S Adquirir los conceptos básicos
TEMA 1: SISTEMAS INFORMÁTICOS. Parte 2: representación de la información
TEMA 1: SISTEMAS INFORMÁTICOS Parte 2: representación de la información Qué vamos a ver? Cómo se representa y almacena la información en un ordenador Cómo podemos relacionar la información que entendemos
ACTIVIDADES TEMA 1. EL LENGUAJE DE LOS ORDENADORES. 4º E.S.O- SOLUCIONES.
1.- a) Explica qué es un bit de información. Qué es el lenguaje binario? Bit es la abreviatura de Binary digit. (Dígito binario). Un bit es un dígito del lenguaje binario que es el lenguaje universal usado
Tema I. Sistemas Numéricos y Códigos Binarios
Tema I. Sistemas Numéricos y Códigos Binarios Números binarios. Aritmética binaria. Números en complemento-2. Códigos binarios (BCD, alfanuméricos, etc) Números binarios El bit. Representación de datos
Matemática de redes Representación binaria de datos Bits y bytes
Matemática de redes Representación binaria de datos Los computadores manipulan y almacenan los datos usando interruptores electrónicos que están ENCENDIDOS o APAGADOS. Los computadores sólo pueden entender
DESARROLLO DE HABILIDADES DEL PENSAMIENTO LÓGICO
I. SISTEMAS NUMÉRICOS DESARROLLO DE HABILIDADES DEL PENSAMIENTO LÓGICO LIC. LEYDY ROXANA ZEPEDA RUIZ SEPTIEMBRE DICIEMBRE 2011 Ocosingo, Chis. 1.1Sistemas numéricos. Los números son los mismos en todos
Una computadora de cualquier forma que se vea tiene dos tipos de componentes: El Hardware y el Software.
ARQUITECTURA DE LAS COMPUTADORAS QUE ES UNA COMPUTADORA (UN ORDENADOR)? Existen numerosas definiciones de una computadora, entre ellas las siguientes: 1) Una computadora es un dispositivo capaz de realizar
INTRODUCCIÓN. Definiciones ORDENADOR (RAE 1992): En esta asignatura computador y ordenador tiene el mismo significado
INTRODUCCIÓN UPCO ICAI Departamento de Electrónica y Automática 1 Definiciones ORDENADOR (RAE 1992): Máquina electrónica dotada de una memoria de gran capacidad y de métodos de tratamiento de la información,
ANEXO 2: REPRESENTACION DE LA INFORMACION EN LOS COMPUTADORES
ANEXO 2: REPRESENTACION DE LA INFORMACION EN LOS COMPUTADORES SISTEMA DE NUMERACIÓN BASE 2 El sistema de numeración binario es el conjunto de elementos {0, 1} con las operaciones aritméticas (suma, resta,
1. Informática e información. 2. Sistemas de numeración. 3. Sistema binario, operaciones aritméticas en binario, 4. Sistemas octal y hexadecimal. 5.
Representación de la información Contenidos 1. Informática e información. 2. Sistemas de numeración. 3. Sistema binario, operaciones aritméticas en binario, 4. Sistemas octal y hexadecimal. 5. Conversiones
Práctica 1. Introducción al SIEMENS 80C167
Práctica 1 Introducción al SIEMENS 80C167 1 Objetivos Toma de contacto con los recursos de la tarjeta PHY80C167 basada en el microcontrolador SIEMENS 80C167 Familiarizarse con la herramienta de desarrollo
La Unidad Procesadora.
La Unidad Procesadora. En un sistema digital complejo, la capa de hardware de la máquina es el nivel más bajo del modelo de capas de un sistema microcomputarizado. La unidad procesadora es una parte del
INTRODUCCION A LA PROGRAMACION DE PLC
INTRODUCCION A LA PROGRAMACION DE PLC Esta guía se utilizará para estudiar la estructura general de programación de um PLC Instrucciones y Programas Una instrucción u orden de trabajo consta de dos partes
Clase 02: Representación de datos
Arquitectura de Computadores y laboratorio Clase 02: Representación de datos Departamento de Ingeniería de Sistemas Universidad de Antioquia 2015-2 Contenido 1 2 Representación de la Información Y sistemas
Capítulo 1 Introducción a la Computación
Capítulo 1 Introducción a la Computación 1 MEMORIA PRINCIPAL (RAM) DISPOSITIVOS DE ENTRADA (Teclado, Ratón, etc) C P U DISPOSITIVOS DE SALIDA (Monitor, Impresora, etc.) ALMACENAMIENTO (Memoria Secundaria:
Naturaleza binaria. Conversión decimal a binario
Naturaleza binaria En los circuitos digitales sólo hay 2 voltajes. Esto significa que al utilizar 2 estados lógicos se puede asociar cada uno con un nivel de tensión, así se puede codificar cualquier número,
Sistemas de Numeración Operaciones - Códigos
Sistemas de Numeración Operaciones - Códigos Tema 2 1. Sistema decimal 2. Sistema binario 3. Sistema hexadecimal 4. Sistema octal 5. Conversión decimal binario 6. Aritmética binaria 7. Complemento a la
Sistemas de numeración
Sistemas de numeración Un sistema de numeración es un conjunto de símbolos y reglas que permiten representar datos numéricos. Los sistemas de numeración actuales son sistemas posicionales, que se caracterizan
Tema 2. La Información y su representación
Tema 2. La Información y su representación 2.1 Introducción. Un ordenador es una máquina que procesa información. La ejecución de un programa implica la realización de unos tratamientos, según especifica
La informática es el conjunto de técnicas y conocimientos necesarios para el tratamiento automático de la información mediante el ordenador.
Qué es la informática? La informática es el conjunto de técnicas y conocimientos necesarios para el tratamiento automático de la información mediante el ordenador. Se llama sistema informático al sistema
INSTITUTO POLITÉCNICO NACIONAL ESCUELA SUPERIOR DE INGENIERIA MECANICA Y ELECTRICA INGENIERIA EN COMUNICACIONES Y ELECTRÓNICA ACADEMIA DE COMPUTACIÓN
I. P. N. ESIME Unidad Culhuacan INSTITUTO POLITÉCNICO NACIONAL ESCUELA SUPERIOR DE INGENIERIA MECANICA Y ELECTRICA UNIDAD CULHUACAN INGENIERIA EN COMUNICACIONES Y ELECTRÓNICA ACADEMIA DE COMPUTACIÓN LABORATORIO
1. SISTEMAS DIGITALES
1. SISTEMAS DIGITALES DOCENTE: ING. LUIS FELIPE CASTELLANOS CASTELLANOS CORREO ELECTRÓNICO: [email protected] [email protected] PAGINA WEB MAESTROFELIPE.JIMDO.COM 1.1. INTRODUCCIÓN
Actividad 4: Comunicación entre PLC s vía Ethernet
Actividad 4: Comunicación entre PLC s vía Ethernet 1.- Listado de materiales: PC con Tarjeta de red 3com o similar. 2 PLC Omrom CJ1M CPU11 ETN Estos autómatas llevan integrada la tarjeta de comunicaciones
SISTEMAS DE NUMERACIÓN. Sistema de numeración decimal: 5 10 2 2 10 1 8 10 0 =528 8 10 3 2 10 2 4 10 1 5 10 0 9 10 1 7 10 2 =8245,97
SISTEMAS DE NUMERACIÓN Un sistema de numeración es un conjunto de símbolos y reglas que permiten representar datos numéricos. La norma principal en un sistema de numeración posicional es que un mismo símbolo
Introducción a la Programación 11 O. Humberto Cervantes Maceda
Introducción a la Programación 11 O Humberto Cervantes Maceda Recordando En la sesión anterior vimos que la información almacenada en la memoria, y por lo tanto aquella que procesa la unidad central de
AUTOMATIZACION. Reconocer la arquitectura y características de un PLC Diferenciar los tipos de entradas y salidas
AUTOMATIZACION GUIA DE TRABAJO 2 DOCENTE: VICTOR HUGO BERNAL UNIDAD No. 3 OBJETIVO GENERAL Realizar una introducción a los controladores lógicos programables OBJETIVOS ESPECIFICOS: Reconocer la arquitectura
Transformación de binario a decimal. Transformación de decimal a binario. ELECTRÓNICA DIGITAL
ELECTRÓNICA DIGITAL La electrónica es la rama de la ciencia que se ocupa del estudio de los circuitos y de sus componentes, que permiten modificar la corriente eléctrica amplificándola, atenuándola, rectificándola
CURSO 2010-2011 TECNOLOGÍA TECNOLOGÍA 4º ESO TEMA 5: Lógica binaria. Tecnología 4º ESO Tema 5: Lógica binaria Página 1
Tecnología 4º ESO Tema 5: Lógica binaria Página 1 4º ESO TEMA 5: Lógica binaria Tecnología 4º ESO Tema 5: Lógica binaria Página 2 Índice de contenido 1. Señales analógicas y digitales...3 2. Código binario,
Temporizadores y contadores en tiempo real: El módulo Timer0 y el prescaler del PIC
Temporizadores y contadores en tiempo real: El módulo Timer0 y el aler del PIC 1. Introducción...1 2. Estructura del Timer0...1 3. Funcionamiento del Timer0...2 3.1. Entrada de reloj del modulo Timer0...
Tema IV. Unidad aritmético lógica
Tema IV Unidad aritmético lógica 4.1 Sumadores binarios 4.1.1 Semisumador binario (SSB) 4.1.2 Sumador binario completo (SBC) 4.1.3 Sumador binario serie 4.1.4 Sumador binario paralelo con propagación del
TEMA II: REPRESENTACIÓN DE LA INFORMACIÓN
TEMA II: REPRESENTACIÓN DE LA INFORMACIÓN 2.1. Introducción. El computador procesa información. Para que un ordenador ejecute unos datos es necesario darle dos tipos de información: las instrucciones que
PROBLEMAS DE FUNDAMENTOS DE TECNOLOGÍA DE COMPUTADORES T5. MEMORIAS
PROBLEMAS DE FUNDAMENTOS DE TECNOLOGÍA DE COMPUTADORES T5. MEMORIAS Tema 5 Memorias Hoja: 2 / 14 Base teórica La memoria es el lugar en el que se almacenan las instrucciones y los datos para que se puedan
Fundamentos de Programación. Sabino Miranda-Jiménez
Fundamentos de Programación Sabino Miranda-Jiménez MÓDULO 1. Introducción a la computación Temas: La computación en el profesional de ingeniería Desarrollo computacional en la sociedad Aplicaciones Software
La memoria principal. Los subsistemas de E/S. Los buses del sistema
GUIA 23: MEMORIA E/S La estructura básica de la mayoría de los ordenadores actuales se representa mediante los siguientes elementos básicos: La Unidad Central de Procesamiento, CPU La memoria principal
Apuntes de Microcontroladores (Repaso de temas previos)
Apuntes de Microcontroladores (Repaso de temas previos) Por M. C. Miguelangel Fraga Aguilar Enero 2015 Representaciones numéricas En estos apuntes se usara el posfijo b para denotar un número escrito en
Qué es un Microcontrolador?
Curso de Microcontroladores Qué es un Microcontrolador? Al igual que la mayoría de las computadoras, los microcontroladores son simples ejecutores de instrucciones de propósito general. La verdadera estrella
18. Camino de datos y unidad de control
Oliverio J. Santana Jaria Sistemas Digitales Ingeniería Técnica en Informática de Sistemas Curso 2006 2007 18. Camino de datos y unidad de control Un La versatilidad una característica deseable los Los
TEMA 2: Representación de la Información en las computadoras
TEMA 2: Representación de la Información en las computadoras Introducción Una computadora es una máquina que procesa información y ejecuta programas. Para que la computadora ejecute un programa, es necesario
TEMA 5. ELECTRÓNICA DIGITAL
TEMA 5. ELECTRÓNICA DIGITAL 1. INTRODUCCIÓN Los ordenadores están compuestos de elementos electrónicos cuyas señales, en principio, son analógicas. Pero las señales que entiende el ordenador son digitales.
SISTEMAS NUMERICOS CAMILO ANDREY NEIRA IBAÑEZ UNINSANGIL INTRODUCTORIO A LA INGENIERIA LOGICA Y PROGRAMACION
SISTEMAS NUMERICOS CAMILO ANDREY NEIRA IBAÑEZ UNINSANGIL INTRODUCTORIO A LA INGENIERIA LOGICA Y PROGRAMACION CHIQUINQUIRA (BOYACA) 2015 1 CONTENIDO Pág. QUE ES UN SISTEMA BINARIO. 3 CORTA HISTORIA DE LOS
UN SIMULADOR DE UNA MAQUINA COMPUTADORA COMO HERRAMIENTA PARA LA ENSEÑANZA DE LA ARQUITECTURA DE COMPUTADORAS
UN SIMULADOR DE UNA MAQUINA COMPUTADORA COMO HERRAMIENTA PARA LA ENSEÑANZA DE LA ARQUITECTURA DE COMPUTADORAS Autores GROSSI, María Delia ([email protected]) JIMÉNEZ REY, M. Elizabeth ([email protected])
CIRCUITOS ARITMÉTICOS
LABORATORIO # 6 Realización: 26-05-2011 CIRCUITOS ARITMÉTICOS 1. OBJETIVOS Comprender los circuitos aritméticos dentro de la lógica binaria Utilizar sumadores totales de cuatro bits dentro de un Circuito
Cuestionario: Programación en C y máscaras (II)
Este documento es un autotest de ayuda enmarcado en la asignatura Informática Industrial y corresponde al tema Programación en C, pero es abierto y puede servir para entender el funcionamiento básico de
Los Microprocesadores MIA José Rafael Rojano Cáceres Arquitectura de Computadoras I Evolución Histórica de los Microprocesadores Intel Evolución de los microprocesadores Intel de la década de los 70 4004
1 1 0 1 x 1 0 1 1 1 1 0 1 + 1 1 0 1 0 0 0 0 1 1 0 1 1 0 0 0 1 1 1 1
5.1.3 Multiplicación de números enteros. El algoritmo de la multiplicación tal y como se realizaría manualmente con operandos positivos de cuatro bits es el siguiente: 1 1 0 1 x 1 0 1 1 1 1 0 1 + 1 1 0
❷ Aritmética Binaria Entera
❷ Una de las principales aplicaciones de la electrónica digital es el diseño de dispositivos capaces de efectuar cálculos aritméticos, ya sea como principal objetivo (calculadoras, computadoras, máquinas
Sistemas de numeración, operaciones y códigos.
Tema : Sistemas de numeración, operaciones y códigos. Para representar ideas, los seres humanos (al menos los occidentales) utilizamos cadenas de símbolos alfanuméricos de un alfabeto definido. En el mundo
Sistemas Digitales Ingeniería Técnica en Informática de Sistemas Curso 2006 2007 Aritmética binaria
Oliverio J. Santana Jaria 3. Aritmética tica binaria Sistemas Digitales Ingeniería Técnica en Informática de Sistemas Curso 2006 2007 Para Los La en conocer muchos aritmética comprender otros binaria tipos
Figura 1. Símbolo que representa una ALU. El sentido y la funcionalidad de las señales de la ALU de la Figura 1 es el siguiente:
Departamento de Ingeniería de Sistemas Facultad de Ingeniería Universidad de Antioquia Arquitectura de Computadores y Laboratorio ISI355 (2011 2) Práctica No. 1 Diseño e implementación de una unidad aritmético
Divisibilidad y números primos
Divisibilidad y números primos Divisibilidad En muchos problemas es necesario saber si el reparto de varios elementos en diferentes grupos se puede hacer equitativamente, es decir, si el número de elementos
Práctica 4 Diseño de circuitos con puertas lógicas.
Práctica 4 Diseño de circuitos con puertas lógicas. Descripción de la práctica: -Esta práctica servirá para afianzar los conocimientos adquiridos hasta ahora de simplificación, e implementación de funciones,
Tema 4. Gestión de entrada/salida
Tema 4. Gestión de entrada/salida 1. Principios de la gestión de E/S. 1.Problemática de los dispositivos de E/S. 2.Objetivos generales del software de E/S. 3.Principios hardware de E/S. 1. E/S controlada
Unidad 1. La información
Unidad 1. La información En esta unidad aprenderás: Los conceptos básicos de la informática. Cómo se representa la información dentro del ordenador. Las unidades de información. 1.1 Conceptos básicos Informática.
Capítulo 2 REPRESENTACIÓN DE LOS DATOS. Presentación resumen del libro: "EMPEZAR DE CERO A PROGRAMAR EN lenguaje C"
Presentación resumen del libro: "EMPEZAR DE CERO A PROGRAMAR EN lenguaje C" Autor: Carlos Javier Pes Rivas ([email protected]) Capítulo 2 REPRESENTACIÓN DE LOS DATOS 1 OBJETIVOS Entender cómo la computadora
UNIDAD 1. LOS NÚMEROS ENTEROS.
UNIDAD 1. LOS NÚMEROS ENTEROS. Al final deberás haber aprendido... Interpretar y expresar números enteros. Representar números enteros en la recta numérica. Comparar y ordenar números enteros. Realizar
Lección 1. Representación de números
Lección 1. Representación de números 1.1 Sistemas de numeración Empecemos comentando cual es el significado de la notación decimal a la que estamos tan acostumbrados. Normalmente se escribe en notación
Informática. Temas 27/03/2014. Carrera: Bioingeniería Profesora: Lic. S. Vanesa Torres JTP: Ing. Thelma Zanon
Informática Carrera: Bioingeniería Profesora: Lic. S. Vanesa Torres JTP: Ing. Thelma Zanon Temas O Sistema de Numeración O Conversión entre números decimales y binarios. O El tamaño de las cifras binarias
T6. CIRCUITOS ARITMÉTICOS
T6. CIRCUITOS ARITMÉTICOS Circuitos Aritméticos Son dispositivos MSI que pueden realizar operaciones aritméticas (suma, resta, multiplicación y división) con números binarios. De todos los dispositivos,
Arquitectura de Computadores
Arquitectura de Computadores [email protected] Curso 2004-2005 Arquitectura de Computadores Arquitectura de computadores es la disciplina que estudia la organización y funcionamiento de los computadores
Anexo B. Comunicaciones entre mc y PC
Anexo B Comunicaciones entre mc y PC En este apartado se hará hincapié en los comandos para el manejo del módulo de comunicaciones desde el PC. Conociendo estos comando se podrá realizar una aplicación
Materia: Informática. Nota de Clases Sistemas de Numeración
Nota de Clases Sistemas de Numeración Conversión Entre Sistemas de Numeración 1. EL SISTEMA DE NUMERACIÓN 1.1. DEFINICIÓN DE UN SISTEMA DE NUMERACIÓN Un sistema de numeración es un conjunto finito de símbolos
ELECTRÓNICA DIGITAL. Una señal es la variación de una magnitud que permite transmitir información. Las señales pueden ser de dos tipos:
ELECTRÓNICA DIGITAL INDICE 1. TIPOS DE SEÑALES... 3 1.1. SEÑALES ANALÓGICAS... 3 1.2. SEÑALES DIGITALES... 3 2. REPRESENTACIÓN DE LAS SEÑALES DIGITALES... 3 2.1. CRONOGRAMAS... 3 2.2. TABLA DE VERDAD...
La arquitectura del 8086/8088
Repasamos aquí la arquitectura de un PC. Para más información sobre lo aquí expuesto se puede consultar [PA01] y los capítulos iniciales de [Tej01], [Rod00] y [Nor01]. Anatomía del PC A grandes rasgos,
Ensamblador. Interrupciones. Dentro de una computadora existen dos clases de interrupciones:
Ensamblador Interrupciones Definición: Una interrupción es el rompimiento en la secuencia de un programa para ejecutar un programa especial llamando una rutina de servicio cuya característica principal
Primeros conmutadores: diodos de cristal y de tubos de vacío (1906). Transistor (TRT): más pequeño y fiable, de material semiconductor (1950).
Código binario en Sistemas Digitales Historia Primeros conmutadores: diodos de cristal y de tubos de vacío (1906). Transistor (TRT): más pequeño y fiable, de material semiconductor (1950). Circuitos integrados
Introducción a los Sistemas Digitales
Tema Sistema Estructura y comportamiento Señal analógica y señal digital Señal binaria Sistemas de numeración Representación de números enteros Signo-magnitud Complemento a dos Codificación Códigos numéricos
Estructura de Computadores
Estructura de Computadores Tema 4. El procesador Departamento de Informática Grupo de Arquitectura de Computadores, Comunicaciones y Sistemas UNIVERSIDAD CARLOS III DE MADRID Contenido Elementos de un
EJERCICIOS DEL TEMA 1
EJERCICIOS DEL TEMA 1 Introducción a los ordenadores 1) Averigua y escribe el código ASCII correspondiente, tanto en decimal como en binario, a las letras de tu nombre y apellidos. Distinguir entre mayúsculas/minúsculas,
Mod. I, Unid. 1, Obj. 1 Criterio de Dominio 1/1
M.R. 333 VERSION 1 Prueba Integral 1/5 UNIVERSIDAD NACIONAL ABIERTA VICERRECTORADO ACADÉMICO ÁREA INGENIERIA MODELO DE RESPUESTA ASIGNATURA: ARQUITECTURA DEL COMPUTADOR CÓDIGO: 333 MOMENTO: PRUEBA INTEGRAL
Estructura y Tecnología de Computadores (ITIG) Luis Rincón Córcoles Ángel Serrano Sánchez de León
Estructura y Tecnología de Computadores (ITIG) Luis Rincón Córcoles Ángel Serrano Sánchez de León Programa. Introducción. 2. Elementos de almacenamiento. 3. Elementos de proceso. 4. Elementos de interconexión.
El sistema decimal, es aquél en el que se combinan 10 cifras (o dígitos) del 0 al 9 para indicar una cantidad específica.
5.2 SISTEMAS DE NUMERACIÓN. DECIMAL El sistema decimal, es aquél en el que se combinan 10 cifras (o dígitos) del 0 al 9 para indicar una cantidad específica. La base de un sistema indica el número de caracteres
LECCIÓN 8: CIRCUITOS Y ALGORITMOS DE MULTIPLICACIÓN DE ENTEROS
ESTRUCTURA DE COMPUTADORES Pag. 8.1 LECCIÓN 8: CIRCUITOS Y ALGORITMOS DE MULTIPLICACIÓN DE ENTEROS 1. Circuitos de multiplicación La operación de multiplicar es mas compleja que la suma y por tanto se
OR (+) AND( ). AND AND
Algebra de Boole 2.1.Introducción 2.1. Introducción El Algebra de Boole es un sistema matemático que utiliza variables y operadores lógicos. Las variables pueden valer 0 o 1. Y las operaciones básicas
Entrada/Salida. Polling e Interrupciones. Verano de 2011. Mariano Moscato. Organización del Computador 1
Entrada/Salida Polling e Interrupciones Mariano Moscato Organización del Computador 1 Verano de 2011 El GuidoBot Robot docente Basado en tecnología ORGA1 tiene un procesador y una memoria ORGA1 Sus dispositivos
BENEMÉRITA UNIVERSIDAD AUTÓNOMA DE PUEBLA FACULTAD DE CIENCIAS DE LA COMPUTACIÓN
BENEMÉRITA UNIVERSIDAD AUTÓNOMA DE PUEBLA FACULTAD DE CIENCIAS DE LA COMPUTACIÓN PRÁCTICA DE LABORATORIO No I NOMBRE DE LA PRÁCTICA: COMANDOS BÁSICOS DE DEBUG( PARTE I) OBJETIVO: APRENDER LOS COMANDOS
