VW TDI forum, Audi, Porsche, and Chevy Cruze Diesel forum banner
1 - 3 of 3 Posts

3 Posts
Discussion Starter · #1 ·
2005 Passat and 2006 Jeep CRD - ECM Glowplug Control Signal Issue - Arduino Solution

I rarely post to forums, but this is such an unexpected problem I feel I should share.
We have both a 2005 Passat 2.0L TDI and a 2006 Jeep Liberty 2.8L CRD. Two completely different vehicles, with the same symptoms and the same root cause. They both developed difficulty in starting in cold weather, noticeable below 0 celsius, and progressively harder as it gets colder.
I went through quite a lot of trouble to find this, but both vehicles use Bosch glowplug control modules (GCM) and while they weren’t the problem, the pulse-width modulated (PWM) signal from the engine control module was.
I’m a retired electronics technician, so I’ll list all the info I have. With a Fluke DMM, the Passat glowplug voltage would start at 7.8Vdc (rms) for 1 to 2 seconds, then drop to 1.8V for the remainder of the 13 second cycle after you turn the key. This was the same for all glowplugs, and they were all drawing the same current. BTW, I must state that glowplugs or any heating element like them, like in a stove or hot water tank, do not get weak. They’re either good or they’re not, like an incandescent light bulb. And they should glow red brightly when you bench test them, something I didn’t know, though it seems obvious now.
The Passat glowplugs are rated for 11Vdc. While at the 1.8Vrms level a scope on the glowplugs show a 6.6Vp-p pulse at 31hz with a 18% duty cycle. Far too little, you barely see it glow at all at the end of a few 14 second cycles with your hands cupped around it. 11 volts is about 87% of a 12.6V battery, so you should see a pulse duty cycle of 87%. That is, the glowplugs should be on 87% of the time, not the 18% I was seeing. If I connected the glowplugs to the battery, through some resistance wire to drop the voltage to 11V, the car started without difficulty.
I don’t have schematics for this vehicle and some schematics I found online for a previous year show the glowplugs directly connected to the engine control module (ECM). This is incorrect for my Passat and only after I’d opened the ECM and started some continuity checks did I find there’s a separate GCM located under the coolant reservoir.

The GCM is a Bosch# 0281 003 027, VW#038907281C. The nine leads are as follows:
G1-G4 Glowplug leads
87 Switched 12V from ignition
31 Ground
D1 Data out to the ECM
30 Always-on 12V
ST Control signal from the ECM

Inside the GCM there’s one main IC, an ST Microsystems L9524 glowplug controller and four ST P85NF55L MOSFETs that do the actual pulsing of the glowplug voltage. These are obsolete parts but you can still find the datasheets online. After study of the datasheets the input signal from the ECM will be used as a clock for the Data Out line and the input signal pulse should be the inverse of what you want on the glowplugs. So I connected the ST input to a separate PWM signal from an Arduino circuit and found the glowplugs were now getting the correct voltage, ruling out the GCM as the source of the trouble. Since I had the ECM open anyway, I traced the glowplug control signal directly to a Bosch#30554 controller IC. I couldn’t find a datasheet for this one, but I found some replacements available on Aliexpress and others. This is about a 64-pin chip though, so clearly it controls more than just the glowplugs, likely the injector solenoids as well. And because of that it’s likely programmable for timing and the number of cylinders. If replaced, it’s possible I might be able to program it via the Rosstech VSDS, but it may be programmed separately and without information on it I really can’t take that chance. And of course replacing the ECM on either vehicle would be far too expensive.
So as an alternative, I created a low-cost Arduino circuit that inputs the PWM signal from the ECM, synchronizes a new correct PWM signal to it, then outputs it to the GCM.
Our Jeep Bentley manual outlines a control strategy based on the coolant temperature as follows:

Temp Wait-To Start Pre-Heat Post-Heat
"Key ON" Lamp "ON" (s) GlowPlugs on (s)
-30C 10 SEC. 35 SEC. 200 SEC.
-10C 10 SEC. 23 SEC. 180 SEC.
+10C 1 SEC. 21 SEC. 160 SEC.
+30C 1 SEC. 20 SEC. 140 SEC.
+40C 1 SEC. 19 SEC. 70 SEC.
+70C 1 SEC. 16 SEC. 20 SEC.

