FuzboardProgramming

From Simreal

Jump to: navigation, search

robots.gif


There are three levels to this discussion. On the MCU side, we will discuss the JTAG interface and what it does. On the PC side, we will create a simple communications object that will interface with the serial port in the Microsoft Windows environment. Finally, there is additional code that manipulates the serial interface to send JTAG commands.

Of course, if you are using the Cygnal development interface, this project is not exactly necessary. In fact, flash programming via the JTAG protocol is not particularly simple, so only do the work in this section if you like a good puzzle. If you just want to see how to abuse the serial port until it squeaks, jump down to the section labeled "Windows Serial Interface."

JTAG Protocol

Also known as the IEEE 1149.1b. For a fee you can get detailed information on this protocol from the esteemed IEEE organization. JTAG stands for "Joint Test Action Group" and the problem they solved with the IEEE 1149.1b protocol is how to test a complex chip in a circuit without poking it with lots of probes. Especially when not all of its internal state may be visible on the external pins. Though JTAG isn't really the name of the protocol, it is less cumbersome than the full name, "IEEE 1149.1b Test Access Port and Boundary-Scan Architecture."

The purpose of the JTAG interface is to facilitate testing. To do this, it gives access to the state of a chip while it is in circuit and even, if desired, while running. The basic procedure for this is the "Boundary Scan" which can rattle off the contents of all of the chip's internal state (e.g. registers and whatnot) in a single pass.

The protocol also provides access to multiple JTAG-enabled chips wired to the same 4-signal interface (the TDI, TDO, TCK, and TMS pins).

Another neat trick of the JTAG protocol on this particular MCU is that it provides read and write access to the program flash memory. It is this particular feature that is explored here. Once you have this working you can explore the wonders of the boundary scan itself later.

Communication to the JTAG interface is initiated through a state machine that is driven by the TCK and TMS pins. Once it is in the correct control state, data can be transferred to and from the JTAG Address and Data registers using TDI, TDO, and TCK. How the data is used depends on what command you have given the interface; which is, itself, a form of data.

Let's start at the beginning and hopefully things will all clear up by the end. Cygnal has a 25-page application note (AN005) on this subject, so our discussion can be relatively short.

Test Access Port (TAP)

The four lines you connect to on the MCU are known as the Test Access Port (TAP). TCK, TMS, and TDI are inputs, and TDO is the output. On the C8051F series MCU, these are 5-volt tolerant lines.

TCK is the input clock. Data is read by the JTAG interface on the rising edge of the clock, and is ready to be sampled by the PC from TDO on the falling edge.

TMS is the mode select. It is used to negotiate the state machine, which is discussed next.

TDI is the data input and TDO is the output. Pretty much like any other serial interface, really.

State Machine

Every time the clock cycles something happens in the TAP state machine. Depending on the current state and the level of TMS when the clock goes high, the machine may change states, data may be transferred, or both. Referring to the state flow diagram you may note that each state has two exit paths, depending on the value of TMS (0 or 1), noted on the arrow.

(fig10-16.jpg, TAP State Machine, would go here, if I still had it)

Reset. When the MCU is first started, it probably begins in state 0, Reset. Regardless, the first thing you should do prior to using the JTAG interface is to reset the TAP state machine. This way you always know where you are.

To reset the state machine you simply raise TMS high and cycle the clock five times. Looking at the state machine you can trace the action of the reset for any given state. For example, if you are in D4 Shift, the five TMS=1 events will move you through this path; D4 Shift to D5 Exit1, to D8 Update, to D2 Data, to A2 Address, to 0 Reset.

Idle. From state 0 Reset, you move into Idle by lowering the level of TMS to zero and cycling TCK again. In practice you should make every operation end in the Idle state. This convention simplifies access to the state machine and removes the need to perform the reset sequence except at the beginning of all operations.

Register Access. From Idle, you can proceed to either the Data or Address register paths. These two paths are essentially the same.

