/*******************************************************************************************************

      uCOSDemo3.c
      Z-World, 2000

This program does the following:

Start up task: Sets up stdio window, creates 5 tasks, updates system statistics in
					the stdio window every 5 seconds.
task 1:     	Posts messages to a queue for consumption by tasks 2 and 3.
task 2:			Waits for messages sent by task #1, and displays the received message
task 3:			Waits for up to 250mS for a message sent by task #1. Displays message
					if not timed out, otherwise displays 'T'.
task 4:			Posts a message to a mailbox and then immediately reads the message.
task 5:			Delays itself to determine how long OSTimeDly() takes to execute.

This program also makes use of OSTaskStatHook(), which is a callback function from the
OSTaskStat system task, as well as OSTaskSwHook(), which is called at every task switch.

The compiler will generate "User symbol 'X' overrides library symbol 'Y'" warnings for all
of the hook functions redefined in this file.  These warnings can be ignored.

See Newv251.pdf and Relv251.pdf in the samples/ucos-II directory for
information from Jean J. Labrosse pertaining to version 2.51 of uCOS-II.

********************************************************************************************************/
#class auto 			// Change default storage class for local variables: on the stack

/*
*********************************************************************************************************
*                                                uC/OS-II
*                                          The Real-Time Kernel
*
*                        (c) Copyright 1992-1998, Jean J. Labrosse, Plantation, FL
*                                           All Rights Reserved
*
*                                                 V2.51
*
*                                               EXAMPLE #3
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                               CONFIGURATION
*********************************************************************************************************
*/

// Redefine uC/OS-II configuration constants as necessary
#define OS_MAX_EVENTS          2       // Maximum number of events (semaphores, queues, mailboxes)
#define OS_MAX_TASKS           6  		// Maximum number of tasks system can create (less stat and idle tasks)
#define OS_TASK_CREATE_EN		 0       // Disable normal task creation
#define OS_TASK_CREATE_EXT_EN	 1       // Enable extended task creation
#define OS_TASK_STAT_EN			 1       // Enable statistics task creation
#define OS_Q_EN					 1			// Enable queue usage
#define OS_Q_POST_EN				 1			//
#define OS_MBOX_EN				 1  		// Enable mailbox usage
#define OS_MBOX_POST_EN			 1			//
#define OS_CPU_HOOKS_EN			 0			// Hook functions appear in this file
#define OS_TIME_DLY_HMSM_EN	 1			//

#define STACK_CNT_512	       8       // number of 512 byte stacks (application tasks + stat task + prog stack)

#use "ucos2.lib"

/*
*********************************************************************************************************
*                                             DATA TYPES
*********************************************************************************************************
*/

typedef struct {
    char    TaskName[30];
    INT16U  TaskCtr;
    INT32U  TaskExecTime;
    INT32U  TaskTotExecTime;
} TASK_USER_DATA;

/*
*********************************************************************************************************
*                                              CONSTANTS
*********************************************************************************************************
*/

#define          TASK_STK_SIZE     512                // Size of each task's stacks (# of WORDs)

#define          TASK_START_ID       0                // Application tasks
#define          TASK_1_ID           1
#define          TASK_2_ID           2
#define          TASK_3_ID           3
#define          TASK_4_ID           4
#define          TASK_5_ID           5

#define          TASK_START_PRIO    10                // Application tasks priorities
#define          TASK_1_PRIO        11
#define          TASK_2_PRIO        12
#define          TASK_3_PRIO        13
#define          TASK_4_PRIO        14
#define          TASK_5_PRIO        15

#define          MSG_QUEUE_SIZE     20                // Size of message queue used in example

/*
*********************************************************************************************************
*                                              VARIABLES
*********************************************************************************************************
*/

OS_EVENT        *MsgQueue;                            // Message queue pointer
void            *MsgQueueTbl[20];                     // Storage for messages
INT32U          TaskExeTime;
TASK_USER_DATA  TaskUserData[10];

