TNKernel port for the Microchip PIC24/dSPIC and PIC32


This is an english version of the Alex Borisov article.
The original article (in russian) was publised at the Alex's site http://www.pic24.ru.
Please contact Alex(admin@pic24.ru) with any questions regarding the PIC24/dSPIC and PIC32 port.


Description



1. TNKernel for PIC24/dsPIC and PIC32 - the source code

    The structure of the TNKernel source code was significantly changed in the comparison with the original. The main difference - each function is located in a separate file. Thus, the problem rised by the linker C30, which can not ignore the unused sections of the code.
    With a separate file per a function, when you build the project into an executable, the linker adds only those features that are used in the application. This approach significantly reduces the amount of the program memory (FLASH), used by the kernel. For example, all services require about 15 KB of program memory, while the average application kernel RTOS takes about 6-7 KB.
    Additionaly, the splitting of the source code to the separate files makes TNKernel even more portable - all major types can be overriden, all a CPU-depended parts are placed in the separate modules (prefix port_). This approach helped to add a port for PIC32 with minimal time effort.

    So, after downloading the archive with the project and unpacking it, you will see a directory source with the source code for TNKernel PIC24/dsPIC.
    The file _build_mchp_c30.bat intended to build a library. It can be edited for the desired settings - another level of optimization, memory model, etc.

    The current version of the batch file is used to generate four libraries - two for PIC24 with optimization of Os with the output format coff and elf , and two for dsPIC. The same goes for the PIC32, a batch file for the PIC32 is the _build_mchp_c32.bat file.

    Before the compiling, you should include into the system's PATH variable the path to the compiler.

2. The port for PIC24/dsPIC and PIC32

    There are two separates ports of TNKernel - for PIC24 and dsPIC. In the dsPIC port, the DSP-core registers are not saved at the context switch and it should be treated as a shared resource (the access should be protected by the semaphore or the mutex).

    2.1 The port for PIC32

    Port for the PIC32 was implemented in spring 2010 by the community request. In fact, it is a very similar to the PIC24/dsPIC port.
    Since version 2.5.600, the are example which can be compiled for a PIC24 and PIC32 without changings.

3. The main differences from the original version

    3.1 Data types

    All standard data types (except void) redefined:

  TN_CHAR      - for all architectures corresponds to signed char(size - 8 bit).
  TN_UCHAR     - for all architectures corresponds to unsigned char
  TN_WORD      - for PIC24/dsPIC (compiler C30) is equal to the signed int,
                 (a signed 16-bit integer)
               - for the ARM and PIC32 is equal to the signed int (a signed 32-bit integer)
  TN_UWORD     - the size is equl the size of the machine word.
                 This type is  recommended to use for the task's stack declaration.
                  - for PIC24/dsPIC (compiler C30) it equal to unsigned int(an unsigned 16-bit integer)
                  - for ARM and PIC32 (C32) it is equal to unsigned int(an unsigned 32-bit integer)

  TN_SYS_TIM_T - the type for the system time counter.
                    - for the PIC24/dsPIC it is a 32-bit counter,
                    - for ARM/PIC32 - 64-bit counter,
  TN_TIMEOUT   - type is used for timeouts
                 - for PIC24/dsPIC (compiler C30)it is equal to unsigned int(an unsigned 16-bit integer)
                 - for ARM and PIC32 (C32) it is equal to unsigned int(an unsigned 32-bit integer)

    It is recommended to use these types to declare variables and arrays associated directly with the system - the task's stack, memory blocks of fixed size, message queues, etc.

    For example:

  TN_UWORD task_1_stack [128] TN_DATA;

    3.2 The tasks priorities

    In TNKernel port for PIC24/dsPIC, the user's tasks can have priorities from 1 to 14 (the priorities 0 and 15 are reserved for the system tasks).
    I should say that such a number of priorities is enough, becase TNKernel provides supports for the tasks with the same priority.
    In TNKernel port for PIC32, the user's tasks can have the same priorities as in the original (ARM) variant - from 1 to 30.

