skarven
 
 
  Joined: 12 Mar 2009 Posts: 2
  
			
			 
			 
			
			
			
			
			
			
			
  
		  | 
		
			
				| Boat Autopilot on 16F876 | 
			 
			
				 Posted: Fri Mar 13, 2009 12:20 pm     | 
				     | 
			 
			
				
  | 
			 
			
				Hi
 
A few years ago I made an autopilot for my boat on a 16F876.
 
It has been used for about 5000 hours of boating and has one known bug.
 
(not serious!)
 
 
It controls a hydraulic valve with two solenoids and uses an electronic
 
compass that sends 10 $HEHDT telegrams/s.  This is the system clock.
 
 
Input to the program is an infrared Sony remote control, and I think this part
 
of the code could be interesting to some.  It might not be very elegant, but 
 
it works very well.
 
 
Output is to a LCD 20*4 display.
 
 
The Autopilot is special in that it does not use a rudder position sensor, and while "normal" autopilots run the rudder from port to starboard in about 8s, this one does it in about 2s.
 
 
It will control the 25 feet boat in 8 feet waves! Tested even in following seas!
 
 
The LCD interface is CCS modified to free B0 pin for IR interface
 
 
This autopilot has given us thousands of hours of relaxed boating in a 
 
relatively course unstable boat.
 
 
Kai Dahlqvist
 
kai@smsc.no
 
kaid@broadpark.no
 
 
 
 
 	  | Code: | 	 		  
 
//               Autopilot
 
//
 
//   This program is a complete Autopilot for my boat which is a
 
//   Nord West 741 with length 7.4m beam 2.8m.  The engine is a 
 
//   Volvo Penta MD40A giving 85HP and a maximum cruising speed of about
 
//   13 knots.
 
//
 
//  The steering system is Tennfjord
 
//  with two hand pumps with capacity app. 27ccm/revolution.
 
//   Steering cylinder is 96 ccm.
 
//
 
//   The Servo system is A Servi pump with capacity app. 2 liters/min
 
//   Control of the servo system is by two digital outputs from the PIC controlling
 
//   the hydraulic valve solenoids
 
//  In addition there is a filter change warning signal input,  alarm output
 
//   and a Windscreen Viper output.
 
//
 
//   Heading sensor is Furuno PG-1000 set up to give 10 HCHDT telegrams every second.
 
//
 
//   The user interface is an LCD display and all user input to the program is by
 
//   a SONY remote control.  The remote control signal is input to Pin B0 from 
 
//   an IRM-8601S infrared receiver and decoded with interrupt on change B0.
 
//
 
//   The backlight for the LCD is controlled by Pulse Width
 
//   Modulation from the CCP1.
 
//
 
//#include <stdlib.h>
 
//#define DIAG                     // only for testing and debugging
 
#define DPRINT6
 
//#include <\picc\include\16F877.H>      // 40-pin device
 
//#include <\picc\include\16F876.H>      // 28-pin device
 
//#include <\picc\include\inc16f876.h>      // 28-pin device
 
#include "c:\picc\include\16F876.H"      // 28-pin device
 
#include "c:\picc\include\stdlib.h"
 
//#include "c:\picc\include\STDLIB.H"
 
 
#fuses HS,NOWDT,NOPROTECT,PUT,NOBROWNOUT,NOLVP   // May need to be changed depending on the chip
 
#use delay(clock=20000000)  // Change if you use a different clock
 
#use rs232(baud=4800, xmit=PIN_C6, rcv=PIN_C7)
 
 
////////////////////////////////////////////////////////////////////////////
 
////                             LCD.C                                  ////
 
////                 Driver for common LCD modules                      ////
 
////                                                                    ////
 
////  lcd_init()   Must be called before any other function.            ////
 
////                                                                    ////
 
////  lcd_putc(c)  Will display c on the next position of the LCD.      ////
 
////                     The following have special meaning:            ////
 
////                      \f  Clear display                             ////
 
////                      \n  Go to start of second line                ////
 
////                      \b  Move back one position                    ////
 
////                                                                    ////
 
////  lcd_gotoxy(x,y) Set write position on LCD (upper left is 1,1)     ////
 
////                                                                    ////
 
////  lcd_getc(x,y)   Returns character at position x,y on LCD          ////
 
////                                                                    ////
 
////  LCDB0I.C is a modified LCD.C which frees the B0 pin for input     ////
 
////////////////////////////////////////////////////////////////////////////
 
#include "c:\picc\kpilot\lcdb0i.c"
 
 
//#define T1_1   85  Theoretical
 
//#define T1_65 129
 
//#define T2_2  171
 
#define T1_1   93      // These values were set after testing the real thing
 
#define T1_65 139      // 
 
#define T2_2  186      // 184 - 191
 
#define MErr   10
 
 
#define FILTER_COMPASS   // Filter compass input
 
#define MAXSPEED (12)   // Boats maximum speed
 
#define MAXRUDDER (750)   // Maximum number for rudder angle
 
 
#priority rtcc,ext
 
 
//---------------------------------------------------------------------------
 
//   Global variables for Timer 2
 
//
 
signed long   sl2msTimer = 0;            // incremented every 2 ms
 
short int   bCompassAlarm = 0;         // Will give short beep in alarm if compass not received in 1 second
 
short int   bBlinkDisplay = 0;         // Will blink the LCD backlight
 
short int   bFilterAlarm = 0;         // Indicates that filter alarm has been given
 
short int   bToggleLight = 0;         // Used to Blink LCD backlight
 
char      cError = 0;               // Indicates which error to display
 
                              // 0 = OK, 1 = Compass Error, 2 = Late Button, 3 = Filter Change
 
//---------------------------------------------------------------------------
 
//   Global variables for PID - regulator
 
//
 
signed long slHeading = 0;
 
signed long slOldHeading;
 
#ifdef FILTER_COMPASS
 
   signed long slNewHeading;
 
   signed long slDiff;
 
#endif
 
signed long slHeadingError;
 
signed long slHeadingIntegral = 0;
 
signed long slSetPoint = 0;
 
signed long slROT;
 
signed long slROTFilt;
 
char      cCount10;
 
 
 
//signed scSetDelayPort;               // delay to allow valve time to open
 
//signed scSetDelayStbd;               // delay to allow valve time to open
 
char cStartDelayPort;            // delay to allow valve time to open
 
char cStartDelayStbd;            // delay to allow valve time to open
 
short int   bLastMoveStbd;         // Indicates last move was to starboard
 
signed long slRud = 0;            // Temporary variable
 
signed long slRudder = 0;         // Actual Rudder Angle
 
signed long slCalcRudder = 0;      // Calculated partly from the ROT
 
signed long slOldRudder = 0;      // Actual rudder angle last second
 
signed long slSetRudder = 0;      // Rudder setpoint
 
signed long slRudderMove = 0;
 
signed long   slRudFromROTCalc;
 
float      fRudder;
 
short int   bAutopilot = 0;
 
