Archivo

Entradas Etiquetadas ‘buffer’

[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).

Dialogando con HardwareSerial y SoftwareSerial más fácil

Lunes, 29 de Agosto de 2011 Gaspar Fernández 2 comentarios

A la hora de dialogar con los Serials en Arduino, durante estos días he desarrollado funciones para leer cadenas completas de texto desde el Serial y para escribir con la sintaxis de printf(), ya que esto es mucho más fácil cuando se trata de formatear texto.

Bien, ahora se trata de unirlo todo y de dar soporte a cualquier Serial, ya sea HardwareSerial o SoftwareSerial sin complicarnos mucho la vida, con la posibilidad de cambiar esta entrada/salida en cualquier momento y así hacer nuestro programa más flexible.

Para ello he creado la biblioteca SerialExt:
SerialExt.h

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
#ifndef SERIALEXT_H
#define SERIALEXT_H

/* Quitar comentario si no vamos a usar SoftwareSerial, así
 reducimos un poco el tamaño del ejecutable. */

/* #define SEXT_NOSOFTWARESERIAL */

#include <WProgram.h>
#include <stdio.h>
#include <stdarg.h>

/* Si no vamos a usar el Software Serial, no creamos soporte para el */
#ifndef SEXT_NOSOFTWARESERIAL
#include <dynmem.h>
#include <SoftwareSerial.h>
#endif

class SerialExt
{
public:
  ~SerialExt();
  SerialExt(const HardwareSerial &serial, long bps=19200);
  #ifndef SEXT_NOSOFTWARESERIAL
  SerialExt(uint8_t rxp=2, uint8_t txp=3, long bps=19200);
  #endif
  int readString (char *str, unsigned size, const char *stop="\0");
  void printf(const char *fmt,...);
  // A veces, podemos necesitar esto desde fuera
  void serialPrint(const char *txt);
private:
  bool serialAvailable();
  int serialRead();
  HardwareSerial *hs;
  #ifndef SEXT_NOSOFTWARESERIAL
  SoftwareSerial *ss;
  #endif
  int timeout;
};

#endif

SerialExt.cpp

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "SerialExt.h"

void SerialExt::serialPrint(const char *txt)
{
  if (hs)
    hs->print(txt);
  #ifndef SEXT_NOSOFTWARESERIAL
  else
    ss->print(txt);
  #endif
}

void SerialExt::printf(const char *fmt,...)
{
  char tmp[128]; // resulting string limited to 128 chars
  va_list args;
  va_start (args, fmt );
  vsnprintf(tmp, 128, fmt, args);
  va_end (args);
  serialPrint(tmp);
}

SerialExt::SerialExt(const HardwareSerial &serial, long bps)
{
  hs=(HardwareSerial*)&serial;

  #ifndef SEXT_NOSOFTWARESERIAL
  ss=NULL;
  #endif
  timeout=3000000/(bps/8);  // ( 1000/(bps/8) ) * 1000 * 3.0 (milisegundos por signo por 1.5)
  // Initialize serial
  hs->begin(bps);
}

#ifndef SEXT_NOSOFTWARESERIAL
SerialExt::SerialExt(uint8_t rxp, uint8_t txp, long bps)
{
  ss=new SoftwareSerial(rxp, txp);
  hs=NULL;
  // Initialize serial
  ss->begin(bps);
}
#endif

SerialExt::~SerialExt()
{
  #ifndef SEXT_NOSOFTWARESERIAL
  if (ss)
    delete ss;
  #endif
}

// Sólo aplicable con HardwareSerial
bool SerialExt::serialAvailable()
{
  if (hs)
    return hs->available();
  else
    return true;
}

int SerialExt::serialRead()
{
  if (hs)
    hs->read();
  #ifndef SEXT_NOSOFTWARESERIAL
  else
    ss->read();
  #endif
}

int SerialExt::readString(char *str, unsigned size, const char *stop)
{
  unsigned i=0;
  char sIn;
  unsigned long m;
  // Queremos que la cadena se rellene hasta size-2 para que en el carácter
  // size-1 (el último) podamos meter el terminador \0
  --size;      
  while (serialAvailable() && i<size)
    {
      sIn=serialRead();
      if (strchr(stop, sIn))
    break;
      str[i++]=sIn;
      m=micros();
      while (!serialAvailable() && micros()<m+timeout);
    }
  str[i++]='\0';
  return i;
}

