Recibir cadena de caracteres con usart en AVR

#1
Estimados, buenas noches!

Estoy intentando recibir una cadena de caracteres por medio del modulo USART de un ATMEGA328 utilizando el ide de Atmel Studio en C. El problema es que la funcion que hice para recolectar cada caracter recibido no estaria funcionando como corresponde o quizas yo estoy implementando algo mal, pero las demas funciones se encuentran bien dado que hice una prueba con un circuito hecho en mi protoboard enviando solo un caracter y otra prueba enviando una cadena de caracteres desde el micro transmisor al micro receptor, el mismo recibe los caracteres pero no me recibe la cadena.
Debajo adjunto los codigos tanto del tx como del rx para que alguno de ustedes que sea mas experimentado que yo me pueda ayudar con el error!

La funcion que estoy intentando implementar para recibir las cadenas no la he dimensionado por si recibe mas de "x" caracteres en una cadena, porque solo por el momento estoy intentando que al menos pueda recibirlas. La funcion es la siguiente:

Código:
char * usart_recibirCadena(void)
{
    int longitud = 0;
    static char cadena[10];
    
    for (longitud = 0; longitud < 10; longitud++)
    {
        cadena[longitud] = usart_recibirDato();
    }
    
    return cadena;
}


El codigo del tx.c

Código:
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>

void usart_configurar(int baud);
void usart_enviarDato(char dato);
void usart_enviarCadena(char cadena[]);

int main(void)
{
    usart_configurar(9600);
    
    while (1)
    {
        usart_enviarCadena("on");
        _delay_ms(1000);
        
        usart_enviarCadena("off");
        _delay_ms(1000);
    }
    
    return 0;
}

void usart_configurar(int baud)
{

    uint8_t ubrr = ((F_CPU/(16*baud))-1);
    UBRR0 = ubrr;
    
    UCSR0B |= (1<<TXEN0);
    UCSR0C &= ~((1<<UMSEL01)|(1<<UMSEL00));
    UCSR0C &= ~((1<<UPM01)|(1<<UPM00));
    UCSR0C &= ~(1<<USBS0);
    UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
}

void usart_enviarDato(char dato)
{
    while (!(UCSR0A & (1<<UDRE0)));
    UDR0 = dato;
}

void usart_enviarCadena(char cadena[])
{
    int longitud = 0;
    
    while (cadena[longitud] != 0x00)
    {
        usart_enviarDato(cadena[longitud]);
        longitud++;
    }
}
El codigo del rx.c

Código:
#define F_CPU 8000000
#include <avr/io.h>
#include <string.h>

char datoSerial[10];
char encender[] = "on";
char apagar[] = "off";

void usart_configurar(int baud);
char usart_recibirDato(void);
char * usart_recibirCadena(void);

int main(void)
{
    usart_configurar(9600);
    DDRB |= (1<<0);
    
    while (1)
    {
        strcpy(datoSerial, usart_recibirCadena());
        
        if (datoSerial == encender)
        {
            PORTB |= (1<<0);
        }
        
        if (datoSerial == apagar)
        {
            PORTB &= ~(1<<0);
        }
    }
    
    return 0;
}

void usart_configurar(int baud)
{

    uint8_t ubrr = ((F_CPU/(16*baud))-1);
    UBRR0 = ubrr;
    
    UCSR0B |= (1<<RXEN0);
    UCSR0C &= ~((1<<UMSEL01)|(1<<UMSEL00));
    UCSR0C &= ~((1<<UPM01)|(1<<UPM00));
    UCSR0C &= ~(1<<USBS0);
    UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
}

char usart_recibirDato(void)
{
    while (!(UCSR0A & (1<<RXC0)));
    return(UDR0);
}

char * usart_recibirCadena(void)
{
    int longitud = 0;
    static char cadena[10];
    
    for (longitud = 0; longitud < 10; longitud++)
    {
        cadena[longitud] = usart_recibirDato();
    }
    
    return cadena;
}

Desde ya, muchisimas gracias y un saludo a todos!
 

Adjuntos

#2
¿Cuál es el error que te tenés? No pareciera estar mal.

Yo lo haría diferente, crearía 2 buffers globales, uno para Tx y otro para Rx. A c/buffer le asociaría una variable índice para indicar el último elemento almacenado (el índice debe resetearse al superar el límite del buffer).

