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.
In [90]:
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
In [2]:
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]])
Out[2]:
Everything matches with the datasheet
Let's package the previous sequence into a general use function.
In [3]:
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))
''.join('{:02x}'.format(bb) for bb in idregister[::-1])
Out[3]:
Let's try reading a page from Flash (p. 261)
In [4]:
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
Out[4]:
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)
In [5]:
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
Out[5]:
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:
In [6]:
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:
In [79]:
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]])
# Set fuses for 12 MHz crystal
#fuses = fuses | 0xf
#program_fuses(fuses, extended)
#print([hex(d) for d in read_fuses_locks()])
Let's start with C. The C compiler generates object files with all the code and constants the program needs to run. It's our job to get that data to RAM and Flash, and to tell the linker where everything will be available at run time.
This requires a linker script and a startup routine to initialize RAM. This is explained in more detail in the linker documentation.
/* FILE linker.ld */
MEMORY
{
flash (rx) : ORIGIN = 0, LENGTH = 16K
sram (rw) : ORIGIN = 0x100, LENGTH = 1K
}
SECTIONS
{
.text : { KEEP(*(.startup))
*(.text)
*(.progmem.data)
_textend = .;
} > flash
.mdata : { _datastart = .;
*(.rodata);
*(.data);
_dataend = .;
} > sram AT> flash
.bss : { _bssstart = .;
*(.bss);
_bssend = .;
} > sram
}
ENTRY(startup)
// FILE startup.c
#include <avr/io.h>
#include <avr/pgmspace.h>
int stack[16];
extern char _textend, _datastart, _dataend, _bssend;
void startup() __attribute__((naked, section (".startup")));
extern int main();
void startup()
{
SP = (int)(stack + sizeof(stack) / sizeof(int));
char *src = &_textend;
char *dst = &_datastart;
while (dst != &_dataend)
{
*dst++ = pgm_read_byte(src++);
}
while (dst != &_bssend)
{
*dst++ = 0;
}
main();
}
The Makefile now looks like this:
# FILE Makefile
CC=avr-gcc
LD=avr-ld
ASFLAGS=-mmcu=atmega162 -Wa,--gen-debug
CFLAGS=-g -mmcu=atmega162 -std=c99
LDFLAGS=-nostdlib -Tlinker.ld
OBJ=blinkc.bin
all: $(OBJ)
%.bin: %
avr-objcopy -O binary $< $@
blinkc: blinkc.o startup.o
.PHONY: dump clean
dump:
LANG=EN avr-objdump -xd blinkc
clean:
rm -rf $(OBJ)
In [88]:
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)
Out[88]:
In [23]:
''.join('{:02x}'.format(bb) for bb in page2)
Out[23]:
In [213]:
int('0x33')
In [179]:
dev = BitBangDevice(bitbang_mode=BITMODE_SYNCBB)
dev.direction = TMS | TDI | TCK
#program_elf('/home/ignamv/programacion/avr/serial')
program_elf('/home/ignamv/programacion/avr/usbtiny00/main.elf')
In [191]:
import pylibftdi
pylibftdi
Out[191]:
In [209]:
from pylibftdi import Device
port = Device(mode='t')
port.baudrate = 9600
In [202]:
port.write('abcde')
Out[202]:
In [212]:
print(port.read(10))
In [ ]:
In [367]:
RS = 1 << 4
EN = 1 << 5
def write(bytes, data=True):
rs = bool(data) * RS
stream = []
for bb in bytes:
stream.append(EN + rs + bb>>4)
stream.append( rs + bb>>4)
stream.append(EN + rs + bb&0xff)
stream.append( rs + bb&0xff)
tty.write(bytearray(stream))
tty.flush()
tty.write(bytearray([2+EN, 2])) # Data length 4 bits
write([0x28, # Function set 2 lines
0x0f, # Display and cursor on, blink
0x06],# Increment address on each write
False)
In [370]:
def init():
tty.write(bytearray([2+EN, 2])) # Data length 4 bits
def clear():
write([1], data=False)
def lcdreturn():
write([2], data=False)
def entrymode(increment=True, shift=False):
write([4+2*increment+1*shift], data=False)
def displaycontrol(on=True, cursor=True, blink=False):
write([8+4*on+2*cursor+1*blink], data=False)
def functionset():
write([0x28], data=False)
def setaddress(address):
write([0x80+address], data=False)
In [385]:
init()
functionset()
entrymode()
displaycontrol(blink=True)
clear()
In [378]:
write('Hola'.encode('ascii'))
In [368]:
write([0x08], False)
In [323]:
write(0x1, False)
In [9]:
a=50
In [15]:
[int(b) for b in int(50e6).to_bytes(4, 'big')]
Out[15]:
In [25]:
2e3 * 5 * 256
Out[25]:
In [29]:
2e6 / 5 / 256
Out[29]:
In [50]:
import
Out[50]:
In [ ]: