Xwork's Blog

The lord is waiting to take your hand.

BIenvenido el 2022.

No hay comentarios.


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."

No hay comentarios. :

Publicar un comentario

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

No hay comentarios.

 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.

No hay comentarios. :

Publicar un comentario

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

No hay comentarios.

 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.

No hay comentarios. :

Publicar un comentario

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

No hay comentarios.

 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"

No hay comentarios. :

Publicar un comentario

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

No hay comentarios.

 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.

No hay comentarios. :

Publicar un comentario

Cumplimos cinco años!!!

No hay comentarios.

 Hola de nuevo a todos, el día de hoy solo escribo para recordarles que un día cómo hoy pero de hace cinco años, nació el blog de Xwork. He cierto que ha sido altas y bajas, pero al menos seguimos en la lucha. Es cierto que la cantidad de post ha bajado, pero sigo firme en mi promesa de sacar al menos uno al mes (de baja calidad pero sin ser clic bait).

El aspecto del blog se ha renovado y a diferencia de la vez anterior, todos los errores han sido solucionados por lo que hay que darle una oportunidad (pero si el pueblo lo demanda lo cammbio de nuevo).

Aun hay mucho que podemos aprender asi que trataré de seguir hasta que haya un impedimento para hacerlo. Por ahora es todo, sigan vivos y felices y nos leeremos todavía.



No hay comentarios. :

Publicar un comentario

Vamos a programar #96 - Guardando configuraciones en la EEPROM de arduino.

No hay comentarios.

 Hola de nuevo a todos, el día de hoy vamos a continuar con mas sobre la EEPROM de arduino. EN el post anterior vimos cuales son las funciones disponibles para trabajar con ella (y nada mas) pero no vimos ningún ejemplo practico.



Para el ejemplo del día de hoy vamos a suponer que queremos guardar una configuración simple donde solo tenemos dos ajustes. El primero será una cadena de texto de longitud 5 y la segunda un entero (en arduino por default los enteros son de 16 bits para los modelos UNO). Para eso podemos crear código cómo el que sigue.

#include <EEPROM.h> //Para limpiar la memoria, solo deberia de ser usado una vez //Se puede quitar los comentarios, compilar y subir a arduino y despues del primer uso //Eliminarse o comentarse de nuevo para no gastar la vida util de la memoria EEPROM // for (int i = 0 ; i < EEPROM.length() ; i++) { // EEPROM.write(i, 0); // } int CheckData = EEPROM.get(0, CheckData); struct MySettings{ char MyName[20]; int MyValue; }; void setup() { int Address = 0; Serial.begin(9600); while (!Serial) { ; } if (CheckData > -1) { Serial.println("Al parecer no se guardado la configuracion antes"); EEPROM.put(Address , int(-1)); Address += sizeof(CheckData); MySettings SavSettings = { "XWORK", 88 }; EEPROM.put(Address , SavSettings); Serial.println("Guardado"); }else{ Serial.println("Parece haber datos validos de configuracion"); Address += sizeof(CheckData); MySettings SavSettings; EEPROM.get(Address , SavSettings); Serial.println(SavSettings.MyName); Serial.println(SavSettings.MyValue); Serial.println("Cargado"); } } void loop() { }

Antes que nada, hay que tomar un par de consideraciones. A pesar de que tenemos un numero de "seguridad", lo ideal seria limpiar la memoria EEPROM una vez antes de hacer uso de ella, así sabríamos que es lo que contiene antes de hacer uso de ella, si no lo hacemos, no podemos asegurar que es lo que hay y a la hora de revisar podríamos tener un valor válido para "CheckData" pero no significaría que existan datos útiles para cargar. En los ejemplos de arduino existe un sketch llamado "eeprom_clear" y su función es esa, la de definir todos los bytes a 0. La otra consideración es que usamos directamente las funciones "EEPROM.get()" y "EEPROM.put()" y la ventaja de estas es que acepta tipos y estructuras, pero sobre todo, solo actualiza los valores si son diferentes a los que se encuentran guardados (lo que incrementa el tiempo de vida de la EEPROM).

Ahora si veamos cómo es que funciona el código. Para empezar, primero creamos una variable llamada "CheckData" y su valor será lo que este guardado en la memoria EEPROM en la dirección 0. Hay que recordar que la función "EEPROM.get()" toma dos parámetros, el primero será la dirección en la cual va a leer y el segundo el tamaño de los datos en este caso espera un tipo y "CheckData" es del tipo "int". Una vez que tenemos ese valor, creamos una estructura llamada "MySettings" que contendrá dos campos, el primero un arreglo del tipo "char" con una longitud de 20 llamado "MyName" y el segundo un entero llamado "MyValue" del tipo "int".

Dentro de la función "setup()", lo primero que hacemos es revisar el valor de "CheckData", esta variable la usaremos cómo comprobador, en est caso comprobamos que su valor sea mayor que 0, si es así, asumimos que no hemos guardado los datos antes, por lo que procedemos a hacerlo. Cómo lo primero que hicimos fue cargar el número de comprobación "CheckData", entonces es lo primero que hacemos es guardar esa variable con un valor de -1 en la direccion 0 (para este momento la variable "Address" es 0), una vez que se escribió, hay que movernos. Un poco mas arriba mencioné que Arduino UNO usa dos bytes para guardar los enteros, pero cómo es poco practico movernos "de byte en byte", usaremos la función "sizeof()", esta recibe cómo parámetro un tipo y devuelve su longitud en bytes, entonces lo que hacemos, es simplemente asignar el resultado de la llamada a la función "Address".