short int   bAlarmEnable = 1;
 
 
#define pPumpPort PIN_A2   // RA2 (Pin4)  Port Pump Solenoid Ouput Pin
 
#define pPumpStbd PIN_C3   // RC0 (Pin11) Stbd Pump Solenoid Ouput Pin
 
 
#define pAlarm     PIN_C4   // RC1 (Pin12) Alarm Output Pin
 
#define pViper     PIN_C5   // Viper control Output
 
#define pFilter   PIN_A4   // Filter Change Input
 
 
char cViperPeriod = 0;      // Time between viper runs, 0=stop,1=continious
 
char cViperCnt;            // Count seconds since last viper run
 
short int bViperOnce = 0;   // To run viper by pressing D. Keystone
 
 
char  cIdx = 0;
 
float fVar[8];
 
const char c1[8] = { 'K','K','K','S','K','x','x','S' };
 
const char c2[8] = { 'p','i','d','p','2','x','x','c' };
 
//---------------------------------------------------------------------------
 
//   Global variables for serial receive
 
//
 
#define BUFFER_SIZE 8      // 6 is enough, but...
 
char cBuffer[BUFFER_SIZE];
 
char cNextIn = 0;
 
 
char   cNMEAMode = 0;
 
char   cCheckSum;
 
char   cACheckSum;
 
short   bCompassReceived = 0;         // Indicates compass message received
 
short   bFirstCompass = 0;            // Indicate first compass telegram received
 
char   cCompassReceive = 0;         // Cycle counter for compass receive (to indicate that messages are received)
 
//---------------------------------------------------------------------------
 
//   Global variables for remote control receive
 
//
 
#ifdef DIAG
 
   char c1_1  = 0;
 
   char c1_65 = 0;
 
   char c2_2  = 0;
 
   char cX_X  = 0;
 
#endif
 
 
char cSameCnt = 0;               // Same command received X times
 
char cIdleCnt = 0;               // No command received for X * 3.28ms
 
 
char cStartType;   //= 0;         // Indicate length of the header pulse 1 = 1.1us, 2 = 2.2us
 
char cStatus;      //= 0;         // 0 is OK
 
char cBitCnt;       //= 0;         // Number of bits received after Start pulse
 
char cBits[3];      //= { 1,2,3 };   // Remote control command input ends up here
 
 
char cLast1StartType;   // = 0;
 
char cLast1Status;      // = 0;
 
char cLast1BitCnt;      // = 0;
 
char cLast1Bits[3];
 
 
#ifdef DIAG
 
   char cLast2StartType = 0;
 
   char cLast2Status    = 0;
 
   char cLast2BitCnt    = 0;
 
   char cLast2Bits[3];
 
#endif
 
 
char   cPulseState = 0;         // State for remote control state machine
 
 
short   bBit;                  // used to hold data bit
 
short   bCmdReceived = 0;         // Indicates command to be executed
 
char   cCmdBits;               // only 8 of the bits in a command is used
 
char   cCmdBitCnt;               // number of bits in received remote control command
 
 
char j;
 
#ifdef DIAG
 
   char k;
 
#endif
 
//---------------------------------------------------------------------------
 
//   Variables for Menu system
 
//
 
char   cMenuMode = 0;            // 0 = Main menu, 1 = Config menu, 2 = diagnostic menu
 
char   cLight = 15;            // LCD backlight intensity
 
//---------------------------------------------------------------------------
 
//   Interrupt for serial input for telegram: $HCHDT,XXX.X,T*CC<CR>,<LF>  
 
//---------------------------------------------------------------------------
 
#INT_RDA
 
void serial_isr() {
 
   int t;
 
 
   t=getc();
 
//   if (cNMEAMode < 5) cCheckSum ^= t;
 
 
 
   switch (cNMEAMode) {
 
   case 0:            // wait for $
 
      if (t == '$') {
 
         cNMEAMode = 1;
 
         cNextIn = 0;
 
         cCheckSum = 0;
 
      }
 
      break;
 
 
   case 1:            // skip heading, wait for ','
 
      cCheckSum ^= t;
 
      if (t == ',') cNMEAMode = 2;      // input number next
 
      break;
 
 
   case 2:            // input integer and decimal part, skip '.', wait for ','
 
      cCheckSum ^= t;
 
      if (t == ',') {
 
         cBuffer[cNextIn] = 0;   // terminate string
 
         cNMEAMode = 3;      // finnished with number
 
      } else {
 
         if (t != '.') {
 
            cBuffer[cNextIn] = t;
 
            cNextIn += 1;
 
         }
 
      }
 
      break;
 
 
   case 3:            // wait for '*'
 
      if (t == '*') {
 
         cNMEAMode = 4;      // Go get checksum
 
      } else {
 
         cCheckSum ^= t;      // * is not included in checksum
 
      }
 
      break;
 
 
   case 4:            // get first checksum byte 
 
      if (t > '9') cACheckSum = t - 55;
 
            else cACheckSum = t - 48;
 
      swap(cACheckSum);
 
      cNMEAMode = 5;
 
      break;
 
 
   case 5:            // get second checksum byte 
 
      if (t > '9') t = t - 55;
 
            else t = t - 48;
 
      cACheckSum = cACheckSum | t;
 
      if (cAChecksum == cChecksum) {
 
         bCompassReceived = 1;         // Signal Main Loop that compass has been received
 
         sl2msTimer = 0;               // Clear compass timeout for timer 2
 
      }
 
      cNMEAMode = 0;
 
      break;
 
   }
 
}
 
//---------------------------------------------------------------------------
 
//   Timer 2 interrupt every 4ms (3.4ms on the scope)
 
//   This interrupt is used to time the pulses to the hydraulic valve solenoids
 
//   Execution app. 10 us.
 
//---------------------------------------------------------------------------
 
#INT_TIMER2
 
TIMER2_Interrupt()
 
{
 
   signed long slTmp;
 
 
   slTmp = slSetRudder - slRudder;
 
   if (slTmp) {                        // do we have to move the rudder
 
      if (slTmp > 0) {                  // Move rudder starboard ?
 
         bLastMoveStbd = 1;               // Indicate last move was to stbd
 
         if (cStartDelayStbd) {
 
            cStartDelayStbd = cStartDelayStbd - 1;   // Allow valve time to open
 
         } else {
 
            slRudder = slRudder + 1;      // Increment Actual Rudder Angle
 
         }
 
         OUTPUT_LOW(pPumpPort);            // Make shure both bits not set
 
         OUTPUT_HIGH(pPumpStbd);            // Set Stbd Output Pin High
 
      } else {                        // Move rudder port
 
         bLastMoveStbd = 0;               // Indicate last move was to port
 
         if (cStartDelayPort) {
 
            cStartDelayPort = cStartDelayPort - 1;   // Allow valve time to open
 
         } else {
 
            slRudder = slRudder - 1;      // Decrement Actual Rudder Angle
 
         }
 
         OUTPUT_LOW(pPumpStbd);            // Make shure both bits not set
 
         OUTPUT_HIGH(pPumpPort);            // Set Port Output Pin High
 
      }
 
   } else {   // if (slTmp) {               // The rudder position is correct
 
      OUTPUT_LOW(pPumpPort);               // Clear both Hydraulic Pump bits
 
      OUTPUT_LOW(pPumpStbd);               // Don't care which one was set
 
      if (bLastMoveStbd) {
 
         cStartDelayStbd = 18;      // scSetDelayStbd;   // Normal start delay for continued Stbd movement
 
         cStartDelayPort = 17 + 3;   // scSetDelayPort;   // Increase start delay to let walve move the extra distance
 
      } else {
 
         cStartDelayStbd = 18 + 3;   // scSetDelayStbd;   // Increase start delay to let walve move the extra distance
 
         cStartDelayPort = 17;      // scSetDelayPort;   // Normal start delay for continued port movement
 
      }
 
   }
 
 
   sl2msTimer = sl2msTimer + 1;      // sl2msTimer incremented every interrupt
 
   if (sl2msTimer > 740) {
 
      bCompassAlarm = 1;
 
      sl2msTimer = 0;
 
   }
 
}
 
