ATmega32 Bare Metal JTAG - Part 2: Signalling and Initialization

Today's post is a group effort. The code and electronic design comes from yesterday's AUGH, and was authored by Rob Crowther (weilawei), Anthony Russell-Smith, and Luke Dyson (massspec). We set ourselves a goal to wire up the ATmega32 for a full set of JTAG signals and to bitbang the necessary sequence to get to the Run-Test-Idle state in the JTAG state machine on a debug target.

ATmega32 Bare Metal JTAG - Part 2 Breadboard

In Part 1, we cleaned up some old code, prepared to handle external interrupts, and tested the handling using an external 555 timer. This time, we've:

  • wired up a harness to debug a Raspberry Pi 3 B+ target over JTAG,
  • wired up a matching harness for the logic analyzer to do test, development, and debug,
  • added more status LEDs (which will be used to give a visual indicator of which state of the JTAG state machine we think we should be in),
  • made existing code use interrupts more safely (disabling them around atomic sections with cli and sei),
  • written code to
    • issue a reset to a debug target,
    • toggle TMS and TCK pins, and
    • enter the Run-Test-Idle state, and
  • verified the TRST, TMS, and TCK signals on a logic analyzer.

We're using IEEE 1149.1-2001 (R2008): IEEE Standard Test Access Port and Boundary Scan Architecture as our reference version of JTAG. Since the specification is paywalled, we're also referencing Xilinx's description of the JTAG state machine.

ATmega32 Bare Metal JTAG - Part 2 Schematic

We use PORTA to talk to a JTAG target and the logic analyzer, and PORTB is shared between our USBasp and status LEDs

If you have a cheaper USB logic analyzer, like the Saleae knock-off we're using, you may have some issues getting a good capture. We want to watch 9 signal lines at a time, which takes a good bit of sample memory at any sample rate where pulses won't blur together or disappear entirely. Just waiting for the trigger causes ours to run out of memory! To make timing the "reset-start logic analyzer" dance simpler, the status LEDs will flash a "countdown" sequence, flash the first status LED, perform the code to reset a JTAG target, and flash the first status LED again before settling into an infinite loop. You could avoid all of that by using a better logic analyzer. In this project, we've deliberately chosen slow timings and long delays, so that it's easy to follow what's going on at each step.

The code enters the Test-Logic-Reset state, then toggles TMS to walk through Run-Test-Idle, Select-DR-Scan, Select-IR-Scan, and finally back to Test-Logic-Reset in an infinite loop. The status LED on PORTB0 (the red one in the photo) stays lit during this cycle, while the other 4 LEDs display the current JTAG state in binary. The status LED turns off and the loop pauses for a one second delay, after which the cycle repeats.

ATmega32 Bare Metal JTAG - Part 2 Logic Analyzer

There are two new files from last time, eeprom_map.s and sram_map.s, but both of these are empty placeholders for EEPROM data and SRAM reservations. You can download the latest version of the code and the schematic/KiCad files.

$ tree
.
├── boot.s
├── defines.s
├── drivers
│   ├── jtag_bb.s
│   └── led.s
├── eeprom_map.s
├── isr_table.s
├── main.s
├── sram_map.s
├── utility.s
└── vendor
    └── m32def.inc

    2 directories, 10 files

We also bought a few new ATmega32As, which come clocked at 1MHz by default, but we've been using a chip with the fuses set for a 4 MHz internal clock. To calculate the fuse values, you can use the Engbedded AVR Fuse Calculator, which will provide the correct options to pass to avrdude. If you calculate fuse values yourself, bear in mind that a bit value of 0 means programmed and a bit value of 1 is unprogrammed. In this case, to set the clock speed to 4 MHz, we want to do:

$ avrdude -c usbasp -p m32 -P usb -U lfuse:w:0xe3:m -U hfuse:w:0x99:m

To assemble the code and flash the chip from the project root, do:

