Unit 3 Material
Unit 3 Material
4. Top-Down Design Approach: The top-down design approach starts with designing
the main program, followed by modules, sub-modules, and then individual functions.
This helps in managing the complexity of the code by breaking it down into smaller
parts.
Control over Precise and direct control over Indirect control through
Hardware hardware. abstractions.
Application Ideal for device drivers, real-time Best for general applications
Suitability operations. and complex system logic.
3. Constants are defined using the #define directive. This makes code easier to modify,
as changing a constant's value in one place affects its value throughout the program.
Example:
#define PI 3.14159 // Constant for the value of Pi
Key Program Elements: Header and Source Files, Preprocessor Directives
1. Include Directive for the Inclusion of Files
In C, the #include directive is used to insert the contents of a file into the program at compile
time. Files included can be:
Header Files: These files typically end with .h and include function prototypes, macro
definitions, global variables, and constants. Standard C libraries, like stdio.h, provide basic
I/O functions, while user-defined header files allow custom functions and data structures to
be shared across multiple source files.
#include <math.h> // For mathematical functions like sqrt()
#include "network.h" // For custom network-related functions
Source Code Files: These files contain actual program code and typically have .c extensions.
In embedded systems, the application source file will contain the main function and calls to
other functions.
Example:
#include "sysLib.c" // Including system library for system-level functions
Text and Data Files: Non-code files like .txt or .cfg can be included for configurations or
specific data used by the program.
Example:
#include "netDrvConfig.txt" // For network driver configuration details
Source Files: Source files are the backbone of any C application. They contain the actual
implementation of the program's logic, functions, and main execution code. These files are
compiled to generate the final executable binary. Typically, a source file contains:
• Main function (main ()): Entry point for the program execution.
• Function Definitions: Code for any user-defined or library functions that are used in
the application.
• Preprocessor Directives: Instructions for including files or defining constants.
Example:
void main () {
printf ("Hello, World!\n");
// Call other functions here
}
Configuration Files: Configuration files are used to define system parameters and device
configurations. These are especially important in embedded systems where the behavior of
hardware peripherals (e.g., serial communication, network interfaces) must be defined.
• Example: A file called serialLine_cfg.h may contain configuration details for UART
communication in an embedded system:
#include "serialLine_cfg.h"
Preprocessor Directives: Preprocessor directives begin with a # symbol and are
processed before the actual code compilation. These can include
Global Variables: Preprocessor directives can define global variables to be used throughout
the code.
Example: #define volatile int Timer // A volatile global variable Timer
Constants: Preprocessor directives can define constants for values used frequently in the
program. This increases code readability and ease of maintenance.
Example:
#define TRUE 1
#define FALSE 0
Macros: Macros allow the definition of complex expressions that can be reused in the code.
Example: #define SQUARE(x) ((x) * (x))
Macro: The compiler replaces the macro name with its defined code during the
pre-processing phase before compilation. There’s no context saving or restoring,
as it is directly inserted inline where the macro is used.
Example: #include<stdio.h>
#define NUMBER 10
intmain ()
printf("%d", NUMBER);
return0;
MACRO FUNCTION
Use of macro can lead to side effects at Functions do not lead to any side
later stages effects in any case
Macros are useful when small code is Functions are useful when large
repeated many times code is to be written
Use of Data Types: When naming a variable, memory is allocated based on the data type.
Different data types allocate varying amounts of memory, and this is fundamental to how the
program interacts with the system's hardware.
Primitive Data Types in C:
1. char (8 bits): Stores characters, equivalent to 1 byte.
2. byte (8 bits): Similar to char, used for storing small numbers or characters (some
compilers do not define "byte" and use "char" instead).
3. unsigned short (16 bits): Stores positive integers, occupying 2 bytes of memory.
4. short (16 bits): Stores integers, including negative values, within the 16-bit range.
5. unsigned int (32 bits): Stores positive integers, using 4 bytes.
6. int (32 bits): Stores signed integers.
7. long double (64 bits): For floating-point numbers with high precision, using 8 bytes.
8. float (32 bits): Stores floating-point numbers, consuming 4 bytes.
9. double (64 bits): A double-precision floating-point number, consuming 8 bytes.
Some compilers do not support certain types (like "byte"). In those cases, typedef can be used
to create custom types.
Hardware-Specific Data Types:
Example: A 16-bit timer can only use an unsigned short data type because it matches the
hardware architecture, which can count values from 0 to 65535.
Using typedef:The typedef keyword allows the creation of new data types. For instance, if a
compiler doesn't support "unsigned byte", you can define it as:
Example: typedef unsigned char portAdata;
****Demonstrates how to use basic data types such as char, int, and long in embedded C****
#include <stdio.h>
void delay_ms(unsigned int ms) // Function to simulate delay
{
while (ms--) // Simulate a delay by looping
{
// Do nothing
}
}
int main()
{
unsigned char led State = 0x01; // 8-bit value to represent the LED state
unsigned int delay = 1000; // 16-bit delay value (in milliseconds)
while (1) { // Infinite loop to simulate LED blinking
printf("LED ON: 0x%02X\n", ledState);
delay_ms(delay); // Wait for some time
ledState = ~ledState; // Toggle LED state
printf("LED OFF: 0x%02X\n", ledState);
delay_ms(delay); // Wait for some time
}
return 0;
}
2. void *portAdata; // allows the compiler to allocate an address for portAdata without
type checking. It is often used for dynamically allocated memory or passing generic
data structures.
printf("Value of data: %d\n", data);// Print the value of 'data' and its address using the
pointer
printf("Pointer p holds the address: %p\n", p);
printf("Value at the address held by p: %d\n", *p); // Dereference the pointer
*p = 200; // Modify the value of 'data' using the pointer
printf("Modified value of data: %d\n", data);
return 0;
}
Use of Data Structures: Queues, Stacks, Lists, and Trees
In embedded systems programming, data structures are critical tools for efficiently organizing
and manipulating data. They provide mechanisms to store, retrieve, and manage data in
memory in an organized manner.
A data structure is a collection of data elements organized in memory, allowing easy access,
modification, and management of the data. Embedded systems require the careful use of data
structures due to limited resources (memory, CPU speed), making it essential to choose
appropriate structures for specific use cases.
Some of the most commonly used data structures in embedded systems include:
1. Stacks: Used for last-in-first-out (LIFO) data management.
2. Queues: Used for first-in-first-out (FIFO) data management.
3. Lists: A dynamic collection of data elements, often used when the size of the data
collection is not fixed.
4. Trees: A hierarchical structure often used for searching and sorting operations.
Let's break down these data structures and their usage in embedded systems.
Stacks:A stack is a LIFO (Last In, First Out) data structure where the most recent element
added is the first one to be retrieved. When a function call is made, the return address is
"pushed" onto the stack, and when the function returns, the address is "popped" off the stack.
This ensures that the program can return to the correct point in the code.
• Push: Add an element to the stack (increment the stack pointer and place data at the
new top).
• Pop: Remove an element from the stack (retrieve data from the top and decrement the
stack pointer).
Example:
#include <stdio.h>
#define STACK_SIZE 105
int stack[STACK_SIZE];
int top = -1; // This variable top keeps track of the index of the top element of the
stack. It is initialized to -1, meaning the stack is empty.
Queues:A queue is a FIFO (First In, First Out) data structure where the first element added is
the first one to be retrieved.
Queues are commonly used for
Task scheduling: Handling tasks that are processed in the order they are added.
Buffering data: Handling data streams like network packets or data from sensors.
Example:
#include <stdio.h>
#define QUEUE_SIZE 10
int queue[QUEUE_SIZE];
int front = 0;
int rear = -1;
int count = 0;
dequeue();
dequeue();
dequeue();
dequeue(); // This will cause underflow
return 0;
}
Lists:A list is a collection of elements where each element points to the next one or both the
next and the previous one. Lists are useful in embedded systems when you need dynamic
memory allocation or when the size of the data set is unknown or constantly changing.
Example:A list can be used to maintain a collection of tasks in a system, allowing tasks to be
added or removed dynamically.
• Insert: Add a new element anywhere in the list.
• Delete: Remove an element from anywhere in the list.
Trees:A tree is a hierarchical data structure consisting of nodes, where each node has a value
and pointers to child nodes. Trees are useful in operations where sorted data or hierarchical
relationships are needed, such as in:
• Binary Search Trees: Efficient searching and sorting.
• Decision Trees: Used in applications like AI.
• File Systems: Organizing files in a directory hierarchy.
Example:A binary search tree (BST) is a tree where each node has at most two children. The
left child contains a value smaller than its parent, and the right child contains a value larger
than its parent. This allows for efficient searching, insertion, and deletion of elements.
• Insert: Place a new node in the correct position based on its value.
• Delete: Remove a node while maintaining the tree structure.
Use of Modifiers
Modifiers are used to alter the properties of data types or functions, controlling
their behaviour in terms of memory allocation, scope, or optimization.
1.Unsigned Modifier:Unsigned can be applied to short, int, or long data types. It
allows only non-negative values (0 and positive numbers).
Example:
unsigned int positive Value = 100; // Values range from 0 to 2^32 - 1 for 32-bit
unsigned int.
2.Static Modifier Inside a Function Block:When a variable is declared as static
inside a function, its value is persistent across function calls. It retains its value
even after the function finishes execution.
Example:
void counter ()
{
static int count = 0; // Persists across function calls
count++;
printf("%d\n", count);//Every time the counter () function is called, count will
increase, retaining its previous value.
}
3.Const Modifier: The const modifier makes a variable's value read-only. Once
initialized, the variable cannot be modified.
Example:
const int PI = 3.14; // PI cannot be changed&When used in global scope, it
must be initialized during its declaration
4.Register Modifier:The register modifier suggests to the compiler that the
variable should be stored in a CPU register instead of RAM for faster access.
This is often used for loop counters or frequently accessed variables.
Example:
register int counter = 0; // Faster access via CPU register
5.Interrupt Modifier:The interrupt modifier tells the compiler that the function is
an interrupt service routine (ISR). The compiler will save all processor registers
at the entry of the ISR and restore them upon exit.
Example:
void __interrupt ISR_function ()
{
// ISR code
}
6.Extern Modifier:The extern keyword is used to declare a global variable or
function that is defined in another file.
Example:
extern int external_var; // Defined in another file
7.Volatile Modifier:The volatile modifier is used to inform the compiler that the
value of a variable may change unexpectedly.
Example:
volatile int sensor Value; // Can change due to hardware interrupts
void main(void) {
// Declarations and initializations here
while (true)
{
// Code that continuously executes, e.g., monitoring inputs
}
}
// Perform an action when the sensor is active
}
}