?? yavrtos.dox
字號:
lock_off(&porta_mutex);
// Now, suspend this task for another 200 ticks
wait_for_increment_of(&tick, 200);
}
}
// This is our second task - blinking port A bit 1 once every 280 ticks
void blink2(void *p) {
while (1) {
// Obtain a lock on the port A mutex
lock_on(&porta_mutex);
// OK - port A is now all ours, so blink the LED
PORTA ^= 0x02;
// Release our hold on the port A mutex
lock_off(&porta_mutex);
// Now, suspend this task for another 280 ticks
wait_for_increment_of(&tick, 280);
}
}
// This is our idle task - the task that runs when all others are suspended.
// We sleep the CPU - the CPU will automatically awake when the tick interrupt occurs
void idle_task(void *p) {
sleep_enable();
sei();
sleep_cpu();
// This task cannot be stopped, so it is automatically re-started whenever it tries to exit
}
// This is a function that runs every tick interrupt - we use it to increment the tick semaphore value by one
uint8_t tick_interrupt() {
increment_semaphore_by(&tick,1);
// We want a task switch to ALWAYS occur - it is part of the definition of the tick interrupt!
return 1;
}
// Setup the TIMER1_COMPA interrupt - it will be our tick interrupt
TASK_ISR(TIMER1_COMPA_vect, tick_interrupt())
// Our entry point
int main(void) {
// Interrupts should remain disabled - they will be enabled as soon as the first task starts executing
cli();
// Set up port A for output
DDRA = 0xFF;
// Our idle task sleeps the CPU - set the sleep mode to IDLE, as we need the sleep to be
// interruptable by the tick interrupt
set_sleep_mode(SLEEP_MODE_IDLE);
// Create our two tasks
create_task(blink1, 0, 0, 55, 100, 0);
create_task(blink2, 0, 0, 55, 100, 0);
// Set up our TIMER1_COMPA interrupt to tick every 80,000 clock cycles
TCCR1B = 0x0A;
OCR1A = 9999;
TIFR = _BV(OCF1A);
TIMSK |= _BV(OCIE1A);
// Start the RTOS - note that this function will never return
task_switcher_start(idle_task, 0, 55, 55);
return 0;
}
\endcode
*/
/**
\page howdoI How do I?
\section malloc How do I use malloc() and free() safely?
It depends on whether you intend to use malloc() or free() from within ISRs (which is highly
discouraged) or from within idle tasks (i.e. tasks with a zero priority). Note that
create_task() and reserve_task() can call malloc().
\subsection malloc-isr Using malloc() and free() when they are going to be used from within ISRs and/or idle tasks
If you are going to use malloc() or free() from within an ISR and/or an idle task, you will need to disable interrupts
either side of you call to malloc() and free()
\code
void proc() {
int *data = 0;
interrupt_store_t interrupts;
interrupts = disable_interrupts();
data = malloc(REQUIRED_INTS * sizeof(int));
restore_interrupts(interrupts);
...
interrupts = disable_interrupts();
free(data);
restore_interrupts(interrupts);
...
}
\endcode
The reason why we use the \c interrupt_store_t is because our \c proc() may have been called by another function that disabled interrupts for its own purposes, and it could be disastrous to unexpectedly enable interrupts on it.
We also want to disable interrupts for the shortest possible period of time, as disabling interrupts disables the all-important tick.
Note that interrupts will be disabled for the duration of the call to malloc() and free(). Depending on the algorithm used by
malloc() and free(), and depending on what your time margin for the launch of ISRs is, interrupts could be disabled for "too long". And note
that malloc() is called by reserve_task(), and can be called by create_task(), so using create_task() and reserve_task() in such time-critical
situations could break your application.
\subsection malloc-no-isr Using malloc() and free() when they are not going to be used from within ISRs or idle tasks
If you are not going to use malloc() or free() from within ISRs or idle tasks, then you can create a \ref mutex "mutex" for the
microcontroller's memory.
\code
mutex_t memory_mutex;
void proc() {
int *data = 0;
lock_on(&memory_mutex);
data = malloc(REQUIRED_INTS * sizeof(int));
lock_off(&memory_mutex);
...
lock_on(&memory_mutex);
free(data);
lock_off(&memory_mutex);
...
}
\endcode
If you do this, then you \b must specify \c wait_for_mutexes in stop_task() when stopping any task that uses
the memory mutex. This is because stop_task() could be called when the task is in the middle of executing
malloc() or free(), and if malloc() or free() are not allowed to complete themselves, corruption of the tables
used by malloc() and free() could result.
Using a memory mutex, however, has the significant advantage that interrupts are only disabled for a very short period of time
during lock_on() and lock_off().
If you are using a mutex for the microcontrollers' memory, remember to use that mutex in all calls to
create_task() and reserve_task(). (If you haven't yet started the RTOS with task_switcher_start(), then
the \c memory_mutex argument to create_task() and reserve_task() will be ignored, and interrupts
will be disabled during the calls to malloc()).
\code
mutex_t memory_mutex;
void proc() {
int *data = 0;
lock_on(&memory_mutex);
data = malloc(REQUIRED_INTS * sizeof(int));
lock_off(&memory_mutex);
...
// We must use the mutex in all calls to create_task() and reserve_task()!
create_task(task2, 0, 0, 55, 20, &memory_mutex);
...
lock_on(&memory_mutex);
free(data);
lock_off(&memory_mutex);
...
}
\endcode
\section how-write-isr How do I write my own ISR?
For instance, we will write an ISR for the INT0 interrupt.
\code
// Define the ISR prototype, so that the TASK_ISR() macro can reference our ISR function "int0_isr()"
uint8_t int0_isr();
// Set up the INT0 vector so that it calls the ISR
TASK_ISR(INT0_vect, int0_isr())
// Now, we can write the ISR function itself. It should return non-zero if a task switch should occur.
// It is recommended that it returns non-zero - the only time it would be safe for it to return zero
// would be if it didn't do anything to any task, semaphore, mailbox or mutex (i.e. if it didn't do anything
// that could un-suspend a suspended task).
uint8_t int0_isr() {
...
}
\endcode
Upon entry into the \c int0_isr function, interrupts will be disabled, and the system stack will be in use.
*/
/**
\page QandA Questions and Answers
\section qanda-int What happens if I disable interrupts, then suspend myself (e.g. by waiting on a semaphore)?
If you disable interrupts, then the task switcher cannot run, as it depends on the tick interrupt. However, if you then use an API call that can run the task switcher as a side-effect, the task switcher could decide to run a task that didn't disable interrupts Since the task switcher remembers which tasks have disabled interrupts and which tasks haven't, interrupts would be re-enabled.
However, the next time the task switcher chooses your task, it will re-disable interrupts before resuming your task. Indeed, it will disable interrupts before it even decides that your task is the next task that should run.
\section qanda-int2 Why would I want to disable interrupts anyway?
When interrupts are enabled, your task can be interrupted literally in the middle of a statement. If that statement was accessing a shared resource, then the state of that shared resource could change right in the middle of your statement. The only way to ensure that you are acting on a consistent shared resource is to disable interrupts as you are accessing it.
The classic example of this problem is a bank.
\code
int32_t balance = 80;
void deduct_task_1(void *p) {
if (balance >= 20) {
balance -= 20;
}
}
void deduct_task_2(void *p) {
if (balance >= 50) {
balance -= 50;
}
}
\endcode
Assuming that the two tasks are executing simultaneously, and \c deduct_task_1 is the first one to run. It will see that the balance is sufficient, and will execute the "balance -= 20" statement. What that statement actually means, of course, is "balance = balance - 20". In other words, "read the value of balance, subtract 20, and store the result back in balance".
The problem arises when the task is interrupted, for instance, between "subtract 20" and "store the result back in balance". When it has subtracted 20, the new value is 60, and it is prepared to store that result back. However, if the task is interrupted, and \c deduct_task_2 is run, it will put a value of 30 into balance. Then, when \c deduct_task_1 resumes, it will over-write the value of balance with 60, with the result that the customer has gained 50 cent.
The only way to fix this is to make sure that the two tasks cannot interrupt each other -
\code
int32_t balance = 80;
void deduct_task_1(void *p) {
interrupt_store_t interrupts = disable_interrupts();
if (balance >= 20) {
balance -= 20;
}
restore_interrupts(interrupts);
}
void deduct_task_2(void *p) {
interrupt_store_t interrupts = disable_interrupts();
if (balance >= 50) {
balance -= 50;
}
restore_interrupts(interrupts);
}
\endcode
You will notice that we disable interrupts before we check the customers balance - if we didn't, the balance could change between when we check it and when we do the deduction, and so we could end up giving the customer credit on their balance.
\section qanda-int3 Must I therefore disable interrupts every time I access a shared resource?
You can either disable interrupts, or create a \ref mutex "mutex" for the shared resource and lock on that whenever you access the resource. However, since mutexes are not available to ISRs, the only way to access a shared resource that is also used by an ISR is by disabling interrupts.
The only exception to this rule is if your C compiler compiles the statement that accesses the resource into a single CPU instruction. This can happen, but is rare.
Note that all YAVRTOS functions disable interrupts as appropriate, so if you are using \ref mailbox "mailboxes" or
\ref semaphore "semaphores" in an ISR, there is no need to worry about interrupts in your tasks when you are accessing those resources.
\section qanda-stop Why can a task only be stopped by a higher-priority task?
The only way that task A can know if it has stopped task B completely is if task A is a higher-priority task than task B. Why? Because a task C, of higher-priority than both A and B, could re-start task B as soon as it has died. But if task B is of lower priority than task A, task B will not be able to re-start until task A has finished processing the fact that task B has died.
(Therefore, it is in fact possible for a lower-priority task to stop a higher-priority task - it just isn't possible for that task to know when the higher-priority task has completely stopped. However, I think that is bad practice, so it is disabled in YAVRTOS).
\section qanda-pri Can I change the priority of a task?
No. This has to do with the stopping of tasks - if a task is being stopped by a higher-priority task, and it is re-started with an even higher priority, the task may get to start before the stopping task is informed that it has stopped.
\section qanda-sei When my ISR runs, interrupts are disabled - may I re-enable them?
Yes. If your ISR is interrupted by another ISR, the second ISR will continue to use the system stack. When the last ISR has completed, a task switch will be performed if any of the ISRs requested it.
\section qanda-cleanup What is the task cleanup function about?
The task cleanup procedure is called whenever a (non-zero-priority) task is stopped. A task is stopped by someone calling stop_task(), or when the task procedure finishes.
The task cleanup procedure should be used to release any resources that the task may have reserved. Since stop_task() may be called at any time, the task may have reserved resources (e.g. memory) that it would need to release before it goes away.
See task_stopper() for more information.
\section qanda-cleanup2 Can the task cleanup function be stopped?
No, though it is subject to the normal rules of the task scheduler.
\section qanda-task-inf Does a task have to have an infinite loop?
No - if a task returns, and it is a non-zero-priority task, it will be stopped. If it is a zero-priority "idle" task, it will be re-started.
\section qanda-cpuctx In task.h, what do the save_cpu_context() and restore_cpu_context() macros do?
These macros are the central part of a pre-emptive RTOS - what they do is save the state of the CPU to the stack, and restore it from the stack. So, when the CPU state is restored, the CPU is able to continue doing whatever it was doing as if it was never interrupted.
In save_cpu_context(), the R0 register is the first to be saved. Then the status register contents are copied to the R0 register, and interrupts are disabled. Then, the registers R1 to R31 are saved. Finally, R0 (i.e. the status register contents) is saved. Why do I do it this way? I want to disable interrupts as quickly as possible (otherwise, I could be interrupted in the middle of saving the CPU context to the stack, which would initiate another save of the CPU context to the stack, which could quickly overflow the stack). However, I also need to store the state of the global interrupts enabled flag in the status register. So I save R0 to the stack, then load the status register to R0, then disable interrupts, then save everything else.
In restore_cpu_context(), the R0 register (which contains the status register contents - including the global interrupt enable flag) is retrieved from the stack. Then the registers R31 to R1 are restored. Then R0 is copied into the status register - this may have the effect of re-enabling interrupts. Finally, the R0 register is retrieved from the stack.
\section qanda-saveint In task.h, what is "*(((uint8_t*)SP)+1) |= _BV(SREG_I);" about?
In a nutshell, that statement means "set the global interrupt enabled bit in the last byte on the stack". The last byte to be saved to the stack is the status register - the one that contains the global interrupt enabled bit. So why do we set this bit? Because we want interrupts to be enabled when we restore the CPU context. Why? Because interrupts were enabled when the ISR started (otherwise it wouldn't have started!) and we want to restore the state of the interrupts when we are finished.
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -