MSP430 circuit to measure and trick 1997 Jetta LD Pump
/* 1997 Volkwagen Jetta LD Pump Circuit tester/bypasser | |
* Trevor Bentley, September 2011 | |
* | |
* | |
* Operation: | |
* - Displays welcome message on boot | |
* - Dumps 256 bytes of Informational Flash (if UART enabled) | |
* - Enters very low power state while waiting | |
* - Wakes up when a falling edge detected on P1.4, which should go to | |
* the power output of the ECM to the LD Pump solenoid. This requires | |
* extra circuitry. | |
* - Measures each edge as a 'pump', counts number of pumps. | |
* - Red LED (P1.0) blinks rapidly while pumping | |
* - When reach target number of pumps (PUMP_TARGET), green LED (P1.6) | |
* turns on, and SENSE line is pulled high (P1.5). This is held for | |
* SENSE_LINE_SECONDS. | |
* - Measured total number of pumps, and time to reach target number | |
* of pumps. | |
* - Red and Green LEDs both on while measurements written to Informational | |
* Flash. | |
* - Most recent measurements displayed (if UART enabled) | |
* - Back to very low power state while waiting. | |
* | |
* | |
* | |
* EXAMPLE CIRCUIT: | |
* | |
* BAT(-)----------------------- | |
* BAT(+)-----------[3.3REG] | | |
* | | | | |
* [C VCC]--|-^^^|--[ opto ]--^^^|----[P1.4 ] | |
* [A ] _ > [isolator] [ P1.0] - Red LED | |
* [R ] ^ > [ ] [ P1.6] - Green LED | |
* [ ] | > [ ] [ ] | |
* [E GND]--|----|--[ ] [ MSP430] | |
* [C ] [ ] | |
* [M SENSE]---^^^-----[transistor]-------[P1.5 ] | |
* | | | |
* BAT(+)----^^^-/ \--BAT(-) | |
* | |
* | |
* MSP430 powered by 3.3V regulator off of constant battery power. | |
* | |
* Car ECM connects to optoisolator via voltage divider (divide to 3V, | |
* and current limit). Safety diode from ground to power. Optoisolator | |
* connects to P1.4, pulled up to constant battery power. Car ECM will | |
* switch either VCC or GND on and off to pump the LD Pump, which this | |
* circuit replaces. This will result in toggling on P1.4. | |
* | |
* P1.5 controls base of transistor that pulls sense line up to constant | |
* battery power. | |
* | |
*/ | |
#include <msp430x22x2.h> | |
#include <signal.h> | |
#include <string.h> | |
//------------------------------------------------------------------------------ | |
// Hardware-related definitions | |
//------------------------------------------------------------------------------ | |
#define UART_TXD 0x02 // TXD on P1.1 (Timer0_A.OUT0) | |
#define UART_RXD 0x04 // RXD on P1.2 (Timer0_A.CCI1A) | |
#define LDPUMP 0x10 // LDPump detect P1.4 | |
#define SENSE 0x20 // Sense Line P1.5 | |
#define RED_LED 0x01 // Red LED | |
#define GREEN_LED 0x40 // Green LED | |
//#define TEST_VALS | |
#ifndef TEST_VALS | |
#define SENSE_LINE_SECONDS 90U // Seconds to hold SENSE line | |
#define PUMP_TARGET 30U // Number of 'pumps' to detect | |
#else | |
#define SENSE_LINE_SECONDS 4U | |
#define PUMP_TARGET 10U | |
#endif | |
#define CLOCK_DIV (10U) | |
#define MS_PER_TICK (10*CLOCK_DIV) // Milliseconds per clock interrupt | |
// Number of clock interrupts to hold SENSE line for. | |
#define SENSE_TICKS (SENSE_LINE_SECONDS * (1000 / MS_PER_TICK)) | |
// Base address of Informational Flash | |
#define INFO_FLASH_BASE 0x1000 | |
//------------------------------------------------------------------------------ | |
// Conditions for 9600 Baud SW UART, SMCLK = 1MHz | |
//------------------------------------------------------------------------------ | |
#define UART_ENABLED | |
#ifdef UART_ENABLED | |
#define UART_TBIT_DIV_2 (1000000U / (9600 * 2)) | |
#define UART_TBIT (1000000U / 9600) | |
#endif | |
//------------------------------------------------------------------------------ | |
// Global variables used for full-duplex UART communication | |
//------------------------------------------------------------------------------ | |
#ifdef UART_ENABLED | |
unsigned int txData = 0; // UART internal variable for TX | |
unsigned char rxBuffer = 0; // Received UART character | |
#endif | |
// Structure for storing measurements | |
typedef struct { | |
unsigned int pump_count; | |
unsigned int timer_count; | |
} measure_t; | |
// Number of elements in measurements[] | |
#define MEASUREMENT_COUNT (64/sizeof(measure_t)) | |
// Mask for wrapping measurements[] index | |
#define MEASUREMENT_MASK (MEASUREMENT_COUNT-1) | |
// Array of measurement readings | |
measure_t measurements[MEASUREMENT_COUNT]; | |
// Index of current measurement | |
unsigned int measurement_idx = 0; | |
// State machine variable | |
volatile enum tstate { | |
WAITING, | |
PUMPING, | |
HOLDING, | |
UARTTX | |
} timer_state = WAITING; | |
// LUT for hex characters | |
unsigned char hexvals[] = { | |
'0','1','2','3','4','5','6','7', | |
'8','9','A','B','C','D','E','F', | |
}; | |
volatile unsigned int counter = 0; // Timer interrupt ticks | |
volatile unsigned int toggles = 0; // Toggles of pumps | |
volatile unsigned int counter_cycles_until_stop = 0; // IRQ ticks until done | |
//------------------------------------------------------------------------------ | |
// Function prototypes | |
//------------------------------------------------------------------------------ | |
void reset_ldpump(void); | |
void erase_flash(int addr); | |
void save_byte(char byte, char flash_offset); | |
void save_measurements(void); | |
void end_sense_hold(void); | |
void TimerA_Counter_init(void); | |
void Port1_ISR(void); | |
void Timer_A0_ISR(void); | |
#ifdef UART_ENABLED | |
void print_values(void); | |
void uart_isr(void); | |
void TimerA_UART_init(void); | |
void TimerA_UART_tx(unsigned char byte); | |
void TimerA_UART_print(char *string); | |
void TimerB_ISR(void); | |
#endif | |
//------------------------------------------------------------------------------ | |
// main() | |
//------------------------------------------------------------------------------ | |
void main(void) | |
{ | |
#ifdef UART_ENABLED | |
int temp; | |
int i; | |
char str[4]; | |
#endif | |
// Configure clocks | |
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer | |
DCOCTL = 0x00; // Set DCOCLK to 1MHz | |
BCSCTL1 = CALBC1_1MHZ; | |
DCOCTL = CALDCO_1MHZ; | |
// Configure Port 1 | |
P1OUT = 0x00; // Initialize all GPIO | |
P1DIR = 0xFF & ~(UART_RXD | LDPUMP); // Set all pins but RXD to output | |
#ifdef UART_ENABLED | |
P1SEL = UART_TXD | UART_RXD; // Timer function for TXD/RXD pins | |
#endif | |
// P1.4 is interrupt -- LD Pump solenoid line | |
P1IE = 0x10; // Turn on P1.4 interrupts | |
P1IES = 0x10; // High->low edge detect | |
P1REN = 0x10; // Pull-down resistor enabled | |
// Clear measurements | |
memset(measurements, 0xFF, sizeof(measurements)); | |
eint(); // Enable interrupts | |
#ifdef UART_ENABLED | |
// Print 256-bytes of informational flash | |
TimerA_UART_init(); | |
TimerA_UART_print("\r\nLDP-TEST\r\n"); | |
str[2] = ' '; | |
str[3] = 0; | |
for (i = 0; i < 256; i++) { | |
temp = *((volatile char*)INFO_FLASH_BASE+i); | |
str[0] = hexvals[temp >> 4 & 0xF]; | |
str[1] = hexvals[temp & 0xF]; | |
TimerA_UART_print(str); | |
} | |
TimerA_UART_print("\r\n$$$%%%$$$\r\n\r\n"); | |
#endif | |
// Enable pump detector | |
TimerA_Counter_init(); | |
for (;;) | |
{ | |
// Sleep in super-low-power-mode | |
LPM3; | |
// Go to moderate-low-power-mode after PIO wakes us up | |
LPM0; | |
// Awoken after a pump session has finished and written to flash. | |
// Print status (if enabled), and reset state machine | |
#ifdef UART_ENABLED | |
print_values(); | |
#endif | |
reset_ldpump(); | |
} | |
} | |
// Write entire 'measurements' variable to information flash (erases first) | |
// Manages interrupts itself. Do NOT disable. | |
void save_measurements(void) { | |
int i; | |
static int count = 0; | |
static int offset = 0; | |
unsigned char *p = (unsigned char*)measurements; | |
dint(); | |
FCTL3 = FWKEY; // Unlock | |
FCTL1 = FWKEY | ERASE ; // Erase | |
*((volatile char*)(INFO_FLASH_BASE + offset)) = 0; | |
FCTL1 = FWKEY | WRT; // Write | |
memcpy((void*)(INFO_FLASH_BASE + offset + i), p, | |
sizeof(measurements)); | |
FCTL1 = FWKEY; // No-op | |
FCTL3 = FWKEY | LOCK; // Lock | |
// If we've written a full block... | |
if (++count == MEASUREMENT_COUNT) { | |
count = 0; | |
// Clear measurements | |
memset(measurements, 0xFF, sizeof(measurements)); | |
// Next time, write to next block of FLASH. We have 3 64-byte blocks. | |
offset += 0x40; | |
if (offset == 0xc0) offset = 0; | |
} | |
eint(); | |
} | |
void reset_ldpump(void) { | |
// Reset counters and state | |
counter = 0; | |
toggles = 0; | |
counter_cycles_until_stop = 0; | |
timer_state = WAITING; | |
P1OUT &= ~(RED_LED | GREEN_LED | SENSE); // Disable lights and sense | |
} | |
//------------------------------------------------------------------------------ | |
// Turn on timer, interrupt ticks every 10ms | |
// Interrupt shared with UART, so only one can be enabled at a time. | |
//------------------------------------------------------------------------------ | |
void TimerA_Counter_init(void) { | |
// Configure Timer_A | |
// Internal 1MHz timer divided by 8 (ID_3) == 125000 / s | |
// (12500 ticks/s) / 100 == 1250 ticks per 10 ms | |
// One interrupt per 10*CLOCK_DIV ms. | |
TACTL = MC_0; // Disable timer | |
TACCR0 = 1250*CLOCK_DIV; // ticks per interrupt | |
TACCTL0 = CCIE | OUTMOD_7; // Enable interrupts, reset on overflow | |
timer_state = WAITING; | |
TACTL = TASSEL_2 | MC_1 | TACLR | ID_3; // Set to 1MHz clock, turn on | |
} | |
//------------------------------------------------------------------------------ | |
// Interrupt when edge detected on P1.4, signifying switching | |
// on LD Pump solenoid line. | |
//------------------------------------------------------------------------------ | |
interrupt(PORT1_VECTOR) Port1_ISR(void) { | |
if (timer_state == WAITING) { | |
// We were waiting in super-low-power mode. Wake up! | |
LPM3_EXIT; | |
// No longer waiting, it's pump time | |
timer_state = PUMPING; | |
} | |
else if (++toggles == PUMP_TARGET) { | |
// We reached our target number of pumps, so trigger SENSE line. | |
P1OUT |= SENSE | GREEN_LED; | |
P1OUT &= ~(RED_LED); | |
counter_cycles_until_stop = SENSE_TICKS; | |
timer_state = HOLDING; | |
} | |
P1IFG = 0x00; | |
} | |
void end_sense_hold(void) { | |
volatile int i; | |
P1OUT |= RED_LED; | |
measurements[measurement_idx].pump_count = toggles; | |
measurements[measurement_idx].timer_count = counter; | |
measurement_idx = (measurement_idx + 1) & MEASUREMENT_MASK; | |
save_measurements(); | |
P1OUT &= ~RED_LED; | |
} | |
//------------------------------------------------------------------------------ | |
// Timer_A UART - Transmit Interrupt Handler | |
//------------------------------------------------------------------------------ | |
enablenested interrupt (TIMERA0_VECTOR) Timer_A0_ISR(void) | |
{ | |
static unsigned char txBitCnt = 10; | |
switch (timer_state) { | |
case WAITING: // Waiting to detect first pump from ECM | |
break; | |
case PUMPING: // Pumps detected, counting to target number | |
// Increment counter, toggle LED | |
if (++counter) | |
P1OUT ^= RED_LED; | |
break; | |
case HOLDING: // Target number of pumps reached, holding SENSE line | |
if (--counter_cycles_until_stop == 0) { | |
end_sense_hold(); | |
// Wake up main. It will print status and reset state machine | |
LPM0_EXIT; | |
} | |
break; | |
#ifdef UART_ENABLED | |
case UARTTX: | |
goto uart; | |
#endif | |
default: | |
break; | |
} | |
return; | |
#ifdef UART_ENABLED | |
uart: | |
TACCR0 += UART_TBIT; // Add Offset to CCRx | |
if (txBitCnt == 0) { // All bits TXed? | |
TACCTL0 &= ~CCIE; // All bits TXed, disable interrupt | |
txBitCnt = 10; // Re-load bit counter | |
} | |
else { | |
if (txData & 0x01) { | |
TACCTL0 &= ~OUTMOD2; // TX Mark '1' | |
} | |
else { | |
TACCTL0 |= OUTMOD2; // TX Space '0' | |
} | |
txData >>= 1; | |
txBitCnt--; | |
} | |
#endif | |
} | |
#ifdef UART_ENABLED | |
void print_values(void) { | |
unsigned char mstr[10]; | |
TimerA_UART_init(); | |
mstr[0] = hexvals[toggles >> 12 & 0xF]; | |
mstr[1] = hexvals[toggles >> 8 & 0xF]; | |
mstr[2] = hexvals[toggles >> 4 & 0xF]; | |
mstr[3] = hexvals[toggles & 0xF]; | |
mstr[4] = ' '; | |
mstr[5] = hexvals[counter >> 12 & 0xf]; | |
mstr[6] = hexvals[counter >> 8 & 0xF]; | |
mstr[7] = hexvals[counter >> 4 & 0xF]; | |
mstr[8] = hexvals[counter & 0xF]; | |
mstr[9] = 0; | |
TimerA_UART_print(mstr); | |
TimerA_UART_print("\r\n\r\n"); | |
TimerA_Counter_init(); | |
} | |
//------------------------------------------------------------------------------ | |
// Function configures Timer_A for full-duplex UART operation | |
//------------------------------------------------------------------------------ | |
void TimerA_UART_init(void) | |
{ | |
TACTL = MC_0; // Disable timer | |
TACCTL0 = OUT; // Set TXD Idle as Mark = '1' | |
TACCTL1 = SCS + CM1 + CAP + CCIE; // Sync, Neg Edge, Capture, Int | |
timer_state = UARTTX; | |
TACTL = TASSEL_2 + MC_2 + TACLR; // SMCLK, start in continuous mode | |
} | |
//------------------------------------------------------------------------------ | |
// Outputs one byte using the Timer_A UART | |
//------------------------------------------------------------------------------ | |
void TimerA_UART_tx(unsigned char byte) | |
{ | |
while (TACCTL0 & CCIE); // Ensure last char got TX'd | |
TACCR0 = TAR; // Current state of TA counter | |
TACCR0 += UART_TBIT; // One bit time till first bit | |
TACCTL0 = OUTMOD0 + CCIE; // Set TXD on EQU0, Int | |
txData = byte; // Load global variable | |
txData |= 0x100; // Add mark stop bit to TXData | |
txData <<= 1; // Add space start bit | |
} | |
//------------------------------------------------------------------------------ | |
// Prints a string over using the Timer_A UART | |
//------------------------------------------------------------------------------ | |
void TimerA_UART_print(char *string) | |
{ | |
while (*string) { | |
TimerA_UART_tx(*string++); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
// Timer_A UART - Receive Interrupt Handler | |
//------------------------------------------------------------------------------ | |
interrupt (TIMERA1_VECTOR) Timer_A1_ISR(void) | |
{ | |
} | |
//------------------------------------------------------------------------------ | |
#endif //UART_ENABLED |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment