Version:0.9 StartHTML:0000000105 EndHTML:0000378697 StartFragment:0000001499 EndFragment:0000378681 mikroIDE
/*
 * Project name:
     Voodoo - PI/VLF Hybrid Metal Detector
 * Copyright:
     (c) George Overton, 2020.
 * Revision History:
     - V1.0 Initial release
 * Description:
     Pulse Induction / Continuous Wave Hybrid Metal Detector
 * Test configuration:
     MCU:             PIC18F4520
     Oscillator:      20 MHz Crystal
     Ext. Modules:    None.
     SW:              mikroC PRO for PIC
                      http://www.mikroe.com/mikroc/pic/
 * NOTES:
     Sync signal must be present for +5V supply to work.
*/

// Assign port I/O macro definitions
#define TRISA_INIT 0x0F;
#define TRISB_INIT 0xC0;
#define TRISC_INIT 0xF0;
#define TRISD_INIT 0x38;
#define TRISE_INIT 0x06;

// LCD assignments
sbit LCD_D4 at RB3_bit;                          // Data line 4
sbit LCD_D5 at RB2_bit;                          // Data line 5
sbit LCD_D6 at RB1_bit;                          // Data line 6
sbit LCD_D7 at RB0_bit;                          // Data line 7
sbit LCD_RS at RB5_bit;                          // Register select
sbit LCD_EN at RB4_bit;                          // Enable

// LCD Port direction
sbit LCD_RS_Direction at TRISB5_bit;             // Register select
sbit LCD_EN_Direction at TRISB4_bit;             // Enable
sbit LCD_D7_Direction at TRISB0_bit;             // Data bit 7
sbit LCD_D6_Direction at TRISB1_bit;             // Data bit 6
sbit LCD_D5_Direction at TRISB2_bit;             // Data bit 5
sbit LCD_D4_Direction at TRISB3_bit;             // Data bit 4

// Port assignments
sbit debug at LATD2_bit;                         // Debug output
sbit EN1 at LATD6_bit;                           // Enable MOSFET
sbit ps_sync at LATC0_bit;                       // Power supply sync pulse
sbit main_pulse at LATC3_bit;                    // Main sample pulse
sbit efe_pulse at LATD0_bit;                     // EFE sample pulse
sbit disc_pulse at LATC1_bit;                    // DISC_sample pulse
sbit audio_en at LATA4_bit;                      // Audio enable
sbit menu_btn_port at PORTC.B6;                  // Keypad menu button
sbit up_btn_port at PORTC.B5;                    // Keypad up button
sbit down_btn_port at PORTC.B4;                  // Keypad down button
sbit enter_btn_port at PORTD.B3;                 // Keypad enter button

// General macro definitions
#define EN1_on 1;                                // Turn on MOSFET
#define EN1_off 0;                               // Turn off MOSFET
#define main_on 0;                               // Turn on main sample
#define main_off 1;                              // Turn off main sample
#define efe_on 0;                                // Turn on EFE sample
#define efe_off 1;                               // Turn off EFE sample
#define disc_on 0;                               // Turn on DISC sample
#define disc_off 1;                              // Turn off DISC sample
#define audio_on 1;                              // Audio enabled
#define audio_off 0;                             // Audio disabled

// General Constants
const debounce = 100;                            // 100ms debounce time
const btn_off = 1;                               // Keypad button off
const btn_on = 0;                                // Keypad button on
const menu_btn = 1;                              // Keypad menu button
const up_btn = 2;                                // Keypad up button
const down_btn = 3;                              // Keypad down button
const enter_btn = 4;                             // Keypad enter button
const menu_active = 1;                           // Indicates menu is in use
const menu_inactive = 0;                         // Indicates menu is not being used
const pulse = 0;                                 // Indicates pulse detection mode
const hybrid = 1;                                // Indicates hybrid detection mode
const blk_limit = 7;                             // Block display limit

// Timer offset constants (us)
const txon_offset = 4;
const txpd_offset = 8;
const main_dly_offset = 5;
const main_smpl_offset = 4;
const efe_dly_offset = 10;
const disc_dly_offset = 9;
const disc_smpl_offset = 6;

