Vamos a programar #94 - Formateando números en arduino - Camino a speedometer version final.
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.
dtostrf(Speed , 5 , 2 , BuffVelocity);
//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);
}
Vamos a programar #92 - El triangulo de Pascal (ver. C)
Hola de nuevo a todos, el día de hoy vamos a ver cómo hacer un programa en C que calcule el triangulo de Pascal.
Cómo podrás recordar, hace unos día publiqué una version en Python; y tal cómo lo prometí justo antes de acabar el 2020, les traigo la versión en C. Cómo la explicación de que es el triangulo de Pascal se incluye en ese post (además de cómo hacer los cálculos a mano), simplemente pasaremos al código fuente. Al igual que el código en Python, vamos a calcular el triangulo de Pascal de manera recursiva y si surgen dudas las resolveremos en los próximos post
El código en C que sirve para calcular el triangulo de Pascal es el siguiente ():
// Prueba del triangulo de pascal en C
#include <stdio.h>
#include <stdlib.h>
int *CalculateLevel(int Level)
{
int *Result;
Result = (int *)malloc(Level * sizeof(int));
if (Level == 0)
*Result = 1;
if (Level == 1)
{
*Result = 1;
*(Result + 1) = 1;
}
if (Level > 1)
{
int *ResultP = CalculateLevel(Level - 1);
for (int i = 0; i < Level; i++)
{
if (i == 0 || i == Level - 1)
*(Result + i) = 1;
else
*(Result + i) = *(ResultP + i) + *(ResultP + i - 1);
}
}
return(Result);
}
void DrawPascalTriangle(int Number)
{
if (Number > 20)
{
printf("Solo se admiten 20 niveles");
}
else
{
for (int i = 0; i < (Number + 1); i++)
{
int *Result = CalculateLevel(i);
for (int j = 0; j < i; j++)
{
printf( " %5d ", *(Result + j));
}
printf("\n");
}
}
}
int main()
{
int Levels;
printf("Numero de niveles\n");
scanf("%d", &Levels);
DrawPascalTriangle(Levels);
return 0;
}
Vamos a programar #60 - Los números de Fibonacci (Ver. C)
Antes de continuar, vamos a ver que es esta sucesion.
En matemáticas, la sucesión de Fibonacci es la siguiente sucesión infinita de números naturales: 0,1,1,2,3,5,8,13,21,34,55, … La sucesión comienza con los números 0 y 1,2 y a partir de estos, «cada término es la suma de los dos anteriores», es la relación de recurrencia que la define. A los elementos de esta sucesión se les llama números de Fibonacci. Esta sucesión fue descrita en Europa por Leonardo de Pisa, matemático italiano del siglo XIII también conocido como Fibonacci. Tiene numerosas aplicaciones en ciencias de la computación, matemática y teoría de juegos. También aparece en configuraciones biológicas, como por ejemplo en las ramas de los árboles, en la disposición de las hojas en el tallo, en las flores de alcachofas y girasoles, en las inflorescencias del brécol romanesco y en la configuración de las piñas de las coníferas. De igual manera, se encuentra en la estructura espiral del caparazón de algunos moluscos, como el nautilus. Wikipedia/SucesionDeFibonacci.Para ponerlo de manera sencilla, para calcular el valor de la sucesión, debemos de sumar los dos números previos, excepto cuando el valor de n sea 0 o 1, a partir de "n = 2", es cuando debemos de empezar a sumar por lo que tendríamos algo similar a lo siguiente:
El propósito del programa era encontrar una manera de hacerlo de forma secuencial y otra de forma re-cursiva. Partiendo de eso, podemos crear un programa cómo el que sigue:
#include <stdio.h>
long FibonacciNumbersRec(int n)
{
if (n == 0 || n== 1 )
return n;
else
return (FibonacciNumbersRec(n - 1) + FibonacciNumbersRec(n - 2));
}
long FibonacciNumbersIt(int n)
{
long v1 = 0;
long v2 = 1;
long v3, i;
for(i = 1; i <= n; i++)
{
v3 = v1+v2;
v1 = v2;
v2 = v3;
}
return v1;
}
int main()
{
printf("%4d\n", FibonacciNumbersIt(6));
printf("%4d\n", FibonacciNumbersRec(6));
}
El programa consta de dos funciones, una de ellas es "FibonacciNumbersRec" (Rec es por Recursive) y la otra es "FibonacciNumbersIt" (It por Iterative). Para la función "FibonacciNumbersRec", primeramente comprobamos si el parámetro "n" es cualquiera de los números 1 o 0, si es el caso, la función devuelve "n" cómo resultado, si no es el caso anterior, la función se mandará a llamar a si misma con el parámetro "n = n - 1" y al resultado de esto, se le sumará el valor que resulta de llamarse a si misma con parámetro de "n = n -2"; el valor que regresa la función será el resultado de sumar los casos anteriores.
Para evitar la recursividad, podemos aprovechar que la sucesión depende de los dos valores anteriores inmediatos, por lo que podemos hacer todo en un ciclo "for" y usando las variables auxiliares "v1","v2 y "v3". Cómo la sucesión comienza con 1 y con 0, le asignamos esos valores a "v1" y a "v2", el resultado para ese paso dentro de la sucesión, sera la suma "v1" + "v2", los asignamos a "v3" y tomando en cuenta lo mencionado hace un momento, le asignamos a "v1" el valor que tiene "v2" y a "v2" le asignamos el valor de "v3". Todo esto lo hacemos hasta que el iterador llegue al valor del parámetro "n". Finalmente la función regresará cómo resultado el valor de "v1".
Y bien, con el código anterior hemos demostrado que hay más de una forma para llegar al mismo resultado. Muchas veces es más fácil hacer uso de la recursividad, pero si por alguna razón no debemos (o no disponemos, dependiendo del lenguaje) usarla, siempre hay formas de encontrar una solución.
El código, al igual que el del post anterior, puedes copiarlo y probarlo en el compilador online.
Y bien. por ahora es todo, los leo luego.
Vamos a programar #59 - La función de Ackerman (ver. C)
Hace algunos días mientras hablaba con un amigo, me dijo que en la universidad, le habían dejado cómo tarea hacer un programa en C que probara la función de Ackerman.
Pero primero veamos una pequeña definicion sobre lo que es la función de Ackerman:
En teoría de la computación, función de Ackermann es una función matemática recursiva encontrada en 1926 por Wilhelm Ackermann, tiene un crecimiento extremadamente rápido, de interés para la ciencia computacional teórica y la teoría de la computabilidad. Hoy en día, hay una serie de funciones que son llamadas funciones Ackermann. Todas ellas tienen una forma similar a la ley original la función de Ackermann y también tienen un comportamiento de crecimiento similar. Esta función toma dos números naturales como argumentos y devuelve un único número natural. Wikipedia/FuncionDeAckermanY sigue las siguientes reglas:
- n+1 Si m = 0.
- A(m-1, 1) si m > 0 y n = 0.
- A(m - 1, A(m, n - 1)) si m > y n > 0.
Sabiendo lo anterior, podemos crear un programa en C usando el siguiente código:
# include <stdio.h>
int AckermanFunction(int m, int n)
{
if (m == 0)
return (n+1);
else
if (n == 0)
return (AckermanFunction(m - 1, 1));
return(AckermanFunction(m - 1, AckermanFunction(m, n - 1)));
}
int main()
{
int i,j;
for (i = 0; i < 5; i++)
{
for (j = 0; j < 3; j++)
{
printf("%6d", AckermanFunction(i, j));
}
printf("\n");
}
return 0;
}
El código anterior consta de dos partes, la primera es la función llamada "AckermanFunction" que recibe dos parámetros del tipo "int". La función sigue las reglas de la función de Ackerman por lo que si al evaluar, el parámetro "m", este vale cero, la función regresará el valor de "n" mas uno, si no es el caso, la comprobación se hará para el valor de "n = 0", si es el caso, la función se mandará a llamar a si misma y se pasará cómo argumentos a "m" con valor de "m - 1" y "n" con el valor de "n = 1" y regresará el resultado de llamarse a si misma. Finalmente, si "m" y "n" son valores mayores a cero "m > 0", "n > 0", el valor que la función regresará, será el resultante de llamarse a si misma con los parámetros "m-1" para "m" y el resultado de llamarse a si misma con los parámetros "m = m - 1" y "n = n - 1" para "n".
Para poder ver los resultados, puedes copiar el código y usarlo en un compilador de c en linea. y debería de salir un resultado similar al anterior. Puedes modificar el código para ver que pasa "mas allá" de la quinta fila, pero el resultado puede tomar mucho tiempo.
Y bien, por ahora es todo.
Los leo luego.
Vamos a programar #50 - ¿Debo usar las interrupciones de arduino?
En algunos de los post anteriores, vimos código que se encargaba de contar las vueltas que llevaba un motor (usando un sensor de efecto hall y un par de imanes), pero poco después, alguien me contacto en twitter y me dijo que esa no era la manera correcta de hacer la medición, que arduino posee las herramientas necesarias para medir de una manera más precisa. En arduino, es posible asignarle prioridad a uno de los pines, dependiendo de modelo de la placa, al menos dos pines tendrán esta capacidad.
Las interrupciones, se dan cuando se detecta un valor en un pin, siempre y cuando este se haya inicializado con attachinterrupt(), esta es una manera de indicar que uno de los pines tendrá prioridad y cuando su valor cambie (ahorita veremos de que modo), se llevará a cabo cierta acción, esto es independiente a lo que arduino este haciendo.
La función attachinterrupt(), toma 3 argumentos. El primero; es el pin en el cual arduino "vigilará". El segundo; es la función que se ejecutará cuando se detecte un cambio en el pin designado. El tercero, será la forma en que arduino se dará cuenta del cambio.
Para designar el pin, debemos de tomar en cuenta el modelo de arduino, en mi caso, el arduino uno dispone de los pines 2 y 3 (para los modelos Mega y Micro tambien aplica).
La función que se usará cómo segundo parametro, no debe de requerir de ningun parametro y tampoco debería de devolver nigun valor.
Para el modo en que arduino detectará el cambio, disponemos de los siguientes:
- LOW
- CHANGE
- RISING
- FALLING
- HIGH
Para apreciar mejor, veamos el siguiente fragmento de codigo:
#include <LiquidCrystal.h>
const int rs = 12, en = 11, d4 = 9, d5 = 8, d6 = 7, d7 = 6;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
const float Llanta = 1.9572;
//Algunas variables
const int HallSensor = 3;
const int LED = 13;
int HallState = 0;
int Vuelta = 0;
int OldState = 0;
unsigned long OldTime = 0;
float Distancia = 0;
float Speed = 0;
//Incializar todo
void setup() {
Serial.begin(9600);
lcd.begin(16, 2);
lcd.setCursor(0, 0);
lcd.print("@XWork");
pinMode(HallSensor, INPUT);
attachInterrupt(digitalPinToInterrupt(HallSensor), GetVelocity, LOW);
pinMode(LED, OUTPUT);
}
void GetVelocity() {
if (millis()-OldTime > 70) {
Speed=Llanta/((float)(millis()-OldTime)/1000)*3.6;
OldTime=millis();
Distancia=Distancia+Llanta/1000;
}
}
//Bucle principal
void loop() {
Serial.println("Velocidad en m/s");
Serial.println(Speed);
lcd.setCursor(0, 0);
lcd.print("v= Km/h D = Km");
lcd.setCursor(0, 1);
lcd.print(Speed);
lcd.setCursor(8, 1);
lcd.print(Distancia);
if ((millis()-OldTime)>2000){
Speed=0.0;
}
}
Podemos observar que además de la declaración normal del pin (3), justo debajo hacemos la inicialización del pin que se usara para las interrupciones y hacemos uso de la función digitalPinToInterrupt() y le asignamos cómo parámetro el pin que antes habíamos iniciado (el pin 3), luego pasamos cómo parámetro la función GetVelocity(), esta es la que se ejecutará cada vez que se de la interrupción. Finalmente en el ultimo parámetro, le decimos que la interrupción se dará cuando el pin este en LOWEl código anterior es la mejor forma de hacer la cuenta, el código de post anterior cumplía y funcionaba, pero si por alguna razón la rueda girara muy rápido, llegaría el punto en que algunas de las lecturas del sensor del lector serian omitidas. Y bien, por ahora es todo en el próximo post terminaremos con este preyecto.
Los leo luego.
Vamos a programar #48 - Inútil apps #3 -Velocímetro para bicicleta, sensor de efecto Hall.
Cómo algunos sabrán, tengo cierta afición por hacer viajes en bicicleta, pero cómo soy un hombre de números, siempre me ha gustado cuantificar lo que hago (en medida de lo posible), así que decidí llevar la cuenta de la actividad que hacía. Por fortuna, muchos de los teléfonos actuales, tienen aplicaciones que ayudan a llevar el registro (gracias a sensores y al GPS).
Hasta hace poco tenía la idea de hacer una versión propia de un programa que lleve todos esos datos, pero un par de eventos bastante desafortunados, bastaron para pensarlo dos veces. Entonces opté por comprar un velocímetro, lo encargue desde China y tenía un tiempo estimado de entrega de dos meses, pero por sismos y varias cosas, no llegaron (ya va medio año prácticamente, pero aun espero su llegada junto con la de algunos paquetes mas grandes).
Al revisar cómo es que funcionan, noté que la gran mayoría funcionan de la misma manera; se pone un sensor en la tijera que sostiene una llanta y cada vez que ésta da una vuelta, de alguna manera se notifica. En el caso del velocímetro que encargue, en el manual (de internet) decía que se debía de pegar un imán a un rayo de la llanta y el sensor ponerse fijo en la tijera, al revisar más a detalle, averigüe que se trataba de un sensor de efecto Hall.
El sensor de efecto Hall.
Ahora veremos cómo conectar el sensor de efecto hall, para hacerlo, necesitas lo siguiente:El sensor de efecto Hall o simplemente sensor Hall o sonda Hall (denominado según Edwin Herbert Hall) se sirve del efecto Hall para la medición de campos magnéticos o corrientes o para la determinación de la posición en la que está.Si fluye corriente por un sensor Hall y se aproxima a un campo magnético que fluye en dirección vertical al sensor, entonces el sensor crea un voltaje saliente proporcional al producto de la fuerza del campo magnético y de la corriente. Si se conoce el valor de la corriente, entonces se puede calcular la fuerza del campo magnético; si se crea el campo magnético por medio de corriente que circula por una bobina o un conductor, entonces se puede medir el valor de la corriente en el conductor o bobina.Si tanto la fuerza del campo magnético como la corriente son conocidos, entonces se puede usar el sensor Hall como detector de metales. Wikipedia/SensorHall
- Sensor efecto hall.
- Resistencia 1KΩ ~10KΩ.
- Arduino
- Supply (4.5~24V).
- Ground.
- OutPut.
Con lo anterior pasemos al código.
//Pin al que coenctaremos el sensor
const int HallSensor = 3;
//LED para mostrar el cambio en caso de no disponer del monitor serie
const int LED = 13;
//Estado del sensor
int HallSensorVal = 0;
//Número de vuelta
int Lap = 0;
//Estado anterior del sensor
int OldState = 0;
//Inicializar lo necesario
void setup() {
Serial.begin(9600);
pinMode(LED, OUTPUT);
pinMode(HallSensor, INPUT);
}
//Bucle princial
void loop() {
HallSensorVal = digitalRead(HallSensor);
if (OldState != HallSensorVal) {
if (HallSensorVal == LOW) {
digitalWrite(LED, HIGH);
Lap += 1;
Serial.println("Vuelta:");
Serial.println(Lap);
delay(1);
}
else {
digitalWrite(LED, LOW);
}
}
if (Lap > 99)
Lap = 1;
OldState = HallSensorVal;
}
El código anterior, lo único que hace, es verificar cual es el estado del sensor, cuando esta en modo "LOW", prende el LED que viene por default en el pin 13 y a la variable "Lap" le suma 1 (indicando una vuelta). Para probar el código anterior, necesitaras también un par de imanes, en mi caso, los extraje de un quemador de CD viejo, y los pegué en un motor (también extraído del lector de CD).
El resultado:
Con esto podemos probar que el sensor funciona de manera correcta.
Y bien, por ahora es todo, en el siguiente post, veremos cómo medir distancia y velocidad con el mínimo hardware posible. Aun no he decidido si usar las matrices para mostrar los datos o usar una pantalla LCD (cómo la del nokia) o incluso pantalla de siete segmentos. El código lo puedes copiar y pegar para usarlo, en cuanto este completo lo subire a mi dropbox.
Los leo luego.















