Writing embedded software w/o hardware
Not having hardware during the initial stages of firmware development happens.  Common strategies to deal with this are:

Spend time up front architecting the system carefully before you write any code.  Of course you should do this anyway, but in this case it's even more important than usual.  It's much easier to debug well thought out software than a pasta-based mess.

Properly modularize everything, minimizing the interfaces between modules.  This will help contain bugs to individual modules, and allow easier testing of individual modules.

Write code bottom-up, hardware-touching drivers go first, high level application logic last.  This allows discovering inconveniences imposed by the architecture early on.  Don't be afraid to change the architecture as hardware realities come to light, but make sure all the documentation is updated accordingly.

Simulate.  Most microcontrollers companies provide software simulators of their microcontrollers.  These can only go so far, but can still be very useful.  Simulating the inputs and measuring the outputs of the hardware may be difficult, but checking higher level logic this way shouldn't be too hard.

This is where the modular design helps again.  If you can't reasonably simulate some low level hardware interactions, you use a different version of the module that touches that hardware but that passes its own simulated actions to the upper levels.  The upper levels won't know this is happening.  You won't be checking the low level module this way, but most everything else.



In short, use good software desing practices, which of course you should be doing anyway.