This may not be the way the Jeep was actually programmed since the dashboard glowplug indicator has no bearing on the preheat time and the operator would have no clue to pause for that time before turning the key to start. So I have an LED on the dash that the Arduino flashes when the key is turned and glowplugs are started. It will remain flashing for the preheat period, then stays on steady for the post-heat period. So the operator knows to wait until the flashing stops and it’s on steady before starting the engine. The times in the chart may be overkill. You don’t really need 20 seconds of preheat to start it at 20C, but better too much preheat than not enough.

I happened to have some small boards with an ATTiny85 and a MCP9808 temperature sensor, but any temperature sensor would do. The temperature rating of all the parts made it OK to strap the circuit board to the coolant hose in order to sense the temperature, but one could put this sensor on another board (or use a sensor like a DS18B20) and have only it strapped to the coolant hose. You’d have to modify the code to suit a different sensor of course.

Installation: Cut the wire to the GCM ‘ST’ terminal, connect the end that goes to the ECM to ‘ECM IN’ in the circuit below and the end that goes to the ‘ST’ terminal connect to ‘ST - PWM OUT’. Splice the wire going to the 87 terminal so it goes to both the GCM and Arduino circuit ’87 – Key-on 12V’. Splice the ground to both as well. Add an LED to the dashboard and run leads to the Arduino circuit connections. Best to make all connections plug-in so you can bench-test the circuit if anything goes wrong.

The schematic is as follows:
Rectangle Schematic Slope Font Parallel

There is more information in the comments of the code below. The pulseIn() function is sufficient to synchronize the output PWM signal with the ECM, so the ECM can still read the diagnostics from the GCM.
The terminal ‘Tx OUT’ is a diagnostic serial connection that will output the temperature from the sensor, the corresponding times from the interpolated look-up table and indicates Preheat, Posthead and Done when it finishes. After this, the Arduino will stop the PWM output, which will turn off the glowplugs, and then enter sleep mode. In order to read the serial output you’d need a serial converter like the FTDI232 or CH340 that many Arduinos use and a PC with Hyperterminal or just use the serial monitor in the Arduino IDE.

The Passat code is as follows:

* Modification of the Jeep's code for use on the Passat with 11v glowplugs.
* If ATTiny has been used before, burn bootloader first just in case.
* Use 1MHz internal clock source. 8Mhz is 8x too fast for this timer setup.
* Circuit draws 24.7mA with LED on and pulse active, 3ma while 'sleeping' (12V side)
* The 3mA is drawn by the 5Vreg alone with no load.
* The temperature reading was unstable on the finished circuit
* but was corrected by putting a 10uf capacitor across the 5V and Com
* to ATTiny and MCP9809. As assembled the circuit does heat the
* temp sensor over time, but it's just the initial temperature
* that matters. 5.5degC over 150sec cycle. 12V x 24.6mA is 0.3 watts.
* To emulate the ECM glowplug control which now is insufficient to start the engine in cold weather.
* Passat at the plug:
* DMM shows 7.8Vmax but only for 1-2sec, then drops to 1.8V for the rest of the 13 seconds.
* 31hz, 6.6Vp-p, 18% duty cycle for the 1.8v period. We need about 87% duty cycle for 11v.
* Jeep:
* Glowplug PWM is only 38.6% duty cyle (32hz) for 14sec and barely glow if you cup your hands around it.
* 4.1V avg at 5.7A. Glowplugs are rated 7volt.
* Passat:
* A 87% PWM signal to the bs170 mosfet below results in a 23% duty cycle to the glowplug module,
* which then results in 87% PWM glowplug drive. 87% of 12 is 11V
* The ATTiny/MCP9809, clipped to a coolant hose, is connected to the Passat's glowplug control module.
* Powering it from the 87 terminal that goes to 12Volts when the key is turned on.
* A 5V reg of course for the Arduino. The arduino will read the coolant temperature via the MCP9808.
* Based on the temperature, it will drive the glowplugs for the pre-glow time from the chart while flashing an LED indicator.
* After the pre-glow elapses, the LED goes solid, and it continues to drive the glowplugs for the post-glow period, then turns off.
* Uses the ATTinyCore by Spence Konde
ATtiny85 has 3 PWM outputs. They are located on pins 0, 1 and 4
Attiny85 and MCP9808 have max temps of 125degC (257degF). The jeep has a 176F, 80C thermostat.
MC78L05 5Vreg max 120C and BS170 Mosfet max 150C

Jeep service outlines the glowplug durations vs temperature:

