Error processing nested interrupts in STM8 (not described in errata). Interrupt handling, interrupt vectors, software interrupts, IRQ
Nested interrupts
This mechanism is fully supported in the QNX Neutrino OS. The previous scenarios described the simplest and most common situation where only one interrupt occurs. However, to obtain maximum value Latency in the absence of non-maskable interrupts should be considered taking into account all current interrupts at once, since a non-maskable interrupt with a higher priority will preempt the current interrupt.
In Fig. Figure 2.25 shows Thread A running. Interrupt IRQx starts interrupt handler Intx, which is preempted by interrupt IRQy and interrupt handler lnty. The Inty interrupt handler returns an event that starts Thread B, and the Intx interrupt handler returns an event that starts Thread C.
Interrupt calls
The interrupt handling software interface includes the following kernel calls.
Through this software interface a thread with appropriate user privileges can call the InterruptAttachQ or Interrupt Attach EventQ function, passing the hardware interrupt number and the address of the function in the thread's address space that should be called when the interrupt occurs. The QNX Neutrino OS allows multiple interrupt handlers (ISRs) to be associated with each hardware interrupt number. Non-maskable interrupts can be processed while interrupt handlers are already running.
The following is an example program code, with which an interrupt servicer (ISR) is attached to a hardware timer interrupt (which the OS also uses as the system clock). The kernel's timer interrupt handler does its own cleanup of interrupt sources, so it only increments a counter in the thread's data space and then passes control to the kernel.
As a result, user threads with appropriate privileges can dynamically attach (and detach) interrupt handlers to (from) hardware interrupt vectors as they execute. These threads can be debugged at the source level using normal debugging tools, and the interrupt handler itself can be debugged by calling that handler at the thread level and step by step execution at the source level or by calling the InterruptAttachEventQ function.
When a hardware interrupt occurs, the processor calls the microkernel's interrupt redirector. This module writes the register context of the executing thread onto the stack into the appropriate thread table entry and then sets the context so that the interrupt handler has access to the program code and data of the thread that contains the interrupt handler. Thus, the interrupt handler is able to use buffers and code in the user thread to determine the source of the interrupt and, if the thread requires more work to be done high level, generate an event for the thread that includes this interrupt handler, after which this thread can process the data that the interrupt handler placed in its buffer.
Because an interrupt handler is mapped to the context of its containing thread, it can directly operate on devices mapped to the thread's address space or execute I/O commands on its own. As a result, this eliminates the need to bundle device drivers with the kernel.
The initial interrupt handling module, which is contained in the microkernel, calls all handlers associated with a given hardware interrupt. If the returned value indicates that some event should be passed to the process, the kernel queues that event. After the last interrupt handler is called to handle a given interrupt vector, the kernel interrupt handler exits the interrupt handler and "returns from interrupt."
This return from an interrupt does not need to result in the context of the interrupted thread. If a queued event causes a higher priority thread to enter the READY state, the microkernel will return from the interrupt to the context of the thread that is currently active.
This establishes a period (the so-called interrupt service delay) between the occurrence of the interrupt and the execution of the first instruction of the interrupt handler, as well as a period (the so-called scheduling delay) between last instruction interrupt handler and the first instruction of the thread brought to the ready state by the interrupt handler.
The period of maximum interrupt processing latency is strictly determined due to the fact that the OS disables interrupts only for the duration of the execution of several instructions in several critical sections of the program code. The periods during which interruptions are prohibited continue strictly certain time and have no data dependency.
The microkernel's interrupt handler executes several instructions before calling the interrupt handler. As a result, process preemption by hardware interrupts or kernel calls occurs equally quickly and through the same chain of instructions.
During its operation, the interrupt handler has full access to the hardware (since it is part of a privileged thread), but cannot make other kernel calls. The interrupt handler must respond to a hardware interrupt as quickly as possible, do the minimum amount of work to service the interrupt (read a byte from a UART, etc.), and, if necessary, schedule a thread at some priority to perform further work. .
The maximum interrupt latency for a given hardware priority can be directly calculated from the amount of interrupt latency introduced by the kernel and the maximum interrupt handler running time for each interrupt that has a higher hardware priority than the given interrupt handler. Because hardware interrupt priorities can be reassigned, the most important interrupt in the system can be given the highest priority.
It should be noted that calling the InterruptAttachEventQ function does not trigger the interrupt handler. Instead, a user-defined event is generated for each interrupt. Typically, this event causes a waiting thread to be scheduled so that it can do the main work. The interrupt is automatically masked after the event is generated and must be explicitly unmasked at the right time by the thread servicing the device.
Thus, the priority of work carried out by hardware interrupts can be regulated by the operating system itself, and not by the hardware. The interrupt source does not generate a new interrupt until the current one is serviced, allowing you to control the effect of interrupts on critical sections of the program code and provide highly precise scheduling control.
User processes and threads can “intercept” not only hardware interrupts, but also various “events” within the microkernel. When any of these events occur, the kernel can call the specified external function (upcall) on the user thread to handle of this event. For example, each time an idle thread is called, a user thread can instruct the kernel to make an external call to implement special hardware power-saving modes.
Parameter name | Meaning |
Article topic: | Interrupts. |
Rubric (thematic category) | Programming |
Watchdog timers.
Often electrical noise produced by surrounding equipment causes the microcontroller to override wrong address, after which its behavior becomes unpredictable (the microcontroller “goes crazy”). To monitor such situations, watchdog timers are often included in the microcontroller.
This device causes the microcontroller to reset if its contents are not updated within a certain period of time (usually tens of milliseconds to several seconds). If the change in the contents of the program counter does not correspond given program, then the watchdog timer modification command will not be executed. In this case, the watchdog timer resets the microcontroller, setting it to its original state.
Many developers do not use watchdog timers in their applications because they do not see the critical importance of using them to combat the effects of electrical noise, for example, when placing the microcontroller in a cathode ray display close to or near a flyback transformer. ignition coils in a car. In modern electronics, the likelihood of electrical disturbances occurring is negligible, although they sometimes occur in situations similar to those listed above.
It is not recommended to use a watchdog for masking software problems. Although this timer can reduce the likelihood of software errors, it is unlikely to eliminate all possible causes of their occurrence. Instead of relying on hardware to prevent software failures, it is better to test more thoroughly software in various situations.
Many users believe that interrupts are a piece of hardware that is best left alone, as their use requires superior knowledge of the processor to design the interrupt routine. Otherwise, when an interrupt occurs, the system “falls asleep” or “goes crazy”. This feeling usually appears to a developer after experience with interrupts for a personal computer, which has a number of features that complicate the creation of an interrupt handler. Many of these problems do not occur in microcontroller-based hardware. Use in this equipment interrupts can significantly simplify its development and use.
If you have never dealt with interruptions, then you will have a question - what is it? IN computer system An interrupt is the launch of a special subroutine (called an “interrupt handler” or “interrupt service routine”) that is called by a hardware signal. During the execution of this subroutine, the implementation of the current program stops. The term “interrupt request” is used because sometimes a program refuses to acknowledge an interrupt and execute the interrupt handler immediately (Figure 2.19).
Interruptions in a computer system are similar to interruptions in everyday life. A classic example of such an interruption is a phone call while watching a television program. When the phone rings, you have three options. The first is to ignore the call. The second is to answer the call, but say that you will call back later. The third is to answer the call, putting aside all current affairs. The computer system also has three similar responses which can be used in response to an external hardware request.
The first possible response - “do not respond to the interrupt until the current task is completed” - is implemented by disabling (masking) servicing the interrupt request. After the task is completed, one of two options is possible: resetting the mask and enabling maintenance, which will lead to calling the interrupt handler, or analyzing the value of the bits ("polling"). indicating the receipt of interrupt requests and direct execution of the service program without calling the interrupt handler. This interrupt handling method is used when it is necessary to provide specified time execution of the main program, since any interruption can disrupt the implementation of an extremely important interface.
Rice. 2.18 - Executing an interrupt.
It is not recommended to mask interrupts for a long time, since during this time several interrupt events may overlap, and only one will be recognized. The permissible duration of masking depends on the specific application of the microcontroller, the type and frequency of such events. It is not recommended to disable interrupts for more than half the minimum expected period of events requesting interrupts.
The interrupt handler always provides the following sequence of actions:
2. Reset the interrupt controller and the equipment that caused the request.
3. Process the data.
4. Restore the contents of the context registers.
5. Return to the interrupted program.
Context registers are registers that determine the current execution state of the main program. Typically these include a program counter, status registers, and accumulators. Other processor registers, such as index registers, are used during interrupt processing, so their contents are also critical to preserve. All other registers are specific to a particular microcontroller and its application.
After reset to initial state The interrupt controller is ready to accept the next request, and the interrupt-causing equipment is ready to send the request when the appropriate reasons arise. If a new interrupt request is received, the processor's interrupt mask register will prevent the interrupt from being processed, but the interrupt status register will latch this request, who will be waiting for his service. After processing of the current interrupt is completed, the interrupt mask will be reset, and the newly received request will be processed.
Nested interrupts are difficult to implement on some types of microcontrollers that do not have a stack. These interrupts can also cause stack overflow problems. The overflow problem is relevant to microcontrollers due to the limited capacity of their data memory and stack: a sequence of nested interrupts can result in more data being pushed onto the stack than is allowed.
Finally, the interrupt is processed. The second TV example shows that you can quickly respond to an interrupt request by accepting the necessary data, which will then be used after solving the current task. In microcontrollers, this is implemented by storing incoming data in a memory array and then processing it when the execution of the original program is completed. This maintenance method is a good compromise between immediately handling the interrupt completely, which may take a long time, and ignoring the interrupt, which may result in losing information about the event that caused the interrupt.
Restoring the context registers and executing the interrupt return instruction returns the processor to the state it was in before the interrupt occurred.
Let's look at what happens to the contents of various registers when an interrupt is processed. The contents of the status register are usually automatically saved along with the contents of the program counter before the interrupt is processed. This eliminates the critical importance of keeping it in memory. software using forward commands and then restore when returning to original program. At the same time, this automatic saving is not implemented in all types of microcontrollers; therefore, special attention should be paid to the organization of interrupt processing.
If the contents of the status register are saved before the interrupt handler starts executing, then the return command automatically restores it.
If the contents of other processor registers change during interrupt service, they must also be stored in memory before the change and restored before returning to the main program. It is common practice to save all processor registers to avoid unpredictable errors that are very difficult to localize.
The address that is loaded into the program counter when moving to the interrupt handler is usually called the “interrupt vector”. There are several types of vectors. The address that is loaded into the program counter when the microcontroller is started (reset) is usually called the “reset vector”. Different interrupts have different vectors, which relieves the maintenance program of the critical need to determine the cause of the interrupt. The use of one vector by different interrupts usually does not cause problems when operating microcontrollers, since most often the microcontroller executes one single program. This distinguishes a microcontroller from a personal computer, during the operation of which various interrupt sources can be added. (If you have ever connected two devices to the COM1 and COM3 ports, then you have an idea of what we are talking about). In a microcontroller where the hardware is well known, there should not be any problems with sharing interrupt vectors.
The last thing left to consider is software interrupts. There are processor instructions that can be used to simulate hardware interrupts. The most obvious use of these instructions is to call system routines that are located in an arbitrary memory location, or require intersegment jumps to access them. This feature is implemented in microprocessors of the Intel i86 family and is used in basic system BIOS I/O(Basic Input/Output System) and operating DOS system personal computers to call system routines without the critical need to fix the entry point. Instead, various interrupt vectors are used to select the instruction to be executed when such a software interrupt occurs.
Perhaps after reading this chapter the interrupt mechanism will become clearer to you, or vice versa. You will only get more confused. As each microcontroller is described, it will be shown how the use of interrupts can simplify its use.
Interrupts. - concept and types. Classification and features of the "Interruptions" category. 2017, 2018.
3. Examples of interrupts
3.1. Interrupt when signal changes on I/O ports (example in PROTEUS)
3.2. External interrupt INT (example in PROTEUS)
All types of dsPIC microcontrollers have the same interrupts. Therefore, you can safely study interrupts in the PROTEUS program. For the study we will use a microcontroller, which is available in PROTEUS.
So, what kind of interrupts exist in the dsPIC33FJ32GP204 microcontroller?
And there are a lot of interruptions in the microcontroller. And it makes no sense to list everything in this lecture. After all, the first thing we pay attention to when choosing a microcontroller is what modules it has, and only then we look at what interruptions it has. We will also study everything in the same sequence. As we study the microcontroller, we will analyze the interrupts of various modules.
In general, you need to know that for the microcontroller there is a table of interrupt vectors, which is located in the program memory. Each interrupt has its own vector, that is, the address of the start of the specific interrupt routine (ISR). There is also an alternative interrupt vector table. Using a single ALTINV bit, you can specify from which table the address of the interrupt routine should be taken. That is, there is an alternative to the main table. But you need to remember that the ALTINV bit is used for the entire table at once. And if an alternative table is installed, then absolutely all addresses of the interrupt handling subroutine, the main program will be taken from the alternative table. The alternative table is very useful for debugging mode, as it allows you to write a program to the controller once and, using, for example, a button, choose between two algorithms for the program.
dsPIC has expanded the range of interrupt priorities. If in PIC18 there were only 2 of them, then in dsPIC there are already 8 levels. And when I was developing a module for communication via the GPRS channel, this was very useful to me, since the microcontroller used almost all the peripherals available on board, and the number of interrupts processed was about twenty.
The most important thing you need to know is that to work with each interrupt, there are only 3 main bits (registers): interrupt enable bit, interrupt flag, three interrupt priority selection bits.
So, what does a programmer need from a programmer to work with an interrupt:
1. It needs to enable a specific interrupt.
2. After the event that caused the interrupt has occurred, the corresponding interrupt flag is set, and the program, so to speak, moves along the interrupt vector. And if we say in normal language: then as soon as an interruption occurs, the subroutine for processing a specific interrupt will be immediately called.
3. The main thing is not to forget to reset the flag of this very interruption in the interrupt subroutine.
For the dsPIC microcontroller, the C30 compiler provides the following rule for describing interrupt handling routines, for example, for the INT1 interrupt:
void __ attribute__((__ interrupt__)) _ INT1 Interrupt()
The main thing you need to pay attention to is the end of the subroutine title text: _ INT1 Interrupt() - this is the only parameter that must be changed for various types interrupts. Each interrupt from the interrupt vector table has its own designation this parameter. And here you will have to open the help file for the C30 compiler, in particular the section (I have this...) “dsPIC30F DSCs (SMPS) Interrupt Vectors” and look at the table of which interrupt should be called in what way. I don’t think it’s advisable to present this table in this article, since anyone who has a C30 compiler should have a help file attached hlpMPLABC30.chm(personally, that’s what I call it). And for those who don’t have this compiler, they don’t need a table J. Okay, let’s digress...
By the way, if you get more deeply acquainted with the principles of describing the interrupt function, you will find the presence of a special attribute auto_psv. This parameter is intended to instruct the microcontroller that it should save the main registers before proceeding to interrupt processing. I assume that you know why you need to save key registers.
For example, the interrupt description would look like this:
void__attribute__((interrupt, auto_psv)) _INT1Interrupt()
Above was an example of a subroutine call header using the address of the main table. And for an alternative table you only need to changeable parameter insert after the underline Alt, For example:
_ INT1 Interrupt() -> _ AltINT1 Interrupt()
I would like to draw your attention to the fact that although the interrupt subroutine looks like a subroutine, it does not need to be declared, just like main , since it is already declared by the compiler, you only need to correctly write the header of the interrupt handling subroutine.
Let's start by studying elementary interrupts, and then as we study each module, we will become familiar with the interrupts inherent in them.
3.1. Interrupt when signal changes on I/O ports (CN)
One of the easiest interrupts to understand is the state change interrupt at the CN input. The dsPIC33FJ32GP204 microcontroller is full of such inputs, so I think this number will satisfy any request. No matter which state changes on these channels (1 -> 0 or 0 -> 1), this change, if enabled, will cause the CNIF flag to be set. In order to activate an interrupt when a signal changes, you need to do the following:
1. Configure the necessary CN channels for input (using the TRISx register).
2. Enable control of signal changes at the corresponding CN input. For this there are already 2 registers CNEN1 and CNEN2. You can either access each register in its entirety for configuration, or access the corresponding bits (for example, _CN15IE=1; _CN6IE=1;)
3. If necessary, turn on the pull-up resistors. For this purpose there are also two registers CNPU1 and CNPU2. It is also possible separately, for example _CN15PUE=1; _CN6PUE=1;
4. Enable interrupt when the signal changes on CN ( _ CNIE=1 )
5. Now, as soon as the signal on the monitored CN pins changes, the _CNIF interrupt flag will be set. And the program enters the interrupt handling function. The C30 compiler for interrupting when a signal changes on CN provides the following description of the function:
void __ attribute __((__ interrupt __)) _ CNInterrupt ()
This is where this interrupt is processed (see example)
6. In the interrupt handling subroutine, do not forget to reset the interrupt flag.
To familiarize yourself with this type of interrupt, consider the following example. There is a motor and 4 sensors. When everything is in order, the engine should rotate counterclockwise. But as soon as the sensor state changes (emergency mode), the engine should immediately begin to rotate in reverse side(clockwise). And after a given period of time, begin to rotate counterclockwise again.
A metal detector is implemented similarly on harvesting machines. When, for example, mown grass enters the machine, this is normal operation. But as soon as the metal detector detects a metal object, it instantly reverses and the machine seems to spit out this metal object so as not to damage the mechanism. And metal is detected by a special sensor, consisting of several independent channels, but combined into one general device. In our example, we will, of course, simplify everything very much; instead of a metal presence sensor, we will use ordinary buttons.
Assembling the circuit in PROTEUS
Now let’s create a program to complete the task.
# include " p33 Fxxxx. h"
_ FOSCSEL(0 x02);
_ FOSC(0 xE2);
char state; // variable stores the direction of rotation of the motor
// "1" - emergency mode, "0" - normal mode
void init (void);
void init (void)
{ _ CN8 PUE=1; //turn on the pull-up resistor at input CN8 (RC0)
_ CN10 PUE=1; //turn on the pull-up resistor at input CN10 (RC2)
_ CN17 PUE=1; //turn on the pull-up resistor at input CN17 (RC7)
_ CN19 PUE=1; //turn on the pull-up resistor at input CN19 (RC9)
AD1 PCFGL=0 xffff;
PORTC=0; // initialize port C
LATC=0;
TRISC=0 xFFFF; // set up port C as input
PORTC=0 xFFFF;
PORTA=0; // initialize port A
LATA=0;
TRISA=0; // Set all output to output,
PORTA=0;
_ CN8 I.E.=1; // enable control of signal changes to
_ CN10 I.E.=1; // corresponding outputs
_ CN17 I.E.=1; // if the signal changes on them, then
_ CN19 I.E.=1; // interrupt flag _CNIF is set
_ CNIE=1; // enable interrupt INT1
void __ attribute__((__ interrupt__)) _ CNInterrupt()
state=1; // this is emergency mode
_ CNIF=0; // reset the interrupt flag when the signal changes
void main (void)
{ static long int i;
init();
state=0; // we assume that the operating mode at startup is normal
while(1) // launch endless loop
{ if (state) // if emergency mode, then
{ _ CNIE=0; // disable interrupt when signal changes
_ R.A.8=0;
_ R.A.0=1; // in emergency mode
for(i=0; i<210000; i++) //provide delay
state=0; // activate normal engine operation
_ CNIE=1; // enable CN interrupts
{ _ R.A.8=1; // in normal mode
_ R.A.0=0; // provide the direction of rotation of the engine
} // while(1)
3.2. External interruptINT
If you need your microcontroller to respond immediately to external devices, then the INT interrupt is what you need. The dsPIC33FJ32GP204 microcontroller has three INT channels (INT0, INT1, INT2). That is, you can provide your own interrupt for each of the three external devices. This interrupt is good because it can wake the controller from SLEEP mode. In addition, you do not need to continuously scan these inputs, which would consume microcontroller resources.
An INT interrupt occurs when the input signal changes either “0” -> “1” or “1” -> “0”. It depends on the state of the bit _INT1EP. If _INT1EP=1, then on the falling edge; If _INT1EP=0, then along the leading edge (for example _INT1EP=1 – INT0 interrupt occurs on a falling edge).
As for priority, the INT0 interrupt has the highest priority, and it is the very first in the interrupt table. Below are the INT1 and INT2 interrupts.
In principle, these are all the features of this interrupt. Everything else is the same as for other interrupts, i.e. you need to enable the corresponding external interrupt. And if an interrupt occurs, it will set the corresponding _INTхIF flag (for example _INT1IF). Don't forget to reset it. In general, everything is as simple as shelling pears.
Let's move on to an example
We issue an electronic call to an apartment or house. When you press the button, the device should issue beep and wink with light (for those who like to listen to music loudly).
We are drawing up a circuit in PROTEUS; in addition to the microcontroller, we will need a button, a beeper and an LED.
I think everything is very clear in the picture. Let's move on to the program.
#include"p33Fxxxx. h"
// Set the oscillator tuning bits (HS)
_ FOSCSEL(0 x02);
_ FOSC(0 xE2);
charstate;// "1" - enable the bell, "0" - disable the bell
voidinit(void);//declare the initialization routine
// ** subroutine initialization **
voidinit(void)
( _CN4PUE=1;//turn on the pull-up resistor at input CN4 (RB0)
AD1PCFGL=0xffff;// all pins as digital I/O
PORTB=0x00;// initialize port B
LATB=0;
TRISB=0x0001;// set RB0 output to input
PORTB=0x00;
// initializing port C.
PORTC=0;
LATC=0;
TRISC=0;//set all pins as output
PORTC=0;
RPINR0=0x0000;// the INT1 signal is configured to remove from the RP0 (RB0) pin (this
// command not for all dsPIC
_INT1EP=1;// INT1 interrupt occurs on falling edge
_INT1IE=1;// enable interrupt INT1
// ******* interrupt handling subroutine *
void __ attribute__((__ interrupt__)) _ INT1 Interrupt()
state=1; // call must be enabled
_ INT1 IF=0; // reset interrupt flag INT1
// **** Entry point to the program *
void main (void)
{ static long int i; //declare a variable to organize a delay
init();// call the initialization subroutine
state=0;// first the call must be turned off
while(1)// start an endless loop
( if (state)// if state is "1", then
( _RC0=1;// light up the LED
_RC2=1;// turn on the beeper
for(i=0;i<160000;i++) // delay, ensures a certain playing time
state=0;// indicate that the beeper should be turned off
else// otherwise, if state is "0", then
( _RC0=0;// turn off the LED
_RC2=0;// stop beeping
} // while(1)
Work similarly with interrupts INT0 and INT2. In further examples we will use these interrupts more than once.
There is a situation when you need to assign many different tasks to one peripheral device, but there is only one device and something needs to be done about it.
A simple example is a timer and its overflow interrupt.
We can set the shutter speed and perform some operations upon interruption. But if at one point in time we want the interrupt timer to perform one operation, and then another, a third. Yes, as much as you like, depending on the condition. And there is only one vector.
Or, for example, USART. We may easily need different code to be executed depending on the byte arrival interrupt mode. In one mode - issuing a greeting, in the other - sending obscenities to the bathhouse. In the third, a blow to the head. And there is only one vector.
Of course, you can add a switch-case construct to the interrupt handler and, by selecting a mode, go to the desired section of code, but this is quite cumbersome, and most importantly, the transition time will be different, depending on the order in which the switch-case comparison survey will take place structures.
That is, in a switch like:
1 2 3 4 5 6 7 | switch (x) ( 1 : Action 1 2 : Action 2 3 : Action 3 4 : Action 4 ) |
switch(x) ( 1: Action 1 2: Action 2 3: Action 3 4: Action 4 )
There will be a sequential comparison of x first with 1, then with 2, then with 3, and so on until all options are enumerated. And in this case, the reaction to Action 1 will be faster than the reaction to Action 4. This is especially important when calculating the exact time intervals on the timer.
But there is a simple solution to this problem - index jump. Before we start expecting an interrupt, it is enough to first load into variables (or even directly into the index register Z) the direction where we need to redirect our vector and insert an index transition into the interrupt handler. And voila! The transition will be where you need it, without any comparison of options.
We create variables in memory for a floating vector:
Timer0_Vect_L: .byte 1 ; Two address bytes, high and low Timer0_Vect_H: .byte 1
Preparing to wait for an interrupt is simple, we take it and load it into our variable with the desired address
CLI ; The critical part. Interrupts OFF LDI R16,low(Timer_01) ; Take the address and save STS Timer0_Vect_L,R16 ; it into a memory cell. LDI R16,High(Timer_01) ; Similarly, but with the highest vector STS Timer0_Vect_H,R16 SEI ; Interrupts ON
That's it, you can start the timer and wait for our interruption. It’s similar with other cases.
And the handler looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | ;============================ ; Entering the overflow interrupt from Timer0 ;=========================== TIMER_0: PUSH ZL ; save the index register onto the stack PUSH ZH ; because we use it PUSH R2; we save R2, because we spoil it too IN R2,SREG ; Retrieve and save the flag register PUSH R2; If you do not do this, then we will 100% get glitches LDS ZL,Timer0_Vect_L ; load the address of the new LDS vector ZH,Timer0_Vect_H ; both bytes. CLR R2; Clear R2 OR R2,ZL ; We check the vector for zero. Otherwise, we grab the analogue of OR R2,ZH ; reset"a. The check is carried out through the operation OR BREQ Exit_Tm0; with the accumulation of the result in R2; this way we don’t spoil the contents of Z and we don’t have to; load it again IJMP; We leave along a new vector; Exit from the interrupt. Exit_Tm0: POP R2 ; We take it out and restore the flag register OUT SREG, R2 POP R2 ; restore Z POP ZL RETI ; Additional vector 1 Timer_01: NOP ; here we can do whatever we want NOP ; after all. If we use any other NOP registers, then we also save them on the stack RJMP Exit_Tm0 ; This is a transition to the interrupt output; additional vector 2; save a dozen bytes on the return code:))) Timer_02: NOP NOP NOP NOP RJMP Exit_Tm0 ; Additional vector 3 Timer_03: NOP NOP NOP NOP NOP RJMP Exit_Tm0 |
;============================ ; Entering the overflow interrupt from Timer0 ;============================ TIMER_0: PUSH ZL ; save the index register onto the stack PUSH ZH ; because we use it PUSH R2; we save R2, because we spoil it too IN R2,SREG ; Retrieve and save the flag register PUSH R2; If you do not do this, then we will 100% get glitches LDS ZL,Timer0_Vect_L ; load the address of the new LDS vector ZH,Timer0_Vect_H ; both bytes. CLR R2; Clear R2 OR R2,ZL ; We check the vector for zero. Otherwise, we grab the analogue of OR R2,ZH ; reset"a. The check is carried out through the operation OR BREQ Exit_Tm0; with the accumulation of the result in R2; this way we don’t spoil the contents of Z and we don’t have to; load it again IJMP; We leave along a new vector; Exit the interrupt. Exit_Tm0: POP R2 ; We take it out and restore the flag register OUT SREG, R2 POP R2 ; restore Z POP ZL RETI ; Additional vector 1 Timer_01: NOP ; here we can do whatever we want NOP ; after all. If we use any other NOP registers, then we also save them on the stack RJMP Exit_Tm0 ; This is a transition to the interrupt output; additional vector 2; save a dozen bytes on the return code:))) Timer_02: NOP NOP NOP NOP RJMP Exit_Tm0 ; Additional vector 3 Timer_03: NOP NOP NOP NOP NOP RJMP Exit_Tm0
Implementation for RTOS
But what should we do if our program is built in such a way that all the code rotates along task chains through the RTOS manager? It is very difficult to calculate in your mind how these chains are performed relative to each other. And each of them can try to take over the timer (of course, not without permission, at our suggestion, we are writing a program, but it will be difficult to track everything in time).
In modern large axes, there is a Mutual exclusion mechanism for this case - mutex. Those. it's a kind of busy flag. If some process communicates, for example, with the UART, then another process does not dare to put a byte there and obediently waits until the first process frees the UART, about which the flag signals.
There are no mutual exclusion mechanisms in mine, but they can be implemented. At least make some minimal resemblance. I don’t want to do a full implementation of all this junk, because... my goal is to keep the kernel size at 500-800 bytes.
The easiest way is to reserve one more byte in memory - the busy variable. And when one process seizes a resource, then in this variable it records the time when it will approximately release it. Time passes in system timer ticks, which for me is 1ms.
If any other process tries to access the same hardware resource, it will first look at its busy state, count the time during which it will be busy and go off to smoke for this period - it will load itself into a timer queue. There he will check again and so on. This is the simplest option.
The problem here is that if there are many applicants for one vector, then the processes will continue to run around, like drunken youth around the only toilet in the square during the holiday festivities. If someone's bladder can't stand it, the algorithm will screw up. And who cares to guess here, because... It will be difficult to model this.
The solution to the problem is to add another next chain, this time for access to the resource. So that it does not stand idle at all. Those. one jumped out, then a second, a third, and so on until all the processes relieved their need in some kind of USART.
The disadvantage is obvious - another queue means additional memory, additional code, additional time. You can, of course, get perverted and put the code of the main circuit manager on the vector queue. But here you need to carefully debug everything, because it will be called by interruption! Yes, and it’s cumbersome, it’s only required when we have a lot of people willing to do it.
The second solution is to throw out the busy time variable, leaving only the “Busy!” flag. And the process that is trying to contact does not run away to smoke, but jumps back a couple of steps - to the end of the task queue and immediately breaks back. The people around the toilet are not running around, but jostling with their elbows at the entrance on the principle of who can get through first.
Another drawback is that there is a large load on the main conveyor, a lot of requests for queuing so it doesn’t take long to swell up to the entire RAM and run into the stack, and this risks a global apocalypse.
Of course, the timer is given here as an example, most problems can be solved by the RTOS system timer, but if you suddenly need less discreteness or a high speed of response to an event (and not until the main conveyor carries the task to execution), then the controlled interrupt mechanism, IMHO, is what Dr. prescribed.