4. The System Initialization

    The initialization of the system in the original version TNKernel is performed by the function tn_start_system() without parameters.
    In the TNKernel port for PIC24/dsPIC and PIC32, this function is declared as:


  void tn_start_system (TN_UWORD * timer_task_stack,
                       TN_UWORD timer_task_stack_size,
                       TN_UWORD * idle_task_stack,
                       TN_UWORD idle_task_stack_size,
                       void (* app_in_cb) (void),
                       void (* cpu_int_en) (void),
                       void (* idle_user_cb) (void));


Allowed to call: in the task context

Function parameters:

  timer_task_stack          the stack pointer system task timer
  timer_task_stack_size     size of the stack system task timer (in machine words)
  idle_task_stack           the stack pointer system task idle
  idle_task_stack_size      size of the stack system task idle time (in machine words)
  app_in_cb                 function pointer to initialize the application.
                            This function is called after the system tasks are created,
                            and the scheduler is running
  cpu_int_en                a pointer to a configuration function interrupts.
                            This function is called immediately after the function
                            app_in_cb()
  idle_user_cb              pointer to function cyclically invoked from the idle task
                            (for example, to control the power consumtion etc.)

Return values: No

The usage example:


  #define TMR_TASK_STACK_SIZE 128
  #define IDL_TASK_STACK_SIZE 128

  TN_UWORD stk_tmr [TMR_TASK_STACK_SIZE] TN_DATA; /* task stack timer */
  TN_UWORD stk_idl [IDL_TASK_STACK_SIZE] TN_DATA; /* Idle task stack */

  void appl_init (void);
  void intr_init (void);
  void idle_user (void);

  int main (void)
  {
      tn_start_system (stk_tmr,
                      TMR_TASK_STACK_SIZE,
                      stk_idl,
                      IDL_TASK_STACK_SIZE,
                      appl_init,
                      intr_init,
                      idle_user);
  }

  void appl_init (void)
  {
      /* Initialization */
  }

  void intr_init (void)
  {
      /* Initialization and enabling interrupt(s)  */
  }

  void idle_user (void)
  {
      /* we are here, when the system idle task is running */
  }


    The functions app_in_cb() and cpu_int_en() replace the function tn_app_init() and tn_cpu_int_enable() from the TNKernel original version.
    The parameters in a function tn_start_system() provides more flexibility to customize the system, in particular, to choose the stack sizes of the system tasks and to perform some actions in the tn_task_idle().

5. The task creating

    The function tn_task_create() is declared in the TNKernel port as:


  TN_RETVAL tn_task_create (TN_TCB * task,
                           void (* task_func) (void * param),
                           TN_UWORD priority,
                           TN_UWORD * task_stack_start,
                           TN_UWORD task_stack_size,
                           void * param,
                           TN_UWORD option);

    The function parameter task_stack_start points to the lowest address of the task stack, whereas in the original version task_stack_start points to the highest address of the stack. This is due to the fact that PIC24/dsPIC stack grows from the low to the high address.
    In version TNKernel for PIC32 function parameter task_stack_start also should points to the lowest address of the task stack, despite the fact that the MIPS32 stack grows from the high to the low addresses.

6. The new parts

    6.1 Critical sections

    In TNKernel for PIC24/dsPIC and PIC32, the critical sections support was added.
    The functions tn_sys_enter_critical() and tn_sys_exit_critical() work similary to the functions tn_disable_interrupt() and tn_enable_interrupt() in the original version.

    An example of the functions usage:


  /* ...  */

  tn_sys_enter_critical();

  /*
     critical section of code that prohibited the context switch
   */

  tn_sys_exit_critical();

  /* ...  */


    The functions names reflect their purpose - the operating inside the critical section, when context switching is forbidden.
    The names tn_disable_interrupt() and tn_enable_interrupt() are not a right names for PIC24/dsPIC with their the vector priority interrupt controller.

    In the PIC32 TNKernel version, the function tn_sys_enter_critical() disables all interrupts!

    6.2 New Services

