Editing A Tutorial on PIC interrupts using BoostC including Example Programs
Jump to navigation
Jump to search
Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then save the changes below to finish undoing the edit.
Latest revision | Your text | ||
Line 3: | Line 3: | ||
'''This is still a draft, but mostly right''' | '''This is still a draft, but mostly right''' | ||
− | A nice feature of many ( probably all that you want to use ) PICs is the interrupt feature. This makes some programming tasks much easier, and | + | A nice feature of many ( probably all that you want to use ) PICs is the interrupt feature. This makes some programming tasks much easier, and make make the impossible possible. This article will give some tips on the why and how of using interrupts. Please let me know if you find areas for improvement, or just make them. The code here is extracted from real working projects. Normally it has been somewhat simplified, and some of the declarations and setup are not shown. In all cases there are links to the actual project where all the code is, and, as far as we know works. |
Line 25: | Line 25: | ||
Features of the external interrupt that we will use. | Features of the external interrupt that we will use. | ||
− | The interrupt is | + | The interrupt is trigger by input on RB0, either ( depending on how we set it up ) as the signal goes from 0 to 1, called the rising edge, or as the signal goes from 1 to 0, called the falling edge. This sets the interrupt flag, and if the interrupt is enables ( and we are not already in an interrupt ) the microcontroller goes to the interrupt subroutine. |
Line 35: | Line 35: | ||
===== Setup: ===== | ===== Setup: ===== | ||
− | |||
− | |||
The RBO interrupt -- Clear the flag, set for rising edge, and enable the interrupt. | The RBO interrupt -- Clear the flag, set for rising edge, and enable the interrupt. | ||
− | + | clear_bit( intcon, INTF ); | |
− | + | set_bit( option_reg, INTEDG ); // set for rising edge | |
− | + | set_bit( intcon, INTE ); // set to enable the interrupt | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
Here we assume the interrupt is originally off ( or the global interrupt is off ). If you are not sure then turn them off at the beginning. | Here we assume the interrupt is originally off ( or the global interrupt is off ). If you are not sure then turn them off at the beginning. | ||
Line 60: | Line 47: | ||
In interrupts we almost always make sure that the interrupt is the one we think it is, clear the flag, and do the processing. A key to the post processing is setting the counts and the MinAct flag. | In interrupts we almost always make sure that the interrupt is the one we think it is, clear the flag, and do the processing. A key to the post processing is setting the counts and the MinAct flag. | ||
− | |||
− | |||
if ( intf ) { // are we in the external interrupt. | if ( intf ) { // are we in the external interrupt. | ||
Line 68: | Line 53: | ||
SubSecCount ++; | SubSecCount ++; | ||
− | + | if ( SubSecCount >= 60 ) { | |
− | + | SubSecCount = 0; | |
− | + | SecCount ++; | |
− | + | SecAct = 1; | |
− | + | ||
− | + | if ( SecCount >= 60 ) { | |
− | + | SecCount = 0; | |
− | + | MinCount ++; | |
− | + | MinAct = 1; | |
− | + | ||
− | + | if ( MinCount >= 60 ) { | |
− | + | MinCount = 0; | |
− | + | HrCount ++; | |
− | + | HrCount24 ++; | |
− | + | ||
− | + | if ( HrCount >= 13 ) { | |
− | + | HrCount = 1; | |
− | + | ||
− | + | } | |
− | + | } | |
− | + | } | |
− | + | } | |
− | + | } | |
===== Post interrupt: ===== | ===== Post interrupt: ===== | ||
− | When you get around to it ( but at least every minute ) check the MinAct flag, then do what you have to. | + | When you get around to it ( but at least every minute ) check the MinAct flag, then do what you have to. |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | if ( MinAct == 1 ) { | |
+ | ........ what you have to do ..... | ||
+ | } | ||
− | |||
− | |||
In the full code you also have to have code to move the hands of the clock, set it, etc. It is in there: [[PIC based Stepper Motor Dancing Analog Clock]] | In the full code you also have to have code to move the hands of the clock, set it, etc. It is in there: [[PIC based Stepper Motor Dancing Analog Clock]] | ||
Line 125: | Line 96: | ||
=== The problem: === | === The problem: === | ||
− | An IR transmitter sends bursts of infra red to a receiver. This receiver converts the IR to pulses which ( in the case we will consider the NEC protocol ) consists of about 70 transition over a period of time of about .1 seconds. We want to measure these transitions and determine what button was pressed on the IR transmitter | + | An IR transmitter sends bursts of infra red to a receiver. This receiver converts the IR to pulses which ( in the case we will consider the NEC protocol ) consists of about 70 transition over a period of time of about .1 seconds. We want to measure these transitions and determine what button was pressed on the IR transmitter. |
=== Solution: === | === Solution: === | ||
Line 135: | Line 106: | ||
===== Issues for IR receive ===== | ===== Issues for IR receive ===== | ||
− | + | Issue: How do we avoid tying up the CPU during the receive? | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | Response | + | Response 1: It only takes about .1 sec, so what is the problem? |
+ | A tenth of a second can be a long time on a PIC and actually if you are waiting to receive something this can take the CPU forever ( until the transmitter decides to send something ) | ||
− | + | Response 2: Use interrupts that occur when the state of the IR signal changes, process quickly and go back to other less time critical tasks. | |
− | |||
− | Response | + | Issue: How do we know when to start receiving? |
+ | Response 1: If the IR signal has been quite for a period of time over about .1 sec then the next transition must be the beginning of the signal. | ||
+ | Response 2: If the signal has error checking built in: start receiving wherever check if the data is good. If it is then you must have started at the beginning. In any case you are now at the end and start receiving the next burst at the beginning. | ||
− | Response 3: Some protocols have a special “pulse” or something that we can detect at the end. | + | Issue: How do we no when a received message is finished. |
+ | Response 1: Count the number of transitions in the received signal, done when get up to the required count. But if we miss a transition we could be stuck. | ||
+ | Response 2: Wait until a quiet period signals the end of the signal. | ||
+ | Response 3: Some protocols have a special “pulse” or something that we can detect at the end. | ||
+ | Response 1: | ||
− | |||
− | |||
− | Response 2: Many protocols have a fixed number of transition, | + | Issue: How do we know the data is good |
+ | Response 1: It usually is, hope for the best. | ||
+ | Response 2: Many protocols have a fixed number of transition, count and compare. | ||
+ | Response 3: Some protocols have error checking built in. For the nec protocol the data in the third byte is repeated, inverted in the fourth byte. If you exclusive or ( xor ) the bytes together you should get FF or 11111111. | ||
− | |||
===== The program design ===== | ===== The program design ===== | ||
− | We could have the program go in a tight loop counting the number of times it loops and checking the input port for the transition in the IR signal. Each time it changes we record the count and restart at 0. This is how the program | + | We could have the program go in a tight loop counting the number of times it loops and checking the input port for the transition in the IR signal. Each time it changes we record the count and restart at 0. This is how the program IR.c works. It is called a blocking routine because it blocks the microcontroller from doing any other operation ( except for interrupts ) during the receive. So we will make our routine interrupt driven to end this blocking. |
− | In some interrupt driven programming you can do almost all the work required in the interrupt (blinking | + | In some interrupt driven programming you can do almost all the work required in the interrupt ( blinking a led for example ). But many like this one requires that the work be distributed between the interrupt and non-interrupt processing. And since we jump in and out of the interrupt ( in the interrupt for as short a time as we can ) we can not keep track of what we are doing by where we are in the program, instead we will introduce global state variables. Global because any routine anywhere can access the variables, and state because they tell the state of the processing. One of the most important of these is called IRState. The states that are define for this are: |
− | + | Waiting for the beginning of the receive, perhaps because a signal began before we tried to receive. In the program this value is the #define IRSTATE_WAIT | |
− | + | Busy meaning that we were waiting for a quiet period, but found that the signal was present, thus busy. We could continue to wait, but that might put us in a tight loop of waiting, thus keeping the microcontroller busy. We enter this state so the program know that we could not begin the receive, and to try again when we get around to it. In the program this value is the #define IRSTATE_BUSY | |
− | + | Ready meaning that we waited for no signal, found it, and are now ready to receive. In the program this value is the #define IRSTATE_BUSY | |
− | + | Reading meaning that we are in the process of receiving, the data should be complete in about a tenth of a second. In the program this value is the #define IRSTATE_READ. | |
− | + | Got meaning that we have completed the reading of the signal. In the program this value is the #define IRSTATE_GOT | |
− | + | Not applicable is a special state that means that we are not trying to receive anything. | |
− | So in a normal receive we go through the states in the order | + | So in a normal receive we go through the states in the order wait -> ready ->read->got and then around again. |
− | OK | + | OK so how do we do the programming? I am going to take a bit of an odd approach to the explanation here by focusing on the main part of the receive loop, this is where most of the action occurs, so I think that is a good place to start. Later I will have to explain how we get into and out of this loop. The plan here is to set up an interrupt on the port for the IR receive ( this is port RB0 ) while at the same time a timer is running. When the interrupt occurs we will measure the time by reading the timer. |
− | Features of the external interrupt that we will use | + | Features of the external interrupt that we will use. |
− | The interrupt is | + | The interrupt is trigger by input on RB0, either ( depending on how we set it up ) as the signal goes from 0 to 1, called the rising edge, or as the signal goes from 1 to 0, called the falling edge. This sets the interrupt flag, and if the interrupt is enables ( and we are not already in an interrupt ) the microcontroller goes to the interrupt subroutine. Since we want to time all transitions we will sometimes set the interrupt for the falling edge, sometimes for the rising edge. |
− | Features of the counter/timer we will use | + | Features of the counter/timer we will use. |
− | The timer can be connected to | + | The timer can be connected to crystal clock to count up. It is a two byte counter. We will use only the high byte because we are timing fairly long values. The timer can be turned off or on. We can also divide, or pre-scale the clock, before counting it. This is useful because it allows us to count longer times, but with less precision. |
− | A feature of the C compiler is that all | + | A feature of the C compiler is that all values of the controller that it needs to return to the place where the interrupt occurs are automatically saved at the beginning of the interrupt ( called the “context save” ) and restored at the end. In assembler you need to write the code yourself to do this. |
===== Main Loop ===== | ===== Main Loop ===== | ||
Line 202: | Line 171: | ||
====== Interrupt: ====== | ====== Interrupt: ====== | ||
− | The first thing to remember is that there are several reasons that the program can end up in the interrupt routine | + | The first thing to remember is that there are several reasons that the program can end up in the interrupt routine, that is because there are different reasons that cause an interrupt. So the first thing to check is that you are in for the reason you think. In the case of the RB0 external interrupt that is usually done by checking the interrupt flag. I have discover however that the flag may be set even if the interrupt is not enabled and another interrupt has be triggered. Thus you should consider checking that not only is the flag set but also that the interrupt is enabled. If this is true we next stop the timer ( TIMER0 ) and read it. Then we reset it for the next count. To get ready for the next interrupt we need to reset the interrupt flag ( set by the processor when the interrupt is triggered ) set the edge we are triggering on to rising if it was falling and vise versa. |
====== Post Interrupt: ====== | ====== Post Interrupt: ====== | ||
Line 210: | Line 179: | ||
The code in the interrupt subroutine, also know as the interrupt service routine or isr: | The code in the interrupt subroutine, also know as the interrupt service routine or isr: | ||
− | + | if ( ( test_bit( intcon, INTE ) ) && ( test_bit( intcon, INTF ) ) ) { | |
− | + | clear_bit( intcon, INTF ); // the flag | |
− | + | ||
− | + | ||
− | + | unsigned char period = 0; // time from last transition | |
− | + | clear_bit( intcon, INTF ); // the flag | |
− | + | ||
− | + | // read the timer1 | |
− | + | clear_bit( t1con, TMR1ON ); | |
− | + | // T1CON: Timer1 On 1 = Enables Timer1 0 = Stops Timer1 | |
− | + | ||
− | + | period = tmr1h; // just use the high byte | |
− | + | ||
− | + | // reset timer1 | |
− | + | tmr1l = 0; | |
− | + | tmr1h = 0; | |
− | + | ||
− | + | set_bit( t1con, TMR1ON ); // Timer1 On 1 = Enables Timer1 0 = Stops Timer1 | |
− | + | ||
− | + | // store in the log | |
− | + | logData[ logIx ] = period; | |
− | + | ||
− | + | // set up for next interrupt | |
− | + | ||
− | + | if ( iRLow ) { | |
− | + | set_bit( option_reg, INTEDG ); // set rising edge clear falling edge | |
− | + | } else { | |
− | + | clear_bit( option_reg, INTEDG ); // set rising edge clear falling edge | |
− | + | } | |
− | + | iRLow = !iRLow; | |
− | + | ||
− | + | set_bit( intcon, INTE ); // set = enable | |
− | + | ||
− | + | ||
− | + | } | |
− | + | return; | |
− | + | } | |
Note that: | Note that: | ||
− | + | we save the period in a ( global ) array, logData[ logIx ], for later use, | |
− | + | set the interrupt edge to the opposite it was, | |
− | + | and keep a ( global ) variable, iRLow, to tell us if the next state will be high or low ( iRLow ) | |
− | OK that is the heart of the routine, how do we get in and how do we get out | + | OK that is the heart of the routine, how do we get in and how do we get out. |
===== Getting In: ===== | ===== Getting In: ===== | ||
Getting In: | Getting In: | ||
− | For this code ( and I am not sure this is a great way, but it works and uses more interrupt programming ) I chose to wait for a period of no signal. My basic idea is this. Set two interrupts | + | For this code ( and I am not sure this is a great way, but it works and uses more interrupt programming ) I chose to wait for a period of no signal. My basic idea is this. Set two interrupts one based on time and one based on getting an IR signal. If the time interrupt goes off first then there was no IR if the IR interrupt goes off first then there is an IR signal. While I am waiting for the interrupt I can have the processor do something else. |
Setup: | Setup: | ||
Line 270: | Line 239: | ||
− | + | iRLow = true; | |
− | + | clear_bit( option_reg, INTEDG ); // set rising edge clear falling edge | |
− | + | clear_bit( intcon, INTF ); // the interrupt flag | |
− | + | logIx = 0; // reset the log | |
− | + | ||
− | + | // turn off all the interrupts we are using | |
− | + | ||
− | + | clear_bit( pie1, TMR1IE ); // TMR1IE = timer 1 interrupt enable / set = enable | |
− | + | clear_bit( pir1, TMR1IF ); // clear timer 1 interrupt flag bit | |
− | + | ||
− | + | clear_bit( intcon, INTE ); // INTE = external int erupt enable / set = enable | |
− | + | clear_bit( intcon, INTF ); // INTF = external int erupt flag / clear = clear | |
− | + | ||
− | + | ||
− | + | IRState = IRSTATE_WAIT; | |
− | + | // clear timer0 flag and enable | |
− | + | ||
− | + | // set counter to something low ( do we need to mess with the prescaler ) | |
− | + | ||
− | + | clear_bit( t1con, TMR1ON ); // Timer1 On 1 = Enables Timer1 0 = Stops Timer1 | |
− | + | tmr1l = 0; | |
− | + | tmr1h = 0; | |
− | + | set_bit( t1con, TMR1ON ); // Timer1 On 1 = Enables Timer1 0 = Stops Timer1 | |
− | + | ||
− | + | // now turn them both on and let the race begin | |
− | + | ||
− | + | set_bit( pie1, TMR1IE ); // TMR1IE = timer 1 interrupt enable / set = enable | |
− | + | set_bit( intcon, INTE ); // INTE = external interrupt enable / set = enable | |
− | + | ||
====== Interrupt: ====== | ====== Interrupt: ====== | ||
Line 304: | Line 273: | ||
We need to use our state variables, flags etc to distinguish between the earlier interrupt and then to figure out who won the race. If the signal was absent we set up for the ready state. If the signal was present we enter the busy and turn off the interrupts. | We need to use our state variables, flags etc to distinguish between the earlier interrupt and then to figure out who won the race. If the signal was absent we set up for the ready state. If the signal was present we enter the busy and turn off the interrupts. | ||
− | + | if ( ( test_bit( intcon, INTE ) ) && ( test_bit( intcon, INTF ) ) ) { | |
− | + | clear_bit( intcon, INTF ); // the flag | |
− | + | ||
− | + | if ( ( IRState == IRSTATE_READY ) || ( IRState == IRSTATE_READ )) { | |
− | + | ||
− | + | ..... this is the code we have already covered | |
− | + | ||
− | + | } | |
− | + | } else { // should be state wait | |
− | + | // bad, we got ir before time out | |
− | + | IRState = IRSTATE_BUSY; | |
− | + | ...... both interrupts off | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | === | + | } |
+ | } | ||
+ | |||
+ | //Handle timer1 interrupt | ||
+ | if( pir1 & (1<<TMR1IF) ) { | ||
+ | |||
+ | // timer interrupted before ir began | ||
+ | IRState = IRSTATE_READY; | ||
+ | clear_bit( pie1, TMR1IE ); // done with the time interrupt | ||
+ | |||
+ | clear_bit( intcon, INTF ); // the flag | ||
+ | set_bit( intcon, INTE ); | ||
+ | // inte = interrupt enable, think just rb0, set = enable | ||
+ | |||
+ | } | ||
− | + | return; | |
− | + | Note: not show here ( but present in the full code ) is the use of the global interrupt enable. It is one instruction that can turn off all interrupts. It has to be turned on before any interrupts will take place. Also in the full code an interrupt is used to support the RS232 receiver. | |
− | |||
− | |||
− | |||
− | |||
− | + | ===== Getting Out ===== | |
− | + | need to write this still |