Una vez que esta guardado el número de comprobación, podemos guardar nuestros datos, para eso primero creamos una variable de nuestra estructura, en este caso "SavSettings" y le asignamos valores. Con los valores asignados, procedemos a guardarlos usando "EEPROM.put()". Cómo primer parámetro (dirección en donde guardar), le asignaremos la variable "Address" y cómo segundo parámetro, "SavSettings".

Si la comprobación dice que ya hay valores validos, simplemente leemos los datos que estén después de "CheckData" usando "EEPROM.get()" con parámetros "Address" para la dirección y "SavSettings" cómo el tipo que se va a leer. AL finalizar la lectura, simplemente los ponemos en el monitor serie y los mostramos.

Ahora solo a manera de ejemplo, veamos el caso para guardar dos  campos diferentes.

#include <EEPROM.h>


//Para limpiar la memoria, solo deberia de ser usado una vez //Se puede quitar los comentarios, compilar y subir a arduino y despues del primer uso //Eliminarse o comentarse de nuevo para no gastar la vida util de la memoria EEPROM // for (int i = 0 ; i < EEPROM.length() ; i++) { // EEPROM.write(i, 0); // } int CheckData = EEPROM.get(0, CheckData); struct MySettings{ char MyName[20]; int MyValue; }; void setup() { int Address = 0; Serial.begin(9600); while (!Serial) { ; } if (CheckData > -1) { Serial.println("Al parecer no se guardado la configuracion antes"); EEPROM.put(Address , int(-1)); Address += sizeof(CheckData); MySettings SavSettings = { "XWORK", 88 }; EEPROM.put(Address , SavSettings); Address += sizeof(MySettings); MySettings SavSettings2 = { "MPM88", 90 }; EEPROM.put(Address , SavSettings2); Serial.println("Guardado"); }else{ Serial.println("Parece haber datos validos de configuracion"); Address += sizeof(CheckData); MySettings SavSettings; MySettings SavSettings2; EEPROM.get(Address , SavSettings); Serial.println("Datos 1"); Serial.println(SavSettings.MyName); Serial.println(SavSettings.MyValue); Address += sizeof(MySettings); EEPROM.get(Address , SavSettings2); Serial.println("Datos 2"); Serial.println(SavSettings2.MyName); Serial.println(SavSettings2.MyValue); Serial.println("Cargado"); } } void loop() { }


Lo importante del ejemplo anterior es que para leer el segundo campo, simplemente debemos de hacer uso de "sizeof()" pasando cómo parámetro la estructura "MySettings" para establecer el inicio de la siguiente dirección.

Y bien, por ahora es todo. Cómo de costumbre, puedes bajar el código de mi dropbox para probarlo.

Los leo luego.

No hay comentarios. :

Publicar un comentario

Vamos a programar #95 - Arduino y su EEPROM

No hay comentarios.

 Hola de nuevo a todos, el dia de hoy vamos a ver cómo usar la memoria EEPROM que viene incluida en la mayoría de los Arduinos.


EL otro día mientras trabajaba en uno de los proyectos que voy a publicar cuando el blog cumpla 5 años. De nueva cuenta quería guardar las configuraciones para poder retornarlas posteriormente, al crear un archivo de prueba con la mayoría de las configuraciones que planeo usar, el archivo difícilmente pesaba 120 bytes. Suponiendo que la mayoría de las memorias disponibles al día de hoy tienen una capacidad de 4GB (4,000,000,000 bytes), eso significaría que podría guardar unos 33 millones de archivos de configuración.


El almacenamiento es relativamente barato, por lo que no tendría ningún problema en usar una tarjeta de memoria de esa capacidad, pero desde el punto de vista de lo optimo; es un desperdicio. Por eso me decidí a usar la memoria que viene incluida con arduino. Pero antes que nada , que es una memoria EEPROM.

 EEPROM o E²PROM son las siglas de Electrically Erasable Programmable Read-Only Memory. Es un tipo de memoria ROM que puede ser programada, borrada y reprogramada eléctricamente. Wikipedia/EEPROM

 EN arduino la podemos usar  para guardar las configuraciones ya que; dependiendo del modelo; nos va a dar unos cuantos bytes. Para los modelos basados en el ATMEGA328P; tenemos 1024 bytes, para el ATMEGA168; 512 bytes, para los modelos ATMEGA1280 y ATMEGA2560; 4096 bytes.

Para poder usarla, debemos de incluir la libreria "EEPROM.h" que contiene las siguientes funciones:

  • EEPROM.Clear(): Limpia la memoria llenandola con ceros.
  • EEPROM.Read(Address): Lee la memoria y regresa el valor del byte en la dirección marcada por el parámetro "Address".
  • EEPROM.Write(Address, Value):Escribe un valor en la memoria, El valor será el parámetro "Value" y lo hará en la dirección  "Address"
  • EEPROM.Get(Address,Data): Lee la memoria y regresa el valor en la dirección "Address". El parámetro "Data", indica un tipo o estructura y será leída cada vez (por ejemplo si asignamos a Data un tipo "int", leerá todos los bytes que lo conforman).
  • EEPROM.Put(Address,Data): Escribe en la memoria en la dirección "Address" los datos en "Data", puede ser cualquier tipo de los soportados o una estructura. Al igual que EEPROM.Get() se escribirán todos los bytes que conformen al tipo o estructura..
  • EEPROM.Update(Address, Value): Actualiza el valore de "Value" en "Address" solo si el valor es distinto al existente (Solo un byte) 
