The short answer is that achieving true, complex functional verification without any physical hardware is extremely difficult on modern Cortex-M chips, but it isn't impossible if you change how you view the problem. You aren't really mocking the hardware registers in the traditional sense; you're running a detailed software simulation of the environment that the firmware expects. For your state machine logic, what you need is a Test Harness. You write a seprate piece of code, ussually in Python or C++, that simulates the inputs and the outputs of the peripheral you are testing. Instead of waiting for a real sensor or driver to generate an interrupt, your test harness feeds the exact input values and timing sequences your state machine is designed to handle. This approach lets you control every single edge case—fast transitions, dropped packets, unexpected sequencing—in a loop. You can input thousands of simulated events and verify that the state machine transitions exactly as written, purely in the PC environment. You bypass the need for the physical chip, the timing jitter, and the complexities of peripheral drivers during this initial phase. It becomes a purely algorithmic test of your state machine logic. If your system is highly dependent on timing, like in a real-time radio stack, you'll hit a wall quickly with pure simulation. Software simulations often struggle with accurate clock cycles and hardware latency. For those scenarios, most engineers use a hardware-in-the-loop (HIL) setup, where you simulate the outside world (like the sensor inputs) but still run the firmware on the actual STM32 chip. You plug the chip into a virtual environment that feeds it the simulated data.
But for the initial verification of complex state transitions, stick to the unit testing approach using a custom test harness. Treat the peripheral interaction logic as a function, isolate it, and then write tests around it. This confirms the flow before you waste time or energy worrying about pin assignments or power rails.