Xwork's Blog

The lord is waiting to take your hand.

Cumplimos 6 años.

Un día cómo hoy pero del 2016, nació el blog de Xwork y cómo cada año hay que celebrar; pero no en esta ocasión. Hoy solo rendiremos tributo a los que se adelantaron en el camino y solo esperan a que nos reunamos con ellos en la nada.




Att Xwork. Nos seguimos leyendo.

Inutil apps #5 - Visualizer V3 (parte 2)

 Hola de nuevo a todos, el día de hoy vamos a continuar con más de Visualizer V3. En el post anterior, vimos cómo amplificar la señal de audio para que el nivel de las luces no dependa totalmente del volumen del audio, el día de hoy vamos a ver cómo hacer las pantallas de LEDs usando el viejo y confiable LM3915.



Las conexiones a seguir son realmente simples, para ello primero veamos el diagrama siguiente:



Las conexiones son:

  • Pin 1 - Salida de LED.
  • Pin 2 - Tierra.
  • Pin 3 - VCC (3 ~ 35V).
  • Pin 4 - Divisor bajo
  • Pin 5 - Input
  • Pin 6 - Divisor alto.
  • Pin 7 - Referencia de salida.
  • Pin 8 - Ajuste de referencia.
  • Pin 9 - Selector de modo.
  • Pin 10 ~ Pin 18 - Salida de LED's
Si recuerdas del post inutil apps #4 - Visualizador de sonido y comparas los diagramas, verás que son prácticamente iguales, excepto por una sola conexión, en este caso el pin 6, lo conectamos a 5v, probablemente te estés preguntando ¿para que? y la respuesta es simple: así tenemos un mejor control de cual es el marco de trabajo.

En el post anterior vimos una de la aplicaciones de los amplificadores operacionales, en ese caso lo usamos cómo amplificador, pero es posible usarlo cómo comparador y el LM3915 hace uso de ese comportamiento.


Si observas detenidamente , todas las salidas son las salidas de los amplificadores operacionales, mientras que las entradas "positivas" se conectan a una serie de resistencias que van desde el pin 4 al pin 6, si recuerdas bien, esta configuración es un divisor resistivo y usarse resistencias del mismo valor en cada paso, tendremos que el voltaje será proporcional y será determinado por los valores que se usen en los pines 4 y 6. Así que si conectamos el pin 4  a tierra y el pin 6 a 5v, tendremos en cada paso 0.5v.

En la entrada negativa tendremos el voltaje que se aplique al pin 5, que en nuestro caso, será el voltaje proporcional al nivel de sonido. La forma en que  se usa es: cuando a un amplificador operacional se le aplica un voltaje mayor en la entrada negativa, la salida de este tendrá un voltaje igual al aplicado a la entrada negativa, en caso contrario (si el voltaje es mayor en la entrada positiva), la salida tendrá el voltaje de la entrada positiva.

Si tomamos en cuenta quu aplicamos un voltaje de 5v en el pin 5 y el pin 6 esta conectado a 5v, eso implica que todos los led prenderán, pero además eso aplica también para cuando el voltaje es mayor a 5v (valga la redundancia). Ahora si por ejemplo; aplicamos 2.7v al pin 5 mientras los pin 4 y 6 siguen conectados igual, del primero al quinto LED son los que cumplen la condición de tener un voltaje mayor en la entrada negativa por lo que se encenderían.

Y bien, por ahora es todo, en el siguiente y ultimo post, agregamos los componentes adicionales y pondré a disposición de cualquiera los diagramas para la fabricación de los PCB, que recalco, ya han sido probadas y están listas para usarse.

Los leo luego.

Inutil apps #5 - Visualizer V3 (parte 1).

 Hola de nuevo a todos, despues de un tiempo de ausencia, es hora de retormar el curso y crear un poco de contenido para el blog. En esta ocasion vamos a hacer mejoras para las luces audio-ritmicas que en algun momento hice. Si bien funcionan bien, había algunos detalles por arreglar para que funcionanran de manera adecuada. El proyecto se va a dividir en dos partes en donde detallaré cómo es que funciona, pero ademas pondré a disposicion de cualquiera que quiera ordenar las placas PCB, los archivos gerber del diagrama que veremos. Este a diferencia del velocimetro, ya esta probado, por lo que puedes encargarlos sin el temor de que algo funcione mal.


Si hacemos un poco de memoria (o revisamos el post), veremos que todo funciona cómo es debido, pero tiene un problema un tanto grave: ¡¡No tiene control de volumen!!, por lo que los niveles que se muestran en las luces, dependen directamente del nivel de volumen de la fuente de sonido, pero no solo eso, además el sonido tenía que estar amplificado para poder ver algo. Al inicio lo pensé así por la sencilla razón que no tengo un amplificador grande; y eso fue hasta hace poco.

Ahora cómo el sistema de sonido es mas grande, al subir un poco el volumen , este ya alcanza un nivel considerable de ruido y aun así no se alcanza a iluminar todas las luces del diseño pasado. La solución es simple, debemos de amplificar por separado el audio para las luces. Para poder conseguir esa tarea, vamos a usar un amplificador operacional.

Para esta tarea cualquier amplificador operacional funciona, pero en mi caso voy a usar el circuito integrado LM358.

Para poder usarlo, debemos de hacer las conexiones cómo sigue:


Cómo podrás observar, usamos tres resistencias. R3 sirve para establecer a tierra cómo referencia entre la señal que ingresamos, R1 y R2 se utilizan para establecer la ganancia que va a tener. Hay que tener en cuenta que este tipo de conexión conocida cómo "non inverting" sirve para amplificar la ganancia de la señal (hay varios tipos de configuraciones, pero eso esta fuera del tema del post), pero además existe una formula que nos sirve para calcular la ganancia.

Tomando en cuenta que los voltajes de salida en los conectores de audio de la mayoría de los dispositivos esta en un rango de hasta 2.2v, debemos de tratar de elevarlo al menos diez veces. En la practica he tenido problemas asumiendo esto, debido a que no poseo un osciloscopio, me resulta difícil obtener una medición exacta, pero podemos aquí usar una ganancia de 200, aunque parezca algo exagerado, debemos de tener en cuenta la amplificación no va a ir mas allá del voltaje de entrada que le demos al amplificador, por lo que si lo alimentamos con 12v, en el peor de los casos, tendremos 11.7v aproximadamente cómo máximo a la salida (eso dependiendo del amplificador, pero una vez mas, es un tema que merece un post a parte). Pero además 2.2v es el máximo por lo que tener al mínimo el volumen podemos esperar valores en el rango de los milivolts y si por ejemplo; tenemos 0.01v, al amplificarlo 200 veces, tendremos 2v

