Version:0.9 StartHTML:0000000105 EndHTML:0000378697 StartFragment:0000001499 EndFragment:0000378681
/*
* 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;
}
}
}