I had an ATMEGA162 lying around and nothing to program it with, so I implemented the JTAG programming protocol connecting the ATMEGA to the PC with an FT232 USB to serial adapter. To make this an amenable experience I used IPython Notebook, which allowed me to quickly test code snippets, see the output, and finally convert everything into a .py file. You can download the original IPython notebook, read the finished program or follow the implementation process on this post, painstakingly converted to wordpress HTML.

We’re using synchronous bitbang mode on the FT232R, which batches communications for extra speed. The procedure is:

Build the waveform Output the whole thing Read the response

For every byte clocked out, the response will have a byte with the state of the input pins before the output pins changed. The JTAG interface shifts bits out of TDO on the rising edge of TCK. Therefore, if we send a rising edge on byte N then we must read TDO on the response byte N+1.

Atmega162 datasheet p. 250:

Programming through the JTAG interface requires control of the four JTAG specific pins: TCK,

the JTAG Interface TMS, TDI, and TDO. Control of the Reset and clock pins is not required

Let’s connect the JTAG pins to FT232 data pins, leaving D0 and D1 free for serial communications later.

FT232 ATMEGA PIN -------------------- D4 TMS 26 D2 TDI 28 D3 TDO 27 D5 TCK 25 GND GND 20

from pylibftdi import BitBangDevice from pylibftdi.driver import BITMODE_SYNCBB TMS = 1 << 4 TDI = 1 << 2 TDO = 1 << 3 TCK = 1 << 5 dev = BitBangDevice(bitbang_mode=BITMODE_SYNCBB) dev.direction = TMS | TDI | TCK

First we test the driver, wiring and my understanding of this state diagram

by reading the device identification register. The 1s and 0s in the state diagram show how the state transitions depend on TMS at the clock edge.

stream = [0] def clock_in(bits): """Output bits then raise TCK. Returns index of (input state after the rising TCK edge) in the response array.""" stream.append(bits) stream.append(bits | TCK) return len(stream) # Take TAP controller from Test-Logic-Reset to Run-Test/Idle for ii in range(2): clock_in(0) # Take TAP to Shift-IR for tms in [1,1,0,0]: clock_in(tms * TMS) # Shift IDCODE (0x1) into IR for ir in [1,0,0,0]: clock_in(ir * TDI) # MSB of IR is shifted with TMS high stream[-2] |= TMS stream[-1] |= TMS # Take TAP to Run-Test/Idle, then Shift-DR for tms in [1,0,1,0,0]: clock_in(tms * TMS) # Shift out 32 bits of ID register for ii in range(32): retindex = clock_in(0) stream[-1] |= TMS for tms in [1,0]: clock_in(tms * TMS) clock_in(0) dev.flush() dev.write(bytearray(stream)) ret = dev.read(len(stream)); len(ret) ''.join(['01'[rr & TDO != 0] for rr in ret[retindex:retindex-64:-2]])

‘01111001010000000100000000111111’

We can check this string against the datasheet:

Everything matches, including the version 0x7 = revision H.

Let’s package the previous sequence into a general use function.

from math import ceil from enum import Enum class AVR_JTAG(Enum): # (instruction register value, number of bits in selected data register) IDCODE = (0x1, 32) PROG_ENABLE = (0x4, 16) PROG_COMMANDS = (0x5, 15) PROG_PAGELOAD = (0x6, 1024) PROG_PAGEREAD = (0x7, 1032) AVR_RESET = (0xC, 1) BYPASS = (0xF, 1) def jtag_command(instruction, data): """Set the instruction register, shift in bits from data, return the output bits data[0] holds the least significant bits""" if not isinstance(instruction, AVR_JTAG): raise ValueError("instruction must be member of AVR_JTAG") irvalue = instruction.value[0] nbits = instruction.value[1] if isinstance(data, int): data = data.to_bytes(ceil(nbits/8), 'little') stream = [0] IR_LENGTH = 4 def clock_in(bits): """Output bits then raise TCK. Returns index of (input state after the rising TCK edge) in the response array.""" stream.append(bits) stream.append(bits+TCK) return len(stream) # Take TAP controller from Test-Logic-Reset to Run-Test/Idle for ii in range(2): clock_in(0) # Take TAP to Shift-IR for tms in [1,1,0,0]: clock_in(tms * TMS) # Shift IDCODE (0x1) into IR for bit in range(IR_LENGTH): clock_in((irvalue & 1) * TDI) irvalue >>= 1 # MSB of IR is shifted with TMS high stream[-2] |= TMS stream[-1] |= TMS # Take TAP to Run-Test/Idle, then Shift-DR for tms in [1,0,1,0,0]: clock_in(tms * TMS) # Shift out nbits of data register # data[0] is LSB retindex = None for bit in range(nbits): byte = int(bit / 8) if byte < len(data): ret = clock_in(TDI*bool( (data[byte] >> (bit%8)) & 1)) else: # Pad with zeros ret = clock_in(0) if bit == 0: retindex = ret #data[int(bit / 8)] >>= 1 # MSB of DR is shifted with TMS high stream[-2] |= TMS stream[-1] |= TMS # Take TAP to Run-Test/Idle for tms in [1,0]: clock_in(tms * TMS) clock_in(0) dev.flush() # Return buffer bytes = bytearray(ceil(nbits / 8)) CHUNK_SIZE = 256 read = [] for offset in range(0, len(stream), CHUNK_SIZE): written = dev.write(bytearray(stream[offset:offset+CHUNK_SIZE])) read.append(dev.read(written)) ret = b''.join(read) for bit in range(nbits): bytes[int(bit / 8)] |= bool(ret[retindex + 2 * bit] & TDO) << (bit % 8) return bytes idregister = jtag_command(AVR_JTAG.IDCODE, bytearray(4))