//---------------------------------------------------------------------------
 
//   Interrupt when Timer 0 overflows from 0xFF to 0x00
 
//   Timer 0 interrupt is together with B0 interrupt used to decode the input
 
//   from the remote control.  Pulse lengths is timed with Timer 0, and the end of
 
//   a command is detected by Timer 0 overflowing.
 
//
 
//   if the cPulseState is non-zero this interrupt comes 3.28ms after the last
 
//   pulse on B0.  This is the normal way the end of a command from the remote
 
//   control is detected.
 
//
 
//   If cPulseState is zero, that indicates that we are not receiving any commands.
 
//   This will happen every 3.28ms when no commands are received.
 
//
 
//---------------------------------------------------------------------------
 
#INT_RTCC
 
RTCC_Interrupt()
 
{
 
   disable_interrupts(INT_EXT);            // Disable PIN B0 interrupt
 
   ext_int_edge(H_TO_L);                  // Set to look for start of next Start Pulse
 
 
   if (cPulseState) {                     // are we receiving a command that timed out
 
      #ifdef DIAG
 
         cLast2StartType = cLast1StartType;
 
         cLast2BitCnt  = cLast1BitCnt;
 
         cLast2Status  = cLast1Status;
 
         cLast2Bits[0] = cLast1Bits[0];
 
         cLast2Bits[1] = cLast1Bits[1];
 
         cLast2Bits[2] = cLast1Bits[2];
 
      #endif
 
 
      // Shift command bits right so they go from bit 0 to cBitCnt-1
 
 
      j = cBitCnt;
 
      while (j < 24) {                  // Get Bits to the Right
 
         SHIFT_RIGHT(cBits, 3, 0);
 
         j++;
 
      }
 
 
      // Check if the same command is received again.  The remote control will
 
      // send commands as long as the button is pressed.
 
 
      if ((cStartType==cLast1StartType) && (cBitCnt==cLast1BitCnt) &&
 
         (cStatus==0) && (cBits[0]==cLast1Bits[0]))
 
      {                              // Same command
 
         cSameCnt++;                     // Count it
 
         if (cSameCnt==2) {
 
            bCmdReceived = 1;            // Set Command received bit if 2 equal cmds received
 
            cCmdBits   = cLast1Bits[0];
 
            cCmdBitCnt = cLast1BitCnt;
 
         } else {
 
//            if (cSameCnt>20) {            // Button held for 0.7sec.   (a bit slow
 
//            if (cSameCnt>16) {            // Button held for 0.7sec.
 
            if (cSameCnt>12) {            // Button held for 0.7sec.
 
//               cSameCnt = 17;            // Repeat command in a short time
 
//               cSameCnt = 13;            // Repeat command in a short time
 
               cSameCnt = 9;            // Repeat command in a short time
 
               bCmdReceived = 1;         // Set Command received bit to repeat command
 
            }
 
         }
 
 
      } else {                        // New command or bad status
 
         cSameCnt = 0;
 
         cLast1StartType = cStartType;      // Transfer bits,cnt,type,status to "Last1"
 
         cLast1BitCnt  = cBitCnt;
 
         cLast1Status  = cStatus;
 
         cLast1Bits[0] = cBits[0];
 
         cLast1Bits[1] = cBits[1];
 
         cLast1Bits[2] = cBits[2];
 
      }
 
 
      cStartType = 0;   // Undefined pulse yet.
 
      cBitCnt  = 0;
 
      cStatus  = 0;
 
      cBits[0] = 0;
 
      cBits[1] = 0;
 
      cBits[2] = 0;
 
 
      cPulseState = 0;         // Set state to wait for start of new start pulse
 
      #ifdef DIAG
 
         k++;
 
      #endif
 
   } else { // if (cPulseState) {            // are we receiving a command that timed out, No this is idle
 
      if (cIdleCnt < 250) cIdleCnt++;         // don't overflow idle count
 
      if (cIdleCnt == 10) {               // if idle for 30ms, reset last to enable new command
 
         cSameCnt = 0;
 
         cLast1StartType = 0;            // Set Last 
 
         cLast1BitCnt  = 0;
 
         cLast1Status  = 0;
 
         cLast1Bits[0] = 0;
 
//         cLast1Bits[1] = 0;
 
//         cLast1Bits[2] = 0;
 
      }
 
   } //    if (cPulseState) {
 
 
   enable_interrupts(INT_EXT);               // Enable PIN B0 interrupt
 
}
 
//---------------------------------------------------------------------------
 
//   Interrupt when PIN B0 changes state
 
//
 
//   This interrupt decodes the remote control input on B0.
 
//   Note that the timing of the start pulse is from the leading H to L
 
//   transition to the L to H one.
 
//   The normal data pulses is timed from the L to H to the L to H on the next pulse.
 
//
 
//                                                      
 
//                  Start Pulse                        
 
//---------------                         -------          --------          ----.....
 
//               |       2.2us           |       | 0.55us |        | 0.55   |
 
//               |        or             |0.55us |  or    | 0.55us |  or    |
 
//               |       1.1us           |       | 1.1us  |        | 1.1us  |
 
//              ------------------------         ---..---          ---..---
 
//
 
//B0 Interrupt   1                       2                3                 4 
 
//
 
//
 
//   The length of the start pulse is indicated by the cStartType variable.
 
//   Some commands from SONY remotes have a start pulse of 1.1 us which set sStartType = 1.
 
//   All the commands used in this program has a start pulse of 2.2us and a starttype of 2.
 
//   The remote control that I use is for a SONY projector, and the type number on it is RM-PJ2.
 
//   I also have another one, the RM-PJM10 with the same buttons giving the same codes.
 
//
 
//   Button         Bit Pattern      Bits   Start Type
 
//   On/Off         002A15            15       2
 
//   Volume +       002A12            15       2
 
//   Volume -       002A13            15       2
 
