/* Comatos8515.c - Source code for the COMATOS RTOS (AT90S8515 version)
 Modified by BRL for 8 MHz 18Feb02
 Ben Greenblatt 6/8/00 - Adapted from assembly language version for the ATMega  103 
 1/29/01 - Modified for the AT90S8515 
 
 1/29/01:
 Compiled version is 2k with a minimal main() function 
 1/30/01:
 Tested several programs, including earlier designs for blinking light
 programs, button response, acknowledgements, and UART
 
 Interesting behavior found in the UART. If either the buffer is too
 small or the global timeout is too short, partial strings will be sent.
 In the acknowledgement program, the first task would partially fill the
 buffer. The second task would try to fill it more and be waiting for
 the first string to clear. During that time, the task would time out.
 The system timer would try to put the crash message in the buffer and
 also be stuck waiting. Then, the system would reset.
 
 Thus, it is important to either allocate a large enough buffer to prevent
 such clashes or adjust timeout length appropriately.
 */
 #include <90s8515.h> 
 #include <delay.h> 
 #include <stdlib.h>
 #include <stdio.h> 
   //#include <mega163.h>
   #include "comatos8515.h"
void OSInit(unsigned maxTimeout, unsigned char debug) {
   
   OSNumTasks=0xff;
   OSCurrentTask=0xff;
   OSMaxTimeout=maxTimeout;
 
 TCCR0=T0Prescale;
   TIMSK=TIMSK|TIMER0INTON;
   
   if (debug==1){
   
   OSInitUART(CLOCKSPEED/(16*9600) - 1); // Set up UART for 9600 baud
   UCR &= ~(1<<UDRIE); 
 }
   }
 
/* Create a task
   Sets up the PCB along with scheduling preferences (timeout value and messageMask)
   */
   void OSCreateTask(int (*entry_pt_a)(void),unsigned char timeout, unsigned char    messageMask, unsigned char state) {
   char *PCBptr;
   int OSNumTasksOffset; 
   //int temp;
   PCBptr = &PCBs; 
   OSNumTasks++;
   //OSNumTasksOffset = ((int)OSNumTasks) << 4;
   
   PCBs[OSNumTasks].entry_pt=entry_pt_a; 
   PCBs[OSNumTasks].PCBTimeoutVal=timeout; 
   PCBs[OSNumTasks].PCBMessMask=messageMask; 
   PCBs[OSNumTasks].PCBState=state; 
   }
/* MESSAGE PASSING RELATED FUNCTIONS */
/* Returns the current task's message source */
   unsigned char OSGetMessSrc(void) { 
   unsigned char temp; 
   char *PCBptr;
   int taskIndexOffset; 
   PCBptr = &PCBs; 
   taskIndexOffset = ((int)OSCurrentTask) << 4;
   //return PCBs[OSCurrentTask].PCBMessSrc; 
   temp = *(PCBptr + taskIndexOffset + PCBMessSrcOffset);
   *(PCBptr + taskIndexOffset + PCBMessSrcOffset)=0;
   return temp;
   }
/* Returns the acknowledgement byte of the current task */
   unsigned char OSGetAck(void) {
   unsigned char temp; 
   char *PCBptr;
   int OSCurrentTaskOffset; 
   PCBptr = &PCBs; 
   OSCurrentTaskOffset = ((int)OSCurrentTask) << 4;
 //temp=PCBs[OSCurrentTask].PCBAck;
   temp = *(PCBptr + OSCurrentTaskOffset + PCBAckOffset);
   //PCBs[OSCurrentTask].PCBAck=0;
   *(PCBptr + OSCurrentTaskOffset + PCBAckOffset) = 0;
   return temp; 
   }
/* Sends an acknowledgement to the specified task */
   void OSSendAck(unsigned char task) {
   char i;
   char ackMask; 
   char *PCBptr;
   int taskOffset; 
   PCBptr = &PCBs; 
   taskOffset = ((int)task) << 4;
   ackMask=1;
   
   /* Create the mask with the current task's bit set */
   //for (i=0;i<OSCurrentTask-1;i++)
   // ackMask<<1;
   // ackMask<<OSCurrentTask;
   ackMask<<=OSCurrentTask;
   
   //PCBs[task].PCBAck|=ackMask;
   *(PCBptr + taskOffset + PCBAckOffset)|=ackMask; 
   }