All of the actual work is done while passing through a shift state. Everything else is simply getting you to the shift state or out of it. The register in question is written and read simultaneously.

At each entry into a shift state (either from capture, or back in from itself), the data on TDO is valid on the falling edge of the clock. At each exit from a shift state, the data on TDI is valid on the rising edge of the clock. Data is shifted least significant bit (LSB) first.

The process of simultaneously shifting data into and out of a register is known as a scan.

Instruction Register

All instructions are 16 bits long. Each instruction consists of two parts: the state control, and a data register address. The state control consists of the upper four bits of the address word (0xf000) and the actual address lives in the lower twelve bits (0x0fff).

States. There are five legal states available at this time (shown here as the top nibble of a hexadecimal word):

0x0000 Normal (0)
0x1000 Halt
0x2000 Reset System
0x4000 Suspend CPU Core
0xf000 Normal (1)

Addresses. Though there is a 12-bit address space, there are only eight data register addresses in use:

0x0000 EX Test
0x0002 Sample/Preload
0x0004 ID Code
0x0fff Bypass
0x0082 Flash Control
0x0083 Flash Data
0x0084 Flash Address
0x0085 Flash Scale

The control nibble affects the status of the CPU core during the access. Normally you will operate in Suspend mode.

When you set the EX Test address, a later data scan will retrieve the boundary data but will not set (update) it.

Sample/Preload not only reads the boundary data but updates (writes) it as well. For the C8051F015 MCU, the boundary data is 87 bits long. For more information on this, read the Cygnal documentation.

The ID Code is a 32-bit code that contains information about the chip in question.

Bypass is a special purpose register that is used to control access to multiple chips in the JTAG chain (if there are more than one).

The four flash registers will be looked at in more detail later. They provide yet another level of indirection in the process and give you full access to the flash program memory.

Data Register

After an instruction scan you will normally perform a data scan. The size of the data (and hence, the number of passes through the data shift state) depends on what address was specified in the instruction scan.

Since our main concern is with flash data, that is explored in more detail in the next section. Also see the actual C++ code later on for details.

Flash Access

There are four flash addresses, as listed above; Control, Data, Address, and Scale. Each of these flash registers (also known as indirect data registers) has its own data length and format.

There is also asymmetry in data length between reading and writing the flash registers.

Regardless of which register you are writing to, there are always an additional two bits at the top of the data that provide access control:

00 Poll
10 Read
11 Write

Read means read, and write is write--polling is where you are just checking a status bit to see if an operation is done yet.

When writing to a nominally 8-bit flash register, there will be 10 bits shifted through it: 9:8 control, and 7:0 the actual data.

When reading a flash register, there is a single bit added at the bottom of the shift that is a busy flag. This bit is high when a flash command is active (and during which time, all additional commands are ignored), and low when a new command can be accepted. Since data is read LSB first, the busy flag can be polled with a single data shift.

For reading from a nominally 8-bit flash register, there will be 9 bits shifted through it: 8:1 data and bit 0 for the busy flag.

Flash Scale. At the very beginning of all flash activity you need to write to the 8-bit scale register. This sets the internal timing circuitry to create the timing for the flash data operations. The correct value for this register depends on the current clock speed of the MCU.

Fortunately the MCU always reverts to the internal 2MHz clock when reset, so for our purposes the flash scale register can always be set to 0x86 ñ the correct value for a 2MHz clock.

Flash Control. The flash control register is an 8-bit register that is used to mediate the operation of the flash memory. This register is described in the documentation as containing two nibbles; the low 4 bits control the read mode, and the high 4 bits the write mode. In practice, it's easier to just list the byte codes for the four basic flash operations:

0x00 Poll
0x02 Read
0x10 Write
0x20 Erase

Before you perform any flash operation, you need to set the control mode. Though it seems a bit inefficient, the documentation says you need to set the control mode prior to each flash access, whether that mode is changing or not.

Again, polling is what you do when you are checking the status of an operation. First, you set the flash control mode to "poll", and then you do a bunch of flash data scans.