Ahora veamos los cálculos.




Si queremos saber cual es el valor de las resistencias que debemos de usar, bastará con realizar la sustitución de dos valores, si por ejemplo queremos una ganancia de 201, pero además tenemos una resistencia de 1k para R1 podemos sustituir en la formula:





Y después simplemente resolvemos:


Es recomendable que se usen valores en el rango de los kilo ohms para R1 de esta forma evitamos algunos problemas, pero además, también es recomendable mantener la ganancia no tan alta, una ganancia de 300x es aceptable, pero hay que tener en cuenta que dependiendo del tipo de señal de entrada, del propio circuito integrado y otros factores, se puede introducir "ruido" o distorsión que normalmente no son deseables, pero para este tipo de proyecto, no afecta tanto.

Antes de continuar veamos un ejemplo donde tenemos el valor de R1 que es 100k; y R2 que es 50k; y solo queremos saber cual es la guanacia:


Sabiendo lo anterior, podemos hacer las conexiones al lm358 tomando en cuenta cuales son los pines.


  • El pin 4 se conecta a tierra.
  • El pin 8 se conecta a VCC, en nuestro caso 12v
  • El pin 3 es la entrada, aquí conectaremos nuestra señal de audio.
  • Entre el pin 1 y 2 conectaremos R2 .
  • Entre el pin 2 y tierra, conectaremos R1.
  • El pin 1 es la salida amplificada.
Con las conexiones hechas, tendremos la señal lista para la siguiente parte (y no te preocupes no tardara un mes para salir).


Y bien, por ahora es todo, si bien no hemos aprendido la capacidad real de los amplificadores operacionales, este post sirve cómo una aproximación a ellos. Recuerda que aquí no trato de enseñar los fundamentos de la electrónica, pero a lo mejor en el futuro, hago una explicación mas profunda de todo lo que se puede hacer. Recuerda que en el siguiente post publicaré la parte de las luces, pero además publicaré los esquemas para que puedas fabricar tu propia versión.

Los leo luego.

Vamos a programar #100 - Speedometer M. Versión Final.

 Hola de nuevo a todos, el día de hoy empezaremos a ver la versión final del velocímetro.

Este proyecto fue iniciado mas o menos por el 2018 y ha sufrido una serie de cambios a lo largo del tiempo, si bien la última versión que fue publicada funcionaba bien, poco a poco se fueron agregando funciones para hacerlo funcionar de manera optima con el mínimo de hardware posible (de ahí que nunca le agregue ningún tipo de botón). Finalmente, tras días de pruebas y pruebas, he decidido finalizar con este proyecto porque finalmente ha alcanzado el nivel de confiabilidad para poder usarse en el día a día.


Primero que nada, veamos el código que hace funcionar a "Speeedometer M"


// Incluir la libreria para la pantalla
#include <LiquidCrystal.h>
// Pines usados en la pantalla extraido de los ejemplos incluidos en la libreria
const int rs = 13, en = 12, d4 = 11, d5 = 10, d6 = 9, d7 = 8;
// iniciar el objeto LiquidCrystal
LiquidCrystal lcd(rs , en , d4 , d5 , d6 , d7);
// Este es el valor que debes de modificar para que funcione correctamente
// Puedes ver instrucciones en https://xworkforall.blogspot.mx/2018/03/vamos-programar-49.html
const float Llanta = 1.9572;
//Algunas variables
const int HallSensor1 = 3;
const int LED1 =  7;
const int LED2 = 6;
const int LED3 = 5;
int Vuelta = 0;
unsigned long OldTime = 0;
float Distancia = 0.0;
float Speed = 0.0;
int ledState = LOW;
int VoltSensor = 0;
float BattVolt = 0.0;

// v=99.99
char BuffVelocity[15];
//d=999.99
char BuffDistance[15];
//R=999999
char BuffRevolution[15]; 
//V=4.2
char BuffBattery[15];

//Incializar todo
void setup() {
  //Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("@XWork");
  pinMode(HallSensor1, INPUT);
  attachInterrupt(digitalPinToInterrupt(HallSensor1) , GetVelocity , LOW);
  pinMode(LED1 , OUTPUT);
  pinMode(LED2 , OUTPUT);
  pinMode(LED3 , OUTPUT);
}

// Calcular la velocidad
void GetVelocity() {
  if (millis() - OldTime > 70) {
    Speed = Llanta / ((float)( millis() - OldTime) / 1000) * 3.6;
    OldTime = millis();
    Distancia = Distancia + Llanta / 1000;
    Vuelta += 1;
    if (ledState == LOW)
      ledState = HIGH;
    else 
      ledState = LOW;
  }
}
//Bucle principal
void loop() {
  VoltSensor = analogRead(A5);
  BattVolt = (VoltSensor * (10.0 / 1023.0));

  dtostrf(Speed , 5 , 2 , BuffVelocity);
  dtostrf(Distancia , 6 , 2, BuffDistance);
  sprintf(BuffRevolution , "R=%6d", Vuelta);
  dtostrf(BattVolt , 5 , 1, BuffBattery);
  
  lcd.setCursor(0, 0);
  lcd.print("d=");
  lcd.setCursor(2 , 0);
  lcd.print(BuffDistance);
  
  lcd.setCursor(9, 0);
  lcd.print("v=");
  lcd.setCursor(11, 0);
  lcd.print(BuffVelocity);
  
  lcd.setCursor(0,1);
  lcd.print(BuffRevolution);
  
  lcd.setCursor(9,1);
  lcd.print("V=");
  lcd.print(BuffBattery);
  
  digitalWrite(LED1 , ledState);
  digitalWrite(LED2 , ledState);
  digitalWrite(LED3 , ledState);
  
  if ((millis() - OldTime) > 3000){
    ledState = LOW;
    Speed = 0.0;
  }
}