/* Sets the acknowledgement byte of the current task to the specified value    */
   void OSSetOwnAck(unsigned char val) { 
   char *PCBptr;
   int OSCurrentTaskOffset; 
   PCBptr = &PCBs; 
   OSCurrentTaskOffset = ((int)OSCurrentTask) << 4;
   
   //PCBs[OSCurrentTask].PCBAck=val;
   *(PCBptr + OSCurrentTaskOffset + PCBAckOffset)=val;
   }
/* Returns the message sent by the specified task to the current task */
   unsigned char OSGetMess(unsigned char senderTask) {
   /* Clear the message source bit corresponding to the sending task */
   char clearMask;
   //The next variables were added to speed up sched loop
   char *PCBptr;
   int taskIndexOffset; 
   PCBptr = &PCBs; 
   
   taskIndexOffset = ((int)OSCurrentTask) << 4; 
   clearMask=0xfe<<senderTask;
   //PCBs[OSCurrentTask].PCBMessSrc&=clearMask;
   *(PCBptr + taskIndexOffset + PCBMessSrcOffset) &= clearMask; 
 /* Return the message byte */
   //return PCBs[OSCurrentTask].PCBMessages[senderTask];
   return *(PCBptr + taskIndexOffset + PCBMessagesOffset + senderTask);
   }
/* Sends the specified message byte to the indicated task */
   void OSSendMess(unsigned char recipTask, unsigned char message) {
   ISRSendMess(recipTask, OSCurrentTask, message);
   }
   
   /* Sends the specified message byte to the indicated task as an ISR, using the    task number specified */
   void ISRSendMess(unsigned char recipTask, unsigned sendTask, unsigned char message)    {
   char setMask; 
   char *PCBptr;
   int taskIndexOffset; 
   PCBptr = &PCBs; 
   
   taskIndexOffset = ((int)recipTask) << 4; 
   /* Set the message source bit corresponding to the sending task in the receiver's    PCBMessSrc byte */
   setMask=1<<sendTask;
   //PCBs[recipTask].PCBMessSrc|=setMask; 
   *(PCBptr + taskIndexOffset + PCBMessSrcOffset)|= setMask;
 /* Place the message in the appropriate mailbox */
   //PCBs[recipTask].PCBMessages[sendTask]=message;
   *(PCBptr + taskIndexOffset +PCBMessagesOffset + sendTask)=message;
 
}
/* STATE MAINTAINANCE */
   
   /* Returns the state byte of the currently running task */
   unsigned char OSGetState(void) { 
   char *PCBptr;
   int OSCurrentTaskOffset; 
   PCBptr = &PCBs; 
   OSCurrentTaskOffset = ((int)OSCurrentTask) << 4;
   
   //return PCBs[OSCurrentTask].PCBState; 
   return *(PCBptr + OSCurrentTaskOffset + PCBStateOffset);
   
   }
/* Sets the state byte of the currently running task to the specified value    */
   void OSSetState(unsigned char val) { 
   char *PCBptr;
   int OSCurrentTaskOffset; 
   PCBptr = &PCBs; 
   OSCurrentTaskOffset = ((int)OSCurrentTask) << 4;
   //PCBs[OSCurrentTask].PCBState=val;
   *(PCBptr + OSCurrentTaskOffset + PCBStateOffset) = val;
   }
/* TASK SCHEDULING SETTINGS */
/* Sets the timeout value of the currently running task to the specified value    */
   void OSSetTimeout(unsigned char timeout) { 
   char *PCBptr;
   int OSCurrentTaskOffset; 
   PCBptr = &PCBs; 
   OSCurrentTaskOffset = ((int)OSCurrentTask) << 4;
   //PCBs[OSCurrentTask].PCBTimeoutVal=timeout;
   *(PCBptr + OSCurrentTaskOffset + PCBTimeoutValOffset)=timeout;
   }
