← Back to Garden
evergreen ·
cpp esp32 arduino programming

C++ Primer for ESP32

A beginner-friendly guide to understanding the TSI-Telemetry firmware code.

File Structure

firmware/tsi/
├── tsi.ino           # Main entry point (like main() in C)
├── Config.h          # Constants and settings
├── CarData.h         # Data structure for telemetry
├── PIDCommands.h     # OBD-II PID definitions
├── BLEManager.h/cpp  # Bluetooth handling
├── OBDParser.h/cpp   # Response parsing
├── DisplayManager.h/cpp # Serial output
├── WiFiManager.h/cpp # WiFi connection
└── MQTTManager.h/cpp # MQTT publishing

Header (.h) vs Implementation (.cpp)

Header file - declares what exists (the "menu"):

// WiFiManager.h
class WiFiManager {
  bool connect();      // "There's a function called connect"
  bool isConnected();  // "There's a function called isConnected"
};

Implementation file - defines how it works (the "kitchen"):

// WiFiManager.cpp
bool WiFiManager::connect() {
  // Actual code that connects to WiFi
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  // ...
}

Include Guards

Every header file has this pattern:

#ifndef CARDATA_H      // If CARDATA_H is not defined...
#define CARDATA_H      // Define it now

// ... actual code ...

#endif                 // End the conditional

Why? Prevents the same code from being included twice, which would cause "redefinition" errors.

Data Types

Type What it holds Example
int Whole numbers (-2B to +2B) int rpm = 2500;
float Decimals float voltage = 14.2;
bool true or false bool connected = true;
String Text (Arduino) String name = "ESP32";
char Single character char letter = 'A';
unsigned long Big positive numbers unsigned long time = millis();

Structs - Grouping Related Data

struct CarData {
  int rpm = 0;           // Member variable with default
  int speed = 0;
  float batteryVoltage = 0.0;

  void reset() {         // Member function
    rpm = 0;
    speed = 0;
    batteryVoltage = 0.0;
  }
};

// Usage:
CarData carData;         // Create instance
carData.rpm = 2500;      // Access with dot
carData.reset();         // Call function

Classes - Structs with Privacy

class WiFiManager {
public:                          // Anyone can access
  WiFiManager();                 // Constructor
  bool connect();
  bool isConnected();

private:                         // Only class can access
  unsigned long lastConnectAttempt;
};

public vs private:

  • public: Like a car's steering wheel - anyone can use
  • private: Like the engine internals - hidden implementation

Constructors

Special function that runs when object is created:

// Declaration
WiFiManager();

// Implementation with initializer list
WiFiManager::WiFiManager() : lastConnectAttempt(0) {}
//                         ^^^^^^^^^^^^^^^^^^^^^^^^^
//                         Sets lastConnectAttempt to 0

The :: Operator (Scope Resolution)

bool WiFiManager::connect() {
//        ^         ^
//        |         |
//    class name    function name
// "connect() belongs to WiFiManager class"
}

Pointers (The Tricky Part)

A pointer holds a memory address, not a value:

int x = 5;           // Variable holds value 5
int* ptr = &x;       // Pointer holds address of x
                     // & = "address of"

Serial.println(x);   // Prints: 5
Serial.println(*ptr); // Prints: 5 (* = "value at address")

Why use pointers?

// Without pointer - makes a COPY (bad for large objects)
void updateRPM(CarData data) {
  data.rpm = 2500;  // Only updates the copy!
}

// With pointer - modifies ORIGINAL
void updateRPM(CarData* data) {
  data->rpm = 2500;  // Updates the real thing!
}
//    ^
//    Arrow operator: access through pointer

Arduino Program Structure

Every Arduino sketch needs two functions:

void setup() {
  // Runs ONCE when device boots
  Serial.begin(115200);
  WiFi.begin(ssid, password);
}

void loop() {
  // Runs FOREVER, repeatedly
  readSensors();
  publishData();
  delay(1000);
}

Control Flow

If/Else

if (WiFi.status() == WL_CONNECTED) {
  Serial.println("Connected!");
} else {
  Serial.println("Not connected");
}

While Loop

while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}

For Loop

for (int i = 0; i < 10; i++) {
  Serial.println(i);
}

Functions

// Function declaration
bool connect();           // Returns true/false
void printStatus();       // Returns nothing (void)
int getRPM();             // Returns integer
String getIP();           // Returns String

// Function with parameters
void setSpeed(int speed) {
  carData.speed = speed;
}

// Function with pointer parameter
void parseData(CarData* data) {
  data->rpm = 2500;
}

Common Arduino Functions

Function Purpose
Serial.begin(115200) Start serial at baud rate
Serial.println("text") Print with newline
Serial.printf("%d", val) Formatted print
delay(1000) Wait 1000 milliseconds
millis() Get milliseconds since boot
pinMode(pin, OUTPUT) Set pin as output
digitalWrite(pin, HIGH) Set pin high

String vs char[]

// Arduino String (easy, but uses more memory)
String name = "ESP32";
name += "-car";  // Concatenation

// C-style char array (efficient, harder)
char name[] = "ESP32";
// Can't easily concatenate

For ESP32 with limited memory, char[] is better for fixed strings, String is okay for dynamic content.

Memory Considerations

ESP32 has limited RAM (320KB). Watch out for:

// Bad - creates many String copies
String json = "{";
json += "\"rpm\":" + String(rpm) + ",";
json += "\"speed\":" + String(speed);
// Each += creates a new String!

// Better - use sprintf
char json[256];
sprintf(json, "{\"rpm\":%d,\"speed\":%d}", rpm, speed);

Debugging Tips

// Print to serial monitor
Serial.println("Got here!");
Serial.printf("RPM = %d\n", rpm);

// Check memory
Serial.printf("Free heap: %d\n", ESP.getFreeHeap());

// Print pointer address
Serial.printf("Address: %p\n", &carData);

Common Errors

Error Meaning Fix
undefined reference Function declared but not implemented Add .cpp implementation
redefinition of Same thing defined twice Add include guards
no matching function Wrong parameters Check function signature
cannot convert Type mismatch Cast or change type

Resources