Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Battery Charger / Inverter
#1
I've been working on a cheap battery charger.  
What makes it cheap is mostly because it uses a transformer from a microwave oven, or what people call a MOT for short.   One can find free or almost free used microwaves very easily, as the keypads or other things go bad in a microwave, the transformers never go bad.  Then it is just a matter of removing them from the oven.  Be careful as the MOT has a 2000 volt secondary winding, and a 2000 volt capacitor that could still have some charge, so make sure it has been left off a long time and use something to short the cap before getting into one.  Be safe.
Lots of videos on youtube showing how to teardown a microwave and rewind a MOT.

The 2000 volt secondary winding must be removed and replaced with a winding that is appropriate for whatever voltage battery pack you want to charge.  I'm using a LifePo4 pack with 14 cells in series,
which is about 43-47 volts dc, and have wound the secondary for 40 volts AC, which when rectified into DC, it yields a peak of about 60 volts.   The rectified voltage is fed into a DC-DC buck converter that adjusts the voltage down to a constant current and/or voltage for charging the battery.

What is also quite interesting is that instead of using a normal diode bridge rectifier to convert the AC into DC, I'm using four mosfets instead, using their intrinsic diodes to do the rectifying to DC.  The result of this is the mosfets can then be used to reverse this process and take the battery voltage and invert it back out into 120 volts AC.  So the battery charger is also an inverter.  Smile

This is essentially the schematic for this design:
The first four mosfets are the bridge/inverter, and the second two form the buck/boost converter.


Attached Files Thumbnail(s)
   
Reply
#2
Here is picture of the MOT with the 2000 volt secondary already removed.  To remove it I used my trusty 4.5in angle grinder with a metal cutoff disk, and cut off the winding coming out of one side flush to the transformer, and then carefully pounded it out with a hammer and piece of aluminum.  It comes out pretty easily once the winding is cut flush.   One must be careful not to damage the 120vac primary winding when messing with it.

The second pic shows the new secondary winding.  It is a bit challenging to do this winding because the transformer is already welding up.  Normally the windings are done on a bobbin and just slide onto an open two piece transformer where the E and I part have not been attached yet.  We don't get it that easy here,
and must thread the windings through the holes.


Attached Files Thumbnail(s)
       
Reply
#3
Here we have the basic charger prototype setup for testing.
We have the transformer on the left that takes the 120v AC input, and outputs
a 40v AC.  The 40v AC is input into the mosfet bridge, where four of the mosfets
make up the AC to DC bridge rectifier.  Now we have a 60v DC that is on the + and - bus,
and this is used as inputs to the last two mosfets which form a DC-DC Buck converter.
The lower (- bus connected) mosfet is used for its diode in the Buck converter.
The high (+ bus connected) mosfet is used to pulse the DC input into the Inductor,
which controls the output voltage to the battery.   In this case, we have a 12v light as the test load.
This bulb will draw 4 amps at 12v, more at higher voltage until it blows up around 20v.   Wink

You might think the mosfet setup looks just like a three-phase DC motor controller,
and you would be right.  These are the mosfets I used for the BLDC motor controller for the Mars (now Motenergy) brushless motor originally installed in the electric jetski.   These are in a 'package' called a SOT-227, which is a very convenient way to package up a mosfet, as it has four screws to attach wires,
and the bottom is an isolated heat sink mating.  However, you pay quite a premium for this convenience,
as these parts are very expensive, the particular ones used here are $25 each, so that is $150 for all six.
These are also capable of 200 amps, and our charger is going to be limited to just 50 amps max, so they are overkill as well.   What used to be the most common 'package' is the TO-220, and one can buy these for about $2 each.  I've actually got some of the larger TO-247 mosfets on hand that are rated at 100amps and cost about $7 each that were originally purchased to use in a 1000 amp motor controller, but I decided 150 volts was not enough to be safe, and used some 330 volt parts, so I'm going to use these to build the mosfet bridge for the charger.  This requires some mechanical work as you will see.

For a volume product, surface mount parts would be used and a circuit board designed and produced for them.  When you build one or two, it just isn't worth the time and effort.  You may notice the prototype control board is just a Radio Shack perf board with hand placed parts and wires.  Works great qty 1-2.


Attached Files Thumbnail(s)
       
Reply
#4
Here are the power components using TO-247 Mosfets.
As before, we have the top DC Positive bus, followed the middle three connections,
and then the DC Negative bus.  The left two middle blocks are the AC inputs, and the rightside middle connection is the DC-DC output.
We connect the three high-side mosfets by screwing them down to the rail, which are pieces of a large heatsink that have been cut into sections.
The backside of the mosfet is not just the heatsource, but is also the Drain connection,
so we don't need the Drain pin and have removed it.
The Source pins have a ring terminal soldered on, and then screwed into the lower block.
The Gate pins have a small circuit board attached.  This circuit board has a two-pin input connector that goes to the logic board.  It connects to the Gate and Source terminals of the mosfet, and has the Gate resistor on it also.

   

