Before you attend this weeks lab, make sure:
- you can load & store data to/from memory using
ldr
&str
with different addressing modes - youve completed the Week 6/7 blinky lab
In this weeks lab you will:
- use a simple data structure for storing dot-dash morse code codepoints
- store multiple morse codepoints in an array-like structure in memory
- write a program to read an ASCII string and output it as morse code blinks
Introduction
Morse code is a simple communication protocol which uses dots and dashes to represent the letters of the alphabet.
The dots and dashes can be represented in different waysas dots or lines on a page, as short or long beeps coming out of a speaker, or hidden in a song on the radio to reach kidnap victims, or as short or long blinks of the LED light on your discoboard. In this lab content the morse code will be represented visually using a sequence of .
(dot) and _
(dash) characters, but by the end of the lab youll be sending morse code signals by blinking the red LED on your discoboard in short (dot) and long (dash) bursts. Heres the full morse alphabet (courtesy of Wikipedia)
Discuss with your neighbourhave you ever seen (or even used!) morse code before? Where/when?
Exercise 1: a LED utility library
Remember the setup code you wrote in the blinky lab? Now that youve got some more skills with functions under your belt, you can probably imagine how you might package up a lot of that load-twiddle-store stuff into functions to make the code a bit more readable (this was actually proposed as an extension exercise at the end of the last lab).
In fact, Ben Swift (another lecturer at ANU) already wrote this library for you (because hes great 😊). You can find it in the lib/led.S
file after you fork & clone the lab 8 template from GitLab.
Have a read through the code in led.S
you should now be at the stage where you can look at assembly code like this and at least get a general sense of what it does and how it works. Here are a couple of things to pay particular attention to as you look over it.
- The code uses
push
(to store the value in a register onto the stack, and decrement the stack pointersp
) andpop
(to load the top value on the stack into a register and increment the stack pointersp
). You can do this in other ways (e.g.stmdb pc!, {lr}
) butpush
andpop
are convenient when you want to want to usesp
to keep track of the stack. You can see thepush
/pop
instructions in Section A7.7 of your your ARMv7 reference manual - Some (but not all) of the functions take arguments (described in the comments), so before you call these functions make sure youve got the right values in these registers to pass arguments to the functions.
- The
.global delay, red_led_init, red_led_on, red_led_off
line is necessary because youre putting the code above into a separate file to the one where the rest of your program will be (src/main.S
). By default, when you hit build/run the assembler will only look for labels in the current file, so if you try and branchbl
to one of these functions frommain.S
itll complain that the label doesnt exist. By marking these functions as.global
, it means that the assembler will look everywhere for them, even if theyre in a different source file to the one theyre being called from. Finally, the.global
labels in a file are good clue about which functions are useful to call from your own code. - You may notice things like
ldr r0, =ADR_RCC
and wonder why its not a number after=
.ADR_RCC
is a symbol declared inlib/util.S
using.set
directive. This is a somewhat convenient way of abstracting over numeric values.
Discuss with your neighbourwhat are the advantages of using the functions in the LED library? Are there any disadvantages?
Your task in exercise 1 is to use the functions from the led.s
library to write three new functions in your main.S
file:
blink_dot
, which blinks the led for a short period of time (say0x20000
cycleswell call this the dot length) and then pauses (delays) for one dot length before returningblink_dash
, which blinks the led for three times the dot and then pauses (delays) for one dot length before returningblink_space
, which doesnt blink the LED, but pauses (delays) for seven dot lengths before returning
Each of these function calls will contain nested function calls (i.e. calls to delay
or other functions) so make sure you use the stack to preserve the link and argument registers (e.g. with push
and pop
) when necessary.
Once youve written those functions, write a main
loop which blinks out the sequence ... _ _ _
on an endless repeat. Commit & push your program to GitLab.
Exercise 2: a morse data structure
Now its time for the actual morse code part. In morse code, each letter (also called a codepoint) is encoded using up to five dots/dashes. For example, the codepoint for the letter B has 4 dots/dashes: _...
while the codepoint for the letter E is just a single dot .
. You could store this in memory in several different ways, but one way to do it is to use a data structure which looks like this:
Each slot in the data structure is one full word (32 bits/4 bytes), so the total size of the codepoint data structure is 4*6=24 bytes. The first word is an integer which gives the total number of dots/dashes in the codepoint, while the remaining 5 boxes contain either a 0 (for a dot) or a 1 (for a dash).
What will the address offsets for the different slots be? Remember that each box is one 32-bit word in size, but that memory addresses go up in bytes (8 bits = 1 byte).
Here are a couple of examples codepoint B (_...
):
and codepoint E (.
)
In each case, the end slots in the data structure might be unused, e.g. if the codepoint only has 2 dots/dashes then the final 3 slots will be unused, and it doesnt matter if theyre 0 or 1. These slots are coloured a darker grey in the diagrams. (If this inefficiency bums you out, youll get a chance to fix it in Exercise 4).
Your job Exercise 1 is to write a function which is passed (as a parameter) the base address (i.e. the address of the first slot) of one of these morse data structures and blinks out the codepoint using the LED.
As a hint, here are the steps to follow:
- pick any character from the morse code table at the start of this lab content
- store that character in memory (i.e. use the
.data
section) using the morse codepoint data structure shown in the pictures above - write a
blink_codepoint
function which:- takes the base address of the data structure as an argument in
r0
- reads the size of the codepoint from the first slot
- using that size information, loops over the other slots to blink out the dots/dashes for that codepoint (use the
blink_dot
andblink_dash
functions you wrote earlier) - when its finished all the dots/dashes for the codepoint, delays for 3x dot length (the gap between characters)
- takes the base address of the data structure as an argument in
Since the blink_codepoint
function will call a bunch of other functions, make sure you use the stack to keep track of values you care about. If your programs not working properly, make sure youre not relying in something staying in r0
between function calls!
When you start to use functions, the usefulness of the step over vs step in buttons in the debugger toolbar starts to become clear. When the debugger is paused at a function call (i.e. a bl
instruction) then step over will branch, do the things without pausing, and then pause when the function returns, while step in will follow the branch, allowing you to step through the called function as well. Sometimes you want to do one, sometimes you want to do the other, so its useful to have both and to choose the right one for the job.
Write a program which uses the morse data structure and your blink_codepoint
function to blink out the first character of your name on infinite repeat. Commit & push your program to GitLab.
Exercise 3: ASCII to morse conversion
The final part of todays lab is to bring it all together to write a program which takes an input string (i.e. a sequence of ASCII characters) and blinks out the morse code for that string.
To save you the trouble of writing out the full morse code alphabet, you can copy-paste the following code into your editor. It also includes a place to put the input string (using the .asciz
directive).
.datainput_string:.asciz "INPUT STRING"@ to make sure our table starts on a word boundary.align 2@ Each entry in the table is 6 words long@ - The first word is the number of dots and dashes for this entry@ - The next 5 words are 0 for a dot, 1 for a dash, or padding (value doesn't matter)@@ E.g., 'G' is dash-dash-dot. There are 2 extra words to pad the entry size to 6 wordsmorse_table: .word 2, 0, 1, 0, 0, 0 @ A .word 4, 1, 0, 0, 0, 0 @ B .word 4, 1, 0, 1, 0, 0 @ C .word 3, 1, 0, 0, 0, 0 @ D .word 1, 0, 0, 0, 0, 0 @ E .word 4, 0, 0, 1, 0, 0 @ F .word 3, 1, 1, 0, 0, 0 @ G .word 4, 0, 0, 0, 0, 0 @ H .word 2, 0, 0, 0, 0, 0 @ I .word 4, 0, 1, 1, 1, 0 @ J .word 3, 1, 0, 1, 0, 0 @ K .word 4, 0, 1, 0, 0, 0 @ L .word 2, 1, 1, 0, 0, 0 @ M .word 2, 1, 0, 0, 0, 0 @ N .word 3, 1, 1, 1, 0, 0 @ O .word 4, 0, 1, 1, 0, 0 @ P .word 4, 1, 1, 0, 1, 0 @ Q .word 3, 0, 1, 0, 0, 0 @ R .word 3, 0, 0, 0, 0, 0 @ S .word 1, 1, 0, 0, 0, 0 @ T .word 3, 0, 0, 1, 0, 0 @ U .word 4, 0, 0, 0, 1, 0 @ V .word 3, 0, 1, 1, 0, 0 @ W .word 4, 1, 0, 0, 1, 0 @ X .word 4, 1, 0, 1, 1, 0 @ Y .word 4, 1, 1, 0, 0, 0 @ Z
The main addition youll need to make to your program to complete this exercise is a morse_table_index
function which takes a single ASCII character as input, and returns the base address of the corresponding codepoint data structure for that character (which you can then pass to your blink_codepoint
function). For example, the letter P is ASCII code 80
, and the offset of the P codepoint data structure in the table above is 15 (P is the 16th letter) times 24 (size of each codepoint data structure) equals 360 bytes.
So, your main program must:
- loop over the characters in the input string (
ldrb
will be useful here) - if the character is
0
, youre done - if the character is not
0
:- calculate the address of the morse data structure for that character
- call the
blink_codepoint
function with that base address to blink out the character - jump back to the top of the loop and repeat for the next character
If you like, you can modify your program so that any non-capital letter (i.e. ASCII value not between 65 and 90 inclusive) will get treated as a space (blink_space
).
Write a program which blinks out your name in morse code. Commit & push this program to GitLab.
Exercise 4: choose-your-own-adventure
Summary
5/5 – (1 vote)
There are many ways you can extend this program. Here are a few things to try (pick which ones interest youyou dont have to do them in order):
- can you modify your program to accept both lowercase and uppercase ASCII input?
- the current
morse_table
doesnt include the numbers 0 to 9; can you modify your program to handle these as well? - can you remove the need for the number of dots/dashes in each table entry altogether?
- this is far from the most space-efficient way to store the morse codepoints, can you implement a better scheme?
- can you modify the
led.S
library to set up and blink the green LED as wellhow can you use this in your morse blinking?
Reviews
There are no reviews yet.