로그인회원등록 내글장바구니주문조회현재접속자
 상품 검색








 게시판 검색





 
 
회원등록 비번분실


온라인 입금계좌
ㆍ기업은행
ㆍ219-043192-01-028
ㆍ이건영

      거래은행 바로가기
 
 Sensor Applications
아듀이노 공개소스
작성자 avrtools™        
작성일 2009/10/08
첨부#1 obd-v1.zip (27KB) (Down:1120)
ㆍ추천: 0  ㆍ조회: 4169   
  OBDuino ISO9141 소스의 설명
참조 : 아듀이노 0017의 예제(Examples) 안에 들어있는 oduino_iso9141.pde
위치 : Examples -> 0bd-v1 -> obduino_iso9141
설명 : MC33290, MC33199 등을 사용하는 K line의 신호를 인터페이스 하는 OBD의 소스
 
ISO9141는 차량의 ECU에 연결된 OBD-II 커넥터로 통신하는
J1850  물리층(K Line) 인터페이스를 제어하는 논리층의 규격이다.
OBD는 차량의 ECU에서 탑재형-진단(On Board Diagnostic) 기능을 제공하는 규격이다.
 
기판을 Duemilanov ATmega328로 컴파일한 결과는 HEX 파일이 14370 바이트로,
Decimila ATmega168의 최대 업로드 용량인 14336 보다, 겨우 4 바이트가 크네요~
어딘가 줄일수 있는 1줄만 //로 막아도, 4 바이트는 압축이 가능합니다.
 
혹은 아듀이노 듀에밀라 100% 호환기판 M328-USB을 사용하여 업로드할 수 잇습니다.
 

소스 설명
// comment to use MC33290 ISO K line chip ☜ TR과 저항으로 만든 회로를 사용해도 OK
// OBDuino Copyright (C) 2008

// #define DEBUG ☜ 기판을 시험할 때는 앞부분의 //를 풀고 시험한다.
#undef int
#include <stdio.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>

// LCD Pins from mpguino ☜ LCD 표시장치의 정의 (번호는 아듀이노의 출력포트 번호이다)
#define DIPin 4 // LCD의 RS는 D4
#define DB4Pin 7 ☜ LCD 데이터는 D7
#define DB5Pin 8 ☜ LCD 데이터는 D8
#define DB6Pin 12 ☜ LCD 데이터는 D12
#define DB7Pin 13 ☜ LCD 데이터는 D13
#define ContrastPin 6 ☜ LCD의 명암을 제어하는 핀은 D6
#define EnablePin 5 ☜ LCD의 E는 D5
#define BrightnessPin 9 ☜ LCD의 조명의 밝기를 제어하는 핀은 D9
 
// LCD prototypes ☜ LCD의 서브루틴을 미리 정의
void lcd_gotoXY(byte x, byte y);
void lcd_print_P(char *string); // to work with string in flash and PSTR()  ☜ 저장된 문자를 LCD에 표시
void lcd_print(char *string);
void lcd_cls();
void lcd_init();
void lcd_tickleEnable();
void lcd_commandWriteSet();
void lcd_commandWrite(byte value);
void lcd_dataWrite(byte value);
void lcd_pushNibble(byte value);

// Memory prototypes ☜ 인수를 저장할 메모리를 정의
byte params_load(void);
void params_save(void);

// Others prototypes ☜ 문자열 변수를 정의
void int_to_dec_str(long value, char *decs, byte prec);
#define BUTTON_DELAY 250
 
// use analog pins as digital pins ☜ 아날로그 입력핀을 디지털 입력으로 사용하는 정의
#define lbuttonPin 17 // Left Button, on analog 3 ☜ 좌측 단추 A3 = D17
#define mbuttonPin 18 // Middle Button, on analog 4 ☜ 중간 단추 A4 = D18
#define rbuttonPin 19 // Right Button, on analog 5 ☜ 우측 단추 A5 = D19
#define lbuttonBit 8 // pin17 is a bitmask 8 on port C ☜ D17의 비트 마스크 값
#define mbuttonBit 16 // pin18 is a bitmask 16 on port C ☜ D18의 비트 마스크 값
#define rbuttonBit 32 // pin19 is a bitmask 32 on port C ☜ D19의 비트 마스크 값
#define buttonsUp lbuttonBit + mbuttonBit + rbuttonBit // 단추가 눌린 상태을 읽는다
byte buttonState = buttonsUp; // ☜ 단추가 눌린상태를 읽어서 안전한 곳에 저장한다.

byte brightness[]={
40,80,120,160}; // right button cycles through these brightness settings ☜ 우측 단추는 LCD의 밝기를 조절한다
#define brightnessLength 4 //array size ☜ 밝기 조절용 변수
byte brightnessIdx=1;

// for the 4 display corners ☜ LCD 창의 좌우상하의 4개소에 표시할 위치를 0~7 번호로 정의
#define TOPLEFT 0 ☜ 좌측 상단은 0
#define TOPRIGHT 1 ☜ 우측 상단은 1
#define BOTTOMLEFT 2 ☜ 좌측 하단은 2
#define BOTTOMRIGHT 3 ☜ 우측 하단은 3
#define NBCORNER 4 // with a 16x4 display you can use 8 'corners' ☜ LCD에 표시하는 위치의 갯수 4개
#define NBSCREEN 3 // 12 PIDs should be enough for everyone ☜ 모든 PID 값을 LCD에 표시할 수 없다, 동시에 3개만 가능
byte active_screen; // 0,1,2,... selected by left button ☜ 좌측 단추를 눌러서 표시할 내용을 선택
prog_char blkstr[] PROGMEM=" "; // 8 spaces, used to clear part of screen ☜ LCD 화면에 8자를 지우는 사전처리

// parameters, each screen contains n corners ☜ 1개의 LCD 화면에 표시하는 위치의 갯수를 구조식으로 정의
typedef struct {
byte corner[NBCORNER];
} screen_t;

typedef struct {
byte contrast; // we only use 0-100 value in step 20 ☜ 명암은 20 단계로 0 에서 100%를 조절한다
byte useMetric; // 0=rods and hogshead, 1=SI ☜ 미터 단위
byte perHourSpeed; // speed from which we toggle to fuel/hour ☜ 시간당 주행속도
byte vol_eff; // volumetric efficiency measured in percent ☜ 소비된 연료량
byte eng_dis; // Engine Displacement in dL ☜ 엔진 용량 (단위는 1/10 리터)
float trip_dist; // trip distance in m ☜ 주행거리
float trip_fuel; // trip fuel used in mL ☜ 연료 소비량 (단위는 1/1000 리터)
screen_t screen[NBSCREEN]; ☜ 단추에 의해 변경된 현재 표시할 화면의 번호
} params_t;

params_t params; // global parameters ☜ 광역번수
#define STRLEN  40
#define NUL     ''
#define CR      ' '  // carriage return = 0x0d = 13  ☜ 디버그 출력에서 줄바꿈
#define PROMPT  '>'
#define DATA    1  // data with no cr/prompt  ☜ CR과 prompt 없음

// for ISO9141-2 Protocol   ☜ K 라인 정의
#define K_IN    2
#define K_OUT   3

// bit period for 10400 bauds = 1000000/10400 = 96  ☜ K 라인의 통신속도를 정의
#define _bitPeriod 1000000L/10400L

