Lab 4B: Take a Step and Enter a Room/ External Interrupts
At this point in your design, the 7-segment LEDs always changes when a switch is toggled (room and direction) on or off. In this lab, you are going to use and external interrupt service routine (ISR) to control when the SRAM variable room is updated When you are done, each step for navigating the maze will be initiated by pushing a button.
ASK ME HOW TO HANDLE JUMPS OUT OF RANGE.
Table of Contents
What is New?
Here are some of the new concept(s) you will be exploring in this lab.
- Working with External Interrupts
No new instructions or assembly directives are introduced in this lab.
Debounce Circuit
Configure I/O Port Pins
As shown in the Protoshield schmatic, Port D pin 5 is wired to the positive edge triggered clock input of our D flip-flop. Port D pin 2 is wired to the Q output of the D flip-flop. To make your code more readable, add the following mnemonic equates after the include (.INCLUDE ) assembly directives at the top of your program.
; Pushbutton switch ports .EQU dff_Q = PD2 // Q output of debounce flip-flop (PD2) .EQU dff_clk = PD5 // clock of debounce flip-flop (PD5)
Within the initialization section (reset) of your program configure these two Port D pins wired to the debounce flip-flop as defined by Table 13-1 in the ATmega328P data sheet.
; Initialize push-button debounce circuit pins | Table 13-1 sbi DDRD, dff_clk // flip-flop clock | 1X = output from AVR cbi DDRD, dff_Q // flip-flop Q | 00 = input to AVR w/o pull-up cbi PORTD, dff_Q // flip-flop Q loop:
Write Pulse subroutine
To create a 4 Hz low pass filter, we will pulse the clock input of the D flip-flop every 250 ms. Not coincidentally, this is the same frequency at which the direction LED blinks (4 Hz).
Add the following subroutine in the Support Subroutines section of your program.
Next, add a call to the Pulse subroutine (rcall Pulse) after the call to Delay in the Main loop. Now let’s see if everything is working.
Green LED Test Sequence
When you press the reset button, the Green LED on your board should turn on for a short period of time before your program starts running at which time it should turn off. Try it now! If it does not momentarily turn ON, then most likely we are looking at a problem with the circuit.
No, Green LED does not turn on…
Open the detailed circuit schematic of the proto-shield. Using a digital multimeter verify that your 74ALS74 IC is getting five volts (pin 8 is GND and pin 14 Vcc).
IC is not getting Power
It the IC is not getting power trace back (see detailed schematic) to find what parts if any have power. From this information you can hopefully find out where the problem is. For example: power pin is not soldered. Next check if after reset the /Q1 output is high (pin 6).
IC is getting Power
If it is getting power you need to check if reset was applied. Using the multi-meter verify that the CLR input (pin 1 1CLR) to the 74ALS74 is swinging between 5v and 0v (approximately) when you push and release the reset button.
Reset (CLR input) is not toggling
It the CLR input is not being toggled, then trace back (see detailed schematic) to find out why button pin is not pulled up (resistor not wired correctly) or signal is not getting to the IC.
Yes, Green LED turns ON…
Once your program starts running the Green LED should turn off. If it does not then most likely Port D pin 5 is not being clocked. Did you remember to call pulse from your main loop? If pulse is being called correctly, then modify the pulse subroutine to toggle one of the discrete LEDs.
When you press the button the Green LED should turn ON, after a slight delay and then turn OFF when the button is released. A failure here most likely means that the button is not connected to the input of the flip-flop. Using your multimeter, verify at the input to the D flip-flop that when the button is pressed the voltage swings to 0 v.
External Interrupt Service Routine (ISR)
Section 11 “Interrupts” of the ATmega328P data sheet provides detailed instructions on how to configure the ATmega 328P processor to support external interrupts. We will be using External Interrupt Request 0 (INT0) to interrupt the processor whenever the user presses the button wired to our debounce circuit, the output of which is wired to Port D pin 2 (see proto-shield schematic).
Configure External Interrupt 0 registers EICRA and EIMSK
Let’s begin by looking at what the conditions need to be to trigger an external interrupt. Specifically, we need to tell the processor which external interrupt is enabled and if the interrupt is triggered based on the level or edge of the input signal. The ATmega 328P supports two external interrupts which are individually enabled by setting bits INT1 and INT0 in the External Interrupt Mask Register (Section 12.2.2 EIMSK).
For External Interrupt 0 bits ISC01 and ISC00 of the External Interrupt Control Register (Section 12.2.1 EICRA) determine if the interrupt is triggered based on the level or edge of the input as defined in Table 2 “Interrupt 0 Sense Control”.
We want to trigger our external interrupt on “The falling edge of INT0” so we want to set ISC01:ISC00 = 102 (the colon simple means these two bit together). Insert the following code in the reset section of your program to configure the External Interrupt Mask Register (EIMSK).
The location of this code snip-it is not important as long as it is in the reset (before loop label) section of your code. What is important is the location of the “Set the Global Interrupt” Flag (I) in SREG (Status Register) instruction (sei) . As a general rule, I enable interrupts (using instruction sei) as the very last step in the initialization process – just before the label loop. For example:
sei // Global Interrupt Enable loop:
Add INT0 to Interrupt Vector Table
After enabling external interrupt zero (sbi EIMSK, INT0) and all interrupts (sei), if an interrupt is asserted on the INT0 line then the processor will automatically jump to location 0x0002 (INT0addr) in the Interrupt Vector Table (IVT) located in Flash Program memory. Before it jumps to this location it will also save the current location of the Program Counter (PC) so it can return to the interrupted program.
Add a jump to our INT0 Interrupt Service Routine at this location in the IVT.
RST_VECT: rjmp reset ; ----- Interrupt Vector Table (IVT) ----- .ORG INT0addr // 0x0002 External Interrupt Request 0 jmp INT0_ISR // Sect 4.7 Reset & Sect 9.4 Interrupt Vectors in ATmega328P .ORG 0x0100 // bypass IVT
Table 11-6 “Reset and Interrupt Vectors in ATmega328P” provides a complete list of all 26 interrupts supported by our microcontroller. With respect to the above code, notice that our Interrupt Vector Table now implements one of these interrupts. At this time you may want to read the Owner’s Manual located near the end of the lab.
Write INT0 Interrupt Service Routine
As discussed in the Owner’s Manual we only want to display the room in the maze that the bear is entering after the button is pressed. We will implement this change by adding a third state (S2) to the FSM designed in part A of the lab. To transition to this third state, a new variable named walk will be set to true. Once defined and initialized, the ISR will set this variable.
Step 1: Add an assembly directive equating the text string “S2” to the number 0b10
.EQU S0 = 0b00 // state 0 .EQU S1 = 0b01 // state 1 .EQU S2 = 0b10 // state 2
Step 2: After this equate, add an assembly directive equating the text string “true” and “false” to 0xFF and 0X00 respectively.
; true and false .EQU true = 0xFF .EQU false = 0x00
Step 3: Define variable walk
room: .BYTE 1 dir: .BYTE 1 next_state: .BYTE 1 // FSM next state walk: .BYTE 1
Step 4: Initialize the variable
clr r16 sts dir, r16 sts room, r16 sts next_state, r16 sts walk, r16 // do not walk
Step 5: Add the following external interrupt service routine, which updates the variable room. Place just after the main loop.
rjmp loop ; ---- External Interrupt 0 Service Routine ----------------- ; Called when a falling edge is asserted on the INT0 pin (PIND2) ; INTF0 flag automatically cleared by AVR on vector interrupt ; SRAM Variable room is modified by this ISR INT0_ISR: push reg_F in reg_F,SREG push r16 ldi r16, true sts walk, r16 pop r16 out SREG,reg_F pop reg_F reti
Software Implementation of a 3-State Finite State Machine
Now lets add a third state (S2) to the 2-state FSM built in the part A of the lab.
We have just written the ISR to set the variable walk to true when the user presses the programmable button on the protoshield. As seen in FSM state transition diagram above, when walk is true, we want to transition to our new S2 state. Within S2 we want to set the walk variable back to false, takeAStep, and display the room only.
Step 1: Move the code that assigns the switches to the room in the main loop to a new subroutine named takeAStep.
loop: call ReadSwitches // read switches into r7mov r17, switch // move switch (r7) to temporary register r16cbr r17, 0x0F // mask-out least significant nibbleswap r17 // swap nibblessts room, r17 // save formatted value to SRAM variable room// dir = switch & 0x03; mov r17, switch // move switch (r7) to temporary register r16 cbr r17, 0xFC // mask-out most significant 6 bits sts dir, r17 // save formatted value to SRAM variable dir. : rjmp loop : TakeAStep: push r17 call ReadSwitches // read switches into r7 mov r17, switch // move switch (r7) to temporary register r16 cbr r17, 0x0F // mask-out least significant nibble swap r17 // swap nibbles sts room, r17 // save formatted value to SRAM variable room pop r17 ret
Step 2: Update the next state decoder for state S1 to reflect the FSM
state_S1: cpi r20, S1 brne case_S2 // output decoder : // next_state decoder lds r17, walk tst r17 ldi r16, S0 // guess don't walk breq end_S1 ldi r16, S2 // wrong guess, walk end_S1: sts next_state, r16 rjmp end_switch // break
Step 3: Write your new S2 state.
state_S2: cpi r20, S2 // test is optional for this lab brne end_switch // output decoder write the code to set walk to false, TakeAStep, and DrawRoom // next_state decoder write the code to set next_state equal to S1
Lesson Learned
By Nicholas Lombardo (F’14)
I have helped several people with debugging and one thing that I saw repeatedly was people using “lds r16, false” when they should be using “ldi” since false is equated to a constant 0x00. I assume that what ends up happening is that the compiler loads the register at ext. I/O address 0x00 which, if I recall correctly, is r0.
It can be a little confusing the first time around, but using the bug usually manifests in the program looping between state 1 and state 2 and is very hard to catch unless you know what you’re looking for. The first time I helped someone with this, it took about an hour of scouring the code and back-stepping until we found out the fix which was literally changing a single letter.
Test Your Program
You may upload your program to quickly check to see if it works. An updated Owner’s Manual is provided in the next section to help you step through the maze. Follow the steps in the Owner’s Manual to check the operation of your program. I would be surprised if your program works the first time; if it does not, you will need to simulate the steps defined in the Owner’s Manual. See “Simulation and Debugging” for help here.
Owner’s Manual
Follow these steps to navigate a maze.
- Download and run the program. The bear should be shown at the entrance of the maze – an empty room. The direction you want the bear to walk should also be blinking. Specifically, toggle switches SW1 and SW0 to tell the bear to walk north. You will not be using switches SW3 or SW2 in this lab – set them both to 0 (no turn).
- Using the maze from Lab 1 as a guide and following the direction indicated by the seven segment display, input the next room the bear will be entering by toggling switches SW7 to SW4. The room LEDs on the 7-segment display should not be updated as you toggle the switches.
- Push the button to provide the bear with the directions. You should now see the room you entered in step 3 displayed along with the direction in which the bear was told to walk (note: the direction may not be visible if it is obscured by a wall).
- Toggle switches SW1 and SW0 to tell the bear which way to walk next. The blinking direction LED on the 7-segment display should be updated as you toggle the switches.
- Repeat steps 2 through 4 to verify the basic operation of the program. You do not need to guide the bear all the way to the forest.
Simulation and Debugging
Enter and verify the basic operation of the program using AVR Studio. Remember, before you run any program you should know what values will be contained in the effected registers. Tip: Within the simulation window expand the PortD, EXTERNAL_INTERRUPT, and TIMER_COUNTER_1 items in the I/O View window to easily initiate an interrupt and to view the operation of your program. To initiate an interrupt you can simulate the button being pushed by toggling PortD PIND bit 2.
- Stop program execution . In the I/O View window select PORTD. In the bottom window check PIND bit 2. This simulates pressing the button down.
- Single step the program once or twice. Then uncheck PIND bit 2. This simulates releasing the button. The external interrupt is configured to trigger on the falling edge of the input signal.
- Single step the program at least twice to initiate the call to the interrupt. You should now see the program counter go to the INT0 address (INT0addr) within the interrupt vector table (IVT).
Note: Unlike Timer Interrupts you cannot initiate an external interrupt by directly setting the external interrupt flag.
After you have verified the basic operation of your circuit, and your INT0 interrupt service routine (ISR), upload and run your program on the Arduino.
Lab Demonstration
You may be asked to…
- Simulate an external interrupt
- Demonstrate using AVR Studio that the SRAM variable room is updated every time the button is pressed.
- Demonstrate the correct operation of your program as defined in your “Owner’s Manual” found at the beginning of this lab.
Design Challenge – Timer 0 (4 Points)
You can skip these sections if you are happy receiving a passing grade (“C+”) on the lab. If you want to receive a good or excellent grade you will need to accept the challenge.
You may have already discovered that if you press the button quickly enough the new room is not updated. The cause of this intermittent behavior comes from the current design clocking the D flip-flop every 250 ms (4 Hz). To solve this problem, apply what you learned in Lab 4A and lecture to call the pulse subroutine every 10 msecs (100 Hz). This time is fast enough to catch the button being pressed, while also filtering out any switch bounce. There are two methods for implementing this, which are modifying the existing delay subroutine or to utilize the overflow interrupt.
Here are some tips to help you translate your code from Lab 4A’s Timer 1 to Lab 4B’s Timer 0
Calculate Timer 0 Load Value
While Timer 1 is a 16-bit timer, Timer 0 is only an 8-bit timer. This introduces a number of problems. First, an 8-bit timer can not generate a 10 msec delay with the clock divided by 64. Fortunately, it can with the clock divided by 1024. Next, instead of subtracting by 216 = 65,536, you now will want to subtract by 28 = 256. Next, you will be reloading TCNT0, a single 8-bit register and not a 16-bit register (8 bit registers TCNT1H and TCNT1L).
Setup and Timer 0 Polling
Probably the most subtle difference between the two timers is the mapping of the registers. All Timer 1 Registers (TCCR1B, TCNT1H, TCNT1L, and TIMSKI) are outside the I/O address space of the ATmega 328P. Consequently, you used the Store to SRAM (sts) assembly instruction for accessing these registers. In contrast Timer 0 Registers TCCR0B and TCNT0 are within the I/O address space of the ATmega 328P, while TIMSK0 remains outside the I/O address space. The main consequence of this mapping is the need to use the I/O output (out) instruction, in place of the sts instruction when writing to registers TCCR0B and TCNT0.
Do not forget to remove your call to the pulse subroutine from the end of the main loop.
For the first method of modifying the existing delay subroutine, consider how the polling routines work. We still require the 250 msec delay for the transition between states in the main loop. During that time, the program is unable to check the status of Timer 0. It is not possible to call one polling routine during another. There will be some modification required to generate the 250 msec delay and call the pulse subroutine every 10 msec.
For the overflow interrupt method, determine the registers that need to be modified and figure out the additional code that needs to be added.
Lab 4 Deliverable(s)
STOP Read the Lab READ ME document contained in the Labs Folder. Be absolutely sure you have followed all instruction in the “Lab Formatting” section of this document. Points will be deducted if you do not follow these instructions. You have been warned.
Make sure your lab notebook is up to date. Follow the guidelines provided in the “Lab Notebook” section of the Lab READ ME document.
Make sure you have read and understand the “Plagiarism” section of the Lab READ ME document, especially if you are repeating the class.
All labs should represent your own work – DO NOT COPY.
Table of Contents
- Basic Lab
a. Main Program (Title Block, Initialization (setup), Main Loop (loop))
b. Interrupt service routine INT0_ISR
c. Subroutines (TakeAStep, Delay, and Pulse) in that order. - Design Challenge – if applicable