- Insane debugging: you have to debug extremely low-level failures with very limited visibility (e.g. no operating system to catch your segfaults) and proprietary or complex tools.
- A hard language made harder: C is difficult to get right, but embedded C (no dynamic memory, lots of bitwise operations for MMIO register writes, targeting exotic architectures, etc) is even more difficult.
- Extreme constraints: The products you work can have constraints like safety certification, minimal power utilization, or ultra low latency response. Meeting those constrains often means having to deeply understand hardware.
You have a tiny computer with some built-in interfaces. These days it almost always is a SOC and the documentation is often 300-500 pages.
Then you typically have some additional chips connected. Simple ones might have a 10 page doc, complex ones 100+ pages.
And you have your bespoke designed circuitry.
All of these things communicate over a variety of circuits, which generally are susceptible to electrical noise and transmission line effects. At typical frequencies everything over a couple of inches is a transmission line.
Then there are design errors, especially during prototyping.
So when you have a "bug" you need to track down whether the problem (and it can be one or more of the following) is in your code, the interfaces, the wiring, in your understanding of the documentation and of course the documents have errors as well.
Then to further add to your woes, your code edit, compile, download to flash can take several seconds for each iteration. And the flash wears out after say 10,000 iterations.
It's sort of like writing Linux drivers for custom hardware, but without the Linux libraries, or Linux.