// Variables
unsigned i;                                      // Generic variable
unsigned tmp;                                    // Temporary variable
char int_state;                                  // State machine for interrupt routine
char main_state;                                 // State machine for main routine
char hybrid_cycle;                               // Hybrid operating cycle (0 = pulse, 1 = disc)
unsigned t;                                      // Temporary storage for ADC readings
unsigned vbatt;                                  // Battery voltage
unsigned pi_target;                              // PI target voltage
unsigned disc_target;                            // DISC target voltage
char loop_count;                                 // Main loop count
unsigned pi_array[64];                           // Running average array for PI target voltages
char pi_pointer;                                 // Pointer into PI array
unsigned pi_tmp;                                 // Temporary variable for PI array calculations
unsigned pi_total;                               // Total of pi_array contents
unsigned disc_array[64];                         // Running average array for disc target voltages
char disc_pointer;                               // Pointer into disc array     
unsigned disc_tmp;                               // Temporary variable for disc
unsigned disc_total;                             // Total of disc_array contents
unsigned short pi_adc = 0;                       // PI channel ADC
unsigned short disc_adc = 1;                     // Disc channel ADC
unsigned short batt_adc = 7;                     // Battery monitor ADC
char keypad_btn;                                 // Keypad button that has been pressed
char result;                                     // Records result of a button push
char menu_flag;                                  // Flags state of menu system (active or inactive)
char menu_disp;                                  // Indicates current menu display screen
char detect_mode;                                // Operating mode of detector (0 = pulse, else = hybrid)
unsigned pi_thr;                                 // Audio threshold for PI channel
unsigned disc_thr;                               // Audio threshold for disc channel
char thr_disp;                                   // Audio threshold for display purposes
char txon;                                       // TX pulse width (us)
char txonh;                                      // TX pulse width high byte
char txonl;                                      // TX pulse width low byte
unsigned txpd;                                   // TX pulse period (us)
char txpdh;                                      // TX pulse period high byte
char txpdl;                                      // TX pulse period low byte
char main_dly;                                   // Main sample delay (us)
char main_dlyh;                                  // Main sample delay high byte
char main_dlyl;                                  // Main sample delay low byte
char main_smpl;                                  // Main sample delay pulse width (us)
char main_smplh;                                 // Main sample pulse width high byte
char main_smpll;                                 // Main sample pulse width low byte
unsigned efe_dly;                                // EFE sample delay (us)
char efe_dlyh;                                   // EFE sample delay high byte
char efe_dlyl;                                   // EFE sample delay low byte
char efe_smplh;                                  // EFE sample pulse width high byte
char efe_smpll;                                  // EFE sample pulse width low byte
char pi_num;                                     // Number of PI readings to average
char disc_dly;                                   // DISC sample delay (us)
char disc_dlyh;                                  // DISC sample delay high byte
char disc_dlyl;                                  // DISC sample delay low byte
char disc_smpl;                                  // DISC sample pulse width (us)
char disc_smplh;                                 // DISC sample pulse width high byte
char disc_smpll;                                 // DISC sample pulse width low byte
char disc_num;                                   // Number of disc readings to average
char accept;                                     // Accept (non-ferrous) counter
char accept_blk;                                 // Accept block display counter
char reject;                                     // Reject (ferrous)counter
char reject_blk;                                 // Reject block display counter
char update_disp;                                // Update display flag
char meter_zero;                                 // Meter zero counter
char meter_zero_limit;                           // Meter zero counter limit
char counter_limit;                              // Accept and reject counter limit

// Interrupt routine
void interrupt() {
  switch (int_state) {
    case 0:                                      // TX on - charge coil
      EN1 = EN1_on;                              // Turn on MOSFET
      TMR0H = txonh;                             // Load TMR0 for TX pulse
      TMR0L = txonl;
      TMR1H = txpdh;                             // Load TMR1 for TX period
      TMR1L = txpdl;
      ps_sync = 1;                               // Sync power supply
      debug = 1;
      PIR1 = 0x00;                               // Clear TMR1 overflow interrupt flag
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      if (hybrid_cycle == 0) {
        int_state = 1;                           // Pulse sample
      } else {
        int_state = 6;                           // Disc sample
      }
      break;
    case 1:                                      // TX off - discharge coil
      EN1 = EN1_off;                             // Turn off MOSFET
      TMR0H = main_dlyh;                         // Load TMR0 for main sample delay
      TMR0L = main_dlyl;
      ps_sync = 0;                               // Sync power supply
      debug = 0;
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      int_state = 2;
      break;
    case 2:                                      // Main sample pulse on
      main_pulse = main_on;                      // Turn on main sample
      TMR0H = main_smplh;                        // Load TMR0 for main sample width
      TMR0L = main_smpll;
      ps_sync = 1;                               // Sync power supply
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      int_state = 3;
      break;
    case 3:                                      // Main sample pulse off
      main_pulse = main_off;                     // Turn off main sample
      TMR0H = efe_dlyh;                          // Load TMR0 for EFE sample delay
      TMR0L = efe_dlyl;
      ps_sync = 0;                               // Sync power supply
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      int_state = 4;
      break;
    case 4:                                      // EFE sample pulse on
      efe_pulse = efe_on;                        // Turn on EFE sample
      TMR0H = efe_smplh;                         // Load TMR0 for EFE sample width
      TMR0L = efe_smpll;
      ps_sync = 1;                               // Sync power supply
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      int_state = 5;
      break;
    case 5:                                      // EFE sample pulse off
      efe_pulse = efe_off;                       // Turn off EFE sample
      TMR0H = 0x00;                              // Load TMR0 with maximum delay
      TMR0L = 0x00;
      ps_sync = 0;
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      if (detect_mode == pulse) {                // Check detector mode (pulse or hybrid)
        hybrid_cycle = 0;                        // Continue with pulse sampling
      } else {
        hybrid_cycle = 1;                        // Switch to disc sampling
      }
      int_state = 0;
      break;
    case 6:                                      // TX off - discharge coil
      EN1 = EN1_off;                             // Turn off MOSFET
      TMR0H = disc_dlyh;                         // Load TMR0 for DISC sample delay
      TMR0L = disc_dlyl;
      ps_sync = 0;                               // Sync power supply
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      int_state = 7;
      break;
    case 7:                                      // Disc sample pulse on
      disc_pulse = disc_on;                      // Turn on disc sample pulse
      TMR0H = disc_smplh;                        // Load TMR0 for disc sample pulse width
      TMR0L = disc_smpll;
      ps_sync = 1;                               // Sync power supply
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      int_state = 8;
      break;
    case 8:                                      // Disc sample pulse off
      disc_pulse = disc_off;                     // Turn off disc sample pulse
      TMR0H = 0x00;                              // Load TMR0 with maximum delay
      TMR0L = 0x00;
      ps_sync = 0;                               // Sync power supply
      INTCON = 0xE0;                             // Clear TMR0 overflow interrupt flag
      hybrid_cycle = 0;                          // Switch to pulse sampling
      int_state = 0;
      break;
  }
}

