muchas gracias por vuestros comentarios...
Antes de meterme con las tareas, voy a explicar unas cosillas sobre los drivers, que tambien tienen su trabajo...En el pasado tambien publique algunas cosas sobre drivers en el post de
"Juego del Pong basado en FreeRTOS", y desde entonces he aprendido alguna cosilla mas qur voy a comentar.
IntroduccionUno de los problemas más pesados cuando uno cambia de arquitectura es tener que aprender de nuevo a controlar los periféricos. Cada fabricante, al diseñar el periférico, escoge la estructura y los registros de configuración/estado de acuerdo a sus necesidades, y esto siempre dificulta la portabilidad.
Distintos periféricos en un microcontrolador AVR
El driver ayuda a resolver este problema, a costa de reducir la funcionalidad de un periférico al máximo común divisor de todos los fabricantes. Escribir programas portables con drivers es mucho más sencillo ya que, desde el punto de vista externo, los drivers tienen siempre la misma “forma”, independientemente de la estructura interna del periférico e incluso del tipo de periférico.
La estructura del driver generico desarrollado para el proyecto
La estructuraLa estructura de los drivers desarrollados para este proyecto se divide en 4 partes: funciones, estructuras, interrupciones y macros.
Las funcionesTodo la interacción necesaria entre un programa y un periférico se puede reducir a 4 simples funciones. Adicionalmente siempre se pueden definir mas funciones como Reset() o WriteBuffer() si el programa lo requiere. Las funciones son:
Open() Resetea el periférico, lo activa y lo configura según los argumentos que le pasemos.
Close() Desactiva el periférico.
Read() Lee datos desde el periférico.
Write() Enviad datos al periférico.
Las estructuras Normalmente un microcontrolador posee varios periféricos del mismo tipo, con un funcionamiento idéntico. Utilizar una estructura que englobe todos los registros de un periférico permite utilizar uno u otro cambiando solamente una variable en el programa. Una estructura simplificada* de una USART podría tener esta forma:
struct sUSART{ int *pControl; int *pStatus; int *pData; };
Para poder utilizar un periférico u otro las funciones deben aceptar un parámetro que sea la estructura, por ejemplo: Open( struct sUSART *this )...
*La estructura implementada para AVRPLC tiene 14 campos.
Las interrupcionesNormalmente un periférico tarda mucho más tiempo en “procesar” con un dato que el que tarda la CPU, además, un periférico puede “generar” un dato en cualquier momento, de manera impredecible para la CPU. Por este motivo los periféricos incorporan las interrupciones, que liberan a la CPU de la necesidad de comprobar continuamente el estado de los periféricos manteniendo el “ancho de banda” al máximo permitido por los periféricos.
Según el ancho de banda necesario se pueden utilizar interrupciones con mayor o menor relación complejidad/rendimiento. Aquí voy a citar 4:
-Sondeo
…todo te da miedo y te sientes muy solo, te quedas cerquita del periférico y le dices por favor que te avise cuando termine….
-Interrupción
Es el modo más común. Cuando el periféricos esta listo genera la interrupción, la CPU copia/lee los datos en la ISR. Es sencillo y eficiente, pero también tiene sus inconvenientes: Si la CPU produce 2 datos seguidos tendrá que esperar a que el periférico termine con el primero y lo que es peor, si el periférico produce 2 datos seguidos el primero se perderá.
-Interrupción con buffer
Este es el método utilizado en el AVRPLC. La CPU ya no escribe los datos directamente en el periférico, lo hace en un buffer. Cuando el periférico termina con un dato genera una IRQ y en la ISR la CPU saca el siguiente dato del buffer. De esta manera los periféricos como que normalmente envían/reciben “ráfagas” de datos trabajan mucho más eficientemente
Funcionamiento del buffer circular con doble puntero:
-Interrupción con DMA
El DMA es en si un periférico cuyo objetivo es mover bloques de datos de un lugar a otro dentro del micro. La CPU solo tiene que escribir los datos en el buffer y el DMA se ocupa del resto. Es el más eficiente, con mucha diferencia, pero normalmente los microcontroladores no disponen de DMA.
Los buffers y las condiciones de carreraUna de las complicaciones al utilizar el buffer y las interrupciones es evitar la posibilidad de que se produzcan condiciones de carrera. Resumiendo, una condición de carrera se puede producir si se modifica un dato mientras este se estaba leyendo. En los sistemas embebidos esto puede ocurrir únicamente si la ISR y la función principal pueden modificar la misma variable.
Los buffers implementados en AVRPLC son buffers circulares que disponen de dos punteros llamados Head y Tail. Head siempre apunta a la primera posición vacia del buffer y Tail a la ultima posición llena. De esta manera ni los datos del buffer, ni los punteros Head y Tail pueden ser escritos al mismo tiempo, impidiendo la condición de carrera.
En el caso de la USARTwrite, la operación para introducir un dato en el buffer es:
pUSART->buffTX[ pUSART->buffTXptrH++ & BUFF_TX_MASK ] = data;
y el código para sacar un dato en la ISR de USART_TX es:
pUSART->UDRn = pUSART->buffTX[pUSART->buffTXptrT++ & BUFF_TX_MASK ];
Puesto no comparten el puntero, y se comprueba previamente que los punteros no tengan el mismo valor(operación permitida ya que solo es de lectura), no se pueden producir condiciones de carrera.
Las macrosLas macros permiten al programador controlar el estado del periférico sin tener que acceder directamente al hardware. Se pueden usar por ejemplo para activar/desactivar las interrupciones, borrar el flag de interrupción, comprobar si los buffers están llenos o vacios… El driver de la USART tiene 18 macros definidas.
Los drivers y el RTOSEl RTOS desactiva las interrupciones globales cuando entra en una región critica. Esto es necesario para el RTOS ya que así impide el cambio de contexto durante la ejecución de su propio kernel. Esto puede reducir ligeramente la velocidad de los periféricos sin necesidad. Lo que se ha hecho es cambiar la definición de las macros taskENTER_CRITICAL, taskEXIT_CRITICAL, taskENABLE_INTERRUPTS y taskDISABLE_INTERRUPTS para que solo desactiven el interrupción del TIMER del RTOS, impidiendo el cambio de contexto en las requines criticas y al mismo tiempo permitiendo a las IRQs ser atendidas sin espera.
De momento esto lo tengo en pruebas, pero aparentemente no hay ningún motivo que pueda provocar que el sistema se vuelva inestable.
Adjunto el codigo de lod archivod UartDriver.c y .h
/**********/
/* UsartDriver.h */
// Based on UART library by Peter Fleury
/**********/
#ifndef USARTDRIVER_H
#define USARTDRIVER_H 1
#include <avr/io.h>
#include <avr/interrupt.h>
#define BUFF_RX_SIZE 128 // Must be a power of two.
#define BUFF_TX_SIZE 128 // Must be a power of two.
#define TXIrqEnable( pUSART ) ( BitS( *(pUSART)->UCSRnB, UDRIE0 ) )
#define TXIrqDisable( pUSART ) ( BitR( *(pUSART)->UCSRnB, UDRIE0 ) )
#define RXIrqEnable( pUSART ) ( BitS( *(pUSART)->UCSRnB, RXCIE0 ) )
#define RXIrqDisable( pUSART ) ( BitR( *(pUSART)->UCSRnB, RXCIE0 ) )
#define TXFlagSet( pUSART ) ( BitS( *(pUSART)->UCSRnA, UDRE0 ) )
#define TXFlagClear( pUSART ) ( BitR( *(pUSART)->UCSRnA, UDRE0 ) )
#define RXFlagSet( pUSART ) ( BitS( *(pUSART)->UCSRnA, RXC0 ) )
#define RXFlagClear( pUSART ) ( BitR( *(pUSART)->UCSRnA, RXC0 ) )
#define TXOn( pUSART ) ( BitS( *(pUSART)->UCSRnB, TXEN0 ) )
#define TXOff( pUSART ) ( BitR( *(pUSART)->UCSRnB, TXEN0 ) )
#define RXOn( pUSART ) ( BitS( *(pUSART)->UCSRnB, RXEN0 ) )
#define RXOff( pUSART ) ( BitR( *(pUSART)->UCSRnB, RXEN0 ) )
#define USARTPinEnable( pUSART ) { BitR( *(pUSART)->DDRx, (pUSART)->PIN ); BitS( *(pUSART)->DDRx, (pUSART)->PIN+1 ); }
#define USARTPinDisable( pUSART ) { BitR( *(pUSART)->DDRx, (pUSART)->PIN ); BitR( *(pUSART)->DDRx, (pUSART)->PIN+1 ); }
#define isBufferTXFull( pUSART ) ( (((pUSART)->buffTXptrH - (pUSART)->buffTXptrT) & BUFF_TX_MASK) == BUFF_TX_MASK )
#define isBufferTXEmpty( pUSART ) ( (pUSART)->buffTXptrH == (pUSART)->buffTXptrT )
#define isBufferRXFull( pUSART ) ( (((pUSART)->buffRXptrH - (pUSART)->buffRXptrT) & BUFF_RX_MASK) == BUFF_RX_MASK )
#define isBufferRXEmpty( pUSART ) ( (pUSART)->buffRXptrH == (pUSART)->buffRXptrT )
#define BAUD_9600 ( ( (F_CPU) / ( (9600UL) * 8 ) - 1 ) )
#define BAUD_57600 ( ( (F_CPU) / ( (57600UL) * 8 ) - 1 ) )
#define BAUD_115200 ( ( (F_CPU) / ( (115200UL) * 8 ) - 1 ) )
#define BAUD_115200_XBEE ( ( (F_CPU) / ( (111111UL) * 8 ) - 1 ) )
#define BAUD_230400 ( ( (F_CPU) / ( (230400UL) * 8 ) - 1 ) )
struct USART {
volatile unsigned char *UDRn;
volatile unsigned char *UCSRnA;
volatile unsigned char *UCSRnB;
volatile unsigned char *UCSRnC;
volatile unsigned char *UBRRnL;
volatile unsigned char *UBRRnH;
volatile unsigned char *DDRx;
volatile unsigned char PIN;
volatile unsigned char *buffRX;
volatile unsigned char *buffTX;
volatile unsigned char buffTXptrH;
volatile unsigned char buffTXptrT;
volatile unsigned char buffRXptrH;
volatile unsigned char buffRXptrT;
};
extern struct USART USART0;
extern struct USART USART1;
extern struct USART USART2;
extern struct USART USART3;
void USARTopen( struct USART *pUSART, unsigned int baudrate );
void USARTclose( struct USART *pUSART );
unsigned char USARTread( struct USART *pUSART );
void USARTwrite( struct USART *pUSART, unsigned char data );
void USARTputs( struct USART *pUSART, char *s );
void USARTputc( struct USART *pUSART, char c );
void USARTputInt( struct USART *pUSART, unsigned int i );
#endif
UsartDriver.c
/**********/
/* UsartDriver.c */
// Based on UART library by Peter Fleury
/**********/
#include "UsartDriver.h"
#include "Utils.h"
#define BUFF_RX_MASK ( BUFF_RX_SIZE - 1 )
#define BUFF_TX_MASK ( BUFF_TX_SIZE - 1 )
static unsigned char buffRX0[ BUFF_RX_SIZE ];
static unsigned char buffTX0[ BUFF_TX_SIZE ];
//static unsigned char buffRX1[ BUFF_RX_SIZE ];
//static unsigned char buffTX1[ BUFF_TX_SIZE ];
static unsigned char buffRX2[ BUFF_RX_SIZE ];
static unsigned char buffTX2[ BUFF_TX_SIZE ];
//static unsigned char buffRX3[ BUFF_RX_SIZE ];
//static unsigned char buffTX3[ BUFF_TX_SIZE ];
struct USART USART0 = {
&UDR0, &UCSR0A, &UCSR0B, &UCSR0C, &UBRR0L, &UBRR0H,
&DDRE, 0,
&buffRX0[0], &buffTX0[0],
0, 0, 0, 0
};
/*struct USART USART1 = {
&UDR1, &UCSR1A, &UCSR1B, &UCSR1C, &UBRR1L, &UBRR1H,
&DDRD, 2,
&buffRX1[0], &buffTX1[0],
0, 0, 0, 0
};*/
struct USART USART2 = {
&UDR2, &UCSR2A, &UCSR2B, &UCSR2C, &UBRR2L, &UBRR2H,
&DDRH, 0,
&buffRX2[0], &buffTX2[0],
0, 0, 0, 0
};
/*struct USART USART3 = {
&UDR3, &UCSR3A, &UCSR3B, &UCSR3C, &UBRR3L, &UBRR3H,
&DDRJ, 0,
&buffRX3[0], &buffTX3[0],
0, 0, 0, 0
};*/
#define ISR_USART_RX( VECTOR, USART ) \
ISR( VECTOR ){ \
LEDRunON(); \
if( !isBufferRXFull( &(USART) ) ) \
(USART).buffRX[ (USART).buffRXptrH++ & BUFF_RX_MASK] = *(USART).UDRn; \
else{ \
RXIrqDisable( &(USART) ); \
} \
}
ISR_USART_RX( USART0_RX_vect, USART0 )
//ISR_USART_RX( USART1_RX_vect, USART1 )
ISR_USART_RX( USART2_RX_vect, USART2 )
//ISR_USART_RX( USART3_RX_vect, USART3 )
#define ISR_USART_TX( VECTOR, USART ) \
ISR( VECTOR ){ \
LEDRunON(); \
if( !isBufferTXEmpty( &(USART) ) ){ \
*(USART).UDRn = (USART).buffTX[ (USART).buffTXptrT++ & BUFF_TX_MASK ]; \
}else{ \
TXIrqDisable( &(USART) ); \
} \
}
ISR_USART_TX( USART0_UDRE_vect, USART0 )
//ISR_USART_TX( USART1_UDRE_vect, USART1 )
ISR_USART_TX( USART2_UDRE_vect, USART2 )
//ISR_USART_TX( USART3_UDRE_vect, USART3 )
void USARTopen( struct USART *pUSART, unsigned int baudrate ){
TXIrqDisable( pUSART );
RXIrqDisable( pUSART );
// Enable 2x speed
BitS( *pUSART->UCSRnA, U2X0 );
// Set Baud Rate
*pUSART->UBRRnH = (baudrate>>8) & 0x00FF;
*pUSART->UBRRnL = baudrate & 0x00FF;
// Set frame format: asynchronous, 8data, no parity, 1stop bit
BitS( *pUSART->UCSRnC, UCSZ00 ); // 8 data
BitS( *pUSART->UCSRnC, UCSZ01 );
BitR( *pUSART->UCSRnB, UCSZ02 );
BitR( *pUSART->UCSRnB, UPM00 ); // no parity
BitR( *pUSART->UCSRnB, UPM01 );
BitS( *pUSART->UCSRnC, USBS0 ); // 1 stop bit
pUSART->buffTXptrT = 0;
pUSART->buffTXptrH = 0;
pUSART->buffRXptrT = 0;
pUSART->buffRXptrH = 0;
RXOn( pUSART );
TXOn( pUSART );
// USARTPinEnable( pUSART );
TXFlagClear( pUSART );
RXFlagClear( pUSART );
TXIrqEnable( pUSART );
RXIrqEnable( pUSART );
}
void USARTclose( struct USART *pUSART ){
TXIrqDisable( pUSART );
RXIrqDisable( pUSART );
// Disable USART receiver and transmitter
RXOff( pUSART );
TXOff( pUSART );
USARTPinDisable( pUSART );
}
unsigned char USARTread( struct USART *pUSART ){
unsigned char data;
while( isBufferRXEmpty( pUSART ) ){
irqONOFF();
}
data = pUSART->buffRX[ pUSART->buffRXptrT++ & BUFF_RX_MASK ];
RXIrqEnable( pUSART );
return data;
}
void USARTwrite( struct USART *pUSART, unsigned char data){
// Modo de espera, todos los caracteres son transmitidos.
// No puede ser usado en modo DEBUG con TRACE_VISUAL activado y tasas de transmision altas
while( isBufferTXFull( pUSART ) ){
irqONOFF();
}
pUSART->buffTX[ pUSART->buffTXptrH++ & BUFF_TX_MASK ] = data;
TXIrqEnable( pUSART );
// Modo SIN espera, No se garantiza la transmision de todos los caracteres
// Recomendado para Debug con TRACE VISUAL y tasas de transmision rapidas.
// if( !isBufferTXFull( pUSART ) ){
// pUSART->buffTX[ pUSART->buffTXptrH++ & BUFF_TX_MASK ] = data;
// }
// TXIrqEnable( pUSART );
}
void USARTputc( struct USART *pUSART, char c ){
USARTwrite( pUSART, c );
}
void USARTputs( struct USART *pUSART, char *s ){
while( *s != '\0' ){
USARTwrite( pUSART, *s );
s++;
}
}
void USARTputInt( struct USART *pUSART, unsigned int i ){
USARTwrite( pUSART, toHex( (i>>12)&0x0F ) );
USARTwrite( pUSART, toHex( (i>>8 )&0x0F ) );
USARTwrite( pUSART, toHex( (i>>4 )&0x0F ) );
USARTwrite( pUSART, toHex( (i>>0 )&0x0F ) );
}