Zbotic Logo Zbotic Logo
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

  • Shop
  • About Us
  • Contact Us
  • Reseller
  • Blogs
020 69134444
1800 209 0998
[email protected]
Help Desk
Facebook Twitter Instagram Linkedin YouTube
Zbotic Logo Zbotic Logo
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

All departments
  • 3D Print Service
  • 3D Printer
  • Batteries & Chargers
  • Development Boards
  • Drone Parts
  • EBike parts
  • Sensor Modules
  • Electronic Components
  • Electronic Modules
  • IoT and Wireless
  • Mechanical Parts and Workbench Tools
  • Motors & Drivers & Pumps & Actuators
  • DIY and Robot Kits
  • Show more
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
Return to previous page
Home Arduino & Microcontrollers

Arduino PROGMEM Tutorial: Store Strings in Flash Memory

Arduino PROGMEM Tutorial: Store Strings in Flash Memory

March 11, 2026 /Posted byJayesh Jain / 0

The Arduino PROGMEM tutorial addresses one of the most common causes of mysterious crashes and instability in Arduino projects: running out of SRAM. The Arduino Uno has only 2 KB of SRAM — and a surprisingly large chunk of it can be consumed by string constants defined in your code. PROGMEM (Program Memory) is a mechanism to store constant data in the much larger flash memory (32 KB on the Uno) instead of SRAM, freeing precious RAM for variables, buffers, and function call stacks. This guide covers everything from basic string storage to lookup tables and practical large-project strategies.

Table of Contents

  • Why SRAM Is So Scarce and Why PROGMEM Matters
  • Basic PROGMEM Usage: Storing Strings
  • The F() Macro: The Easy Way
  • Reading Data Back from PROGMEM
  • Arrays and Lookup Tables in PROGMEM
  • PROGMEM String Tables
  • Checking Available SRAM at Runtime
  • Frequently Asked Questions

Why SRAM Is So Scarce and Why PROGMEM Matters

When your Arduino sketch starts, all global variables — including string literals — are copied from flash into SRAM. This happens automatically at boot. Consider a sketch with ten error messages like "Error: sensor not responding", "Initialising SD card...", and so on. Each character takes one byte of SRAM. Ten messages averaging 30 characters each consume 300 bytes of SRAM — 15% of the Uno’s entire 2 KB budget — just for text that is never modified.

When SRAM runs out, the symptoms are subtle and maddening: sketches that compile and upload successfully but behave erratically, crash randomly, or produce garbage output. The heap (dynamic allocation) and stack (function calls) grow toward each other, and when they collide, undefined behaviour results — often silently.

PROGMEM solves this by keeping string constants in flash, reading them byte-by-byte only when needed. The trade-off: reading from flash requires special functions and adds a tiny amount of code complexity. For most projects with more than a few dozen bytes of string constants, this trade-off is absolutely worth it.

Recommended: Arduino Uno R3 Beginners Kit — the Uno’s 2 KB SRAM constraint makes PROGMEM essential for any non-trivial project. This kit includes everything to start experimenting with memory optimisation techniques.

Basic PROGMEM Usage: Storing Strings

To store a string in flash memory, use the PROGMEM keyword along with the PGM_P or char type in program memory:

#include <avr/pgmspace.h>

// Store a string in flash (program memory)
const char welcome_msg[] PROGMEM = "Welcome to Zbotic Arduino Project!";
const char error_msg[] PROGMEM = "Error: Sensor not found. Check wiring.";
const char ready_msg[] PROGMEM = "System ready. Waiting for input...";

void setup() {
  Serial.begin(9600);
}

Important rules for PROGMEM variables:

  • Must be declared as const — PROGMEM data is read-only.
  • Must be declared at global or static scope — not inside functions on the stack.
  • The PROGMEM attribute goes after the type, before the variable name, or as shown above.
  • You cannot read PROGMEM data with normal pointer dereferences — use the special pgm_read_* functions.

The F() Macro: The Easy Way

For Serial.print() statements specifically, Arduino provides the elegantly simple F() macro that wraps a string literal to be stored in flash:

// Without F() — string stored in SRAM (wastes RAM)
Serial.println("System initialising...");  // Bad

// With F() — string stored in flash (saves RAM)
Serial.println(F("System initialising..."));  // Good

The F() macro works with any function that accepts a __FlashStringHelper* argument — and all Arduino’s print functions (Serial.print, lcd.print, Serial.println) support it directly. This is the single most impactful and easiest PROGMEM optimisation available, and you should use it habitually for all string literals passed to print functions.

Replace all these:

Serial.println("Initialising...");
Serial.println("SD card OK");
Serial.println("Sensor error!");
Serial.println("Loop started");

With these:

Serial.println(F("Initialising..."));
Serial.println(F("SD card OK"));
Serial.println(F("Sensor error!"));
Serial.println(F("Loop started"));

On a typical logging sketch with 20 such messages, this single change can save 300–500 bytes of SRAM with zero functional change.

Recommended: Arduino Mega 2560 R3 Board — with 8 KB SRAM and 256 KB flash, the Mega is far more forgiving of large projects. Still good practice to use PROGMEM, but the Mega gives you room to breathe while prototyping before optimising.

