La división binaria con decimales siempre nos cuesta un poco, y se nos vuelve un dolor de cabeza, pero no debe de ser así, por eso en este post les traigo una alternativa para realizar esta división en lenguaje ensamblador, con ayuda de MpLab X, y nuestro muy querido microcontrolador 16f877a.
Para ser más específicos, la idea es explicar como realizar división entre 2 números (entre 0 y 9), esto quiere decir que en el uC vamos a tener como entrada 8 bits, 4 para el dividendo, 4 para el divisor. Para tal fin, vamos a usar el puerto D.
El resultado se va a mostrar en 2 puertos, uno para mostrar la parte entera, y otro para mostrar la parte decimal. Para este ejemplo en particular, se mostrará como obtener un decimal, pero se darán ideas por si se quieren sacar más decimales.
Empecemos por entender como realizar la división teniendo en cuenta la limitación en las operaciones del lenguaje ensamblador. La división binaria se puede hacer por varios métodos, pero la más fácil de entender es el método de restas sucesivas, en esta, simplemente se toma el dividiendo y se le resta el divisor varias veces, hasta que el dividendo (en este caso representaría al residuo), sea menor al divisor.
Si nosotros contamos el número de veces que restamos el divisor del dividendo, antes que el residuo sea cero o un numero menor al divisor, obtendremos la parte entera de la división. Esto lo que nos dice es cuantas veces cabe el divisor en el dividendo por así decirlo.
De esta ultima operación, también podemos sacar el residuo de la división, el cual es, el resultado de la ultima resta.
Pero por supuesto, lo que queremos ver es una división cuyo resultado sea decimal. Bueno, el procedimiento inicial es el mismo, restar sucesivamente hasta que el residuo sea menor al divisor, ya que esperamos que este sea diferente de cero. De esta forma seguiremos obteniendo la parte entera, ¿pero cómo sacamos la parte decimal?, pues vamos a hacer lo mismo que cuando realizamos una división a mano. Cuando llegamos a este punto, lo que hacemos es poner una coma después del entero, y al residuo agregarle un cero a la derecha, dicho de otra forma, multiplicar por 10 el residuo.
Como en assembler no podemos hacer multiplicaciones directamente, vamos a aplicar el mismo principio, que con la división; pero esta vez con la suma, vamos a realizar sumas sucesivas.
Una multiplicación no es más que sumar n cantidad de veces un número, por esto, vamos a sumar 10 veces el residuo y una vez completa esta tarea, volvemos a usar el mismo método para dividir; restas sucesivas, restamos hasta que el residuo sea igual a cero, o menor al divisor. Contado el numero de restas realizadas, ahora hemos calculado el primer decimal de dicha división.
Si el residuo nuevamente fuera distinto de cero, podríamos volver a emplear este procedimiento las veces que fuera necesario, pero como dije anteriormente, para este ejemplo solo lo haremos una vez independientemente de si el residuo resultante es diferente de cero.
Nota: El procedimiento de sumar 10 veces el residuo para hallar el decimal, aplica únicamente para este caso donde vamos a usar como dividendo y divisor números entre 0 y 9. Si quisiéramos tener un divisor de 2 dígitos o más, tendríamos que realizar la comprobación de que después de multiplicar por 10 el residuo resultante sea mayor o igual al divisor; si es así, empezar a restar sucesivamente. Por el contrario, si el resultado sigue siendo menor al divisor volvemos a sumar 10 veces el residuo, agregamos un cero a la derecha de la coma en el resultado, y volvemos a comprobar.
Una vez claro esto, ahora si vamos con el código, este tiene comentarios con explicaciones breves que complementaran la explicación escrita en el blog. Configuramos los fusibles como para cualquier aplicación sencilla, protección de código apagada (CP), el WatchDogTimer (WDT) apagado, el temporizador de encendido (PWRTE) activo, y especificamos que vamos a usar un cristal como oscilador de baja frecuencia (XT_OSC), específicamente usaremos un cristal de 4MHz.
Después abriremos el Cblock para almacenar las diferentes variables que vamos a utilizar, el uso de cada uno de estos es evidente con tan solo leer su nombre, pero de todos modos aquí viene una pequeña descripción.
Dividendo: Este almacenara inicialmente el dato de entrada correspondiente a los 4 bits del dividendo. Ya en las operaciones se usara recursivamente para guardar el resultado de las restas sucesivas, o de las sumas para obtener el residuo multiplicado por 10.
Divisor: Almacenara durante toda la ejecución los 4 bits correspondientes al divisor, este dato nunca cambiará, a menos que se cambie el dato de entrada.
Residuo: Esta variable solo entra en funcionamiento si es necesario calcular una parte decimal, de ser así, almacena el número que hay en el dividendo al final de las restas sucesivas para hallar el entero. Luego este se le sumara 9 veces al dividendo, para poder volver a restar sucesivamente.
Se preguntarán, ¿Porqué 9 si se debe sumar este número 10 veces?, pues porque en el dividendo ya tenemos el primer número de esa suma, solo faltan 9 más.
Entero: Esta variable esta encargada de contar cuantas veces se realizan las restas sucesivas, y es la que al final nos da el resultado en el puerto C.
M10: Esta es la variable que realiza el conteo de las 9 veces que se debe sumar el residuo al dividendo, en otras palabras, es la multiplicación por 10.
Decimal: Al igual que la variable residuo, solo entra en acción cuando es necesario calcular una parte decimal. Si este es el caso, realizara cuentas de las restas sucesivas necesarias para obtener la parte decimal.
Continuamos realizando la inicialización correspondiente de los puertos que vamos a usar, y su correspondiente limpieza (mandar a ceros), esta es una practica aconsejable, ya que cuando estamos probando físicamente el circuito, evita lecturas erróneas o mal funcionamiento debido a ruido en el ambiente o estática.
Luego, guardamos en los datos ingresados en el puerto en las variables dividendo y divisor; organizamos los datos de forma que podamos operarlos de forma correcta. Lo que quiero decir es, como estamos guardando en las 2 variables el puerto completo (8 bits), debemos asegurarnos de que solo quede almacenado el dato que le corresponde a cada una.
Para el divisor, como el nibble bajo es el que corresponde a esta variable, debemos eliminar el nibble alto; esto se logra, realizando una AND lógica que elimine los datos no deseados, y conserve los datos requeridos, a esto se le llama enmascaramiento. La operación AND la realizamos entre el divisor y el dato 0x0F en hexadecimal, o en binario (00001111), esto nos dará como resultado que el nibble alto del divisor queda en ceros, y el nibble bajo con el dato ingresado.
Con respeto al dividendo, tenemos el dato requerido en el nibble alto, por lo que es necesario pasarlo al nibble bajo, esto se logra usando la instrucción SWAPF, esta intercambia los bits del nibble bajo con los bits de nibble alto. Pero, aún tenemos en el nibble alto el dato correspondiente a divisor, por lo que es necesario volver a enmascarar la variable.
Realizamos la limpieza de las variables entero y decimal, ya que cada vez que queramos iniciar una operación, estas no deben tener ningún valor residual de una operación anterior.
Como bien sabemos, si el divisor es 0, la división es indeterminada y si el dividiendo es 0, la división tendrá un resultado 0. Estos dos caso los vamos a comprobar al inicio para no hacer cálculos necesarios, y para los dos la parte entera y decimal mostraremos cero.
Para realizar comprobaciones, a modo de condicionar usaremos la resta, y las banderas que se desprenden de esta operación. Las banderas que vamos a usar son la de carry (C) y cero(Z).
Si no es el caso, continuamos con comprobación de que al restar el divisor al dividendo, el resultado no sea cero. Si es cero, se aumenta en uno el entero y se imprime el dato en los puertos.
De no ser cero el resultado, la resta debe dar, o que el dividendo es mayor o menor al divisor. Si el dividendo resulta ser mayor, se resta el divisor de dividendo, este resultado se guarda en el mismo dividendo, y se vuelve a la comprobación de si el dividendo actual menos el divisor es igual a cero, y así sucesivamente.
Si el dividendo es menor, quiere decir que debemos calcular ahora la parte decimal. Lo primero que se hace es pasar el número que queda en el dividendo después de las restas, y se lo asignamos a la variable residuo. Posteriormente se procede a sumar este residuo al dividendo 9 veces; se guarda el resultado en dividendo, esto con un bucle generado con ayuda de la variable con función de contador M10. Por cada suma que se realice se aumenta en 1 M10, y se comprueba si ya llego a 9, cuando se cumpla esto se pasa a la siguiente fase, la cual consiste en nuevamente realizar restas sucesivas como se realizo anteriormente, pero esta vez por cada resta se incrementa en 1 la variable decimal. La restas se ejecutan hasta que el dividendo sea igual a cero, o menor al divisor, luego de esto se imprimen los datos en los puerto respectivos, puerto C para la parte entera, puerto B para la parte decimal.
Finalmente el código vuelve ejecutarse nuevamente desde el principio para evaluar si hay algún cambio en los datos de entrada.
Nota: Si se desea, hallar más de un decimal, o el divisor es mayor a 9, en este punto se tendría que realizar la verificación de que justo después de realizar las 9 sumas, el dividendo sea mayor o igual al divisor, si no es así, volver a sumar y comprobar.
Espero haya sido de gran ayuda este post, y si tienen alguna comentario o pregunta, no duden en escribir. ¡Un abrazo!
Comentarios
Publicar un comentario