Con las funciones anteriores podemos leer y escribir en la memoria EEPROM de arduino, solo que hay que considerar que la memoria tiene un ciclo de vida finito, aproximadamente cien mil veces es lo que se puede escribir (para leer no hay limite), por lo que solo es recomendable usarse para valores que no cambiaran tanto, para el caso que la necesito, difícilmente se cambiaran una vez al día por lo que esta garantizado que no se echara a perder.

Y bien, por ahora es todo, en el siguiente post veremos un ejemplo de como usar la mayoria de las funciones y cuales ofrecen ventajas a la hora de manejar la memoria.

Los leo luego.

No hay comentarios. :

Publicar un comentario

Vamos a platicar #12 - La luz al final del túnel

No hay comentarios.

 Hola de nuevo a todos, el día de hoy solo vamos a platicar un poco.


Cómo muchos sabrán, la situación del mundo se complico a finales del año 2019 e inicios del 2020, pero se complico aun más en marzo, por lo que ya ha pasado mas de un año. Para todos fue un problema y más si perdimos a alguien. El tiempo ha pasado, y si bien no mencioné nada (y trate de no hacerlo, ya que había información de sobra) es importante que las cosas ya van mejorando.

Hay mucho que celebrar. La primera es que si estas leyendo esto, significa que casi la libramos ya (eso dependiendo de que país seas, pero por lo general). La segunda cosa es que en junio cumplimos cinco años y he estado trabajando para terminar los proyectos que quiero publicar y finalmente, también parece ser que el blog alcanzará sus primeras cien mil visitas en el mes de junio por lo que decidí hacer una buena tanda de post para ese mes.

Hace algún tiempo pensé en hacer alguna clase de cooperación con algunos de los blogs que surgieron casi al mismo tiempo que el mío, pero para mala suerte de todos, todos ellos desaparecieron.

Y bien, por ahora es todo.

Los leo luego

No hay comentarios. :

Publicar un comentario

Learning machine #17 - Programando en Pascal - Arreglos, arreglos dinámicos y arreglos multidimensionales.

No hay comentarios.

 Hola de nuevo a todos, el dia de hoy vamos a continuar con un poco mas de programación en Pascal. EN alguno de los post anteriores, vimos cómo usar arreglos, arreglos de punteros,  pero nos falto ver cómo usar arreglos dinámicos y arreglos de dimensiones múltiples. Si bien ya los usamos en los arreglos de punteros, no hemos visto aun cómo son la reglas para usarlos.


Arreglos de una sola dimensión.

A manera de repaso veamos cómo es que se crea un arreglo de una sola dimensión en Pascal. En Pascal, para poder crear un arreglo, se debe de hacer lo siguiente.
type
	MiArr = array [0..9] of integer;
var
    Lista : MiArr;
Primero, debemos de crear un nuevo tipo, en el ejemplo anterior es "MiArr", seguido de la palabra reservada "array[]", dentro de los corchetes, se debe de establecer el rango para los valores de los índices. Normalmente se establece de cero a n pero se pueden crear arreglos que no sean de base cero (que no empiece el primer índice en cero). Incluso para valores que NO sean del tipo "real", se pueden usar valores negativos. Después de los corchetes debe de ir la palabra reservada "of" y finalmente el tipo del que serán los elementos contenidos en el arreglo.

Para inicializar los valores del arreglo, simplemente se les asigna un valor (normalmente usando un "for") y para poder usar los valores, bastara con llamar a la variable seguida de un corchete dentro del cual ira el valor del índice que queremos leer. Para ver con más claridad, retomemos un ejemplo que ya habíamos visto.
program Arreglos;

type
ArregloDeNumeros = array[0..99] of integer;

var
MisNumeros : ArregloDeNumeros;
i : integer;

begin

    for i := 0 to 99 do
    begin 
        MisNumeros[i] := i * 2;
    end;
    
    for i := 1 to 99 do
    begin 
        writeln(MisNumeros[i]);
    end;
    
end.
Cómo podrás ver, para asignar valores a todos los elementos del arreglo, simplemente usamos un ciclo "for" y para leer los valores usamos otro, pero si queremos leer solo uno en particular, solo bastaría con algo cómo "MisNúmeros[3]" que serviría para retornar el valor del elemento con índice tres.

Arreglos multidimensionales.

Para declarar un arreglo de dimensión múltiple, se sigue mas o menos los pasos anteriores, es decir: creamos un tipo, asignamos a una variable el nuevo tipo  y lo usamos. Pero el cambio viene al momento de definir el tamaño del arreglo. Primero veamos el siguiente ejemplo.

type
	MiArr = array [0..9 , 0..9] of integer;
var
    Lista : MiArr;

