/********************************************************************************** * * * ngencontrol -- generator controller * v1.0a34 Aug 8, 2007 mjn -- put derivative code back to 1.0a30 version -- it was right before * divide integral portion by 32 instead of 4. this gives us more * fractional resolution for the iterm. * v1.0a33 July 27, 2007 mjn -- modify stop1 to not change to run2, or monitor1 * unless both rpm and oil pressure are above minimum * v1.0a32 july 20, 2007 mjn -- fix error in derivative portion of PID * v1.0a31 June 29, 2007 mjn -- first limited release of alpha level code * * Copyright (c) 2007 Martin Nile * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * martin . nile @ gmail . com * * * *********************************************************************************/ #include #include //#include // handle CPU sleep (used in a/d conversion) #include // watch dog timer support #include "ngencontrol.h" #include // we need this for the "abs" function /********************************************************************************** * * * Generator Control Mainline * * *********************************************************************************/ int main( void ) { ports_init(); //Ports Initialization USART_Init(BAUD9600); // set up the UART 9600 baud sei(); //Enable interrupts putstr("\r\nGencontrol 1.0a34\r\n"); while(1) mainstate(); } /********************************************************************************** * * * Initialization Code * * *********************************************************************************/ void ports_init(void) { DDRB = PORTBDIR; DDRC = PORTCDIR; DDRD = PORTDDIR; PORTB = PORTBPULLUPS; PORTC = PORTCPULLUPS; PORTD = PORTDPULLUPS; // Timer/Counter 0. stepper motor clock // 8 bit, /256 prescale // timer is preloaded and started with each step of the motor TCCR0 = (1 << CS02); // 62500 ticks/second // timer is not started until the stepper is energized // Timer/Counter 1. tachometer clock // 16 bit /256 prescale. 62500 ticks per second TCCR1A = 0; // normal counter operation TCCR1B = _BV(ICNC1) // Input capture noise cancellation | (0 << ICES1) // Input capture on falling edge | _BV(CS12); // clk/256 TIMSK |= (1 << TICIE1) |// Input Capture Interrupt Enable (1 << TOIE1); // Overflow Interrupt Enable // Timer Counter 2. realtime clock // 8 bit /256 prescale=62500 counts/sec. 250 count = realtime clock 250 interrupt / sec TCCR2 = (1 << WGM21) | // clear timer on compare (1 << CS22) | // /256 prescale = 62500 counts/seconds (1 << CS21); OCR2 = REALTIMECLOCKTICKS; // 62500/250 = 250 interrupts per second TIFR |= (1 << OCF2); // enable output compare TIMSK |= (1 << OCIE2); // enable output compare interrupt. // set up MCU control register to enable sleep with ADC noise reduction mode // the cpu will be set to sleep mode immidately after initiating an a/d conversion // the cpu will wake up from sleep mode upon a/d interrupt, or timer interrupt. // MCUCR = 0b00101000; //MCUCR = (1 << SE) | // sleep // (1 << SM0); //ACD noise reduction mode // enable the a/d converter with a/d clock=sysclock/128 // we will be doing single conversions. The conversion will be // intiated just before we want to read the adc ADCSRA = (1 << ADEN) | // analog to digital conversion enabled (0 << ADIE) | // a/d interrupt disabled (1<>8); UBRRL = (unsigned char) baudrate; /* Enable UART receiver and transmitter */ UCSRB = ( ( 1 << RXCIE ) | ( 1 << RXEN ) | ( 1 << TXEN ) ); /* Set frame format: 8 data 2 stop */ UCSRC = (1< steppercurrent) { steppertarget=steppercurrent; } if(steppertarget == steppercurrent) { PORTD &= ~((1 << STEPPER1) | // Turn off all stepper coils to save power (1 << STEPPER2) | // (otherwise the limiting resistors get too hot) (1 << STEPPER3) | (1 << STEPPER4)); TIMSK &= ~(1 << TOIE0); // disable the stepper timer overflow interrupt; } else { // move to the next step if(steppertarget < steppercurrent) { steppercurrent--; // counter clockwise if(laststep-- == 0) laststep=3; } else { steppercurrent++; // clockwise if(laststep++ == 3) laststep=0; } PORTD = stepmap[(int)laststep] | (PORTD & 0xf); TCNT0 = steppertime; // reload the counter for the next step } } // Tachometer capture // Timer Counter 1 Input Capture Interrupt // whenever the tach pulse happens, the timer1 count is saved and // the interrupt is invoked. Subtract the previous reading from the // current reading to get the total number of ticks between tach pulses. ISR(TIMER1_CAPT_vect) { prevtachticks = tachticks; tachticks = (((long)tachoverflow << 16) + ICR1) - prevtach; prevtach= ICR1; tachoverflow=0; } // timer Counter 1 overflow interrupt // the 16 bit counter overflows about once a second ISR(TIMER1_OVF_vect) { tachoverflow++; } // realtime clock // timer counter 2 output compare interrupt ISR(TIMER2_COMP_vect) { // in CTC mode, the timer is cleared on compare, so we don't need to subtract. //TCNT2 -= (REALTIMECLOCKTICKS); if(++realtime >= 25) { // every 25 ticks, bump the tenths counter if(++tenths >= 10) { // every 10 times, bump the seconds counter tenths=0; seconds++; } realtime=0; } //if(++realtime >= 250) { // after 250 ticks, bump the second counter by one // realtime=0; // seconds++; //} } /* USART Interrupt handlers */ // USART Receive Data Ready ISR(USART_RXC_vect) { unsigned char data; unsigned char tmphead; /* Read the received data */ data = UDR; /* Calculate buffer index */ tmphead = ( USART_RxHead + 1 ) & USART_RX_BUFFER_MASK; USART_RxHead = tmphead; /* Store new index */ if ( tmphead == USART_RxTail ) { /* ERROR! Receive buffer overflow */ } USART_RxBuf[tmphead] = data; /* Store received data in buffer */ } // ISR(USART_TXC_vect) // USART Data Register Empty ISR(USART_UDRE_vect) { unsigned char tmptail; /* Check if all data is transmitted */ if ( USART_TxHead != USART_TxTail ) { /* Calculate buffer index */ tmptail = ( USART_TxTail + 1 ) & USART_TX_BUFFER_MASK; USART_TxTail = tmptail; /* Store new index */ UDR = USART_TxBuf[tmptail]; /* Start transmition */ } else { UCSRB &= ~(1<> 8); //average 256 samples } // read the analog to digital converter return a 10 bit value // channel = which ADC channel to read: 0=injector temp, 1=water temp, 2 = oil pressure int getadc10(unsigned char channel) { //int x; //int temperature; //ADMUX =(0b11100000 | channel); // use internal 2.56v ref,left adjusted, only read adch ADMUX=(0b11000000 | channel); // internal 2.56v ref adc0 right adjusted, read adc ADCSRA |= _BV(ADSC); // sleep until conversion completes //sleep_cpu(); while(ADCSRA & (1 << ADSC)); // wait for the conversion to complete return(ADC); } /********************************************************************************** * * Stepper motor routines * *********************************************************************************/ // setstepper -- set the speed control to a specific step count // assumes that the steppercurrent has already been calibrated void setstepper(int target) { steppertarget=target; if(TIMSK & (1 << TOIE0)) { // stepper is already running // it just needs a new target // don't force an immediate interrupt, because we will likely slip a step and // eventually lose track of where we are } else { // stepper is stopped, enable interrupts and force first step TIMSK |= (1 << TOIE0); // enable the stepper timer overflow interrupt TIFR |= (1 << TOV0); // force an immediate overflow to start the motor } } // recalibrate -- move the stepper all the way back to zero void recalibrate(void) { steppertarget=0; steppercurrent=4000; // this is just random guess just so that the stepper goes towards zero TIMSK |= (1 << TOIE0); // enable the stepper timer overflow interrupt TIFR |= (1 << TOV0); // force an immediate interrupt to start the motor (possibly interrupting a step already in progress) } /********************************************************************************** * * * State machine routines * * *********************************************************************************/ /******************************************************* * setstate -- change to the requested state * "setstate" saves the requested state, and captures the runtime clock * in the variable "statestarttime". This is used as the reference point for * states which require a delay of some sort. The initstate flag is set for * states which do initialization on the first invocation. * ******************************************************/ void setstate(unsigned char nextstate) { putstr("\r\n"); putstr(statenames[currentstate]); putstr("->"); currentstate=nextstate; putstr(statenames[currentstate]); putstr("\r\n"); statestarttime=seconds; // timestamp of when the state started initstate=1; timeout=0; } /******************************************************* * stop1 -- main entry point for the state system ******************************************************/ void stop1(void) { // if the run signal is present, and we already have oil pressure, then // we have probably just been reset while the engine was already running // switch to RUN state after a short delay to allow the tach to get a // reading on the current RPM. if(RUN && oilpressure >= minoilpressure && rpm > startrpm) { setstate(RUN2); } // if we have the run signal and no oil pressure, then start the engine if(RUN && oilpressure < minoilpressure && rpm == 0) { setstate(START1); crankretries=0; } // if we have oil pressure, but no run signal, then the engine has been started // manually. Change to the monitor mode after a short delay to allow the tach // to catch up. if(STOP && oilpressure >= minoilpressure && rpm > startrpm) { setstate(MONITOR1); } // otherwise keep waiting in stop1 state } /******************************************************* * start1 -- turn on the glow plugs and wait for glowplugdelay ******************************************************/ void start1(void) { if(initstate) { // first time, turn on the glow plug PORTC |= (1 << GLOWPLUG); recalibrate(); initstate=0; timeout=seconds+glowplugdelay; } // if we get a stop signal, then turn off the glowplugs and go back to stop state if(STOP) { PORTC &= ~(1 << GLOWPLUG); // glow plug off setstate(SHUTDOWN2); } // if we have reached the glow plug timeout, and the stepper has // finished the recalibrate, then turn off // the glow plug and move to the next state else if(stateruntime >= glowplugdelay && steppercurrent ==0) { PORTC &= ~(1 << GLOWPLUG); // glow plug off setstate(START2); } // otherwise keep waiting until the glow plug timeout } /******************************************************* * start2 -- pull the decompression lever, and start cranking ******************************************************/ void start2(void) { if(initstate) { PORTB |= (1 << DECOMPRESS) | (1 << STARTOUT); // pull the decompression lever and engage the starter initstate=0; setstepper(startspeed); // set the speed control to "start" timeout=seconds+decompressdelay; } // If the run signal goes away, close the decompression lever // and shut down. if(STOP) { PORTB &= ~(1 << DECOMPRESS); setstate(SHUTDOWN2); } // keep going until the decompression timeout happens if(stateruntime >= decompressdelay) { PORTB &= ~(1 << DECOMPRESS); // drop the decompression lever, keep cranking setstate(START3); } } /******************************************************* * start3 -- main cranking state ******************************************************/ void start3(void) { if(initstate) { timeout=seconds+cranktimelimit; initstate=0; } // if the run signal goes away, then do a shutdown if(STOP) { PORTB &= ~(1 << STARTOUT); // stop cranking setstate(SHUTDOWN2); } // if we have been cranking too long, stop cranking and go to take a break else if(stateruntime > cranktimelimit) { PORTB &= ~(1 << STARTOUT); // stop cranking // if we have retried too many times, stop altogether if(crankretries++ > crankretrylimit) { errorstate=CRANKRETRYERROR; setstate(STOP2); } // otherwise, delay and try again else { setstate(START6); } } // if we are cranking too slowly, then error else if(rpm <= mincrankspeed) { PORTB &= ~(1 << STARTOUT); // stop cranking errorstate=SLOWCRANKERROR; setstate(STOP2); // cranking too slow error } // if the rpm is above startrpm, then the engine has started else if(rpm > startrpm) { PORTB &= ~(1 << STARTOUT); // stop cranking setstate(START4); // go wait for the oil pressure to build up } // otherwise, keep cranking } /******************************************************* * start4 -- wait for the oil pressure to come up ******************************************************/ void start4(void) { if(initstate) { timeout=seconds+oilpressuretimeout; initstate=0; } // if the run signal goes away, then do a shutdown if(STOP) { setstate(SHUTDOWN2); } // if we dont have oil pressure by a certain amount of time, then // do a panic shutdown. else if(oilpressure < minoilpressure && stateruntime > oilpressuretimeout) { errorstate=LOWOIL; setstate(ESTOP1); } // if the oil pressure reaches the minimum (+ 2psi hysterisis), move on to warming up the engine else if(oilpressure >= minoilpressure+2) { setstate(START5); // warm up the engine } // if the rpm drops while waiting for oil pressure, delay and try starting again else if(rpm < startrpm) { setstate(START8); } } /******************************************************* * checkestop -- check all variables which could cause an estop ******************************************************/ unsigned char checkestop() { if(rpm > maxrpm) errorstate=OVERSPEED; if(oilpressure < minoilpressure) errorstate=LOWOIL; if(watertemp > maxwatertemp) errorstate=OVERTEMP; if(ESTOP) errorstate=MANUALESTOP; // if the oil pressure is 255, that indicates that we have lost the oil // pressure sender. if(oilpressure == 255) errorstate=OILLOSTERROR; // If we don't have an rpm reading, then we must have lost the tach sensor. // Treat this as an emergency stop situation // (the rpm sensor had to be working for us to even get to this state, so // something is definitely wrong); if(rpm == 0) errorstate=TACHLOSTERROR; return(errorstate); } /******************************************************* * start5 -- warm up the engine ******************************************************/ void start5(void) { if(initstate) { timeout=seconds+warmuptimeout; initstate=0; } if(checkestop()) { // check everything which could cause an emergency stop setstate(ESTOP1); } // if the run signal goes away, then do a shutdown if(STOP) { setstate(SHUTDOWN2); } else if(stateruntime > warmuptimeout) { // let the pid controller in the run1 state handle getting us to speed // setstepper(runspeed); // set the speed control to "run speed" setstate(RUN1); // we are now fully running } // if the engine stops while waiting for warmup, try to restart else if(rpm < startrpm) { setstate(START8); } } /******************************************************* * start6 -- overcrank delay. * We end up here if we cranked the engine too long without starting * delay for a while to let the starter cool down. Then go try starting again ******************************************************/ void start6(void) { if(initstate) { timeout=seconds+overcranktimeout; initstate=0; } if(STOP) { setstate(SHUTDOWN2); } else if(stateruntime > overcranktimeout) { setstate(START1); // if we have waited long enough, go try starting again } } /******************************************************* * start7 -- restart the engine after it stopped in run state * this state pauses to ensure the engine is really stopped * after rpm and oil pressure drop, the state moves to start1 * (or error if restart attempts exceeded) * this differs from the start8 state in that start8 goes back to start5 ******************************************************/ void start7(void) { if(STOP) { setstate(SHUTDOWN2); } if(ESTOP) { // manual estop is the only emergency stop supported in this state setstate(ESTOP1); } // when the engine is fully stopped (no rpm and no oil pressure) // increment our start attempt counter, and go try to restart if(rpm == 0 && oilpressure < minoilpressure) { // if we exceed the restart limit, then stop with error if(++restartattempts > restartretrylimit) { errorstate=RESTARTATTEMPTS; setstate(STOP2); } // otherwise, go try starting the engine again. else { setstate(START1); } } // if the engine somehow restarts itself while we are waiting for oil pressure // to drop, then switch back to run state. if(rpm > startrpm && oilpressure > minoilpressure) { setstate(RUN1); } // otherwise, keep waiting for the engine to stop } /******************************************************* * start8 -- restart the engine after it stopped while waiting * for oil pressure to build up. * this state pauses to ensure the engine is really stopped * after rpm and oil pressure drop, the state moves to start1 * (or error if restart attempts exceeded) * if the engine restarts itself, the state moves back to start5 ******************************************************/ void start8(void) { if(STOP) { setstate(SHUTDOWN2); } // when the engine is fully stopped (no rpm and no oil pressure) // increment our start attempt counter, and go try to restart if(rpm == 0 && oilpressure < minoilpressure) { if(++restartattempts > restartretrylimit) { errorstate=RESTARTATTEMPTS; setstate(STOP2); } else { setstate(START1); } } // if the engine somehow restarts itself while we are waiting for it to stop // then switch back to run state. if(rpm > startrpm && oilpressure > minoilpressure) { setstate(START5); } // otherwise, keep waiting for the engine to stop } /******************************************************* * run1 -- the main run state -- monitor a bunch of things ******************************************************/ void run1(void) { if(checkestop()) { // check everything which could cause an emergency stop setstate(ESTOP1); } // if the turn off the "run" switch, then to a clean shutdown else if(STOP) { setstate(SHUTDOWN1); } #ifndef PIDTUNE // PID setup uses uco and diesel empty as a/d ports // if we are runing on UCO, and the UCO tank goes empty // switch to diesel and raise an error. else if(RUNNINGONUCO && UCOEMPTY) { PORTD &= ~(1 << UCOVALVEBIT); // switch to diesel errorstate=UCOEMPTYERROR; } // if we run out of diesel (even if we are running on UCO), // switch to UCO and do a purge/shutdown else if(DIESELEMPTY) { PORTD &= ~(1 << UCOVALVEBIT); // switch to diesel errorstate=DIESELEMPTYERROR; setstate(SHUTDOWN1); } // if we are running on diesel, and the injector line is up to the minimum, // and the water temperature is at the minimum, then // switch to UCO fuel (as long as there is UCO in the tank). else if(RUNNINGONDIESEL && !UCOEMPTY && injtemp >= mininjtemp && watertemp >= minwatertemp) { PORTD |= (1 << UCOVALVEBIT); // switch to UCO } #endif // If we are running on UCO and the injector line, or water temp drops // below the minimum, then switch back to diesel. // note: the minimum temps have 2 degrees of hysterisis to prevent // oscillation around the switchover temperature else if(RUNNINGONUCO && injtemp < (mininjtemp-2) && watertemp < (minwatertemp-2)) { PORTD &= (1 << UCOVALVEBIT); // switch to DIESEL; } // if the engine speed drops below the start rpm, make sure it really is stopped, // then try restarting else if(rpm < startrpm) { setstate(START7); } // every tenth of a second, calculate any adjustment to the speed if(pidtenths != tenths) { pidtenths = tenths; setstepper(steppercurrent + pidcontroller()); } } /******************************************************* * run2 -- delay for up to 1 second for the rpm reading to show up * we get to this state if the controller wakes up and finds that the * engine is already running, (oil pressure) and the "run" signal is present. We delay * temporarily to give the tach a chance to start registering (0 rpm is * cause for an ESTOP when in RUN1 state. * The most likely reason to be in this state is that the engine was * started manually (because of a dead battery). * It is remotely possible to end up here after a watchdog timeout. * The watchdog timer is reset each time through the main state loop. If the * watchdog timer runs out, then it generates a reset signal to start over. * * as of version 1.0a33 this state is not needed, because we don't leave * the stop1 state until both the oil pressure and rpm are reading * above minimum. For now, this state just falls through to run1. ******************************************************/ void run2(void) { if(rpm >= startrpm ) { setstate(RUN1); // we are now fully running } // if we wait more than 1 second for an rpm reading to show up, then // we have something wrong (we have oil pressure and a run signal, but no rpm). else if(stateruntime > 1) { errorstate=TACHLOSTERROR; setstate(ESTOP1); } } /******************************************************* * monitor1 -- delay for up to 1 second for rpm before switching to monitor mode * monitor mode is an emergency fall back mode. If the battery is dead, * the controller is also dead. The engine can be manually started without the * controller. Once the engine starts, it begins to create power and the * controller will likely wake up. If the controller wakes up and finds * that there is oil pressure (which will come up long before the generator) then * the controller assumes the engine was started manually. In monitor mode * the controller will only monitor the critical engine variables. * * as of version 1.0a33 this state is not needed, because we don't * change out of stop1 state until after rpm and oil pressure are * both above minimums. For now, it just falls through to monitor2. ******************************************************/ void monitor1(void) { if(rpm >= startrpm ) { setstate(MONITOR2); // the engine is running, just monitor } // if the oil pressure disappears while we are waiting for the rpm, // then go back to the stopped state (this sometimes happened during // testing after a power on reset. The adc sometimes picked up noise // which made it look like we had oil pressure. This prevents ending up // in estop state caused by a short glitch in the oil pressure sensor reading else if(oilpressure < minoilpressure) { setstate(STOP1); } // if the oil pressure is 255, that indicates that we have lost the oil // pressure sender. else if(oilpressure == 255) { errorstate=OILLOSTERROR; setstate(ESTOP1); } // If we wait more than 1 second for an rpm reading to show up, then // we have something wrong. (If the engine is running it should take // 1/30th of a second to get our first rpm reading. // we have oil pressure, no run signal, but no rpm). else if(stateruntime > 1) { errorstate=TACHLOSTERROR; setstate(ESTOP1); } } /******************************************************* * monitor2 -- monitor the critical inputs, but don't do anything else * We get here by manually starting the engine without * turning on the "run" signal. ******************************************************/ void monitor2(void) { if(checkestop()) { // check everything which could cause an emergency stop setstate(ESTOP1); } else if(RUN) { // if they turn on the "run" switch, then change to full run mode setstate(RUN1); } else if(rpm < startrpm) { // manual shut down setstate(STOP1); } } /******************************************************* * shutdown1 -- switch from UCO to diesel, and wait for purge timeout ******************************************************/ void shutdown1(void) { if(initstate) { initstate=0; timeout=seconds+purgetimeout; if(RUNNINGONDIESEL) { // if already running on diesel, skip the purge setstate(SHUTDOWN2); } PORTD &= ~(1 << UCOVALVEBIT); // switch to DIESEL; } if(checkestop()) { // check everything which could cause an emergency stop setstate(ESTOP1); } else if(stateruntime > purgetimeout) { setstate(SHUTDOWN2); } } /******************************************************* * shutdown2 -- set the speed control to zero, wait for rotation to stop ******************************************************/ void shutdown2(void) { if(initstate) { recalibrate(); // force the stepper to zero speed initstate=0; timeout=seconds+shutdowntimeout; } // when the speed hits zero, and the oil pressure drops below the minimum, // then we know for certain the engine has stopped. if(rpm == 0 && oilpressure < minoilpressure) { setstate(SHUTDOWN3); } // if it takes too long to shutdown, then do an estop else if(stateruntime > shutdowntimeout) { errorstate=SHUTDOWNTIMEOUTERROR; setstate(ESTOP1); } } /******************************************************* * shutdown3 -- the engine has stopped, put the speed control back to run. * Note: This is a useless state, because unless the design gets changed, * we will lose power soon unless the 12 volt supply is plugged into * something which supplies power. To make this step useful, the system * system must be redesigned to have battery power available at all times. * In order to do that, we will need to redesign for minimum power consumption ******************************************************/ void shutdown3(void) { if(initstate) { recalibrate(); initstate=0; } if(steppercurrent == 0) { // wait for the recalibrate to finish setstepper(runspeed); setstate(STOP2); } } /******************************************************* * stop2 -- wait for the stop signal to appear before switching to full stop mode * this prevents the system from oscillating between run-estop-start-run-estop etc... ******************************************************/ void stop2(void) { if(rpm > 0 || oilpressure > minoilpressure) { setstate(SHUTDOWN1); } else if(STOP) { errorstate=0; setstate(STOP1); } } /******************************************************* * estop1 -- EMERGENCY STOP * shut down everything and pull the plug ******************************************************/ void estop1(void) { if(initstate) { // turn off glow plug PORTC &= ~(1 << GLOWPLUG); // stop cranking PORTB &= ~(1 << STARTOUT); // turn off UCO valve PORTD &= ~(1 << UCOVALVEBIT); // set the speed control to zero setstepper(-4000); // force the speed fully off // pull the decompression lever PORTB |= (1 << DECOMPRESS); initstate=0; } // keep holding the decompression lever until all rotation stops // and the oil pressure drops below the minimum. After that, // we can go to the stop2 state and wait for the user to // turn off the "run" switch. if(rpm == 0 && oilpressure < minoilpressure) { PORTB &= ~(1 << DECOMPRESS); // close the decompression lever setstate(SHUTDOWN3); } } /******************************************************* * getinput -- read all of the engine variables ******************************************************/ void getinput(void){ long x; injtemp=getadc(0); // ADC channel 0 = injector line temperature #ifndef PIDTUNE watertemp=getadc(1); // ADC channel 1 = water temperature #else watertemp=20; #endif oilpressure=getadc(2); // ADC channel 2 = oil pressure (this will need to be tweaked to convert volts to PSI) if(oilpressure < 255) { // 255 indicates loss of oil pressure sender oilpressure-= 19; // by sheer coincidence, other than a 19psi offset, the volts read directly as psi } // calculate the current RPM based on the number of timer ticks for the last revolution // calculate a moving average of the last two rotations because there is // only one power pulse every two rotations. Only average the rpm when in run1 mode // The earlier states sometimes get an obviously wrong rpm reading because prevtachticsk // may have happened during shutdown where it gets if(currentstate == RUN1) { x=(tachticks+prevtachticks)/2; } else { x=tachticks; } // tachticks is set by the tach interrupt service routine // this is the number of timer ticks in the last revolution // the tach timer runs at 3750000 ticks per minute // divide ticks/minute by ticks/rotation gives rotations/minute rpm=(int)((long)3750000/x); if(tachoverflow > 1) { rpm=0; } if(testchar()) { inputchar=getch(); } } /******************************************************* * updatedisplay -- show the current state information * If an error is pending, alternate the current state * display with the error display every other second ******************************************************/ void updatedisplay(void ) { // if they typed anything, see if they want to speed up, or slow down if(inputchar == '+') { setstepper(steppercurrent + 10); } else if(inputchar == '-') { setstepper(steppercurrent - 10); } else if(inputchar == ',' || inputchar == '<') { setstepper(steppercurrent -1); } else if(inputchar == '.' || inputchar == '>') { setstepper(steppercurrent +1); } else if(inputchar == 'R' || inputchar == 'r') { recalibrate(); } inputchar = 0; //1234567890123456 //state name //error name //timeout xxxx //Wnnn Onnn Innn V <--- water temp, oil pressure, Injector temp, Fuel (Diesel/Vegoil) //Rnnnn Sxxxx Tnnnn <--- RPM, stepper, time putstr(statenames[currentstate]); // first line (possibly blank) if(errorstate) { putstr(errornames[errorstate]); } else if(timeout > seconds) { putstr("Timeout "); print4(timeout - seconds); } putch('W');print3(watertemp);putch(' '); // second line (1st if state is blank) putch('O');print3(oilpressure);putch(' '); putch('I');print3(injtemp);putch(' '); if(RUNNINGONDIESEL) putch('D'); else putch('V'); putch('R');print4(rpm);putch(' '); // third line (2nd if state is blank) putch('S');print4(steppercurrent),putch(' '); putch('T');print4(seconds); /* putstr(", RPM="); print4(rpm); putstr(", Inj ="); print3(injtemp); putstr(", Water="); print3(watertemp); putstr(", Oil="); print3(oilpressure); putstr(", Step="); print4(steppercurrent); putstr(errornames[errorstate]); */ putch('\r'); } /******************************************************* * pidcontroller -- calculate the adjustment to the stepper position * * based on emprical tests: Pc=20, Kc=280 * * Ziegler-Nichols tuning: pgain=0.65 * Kc = 182 * Ti=0.5 * Pc = 10 * Td=0.12 * Pc = 2.4 * dgain= pgain*Td = 182*2.4=436 * igain= pgain/Ti = 18 *4 = 72 * All gain values are multiplied by 256 so we can do * the arithmetic in integers. The result is divided by 256 * ******************************************************/ int pidcontroller(void) { int error; long pterm; // proportional term. The pterm is long because on startup // the pterm has a very large error when multiplied by the pgain // will exceed the size of a normal int. int iterm; // integral term int dterm; // derivative term int temp; #ifdef PIDTUNE pgain=(getadc10(3)-8)*2; // pc3 (uco empty) is temporarily used as a pgain control if(pgain < 0) pgain=0; igain=getadc(4)-8; // pc4 (diesel empty) is used as igain control if(igain < 0) igain = 0; dgain=(getadc10(1)-8)*4; // pc1 (watertemp) is dgain control if(dgain < 0) dgain = 0; #endif error=TARGETRPM-rpm; // calculate the proportional portion pterm=((long)error*pgain); // calculate the integral portion // If we are at the stepper limit, and still want to go higher, // do not add any more to the integral if(!(PINB & LIMITHI) && pterm > 0) { temp=sumerror; } else { temp = sumerror + error; } if(temp > 5000){ // limit the integral windup temp=5000; } else if(temp < -5000){ temp=-5000; } sumerror = temp; iterm = (igain * sumerror)/32; // note!! /32 gives us more fractional resolution for the igain variable // Calculate the derivative portion dterm = dgain * (rpm - lastrpm); lastrpm = rpm; temp = (pterm + iterm + dterm) / 256; // if the adjustment is less than 2 steps, don't bother if(abs(temp) < 2) { temp=0; } #ifdef PIDTUNE putstr("\r\n"); print4(rpm); putch(' '); print3(pgain); putch(' '); print3(igain); putch(' '); print3(dgain); putch(' '); print5(temp); #endif return(temp); }