Next we need to discuss how a mosfet works, and how we control them.

A mosfet is basically a switch. It has two terminals that are disconnected from each other when OFF,
and connected when its ON. That is not exactly true, because a Mosfet has a Diode so that there is a connection between the two terminals that allows electricity to flow in one direction. The switch controls the flow in the other direction.

The power terminals of a mosfet are called the Drain and the Source. Why they use these names has some technical reason, but they are essentially backward from a normal vocabulary. The Drain is where the positive voltage goes and is prevented from flowing to the Source side when the switch is OFF.
If you look at the schematic of the charger, you see the symbol of the mosfet showing the diode and the Switch that controls the Drain to Source connection.

The switching ON/OFF of the mosfet is controlled by the Gate pin. When the gate pin is connected to Ground, the switch is OFF. When a voltage is applied, the switch turns ON. The amount of voltage needed to turn it on depends on the specific mosfet. There are some that turn on with just 1v at the gate.
A standard mosfet needs about 4v to turn on, and in fact they have what is called the 'linear' region where the mosfet is not fully turned on and allows current to flow, but with higher resistance. This can be useful in some cases, but generally it is very bad, and you want the mosfet to be fully on or off. At 10v the mosfet will be fully turned on. We use a microprocessor to control the mosfets, which turns them on/off with just a 3v output, so additional parts are needed to take the logical on/off signal from the microprocessor and do the hard work of actually turning on/off the mosfet. While one can use a bunch of parts (termed 'discrete logic'), the relatively simple thing is to use a 'driver' chip, of which there are countless varieties with various features. In this first example, we are using the International Rectifier (IR) driver chip, the IR2110.
You can view the IR2110 datasheet here.
Reply
#5
The IR2110 mosfet driver will drive 2 mosfets, one 'high-side' and one 'low-side'.
For our charger, we need 3 of them, two are used for the inverter, the third is used for the dc-dc.
The basic connection layout is shown on the datasheet copied below.

   

To fully understand what this driver does, we must go back and review how mosfets work,
or more specifically how the kind of mosfets we are using called N-channel mosfets.
As described earlier, the mosfet turns on with a 10v input on the Gate.
But now we need to be more specific, what is needed is 10v relative to the Source pin.
When the mosfet is used as a 'low-side' switch, the Source pin is connected to Ground,
and therefore 10v is relative to Ground.  All voltage is normally referenced to Ground.
However when the mosfet is placed on the 'high-side', the Source pin is no longer connected to Ground,
when it is turned off, it isn't connected to anything Smile or what is called 'floats', and when it is turned on, it is connected to the positive side of the 'load'.  This is a complicated issue which will be discussed again,
but for now the solution to this issue is what is called a 'charge pump'.  

The IR2110 uses a charge pump to charge up a capacitor to 10v that has its Ground connected to the Source pin of the high-side mosfet.  To turn on the high-side mosfet, this 10v is sent to the Gate.
While it is turned on, the load provides a path to Ground, and the capacitor Ground is temporarily connected to Ground is charged backup ready for the next turn-on request after the switch is turned off.

The low-side switch does not need this complexity, as it can just use the 10v input to the driver as the voltage sent to the low-side gate.
So if we look at the connections on the IR2110, we can see the low-side driver, which consists of the pins labeled Vcc, COM, and LO.  The Vcc is a 10v-20v that is sent to the LO pin to turn on the low-side Gate,
and COM is the low-side Ground.    For the high-side, we have pins labeled Vb, Vs, and HO, which corresponds to the Vcc, COM, and LO on the low-side.  Where they come up with these labels, only an EE knows. Wink  As discussed previously, the high-side uses a charge-pump, so the Vb or high-side voltage comes from the low-side Vcc through a diode (so the capacitor does not discharge), and a capacitor is connected from Vb to Vs, where Vs is connected to the Source pin of the mosfet.  
Here is a picture of one of the IR2110 chips on the logic board showing its output connections,
with labels that make sense to a human.

   

The Input to the IR2110 comes from the microprocessor.   The input side has its own separate power supply (Vdd) and Ground (Vss), and the two inputs to turn on the high-side (HIN) and the low-side (LIN),
as well as a third input that disables the chip, which we do not use.   The IR2110 has a few pins that do nothing as well.  Later we will use another driver chip that is basically the same, but is simpler and more compact, the IR2011.   Yes, make a minor type-o and you can get a whole different chip.  Smile

   
Reply
#6
Thumbs Up 
Let's look at the microprocessor that provides the control inputs to the driver chips.

I happen to have quite a few of them, by the dozens.  

I created a radio control system for R/C racing using digital 2.4Ghz instead of the old fashioned sword-fight 27/49Mhz analog systems.   While there are many reasons, one thing was the battery consumption of the handheld transmitters.  They used 8 AA batteries, and pretty much didn't last very long, and as they faded their voltage the xmit power went down.   I also wanted to make a receiver that was very small.

