To demonstrate how to create a subroutine or program in a new environment, the customary and ubiquitous "Hello, World!" is usually presented. We'll abide by that tradition and show how to create a subroutine in MIRTOS that outputs that string to the serial port.
Start by creating a new file, which we can call, very simply, "HelloWorld.Asm". It should contain just the string to print, specified using the ".STRING" Assembler directive, which is NOT case sensitive. To go on a bunny trail for a moment, you may notice that our examples CAPITALIZE Assembler directives (.ASCII, .BALIGN, .BYTE, .WORD, .ORG, .PRINT, .STRING, .WARNING, as a few examples.) This is mostly out of the habit of capitalizing keywords and constants, while using mixed case for variables.
Open a command prompt and enter the single line shown below, followed by Enter. This places the cursor on the next line. Enter Ctrl-Z, which means press the "Control" key then "Z". This usually displays as ^Z as shown below. Unless modified, the F6 key usually also does this. The Control-Z character marks the end of a text file, and in the case of console input, tells the Operating System that file input is complete. Assemble the one-line "program". There should be no errors, but six (6) output files are generated, as shown with the "Dir" command.
Notice that the string entered below ends with "\r\n". Those are the characters Carriage Return (= 13 = 0x0D) and Line Feed (= 10 = 0x0A), respectively. They simply place the cursor at the start the next line after our string is output.
C:\TestDir> Copy CON HelloWorld.Asm .STRING "Hello, World!\r\n" ^Z 1 file(s) copied. C:\TestDir>Assemble HelloWorld C:\TestDir>Dir /B HelloWorld.* HelloWorld.Asm HelloWorld.Elf HelloWorld.Hex HelloWorld.Trm HelloWorld.Sym HelloWorld.Dmp HelloWorld.Ptr C:\TestDir>
Well, that 's a good start, but incomplete, since we've just specified WHAT is to be output, but not given any instruction on HOW to it is to be output. It's similar to an incomplete sentence. It has a subject, but no verb. So, the next thing is to open a text editor and add both a string title, so the Assembler knows how to reference it by name, and the command that will cause it to be output.
Open HelloWorld.Asm in a text editor. We'll place the CALL to PrintFLASHASCIIz, the MIRTOS system function that performs the printing. Upon opening that webpage and scrolling to the bottom, there is a little section called "Dropin: (setup code)", which we'll copy and paste into our file just above the CALL to PrintFLASHASCIIz. After that, place a RETurn statement to return control to the routine which will be calling our Hello, World! subroutine. Our file might well look something like this now:
.STRING "Hello, World!\r\n" ; Setup for the PrintFLASHASCIIz() call: ; Passed: R25:R24 CHAR * szPtrBuffer pointer to the string to print ; Returns: R25:R24 CHAR * szPtrBuffer pointer to the string, unmodified ; R22 BYTE bPrintCount number of BYTEs placed in the print queue ; Alters: R20 and the FLAGs, in addition to R22, R24 and R25 returned CALL APrintFLASHASCIIz RET
But notice that the only requirement is that the R25:R24 Register pair
specify the FLASH address of the string to print. OK, since it is at the
start of the memory, we'll assume that it's FLASH address is 0x0000 and
initialize both R25 and R24 to 0 using the
.STRING "Hello, World!\r\n" ; Setup for the PrintFLASHASCIIz() call: ; Passed: R25:R24 CHAR * szPtrBuffer pointer to the string to print ; Returns: R25:R24 CHAR * szPtrBuffer pointer to the string, unmodified ; R22 BYTE bPrintCount number of BYTEs placed in the print queue ; Alters: R20 and the FLAGs, in addition to R22, R24 and R25 returned LDI R25, 0 LDI R24, 0 CALL PrintFLASHASCIIz RET
Assemble HelloWorld.Asm again. Observe that it generated no errors. Let's examine the listing (.DMP) file. One easy way is to use TYPE.
C:\TestDir> Assemble HelloWorld C:\TestDir>Type HelloWorld.Dmp HelloWorld.Elf: file format elf32-avr Disassembly of section .text: 00000000 <.text>: 0: 48 65 ori r20, 0x58 ; 88 2: 6c 6c ori r22, 0xCC ; 204 4: 6f 2c mov r6, r15 6: 20 57 subi r18, 0x70 ; 112 8: 6f 72 andi r22, 0x2F ; 47 a: 6c 64 ori r22, 0x4C ; 76 c: 21 0d add r18, r1 e: 0a 00 .word 0x000a ; ???? 10: 90 e0 ldi r25, 0x00 ; 0 12: 80 e0 ldi r24, 0x00 ; 0 14: 0e 94 00 00 call 0 ; 0x0 <.text> 18: 08 95 ret C:\TestDir>
Yecch! It's all run together. It isn't real obvious where the string ends and where the code begins. If we assign labels to both the starting locations of the string and the code, it should help to identify each section. Labels are case sensitive, are placed just prior to the start of the string or code they are to identify, and end with a colon. The label can be on the same line as the Assembler directive or instruction. There is one done each way in the changes below.
HelloWorldString: .STRING "Hello, World!\r\n" ; Setup for the PrintFLASHASCIIz() call: ; Passed: R25:R24 CHAR * szPtrBuffer pointer to the string to print ; Returns: R25:R24 CHAR * szPtrBuffer pointer to the string, unmodified ; R22 BYTE bPrintCount number of BYTEs placed in the print queue ; Alters: R20 and the FLAGs, in addition to R22, R24 and R25 returned HelloWorld: LDI R25, 0 LDI R24, 0 CALL PrintFLASHASCIIz RET
Let's go ahead and Assemble and check the listing again.
C:\TestDir> Assemble HelloWorld C:\TestDir>Type HelloWorld.Dmp HelloWorld.Elf: file format elf32-avr Disassembly of section .text: 00000000 <HelloWorldString>: 0: 48 65 ori r20, 0x58 ; 88 2: 6c 6c ori r22, 0xCC ; 204 4: 6f 2c mov r6, r15 6: 20 57 subi r18, 0x70 ; 112 8: 6f 72 andi r22, 0x2F ; 47 a: 6c 64 ori r22, 0x4C ; 76 c: 21 0d add r18, r1 e: 0a 00 .word 0x000a ; ???? 00000010 <HelloWorld>: 10: 90 e0 ldi r25, 0x00 ; 0 12: 80 e0 ldi r24, 0x00 ; 0 14: 0e 94 00 00 call 0 ; 0x0 <HelloWorldString> 18: 08 95 ret C:\TestDir>
That listing is much clearer. Notice, however, that the address of the MIRTOS system function PrintFLASHASCIIz resolved to zero. To remedy that, we'll need to use .INCLUDE to pull in a file which has the addresses of all of the MIRTOS system functions. The 328P, 1284P, and 2560 each have their own file, since the system function addresses differ among them.
Upon examining the Addresses328.Def file, you'll notice that there is no "PrintFLASHASCIIz", but that there is "APrintFLASHASCIIz". In fact, all of the system functions in the file begin with 'A'. The 'A' means "address of" and is prepended to all of the labels by the MakePtr program contained in the Assemble.Bat script. So, we'll simply change PrintFLASHASCIIz to APrintFLASHASCIIz.
Next, if we check our MIRTOS device, there is existing code already at address zero, but none at address 0x0800. The .ORG directive allows us to tell the Assembler to advance its code pointer to an address at or past the next FLASH address that it would use if the .ORG was not present. So, we'll tell it to start at FLASH address 0x0800. That means that the address we placed in R25:R24 must be changed to 0x0800 as well. The High order portion of the address is in R25, so it changes to 8 while the Low order portion in R24 remains unchanged. Here is our file's contents:
.ORG 0x800 .INCLUDE "Addresses328.Def" HelloWorldString: .STRING "Hello, World!\r\n" ; Setup for the PrintFLASHASCIIz() call: ; Passed: R25:R24 CHAR * szPtrBuffer pointer to the string to print ; Returns: R25:R24 CHAR * szPtrBuffer pointer to the string, unmodified ; R22 BYTE bPrintCount number of BYTEs placed in the print queue ; Alters: R20 and the FLAGs, in addition to R22, R24 and R25 returned HelloWorld: LDI R25, 8 LDI R24, 0 CALL APrintFLASHASCIIz RET
Let's Assemble the file again, and examine the listing as before:
C:\TestDir> Assemble HelloWorld C:\TestDir>Type HelloWorld.Dmp HelloWorld.Elf: file format elf32-avr Disassembly of section .text: 00000000 <HelloWorldString-0x800>: ... 00000800 <HelloWorldString>: 800: 48 65 ori r20, 0x58 ; 88 802: 6c 6c ori r22, 0xCC ; 204 804: 6f 2c mov r6, r15 806: 20 57 subi r18, 0x70 ; 112 808: 6f 72 andi r22, 0x2F ; 47 80a: 6c 64 ori r22, 0x4C ; 76 80c: 21 0d add r18, r1 80e: 0a 00 .word 0x000a ; ???? 00000810 <HelloWorld>: 810: 98 e0 ldi r25, 0x08 ; 8 812: 80 e0 ldi r24, 0x00 ; 0 814: 0e 94 49 30 call 0x6092 ; 0x6092 <APrintASCIIz> 818: 08 95 ret C:\TestDir>
No errors and the listing has three (3) code values correctly resolved. Next, we'll load and test our routine in device memory. HelloWorld.Trm has the loadable code in iHex format.
> DF+ 0x800 64 FLASH (program memory) contents: 0800: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ 0810: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ 0820: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ 0830: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ >; Use the '$' first character to enter Manager mode m>:1008000048656C6C6F2C20576F726C64210D0A0068 m>:0A08100098E080E00E94493008954E m>:00000001FF m>DF+ 0x800 64 FLASH (program memory) contents: 0800: 48 65 6C 6C 6F 2C 20 57-6F 72 6C 64 21 0D 0A 00 Hello, World!... 0810: 98 E0 80 E0 0E 94 49 30-08 95 FF FF FF FF FF FF ......I0........ 0820: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ 0830: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ m>@ 0x810 ; Call the subroutine from the command line Hello, World! m>
Although our Hello, World! subroutine works as expected, it would be better if the subroutine started at the known address of 0x0800 rather than having to determine from the listing that it starts at 0x0810. Also, it would be much better to have the program resolve and insert the string's address into R25 and R24 rather than having to load each with a numeric constant.
The first part is easy; just move the string to the bottom. Loading R25:R24 takes two steps: the first is to .INCLUDE the .PTR file, which has been built during each Assembly. It, like Addresses328.Def, also prepends 'A' to set its label address constants. The second step is to change the LDI to load the High 8-BIT portion of that 16-BIT constant into R25 using "constant >> 8" which means "shift the constant right 8 bit positions" and "constant & 0xFF" which means "leave only the low 8 BITs of the constant." For the 1284P and 2560, there is a third line to set R26. Our file revisions should leave it something like this:
.ORG 0x800 .INCLUDE "Addresses328.Def" .INCLUDE "HelloWorld.Ptr" ; Setup for the PrintFLASHASCIIz() call: ; Passed: R25:R24 CHAR * szPtrBuffer pointer to the string to print ; Returns: R25:R24 CHAR * szPtrBuffer pointer to the string, unmodified ; R22 BYTE bPrintCount number of BYTEs placed in the print queue ; Alters: R20 and the FLAGs, in addition to R22, R24 and R25 returned HelloWorld: LDI R25, AHelloWorldString >> 8 LDI R24, AHelloWorldString & 0xFF CALL APrintFLASHASCIIz RET HelloWorldString: .STRING "Hello, World!\r\n"
This time, we must Assemble twice. The first time both checks for any errors and updates the .PTR file, since the addresses of both the subroutine entry point and string changed. The second time applies the constants to the program. Here is the result:
C:\TestDir> Assemble HelloWorld C:\TestDir>Assemble HelloWorld C:\TestDir>Type HelloWorld.Dmp HelloWorld.Elf: file format elf32-avr Disassembly of section .text: 00000000 <HelloWorld-0x800>: ... 00000800 <HelloWorld>: 800: 98 e0 ldi r25, 0x08 ; 8 802: 8a e0 ldi r24, 0x0A ; 10 804: 0e 94 49 30 call 0x6092 ; 0x6092 <APrintASCIIz> 808: 08 95 ret 0000080a <HelloWorldString>: 80a: 48 65 ori r20, 0x58 ; 88 80c: 6c 6c ori r22, 0xCC ; 204 80e: 6f 2c mov r6, r15 810: 20 57 subi r18, 0x70 ; 112 812: 6f 72 andi r22, 0x2F ; 47 814: 6c 64 ori r22, 0x4C ; 76 816: 21 0d add r18, r1 818: 0a 00 .word 0x000a ; ???? C:\TestDir>
OK, everything is in place. R25 and R24 are being loaded correctly and the MIRTOS PrintFLASHASCIIz function address is correct. Let's reload the iHex image into our device and test it again:
m> :1008000098E08AE00E944930089548656C6C6F2C2E m>:0A08100020576F726C64210D0A007E m>:00000001FF m>DF+ 0x800 64 FLASH (program memory) contents: 0800: 98 E0 8A E0 0E 94 49 30-08 95 48 65 6C 6C 6F 2C ......I0..Hello, 0810: 20 57 6F 72 6C 64 21 0D-0A 00 FF FF FF FF FF FF World!......... 0820: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ 0830: FF FF FF FF FF FF FF FF-FF FF FF FF FF FF FF FF ................ m>@ 0x800 ; Call it again from the command line Hello, World! m>
We could get rid of the setup notes, but it's OK this way as well. There are a couple ways of coding to eliminate the need to manually .INCLUDE Addresses328.Def, or Addresses1284.Def, or Addresses2560.Def, however. The first is by using the subroutine index. This would take replacing two lines and adding two, as shown below:
The second way is with a sequence of .IF conditional assembly statements. It is shown in the multiple ".IF PROCESSOR == ..." lines in the Sequencer and Secret Password examples.
SoftVector: ; MIRTOS system routine at FLASH address 0 .ORG 0x800 .INCLUDE "Indices.Def" ; Replaced "Addresses328" .INCLUDE "HelloWorld.Ptr" ; Setup for the PrintFLASHASCIIz() call: ; Passed: R25:R24 CHAR * szPtrBuffer pointer to the string to print ; Returns: R25:R24 CHAR * szPtrBuffer pointer to the string, unmodified ; R22 BYTE bPrintCount number of BYTEs placed in the print queue ; Alters: R20 and the FLAGs, in addition to R22, R24 and R25 returned HelloWorld: LDI R25, AHelloWorldString >> 8 LDI R24, AHelloWorldString & 0xFF LDI R30, I_PrintFLASHASCIIz ; This is the line added CALL ASoftVector ; This invokes the indexed call RET HelloWorldString: .STRING "Hello, World!\r\n"
Finally, our subroutine's code could be trimmed by 2 BYTEs by changing the end of it so that the PrintFLASHASCIIz routine returns directly to the caller. This is done by simply changing the CALL and immediate RET to a JMP. Everything else remains the same.
HelloWorld: LDI R25, AHelloWorldString >> 8 LDI R24, AHelloWorldString & 0xFF LDI R30, I_PrintFLASHASCIIz ; This is the line added JMP ASoftVector ; This invokes the indexed jump