//   Muting/Pic     002A24            15       2
 
//   INPUT          002A57            15       2
 
//   MENU           002A29            15       2
 
//   ENTER          002A5A            15       2
 
//   RESET          002A7B            15       2
 
//   Right Arrow    002A33            15       2
 
//   Left Arrow     002A34            15       2
 
//   Up Arrow       002A35            15       2
 
//   Down Arrow     002A36            15       2
 
//
 
//   D Zoom +       02AD6A            20       2
 
//   D Zoom -       02AD6B            20       2
 
//   FREEZE         02AD67            20       2
 
//   APA            02AD60            20       2
 
//   D Keystone     02AD3A            20       2
 
//   Function 1     02AD68            20       2
 
//   Function 2     02AD69            20       2
 
//---------------------------------------------------------------------------
 
#INT_EXT
 
PIN_B0_Interrupt()
 
{
 
  char cTim;
 
 
   cTim = get_rtcc();
 
   set_rtcc(0);               // Clear timer to check next pulse length
 
                           // Clear RTCC interrupt bit also ? Not Necessary
 
   switch (cPulseState) {
 
 
   case 0:                     // waiting for Start of Start Pulse
 
      ext_int_edge(L_TO_H);      // Set PIN B0 interrupt from Low to High for end of Start Pulse
 
      cIdleCnt    = 0;         // Reset Idle time indicator
 
      cPulseState = 1;         // Set state to wait for end of start pulse
 
      break;
 
 
   case 1:                                       // waiting for End of Start Pulse
 
      if ((cTim < T1_1-MErr) || (cTim > T2_2+Merr)) {      // Pulse was too short (<1.1) or too long (>2.2ms)
 
         #ifdef DIAG
 
            if (cTim < (T1_1-MErr)) cStatus = 1;
 
               else            cStatus = 3;
 
            cX_X    = cTim;                        // Save for diagnostic
 
         #else
 
            cStatus = 1;
 
         #endif
 
         cPulseState = 10;                        // Wait for Inter-code break
 
      } else {                                 // Pulse might be OK
 
         if ((cTim < T2_2-Merr) && (cTim > T1_1+Merr)) {   // 
 
            cStatus   = 2;   
 
            #ifdef DIAG
 
               cX_X    = cTim;                     // Save for diagnostic
 
            #endif
 
            cPulseState = 10;                     // Wait for Inter-code break
 
         } else {                              // Pulse is OK
 
            if (cTim > T1_1+Merr) {                  // Pulse 2.2ms
 
               #ifdef DIAG
 
                  c2_2 = cTim;                  // Save for diagnostic
 
               #endif
 
               cStartType  = 2;                  // 2.2ms
 
               cPulseState = 2;                  // Look for Normal data bits
 
            } else {            
 
               #ifdef DIAG
 
                  c1_1 = cTim;                  // Save for diagnostic
 
               #endif
 
               cStartType  = 1;                  // 1.1ms
 
               cPulseState = 10;                  // Wait for Inter-code break
 
            }
 
         }
 
      }
 
       break;
 
 
   case 2:                                       // Receiving Normal Data Bits  ( 1.1ms or 1.65ms)
 
      if ((cTim < T1_1-MErr) || (cTim > T1_65+Merr)) {   // Pulse was too long (>1.65) or too short (<1.1ms)
 
         #ifdef DIAG
 
            if (cTim < (T1_1-MErr)) cStatus = 4;
 
               else            cStatus = 6;
 
            cX_X    = cTim;                        // Save for diagnostic
 
         #else
 
            cStatus = 4;
 
         #endif
 
         cPulseState = 10;                        // Wait for Inter-code break
 
      } else {
 
         if ((cTim < T1_65-Merr) && (cTim > T1_1+Merr)) {   
 
            #ifdef DIAG
 
               cX_X = cTim;                     // Save for diagnostic
 
            #endif
 
            cStatus = 5;
 
            cPulseState = 10;                     // Wait for Inter-code break
 
         } else {                              // Pulse is OK
 
            if (cTim > T1_1+Merr) {
 
               #ifdef DIAG
 
                  c1_65 = cTim;                  // Save for diagnostic
 
               #endif
 
               bBit  = 1;                        // 1.65ms
 
            } else {
 
               #ifdef DIAG
 
                  c1_1 = cTim;                  // Save for diagnostic
 
               #endif
 
               bBit = 0;                        // 1.1ms
 
            }
 
            SHIFT_RIGHT(cBits, 3, bBit);
 
            cBitCnt += 1;
 
         }
 
      }
 
      break;
 
 
   case 10:         // Bit Error.  This will just eat bits until end of command
 
      break;
 
  }
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void norm3600(signed long °)
 
{
 
   if (deg >= 3600) deg = deg - 3600;
 
   if (deg <     0) deg = deg + 3600;
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void norm1800(signed long °)
 
{
 
   if (deg >  1800) deg = deg - 3600;
 
   if (deg < -1799) deg = deg + 3600;
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void resetall()
 
{
 
   slHeadingIntegral = 0;
 
   disable_interrupts(INT_TIMER2);            // avoid conflict with interrupt routine
 
   slSetRudder = 0;                     // New Rudder Setpoint
 
   slRudder = 0;
 
   enable_interrupts(INT_TIMER2);
 
   slCalcRudder = 0;
 
}
 
//---------------------------------------------------------------------------
 
#ifdef DPRINT6
 
#SEPARATE
 
void print6(signed long l)
 
{
 
   float f;
 
 
   if (l < 0) {
 
      lcd_putc('-');
 
      l = - l;
 
   } else {
 
      lcd_putc(' ');
 
   }
 
 
   f = (l * 0.1) + 0.04;
 
 
   if (f > 99.95) {
 
      printf(lcd_putc,"%5.1f", f);
 
   } else {
 
      if (f >  9.95) {
 
         printf(lcd_putc," %4.1f", f);
 
      } else {
 
         printf(lcd_putc,"  %3.1f", f);
 
      }
 
   }
 
}
 
#endif
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void setfromeeprom()
 
{
 
   //   Rudder Angle(Steps) = ROT * (MaxSpeed/Speed) * 0.55 * 10   // for range +/- 375 for rudder angle
 
   //   Rudder Angle(Steps) = ROT * (MaxSpeed/Speed) * 0.55 * 20   // for range +/- 750 for rudder angle
 
   //                       Speed
 
   slRudFromROTCalc =  (MAXSPEED/fVar[7]) * 11;   // Calculate factor for Rudder from ROT Calculation (MAXRUDDER = 750)
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void disableautopilot()
 
{
 
   bAutopilot = 0;
 
   disable_interrupts(INT_TIMER2);         // avoid conflict with interrupt routine
 
   slSetRudder = slRudder;
 
   OUTPUT_LOW(pPumpPort);               // Clear both Hydraulic Pump bits
 
   OUTPUT_LOW(pPumpStbd);               // Don't care which one was set
 
   enable_interrupts(INT_TIMER2);
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void setup()
 
{
 
   short int btmp;
 
   unsigned int i;
 
   unsigned int Line,Col;
 
 
   OUTPUT_LOW(pAlarm);                  // Reset Alarm Output Pin
 
   OUTPUT_LOW(pViper);                  // Reset Viper Output Pin
 
   OUTPUT_LOW(pPumpPort);               // Clear both Hydraulic Pump bits
 
   OUTPUT_LOW(pPumpStbd);               // Don't care which one was set
 
 
   for (i=0; i<32; i++) {                     // Read from EEPROM
 
      *(((char*)&fVar[0])+i) = READ_EEPROM(i);
 
   }
 
 
   setfromeeprom();
 
 
   lcd_init();
 
 
   SETUP_ADC_PORTS(RA0_RA1_RA3_ANALOG);         // 0x84 RA0,RA1 and RA3 Analog Inputs. Ref=Vdd.
 
/*
 
   SET_TRIS_A(0b11111011);                     // PORT A Tristate Setup
 
                                       // 1 RA0 (PIN2) Analog Input AN0
 
                                       // 1 RA1 (PIN3) Analog Input AN1
 
                                       // 0 RA2 (PIN4) Output Pump Port
 
                                       // 1 RA3 (Pin5) Analog Input AN3
 
                                       // 1 RA4 (Pin6) Input Filter Change Open Drain
 
                                       // 1 RA5 (Pin7) SS Slave Select
 
 
   SET_TRIS_C(0b11111111);                     // PORT C Tristate Setup This is OK
 
 
   SET_TRIS_C(0b00011111);                     // PORT C Tristate Setup
 
                                       // 0 RC0 (Pin11) Output Pump Stbd
 
                                       // 0 RC1 (Pin12) Output Alarm (CCP2)
 
                                       // 0 RC2 (Pin13) Output Light (CCP1)
 
                                       // 1 RC3 (Pin14) SCK/SCL
 
                                       // 1 RC4 (Pin15) 
 
                                       // 1 RC5 (Pin16) 
 
                                       // 1 RC6 (Pin17) USART TX
 
                                       // 1 RC7 (Pin18) USART RX
 
*/
 
   setup_counters(RTCC_INTERNAL, RTCC_DIV_64);      // Set up timer 0 as timer ticking every 12.8us
 
   enable_interrupts(INT_RTCC);               // enable timer 0 interrupt
 
 
   ext_int_edge(H_TO_L);                     // Set PIN B0 interrupt from High to Low
 
   enable_interrupts(INT_EXT);                  // Enable PIN B0 interrupt
 
 
   enable_interrupts(int_rda);                  // Serial Receive data Interrupt
 
 
                                       // Timer 2 used for PWM CCP1  freq = 1.25kHz (2.5kHz)
 
   SETUP_TIMER_2(T2_DIV_BY_16, 250, 2);         // Interrupt every 4ms. (4)
 
   enable_interrupts(INT_TIMER2);               // Enable Timer 2 interrupt
 
   SETUP_CCP1(CCP_PWM);
 
   SET_PWM1_DUTY(cLight);                     // Duty cycle to 50%
 
 
   enable_interrupts(GLOBAL);                  // Enable all enabled interrupts
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void ChangeRudder(signed long slR)
 
{
 
   signed long slTmp;
 
 
   disable_interrupts(INT_TIMER2);            // avoid conflict with interrupt routine
 
   slTmp = slSetRudder;
 
   enable_interrupts(INT_TIMER2);
 
 
   slTmp = slTmp + slR;
 
   if (slTmp > MAXRUDDER) slTmp = MAXRUDDER;
 
   if (slTmp < -MAXRUDDER) slTmp = -MAXRUDDER;
 
   disable_interrupts(INT_TIMER2);            // avoid conflict with interrupt routine
 
   slSetRudder = slTmp;                  // New Rudder Setpoint
 
   enable_interrupts(INT_TIMER2);
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void SetRudder(signed long slR)
 
{
 
   if (slR > MAXRUDDER) slR = MAXRUDDER;
 
   if (slR < -MAXRUDDER) slR = -MAXRUDDER;
 
   disable_interrupts(INT_TIMER2);            // avoid conflict with interrupt routine
 
   slSetRudder = slR;                     // New Rudder Setpoint
 
   enable_interrupts(INT_TIMER2);
 
}
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void HandleCmd()
 
{
 
   unsigned int i;
 
   signed long j;
 
 
   switch (cMenuMode) {
 
   //-------------------------------------------------------------
 
   case 0:                        // Main Menu
 
   case 2:                        // Diag Menu
 
      switch (cCmdBits) {
 
 
      case 0x15:                  // On/Off Button
 
         disableAutopilot();
 
         break;
 
 
      case 0x29:                  // menu button
 
         if (cMenuMode) {         // Must be 2
 
            cMenuMode = 0;
 
         } else {               // is 0
 
            cMenuMode = 1;
 
         }
 
         lcd_putc('\f');
 
         break;
 
 
      case 0x35:                  // "^" button
 
         if (bAutoPilot) {
 
            slSetPoint += 10;
 
         } else {
 
            ChangeRudder(10);
 
         }
 
         break;
 
      case 0x36:                  // "v" button
 
         if (bAutoPilot) {
 
            slSetPoint -= 10;
 
         } else {
 
            ChangeRudder(-10);
 
         }
 
         break;
 
      case 0x33:                  // "->" button
 
         if (bAutoPilot) {
 
            slSetPoint += 50;
 
            slHeadingIntegral = 0;
 
         } else {
 
            ChangeRudder(30);
 
         }
 
         break;
 
      case 0x34:                  // "<-" button
 
         if (bAutoPilot) {
 
            slSetPoint -= 50;
 
            slHeadingIntegral = 0;
 
         } else {
 
            ChangeRudder(-30);
 
         }
 
         break;
 
      case 0x5A:                  // "Enter" button
 
         slSetPoint = ((slHeading+5) / 10) * 10;
 
         resetall();
 
         bAutopilot = 1;
 
         break;
 
      case 0x57:                  // "Input" Increase Viper Period
 
         if (cViperPeriod < 15) {
 
            cViperPeriod++;
 
         } else {
 
            cViperPeriod = 0;
 
         }
 
         cViperCnt = 1;            // Make Viper start at once
 
         break;
 
      case 0x60:                  // "APA"   Decrease Viper Period
 
         if (cViperPeriod >  0) {
 
            cViperPeriod--;
 
         } else {
 
            cViperPeriod = 15;
 
         }
 
         cViperCnt = 1;            // Make Viper start at once
 
         break;
 
      case 0x3A:                  // "D. Keystone" Run Viper Once
 
         bViperOnce = 1;
 
         OUTPUT_HIGH(pViper);                  // Run Viper 1 second
 
         break;
 
      case 0x67:                  // "Freeze" Enable/Disable Alarm
 
         if (bAlarmEnable) {
 
            bAlarmEnable = 0;
 
         } else {
 
            bAlarmEnable = 1;
 
         }
 
         break;
 
      case 0x6A:                  // "+" button
 
         cLight = (cLight+1)*2 - 1;   // 0 1 3 7 15 31 63 127 255  // (255+1)*2 - 1 = 255 !
 
         SET_PWM1_DUTY(cLight);      // Set Duty cycle
 
         break;
 
      case 0x6B:                  // "-" button
 
         cLight = (cLight)/2;      // 255 127 63 31 15 7 3 1 0  // 0/2 = 0
 
         SET_PWM1_DUTY(cLight);      // Set Duty cycle
 
         break;
 
      case 0x7B:                  // Reset button
 
         resetall();
 
         break;
 
      default:
 
         disableAutopilot();
 
         break;
 
      } // switch (cCmdBits)
 
      norm3600(slSetPoint);
 
 
      break;
 
      //-------------------------------------------------------------
 
   case 1:                  // Configuration menu to set eeprom values
 
      switch (cCmdBits) {
 
      case 0x29:            // menu button
 
         cMenuMode = 2;
 
         lcd_putc('\f');
 
         break;
 
      case 0x67:            // "Freeze" button used as "SAVE"
 
         for (i=0; i<32; i++) {
 
            WRITE_EEPROM(i, *(((char*)&fVar[0])+i));
 
         }
 
         break;
 
      case 0x57:            // "Input" button used as "Restore from EEPROM"
 
         for (i=0; i<32; i++) {
 
            *(((char*)&fVar[0])+i) = READ_EEPROM(i);
 
         }
 
         break;
 
      case 0x5A:            // "Enter" button
 
         fVar[cIdx] = 0.0;
 
         break;
 
      case 0x7B:            // "Reset" button sets all to Zero
 
         for (i=0; i<8; i++) {
 
            fVar[i] = 0.0;
 
         }
 
         break;
 
      case 0x6A:            // "+" button advance 0 1 2 3 4 5 6 7
 
         if (cIdx<7) cIdx += 1;
 
         break;
 
      case 0x6B:            // "-" button decrease 7 6 5 4 3 2 1 0
 
         if (cIdx>0) cIdx -= 1;
 
         break;
 
      case 0x33:            // "->" button
 
         fVar[cIdx] = fVar[cIdx] + 0.01;
 
         break;
 
      case 0x34:            // "<-" button
 
         fVar[cIdx] = fVar[cIdx] - 0.01;
 
         break;
 
      case 0x35:            // "up" button
 
         fVar[cIdx] = fVar[cIdx] + 1.0;
 
         break;
 
      case 0x36:            // "down" button
 
         fVar[cIdx] = fVar[cIdx] - 1.0;
 
         break;
 
      default:
 
         disableAutopilot();
 
         break;
 
      } // switch (cCmdBits)
 
      setfromeeprom();
 
      break;
 
      //-------------------------------------------------------------
 
   } // switch (cMenuMode)
 
}
 
//---------------------------------------------------------------------------
 
/*
 
#SEPARATE
 
void ()
 
{
 
}
 
*/
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void PID()
 
{
 
   signed long slHdgAbove3;
 
   signed long slHdgUnder3;
 
 
   if (!bAutoPilot) {
 
      slSetPoint = slHeading;
 
   } // if (!bAutoPilot) {
 
 
   slHeadingError = slHeading - slSetPoint;               // P
 
   norm1800(slHeadingError);
 
 
   slHeadingIntegral = slHeadingIntegral + slHeadingError;      // I
 
 
   slROT = slHeading - slOldHeading;                     // D
 
   norm1800(slROT);
 
 
   if (slHeadingError < 0) {
 
      if (slHeadingError < -30) {
 
         slHdgAbove3 = slHeadingError + 30;
 
         slHdgUnder3 = -30;
 
      } else {
 
         slHdgAbove3 = 0;
 
         slHdgUnder3 = slHeadingError;
 
      }
 
   } else {
 
      if (slHeadingError > 30) {
 
         slHdgAbove3 = slHeadingError - 30;
 
         slHdgUnder3 = 30;
 
      } else {
 
         slHdgAbove3 = 0;
 
         slHdgUnder3 = slHeadingError;
 
      }
 
   }
 
   
 
   fRudder   = -( fVar[0] * slHdgUnder3 * labs(slHdgUnder3) + fVar[4] * slHdgAbove3 ) / fVar[3];   // (P^2 + P)/ Speed
 
 
   fRudder   = fRudder - fVar[2] * slROT;                  // D
 
 
   slOldHeading = slHeading;
 
}
 
 
//---------------------------------------------------------------------------
 
#SEPARATE
 
void CalcRudder()
 
{
 
   short int btmp;
 
   unsigned int i;
 
 
   // The hydraulic system have small leaks in the manual steering pumps which
 
   // give a drifting rudder.  If this is not compensated in some way, the rudder angle
 
   // will grow continuously and give misleading values, and finally overflow.
 
   // The following calculation "filters in" a value for the rudder calculated
 
   // from the Rate of Turn.  The formula for ROT was found by a turning
 
   // test with the boat.  The ROT is fairly proportional with the Rudder Angle within
 
   // at least +/- 20 degrees.  The ROT is also approximately proportional with the boat speed
 
   // For my boat the boat speed used with autopilot varies between 6 and 12 knots.
 
   //
 
   // All this to avoid making a sensor for the rudder angle!
 
   // This will of course also defeat the integral part of the PID regulator!
 
   // I'm planning to use the Cross Track Error and True Course output from
 
   // the GPS to correct for that.  In the meantime it is nice to have a boat
 
   // which will keep its heading relatively constant, even if you have to correct
 
   // manually for wind and current.
 
   //
 
   // Note that all rudder angles are in 1.7 millisecond movements.  The pump will move the
 
   // rudder from fully port to fully starboard in about 3 sec.
 
   // Total rudder angle is +/- 750, which is approximately +/-35 degrees
 
   // For simplicity I divide by 2 (20) for +/- 37.5 as rudder angle for display
 
   //
 
   // Example from ROT test. (steps of 3.3deg rudder equals one spoke on the steering wheel).
 
   //      1500 RPM  5.8kts:               3200 RPM  12kts:
 
   //      Rudder         ROT               Rudder         ROT      
 
   //      3.3deg. stbd   3deg./s             -             -
 
   //      3.3deg. port   3deg./s            3.3deg. port    6deg./s
 
   //      6.6deg. port   6deg./s            6.6deg. port   12deg./s
 
   //
 
   //   Rudder Angle(Steps) = ROT * (MaxSpeed/Speed) * 0.55 * 20   // for range +/- 750 for rudder angle
 
 
   disable_interrupts(INT_TIMER2);               // avoid conflict with interrupt routine
 
   slRud = slRudder;                        // get actual rudder angle
 
   enable_interrupts(INT_TIMER2);
 
 
   slCalcRudder = slRudFromROTCalc * slROT;      // use precalculated slRudFromROTCalc which include division by speed!
 
   slCalcRudder = (slCalcRudder + 15*slRud) / 16;   // "filter in" the calculated rudder
 
 
   slRud = fRudder;                        // slRud is now used to make int of new rudder setting from PID
 
   if (slRud >  300) {                        // Limit Rudder angle to +/-15 degrees.
 
      slRud =  300;
 
   } else if (slRud < -300) {
 
      slRud = -300;
 
   }
 
 
   if (bAutoPilot) {
 
      disable_interrupts(INT_TIMER2);            // avoid conflict with interrupt routine
 
      slSetRudder = slRud;                  // set rudder setpoint from PID
 
      slRudder = slCalcRudder;               // set the actual rudder angle from calculated
 
      enable_interrupts(INT_TIMER2);
 
   }
 
}
 
//---------------------------------------------------------------------------
 
//---------------------------------------------------------------------------
 
 
main() {
 
   short int btmp;
 
   unsigned int i;
 
   unsigned int Line,Col;
 
 
   setup();
 
 
   while (TRUE) {
 
      // 
 
      //  Now we must handle compass input (10 times per second)
 
      //
 
      if (bCompassReceived) {                  // Have we received an NMEA compass message
 
         bCompassReceived = 0;               // Clear the bit
 
         bFirstCompass = 1;                  // We have received one compass telegram
 
 
         #ifdef FILTER_COMPASS
 
            slNewHeading = atol((char*)&cBuffer[0]);
 
            slDiff = slNewHeading - slHeading;         // Handle change through 0,360
 
            if (slDiff > 1800) {
 
               slNewHeading = slNewHeading - 3600;
 
            } else if (slDiff <= -1800) {
 
               slNewHeading = slNewHeading + 3600;
 
            }
 
            slHeading = (slNewHeading + slHeading) / 2;   // A little filtering
 
         #else
 
            slHeading = atol((char*)&cBuffer[0]);      // No filtering
 
         #endif
 
 
         norm3600(slHeading);
 
 
         cCount10 = cCount10 + 1;
 
         if (cCount10 > 9) {            // Following code executed every second
 
            cCount10 = 0; 
 
 
            // Handle the Windscreen Viper
 
 
            if (bViperOnce) {                        // Viper Once pressed
 
//               OUTPUT_HIGH(pViper);                  // Run Viper 1 second
 
               bViperOnce = 0;                        // Reset request
 
            } else {                              // No, Normal Period Viper
 
               if (cViperPeriod == 0) {               // Stop Viper
 
                  OUTPUT_LOW(pViper);                  
 
//                  cViperCnt = cViperPeriod;            // Reset Timer
 
               } else if (cViperPeriod == 1) {            // Run Viper All the time
 
                  OUTPUT_HIGH(pViper);
 
//                  cViperCnt = cViperPeriod;            // Reset Timer
 
               } else {                           // Period from 2 - 20 sec.
 
                  if (--cViperCnt == 0) {               // Timer to zero
 
                     OUTPUT_HIGH(pViper);            // Run Viper 1 second
 
                     cViperCnt = cViperPeriod;         // Reset Timer
 
                  } else {
 
                     OUTPUT_LOW(pViper);               // Stop Viper
 
                  }
 
               } 
 
            } // if (bViperOnce) {
 
 
            if (bAutopilot && bAlarmEnable) {   // Is this program controlling the boat ?
 
               if (cCompassReceive < 250) cCompassReceive++;   // Increment seconds since last button pressed
 
               if (cCompassReceive > 110) {                  // More Than 75 seconds since last button press
 
                  OUTPUT_HIGH(pAlarm);                  // Set Alarm Output Pin High for continious alarm
 
               } else    if (cCompassReceive > 100) {            // More Than 70 seconds since last button press
 
                  OUTPUT_HIGH(pAlarm);                  // Set Alarm Output Pin High
 
                  SET_PWM1_DUTY(cLight);                  // Set Duty cycle back to set value
 
                  //delay_ms(1);                        // Alarm lasts 1 ms, still hardly bearable!
 
                  delay_us(250);                        // Alarm lasts 250us, now quite bearable!
 
                  OUTPUT_LOW(pAlarm);                     // Set Alarm Output Pin Low
 
               } else    if (cCompassReceive > 90) {            // More Than 60seconds since last button press
 
                  cError = 2;                           // Set Error message to display "Late Button Press!"
 
                  if (bToggleLight) {
 
                     bToggleLight = 0;
 
                     if (clight) {                     // Is the light on at all
 
                        SET_PWM1_DUTY(0);               // Set Duty cycle for black display
 
                     } else {                        // No Light on, probably dark outside
 
                        SET_PWM1_DUTY(2);               // Set Duty cycle for weak light
 
                     }
 
                  } else {
 
                     bToggleLight = 1;
 
                     SET_PWM1_DUTY(cLight);               // Set Duty cycle for selected light level
 
                  }
 
               }
 
            } // (bAutopilot && bAlarmEnable && bFirstCompass) {
 
            PID();
 
            CalcRudder();
 
         } // (cCount10 > 9) {
 
 
      } else { // if (bCompassReceived) {
 
 
         // The bFirstCompass is used to avoid the initial compass alarm we
 
         // get because the compass takes about 3 seconds to start
 
 
         if (bCompassAlarm && bFirstCompass) {
 
            OUTPUT_HIGH(pAlarm);            // Set Alarm Output Pin High
 
            bCompassAlarm = 0;               // Reset Error Bit
 
            delay_ms(3);                  // Alarm lasts 5 ms
 
            cError = 1;                     // Set Error message to display
 
            OUTPUT_LOW(pAlarm);               // Set Alarm Output Pin Low
 
         }
 
      } // if (bCompassReceived) {
 
 
      //  Now we must handle command input
 
 
      if (bCmdReceived) {
 
         bCmdReceived = 0;                  // Clear indicator bit
 
         OUTPUT_LOW(pAlarm);                  // Reset Alarm Output Pin
 
         cCompassReceive = 0;               // Reset the time since last command
 
         SET_PWM1_DUTY(cLight);               // Set Duty cycle back to set value
 
 
         if (cError == 0) {
 
            HandleCmd();
 
         } else {
 
            cError = 0;                        // Reset Error Message
 
         }
 
      } // if (bCmdReceived) {
 
      
 
      // Now check the filter Alarm input (if not alarm already)
 
 
      if (!bFilterAlarm) {                  // Filter alarm not issued yet?
 
         if (!INPUT(pFilter)) {               // Check if low
 
            bFilterAlarm = 1;               // Indicate filter alarm issued
 
            OUTPUT_HIGH(pAlarm);            // Set Alarm Output Pin High
 
            delay_ms(25);                  // Alarm lasts 25 ms
 
            cError = 3;                     // Set Error message to display "Filter Change!"
 
            OUTPUT_LOW(pAlarm);               // Set Alarm Output Pin Low
 
         }
 
      }
 
 
      switch (cMenuMode) {
 
      //----------------------------------------------------------------------------
 
      case 0:                           // Main Menu
 
 
         // Handle Line 1
 
 
         lcd_gotoxy(1,1);
 
         printf(lcd_putc,"HDG");
 
         #ifdef DPRINT6
 
            print6(slHeading);
 
         #else
 
            printf("%6ld",slHeading);
 
         #endif
 
 
         lcd_gotoxy(12,1);
 
         printf(lcd_putc,"ROT");
 
         #ifdef DPRINT6
 
            print6(slROT);
 
         #else
 
            printf("%6ld",slROT);
 
         #endif
 
 
         // Handle Line 2
 
 
         lcd_gotoxy(1,2);
 
         printf(lcd_putc,"SET");
 
         if (bAutopilot) {
 
            printf(lcd_putc,"%4ld", (slSetPoint/10));
 
         } else {
 
            printf(lcd_putc," ---");
 
         }
 
 
         lcd_gotoxy(12,2);
 
         printf(lcd_putc,"INT");
 
         #ifdef DPRINT6
 
            print6(slHeadingIntegral);
 
         #else
 
            printf("%6ld",slHeadingIntegral);
 
         #endif
 
      
 
/*
 
         lcd_gotoxy(12,2);
 
         printf(lcd_putc,"RRT");
 
         #ifdef DPRINT6
 
            print6(slRRT);
 
         #else
 
            printf("%6ld",slRRT);
 
         #endif
 
*/   
 
 
         // Handle Line 3
 
 
         switch (cError) {
 
         case (0):
 
            lcd_gotoxy(1,3);
 
            printf(lcd_putc,"ERR");
 
            #ifdef DPRINT6
 
               print6(slHeadingError);
 
//               printf("  ");
 
            #else
 
               printf("%6ld",slHeadingError);
 
            #endif
 
         
 
            lcd_gotoxy(10,3);
 
            printf(lcd_putc,"  Cal");
 
            #ifdef DPRINT6
 
               print6(slCalcRudder/2);
 
            #else
 
               printf("%6ld",slCalcRudder/2);
 
            #endif
 
            break;
 
 
         case (1):                  // Compass not received
 
            lcd_gotoxy(1,3);
 
            printf(lcd_putc,"Late Compass!       ");
 
            break;
 
 
         case (2):                  // Late Button Press
 
            lcd_gotoxy(1,3);
 
            printf(lcd_putc,"Late Button Press!  ");
 
            break;
 
 
         case (3):                  // Filter Change
 
            lcd_gotoxy(1,3);
 
            printf(lcd_putc,"Filter Change!      ");
 
            break;
 
         }
 
 
         // Handle Line 4
 
 
         lcd_gotoxy(1,4);
 
         printf(lcd_putc,"RUD");
 
         #ifdef DPRINT6
 
            print6(slSetRudder/2);
 
            print6(slRudder/2);
 
         #else
 
            printf("%6ld",slSetRudder/2);
 
            printf("%6ld",slRudder/2);
 
         #endif
 
      
 
         lcd_gotoxy(17,4);
 
         if (cViperPeriod != 0) {
 
            printf(lcd_putc,"%3U", cViperPeriod);      // Show Viper Period
 
         } else {
 
            printf(lcd_putc,"%3U", cCompassReceive);   // time since button press
 
         }
 
         if (bAlarmEnable) {
 
            lcd_putc('*');
 
         } else {
 
            lcd_putc(' ');
 
         }
 
         break;
 
      //----------------------------------------------------------------------------
 
      case 1:                           // Config menu
 
 
         for (i=0; i<8; i++) {
 
            Line = (i & 0x03) + 1;
 
            if (i < 4) {
 
               Col = 1;
 
            } else {
 
               Col = 11;
 
            }
 
            lcd_gotoxy(Col,Line);
 
            lcd_putc(c1[i]);
 
            lcd_putc(c2[i]);
 
            if (i==cIdx) {
 
               printf(lcd_putc,"*%5.2f*", fVar[i]);
 
            } else {
 
               printf(lcd_putc," %5.2f  ", fVar[i]);
 
            }
 
            } // of for (i=0....
 
            break;
 
 
      //----------------------------------------------------------------------------
 
      case 2:                           // Diagnostic menu
 
   
 
         #ifdef DIAG
 
            lcd_gotoxy(1,1);
 
      //      printf(lcd_putc,"%2X %3u %3u %3u %3u", k, c1_1, c1_65, c2_2, cX_X);
 
      //      printf(lcd_putc,"%2X %3u %3u %3u %3u", k, cPulseState, cSameCnt, cIdleCnt, cX_X);
 
      //      printf(lcd_putc,"%2X %3u %3u %3u %3u", cBuffer[0], cNextIn, cSameCnt, cIdleCnt, cX_X);
 
            printf(lcd_putc,"%2X %3u %3u %3u %3u", cBuffer[0], cNextIn, cSameCnt, cIdleCnt, cX_X);
 
      
 
            lcd_gotoxy(1,3);
 
            printf(lcd_putc,"SBT=%1u,%2u,%1u,%2X%2X%2X",
 
            cLast2Status, cLast2BitCnt, cLast2StartType, cLast2Bits[2], cLast2Bits[1], cLast2Bits[0]);
 
         #endif
 
      
 
         lcd_gotoxy(1,1);
 
         printf(lcd_putc,"HDG");
 
         #ifdef DPRINT6
 
            print6(slHeading);
 
         #else
 
            printf("%6ld",slHeading);
 
         #endif
 
         
 
            lcd_gotoxy(11,1);
 
         printf(lcd_putc,"ROT");
 
         print6(slROT);
 
      
 
         lcd_gotoxy(1,2);
 
      //   printf(lcd_putc,"SET");
 
      //   print6(slSetPoint);
 
         printf(lcd_putc,"SET%4ld", (slSetPoint/10));
 
 
         lcd_gotoxy(11,2);
 
         printf(lcd_putc,"RUD");
 
         print6((slRudder/2));
 
 
         lcd_gotoxy(11,3);
 
         printf(lcd_putc,"Cal");
 
         print6((slCalcRudder/2));
 
 
         lcd_gotoxy(1,3);
 
         i = 0;
 
         while (cBuffer[i] != 0) {
 
            printf(lcd_putc,"%C", cBuffer[i]);
 
            i++;
 
         }
 
         printf(lcd_putc, ">");
 
 
         lcd_gotoxy(1,4);
 
         printf(lcd_putc,"FromROT %4ld  ", slRudFromROTCalc);
 
//         printf(lcd_putc,"SQR + Linear P/Speed");
 
//         printf(lcd_putc,"%3D %3D",scSetDelayPort,scSetDelayStbd);
 
//         printf(lcd_putc,"SBT=%1u,%2u,%1u,%2X%2X%2X",
 
//         cLast1Status, cLast1BitCnt, cLast1StartType, cLast1Bits[2], cLast1Bits[1], cLast1Bits[0]);
 
         break;
 
      
 
      } // switch (cMenuMode)
 
   } // while (TRUE) 
 
}
 
 | 	 
  | 
			 
		  |