This page contains quick guide on system startup and important implementation details.
Using TNeo in your application
The easiest way is to download version archive from downloads page: it contains bin
folder with library files for various platforms. These library files were built with default configuration file (src/tn_cfg_default.h
, see the section Configuration file).
If you use MPLABX, it is probably better to add library project to your main project instead; library projects reside in the <tneo_path>/lib_project
directory.
In either case, all you need is the following:
- Add library file for appropriate platform to your project (or probably library project in case of MPLABX);
- Add C include path:
<tneo_path>/src
;
- Copy default configuration file as current configuration file:
cp <tneo_path>/src/tn_cfg_default.h <tneo_path>/src/tn_cfg.h
(for more information about configuration file and better ways to manage it, refer to the section Configuration file)
- Add the file
<tneo_path>/src/tn_app_check.c
to your application project (see TN_CHECK_BUILD_CFG
for details on it).
That's all; you can use it in your project now. See below how to do this.
- Attention
- If you need to change the configuration, you can't just edit
tn_cfg.h
and keep using pre-built library file: you need to rebuild the library after editing tn_cfg.h
. Refer to the page Building TNeo for details.
Time ticks
The kernel needs to calculate timeouts. There are two schemes available: static tick and dynamic tick. For a quick guide, it's quite enough to just read about static tick, so, for the details on it, refer to the section Static tick and then return back here.
Starting the kernel
Quick guide on startup process
- You allocate arrays for idle task stack and interrupt stack, there is a convenience macro
TN_STACK_ARR_DEF()
for that. It is good idea to consult the TN_MIN_STACK_SIZE
to determine stack sizes (see example below).
- You provide callback function like
void init_task_create(void) { ... }
, in which at least one (and typically just one) your own task should be created and activated. This task should perform application initialization and create all the rest of tasks. See details in TN_CBUserTaskCreate()
.
- You provide idle callback function to be called periodically from idle task. It's quite fine to leave it empty.
- In the
main()
you should:
- disable system interrupts by calling
tn_arch_int_dis()
;
- perform some essential CPU configuration, such as oscillator settings and similar things.
- setup system timer interrupt (from which
tn_tick_int_processing()
gets called)
- call
tn_sys_start()
providing all necessary information: pointers to stacks, their sizes and your callback functions.
- Kernel acts as follows:
- performs all necessary housekeeping;
- creates idle task;
- calls your
TN_CBUserTaskCreate()
callback, in which your initial task is created with TN_TASK_CREATE_OPT_START
option;
- performs first context switch (to your task with highest priority).
- At this point, system operates normally: your initial task gets executed and you can call whatever system services you need. Typically, your initial task acts then as follows:
- Perform initialization of various on-board peripherals (displays, flash memory chips, or whatever);
- Initialize software modules used by application;
- Create all the rest of your tasks (since everything is initialized already so that they can proceed with their job);
- Eventually, perform its primary job (the job for which task was created at all).
Basic example for PIC32
This example project can be found in the TNeo repository, in the examples/basic/arch/pic32
directory.
- Attention
- Before trying to build examples, please read Building TNeo page carefully: you need to copy configuration file in the tneo directory to build it. Each example has
tn_cfg_appl.h
file, and you should either create a symbolic link to this file from tneo/src/tn_cfg.h
or just copy this file as tneo/src/tn_cfg.h
.
#include <xc.h>
#include <plib.h>
#include <stdint.h>
#pragma config FNOSC = PRIPLL // Oscillator Selection
#pragma config FPLLIDIV = DIV_4 // PLL Input Divider (PIC32 Starter Kit: use divide by 2 only)
#pragma config FPLLMUL = MUL_20 // PLL Multiplier
#pragma config FPLLODIV = DIV_1 // PLL Output Divider
#pragma config FPBDIV = DIV_2 // Peripheral Clock divisor
#pragma config FWDTEN = OFF // Watchdog Timer
#pragma config WDTPS = PS1 // Watchdog Timer Postscale
#pragma config FCKSM = CSDCMD // Clock Switching & Fail Safe Clock Monitor
#pragma config OSCIOFNC = OFF // CLKO Enable
#pragma config POSCMOD = HS // Primary Oscillator
#pragma config IESO = OFF // Internal/External Switch-over
#pragma config FSOSCEN = OFF // Secondary Oscillator Enable
#pragma config CP = OFF // Code Protect
#pragma config BWP = OFF // Boot Flash Write Protect
#pragma config PWP = OFF // Program Flash Write Protect
#pragma config ICESEL = ICS_PGx2 // ICE/ICD Comm Channel Select
#pragma config DEBUG = OFF // Debugger Disabled for Starter Kit
#define PIC32_SOFTWARE_BREAK() __asm__ volatile ("sdbbp 0")
#define SYS_FREQ 80000000UL
#define PB_FREQ 40000000UL
#define SYS_TMR_FREQ 1000
#define SYS_TMR_PRESCALER T5_PS_1_8
#define SYS_TMR_PRESCALER_VALUE 8
#define SYS_TMR_PERIOD \
(PB_FREQ / SYS_TMR_PRESCALER_VALUE / SYS_TMR_FREQ)
#define IDLE_TASK_STACK_SIZE (TN_MIN_STACK_SIZE + 32)
#define INTERRUPT_STACK_SIZE (TN_MIN_STACK_SIZE + 64)
#define TASK_A_STK_SIZE (TN_MIN_STACK_SIZE + 96)
#define TASK_B_STK_SIZE (TN_MIN_STACK_SIZE + 96)
#define TASK_C_STK_SIZE (TN_MIN_STACK_SIZE + 96)
#define TASK_A_PRIORITY 7
#define TASK_B_PRIORITY 6
#define TASK_C_PRIORITY 5
{
INTClearFlag(INT_T5);
}
void appl_init(void);
void task_a_body(void *par)
{
appl_init();
for(;;)
{
mPORTEToggleBits(BIT_0);
}
}
void task_b_body(void *par)
{
for(;;)
{
mPORTEToggleBits(BIT_1);
}
}
void task_c_body(void *par)
{
for(;;)
{
mPORTEToggleBits(BIT_2);
}
}
void hw_init(void)
{
SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);
AD1PCFG = 0xffffffff;
OpenTimer5((0
| T5_ON
| T5_IDLE_STOP
| SYS_TMR_PRESCALER
| T5_SOURCE_INT
),
(SYS_TMR_PERIOD - 1)
);
INTSetVectorPriority(INT_TIMER_5_VECTOR, INT_PRIORITY_LEVEL_2);
INTSetVectorSubPriority(INT_TIMER_5_VECTOR, INT_SUB_PRIORITY_LEVEL_0);
INTClearFlag(INT_T5);
INTEnable(INT_T5, INT_ENABLED);
INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR);
}
void appl_init(void)
{
mPORTESetPinsDigitalOut(BIT_0 | BIT_1 | BIT_2);
mPORTEClearBits(BIT_0 | BIT_1 | BIT_2);
&task_b,
task_b_body,
TASK_B_PRIORITY,
task_b_stack,
TASK_B_STK_SIZE,
NULL,
);
&task_c,
task_c_body,
TASK_C_PRIORITY,
task_c_stack,
TASK_C_STK_SIZE,
NULL,
);
}
void idle_task_callback (void)
{
}
void init_task_create(void)
{
&task_a,
task_a_body,
TASK_A_PRIORITY,
task_a_stack,
TASK_A_STK_SIZE,
NULL,
);
}
int32_t main(void)
{
#ifndef PIC32_STARTER_KIT
DDPCONbits.JTAGEN = 0;
#endif
hw_init();
idle_task_stack,
IDLE_TASK_STACK_SIZE,
interrupt_stack,
INTERRUPT_STACK_SIZE,
init_task_create,
idle_task_callback
);
return 1;
}
void __attribute__((naked, nomips16, noreturn)) _general_exception_handler(void)
{
PIC32_SOFTWARE_BREAK();
for (;;) ;
}
Round-robin scheduling
TNKernel has the ability to make round robin scheduling for tasks with identical priority. By default, round robin scheduling is turned off for all priorities. To enable round robin scheduling for tasks on certain priority level and to set time slices for these priority, user must call the tn_sys_tslice_set()
function. The time slice value is the same for all tasks with identical priority but may be different for each priority level. If the round robin scheduling is enabled, every system time tick interrupt increments the currently running task time slice counter. When the time slice interval is completed, the task is placed at the tail of the ready to run queue of its priority level (this queue contains tasks in the RUNNABLE
state) and the time slice counter is cleared. Then the task may be preempted by tasks of higher or equal priority.
In most cases, there is no reason to enable round robin scheduling. For applications running multiple copies of the same code, however, (GUI windows, etc), round robin scheduling is an acceptable solution.
- Attention
- Round-robin is not supported in Dynamic tick mode.