Jump to content
nimblemotors

Tractor Engine Control

Recommended Posts


//******************************************************************************

//

//  Electronic Fuel Injection, Written by Jack Murray  2011

//    NOTE, this is not cleaned up finished code, but decided to share it anyway.

//

//  We use a Geo Metro 1.0L Throttle Body Injector (TBI) for fuel

//  The throttle is an electronic controlled from a 2004 Prius

//  The water temp sensor is from a '87 Chevy

//  The 02 sensor is a generic narrow band.

//  We use the on-chip temp sensor to proxy the air temp

//  The Fuel Pump is from an '89 Jeep Cherokee, and is turned on through

//  a relay from a Geo Metro.

//

//  The EFI control is simply to determine how much time to turn on the injector,

//  which is how long to make the pulse widths.

//

//  For the tractor, we also control the throttle, and try to maintain a

//  given RPM that is determined by the RPM adjustment knob

//

//

//  The controller is a repurposed MSP4301232 board that was a battery charger.

//  It has a two line LCD, one darlington, two mosfets and 8 10bit ADC inputs

//  We use the darlington to turn on the fuel pump relay, which is P1.1

//  Mosfet A is used to for the fuel injector driver, P1.3 TA2

//  Mosfet B is used to control the throttle, P1.2 TA1

//  ADC inputs:

//    A0   -- user interface selector

//    A1   o2 sensor

//    A2   -- lcd dial

//    A3   water temp

//    A4   rpm adjuster pot

//    A5   -- connected to lcd

//    A6   MAP sensor

//    A7   throttle position sensor from prius

//

//    P2.5 is the tach input, that goes low on an ignition event

// 

//******************************************************************************

 

 

#include  <signal.h>

#include  <io.h>

 

 

// efi settings, we need to limit this to 128 bytes if possible

// so we can erase one segment and not lose everything if something goes wrong

// during the update.

struct ConfigEFI {

  unsigned char valid;   // whether containts are valid

  unsigned char airTable[32];   // PWM Adjustment for air temperature

  unsigned char watTable[32];   // PWM Adjustment for water temp  

  unsigned char rpmTable[32];   // PWM Adjustment for rpm

};

 

 

// Copy of current config parameters in Memory

struct ConfigEFI Config, *FConfig; 

 

 

unsigned short selectime;

unsigned short selecting;

unsigned short dstate, lastSeconds, seconds;

unsigned short lastdial, dialpos;

 

 

 

 

unsigned short throttleTimeOn, throttleTimeOff, throttleIsOn;

unsigned short injectorIsOn, injectorStartTime, injectorPulseWidth;

unsigned short injectorBaseWidth;

unsigned short injectorTimeOff, injectorTimeOn;

 

 

unsigned short dial, tps;

unsigned short targetTPS = 500;

 

 

unsigned short tach, rpm, lastTach;

 

 

unsigned short airSensor;

unsigned short waterSensor;

unsigned short mapSensor;

unsigned short o2Sensor;

 

 

void startInjector();

void updateInjectorWidth();

void updateThrottle();

 

 

 

 

#define Reboot()     WDTCTL = 0xFFFF        // cause reset from invalid WDT password

  

flashcopy(char *mptr, char *fptr, int size)

{

  int i;

  for (i=0; i<size; i++)

    {

      asm("dint");

      FCTL3 = FWKEY;                  // Clear Lock bit

      FCTL1 = FWKEY + WRT;            // Set WRT bit for write operation

      *fptr++ = *mptr++;         // Write value to flash

      FCTL1 = FWKEY;                  // Clear WRT bit

      FCTL3 = FWKEY + LOCK;           // Reset LOCK bit

      asm("eint");    

    }

}

 

 

// We save configs to flash.  We can only erase an entire segment,

// so we first erase all of Segment A, copy the Segment B values + New values into A,

// then erase B, and then copy A into B.  B now has the updated values.

//

SaveConfig()

{

  int c,i;

  char *mptr;

  char *fptr;  

  int cfgsize = sizeof(Config);

  struct ConfigEFI *Aptr, *Bptr;

 

 

  Aptr = (struct ConfigEFI *)0x1080;

  Bptr = (struct ConfigEFI *)0x1000;  

 

 

  WDTCTL = WDTPW + WDTHOLD;       // Stop watchdog timer

  FCTL2 = FWKEY + FSSEL0 + FN4;   // 5Mhz MCLK/16 = 312Khz for Flash Timing Generator  

  //

  // Erase Segment A 

  fptr = (char *)Aptr;   // Initialize Flash segment B pointer

  asm("dint");

  FCTL1 = FWKEY + ERASE;          // Set Erase bit

  FCTL3 = FWKEY;                  // Clear Lock bit

  *fptr = 0;                // Dummy write to erase Flash segment A

  asm("eint");

  //

  // Write Segment A from memory

  mptr = (char *)&Config;

  flashcopy(mptr, (char *)Aptr, cfgsize);

  //

  // Now all of Erase B

  fptr = (char *)Bptr;   // Initialize Flash segment B pointer

  asm("dint");

  FCTL1 = FWKEY + ERASE;          // Set Erase bit

  FCTL3 = FWKEY;                  // Clear Lock bit

  *fptr = 0;                // Dummy write to erase Flash segment A

  asm("eint");

  //

  // Now copy all of A to B

  flashcopy((char *)Aptr, (char *)Bptr, cfgsize);

  // restart watchdog

  // WDTCTL = WDT_ADLY_1000;               // Set Watchdog Timer interval back to 1000ms (1-sec)

}

 

 

unsigned short adcRead(unsigned short channel)

{

  unsigned short val; 

  ADC10CTL0 &= ~ENC;             // Disable while changing input channel

  ADC10CTL1 = (channel << 12);

  ADC10CTL0 |= ENC + ADC10SC;             // Sampling open

  while ((ADC10CTL1 & ADC10BUSY) == 1);   // ADC10BUSY?

  val = ADC10MEM;

  return val;

}

 

 

unsigned short readchannel(unsigned short channel)

{

  unsigned short i, val;

  unsigned long avg;

  avg = 0;

  for(i=0; i<256; i++) {

    avg = avg + adcRead(channel);

  }

  val = avg >> 8;

  return val;

}

 

 

unsigned short waterSensor;

unsigned short waterTemp;

 

 

 

 

unsigned short readWaterTemp(unsigned short channel)

