Interrupt-Driven Bufferred OCD Messaging

A more complex method of using AVR MCU OCD messaging involves a small I/O buffer into which a printf function can inject data which will be gradually transferred to the debugger. A timer interrupt is used to periodically service the printf buffer. On each interrupt a character will be sent from the buffer, if the message channel is ready and data is available. This example runs on a megaAVR® device with JTAG interface, but a similar mechanism can be employed on other AVR device architectures supporting OCD messaging.

#include <avr/io.h>
#include <stdio.h>
#include <avr/interrupt.h>

// Buffer allocated to OCD messaging
#define OCDR_BUFFER_SIZE 32
static uint8_t ocdr_buffer[OCDR_BUFFER_SIZE];

// Buffer pointers
static uint8_t head;
static uint8_t tail;

// Flag to indicate if a debugger is picking up the messages
static uint8_t debugger_attached = 1;

// Declarations
static int ocdr_putchar(char c, FILE *stream);
static FILE mystdout = FDEV_SETUP_STREAM(ocdr_putchar, NULL, _FDEV_SETUP_WRITE);

// Puts a char into the stream
static int ocdr_putchar(char c, FILE *stream)
{
    // If the debugger fails to collect, rather just abort
    if (!debugger_attached)
        return 1;   
    
    // Increment head with wrapping
    uint8_t tmphead;
    tmphead = (head + 1 );
    if (tmphead >= OCDR_BUFFER_SIZE)
        tmphead = 0;
    if (tmphead == tail) {
        // Overflow, abort
        debugger_attached = 0;
        return 0;
    }
    
    // Add data
    ocdr_buffer[tmphead] = c;
    head = tmphead;
    return 1;
}

// Timer interrupt regularly sends data
ISR(TIMER0_OVF_vect)
{
    // If no data, continue
    if (head == tail)
        return;
        
    // If the previous byte has not been collected, continue
    if (OCDR & 0x80)
        return;
        
    // Increment tail
    uint8_t tmptail = (tail + 1);
    if (tmptail >= OCDR_BUFFER_SIZE)
        tmptail = 0x00;
    tail = tmptail;
    
    // Send data to debugger
    OCDR = ocdr_buffer[tmptail];
    
    // Reset attached flag to allow hot-plugging
    debugger_attached = 1;
}

void ocdr_printf_init (void)
{
    // Zero buffer pointers
    head = 0;
    tail = 0;
    
    // TC setup. 8Mhz DIV32 gives ~1ms overflow ticks
    TIFR = (1 << TOV0);
    TIMSK = (1 << TOIE0);
    TCCR0 = (1 << CS01) | (1 << CS00);
    sei();
}

int main(void)
{
    // Port init
    DDRB |= 0xFF;
    PORTB = 0x55;
    
    // Buffer init
    stdout = &mystdout;
    ocdr_printf_init();
    
    // Demo loop
    uint8_t c = 0;
    while(1)
    {
        c++;
        PORTB = ~c;
        printf("led %d\n", c);

        // Must delay > ~8ms to guarantee printf delivery
        uint16_t delay = 0x3FFF;
        while (delay--)
            ;
    }
}