2016-05-21

Writing Debug Output

For a while, as I've started working on more complex features, I started having more and more problems getting my code to work correctly. Working with Rust, I don't have a lot of other embedded code to look at[1] and, except for a single blinking LED, I haven't had any way of communicating the program's state back to me. I took some measures to ease the difficulty of developing new features, but it hasn't nearly been enough. It's time for bringing in some bigger guns: Writing debug output that shows up on my computer.

There are several ways for the microcontroller to send messages to the outside world, but since it connects to my office computer via USB, that limits the options. The SAM3X8E has native support for USB[2], but it's been very difficult to wrap my had around it. This is exactly the kind of complex feature that I need debug support for in the first place.

Fortunately there's another option: The Arduino Due actually contains a second microcontroller that connects to the SAM3X8E's UART pins and connects to one of the board's USB ports on the other side. From the program's perspective, UART is a lot simpler to handle than USB, so this seemed to be a much better approach to get debugging capability.[3]

To get started with UART, we first need to configure the pins it is going to use. There are two pins total, one for transmitting and one for receiving. For debug output, we're only going to transmit, so I'm mostly ignoring the receiving part.

I've talked about parallel I/O before. It can be used to controll all the I/O pins directly, but in this case, we want the UART peripheral to control the pin for us. Here's how we configure the pin to do that:

let tx_pin = unsafe { pio::a().pin_9() };
let mut tx_pin = tx_pin
    .disable()
    .enable_pull_up();

tx_pin.select_peripheral_a();
            

A few things happen here:

  1. The pin used by UART for transmitting is pin 9 of Parallel I/O Controller A. We create a handle for that.[4]
  2. By disabling the pin, we tell the Parallel I/O Controller that we don't need it to handle it. In effect, this gives full control to whatever peripheral the pin is assigned to.[5]
  3. Enabling the pull-up resistor ensures that the pin's output signal is always in a defined state (high potential to be specific, therefore the name "pull-up").[6]
  4. Each pin is assigned to up to two peripherals. After telling the Parallel I/O Controller that it's not responsible for that pin, we need to select which other peripheral actually is. For this pin, UART is defined as peripheral A, so that's what we're selecting.[7]

There are two things to take note of:

  1. As you might have noticed, I added additional methods to the parallel I/O code to support all of this. Since there is nothing groundbreakingly new here, I'm not going to go into it right now. Please refer to the previous article about parallel I/O or check out the full code on GitHub.
  2. None of this is strictly necessary in the context of our program. Pins are disbled by default, pull-ups are enabled by default and peripheral A is selected by default. However, we might move the code around later, so putting everything in a defined state before using it is more robust.

Now that the I/O pin is set up, we can get started with UART itself. Before doing anything with it, let's put that too a defined state:

(*UART).control.write(
    uart::RSTRX | uart::RSTTX | uart::RXDIS | uart::TXDIS
);
            

Here we are writing the reset and disable bits for both the transmitter and receiver to UART's control register.[8][9] I believe this is total overkill, actually. If I'm reading the documentation correctly, just resetting should be enough, but all the example code I could find (for example in ASF) did it like this, so I decided to just roll with it instead of risking weird problems later.

Next up is setting the baud rate that our connection should run on. Baud means symbols per second. I don't know if that's any different from bits per seconds in this case (I guess not?), but honestly, I don't care that much. The important thing is to set the baud rate to the same value at both ends of the connection. Here, I've opted for a baud rate of 9600. A nice, low value that is none the less more than fast enough for our debug output.

const BAUD_RATE: u32 = 9600;
let clock_divisor = pmc::main_clock_frequency_hz() / BAUD_RATE / 16;
(*UART).baud_rate_generator.write(clock_divisor);
            

We can't just set the baud rate directly. Instead, we need to write a divisor value to a specific register. What that value needs to be is dependent on the main clock frequency that the microcontroller is currently running on. The divisor value can be computed as shown above.[10] Since the clock frequency can vary, depending on the microcontroller and its current configuration, it would be unwise to just hardcode a value in the code. Instead we need be be a bit more clever.