Temperature Wait-To Start Pre-Heat Cycle Post-Heat Cycle
"Key ON" Lamp "ON" (s) GlowPlugs on (s) (s)
-30C 10 SEC. 35 SEC. 200 SEC.
-10C 10 SEC. 23 SEC. 180 SEC.
+10C 1 SEC. 21 SEC. 160 SEC.
+30C 1 SEC. 20 SEC. 140 SEC.
+40C 1 SEC. 19 SEC. 70 SEC.
+70C 1 SEC. 16 SEC. 20 SEC.

MOSI PB0 (Pin5)
MISO PB1 (Pin6)
RESET PB5 (Pin1)
CLK PB2 (Pin7)

MCP9808 ATTiny85 DIP
Pin2 Pin7 (SCL, PB2)
Pin1 Pin5 (SDA, PB0)

Physical Pin 3 (PB4) output via a 100k resistor to the mosfet center terminal.

|---------------------> to glowplug module 'control' input light-blue/brown wire (labeled ST)
| BS170
12V >-------VVVVVVVV---|----| | |----------< GND
1k |
PB4 >-------VVVVVVVV-----------|


#include <Wire.h>
#include <avr/interrupt.h> // library for interrupts handling
#include <avr/sleep.h> // library for sleep

#define SDA_Pin 0 //0 refers to PB0, pin 5 on the ATTiny85 DIP
#define SCK_LED_Pin 2 //2 refers to PB2, pin 7 on the ATTiny85 DIP
#define PWM_Pin 4 //4 refers to PB4, pin 3 on the ATTiny85 DIP
//#define LED_Pin 3 //3 refers to PB3, pin 2 on the ATTiny85 DIP
#define TX_Pin 1 //1 refers to PB1, pin 6 on the ATTiny85 DIP
#define ECM_Pin 3 //3 refers to PB3, pin 2 on the ATTiny85 DIP

#define ArraySize 6

//software serial used on LED_Pin to confirm temperature measurements and look-up table, but then not used.
//software serial is also incompatible with the PCINT vector ISR

#include <SoftwareSerial.h>
// software serial #1: TX,RX. TX>RX, RX>TX
SoftwareSerial portOne(-1, TX_Pin); //transmit only, so -1 to not use a pin for tx. 3 refers to PB3, pin 2 on the ATTiny85 DIP

#ifndef cbi //to turn off ADC
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))

// Device address
const int MCP9808_Address = 0x18; //address pins tied to ground
//with address pins floating a scanner sketch reports address 0x1F

// Make sure the sensor is found, you can also pass in a different i2c
// address with tempsensor.begin(0x19) for example, also can be left in blank for default address use
// Also there is a table with all addres possible for this sensor, you can connect multiple sensors
// to the same i2c bus, just configure each sensor with a different address and define multiple objects for that
// A2 A1 A0 address SDA 1 8 Vdd
// 0 0 0 0x18 this is the default address SCL 2 7 A0
// 0 0 1 0x19 Alert 3 6 A1
// 0 1 0 0x1A GND 4 5 A2
// 0 1 1 0x1B
// 1 0 0 0x1C
// 1 0 1 0x1D
// 1 1 0 0x1E
// 1 1 1 0x1F pins hi or floating

//Bolus boards have A0, A1, A2 grounded (0x18).

//#define MCP9808_I2CADDR_DEFAULT 0x18 ///< I2C address
#define MCP9808_REG_CONFIG 0x01 ///< MCP9808 config register

#define MCP9808_REG_CONFIG_SHUTDOWN 0x0100 ///< shutdown config
//#define MCP9808_REG_CONFIG_CRITLOCKED 0x0080 ///< critical trip lock
//#define MCP9808_REG_CONFIG_WINLOCKED 0x0040 ///< alarm window lock
//#define MCP9808_REG_CONFIG_INTCLR 0x0020 ///< interrupt clear
//#define MCP9808_REG_CONFIG_ALERTSTAT 0x0010 ///< alert output status
//#define MCP9808_REG_CONFIG_ALERTCTRL 0x0008 ///< alert output control
//#define MCP9808_REG_CONFIG_ALERTSEL 0x0004 ///< alert output select
//#define MCP9808_REG_CONFIG_ALERTPOL 0x0002 ///< alert output polarity
//#define MCP9808_REG_CONFIG_ALERTMODE 0x0001 ///< alert output mode

#define MCP9808_REG_UPPER_TEMP 0x02 ///< upper alert boundary
#define MCP9808_REG_LOWER_TEMP 0x03 ///< lower alert boundery
#define MCP9808_REG_CRIT_TEMP 0x04 ///< critical temperature
#define MCP9808_REG_AMBIENT_TEMP 0x05 ///< ambient temperature
#define MCP9808_REG_MANUF_ID 0x06 ///< manufacture ID
#define MCP9808_REG_DEVICE_ID 0x07 ///< device ID
#define MCP9808_REG_RESOLUTION 0x08 ///< resolutin

