Link Search Menu Expand Document

Embedded Programming and Memory

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

Writeup due 9/30/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 Uno R4 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

    As we move into writing more complex code for the labs, it’s a good idea to have a simple file handy that tests out the circuitry to make sure that it is working as expected. You’ll be asked to write such code for later labs, but we are providing the code for this lab. The onboard LED should light up when one button is held, and the external LED should light up when the other button is held.

       void setup() {
          for (int i = 0; i < 2; i++) {
             pinMode(2 + i, INPUT);
             pinMode(12 + i, OUTPUT);
          }
       }
    
       void loop() {
          for (int i = 0; i < 2; i++) {
             if (digitalRead(2 + i)) {
                digitalWrite(12 + i, HIGH);
             } else {
                digitalWrite(12 + i, LOW);
             }
          }
       }
    
  2. Create a new sketch. In this sketch, you will work with the I/O registers to blink the onboard LED.

    1. Fill in the blanks in the following code, following the comments. Ignore the commented out code for step 3 for now. The onboard LED is wired to pin 13 of the Arduino.

      // TODO: fill in the RA4M1 pin name below, based on Arduino datasheet
      // (Just like you did for Prelab Q1, except for digital pin 13)
      const unsigned int D13_PORT = ;
      const unsigned int D13_PIN = ;
      
      // TODO for step 3: (use Prelab Q1)
      //const unsigned int D3_PORT = ;
      //const unsigned int D3_PIN = ;
      
      void setup() {
         // Set the direction of D13 to output
         // TODO: Prelab Q2.1 for the field name, Q2.3 for the value
         R_PFS->PORT[D13_PORT].PIN[D13_PIN].PmnPFS_b.___ = ; 
      
         // TODO for step 3: configure D3 as an input
         // R_PFS->PORT[D3_PORT].PIN[D3_PIN].PmnPFS_b.___ = ;
      }
      
      void loop() {
         step2();
         // step3();
      }
      
      void step2() {
         // Drive D13 low
         // TODO: Prelab Q2.3 for the field name, Q2.4 for the value
         R_PFS->PORT[D13_PORT].PIN[D13_PIN].PmnPFS_b.___ = ;
         delay(1000);
      
         // Drive D13 high
         // TODO: Prelab Q2.3 for the field name, Q2.5 for the value
         R_PFS->PORT[D13_PORT].PIN[D13_PIN].PmnPFS_b.___ = ;
         delay(1000);
      }
      
      void step3() {
         // TODO for step 3: drive D13 high iff D3 is pressed
      }
      
    2. Upload, run, and debug your code, and, when working (when the onboard LED blinks on for one second and off for one second), get it checked off by a TA. When you ask a TA for debugging help, the TA will ask you to pull up the debugger (which you learned to do in the prelab) and show you the PmnPFS register (remember the mn corresponds to the RA4M1 pin number, so port 1, pin 1 would be P101PFS). Be ready to talk about what you expect the field values to be.

  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. Fill in the TODOs related to step 3 above. For the body of the step3 function, you could use an if-expression, but you can also write the code as a single assignment.

    2. Change loop to call step3 instead of step2.

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

    4. Save the code as step3.ino to be submitted later.

  4. Create a new sketch. In this sketch, you will set up an interrupt on Pin 2 to detect the rising edge of the signal, indicating that the button has been pressed. The number of presses will be printed to Serial.

    1. Fill in the following code (Note: changes on 9/24: wait until Serial is started to initialize CPU_INT)

      // TODO:
      const unsigned int D2_PORT = ;
      const unsigned int D2_PIN = ;
      // TODO: (Prelab step 4.5)
      const unsigned int D2_IRQ = ; 
      
      // The MCU gives us 32 possible CPU interrupt channels to connect to
      // We'll just use the first available one (see Datasheet Table 13.3.1 for more info)
      unsigned int getNextCPUINT(unsigned int start) {
         /* Get next unused CPU interrupt > start */
         unsigned int tryInt = start + 1;
            while (tryInt < 32) {
               if (NVIC_GetEnableIRQ((IRQn_Type) tryInt) == 0) {
                  return tryInt;
               }
            tryInt++;
         }
      }
      unsigned int CPU_INT;
      
      void setup() {
         Serial.begin(9600);
         while(!Serial);
      
         // Disable the CPU interrupt while we configure it
         //NVIC_DisableIRQ((IRQn_Type) CPU_INT); this line removed 9/24 because CPU_INT will be disabled by definition
      
         // TODO: Configure D2 as an input AND to be used as an IRQ input pin
         // Note: read the PmnPFS register description to find the name of the field that
         // configures a pin for IRQ
         // Both fields should be set in one write, using the bitfield macro style you saw
         // in lecture/prelab Q3. All other bitfields should be 0, so you don't have to worry
         // about leaving them unchanged -- just overwrite the whole register
         // The macro(s) you want to use will start with R_PFS_PORT_PIN_PmnPFS
         R_PFS->PORT[D2_PORT].PIN[D2_PIN].PmnPFS = 
      
         // TODO: Trigger an interrupt when D2 goes from LOW to HIGH (prelab Q4.6)
         // Again, do not use the _b notation. We can just overwrite the register and make the
         // values of the other fields 0.
         R_ICU->IRQCR[D2_IRQ] = ;
         // Configure the ICU to connect the Port IRQ to the CPU (NVIC) IRQ (prelab Q4.7)
         CPU_INT = getNextCPUINT(1)
         R_ICU->IELSR[CPU_INT] = ;
         
         // Use the Arm CMSIS API to enable *CPU* interrupts
         NVIC_SetVector((IRQn_Type) CPU_INT, (uint32_t) &ourISR); // set vector entry to our handler
         NVIC_SetPriority((IRQn_Type) CPU_INT, 14); // Priority lower than Serial (12)
         NVIC_EnableIRQ((IRQn_Type) CPU_INT);
      }
      
      void ourISR() {
         static int timesPushed = 0; // static means value persists between function calls
         Serial.println(timesPushed++);
      
         // TODO: Clear the pending interrupt flag (Prelab Q4.8) on the MCU side
         R_ICU->IELSR_b[CPU_INT].___ = ;
         // Clear the pending interrupt on the CPU side
         NVIC_ClearPendingIRQ((IRQn_Type) CPU_INT);
      }
      
      void loop() {
         delay(100000); // IRQ should be able to interrupt this delay no matter when it happens
      }
      
    2. 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, using the debugger. 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 3). You will write code such that the external LED toggles between on and off whenever the button on D2 is pressed, and the on-board LED toggles between on and off whenever the button on D3 is pressed. Make a new file and:

    1. In setup, using information from the Arduino schematic and the configuration you used in Step 2 of the lab, configure pins 12 and 13 as GPIO outputs.

    2. In setup, using the configuration you used in Step 4 of the lab, configure pins 2 and 3 as inputs that trigger an interrupt on the rising edge. You will essentially be duplicating the code you used for pin 2 and adapting it to set up pin 3, as well. Because we have two interrupt sources (two pins), we will also have two CPU interrupts: make sure both are available by modifying the input to getNextCPUINT (don’t forget to copy getNextCPUINT over, and make sure this initialization happens after Serial is started), e.g.

      CPU_INT_1 = getNextCPUINT(0);
      CPU_INT_2 = getNextCPUINT(CPU_INT_1);
      

      Both CPU interrupts should go to the same handler (for example, ourISR), i.e. your CMSIS function calls should be:

      NVIC_SetVector((IRQn_Type) CPU_INT_1, (uint32_t) &ourISR);
      NVIC_SetPriority((IRQn_Type) CPU_INT_1, 14);
      NVIC_EnableIRQ((IRQn_Type) CPU_INT_1);
      NVIC_SetVector((IRQn_Type) CPU_INT_2, (uint32_t) &ourISR);
      NVIC_SetPriority((IRQn_Type) CPU_INT_2, 14);
      NVIC_EnableIRQ((IRQn_Type) CPU_INT_2);
      
    3. In Q4.8 of the prelab, you learned that the MCU sets a flag that can help us track down the source of an interrupt. You cleared this flag without checking it in step 4, because there was only one interrupt source. You can also check this flag to find out the source of the interrupt. One of your pin interrupts will be connected to CPU_INT_1, and the other pin interrupt will be connected to CPU_INT_2, so you can check the corresponding flag for each CPU interrupt to figure out the source. Once you determine the source, use this information to toggle either the external LED (for the interrupt on pin D2) or the on-board LED (for the interrupt on pin D3). Bit operation hint: you can flip the value of a bit using XOR (^), e.g. 1 ^ 1 = 0 and 0 ^ 1 = 1. Remember to clear the flag and use NVIC_ClearPendingIRQ for the corresponding CPU interrupt when you’re done with the ISR.

    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 step5.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 and copy in the following code. For the TODO line, pick your favorite peripheral register and replace the R_PERIPHERAL->REG expression with it. Uncomment that line and run the code, making sure to save the output somewhere that all group members can access it. If some of the syntax or keywords look unfamiliar, talk through them or ask a TA – not everyone in the course has taken or remembers their intermediate systems course. The syntax *addr assumes a memory address is stored in the variable addr and gives you the value stored at that address. Conversely, the syntax &val assumes val is a variable (or function name, or other piece of data that might exist in memory) of some sort and gives you the address of that piece of data.

    Note: if you upload the code and nothing appears on the serial monitor, single-press the reset button on the Arduino.

       int global1 = 1;
       int global2 = 4;
    
       int* addr;
       int* addrStatic;
    
       void setup() {
          int local1 = 5;
          char local2 = 'b';
          unsigned long local3 = 0xdecafbad;
    
          Serial.begin(9600);
          while(!Serial);
    
          Serial.print("global1 addr: ");
          Serial.println((unsigned int) &global1, HEX);
          Serial.print("global2 addr: ");
          Serial.println((unsigned int) &global2, HEX);
          Serial.print("local1 addr: ");
          Serial.println((unsigned int) &local1, HEX);
          Serial.print("local2 addr: ");
          Serial.println((unsigned int) &local2, HEX);
          Serial.print("local3 addr: ");
          Serial.println((unsigned int) &local3, HEX);
    
          Serial.print("loop addr: ");
          Serial.println((unsigned int) &loop, HEX);
          Serial.print("helper addr: ");
          Serial.println((unsigned int) &helper, HEX);
    
          // TODO: print out the address of a peripheral register
          //Serial.print("register addr: ");
          //Serial.println((unsigned int) &(R_PERIPHERAL->REG), HEX); // TODO
    
          helper();
          Serial.println("**BACK IN SETUP**");
          Serial.print("addr value: ");
          Serial.println((unsigned int) addr, HEX);
          Serial.print("addr dereference: ");
          Serial.println((unsigned int) *addr);
    
          Serial.print("addrStatic value: ");
          Serial.println((unsigned int) addrStatic, HEX);
          Serial.print("addrStatic dereference: ");
          Serial.println((unsigned int) *addrStatic);
       }
    
       void helper() {
          int helperLocal = 1600;
          static int helperStatic = 40;
          addr = &helperLocal;
          addrStatic = &helperStatic;
          Serial.println("**INSIDE HELPER**");
          Serial.print("helperLocal addr: ");
          Serial.println((unsigned int) &helperLocal, HEX);
          Serial.print("helperLocal value: ");
          Serial.println(helperLocal);
          Serial.print("helperStatic addr: ");
          Serial.println((unsigned int) &helperStatic, HEX);
          Serial.print("helperStatic value: ");
          Serial.println(helperStatic);
       }
    
    
       void loop() {
          Serial.println("A print in the loop");
          while(true); // don't loop again
       }
    
    1. At the very end of the setup() function, include the following code. Before running it, make a prediction of what will happen.

      int* corruptLoop = (int*) &loop;
      *corruptLoop = 32580; // can use any nonsense value
      Serial.print("corruptLoop: ");
      Serial.println((unsigned int) corruptLoop, HEX);
      Serial.print("corruptLoop dereferenced: ");
      Serial.println(*corruptLoop);
      

      Run the code and make note of what happened.

  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.