; File: updi-test-pub\main.asm ; Device: ATtiny3216 ; Created: 09.06.2024 21:52:27 ; Version: 2024-06-13 ; Author: Johannes Fechner .nolist .include .list #define ROUND(X) (int(1.0*(X)+0.5)) .macro xout ; eXtended "out" .if @0 > 0x3F sts @0, @1 .else out @0, @1 .endif .endmacro .macro xin ; eXtended "in" .if @1 > 0x3F lds @0, @1 .else in @0, @1 .endif .endmacro .macro ldiz ; Load immediate into Z double register. ldi ZH, high(@0) ldi ZL, low(@0) .endmacro ; == Hardware definitions == ; === CPU clock frequency === .equ F_CPU = 18_432_000 ; Hz ; === Hardware connections === ; USART0.TxD output: .equ TXD_VDIR = VPORTB_DIR .equ TXD_bp = 2 ; Target UPDI (connected via 1kOhm resistor to UPDI of target device): .equ TARGET_UPDI_INTFLAGS = PORTC_INTFLAGS .equ TARGET_UPDI_PINCTRL = PORTC_PIN0CTRL .equ TARGET_UPDI_PORT_VECT = PORTC_PORT_vect .equ TARGET_UPDI_VDIR = VPORTC_DIR .equ TARGET_UPDI_VIN = VPORTC_IN .equ TARGET_UPDI_VOUT = VPORTC_OUT .equ TARGET_UPDI_bp = 0 ; Target UPDI Rx LED: .equ LED_UPDI_RX_VDIR = VPORTB_DIR .equ LED_UPDI_RX_VIN = VPORTB_IN .equ LED_UPDI_RX_VOUT = VPORTB_OUT .equ LED_UPDI_RX_bp = 0 ; Target UPDI Tx LED: .equ LED_UPDI_TX_VDIR = VPORTB_DIR .equ LED_UPDI_TX_VOUT = VPORTB_OUT .equ LED_UPDI_TX_bp = 1 ; == UART definitions == ; Set the UART baud rate: .equ UART_BPS = 115_200 ; bits per second ; == UPDI protocol definitions == .equ UPDI_BPS = 9_600 ; UART baud rate in bits per second. .equ UPDI_SYNCH = 0x55 ; Synchronization byte. .equ UPDI_ACK = 0x40 ; Acknowledge (= "successfully written, ready to accept new transmission" answer) byte. ; == Register name definitions == .def reg00 = r2 ; === Temporary registers === .def temp0 = r16 .def temp1 = r17 .def temp2 = r18 .def temp3 = r19 .def temp4 = r20 .def temp5 = r21 .def tempL = r24 .def tempH = r25 ; === Main registers === .def updiFlags = r3 ; Miscellaneous status bits and flags. .equ UPDI_FLAGS_RXC_bp = 0 ; UPDI Frame Received flag. ; == RAM usage == .dseg .org SRAM_START updiUartBitCntr_ram: .byte 1 ; == 0: during start bit. ; == 1: during data LSb. ; ... ; == 8: during data MSb. ; == 9: during (even) parity bit. ; ==10: during 1st stop bit. ; ==11: during 2nd stop bit. updiUartBitMask_ram: .byte 1 updiUartData_ram: .byte 1 updiUartParityCntr_ram: .byte 1 .cseg .org 0 rjmp reset ; == Interrupt vectors == .org TARGET_UPDI_PORT_VECT rjmp isrUpdiFallingEdge .org TCA0_OVF_vect rjmp isrUpdiUartTimer .org INT_VECTORS_SIZE ; == Zero-terminated strings == szGetSib: .db "T: Get SIB", '\r', '\n', 0, 0 szReceive: .db "R: ", 0 szLineBreak: .db '\r', '\n', 0, 0 ; == Execution entry point after reset == reset: ; == Set clock == ; Set to external clock: ldi temp0, CPU_CCP_IOREG_gc xout CPU_CCP, temp0 ldi temp0, CLKCTRL_CLKSEL_EXTCLK_gc xout CLKCTRL_MCLKCTRLA, temp0 ; Disable clock prescaler: ldi temp0, CPU_CCP_IOREG_gc xout CPU_CCP, temp0 ldi temp0, 0 xout CLKCTRL_MCLKCTRLB, temp0 ; == Initialize registers == clr reg00 clr updiFlags ; == Initialize UART (for communication with PC) == .equ BAUD_REG_VAL = ROUND(4.0*F_CPU/(1.0*UART_BPS)) ldi temp0, low(BAUD_REG_VAL) xout USART0_BAUDL, temp0 ldi temp0, high(BAUD_REG_VAL) xout USART0_BAUDH, temp0 ; USART0_CTRLC = $03 (reset value): asynchronous, 8N1. ldi temp0, (1 << USART_TXEN_bp) | (1 << USART_RXEN_bp) xout USART0_CTRLB, temp0 ; == Initialize TCA0 for UPDI Soft-UART (for communication with target AVR MCU) == .equ UPDI_TIMER_PERIOD = ROUND(F_CPU/(1.0*UPDI_BPS)) ldi temp0, low(UPDI_TIMER_PERIOD) xout TCA0_SINGLE_PER, temp0 ldi temp0, high(UPDI_TIMER_PERIOD) xout TCA0_SINGLE_PER+1, temp0 ; Enable overflow interrupt: ldi temp0, TCA_SINGLE_OVF_bm xout TCA0_SINGLE_INTCTRL, temp0 ; == Initialize I/O == ; Configure TxD pin as output: sbi TXD_VDIR, TXD_bp ; Configure Rx/Tx LEDs as outputs: sbi LED_UPDI_RX_VDIR, LED_UPDI_RX_bp sbi LED_UPDI_TX_VDIR, LED_UPDI_TX_bp ; Enable pull-up at Target UPDI pin: ldi temp0, PORT_PULLUPEN_bm xout TARGET_UPDI_PINCTRL, temp0 ; == Enable interrupts == sei loop: rcall uart_getChar ; Wait for PC key press. ; == Enable Target UPDI == ; Drive Target UPDI low for c. 500ns: cbi TARGET_UPDI_VOUT, TARGET_UPDI_bp sbi TARGET_UPDI_VDIR, TARGET_UPDI_bp .equ DELAY_LOOP_CYCLES = ROUND(F_CPU*(5.0e-7)/2.0)-3 .if DELAY_LOOP_CYCLES < 1 .error "delayLoop has to be replaced by a couple of 'nop' instructions." .endif ldi temp0, DELAY_LOOP_CYCLES delayLoop: dec temp0 brne delayLoop cbi TARGET_UPDI_VDIR, TARGET_UPDI_bp ; Wait for target device to get ready: waitForTarget: sbis TARGET_UPDI_VIN, TARGET_UPDI_bp rjmp waitForTarget ; Wait for c. 100us: ldi temp0, ROUND(F_CPU*(1.0e-4)/8.0)-3 loop_delayBeforeSynch: dec temp0 nop nop nop nop nop nop brne loop_delayBeforeSynch ; == Get SIB from Target MCU == ; Send UART message: ldiz 2*szGetSib rcall uart_putSz ; Send SYNCH character: ldi temp0, UPDI_SYNCH rcall updiUartTx ; Send KEY Get SIB instruction: ldi temp0, 0b111_00_1_01 rcall updiUartTx ; Read answer bytes from Target UPDI: ldiz 2*szReceive rcall uart_putSz clr temp1 loop_waitForAnswer: sbrs updiFlags, UPDI_FLAGS_RXC_bp rjmp loop_waitForAnswer ; updiUartData_ram now contains the received byte. ; First, clear the flag: clt bld updiFlags, UPDI_FLAGS_RXC_bp ; Print the answer to UART: lds temp0, updiUartData_ram rcall uart_putChar inc temp1 cpi temp1, 16 brlo loop_waitForAnswer ; 16 bytes received. ldiz 2*szLineBreak rcall uart_putSz ; Wait for c. 100us: ldi temp0, ROUND(F_CPU*(1.0e-4)/8.0)-3 loop_delayBeforeDisable: dec temp0 nop nop nop nop nop nop brne loop_delayBeforeDisable ; == Disable Target UPDI == ; Send SYNCH character: ldi temp0, UPDI_SYNCH rcall updiUartTx ; Send STCS instruction: ldi temp0, 0b110_0_0000 | 0x03 ; UPDI_CTRLB. rcall updiUartTx ldi temp0, 1<<2 ; UPDIDIS bit. rcall updiUartTx rjmp loop ; == Subroutines == ; === Transmit single character via UART === ; Parameter: temp0, the character to be transmitted. uart_putChar: push temp0 ; Make sure the transmit data registers are empty: uart_sendByte_wait: xin temp0, USART0_STATUS sbrs temp0, USART_DREIF_bp rjmp uart_sendByte_wait pop temp0 xout USART0_TXDATAL, temp0 ret ; === Transmit string via UART === ; Parameter: Z pointer, start address of the null-terminated string. ; String length is limited to 255 characters. uart_putSz: push temp0 push temp1 clr temp1 uart_putSz_loop: lpm temp0, Z+ tst temp0 breq uart_putSz_end rcall uart_putChar inc temp1 cpi temp1, $FF breq uart_putSz_end rjmp uart_putSz_loop uart_putSz_end: pop temp1 pop temp0 ret ; == Wait for reception of character from UART == ; Return: temp0, the received byte. uart_getChar: xin temp0, USART0_STATUS sbrs temp0, USART_RXCIF_bp rjmp uart_getChar xin temp0, USART0_RXDATAL ret ; === Transmit single byte via UPDI === ; Parameters: temp0, the data to be transmitted. updiUartTx: ; Wait for any previous transmission to have finished: updiUartTx_wait: sbic TARGET_UPDI_VDIR, TARGET_UPDI_bp rjmp updiUartTx_wait ; Initialize bit counter and copy data byte: sts updiUartBitCntr_ram, reg00 sts updiUartData_ram, temp0 ; Disable Target UPDI edge interrupt: push temp0 xin temp0, TARGET_UPDI_PINCTRL andi temp0, ~PORT_ISC_gm xout TARGET_UPDI_PINCTRL, temp0 ; Set Target UPDI pin as low-level output: cbi TARGET_UPDI_VOUT, TARGET_UPDI_bp sbi TARGET_UPDI_VDIR, TARGET_UPDI_bp ; Reset and start UPDI UART timer: ldi temp0, TCA_SINGLE_CMD_RESTART_gc xout TCA0_SINGLE_CTRLESET, temp0 ldi temp0, TCA_SINGLE_ENABLE_bm xout TCA0_SINGLE_CTRLA, temp0 ; Switch on UPDI Tx LED: sbi LED_UPDI_TX_VOUT, LED_UPDI_TX_bp pop temp0 ret ; == Interrupt Service Routines == ; === UPDI Falling Edge ISR === isrUpdiFallingEdge: push temp0 xin temp0, CPU_SREG push temp0 ; Clear interrupt flag: xin temp0, TARGET_UPDI_INTFLAGS ori temp0, (1< receiving. ; Target UPDI set as output -> currently transmitting. ; temp0 still contains bit counter value. cpi temp0, 1 breq isrUpdiUartTimer_txAfterStartBit cpi temp0, 9 breq isrUpdiUartTimer_txAfterLastDataBit cpi temp0, 10 breq isrUpdiUartTimer_txAfterParityBit cpi temp0, 11 breq isrUpdiUartTimer_txAfterParityBit cpi temp0, 12 brsh isrUpdiUartTimer_txEndOfFrame ; (2 <= bit counter <= 8) -> [2nd to 8th] data bit follows. isrUpdiUartTimer_txInsideData: ; Load data byte and bit mask, set Target UPDI output accordingly: lds temp0, updiUartData_ram lds temp1, updiUartBitMask_ram and temp0, temp1 breq isrUpdiUartTimer_txInsideData_0 ; Branch if data bit is 0. sbi TARGET_UPDI_VOUT, TARGET_UPDI_bp lds temp0, updiUartParityCntr_ram inc temp0 sts updiUartParityCntr_ram, temp0 rjmp isrUpdiUartTimer_txShiftBitMask isrUpdiUartTimer_txInsideData_0: cbi TARGET_UPDI_VOUT, TARGET_UPDI_bp isrUpdiUartTimer_txShiftBitMask: ; Left-shift the bit mask and write back: lsl temp1 sts updiUartBitMask_ram, temp1 rjmp isrUpdiUartTimer_pop isrUpdiUartTimer_txAfterStartBit: ; Initialize bit mask and parity counter: ldi temp1, 1 sts updiUartBitMask_ram, temp1 sts updiUartParityCntr_ram, reg00 rjmp isrUpdiUartTimer_txInsideData isrUpdiUartTimer_txAfterLastDataBit: ; Set Target UPDI output according to parity: lds temp0, updiUartParityCntr_ram bst temp0, 0 xin temp0, TARGET_UPDI_VOUT bld temp0, TARGET_UPDI_bp xout TARGET_UPDI_VOUT, temp0 rjmp isrUpdiUartTimer_pop isrUpdiUartTimer_txAfterParityBit: ; Stop bit (high) follows: sbi TARGET_UPDI_VOUT, TARGET_UPDI_bp rjmp isrUpdiUartTimer_pop isrUpdiUartTimer_txEndOfFrame: ; After 2nd=last stop bit -> end of frame. ; Stop timer: xout TCA0_SINGLE_CTRLA, reg00 ; Set Target UPDI pin as input: cbi TARGET_UPDI_VDIR, TARGET_UPDI_bp ; Enable Target UPDI edge interrupt: xin temp0, TARGET_UPDI_PINCTRL ori temp0, PORT_ISC_FALLING_gc xout TARGET_UPDI_PINCTRL, temp0 ; Switch off UPDI Tx LED: cbi LED_UPDI_TX_VOUT, LED_UPDI_TX_bp rjmp isrUpdiUartTimer_pop isrUpdiUartTimer_rx: ; temp0 still contains bit counter value. cpi temp0, 1 breq isrUpdiUartTimer_rxStart cpi temp0, 10 breq isrUpdiUartTimer_rxParity cpi temp0, 11 breq isrUpdiUartTimer_rx1stStop cpi temp0, 12 brsh isrUpdiUartTimer_rx2ndStop ; (2 <= bit counter <= 9) -> data bit. lds temp0, updiUartData_ram lds temp1, updiUartBitMask_ram sbic TARGET_UPDI_VIN, TARGET_UPDI_bp or temp0, temp1 sts updiUartData_ram, temp0 ; Left-shift the bit mask and write back: lsl temp1 sts updiUartBitMask_ram, temp1 rjmp isrUpdiUartTimer_pop isrUpdiUartTimer_rxStart: ; Initialize the bit mask and clear the data byte: ldi temp0, 1 sts updiUartBitMask_ram, temp0 sts updiUartData_ram, reg00 rjmp isrUpdiUartTimer_pop isrUpdiUartTimer_rxParity: ; ##### To be done: test for correct parity. ##### isrUpdiUartTimer_rx1stStop: ; ##### To be done: test for high level. ##### rjmp isrUpdiUartTimer_pop isrUpdiUartTimer_rx2ndStop: ; ##### To be done: test for high level. ##### ; Stop timer: xout TCA0_SINGLE_CTRLA, reg00 ; Enable Target UPDI edge interrupt: xin temp0, TARGET_UPDI_PINCTRL ori temp0, PORT_ISC_FALLING_gc xout TARGET_UPDI_PINCTRL, temp0 ; Set UPDI Frame Received flag: set bld updiFlags, UPDI_FLAGS_RXC_bp ; Switch off UPDI Rx LED: cbi LED_UPDI_RX_VOUT, LED_UPDI_RX_bp isrUpdiUartTimer_pop: pop temp0 xout CPU_SREG, temp0 pop temp1 pop temp0 reti