|
Home Up AvrX Fifo Overview Theory Getting Started AvrX 2.6 AvrX 2.3 IAR Mail
|
Getting Started
The easiest way to get started is to
build the kernel with one of the samples or test cases. After
that has been verified to work, strip the file down, rename it and add
your own code.
The top level file, in all but the most
trivial cases, like the test cases, should contain the at least the
following sections:
 | AvrXTimerHandler interrupt handler |
 | One or more internal or external
TASK definitions |
 | CPU_Reset routine or main(void) |
The last item needs to perform any
tasks necessary to prepare the system for running. These are at
least the following items, of which the first three are done for you
by the C compiler runtime:
 | Set the hardware Stack Pointer |
 | Clear SRAM |
 | Clear Registers |
 | Initialize tasks structures (AvrXInitTask
or AvrXRunTask) |
 | Initialize hardware (Timer0 or 1,
ports, serialio, whatever.) |
 | Jump to the routine Epilog(). |
The last instruction in the startup
code needs to be a jump to Epilog. That will start the
scheduling by switching the running context to the first item on the
_RunQueue. If the monitor is included in your program, it should
have the highest priority (0) and will be the first thing to run.
Typically user code would reside in
separate files and just the Task Control Blocks (TCB) would be
imported into the top level file. For AvrX 2.6 the C macro AVRX_EXTERNALTASK does the importing. Please refer to the header
files (avrx.inc or avrx.h) for specific details on how each macro
works and where they apply.
The startup code runs under the stack
set up by C runtime, or where ever you place it, in the case of
assembly. This location is essentially the stack that AvrX will
use as the kernel stack. So, everything done in the main() or
reset routine is considered to be running in the kernel context.
At least one task needs to be prepared to run with AvrXRunTask()
before exiting the reset code. If no tasks have been prepared,
Epilog() will find the RunQueue empty and will enter the idle task
permanently.
Although I have not done this, it would
be possible to start up in the idle mode and have an interrupt handler
"AvrXRunTask() a task. A more reasonable situation would be
to run all your tasks and have them do whatever initialization they
require and then block on something (timer, semaphore or message)
Optional Stuff
At a minimum, AvrX is simply task
initialization and semaphore support. Single Step, Timer Queue
Management and Message Queue Management are all optional services that
can be left out if not needed. The resulting kernel is quite
small without these services. Here are rough sizing for various
kernel functions
 | Basic tasking and Semaphore Queue
Management: ~670 bytes |
 | Time Queue Manager:
~236 |
 | Message Queue Manager:
48 |
 | Miscellaneous (Singe Step, Advanced
Tasking): 200 |
 | Debug Monitor: ~1300 bytes. |
 | Fifo support (written in C) ~300