Reading Data Back from PROGMEM

To use a PROGMEM string, you must copy it to a SRAM buffer first using strcpy_P(), or read it character by character using pgm_read_byte():

#include <avr/pgmspace.h>

const char error_msg[] PROGMEM = "Error: Sensor not found!";

void printProgmemString(const char* str_P) {
  char c;
  while ((c = pgm_read_byte(str_P++)) != 0) {
    Serial.write(c);
  }
  Serial.println();
}

void setup() {
  Serial.begin(9600);
  
  // Method 1: Print character by character
  printProgmemString(error_msg);
  
  // Method 2: Copy to buffer then use
  char buffer[32];
  strcpy_P(buffer, error_msg);  // Copies from flash to SRAM buffer
  Serial.println(buffer);        // Now usable as normal string
  
  // Method 3: Use Serial.print's built-in PROGMEM support
  Serial.println((__FlashStringHelper*) error_msg);
}

Key PROGMEM reading functions from <avr/pgmspace.h>:

  • pgm_read_byte(addr) — reads one byte (char or uint8_t) from flash
  • pgm_read_word(addr) — reads two bytes (int or uint16_t) from flash
  • pgm_read_dword(addr) — reads four bytes (long or uint32_t) from flash
  • pgm_read_float(addr) — reads four bytes as float from flash
  • strcpy_P(dest, src_P) — copy PROGMEM string to SRAM buffer
  • strcat_P(dest, src_P) — concatenate PROGMEM string onto SRAM string
  • strcmp_P(str, str_P) — compare SRAM string with PROGMEM string
  • strlen_P(str_P) — get length of PROGMEM string without copying

Arrays and Lookup Tables in PROGMEM

PROGMEM is not limited to strings — numeric arrays, sine wave tables, font bitmaps, and any constant data can live in flash. This is particularly useful for lookup tables used in signal processing or graphics:

#include <avr/pgmspace.h>

// Sine wave lookup table (256 values, 0–255 representing 0–1)
const uint8_t sine_table[256] PROGMEM = {
  128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167,
  170, 173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206,
  // ... (full 256 values)
  128
};

// Temperature calibration offsets for 8 sensors
const float cal_offsets[8] PROGMEM = {
  0.12, -0.08, 0.23, -0.15, 0.04, -0.21, 0.18, -0.06
};

void loop() {
  // Read from sine table
  uint8_t phase = 64;  // Quarter-wave
  uint8_t amp = pgm_read_byte(&sine_table[phase]);
  
  // Read float from calibration table
  float offset = pgm_read_float(&cal_offsets[2]);  // Sensor 3's offset
}

Note the & operator before the array element — you are passing the flash address of the element to pgm_read_*, not the value. This is a common mistake that leads to subtle bugs: pgm_read_byte(sine_table[phase]) would try to read from the address equal to the table value, not the address of the element. Always use pgm_read_byte(&array[index]).

Recommended: Arduino Nano Every with Headers — the ATmega4809 on the Nano Every has 6 KB SRAM (3x the classic Nano’s 2 KB) and 48 KB flash, providing much more headroom for data-intensive projects while retaining the Nano form factor.

PROGMEM String Tables

When you need a table of multiple strings — error messages, menu items, day/month names — a string table in PROGMEM is the efficient solution. This requires an array of PROGMEM pointers to PROGMEM strings (a pointer-to-pointer in flash):

#include <avr/pgmspace.h>

// Individual strings in PROGMEM
const char day0[] PROGMEM = "Sunday";
const char day1[] PROGMEM = "Monday";
const char day2[] PROGMEM = "Tuesday";
const char day3[] PROGMEM = "Wednesday";
const char day4[] PROGMEM = "Thursday";
const char day5[] PROGMEM = "Friday";
const char day6[] PROGMEM = "Saturday";

// Table of pointers to PROGMEM strings — also in PROGMEM
const char* const day_table[] PROGMEM = {
  day0, day1, day2, day3, day4, day5, day6
};

void printDay(uint8_t day_num) {
  // Two-step read: first read the pointer from flash, then read the string
  PGM_P p = (PGM_P) pgm_read_word(&day_table[day_num]);
  char c;
  while ((c = pgm_read_byte(p++)) != 0) {
    Serial.write(c);
  }
}

void setup() {
  Serial.begin(9600);
  printDay(3);  // Prints "Wednesday"
  Serial.println();
}

The double PROGMEM dereference (pgm_read_word() to get the address, then pgm_read_byte() to read the string) is the standard pattern. On 32-bit Arduino boards (Zero, Due, Nano 33 series), pointers are 4 bytes — use pgm_read_dword() or just pgm_read_ptr() which is architecture-aware.

Checking Available SRAM at Runtime

Before and after applying PROGMEM optimisations, measure the actual SRAM usage. Add this function to your sketch:

