Embedded C programming

If you like it, share it
Embedded C programming for STM32

Why using Embedded C ?
Simply because is the most favorite language for programming MCU, is stable and is easy to learn.

In this article where are some ‘pills’ regarding the Embedded C Programming, if you need go in deep on this argument there are a lot of books and on Udemy there is this course. Also you find in this pages an introduction concerning the way to use the STM32CubeIDE that will use for do some test in conjunction with the STM32 mcu.
For more info regarding MCU architecture see here.

Introduction

We point out that there are several versions of the C, here we refer to the latest release which is the C11 (updated in 2011).
Some GNU compilers call it gnu11.

C11 is a superset of the C99 and for this is compatible with the C90.

Remember that for us the HOST is our PC and TARGET is STM32 eval boards, see below. Here you can find all the STM32 eval boards.

NOTE:
For practice in some C syntax or if you need to test a piece of C code without using the MCU we suggest to use:

  • STM32CubeIDE for write a C code for your PC under Linux (see here)
  • or the online compiler for example the onlinegdb

We divide this course in two macro area that are:
Section n.1 we explain the tools to use for test your C programs on STM32 and on PC.
Section n.2 we highlight some important notes regarding Embedded C programming.

Tools to use for test your C programs

The examples that you can find here are for STM32 mcu and for develop our examples we use the STM32CubeIDE.
STM32CubeIDE data brief is here.
STM32CubeIDE documentations are here.

The features of STM32CubeIDE are below.
For us one of the best feature is that it’s a multi platform tool because we use LINUX (Ubuntu).
Also the STLINK-v2 (is STM32 low coast emulator) is 100% compatible with Ubuntu.
NOTE: The STLINK-v2 is embedded in all STM32 eval boards.

  • Integration of STM32CubeMX that provides services for:
    • STM32 microcontroller and microprocessor selection
    • Pinout, clock, peripheral, and middleware configuration
    • Project creation and generation of the initialization code
  • Based on ECLIPSE™/CDT, with support of ECLIPSE™ add-ons, GNU C/C++ for Arm® toolchain and GDB debugger
  • Additional advanced debug features including:
    • CPU core, peripheral register, and memory views
    • Live variable watch view
    • System analysis and real-time tracing (SWV)
    • CPU fault analysis tool
  • Support of ST-LINK (STMicroelectronics) and J-Link (SEGGER) debug probes
  • Import project from Atollic® TrueSTUDIO® and AC6 System Workbench for STM32 (SW4STM32)
  • Multi-OS support: Windows®, Linux®, and macOS®, 64-bit versions only

We use the UBUNTU 18.04.3 LTS (a Linux version).
If you use Linux Ubuntu download STM32CubeIDE, unzip it, change the permission of the file:
st-stm32cubeide_1.1.0_4551_20191014_1140_amd64.deb_bundle.sh
and in the terminal do the commands below:
sudo su
chmod +x st-stm32cubeide_1.1.0_4551_20191014_1140_amd64.deb_bundle.sh
next install it:
./st-stm32cubeide_1.1.0_4551_20191014_1140_amd64.deb_bundle.sh

NOTE:
On your PC (host) it is possible to add additional compilers for example for Linux or for Windows and use it inside the STM32CubeIDE.
For install the compiler for Linux do the commands below:

  • sudo su
  • apt update
  • apt install build-essentia

After the above commands on your Linux there is the GCC compiler for your PC.
Now is possible use the STM32CubeIDE also for write a C code for your PC (Host).
For more info see here.

For know the version of the installed GCC use the command below:
gcc –version
You must see an answer like below:
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Before to speak about the embedded C is important that you install on your PC a tools for test the examples that you can find here.
As we mentioned above, we use the STM32 mcu and STM32CubeIDE.

Create a NEW project for STM32 board

For create a new project for STM32 see this tutorial.


Section n.2 notes regarding Embedded C programming

Remember that for us the HOST is our PC and TARGET is STM32 eval boards, see below. Here you can find all the STM32 eval boards.

printf escape sequence and fflush

The escape sequence are used for insert in your C code a particular characters that compiler use for do a particular sequence, the typical application is in the printf function.
From Wikipedia (see here), below there is the escape sequence table.

