[Solved] Comp2300 Lab 6

$25

File Name: Comp2300_Lab_6.zip
File Size: 131.88 KB

SKU: [Solved] Comp2300 Lab 6 Category: Tag:
5/5 - (1 vote)

Before you attend this weeks lab, make sure:

  1. you can read and write basic assembly code: programs with registers, instructions, labels and branching
  2. you have read the laboratory text below

In this weeks lab you will:

  1. learn how to load and store with ldr and str
  2. blink the LEDs by reading & writing special hardware registers
  3. use functions to structure your code and remove duplication

Heres a table of contents for this weeks lab, just in case you need to jump around.

Introduction

In this weeks lab youll read and write some of the special hardware registers on your discoboard to see LED output from your program. Seeing stuff happen in the real world is a big part of the fun of microcontrollers, so this is going to be fun.

Youll also start to see more clearly the connections between what weve been covering in this course and the higher-level programming languages youre used to, with their if statements, for loops, and other niceties. This process of demystifying programming is a big part of what this course is about, so take the time to reflect on what youre doing and how it fits in with what you know and do in other programming situations.

Exercise 0: bit shifting practice

Plug in your discoboard, fork & clone the lab 6 template to your machine, and open the src/main.S file as usual.

The stuff in Exercise 0 is all stuff weve covered already (in some cases as far back as week 2) but its sometimes good to have a warm-up/refresher. Feel free to jump straight to Exercise 1 if youre feeling adventurous, but theres no shame in taking your time and working through the warm-up stuffpractice makes perfect!

Bit-shifting & logic ops

You will need to use some logic operations in this lab, in particular, setting (set 1) and clearing (set 0) bits. This warm up exercise gives you a chance to practice bit shifting and using logic operations to set and clear bits.

Edit your main.S file so that it looks like the following. Dont forget the required .syntax unified and .global main statements at the top of the file.

main:  ldr r0, =0xcafe0000  ldr r1, =0xffff  @ your code goes here@ when it's all done, spin in an infinite looploop:  nop  b loop
Decimal
Hex
Binary

These shouldnt require heaps of codejust a couple of instructions for each. Remember the stuff youve done in previous labs.

Did you need both of the starting values (e.g. 0xcafe0000 and 0xffff) or could you have got the job done with only some of them? Which ones are essential?

Can you do each task with only one instruction each?

Exercise 1: using labels, ldr, and str

Labels and loading arbitrary numbers into registers

Labels (Page 24) are the symbols in your source code followed by a colon (:), e.g. main:. Youve probably already got an intuitive feel for how they work: you put them in your code wherever you like, and when you want to branch to that part of the program you put the label in as the destination part of the branch instruction. Heres an example:

loop:  @ do stuff  b loop @ branch back to the "loop" label

In the week 3 lab you even used conditional branches to only branch under certain conditions (i.e., if certain flags were set). And we covered this in the week 4 lectures as wellincluding how to turn mathematical conditional expressions (e.g. x >= -45) into sequences of assembly instructions.

But what are labels, really? Add this code to your program (under the main label):

ldr r0, =main

After you step through this line, whats in r0? You might be wondering what the = sign is doing in your program. Remember from your week 2 lab that instructions are stored in memory with various encodings (some are 16-bit, some are 32-bit) and that when you use an immediate value constant (e.g. 42) in an instruction which supports it then the bit pattern for 42 (which is 0b101010) is stored inside that instruction.

This means that if you need to include a constant which is 32 bits long (e.g. 0xABCD1234) then you cant fit it in the instruction. You may have run into this problem alreadythe error message will be something like

Error: invalid constant (ffffffffabcd1234) after fixup

and what it means is that the constant value youre using is too big (too many bits) for the instruction youre trying to fit it inside.

Because this is a bit of a pain, the assembler provides a special syntax for storing larger values in registers. Its based around the ldr (load register) instruction, and if you prefix the constant with an = sign then the assembler will generate the code to load the full value into the register, as we discussed in the week 4 lectures.

So how does this relate to the ldr r0, =main instruction above? Well, the answer is that the labels in your program are just valuestheyre the addresses (in your boards memory space) of the instruction which occurs after them in the program. After the linker figures out exactly which address each label points to, it replaces them in the program, so that

ldr r0, =main

becomes something like

ldr r0, =0x80001c8

or whatever address the main label ends up pointing to (which will change every time your program changes).

And since 0x80001c4 (or whatever it is) is just a bit pattern in a register, you can do the usual arithmetic/logic stuff you can do with any values in registers:

Write a small program which calculates the size (in memory) of the movs r3, 1 instruction and stores the result in r0.

Exercise 2: the load-twiddle-store pattern