Let’s try reading a page from Flash (p. 261)

As a check, I’ll read one page with page commands and with byte commands, and see if they match.

jtag_command(AVR_JTAG.AVR_RESET, 1) jtag_command(AVR_JTAG.PROG_ENABLE, 0xA370) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2302) # Enter Flash Read jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0700) # Load Address High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0300) # Load Address Low Byte page = jtag_command(AVR_JTAG.PROG_PAGEREAD, 0)[1:] page2 = bytearray(len(page)) for address in range(int(len(page2)/2)): jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0700) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0300+address) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3200) page2[2*address] = jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3600)[0] page2[2*address+1] = jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700)[0] page == page2

True

Now let’s write a program to blink a LED. We’ll just have a 2 byte counter and output the MSB on PORTB. Since we’re not setting the direction, the port pins will toggle between high-impedance and a 20kΩ pull-up. This means we can connect the LED directly between the port pin and ground.

; FILE blink.S

#include <avr/io.h>

loop: adiw r24,1 ; Add 1 to r25:r24

out _SFR_IO_ADDR(PORTB), r25 ; Write MSB to PORTB

rjmp loop ; Repeat

We automate assembly and linking with Make:

# FILE Makefile

CC=avr-gcc

LD=avr-ld

ASFLAGS=-mmcu=atmega162 -Wa,--gen-debug

LDFLAGS=-nostdlib

OBJ=blink

all: $(OBJ)

.PHONY: dump clean

dump:

LANG=EN avr-objdump -d $(OBJ)

clean:

rm -rf $(OBJ)

Assemble and link blink.S

$ make

avr-gcc -mmcu=atmega162 -Wa,--gen-debug -nostdlib blink.S -o blink

We can look at the machine code using avr-objdump:

$ avr-objdump -d blink

LANG=EN avr-objdump -d blink

blink: file format elf32-avr

Disassembly of section .text:

00000000 <__ctors_end>:

0: 01 96 adiw r24, 0x01 ; 1

2: 98 bb out 0x18, r25 ; 24

4: fd cf rjmp .-6 ; 0x0 <__ctors_end>

Our program boils down to the machine code 0x019698bbfdcf. Let’s write it to Flash, following the instructions in the datasheet (p. 260)

from time import sleep program = 0xcffdbb989601 # Reversed byte order due to endianness jtag_command(AVR_JTAG.AVR_RESET, 1) jtag_command(AVR_JTAG.PROG_ENABLE, 0xA370) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2380) # Chip Erase jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3180) # Chip Erase jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3380) # Chip Erase jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3380) # Chip Erase sleep(10e-3) # Wait for chip erase jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2310) # Enter Flash Write jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0700) # Load Address High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0300) # Load Address Low Byte jtag_command(AVR_JTAG.PROG_PAGELOAD, program) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) # Write Flash Page jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3500) # Write Flash Page jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) # Write Flash Page jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) # Write Flash Page sleep(5e-3) # Wait for Flash write jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2302) # Enter Flash Read jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0700) # Load Address High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0300) # Load Address Low Byte pagenew = jtag_command(AVR_JTAG.PROG_PAGEREAD, 0)[1:] jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2300) # Exit Programming Mode jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3300) # Exit Programming Mode jtag_command(AVR_JTAG.PROG_ENABLE, 0) jtag_command(AVR_JTAG.AVR_RESET, 0) # Verify int.from_bytes(pagenew[:int(program.bit_length()/8)], 'little')==program

