Skip to content

Instantly share code, notes, and snippets.

@mrmekon
Created January 16, 2012 23:05
Show Gist options
  • Save mrmekon/1623498 to your computer and use it in GitHub Desktop.
Save mrmekon/1623498 to your computer and use it in GitHub Desktop.
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
all: default run
default:
msp430-gcc -I/usr/local/msp430-gcc-4.4.3//msp430/include/ ldpump_tester.c -save-temps -mendup-at=main -mmcu=msp430x2111 -Os
run:
mspdebug rf2500 "prog a.out"
dump:
mspdebug rf2500 "md 0x1000 256"
erase_info:
mspdebug rf2500 "erase segment 0x1000"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment