sábado, marzo 12, 2016

Buenas Maneras (II): Errores y excepciones

En C++, en JAVA y en C# (y supongo que en la mayoría de los lenguajes de alto nivel), existe una herramienta muy potente para la gestión de errores dentro de un programa: las Excepciones.

A decir verdad en C++ no pasan más que por ser un retorno de un int por una vía distinta a la habitual, sin embargo sigue siendo un camino a tener en cuenta. Hay que tener en cuenta que las excepciones no son necesariamente errores en una función, sino situaciones excepcionales y distintas al flujo normal del programa (la propia definición de la palabra lo dice).

He observado que algunos programadores utilizan las excepciones para devolver valores o ejecutar un código dentro del flujo normal del programa, y esto a mi modo de ver es un error muy grande.

Para hacerlo más claro voy a explicar un ejemplo:
Un programa que comunica el PC con un microcontrolador vía serie. Al enviar el PC una orden, el micro responde que la operación no se ha podido realizar de forma correcta. Algunos programadores utilizan una excepción para informar al usuario, lo cual es un error. En este caso deberíamos tener en cuenta los posibles casos que el micro pueda informar y tratarlos adecuadamente.
Debemos dejar las excepciones para resolver problemas que nos surjan fuera del flujo normal, e intentar dejar las respuestas de las diferentes partes del programa al margen de las excepciones.

Otro ejemplo un poco menos claro:
En el mismo programa de antes se debe tener en cuenta que el micro pueda estar desconectado u ocupado realizando alguna otra tarea. Esto generalmente provocará un TIMEOUT en la llamada al puerto serie. Muchos programadores utilizan una excepción para informar al usuario que el puerto no responde (en la propia excepción). Si bien el TIMEOUT es una excepción en sí misma, no se debe utilizar para informar al usuario, sino que, lo que se debería hacer es recoger la excepción y tratarla en el flujo normal del programa (reintentos de enviar el comando, informar al usuario, etc.).

En definitiva para una función cualquiera la cabecera debería seguir la siguente pauta:

tRespuesta Funcion(tParametros) throw tExcepcion

Donde tRespuesta definirá las respuestas normales de la función y tExcepcion definirá los errores y excepciones que no hayan podido ser tratados dentro de la función o que deban ser informados a otras funciones.

En C el tema cambia, ya que no existen las excepciones de forma nativa, así que u optamos por utilizar librerías externas que implementan macros para realizar esta función, o directamente nos montamos nosotros el sistema.

En mi caso he decidido hacer esto último de una forma muy sencilla: todas mis funciones devuelven un entero:
int Funcion(tParametros)

de tal forma que si el entero es un número negativo la función está devolviendo un código de error. Si la función devuelve 0 (OK) es un funcionamiento normal y dejo los números positivos para la devolución de otros resultados (por ejemplo números de bytes leidos de un puerto, número de estado de una máquina de estados, etc.)

De esta forma siempre puedo hacer el siguiente código:

#include "error.h"
return = foo(parameters);
if(return < OK) { // Tratamiento de los errores }

He incluso tratar cada uno de los posibles errores por separado con un switch-case-default.

Evidentemente reconocer los posibles problemas dentro de la función (punteros no válidos, indices fuera de rango, etc.) corre a cargo mío, pero es un sistema que me funciona bastante bien y muy simple y rápido de implementar.

Sólo hay que tener en cuenta que los valores a devolver por la función siempre tienen que ser enteros positivos y que si se necesitaran otro tipo de valores se deberá pasar un parámetro por referencia para guardarlos allí.

Un apunte, algunos microcontroladores tienen implementado una serie de traps, es decir funciones a las que se llaman cuando hacemos una operación prohibida (como una división por cero), estas traps ya hacen de excepciones (aunque en realidad son errores graves) por lo que se debería no intentar que saltasen nunca ya que hacerlo debería significar hacer un reset del programa.


S2

Ranganok Schahzaman

sábado, marzo 05, 2016

Entendiendo los... Osciloscopios (III): cuantificación

Una vez que hemos determinado el muestreo, el convertidor analógico-digital (ADC) tiene que cuantificar el valor de la señal recibida (dado que ha de pasarlo a información digital), y como no tenemos una memoria infinita truncará la resolución con la que se adquiere la medida.

Dependiendo del número de bits que resuelva el ADC se tendrán más o menos niveles. Por ejemplo para un ADC de 8 bits se tienen 256 niveles posibles de tensión, para uno de 10 bits 1024 niveles, etc. (a razón de 2^n niveles). Es decir, si ponemos una señal rampa (continúa) a la entrada del osciloscopio, el ADC lo digitalizará de la siguiente forma:

Cuantificacion Midthread
Niveles de cuantificación con 4 bits
Los escalones serán más finos cuantos más niveles (más bits de información por muestra) tengamos. Con cada bit extra se dobla el número de niveles, por lo tanto queda claro que nos interesan un ADC del máximo número de bits posibles.:
2-bit resolution analog comparison3-bit resolution analog comparison
Cuantificación de una
señal senoidal con  2 bits
Cuantificación de una
señal senoidal con  3 bits
Además hay que tener en cuenta que, aunque aquí se muestra todos los niveles de cuantificación disponibles para la señal, en un osciloscopio la cuantificación se realiza para el fondo de escala (es decir para lo que se muestra en pantalla), por lo que para la señal los bits disponibles siempre serán menos.

Aquí nos encontramos con el problema de siempre: no lo podemos tener todo, los ADC's tardan un tiempo en cuantificar la señal, y este tiempo augmenta según augmenta el número de bits, por lo que tener un ADC de 6GS/s y 24bits será tremendamente complicado (y muy muy caro). Así que debemos preguntarnos que cuál es la resolución y la velocidad que necesitamos para nuestra aplicación -evidentemente 6GS/s y 24bits servirán para practicamente todas las aplicaciones que necesitemos, sin embargo ¿necesitamos tantos datos?-.

Por un lado ¿cómo de rápidos serán los cambios? No es lo mismo leer una señal térmica (que toma algunos segundos en variar) que una "glich" en la señal, por lo que necesitamos un ancho de banda suficiente para detectar la señal (ya hemos hablado de esto y como mínimo necesitamos el doble del ancho de banda de la señal).

Además de eso, de que magnitud es el cambio para que sea relevante respecto a nuestra señal principal:
  • En una señal de 5V con 8 bits tendremos 256 niveles, es decir que cada bit corresponderá a unos 20mV (5V/256 = 19.53mV) de resolución
  • Si utilizamos 10bits tendremos 1024 niveles que cada bit corresponderá a unos 5mV (5V/1024 = 4.88mV)
  • Para 12bits, es decir 4096 niveles, la resolución será de aproximadamente 1mV (5V/4096=1.22mV), etc.
¿Realmente necesitamos ver la variación de 1mV sobre una señal de 5V (un 0,02% de la señal)?

Por lo que debemos tener una idea de la variación necesaria de la señal para que esta sea suficientemente importante para ser registrada, sobretodo sabiendo que si necesitamos mayor resolución en un punto aplicaremos una ganancia a la señal analógica antes del ADC, o lo que es lo mismo cambiaremos el fondo de escala a un valor menor (en vez de 5V aplicaremos 2V, 1V, 500mV, etc. en las ecuaciones anteriores).

Por otro lador  en un osciloscopio nos limita la resolución de la pantalla, por lo que si la pantalla es Full HD, es decir, con 1080 pixeles de altura, ¿qué sentido tiene tener 12bits (4096 niveles)? Seremos incapaces de representarlos en la propia pantalla del osciloscopio, y eso sin contar que muchos osciloscopios todavía utilizan pantallas con resolución VGA (o XVGA) de 480 pixeles de altura.

Lo más sencillo es sacrificar la velocidad y tener ADCs de más bits (podemos tener 24bits y 110kS/s muy baratos), o, por el contrario sacrificar los bits y tener major velocidad (8bits y 250MS/s bastante asequibles). Por ejemplo, los osciloscopios modernos de bajo coste (<500€) suelen tener 8bits y 1GS/s de muestreo en total (para los 2 o 4 canales que tienen). Incluso los de marca de coste asequible aumentan la velocidad sin cambiar el número de bits (8bits y 2GS/s). Es decir, los fabricantes en general están de acuerdo que, en un osciloscopio es más importante la velocidad de muestreo que el número de bits aplicados a cuantificar la señal.

S2

Ranganok Schahzaman