// Extract requested digit from value and display at selected row and column on LCD
void extract_and_disp(
  unsigned value,                                // Number to display on LCD
  unsigned divisor,                              // Selects which digit to extract
  char row,                                      // Select row on LCD
  char col) {                                    // Select column on LCD
  char ch;                                       // Digit to display
  ch = value / divisor % 10;                     // Extract selected digit
  Lcd_Chr(row, col, 48 + ch);                    // Display selected digit
}

// Acquire battery voltage
void battery_measure() {
  t = 0;                                         // Clear battery voltage accumulator
  for (i = 1; i <= 8; i++) {
    t += ADC_Read(batt_adc);                     // Accumulate battery voltage readings
  }
  t = t / 8;                                     // Average readings
  t = (5000 - (t * 5)) * 3 / 100;                // Calculate battery voltage
}

// Show battery voltage on LCD
void battery_display() {
  extract_and_disp(t, 100, 1, 10);
  extract_and_disp(t, 10, 1, 11);
  Lcd_Out(1, 12, ".");
  extract_and_disp(t, 1, 1, 13);
  Lcd_Out(1, 14, "V ");
}

// Display appropriate battery symbol
void battery_symbol() {
  if (t >= 120)
    Lcd_Chr(1, 16, 0);
  else if (t >= 117)
    Lcd_Chr(1, 16, 1);
  else if (t >= 114)
    Lcd_Chr(1, 16, 2);
  else if (t >= 111)
    Lcd_Chr(1, 16, 3);
  else if (t >= 108)
    Lcd_Chr(1, 16, 4);
  else if (t >= 105)
    Lcd_Chr(1, 16, 5);
  else
    Lcd_Out(1, 16, "X");
}

void calc_txon() {                               // Calculate TX pulse width
  tmp = 65535 - ((txon - txon_offset) / 2) * 10;
  txonh = tmp >> 8;
  txonl = tmp - (txonh << 8);
}

void calc_txpd() {                               // Calculate TX period
  tmp = 65535 - ((txpd - txpd_offset) / 2) * 10;
  txpdh = tmp >> 8;
  txpdl = tmp - (txpdh << 8);
}

void calc_main_dly() {                           // Calculate main sample delay
  tmp = 65535 - ((main_dly - main_dly_offset) / 2) * 10;
  main_dlyh = tmp >> 8;
  main_dlyl = tmp - (main_dlyh << 8);
}

void calc_main_smpl() {                          // Calculate main sample pulse width
  tmp = 65535 - ((main_smpl - main_smpl_offset) / 2) * 10;
  main_smplh = tmp >> 8;
  main_smpll = tmp - (main_smplh << 8);
}

void calc_efe_dly() {                            // Calculate EFE sample delay
  tmp = 65535 - ((efe_dly - efe_dly_offset) / 2) * 10;
  efe_dlyh = tmp >> 8;
  efe_dlyl = tmp - (efe_dlyh << 8);
}

void calc_efe_smpl() {                           // Calculate EFE sample pulse width
  efe_smplh = main_smplh;                        // EFE sample pulse width must equal main sample
  efe_smpll = main_smpll;
}

void calc_disc_dly() {                           // Calculate disc sample delay
  tmp = 65535 - ((disc_dly - disc_dly_offset) / 2) * 10;
  disc_dlyh = tmp >> 8;
  disc_dlyl = tmp - (disc_dlyh << 8);
}

void calc_disc_smpl() {                          // Calculate disc sample pulse width
  tmp = 65535 - ((disc_smpl - disc_smpl_offset) / 2) * 10;
  disc_smplh = tmp >> 8;
  disc_smpll = tmp - (disc_smplh << 8);
}

void clear_arrays() {                            // Clear both PI and disc arrays
  for (i = 0; i <= (pi_num - 1); i++) {
    pi_array[i] = 0;                             // Clear PI array
    disc_array[i] = 0;                           // Clear disc array
  }
  pi_pointer = 0;                                // Reset PI array pointer
  pi_total = 0;                                  // Clear PI array total
  disc_pointer = 0;                              // Reset disc array pointer
  disc_total = 0;                                // Clear disc array total;
}

