walkertexan
Published © CC BY-NC-SA

ALDL Scantool for 87-88 Fiero and possibly similar vehicles

This is an Arduino Nano based scan tool designed to work with 8192 baud ALDL (OBD1) systems in the 87-88 Fiero.

IntermediateFull instructions provided10 hours626
ALDL Scantool for 87-88 Fiero and possibly similar vehicles

Things used in this project

Hardware components

Arduino Nano
×1
SSD1306 0.9 128x64
×2
2N2222A NPN transistor
or equivalent
×4
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
or equivalent
×3
Through Hole Resistor, 1 kohm
Through Hole Resistor, 1 kohm
1/8W or greater
×3
Through Hole Resistor, 3.3 kohm
Through Hole Resistor, 3.3 kohm
1/8W or greater
×1
Through Hole Resistor, 10 kohm
Through Hole Resistor, 10 kohm
1/8W or greater
×9
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
very low current required so you may pick any switch you desire. I used snap-style PCB switches for mine.
×3
perf board
or equivalent. You can make your own PCB if you choose.. I just quickly made mine using point-to-point wiring because I was lazy.
×1

Story

Read more

Schematics

ALDL Scan Tool Schematic

Allows you to build the hardware and understand the circuit.

Design Notes

Detailed information about the hardware and software along with the mask file data for the 87-88 Fiero

ALDL Scan Tool Schematic in JPG format

ALDL Scan Tool Schematic in BMP format

ALDL Scan Tool Schematic - dummy file

I don't want to make another schematic in another tool for no good reason

Code

Fiero_ALDL_Scan_Tool.ino

Arduino
//FIERO ALDL SCAN TOOL FOR 1987-88 ECMs
//Designed by Michael Walker
//Burleson, Texas
//Copyright 2022-2023. 

//Revision A: Improved the O2 voltage value by multiplying by 0.0044 instead of dividing by 226 so that smaller
//            values would not be returned as 0V.

//Free and Open Source Rights: This hardware and firmware design is provided without cost or renumeration of
//any kind and may used for any purposes with the following restrictions;
//1. The design is provided with no warranty expressed or implied and you use this design at your own risk.
//    The author assumes no responsibility for the use of it or of any consequences.
//2. The design may not be sold or used for ANY COMMERCIAL use what so ever! It is free to anyone and no one
//    may use the design for their own monetary or personal benefit. It is for the general automobile
//    community at large and requires no compensation.
//3. The design may be altered as you desire for your own use however ANY DISTRIBUTION of the changed design
//    MUST credit the original author and the altered design may NOT be sold or used for ANY COMMERCIAL use.


//The ALDL Scan Tool is designed to work with the 87-88 Pontiac Fiero and other GM vehicles that use the 
//1227748 ECM (or similar). This code is specific to the Fiero 2.5 and other cars that use the 2.5 Iron
//Duke engine and Isuzu 5 speed manual transaxle. It is easily modified to include the values, such as
//TCC for an automatic transaxle. See the DESIGN NOTES document for details.

//NOTE that this hardware and program will work for virtually any GM ECM that utilizies the 8192
//Baud rate and protocol; the difference between the 2.5 Fiero and other usages is in the meaning of the data
//returned. There are several websites available that detail the data coding for various engines/cars, such as
//the websites devoted to "TunerPro" and other tools that should help you to adapt this to other uses. I am not
//involved in the TunerPro and TunerPro RT products in any way.

//Disclaimer:
//To be honest, I was only interested in the Fiero because this is my personal car. I have used and definitely
//recommend TunerPro RT for analysis and repair of GM cars; frankly, it is a superior tool to mine in the fact
//that it can be used for extreme tuning and datalogging which I do not support with this tool. The disadvantage
//of TunerPro is the need for a laptop (or in some implementations a cell phone app) to use it.  This is fine for
//analysis at home in the comfort of your garage yet I wanted a tool that could travel with the car and not
//require much else for diagnosis. Plus, I'm an electrical engineer with decades of hardware and firmware design
//experience and wanted to play. Admittedly this is a somewhat crude adaptation tool because I didn't
//want to spend a lot of time and money on something I considered a "nice to have" and I wanted it to be cheap and
//easy enough for a person with average electronic skills to build and program should they want one as well. Most
//people have a need to diagnose failures and little else so this is perfect for that. Also beats reading "blinks"
//when you need to know what code set off the "check engine" or "service engine soon" lamp.

