/************************************************************ 
Copyright (C) 1995-2006 Pumpkin, Inc. and its
Licensor(s). Freely distributable.

$Source: C:\\RCS\\D\\Pumpkin\\Salvo\\Examples\\ARM\\Luminary_LM3S1XX\\Luminary_DK-LM3S1XX\\RVMDK\\Tut\\Tut5\\tut5.c,v $
$Author: aek $
$Revision: 3.4 $
$Date: 2006-05-16 22:30:51-07 $

Tutorial program Tut5.

See Salvo User Manual for in-depth description. 

This application builds on Salvo's tut5 by adding more
functionality to help illustrate the advantages of 
multitasking. After startup, the LCD on the Stellaris 
development board will display the current number of
task switches and the number of times the idling hook has
been called. Since TaskCount() only yields, when it is
running, the idling hook cannot run (because taskCount() is
always eligible). By pressing the pushbutton you can start
and stop TaskCount(). When stopped, the idling hook can
run during those times that TaskShow(), TaskBlink() and 
TaskLCD() are delayed or waiting. For a 6MHz crystal, this
application task switches at approximately 23,000 Hz.
The idling hook -- which runs in the window when no tasks
are eligible to run -- runs at approximately 64,000 Hz.

TaskLCD uses both local auto (strTmp1, strTmp2) and static
(i, status) variables depending on whether persistence
across context switches is required.

Function execution speeds (with 6MHz crystal):

Name			Speed	Comment
--------------------------------------------------------
IdlingHook()   ~64kHz	only when TaskCount() is stopped
TaskCount()	   ~23kHz	under control of pushbutton SW3
TaskLCD() 		 50Hz	continuously
TaskShow()		~44Hz	only when TaskCount() is running
TaskBlink() 	  2Hz 	continuously

Over a 10-minute period, there are almost 150 times as 
many idling hook executions as task switches. Therefore 
the application is idle around 99.3% of the time ... which 
is typical for an event-driven RTOS like Salvo.

************************************************************/
 
#include <salvo.h>

#include "hw_types.h"
#include "hw_memmap.h"  /* peripheral addresses 			*/
#include "sysctl.h"     /* system control API 				*/
#include "gpio.h"       /* GPIO API 						*/
#include "osram96x16.h" /* API for peripheral controller 	*/

extern void InitSysTickTimer(void);
#define GPIO_PIN_BLINKLED		 GPIO_PIN_5
#define GPIO_PIN_FREEZE		     GPIO_PIN_4

#define PUSHBUTTON_PRESSED 		 (GPIOPinRead(GPIO_PORTC_BASE, GPIO_PIN_FREEZE) != GPIO_PIN_FREEZE)
#define PUSHBUTTON_RELEASED 	 (GPIOPinRead(GPIO_PORTC_BASE, GPIO_PIN_FREEZE) == GPIO_PIN_FREEZE)

#define TASK_COUNT_P             OSTCBP(1)  /* task #1        */
#define TASK_SHOW_P              OSTCBP(2)  /*  ""  #2        */
#define TASK_BLINK_P             OSTCBP(3)  /*  ""  #3        */
#define TASK_LCD_P               OSTCBP(4)  /*  ""  #4        */

#define PRIO_COUNT               10         /* task priorities*/
#define PRIO_SHOW                10         /*  ""            */
#define PRIO_BLINK               2          /*  ""            */
#define PRIO_LCD                 5          /*  ""            */

#define BINSEM_UPDATE_PORT_P     OSECBP(1)  /* binSem #1      */

#if OSSALVOLITE
#define strHeader0				"Salvo Lite RTOS"
#elif OSSALVOLE
#define strHeader0				"Salvo LE RTOS  "
#elif OSSALVOPRO
#define strHeader0				"Salvo Pro RTOS "
#else
#error tut5.c: Unknown Salvo distribution -- check your salvocfg.h.
#endif

#define strHeader1		 		" on  Stellaris "

#define strExplain0		 		"LCD displays   "
#define strExplain1		 		"the number of  "
#define strExplain2		 		"task switches  "
#define strExplain3		 		"and idle hooks "
#define strExplain4		 		"called. Press  "
#define strExplain5		 		"USER button to "
#define strExplain6		 		"start or stop  "
#define strExplain7		 		"TaskCount.     "

#define strMessage0		 		"TaskCount()    "
#define strMessage1		 		"was stopped.   "
#define strMessage2		 		"is running.    "

char const str[16][16] = {  	strHeader0,
								strHeader1,
								strExplain0,
								strExplain1,
								strExplain2,
								strExplain3,
								strExplain4,
								strExplain5,
								strExplain6,
								strExplain7,
								strMessage0,
								strMessage1,	   
								strMessage2 };	   


unsigned int counter, taskCounter, idleCounter;


void Write2LinestoLCD (const char *  str1, const char *  str2)
{
    OSRAMStringDraw(str1, 0, 0);
    OSRAMStringDraw(str2, 0, 1);
}


/* Quick itoa to avoid using run time library or sprintf(). */
char *utoapad(unsigned int u, int pad)
{
    static char buf[16];
    int digit;
    int idx;

    idx = sizeof(buf) - 1;
    buf[idx] = 0;
    do
    {
        --idx;
        if((idx == 0) && (u > 10))
        {
            buf[0] = '!';
            return(&buf[0]);
        }
        digit = u % 10;
        buf[idx] = digit + '0';
        u /= 10;
        if(pad)
        {
            --pad;
        }
    } while((u || pad) && idx);

    return(&buf[idx]);
}