The load-twiddle-store pattern is a fundamental pattern in making your discoboard do useful work. The basic idea is this:

  1. load some data from memory into a register
  2. operate on (twiddle) the value in the register (e.g. with an add or and instruction)
  3. store this new value from the register back into memory

load-twiddle-store

load-twiddle-store

If youve forgotten about data and text sections, you can review it in the week 5 lab content.

Lets now make use of a data section to store some (spoilers) data, and attempt to load-twiddle-store.

main:  ldr r1, =storage  @ your code starts here.datastorage:  .word 2, 3, 0, 0 @ don't change this line

Starting with the code above, use the load-twiddle-store pattern to change the first four data words to 2 3 0 1 instead of 2 3 0 0. Hint: first load the storage label using the = instruction, then remember that you can load and store with an offset from this base address (check the cheat sheet). Youll probably also want to use the memory browser view (like you did in week 2) to watch the values change in memory.

Exercise 3: hello, LED!

So what does all that stuff have to do with blinking the LEDs? Well, the answer is that theres a section of the discoboards address space (Page 23) (0x40000000 to 0x5FFFFFFF) which is mapped to peripherals (as shown in the picture above). To interact with the LEDs, LCD, microphone etc. on the board you need to talk to the hardware by reading and writing to special memory locations in this memory range. To figure out exactly which addresses are mapped to which peripherals, you need to look at the discoboard reference manual

discoboard with LEDs lit up

discoboard with LEDs lit up

One type of peripheral is a General Purpose Input/Output pin. You can see them on your discoboard as little gold-coloured spikes sticking up out of the top and bottom of the board. Your discoboard has lots of them, and you can wire them up to other devices to make more sophisticated devices.

This exercise is pretty long, so here are the steps youll go through to turn on the LED:

  1. enable the clock for correct LED GPIO pin
  2. set the pin to output mode
  3. set a bit in the pins data register to turn the LED on

Dont worry if you dont understand some of those termsthe rest of this exercise will explain all the details.

Some of the ports on the discoboard are already connected to certain bits of hardware on the board. In the discoboard user manual Section 7.5 User interface: LCD, joystick, LEDs it says:

  • LD4 user: the red LED is a user LED connected to the I/O PB2 of the STM32L476VGT6
  • LD5 user: the green LED is a user LED connected to the I/O PE8 of the STM32L476VGT6

The first bullet point says that the red LED is connected to GPIO pin PB2. This means that its connected to pin 2 of port B. Just a note that the user manual (short) is different from the reference manual (long & detailed).

What port+pin do you think the green LED is connected to?

The GPIO pins are grouped into 8 ports (port A to port E) and each port has 16 pins (pin). Its worth pointing out that the pin numbering starts at 0, so the first pin in port A is PA0.

Ports and pins

Ports and pins

From the reference manual:

Each general-purpose I/O port has four 32-bit configuration registers (GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR and GPIOx_PUPDR), two 32-bit data registers (GPIOx_IDR and GPIOx_ODR) and a 32-bit set/reset register (GPIOx_BSRR).

Dont worry if this seems overwhelming, the main point is that each of these ports has a few dedicated configuration registerssome are used to turn it on, some are used to set it up, and some are used to receive data (read the voltage on the pin as 0 or 1) or send data out (set the voltage to high or low). When it says GPIOx, that means that there exists a version for all the ports, so the port A version would be called GPIOA, etc.