/*
*********************************************************************************************************
*                                         FUNCTION PROTOTYPES
*********************************************************************************************************
*/

void             TaskStart(void *data);               // Function prototypes of tasks
void             TaskStartCreateTasks(void);
void             Task1(void *data);
void             Task2(void *data);
void             Task3(void *data);
void             Task4(void *data);
void             Task5(void *data);
void             ClearExtData();
void             ClearScreen();
void             DispStr(int x, int y, char *s);

/*
*********************************************************************************************************
*                                                  MAIN
*********************************************************************************************************
*/

void main (void)
{
    ClearScreen();                                         // Clear the screen
    OSInit();                                              // Initialize uC/OS-II
    strcpy(TaskUserData[TASK_START_ID].TaskName, "StartTask");
    OSTaskCreateExt(TaskStart,
                    (void *)0,
                    TASK_START_PRIO,
                    TASK_START_ID,
                    TASK_STK_SIZE,
                    &TaskUserData[TASK_START_ID],
                    0);
    OSStart();                                             // Start multitasking
}

/*
*********************************************************************************************************
*                                               STARTUP TASK
*********************************************************************************************************
*/

void  TaskStart (void *data)
{
    static char   s[80];
    auto INT16S key;
    auto INT8U  err;


    data = data;                                           // Prevent compiler warning

    ClearExtData();

    DispStr(26,  0, "uC/OS-II, The Real-Time Kernel");
    DispStr(33,  1, "Jean J. Labrosse");
    DispStr(36,  3, "EXAMPLE #3");
    DispStr( 0,  9, "Task Name         Counter  Exec.Time(mS)   Tot.Exec.Time(mS)  %Tot.");
    DispStr( 0, 10, "----------------- -------  -------------   -----------------  -----");
    DispStr( 0, 22, "Determining  CPU's capacity ...");
    DispStr(28, 24, "<-PRESS 'Q' TO QUIT->");

    OSStatInit();

    MsgQueue = OSQCreate(&MsgQueueTbl[0], MSG_QUEUE_SIZE); // Create a message queue

    TaskStartCreateTasks();

    TaskExeTime = MS_TIMER;                                // Start timer for first task

    DispStr(0, 22, "#Tasks          : xxxxx  CPU Usage: xxx %");
    DispStr(0, 23, "#Task switch/sec: xxxxx");
    for (;;) {
        sprintf(s, "%5d", OSTaskCtr);                      // Display #tasks running
        DispStr(18, 22, s);

        sprintf(s, "%3d", OSCPUUsage);                     // Display CPU usage in %
        DispStr(36, 22, s);

        sprintf(s, "%5d", OSCtxSwCtr / 5);                 // Display #context switches per second
        DispStr(18, 23, s);

        sprintf(s, "V%1d.%02d", OSVersion() / 100, OSVersion() % 100);
        DispStr(75, 24, s);

        OSCtxSwCtr = 0;                                    // Clear the context switch counter

        if (kbhit()) {                                     // See if key has been pressed
            key = getchar();
				if (key == 0x71 || key == 0x51) {              // Yes, see if it's the q or Q key
                exit(0);
            }
        }

        OSTimeDly(OS_TICKS_PER_SEC * 5);                   // Wait five seconds
    }
}

/*
*********************************************************************************************************
*                                      CREATE APPLICATION TASKS
*********************************************************************************************************
*/