Luego desde la rutina de interrupción o si querés hacerlo por pulleo, se trabaja con esos buffers.

El uso de este tipo de buffers es lo que se conoce como cola circular.
 
#3
¿Cuál es el error que te tenés? No pareciera estar mal.

Yo lo haría diferente, crearía 2 buffers globales, uno para Tx y otro para Rx. A c/buffer le asociaría una variable índice para indicar el último elemento almacenado (el índice debe resetearse al superar el límite del buffer).

Luego desde la rutina de interrupción o si querés hacerlo por pulleo, se trabaja con esos buffers.

El uso de este tipo de buffers es lo que se conoce como cola circular.
Muchisimas gracias por responder cosme!

El problema es que cuando grabo los micros noto que recibo la cadena pero en el receptor pareciera que no se esta procesando como corresponde el codigo aunque el compilador no me marque error.
El envio de y recepcion de bytes no tiene problema, el problema es la recepcion de cadenas porque el envio de las mismas tambien las realiza bien el micro que esta configurado como tx y el compilador no me notifica problema alguno. Es por eso que recurri a ustedes que quizas ya habian pasado por esto!

Me voy a poner a buscar en la red informacion sobre buffer circular y si encuentro algo voy a postear el codigo! Te agradezco muchisimo y te envio un saludo grande!
 
#4
Viendo bien tú código, las modificaciones mínimas que haría son las siguiente:

1- En vez de usar "static char cadena[10];" en la función "usart_recibirCadena", usaría directamente el vector global "datoSerial" y de paso te ahorrás el strcpy.

2- El "if (datoSerial == encender)" está mal, ahí estás comparando el puntero del vector "datoSerial" con el puntero "encender". En ese if deberías usar la función "strcmp" (string compare), que devuelve un 0 cuando son iguales.

3- La función "usart_recibirDato(void)" tiene un problema grave, en caso de ser llamada y no recibir nada, queda bloqueada en el while. Necesitás agregar un time-out que evite ese bloqueo (usando un timer o un cierto contador dentro de ese while). Lo ideal, es usar interrupción.
 
#5
Viendo bien tú código, las modificaciones mínimas que haría son las siguiente:

1- En vez de usar "static char cadena[10];" en la función "usart_recibirCadena", usaría directamente el vector global "datoSerial" y de paso te ahorrás el strcpy.

2- El "if (datoSerial == encender)" está mal, ahí estás comparando el puntero del vector "datoSerial" con el puntero "encender". En ese if deberías usar la función "strcmp" (string compare), que devuelve un 0 cuando son iguales.

3- La función "usart_recibirDato(void)" tiene un problema grave, en caso de ser llamada y no recibir nada, queda bloqueada en el while. Necesitás agregar un time-out que evite ese bloqueo (usando un timer o un cierto contador dentro de ese while). Lo ideal, es usar interrupción.
Cosme,

Aun sigo leyendo sobre buffers circulares para entender bien como funcionan y basandome un poco en lo que me acabas de escribir pude hacerlo funcionar pero modificando las funciones de envio y recepcion de cadenas. Encontre dos metodos, pero hay uno que me resulto mas efectivo que otro donde lo explico mas adelante y expongo los codigos para que maso menos entiendas como voy!

Lo primero que hice fue dimensionar la cadena a 255, la cual va ser recorrida mientras existan caracteres por enviar y esta no sea igual a "0". De esta forma me permite enviar los caracteres uno a uno hasta que no tenga mas caracteres por enviar y cuando eso suceda (que cadena sea igual a 0), envio el caracter "/" indicando el fin de la cadena.
Código:
void usart_enviarCadena(char * cadena)
{
    int longitud;
    
    for (longitud = 0; longitud < 255; longitud++)
    {
        if (cadena[longitud] != 0)
        {
            usart_enviarDato(cadena[longitud]);
        }
        else
        {
            usart_enviarDato('/'); // Fin de linea
            break;
        }
    }
}

Aca dejo el codigo del TX.c para que veas como quedo el codigo completo:
Código:
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>

void usart_configurar(int baud);
void usart_enviarDato(char dato);
void usart_enviarCadena(char * cadena);