/* Sets the message mask of the currently running task to the specified value    */
   void OSSetMessMask(unsigned char mask) {
   char *PCBptr;
   int OSCurrentTaskOffset; 
   PCBptr = &PCBs; 
   OSCurrentTaskOffset = ((int)OSCurrentTask) << 4;
   //PCBs[OSCurrentTask].PCBMessMask=mask;
   *(PCBptr + OSCurrentTaskOffset + PCBMessMaskOffset)=mask;
   }
/*********************************************SCHEDULER**********************************************************/
   /* Starts the OS */
   /* Contains the SCHEDULER code */
void OSStart(void) {
   char taskIndex;
   unsigned char temp; 
   unsigned char nTasks;
   unsigned char eBuf[3]; 
   char dead[]="dead";
   int retCode; 
 char (*fncn_temp)(void); 
   //The next variables were added to speed up sched loop
   char *PCBptr;
   int taskIndexOffset; 
   
   #asm("sei") // Enable interrupts
 nTasks=OSNumTasks; 
   PCBptr = &PCBs; 
   
   while (1) { 
   for (taskIndex=0;taskIndex<=OSNumTasks;taskIndex++) { 
   //get offest to PCB entry
   //works because there are 16 bytes/entry
   taskIndexOffset = ((int)taskIndex) << 4;
   
   //temp=PCBs[taskIndex].PCBMessMask & PCBs[taskIndex].PCBMessSrc;
   temp = *(PCBptr + taskIndexOffset + PCBMessMaskOffset) &
   *(PCBptr + taskIndexOffset + PCBMessSrcOffset); 
   
   /* If the task's message mask and message source match or the task is using    timeout and it's counter is zero, run it */
   // if ((PCBs[taskIndex].PCBTimeoutVal!=0 && PCBs[taskIndex].PCBCurrentTime==0)||    temp==PCBs[taskIndex].PCBMessMask) {
   if ((*(PCBptr + taskIndexOffset + PCBTimeoutValOffset)!=0 && 
   *(PCBptr + taskIndexOffset + PCBCurrentTimeOffset)==0) || 
   temp==*(PCBptr + taskIndexOffset + PCBMessMaskOffset)) {
   
   //PCBs[taskIndex].PCBCurrentTime=PCBs[taskIndex].PCBTimeoutVal;
   *(PCBptr + taskIndexOffset +PCBCurrentTimeOffset)=*(PCBptr + taskIndexOffset    +PCBTimeoutValOffset);
   OSCurrentTask=taskIndex; // Indicate which task is running
   OSTimeout=OSMaxTimeout; 
 //fncn_temp=PCBs[taskIndex].entry_pt; 
   //retreive an int from a char pointer
   fncn_temp=*(int*)(PCBptr + taskIndexOffset + entry_ptOffset);
   
   // Run the task. If it returns a non-zero value, stop the system 
   retCode=(*fncn_temp)();
   if (retCode!=0) {
   eBuf[0]=OSBadCode;
   eBuf[1]=taskIndex;
   eBuf[2]=(unsigned char)retCode;
   OSUARTTransmitBytes(&eBuf[0],3); 
   OSUARTTransmitBytes(dead,4); 
 while(1);
   } 
   //PCBs[taskIndex].PCBMessSrc=0; // Clear all evidence of messages
   *(PCBptr + taskIndexOffset + PCBMessSrcOffset)=0; 
   OSCurrentTask=0xff; // Indicate that no task is running 
   }
   
   
   }
   }
   }