// PID stuff   ☜ PID의 상수를 정의
unsigned long  pid01to20_support;
unsigned long  pid21to40_support;
unsigned long  pid41to60_support;
#define PID_SUPPORT20 0x00
#define MIL_CODE      0x01
#define FREEZE_DTC    0x02
#define FUEL_STATUS   0x03
#define LOAD_VALUE    0x04
#define COOLANT_TEMP  0x05
#define STF_BANK1     0x06
#define LTR_BANK1     0x07
#define STF_BANK2     0x08
#define LTR_BANK2     0x09
#define FUEL_PRESSURE 0x0A
#define MAN_PRESSURE  0x0B
#define ENGINE_RPM    0x0C
#define VEHICLE_SPEED 0x0D
#define TIMING_ADV    0x0E
#define INT_AIR_TEMP  0x0F
#define MAF_AIR_FLOW  0x10
#define THROTTLE_POS  0x11
#define SEC_AIR_STAT  0x12
#define OXY_SENSORS1  0x13
#define B1S1_O2_V     0x14
#define B1S2_O2_V     0x15
#define B1S3_O2_V     0x16
#define B1S4_O2_V     0x17
#define B2S1_O2_V     0x18
#define B2S2_O2_V     0x19
#define B2S3_O2_V     0x1A
#define B2S4_O2_V     0x1B
#define OBD_STD       0x1C
#define OXY_SENSORS2  0x1D
#define AUX_INPUT     0x1E
#define RUNTIME_START 0x1F
#define PID_SUPPORT40 0x20
#define DIST_MIL_ON   0x21
#define FUEL_RAIL_P   0x22
#define FUEL_RAIL_DIESEL 0x23
#define O2S1_WR1_V    0x24
#define O2S2_WR1_V    0x25
#define O2S3_WR1_V    0x26
#define O2S4_WR1_V    0x27
#define O2S5_WR1_V    0x28
#define O2S6_WR1_V    0x29
#define O2S7_WR1_V    0x2A
#define O2S8_WR1_V    0x2B
#define EGR           0x2C
#define EGR_ERROR     0x2D
#define EVAP_PURGE    0x2E
#define FUEL_LEVEL    0x2F
#define WARM_UPS      0x30
#define DIST_MIL_CLR  0x31
#define EVAP_PRESSURE 0x32
#define BARO_PRESSURE 0x33
#define O2S1_WR1_C    0x34
#define O2S2_WR1_C    0x35
#define O2S3_WR1_C    0x36
#define O2S4_WR1_C    0x37
#define O2S5_WR1_C    0x38
#define O2S6_WR1_C    0x39
#define O2S7_WR1_C    0x3A
#define O2S8_WR1_C    0x3B
#define CAT_TEMP_B1S1 0x3C
#define CAT_TEMP_B2S1 0x3D
#define CAT_TEMP_B1S2 0x3E
#define CAT_TEMP_B2S2 0x3F
#define PID_SUPPORT60 0x40
#define MONITOR_STAT  0x41
#define CTRL_MOD_V    0x42
#define ABS_LOAD_VAL  0x43
#define CMD_EQUIV_R   0x44
#define REL_THR_POS   0x45
#define AMBIENT_TEMP  0x46
#define ABS_THR_POS_B 0x47
#define ABS_THR_POS_C 0x48
#define ACCEL_PEDAL_D 0x49
#define ACCEL_PEDAL_E 0x4A
#define ACCEL_PEDAL_F 0x4B
#define CMD_THR_ACTU  0x4C
#define TIME_MIL_ON   0x4D
#define TIME_MIL_CLR  0x4E
#define LAST_PID      0x4E  // same as the last one defined above   ☜ PID 상수의 끝

// our internal fake PIDs   ☜ 내부 PID 상수
#define NO_DISPLAY    0xF0
#define FUEL_CONS     0xF1
#define TRIP_CONS     0xF2
#define TRIP_DIST     0xF3
#define BATT_VOLTAGE  0xF4
#define CAN_STATUS    0xF5
#ifdef DEBUG
#define FREE_MEM      0xFF
#endif

// returned length of the PID response.    ☜ PID 응답 길이를 주함수로 반환
// constants so put in flash    ☜ 작동시 PID 응답 상수를 플래시 메모리에 저장
prog_uchar pid_reslen[] PROGMEM=
{
  // pid 0x00 to 0x1F
  4,4,8,2,1,1,1,1,1,1,1,1,2,1,1,1,
  2,1,1,1,2,2,2,2,2,2,2,2,1,1,1,2,
  // pid 0x20 to 0x3F
  4,2,2,2,4,4,4,4,4,4,4,4,1,1,1,1,
  1,2,2,1,4,4,4,4,4,4,4,4,2,2,2,2,
  // pid 0x40 to 0x4E
  4,8,2,2,2,1,1,1,1,1,1,1,1,2,2
};

// some globals, for trip calculation and others   ☜ 기타 광역변수
byte has_rpm;
unsigned long old_time;
long vss;  // speed    ☜ 차량속도
long maf;  // MAF   ☜ 흡기관 공기 유입량

// flag used to save distance/average consumption in eeprom only if required
// 거리당 평균 소비량을 필요한 경우에 EEPORM에 저장
byte engine_started;
byte param_saved;

//attach the buttons interrupt   ☜ 단추의 눌림을 처리하는 가로채기 함수
ISR(PCINT1_vect)
{
  byte p = PINC;  // bypassing digitalRead for interrupt performance    ☜ 포트를 읽는다
  buttonState &= p;    // 포트에서 읽은 값을 저장한다
}

// K 라인 포트로 들어오는 데이터를 바이트로 읽는다
// ISO 9141 바이트 수신 함수
int iso_read_byte()
{
  int val = 0;
  int bitDelay = _bitPeriod - clockCyclesToMicroseconds(50);
  unsigned long timeout;
 
  // one byte of serial data (LSB first)    ☜ 1 바이트의 K라인 데이터는 LSB 우선으로 들어온다
  // start  0   1   2   3   4   5   6   7 stop    ☜ 포맷은 비동기 직렬통신과 같다
  timeout=millis();
  while (digitalRead(K_IN))    // wait for start bit   ☜ 시작비트를 감지
  {
    if((millis()-timeout) > 300L)  // timeout after 300ms   ☜ 시작비트의 길이
      return -1;
  }
 
  // confirm that this is a real start bit, not line noise   ☜ 실제의 시작 비트를 처리
  if (digitalRead(K_IN) == LOW)
  {
    // frame start indicated by a falling edge and low start bit    ☜ 시작비트를 감지하여 프레임의 시작을 알린다
    // jump to the middle of the low start bit   ☜ 정확한 수신을 위해 시작비트의 중심으로 점프해야 한다
    delayMicroseconds(bitDelay / 2 - clockCyclesToMicroseconds(50));
    // offset of the bit in the byte: from 0 (LSB) to 7 (MSB)    ☜ 수신하는 1바이트 안의 현재 비트위치
    for (int offset = 0; offset < 8; offset++)
    {
      // jump to middle of next bit    ☜ 정확한 수신을 위해 각 비트의 중심으로 점프해야 한다
      delayMicroseconds(bitDelay);
      // read bit    ☜ 정확한 수신을 위해 이제 각 비트의 레벨을 읽는다.
      val |= digitalRead(K_IN) << offset;
    }
    delayMicroseconds(_bitPeriod);
    return val;
  }
  return -1;
}

// K 라인 포트에 1 바이트 쓰기 함수 (차량의 ECU로 1 바이트를 보낸다)
void iso_write_byte(byte b)
{
  int bitDelay = _bitPeriod - clockCyclesToMicroseconds(50); //   ☜ 1 비트는 약 50 명령이다
  digitalWrite(K_OUT, LOW);
  delayMicroseconds(bitDelay);
  for (byte mask = 0x01; mask; mask <<= 1)
  {
    if (b & mask) // choose bit
      digitalWrite(K_OUT, HIGH); // send 1    ☜  비트 1을 송신
    else
      digitalWrite(K_OUT, LOW); // send 0   ☜ 비트 0을 송신
    delayMicroseconds(bitDelay);
  }
  digitalWrite(K_OUT, HIGH);    // stop bit   ☜  정지 비트를 송신
  delayMicroseconds(bitDelay);  // lemght of stop bit    ☜ 정지 비트의 폭
}

// inspired by SternOBDIIcodechecksum.c    ☜ OBDII의 첵섬을 처리하는 함수
byte iso_checksum(byte *data, byte len)
{
  byte i;
  byte crc;
  crc=0;
  for(i=0; i<len; i++)
    crc=crc+data[i];
  return crc;
}