The following services have been added:


  tn_task_isuspend ()   - stop the task inside the interrupt
  tn_task_iresume ()    - resume task inside interrupt
  tn_sys_context_get () - get the current system context
  tn_task_reference ()  - get the information about the task
  tn_task_ireference () - get the information about the task inside interrupt


    6.3 The task attribute

    The task's function may be declared with the attribute TN_TASK.
The attribute tells the compiler that the function has an infinite loop inside (no exit). In most cases, it reduces the task stack size usage.

Example:


  void TN_TASK Task (void * par)
  {
     for(;;)
     {
        tn_task_sleep (10);
     }
  }


    6.4 Attribute data

    The data objects(variables) and task stacks may be declared with the attribute TN_DATA.
    In fact, the attribute places the variables in a separate RAM section - it allows to control the amount of RAM footprint.
    For this purpose, you should add to the linker script a following lines: (for instance, see the file ..\example1\p24FJ128GA006.gld)


 .tnk_data :
 {
     *(tnk_data);
 } > data


Example of the attribute usage:


TN_SEM   Sem_From_IRQ  TN_DATA;
TN_DQUE  que_test      TN_DATA;


    All the TNKernel services are placed in a separate code sections.
    It allows to control the amount of program memory.
    For this purpose, you should add to the linker script a following lines: (for instance, see the file ..\example1\p24FJ128GA006.gld ):


 .tnk_code :
 {
     *(tnk_code);
 } > program


    In TNKernel port for PIC32, the named section of the code are not yet supported.

7. Debugging

    If the TN_DEBUG is not defined in the header file tnkernel_conf.h, the internal structure of the all objects will be hidden from the user, and structure of objects in the debugger Watch window will be displayed as a byte array.
    If the TN_DEBUG is defined, the object structure fields will be visible in degugger.

8. Checking the function arguments validity

    The TNKernel port for PIC24/dsPIC and PIC32 has a two sets of services - with and without a function arguments validity checking.
    The code, generated without arguments checking, will take up less memory and the program will run faster.
    The defining TN_NO_ERROR_CHECKING in the system configuration header file tnkernel_conf.h enables this mode.

9. The stack overflow control

    The PIC24/dsPIC MCUs have a hardware stack overflow control mechanism that is fully engaged in TNKernel for PIC24/dsPIC.
    In order to control the stack overflow, you must declare in the code an exception (trap) on the error stack:


  void __attribute__ ((interrupt, no_auto_psv)) _StackError (void)
  {
     for (;;); / * for stack overflow problem would get here * /
  }


    In TNKernel PIC32 port, the stack overflow control is not supported.

10. The TERR_EXS return value

    The services that creates an object(tn_task_create(), tn_sem_create(), tn_queue_create() etc.) examines(regardless of the checking function arguments validity mode) the state of the object(already exists or not) and, if the object already exists, returns the error code TERR_EXS.

11. Obtaining TNKernel version

    The header file tnkernel_rev.h was added for this purpose. The file contains the following definitions:


  __TNKERNEL_VERSION   - current version(float)
  __TNKERNEL_REVISION  - current revision(unsigned int)
  __TNKERNEL_REVISION_TIME_STRING - date and time of the revision creation (text string)
  __TNKERNEL_BUILD_TIME_STRING    - date and time of the assembly library creation (text string)


For example:


  TN_UWORD tn_revision = __TNKERNEL_REVISION;
  char * tn_data       = __TNKERNEL_REVISION_TIME_STRING;
  char * tn_build      = __TNKERNEL_BUILD_TIME_STRING;

  #if (__TNKERNEL_VERSION == 2.5)
      #If (__TNKERNEL_REVISION == 977)

          / * ...  * /

      #endif
  #else

      /*...*/

  #endif

  printf (tn_data);


12. The system timer

    The system timer is a counter,that is incremened per each system tick. To set and get the value of the system timer, the following functions are used:


  tn_sys_time_set() - set the system timer
  tn_sys_time_get() - retrieves the value of the system timer


