Home Wiki Programming & Logic Embedded Programming: Programming Microcontrollers
Programming & Logic

Embedded Programming: Programming Microcontrollers

What is Embedded Programming?

Inside every factory, dozens of small controllers work invisibly: one monitors furnace temperature, another controls motor speed, a third reads fluid level in a tank. These are all embedded systems -- small computers dedicated to a single, specific task.

Embedded programming means writing the software (firmware) that runs directly on these chips -- without a full operating system, without a display, without a keyboard. You communicate directly with hardware: ports, registers, and interrupts.

Microcontrollers: Types and Selection

Arduino (ATmega328P)

The easiest platform for beginners. The ATmega328P runs at 16 MHz, has 32 KB of flash, 14 digital pins, and 6 analog inputs.

Strengths: easy to learn, vast libraries, large community Weaknesses: slow, limited memory, unsuitable for complex tasks

When to use: rapid prototyping, simple sensor reading, educational projects

ARM Cortex-M (STM32, ESP32)

For serious industrial projects. The STM32F4, for example, runs at 168 MHz, has 1 MB flash, and supports DMA and multi-level interrupts.

Strengths: fast, many peripherals, RTOS support Weaknesses: steeper learning curve, requires deeper hardware understanding

When to use: motor control, industrial monitoring systems, advanced IoT

ESP32

With built-in WiFi and Bluetooth, it is ideal for IoT projects. Dual-core at 240 MHz.

When to use: connecting sensors to the cloud, remote monitoring, wireless dashboards

GPIO: Your Gateway to the Physical World

GPIO (General Purpose Input/Output) pins connect the microcontroller to the outside world. Each pin can be:

  • Input: read a button state, sensor, limit switch
  • Output: drive an LED, relay, solenoid valve

Example: Reading a Temperature Sensor and Controlling a Fan

// Arduino: read NTC temperature sensor and control a cooling fan
const int TEMP_SENSOR = A0;    // temperature sensor on analog pin 0
const int FAN_RELAY = 7;       // fan relay on digital pin 7
const float TEMP_THRESHOLD = 60.0;  // threshold temperature in Celsius

