Recently I had the opportunity to work on a software project involving low level C code for a battery charger system. I know what you’re thinking, what does C code (or any software) have to do with a battery charger? What you probably don’t realize is today’s modern batteries that power everything from cell phones to laptops to power equipment, involve some pretty sophisticated components. The batteries themselves have controller chips which the host device communicates with to monitor battery health and manage charging.
Most of my software development experience has been building desktop and server software for PC grade systems and Apple’s iOS family of devices. Both types of systems allow programming using high level languages and frameworks and have vast amounts of memory, measured in gigabytes, available for use. In addition, these platforms have mature, full featured development environments which make correcting programming mistakes relatively painless.
This particular battery charger was using an 8 bit Programmable System on a Chip or PSoC (pronounced pee-sok) with 256 bytes of RAM. You read that right, bytes — not megabytes, or even kilobytes, just bytes. I needed to make adjustments to the code to support charging a different type of battery and also add code to display various error conditions using the charge indicator’s LED segments. I installed the PSoC vendor’s development tools which allowed me to compile the existing code and write it to the PSoC’s ROM.
Heat Up the Iron
I was provided with two circuit boards from production chargers to which I needed to solder 5 pin programming ports to allow connection of the PSoC vendor’s programming module. This module contains a mini USB port for connecting the module to the development PC. The PSoC development tools communicate with the programming module to download the compiled software to the PSoC’s ROM. So far so good.
Debugging programming errors is a different story entirely. To effectively debug any software system an engineer needs to be able to stop a running program at any arbitrary point to examine the state of variables. This allows the engineer to see exact values and determine if the values match the expectations of the original design. During this evaluation, the engineer will also want to single step through the code which allows the processor to execute a single instruction and then stop again to allow inspection of the variables. Good development tools contain a debugger that provides these functions at a minimum.
Unfortunately, the circuit boards I was provided were production boards which had the PSoC surface mounted to the board with solder. In order to use the PSoC vendor’s debugger, I would need to have the PSoC chip removed from the board and have a development “pod” soldered in it’s place. This pod allows a cable to be connected between it and another module called an In-Circuit Emulation module, or ICE module. Like the programming module, the ICE module contains a mini USB port for connecting it to the development PC.
The Plot Thickens
In theory using the PSoC vendor’s debugger would allow me to find and repair software bugs with relative ease, however, this was not to be the case. I was able to get the debugger started, but running the battery charger’s program was proving to be impossible. The software simply did not run past the first few lines of code without essentially crashing. After several attempts at ruling out a possible hardware problem, I gave up on using the debugger and resorted to trying to write variable values to a built in serial port on the charger’s circuit board.
This posed a series of issues. In order to write to the serial port I needed to introduce additional variables to handle the communication. This in turn used more RAM which was already in short supply. It also inserts additional code which needed to be executed in order to transmit the debugging information to the serial port. The extra instructions affected the timing of operations within the battery charger’s code which could mask some types of bugs which are timing dependent. Worse, it can affect timing in a negative way by causing time sensitive code to fail.
Without a working “on chip” debugger, it was virtually impossible to determine the cause of some issues affecting the charger code. The only course left was to use the serial port output to check assumptions as much as possible and then remove the serial port code and run tests to determine if a particular fix was successful. Obviously, this was a very time consuming process, but was necessary in this case.
So if you have the luxury of working on a system with a good debugger, don’t take it for granted! Writing good, reliable software without such tools can be very difficult. If you have worked on similar projects to the battery charger I described, you already know that.