13. The configuration file

    The user's project should include the configuration header file tnkernel_conf.h. Inside the file, TN_DEBUG and TN_NO_ERROR_CHECKING may be defined.
    In the MPLAB IDE Build Options For Project dialog, the file's directory must be added to the search path for the header files:

MPLAB IDE dialog

14. The interrupts usage

    All interrupts must be disabled before the system startup. For the configuration purpose an interrupt sources and interrupt enabling, the function cpu_int_en() is used. The pointer for the function is passed to the function tn_start_system().
    The interrupts that calls the system service(s) are system interrupts, and all other interrupts - user interrupts.

    In TNKernel for PIC24/dsPIC system interrupts should have priority TN_INTERRUPT_LEVEL (Priority 1).
    The usage of the system services inside user interrupts(with higher priority) is forbidden - it will cause a system crash.
    In the current TNKernel version, there are no protection from the calling system services in a user interrupt - this is a programmer's responsibility.

    In TNKernel for PIC32 a system interrupts should have the same priority, but not necessarily equal to TN_INTERRUPT_LEVEL. However, for the code compatibility, it is recommended to use priority 1, as in TNKernel port for PIC24/dsPIC.

    In TNKernel ports for PIC24/dsPIC and PIC32 there are no nested interrupts.

    On the one hand, this approache could delay processing of the interrupt, on the other hand - it saves stack usage problem, what is actually more important, especially for the PIC32.
    In the very rare cases, when the delay in the interrupt entrance is unacceptable, you can use a user interrupt with a priority greater than TN_INTERRUPT_LEVEL.
    However, we should not forget that the call to system service in the user interrupt is prohibited, so something like global variables should be used.
    It's ugly and it isn't a right approach, but there is no alternative here...

    The system interrupts are declared using the macro tn_sys_interrupt


   /* PIC24/dsPIC */

  tn_sys_interrupt(_INT0Interrupt)  /* The system interrupt, source is INT0 */
  {
      IFS0bits.INT0IF = 0;
      tn_queue_isend_polling(& que_test, transceived_buff);
  }

  /* PIC32 */

  tn_sys_interrupt(_CHANGE_NOTICE_VECTOR)
  {
      INTClearFlag(INT_CN);

      /* Interrupt handler */
        ...
  }


    The user interrupts are declared by the standart C30/C32 compiler approach.
    A certain system interrupt should always be reserved for the system timer. Usually, this interrupt powered by the hardware timer with a period of 1..10 ms:


  /* PIC24/dsPIC */
 tn_sys_interrupt (_T2Interrupt) /* System interrupt, the source - TMR2 */
 {
     IFS0bits.T2IF = 0;
     tn_tick_int_processing();
 }

 /* PIC32 */
 tn_sys_interrupt (_CORE_TIMER_VECTOR) / * System interrupt, the source -
                                                    a MIPS32 system timer  */
 {
     sys_tmr_int_handler();
     tn_tick_int_processing();
 }


    The function tn_tick_int_processing() should only be called from the system interrupt. In the intertupt(where the function is called), the call to other services (TNKernel functions) is forbidden.

    For PIC24/dsPIC only, the function tn_sys_enter_critical() disables the 'system' interrupt only, while an any interrupt with a priority greater than TN_INTERRUPT_LEVEL remains active.

    For PIC32, the function tn_sys_enter_critical() disables all interrupts.

15. The PIC32 port traits

    - The function tn_sys_enter_critical() disables all interrupts, not just wit with the priority TN_INTERRUPT_LEVEL
    - The data and code are not placed into named sections
    - The system interrupts level may be different from the priority TN_INTERRUPT_LEVEL, but the priority level should be the same for all system interrupts.
    - The user's tasks priorities may have level 1..30
    - The stack overflow is not controlled.



Downloads



tnkernel_2_5_716.zip   This is TNKernel port for PIC24/dsPIC/PIC32, provided by Alex Borisov.
  The original file can be downloaded from http://www.pic24.ru