Introduction

The world of digitally controlled switchmode power supplies has always held a certain allure for me: the idea that you could sample things like output voltage and current, apply some DSP techniques, and then based on that output a gate drive signal in real time, giving you whatever response you want out of your buck/boost converter just seems so… insanely cool.

That being said, a lot of times these require a good understanding of the control loop architecture and parameters of switchmode power supplies to be able to properly stabilize, which has proven to be quite the barrier to entry. See the Opamp Based Boost Converter project for a bit more on what could go wrong.

Well not this time.

The constant on time control loop literally requires no loop compensation at all, it really is just sampling the output voltage and firing off a pulse of fixed width (hence the constant on time) if the instantaneous voltage is below the setpoint.

Even the old and slow MSP430 has no problem handling it.

Schematic

SVG Image created as LED Dimmer.svg date 2024/01/25 10:41:20Image generated by Eeschema-SVG+3.3V+3.3VD104D104MEDMEDD105D105HIGHHIGHD102D102OFFOFFGNDGNDR104R1043k93k91122J102J102OUTOUTSW104SW104HIGHHIGHGNDGNDGNDGNDGNDGNDC108C1081u1uGNDGNDR109R1091k1kR111R1111k1kD101D101SS35SS35+3.3V+3.3VC101C1011n1nGNDGNDGNDGND+3.3V+3.3VVCCVCCR102R102470470R103R103470470GNDGNDGNDGNDVCCVCCSW102SW102LOWLOWR108R1081k1kR110R1101k1kVCCVCCR105R1051k1kC107C1071u1uGNDGND11223344J101J101SBWSBWGNDGND112233Q102Q102PXP015-30QLPXP015-30QLC102C102330u330uGNDGNDD103D103LOWLOWL101L10156u56uC103C103470u470uVOUTVOUT11GNDGND22VINVIN33U102U102AP2204RB-3.3AP2204RB-3.31122J103J103VINVINC106C1061u1uGNDGNDGNDGNDR106R1069k19k1R107R1071k1kGNDGNDVCCVCCGNDGNDC105C1051n1nVCCVCCR101R1011k1k112266Q103AQ103APBSS4240DPNPBSS4240DPN334455Q103BQ103BPBSS4240DPNPBSS4240DPNGNDGNDGNDGND112233Q101Q101PMBT4401PMBT4401DVCCDVCC11P2.2/TA1.1P2.2/TA1.11010P2.3/TA1.0P2.3/TA1.01111P2.4/TA1.2P2.4/TA1.21212P2.5/TA1.2P2.5/TA1.21313TDI/TCLK/UCB0SOMI/UCB0SCL/TA0.1/CA6/A6/P1.6TDI/TCLK/UCB0SOMI/UCB0SCL/TA0.1/CA6/A6/P1.61414TDO/TDI/UCB0SIMO/UCB0SDA/CAOUT/CA7/A7/P1.7TDO/TDI/UCB0SIMO/UCB0SDA/CAOUT/CA7/A7/P1.71515~{RST}/NMI/SBWTDIO~{RST}/NMI/SBWTDIO1616TEST/SBWTCKTEST/SBWTCK1717P2.7/XOUTP2.7/XOUT1818P2.6/XIN/TA0.1P2.6/XIN/TA0.11919TA0CLK/ACLK/CA0/A0/P1.0TA0CLK/ACLK/CA0/A0/P1.022DVSSDVSS2020UCA0RXD/UCA0SOMI/TA0.0/CA1/A1/P1.1UCA0RXD/UCA0SOMI/TA0.0/CA1/A1/P1.133UCA0TXD/UCA0SIMO/TA0.1/CA2/A2/P1.2UCA0TXD/UCA0SIMO/TA0.1/CA2/A2/P1.244CAOUT/VREF-/VeREF-/CA3/A3/P1.3CAOUT/VREF-/VeREF-/CA3/A3/P1.355TCK/SMCLK/UCB0STE/UCA0CLK/VREF+/VeREF+/CA4/A4/P1.4TCK/SMCLK/UCB0STE/UCA0CLK/VREF+/VeREF+/CA4/A4/P1.466TMS/UCB0CLK/UCA0STE/TA0.0/CA5/A5/P1.5TMS/UCB0CLK/UCA0STE/TA0.0/CA5/A5/P1.577P2.0/TA1.0P2.0/TA1.088P2.1/TA1.1P2.1/TA1.199U101U101MSP430G2553IN20MSP430G2553IN20GNDGNDGNDGNDSW103SW103MEDMED+3.3V+3.3VR112R11247k47kC104C1041u1u+3.3V+3.3VSW101SW101OFFOFFSW105SW105RESETRESETGNDGNDSBWTCKSBWTCKVFBVFBSBWTDIO/RSTSBWTDIO/RSTSWSWSBWTDIO/RSTSBWTDIO/RSTVCC_SNSVCC_SNSPWM_OUTPWM_OUTPWM_OUTPWM_OUTVFBVFBSBWTDIO/RSTSBWTDIO/RSTSBWTCKSBWTCKVCC_SNSVCC_SNSQ102Q102PXP015-30QLPXP015-30QL112233Q103AQ103APBSS4240DPNPBSS4240DPN112266Q103BQ103BPBSS4240DPNPBSS4240DPN334455U101U101MSP430G2553IN20MSP430G2553IN20DVCCDVCC11P2.2/TA1.1P2.2/TA1.11010P2.3/TA1.0P2.3/TA1.01111P2.4/TA1.2P2.4/TA1.21212P2.5/TA1.2P2.5/TA1.21313TDI/TCLK/UCB0SOMI/UCB0SCL/TA0.1/CA6/A6/P1.6TDI/TCLK/UCB0SOMI/UCB0SCL/TA0.1/CA6/A6/P1.61414TDO/TDI/UCB0SIMO/UCB0SDA/CAOUT/CA7/A7/P1.7TDO/TDI/UCB0SIMO/UCB0SDA/CAOUT/CA7/A7/P1.71515~{RST}/NMI/SBWTDIO~{RST}/NMI/SBWTDIO1616TEST/SBWTCKTEST/SBWTCK1717P2.7/XOUTP2.7/XOUT1818P2.6/XIN/TA0.1P2.6/XIN/TA0.11919TA0CLK/ACLK/CA0/A0/P1.0TA0CLK/ACLK/CA0/A0/P1.022DVSSDVSS2020UCA0RXD/UCA0SOMI/TA0.0/CA1/A1/P1.1UCA0RXD/UCA0SOMI/TA0.0/CA1/A1/P1.133UCA0TXD/UCA0SIMO/TA0.1/CA2/A2/P1.2UCA0TXD/UCA0SIMO/TA0.1/CA2/A2/P1.244CAOUT/VREF-/VeREF-/CA3/A3/P1.3CAOUT/VREF-/VeREF-/CA3/A3/P1.355TCK/SMCLK/UCB0STE/UCA0CLK/VREF+/VeREF+/CA4/A4/P1.4TCK/SMCLK/UCB0STE/UCA0CLK/VREF+/VeREF+/CA4/A4/P1.466TMS/UCB0CLK/UCA0STE/TA0.0/CA5/A5/P1.5TMS/UCB0CLK/UCA0STE/TA0.0/CA5/A5/P1.577P2.0/TA1.0P2.0/TA1.088P2.1/TA1.1P2.1/TA1.199GNDGND