So I choose to use a MSP430 'ultra low power' microprocessor.  Most everyone else at the time was into 'PIC' micros, which had a nice feature of having through-hole packages.  The MSP430 only came in small surface-mount packages, but this is OK when you want to go small for production.  They were also pretty cheap for the minimal memory versions, and supported by mspgcc compiler, although it is no picnic getting all the development environment setup.  The latest MSP430 chips are really very user friendly, even have through-hole versions now I think, and USB connections, and even cheaper and faster.
For more info, might check out MSP430 Overview or the user website 43oh.com.
I'll also mention the MSP430 Chronos.  Idea
[Image: ti_chronos_watch_ez4301.png]  

Below is a pic of the MSP430F1122 production boards that I used for my R/C receivers/transmitters.
The design is laid out so the radio portion can either stay put and used as-is for the transmitter,
and for the receiver it is cutoff and then 'folded back' under the microprocessor.  Then placed into a small box and filled with epoxy to water proof them.  

   

I have used these things for all kinds of projects, usually I just remove the radio portion, and this gives me a compact micro board that has all but one of the I/O pins connected to standard header pins.
Here are the details on the MSP430F1122 I/O and how they are laid out into the Nimble Motorsports board.

   

Let's look at how this micro is used as the inverter to generate 60Hz 120vac.
It is really a quite simple task in basic form.  
We have the full H-Bridge layout of the upper and lower mosfets, that are driven by the IR2110 chip.
The micro must provide the four inputs to generate the AC into the transformer, which is a simple case of alternating between turning on the positive pulse and the negative pulse.
I label the sides halves of the H-Bridge A and B.
The positive pulse is turning on the A high-side and B low-side.  The negative pulse is turning on the B high-side and A low-side.  
The only real issue is getting the timing right, and fortunately the microboard has a 32Khz clock crystal which has a 32,768 tick clock which we configured as:

Code:
// turn on timer using ACLK  32khz/8 = 4khz  or 4096 ticks per second.
// 4096/60 = 68 ticks per 60hz cycle = 34 ticks per half cycle
 TACTL = TASSEL_ACLK + TACLR + ID_DIV8 + MC_CONT;

   // positive wave
   // A+, B-  for half cycle    
   tickdelay(2);   // dead time 2 ticks

   apon();      
   bnon();
   tickdelay(30);   // Leave on for 30 ticks

   apoff();      
   bnoff();
   tickdelay(2);    // dead time 2 ticks
   
   // negative wave
   // B+, A-  for half cycle
   
   tickdelay(2);   // dead time 2 ticks

   bpon();      
   anon();
   tickdelay(30);   // Leave on for 30 ticks

   bpoff();      
   anoff();
   tickdelay(2);    // dead time 2 ticks

The complete source file is attached, which isn't much more than the above.


.txt   invcode.txt (Size: 3.45 KB / Downloads: 0)

Here is the logic board hooked up to the mosfets and the micro on the left hooked up to control the inverter.
Given how many and cheap these are sometimes it is just easier to dedicate one to a task.  The micro on the right is used for the DC-DC portion.  However, I may just use a bigger msp430, a MSP430F149 that has a lot of I/O pins and TWO timers, so it can control a LCD for user configuration and display status, as well as the inverter timer and the DC-DC timer.  While I don't have dozens of them, they are not expensive.

   

Here is the output of the transformer on my little scope.  It looks like the wave form is OK, but not that great.  It ramps up nicely, but turns off rather quickly.  We might just modify the simple code shown to tweak the output to give a better sine-wave approximation.  For now we are good.  Smile

   
Reply
#7
Here we look at the actual charger control.  While not as simple as the inverter code, the code for doing battery charging is not much more complicated.

The code is essentially nothing more than controlling the pulse width of the high-side mosfet.
The rest is mostly details that are hardware specific.  
A Lithium battery is charged using a Constant-Current-Constant-Voltage (CCCV) algorithm.
The first phase is controlling the current so it doesn't exceed the maximum.
The second phase is controlling the voltage so it doesn't exceed the maximum.
The way current is controlled is by controlling voltage, which is done by controlling the pulse width,
so these phases are not really different.  Smile

The code below is the essential operation of the battery charger:

Code:
checkCharge()
{
 if (statusStop == chargeStatus) {
   // do nothing, PWM is stopped
 }
 // reduce pwm if battery voltage is too high    
 else if (chargeVoltage > voltageConstant) {
   chargeStatus = statusCV;   // we are in constant voltage phase
   if (chargeCurrent < currentMin) {  // check if current has dropped below stop cutoff
     chargeStatus = statusStop;    
     stopPWM();
   }
   else {
     decreasePWM();  // lower voltage
   }
 }
 // reduce pwm if we are above the current limit    
 else if (chargeCurrent > currentLimit) {
   chargeStatus = statusCC;   // we are in constant voltage phase
   decreasePWM();   // lower voltage(current)
 }
 // otherwise we increase until we hit the above limits
 // can be in either CV or CC mode
 else {
   increasePWM();
 }
}  

