TI MSP430 Polling, Interrupts, ISRs 1 Polling, Interrupts • Polling • Εισαγωγή στα Interrupts • Παραδείγματα 2 Polling vs Interrupt 3 Interrupt Service Routines in Assembly • Το υποπρόγραμμα εξυπηρέτησης διακοπής είναι αρκετά απλό. (Παρατηρήστε την εντολή “reti”.) • Αποθηκεύει την διεύθυνση της συγκεκριμένης ISR (PORT1_ISR) στην κατάλληλη θέση του πίνακα διανυσμάτων (vector table) με την οδηγία (directive) ORG. • Το PORT1_VECTOR ορίζεται στο αρχείο επικεφαλίδας (header file) αναφορικά με το INTVEC segment. Η οδηγία COMMON πρέπει να χρησιμοποιηθεί ώστε η καθορισμένη διεύθυνση (PORT1_ISR) να τοποθετηθεί στην σωστή θέση. Αυτό είναι χρήσιμο ώστε να ορίζουμε 4 ISRs σε πολλαπλά αρχεία. PORT1_ISR: ; respond to inputs on port1 reti ; return from interrupt ORG DW 0FFE8 PORT1_ISR -- or -COMMON ORG DW INTVEC PORT1_VECTOR PORT1_ISR Interrupt Service Routines in C // tell the compiler the vector for this ISR #pragma vector = PORT1_VECTOR __interrupt void port1_isr(void) { // respond to inputs on port1 } • Η οδηγία “#pragma vector” πληροφορεί τον compiler για την αποθήκευση της διεύθυνσης του υποπρογράμματος στην θέση PORT1_VECTOR. • Η οδηγία“__interrupt” πληροφορεί τον compiler ότι αυτή η συνάρτηση πρέπει να τελειώνει με την εντολή “reti”. Το όνομα της συνάρτησης port1_isr δεν έχει σημασία… • 5 Interrupt Vector Jump Table • Ο πίνακας των διανυσμάτων πληροφορεί το πρόγραμμα για το που θα συνεχίσει όταν συμβεί ένα interrupt. • Ένα διάνυσμα- vector δείχνει στην διεύθυνση όπου ξεκινά το πρόγραμμα εξυπηρέτησης της διακοπής (interrupt service routine). • Ο πίνακας διανυσμάτων διακοπών για τον MSP430 είναι αποθηκευμένος στην μνήμη RAM από 0xFFC0 έως 0xFFFF και μπορεί 6 να τροποποιηθεί. MSP430 Interrupts 7 MSP430 Status Register • GIE (bit 3): Global Interrupt Enable – 1: Ενεργοποιεί όλες τις διακοπές (Οι διακοπές πρέπει επιπλέον να ενεργοποιηθούν η καθεμία ξεχωριστά!!!) – 0: Απενεργοποιεί όλες τις διακοπές (Disable all interrupts ) Σημείωση: Ο καταχωρητής κατάστασης SR σώζεται στον σωρό (stack) και καθαρίζεται (απενεργοποιούνται οι διακοπές!!!) όταν εκτελείται μια ISR . 8 Setting and Clearing the General Interrupt Mask • Assembly EINT – Enable interrupts (set GIE) DINT – Disable interrupts (clear GIE) • C asm(“ asm(“ EINT”); DINT”); • C (#include intrinsics.h) __enable_interrupt(); __disable_interrupt(); 9 ECE447: MSP430 Interrupt execution flow RAM JUMP TABLE 0xFFFF EE PC SR 10EE 00F8 10 0000 0xFFC0 STACK F8 00 SP RAM SERVICE ROUTINE 0x10EE 10 MSP430 Interrupt execution flow 11 Interrupts Example: Toggling LEDs // Listing 6.5: Toggles LEDs in ISR using interrupts from timer A CCR0 // in Up mode with a period of about 0.5s #define LED1 BIT3 #define LED2 BIT4 void main (void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer P2OUT = ~LED1; // Preload LED1 on, LED2 off P2DIR = LED1 | LED2; // Set pins with LED1,2 to output TACCR0 = 49999; // Upper limit of count for TAR TACCTL0 = CCIE; // Enable interrupts on Compare 0 TACTL = MC_1 | ID_3 | TASSEL_2 | TACLR; // Set up and start Timer A // “Up to CCR0” mode, divide clock 8, clock SMCLK, clear timer __enable_interrupt ( ); // Enable interrupts (intrinsic) for (;;) { // Loop forever doing nothing } // Interrupts do the work } // Interrupt Service Routine for Timer A channel 0 #pragma vector = TIMERA0_VECTOR // Assoc. the funct. w/ an interrupt vector __interrupt void TA0_ISR (void) // name of the interrupt function (can be anything) { P2OUT ^= LED1 | LED2; // Toggle LEDs 12 } MSP430 Interrupts 13 14 Interrupt processing 15 MSP430 Interrupts 16 Interrupts 17 Προτεραιότητες interrupts 18 Interrupt vector addresses 19 Stack 20 Μηχανισμός Interrupt 21 Μηχανισμός Interrupt (…) 22 23 MSP430 Interrupt Exercise • Write a program that collects samples from input Port 2. A signal line that indicates a new data value is available is connected to pin P1.0. Use an interrupt to detect a rising edge on the P1.0. When an interrupt is triggered the value on Port2 should be stored into a byte array of 32 entries. When 32 samples have been collected, P1.7 should be driven as an output high to indicate the memory is “full” 24 25 26 27 28 29 BITFIELDS Time Based Processing on an MSP430 Launchpad I recently saw a video on MyBitBox on using bitfields in time based processes and thought it was and excellent example of a fairly advanced topic in embedded systems. In that blog, an mBed is used, which is an ARM based dev kit. I thought it would be useful to port this example to the MSP430 Launchpad. In this example, I’m using the MSP430G2231, but any MSP430 with Timer_A can be used. This example is used to show that microcontrollers can be setup to run several tasks at multiple intervals. The desired result is a system that executes these commands deterministically, meaning that at the desired time interval a routine WILL execute. this is useful when a microcontroller needs to multitask or divide a workload. For this example, we’re going to setup a routine to run every second and another routine to run every 100ms. There are two LEDs on the Launchpad, so for this example we’ll toggle one LED at 100ms and the other at 1 second. The first thing to do is define a struct with a bitfield to set flags whenever a time interval is reached. 30 http://busted240sx.wordpress.com/2011/11/27/time-based-processing-on-an-msp430-launchpad/ typedef struct time_flags{ unsigned time_1ms :1; unsigned time_100ms :1; unsigned time_1s :1;}time_flags;extern volatile struct time_flags sys_time; These flags will be set by an interrupt service routine that executes every millisecond. This ISR increments several counters. counter_base increments every millisecond until it reaches 100 and is reset. counter_100ms increments every 100ms until it reaches 10 (1 second), then it is reset. whenever we reach these desired increments we set the flags of the structs we created earlier. This method can be extended as much as desired so you could have an hour counter or day counter if you wanted. 31 #pragma vector=TIMERA0_VECTOR__interrupt void Timer_ISR(void){ sys_time.time_1ms = TRUE; counter_base++; if(counter_base>=100) { counter_base=0; sys_time.time_100ms = TRUE; counter_100ms++; //this is for 1sec if(counter_100ms>=10) { counter_100ms=0; //counter_1s++; sys_time.time_1s = TRUE; } } return;} 32 In a separate file we’ll setup our main functions and the routines we wish to call. After turning off the Watchdog timer, we setup Timer A which we’ll use to trigger the interrupt. We set Timer A to Up mode, which will generate an interrupt when the timer reaches the value of CCR0 and then reset the timer. By default, the SMCLK is setup for 1MHz, so I didn’t have to divide the clock down or change the source. If you want to use a different clock, go right ahead but be aware that the value of CCR0 will have to be changed. the while(1) starts an infinite loop that the microcontroller will never break from. This is known as the “idle loop”. The ISR will handle all the counting and setting of the flags and most of the time, the MSP will just loop through this and check the flags status. If the flag is set, a routine is called and the flag is cleared. 33 void main(void){ WDTCTL = WDTPW + WDTHOLD; //stop watchdog TACTL = TASSEL_2 + MC_1 + TAIE; //Timer A, SMCLK, up mode, enable interrupts P1DIR |= BIT0 + BIT6; CCTL0 = CCIE; //CCR0 Interrupt enable CCR0 = 1000-1; //SMCLK/CCR0 = 1MHz/1000 = 1kHz _BIS_SR(GIE); //enable global interrupts TRUE) while(1) //idle loop { if(sys_time.time_1ms == TRUE) { //main_1ms_routine , if needed sys_time.time_1ms = FALSE; } if(sys_time.time_100ms == TRUE) { main_100ms_routine(); sys_time.time_100ms = FALSE; } if(sys_time.time_1s == { main_1s_routine(); sys_time.time_1s = FALSE; } } } 34 Finally, we define the routines we want to run. Here, we’re just blinking LEDs, but it could be anything. On the Launchpad, the Green LED is on Port 1 Pin 6, so I set Bit 6 to toggle with an XOR every second. The same is done with the red LED on Port 1 Pin 0 every 100ms. void main_1s_routine (void){ //toggle Green LED P1OUT ^= BIT6;}void main_100ms_routine (void){ //toggle Red LED P1OUT ^= BIT0;} 35 Thats it! We’ve setup a timer to trigger an interrupt every millisecond, defined the flags and counters, incremented the counters and set the flags in the ISR, and created routines to run based on the flags that are set. Now, we Build the code and watch it run! You can download the full source code here. Thanks. Big thanks to myBitBox for enlightening me to this method. 36 37 MSP430 Launchpad - Low Power Thermometer Code TI has a really low cost development platform called launchpad that I have been playing with recently. It is $4.30 with free shipping until august and it has some crazy low power capabilities. Below is my first program beyond a blinky. It sleeps in the second to lowest power mode then wakes up, reads the temperature, counts it out on 2 LEDs on the dev board then goes back to sleep. Other than driving the LEDs, this should use so little power that batteries could conceivably die from age as fast as they are used if I cranked the sleep all the way up. It also has the advantage of working on a launchpad in factory condition with no additional parts. . 38 http://bill-landers.blogspot.com/2010/08/msp430-launchpad-low-power-thermometer.html // the goal was to sleep as long as possible, read temp // then display it through flashing the launchpad leds // // when not flashing or measuring go to the lowest power reasonable // #include "msp430x20x2.h" //unsigned short cc = 65470; //unsigned short cc = 1000; unsigned short cc = 10000; //longest CCR0 //debugging CCR0 value //interval between tests - CCR0 value long IntDegF; //this is all setup void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop WDT BCSCTL3 |= LFXT1S_2; // make clock work without crystal P1DIR |= 65; // P1.0 & 6 output P1OUT = 65; // lights on so we know we are started CCTL0 = CCIE; // CCR0 interrupt enabled CCR0 = cc; // set the long interval TACTL = TASSEL_1 + MC_1 + ID_3 ; // SMCLK, upmode, / 8 _BIS_SR(LPM3_bits + GIE); } // Enter LPM3 w/ interrupt 39 //this captures the temp value void dotemp(void) { WDTCTL = WDTPW + WDTHOLD; // Stop WDT ADC10CTL1 = INCH_10 + ADC10DIV_3; // Temp Sensor ADC10CLK/4 ADC10CTL0 = SREF_1 + ADC10SHT_3 + REFON + ADC10ON + ADC10IE; ADC10CTL0 |= ENC + ADC10SC; // Sampling and conversion start __bis_SR_register(CPUOFF + GIE); // LPM0 with interrupts enabled // oF = ((A10/1024)*1500mV)-923mV)*1/1.97mV = A10*761/1024 - 468 long temp = ADC10MEM; // raw adc temp IntDegF = ((temp - 630) * 761) / 1024; CCR0 = 999; // set short interval for display } 40 // ADC10 interrupt service routine #pragma vector=ADC10_VECTOR __interrupt void ADC10_ISR (void) { __bic_SR_register_on_exit(CPUOFF); } // Clear CPUOFF bit from 0(SR) 41 / Timer A0 interrupt service routine - this is the main flow control #pragma vector=TIMERA0_VECTOR __interrupt void Timer_A (void) { if (CCR0<1000) //if we are on the short interval we are blinking if (P1OUT > 0) //if the leds are on turn them off { P1OUT = 0; CCR0 = 600; } else //time to count down if (IntDegF == 0) //if we have counted to zero back to the long count CCR0 = cc; else { CCR0 = 100; if (IntDegF > 9) //if there are more than 10 left use the first led { P1OUT = 1; //first led on IntDegF -= 10; //decrement 10 } else { P1OUT = 64; //less than ten, second led IntDegF -= 1; //decrement one } } else dotemp(); //do the temp measure } 42