Jump to content
43oh

nimblemotors

Members
  • Content Count

    20
  • Joined

  • Last visited

  • Days Won

    3

Posts posted by nimblemotors

  1. Anyone have experience using this wireless module with the msp430?

    I have a pair running hooked to msp430's, not using any library, using pretty much all the chip defaults, just fixed length packets of 10 bytes.

    I have one running sending a packet every 250ms, I timed the xmit time, and it takes 33ms to send a packet at the default 4.8kbps.

    The other in recv mode.   It is getting packets, but losing about 99% of them, but every once in a while

    it gets one, which has me stumped.   After I get a packet I put it in standby, and have a watchdog run every second that puts it back in recv mode.

    If you have any experience or insight, I would appreciate it!

  2. Well, I have a custom car where I repalced 15lbs of wiring with a wireless control for the brakes and turn signals and hazards.

    I have a little msp430 board that gets a 'turn left' message and does the flashing itself until it hears a no-turn or right-turn message,

    and then it stops flashing.  Actual flashing code is about as simple as it gets, just use the watchdog timer at 1 second intervals,

    and turn on/off the light. You need a 3v relay or mosfet to toggle the 12v power (I use LEDs for my lights also, very low power)

  3. Well, I finally got this working..sort of.  It looks to me the 9850 board has an issue with bad solder joints or something,

    as it will output the signal but then stop and start again if I physically mess with it.  Thought it might be the 125mhz clock chip as pushing down it

    got it outputing again, but that is not consistent, sometimes it doesn't work I need to twist the board and it works again,

    and sometimes that doesn't work either, but eventually if I mess with it it outputs again until it stops again.

     

    What may be of interest here is the code to compute the 32bit tuning word.  In general it is a floating point multiply, no good on a 430..

    I broke it down into integer 16bit multiples.  If the code is of interest, I can post it,  it is on my other computer in the 'lab'. ;)

  4. One of those useless gadgets that people might like.  

    Cold beer never gets any better when warmer, so looks to me this is only applicable to too-hot drinks.

    While a coaster would be nice, it seems to me with variations in the insulation of a cup, you can't really

    know the fluid temperature from measuring the cup.  I think you need a new cup of tea.

    One powered by the hot liquid, and wireless connected to the internet and can scroll tweets on it...  

    Yeah, that is it, a $50 internet cup of tea.  Hope that helps. ;)

     

  5. I need to have a variable frequency generator in order to measure the inductance of a coil.

    There are these very cheap AD9850 boards that can output sine waves of any frequency up to even 40Mhz.

    Just what I need, so I took one of my old MSP430 battery charger boards and wired up the interface to the AD9850 board.

    But it is not working, I get no output.  I've seen lots of Arduino's interfaced to them, but not any msp430.

     

    I thought I figured out my problem when I discovered that at 5v power, a HIGH signal must be 4.9v,

    and as I run my 430 at 3v, not going to work.  But the 9850 also runs at 3.3v, and then just 2.4v was high,

    so I changed its power to 3.3v,  but no joy.

     

    Was hoping someone else might have interfaced a 430 to one of these boards with success.

     

  6. I've built a BLDC controller, but it is not a sensorless.  That really complicates things.  

    With the sensor, control is really quite simple software, the power hardware is the difficult part.

    My system was orginally tested on a small Novac BLDC 1/10 scale R/C car motor.

    I used it for a Mars 15hp pancake motor for an electric jetski I built (but never finished).

    I orginally used a IR2130 as the gate driver, but my newer version uses two LT1161,

    and six fets in a sot227 format, these are so much nicer, mounted to aluminum heatsink.

     

    Jack

     

    Series%20HXP%20200,%20SOT%20227_1.jpg

     

  7. 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
      }
    }
    
    
    
  8. 
    

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

    //

    //  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(); 

          }

        }

      }

     

     

    }

  9. I don't have a schematic of the battery charger, which is what the hardware was used for orginally.

    But I can tell you about the connection to the msp430. I used a LTC1155. It is a high-side mosfet driver that drives n-channel mosfet at +10v over the supply. So unlike most applications, I switch on/off the 12v to the fuel inject, instead of switching ground on/off. This was important for the battery charger, because switching the high side it could detect when a battery was attached, and automatically start charging them.

    The LTC1155 has two on/off inputs that are connected directly to the msp430 outputs. I used the TA1 and TA2 outputs,

    and did timerA pwm. hope that helps! Jack

  10. Hello again my friends,

    I posted the video of the full msp430 based EFI system running my tractor.

    You can see the waterproof box that holds the fuses and all the connections.

    Originally this tractor did not have a single fuse.

     

     

    The fuel injector is a TBI unit from a Geo Metro, just using the injector portion of the TB.

    The throttle plate is from a Prius, and is an electronic controlled throttle.

    Processor is a msp4301232, with two high-side mosfets (previously used to charge NiMH batterys),

    one used to control the throttle, the other controls the fuel injector. The ADC inputs from the 430 are used to read the analog sensor inputs (GM parts) the o2 sensor, the MAP, the water temp.

    The Tach input signal of course is the msp430 based coil-on-plug ignition I showed earlier.

     

    Also here is the dashboard I built for it, using msp430 processors that drive bargraphs on the LCDs

    for the readings. Fuel level, Water Temp, Battery Voltage, Oil Pressure, and RPM.

     

    a55679.jpg

  11. randomness isn't really needed, but just the perception of randomness, or said another way,

    there is no perception of a pattern.

    So just pick some numbers that appear random, and use them in order, and every third time skip over by 2

    and then do them backwards after 5 times.

     

    and btw, you are the devil!

  12. I need the mclock to be consistant for pwm and use the 32khz aclock to sync it.

    I've used the code below to do this. I have two issues, one is it seems I must divide Aclk by 8

    so it works, otherwise I think it is too fast.

    My real issue is how to I keep it in sync? In my previous application, i have timer outputs that I know

    have dead time where I can use CCTL2 (which is only control with Aclk input) during the deadtime

    to sync, but in my current situation, it isn't clear when I have time to resynch the mclock,

    the PWM on TA2 is almost constant. Am I just going to have to find some dead time to do it?

     

    Also, wouldn't it be valid to assume it doesn't need to be adjust as long as the internal chip temp

    reading doesn't change too much?

     

    //
    // At reset we adjust DCO to 4Mhz
    //
    setdco()
    {
     unsigned short ov, lastmclk, mclk;
    
     lastmclk = mclk = mticks = 0;
     TACTL = TASSEL_SMCLK + TACLR + MC_CONT;
     CCTL2 = CAP | SCS | CCIS_1 | CM_1;    // capture, CCIxB=Aclk, rising edge, no interrupts
     lastmclk = TAR;
     for(dco=0; dco<255; dco++) {
       DCOCTL = dco;
       ov = 0;
       // poll the capture flag waiting for the 32k clock to tick up
       while (1) {
         if ((CCTL2 & COV) == COV) {  // overflow
    CCTL2 &= ~(COV);     // clear flag
    ov = 1;
    break;
         }
         else if ((CCTL2 & CCIFG) == CCIFG) { // capture occured
    CCTL2 &= ~(CCIFG);     // clear flag
    break;
         }
       }
       if (!ov) {
         mclk = CCR2;  // get mclk count at capture
         mticks = mclk - lastmclk;
         lastmclk = mclk;
         if (mticks > 1023) break;  // done
       }
     }
    }
    
    main() { 
     DCOCTL = 0xE0;   // DCO=7, MOD=0.
     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(800);  // wait
     if (TAR > 0) {
       setdco();  // adjust DCO clock to 4Mhz
       setdco();  // adjust DCO clock to 4Mhz
     }
     else {
       Reboot(); // reboot
     }
     ...  
    

  13. I have do this often for my LCD display output, and wrote a routine called BCDdecode that does it for 3 digits,

    and then returns the remainder so it can work for more digits if needed.

    And also note if d3 is zero, it is just doing two digits

     

    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;
    }
    
    // and example use to display temp
    
    LCDout_temp (unsigned short temp)
    {
     unsigned short val = temp;
     char d1,d2,d3,d4;
    
     BCDecode(val, &d1, &d2, &d3);
     if (d3 > 0) {
       LCDdigit(d3);
     }
     else {
       LCDout(1, ' ');
     }
     LCDdigit(d2);
     LCDdigit(d1);
     LCDout(1, 'F');
    }
    
    

  14. Hello, I've got a project that uses the msp430 that I thought you folks would enjoy.

    I have an old 1960 Tractor that was running like crap, and turns out the distributor was junk.

    New parts have long since become obsolete, but who wants the old technology anyway (.025 gap plugs)

    So I have converted the ignition to electronic coil-on-plug,

    which eliminates the distributor and the plug wires, and uses 4 ignition coils, one on each spark plug,

    and now have vastly increase spark with 0.060 gap plugs.

     

    Here is a youtube video of it running in the final setup:

    OK, so they won't let me include URLs as a newbie, you can search youtube for my username and find them.

     

     

    You can view the older video that shows a little different setup, but it shows the msp430 processor board I used,

    which was created years ago for another application, so I have dozens if not hundreds of them.

     

    I've also used the processor board and some surplus LCDs to create digital gauges for the tractor.

    The gauages use the msp430 to read the sensors (oil pressure, water temp, rpm, fuel level, voltage),

    and generate a bargraph on the lcd to display the value. I embed these in a custom created faceplate using the original tractor dash bezel. You can see the end results here:

    OKay, no links allowed, so maybe later you can see it.

     

    I'm now working on a custom fuel injection system for the tractor using another 430, this one a 1232

    that was originally created as a battery charger.

     

    Hope you like it.

    Jack

×
×
  • Create New...