//The design is based on the readily available Arduino NANO simply because the ARDUINO series of microcontrollers
//are so easy to use and understand for the average person interested in electronics. The "Wiring" language, although
//based on the 'C' language, is quite simple and the available libraries make it an exceptional tool for the beginner
//and expert as long as the application doesn't require a high-speed implementation. Serial communications rarely
//if ever, warrants performing a "bare-metal" high speed implementation so use of something more difficult and 
//expensive, such as a Zynq processor, etc. isn't warranted and is beyond most hobbyists. I have literally 35+
//years experience with those systems and could have used them...but again, cheap and easy is the idea. I wanted
//others to have fun with this as well. Other Arduino devices could also be used as long as you modify the code to
//use the definitions required for the other device. At the time the NANO was the smallest, simplest, cheapest,
//available from dozens of sources and is more than up to the task.

//The design "as is" will allow you to communicate with the 8192 baud rate ECMs; only the routines that display
//the data in a "human-readable" format will need to be revised for your car's format which is simple once you study
//how it works and have the definition file for your car...which is often available from the TunerPro website (and others).


#include <Arduino.h>
#include <U8x8lib.h>
#include <Wire.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#define MS 1 //menu/select button
#define UP 2 //Cursor Up button
#define DN 3 //Cursor Down button