Read and write should be fairly self-explanatory. If not, it will come clear in the source code later.

Erase is a bit more complicated. After setting the erase control mode, you must set the flash data to 0xA5 to initiate the erasure. If the current flash address is 0x7DFE or 0x7DFF, all of flash memory is erased. Any other address will erase only the 512 byte page that the address resides in. Erasure, in flash terms, sets the memory to the bit pattern 0xFF.

Flash Address. The flash address register is a 16-bit address into flash memory. Other than the fact that this address is automatically incremented after each data read or write, it is a very simple register.

Flash Data. Flash data is a 10-bit register, of which the top 8 bits are data, and the bottom two bits are status bits. Due to the magic of reading everything LSB first, you can read just the status bits by shifting only two or three bits (and ignoring the sometimes-irrelevant data bits; but noting the extra busy flag that is outside the actual content of the data register).

Bit zero of the flash data register is the flash busy bit (which you check repeatedly to see if your flash operation is done), and bit one is the fail bit (which will be a one if the flash operation failed). Note that the flash busy bit is in addition to the busy bit associated with all register reads. Yeah, it hurt my brain at first, too.

We will now leave our entirely too-short review of the JTAG protocol in order to jump right into the code that implements it. The theory being, that a line of source code is worth a hundred words (or something like that).

Windows Serial Interface

