I have two interrupt service routines (ISR) which basically do the exact same
thing but each handles an interrupt from a different device (although the same type of device). Therefore, the logic is the same but they access different CPU registers and memory locations.
As a simple example consider the following code:
extern volatile unsigned int dev1_rx_buffer;
extern volatile unsigned int dev2_rx_buffer;
volatile unsigned char data;
void __attribute__((__interrupt__)) _dev1_interrupt(void)
{
/* Clear interrupt flag */
dev1.IF = 0;
if (dev1.IS_FULL) {
/* Read data from device */
data = dev1_rx_buffer;
} else {
/* do something else using registers of device 1 */
}
/* More stuff using registers of device 1 */
}
void __attribute__((__interrupt__)) _dev2_interrupt(void)
{
/* Clear interrupt flag */
dev2.IF = 0;
if (dev2.IS_FULL) {
/* Read data from device */
data = dev2_rx_buffer;
} else {
/* do something else using registers of device 2 */
}
/* More stuff using registers of device 2 */
}
How can I avoid the code duplication with the restrictions that apply to ISRs
(i.e. I cannot pass parameters to the ISRs and function calls should be avoided
because of their overhead).
I have thought of writing a template from which to generate the two ISRs using a higher level scripting language, but I'd prefer a solution using only C or C preprocessor macros.
In cases like this I usually have the front-end of the ISR (vector entry point) set up a pointer to a device specific block, and then call the common code with a pointer to that block.
Roughly (not worrying about ISR syntax etc.)
void __attribute__((__interrupt__)) Isr1(void)
{
CommonISR(&dev1info);
}
void __attribute__((__interrupt__)) Isr2(void)
{
CommonISR(&dev2info);
}
void CommonISR(Foo *devptr)
{
devptr->count = 0;
devptr->reset();
etc...
}
dev1info and dev2info are configured/initialized at startup; they might have pointers to HW registers, etc...
Why don't you use an inline helper function which gets pointers to the device and the buffer?
I would check the generated assembly to make sure the compiler does what I expect, though.
You could also use a macro, but IMHO it is not good to do this for functions this long.
If they are handling the same type of device it's quite reasonable to have just one interrupt handler handling multiple interrupts. You could check which flag was set at the top and continue on from there. However, I wouldn't recommend this if the two interrupt handlers are for different types of devices and just have the same logic flow.
are you sure that your compiler will not optimize a function calls ?
You definitely can use a macros to generate this code automatically, but it will be a bit ugly :
#define __CONCAT(a,b) a ## b
#define ISR_DECLARE(name) \
\
void __attribute__((__interrupt__)) _CONCAT(name,_interrupt)(void) \
{ \
/* Clear interrupt flag */ \
name.IF = 0; \
\
if (name.IS_FULL) \
{ \
/* Read data from device */ \
data = _CONCAT(name, _rx_buffer); \
} \
else \
{ \
/* do something else using registers of device 1 */ \
}\
/* More stuff using registers of device 1 */ \
}
and then:
ISR_DECLARE(dev_1)
ISR_DECLARE(dev_2)
But i would strongly suggest to check first if your compiler will optimize the code using inline, as suggested in previous posts.
Related
This is a question about sharing data that is "global", mimicking a piece of addressable memory that any function could access.
I'm writing code for an embedded project, where I've decoupled my physical gpio pins from the application. The application communicates with the "virtual" gpio port, and device drivers then communicate with the actual hardware. The primary motivation for this is the comfort it allows me in switching out what pins are connected to what peripheral when developing, and to include things like button matrices that use fewer physical pins while still handling them as regular gpio device registers.
typedef struct GPIO_PinPortPair
{
GPIO_TypeDef *port; /* STM32 GPIO Port */
uint16_t pin; /* Pin number */
} GPIO_PinPortPair;
typedef struct GPIO_VirtualPort
{
uint16_t reg; /* Virtual device register */
uint16_t edg; /* Flags to signal edge detection */
GPIO_PinPortPair *grp; /* List of physical pins associated with vport */
int num_pins; /* Number of pins in vport */
} GPIO_VirtualPort;
This has worked well in the code I've written so far, but the problem is that I feel like I have to share the addresses to every defined virtual port as a global. A function call would look something like this, mimicking the way it could look if I were to use regular memory mapped io.
file1.c
GPIO_VirtualPort LEDPort;
/* LEDPort init code that associates it with a list of physical pins */
file2.c
extern GPIO_VirtualPort LEDPort;
vgpio_write_pin(&LEDPort, PIN_1, SET_PIN);
I've searched both SO and the internet for best practices when it comes to sharing variables, and I feel like I understand why I should avoid global variables (no way to pinpoint where in code something happens to the data) and that it's better to use local variables with pointers and interface functions (like a "get current tick" function rather than reading a global tick variable).
My question is, given that I want to the keep the syntax as simple as possible, what is the best way to define these struct variables and then make them available for functions in other modules to use? Is it okay to use these struct variables as globals? Should I use some kind of master-array of pointers to every virtual port I have and use a getter function to avoid using extern variables?
I like to do it this way:
file1.h
typedef enum
{
VirtualPortTypeLED
} VirtualPortType;
typedef struct GPIO_PinPortPair
{
GPIO_TypeDef *port; /* STM32 GPIO Port */
uint16_t pin; /* Pin number */
} GPIO_PinPortPair;
typedef struct GPIO_VirtualPort
{
uint16_t reg; /* Virtual device register */
uint16_t edg; /* Flags to signal edge detection */
GPIO_PinPortPair *grp; /* List of physical pins associated with vport */
int num_pins; /* Number of pins in vport */
} GPIO_VirtualPort;
file1.c
GPIO_VirtualPort LEDPort;
void VirtualPortInit()
{
/* fill in all structures and members here */
LEDPort.reg = 0x1234;
...
}
GPIO_VirtualPort *VirtualPortGet(VirtualPortType vpt)
{
switch(vpt) {
case VirtualPortTypeLED:
return &LEDPort;
}
return NULL;
}
file2.c
#include file1.h
GPIO_VirtualPort *myLed;
VirtualPortInit();
myLed = VirtualPortGet(VirtualPortTypeLED);
Btw, I didn't compile this ... :)
To do this without using a global struct that references a given set of hardware or a global set of addresses you create a handle to the GPIO struct at the location that you want when starting out.
I'm not sure how the STM32 is laid out as I have no experience with that family of devices but I have seen and used this method in the situation you describe.
If your hardware is located at a particular address in memory, eg: 0x50, then your calling code asks a GPIO_Init() to give it a handle to the memory at that location. This still allows you to assign the struct at different locations if you need, for example:
/* gpio.h */
#include <stdef.h>
#include <stdint.h>
#include <bool.h>
typedef struct GPIO_Port GPIO_Port; // forward declare the struct definition
GPIO_Port *GPIO_Init(void *memory, const size_t size);
GPIO_Write_Pin(GPIO_Port *port_handle, uint8_t pin number, bool state);
A simple implementation of the GPIO_Init() function might be:
/* gpio.c */
#include "gpio.h"
struct GPIO_Port // the memory mapped struct definition
{
uint16_t first_register;
uint16_t second_register;
// etc, ordered to match memory layout of GPIO registers
};
GPIO_Port *GPIO_Init(void *memory, const size_t size)
{
// if you don't feel the need to check this then the
// second function parameter probably won't be necessary
if (size < sizeof(GPIO_Port *))
return (GPIO_Port *)NULL;
// here you could perform additional operations, e.g.
// clear the memory to all 0, depending on your needs
// return the handle to the memory the caller provided
return (GPIO_Port *)memory;
}
GPIO_Write_Pin(GPIO_Port *port_handle, uint8_t pin number, bool state)
{
uint16_t mask = 1u << pin_number;
if (state == true)
port_handle->pin_register |= mask; // set bit
else
port_handle->pin_register &= ~mask; // clear bit
}
Where the struct itself is defined only within the source file and there is no single global instance. Then you can use this like:
// this can be defined anywhere, or for eg, returned from malloc(),
// as long as it can be passed to the init function
#define GPIO_PORT_START_ADDR (0x50)
// get a handle at whatever address you like
GPIO_Port *myporthandle = GPIO_init(GPIO_PORT_START_ADDR, sizeof(*myporthandle));
// use the handle
GPIO_Write_Pin(myporthandle, PIN_1, SET_HIGH);
For the init function you can pass in the address of the memory with the real hardware location of the GPIO registers, or you can allocate some new block of RAM and pass the address of that.
Your addresses of the used memory do not have to be global, they are just passed to GPIO_Init() from the calling code and so ultimately could come from anywhere, the object handle takes over any subsequent referencing to that chunk of memory by passing to subsequent GPIO function calls. You should be able to build up your more complex functions around this idea of passing in the information that changes and the abstracted mapped memory such that you can still allow the functionality you mention with the "virtual" port.
This method has the benefit of separation of concerns (your GPIO unit is concerned only with the GPIO, not memory, something else can handle that), encapsulation (only the GPIO source needs to concern itself with the members of the GPIO port struct) and no/few globals (the handle can be instantiated and passed around as needed).
Personally I find this pattern pretty handy when it comes to unit testing. In release I pass the address for the real hardware but in test I pass an address for a struct somewhere in memory and test that the members are changed as expected by the GPIO unit - no hardware involved.
I am working on a project that requires USB communication.
I'm using a Nuvoton NUC123 which runs an ARM cortex M0 core, with a speed of 48MHz, 20kb RAM and 64kb flash memory.
The microcontroller implements a hardware interrupt whenever a USB endpoint gets data transmitted to it from the host, whether it's an Ack, Nak or a setup packet.
The sample code supplied by the manufacturer is rather dirty, it involves switch-case-ing the endpoint to which the interrupt belongs and if it is a setup packet containing a class-specific request, it make a switch-case for every interface or endpoint that may be the target of the request.
I figured I can make things prettier by defining an array of structure:
typedef void UsbEventCallback(uint32_t u32IntFlag, uint32_t u32EPSTS);
typedef uint32_t UsbClassReqCallback(void);
typedef struct
{
uint8_t ep_address;
uint32_t config;
uint32_t buff_offset;
UsbClassReqCallback *usb_classreq_cb;
UsbEventCallback *usb_event_cb;
} ATTR_PACKED EP_Config_Struct;
typedef struct
{
uint8_t interface_id;
UsbClassReqCallback *usb_classreq_cb;
} ATTR_PACKED Interface_Config_Struct;
extern const EP_Config_Struct EP_config_settings[TOTAL_NUM_ENDPOINTS];
extern const Interface_Config_Struct interfaces_config_settings[TOTAL_NUM_INTERFACES];
and then, in the interrupt callback I do:
switch( req_destination )
{
case 1: //interface
for ( uint8_t interface_index = 0 ; interface_index < TOTAL_NUM_INTERFACES ; interface_index++ )
{
if ( interfaces_config_settings[interface_index].interface_id == UsbDev.Setup.wIndex )
{
if ( interfaces_config_settings[interface_index].usb_classreq_cb == NULL )
return FALSE;
else
return (*interfaces_config_settings[interface_index].usb_classreq_cb)();
}
}
break;
case 2: //endpoint
for ( uint8_t ep_index = 0 ; ep_index < TOTAL_NUM_ENDPOINTS ; ep_index++ )
{
if ( EP_config_settings[ep_index].ep_address == UsbDev.Setup.wIndex )
{
if ( EP_config_settings[ep_index].usb_classreq_cb == NULL )
return FALSE;
else
return (*EP_config_settings[ep_index].usb_classreq_cb)();
}
}
break;
}
return FALSE;
My questions are:
Is it better to not actually make all these decisions and calling all these other functions in interrupt time? Am I better to just save the interrupt data and switch some flag on requiring the main thread to process the interrupt?
How important is it to return from the callback as soon as possible?
What do you think is the correct architecture for such a program?
Thank you
It is hard to say without knowing precisely your application, but your interrupt handler looks quite reasonable.
Generally for multi-tasks systems it is advised to do the least possible in interrupt handlers, because while an interrupt is being handled the different tasks on the systems are not being scheduled any more. This can be a lot more complicated than that, especially when using interrupt priorities and interrupt nesting, but still the general idea is to avoid staying too long in interrupt handlers.
For your USB driver, I would select the appropriate endpoint/interface in the interrupt handler, then write the data received in the appropriate queue/array and finally trigger a flag/semaphore to signal that some data has been received. I would then parse the data received in a normal task rather than directly in the interrupt handler, to keep the interrupt handler minimal.
Not sure whether it's critical to keep busy ISR in your project but in principle interrupt handler should return as soon as possible. If i were you I would do following regarless of the situation.
Parse protocol in ISR and then feed data to a ring buffer as a parsed packets. Ring buffer may require the ability of variable length data peek/push/pop according the protocol. Then proceed time consuming work in main.
I am attempting to pass a reference to an I/O pin as an function argument on a PIC24F MCU using C. For PICs, the device header file provides access to the i/o buffer registers via:
LATAbits.LATA2 = 0; // sets the pin (RA2 in this case) low.
if (PORTAbits.RA3) { // reads the state of the pin. (RA3)
I want to do something like this:
int main() {
Configure(); // Sets up peripherals, etc.
WaitForHigh(PORTAbits.RA3); // waits for pin RA3 to go hi.
...
return 0;
}
void WaitForHigh( ?datatype? pin_reference ) {
while( !pin_reference ); // Stays here until the pin goes hi.
}
So what datatype am I trying to pass here? And what's actually going on when I poll that pin? Below, I copy a relevant portion from the PIC24F device header that I'm using in case it helps.
#define PORTA PORTA
extern volatile unsigned int PORTA __attribute__((__sfr__));
typedef struct tagPORTABITS {
unsigned RA0:1;
unsigned RA1:1;
unsigned RA2:1;
unsigned RA3:1;
unsigned RA4:1;
unsigned RA5:1;
} PORTABITS;
extern volatile PORTABITS PORTAbits __attribute__((__sfr__));
Thank you in advance!
As an alternative to using a macro, a function can accept both the PORT register address (or latch register address, eg. LATA in the case of a pin configured for output) and the mask of the bit in the register that is needed. For example:
#include<p24FV32KA301.h> // defines both PORTA and _PORTA_RA3_MASK
void WaitForHigh( volatile unsigned int * port, pin_mask ) {
while( !(*port & pin_mask) ); // Stays here until the pin goes hi.
}
int main()
{
...
WaitForHigh( &PORTA, _PORTA_RA3_MASK ); // waits for pin RA3 to go hi.
...
return 0;
}
Please, note that the PORT bit values are obtained through a bit field, so, answering your question, you can't. Bit fields doesn't have address, so you cannot pass it as a pointer to a function.
Instead, you could use a Macro:
#define WaitForHigh(p) do{while(!(p));}while(0)
It is true that macros has it's draw backs on code readability, yet, given that proper care is taken, there are situations where they're the best solution. It is arguable if macro is the best solution in this Q&A, yet it is important to mention.
Thanks to the commenters for the suggestions to improve the macro safeness.
You can combine preprocessor processing with a function to get what you wan along with compile time checking of the symbols. For example:
#define PORT_FUNC(f, p, b) f(p, b)
#define WaitForHigh(p, b) PORT_FUNC(WaitForHighImp, &p, _ ##p## _ ##b## _MASK)
void WaitForHighImp(volatile unsigned* p, unsigned char mask)
{
while (!(*p & m))
;
}
int main()
{
WaitForHigh(PORTA, RA3);
}
The advantage of this approach is that you online say "PORTA" once and "RA3" once at the time of the call, you make sure the bit name is present in the port and that the bit is present.
I am having some trouble, and I cannot tell if it's my understanding of the Atmel syntax, the Atmel Studio 6.0 or, the program itself.
I cannot seem to get the interrupt handler to receive a simple string then do something. I have success with just implimenting a single character turning an LED when USART receives one character it turns the LED on, then if it receives a different character it turns the LED off. By the way I have a design board that the program is having some trouble getting to the receive sub routine because the send code within the main is so large, so it was suggested to me to utilize interrupts to fix this.
By the way, I am trialing this program on an EVK1100 AVR32 board MCU:AT32UC3A0512-U, not sure if any of you have played with these before, but they're pretty great. Not sure I like Atmel syntax though.
Anyway, you can see I'm doing nothing in the main at the moment until I get the receive portion working.
I'm fairly new to interrupts in the Atmel world.
Any help would be much appreciated. I made just a few modifications to the built in ASF USART interrup "INTC" project example.
Thanks,
#include <string.h>
#include <avr32/io.h>
#include "compiler.h"
#include "board.h"
#include "print_funcs.h"
#include "intc.h"
#if defined (__GNUC__)
# if defined (__AVR32_AP7000__)
# include "pm_at32ap7000.h"
# else
# include "power_clocks_lib.h"
# endif
#elif defined (__ICCAVR32__) || defined (__AAVR32__)
# if defined (__AT32AP7000__)
# include "pm_at32ap7000.h"
# else
# include "power_clocks_lib.h"
# endif
#endif
#include "gpio.h"
#include "usart.h"
//#include "conf_example.h"
# define EXAMPLE_TARGET_PBACLK_FREQ_HZ FOSC0 // PBA clock target frequency, in Hz
#if BOARD == EVK1100
# define EXAMPLE_USART (&AVR32_USART1)
# define EXAMPLE_USART_RX_PIN AVR32_USART1_RXD_0_0_PIN
# define EXAMPLE_USART_RX_FUNCTION AVR32_USART1_RXD_0_0_FUNCTION
# define EXAMPLE_USART_TX_PIN AVR32_USART1_TXD_0_0_PIN
# define EXAMPLE_USART_TX_FUNCTION AVR32_USART1_TXD_0_0_FUNCTION
# define EXAMPLE_USART_CLOCK_MASK AVR32_USART1_CLK_PBA
# define EXAMPLE_PDCA_CLOCK_HSB AVR32_PDCA_CLK_HSB
# define EXAMPLE_PDCA_CLOCK_PB AVR32_PDCA_CLK_PBA
# define EXAMPLE_USART_IRQ AVR32_USART1_IRQ
# define EXAMPLE_USART_BAUDRATE 57600
#endif
/**
* \brief The USART interrupt handler.
*
* \note The `__attribute__((__interrupt__))' (under GNU GCC for AVR32) and
* `__interrupt' (under IAR Embedded Workbench for Atmel AVR32) C function
* attributes are used to manage the `rete' instruction.
*/
#if defined (__GNUC__)
__attribute__((__interrupt__))
#elif defined(__ICCAVR32__)
__interrupt
#endif
static void usart_int_handler(void)
{
static char Cmnd[30];
int index = 0;
int c;
usart_read_char(EXAMPLE_USART, &c);
Cmnd[index++] = c;
if (c = '\r')
{
Cmnd[index] = '\0';
usart_write_line(EXAMPLE_USART, Cmnd);
}
}
/**
* \brief The main function.
*
* It sets up the USART module on EXAMPLE_USART. The terminal settings are 57600
* 8N1.
* Then it sets up the interrupt handler and waits for a USART interrupt to
* trigger.
*/
int main(void)
{
static const gpio_map_t USART_GPIO_MAP =
{
{EXAMPLE_USART_RX_PIN, EXAMPLE_USART_RX_FUNCTION},
{EXAMPLE_USART_TX_PIN, EXAMPLE_USART_TX_FUNCTION}
};
// USART options.
static const usart_options_t USART_OPTIONS =
{
.baudrate = 57600,
.charlength = 8,
.paritytype = USART_NO_PARITY,
.stopbits = USART_1_STOPBIT,
.channelmode = USART_NORMAL_CHMODE
};
#if BOARD == EVK1100 || BOARD == EVK1101 || BOARD == UC3C_EK \
|| BOARD == EVK1104 || BOARD == EVK1105 || BOARD == STK600_RCUC3L0 \
|| BOARD == STK600_RCUC3D
/*
* Configure Osc0 in crystal mode (i.e. use of an external crystal
* source, with frequency FOSC0) with an appropriate startup time then
* switch the main clock source to Osc0.
*/
pcl_switch_to_osc(PCL_OSC0, FOSC0, OSC0_STARTUP);
#elif BOARD == STK1000
pm_reset();
#elif BOARD == UC3L_EK
/*
* Note: on the AT32UC3L-EK board, there is no crystal/external clock
* connected to the OSC0 pinout XIN0/XOUT0. We shall then program the
* DFLL and switch the main clock source to the DFLL.
*/
pcl_configure_clocks(&pcl_dfll_freq_param);
/*
* Note: since it is dynamically computing the appropriate field values
* of the configuration registers from the parameters structure, this
* function is not optimal in terms of code size. For a code size
* optimal solution, it is better to create a new function from
* pcl_configure_clocks_dfll0() and modify it to use preprocessor
* computation from pre-defined target frequencies.
*/
#end if
// Assign GPIO to USART.
gpio_enable_module(USART_GPIO_MAP,
sizeof(USART_GPIO_MAP) / sizeof(USART_GPIO_MAP[0]));
// Initialize USART in RS232 mode.
usart_init_rs232(EXAMPLE_USART, &USART_OPTIONS,
EXAMPLE_TARGET_PBACLK_FREQ_HZ);
print(EXAMPLE_USART, ".: Using interrupts with the USART :.\r\n\r\n");
// Disable all interrupts.
Disable_global_interrupt();
// Initialize interrupt vectors.
INTC_init_interrupts();
/*
* Register the USART interrupt handler to the interrupt controller.
* usart_int_handler is the interrupt handler to register.
* EXAMPLE_USART_IRQ is the IRQ of the interrupt handler to register.
* AVR32_INTC_INT0 is the interrupt priority level to assign to the
* group of this IRQ.
*/
INTC_register_interrupt(&usart_int_handler, EXAMPLE_USART_IRQ, AVR32_INTC_INT0);
// Enable USART Rx interrupt.
EXAMPLE_USART->ier = AVR32_USART_IER_RXRDY_MASK;
print(EXAMPLE_USART, "Type a character to use the interrupt handler."
"\r\nIt will show up on your screen.\r\n\r\n");
// Enable all interrupts.
Enable_global_interrupt();
/**
* We have nothing left to do in the main, so we may switch to a device
* sleep mode: we just need to be sure that the USART module will be
* still be active in the chosen sleep mode. The sleep mode to use is
* the FROZEN sleep mode: in this mode the PB clocks are still active
* (so the USART module which is on the Peripheral Bus will still be
* active while the CPU and HSB will be stopped).
* --
* Modules communicating with external circuits should normally be
* disabled before entering a sleep mode that will stop the module
* operation: this is not the case for the FROZEN sleep mode.
* --
* When the USART interrupt occurs, this will wake the CPU up which will
* then execute the interrupt handler code then come back to the
* while(1) loop below to execute the sleep instruction again.
*/
while(1)
{
/*
* If there is a chance that any PB write operations are
* incomplete, the CPU should perform a read operation from any
* register on the PB bus before executing the sleep
* instruction.
*/
AVR32_INTC.ipr[0]; // Dummy read
/*
* When the device wakes up due to an interrupt, once the
* interrupt has been serviced, go back into FROZEN sleep mode.
*/
}
}
Try to keep your interrupt handlers short. Interrupt handlers or ISRs are executed usually with interrupts disabled. Their goal is to get in, do something, and get out quickly. If you have other interrupts running (like a real-time clock for example), they are blocked out while you're in the ISR and that can cause problems such as lost interrupts.
The best thing to do here would be to declare the input buffer as a global static. Also define a static uchar_t have_line or similar, and in the ISR, save each character until either the buffer is full (an important check!), or you receive a CR (\r). Also check for newline (\n). Save a zero to null-terminate the buffer, and then set have_line = 1.
In your main loop, wait for have_line to be non-zero, and then process what's in the input buffer. Finally, set have_line back to zero.
That way, your ISR is brief and fairly robust.
In your sample code, does the function usart_write_line require interrupts to be enabled?
Oops! Sorry, I just noticed that your index isn't a static variable. That means it'll be initialized to zero each time the routine is called. Add a static declarator in front of int index and that should sort it out.
I am looking for ideas for a receive buffer for a small application dealing with 15 byte packets at 921.6Kbaud over rs485. I am thinking of using a circular buffer as the interface between the UART ISR and main. As it is a microprocessor I was wanting to put
while (uartindex!=localindex) { do stuff }
in the
while (;;) {do forever}
part of main but I have been told this is not acceptable.
How do people deal with their uarts under similar circumstances?
ISR should fill a FIFO. Main should consume it.
Bellow a very simple fifo algorithm:
#define RINGFIFO_SIZE (1024) /* serial buffer in bytes (power 2) */
#define RINGFIFO_MASK (RINGFIFO_SIZE-1ul) /* buffer size mask */
/* Buffer read / write macros */
#define RINGFIFO_RESET(ringFifo) {ringFifo.rdIdx = ringFifo.wrIdx = 0;}
#define RINGFIFO_WR(ringFifo, dataIn) {ringFifo.data[RINGFIFO_MASK & ringFifo.wrIdx++] = (dataIn);}
#define RINGFIFO_RD(ringFifo, dataOut){ringFifo.rdIdx++; dataOut = ringFifo.data[RINGFIFO_MASK & (ringFifo.rdIdx-1)];}
#define RINGFIFO_EMPTY(ringFifo) (ringFifo.rdIdx == ringFifo.wrIdx)
#define RINGFIFO_FULL(ringFifo) ((RINGFIFO_MASK & ringFifo.rdIdx) == (RINGFIFO_MASK & (ringFifo.wrIdx+1)))
#define RINGFIFO_COUNT(ringFifo) (RINGFIFO_MASK & (ringFifo.wrIdx - ringFifo.rdIdx))
/* buffer type */
typedef struct{
uint32_t size;
uint32_t wrIdx;
uint32_t rdIdx;
uint8_t data[RINGFIFO_SIZE];
} RingFifo_t;
RingFifo_t gUartFifo;
(Care must be taken with this FIFO algorithm, size MUST be power of 2)
The ISR should behave like this:
void ISR_Handler()
{
uint8_t c;
while(UART_NotEmpty()) {
c = UART_GetByte();
RINGFIFO_WR(gUartFifo, c);
}
}
And the Main:
while(1)
{
if (!RINGFIFO_EMPTY(gUartFifo)) {
/* consume fifo using RINGFIFO_RD */
}
}
This algorithm reads the FIFO directly from the main loop, you should use a intermediate layer that checks if there is a full packet in the buffer, and deals with it, in such a manner that main would be like this:
uint8_t ptrToPacket;
uint32_t packetSize;
while(1)
{
if (!Uart_HasValidPacket()) {
Uart_GetPacket(&ptrToPacket, &packetSize)
/* Process packet using ptrToPacket and packetSize */
}
}
The approach you suggest would probably be workable if the uartindex is never written in the main loop (except to initialize it while interrupts are disabled), and localindex is never touched by the interrupt routine.
I would suggest that you make your buffer size be a power of 2, use unsigned integers for the two indices, and allow them to count freely over their full 32-bit size; use bit masking when indexing your buffer in both the "stuff" and "fetch" routines. If you do that, then
(unsigned)(uartindex-localindex)
should indicate how many characters are in the buffer, even when it's completely full, without requiring special-case behavior in the buffer-full case and without limiting an N-byte buffer to holding N-1 items.
Note that while the typecast in the aforementioned expression isn't strictly necessary, I would recommend including it since it makes obvious that the wrapping behavior when subtracting unsigned quantities is deliberate and expected.