// inspired by SternOBDIIcodeiso.c  ☜ ISO 9141 데이터를 송신하는 함수
byte iso_write_data(byte *data, byte len)
{
  byte i, n;
  byte buf[20];
  // ISO header
  buf[0]=0x68;
  buf[1]=0x6A;  // 0x68 0x6A is an OBD-II request  ☜ 0x68 0x6A는 ODB-II 요청이다
  buf[2]=0xF1;  // our requester address (off-board tool)  ☜ 0xF1은 OBD-II로 송신하는 주소이다.
  // append message   ☜ 명령과 주소뒤에 이어지는 데이터를 OBD-II로 송신한다.
  for(i=0; i<len; i++)
    buf[i+3]=data[i];
  // calculate checksum  ☜ 첵섬을 계산한다.
  i+=3;
  buf[i]=iso_checksum(buf, i);
  // send char one by one   ☜ 이제 첵섬이 더해진 데이터 블록을 한 바이트씩 모두 OBD-II로 송신한다.
  n=i+1;
  for(i=0; i<n; i++)
  {
    iso_write_byte(buf[i]);
    delay(20); // inter character delay  ☜  1개의 문자를 보내고 일정시간 기다려야 한다.
  }
  return 0;
}

// read n byte of data (+ header + cmd and crc)   ☜ OBD-II에서 명령에 응답하는 데이터를 수신한다.
// return the result only in data  ☜ 데이터만 골라서 주함수로 반환한다
byte iso_read_data(byte *data, byte len)
{
  byte i;
  byte buf[20];
  // header 3 bytes: [80+datalen] [destination=f1] [source=01]   ☜ 수신한 버퍼에서 머릿글은 3 바이트
  // data 1+len bytes: [40+cmd0] [result0]   ☜ 데이터의 길이는 len +1 이다.
  // checksum 1 bytes: [sum(header)+sum(data)]   ☜ 1 바이트 씩 첵섬을 게산한다
    for(i=0; i<3+1+1+len; i++)
    buf[i]=iso_read_byte();
  // test, skip header comparison  ☜ 머릿글을 미교한다
  // ignore failure for the moment (0x7f)   ☜ 0x7F는 오류를 무시한다 
  // ignore crc for the moment  ☜ 따라서 CRC도 무시한다
  // we send only one command, so result start at buf[4];   ☜ buf +4 부터 실지 데이터가 들어 있다.
  memcpy(data, buf+4, len);
  return len;
}

// ISO 9141 init   ☜ ISO 9141 초기화
byte iso_init()
{
  byte b;
  // drive K line high for 300ms
  digitalWrite(K_OUT, HIGH);
  delay(300);
  // send 0x33 at 5 bauds
  // start bit
  digitalWrite(K_OUT, LOW);
  delay(200);
  // data
  b=0x33;
  for (byte mask = 0x01; mask; mask <<= 1)
  {
    if (b & mask) // choose bit
      digitalWrite(K_OUT, HIGH); // send 1
    else
      digitalWrite(K_OUT, LOW); // send 0
    delay(200);
  }
  // stop bit
  digitalWrite(K_OUT, HIGH);
  delay(200);
  // pause between 60 ms and 300ms (from protocol spec)
  delay(60);
  // switch now to 10400 bauds
  // wait for 0x55 from the ECU
  b=iso_read_byte();
  if(b!=0x55)
    return -1;
  delay(5);
  // wait for 0x08 0x08
  b=iso_read_byte();
  if(b!=0x08)
    return -1;
  delay(20);
  b=iso_read_byte();
  if(b!=0x08)
    return -1;
  delay(25);
  // sent 0xF7 (which is ~0x08)
  iso_write_byte(0xF7);
  delay(25);
  // ECU answer by 0xCC
  b=iso_read_byte();
  if(b!=0xCC)
    return -1;
  // init OK!
  return 0;
}

// get value of a PID, return as a long value
// and also formatted for output in the return buffer
long get_pid(byte pid, char *retbuf)
{
  byte i;
  byte cmd[2];    // to send the command
  byte buf[10];   // to receive the result
  long ret;       // return value
  byte reslen;
  char decs[16];
  // check if PID is supported
  if( pid!=PID_SUPPORT20 )
  {
    if( (pid<=0x20 && ( 1L<<(0x20-pid) & pid01to20_support ) == 0 )
      ||  (pid>0x20 && pid<=0x40 && ( 1L<<(0x40-pid) & pid21to40_support ) == 0 )
      ||  (pid>0x40 && pid<=0x60 && ( 1L<<(0x60-pid) & pid41to60_support ) == 0 )
      ||  (pid>LAST_PID) )
    {
      // nope
      sprintf_P(retbuf, PSTR("%02X N/A"), pid);
      return -1;
    }
  }

  // receive length depends on pid
  reslen=pgm_read_byte_near(pid_reslen+pid);
  cmd[0]=0x01;    // ISO cmd 1, get PID
  cmd[1]=pid;
  // send command, length 2
  iso_write_data(cmd, 2);
  // read requested length, n bytes received in buf
  iso_read_data(buf, reslen);
  // formula and unit
  switch(pid)
  {
  case ENGINE_RPM:
#ifdef DEBUG
    ret=1726;
#else
    ret=(buf[0]*256U+buf[1])/4U;
#endif

    sprintf_P(retbuf, PSTR("%ld RPM"), ret);
    break;

  case MAF_AIR_FLOW:
#ifdef DEBUG
    ret=2048;
#else
    ret=buf[0]*256U+buf[1];
#endif

    // not divided by 100 for return value!!
    int_to_dec_str(ret, decs, 2);
    sprintf_P(retbuf, PSTR("%s g/s"), decs);
    break;

  case VEHICLE_SPEED:
#ifdef DEBUG
    ret=100;
#else
    ret=buf[0];
#endif

    if(!params.useMetric)
      ret=(ret*621U)/1000U;
    sprintf_P(retbuf, PSTR("%ld %s"), ret, params.useMetric?"":"");
    break;

  case FUEL_STATUS:
#ifdef DEBUG
    ret=0x0200;
#else
    ret=buf[0]*256U+buf[1];
#endif

    if(buf[0]==0x01)
      sprintf_P(retbuf, PSTR("OPENLOWT"));  // open due to insufficient engine temperature
    else if(buf[0]==0x02)
      sprintf_P(retbuf, PSTR("CLSEOXYS"));  // Closed loop, using oxygen sensor feedback to determine fuel mix
    else if(buf[0]==0x04)
      sprintf_P(retbuf, PSTR("OPENLOAD"));  // Open loop due to engine load
    else if(buf[0]==0x08)
      sprintf_P(retbuf, PSTR("OPENFAIL"));  // Open loop due to system failure
    else if(buf[0]==0x10)
      sprintf_P(retbuf, PSTR("CLSEBADF"));  // Closed loop, using at least one oxygen sensor but there is a fault in the feedback system
    else
      sprintf_P(retbuf, PSTR("%04lX"), ret);
    break;

  case LOAD_VALUE:
  case THROTTLE_POS:
  case REL_THR_POS:
  case EGR:
  case EGR_ERROR:
  case FUEL_LEVEL:
  case ABS_THR_POS_B:
  case ABS_THR_POS_C:
  case ACCEL_PEDAL_D:
  case ACCEL_PEDAL_E:
  case ACCEL_PEDAL_F:
  case CMD_THR_ACTU:

#ifdef DEBUG
    ret=17;
#else
    ret=(buf[0]*100U)/255U;
#endif

    sprintf_P(retbuf, PSTR("%ld %%"), ret);
    break;

  case B1S1_O2_V:
  case B1S2_O2_V:
  case B1S3_O2_V:
  case B1S4_O2_V:
  case B2S1_O2_V:
  case B2S2_O2_V:
  case B2S3_O2_V:
  case B2S4_O2_V:
    ret=buf[0]*5U;  // not divided by 1000 for return!!
    if(buf[1]==0xFF)  // not used in trim calculation
      sprintf_P(retbuf, PSTR("%ld mV"), ret);
    else
      sprintf_P(retbuf, PSTR("%ldmV/%d%%"), ret, ((buf[1]-128)*100)/128);
    break;

  case DIST_MIL_ON:
  case DIST_MIL_CLR:
    ret=buf[0]*256U+buf[1];
    if(!params.useMetric)
      ret=(ret*621U)/1000U;
    sprintf_P(retbuf, PSTR("%ld %s"), ret, params.useMetric?"":"mi");
    break;
  case TIME_MIL_ON:
  case TIME_MIL_CLR:
    ret=buf[0]*256U+buf[1];
    sprintf_P(retbuf, PSTR("%ld min"), ret);
    break;
  case COOLANT_TEMP:
  case INT_AIR_TEMP:
  case AMBIENT_TEMP:
  case CAT_TEMP_B1S1:
  case CAT_TEMP_B2S1:
  case CAT_TEMP_B1S2:
  case CAT_TEMP_B2S2:
    if(pid>=CAT_TEMP_B1S1 && pid<=CAT_TEMP_B2S2)
#ifdef DEBUG
      ret=600;
#else
    ret=(buf[0]*256U+buf[1])/10U - 40;
#endif
    else

#ifdef DEBUG
      ret=40;
#else
    ret=buf[0]-40;
#endif

    if(!params.useMetric)
      ret=(ret*9)/5+32;
    sprintf_P(retbuf, PSTR("%ld%c"), ret, params.useMetric?'C':'F');
    break;
  case STF_BANK1:
  case LTR_BANK1:
  case STF_BANK2:
  case LTR_BANK2:
    ret=(buf[0]-128)*7812;  // not divided by 10000
    int_to_dec_str(ret/100, decs, 2);
    sprintf_P(retbuf, PSTR("%s %%"), decs);
    break;
  case FUEL_PRESSURE:
  case MAN_PRESSURE:
  case BARO_PRESSURE:
    ret=buf[0];
    if(pid==FUEL_PRESSURE)
      ret*=3U;
    sprintf_P(retbuf, PSTR("%ld kPa"), ret);
    break;
  case TIMING_ADV:
    ret=(buf[0]/2)-64;
    sprintf_P(retbuf, PSTR("%ld"), ret);
    break;

#if 1  // takes 254 bytes, may be removed if necessary
  case OBD_STD:
    ret=buf[0];
    if(buf[0]==0x01)
      sprintf_P(retbuf, PSTR("OBD2CARB"));
    else if(buf[0]==0x02)
      sprintf_P(retbuf, PSTR("OBD2EPA"));
    else if(buf[0]==0x03)
      sprintf_P(retbuf, PSTR("OBD1&2"));
    else if(buf[0]==0x04)
      sprintf_P(retbuf, PSTR("OBD1"));
    else if(buf[0]==0x05)
      sprintf_P(retbuf, PSTR("NOT OBD"));
    else if(buf[0]==0x06)
      sprintf_P(retbuf, PSTR("EOBD"));
    else if(buf[0]==0x07)
      sprintf_P(retbuf, PSTR("EOBD&2"));
    else if(buf[0]==0x08)
      sprintf_P(retbuf, PSTR("EOBD&1"));
    else if(buf[0]==0x09)
      sprintf_P(retbuf, PSTR("EOBD&1&2"));
    else if(buf[0]==0x0a)
      sprintf_P(retbuf, PSTR("JOBD"));
    else if(buf[0]==0x0b)
      sprintf_P(retbuf, PSTR("JOBD&2"));
    else if(buf[0]==0x0c)
      sprintf_P(retbuf, PSTR("JOBD&1"));
    else if(buf[0]==0x0d)
      sprintf_P(retbuf, PSTR("JOBD&1&2"));
    else
      sprintf_P(retbuf, PSTR("OBD:%02X"), buf[0]);
    break;
#endif

    // for the moment, everything else, display the raw answer
  default:
    // transform buffer to an integer value
    ret=0;
    for(i=0; i<reslen; i++)
    {
      ret*=256L;
      ret+=buf[i];
    }
    sprintf_P(retbuf, PSTR("%08lX"), ret);
    break;
  }
  return ret;
}

