Electrical – Atmel AVR XMEGA-A USART MPCM (Multiprocessor communication mode) configuration with ASF 3.33

atmelavruartxmega

I'm using two XMEGA256-A3BU's on XMEGA-A3BU Xplained boards. My goal is to make MPCM (Multiprocessor communication mode) possible between the two using their USART in synchronous mode.
They are physically connected so that the master's PC1 (SCL), PC2 (RXD) and PC3 (TXD) are connected to the slave's PC1 (SCL), PC3 (TXD) and PC2 (RXD) respectively.
I use ASF 3.33's "USART – serial service" to make the communication work.

Master's initialization code:

static usart_serial_options_t usart_options = {
    .baudrate = 1200;
    .charlength = USART_CHSIZE_8BIT_gc,
    .paritytype = USART_PMODE_ODD_gc,
    .stopbits = false
};

sysclk_enable_module(SYSCLK_PORT_C, PR_USART0_bm);
usart_serial_init(&USARTC0, &usart_options);

usart_set_mode(&USARTC0, USART_CMODE_SYNCHRONOUS_gc);

// Pin to set as output for clock signal.
ioport_configure_pin(IOPORT_CREATE_PIN(PORTC, 1), IOPORT_DIR_OUTPUT);
// Port to output clock signal on.
PORTCFG.CLKEVOUT = PORTCFG_CLKOUT_PC1_gc;

usart_set_rx_interrupt_level(&USARTC0, USART_RXCINTLVL_OFF_gc);
usart_set_tx_interrupt_level(&USARTC0, USART_TXCINTLVL_OFF_gc);

Slave's initialization code:

static usart_serial_options_t usart_options = {
    .baudrate = 1200;
    .charlength = USART_CHSIZE_8BIT_gc,
    .paritytype = USART_PMODE_ODD_gc,
    .stopbits = false
};

sysclk_enable_module(SYSCLK_PORT_C, PR_USART0_bm);
usart_serial_init(&USARTC0, &usart_options);

usart_set_mode(&USARTC0, USART_CMODE_SYNCHRONOUS_gc);

// Pin to set as input for clock signal.
ioport_configure_pin(IOPORT_CREATE_PIN(PORTC, 1), IOPORT_DIR_INPUT);
// Do not output the clock signal.
PORTCFG.CLKEVOUT = PORTCFG_CLKOUT_OFF_gc;

usart_set_rx_interrupt_level(&USARTC0, USART_RXCINTLVL_OFF_gc);
usart_set_tx_interrupt_level(&USARTC0, USART_TXCINTLVL_OFF_gc);

I use usart_serial_putchar(&USARTC0, buffer[i]) and usart_serial_getchar(&USARTC0, &buffer[i]) to send and receive data for testing, the final design will use DMA.

In order to make MPCM work (and make it possible to add more slaves to the bus), I know that I need to switch to the 1 start bit + 9 data bits + parity + 1 stop bit format, and I also know that somehow I should use these constants I found in iox256a3bu.h file:

#define USART_MPCM_bm  0x02  /* Multi-processor Communication Mode bit mask. */
#define USART_MPCM_bp  1  /* Multi-processor Communication Mode bit position. */

Could someone help me out with some ASF sample code? What should I add to set the slave's own address, and where should I define on the master who I'm intending to send the message to?

Best Answer

I've spent a few hours on the problem, and fortunately it works now with one master and two slaves. It doesn't use DMA because I think it is impractical in this case, so I decided to use interrupts instead.
First I changed the USART's format to 9 bits, so the initialization method starts like this:

static usart_serial_options_t usart_options = {
    .baudrate = 1200;
    .charlength = USART_CHSIZE_9BIT_gc,
    .paritytype = USART_PMODE_ODD_gc,
    .stopbits = false
};

And I changed the interrupt level at its end:

usart_set_rx_interrupt_level(USART_SERIAL, USART_RXCINTLVL_OFF_gc);
usart_set_tx_interrupt_level(USART_SERIAL, USART_TXCINTLVL_MED_gc);

Then I defined two methods to replace the usart_serial_putchar(&USARTC0, buffer[i]) and usart_serial_getchar(&USARTC0, &buffer[i]) methods:

static void usart_send_mpcm_data(usart_if usart, uint8_t data, bool is_address)
{
    /* Wait until we are ready to send the next 9 bits */
    while (usart_data_register_is_empty(usart) == false) {}

    /* First we need to set the 9. bit of the frame, which is the indicator for the address frame */
    if (is_address)
    {
        (usart)->CTRLB |= USART_TXB8_bm;
    } else
    {
        (usart)->CTRLB &= ~USART_TXB8_bm;
    }

    /* Second, let's load the 8-bit data into the DATA register of the USART */
    (usart)->DATA = data;
}


static bool usart_receive_mpcm_data(usart_if usart, uint8_t* data, uint8_t my_address)
{
    bool result = false;

    /* Are we in MPCM mode? */
    bool MPCM_mode = usart->CTRLB & USART_MPCM_bm;
    /* Is this an address frame? */
    bool address_frame = usart->STATUS & USART_RXB8_bm;

    /* Read char, clearing RXCIF */
    uint8_t read_data = usart->DATA;

    if ((!MPCM_mode) && (!address_frame))
    {
        /* This is a data frame, and we are interested in it */
        *data = read_data;
        result = true;
    }

    if (address_frame)
    {
        /* This frame is an address frame, so let's check if it addresses us (my_address) or broadcasts (0) */
        MPCM_mode = !((read_data == my_address) || (read_data == 0));
    }

    /* Set the MPCM bit in the USART's control register */
    if (MPCM_mode)
    {
        usart->CTRLB |= USART_MPCM_bm;
    } else
    {
        usart->CTRLB &= ~USART_MPCM_bm;
    }

    /* Return true if we read some useful data */
    return result;
}

After having these, I could setup the interrupt method on the slaves:

// USART-C Reception Complete Interrupt
ISR(USARTC0_RXC_vect)
{
    if (usart_receive_mpcm_data(&USARTC0, &usart_destination[usart_interrupt_counter], settings.device_index))
    {
        usart_interrupt_counter = (usart_interrupt_counter+1) % DMA_BUFFER_SIZE;
        rx_counter += 1;    
    }
}

On the master I can use the usart_send_mpcm_data(&USARTC0, usart_source[i], false); method to send data, or usart_send_mpcm_data(&USARTC0, slave_address, true); to change the target slave address. If the address is 0, then that's going to be a broadcast message, so all slaves will receive the data.