{

  unsigned short val, tmp1, tmp2, tmp3, tmp4;

  //

  // The water temp is a GM water temp sensor

  // It has a log-scale of 10k at 0F down to 100 ohm at 250F

  // We use a divider circuit with a 330ohm resistor,

  // basically we just hardcode the readings to 32 levels 

  ADC10CTL0 =            REFON | REF2_5V | ADC10SHT_2 | SREF_1;   // turn OFF

  ADC10CTL0 =  ADC10ON | REFON | REF2_5V | ADC10SHT_2 | SREF_1;   // use 2.5v ref

  // ADC10CTL0 =  ADC10ON | ADC10SHT_3 | SREF_0;   // use VCC ref

  tmp1 = adcRead(3);

  tmp2 = adcRead(3);

  tmp3 = adcRead(3);

  tmp4 = readchannel(3);

  waterSensor = (tmp1 + tmp2 + tmp3 + tmp4) >> 2;

  // *=measured

  // temp    res      volts   1.5v   2.5v

  // 244 F : 110 ohm : 0.75v :  512 :  307 

  // 240 F : 120 ohm : 0.80v :  546 :  327 

  // 236 F : 130 ohm : 0.85v :  578 :  347 

  // 232 F : 140 ohm : 0.89v :  610 :  366 

  // 228 F : 150 ohm : 0.94v :  640 :  384 

  // 224 F : 160 ohm : 0.98v :  668 :  401 

  // 220 F : 170 ohm : 1.02v :  696 :  417 

  // 216 F : 180 ohm : 1.06v :  722 :  433 

  //*212 F : 190 ohm : 1.10v :  748 :  448 

  // 208 F : 200 ohm : 1.13v :  772 :  463 

  // 204 F : 210 ohm : 1.17v :  796 :  477 

  //*200 F : 220 ohm : 1.20v :  819 :  491 

  // 196 F : 236 ohm : 1.25v :  853 :  512 

  // 192 F : 252 ohm : 1.30v :  886 :  532 

  // 188 F : 268 ohm : 1.34v :  917 :  550 

  // 184 F : 284 ohm : 1.39v :  947 :  568 

  //*180 F : 300 ohm : 1.43v :  975 :  585 

  // 176 F : 324 ohm : 1.49v : 1014 :  608 

  // 172 F : 348 ohm : 1.54v : 1051 :  630 

  // 168 F : 372 ohm : 1.59v : 1085 :  651 

  // 164 F : 396 ohm : 1.64v : 1117 :  670 

  //*160 F : 420 ohm : 1.68v : 1146 :  688 

  // 156 F : 460 ohm : 1.75v : 1192 :  715 

  // 152 F : 500 ohm : 1.81v : 1233 :  740 

  // 148 F : 540 ohm : 1.86v : 1271 :  762 

  // 144 F : 580 ohm : 1.91v : 1305 :  783 

  //*140 F : 620 ohm : 1.96v : 1336 :  801 

  // 136 F : 660 ohm : 2.00v : 1365 :  819 

  // 132 F : 700 ohm : 2.04v : 1391 :  835 

  // 128 F : 740 ohm : 2.07v : 1416 :  849 

  // 124 F : 780 ohm : 2.11v : 1439 :  863 

  // 120 F : 820 ohm : 2.14v : 1460 :  876

  if (waterSensor < 600) {  // if temp is > 180, we switch to using the 1.5v reference to get better resolution

    ADC10CTL0 =            REFON | ADC10SHT_2 | SREF_1;   // use VCC ref, turn OFF

    ADC10CTL0 =  ADC10ON | REFON | ADC10SHT_2 | SREF_1;   // use 1.5v ref

    tmp1 = adcRead(3);

    tmp2 = adcRead(3);

    tmp3 = adcRead(3);

    tmp4 = adcRead(3);

    // switch it back to 2.5v

    ADC10CTL0 =            REFON | REF2_5V | ADC10SHT_2 | SREF_1;   // turn OFF

    ADC10CTL0 =  ADC10ON | REFON | REF2_5V | ADC10SHT_2 | SREF_1;   // use 2.5v ref

    waterSensor = (tmp1 + tmp2 + tmp3 + tmp4) >> 2;

    if (waterSensor <= 512) { waterTemp = 32; }

    else if (waterSensor <= 546) { waterTemp = 31; }

    else if (waterSensor <= 578) { waterTemp = 30; }

    else if (waterSensor <= 610) { waterTemp = 29; }

    else if (waterSensor <= 640) { waterTemp = 28; }

    else if (waterSensor <= 668) { waterTemp = 27; }

    else if (waterSensor <= 696) { waterTemp = 26; }

    else if (waterSensor <= 722) { waterTemp = 25; }

    else if (waterSensor <= 748) { waterTemp = 24; }

    else if (waterSensor <= 772) { waterTemp = 23; }

    else if (waterSensor <= 796) { waterTemp = 22; }

    else if (waterSensor <= 819) { waterTemp = 21; }

    else if (waterSensor <= 853) { waterTemp = 20; }

    else if (waterSensor <= 886) { waterTemp = 19; }

    else if (waterSensor <= 917) { waterTemp = 18; }

    else if (waterSensor <= 947) { waterTemp = 17; }

    else if (waterSensor <= 975) { waterTemp = 16; }

    else { waterTemp = 15; }

  }

  // 176 F : 324 ohm : 1.49v : 1014 :  608 

  // 172 F : 348 ohm : 1.54v : 1051 :  630 

  // 168 F : 372 ohm : 1.59v : 1085 :  651 

  // 164 F : 396 ohm : 1.64v : 1117 :  670 

  // 160 F : 420 ohm : 1.68v : 1146 :  688 

  // 156 F : 460 ohm : 1.75v : 1192 :  715 

  // 152 F : 500 ohm : 1.81v : 1233 :  740 

  // 148 F : 540 ohm : 1.86v : 1271 :  762 

  // 144 F : 580 ohm : 1.91v : 1305 :  783 

  // 140 F : 620 ohm : 1.96v : 1336 :  801 

  // 136 F : 660 ohm : 2.00v : 1365 :  819 

  // 132 F : 700 ohm : 2.04v : 1391 :  835 

  // 128 F : 740 ohm : 2.07v : 1416 :  849 

  // 124 F : 780 ohm : 2.11v : 1439 :  863 

  // 120 F : 820 ohm : 2.14v : 1460 :  876

  else if (waterSensor <= 608) { waterTemp = 15; }

  else if (waterSensor <= 630) { waterTemp = 14; }

  else if (waterSensor <= 651) { waterTemp = 13; }

  else if (waterSensor <= 670) { waterTemp = 12; }

  else if (waterSensor <= 688) { waterTemp = 11; }

  else if (waterSensor <= 715) { waterTemp = 10; }

  else if (waterSensor <= 740) { waterTemp = 9; }

  else if (waterSensor <= 762) { waterTemp = 8; }

  else if (waterSensor <= 783) { waterTemp = 7; }

  else if (waterSensor <= 801) { waterTemp = 6; }

  else if (waterSensor <= 819) { waterTemp = 5; }

  else if (waterSensor <= 835) { waterTemp = 4; }

  else if (waterSensor <= 849) { waterTemp = 3; }

  else if (waterSensor <= 863) { waterTemp = 2; }

  else if (waterSensor <= 1024) { waterTemp = 1; }

  return waterTemp;

}

 

 

 

 

__attribute__ ((naked)) void delay() {

    //first arg comes in register R15, loop uses 3 cycles per round

    //the eight cycles come from the call overhead and ret

    //delay_time = (1/MCLK)*(8+(3*n))

  asm("xloop: dec r15\n  jnz xloop\n ret");

}

 

 

//  To send command, Set Enable to High, set RS+DB command, then Clear Enable

void

LCDout(int rs, int data)

{

  unsigned char high4 = (data >> 4) & 0x0F;

  unsigned char low4 = (data & 0x0F);

  unsigned char status = 0;

  unsigned char clear = 0;

  unsigned short max = 0;

  

  if (rs) {

    P3OUT =  0x30;  // Set Enable + RS, clear data bits 

    P3OUT |= high4; // Set high 4-bits

    P3OUT &= ~0x10;  // Clear Enable

    P3OUT = 0x30;  // Set Enable + RS

    P3OUT |= low4; // Set low 4-bits

    P3OUT &= ~0x10;  // Clear Enable    

  }

  else {

    P3OUT = 0x10;  // Set Enable, Clear RS, Clear data bits      

    P3OUT |= high4; // Set high 4-bits

    P3OUT &= ~0x10;  // Clear Enable

    // Now write low4 bits

    P3OUT = 0x10;  // Set Enable, Clear RS, Clear data bits      

    P3OUT |= low4; // Set low 4-bits

    P3OUT &= ~0x10;  // Clear Enable

  }

 

 

  delay(750*5);

 

 

  return;

}

 

 

void

LCDouts(char *str)

{

  while (*str != 0) {

    LCDout(1, *str++);        //write char and increment pointer, careful about 7-8 position!

  }

}

 

 

// initialize LCD.

//  P3.0-1-2-3 connected to DB4-5-6-7

//  P3.4 is Enable, P3.5 is RS(RegisterSelect, Read/Write is wired to Zero/Ground.

void

initLCD()

{

  unsigned int wait;

  P3OUT = 0x00;    // Clear P3.0-7

  P3DIR = 0x3F;    // Set P3.0-5 to output direction

  

  P3OUT = 0x00;   // Clear Enable, RS, Data

 

 

  

  P3OUT = 0x03;   // Set Data Cmd for 8bit mode

  P3OUT |= 0x10;  // Set Enable.

  P3OUT &= ~0x10;  // Clear Enable.

  delay(750*20);

  

  P3OUT = 0x03;   // Set Data Cmd 

  P3OUT |= 0x10;  // Set Enable.

  P3OUT &= ~0x10;  // Clear Enable.

  delay(750*20);

  

  P3OUT = 0x03;   // Set Data Cmd 

  P3OUT |= 0x10;  // Set Enable.

  P3OUT &= ~0x10;  // Clear Enable.

  delay(750*20);

  

  P3OUT = 0x02;   // Now set Data Cmd to 0x20 for switch to 4bit mode

  P3OUT |= 0x10;  // Set Enable.

  P3OUT &= ~0x10;  // Clear Enable.

  delay(750*20);

  

  // Now we are in 4bit mode.  Do command again, now in 4bit mode.

 

 

  P3OUT = 0x02;   // Now send Function Set Data Cmd to 0x20 for switch to 4bit mode

  P3OUT |= 0x10;  // Set Enable.

  P3OUT &= ~0x10;  // Clear Enable.

  P3OUT = 0x0C;   // Send lower 4bit for 2-lines

  P3OUT |= 0x10;  // Set Enable.

  P3OUT &= ~0x10;  // Clear Enable.

  delay(750*20);

 

 

}

 

 

