Nile University Embedded Real Time Systems
School of Engineering and Applied Sciences
Lecture 2
Embedded Systems programming
Development Tools:
Integrated Development Environments (IDEs): Microchip Studio
Microchip Studio is an Integrated Development Environment (IDE) for developing
and debugging AVR® and SAM microcontroller applications
https://www.microchip.com/en-us/tools-resources/develop/microchip-studio#Downloads
Recommended Reading:
Book: " The AVR Microcontroller and Embedded Systems: Using Assembly and
C " by Muhammad Ali Mazidi, Sarmad Naimi, and Sepehr Naimi
Embedded Systems consist of hardware and firmware.
What is the firmware?
A specialized type of software embedded in the embedded system (ES) provides low-
level control over the device's specific hardware.
It is stored in non-volatile memory, such as Flash, EEPROM, or ROM.
Unlike standard software, it can directly control, manipulate, and interact with the
device hardware.
Layered Architecture of Firmware:
Organizing the code in a hierarchical manner in which individual layers are
implemented and interfacing to perform the desired application
Hardware Abstraction Layer(HAL)
Driver Layer
Middleware Layer
Application code Layer (Top layer)
Layered Firmware Architecture:
1. Hardware Abstraction Layer (HAL)
Wraps raw registers in easy-to-use functions
Hides MCU-specific details
2. Driver Layer
Builds on HAL to control peripherals (e.g. timers, ADC, I²C)
Offers clear Application Programming Interface (APIs) like
ADC_Read(channel) or UART_Send(buf)
3. Middleware Layer
Implements common services (e.g. TCP/IP stack, file system, RTOS)
Doesn’t touch hardware directly (uses Drivers)
4. Application Layer
Your product logic (User Interface (UI), control loops, data
processing)
Calls Middleware or Drivers, never raw registers
ECEN435 Dr. Noha Younis
Benefits of Layering
Modularity: Each layer is a self-contained block you can develop or test on
its own
Portability: Swap out HAL/Drivers for a new MCU without rewriting your
app
Maintainability: Bugs and updates stay localized to one layer
Drawbacks of Layering in ERTS:
Overhead: Extra function calls and interfaces add CPU cycles & stack
usage
ECEN435 Dr. Noha Younis
Programming Languages for Firmware:
Main criteria for selecting a suitable programming language for embedded
systems:
1. Efficiency: Optimal utilization of resource constraints.
2. High Performance: Ability to achieve real time performance.
3. Ease of Use and Implementation: Simplicity in coding and deploying.
4. Portability: Ease of transferring code across different hardware platforms.
5. Maintainability: Ease of updating and modifying the code.
6. Readability: Clarity and understandability of the code.
7. Widespread Integration: Widely recognized in several powerful Integrated
Development Environments IDEs.
Various programming languages are used for firmware, including C, C++,
Assembly, Basic, Python, Java, Fortran, and others.
Feature C C++ Assembly
High High
Efficiency The best
(with optimization) (with optimization)
High High
Performance The best
(with optimization) (with optimization)
Implementation
Moderate Moderate to High Very High
Complexity
Maintainability Moderate to high Moderate to high Low
Readability Moderate to high High Low
High with some High with some
Portability Low
modification modification
Rich of standard Rich of standard
Libraries Rare
libraries libraries for OOD
Performance-
Various Embedded
Complex and large critical and
Applications Real-Time Systems
projects. resource-
(ERTS)
constrained
ECEN435 Dr. Noha Younis
Programming Language to Hex File
1. Writing Firmware:
Write your firmware in a high-level language.
2. Compilation:
Compile the created project code into machine code specific to the
target MCU, generating object files.
Perform the first optimization level of the machine code.
Compilers provide error checking and debugging support.
3. Linking:
Link the generated object files and required libraries, then optimize
the linked files and generates the executable file (.elf)
Map the machine code into the appropriate sections of memory.
4. Hex File Generation:
Convert the executable file into a hexadecimal file that contains the
binary instructions in a format suitable for burning into the
microcontroller’s memory.
Several Integrated Development Environments (IDEs) such as Microchip Studio,
Eclipse and others streamline all these processes, providing a cohesive platform
for writing, compiling, linking, and generating hex files.
GCC Compiler :
Supported Languages: C, C++, Ada, Fortran, Objective-C, etc.
Platforms: Widely used on various platforms including Linux, Windows,
macOS.
Target Architectures: Supports a wide range of embedded architectures
including ARM, AVR, MIPS, PowerPC, etc.
Features: Highly configurable and widely adopted open-source compiler
suite.
ECEN435 Dr. Noha Younis
Data Types in Embedded C
Advantages of Defining Data Types:
1. Performance and Memory Management: Using smaller data types can
enhance performance and reduce memory usage.
2. Portability: Custom data types can be defined (using typedef or in a
separate header file) to align with the target architecture's capabilities and
constraints. typedef int8_t s8;
3. Type Safety: Helps prevent errors related to incorrect data manipulation.
4. Readability and Maintenance: Enhances the readability and
maintainability of the code.
5. Data Standardization: Ensures consistency across different compilers for
the same hardware architecture.
ECEN435 Dr. Noha Younis
Data Types in Embedded C
*Certain data types require including stdint.h (int8_t, uint16_t, ...etc.).
ECEN435 Dr. Noha Younis
I. GPIO Access of Atmega32
general-purpose input/output
ATmega32 Microcontroller has four Ports, namely Port A, Port B, Port C and Port D.
The Input /Output functions of each port are set by three Registers.
1- Data Direction Register (DDRX): Determines whether a pin is input or output for Port X.
1) At program start (initial setup) :::: E7na input wala output
EX: set whether a pin will send data (output) or receive data (input)
DDRA = 0XFF; // Set PORTA as an OUTPUT
DDRD = 0; // Set PORTD as an INPUT
If pin is output: writes a HIGH (1) logic level.
2-Data register (PORTX): - Sets the output value of Port X.
You want to send output signal (e.g., turn
on LED, send data).
PORTA = 0XF5 ; // Send the value 0xF5 to the output port PORTA.
3- Port input register (PINX): - Reads the value of Port X.
To detect button presses, sensor input, etc.
uint8_t K = PINC // read the value received data at PORTC and store it in variable K
ECEN435 Dr. Noha Younis
I/O Bit-wise Accessing
1-Bit-wise output pin configuration and access:
DDRX |= (1 << i); // Set the ith pin in PORTx as output
PORTX &= ~(1 << i); //send logic zero to the ith pin in PORTX that is configured as output
PORTX |= (1 << i); //send logic one to the ith pin in PORTX that is configured as output
2-Bit-wise input pin configuration and access:
DDRX &= ~(1 << i) // Set the ith pin in PINX as input
uint8_t K = (PINX >> i) & 0X01 // Variable K stores the ith input pin of PINX
Internal Pull-Up Resistor:
When a pin is configured as an input, it can be left floating, connected to Vcc, or
connected to GND.
A pull-up resistor internally connects the pin to Vcc (logic high) when no external signal
is applied, preventing the pin from floating (undefined state).
Pull-up resistance is enabled by sending one to PORTx :
DDRD = 0; // Set PORT D as an INPUT
PORTD = 0XFF ; // enable Pull-up resistor
uint8_t J = PIND ; // read the value received at PORTD and store it in variable J
ECEN435 Dr. Noha Younis
Example: Write an Embedded C program to toggle 8 active high LEDs connected to Port D
of an AVR microcontroller at a rate of 500 ms as long as an active low on off switch connected
to PORTA.3 turns on. Assume that the clock frequency is 9 MHz and the LEDs are initially
off.
#include <avr/io.h>
#define F_CPU 9000000UL
#include <util/delay.h>
#define L_SW 3
#define DELAY 500 // ms
int main(void) {
DDRD = 0xFF; // LEDs on Port D = outputs
DDRA &= ~(1 << L_SW); // Switch on PA.3 = input
PORTA |= (1 << L_SW); // Pull-up resistance
PORTD = 0x00; // Ensure LEDs start off
while (1) {
// if switch == 0
if ( !(PINA & (1 << L_SW)) ) {
PORTD ^= 0xFF; // toggle LEDs
_delay_ms(DELAY); // wait 500 ms
}
else {
PORTD = 0x00; // switch released → LEDs off
}
}
return 0; // never return
}
ECEN435 Dr. Noha Younis
ERTS in Industry (Robotics Applications)
Pulse Width Modulation (PWM)
Pulse Width Modulation (PWM) is widely used for controlling the speed of DC motors, used in
robotics applications, by varying the duty cycle of the PWM signal. The average voltage applied
to the motor is proportional to the duty cycle, allowing fine adjustment of motor speed.
Two Methods of PWM Generation: Software-based and Hardware-based PWM
1- Software-based PWM
Example: Write a C program to generate a 50 Hz PWM signal to control the speed of a DC motor.
The motor speed is regulated by an active-high switch: when the switch is off, the motor runs at full
speed (100% duty cycle). When the switch is turned on, the motor should gradually decelerate from
full speed to a stop (0% duty cycle), decreasing the duty cycle by 10% every 2 seconds.
The clock frequency is 16 MHz.
The PWM is generated at PORTA.6
The switch is connected to PORTC.4.
The switch is initially on
#include <avr/io.h>
#define F_CPU 16000000UL // Adjusted to 16 MHz
#include <util/delay.h>
#define PERIOD 20 // Period = 20 ms
#define PWM_OUT 6
#define H_SPEED_SW 4
void GEN_PWM(void)
{
for (uint8_t i = 90; i > 0; i -= 10)
{
uint8_t DELAY_H = 20 * i / 100; // duty cycle in ms
uint8_t DELAY_L = 20 - DELAY_H;
for (uint8_t j = 0; j < 100; j++) // to wait for 2 sec = 2000 ms/20 ms
= 100
{
PORTA |= (1 << PWM_OUT);
for (uint8_t k = 0; k < DELAY_H; k++) // Custom delay loop for high
time
{
_delay_ms(1);
}
PORTA &= ~(1 << PWM_OUT);
ECEN435 Dr. Noha Younis
for (uint8_t k = 0; k < DELAY_L; k++) // Custom delay loop for low
time
{
_delay_ms(1);
}
}
}
int main(void)
{
DDRA |= (1 << PWM_OUT); // Set PWM_OUT as output
PORTA |= (1 << PWM_OUT); // SET THE OUTPUT INITIALLY AS HIGH
DDRC &= ~(1 << H_SPEED_SW); // Set H_SPEED_SW as input. Note that a pull down
//resistance MUST be connected to this pin externally
uint8_t PREV_SW = 0 ; // As an initial state
uint8_t CURRENT_SW ;
while (1)
{
CURRENT_SW = (PINC >> H_SPEED_SW) & 0x01 ;
if (CURRENT_SW != PREV_SW) // A change occurs on the SW so I have to take
//an action
{ PREV_SW = CURRENT_SW ;
if (CURRENT_SW == 1) // The switch is on (active-high)
{
GEN_PWM(); // Turn off the motor gradually
}
else // The switch is off = 0 ;
{
PORTA |= (1 << PWM_OUT); // Set PWM_OUT high for full speed
}
return 0; // Return statement
}
ECEN435 Dr. Noha Younis
Disadvantages of Software-Based PWM:
Accuracy: Software PWM is less accurate than hardware PWM because it
relies on the software delays. The accuracy is limited by the precision of the
delay implementation and the overhead of the software loop.
Degradation of ERTS Performance: Software-based PWM consumes
more CPU resources compared to hardware PWM. The CPU is busy
executing the delay, which negatively affect the performance of other tasks
running within the Embedded Real-Time System (ERTS).
ECEN435 Dr. Noha Younis
Impact of Flatten, Macro, and Function-Based Coding in ERTS
Example: Write an AVR C program that continuously toggles two LEDs connected to
PORTA.2 and PORTD.3 every half a second. Assume that the CPU frequency is 9 MHz.
1-Flatten architecture: Low level abstract
#include <avr/io.h>
#define F_CPU 9000000UL // Must define the clk frequency as 9 MHz
#include <util/delay.h>
#define DELAY 500 // Define the toggling delay as 500 ms
void main(void) {
DDRD |=(1 << 3) ;
DDRA |=(1 << 2) ;
while (1) {
PORTD ^= (1 << 3);
PORTA ^= (1 << 2);
_delay_ms(DELAY); // Delay for 500 ms
}}
2-Maccro based architecture: Inline Expansion
#include <avr/io.h>
#define F_CPU 9000000UL // Must define the clk frequency as 9 MHz
#include <util/delay.h>
#define OUT_BIT(var , bit) (var) |=(1 << (bit))
#define SET_BIT(var , bit) (var) |= (1 << (bit))
#define CLR_BIT(vart, bit) (var) &=~(1 << (bit))
#define TOG_BIT(var, bit) (var) ^= (1 << (bit))
#define DELAY 500 // Define the toggling delay as 500 ms
void main(void) {
//DDRD |=(1 << 3) ; // Set all pins of Port D as outputs
OUT_BIT(DDRD , 3) ;
OUT_BIT(DDRA , 2) ;
while (1) {
//PORTD ^= (1 << 3); // Toggle all pins on Port D
TOG_BIT(PORTD , 3);
TOG_BIT(PORTA , 2);
_delay_ms(DELAY); // Delay for 500 ms }}
ECEN435 Dr. Noha Younis
3-Function based Architecture
#include <avr/io.h>
#define F_CPU 9000000UL // Define the clock frequency as 9 MHz
#include <util/delay.h>
#define DELAY 500 // Define the toggling delay as 500 ms
void OUT_BIT(volatile uint8_t *var, uint8_t bit) {
*var |= (1 << bit);
}
void SET_BIT(volatile uint8_t *var, uint8_t bit) {
*var |= (1 << bit);
}
void CLR_BIT(volatile uint8_t *var, uint8_t bit) {
*var &= ~(1 << bit);
}
void TOG_BIT(volatile uint8_t *var, uint8_t bit) {
*var ^= (1 << bit);
}
void main(void) {
OUT_BIT(&DDRD, 3); // Set pin 3 of Port D as output
OUT_BIT(&DDRA, 2); // Set pin 2 of Port A as output
while (1) {
TOG_BIT(&PORTD, 3); // Toggle pin 3 of Port D
TOG_BIT(&PORTA, 2); // Toggle pin 2 of Port A
_delay_ms(DELAY); // Delay for 500 ms
} }
ECEN435 Dr. Noha Younis
1- Fair optimization for debugging:
Flatten, Hex_size = 463Byts
MAC, Hex_size = 463Byts
FUNC, Hex_size = 635Byts
2-Maximum optimization after debugging:
ALL, Hex_size = 447 Byts
Flattening and inlining functions, especially nested ones, depend on the optimization
efficiency of the utilized compiler and linker. This can affect both storage and performance.
ECEN435 Dr. Noha Younis