// ex: get a long as 687 with prec 2 and output the string "6.87"
// precision is 1 or 2
void int_to_dec_str(long value, char *decs, byte prec)
{
  byte pos;
  // sprintf_P does not allow * for the width ?!?
  if(prec==1)
    sprintf_P(decs, PSTR("%02ld"), value);
  else if(prec==2)
    sprintf_P(decs, PSTR("%03ld"), value);
  pos=strlen(decs)+1;  // move the too
  // a simple loop takes less space than memmove()
  for(byte i=0; i<=prec; i++)
  {
    decs[pos]=decs[pos-1];
    pos--;
  }
  decs[pos]='.';
}

void get_cons(char *retbuf)
{
  long toggle_speed;
  long cons;
  char decs[16];
  // divide MAF by 100 because our function return MAF*100
  // but multiply by 100 for double digits precision
  // divide MAF by 14.7 air/fuel ratio to have g of fuel/s
  // divide by 730 (g/L) according to Canadian Gov to have L/s
  // multiply by 3600 to get litre per hour
  // formula: (3600 * MAF) / (14.7 * 730 * VSS)
  // = maf*0.3355/vss L/km
  // mul by 100 to have L/100km
  toggle_speed=params.perHourSpeed;
  if(!params.useMetric)  // convert toggle speed to km/h
    toggle_speed=(toggle_speed*1609)/1000;
  if(vss<toggle_speed)
    cons=(maf*3355)/10000;  // do not use float so mul first then divide
  else
    cons=(maf*3355)/(vss*100); // 100 comes from the /10000*100
 
  if(params.useMetric)  {
    int_to_dec_str(cons, decs, 2);
    sprintf_P(retbuf, PSTR("%s %s"), decs, (vss<toggle_speed)?"L":"" );
  }

  else  {
    // MPG
    // 6.17 pounds per gallon
    // 454 g in a pound
    // 14.7 * 6.17 * 454 * (VSS * 0.621371) / (3600 * MAF / 100)
    // multipled by 10 for single digit precision
    // new comment: convert from L/100 to MPG
    if(vss<toggle_speed)
      cons=(cons*378)/100;  // convert to gallon
    else
      cons=235214/cons;     // convert to MPG
    int_to_dec_str(cons, decs, 1);
    sprintf_P(retbuf, PSTR("%s %s"), decs, (vss<toggle_speed)?"G":"" );
  }
}

void get_trip_cons(char *retbuf)
{
  float trip_cons;  // takes same size if I use long, so keep precision
  char decs[16];
  if(params.trip_dist==0.0 || params.trip_fuel==0.0)
      trip_cons=0.0;

  else  {
    // from mL/m to L/100 so div by 1000 for L and mul by 100000 for 100km
    // multiply by 100 to have 2 digits precision
    trip_cons=(params.trip_fuel/params.trip_dist)*10000.0;
    if(params.useMetric)
      int_to_dec_str((long)trip_cons, decs, 2);

    else   {
      // from m/mL to MPG so * by 3.78541178 to have gallon and * by 0.621371 for mile
      // multiply by 10 to have a digit precision
      // new comment: convert L/100 to MPG
      trip_cons=235214.5/trip_cons;
      int_to_dec_str((long)trip_cons, decs, 1);
    }
  }
  sprintf_P(retbuf, PSTR("%s %s"), decs, params.useMetric?"":"" );
}

void get_trip_dist(char *retbuf)
{
  float cdist;  // takes 20 bytes more if I use unsigned long
  char decs[16];
  // convert from meters to hundreds of meter
  cdist=params.trip_dist/100.0;
  // convert in miles if requested
  if(!params.useMetric)
    cdist*=0.621731;
  int_to_dec_str((long)cdist, decs, 1);
  sprintf_P(retbuf, PSTR("%s %s"), decs, params.useMetric?"":"" );
}

