An NHW20 test shows that the engine runs ~80 seconds to bring the
catalytic converter to operating temperature and put an initial charge
on the ICE:

A US NHW11 requires the engine to run until the coolant reaches 70C.
Like Ken@Japan's NHW20, the battery was charged in ~80 seconds but the ICE
continues to run:

An automated thermistor circuit was used to modify the NHW11 warmp-cycle:

Using a voltage-temperature plot from Hobbit, Ken@Japan and the maintenance
manual, the voltage at each temperature is known:

At ICE temperatures below 30C, too low of a resister results in the engine stalling and throwing an error code. Higher value resistors delay getting the engine controller to start auto-shutdown. The following chart shows what happens when the circuit is manually switched in at 30C compared to an ordinary warm-up cycle:
The power supply consists of a 3A, 1N4003, protection diode into a 47 uF capacitor, slightly less than 12VDC rail voltage. The 12VDC supply capacitor feeds a 5V linear voltage regulator for the test resistor network and a 3.3V linear regulator for the microprocessor.
The interface circuit is:
The interface has these major componets:
The software, analog-to-digital converter loop also handles generating the output PWM signal. Be sure to handle a power-on race condition where the microprocessor comes up before the engine ECU and thermistor circuit. For now, the circuit keeps the transistor off until a temperature of 40C is reached. From 40C to 80C, the transistor is set steady-on and adjusts the circuit to 70C. Once the temperature reaches 80C, the PWM begins to turn down the transistor.
The following is the wire map from the 14 conductor flat cable to the MSP430-F2013 pins:
/******************************************************************************/
//
// temperature_hack.c
//
// This program manages the thermistor voltage in an NHW11 Prius. When the
// ICE coolant temperature is below 40C, the LED remains off. Once the
// coolant temperature reaches 40C (the set point,) a bias transistor is
// pulsed to raise the apparent temperature to 70C. Thereafter, the LED
// flashes using the pulse width modulation (PWM) pattern.
//
// The target temperature, approximately 73C, has a dead-band below and
// above of about 2-4C. If the temperature rises above the dead-band,
// the PWM pattern decrements by one. Too low and the PWM pattern
// increments. Each step occurs on a one second interval.
//
// Internally, all measurements are taken from a continious stream and
// averaged over the last eight samples. Only the high-order, 16-bits
// are used since this provides sufficient accuracy for the 3 - 0.20 VDC
// thermistor range and matches the expected accuracy of the SD16.
//
// The watchdog timer is used as a 1 second, interval timer. This drives
// the temperature and LED flashing logic. After initialization, the LED
// flashes the current, PWM modulation pattern. A solid LED is maximum
// and off means it is the minimum modulation.
//
// PWM pulse generation is integrated with the measurements because of
// the high sampling rate, ~30kHz. The actual modulation pattern is
// embedded in a 9 element, byte array and the watchdog timer selects the
// PWM pattern to use based upon the current, measured temperature.
//
// The modulation array varies from eight, 0, to eight, 1. All zeros is
// the minimum, PWM modulation. However, tri-state OFF, by turning off
// P1.4, is the 'most off' and used during the initial warm-up phase.
//
// The conditional debug symbols for debug code are still in the source
// file.
//
// Bob Wilson, January 31, 2007, bwilson4use@hotmail.com
//
/******************************************************************************/
#include "msp430x20x3.h"
#include "intrinsics.h"
#include "stdio.h"
#include "string.h"
//
// The LED timing pattern starts with low bit and the last
// high bit ends the pattern. Each bit status is used,
// except the highest, last one, which resets the pattern.
// Thus '101' would flash ON-OFF and the load the next pattern.
//
#define LED_on 0x3
#define LED_off 0x2
#define LED_flash 0x5
#define LED_full 0x2d
#define LED_slow 0x10f
#define LED_start 0x115
//
// SD16-Divisions per 0.01 volt
// Voltage-Thermister_low-temp
// Voltage-Thermister_high-temp
#define SD16_volt 69
#define VT_low 150
#define VT_target 60
#define VT_range 8
#define VT_high 20
#define O2_delay 8
//
// Tri-state, a PWD_max disables the digital output
//
#define PWM_max 9
//
// Enable IDLE and conversion counters, for debug
//
//#define __IDLE__
//#define __SD16__
//#define __CAL__
//
// Global data
//
static unsigned char WDCLK_cnt=0; // Counter of watchdog-interval timer, seconds
static unsigned char O2_warmup=0; // Seconds for O2 warm-up and power up
#ifdef __IDLE__
static unsigned char IDLE_cnt=0; // The idle loop seconds flag
#endif
#ifdef __SD16__
static unsigned char SD16_cnt=0; // The SD16 converter seconds flag
#endif
static unsigned int LED_state=0x1; // LED display bits, lowest bit is state
static unsigned int LED_next=LED_off; // Start quiet
static unsigned int SD16_average=0; // Average of eight SD16 values, 0 is null
static unsigned int SD16_value[8]={0,0,0,0,0,0,0,0}; // Raw values, samples
#ifdef __IDLE__
static unsigned long IDLE_last[2]={0,0}; // Last idle counters
#endif
#ifdef __SD16__
static unsigned long SD16_last[2]={0,0}; // Last count of conversions
#endif
//
// PWM variables and lookup table
//
static unsigned int PWM_state=0x1; // Signal load the next
static unsigned char PWM_next=PWM_max; // The next value index, requires array
static unsigned char PWM_value[PWM_max] = // The array of PWM values
{0x00,0x01,0x11,0x49,0x55,0xb6,0xee,0xfe,0xff}; // the 1s increasing values
//
// This is entered via a Power On Reset and the 'glue' logic includes
// an invisible routine to clear the RAM. By default, the watchdog
// timer re-enters this way, thus wiping out all flags and counters.
//
void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Turn off the watchdog timer, for now
//
// Setup clocks, max MCLCK, fastest CPU and stuff
//
DCOCTL = CALDCO_16MHZ; // Set DCO to 16 MHZ
BCSCTL1 = CALBC1_16MHZ; // MCLK 16 MHZ modulator
BCSCTL3 = 0; // Make sure ACLK is Xtal
WDTCTL = WDTPW+WDTTMSEL+WDTSSEL; // Set the watchdog timer interval, 1 sec by Xtal
//
// Ready the LED to flash and let watchdog run when ready
//
P1DIR = 0x01; // Set P1.0 to output, others left alone
IE1 |= WDTIE; // Enable timer interrupts when GIE enabled
//
// Setup the SD16 to start running with the zero value.
//
SD16AE = SD16AE2; // Enable the external sensor on P1.2
SD16CTL = SD16INTDLY_2 + SD16SSEL_1 + // Set the SD16 to use SMCLK/16
SD16REFON + // use internal reference voltage
0 ; // end of register options
SD16INCTL0 = 0 + // Interrupt on 4th conversion
SD16INCH_1 + // select P1.2 as source signal
SD16GAIN_2 + // use 2x gain, based on earlier vehicle tests
0 ; // end of register options
SD16CCTL0 = SD16XOSR + SD16OSR_1024 + // Extended conversion, 1024 oversampling
SD16DF + // make it 2s complement
SD16UNI + // unipolar to cut high order bit
SD16IE + // enable interrupt
SD16SC + // start conversions
0 ; // End of register options
//
// Enable interrupts and put initialization code 'to sleep'
//
_BIS_SR(GIE); // Enable the interrupts
#ifdef __IDLE__
//
// Typical idle loop, no extra, is 566,000 loop ticks
//
do { // Idle CPU counting loop
if (IDLE_cnt == WDCLK_cnt)
{
IDLE_last[1]++; // Count idle loop
} else
{
IDLE_last[0] = IDLE_last[1]; // Save the oldest idle counter
IDLE_last[1] = 0; // Reset the idle counter
IDLE_cnt = WDCLK_cnt; // Reset the counter until next second
}
} while ( 1 ); // Idle loop never ends
#else
_BIS_SR(LPM1_bits); // Turn off CPU in idle loop
#endif
// MCLK disabled
// SMCLK and ACLK remain active
}
// 0xFFF4 Watchdog Timer - currently flashes LED
#pragma vector=WDT_VECTOR
__interrupt void wdt_vector(void)
{
WDCLK_cnt++; // Count interrupt for performance tests
if ( LED_state == 0x0001 ) // End of the current LED state?
{
LED_state = LED_next; // Load the next pattern
}
if ( LED_state & 0x1 ) // Check or next LED status
{
P1OUT |= 0x01; // Keep LED on solily
} else
{
P1OUT &= 0xFE; // Turn off the LED
}
LED_state >>= 1; // Remove last LED bit
#ifndef __CAL__
//
// Wait for O2 sensors and power-up to stabalize
//
if ( O2_warmup < O2_delay)
{
O2_warmup += 1; // Count this time delay
}
//
// The following routine manages the PWM parameters.
//
if ( // Check if within management range
( SD16_average > (VT_low * SD16_volt) )
)
{
PWM_next = PWM_max; // Too cold or too hot, tri-state off
}
else
{
if ( PWM_next == PWM_max ) // Are we off?
{
if ( SD16_average < ( VT_target * SD16_volt) ) // high or low?
{
PWM_next = 7; // Start with maximum adjustment
} else
{
PWM_next = 0; // Start with minimum adjustment
}
} else // No, see if we need to adjust
{
//
//
//
if ( SD16_average < ( (VT_target-VT_range) * SD16_volt ) ) // Too hot?
{
if ( PWM_next != 0 )
{
PWM_next--; // YES - cut down the adjustment
}
}
if ( SD16_average > ( (VT_target+VT_range) * SD16_volt ) ) // Too cool?
{
if ( PWM_next != (PWM_max-1) )
{
PWM_next++; // YES - increase the adjustment
}
}
}
}
#else
{
int cur_state=0; // Current state
if ( (O2_warmup & 0x07) == 0 )
{
cur_state = O2_warmup >> 3; // Convert 1sec to 8 sec internval
switch ( cur_state )
{
case 0:
PWM_next=PWM_max;
LED_next=LED_on;
break;
case 1:
PWM_next=0;
LED_next=LED_off;
break;
case 2:
PWM_next=1;
LED_next=LED_on;
break;
case 3:
PWM_next=2;
LED_next=LED_off;
break;
case 4:
PWM_next=3;
LED_next=LED_on;
break;
case 5:
PWM_next=4;
LED_next=LED_off;
break;
case 6:
PWM_next=5;
LED_next=LED_on;
break;
case 7:
PWM_next=6;
LED_next=LED_off;
break;
case 8:
PWM_next=7;
LED_next=LED_on;
break;
default:
O2_warmup = 0xff;
}
}
cur_state = SD16_average + 1; // Add and forget
O2_warmup+=1; // Count this one second interval
}
#endif
}
// 0xFFEA Sigma Delta ADC
#pragma vector=SD16_VECTOR
__interrupt void sd16_vector(void)
{
int i; // Counter
unsigned long average=0; // Set the average to zero
//
// Looks like it is making 30,780 samples/sec
//
#ifdef __SD16__
if ( SD16_cnt == WDCLK_cnt ) // Same seconds as last?
{
SD16_last[1]++; // Count this conversion
} else
{
SD16_last[0] = SD16_last[1]; // Remember the last count
SD16_last[1] = 0; // Reset the conversion counter
SD16_cnt = WDCLK_cnt; // Reset for next second
}
#endif
//
// Based upon the PWM state, clock out the next bit
//
if ( PWM_next == PWM_max ) // Turn off the PWM output?
{
P1DIR &= (0xff - 0x08); // Turn off the PWM
} else
{
P1DIR |= 0x08; // Turn on the PWM
//
// Check for reloading the shift register
//
if ( PWM_state == 0x1 ) // Time for a reload?
{
PWM_state = PWM_value[PWM_next]; // Grab the next value
PWM_state |= 0x100; // Set the EOF bit
LED_next = PWM_state; // Set the LED flash to current PWM pattern
LED_state = 0x2; // Trigger early LED reload and avoid shift race condition
}
if ( PWM_state & 0x1 )
{
P1OUT |= 0x08; // Turn on the output
} else
{
P1OUT &= (0xff - 0x08); // Turn off the output
}
PWM_state >>= 1; // Shift out the last bits
}
//
// Last, figure the average, so the next timer tick and set the mode
//
for (i=0;i<7;i++)
{
SD16_value[i] = SD16_value[i+1]; // Shift everyone down
average += SD16_value[i]; // Add to accumulator
}
SD16_value[7] = SD16MEM0; // Save the most recent
average += SD16_value[7]; // Add to accumulator
if (SD16_value[0] != 0) // Do we have enough for useful average?
{
average >>= 3; // Now we average everyone
SD16_average = average; // Do an atomic update to average
}
}