I can think of a couple of things that you will need to consider. The first is to make your hardware access layer as thin as you can, even if that means creating a basic wrapper-type of layer for it. This offers you a couple of advantages. The first is that it allows you to isolate the hardware-specific behaviours of your code from the hardware access itself, which means you can test everything down to the very bottom of your hardware comms without needing to access the hardware itself.
For example, if you need to design an I2C-based signalling protocol, you can test your code is generating the correct I2C signals without needing to hook the hardware into your tests.
For calls to the actual hardware, you can test that they are behaving correctly by mocking your hardware layer, and this is where keeping a very thin hardware layer really pays off, because you can reduce your mock to needing only handle the minimum functions required to actually address the hardware, but you don't necessarily need to test the individual signals themselves, as all of your signalling should have been testable at a higher level. This means then that you use your mock to check that calls are made to specific hardware methods that cause your signals to be sent to hardware. If you need to poll your hardware, then your mock needs to be able to trigger events or methods in your code only, because again, your return signalling should be handled in a higher layer.
This basically fits with what Oleksi said in his answer, in that it is usually more work to mock hardware-level stuff, however it isn't so difficult if you keep to the leanest possible minimal-code/call layer you can make for the hardware.
When you have code that passes all of its tests, you will still need to run through a series of manual tests in order to be sure you hoked everything up correctly in your hardware layer.
The other thing that comes to mind aside from the mocking and the layering, is to use a test-first development practice. Essentially, you code your requirements as test criteria, and you base your implementation off your tests. This will help you to ensure you keep your implementation code to a minimum, while ensuring all of your test cases are driving your development efforts. By not wasting too much time on other potentially non-critical code that you might be tempted to do "just because", test first helps you to stay focused, and will make it easier to change your code as you debug, as will the use of your unit tests and mocks. Debugging software bugs through the hardware is notoriously complicated and sucks up large amounts of your time that you would find better spent on other tasks.