int main(void)
{
    usart_configurar(9600);
    
    while(1)
    {
        usart_enviarCadena("on");
        _delay_ms(1000);
        
        usart_enviarCadena("off");
        _delay_ms(1000);
    }
}

void usart_configurar(int baud)
{
    uint8_t ubrr = ((F_CPU/(16*baud))-1);
    UBRR0 = ubrr;
    
    UCSR0B |= (1<<TXEN0);
    UCSR0C &= ~((1<<UMSEL01)|(1<<UMSEL00));
    UCSR0C &= ~((1<<UPM01)|(1<<UPM00));
    UCSR0C &= ~(1<<USBS0);
    UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
}

void usart_enviarDato(char dato)
{
    while (!(UCSR0A & (1<<UDRE0)));
    UDR0 = dato;
}

void usart_enviarCadena(char * cadena)
{
    int longitud;
    
    for (longitud = 0; longitud < 255; longitud++)
    {
        if (cadena[longitud] != 0)
        {
            usart_enviarDato(cadena[longitud]);
        }
        else
        {
            usart_enviarDato('/'); // Fin de linea
            break;
        }
    }
}

Lo siguiente fue modificar la funcion de recepcion de cadenas pero como te comente anteriormente, lo hice de dos formas distintas.
La primera es esta:

Declaro a "datoSerial" como puntero.
Código:
char * datoSerial;
Despues dentro la funcion "usart_recibirCadena" inicio un ciclo while para repetir la recepcion de datos. La variable "caracter" almacena el dato recibido del puerto serie y consulto con un if si el valor almacenado es igual a '/' (el cual adopte como final de cadena) o si es igual a 13(el numero 13 en ASCII es retorno de carro o CR).
El retorno de carro me sirve por ejemplo para utilizar el hyperterminal en windows, lo probe y funciona perfecto.
Retomando, si es igual a '/' entonces retorno la cadena y si no es igual sigo almacenado los valores mientras aumento la longitud de la cadena.

Hasta aca todo muy bien, esta funcion trabaja muy bien pero el problema es que al tener declarada la variable "cadena" como estatica(static char cadena[255]). Los datos no se reemplazan y la misma tampoco se puede borrar por ser estatica, realice una prueba donde detecte que me llegaban los datos en este orden:

- on
- off
- onf
- off
- onf

etc...

Obviamente se ve que la primera secuencia se ejecuta bien pero cuando descubri que despues de la primera secuencia todo cambia me encontre en un lugar donde no puedo salir. Este problema es por no poder vaciar esta variable y logicamente porque la misma es estatica, entonces a partir del segundo ciclo en vez de comparar si "on == on", lo que termina comparando es que si "onf == on" y asi jamas se encenderia el led.

Código:
char * usart_recibirCadena(void)
{
    char caracter;
    int longitud = 0;
    static char cadena[255];
    
    while (1)
    {
        caracter = usart_recibirDato();
        
        if ((caracter == '/') || (caracter == 13)) // Fin de linea o retorno de carro
        {
            return cadena;
        }
        else
        {
            cadena[longitud] = caracter;
            longitud++;
        }
    }
}
Aca dejo la hoja RX.c completa para mostrar como quedo el primer ejemplo

Código:
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>

char * datoSerial;

void usart_configurar(int baud);
char usart_recibirDato(void);
char * usart_recibirCadena(void);
void usart_cadena(char cadena[]);

int main(void)
{
    DDRB |= (1<<0);
    usart_configurar(9600);
    
    while(1)
    {   
        datoSerial = usart_recibirCadena();
        
        if (strcmp(datoSerial, "on") == 0)
        {
            PORTB |= (1<<0);
        }
        else
        {
            PORTB &= ~(1<<0);
        }
    }
}

void usart_configurar(int baud)
{
    uint8_t ubrr = ((F_CPU/(16*baud))-1);
    UBRR0 = ubrr;
    
    UCSR0B |= (1<<RXEN0);
    UCSR0C &= ~((1<<UMSEL01)|(1<<UMSEL00));
    UCSR0C &= ~((1<<UPM01)|(1<<UPM00));
    UCSR0C &= ~(1<<USBS0);
    UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
}

char usart_recibirDato(void)
{
    while (!(UCSR0A & (1<<RXC0)));
    return(UDR0);
}