Cömo podrás observar, simplemente se definen las dimensiones de cada uno de los arreglos, por lo que en ejemplo anterior, habremos creado un arreglo de dos dimensiones con un tamaño de 10x10. Para agregar una dimensión mas, bastará con agregar otra coma seguida del rango.

Ahora miremos el siguiente ejemplo:
program ArreglosMulti;

type
ArregloDeNumeros = array[0..9 , 0..9] of integer;

var
MisNumeros : ArregloDeNumeros;
i , j : integer;

begin

    for i := 0 to 9 do
    begin 
    for j := 0 to 9 do
        MisNumeros[i,j] := i + j * 2;
    end;
    
    for i := 1 to 9 do
    begin 
        for j := 0 to 9 do
        write(' ',MisNumeros[i,j]);
        writeln();
    end;
	
end.
Si miramos bien ahora los arreglos se accesan mediante un sistema coordenado. para acceder al tercer elemento de la segunda fila, bastaría con tener algo cómo "MisNumeros[2,3]". Finalmente y de manera meramente ilustrativa, veamos el ejemplo de un array de 10x10x10 elementos.
program ArreglosMulti;

type
ArregloDeNumeros = array[0..9 , 0..9, 0..9] of integer;

var
MisNumeros : ArregloDeNumeros;
i , j , k : integer;

begin

    for i := 0 to 9 do
    begin 
        for j := 0 to 9 do
        begin
            for k := 0 to 9 do
                MisNumeros[i , j , k] := i + j + k * 2;
        end;
    end;
    
    for i := 1 to 9 do
    begin 
        for j := 0 to 9 do
        begin
            for k := 0 to 9 do
            write(' ',MisNumeros[i,j,k]);
            
        writeln();
        end;
        writeln();
    end;

end.
Y de manera similar, para acceder a un elemento seria "MisNumeros[1,2,3]".
Al momento de crear arreglos de varias dimensiones, no es necesario que estos sean "cuadrados", en los ejemplos, son arreglos de 10x10, pero pueden ser de valores diferentes cómo de 3x4x10 y para definir su tamaño solamente debemos de establecerlos al momento de crear el tipo "array[0..3,0..4,0..10]", solamente debemos de tener cuidado a la hora de acceder a los valores y que el indice al que queremos acceder, este dentro del intervalo.

Arreglos dinámicos.

Los arreglos dinámicos se distinguen de los otros por no tener un tamaño fijo y que a la hora de inicializarlos, tienen tamaño cero. Son especialmente útiles a la hora de ejecución, ya que nos permite cambiar el tamaño del arreglo en función a lo que el programa requiera y así en lugar de crear un arreglo de longitud 1000 para cubrir todas las posibles necesidades, solamente se crean los necesarios a cómo se vaya necesitando.

Para crear un arreglo dinámico, se siguen los mismo pasos que antes, es decir; se crea un tipo  nuevo y cuando le indicamos que va  a ser un arreglo no establecemos la longitud. Para apreciar mejor cómo se hace, veamos el siguiente ejemplo:
program ArreglosDina;

type
ArregloDeNumeros = array of integer;

var
MisNumeros : ArregloDeNumeros;
i , Items : integer;

begin

    Items := 10;
    setlength(MisNumeros , Items);
    
    for i := 0 to (Items - 1) do
            MisNumeros[i] := i * 2;

    for i := 0 to (Items - 1) do
            writeln(' ',MisNumeros[i]);

end.
Cómo podrás ver, al momento de crear el tipo "ArregloDeNumeros" seguido de la palabra reservada array, no se establece el tamaño pero simplemente se establece el tipo. Para ajustar el tamaño del arreglo, antes de usarlo, debemos de usar la función "setlength()". La función "setlength()".recibe dos o mas parámetros, el primero es el nombre de la variable del cual va a ajustar el tamaño, en el ejemplo anterior fue "MisNumeros", seguido del tamaño a asignar (que en el ejemplo fue la variable "Items" con un valor de 10). Para acceder al valor de cada elemento se hace igual que con los arreglos tradicionales.

Y bien, por ahora es todo. Aun quedan muchas cosas pendientes en el lenguaje Pascal, pero creo que solo un post mas dedicado a los arreglos estará bien (antes de olvidarme de la serie "programando en Pascal" por dos años). Cómo de costumbre puedes copiar y pegar los códigos para probarlos.

Los leo luego.


No hay comentarios. :

Publicar un comentario

Vamos a programar #94 - Formateando números en arduino - Camino a speedometer version final.

No hay comentarios.

Hola de nuevo a todos, el día de hoy vamos a continuar con mas partes para llegar a la versión final del velocímetro.


Leyendo algunos comentarios que hace la gente sobre que más se le podría modificar al velocímetro, muchos de ellos coincidían que había que corregir algunos de ellos. Los principales "detalles" estaban en la pantalla. Si bien no eran errores, aprovechando que se va a actualizar, decidí arreglarlos de una vez.

