Archivo

Entradas Etiquetadas ‘float’

Utilizar float con sprintf() y derivados en Arduino

Miércoles, 7 de Diciembre de 2011 Gaspar Fernández 2 comentarios

globo-de-aire
El objetivo de la plataforma Arduino es que los programas sean pequeños, ya que tienen que caber en pocos Kbs. Un pequeño problema que tenemos con eso es que muchas bibliotecas no están implementadas completamente, sino que las encontramos en su versión light, en las que implementan sólo las funcionalidades más normales.
Un ejemplo de ello son los comandos printf() y scanf() cuya funcionalidad no cubre los valores de punto flotante (float, double), y que si hacemos el siguiente programa:

1
2
3
4
5
6
7
8
9
10
11
12
void setup()
{
  Serial.begin(9600);
}

void loop()
{
  char buffer[30];
  sprintf (buffer, "Valor: %f\n", 25.4);
  Serial.println(buffer);
  delay(1000);
}

Veremos constantemente:


Valor: ?
Valor: ?
Valor: ?

Y no muestra el valor deseado. Eso sí, ocupa 4.29Kb

La clave para la solución

El tema está en que el compilador no enlaza con las bibliotecas buenas, sino que enlaza con las ligeras. Por lo que tenemos que hacer que éste enlace con las bibliotecas correctas añadiendo el siguiente parámetro al compilador avr-gcc:

-Wl,-u,vfprintf -lprintf_flt

Haciendo el cambio para SConstruct

Si para compilar tus proyectos de Arduino utilizas este método, tienes que modificar una línea en el archivo SConstruct… pongo parte del contexto, cerca de la línea 170, donde pone:

1
2
3
4
5
6
envArduino.Append(BUILDERS = {'Processing':Builder(action = fnProcessing,
        suffix = '.cpp', src_suffix = '.pde')})
envArduino.Append(BUILDERS={'Elf':Builder(action=AVR_BIN_PREFIX+'gcc '+
        '-mmcu=%s -Os -Wl,--gc-sections -o $TARGET $SOURCES -lm'%MCU)})
envArduino.Append(BUILDERS={'Hex':Builder(action=AVR_BIN_PREFIX+'objcopy '+
        '-O ihex -R .eeprom $SOURCES $TARGET')})

Debemos escribir:

1
2
3
4
5
6
envArduino.Append(BUILDERS = {'Processing':Builder(action = fnProcessing,
        suffix = '.cpp', src_suffix = '.pde')})
envArduino.Append(BUILDERS={'Elf':Builder(action=AVR_BIN_PREFIX+'gcc '+
        '-mmcu=%s -Os -Wl,--gc-sections -o $TARGET $SOURCES-Wl, -u,vfprintf -lprintf_flt -lm'%MCU)})
envArduino.Append(BUILDERS={'Hex':Builder(action=AVR_BIN_PREFIX+'objcopy '+
        '-O ihex -R .eeprom $SOURCES $TARGET')})

Con esta modificación, el programa en Arduino (el binario ocupa 5.8Kb) debe dar:

Valor: 25.400000
Valor: 25.400000
Valor: 25.400000

Más información

La IDE de Arduino hace las llamadas al compilador desde su código fuente en lugar de utilizar un Makefile, por ejemplo, por lo que para compilar incluyendo las instrucciones del linkador en el código fuente del IDE. El problema de esto es que no hay una forma fácil de conmutar el linkado con printf_flt y printf_min (que es como se llama la versión light).

Con scanf() también se puede

scanf() también permite la inclusión de la biblioteca que es capaz de leer floats. La instrucción que hay que escribir es:

-Wl,-u,vfscanf -lscanf_flt

Foto: Eric Lim Photography (Flickr) compartido con CC-by a 30/11/2011

[Arduino] Utilizando la memoria Flash en lugar de la SRAM para constantes

Viernes, 2 de Diciembre de 2011 Gaspar Fernández Sin comentarios

temp_ardublogOtra cosa no, pero los Arduino no son conocidos por su gran memoria RAM, y es que, por ejemplo en la serie Diecimila, con el Atmega168 tenemos 1Kb de RAM, con el Atmega328, hay 2Kb de RAM, aunque puede que para algunos de nuestros programas nos quedemos un poco cortos.

