TITLE: Retargeting the C Library
AUTHOR: Chuck McManis
LAST UPDATE: 23-Jun-2014

Introduction

So in the blink article I mentioned how the lack of an operating system meant that many embedded programs ran with little use of the C library. However, many is not all, and it is convienient to use them if you can provide enough scaffolding for the C library in order to call those functions. This process of providing the scaffolding is called retargeting.

Retargeting GCC Embedded

The version of the C library that is used by the GCC Embedded project is called Newlib, and they use a variant called Newlib-nano. The goal of this particular article will be to get the libc functions of printf and fgets to work so that we might think about writing some code which interacts with us via a serial port. There is an piece of example code in the gcc distribution in the arm-none-eabi-4.8-xxx/share/gcc-arm-none-eabi/samples/src/retarget directory which we will start with.

The Easy Part

The simple part is the generic code, that is just a variant of hello.c which is shown below:

As you can see it includes the standard I/O header stdio.h and it calls printf on the string Hello Word!, then goes into an infinite loop. Compile and run this on any Linux system or OS X system and it prints out the string and hangs until you hit ^C.

The Less Easy Part

That program works because there is an operating system and its bound into the implementation of the subroutine printf which has other routines it calls and an entire I/O eco-system that it uses to put those things together. When retargeting embedded systems you have to fake all of that, and that is done with some ‘well known’ (basically documented) routines that are weakly bound to null routines in the embedded version of nanolib but you can implement to make real versions. The sample retarget code starts out pretty bare bones.

It too is pretty simple, but what you see is that there are functions like _write which use the C notion of a leading underscore means “private” and some key functions like read and write.

The Machine Specific Part

Now we step away from being able to write generic code that runs anywhere, and like our blink example we need to provide our own startup script with interrupt vectors, and a linker script tuned to our board or system. Finally we will need to add code to the retarget.c file that initializes a serial port and then sends characters to that serial port.

The linker script can be the same, this example targets the same Butterfly board as the blink example, but the startup needs to be updated if you are going to use interrupts from the STM32F4 series.

Adding in the GPIO definitions

In the blink code, I hand coded the bits that were set when configuring GPIOD. For small cases you can do that, for more complex cases you need some previously validated #define definitions.

For this example there are two choices, you can use the ones provided by ST Micro as part of their Firmware Package, but those come with the MCD-ST Liberty SW License which basically says you can’t use anything in their software (including #defines) for any processor other than ones made by ST Micro, and you don’t get a warranty, and they can sue you if you try to sell software you make that uses their software, in whole or in part. Or you can use an open source library like libopencm3 which is covered under an LGPL3 license and is actually ok with you selling software you made with it as long you give back any changes you make to the core libraries. I picked option #2.

This decision has two direct effects, one the symbol names for the interrupt vectors ideally need to be compatible with the weak references in libopencm3, (if they aren’t and you use the default loc3 startup code you won’t bind to the ISRs correctly), and two you will need to be sure that libopencm3 and its include files are available during compile and linking by using specific options in the Makefile.

A slightly altered flow

With these changes when the program is loaded it will have a slightly altered flow. The startup code will initialize memory that needs to be initialized, then it will call SystemInit, and then it will call whatever you defined as _START when you compiled the startup code (defaults to _start, I change it in the Makefile to main).

Using Libopencm3 to initialize the USART

The USART that is connected to the DB–9 port on the Embest baseboard is USART6. The GPIO pins are Port C, pins 6 and 7. There is no hardware flow control available. So initialization is simple.

Snippet from retarget.c : USART Initialization Code
#include <stdint.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>

/* This is the original retarget.c but with code filled in for
 * the STM32F4 and the Butterfly board.
 */
void retarget_init()
{
  // Initialize UART
/* *** Added Code Begin *** */
    /* MUST enable the GPIO clock in ADDITION to the USART clock */
    rcc_periph_clock_enable(RCC_GPIOC);

    /* This example uses PD5 and PD6 for Tx and Rx respectively
     * but other pins are available for this role on USART6 (our chosen
     * USART) as well, such as PA2 and PA3. You can also split them
     * so PA2 for Tx, PD6 for Rx but you would have to enable both
     * the GPIOA and GPIOD clocks in that case
     */
    gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6 | GPIO7);

    /* Actual Alternate function number (in this case 7) is part
     * depenedent, CHECK THE DATA SHEET for the right number to
     * use.
     */
    gpio_set_af(GPIOC, GPIO_AF8, GPIO6 | GPIO7);


    /* This then enables the clock to the USART6 peripheral which is
     * attached inside the chip to the APB2 bus. Different peripherals
     * attach to different buses, and even some UARTS are attached to
     * APB1 and some to APB2, again the data sheet is useful here.
     */
    rcc_periph_clock_enable(RCC_USART6);

    /* Set up USART/UART parameters using the libopencm3 helper functions */
    usart_set_baudrate(USART6, 115200);
    usart_set_databits(USART6, 8);
    usart_set_stopbits(USART6, USART_STOPBITS_1);
    usart_set_mode(USART6, USART_MODE_TX_RX);
    usart_set_parity(USART6, USART_PARITY_NONE);
    usart_set_flow_control(USART6, USART_FLOWCONTROL_NONE);
    usart_enable(USART6);

/* *** Added Code End *** */

}

The include files for the libopencm3 are pulled in, the interesting ones are the gpio.h and usart.h, and using the definitions USART6 is initialized and enabled. Further the GPIO pins PC6 and PC7 are converted to their alternate function (USART6). The baud rate is 115200, 8 bits, one stop bit, and no parity. I use the GNU screen program to connect my Linux host to the USB serial adapter, the command is screen /dev/ttyUSB0 115200.

Now if you were doing more I/O than just serial I/O you would need to know which number was in the fd paramater being passed to _write and _read. The important ones for serial I/O are 0 is the standard input, 1 is the standard output, and 2 is the standard error output. In this case everything is going out to the same port so I don’t check the value.

Typical _write implementation is shown below.

The implementation is simply, given a pointer to characters and a length, send that many characters to the USART.

The other only interesting bit is that when SystemInit is called I take that opportunity to set up the system clock. That is shown here:

And yes, presumably I could put the USART intialization here as well, I didn’t because the original sample code had call to retarget_init and I was following it closely.

Putting it all together

Putting it all together is pretty easy, the Makefile compiles the startup code, the retarget code, and main then links it all together with the same ldscript used for blink. The complete project is on Github if you want to fork it an play with it but I recommend doing it from scratch as it will help put that information deeper into your brain.