/******************************************SYSTEM CLOCK*********************************************/
// Assembly definitions
   /* Timer 0 compare ISR: Runs every millisecond to decrement timers and increment    the four-byte global timer */ 
   interrupt [TIM0_OVF] void OS_System_Timer(void) { 
   unsigned char eBuf[5]; 
   char taskIndex; 
   int taskIndexOffset; 
   char pong[]="pong";
   char *PCBptr;
   PCBptr = &PCBs; 
 
 
   // Offset the count to accomodate for the error introduced by timer0 counting
   // to 255, not 250 and by the SaveISR function at the beginning of the interrupt
   // With this offset at 11, the system clock can obtain approximately 1 us accuracy.
   TCNT0=OSTIMER0START;
   
   
   // Check to see if this is a full millisecond or a half millisecond
   // Full millisecond if this condition is true 
   //MOD to ALWAYS 1 mSec
   // if (OSStatus&0x01) 
   OSStatus&=0xfe;
   // Half millisecond if the else is taken
   //else {
   // OSStatus|=0x01; // Indicate next time will be a full millisecond
   // return;
   //}
 
   
   // Turn on interrupts
   #asm("sei");
 // Increment the four byte global time
   OSGlobalTime++ ;
 // Check whether a task has failed to return. 
   if (OSCurrentTask!=0xff) {
   if (OSTimeout==0) {
   eBuf[0]=OSTimeoutCrash;
   eBuf[1]=0x30+OSCurrentTask;
   eBuf[2]=OSPCDumpFlag;
   #asm
   in r30, 0x3d ; Put the stack pointer in the Z register
   in r31, 0x3e
   adiw r30, 1 ; Increment Z pointer
   ld r22, Z+ ; Load high byte of PC from stack
   std Y+4, r22 ; Store it in eBuf[3]
   ld r22, Z ; Load the low byte of PC 
   std Y+5, r22 ; Store it in eBuf[4]
   #endasm
   #asm("sei")
   OSUARTTransmitBytes(&eBuf[0],5); 
   OSUARTTransmitBytes(pong,4); 
   TIMSK=0x00;
   while (1); // Halt the system 
   
   }
   else
   OSTimeout--;
   }
   
   // Run through each task and decrement it's PCBCurrentTime as appropriate 
   for (taskIndex=0;taskIndex<=OSNumTasks;taskIndex++) {
   
   if (PCBs[taskIndex].PCBTimeoutVal!=0 && PCBs[taskIndex].PCBCurrentTime!=0)
   //if(*(PCBptr + taskIndexOffset + PCBTimeoutValOffset) !=0 && 
   //*(PCBptr + taskIndexOffset + PCBCurrentTimeOffset) !=0)
   PCBs[taskIndex].PCBCurrentTime--;
   // *(PCBptr + taskIndexOffset + PCBCurrentTimeOffset)=*(PCBptr + taskIndexOffset    + PCBCurrentTimeOffset)-1 ;
   }
   } 
/***************************************** UART features *******************************************/
/* initialize UART */
   void OSInitUART( unsigned char baudrate ) {
   unsigned char x;
   UBRR = baudrate; /* set the baud rate */
   /* enable UART receiver and transmitter, and
   receive interrupt */
   UCR = ( (1<<RXCIE) | (1<<RXEN) | (1<<TXEN) );
   x = 0; /* flush receive buffer */
   UART_RxTail = x;
   UART_RxHead = x;
   UART_TxTail = x;
   UART_TxHead = x;
   }
/* interrupt handlers */
   interrupt [UART_RXC] void OSUART_RX_interrupt( void ) {
   unsigned char data;
   unsigned char tmphead;
   data = UDR; /* read the received data */
   /* calculate buffer index */
   tmphead = ( UART_RxHead + 1 ) & UART_RX_BUFFER_MASK;
   UART_RxHead = tmphead; /* store new index */
   if ( tmphead == UART_RxTail ) {
   /* ERROR! Receive buffer overflow */
   }
   UART_RxBuf[tmphead] = data; /* store received data in buffer */
   }
interrupt [UART_DRE] void OSUART_TX_interrupt( void ) {
   unsigned char tmptail;
   /* check if all data is transmitted */
   if ( UART_TxHead != UART_TxTail ) {
   /* calculate buffer index */
   tmptail = ( UART_TxTail + 1 ) & UART_TX_BUFFER_MASK;
   UART_TxTail = tmptail; /* store new index */
   UDR = UART_TxBuf[tmptail]; /* start transmition */
   }
   else {
   UCR &= ~(1<<UDRIE); /* disable UDRE interrupt */
   }
   }