Before we can do fancy things with the JTAG interface, we need to be able to get the data out of the desktop computer. Hearkening back to the diagrams and tables in Chapter 8 of Applied Robotics II, you will recall that the computer's serial port connects the outputs of the DTR line to TMS, RTS to TCK, and TX to TDI. It, in turn, reads DSR from the MCU's TDO signal (and RX, too, but that won't come in play here).

Writing about source code is rarely exciting; there are all sorts of practical details you need to list, and it can get long and tedious. Sorry. First, some definitions.

Definitions

#define SERIAL_READ_BUFLEN 1024
#define READ_TIMEOUT       50
#define WRITE_TIMEOUT      50
#define RETRY_DELAY        50

   HANDLE      m_comm;
   CString     m_portname;
   COMMCONFIG  m_config;
   int         m_read_buflen;
   BYTE*       m_read_buf;
   int         m_read_head;
   int         m_read_tail;
   int         m_timeout;

Though the code (to follow) is implemented as a C++ class called CSerial, I'll skip the tedious definitions for that class. However, it's always nice to know what the magic numbers are, and what the data types are.

Configuring the Serial Port

BOOL   
CSerial::ConfigDialog(
   const CString& in_portname )
{
   m_portname = in_portname;
   DWORD size = sizeof( m_config );
   GetDefaultCommConfig( m_portname, &m_config, &size );
   if (CommConfigDialog( m_portname, NULL, &m_config ))
   {
      SetDefaultCommConfig( m_portname, &m_config, sizeof(m_config) );
      return TRUE;
   }
   return FALSE;
}

This method calls some system functions that setup the serial port. Valid values for the serial port's name include such things as "COM1", "COM2", and so forth. You don't have to call the configuration dialog if you know the port is setup right for your needsÖ but something, somewhere, needs to set the class variable m_portname.

Serial Port Access

BOOL
CSerial::Open( void )
{
   m_comm = CreateFile(   m_portname,
                     GENERIC_READ
                     | GENERIC_WRITE,
                     0,             // Exclusive access
                     NULL,          // No security
                     OPEN_EXISTING,
                     0,             // No attributes
                     NULL );        // Not copied from another
   if (m_comm == INVALID_HANDLE_VALUE)
   {
      error_message( GetLastError() );
      return FALSE;
   }
   DWORD size = 0;
   GetDefaultCommConfig( m_portname, &m_config, &size );
   SetCommConfig( m_comm, &m_config, sizeof(m_config) );
   COMMTIMEOUTS timeout;
   GetCommTimeouts( m_comm, &timeout );
   timeout.ReadIntervalTimeout         = 0;
   timeout.ReadTotalTimeoutMultiplier   = 0;
   timeout.ReadTotalTimeoutConstant      = READ_TIMEOUT;
   timeout.WriteTotalTimeoutMultiplier   = 0;
   timeout.WriteTotalTimeoutConstant      = WRITE_TIMEOUT;
   SetCommTimeouts( m_comm, &timeout );
   m_read_head = 0;
   m_read_tail = 0;
   return TRUE;
}

BOOL
CSerial::Close( void )
{
   if (m_comm != INVALID_HANDLE_VALUE)
      CloseHandle( m_comm );
   m_comm = INVALID_HANDLE_VALUE;
   return TRUE;
}

One of the more interesting things about the serial port in Windows is that it is treated like a file. These methods simply open the port and close it. When your application has the port open, no other application can open it. Likewise, if the Open method fails, somebody (even a crashed version of the same program) is probably currently holding it open.

Sending Serial Data

BOOL         
CSerial::Send( 
   BYTE* in_block, 
   int   in_len )
{
   DWORD      written;
   if (!WriteFile( m_comm, in_block, in_len, &written, NULL ))
   {
      error_message( GetLastError() );
      return FALSE;
   }
   return TRUE;
}

This method just writes an arbitrary number of bytes to the serial port. Just like a file. Creepy. But very simple.

Receiving Serial Data

BOOL
CSerial::Receive( 
   BYTE* out_block, 
   int   nlen )
{
   int   got_len;
   BYTE* ptr;
   int   retry = (m_timeout / (READ_TIMEOUT + RETRY_DELAY)) + 1;
   while (retry--)
   {
      Pump(); 
      if (m_read_tail < m_read_head)
         got_len = (m_read_buflen - m_read_head) + m_read_tail;
      else
         got_len = m_read_tail - m_read_head;
      if (got_len >= in_len)
      {
         ptr = out_block;
         while (in_len--)
         {
            *ptr++ = m_read_buf[m_read_head++];
            if (m_read_head >= m_read_buflen)
               m_read_head = 0;
         }
         return TRUE;
      }
   }
   return FALSE;
}

Receiving serial data is just like reading a file, though you can't tell from this method. What is happening here is, the data from the port is being extracted out of a circular data buffer. The Pump() call at the top fills the buffer (if there is anything to put into it) and then the requested number of bytes is copied out of the buffer (if it is available).

The retry counter keeps the method banging away until it has determined that the data is never going to show up, and then it quits.

void
CSerial::Pump( void )
{
   BYTE  val;
   DWORD read;
   if (m_comm == INVALID_HANDLE_VALUE)
      return;
   while (TRUE)
   {
      if (!ReadFile( m_comm, &val, 1, &read, NULL ))
         return;
      if (read != 1)
         return;
      m_read_buf[m_read_tail++] = val;
      if (m_read_tail >= m_read_buflen)
         m_read_tail = 0;
   }
}

The Pump() method is where you can see the serial port acting like a file. This buffering system is actually fairly useful when you are reading a file, though this buffering is usually done for you by the operating system. With this system you can read the data from the device at a pace or in chunks that are natural (and, hence, efficient) for that device, and then parcel it out in whatever sizes the client application desires.

Setting Control Bit

BOOL      
CSerial::DSRbit( void ) const
{
   DWORD bit = 0;
   if (m_comm != INVALID_HANDLE_VALUE)
      GetCommModemStatus( m_comm, &bit );
   return (bit & MS_DSR_ON) > 0;
}

void      
CSerial::TXbit( BOOL bit ) const
{
   if (m_comm == INVALID_HANDLE_VALUE)
      return;
   if (bit)
      EscapeCommFunction( m_comm, SETBREAK );
   else
      EscapeCommFunction( m_comm, CLRBREAK );
}

void
CSerial::DTRbit( BOOL bit ) const
{
   if (m_comm == INVALID_HANDLE_VALUE)
      return;
   if (bit)
      EscapeCommFunction( m_comm, SETDTR );
   else
      EscapeCommFunction( m_comm, CLRDTR );
}

void   
CSerial::RTSbit( BOOL bit ) const
{
   if (m_comm == INVALID_HANDLE_VALUE)
      return;
   if (bit)
      EscapeCommFunction( m_comm, SETRTS );
   else
      EscapeCommFunction( m_comm, CLRRTS );
}

These extremely simple methods set (or read) the various named communications bits. The escape functions are very slow, but should work across all versions of Windows.

Error Message

void
CSerial::error_message(
   int in_error )
{
   LPVOID   err_msg;
   FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER
                  | FORMAT_MESSAGE_FROM_SYSTEM,
                  NULL,
                  in_error, 
                  MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
                  (LPTSTR)&err_msg,
                  0,
                  NULL );
   MessageBox( NULL, (LPCSTR)err_msg, NULL, MB_OK|MB_ICONINFORMATION );
   LocalFree( err_msg );
}