void  TaskStartCreateTasks (void)
{
    strcpy(TaskUserData[TASK_1_ID].TaskName, "MsgQ Tx Task");
    OSTaskCreateExt(Task1,
                    (void *)0,
                    TASK_1_PRIO,
                    TASK_1_ID,
                    TASK_STK_SIZE,
                    &TaskUserData[TASK_1_ID],
                    0);

    strcpy(TaskUserData[TASK_2_ID].TaskName, "MsgQ Rx Task #1");
    OSTaskCreateExt(Task2,
                    (void *)0,
                    TASK_2_PRIO,
                    TASK_2_ID,
                    TASK_STK_SIZE,
                    &TaskUserData[TASK_2_ID],
                    0);

    strcpy(TaskUserData[TASK_3_ID].TaskName, "MsgQ Rx Task #2");
    OSTaskCreateExt(Task3,
                    (void *)0,
                    TASK_3_PRIO,
                    TASK_3_ID,
                    TASK_STK_SIZE,
                    &TaskUserData[TASK_3_ID],
                    0);

    strcpy(TaskUserData[TASK_4_ID].TaskName, "MboxPostPendTask");
    OSTaskCreateExt(Task4,
                    (void *)0,
                    TASK_4_PRIO,
                    TASK_4_ID,
                    TASK_STK_SIZE,
                    &TaskUserData[TASK_4_ID],
                    0);

    strcpy(TaskUserData[TASK_5_ID].TaskName, "TimeDlyTask");
    OSTaskCreateExt(Task5,
                    (void *)0,
                    TASK_5_PRIO,
                    TASK_5_ID,
                    TASK_STK_SIZE,
                    &TaskUserData[TASK_5_ID],
                    0);
}

/*
*********************************************************************************************************
*                                               TASK #1
*********************************************************************************************************
*/

void  Task1 (void *data)
{
    static char one;
    static char two;
    static char three;
    one   = '1';
    two   = '2';
    three = '3';
    data = data;

    for (;;) {
        OSQPost(MsgQueue, (void *)&one);
        OSTimeDly(OS_TICKS_PER_SEC);			// Delay for 1 second
        OSQPost(MsgQueue, (void *)&two);
        OSTimeDly(OS_TICKS_PER_SEC / 2);		// Delay for 500 mS
        OSQPost(MsgQueue, (void *)&three);
        OSTimeDly(OS_TICKS_PER_SEC);			// Delay for 1 second
    }
}

/*
*********************************************************************************************************
*                                               TASK #2
*
* Description: This task waits for messages sent by task #1.
*********************************************************************************************************
*/

void  Task2 (void *data)
{
    auto INT8U *msg;
    auto INT8U  err;
    auto char   buf[2];

    data = data;
    for (;;) {
        msg = (INT8U *)OSQPend(MsgQueue, 0, &err);    // Wait forever for message
        sprintf(buf, "%c", *msg);
        DispStr(70, 13, buf);
        OSTimeDly(OS_TICKS_PER_SEC / 2);              // Delay for 500 mS
    }
}

/*
*********************************************************************************************************
*                                               TASK #3
*
* Description: This task waits for up to 250mS for a message sent by task #1.
*********************************************************************************************************
*/

void  Task3 (void *data)
{
    auto INT8U *msg;
    auto INT8U  err;
    auto char   buf[2];

    data = data;
    for (;;) {
        msg = (INT8U *)OSQPend(MsgQueue, OS_TICKS_PER_SEC / 4, &err);  // Wait 250 mS for a msg
        if (err == OS_TIMEOUT) {
            DispStr(70, 14, "T");
        } else {
            sprintf(buf, "%c", *msg);
            DispStr(70, 14, buf);
        }
    }
}

/*
*********************************************************************************************************
*                                               TASK #4
*
* Description: This task posts a message to a mailbox and then immediately reads the message.
*********************************************************************************************************
*/

void  Task4 (void *data)
{
    auto OS_EVENT *mbox;
    auto INT8U     err;

    data = data;
    mbox = OSMboxCreate((void *)0);
    for (;;) {
        OSMboxPost(mbox, (void *)1);             // Send message to mailbox
        OSMboxPend(mbox, 0, &err);               // Get message from mailbox
        OSTimeDlyHMSM(0, 0, 0, 10);              // Delay 10 mS
    }
}