float curTemp; //degC
float PreHeat; //seconds
float PostHeat; //seconds

void sleep() {

GIMSK |= _BV(PCIE); // Enable Pin Change Interrupts
PCMSK |= _BV(PCINT3); // Use PB3 as interrupt pin
ADCSRA &= ~_BV(ADEN); // ADC off
// adc off:
//ADCSRA = 0;
// cbi(ADCSRA, ADEN); // switch Analog to Digitalconverter OFF

set_sleep_mode(SLEEP_MODE_PWR_DOWN); // replaces above statement

sleep_enable(); // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
sei(); // Enable interrupts
interrupts (); // guarantees next instruction executed
sleep_cpu(); // sleep

cli(); // Disable interrupts
PCMSK &= ~_BV(PCINT3); // Turn off PB3 as interrupt pin
sleep_disable(); // Clear SE bit
ADCSRA |= _BV(ADEN); // ADC on

sei(); // Enable interrupts
} // sleep

//This ISR vector not compatible with software serial
//ISR(PCINT0_vect) {
// // This is called when the interrupt occurs, but I don't need to do anything in it

// Setup **

void setup()
unsigned long Htime;
unsigned long Ltime;
unsigned long Ttime;
//set unused pins to outputs and low to reduce power
// pinMode (1, OUTPUT);
// digitalWrite (1, LOW);
pinMode (5, OUTPUT);
digitalWrite (5, LOW);
delay(100);//had this at 5, temp IC would ini OK, but always return 0degC

// ADCSRA = 0;
// cbi(ADCSRA, ADEN); // switch Analog to Digitalconverter OFF