Since it is used, I feel compelled to document the lowly error message method, too.

Windows JTAG Interface

Finally, here is the additional layer of software that implements the JTAG protocol over the serial port class.

Definitions

#define IR_NORMAL0   0x0000
#define IR_HALT      0x1000
#define IR_RESET     0x2000
#define IR_SUSPEND   0x4000
#define IR_NORMAL1   0xf000
#define DR_EXTEST    0x0000
#define DR_SAMPLE    0x0001
#define DR_IDCODE    0x0004
#define DR_BYPASS    0x0fff
#define DR_FLASHCON  0x0082
#define DR_FLASHDAT  0x0083
#define DR_FLASHADR  0x0084
#define DR_FLASHSCL  0x0085
// -----------------
#define SCALE_2MHZ   0x86
// -----------------
#define FCON_POLL    0x00
#define FCON_READ    0x02
#define FCON_WRITE   0x10
#define FCON_ERASE   0x20
// -----------------
#define FDAT_POLL    0x0
#define FDAT_READ    0x2
#define FDAT_WRITE   0x3

You should recognize the majority of these magic numbers from the earlier JTAG discussion. Other than these, there is only one data member in the CJtag class:

   CSerial  m_port;

JTAG to Serial Bit Mappings

void  TMS( BOOL bit )      { m_port.DTRbit( !bit ); }
void  TCK( BOOL bit )      { m_port.RTSbit( !bit ); }
void  TDI( BOOL bit )      { m_port.TXbit( !bit ); }
BOOL  TDO( void )          { return !m_port.DSRbit(); }

These macros allow us to use bit names that make sense in the JTAG world instead of the serial port generic bit names. It is also interesting to note the logical inversion applied here, to map from the RS232 logic standard to the JTAG standard.

Serial Port Access

BOOL
CJtag::Open( 
   const CString& portname )
{
   Close();
   m_port.setPortName( portname );
   if (!m_port.Open())
   {
      MessageBox( NULL, "Error opening port", NULL, MB_OK|MB_ICONINFORMATION  );
      return FALSE;
   }
   TMS(0);
   TCK(0);
   TDI(0);
   return TRUE;
}

BOOL
CJtag::Close( void )
{
   m_port.Close();
   return TRUE;
}

These methods just call the underlying serial port methods of the same name, though Open() also resets the port's output bits.

JTAG Foundations

void   
CJtag::clock( void )
{
   TCK( 1 );
   TCK( 0 );
}

The clock() method simply toggles the clock bit up and down. This is a fundamental method that, essentially, everything uses.

void   
CJtag::reset( void )
{
   TMS( 1 );
   for (int idx=0; idx<5; idx++)
      clock();
   TMS( 0 );
   clock();
}

The reset() method moves through the state machine through the reset state to the default Idle state. This should be called at the start of a JTAG session.

Scanning Instruction and Data Registers