$ avra main.s && avrdude -c usbasp -p m32 -P usb -U flash:w:main.s.hex:i

main.s

;   Platform constant definitions
.nolist
.include "vendor/m32def.inc"
.list

;   Constant definitions and macros
.include "defines.s"

;   EEPROM segment
.eseg
.include "eeprom_map.s"

;   SRAM segment
.dseg
.org SRAM_START 
.include "sram_map.s"

;   Code segment
.cseg
.org CODE_START

;   Initialization code.
.include "isr_table.s"                  ;   ISR table must be located at 0x0000 in memory.
.include "boot.s"

;   General system utility functions
.include "utility.s"

;   Hardware drivers
.include "drivers/led.s"
.include "drivers/jtag_bb.s"

;   Main application
start:
    call jtag_bb_main
    call flash_status_led

    ;   Enable interrupts
    sei

    forever:                        
        ldi r24, (1 << PINB0)
        call led_on

        ;   We start out in Test-Logic-Reset
        ldi r24, TLR

        ;   Status LEDs to toggle on and off 
        call jtag_bb_TMS_off            ;   Test-Logic-Reset    -> Run-Test/Idle:  Bring TMS low, clock TCK
        call jtag_bb_cycle_TCK
        call led_off
        ldi r24, RTI
        call led_on

        call jtag_bb_TMS_on             ;   Run-Test/Idle       -> Select-DR-Scan
        call jtag_bb_cycle_TCK
        call led_off
        ldi r24, SDRS
        call led_on

        call jtag_bb_cycle_TCK          ;   Select-DR-Scan      -> Select-IR-Scan
        call led_off
        ldi r24, SIRS
        call led_on

        call jtag_bb_cycle_TCK          ;   Select-IR-Scan      -> Test-Logic-Reset
        call led_off
        ldi r24, TLR
        call led_on

        ldi r24, (1 << PINB0)
        call led_off
        call one_sec_delay
        rjmp forever

defines.s

.equ CODE_START = 0x00

.equ INT0_TRIGGER_LOW       = 0
.equ INT0_TRIGGER_CHANGE    = 1
.equ INT0_TRIGGER_FALL      = 2
.equ INT0_TRIGGER_RISE      = 3

.macro INTERRUPT_START
    push r16
    in r16, SREG
    push r16
.endmacro

.macro INTERRUPT_END
    pop r16
    out SREG, r16
    pop r16
    reti
.endmacro

isr_table.s

;   Interrupt Handlers
jmp boot            ;  RESET    = External Reset Vector  
jmp jtag_bb_RTCK    ;  INT0     = External Interrupt Request 0
reti                ;  INT1     = External Interrupt Request 1
reti                ;  INT2     = External Interrupt Request 2
reti                ;  OC2      = Timer/Counter2 Compare Match
reti                ;  OVF2     = Timer/Counter2 Overflow
reti                ;  ICP1     = Timer/Counter1 Capture Event
reti                ;  OC1A     = Timer/Counter1 Compare Match A
reti                ;  OC1B     = Timer/Counter1 Compare Match B
reti                ;  OVF1     = Timer/Counter1 Overflow
reti                ;  OC0      = Timer/Counter0 Compare Match
reti                ;  OVF0     = Timer/Counter0 Overflow
reti                ;  SPI      = Serial Transfer Complete
reti                ;  URXC     = USART, Rx Complete
reti                ;  UDRE     = USART Data Register Empty
reti                ;  UTXC     = USART, Tx Complete
reti                ;  ADCC     = ADC Conversion Complete
reti                ;  ERDY     = EEPROM Ready
reti                ;  ACI      = Analog Comparator
reti                ;  TWI      = 2-wire Serial Interface
reti                ;  SPMR     = Store Program Memory Ready

boot.s

