Suppose you want to enable or disable external interrupts with certain configurations. You'll have to wiggle some SFR bits for the purpose, probably involving multiple SFRs.
For me, it's looking thus:
;this is ext_int_enable orl TCON, #0x05 ;IT1|IT0 - falling edge only orl IE, #0x05 ;EX1 | EX0 orl AUXR2, #0x30 ;EX3 | EX2 ;this is ext_int_disable anl AUXR2, #~0x30 ;EX3 | EX2 anl IE, #~0x05 ;EX1 | EX0
This is quite ugly. I want to write it down only once, and than use it couple of times. It makes the code more readable as the purpose of code snippets is made clear to the reader, and reduces the nuisance of writing the whole thing multiple times, reducing bugs and errors introduced by non-careful typeing.
Sure I can treat these as functions, with calls to their label and returns, but it will miss the point - putting aside the overhead of the function call, which can grow quite large for configurations-rich code, this is not the conceptual Idea I wanted to use in the first place.
What I really want is that the assembler will replace each of these tags:
ext_int_enable
with the code snippet:
orl TCON, #0x05 ;IT1|IT0 - falling edge only orl IE, #0x05 ;EX1 | EX0 orl AUXR2, #0x30 ;EX3 | EX2
I want it to be replaced directly everywhere the code where the tag appears. I want it to simply delete the tag and paste the relevant snippet instead, in the source code.
For those of you familiar with C, this is akin to using preprocessor macros (conceptually you can achieve it with an inline function too, given you forcethe compiler to inline).
Good assemblers, such as ASXXXX which SDAS is based upon, support macros which act just that way. The way to use these with SDAS looks thus:
;ext_int .macro ext_int_enable orl TCON, #0x05 ;IT1|IT0 - falling edge only orl IE, #0x05 ;EX1 | EX0 orl AUXR2, #0x30 ;EX3 | EX2 .endm .macro ext_int_disable anl AUXR2, #~0x30 ;EX3 | EX2 anl IE, #~0x05 ;EX1 | EX0 .endm
Calling macros is almost trivial. In the last post I defined my external interrupt handler thus:
ext_interrupt_handler: anl AUXR2, #~0x30 ;EX3 | EX2 anl IE, #~0x05 ;EX1 | EX0 reti
The inside is ext_int_disable, which can simply be called as a macro defined earlier:
ext_interrupt_handler: ext_int_disable reti
The assembler replaces the ext_int_disable symbol with the internals of the macro definition above before assembling it. Quite neat IMO.
Macro arguments
Say I want to do something cleverer than static configuration of SFRs, e.g. configuring a timer to some value:
mov TL0, #(0x10000-count)&0xff mov TH0, #((0x10000-count)>>8)&0xff
Where count is the number of timer cycles I want. I might want to use this in several places with different cycle count (that are constant in the code), or rather change this value upon assembly with variable flags (say, different values for different main-clock frequencies).
One must pass the value to the macro with each use, some how. Luckily, ASXXXX is smart enough to do it quite trivially, by adding the arguments with commas to the macro definition:
.macro t0_set_count, count mov TL0, #(0x10000-count)&0xff mov TH0, #((0x10000-count)>>8)&0xff .endm
The use is similar. Possible use I had in my code:
delay_debounce: t0_set_count, 0x0010 sjmp delay_activate delay_display2: t0_set_count, 0x2000 sjmp delay_activate delay_display: t0_set_count, 0x5000 sjmp delay_activate
Notice that the values are constant. They can't change on the fly, only during assembly time. To change these values midrun, one must use functions rather than macros. Other possible solution is using a macro with static variables instead of the "count" argument, and change these variables between macro calls.
Advanced macros
Assume one want a macro even more elaborate. For example, I want to choose sleep-mode upon calling some macro. I might want it to look something like this
.macro ext_int_get_input, pd_flag ext_int_get_input_beginning: clear_ext_int_flags ;Necessary ext_int_enable interrupt_enable .if pd_flag = 1 orl PCON, #0x01 ;PD = power down .else orl PCON, #0x01 ;IDL .endif interrupt_disable .endm
First, notice that I call macros from within this macro definition. This is called macro nesting, and is allowed by ASXXXX up to a depth of 20 calls-within-calls. The number is arbitrary, but should be plenty for virtually all users.
The .if and .else lines are assembly-time if/else expressions, akin to #IF/#ELSE in C preprocessor. These will choose whether the assembler will put one branch in the code, or the other.
In the example above, calling
ext_int_get_input, 1
will effectively be replaced with
clear_ext_int_flags ;Necessary ext_int_enable interrupt_enable orl PCON, #0x01 ;IDL interrupt_disable
so that the .if branch is ignored and not assembled.
This is fine for using only one of the options, as if pd_flag is a global assembly flag. However, using both the options in the same code will raise an error:
<m> Multiple definitions of the same label, multiple .module directives, multiple conflicting attributes in an .area or .bank directive or the use of .hilo and lohi within the same assembly.</m>
The wits of ASXXXX macro processor are limited, and different pd_flag values require it to assemble the macro once again, but the label "ext_int_get_input_beginning" is already in use by the other version of the macro, and it simply can't be assembled.
For us it's clear that each instance of the macro is different, and expect the assembler to assign these with different names such as "ext_int_get_input_beginning10000$" and "ext_int_get_input_beginning10001$" depending on the instance, but it really can't make this abstraction for us.
There's a manual way for doing it:
.macro ext_int_get_input, pd_flag, ?rand ext_int_get_input_beginning'rand: clear_ext_int_flags ;Necessary. ext_int_enable interrupt_enable .if pd_flag = 1 orl PCON, #0x01 ;PD = power down .else orl PCON, #0x01 ;IDL .endif interrupt_disable .endm
The '?' in ?rand is a special letter which means that rand is getting a randomly generated value unless directly assigned a value for. This random value will change each time the macro is called.
the ' operator is for symbol name concatenation. If ?rand gets the value "10097" than "ext_int_get_input_beginning'rand" will be replaced by the label "ext_int_get_input_beginning10097$".
This is a rather nasty business to write down, but it does work very well and allow complex macros to be written.
That's about all there is to tell about macros in ASXXXX.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.