PIC debugging routine reads out binary numbers
The "burn and learn" method of firmware development excludes an in-circuit emulator and a serial port. With this method, it is common practice to use spare I/O pins on a µC as a debugging aid. By strategically placing instructions to set and clear these I/O pins in the code and then observing the pins with a scope, you can obtain limited real-time information about the execution of the code. An I/O pin serves as a 1-bit debugging port.
You can overcome this limitation somewhat by writing a function or subroutine that shifts data out serially on the port pin. Then you can use the scope to capture and observe several bits of information. However, setting the port pin high for a one bit and low for a zero bit results in a display that requires careful reading. Unless you know and accurately measure the timing of the bits, judging which bit corresponds to which position on the scope display presents a challenge. If the data includes several zeros or several ones in a row, no transitions exist with which to align the timing. This problem becomes particularly acute when attempting to capture more than 8 bits.
A software routine for midrange PIC µCs (Listing 1) overcomes this difficulty by producing a scope display that you can read at a glance. The routine encodes zero as a short pulse and one as a long pulse. Using an open-drain I/O pin with a large-value pullup resistor results in fast falling and slow rising edges, which is due to the RC time constant of the pullup resistor and the capacitance of the pin and scope probe (Figure 1). Consequently, zeros show up as short spikes in the display, and ones appear as medium spikes. The separation between consecutive bytes appears as a tall spike or pulse (Figure 2). Each byte starts with a clean falling edge, which serves as a convenient trigger signal for the scope. A midrange PIC running at 4 MHz using a 100-kW pullup resistor produces the plot in Figure 2. The resistor value is not critical. To use another clock rate, you can scale the resistor approximately as the inverse of the clock rate. For example, at 8 MHz, a pullup resistor of 47 kW produces equivalent results.
The scope plot depicts a 16-bit value of 00000010 for the first byte and 00011010 for the second byte. Each invocation of the subroutine in Listing 1 displays the most-significant bit of 1 byte first. By taking care to invoke the subroutine on the most-significant byte of a multibyte value first, the scope display naturally reads from left to right. Hence, the depicted value is 021A hex. By slowing the timebase of the scope, you can display a 32-bit value. The resolution of the scope is the only limitation on the amount of data the scope can display.
Because the subroutine preserves all registers and flags, except for the general-purpose register for the subroutine itself, you can safely insert a call to the subroutine at any point in your code to obtain visibility into the value of the W register. The limitation on the W register is not a severe restriction because in the PIC architecture, most operations pass through the W register. One additional instruction suffices to load any general-purpose register into W before calling the subroutine.
The code snippet in Listing 2 shows the addition of a 16-bit value called Result to a 24-bit Base value. Inserting call Debug instructions at the points that the arrows indicate makes the 16-bit Result value visible (Figure 2).