0

mi idea es realizar una función que te tome un valor de velocidad entre 0 y 1023 (10 bits) y descomponerla en 2 bytes (16 bits). En donde del bit 15 al 11 serían siempre cero y el bit 10 dependerá de si se quiere una dirección de giro del motor horaria o antihoraria. Por tanto, los otros 10 bits restantes serían los de la velocidad. Es decir, de un valor en 10 bits quiero poner los 8 bits de menos peso en un byte y los otros 2 bits que sean del otro byte, junto con las condiciones anteriores. Un ejemplo sería tal que así:

velocidad = 1000 --> Byte más peso = 00000011 Byte menos peso = 11101000

Esto lo hago porque quiero escribir en dos posiciones de memoria (0x20 y 0x21), en donde el byte de menos peso irá en la posición 0x20 y el de más peso en la 0x21. Por tanto, al ser contiguas las direcciones de memoria lo único que tengo que conseguir es que la función velocidad me devuelva algo tipo :{0x20,Byte_menos_peso,Byte_más_peso}

El problema que tengo es que creo que al ser una declaración local del array, por eso no da bien. Entonces, sin usar malloc, qué cambio debería retocar en este código para lograr obtener el array que busco? Gracias!

uint8_t * velocidad(uint16_t velocidad_0_to_1023, uint8_t direccion, uint8_t *array){ // Se le pasará una velocidad y una dirección: CW=0x04 o CCW=0x00
volatile byte i; // Variables contador
uint8_t *pointer_to_data = &array_velocidad; 
uint8_t array_velocidad[3] = {0};
if(velocidad_0_to_1023 > 1023){
    velocidad_0_to_1023 = 1023; // Por si el usuario pone una velocidad mayor a la permitida
}
uint8_t velocidad_L = (uint8_t) 0x00FF & velocidad_0_to_1023;
uint8_t velocidad_H = (uint8_t) ((0x00FF & (velocidad_0_to_1023 >> 8)) | direccion);
array_velocidad[0] = 0x20;
array_velocidad[1] = velocidad_L;
array_velocidad[2] = velocidad_H;
return pointer_to_data;

}

2 Answers2

2

En primer lugar, la palabra clave volatile se usa en el contexto de hilos, por lo que si no los usas, no va a ser necesaria.

Más adelante, asignas una dirección de memoria de una variable todavía no declarada a otra variable, por lo que solo hay que cambiar el orden de las instrucciones.

Finalmente, devuelves un puntero, lo que es totalmente legal, pero ese puntero apunta a una variable que es local y que al finalizar la llamada a la función dicha variable va a ser destruida.

Lo habitual sería crear el vector en memoria dinámica, pero como no es lo que buscas, la otra solución es no devolver un puntero al vector, sino el vector en sí mismo.

Pero va a ocurrir otro problema: cuando devuelves un dato de un tipo primitivo (o estructuras), lo que devuelves es una copia del dato que se va a destruir al finalizar la función, mientras que si devuelves un vector, que en esencia es lo mismo que devolver una dirección de memoria (o puntero), no se realiza dicha copia.

La solución a esto es envolver el vector en una estructura, que al devolverla sí será copiada antes de ser destruida en la pila.

Con todo, el código quedaría:

struct velocidades
{
    uint8_t array_datos[3];
};

struct velocidades velocidad(uint16_t velocidad_0_to_1023, uint8_t direccion)
{
    struct velocidades envoltura_velocidad;

    if (velocidad_0_to_1023 > 1023) velocidad_0_to_1023 = 1023;

    uint8_t velocidad_L = (uint8_t) 0x00FF & velocidad_0_to_1023;
    uint8_t velocidad_H = (uint8_t) ((0x00FF & (velocidad_0_to_1023 >> 8)) | direccion);

    envoltura_velocidad.array_datos[0] = 0x20;
    envoltura_velocidad.array_datos[1] = velocidad_L;
    envoltura_velocidad.array_datos[2] = velocidad_H;

    return envoltura_velocidad;
}

Fuentes:

https://stackoverflow.com/questions/19042552/returning-a-pointer-of-a-local-variable-c https://stackoverflow.com/questions/4570366/how-to-access-a-local-variable-from-a-different-function-using-pointers/4581061#4581061


Edición

Como preguntas sobre el uso de memoria dinámica, te pongo el código, que hace uso de la función malloc para reservar memoria montón (o heap), aunque no era lo que buscabas en un principio.

