PROGMEM: Is It Only For AVR MCUs?
So, the big question is: do microcontrollers other than the ubiquitous AVR family need a PROGMEM API to get at that sweet, sweet programmable memory?  We all know PROGMEM is like, totally an AVR thing. And yeah, a bunch of modern MCUs are smart cookies and stash constant data in programmable memory automatically when you slap a const on it.  Plus, loads of modern MCUs have memory management units (MMUs) that can handle all sorts of memory shenanigans.  But does that mean PROGMEM is only for AVR? Let's dive in, guys, because memory stuff can get hairy fast.
What's the Deal with PROGMEM?
First, let's break down what PROGMEM actually does.  On AVR microcontrollers, PROGMEM is a macro (or actually a set of macros) that tells the compiler to store specific data – usually constant data like strings, lookup tables, or pre-calculated values – in the flash memory (program memory) of the microcontroller, rather than in the SRAM (static RAM).  Why?  Because SRAM is usually way more limited than flash memory on AVRs.  If you tried to store all your constant data in SRAM, you'd run out of space faster than you can say "out of memory error!" The AVR architecture, in its original form, couldn't directly access flash memory like it accesses SRAM. That is, you couldn't just use a regular pointer to read data from flash. You needed special instructions (like LPM – Load Program Memory) to fetch data from flash and bring it into the CPU. The PROGMEM macro, along with helper functions like pgm_read_byte(), pgm_read_word(), and so on, provides a way to use these special instructions and access your data stored in flash.
Think of it like this: SRAM is your desk – it's fast to access, but you only have so much space. Flash memory is like a filing cabinet – it holds tons of stuff, but you need to use a special tool (the LPM instruction, accessed via pgm_read_* functions) to get things out of it. So, PROGMEM isn't just about storing data in flash; it's about providing a mechanism to access that data because of the AVR architecture's limitations. That's the key takeaway, folks! Understanding this distinction is crucial to understanding why other MCUs might not need it. This also means, crucially, that PROGMEM is intrinsically tied to the AVR toolchain and the way AVR chips handle memory. It's not a generic C/C++ feature; it's an AVR-specific workaround for an AVR-specific hardware constraint. Therefore, expecting to find an equivalent named PROGMEM or working exactly the same way on another platform is generally not a fruitful endeavor. Instead, you need to understand the memory architecture of the other MCU and how its compiler and toolchain handle constant data.
Modern MCUs and const
Now, let's talk about the modern MCU landscape.  You mentioned that many modern MCUs automatically store const data in programmable memory. This is generally true and a welcome change from the AVR days (at least in some ways!). Modern architectures, like those based on ARM Cortex-M cores, often have a unified address space. This means that the CPU can access both SRAM and flash memory using the same instructions and the same addressing scheme.  When you declare a variable as const, the compiler, linker, and loader work together to ensure that the data is placed in flash memory at compile time and that the program can access it directly at runtime without any special instructions or helper functions.  It just works. This magic is usually handled by the linker script, which is a configuration file that tells the linker where to place different sections of your code and data in memory. The linker script will typically have sections defined for read-only data (like constant data) and will map those sections to the appropriate region of flash memory. The compiler then knows, based on the memory map defined by the linker script, where const variables will reside and can generate the correct machine code to access them. This simplifies things immensely from a programmer's perspective. You can treat const data just like any other data, without having to worry about special macros or access functions. But, hold on! This isn't always the case. There can be situations where you still need to be mindful of where your data is stored and how you access it, even on modern MCUs. This often comes down to more advanced use cases, such as dealing with external memory, memory-mapped peripherals, or custom memory layouts. Also, while the core might support unified memory access, the toolchain might still benefit from hints to optimize flash usage. It all depends on the specific architecture, compiler, and the level of control you need over memory management.
MMUs to the Rescue?
And then you brought up Memory Management Units (MMUs).  MMUs add another layer of complexity (and power!) to the memory game.  An MMU translates virtual addresses (the addresses your program uses) to physical addresses (the actual addresses in memory). This allows for memory protection, virtual memory, and other advanced features.  In the context of constant data, an MMU can be configured to map a region of flash memory into the address space of a process, making it appear as if it were regular RAM.  The program can then access the data directly without needing special instructions. However, MMUs are typically found in more powerful microprocessors (MPUs) rather than microcontrollers (MCUs). MCUs are generally more resource-constrained and don't have the overhead of an MMU. So, while an MMU can simplify access to constant data in flash, it's not a common feature in the types of devices where you'd typically be using something like PROGMEM in the first place. Furthermore, even with an MMU, there might still be performance considerations when accessing flash memory. Flash memory typically has slower access times than SRAM, so accessing constant data frequently might still impact performance. In such cases, you might want to consider caching frequently accessed data in SRAM or using DMA (Direct Memory Access) to transfer data from flash to SRAM for faster access. In summary, while MMUs offer a powerful way to manage memory, they are not a universal solution and might not be present or necessary in all MCU applications.
So, When Do You Need a PROGMEM-like Thing on Other MCUs?
Okay, so after all that, where does this leave us?  The short answer is: usually not. Most modern MCUs, especially those with a unified address space and decent toolchain support, handle const data in flash memory transparently. You don't need a PROGMEM-like API to access it.  However, there are exceptions.  Here's when you might need something similar:
- Very low-level or embedded systems: If you're working with a very basic MCU that doesn't have a unified address space or if the compiler is particularly naive, you might need to manually manage flash memory access. This is becoming increasingly rare, but it's still a possibility, especially with older or very low-cost devices.
 - External flash memory: If your constant data is stored in external flash memory (i.e., flash memory that's not directly mapped into the CPU's address space), you'll likely need to use special drivers or libraries to access it. These drivers will provide functions to read data from the external flash, similar to how 
pgm_read_byte()works on AVRs. The specific API will depend on the external flash chip and the interface used to communicate with it (e.g., SPI, QSPI, or parallel). - Optimizing for performance: Even if the MCU can directly access flash memory, accessing it might be slower than accessing SRAM. If you have performance-critical code that relies heavily on constant data, you might want to manually copy that data into SRAM at startup or use DMA to transfer it on demand. In this case, you'd be implementing your own caching mechanism, which could involve something similar to 
PROGMEMin terms of managing memory regions and access methods. However, instead of using a macro to declare the data's location, you'd likely use linker directives or custom memory allocation functions to place the data in specific memory regions. - Dealing with specific compiler/linker limitations: In some cases, the compiler or linker might have limitations that prevent it from automatically placing 
constdata in flash memory in the way you want. This could be due to bugs, misconfigurations, or simply the way the toolchain is designed. In such cases, you might need to use linker scripts or other advanced techniques to manually control memory placement. Again, this is less common with modern toolchains, but it's still a possibility, especially when working with older or less mature platforms. If the compiler isn't cooperating fully with placing the data in Flash you might need to use pragmas or other compiler-specific directives to help guide it. 
In Conclusion
So, to wrap it all up, the need for a PROGMEM-like API on MCUs other than AVR is decreasing thanks to advancements in MCU architectures and compiler technology. Most of the time, you can just use const and let the toolchain do its thing. But, it's always a good idea to understand how your MCU's memory system works and to be prepared to handle special cases when they arise. Memory management might sound boring, but it's a fundamental aspect of embedded programming, and mastering it will make you a much better developer. Keep coding, keep experimenting, and keep those memory maps in mind, folks! By understanding how memory is managed and accessed on your target platform, you can write more efficient, reliable, and maintainable code. And that's what it's all about, right?