////////////////////////////////////////////////////////////////////////////////
//
// Author: Me
//
// Filename: MikroC Senior Project v1.3.c
//
// Title: AEM EMS Serial Data Display with Analog Sensors
//
// Date: 23FEB14
//
// Version: 1.3
//
// MCU: PIC16F887
//
// FOSC: External 20.0 MHz
//
// Config: CONFIG1 :$2007 : 0x23EA
// CONFIG2 :$2008 : 0x0700
//
// Function: The program will read in data bytes via UART from an AEM Engine
// Management System (EMS). It will then convert these bytes to a
// human readable format and display them on a 40X2 character LCD
// display.
//
// The program will also receive analog voltage values from two
// external sensors. It will then scale these values to a human
// readable format and display the values on the LCD display.
//
// Compiler: MikroC PRO for PIC v.6.0.1
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Changelog
////////////////////////////////////////////////////////////////////////////////
//
// 17FEB14 v1.0
// - Initial Release
// 19FEB14 v1.0.1
// - Changed CONFIG2 to 0x0700 for BOR = 4.0V
// - Added a delay at the beginning of the program to ensure voltage is
// stable before writing to the LCD. Eliminates corruption on LCD from
// voltage sag during engine start
// 20FEB14 v1.1
// - Removed constant float array for offset and scalar. Discovered that
// memory would overflow and corrupt display. Placed offset and scalar
// values inline with their respective functions. Overflow condition
// has been eliminated.
// - Removed constant offset and scalar for sensor ADC conversions
// 21FEB14 v1.1.1
// - Changed RA and RB debug output routine to always output so that
// operations can be verified without PIC board. Also improves
// stability of LCD display.
// 22FEB14 v1.2
// - Memory bug still persisted. Went into ICD mode and discovered that
// if an input string into the UART was exceptionally long and
// incorrect the code would lose its place after about 20 incorrect
// input bytes. Removed the --stream_counter statement to break the
// while loop. If checksum fails the stream_counter is reset to its
// maximum value to avoid a major stream_counter overflow once it
// rolls over 0. Tested with over 100 incorrect bytes and the system
// remains still as expected vice creating havoc like corrupting
// the LCD, locking up the UART, and causing a WDT reset event.
// - Recreated offset and scalar arrays for simplified editing of the
// values. Padded the first value of the array "[0]" with a null so
// that the offset and scalar values can be changed in accordance with
// what is provided in the telemetry settings. Byte 1 = offset and
// scalar [1] vice offset and scalar [0]
// - Removed most functions out of main and placed them in their own
// space for better memory allocation.
// - Removed the sprinti() function from the AFR calculations. Was
// causing the function to overlap pages forcing a manual IRP address
// condition. Removing this function saved 2655 program words and
// 56 bytes of RAM! Manually recreated the AFR function similar to the
// knock volts function. This causes an ugly leading 0 if the number
// is less than 10. Ideally this should never be seen unless the O2
// sensor is having issues. 7-9.99 AFR is just too rich for any normal
// condition and should be eliminated immediately within the engine
// tune. The leading 0 is tolerable given the major code savings.
// - Generated preliminary code for the alarm function. The initial code
// is located in the get_oil_psi() function and will need to be
// relocated to main as a separate function. A case/switch
// function needs to be developed to identify which parameter is in a
// alarm condition along with a timer so that the user can identify
// which one alarmed. Currently the function is too fast to identify
// where the alarm came from if the faulty condition immediately
// clears. Alarm sound is output on RC2 @ 5kHz 50% duty cycle.
// - Fixed leading 0 in the AFR location. Added an if/else statement.
// - Removed persistent RA and RB output as they had nothing to do with
// LCD stability. That small time delay was only masking the dirty
// little secret that the --stream_counter statement was causing.
// 23FEB14 - Added alarm functions to boost, knock volts, and oil pressure.
// Duration that alarm is active is about 3 seconds after clearing the
// alarm condition so that the alarm can be recognized by the driver.
// - Alarm sound is output on RC2 PWM output
// - Alarm LED pulse is output on RC0
////////////////////////////////////////////////////////////////////////////////
// Included Precompiled Libraries (c)2013 Mikroelektronika
////////////////////////////////////////////////////////////////////////////////
// ADC
// Conversions
// C_String
// C_Type
// Lcd
// Lcd_Constants
// UART
// PWM
////////////////////////////////////////////////////////////////////////////////
// Define the connections from the PIC to the LCD display
////////////////////////////////////////////////////////////////////////////////
// Corresponding LCD pin to PIC pin
sbit LCD_RS at RD6_bit;
sbit LCD_EN at RD7_bit;
sbit LCD_D4 at RD0_bit;
sbit LCD_D5 at RD1_bit;
sbit LCD_D6 at RD2_bit;
sbit LCD_D7 at RD3_bit;
// Corresponding TRIS bit to change direction of the pin
sbit LCD_RS_Direction at TRISD6_bit;
sbit LCD_EN_Direction at TRISD7_bit;
sbit LCD_D4_Direction at TRISD0_bit;
sbit LCD_D5_Direction at TRISD1_bit;
sbit LCD_D6_Direction at TRISD2_bit;
sbit LCD_D7_Direction at TRISD3_bit;
////////////////////////////////////////////////////////////////////////////////
// **Debug for testing project on EasyPICv7 Development Board
////////////////////////////////////////////////////////////////////////////////
// Corresponding LCD pin to PIC pin
// sbit LCD_RS at RB4_bit;
// sbit LCD_EN at RB5_bit;
// sbit LCD_D4 at RB0_bit;
// sbit LCD_D5 at RB1_bit;
// sbit LCD_D6 at RB2_bit;
// sbit LCD_D7 at RB3_bit;
// Corresponding TRIS bit to change direction of the pin
// sbit LCD_RS_Direction at TRISB4_bit;
// sbit LCD_EN_Direction at TRISB5_bit;
// sbit LCD_D4_Direction at TRISB0_bit;
// sbit LCD_D5_Direction at TRISB1_bit;
// sbit LCD_D6_Direction at TRISB2_bit;
// sbit LCD_D7_Direction at TRISB3_bit;
////////////////////////////////////////////////////////////////////////////////
// Variables
// All of the variables will be defined below
////////////////////////////////////////////////////////////////////////////////
// Define the parameters that will be displayed on the LCD upon first
// turn on. The write pids function will display this information.
char pid_1 [] = "AFR:";
char pid_2 [] = "OPSI:";
char pid_3 [] = "kPa:";
char pid_4 [] = "KNKV:";
char pid_5 [] = "BST:";
char pid_6 [] = "FPSI:";
char pid_7 [] = "IDC:";
char pid_8 [] = "TMNG:";
char pid_9 [] = "";
char pid_10 [] = "";
char pid_11 [] = "";
char pid_12 [] = "";
char pid_13 [] = "";
char pid_14 [] = "";
char pid_15 [] = "";
char pid_16 [] = "";
char pid_17 [] = "";
char pid_18 [] = "";
char pid_19 [] = "";
char vacuum [] = "VAC:";
char warning [] = "**** ";
const char pids = 8; // Baseline amount of items that will be shown on the LCD
unsigned int pid_count = pids; // Establish the initial background LCD display
const unsigned long baud_rate = 19200; // Establish baud rate for UART
unsigned opsi_temp, fpsi_temp; // Raw values read from A/D converter
char data_temp; // Temporary storage of data bytes read from UART
char opsi_raw[4]; // Value to be displayed as oil pressure
char fpsi_raw[4]; // Value to be displayed as fuel pressure
char data_to_display[15]; // Untrimmed display data
const int stream_size = 6; // Given by parameters in AEM configuration.
// Subtract by one to get number of data bytes
// which includes the checksum for later
// calculation
char stream_counter; // Temporary counter to store incoming data
unsigned short data_bytes[stream_size]; // Create an array to store the input data bytes
float conversion_buffer; // This is the buffer space to conduct calculations
char output[15]; // Output array that will be displayed on the LCD
char checksum; // Checksum variable to be evaluated against data_bytes[0]
char i; // Simple one byte variable to be used in a for loop
unsigned long eliminate_float; // Variable to eliminate scientific notation from a
// floating point number that has fractional
// values
const float sensor_scalar = 0.4902; // Scalar value for analog sensors
const float sensor_offset = -12.5; // Offset value for analog sensors
const float scalar[]= // Scalar values that correspond with AEM telemetry settings
{
'\0', // Pad first location
0.057188, // Byte 1
1.226563, // Byte 2
0.976563, // Byte 3
0.019531, // Byte 4
0.351562 // Byte 5
};
const float offset[]= // Offset values that correspond with AEM telemetry settings
{
'\0', // Pad first location
7.32, // Byte 1
2, // Byte 2
0, // Byte 3
0, // Byte 4
-17 // Byte 5
};
char alarm_identifier; // Identifies which alarm is present
const char low_oil_threshold = 10; // Threshold value for low oil pressure
const char overboost_threshold = 170; // Threshold value for overboost condition in kPa
const char knock_volts_threshold = 1.00; // Knock volts threshold
char alarm_timer; // Variable to allow the alarm condition to persist after clearing
////////////////////////////////////////////////////////////////////////////////
// Functions
// Define and prototype functions to be used by the main program
////////////////////////////////////////////////////////////////////////////////
void write_pids(pid_count) {
//
// Input the pid_count variable and traverse down the list of display values
// decrementing the pid_count until 0 is reached. This will allow the
// programmer to scale the program to accommodate a different LCD size
//
// Once the value of 0 has been reached exit the function as all desired
// background characters have been written to the LCD
//
if (pid_count == 0)
return;
Lcd_Out(1,1,pid_1);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out(1,11,pid_2);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out(1,21,pid_3);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out(1,31,pid_4);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out(2,1,pid_5);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out(2,11,pid_6);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out(2,21,pid_7);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out(2,31,pid_8);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_9);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_10);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_11);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_12);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_13);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_14);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_15);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_16);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_17);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_18);
--pid_count;
if (pid_count == 0)
return;
Lcd_Out_Cp(pid_19);
return;
}
void get_oil_pressure(){
opsi_temp = ADC_Read(5); // Read from analog 5 for oil pressure
ByteToStr(((opsi_temp >> 2)*sensor_scalar)+sensor_offset, opsi_raw); // Shift right by 2 and scale raw value to parameters given by sensor
if ((((opsi_temp >> 2)*sensor_scalar)+sensor_offset) < low_oil_threshold){ // If OPSI is less than 10 set alarm_identifier to 1
alarm_identifier=1;
alarm_timer = 10;
}
Lcd_Out(1,17,opsi_raw); // Display actual pressure on the LCD
return; // Exit the function with no return value
}
void get_fuel_pressure(){
fpsi_temp = ADC_Read(6); // Read from analog 6 for fuel pressure
ByteToStr(((fpsi_temp >> 2)*sensor_scalar)+sensor_offset, fpsi_raw); // Shift right by 2 and scale raw value to parameters given by sensor
Lcd_Out(2,17,fpsi_raw); // Display actual pressure on the LCD
return; // Exit the function with no return value
}
void fill_array(){
--stream_counter; // Decrement counter to align with first position of array
data_bytes[stream_counter] = data_temp; // If data byte received is not a header we need to store it
// Save current data byte in first position of array
return; // Exit the function with no return value
}
void calculate_checksum(){
for(checksum = 0, i=1 ; i <= (stream_size-1); ++i){ // Set the checksum value to 0
// Set the value of i to 1
// Increment i for each loop
// Loop while i <= stream_size-1
checksum += data_bytes[i]; // sum each byte from the data_bytes array in the checksum location
}
return; // return with no value once all values have been summed
}
void display_AFR(){
// Air to Fuel Ratio
// Unit: AFR
// Calculate the input byte in hex to a decimal value to be displayed
// Format: ((data_byte[x]*offset value)+scalar value) = value in decimal
// Save the calculated value in the conversion buffer
conversion_buffer = ((data_bytes[5]*scalar[1])+offset[1]);
// Multiply the value by 10 to remove the decimal points
eliminate_float = conversion_buffer*10;
if (eliminate_float >= 100) {
// Determine the tens digit in the whole number section
// Once determined add the character value of '0' to convert the
// number to a writeable character
output[0] = eliminate_float/100 + '0';
// Determine the ones digit and find the modulo of 10
// Add the character value of '0' to convert the number to a
// writeable character
output[1] = (eliminate_float/10) %10 + '0';
// Add a decimal point character
output[2] = '.';
// Determine the tenths digit and find the modulo of 10
// Add the character value of '0' to convert the number to a
// writeable character
output[3] = eliminate_float % 10 + '0';
// Write a null to the array to symbolize the end of the string
output[4] = '\0';
// Display the data on the LCD at Row 1, Column 6
Lcd_Out(1,6,output);
}
else {
// Pad a blank space
output[0] = 0x20;
// Determine the ones digit and find the modulo of 10
// Add the character value of '0' to convert the number to a
// writeable character
output[1] = (eliminate_float/10) %10 + '0';
// Add a decimal point character
output[2] = '.';
// Determine the tenths digit and find the modulo of 10
// Add the character value of '0' to convert the number to a
// writeable character
output[3] = eliminate_float % 10 + '0';
// Write a null to the array to symbolize the end of the string
output[4] = '\0';
// Display the data on the LCD at Row 1, Column 6
Lcd_Out(1,6,output);
}
return;
}
void display_engine_load(){
// Engine Load
// Unit: kPa
// Calculate the input byte in hex to a decimal value to be displayed
// Format: ((data_byte[x]*offset value)+scalar value) = value in decimal
// Save the calculated value in the conversion buffer
conversion_buffer = ((data_bytes[4]*scalar[2])+offset[2]);
if (conversion_buffer > overboost_threshold) {
alarm_identifier=3;
alarm_timer = 10;
}
// Convert the value to a character string to the greatest whole number
BytetoStr(conversion_buffer, output);
// Display the data on the LCD at Row 1, Column 26
Lcd_Out(1,26,output);
return;
}
void display_boost_vac(){
// Boost/Vac
// Unit: PSIG or inHg
// Calculate the input byte in hex to a decimal value to be displayed
// Format: ((data_byte[x]*offset value)+scalar value) = value in decimal
// Save the calculated value in the conversion buffer
conversion_buffer = ((data_bytes[4]*scalar[2])+offset[2]);
// If the manifold pressure is in a vacuum condition or <= 101.325 kPa
if (conversion_buffer <= 101.325){
// Convert the value to a character string to the greatest whole number in inHg
BytetoStr(((conversion_buffer*-0.2953)+29.92), output);
// Change the display to show VAC at Row 2, Column 1
Lcd_Out(2,1,vacuum);
// Display the data on the LCD at Row 2, Column 6
Lcd_Out(2,6,output);
}
// If the manifold pressure is in a boost condition or > 101.325 kPa
else {
// Convert the value to a character string to the greatest whole number in PSIG
BytetoStr(((conversion_buffer*0.145037738)-14.6959),output);
// Change the display to show BST at Row 2, Column 1
Lcd_Out(2,1,pid_5);
// Display the data on the LCD at Row 2, Column 6
Lcd_Out(2,6,output);
return;
}
}
void display_injector_duty(){
// Injector Duty
// Unit: Percentage
// Calculate the input byte in hex to a decimal value to be displayed
// Format: ((data_byte[x]*offset value)+scalar value) = value in decimal
// Save the calculated value in the conversion buffer
conversion_buffer = ((data_bytes[3]*scalar[3])+offset[3]);
// Convert the value to a character string to the greatest whole number
BytetoStr(conversion_buffer, output);
// Display the data on the LCD at Row 2, Column 26
Lcd_Out(2,26,output);
// Add the percentage symbol after the data
Lcd_Out(2,29,"%");
return;
}
void display_knock_volts(){
// Knock Volts
// Unit: 0.00 - 5.00 Volts
// Calculate the input byte in hex to a decimal value to be displayed
// Format: ((data_byte[x]*offset value)+scalar value) = value in decimal to be displayed.
// Save the calculated value in the conversion buffer
conversion_buffer = ((data_bytes[2]*scalar[4])+offset[4]);
if(conversion_buffer > knock_volts_threshold){
alarm_identifier=2;
alarm_timer = 10;
}
// Multiply the value by 100 to remove the decimal points
eliminate_float = conversion_buffer*100;
// Determine the first digit in the whole number section
// Once determined add the character value of '0' to convert the
// number to a writable character
output[0] = eliminate_float/100 + '0';
// Add a decimal point character
output[1] = '.';
// Determine the tenths digit and find the modulo of 10
// Add the character value of '0' to convert the number to a
// writable character
output[2] = (eliminate_float/10) %10 + '0';
// Determine the ones digit and find the modulo of 10
// Add the character value of '0' to convert the number to a
// writable character
output[3] = eliminate_float % 10 + '0';
// Write a null to the array to symbolize the end of the string
output[4] = '\0';
Lcd_Out(1,37,output);
return;
}
void display_timing(){
// Timing
// Unit: Degrees
// Calculate the input byte in hex to a decimal value to be displayed
// Format: ((data_byte[x]*offset value)+scalar value) = value in decimal to be displayed.
// Save the calculated value in the conversion buffer
conversion_buffer = ((data_bytes[1]*scalar[5])+offset[5]);
// Convert the value to a character string to the greatest whole number
BytetoStr(conversion_buffer, output);
// Display the data on the LCD at Row 2, Column 37
Lcd_Out(2,37,output);
// Add the degree symbol after the data
// Character code is from LCD datasheet = 0xDF
// Shows as German letter esszett below
Lcd_Out(2,40,"ß");
return;
}
void oil_pressure_alarm(){
PWM1_Start(); // Turn on piezo speaker
Lcd_Out(1,11,warning); // Output warning message **** on LCD
return;
}
void knock_volts_alarm(){
PWM1_Start(); // Turn on piezo speaker
Lcd_Out(1,31,warning); // Output warning message **** on LCD
return;
}
void overboost_alarm(){
PWM1_Start(); // Turn on piezo speaker
Lcd_Out(2,1,warning); // Output warning message **** on LCD
return;
}
void check_alarm(){
switch (alarm_identifier){
case 0: // No alarm. Silence active alarms and exit
PWM1_Stop(); // Turn off PWM output to RC2
alarm_timer = 0; // Ensure that the timer is cleared
break;
case 1: // Low oil pressure
oil_pressure_alarm();
break;
case 2: // High knock volts
knock_volts_alarm();
break;
case 3: // Overboost
overboost_alarm();
break;
default:
return;
}
}
void refresh_display(){ // This function will refresh the background labels on the LCD
if (alarm_timer == 0){ // If alarm_timer has reached 0
pid_count=pids; // Reset pid_count
write_pids(); // Redisplay the background labels
alarm_identifier = 0; // Set alarm_identifier to 0
PORTC.B0 = 0; // Ensure that RC0 is cleared
}
else // If alarm_timer is != 0 then exit the function
return;
}
void alarm_time_out(){ // Function to allow alarm to persist after condition is cleared
if (alarm_timer > 0 ){ // If there is an active alarm decay the alarm_timer variable until 0
-- alarm_timer;
PORTC.B0 = ~PORTC.B0; // Invert RC0 to either illuminate or extinguish the LED
refresh_display(); // Once 0 is reached refresh the background messages if not then carry on
}
else alarm_identifier = 0; // If alarm_timer == 0 them clear alarm by setting alarm identifier to 0
return;
}
////////////////////////////////////////////////////////////////////////////////
// Main program
////////////////////////////////////////////////////////////////////////////////
void main(){
////////////////////////////////////////////////////////////////////////////////
// Initial setup
////////////////////////////////////////////////////////////////////////////////
Delay_ms(2000); // 2 second delay to ensure voltage is stable
TRISA = 0x00; // Set all of PORTA to outputs
TRISB = 0x00; // Set all of PORTB to outputs
TRISC = 0x80; // Set RC(7) as input rest as outputs
TRISD = 0x00; // Set all of PORTD to outputs
TRISE = 0x03; // Set RE(0:1) as input rest as outputs
ANSEL = 0b0110000; // Configure AN(5:6) as analog and the rest as digital I/O
ANSELH = 0x00; // Configure the rest of the analog pins to digital I/O
C1ON_bit = 0; // Disable comparators
C2ON_bit = 0; // Disable comparators
UART1_Init(baud_rate); // Initialize UART module at 19200 bps
Delay_ms(100); // Wait for UART to stabilize
PWM1_Init(5000); // Init PWM output @ 5kHz on RC2
PWM1_Set_Duty(128); // Set PWM for 50% duty cycle
alarm_identifier = 0; // Set alarm identifier to 0
PORTA = 0; // Clear all ports
PORTB = 0;
PORTC = 0;
PORTD = 0;
PORTE = 0;
Lcd_Init(); // Initialize LCD for 4-bit communications
Lcd_Cmd(_LCD_CLEAR); // Clear display
Lcd_Cmd(_LCD_CURSOR_OFF); // Cursor off
write_pids(); // Write the background PIDS and prepare for data
////////////////////////////////////////////////////////////////////////////////
// Main Loop
////////////////////////////////////////////////////////////////////////////////
while(1){ // Infinite loop
if(UART1_Data_Ready()) { // Is there a byte in the UART data buffer?
data_temp = UART1_Read(); // If so then read the UART data buffer
// PORTA = RCSTA; // **debug display RCSTA on PORTA (LED array) to show serial port status
// PORTB = data_temp; // **debug display raw hex on PORTB (LED array) showing input serial data
if (data_temp == 0x55){ // Header is received and need to gather data bytes
stream_counter = stream_size; // Set counter to prepare for first data byte
}
else{ // If a header has not been received then the byte is usable data
fill_array(); // Fill the data array with all data bytes and the checksum in (data_bytes[0])
}
while (stream_counter == 0) { // All bytes have been filled now it is time to manipulate and display them
calculate_checksum(); // Verify that the last byte of the stream == the sum of data_bytes[5:1]
if(checksum == data_bytes[0]) { // If checksum is correct proceed to manipulate and display the data
// If checksum is not correct fall off the end and reset
// the stream_counter to break the while loop also check oil and fuel pressures
display_AFR(); // Display AFR on the LCD
display_engine_load(); // Display load in kPa on the LCD
display_boost_vac(); // Display boost or vacuum condition on the LCD
display_injector_duty(); // Display injector duty cycle on the LCD
display_knock_volts(); // Display knock volts on the LCD
display_timing(); // Display timing degrees on the LCD
}
stream_counter = stream_size; // Reset stream_counter so that it breaks the loop
get_fuel_pressure(); // Get and display fuel pressure from analog input
get_oil_pressure(); // Get and display oil pressure from analog input
check_alarm(); // Check if an alarm condition has been set
alarm_time_out(); // Sound and display alarm for a specified time after cleared
}
}
else; // No byte is ready to be read CLRWDT and loop back around
asm{CLRWDT}; // Clear the watchdog after every pass if not then reset
}
}