Supongamos que en la pantalla tenemos una velocidad de 10.09 km/h pero si desaceleramos, a por ejemplo 9.1 la pantalla mostraba algo cómo 9.19, y la cosa era peor si nos deteníamos ya que se mostraba 0.09. La razón por la que pasaba esto es por cómo se maneja la pantalla. Para ponerlo de forma simple, solo cuando se escribe algo en cierta posición, la pantalla se actualiza pero solo en esa zona, cuando escribimos "v=10.09" se usan siete espacios y mientras los usemos con números que usen el mismo "ancho" no se verá ningún problema, pero cuando tenemos cualquier número inferior a 10.00 cómo 9.9 ahora solo se usan menos espacios por lo que en lugar de actualizar los mismos siete de antes, solo se actualizan cinco o seis. Para poder solucionarlo, podemos escribir número que si importar cual sea, siempre ocupen la misma cantidad de espacios.

En C existen varias formas de formatear números, pero algunas de ellas no están disponibles para usarse en arduino (y debo de admitirlo, eso no lo sabia hasta apenas) y por eso veremos cómo es posible usar el reemplazo que arduino ofrece.

Primero que nada, hay que definir cual es el "estilo" que queremos que cada número tenga. En el caso del velocímetro, hay cuatro números importantes: la velocidad, la distancia, las revoluciones (o vuletas) y el voltaje de la batería. En el caso de la velocidad, una velocidad máxima de 99.99km/h es mas que suficiente para una bicicleta por lo que el numero ocuparía cinco espacios (con el punto incluido). Para las distancia, un viaje de 999.99 km es mas que suficiente para la mayoría de los ciclistas, por lo que seis digitos parecen suficiente, para las revoluciones, 999999 parecen suficientes por lo que seis espacios parecen suficientes y finalmente la batería, ya que el voltaje máximo de una sola pila es 4.2v, con solo reservar tres espacios es mas que suficiente.

Ahora veamos un código hipotético en C que serviría para usarse en un programa de computadora (por ejemplo) pero que no produciría el resultado esperado en arduino. EL código es el siguiente:

#include <stdio.h>

float Speed = 9.1;
float Distance = 9.04;
int Revolution = 16384;
float Battery = 2.0;

char BuffSpeed[20];
char BuffDistance[20];
char BuffRevolution[20];
char BuffBattery[20];

int main()
{

    sprintf(BuffBattery , "R=%3.1f\n", Battery);
    sprintf(BuffRevolution , "R=%6d\n", Revolution);
    sprintf(BuffDistance , "d=%6.2f\n", Distance);
    sprintf(BuffSpeed, "V=%5.2f\n", Speed);

    printf(BuffSpeed);
    printf(BuffDistance);
    printf(BuffRevolution);
    printf(BuffBattery);

    return 0;
}

Al compilarlo con los valores asignados, produce el siguiente resultado:

V= 9.10

d=  9.04

R= 16384

R=2.0

Pero cómo no es muy fácil de apreciar, solo para este ejemplo vamos a poner "0" en lugar de espacios en blanco para que se mas fácil de apreciar (en la pantalla no para poder preservarla un poco mas.) por lo que probamos con el siguiente código:

#include <stdio.h>

float Speed = 9.1;
float Distance = 9.04;
int Revolution = 16384;
float Battery = 2.0;

char BuffSpeed[20];
char BuffDistance[20];
char BuffRevolution[20];
char BuffBattery[20];

int main()
{

    sprintf(BuffBattery , "R=%3.1f\n", Battery);
    sprintf(BuffRevolution , "R=%06d\n", Revolution);
    sprintf(BuffDistance , "d=%06.2f\n", Distance);
    sprintf(BuffSpeed, "V=%05.2f\n", Speed);

    printf(BuffSpeed);
    printf(BuffDistance);
    printf(BuffRevolution);
    printf(BuffBattery);

    return 0;
}

Al compilarlo con los valores asignados, produce el siguiente resultado:

V=09.10

d=009.04

R=016384

R=2.0

Ahora resulta mas sencillo observar que por ejemplo la velocidad siempre ocupara cinco espacios a pesar de ser menor a 10km/h y que por ejemplo la distancia ocupa seis espacios a pesar de ser menor a 100km.

Pero que pasa si tratamos de ejecutar el mismo código en arduino?


Cómo puede observarse, la mayor parte del código se compila pero no funciona. Esto se debe principalmente a que a la hora de formatear los números del tipo "float" requiere mas poder de procesamiento por lo que en arduino no se implementó (**o eso es lo que quiero creer**), para el caso del los enteros es posible usar la misma función "sprintf()".

Para poder hacer algo similar, podemos usar la funcion "dtostrf()". Esta funcion lo que hace es convertir un número "double" a su equivalente en "string", para eso la funcion recibe cuatro parametros.

  • char * dtostrf (double __val, signed char __width, unsigned char __prec, char *__s)
    • __val es el valor que se va a convertir
    • __width es el tamaño que debe de tener la cadena.
    • __prec Es la cantidad de decimales que usaran
    • __s Es el sitio donde se almacenara el resultado.
Ahora recordemos que queremos que la velocidad tenga cinco espacios, pero además de dos decimales, lo que podemos hacer es algo cómo lo que sigue:

dtostrf(Speed , 5 , 2 , BuffVelocity);

Con lo anterior le estamos indicando que el string resultante debe de convertir la variable "speed", pero además el resultado, debe de tener una longitud de 5 y dos decimales, todo lo anterior lo debe de guardar en "BuffVelocity". Solo para ejemplificar, portemos el programa que corre en la computadora para que corra en arduino y para eso usaremos una pantalla LCD.

