TLDR: STM32’s flash protection requires a specific sequence of key registers and timing considerations to unlock. This guide explains the practical steps needed for FPEC unlock, option byte access, and handling the power cycle requirements.
The STM32’s flash protection operates through a sequence of key registers and timing requirements. Here’s what you need to understand to work with it effectively.
Key Sequence Overview
Basic Unlock Procedure
Here’s a working OpenOCD sequence for basic flash unlock:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "init" -c "reset halt" \
-c "mww 0x40022004 0x45670123" \ # FPEC key 1
-c "mww 0x40022004 0xCDEF89AB" # FPEC key 2
For option byte access, you need additional keys:
-c "mww 0x40022008 0x45670123" \ # Option key 1
-c "mww 0x40022008 0xCDEF89AB" # Option key 2
Critical Requirements
Three key points are essential for successful unlock:
- Keys must be written in exact sequence – wrong order = immediate lock
- Operations must complete within timing window
- Changes require power cycle to take effect
A common mistake is not including proper timing delays between operations. Always add a sleep after register changes:
-c "mww 0x40022010 0x220" \ # Enable option byte loading
-c "mww 0x40022010 0x260" \ # Set OPTER and OPTWRE
-c "sleep 100" \ # Mandatory delay
-c "mwh 0x1ffff800 0x5AA5"
Layers of Protection
The STM32’s protection system operates through carefully orchestrated layers of defense, creating what security architects call “defense in depth.” Each layer serves a specific purpose while complementing the others, forming a complete security chain.
You might wonder why ST implemented such a complex system just to protect flash memory. The answer lies in real-world threats to embedded systems. A simple lock bit could be bypassed by voltage glitching or timing attacks. Instead, ST created multiple defensive layers that work together, each protecting against different attack vectors.
The Time Domain as Security Barrier
One of the most fascinating aspects of the protection system is how it leverages time itself as a security mechanism. Let’s look at a complete unlock sequence:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "init" -c "reset halt" \
-c "mww 0x40022004 0x45670123" \ # First key write
-c "mww 0x40022004 0xCDEF89AB" \ # Must follow immediately
-c "mww 0x40022008 0x45670123" \ # Option unlock start
-c "mww 0x40022008 0xCDEF89AB" \ # Complete option sequence
-c "sleep 100" \ # Mandatory timing gap
-c "mww 0x40022010 0x260"
Notice the critical timing elements here. The first two keys must be written in quick succession – too slow and the sequence fails. But after the option key sequence, we need a deliberate delay. This isn’t just good practice – it’s a security feature. The mandatory delays prevent voltage glitch attacks that might otherwise bypass security by disrupting power at precise moments.
Understanding Read Protection
The read protection system reveals another clever design choice – the irreversible link between protection removal and memory erasure. When you disable read protection, the device automatically triggers a mass erase. This isn’t a bug, it’s a feature. If an attacker manages to bypass the read protection somehow, they still can’t extract the original code.
When Things Go Wrong
In my experience, most protection-related issues stem from misunderstanding the state machine. Here are the critical points that have saved me hours of debugging:
- Always verify power stability during protection changes. A voltage drop at the wrong moment can leave the device in limbo.
- If you’re working through a debugger and change protection settings, you need a full power-on reset (POR), not just a system reset.
- Watch the Flash Status Register (FLASH_SR) carefully. It will tell you exactly what went wrong through the WRPRTERR and PGERR flags.
The protection system’s sophistication means there’s more to understand, but mastering these fundamentals will help you avoid the most common pitfalls when working with STM32 flash protection.
Write Protection: The Last Line of Defense
While read protection guards against external threats, write protection serves a different but equally crucial purpose – preventing accidental code corruption. What makes STM32’s implementation particularly interesting is how it scales across different device families.
For high-density devices, ST implemented a fascinating granular approach. The first 62 pages can be protected in pairs, while the remaining memory gets a single, sweeping protection bit. In practice, this means you can:
- Protect bootloader code with fine granularity
- Lock down application code in larger blocks
- Keep sectors open for runtime updates
Here’s how to implement selective write protection for a bootloader:
# First, unlock FPEC and option bytes
openocd> mww 0x40022004 0x45670123
openocd> mww 0x40022004 0xCDEF89AB
openocd> mww 0x40022008 0x45670123
openocd> mww 0x40022008 0xCDEF89AB
# Configure write protection for first 8 pages (4 blocks)
openocd> mww 0x1FFFF808 0xFFFFFF00
# Remember: Changes need power cycle
The Time Domain in Practice
One aspect of STM32’s protection that often catches developers off guard is its use of timing as a security mechanism. When I first encountered this, I thought the sleep delays in programming sequences were just conservative safety margins. The reality is far more interesting.
Consider this sequence that failed in the field:
# This sequence fails
mww 0x40022010 0x220 # Enable option byte loading
mww 0x40022010 0x260 # Set OPTER and OPTWRE
mwh 0x1ffff800 0x5AA5 # Attempt protection change
# This sequence works
mww 0x40022010 0x220
sleep 100 # Critical timing gap
mww 0x40022010 0x260
sleep 100 # Another required delay
mwh 0x1ffff800 0x5AA5
The timing requirements aren’t just about reliable operation – they’re an integral part of the security architecture. By requiring specific delays between operations, ST created a temporal maze that helps prevent automated attacks while still allowing legitimate programming operations.
Error Detection Through Complementary Patterns
Perhaps the most elegant aspect of STM32’s protection is its use of complementary patterns for error detection. Every critical value has its complement stored alongside it. When working with option bytes, you’ll see this pattern:
Address Content Complement
0x1FFFF800 0x5A 0xA5 # RDP value
0x1FFFF801 0xXX 0xYY # User options
This isn’t just simple redundancy – it’s a sophisticated error detection mechanism. If a bit flips due to power issues or other interference, the complement check fails, and the device defaults to its protected state. It’s a perfect example of fail-secure design.
Real-World Implementation Tips
After years of working with these devices, here are the crucial practices I’ve learned:
- Recovery Procedures: Always have a documented recovery procedure before experimenting with protection settings. Once locked, a device needs a specific sequence to unlock.
- Testing Protection: Verify protection settings work as expected before deploying. I’ve seen cases where developers assumed protection was active, but timing issues prevented proper configuration.
- Debugging Strategy: When protection-related operations fail, check these in order:
- Power stability during operations
- Correct key sequences
- Timing between operations
- Protection flag states in FLASH_SR
Final Thoughts
The STM32’s protection system demonstrates that effective security doesn’t have to be complex – it needs to be thoughtfully designed. By combining simple mechanisms like complementary patterns and timing requirements, ST created a robust yet maintainable security architecture.
When implementing these protections in your own projects, remember: the goal isn’t to create unbreakable security. It’s to ensure that any breach requires deliberate effort, preventing accidental modifications while allowing intentional updates when properly authenticated.