Writing to the console window
While a variant of the helloBlinky
recipe is usually the first program introduced in most embedded tutorials, the first program found most C textbooks usually outputs the string "Hello World" to the screen. To run such a program on our evaluation board, we'll need to install a terminal emulation program on our PC host. PuTTY® http://www.chiark.greenend.org.uk/~sgtatham/putty/, an open source terminal emulation program is a good choice. We also need to connect the evaluation board to the PC's (COM) serial port. Most PCs and laptops are no longer fitted with 9-pin D-type (COM) ports, so you may need to purchase a USB to Serial Adaptor cable.
Getting ready
Follow these steps to install PuTTY, and connect the evaluation board to the PC's COM port:
- If you're using a USB Serial Adaptor, then plug it into the laptop, and wait for the driver to be installed.
- Open the Control Panel, and make a note of the COM port that has been allocated (you will need this later to configure PuTTY).
- Connect the 9-Pin D-type UART1/3/4 connector on the evaluation board to the PC USB port, and ensure that the jumpers J13 and J14 are set to short pins 1 and 2 thereby selecting USART4. Pin 1 can be easily be identified by its square solder pad, easily visible on the underside of the board. Install PuTTY, and configure the serial connection to use the COM port you previously identified in Control Panel, configured to 115200 Baud, 8 data bits, 1 stop-bit, no parity or flow control.
How to do it…
- Create a new folder named
helloWorld
; invoke uVision5, and create a new project. Using the RTE manager, select the MCBSTM32F400 board, but don't check any of the board support tick boxes. Check CMSIS → CORE, RTOS (API) → KeilRTX, Device → Startup, and Device → STM32Cube Framework (API) → Classic. Click Resolve to automatically load any additional software components needed. Then exit by clicking on OK. - The source code for this project is pided between three source code files. Create a new file (File →New…), and enter the source code shown. Save the file (File →SaveAs) as
helloWorld.c
. The source file namedhelloWorld.c
contains the main function in the project, illustrated using the folding editor feature to hide the boilerplate./*************************************************** * Recipe: helloWorld_c2v0 * File: helloWorld.c * Purpose: Serial I/O Example *************************************************** * * Modification History * 2014 Created * 03.12.15 Updated for uVision_5.17 & DFP_2.6.0 * * Dr Mark Fisher, CMP, UEA, Norwich, UK. ***************************************************/ #include "stm32F4xx_hal.h" #include "cmsis_os.h" #include <stdio.h> #include "Serial.h" /* Function prototypes */ void wait(unsigned long delay); extern void init_serial(void); extern int sendchar(int c); extern int getkey(void); #ifdef __RTX /* Function prototypes */ void wait(unsigned long delay); extern void init_serial(void); extern int sendchar(int c); extern int getkey(void); #ifdef __RTX _____________________________________________________ /*------------------------------------------------- System Clock Configuration *-------------------------------------------------*/ _____________________________________________________ void SystemClock_Config(void) { /*------------------------------------------------- * wait *--------------------------------------------------*/ void wait (unsigned long delay){ unsigned long i; for (i = 0; i < delay; i++) ; } int main (void) { HAL_Init (); /* Init Hardware Abstraction Layer */ SystemClock_Config (); /* Config Clocks */ SER_Init(); for (;;) { /* Loop forever */ wait(1000000); printf("Hello World!\n"); } }
- In the project window, right-click on the
Source Group 1
folder, and add the source filehelloWorld.c
to the project. - Create a new file, enter the following code, name the new file
Retarget.c
, and add it in the project. This source file redefines some functions used by C's standard input output library,<stdio.h>
./*------------------------------------------------- * Name: Retarget.c * Purpose: 'Retarget' layer for target- * dependent low level functions * Note(s): *------------------------------------------------- * This file is part of the uVision/ARM * development tools. *-------------------------------------------------*/ #include <stdio.h> #include <rt_misc.h> #include "Serial.h" #pragma import(__use_no_semihosting_swi) struct __FILE { int handle; /* Add whatever you need here */ }; FILE __stdout; FILE __stdin; int fputc(int c, FILE *f) { return (SER_PutChar(c)); } int fgetc(FILE *f) { return (SER_GetChar()); } int ferror(FILE *f) { /* Your implementation of ferror */ return EOF; } void _ttywrch(int c) { SER_PutChar(c); } void _sys_exit(int return_code) { label: goto label; /* endless loop */ }
- Create a new file, enter the
SER_Init()
function, name the new fileSerial.c
, and add it in the project./*------------------------------------------------- * Name: Serial.c * Purpose: Low level serial routines * Note(s): *------------------------------------------------- * This file is part of the uVision/ARM * development tools. *-------------------------------------------------*/ #include "stm32f4xx.h" /* STM32F4xx Defs */ #include "Serial.h" #ifdef __DBG_ITM volatile int32_t ITM_RxBuffer; #endif /*------------------------------------------------- * SER_Init: Initialize Serial Interface *-------------------------------------------------*/ void SER_Init (void) { #ifdef __DBG_ITM ITM_RxBuffer = ITM_RXBUFFER_EMPTY; #else RCC->APB1ENR |= (1UL << 19); /* Enable USART4 clock */ RCC->APB2ENR |= (1UL << 0); /* Enable AFIO clock */ RCC->AHB1ENR |= (1UL << 2); /* Enable GPIOC clock */ GPIOC->MODER &= 0xFF0FFFFF; GPIOC->MODER |= 0x00A00000; GPIOC->AFR[1] |= 0x00008800; /* PC10 UART4_Tx, PC11 UART4_Rx (AF8) */ /* Configure UART4: 115200 baud @ 42MHz, 8 bits, 1 stop bit, no parity */ UART4->BRR = (22 << 4) | 12; UART4->CR2 = 0x0000; UART4->CR1 = 0x200C; #endif }
- Add the functions
SER_getc()
andSER_putc()
toSerial.c
/*------------------------------------------------- * SER_PutChar: Write a char to Serial Port *-------------------------------------------------*/ int32_t SER_PutChar (int32_t ch) { #ifdef __DBG_ITM int i; ITM_SendChar (ch & 0xFF); for (i = 10000; i; i--) ; #else while (!(UART4->SR & 0x0080)); UART4->DR = (ch & 0xFF); #endif return (ch); } /*------------------------------------------------- * SER_GetChar: Read a char from Serial Port *-------------------------------------------------*/ int32_t SER_GetChar (void) { #ifdef __DBG_ITM if (ITM_CheckChar()) return ITM_ReceiveChar(); #else if (UART4->SR & 0x0020) return (UART4->DR); #endif return (-1); }
- Create a new file, enter the following code, name the file
Serial.h
, and add it to the project. This is the header file that declares the function prototypes forSerial.c
/*------------------------------------------------- * Name: Serial.h * Purpose: Low level serial definitions * Note(s): *-------------------------------------------------*/ #ifndef __SERIAL_H #define __SERIAL_H extern void SER_Init (void); extern int SER_GetChar (void); extern int SER_PutChar (int c); #endif
- Configure PuTTY as shown in part a) of the following image. Build, download, and run the program to achieve the output shown in b)
How it works…
The evaluation board and PC communicate by exchanging data using an RS232 serial Input/Output (I/O) connection (http://en.wikipedia.org/wiki/RS-232). RS232 is a 2-wire full-duplex communications standard. PuTTY manages the protocol at the PC, but we are responsible for the evaluation board. To use serial I/O, we need to configure the microcontroller's Universal Synchronous/Asynchronous Receiver/Transmitter (USART). We can do this by including a peripheral driver applications interface (API) in our project. uVision5's RTE manager includes a suitable API, but this provides many more features than we need for our simple helloWorld recipe. So, for the time being, we'll use the simpler driver named Serial.c
shown in step 4 and step 5 that ARM shipped with uVision4. File Serial.c
comprises three functions SER_Init()
, SER_PutChar()
, and SER_GetChar()
. The function SER_Init()
is the first function called by main()
. It initializes the USART peripheral by writing values to its registers so that it is configured to mirror the channel setup in PuTTY (that is, 115200 baud, 8 data-bits, 1 stop-bit). These parameters are critical. The baud rate is derived from the Peripheral Clock, and in turn the System Clock, so any change in the clock configuration will affect the baud rate. The baud rate is set by the value we write to the Baud Rate Register (BRR). Reference manual RM0090 (www.st.com) describes this as calculated by
Rearranging the preceding formula, with OVER8 = 1 (since we're using 8 x oversampling) and fclk = 42 MHz we get:
The other two functions read and write characters from/to the USART (these perform the low-level I/O ). We'll discuss this in more detail in Chapter 3, Assembly Language Programming.
Any program that wishes to use the services that Serial.c
provides must include its function prototype. To facilitate this, the prototypes are declared in a so-called header file called Serial.h
shown in step 6, and included in the program using a #include
preprocessor directive (for example, see line 15 of main.c
). If we look closely at Serial.h
, we see the prototypes are preceded by the qualifier extern. This is a message to the compiler that the functions are defined in another file (that is, not main.c
), and the function call reference must be resolved later by the linker. We can also see that the prototype declarations are enclosed within a conditional preprocessor statement, that is:
#ifndef __SERIAL_H #define __SERIAL_H /* function prototypes */ #endif
This ensures that the code enclosed within the conditional preprocessor statement is included in the project only once, even though both, main.c
and Serial.c
, include the statement:
#include "serial.h"
The main()
function calls printf()
to output the string "Hello World\n"
. The string "Hello World\n"
is stored as a sequence of characters terminated by a NULL character. C interprets '\n'
as a newline character, but the actual ASCII code ( http://en.wikipedia.org/wiki/ASCII) used to represent newline varies between operating systems; so to cover all eventualities, we can configure PuTTY as shown in step 7.
The function printf()
is defined in C's standard input output library <stdio.h>
. This function calls fputc()
, which is also defined in <stdio.h>
, but redefined in Retarget.c
. So it calls SER_PutChar()
to send the characters to the USART. Most microcontrollers use this technique to allow them to make use of the C library functions printf()
and, as we'll see later, scanf()
too.
File Retarget.c
also uses the preprocessor directive #pragma
, which is used to specify machine- or operating system-specific compiler features. In this case, the directive is used to disable semihosting. Semihosting is a mechanism that allows ARM targets to communicate with a host computer using the JTAG interface. Semihosting can be used with the function trace_printf()
, to enable debug statements to write to the output window of the IDE. Obviously, we can achieve similar functionality using the COM port and PuTTY.