https://github.com/choiben314/pic-programmer
We created an external programming tool to load programs on to the PIC32 using the Raspberry Pi A+. Currently, we are still in the process of adding support for loading programs, but the programmer successfully checks for device status, erases the device, and enters the serial execution mode needed to load programs.
We worked with the PIC32 in a mechatronics course (ME218B) using an external programming tool (SNAP) and wanted to learn the inner-workings of the programming tool and how it actually loads programs on to the PIC. We also wanted to explore JTAG and ICSP programming.
This code was developed and tested on a Raspberry Pi A+. Connect pins as follows for 4-wire JTAG:
- MCLR: gpio26 on Pi to pin 1 on PIC
- TDI: gpio5 on Pi to pin 16 on PIC
- TCK: gpio6 on Pi to pin 17 on PIC
- TDO: gpio13 on Pi pin 18 on PIC
- TMS: gpio19 on Pi to pin 22 on PIC
Figure 1. 4-Wire Hardware Setup with Arduino ArduPIC32 Setup Unconnected
Connect pins as follows for 2-wire ICSP:
- MCLR: gpio26 on Pi to pin 1 on PIC
- PGD: gpio5 on Pi to pin 4 on PIC
- PGC: gpio6 on Pi to pin 5 on PIC
Figure 2. 2-Wire Hardware Setup
Upon reviewing the PIC32 Flash Programming Specification, we realized there are 3 main ways we could write an external programmer for the PIC32: 4-wire Enhanced JTAG (EJTAG), 2-wire ICSP with two-phase multiplexing, and 2-wire ICSP with four-phase multiplexing.
Figure 3. High-Level Flash Programming Block Diagram
4-wire JTAG attempt
We first decided to use the 4-wire JTAG implementation because we had enough pins on both the Pi and PIC to use more wires, we wanted to learn about the more general JTAG industry standard, and we could avoid the time multiplexing that needed to be done for the 2-wire ICSP implementations.
However, with the 4-wire JTAG attempts, we had trouble correctly reading the device status. We implemented the pseudoinstructions set_mode
, send_command
, and xfer_data
by manually setting the JTAG gpio outputs MCLR, TDI, TCK, TDO, and TMS according to the figures below.
Figure 4. SetMode 4-Wire Timing Diagram
Figure 5. SendCommand 4-Wire Timing Diagram
Figure 6. XferData 4-Wire Timing Diagram
After implementing these initial pseudoinstructions we were able to implement reading the device status of the PIC using the previously implemented pseudoinstructions as shown in Figure 7.
Figure 7. Check Device Status Block Diagram
Upon implementing this, we saw that device status was never correctly set with the correct values for FCBUSY
and CFGRDY
bits.
To debug this, we tried a number of steps incuding verifying hardware, software debugging, and checking signals with a logic analyzer. The first issue we found was that we needed to ensure both pullup and pulldown pins were disabled for the input pin TDO. Another major issue we discovered was setting appropriate setup and hold time delays before clocking in TDI/TMS or reading TDO.
Next, with the logic analyzer we verified that statusVal
variable in software was correctly getting the values of the input TDO pin from hardware. We verified these values matched with a logic analyzer; however, the values were just never getting correctly set to indicate the device status was ready.
Figure 8 below shows the overall check device status waveform on the logic analyzer and Figures 9-12 show the specific waveforms for each pseudoinstruction step in checking device status that we verified has the same values in software. Figure 11 shows the XferData
command in which TDO was not getting the correct values for bits FCBUSY
and CFGRDY
.
Figure 8. Overall 4-wire Check Device Status Logic Analyzer Waveform
Figure 9. 4-Wire SetMode Instruction Logic Analyzer Waveform
Figure 10. 4-Wire SendCommand(MTAP_SW_MTAP) Logic Analyzer Waveform
Figure 11. 4-Wire SendCommand(MTAP_COMMAND) Logic Analyzer Waveform
Figure 12. 4-Wire XferData(MCHP_STATUS) Logic Analyzer Waveform
Next, we attempted to compare our statusVal
to that of a working implementation of a PIC32 programmer. Unfortunately, we did not have a JTAG programmer to verify with and only the 2-wire ICSP SNAP programmer. We connected the SNAP programmer and loaded a random program. We manually reviewed the programming sequence at the beginning of loading the program using the logic analyzer again. However, because the SNAP uses a 2-wire ICSP SNAP programmer we had to manually demultiplex the values of PGD. Because there are two possible multiplexing options for the 2-wire ICSP implementation, we had to determine whether it was using 2-phase or 4-phase multiplexing by comparing the very first set_mode command we saw to what we expected from the Flash Programming Specification datasheet. We, then determined that PGD was using a 4-phase demultiplexing scheme where TDI and TMS are written on the falling edge of the first two clock cycles and TDO is read on the rising edge of the fourth clock cycle in the 4-phases (pictured in Figure 13).
Figure 13. 4-Phase Multiplexing Diagram
Figures 14-18 below show the various logic analyzer waveforms observed from the 2-wire, 4-phase SNAP programmer during the Check Device Status operation.
Figure 14. 2-wire Enter Enhanced ICSP Mode
Figure 15. 2-Wire Key Sequence after Enter Enhanced ICSP mode
Figure 16. 2-Wire Check Device Status SetMode(6'b011111)
, SendCommand(MTAP_SW_MTAP)
, and SendCommand(MTAP_COMMAND)
Operations
Figure 17. 2-Wire Check Device Status SetMode(6'b011111)
Zoomed In
Figure 18. 2-Wire Check Device Status XferData(MCHP_STATUS)
Operation
We continued to follow the waveform generated by the SNAP when checking device status, which successfully matched our expectations until the XferData
step (Figure 18). During this step, we noticed XferData
was not transmitting TMS header, 32 bits, then TMS footer as we expected. Instead, it transmitted TMS header, 13 bits, then the TMS footer. We determined that this was likely a result of optimizations done by SNAP to speed up programming; however, this meant that we could no longer correctly verify our statusVal
value against the proper demultiplexed TDO value from a working implementation.
Therefore, we decided to verify against the closest thing to a working 4-wire JTAG implementation we could find. We found tekaikko's ArduPIC32 repo which implemented a 4-wire JTAG programmer for the PIC32 on the Arduino instead of the Pi. We cloned this repository and tried testing on an Arduino Mini, which compiled right out-of-the-box. Interestingly, this implementation also did not work for us and hung in the same place our 4-wire JTAG implementation was hanging - getting the correct device status. Therefore, we concluded that we may have needed an additional hardware JTAG adapter or something else to enable JTAG programming on the PIC32 as the IDE we use to program it is only setup by default to use 2-wire ICSP programming through the SNAP.
Switch to 2-wire 4-phase ICSP
Thus, we decided to switch gears and try implementing a 2-wire 4-phase programmer similar to the SNAP that we could compare against. To do so, we had to modify our pseudoinstructions and helper functions to multiplex values of TDI, TMS, and TDO according to the figures below.
Figure 19. SetMode 2-Wire Timing Diagram
Figure 20. SendCommand 2-Wire Timing Diagram
Figure 21. XferData 2-Wire Timing Diagram
This two-wire implementation ended up working and correctly getting device status, erasing the device, and setting the device in serial execution mode.
Loading Program Executive
The program executive (PE) is code downloaded onto RAM that receives and loads the program into device flash memory.
According to Section 11.0 in PIC32MX Flash Programming Specification, loading the PE onto RAM consists of two steps:
- Loading the PE Loader onto RAM. This loads the PE binary into the correct location on RAM and then jumps to it.
- Actually providing the PE Loader with the PE binary once the loader is in place.
The PE hex file can be found on the Microchip website. Detailed instructions for loading the PE into device RAM can be found in Section 11.0 of PIC32MX Flash Programming Specification.
Downloading a Data Block
Once the PE is loaded into RAM, the actual program must be downloaded onto the device. Once the PE is loaded, block-wise programming occurs trivially via the following commands.
XferFastData(PROGRAM|DATA_SIZE)
XferFastData(ADDRESS)
XferFastData(32'h0x0)
The PE then immediately writes the program to flash memory from RAM automatically.
Verify Device Memory
The program can then be verified once it has been downloaded to ensure that all data was programmed correctly. This is completed through calculation of checksums, as shown below.
XferFastData(GET_CRC)
XferFastData(START_ADDRESS)
XferFastData(LENGTH)
valCkSum = XferFastData(32'h0x0)
MCLR may then be asserted to exit programming mode and begin program execution.
- PIC32MX Flash Programing Specification
- most useful resource by far
- contains useful instructions, block diagrams, and timing diagrams describing how to put PIC32 into programming mode and load programming executive and actual program code
- Arduino ICSP Programmer Repo for PIC16
- similar project to ours but there are many differences between PIC16 family and PIC32 family
- PIC32MX Section 5: Flash Programming Documentation
- more general overview of flash programming than first link
- PIC32 Programming and Diagnostics
- PIC32 Family Datasheet
- useful for PIC32 pinouts
- ICSP Guide
- General Overview of ICSP
- Technical Guide to JTAG
- ArduPIC32 Repo - Arduino JTAG programmer for PIC32