int freeRam() {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

void setup() {
  Serial.begin(9600);
  Serial.print(F("Free SRAM: "));
  Serial.print(freeRam());
  Serial.println(F(" bytes"));
}

This function returns the number of free bytes between the heap (top of dynamic allocation) and the stack (bottom of current call stack). A value below 200–300 bytes is dangerous — the sketch is at risk of stack-heap collision. Below 100 bytes and crashes are nearly certain under any non-trivial code path.

Arduino IDE 2.x also shows SRAM usage in the compiler output after a successful build (look for “Global variables use X bytes of dynamic memory”). This reports the minimum SRAM used by global variables, but does not account for stack depth or heap fragmentation at runtime.

Recommended: Arduino Starter Kit with 170 Pages Project Book — the official Arduino Starter Kit’s project book teaches memory-conscious programming practices alongside circuits. Ideal for beginners learning proper Arduino coding habits from the start.

Frequently Asked Questions

Does PROGMEM work on all Arduino boards?

PROGMEM is an AVR-specific feature — it applies to Arduino boards using AVR microcontrollers: Uno, Mega, Nano, Leonardo, Pro Mini, and Nano Every. ARM-based boards (Arduino Due, Zero, Nano 33 BLE Sense, Nano RP2040 Connect) store constants in flash automatically and don’t require PROGMEM. The F() macro is defined as a no-op on ARM boards for code compatibility, but it doesn’t save RAM there.

Why do I get an error when trying to print a PROGMEM string directly?

PROGMEM pointers cannot be used where a standard char* is expected because the data lives in a different address space. Use (__FlashStringHelper*) cast with Serial.print, or use strcpy_P() to copy to a SRAM buffer first. The compiler will often warn (but not error) when you pass a PROGMEM pointer where a regular pointer is expected — watch for these warnings.

Can I use the String class with PROGMEM data?

You can construct a String from PROGMEM data: String s = String((__FlashStringHelper*) my_pgm_str); — but this defeats part of the purpose since String allocates heap memory. In memory-constrained AVR sketches, avoid the String class entirely. Use character arrays (char buf[32]), strcpy_P(), strtok(), and sprintf() for string manipulation.

How much flash memory does a PROGMEM string use compared to SRAM?

Exactly the same number of bytes — one byte per character plus a null terminator. A 30-character string uses 31 bytes whether stored in SRAM or PROGMEM. The difference is WHERE those bytes live: without PROGMEM they occupy precious SRAM; with PROGMEM they consume flash (of which there is 16–256x more depending on the board), freeing SRAM for runtime data.

I moved my strings to PROGMEM but my sketch still crashes. What else could be using SRAM?

Other SRAM consumers beyond string literals include: local variables in deeply-nested function calls (stack depth), dynamic memory allocation with malloc() or new, the String class (avoid it entirely on AVR), large buffers like char buf[512] on the stack, and interrupt service routines that maintain their own stack frame. Check freeRam() at multiple points in your code to find where SRAM drops below safe levels.

Mastering PROGMEM is a rite of passage for serious Arduino developers. Once you internalise the habit of storing constants in flash — especially the effortless F() macro for all Serial.print strings — you will find your projects become dramatically more stable, and you will have SRAM headroom for the features that actually matter.

Take your Arduino skills further. Browse our complete Arduino boards and accessories at Zbotic — including the Uno, Mega, Nano Every, and complete starter kits, shipped fast across India at competitive prices.

Tags: Arduino, arduino tutorial, AVR programming, flash memory, memory optimisation, PROGMEM, SRAM
Share Post
  • Facebook
  • Linkedin
  • Whatsapp
Arduino CAN Bus Tutorial: MCP2...
blog arduino can bus tutorial mcp2515 module for automotive projects 594690
blog best arduino clone boards ch340 vs ftdi and what to buy 594698
Best Arduino Clone Boards: CH3...

Related posts

Svg%3E
Read more

Arduino Batch Programming: Flash Multiple Boards Quickly

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Based Radar System with Ultrasonic Sensor

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Automatic Plant Monitor: Sunlight, Moisture, Temperature

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Lie Detector: GSR Sensor Polygraph Project

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Metal Detector: Build a Treasure Finder

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading

Add comment Cancel reply

Your email address will not be published. Required fields are marked

Facebook Twitter Instagram Pinterest Linkedin Youtube

Get the latest deals and more.

Download on Google Play Download on the App Store

Call us: 020 69134444 / 1800 209 0998

Monday - Saturday 09:30 AM - 06:00 PM
For Technical Supports Email: [email protected]
For Sales / Enquiries Email: [email protected]

  • My Account

    • Cart

    • Wishlist

    • Checkout

    • My Orders

    • Track Order

    • My Account

  • Information

    • FAQs

    • Blogs

    • Career

    • About Us

    • Contact Us

    • Payment Options

  • Policies

    • Privacy Policy

    • Terms & Conditions

    • GST Input Tax Credit

    • Shipping Return Policy

    • E-Waste Collection Points

    • Our Sitemap

© Zbotic.in is registered trademark of Moxie Supply Pvt Ltd – All Rights Reserved
Login
Use Phone Number
Use Email Address
Not a member yet? Register Now
Reset Password
Use Phone Number
Use Email Address
Register
Already a member? Login Now