Morse Code in Assembly Language
In this sample assembly language solution, our tutor is demonstrating the creation of Morse Code. Here, students are required to make use of STDIN (Standard Input) data stream which is assigned to the keyboard of the computer. Data is retrieved from STDIN buffer using the read syscall. Here data is used from STDIN to convert Characters and Numbers into Morse Code.
SOLUTION : –
.global _start
_start: @ we should cycle back to the start until
@ the user tells us they are done
LDR r1, =prompt @ place the address string in r1
BL _sPrint @ use library function to display string
/* Step 1 read stream into memory
*
* Get input from the user using the _sInput function then
* print a newline before displaying morse code of input.
*/
LDR r1, =userInput @ point to the space allocated for input
MOV r2, #40 @ set the limit of character to read
BL _sInput @ get the input
LDR r1, =newline @ move to the next line (make it pretty)
BL _sPrint @ print a ‘\n’
/* Step 2 prepare the input for parsing
*
* Our morse code generator will ignore all characters except letters,
* numbers, and punctuation (which simply prints a newline).
* To make our job easier we will convert all letters to capitals.
*
* Additionally we will check if the user entered a “/q” to indicate
* they would like to quit the program.
*
* NOTE: Comment the following code to demonstrat that you understand it.
*
*/
LDR r1, =userInput @ point to the input string
BL _strUpper @ convert input string to uppercase
LDR r1, =userInput @ point to input string
LDRB r0, [r1], #1 @ load first character from input, advance pointer
CMP r0, #92 @ see if the first character is a slash ‘\’
BNE morseCoder @ if not, go to morse coder
LDRB r0, [r1] @ else, load second character from input string
CMP r0, #81 @ see if the second character is a ‘Q’
BEQ _end @ if the string was \Q, end the program
/* Step 3 stream parser
*
* This code examines each character in our prepared input stream
* and does the following:
* – prints letters as morse code using a lookup table
* – prints numbers as morse code using a lookup table
* – prints periods and commas as newline characters
* – prints a space for a space character
* – ignores all other characters.
*
* NOTE: You will write the parsing engine here. It needs to move through the
* user input and perform the above actions.
*
*/
morseCoder:
LDR r1, =userInput @ load in the address of user input
@ Your code goes here!
processChar:
LDRB r0, [r1], #1 @ load a character from the string
CMP r0, #0 @ see if we reached the end of string
BEQ endOfInput @ if end of string, end input
PUSH {r1} @ save r1 in the stack
CMP r0, #46 @ see if it’s a period
BEQ printNL @ if period, print newline
CMP r0, #44 @ see if it’s a comma
BEQ printNL @ if comma, print newline
CMP r0, #32 @ see if it’s a space
BEQ printSpace @ if space, print spaces
CMP r0, #65 @ see if it’s above or equal to letter ‘A’
BLT testDigit @ if not a letter, test if it’s a digit
CMP r0, #90 @ see if it’s below or equal to letter ‘Z’
BGT nextChar @ if not a letter, skip to next char
printLetter:
SUB r0, r0, #65 @ convert ascii ‘A’ to ‘Z’ to a numeric value 0 to 25
MOV r1, #6 @ load number 6 in r1 for multiplication
MUL r0, r1, r0 @ mulyiply value*6
LDR r1, =letters @ point ot start of letter table
ADD r1, r1, r0 @ add value*6 to point to letter entry in table
BL _sPrint @ print morse conversion
BAL nextChar @ repeat to process next character
testDigit:
CMP r0, #48 @ see if it’s above or equal to digit ‘0’
BLT nextChar @ if not a digit, skip to next char
CMP r0, #57 @ see if it’s below or equal to digit ‘9’
BGT nextChar @ if not a digit, skip to next char
printDigit:
SUB r0, r0, #48 @ convert ascii ‘0’ to ‘9’ to a numeric value
MOV r1, #7 @ load number 7 in r1 for multiplication
MUL r0, r1, r0 @ mulyiply value*7
LDR r1, =numbers @ point ot start of number table
ADD r1, r1, r0 @ add value*7 to point to digit entry in table
BL _sPrint @ print morse conversion
BAL nextChar @ repeat to process next character
printNL:
LDR r1, =newline @ point to string to print
BL _sPrint @ print a ‘\n’
BAL nextChar @ repeat to process next character
printSpace:
LDR r1, =space @ point to string to print
BL _sPrint @ print a series of spaces
nextChar:
POP {r1} @ restore value of r1
BAL processChar @ repeat to process next character
endOfInput:
LDR r1, =newline @ move to the next line (make it pretty)
BL _sPrint @ print a ‘\n’
BAL _start @ do not end the program unless user types \q
_end:
MOV r7, #1 @ set EXIT syscall
SWI 0 @ execute syscall
.data
prompt: .asciz “Please enter text to translate\nMC> ”
newline: .asciz “\n”
space: .asciz ” ”
userInput: .space 44
.align
@ Note that the following are aligned so that each letter takes up
@ exactly six bytes, therefore letters+6bytes gives us the second letter
@ and so on.
letters: .ascii “.- ” @ A
.byte 0x00, 0x00, 0x00
.ascii “-… ” @ B
.byte 0x00
.ascii “-.-. ” @ C
.byte 0x00
.ascii “-.. ” @ D
.byte 0x00, 0x00
.ascii “. ” @ E
.byte 0x00, 0x00, 0x00, 0x00
.ascii “..-. ” @ F
.byte 0x00
.ascii “–. ” @ G
.byte 0x00, 0x00
.ascii “…. ” @ H
.byte 0x00
.ascii “.. ” @ I
.byte 0x00, 0x00, 0x00
.ascii “.— ” @ J
.byte 0x00
.ascii “-.- ” @ K
.byte 0x00, 0x00
.ascii “.-.. ” @ L
.byte 0x00
.ascii “– ” @ M
.byte 0x00, 0x00, 0x00
.ascii “-. ” @ N
.byte 0x00, 0x00, 0x00
.ascii “— ” @ O
.byte 0x00, 0x00
.ascii “.–. ” @ P
.byte 0x00
.ascii “–.- ” @ Q
.byte 0x00
.ascii “.-. ” @ R
.byte 0x00, 0x00
.ascii “… ” @ S
.byte 0x00, 0x00
.ascii “- ” @ T
.byte 0x00, 0x00, 0x00, 0x00
.ascii “..- ” @ U
.byte 0x00, 0x00
.ascii “…- ” @ V
.byte 0x00
.ascii “.– ” @ W
.byte 0x00, 0x00
.ascii “-..- ” @ X
.byte 0x00
.ascii “-.– ” @ Y
.byte 0x00
.asciz “–.. ” @ Z
.align
@ Note that the following data is for numbers (0-9) and you can choose
@ to use the data as is or simply produce the numbers on the fly
@ using the relationship between the number and the dots and dashes.
@ Also note that in ASCII the 0 comes first, in morse code 0 is last.
@ How do you account for this?
@
numbers: .asciz “—– “, “.—- “, “..— “, “…– “, “….- ”
.asciz “….. “, “-…. “, “–… “, “—.. “, “—-. ”
@ Declarations of functions in library
.equ STDOUT, 1 @ set a constant for the standard output
.equ STDIN, 0 @ set a constant for the standard input
.global _strLen @ returns the length of an array in bytes
.global _strCat @ concatonates two bchar arrays
.global _strUpper @ replaces all lowercase letters with uncials
.global _sPrint @ prints a string onto the STDOUT
.global _sInput @ takes in a string from the STDIN flushing buffer
/* String Length Function
*
* This function takes the address of a null terminated array of bytes
* in register 1 and returns the number of characters in the array in
* register 2.
*
*/
_strLen:
MOV r2, #0 @ start the element counter at 0
findEnd:
LDRB r0, [r1], #1 @ r1 contains the address of the byte to
@ load into r0, increment the address in r1
ADD r2, r2, #1 @ increment the byte counter
CMP r0, #0 @ set the status register by comparing the
@ value in r0 with null (#0)
BNE findEnd @ if r0 is not null continue looping
SUB r2, r2, #1 @ fix that we counted the null character
MOV pc, lr @ restore control to calling code
/* String to Upper Case Function
*
* Takes the addresses of a null terminated string and converts any
* lower case letters to their uncial (upper case) equivalent.
*
*/
_strUpper:
LDRB r0, [r1] @ load the next byte of array 1
CMP r0, #97 @ compare byte to ‘a’
BLT writeChar @ preserve non-lowercase letter
CMP r0, #122 @ compare to ‘z’
SUBLE r0, r0, #32 @ change to uppercase
writeChar:
STRB r0, [r1], #1 @ if not end of array 1 store value
CMP r0, #0 @ look for end of string
BNE _strUpper @ loop until end of string
MOV pc, lr @ return to calling environment
/* String Printer
*
* Takes the address of a null terminated string array and outputs
* that string to STDOUT (console). String address in register 1
*
*/
_sPrint:
MOV r0, #STDOUT @ set the output to the console
PUSH {r0-r1, lr} @ save the working registers and LR
BL _strLen @ set register 2 to string length
POP {r0-r1, lr} @ restore working registers and LR
MOV r7, #4 @ set the syscall to WRITE
SWI 0 @ make the syscall
MOV pc, lr @ return to calling environment
/* String Inputer
*
* Takes the address for a string in register 1 and the number of
* characters to read in register 2. Makes the syscall to READ from
* STDIN and then flushes any remaining characters from the buffer.
*
*/
_sInput:
PUSH {r1-r2, lr} @ preserve values for function
MOV r0, #STDIN @ set the input to the console
MOV r7, #3 @ set the syscall to READ
SWI 0 @ make the syscall
POP {r1-r2, lr} @ restore values sent to function
sInputStrStart:
CMP r2, #0 @ memory bounds checker
MOVLE r0, #0 @ if no more characters put null in R1
STRLEB r0, [r1] @ if no more characters store null in array
BLE sInputEnd @ if no more characters exit
LDRB r0, [r1] @ load in the next byte of sent array
CMP r0, #10 @ check for newline character
MOVEQ r0, #0 @ if end put null charater in R1
STRB r0, [r1], #1 @ write byte out at end of array 1
SUB r2, r2, #1 @ decrement letter counter
BNE sInputStrStart @ loop until newline
sInputEnd:
MOV pc, lr @ return to calling environment