//The U8x8 library is part of the U8g2 library and is text-only operation. This prevents using a graphics
//buffer for each display which takes up virtually all of the NANO RAM! Text is obviously better for this.
//The Adafruit_SSD1306 library is more versatile EXCEPT it has a bug that prevents using more
//than one display!  U8x8 is the way to go for this project.
//Define two displays:
U8X8_SSD1306_128X64_NONAME_SW_I2C TOP(/* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // OLEDs without Reset of the Display
U8X8_SSD1306_128X64_NONAME_SW_I2C BOTTOM(/* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // OLEDs without Reset of the Display


//The Nano uses Asynchronous Double Speed mode by default for the serial port.  This means the baud rate
//register equation is 16Mhz / (8 x baudrate) -1.  To get 8192 baud the value of 243 must be written to
//the baud rate register UBRRn.  The Arduino header file defines the register with a macro as UBRR0.
//After experimenting with this I was able to prove that using 8192 directly in the Serial.begin() command
//accurately programs the baud rate without any additional manipulation.

//Data for the xmit message to the Fiero ECM.
// 0xF0; //start message, LSN is message ID. Using 0 every time to simply the code by not generating IDs.
// 0x56; //# of message words (1) + 85 per GM protocol. 86 = 0x56. NOTE: GM refers to each BYTE as a WORD!
// 0x01; //the data word which contains Mode 1 (10K) mode which includes all data from ECM
// 0xB9; //2's complement of checksum
//This is stored in outbuffer.  Array inbuffer contains the raw data returned from the Fiero ECM.


//Many Global variables are used because they are shared with multiple routines. 
//Normally I avoid using globals because it is bad practice...but in this case it seemed clearer than passing
//the variables with each call. This makes it much easier for an amateur programmer, i.e. a 
//hobbyist to follow. The program is small enough that optimization isn't necessary so this approach is fine.
//All of the "expert" programmers out there can refrain from critiquing the code because I know. I made it simple
//to understand ON PURPOSE. I could have written it in Assembly in my sleep but that helps no one else.
//FYI I taught Firmware Development in after-hours college level classes for several years and I've heard enough
//"critique" from "experts" in forums to last a lifetime. Accept it as-is; I have no wish to revise it to someone
//else's opinion since it works quite well. This was a fun experiment, don't try to make it hard.
byte outbuffer[5]={0xF0,0x56,0x01,0xB9}, inbuffer[130], checksum;
int button, buttonRef, menuVal, upVal, downVal, change, Cursor, current_display;
int tenK, F_RT, Raw, start, prior, bytecount,m, I, found, cs_flag;
unsigned long st_time, end_time, ms_loop;

//string constants that are stored in Flash. The "F("") encapsulation tells the compiler/linker to place 
//the data between the quotation marks into Flash memory.
#define BLANKLINE F("                ")
#define Y F("Y")
#define N F("N")
#define SPACE F(" ")
#define ZERO F("0")

//Interrupt service routine for the three buttons: menu/select, cursor up and cursor down
//For simplest debounce logic, the buttons function by holding them down until you see a change on the display(s).
//Releasing the button "resets" the logic such that the next press can be entered. A little slow but speed
//isn't really required for this and a proper debounce circuit would have added a great deal to the size of the
//circuit for little improvement. I was going for quick design, simple, and cheap.
//The interrupt is released when the button is released after the change is seen on the display(s)
void buttonISR()
{
  int i, count[10], strikeMS =0, strikeUP=0, strikeDN=0;
  
  
  //initialize button to 0
  button = 0;
  noInterrupts();//stop button presses generating interrupt. Will be enabled after change is processed.
 
  //get 10 samples of the button pressed
  for(i = 0; i<10;i++) count[i] = analogRead(A0);//read 10 voltage divider samples
  //get the strike count for each sample; the most samples equals the correct button
  for(i=0; i<10; i++)
  {
    if (count[i] < menuVal) strikeMS++;
    if ((count[i] > menuVal) && (count[i] < upVal)) strikeUP++;
    if ((count[i] > upVal) && (count[i] < downVal)) strikeDN++;
  }

   
   if ((strikeMS > strikeUP) && (strikeMS > strikeDN)) button = MS;
   if ((strikeUP > strikeMS) && (strikeUP > strikeDN)) button = UP;
   if ((strikeDN > strikeMS) && (strikeDN > strikeUP)) button = DN;
 
  //set the change request flag
  change = 1;
}


//The Main Menu appears on the top display and is provided by this routine. The menu provides the user several
//options prior to requesting data from the ECM. 

//10K Mode: The Fiero ECM provides data in three defined packet structures (others may use more modes): Mode 0
//(22 bytes of data), Mode 1 (63 bytes of data) and Mode 2 (22 bytes of data). Mode 0 is requested using an open
//circuit on pin M of the ALDL. Mode 1 is provided by connecting a 10K resistor on pin M. Mode 2 is requested 
//using a short circuit on pin M. I chose to implement Mode 1 only because it provides the most data using the 
//10K resistor. The menu provides the option to select the 10K mode or open mode although
//I have not programmed any code for open so this is a future option if you choose. Default is 10K mode

//Frame/Realtime Mode: This allows the user to request a single frame of data or a continuous stream of frames
//in "real time". Single frame mode means you must return to the menu and request another frame of data when
//you are ready. Realtime mode means once you request data frames the dat will continue and update the displays
//every few seconds until you select to return to the menu. NOTE that "real time" is a misnomer because the ECM
//has a latency on most data of 1 to 1.5 seconds so you won't actually get data in real time but it will be
//updating automatically in that mode.  Realtime mode is the default.

//Raw Data Mode: This is mainly a debugging tool yet it is useful if you wish to learn the protocol of the data.
//This selection displays the 66 bytes returned from the ECM in a single frame for debugging purposes. You can
//also use this to see that the ECM is actually returning data. The buffer is cleared to all zeros prior to 
//receiving data so displays of all zeros indicate no data was returned. A proper frame will start with the 
//protocol "echo" bytes of "F0 95 01" followed by the data that defines the engine management values. These 
//bytes are then converted to the physical values that are "human readable" in the other modes but here they are
//shown as the computer "sees" them. The default is NO to this mode. The checksum, calculated checksum and the
//result, either OK or BAD are at the bottom of the display.

//Start: This selection executes your menu choices and begins communication with the ECM. The display will 
//indicate "GO" when executed and the displays will change to the data displays.
void menu()
{
  //This routine is called any time the Main Menu is requested
  TOP.setCursor(0,0);
  TOP.print(F(" Main Menu      \n"));
  TOP.print(F(" Use 10K:"));
  if(tenK)
    TOP.print(F("Y      \n"));
  else
    TOP.print(F("N      \n"));
    
  TOP.print(F(" Frame/RT:"));
  if(F_RT)
    TOP.print(F("RT    \n"));
  else
    TOP.print(F("F     \n"));

  TOP.print(F(" Raw Data:"));
  if(Raw)
    TOP.print(F("Y     \n"));
  else 
    TOP.print(F("N     \n"));

  if(start)
    TOP.print(F(" Start  GO      \n"));
  else
    TOP.print(F(" Start          \n"));

    //clear remaining 3 lines
    TOP.setCursor(0,5);
    TOP.print(BLANKLINE);
    TOP.setCursor(0,6);
    TOP.print(BLANKLINE);
    TOP.setCursor(0,7);
    TOP.print(BLANKLINE);

  //make sure Cursor value is within limits.
  if (Cursor < 0) Cursor = 0;
  if (Cursor > 3) Cursor = 3;
  //clear any previous cursor from display
  TOP.setCursor(0,Cursor);
  TOP.print(SPACE);
  //now print the current cursor on the display
  TOP.setCursor(0,Cursor + 1);//this should initialize cursor at Start based on initial Cursor value in setup
  TOP.print(F(">")); //use this as a cursor.

  BOTTOM.clearDisplay();
  current_display = 0; //indicate Main Menu in value
}


//The setup() routine is the standard Arduino process for initializing functions and hardware settings. At 
//completion the routine calls the Main Menu.
void setup()
{
  pinMode(2, INPUT_PULLUP);//interrupt input for buttons.
  pinMode(3, OUTPUT);//RX control, LOW to activate, HIGH for TX. Connected to digital pin 3.
  digitalWrite(3, HIGH); //initialize RX to OFF, TX mode.
  
  pinMode(4, OUTPUT);//10K resistor is connected to digital pin 4.
  change = 0; //clear the change request flag
  Cursor = 3; //set the Cursor position value for "Start" menu item
  current_display = 0; //set to Main Menu value
  button = 0; //clear button so that none are indicated
  tenK = 1; //default 10K use to Yes
  F_RT = 1; //default Frame/Realtime to Realtime
  Raw = 0; //default raw data to No.
  start = 0; //default start communication to No
 

//MUST SET THIS UP TO GET BOTH DISPLAYS TO FUNCTION CORRECTLY!!!!!!
//IF YOUR DISPLAYS HAVE DIFFERENT ADDRESSES THEN MODIFY THESE VALUES ACCORDINGLY.
//NOTE THAT *SOME* ARE MARKED WITH 0X3C AND 0X3D WHICH IS HALF OF THESE VALUES, I.E. 0X78 / 2 = 0X3C.
//THAT IS BECAUSE I2C WAS ORIGINALLY A 7 BIT INTERFACE AND LATER UPDATED TO 8 BIT, I.E. A 1 BIT SHIFT IN BINARY
//IS THE SAME AS MULTIPLYING BY 2. ANY I2C DEVICE CAN WORK WITH THIS BUT IT IS UP TO THE PROGRAMMER TO DETERMINE
//WHICH ADDRESSING METHOD YOU USE! IT IS EASIER IF YOU USE TWO IDENTICAL DEVICES RATHER THAN TRYING TO USE
//DIFFERENT DISPLAYS SINCE IT MAY COMPLICATE PROGRAMMING. YOU will NEED TWO VALID ADDRESSES SO YOU WANT A DISPLAY
//THAT SUPPORTS PLACING IT ON UNIQUE ADDRESSES, USUALLY THROUGH A SOLDER JUMPER OR MOVING A RESISTOR/JUMPER.
//ONCE YOU HAVE ONE DISPLAY WORKING YOU WILL NEED TO MODIFY THE SECOND TO BE ON A DIFFERENT ADDRESS.
// Mine required 0x78 and 0x7A to work despite being labelled as 0x3C and 0x3D on the board silkscreen.
  TOP.setI2CAddress(0x78);
  BOTTOM.setI2CAddress(0x7A);

  TOP.begin();
  TOP.setPowerSave(0);
  TOP.setFont(u8x8_font_chroma48medium8_r);

  BOTTOM.begin();
  BOTTOM.setPowerSave(0);
  BOTTOM.setFont(u8x8_font_chroma48medium8_r);
  
//Set USART to Fiero ECM baud rate of 8192
  Serial.begin(8192);

  //calibrate the button voltage divider so that variances in 5V will be ignored.
  //Menu-Select should always be 0 so any small value will work for that button.
  //Cursor Up uses a 1K to 1K ratio so any value approximating half-scale plus tolerance is OK.
  //Cursor Down uses a 3.3K to 1K so any value approximating one-third scale plus tolerance is OK.
   buttonRef = analogRead(A0);

  menuVal = 100; //if less than 100 then MS was pressed.
  upVal = buttonRef / 1.9; //if less than this value then Cursor Up was pressed
  downVal = buttonRef / 1.2; //if less than this value then Cursor Down was pressed
  attachInterrupt(digitalPinToInterrupt(2), buttonISR, FALLING);
  interrupts(); //enable the interrupt

   //Initialize the displays using Main Menu routine
  menu();
}



 //The Arduino main loop will monitor the change request flag.  If the button ISR sets the flag then the loop
 //will process the changes needed.  The Menu/Select button will either bring up the Main Menu or will
 //select a choice if the Main Menu is already being displayed.  The Cursor Up/Down buttons will scroll
 //through the display screens as required until the Menu/Select button is pressed and therefore reverting
 //to the Main Menu.  The Cursor Up/Down will move the cursor on the Main Menu to the available choices and
 //pressing Menu/Select will act on the value that is indexed by the Cursor.
void loop()
{
  
 if(change)
 {
   noInterrupts();

   if(button == UP) Cursor--;
   if(button == DN) Cursor++;

   if((current_display != 0) && (button == UP))
   {
      current_display--;
      if (current_display < 1) current_display = 1; //go to previous display of data or stay on first
   }
   if((current_display != 0) && (button == DN))
   {
      current_display++;
      if (current_display > 3) current_display = 3; //go to next display of data or stay on last
   }

   //if already on Main Menu then update Cursor position
   if((current_display == 0) && (button != MS)) menu();

   //if on Main Menu and MS pressed then choice was selected therefore evaluate it
   if((current_display == 0) && (button == MS))
   {
      switch (Cursor)
      {
        case 0:
          if(tenK) tenK = 0; else tenK = 1; //toggle 10K resistor setting
          break;
        case 1:
          if(F_RT) F_RT = 0; else F_RT = 1; //toggle Frame/Realtime setting
          break;
        case 2:
          if(Raw) Raw = 0; else Raw = 1; //toggle Raw Data setting
          break;
        case 3:
          if (start) start = 0; else start = 1; //flag to start communication
          break;
      }
      menu(); //call menu to update the selection
   }

   //if on any display other than Main Menu, then the MS button returns to the menu
   if((current_display != 0)&& (button == MS))
   {
    start = 0;//stop collecting data
    Cursor = 3;//set to "start"
    menu();
   }

   //Connect 10K resistor low side to return if selected else let it float to 5V
   if(tenK)
    digitalWrite(4,LOW);//connect it
   else
    digitalWrite(4, HIGH);//disable it


  change = 0; //clear the change request flag
  interrupts(); //enable the button interrupt
 } //end of if(change)

  //get the data from the ECM, either once for Frame or continuously for Realtime (default).
  //the logic of this section allows the data to be updated continuosly as long as the "start" flag
  //is set. It will only display the Raw data or the single frame display ONCE because the start flag
  //is cleared for those choices after the data is gathered.
  if(start)
  {
    found = 0; //clear the "data found in buffer" flag
    while(found < 3) //while loop allows three tries to find the data in the buffer
    {
      //flush the receiver buffer
      for(bytecount = 0; bytecount < 130; bytecount++) inbuffer[bytecount] = 0;
      m = 0; //index for where the data is stored in the buffer  

      digitalWrite(3, HIGH); //disable RX so transmit can occur
      if(Serial.available() > 0) Serial.read(); //clear out the Serial library input buffer to prevent any garbage
      Serial.write(outbuffer, 4);//send the command to the Fiero ECM
      digitalWrite(3, LOW); //enable RX
      //now get the data in the huge buffer and parse the data into the beginning of the buffer.
      //The first three bytes should be F0 95 01.
      Serial.readBytes(inbuffer, 130);//receive command echo followed by 63 data bytes in a HUGE buffer 
      for(bytecount = 0; bytecount < 64; bytecount++) //need to find it before the last 66 bytes of the buffer
      {
         if((inbuffer[bytecount] == 0xF0)&&(inbuffer[bytecount+1] == 0x95)&&(inbuffer[bytecount+2] == 0x01))
        {
          m = bytecount;
          found = 3;//found it, get out
          break;
        }
      }
      found++; //increment attempt counter. Bogus data = timeout. Aids debugging when not connected to the ECM.
    }// end of while(found < 3)...
    
    digitalWrite(3, HIGH); //disable RX to stop ECM data from coming in while processing the data
    //now use index "m" to move the data to the beginning of the buffer. Include one extra byte that should contain
    //the ECM checksum.
    for(bytecount = 0; bytecount < 67; bytecount++)
    {
      inbuffer[bytecount] = inbuffer[bytecount + m];
    }

    //accumulate the checksum of all the data bytes except the checksum but including the header.
     checksum = 0;
     cs_flag =1;// force it to BAD until proven good
    for(I = 0; I<65; I++) //add all EXCEPT checksum
    {
      checksum+=inbuffer[I];
    }
    checksum&=0xFF;//clear overflow so that value is 0xFF or less
    if(((0xFE - inbuffer[66])&0xFF) == checksum) cs_flag = 0;//if good, clear the flag
    
    if(current_display == 0) current_display = 1; //if on main menu, set to first data display  
  if ((F_RT == 0)||(Raw == 1)) start = 0; //single frame, stop getting data after first set
  //determine the data to display based on the current_display value with 1 = to the first data.
  //These are updated in "real time".
  //NOTE: if you wish to add more displays then modify the "switch" statement to add calls after
  //the "case 3" block. You will then need to create the routines that contain your new displays.
  //See the next section and the functions later in this file for examples of how to write those routines.
  switch(current_display)
  {
    case 0://do nothing for main menu.  Shouldn't happen so this is just an error catch-all
     break;  
    case 1:
      if(Raw) raw_data(); else first_data(); //display either first data or raw data on both displays
      prior = 1; //save the last choice for the F_RT processing
      break;
     case 2:
      second_data(); //display second set of data on both LED displays
      break;
     case 3:
      third_data(); //display third set of data on both LED displays
      break;
  }
  } //end of if(start)

  //this next logic allows the Frame data displays to be shown.  The previous logic will only show the 
  //first data display because the start flag is cleared thereby preventing the calling of the three display
  //choices.  This logic is triggered by the F_RT flag so that all three data sets can be rotated through
  //the screens. Not the best way to do this yet I didn't want to add "while(1)" statements and
  //the error trapping that would require on such a simple program.

  if((F_RT == 0) && (current_display != 0)) //Frame mode selected
  {
   //determine the data to display based on the current_display value with 1 = to the first data.
   //These are not updated, they only display the previously acquired data again.  You must enter the
   //Main Menu again to acquire new data in single Frame mode.

   //Do not re-print the display if the current_display is the same as the prior display. Only print the
   //display again if the current_display changed.  This will prevent "flickering" of the screen.
   
   //NOTE: if you wish to add more displays then modify the "switch" statement to add calls after
   //the "case 3" block here as well.

   if (current_display != prior)
   {
      switch(current_display)
      {
        case 0://do nothing for main menu.  Shouldn't happen so this is just an error catch
        break;  
        case 1:
          first_data(); //display either first data or raw data on both displays
          prior = current_display; //save the selection for the next loop
        break;
        case 2:
          second_data(); //display second set of data on both LED displays
          prior = current_display; //save the selection for the next loop
        break;
        case 3:
          third_data(); //display third set of data on both LED displays
          prior = current_display; //save the selection for the next loop
        break;
      }//end of switch(current_display)
   } //end of if(current_display  != prior)
  } //end of if(F_RT == 0)

}// end of main loop

//Routine first_data() displays the first 16 lines worth of the calculated values from the Fiero ECM.
//Each display contains 8 lines and EACH LINE IS 16 CHARS LONG with the first character at position 0l
//Values are calculated from the data returned in "inbuffer".
void first_data()
{
  TOP.setCursor(0,0); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
  if(cs_flag !=0) //if not 0 then a data error occurred because the checksum was wrong
  {
    TOP.print(F("***DATA ERROR***"));    
  }
  else //checksum was good, display the new data
  {
    TOP.print(F("DTC SET?        "));
    TOP.setCursor(11,0);
    //check to see if any DTC values are set and if so, then tell me
    if ((inbuffer[5] == 0) && (inbuffer[6] == 0) && (inbuffer[7] == 0))
      TOP.print(F("NO  "));
    else
      TOP.print(F("YES!"));
  
    TOP.setCursor(0,1); 
    TOP.print(F("CTS            F"));
    TOP.setCursor(4,1);
    TOP.print(inbuffer[8] * 1.35 -40);
    TOP.setCursor(0,2);
    TOP.print(F("TPS            V"));
    TOP.setCursor(4,2);
    TOP.print(inbuffer[10] * 0.0196);
    TOP.setCursor(0,3); 
    TOP.print(F("RPM             "));
    TOP.setCursor(4,3);  
    TOP.print(inbuffer[12] * 25);
    TOP.setCursor(0,4); 
    TOP.print(F("Tgt RPM         "));
    TOP.setCursor(8,4);
    TOP.print(inbuffer[25] * 12.5);
    TOP.setCursor(0,5); 
    TOP.print(F("O2             V"));
    TOP.setCursor(3,5);
    TOP.print(inbuffer[17] * 0.0044); // value/226, multiply by inverse so > 0 for all returned values.
    TOP.setCursor(0,6);
    TOP.print(F("O2 R/L Cnt      "));
    TOP.setCursor(11,6);
    TOP.print(inbuffer[18]);
    TOP.setCursor(0,7);
    TOP.print(F("RICH/LEAN      "));
    TOP.setCursor(10,7);
    if((inbuffer[57] & 0x40) == 0x40)
      TOP.print(F("Rich"));
    else
      TOP.print(F("Lean"));

    //now the bottom display
    BOTTOM.setCursor(0,0); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
    BOTTOM.print(F("O2 RDY          "));
    BOTTOM.setCursor(14,0);
    if((inbuffer[63] & 0x01) == 0x01)
      BOTTOM.print(Y);
    else
      BOTTOM.print(N);
    BOTTOM.setCursor(0,1); 
    BOTTOM.print(F("Closed Loop     "));
    BOTTOM.setCursor(14,1);
    if((inbuffer[57] & 0x80) == 0x80)
      BOTTOM.print(Y);
    else
      BOTTOM.print(N);
    BOTTOM.setCursor(0,2);
    BOTTOM.print(F("BLM             "));
    BOTTOM.setCursor(4,2);
    BOTTOM.print(inbuffer[20]);
    BOTTOM.setCursor(0,3); 
    BOTTOM.print(F("INT             "));
    BOTTOM.setCursor(4,3);
    BOTTOM.print(inbuffer[22]);
    BOTTOM.setCursor(0,4); 
    BOTTOM.print(F("BARO           V"));
    BOTTOM.setCursor(6,4);
    BOTTOM.print(inbuffer[27] * 0.0196);
    BOTTOM.setCursor(0,5); 
    BOTTOM.print(F("MAP(P)         V"));
    BOTTOM.setCursor(8,5);
    BOTTOM.print(inbuffer[28] * 0.0196);
    BOTTOM.setCursor(0,6);
    BOTTOM.print(F("MAPld        KPa"));
    BOTTOM.setCursor(7,6);
    BOTTOM.print(inbuffer[29] / 3.2);
    BOTTOM.setCursor(0,7);
    BOTTOM.print(F("MAT            F"));
    BOTTOM.setCursor(5,7);
    BOTTOM.print(inbuffer[32] * 1.35 -40);
   }

}

//Routine second_data() displays the second 16 lines worth of the calculated values from the Fiero ECM.
//Values are calculated from the data returned in "inbuffer".
void second_data()
{
  int index, tempval;

  if(cs_flag !=0) //if not 0 then a data error occurred because the checksum was wrong
  {
    TOP.print(F("***DATA ERROR***"));    
  }
  else //checksum was good, display the new data
  {
  
    TOP.setCursor(0,0); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
    TOP.print(F("IAC now         "));
    TOP.setCursor(10,0);
    TOP.print(inbuffer[23]);
    TOP.setCursor(0,1); 
    TOP.print(F("IAC Tgt         "));
    TOP.setCursor(10,1);
    TOP.print(inbuffer[24]);
    TOP.setCursor(0,2);
    TOP.print(F("BATT           V"));
    TOP.setCursor(6,2);
    TOP.print(inbuffer[34] / 10);
    TOP.setCursor(0,3); 
    TOP.print(F("BATT Overvolt?  "));
    TOP.setCursor(15,3);  
    if((inbuffer[7] & 0x04) == 0x04)
      TOP.print(Y);
    else
      TOP.print(N);
    TOP.setCursor(0,4); 
    TOP.print(F("Spk Adv        D"));
    TOP.setCursor(9,4);
    TOP.print(inbuffer[35] * 0.3515);
    TOP.setCursor(0,5); 
    TOP.print(F("Spk Adv         "));
    TOP.setCursor(9,5);
    tempval = inbuffer[36] * 256 + inbuffer[37];
    if((inbuffer[36] & 0x80) == 0x80) //negative therefore retarded timing
      TOP.print((65536 - tempval) * 0.3516);
    else
      TOP.print(tempval * 0.3516); //positive therefore advanced timing
    TOP.setCursor(0,6);
    TOP.print(F("BPW           ms"));
    TOP.setCursor(5,6);
    TOP.print((inbuffer[38] * 256 + inbuffer[39]) / 65.536);
    TOP.setCursor(0,7);
    TOP.print(F("Tgt A/F         "));
    TOP.setCursor(9,7);
    TOP.print(inbuffer[40] / 10);

    //now the bottom display shows the DTCs that are set, up to 7 lines worth.
    BOTTOM.setCursor(0,0); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
    BOTTOM.print(F("DTC set, <8     "));
    index = 1;
    if ((index < 8) && (inbuffer[5] & 0x01))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("24 VSS          "));
      index++;
    }
    if ((index < 8) && (inbuffer[5] & 0x02))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("23 MAT Low      "));
      index++;
    }
    if ((index < 8) && (inbuffer[5] & 0x04))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("22 TPS Low      "));
      index++;
    }
    if ((index < 8) && (inbuffer[5] & 0x08))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("21 TPS High     "));
      index++;
    }
    if ((index < 8) && (inbuffer[5] & 0x10))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("15 CTS Low      "));
      index++;
    }
    if ((index < 8) && (inbuffer[5] & 0x20))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("14 CTS High     "));
      index++;
    }
    if ((index < 8) && (inbuffer[5] & 0x40))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("13 O2 Open      "));
      index++;
    }
    if ((index < 8) && (inbuffer[5] & 0x80))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("12 No Ref Pulse "));
      index++;
    }
    if ((index < 8) && (inbuffer[6] & 0x01))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("42 EST Monitor  "));
      index++;
    }
    if ((index < 8) && (inbuffer[6] & 0x04))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("35 IAC          "));
      index++;
    }
    if ((index < 8) && (inbuffer[6] & 0x08))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("34 MAP Low      "));
      index++;
    }
    if ((index < 8) && (inbuffer[6] & 0x10))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("35 MAP High     "));
      index++;
    }
    if ((index < 8) && (inbuffer[6] & 0x80))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("25 MAT High     "));
      index++;
    }
    if ((index < 8) && (inbuffer[7] & 0x01))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("55 SBus Error   "));
      index++;
    }
    if ((index < 8) && (inbuffer[7] & 0x10))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("51 PROM Error   "));
      index++;
    }
    if ((index < 8) && (inbuffer[7] & 0x20))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("45 O2 Rich      "));
      index++;
    }
    if ((index < 8) && (inbuffer[7] & 0x40))
    {
      BOTTOM.setCursor(0, index);
      BOTTOM.print(F("44 O2 Lean      "));
      index++;
    }

    //blank out any remaining lines
      for (tempval = index; tempval < 8; tempval++)
      {
        BOTTOM.setCursor(0, tempval);
        BOTTOM.print(BLANKLINE);  
      }
  }
} //end of second_data

//Routine third_data() displays the third 16 lines worth of the calculated values from the Fiero ECM.
//Currently only one line is used so if you wish to display further information this would be the easiest
//place to do it. See the previous data routines for examples of how you could modify this.
//Values are calculated from the data returned in "inbuffer".
void third_data()
{
  int index, tempval;
  
  if(cs_flag != 0) //if not 0 then a data error occurred because the checksum was wrong
  {
    TOP.print(F("***DATA ERROR***"));    
  }
  else //checksum was good, display the new data
  {
    TOP.setCursor(0,0); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
    TOP.print(F("PROMID:         "));
    TOP.setCursor(10,0);
    TOP.print(inbuffer[3] * 256 + inbuffer[4]);
    //blank out any remaining lines
      for (tempval = 1; tempval < 8; tempval++)
      {
        TOP.setCursor(0, tempval);
        TOP.print(BLANKLINE);  
      }
  
    BOTTOM.clear();
  }
}

//Routine raw_data() displays the data received from the Fiero ECM without any calculations.
//The lines are filled with 5 bytes each in hexadecimal format and end with the checksum byte.
//The next to last line contains the calculated checksum from that last value and the bottom
//line contains the sum of the data values for comparison purposes and the results of the comparison,
//either "OK" or "BAD". This display is for debugging purposes as well as allowing you to manually
//calculate the engine management values to see if your calculations are reasonable. See the Design
//Notes or the source of your ECM data word descriptions if not using on a Fiero.
void raw_data()
{
  int index, I, J;
  
  TOP.clearDisplay();
  BOTTOM.clearDisplay();
  index = 0;

  //place data, up to 40 bytes, on top display, line by line sequentially.
  for(I = 0; I < 8; I++) //perform for each line in top display
  {
    TOP.setCursor(0,I); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
    for(J = 0; J < 5; J++) // five values per line
    {
      if (inbuffer[index] < 16) TOP.print(ZERO); //add leading 0 if only a single hex digit
      TOP.print(inbuffer[index], HEX); //print the value in hexadecimal
      TOP.print(SPACE); //add space between values
      index++;   
    }
  }      
  //place remaining data on bottom display, line by line sequentially.
  for(I = 0; I < 8; I++) //perform for each line in bottom display
  {
    if (index > 66) continue; //finished all 66 bytes + checksum, do nothing more in this loop
    BOTTOM.setCursor(0,I); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
    for(J = 0; J < 5; J++) // five values per line
    {
      if (index > 66) continue; //finished them all, do nothing more in this loop
      if (inbuffer[index] < 16) BOTTOM.print(ZERO); //add leading 0 if only a single hex digit
      BOTTOM.print(inbuffer[index], HEX); //print the value in hexadecimal
      BOTTOM.print(SPACE); //add space between values
      index++;   
      //reached the end of the data?
    }  
  }

  //display the checksum and results as OK or BAD
  BOTTOM.setCursor(0,7); //NOTE THIS FUNCTION IS COLUMN, ROW !!!
  BOTTOM.print(F("CS             "));
  BOTTOM.setCursor(3,7);
  BOTTOM.print(checksum, HEX); //print the sum in hexadecimal

  BOTTOM.setCursor(3,6);
  BOTTOM.print((0xFE - inbuffer[65]), HEX);
  //calculate the sent checksum value and evaluate whether correct or not and display results
  BOTTOM.setCursor(7,7); //position for displaying results
  if(cs_flag !=0) 
  {
    BOTTOM.print(F("BAD"));
  }
  else
  {
    BOTTOM.print(F(" OK"));
  }
  
}//end of raw_data

//end of file

Credits

walkertexan

walkertexan

3 projects • 1 follower
Retired Electrical Engineer having fun every day; or most days, anyway.

Comments