//YWROBOT
//Compatible with the Arduino IDE 1.0
//Library version:1.1
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
// Conexiones de la pantalla usando el convertidor a i2c
//SCL - A5
//SDA - A4
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display

float Speed = 9.1;
float Distance = 9.04;
int Revolution = 16384;
float Battery = 2.0;

char BuffSpeed[20];
char BuffDistance[20];
char BuffRevolution[20];
char BuffBattery[20];


void setup()
{
    lcd.init();
    lcd.backlight();
}


void loop()
{
    dtostrf(Speed , 5 , 2 , BuffSpeed);
    dtostrf(Distance , 6 , 2, BuffDistance);
    sprintf(BuffRevolution , "R=%6d", Revolution);
    dtostrf(Battery , 4 , 2, BuffBattery);

    lcd.setCursor(0, 0);
    lcd.print("v=");
    lcd.setCursor(2 , 0);
    lcd.print(BuffSpeed);

    lcd.setCursor(8, 0);
    lcd.print("d=");
    lcd.setCursor(10, 0);
    lcd.print(BuffDistance);

    lcd.setCursor(0,1);
    lcd.print(BuffRevolution);

    lcd.setCursor(10,1);
    lcd.print("v=");
    lcd.print(BuffBattery);
}


Al compilarlo y ejecutarlo tenemos que ahora si se produce el resultado esperado.

Y bien, por ahora es todo, ya casi llegamos a la versión final del velocímetro que justo acaba de cumplir tres años desde la primera.

Los códigos de antes solo fueron ilustrativos, por lo que si quieres probarlos, puedes copiarlos y pegarlos para usarlos.

Los leo luego.

No hay comentarios. :

Publicar un comentario

Learning machine #16 - Programando en Pascal - Arreglos de punteros.

No hay comentarios.

 Hola de nuevo a todos, el día de hoy vamos a continuar con más aprendizaje del lenguaje de programación Pascal.


En el post anterior vimos cómo es posible crear arreglos y usar punteros, pero en el programa del triangulo de Pascal, fue cuando mucha gente prguntó cómo es que funcionaba. Hay que recordar que cuando usamos punteros, el código debe de ser bien manejado si no, pueden ocurrir cosas malas.


Para poder crear un arreglo de punteros, primero debemos de crear un par de tipos, el primero debe de ser una nuevo tipo del tipo "array of algo" donde "algo" es alguno de los tipos soportados por Pascal. Seguido debemos de crear un nuevo tipo que va a ser un puntero del tipo que acabamos de crear. Para ilustrar un poco mejor, retomemos parte del código del triangulo de Pascal.


type
    Values = array of integer;
    PValues = ^Values;

En la declaración de los tipos, creamos primero el tipo "Values" que a su vez es del tipo "array of integer", una vez creado, solo queda crear un nuevo tipo que será un puntero al tipo que acabamos de crear, en este caso es "PValues" que es un puntero de "^Values".

Para poder utilizar los punteros hay que inicializarlos y para eso, hay que hacer la declaración de una variable que sea del tipo que creamos al ultimo, es decir al puntero "PValues" (en el área de las variables) y después cuando vayamos a usar la variable, debemos de inicializarla de forma explicita haciendo uso de la función "new()". Finalmente hay que establecer el tamaño que tendrá el arreglo haciendo uso de la función "setlength()". Para poder apreciar un poco más, de nueva cuenta tomemos el código del triangulo de Pascal y revisemos la función "CalculateLevel()".

function CalculateLevel (Level : integer) : PValues;
var
    i : integer;
    Result1 : PValues;
    PResult : PValues;

begin
    
    new(Result1);
    Setlength(Result1^, Level);
    
    
    if (Level = 1) then
    begin
        Result1^[1] := 1;
    end;

    if (Level = 2) then
    begin
        Result1^[1] := 1;
        Result1^[2] := 1;
    end;
	
    if (Level > 2) then
    begin
        PResult := CalculateLevel(Level - 1);
        for i := 1 to Level do
        begin
            if (i = 1) or (i = Level) then
            begin
                Result1^[i] := 1;
            end
            else
            begin
                Result1^[i] := PResult^[i] + PResult^[i - 1];
            end;
        end;
    end;
	
    exit(Result1);
	
end;
Para poder usar los valores dentro del arreglo, bastará con usar el nombre de la variable que se el arreglo, seguido de "^" y a su vez seguido del número del índice entre corchetes, en el código anterior, cuando queremos asignar el valor de uno al elemento uno, tenemos "Result1^[1] := 1;".

Un ejemplo pero ahora usando el tipo string.

program ArregloDePunteros;
    type
        Nombres = Array of string;
        PointToNombres = ^Nombres;
    var
        ListaDeNombres : PointToNombres;
        i : integer;
begin
    New(ListaDeNombres);
    Setlength(ListaDeNombres^ , 4);

    ListaDeNombres^[1] := 'RMFP';
    ListaDeNombres^[2] := 'RSC';
    ListaDeNombres^[3] := 'USG';
    ListaDeNombres^[4] := 'SCL';

    for i := 1 to 4 do
        writeln (ListaDeNombres^[i]);
        
end.

