Implementing a JTAG Bootloader Master for the MAXQ2000 Microcontroller
OverviewMAXQ microcontrollers that include rewriteable onboard program memory typically include a ROM-based bootloader that enables the program memory to be loaded using the microcontroller's JTAG-compatible debug port. Although the exact functionality provided by the JTAG bootloader varies from device to device, it typically includes commands which allow program and data memory to be read, written, verified, and erased. Some devices provide alternative interfaces to the bootloader (such as a serial port or SPI™ interface), but the JTAG interface is most commonly used for two reasons. First, the JTAG interface must already be present to support in-circuit debugging functions. Second, the JTAG interface is not generally utilized by the end-user application (unlike a serial port). An optional password mechanism can be used to restrict access to the bootloader or in-circuit debugging features once the program code has been loaded. Consult the product specific information, including data sheets and User's Guides, for detailed information on the functionality supported by each MAXQ device.
This application note covers the basic steps needed to implement a JTAG bootloader master for the MAXQ2000. These steps include interfacing to the JTAG port, communicating with the Test Access Port (TAP) controller, activating bootloader mode, and sending commands to the ROM-based bootloader. Since the JTAG port generally operates the same on all MAXQ devices and since MAXQ bootloaders operate using a shared command set, most of the topics covered in this application note (as well as most of the example code) will apply when implementing a JTAG bootloader master for any MAXQ microcontroller.
Other than a serial port, no special features of the MAXQ2000 were used for this implementation. This means that the example code presented here could easily be retargeted to run on any MAXQ20 device with sufficient program memory. The code was written in MAXQ assembly and compiled using the MAX-IDE development environment. The code is available for download.
Hardware SetupThe example code for this application note was developed using a pair of MAXQ2000 evaluation (EV) kits. Two MAXQ2000 EV kits are needed to execute the software described in here. One MAXQ2000 (the JTAG master) runs the example code; the second MAXQ2000 acts as the JTAG slave which is reprogrammed by the master. Standard 8.00MHz crystals were used on both MAXQ2000 microcontrollers.
The master MAXQ2000 EV kit was modified by installing a 2 x 5 pin header in the prototyping area, which provided the master side of the JTAG cable connection. The pins on the header followed the standard MAXQ JTAG header layout and were connected as shown in Table 1.
Table 1. MAXQ2000 JTAG Connections
|JTAG Header Pin||JTAG Cable Function||MAXQ2000 JTAG
|1||TCK (Test Clock)||P0.0 (Output)||P4.0 (Input)|
|3||TDO (Test Data Out)||P0.1 (Input)||P4.3 (Output)|
|5||TMS (Test Mode Select)||P0.2 (Output)||P4.2 (Input)|
|6||nRESET||P0.4 (Open Drain)||nRESET (Input)|
|9||TDI (Test Data In)||P0.3 (Output)||P4.1 (Input)|
No modifications were required for the slave MAXQ2000 EV kit. The 2 x 5 JTAG header was installed and connected as described above on the prototyping area of the master MAXQ2000 EV kit.Then the two EV kits were joined; a standard
To simplify matters, no attempt was made to connect the power or reference voltage through the JTAG cable from the master EV kit to the slave EV kit. Instead, both EV kits were set up to run with the same VDDIO voltage (approximately 3.6V). This setup ensured that the master and slave MAXQ2000s would operate with common I/O rail levels.
The slave EV kit also had the LCD daughterboard (MAXQ2000-K01) installed on header J3. Jumpers and DIP switch settings for both boards (along with the Serial-to-JTAG board) are listed in Table 2. Note: all jumpers not listed should be disconnected. Figure 1 shows the final setup.
Table 2. Switch and Jumper Settings for Boards
|Board||Switch or Jumper||Setting||Notes|
|JH3||Connected||Supplies 5V power over JTAG cable|
|Master MAXQ2000 EV Kit||JU1||Pins 1 and 2 connected||Powers VDD from 2.5V supply|
|JU2||Pins 1 and 2 connected||Powers VDDIO from 3.6V supply|
|JU3||Pins 1 and 2 connected||Powers VLCD from 3.6V supply|
|JU11||Connected||Powers kit board from JTAG 5V supply|
|DIP SW1||Switches #4 and #8 ON; all other switches OFF||Enables serial port 0 output to J5|
|DIP SW3||All switches OFF|
|DIP SW6||All switches OFF|
|Slave MAXQ2000 EV Kit||JU1||Pins 1 and 2 connected||Powers VDD from 2.5V supply|
|JU2||Pins 1 and 2 connected||Powers VDDIO from 3.6V supply|
|JU3||Pins 1 and 2 connected||Powers VLCD from 3.6V supply|
|DIP SW1||All switches OFF|
|DIP SW3||All switches OFF|
|DIP SW6||All switches OFF|
Figure 1. JTAG demo setup.
The MAXQ JTAG InterfaceThe JTAG interface on MAXQ microcontrollers consists of four signal lines which are used to shift information in and out of the Test Access Port (TAP) controller. This TAP controller, in turn, provides access to the MAXQ's bootloader and in-circuit debugging functions. (Note that implementing a debug master, while similar to implementing a bootloader master, is beyond the scope of this application note.) The four JTAG signal lines are listed in Table 3.
Table 3. JTAG Interface Signals
|JTAG Signal||Signal Name||Direction (Master)||Direction (Slave)||Signal Description|
|TMS||Test Mode Select||Output||Input||This signal line, along with the TCK line, is used to shift the TAP controller from one operational state to the next.|
|TCK||Test Clock||Output||Input||This signal provides the clock for the JTAG interface. The JTAG clock is limited to a maximum of the slave's clock frequency divided by 8. For example, if the slave is running at a clock frequency of 8MHz, the JTAG clock at TCK cannot run any faster than 1MHz.|
|TDI||Test Data In||Output||Input||This signal carries data that is sent from the master to the slave.|
|TDO||Test Data Out||Input||Output||This signal carries data that is sent from the slave back to the master.|
The nRESET pin is not, technically speaking, part of the JTAG interface. It is included on the JTAG cable to allow the JTAG master to reset the slave microcontroller. Resetting the slave microcontroller is a required step to enter bootloader mode, and can also be useful if JTAG communications are unexpectedly interrupted.
Since the JTAG interface is full-duplex, data is shifted in from the master to the slave on the TDI line at the same time that data is shifted out from the slave to the master on the TDO line. The slave samples incoming data (on both TDI and TMS) on the rising edge of TCK and drives outgoing data to the master on TDO on the falling edge of TCK. For both incoming and outgoing data, values are transferred with least significant bit first.
This application note provides only a brief overview of the JTAG interface and TAP controller in order to explain the operation of the example code. For a much more detailed discussion of these features, refer to the Test Access Port (TAP), In-Circuit Debug Mode, and In-System Programming sections of the MAXQ Family User's Guide which can be found on the Company website.
Communicating with the TAP ControllerThe TAP controller is structured around a state machine, as shown below in Figure 2. There are sixteen discrete states included in the TAP state machine. Transition from one state to the next occurs on each rising edge of TCK based on the value of the TMS signal. For example, if the TAP controller is in the Select-DR-Scan state, and a rising edge occurs on TCK:
- If TMS = 1, the TAP controller will transition to the Select-IR-Scan state.
- If TMS = 0, the TAP controller will transition to the Capture-DR state.
- Five '1' transitions (holding TMS high and clocking TCK for five full cycles) will always bring the state machine back to Test-Logic-Reset, regardless of the starting state. This means that if the current state of the TAP controller is unknown or if communication between the JTAG master and slave has been disrupted in some manner, it is always possible to bring the TAP controller back to a known state by clocking in five '1' transitions.
- It is possible to pause JTAG communcation and remain in the Run-Test-Idle, Pause-DR, or Pause-IR states indefinitely without affecting the state of the TAP controller, even if the TCK clock continues running.
Figure 2. Test Access Port (TAP) state machine.
The TAP controller's state machine provides access to two control registers which, in turn, provide interfaces to the bootloader, the debug interface, and other functions.
- IR (Instruction Register) is always three bits in width. This register acts as an index register which, in turn, controls the function of DR (see below).
- DR (Data Register) is an access point to one of several registers within the TAP controller. The actual register accessed when bits are shifted into or out of DR depends on the current value of IR.
Figure 3. Register access in the TAP controller.
As shown in Figure 3 (the DR points to one of three internal registers depending on the value of the IR.
- If IR = 011b, the TAP controller is in Bypass mode. In this mode (which is the default mode for the TAP controller), data shifted into DR (through TDI) is simply shifted back out again through TDO. No internal registers are altered by the data shifted through the TAP controller.
- Setting IR = 100b places the TAP controller in System Programming mode. In this mode, data shifted into DR is shifted into the 3-bit System Programming register. This register (also accessible by the MAXQ micro as bits [3:1] of the ICDF register) controls whether the MAXQ will enter normal program execution mode or bootloader mode following a reset. If bootloader mode is enabled, it also controls which interface (JTAG, serial, or SPI) will be used by the bootloader.
- Setting IR = 010b places the TAP controller in Debug mode. In this mode, data shifted into DR is shifted into an internal 10-bit debugging register which can be read by the bootloader. Data output by the bootloader is also shifted back out (along with two status bits) through this register and from there through TDO. This register is used to transfer data in both the bootloader and in-circuit debugging modes.
Controlling the JTAG Port and Reset LineThe four lines of the JTAG/TAP port on the slave MAXQ2000 (TMS, TCK, TDO, and TDI) and the nRESET line are each connected to one port pin on the master MAXQ2000. The first step in controlling the JTAG interface is to properly configure these lines.
#define TCK PO0.0 ; Test Clock - Master output #define TDO PI0.1 ; Test Data Out - Slave output, master input #define TMS PO0.2 ; Test Mode Sel - Master output #define TDI PO0.3 ; Test Data In - Master output #define RST PD0.4 ; Reset - Master open-drain output (on 1)The four JTAG lines are operated in standard drive mode. From the master's perspective, TMS, TCK, and TDI will always be driven as outputs, while TDO (driven by the slave) will always be an input. The direction of the JTAG lines is fixed and not bidirectional. The nRESET line is a special case, and is configured to be an open-drain output from the master side. Normally, the slave will pull its own nRESET line high, so the master should only pull the line down (when resetting the slave) or release it completely (at all other times). There is no reason for the master to explicitly drive the slave's nRESET line high.
;============================================================================== ;= ;= initializeJTAG ;= ;= Sets up the port pins for the JTAG interface. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : None ;= initializeJTAG: move PD0.0, #1 ; TCK - master output move PO0.0, #1 ; Drive high move PD0.1, #0 ; TDO - master input move PO0.1, #1 ; Weak pullup on move PD0.2, #1 ; TMS - master output move PO0.2, #1 ; Drive low move PD0.3, #1 ; TDI - master output move PO0.3, #1 ; Drive high move PD0.4, #0 ; RST - open drain when 1, tristate when 0 move PO0.4, #0 ; Weak pullup off retAfter initializing the port pins, the clock0 and clock1 routines are used to clock static 0s and 1s on the TMS line to advance the TAP controller from one state to the next. The JTAG clock can be driven at any frequency, as long as the JTAG clock rate is kept below the maximum of 1/8 of the slave microcontroller's system clock rate. The master's system clock rate need not be considered here; it need not be matched to the slave's system clock rate in any way. The master can run faster or slower than the slave without causing JTAG communication problems.
Since the slave MAXQ2000 in this example has an 8MHz clock crystal installed, the master can drive the JTAG clock at up to 1MHz without problems. For this example, a JTAG clock rate of 100kHz will be sufficient.
#define JCLOCK 40 ; 100kHz : (((10us / (1/8MHz)) / 2) ;============================================================================== ;= ;= clock0 ;= ;= Clocks a zero TMS bit into the JTAG interface. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : LC clock0: move TMS, #0 ; Drive TMS low move LC, #JCLOCK djnz LC, $ move TCK, #1 ; Clock rising edge move LC, #JCLOCK djnz LC, $ move TCK, #0 ; Clock falling edge move LC, #JCLOCK djnz LC, $ ret ;============================================================================== ;= ;= clock1 ;= ;= Clocks a one TMS bit into the JTAG interface. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : LC clock1: move TMS, #1 ; Drive TMS high move LC, #JCLOCK djnz LC, $ move TCK, #1 ; Clock rising edge move LC, #JCLOCK djnz LC, $ move TCK, #0 ; Clock falling edge move LC, #JCLOCK djnz LC, $ retWith these two routines in place, we can add a routine to initialize the TAP controller by forcing it back to the Test-Logic-Reset state. Note that the Test-Logic-Reset state, as its name suggests, performs a complete reset of the TAP logic, including the bits (SPE and PSS[1:0]) which determine whether or not the bootloader is enabled and which interface the bootloader is using. Therefore, once entering Bootloader mode, setting the TAP controller to Test-Logic-Reset will reset the device and exit the Bootloader mode. The JTAG routines used in the demo application (with the exception of testLogicReset itself) all assume that the TAP controller is in the Run-Test-Idle state at the start of the routine. During the JTAG communications routine, the TAP controller will be shifted through various states; at the end of the routine, the TAP controller will always be back at Run-Test-Idle.
;============================================================================== ;= ;= testLogicReset ;= clock0, clock1 ;= ;= Resets the JTAG/TAP controller to its starting state. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : LC ;= testLogicReset: call clock1 call clock1 call clock1 call clock1 call clock1 call clock1 call clock1 call clock0 ; Brings us to Run-Test-Idle ret
Writing to the TAP Instruction RegisterLoading values into the TAP controller's IR is accomplished by traversing the TAP state machine down to the Shift-IR state and clocking in a new 3-bit value. The value is not actually copied from the shift register into the Instruction Register until the Update-IR state is entered.
As with all JTAG shift register operations, the current contents of the register are shifted out (by TDO) as the new value is shifted in. However, the value shifted out will always be fixed (001b); this is mandated by the JTAG standard for use in testing the functionality of the JTAG interface.
The procedure for shifting a bit in and out of either shift register (IR or DR) is identical; the TAP state machine must be in the Shift-IR or Shift-DR state, respectively. The input bit (at TDI) is sampled by the TAP controller on the rising edge of each TCK cycle, while the output bit (at TDO) is driven out on the falling edge of the TCK cycle.
;============================================================================== ;= ;= shift ;= ;= In a shift register state, clocks in a TDI bit and clocks out a TDO bit. ;= ;= Inputs : C - Bit to shift in to TDI. ;= Outputs : C - Bit shifted out from TDO. ;= Destroys : PSW, LC ;= shift: jump C, shift_bit1 shift_bit0: move TDI, #0 ; Shift in zero bit jump shift_bitEnd shift_bit1: move TDI, #1 ; Shift in one bit jump shift_bitEnd shift_bitEnd: move LC, #JCLOCK djnz LC, $ move TCK, #1 ; Rising edge, TDI is sampled move LC, #JCLOCK djnz LC, $ move TCK, #0 ; Falling edge, TDO is driven out move LC, #JCLOCK djnz LC, $ move C, TDO ; Latch TDO value retFor each bit in the transfer, except the final bit, TMS must remain low. This is important so that, as each bit is shifted in and shifted out, the state machine remains in the Shift-IR or Shift-DR state. On the final bit cycle, TMS must be driven high so that, as the last bit is clocked in and out, the TAP controller will advance to the Exit1-DR or Exit1-IR state.
As an example, in Table 4 the value 100b (System Programming mode) is being shifted into the IR register by the JTAG master. The IR register starts out programmed to the bypass value 011b; the TAP controller begins in the Run-Test-Idle state. As shown in Figure 4 below, the bits are shifted into (and out of) the TAP controller beginning with the least significant bit and ending with the most significant bit. So on the first shift cycle, bit 0 of the new value is shifted in, and bit 0 of the old value is shifted out.
Table 4. Instruction Register Shift Example
|TCK||TMS||TDI||TDO||TAP State||Shift Register||Instruction Register|
|bit 2||bit 1||bit 0||bit 2||bit 1||bit 0|
The routine used to perform this operation, shiftIR3, is shown below.
;============================================================================== ;= ;= shiftIR3 ;= clock0, clock1, shift ;= ;= Shifts a 3-bit value into the IR register. ;= ;= Inputs : A - Low three bits contain value to shift into IR ;= Outputs : None ;= Destroys : AP, APC, A, PSW, LC ;= shiftIR3: move APC, #80h ; Acc => A, turn off auto inc/dec call clock1 ; (Select DR Scan) call clock1 ; (Select IR Scan) call clock0 ; (Capture IR - loads 001b to shift register) call clock0 ; (Shift IR) move TMS, #0 ; Drive TMS low move C, TDO ; xxxxx210 c = s rrc ; sxxxxx21 c = 0 call shift ; Shift in IR bit 0 rrc ; ssxxxxx2 c = 1 call shift ; Shift in IR bit 1 rrc ; sssxxxxx c = 2 move TMS, #1 ; Drive TMS high for last bit call shift ; Shift in IR bit 2 (Exit1 IR) call clock1 ; (Update IR) call clock0 ; (Run Test Idle) ret
Writing to the TAP Data RegisterShifting values into and out of the DR of the TAP controller is performed in a similar manner to the load/unload of the IR. Normally, this will only be done when IR is set to one of two values: 100b (placing the TAP controller in System Programming mode) or 010b (placing the TAP controller in Debug mode).
When System Programming mode is active, loading and unloading the DR register operates as follows.
- The shift into and out of the DR register is three bits in width. All three bits represent valid data.
- The value transferred into the DR register is copied into the slave microcontroller's internal System Programming register when the Update-DR state is reached. These three bits are used as follows.
- Bit 0 is accessible (read/write) by the slave microcontroller as register bit ICDF.1 (SPE), and is also known as the System Program Enable bit. This bit is examined by the Utility ROM following reset to determine if bootloader mode (SPE = 1) or normal program execution mode (SPE = 0) should be entered.
- Bits 1 and 2 are accessible (read/write) by the slave microcontroller as register bits ICDF.2-3 (PSS0-PSS1), and are also known as the Programming Source Select bits. On microcontrollers that support more than one interface to the bootloader, such as the MAXQ2000, these bits are used to select which bootloader interface will be activated when SPE = 1. When SPE = 0 (normal program execution mode), the settings of these bits have no effect.
- The value transferred out of the DR register is the previous value of the System Programming register (latched into the shift register when the Capture-DR state is entered).
- The shift into and out of the DR register is 10 bits in width. For the data shifted out, all 10 bits represent valid data (eight data bits and two status bits). For the data shifted in, only the values of the eight data bits are used; the two status bits are not used.
- The high eight bits of the value shifted into the DR register are then unloaded and read by the bootloader (running from the Utility ROM) as part of a bootloader command.
- The 10-bit value shifted out of the DR register consists of an 8-bit value loaded by the bootloader (as part of a command output) and two bits of status information which are set by the TAP controller.
;============================================================================== ;= ;= shiftDR3 ;= ;= Shifts a 3-bit value into the DR register. This operation should only be ;= performed when IR =100b (System Programming Mode). ;= ;= Inputs : A - Low 3 bits contain value to shift into SPB (PSS1:PSS0:SPE) ;= Outputs : None ;= Destroys : AP, APC, A, PSW, LC shiftDR3: move APC, #80h ; Acc => A, turn off auto inc/dec call clock1 ; (Select DR Scan) call clock0 ; (Capture DR) call clock0 ; (Shift DR) move TMS, #0 ; Drive TMS low move C, TDO ; xxxxx210 c = s rrc ; sxxxxx21 c = 0 call shift ; Shift in DR bit 0 rrc ; ssxxxxx2 c = 1 call shift ; Shift in DR bit 1 rrc ; sssxxxxx c = 2 move TMS, #1 ; Drive TMS high for last bit call shift ; Shift in DR bit 2 (Exit1 DR) call clock1 ; (Update DR) call clock0 ; (Run Test Idle) ret ;============================================================================== ;= ;= shiftDR ;= clock0, clock1, shift ;= ;= Shifts a 10-bit value into and out of the DR register. This operation ;= should only be performed when IR = 010b (Debug/Loader Mode). ;= ;= Inputs : A - Byte value (input) to shift into DR ;= Outputs : A - Byte value (output) shifted out of DR ;= A - Low two bits are status bits 1:0 shifted out of DR ;= A - Byte value shifted in, cached for use by shiftDR_next ;= Destroys : AP, APC, PSW, LC shiftDR: move APC, #80h ; Acc => A, turn off auto inc/dec move A, A ; Cache input byte value for use by shiftDR_next sla2 ; Add two empty bits (for status) call clock1 ; (Select DR Scan) call clock0 ; (Capture DR) call clock0 ; (Shift DR) move TMS, #0 ; Drive TMS low move C, TDO ; xxxxxxxx76543210 c = s rrc call shift ; Shift in DR bit 0 rrc call shift ; Shift in DR bit 1 rrc call shift ; Shift in DR bit 2 rrc call shift ; Shift in DR bit 3 rrc call shift ; Shift in DR bit 4 rrc call shift ; Shift in DR bit 5 rrc call shift ; Shift in DR bit 6 rrc call shift ; Shift in DR bit 7 rrc call shift ; Shift in DR bit 8 rrc move TMS, #1 ; Drive TMS high for last bit call shift ; Shift in DR bit 9 (Exit1 DR) call clock1 ; (Update DR) call clock0 ; (Run Test Idle) push Acc ; sddd dddd 10xx xxxx sra4 ; ssss sddd dddd 10xx sra2 ; ssss sssd dddd dd10 and #0003h ; ---- ---- ---- --10 move A, Acc ; Return status bits only in A pop Acc and #0FF00h xch ; Return data bits only in A ret
Entering JTAG Bootloader ModeThe following steps are required to put the MAXQ2000 into JTAG bootloader mode.
- Initialize the TAP controller, resetting it to the Test-Logic-Reset state.
- Set the Instruction Register (IR) to 100b to enable System Programming mode.
- Set the Data Register (DR) to 001b. This sets the SPE (System Programming Enable) bit to 1 to enable the bootloader, and sets the PSS[1:0] (Programming Source Select) bits to 00b to select the JTAG interface.
- Hold nRESET low to reset the MAXQ2000.
- Release nRESET. This causes the MAXQ2000 to vector to the standard reset point in the Utility ROM (8000h). The Utility ROM code will then examine the values of the SPE and PSS bits and activate the JTAG bootloader accordingly. At this point, the bootloader is running and ready to accept JTAG commands.
- Set the Instruction Register (IR) to 010b to enable Debug mode. This is the mode used to communicate with either the JTAG bootloader or the debug engine; we will be using this mode to communicate with the bootloader in this case.
- Begin sending commands to the JTAG bootloader by shifting 10-bit values through DR.
#define IR_DEBUG 010b ; Debug Mode #define IR_BYPASS 011b ; Bypass Mode (default) #define IR_SYSTEM_PROG 100b ; System Programming Mode (activate loader) ; System Programming Register settings #define SP_EXECUTE 000b ; Bootloader disabled #define SP_LOAD_JTAG 001b ; Activate JTAG bootloader #define SP_LOAD_UART 011b ; Activate UART bootloader #define SP_LOAD_SPI 101b ; Activate SPI bootloader (invalid on 2000) #define SP_RESERVED 111b ; Reserved value ... call initializeJTAG ; Set up port pins for JTAG call testLogicReset ; Reset JTAG port (ending state: Run-Test-Idle) move Acc, #IR_SYSTEM_PROG call shiftIR3 ; Load the System Programming instruction into IR move Acc, #SP_LOAD_JTAG call shiftDR3 ; Enable the bootloader in JTAG interface mode move RST, #1 ; Drive nRESET low move Acc, #100 ; Delay for 100ms call delayMS call clock0 ; Remain in Run-Test-Idle move RST, #0 ; Release nRESET move Acc, #100 ; Delay for 100ms call delayMS move Acc, #IR_DEBUG call shiftIR3 ; Enable access to the 10-bit debug shift register ;;;; Bootloader commands may now be shifted through the DR register call waitForPrompt ; Verify that the bootloader is responding ...
Communicating with the BootloaderOnce the bootloader is running, the bootloader code in the Utility ROM reads command codes from an internal register loaded from the DR shift register. The Utility ROM also writes result data to another internal register that can be shifted out through the DR register by the JTAG master. In this way, the bootloader code and the JTAG master can exchange information.
The bootloader code and the JTAG master are, however,not synchronized. As long as the JTAG clock's rate remains below the maximum of 1/8 of the MAXQ system clock, the exact values of the two clocks do not matter for communication purposes because JTAG is a synchronous interface. However, the MAXQ system clock rate and the nature of the bootloader commands received will determine how long a delay occurs between when the bootloader receives a command and sends a reply. For example, a MAXQ2000 running at 1MHz will take longer to respond to a given bootloader command than a MAXQ2000 running at 10MHz, even though both microcontrollers can communicate using a JTAG clock of 100kHz. Commands which simply read and return information, such as commands to return the ROM banner ID or read from program memory, also take less time than commands which include operations such as programming flash memory.
The data transferred through the JTAG interface is not buffered. If another 10-bit command is sent before the previous command has been read, the previous command's result is lost. The ROM code and TAP controller ensure that no additional data will be sent out by the bootloader before the previous data has been unloaded by the JTAG master, but synchronization in the reverse direction is needed as well. The JTAG master needs a way to tell if the previous byte that it shifted into DR has been read by the bootloader. In that way the JTAG master can tell when it is time to send the next byte. The method for knowing this derives from the two additional status bits sent out by the TAP controller with each 8-bit byte output of the bootloader.
Figure 4. Shifting Data and Status Bits Through DR.
As shown above in Figure 4, the data shifted into and out of DR in TAP Debug mode consists of eight data bits (bits 2 through 9) and two status bits (bits 0 and 1). When the JTAG master shifts data in, only the eight data bits are used. The two status bits (which still must be shifted in) are not used by the TAP controller and can be set to zero or any other value.
In the 10-bit value shifted out from the TAP controller, the two status bits provide information about the state of the bootloader or the debug engine. When the bootloader is running, only the last two states (Debug Busy or Debug Valid) will normally be encountered, as the other two status values do not occur in bootloader mode. (See Status/Condition in Figure 4.)
- If the status bits are set to the Debug Busy (10b) value, the bootloader has not yet read the previous value that was shifted into DR by the JTAG master. This also means that the 8-bit data value shifted out is meaningless, since the bootloader has not yet completed the command. When this value is received, the JTAG master must then reload DR with the previously transmitted byte value, so that the bootloader can access it. The state sequence to perform this operation correctly is as follows.
- After shifting the last bit into DR in the Shift-DR state and transitioning to Exit1-DR, the two status bits should be checked by the JTAG master.
- If the Debug Busy value was received, transition to the Pause-DR state and from there to Exit2-DR, then back to Shift-DR again. Reload the previous DR value (not the value that was shifted in this time), and then go through Exit1-DR and Update-DR, disregarding the value of the status bits on the second pass through.
- Delay for a short period of time (depending on the command being executed) and retry the byte transfer.
- If the status bits are set to the Debug Valid (11b) value, the bootloader has read the last byte shifted in and has loaded a reply byte. The 8-bit value shifted out contains valid data.
;============================================================================== ;= ;= sendCommand ;= ;= Transmits a loader command by shifting bytes through DR. ;= ;= Inputs : DP - Points to area of RAM which stores input bytes ;= for command and which will be filled with output. ;= LC - Number of bytes to transmit/receive ;= Outputs : C - Set on JTAG communication error ;= Destroys : AP, APC, A, A, A, A, PSW, LC ;= sendCommand: move APC, #80h ; Acc => A, turn off auto inc/dec push LC call waitForPrompt pop LC jump C, sendCommand_fail move Acc, @DP ; Read first byte to transmit call shiftDR push Acc move Acc, A cmp #3 ; Should be valid status since we had a prompt pop Acc jump NE, sendCommand_fail move @DP, Acc ; Store first received byte move NUL, @DP++ ; Increment data pointer djnz LC, sendCommand_loop jump sendCommand_pass sendCommand_loop: move A, #10 ; Number of retries allowed sendCommand_retry: move Acc, @DP ; Get next byte to transmit call shiftDR_next push Acc move Acc, A cmp #3 pop Acc jump NE, sendCommand_stall move @DP, Acc ; Store received byte move NUL, @DP++ ; Increment data pointer djnz LC, sendCommand_loop jump sendCommand_pass sendCommand_stall: move LC, #8000 ; About a millisecond djnz LC, $ move Acc, A sub #1 jump NZ, sendCommand_retry jump sendCommand_fail
Functions Provided by the BootloaderBootloader functions on MAXQ microcontrollers generally follow a shared pattern, which means that many of the commands and status codes are identical among the devices. Different devices can implement different subsets of the bootloader command set, depending on the structure of their internal program memory and other requirements. For specific details, consult the User's Guide Supplement for the MAXQ microcontroller that you are using. In this case, we will refer to the bootloader commands in the "In-System Programming" section of the MAXQ2000 User's Guide Supplement.
As with other MAXQ bootloaders, the commands provided by the MAXQ2000 bootloader are divided into command families from 0 to 15. Each command begins with a command byte, which is a combination of the command family (top four bits) and a number specific to the command (low four bits), as shown in Table 5. As a general rule, Family 0 commands, which are informational in nature, are implemented by all devices; other command families are optional. To determine the command families supported by a specific MAXQ device, please refer to the device's documentation. The Family 0 command 05h (Get Supported Commands) returns a bitmask indicating which other command families are supported by that bootloader.
Bootloader command families supported by the MAXQ2000 follow.
- Family 0—Information and Status. The commands in this family can be used to obtain basic information about the MAXQ device, including the identity and version of the ROM/bootloader, the result (status code) from the most recent command, and the sizes of program and data memory. This family also includes the Master Erase command, which clears all program and data memory on the device.
- Family 1—Load Variable Length. The commands in this family can be used to load program (flash) or data (RAM) memory.
- Family 2—Dump Variable Length. The commands in this family can be used to read the contents of program or data memory.
- Family 3—CRC Variable Length. The commands in this family can be used to obtain a CRC-16 value calculated over a specified range of program or data memory.
- Family 4—Verify Variable Length. The commands in this family can be used to verify whether or not a specified range of program or data memory matches data provided by the JTAG master.
- Family 5—Load and Verify Variable Length. The commands in this family combine the functionality of the Load and Verify commands into one command.
- Family 6—Erase Variable Length. On the MAXQ2000, this command can be used to clear a specified area of data RAM to zero.
- Family 7—Erase Fixed Length. On the MAXQ2000, this command can be used to erase individual pages of flash program memory, instead of erasing all flash memory at once with the Master Erase command.
Table 5. Generic MAXQ Bootloader Command Families
|Bit 7||Bit 6||Bit 5||Bit 4||Bit 3||Bit 2||Bit 1||Bit 0||Code||Family/Command|
|0||0||0||0||x||x||x||x||0 x h||Family 0—Informational Commands|
|0||1||0||1||05h||Get Supported Commands|
|0||1||1||0||06h||Get Code Memory Size|
|0||1||1||1||07h||Get Data Memory Size|
|1||0||0||0||08h||Get Loader Version|
|1||0||0||1||09h||Get Utility ROM Version|
|1||0||1||0||0Ah||Set Word/Byte Access Mode|
|1||1||0||1||0Dh||Get ID Information|
|0||0||0||1||x||x||x||x||1 x h||Family 1—Variable-Length Load|
|0||0||0||1||0||0||0||0||10h||Load Code Variable Length|
|0||0||0||1||0||0||0||1||11h||Load Data Variable Length|
|0||0||1||0||x||x||x||x||2 x h||Family 2—Variable-Length Dump|
|0||0||1||0||0||0||0||0||20h||Dump Code Variable Length|
|0||0||1||0||0||0||0||1||21h||Dump Data Variable Length|
|0||0||1||1||x||x||x||x||3 x h||Family 3—Variable-Length CRC|
|0||0||1||1||0||0||0||0||30h||CRC Code Variable Length|
|0||0||1||1||0||0||0||1||31h||CRC Data Variable Length|
|0||1||0||0||x||x||x||x||4 x h||Family 4—Variable-Length Verify|
|0||1||0||0||0||0||0||0||40h||Verify Code Variable Length|
|0||1||0||0||0||0||0||1||41h||Verify Data Variable Length|
|0||1||0||1||x||x||x||x||5 x h||Family 5—Variable-Length Load and Verify|
|0||1||0||1||0||0||0||0||50h||Load/Verify Code Variable Length|
|0||1||0||1||0||0||0||1||51h||Load/Verify Data Variable Length|
|0||1||1||0||x||x||x||x||6 x h||Family 6—Variable-Length Erase|
|0||1||1||0||0||0||0||0||60h||Erase Code Variable Length|
|0||1||1||0||0||0||0||1||61h||Erase Data Variable Length|
|0||1||1||1||x||x||x||x||7 x h||Family 7—Reserved (for expansion)|
|1||0||0||0||x||x||x||x||8 x h||Family 8—Reserved (for expansion)|
|1||0||0||1||x||x||x||x||9 x h||Family 9—Fixed-Length Load|
|1||0||0||1||0||0||0||0||90h||Load Code Fixed Length|
|1||0||0||1||0||0||0||1||91h||Load Data Fixed Length|
|1||0||1||0||x||x||x||x||A x h||Family A —Fixed-Length Dump|
|1||0||1||0||0||0||0||0||A0h||Dump Code Fixed Length|
|1||0||1||0||0||0||0||1||A1h||Dump Data Fixed Length|
|1||0||1||1||x||x||x||x||B x h||Family B—Fixed-Length CRC|
|1||0||1||1||0||0||0||0||B0h||CRC Code Fixed Length|
|1||0||1||1||0||0||0||1||B1h||CRC Data Fixed Length|
|1||1||0||0||x||x||x||x||C x h||Family C—Fixed-Length Verify|
|1||1||0||0||0||0||0||0||C0h||Verify Code Fixed Length|
|1||1||0||0||0||0||0||1||C1h||Verify Data Fixed Length|
|1||1||0||1||x||x||x||x||D x h||Family D—Fixed-Length Load and Verify|
|1||1||0||1||0||0||0||0||D0h||Load/Verify Code Fixed Length|
|1||1||0||1||0||0||0||1||D1h||Load/Verify Data Fixed Length|
|1||1||1||0||x||x||x||x||E x h||Family E —Fixed-Length Erase|
|1||1||1||0||0||0||0||0||E0h||Erase Code Fixed Length|
|1||1||1||0||0||0||0||1||E1h||Erase Data Fixed Length|
|1||1||1||1||x||x||x||x||F x h||Family F—Reserved (device specific)|
Identifying the JTAG Slave MicrocontrollerAfter the bootloader is up and running, the next step is to identify the slave MAXQ microcontroller. The Family 0 command 0Dh (Get ID Information) returns a variable-length ASCII string (zero terminated) identifying the device and the utility ROM version.
Our example program obtains this information and then prints it out to the serial port as part of the demo. The routine used to do this is getBanner.
;============================================================================== ;= ;= getBanner ;= waitForPrompt ;= shiftDR ;= clock0, clock1, shift ;= ;= Executes command 0Dh to retrieve the ROM (device ID) banner and prints ;= the banner text out over the serial port. ;= ;= Inputs : None ;= Outputs : C - Set on JTAG communication error ;= Destroys : AP, APC, Acc, PSW, LC ;= getBanner: call waitForPrompt jump C, getBanner_fail move Acc, #CMD_GET_ROM_BANNER call shiftDR move Acc, #00h call shiftDR getBanner_loop: move Acc, #00h call shiftDR cmp #0FFh ; The banner is ASCII, so receiving this character ; most likely indicates that the JTAG lines are ; floating high and that no device is connected jump E, getBanner_fail jump Z, getBanner_done call txChar jump getBanner_loop getBanner_done: call txNewline jump getBanner_pass getBanner_fail: move C, #1 ret getBanner_pass: move C, #0 ret
Erasing Program MemoryFlash program memory, which is included on the MAXQ2000, must be erased (set to 0FFFFh) before it can be programmed. The JTAG demo application erases the flash memory by sending the 02h (Master Erase) bootloader command, instructing the bootloader to erase all program and data memory. This command also clears the password lock bit, thereby enabling all supported command families.
Depending on the MAXQ device, this 02h (Master Erase) command can require a few seconds to complete. As this is a single-byte command, the easiest way to determine when it is finished is to send the No Operation (00h) command followed by a 1 millisecond delay continuously until the bootloader returns a 3Eh, indicating command complete.This methodology is shown in the masterErase routine below.
;============================================================================== ;= ;= masterErase ;= ;= Executes command 02h (Master Erase) to clear all program and data memory. ;= ;= Inputs : None ;= Outputs : C - Set on JTAG communication error ;= Destroys : Acc, PSW, LC, LC ;= masterErase: call waitForPrompt jump C, masterErase_fail move Acc, #CMD_MASTER_ERASE call shiftDR move Acc, #00h call shiftDR move LC, #5000 ; Number of retries before returning an error masterErase_loop: move Acc, #CMD_NOP call shiftDR cmp #3Eh jump E, masterErase_pass move LC, #8000 ; Delay for about a millisecond djnz LC, $ djnz LC, masterErase_loop masterErase_pass: move C, #0 ret masterErase_fail: move C, #1 ret
Retrieving Status InformationAfter MasterErase (or virtually any other bootloader command) has finished, the 04h (Get Status) command can be used to determine whether or not the command completed successfully. The Get Status command returns two bytes of data: one byte contains status flags (indicating password lock set/unset, word/byte mode active, and other information) and the second byte is a single-byte status code.
The status code is always 00h (No Error) if the last command completed successfully. A nonzero status code indicates an error. An exhaustive list of status codes is available in the MAXQ2000 User's Guide Supplement; a few common error codes are listed here.
- 01h/02h—Family Not Supported/Invalid Command. These error codes usually indicate a communication glitch or alignment error with the JTAG master. If the JTAG master sends more (or fewer) bytes than the bootloader expects for a given command code, the bootloader will interpret one of the data bytes as the start of a new command.
- 03h—No Password Match. This error code usually indicates that the JTAG master tried to use a command which was password-protected (which generally includes any command not in Family 0) without first clearing the password lock. This error happens if the JTAG master accesses a part which already has code loaded into word addresses 0010h – 001Fh. In this case, the part must either be Master Erased or the Password Match command (03h) must be used to unlock the part.
- 05h—Verify Failed. The verification step failed on a Load/Verify or a Verify command. This error can occur after a communications glitch, or also because an attempt was made to reprogram previously programmed flash memory without first erasing it.
Loading Code into Program MemoryTo demonstrate the JTAG bootloader's functionality, the demo application needs to load code into the slave MAXQ2000 over the JTAG connection. In this example, the code loaded will be a simple routine to start the LCD controller and show a 4-digit number on the LCD display.
The code loaded into the slave MAXQ2000 is based on the following simple demo project.
org 0 ljump main org 20h main: move LCRA, #03E0h ; xxx0001111100000 ; 00 - DUTY : Static ; 0111 - FRM : Frame freq ; 1 - LCCS : HFClk / 128 ; 1 - LRIG : Ground VADJ ; 00000 - LRA : RADJ = Min move LCFG, #0F3h ; 1111xx11 ; 1111 - PCF : All segments enabled ; 1 - OPM : Normal operation ; 1 - DPE : Display enabled move LCD0, #LCD_CHAR_0 move LCD1, #LCD_CHAR_0 move LCD2, #LCD_CHAR_0 move LCD3, #LCD_CHAR_2 move LCD4, #00h sjump $However, since we are loading the code bytes from a demo application instead of a hard-coded hex file (as would be the case when loading code with MTK or MAX-IDE), the demo application will modify the application loaded based on user input.
Before the code is loaded, the JTAG communications demo application first instructs the user to enter a 4-digit decimal number and then modifies the application being loaded as follows.
- The number displayed on the LCD is the 4-digit number entered by the user.
- The first four bytes of the password area (starting at word address 010h) are programmed to the ASCII values of the four characters entered by the user.
;;;; ;;;; First load - LJUMP 0020h at start of program memory ;;;; move DP, #0 move @DP, #CMD_LOAD_CODE_VARIABLE move @++DP, #4 ; Length - 4 bytes move @++DP, #00h ; AddrL (byte address 0000h) move @++DP, #00h ; AddrH move @++DP, #000h ; 00 0B 20 0C - ljump 0020h move @++DP, #00Bh move @++DP, #020h move @++DP, #00Ch move @++DP, #000h ; Padding move @++DP, #000h ; Padding move @++DP, #55h move LC, DP move DP, #0 nop call sendCommand nop jump C, main_failJTAG call getStatus jump C, main_failJTAG move Acc, A ; Check that loader status is 00h (no error) jump NZ, main_failStatusThe remaining two blocks of code memory are loaded in a similar fashion.
Verifying Code in Program MemoryOnce the code is loaded, there are several methods to verify that it loaded correctly.
- Instead of using the Load Code Variable Length command (10h), use the Load and Verify Code Variable Length command (50h). This latter command combines the code load operation with a verification step.
- Alternatively, the verification can be performed separately by calling the Verify Code Variable Length command (40h).
- Instead of having the bootloader verify the loaded code, the JTAG host can verify the loaded values directly by using the Dump Code Variable Length (20h) to verify that the loaded code matches the data sent.
Dumping Code from Program MemoryAfter all code is loaded, the final step in the JTAG demo is to read all the code back out in a single block. This read is done by sending the Dump Code Variable Length (20h) command to the bootloader. The parameters for this command are the address to begin dumping (reading) from, and the number of bytes to dump. Note that, as with all JTAG bootloader commands, a byte must be sent for every byte read back. Consequently, there are a large number of zero pad bytes needed by this command, one for each byte that will be read from program memory.
;;;; ;;;; Dump program code ;;;; move DP, #str_dumpCodeVariable call txString move DP, #0 move @DP, #CMD_DUMP_CODE_VARIABLE move @++DP, #1 ; Indicates single byte length (<256 bytes) move @++DP, #00h ; AddrL (byte address 0000h) move @++DP, #00h ; AddrH move @++DP, #128 ; Length - 128 bytes move LC, #128 main_loop1: move @++DP, #00h djnz LC, main_loop1 move @++DP, #000h ; Padding move @++DP, #000h ; Padding move @++DP, #55h move LC, DP move DP, #0 nop call sendCommand nop jump C, main_failJTAG call getStatus jump C, main_failJTAG move Acc, A ; Check that loader status is 00h (no error) jump NZ, main_failStatusThe JTAG demo application outputs these code bytes over the serial port in hex format after they are read.
Exiting the BootloaderThe single-byte Family 0 command 01h (Exit Loader) causes the bootloader to complete operations and exit as follows.
- The bootloader initiates an internal reset which clears the SPE and PSS bits to zero and resets the microcontroller.
- The microcontroller exits reset and begins execution in the Utility ROM at 8000h.
- With SPE now cleared to zero, the Utility ROM code causes execution to jump to the start of user application code at address 0000h.
;;;; ;;;; Exit loader mode and allow program code to execute ;;;; call waitForPrompt move Acc, #CMD_EXIT_LOADER call shiftDR move Acc, #00h call shiftDR move Acc, #00h call shiftDR move Acc, #00h call shiftDR
Running the DemoThe following hardware and software components are required to run the JTAG bootloader demo.
- Two MAXQ2000 Evaluation Kit boards (MAXQ2000-K00 REV B); one EV kit will act as the master MAXQ2000 kit and the other will serve as the slave MAXQ2000 kit.
- MAXQ2000 LCD Daughterboard (MAXQ2000-K01 REV B)
- Two 2 x 5 JTAG interface cables (included with the MAXQ2000 EV kit)
- Serial-to-JTAG Interface Board (MAXQJTAG-001 REV B)
- DB9 straight-through serial cable
- Two 5V regulated (±5%) DC wall supplies, center post positive, CUI Inc. DPR050030-P6 or equivalent
- Two HC49US 8.00MHz crystals
- 2 x 5 0.100inch pin header
- JTAG demo software package
- MAX-IDE Development Environment for MAXQ
- Microcontroller Tool Kit (MTK) for MAXQ
- If MAX-IDE is not already installed, download and install according to the documentation included with the MAXQ2000 EV kit.
- If MTK is not already installed, download and install according to the documentation included with the MAXQ2000 EV kit.
- Connect the LCD daughterboard to header J3 on the slave MAXQ2000 Kit, as shown in Figure 1 above. The LCD daughterboard should be hanging off the top side of the MAXQ2000 kit board.
- Install the 8.00MHz crystals on both MAXQ2000 EV kit boards (at Y1).
- Install the 2 x 5 JTAG header in the prototyping area of the master MAXQ2000 kit, with pins connected to the master MAXQ2000 pins, as listed in Table 1.
- Configure the jumpers and DIP switches on the Serial-to-JTAG board and both MAXQ2000 EV kit boards, as listed in Table 2.
- Connect the DB9 serial cable from the COM1 port on the PC to J1 on the Serial-to-JTAG board.
- Connect the first 5V power supply to J2 on the Serial-to-JTAG board.
- Connect the second 5V power supply to J1 on the slave MAXQ2000 EV kit board.
- Connect the first JTAG cable from P2 on the Serial-to-JTAG board to J4 on the master MAXQ2000 kit. The red wire should go to pin 1 on both JTAG headers.
- Connect the second JTAG cable from the prototype area JTAG header on the master MAXQ2000 kit to J4 on the slave MAXQ2000 kit. The red wire should go to pin 1 on both JTAG headers.
- Download the JTAG demo software package and unzip it into a working directory.
- Start MAX-IDE.
- Turn on power to both 5V power supplies.
- Select Project Open Project from the menu, and select the maxqjtag.prj project file to open it.
- Select Debug Make from the menu. A "Build Successful" message should appear.
- Select Debug Run from the menu. A series of "Loading" messages should appear, followed by "Done".
- Select Debug Stop from the menu.
- Close MAX-IDE.
- Turn off power.
- With power off, disconnect the DB9 serial cable from the Serial-to-JTAG board.
- Connect the DB9 cable to J5 on the master MAXQ2000 kit board.
- Start MTK. Select "Dumb Terminal" in the startup dialog box.
- Select Options Configure Serial Port from the menu. In the dialog box, set Port to COM1 and Speed to 9600 baud.
- Select Target Open COM1 at 9600 baud from the menu.
- Turn power on.
Figure 5. Demo application, serial-port prompt.
Now enter a 4-digit decimal number; there is no need to press ENTER afterwards. The demo application will complete its remaining operations (master erase, load code, and dump code) and will output the results, along with the hex values of the bytes dumped from program memory, as shown in Figure 6.
Figure 6. Demo application, serial-port output.