I am writing a program for bit-banging a JTAG interface. As far as I understood the JTAG interface, it should act like this:
- On clock rise, the TDI and TMS inputs are sampled, if Shift-IR or Shift-DR is the current state, the register is shifted with the value of TDI right one place.
- On clock fall, TDO is set based on the value that fell off the shift register if the current state of the TAP is a Shift state, and then the state of the TAP changes based on the value sampled on TMS.
- Shifting data in the Shift-IR register only has an effect when navigating to Update-IR, and on Capture-DR the selected register in IR is latched on TDI/TDO and ready for shifting in Shift-DR. If a register has a particular function, the data shifted in Shift-DR will be handled on Update-DR.
So in order to input an instruction, I should navigate the TAP to Shift-IR, then shift my instruction LSB first, then at the same time last bit is shifted in, exit the Shift-IR state. Same for Shift-DR to input or output data.
Here is a sample of the code I'm working with, using a STM32F4 as my bit-banger. jtag_tck() cycles the JTAG clock.
uint8_t jtag_tdi_tms(int32_t tdi_state, uint8_t tms_state)
{
jtag_set(JTAG_TDI_GPIO_Port, JTAG_TDI_Pin, tdi_state ? GPIO_PIN_SET : GPIO_PIN_RESET);
jtag_set(JTAG_TMS_GPIO_Port, JTAG_TMS_Pin, tms_state ? GPIO_PIN_SET : GPIO_PIN_RESET);
jtag_tck();
return jtag_get(JTAG_TDO_GPIO_Port, JTAG_TDO_Pin);
}
uint32_t idcode()
{
uint32_t result = 0;
uint32_t status = 0;
reset_TAP();
// Navigate from Test-Logic-Reset to Shift-IR
jtag_tdi_tms(0, 0); // Test-Logic-Reset->Run-Test/Idle
jtag_tdi_tms(0, 1); // Run-Test/Idle->Select-DR Scan
jtag_tdi_tms(0, 1); // Select-DR Scan->Select-IR Scan
jtag_tdi_tms(0, 0); // Select-IR Scan->Capture-IR
jtag_tdi_tms(0, 0); // Capture-IR->Shift-IR (first 1 here, read below)
// Shift in the instruction: IDCODE = 0x01, LSB first
status |= jtag_tdi_tms(1, 0) ; // Shift-IR->Shift-IR
status |= jtag_tdi_tms(0, 0) << 1; // Shift-IR->Shift-IR
status |= jtag_tdi_tms(0, 0) << 2; // Shift-IR->Shift-IR
status |= jtag_tdi_tms(0, 0) << 3; // Shift-IR->Shift-IR
status |= jtag_tdi_tms(0, 1) << 4; // Shift-IR->Exit1-IR
#if DEBUG == 1
printf("IDCODE IR Status: %lu\n", status);
#endif
status = 0;
// End instruction shift
// Return to Run-Test/Idle
jtag_tdi_tms(0, 1); // Exit1-IR->Update-IR
jtag_tdi_tms(0, 0); // Update-IR->Run-Test/Idle
// Navigate from Run-Test/Idle to Shift-DR
jtag_tdi_tms(0, 1); // Run-Test/Idle->Select-DR Scan
jtag_tdi_tms(0, 0); // Select-DR Scan->Capture-DR
jtag_tdi_tms(0, 0); // Capture-DR->Shift-DR
// Shift out the value in the IDCODE register
for(int i = 0; i < 31; i++)
result |= (jtag_tdi_tms(0, 0) << i); // Shift-DR->Shift-DR
result |= (jtag_tdi_tms(0, 1) << 31); // Shift-DR->Exit1-DR
// Return to Run-Test/Idle
jtag_tdi_tms(0, 1); // Exit1-DR->Update-DR
jtag_tdi_tms(0, 0); // Update-DR->Run-Test/Idle
return result;
}
Here is the result I get on my serial output, which is basically 0x7fffffff:
IDCODE IR Status: 0
2147483647
Beyond this, the documentation for the MCU I'm connected with JTAG on tells me that the output on TDO from shifting in the instruction should look like "p0001", with bits p for "Protected". Except the first 1 comes out as the TAP exits the Capture-IR state and enters the Shift-IR state, and no other 1 is shifted out, which tells me there is no protection issue.
Resetting the TAP to TLR, immediately navigating to Shift-DR and shifting bits out brings the id code as normal.
First: is my understanding of the TAP navigation and data-shifting correct?
Second: Am I missing something on the instruction shifting stage in my code? Or is it that the MCU I'm trying to communicate with is locked somehow?