You may be used to debugging code by using the inspection features built into an IDE. That would be my recommended first step, but there are other debugging strategies that you can employ that date back to the days before interactive debuggers. You can use those same strategies when it comes to debugging hardware or hardware/software combinations.
Isolate and test
A piece of software is typically separated into a series of functions which can each be tested individually. If your software is not working, a good strategy is to test each function in isolation. When you have tested one function without finding the error, you know that it must be elsewhere. You can therefore concentrate on the other functions, and you do not need to revisit the first one because you know that it works.
Applying the same approach to hardware means thinking about how to check individual elements or circuits. In a simple example such as blinking an external LED, there is only one circuit (from the digital IO pin, through the LED and resistor and back to GND. More complex examples might have several circuits for different components.
Even in the simple LED case, however, there are several parts that can be checked individually. The first thing to check is whether the LED is working. You can do that by connecting the long leg directly to the power supply pin on your microprocessor and the short leg directly to GND (via the resistor to prevent it burning out). This removes the code from the picture and is equivalent to connecting the LED directly to a battery. If it does not light up in that situation, check that you have it the right way round. If it still does not light up, try a different LED. If the second one works, the first is damaged and should be thrown away. Other parts of the circuit to check are the connections between the components on the breadboard. It is very easy to put one end of connector wire into the row next to the one it should actually be in.
The steps outlined above illustrate the main general point: testing an element of your circuit is easier if it is relatively simple. If you are debugging, this might mean that you have to temporarily simplify your layout in order to carry out a specific test. If you consider testability from the outset though, it can lead to better designs. This is the application of another strategy from software design known as divide-and-conquer.
Inspect the state of the system
The term inspection refers to the examination of variable values as the code is in
operation. Interactive debuggers allow you to set breakpoints to stop the code and
give you time to go through the variables of interest. You can achieve a similar
outcome in the absence of an interactive debugger by outputting variable values at
strategic points. Most microprocessor platforms give you the ability to output
text-based messages on their serial interface (the statement may be called
particle serial monitor
Find out how to create and read debug messages for your platform.
Use working examples as testing tools
It may be a big assumption to say that the majority of application code is now based on existing examples, but it does have some justification. Just consider the huge number of pre-written libraries and packages available in different coding environments. Add to that the use of applications frameworks as starting points and the ready availability of code examples on problem-solving sites such as StackOverflow and the proportion of software written from scratch is put into perspective.
You can use the same approach to the design and implementation of microprocessor-based applications. The majority of platforms provide working examples and tutorials. You can use these as a starting point for something more complex. The first step is to replicate the example to ensure that it works on your particular device. The next step is to make a series of incremental changes towards your target system.
When it comes to debugging, the simple examples can be very helpful. For example, if
you want to check that your component layout is correct, you can sometimes test a
hardware connection by using the
blink sketch. If the component of interest responds
to the blink signal on the appropriate pin, you know that the circuit is correctly
constructed. Conversely, if you want to test that your code is producing a signal on
a pin as expected, you could quickly attach an external LED.
One of the most useful features of microprocessors is the on-board LED. This is
usually connected internally to one of the digital IO pins (e.g. D07 on a Particle
Argon, D13 on an Arduino). You can see which pin to use by looking at the code of
blink example provided in your platform. If you connect an external LED to the
same pin and it should go on and off at the same time as the on-board one. If the
on-board LED works but the external one doesn’t, then there is a hardware problem.
The same approach can be taken with external components other than LEDs. Basically,
the on-board LED gives you some visual feedback to show whether the associated pin
is active or not. To take advantage of that just means that you need to connect your
external component to the correct pin which might be a temporary measure.
Double-check that the pins you are using on the breadboard are the same as in the code
The pin definitions in the code must correspond to the ones on the Argon; otherwise
your instructions will not get through to your components. The pin definitions are
an interface between the code and the hardware. As mentioned above, try using
blink sketch first to see the on-board LED work. Next connect
an external LED to D07. Then, connect the external LED to a different pin and change
the code to match. It is very important to remember that if there is a pin mismatch,
there are two solutions: one is to change the hardware (i.e. connect your component to
the pin used in the code), and the other is to change the software (i.e. force the code
to correspond to the existing hardware layout). The important thing is that the code
and the hardware match. It is therefore unlikely that you will have to change both at
the same time to fix a problem.