Esta biblioteca, hace uso de dynmem, para poder utilizar new y delete y crear el objeto SoftwareSerial. Aunque si no vamos a utilizar el Serial por Software, hay una directiva de pre-procesador (#define SEXT_NOSOFTWARESERIAL) que si quitamos el comentario no compilará nada del soporte, con lo que ahorraremos unos 600 bytes en el binario (que a veces, pueden salvarnos la vida).

Para probar la biblioteca, cargamos el siguiente programa de ejemplo:
serial1.h

1
2
3
4
5
6
7
/* En este archivo veremos la configuración de nuestro proyecto */

#define SERIAL_SPEED 19200
/* Led que queremos que parpadee mientras se ejecuta el programa */
#define STATUS_LED   11
/* Led que indica transferencia de datos */
#define BLINK_DELAY  500

serial1.pde:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// serial1
// Hace un eco con el puerto serie del Arduino
// Dialogamos con el Serial a través de la clase SerialExt. Desde aquí podremos dialogar
// con cualquier HardwareSerial o SoftwareSerial

#include "serial1.h"
#include <dynmem.h>
#include <SerialExt.h>
#include <SoftwareSerial.h>

#define MAX_BUFFER 100

// Almacenamos el estado como variable global
int estado=LOW;
// Almacenamos también el número de milisegundos anterior
unsigned long momento_anterior=0;
unsigned long bytes_recibidos=0;
// SerialExt SExt(Serial);

SerialExt *SExt;

void setup()
{
  // Queremos que un led parpadee mientras trabajamos
  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, HIGH);
  delay(1000);
  digitalWrite(STATUS_LED, LOW);
  delay(1000);
  SExt = new SerialExt(Serial);
  digitalWrite(STATUS_LED, HIGH);
}

void loop ()
{  
  int recibe;
  unsigned long momento_actual=millis();
  char buf[MAX_BUFFER];
// No bloqueante, si hay algo para leer entramos, si no, no.
  if(Serial.available())
    {
      SExt->readString(buf, MAX_BUFFER);
      // Escribimos el buffer completo
      SExt->printf("Texto recibido: %s\n", buf);
    }
  // No usamos delay para el parpadeo porque nos entorpece la comunicación con el serial
  if (momento_actual-momento_anterior>=BLINK_DELAY)
    {
      // Cambiamos el estado siguiente. Si era HIGH (1) ahora será
      // LOW (0). He leído en algún lado que el valor de HIGH no
      // siempre es 1; pero en este caso sí es así.
      estado=!estado;
      // Escribimos el estado actual del led
      digitalWrite(STATUS_LED, estado);
      // Establecemos el momento anterior como actual.
      momento_anterior=momento_actual;
    }
}

Como vemos, SerialExt se encarga de hacer Serial.begin() y todo (tampoco es que sea gran cosa, pero nos ahorra una línea); en este ejemplo podemos ver cómo podemos intercambiar líneas completas de texto con Arduino a través del puerto serie.

Recibiendo cadenas de texto completas con Arduino por USB (I)

Jueves, 18 de Agosto de 2011 Gaspar Fernández Sin comentarios

Uno de los problemas de trabajar con Arduino con el puerto serie es la recepción de cadenas de caracteres. De serie, con las bibliotecas disponibles podemos leer:

  • Caracteres de uno en uno
  • Valores numéricos tipo int

Otro tipo de entradas es fácil de leer con un poco de esfuerzo, por eso se me ocurrió crear una función que lea un chorro de caracteres desde el Serial, y lo almacene en un array de char (una cadena de caracteres de toda la vida). Esta función leerá carácter a carácter todo lo que venga del Serial y lo almacenará en la cadena. La función terminará cuando se hayan leído todos los caracteres o cuando se haya alcanzado el tamaño del buffer. Un pequeño ejemplo completo aquí:

serial1.pde:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// serial1
// Hace un eco con el puerto serie del Arduino, leyendo una cadena completa

#include "serial.h"
#include <dynmem.h>

#define MAX_BUFFER 100

// Almacenamos el estado como variable global
int estado=LOW;
int estado_transf=LOW;
// Almacenamos también el número de milisegundos anterior
unsigned long momento_anterior=0;
unsigned long bytes_recibidos=0;

void setup()
{
  Serial.begin(SERIAL_SPEED);
  // Queremos que un led parpadee mientras trabajamos
  pinMode(STATUS_LED, OUTPUT);
  // Queremos salida por el led de transferencia
  pinMode(TRANSF_LED, OUTPUT);
}