bytes |
Without the debug monitor the total
size of AvrX for GCC is only ~600 words (1200 bytes) or 14% of the
code space of an 8515.
There is no fundamental reason that
timer support has to be included in your AvrX application. The
timer Queue Manager is just one implementation of a mechanism to allow
multiple competing tasks to schedule time delays. For simple
applications one might simply have an Real Time Clock interrupt signal
a semaphore and have a process that encapsulates all the time
dependent stuff. With every tick, the process will run, do work
and, optionally, set other semaphores to signal other processes that
it is time to do work. Alternatively, it could send messages. If
your timing requirements are modest, a simple task, or even interrupt
routine, might well be more efficient in both code space and processor
cycles.
The reason that Message Queue Manager
is so small is that many functions are already provided by the basic
tasking/semaphore module. Message queues are a really simple
concept that derives from the power of queueing semaphores.
Structure of a task
Although not absolutely necessary, a
task typically is a routine with an entry, some initialization and
then an endless loop. The endless loop typically involves
blocking, or waiting, on a semaphore. That might be explicitly
as in the case of AvrXWaitSemaphore, or it might be implicit in the
case of AvrXWaitTimer or AvrXWaitMessage. These last two items
actually bock on a semaphore embedded in the timer or message data
structure.
There are some data structures that
need to be defined along with the code. Most of the work can be
avoided by using handy macros defined in AvrX.inc or AvrX.h, depending
upon the version being used.
Below is a simple example of a task
that simply blocks on a timer and signals a semaphore. This is
code for AvrX 2.5, the GCC compiler.
Mutex Timeout;
AVRX_TASKDEF(myTask, 10, 3)
{
TimerControlBlock MyTimer;
while (1)
{
AvrXDelay(&MyTimer,
10); // 10ms delay
AvrXSetSemaphore(&Timeout);
}
}
The macro, AVRX_TASKDEF, takes three arguments: the
task (procedure) name, the additional stack required above the 35
bytes used for the standard context, and the priority. It builds
all required AvrX data structures and declares the C task procedure.
Please refer to the file AvrX.h for details.
Structure of an Interrupt Handler
Interrupt handlers have to have a specific name
associated with them for the GCC compiler to set the appropriate
interrupt vector name. Look at the avr-gcc file sig-avr.h for a
list of possible vectors. AvrX completely handles the saving and
restoring of the interrupted context so you DON'T want to use the GCC
procedure qualifiers SIGNAL or INTERRUPT. Instead use the
following:
AVRX_SIGINT(SIG_OVERFLOW0)
{
IntProlog();
// Switch to kernel stack/context
EndCriticalSection(); //
Re-enable interrupts
outp(TCNT0_INIT, TCNT0); // Reset timer overflow
count
AvrXTimerHandler();
// Call Time queue manager
Epilog();
// Return to tasks
}
IntProlog() does not re-enable the interrupts in an
interrupt handler (this is different from AvrX 2.3) so if you
want to be able to nest interrupts you need to explicitly enable them
in your handler. Also, beware, some sources of interrupts are
not cleared by servicing the interrupt handler, so you need to clear
them BEFORE enabling interrupts or you will endlessly re-enter your
code and blow your stack. The serial UART handlers are a good
example of this. Check the serial I/O files to see how this is
handled.
AvrX 2.3 enables interrupts by default in
IntProlog(), so you need to clear any pending interrupts BEFORE
calling IntProlog().
Structure of your main() code:
The main() code simply initializes hardware and any
tasks and then jumps to Epilog(). Here is the main() for the
sample file "messages.c" (Please refer to
the actual sample as coding styles have changed since this was
originally written) void main(void)
// Main runs under the AvrX Stack
{
outp((1<<SE) , MCUCR);
// Enable "Sleep" instruction for idle loop
outp(TCNT0_INIT, TCNT0); // TCNT0_INIT
defined in "hardware.h"
outp(TMC8_CK256 , TCCR0); // Set up Timer0
for CLK/256 rate
outp((1<<TOIE0), TIMSK); //
Enable0 Timer overflow interrupt
outp(-1, LED-1);
// Make PORTB output and
outp(-1, LED);
// drive high (LEDs off)
AvrXRunTask(TCB(task1));
AvrXRunTask(TCB(task2));
AvrXRunTask(TCB(Monitor));
InitSerialIO(UBRR_INIT); //
Initialize USART baud rate generator
Epilog();
// Switch from AvrX Stack to first task
}
Determining the size of various
stacks
For AvrX 2.6 the task stack needs to be 35 bytes +
any additional stack needed by the task code. The C compiler
makes pretty heavy use of the stack when calling procedures. It
just depends upon how many automatic variables are used in each
procedure. The best way to determine proper stack sizing is to
allocate lots of stack (say, 70 bytes) and run your application for a
while to see how deep the stack gets. Since GCC zeros all memory
during startup it is pretty easy to tell. However, to be safe, use the
emulator or debug monitor to write a few words of 0xFFFF near the
perceived end of the stack to verify exactly how deep it gets.
Then, allocate a couple more bytes (at least one or two words) extra
to insure you don't run over onto another stack or data structure.
Determining the kernel stack size is a little
easier. AvrX doesn't consume much stack in it's normal
operation. Perhaps 4-6 bytes at the most. However,
the kernel is re-entrant and will stack a full context for each
interrupt that is nested. So, you need to multiply the total
number of interrupt sources (assuming they are all active at all
times) by 35 and add any additional stack used by the interrupt code +
4-6 bytes used by AvrX to get a rough idea of the total stack needed.
Of, course, if your application has an interrupt that doesn't
use AvrX (e.g. no IntProlog()/Epilog()) then it only stacks whatever
it uses onto the kernel stack or the user stack. In either case,
use the above procedure to determine the exact size: allocate extra
space, run your application for a while (exercising the interrupt
sources) and see just how deep the stack gets. You might have to
deduce the maximum possible depth as some interrupt sources might not
happen often enough to cover all possible cases. For high speed
stuff (e.g. serial link dumping lots of data, basic timer for the
clock) usually all possible combinations will be covered within a
short while.
|