Link Search Menu Expand Document

Embedded Programming and Memory

Prelab due 9/26/22 at 11:00 am

Writeup due 10/03/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. With your partner, come up with a high-level, 3-5 sentence summary of how Arduino code gets compiled to code that is compatible with the architecture of the microcontroller on the Arduino. Tell this summary to a TA. If you are waiting for a TA, go ahead and get started on the next steps of the lab.

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

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

    1. Refer back to Question 4 of the prelab, where you determined the register bits to set and clear to drive Pin 6 as an output.

    2. To read and write to the port registers in Arduino code, you can access them using PORT->Group[PORTX].REGNAME.reg, where X is A or B (the port you are referencing), and REGNAME is the name of the register, like DIR.

    3. Write some Arduino code to use the registers to change the mode of Pin 6 to output (in the setup() function of a blank Arduino sketch), and drive it high (on) for 1 second and drive it low (off) for two seconds (in the loop() function). You may use delay(...) in your code, but not pinMode(...) or digitalWrite(...). It may be helpful to look back at the review of bit operations in Question 2 of the prelab.

    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!

    1. Upload, run, and debug your code, and, when working, get it checked off by a TA.
  4. 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. Referencing the prelab see what register bits to set or clear, set the mode of Pin 0 to input, and read its input value (you will have to refer to the PORT registers in the datasheet to see how to read this value). Write some code to control the on-board LED with the button. When the button is held down, the LED should be lit. When the button is released, the LED should not be lit. Again, you may not use pinMode(...), digitalWrite(...), digitalRead(...), or attachInterrupt(...) in your code.

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

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

  5. 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. In setup(), Configure Pin 0 as an input as above. Also configure the pin to be used by the External Interrupt Controller (EIC) based on your answers in prelab Question 5. (The registers you will set in this step are the PINCFG and PMUX registers you identified in that question).

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

      As you saw in the prelab, you will reference the relevant PMUX register using PORT->Group[PORTX].PMUX[y/2].reg

    2. Still working in setup(), initialize Serial like in labs 1 and 2, and use Serial.println(...) to print the PM and NMI registers from question 6 of the prelab to the serial monitor in binary, so that you can verify that the necessary bits (identified in prelab Question 6.1) are already set. PM registers can be accessed using PM->REGNAME.reg, and similarly with NMI.

    3. In setup(), configure the GCLK according to the bits you determined in Question 6.2 of the prelab. The registers you will be writing to are GENCTRL and CLKCTRL. Macros that name the bits for these registers (for example, GCLK_GENCTRL_IDC are found in gclk.h in the header files.

      15.6.6 of the datasheet tells us that when we write to GENCTRL, we need to also wait for the SYNNCBUSY bit of the STATUS register to clear before proceeding, which we can do by adding the following line after writing to GENCTRL:

      while(GCLK->STATUS.bit.SYNCBUSY);
      

      As described in our datasheet guide, be sure not to use |= to write to GENCTRL, because it may cause an incorrect ID configuration in GENCTRL and cause SYNCBUSY to hang indefinitely.

    4. Now that we have configured the generic clock, it is time to configure the EIC, using your answers from prelab Question 7 (there are three registers to write to for this step). You can refer to eic.h in the header files for the bit macros. You can reference the registers using EIC->REGNAME.reg.

    5. Finally, enable the EIC using the CTRL (21.8.1) register. Because CTRL is write-synchronized, we also need to wait for the EIC->STATUS.bit.SYNCBUSY bit to clear, as for the GCLK STATUS SYNCBUSY bit above.

    6. Enable interrupts on individual pins by writing a 1 to the appropriate index bit of the INTENSET register (21.6.6/21.8.7).

    7. The nested vector interrupt controller (NVIC) manages all the interrupts on the board. We set the priority of the EIC at 3 because the button push isn’t timing-critical, and we also enable the interrupt request definition (IRQ, the function that handles the interrupt) using the following two lines:

      NVIC_SetPriority(EIC_IRQn, 3);
      NVIC_EnableIRQ(EIC_IRQn);
      
    8. Finally, implement the function

      void EIC_Handler(void)
      

      with the desired behavior. For this code, you should increment the number of times the button has been pushed, and print the number to the serial monitor every time the button is pushed. You must write a 1 to the appropriate index bit of the EIC INTFLAG register to clear the interupt at the end of the handler function (21.8.8).

    9. Upload, run, and debug your code. If it is not working, carefully read through every substep of this lab step, to see if you missed configuring any registers. Once you are confident your code works demo it to a TA.

  6. In this step, you will enable interrupts on the other button (pin 1). Remember to check which Port/pin 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 3.

    2. You should configure pin 1 the same was you did pin 0, by writing the corresponding registers. Note that the EIC handles 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.

    3. When an interrupt is triggered and the EIC_Handler function is called, 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 2 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.

  7. 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 &: &varname or &loop or &(PERIPHERAL->REGNAME.reg) (don’t put the parentheses after the function name, because you are not calling the function). You can print something in hex using the syntax below 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).

  8. Turn in your work:

    1. Save your code from steps 4c and 6e. 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.