Cómo podrás observar, el procedimiento es exactamente el mismo. Primero creamos los tipos "Nombres"; que es del tipo "Array Of String", y seguido creamos el tipo "PointToNombres" que es un puntero a "Nombres". Después creamos una variable llamada "ListaDeNombres" que será del tipo "PointToNombres", luego la inicializamos usando "New()" y establecemos el tamaño usando "Setlength()". Finalmente para acceder a los valores, usamos la variable "ListaDeNombres^".




Y bien, por ahora es todo, en el siguiente post veremos mas a detalle los arreglos. Cómo de costumbre, puedes usar el compilador online para probar el código.

Los leo luego.

No hay comentarios. :

Publicar un comentario

Learning machine #15 - Programando en Pascal - Tipos, Punteros y arreglos.

No hay comentarios.

 Hola de nuevo a todos, el día de hoy vamos a continuar con un poco mas del lenguaje de programacion Pascal.


Hace algunos días hice un programa que sirve para calcular el triangulo de Pascal. La versión en C, Python y Pascal están disponibles para su consulta, pero a partir de ellos, se generaron dudas y si bien en el caso de Python y C siempre he dicho que en el blog no hay tutoriales para aprender a programar, en el caso de Pascal no es así. En el caso de C y Pascal se hacen uso de punteros y si bien la forma de declararlos en cada uno es diferente, la forma de uso es esencialmente la misma, por eso este post sirve para poder entender cómo es que se utilizan los punteros en Pascal, pero la idea para C es la misma (en el caso de los punteros). Para poder entender cómo se usan los tipos también es necesario saber sobre los tipos (solo en el caso de Pascal)


Tipos.

En pascal es simple declarar tipos. Para hacerlo, debemos de hacer uso de la palabra reservada "Type"
Tomemos cómo ejemplo el código que sigue.

program Tipos;
type
    Colores = string;
    DiasDeLaSemana = string;
    Numeros = integer;
    
var
    MiColor : Colores;
    Hoy : DiasDeLaSemana;
    Fecha : Numeros;
    
begin

    MiColor := 'Verde';
    Hoy := 'Sabado';
    Fecha := 24;

    writeln (MiColor);
    writeln (Hoy);
    writeln (Fecha);

end.

Con lo anterior, habremos creado tres nuevos tipos, el tipo "Colores", el tipo "DiasDeLaSemana" y el tipo "Numeros", a su vez cada uno hereda del tipo "string", "string" e "integer" respectivamente. Para poder usar cada nuevo tipo creado, simplemente creamos variables y les asignamos el tipo que acabamos de crear, en el caso del ejemplo anterior tenemos: "MiColor", "Hoy" y "Fecha" cada una de los tipos que acabamos de declarar.

A simple vista no parece tener mucho chiste, ¿por qué no usar "MiColor" directamente con el tipo "string" o "Fecha" con el tipo "integer"?, la respuesta es simple, el poder declarar tipos "propios" nos da la posibilidad de manejar nuestro código mejor, pero no solo eso, algunas variables cómo los registros "record" son mas fáciles de manejar así, pero además es un requisito necesario para poder trabajar con arreglos de punteros.

Punteros.

En ciencias de la computación, un puntero es un objeto del lenguaje de programación, cuyo valor se refiere a (o "apunta a") otro valor almacenado en otra parte de la memoria del ordenador utilizando su dirección. Un puntero referencia a una ubicación en memoria, y a la obtención del valor almacenado en esa ubicación se la conoce como desreferenciación del puntero. A modo de analogía, un número de página en el índice de un libro podría considerarse un puntero a la página correspondiente; desreferenciar un puntero sería como ir a la página con el número de página especificada en el índice wikipedia/Punteros


En pascal, primero debemos de crear un tipo, este a su vez será un puntero de cualquiera de los admitidos por Pascal precedido por "^". Si queremos declarar un puntero hacia un valor "integer", debemos de hacer una declaración cómo la que sigue:

type
    PointToInt = ^integer;
Con eso habremos creado un nuevo tipo llamado "PointToInt" que es un puntero hacia el tipo "integer", al igual que cualquier tipo que hayamos creado, para usarlo, simplemente debemos de crear una nueva variable del tipo "PointToInt" (en este caso)

program Pointers;
type
    PointToInt = ^integer;
var
    MiNumero : PointToInt;
begin
    new(MiNumero);
    MiNumero^ := 20;
    writeln (MiNumero^);
    Dispose(MiNumero);
end.

Para poder utilizar la variable, además de declarar la  variable con el nuevo tipo, debemos de inicializarla (reservar memoria), esto lo logramos con el procedimiento "new()" que recibe cómo parámetro la variable que queremos usar. Una vez que se inicializo, para poder acceder a los valores, debemos de escribir el nombre de la variable seguido por "^". Cuando terminamos de usar cualquier variable creada de esta forma, debemos de liberar la memoria que usamos (dependiendo del sistema operativo, en la mayoría de los casos cuando cerramos el programa, se liberan en automático los recursos, pero siempre es una buena idea hacerlo por nuestra cuenta) para poder hacerlo, hacemos uso del procedimiento "dispose()" que recibe cómo parámetro el nombre de la función.

Algunos dirán que no es necesario crear un tipo para cada puntero, pero creo que más sencillo hacerlo, así si queremos crear más punteros bastará con mandar a llamar al tipo.

