C Refresher

Variables & Types

A variable is a named box in memory that holds a value. You must tell C what type of value the box holds before you can use it.


The Basics of Variables

Think of variables like labeled containers:

text
┌─────────────┐  ┌─────────────────┐  ┌───────────┐
│  age: 25    │  │ temperature:36.6│  │ grade: 'A'│
│  (int)      │  │   (float)       │  │  (char)   │
└─────────────┘  └─────────────────┘  └───────────┘
  • Type (int, float, char) = what kind of thing goes in the box
  • Name (age, temperature, grade) = the label on the box
  • Value (25, 36.6, 'A') = what's inside the box right now
c
1int age = 25; // "age" holds a whole number (integer)
2float temperature = 36.6; // "temperature" holds a decimal number
3char grade = 'A'; // "grade" holds a single character (use single quotes)

Rules for Variable Names

c
1// ✅ Good names — descriptive, clear
2int sensor_count = 0;
3float battery_voltage = 3.7;
4uint8_t led_brightness = 128;
5
6// ❌ Bad names — unclear, too short
7int x = 0; // what is x?
8float f = 3.7; // what does f mean?
9uint8_t n = 128; // n could be anything
10
11// ❌ Invalid names — C has strict rules
12int 2nd_reading = 0; // can't start with a number
13int my-var = 0; // can't use hyphens (use underscores instead)
14int float = 0; // can't use C keywords (float is a protected type name)

How to Read printf

printf is how you print text and values to the screen. The f stands for "formatted" — you can mix text with variable values using format specifiers.

c
1int age = 25;
2float temp = 36.6f;
3char grade = 'A';
4char name[] = "ESP32";
5
6// Format specifiers start with % and are replaced by the variable value
7printf("I am %d years old\n", age); // %d → integer → "I am 25 years old"
8printf("Temperature: %.1f°C\n", temp); // %.1f → float with 1 decimal → "Temperature: 36.6°C"
9printf("Grade: %c\n", grade); // %c → single character → "Grade: A"
10printf("Device: %s\n", name); // %s → string → "Device: ESP32"

Common Format Specifiers

SpecifierTypeExample Output
%dint (signed)42
%uunsigned int255
%ffloat3.140000
%.2ffloat (2 decimals)3.14
%cchar (single character)A
%schar[] (string)Hello
%xint (hexadecimal)ff
%#xint (hex with 0x prefix)0xff

Always match the specifier to the type!

Using %d for a float or %f for an int will print garbage. The compiler won't always warn you — this is a common source of confusing bugs.


Normal C vs Embedded C — Type Comparison

C is statically typed. In embedded C, you use fixed-width types from <stdint.h> to guarantee exact bit widths, because the size of standard types like int varies across platforms.

Normal C TypeSize (typical)Embedded C TypeSize (guaranteed)Signed?RangeWhen to Use
char1 byte (8-bit)int8_t8-bitYes-128 to 127Small signed values, ASCII
unsigned char1 byteuint8_t8-bitNo0 to 255Byte data, registers, flags
short2 bytes (16-bit)int16_t16-bitYes-32768 to 32767Small signed counters
unsigned short2 bytesuint16_t16-bitNo0 to 65535ADC readings, PWM duty
int2 or 4 bytes ⚠️int32_t32-bitYes-2.1B to 2.1BGeneral-purpose signed
unsigned int2 or 4 bytes ⚠️uint32_t32-bitNo0 to 4.3BTimestamps, addresses
long4 or 8 bytes ⚠️Avoid in embedded
float4 bytesfloat32-bit±3.4 × 10³⁸Sensor values
double8 bytesdouble64-bit±1.7 × 10³⁰⁸Avoid on ESP32
size_t32-bit on ESP32No0 to 4.3BSizes, array indices
bool1 bytetrue/falseFlags (from <stdbool.h>)

int size varies across platforms

On a desktop PC, int is typically 32 bits. On an 8-bit AVR (Arduino Uno), int is 16 bits. On ESP32-S3, int is 32 bits. Never assume int is a specific size — always use int32_t / uint32_t when the width matters.

Avoid double on ESP32-S3

The ESP32-S3 has a single-precision FPU that accelerates float operations in hardware. double operations are emulated in software and are 10–20× slower. Always use float (append f to literals: 25.5f not 25.5).

Why Fixed-Width Types Matter

c
1// ❌ Dangerous — int size varies
2int sensor_value = 0;
3if (sensor_value > 32767) { // works on 32-bit, overflows on 16-bit int!
4 handle_overflow();
5}
6
7// ✅ Safe — exact width guaranteed
8int32_t sensor_value = 0;
9if (sensor_value > 32767) { // always works correctly
10 handle_overflow();
11}
12
13// ❌ Dangerous — unsigned int may be 16-bit on some platforms
14unsigned int address = 0x40000000; // overflow if int is 16-bit!
15
16// ✅ Safe — uint32_t is always 32-bit
17uint32_t address = 0x40000000; // always correct

ESP-IDF Specific Types

ESP-IDF defines additional convenience types:

c
1#include "esp_err.h"
2esp_err_t result = ESP_OK; // int32_t — error codes (ESP_OK, ESP_FAIL, etc.)
3
4#include "driver/gpio.h"
5gpio_num_t led = GPIO_NUM_2; // enum — GPIO pin number
6
7#include "freertos/FreeRTOS.h"
8TickType_t delay = pdMS_TO_TICKS(100); // uint32_t — FreeRTOS tick count
9TaskHandle_t task_handle = NULL; // void * — opaque task handle
10BaseType_t priority = 1; // long — task priority

Printf Format Specifiers for Fixed-Width Types

A common pitfall — you can't reliably use %d for fixed-width types across different architectures. You must use macros from <inttypes.h>.

TypeFormat SpecifierExample
int8_t%d (promoted to int)printf("%d", val)
uint8_t%u (promoted to unsigned int)printf("%u", val)
int32_tPRId32printf("%" PRId32, val)
uint32_tPRIu32printf("%" PRIu32, val)
c
1#include <inttypes.h> // provides PRId32, PRIu32, etc.
2
3uint32_t timestamp = 1713427200;
4
5// Notice how the macro is placed OUTSIDE the string quotes
6printf("Timestamp: %" PRIu32 "\n", timestamp); // portable across all platforms
Previous
Compilation (GCC)