jmr1972 wrote on Thursday, May 22, 2008:
Hi folks,
I’ve been working on a project using an ATMega640, WinAVR and FreeRTOS for the past few months.
I made the following changes to port.c to enable Timer 1 as my OS tick.
I’ve also modified the serial port driver to enable the serial ports on this processor. I’ve tried to make it generic but since so far the ATMega640 is the only processor I’ve used it with I don’t know if it’ll work on anything else… Use at your own risk…
Hope this proves useful to someone out there.
JM
in port.c:
---------------------------------------------------------------------------------------------------------------
/* Hardware constants for timer 1. */
#define portCLEAR_COUNTER_ON_MATCH ( ( unsigned portCHAR ) 0x08 )
#define portPRESCALE_64 ( ( unsigned portCHAR ) 0x03 )
#define portCLOCK_PRESCALER ( ( unsigned portLONG ) 64 )
#define portCOMPARE_MATCH_A_INTERRUPT_ENABLE ( ( unsigned portCHAR ) 0x02 )
/*
* Setup timer 1 compare match A to generate a tick interrupt.
*/
static void prvSetupTimerInterrupt( void )
{
unsigned portLONG ulCompareMatch;
unsigned portCHAR ucHighByte, ucLowByte;
/* Using 16bit timer 1 to generate the tick. Correct fuses must be
selected for the configCPU_CLOCK_HZ clock. */
ulCompareMatch = configCPU_CLOCK_HZ / portCLOCK_PRESCALER;
/* calculates the RTOS tick rate based on the timer’s tick rate. */
ulCompareMatch /= configTICK_RATE_HZ;
/* Adjust for correct value. */
ulCompareMatch -= ( unsigned portLONG ) 1;
//ulCompareMatch = (unsigned portLONG) 8639;
/* Setup compare match value for compare match A. Interrupts are disabled
before this is called so we need not worry here. */
ucLowByte = ( unsigned portCHAR ) ( ulCompareMatch & ( unsigned portLONG ) 0xff );
ulCompareMatch >>= 8;
ucHighByte = ( unsigned portCHAR ) ( ulCompareMatch & ( unsigned portLONG ) 0xff );
OCR1AH = ucHighByte;
OCR1AL = ucLowByte;
/* Setup clock source and compare match behaviour. */
ucLowByte = portCLEAR_COUNTER_ON_MATCH | portPRESCALE_64;
TCCR1B = ucLowByte;
/* Enable the interrupt - this is okay as interrupt are currently globally
disabled. */
ucLowByte = TIMSK1;
ucLowByte |= portCOMPARE_MATCH_A_INTERRUPT_ENABLE;
TIMSK1 = ucLowByte;
}
----------------------------------------------------------------------------------------------------------
/*
* serial.h
* Description:
* serial port driver for the ATMega640
*/
#ifndef SERIAL_COMMS_H
#define SERIAL_COMMS_H
typedef enum
{
serCOM1,
serCOM2,
serCOM3,
serCOM4,
serCOMPortInvalid
} eCOMPort;
typedef enum
{
serNO_PARITY,
serODD_PARITY,
serEVEN_PARITY,
serMARK_PARITY,
serSPACE_PARITY
} eParity;
typedef enum
{
serSTOP_1,
serSTOP_2
} eStopBits;
typedef enum
{
serBITS_5,
serBITS_6,
serBITS_7,
serBITS_8,
serBITS_9
} eDataBits;
typedef enum
{
ser50,
ser75,
ser110,
ser134,
ser150,
ser200,
ser300,
ser600,
ser1200,
ser1800,
ser2400,
ser4800,
ser9600,
ser19200,
ser38400,
ser57600,
ser115200
} eBaud;
extern eCOMPort xSerialPortInit( eCOMPort ePort, eBaud eWantedBaud, eParity eWantedParity, eDataBits eWantedDataBits, eStopBits eWantedStopBits, unsigned portBASE_TYPE uxBufferLength );
extern signed portBASE_TYPE xSerialPutString( eCOMPort ePort, const portCHAR * pcString, portTickType xBlockTime );
extern signed portBASE_TYPE xSerialGetChar( eCOMPort ePort, portCHAR *pcRxedChar, portTickType xBlockTime );
extern signed portBASE_TYPE xSerialPutChar( eCOMPort ePort, portCHAR cOutChar, portTickType xBlockTime );
extern unsigned portBASE_TYPE ubSerialIsActive(eCOMPort ePort);
extern void vSerialClose( eCOMPort xPort );
#endif
/*
* serial.c
* Description:
* This is a quad serial port driver for ATMega644.
* It gives a common interface to open/close serial ports.
* $Log: serial.c,v $
* Revision 1.7 2008-05-09 14:23:27+01 jmr
* Fixed a memory leak in the serial port driver where allocated memory for the queues wasn’t freed but the pointers to the queues nulled. This meant that if a task opened the same port twice it didn’t get the same queue again…
* The driver now simply will not reallocate a queue when the port is reopened.
*
*/
#include <stdlib.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include "serial.h"
#define serialMAX_NUM_PORTS 4
#define serialBAUD_DIV_CONSTANT 16
/*-----------------------------------------------------------*/
static void prvInterruptOn(eCOMPort ePort);
static void prvInterruptOff(eCOMPort ePort);
/*-----------------------------------------------------------*/
/* declare some useful constants */
static unsigned portSHORT usBaudRateDivisors[] PROGMEM =
{
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/50 -1, // 50
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/75 -1, // 75
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/110 -1, // 110
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/134 -1, // 134
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/150 -1, // 150
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/200 -1, // 200
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/300 -1, // 300
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/600 -1, // 600
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/1200 -1, // 1200
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/1800 -1, // 1800
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/2400 -1, // 2400
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/4800 -1, // 4800
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/9600 -1, // 9600
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/19200 -1, // 19200
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/38400 -1, // 38400
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/57600 -1, // 57600
configCPU_CLOCK_HZ/serialBAUD_DIV_CONSTANT/115200 -1 // 115200
};
static unsigned portCHAR ucParityBits[] PROGMEM = { 0x00, /* No parity */
0x30, /* Odd parity */
0x20, /* Even parity */
0x00, /* Mark parity (not supported) */
0x00 /* Space parity (not supported) */
};
static unsigned portCHAR ucDataBits[] PROGMEM = { 0x00, /* 5 data bits */
0x02, /* 6 data bits */
0x04, /* 7 data bits */
0x06, /* 8 data bits */
0x06 /* 9 data bits */
};
/* The Rx And Tx queues used for the serial communications */
static xQueueHandle xRxedChars[serialMAX_NUM_PORTS];
static xQueueHandle xCharsForTx[serialMAX_NUM_PORTS];
/* Serial port initialisation */
eCOMPort xSerialPortInit( eCOMPort ePort, eBaud eWantedBaud, eParity eWantedParity, eDataBits eWantedDataBits, eStopBits eWantedStopBits, unsigned portBASE_TYPE uxQueueLength )
{
unsigned portSHORT usBaudRateCounter;
unsigned portCHAR ucByte;
/* Verify that the desired port number is supported */
switch( ePort )
{
case serCOM1:
#if serialMAX_NUM_PORTS > 1
case serCOM2:
#endif
#if serialMAX_NUM_PORTS > 2
case serCOM3:
#endif
#if serialMAX_NUM_PORTS > 3
case serCOM4:
#endif
break;
default:
return serCOMPortInvalid;
break;
}
/* Modifying the serial port registers needs to have all interrupts disabled */
portENTER_CRITICAL();
{
/* Create the queues used by the com test task if they weren’t created by a previous call to SerialPortInit
*/
if( xRxedChars[ePort] == NULL)
{
xRxedChars[ePort] = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed portCHAR ) );
}
if( xCharsForTx[ePort] == NULL)
{
xCharsForTx[ePort] = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed portCHAR ) );
}
/* Calculate the baud rate register value from the equation in the
data sheet. */
//usBaudRateCounter = ( configCPU_CLOCK_HZ / ( serialBAUD_DIV_CONSTANT * ulWantedBaud ) ) - ( unsigned portSHORT ) 1;
usBaudRateCounter = pgm_read_word(&usBaudRateDivisors[eWantedBaud]);
switch( ePort)
{
case serCOM1:
/* Set Parity, data and stop bits */
ucByte = 0;
ucByte |= pgm_read_byte(&ucParityBits[eWantedParity]);
ucByte |= pgm_read_byte(&ucDataBits[eWantedDataBits]);
ucByte |= eWantedStopBits? _BV(USBS0): 0;
UCSR0C = ucByte;
/* Set the baud rate. */
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0xff );
UBRR0L = ucByte;
usBaudRateCounter >>= ( unsigned portSHORT ) 8;
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0x0f );
UBRR0H = ucByte;
/* Enable the Rx interrupt. The Tx interrupt will get enabled
later. Also enable the Rx and Tx. */
UCSR0B = ( _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0) |((eWantedDataBits==serBITS_9)?_BV(UCSZ02):0));
break;
case serCOM2:
/* Set Parity, data and stop bits */
ucByte = 0;
ucByte |= pgm_read_byte(&ucParityBits[eWantedParity]);
ucByte |= pgm_read_byte(&ucDataBits[eWantedDataBits]);
ucByte |= eWantedStopBits? _BV(USBS0): 0;
UCSR1C = ucByte;
/* Set the baud rate. */
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0xff );
UBRR1L = ucByte;
usBaudRateCounter >>= ( unsigned portSHORT ) 8;
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0x0f );
UBRR1H = ucByte;
/* Enable the Rx interrupt. The Tx interrupt will get enabled
later. Also enable the Rx and Tx. */
UCSR1B = ( _BV(RXCIE1) | _BV(RXEN1) | _BV(TXEN1) |((eWantedDataBits==serBITS_9)?_BV(UCSZ12):0) );
break;
case serCOM3:
/* Set Parity, data and stop bits */
ucByte = 0;
ucByte |= pgm_read_byte(&ucParityBits[eWantedParity]);
ucByte |= pgm_read_byte(&ucDataBits[eWantedDataBits]);
ucByte |= eWantedStopBits? _BV(USBS0): 0;
UCSR2C = ucByte;
/* Set the baud rate. */
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0xff );
UBRR2L = ucByte;
usBaudRateCounter >>= ( unsigned portSHORT ) 8;
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0x0f );
UBRR2H = ucByte;
/* Enable the Rx interrupt. The Tx interrupt will get enabled
later. Also enable the Rx and Tx. */
UCSR2B = ( _BV(RXCIE2) | _BV(RXEN2) | _BV(TXEN2) |((eWantedDataBits==serBITS_9)?_BV(UCSZ22):0) );
break;
case serCOM4:
/* Set Parity, data and stop bits */
ucByte = 0;
ucByte |= pgm_read_byte(&ucParityBits[eWantedParity]);
ucByte |= pgm_read_byte(&ucDataBits[eWantedDataBits]);
ucByte |= eWantedStopBits? _BV(USBS0): 0;
UCSR3C = ucByte;
/* Set the baud rate. */
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0xff );
UBRR3L = ucByte;
usBaudRateCounter >>= ( unsigned portSHORT ) 8;
ucByte = ( unsigned portCHAR ) ( usBaudRateCounter & ( unsigned portSHORT ) 0x0f );
UBRR3H = ucByte;
/* Enable the Rx interrupt. The Tx interrupt will get enabled
later. Also enable the Rx and Tx. */
UCSR3B = ( _BV(RXCIE3) | _BV(RXEN3) | _BV(TXEN3) |((eWantedDataBits==serBITS_9)?_BV(UCSZ32):0) );
break;
default:
break;
}
}
portEXIT_CRITICAL();
/* Unlike other ports, this serial code does not return a pointer to a port structure
but returns the number of the port opened if successful. Otherwise it returns eCOMPortInvalid.
*/
return ePort;
}
/*-----------------------------------------------------------*/
/* Get a character from the serial port */
signed portBASE_TYPE xSerialGetChar( eCOMPort ePort, portCHAR *pcRxedChar, portTickType xBlockTime )
{
if( xRxedChars[ePort] == NULL )
{
return pdFAIL;
}
/* Get the next character from the buffer. Return false if no characters
are available, or arrive before xBlockTime expires. */
if( xQueueReceive( xRxedChars[ePort], pcRxedChar, xBlockTime ) )
{
return pdPASS;
}
else
{
return pdFAIL;
}
}
/*-----------------------------------------------------------*/
/* Write a character to the serial port */
signed portBASE_TYPE xSerialPutChar( eCOMPort ePort,portCHAR cOutChar, portTickType xBlockTime )
{
/* ensure the queue has been initialised */
if( xCharsForTx[ePort] == NULL )
{
return pdFAIL;
}
/* Return false if after the block time there is no room on the Tx queue. */
if( xQueueSend( xCharsForTx[ePort], &cOutChar, xBlockTime ) != pdPASS )
{
return pdFAIL;
}
prvInterruptOn(ePort);
return pdPASS;
}
/* write a NULL terminated string to the serial port */
signed portBASE_TYPE xSerialPutString( eCOMPort ePort, const portCHAR * pcString, portTickType xBlockTime )
{
signed portBASE_TYPE retVal = pdPASS;
while(*pcString && (retVal == pdPASS) )
{
retVal = xSerialPutChar( ePort, *pcString++, xBlockTime);
}
return retVal;
}
unsigned portBASE_TYPE ubSerialIsActive(eCOMPort ePort)
{
if( uxQueueMessagesWaiting(xRxedChars[ePort])
||uxQueueMessagesWaiting(xCharsForTx[ePort]) )
{
return pdTRUE;
}
else
{
return pdFALSE;
}
}
/*-----------------------------------------------------------*/
/* Close the serial port */
void vSerialClose( eCOMPort ePort )
{
unsigned portCHAR ucByte;
/* Turn off the interrupt. We may also want to delete the queues and/or
re-install the original ISR. */
portENTER_CRITICAL();
{
/* turn off the Tx interrupt */
prvInterruptOff(ePort);
/* now turn off the Rx interrupt */
switch( ePort )
{
case serCOM1:
ucByte = UCSR0B;
ucByte &= ~_BV(RXCIE0);
UCSR0B = ucByte;
break;
case serCOM2:
ucByte = UCSR1B;
ucByte &= ~_BV(RXCIE1);
UCSR1B = ucByte;
break;
case serCOM3:
ucByte = UCSR2B;
ucByte &= ~_BV(RXCIE2);
UCSR2B = ucByte;
break;
case serCOM4:
ucByte = UCSR3B;
ucByte &= ~_BV(RXCIE3);
UCSR3B = ucByte;
break;
default:
break;
}
/* invalidate the queue handle */
//xRxedChars[ePort] = NULL;
//xCharsForTx[ePort] = NULL;
// Don’t do the above two lines as this leads to memory leakage on the massive scale!!!
}
portEXIT_CRITICAL();
}
/*-----------------------------------------------------------*/
/* Turn the transmit interrupt ON */
static void prvInterruptOn(eCOMPort ePort)
{
unsigned portCHAR ucByte;
switch( ePort )
{
case serCOM1:
ucByte = UCSR0B;
ucByte |= _BV(UDRIE0);
UCSR0B = ucByte;
break;
case serCOM2:
ucByte = UCSR1B;
ucByte |= _BV(UDRIE0);
UCSR1B = ucByte;
break;
case serCOM3:
ucByte = UCSR2B;
ucByte |= _BV(UDRIE0);
UCSR2B = ucByte;
break;
case serCOM4:
ucByte = UCSR3B;
ucByte |= _BV(UDRIE0);
UCSR3B = ucByte;
break;
default:
break;
}
}
/* Turn the transmit interrupt OFF */
static void prvInterruptOff(eCOMPort ePort)
{
unsigned portCHAR ucByte;
switch( ePort )
{
case serCOM1:
ucByte = UCSR0B;
ucByte &= ~_BV(UDRIE0);
UCSR0B = ucByte;
break;
case serCOM2:
ucByte = UCSR1B;
ucByte &= ~_BV(UDRIE0);
UCSR1B = ucByte;
break;
case serCOM3:
ucByte = UCSR2B;
ucByte &= ~_BV(UDRIE0);
UCSR2B = ucByte;
break;
case serCOM4:
ucByte = UCSR3B;
ucByte &= ~_BV(UDRIE0);
UCSR3B = ucByte;
break;
default:
break;
}
}
/*-----------------------------------------------------------*/
/*************************************************************
* Interrupt handlers below
*************************************************************/
/*
* serial port 1 Rx and Tx interrupt handlers
*/
ISR( USART0_RX_vect )
{
portCHAR cChar;
portBASE_TYPE bResVal;
/* Get the character and post it on the queue of Rxed characters.
If the post causes a task to wake force a context switch as the woken task
may have a higher priority than the task we have interrupted. */
cChar = UDR0;
portENTER_CRITICAL();
bResVal = xQueueSendFromISR( xRxedChars[0], &cChar, pdFALSE );
portEXIT_CRITICAL();
if( bResVal == pdTRUE )
{
taskYIELD();
}
}
/*-----------------------------------------------------------*/
ISR( USART0_UDRE_vect )
{
portCHAR cChar;
signed portCHAR cTaskWoken;
portBASE_TYPE bResVal;
portENTER_CRITICAL();
bResVal = xQueueReceiveFromISR( xCharsForTx[0], &cChar, &cTaskWoken );
portEXIT_CRITICAL();
if( bResVal == pdTRUE )
{
/* Send the next character queued for Tx. */
UDR0 = cChar;
}
else
{
/* Queue empty, nothing to send. */
prvInterruptOff(serCOM1);
}
}
/*
* serial port 2 Rx and Tx interrupt handlers
*/
ISR( USART1_RX_vect )
{
portCHAR cChar;
/* Get the character and post it on the queue of Rxed characters.
If the post causes a task to wake force a context switch as the woken task
may have a higher priority than the task we have interrupted. */
cChar = UDR1;
if( xQueueSendFromISR( xRxedChars[1], &cChar, pdFALSE ) )
{
taskYIELD();
}
}
/*-----------------------------------------------------------*/
ISR( USART1_UDRE_vect )
{
portCHAR cChar;
signed portCHAR cTaskWoken;
if( xQueueReceiveFromISR( xCharsForTx[1], &cChar, &cTaskWoken ) == pdTRUE )
{
/* Send the next character queued for Tx. */
UDR1 = cChar;
}
else
{
/* Queue empty, nothing to send. */
prvInterruptOff(serCOM2);
}
}
/*
* serial port 3 Rx and Tx interrupt handlers
*/
ISR( USART2_RX_vect )
{
portCHAR cChar;
/* Get the character and post it on the queue of Rxed characters.
If the post causes a task to wake force a context switch as the woken task
may have a higher priority than the task we have interrupted. */
cChar = UDR2;
if( xQueueSendFromISR( xRxedChars[2], &cChar, pdFALSE ) )
{
taskYIELD();
}
}
/*-----------------------------------------------------------*/
ISR( USART2_UDRE_vect )
{
portCHAR cChar;
signed portCHAR cTaskWoken;
if( xQueueReceiveFromISR( xCharsForTx[2], &cChar, &cTaskWoken ) == pdTRUE )
{
/* Send the next character queued for Tx. */
UDR2 = cChar;
}
else
{
/* Queue empty, nothing to send. */
prvInterruptOff(serCOM3);
}
}
/*
* serial port 4 Rx and Tx interrupt handlers
*/
ISR( USART3_RX_vect )
{
portCHAR cChar;
/* Get the character and post it on the queue of Rxed characters.
If the post causes a task to wake force a context switch as the woken task
may have a higher priority than the task we have interrupted. */
cChar = UDR3;
if( xQueueSendFromISR( xRxedChars[3], &cChar, pdFALSE ) )
{
taskYIELD();
}
}
/*-----------------------------------------------------------*/
ISR( USART3_UDRE_vect )
{
portCHAR cChar;
signed portCHAR cTaskWoken;
if( xQueueReceiveFromISR( xCharsForTx[3], &cChar, &cTaskWoken ) == pdTRUE )
{
/* Send the next character queued for Tx. */
UDR3 = cChar;
}
else
{
/* Queue empty, nothing to send. */
prvInterruptOff(serCOM4);
}
}