So would something as simple as flashing an LED or using a serial output for each function entered or exited be classed as a unit test? Or is this something much more complicated?
No it doesn't have to be. Most unit tests are just calling functions and checking whether it computes the right values. This can be a "pure" function like CRC, data goes in, checksum goes out, and there is only 1 correct answer.
But it could also be a whole module with statemachines that is checked and exercised.
Just checking the calling of functions is only a subset of what an unit test can be. It doesn't tell the whole story, although it is sometimes used in mocking. Mocking is a method to isolate your unit test code from other modules. You then "mock" whatever other code would be called. Many test frameworks allows you to set this up conveniently, and also test whether the mocked calls are correct (e.g. correct number of calls, correct arguments, etctera).
For example: say you write a piece of code that stores and retrieves configuration parameters for your application. The settings are stored in EEPROM that is handled by some other code. If you want to test your configuration code in isolation, you would mock the EEPROM module You could then write unit tests for reading settings... e.g. you mock the EEPROM read function and in the test handcode some values that correspond to some expected configuration state. Likewise for writing you could pass some configuration state, and then mock the EEPROM write function.. at which you check whether the correct bytes are written to memory.
These trivial examples may not be that interesting to test; the nice thing starts when you need to handle exceptional cases which are hard to exercise by hand or don't occur frequently. In manual testing these tend to be less well tested. Unit tests are just an automation of those tests. For example, migration of parameters between firmware upgrades might be one of those things you want to test. Etc.
It just so happens a LED blinky would be quite hard to test because
A) It interfaces directly to hardware code.. and unit tests on your workstation won't capture that unless you would go through the effort to setup a whole emulation environment etc. Or perhaps test on real hardware and have equipment that externally verify.. but that is more often only worth it as integration test. Unit tests should aid development and quality, not create a bunch of useless bureaucracy before you can start writing code.
B) The requirements for a "correct" blinky should include timing, and that is one the hardest parts of unit testing.. Just the function calls themselves don't reveal anything about how long something will take, and whether that has any interactions with the rest of your program. Maybe you could write a test where you mock e.g. delay_ms.. but that assumes that delay_ms on your platform works OK.
It does lead to another issue in writing "solid" code: programs that depend on timing are in my experience the worst to test and verify. However, this can be an essential part of embedded, where we have to deal with real-time deadlines for something as simple as an UART. Unit tests won't reveal whether your code will miss interrupts or have race conditions on the target platform.
At best, you can test what happens in some emulated failure cases. E.g for an UART Rx interrupt.. getting the byte from hardware has a hard deadline. However, if that byte is pushed into a FIFO for the application to read, then that can still fail (e.g. your main loop is stalled for CPU time). You could write an unit test that checks what happens in cae of a FIFO overrun (e.g. you emulate the arrival of many UART Rx events). You could verify that your application won't hang, whether partial packets are discarded, etc. This can help in some robustness, but it won't magically make all problems go away.