// accumulate data for trip, called every loop()
void accu_trip(void)
{
  static byte min_throttle_pos=255;   // idle throttle position
  byte throttle_pos;   // current throttle position
  byte open_load;      // to detect open loop
  char str[STRLEN];
  unsigned long time_now, delta_time;

  // time elapsed
  time_now = millis();
  delta_time = time_now - old_time;
  old_time = time_now;

  // distance in m
  vss=get_pid(VEHICLE_SPEED, str);
  if(vss>0)
    params.trip_dist+=((float)vss*(float)delta_time)/3600.0;
  // if engine is stopped, we can get out now
  if(!has_rpm)
  {
    maf=0;
    return;
  }

  // accumulate fuel only if not in DFCO
  // if throttle position is close to idle and we are in open loop -> DFCO
  // detect idle pos
  throttle_pos=get_pid(THROTTLE_POS, str);
  if(throttle_pos<min_throttle_pos)
    min_throttle_pos=throttle_pos;

  // get fuel status
  open_load=(get_pid(FUEL_STATUS, str)&0x0400)?1:0;
  if(throttle_pos<(min_throttle_pos+4) && open_load)
    maf=0;  // decellerate fuel cut-off, fake the MAF as 0 :)
  else
  {
    // check if MAF is supported
    if((1L<<(32-MAF_AIR_FLOW) & pid01to20_support) != 0)
    {
      // yes, just request it
      maf=get_pid(MAF_AIR_FLOW, str);
    }

    else
    {
      /*
      I just hope if you don't have a MAF, you have a MAP!!
      
       No MAF (Uses MAP and Absolute Temp to approximate MAF):
       IMAP = RPM * MAP / IAT
       MAF = (IMAP/120)*(VE/100)*(ED)*(MM)/(R)
       MAP - Manifold Absolute Pressure in kPa
       IAT - Intake Air Temperature in Kelvin
       R - Specific Gas Constant (8.314472 J/(mol.K)
       MM - Average molecular mass of air (28.9644 g/mol)
       VE - volumetric efficiency measured in percent
       ED - Engine Displacement in liters
       This method requires tweaking of the VE for accuracy.
       */
      long imap, rpm, map, iat;
      rpm=get_pid(ENGINE_RPM, str);
      map=get_pid(MAN_PRESSURE, str);
      iat=get_pid(INT_AIR_TEMP, str);
      imap=(rpm*map)/(iat+273);
      // does not divide by 100 because we use (MAF*100) in formula
      // but divide by 10 because engine displacement is in dL
      // 28.9644/(120*8.314472*10)= about 0.0029 or 29/10000
      maf=(imap * params.vol_eff * params.eng_dis * 29) / 10000;
    }
    // add MAF result to trip
    // we want fuel used in mL/s
    // maf gives grams of air/s
    // divide by 100 because our MAF return is not divided!
    // divide by 14.7 (a/f ratio) to have grams of fuel/s
    // divide by 730 to have L/s
    // mul by 1000 to have mL/s
    // divide by 1000 because delta_time is in ms
    params.trip_fuel+=((float)maf*(float)delta_time)/1073100.0;
  }
}
void display(byte corner, byte pid)
{
  char str[STRLEN];
  /* check if it's a real PID or our internal one */
  if(pid==NO_DISPLAY)
    return;
  else if(pid==FUEL_CONS) get_cons(str);
  else if(pid==TRIP_CONS) get_trip_cons(str);
  else if(pid==TRIP_DIST) get_trip_dist(str);
  //else if(pid==BATT_VOLTAGE) elm_command(str, PSTR("ATRV "));
  //else if(pid==CAN_STATUS) elm_command(str, PSTR("ATCS "));
#ifdef DEBUG
  else if(pid==FREE_MEM) sprintf_P(str, PSTR("%d free"), memoryTest());
#endif

  else (void)get_pid(pid, str);
  // left corners are left aligned
  // right corners are right aligned
  if(corner==TOPLEFT)
  {
    lcd_gotoXY(0,0);
    lcd_print_P(blkstr);
    lcd_gotoXY(0,0);
  }
  else if(corner==TOPRIGHT)
  {
    lcd_gotoXY(8, 0);
    lcd_print_P(blkstr);
    lcd_gotoXY(16-strlen(str), 0);  // 16 = screen width
  }
  else if(corner==BOTTOMLEFT)
  {
    lcd_gotoXY(0,1);
    lcd_print_P(blkstr);
    lcd_gotoXY(0,1);
  }
  else if(corner==BOTTOMRIGHT)
  {
    lcd_gotoXY(8, 1);
    lcd_print_P(blkstr);
    lcd_gotoXY(16-strlen(str), 1);
  }
  lcd_print(str);
}
void check_supported_pid(void)
{
  char str[STRLEN];
#ifdef DEBUG
  pid01to20_support=0xBE1FA812;
#else
  pid01to20_support=get_pid(PID_SUPPORT20, str);
#endif
  pid21to40_support=get_pid(PID_SUPPORT40, str);
  // if not supported, get_pid return -1
  if(pid21to40_support==-1)
    pid21to40_support=0;
  pid41to60_support=get_pid(PID_SUPPORT60, str);
  if(pid41to60_support==-1)
    pid41to60_support=0;
}
// might be incomplete
void check_mil_code(void)
{
  unsigned long n;
  char str[STRLEN];
  byte nb;
  byte cmd[2];
  byte buf[6];
  byte i, j, k;
  n=get_pid(MIL_CODE, str);

  /* A request for this PID returns 4 bytes of data. The first byte contains
   two pieces of information. Bit A7 (the seventh bit of byte A, the first byte)
   indicates whether or not the MIL (check engine light) is illuminated. Bits A0
   through A6 represent the number of diagnostic trouble codes currently flagged
   in the ECU. The second, third, and fourth bytes give information about the
   availability and completeness of certain on-board tests. Note that test
   availability signified by set (1) bit; completeness signified by reset (0)
   bit. (from Wikipedia)
   */
  if(1L<<31 & n)  // test bit A7
  {
    // we have MIL on
    nb=(n>>24) & 0x7F;
    lcd_cls();
    lcd_print_P(PSTR("CHECK ENGINE ON"));
    lcd_gotoXY(0,1);
    sprintf_P(str, PSTR("%d CODE(S) IN ECU"), nb);
    lcd_print(str);
    delay(2000);
    lcd_cls();
    // we display only the first 6 codes
    // if you have more than 6 in your ECU
    // your car is obviously wrong :-/
    // retrieve code
    cmd[0]=0x03;
    iso_write_data(cmd, 1);
    for(i=0;i<nb/3;i++)  // each received packet contain 3 codes
    {
      iso_read_data(buf, 6);
      k=0;  // to build the string
      for(j=0;j<3;j++)  // the 3 codes
      {
        switch(buf[j*2] & 0xC0)
        {
        case 0x00:
          str[k]='P';  // powertrain
          break;
        case 0x40:
          str[k]='C';  // chassis
          break;
        case 0x80:
          str[k]='B';  // body
          break;
        case 0xC0:
          str[k]='U';  // network
          break;
        }
        k++;
        str[k++]='0' + (buf[j*2] & 0x30)>>4;   // first digit is 0-3 only
        str[k++]='0' + (buf[j*2] & 0x0F);
        str[k++]='0' + (buf[j*2 +1] & 0xF0)>>4;
        str[k++]='0' + (buf[j*2 +1] & 0x0F);
      }
      str[k]='';  // make asciiz
      lcd_print(str);
      lcd_gotoXY(0, 1);  // go to next line to display the 3 next
    }
  }
}

// Configuration menu
void delay_button(void)
{
  // when we delay for the button, do not forget to accumulate data for trip
  // but anyway you should not configure your OBDuino while driving!
  delay(BUTTON_DELAY);
  accu_trip();
}

void trip_reset(void)
{
  byte p;
  lcd_cls();  // to reset trip
  lcd_print_P(PSTR("Reset trip data"));
  p=0;
  // set value with left/right and set with middle
  buttonState=buttonsUp;  // clear button
  do
  {
    if(!(buttonState&lbuttonBit))
      p=0;
    else if(!(buttonState&rbuttonBit))
      p=1;
    lcd_gotoXY(4,1);
    if(p==0)
      lcd_print_P(PSTR("(NO) YES"));
    else
      lcd_print_P(PSTR("NO (YES)"));
    buttonState=buttonsUp;
    delay_button();
  }
  while(buttonState&mbuttonBit);
  if(p==1)
  {
    params.trip_dist=0.0;
    params.trip_fuel=0.0;
  }
}