Full Schematic

SVG Image created as LED Dimmer.svg date 2024/01/25 10:41:20Image generated by Eeschema-SVGR104R1043k93k91122J102J102OUTOUTGNDGNDGNDGNDC108C1081u1uGNDGNDD101D101SS35SS35C101C1011n1nGNDGNDVCCVCCR102R102470470R103R103470470VCCVCCR105R1051k1k112233Q102Q102PXP015-30QLPXP015-30QLC102C102330u330uGNDGNDL101L10156u56uR101R1011k1k112266Q103AQ103APBSS4240DPNPBSS4240DPN334455Q103BQ103BPBSS4240DPNPBSS4240DPNGNDGNDGNDGND112233Q101Q101PMBT4401PMBT4401VFBVFBSWSWPWM_OUTPWM_OUTQ102Q102PXP015-30QLPXP015-30QL112233Q103AQ103APBSS4240DPNPBSS4240DPN112266Q103BQ103BPBSS4240DPNPBSS4240DPN334455

Power Stage Schematic

Full design files can be found here on GitHub .

This is essentially a buck converter, with additional gate drive circuitry to properly level shift the PWM output from the microcontroller to be compatible with the P-Channel MOSFET, and also to provide a strong drive capability to properly turn it on and off.

C101 acts as a “speed-up” capacitor, pushing more current into the base of Q101 to try to speed up the transition times, while reducing the load on the MCU I/O pin once turn on amd turn off has been achieved.