void write_eeprom() {
  EEPROM_Write(0, 0xAA);                         // Initialize EEPROM
  EEPROM_Write(1, detect_mode);                  // Write detection mode
  tmp = pi_thr >> 8;                             // Extract PI threshold high byte
  EEPROM_Write(2, tmp);                          // Write PI threshold high byte
  tmp = (pi_thr << 8) >> 8;                      // Extract PI threshold low byte
  EEPROM_Write(3, tmp);                          // Write PI threshold low byte
  EEPROM_Write(4, txon);                         // Write TX pulse width
  tmp = txpd >> 8;                               // Extract TX period high byte
  EEPROM_Write(5, tmp);                          // Write TX period high byte
  tmp = (txpd << 8) >> 8;                        // Extract TX period low byte
  EEPROM_Write(6, tmp);                          // Write TX period low byte
  EEPROM_Write(7, main_dly);                     // Write main sample delay
  EEPROM_Write(8, main_smpl);                    // Write main sample pulse width
  tmp = efe_dly >> 8;                            // Extract EFE sample delay high byte
  EEPROM_Write(9, tmp);                          // Write EFE sample delay high byte
  tmp = (efe_dly << 8) >> 8;                     // Extract EFE sample delay low byte
  EEPROM_Write(10, tmp);                         // Write EFE sample delay low byte
  EEPROM_Write(11, pi_num);                      // Write number of PI readings to average
  tmp = disc_thr >> 8;                           // Extract disc threshold high byte
  EEPROM_Write(12, tmp);                         // Write disc threshold high byte
  tmp = (disc_thr << 8) >> 8;                    // Extract disc threshold low byte
  EEPROM_Write(13, tmp);                         // Write disc threshold low byte
  EEPROM_Write(14, disc_dly);                    // Write disc sample delay
  EEPROM_Write(15, disc_smpl);                   // Write disc sample pulse width
  EEPROM_Write(16, disc_num);                    // Write number of disc readings to average
  EEPROM_Write(17, meter_zero_limit);            // Write meter zero limit
  EEPROM_Write(18, counter_limit);               // Write accept and reject counter limit
}

// Scan keypad for a key press
char scan_keypad() {
  result = 0;
  if (menu_btn_port == btn_on) {                 // Check menu button
    result = menu_btn;
    Delay_ms(debounce);
    while (menu_btn_port == btn_on) {}
    Delay_ms(debounce);
  } else {
    if (up_btn_port == btn_on) {                 // Check up button
      result = up_btn;
      Delay_ms(debounce);
      while (up_btn_port == btn_on) {}
      Delay_ms(debounce);
    } else {
      if (down_btn_port == btn_on) {             // Check down button
        result = down_btn;
        Delay_ms(debounce);
        while (down_btn_port == btn_on) {}
        Delay_ms(debounce);
      } else {
        if (enter_btn_port == btn_on) {          // Check enter button
          result = enter_btn;
          Delay_ms(debounce);
          while (enter_btn_port == btn_on) {}
          Delay_ms(debounce);
        }
      }
    }
  }
  return result;
}