void config_menu(void)
{
  char str[STRLEN];
  byte p;
  // display protocol, just for fun
  lcd_cls();
//#ifndef DEBUG

  memset(str, 0, STRLEN);
  //elm_command(str, PSTR("ATDP "));
  if(str[0]=='A')  // string start with "AUTO, ", skip it
  {
    lcd_print(str+6);
    lcd_gotoXY(0,1);
    lcd_print(str+6+16);
  }
  else
  {
    lcd_print(str);
    lcd_gotoXY(0,1);
    lcd_print(str+16);
  }
  delay(2000);
//#endif

  // go through all the configurable items
  // first one is contrast
  lcd_cls();
  lcd_print_P(PSTR("LCD Contrast"));
  // set value with left/right and set with middle
  do
  {
    if(!(buttonState&lbuttonBit) && params.contrast!=0)
      params.contrast-=20;
    else if(!(buttonState&rbuttonBit) && params.contrast!=100)
      params.contrast+=20;
    lcd_gotoXY(5,1);
    sprintf_P(str, PSTR("- %d + "), params.contrast);
    lcd_print(str);
    analogWrite(ContrastPin, params.contrast);  // change dynamicaly
    buttonState=buttonsUp;
    delay_button();
  }

  while(buttonState&mbuttonBit);
  // then the use of metric
  lcd_cls();
  lcd_print_P(PSTR("Use metric unit"));
  // set value with left/right and set with middle
  do
  {
    if(!(buttonState&lbuttonBit))
      params.useMetric=0;
    else if(!(buttonState&rbuttonBit))
      params.useMetric=1;
    lcd_gotoXY(4,1);
    if(!params.useMetric)
      lcd_print_P(PSTR("(NO) YES"));
    else
      lcd_print_P(PSTR("NO (YES)"));
    buttonState=buttonsUp;
    delay_button();
  }

  while(buttonState&mbuttonBit);
  // speed from which we toggle to fuel/hour
  lcd_cls();
  lcd_print_P(PSTR("Fuel/hour speed"));
  // set value with left/right and set with middle
  do
  {
    if(!(buttonState&lbuttonBit) && params.perHourSpeed!=0)
      params.perHourSpeed--;
    else if(!(buttonState&rbuttonBit) && params.perHourSpeed!=255)
      params.perHourSpeed++;
    lcd_gotoXY(5,1);
    sprintf_P(str, PSTR("- %d + "), params.perHourSpeed);
    lcd_print(str);
    buttonState=buttonsUp;
    delay_button();
  }

  while(buttonState&mbuttonBit);
  // volume efficiency
  lcd_cls();
  lcd_print_P(PSTR("Vol effncy (MAP)"));
  // set value with left/right and set with middle
  do
  {
    if(!(buttonState&lbuttonBit) && params.vol_eff!=0)
      params.vol_eff--;
    else if(!(buttonState&rbuttonBit) && params.vol_eff!=100)
      params.vol_eff++;
    lcd_gotoXY(5,1);
    sprintf_P(str, PSTR("- %d%% + "), params.vol_eff);
    lcd_print(str);
    buttonState=buttonsUp;
    delay_button();
  }
  while(buttonState&mbuttonBit);
  // engine displacement
  lcd_cls();
  lcd_print_P(PSTR("Eng dplcmt (MAP)"));
  char decs[16];
  // set value with left/right and set with middle
  do
  {
    if(!(buttonState&lbuttonBit) && params.eng_dis!=0)
      params.eng_dis--;
    else if(!(buttonState&rbuttonBit) && params.eng_dis!=100)
      params.eng_dis++;
    lcd_gotoXY(4,1);
    int_to_dec_str(params.eng_dis, decs, 1);
    sprintf_P(str, PSTR("- %sL + "), decs);
    lcd_print(str);
    buttonState=buttonsUp;
    delay_button();
  }
  while(buttonState&mbuttonBit);
  // pid for the 4 corners, and for the n screen
  lcd_cls();
  lcd_print_P(PSTR("Configure PIDs"));
  p=0;

  // set value with left/right and set with middle
  do
  {
    if(!(buttonState&lbuttonBit))
      p=0;
    else if(!(buttonState&rbuttonBit))
      p=1;
    lcd_gotoXY(4,1);
    if(p==0)
      lcd_print_P(PSTR("(NO) YES"));
    else
      lcd_print_P(PSTR("NO (YES)"));
    buttonState=buttonsUp;
    delay_button();
  }
  while(buttonState&mbuttonBit);
 
  if(p==1)
    for(byte cur_screen=0; cur_screen<NBSCREEN; cur_screen++)
    {
      for(byte cur_corner=0; cur_corner<NBCORNER; cur_corner++)
      {
        lcd_cls();
        sprintf_P(str, PSTR("Scr %d Corner %d"), cur_screen+1, cur_corner+1);
        lcd_print(str);
        p=params.screen[cur_screen].corner[cur_corner];
        // set value with left/right and set with middle
        do
        {
          if(!(buttonState&lbuttonBit))
            p--;
          else if(!(buttonState&rbuttonBit))
            p++;
          lcd_gotoXY(5,1);
          sprintf_P(str, PSTR("- %02X +"), p);
          lcd_print(str);
          buttonState=buttonsUp;
          delay_button();
        }
        while(buttonState&mbuttonBit);
        // PID is choosen, set it
        params.screen[cur_screen].corner[cur_corner]=p;
      }
    }

  // save params in EEPROM
  lcd_cls();
  lcd_print_P(PSTR("Saving config"));
  lcd_gotoXY(0,1);
  lcd_print_P(PSTR("Please wait..."));
  params_save();
}

void test_buttons(void)
{
  // middle + right = trip reset
  if(!(buttonState&mbuttonBit) && !(buttonState&rbuttonBit))
  {
    trip_reset();
  }
  // left is cycle through active screen
  else if(!(buttonState&lbuttonBit))
  {
    active_screen = (active_screen+1) % NBSCREEN;
    lcd_cls();
  }

  // right is cycle through brightness settings
  else if(!(buttonState&rbuttonBit))
  {
    brightnessIdx = (brightnessIdx + 1) % brightnessLength;
    analogWrite(BrightnessPin, 255-brightness[brightnessIdx]);
  }
  // middle is go into menu
  else if(!(buttonState&mbuttonBit)) config_menu();
  buttonState=buttonsUp;
}

//Initialization
void setup()                    // run once, when the sketch starts
{
  byte r;
  char str[STRLEN];
  char VERSION[] = "$Revision$";
  // init pinouts
  pinMode(K_OUT, OUTPUT);
  pinMode(K_IN, INPUT);
  // buttons init
  pinMode( lbuttonPin, INPUT );
  pinMode( mbuttonPin, INPUT );
  pinMode( rbuttonPin, INPUT );
  // "turn on" the internal pullup resistors
  digitalWrite( lbuttonPin, HIGH);
  digitalWrite( mbuttonPin, HIGH);
  digitalWrite( rbuttonPin, HIGH);
  // low level interrupt enable stuff
  // interrupt 1 for the 3 buttons
  PCMSK1 |= (1 << PCINT11) | (1 << PCINT12) | (1 << PCINT13);
  PCICR |= (1 << PCIE1);
  // load parameters
  if(params_load()==0)  // if something is wrong, put default parms
  {
    params.contrast=40;
    params.useMetric=1;
    params.perHourSpeed=20;
    params.vol_eff=80;  // start at 80%, need tweaking for MAP
    params.eng_dis=20;  // for a 2.0L engine
    params.trip_dist=0.0;
    params.trip_fuel=0.0;
    params.screen[0].corner[TOPLEFT]=FUEL_CONS;
    params.screen[0].corner[TOPRIGHT]=TRIP_CONS;
    params.screen[0].corner[BOTTOMLEFT]=ENGINE_RPM;
    params.screen[0].corner[BOTTOMRIGHT]=VEHICLE_SPEED;
    params.screen[1].corner[TOPLEFT]=TRIP_CONS;
    params.screen[1].corner[TOPRIGHT]=TRIP_DIST;
    params.screen[1].corner[BOTTOMLEFT]=COOLANT_TEMP;
    params.screen[1].corner[BOTTOMRIGHT]=CAT_TEMP_B1S1;
    params.screen[2].corner[TOPLEFT]=PID_SUPPORT20;
    params.screen[2].corner[TOPRIGHT]=PID_SUPPORT40;
    params.screen[2].corner[BOTTOMLEFT]=PID_SUPPORT60;
    params.screen[2].corner[BOTTOMRIGHT]=OBD_STD;
  }

  // LCD pin init
  analogWrite(BrightnessPin,255-brightness[brightnessIdx]);
  pinMode(EnablePin,OUTPUT);
  pinMode(DIPin,OUTPUT);
  pinMode(DB4Pin,OUTPUT);
  pinMode(DB5Pin,OUTPUT);
  pinMode(DB6Pin,OUTPUT);
  pinMode(DB7Pin,OUTPUT);
  delay(500);
  analogWrite(ContrastPin, params.contrast);
  lcd_init();
  VERSION[strlen(VERSION)-1]=NUL;  // replace last $ with
  sprintf_P(str, PSTR("  OBDuino v%s"), strchr(VERSION, ' ')+1);
  lcd_print(str);
  do // init loop
  {
    lcd_gotoXY(0,1);
    lcd_print_P(PSTR("ISO9141 Init"));
    r=iso_init();
    lcd_gotoXY(0,1);
    if(r==0)
      lcd_print_P(PSTR("Successful!     "));
    else
      lcd_print_P(PSTR("Failed!         "));
    delay(1000);
  }
#ifdef DEBUG
    while(r ==0); // end init loop
#else
    while(r !=0); // end init loop
#endif

  // check supported PIDs
  check_supported_pid();
  // check if we have MIL code
  check_mil_code();
  has_rpm=0;
  vss=0;
  maf=0;
  active_screen=0;
  engine_started=0;
  param_saved=0;
  lcd_cls();
  old_time=millis();  // epoch
}

