Skill Lab: Embedded C
Embedded C: Practice and hands on using C51 in Keil µvision
Topics Covered
• Basics of Embedded 'C' programming
• Keywords
• Importance of Bit-Wise operators in "Embedded" programming
• Bit manipulation techniques using bit-wise operators and examples
• Bit extraction and working with memory mapped peripheral registers
• Inline Functions in 'C'
• Implicit and explicit casting and its importance
• Pointers and casting
• Pointers arithmetic and importance of pointer datatypes
• Accessing memory mapped peripheral registers using pointers
• Structures and unions
• Importance of structures in peripheral register access
• Structures and pointers
• Const and volatile type qualifiers
• Importance of volatile during compiler optimization
• const data, const pointer, cont volatile explanation with examples
• Importance of const
Source: Embedded C course on Udemy
Dept. of ECE, RVCE Faculty of Embedded Vertical 1
Skill Lab: Embedded C
Basics of Embedded C Programming:
An embedded system is an application that contains at least one programmable computer
(typically in the form of a microcontroller, a microprocessor or digital signal processor chip).
The embedded system is a combination of hardware and software integrated in an optimal way
to perform predefined set of functions.
Usually, software(firmware) consists of system software and application software. The system
software is composed of Kernel, Board support package, etc. supplied by the manufacturer of
the chip or third-party vendors for board bring up or initial configuration of the hardware. The
application software is set of commands written by the user to perform some operations. The
software is stored is permanent memory devices like Flash ROM. The application software is
developed using high level or assembly level programming languages. The most preferred
programming language in embedded systems is C.
• The ‘C’ Programming Language was originally developed for and implemented on the
UNIX operating system, by Dennis Ritchie in 1971.
• One of the best features of C is that it is not tied to any particular hardware or system.
This makes it easy for a user to write programs that will run without any changes on
practically all machines.
• C is often called a middle-level computer language as it combines the elements of high-
level languages with the functionalism of assembly language.
• To produce the most efficient machine code, the programmer must not only create an
efficient high level design, but also pay attention to the detailed implementation.
C is much more flexible and free-wheeling than other high-level programming languages:
▪ C is a structured language.
▪ C is a relatively small language.
▪ C has very loose data typing.
▪ C easily supports low-level bit-wise data manipulation.
▪ C is sometimes referred to as a “high-level assembly language”.
When compared to assembly language programming:
▪ Code written in C can be more reliable.
▪ Code written in C can be more scalable.
▪ Code written in C can be more portable between different platforms.
▪ Code written in C can be easier to maintain.
▪ Code written in C can be more productive.
There are some of the common issues that we encounter when considering moving to the C
programming language:
▪ Big and inefficient code generation.
▪ Fat code for the standard IO routines (printf, scang, strcpy, etc…).
▪ The use of memory allocation: malloc(), alloc(), …
▪ The use of the stack is not so direct in C.
▪ Data declaration in RAM and ROM.
▪ Compiler optimizations.
Dept. of ECE, RVCE Faculty of Embedded Vertical 2
Skill Lab: Embedded C
▪ Difficulty writing Interrupt Service Routines.
Many of these concerns are the result of failing to acknowledge the available resource differences
between Embedded Microcontrollers and Desktop Computing environments.
Main characteristics of an Embedded programming environment:
▪ Limited ROM.
▪ Limited RAM.
▪ Limited stack space.
▪ Hardware oriented programming.
▪ Critical timing (Interrupt Service Routines, tasks, …).
▪ Many different pointer kinds (far / near / rom / uni / paged / …).
▪ Special keywords and tokens (@, interrupt, tiny, …).
Successful Embedded C programs must keep the code small and “tight”. This may mean breaking
a few Desktop programming rules:
▪ Global scratch memory.
▪ Global labels.
▪ Global interrupt enabling/disabling.
▪ Pointer support.
▪ Using GOTO.
Pure ANSI C is not always convenient for embedded systems because:
▪ ANSI C has various type promotion rules that are absolute performance killers to an 8-
bit machine.
▪ Embedded systems interact with hardware. ANSI C provides extremely crude tools for
addressing registers at fixed memory locations.
▪ Almost all embedded systems use interrupts.
▪ Some MCU architectures don’t have hardware support for a C stack.
▪ Some MCUs have multiple memory spaces.
Working of Compiler:
Figure 1: Working of a compile
[Source: Udemy online classes]
Dept. of ECE, RVCE Faculty of Embedded Vertical 3
Skill Lab: Embedded C
Question 1:
By referring figure 1, define the following:
i. Compiler ii. Assembler iii. Linker iv. OH converter
Write the importance of following file formats:
.c, .src, .obj, .abs, .hex
Practice 1:
Working with Keil uVision IDE for program development and understanding different tools.
Type the below program as target as 8051 MCU:
#include<reg51.h>
void main(){
unsigned char arr[] = {0x00, 0x02, 0x02, 0x01, 0x02, 0x01, 0x01,
0x03}, temp;
P0=0x00; // output port
P1 = 0xff; // configuration of input port
while(1)
{
temp = P1 & 0x07;
P0=arr[temp];
}
}
Write a summary after completion of practice.
Keywords
Identifiers Iteration Preprocessing Directives
Data type
struct do #include
char
union while #define
short
void for #undef
signed
unsigned enum #line
Jump
int #error
Selection goto
float #pragma
if continue
long else break
double switch Conditional Compilation
case return
# if
Modifiers default
Function Specifier # ifdef
const # ifndef
Storage Specifiers inline
static
register # elif
volatile
typedef # else
restrict
auto # endif
extern
Dept. of ECE, RVCE Faculty of Embedded Vertical 4
Skill Lab: Embedded C
Datatypes
Table below shows commonly used data types in C:
Number Representation
Decimal 0 2 63 83
Octal 00 02 077 0123
Hexadecimal 0x0 0x2 0x3F 0x53
The typedef keyword in C is used to create an alias or a new name for an existing data type. This
is particularly useful for improving code readability and making it easier to modify data types,
especially in situations where the data types used in a program might need to be changed later.
The programming example below shows used of typedef keyword assign names for integer data
type.
#include <stdio.h>
typedef int INTEGER;
typedef float REAL;
void main() {
INTEGER num1 = 10;
REAL num2 = 3.14;
printf("Integer: %d\n", num1);
printf("Real: %.2f\n", num2);
}
Dept. of ECE, RVCE Faculty of Embedded Vertical 5
Skill Lab: Embedded C
In embedded programming by using typedef, you can create aliases for data types that may have
different sizes on various platforms, making your code more portable. For example, the code
below shows use of typedef keyword to assign user defined names for the codes.
#include <stdint.h> // For fixed-width integer types
// Define aliases for specific integer sizes
typedef char int8_t;
typedef unsigned char uint8_t;
typedef short int16_t;
typedef unsigned uint16_t;
typedef int int32_t;
typedef unsigned uint32_t;
typedef int8_t s8;
typedef uint8_t u8;
typedef int16_t s16;
typedef uint16_t u16;
typedef int32_t s32;
typedef uint32_t u32;
// Example usage
void processSensorData(u16 sensorValue) {
// Processing logic here
}
int main() {
u16 sensorReading = 0x1234;
processSensorData(sensorReading);
return 0;
}
Question 2:
Write C program statements for the following:
• Read bits from register R1 (16 bits, non-numeric)
• Write pre scale value (16-bit unsigned) to timer register t1.
• Read 32-bit value from ADC (unsigned)
• Loop counter for 100 program loops (unsigned)
• Define a structure representing a temperature sensor with fields for the sensor ID and
temperature value. Use typedef to create an alias for this structure. Write a function that
takes the sensor data as a parameter
Practice 2:
• Create a header file ‘mytypes.h’ by assigning user defined names for char, integer, float.
Dept. of ECE, RVCE Faculty of Embedded Vertical 6
Skill Lab: Embedded C
Bit-Wise operators in "Embedded" programming
Bitwise operators in embedded C play a crucial role in manipulating individual bits within data
registers, which is a common requirement in embedded systems programming. Embedded
systems often involve interfacing with hardware peripherals such as GPIO (General Purpose
Input/Output), UART (Universal Asynchronous Receiver/Transmitter), timers, and other control
registers. Bitwise operators are used to set, clear, toggle, or check the status of specific bits within
these hardware registers.
Operators:
<<, >> - shifts value k bits to the left or right
Ex: 0x08 >> 3 // 0x01
& - bitwise ANDs two operands together
Ex: 0x08 & 0xF0 // 0x00
| - bitwise ORs two operands together
Ex: 0x08 | 0xF0 // 0xF8
^ - bitwise XORs two operands together
Ex: 0xAA ^ 0xFF // 0x55
The bitwise AND and OR operators can be used to read/write individual bits in a variable:
Ex. unsigned char reg = 0xB5;
if(reg & 0x08) // Is the 4th bit a “1”?
…
else // 4th bit is not a “1”
…
reg = reg | 0x02; // Set the 2nd bit to a “1”
reg = reg & 0xFE; // Set the 1st bit to a “0”
Question 3:
a. Write the output of following operations.
OPERATION RESULT
0x35 & 0x0F
0x04 | 0x68
0x54 ^ 0x78
~ 0x55
0x9A >>3
0x77 >> 4
0x06 << 4
Dept. of ECE, RVCE Faculty of Embedded Vertical 7
Skill Lab: Embedded C
b. The bit pattern of serial control (SCON) register of 8051 MCU is given below:
Write program statement to perform the following:
i. Set to 8-bit UART mode with multiprocessor disable.
ii. Set to 9-bit UART with additional bit as 1.
c. The bit pattern of Timer mode control(TMOD) register of 8051 MCU is given below:
Write program statement to perform the following:
i. Set timer 0 as 8 bit auto reload counter.
ii. Set timer 1 as 16-bit timer.
Practice 3:
a. Write a program to convert BCD 29 to ASCII and to display the results in watch window.
Use Keil uVision with target as 8051.
b. Write a program to send 8-bit digital value on LSB pin of P1 of 8051. Use Keil uVision
with target as 8051.
Bit extraction and working with memory mapped peripheral registers
Dept. of ECE, RVCE Faculty of Embedded Vertical 8
Skill Lab: Embedded C
In embedded systems programming, bit extraction and manipulation of memory-mapped
peripheral registers are common tasks. Memory-mapped registers are regions in the
microcontroller's memory space that represent control and status registers for various hardware
peripherals. Bit extraction involves isolating specific bits from a register's content, and bitwise
operators play a crucial role in these operations.
Masking:
• Use bitwise AND (&) to create a mask with 1s in the positions of the bits you want to
extract.
• Use this mask to perform AND operation with the register content to extract the desired
bits.
Ex:
// Extract bits 3 to 6 from register
uint8_t registerValue = /* read from memory-mapped register */;
uint8_t extractedBits = (registerValue & 0x78) >> 3;
// Extract bits 4 to 7 from register
uint8_t registerValue = /* read from memory-mapped register */;
uint8_t extractedBits = (registerValue >> 4) & 0x0F;
Question 4:
Identify the output of following program statements:
uint16_t *controlRegister = (volatile uint16_t *)0x40020000; // Example address
uint16_t mask = 0x0F00; /
uint16_t valueToSet = (newValue << 8) & mask;
*controlRegister = (*controlRegister & ~mask) | valueToSet;
volatile uint8_t *statusRegister = (volatile uint8_t *)0x40030000; // Example address
uint8_t bitsToToggle = 0x03;
*statusRegister ^= bitsToToggle;
Practice 5: Simulate the program and verify the output.
#include <reg51.h>
// Define the memory-mapped register address
sfr myPeripheralReg = 0x80;
sfr newvalues=P1;
void main() {
unsigned char regValue = myPeripheralReg;
unsigned char extractedBits = (regValue >> 3) & 0x0F;
myPeripheralReg |= (1 << 2);
myPeripheralReg &= ~(1 << 5);
myPeripheralReg ^= (1 << 1);
if (myPeripheralReg & (1 << 4)) {
P2=0;
} else {
P2=0xFF;
}
Dept. of ECE, RVCE Faculty of Embedded Vertical 9
Skill Lab: Embedded C
myPeripheralReg = (myPeripheralReg & 0x0F) | ((newValues & 0x03) << 5);
while (1);
}
Inline functions in C
n C programming, an inline function is a function that is expanded in line at the point of the
function call rather than being called through the usual function call mechanism. The inline
keyword is used to suggest to the compiler that a particular function should be expanded inline.
Ex:
#include <stdio.h>
// Inline function declaration
inline int add (int a, int b);
int main() {
int result = add(3, 5);
printf("Result: %d\n", result);
return 0;
}
// Inline function definition
inline int add(int a, int b) {
return a + b;
}
#define macros in C
In C programming, #define is a preprocessor directive that allows you to create macros. Macros
are a way to define simple, text-based replacements in your code. They are processed by the
preprocessor before actual compilation, and the specified text is substituted wherever the macro
is used.
Ex:
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 5, y = 8;
int max_value = MAX(x, y);
printf("The maximum value is: %d\n", max_value);
return 0;
}
Question 5:
Answer the following questions:
When should you consider using inline functions in your code?
What is the potential advantage of using inline functions in terms of code optimization?
Compare and contrast inline functions and macros in terms of advantages and disadvantages.
Dept. of ECE, RVCE Faculty of Embedded Vertical 10
Skill Lab: Embedded C
What is the output of the following program?
#include<stdio.h>
#define CUBE(x) (x*x*x)
void main()
{
int a, b=2;
a=CUBE(b++);
printf(“%d %d”, a,b)
}
Practice 5:
Create an inline function to find the maximum of three numbers. Use this function in your main
program.
Write an inline function that checks whether a given number is even or odd. Use this function in
your main program.
Create a macro called SWAP to swap the values of two variables. Demonstrate the usage of this
macro in your program. Test the program in Keil uVision IDE.
Implicit and Explicit type casting and importance
Type casting, also known as type conversion, is the process of converting a variable from one
data type to another. There are two main types of type casting: implicit and explicit.
Implicit Type Casting:
Implicit type casting, also known as automatic type conversion, can be convenient in certain
situations where the compiler automatically promotes or demotes data types as needed. It is
helpful when dealing with expressions involving mixed data types, and the compiler can
automatically handle the conversions.
Ex: char charValue = 'A';
int intValue = 2;
int result = charValue * intValue; // Implicit casting of 'charValue' to int
unsigned int unsignedValue = 100;
int signedValue = -50;
int sum = unsignedValue + signedValue; // Implicit casting of 'signedValue' to unsigned int
short int a = 5;
int b = 10;
int sum = a + b; // Implicit casting of 'a' to int before addition
Explicit Type Casting:
Explicit type casting, also known as type conversion or type casting, occurs when a programmer
manually converts a variable from one data type to another. This is done by using casting
operators in the programming language. Explicit type casting provides control over the
conversion process, allowing the developer to choose the desired type and handle potential data
loss or precision issues.
Dept. of ECE, RVCE Faculty of Embedded Vertical 11
Skill Lab: Embedded C
Ex: double doubleValue = 5.75;
int intValue = (int)doubleValue; // Explicit casting from double to int
double doubleValue = 3.14;
int *intPointer = (int *)&doubleValue; // Explicit casting from double pointer to int pointer
void *genericPointer = /* some memory address */;
int *intPointer = (int *)genericPointer; // Explicit casting from void pointer to int pointer
unsigned int bitmask = 0xFF;
char result = (char)(bitmask & 0xFF); // Explicit casting with bitwise operation
Why type casting is required?
• Embedded systems often have limited memory resources. Type casting allows you to
optimize memory usage by converting data types to the smallest size necessary for a
particular operation.
• Embedded systems often interface with various peripherals, such as sensors and
communication modules. These peripherals may expect data in a specific format or size,
requiring type casting to match the expected data type.
• Embedded systems can vary significantly in terms of architecture and hardware. Type
casting can be used to ensure code portability by adapting to the specific data type
requirements of different platforms.
• Certain hardware modules in embedded systems might have specific requirements
regarding the data type they accept or return. Type casting allows you to conform to these
requirements.
Pointers
In C, pointers are a fundamental concept that allows you to directly manipulate memory
addresses.
We declare a pointer variable by specifying the data type it points to followed by an asterisk (*),
then the variable name. We can initialize a pointer to point to a specific memory address or to
NULL (indicating it does not point anywhere yet).
Ex: int *ptr; // Pointer to an integer
int *ptr = NULL; // Initialized to NULL
Dereferencing:
int x = 10;
int *ptr = &x; // Pointer points to the address of x
printf("%d", *ptr); // Output: 10
Pointer Arithmetic:
C allows arithmetic operations on pointers. Adding or subtracting an integer from a pointer
moves it to the next or previous memory location of the same data type.
Ex: int arr[] = {10, 20, 30, 40};
int *ptr = arr; // Pointer points to the first element of arr
printf("%d", *(ptr + 2)); // Output: 30
Dept. of ECE, RVCE Faculty of Embedded Vertical 12
Skill Lab: Embedded C
Question 6:
Analyze the use of different statements in the program given below:
#include <reg51.h>
// Define pointers to access specific registers
unsigned char * const P0 = (unsigned char *)0x80; // Pointer to Port 0
unsigned char * const P1 = (unsigned char *)0x90; // Pointer to Port 1
unsigned char * const P2 = (unsigned char *)0xA0; // Pointer to Port 2
unsigned char * const P3 = (unsigned char *)0xB0; // Pointer to Port 3
unsigned char * const PSW = (unsigned char *)0xD0; // Pointer to Program Status Word
unsigned char * const ACC = (unsigned char *)0xE0; // Pointer to Accumulator
unsigned char * const B = (unsigned char *)0xF0; // Pointer to B register
void main() {
// Example usage: Set Port 0 to output
*P0 = 0xFF; // Set all pins of Port 0 to output
// Example usage: Read from Port 1 and store the value in accumulator
*ACC = *P1; // Read from Port 1 and store the value in accumulator
// Example usage: Increment the value of Port 2
(*P2)++; // Increment the value of Port 2
// Example usage: Toggle a bit in Port 3
*P3 ^= (1 << 3); // Toggle the 4th bit of Port 3
// Example usage: Modify bits in Program Status Word (PSW)
*PSW |= (1 << 0); // Set the CY (Carry) flag in PSW
}
Practice 6:
Write a program to create a string “RVCE is my college.” Add statements to send vowels in the
string to port 1 and consonants to port 2. Use pointers to access the characters from the string.
Accessing memory mapped peripheral registers using pointers
The addressable memory space of a microcontroller or microprocessor depends on their
address bus width. For instance, if we take the example of ARM Cortex M4 32-bit
microcontroller, its addressable memory space is 2^32 which is equal to 4 gigabytes of memory.
Each byte of this memory space has a unique memory address and the Cortex M4 microcontroller
can access each memory location either to read and write data to each memory location.
When working with memory-mapped peripheral registers in embedded systems, pointers
play a crucial role. Let’s dive into how you can access these registers using pointers:
Memory-Mapped Peripheral Registers:
Microcontrollers have two types of memory regions: memory-mapped and non-memory-
mapped. Non-memory-mapped region includes internal general-purpose and special function
registers of the CPU. These registers are accessed using their names in assembly language or
inline assembly features in C programming. However, for memory-mapped peripheral registers,
we use their memory addresses directly.
Dept. of ECE, RVCE Faculty of Embedded Vertical 13
Skill Lab: Embedded C
Let’s consider the 8051 microcontroller.
The diagram below shows the RAM organization:
The RAM locations between 0x80 to 0xFF are used to realize peripheral registers (e.g., P0,
P1,SP,A,TH0, TH, etc.).
Accessing Registers Using Pointers:
To access a peripheral register, define a pointer to its address:
volatile uint32_t *register_address = (int8_t *)0x80;
For example, to set the 5th bit of a register:
*register_address |= 1 << 5;
Use of pointers to access Peripheral Registers in 32-bit MCUs
To understand this concept, let’s take an example of a direction control register of PORTF of
TM4C123GH6PM microcontroller. This register is used to configure the pins of PORTF either
input or output. This is a 32-bit register but only the first 8 bits [0:7] are used. Bits 0 to 7 are used
to set the direction pin. The figure below shows bits assignment of GPIO direction register.
Example: Finding the physical address of the direction control register GPIODIR of PORTF.
Base address of PORTF= 0x4002500
Offset address of GPIODIR=0x400
GPIOFDIR Physical address=0x40025000+0x400=
Dept. of ECE, RVCE Faculty of Embedded Vertical 14
Skill Lab: Embedded C
The GPIOFDIR, which is the direction control register of PORTF, is mapped to the address
0x40025400 in the peripheral region. We want to set the first four pins as digital input pins and
last four pins as digital output pins. Two methods are followed.
• Using a pointer variable
This method requires the use of a pointer variable. Let’s initialize a pointer variable and assign
it to the memory address that we calculated in the last step.
volatile const uint32_t * GPIO_PORTF_DIR_R= 0x40025400
By using dereference operation with the pointer variable “GPIO_PORTF_DIR_R”, it will update
this value to the memory location 0x40025400.
*GPIO_PORTF_DIR_R = 0xF0;
0xF0 is equal to 1111_0000 in binary. Hence, it will configure the first four pins as input and the
last four pins as output.
• Directly dereferencing memory
Directly dereferencing memory method does not create any memory storage to interact with
peripheral register through their memory addresses.
// write value 0xF0 to GPIOFDIR register
(* ( ( volatile unsigned int * ) 0x40025400 ) ) = 0xF0;
//read value GPIOFDIR register memory address and store it in variable data.
data = (* ( ( volatile unsigned int * ) 0x40025400 ) ) ;
We do not need to create any pointer variable and we can still read/write a value to a specific
memory location. But one disadvantage of this method is that it makes the code very difficult
and hard to understand. The easiest way to make code readable is the use of #define directives
and give it a readable and easily understandable name like this:
#define GPIO_PORTF_DIR_R (*( ( volatile unsigned int * )0x40025400 ) )
Dept. of ECE, RVCE Faculty of Embedded Vertical 15
Skill Lab: Embedded C
We can use this name inside our code to read and write value to memory location 0x40025400.
GPIO_PORTF_DIR_R = 0xF0;
data = GPIO_PORTF_DIR_R;
Storage Class specifiers in C
In C programming, storage class specifiers are keywords that define the scope, duration, and
visibility of variables. These specifiers determine where and how variables are stored in memory,
as well as their lifetime and accessibility within a program.
The storage class specifiers in C are:
auto: This is the default storage class specifier for local variables. Variables declared with the
auto specifier are typically stored in the stack memory and have automatic duration, meaning
they are created when the block containing their declaration is entered and destroyed when the
block is exited.
Ex: #include <stdio.h>
int main() {
auto int x = 10; // 'auto' is optional for local variables
printf("x = %d\n", x);
return 0;
}
register: This specifier is used to request that the compiler store the variable in a CPU register
for faster access. However, the compiler may ignore this request if it deems it unnecessary.
Variables declared with the register specifier have automatic duration like auto variables.
Ex: #include <stdio.h>
int main() {
register int x = 10; // Compiler may choose to store 'x' in a register
printf("x = %d\n", x);
return 0;
}
static: Variables declared with the static specifier have static duration, meaning they are allocated
memory once when the program starts and retain their value throughout the program's execution.
The scope of static variables is limited to the block in which they are declared, but they retain
their value between function calls.
Ex: #include <stdio.h>
void increment() {
static int count = 0; // 'count' retains its value between function calls
count++;
printf("Count: %d\n", count);
}
int main() {
increment(); // Output: Count: 1
increment(); // Output: Count: 2
Dept. of ECE, RVCE Faculty of Embedded Vertical 16
Skill Lab: Embedded C
increment(); // Output: Count: 3
return 0;
}
extern: This specifier is used to declare variables that are defined in another file. It tells the
compiler that the variable is defined elsewhere, allowing the program to use the variable without
redefining it. extern variables are typically declared in header files and defined in source files.
Ex: file1.c
#include <stdio.h>
int globalVar = 10; // Definition of globalVar
void printGlobalVar() {
printf("Global Variable: %d\n", globalVar);
}
file2.c
#include <stdio.h>
extern int globalVar; // Declaration of globalVar
int main() {
printf("Global Variable: %d\n", globalVar); // Accessing globalVar from another file
}
Question 7:
What are the potential drawbacks of using too many static variables or functions in an embedded
C program?
Under what circumstances should you use the register storage class?
Write a C program to count number of times a function is called.
Volatile Keyword
In C programming, the volatile keyword is used to indicate to the compiler that a variable may
be changed at any time by external forces not directly related to the program's flow. This prevents
the compiler from performing certain optimizations that could lead to incorrect behaviour when
dealing with such variables.
Ex: include <stdio.h>
// Memory-mapped hardware register
volatile unsigned int* timer_register = (volatile unsigned int*)0x12345678;
void delay(int milliseconds) {
*timer_register = 0; // Reset the timer register
// Wait until the specified time elapses
while (*timer_register < milliseconds) {
// Do nothing
}
}
int main() {
delay(1000); // Delay for 1000 milliseconds
printf("Delay complete!\n");
return 0;
}
Dept. of ECE, RVCE Faculty of Embedded Vertical 17
Skill Lab: Embedded C
In this example, timer_register is declared as a pointer to a volatile unsigned integer, representing
a memory-mapped hardware register. The delay function waits for a specified time using this
hardware register.
Structures & Unions
A structure is a user-defined data type that allows you to combine data items of different kinds
into a single unit. It is commonly used to represent a record, where each member of the
structure holds a different piece of information. To define a structure, you use the struct
statement.
The format is as follows:
struct [structure name] {
member definition;
member definition;
// ...
};
Ex:
struct example_struct {
unsigned int member1;
float member2;
char* member3;
};
struct example_struct foo;
struct example_struct bar = {4,1.4,”hello world”};
struct example_struct
{
unsigned int member1;
float member2;
char* member3;
} foo, bar;
foo.member1=2
Accessing data members using pointer:
struct example_struct *foo;
foo->member1=2;
Unions
In C programming, unions are a type of composite data structure that allows you to store different
data types in the same memory location. Unlike structures, where each member has its own
memory space, members of a union share the same memory location. This means that only one
member of the union can hold a value at a time.
Dept. of ECE, RVCE Faculty of Embedded Vertical 18
Skill Lab: Embedded C
Ex: #include <stdio.h>
// Define a union named Number
union Number {
int intValue;
float floatValue;
double doubleValue;
};
int main() {
union Number num; // Declare a variable of type Number
num.intValue = 10; // Assign a value to the int member
printf("Integer value: %d\n", num.intValue);
num.floatValue = 3.14; // Assign a value to the float member
printf("Float value: %f\n", num.floatValue);
num.doubleValue = 2.71828; // Assign a value to the double member
printf("Double value: %lf\n", num.doubleValue);
}
Difference between Structure and Union
In structure each member gets separate space in memory.
Ex: struct student {
int rollno;
char gender;
float marks;
} s1;
The total memory required to store a structure variable is equal to the sum of size of all the
members. In above case 7 bytes (2+1+4) will be required to store structure variable s1.
In union, the total memory space allocated is equal to the member with largest size. All other
members share the same memory space. This is the biggest difference between structure and
union.
Ex: union student {
int rollno;
char gender;
float marks;
} s1;
In above example variable marks is of float type and have largest size (4 bytes). So, the total
memory required to store union variable s1 is 4 bytes.
The following code will work fine but will show erroneous output in the case of union. Because
we can access only that variable whose value is recently stored.
s1.rollno = 20;
s1.marks = 90.0;
printf("%d",s1.rollno);
Dept. of ECE, RVCE Faculty of Embedded Vertical 19
Skill Lab: Embedded C
Question 8
a. Write the declaration of structure for the following specifications:
Use typedef keyword to define structure of type MY_REGISTER_TYPE
Add a data member of size 32 bits at an offset of 0 bytes from base address.
Add another member of 4 elements each of size 16 bits at an offset of 10 bytes from base
address.
Add another member of size 32 bits at an offset of 32 bytes from base address.
b. Write statement to create a structure instance at memory address 0x4000F4000. Show the
statements to access the members of the same.
c. Analyze the following code and identify the use of unions:
typedef struct { /*!< (@ 0x40007000) USB1 Structure */
uint32_t RESERVED0[64];
uint32_t CAPLENGTH; /*!< (@ 0x40007100) Capability register length */
uint32_t RESERVED1[5];
union {
uint32_t USBCMD_H; /*!< (@ 0x40007140) USB command (host mode) */
uint32_t USBCMD_D; /*!< (@ 0x40007140) USB command (device mode)
};
union {
int32_t USBSTS_H; /*!< (@ 0x40007144) USB status (host mode) */
uint32_t USBSTS_D; /*!< (@ 0x40007144) USB status (device mode) */
};
union {
uint32_t FRINDEX_H; /*!< (@ 0x4000714C)USB frame index (host mode) */
uint32_t FRINDEX_D; /*!< (@ 0x4000714C)USB frame index (device mode) */
};
uint32_t BURSTSIZE; /*!< (@ 0x40007160) Programmable burst size */
uint32_t ULPIVIEWPORT; /*!< (@ 0x40007170) ULPI viewport */
__I uint32_t RESERVED6[8];
union {
uint32_t USBMODE_H; /*!< (@ 0x400071A8) USB mode (host mode) */
uint32_t USBMODE_D; /*!< (@ 0x400071A8) USB mode (device mode) */
};
uint32_t ENDPTSETUPSTAT; /*!< (@ 0x400071AC) Endpoint setup status */
uint32_t ENDPTPRIME; /*!< (@ 0x400071B0) Endpoint initialization */
uint32_t ENDPTCTRL1; /*!< (@ 0x400071C4) Endpoint control */
uint32_t ENDPTCTRL2; /*!< (@ 0x400071C8) Endpoint control */
uint32_t ENDPTCTRL3; /*!< (@ 0x400071CC) Endpoint control */
} LPC_USB1_Type;
Dept. of ECE, RVCE Faculty of Embedded Vertical 20
Skill Lab: Embedded C
d. The table below shows registers of Power Mode Controller(PMC) of a 32 bit microcontroller.
The corresponding structure declaration is shown below:
typedef struct { /*!< (@ 0x40042000) PMC Structure */
uint32_t PD0_SLEEP0_HW_ENA; /*!< (@ 0x40042000) Hardware sleep event enable register */
uint32_t RESERVED0[6];
uint32_t PD0_SLEEP0_MODE; /*!< (@ 0x4004201C) Sleep power mode register */
} LPC_PMC_Type;
Identify the operation of the following satements:
#define LPC_PMC_BASE 0x40042000
#define LPC_PMC ((LPC_PMC_Type *) LPC_PMC_BASE)
References:
1. ChatGPT
2. Online material
For queries:
Dr.Govinda Raju M Prof. Praveen S
[email protected] [email protected]
Prof. Ravishankar Holla Prof. Pratibha K
[email protected] [email protected]
Dept. of ECE, RVCE Faculty of Embedded Vertical 21