void

LCDdigit(unsigned char d) {

  LCDout(1, '0'+ d);

}

 

 

unsigned short

BCDecode(unsigned short val, unsigned char *d1, unsigned char *d2, unsigned char *d3)

{

  *d1 = val % 10;

  val = val / 10;

  *d2 = val % 10;

  val = val / 10;

  if (d3)

    { *d3 = val % 10;

      val = val / 10;

    }

  return val;

}

 

 

// output charge time.  We increment only every 4 seconds, so 15 seconds is actually 60

// we divide by 15 to get minutes.

// 11.72v Ach 12:22

// 11.72v Ach   81F

LCDout_minutes (unsigned short ticks)

{

  unsigned short val;

  char d1,d2;

 

 

  val = ticks/60;  

  BCDecode(val, &d1, &d2, 0);

  if (d2>0) { LCDdigit(d2); } else { LCDout(1,' '); }

  LCDdigit(d1);

 

 

  LCDout(1, ':');

  val = (ticks)%60;

  BCDecode(val, &d1, &d2, 0);

  LCDdigit(d2);

  LCDdigit(d1);

}

 

 

 

 

LCDout_temp (unsigned short temp)

{

  unsigned short val = temp;

  char d1,d2,d3,d4;

 

 

  BCDecode(val, &d1, &d2, &d3);

  // d1 = val % 10;

  // val = val / 10;

  // d2 = val % 10;

  // val = val / 10;

  // d3 = val % 10;

  if (d3 > 0) {

    LCDdigit(d3);

  }

  else {

    LCDout(1, ' ');

  }

  LCDdigit(d2);

  LCDdigit(d1);

  LCDout(1, 'F');

}

 

 

LCDout_volts (unsigned short volts)

{

  unsigned short val = volts;

  char d1,d2,d3,d4;

 

 

  val = BCDecode(val, &d1, &d2, &d3);  

  // d1 = val % 10;

  // val = val / 10;

  // d2 = val % 10;

  // val = val / 10;

  // d3 = val % 10;

  // val = val / 10;

  d4 = val % 10;

  if (d4 > 0) {

    LCDdigit(d4);

  }

  else {

    LCDout(1, ' ');

  }

  LCDdigit(d3);

  LCDout(1, '.');  

  LCDdigit(d2);

  LCDdigit(d1);

  LCDout(1, 'v');

}

 

 

LCDout_adc (unsigned short adc)

{

  unsigned short val = adc;

  char d1,d2,d3,d4,d5;

 

 

  val = BCDecode(val, &d1, &d2, &d3);  

  d4 = val % 10;

  val = val / 10;

  if (d4 > 0) {

    d5 = val % 10;

    LCDdigit(d5);

  }

  else {

    LCDout(1, ' ');

  }

  LCDdigit(d4);

  LCDdigit(d3);

  LCDdigit(d2);

  LCDdigit(d1);

}

 

 

 

 

LCDout_Mamps (unsigned short ma)

{

  unsigned short val = ma/8;

  char d1,d2,d3,d4;

 

 

  val = BCDecode(val, &d1, &d2, &d3);

  LCDdigit(val);  

  LCDdigit(d3);

  LCDdigit(d2);

  LCDdigit(d1);

  LCDout(1, 'm');

  LCDout(1, 'a');

  LCDout(1, ' ');

}

 

 

 

 

LCDout_amps (unsigned short amps)

{

  unsigned short val = amps;

  char d1,d2,d3,d4;

 

 

  val = BCDecode(val, &d1, &d2, &d3);

  LCDdigit(d3);

  LCDout(1, '.');  

  LCDdigit(d2);

  LCDdigit(d1);

  LCDout(1, 'a');

  LCDout(1, ' ');

}

 

 

 

 

// Watchdog Timer interrupt service routine

// Runs each second

interrupt (WDT_VECTOR) 

watchdog_timer(void)

{

  unsigned short volts,temp;

 

 

  seconds++;

 

 

  _BIC_SR_IRQ(CPUOFF);             // Clear CPUOFF bits to wake up main loop

 

 

}

 

 

//

// with two batteries charging, the ADC dial reading fluctuates badly,

// so we stablize it by taking an average reading over 65,000 readings

// and return that value, and then use hysteresis to have it snap

// from one position to the next.

//

newDial()

{

  unsigned short pos, ch1;

 

 

  while (selecting < 2) {  // no button pressed

    ch1 = readchannel(2); // read position

    pos = ch1/64; //  ndiv;

    if ((pos > dialpos) && (ch1-5 < lastdial))

      { pos = dialpos; }

    if ((pos < dialpos) && (ch1+5 > lastdial))

      { pos = dialpos; }  

    if (pos != dialpos) {

      // we have a new position, update globals and return

      lastdial = ch1;

      dialpos = pos;

      selecting = 1;     // reset watchdog timeout shutdown

      return dialpos;

    }

  }

  return dialpos;

}

 

 

 

 

int

SelectInput(char *options)

{

  LCDout(0, 0xC0); // position cursor at 2nd line

  LCDouts(options);

  LCDout(0, 0xC0); // position cursor at 2nd line

  LCDout(0, 0x0F);  // On, Cursor, Blink

 

 

  // init globals

  dialpos = 16;

  lastdial = 1060;

  

  selecting = 1;

  while (selecting < 2) {  // button not pressed (again)

    newDial();  // readchannel(INCH_2); // read position

    LCDout(0, 0xC0+dialpos); // update cursor position

  }

  return dialpos;  // return position when button pressed.

}

 

 

int

buttonPress()

{

  int val;

  unsigned int ch1, ch2, pos, cpos;

 

 

  while (1) {

    //

    // Offer selection of setting to edit

    //

    LCDout(0, 0x01);  // Clear Display

    LCDout(0, 0xC0);  // Move Cursor to position for this charger

    // LCDouts("X A:CtE B:CtE   ");

    LCDouts("XS A23E23 B23E23");

    // wait 1-2 sec to be sure button is fully back up

    // sec2 = seconds;

    // while (sec2 == seconds);

    LCDout(0, 0xC0); // position cursor at 2nd line

    LCDout(0, 0x0F);  // On, Cursor, Blink

 

 

    dialpos = 16;

    lastdial = 1060;

    selecting = 1;

    

    while (selecting < 2) {  // button not pressed (again)

      pos = newDial(); // readchannel(INCH_2); // read position

      LCDout(0, 0x80); // position cursor at 1st line, 3rd char "Px "

      switch (pos) {

      case 0:

{

 LCDouts("Exit Menu   ");

 break;

}

      case 1:

      case 2:

{

 LCDouts("System Menu");

 break;

}

      case 3:

      case 4:

      case 5:

{

 LCDouts("A: Charge ");

 LCDdigit(pos-2);

 break;

}

      case 6:

      case 7:

      case 8:

{

 LCDouts("A: Edit   ");

 LCDdigit(pos-5);

 break;

}

      case 9:

{

 LCDouts("System Menu");

 break;

}

      case 10:

      case 11:

      case 12:

{

 LCDouts("B: Charge ");

 LCDdigit(pos-9);

 break;

}

      case 13:

      case 14:

      case 15:

{

 LCDouts("B: Edit   ");

 LCDdigit(pos-12);

 break;

}

      }

      LCDouts("    ");    

      LCDout(0, 0xC0+pos); // position cursor at 2nd line pos

    }

    //

    // Execute Selected Action, and Return.

    // For Editing, because of stack overflow problems,

    // we return back to main to execute them.

    //

    switch (pos) {

      

    case 0: return 0;

    case 1:

    case 2:  return 1;

      

    case 3:

    case 4:

    case 5:      

      {

return 0;

      }

    case 6:

    case 7:

    case 8:            

      {

return pos-4;  // 2,3,4

      }

    case 9:  return 1;

      

    case 10:

    case 11:

    case 12:        

      {

return 0;

      }

    case 13:

    case 14:

    case 15:

      {

return pos-8;  // 5,6,7

      }

    }

    // return if not editing settings

    return 0;  // done

  }

}

 

 

 

 