if (MCP9808_init()) { //initialize temp sensor
setResolution(0); // sets the MCP9808 resolution mode of reading, the modes are defined in the table bellow:
// Mode Resolution SampleTime
// 0 0.5°C 30 ms
// 1 0.25°C 65 ms (gives a 2dec int with last number 0 or 5 only)
// 2 0.125°C 130 ms (gives a 2dec int more or less properly)
// 3 0.0625°C 250 ms


portOne.print (curTemp);
portOne.println (" C");
portOne.print (PreHeat);
portOne.println (" s");
portOne.print (PostHeat);
portOne.println (" s");

Htime = pulseIn(ECM_Pin, HIGH);
Ltime = pulseIn(ECM_Pin, LOW);
Ttime = Htime + Ltime;
portOne.print("ECM Period: ");
portOne.println(" us");
//Start glowplugs immediately after power-up
// Timer 1
//TCCR1 = bit (CS10); // no prescaler, 8kHz
TCCR1 = bit (CS13)| bit (CS10); // 8khz div by 256 = 31.2hz
GTCCR = bit (COM1B1) | bit (PWM1B); // clear OC1B on compare
OCR1B = 111; // 31 of 127 = 25% duty cycle, 79 is 62.4% duty cycle, 111 is 87% duty cycle
OCR1C = 127; // frequency
/*set 30.9Hz Frequency */
void loop()
{ //Preheat glowplugs
unsigned long Timeout = millis()+(PreHeat*1000);
portOne.println("Preheat"); //LED flashing during preheat
while (millis()<Timeout){
//pulse LED pin
digitalWrite(SCK_LED_Pin, HIGH);
digitalWrite(SCK_LED_Pin, LOW);
portOne.println ("");
portOne.println ("Postheat");

//signal 'high' for engine start and set post-heat delay
digitalWrite(SCK_LED_Pin, LOW); //turn LED solid on
//done with glowplugs, so turn off LED and PWM and go to sleep
digitalWrite(SCK_LED_Pin, HIGH); //turn LED off
digitalWrite(PWM_Pin,LOW); //high or low will stop the PWM signal
portOne.println ("Done");

float readTempC() {
uint16_t t = read16(MCP9808_REG_AMBIENT_TEMP);

float temp = t & 0x0FFF;
temp /= 16.0;
if (t & 0x1000)
temp -= 256;

return temp;

void shutdown_wake(boolean sw) {
uint16_t conf_shutdown;
uint16_t conf_register = read16(MCP9808_REG_CONFIG);
if (sw == true) {
conf_shutdown = conf_register | MCP9808_REG_CONFIG_SHUTDOWN;
write16(MCP9808_REG_CONFIG, conf_shutdown);
if (sw == false) {
conf_shutdown = conf_register & ~MCP9808_REG_CONFIG_SHUTDOWN;
write16(MCP9808_REG_CONFIG, conf_shutdown);

uint16_t read16(uint8_t reg) {
uint16_t val = 0xFFFF;
uint8_t state;

state = Wire.endTransmission();

if (state == 0) {
Wire.requestFrom((uint8_t)MCP9808_Address, (uint8_t)2);
val = Wire.read();
val <<= 8;
val |= Wire.read();

return val;
void write16(uint8_t reg, uint16_t value) {
Wire.write(value >> 8);
Wire.write(value & 0xFF);

void write8(uint8_t reg, uint8_t value)
uint8_t read8(uint8_t reg)
uint8_t val = 0xFF;
uint8_t state;

state = Wire.endTransmission();

if (state == 0)
Wire.requestFrom((uint8_t)MCP9808_Address, (uint8_t)1);
val = Wire.read();

return val;
void setResolution(uint8_t value) {
write8(MCP9808_REG_RESOLUTION, value & 0x03);
uint8_t getResolution() {
return read8(MCP9808_REG_RESOLUTION);

bool MCP9808_init() {
if (read16(MCP9808_REG_MANUF_ID) != 0x0054)
return false;
if (read16(MCP9808_REG_DEVICE_ID) != 0x0400)
return false;
uint8_t e = 0;

write16(MCP9808_REG_CONFIG, 0x0);
return true;

void GetGlowPeriods(){
shutdown_wake(false); //wake MSP9808(true = shutdown / false = wakeup)
curTemp = readTempC();

shutdown_wake(true); // shutdown MSP9808 - power consumption ~0.1 uA, stops temperature sampling
// bitClear(DDRB,SCK); //going to be using the clock line for the LED after temp is read.
// bitClear(PORTB,SCK);

#define ArraySize 6
//can't be integers. won't interpolate because of the division having no remainder.
float TempC[ArraySize] = {-30,-10,10,30,40,70}; //jeep thermostat is 176F, 80C
float PreGlow[ArraySize] = {35,23,21,20,19,16};
float PostGlow[ArraySize] = {200,180,160,140,70,20};
boolean coefPos;

//Check if EGU is a positive or negative coefficient. adc array is assumed to be increasing.
if (PreGlow[1] - PreGlow[0] > 0)
{coefPos = true;}
{coefPos = false;}

//Check if temp is out of range and clamp results to array min or max
if (curTemp < TempC[0] or curTemp > TempC[ArraySize-1])
{if (curTemp < TempC[0]){
PreHeat = PreGlow[0];
PostHeat = PostGlow[0];
else if (curTemp > TempC[ArraySize-1]){
PreHeat = PreGlow[ArraySize-1];
PostHeat = PostGlow[ArraySize-1];

for (int x = 0; x<ArraySize; x++){
//check for an exact Adc table entry
if (curTemp == TempC[x]){ //remember == not = or it will write the Adc[x] to curAdc
PreHeat = PreGlow[x];
PostHeat = PostGlow[x];
else {
//Interpolate a match
if (curTemp > TempC[x] and curTemp < TempC[x+1]){
if (coefPos == true){
PreHeat = (curTemp-TempC[x])/(TempC[x+1]-TempC[x])* (PreGlow[x+1]-PreGlow[x])+ PreGlow[x]; //positive coefficient sensors
PostHeat = (curTemp-TempC[x])/(TempC[x+1]-TempC[x])* (PostGlow[x+1]-PostGlow[x])+ PostGlow[x];}
else {
PreHeat = PreGlow[x]-(curTemp-TempC[x])/(TempC[x+1]-TempC[x]) *(PreGlow[x]-PreGlow[x+1]); //negative coefficient sensors
PostHeat = PostGlow[x]-(curTemp-TempC[x])/(TempC[x+1]-TempC[x]) *(PostGlow[x]-PostGlow[x+1]);}

3 Posts
Discussion Starter · #2 ·
I should note specifically, the input signal to the Bosch glowplug control module from the ECM is inverted compared to the output signal. So don't be confused when testing both with an oscilloscope. As noted in the code comments, the Arduino circuit outputs a standard 5V PWM pulse which is inverted and amplified to a 12V pulse by the MOSFET, then sent to the GCM. So if you want the glowplugs to be on 87% of the time for an equivalent of 11Volts, the input signal to the GCM should show a 13% duty cycle.
1 - 3 of 3 Posts