WORD   
CJtag::scan_ir( 
   WORD  data_out, 
   int   bitnum )
{
   TMS(1);
   clock();       // Select DR
   clock();       // Select IT
   TMS(0);   
   clock();       // Capture IR
   clock();       // Shift IR
   WORD data_in = 0x0000;
   WORD bitflag = 0x0001;
   while (bitnum--)
   {
      if (!bitnum)
         TMS(1);  // Exit IR
      TDI( data_out&0x0001 );
      data_out >>= 1;
      if (TDO())
         data_in |= bitflag;
      bitflag <<= 1;
      clock();
   }
   TMS(1);
   clock();       // Update IR
   TMS(0);
   clock();       // RTI
   return data_in;
}

DWORD   
CJtag::scan_dr( 
   DWORD data_out, 
   int   bitnum )
{
   TMS(1);
   clock();       // Select DR
   TMS(0);   
   clock();       // Capture DR
   clock();       // Shift DR
   DWORD data_in = 0x0000;
   DWORD bitflag = 0x0001;
   while (bitnum--)
   {
      if (!bitnum)
         TMS(1);      // Exit DR
      TDI( data_out&0x0001 );
      data_out >>= 1;
      if (TDO())
         data_in |= bitflag;
      bitflag <<= 1;
      clock();
   }
   TMS(1);
   clock();       // Update DR
   TMS(0);
   clock();       // RTI
   return data_in;
}

These methods scan the instruction (IR) and data (DR) registers. When a register is scanned, the data is flowing both directions at onceÖ into the register and out of it. In these methods the perspective is from the computer (not the MCU) so "data_out" means data out of the computer (in to the MCU) and vice-versa.

The various clocking and fiddling with the TMS bit is where the state machine is navigated. Once in the shift state, the data is scanned into (and out of) the register. Finally, the state machine is navigated back to the Run-Test/Idle (or simply Idle) state.

All of the data is passed through one of these registers, so the remaining methods mostly just call the clock and the two register scan methods.

Control States and Waiting

void   
CJtag::control( 
   BYTE mode )
{
   scan_ir( IR_SUSPEND | DR_FLASHCON );
   scan_dr( (FDAT_WRITE<<8)|mode, 10 );
   wait_busy();
}

This method sets the control mode. The mode parameter should be set to one of the FCON_* constants.

void   
CJtag::wait_busy( void )
{
   while (scan_dr( 0, 1 ))
   {}
}

void
CJtag::wait_flash( void )
{
   control( FCON_POLL );
   while (TRUE)
   {
      scan_ir( IR_SUSPEND | DR_FLASHDAT );
      scan_dr( FDAT_READ, 2 );
      wait_busy();
      BYTE status = (BYTE)scan_dr( FDAT_POLL, 2 );
      if (!(status & 0x02))
         break;
   }
}

These two wait methods block execution until the JTAG state machine is ready to accept a new command, or until the last flash operation has completed (respectively). They scan a minimum amount of data through the registers, essentially just the bottom one or two bits.

void   
CJtag::Scale( 
   BYTE scale )
{
   scan_ir( IR_SUSPEND | DR_FLASHSCL );
   scan_dr( (FDAT_WRITE<<8)|scale, 10 );
   wait_busy();
}

Finally, the Scale() method sets the flash scaling register. The scale parameter should be set to SCALE_2MHZ, which is 0x86.

Flash Address

void   
CJtag::Address( WORD address )
{
   scan_ir( IR_SUSPEND | DR_FLASHADR );
   scan_dr( (FDAT_WRITE<<16)|address, 18 );
   wait_busy();
}

This extremely simple method sets the flash address. It should be called before any data or erasure calls. Note that it scans the instruction register with a suspend mode; this (and the other) methods operate on a suspended MCU.

At the end of the method it waits to return until the address has been processed. Another way to write this code might be to put a wait_busy() (and wait_flash()) call at the beginning of each method ñ this could decrease the amount of time spent in this loop, since time could pass executing non-JTAG code.

Erase Flash