;   On RESET
boot:
    enable_stack:                   ;   Setup stack so we can use call
        ldi r16, high(RAMEND)
        out SPH, r16
        ldi r16, low(RAMEND)
        out SPL, r16
    call enable_led                 ;   Enable the PORTB(0-4) status LEDs
    call countdown_status_led
    call flash_status_led           ;   Flash the PORTB0 status LED to signal handover to user code
    cli                             ;   Disable global interrupts to clean up after boot
    jmp start

utility.s

;   Delay is for r24 * r25 * 250 microseconds
;   r24 and r25 must be > 0 
delay:
    push r16
    push r17
    push r24
    push r25

    mov r17, r24
        ldi r16, 0x3E
            dec r16
            brne PC - 1
        dec r17
        brne PC - 4
    dec r25
    brne PC - 7

    pop r25
    pop r24
    pop r17
    pop r16
    ret

one_sec_delay:
    push r24
    push r25
        ldi r24, 0x7F
        ldi r25, 0xFF
        call delay
    pop r25
    pop r24 
    ret

quarter_sec_delay:
    push r24
    push r25
        ldi r24, 0x3F
        ldi r25, 0xFF
        call delay
    pop r25
    pop r24 
    ret

drivers/led.s

;  LED driver for PORTB, PINB(0-4)
enable_led:
    push r16
    cli
        in r16, DDRB 
        ori r16, (1 << PINB0) | (1 << PINB1) | (1 << PINB2) | (1 << PINB3) | (1 << PINB4)
        out DDRB, r16
    sei
    pop r16
    ret

toggle_status_led:
    sbic PORTB, PINB0
    rjmp status_led_off
    rjmp status_led_on

status_led_on:
    sbi PORTB, PINB0
    ret

status_led_off:
    cbi PORTB, PINB0
    ret

;   Library functions
flash_status_led:
    push r16
    push r24
    push r25
        ldi r16, 0x06               ;   Loop counter
        ldi r24, 0x7D               ;   Delay arguments
        ldi r25, 0x10

        _flash_status_led_loop:
            call delay
            call toggle_status_led

        dec r16
        brne _flash_status_led_loop 
    pop r25
    pop r24
    pop r16
    ret

;   Performs a "countdown" of the status LEDs on PORTB(0-4)
countdown_status_led:
    push r16
    push r17
    push r18
        ldi r16, 0b00011111         ;   Status bitmask
        ldi r17, 0b00011111         ;   Mask off bits we don't own

        _countdown_status_led_loop:
            and r16, r17            ;   Restrict from updating bits we don't own

            cli                     ;   On  
                in r18, PORTB           
                or r18, r16
                out PORTB, r18
            sei

            call quarter_sec_delay  ;   Delay

            com r16
            cli
                in r18, PORTB
                and r18, r16
                out PORTB, r18
            sei
            com r16

            lsl r16                 ;   Update

        brne _countdown_status_led_loop
    pop r18
    pop r17
    pop r16
    ret

led_on:
    push r16
        in r16, PORTB
        or r16, r24
        out PORTB, r16
    pop r16
    ret

led_off:
    push r16
    push r24
        com r24
        in r16, PORTB
        and r16, r24
        out PORTB, r16
    pop r24
    pop r16
    ret

drivers/jtag_bb.s

.equ TLR    = (0 << 1)
.equ RTI    = (1 << 1)
.equ SDRS   = (2 << 1)
.equ SIRS   = (9 << 1)

jtag_bb_main:
    ;   Configure pins on PORTA to use for JTAG bitbanging
    call jtag_bb_status_enable

    ;   Configure JTAG bitbanging interrupt on INT0 to handle JTAG adaptive clocking using RTCK
    ;   We need to do work on the rising and falling edges, so use INT0_TRIGGER_CHANGE
    push r24
        ldi r24, INT0_TRIGGER_CHANGE
        call jtag_bb_int0_trigger_on
    pop r24
    call jtag_bb_int0_enable

    call jtag_bb_init_TRST          ;   ? -> Test-Logic-Reset: Configure TRST (active low), bring TMS high, pulse TRST low and back high

    ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  