// Menu system
void menu_system() {
  PIE1 = 0x00;                                   // Disable TMR1 interrupt
  INTCON = 0x00;                                 // Disable global, peripheral and TMR0 interrupts
  menu_flag = menu_active;                       // Flag menu system as active
  EN1 = EN1_off;                                 // Turn off MOSFET to save power
  audio_en = audio_off;                          // Disable audio while in menu system
  menu_disp = 0;                                 // Set menu display to first screen
  while (menu_flag == menu_active) {             // Enter menu system
    switch (menu_disp) {
    case 0:
      Lcd_Out(1, 1, " DETECTING MODE ");
      if (detect_mode == pulse) {
        Lcd_Out(2, 1, "     PULSE      ");
      } else {
        Lcd_Out(2, 1, "     HYBRID     ");
      }
      if ((keypad_btn == up_btn) || (keypad_btn == down_btn)) { // Toggle detection mode
        if (detect_mode == pulse) {
          detect_mode = hybrid;
        } else {
          detect_mode = pulse;
        }
      }
      break;
    case 1:
      Lcd_Out(1, 1, "  PI THRESHOLD  ");
      Lcd_Out(2, 1, "       ");
      thr_disp = pi_thr - 511;
      extract_and_disp(thr_disp, 10, 2, 8);
      extract_and_disp(thr_disp, 1, 2, 9);
      Lcd_Out(2, 10, "       ");
      if (keypad_btn == up_btn) {
        pi_thr++;
        if (pi_thr > 531) {pi_thr = 531;}
      }
      if (keypad_btn == down_btn) {
        pi_thr--;
        if (pi_thr < 511) {pi_thr = 511;}
      }
      break;
    case 2:
      Lcd_Out(1, 1, " PI PULSE WIDTH ");
      Lcd_Out(2, 1, "     ");
      extract_and_disp(txon, 100, 2, 6);
      extract_and_disp(txon, 10, 2, 7);
      extract_and_disp(txon, 1, 2, 8);
      Lcd_Out(2, 9, "us      ");
      if (keypad_btn == up_btn) {
        txon += 10;
        if (txon > 200) {txon = 200;}
      }
      if (keypad_btn == down_btn) {
        txon -= 10;
        if (txon < 10) {txon = 10;}
      }
      break;
    case 3:
      Lcd_Out(1, 1, "PI SAMPLE DELAY ");
      Lcd_Out(2, 1, "      ");
      extract_and_disp(main_dly, 10, 2, 7);
      extract_and_disp(main_dly, 1, 2, 8);
      Lcd_Out(2, 9, "us      ");
      if (keypad_btn == up_btn) {
        main_dly++;
        if (main_dly > 50) {main_dly = 50;}
      }
      if (keypad_btn == down_btn) {
        main_dly--;
        if (main_dly < 15) {main_dly = 15;}
      }
      break;
    case 4:
      Lcd_Out(1, 1, "PI SAMPLE WIDTH ");
      Lcd_Out(2, 1, "     ");
      extract_and_disp(main_smpl, 100, 2, 6);
      extract_and_disp(main_smpl, 10, 2, 7);
      extract_and_disp(main_smpl, 1, 2, 8);
      Lcd_Out(2, 9, "us      ");
      if (keypad_btn == up_btn) {
        main_smpl++;
        if (main_smpl > 60) {main_smpl = 60;}
      }
      if (keypad_btn == down_btn) {
        main_smpl--;
        if (main_smpl < 20) {main_smpl = 20;}
      }
      break;
    case 5:
      Lcd_Out(1, 1, "   PI AVERAGE   ");
      Lcd_Out(2, 1, "       ");
      extract_and_disp(pi_num, 10, 2, 8);
      extract_and_disp(pi_num, 1, 2, 9);
      Lcd_Out(2, 10, "       ");
      if (keypad_btn == up_btn) {
        pi_num++;
        if (pi_num > 64) {pi_num = 64;}
      }
      if (keypad_btn == down_btn) {
        pi_num--;
        if (pi_num < 1) {pi_num = 1;}
      }
      break;
    case 6:
      Lcd_Out(1, 1, " DISC THRESHOLD ");
      Lcd_Out(2, 1, "       ");
      thr_disp = disc_thr - 511;
      extract_and_disp(thr_disp, 10, 2, 8);
      extract_and_disp(thr_disp, 1, 2, 9);
      Lcd_Out(2, 10, "       ");
      if (keypad_btn == up_btn) {
        disc_thr++;
        if (disc_thr > 531) {disc_thr = 531;}
      }
      if (keypad_btn == down_btn) {
        disc_thr--;
        if (disc_thr < 511) {disc_thr = 511;}
      }
      break;
    case 7:
      Lcd_Out(1, 1, "DISC SMPL DELAY ");
      Lcd_Out(2, 1, "     ");
      extract_and_disp(disc_dly, 100, 2, 6);
      extract_and_disp(disc_dly, 10, 2, 7);
      extract_and_disp(disc_dly, 1, 2, 8);
      Lcd_Out(2, 9, "us      ");
      if (keypad_btn == up_btn) {
        disc_dly++;
        if (disc_dly > 100) {disc_dly = 100;}
      }
      if (keypad_btn == down_btn) {
        disc_dly--;
        if (disc_dly < 20) {disc_dly = 20;}
      }
      break;
    case 8:
      Lcd_Out(1, 1, "DISC SMPL WIDTH ");
      Lcd_Out(2, 1, "     ");
      extract_and_disp(disc_smpl, 100, 2, 6);
      extract_and_disp(disc_smpl, 10, 2, 7);
      extract_and_disp(disc_smpl, 1, 2, 8);
      Lcd_Out(2, 9, "us      ");
      if (keypad_btn == up_btn) {
        disc_smpl++;
        if (disc_smpl > 100) {main_smpl = 100;}
      }
      if (keypad_btn == down_btn) {
        disc_smpl--;
        if (disc_smpl < 10) {disc_smpl = 10;}
      }
      break;
    case 9:
      Lcd_Out(1, 1, "  DISC AVERAGE  ");
      Lcd_Out(2, 1, "       ");
      extract_and_disp(disc_num, 10, 2, 8);
      extract_and_disp(disc_num, 1, 2, 9);
      Lcd_Out(2, 10, "       ");
      if (keypad_btn == up_btn) {
        disc_num++;
        if (disc_num > 64) {disc_num = 64;}
      }
      if (keypad_btn == down_btn) {
        disc_num--;
        if (disc_num < 1) {disc_num = 1;}
      }
      break;
    case 10:
      Lcd_Out(1, 1, "METER ZERO LIMIT");
      Lcd_Out(2, 1, "       ");
      extract_and_disp(meter_zero_limit, 10, 2, 8);
      extract_and_disp(meter_zero_limit, 1, 2, 9);
      Lcd_Out(2, 10, "       ");
      if (keypad_btn == up_btn) {
        meter_zero_limit++;
        if (meter_zero_limit > 20) {meter_zero_limit = 20;}
      }
      if (keypad_btn == down_btn) {
        meter_zero_limit--;
        if (meter_zero_limit < 5) {meter_zero_limit = 5;}
      }
      break;
    case 11:
      Lcd_Out(1, 1, " COUNTER LIMIT  ");
      Lcd_Out(2, 1, "      ");
      extract_and_disp(counter_limit, 100, 2, 7);
      extract_and_disp(counter_limit, 10, 2, 8);
      extract_and_disp(counter_limit, 1, 2, 9);
      Lcd_Out(2, 10, "       ");
      if (keypad_btn == up_btn) {
        counter_limit += 10;
        if (counter_limit > 150) {counter_limit = 150;}
      }
      if (keypad_btn == down_btn) {
        counter_limit -= 10;
        if (counter_limit < 10) {counter_limit = 10;}
      }
      break;
    }
    keypad_btn = scan_keypad();                  // Scan the keypad
    if (keypad_btn == menu_btn) {
      menu_flag = menu_inactive;                 // Deactivate menu system if menu button pressed
      write_eeprom();                            // Write data to EEPROM
      calc_txon();                               // Calculate TX pulse width and set timer values
      calc_txpd();                               // Calculate TX period and set timer values
      calc_main_dly();                           // Calculate main sample delay and set timer values
      calc_main_smpl();                          // Calculate main sample pulse width and set timer values
      calc_efe_dly();                            // Calculate EFE sample delay and set timer values
      calc_efe_smpl();                           // Calculate EFE sample pulse width and set timer values
      calc_disc_dly();                           // Calculate disc sample delay and set timer values
      calc_disc_smpl();                          // Calculate disc sample pulse width and set timer values
      clear_arrays();                            // Clear both PI and DISC arrays
      int_state = 0;                             // Reset interrupt state machine
      PIE1 = 0x01;                               // Enable TMR0 interrupt
      INTCON = 0xE0;                             // Enable global, peripheral and TMR1 interrupts
    } else {
      if (keypad_btn == enter_btn) {             // Navigate menu system
        switch (menu_disp) {
        case 0:                                  // Detect mode
          if (detect_mode == hybrid) {
            menu_disp = 6;                       // Go to hybrid settings
          } else {
            menu_disp = 1;                       // Go to pulse settings
          }
          break;
        case 1:                                  // PI threshold
          menu_disp = 2;
          break;
        case 2:                                  // PI pulse width
          menu_disp = 3;
          break;
        case 3:                                  // PI sample delay
          menu_disp = 4;
          break;
        case 4:                                  // PI sample pulse width
          menu_disp = 5;
          break;
        case 5:                                  // PI running average
          menu_disp = 0;
          break;
        case 6:                                  // Disc threshold
          menu_disp = 7;
          break;
        case 7:                                  // Disc sample delay
          menu_disp = 8;
          break;
        case 8:                                  // Disc sample pulse width
          menu_disp = 9;
          break;
        case 9:
          menu_disp = 10;                        // Disc running average
          break;
        case 10:                                 // Meter zero limit
          menu_disp = 11;
          break;
        case 11:                                 // Accept and reject counter limit
          menu_disp = 0;
          break;
        }
      }
    }
  }
}