Una gran ayuda para esto puede ser utilizar las constantes que cree nuestro programa, en forma numérica de tabla de valores constante, o de cadena de caracteres, por ejemplo, para enviar mensajes predeterminados por el Serial, decir el nombre de la aplicación, la versión, etc.

PROGMEM

Será una macro creada para almacenar datos en espacio de programa. El programa no ocupará más, y tendremos más memoria libre para utilizar y reservar a nuestro antojo.

Antes de utilizar PROGMEM, debemos hacer

1
#include <avr/pgmspace.h>

y así poder tener acceso a todas las funciones adicionales que nos proporciona esta biblioteca.

Viendo la memoria libre

Hay una función que encontré aquí, un poco chapucera, pero eficiente (en la web hay mejores funciones, pero esta es la primera que encontramos), y que calcula el espacio que queda en la memoria (en bytes):

1
2
3
4
5
6
7
8
9
10
11
12
13
// this function will return the number of bytes currently free in RAM
// written by David A. Mellis
// based on code by Rob Faludi http://www.faludi.com
int availableMemory() {
  int size = 1024; // Use 2048 with ATmega328
  byte *buf;

  while ((buf = (byte *) malloc(--size)) == NULL);

  free(buf);

  return size;
}

Con esta función podemos ver la memoria que nos queda:

PROGMEM CON NÚMEROS (int, float, char, byte, unsingeds…)

Para probarlo, lo mejor es ver una demostración (no he incluido la función availableMemory(), copiad y pegad de arriba):

1
2
3
4
5
6
7
8
9
10
11
12
void setup()
{
  Serial.begin(9600);
}

PROGMEM int numero=25;
void loop()
{
  Serial.println(numero, DEC);
  Serial.println(availableMemory(), DEC);
  delay(1000);
}

Podemos ver cómo numero está declarado como PROGMEM int, bien, eliminemos el PROGMEM y vemos qué hace, ¡tenemos 2 bytes menos libres! Aquí demostramos que de verdad no estamos utilizando la RAM.

Arrays de números

Ahora viene lo bueno, no hacemos nada si sólo almacenamos en Flash valores, por separado, lo interesante es poder almacenar arrays con lo que tendremos muchas más posibilidades.
Por ejemplo, podemos hacer:

1
2
3
4
5
6
7
8
9
10
11
12
PROGMEM int numeros[]={10, 29, 38, 47, 56, 64, 73, 82, 91, 0};

void loop()
{
  static int pos=0;
  Serial.println(pgm_read_word(&numeros[pos++]));
  Serial.println(availableMemory(), DEC);
  if (pos==10)
    pos=0;

  delay(1000);
}

Cada segundo mostrará por pantalla un número del array de enteros, y todos estarán en Flash, el coste de eso será de unos 100bytes más en el binario que, por tanto también irá a Flash, además de algunos ciclos de procesador; aunque en este caso, importa más la memoria.

He utilizado pgm_read_word() porque el array es de enteros (2 bytes = 1 word), si nuestra variable fuera de 1 byte (char, byte) se podrá utilizar pgm_read_byte() y si la variable es de 4 bytes (long) podremos utilizar pgm_read_dword(), para variables tipo float tenemos de igual manera pgm_read_float().

Cadenas de caracteres sin buffer

Para escribir cadenas de caracteres, lo mejor es utilizar un buffer (pero ya estamos gastando memoria), por tanto vamos a hacer un ejemplo para imprimir por el Serial sin necesidad de utilizar buffer:

1
2
3
4
5
6
7
8
9
10
11
12
13
char mens[] PROGMEM = "Hola mundo cruel y despiadado";

void loop()
{
  char *mem=mens;

  while (pgm_read_byte(mem) != 0x00) /* Comparamos con \0, un terminador */
    Serial.print(pgm_read_byte(mem++));
  Serial.println();

  Serial.println(availableMemory());
  delay(1000);
}

Podemos hacer el mensaje más largo, que seguimos consumiendo la misma cantidad de memorial. Para imprimir por el Serial con esta técnica podemos crear una función (printpgm()):

1
2
3
4
5
6
7
8
void printpgm(char *texto)
{
  char *mem=texto;

  while (pgm_read_byte(mem) != 0x00) /* Comparamos con \0, un terminador */
    Serial.print(pgm_read_byte(mem++));
  Serial.println();
}

o como comentan aquí, modificar la clase HardwareSerial para incluir un método que imprima cadenas de caracteres procedentes de la memoria de programa.
Y creando estas funciones nos llevamos alguna que otra sorpresa en memoria libre (aunque pequeña).

Bailando con bits: Trabajando a nivel de bit II

Miércoles, 23 de Junio de 2010 Gaspar Fernández Sin comentarios

Hace unos días empecé con la serie Bailando con Bits (aunque llevaba escrito varios meses) trata de formas para trabajar a nivel de bit desde C.

Hoy voy a proponer otra forma, quizás menos intuitiva que la anterior, pero diferente. Esta vez no utilizaremos un registro enorme ni nada parecido, utilizaremos un mismo número entero para hacer el ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>

#define PESOBIT(bpos) 1<<bpos
#define COGEBIT(var,bpos) (var & PESOBIT(bpos))?1:0
#define PONE_1(var,bpos) var | PESOBIT(bpos)
#define PONE_0(var,bpos) var & ~(PESOBIT(bpos))
#define CAMBIA(var,bpos) var ^ PESOBIT(bpos)

int main()
{
  int numero;
  int i;

  numero=63;
  printf ("Numero: %d\n", numero);


  for (i=31; i>=0; i--)
    printf("%4d", i);

  printf("\n");

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\n");

  numero=PONE_1(numero, 17);
  numero=PONE_0(numero, 3);
  numero=CAMBIA(numero, 20);
  numero=CAMBIA(numero, 5);

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\nNúmero: %d\n", numero);

}

Ahora usamos varias macros que harán operaciones de bit con la variable a analizar (están definidas en la parte de arriba), tenemos PESOBIT, COGEBIT, PONE_1, PONE_0 y CAMBIA:

  • PESOBIT: Nos dice cuánto vale un bit con valor 1 en la posición especificada, por ejemplo en la posición 0 (LSB) vale 1, en la posición 1, vale 2, en la posición 3, vale 4, en la posición 4, vale 8…
  • COGEBIT: Nos dice si el bit en la posición bpos de la variable var vale 0 ó 1
  • PONE_1: Pone un 1 en el bit bpos de la variable var
  • PONE_0: Pone un 0 en el bit bpos de la variable var
  • CAMBIA: Cambia el valor (de 0 a 1 y viceversa) del bit en la posición bpos de la variable var

Como vemos en el ejemplo si queremos poner a 1 el bit 5 de numero, tendremos que hacer numero=PONE_1(numero,5), aunque en el siguiente ejemplo veremos cómo simplificar todo eso.

Con este ejemplo podemos jugar con los bits de números enteros. Pero, y si queremos utilizar otro tipo de variables? (aunque estamos limitados a 32bits) También tenemos este ejemplo con un tipo float:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>

#define PESOBIT(bpos) 1<<bpos
#define COGEBIT(var,bpos) (*(unsigned*)&var & PESOBIT(bpos))?1:0
#define PONE_1(var,bpos) *(unsigned*)&var |= PESOBIT(bpos)
#define PONE_0(var,bpos) *(unsigned*)&var &= ~(PESOBIT(bpos))
#define CAMBIA(var,bpos) *(unsigned*)&var ^= PESOBIT(bpos)

int main()
{
  float numero;

  int i;

  numero=63.2317;
  printf ("Numero: %f\n", numero);


  for (i=31; i>=0; i--)
    printf("%4d", i);

  printf("\n");

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\n");

  PONE_1(numero, 5);
  PONE_0(numero, 3);
  CAMBIA(numero, 20);
  CAMBIA(numero, 5);
  CAMBIA(numero, 31);

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\n");

  printf("\nNúmero: %f\n", numero);

}

En este caso no tenemos que hacer una variable igual a PONE_1(variable, 5) para poner su bit a 1; simplemente con PONE_1(variable, 5) nos vale. En este ejemplo podemos ver una variable de tipo float desglosada en bits y podemos modificar a nivel de bit para obtener otros valores. (No le veo mucha utilidad a esto, pero bueno).

Post dedicado a algm, por su comentario en el artículo Bailando con bits anterior.

Visita otras webs de la red