void setup() {
  pinMode(FAN_RELAY, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int raw = analogRead(TEMP_SENSOR);  // read 0-1023
  float voltage = raw * (5.0 / 1023.0);
  float temperature = (voltage - 0.5) * 100.0;  // convert to Celsius

  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.println(" C");

  if (temperature > TEMP_THRESHOLD) {
    digitalWrite(FAN_RELAY, HIGH);   // turn fan ON
    Serial.println("Fan ON - Cooling active");
  } else {
    digitalWrite(FAN_RELAY, LOW);    // turn fan OFF
  }

  delay(1000);  // read every second
}

Interrupts: Instant Response

The problem with delay() and loop() is that you may miss a critical event while waiting. What if the emergency button is pressed while the controller is in the middle of delay(1000)?

Interrupts solve this: when an event occurs on a specific pin, the main program halts immediately, the interrupt handler executes, and then normal execution resumes.

// Arduino: emergency stop button with interrupt
const int EMERGENCY_BTN = 2;    // pin 2 supports external interrupts
const int MOTOR_RELAY = 8;
volatile bool emergencyStop = false;

void setup() {
  pinMode(EMERGENCY_BTN, INPUT_PULLUP);
  pinMode(MOTOR_RELAY, OUTPUT);
  // Attach interrupt: on falling edge, execute handler
  attachInterrupt(
    digitalPinToInterrupt(EMERGENCY_BTN),
    onEmergencyPress,
    FALLING
  );
}

// ISR -- must be short and fast
void onEmergencyPress() {
  emergencyStop = true;
}

void loop() {
  if (emergencyStop) {
    digitalWrite(MOTOR_RELAY, LOW);  // stop motor immediately
    Serial.println("EMERGENCY STOP ACTIVATED");
    while (true) {
      // stay stopped until manual reset
    }
  }
  // normal motor operation
  digitalWrite(MOTOR_RELAY, HIGH);
}

Key rules for interrupts:

  • Keep the ISR (Interrupt Service Routine) as short as possible
  • Use volatile for variables shared between the ISR and the main program
  • Never use delay() or Serial.print() inside an ISR

PWM: Controlling Speed and Intensity

PWM (Pulse Width Modulation) lets you control motor speed or LED brightness by varying the on-time ratio of the signal. Instead of simple on/off, you control the percentage.

Duty Cycle 25%:  ████____________████____________
Duty Cycle 50%:  ████████________████████________
Duty Cycle 75%:  ████████████____████████████____
Duty Cycle 100%: ████████████████████████████████
// Control DC motor speed via PWM
const int MOTOR_PWM = 9;          // PWM-capable pin
const int SPEED_POT = A0;         // potentiometer for speed control

void setup() {
  pinMode(MOTOR_PWM, OUTPUT);
}

void loop() {
  int potValue = analogRead(SPEED_POT);    // 0-1023
  int motorSpeed = map(potValue, 0, 1023, 0, 255);  // map to 0-255
  analogWrite(MOTOR_PWM, motorSpeed);      // write PWM
  delay(50);
}

Serial Communication: UART, SPI, and I2C

Microcontrollers need to communicate with sensors, displays, and other controllers. Three fundamental protocols:

UART (Serial)

Simple point-to-point communication using two wires (TX and RX). Used for communicating with PCs or GPS/GSM modules.

// Send readings to a PC via UART
Serial.begin(115200);
Serial.println("Temp: 67.5C | Pressure: 4.8 bar");

I2C

A two-wire bus (SDA and SCL) connecting up to 127 devices on the same bus. Each device has a unique address. Ideal for temperature sensors, humidity sensors, and small OLED displays.

#include <Wire.h>
// Read an I2C temperature sensor (address 0x48)
Wire.begin();
Wire.requestFrom(0x48, 2);
int msb = Wire.read();
int lsb = Wire.read();
float temp = ((msb << 8) | lsb) / 256.0;

SPI

Much faster than I2C but requires more wires (MOSI, MISO, SCK, CS). Used for TFT displays, SD cards, and high-speed ADCs.

Protocol Wires Speed Devices Use Case
UART 2 Slow 1:1 PC communication
I2C 2 Medium Up to 127 Sensors, OLED displays
SPI 4+ Fast Limited TFT displays, SD cards

Firmware: Software on Bare Metal

What makes firmware different from regular software:

  • No operating system to protect you -- if your code crashes, the controller freezes
  • Resources are limited -- every byte of memory matters
  • Real-time execution is required -- a millisecond delay can mean danger
  • No easy restart -- the device is in the field, far from your desk

Watchdog Timer: The Safety Net

#include <avr/wdt.h>

void setup() {
  wdt_enable(WDTO_2S);  // if the timer is not reset within 2 seconds
                          // the controller reboots automatically
}

void loop() {
  wdt_reset();           // I am alive -- reset the timer
  readSensors();
  processData();
  updateOutputs();
  // if the program hangs here and never reaches wdt_reset()
  // the controller reboots after 2 seconds
}

Integration Example: Industrial Monitoring Unit

A complete unit that reads 3 sensors, sends data via Serial, and triggers an alarm:

// Compact industrial monitoring unit
#include <Wire.h>

struct SensorData {
  float temperature;
  float pressure;
  float vibration;
  bool alarm;
};

const float TEMP_MAX = 85.0;
const float VIB_MAX = 10.0;
const int ALARM_PIN = 13;
const int BUZZER_PIN = 6;

SensorData data;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  pinMode(ALARM_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  Serial.println("Industrial Monitor v1.0 Ready");
}

void loop() {
  data.temperature = readTemperature();
  data.pressure = readPressure();
  data.vibration = readVibration();

  data.alarm = (data.temperature > TEMP_MAX) ||
               (data.vibration > VIB_MAX);

  if (data.alarm) {
    digitalWrite(ALARM_PIN, HIGH);
    tone(BUZZER_PIN, 2000, 500);
  } else {
    digitalWrite(ALARM_PIN, LOW);
  }

  // Send data as JSON over Serial
  Serial.print("{\"temp\":");
  Serial.print(data.temperature, 1);
  Serial.print(",\"pres\":");
  Serial.print(data.pressure, 1);
  Serial.print(",\"vib\":");
  Serial.print(data.vibration, 1);
  Serial.print(",\"alarm\":");
  Serial.print(data.alarm ? "true" : "false");
  Serial.println("}");

  delay(500);
}

Summary

Concept Purpose
GPIO Read sensors and drive outputs
Interrupts Immediate response to critical events
PWM Gradual control of speed and brightness
UART/I2C/SPI Communicate with external devices
Watchdog Protect against firmware hangs
Firmware Software running directly on hardware

Embedded programming is where an engineer is closest to the hardware. Every line of code translates directly into an electrical signal that moves a motor or reads a sensor. This is the bridge between the world of software and the world of machines.

embedded microcontroller Arduino ARM firmware GPIO البرمجة المدمجة المتحكم الدقيق اردوينو البرنامج الثابت المنافذ الرقمية المقاطعات