Si haz seguido el proyecto desde el inicio, te darás cuenta que la mayoría de las funciones han cambiado un poco y algunos de los elementos que en ocasiones usamos para algunos post, los retomamos para llegar a está versión. Antes de continuar, voy a responder a unas cuantas preguntas que surgieron durante todo el proceso. La primera de ellas: ¿Cuánta es la máxima distancia que se puede alcanzar con el diseño original? La respuesta es relativamente sencilla, ya que usamos seis espacios de la pantalla y usamos tres para los enteros, una para el punto y dos para los decimales; la máxima distancia posible es 999.99 km, que es mas que suficiente para la mayoría de las personas, pero aquí es donde viene la parte "truculenta". La parte donde se llevan la cuenta de las revoluciones, el máximo posible es de 999,999 vueltas, en el caso de una llanta 24x2.25, la circunferencia es de 1.95m por lo que al multiplicar por el número de vueltas máximo obtenemos 1,949,998.05m máximo o lo que es lo mismo 1949km y esto bastaría para desbordar la parte de la distancia.

Otro dato que cambio fue la información que se usa para la batería. En algún momento del desarrollo, pensé en incluir un icono de batería, pero cada una de las baterías son diferentes y por lo tanto no es factible escribir el código ya que es casi una garantía que la pila no es igual en la mayoría de los casos. y por ejemplo, la pila que yo uso era de un teléfono y traía consigo una protección que corta la corriente cuando la pila alcanza 2.5v, pero una pila Sony VTC6 recomienda que el voltaje nunca baje de los 3v.

Es posible usar una pantalla de 20x4, pero la 16x2 es mas compacta y además, permite tener todo de manera lo mas sencilla posible.

Por último, he actualizado el proyecto en easyeda y he hecho el PCB de la mejor manera posible y es posible ordenarlos, pero quiero resaltar que aun no los he probado (o no al momento de que esto fue escrito) y eso se debe a que van a tardar un par de semanas. De todos modos, se pueden seguir las conexiones del diagrama y hacerlo en un placa perforada a mano.

O mandarlas a fabricar (que repito, aun no he probado.)

Y bien por ahora es todo, tal y cómo lo mencioné al principio, ya no voy a publicar mas actualizaciones, pienso que todo funciona cómo debe, pero además todo está disponible para que cada quien lo modifique a cómo lo necesite, recuerda que puedes hacer el pedido desde JLCPCB y además puedes descargar el código desde mi DropBox para que lo modifiques a tu gusto.

Los leo luego

Vamos a programar #99 - Modo obscuro en CSS 3.

 Hola de nuevo a todos, el día hoy vamos a ver como aplicar el modo obscuro a una página web de manera sencilla.



Si te has dado vueltas por el blog, podrás observar que ahora, dependiendo de la configuración del sistema operativo-navegador, la página cambia de color, si el tema es obscuro, la página también lo será y si es claro, pasa lo mismo.

Para poder aplicar un tema de un color para cada caso, existe una forma sencilla de hacerlo y para poder apreciarlo mejor, vamos a ver un ejemplo con los estilos para la calculadora de Vigenere.

Para empezar, veamos cuales son los estilos actuales (dependiendo de cuando leas esto, los estilos pudieron haber cambiado, para que no haya problema puedes ir y revisar el código fuente de la página en vivo).

.h1v
{
    font-size: 16px;
}
.formVigenere{
    font: 95% Arial, Helvetica, sans-serif;
    max-width: 400px;
    margin: 10px auto;
    padding: 16px;
    background: #000;
}
.textareaV
{
    width: 100%;
    height: 150px;
    padding: 5px 8px;
    box-sizing: border-box;
    border: 2px solid #999;
    border-radius: 4px;
    resize: none;
    -webkit-transition-duration: 0.4s; /* Safari */
    transition-duration: 0.4s;
}

.textareaV:focus
{
    width: 100%;
    height: 150px;
    padding: 2px 5px;
    box-sizing: border-box;
    border: 2px solid #50b;
    border-radius: 4px;
    resize: none;
}

.TextV
{
    width: 100%;
    box-sizing: border-box;
    border: 2px solid #999;
    border-radius: 4px;
}

.ButtonV
{
width: 100%;
    background-color: #57a2e2;
    border: none;
  border-radius: 4px;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    -webkit-transition-duration: 0.4s; /* Safari */
    transition-duration: 0.4s;
}

.ButtonV:hover
{
width: 100%;
    background-color: #224360;
    border: none;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
}
En CSS3, podemos definir clases que serán estilos para los objetos, cada vez que se le asigne una clase a un objeto, si está definido, este tomara la apariencia que la clase diga, pero además es posible sobrecargar ciertos estilos. En este caso, el primero paso para poder aplicar un tema obscuro, debemos de definir los estilos que serán predeterminados y para esto asumiremos que el estado normal siempre será el tema claro y la mayor parte del tema se basara en este.


Si observamos el caso de nuestro formulario tenemos que el texto es de color negro, el fondo es de color gris claro, los botones son de color azul claro y el área de las cajas de texto es blanco. Para poder idear un esquema de colores para el caso del tema obscuro, uno pensaría que bastaría con invertir los colores, pero no siempre es tan sencillo.

Si observamos la imagen de nueva cuenta, es el resultado de invertir los colores y si no está tan mal, hay que tener en cuanta cual es el color base del tema, en el caso del blog, la mayoría de elementos son de color azul y deberíamos de tratar de preservar la mayor parte de elementos de este color. 

Analizando las partes del formulario, observamos que consta de los siguientes elementos: etiquetas, cajas de texto, botones de opción, botones y el área del formulario. Si por ejemplo observamos que las etiquetas tienen el fondo transparente y el color de las letras es negro, bastará con cambiar el área del formulario que directamente afecta el color de fondo de las etiquetas, que al ser transparentes tomaran el color del área del formulario y por lo cual solo debemos de ocuparnos del color del texto. Para eso bastara con tener en cuenta que para el área del formulario queremos un color muy oscuro o simplemente negro, para las etiquetas vamos a querer un color claro o blanco y para poder elegir uno adecuado, podemos usar las herramientas que nos ofrecen la mayoría de navegadores.

En google chrome si presionamos F12 se abrirá las herramientas para desarrolladores y podremos inspeccionar los elementos que la conforman, al poner el ratón sobre un elemento, se mostraran algunas propiedades y veremos que hay una que dice "Contrast" si aparece un icono de exclamación (cómo en la imagen anterior) quiere decir que la combinación de colores podría resultar difícil de leer.
En cambio si aparece una marca verde, eso indica que la combinación es adecuada para la lectura, pero además nos sirve para crear nuestro esquema de colores.

La ventaja del color azul que seleccioné para el blog es que  resalta, pero no solo en colores oscuros, si no también en los obscuros, por eso podemos cambiar el área del formulario, el color  de las letras en las  etiquetas y con eso bastaría para que la lectura sea agradable em ambos modos: obscuro y claro.