/* Read and write functions */
   unsigned char OSUARTReceiveByte( void ) {
   unsigned char tmptail;
   
   while ( UART_RxHead == UART_RxTail ); /* wait for incomming data */
   
   tmptail = ( UART_RxTail + 1 ) & UART_RX_BUFFER_MASK;/* calculate buffer    index */
   UART_RxTail = tmptail; /* store new index */
   return UART_RxBuf[tmptail]; /* return data */
   }
   unsigned int OSUARTReceiveBytes( char *data, unsigned int n ) {
   unsigned char tmptail;
   int i;
 for (i=0;i<n;i++) {
   //while ( UART_RxHead == UART_RxTail ); /* wait for incomming data */
   if ( UART_RxHead == UART_RxTail ) /* wait for incomming data */
   break;
 tmptail = ( UART_RxTail + 1 ) & UART_RX_BUFFER_MASK;/* calculate buffer    index */
   UART_RxTail = tmptail; /* store new index */
   data[i]=UART_RxBuf[tmptail]; /* copy data into requested buffer */
   }
   return i; /* return data */
   }
void OSUARTTransmitByte( unsigned char data ) {
   unsigned char tmphead;
   /* calculate buffer index */
   tmphead = ( UART_TxHead + 1 ) & UART_TX_BUFFER_MASK; /* wait for free space    in buffer */
   while ( tmphead == UART_TxTail );
   UART_TxBuf[tmphead] = data; /* store data in buffer */
   UART_TxHead = tmphead; /* store new index */
   UCR |= (1<<UDRIE); /* enable UDRE interrupt */
   }
void OSUARTTransmitBytes( unsigned char *data, unsigned int n ) {
   unsigned char tmphead, i;
   /* calculate buffer index */
   for (i=0;i<n;i++) {
   tmphead = ( UART_TxHead + 1 ) & UART_TX_BUFFER_MASK; /* wait for free space    in buffer */
   while ( tmphead == UART_TxTail );
   UART_TxBuf[tmphead] = data[i]; /* store data in buffer */
   UART_TxHead = tmphead; /* store new index */
   }
   UCR |= (1<<UDRIE); /* enable UDRE interrupt */
   }
unsigned char OSUARTDataInReceiveBuffer( void ) {
   //return (UART_RxHead!=UART_RxTail); 
   // If the pointer to the head is in front of the tail, just subtract
   if (UART_RxHead >= UART_RxTail) 
   return (UART_RxHead-UART_RxTail);
   else
   // If the head is behind the tail, the head wrapped around at some point.
   // Adjust for the wrap around.
   return (UART_RxHead+(UART_RX_BUFFER_SIZE-UART_RxTail));
   }
   /***************************************** KEYPAD features *******************************************/
   void OSKeyPadInit(void)
   { 
   OSCreateTask(OSKeyPadDebounce, DebounceTime, NoKeyPadMessage, KEYSTATE_NoPush);    
   } 
