// Interrupts // Logistics: // 1. Design reviews today // - Use more pictures guys // - My office // 2. Presentation coming up // 3. Order parts immediately // - (after project approved if you must) // An annoying thing // Spending a _lot_ of cycles polling void main(void) { // ... set up timer 1 &c &c while (1) { while (TMR1 < 1000) {} PORTAbits.RA0 = !PORTAbits.RA0; TMR1 = 0; } } // Not a big deal if you don't have much to do, // but what if you want to get some processing done? // e.g.: Robot doing mapping _and_ listening to inputs // Interrupts disrupt the normal flow of code // - A special case of an _exception_, e.g.: reset // - Handled by the interrupt controller hardware // // Hardware Flag (IRQ): idle flag idle // Interrupt vector: 0000 0010 0000 // Executing Main Code: ---------> ---------> // Executing ISR: ------> // Flag is called IRQ = interrupt request // - Example of flag is given in TIMER1 // - T1IF set when TMR1 = PR (show figure, p. 197) // ISR = interrupt service routine // Needs to clear hardware flag & do it's thing // ISRs live at a _vector_, a spot in memory for insts // - there's also a reset vector // - and a general exception vector (lab 5 bugs) // Two major vector modes for your PICs // -- single vector, all interrupts go to same place // -- figure out what to do in software @ vector // -- e.g.: poll all interesting flags in order // -- multi vector // -- hardware picks a memory location to dispatch to // -- different location for each interrupt // -- ... kind of. Lots of interrupts, so some share // -- write multiple ISRs, one for each vector // Big tradeoffs between them, multi-vec more modern // Single Vector Example void __ISR_SINGLE() SingleVecHandler(void) { while(INTSTAT) { if IFS0bits.T1IF { /* do a thing */ } else if IFS0bits.T2IF { /* do a different thing */ } } } // Multi-Vec example void __ISR(_TIMER_1_VECTOR, ipl7AUTO) Timer1Handler(void) { /* do a thing */ } void __ISR(_TIMER_2_VECTOR, ipl6) Timer1Handler(void) { /* do a different thing */ } // This requires interrupt priority levels to be set right // - Talk about it later // Your context has to go somewhere during an interrupt // Two major targets - the stack and the shadow set // Shadow set is 32 registers which mirror the main // 32 in MIPS architecture. Quick save as you enter // interrupt. // From C, stuff will wind up on the stack. Don't need // to explicitly save context. Macros can set up shadow // From assembly, be careful. // It is tempting to hand optimize interrupts for absolute low latency // So what if two interrupts go off at once? // System needs a way to decide between them // User assigned interrupt priority // 7 priority levels, 4 sub levels, and native priority // - on pic16/pic8 systems you only get native priority // Denoted by iplx in our macro // - must match priority in IPCx or compiler is weird // - - generally, need to have ISR w/ priority of interrupt // - sublevel separately set by INTSetSubPriority macro void __ISR(_TIMER_2_VECTOR, ipl6) Timer1Handler(void) { /* do a different thing */ } // Setting registers or use macros/libraries? Latter more portable // The ISR must clear the IRQ flag that triggered it, // - o/w it will be re-triggered (or ignored ...?) // Aside: software can trigger interrupt by setting flag // Aside: persistent flags can't be cleared // BAD void __ISR(_TIMER_2_VECTOR, ipl6) Timer1Handler(void) { /* do a different thing */ } // GOOD void __ISR(_TIMER_2_VECTOR, ipl6) Timer1Handler(void) { /* do a different thing */ IFS0bits.T2IF = 0; } // Registers to care about // - INTCON - Interrupt control, // - notably MVEC, shadow set single vec, external polarity // - INTSTAT - Interrupt status, priority & vector sent to core // - IFSx - Interrupt flag status (1 bit/interrupt) // - IECx - Interrupt enable control (1 bit/interrupt) // - you kids don't deal with GIE/PIE // - IPCx - Interrupt priority control (5 bits/interrupt) // - TPTMR - Temporal proximity timer reload value // - DEVCFG3 - shadow set assign in multi-vec mode // - CP0 - system control coprocessor (Scary!) // - contains Status.IE bit (interrupt enable) // - EBASE - exception base address // Look at interrupt register space p 69 // Look at interrupt table p. 131 // Library functions to care about // -- docs in MPLABX --> Help --> Contents --> // Language Tools --> Peripheral Libraries --> // Interrupts // - INTEnableSystemSingleVectoredInt(); // - INTEnableSystemMultiVectoredInt(); // - __ISR( vector, priority) // - vector and priority defined in header // - e.g.: ipl6, _TIMER_1_VECTOR // Exercise, write an interrupt that toggles an LED // every 10ms and raises RA0 if it detects a rising edge // from an external "brake" module // - note: external interrupts 0-4 live on pins 72,18,19,66,67 // Step 1: What registers should you write to? // -- Do you need a timer? Which one? #include //#include #include void main(void) { //OpenCoreTimer(10000000); // Open the core timer // Initialize Interrupt Registers INTCON = 0x00000001; // Set single vec mode, rising edge on ext int 0 IPC1 = 0x0000001F; // Timer 1 IPL = 7, Sub = 3 IFS0bits.T1IF = 0; IEC0bits.T1IE = 1; // Enable Timer1 Interrupt IPC1 = IPC1 | 0x1F000000; // External Interrupt IPL=7, SUB=3 IFS0bits.INT0IF = 0; IEC0bits.INT0IE = 1; // Enable external interrupt 1 // Library functions would have made this easier // INT_ENABLE( INT_SOURCE_EX_INT0, ENABLE) // INTSetVectorPriority( _EX_INT0_VECTOR, 7) // INTSetVectorSubPriority( _EX_INT0_VECTOR, 3) // Configure Timer1 T1CON = 0x00000000; // T1 Off, PBus/1 Clock TMR1 = 0; // Initialize to count of 0 PR1 = 0x20; // 100mS timer TRISB = 0x0000; PORTBbits.RB0 = 0; PORTBbits.RB1 = 0; //INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR); INTEnableSystemMultiVectoredInt(); //INTEnableSystemSingleVectoredInt(); //INTEnableInterrupts(); T1CONbits.ON = 1; while (1) {} } void __ISR(_TIMER_1_VECTOR, ipl7) Timer1Handler(void) { //ipl7AUTO //void __ISR_SINGLE() Timer1Handler(void) { if(IFS0bits.T1IF) // If interrupt was cause by Timer 1 PR match { PORTBbits.RB1 = 0; PORTBbits.RB0 = !PORTBbits.RB0; TMR1 = 0; // Initialize to count of 0 IFS0bits.T1IF = 0; } } // Not sure of this vector name void __ISR(_EX_INT0_VECTOR, ipl7) BrakeModuleHandler(void) { PORTBbits.RB1 = 1; IFS0bits.INT0IF = 0; } // What exactly happens in an interrupt // 1. IF is set, often on rising edge of clock // 2. All IRQ sampled on rising edge of clock // - note the 1 cycle delay // 3. IFSx & IECx = 1 --> further processing // 4. IRQ encoded to vector & assign IPC / SS0 // 5. Controller picks highest priority & sends to core // - sets in INTSTAT // 6. Core samples between "E" and "M" pipe stages // Compares priority to current IPL bits (status<12:10>) // If higher serviced, else pending // - note, prox timer helpful here // 7. PC-->EPC, EXL set, branch to vector, // 8. ISR // 9. ERET assembly command returns, clears EXL // Tricky interrupt problems // - Why aren't you polling in main loop? // - Only really care if need latency guarantee // - Latency, esp. variable latency // - multi vec latency slightly bigger, but _consistent_ // - generally faster if you have lots of different interrupts // - one priority gets the shadow set, they are faster // - interrupt size // - assembly needs to fit in the space before next interrupt // - there's a programmable offset mode on other pics // - but your ISR is probably too slow in that case // - If you're using assembly be very careful with context // - especially w/ single vector, neted interrupt // - 2 day debug: I had interrupts clobbering a register // Examples of some interrupts TODO: Figure out proximity timer Walk through exact process for them asm("ei") Some interrupt examples -Timer, UART, PWM / Servo