For most of my time as an embedded software developer, I almost exclusively wrote code that was going to be run on some microcontroller. I'd fire up the IDE, crank out some code, download (this often took a couple minutes) and run. Then I'd somehow try to figure out if what I had written was correct.
Even though I was writing C, I was so used to writing for the specific hardware that I didn't even consider that C is a general-purpose programming language suitable for use on a variety of platforms -- including the host computer sitting on my desk in front of me.
Actually the computer was so big that it was under the desk. Most of the desk was taken up by giant CRT monitors (remember those?).
Anyway... I got so completely used to living inside the world of a particular microcontroller -- with it's registers and interrupts and whatever -- that I lost sight of what I was actually doing... writing an application in a general-purpose programming language.
Sure, there were always vague concerns about portability, e.g. shouldn't I write this so we can port it to a new processor in the future? But I never really understood how likely that actually was to happen (hint: not very likely).
Over time I went off and wrote other (non-embedded) software in variety of languages and started to learn a bit about unit testing. In the process of figuring out how to unit test my embedded software it eventually dawned on me that I could build and run C code without any particular hardware.
Experimenting with Ceedling (the unit test system) reminded me that writing plain, old C code for my laptop is a great way to practice development and learn through experience. And it's way faster and easier than running my code on some dev board.
Now if you're only used to building C applications through your micro's IDE (like I was), then you might not know where to start compiling native C for your laptop. But that's were Ceedling can really help. Just a single command to create a new project like this:
ceedling new <project-name>
and you're ready to start writing C code without the hardware.
I think that my practice writing code like this without the hardware has made me a better developer. With Ceedling I'm quickly able to create a new project as an experiment to try out a new idea. I'm not necessarily creating a complete application each time, but just a module or two where I can test things out. And my experiments are driven by unit tests that allow me to explore different module interfaces.
I also find this approach allows me to think about ideas without the distractions of the hardware. When I'm designing an embedded application, it can be easy for me to get hung up on something like how a particular interrupt or peripheral works. Working without the hardware forces me to think more abstractly about what a particular hardware interface really is and how it might interact with the rest of the system.
Then I can design the important components -- i.e. my application's business logic -- in a way that is testable... even without the hardware. And portability (oh, thank goodness!) is even a side effect.
I've also spent a lot of time maintaining legacy embedded code which can be a big barrier to trying new ideas. With legacy code I often fell constrained by the existing (terrible!) designs and spent most of my time trying not to break anything.
Creating little C experiments like this let me try things that I wouldn't necessarily work on otherwise. Any some of them might suck. But that's okay... no real project is depending on it and I can just throw it away... and try again.