Link Search Menu Expand Document

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

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

  1. Wire up the following circuit. At different steps in the lab, we will work with different parts of the circuit.

    L03D01

    Test that each component is working by writing some code (using digitalWrite and digitalRead) 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 poll digitalRead(...) 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.

  2. Create a new sketch. In this sketch, you will work with the PORT registers to blink the onboard LED.

    1. 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 or digitalWrite. 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!

    2. Upload, run, and debug your code, and, when working, get it checked off by a TA.

  3. 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.

    1. 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 into PINCFG for the specific pin, e.g. PORT->Group[PORTA].PINCFG[22].reg) Keep the configuration for Pin 6 the same.

    2. In loop, read Pin 0’s input value using the IN register (23.8.9 on page 381 of the datasheet).

    3. 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(...), or attachInterrupt(...) in your code.

    4. Upload, run, and debug your code, and, when working, get it checked off by a TA.

    5. Save the code as registers1.ino to be submitted later.

  4. 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.

    1. 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 macro PORT_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 in gclk.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
      }
      
    2. 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 line EIC->STATUS.bit.SYNCBUSY after writing to the EIC CTRL register in the code above.

    3. Leave the loop function empty – all of the functionality will be handled in the interrupt service routine.

    4. 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.

    5. 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.

  5. 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:

    1. You should configure both LEDs as outputs, just like you did for the on-board LED in Step 2.

    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.

    3. 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 the INTFLAG 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.

    4. Finish writing your code, and upload, run, and debug it. When it is working, check it off with your TA.

    5. Save the code as registers2.ino to be submitted later.

  6. 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.

    1. 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 empty helper() function. Initialize Serial in setup() and use it to print something in loop() (you can make loop() print once by adding a while(true); at the end of loop()).

    2. Using Serial.println(...) in the setup() function, print out the memory addresses of: both global variables, all three local variables, the loop() function, and the setup() 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);
      
    3. Now declare two global variables of type int*: addr and addr_static. In the helper() function, declare and initialize a local variable of type int. Write down the value you initialized this variable to. In helper(), store the address of this variable into addr (to get the address of a variable, precede it with &, i.e. addr = &var). Also create a local static variable in helper(), and store its address in addr_static. Back in the setup() function, call helper() and then dereference addr and addr_static to print the values stored at their addresses (to dereference, use *, e.g. Serial.println(*addr)). Also print the values of addr and static_addr (since they’re pointers that store addresses, you can just print them using e.g. Serial.println((unsigned int) addr)).

    4. Make sure both partners have a copy of the output to refer to in the lab writeup.

    5. 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).

  7. Turn in your work:

    1. 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).

    2. INDIVIDUALLY, complete the Lab 3 writeup assignment on Gradescope.