Cool project, I did something very similar as well!
https://github.com/lgbeno/BLDC-Booster
There is software on there too that you are free to reference. It works with some exceptions. The method that I used for commutation is bemf integration similar to the ti instaspin approach. The issues that I had was that for very fast motors(>500Kv), I wasn't sampling the ADC fast enough to get the commutation timing right but for slow motors, it works great.
A few words of wisdom since I worked on this project for quite some time. I would recommend using the 28pin Tssop or qfn32 versions of g2553. Reason being is that it has 3 pins attached to ta1.1 which you can use for the high side pwm signals and 3 pins attached to ta1.2 to be used for low side pwm signals. This does 3 things, frees up timer 0 for something else, allows you to do dead time insertion (if your gate drivers don't already) and just makes your code easier too!
The other thing is that you will see your pwm super imposed on your bemf which will likely cause false trips of the comparator. To solve this, there's a low pass filter but that also introduces a dc offset that throws off your zero cross point.
Long story short, I came up with a different network that filters and then ac couples the bemf to the comparator. I was seeing pretty good results with that approach. Then I had to return my borrowed scope and have not been able to work on it. Good news is I'm getting a brand new Rigol DS2072 for keeps on Thursday.
Anyway I guess that is my project life story, hope it helps. Tomorrow ill try to dig up schematics with those improvements (not in github yet).
Btw the schematics are very inspired by the microkoptor.de schematics, mine are as well... I wonder how they got away with the pwm glitches in their software. Obviously theirs works quite well.
my current project is to build a controller for a sensorless BLDC motor using a MSP430G2553. I want to realize trapezoidal control with Back-EMF/zero-crossing detection. I plan to detect zero-crossing with the Comparator(CA2/3/4 vs. CA1) and generate PWM using Timer1. Timer0 will be used for things like 30
BLMC_.zip
//******************************************************************************
//
// Electronic Fuel Injection, Written by Jack Murray 2011
// NOTE, this is not cleaned up finished code, but decided to share it anyway.
//
// We use a Geo Metro 1.0L Throttle Body Injector (TBI) for fuel
// The throttle is an electronic controlled from a 2004 Prius
// The water temp sensor is from a '87 Chevy
// The 02 sensor is a generic narrow band.
// We use the on-chip temp sensor to proxy the air temp
// The Fuel Pump is from an '89 Jeep Cherokee, and is turned on through
// a relay from a Geo Metro.
//
// The EFI control is simply to determine how much time to turn on the injector,
// which is how long to make the pulse widths.
//
// For the tractor, we also control the throttle, and try to maintain a
// given RPM that is determined by the RPM adjustment knob
//
//
// The controller is a repurposed MSP4301232 board that was a battery charger.
// It has a two line LCD, one darlington, two mosfets and 8 10bit ADC inputs
// We use the darlington to turn on the fuel pump relay, which is P1.1
// Mosfet A is used to for the fuel injector driver, P1.3 TA2
// Mosfet B is used to control the throttle, P1.2 TA1
// ADC inputs:
// A0 -- user interface selector
// A1 o2 sensor
// A2 -- lcd dial
// A3 water temp
// A4 rpm adjuster pot
// A5 -- connected to lcd
// A6 MAP sensor
// A7 throttle position sensor from prius
//
// P2.5 is the tach input, that goes low on an ignition event
//
//******************************************************************************
#include <signal.h>
#include <io.h>
// efi settings, we need to limit this to 128 bytes if possible
// so we can erase one segment and not lose everything if something goes wrong
// during the update.
struct ConfigEFI {
unsigned char valid; // whether containts are valid
unsigned char airTable[32]; // PWM Adjustment for air temperature
unsigned char watTable[32]; // PWM Adjustment for water temp
unsigned char rpmTable[32]; // PWM Adjustment for rpm
};
// Copy of current config parameters in Memory
struct ConfigEFI Config, *FConfig;
unsigned short selectime;
unsigned short selecting;
unsigned short dstate, lastSeconds, seconds;
unsigned short lastdial, dialpos;
unsigned short throttleTimeOn, throttleTimeOff, throttleIsOn;
unsigned short injectorIsOn, injectorStartTime, injectorPulseWidth;
unsigned short injectorBaseWidth;
unsigned short injectorTimeOff, injectorTimeOn;
unsigned short dial, tps;
unsigned short targetTPS = 500;
unsigned short tach, rpm, lastTach;
unsigned short airSensor;
unsigned short waterSensor;
unsigned short mapSensor;
unsigned short o2Sensor;
#define Reboot() WDTCTL = 0xFFFF // cause reset from invalid WDT password
flashcopy(char *mptr, char *fptr, int size)
{
int i;
for (i=0; i<size; i++)
{
asm("dint");
FCTL3 = FWKEY; // Clear Lock bit
FCTL1 = FWKEY + WRT; // Set WRT bit for write operation
*fptr++ = *mptr++; // Write value to flash
FCTL1 = FWKEY; // Clear WRT bit
FCTL3 = FWKEY + LOCK; // Reset LOCK bit
asm("eint");
}
}
// We save configs to flash. We can only erase an entire segment,
// so we first erase all of Segment A, copy the Segment B values + New values into A,
// then erase B, and then copy A into B. B now has the updated values.
//
SaveConfig()
{
int c,i;
char *mptr;
char *fptr;
int cfgsize = sizeof(Config);
struct ConfigEFI *Aptr, *Bptr;
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
FCTL2 = FWKEY + FSSEL0 + FN4; // 5Mhz MCLK/16 = 312Khz for Flash Timing Generator
//
// Erase Segment A
fptr = (char *)Aptr; // Initialize Flash segment B pointer
asm("dint");
FCTL1 = FWKEY + ERASE; // Set Erase bit
FCTL3 = FWKEY; // Clear Lock bit
*fptr = 0; // Dummy write to erase Flash segment A
asm("eint");
//
// Write Segment A from memory
mptr = (char *)&Config;
flashcopy(mptr, (char *)Aptr, cfgsize);
//
// Now all of Erase B
fptr = (char *)Bptr; // Initialize Flash segment B pointer
asm("dint");
FCTL1 = FWKEY + ERASE; // Set Erase bit
FCTL3 = FWKEY; // Clear Lock bit
*fptr = 0; // Dummy write to erase Flash segment A
asm("eint");
//
// Now copy all of A to B
flashcopy((char *)Aptr, (char *)Bptr, cfgsize);
// restart watchdog
// WDTCTL = WDT_ADLY_1000; // Set Watchdog Timer interval back to 1000ms (1-sec)
}
unsigned short adcRead(unsigned short channel)
{
unsigned short val;
ADC10CTL0 &= ~ENC; // Disable while changing input channel
ADC10CTL1 = (channel << 12);
ADC10CTL0 |= ENC + ADC10SC; // Sampling open
while ((ADC10CTL1 & ADC10BUSY) == 1); // ADC10BUSY?
val = ADC10MEM;
return val;
}
unsigned short readchannel(unsigned short channel)
{
unsigned short i, val;
unsigned long avg;
avg = 0;
for(i=0; i<256; i++) {
avg = avg + adcRead(channel);
}
val = avg >> 8;
return val;
}
unsigned short waterSensor;
unsigned short waterTemp;
unsigned short readWaterTemp(unsigned short channel)
{
unsigned short val, tmp1, tmp2, tmp3, tmp4;
//
// The water temp is a GM water temp sensor
// It has a log-scale of 10k at 0F down to 100 ohm at 250F
// We use a divider circuit with a 330ohm resistor,
// basically we just hardcode the readings to 32 levels
ADC10CTL0 = REFON | REF2_5V | ADC10SHT_2 | SREF_1; // turn OFF
ADC10CTL0 = ADC10ON | REFON | REF2_5V | ADC10SHT_2 | SREF_1; // use 2.5v ref
// ADC10CTL0 = ADC10ON | ADC10SHT_3 | SREF_0; // use VCC ref
tmp1 = adcRead(3);
tmp2 = adcRead(3);
tmp3 = adcRead(3);
tmp4 = readchannel(3);
waterSensor = (tmp1 + tmp2 + tmp3 + tmp4) >> 2;
// *=measured
// temp res volts 1.5v 2.5v
// 244 F : 110 ohm : 0.75v : 512 : 307
// 240 F : 120 ohm : 0.80v : 546 : 327
// 236 F : 130 ohm : 0.85v : 578 : 347
// 232 F : 140 ohm : 0.89v : 610 : 366
// 228 F : 150 ohm : 0.94v : 640 : 384
// 224 F : 160 ohm : 0.98v : 668 : 401
// 220 F : 170 ohm : 1.02v : 696 : 417
// 216 F : 180 ohm : 1.06v : 722 : 433
//*212 F : 190 ohm : 1.10v : 748 : 448
// 208 F : 200 ohm : 1.13v : 772 : 463
// 204 F : 210 ohm : 1.17v : 796 : 477
//*200 F : 220 ohm : 1.20v : 819 : 491
// 196 F : 236 ohm : 1.25v : 853 : 512
// 192 F : 252 ohm : 1.30v : 886 : 532
// 188 F : 268 ohm : 1.34v : 917 : 550
// 184 F : 284 ohm : 1.39v : 947 : 568
//*180 F : 300 ohm : 1.43v : 975 : 585
// 176 F : 324 ohm : 1.49v : 1014 : 608
// 172 F : 348 ohm : 1.54v : 1051 : 630
// 168 F : 372 ohm : 1.59v : 1085 : 651
// 164 F : 396 ohm : 1.64v : 1117 : 670
//*160 F : 420 ohm : 1.68v : 1146 : 688
// 156 F : 460 ohm : 1.75v : 1192 : 715
// 152 F : 500 ohm : 1.81v : 1233 : 740
// 148 F : 540 ohm : 1.86v : 1271 : 762
// 144 F : 580 ohm : 1.91v : 1305 : 783
//*140 F : 620 ohm : 1.96v : 1336 : 801
// 136 F : 660 ohm : 2.00v : 1365 : 819
// 132 F : 700 ohm : 2.04v : 1391 : 835
// 128 F : 740 ohm : 2.07v : 1416 : 849
// 124 F : 780 ohm : 2.11v : 1439 : 863
// 120 F : 820 ohm : 2.14v : 1460 : 876
if (waterSensor < 600) { // if temp is > 180, we switch to using the 1.5v reference to get better resolution
ADC10CTL0 = REFON | ADC10SHT_2 | SREF_1; // use VCC ref, turn OFF
ADC10CTL0 = ADC10ON | REFON | ADC10SHT_2 | SREF_1; // use 1.5v ref
tmp1 = adcRead(3);
tmp2 = adcRead(3);
tmp3 = adcRead(3);
tmp4 = adcRead(3);
// switch it back to 2.5v
ADC10CTL0 = REFON | REF2_5V | ADC10SHT_2 | SREF_1; // turn OFF
ADC10CTL0 = ADC10ON | REFON | REF2_5V | ADC10SHT_2 | SREF_1; // use 2.5v ref
waterSensor = (tmp1 + tmp2 + tmp3 + tmp4) >> 2;
if (waterSensor <= 512) { waterTemp = 32; }
else if (waterSensor <= 546) { waterTemp = 31; }
else if (waterSensor <= 578) { waterTemp = 30; }
else if (waterSensor <= 610) { waterTemp = 29; }
else if (waterSensor <= 640) { waterTemp = 28; }
else if (waterSensor <= 668) { waterTemp = 27; }
else if (waterSensor <= 696) { waterTemp = 26; }
else if (waterSensor <= 722) { waterTemp = 25; }
else if (waterSensor <= 748) { waterTemp = 24; }
else if (waterSensor <= 772) { waterTemp = 23; }
else if (waterSensor <= 796) { waterTemp = 22; }
else if (waterSensor <= 819) { waterTemp = 21; }
else if (waterSensor <= 853) { waterTemp = 20; }
else if (waterSensor <= 886) { waterTemp = 19; }
else if (waterSensor <= 917) { waterTemp = 18; }
else if (waterSensor <= 947) { waterTemp = 17; }
else if (waterSensor <= 975) { waterTemp = 16; }
else { waterTemp = 15; }
}
// 176 F : 324 ohm : 1.49v : 1014 : 608
// 172 F : 348 ohm : 1.54v : 1051 : 630
// 168 F : 372 ohm : 1.59v : 1085 : 651
// 164 F : 396 ohm : 1.64v : 1117 : 670
// 160 F : 420 ohm : 1.68v : 1146 : 688
// 156 F : 460 ohm : 1.75v : 1192 : 715
// 152 F : 500 ohm : 1.81v : 1233 : 740
// 148 F : 540 ohm : 1.86v : 1271 : 762
// 144 F : 580 ohm : 1.91v : 1305 : 783
// 140 F : 620 ohm : 1.96v : 1336 : 801
// 136 F : 660 ohm : 2.00v : 1365 : 819
// 132 F : 700 ohm : 2.04v : 1391 : 835
// 128 F : 740 ohm : 2.07v : 1416 : 849
// 124 F : 780 ohm : 2.11v : 1439 : 863
// 120 F : 820 ohm : 2.14v : 1460 : 876
else if (waterSensor <= 608) { waterTemp = 15; }
else if (waterSensor <= 630) { waterTemp = 14; }
else if (waterSensor <= 651) { waterTemp = 13; }
else if (waterSensor <= 670) { waterTemp = 12; }
else if (waterSensor <= 688) { waterTemp = 11; }
else if (waterSensor <= 715) { waterTemp = 10; }
else if (waterSensor <= 740) { waterTemp = 9; }
else if (waterSensor <= 762) { waterTemp = 8; }
else if (waterSensor <= 783) { waterTemp = 7; }
else if (waterSensor <= 801) { waterTemp = 6; }
else if (waterSensor <= 819) { waterTemp = 5; }
else if (waterSensor <= 835) { waterTemp = 4; }
else if (waterSensor <= 849) { waterTemp = 3; }
else if (waterSensor <= 863) { waterTemp = 2; }
else if (waterSensor <= 1024) { waterTemp = 1; }
return waterTemp;
}
__attribute__ ((naked)) void delay() {
//first arg comes in register R15, loop uses 3 cycles per round
//the eight cycles come from the call overhead and ret
//delay_time = (1/MCLK)*(8+(3*n))
asm("xloop: dec r15\n jnz xloop\n ret");
}
// To send command, Set Enable to High, set RS+DB command, then Clear Enable
void
LCDout(int rs, int data)
{
unsigned char high4 = (data >> 4) & 0x0F;
unsigned char low4 = (data & 0x0F);
unsigned char status = 0;
unsigned char clear = 0;
unsigned short max = 0;
if (rs) {
P3OUT = 0x30; // Set Enable + RS, clear data bits
P3OUT |= high4; // Set high 4-bits
P3OUT &= ~0x10; // Clear Enable
P3OUT = 0x30; // Set Enable + RS
P3OUT |= low4; // Set low 4-bits
P3OUT &= ~0x10; // Clear Enable
}
else {
P3OUT = 0x10; // Set Enable, Clear RS, Clear data bits
P3OUT |= high4; // Set high 4-bits
P3OUT &= ~0x10; // Clear Enable
// Now write low4 bits
P3OUT = 0x10; // Set Enable, Clear RS, Clear data bits
P3OUT |= low4; // Set low 4-bits
P3OUT &= ~0x10; // Clear Enable
}
delay(750*5);
return;
}
void
LCDouts(char *str)
{
while (*str != 0) {
LCDout(1, *str++); //write char and increment pointer, careful about 7-8 position!
}
}
// initialize LCD.
// P3.0-1-2-3 connected to DB4-5-6-7
// P3.4 is Enable, P3.5 is RS(RegisterSelect, Read/Write is wired to Zero/Ground.
void
initLCD()
{
unsigned int wait;
P3OUT = 0x00; // Clear P3.0-7
P3DIR = 0x3F; // Set P3.0-5 to output direction
P3OUT = 0x00; // Clear Enable, RS, Data
P3OUT = 0x03; // Set Data Cmd for 8bit mode
P3OUT |= 0x10; // Set Enable.
P3OUT &= ~0x10; // Clear Enable.
delay(750*20);
P3OUT = 0x03; // Set Data Cmd
P3OUT |= 0x10; // Set Enable.
P3OUT &= ~0x10; // Clear Enable.
delay(750*20);
P3OUT = 0x03; // Set Data Cmd
P3OUT |= 0x10; // Set Enable.
P3OUT &= ~0x10; // Clear Enable.
delay(750*20);
P3OUT = 0x02; // Now set Data Cmd to 0x20 for switch to 4bit mode
P3OUT |= 0x10; // Set Enable.
P3OUT &= ~0x10; // Clear Enable.
delay(750*20);
// Now we are in 4bit mode. Do command again, now in 4bit mode.
P3OUT = 0x02; // Now send Function Set Data Cmd to 0x20 for switch to 4bit mode
P3OUT |= 0x10; // Set Enable.
P3OUT &= ~0x10; // Clear Enable.
P3OUT = 0x0C; // Send lower 4bit for 2-lines
P3OUT |= 0x10; // Set Enable.
P3OUT &= ~0x10; // Clear Enable.
delay(750*20);
unsigned short
BCDecode(unsigned short val, unsigned char *d1, unsigned char *d2, unsigned char *d3)
{
*d1 = val % 10;
val = val / 10;
*d2 = val % 10;
val = val / 10;
if (d3)
{ *d3 = val % 10;
val = val / 10;
}
return val;
}
// output charge time. We increment only every 4 seconds, so 15 seconds is actually 60
// we divide by 15 to get minutes.
// 11.72v Ach 12:22
// 11.72v Ach 81F
LCDout_minutes (unsigned short ticks)
{
unsigned short val;
char d1,d2;
val = ticks/60;
BCDecode(val, &d1, &d2, 0);
if (d2>0) { LCDdigit(d2); } else { LCDout(1,' '); }
LCDdigit(d1);
// Watchdog Timer interrupt service routine
// Runs each second
interrupt (WDT_VECTOR)
watchdog_timer(void)
{
unsigned short volts,temp;
seconds++;
_BIC_SR_IRQ(CPUOFF); // Clear CPUOFF bits to wake up main loop
}
//
// with two batteries charging, the ADC dial reading fluctuates badly,
// so we stablize it by taking an average reading over 65,000 readings
// and return that value, and then use hysteresis to have it snap
// from one position to the next.
//
newDial()
{
unsigned short pos, ch1;
while (selecting < 2) { // no button pressed
ch1 = readchannel(2); // read position
pos = ch1/64; // ndiv;
if ((pos > dialpos) && (ch1-5 < lastdial))
{ pos = dialpos; }
if ((pos < dialpos) && (ch1+5 > lastdial))
{ pos = dialpos; }
if (pos != dialpos) {
// we have a new position, update globals and return
lastdial = ch1;
dialpos = pos;
selecting = 1; // reset watchdog timeout shutdown
return dialpos;
}
}
return dialpos;
}
int
SelectInput(char *options)
{
LCDout(0, 0xC0); // position cursor at 2nd line
LCDouts(options);
LCDout(0, 0xC0); // position cursor at 2nd line
LCDout(0, 0x0F); // On, Cursor, Blink
// init globals
dialpos = 16;
lastdial = 1060;
selecting = 1;
while (selecting < 2) { // button not pressed (again)
newDial(); // readchannel(INCH_2); // read position
LCDout(0, 0xC0+dialpos); // update cursor position
}
return dialpos; // return position when button pressed.
}
int
buttonPress()
{
int val;
unsigned int ch1, ch2, pos, cpos;
while (1) {
//
// Offer selection of setting to edit
//
LCDout(0, 0x01); // Clear Display
LCDout(0, 0xC0); // Move Cursor to position for this charger
// LCDouts("X A:CtE B:CtE ");
LCDouts("XS A23E23 B23E23");
// wait 1-2 sec to be sure button is fully back up
// sec2 = seconds;
// while (sec2 == seconds);
LCDout(0, 0xC0); // position cursor at 2nd line
LCDout(0, 0x0F); // On, Cursor, Blink
dialpos = 16;
lastdial = 1060;
selecting = 1;
while (selecting < 2) { // button not pressed (again)
pos = newDial(); // readchannel(INCH_2); // read position
LCDout(0, 0x80); // position cursor at 1st line, 3rd char "Px "
switch (pos) {
case 0:
{
LCDouts("Exit Menu ");
break;
}
case 1:
case 2:
{
LCDouts("System Menu");
break;
}
case 3:
case 4:
case 5:
{
LCDouts("A: Charge ");
LCDdigit(pos-2);
break;
}
case 6:
case 7:
case 8:
{
LCDouts("A: Edit ");
LCDdigit(pos-5);
break;
}
case 9:
{
LCDouts("System Menu");
break;
}
case 10:
case 11:
case 12:
{
LCDouts("B: Charge ");
LCDdigit(pos-9);
break;
}
case 13:
case 14:
case 15:
{
LCDouts("B: Edit ");
LCDdigit(pos-12);
break;
}
}
LCDouts(" ");
LCDout(0, 0xC0+pos); // position cursor at 2nd line pos
}
//
// Execute Selected Action, and Return.
// For Editing, because of stack overflow problems,
// we return back to main to execute them.
//
switch (pos) {
case 0: return 0;
case 1:
case 2: return 1;
case 3:
case 4:
case 5:
{
return 0;
}
case 6:
case 7:
case 8:
{
return pos-4; // 2,3,4
}
case 9: return 1;
case 10:
case 11:
case 12:
{
return 0;
}
case 13:
case 14:
case 15:
{
return pos-8; // 5,6,7
}
}
// return if not editing settings
return 0; // done
}
}
//
// Input Button interrupt occurs when button pressed
// Button is in two states, up or down.
// if it's up, we interrupt when it goes down,
// but don't change state unless the button stays down
// for a sufficient time, 30-50ms.
// If it was down, and goes back up,
// we acknowledge the button by incrementing the selecting variable.
//
interrupt (PORT1_VECTOR) p1int(void)
{
int in = P1IFG;
if (in & 0x1) { // P1.0 changed hardware state
//
// If button state was up, wait for a while to confirm it is now down
//
if ((P1IES & 0x01) == 0x01) { // int on high-low transition, so button is up and went down
//
// Aclock is running at 2-Mhz. 60,000 ticks would be 30msec.
// we wait until it rolls through zero twice, that would be anywhere from 60k-120k
// while (TAR > 4) {
// in = P1IN;
// }
in = P1IN;
while (TAR > 4) {
in = P1IN;
}
// now check if button is still down, if so, change interrupt to fire
// when it goes up, and return from interrupt
if ((in & 0x01)==0x00) { // button is still down
P1IES = 0x00; // now interrupt on button-up low-high transition
}
}
// we have low-high interrupt, so button was down, now it is up.
else {
// while (TAR > 4) { // wait until TAR rolls to zero
// in = P1IN;
// }
in = P1IN;
while (TAR > 4) {
in = P1IN;
}
if ((in & 0x01)==0x01) { // button is still up
P1IES = 0x01; // now interrupt on button-down high-low transition
// button is now considered pressed
if (selecting==0) {
selecting=1;
_BIC_SR_IRQ(CPUOFF); // Clear CPUOFF bits to wake up main loop
}
else {
selecting++; // increment to indicate button press
}
}
}
}
P1IFG = 0; // clear handled interrupt
}
unsigned short tar;
//
// Timer_A Interrupt Vector (TAIV) handler for CCR0 and CCR0 capture/compare,
// and overflow interrupts
interrupt (TIMERA0_VECTOR) a0timer(void)
{
unsigned short v;
tar = TAR;
if (TAIV == 10) {
}
}
//
// TACH signal went low
// We get the rpm by measuring how long it was from
// the last event to this one for the last 4 events (2 revolutions)
// and generate a running average from them.
// If we are in startup with less than 4 events, just use the last one.
//
// To get rpm, we get the rev per second. we have a 32k clock,
// so 32768 ticks per second, and thus 32768 / ticks = rev per second,
// so if we get 16384 ticks, it would be 2 revolutions in a second.
// We divide that in half since we have 2 events per revolution,
// so 16384 / ticks = rev per second, if we multiply by 60 seconds per minute,
// we get revolutions per minute (rpm).
// We should not see less than 100rpm during cranking, which is 10,000 ticks
// and thus times 4 = 40,000 which still fits into a 16bit unsigned short.
unsigned short tachTimes[4];
interrupt (PORT2_VECTOR) p2int(void)
{
unsigned short in = P2IFG;
unsigned short tar = TAR;
unsigned short tach0;
// if (in & 0x20) { } // P2.5 changed hardware state
if (tar < lastTach) { // timer rolled over
tach0 = tar + (0xFFFF - lastTach); // get up to here, plus the roll
}
else {
tach0 = tar - lastTach;
}
lastTach = tar;
tach++;
if (tach > 3) {
tachTimes[ (tach & 0x3) ] = tach0;
// now compute average of last four values
tach0 = tachTimes[0] + tachTimes[1] + tachTimes[2] + tachTimes[3];
tach0 = tach0 >> 2;
// do injector spray after each ignition event once we've made at least one revolution
rpm = (32768 / tach0) * 3; // this gives us a better number
startInjector();
// wake up main loop to update injectorPulseWidth after we have started
// nope, we must do it here, because if main is running the display (which takes 100ms)
// wake up will do nothing, so we must force it now from interrupt.
// we re-enable interrupts for pwm timers to execute.
// if we get another tach interrupt before we're done here, we are toast!
// _EINT(); // Now Enable interrupts
// updateInjectorWidth();
// updateThrottle ();
}
// rpm = (16384 / tach0) * 60;
rpm = (32768 / tach0) * 3; // this gives us a better number
P2IFG = 0; // clear handled interrupt
_BIC_SR_IRQ(CPUOFF); // Clear CPUOFF bits to wake up main loop
return;
}
//
// spray the fuel injector.
// do a peak of full-on for 1.2 ms, then 25% pwm duty for remaining on-time
//
void startInjector() {
tar = TAR;
injectorStartTime = tar; // get timer value of when to leave it off
CCR2 = tar + 39; // 39 is 1.2ms for 32k timer
CCTL2 = OUT; // turn ON injector
CCTL2 = CCIE | OUTMOD_5; // turn off at CCR2 after 1.2ms, and interrupt
injectorIsOn = 1; // peak period
injectorPulseWidth = injectorBaseWidth;
}
void updateInjectorWidth() {
//
// When we get tach input we wake up here
// to read the sensors and initiate the injector spray
//
// For our 4cyl 159ci tractor engine, and a 45lb/hr injector, we have a base injector time of 9ms for
// each ignition event. It is adjusted by the o2sensor, airTemp, waterTemp, and MAP readings
//
// At our max of 2500 rpm = 41.666 RpS = 1/x = 24ms per revolution.
// With two events per rev, that leaves us 12ms, so we are almost 100% at 2500
// At 2200 rpm = 36.6 RpS = 1/x = 27ms per revolution, so we have 13.6ms
// We use the previous sensor values to initiate the injector pulse width,
// and then while that is going, we can do the 2-3ms needed to update the sensor readings
//
// read all the sensors, this takes
//
// read the MAP using 3v reference, 16x sample
ADC10CTL0 = 0;
ADC10CTL0 = ADC10ON | ADC10SHT_2 | SREF_0;
mapSensor = adcRead(6);
// read dial to get base pulse width adjustment
// baseWidth is 9ms, which is 32*9 = 288 clock ticks
//
dial = adcRead(2);
injectorBaseWidth = dial >> 1; // divide in half, 0 to 512 range 0 to 16ms
//
// read the O2 sensor using 1.5v reference
ADC10CTL0 = 0;
ADC10CTL0 = ADC10ON | REFON | ADC10SHT_2 | SREF_1; // use 1.5v ref
o2Sensor = adcRead(1);
//
// read the chip temperature as proxy for outside air temp using 1.5v ref
airSensor = adcRead(0xA);
//
// read water temp,
waterTemp = readWaterTemp(3);
//
// adjust pulse width for MAP and temps until water temp is at operating temp
// and then use o2 sensor for closed loop feedback
//
injectorPulseWidth = injectorBaseWidth;
if (waterTemp < 15) { // doing open loop
//
}
else if (o2Sensor < 500) { // o2sensor is lean, slightly richen until o2 goes rich
// injectorPulseWidth = injectorPulseWidth + 1;
}
else if (o2Sensor > 520) {
// injectorPulseWidth = injectorPulseWidth - 1;
}
}
unsigned short diff;
unsigned short throttleHold = 40;
unsigned short throttleLowHold = 64;
unsigned short throttleOpen = 10;
unsigned short throttleClose = 900;
unsigned short throttleLowClose = 1600;
// adjust the throttle PWM
tps = adcRead(7);
if (tps < targetTPS) { // need to close it
diff = targetTPS - tps;
if (diff > 12) {
// set pw to slow close
// if tp is almost closed, we must use less force
if (diff > 200) {
throttleTimeOff = throttleLowClose;
}
else {
throttleTimeOff = throttleClose;
}
}
else {
throttleTimeOff = throttleHold;
if (tps > 800) {
throttleTimeOff = throttleLowHold;
}
}
}
else {
diff = tps - targetTPS;
if (diff > 12) { // need to open it
if (diff > 200) {
throttleTimeOff = throttleOpen; // fast open
}
else if (tps > 800) {
throttleTimeOff = throttleOpen;
}
else {
throttleTimeOff = throttleOpen;
}
}
else {
throttleTimeOff = throttleHold;
}
}
}
//
// Timer_A Interrupt Vector (TAIV) handler for CCR1 and CCR2 capture/compare,
// and overflow interrupts
interrupt (TIMERA1_VECTOR) axtimer(void)
{
unsigned short v;
tar = TAR;
v = TAIV;
if (v == 2) { // CCR1 compare
// if throttle was ON,
// then the output was turned off at compare,
// and we must setup to turn it back on at next compare
if (throttleIsOn) {
CCR1 = tar + throttleTimeOff;
// CCTL1 = 0; // turn OFF throttle
CCTL1 = CCIE | OUTMOD_1; // turn on at CCR1
throttleIsOn = 0;
}
else {
CCR1 = tar + throttleTimeOn;
// CCTL1 = OUT; // turn ON throttle
CCTL1 = CCIE | OUTMOD_5; // turn off at CCR1
throttleIsOn = 1;
}
}
else if (v == 4) { // CCR2 compare
// we initiate the full-on peak period,
// we in the interrupt we are doing the pwm hold
// use a on time of .125ms, offtime of .250ms
// until the injectorOffTime is reached, then we leave it off
if (injectorIsOn) {
// if the on period has been reached, the spray is complete and we leave it off
injectorIsOn = 0;
unsigned short onTime = (tar - injectorStartTime);
// turn back on if still not done with hold time.
if (onTime < injectorPulseWidth) {
CCR2 = tar + injectorTimeOff;
CCTL2 = CCIE | OUTMOD_1; // turn on at CCR2
}
else {
// keep it off, done
CCR2 = 0;
CCTL2 = 0;
}
}
else {
CCR2 = tar + injectorTimeOn;
CCTL2 = OUT; // turn ON injector
CCTL2 = CCIE | OUTMOD_5; // turn off at CCR2
injectorIsOn = 1;
}
}
else { // overflow, timer hit 0xffff
}
}
unsigned short tdel = 1;
unsigned short t1 = 0;
unsigned short t2 = 0;
//
// MAIN
//
int main(void)
{
int x;
unsigned short s;
// check if we have a working 32k ACLK
TACTL = TASSEL_ACLK + TACLR + MC_CONT;
delay(1200); // wait
if (TAR == 0) {
Reboot(); // can't run without working 32k clock, wait for it to warm up
}
P1OUT = 0x00; // Clear
P2OUT = 0x00; // Clear
//
// copy from flash into memory sys config settings
// check for bootstrap uninitialized values
FConfig = (struct ConfigEFI *)0x1000;
Config = *FConfig;
if (Config.valid == 0xFF) {
Config.valid = 0;
SaveConfig();
Reboot(); // can't run without working 32k clock, wait for it to warm up
}
P1DIR = 0x0E; // output for P1.1,2,3, input for P1.0
P1OUT = 0x00;
P2DIR = 0x00;
P1IES = 0x00; // init with high-low transition, so button must 1st go down
P1IE = 0x01; // enable interrupts on P1.0
P2IES = 0x00; // init with high-low transition for TACH signal
P2IE = 0x20; // enable interrupts on P2.5
// PWM output for charge transistor control
TACTL = TASSEL_ACLK + TACLR + MC_STOP;
P1SEL = 0x0C; // P1.3 is TimerA2 output for Throttle, P1.2 is TimerA1 for Throttle
// P1.0 is Button input, P2.3 is select dial
initLCD();
// Turn on LCD Display
LCDout(0, 0x0C); // Display On, no Cursor, no Blink
delay(750*20);
LCDout(0, 0x06); // Move cursor to right
LCDout(0, 0x01); // Clear Display
delay(750*20);
//
// initialize variables used by watchdog now before enable interrupts
//
tach = 0;
selecting = 0;
selectime = 0;
seconds = 0;
//
// see if button is down, if so we go into edit mode,
// otherwise, get ready to run the engine
//
if (1) { // ((P1IN & 0x1)==0) { // p1.0 is down
LCDouts(" Nimble");
LCDout(0, 0xC0); // position cursor at char 8
LCDouts("Motorsports, LLC");
for(x=0; x<20; x++) {
delay(25000);
}
LCDout(0, 0x01); // Clear Display
delay(750*20);
LCDouts(" Electronic ");
LCDout(0, 0xC0); // position cursor at char 8
LCDouts(" Fuel Injection 1.0 ");
for(x=0; x<20; x++) {
delay(25000);
}
}
P1IFG = 0; // clear button interrupt
//
// we are ready to run the engine, turn on the fuel pump
//
P1OUT = 0x02;
WDTCTL = WDT_ADLY_1000; // Set Watchdog Timer interval back to 1000 (1-sec)
// WDTCTL = WDT_ADLY_250;
IE1 |= WDTIE; // Enable WDT interrupt
// _EINT(); // Now Enable interrupts
Hello, I've got a project that uses the msp430 that I thought you folks would enjoy.
I have an old 1960 Tractor that was running like crap, and turns out the distributor was junk.
New parts have long since become obsolete, but who wants the old technology anyway (.025 gap plugs)
So I have converted the ignition to electronic coil-on-plug,
which eliminates the distributor and the plug wires, and uses 4 ignition coils, one on each spark plug,
and now have vastly increase spark with 0.060 gap plugs.
Here is a youtube video of it running in the final setup:
OK, so they won't let me include URLs as a newbie, you can search youtube for my username and find them.
You can view the older video that shows a little different setup, but it shows the msp430 processor board I used,
which was created years ago for another application, so I have dozens if not hundreds of them.
I've also used the processor board and some surplus LCDs to create digital gauges for the tractor.
The gauages use the msp430 to read the sensors (oil pressure, water temp, rpm, fuel level, voltage),
and generate a bargraph on the lcd to display the value. I embed these in a custom created faceplate using the original tractor dash bezel. You can see the end results here:
OKay, no links allowed, so maybe later you can see it.
I'm now working on a custom fuel injection system for the tractor using another 430, this one a 1232
that was originally created as a battery charger.
It is for a lab laser. My research group uses digital inline holography to study clouds and turbulence in them. The process involves shining an expanded, collimated laser beam across a test volume onto a CCD. The diffraction pattern created by the particles in the volume is a form of hologram and is recorded by the cameras. We can then take these holograms and numerically refocus it to virtually any position within the original sample volume, which reconstructs the original light field. By iterating over the entire volume, we can see where particles come into focus and backout 3D position as well as size and shape.
Since the exposure time of even high speed cameras is too slow to give blur-free results, we use a laser with a ~1ns pulse length and actively trigger it in sync with the frame rate of the camera. Since there is latency in the trigger line and in the response of the camera, we have to implement some sort of adjustable delay to the trigger line to keep everything in sync. The laser itself is a stand alone actively q-switched DPSS laser, so outside of just monitoring diode current and temperature to make sure the laser's own internal regulation is doing its job, there isn't much control other than on/off and fire. The main control is adjusting the triggering rate/dealy, (attempting to) keep it synced to an external reference, and allowing an external interlock system to kill the laser if needed.
Anyway, my department recently won a grant to build a pi m^3 cloud simulation chamber, and one of the features is one of these holographic systems. I decided since this was going to be a 'community' instrument, it needed a better control system than the 'mess of wires in a box with a 30 year old signal generator' system we have driving the smaller chamber we have in our lab.
The msp430 was my first micro. At $4.30 and free shipping any newbie is willing to "take the risk" into the world of microcontrollers. I don't think $10 is outrageous but I can guarantee I would look around on the internet more to see what else I could get for $10 first before i picked up an msp430.
I used Altium Designer. We begged for over a year to get the program installed on the computers in our lab. It's expensive, but I like the program a lot.
Hello all,
This is my project for my design class at the university I am currently attending. It is the first circuit/PCB I have made that is not just a simple circuit on a breadboard.
I'm happy to say it worked on the first try and thanks to Username and his reflow oven I was able to solder all of the surface mount components.
I'm sure I'll be saying this again in the future, but thank you RobG for the inspiration for this project! :thumbup:
Parts:
MSP430G2553
TLC5971
Piranha RGB LEDs
Hello all,
This is my project for my design class at the university I am currently attending. It is the first circuit/PCB I have made that is not just a simple circuit on a breadboard.
I'm happy to say it worked on the first try and thanks to Username and his reflow oven I was able to solder all of the surface mount components.
I'm sure I'll be saying this again in the future, but thank you RobG for the inspiration for this project! :thumbup:
Parts:
MSP430G2553
TLC5971
Piranha RGB LEDs
Hello all,
This is my project for my design class at the university I am currently attending. It is the first circuit/PCB I have made that is not just a simple circuit on a breadboard.
I'm happy to say it worked on the first try and thanks to Username and his reflow oven I was able to solder all of the surface mount components.
I'm sure I'll be saying this again in the future, but thank you RobG for the inspiration for this project! :thumbup:
Parts:
MSP430G2553
TLC5971
Piranha RGB LEDs