Debugging embedded systems can be frustrating at times. When debugging your embedded projects, won’t it be better if we can use C standard library functions like printf() ? Many of us have same questions in mind i.e. How to use printf in keil uvision? or How to retarget printf for my ARM microcontroller? Also, after learning UART programming an obvious though would be like, how can I redirect printf to UART/Serial port in keil and print the debug output on my PC using Terminal software? Well, in this Tutorial I will try to anwser these questions.
Low-Level I/O functions used by printf()
For ARM based Microcontrollers, C Standard library functions are supported via ARM C Library and Microlib. As with the standard library, printf() and scanf() are declared in stdio.h (Standard Input/Ouput header) file. Hence, the first thing to note is that we need to include stdio.h in your Keil projects. The printf() function depends on stream I/O function fputc() for printing on target device, depending on how fputc() is retargeted. Similarly scanf() function depends on fgetc() for fetching user input.
Bascially fputc() writes a character to a stream (FILE *). A stream represents a communication channel to either: a Device, File or Process. Generally when we want to display the output in console/terminal , the stream is either STDOUT or STDERR. Writing to either of the stream will divert or redirect the written characters to console output. Likewise fgetc() reads a character from a given stream. For getting input from user via a console, the stream used is STDIN.
Re-Implementing C Library functions for Retargeting printf()
So, our job now is rewrite fputc() and fgetc() so we can perform Output and Input from UART or Serial Port. These functions are in turn dependent on UART routines which are responsible for tranmission and reception of data characters (BYTEs). If you want to use ARM C Library which is default, then you must also redefine __FILE structure along with __stdout for fputc() and __stdin for fgetc() as given below:
struct __FILE
{
int dummyVar; //Just for the sake of redefining __FILE, we won't we using it anyways ;)
};
FILE __stdout;
FILE __stdin;
int fputc(int c, FILE * stream)
{
uartWrite(c);
return c; //return the character written to denote a successfull write
}
int fgetc(FILE * stream)
{
char c = uartRead();
uartWrite(c); //To echo Received characters back to serial Terminal
return c;
}
Since we want to use printf to redirect output to UART/Serial port, the paramter stream is not required. As saw in the code above, to re-implement them we will need 2 basic UART functions: one to read and other to write a character. Hence, Lets define 2 functions viz. uartWrite() and uartRead(). Note that these are like driver functions which are device specific i.e. you need to rewite these functions for your specific microcontroller. I have given the code for LPC176x and LPC214x microcontrollers in Example section below. These can implemented for any ARM microcontroller of your choice like STM32, LPC13xx, LPC11xx etc in KEIL uVision.
void uartWrite(char c) //Used by fputc()
{
//Device specific code to transmit a byte on TX pin.
//For code See Example section
//Transmit the character(byte)
}
char uartRead(void) //Used by fgetc()
{
//Device specific code to Receive a byte from RX pin.
//For code See Example section
//return received chararacter(byte)
}
The function call heirarchy:
And thats pretty much it. Now you can directly use printf redirected to UART in KEIL. Lets do some real world examples.
Retargeted printf() UART Examples
Redirect/Retarget printf() output using UART on ARM-Cortex-M3 LPC1768/LPC1769
In this example we will use UART0 of LPC176x MCU to redirect printf output running at a baud-rate of 115200. The uart routines used below are from my previous LPC1768 UART Tutorial. Here is screenshot of this example in action on LPC176x:
/* (C) Umang Gajera. www.ocfreaks.com
Example: Retarget printf() & scanf() for ARM Cortex-M3 LPC1768/LPC1769 in KEIL
UART code from OCFreaks LPC1768 UART Tutorial
@ https://www.ocfreaks.com/lpc1768-uart-programming-tutorial/
License: GPL.*/
#include <lpc17xx.h>
#include <stdio.h> //Obviously!
#include "uart.h" //for initUART0() and other macros
struct __FILE
{
int dummyVar; //Just for the sake of redefining __FILE, we won't we using it anyways ;)
};
FILE __stdout; //STDOUT
FILE __stdin; //STDIN
int fputc(int c, FILE * stream)
{
uartWrite(c); //Transmit Character
return c; //return the character written to denote a successful write
}
int fgetc(FILE * stream)
{
char c = uartRead(); //Receive Character
uartWrite(c); //To echo Received characters back to serial Terminal
return c;
}
void uartWrite(char c)
{
while ( !(LPC_UART0->LSR & THRE) ); //wait till the THR is empty
if( c == '\n' ) //Send
{
LPC_UART0->THR = CARRIAGE_RETURN;
while( !(LPC_UART0->LSR & THRE ));
LPC_UART0->THR = LINE_FEED;
}
else
{
LPC_UART0->THR = c;
}
}
char uartRead(void)
{
while( !(LPC_UART0->LSR & RDR) ); //wait for data to arrive
return LPC_UART0->RBR;
}
int main(void)
{
int num=0;
//SystemInit(); //Gets called by startup code before main()
initUART0(); //Init UART0 at baud-rate: 115200 @ 25Mhz PLCK
printf("Hey! This is your beloved printf().\nI have been redirected here with scanf()!\n");
while(1)
{
printf("Enter an Integer between 0 and 1000:");
scanf("%d",&num);
if(num>=0 && num<= 1000) printf("\nYou entered: %d\n",num);
else printf("\nInvalid input!\n");
}
//return 0; //This won't execute.
}
Redirect/Retarget printf() output using UART on ARM7 LPC2148
In this example we will use UART0 of LPC214x MCU to redirect printf() output and scanf() input. The baud-rate is configured to 9600 bauds. The UART functions for read and write are taken from my previous LPC2148 UART Tutorial.
/* (C) Umang Gajera. www.ocfreaks.com
Example: Retarget printf() & scanf() using UART on ARM7 LPC2148 in KEIL
UART code from OCFreaks LPC2148 UART Tutorial
@ https://www.ocfreaks.com/lpc2148-uart-programming-tutorial/
License: GPL.*/
#include <lpc214x.h>
#include <stdio.h> //Obviously!
#include "uart.h" //for initUART0() and other macros
struct __FILE
{
int dummyVar; //Just for the sake of redefining __FILE, we won't we using it anyways ;)
};
FILE __stdout; //STDOUT
FILE __stdin; //STDIN
int fputc(int c, FILE * stream)
{
uartWrite(c); //Transmit Character
return c; //return the character written to denote a successful write
}
int fgetc(FILE * stream)
{
char c = uartRead(); //Receive Character
uartWrite(c); //To echo Received characters back to serial Terminal
return c;
}
void uartWrite(char c)
{
while (!(U0LSR & THRE)); //wait till the THR is empty
if( c == '\n' ) //Send
{
U0THR = CARRIAGE_RETURN;
while( !(U0LSR & THRE ));
U0THR = LINE_FEED;
}
else
{
U0THR = c;
}
}
char uartRead(void)
{
while(!(U0LSR & RDR)); // wait till any data arrives
return U0RBR;
}
int main(void)
{
/*CCLK is set to 60Mhz by Startup code*/
int num=0;
VPBDIV = 0x01; // set PCLK same as CCLK i.e 60Mhz for UART
initUART0(); //Init UART0 @ 9600 baud-rate 60Mhz PLCK
printf("Hey! This is your beloved printf().\nI have been redirected here with scanf()!\n");
while(1)
{
printf("Enter an Integer between 0 and 1000:");
scanf("%d",&num);
if(num>=0 && num<= 1000) printf("\nYou entered: %d\n",num);
else printf("\nInvalid input!\n");
}
//return 0; //This won't execute.
}