//

// Input Button interrupt occurs when button pressed

// Button is in two states, up or down.

// if it's up, we interrupt when it goes down,

// but don't change state unless the button stays down

// for a sufficient time, 30-50ms.

// If it was down, and goes back up,

// we acknowledge the button by incrementing the selecting variable.

//

 

 

interrupt (PORT1_VECTOR) p1int(void)

{

  int in = P1IFG;

  if (in & 0x1) {   // P1.0 changed hardware state

    //

    // If button state was up, wait for a while to confirm it is now down

    //

    if ((P1IES & 0x01) == 0x01) { // int on high-low transition, so button is up and went down

      //

      // Aclock is running at 2-Mhz.  60,000 ticks would be 30msec.

      // we wait until it rolls through zero twice, that would be anywhere from 60k-120k

      // while (TAR > 4) {

      // in = P1IN;

      // }

      in = P1IN;      

      while (TAR > 4) {

in = P1IN;

      }

      // now check if button is still down, if so, change interrupt to fire

      // when it goes up, and return from interrupt

 

 

      if ((in & 0x01)==0x00) {  // button is still down

P1IES = 0x00; // now interrupt on button-up low-high transition

      }

    }

    // we have low-high interrupt, so button was down, now it is up. 

    else {

      // while (TAR > 4) {  // wait until TAR rolls to zero

      // in = P1IN;

      // }

      in = P1IN;      

      while (TAR > 4) {

in = P1IN;

      }

      if ((in & 0x01)==0x01) {  // button is still up

P1IES = 0x01; // now interrupt on button-down high-low transition

// button is now considered pressed

if (selecting==0) {

 selecting=1;

 _BIC_SR_IRQ(CPUOFF);             // Clear CPUOFF bits to wake up main loop

}

else {

 selecting++;  // increment to indicate button press

}

      }

    }

  }

  P1IFG = 0;  // clear handled interrupt

}

 

 

 

 

unsigned short tar;

 

 

//

// Timer_A Interrupt Vector (TAIV) handler for CCR0 and CCR0 capture/compare,

// and overflow interrupts

interrupt (TIMERA0_VECTOR) a0timer(void)

{

  unsigned short v;

  tar = TAR;

  if (TAIV == 10) {

  }

}

 

 

//

// TACH signal went low

// We get the rpm by measuring how long it was from

// the last event to this one for the last 4 events (2 revolutions)

// and generate a running average from them.

// If we are in startup with less than 4 events, just use the last one.

//

// To get rpm, we get the rev per second.  we have a 32k clock,

// so 32768 ticks per second, and thus 32768 / ticks = rev per second,

// so if we get 16384 ticks, it would be 2 revolutions in a second.

// We divide that in half since we have 2 events per revolution,

// so 16384 / ticks = rev per second, if we multiply by 60 seconds per minute,

// we get revolutions per minute (rpm).

// We should not see less than 100rpm during cranking, which is 10,000 ticks

// and thus times 4 = 40,000 which still fits into a 16bit unsigned short.

 

 

unsigned short tachTimes[4];

 

 

interrupt (PORT2_VECTOR) p2int(void)

{

  unsigned short in = P2IFG;

  unsigned short tar = TAR;

  unsigned short tach0;

  // if (in & 0x20) { }  // P2.5 changed hardware state

  if (tar < lastTach) {  // timer rolled over

    tach0 = tar + (0xFFFF - lastTach);  // get up to here, plus the roll

  }

  else {

    tach0 = tar - lastTach;

  }

  lastTach = tar;

  tach++;  

  if (tach > 3) {

    tachTimes[ (tach & 0x3) ] = tach0;

    // now compute average of last four values

    tach0 = tachTimes[0] + tachTimes[1] + tachTimes[2] + tachTimes[3];

    tach0 = tach0 >> 2;

    // do injector spray after each ignition event once we've made at least one revolution

    rpm = (32768 / tach0) * 3;  // this gives us a better number

 

 

    startInjector();

    // wake up main loop to update injectorPulseWidth after we have started

    // nope, we must do it here, because if main is running the display (which takes 100ms)

    // wake up will do nothing, so we must force it now from interrupt.

    // we re-enable interrupts for pwm timers to execute.

    // if we get another tach interrupt before we're done here, we are toast!

    

    // _EINT();  // Now Enable interrupts 

    // updateInjectorWidth();

    // updateThrottle ();     

  }

  // rpm = (16384 / tach0) * 60;

  rpm = (32768 / tach0) * 3;  // this gives us a better number

  P2IFG = 0;       // clear handled interrupt

  _BIC_SR_IRQ(CPUOFF);             // Clear CPUOFF bits to wake up main loop

  return;

}

 

 

//

// spray the fuel injector.

// do a peak of full-on for 1.2 ms, then 25% pwm duty for remaining on-time

// 

void startInjector() {

  tar = TAR;

  injectorStartTime = tar;   // get timer value of when to leave it off

  CCR2 = tar + 39;   // 39 is 1.2ms for 32k timer 

  CCTL2 = OUT;  // turn ON injector

  CCTL2 = CCIE | OUTMOD_5;  // turn off at CCR2 after 1.2ms, and interrupt

  injectorIsOn = 1;         // peak period

  injectorPulseWidth = injectorBaseWidth;    

}

 

 

void updateInjectorWidth() {

  //

  // When we get tach input we wake up here

  // to read the sensors and initiate the injector spray

  //

  // For our 4cyl 159ci tractor engine, and a 45lb/hr injector, we have a base injector time of 9ms for

  // each ignition event.   It is adjusted by the o2sensor, airTemp, waterTemp, and MAP readings 

  // 

  // At our max of 2500 rpm = 41.666 RpS = 1/x = 24ms per revolution.

  // With two events per rev, that leaves us 12ms, so we are almost 100% at 2500

  // At 2200 rpm = 36.6 RpS = 1/x = 27ms per revolution, so we have 13.6ms

 

 

  // We use the previous sensor values to initiate the injector pulse width,

  // and then while that is going, we can do the 2-3ms needed to update the sensor readings

  // 

  // read all the sensors, this takes 

  //

  // read the MAP using 3v reference, 16x sample

  ADC10CTL0 =  0;

  ADC10CTL0 =  ADC10ON | ADC10SHT_2 | SREF_0; 

  mapSensor = adcRead(6);

  

  // read dial to get base pulse width adjustment

  // baseWidth is 9ms, which is 32*9 = 288 clock ticks

  // 

  dial = adcRead(2);

  injectorBaseWidth = dial >> 1;  // divide in half,  0 to 512 range 0 to 16ms

 

 

  //

  // read the O2 sensor using 1.5v reference

  ADC10CTL0 =  0;

  ADC10CTL0 =  ADC10ON | REFON | ADC10SHT_2 | SREF_1;   // use 1.5v ref

  o2Sensor = adcRead(1);

  //

  // read the chip temperature as proxy for outside air temp  using 1.5v ref

  airSensor = adcRead(0xA);

  //

  // read water temp, 

  waterTemp = readWaterTemp(3);

 

 

  //

  // adjust pulse width for MAP and temps until water temp is at operating temp

  // and then use o2 sensor for closed loop feedback

  //

  injectorPulseWidth = injectorBaseWidth;    

  if (waterTemp < 15) {  // doing open loop

    //

  }

  else if (o2Sensor < 500) {  // o2sensor is lean, slightly richen until o2 goes rich

    // injectorPulseWidth = injectorPulseWidth + 1;

  }

  else if (o2Sensor > 520) {

    // injectorPulseWidth = injectorPulseWidth - 1;

  }

}

 

 

  unsigned short diff;

  unsigned short throttleHold     =   40;

  unsigned short throttleLowHold  =   64;

  unsigned short throttleOpen     =   10;

  unsigned short throttleClose    =  900;

  unsigned short throttleLowClose = 1600;

 

 

void updateThrottle ()