uint8_t * velocidad(uint16_t velocidad_0_to_1023, uint8_t direccion)
{
    uint8_t * velocidades = (uint8_t *) malloc(3);

    if (velocidad_0_to_1023 > 1023) velocidad_0_to_1023 = 1023;

    uint8_t velocidad_L = (uint8_t) 0x00FF & velocidad_0_to_1023;
    uint8_t velocidad_H = (uint8_t) ((0x00FF & (velocidad_0_to_1023 >> 8)) | direccion);

    velocidades[0] = 0x20;
    velocidades[1] = velocidad_L;
    velocidades[2] = velocidad_H;

    return velocidades;
}

Tienes que tener en cuenta que el dato devuelto no será destruido nunca durante la ejecución del programa, porque como se ha reservado explícitamente, se debe liberar explícitamente cuando ya no necesites usar más el vector.

int main(int argc, char * argv[]) {
    uint16_t velocidad_0_to_1023;
    uint8_t direccion;
    // Obtenemos los datos que vamos a pasar a la función
    uint8_t * velocidades = velocidad(velocidad_0_to_1023, direccion);
    // Trabajas con el vector que está en memoria montón
    free(velocidades);
    return 0;
}

En el ejemplo, se obtiene un vector utilizando memoria dinámica al llamar a la función velocidad, lo utilizas para cálculos, lo imprimes, lo que quieras hacer; y más tarde lo liberas mediante la función free.


Tercera versión

Como sugiere MrDave1999, una tercera opción es pasar la dirección de un vector ya existente a la función, de tal manera que puedes no usar memoria dinámica y no hace falta envolver el vector en una estructura.

void velocidad(uint16_t velocidad_0_to_1023, uint8_t direccion, uint8_t * velocidades)
{
    if (velocidad_0_to_1023 > 1023) velocidad_0_to_1023 = 1023;

    uint8_t velocidad_L = (uint8_t) 0x00FF & velocidad_0_to_1023;
    uint8_t velocidad_H = (uint8_t) ((0x00FF & (velocidad_0_to_1023 >> 8)) | direccion);

    velocidades[0] = 0x20;
    velocidades[1] = velocidad_L;
    velocidades[2] = velocidad_H;
}

int main(int argc, char * argv[]) {
    uint16_t velocidad_0_to_1023;
    uint8_t direccion;
    // Obtenemos los datos que vamos a pasar a la función
    uint8_t destino[3];
    velocidad(velocidad_0_to_1023, direccion, destino);
    // Trabajas con el vector que está en la pila del main
    return 0;
}

Una nota final, a raíz de la última duda planteada como comentario.

Cualquier variable local a una función, es destruida/liberada al terminar esta función. Por tanto, una manera de guardar los datos que se generan en una función es devolver una copia, como se hace cuando invocas return variable, para cualquier variable de tipo primitivo o estructura.

Otra forma es crear la variable fuera, y como en este caso, pasar la dirección de memoria a la función, para que la función la rellene con datos.

Además, creo que tienes una confusión con respecto a crear vectores o variables en general. Y es que al declarar una variable no hace falta inicializarla.

Si declaras lo siguiente:

int x;
int vector[10];

Solo por el mero hecho de declarar una variable de tipo primitivo, como x, se reserva memoria para dicha variable. Y cuando declaras un vector de tamaño fijo, como vector[10], también se reserva espacio suficiente para tantos elementos del tipo que hayas indicado. En ambos casos no hace falta dar valors iniciales a las variables.

Sin embargo, es muy importante que controles las variables y cuando van a recibir información, porque C no inicializa las variables a ningún valor. Es decir, se asigna una zona de memoria para esa variable, y el valor que contiene puede ser cualquier número. Por ejemplo, si otra aplicación usó esa dirección, y al terminar de ejecutarse se te asigna esa misma dirección para alguna variable, tu variable inicialmente puede contener el valor que tenía por el uso de la aplicación interior.

A esto, comunmente, nos solemos referir como valores basura.

Por tanto, al declarar el vector de velocidades en el main, se reserva espacio para 3 datos, y estos contienen valores basura cualesquiera. A continuación se pasa la dirección de memoria donde empieza el vector para que la función rellene con información válida el vector, sobre escribiendo los valores basura.

Finalmente, por esto de los valores basura, es buena práctica dar una valor inicial a tus variables, menos en los casos donde sepas que las vas a iniciar más adelante y no vas a usar su valor entre su declaración y su rellenado.