char OSKeyPadDebounce(void) //debounce state machine
   { 
   if(OSGetState()== KEYSTATE_NoPush)
   { KEYPAD_KeyPress=OSKeyPad_Read(); 
   if(KEYPAD_KeyPress!=16)
   { 
   OSSendMess(0, KEYSTATE_StillPressed);
   OSSetState(KEYSTATE_MaybePush); 
   }
   else OSSetState( KEYSTATE_NoPush); 
   } 
   if(OSGetState()== KEYSTATE_MaybePush) { 
   
   if(KEYPAD_KeyPress==OSKeyPad_Read() && OSGetMess(0)== KEYSTATE_StillPressed)
   OSSetState( KEYSTATE_Pushed); 
   else 
   {
   // OSSendMess(0, KEYSTATE_error);
   OSSetState( KEYSTATE_NoPush); 
   
   }
   }
   if(OSGetState()== KEYSTATE_Pushed)
   {
   if(KEYPAD_KeyPress==OSKeyPad_Read() && OSGetMess(0)== KEYSTATE_StillPressed)
   OSSetState( KEYSTATE_Pushed);
   else 
   OSSetState( KEYSTATE_DebouncePress);
   
   }
   if(OSGetState()== KEYSTATE_DebouncePress)
   {
   // OSSendMess(1,1); 
   
   /* if(KEYPAD_letter[KEYPAD_KeyPress]=='D')
   { 
   OSKeyPadClearBuffer();
   }*/
   OSKeyPadWriteBuffer(KEYPAD_letter[KEYPAD_KeyPress]);
 OSSetState( KEYSTATE_NoPush);
   } 
   
   return 0; 
   }
   
   char OSKeyPad_Read(void) //reads the keypad
   { 
   char temp; 
   
   //get lower nibble
   DDRC = 0x0f;
   PORTC = 0xf0; 
   delay_us(5);
   KEYPAD_Key = PINC;
   
   //get upper nibble
   DDRC = 0xf0;
   PORTC = 0x0f; 
   delay_us(5);
   KEYPAD_Key = KEYPAD_Key | PINC;
   
   //find matching keycode in keytbl
   if (KEYPAD_Key != 0xff)
   {
   for (KEYPAD_butnum=0; KEYPAD_butnum<KEYPAD_maxkeys; KEYPAD_butnum++)
   { 
   if (KEYPAD_keytbl[KEYPAD_butnum]==KEYPAD_Key)
   { 
   temp=KEYPAD_butnum;
   break; 
   }
   }
   if (KEYPAD_butnum==KEYPAD_maxkeys) KEYPAD_butnum=0;
   else KEYPAD_butnum++; //adjust by one to make range 1-16
   } 
   else 
   {
   temp = 16;
   KEYPAD_butnum=0; 
   }
   return temp;
   } //end main
   /*void OSKeyPadWriteBuffer(char Key)
   { //send the value if the enter key'A' is pressed or when the buffer is full
   if((Key=='A' && KEYPAD_KeyFlag==1) || (KEYPAD_index ==KEYPAD_BUFFERSIZE))
   {
   
   KEYPAD_KeyFlag=0; 
   KEYPAD_Key_Ready=1; //string ready
   KEYPAD_index = 0; //reset index count
   } 
   else if(Key=='A' && KEYPAD_KeyFlag==0)
   {
   // printf("\r\n"); 
   PORTB=~0b10101010;
   // OSUARTTransmitBytes(empty, 20);
   // printf("\r\n"); 
   }
   else
   {
   KEYPAD_Buffer[KEYPAD_index]=Key; 
   // OSUARTTransmitBytes(buffer, 5); 
   // OSUARTTransmitByte(Key); 
   // printf("\r\n");
   KEYPAD_index++;
   KEYPAD_KeyFlag=1;
   } 
   
   } */
   void OSKeyPadWriteBuffer(char Key)
   { //send the value if the enter key'A' is pressed or when the buffer is full
   if(KEYPAD_index == (OSNumCharInput-1)) 
   {
   
   KEYPAD_KeyFlag=0; 
   KEYPAD_Key_Ready=1; //string ready
   KEYPAD_index = 0; //reset index count
   } 
   
   else
   {
   KEYPAD_Buffer[KEYPAD_index]=Key; 
   // OSUARTTransmitBytes(buffer, 5); 
   // OSUARTTransmitByte(Key); 
   // printf("\r\n");
   KEYPAD_index++;
   KEYPAD_KeyFlag=1;
   } 
   
   } 
   /*
   void OSKeyPadClearBuffer()
   { 
   while(KEYPAD_index!=0)
   {
   KEYPAD_Buffer[KEYPAD_index--]=0;
   
   }
   KEYPAD_KeyFlag=0;
   
   } 
   */ 
   void OSGetKeyReady(unsigned char x)
   {
   if(OSKeyLock==0) //if no one is using the keypad 
   {
   OSNumCharInput=x; 
   OSKeyLock=OSCurrentTask; //set lock so no one can use it 
   OSKeyCommand=0;
   }
   else OSKeyCommand=1; //some other task is currently using the keypad right now,    OSGetKeyCommand is off until the key unlocks
 
}
   unsigned int OSGetKeyCommand(char *data)
   { 
   if(OSKeyCommand==0)
   {
   int i;
   if(KEYPAD_Key_Ready==1)
   {
   
   for(i=0;i<OSNumCharInput;i++)
   {
   data[i]=KEYPAD_Buffer[i];
   KEYPAD_Buffer[i]=0;
   }
   data[i]=0; //terminate to make string
   KEYPAD_Key_Ready=0; 
   OSKeyLock = 0; //unlocks the KeyPad for someone else
 return 1;
   }
   
   }
   else return 0;
   
   }