{

  //

  // adjust throttle position to maintain rpm setting

  //

  ADC10CTL0 =  0;

  ADC10CTL0 =  ADC10ON |  REFON | REF2_5V | ADC10SHT_2 | SREF_1;   // use 2.5v ref

  targetTPS = adcRead(4);

 

 

  throttleTimeOn = 8;  // constant

   

  // adjust the throttle PWM

  tps = adcRead(7);

  if (tps < targetTPS) {  // need to close it

    diff = targetTPS - tps;

    if (diff > 12) {

      // set pw to slow close

      // if tp is almost closed, we must use less force

      if (diff > 200) {

throttleTimeOff = throttleLowClose;

      }

      else {

throttleTimeOff = throttleClose;

      }

    }

    else {

      throttleTimeOff = throttleHold;

      if (tps > 800) {

throttleTimeOff = throttleLowHold;

      }

    }

  }

  else {

    diff = tps - targetTPS;

    if (diff > 12) {   // need to open it

      if (diff > 200) {

throttleTimeOff = throttleOpen; // fast open

      }

      else if (tps > 800) {

throttleTimeOff = throttleOpen;

      }

      else {

throttleTimeOff = throttleOpen;

      }

    }

    else {

      throttleTimeOff = throttleHold; 

    }

  }

}

 

 

 

 

//

// Timer_A Interrupt Vector (TAIV) handler for CCR1 and CCR2 capture/compare,

// and overflow interrupts

interrupt (TIMERA1_VECTOR) axtimer(void)

{

  unsigned short v;

  tar = TAR;

  v = TAIV;

  if (v == 2) {  // CCR1 compare

    // if throttle was ON,

    // then the output was turned off at compare,

    // and we must setup to turn it back on at next compare

    if (throttleIsOn) {

      CCR1 = tar + throttleTimeOff;

      // CCTL1 = 0;  // turn OFF throttle

      CCTL1 = CCIE | OUTMOD_1;  // turn on at CCR1

      throttleIsOn = 0;

    }

    else {

      CCR1 = tar + throttleTimeOn;

      // CCTL1 = OUT;  // turn ON throttle

      CCTL1 = CCIE | OUTMOD_5;  // turn off at CCR1

      throttleIsOn = 1;

 

 

    }

  }

  else if (v == 4) { // CCR2 compare

    // we initiate the full-on peak period,

    // we in the interrupt we are doing the pwm hold

    // use a on time of .125ms, offtime of .250ms

    // until the injectorOffTime is reached, then we leave it off

    if (injectorIsOn) {

      // if the on period has been reached, the spray is complete and we leave it off

      injectorIsOn = 0;

      unsigned short onTime = (tar - injectorStartTime);

      // turn back on if still not done with hold time.

      if (onTime < injectorPulseWidth) {

CCR2 = tar + injectorTimeOff;

CCTL2 = CCIE | OUTMOD_1;  // turn on at CCR2

      }

      else {

// keep it off, done

CCR2 = 0;

CCTL2 = 0;

      }

    }

    else {

      CCR2 = tar + injectorTimeOn;

      CCTL2 = OUT;  // turn ON injector

      CCTL2 = CCIE | OUTMOD_5;  // turn off at CCR2

      injectorIsOn = 1;      

    }

  }

  else {  // overflow, timer hit 0xffff

 

 

  }

}

 

 

unsigned short tdel = 1;

unsigned short t1 = 0;

unsigned short t2 = 0;

 

 

//

// MAIN

// 

int main(void)

{

  int x;

  unsigned short s;

  

  WDTCTL = WDTPW + WDTHOLD;             // Stop WDT

  DCOCTL = 0xE0;   // DCO=7, MOD=0

  BCSCTL1 = 0x07;  // RSEL=7    == 5Mhz for F1232

  // BCSCTL1 = (DIVA1 | DIVA0) | 0x07;  // Aclk/8 32k now 4k, RSEL=7,  4Mhz at 3v

 

 

  // check if we have a working 32k ACLK

  TACTL = TASSEL_ACLK + TACLR + MC_CONT;

  delay(1200);  // wait

  if (TAR == 0) {

    Reboot(); // can't run without working 32k clock, wait for it to warm up

  }

  P1OUT = 0x00;                     // Clear

  P2OUT = 0x00;                     // Clear

 

 

  //

  // copy from flash into memory sys config settings

  // check for bootstrap uninitialized values

  FConfig = (struct ConfigEFI *)0x1000;

  Config = *FConfig; 

  if (Config.valid == 0xFF) {

    Config.valid = 0;    

    SaveConfig();

    Reboot(); // can't run without working 32k clock, wait for it to warm up

  }

  

  // ADC10ON and 64x sample and hold time.

  // 2.5V reference

  // ADC10CTL0 =  REF2_5V | REFON | ADC10ON | ADC10SHT_3 | SREF_1;

  // ADC10ON and 16x sample and hold time.

  ADC10CTL0 =  REF2_5V | REFON | ADC10ON | ADC10SHT_2 | SREF_1;

  //

  // ADC option select

  // P2.0=A0 

  // P2.1=A1 O2

  // P2.2=A2 Dial

  // P2.3=A3 WaterTemp

  // P2.4=A4 RPM Dial

  // P3.6=A6 MAP

  // P3.7=A7 TPS Throttle Position

  //

  ADC10AE = 0xDF;

 

 

  // P2.5  tach input

  // P1.0  board button  input

  // P1.1  fuel pump relay output

  // P1.2  throttle 

  // P1.3  fuel injector  

 

 

  P1DIR = 0x0E;   // output for P1.1,2,3, input for P1.0

  P1OUT = 0x00;

  P2DIR = 0x00;

 

 

  P1IES = 0x00;  // init with high-low transition, so button must 1st go down

  P1IE  = 0x01;  // enable interrupts on P1.0

  

  P2IES = 0x00;  // init with high-low transition for TACH signal

  P2IE  = 0x20;  // enable interrupts on P2.5

 

 

  // PWM output for charge transistor control

  TACTL = TASSEL_ACLK + TACLR + MC_STOP;

  P1SEL = 0x0C;     // P1.3 is TimerA2 output for Throttle,  P1.2 is TimerA1 for Throttle

 

 

  // P1.0 is Button input, P2.3 is select dial

  initLCD();

 

 

  // Turn on LCD Display

  LCDout(0, 0x0C);  // Display On, no Cursor, no Blink

  delay(750*20);

  LCDout(0, 0x06);  // Move cursor to right

  LCDout(0, 0x01);  // Clear Display

  delay(750*20);

 

 

  //

  // initialize variables used by watchdog now before enable interrupts

  //

  tach = 0;

  selecting = 0;

  selectime = 0;

  seconds = 0;

 

 

  CCR1 = 0;

  CCR2 = 0;

  CCTL2 = 0x00; //init off

  CCTL1 = 0x00; //init off

  TACTL = TASSEL_ACLK + TACLR + TAIE + MC_CONT; // start timer

 

 

  //

  // see if button is down, if so we go into edit mode,

  // otherwise, get ready to run the engine

  //

  if (1) { // ((P1IN & 0x1)==0) {  // p1.0 is down

    LCDouts("   Nimble");

    LCDout(0, 0xC0); // position cursor at char 8  

    LCDouts("Motorsports, LLC");

    for(x=0; x<20; x++) {

      delay(25000);

    }

    LCDout(0, 0x01);  // Clear Display

    delay(750*20);  

    LCDouts("    Electronic      ");

    LCDout(0, 0xC0); // position cursor at char 8  

    LCDouts(" Fuel Injection 1.0 ");

    for(x=0; x<20; x++) {

      delay(25000);

    }

  }

 

 

  P1IFG = 0;  // clear button interrupt

  

  //

  // we are ready to run the engine, turn on the fuel pump

  //

  P1OUT = 0x02;

  

  WDTCTL = WDT_ADLY_1000;               // Set Watchdog Timer interval back to 1000 (1-sec)

  // WDTCTL = WDT_ADLY_250;

  IE1 |= WDTIE;                         // Enable WDT interrupt

  // _EINT();                              // Now Enable interrupts  

 

 

  // Init throttle PWM

  // CCR0 = 0xFFFF;

  // TACTL = TASSEL_SMCLK + TACLR + TAIE + MC_UPTO_CCR0; // start timer    

  TACTL = TASSEL_ACLK + TACLR + TAIE + MC_CONT; // start timer

  //

  // crack open the throttle for starting

  //

  throttleIsOn = 1;

  throttleTimeOn = 640;

  throttleTimeOff = 3200;

  CCR1 = throttleTimeOn;

  CCTL1 = OUT;  // turn ON throttle

  CCTL1 = CCIE | OUTMOD_5;  // turn off at CCR1 and interrupt

 

 

 

 

  injectorBaseWidth = 9*32;   // 9ms

  injectorPulseWidth = injectorBaseWidth;

  injectorTimeOff = 32;

  injectorTimeOn = 32;

  dstate = 0;

  

  _EINT();  // Now Enable interrupts

 

 

  while (1) {

    selecting = 0;      

    //  Sleep until next interval status update

    //_BIS_SR(CPUOFF);                 // Enter LPM3

    //_NOP();

 

 

    // we wake up when ignition event occurs,

    // recalculate the injector on time

    

    updateInjectorWidth();

 

 

    t1 = TAR;

 

 

    updateThrottle ();     

 

 

    t2 = TAR;

 

 

    dstate++;

    // do display in tiny steps

    if (dstate == 1) {    

      LCDout(0, 0x80); // A position

    }

    else if (dstate == 2) {

      //LCDouts("F:");

      //val = adcRead(0x0A);

      LCDouts("R:");

    }

    else if (dstate == 3) {

      LCDout_adc(rpm);

    }

    else if (dstate == 4) {

      LCDouts(" I:");

    }

    else if (dstate == 5) {

      LCDout_adc(injectorPulseWidth);

      // LCDout_adc(throttleTimeOff);      

    }

    else if (dstate == 6) {

      LCDout(0, 0xC0);  // Move Cursor to position for this charger

    }

    else if (dstate == 7) {

      LCDouts("T:");

    }

    else if (dstate == 8) {

      // LCDout_adc(waterTemp);

      LCDout_adc(tps);

    }

    else if (dstate == 9) {

      LCDouts(" D:");

    }

    else if (dstate == 10) {

      LCDout_adc(targetTPS);

    }

    else {

      dstate = 0;  // restart

    }

    t2 = TAR;

 

 

    // see if we woke up from button press interrupt

    if (selecting > 0) {

      selectime = 0;

      // because of stack overflow problem, we return from buttonPress

      // and execute any editing choice here.

      x = buttonPress();

      if (x == 0) {

LCDout(0, 0x0C);  // On, no Cursor, no Blink

selecting = 0;

      }

      else if (x == 1) {

// sysSettings();

// SaveConfig(0);

      }

      else if (x < 5 ) { // edit settings, 2,3,4

// editSettings("A", x-2, &cset);

// SaveConfig(); 

      }

      else if (x < 8) { // edit settings   5,6,7

// editSettings("B", x-5, &cset);

// SaveConfig(); 

      }

    }

  }

 

 

}