void TaskCount (void)
{
    for (;;) 
    {
        counter++;
        
        /* Every time bit 8 of counter rolls over, signal the binSem. 		*/ 
        if (!(counter & 0x01FF))
        {
            OSSignalBinSem(BINSEM_UPDATE_PORT_P);
        }
    
        /* Context-switch so other tasks can run.                     		*/
        OS_Yield();
		taskCounter++;
    }
}


void TaskShow (void)
{
    for (;;) 
    {
        /* Context-switch and wait for TaskCount() to signal that 512 		*/
        /*  counts have elapsed.                                      		*/
        OS_WaitBinSem(BINSEM_UPDATE_PORT_P, OSNO_TIMEOUT);
		taskCounter++;
    }
}


void TaskBlink (void)
{
    for (;;)
    {
		/* Toggle LED0. 													*/
        GPIOPinWrite(GPIO_PORTC_BASE, GPIO_PIN_BLINKLED, 
            ~GPIOPinRead(GPIO_PORTC_BASE, GPIO_PIN_BLINKLED));

        /* Context-switch and return in 500ms. 								*/
        OS_Delay(50);
		taskCounter++;
    }
}


void TaskLCD (void)
{
	char strTmp1[16], strTmp2[16];	/* local storage of strings 			*/
	static unsigned char i;  		/* persistent loop counter 				*/
	typedef struct {
  		unsigned int stateDebounce:2;
  		unsigned int runTaskCount:1;
		unsigned int updateLCD:1;
	} typeStatus;
	static typeStatus status;  		/* bitfields for debounce state machine */


	/* Initialize LED. */
    OSRAMInit(false);

	/* Initial messages. */
	for (i = 0; i < 10 ; i+= 2)
	{
		Write2LinestoLCD(str[i], str[i+1]);
		OS_Delay(150);
		taskCounter++;
	}

	i = 25; 					/* 25 x 2 = 0.5s, the LCD update period.    */
	status.stateDebounce = 0;	/* debounce state machine's initial state 	*/
	status.runTaskCount  = 1;  	/* TaskCount() is initially running. 		*/
	status.updateLCD     = 0;   /* for feedback when pushbutton is pressed 	*/

	/* Debounce button and display counters in real time. Salvo's delay		*/
	/*  services simplify the state machine and allow other tasks to run	*/
	/*  concurrently during the relatively long 20ms debounce period.		*/
	/*																		*/
	/* State 0: Initial state, advance only if pushbutton is depressed.		*/
	/* State 1:	Debounce key and if good, start or stop TaskCount().		*/
	/* State 2: Return to initial state after pushbutton is released.		*/
	/*																		*/
	/* Update the LCD every 50 ticks (0.5s). Either flash a message about 	*/
	/*  TaskCount()'s current state or update the counters display.	<i>		*/
	/*  is used as a persistent loop counter to be able to update the LCD	*/
	/*  at a multiple (here, 25) of this task's delay period.				*/
    for (;;)
    {
	  	switch (status.stateDebounce)
		{
			case 0:
				if (PUSHBUTTON_PRESSED)
				{
					status.stateDebounce++;
				}
				break;

			case 1:
				if (PUSHBUTTON_PRESSED)
				{
					if (status.runTaskCount == 1)
					{
						OSStopTask(TASK_COUNT_P);
						status.runTaskCount = 0;
					}
					else 
					{
						OSStartTask(TASK_COUNT_P);
						status.runTaskCount = 1;
					}
					status.updateLCD = 1;
					status.stateDebounce++;
				}
				else 
				{
					status.stateDebounce = 0;
				}
				break;
				
			case 2:
				if (PUSHBUTTON_RELEASED)
				{
					status.stateDebounce = 0;
				}
				break;

			default:
				break;
		}
		
		if (i-- == 0)
		{
			if (status.updateLCD)
			{
			 	if (status.runTaskCount == 0)
				{
					Write2LinestoLCD(str[10], str[11]);
				}
				else
				{
					Write2LinestoLCD(str[10], str[12]);
				}
				
			  	status.updateLCD = 0;
			}
			
			else
			{
				strTmp1[0] = 0;
				strTmp2[0] = 0;
				strcat(strTmp1, "idle:");
				strcat(strTmp2, "task:");
				strcat(strTmp1, utoapad(idleCounter, 10));
				strcat(strTmp2, utoapad(taskCounter, 10));
				Write2LinestoLCD(strTmp1, strTmp2);
			}

			i = 25; 
		}

		/* Delay 2 ticks (20ms) and continue forever */
		OS_Delay(2);
		taskCounter++;
    }
}


void OSIdlingHook (void)
{
	idleCounter++;
}


int main (void)
{
    /* Initialize peripherals. */
    OSRAMInit(false);
    InitSysTickTimer();
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
    GPIODirModeSet(GPIO_PORTC_BASE, GPIO_PIN_BLINKLED, GPIO_DIR_MODE_OUT);  /* b0 output */
    GPIODirModeSet(GPIO_PORTC_BASE, GPIO_PIN_FREEZE,   GPIO_DIR_MODE_IN); 	/* b4 input */

	/* Initialize Salvo. */
    OSInit();
    
    /* Create Salvo tasks. */
    OSCreateTask(TaskCount, TASK_COUNT_P, PRIO_COUNT);
    OSCreateTask(TaskShow,  TASK_SHOW_P,  PRIO_SHOW );
    OSCreateTask(TaskBlink, TASK_BLINK_P, PRIO_BLINK);
    OSCreateTask(TaskLCD,   TASK_LCD_P,   PRIO_LCD);

	/* Create alvo events. */
    OSCreateBinSem(BINSEM_UPDATE_PORT_P, 0);

	/* Initialize global var <counter>. */
    counter = 0;

	/* Enable interrupts. */
    OSEnable();
    
	/* Multitask forever. */
    for (;;)
    {
       OSSched();
    }
}