int serialGetString(char *string, int max)
{
  unsigned i=0;
  char sIn;
  // Queremos que la cadena se rellene hasta max-2 para que en el carácter
  // max-1 (el último) podamos meter el terminador \0
  --max;       
  while (Serial.available() && i<max)
    {
      sIn=Serial.read();
      string[i++]=sIn;
      // La recepción tiene una latencia, se produce a través de una interrupción, que a lo mejor se ejecuta
      // un poco después del Serial.available() por lo que el dato no entraría, por eso hacemos una pequeña espera
      delayMicroseconds(500);
    }
  string[i++]='\0';
  return i;
}

void loop ()
{  
  int recibe;
  unsigned long momento_actual=millis();
  char buf[MAX_BUFFER];
// No bloqueante, si hay algo para leer entramos, si no, no.
  if(Serial.available())
    {
      serialGetString(buf, MAX_BUFFER);
      // Escribimos el buffer completo
      Serial.println((char*)buf);
    }
  // No usamos delay para el parpadeo porque nos entorpece la comunicación con el serial
  if (momento_actual-momento_anterior>=BLINK_DELAY)
    {
      // Cambiamos el estado siguiente. Si era HIGH (1) ahora será
      // LOW (0). He leído en algún lado que el valor de HIGH no
      // siempre es 1; pero en este caso sí es así.
      estado=!estado;
      // Escribimos el estado actual del led
      digitalWrite(STATUS_LED, estado);
      // Establecemos el momento anterior como actual.
      momento_anterior=momento_actual;
    }
}

Antes de probarlo, tenemos que tener en cuenta lo siguiente:

  • La recepción por puerto serie se hace a través de interrupciones, cada vez que llega algo, y tarda un tiempo en meterse la información en el buffer de entrada de Serial por lo que a veces hay un pequeño retraso para ver la información. (por eso el delayMicroseconds())
  • La información recibida se guarda en un ring buffer, o buffer circular, y su tamaño típico es de 128bytes, por lo que si escribimos muy rápidamente por el puerto serie (que podemos), el Arduino llena el buffer muy rápido, y si no sacamos la información a tiempo podemos tener problemas. Lo ideal es no enviar muchos datos juntos, porque las interrupciones siempre tendrán prioridad, aunque también podemos hacer el buffer un poco más grande (sin pasarnos, no sea que nos quedemos sin memoria). Una velocidad segura son 19200 bps

Este ejemplo, dejará un led parpadeando mientras nos permite recibir cadenas completas desde el Serial, esto nos permitirá algo importante: parsear los datos entrantes.
La función serialGetString() funciona de forma parecida a fgets(), nosotros le pasamos el buffer donde queremos almacenar la información y el tamaño de dicho buffer, pararemos de recibir cuando hayamos terminado de recibir información o cuando el buffer se haya llenado.

Una modificación de serialGetString que funciona muy bien es la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int serialGetString2(char *string, int max)
{
  unsigned i=0;
  char sIn;
  unsigned long m;
  // Queremos que la cadena se rellene hasta max-2 para que en el carácter
  // max-1 (el último) podamos meter el terminador \0
  --max;       
  while (Serial.available() && i<max)
    {
      sIn=Serial.read();
      string[i++]=sIn;
      m=millis();
      while (!Serial.available() || millis()>=m+1);
    }
  string[i++]='\0';
  return i;
}

Aquí no hacemos la espera con delay, en su lugar hacemos un bucle esperando que haya datos en el Serial, aunque con un timeout de 1ms (millis()>=m+1); es decir cuando pase el milisegundo dejaremos de esperar. Tenemos que tener cuidado con esto, y es que si la transferencia es demasiado lenta, por ejemplo 4800bps (transmitiremos 600 símbolo de 8bit en un segundo, por lo que un símbolo llegará al buffer en 1.66ms, y si introducimos este timeout de 1ms lo más seguro que no se reciban cadenas completas de esta forma).

También es curiosa la forma de introducir el timeout y es que en 50 días más o menos, el reloj que controla millis() en Arduino, se desbordará, lo que es parecido a un pequeño efecto 2000, pero en este caso, un efecto 50 días. Por eso, tanto m como millis() se desbordarán a la vez, es decir, si m está a punto de desbordarse, m+1 será 0 y millis(), que estaría también a punto de desbordarse, cuando avance 1 será también 0.

Fibonacci recursivo en C [ intentemos no repetir operaciones! ]

Lunes, 21 de Junio de 2010 Gaspar Fernández Sin comentarios