True

The program works, although it’s blinking almost too fast to tell.

We’d like to avoid typing machine code by hand. Let’s have a function read the output’s .text section and write that into Flash:

from elftools.elf.elffile import ELFFile def program_elf(file, verify=True): """Write executable into atmega162 program memory. Example: >>> program('elffile') """ elf = ELFFile(open(file, 'rb')) program = elf.get_section_by_name(b'.text').data() jtag_command(AVR_JTAG.AVR_RESET, 1) jtag_command(AVR_JTAG.PROG_ENABLE, 0xA370) # Chip Erase jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2380) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3180) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3380) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3380) sleep(10e-3) # Wait for chip erase jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2310) # Enter Flash Write PAGE_BYTES = int(1024 / 8) for offset in range(0, len(program), PAGE_BYTES): address = offset >> 1 # Flash words are 2 bytes jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0700 | (address>>8) & 0xff) # Load Address High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0300 | address & 0xff) # Load Address Low Byte jtag_command(AVR_JTAG.PROG_PAGELOAD, program[offset:offset+PAGE_BYTES]) # Write Flash Page jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3500) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) sleep(10e-3) # Wait for Flash write if verify: jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2302) # Enter Flash Read for offset in range(0, len(program), PAGE_BYTES): address = offset >> 1 # Flash words are 2 bytes jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0700 | (address>>8) & 0xff) # Load Address High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x0300 | address & 0xff) # Load Address Low Byte read = jtag_command(AVR_JTAG.PROG_PAGEREAD, 0)[1:] if read[:len(program)-offset] != program[offset:offset+PAGE_BYTES]: raise RuntimeError('Verification failed at offset {}: wrote {}, read {}'.format( offset, program[offset:offset+PAGE_BYTES], read)) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2300) # Exit Programming Mode jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3300) # Exit Programming Mode jtag_command(AVR_JTAG.PROG_ENABLE, 0) jtag_command(AVR_JTAG.AVR_RESET, 0) program_elf('/home/ignamv/programacion/avr/blinkc', verify=True)

Now it’s time for the fuses, so we can set the clock source and other configuration bits. This is straight from page 262 of the datasheet:

def poll_fuse_write_complete(): while True: if jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700)[1] & 2: break def program_fuses(fuses, extended_fuses): if not isinstance(fuses, int): raise TypeError('Expected int for fuses') jtag_command(AVR_JTAG.AVR_RESET, 1) jtag_command(AVR_JTAG.PROG_ENABLE, 0xA370) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2340) # Enter Fuse Write jtag_command(AVR_JTAG.PROG_COMMANDS, 0x1300 | (extended_fuses & 0xff)) # Load Data Low Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3b00) # Write Fuse Extended Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3900) # Write Fuse Extended Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3b00) # Write Fuse Extended Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3b00) # Write Fuse Extended Byte poll_fuse_write_complete() jtag_command(AVR_JTAG.PROG_COMMANDS, 0x1300 | ((fuses >> 8) & 0xff)) # Load Data Low Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) # Write Fuse High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3500) # Write Fuse High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) # Write Fuse High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700) # Write Fuse High Byte poll_fuse_write_complete() jtag_command(AVR_JTAG.PROG_COMMANDS, 0x1300 | (fuses & 0xff)) # Load Data Low Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3300) # Write Fuse High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3100) # Write Fuse High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3300) # Write Fuse High Byte jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3300) # Write Fuse High Byte poll_fuse_write_complete() def read_fuses_locks(): jtag_command(AVR_JTAG.AVR_RESET, 1) jtag_command(AVR_JTAG.PROG_ENABLE, 0xA370) jtag_command(AVR_JTAG.PROG_COMMANDS, 0x2304) # Enter Fuse/Lock Bit Read jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3a00) # Read fuses and lock bits extended = jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3e00)[0] # Read fuses and lock bits high = jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3200)[0] # Read fuses and lock bits low = jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3600)[0] # Read fuses and lock bits lock = jtag_command(AVR_JTAG.PROG_COMMANDS, 0x3700)[0] # Read fuses and lock bits return extended, high << 8 | low, lock extended, fuses, lock = read_fuses_locks() print([hex(d) for d in [extended, fuses, lock]])

[‘0xff’, ‘0x99ff’, ‘0xff’]

Future posts might cover linking and linker scripts, because you really shouldn’t write unlinked object files to Flash.

Me gusta esto: Me gusta Cargando... Relacionado

Etiquetas: atmega, atmega162, flash, ft232, ipython, jtag, notebook, python