Edited by bluehash
Added code tags.

Share this post


Link to post
Share on other sites

This is awesome! I've always wanted to make my own electronic ignition and EFI for vintage motorcycles but I have no time to actually do it. Thanks for the post nimblemotors! Do you have any good resources you can share that helped you with the project?

Share this post


Link to post
Share on other sites

The code I posted was just the fuel injection control.  The ignition control is an independant msp430 board.

Here is the code for the ignition.  Was looking at it today, as I've got a new project to control a 12A rotary engine

that is going into a jet boat I'm building, so thought I'd post it while I have it up. This code is much cleaner as it was all finished, unlike the EFI which was still a work in progress.

 

Hope it helps someone.

 

Cheers,

Jack

#include  <signal.h>
#include  <io.h>
#include <msp430/usart.h>
#include <msp430/common.h>
#include <msp430/basic_clock.h>

// Ignition Controller for 4-cyl coil-on-plug tractor, written by Jack Murray 2011
// We have a hall-effect crank sensor with 8 magnets, 1 missing, 45-degree before TDC
// We have two outputs to fire the two coils, one for cyl 1-4, and second for cyl 2-3
//
// We have the magnets spaced evenly 45-degree apart.
// The missing magnet is 135 degrees before #1 TDC, 
// We must have at least 3 triggers to get the timing between triggers
// and then recognize the missing magnet as double the previous two triggers
// then we know the next trigger is TDC and can calculate the delay needed
// for the timing advance.
//
// The engine cranking rpm is quite low, about 200 RPM, revolutions per minute.
// 200 RPM = 3.33 Revolutions per Second * 8 triggers = 26.64 triggers/sec
// 26.64 triggers/sec = 1/26.64 = .0375 sec/trigger or 37.5 ms 
// The tractor motor 4-cyl has a governer that limits it to 2200 rpm
// 2200 rpm = 36.666 RpS * 8 =  293 triggers/sec = .003409 sec/trigger or 3.40 ms
// 2000 rpm = 33.333 RpS * 8 =  266 triggers/sec = .00375  sec/trigger or 3.75 ms
//
// determine rpm from tigger ms:  ms per trigger 1/x = rev per sec / 8 * 60 = rpm
//
// we use a 32Khz clock signal, so we have 32 clock ticks per millisecond.
//   200rpm, 37.5ms * 32 = 1200 clock ticks
//  2000rpm, 3.4ms * 32 = 108 clock ticks


// F1122 board

#define ledOn()       P2OUT &= ~0x08;    //  Led ON  p2.3
#define ledOff()      P2OUT |= 0x08;    //  Led Off  p2.3

#define tachHigh()    P1OUT |= 0x04;   // 3v tach output == coil1
#define tachLow()     P1OUT &= ~0x04;
#define coil1High()   P1OUT |= 0x01;   // coil1 is trigger by p1.0
#define coil1Low()    P1OUT &= ~0x01;
#define coil2High()   P1OUT |= 0x02;   // coil2 is trigger using p1.1
#define coil2Low()    P1OUT &= ~0x02;


unsigned int lastCount = 0;
unsigned int currentTime = 0;
unsigned int thisTime = 0;
unsigned int doubleTime = 0;
unsigned int lastTime0 = 0;
unsigned int lastTime1 = 0;
unsigned int lastTime2 = 0;

//for debugging
unsigned short di = 0;
unsigned short dbgsize = 48;
unsigned short dbg[49];

unsigned short tachIsLow = 0;

unsigned int timeadv = 0;
unsigned short tar = 0;
unsigned short xtar = 0;
unsigned short ztar = 0;

//
// Watchdog Timer interrupt service routine
// we don't use it
interrupt (WDT_VECTOR) 
wdt() 
{
}

//
// P1 Interrupt not used
//
interrupt (PORT1_VECTOR) p1int(void)
{
  P1IFG = 0;  // clear handled interrupt
}

void restartTimer() {
  // 32khz clock, restart at zero, interrupt enable, divide clock by 1, run to 0-0xffff
  TACTL = TASSEL_ACLK + TACLR + TAIE + ID_DIV1 + MC_CONT;
}

//
// if we get a fatal error, blink the led slowly until we power cycle to reset
//
void crankError() {
  while(1) {
    TACTL = TASSEL_ACLK + TACLR + MC_CONT;
    ledOn();   //  Led ON
    tar = TAR;  // wait half second
    while (tar < 0xF000) {
      tar = TAR;
    }
    TACTL = TASSEL_ACLK + TACLR + MC_CONT;
    ledOff();   //  Led ON
    tar = TAR;
    while (tar < 0xF000) {
      tar = TAR;
    }
  }
}


//
// To fire the coil using the BIP373 darlington, we pull it low to break the connection to ground
// and force the secondary to high voltage ground through the spark plug.
// We must keep it off and then turn it back on about 2-4ms before the next firing to give
// the coil(s) time to charge up before the next firing.
// 
void firecoil(int which) {
  if (which == 1) {
    ledOn();  // turn on LED only for cylinder 1-4
    coil1Low();
    tachLow();
  }
  else if (which == 2) {
    coil2Low();
    tachLow();
  }
}

// setup the timing advance for the coil
// for the current rpm

// determine rpm from tigger ms:  ms per trigger 1/x = rev per sec / 8 * 60 = rpm
// (1 / ((RPM / 60) * 8)) * 1000 * 32 = clock ticks
// we use a 32Khz clock signal, so we have 32 clock ticks per millisecond.