Summary of printf format specifiers
%c …. character
%d …. decimal (integer) number (base 10)
%e …. exponential floating-point number
%f …. floating-point number
%lf … double floating-point number
%e … scientific notation
%le .. double scientific notation
%i …. integer (base 10)
%o … octal number (base 8)
%s … a string of characters
%u … unsigned decimal (integer) number
%x … number in hexadecimal (base 16)
%p … show in HEX format the address of the variable
%lu .. unsigned long number
%llu . unsigned long long number
%% .. print a percent sign
\% … print a percent sign

NOTE – not all the %xx are supported of all compiler.

There is also the possibility to define the appearance of the result by using some extra tag in your printf, see below.
Test the example below using the onlinegdb

#include <stdio.h>
int main()
{
printf(“Hello !!!\n”);
printf(“‘%5d’\n”, 10);
printf(“‘%-5d’\n”, 10);
printf(“‘%05d’\n”, 10);
printf(“‘%8.4f’\n”, 10.3456);

printf(“Press ENTER to exit…”);
getchar();
return 0;
}

The results must be, see below.

ATTENTION
If you use ECLIPSE for debug and use the sequence below:

printf("Enter a number: ");
scanf("%d", &number1);

my be possible that you don’t see the result in your consolle, this is because the sequence of printf is:
write to a buffer next write to the display (consolle of your eclipse), see below.
To solve this problem we then use the fflush function in order to download immediately the buffer to the display.

printf("Enter a number: ");
fflush(stdout);
scanf("%d", &number1);

For more info regarding printf format specifiers see here and here.

Comments

For insert a comments in your C code you have two possibility that are:
Start the comment with the //
Start the comment with the /* and and it using the */
Normally the /* and */ is preferred when is necessary insert a lot of lines of comments.

C data types and variables

Data types determine the type and size of data associated with variables.
Variable me be: numeric, character, strings, etc.

In C there are some way for declare data that are:
Integer data types (10, 15, 100, etc) – char, int, short int, long int, long long int
Float data types (12.5, 22.7, 125.8, etc) – unsigned char, unsigned int, unsigned short int, unsigned long int, unsigned long long int

Unfortunately the memory size of the variables can change from one compiler to another, for avoid ambiguous data memory size declaration we suggest to use, in embeds C, the below declarations:

Variable definition is made by 3 part that are:
Data_Type …. Variable_Name …. Inizializzation_Value
for example:
uint8_t Var1=10;

It’s very important to initialize all the variables that you declare before to use they.
This is important because some compiler initialize the variable to 0 but some other initialize the variable to FF, this my be generate a wrongs results.

  • NOTE:
    • Variable must be declare before to use.
    • Variable are stored in the RAM of the MCU.The name of the variable are not stored but the compiler use the RAM location address.
    • Variable name accept alphabet characters (lower/capital letters and numbers).
    • The only special character accepted in the variable name is underscore _
    • The first letter of a variable cannot be a digit, must be an alphabet character or underscore.
    • You cannot use the C standard reserved keywords as a variable name, see the image below (C99 reserved keywords).

Is it possible define the variable in 3 different mode that are:
local – defined inside the function that use it
global – defined at the top of the file
external – defined in an external file

Local variable

Global variable


Number representation and how to manage a big and small floating number

The example below show you how to manage a big floating number.
Try, using the STM32CubeIDE to compile the below example for your PC with Ubuntu or try the example using the online compiler.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    float number   = 45.78976834578;
    double number2 = 45.78976834578;

    printf("NUMBER is: %e\n", number);
    printf("NUMBER is: %f\n", number);
    printf("NUMBER is: %0.09f\n", number);
    printf("NUMBER is: %0.14f\n\r", number);

    printf("NUMBER2 is: %lf\n", number2);
    printf("NUMBER2 is: %0.09lf\n", number2);
    printf("NUMBER2 is: %0.14lf\n", number2);

    return 0;
}

The example below show you how to manage a small floating number.

#include <stdio.h>

int main(void)
{
    double ChargeE = -1.60217662e-19;

    printf("ChargeE is: %le\n", ChargeE);
    printf("ChargeE is: %0.8le\n", ChargeE);

    return 0;
}