The rest are details, which I go through for completeness.

Most all microprocessors, but particularly the MSP430, is most often designed as an interrupt-driven system.
The inverter code didn't use any interrupts, which is unusual.  Interrupts allow the processor to respond immediately to real-time events, and otherwise go to sleep and conserve power.  The MSP430 in particular has very low power sleep modes where only nano amps of power are consumed.

For our battery charger, power consumption is not an issue.  What is the main issue is the Timer.
The Timer controls the on/off switching of the mosfet, or pulse-width-modulation (PWM).
The MSP430 has a Timer that is quite powerful and a lot of functionality can be done by the hardware.
The key is to get it configured properly, which can be confusing even after my years of using them.

Let's look at the setupTimer() code that does the hardpart of configuring TimerA which is essentially just three lines:
Code:
CCRO = 256;
TACTL = TASSEL_SMCLK + TACLR + TAIE + MC_UPTO_CCR0;
CCTL1 = OUTMOD_3;

The MSP430 is configured by setting bits in control registers, which can be tedious, and we try to avoid that using some named values and do what looks like adding up a bunch of named features, but it really is constructing a bitfield value and assigning to the register.   So TACTL is the TimerA configuration,
which we set four features. TASSEL_SMCLK sets the clock for the timer, which is the 'SubMain' clock otherwise called the CPU clock which we set to 4Mhz earlier.  'TACLR' just initializes the timer to zero.
'TAIE' enables timer interrupts. The key is the 'MC_UPTO_CCRO' setting, which configures the timer to count from 0 up to the value of the CCRO register over and over.  So essentially the CCRO register defines the frequency of the PWM timer.  With a 4Mhz clock that restarts at zero every 256 clocks, we have a 16Khz frequency.  

The next line configures TimerA 'Counter Control' for counter one (there are three, we only use number one).  The cryptic OUTMOD_3, configures the counter control to generate an output that turns OFF when the timer hits CCRO, and turns ON when the timer hits CCR1.  So with this all setup, we just have to set the CCR1 register to control the PWM on-time.  

So for example, when CCR1 is 200, the output is off while it counts from 0 to 200, then output turns on at 200 until 256 when the timer turns off the output and restarts the count back to zero again.
So the on-time is 56/256 or 21% on, 79% off.

I should also mention the detail of what pin on the MSP430 chip has the outputs.  The TimerA Counter One output is on the TA1 pin.  Which is the TA1 pin?  If you look at the chip documentation (shown in the inverter post), you can see pins that have labels TA1 next to them.  If you look at the setupPorts() function,
you can see the line where we set the P1SEL register to configure two pins as TA1 pins, and not their normal I/O function.
Code:
 P1SEL = 0x44;

I hope that was not confusing, but I can understand how it might be, and all it takes is having something a 'bit off' and it can be a mystery why it isn't working the way it should.  The good news is the code shown does indeed work.  Smile

Staying on the PWM, we can look at the code which updates the PWM.

As a battery charging it only needs to update quite slowly, unlike motor control which one might want it to speed up or slow down very quickly.  
We use a very simple method here of just increasing or decreasing the pulse width, and actually do so quite slowly by using a 16 bit counter for the PWM value, but only use the top 8 bits for the actual pulse width.
This reduces the noise effects of the analog-to-digital conversions by essentially averaging them over 256 readings, since we must increase the pwm 256 times before it actually has an effect on the pulse width.
So if we increment 300 times and decrement 200 times, we have averaged an increment of 100, which still isn't an actual increment!   We can do this because the timer is running at a pretty high speed of 16Khz, or 16,000 times a second, so 1,000 increments can still be done 16 times a second.  
So here is the code that does all this magic in a few lines.  
Note again, the what the code is doing is setting CCR1 which adjust the PWM.  If the value set is not different than its previous value, it has no effect on the PWM, all we did was adjust the pwm variable.
(note that the '>>' operation is a shift-right, which shifts out the low 8 bits, and only uses the high 8 bits)

Code:
decreasePWM()
{  
 if (pwm > 1) {
   pwm = pwm - 1;
   CCR1 = 256-(pwm >> 8);  // update for next cycle.
 }
}

increasePWM()
{
 if (pwm < 0xFFFF) {
   pwm = pwm + 1;
   CCR1 = 256-(pwm >> 8);  // update for next cycle.
 }
}

Let us now turn to the Analog-To-Digital Conversion or ADC.  
What the ADC does is convert analog voltage levels on the processor pins into digital binary data.
The MSP430 ADC is once again fairly sophisticated and its advanced features are fairly confusing.
We limit it to the basic operation of doing one conversion at a time.  
We start with the configuration, which is essentially two lines.
We set the ADC10AE (ADC Enable) register to enable the ADC function for A0, A1, and A2.
Similar to the TA1 output,  looking at the chip documentation, there are pins with labels for A0-A8,
and when they are selected by this register, they become analog input pins.