// doing advance computation takes too long with our little 4mhz processor,
// so we reduce it to just use a table lookup.
// here is the table calculations for 0-2500 rpm
//                                            advance ticks
//  RPM    tickper45  /32    /16   /8    /45.0   30    20    16    12    8     6     4
// ------- ---------  ---    ---   ---   -----   --------------------------------------
//  2500 :     97 :    3 :    6 :   12 :  2.17    32 :  54 :  62 :  71 :  75 :  80 :  84   36 =  19 
//  2450 :    100 :    3 :    6 :   12 :  2.22    33 :  55 :  64 :  73 :  77 :  82 :  86   35 =  22 
//  2400 :    100 :    3 :    6 :   12 :  2.22    33 :  55 :  64 :  73 :  77 :  82 :  86   34 =  24 
//  2350 :    102 :    3 :    6 :   12 :  2.28    34 :  56 :  66 :  75 :  79 :  84 :  88   33 =  27 
//  2300 :    105 :    3 :    6 :   13 :  2.34    35 :  58 :  67 :  77 :  81 :  86 :  91   32 =  30 
//  2250 :    108 :    3 :    6 :   13 :  2.40    36 :  60 :  69 :  79 :  84 :  88 :  93   31 =  33 
//  2200 :    111 :    3 :    6 :   13 :  2.47    37 :  61 :  71 :  81 :  86 :  91 :  96   30 =  37 
//  2150 :    114 :    3 :    7 :   14 :  2.54    38 :  63 :  73 :  83 :  88 :  93 :  99   29 =  40 
//  2100 :    114 :    3 :    7 :   14 :  2.54    38 :  63 :  73 :  83 :  88 :  93 :  99   28 =  43 
//  2050 :    117 :    3 :    7 :   14 :  2.61    39 :  65 :  75 :  86 :  91 :  96 : 101   27 =  47 
//  2000 :    121 :    3 :    7 :   15 :  2.69    40 :  67 :  78 :  88 :  94 :  99 : 105   26 =  51 
//  1950 :    125 :    3 :    7 :   15 :  2.78    41 :  69 :  80 :  91 :  97 : 102 : 108   25 =  55 
//  1900 :    129 :    4 :    8 :   16 :  2.87    43 :  71 :  83 :  94 : 100 : 106 : 111   24 =  60 
//  1850 :    133 :    4 :    8 :   16 :  2.96    44 :  74 :  85 :  97 : 103 : 109 : 115   23 =  65 
//  1800 :    133 :    4 :    8 :   16 :  2.96    44 :  74 :  85 :  97 : 103 : 109 : 115   22 =  68 
//  1750 :    137 :    4 :    8 :   17 :  3.07    45 :  76 :  88 : 101 : 107 : 113 : 119   21 =  73 
//  1700 :    142 :    4 :    8 :   17 :  3.17    47 :  79 :  92 : 104 : 111 : 117 : 123   20 =  79 
//  1650 :    148 :    4 :    9 :   18 :  3.29    49 :  82 :  95 : 108 : 115 : 121 : 128   19 =  85 
//  1600 :    153 :    4 :    9 :   19 :  3.42    51 :  85 :  99 : 112 : 119 : 126 : 133   18 =  92 
//  1550 :    160 :    5 :   10 :   20 :  3.56    53 :  88 : 103 : 117 : 124 : 131 : 138   18 =  96 
//  1500 :    160 :    5 :   10 :   20 :  3.56    53 :  88 : 103 : 117 : 124 : 131 : 138   17 =  99 
//  1450 :    166 :    5 :   10 :   20 :  3.70    55 :  92 : 107 : 122 : 129 : 137 : 144   16 = 107 
//  1400 :    173 :    5 :   10 :   21 :  3.86    57 :  96 : 112 : 127 : 135 : 142 : 150   15 = 115 
//  1350 :    181 :    5 :   11 :   22 :  4.04    60 : 101 : 117 : 133 : 141 : 149 : 157   14 = 125 
//  1300 :    190 :    5 :   11 :   23 :  4.23    63 : 105 : 122 : 139 : 148 : 156 : 165   13 = 135 
//  1250 :    200 :    6 :   12 :   25 :  4.44    66 : 111 : 128 : 146 : 155 : 164 : 173   12 = 146 
//  1200 :    200 :    6 :   12 :   25 :  4.44    66 : 111 : 128 : 146 : 155 : 164 : 173   11 = 151 
//  1150 :    210 :    6 :   13 :   26 :  4.68    70 : 116 : 135 : 154 : 163 : 173 : 182   10 = 163 
//  1100 :    222 :    6 :   13 :   27 :  4.94    74 : 123 : 143 : 162 : 172 : 182 : 192    9 = 177 
//  1050 :    235 :    7 :   14 :   29 :  5.23    78 : 130 : 151 : 172 : 183 : 193 : 203    8 = 193 
//  1000 :    250 :    7 :   15 :   31 :  5.56    83 : 138 : 161 : 183 : 194 : 205 : 216    7 = 211 
//   950 :    266 :    8 :   16 :   33 :  5.93    88 : 148 : 171 : 195 : 207 : 219 : 231    6 = 231 
//   900 :    266 :    8 :   16 :   33 :  5.93    88 : 148 : 171 : 195 : 207 : 219 : 231    5 = 237 
//   850 :    285 :    8 :   17 :   35 :  6.35    95 : 158 : 184 : 209 : 222 : 234 : 247    4 = 260 
//   800 :    307 :    9 :   19 :   38 :  6.84   102 : 170 : 198 : 225 : 239 : 252 : 266    3 = 287 
//   750 :    333 :   10 :   20 :   41 :  7.41   111 : 185 : 214 : 244 : 259 : 274 : 288    2 = 318 
//   700 :    363 :   11 :   22 :   45 :  8.08   121 : 202 : 234 : 266 : 282 : 298 : 315    1 = 355 
//   650 :    400 :   12 :   25 :   50 :  8.89   133 : 222 : 257 : 293 : 311 : 328 : 346    0 = 400 
//   600 :    400 :   12 :   25 :   50 :  8.89   133 : 222 : 257 : 293 : 311 : 328 : 346    0 = 400 
//   550 :    444 :   13 :   27 :   55 :  9.88   148 : 246 : 286 : 325 : 345 : 365 : 385    0 = 444 
//

// at 1000+ rpm we use the a /8 index table, to get 19 entries, 12-31

unsigned short rpm2ccr0[] =
  {27,  // 2350-2500 27
   33,  // 2300      33
   37,  // 2250      37
   43,  // 2100      43
   52,  // 2000      52
   65,  // 1850      65 
   79,  // 1700      79 
   85,  // 1650      85 
   92,  // 1600      92 
   99,  // 1500      99 
   115, // 1400      115 
   125, // 1350      125
   135, // 1300      135
   135, // 1275      135
   148, // 1200      148
   163, // 1150      163 
   177, // 1100      177 
   185, // 1080      185
   193, // 1050      193
   200, // 1020      200
   211  // 1000      211
  };

unsigned short adv = 9;
unsigned short x45 = 0;

void coiladvance(unsigned int c45) {
  // we get the number of timer clicks for the last 45 degrees of rotation,
  // The number of clicks determines the RPM, and the degrees of advance
  // which translated into the amount of timer clicks before the coil fires
  xtar = TAR;
  x45 = c45;
  adv = c45 >> 3;  // divide ticks by 8
  // if the rpm is greater than 1000, we use the table lookup   
  if (adv < 32) {
    if (adv < 12) adv = 12;  // don't mess up if >2500 rpm
    adv = adv - 12;  // take out base
    timeadv = rpm2ccr0[adv];
    CCR0 = timeadv;
    CCTL0 = CCIE;
    tar = TAR;
  }
  // for idle or cranking, we have plenty of time to compute a 2-degree advance directly
  // and the timing is slow enough that approximite times can be off a lot
  else {
    // we get the number of timer clicks for the last 45 degrees of rotation,
    // The number of clicks determines the RPM, and the degrees of advance,
    // dpt = c45 / 45.0;  // get the number of degrees per timer tick
    //timeadv = (c45 / 45.0) * (45 - 2); // 45 would be TDC, we advance 2 degrees
    // which is c45 * 43/45, which to avoid floating point, we convert 43/45 to 60/64
    // so we can divide by 64.  so really this is 3 degrees advanced not 2
    // to multiply by 60, we do 32 + 16 + 8 + 4
    // at 200 rpm, we can overflow 16bits, so we use 32 bits
    unsigned long t1 = c45;
    unsigned long t2 = (t1 << 5);
    t2 = t2 + (t1 << 4);
    t2 = t2 + (t1 << 3);
    t2 = t2 + (t1 << 2);
    timeadv = (t2 >> 6);
    // now setup a timer interrupt when we hit that timer tick to fire the coil
    // this MUST occur before the next trigger event, since if it doesn't
    // we have an error, or the engine speed increased significantly right after
    // this trigger so our timing estimate was way off.
    //timeadv = timeadv - coildelay;  // backup the time it takes to trigger the duraspark
    tar = TAR;
    CCR0 = timeadv;
    CCTL0 = CCIE;  
  }
}

