Summary
When I started on my generator project , I always had in the back of my mind that I wanted the system to be fully automatic. The engine only has a manual speed control lever and a little button which pops up indicating adequate oil pressure. The engine has no provision for shutdown in the event of overheat or loss of oil pressure. I have been a computer programmer for over 25 years, so some sort of computer control was an obvious solution. This page documents the work towards that solution.
Microcontroller
The company where I work assembles circuit boards for various companies around the world. Being the "computer guy", if any problem shows up which involves a computer, I get called in to help. One of the problems that cropped up was a circuit board which had a built in CPU which had to be programmed via a cable plugged into the board after it was assembled. This was my first exposure to the newest version of in-system-programmable microcontrollers. The customer engineer had designed his board around an Atmel AVR Microcontroller. In order to solve the problem I ended up learning about the AVR cpu's and I liked what I found. The chips had a clean instruction set, ran at up to 20mhz. Best of all, Atmel provides a full development environment for free. The AVR line of CPUs runs from tiny 8 pin dip chips to 100 pin surface mount chips. Since I will be building everything by hand, I have no desire to design around a surface mount cpu, but I found a nice compromise in the ATMega8 CPU. This chip comes in a 28 pin dip, has 8 kilobytes of in-system-programmable flash memory, lots of i/o pins and analog to digital conversion. All of this on a chip which costs around $5.00
There are many other microcontrollers out there. Notably the "PIC" line of controllers from Microchip Technology. I'm sure the PIC line of CPUs would fit the bill nicely, but since I already had some experience with the AVR chips, I went with the Atmel solution.
Design Considerations
In working up the design for the controller I had the following requirements.
- Start/stop the engine remotely
- Warm the engine up at a reasonable rpm before switching to full speed
- Utilize the engine decompression for starting and emergency stop
- Provide for future glow plug support
- Monitor critical engine parameters (oil pressure, water temperature, rpm) and provide for emergency stop if any parameter exceeds its limits.
- Switch between diesel and Used Cooking Oil (UCO) fuel.
- Purge UCO fuel upon shutdown
- Manual emergency stop
- Keep track of hours operated on Diesel Fuel and UCO Fuel
- Keep track of how many "Starts"
- Provide for manual operation in case of battery failure, or controller problem
- Optionally allow engine parameters to be edited and stored in flash memory
- Optionally provide "Sag" support for the "lazy" governor on the engine
Starter and speed control
The engine already has electric start, so the remote start was easy. Warming up the engine at at slower rpm than full run speed meant that I needed a way to control the speed. I chose to use a stepper motor linked to a leadscrew actuator to move the speed control lever on the engine. Stepper motor/leadscrew assemblies show up routinely on e-bay where people snap them up to build computer controlled milling machines or robots. I ran across one which had been listed in the wrong category. I got it at the starting bid price.
RPM monitoring
To monitor RPM, I needed some way to sense the rotation of the engine. The easiest way is to count the alternating current put out by the generator. Since I am planning on monitoring cranking speed while starting the engine, the a/c frequency option was out. The next option was to use a "hall effect" sensor which detects a magnet attached to the flywheel. I considered that option, but I could not find a good place to stick a magnet onto the flyweel. If I glued the magnet to the wheel, I had visions of the magnet flying off and losing my speed sensor. The solution that I selected was to use a QRB1114 optical sensor which bounces infrared light off of the flyweel. A reflective strip on the wheel is all that is needed. In my testing, I put a white "sticker" on one blade of a small fan. The sensor picked up the 5000RPM pulses easily.
Update 7/26/2007 It turns out that the optical sensor was a bad idea for two reasons:
1. It does not work in direct sunlight. I came up with a little shield to protect the sensor, but the problem still exists.
2. On a hot day, the glue holding the reflective tape turned soft, then the tape came off and the engine stopped itself (because it lost the tach input).
I now feel that a hall effect, or inductive sensor are in order. I still don't like the idea of drilling a hole into the flywheel and gluing a magnet in place. One option is to use a hall effect gear tooth sensor and count the teeth on the flywheel. Or, I may count the 3 holes in the flywheel. Either way, I'll need to rewrite the tachometer code.
Temperature Monitoring
To monitor temperature, I decided to use the LM35temperature sensor. I would have preferred to use an LM34 which provides the output in degrees Fahrenheit, but there was a bunch of the LM35 chips in the scrap box at work, so that made the choice easy. The LM35 provides a very easy way to read tempeature directly with an analog to digiital converter. For every degree Celcius above zero, the LM35 raises the voltage by .01 volts. So 100ºC reads as exactly 1 volt. 25ºC reads as .25 volts. By using a 2.56 volt reference on an 8 bit converter, the volts read directly as degrees Celcius. Another option was to use an automotive temperature sender, but they are not neatly calibrated like the LM35.
I will be monitoring two temperature sources. Water temperature is of primary importance for monitoring the engine performance. If the water temperature exceeds a fixed limit, the controller will initiate an emergency shutdown. I will also be monitoring the temperature of the injector line just prior to the injector. This temperature is used in deciding when two switch between Diesel and UCO fuel. The generally agreed upon minimum UCO temperature is 70ºC with the engine at normal operating temperature. The injector line is heated with a 12 volt resistance heater and runs between 100ºC and 120ºC.
Oil Pressure Monitoring
To monitor the oil pressure, I needed to find something which could handle up to 300PSI (The Changfa really puts out that much pressure). The best solution that I found was to use a VDO pressure sender rated at 300PSI. The VDO sender varies the resistance to ground from 10 to 180 ohms. If I use a 180ohm resister to +5V then the full range of voltage will be between 0 and 2.5 volts which fits nicely with the 2.56 volt maximum on my a/d converter.
Hardware Design
Here is the current schematic for the controller: (click for full res., or PDF here.)
The system is built with wire-wrap and hand solder on a 4inch by 8inch perfboard. Most of the space on the board is occupied by the 4 relays and the 6 power resistors for the stepper motor.
Stepper Driver
The stepper motor driver is designed to drive a unipolar stepper motor. Each of the 4 coils is driven by a MOSFET in series with the coil with a current limiting resistor. The motor I ended up with is rated at 3.8 amps per coil. With a 12 volt power supply I need a 3.15ohm resistor to limit the current to 3.8 amps. I am using three 10ohm 10watt resisistors in parallel which gives me 3.3ohms. The total power consumption is greater than the 10 watt rating of the resistors, but the stepper motor will only be running for short bursts so this is not expected to be a problem. A more elegant solution would be to use pulse width modulation (PWM) to limit the current. I experimented with this, but I found that writing software to implement a 4 phase PWM driver was beyond what I wanted to tackle at the time.
In order to save space, the limiting resistors are shared between two coils. Since the stepper sequence never has coil 1 and coil 3 energized at the same time, they can share the same resistor. The same is true for coils 2 and 4. The diode across the stepper coil serves to absorb the voltage spike created by the collapsing field in the coil when the coil is turned off.
Relay Driver
The four relays in the system are driven by a ULN2003 integrated circuit. This circuit has seven darlington driver pairs, each with a built in clamp diode.
Analog Inputs
The ATMega8 cpu has a built in analog to digital converter (adc) which an analog multiplexor to select from multiple input signals. The analog signals are fed directly into the CPU. The ATMega8 adc has up to 10 bit resolution, but for this application, I am only using 8 bits. The reference voltage is 2.56 which makes each of the 256 possible combinations of an 8 bit value exactly .01 volts. In the case of the LM35 temperature sensor, .01 volts is the same as 1degree Celcius.
Digital Inputs
The Diesel empty, UCO empty, stepper limit and ESTOP lines are fed directly into the CPU. The CPU is configured to enable a built in "pullup" resistor. In the normal state, the line is pulled up to +5V. When the Diesel empty switch closes, it pulls the line to ground.
The "stop/run" signal is similar except that it is isolated using a 4N26 optical isolator. (The "stop/run" signal will be running a long distance to the house and is more likely to pick up voltage spikes which could damage the CPU.)
I should probably optically isolate all of the signals being fed to the CPU. However, I have not yet figured out how to optically isolate an analog signal. Since I have 3 different analog signals being fed into the cpu, a few other lines isn't going to hurt. (Or so I hope.)
Software Design
The controller software is written in 'C' under the Atmel AVR Studio. The software is implemented so that all of the timing critical portions are implemented in the background by way of timers and interrupts. The rest of the functions are performed by a "State Machine" which controls everything else.
The main loop pseudo-code looks like this:
while(true) {reset watchdog timer
read engine parameters
check for emergency stop request
once per second update the display
execute current state code
}
The main loop is executed several hundred times per second. If for any reason the loop gets stuck, the watchdog timer will expire in half a second and force a system reset.
Here is the diagram showing the state machine: (click to see a full res., or PDF here.)
The state machine always starts in the "STOP1" state. Each state has one or more events which will cause the machine to move to a new state. Each state has a timer variable indicating how many seconds a state has been running. This variable is used in implementing the various delays in the system. The simplest state code is for a state which implements a timeout. Here is what the "START6" state looks like:
/*******************************************************
* start6 -- overcrank delay.
******************************************************/
void start6(void) {
if(stateruntime > overcranktimeout) {
setstate(START1); // if we have waited long enough, go try starting again
}
}
This is the state which is executed after the engine has cranked too long without starting. This state pauses for a while to allow the starter motor to cool down before attempting to start again. (The state machine will only allow so many retry attempts before stopping with an error.) This state code is executed repeatedly by the main state loop until the timer runs out and the state changes to the "START1" state.
Each state also has an "initialize" variable indicating the first time that a state has been entered. This is used to perform one time operations upon entering the state. (Such as "Engaging the Glow Plugs" upon entering the "START1" state.) The rest of the "START1" state is "wait until the glow plug timeout occurs". Here is what the START1 state code looks like:
* start1 -- turn on the glow plug and wait for glowplugdelay
******************************************************/
void start1(void) {
if(initstate) { // first time, turn on the glow plug
PORTC |= (1 << GLOWPLUG);
// set the speed control to "start position"
//(this moves the speed lever, but does not engage the starter)
setstepper(startspeed);
initstate=0;
}
// if we get a stop signal, then turn off the glowplugs and go back to stop
if(STOP) {
PORTC &= ~(1 << GLOWPLUG); // glow plug off
setstate(SHUTDOWN2);
}
// if we have reached the glow plug timeout, then turn off
// the glow plug and move to the next state
else if(stateruntime >= glowplugdelay) {
PORTC &= ~(1 << GLOWPLUG); // glow plug off
setstate(START2);
}
// otherwise keep waiting until the glow plug timeout
}
This state has two possible exits: The external stop signal (somebody turns off the "run" switch), and the glowplug delay timer expiring. Either way, the glow plugs are turned off before changing to the next state.
The real annoying thing for me with the glow plug code is that I cannot find a glow plug which will fit the head on the Changfa. If anybody knows the part number of a glow plug which will fit, please let me know.
The ATMega8 has 3 independent timer/counters. These are used as follows:
Timer/Counter 0 -- Stepper Motor Timing
This counter is used to implement the delays between steps of the stepper motor. This is a simple 8 bit counter with a divide by 256 prescaler. The 16mhz system clock is divided by 256 to create a 62500hz timer clock. When a step is initiated on the stepper motor, the appropriate stepper coils are energized, the timer is loaded with a preset value, and the timer overflow interrupt is enabled.
As the stepper is moving to the next position, the counter runs up to 255. When the counter overflows, indicating that the step time has expired. The interrupt service routine moves the stepper to the next step, and resets the counter. If the stepper motor has reached the desired step number, then the overflow interrupt is disabled and the motor stops.
The stepper motor as built can operate at up to 600 steps per second, but it has very little power at that speed, so the motor runs around 500 steps per second. The leadscrew has about 3400 steps from end to end so it takes about 7 seconds for full motion. During operation of the engine, the stepper will only be moving a small amount, so this slow response will not be a problem.
Timer/Counter 1 -- Tachometer
This counter is used with the optical flywheel sensor to time how long a single rotation of the engine takes. This is a 16 bit counter with a "Input Capture" feature. Like timer0, the timer1 clock is set to 62500hz. The counter runs until a signal is detected on the "ICP1" (tach) pin on the cpu. The counter value is saved and the "Input Capture Interrupt Service Routine" is called.
The service routine determines how many "clock ticks" have occurred since the last time the interrupt was called. This "tickcount" is saved and used by the main state machine at the top of the loop to calculate the current RPM of the engine (rpm = 3750000/tickcount).
Timer/Counter 2 -- realtime clock
This clock is used to create a real time clock which is used in the various state delays and recording total runtime of the engine. This is an 8 bit timer with a 62500hz clock. The timer is set such that it overflows after 250 counts and triggers an interrupt (i.e. 62500/250 = 250 interrupts/second). The interrupt increments a "realtime tick counter" on each call. It increments the system runtime counter after every 250 ticks.
USART -- Communicate with the rest of the world
The controller itself does not have a display, so state and error information are sent serially via the built in USART to a separate display, or to a computer running a terminal emulator. All USART i/o is all interrupt driven, so the main state loop does not need to bother with polling to send and receive data.
ESTOP -- Emergency Stop
An Emergency stop can be initiated by multiple methods depending upon what state the machine is in. Regardless of state, a Manual "ESTOP" request is always executed. Overheat, Overspeed and Low Oil pressure will trigger an ESTOP in START5 or above. An RPM reading of 0 in the RUN1 state (meaning that we have lost the RPM sensor) will also cause an ESTOP.
The ESTOP1 state turns off all relays, moves the speed control to "stop" and opens the decompression solenoid. After the rotation has stopped (or more than 3 seconds between RPM ticks), the decompression solenoid is closed (so we don't run down the battery).
Normal State Flow -- running the engine from startup to shutdown
The controller starts at the "STOP1" state. When the "run" switch is closed, the state changes to "START1".
START1 moves the speed control to the "start" position and runs the glow plug for a fixed number of seconds. After the glow plug timeout, the state machine switches to "START2".
START2 opens the decompression solenoid and engages the starter. After a fixed amount of time, the decompression solenoid is closed and the state machine switches to "START3".
START3 keeps cranking until the crank timeout occurs, or the RPM exceeds the "STARTRPM" speed, or the RPM drops below the minimum set for cranking speed. Once the rpm exceeds the STARTPM speed, the starter is disengaged and the state moves to "START4".
START4 waits for a fixed amount of time for the oil pressure to rise above minimum. If the wait time expires before oil pressure rises, then ESTOP is executed. When the oil pressure rises agove minimum, the state moves to "START5".
START5 waits a fixed amount of time for the engine to warm up. After the warm up timer expires, the speed control is set to normal run speed, and the state moves to "RUN1".
RUN1 monitors all of the engine variables. If any exceed their limits, ESTOP is executed. When the injector line temperature exceeds 70C and the water temp exceeds 70C, the fuel is switched to UCO. When the "STOP" signal is detected, the state moves to "SHUTDOWN1".
SHUTDOWN1 switches fuel from UCO to Diesel, and waits for the UCO purge delay. When the UCO purge delay expires, the state moves to "SHUTDOWN2".
SHUTDOWN2 moves the speed control to the "stop" position and waits for RPM to drop to zero. (zero rpm is defined as the 16 bit counter overflowing at 65200 hz 3 times. This is about 3 seconds per rotation. If the rpms do not drop to zero within "SHUTDOWNTIMEOUT" seconds, ESTOP is executed. Otherwise the state moves to SHUTDOWN3.
SHUTDOWN3 moves the speed control BACK to normal. At this point the engine has stopped, so this is setting up for the engine to be manually started next time in the event of a dead battery or failed controller. The state then moves to the "STOP2" state.
STOP2 is the catchall state for all states moving to the "STOP1" state. STOP2 waits for the run signal to go away before moving to STOP1. After an error, (such as overcrank, or diesel empty) the engine will be stopped, but the "run" switch is still on. The user must turn off "run" to clear the error before a restart can be attempted. On a normal shutdown, the run signal going away is what initiated the shutdown, so the state moves immediately to STOP1.
State Descriptions
STOP1 -- Stopped
This is the entry point to the state machine. Reset or watchdog timeout goes to this state. The state waits until it senses oil pressure, or a "run" signal.
If the oil pressure rises above the minimum and the run signal is not present, the state changes to MONITOR1. This is to support manual operation of the engine.
If the oil pressure is above the minimum and the run signal is present, the state changes to RUN2. This is normally the case when the controller resets while the engine is runnihng.
When the run signal is present, but no oil pressure, The state changes to START1.
START1 -- Glow Plugs
This state energizes the glow plugs and delays for a fixed amount of time. The speed control stepper motor is recalibrated to zero. If the stop signal appears, the state switches to SHUTDOWN1.
After the glow plug timer expires, the glow plugs are turned off and the state changes to START2
START2 -- Decompression
This state pulls the decompression lever, and starts cranking the engine. The stepper motor is set to the START position.
When the decompression timer expires, the decompression lever is closed and the state changes to START3.
START3 -- Cranking
This state waits while the starter is cranking the engine.
If the RPM while cranking is below the minimum cranking speed, an error is raised, and the state changes to STOP2.
If the crank timer expires, without the engine starting, the starter is disengaged and the state is changed to START6. If the engine has tried starting too many times without success, an error is raised, and the state is changed to STOP2.
When the RPM exceeds the Start RPM, the starter is disengaged and the state changes to START4.
START4 -- Oil pressure wait
This state waits for oil pressure to build up after the START3 state detects rpm > startrpm.
If the rpm drops below the startpm level, this indicates that the engine has failed to start. The restart counter is incremented and the state is changed to START8. If the restart counter exceeds the maximum, an error is raised and the state changes to SHUTDOWN1.
If the oil pressure wait timer expires, this indicates that the engine is running, but does not have oil pressure. The state is changed to ESTOP1.
If the oil pressure exceeds the minimum, the state is changed to START5.
START5 -- Warmup
This state delays for a fixed amount of time for the engine to warm up before switching to full run speed.
If the rpm drops below the startrpm, this indicates that the engine has failed to start. The restart counter is incremented and the state changes to START8. If the restart counter exceeds the maximum, an error is raised and the state changes to SHUTDOWN1.
When the warmup timer expires, the speed control stepper motor is set to "run" speed and the state changes to RUN1.
START7 -- restart delay
The purpose of this state is to wait for the engine to fully stop after the rpm has dropped below the minimum while in the RUN1 state. (If the engine stops while it is running, we need to restart, but don't want to engage the starter while it is still turning.)
If the RPM rises above the startpm, this indicates that the engine has somehow restarted itself, so the state changes to RUN1.
If the rpm reaches zero, and the oil pressure drops below the minimum, the state changes to START1.
START8 -- restart delay
This state is identical to START7 except that the state returns to START4 instead of RUN1 if the engine restarts itself.
The purpose of this state is to wait for the engine to fully stop after the rpm has dropped below the minimum while in the START4 or START5 state. (If the engine stops while it is running, we need to restart, but don't want to engage the starter while it is still turning.)
If the RPM rises above the startpm, this indicates that the engine has somehow restarted itself, so the state changes to START4.
If the rpm reaches zero, and the oil pressure drops below the minimum, the state changes to START1.
RUN1 -- The main run state
This is the main state while the engine is running.
If the stop signal occurs, the state changes to SHUTDOWN1.
If the rpm drops below the startrpm, the retstart counter is incremented. If the restart limit is exceeded, an error is raised, and the state is changed to SHUTDOWN1.Otherwise, the state is changed to START7.
If the "DIESEL EMPTY" signal occurs, the state changes to SHUTDOWN1.
If the Injector temperature exceeds the minimum, and the water temperature exceeds the minimum, the fuel selector is changed to UCO (Used Cooking Oil).
If the fuel selector is set to UCO, and the UCO empty signal occurs, the fuel selector is changed to diesel. An error is raised, but the engine continues to run.
RUN2 -- Wait for rpm sensor after a reset
The purpose of this state is to delay for a short period of time to allow the RPM sensor to register after the controller has reset. If the state changes immediately from STOP1 to RUN1 after a reset, the RPM sensor will not have had time indicate anything. Zero rpm in the RUN1 state indicates a loss of the rpm sensor and produces an emergency stop.
If the rpm delay timer expires, this indicates that we have oil pressure, but no rpm sensor. The state changes to ESTOP1.
If the rpm rises above startrpm, the state is changed to RUN1.
MONITOR1 -- Wait for rpm sensor after manual start.
This state is identical to the RUN2 state, except that it is only used when the engine is started manually.
If the engine is started manually because of a dead battery, when power
comes on, the controller will sense that the engine is already running
and change to this state. This state prevents an emergency stop
because the rpm sensor has not yet had time to count the first rotation.
If the engine is started manually, oil pressure will rise, but the run signal will not be present. This state delays to ensure an accurate rpm reading before switching to the MONITOR2 state.
If the rpm delay timer expires, this indicates that we have oil pressure, but no rpm sensor. The state changes to ESTOP1.
If the oil pressure goes away while waiting for the rpm to pick up, then the state changes to S
TOP1. This helps catch glitches that occur on startup where the oil pressure sender momentarily indicates pressure which changes the state to the monitor mode, but ends up in ESTOP1 when the oil pressure disappears.
If the rpm rises above startrpm, the state is changed to MONITOR2.
MONITOR2 -- Monitor the engine after a manual start.
This state monitors the critical engine paramaters and initiates an emergency stop if anything strays outside of the limits.
If the rpm drops below the startrpm, the state is changed to SHUTDOWN1.
If the run signal is detected, the state is changed to RUN1.
SHUTDOWN1 --Switch to diesel fuel and purge UCO from the system.
This is the first state in the normal shutdown series of states. Any state (other than MONITOR1, and MONITOR2) which detects the "stop" signal will change state to SHUTDOWN1.
If the engine is running on UCO, the fuel selector is changed to DIESEL and the purge timer is started.
If the engine is running on DIESEL, the state changes to SHUTDOWN2.
When the purge timer expires, the state changes to SHUTDOWN2.
SHUTDOWN2 -- Shut down the engine
On entry, the speed control is set to the STOP position and the stop timer is started.
If the stop timer expires, this indicates that the engine is not shutting down properly, so the state changes to ESTOP1.
If rpm = 0 and oil pressure < minimum oil pressure, the state changes to SHUTDOWN3.
SHUTDOWN3 -- reset the speed control for manual operation
After the engine has stopped, this state resets the speed control back to the normal run position. The purpose of this is to allow for a manual startup in the future. NOTE: The current hardware design receives power either through a 12 volt unregulated power from line voltage, or from the "run" signal. If the line power is not available at the time the run signal goes away, the controller will lose power at the SHUTDOWN2 state and will not be able to execute the SHUTDOWN3 state. To prevent this from happening, switch to utility power before switching from "auto" to "manual" mode.
After setting the speed control to run speed, the state changes to STOP2.
STOP2 -- wait for the stop signal.
This is the catchall state for all states moving to the stopped (STOP1) state. This ensures that the engine is fully stopped, and the run signal is gone. Errors which occur during the START phase will end up here with the run signal still present. This state delays until the user switches from "run" to "stop". During a normal shutdown, the "stop" signal is what intiates the shutdown, so this state falls immediately through to STOP1.
If rpm > 0, this indicates that somehow the engine is running. (It should be fully stopped by the time we get here.) The state is changed to SHUTDOWN2 to stop the engine.
If the "stop" signal is present, the state changes to STOP1.
ESTOP1 -- Emergency stop
This state does whatever it takes to shutdown the engine. If the state machine detects a manual "ESTOP" signal at any time, it switches to this state.
START5, MONITOR2, RUN1 and SHUTDOWN1 monitor the following inputs:
- oil pressure
- water temperature
- overspeed
- loss of tachometer signal
- loss of oil pressure signal
If any of these inputs exceed the limits, the state changes to ESTOP1.
On entry, the speed control is set to zero, and the decompression lever is opened.
When rpm = 0 and oil pressure < minimum, the decompression lever is closed and the state changes to STOP2.
Note, this state will leave the decompression lever solenoid on until either the engine stops, or the battery runs down.
Display
Here is what the status screen looks like at the moment. The state is "stopped" the RPM is showing somewhere around 5600rpm (the fan really does turn that fast). The Injector Line temperature is 24ºC. The water temp is showing 24ºC. The oil pressure is showing 18 and the current stepper location is 0

PID Controller
One of the "optional" features for the controller was to provide "sag" support for the engine governor. The stock Changfa governor allows the RPM to drop by 100 to 150 rpm from no load to full load. After implementing all of the primary features on the controller, I still had 1.5 kilobytes of space available on the CPU, so I went ahead wrote a simple "P" (Proportional) type controller to see how well it would work.
A "P" controller applies an adjustment proportional to the size of the error. My implementation was simple:
pterm = (TARGETRPM - rpm) * PGAIN;
setstepper(steppercurrent + pterm);
This is done 10 times per second while in the RUN1 state. With a little tweaking of PGAIN, I was able to get the RPM to settle within 5 RPM of target. Unfortunately, there was significant overshoot and oscillation, so a full PID controller was needed.
With only 1.5 kilobytes to
work with, I did not want to use floating point arithmetic. I found a
good example of a fixed point PID controller in the Atmel AVR221 Application Note. This formed the starting point for my controller.
A PID controller combines three different control algorithms: P=Proportional, I=Integral, D=Derivative.
The Integral portion is used to eliminate the "steady state" error (5 RPM) in my case.
error = TARGETRPM - rpm;
sumerror += error;
iterm = sumerror * IGAIN;
The Integral portion, sums up all of the errors over time multiplied by a constant (IGAIN). One of the problems with the integral is when the engine is overloaded, the integral tends to wind up and cause a massive overshoot when the load goes away. To deal with this, I do not accumulate any error when the stepper is at max.
The Derivative portion is used to eliminate the overshoot.
dterm = (rpm - lastrpm) * DGAIN;
lastrpm = rpm;
The derivative tracks the direction that the speed is moving and applies an adjustment relative to how rapidly the rpm is changing. The effect is to slow down a rapidly changing speed. This helps prevent overshoot.
The pid controller sums all three terms which are applied as an adjustment to the stepper motor position.
setstepper(steppercurrent + pterm + iterm + dterm);
PID Tuning
The biggest difficulty lies in tuning the controller to provide fast response with no overshoot and rapid settling. Much has been written about PID tuning. You can buy fancy software packages to do computer modeling of your system. In my case, The Ziegler-Nichols method looked like a good starting point. You start by setting the IGAIN and DGAIN to zero, then raise PGAIN until the system exhibits stable oscillation. Here is where I ran into trouble. it seems that stable oscillation in my system involves RPM peaks higher than 2200rpm. Whenever the controller saw the RPM exceed 2200 rpm, then the controller killed the engine with "Emergency Stop". I was able to guess that the "Kp" (Critical PGAIN) was around 90 with an oscillation period of 2.4 seconds or 24 sample periods. From there I was able to calculate initial values for PGAIN, IGAIN and DGAIN.
Setting the upper limit switchAfter living through a manual tweaking of the PID variables, I realized that the first step should have been to set the maximum travel on the leadscrew to the maximum power point of the engine at full load and 1800 rpm. There is no point in opening the rack any further because that just wastes fuel, and could lead to overspeed in the event of a computer failure. Here is the procedure for that setup:
- Connect a computer to the serial port on the controller. The terminal emulator needs to be set at 9600 baud N81.
- Leave the run/stop switch in "stop" mode. Start the engine manually.
- Monitor the RPMs of the engine. You can type '+' to add 10 steps to the current stepper position, or '-' to subtract 10 steps. When you get close to 1800 rpm, start using ',' or '<' to subtract one step and '.' or '>' to add one step.
- Once you have stablized at 1800 rpm, note the stepper postion. You can add that position to the source program as the default stepper position for 1800 rpm.
- Add load to the engine. Manually re-adjust to 1800 rpm.
- Keep adding load until the engine will no longer able to reach 1800 rpm. Back off the load until the engine is just able to carry the load at 1800 prm. Adjust the upper limit switch to engage at this point. Doing so will prevent the engine from overspeeding during the Ziegler-Nichols tuning procedure.
To facilitate tweaking the gain variables, I hooked 3 10K variable resistors to three of the inputs and use the A/D converter to give me a number between 0 and 255. I could set the gain, then apply load to the engine to see how it responded.
My first attempt at running the engine under PID control met with failure. The system immediately shut down. I gave up on the Z-N values and started tweaking the gain manually. I would adjust the gain until I had something reasonable, but when I tried to start the engine under PID control it always shut down. I tracked down the problem to my fixed point implementation. It turns out that when the engine rpm is very low (i.e. immediately after startup), the error is high. The value for PGAIN was high enough to cause the 16 bit integer to overflow to a negative value. I had to sacrifice several hundred bytes to upgrade my code to long integer math.
This graph shows the stock performance compared with the first acceptable configuration. The stock governor drops to 1700 rpm within a second. The PID governor starts picking up after half a second and has recovered to 1800 within 3 seconds. The RPM overshoots by 6 rpm, but has settled back to 1800rpm within 10 seconds. The integral portion is set to zero because it introduces significant overshoot which cannot be cancelled with the dgain.
This graph shows the governor response when load is removed. The stock governor returns to 1800 rpm within 1 second. The PID governor sees the rpm hit 1818 within .1 seconds and starts backpeddling as fast as it can. The rpm peaks at 1860 rpm at .4 seconds. The rpm reaches 1800 rpm within a second, but undershoots the target by 16 rpm and settling at 5 rpm under target within 1.5 seconds. The "steady state error" of 5 under rpm is a result of Igain set to zero.
Here is how the stepper motor links up with the governor spring. It is almost as if this stepper assembly was custom designed for this application. The wires are a total mess at this point because in order to change the programming on the controller, I have to disconnect everything from the engine and take it back into the house. Eventually, I will tidy this all up
It is a little dark, but you can see where the stock speed lever has been disconnected, and the spring hooked onto the stepper post.
Here is the view from the other side. You can see the screw with limit switches at both ends and the governor spring in the 1800 rpm run position.
Here is a 26 second video showing the controller starting the engine. Here is the sequence of events:
- Off screen, I turn the switch from "manual" to "automatic".
- The stepper moves all the way back to the zero position. At the same time the glow plug relay energizes.
- As soon as the glow plug timer expires (about the same time as the stepper getting to zero) the glow plug relay is turned off and the engine starts cranking with the decompression relay energized.
- Simultaneously, the speed control moves towards the "start" position (set for about 1000 rpm).
- As soon as the RPM exceeds the minimum crank speed, the decompression relay is turned off.
- Once the engine rpm exceeds the "start RPM" limit, the starter is disengaged and the controller enters a brief delay for oil pressure to exceed the minimum. If oil pressure does not rise within a fixed amount of time, the engine will shut down.
- The engine is allowed to warm up at 1000 rpm for 10 seconds. For the final version this warmup will wait for the water temperature to reach a minimum value before continuing. For debugging purposes I didn't want to wait that long.
- Control of the engine rpm is then passed to the "PID controller" which calls for a big bump in speed. You can see the speed overshoot and the controller back off and then back on a bit. I'm still working on eliminating the overshoot.
Here is a 21 second video showing the engine stop sequence.
- Off screen, I turn the switch from "automatic" back to "manual"
- The stepper moves toward zero.
- The controller waits for RPM to reach zero, and for oil pressure to drop below the minimum.
- The stepper moves back to the 1800rpm run position in preparation for manual operation
Pressure Sender
The stock Changfa engine has a little plastic fitting with a reddish button which pops up when oil pressure is present. If you remember to look, the button will show if you have oil pressure. The first problem is this thing is within about an inch and a half from the muffler. During one of the hot days last summer when I was running a heavy load, the plastic on the fitting melted a bit. The button still pops up, but to protect from a total meltdown, I stuck a bit of foam backed aluminum tape on the fitting to protect it in the future.
To allow the controller to monitor the oil pressure, I decided to use a 350 psi VDO oil pressure sender. I took off the banjo bolt on the oil line feeding the rocker arms. I chucked the bolt in the lathe and drilled a hole all the way through the bolt. Then made an adapter fitting out of a piece of brass rod and soldered it onto the banjo bolt. The VDO sender screws into the adapter. When fitted out with a bit of insulating aluminum tape, it looks like this:
One side of the sender goes to the controller, the other is tied to ground. People have reported seeing oil pressures in excess of 300 psi. So far, the highest I've seen is in the 200psi range, but when the engine is hot, the oil pressure drops to 80 or 90 psi.
Software Source
The current software is not perfect, and there are items which I have not yet implemented. However at this point, I doubt that I'll be adding any more features. If you are interested, the source code can be had at http://ngencontrol.googlecode.com/svn/trunk/