Una vez que hemos determinado que es lo que se va a cambiar, en CSS3 existe una clase o consulta (queries) en la cual se va a aplicar cierto estilo basado en la configuración del sistema operativo, en este caso va a ser en el aspecto, tanto windows cómo Mac os tienen un ajuste en el cual se puede establecer si la mayor parte de los elementos serán claros o obscuro, pero además la mayoría de los navegadores tienen está misma opción.

Para hacer uso debemos usar "@media (prefers-color-scheme: dark)" y cuando el sistema operativo, navegador diga que se debe de usar el tema oscuro, todas las clases que estén aquí se aplicaran automáticamente sobrecargando las actuales (si se usan).

en nuestro caso, veamos que la definición para el área del formulario es:
.formVigenere{
    font: 95% Arial, Helvetica, sans-serif;
    max-width: 400px;
    margin: 10px auto;
    padding: 16px;
    background: #eee;
}

para hacer el cambio, dentro de "@media (prefers-color-scheme: dark)", debemos de crear una subclase que sera igual a la queremos modificar, en este caso, ".formVigenere" y solo incluiremos las propiedades que se van a cambiar en función al color, por lo que tendríamos algo así:
@media (prefers-color-scheme: dark) {
.formVigenere{
  background: #000;
  }
Para cada elemento iremos haciendo lo mismo hasta cubrir los cambio necesarios.

Y bien, por ahora es todo, cómo podrás ver, resulta sencillo hacer el cambio de esquemas y sirve de introducción para poder crear una variedad de temas y no solo claro y obscuro.

Los leo luego.

BIenvenido el 2022.



Hola de nuevo a todos, el día de hoy, solo escribo para desearles a todos un próspero año 2022. El año 2021 fue realmente difícil y mucha de la gente valiosa simplemente se nos adelanto en el camino. Hay que seguirnos cuidando y seguir trabajando para que el año venidero sea mas llevadero.

El blog ha sufrido una caída importante y no he podido publicar cómo es debido, pero le hice la promesa a algunas personas de que este proyecto bajo ninguna manera lo voy a dejar morir, así que simplemente tratare de mantener la promesa de un buen post al mes.

Sin mas que decir, espero que sigas pasándote por el blog de XWork y que algo de lo que aquí hay sea de utilidad.

Nos seguimos leyendo.

"In the 8th day god said: 'Let there be light' and there was Sir Isaac Newton."

Vamos a programar #98½ - Ver el rendimiento del PC usando arduino y C#.

 Hola de nuevo a todos, el día de hoy vamos a ver cómo es posible crear una barra de progreso en una pantalla LCD.



En el post anterior vimos cómo es posible usar arduino para monitorear el uso del CPU y la memoria RAM. Usando el programa, era posible visualizar el porcentaje en dos barras de progreso. Si bien todo el programa funcionaba, no lo hacia del todo bien, ya que el procedimiento que se encargaba de hacerlo tenia un par de cosas que le hacían falta y que no consideré hasta que escribí el poste anterior.

Algunas personas me mandaron sus soluciones, y de hecho de eso se trata el blog, no solo de enseñar, si no de aprender todos juntos. Por eso en el post del día de hoy, veremos paso a paso cómo corregir esa situación.

Para empezar vamos a entender cómo es que la barra funciona, en el post anterior, vimos cómo es que se crea cada parte, ahora veamos la lógica. Si hacemos memoria, el código de la barra es el que sigue

void DrawProgressBar(uint8_t Value, uint8_t XLocation, uint8_t YLocation){
    
    uint8_t Progress = map(Value , 1 , 100 , 0 , 20);
    
    lcd.setCursor(XLocation , YLocation);
    
    for (uint8_t i = 0; i < 20; i++)
    {
        if (i == 0)
        {
            if (Progress == 0)
                lcd.write(0);
            else
                lcd.write(1);
        }
            
        else if (i == 19)
        {
            if (Progress == 20)
                lcd.write(3);
            else
                lcd.write(2);
        }
        else
        {
            if (Progress <= i)
                lcd.write(4);
            else
                lcd.write(5);
        }
            
    }
}

Para ver cual es el error, bastará con asignarle algunos valores diferentes de los que trae. La función recibe tres parámetros, el primero es el valor que se quiere representar y debe de ser un valor entre 0 y 100 (o un porcentaje), el segundo es el valor en el cual se posicionara en el eje X y finalmente, el tercero sirve para indica cual será su ubicación en el eje Y. Pero que pasa si usamos otros valores cómo 0 y 3 (solo para las ubicaciones) o 10 y 3. Bueno tendremos algo cómo lo que sigue:

La razón por la que ocurre es que no podemos simplemente poner código y esperar a que funcione, en algunos de los post previos, vimos a grandes rasgos cómo es que funciona la pantalla y el resultado anterior es debido a que primera línea, continua en la tercera y la segunda continua en la cuarta, entonces si escribimos la barra con su tamaño "completo" al llegar al linte se la primera o segunda línea, esta continuara en la tercera o cuarta.

Para poder resolver el problema, vamos a tomar en cuenta cuales son las cosas que tenemos. Para empezar, hay que considerar el tamaño de la pantalla, cómo es algo que no cambia (para cada caso individual), una vez que tenemos identificado el tamaño, lo podemos asignar a unas constantes, para eso, justo antes de crear el objeto de pantalla "lcd" definimos a "SCREEN_SIZE_X" que será el tamaño de la pantalla en el eje X y que en mi caso es 20, también definimos "SCREEN_SIZE_Y" que será el tamaño de la pantalla y que en mi caso es 4. Para el caso de una pantalla de 16x2, bastara con asignar a "SCREEN_SIZE_X" con 16 y a "SCREEN_SIZE_Y" con 2. Cuando creamos el objeto "LiquidCrystal_I2C", pasamos el primer parámetro "0x27" que es la dirección por la cual se comunicará (y puede cambiar pero por ahora cómo es el único dispositivo I2C conectado), luego se pasa el parámetro del tamaño en X de la pantalla, seguido por el del eje Y.




Ahora que tenemos identificado el tamaño de la pantalla, podemos hacer los cálculos de manera mas sencilla en el procedimiento "DrawProgressBar", la barra esta pensada para que se dibuje desde la ubicación del parámetro "XLocation" hasta el final de la pantalla, entonces si ahora que tenemos el tamaño de la pantalla, para determinar el tamaño de la barra, bastará con restar el tamaño de la pantalla menos la ubicación en el eje X, el resultado de está operación lo podemos asignar a una variable y así en lugar de iterar de uno a un número fijo, lo haremos solo hasta lo que debe de ser la longitud de la barra. Así podemos hacer un código cómo el que sigue solo para la barra y las definiciones del tamaño.
//Para las pantallas de 16 x 2, basta con reemplazar el 20 por 16  y el 4 por 2
#define SCREEN_SIZE_X 20
#define SCREEN_SIZE_Y 4

//SCL - A5
//SDA - A4
LiquidCrystal_I2C lcd(0x27 , SCREEN_SIZE_X , SCREEN_SIZE_Y);

//{..............}
//Barra de progreso
void DrawProgressBar(uint8_t Value, uint8_t XLocation, uint8_t YLocation){
    
    uint8_t BarSize = SCREEN_SIZE_X - XLocation;
    
    uint8_t Progress = map(Value , 1 , 100 , 0 , BarSize);
    
    lcd.setCursor(XLocation , YLocation);   

    
    for (uint8_t i = 0; i < BarSize; i++)
    {
        if (i == 0)
        {
            if (Progress == 0)
                lcd.write(0);
            else
                lcd.write(1);
        }
            
        else if (i == BarSize - 1)
        {
            if (Progress == BarSize)
                lcd.write(3);
            else
                lcd.write(2);
        }
        else
        {
            if (Progress <= i)
                lcd.write(4);
            else
                lcd.write(5);
        }
            
    }
}

Ahora si miramos la imagen anterior, para poder usar una barra similar, bastará con usar "100 , 11 , 1" cómo parámetros. Hay que resaltar que el tamaño de la barra debe de ser mínimo de tres por lo que la posición máxima en X es 17, cualquier número más allá de el, hará que la barra no se muestre correctamente.

Y bien, por ahora es todo, el vínculo actualizado como de costumbre lo puedes descargar de mi dropbox, para que lo modifiques a tu gusto (si por ejemplo quieres cambiar el tamaño sin que pegue en la orilla).

Los leo luego.

Vamos a programar #98 - Ver el rendimiento del PC usando arduino y C#.

 Hola de nuevo a todos, el día de hoy vamos a ver cómo ver el rendimiento del PC usando C# y arduino.


En el post anterior vimos una versión beta de un programa que sirve para monitorear cual es el rendimiento del PC, y si bien ya era algo funcional, le he hecho algunas mejoras para que resulte más fácil de leer.

El código está conformado por dos partes, la primera, un programa en C# para windows que se encarga de obtener la información de cual es la carga del CPU en porcentaje, pero además de la RAM también en porcentaje, luego esa información la envía a un puerto al cual conectamos un arduino. La segunda parte consiste en un arduino que toma la información que recibió del programa y la muestra.

El programa en C#

El código fuente (del formulario principal) es el siguiente:

using System;
using System.Windows.Forms;
using System.IO.Ports;

namespace CPUMeterToArduino
{

    public partial class Form1 : Form
    {
        System.Diagnostics.PerformanceCounter CPULoad;
        System.Diagnostics.PerformanceCounter RAMInUse;
        System.Diagnostics.PerformanceCounter AvailableRAM;

        private SerialPort Port = new SerialPort();

        private void ArduinoMessage(string Message)
        {
            try
            {
                Port.Write(Message);        
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

        }

        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            string DATA = string.Concat(((int)CPULoad.NextValue()).ToString("D3") , "," , PercentRAM(RAMInUse.NextValue() , AvailableRAM.NextValue()).ToString("D3") , "\0");

            SerialPort sp = (SerialPort)sender;
            string indata = sp.ReadExisting();
            if (indata.Contains("\r\n"))
            {
                //ArduinoMessage("100,050\0");
                ArduinoMessage(DATA);
            }
        }

        Timer TimerMain;

        private int PercentRAM(float InUse, float Available)
        {
            if (Available == 0)
                return 100;
            else
            {
                float RamMBInUse = (InUse / 1024 / 1024);
                float TotalRam = RamMBInUse + Available;
                float Percent = ((RamMBInUse / TotalRam) * 100);
                return (int)Percent;
            }
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            RAMInUse = new System.Diagnostics.PerformanceCounter("Memory", "Committed Bytes");
            AvailableRAM = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes");
            CPULoad = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total");


            TimerMain = new Timer();
            TimerMain.Interval = 1000;
            TimerMain.Enabled = true;
            TimerMain.Tick += new System.EventHandler(TimerMain_Tick);


            string[] Ports = SerialPort.GetPortNames();
            cboportname.Items.AddRange(Ports);
            cboportname.Text = "COM3";
            Port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
            TSSLbl.Text = Port.IsOpen ? "Conectado" : "No conectado";

        }

        private void TimerMain_Tick(object sender, EventArgs e)
        {
            PBCPUUsage.Value = (int)CPULoad.NextValue();
            int PR = PercentRAM(RAMInUse.NextValue(), AvailableRAM.NextValue());
            PBRAMUsage.Value = PR;
            this.Text = "Uso de RAM " + PR + "%" + " | CPU: " + PBCPUUsage.Value; ;
            NTFYMain.Text = "Uso de RAM " + PR + "%";
            TSSLbl.Text = Port.IsOpen ? "Conectado con " + Port.PortName : "No conectado";

        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                Hide();
                NTFYMain.Visible = true;
            }
        }

        private void NTFYMain_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            Show();
            this.WindowState = FormWindowState.Normal;
            NTFYMain.Visible = false;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (Port.IsOpen == true)
            {
                Port.Close();
            }
            NTFYMain.Visible = false;
        }

        private void BtnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                if (Port.IsOpen == false)
                {
                    Port.BaudRate = 9600;
                    Port.PortName = cboportname.Text;
                    Port.Open();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void BtnDis_Click(object sender, EventArgs e)
        {
            if (Port.IsOpen == true)
            {
                Port.Write("Se ha desconectado el cliente\0");
                Port.Close();
            }
        }

        private void TSMIRestore_Click(object sender, EventArgs e)
        {
            NTFYMain_MouseDoubleClick(null, null);
        }

        private void TSMIExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}


Cómo podrás ver el código no sufrió tantos cambios por lo que recomiendo que leas el post anterior donde se detalla cada parte, de cualquier forma, lo puedes descargar al final del post.

El código para arduino.

La parte para arduino fue la que mas cambio, hay que recordar que hacemos uso de la libreria "LiquidCrystal_I2C.h" y la puedes encontrar en el administrados de librerías de arduino.
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
//SCL - A5
//SDA - A4
LiquidCrystal_I2C lcd(0x27,20,4);

char Texto[80];
char BuffCPU[20];
char BuffRAM[20];


byte BarBeginEmpty[] = {
    B11111,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B11111
};

byte BarBeginFull[] = {
    B11111,
    B10000,
    B10111,
    B10111,
    B10111,
    B10111,
    B10000,
    B11111
};

byte BarSegmentFull[] = {
    B11111,
    B00000,
    B11111,
    B11111,
    B11111,
    B11111,
    B00000,
    B11111
};

byte BarSegmentEmpty[] = {
    B11111,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B11111
};

byte BarEndEmpty[] = {
    B11111,
    B00001,
    B00001,
    B00001,
    B00001,
    B00001,
    B00001,
    B11111
};

byte BarEndFull[] = {
    B11111,
    B00001,
    B11101,
    B11101,
    B11101,
    B11101,
    B00001,
    B11111
};


void setup() {
    lcd.init();
    
    lcd.createChar(0 , BarBeginEmpty);
    lcd.createChar(1 , BarBeginFull);
    lcd.createChar(2 , BarEndEmpty);
    lcd.createChar(3 , BarEndFull);
    lcd.createChar(4 , BarSegmentEmpty);
    lcd.createChar(5 , BarSegmentFull);

    lcd.backlight();
    Serial.begin(9600);
}


void DrawProgressBar(uint8_t Value, uint8_t XLocation, uint8_t YLocation){
    
    uint8_t Progress = map(Value , 1 , 100 , 0 , 20);
    
    lcd.setCursor(XLocation , YLocation);
    
    for (uint8_t i = 0; i < 20; i++)
    {
        if (i == 0)
        {
            if (Progress == 0)
                lcd.write(0);
            else
                lcd.write(1);
        }
            
        else if (i == 19)
        {
            if (Progress == 20)
                lcd.write(3);
            else
                lcd.write(2);
        }
        else
        {
            if (Progress <= i)
                lcd.write(4);
            else
                lcd.write(5);
        }
            
    }
}

void loop() {

    int i = 0;
    if (Serial.available()) {
        while (Serial.available() > 0) {
            Texto[i] = Serial.read();
            i++;
        }
        Texto[i] = '\0';
    }


    String Text(Texto);

    int X = Text.substring(0 , 3).toInt();
    int Y = Text.substring(5 , 7).toInt();

    lcd.setCursor(0 , 0);
    //lcd.print(Texto);
    sprintf(BuffCPU , "CPU: %03d", X);
    sprintf(BuffRAM , "RAM: %03d", Y);
    lcd.print(BuffCPU);
    lcd.setCursor(0,2);
    lcd.print(BuffRAM);
    
    DrawProgressBar(X , 0 , 1);
    DrawProgressBar(Y , 0 , 3);

    Serial.println("\r\n"); 
    delay(500);
}

Para empezar, creamos seis "sprites"  que corresponden a los segmentos de la barra de progreso "BarBeginEmpty", "BarBeginFull", "BarSegmentFull", "BarSegmentEmpty", "BarEndEmpty" y "BarEndEmpty", aunque sus nombres pueden resultar auto-explanatorios para algunos, cada uno define cada estado para las diferentes partes de la barra de progreso. Cada uno de los segmentos de la pantalla están conformados por una matriz de 5x8, entonces (y por ahora no entrare en detalles) creamos un array de esa dimensión y para simplificarlo, podemos aprovechar que arduino acepta numero en binario siempre y cuando vayan precedidos por el prefijo "B", ahora observemos la siguiente imagen:

Está es la definición de "BarBeginFull"

Podemos observar que para formar el sprite, simplemente debemos de decidir si queremos o no usar cada cuadrado de la matriz. Si nos fijamos bien en la imagen, "BarBeginFull" es: "B11111,B10000,B10111,B10111,B10111,B10111,B10000,B11111"

Por lo tanto podemos usar una tabla e iluminar las celdas para formar el sprite que queramos.

Definición de "BarSegmentFull"


Definición de "BarSegmentEmpty"

Para poder hacer uso de los sprites, bastara con usar la función  "lcd.createChar()" que recibe cómo parámetros un valor del tipo "byte" que sirve para indicar en cual de los espacios reservados para caracteres personalizados vamos a almacenar nuestro sprite (esto hay de que detallarlo, pero será en otro post, por ahora solo toma en cuenta que los primeros ocho espacios son utilizables, es decir de 0 a 7, después de eso son los caracteres definidos dentro de la propia pantalla, pero cómo ya dije eso es para otro post). El segundo parámetro es el arreglo que contiene os datos de nuestro sprite.

Luego tenemos el procedimiento "DrawProgressBar()", este toma tres parámetros. El primero es el valor que tendrá la barra y para esto se asume que se recibirá siempre un valor entre 1 y 100 (para facilitar las cosas), el segundo parámetro sirve para indicar cual será la locación X de la barra, puede ser cualquier valor, pero ya que por lo general las pantallas tienen 16 o 20 espacios, lo recomendable es que el valor este dentro de estos, pero peor aun y es algo que note al escribir esto (y que voy a dejar asi cómo muestra de mi estupidez) es que la barra no se va a adaptar si se usa un valor distinto de 0, si se escribe otro, simplemente aparecerá incompleta. El tercer parámetro es la locación en el eje Y, al igual que el anterior, sirve para indicar la ubicación y puesto que la mayor parte de la pantallas son de 16x2 o 20x4, el parámetro debería de estar entre 0 y 1 para la primera y 0 y 3 para la segunda. Después se toma el parámetro "Values" y se le aplica la función "map" para que el rango este entre 0 y 20 que es el tamaño de la pantalla en mi caso, si tu pantalla es de 16 x 2 por ejemplo, esta línea la debes de modificar (y es la parte que olvide a la hora de escribir el código). Luego, en el ciclo "for", debemos de recorrer de 0  hasta el tamaño de pantalla y comprobar los distintos estados, puesto que la variable "Progress" ya tiene un valor que esta dentro del los limites de la pantalla, bastara con comparar, si el iterador es menor o igual que "Progress" dibujamos el sprite lleno, pero si no es el caso, dibujamos el vacío.

Y bien, por ahora es todo, aprovechando que no tome las previsiones necesarias para cuando las pantallas son diferentes a la mía, en los próximos días actualizaré el programa, pero de igual manera, el código fuente del programa de C# lo puedes descargar de mi dropbox y tambien el código de arduino lo puedes descargar de mismo lugar para que lo modifiques o lo pruebes mientras subo la version corregida.

Los leo luego.

Vamos a programar #97 - Ver el rendimiento del PC usando arduino y C# (beta).

 Hola de nuevo a todos, hace unos días mientras hacia algunas cosas en la computadora, apague el monitor mientras la PC hacia los suyo, pero al paso de una hora, comencé a prender el monitor cada diez minutos para poder ver si ya había terminado de trabajar, tras cinco encendidos, la computadora finalmente termino de hacer lo que estaba haciendo y proseguí con mi labor. Cómo se me hizo fastidioso apagar y prender el monitor en algo que se que va a tardar mucho, pero que sin embargo no tengo la certeza de cuanto va durar, decidí usar la vieja combinación de arduino y una pantalla LCD.


La mayor parte de aplicaciones que requieren tiempo para realizar su trabajo, consumen una gran cantidad de recursos, por lo que podemos conectar un arduino, y monitorear cual es la carga para el CPU, usualmente, si la actividad del CPU es mayor a 25%, podemos decir que aun se lleva a cabo un proceso. Para poder mostrar la información, usaremos de nueva cuenta un arduino y la pantalla LCD, tal y cómo lo hicimos con "Message Sender" (o la aplicación para ver cual era la ventana activa). En este caso, un programa en C# obtenía la información y la enviaba a arduino para poder desplegarla, cosa que también haremos ahora.

La aplicación en C#



El código en C# que sirve para enviar la información del uso del CPU a arduino es el que sigue.

using System; using System.Windows.Forms; using System.IO.Ports; namespace CPUMeterToArduino { public partial class Form1 : Form { System.Diagnostics.PerformanceCounter CPULoad; System.Diagnostics.PerformanceCounter RAMInUse; System.Diagnostics.PerformanceCounter AvailableRAM; private SerialPort Port = new SerialPort(); private void ArduinoMessage(string Message) { try { Port.Write(Message); } catch (Exception e) { MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { string DATA = string.Concat("CPU ", CPULoad.NextValue().ToString("F2"), "%", " Ram ", PercentRAM(RAMInUse.NextValue(), AvailableRAM.NextValue()), "%"); SerialPort sp = (SerialPort)sender; string indata = sp.ReadExisting(); if (indata.Contains("\r\n")) { ArduinoMessage(DATA); } } Timer TimerMain; private int PercentRAM(float InUse, float Available) { if (Available == 0) return 100; else { float RamMBInUse = (InUse / 1024 / 1024); float TotalRam = RamMBInUse + Available; float Percent = ((RamMBInUse / TotalRam) * 100); return (int)Percent; } } public Form1() { InitializeComponent(); } private void Form1_Load(object sender, System.EventArgs e) { RAMInUse = new System.Diagnostics.PerformanceCounter("Memory", "Committed Bytes"); AvailableRAM = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes"); CPULoad = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total"); TimerMain = new Timer(); TimerMain.Interval = 1000; TimerMain.Enabled = true; TimerMain.Tick += new System.EventHandler(TimerMain_Tick); string[] Ports = SerialPort.GetPortNames(); cboportname.Items.AddRange(Ports); cboportname.Text = "COM3"; Port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); } private void TimerMain_Tick(object sender, EventArgs e) { PBCPUUsage.Value = (int)CPULoad.NextValue(); int PR = PercentRAM(RAMInUse.NextValue(), AvailableRAM.NextValue()); PBRAMUsage.Value = PR; this.Text = "Uso de RAM " + PR + "%"; NTFYMain.Text = "Uso de RAM " + PR + "%"; } private void Form1_Resize(object sender, EventArgs e) { if (this.WindowState == FormWindowState.Minimized) { Hide(); NTFYMain.Visible = true; } } private void NTFYMain_MouseDoubleClick(object sender, MouseEventArgs e) { Show(); this.WindowState = FormWindowState.Normal; NTFYMain.Visible = false; } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (Port.IsOpen == true) { Port.Close(); } NTFYMain.Visible = false; } private void BtnConnect_Click(object sender, EventArgs e) { try { if (Port.IsOpen == false) { Port.BaudRate = 9600; Port.PortName = cboportname.Text; Port.Open(); } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void BtnDis_Click(object sender, EventArgs e) { if (Port.IsOpen == true) { Port.Write("Se ha desconectado el cliente"); Port.Close(); } } private void TSMIRestore_Click(object sender, EventArgs e) { NTFYMain_MouseDoubleClick(null, null); } private void TSMIExit_Click(object sender, EventArgs e) { Application.Exit(); } } }


Para poder utilizarlo requerimos de los siguientes controles:

  • 2 ProgressBar
    • PBCPUUsage
    • PBRAMUsage
  • 1 ComboBox
    • cboportname
  • 2 Button
    • BtnConnect
    • BtnDis
  • 1 NotifyIcon
    • NTFYMain
  • 1 ContextMenuStrip
    • CMSTray
Para poder revisar cual es el rendimiento, deponemos de algunos objetos de "System.Diagnostics.PerformanceCounter" que fueron creados para poder obtener información del sistema, en este caso creamos tres objetos de este tipo; "CPULoad", "RAMInUse" y "AvailableRAM", para poder utilizarlos, debemos de usar los constructores, este recibe tres parámetros, el primero del tipo "string" que es el nombre de la categoría, que para "RAMInUse" y "AvailableRAM" es "Memory" y para "CPULoad" es "Processor". En este caso existen mas categorías que podrían ser de utilidad, para poder ver cuales son las disponibles, podemos hacer uso de la función "PerformanceCounterCategory.GetCategories()" (pero eso lo veremos en otro post). Para el segundo parámetro, usamos en "RAMInUse" "Committed Bytes", para "AvailableRAM" "Available MBytes" y para "CPULoad" "% Processor Time". Cómo "RAMInUse" y "AvailableRAM" no cuentan con una sub-categoría, el tercer parámetro lo dejamos vacío, pero para "CPULoad" usamos "_Total".

Una vez creados los objetos, al momento de inicializar el formulario, crearemos un temporizador "TimerMain" con la propiedad "Interval" igual a "1000", la propiedad "Enabled" igual a "true" y para el evento "Tick" usaremos "System.EventHandler" con parámetro "TimerMain_Tick". Cada vez que se produzca el evento "Tick" de "TimerMain", es cuando se va a realizar la lectura de los valores usando las funciones "NextValue()" de los objetos "RAMInUse", "AvailableRam" y "CPULoad", esta función devuelve un valor del tipo "float" que en el caso de "RAMInUse" corresponde al numero de bytes utilizados de la memoria RAM, en el caso de "AvailableRAM"; representa la cantidad de mega bytes disponibles de la memoria RAM y finalmente en "CPULoad" representa el porcentaje de uso del procesador.

También creamos una función del tipo "int" que sirve para calcular el porcentaje de un número, para hacerlos, recibe dos parámetros del tipo "float", "InUse" y "Available", en este caso cómo lo vamos a usar para llenar la barra de progreso en proporción a la cantidad de memoria en uso, al solo obtener la cantidad de RAM usada y la disponible no usada, para saber cual es la cantidad total disponible, debemos de sumarlas, pero cómo la cantidad en uso está en bytes y la disponible está en mega bytes, debemos de convertir en este caso decidí convertir los bytes a mega bytes y para eso, solo basta con dividir entre 1024 y nuevamente entre 1024, una vez que tenemos los valores, simplemente aplicamos la formula del porcentaje y devolvemos el valor cómo resultado.

Los demás procedimientos son usados para crear el puerto y manejar la información y son tal cual los usamos en MessageSender.

El código en arduino.

El código para arduino es el siguiente, pero es igual al de "MessageSender"


#include <Wire.h> #include <LiquidCrystal_I2C.h> //SCL - A5 //SDA - A4 LiquidCrystal_I2C lcd(0x27,20,4); // set the LCD address to 0x27 for a 16 chars and 2 line display char Texto[80]; void setup() { lcd.init(); lcd.backlight(); Serial.begin(9600); } void loop() { int i = 0; if (Serial.available()) { while (Serial.available() > 0) { Texto[i] = Serial.read(); i++; } Texto[i] = '\0'; } lcd.clear(); lcd.setCursor(0 , 0); lcd.print(Texto); Serial.println("\r\n"); delay(1000); }


Y bien, por ahora es todo, en el siguiente post publicaré la version final y publicaré todo el código fuente para que lo modifiques a tu gusto, por ahora puedes bajar el ejecutable para que lo pruebes y basta con copiar y compilar el código para arduino.

Los leo luego.

"IMOSIN"

Vamos a programar #97 - Ver el contenido de la memoria EEPROM de arduino.

 Hola de nuevo a todos, el día de hoy vamos a continuar con un poco mas de Arduino, pero ma precisamente de la memoria EEPROM.



En post anteriores hemos visto que las placas de arduino poseen una memoria que no es volátil y que sirve para guardar cosas, dependiendo del modelo, tendremos hasta 4096 bytes de espacio, si es poco lo que se va a guardar nos sirve. Recientemente he estado trabajando en un proyecto que hace uso de la memoria, pero surgió un problema, me interesa saber cual es el contenido y me resulta molesto llamar de una por una las variables para ver su contenido.

Para resolverlo, decidí hacer un visualizador de memoria al mas puro estilo XVI32 (pero mucho mas chafa), el chiste es tener una forma de visualizar un poco la información. Para lograrlo use el siguiente código.

// Mostrar el contenido de la memoria EEPROM de arduino al estilo XVI32
#include <EEPROM.h>

uint8_t PrintableCharacter(uint8_t TheChar){
    if (TheChar < 31 || TheChar > 127)
        return 46;
    else
        return TheChar;
}


void setup(){
    Serial.begin(115200);
    while (!Serial) {
        ;
    }
    
    for (int Block = 0 ; (Block * 16) <= (EEPROM.length() - 16) ; Block++){
        uint8_t Values[16];
        for (uint8_t CurrentValue = 0 ; CurrentValue <= 16 ; CurrentValue++){
            int address = (Block * 16) + CurrentValue;
            Values[CurrentValue] = EEPROM.read(address);
        }

        char buf[100];
        sprintf(buf, "%04X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X   %02c %02c %02c %02c %02c %02c %02c %02c %02c %02c %02c %02c %02c %02c %02c %02c",
        Block * 16,
        Values[0] , Values[1] , Values[2] , Values[3] , Values[4] , Values[5] , Values[6] , Values[7] ,
        Values[8] , Values[9] , Values[10] , Values[11] , Values[12] , Values[13] , Values[14] , Values[15] ,
        PrintableCharacter(Values[0]) , PrintableCharacter(Values[1]) , PrintableCharacter(Values[2]) , PrintableCharacter(Values[3]) , PrintableCharacter(Values[4]) , PrintableCharacter(Values[5]) , PrintableCharacter(Values[6]) , PrintableCharacter(Values[7]) ,
        PrintableCharacter(Values[8]) , PrintableCharacter(Values[9]) , PrintableCharacter(Values[10]) , PrintableCharacter(Values[11]) , PrintableCharacter(Values[12]) , PrintableCharacter(Values[13]) , PrintableCharacter(Values[14]) , PrintableCharacter(Values[15]));
        Serial.println(buf);
    }
}

void loop(){
    
}

El programa consta de una función (mas las dos por default que usa Arduino) la primera de ellas del tipo "uint8_t" es  "PrintableCharacter()" y su función es la determinar si el numero que se la pasa cómo parámetro esta dentro del rango de caracteres imprimibles, si el numero esta dentro del rango 31 ~ 127, la función regresa el mismo numero que se paso cómo parámetro, en caso contrario regresa el numero 46 que en los valores ASCIIrepresenta al punto ".".

Dentro del procedimiento setup(), simplemente hacemos un ciclo "for" que recorrerá desde cero hasta "EEPROM.length() - 16 " pero cómo queremos que cuente de 16 en 16, cada línea consistirá de 16 bytes que se leerán y se almacenaran en un buffer llamado "Values[]", una vez que se ha leído cada bloque, se desplegara usando el siguiente formato: cuatro números hexadecimales para la dirección, dos números hexadecimales para cada valor y además llamaremos a la función "PrintableCharacter" para saber si debemos de mostrar un punto o el valor ASCII del numero.



Cómo podrás observar, funciona y ahora podremos ver de manera mas sencilla el contenido de la memoria, hay que recordar que esta es independiente del espacio donde se almacena el programa por lo que podemos ejecutar este y una vez visualizados los datos deseados, volver a instalar el programa que usemos para escribir en la EEPROM. Cómo de costumbre, puedes bajar el código de mi dropbox para probarlo. En el siguiente post, veremos cómo editar la memoria desde la computadora y repasaremos cómo es que arduino almacena los tipos y las variables en memoria.

Los leo luego.