Then we configure the ADC operation itself, ADC10ON turns on the ADC, REFON enables the use of a reference voltage for doing ADC conversions, REF2_5V selects the 2.5v reference, and then yet another cryptic SREF_1 setting specifies that ADC conversions are done using Ground and the Reference Voltage
as the + and - range for the conversion.  So what this means is that an input voltage of 1.25v is the midpoint between chip ground and the 2.5v reference voltage.
We have a 10 bit ADC in this chip, thus it has a digital range of 0-1024, and so a mid-point voltage would yield a value of 512.  A 2.5v input would yield 1024.  

Code:
 ADC10AE = 0x07;               // ADC enable for ADC A0,A1,A2 (ports P2.0,1,2)
 ADC10CTL0 =  ADC10ON | REFON | REF2_5V | SREF_1;

And finally we have the function readADC(channel), which takes a 'channel' and converts it to digital and returns the digital value.  This is again somewhat cryptic, but basically we disable the ADC before we change the channel (i.e. the pin), then start the conversion.  We then wait for the conversion to complete, after completed the value is in the ADC10MEM register, which we return.

Code:
readADC(channel)
{
 ADC10CTL0 &= ~ENC;           // Disable while changing input channel
 ADC10CTL1 = channel;  // Set ADC Channel to Read
 ADC10CTL0 |= ENC + ADC10SC;  // Start the Analog-to-Digital conversion
 while ((ADC10CTL1 & ADC10BUSY) == 1);   // Wait for ADC to finish the conversion
 return ADC10MEM;             // return the digital value
}  

And finally we have the code the flashes the LED, but it isn't too important or complicated,
so I will not cover it here.  The entire source code is attached for your enjoyment.

The major issue we have left is we have hardcoded into the source code the current and voltage limits to control the charging.  While that works OK for someone like me that can reprogram the chip for another situation, it isn't an acceptable method for anyone else, and not even for me either.  So we need a way to configure these variables, and would also like to have a display that shows the current and voltage, and not just a blinking led.  Smile


.txt   mot1.txt (Size: 9.17 KB / Downloads: 0)
Reply
#8
We need a user-interface to the charger.  We use the common 2-line 16-character LCD display.
These are a simple, cheap, and effective approach, and only need 6 microprocessor pins to operate, plus 5v, and a backlight voltage source when a backlight is required.  
Here we show the display when operating the charger.  
The top line shows the battery voltage and the charging current, the bottom line shows the voltage and current limits.  Then we have the first characters on the lines show the status, CC for constant current mode, and CV for constant voltage mode, or FN for finished charging.
As shown, in CC mode, the current is held to 4.9amps, and in CV mode, the voltage is held to 43.36v.

   

   

Let's look at this hardware briefly. I'm using the same hardware that I used in my R/C Charger.  Just like the receiver boards, I often repurpose these boards for other tasks.  While they are not as convenient to use because the hardware is laid out specific to the charger task, it isn't that difficult to make modifications to the board.   The key feature of course is having the LCD interface, as well as a button and dial.
The last little project using these boards with just the LCD was the battery capacity tester, where the board would discharge a Lithium cell and count up the amps and then shutoff the discharge when the low voltage limit was reached.  In fact, it did two at once.  In this case, the LCD and the current sensors were used and the temp sensor lines used to control relays.

Now we are using them once again for a battery charger.  But now most of the hardware is on the separate driver board, and we use mostly just the microprocessor and the user interface, the LCD, button and dial.
Here is the back of the board showing it is really just three voltage regulators and the processor.

   
Reply
#9
Let's look at the user interface code.   Unlike the basic control code, a user interface is significantly more complicated. A button, dial, menus, input/output, it just takes a lot more code.
And it can also be highly processor and hardware dependent, but we have attempted to reduce this as much as practical.  There is always a tradeoff between more abstraction vs direct code.

Let's start look at what is involved in just pressing a button.   We have attempted to reduce this into a simple variable buttonPress, which increments when the button is pressed.
Now our main loop is changed from waiting to wakeup and report the charge status, to also checking to see if the button was pressed, and if so, invoke the configureSettings function.

Code:
 // initialize display
 initializeStatusDisplay();
 while(1)
 {
   stopCPU();  // go to sleep, wait to wake up
   if (buttonPress > 0) {  // user hit button, let them configure settings
     configureSettings();
     initializeStatusDisplay();  // clear and go back to showing status
   }
   reportStatus();
 }

Nice and clean, but now we must dig into the ugly dirt that gives us a nice buttonPress variable.