char * usart_recibirCadena(void)
{
    char caracter;
    int longitud = 0;
    static char cadena[255];
    
    while (1)
    {
        caracter = usart_recibirDato();
        
        if ((caracter == '/') || (caracter == 13)) // Fin de linea o retorno de carro
        {
            return cadena;
        }
        else
        {
            cadena[longitud] = caracter;
            longitud++;
        }
    }
}

El segundo metodo que utilice fue el que termino funcionando muy bien, es casi como la funcion anterior a diferencia que el dato que retorno es a una variable declarada como matriz y no como puntero. Donde lo primero que hago es:
Código:
char datoSerial[255];
Como dije anteriormente, realizo casi las mismas operaciones que la funcion hecha para el primer ejemplo pero a diferencia que el dato lo retorno a una matriz y no a un puntero.

Código:
void usart_recibirCadena(char cadena[])
{
    char caracter;
    int longitud = 0;
    
    while (1)
    {
        caracter = usart_recibirDato();
        
        if ((caracter == '/') || (caracter == 13)) // Fin de linea o retorno de carro
        {
            cadena[longitud] = 0;
            return;
        }
        else
        {
            cadena[longitud] = caracter;
            longitud++;
        }
    }
}
Aca dejo el codigo RX.c del segundo ejemplo que si funciona correctamente
Código:
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>

char datoSerial[255];

void usart_configurar(int baud);
char usart_recibirDato(void);
char * usart_recibirCadena(void);
void usart_cadena(char cadena[]);

int main(void)
{
    DDRB |= (1<<0);
    usart_configurar(9600);
    
    while(1)
    {   
        usart_cadena(datoSerial);

        if (strcmp(datoSerial, "on") == 0)
        {
            PORTB |= (1<<0);
        }
        
        if (strcmp(datoSerial, "off") == 0)
        {
            PORTB &= ~(1<<0);
        }
    }
}

void usart_configurar(int baud)
{
    uint8_t ubrr = ((F_CPU/(16*baud))-1);
    UBRR0 = ubrr;
    
    UCSR0B |= (1<<RXEN0);
    UCSR0C &= ~((1<<UMSEL01)|(1<<UMSEL00));
    UCSR0C &= ~((1<<UPM01)|(1<<UPM00));
    UCSR0C &= ~(1<<USBS0);
    UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
}

char usart_recibirDato(void)
{
    while (!(UCSR0A & (1<<RXC0)));
    return(UDR0);
}

void usart_recibirCadena(char cadena[])
{
    char caracter;
    int longitud = 0;
    
    while (1)
    {
        caracter = usart_recibirDato();
        
        if ((caracter == '/') || (caracter == 13)) // Fin de linea o retorno de carro
        {
            cadena[longitud] = 0;
            return;
        }
        else
        {
            cadena[longitud] = caracter;
            longitud++;
        }
    }
}
 
#6
Del 2do método de recepción que subiste, modificaría lo siguiente:

Código:
void usart_recibirCadena(char cadena[])
{
    char caracter;
    int longitud = 0;
    
    for(; longitud<=255; longitud++)
    {
        caracter = usart_recibirDato();
        
        if ((caracter == '/') || (caracter == 13)) // Fin de linea o retorno de carro
        {
            cadena[longitud] = 0;
            return;
        }
        else
            cadena[longitud] = caracter;           
    }
}
Evitás superar los 255 elementos durante la recepción.

Y como alternativa, en este caso "datoSerial" no sería necesario que fuera global, podrías hacerla local en main.
 
#7
Del 2do método de recepción que subiste, modificaría lo siguiente:

Código:
void usart_recibirCadena(char cadena[])
{
    char caracter;
    int longitud = 0;
    
    for(; longitud<=255; longitud++)
    {
        caracter = usart_recibirDato();
        
        if ((caracter == '/') || (caracter == 13)) // Fin de linea o retorno de carro
        {
            cadena[longitud] = 0;
            return;
        }
        else
            cadena[longitud] = caracter;           
    }
}
Evitás superar los 255 elementos durante la recepción.

Y como alternativa, en este caso "datoSerial" no sería necesario que fuera global, podrías hacerla local en main.

Acabo de realizar la modificación que me dijiste Cosme, compile y probé!
Funciona de maravilla, mil gracias por la ayuda!

Un saludo grande!!
 
Arriba