This example expands on the code from previous examples running code on the ATmega32. Note that this code is building towards interrogating other devices using JTAG, not demonstrating the internal JTAG features of the ATmega32 specifically.
I started by cleaning up the driver for the status LED on
PORTB0. This is important in order to share the system safely with other code, particularly the new code to handle an external interrupt on the
INT0 pin. I'm driving that pin with a 555 timer generating a ~1Hz square wave. Depending on how external interrupts (
INT2) are configured in the
MCUCR, they can be set to trigger on one of four conditions: logic level low, logic level change, falling edge, or rising edge. Any typical function generator feeding a 0-Vcc logic signal into
INT0) should work.
If a given external interrupt is enabled in the
GICR, or Global Interrupt Control Register, then the appropriate condition on the corresponding pin will cause a per-interrupt (i.e.,
INT2) bit to be set in the
GIFR, or Global Interrupt Flags Register. On completion of the current instruction, if the
I-bit in the
SREG is set, enabling interrupts globally, then the processor will jump to the appropriate external interrupt vector:
This means that external interrupts can continue to set flags in the
GIFR, even if interrupts are globally disabled by the
I-bit in the
SREG, unless they are also disabled by unsetting the corresponding bit in the
GICR. This enables the ATmega32 to queue up a single future interrupt per pin by setting the appropriate bit in the
GIFR while an ISR (Interrupt Service Routine) is executing. However, further interrupts can only set the same flag value, resulting in that information being lost.
Upon entry to an ISR, the
I-bit in the
SREG is automatically disabled to prevent the nesting of interrupt handlers from overflowing the stack. (It is possible to re-enable interrupts inside an ISR, but the consequences are beyond the scope of this post.) When
reti is executed, the ATmega32 will restore the
PC from the stack and
jmp to that location to continue execution.
In this example, the ISR
jtag_bb_TCK will save
INT0, clear the
INTF0 bit in the
GIFR (to ignore any interrupts that might have happened since the ISR began), pulse
call toggle_led to change the state of the status LED on
call delay for approximately 3 seconds, re-enable interrupts on
SREG, and return control to the main thread. You normally want your ISRs to return as quickly as possible, but this is done for demonstration purposes.
The project has been broken into out individual files:
$ tree . ├── boot.s ├── defines.s ├── drivers │ ├── jtag_bb.s │ └── led.s ├── isr_table.s ├── main.s ├── utility.s └── vendor └── m32def.inc 2 directories, 8 files
vendor/m32def.inc is a set of defines (which are, practically speaking, optional) provided by the manufacturer with their assembler, but you can find a copy on GitHub. These are solely to improve the readability of the code. You could do without them, or provide your own definitions (e.g., for different register locations in the memory map), but as the values are machine-specific, you'll wind up writing much the same code. There are a couple things missing that would be nice to have, so I put them in
There are also more highly optimized ways to do these operations, to save cycles here and there, but the goals of the code are clarity and predictability, not raw performance or code size.
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
; Platform constant definitions .nolist .include "vendor/m32def.inc" .list ; Constant definitions .include "defines.s" ; SRAM segment .dseg .org SRAM_START ; 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: ; Enable PORTB1 to use as a status light for the JTAG bitbanging interface call jtag_bb_status_enable ; Configure JTAG bitbanging interrupt on INT0 push r24 ldi r24, INT0_TRIGGER_RISE call jtag_bb_int0_trigger_on pop r24 call jtag_bb_int0_enable ; Enable interrupts sei ; Infinite sleep loop to let interrupts drive program forever: sleep rjmp forever
.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
; Interrupt Handlers jmp boot ; RESET = External Reset Vector jmp jtag_bb_TCK ; 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
; 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 PORTA0 status LED (reserves r26) call flash_led ; Flash the PORTA0 status LED to signal handover to user code jmp start
; 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
; LED driver for PORTB, PINB0 enable_led: sbi DDRB, PINB0 ret toggle_led: sbic PORTB, PINB0 rjmp led_off rjmp led_on led_on: sbi PORTB, PINB0 ret led_off: cbi PORTB, PINB0 ret ; Library functions flash_led: push r16 push r24 push r25 ldi r16, 0x06 ldi r24, 0x7D ldi r25, 0x10 call delay call toggle_led dec r16 brne PC - 5 pop r25 pop r24 pop r16 ret
; Enable PINB1 and PINB2 jtag_bb_status_enable: push r16 in r16, DDRB ori r16, (1 << PINB1) | (1 << PINB2) out DDRB, r16 pop r16 ret ; Set the triggering mode for INT0. 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 jtag_bb_int0_disable: push r16 in r16, GICR andi r16, ~(1 << INT0) out GICR, r16 pop r16 ret ; Clear the INTF0 interrupt flag in the GIFR jtag_bb_int0_clear: push r16 in r16, GIFR andi r16, ~(1 << INTF0) out GIFR, r16 pop r16 ret ; Pass pin mask to pulse on PORTB in r24 jtag_bb_pulse: push r16 in r16, PORTB ; Get current state push r16 ; Save for later or r16, r24 ; Enable bitmask and write to PORTB out PORTB, r16 pop r16 ; Restore state and write to PORTB out PORTB, r16 pop r16 ret ; ISR to handle JTAG clocking jtag_bb_TCK: INTERRUPT_START ; Pushes r16, then pushes SREG to the stack call jtag_bb_int0_disable ; Disable INT0 so GIFR won't be set (even though GIE I-bit is cleared during interrupt handling). call jtag_bb_int0_clear ; Clear the INTF0 interrupt flag in the GIFR push r24 ; Pulse PORTB, PINB1 ldi r24, (1 << PINB1) call jtag_bb_pulse pop r24 call toggle_led ; Toggle the LED so we have a visual cue push r24 ; Delay inside the ISR to skip over a small number of potential interrupts push r25 ; This is only for demonstration purposes. Normally, you want your ISR to ldi r24, 0xFF ; process as fast as possible and return control to the main thread. ldi r25, 0xFF call delay pop r25 pop r24 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