Embedded Programming and Memory
Prelab due 9/25/22 at 11:00 am
Writeup due 10/02/22 at 11:00 am
About
In this lab, you will interact directly with the registers for the AMD chip that operates your Arduino. While in previous labs you saw how to use the Arduino API to control input and output on pins, most MCUs do not come with such a useful layer of abstraction. In this lab, you will learn what happens under the hood of this abstraction, by setting bits in MCU control registers yourself. In doing this, you will gain some experience interpreting a datasheet for a device as complex as a microcontroller. In the last part of the lab, you will also get some sense of the memory limits of this microcontoller.
Lab 3 Rubric
Resources
- Arduino API
- Arduino Help
- Our datsheet guide
- MKR1000 schematic
- SAM D21E/SAM D21G/SAM D21J Datasheet Summary
- Atmel SAM D21 Family Datasheet DATA/STORAGE WARNING: 1115 page PDF
- SAM D21 peripheral header files
Materials
Included in your kits:
- Arduino MKR1000 and USB cable
- Breadboard
- 1 LED (any color)
- 2 push buttons
- 3 resistors (1 x 1kΩ; 2 x 10kΩ)
- Jumpers/wires
Provided:
- None necessary
Steps
-
Wire up the following circuit. At different steps in the lab, we will work with different parts of the circuit.
Test that each component is working by writing some code (using
digitalWrite
anddigitalRead
) to turn on the external LED and the on-board LED (on pin 6) and output the signal of each button onto the serial monitor, similar to Lab 1. You can just polldigitalRead(...)
for the buttons, instead of setting up an interrupt handler. If you need help debugging your lab, your TAs will first ask you to pull up this code to make sure your circuit is working before going on to more complicated diagnosis. -
Create a new sketch. In this sketch, you will work with the PORT registers to blink the onboard LED.
-
Write some Arduino code to use the registers to change the mode of Pin 6 to output, and drive it high (on) for 1 second and drive it low (off) for two seconds. You may not use
pinMode
ordigitalWrite
. Use the code below as a starting point, and refer back to the relevant questions in the prelab:void setup() { // Set the direction of Pin 6 to output // (Prelab Q 3.1) PORT->Group[PORTA].____.reg } void loop() { // Drive Pin 6 high PORT->Group[PORTA].____.reg delay(1000); // Drive Pin 6 low PORT->Group[PORTA].____.reg delay(2000); }
Fill in the relevant register names in the blanks above, and then set the correct bits inside of the registers. For this step, you can either use the read-modify-write version of the registers, or use the
SET
/CLR
registers (refer to our how to read a datasheet guide for more details). We recommend trying both! -
Upload, run, and debug your code, and, when working, get it checked off by a TA.
-
-
Now, you will modify the code to read the input from one of the buttons and use it to drive the LED on when the button is pressed down and off when the button is not pressed.
-
In
setup
, referencing question 3.2 of the prelab, set the mode of Pin 0 to input. Also enable the input buffer, as described in questions 3.2/4.2 of the prelab (you will need to index intoPINCFG
for the specific pin, e.g.PORT->Group[PORTA].PINCFG[22].reg
) Keep the configuration for Pin 6 the same. -
In
loop
, read Pin 0’s input value using the IN register (23.8.9 on page 381 of the datasheet). -
Change the code that controls the on-board LED (on Pin 6). When the button is held down, the LED should be lit. When the button is released, the LED should not be lit. Note that this code should just poll Pin 0, so the EIC is not needed. Again, you may not use
pinMode(...)
,digitalWrite(...)
,digitalRead(...)
, orattachInterrupt(...)
in your code. -
Upload, run, and debug your code, and, when working, get it checked off by a TA.
-
Save the code as
registers1.ino
to be submitted later.
-
-
Create a new sketch. In this sketch, you will set up an interrupt on Pin 0 to detect the rising edge of the signal, indicating that the button has been pressed.
-
Use the following
setup
starter code to configure Pin 0 to be used by the External Interrupt Controller (EIC) based on your prelab answers.The SAM D21
port.h
header file file gives some convenient definitions of the bits. For example, instead of manually writing bit 2 to PINCFG to enable the pull resistor, you can use the pre-defined macroPORT_PINCFG_PULLEN
without needing to shift the bits, so you can connect all the relevant flags with a bitwise-or:PORT->Group[PORTX].PINCFG[y].reg = PORT_PINCFG_PULLEN | ...
. You can find similar definitions for GCLK ingclk.h
.void setup() { Serial.begin(9600); while (!Serial); Serial.println("===="); // **Set Pin 0 direction to input** PORT->Group[PORTA].[REGNAME].reg // **Configure Pin 0 with the EIC** // Enable input buffer, pull resistor, and multiplexing // (Prelab 4.2 and 4.3) PORT->Group[PORTA].PINCFG[22].reg = ____ // Select the multiplexer function // (Prelab 4.4) PORT->Group[PORTA].PMUX[___].reg // ** CHECK APBA and NMI** // (Prelab 5.1) Serial.println("APBA enabled? Bit __ should be 1"); Serial.print(PM->____.reg, BIN); // prints binary value Serial.print("NMI? Bit __ should be 0"); Serial.println(EIC->____.reg, BIN); // ** Set up GCLK for EIC ** // (Prelab 5.2-5.7) GCLK->GENDIV.reg = 0x00000000 | GCLK_GENDIV_ID(4); while(GCLK->STATUS.bit.SYNCBUSY); // write-synchronized (15.6.6) GCLK->CLKCTRL.reg = ____ GCLK->GENCTRL.reg = ____ while(GCLK->STATUS.bit.SYNCBUSY); // write-synchronized // ** Activate EXTINT 6 (pin 0) in EIC ** // (Prelab 6.1-6.3) EIC->CONFIG[__].reg // Two other registers (Prelab 6.2 and 6.3): ____ ____ // ** Enable EIC** // (Prelab 6.4) EIC->CTRL ____ // ** Clear interrupt flags ** // (Prelab 6.5) EIC->INTENSET.reg ____ Serial.println("Interrupts ready!"); // Set up priority and handler // NVIC = Nested Vector Interrupt Controller NVIC_SetPriority(EIC_IRQn, 3); // do not need the highest priority NVIC_EnableIRQ(EIC_IRQn); // enable EIC_handler }
-
Take a look at how the
while(GCLK->STATUS.bit.SYNCBUSY)
lines appear after any write to a write-synchronized register in GCLK. Based on your answer to prelab 6.4, choose to include or omit the lineEIC->STATUS.bit.SYNCBUSY
after writing to the EIC CTRL register in the code above. -
Leave the
loop
function empty – all of the functionality will be handled in the interrupt service routine. -
Implement the interrupt service routine
void EIC_Handler(void)
to increment the number of times the button has been pushed (hint: define a global variable in your code), and print the number to the serial monitor every time this happens. You must clear the interupt at the end of the handler function (prelab 6.6). Note that you do not have to read the input value of Pin 0 here – the whole point of interrupts is that entering the interrupt service routine lets us know that the event in question (the button push) has happened.
-
Upload, run, and debug your code. If it is not working, carefully check that you configured the registers according to your answers in the prelab, to see if you missed configuring any registers. Once you are confident your code works demo it to a TA.
-
-
In this step, you will enable interrupts on the other button (pin 1). Remember to check which SAMD21 Port number Arduino pin 1 is on, and which EIC index corresponds to that port pin. You will then change the EIC_Handler function such that the external LED toggles between on and off whenever the pin 1 button is pressed, and the on-board LED toggles between on and off whenever the pin 0 button is pressed:
-
You should configure both LEDs as outputs, just like you did for the on-board LED in Step 2.
-
You should configure pin 1 the same was you did pin 0, by writing the corresponding registers. Note that
EIC_Handler
is called for all external interrupts on all pins, so your GCLK configuration shouldn’t change. You will, however, need to enable some extra bits on the three EIC configuration registers and INTENSET. -
Unlike in step 4, when an interrupt is triggered and the
EIC_Handler
function is called, now you need to determine which button push triggered the interrupt. You can check which EIC index is responsible for the interrupt by reading theINTFLAG
register and seeing which bit is a 1. After handling the interrupt, you then write a 1 to that bit again to clear the interrupt. This may not seem intuitive, but that is why this register is called the Interrupt Flag Status and Clear register (see 21.6.3).How would you check if a bit at a certain index of a register is set? Refer back to pre-lab Question 1 and discuss with your partner.
Implement this check to determine which button was pushed, and toggle the external LED when the pin 1 button is pressed and the on-board LED when the pin 0 button is pressed. Don’t worry about handling the case where both buttons are pressed at the same time.
-
Finish writing your code, and upload, run, and debug it. When it is working, check it off with your TA.
-
Save the code as
registers2.ino
to be submitted later.
-
-
You can do this last step during lab with your partner, or alone after the lab. This step requires your Arduino but doesn’t require you to build any circuits.
-
Create a new sketch that declares and initializes two global variables (of types(s) of your choosing), three local variables in the
setup()
function, and an emptyhelper()
function. InitializeSerial
insetup()
and use it to print something inloop()
(you can makeloop()
print once by adding awhile(true);
at the end ofloop()
). -
Using
Serial.println(...)
in thesetup()
function, print out the memory addresses of: both global variables, all three local variables, theloop()
function, and thesetup()
function, and one of the registers you used in this lab in hexadecimal. You can access the address of a variable, function, or register by preceding it with&
: e.g.&varname
or&loop
or&(PERIPHERAL->REGNAME.reg)
(don’t put parentheses after the function name, because you are not calling the function). You can print something in hex using the syntax:Serial.println((unsigned int) thing_to_print, HEX)
. It will be helpful to precede each address with a helpful label, e.g.Serial.print("A local variable: "); Serial.println((unsigned int) &local_var, HEX);
-
Now declare two global variables of type
int*
:addr
andaddr_static
. In thehelper()
function, declare and initialize a local variable of typeint
. Write down the value you initialized this variable to. Inhelper()
, store the address of this variable intoaddr
(to get the address of a variable, precede it with&
, i.e.addr = &var
). Also create a local static variable inhelper()
, and store its address inaddr_static
. Back in thesetup()
function, callhelper()
and then dereferenceaddr
andaddr_static
to print the values stored at their addresses (to dereference, use*
, e.g.Serial.println(*addr)
). Also print the values ofaddr
andstatic_addr
(since they’re pointers that store addresses, you can just print them using e.g.Serial.println((unsigned int) addr)
). -
Make sure both partners have a copy of the output to refer to in the lab writeup.
-
At the very end of the
setup()
function, include the following code:int* corrupt_loop = (int*) &loop; *corrupt_loop = 32580; // can use any nonsense value
Run the code. What do you notice (hint: what is missing that existed in the previous output?) You will be asked to explain this in the writeup.
Note: after doing this, try to upload a new sketch, like BareMinimum, to the board. The IDE should hang. You can reset the bootloader of the Arduino by double-pressing the reset button, which should allow you to upload the sketch (check to see if the port that the Arduino is on has changed when you reset the bootloader).
-
-
Turn in your work:
-
Save your code from steps 3 and 5. Upload this to the “Lab 3 Code” assignment on Gradescope (include all partner(s) on the submission).
-
INDIVIDUALLY, complete the Lab 3 writeup assignment on Gradescope.
-