I see a lot of high-level operating system answers here, but you specifically said low-level.
Some scattered thoughts:
Design for test. As you work through a problem only change one thing at a time per test. You need to understand busses and interfaces, spi, i2c, usb, ethernet, etc. Number one interface, today, yesterday, and tomorrow, the uart, serial. The steps involved in programming a flash. Tricks to avoid making the product easily brickable. Bootloaders in general. Bit-banging above said interfaces on various families of parts (different chip vendors have different ideas about io pins, pull ups, direction controls, etc). Board and chip bring up, you certainly never want to boot a many tens of thousands of lines of code program on the first power up (think led on, led off). How to debug a product without using too much test equipment (logical analyzers and scopes), at the same time you have to learn to use a scope for debugging, you are far more valuable if you dont HAVE TO have a tech or engineer in the lab with you. How would you reprogram the unit in the field? What would you do to minimize human error when allowing the user to field upgrade the unit? Remember field downgrades as well. What would you do to discourage hacking (binaries, etc). Efficient use of the flash/rom (dont wear out one bank or section, spread the wear around, or see if the flash is doing it for you). How and when to use a watchdog timer. State machines, very useful with bytestreams (serial and ethernet), design packet structures that stream well and are tailored to a state machine, and that have a header and checksum or other structure that insures you do not interpret partial packets or random data as a good packet.