Address of the variables (POINTER)

POINTER is one of more important features in C programming.
In embedded C pointers are used for:
* Programming/Reading the peripherals REGISTERs
* Read/Write the RAM and FLASH

POINTER is the memory location where are the MCU registers, is the memory location where is a VARIABLE, etc.

On the POINTER is possible do do the mathematical operations + and , for increase or decrease it, to point an another register or memory area.

IMPORTANT
Pointer on 32 bit MCU (STM32) is 4 byte long, on 64 bit MCU (like the PC) is 8 byte long.

POINTER variable definition is:
<pointer data type> <variable name>;

The * indicate that the declaration is a pointer.

For create a pointer variable you must use a declaration like below.
long Val1=10;
long* PointerOfVal1 = Val1;

But pay attention, the pointer, as I told before, on 32 bit MCU is 4 byte long, so it do not respect the variable declaration type.
In the end, using char *, Int *, etc, nothing changes, the pointer on 32bit machines will be 4 bytes long.
We define the type of pointer (char *, int *, etc.) is important because this will influence the operations we will do on it (read, write, increment, etc).
See below.

So the pointer behaves differently depending on how it was defined (char *, int *, etc).


* -> means: "value at Address" of the operator 
& -> means: "Address of" operator

Read Data from Pointer

Write Data from Pointer


If you need to know the address (pointer) memory (RAM) of a variable you must use the operator & in front of the variable,
int Var1=10;
&Var1 -> give you the address of the memory location of Var1
see the example below

Here there is the ZIP file to use in STM32CubeIDE for generate an example for your PC. Remember your PC must be a Linux PC.
Also you can test example using the online C compiler.

Storage class of variable – static, extern, etc

Storage class define the scope, the visibility and the life time of a variable.

static

static variables in C have the lifetime of the program.
If defined in a function, they have local scope, i.e. they can be accessed only inside those functions but the value of static variables is preserved between function calls.
See the below example.

Static is not only reserved for the variables but it’s possible use it for restrict the visibility of a function outside the file where it is.
Remember that a C program is normally made by a group of C files.

So, suppose that your project is formed of 2 C file ie:
main.c
Calc.c

If in main.c we declare a function:
static void Calcola(void);
this function is available only inside the main.c this means that is not possible use this function in the file Calc.c

extern

For use in a file present in our project, for example the file named: file.c, a global variable or a functions, that is declared or defined in other file, for example in main.c, is necessary use the extern tag, see the example below where we has:
main.c and File2.c
in one project named: TSTHostLinux1

Here there is the below example for use in STM32CubeIDE.

register

register, hints to the compiler that access to an object should be as fast as possible.
The register storage class is more appropriate for variables that are defined inside a block and are accessed with high frequency. See below.

typedef

typedef – defines a new type based on an existing type.
See below.

sizeof operator

If you need to know how many byte use a declaration of a type of a variable in your C compiler, you have two possibility, spend a lot of time to read the manual of your C compiler or use the sizeof operator.
The output of the sizeof operator can be different on different mcu because it depends on the compiler.
In other words, sizeof is used for find the storage size of the below data type

  • char
  • int
  • shot
  • long
  • long long
  • double

Try to use the below example using STM32CubeIDE or onlinegdb compiler.

Here there is the source file for STM32CubeIDE.
We will back on this example also in FUNCTION description to go in deep on it.


scanf

scanf – is a standard library function used for read input from standard in.
Standard in is, on PC, the keyboard but in embedded C is possible redirect this function for read data for example from RS232 (USART), we see how to do later in this manual.

Following is the declaration for scanf() function.

int scanf(%input_format_type, &variable_to_store_input)

The scanf function uses the same placeholders as printf, see below.

Try, using the STM32CubeIDE to compile the below example for your PC with Ubuntu or try the example using the online compiler.

#include <stdio.h>

int main(void)
{
    int age=0;

    printf("Please insert your age: ");
    scanf("%d", &age);
    printf("Your age is: %d\n", age);

	return 0;
}

scanf – my be used for read multiple input data, for example see below.

See also below, the interesting way to use the scanf and getchar.

