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;
}.formVigenere{
font: 95% Arial, Helvetica, sans-serif;
max-width: 400px;
margin: 10px auto;
padding: 16px;
background: #eee;
}@media (prefers-color-scheme: dark) {
.formVigenere{
background: #000;
}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 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);
}
}
}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#
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();
}
}
}
El código para 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" |
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
El código en 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); } 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); }
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.
Cumplimos cinco años!!!
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.
Vamos a programar #96 - Guardando configuraciones en la EEPROM de arduino.
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.
Vamos a programar #95 - Arduino y su EEPROM
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)
Vamos a platicar #12 - La luz al final del túnel
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

