// Main loop
void loop()                     // run over and over again
{
  char str[STRLEN];
  // test if engine is started
  has_rpm=(get_pid(ENGINE_RPM, str)>0)?1:0;
  if(engine_started==0 && has_rpm!=0)
  {
    engine_started=1;
    param_saved=0;
  }

  // if engine was started but RPM is now 0
  // save param only once, by flopping param_saved
  if(param_saved==0 && engine_started!=0 && has_rpm==0)
  {
    params_save();
    param_saved=1;
    engine_started=0;
    lcd_cls();
    lcd_print_P(PSTR("TRIP SAVED!"));
    delay(2000);
  }
  // this read and assign vss and maf and accumulate trip data
  accu_trip();
  // display on LCD
  for(byte cur_corner=0; cur_corner<NBCORNER; cur_corner++)
    display(cur_corner, params.screen[active_screen].corner[cur_corner]);
  // test buttons
  test_buttons();
}

 
//  Memory related functions
// we have 512 bytes of EEPROM
void params_save(void)
{
  uint16_t crc;
  byte *p;
  // start at address 0
  eeprom_write_block((const void*)&params, (void*)0, sizeof(params_t));
  // CRC at the end
  crc=0;
  p=(byte*)&params;
  for(byte i=0; i<sizeof(params_t); i++)
    crc+=p[i];
  eeprom_write_word((uint16_t*)sizeof(params_t), crc);
}

//return 1 if loaded ok
byte params_load(void)
{
  uint16_t crc, crc_calc;
  byte *p;
  // read params
  eeprom_read_block((void*)&params, (void*)0, sizeof(params_t));
  // read crc
  crc=eeprom_read_word((const uint16_t*)sizeof(params_t));
  // calculate crc from read stuff
  crc_calc=0;
  p=(byte*)&params;
  for(byte i=0; i<sizeof(params_t); i++)
    crc_calc+=p[i];
  // compare CRC
  if(crc==crc_calc)
    return 1;
  else
    return 0;
}
#ifdef DEBUG  // takes 578 bytes!!

// this function will return the number of bytes currently free in RAM
// there is about 680 bytes free in memory when OBDuino is running
extern int  __bss_end;
extern int  *__brkval;
int memoryTest(void)
{
  int free_memory;
  if((int)__brkval == 0)
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
  else
    free_memory = ((int)&free_memory) - ((int)__brkval);
  return free_memory;
}
#endif

// LCD functions
// x=0..16, y=0..1
void lcd_gotoXY(byte x, byte y)
{
  byte dr=0x80+x;
  if(y!=0)    // save 2 bytes compared to "if(y==1)"
    dr+=0x40;
  lcd_commandWrite(dr);
}

void lcd_print_P(char *string)
{
  char str[STRLEN];
  sprintf_P(str, string);
  lcd_print(str);
}

void lcd_print(char *string)
{
  while(*string != 0)
    lcd_dataWrite(*string++);
}

// do the lcd initialization voodoo
// thanks to Yoshi "SuperMID" for tips :)
void lcd_init()
{
  delay(16);                    // wait for more than 15 msec
  lcd_pushNibble(B00110000);  // send (B0011) to DB7-4
  lcd_commandWriteSet();
  delay(5);                     // wait for more than 4.1 msec
  lcd_pushNibble(B00110000);  // send (B0011) to DB7-4
  lcd_commandWriteSet();
  delay(1);                     // wait for more than 100 usec
  lcd_pushNibble(B00110000);  // send (B0011) to DB7-4
  lcd_commandWriteSet();
  delay(1);                     // wait for more than 100 usec
  lcd_pushNibble(B00100000);  // send (B0010) to DB7-4 for 4bit
  lcd_commandWriteSet();
  delay(1);                     // wait for more than 100 usec
  // ready to use normal CommandWrite() function now!
  lcd_commandWrite(B00101000);   // 4-bit interface, 2 display lines, 5x8 font
  lcd_commandWrite(B00001100);   // display control on, no cursor, no blink
  lcd_commandWrite(B00000110);   // entry mode set: increment automatically, no display shift
  //creating the custom fonts (8 char max)
  // char 0 is not used
  // 1&2 is the L/100 datagram in 2 chars only
  // 3&4 is the km/h datagram in 2 chars only
  // 5 is the ?char (degree)
  // 6&7 is the mi/g char

#define NB_CHAR  7
  // set cg ram to address 0x08 (B001000) to skip the
  // first 8 rows as we do not use char 0
  lcd_commandWrite(B01001000);
  static byte chars[] PROGMEM ={
    B10000,B00000,B10000,B00010,B00111,B11111,B00010,
    B10000,B00000,B10100,B00100,B00101,B10101,B00100,
    B11001,B00000,B11000,B01000,B00111,B10101,B01000,
    B00010,B00000,B10100,B10000,B00000,B00000,B10000,
    B00100,B00000,B00000,B00100,B00000,B00100,B00111,
    B01001,B11011,B11111,B00100,B00000,B00000,B00100,
    B00001,B11011,B10101,B00111,B00000,B00100,B00101,
    B00001,B11011,B10101,B00101,B00000,B00100,B00111,
  };

  for(byte x=0;x<NB_CHAR;x++)
    for(byte y=0;y<8;y++)  // 8 rows
      lcd_dataWrite(pgm_read_byte(&chars[y*NB_CHAR+x])); //write the character data to the character generator ram
  lcd_cls();
  lcd_commandWrite(B10000000);  // set dram to zero
}

void lcd_cls()    // LCD 화면 지우기
{
  lcd_commandWrite(B00000001);  // Clear Display
  lcd_commandWrite(B00000010);  // Return Home
}

void lcd_tickleEnable()    // LCD에 E신호를 준다 (전송한 데이터를 LCD가 읽도록 한다)
{
  // send a pulse to enable
  digitalWrite(EnablePin,HIGH);
  delayMicroseconds(1);  // pause 1 ms according to datasheet
  digitalWrite(EnablePin,LOW);
  delayMicroseconds(1);  // pause 1 ms according to datasheet
}

void lcd_commandWriteSet()    // LCD에 제어명령을 보낸다
{
  digitalWrite(EnablePin,LOW);
  delayMicroseconds(1);  // pause 1 ms according to datasheet
  digitalWrite(DIPin,0);
  lcd_tickleEnable();
}