The key is configuring a pin on the micro for input and having an interrupt occur when the pin changes state.
On the charger board, pin P1.0 is used.  The pin is setup to normally be high (1) with a 'pullup' 4k ohm resistor that connects the pin to 3v.   When the button is pressed, it connects P1.0 to ground, and thus changes the state from a '1' to a '0' or low.  When the button is released it goes back to being high.
This invokes the Port 1 Interrupt routine, which is invoked for all interrupts on all Port 1 pins configured for interrupts.  We must also select whether the interrupt occurs from a change from high-to-low, or low-to-high.   To get the normal 'button press' behavior, we must first identify a button press which is followed by a button release.  The good news is we can initiate an interrupt for the press, and then change it to interrupt for the release.  The bad news is the button action at the hardware level is a horrible mess of noise!
We have what is called 'bounce' where the button bounces all over the place while being pressed or released.  We deal with this bounce by putting in a delay that waits to see if the button remains down (or up) after a short time.  If not, we ignore the press or release.  Otherwise, we recognize the action, and if it is the button up case, we can now increment our 'buttonPress' variable and wakeup the processor (if it was sleeping) to take action on the button press.
This is the full code for the button processing.
The required delay is implemented by watching the TimerA counter and waiting until it rolls over three times.  This is a not-too-long-but-not-to-short delay.

Code:
interrupt (PORT1_VECTOR)
buttonInterrupt(void)
{
 unsigned short in = P1IFG;
 
 if (in & 0x1) { // make sure P1.0 changed
 
 // 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
   while (TAR != 0) { in = P1IN; }
   while (TAR != 0) { in = P1IN; }
   while (TAR != 0) { 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 != 0) { in = P1IN; }
     while (TAR != 0) { in = P1IN; }
     while (TAR != 0) { 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 (buttonPress==0) {
      buttonPress=1;
      _BIC_SR_IRQ(CPUOFF);             // Clear CPUOFF bits to wake up main loop
    }
    else {
      buttonPress++;  // increment to indicate button press
    }
     }
   }
 }
 P1IFG = 0;  // clear handled interrupt
}

The dial interface is nothing complicated.  It is just a potentiometer so we get its position using the Analog-to-Digital (ADC) conversion.   However there is a complication in that all the ADC conversions are done in the timer interrupt, because they can't be interrupted themselves, or the processor will get hung.  But this is ok, as just like we have 'buttonPressed', we have a variable 'dialPosition' we can simply reference.

Now we can look at the code for configureSettings().
It has a list of commands, and we divide up the dial range and assign each command to one part of the range.  So when the dial moves, it scrolls through the possible commands.  We wait for the button press to select one, and then execute the command.

Code:
void
configureSettings()
{
 enum commandKeys {
   ExitCmd,
   SaveCmd,        
   CancelCmd,
   VLimitCmd,
   CLimitCmd,
   CMinCmd };
 unsigned char *commands[] = {
   "Exit             ",
   "Save             ",    
   "Cancel           ",                    
   "Voltage Limit    ",
   "Current Limit    ",
   "Current Minumum  " };
 unsigned short nCmds = 6;
 unsigned short dialCmdRange = 1024/(nCmds-1);
 unsigned short inDecimal,val,xx,yy;
 unsigned short dial,cmdIndex;  
 unsigned char d1,d2,d3,d4;

 // STOP pwm until they Exit
 chargeStatus = statusStop;    
 stopPWM();  // stop charge output

 // get currently used variables for editing
 voltageLimitUpdate =  voltageLimit;
 currentLimitUpdate = currentLimit;  
 currentMinimumUpdate = currentMinimum;

 //
 // Turn dial to select which setting to change, hit button to select one
 //
 // loop until they select an option
 while (1) {  // Loop until exit selected

 clearLCD();
 cursorLCD(0,0);
 stringLCD("Select Option: ");
 cursorLCD(1,0);
   
 buttonPress = 1;    
 while (buttonPress < 2) {  // wait for button pressed
   dial = dialPosition; // get dial position read in atimer loop
   cmdIndex = dial / dialCmdRange;
   cursorLCD(1,0);    
   stringLCD(commands[cmdIndex]);
 }
 //
 // execute command choice
 //
 if (ExitCmd==cmdIndex) {
   // Copy Temp settings into current ones
   // update only volatile memory values
   voltageLimit =  voltageLimitUpdate;
   currentMinimum = currentMinimumUpdate;
   currentLimit = currentLimitUpdate;
   chargeStatus = 0x07; // power on status
   setupTimer();        
   buttonPress = 0;     // done
   return;
 }
 else if (CancelCmd==cmdIndex) {
   chargeStatus = 0x07; // power on status
   setupTimer();        
   buttonPress = 0;     // done
   return;
 }
 else if (SaveCmd==cmdIndex) {
   // save into flash memory
   saveSettings(1);
   // update only volatile memory values
   voltageLimit =  voltageLimitUpdate;
   currentMinimum = currentMinimumUpdate;
   currentLimit = currentLimitUpdate;
 }
 else if (VLimitCmd==cmdIndex) {
   cursorLCD(0,0); stringLCD("Voltage:        ");
   cursorLCD(0,9); voltageLCD(voltageLimitUpdate);
   cursorLCD(1,0); stringLCD("New: xx.yy       ");
   xx = digitSelect(1,5,2);
   yy = digitSelect(1,8,2);
   // convert decimal voltage xx.yy into ADC voltage value
   // 6.3 ticks per volt, so multiply xx by 6.3, 100/16
   voltageLimitUpdate = ((xx * 163)/10) + ((yy * 16) / 100);
 }
 else if (CLimitCmd==cmdIndex) {
   cursorLCD(0,0); stringLCD("Current:         ");
   cursorLCD(0,9); currentLCD(currentLimitUpdate);
   cursorLCD(1,0); stringLCD("New: xx.y       ");
   xx = digitSelect(1,5,2);
   yy = digitSelect(1,8,1);
   // convert decimal voltage xx.yy into ADC voltage value
   // 6.3 ticks per volt, so multiply xx by 6.3, 100/16
   currentLimitUpdate = CurrentZeroOffset + (xx * 9) + ((yy * 90) / 100);
 }
 else if (CMinCmd==cmdIndex) {
   cursorLCD(0,0); stringLCD("Current:         ");
   cursorLCD(0,9); currentLCD(currentMinimumUpdate);
   cursorLCD(1,0);  stringLCD("New: xx.y       ");
   xx = digitSelect(1,5,2);
   yy = digitSelect(1,8,1);
   // convert decimal voltage xx.yy into ADC voltage value
   // 6.3 ticks per volt, so multiply xx by 6.3, 100/16
   currentMinimumUpdate = CurrentZeroOffset + (xx * 9) + ((yy * 90) / 100);
 }
 }
}