Eequiis
  • 1,807
  • 4
  • 19
  • Muchas gracias por la respuesta. Detallada y clara. Por curiosidad, cómo sería con memoria dinámica y porqué se usa preferiblemente? Gracias de antemano! – Alex Garcia Jun 16 '20 at 16:37
  • De nada! He añadido una edición con la versión que comentas. – Eequiis Jun 17 '20 at 15:14
  • 2
    @AlexGarcia En tu caso podrías [pasar por puntero](https://es.stackoverflow.com/questions/353484/c%c3%b3mo-funcionan-los-punteros-por-referencia-pasados-como-par%c3%a1metro-de-funciones/353719#353719), así evitas envolver el array en una estructura o usar memoria dinámica. Claro, la función sería definida como `void`. – MrDave1999 Jun 17 '20 at 15:51
  • Entonces, sería como la tercera 'versión' de solución a mi problema dices? He entendido lo de la struct y la memoria dinámica, pero lo de pasar por puntero como quedaría y qué ganas? simplicidad entiendo? Gracias de antemano! – Alex Garcia Jun 17 '20 at 16:15
  • Se refiere a pasar la dirección del vector destino a la función, con lo que la puedes tratar como array, y no te hace falta memoria dinámica. – Eequiis Jun 17 '20 at 16:27
  • @AlexGarcia Me refiero a que declaras el *array* (que en tu caso lo llamaste `array_velocidad`) en el ámbito donde invoques a la función `velocidad` y posteriormente, pasas la dirección base del *array* al parámetro de la función, en este caso, la función `velocidad` debería tener un parámetro (un puntero a `uint8_t `) que reciba dicha dirección. – MrDave1999 Jun 17 '20 at 16:32
  • Al ser muy novatillo en C te planteo mi duda con tu sugerencia. Entiendo que dices de crearte el array_velocidad fuera de la función velocidad en sí? Porque sino no puedes apuntar un puntero a un array local. Pero para crearte el array_velocidad necesitas los valores algo del estilo {0x20, velocidad_L, velocidad_H} en donde velocidad_L y velocidad_H se calculan en la función. Osea digamos que no entiendo bien a qué te refieres, ya que como ves lo que he entendido no resultad razonable.. Gracias! Siento la perseverancia en algo tan básico. – Alex Garcia Jun 17 '20 at 17:16
  • 2
    No te preocupes. El tema de punteros se resiste al principio. Ten en cuenta que un puntero es solo una dirección de memoria. Que exista algo ahí es otra cosa. Los valores que vas a meter en el vector se calculan dentro de la función, en variables locales. Como toda variable local, estas serán destruidas al terminar. Lo único que se hace es meter esos valores en un vector que empieza en la dirección indicada en el parámtero, que, efectivamente, se crea fuera de la función (como vector de tamaño fijo o con memoria dinámica, a elección tuya). – Eequiis Jun 17 '20 at 17:19
  • Entonces, para acabar de aclarar: supón que yo tengo esta función y quiero que array[3] esté el valor de {0x20, velocidad_L, velocidad_H}, cómo se haría lo que sugerís? Si se puede y no es molestia. Gracias de antemano a los dos! void derecha(uint16_t vel_motor1){ velocidad_motor1 = velocidad(vel_motor1, 0x04); TX_UART(1,2,array[3]); – Alex Garcia Jun 17 '20 at 18:39
  • Y si el vector resultante de la función velocidad has de pasarlo como parámetro de una función que se supone que ha de recibir un puntero a un dato uint8_t, has de sustituir la función velocidad para que, envez de devolverte nada (void) te devuelva uint8_t? La función a la que tendría que pasarle sería tipo: void Tx(uint8_t *velocidad). Gracias! – Alex Garcia Jun 19 '20 at 09:06
0

Creo que una solución perfecta sería que tu estructura hiciera uso de los campos de bits, abajo un ejemplo de uso:

struct mybitfields
{
    unsigned short A : 4;
    unsigned short B : 5;
    unsigned short C : 7;
} MiTest;

int main( void );
{
    MiTest.A = 2;     // Binario 10
    MiTest.B = 31;    // Binario 11111
    MiTest.C = 0;     // Binario 0
}

Y en memoria tendríamos:

00000001 11110010
CCCCCCCB BBBBAAAA
Antonio S.F.
  • 1,205
  • 3
  • 9
  • Parece bastante interesante, no lo había planteado nunca así. Lo tendré en cuenta para casos posteriores, ya que parece simple pero útil. – Alex Garcia Jun 17 '20 at 16:13