/*
JTAGenum
Given a Arduino compatible microcontroller JTAGenum scans
pins[] for basic JTAG functionality. After programming
your microcontroller open a serial terminal with 115200
baud and send 'h' to see usage information.
SETUP:
Define the pins[] and pinnames[] map of pin names to pins
you want to scan with. If you are using a 3.3v board
uncomment the CPU_PRESCALE defintions at the top and in
the setup() function.
If you plan to use IDCODE, Boundary or IR scan routines
define the IR_IDCODE, IR_SAMPLE+SCAN_LEN and
IR_LEN+CHAIN_LEN values according to suspected or
documented values.
This code is an extensive modification and port to Arduino
of Lekernel's ArduiNull [1] which was itself inspired by
Hunz's JTAG Finder (aka jtagscanner) [2]. The advantage
of using Arduino is that the code can be quickly programmed
to any microcontroller supported by the platform (including
PIC[3], AT90USB[4], others) with little to no modification
required. While The Law Of Leaky Abstractions [5] still
applies using Arduino might be helpful for engineers with
tight deadlines.
[1]http://lekernel.net/blog/?p=319
[2]http://www.c3a.de/wiki/index.php/JTAG_Finder
[3]http://www.create.ucsb.edu/~dano/CUI/
[4]http://www.pjrc.com/teensy/
[5]http://joelonsoftware.com/articles/LeakyAbstractions.html
TODO: add support for longer chains when using TAP_SHIFIR
Copyright 2009 Nathan Fain
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
* BEGIN USER DEFINITIONS
*/
//#define DEBUGTAP
//#define DEBUGIR
// For 3.3v AVR boards. Cuts clock in half. Also see cmd in setup()
#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
// Setup the pins to be checked
// The first (currently commented out) is an example broad scan
// used to determine which pins from a set are meant for JTAG.
// The second (uncommented) is used when you know the JTAG pins
// already.
//byte pins[] = {
// PIN_B7, PIN_D0, PIN_D1, PIN_D2, PIN_D3, PIN_D4,/*PIN_D5*/ PIN_D6, /*PIN_D7*/
// PIN_B6, PIN_B5, PIN_B4, PIN_B3, PIN_B2, PIN_B1, PIN_B0 /*PIN_E7*//*PIN_E6*/
//};
//char * pinnames[] = {
// " 3", " 6", "10", "17", "19", "21", /*"24"*/ "26", /*"PIN_D7"*/
// " 2", " 5", " 9", "13", "18", "20", "22" /*"25"*//*"PIN_E6"*/
//};
byte pins[] = { PIN_B7, PIN_D0, PIN_D1, PIN_D2 };
char * pinnames[] = { "TCK_9 ", "TMS_22", "TDO ", "TDI " };
// Pattern used for scan() and loopback() tests
#define PATTERN_LEN 64
// Use something random when trying find JTAG lines:
static char pattern[PATTERN_LEN] = "0110011101001101101000010111001001";
// Use something more determinate when trying to find
// length of the DR register:
//static char pattern[PATTERN_LEN] = "1000000000000000000000000000000000";
// Number of JTAG enabled chips (CHAIN_LEN) and length
// of the DR register together define the number of
// iterations to run for scan_idcode():
#define CHAIN_LEN 2
#define DR_LEN 32
#define IR_IDCODE_ITERATIONS CHAIN_LEN*DR_LEN
// Target specific, check your documentation or guess
#define SCAN_LEN 1890 // used for IR enum. bigger the better
#define IR_LEN 10
// IR registers must be IR_LEN wide:
#define IR_IDCODE "0110000000" // always 011
#define IR_SAMPLE "1010000000" // always 101
#define IR_PRELOAD IR_SAMPLE
/*
* END USER DEFINITIONS
*/
// TAP TMS states we care to use. NOTE: MSB sent first
// Meaning ALL TAP and IR codes have their leftmost
// bit sent first. This might be the reverse of what
// documentation for your target(s) show.
#define TAP_RESET "11111" // looping 1 will return
// IDCODE if reg available
#define TAP_SHIFTDR "111110100"
#define TAP_SHIFTIR "1111101100"
// how many bits must change in scan_idcode() in order to print?
// in some cases pulling a bit high or low might change the state
// of other pins, having nothing to do with JTAG. So 2 is likely
// a good number. Note: these first two bit changes, will not be
// printed to the console.
int IDCODETHRESHOLD = 2;
// Ignore TCK, TMS use in loopback check:
#define IGNOREPIN 0xFFFF
// Flags configured by UI:
boolean VERBOSE = 0; // 255 = true
boolean DELAY = 0;
long DELAYUS = 5000; // 5 Milliseconds
boolean PULLUP = 255;
byte pinslen = sizeof(pins);
void setup(void)
{
// Uncomment for 3.3v boards. Cuts clock in half
CPU_PRESCALE(0x01);
Serial.begin(115200);
}
/*
* Set the JTAG TAP state machine
*/
void tap_state(char tap_state[], int tck, int tms)
{
#ifdef DEBUGTAP
Serial.print("tms set to: ");
#endif
while (*tap_state) { // exit when string \0 terminator encountered
if (DELAY) delayMicroseconds(50);
digitalWrite(tck, LOW);
digitalWrite(tms, *tap_state-'0'); // conv from ascii pattern
#ifdef DEBUGTAP
Serial.print(*tap_state-'0',DEC);
#endif
digitalWrite(tck, HIGH); // rising edge shifts in TMS
*tap_state++;
}
#ifdef DEBUGTAP
Serial.println();
#endif
}
static void pulse_tms(int tck, int tms, int s_tms)
{
if (tck == IGNOREPIN) return;
digitalWrite(tck, LOW);
digitalWrite(tms, s_tms);
digitalWrite(tck, HIGH);
}
static void pulse_tdi(int tck, int tdi, int s_tdi)
{
if (DELAY) delayMicroseconds(50);
if (tck != IGNOREPIN) digitalWrite(tck, LOW);
digitalWrite(tdi, s_tdi);
if (tck != IGNOREPIN) digitalWrite(tck, HIGH);
}
byte pulse_tdo(int tck, int tdo)
{
byte tdo_read;
if (DELAY) delayMicroseconds(50);
digitalWrite(tck, LOW); // read in TDO on falling edge
tdo_read = digitalRead(tdo);
digitalWrite(tck, HIGH);
return tdo_read;
}
/*
* Initialize all pins to a default state
* default with no arguments: all pins as INPUTs
*/
void init_pins (int tck=IGNOREPIN, int tms=IGNOREPIN, int tdi=IGNOREPIN)
{
// default all to INPUT state
for (int i=0; i 1)
return bitstoggled; // no match but activity found
else
return 0; // no match and no activity on tdo
}
/*
* Shift JTAG TAP to ShiftIR state. Send pattern to TDI and check
* for output on TDO
*/
static void scan()
{
int tck, tms, tdo, tdi;
int checkdataret=0;
Serial.print(
"================================\n"
"Starting scan for pattern:\n");
Serial.println(pattern);
for(tck=0;tck 1) {
Serial.print("active ");
Serial.print(" tck:");
Serial.print(pinnames[tck]);
Serial.print(" tms:");
Serial.print(pinnames[tms]);
Serial.print(" tdo:");
Serial.print(pinnames[tdo]);
Serial.print(" tdi:");
Serial.print(pinnames[tdi]);
Serial.print(" bits toggled:");
Serial.println(checkdataret);
}
else if(VERBOSE) Serial.println();
}
}
}
}
Serial.print("================================\n");
}
/*
* Check for pins that pass pattern[] between tdi and tdo
* regardless of JTAG TAP state (tms, tck ignored).
*
* TDO, TDI pairs that match indicate possible shorts between
* pins. Pins that do not match but are active might indicate
* that the patch cable used is not shielded well enough. Run
* the test again without the cable connected between controller
* and target. Run with the verbose flag to examine closely.
*/
static void loopback_check()
{
int tdo, tdi;
int checkdataret=0;
Serial.print(
"================================\n"
"Starting loopback check...\n");
for(tdo=0;tdo 1) {
Serial.print("active ");
Serial.print(" tdo:");
Serial.print(pinnames[tdo]);
Serial.print(" tdi:");
Serial.print(pinnames[tdi]);
Serial.print(" bits toggled:");
Serial.println(checkdataret);
}
else if(VERBOSE) Serial.println();
}
}
Serial.print("================================\n");
}
/*
* Scan TDI for IDCODE
* no need for TDO stimulation
*/
static void scan_idcode()
{
int tck, tms, tdo, i;
int bitstoggled;
byte prevbit, tdo_read;
Serial.print(
"================================\n"
"Starting scan for IDCODE...\n"
//"(if activity found, examine for IDCODE. Pits printed in shift right order with MSB first)\n"
);
char idcodestr[] = " ";
int idcode_i=31; // TODO: artifact that might need to be configurable
uint32_t idcode;
for(tck=0;tck 0) {
idcode |= ((uint32_t)tdo_read) << (31-idcode_i);
idcodestr[idcode_i--] = tdo_read+'0';
Serial.print(tdo_read,DEC);
if (i % 32 == 31) Serial.print(" ");
}
prevbit = tdo_read;
}
if(bitstoggled >= IDCODETHRESHOLD) {
Serial.print("\n tck:");
Serial.print(pinnames[tck]);
Serial.print(" tms:");
Serial.print(pinnames[tms]);
Serial.print(" tdo:");
Serial.print(pinnames[tdo]);
Serial.print("\n bits toggled:");
Serial.print(bitstoggled);
Serial.print("\n idcode buffer: ");
Serial.print(idcodestr);
Serial.print(" 0x");
Serial.println(idcode,HEX);
}
else if (bitstoggled || VERBOSE)
Serial.println();
}
}
}
Serial.print("================================\n");
}
static void shift_bypass()
{
int tdi, tdo;
int checkdataret;
Serial.print(
"================================\n"
"Starting shift of pattern through bypass...\n"
"(assuming TDI->bypassreg->TDO state (no tck or tms))\n");
for(tdi=0;tdi 1) {
Serial.print("active ");
Serial.print(" tdo:");
Serial.print(pinnames[tdo]);
Serial.print(" tdi:");
Serial.print(pinnames[tdi]);
Serial.print(" bits toggled:");
Serial.println(checkdataret);
}
else if(VERBOSE) Serial.println();
}
}
Serial.print("================================\n");
}
void ir_state(char state[], int tck, int tms, int tdi)
{
tap_state(TAP_SHIFTIR, tck, tms);
#ifdef DEBUGIR
Serial.print("ir set to: ");
#endif
for (int i=0; i < IR_LEN; i++) {
if (DELAY) delayMicroseconds(50);
// TAP/TMS changes to Exit IR state (1) must be executed
// at same time that the last TDI bit is sent:
if (i == IR_LEN-1) {
digitalWrite(tms, HIGH); // ExitIR
#ifdef DEBUGIR
Serial.print("ExitIR");
#endif
}
pulse_tdi(tck, tdi, *state-'0');
// digitalWrite(tck, LOW);
// digitalWrite(tdi, *state-'0'); // conv from ascii pattern
#ifdef DEBUGIR
Serial.print(*state-'0', DEC);
#endif
// TMS already set to 0 "shiftir" state to shift in bit to IR
*state++;
}
#ifdef DEBUGIR
Serial.print("\nUpdateIR with ");
#endif
// a reset would cause IDCODE instruction to be selected again
tap_state("11", tck, tms); // UpdateIR & SelectDR
tap_state("00", tck, tms); // CaptureDR & ShiftDR
}
static void sample(int iterations, int tck, int tms, int tdi, int tdo)
{
Serial.print("================================\n"
"Starting sample (boundary scan)...\n");
init_pins(tck, tms ,tdi);
// send instruction and go to ShiftDR
ir_state(IR_SAMPLE, tck, tms, tdi);
// Tell TAP to go to shiftout of selected data register (DR)
// is determined by the instruction we sent, in our case
// SAMPLE/boundary scan
for (int i=0; i ");
i = 0;
while(1) {
c = Serial.read();
switch(c) {
case '0':
case '1':
if(i < (PATTERN_LEN-1)) {
pattern[i++] = c;
Serial.print(c);
}
break;
case '\n':
case '\r':
case '.': // bah. for the arduino serial console
pattern[i] = 0;
Serial.println();
Serial.print("new pattern set [");
Serial.print(pattern);
Serial.println("]");
return;
}
}
}
/*
* main()
*/
void loop() {
char c;
if (Serial.available() > 0) {
c = Serial.read();
byte result = 0;
Serial.println(c);
switch (c) {
case 's':
scan();
break;
case 'p':
set_pattern();
break;
case '1':
init_pins(pins[1], pins[2], PIN_D2);
Serial.println(check_data(pattern, (2*PATTERN_LEN), pins[1],PIN_D1, PIN_D2)
? "found pattern or other" : "no pattern found");
init_pins(pins[1], pins[2], PIN_D2);
Serial.println(check_data(pattern, (2*PATTERN_LEN), pins[1],PIN_D2, PIN_D1)
? "found pattern or other" : "no pattern found");
break;
case 'l':
loopback_check();
break;
case 'i':
scan_idcode();
break;
case 'b':
shift_bypass();
break;
case 'x':
Serial.print("pins tck tms tdi tdo: ");
Serial.print(pinnames[0]);
Serial.print(pinnames[1]);
Serial.print(pinnames[3]);
Serial.println(pinnames[2]);
sample(SCAN_LEN+100, pins[0]/*tck*/, pins[1]/*tms*/, pins[3]/*tdi*/, pins[2]/*tdo*/);
break;
case 'y':
brute_ir(SCAN_LEN, pins[0]/*tck*/, pins[1]/*tms*/, pins[3]/*tdi*/, pins[2]/*tdo*/);
break;
case 'v':
VERBOSE = ~VERBOSE;
Serial.println(VERBOSE ? "Verbose ON" : "Verbose OFF");
break;
case 'd':
DELAY = ~DELAY;
Serial.println(DELAY ? "Delay ON" : "Delay OFF");
break;
case '-':
Serial.print("Delay microseconds: ");
if (DELAYUS != 0 && DELAYUS > 1000) DELAYUS-=1000;
else if (DELAYUS != 0 && DELAYUS <= 1000) DELAYUS-=100;
Serial.println(DELAYUS,DEC);
break;
case '+':
Serial.print("Delay microseconds: ");
if (DELAYUS < 1000) DELAYUS+=100;
else DELAYUS+=1000;
Serial.println(DELAYUS,DEC);
break;
case 'r':
PULLUP = ~PULLUP;
Serial.println(PULLUP ? "Pullups ON" : "Pullups OFF");
break;
default:
Serial.println("unknown command");
case 'h':
Serial.print("\n"
"s > scan\n"
"\n"
"l > loopback\n"
" ignores tck,tms. if patterns passed to tdo pins are\n"
" connected there is a short or a false-possitive\n"
" condition exists that should be taken into account\n"
"\n"
"i > idcode scan\n"
" ignores tdi. assumes IDCODE is default on reset state.\n"
" sets TAP state to DR_SHIFT and prints TDO to console\n"
" if TDO appears active. Human examination required to\n"
" determine if actual IDCODE is present. Run several\n"
" times to check for consistancy or compare against\n"
" active tdo lines found with loopback test.\n"
"\n"
"b > shift_bypass\n"
" currently broken. need to add tck\n"
"\n"
"x > sample (aka boundary scan)\n"
"\n"
"y > brute force IR search\n"
"\n"
"1 > single check\n"
" runs a full check on one code-defined tdi<>tdo pair and\n"
" you will need to look at the main()/loop() code to specify.\n"
"r > pullup resistors on inputs on/off\n"
" might increase stability when using a bad patch cable.\n"
"v > verbose on/off\n"
" print tdo bits to console during testing. will slow\n"
" down scan.\n"
"d > delay on/off\n"
" will slow down scan.\n"
"- > delay - 1000us (or 100us)\n"
"+ > delay + 1000us\n"
"p > set pattern ["
);
Serial.print(pattern);
Serial.println("]\n\n"
"h > help");
break;
}
Serial.print("\n> ");
}
}