Jump to content

OhmMegaman

Members
  • Content Count

    9
  • Joined

  • Last visited


Reputation Activity

  1. Like
    OhmMegaman reacted to Lgbeno in Sensorless BLDC motor control   
    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.
  2. Like
    OhmMegaman reacted to Tribes in Sensorless BLDC motor control   
    Hi there,
     
    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




  3. Like
    OhmMegaman reacted to nimblemotors in Tractor Engine Control   
    //******************************************************************************
    //
    //  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;
     
     
    void startInjector();
    void updateInjectorWidth();
    void updateThrottle();
     
     
     
     
    #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;
     
     
      Aptr = (struct ConfigEFI *)0x1080;
      Bptr = (struct ConfigEFI *)0x1000;  
     
     
      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);
     
     
    }
     
     
    void
    LCDdigit(unsigned char d) {
      LCDout(1, '0'+ d);
    }
     
     
    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);
     
     
      LCDout(1, ':');
      val = (ticks)%60;
      BCDecode(val, &d1, &d2, 0);
      LCDdigit(d2);
      LCDdigit(d1);
    }
     
     
     
     
    LCDout_temp (unsigned short temp)
    {
      unsigned short val = temp;
      char d1,d2,d3,d4;
     
     
      BCDecode(val, &d1, &d2, &d3);
      // d1 = val % 10;
      // val = val / 10;
      // d2 = val % 10;
      // val = val / 10;
      // d3 = val % 10;
      if (d3 > 0) {
        LCDdigit(d3);
      }
      else {
        LCDout(1, ' ');
      }
      LCDdigit(d2);
      LCDdigit(d1);
      LCDout(1, 'F');
    }
     
     
    LCDout_volts (unsigned short volts)
    {
      unsigned short val = volts;
      char d1,d2,d3,d4;
     
     
      val = BCDecode(val, &d1, &d2, &d3);  
      // d1 = val % 10;
      // val = val / 10;
      // d2 = val % 10;
      // val = val / 10;
      // d3 = val % 10;
      // val = val / 10;
      d4 = val % 10;
      if (d4 > 0) {
        LCDdigit(d4);
      }
      else {
        LCDout(1, ' ');
      }
      LCDdigit(d3);
      LCDout(1, '.');  
      LCDdigit(d2);
      LCDdigit(d1);
      LCDout(1, 'v');
    }
     
     
    LCDout_adc (unsigned short adc)
    {
      unsigned short val = adc;
      char d1,d2,d3,d4,d5;
     
     
      val = BCDecode(val, &d1, &d2, &d3);  
      d4 = val % 10;
      val = val / 10;
      if (d4 > 0) {
        d5 = val % 10;
        LCDdigit(d5);
      }
      else {
        LCDout(1, ' ');
      }
      LCDdigit(d4);
      LCDdigit(d3);
      LCDdigit(d2);
      LCDdigit(d1);
    }
     
     
     
     
    LCDout_Mamps (unsigned short ma)
    {
      unsigned short val = ma/8;
      char d1,d2,d3,d4;
     
     
      val = BCDecode(val, &d1, &d2, &d3);
      LCDdigit(val);  
      LCDdigit(d3);
      LCDdigit(d2);
      LCDdigit(d1);
      LCDout(1, 'm');
      LCDout(1, 'a');
      LCDout(1, ' ');
    }
     
     
     
     
    LCDout_amps (unsigned short amps)
    {
      unsigned short val = amps;
      char d1,d2,d3,d4;
     
     
      val = BCDecode(val, &d1, &d2, &d3);
      LCDdigit(d3);
      LCDout(1, '.');  
      LCDdigit(d2);
      LCDdigit(d1);
      LCDout(1, 'a');
      LCDout(1, ' ');
    }
     
     
     
     
    // 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;
     
     
    void updateThrottle ()
    {
      //
      // adjust throttle position to maintain rpm setting
      //
      ADC10CTL0 =  0;
      ADC10CTL0 =  ADC10ON |  REFON | REF2_5V | ADC10SHT_2 | SREF_1;   // use 2.5v ref
      targetTPS = adcRead(4);
     
     
      throttleTimeOn = 8;  // constant
       
      // 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;
      
      WDTCTL = WDTPW + WDTHOLD;             // Stop WDT
      DCOCTL = 0xE0;   // DCO=7, MOD=0
      BCSCTL1 = 0x07;  // RSEL=7    == 5Mhz for F1232
      // BCSCTL1 = (DIVA1 | DIVA0) | 0x07;  // Aclk/8 32k now 4k, RSEL=7,  4Mhz at 3v
     
     
      // 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
      }
      
      // ADC10ON and 64x sample and hold time.
      // 2.5V reference
      // ADC10CTL0 =  REF2_5V | REFON | ADC10ON | ADC10SHT_3 | SREF_1;
      // ADC10ON and 16x sample and hold time.
      ADC10CTL0 =  REF2_5V | REFON | ADC10ON | ADC10SHT_2 | SREF_1;
      //
      // ADC option select
      // P2.0=A0 
      // P2.1=A1 O2
      // P2.2=A2 Dial
      // P2.3=A3 WaterTemp
      // P2.4=A4 RPM Dial
      // P3.6=A6 MAP
      // P3.7=A7 TPS Throttle Position
      //
      ADC10AE = 0xDF;
     
     
      // P2.5  tach input
      // P1.0  board button  input
      // P1.1  fuel pump relay output
      // P1.2  throttle 
      // P1.3  fuel injector  
     
     
      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;
     
     
      CCR1 = 0;
      CCR2 = 0;
      CCTL2 = 0x00; //init off
      CCTL1 = 0x00; //init off
      TACTL = TASSEL_ACLK + TACLR + TAIE + MC_CONT; // start timer
     
     
      //
      // 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  
     
     
      // Init throttle PWM
      // CCR0 = 0xFFFF;
      // TACTL = TASSEL_SMCLK + TACLR + TAIE + MC_UPTO_CCR0; // start timer    
      TACTL = TASSEL_ACLK + TACLR + TAIE + MC_CONT; // start timer
      //
      // crack open the throttle for starting
      //
      throttleIsOn = 1;
      throttleTimeOn = 640;
      throttleTimeOff = 3200;
      CCR1 = throttleTimeOn;
      CCTL1 = OUT;  // turn ON throttle
      CCTL1 = CCIE | OUTMOD_5;  // turn off at CCR1 and interrupt
     
     
     
     
      injectorBaseWidth = 9*32;   // 9ms
      injectorPulseWidth = injectorBaseWidth;
      injectorTimeOff = 32;
      injectorTimeOn = 32;
      dstate = 0;
      
      _EINT();  // Now Enable interrupts
     
     
      while (1) {
        selecting = 0;      
        //  Sleep until next interval status update
        //_BIS_SR(CPUOFF);                 // Enter LPM3
        //_NOP();
     
     
        // we wake up when ignition event occurs,
        // recalculate the injector on time
        
        updateInjectorWidth();
     
     
        t1 = TAR;
     
     
        updateThrottle ();     
     
     
        t2 = TAR;
     
     
        dstate++;
        // do display in tiny steps
        if (dstate == 1) {    
          LCDout(0, 0x80); // A position
        }
        else if (dstate == 2) {
          //LCDouts("F:");
          //val = adcRead(0x0A);
          LCDouts("R:");
        }
        else if (dstate == 3) {
          LCDout_adc(rpm);
        }
        else if (dstate == 4) {
          LCDouts(" I:");
        }
        else if (dstate == 5) {
          LCDout_adc(injectorPulseWidth);
          // LCDout_adc(throttleTimeOff);      
        }
        else if (dstate == 6) {
          LCDout(0, 0xC0);  // Move Cursor to position for this charger
        }
        else if (dstate == 7) {
          LCDouts("T:");
        }
        else if (dstate == 8) {
          // LCDout_adc(waterTemp);
          LCDout_adc(tps);
        }
        else if (dstate == 9) {
          LCDouts(" D:");
        }
        else if (dstate == 10) {
          LCDout_adc(targetTPS);
        }
        else {
          dstate = 0;  // restart
        }
        t2 = TAR;
     
     
        // see if we woke up from button press interrupt
        if (selecting > 0) {
          selectime = 0;
          // because of stack overflow problem, we return from buttonPress
          // and execute any editing choice here.
          x = buttonPress();
          if (x == 0) {
    LCDout(0, 0x0C);  // On, no Cursor, no Blink
    selecting = 0;
          }
          else if (x == 1) {
    // sysSettings();
    // SaveConfig(0);
          }
          else if (x < 5 ) { // edit settings, 2,3,4
    // editSettings("A", x-2, &cset);
    // SaveConfig(); 
          }
          else if (x < 8) { // edit settings   5,6,7
    // editSettings("B", x-5, &cset);
    // SaveConfig(); 
          }
        }
      }
     
     
    }
  4. Like
    OhmMegaman reacted to nimblemotors in Tractor Engine Control   
    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.
     
    Hope you like it.
    Jack
  5. Like
    OhmMegaman reacted to mbeals in Laser Controller   
    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.
  6. Like
    OhmMegaman got a reaction from EngIP in Is it just me or tonight TI doubled LaunchPad price?   
    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.
  7. Like
    OhmMegaman got a reaction from Rickta59 in Another RGB LED Board   
    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.
  8. Like
    OhmMegaman reacted to TheDirty in Another RGB LED Board   
    You should merge the copper plane with your ground net.
  9. Like
    OhmMegaman got a reaction from cubeberg in Another RGB LED Board   
    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
     


  10. Like
    OhmMegaman got a reaction from bluehash in Another RGB LED Board   
    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
     


  11. Like
    OhmMegaman got a reaction from RobG in Another RGB LED Board   
    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
     


×
×
  • Create New...