void disp_reset() {
  if (detect_mode == pulse) {
    Lcd_Out(1, 1, " PULSE   ");
  } else {
    Lcd_Out(1, 1, " HYBRID  ");
  }
  Lcd_Out(2, 1, "       ");
  Lcd_Chr(2, 8, 7);                              // Display middle marker (vertical line)
  Lcd_Out(2, 9, "        ");
}

// Main program section
void main() {
  // Initialize ports
  TRISA = TRISA_INIT;
  TRISB = TRISB_INIT;
  TRISC = TRISC_INIT;
  TRISD = TRISD_INIT;
  TRISE = TRISE_INIT;
  
  // Turn off MOSFET and sample pulses
  EN1 = EN1_off;                                 // Turn off MOSFET
  main_pulse = main_off;                         // Turn off main sample pulse
  efe_pulse = efe_off;                           // Turn off EFE sample pulse
  disc_pulse = disc_off;                         // Turn off disc sample pulse

  // Initialize ADCs
  ADCON1 = 0x07;                                 // Enable AD0 to AD7 (VDD and VSS as voltage reference)
  ADC_Init();
  
  // Initialize LCD
  Lcd_Init();
  Lcd_Cmd(_LCD_CLEAR);
  Lcd_Cmd(_LCD_CURSOR_OFF);
  
  // Load custom characters into CG RAM of LCD
  Lcd_Cmd(64);
  // Battery >= 13.5V (character 0)
  Lcd_Chr_Cp(14);
  for (i = 2; i <= 8; i++) {Lcd_Chr_Cp(31);}
  // Battery >= 13.0V (character 1)
  Lcd_Chr_Cp(14);
  Lcd_Chr_Cp(31);
  Lcd_Chr_Cp(17);
  for (i = 4; i <= 8; i++) {Lcd_Chr_Cp(31);}
  // Battery >= 12.5V (character 2)
  Lcd_Chr_Cp(14);
  Lcd_Chr_Cp(31);
  for (i = 3; i <= 5; i++) {Lcd_Chr_Cp(17);}
  for (i = 6; i <= 8; i++) {Lcd_Chr_Cp(31);}
  // Battery >= 12.0V (character 3)
  Lcd_Chr_Cp(14);
  Lcd_Chr_Cp(31);
  for (i = 3; i <= 5; i++) {Lcd_Chr_Cp(17);}
  for (i = 6; i <= 8; i++) {Lcd_Chr_Cp(31);}
  // Battery >= 11.5V (character 4)
  Lcd_Chr_Cp(14);
  Lcd_Chr_Cp(31);
  for (i = 3; i <= 6; i++) {Lcd_Chr_Cp(17);}
  for (i = 7; i <= 8; i++) {Lcd_Chr_Cp(31);}
  // Battery >= 11.0V (character 5)
  Lcd_Chr_Cp(14);
  Lcd_Chr_Cp(31);
  for (i = 3; i <= 7; i++) {Lcd_Chr_Cp(17);}
  Lcd_Chr_Cp(31);
  // Block symbol for ferrous /non-ferrous display (character 6)
  for (i = 1; i <= 8; i++) {Lcd_Chr_Cp(31);}
  // Middle character on ferrous / non-ferrous display (character 7)
  for (i = 1; i <= 8; i++) {Lcd_Chr_Cp(4);}
  
  // Display splash screen
  Lcd_Out(1,1,"  VOODOO V1.0   ");
  Lcd_Out(2,1,"HYBRID DETECTOR ");
  delay_ms(2000);
  Lcd_Cmd(_LCD_CLEAR);
 
  // Initialize variables
  int_state = 0;                                 // Initialize interrupt state machine
  main_state = 0;                                // Initialize main state machine
  loop_count = 0;                                // Reset loop counter
  hybrid_cycle = 0;                              // Hybrid operating cycle (0 = pulse, 1 = disc)
  accept = 0;                                    // Clear accept (non-ferrous) counter
  accept_blk = 0;                                // Clear accept block display counter
  reject = 0;                                    // Clear reject (ferrous) counter
  reject_blk = 0;                                // Clear reject block display counter
  update_disp = 0;                               // Clear update display flag
  meter_zero = 0;                                // Clear meter zero counter
  
  // All default settings below may be overwritten by EEPROM
  detect_mode = hybrid;                          // Set default detection mode to hybrid
  pi_thr = 525;                                  // Set default pi threshold
  disc_thr = 511;                                // Set default disc threshold
  txon = 150;                                    // Set default TX pulse width (us)
  txpd = 1000;                                   // Set default TX pulse period (us)
  main_dly = 27;                                 // Set default main sample delay (us)
  main_smpl = 60;                                // Set default main sample pulse width (us)
  efe_dly = 650;                                 // Set default EFE sample delay (us)
  pi_num = 64;                                   // Set default number of PI readings to average
  disc_dly = 45;                                 // Set default disc sample delay (us)
  disc_smpl = 45;                                // Set default disc sample pulse width (us)
  disc_num = 64;                                 // Set default number of disc readings to average
  meter_zero_limit = 10;                         // Set meter zero limit
  counter_limit = 100;                           // Set accept and reject counter limit
  
  clear_arrays();                                // Clear both PI and DISC arrays
  
  // Load saved values from EEPROM
  if (EEPROM_Read(0) == 0xAA) {                  // Check if EEPROM already initialized
    detect_mode = EEPROM_Read(1);                // Read detection mode (pulse or hybrid)
    pi_thr = EEPROM_Read(2);                     // Read PI threshold high byte
    pi_thr = pi_thr << 8;
    pi_thr += EEPROM_Read(3);                    // Read PI threshold low byte
    txon = EEPROM_Read(4);                       // Read TX pulse width
    txpd = EEPROM_Read(5);                       // Read TX period high byte
    txpd = txpd << 8;
    txpd += EEPROM_Read(6);                      // Read TX period low byte
    main_dly = EEPROM_Read(7);                   // Read main sample delay
    main_smpl = EEPROM_Read(8);                  // Read main sample pulse width
    efe_dly = EEPROM_Read(9);                    // Read EFE sample delay high byte
    efe_dly = efe_dly << 8;
    efe_dly += EEPROM_Read(10);                  // Read EFE sample delay low byte
    pi_num = EEPROM_Read(11);                    // Read number of PI readings to average
    disc_thr = EEPROM_Read(12);                  // Read disc threshold high byte
    disc_thr = disc_thr << 8;
    disc_thr += EEPROM_Read(13);                 // Read disc threshold low byte
    disc_dly = EEPROM_Read(14);                  // Read disc sample delay
    disc_smpl = EEPROM_Read(15);                 // Read disc sample pulse width
    disc_num = EEPROM_Read(16);                  // Read number of disc readings to average
    meter_zero_limit = EEPROM_Read(17);          // Read meter zero limit
    counter_limit = EEPROM_Read(18);             // Read accept and reject counter limit
  } else{
    write_eeprom();                              // Write settings to EEPROM
  }
  calc_txon();                                   // Calculate TX pulse width and set timer values
  calc_txpd();                                   // Calculate TX period and set timer values
  calc_main_dly();                               // Calculate main sample delay and set timer values
  calc_main_smpl();                              // Calculate main sample pulse width and set timer values
  calc_efe_dly();                                // Calculate EFE sample delay and set timer values
  calc_efe_smpl();                               // Calculate EFE sample pulse width and set timer values
  calc_disc_dly();                               // Calculate disc sample delay and set timer values
  calc_disc_smpl();                              // Calculate disc sample pulse width and set timer values
  disp_reset();                                  // Reset display
  
  // Initialize timers and interrupts
  // TMR0 used to generate sample pulses and delays (main and EFE) and TX pulses
  // TMR1 used to generate TX period
  T0CON = 0x88;                                  // Configure TMR0 as 16-bit
  T1CON = 0x85;                                  // Configure TMR1 as 16-bit
  TMR0H = txonh;                                 // Load TX pulse width
  TMR0L = txonl;
  TMR1H = txpdh;                                 // Load TX period
  TMR1H = txpdl;
  PIR1 = 0x00;                                   // Clear TMR1 interrupt overflow flag
  PIE1 = 0x01;                                   // Enable TMR1 overflow interrupt
  INTCON = 0xE0;                                 // Enable interrupts

  while(1) {
    switch (main_state) {
      case 0:
        pi_target = Adc_Read(pi_adc);            // Acquire PI target reading
        pi_tmp = pi_array[pi_pointer];           // Save current PI array value
        pi_array[pi_pointer] = pi_target;        // Add latest reading to PI array
        pi_total = (pi_total + pi_target) - pi_tmp; // Calculate PI array total
        pi_target = pi_total / pi_num;           // Average readings
        pi_pointer++;                            // Increment PI pointer
        if (pi_pointer >= pi_num) {
          pi_pointer = 0;                        // Reset PI array pointer
        }
        if (detect_mode == hybrid) {
          disc_target = Adc_Read(disc_adc);      // Acquire DISC target reading
          disc_tmp = disc_array[disc_pointer];   // Save current disc array value
          disc_array[disc_pointer] = disc_target; // Add latest reading to disc array
          disc_total = (disc_total + disc_target) - disc_tmp; // Calculate disc array total;
          disc_target = disc_total / disc_num;   // Average readings
          disc_pointer++;                        // Increment disc pointer
          if (disc_pointer >= disc_num) {
            disc_pointer = 0;                    // Reset disc array pointer
          }
        }

        if (pi_target >= pi_thr) {               // Decide whether to beep or not
          if (detect_mode == hybrid) {           // Check for hybrid mode
            update_disp = 0;                     // Reset update display flag
            if (disc_target >= disc_thr) {       // Non-ferrous target detected
              audio_en = audio_on;               // Beep
              if (reject_blk == 0) {
                accept++;                        // Increment accept counter if no reject blocks displayed
                reject = 0;                      // Clear reject counter
                if (accept > counter_limit) {    // Check if accept counter has reached limit
                  accept = 0;                    // Reset accept counter to zero
                  accept_blk++;                  // Increment number of accept blocks
                  if (accept_blk <= blk_limit) {
                    update_disp = 1;             // Set update display flag
                  } else {
                    accept_blk = blk_limit;      // Limit number of accept blocks
                  }
                }
              } else {                           // There must be some reject blocks displayed
                if (reject != 0) {
                  reject--;                      // Decrement reject counter if not at zero
                } else {                         // Reject counter must have reached zero
                  reject = counter_limit;        // Set reject counter to limit
                  if (reject_blk != 0) {
                    reject_blk--;                // Decrement number of reject blocks if not zero
                    update_disp = 1;             // Set update display flag
                  }
                }
              }
            } else {                             // Ferrous target detected
              audio_en = audio_off;              // Do not beep
              if (accept_blk == 0) {
                reject++;                        // Increment reject counter if no accept blocks displayed
                accept = 0;                      // Clear accept counter
                if (reject > counter_limit) {    // Check if reject counter has reached limit
                  reject = 0;                    // Reset reject counter to zero
                  reject_blk++;                  // Increment number of reject blocks
                  if (reject_blk <= blk_limit) {
                    update_disp = 1;             // Set update display flag
                  } else {
                    reject_blk = blk_limit;      // Limit number of reject blocks
                  }
                }
              } else {                           // There must be some accept blocks displayed
                if (accept != 0) {
                  accept--;                      // Decrement accept counter is not at zero
                } else {                         // Accept counter must have reached zero
                  accept = counter_limit;        // Set accept counter to limit
                  if (accept_blk != 0) {
                    accept_blk--;                // Decrement number of accept blocks if not zero
                    update_disp = 1;             // Set update display flag
                  }
                }
              }
            }
            
          } else {                               // Mode must be PI
            audio_en = audio_on;                 // Beep
          }
        } else {                                 // No target detected
          audio_en = audio_off;                  // Do not beep
        }
        if (update_disp == 0) {                  // Only zero display when no signal is present
          meter_zero++;                          // Increment meter zero counter
          if (meter_zero >= meter_zero_limit) {
            if (accept_blk != 0) {               // Must be accept blocks displayed
              if (accept != 0) {
                accept--;                        // Decrement accept counter if not already zero
              } else {
                accept_blk--;                    // Decrement number of accept blocks
                accept = counter_limit;          // Set accept counter to limit
                update_disp = 1;                 // Set update display flag
              }
            } else {
              if (reject_blk != 0) {             // Otherwise must be reject blocks displayed
                if (reject != 0) {
                  reject--;                      // Decrement reject counter if not already zero
                } else {
                  reject_blk--;                  // Decrement number of reject blocks displayed
                  reject = counter_limit;        // Set reject counter to limit
                  update_disp = 1;               // Set update display flag
                }
              }
            }
            meter_zero = 0;                      // Reset meter zero counter
          }
        }
        if (update_disp == 1) {                  // Check if display needs to be updated
          if (accept_blk != 0) {
            for (i = 1; i <= accept_blk; i++) {
              Lcd_Chr(2, 8 + i, 6);              // Display required number of non-ferrous blocks
            }
            if (accept_blk != blk_limit) {
              for (i = accept_blk + 1; i <= blk_limit; i++) {
                Lcd_Chr(2, 8 + i, 32);           // Fill rest of non-ferrous display with spaces
              }
            }
          } else {
            Lcd_Chr(2, 9, 32);                   // Clear first block in non-ferrous display
            if (reject_blk != 0) {
              for (i = 1; i <= reject_blk; i++) {
                Lcd_Chr(2, 8 - i, 6);            // Display required number of ferrous blocks
              }
              if (reject_blk != blk_limit) {
                for (i = reject_blk + 1; i <= blk_limit; i++) {
                  Lcd_Chr(2, 8 - i, 32);         // Fill rest of ferrous display with spaces
                }
              }
            } else {
              Lcd_Chr(2, 7, 32);                 // Clear first block in ferrous display
            }
          }
          update_disp = 0;                       // Reset update display flag
        }
        keypad_btn = scan_keypad();
        if (keypad_btn == menu_btn) {            // Check if menu button pressed
          menu_system();                         // Enter menu system
          disp_reset();                          // Reset display after exiting menu system
        }
        loop_count++;                            // Increment loop count
        main_state = (loop_count == 128)?1:0;    // Read battery voltage after loop count of 128
        break;
      case 1:
        battery_measure();                       // Acquire battery voltage
        battery_display();                       // Display battery voltage
        battery_symbol();                        // Dislay appropriate battery symbol
        loop_count = 0;                          // Reset loop count
        main_state = 0;                          // Reset main program state machine
        break;
      }
    }
  }