/*
*********************************************************************************************************
*                                               TASK #5
*
* Description: This task simply delays itself.  We basically want to determine how long OSTimeDly() takes
*              to execute.
*********************************************************************************************************
*/


void  Task5 (void *data)
{
    data = data;
    for (;;) {
        OSTimeDly(1);
    }
}


/*
*********************************************************************************************************
*                                      DISPLAY TASK RELATED STATISTICS
*********************************************************************************************************
*/

void DispTaskStat (INT8U id)
{
    static char s[80];
    auto INT8U err;


    sprintf(s, "%-18s %05u      %5ld          %10ld",
            TaskUserData[id].TaskName,
            TaskUserData[id].TaskCtr,
            TaskUserData[id].TaskExecTime,
            TaskUserData[id].TaskTotExecTime);
    DispStr(0, id + 11, s);
}

/*
*********************************************************************************************************
*                                           TASK SWITCH HOOK
*
* Description: This function is called when a task switch is performed.  This allows you to perform other
*              operations during a context switch.
*
* Arguments  : none
*
* Note(s)    : 1) Interrupts are disabled during this call.
*              2) It is assumed that the global pointer 'OSTCBHighRdy' points to the TCB of the task that
*                 will be 'switched in' (i.e. the highest priority task) and, 'OSTCBCur' points to the task
*                 being switched out (i.e. the preempted task).
*              3) This function should be marked nodebug so that the debug kernel does not interrupt the
*                 OS during a task switch.
*********************************************************************************************************
*/
nodebug void OSTaskSwHook (void)
{
    auto TASK_USER_DATA *puser;

    TaskExeTime = MS_TIMER  - TaskExeTime;               // This task is done
    puser = OSTCBCur->OSTCBExtPtr;                       // Point to used data
    if (puser != (void *)0) {
        puser->TaskCtr++;                                // Increment task counter
        puser->TaskExecTime     = TaskExeTime;           // Update the task's execution time
        puser->TaskTotExecTime += TaskExeTime;           // Update the task's total execution time
    }
    TaskExeTime = MS_TIMER;                              // Start timer for next task
}

/*
*********************************************************************************************************
*                                          STATISTIC TASK HOOK
*
* Description: This function is called every second by uC/OS-II's statistics task.  This allows your
*              application to add functionality to the statistics task.
*
* Arguments  : none
*********************************************************************************************************
*/
void OSTaskStatHook (void)
{
    static char   s[80];
    auto INT8U  i, err;
    auto INT32U total;
    auto INT8U  pct;


    total = 0L;                                          // Totalize TOT. EXEC. TIME for each task
    for (i = 0; i < 6; i++) {
        total += TaskUserData[i].TaskTotExecTime;
        DispTaskStat(i);                                 // Display task data
    }
    if (total > 0) {
        for (i = 0; i < 6; i++) {                        // Derive percentage of each task
            pct = (INT8U)(100 * TaskUserData[i].TaskTotExecTime / total);
            sprintf(s, "%3d %%", pct);
            DispStr(62, i + 11, s);
        }
    }
    if (total > 1000000000L) {                           // Reset total time counters at 1 billion
        for (i = 0; i < 6; i++) {
            TaskUserData[i].TaskTotExecTime = 0L;
        }
    }
}

/*
*********************************************************************************************************
*                                      HELPER FUNCTIONS
*********************************************************************************************************
*/

nodebug void ClearExtData()
{
   auto INT8U i;

   for(i = TASK_START_ID; i <= TASK_5_ID; i++)
   {
       TaskUserData[i].TaskCtr         = 0;
       TaskUserData[i].TaskExecTime    = 0;
       TaskUserData[i].TaskTotExecTime = 0;
   }
}

nodebug void ClearScreen()
{
   printf ( "\x1Bt" );            	// Space Opens Window
}

nodebug void DispStr(int x, int y, char *s)
{
   x += 0x20;
   y += 0x20;
   printf ("\x1B=%c%c%s", x, y, s );
}

#memmap xmem