Looking at this code, you will see the use of many xxxLCD functions for controlling the LCD.
These are all pretty straightforward functions, the tricky bit having some required delay times for some LCD operations, like clearing the display, as well as the initialization of the LCD which is a facet of the LCD hardware, which is pretty standard.

You can see how we use the buttonPress variable here just as in the main loop.
What is more interesting is the call to the function digitSelect which prompts for a decimal value from the user.  It takes three arguments, the first two the cursor positioning, and then what size (length) of digits to get.   This function uses both the buttonPress and dialPosition variables, which shows how we have a useful abstraction, and the function is mostly about converting single digits into the full decimal value to return.

Code:
unsigned short
digitSelect(char line, char position, char ndigits)
{
 multiplier = 1;
 for (n=0; n<(ndigits-1); n++) {
   multiplier = multiplier * 10; // 10^(n-1) the manual way
 }

 value = 0;
 // get the number of digits they want
 for (n=0; n<ndigits; n++) {
   lastdigit = 11;  // initialize to invalid
   // wait for button pressed
   buttonPress = 1;  // reset to wait for next button press
   while (buttonPress < 2) {  
     // get dial position read in atimer loop //readADC(dialChannel); // read position
     digit = dialPosition/100;
     if (digit > 9) digit = 9;
     if (digit != lastdigit) {
    // we have a new position, show the digit
    cursorLCD(line,position+n);
    digitLCD(digit);
    lastdigit = digit;
     }
   }
   // after button press, add this digit to number value
   value = (digit * multiplier) + value;
   multiplier = multiplier  / 10;   // move to next lower digit
 }
 return value;
}

We only get either one or two digits, as we break up the digits into the left and right side of the decimal point, and then use those two decimals values to construct the ADC value for the voltage and current limits, because the ADC value is what the micro deals with.
This leads to how we must convert 'human' decimal numbers into the appropriate ADC values.
The conversion is dependent on how the hardware is setup.  I also note that our ADC is a 10bit device,
and so it really only has about 3 decimal digits of range, so when a voltage of 45.75 is specified, this can be rounded significantly different into just three digits of precision.  While not great, it certainly is good enough for battery charging, as the different between 45.5 and 45.55 is not significant.

The final issue is we must store the setting values so they don't disappear when power is cycled.
The ability to write the flash memory of the MSP430 makes this very easy, and so we have loadSettings and saveSettings procedures.  They are pretty highly dependant on some MSP430 specific magic, the biggest issue here is we can't have any interrupts occur and the clock timings need to be just right, or failure will occur.  Sad

The entire code is attached.   The code just handles doing charging.  We will get back to doing the Inverting again.


Attached Files
.txt   motlcd.txt (Size: 25.29 KB / Downloads: 0)
Reply
#10
With the charger software complete, we return to the inverter.  