Q103A and Q103B are a complementary pair together within a single package, and provide quite a nice and compact gate drive solution.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <msp430.h> 

/**
 * main.c
 */

unsigned int status = 1; //1->8 === off->max
unsigned int prevStatus = 1;

int setpoint[9] = {0, 0, 860, 0, 950,  0, 0, 0, 1015}; //quick access setpoint array thing

int main(void)
{
	WDTCTL = WDTPW | WDTHOLD; // stop watchdog timer
	DCOCTL = 0;  // Select lowest DCOx and MODx settings
	BCSCTL1 = CALBC1_16MHZ; // Set range
	DCOCTL = CALDCO_16MHZ; // Set DCO step + modulation*/

	P1DIR |= 1<<6; // set PWM output pins
	P1DIR |= 0b1111<<2; //set LED output pins
	P2DIR = 0;
	P2REN = 0xFF; //enable pulldowns on all pins on P2
	P2OUT = 0;
	P1OUT = prevStatus << 2;

	ADC10CTL0 = ADC10SHT_2 + ADC10ON + SREF_1 + MSC + REF2_5V + REFON; // ADC10ON, select internal reference
	ADC10CTL1 = INCH_7 + CONSEQ_2 + ADC10SSEL_2; //choose channel, set clock source and set continuous conversion
	ADC10AE0 = 1<<7; //configure A7 analog input
	ADC10CTL0 |= ENC + ADC10SC; //enable conversion immediately

	__delay_cycles(10000); //wait for voltage to stabilize

	while(1){
	    //poll buttons and set status
	    if (P2IN & 0b1111){
	        status = P2IN & 0b1111;
	        if (status != prevStatus){
                prevStatus = status;
                P1OUT &= !(0b1111<<2);
                P1OUT |= prevStatus << 2;
            }
	    }

        if(ADC10MEM < setpoint[prevStatus]){ //ADC reads that setpoint is low, provide a pulse, less than so if the setpoint is 0 it will definitely not trigger
            P1OUT |= 1<<6;
            __delay_cycles(70);
            P1OUT &= ~(1<<6);
        }
	    else {
	        P1OUT &= ~(1<<6);
	    }

	}
	
	return 0;
}

// ADC10 interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(ADC10_VECTOR))) ADC10_ISR (void)
#else
#error Compiler not supported!
#endif
{
    P1OUT ^= 1<<6; //just for debug purposes
}

The code for this project cannot be simpler. Apart from the usual setup code, it just loops over a simple routine, comparing the current output voltage from the set output voltage, and firing off a pulse if the output is low. This has the effect of sending a packet of energy to the output, increasing the voltage ever so slightly and thus achieveing regulation.

An interesting tidbit is that the ADC is set to continuous sampling mode, with no regard paid to whether or not the result register, ADC10MEM has been updated every time it’s value is polled. The reason for this is to match the behavior of the code as much as possible to the control loop simulation in the next section.

The constant on time is implemented very simply with a hand-tuned delay; while the minimum off time is provided by the time it takes to execute the rest of the code. All in all, it gives a maximum duty cycle of about 75%.

Control Loop Architecture and Analysis

The first proof of concept for this project was established in PLECS, which is very very similar to Simulink.

Qt SVG DocumentGenerated with QtV_dcV:20FETDD1L1L:56e-6C1C:330e-6R1R:0.2R2R:5VVm1Quantizerq:1ZOHZero-OrderHoldKGainfloorRounding<RelationalOperatorCConstantANDLogicalOperatorSR Flip-flopRSQ/QMonoflopPulse DelayEdge DetectionNOTLogicalOperator1ScopeVVm2AAm1R3R4R:15S1StepWhite Noise

Control loop setup in PLECS.

