Jump to content

nimblemotors

Members
  • Content Count

    20
  • Joined

  • Last visited

  • Days Won

    3

nimblemotors last won the day on July 18 2013

nimblemotors had the most liked content!

About nimblemotors

  • Rank
    Member
  1. nimblemotors

    RFM69HCW

    Found out the problem, the sync values defaulted to zero which don't really work as sync values.
  2. nimblemotors

    RFM69HCW

    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!
  3. nimblemotors

    MSP430FR5969 DCO Accuracy

    It looks like you can't trim the clock with the same level of adjustments as the older chips.
  4. nimblemotors

    MSP430FR5969 DCO Accuracy

    In my R/C controller the DCO clock changes quite a lot over temperature, and when it got warm, the servo signal kept changing. So I used the 32k crystal to adjust the DCO to keep it consistent.
  5. nimblemotors

    Turn Signal/Hazard Flasher

    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)
  6. nimblemotors

    AD9850 Frequency Generator

    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'.
  7. nimblemotors

    Smart coaster project.

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

    AD9850 Frequency Generator

    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.
  9. nimblemotors

    Sensorless BLDC motor control

    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
  10. nimblemotors

    Tractor Engine Control

    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 } }
  11. nimblemotors

    Tractor Engine Control

    //****************************************************************************** // // 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(); } } } }
  12. nimblemotors

    Tractor Engine Control

    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
  13. nimblemotors

    Tractor Engine Control

    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.
  14. nimblemotors

    ANnoyATron

    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!
  15. nimblemotors

    Tractor Engine Control

    yep! that's it
×