Xwork's Blog

The lord is waiting to take your hand.

Mostrando las entradas con la etiqueta LCD. Mostrar todas las entradas
Mostrando las entradas con la etiqueta LCD. Mostrar todas las entradas

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 #74 - Message Sender 1.0.1.

Hola de nuevo a todos, el día de hoy vamos a continuar con Message Sender Windows que es una actualización de Message Sender que se público ya hace algún tiempo.


En el post anterior vimos cómo es que podemos obtener el titulo de una ventana si estamos usando Windows. Si fuiste de los que intento unir todo para que funcionara, lo primero que notaste es que la versión anterior usaba las matrices de LED de 8x8 y en esta ocasión usaremos una pantalla LCD (de acuerdo a lo que pidieron).

El código en C#.


Primero veamos el código en C# para la aplicación de Windows.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MessageSenderWindows
{
	public partial class FrmMain : Form
	{
		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		static extern IntPtr GetForegroundWindow();
		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		static extern int GetWindowTextLength(IntPtr hWnd); 

		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 string GetCaptionOfActiveWindow()
		{
			string strTitle = string.Empty;
			IntPtr handle = GetForegroundWindow();
			int intLength = GetWindowTextLength(handle) + 1;
			StringBuilder stringBuilder = new StringBuilder(intLength);
			if (GetWindowText(handle, stringBuilder, intLength) > 0)
			{
				strTitle = stringBuilder.ToString();
			}
			return strTitle;
			}

		public FrmMain()
		{
			InitializeComponent();
		}
		private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
		{
			SerialPort sp = (SerialPort)sender;
			string indata = sp.ReadExisting();
			if (indata.Contains("\r\n"))
			{
				ArduinoMessage(GetCaptionOfActiveWindow());

			}
		}

		private void FrmMain_Load(object sender, EventArgs e)
		{
			string[] Ports = SerialPort.GetPortNames();
			cboportname.Items.AddRange(Ports);
			cboportname.Text = "COM7";
			Port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
		}

		private void BtnConnect_Click(object sender, EventArgs e)
		{
			try
			{
				if (Port.IsOpen == false)
				{
					Port.BaudRate = (int)(NUDSpeed.Value);
					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 FrmMain_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (Port.IsOpen == true)
			{
				Port.Close();
			}
		}
	}
}

La aplicacion usa varias funciones y procedimientos; en orden de aparacion hacen:


  • "GetForegroundWindow()". Obtiene el "handle" de una ventana.
  • "GetWindowText()". Obtiene el texto de una ventana.
  • "GetWindowTextLength()". Obtiene la longitud del texto de una ventana.
  • "ArduinoMessage()". Intenta escribir datos en un puerto serie. Recibe un parámetro del tipo "string" que es el mensaje que se quiere escribir.
  • "GetCaptionOfActiveWindow()". Esta función sera la encargada de obtener el texto de la ventana que se encuentre activa. Regresa un valor del tipo "string" que representa el título de la ventana con el foco.
  • "DataReceivedHandler()". Este procedimiento es el encargado de procesar el evento "DataReceived" que se genera desde el objeto "Port". Recibe dos parámetros, el primero del tipo "object" que sera el objeto del tipo "Port" que desencadeno el evento, el segundo es un valor del tipo "EventArgs" que contienen algunos datos sobre el evento (que por ahora no usamos).
Con lo anterior tendremos una aplicación que luce mas o menos asi:
Una version reducida de la primer version.

El código para arduino.

El código para arduino es un tanto diferente al de la primer versión por el simple hecho de que usamos una pantalla LCD en lugar del las matrices LED. Lo primero que tenemos que tomar en cuenta es cómo se va a conectar la pantalla. En el post sobre el velocímetro hicimos uso de la pantalla de 16x2 y la conectamos directamente al arduino, pero ahora usaremos una pantalla de 20x4, pero además usaremos un adaptador al que se conectará la pantalla y nos permitirá utilizar I2C para hacer el intercambio de datos.

Lo primero que debemos de hacer es instalar la librería, una vez hecho esto, es importante tener en cuenta que el código está diseñado para una PANTALLA DE 20X4, por lo que si usas una diferente deberás de hacer los ajustes pertinentes (igual incluyo los comentarios que creo son pertinentes). Con todo lo anterior dicho, veamos el código fuente de la aplicación para arduino.

#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);
}
// la mayor parte las pantallas lcd vienen en tamaños de 16x2 o 20x4
// para que la funcion "funcione" en la version mas pequeña, debemos de hacer los calculos
// en  multiplos de 16
void MoveText(String Message){
	lcd.clear();
	if (Message.length() < 21)
		lcd.print(Message);
	if (Message.length() > 20 && Message.length() < 41)
	{
		lcd.setCursor(0 , 0);
		lcd.print(Message.substring(0 , 20));
		lcd.setCursor(0 , 1);
		lcd.print(Message.substring(20 , Message.length()));
	}
	if (Message.length() > 40 && Message.length() < 61)
	{
		lcd.setCursor(0 , 0);
		lcd.print(Message.substring(0 , 20));
		lcd.setCursor(0 , 1);
		lcd.print(Message.substring(20 , 40));
		lcd.setCursor(0 , 2);
		lcd.print(Message.substring(40 , Message.length()));
	}
	if (Message.length() > 60)
	{
		lcd.setcursor(0 , 0);
		lcd.print(Message.substring(0 , 20));
		lcd.setCursor(0 , 1);
		lcd.print(Message.substring(20 , 40));
		lcd.setCursor(0 , 2);
		lcd.print(Message.substring(40 , 60));
		lcd.setCursor(0 , 3);
		lcd.print(Message.substring(60 , Message.length()));
	}
	delay(1500); 
	Serial.println("\0\0");	
}

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

	}
	MoveText(Texto);
}


La aplicación consta de solo una función "MoveText()", en está función es donde se procesa todo el texto y está es la parte a la que debemos de prestar atención. Al iniciar nos encontramos con un "if" que sirve  para comprobar si la longitud del texto es mayor a 20 pero si la pantalla fuera de 16x2 por ejemplo; deberíamos de hacer la comprobación con el número 16, esto para que el texto se muestre en orden (aunque creo que en la pantalla de 16x2 no hay mayor problema, después veremos un poco de cómo es que funciona la pantalla), pero en el caso de la pantalla de 20x4, lo que haremos es separar el texto de tal forma que solo haya 20 caracteres por linea. Si viste el post anterior, notarás que la pantalla hacia un efecto de scroll, pero decidí omitirlo porque tardaba mucho y para no complicar tanto el proyecto, simplemente opté por la solución sencilla.

Y bien, por ahora es todo, en futuros posts veremos una explicación un poco más detallada sobre las pantallas de este tipo. Cómo de costumbre el código fuente lo puedes descargar de mi dropbox para probarlo y no dudes en modificarlo si tienes alguna idea.

Los leo luego.