#include <stdio.h>
int main(void)
{
char c1=0, c2=0, c3=0;

printf("Enter 3 characters separated by a space\n");
scanf("%c",&c1);
getchar(); // clear/remove the SPACE
scanf("%c",&c2);
getchar(); // clear/remove the SPACE
scanf("%c",&c3);
getchar(); // clear/remove the SPACE
printf("\nASCII codes : %u, %u, %u",c1,c2,c3);

printf("\nPress ENTER to exit...");
while(getchar() != '\n')
{
// Wait \n (ENTER) char for exit
}
getchar(); // clear buffer

return 0;
}

getchar and putchar

int getchar(void) – this function is used to read a character from keyboard input
int putchar(int char) – this function is used to write a character on standard output/screen

Try, using the STM32CubeIDE to compile the below example for your PC with Ubuntu or try the example using the online compiler.

#include <stdio.h>
#include <ctype.h>

int main()
{
   char c;
   printf("Enter some character. Enter $ to exit...\n");
   while (c != '$')
   {
      c = getchar();
      printf("\n Entered character is: ");
      if ((c<=31) | (c>=127))
    	  printf("NON printable CHAR");
      else
    	  putchar(c);
      printf("\n");
   }
   printf(" END program... \n");
   return 0;
}

I/O and file handling functions

There is a lot of I/O and FILE handling functions available in C, for more info see below and here -> https://fresh2refresh.com/c-programming/

handling
functions
Description
fopen()function creates a new file or opens an existing file.
fclose()function closes an opened file.
getw()function reads an integer from file.
putw()functions writes an integer to file.
fgetc()function reads a character from file.
fputc()functions write a character to file.
gets()function reads line from keyboard.
puts()function writes line to o/p screen.
fgets()function reads string from a file, one line at a time.
fputs()function writes string to a file.
feof()function finds end of file.
fgetchar()function reads a character from keyboard.
fprintf()function writes formatted data to a file.
fscanf()function reads formatted data from a file.
fputchar()function writes a character onto the output
screen from keyboard input.
fseek()function moves file pointer position to given location.
SEEK_SETmove file pointer position to the beginning of the file.
SEEK_CURmoves file pointer position to given location.
SEEK_ENDmoves file pointer position to the end of file.
ftell()function gives current position of file pointer.
rewind()function moves file pointer position to the beginning of the file.
getc()function reads character from file.
getch()function reads character from keyboard.
getche()function reads character from keyboard and echoes
to o/p screen.
getchar()function reads character from keyboard until you
press ENTER
putc()function writes a character to file.
putchar ()function writes a character to screen.
printf()function writes formatted data to screen.
sprinf ()function writes formatted output to string.
scanf()function reads formatted data from keyboard.
sscanf()function Reads formatted input from a string.
remove()function deletes a file.
fflush()function flushes a file.

Casting

Converting one data type into another is known as type casting or, type-conversion.
In other words, casting is used for changing a variable of one data type into another.

NOTE:
Data will be truncated when the higher data type is converted in to lower.
Always pay close attention to the compiler warnings because an often overlooked warning can be the indicator of a potential casting problem.

For example suppose to have this situation:
unsigned long long int AddresOfVar1
and
char Var1

Suppose that you need to do the operation below:
unsigned long long int AddresOfVar1 = &Va1
the right way to write the line is:
unsigned long long int AddresOfVar1 = (unsigned long long int)&Va1
this is the casting, we had converter a pointer (&Var1) into a number (unsigned long long int).

Try to use the example below and see the results.

Here there is the example for STM32CubeIDE.

ATTENTION:
Be careful when working with numbers because it is easy to make mistakes and/or have wrong results if you do not carefully consider the size of the data you are dealing with.

For example:
float result = 80/3;
this is WRONG
float result = (float) 80/3;
this is OK
Test it on your STM32CubeIDE.

Preprocessor Directives

The C preprocessor or cpp is the macro preprocessor for the C and C++ computer programming languages.
The preprocessor provides the ability for the inclusion of header files, macro expansions, conditional compilation, and line control. (From Wikipedia)

List of preprocessor directives :

