I suppose it would be easier if i gave an example of my code. Its the same code im trying to write with keys 1-8 playing musical notes. Someone recommended me to use jmp commands to a 'mainroutine' to reduce the size of code, however i cant always get the program to jump back to the original instruction without being in endless loops. So what i want to do is use subroutines, then the prog will return to the next instruction in the main block.
Heres a sample:
.model large
.stack 100h
.data
prompt DB 'Press 1-8 to play notes! Press x to exit!',13,10,'$'
.code
mov ax,@data
mov ds,ax
jmp rowrow
mainroutine proc
pop ax
out 42h,al
pop ax ;popping them back off again
out 42h,al ;and loading into port 42h
in al,61h
or al,00000011b
out 61h,al ;turn on speaker
RET
rowrow:
mov al,10110110b
out 43h,al
mov al,97h
push ax ;push LSB for notes reverse order
mov al,0ah
push ax ;push MSB for the notes
CALL mainroutine
CALL delay3
CALL turnoffspeaker
**
**
code
**
END
This doesnt work - you may see it as obvious but i am just a beginner. Do you have to push the address of the instruction you want to return to? if so im pushing other values onto ax that i need, is there another way i can do this?
I put the 'mainroutine' subroutine at the beginning only because i seen that is what you done, is this right?.
I am not getting any errors when i assemble but when i run the program i get booted out of dos and i need to restart my PC.
What does 'mov ax,bx' do?, whats its function here? since i see i have missed it out.
If your wondering how im getting the values for the notes it's the clock frequency, 1193180Hz divided by the frequency of the note required, i.e. for concert A, where all musical scales are taken from, 1193180/440 = 2711d = A97h, this value is loaded into counter 2 of the PIT 8253/8254 byte at a time, then the speaker is switched on by oring 00000011 with bit 0 and 1 of port B (61h), since we dont want to change any of the other bits.
Any advice you could give on the above would be greatly appreciated.
Hm, not easy to explain this.
In assembler you are largely responsible for everything the processor does. If you write a procedure using PROC, all that's really happening is you are telling the assembler to remember its name, so this location can be called by name later.
So a piece of code starting
MyName PROC NEAR
.....
and ending
MyName ENDP
is simply a chunk of code. The assembler won't even put the return at the end of it (well, my ancient copy of tasm doesn't, anyway). This isn't a weakness of the assembler. It's what assembler is all about: you only get what you write, and you can write whatever you want.
To make something function as a subroutine, it needs to end by returning to the code that called it. To do this, you put ret at the end of your procedure. This will be interpreted as ret near or ret far according to whether you defined a PROC NEAR or PROC FAR. But the assembler won't object if you actually override it and specify retf at the end of a near procedure!
Ret returns to the address put on the stack, which is an offset in all cases, and also a segment if the call was far. So retf takes both an offset and a segment from the stack, while retn takes just an offset. The point of a call instruction is to put the appropriate offset and segment on the stack before it does a jump.
You can still call a procedure using a jump rather than a call, but a jump, unlike call, doesn't save the return address on the stack. Therefore at the end of the procedure there will be a horrible crash unless you explicitly put some reasonable address on the stack. There are odd occasions you might do this, for instance to make a procedure "return" to some other place in your code, not the instruction following the jump.
You are right to think about structure in your code. In my view it is even more important in assembler, which is basically hard to read, than in high level languages, which shouldn't be! But I'd try to keep things really simple in your procedure structure.
By the way, I'm not really very sure about your prompt-printing bit. Are you sure you're addressing the line of text correctly?
On procedures, there are all sorts of more complicated structures that tend to appear at the begining and end of procedures, especially when created by higher-level languages. These are things like
push bp
mov bp, sp
etc., which are involved in setting up bp so that it can be used to access variables pushed onto the stack.
Thanx lionelhill, i managed to get it working, heres what i done:
.model large
.stack 100h
.data
prompt DB 'Press 1-9 to play notes in the scale of D!',13,10,'$'
exit DB 'Press x to exit!',13,10,'$'
.code
mov ax,@data
mov ds,ax
jmp firstnote
public mainroutine
mainroutine proc near
pop bx
pop ax
out 42h,al
pop ax
out 42h,al
in al,61h
or al,00000011b
out 61h,al
push bx
RET
mainroutine endp
firstnote:
mov al,10110110b
out 43h,al
mov al,011h
push ax
mov al,0cah
push ax
CALL mainroutine
CALL delay1
CALL offspeak
CALL pause
"
"
rest of code
"
"
END
obviously i have more procedures in the code than i have highlighted here.
My problem was that i was passing data between main and the procedure. I was storing them onto the stack for this. But when you call the procedure the return address gets stored on the top of the stack which would then muck up my data that i was passing. So to combat this, when i first enter the procedure i popped the return address into bx, since bx is not being used, then just before the RET command i pushed it back onto the top of the stack and this restored the return address, woo hoo!
Regarding your statement about my prompt, i never included the command in my example code that displays the code, which goes like;
mov dx, OFFSET prompt
mov ah,9
int 21h
mov dx, OFFSET exit
mov ah,9
int 21h
And they display OK, thanx for your concern and advice.
My problem was that i was passing data between main and the procedure. I was storing them onto the stack for this. But when you call the procedure the return address gets stored on the top of the stack which would then muck up my data that i was passing ....
At first I already figured that something wrong with the stack, but I didn't know how to explain cause I didn't know the exact result. That's why I asked you to learn debug. I guess you already tried it, aren't you ? Debugger will you best friend in asembly world
Now since you have found out, lets try this one (I'm trying to avoid push & pop by using register only)
mainroutine proc near
mov ax, dx
out 42h,al
shr ax,8
out 42h,al
in al,61h
or al,00000011b
out 61h,al
RET
mainroutine endp
Aircon is right, you can avoid pushing and popping by using the registers.
Alternatively if you cannot get all the data you need in registers, you can use the stack as you suggest, but there is a much better way to access things you've put on the stack, and that is to use bp.
The way you do this, is at the begining of your subroutine you set bp to be equal to the stack pointer. Conventionally you push bp first in case you ever find yourself working in nested subroutines, where the subroutine above where you are at the moment might itself be using bp in the same way. Hence a subroutine includes at its start
push bp
mov bp, sp
You'll find that the stack now contains:
+08: a word you pushed
+06: segment part of return address
+04: offset part of return address
+02: old bp
I might have slipped 2 here. Do check. I always get confused whether the stack pointer is decremented before or after pushing.... sorry I'm a bit thick.
Or something similar depending on whether you were making near or far calls.
Hence you can now get at your passed (pushed!) parameters with statements like
mov ax, ss:[bp+08]
bp defaults to using stack segment for exactly this purpose.
If you need more local storage space internally to your subroutine, you can even extend this by subtracting a lump from sp in order to reserve a little local stack space.
push bp
mov bp, sp
sub sp, 06
This space can now be addressed as ss:[bp-6] etc....
Either way, at the end of your subroutine you must restore the old bp and get the sp back to where the return address is before doing the return. You can do this simply by doing
mov sp, bp
pop bp
ret however....
The final ret instruction can free up space on the stack taken by your pushed variables.
I'm not advocating imitating a compiler (otherwise there wouldn't be a lot of point in writing assembler!) but this sort of local stack frame structure is very powerful, and worth knowing about. If you're bursting into assembler to make a recursive routine more efficient, though, try to avoid it.
Just a note on "enter" and "leave". These are Intel's realisation just how important local stack frames are, and do exactly what I've described above. However, they're slower than the individual instructions, so not recommended.
yes, the whole purpose of the bp register is to access parameters passed to a called routine.
however, recent complexity of routines are requiring alot more paramters to be passed. have you looked at the enter and leave opcodes? these opcodes are specifically used to pass a block of paramters to called routines.
anyhow, i beleive the favoured method as with the modern day BIOS is to use a paramter table and only pass the pointer of this table to the routine such as DS:SI - of corse there are many methods of passing parameters.
you just have to decide which you prefer
straiph
"There are 10 types of people in this world, those who know binary and those who don't!"
I think enter and leave simply do the same as push bp, mov sp, bp and all that stuff, but with less code, and a little more execution time. But I can't swear to how fast they run on good up-to-date processors. I'm still kicking around in early pentium world on that one.
Yes, passing a parameter by reference, rather than by value, is a good time-saver if you've a lot to pass. Straiph is quite right about that one. If you have several complex blocks of values you want to pass, you can of course push their addresses and retrieve them with les si, ss:[bp+something], or whatever.
And in assembler there's still a good case for the odd global variable! You can use that from anywhere without passing anything, so it may not make for easy-read code, but there's nothing quicker.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.