CMSIS, as used in the Arduino code, actually maintains a variable that it updates whenever the clock configuration changes. That seems error-prone though. Fortunately, I've found a way to get the current main clock frequency from the Power Management Controller at runtime:

pub fn main_clock_frequency_hz() -> u32 {
    let main_clock_frequency_within_16_slow_clock_cycles = unsafe {
        while (*PMC).main_clock_frequency.read() & MAINFRDY == 0 {}
        (*PMC).main_clock_frequency.read() & MAINF_MASK
    };

    main_clock_frequency_within_16_slow_clock_cycles
        * SLOW_CLOCK_FREQUENCY_HZ / 16
}
            

This code waits until the main clock frequency has stabilized (it might not be stable directly after it has changed), then reads it from the register. The value read is not the actual frequency, but the number of cycles within 16 cycles of the slow clock.[11] The slow clock on the SAM3X8E always runs at 32768 Hz.[12]

Some more configuration is necessary:

(*UART).mode.write(
    uart::MODE_NORMAL | uart::PARITY_NO
);
            

This puts UART into normal mode (as opposed to one of the test modes) and configures it to not use a parity bit.[13]

The only thing left to do is enabling UART:[14]

(*UART).control.write(uart::RXEN | uart::TXEN);
            

That finished the initialization. I've packaged all of that neatly into a struct for reasons that will soon become apparent:

pub type UndefinedPin   = Pin<status::Undefined, output_status::Undefined>;
pub type InitializedPin = Pin<status::Disabled , output_status::Undefined>;


pub struct Uart {
    _tx_pin: InitializedPin,
}

impl Uart {
    pub unsafe fn new(tx_pin: UndefinedPin) -> Self {
        // Initialization code goes here.

        Uart {
            _tx_pin: tx_pin,
        }
    }
}
            

Now that we've initialized the UART peripheral, we can use it to write debug messages. However, we don't just want to implement any old method to do that. We want to hook this up with Rust's formatting infrastructure. This infrastructure lives in the core library, which means it's directly available to us, no porting to be done!

All we need to do is implement the fmt::Write trait as shown here:

impl fmt::Write for Uart {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for &b in s.as_bytes() {
            unsafe {
                while (*UART).status.read() & uart::TXRDY == 0 {}
                (*UART).transmit_holding.write(b as u32);
            }
        }

        unsafe {
            while (*UART).status.read() & uart::TXEMPTY == 0 {}
        }

        // Error handling code omitted

        Ok(())
    }
}
            

For each byte of the given string, this method waits until the transmitter is ready, then writes the byte to the holding register. After all bytes have been written, it waits until they have been sent, then returns.[15][16]

All of this is very low-tech by design. If we were transferring large amounts of data, we could speed it up by writing into a buffer and using interrupts to tell us whenever another byte can be sent. This code is designed for debug output, however. We likely won't need to send a lot of data, and robustness is more important in this case than performance.

Now we can use Rust's infrastructure, like the write! macro, to print debug output. Here's how our program now looks, with debug output added:

// LED initialization goes here

let     uart_tx = unsafe { pio::a().pin_9() };
let mut uart    = unsafe { Uart::new(uart_tx) };

loop {
    // Ignore logging errors. It's not worth killing the program because of
    // failed debug output. It would be nicer to save the error and report
    // it later, however.
    let _ = write!(uart, "Start main loop iteration\n");

    // LED blinking and other stuff goes here
}
            

To receive the debug output on your computer, just use any serial monitor application. Be sure to set it to 8 data bits, no parity bit, and one stop bit. Make sure you connect the USB cable to the programming port, that's the one closer to the power jack, instead of the native USB port that we use for uploading the program.

For my own use, I've opted to write a simple serial monitor that I've added to the repository.

And that's it for today! As always, the full code is available on GitHub. See you next time!