;;; File trutil.inc ;;; ;;; Utility macros for use with AVRASM2 ;;; ;;; Copyright 2011-2019: Dr. Thomas Redelberger, redetho@gmx.de ;;; This code is provided as-is, without any warranty and I disclaim any liability for any damages ;;; resulting from using the code. ;;; When using this code, you have to include my name and e-mail address in a publicly accessible ;;; credits section of your project and/or product. ;;; ;;; Tab size 8, true tabs ;;; ;;; $Id: trUtil.inc 1.3 2018/12/28 19:56:18 redetho Exp redetho $ #ifndef _TRUTIL_INC_ #define _TRUTIL_INC_ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Long branch instructions ;;; Do /not/ work in macros, because labels in macros are not known in nested macros ;;; .macro _breq brne PC+2 rjmp @0 .endm .macro _brne breq PC+2 rjmp @0 .endm .macro ldiw ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Macro to simplify loading a RAM address into x, y, or z ;;; macro arg1=@0: either x, y, z register to load with... ;;; macro arg2=@1: ...address literal ;;; ldi @0L, LOW(@1) ldi @0H, HIGH(@1) .endm ; ldiw .macro MtrClrMem ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Macro to clear dseg bytes ;;; macro arg1=@0: address to start from ;;; macro arg2=@1: number of bytes to clear ;;; if @1 = 0 then 256 bytes are cleared! ;;; ;; check the macro arguments .if @1 > 256 || @1 < 1 .error "Number of bytes to clear must be <= 256 and >= 1" .endif ldiw x, @0 ldi r24, @1 t: st x+, RisZero dec r24 brne t .endm ; MtrClrMem ;; Number of bits to use for a dynamic symbol .equ TRUGD_LD_NUM = 8 .macro MtrUGenDynAppndDgt_ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Auxiliary macro to create a unique string from a number ;;; ;;; Uses macro recursion to append binary digits. ;;; AVRASM2 does not like to concatenate 0 and 1 to macro arguments, hence I use L and H ;;; instead as binary digits ;;; ;;; When recursion ends ;;; a user defined macro will be called (@1) ;;; receiving the instance string (@2) as first macro argument and ;;; the binary digit string (@3) as second macro argument ;;; .set TRUGD_MASK = TRUGD_MASK >> 1 ; next binary digit .if TRUGD_MASK > 0 ; check for end of recursion .if ((@0) & TRUGD_MASK) == 0 MtrUGenDynAppndDgt_ @0, @1, @2, @3L .else MtrUGenDynAppndDgt_ @0, @1, @2, @3H .endif .else ;; invoke the user defined macro and pass instance string and digit string @1 @2, @3 .endif .endm ; MtrUGenDynAppndDgt_ .macro MtrUGenDyn ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Macro to create a unique string from a number which can for example ;;; be used to generate a dynamic symbol. ;;; ;;; Number can go up to 255 = 2^8 -1 as per TRUGD_LD_NUM = 8 ;;; In principle up to 2^15 would be possible, as the maximum macro recursion depth allowed in ;;; AVRASM2 is 15 ;;; ;;; Macro arg1: Number literal or symbol standing for number ;;; Macro arg2: Name of a Macro which will finally perform the required action ;;; Macro arg3: Auxiliary literal which can for example be used to distinguish instances ;;; ;;; The "digit string" - which is the fourth argument to MtrUGenDynAppndDgt_ - is started ;;; by supplying "_", because AVRASM2 macro arguments do not seem to allow for zero length ;;; ;; check the macro arguments .if @0 > 255 || @0 < -128 .error "Number must be <= 255 and >= -128" .endif ;; initialise mask used to test binary digits later .set TRUGD_MASK = 1 << TRUGD_LD_NUM MtrUGenDynAppndDgt_ @0, @1, @2, _ .endm ; MtrUGenDyn ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Macros to perform a Switch/Case control structure like in C ;;; ;;; Usage: ;;; MtrSwitch Ident <= Switch Identifier. Must be unique in whole source. ;;; Allows for nesting of switches ;;; cpi r24, 0 <= any comparison which sets the zero processor flag ;;; MtrCaseEq Ident ;;; ...Statements... ;;; MtrCaseEqEndBrk Ident ;;; ;;; cpi r24, 1 ;;; MtrCaseEq Ident ;;; ...Statements... ;;; MtrCaseEqEndBrk Ident ;;; ;;; cpi r24, 2 ;;; MtrCaseEq Ident ;;; ...Statements... <= This may be empty ;;; MtrCaseEqEndFallThru Ident <= Jumps to *) ;;; ;;; cpi r24, 3 ;;; MtrCaseEq Ident *) ;;; ...Statements... ;;; MtrCaseEqEndBrk Ident ;;; ;;; MtrSwitchEnd Ident ;;; .macro MtrSwitch .set TR_CASE_CNTR_@0 = 0 ; initialise case counter .endm ; MtrSwitch .macro MtrCaseEq_ ; Helper macro, not to be used by itself brne trCb_@0@1 ; branch around the case if not equal .equ trCt_@0@1 = PC ; create label for the case "top" (Ct) .endm ; MtrCaseEq_ .macro MtrCaseEq MtrUGenDyn TR_CASE_CNTR_@0, MtrCaseEq_, @0 .endm ; MtrCaseEq ;; Same but for the case when branch is out of reach. "far", "not far" can be mixed freely .macro MtrCaseEq_far_ ; Helper macro, not to be used by itself breq PC+2 rjmp trCb_@0@1 ; branch around the case if not equal .equ trCt_@0@1 = PC ; create label for the case "top" (Ct) .endm ; MtrCaseEq_far_ .macro MtrCaseEq_far MtrUGenDyn TR_CASE_CNTR_@0, MtrCaseEq_far_, @0 .endm ; MtrCaseEq_far .macro MtrCaseEqEndBrk_ ; Helper macro, not to be used by itself rjmp trSb_@0 ; to break, jump to the bottom of switch (Sb) **) .equ trCb_@0@1 = PC ; create label for the case "bottom" (Cb) .endm ; MtrCaseEqEndBrk_ .macro MtrCaseEqEndBrk MtrUGenDyn TR_CASE_CNTR_@0, MtrCaseEqEndBrk_, @0 .set TR_CASE_CNTR_@0 = TR_CASE_CNTR_@0 + 1 ; increment case counter .endm ; MtrCaseEqEndBrk .macro MtrCaseEqEndFallT_1 ; Helper macro, not to be used by itself rjmp trCt_@0@1 ; jump to the top (Ct) of the /following/ case .endm ; MtrCaseEqEndFallT_1 .macro MtrCaseEqEndFallT_2 ; Helper macro, not to be used by itself ; /no/ break here unlike in macro **) above .equ trCb_@0@1 = PC ; create label for the case "bottom" (Cb) .endm ; MtrCaseEqEndFallT_2 .macro MtrCaseEqEndFallThru .set TR_CASE_CNTR_@0 = TR_CASE_CNTR_@0 + 1 ; increment case counter provisionally MtrUGenDyn TR_CASE_CNTR_@0, MtrCaseEqEndFallT_1, @0 .set TR_CASE_CNTR_@0 = TR_CASE_CNTR_@0 - 1 ; bring case counter back to where it was MtrUGenDyn TR_CASE_CNTR_@0, MtrCaseEqEndFallT_2, @0 .set TR_CASE_CNTR_@0 = TR_CASE_CNTR_@0 + 1 ; increment case counter finally .endm ; MtrCaseEqEndFallThru .macro MtrSwitchEnd .equ trSb_@0 = PC ; create label for the switch bottom (Sb) .endm ; MtrSwitchEnd ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Macros to generate symbols for structure member offsets ;;; ;;; Usage: ;;; MtrSetOffset is used to set the offset "counter", also to reset it to zero ;;; MtrNewOffsetSymbol generates a new global symbol to be used as a structure member offset ;;; ;;; Example: in C you would write ;;; struct point { ;;; int x; ;;; int y; ;;; }; ;;; ;;; With the macros you write ;;; MtrSetOffset 0 ;;; MtrNewOffsetSymbol POINT_X_OFFS, 2 ;;; MtrNewOffsetSymbol POINT_Y_OFFS, 2 ;;; assuming you need two bytes for each integer. ;;; Often you would want to follow this with ;;; .equ POINT_LEN = TR_OFFSET ;;; which is 4 in this example, the length of the structure in bytes ;;; ;;; You would then use this in C like ;;; struct point pt; ;;; pt.x = 456 ;;; pt.y = 789 ;;; ;;; in assembler you do ;;; .dseg ;;; pt: .byte POINT_LEN ;;; ;;; ldi r24, low(456) ;;; ldi r25, high(456) ;;; sts pt+POINT_X_OFFS+0, r24 ;;; sts pt+POINT_X_OFFS+1, r25 ;;; ldi r24, low(789) ;;; ldi r25, high(789) ;;; sts pt+POINT_Y_OFFS+0, r24 ;;; sts pt+POINT_Y_OFFS+1, r25 ;;; or ;;; ;;; ldi r24, low(456) ;;; ldi r25, high(456) ;;; ldi zl, low(pt) ;;; ldi zh, high(pt) ;;; std z+POINT_X_OFFS+0, r24 ;;; std z+POINT_X_OFFS+1, r25 ;;; ... ;;; which is better when you have an array of points and ;;; z shall step through the array elements like ;;; adiw z, POINT_LEN ;;; .macro MtrSetOffset .set TR_OFFSET = @0 .endm ; MtrSetOffset .macro MtrNewOffsetSymbol .equ @0 = TR_OFFSET .set TR_OFFSET = TR_OFFSET + @1 .endm ; MtrNewOffsetSymbol .macro MtrCheckDSEGspace ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Check if all allocated space in DSEG plus stack space fits in RAM. ;;; The DSEG space left could be read by looking up the symbol TR_RAM_LEFT_LEN in the map file. ;;; ;;; The symbol TR_STACK_LEN must be defined. It shall be equated to the estimated needed number ;;; of bytes for the stack ;;; .dseg dummy: ; get the dot .equ TR_RAM_LEFT_LEN = RAMEND - (dummy + TR_STACK_LEN) .if TR_RAM_LEFT_LEN <= 0 .error "There is no space left in DSEG. Look-up TR_RAM_LEFT_LEN to see the deficit" .endif .endm ; MtrCheckDSEGspace #endif /* _TRUTIL_INC_ */ ;;; ***** END OF FILE ******************************************************