The Cortex-M0 is an ultra low power 32-bit ARM Cortex-M microcontroller targeted for various nano-scale embedded applications. Retargetting printf allows directing output from the standard C library printf function to a custom destination like UART, LCD display or other peripherals instead of default semihosting console. This enables printing messages directly from C programs running on Cortex-M0 without need for debugger connection.
Overview of Cortex-M0 Architecture
The Cortex-M0 processor is a 32-bit RISC core optimized for low power consumption. Key features include:
- 32-bit ARMv6-M architecture
- Up to 48MHz clock speed
- Single-cycle 32-bit multiplier
- Nested Vectored Interrupt Controller
- Wake up interrupt controller
- Single precision hardware FPU (optional)
The Cortex-M0 integrates on-chip debug components like Embedded Trace Macrocell (ETM) and Micro Trace Buffer (MTB) for real-time tracing. It has a Memory Protection Unit (MPU) for memory access control and supports ARMv6 Thumb-2 instruction set for higher code density.
For I/O and peripherals, the Cortex-M0 includes upto 32 GPIO pins, SPI, I2C, UART, ADC and DAC. The debug interface uses Serial Wire Debug (SWD) and JTAG protocols. Overall, the Cortex-M0 microarchitecture provides an optimal balance of power, performance and cost for tiny battery-powered embedded devices.
Retargetting Printf to Output to UART
By default, the printf function in newlib C library for Cortex-M0 uses semihosting to send output to the debugger console. To retarget printf to UART serial port, we need to reimplement the low-level _write() function which is called by printf internally.
The steps are:
- Configure UART module for required baud rate, frame format and I/O pins.
- Implement _write() to send bytes to UART transmit buffer.
- Link the C library for your application with retargetted _write().
This will redirect any printf output to UART rather than semihosting. For example:
// UART initialization
void init_uart() {
// Enable clock for UART module
// Set baud rate, frame format
// Configure TX and RX pins
// Enable UART module
}
// Retargetted _write()
int _write(int fd, char* ptr, int len) {
// Load UART transmit buffer one byte at a time
for(int i = 0; i < len; i++) {
uart_send_byte(ptr[i]);
}
return len;
}
int main() {
init_uart(); // Initialize UART
printf("Hello World!"); // Output over UART
}
Optimizing Printf Output
There are several techniques to optimize printf output on Cortex-M0 to reduce code size and improve performance:
- Enable printf call optimization: Compiler flags like -ffunction-sections and –gc-sections can eliminate unused printf functionality.
- Use reduced printf library: Lightweight printf versions like miniprintf have smaller code size.
- Avoid float formatting: Omit any %f specifiers to avoid linking in floating point support.
- Use printf buffering: Buffer characters locally before sending to UART to reduce interrupts.
- Consider printf alternatives: Direct UART output functions can be more efficient for simple needs.
Let’s look at some examples of these optimizations:
// Optimized printf call
#include "printf.h" // Link only required printf functions
int main() {
printf("Value: %d", var); // No unused formatting features
}
// Reduced library
#include "miniprintf.h" // Smaller printf implementation
void main() {
mprintf("Result: %d", result); // Optimized for embedded systems
}
// Avoid floats
int main() {
uint32_t count = 42;
printf("Count: %lu", count); // Use integer format specifiers
}
// Printf buffering
#define PRINTF_BUF_SIZE 64
char printf_buf[PRINTF_BUF_SIZE];
int printf_idx;
int _write(int fd, char* ptr, int len) {
// Add chars to local buffer
// Transmit buffer when full
// Clear buffer
}
// Alternative I/O
void uart_printf(char *str) {
// Direct UART output
while(*str) {
uart_send_byte(*str++);
}
}
The optimal printf implementation depends on the application requirements. A combination of the above techniques allows efficient printf usage even on the memory and performance constrained Cortex-M0 MCU.
Retargetting Printf to LCD Display
For applications using LCD display, printf can also be retargetted to directly print messages on the LCD screen. This again requires implementing a custom _write() function.
For example with a character LCD:
#include "lcd.h" // LCD library
int _write(int fd, char *ptr, int len) {
for(int i = 0; i < len; i++) {
lcd_print_char(ptr[i]); // Print each char
}
return len;
}
int main() {
lcd_init(); // Initialize LCD
printf("Cortex-M0 LCD!"); // Print on LCD
}
The LCD library handles lower level interfacing like sending data and commands over SPI or I2C bus to the LCD controller. The retargetted _write() routes the printf output to lcd_print_char() function to display each character on the LCD screen.
For graphics LCD, printf can be made to print text at specific coordinates by implementing lcd_print_char() accordingly in the LCD library interface.
Conclusion
Retargetting printf allows flexible redirection of standard output to fit the design needs of tiny Cortex-M0 systems. Choosing appropriate optimizations can ensure printf usage has minimal impact on performance and code size. The ability to retarget printf makes it easier to integrate printf debugging in the development workflow for Cortex-M0 projects.