The original setup used one small board to control the inverter mosfets, but it would be nice to have it all run from the LCD board, and it turns out with a little creativity we can do it.  The first thing is to realize that controlling the four mosfets for doing the inversion from DC into AC can be simplified into just two signals.   This is possible because the upper and lower mosfets are always turned on/off together, so we can just jumper together their inputs, and provide a single output from the micro to the combined input.  So we have the positive-wave on/off output, and the negative-wave on/off output.
The real key however, is being able to have a second timer using the msp430.  The watchdog timer can also be run as an interval timer.  We already use it in this way at 16msec to wakeup the main loop and update the LCD status every second.   Now we can turn-up this timer to the maximum 1.9msec and get eight intervals to generate a 64hz modified square wave.

Let's look at the new watchdog timer interrupt, now running at 1.9msec.  First off, we now have a variable inverting that is true when running as an inverter.
We use a variable inverterCnt to track the status of the 64hz output.  The modified square wave turns on the positive-wave for 3 intervals (ApBnON the negative-wave for 3 intervals (AnBpON), with an idle interval between each wave change (ABOFF).

Code:
watchdogTimerInterrupt()
{
 msec16++;   // maintain a 16ms counter (or 1.9ms for inverter)
 if (inverting) {
   inverterCnt++;
   // A+, B-  for half cycle
   if (inverterCnt == 1) {
     ABOFF();
   }
   else if (inverterCnt == 2) {
     ApBnON();
   }
   else if (inverterCnt == 3) {
     ApBnON();
   }
   else if (inverterCnt == 4) {
     ApBnON();
   }
   else if (inverterCnt == 5) {
     ABOFF();
   }
   else if (inverterCnt == 6) {
     AnBpON();
   }
   else if (inverterCnt == 7) {
     AnBpON();
   }
   else {
     AnBpON();
     inverterCnt = 0; // restart    
   }
   if ((msec16 & 0x1ff) == 0) {  // only wake main loop every second inverter mode
     _BIC_SR_IRQ(CPUOFF);  
   }
 }
 else {
   _BIC_SR_IRQ(CPUOFF);             // Clear CPUOFF bits to wake up main loop
 }
 return;
}

While the watchdog timer change is the key update, there are other changes required.
We now have a voltage sensor for the 'line voltage', or the voltage that is the output from the DC-DC Boost converter.  And we also need to control the lower mosfet.   With the versatility of the MSP430, we use the same Timer A1 output to control the lower mosfet as we do for the upper mosfet.  Smile
This is possible because we can dynamically configure a pin of the MSP430 as the Timer A1 output,
and it has three such pins in our processor.  So now we have the function setupPorts take an argument telling it whether we are inverting or not, and have it configure either the upper (Pin 2.3) or lower (Pin 1.2) mosfet as the Timer A1 output.

Code:
volatile void
setupPorts(unsigned short inverter)
{
 //  x.3     x.2      x.1      x.0
 //   |        |        |        |
 // 8=1000, 9=1001, A=1010, B=1011, C=1100, D=1101, E=1110, F=1111
 P1OUT = 0;     // take them all low
 P1DIR = 0xFE;  // All P1 ports are outputs, except P1.0 is button input
 if (inverter) {
   P1SEL = 0x04;  // Select special function for P1.2 Timer A outputs (TA1) for inverter PWM
 }
 else {
   P1SEL = 0x00;  // turn OFF special function for P1.2 Timer A outputs (TA1) for inverter PWM
 }
 P1OUT = 0;     // take them all low again

 P1IES = 0x01;  // init with high-low transition, so button must 1st go down
 P1IE  = 0x01;  // enable interrupts on P1.0

 P2OUT = 0;     // init with them off
 P2DIR = 0x0B;  // p2.0,p2.1 inverter output, p2.2 adc, p2.3 output, p2.4 adc  // 0b00001011
 if (inverter) {
   P2SEL = 0x00;  // turn OFF P2.3 as TA1 output
 }
 else {
   P2SEL = 0x08;  // turn ON P2.3 as TA1 output
 }
 P2OUT = 0;     // off again

 P3OUT = 0;  // init with them off
 P3DIR = 0x3F; // 0b111111 Set P3.0-5 to output direction for LCD
 P3OUT = 0;  // init with them off
 
}

And now in the function checkCharge, we add a test to see if we are inverting,
and then adjust the PWM rate to keep the line voltage and current under the configured limits.

Code:
...  else if (statusInverting == chargeStatus) {
   // if inverting, we adjust the line voltage
   if (lineVoltage > lineVoltageLimit) {
     decreasePWM();  // lower voltage/current
   }
   else if (chargeCurrent < lineCurrentLimit) {
      decreasePWM();  // lower current/voltage
   }
   else {
     increasePWM();  // raise voltage/current
   }
 }
...

We also add the two configuration settings for lineVotageLimit, and lineCurrentLimit,
and also have a menu option toggle whether we run as inverter or charger.

And that pretty much sums it up.  
We have a relatively simple hardware and software design for doing both charging and inverter output.
The entire code is attached.


Attached Files
.txt   motlcdinv.txt (Size: 31.95 KB / Downloads: 1)
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)