void   
CJtag::Erase( void )
{
   control( FCON_ERASE );
   scan_ir( IR_SUSPEND | DR_FLASHDAT );
   scan_dr( (FDAT_WRITE<<8)|0xa5, 10 );
   wait_busy();
   wait_flash();
}

This method erases flash memory, as explained in the JTAG discussion above. Note that this method, and the later method that writes to flash, has to wait f or two levels of busyness. First, the JTAG command must be processed (wait_busy()) and then there is more waiting while the flash is updated (wait_flash()).

Write Flash Data

void   
CJtag::Data( 
   BYTE data )
{
   control( FCON_WRITE );
   scan_ir( IR_SUSPEND | DR_FLASHDAT );
   scan_dr( (FDAT_WRITE<<8)|data, 10 );
   wait_busy();
   wait_flash();
}

This method simply writes a byte to flash memory at the current address. Once this byte has been written the address is incremented by one so the Address() method does not need to be called again.

Read Flash Data

BYTE   
CJtag::Data( void )
{
   control( FCON_READ );
   scan_ir( IR_SUSPEND | DR_FLASHDAT );
   scan_dr( FDAT_READ, 2 );
   wait_busy();
   wait_flash();
   scan_ir( IR_SUSPEND | DR_FLASHDAT );
   WORD data = (WORD)scan_dr( (FDAT_READ<<8), 11 );
   wait_busy();
   return (BYTE)(data>>3);
}

This method complexly (well, hey, "simply" is a word, so should "complexly") reads a byte from flash. Reading flash is a two-stage process. First, the flash data register has a read command scanned into it (which only takes two data bits). Once the command has been sent, we wait for it to be executed. Once the flash has been read we are able to scan it out of the flash data register. We read eleven bits instead of eight because of the additional flash fail and the two busy bits. We currently ignore these and simply return the data byte.

Other Stuff

DWORD   
CJtag::IDCode( void )
{
   reset();
   scan_ir( IR_RESET | DR_BYPASS );
   scan_ir( IR_HALT | DR_IDCODE );
   DWORD id = scan_dr( 0 );
   scan_ir( IR_NORMAL0 | DR_BYPASS );
   return id;
}

void
CJtag::Halt( void )
{
   reset();
   scan_ir( IR_RESET | DR_BYPASS );
   scan_ir( IR_HALT | DR_IDCODE );
   scan_dr( 0 );
}

void 
CJtag::Restart( void )
{
   scan_ir( IR_RESET | DR_BYPASS );
   scan_ir( IR_NORMAL0 | DR_BYPASS );
}

These methods make up the remainder of the CJtag class. IDCode() retrieves the ID data from the MCU. Halt() stops the processor (including the watchdog timer), and Restart() re-starts it.

Both IDCode() and Halt() start by resetting the JTAG state machine. IDCode() is completely self-contained; all of the other JTAG access methods assume that the client code has called Halt() prior to operation.

Sample Client Code

All of the above is fine and good, but how do you use it?

m_jtag.Halt();
m_jtag.Scale( SCALE_2MHZ );
m_jtag.Address( 0x1000 );
m_jtag.Erase();
m_jtag.Address( 0x1000 );
m_jtag.Data( 0x5a );
m_jtag.Data( 0xc6 );
m_jtag.Address( 0x1000 );

BYTE data = m_jtag.Data();
data = m_jtag.Data();
m_jtag.Restart();

This little code snippet is a basic test for the flash interface.

The first two lines stop the MCU (so it doesn't get cranky or anything) and scales the flash for default behavior. Recall that the Halt() method starts by calling reset() to warm up the state machine.

Then, we set the address to an arbitrary test location and erase the 512-byte page that it lies in (0x1000 through 0x11ff).

Before we write a couple of bytes into the flash we set the address again, though that probably isn't necessary.

Finally, we read the two bytes back from flash. Note that it is necessary to reset the address here, since it increments with each data call.

Once done, the MCU is restarted.

Views
Personal tools