This section describes the interrupt routines used in BRTOS to support the serial communications and keypad.
If you are going to use interrupts it worth while considering all aspects of your system to see which functions could benefit from using this technique. (Once you have grasped the nettle adding additional interrupt functions is not that difficult). Interrupts have been used in four situations in BRTOS:
Let’s look first at the elements of interrupt processing?
1 Initiating the hardware
2 Turning the Interrupt on
3 Saving the current state of the program (Context Save)
4 Passing control to the Interrupt Handler
5 Processing the interrupt (Interrupt Service Routine)
6 Restoring the previous program state (Context Restore)
BRTOS uses a general purpose Interrupt Handler which covers all items in the above list with the exception of item 5 – Processing the Interrupt – and provides recognition and routing of most of the interrupt sources provided on the 18F range of processors. It is written virtually entirely in assembler, partly for historical reasons but mostly to make it possible to use the assembler’s conditional directives.
First a diversion on Assembler Directives – MPASM supports Directives – these are commands to instruct the assembler on certain actions to take during assembly. There are some 58 directives which can be used; however I will only cover an explanation of those used in BRTOS. For those interested in investigating these further look at the MPASM Assembler help file available from Microchip™
All assembler directives must begin with a “#” character
#DEFINE <Name> <String> will substitute <String> whenever <Name> occurs in the code. This is used in the interrupt control routines to associate Interrupts with specific Interrupt Service Routines.
The presence of Define statements can be tested using
#IFDEF <Name> will return true if Name has been #DEFINE(D)
Conversely
#IFNDEF <Name> will return true if Name has not been #DEFINE(D)
These work in the normal way with #ELSE and #ENDIF
Warning - Be careful in the way you use these directives, as the compiler does not recognise them it will attempt to compile all the code regardless of the directives. Only at assembly time will the unwanted code be dropped. This means that you cannot use labels within an #IF conditional unless that label exists even if you don’t intend to use it in the current build.
Again, since, the compiler is unaware of any of these conditionals, you cannot use the string substitution at the Basic level you can only use it inside assembler.
Despite the length of this code the Interrupt Handler is really quite simple. You set up the labels of the Interrupt Service Routine (ISR) you want to run against each interrupt and the program will jump to that label whenever the interrupt occurs.
At the start of your main application you need to place a #DEFINE statement for each interrupt you will be using in the form #DEFINE Peripheral_Name ISR_Label.
E.g. #DEFINE RX_HANDLER ReceiveUSART
will tell the Interrupt handler that you want the USART, when it receives a character, to raise an Interrupt and jump to the routine ReceiveUSART.
Peripherals which are not given a handler reference will not be included in the interrupt routine. These defines must appear ahead of the Interrupt Handler. Similarly the Interrupt Service Routines should come after the Interrupt Handler.
The full code is in Int_Ctrl_P+.inc.
Interrupt_Init: This section must be called from the main application during program initialisation. After making sure interrupts are off and that Priority interrupts are not enabled it will run through any peripheral set up code required. If you haven’t #DEFINE(d) an ISR to the Peripheral the assembler will simply ignore that bit of setup code. The actual set up will depend on the type of peripheral and the state each peripheral is left in is shown in the table below.
TMR1_HANDLER |
|
RX_HANDLER |
|
TX_HANDLER |
|
MSSP_HANDLER |
|
AD_HANDLER |
|
LVD_HANDLER |
|
IOC_HANDLER |
|
INT0_HANDLER |
|
Interrupt Priority has been deliberately disabled as there is no facility in Proton to support a second level of context saving. If you want to use both levels you will have to handle all Context Saving in code.
The Interrupt Handler will Jump to the ISR in question which will carry out the necessary actions in response to the interrupt before Jumping back to the Interrupt Handler.
The Interrupt Service Routines must also clear the associated Interrupt flag if the hardware does not do this automatically. You will need to read the Microchip™ datasheets to find out what flags are automatically reset.
To reiterate an earlier point - It is essential to minimise the time spent in the ISR responding to the Interrupt. As a general guide, an Interrupt subroutine should simply note the fact that an event has taken place, move any data associated with the event into or out of storage and return. If there is processing to be done as a result of the event this should be carried out outside the interrupt subroutine.
All ISRs must return to the Interrupt Handler with a Goto Int_Code_Cont. This ensures the any interrupts that occurred while we were in the ISR are properly handled by the Interrupt Handler.
That concludes the overview of the interrupt handler and ISRs, the next sections, by way of example, describe how the specific USART receive and transmit ISR are implemented.
TX/RX Interrupt Service Routines
BRTOS provides a FIFO buffer for both transmit and receive operations of the USART. The Receive FIFO is loaded on interrupt from the Receive USART channel and the Transmit FIFO is unloaded on interrupt from the Transmit USART channel.
This has the advantage that the application code does not need to wait on receiving characters from the USART. Similarly on the transmit side the application code only has to load an array and leave the transmission to proceed under interrupt control.
As BRTOS is a task scheduler the routines also provide the option of adding a routine to the Immediate task list when the receive buffer contains a defined number of characters.
The code for the TX/RX Drivers is in RX_BUFF_P+.inc
TransmitUSART – is triggered whenever the USART TXREG becomes empty. The routine simply loads the next entry in the FIFO into TXREG, repositions the pointer to the next entry in the FIFO and decrements the FIFO length. If the FIFO is empty the TX interrupt is disabled and if the directive “#DEFINE PUT_TX_RECORD” is added the routine will add the Put_Record routine onto the immediate task list.
ReceiveUSART – is triggered whenever the USART RCREG has been loaded with a character. The routine checks and clears any overrun error sets the Overrun flag, records the framing error bit and loads the character held in RCREG into the head of the receive FIFO providing there is room. It adjusts the pointer to the head of the FIFO and increases the length of the FIFO by one.
The Timer RxTMR is reset to its timeout value and commences to count down. This is used to prevent the receive interface hanging if it does not receive a complete record.
Finally, if the directive “#DEFINE GET_RX_RECORD” is added, the routine checks the length of the received data and if it has reached a defined length (RB_MinMsgLen) it will place the Get_Record routine on the immediate task list. When the program returns to the scheduler this routine will look for a data record and process it. I will describe this record handling in a later article.
Loading and Unloading the FIFOs
Having described how the hardware interfaces to the FIFOs I will describe the functions to load and read from the FIFOs.
RB_GetChar – Data in the receive FIFO can be access through RB_GetChar. This removes the data from head of the FIFO and reduces the FIFO by one character. The data is returned in TXChar.
RB_GetFirstChar and RB_GetLastChar –These two routines return the character in the respective locations but do not unload them from the FIFO. These can be used by the Get_Record routines to test if a record is present before unloading.
RB_GetHex2 and RB_GetHex4 – These two routines will return a HEX byte or HEX Word in RXByte and RXWord respectively
Note - the following 2 functions are called as macros - the square brackets are optional and are retained for consistency with Proton's natural syntax.
HSerOutI [Array] – will load the TX FIFO with the data held in Array and enable the TX Interrupt.
HSerOutI [Byte] – will load the TX FIFO with a single byte and enable the TX Interrupt.
Keypad Interrupt Service Routines
The Menu article in Part 3 skipped the scanning the keys until we had covered the handling of interrupts, so now it is time to explain the two Interrupt Service Routines used in scanning the keypad.
You may recall that I mentioned earlier that I have used TMR1 for de-bounce and key repeat timing. This timer is set to interrupt every 20mSecond and can provide a useful timing source when short term timing is required.
KeyScan_Intr – is triggered whenever a key in first pressed. It simply records the fact that a certain key has been pressed and saves it in the variable Keys. Importantly, it also disables further Interrupt On Change interrupts and sets the KeyRepCtr to 1.
KeyRepTMR – is triggered every 20mSeconds by TMR1. Basically, it checks to see if the Keys value saved by KeyScanIntr is the same as the current Keypad value. If it is it decrements the KeyRepCtr otherwise it will re-enable Interrupt On Change.
If the counter has reached zero it will add the ProcessButtons task onto the immediate Task list so that it will be executed shortly after the return from interrupt and will load the counter with the RepeatRate value. As long as the button remains pressed the Processes buttons will be called at the RepeatRate, once the button is released the program will detect key presses through the PIC’s interrupt on change.
This is a very neat way of providing both de-bounce and a key repeat functions without requiring pauses or other time wasting processes.
A quick look at BRTOS in action. Below are 2 screen shots of the Visual Basic™ Task Monitor while the Clock Demo is running with DEBUG_TASK enabled.
Here, the clock program is running in Default Display mode. Immediate Task list shows the TimedEvent task from the scheduler. This calls the other lower rate tasks. 2Hz the DispTick routine shows the flashing Colon “:”. 1Hz - increment the seconds count (UpDateSeconds) and check the Receive timer RXTmr, (currently idling as there is no incoming serial data). The SendIdleCtr is a TASK_DEBUG message reporting the processor load (displayed in the progress bar on the toolbar). Every minute we read the RTC and display the full time and date on the display (DispTimeDate) |
Here, the clock program is in menu mode and we are adjusting the time. Immediate Task processes the keypad (ProcessButtons) the TimedEvent task will also be called after the ProcessButtons task has completed. 2Hz menu timer is counting down (DecMenuTimer) The remaining 1Hz tasks are as before.
The DispTimeDate task has been removed while the time and date are being edited. |