The Perfect Fusion of Embedded Systems and IoT
In the wave of digital transformation, the combination of embedded systems and Internet of Things (IoT) technologies is redefining how we interact with the world around us. From smart homes to Industry 4.0, these technologies are creating a more intelligent, interconnected future.
Embedded Systems Fundamentals
Hardware Platform Selection
Choosing the right hardware platform is the first step toward a successful project:
ESP32 Series
ESP32 is one of our most recommended IoT development platforms:
#include "WiFi.h"
#include "PubSubClient.h"
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
const char* mqtt_server = "your_mqtt_broker";
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void setup_wifi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
}
Arduino Series
For entry-level projects, Arduino provides a beginner-friendly development environment:
#include <SoftwareSerial.h>
#include <ArduinoJson.h>
SoftwareSerial bluetooth(2, 3);
void setup() {
Serial.begin(9600);
bluetooth.begin(9600);
// Initialize sensors
pinMode(A0, INPUT); // Temperature sensor
pinMode(13, OUTPUT); // LED indicator
}
void loop() {
int temperature = analogRead(A0);
float celsius = (temperature * 5.0 / 1024.0 - 0.5) * 100;
// Build JSON-formatted data
StaticJsonDocument<200> doc;
doc["temperature"] = celsius;
doc["timestamp"] = millis();
String output;
serializeJson(doc, output);
bluetooth.println(output);
delay(1000);
}
Sensor Integration
Digital Sensors
The DHT22 temperature and humidity sensor is a commonly used digital sensor:
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(9600);
dht.begin();
}
void loop() {
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
Serial.print("Humidity: ");
Serial.print(humidity);
Serial.print("% Temperature: ");
Serial.print(temperature);
Serial.println("°C");
delay(2000);
}
Analog Sensors
Example usage of a photoresistor:
const int photocellPin = A0;
const int ledPin = 9;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}
void loop() {
int photocellReading = analogRead(photocellPin);
// Map sensor value to LED brightness
int ledBrightness = map(photocellReading, 0, 1023, 255, 0);
analogWrite(ledPin, ledBrightness);
Serial.print("Analog reading = ");
Serial.print(photocellReading);
Serial.print(" - LED brightness = ");
Serial.println(ledBrightness);
delay(100);
}
IoT Architecture Design
Communication Protocol Selection
MQTT Protocol
MQTT is the most commonly used lightweight messaging protocol in IoT applications:
import paho.mqtt.client as mqtt
import json
import time
def on_connect(client, userdata, flags, rc):
print(f"Connected with result code {rc}")
client.subscribe("sensors/temperature")
def on_message(client, userdata, msg):
try:
data = json.loads(msg.payload.decode())
temperature = data['temperature']
timestamp = data['timestamp']
print(f"Received: {temperature}°C at {timestamp}")
# Data processing logic
if temperature > 30:
client.publish("alerts/high_temperature",
json.dumps({"alert": "High temperature detected"}))
except Exception as e:
print(f"Error processing message: {e}")
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("localhost", 1883, 60)
client.loop_forever()
HTTP REST API
For applications requiring a request-response model:
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
void sendSensorData(float temperature, float humidity) {
if(WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin("http://your-api-server.com/api/sensors");
http.addHeader("Content-Type", "application/json");
StaticJsonDocument<200> doc;
doc["device_id"] = "ESP32_001";
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["timestamp"] = millis();
String jsonString;
serializeJson(doc, jsonString);
int httpResponseCode = http.POST(jsonString);
if(httpResponseCode > 0) {
String response = http.getString();
Serial.println(response);
}
http.end();
}
}
Data Processing and Analysis
Edge Computing
Performing preliminary data processing on the device side:
#include <math.h>
class SensorFilter {
private:
float readings[10];
int index;
bool filled;
public:
SensorFilter() : index(0), filled(false) {}
float addReading(float value) {
readings[index] = value;
index = (index + 1) % 10;
if (index == 0) filled = true;
return getAverage();
}
float getAverage() {
float sum = 0;
int count = filled ? 10 : index;
for (int i = 0; i < count; i++) {
sum += readings[i];
}
return sum / count;
}
bool isAnomalous(float value) {
float avg = getAverage();
float variance = 0;
int count = filled ? 10 : index;
for (int i = 0; i < count; i++) {
variance += pow(readings[i] - avg, 2);
}
float stdDev = sqrt(variance / count);
return abs(value - avg) > (2 * stdDev);
}
};
SensorFilter tempFilter;
void loop() {
float rawTemp = readTemperature();
float filteredTemp = tempFilter.addReading(rawTemp);
if (tempFilter.isAnomalous(rawTemp)) {
Serial.println("Anomalous reading detected!");
// Trigger alert or additional processing
}
// Only send filtered data
sendSensorData(filteredTemp);
delay(1000);
}
Power Management
Low-Power Design
For battery-powered IoT devices, power consumption control is critical:
#include "esp_sleep.h"
#define uS_TO_S_FACTOR 1000000 // Microseconds to seconds conversion factor
#define TIME_TO_SLEEP 30 // Sleep duration (seconds)
void setup() {
Serial.begin(115200);
// Read sensor data
float temperature = readTemperature();
float humidity = readHumidity();
// Send data
sendDataToServer(temperature, humidity);
// Configure wake-up source
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("Going to sleep now");
Serial.flush();
// Enter deep sleep
esp_deep_sleep_start();
}
void loop() {
// This will never execute, as the device enters sleep after setup
}
Battery Monitoring
#define BATTERY_PIN A0
#define VOLTAGE_DIVIDER_RATIO 2.0
float getBatteryVoltage() {
int rawValue = analogRead(BATTERY_PIN);
float voltage = (rawValue / 1024.0) * 3.3 * VOLTAGE_DIVIDER_RATIO;
return voltage;
}
int getBatteryPercentage() {
float voltage = getBatteryVoltage();
// Assuming lithium battery voltage range: 3.0V - 4.2V
if (voltage >= 4.2) return 100;
if (voltage <= 3.0) return 0;
return (int)((voltage - 3.0) / 1.2 * 100);
}
void checkBatteryStatus() {
int batteryLevel = getBatteryPercentage();
if (batteryLevel < 20) {
// Send low battery warning
sendLowBatteryAlert();
// Enable power saving mode
enablePowerSaveMode();
}
}
Security Considerations
Data Encryption
#include "mbedtls/aes.h"
void encryptData(uint8_t* input, uint8_t* output, uint8_t* key) {
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, key, 256);
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, input, output);
mbedtls_aes_free(&aes);
}
void secureDataTransmission(String data) {
uint8_t key[32] = {/* your encryption key */};
uint8_t input[16];
uint8_t encrypted[16];
// Prepare data for encryption
data.getBytes(input, 16);
// Encrypt data
encryptData(input, encrypted, key);
// Send encrypted data
sendEncryptedData(encrypted, 16);
}
Device Authentication
#include <WiFiClientSecure.h>
WiFiClientSecure client;
void setupSecureConnection() {
// Load CA certificate
client.setCACert(ca_cert);
// Load client certificate and private key
client.setCertificate(client_cert);
client.setPrivateKey(client_key);
}
bool authenticateDevice() {
if (client.connect("secure.server.com", 443)) {
String authRequest = "GET /api/authenticate HTTP/1.1\r\n";
authRequest += "Host: secure.server.com\r\n";
authRequest += "Device-ID: " + String(ESP.getEfuseMac()) + "\r\n";
authRequest += "Connection: close\r\n\r\n";
client.print(authRequest);
// Read response
String response = client.readString();
return response.indexOf("200 OK") != -1;
}
return false;
}
Real-World Project Example
Smart Agriculture Monitoring System
#include <WiFi.h>
#include <PubSubClient.h>
#include "DHT.h"
#define SOIL_MOISTURE_PIN A0
#define DHT_PIN 2
#define PUMP_PIN 8
DHT dht(DHT_PIN, DHT22);
WiFiClient espClient;
PubSubClient mqtt(espClient);
struct SensorData {
float temperature;
float humidity;
int soilMoisture;
bool pumpStatus;
float batteryLevel;
};
void setup() {
Serial.begin(115200);
dht.begin();
pinMode(PUMP_PIN, OUTPUT);
connectWiFi();
mqtt.setServer("mqtt.broker.com", 1883);
mqtt.setCallback(mqttCallback);
}
void loop() {
SensorData data = readSensors();
// Automatic irrigation logic
if (data.soilMoisture < 30 && !data.pumpStatus) {
activatePump();
data.pumpStatus = true;
} else if (data.soilMoisture > 70 && data.pumpStatus) {
deactivatePump();
data.pumpStatus = false;
}
// Send data to cloud
publishSensorData(data);
// Check battery status
if (data.batteryLevel < 20) {
enterPowerSaveMode();
}
delay(30000); // 30-second interval
}
SensorData readSensors() {
SensorData data;
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
data.soilMoisture = map(analogRead(SOIL_MOISTURE_PIN), 0, 1023, 0, 100);
data.pumpStatus = digitalRead(PUMP_PIN);
data.batteryLevel = getBatteryLevel();
return data;
}
Field Experience: Managing Multiple Build Variants from One Firmware
Production firmware rarely ships as a single build. On one Zephyr (NCS) project we ended up maintaining 7 build variants, all sharing the exact same source tree.
Why it grew to 7: the production build, an SD-card fallback build, a BLE-secure build, a factory-test build (RTT shell commands), a NAND pin-identification build, and a NAND stress-test build — every one of them driven by a real requirement. The one we felt most was the SD fallback: when our primary SPI NAND flash went out of stock and became unobtainable, we had to switch to a microSD and ship within days.
Approach 1: Layer configuration with overlays instead of forking the code. Zephyr lets you stack multiple .conf overlays via EXTRA_CONF_FILE and devicetree overlays via EXTRA_DTC_OVERLAY_FILE. The SD + secure + factory-test build was just three overlays stacked on top of prj.conf, with each variant getting its own build directory.
Approach 2: Use stubs to keep symbol names alive so the shared code still links. The SD build's devicetree has no NAND node, so it shouldn't compile spi_nand.c — but other modules still call into it. We compiled spi_nand_stub.c instead, providing same-named functions that simply return -ENODEV, so the upper layers link without changing a single line.
Approach 3: Wrap the points of difference in macros. NAND mounts at /NAND: (LittleFS) and SD mounts at /SD: (FATFS); we hid that mount-point difference behind a FILESYS_MNT_POINT macro.
Approach 4: Build every variant in CI. We had CI compile both the production build and the fallback build at the same time, so if any variant failed to compile it blocked the merge.
Lesson learned: build variants aren't something you pile up with
#ifdef. You layer configuration with overlays, hold the interface steady with stubs, collapse the differences behind macros, and let CI guard every last one of them.
Troubleshooting and Maintenance
Remote Diagnostics
void performDiagnostics() {
DiagnosticReport report;
// Check WiFi connection
report.wifiStatus = WiFi.status() == WL_CONNECTED;
report.signalStrength = WiFi.RSSI();
// Check sensor status
report.sensorStatus = testSensors();
// Check memory usage
report.freeHeap = ESP.getFreeHeap();
report.heapFragmentation = ESP.getHeapFragmentation();
// Check power status
report.batteryVoltage = getBatteryVoltage();
// Send diagnostic report
sendDiagnosticReport(report);
}
Over-the-Air Updates
#include <HTTPUpdate.h>
void checkForUpdates() {
String currentVersion = "1.0.0";
String updateURL = "http://update.server.com/firmware.bin";
HTTPClient http;
http.begin(updateURL + "?version=" + currentVersion);
int httpCode = http.GET();
if (httpCode == 200) {
Serial.println("Update available, starting download...");
t_httpUpdate_return ret = httpUpdate.update(updateURL);
switch (ret) {
case HTTP_UPDATE_FAILED:
Serial.printf("Update failed. Error: %s\n",
httpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("No updates available");
break;
case HTTP_UPDATE_OK:
Serial.println("Update successful, rebooting...");
ESP.restart();
break;
}
}
http.end();
}
Future Trends
Edge AI Integration
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
// AI model inference on edge devices
class EdgeAI {
private:
tflite::MicroInterpreter* interpreter;
public:
bool initModel(const unsigned char* model_data) {
static tflite::MicroErrorReporter micro_error_reporter;
static tflite::AllOpsResolver resolver;
// Load the model
model = tflite::GetModel(model_data);
// Create the interpreter
static uint8_t tensor_arena[10000];
interpreter = new tflite::MicroInterpreter(
model, resolver, tensor_arena, 10000, µ_error_reporter);
return interpreter->AllocateTensors() == kTfLiteOk;
}
float predict(float* input_data) {
// Set input data
TfLiteTensor* input = interpreter->input(0);
for (int i = 0; i < input->bytes / sizeof(float); i++) {
input->data.f[i] = input_data[i];
}
// Execute inference
if (interpreter->Invoke() != kTfLiteOk) {
return -1;
}
// Get prediction result
TfLiteTensor* output = interpreter->output(0);
return output->data.f[0];
}
};
Conclusion
The combination of embedded systems and IoT technologies opens the door to limitless possibilities. From smart homes to industrial automation, these technologies are reshaping our world. A successful IoT project requires:
- Appropriate hardware platform selection
- Reliable communication architecture
- Effective power management
- Robust security mechanisms
- Intelligent data processing
At BASHCAT, we have extensive experience in embedded systems and IoT development, providing clients with complete solutions from concept to product. If you have related project needs, feel free to contact us.