void lcd_pushNibble(byte value)    // LCD에 4비트를 쓴다
{
  digitalWrite(DB7Pin, value & 128);
  digitalWrite(DB6Pin, value & 64);
  digitalWrite(DB5Pin, value & 32);
  digitalWrite(DB4Pin, value & 16);
}

void lcd_commandWrite(byte value)    // LCD에 1 바이트 명령을 준다
{
  lcd_pushNibble(value);
  lcd_commandWriteSet();
  value<<=4;
  lcd_pushNibble(value);
  lcd_commandWriteSet();
  delay(5);
}

void lcd_dataWrite(byte value)    // LCD에 1 바이트를 쓴다
{
  digitalWrite(DIPin, HIGH);
  lcd_pushNibble(value);
  lcd_tickleEnable();
  value<<=4;
  lcd_pushNibble(value);
  lcd_tickleEnable();
  delay(5);
}
// end of OBDuino for ISO 9141
   
윗글 M168-mini로 만드는 USBasp
아래글 아듀이노 0017 업그레이드
    N         제목    글쓴이 작성일 조회 추천
61 비접점식 QTouch 방식 근접검출 스위치 leeky 2016/01/21 (목) 716 0
60 Arduino 고속 오실로스코프 #3 avrtools™ 2012/03/29 (목) 17791 0
59 아날로그 8ch 데이터 로거 avrtools™ 2012/03/29 (목) 2835 0
58 Arduino DMX512 송신기 제작 avrtools™ 2012/03/15 (목) 2958 0
57 nRF24L01 무선모듈의 사용방법 avrtools™ 2012/03/07 (수) 5945 0
56 초음파 거리 측정기의 제작 avrtools™ 2011/09/18 (일) 4878 0
55 3축 가속도 센서 ADXL335 avrtools™ 2011/09/09 (금) 3402 0
54 Arduino Uno USBserial 펌웨어의 변경 avrtools™ 2011/08/27 (토) 3001 0
53 Arduino MIDI의 소개 avrtools™ 2011/08/19 (금) 3404 0
52 Arduino 고속 Oscillo Scope #2 avrtools™ 2011/08/12 (금) 3023 0
51 Arduino Uno 및 USB2serial Uno의 소개 [2] avrtools™ 2011/07/30 (토) 3294 0
50 Arduino IDE에서 AVRISP-mkII 사용방법 avrtools™ 2010/10/22 (금) 7940 0
49 아듀이노 초음파 거리측정 및 응용 avrtools™ 2010/03/14 (일) 4149 0
48 M328-USB의 비밀 온도센서 avrtools™ 2010/01/02 (토) 3564 0
47 M168-mini로 만드는 USBasp avrtools™ 2009/10/10 (토) 5340 0
46 OBDuino ISO9141 소스의 설명 avrtools™ 2009/10/08 (목) 4169 0
45 아듀이노 0017 업그레이드 avrtools™ 2009/08/18 (화) 3697 0
44 Arduino 0015 업그레이드 avrtools™ 2009/06/01 (월) 3384 0
43 CC2500 Zigbee RF Modem #1 [4] avrtools™ 2008/11/20 (목) 6650 0
42 아듀이노 소프트웨어 Ver 0012 avrtools™ 2008/09/23 (화) 4014 0
41 Arduino 소프트웨어 설치방법 avrtools™ 2008/08/31 (일) 5589 0
40 아듀이노 PC 카메라 제작 avrtools™ 2008/10/28 (화) 7207 0
39 아듀이노 AVR-ISP V2의 제작 [6] avrtools™ 2008/10/22 (수) 8288 0
38 아듀이노 J1850-PWM 스캐너 avrtools™ 2008/10/15 (수) 4532 0
37 아듀이노 MPGduino의 제작 avrtools™ 2008/10/11 (토) 4904 0
36 아듀이노 OBD-II PID 처리함수 avrtools™ 2008/10/12 (일) 5486 0
35 아듀이노 OBD-II PID 송수신 함수 avrtools™ 2008/10/09 (목) 9654 0
34 아듀이노 ODB-II 스캐너 제작 [9] avrtools™ 2008/10/04 (토) 17257 0
33 아듀이노 AC전원 THD 측정 [2] avrtools™ 2008/09/30 (화) 6787 0
32 아듀이노 소프트방식 16Bit ADC avrtools™ 2008/09/23 (화) 5558 0
31 아듀이노 초음파 모듈의 제작. avrtools™ 2008/09/22 (월) 4841 0
30 아듀이노 Wii 프로세싱 avrtools™ 2008/09/20 (토) 4144 0
29 아듀이노 초음파 거리측정 avrtools™ 2008/09/20 (토) 6055 0
28 아듀이노 8x5 초소형 전광판 avrtools™ 2008/09/11 (목) 4664 0
27 아듀이노 4선식 터치패널 avrtools™ 2008/09/10 (수) 4658 0
26 아듀이노 2색 8x8 LED avrtools™ 2008/09/10 (수) 4897 0
25 아듀이노 24x6 LED 전광판 avrtools™ 2008/09/10 (수) 5440 0
24 아듀이노 8x8 LED 프로세싱 avrtools™ 2008/09/10 (수) 4105 0
23 아듀이노 32x16 RGB 전광판 avrtools™ 2008/09/06 (토) 13676 0
22 아듀이노 맥박검출기 avrtools™ 2008/09/03 (수) 9489 0
21 아듀이노 적외선 거리센서 avrtools™ 2008/09/01 (월) 5941 0
20 아듀이노 DMX 수신장치 [7] avrtools™ 2008/08/31 (일) 6782 0
19 아듀이노 AVR-ISP 만들기 avrtools™ 2008/08/30 (토) 4137 0
18 아듀이노 POV #3 avrtools™ 2008/08/30 (토) 3463 0
17 아듀이노 POV #2 avrtools™ 2008/08/30 (토) 3247 0
16 AVR 병렬포트 굽기장치 avrtools™ 2008/08/30 (토) 5212 0
15 아듀이노 DMX 송신기 avrtools™ 2008/08/28 (목) 5124 0
14 아듀이노 부트로더의 개조 [1] avrtools™ 2008/08/28 (목) 5771 0
13 아듀이노 병렬포트 굽기장치 avrtools™ 2008/08/26 (화) 4208 0
12 아듀이노 POV #1 avrtools™ 2008/08/26 (화) 3491 0
11 아듀이노 MIDI 드럼 leeky 2008/08/24 (일) 4774 0
10 아듀이노 SD/MMC 카드 avrtools™ 2008/08/24 (일) 9784 0
9 아듀이노 MIDI 플륫 avrtools™ 2008/08/24 (일) 3602 0
8 아듀이노 RGB LED avrtools™ 2008/08/23 (토) 5036 0
7 아듀이노 USB 오실로스코프 [2] leeky 2008/08/21 (목) 6430 0
6 가속도계 ADXL202 응용소스 avrtools™ 2008/08/20 (수) 4906 0
5 아듀이노 가속도센서 avrtools™ 2008/08/18 (월) 4893 0
4 아듀이노 RC서보 제어 avrtools™ 2008/08/17 (일) 5226 0
3 아듀이노 910-ISP 만들기 avrtools™ 2008/08/16 (토) 4719 0
2 아듀이노 온도센서, XBee 송수신 avrtools™ 2008/08/15 (금) 7527 0
1 아듀이노 기본명령 avrtools™ 2008/08/07 (목) 6620 0
1

바구니 : 0
 보관함 : 0
오늘뷰 : 0
HOME   |   회사소개   |   제휴안내   |   회사위치   |   서비스이용 약관   |   개인정보 보호정책   |   사이트맵
17015 경기도 용인시 기흥구 동백중앙로16번길 16-25, 508호. 전화 : 031-282-3310
사업자 등록번호 : 697-47-00075 / 대표 : 이건영 / 업태 : 제조업 / 종목 : LED조명, LED전원, 제어장치.
개인정보 관리책임자 : 홈페이지 관리자 . Copyright ⓒ2016 아크레즈 (ACLEDS INC.)
HOME TOP PREVNEXT 0 0 0