fibo
Es un ejercicio muy típico cuando se está aprendiendo con funciones recursivas es la sucesión de Fibonacci, aquella en la que F(n)=F(n-1)+F(n-2).
Aunque esta técnica podemos (y deberíamos) utilizarla para una gran cantidad de algoritmos. A veces es necesario sacrificar un poco la memoria del sistema (sin pasarnos) para agilizar y hacer más rápido el programa. Debemos adoptar una solución que beneficie al usuario, no es plan de dejarlo sin memoria, pero tampoco es plan de que la solución se eternice.

Una primera implementación puede ser la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int fibonacci_simple(int n)
{
  /* La función normal, recursiva */
  if (n>2)
    return fibonacci_simple(n-1) + fibonacci_simple(n-2);
  /* Este es el caso más común,  */
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)       
    return 1;
  else if (n==0)
    return 0;
  else
    return -1;          /* Error */
}

Si creamos un programa de ejemplo como este:

1
2
3
4
5
6
7
8
9
10
int main(int argc, char *argv[])
{
    unsigned int numero;

    /* Generamos Fibonaccis */
    for (numero=0; numero<48; numero++)
      printf("%u\n", fibonacci_simple(numero));

    return 0;
}

Podemos observar que para generar la secuencia de 48 números (he escogido este valor, porque a partir de ahí no tenemos suficiente de 32bits para almacenar el valor obtenido), tardamos varios minutos, en concreto, en mi equipo (Pentium M 1.73MHz), echó algo más de 10 minutos. ¡ 10 minutos ! Es un buen rato para lo que ha hecho.
Si lo estudiamos detenidamente (en negrita pongo las veces que llamamos a fibonacci_simple() sin necesidad):

  • fibonacci_simple(0)
  • fibonacci_simple(1)
  • fibonacci_simple(2)
  • fibonacci_simple(3) (que llama a fibonacci_simple(2) y fibonacci_simple(1) )
  • fibonacci_simple(4) (que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) )
  • fibonacci_simple(5) (que llama a fibonacci_simple(4) ( que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) ) y a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) )

Yo creo que con esto nos hacemos una idea de lo que quiero decir, se repiten demasiadas operaciones, seguro que hay alguna forma de solucionar esto.

Propongo lo siguiente:

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
43
44
45
46
47
48
49
50
51
52
53
54
int fibonacci_buffer(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0; /* Tamaño de buffer */
  int n3;           /* n-2 */
  int i;            /* Recorre buffers */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      for (i=0; i<n3; i++)
        fbuffer[i]=0;   /* Ponemos buffer a 0 */

/*    printf("Creo buffer: %d -- %d\n", n3*sizeof(int), errno); */

      bufsize=n3;
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
/*    printf("Amplio buffer\n"); */

      for (i=bufsize; i<n3; i++) /* Ponemos a 0 la memoria nueva */
        fbuffer[i]=0;

      bufsize=n3;
    }
/*       printf("-%d--%d\n", n, fbuffer[n3-1]); */
      if (fbuffer[n3-1]!=0) /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      //      printf("He generado con éxito fbuffer[%d]\n", n3-1);
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Es algo más enrevesado, ya que usamos variables estáticas (podríamos hacer variables globales, con cuidado), así evitamos que la información que vamos almacenando se pierda entre ejecuciones de la función; por otra parte, utilizamos malloc() y realloc() para reservar sólo la memoria que vamos necesitando.
Por otra parte para reducir la memoria utilizada, hacemos que fibonacci(3) se almacene en la posición 0 del buffer (porque el de 2, 1 y 0, ya nos los sabemos); por otra parte… la variable n3 vale n-2; esto es simplemente porque si tenemos que calcular fibonacci(5), y sabemos que fibonacci(3) está almacenado en fbuffer[0], fibonacci(4) estará en fbuffer[1] y fibonacci(5) estará en fbuffer[2]; es decir tenemos que almacenar 3 valores de fibonacci (5-2=3).
Por otra parte, siempre que reservamos memoria ponemos los nuevos espacios a 0, ya que comprobamos que no hay ningún valor almacenado si éstos son 0.