These are not like the CPU registers youve been using so far (e.g. r0 or r5). Instead, theyre mapped to certain parts of the address space (so theyre sometimes called memory-mapped registers). Read/write access to this register happens through load/store instructions to a specific memory address (as with pretty much everything in a load/store architecture (Page 27).

The register for turning on the clock (step 1) is in the Reset and Clock Control (RCC) section of the address space, which on your discoboard starts at memory address 0x40021000. The specific register which controls the clock for GPIO ports is the RCC_AHB2ENR 32-bit register, which lives at an offset of 0x4C from the RCC base address and looks like this:

RCC_AHB2ENR register

RCC_AHB2ENR register

As you can see, the clock for GPIO port B (where your red LED is) is controlled through bit 1 (i.e. the second bit from the right, because the rightmost bit is bit 0). You can have a look at Section 6.4.17 of the reference manual for all the gory details.

Why do we need to turn on a clock to even use the GPIO pins? Why arent they just permanently enabled to save us all the hassle? Hint: the discoboard is marketed as an ultra-low-power microcontroller. Its designed to support being on battery power for long durations without recharging.

Note that in debug view you can conveniently see this information in the Peripherals pane: Peripheral View

Peripheral View

This is your chance to see the load-twiddle-store pattern from Exercise 1 in action. To turn on GPIO port B, you must:

  1. load: load the RCC base address into a register, then do an offset ldr with the RCC_AHB2ENR offset to read the current state of the RCC_AHB2ENR register into a CPU register
  2. twiddle: use bitwise operations to set the second-from-the-right GPIOBEN bit to 1 while leaving the other bits unchanged
  3. store: write the new RCC_AHB2ENR value back to the memory address you read it from earlier

When we talk about setting a bit, that means that it should be equal to 1, and clearing a bit means it should be equal to 0.

So what does the code to perform these load-twiddle-store steps look like?

@ load r1 with the base address of RCCldr r1, =0x40021000@ load r2 with the value of RCC_AHB2ENR@ (note the 0x4C offset from the RCC base address)ldr r2, [r1, 0x4C]@ set bit 1 of this register by doing a logical or with 2@ think: why does this work?orr r2, 0b10@ store the modified result back in RCC_AHB2ENRstr r2, [r1, 0x4C]

Why is the load part of this process necessary? Why cant you just store a 2 into the RCC_AHB2ENR register and be done with it?

You can paste the above code straight into your program. There are a couple more steps before you can actually turn the LED on, but theyre basically the same load-twiddle-store pattern, except with different addresses and twiddles.

Now its your turn: copy-paste a second copy of the code above as a starting point, but youll need different load/store addresses and different twiddles. To set pin 2 of GPIO port B to output mode, you need to set the MODE2 bits (4 and 5) of the GPIO mode register GPIOB_MODER, which lives at an offset of 0x0 from the GPIO base address of 0x48000400 (see Section 7.4.1 of the manual for more info). Heres what the GPIOB_MODER looks like:

GPIOx_MODER register

GPIOx_MODER register

To configure pin 2 for output mode to power the LED, you need to ensure the mode bits for pin 2 (MODE2 in the diagram) are 01 for output mode (i.e. clear bit 5, set bit 4).

This output mode configuration for the mode register bits is sufficient for turning the LEDs on and off, but there are many more ways to configure the GPIO pins. If youre interested, have a look in Section 7.4 of the manual.

Your PB2 pin is now configured and ready to roll. The only thing left to do is to actually send an on signal to it by setting a 1 into bit 2 (for pin 2) of the port B Output Data Register GPIOB_ODR, which lives at offset 0x14 from the GPIOB base address and looks like this:

GPIOx_ODR register

GPIOx_ODR register

Following the steps above, write a program which turns on the red LED on your discoboard, and push it up to your fork on GitLab.

The red and green LEDs are normally near the top left corner of the reset button. If you look closely, you can see the labels LD4 and LD5 on the board there. The LEDs above the LCD screen are not the ones we are working with.

There are a few fiddly things which can go wrong here. If your LED isnt coming on, talk with your neighbour/tutor about your program. Have you accidentally set the wrong bit (remember that the ports and bits are 0-indexed, so the rightmost bit is bit 0, not bit 1). Are you reading the existing register value correctly? Are you turning the bit on correctly? Are you writing it back to the right memory address? Step through the program with your partner to see what might be going wrong.

Once you can turn the red LED on, add code to turn the green one (PE8) as well. Commit & push your red+green program up to GitLab.

Exercise 4: functions review

Hey wow! You made it to Exercise 4 which is about the halfway point for this lab! As we have both week 6 and week 7 for this lab, you can feel free to stop here and pick it up in week 7. Alternatively, if youre preparing for week 6s lab, make sure you understand everything up to this point. Of course if you want to keep going, Im not going to stop you! Have fun! 😀

You should now feel a warm glow of satisfactionlet there be light! But youll also notice that a few of the steps you had to go through were pretty repetitive. For every step you just did, you were really doing one of two things:

  • setting a specific bit at an offset from a base address, or
  • clearing a specifc bit at an offset from a base address

Wouldnt it be good if we could factor out the common parts of those two tasks, so that the code is simpler and clearer? We can do that with functions (something you should remember from last week). Were going to take this quite slow, if youre feeling confidentyou can skip to the second push box and work from there.

set_bit_0x48000400_0x14_2:  @ code to set bit 2 of word at offset 0x14 of 0x48000400  bx lrmain:  @ ...  bl set_bit_0x48000400_0x14_2  b main

Update your red+green program by writing a few functions using the name pattern set_bit_<base>_<offset>_<index> and clear_bit_<base>_<offset>_<index> following the example above, so that your main function is just a series of bl instructions (and spins in an infinite loop at the end).

Arguments/parameters

Youve now modularised your code (broken it up into smaller, re-usable parts), but its still pretty repetitive. Theres a lot of repeated code between the set_bit_xxxx functions.

The only difference between these repeated versions is the difference in inputs. Therefore we can pass arguments to functions to parameterise those functions, so that we just have one set_bit function that we call with different inputs.

As we learnt last week, we leave values in r0r3 before calling bl to act as inputs for our functions. Consider the following sum_x_y function:

main:  mov r0, 3   @ first argument, x  mov r1, 2   @ second argument, y  bl sum_x_y  @ call sum_x_y(3, 2)  @ get result back in r0.type sum_x_y, %function@ args:@   r0: x@   r1: y@ result: r0sum_x_y:  add r0, r1  bx lr.size sum_x_y, .-sum_x_y

The function adds the values in r0 and r1 and puts the result in r0. So the values in r0 and r1 are arguments (or parameterssame concept, different name). We can just leave the numbers we want to add in r0 and r1, call the function sum_x_y, and expect the result to be in r0 after it finishes.

Did you notice something underhanded going on between the caller (main) and the callee (sum_x_y)? There is an implicit contract/agreement as to

  • which registers hold the input arguments, and
  • which registers hold the result

This is called calling convention, a set of rules that all function calls are expected to adhere to. It is generally CPU architecture and programming language defined. There is more to calling convention, and it will be covered in lecture and in the next lab.

Note that there are some comments before the function about where the arguments are placed. Its a good idea to document what these registers are expected to hold for readability and claritys sake.

Parameterise your set_bit and clear_bit functions so that they each take three arguments: base address, offset and bit index. Modify your main function so that turning the LED on and off is as easy as calling your set_bit or clear_bit functions with the right arguments. Commit & push your newly-functional program to GitLab.

You might have noticed that we havent told you to store the lr register onto the stackthats cause youre creating what are called leaf functions. These leaf functions dont call other functions, so dont need to worry about having lr overwritten.

Exercise 5: blinky

In this exercise youll add a simple loop into your program to blink one of the LEDs on and off. You can write a delay function to do thisdo it now, and call it in-between your on and off instructions. Let the function take in an argument so that you can specify the number of steps to delay for.

There are a bunch of ways to do this, but one way is to

  1. subtract 1 from an input register,
  2. if the value isnt zero then goto step 1, else return back to the caller.

Modify your program so that after the initial setup code, there is a loop which turns the LED on, delays a little while, turns it back off, delays a little again, then branches back to the top of the loop.

Once youve done that, you should be able to blink the LEDs on your board to your hearts content.

For the final exercise, youll make LED blinking more interesting by writing an ARM assembly version of the classic FizzBuzz childrens game (and a common programming interview question). The only difference is that instead of printing "fizz" or "buzz" to the screen (which you cant do anyway, since were not running on the computer, youre running on the discoboard) youll blink the LEDS on the board. So this new version is called FizzBlink, I guess.

Modify your program to:

  1. count up from 0 to 100 in increments of 1
  2. if the number is divisible by 3, blink the red light for some period of time (use your delay function)
  3. if the number is divisible by 5, blink the green light for some period of time
  4. if the number is divisible by both 3 and 5, blink both lights

Rate this product

Using only the instructions in the Logic and Shift/Rotate subsections of the cheat sheet (but as many registers as you need) write a program which puts all of the following values into the listed registers. Use the cheat sheet and the converter widget to help you outdraw bit pattern pictures on a piece of paper if it helps.

  1. 0xcafeffff into r3
  2. 0xcafe into r4
  3. 0xcaff0000 into r5
  4. 0xc0fe0000 into r6

If youre interested in how exactly the ARM instruction set deals with this problem, and which constants can be stored inside a 32-bit instruction, then heres an interesting blog post. Recall we use the Thumb-2 instruction set, so its not exactly the same on the discoboard (see section A5.3.2 in the reference manual for how they work on the discoboard).

Instead, you can play with the following widget to see how our board encodes a shift. The top input is the 12-bit encoded value. The second input is the 32-bit expanded output. Below this, the longer table is the binary representation of the output. An orange colour represents one of the 4 special shifts, while blue means it is a regular shifted byte. Similarly, the shorter table shows the parts of the encoded input, with the shift in purple and the data in green. Note how bit 7 of the encoded input is conditionally part of the shift or data, depending on bits 10 and 11. Each bit in the binary table can also be toggled by clicking it.

313029282726252423222120191817161514131211109876543210

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

11109876543210

0 0 0 0 0 0 0 0 0 0 0 0

Have a look at these two lines of assembly code:

mov r0, 0xFFFldr r0, =0xFFF

will they result in the same assembly instructions when uploaded & running on your discoboard? How might you check? Hint: the disassembler is your friend 😊.

Reviews

There are no reviews yet.

Only logged in customers who have purchased this product may leave a review.

Shopping Cart
[Solved] Comp2300 Lab 6
$25