First of all, the constant on time topology relies on the output ripple to achieve control. Another way to think of it is that the PWM ramp you’d see in typical switching converter control loops is literally the output ripple itself.

Therefore, by definition there has to be ripple at the output. In the board that I put together, at full brightness, the ripple is approximately 400mV peak-to-peak. Ordinarly this would be unacceptable, but for this LED dimming application, there was absolutely no perceptible flicker and thus I deemed it good enough.

ripple at maximum brightness

Measured output voltage ripple at maximum brightness

plecs predicted ripple at maximum brightness

PLECS simulated output voltage ripple at maximum brightness

A divided down version of this ripple is presented to the ADC of the MSP430.

feedback ripple at maximum brightness

Measured feedback voltage

The shape and amplitude of the two waveforms is remarkably similar, which I was really pleased with because it meant that there wasn’t anything egregiously wrong with the simulation and the implemetation of the control loop in the code above, even though the sampling rate of the MSP430 ADC and the ZOH in PLECS aren’t exactly similar

I also spent a lot of time tuning the component values to try to minimize turn on overshoot, which could get pretty bad with certain combinations.

With the component values shown above, the voltage ramps up nicely with absolutely no overshoot.

turn on response

Measured transient response at turn on

simulated turn on response

PLECS simulated transient response at turn on. From top to bottom: output voltage, PWM out, ADC reading, inductor current

The load transient response of this circuit is also pretty good. This is what happens when I switch between the minimum and maximum brightness settings.

output voltage behavior when changing brightness

Output voltage behavior when changing brightenss

Power Stage Performance

This is probably the first time I really sat down and thought through the design of the power stage. So I had some fun measuring its performance.

First of all, when looking at the transition from low brightness to max brightness, it’s interesting to see that the ripple has different shapes in both.

output voltage behavior when changing brightness

Output voltage behavior when changing brightenss

Note that at low brightness, the output voltage is mostly steady, with the occasional spike. This is because the converter has entered discontinuous current mode (DCM). In fact, this behavior, with the seemingly random pulses, is similar to pulse skipping in some of the switchmode converters out there that handle very light loads.

ripple at low brightness

Measured output voltage ripple at low brightness

When examining the gate drive voltage and the switch node voltage (marked SW in the schematic) at low brightness, there’s the characteristic DCM ringing.

gate drive and switch node waveforms with DCM

Gate drive and switch node waveforms with DCM

For completeness, here are the same two waveforms at full brightness aka continuous conduction mode (CCM).

gate drive and switch node waveforms with CCM

Gate drive and switch node waveforms with CCM

Looking closer at the CCM waveform above, there is a peculiar blip in the gate drive waveform (orange) during turn off. I later confirmed this blip to be present in both DCM and CCM scenarios.

I suspect that this is due to the charging of the P channel MOSFET Cgd, although I currently have no way to verify this.

When zooming in, things become more weird because there is not an insignificant amount of delay between Vgs falling to 0 and the rise of Vds. This might be because the current from the diode has to commutate to the MOSFET before the voltage in the switch node can rise. In other words, it is limited by the di/dt.

gate drive and switch node waveforms with CCM

Gate drive and switch node waveforms with CCM

This is sort of strange tho, because the diode is, in fact, a schottky diode, and I don’t think it takes so long for the current to drop to zero.

More clues against this theory arise in DCM, when the diode current is, by definition, 0. In this mode, the exact same delay and blip occurs.

And for whatever reason, it doesn’t occur during turn on.

And in fact, I just couldn’t recreate this in simulation, so yeah, I genuinely don’t know what is going on. Please do get in touch with me if you have any ideas!.

On closer inspection of the datasheet, it seems like the turn off delay of this PXP015 is quoted as being 58ns, compared to the turn on delay of just 4ns. This is on top of a 58ns current rise time. The delay in the waveforms above seem to be much greater than this, but maybe this is part of the reason.

Conclusion

Well, this project proved to be a much bigger success than I thought it would be. It now powers an LED strip in my dorm room, which acts as my main source of light at night.

Only thing now that is left to investigate and fix is the annoying coil whine that this thing gives out…

As always, thanks for reading along!

completed board

Yup, nothing like a little good old DIP micro on a board that has a DFN elsewhere 🤣

power stage of completed board

Power electronics components, with a woefully undersized diode