#include
#define
#undef
#ifdef
#ifndef
#if
#else
#elif
#endif
#error
#pragma

#include directive, see below.

The #include is a processor directive that instructs the processor of the compiler to include a file into the source file.
the syntax is:
#include <C language file>
#include “MCU compiler file or owner file

#define is a segment of code which is replaced by the value of macro

#define PI 3.1415
#define MIN(a,b) ((a)<(b)?(a):(b)) , see the below example

----------------------------------------------------------

#include <stdio.h>  
#define MIN(a,b) ((a)<(b)?(a):(b))  
void main() 
{  
   printf("Minimum between 10 and 20 is: %d\n", MIN(10,20));    
} 

the output is:

Minimum between 10 and 20 is: 10

#undef is used for undefine a macro means to cancel its definition.

#define PI 3.1415  // Define PI
#undef  PI         // UnDefine PI

#ifdef preprocessor directive checks if macro is defined by #define. If yes, it executes the code.

#ifndef preprocessor directive checks if macro is not defined by #define. If yes, it executes the code.

#else preprocessor directive evaluates the expression or condition if condition of #if is false. It can be used with #if, #elif, #ifdef and #ifndef directives.

#error preprocessor directive indicates error. The compiler gives fatal error if #error directive is found and skips further compilation process. For examples see here.

#pragma preprocessor directive is used to provide additional information to the compiler. The #pragma directive is used by the compiler to offer machine or operating-system feature. ATTENTION: Different compilers can provide different usage of #pragma directive.

ASCII table

For assign a value to a char variable you have two possibility that are:
char a1=’A’;
or
char a1=65;
Of course, the first declaration is more easy to use.

It’s also possible use the array strings, see below.
char Var1[]=”Hello !!!”;
We explain it later in this manual.

For print (printf) the ASCII value of a variable is necessary use the %c, see below.
char a1=’A’;
printf(“%c”,a1);

Functions

In C we write our code normally in an executable function or in more executable functions.
This is for maintain a good readability for our code.
The functions must be:
declare
and
prototypes
before to use it.
The functions must be declared in the global area, normally in the main.c after the #include area
or a good idea, is create a include file where put all the function declarations.
See the examples below.


For example if in your program you want to perform the same piece of code again and again, is a good idea create a function where put inside this code.

Below there is the general C function definition or function prototypes.
return_data_type function_name(input_parameter_list)
{
// body of the function
}


NOTE:
All the C program must have a function called main.
The main function is the first function that is call after the MCU initialization.
main must be return an integer and the input parameters are maximum two.
In embedded C programming normally the main function is declared like below, without input parameters.
int main()
{
// body of function
return 0;
}

Return 0 means SUCCESS
Return NON 0 means ERROR


Download from here the example for STM32CubeIDE.
We had already used this example in the EXTERN explanation.

If you run the example everything is correct but… is not a right way to write a function. The function Calcola simply sum the variables,
a3=a1+a2;
this variables are Global variable defined in main.c
More clever and secure is write a function that receive in input the variables that must sum and return the calculated value.

See below and get here the example (for STM32CubeIDE).


Normally in a real program, there are a lot of functions, for this reason is a good idea create a file that contain the functions declarations. Se the example below.

NOTE:
in the line n.15 we use the casting method for return the correct value expected:
long long int
without the casting the function will be return an int and this is an error.

Here there is the example above.

First embedded Hello World for STM32 mcu using STM32CubeIDE

Now we show you how to write a new C program for STM32F401RE, using the NUCLEO-F401RE and the STM32CubeIDE.
Our goal is blinking the green LED (it’s connected to PA5) that is present on the NUCLEO-F401RE and send data via USB (virtual COM) to the PC.
On PC we use, for Windows the Tera-Term and for Linux we use the Minicom or GtkTerm.

NOTE:
In any case, feel free, to chose the STM32 evaluation boards that you prefer.

This parts is very general so we decided to create a dedicated page, with all the explanations, that is here.


The compilation/linker stage

The stage of build a executable file for MCU is divide in three macro stage. See below.

In details:

Normally the intermediate files (.i, .o, etc) are not saved.
If you need analyze this files is necessary configure the compiler for save they.

To be continued as soon as possible….