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.
Follow these steps to install PuTTY, and connect the evaluation board to the PC's COM port:
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.helloWorld.c
. The source file named helloWorld.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! "); } }
Source Group 1
folder, and add the source file helloWorld.c
to the project.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 */ }
SER_Init()
function, name the new file Serial.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 }
SER_getc()
and SER_putc()
to Serial.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); }
Serial.h
, and add it to the project. This is the header file that declares the function prototypes for Serial.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
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
"
. The string "Hello World
"
is stored as a sequence of characters terminated by a NULL character. C interprets '
'
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.
3.140.185.170