;   Configuration and Utility 
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;   Configure pins on PORTA
jtag_bb_status_enable:
    push r16
    cli
        in r16, DDRA
        ori r16, (1 << PINA0) | (0 << PINA3) | (1 << PINA5) | (1 << PINA6) | (1 << PINA7)
        out DDRA, r16
    sei
    pop r16
    ret

;   Pass pin mask to pulse on PORTA in r24
jtag_bb_pulse:
    push r16
    cli
        in r16, PORTA                   ;   Get current state
        push r16                        ;   Save for later
            or r16, r24                 ;   Enable bitmask and write to PORTA
            out PORTA, r16                  
        pop r16                         ;   Restore state and write to PORTA
        out PORTA, r16
    sei
    pop r16
    ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  
;   TRST
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

jtag_bb_init_TRST:
    ;   Configure TRST pin on PORTA4
    sbi PORTA, PINA4
    sbi DDRA, PINA4

    ;   Bring TMS high
    sbi PORTA, PINA7
    sbi DDRA, PINA7

    ;   Pulse TRST low and back high
    cbi PORTA, PINA4
    call one_sec_delay   
    sbi PORTA, PINA4

    ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  
;   TCK
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

jtag_bb_cycle_TCK:
    call quarter_sec_delay
    call jtag_bb_toggle_TCK
    call quarter_sec_delay
    call jtag_bb_toggle_TCK
    ret

jtag_bb_toggle_TCK:
    sbic PORTA, PINA5
    rjmp jtag_bb_TCK_off
    rjmp jtag_bb_TCK_on

jtag_bb_TCK_on:
    sbi PORTA, PINA5
    ret

jtag_bb_TCK_off:
    cbi PORTA, PINA5
    ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  
;   TMS
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

jtag_bb_toggle_TMS:
    sbic PORTA, PINA7
    rjmp jtag_bb_TMS_off
    rjmp jtag_bb_TMS_on

jtag_bb_TMS_on:
    sbi PORTA, PINA7
    ret

jtag_bb_TMS_off:
    cbi PORTA, PINA7
    ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  
;   RTCK
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;   Set the triggering mode for INT0 (handling RTCK). Pass mode in r24:
;       INT0_TRIGGER_LOW
;       INT0_TRIGGER_CHANGE
;       INT0_TRIGGER_FALL
;       INT0_TRIGGER_RISE
jtag_bb_int0_trigger_on:
    push r16
        in r16, MCUCR
        or r16, r24 
        out MCUCR, r16
    pop r16
    ret

;   Enable external interrupt INT0 to listen for a JTAG TCK signal
jtag_bb_int0_enable:
    push r16
        in r16, GICR
        ori r16, (1 << INT0) 
        out GICR, r16
    pop r16
    ret

;   Disable external interrupt INT0 (RTCK)
jtag_bb_int0_disable:
    push r16                        
        in r16, GICR
        andi r16, ~(1 << INT0) 
        out GICR, r16
    pop r16
    ret

;   Clear the INTF0 (RTCK) interrupt flag in the GIFR
jtag_bb_int0_clear:
    push r16                        
        in r16, GIFR
        andi r16, ~(1 << INTF0)
        out GIFR, r16
    pop r16
    ret

;   ISR to handle JTAG adaptive clocking using RTCK on INT0
jtag_bb_RTCK:
    INTERRUPT_START                 ;   Pushes r16, then pushes SREG to the stack
    call jtag_bb_int0_disable       ;   Disable INT0 (RTCK) so GIFR won't be set (even though GIE I-bit is cleared during interrupt handling).
    call jtag_bb_int0_clear         ;   Clear the INTF0 (RTCK) interrupt flag in the GIFR

    ;   Do stuff here

    call jtag_bb_int0_enable        ;   Re-enable INT0 so we don't miss future events
    INTERRUPT_END                   ;   Pop and restore SREG, pop r16, and return

Sections