Con el almacén de los números conseguimos que el esquema anterior se quede en:

  • fibonacci_buffer(0)
  • fibonacci_buffer(1)
  • fibonacci_buffer(2)
  • fibonacci_buffer(3) (que llama a fibonacci_buffer(2) y fibonacci_buffer(1)
  • fibonacci_buffer(4) (que llama a fibonacci_buffer(3) ( que saca el valor del buffer ) y a fibonacci_simple(2) )
  • fibonacci_buffer(5) (que llama a fibonacci_buffer(4) (que saca el valor del buffer) y a fibonacci_buffer(3) (que saca el valor del buffer)

Con este algoritmo consigo generar todos los números en 4ms.

¿Será posible simplificarlo un poco más?
Sólo hay que ver cómo se hace la generación de valores de forma recursiva. Si lo pensamos bien, cuando llamamos a fibonacci_buffer(5) la primera vez y lo queremos almacenar en fbuffer[2] llamaremos a fibonacci_buffer(4) (que se almacenará en fbuffer[1], y éste llamará a fibonacci_buffer(3) (que se almacenará en fbuffer[0]); es decir, lo primero que se generará será fbuffer[0], luego fbuffer[1] y por último fbuffer[2]… ¡ vamos por orden ! Sabiendo esto, nos podremos ahorrar el código de poner a 0 los elementos del buffer sabiendo cuál es el último elemento generado.

Para los incrédulos, podemos eliminar el comentario de la línea:

1
//    printf("He generado con éxito fbuffer[%d]\n", n3-1);

y ejecutar… veremos con qué orden se van generando los números.

Bien, intentemos eliminar las puestas a 0 de los elementos del buffer.

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
43
44
45
46
int fibonacci_buffer2(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0;         /* Tamaño de buffer */
  static int bpos=0;            /* Recorre buffers */
  int n3;           /* n-2 */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      bufsize=n3;   /* Mantenemos bufsize por si redimensionamos el buffer */
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
      bufsize=n3;      
    }

      /* Utilizamos bpos para saber cuál es el último elemento del buffer */
      /* que hemos rellenado. */
      if (bpos>=n3)         /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      bpos=n3;
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Con este algoritmo, consigo generar la secuencia en 2ms. Bastante más rápido que con el primer algoritmo, parece que la puesta a 0 del buffer llevaba su tiempo.

Para concluir, quiero proponer otra solución basada en arrays, supongo que muchos estaréis más familiarizados con estos:

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
int fibonacci_buffer3(int n)
{
  static int fbuffer[MAX_FIB];
  static int bufsize=0;         /* Tamaño de buffer */
  int n3;           /* n-3 */

  if (n>2)
    {
      n3=n-3;

      if (bufsize>n3)           /* Si se encuentra el fibonacci del número que buscaos  */
    return fbuffer[n3]; /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2); /* y lo guardamos */
      bufsize=n3+1;
      /* Al ser fibonacci recursivo, y asignar el tamaño después de la generación */
      /* el buffer se rellenará por orden, es decir, el primero que se rellenará */
      /* será el [0], luego el [1], luego el [2]... */
      return fbuffer[n3];
    }

    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;

  return -1;            /* Error, seguramente me hayan pasado un n negativo*/
}

Esta solución es muy parecida a las anteriores, aunque nos quitamos del medio la reserva de memoria, reducimos las líneas de código, ya que no vamos a alojar tantos valores diferentes… para esta secuencia serían unos 48 * 4 (sizeof(int)) = 192 bytes, y bufsize la utilizamos para ver cuál es el último elemento del buffer con información válida (como vimos antes, se rellenarán por orden, así que sólo tendríamos que llevar la cuenta). Las variables estáticas, como dije, podemos hacerlas globales (y quitarles el static, aunque queda más elegante como static).

Con esta solución tardo unos 5ms de media en generar la secuencia de números.

Foto: Eustaquio Santimano (Flickr)

Buffer de teclado en linux

Sábado, 23 de Mayo de 2009 blakeyed 1 comentario

A veces, mientras se está desarrollando un pequeño programa en C en el que hay entradas del usuario por teclado, hay veces que parece que se pulsan teclas solas, esto es debido a una entrada de teclado anterior que no ha llegado a volcarse entera en nuestras variables.
En principio tenemos fflush(), es una función para el volcado de buffer de escritura, escritura de ficheros o escritura en la pantalla; pero bueno, funciona cuando hacemos fflush(stdin) y compilamos en Windows, pero no en Linux… tenemos un par de soluciones (aunque seguro que se nos ocurren muchas más):

  1. __fpurge() - Aunque no es una función estandar, y en alguna ocasión me ha dado algún problema (no ha hecho su trabajo como debía).
  2. La otra solución, parece un poco más rudimentaria, pero no me ha dejado tirado:
1
2
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
Categories: C/C++, Linux Tags: , , , ,

Visita otras webs de la red