; File: tn12-isp-simulator\main.asm ; Device: AVR64DD32 ; Created: 2025-01-28 ; Version: 2025-02-03 ; Author: Johannes Fechner ; Select whether to output debug messages via USART0: .equ DEBUG = 1 ;.include "C:\Program Files\Microchip\MPLABX\v6.20\packs\Microchip\AVR-Dx_DFP\2.4.286\avrasm\inc\AVR64DD32def.inc" .include "macros.inc" .equ SIMFLASH_SIZE = 1024 ; CPU clock frequency: .equ F_CPU = 20_000_000 ; Hz ; SPI for communication with host/"programmer": .equ SPI_VDIR = VPORTA_DIR .equ SPI_MISO_bp = 5 .equ SPI_SS_bp = 7 .equ SPI_SS_PINCTRL = PORTA_PIN7CTRL .equ SPI_PORT_INTFLAGS = PORTA_INTFLAGS .if DEBUG ; UART for sending debugging messages: .equ UART_DBG_BPS = 115_200 ; bits/s .equ UART_DBG_VPORT_DIR = VPORTA_DIR .equ UART_DBG_TXD_bp = 0 .endif ; == Register name definitions == .def zero = r2 ; Contains always 0x00. .def spi_event = r3 .def spi_dataCopy = r4 .def wri0 = r16 .def wri1 = r17 .def wri2 = r18 .def wri3 = r19 .def wri4 = r20 .def spi_dataCntr = r21 .def wriL = r24 .def wriH = r25 ; == Data memory usage == .dseg .org SRAM_START spiData_sram: .byte 4 simFlash_sram: .byte SIMFLASH_SIZE .cseg .org 0 rjmp reset ; == Interrupt vectors == .org PORTA_PORT_vect rjmp spiSsIsr ; SPI Slave Select ISR .org TCA0_OVF_vect rjmp tcaOvfIsr .org SPI0_INT_vect rjmp spiTcIsr ; SPI Transfer Complete ISR .org INT_VECTORS_SIZE ; == Constant data == signature_flash: .db 0x1E, 0x90, 0x05, 0 .if DEBUG msgSsFallingEdge_flash: .db "SSFE", '\r', '\n', 0, 0 msgProgEnbl_flash: .db "PE", '\r', '\n', 0, 0 msgChipErase_flash: .db "CE", '\r', '\n', 0, 0 msgReadPgmMem_flash: .db "RPM ", 0, 0 msgWritePgmMem_flash: .db "WPM ", 0, 0 msgReadSignature_flash: .db "RSB ", 0, 0 msgLineBreak_flash: .db '\r', '\n', 0, 0 .endif ; DEBUG ; == Execution entry point after reset == reset: ; == Initialize registers == clr zero ; == Initialize SPI_SS (Slave Select) pin == ldi wri0, PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc xout SPI_SS_PINCTRL, wri0 ; == Select OSCHF frequency == ldi wri0, CPU_CCP_IOREG_gc xout CPU_CCP, wri0 ldi wri0, CLKCTRL_FRQSEL_20M_gc xout CLKCTRL_OSCHFCTRLA, wri0 ; == Initialize SPI == ; Connected with host/"programmer" MCU. ; Client role. ldi wri0, SPI_IE_bm xout SPI0_INTCTRL, wri0 ldi wri0, SPI_ENABLE_bm xout SPI0_CTRLA, wri0 sbi SPI_VDIR, SPI_MISO_bp ; == Initialize TCA0 == ; 100-us periodic interrupt. .equ TCA0_PERIOD = ROUND(F_CPU*1.0e-4) ldi wri0, low(TCA0_PERIOD) xout TCA0_SINGLE_PERL, wri0 ldi wri0, high(TCA0_PERIOD) xout TCA0_SINGLE_PERH, wri0 ldi wri0, TCA_SINGLE_OVF_bm xout TCA0_SINGLE_INTCTRL, wri0 .if DEBUG ; == Initialize USART0 == ; Debug message output. .equ BAUD0 = ROUND(4.0*F_CPU/(1.0*UART_DBG_BPS)) ldi wri0, low(BAUD0) xout USART0_BAUDL, wri0 ldi wri0, high(BAUD0) xout USART0_BAUDH, wri0 ldi wri0, USART_TXEN_bm xout USART0_CTRLB, wri0 sbi UART_DBG_VPORT_DIR, UART_DBG_TXD_bp .endif ; DEBUG ; == Enable interrupts == sei loop: tst spi_event breq loop ; SPI byte received clr spi_event mov wri0, spi_dataCopy rcall sendDbgHex ldi wri0, ' ' rcall sendDbgByte rjmp loop ; == SPI Slave Select Edge ISR == spiSsIsr: push wri0 push ZL push ZH xin wri0, CPU_SREG push wri0 ; Clear interrupt flag: ldi wri0, 1< after 1st byte rjmp spiTcIsr0 ; 2 -> after 2nd byte rjmp spiTcIsr7 ; 3 -> after 3rd byte rjmp spiTcIsr3 ; 4 -> after 4th byte spiTcIsr7: ; SPI byte #3 received. ; == Test for Chip Erase command == lds wri0, spiData_sram ; SPI byte #1 cpi wri0, 0xAC brne spiTcIsr1 lds wri0, spiData_sram+1 ; SPI byte #2 andi wri0, 0b1110_0000 cpi wri0, 0b1000_0000 brne spiTcIsr1 ; Chip Erase command received. .if DEBUG ldiz 2*msgChipErase_flash rcall sendDbgMsg .endif ; DEBUG ldi wri0, 0xFF ldiz simFlash_sram ldi wriL, low(SIMFLASH_SIZE) ldi wriH, high(SIMFLASH_SIZE) spiTcIsr4: st Z+, wri0 sbiw wriL, 1 brne spiTcIsr4 rjmp spiTcIsr0 spiTcIsr1: ; == Test for Read Program Memory command == lds wri0, spiData_sram ; first byte andi wri0, 0b1111_0111 cpi wri0, 0b0010_0000 breq spiTcIsr10 rjmp spiTcIsr5 spiTcIsr10: ; Read Program Memory command received. .if DEBUG ldiz 2*msgReadPgmMem_flash rcall sendDbgMsg .endif ; DEBUG lds wri0, spiData_sram+1 ; address MSB andi wri0, 0x01 mov ZH, wri0 lds wri0, spiData_sram+2 ; address LSB mov ZL, wri0 ; Convert the received word address to byte address: lsl ZL rol ZH lds wri0, spiData_sram ; command byte, bit #3 selects low/high byte sbrc wri0, 3 inc ZL .if DEBUG mov wri0, ZH rcall sendDbgHex mov wri0, ZL rcall sendDbgHex .endif ; DEBUG ; Add offset: addi16 ZH, ZL, simFlash_sram ; Load the addressed byte and move it into SPI transmit register: ld wri1, Z xout SPI0_DATA, wri1 .if DEBUG ldi wri0, ' ' rcall sendDbgByte mov wri0, wri1 rcall sendDbgHex ldiz 2*msgLineBreak_flash rcall sendDbgMsg .endif ; DEBUG rjmp spiTcIsr0 spiTcIsr3: ; SPI byte #4 received. clr spi_dataCntr ; == Test for Write Program Memory command == lds wri0, spiData_sram ; first byte andi wri0, 0b1111_0111 cpi wri0, 0b0100_0000 brne spiTcIsr0 ; Write Program Memory command received. .if DEBUG ldiz 2*msgWritePgmMem_flash rcall sendDbgMsg .endif ; DEBUG lds wri0, spiData_sram+1 ; address MSB andi wri0, 0x01 mov ZH, wri0 lds wri0, spiData_sram+2 ; address LSB mov ZL, wri0 ; Convert the received word address to byte address: lsl ZL rol ZH lds wri0, spiData_sram ; command byte, bit #3 selects low/high byte sbrc wri0, 3 inc ZL .if DEBUG mov wri0, ZH rcall sendDbgHex mov wri0, ZL rcall sendDbgHex .endif ; DEBUG ; Add offset: addi16 ZH, ZL, simFlash_sram ; Store the last input byte to the obtained address: lds wri1, spiData_sram+3 ; fourth byte st Z, wri1 .if DEBUG ldi wri0, ' ' rcall sendDbgByte mov wri0, wri1 rcall sendDbgHex ldiz 2*msgLineBreak_flash rcall sendDbgMsg .endif ; DEBUG rjmp spiTcIsr0 spiTcIsr5: ; == Test for Read Signature Byte command == lds wri0, spiData_sram cpi wri0, 0x30 brne spiTcIsr0 ; Read Signature Byte command received. .if DEBUG ldiz 2*msgReadSignature_flash rcall sendDbgMsg .endif ; DEBUG lds wri0, spiData_sram+2 .if DEBUG rcall sendDbgHex ldiz 2*msgLineBreak_flash rcall sendDbgMsg .endif ; DEBUG ldiz 2*signature_flash addz wri0 lpm wri0, Z xout SPI0_DATA, wri0 spiTcIsr0: pop wri0 xout CPU_SREG, wri0 pop ZH pop ZL pop wriH pop wriL pop wri0 reti .if DEBUG ; == Routine: Wait for previous USART0 transmission to finish and send next byte == ; Parameter: wri0, the byte to be sent. sendDbgByte: push wri0 ; Make sure the transmit data registers are empty: sendDbgByte0: xin wri0, USART0_STATUS sbrs wri0, USART_DREIF_bp rjmp sendDbgByte0 pop wri0 xout USART0_TXDATAL, wri0 ret ; === Routine: Send string via USART0 === ; Parameter: Z pointer, start address of the null-terminated string. ; String length is limited to 255 characters. sendDbgMsg: push wri0 push wri1 clr wri1 sendDbgMsg_loop: lpm wri0, Z+ tst wri0 breq sendDbgMsg_end rcall sendDbgByte inc wri1 cpi wri1, $FF breq sendDbgMsg_end rjmp sendDbgMsg_loop sendDbgMsg_end: pop wri1 pop wri0 ret ; === Routine: Send byte in hexadecimal via USART0 === ; Send 8-bit unsigned number as two hex digits. ; Parameter: wri0. sendDbgHex: push wri0 swap wri0 andi wri0, 0x0F rcall sendDbgHexDigit pop wri0 push wri0 andi wri0, 0x0F rcall sendDbgHexDigit pop wri0 ret sendDbgHexDigit: cpi wri0, 10 brlo sendDbgHexDigit0 subi wri0, -('A'-'9'-1) sendDbgHexDigit0: subi wri0, -'0' rcall sendDbgByte ret .endif ; DEBUG