Zoë Blade's notebook

ATmega328P

ATmega328P tech specs

  • Company: Atmel
  • Type: Microcontroller
  • Instructions: 131[1]
  • Clock frequency: 16 MHz
  • RAM: 2 KB

The ATmega328P is an 8-bit microcontroller by Atmel.

MIDI

The ATmega328P has a USART perfectly suited for implementing MIDI. This is accessed via registers. Within C, these simply appear as special variables.

You can set its baud rate with UBRR0H and UBRR0L. Set these to 0x00 and 0x1F respectively to specify MIDI's 31.25 kbaud rate.

The default setting is asynchronous UART, with 8 data bits, no parity, and 1 stop bit (8N1) — perfect for MIDI.

You can enable receiving by setting its bit RXEN0; enable transmission by setting UCSR0B's bit TXEN0; enable a receive-complete interrupt by setting its bit RXCIE0; and enable a ready-to-transmit interrupt by setting its bit UDRIE0.

To receive a byte, first check if UCSR0A's RXC0 bit is set. If it is, there's at least one more byte of received data waiting for you to read. To access it, simply read UDR0. This will then automatically make UDR0 store the next received byte, ready to read at the same location, if there is one yet. Again, check UCSR0A's RXC0 bit first before doing so.

To transmit a byte, first check if UCSR0A's UDRE0 bit is set. If it is, it's safe. You can transmit the next byte of data by copying it to UDR0.

You can receive and transmit data at any time, but you'll likely want to do both as soon as possible, which is where the interrupts come in. If you enabled the receive-complete interrupt, you can make a function for it to call:

ISR(USART_RX_vect)
{
}

And for the ready-to-transmit interrupt:

ISR(USART_UDRE_vect)
{
}

Putting it all together, you might start out with something along these lines:

#include <stdint.h>

/*
 * Bitwise macros
 */

#define SET_BIT(byte, bitNumber) byte |= 1 << bitNumber
#define UNSET_BIT(byte, bitNumber) byte &= ~(1 << bitNumber)
#define GET_BIT(byte, bitNumber) (byte & 1 << bitNumber)

/*
 * MIDI variables
 */

uint8_t  byteReceived = 0x00;
uint8_t  byteToTransmit = 0x00;

/*
 * MIDI received interrupt
 */

ISR(USART_RX_vect)
{
    while (GET_BIT(UCSR0A, RXC0)) {
        byteReceived = UDR0;
    }
}

/*
 * MIDI ready to transmit interrupt
 */

ISR(USART_UDRE_vect)
{
    if (GET_BIT(UCSR0A, UDRE0)) {
        UDR0 = byteToTransmit;
    }
}

/*
 * Initialise everything at bootup
 */

void setup()
{
    /*
     * Set up the inputs and outputs
     */

     UNSET_BIT(DDRD, DDD0);       /* Set MIDI RX as an input */
     SET_BIT(DDRD, DDD1);         /* Set MIDI TX as an output */

    /*
     * Initialise MIDI
     */

    UBRR0H = 0x00;
    UBRR0L = 0x1F;                /* Set MIDI baud rate, 31.25 kHz
                                   * (as per ATmega328P data sheet P146).
                                   * 16,000,000 Hz / (16 * (31 + 1)) = 31,250 Hz;
                                   * (16,000,000 Hz / (16 * 31,250 Hz)) - 1 = 31 */
    SET_BIT(UCSR0B, RXEN0);       /* Enable receiving */
    SET_BIT(UCSR0B, TXEN0);       /* Enable transmission */
    SET_BIT(UCSR0B, RXCIE0);      /* Enable receive-complete interrupt */
    SET_BIT(UCSR0B, UDRIE0);      /* Enable ready-to-transmit interrupt */
}

How you handle the received and transmitted bytes is up to you. The main thing is not to overburden the interrupts.

References

  1. "ATmega328P" Atmel, 2015

Downloads

Documentation

Atmel: ATmega328P

Microcontrollers: ATmega328P | TMP80C49P | μPD650 | μPD8048