//
// we passed a tooth/magnet
//
void tooth() {

  unsigned short rpm;

  currentTime = TAR; // get current counter
  restartTimer();  // restart from zero, don't allow interrupt unless timed out
  thisTime = currentTime;   // get the time of the last Tach

  ledOff();   //  Led OFF

  //debug data
  dbg[di++] = thisTime;
  if (di >= dbgsize) di = 0;
  
  // determine if the previous time was the missing tooth
  // see if thisTime is approximately double the last two
  // see if difference is less than 10%  (we use /8 shift that is 12.5%)
  doubleTime = lastTime1 + lastTime2;
  //int diff10 = thisTime >> 3; // thisTime * .15;
  int diff10 = thisTime >> 2; // 25%
  int diff = abs(doubleTime - thisTime);
  if ( diff < diff10 ) {
    // the next one will be TDC, so we determine the timing advance
    // and setup the timer to fire coil number 1
    // we get timing of the half of the previous 90 degrees from issing tooth
    // ledOn();   //  Led ON
    lastCount = 1;
    coil1High();  // start the current flowing into coils 1-4 if not already started
    tachHigh();
    coiladvance((thisTime >> 1));
    lastTime2 = lastTime1;  // we don't need this really
    lastTime1 = thisTime;
  }
  // the tooth before TDC+180 is where we must start the coil charge
  // if the RPM is above 1400, below that we can wait until the next tooth
  else if (lastCount == 3) {  
    // the next one will be TDC+180-45, so we determine the timing advance
    // and setup the timer to fire coil number 2
    // we get timing of the previous 45 degrees to determine advance timing
    //ledOn();   //  Led ON
    lastCount = 4;
    rpm = thisTime >> 3;
    if (rpm < 21) {  // faster than 1400 rpm
      coil2High();  // start the current flowing into coils 2-3
      tachHigh();
    }
    lastTime2 = lastTime1; 
    lastTime1 = thisTime;
  }
  // previous was not missing tooth.
  // see if the next one will be TDC+180
  else if (lastCount == 4) {  
    // the next one will be TDC+180, so we determine the timing advance
    // and setup the timer to fire coil number 2
    // we get timing of the previous 45 degrees to determine advance timing
    //ledOn();   //  Led ON
    lastCount = 5;
    coil2High();  // start the current flowing into coils 2-3 if not already started
    tachHigh();
    coiladvance(thisTime);     
    lastTime2 = lastTime1;  // we don't need this really
    lastTime1 = thisTime;
  }
  else if (lastCount == 6) {  
    // the next one will be the missing tooth, so we can't just turn on coil charge,
    // as it will be double time, so we setup the timer interrupt to start the coil charge
    // at the same tick count as the previous 45 degrees
    lastCount = 7;
    rpm = thisTime >> 3;
    if (rpm < 21) {  // faster than 1400 rpm
      // create timer interrupt when missing tooth would be so we can start the coil charge
      // coil1High();  // start the current flowing into coils 1-4      
      CCR0 = thisTime;
      CCTL0 = CCIE;
    }
    lastTime2 = lastTime1; 
    lastTime1 = thisTime;
  }
  //
  // We should have recognized the missing tooth, hmm
  // just carry on like we did?  or go into check mode, or failure mode?
  else if (lastCount == 7) {
    // ledOn();   //  Led ON
    coiladvance((thisTime >> 1));
    lastCount = 1;
    lastTime2 = lastTime1;  // we don't need this really
    lastTime1 = thisTime;
  }
  // inbetween tooth, count it and push down the times
  // if we are starting up and haven't recognized the missing tooth,
  // OR the crank speed isn't consistent to recognize the missing tooth,
  // we just keep incrementing the count until we find it
  // and restart it back to 1 so TDC+180 will be recognized.
  else if (lastCount > 36) {
    // we've spun around 3 times, and still didn't recognizing missing tooth,
    // something is wrong, flash error probably lost magnet bad sensor etc
    // they must power cycle to try again.
    crankError();
  }
  else {
    lastCount++;
    lastTime2 = lastTime1;
    lastTime1 = thisTime;
  }
}

//
// The hall sensor interrupt on port 2.2
//
interrupt (PORT2_VECTOR) p2int(void)
{
  tooth();  // when magnet passes the sensor, p2.2 goes low
  P2IFG = 0;  // clear handled interrupt
}

//
// If the timer counter has overflowed, 
// then the crank has slowed/stopped rotating.
// We must resynch to the missing tooth in this case since our timing
// is no longer valid.  We set lastCount to 8 so it must recognize the
// missing tooth, and then restart the timer.
// turn off the coils so they don't overheat if engine is stopped
//
interrupt (TIMERA1_VECTOR) atimer(void)
{
  lastCount == 8;
  restartTimer();
  coil1Low();  // stop any current flowing into coils
  coil2Low();  // stop any current flowing into coils
  tachLow();
}

//
// The Timer Compare interrupts to fire a coil
// If lastCount == 1 then we fire coil 1
// If lastCount == 5 then we fire coil 2
// If lastCount == 6 then we start charge on coil 1
//
interrupt (TIMERA0_VECTOR) a0timer(void)
{
  CCTL0 = 0;  // turn off compare interrupts
  if (lastCount == 1) {
    firecoil(1);
  }
  else if (lastCount == 5) {
    firecoil(2);
  }
  else if (lastCount == 7) {  // missing tooth interrupt to start coil charge
    coil1High();
    tachHigh();
  }
  else {
    // this is an error and should not occur
    lastCount == 8;
  }
}


//
// MAIN
//
int main(void)
{
  int i;
  
  // cheat sheet
  // 0100 = 4
  // 1000 = 8
  // 1001 = 9
  // 1010 = A
  // 1011 = B
  // 1100 = C
  // 1101 = D
  // 1110 = E

  //
  // setup the MSP430 1121 for operation
  //
  WDTCTL = WDTPW + WDTHOLD;             // Stop WDT
  DCOCTL = 0xB0;   // DCO=5, MOD=0
  BCSCTL1 = 0x07;  // RSEL=7    == 4Mhz

  P1SEL = 0x00;     // P1.0-4 are I/O
  P1DIR = 0x0F;     // P1.0 P1.1 are outputs to fire coil, P1.2 tach output,  P1.3 ?
  P1IES = 0x00;     // no interrupts for P1
  P1IE  = 0x00;     // no interrupts for P1

  P2SEL = 0x00;     // P2.0-8 are I/O
  P2DIR = 0x0B;     // P2.3 is LED output, P2.2 is tach input
  P2IES = 0x04;     // interrupt P2.2 on high-low transition
  P2IE  = 0x04;     // use interrupts for P2.2 

  // init all off
  CCTL0 = CCTL1 = CCTL2 = 0;

  //P2.0,2.1 are ADC inputs A0,A1
  //ADC10CTL0 =  ADC10ON | ADC10SHT_3;      // ADC10ON and 64x sample and hold time.
  //ADC10AE = 0x03;                         // P2.0, P2.1 ADC option select

  //
  // We have a 32Khz crystal
  //
  // start timer in continuous 0-0xFFFF mode,
  // timer will interrupt after 1 second if we don't reset it before then
  //
  // TACTL = TASSEL_ACLK + TACLR + TAIE + MC_CONT;

  // flash the LED so we know the software was running when powered up
  // and then leave it on until a tooth is recognized
  TACTL = TASSEL_ACLK + TACLR + MC_CONT;
  ledOn();   //  Led ON
  tar = TAR;  // wait a little bit
  while (tar < 0x1000) {
    tar = TAR;
  }
  TACTL = TASSEL_ACLK + TACLR + MC_CONT;
  ledOff();   //  Led Off
  tar = TAR;
  while (tar < 0x1000) {
    tar = TAR;
  }
  ledOn();  // now leave it on

  // now setup timer for use.
  restartTimer();
  
  //
  // Have Watchdog Reset if we get stuck somewhere after 8ms ?
  //
  coil1Low();
  coil2Low();
  tachLow();
  lastCount = 8;  // force missing tooth discovery
  
  WDTCTL = WDTPW + WDTHOLD;       // Stop WDT
  IE1 |= WDTIE;                   // Enable WDT interrupt
  _EINT();                        // Now Enable interrupts

  //
  // Main loop does nothing, we just wait to process the tooth interrupts
  while(1)
  {
    _BIS_SR(CPUOFF);                    // Enter LPM0
    _NOP();                             // Required only for C-spy
  }
}


Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...