C Language Primer
Fundamentals of Digital and Microprocessor Systems
(KTU course T170B151)
Ž. Nakutis, 2020
C language use in embedded systems programming
(2017 Embedded Market Survey)
2 (Ž.Nakutis, ©)
Why C is so popular in Embedded
Systems
Compilers are available nearly for all 8, 16 and 32
microprocessors
C ensures optimal mix between low level progammin
constructions (drivers) and high level programming (main
program) features
3 (Ž.Nakutis, ©)
Reference for self-studying
See moodle course:
http://www.loirak.com/prog/ctutor.php
http://www.tutorialspoint.com/cprogramming/
http://www.cprogramming.com/tutorial/c-tutorial.html
The C book
C Programming (Steve Summit)
4 (Ž.Nakutis, ©)
The first program: Toogle LED
// This is pseudo code, not to run on any processor
#include <processor.h>
void Delay(const unsigned int uCount) //Function to provide delay
{
unsigned int i=0;
for(; i < uCount; i++)
{
}
}
int main()
{
Port2DIR.1=0; //configuring as output pin
while(1)
{
Port2.1=1; // Make pin high
Delay(1000);
Port2.1=0; // Make pin low
Delay(1000);
* C pseudocode: not
} for particular
} processor or
scematics !
5 (Ž.Nakutis, ©)
What is in Programming Language
Specification?
Syntax: program structure and sentences grammar
Punctuation (semicolon)
Keywords(if, int, switch, etc.)
Function declaration rules (brackets)
Semantics: Meaning of language elements. A sentence might be
free of error from syntax point of view, but the meaning and the
expected result might depend on ...
For example, ++ operator means add one:
If applied to integer type variable, increments value by one.
If applied to pointer (address), then increments address by number of bytes
equal to the size of variable a pointer points to
If applied to float type variable, then complier error will be recognized
because this operation does not has a sense
6 (Ž.Nakutis,s ©)
Simple C program structure
Program
resset
func. F1
Initialization functions
Func. main
func. F2 Library
func. B1
ISR
Library
func. B2
7 (Ž.Nakutis, ©)
Types of high level programming
languages
Compiled:
C / C++ / Java (Arduino scripts are also compiled)
The entire program is converted to machine code,
Program execution is faster, but needs more memory to
store intermediate object code, compilation takes longer
Interpreted:
Phyton / JavaScript / MATLAB
Interpreter converts code line by line during execution
Program execution is slower, does not save object code
https://www.guru99.com/difference-compiler-vs-interpreter.html
In embedded systems compiled program dominate (hardware
resources are limited (no need to save interpreter), real time
processing
8
is important) (Ž.Nakutis, ©)
Basic data types in C language
Data types Number of bytes Range of numbers
char 1 128 127
unsigned char 1 0 255
short 2 32768 32767
unsigned short 2 0 65535
int 2 (8 and 16 bit architecture) 32768 32767 (8 and 16 bit architecture)
4 (32 bit architecture) -2-31 (231-1)
unsigned int 2 (8 and16 bitų architecture) 0 65535 (8 and 16 bit architecture)
4 (32 bit architecture) 0 (232-1)
unsigned long 4 0 (232-1) = 0 4 294 967 295
long long 8 0 (264-1)
float 4 1.175494E-38 3.402823E+38
double 8 2.2250738585072009 × 10−308 ≈
1.7976931348623157 × 10308
9 https://en.wikipedia.org/wiki/C_data_types (Ž.Nakutis, ©)
Portable types <stdint.h>
<stdint.h> fragment
/* exact-width signed integer types */
typedef signed char int8_t;
typedef signed short int int16_t;
/* iškvietimo pavyzdys */
typedef signed int int32_t;
#include <stdint.h>
typedef signed __INT64 int64_t; uint8_t var1;
int32_t var3 = -100;
/* exact-width unsigned integer types */
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __INT64 uint64_t;
10 (Ž.Nakutis, ©)
C types are very important (examples)
unsigned char a, b, c, d;
a = 200;
b = 100;
// What values will be assigned to c and d?
c = a + b;
d = b / a;
11 (Ž.Nakutis, ©)
Collection of variables
Arrays – hold data of the same type
One dimensional
Multi dimensional
Structures – can hold data of different type
Bit fields
12 (Ž.Nakutis, ©)
Arrays
// array declaration example
int array[10];
char string[]=“Hello”;
unsigned char bufer[20];
int array2[5][3];
// using arrays examples
array[5]= -10;
array2[0][2]=array [0];
strcpy(bufer,string);
13 (Ž.Nakutis, ©)
Structures
struct
{ unsigned int amplify_factor;
unsigned char volume;
char attribute;
} settings; // structure declaration
// using structure
settings. amplify_factor =100;
settings. attribute =‘a’; // ASCII code of ‘a’ (97 decimal) will be
assigned
14 (Ž.Nakutis, ©)
Bit fields
typedef struct
{ unsigned char D0_1:2;
unsigned char D2_6:5;
unsigned char D7_8:2;
unsigned char DHigh:1;
} TMyBits;
TMyBits var;
// bit fields usage
var.D0_1=3;
var.D2_6=0x1F;
15 (Ž.Nakutis, ©)
User defined data types
typedef struct
{ unsigned int Year;
unsigned char Hour;
unsigned char Minute;
unsigned char Flag;
} TTime;
// variables declaration
TTime Reminder1, Reminder2, Alarms[10];
// Using array of structures:
Aliarms[1]. Year =2019;
16 (Ž.Nakutis, ©)
Pointers
A pointer is a variable that points at, or refers to, another variable.
If we have a pointer variable of type ``pointer to int,`` it might point to the int variable
i, or to the third cell of the int array a.
Given a pointer variable, we can ask questions like, ``What's the value of the variable
that this pointer points to? (http://c-faq.com/%7Escs/cclass/notes/sx10.html )
int* pConfig;
unsigned char array[10];
char string[ ]=“Hello”;
// using pointers
*pConfig=3;
array= string; // not an error, but incorrect
strcpy(array, string); //how strcpy is declared?
17 https://www.tutorialspoint.com/cprogramming/c_pointers.htm
(Ž.Nakutis, ©)
Pointer operators * and &
Operator * – Dereference Operator or Indirection
Operator
Operatorius & – Address Of Operator
18 (Ž.Nakutis, ©)
Pointer operator * and & using
examples
int* pA;
int B; // static variable
struct {
int Field1;
float Field2;
} MyStruct;
int main()
{
pA= &B; // pointer pA refers to B
*pA=3; // indirect operator, value assignement
B=5; // value assignment to variable
pA=&(MyStruct.Field1); // pointer to structure field Field1
}
19 (Ž.Nakutis, ©)
Variables and functions visibility scope
char A, s; // global variables
char sum(char X1, char X2) // function declaration
{
char Y; // local variable within function sum
Y=X1+X2;
return Y;
}
int main (void)
{
char B; // local variable within function main
B=2;
A=3;
s=sum(A,B); // function sum call (lt. Iškvietimas)
s=sum(10,A); // second function sum call
// Y=sum(B,B); // is this an error???
}
20 (Ž.Nakutis, ©)
C compiler directives
#include <math.h>
#include “my.h”
// using define symbolic names makes program:
// 1. easier to read
// 2. easier to modify (only one place modification)
#define NumberPI 3.14
#define ARRAY_SIZE 50
float Array[ARRAY_SIZE]; // use symbolic name to declare array
int i, A;
int main()
{
for(i=0;i<ARRAY_SIZE;i++)
Array[i]=sin(2*NumberPI*i/ARRAY_SIZE);
if (A<ARRAY_SIZE) // use symbolic definition instead of numbers
A=ARRAY_SIZE;
}
21 (Ž.Nakutis, ©)
Header files (*.h) example
stm32f4xx_gpio.h
/**
* @brief GPIO Configuration Mode enumeration
*/
typedef enum
{
GPIO_Mode_IN = 0x00, /*!< GPIO Input Mode */
GPIO_Mode_OUT = 0x01, /*!< GPIO Output Mode */
GPIO_Mode_AF = 0x02, /*!< GPIO Alternate function Mode */
GPIO_Mode_AN = 0x03 /*!< GPIO Analog Mode */
}GPIOMode_TypeDef;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_IN) || ((MODE) == GPIO_Mode_OUT) || \
((MODE) == GPIO_Mode_AF)|| ((MODE) == GPIO_Mode_AN))
/** @defgroup GPIO_pins_define
* @{
*/
#define GPIO_Pin_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /* Pin 4 selected */
/* GPIO Read and Write functions **********************************************/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
22 (Ž.Nakutis, ©)
Passing parameters to functions
By value:
Values of passed parameters can not be modified because
function operates on their copies
By pointer
Function can modified values of passed parameters and can
return them to the calling program
Variable Function parameter declaration
Value Pointer
func(int param); func(int* param);
Call example: Call example:
int y; // static func(y); func(&y);
Call example: Call example:
int* x; // pointer func(*x); func(x);
23 (Ž.Nakutis, ©)
Task (function check)
1. Create a function named check in C. Two arrays of
arbitrary length has to be passed as parameters, for
example, user name and password. The function
must return „1“ if passed parameters match
corresponding internal arrays, otherwise the
function must return „0“.
2. Declare function prototype in a header file.
3. Present an example of function call from a C
module.
24 (Ž.Nakutis, ©)
Solution: function definition in C module
my_library.c
#define USER_NAME_LEN 4
#define USER_PSW_LEN 5
char UserName[USER_NAME_LEN]="John";
char UserPsw[USER_PSW_LEN]="Abc52";
bool check(char* user, int user_len, char* psw, int psw_len)
{
bool result=true;
int i;
if (user_len != USER_NAME_LEN || psw_len != USER_PSW_LEN)
return false;
for (i=0;i<USER_NAME_LEN;i++)
{ if (user[i] != UserName[i])
{
result=false;
}
} // for
if (result==false) return result;
for (i=0;i<USER_PSW_LEN;i++)
{ if (psw[i] != UserPsw[i])
{
result=false;
}
} // for
return result;
}
25 (Ž.Nakutis, ©)
Solution : function declaration in header file
my_library.h)
#ifndef __MY_LIBRARY_H__
#define __MY_LIBRARY_H__
/*
Parameteres:
user - user name string (array of characters)
user_len - length of user array
psw - password string
psw_len - length of psw array
Return:
TRUE – when user and psw match preset values, otherwise- FALSE
*/
bool check(char* user, int user_len, char* psw, int psw_len);
#endif
26 (Ž.Nakutis, ©)
Solution : function call in C module
#include <my_library.h>
bool MyResult;
int main(void)
{
// Function call
MyResult = check("John",4,"Abc53",5);
// MyResult will become false because passwords do not match
}
27 (Ž.Nakutis, ©)
Task (function EventsLog)
1. Define a function called EventsLog.
2. The function has two input parameters:
Array Events of arbitrary length containing elements of type
Ttime;
Single parameter Alarm of type Ttime.
3. The function must assign „1“ to every element of Events
parameter’s field Flag if values of the corresponding
element‘s time field matches time filed (Year, Month, Day) of
parameter Alarm.
typedef struct
{ unsigned int Year;
unsigned char Month;
unsigned char Day;
unsigned char Flag;
} Ttime; 28
(Ž.Nakutis, ©)
Solution: function definition
my_events.h
typedef struct
{ unsigned int Year;
unsigned char Month;
unsigned char Day;
unsigned char Flag;
} Ttime;
my_events.c
#include “my_events.h”
/* Function declaration
Parameters:
Ttime* Events - array of input events
int NumEvents - size of array Events
Ttime* Alarm -
return : none*/
void EventsLog(Ttime* Events, int NumEvents, Ttime* Alarm)
{
int i;
for (i=0;i<NumEvents,i++)
if (Events[i].Year==Alarm->Year && // alternative *(Events+ i).Year == (*Alarm).Year &&
Events[i].Month==Alarm->Month &&
Events[i].Day==Alarm->Day)
Events[i].Flag=1;
else
Events[i].Flag=0;
}
29 (Ž.Nakutis, ©)
Solution : function call
#include “my_events.h”
#define NUMBER_OF_EVENTS 5
Ttime MyAlarm;
Ttime MyEvents[NUMBER_OF_EVENTS];
int main (void)
{
MyAlarm.Year=2018;
MyAlarm.Month=2;
MyAlarm.Day=20;
// Function EventsLog call
EventsLog(MyEvents, NUMBER_OF_EVENTS, &MyAlarm);
}
30 (Ž.Nakutis, ©)
Bit logic operations
Bit logic operations are used for bit manipulation: set to
„1“, , reset to „0“ or toggle. The result is always a multibit
variable, whose every bit is calculated according to logic
operation from corresponding bits of operands.
Operator Is used for Example
AND Bit reset a = a & 0xF0;
a &= 0xF0;
OR Bit set a = a | 1;
a |= 1;
XOR Bit toggle a = a ^ 0xFF;
a ^= 0xFF;
Inversion Invert all bits a=~0xF0;
31 (Ž.Nakutis, ©)
Bit logic operations (examples)
unsigned char a;
a = 0x55; //X1=0x55
X1 = ~ a; //X1=0xAA
// logic AND
X2 = a & 0xF0; // 0xF0=1111 0000, X2=0x50
// logic OR
X3 = a | 0x03; // 0x03=0000 0011, X3=0x53
// logic XOR,
X4 = a ^ 0x01; // 0x01=0000 0001, X4=0x52
32 (Ž.Nakutis, ©)
Program flow control
Conditional sentences (if else, switch)
Cycles (for, while, until)
Blocks ({})
33 (Ž.Nakutis, ©)
for and if examples
unsigned char mas[10], min;
int main ()
{
min=0xFF;
for (i=0;i<10;i++)
{
if (min>mas[i])
{ min=mas[i];
} else { }
} // for cycle end
} // main function end
34 (Ž.Nakutis, ©)
Multiple conditions sentence switch
char ProgramState;
switch (ProgramState)
{
case 0: {
// Program block 0
ProgramState=1;
} break;
case 1: {
// Program block 1
ProgramState=2;
} break;
case 2: {
// Program block 2
ProgramState=0;
} break;
default: // Program block default
}
35 (Ž.Nakutis, ©)
Interrupts and their sources
Interrupts enable microprocessor to handle asynchronous
external events (external to MCU, e.g. byte reception via
UART interface, logic level change at GPIO, etc.)
Interrupts enable to synchronize program flow with the status
of peripherals, e.g. timer overflow
Sources of interrupts:
General purpose input output (GPIO) ports
Internal timers
Internal ADC (Analog-to-Digital Converter)
Communication interfaces (UART, SPI, I2C)
Direct Memory Access (DMA) channel
Brown-out detector module (observation of supply voltage level)
36 (Ž.Nakutis, ©)
Libraries
Collection of functions (drivers of peripherals, digital
signal processing, communication stacks, etc.)
Library header files must be included in the program
code using directive #include „library_name.h“
Sometimes they can be provided in open C code, but
other time in already compiled format (not readable by
humans)
Building entire project libraries are linked together with
other project modules and this way executable image is
generated. Executable image (file) can by loaded to the
memory of embedded systems (MCU program memory)
37 (Ž.Nakutis, ©)
Examples of libraries
Peripheral driver libraries (ST Microelectronics call them
HAL – Hardware Abstraction Level libraries)
Standard C libraries (math.h, stdio.h, string.h, etc.)
Digital signal processing libraries (digital filters, FTT,
matrix multiplication, etc.)
Communication stacks (TCP/IP, UART, Modbus, wireless
communication stacks)
Motor control libraries
Fixed point numbers and operations library
38 (Ž.Nakutis, ©)
Const and volatile type
qualifiers
const and volatile – are independent qualifiers. It is an error
to think they are opposite qualifiers. Sometimes it is
meaningful to use them together.
const denotes, that the program can not modify the variable,
for example
const int a=10; // variable a can not be assigned any new value,
however, it can be initialized
int main()
{
a=20;
}
References:
1. Introduction to the Volatile Keyword http://www.embedded.com/story/OEG20010615S0107
2. Const and volatile http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html
39 (Ž.Nakutis, ©)
Const qualifier and pointers
char *const X; // constant pointer (X value can not be
modified) to object, whose value can be modified (*X can be
modified by the program)
char const *X; // pointer to constant object; the pointer
itself (X value) can be modified, but the object it points to can
not be modified const char pi=3.14;
const char e=2.71;
const char* p1;
const char* p2;
int main(void)
{
p1=π
p1=&e;
p2=p1;
}
40 (Ž.Nakutis, ©)
Volatile qualifier – attribute often used
in embedded systems programs
Declaration examples:
volatile int a; // volatile variable
volatile int * pb; // pointer to volatile variable of type int
The volatile qualifier tells to compiler, that the value of variable may change
at any time independent from neighboring program code execution
The volatile keyword indicates that the value in a memory location can be
altered in ways unknown to the compiler or have other unknown side effects
(e.g. modification via a signal interrupt, hardware register, or memory mapped
I/O) even though nothing in the program code modifies the contents.
In practice the above applies to three types of variables:
Memory-mapped peripheral registers, for example, new value at the port
input appears not because of SW operation, but due to external HW
Global variables, that may be modified by interrupt service routines
Global variables in programs with concurrent (parallel) multi-threaded
application
41 (Ž.Nakutis, ©)
Peripheral registers and volatile
qualifier
Incorrect code – optimizing compiler reads *ptr once and many
times compares it to 0. A programmer wanted many times
reading and each time after reading to compare to 0.
int* ptr = (int*) 0x1234;
// Wait for register to become non-zero
while (*ptr == 0); // compiler will interpret the code
like read once, check condition ones
// Continue program
Correct code:
int volatile* ptr = (int volatile*) 0x1234;
// Wait for register to become non-zero
while (*ptr == 0); // compiler will interpret the code
like read in every cycle and check in every cycle
// Continue program
42 (Ž.Nakutis, ©)
Volatile qualifier used to declare
structures for peripheral devices
access
#define DEVADDR 0x20001005 // base address of peripheral device
int devno=1; // peripheral device channel number
struct devregs {
unsigned short csr; /* control & status */
unsigned short data; /* data port */
};
volatile struct devregs *const dvp=DEVADDR+devno*sizeof(struct
devregs);
/* structure type variable is volatile (all its fields are
volatile), but pointer dvp is constant and corresponds to the
address of peripheral device*/
if (dvp->data == 0x01) { … } /* process data */
43 (Ž.Nakutis, ©)
Interrupts and volatile qualifier
Incorrect code – optimizing compiler does not know, that ext_rcvd value
may be changed in interrupt service routine, and therefore !ext_rcvd is
always TRUE and program will never exit while cycle.
int ext_rcvd = FALSE;
// correct declaration would be
// int volatile etx_rcvd = FALSE;
void main()
{
while (!ext_rcvd) // Wait
{
// process received data
}
}
interrupt void rx_isr(void)
{
if (ETX == rx_char)
{
ext_rcvd = TRUE;
}
}
44 (Ž.Nakutis, ©)
Volatile qualifier and pointers
char *volatile X;
char* (volatile X);
// volatile pointer (X value may change independent from main program code)
char volatile *X;
(char volatile) *X;
// pointer to volatile object (*X value may change independent of code)
volatile int* p1; // pointer to volatile int
int* p2;
int X1,X2;
X1= (*p1) * (*p1); // twise reads *p1, if *p1 changes X1 not
necessary square of *p1
X2= (*p2) * (*p2); // always square of *p1
45 (Ž.Nakutis, ©)
Incorrect usage (or not using) of
volatile qualifier may be suspected
if:
1. Program behavior was correct until optimizing
compiler was disabled
2. Program behavior was correct until interrupts were
disabled
3. Strange peripheral device driver behaviour
4. Everything was OK in single threaded
implementation, but crashed after running the
second thread
46 (Ž.Nakutis, ©)
Why not to use volatile for every
variable?
In this case we would prevent optimizing compiler from
transforming code into effective executable. Sometimes
intermediate variables indeed reasonable to remove, though
program description looks easier to read when they are
present.
47 (Ž.Nakutis, ©)
static qualifier
static int globalX=0; Features:
// used only in this C 1. Visible only inside one C module (C
module (file)
module is file with *.c extension)
void MyFunction(void) 2. Copy of variable is not created in stack,
{ if variable with static qualifier is
//local variables declared inside a function. The value is
static int staticX=0;
int localX=0;
preserved (not reinitialized) between
calls.
staticX++; int main()
localX++; {
globalX++;
return; MyFunction(); // after return globalX=1;
} // inside staticX=1; localX=1;
MyFunction(); // after return globalX=2;
Static can be applied to // inside staticX=2; localX=1;
MyFunction(); // after return globalX=3;
function also. Then the
// inside staticX=3; localX=1;
function becomes local. }
48 (Ž.Nakutis, ©)