Arreglos.

Los arreglos son colecciones de variables del mismo tipo, se pueden crear de cualquier tipo soportado por Pascal. Al igual que los punteros, se pueden declarar definiendo un tipo. Para hacerlo, debemos de hacerlo declarando al tipo como "array[] of algo", Dentro de los corchetes, debemos de introducir el rango en el cual el arreglo desde índice mas bajo, seguido de ".." y finalmente el índice mas alto, para el tipo "algo", puede ser cualquiera de los que soporta Pascal. Un ejemplo de una declaración de un arreglo es el siguiente:

type
ArregloDeNumeros = array[0..99] of integer;
Con lo anterior habremos creado un nuevo tipo llamado "ArregloDeNumeros", que posee 100 espacios desde 0 hasta 99 y cada uno de ellos será del tipo "integer". Para poder utilizar el arreglo, bastará con crear una variable y asignarle el tipo que recién acabamos de crear. Para inicializar el arreglo, bastará con asignarle valores a cada elemento. La forma más sencilla de hacerlo es mediante un ciclo "for" con el siguiente código:

program Arreglos;

type
ArregloDeNumeros = array[0..99] of integer;

var
MisNumeros : ArregloDeNumeros;
i : integer;

begin

    for i := 1 to 99 do
    begin 
        MisNumeros[i] := i * 2;
    end;
    
    for i := 1 to 99 do
    begin 
        writeln(MisNumeros[i]);
    end;
    
end.

Ahora un ejemplo con arreglos del tipo "string":

program Arreglos;
uses
	sysutils;
type
	ArregloDeNumeros = array[0..99] of integer;
	ArregloDePalabras = array[0..99] of string;
var
	MisPalabras : ArregloDePalabras;
	MisNumeros : ArregloDeNumeros;
	i : integer;
begin
	for i := 0 to 99 do
	begin 
		MisNumeros[i] := i * 2;
		MisPalabras[i] := concat('EL valor de i es ', IntToStr(i));
	end;
	for i := 0 to 99 do
	begin 

	end;
end.

Para acceder a cada valor dentro del arreglo; una vez que ya ha sido inicializado, simplemente escribimos el nombre de la variable, seguido de corchetes ([]) y dentro de estos escribiremos el valor del elemento, en este caso si po ejemplo queremos saber el valor del elemento con índice 90 tendríamos

		writeln(MisNumeros[90]);
		writeln(MisPalabras[90]);

Y bien, por ahora es todo, pero aun queda pendiente una parte importante que hace que el programa del triangulo de pascal funcione y para eso necesitamos saber de arreglos dinámicos en este post solamente vimos como usar los estáticos, pero además necesitamos saber cómo funciona u arreglo de punteros, pero eso será en el siguiente post. Recuerda que puedes probar los códigos en el compilador online.

Los leo luego.

No hay comentarios. :

Publicar un comentario

Vamos a programar #93 - El triangulo de pascal (ver. Pascal)

No hay comentarios.

 Hola de nuevo el día de hoy vamos a continuar con mas del triangulo de Pascal, pero esta vez en el lenguaje de programación pascal.



Para entender un poco sobre el triangulo de pascal, recuerda que en el post "Vamos a programar #91" podemos encontrar cómo se calcula a mano y el código en Python. Ahora veamos el código en Pascal que sirve para calcular el triangulo de Pascal.





Lo primero que hacemos es declarar variables globales. Seguido. creamos dos tipos, el tipo "Values" que a su vez es del tipo "array of integer" y el segundo tipo llamado "PValues" que es un puntero a "Values" (en el siguiente post detallaremos algunas cosas sobre los tipo y los punteros).

Después creamos dos funciones. La primera función es "CalculateLevel" y cómo su nombre sugiere sirve para calcular los valores del nivel actual, recibe un parametro del tipo "integer" que sirve para indicar el numero de nivel a calcular, esta regresa un valor del tipo "PValues"

La siguiente función es "DrawPascalTriangle" que cómo su nombre lo indica sirve para dibujar el triangulo. Recibe un parámetro del tipo "integer" que sirve para indicar el numero de niveles que el triangulo tendrá. Para hacerlo, simplemente recorre de uno al número de niveles y a su vez por cada nivel, recorrerá todos los valores que el resultado de "CalculateLevel" contenga.

SI habrás notado, el código en C y el código en Pascal son bastante diferentes al de Python; esto se debe a un par de razones. La primera de ellas es que el código en Python está administrado por el propio interprete, en el caso de C y Pascal; en estos ejemplos, no es así ya que usamos punteros.

Y bien por ahora es todo, cómo de costumbre, puedes descargar el código de mi dropbox para que lo pruebes o en el código hay un botón de ejecutar que te llevará a la versión en línea (donde se puede ejecutar). Pero antes debo de hacer énfasis en cómo es que pascal maneja el tipo "integer". Por default cuando compilamos código, se usaran 16 bits para los enteros, cómo estos tienen signo, los valores permitidos estarán en el rango de -32,768 a 32,767 (por eso está limitado a 18 niveles en el compilador online). Podemos cambiar las directivas para que se usen 32 bits, pero eso será en otro post. Por ahora recomiendo la versión en línea.

Los leo luego.

No hay comentarios. :

Publicar un comentario