A Real Time Operating System for
the PIC – Part 4
This
article will describe the interrupt routines I have 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). I have used interrupts
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)
In
BRTOS I have 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.
Interrupt Handler
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 |
Sets the TMR1 to use
the internal clock at a default frequency of 100Hz unless TMR1_FREQ is
defined. Allowable Frequency
range is 16Hz to 10KHz. Timer will be left
running with interrupt enabled Adds in code to
reload TMR1 and vectors to TMR1 ISR |
RX_HANDLER |
Enables interrupt of
USART Receive Adds in code to
vector to the USART_ RX ISR |
TX_HANDLER |
Clears the TX
Interrupt flag – Leaves Interrupt Disabled Adds in code to
vector to the USART_TX ISR |
MSSP_HANDLER |
Adds in code to
vector to the MSSP ISR |
AD_HANDLER |
Adds in code to
vector to the AD ISR |
LVD_HANDLER |
Enables the Low
Voltage Handler to trigger at 4.2 Volts Adds in code to
Vector to the LVD ISR |
IOC_HANDLER |
Enables Interrupt on
Change Adds in code to
vector to the IOC_ISR |
INT0_HANDLER |
Sets to Edge
triggering and enables Interrupt |
I
have deliberately disabled Interrupt Priority 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.
Interrupt Service Routines (ISRs)
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 (added to the task list during initialisation) 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
HSerOutI [Array] – will load the TX FIFO with the data
held in Array 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.
BRTOS in action
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 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. |
The
next article will cover the handling of serial records and the parsing the
serial commands.
Attached Code
Int_Ctrl_18F_P+.inc – the Interrupt Handler
RX_Buff_P+.inc – the Interrupt Service routines for
the serial I/O