I hope you don’t mind, but I extracted this information that was buried in another post as to make it a lot easier for people (especially myself) to find it:
For the fun of it I am trying out converting my timerA overflow interrupt to be processed by assembly language. When I try running my code, my LCD that I have stays stuck on showing “LCD TEST MODE…” “BPI-216v1 S.E.E.”
With the Basic version of the timer interrupt, my code apears to work. My Hitachi assembly language skills are very green so let me know if I am screwing it up. Below is both my Basic version of the timer interrupt as well as the Assembly language version.
iOverflowTime var sword
handle_timera
iOverflowTime = iOverflowTime + 256
resume
HANDLE_TIMERA_ASM
mov.l er1,@-er7
mov.w @IOVERFLOWTIME:16,r1
inc r1h
mov.w r1, @IOVERFLOWTIME:16
mov @er7+,er1
rte
Note: I tried push.w r1/pop.w r1 insteat of trying to use ER7. Same results.
Suggestions?
woooooooooooossssssssshhhhhhhhhoooooooo
Did you see that?
This whole thread just went way over my head. LOL
When using Basic interrupts(eg ONINTERRUPT handlers) do not try pushing/poping registers. It is done for you.
Why is iOverflowTime a signed word?
Also your inc command is incorrect. it should either be
inc.b r0l ;increment the low byte of r0
or
inc.w r0 ;increment the word value in r0
Your load and save are fine(eg the mov @IOVERFLOWTIME)
“rte” can not be used in a Basic interrupt. You must use “resume”. On entry to a Basic interrupt all 8 registers are pushed on the stack. If you don’t “resume” those values will never get poped off the stack and your program wil eventually crash, hard. “resume” pops those values off the stack and "rte"s on its own.
There is an Assembly interrupt. You use ONASMINTERRUPT instead of ONINTERRUPT but it can only be used with the real interrupt sources(eg the interrupt vectors listed in the Hitachi hardware manual).For example you would write:
ONASMINTERRUPT WKPINT,myhandler
On entry to the handler you would have to figure out which of the 6 WKP ints actually triggered the WKPINT.
The handler should look something like this:
BEGINASMSUB
myhandler
;push registers you will be using
;check interrupt flag to determine which WKP triggered this int
;clear interrupt flag
;process your interrupt here
;pop registers you upushed at the begining
rte
ENDASMSUB
I don’t know if the BEGIN/ENDASMSUB compiler directives are in 8.0.1.3 but they are in the new release(out Monday). They tell the compiler to assume all code between them is assembly code and not add any “basic to assembly” or “assembly to basic” entry code.
The only real advantage to use the ONASMINTERRUPT is you can specifiy which registers you push/pop/use instead of always pushing all 8 and pop all 8. That can save a lot of clockcycles on a simple interrupt(each push/pop takes 10 clock cycles).
Thanks for the input. Yes, I should probably convert the SWORD to be a word for the overflow. It should not make much difference as I don’t expect the values I will need will exceed 32767 before I reset it. (Each of the 6 pulse widths appear to have about a maximum of maybe 1000.
In my code, when I am using the timer interrupt using Basic to process it, I had
ONINTERRUPT TIMERAINT,handle_timera
When I was trying the Asm interrupt, I had:
ONASMINTERRUPT TIMERAINT, HANDLE_TIMERA_ASM
Can the inc command be:
inc.b r1h
Alternatively I could probably do something like:
add.w #100:16,r1
Not sure from documentation yet if the # number defaults to hex, or decimal, so may need to be #256.
Are you saying that on the processing of the ONASMINTERRUPT, I should not use the RTE and push registers?
I added the BEGINASMSUB and ENDASMSUB directives around my code and it still compiled. Not sure if it processed them as labels or as the actual handler.
Should I wait for your updated release to try this again?
Also do you think others may be interested in this or should we take it off line?
Thanks again
Just in case some one is interested, I think I have it working now. It helps to have the BEGINASMSUB and ENDASMSUB and I needed to clear the interrupt has happened from the IRR1 register. The current code looks like:
BEGINASMSUB
HANDLE_TIMERA_ASM
push.l er1 ; first save away ER1 as we will mess with it.
bclr #6,@IRR1:8 ; clear the cooresponding bit in the interrupt pending mask
mov.w @IOVERFLOWTIME:16,r1 ; Add 256 to our counter
add.w #0x100:16,r1
mov.w r1, @IOVERFLOWTIME:16
pop.l er1
rte
ENDASMSUB
I may try to change my add 0x100 to simply increment the upper byte as it will speed it up slightly and decrease the code by two bytes.
That is correct. Timera only has one interrupt type so it is the same in Basic interrupts and Asm interrupts.
Yes if you are trying to increment by 0x100. One error is see. The “:16” is not used with constants(eg #100). “:16” is used with addresses to tell the assembler this is a 16bit address. “:8” is used with 8bit addresses but those only apply to the high 256 bytes of ram(primarily special registers and ports/pins).
In Asm interrupts you do have to use rte. In Basic interrupts you have to use resume. In Asm interrupts you have to do all the work(clear int flags push/pop registers and rte.
I’m pretty sure these directives are not in 8.0.1.3 or earlier. I added them fairly recently for my own use(specificly for ONASMINTERRUPT handlers).
Yes.
Better to assume they will be interested. If they aren’t then can ignore the thread.
Just so everone is clear, BEGINASMSUB and ENDASMSUB tell the compiler there is no need for the extra code to switch from processing tokens(eg basic) to running actual machine code(asm). The rules the compiler uses to add the switch code is:
- Basic code followed by Asm code add switch to machine mode before asm code.
- Asm code followed by Basic code add switch to token mode before Basic code
This is why asm code can not jump into basic code and basic code cannot jump into asm code. The compiler has no way of know you are trying to do that so no switch to the correct mode can be added.
Examples:
[code]temp var byte
temp = 0
main
;
mov.l @TEMP:16,er0
inc.l #1,er0
mov.l er0,@TEMP:16
;<asm to basic mode switch added here
goto main[/code]
In the above code the goto main jumps to main, so you have basic code jumping into basic code. If however you changed the code to this:
[code]temp var byte
temp = 0
main
;
mov.l @TEMP:16,er0
inc.l #1,er0
mov.l er0,@TEMP:16
bra MAIN:8[/code]
You end up with asm code jumping into basic code which will cause unexpected results.
If you change the code to this:
[code]temp var byte
temp = 0
;<basic to asm mode switch is added here>
nop
main
mov.l @TEMP:16,er0
inc.l #1,er0
mov.l er0,@TEMP:16
bra MAIN:8[/code]
You now have asm code jumping to asm code which will function as expected. This also illustrates I need to add explicit mode switch directives(eg BASICTOASM and ASMTOBASIC) so unnecessary commands are not required(eg the NOP). I’ll see if I can get this in the new release.
BEGINASMSUB and ENDASMSUB are for use when you want to define an asm subroutine(or interrupt handler) without worrying about the compiler adding unexpected mode switch code. Examples follow.
[code]ONASMINTERRUPT TimeraInt,inthandler
main
gosub test
goto main
inthandler
;
rte
test
;
return[/code]
This code shows the compiler adding unecessary mode switches which will cause problems. Add the BEGINASMSUB and ENDASMSUB and this problem will go away.
[code]ONASMINTERRUPT TimeraInt,inthandler
main
gosub test
goto main
BEGINASMSUB
inthandler
rte
ENDASMSUB
test
return[/code]
Note that any BEGINASMSUB defined subroutine can only be called by asm code(eg an asm branch(bsr,bra,jmp etc…) or ONASMINTERRUPT handler.
Sort-of a side question, but suppose you have inline assembly code and you have an invalid opcode or the wrong register name. When you compile it, you might receive an error like:
c:\users\kurt\desktop\lynxmotion\brat acidtechs with experimental hservo.asm: Assembler messages:
c:\users\kurt\desktop\lynxmotion\brat acidtechs with experimental hservo.asm:2475: Error: invalid operands
C:\PROGRA~1\BASICM~1\BASICA~2\bin\ld.exe : cannot open c:\users\kurt\desktop\lynxmotion\brat acidtechs with experimental hservo.o: No such file or directory
The question is how do you find which line the error is on in your .BAS file? I don’t believe the .ASM file is preserved after the compile.
Currently the ASM file is not preserved. I’ll fix this in the next release. For now you’ll have to be carefull.
Suppose in assembly language I would like to change the Atom Pros P12 to low in order to turn on an LED on the BB2. Looking at the Atom Pro, it looks like: P12 is both (P84/FTIOD) and (P10/TMOW). The question is there a preference on which of these IO pins to manipulate to the equivelent of: low p12.
Something like:
For P84
bset.b #4,PCR8
bclr.b #4,PDR8
or P10
bclr.b #0,PMR1 ; Not sure if I need to do this
bset.b #0,PCR1
bclr.b #0,PDR1
Do I need to do something to tristate the one I am not using?
Thanks
You only have to worry about P10/TMOW if you are using the TMOW hardware. Otherwise that pin is tristated. You only have to deal with P84.
Hi Nathan,
I have some C versions of the hardware I2C working reasonably well enough to now convert it to ASM and integrate it with my Rover Atom Pro basic/asm code. The question is how to integrate this in a reasonable way to have them callable from both Basic and Asm.
Example: Suppose I have a function, that in c has the prototype:
extern int I2C_MasterWriteBuffer(unsigned char bAddr, unsigned char *pbBuff, int cb)
Would it be best to have basic at the begining like:
bAddr var byte
pBuff var ???
cb var int
I2C_WriteMasterBuffer[bAddr, pbuff, cb]
(main asm code)
return ; basic return does mode switch..
First question: How do you pass a buffer(array) to a function as a parameter? I wondered about this earlier with my basic LCD functions and ended up simply having a fixed buffer that the functions use. Not great but would get the job done.
Suggestions on if I want to call this from another ASM function. For example I may have another ASM function named something like:
I2C_ReadMultipleRegisters which wants to call the MasterRead and Master Write functions. Are there semi-standards on how to call through or calling conventions? I suppose I could do something like:
bAddr var byte
pBuff var ???
cb var int
I2C_WriteMasterBuffer[bAddr, pbuff, cb]
mov.l @bAddr:16, er0
mov.l @pbuff:16, er1
mov.l @cb:16, er2
mov.l #0, er3
Internal_WriteMasterBuffer:
(main asm code)
cmp.l #0, er3
beq _I2CWMB_BasicReturn
ret
_I2CWMB_BasicReturn:
return ; basic return does mode switch..
I could then have my other ASM functions simply load up the parameters into the registers with ER3 being set to non-zero and do a jsr.
Maybe It may turn out the other wrapper functions are best left in basic as they are not really touching the hardware or the like and are rather simple: for example the current C version of I2C_ReadRegister function is simply:
int I2C_ReadRegister(unsigned char bAddr, unsigned char bReg, unsigned char *pbVal)
{
int iReturn;
iReturn = I2C_MasterWriteBuffer(bAddr, &bReg, 1);
if (iReturn == I2C_ERROR_OK)
iReturn = I2C_MasterReadBuffer(bAddr, pbVal, 1);
return iReturn;
}
Suggestions?
P.S. - I am not sure if I should start using a different thread for questions about asm and basic and then this thread should be edited to only leave the condensed information. Or maybe that is best for the WIKI?
I started porting my C code to asm and so far I have found that it is assuming a H8-3664 family of processor and not the 3694, which is used on the Atom Pro 28. So for example the IO register ICCR is defined, but ICCR1 is not. Will see if I can hand generate the symbols manually. Probably should have the compiler choose the symbols to include based on which atom pro is selected.
Kurt