## Jul 8, 2017

### USB-C battery pack power stage design

This is my capstone project in Coursera's Power Electronics Specialization from University of Colorado at Boulder, wherein I design the switching power delivery and battery charging circuit.  The product requirement is for 3-cell LiPo in series (which will range from 9.6 V to 12 V, with an equivalent series resistance Rbat = 50 mπ) to work in the following operating points:
1. Provide power to USB bus, up to 2 A at Vbus = 5 +/-0.1V (therefore with effective Rbus = 2.5 π), when Vbat = 12.6 V.
2. Provide power to USB bus, 3 A at Vbus = 20 V (therefore with effective Rbus = 6.7 π), when Vbat = 9.6 V.
3. Charge the battery pack (which is at Vbat = 11.1 V) from the USB bus, drawing up to 3 A at 20 V.  Since Rbat = 50 mπ and we want 3A going to the battery, the supply side should be higher than Vbat by 150 mV, so we need D Vbus = 11.1 + 0.150 = 11.25, so D =  11.25 / 20 = 0.56.
In all cases, the given switching frequency Fs = 1/Ts = 100 kHz.

I used Omni Graffle for idea sketch, and LTSpice IV for the schematic capture and simulation (after reading this book on LTSpice).

## Switch design

Since the battery voltage will be higher than the 5 V (the output voltage in mode 1) but lower than 20 V (the output voltage in mode 2), neither the pure step down (buck) nor step-up (boost) converter will do the job alone; I need a buck-boost converter, which will work like the buck converter if Vbat > Vbus, but like the boost converter when Vbat < Vbus, as you can see below.
 Let the switch duty cycle D is the portion of the time (out of one switching period Ts) that the switch is in position 1.  Then: Left: lossless buck converter duty cycle D = Vbus / Vbat = 5 / 12.6 ≈ 0.4 Right: lossless boost converter duty cycle (1-D) = Vbat / Vbus = 9.6 / 20 = 0.48 => D = 0.52   In both cases, the average current flows from left to right (Vbat to Vbus).
If you combine the 2 of them in series, using the same inductor in the middle, then I get a non-inverting buck boost convert converter, shown below.
 Unlike the inverting buck-boost converter, the 2 switches do NOT move together at the transition of the duty cycle in a switching period; one of the switches remain in one position depending on the mode (step up/down) while the other switch does all the swiching.
Because of the duality of buck and boost (step down and step up), I can keep the switches in the boost mode when charging the battery from the bus (whose voltage is guaranteed in this problem to be higher); to visualize this, note the perfect equivalence with the buck converter if I keep Sbuck  closed and switch Sboost between positions 1 and 2 in the picture below.
 When charging the battery, Vbus becomes the input voltage and Vbat becomes the output voltage of a buck converter flipped on the back.  The average current flows from right to left, and the switch duty cycle D is the complement of the ratio Vbat / Vbus = 11.1 / 20 = 0.555; that is, D = 0.445.
Note that the switch setting for Sboost is the opposite of Sbuck, so Vbat / Vbus ratio during charging is not the usual duty cycle ratio, but the complement of that: D'.  To prevent downstream confusion, let's summarize the rough output voltage ratio in the 3 operating cases:
1. Buck mode: Vout = D Vin
2. Boost mode: Vout = Vin / D'
3. Charge mode: Vin = D' Vout
Ignoring losses, in the buck converter mode, the inductor current ripple π«iL ≈ (Vbat - Vbus) D Ts / 2L.  Using the given operating point values in this mode (operating point 1 in the intro),
π«iL ≈ (12.6 - 5) 0.4 / (2 100k L) ≈ 15E-6 / L
Another way to look at this is to compare against the average inductor current, which must equal the average current to the load: iL ≈ 2 A.  The ripple percentage is then π«i/ IL ≈ 15E-6 / L / 2 = 0.75 E-6 / L.  To hold π«iL within say 10% of the steady-state current then, L must be greater than 0.75 E-6 / 10% = 7.5 πH.

And again ignoring losses, in the boost converter mode:
• The voltage ripple at the the output of the converter π«v = Vbus D Ts / (2 Rbus C), where C is the capacitance in series with the load.  If we want π«v < 0.1 V, then C must be larger than Vbus D Ts / (2 Rbus π«v) = ibus D Ts / (2 π«v) = (3 A) (0.52) (10 πs) / (2 * 0.1) = 78 πF.
• π«iL ≈ Vbat D Ts / L which for 10% ripple target means the inductance must be larger than Vbat D Ts / (10% 3 A) = 166 πH.
In charging mode, Sboost is actually working like a buck switch, so the current ripple analysis can be repeated with voltages flipped numbers: π«iL ≈ (Vbus - Vbat) D' Ts / 2L.  Note that the duty cycle is the complement of the usual buck converter duty cycle, because I am using Sboost as the buck converter.  For operating point 3, the ratio of the inductor current ripple π«iL/IL then work out to (20 V - 11.1 V) 0.555 / (2 10% 100k L).  To keep this down below 10% requires L greater than (20 - 11.1) 0.555 / 20k = 247 πH! The inductor is getting bigger and bigger!

### (Solid state) switch design

To implement this schematic with the usual power electronics semiconductor devices, I need the devices Q1~Q4:
 The "Q" just means some kind of semiconductor device, rather than a transistor necessarily.
All switch implementation that meet the following currents and voltages across the switches are permissible.  In each mode, I have to be careful about the voltage that a switch must block while it is open.
 Current flow and voltage across the switches during the D Ts and D' Ts phase of the buck mode
 Current flow and voltage across the switches during the D Ts and D' Ts phase of the boost mode
 Current flow and voltage across the switches during the D Ts and D' Ts phase of the charging mode
From these diagrams, the following requirements can be placed on the Q1~Q4 and a natural choice for the implementation:
• Q1: active +/passive- current, but only + voltage ==> MOSFET in parallel with a diode
• Q2: passive - current, but only + voltage ==> diode
• Q3: active +/passive - current, but only - voltage ==> MOSFET in parallel with a diode
• Q4: active +/passive - current, but only + voltage ==> MOSFET in parallel with a diode

### MOSFET choice

I choose Infineon BSC100N03MS as the MOSFET, because it was used in the instructor given examples.  According to the data sheet, the drain to source voltage (the blocking voltage) is 30 V, which is sufficient for the maximum of expected 20 V applied on the USB bus end.  It is capable of conducting 44 A nominally, and 176 in a pulse.  My expected gate-source voltage is 20 V, which is much larger than Vin (which is 12.6 V max).  In short, it has adequate voltage and current margin for the problem at hand.

### Sbuck implementation using diodes (and a MOSFET)

Q1-Q2 pair that comprises Sbuck has only 1 active element (MOSFET) between them, so the control is rather straight-forward (at first), like this:
 In Sbuck, Q1 consists of fast recovery diode (D3 in this schematic) in anti-parallel with a MOSFET, and Q2 is just another diode.  (I am not sweating over the optimal part selection here; just grabbed any diode previously used in the class.)  The MOSFET conducts the actively turned-on + current, and the passively conducts the negative current in the other direction.  The diode on the right blocks the positive voltage at the source of the MOSFET when it conducts, but passively conducts the negative current (flowing from the bottom to the top, or ground to the inductor) when the MOSFET is turned off.
But since the MOSFET is not grounded, the gate will be floating, so we need a floating gate driver to supply enough current to the gate (to turn it on, or AKA close it) when commanded (c_buck in the schematic below).  The floating gate driver in turn needs floating (i.e. isolated) power supply.  In this class, a low power isolated unregulated DC-DC converter is recommended for use.
 Sbuck, with MOSFET gate driver powered by a floating unregulated DC-DC power supply.  Note Vss pin of the gate driver is connected to the SOURCE (downstream of the current) side of the MOSFET.  If you get this wrong, the MOSFET will always be on!

The isolated DC-DC converter (U8 above) in turn will get the power straight from the battery, and provide VoutP − VoutM = n Vin, where I choose n = 1 (can be between 1 and 2).

Note the resistor between the MOSFET gates and their drivers is there to limit the inrush current into the gate, and should be chosen for the particular MOSFET.  But conversely, we want to remove the gate charge from the MOSFET as quickly as possible during the turn off transition--again as much as the devices can tolerate.  So in some examples, I found a Schottky diode (which is a very fast diode with little voltage drop) shorting out the current limiting resistor (as shown below), to effectively remove the current limit during the turn off transition.  To be honest, I still don't understand why it's OK to remove current limit when removing the gate charge from the MOSFET, while a current limiting resistor is required when adding it.

### Sboost implementation using diodes

Sbuck starts out as an exact dual of Sbuck in that it can pass the average current in either direction: through Mboost-Dboost_Dp pair during boost mode, or through Mchg-Dchg_Dp pair during charge mode.
 During the boost mode (step up power supply), Mboost-Dboost_Dp work in a complimentary fashion to keep the average current flowing from left (Vbat) to right (Vbus).  During battery charging mode, Mchg-Dchg_Dp work together to keep the average current flowing from right (Vbus) to left (Vbat).  During buck mode (step down power supply), Mchg is always open to keep the current flowing only through Dboost_Dp.
Of course, the gate drivers are necessary to drive the MOSFETs.
 BUG ALERT: I should have connected outM pin of Uchg_dd to the SOURCE pin of Mchg, but instead connected it to the drain pin.

### MOSFET logic gate coordination

In this realization of the power stage, there are total of 3 MOSFETS that have to be turned on/off in a coordinated manner.  Let's enumerate their required on (closed) and off (open) states during the 2 phases in each mode:
ModeStep down (buck)Step up (boost)Charge Battery
PhaseD TsD' TsD' TsD TsD' TsD Ts
Mbuck
1
0
1
1
0
0
Mboost
0
0
0
1
0
0
Mchg
0
0
0
0
0
1
The assignment provides 2 independent control signals: Vcontrol1 and Vcontrol2.  To control all 3 switches with just these 2 signals, I must reduce the above truth table to use only 2 inputs.  Digital logic reduces inadvertent switching action from noise, and I can generate the following 2 digital signals from Vcontrol1 and Vcontrol2, using the digital logic threshold voltage Vhigh/2 = 2.5V:
1. Boost = V(control1) > Vhigh/2
2. Charge = V(control2) > Vhigh/2
Since the HW recommended a 5 V digital logic, I am using 2.5 V as the threshold.  I still need a rapidly switching PWM from these 2 control voltages, so I can derive the PWM signals for the 3 modes like this:
1. For step-down mode, PWM(Vcontrol1), with VM=Vhigh/2, so that Mbuck duty cycle will be continuous (D = 1) when going from the buck mode to the boost mode.
2. For step-up mode, PWM(Vcontrol1 - Vhigh/2)
3. For charge mode, PWM(Vcontrol2 - Vhigh/2)
In cases 2 and 3 above, I need a reference voltage and a subtractor.  I am allowed to use an IC to generate a precision reference voltage (despite the irony of having to use a voltage reference in a DC-DC converter design); the LT1121-5 (the "-5" in the part name is for 5 V output) was recommended, and I use a voltage divider to derive a 2.5 V from it.  With a 1% resistor, I know that the Vhigh/2 reference can be 2 % off (if one resistor is 1% higher and the other is 1% lower), but since the design does not require operation near 100 % duty cycle, I'll be OK.  The subtraction of the Vhigh/2 reference voltage can be done with an op-amp with sufficiently high open-loop gain.  I just used the instructor recommended part LT1498 (which has a rail-to-rail input-output, 10 MHz gain-BW product, and 6 V/us slew rate), as you can see below, where V(Ctrl1_2V5) = V(control1) - Ref2V5.
 This subtractor is going to suffer from analog issues (like part and temperature variance).
The reference voltage takes quite a few microseconds to settle, and the reference's ability to track half the 5 V from the voltage generator is somewhat disappointing.
The subtracted signal is then fed into a PWM generator with VM=Vhigh/2.  The truth table for the MOSFETs can then be implemented with this mixed signal logic:
• duty(Mbuck) = NOT(control2 > Vhigh/2) AND (control1 > Vhigh/2 OR PWM(control1))
• duty(Mboost) = NOT(control2 > Vhigh/2) AND control1 > Vhigh/2 AND PWM(control1 - Vhigh/2)
• duty(Mchg) = control2 > Vhigh/2 AND PWM(control2 - Vhigh/2)
To satisfy both the truth table and the MOSFET duty cycles for the operational points, control2 should be clearly be < Vhigh/2 during buck and boost modes.  ANDing the digital signal with PWM's output is necessary because achievable minimum duty cycle may not be 0.  The logic IC implements the "> Vhigh/2" operation internally, so I can simplify the above logic expression:
• duty(Mbuck) = NOT(control2) AND (control OR PWM(control1))
• duty(Mboost) = NOT(control2) AND control1 AND PWM(control1 - Vhigh/2)
• duty(Mchg) = control2 AND PWM(control2 - Vhigh/2)
Here is the implementation of duty(Mbuck), duty(Mboost), and duty(Mchg) that uses the 5 V and "2.5 V" explained above.
At first, I use hard coded values for Vcontrol1 and Vcontrol2 to simulate the converter response, so ideally, I want 3 distinct time periods to exercise the 3 operational modes of the converter.  That is, I want each of the 3 duty cycles to not overlap.  Ignoring noise, it looks like I will be able to do that.

### Excessive power loss through the diodes

If I drive the converter in buck mode with duty(Mbuck) shown above, I can see the expected response of a buck converter.
 The current spikes through the MOSFET at the beginning of each  MOSFET turn-on is many times the inductor current, and is on the order of maximum current spike specified in the MOSFET and the diode data sheet.
The output voltage is much lower than the required 5 V, because of the forward voltage drop of 2 diodes in the forward path (1 in Sbuck and 1 in Sboost): 0.85 V each.  2 A nominal current through 1.7 V drop comes out to 3.4 W, which is 34 % of the power delivered to the bus (2 A at 5 V) in the step-down operation mode: clearly unacceptable as a high efficiency switching converter, so back to the drawing board.  To remove the diodes in the current conducting paths, I need a complementary MOSFET, arranged with a dead time IC.

### Switch implementation without diodes

From the switch current flow and voltage digram given at the outset, it seems that Q3 and Q4 can each pass current in either direction and block the voltage in a single direction.  Therefore, I only need to replace the diode I had used in Q2 with another MOSFET that turns on during the D' interval of the buck mode.  Here is the modified switch implementations that can handle all 3 operational modes.
 MOSFET driver for Mbuck_DP does not need a high side voltage supply, because Mbuck_DP's source is connected to the ground. Except for Mbuck_DP, all MOSFETs can pass current in either direction.  I control that by designing the gate control signal carefully.
In this design, 100ns dead-time is used everywhere.  Whenever the MOSFET source is grounded, the high side gate power supply is unnecessary, so the diode replacement MOSFET is somewhat simpler than the high side.

Now that the MOSFETs are doing the double duty, I have to expand the MOSFET truth table to consider the MOSFETs that are complementary to the existing MOSFETs.
Step down
control1 = 0
control2 = 0
Step up
control1 = 1
control2 = 0
Charge
control1 = 0
control2 = 1
MOSFET D TsD' Ts D TsD' Ts D TsD' Ts
Mbuck 10 11 11
Mbuck_DP 01 00 00
Mboost 00 10 01
Mchg 11 01 10
The "x" in the truth table means "don't care"; which can be helpful in simplifying the digital logic to implement the truth table:
• duty(Mbuck) = control2 OR control1 OR pwm(control1)
• duty(MbuckP) = NOT(control1) AND NOT(control2) AND pwm'(control1)
• duty(Mboost) = (control1 AND pwm(control1 - Vhigh/2)) OR (control2 AND pwm'(control2 - Vhigh/2))
• duty(Mchg) = (NOT(control1) AND NOT(control2)) OR (NOT(control2) AND control1 AND pwm'(control1 - Vhigh/2)) OR (control2 AND pwm(control2 - Vhigh/2))
where I use the prime notation (pwm') to indicate the complement of the duty cycle.  Scanning the above Karnaugh chart, it appears that I need both of the boolean signals for control1 and control2.  So it's just easier to factor out those common digital logic circuit for   reuse.

The 5V and 2.5V reference voltages are shown in a separate part of the schematic:
The NOT(control1) and NOT(control2) are implemented with an off-the-shelf inverters.
Then the 2.5V bias subtracted voltages are derived from Ref2V5 and off-the-shelf op-amps.
The duty cycle and the complementary duty cycle pairs for the buck, boost, and the charge mode are generated by the PWM-deadtime IC pairs; the only differences are the voltage level being commanded.  Here, for example, is the PWM pairs for the boost mode:
 I am using the same dead-time of Td = 100ns for all PWM.
Finally, the 4 duty cycles can be generated from logic gates.  The most complex logic is the 3 AND gates being fed to an OR gate for duty(Mchg), but the benefit of carefully building up the intermediate signals pays off.

### LC filter to damp out voltage ripple

The original power stage design  shown above has only one inductor, and a cap each on the input and output ends.  There are a few problems with this approach: to reduce the input and output voltage ripple requires huge inductance and capacitance, which in turn--since the natural frequency of LC tank is sort(LC)--slows down the transient response.  And for some reason, the LiPo battery model given for the project neglects the naturally huge (>> 1 mF) capacitance.  If such a large capacitance is modeled, the LC tank would slow down the response unacceptably during the transient to the charging mode.  Reducing the L is desirable for speeding up the transient and keeping the part cost down, but the current ripple at the inductor might go above the saturating inductor current.

I think one way to work around the physics of the problem is to put a cascaded LC filter on the input and output side of the power stage, as you can see in this schematic:
 L2-Cout1 damps out the switching frequency ripple.  L2 can easily be an order of magnitude smaller  than the main  inductor in the power stage.
By using a small inductor-capacitor combination, the high frequency (at the switching frequency Fs) Vbus ripple can be reduced with negligible impact on the slower dynamics of the inner power stage circuit.  I want to do the same thing on the input end, but the assignment allows at most 2 inductors, so I held back.

## Inductor design

In this assignment, the Ferroxcube 3F3 material (ferrite) is suggested as the core material.  Ferrite cores have the H-B (electric field intensity-to-magnetic field density) nonlinearity depicted below:
3 core material properties for the 3F3 are:
• Saturation flux density Bsat 0.33T ~ 0.43T, between 25 ΒΊC ~ 100 ΒΊC
• Remnant flux density Br 0.12T
• Coercive force Hc ~ 12 A/m (relatively temperature insensitive)

### Choosing the inductance value

At first, I tried to use L1 = 247 πH calculated above, and go through the core geometrical constant method (the "Kg" method explained in Chapter 14 of the course textbook that I follow in the next section) is used to design the filter inductor L1.  But doing that, I wound up with a HUGE inductor (one that weighs hundreds of grams).  A USB charging IC is selling for less than \$1 at Digikey, so I reasoned that a huge inductor that is an order or magnitude or more expensive than the power management IC is a bad design, and looked for ways to reduce L1.  The cascaded LC filter at the USB bus output end I explained earlier.  I read the rubric, and actually did not find a requirement for the 10 % current ripple on L1, so I empirically arrived at the L1 and L2 value that met the requirements.

### "Kg" method

The step-by-step is to enumerate the requirements and constraints on the inductor:
• Winding material resistivity π Assuming copper at 100 ΒΊC, 2.3E-8 πm.
• Desired inductance: L1 = 10 πH, L2 = 1 πH.
• Maximum current Imax looks to be about 30 A for L1 (the main inductor) and -30 A for L2 (the isolation inductor).
• Bmax < Bsat, which is a material property of the magnetic core.  For ferrite core operating rather hot, Bsat = 0.4 T feels reasonable, so Bmax = 0.3 T.
• Fill factor Ku = 0.5 if using not-so-thin wire.
• R should be determined by acceptable copper loss = I2rms R.  Let's say I accept 0.5 W copper loss (1 W through L1 and L2) at 3 A boost or charge mode.  Then maximum R = 0.5/32 = 56 mπ for each inductor.
The initial guess for Kg is (π / R Ku) (L Imax / Bmax)2, which works out to 8.2E-3 cm5 for L1, and 0.082E-3 cm5 for L2.  Appendix D of the course textbook lists some widely used standard ferrite cores.  Among them, the pot core type 1811 and 905 have Kg close to the values calculated above for L1 and L2 (9.4E-3 and 0.183E-3, according to the table below), and weigh 7.3 g and 1.0 g, respectively.
There are other core design that will also meet the Kg requirement (like the EE core), but since I don't yet understand the subtleties of core material choice, I am just going with the 1st choice.

The next step is to calculate the required air gap lg (inductors store most of the magnetic energy in the air gap) with this formula: lg = L (π0/Ac) (Imax / Bmax)2, where π0 is the air's magnetic permeability 4π E-7 and Ac is the core's cross sectional area (0.433 cm2 and 0.101 cm2 in the above table).  Using the values for L1 and L2 above, I get 2.9 mm and 1.24 mm for inductors L1 and L2.

Next, N--the number of windings around this core--is calculated with N = (L Imax ) / (Bmax Ac).  I get 23.1 for L1 and 9,9 for L2, so just round them to 23 and 10, respectively.  These correspond to  A= L 109 / N2--inductance per turn that can be measured while grinding down the core post to create the air gap--of 18.7 nH and 10.2 nH for the 2 inductors.  A sanity check at this point is to approximate (assuming core permeability πc >> π0) the magnetic flux density B as ≈ π0 (N / lg) I; i.e. proportional to the current through the winding and inversely proportional to the air gap.  For L1 and L2 calculations made so far, B then approximates to 10 mT times the current, so that at 30 A, B < 0.3 T.

Next, the winding wire cross area Awire should be less than Ku WA / N, where WA is the core's window area (bobbin winding area in the above table): 0.187 cm2 and 0.097 cm2 for the 2 cores chosen.  So maximum Awire comes out to 4E-3 cm2 and 1.7E-3 cm2 for L1 and L2.  In the same Appendix D of the course textbook, table D.6 lists the AWG (American Wire Gauge) specs.  From this table, I picked out AWG #22 (Awire = 3.243E-3 cm2; diameter 701 πm) and AWG #25 (Awire = 1.6E-3 cm2; diameter 505 πm) as being reasonably close to the maximum Awire calculated above.  To check that these wire chose does indeed meet the target copper loss, calculate Rser = π N (MLT) / Awire, where MLT is the mean length per turn for the cores given in the above table: 3.71 cm and 2.90 cm for the 1811 and 1408 respectively.  So Rser comes out to 60 mπ for L1, and 27 mπ, which are reasonably close to the Rser sought at the beginning.  For convenience, here is the comparison of the 2 inductors:

"Kg method" formula L1 L2
Target inductance L [πH] Input 10 1
Target serial resistance Rser [mπ] Input 56 56
Target Kg [cm5] (π / R Ku) (L Imax / Bmax)2 8.2E-3 0.082E-3
Off-the-shelf Kg [cm5] Textbook Table D.1 9.4E-3 0.183E-3
Ferrite pot core size [mm2]Textbook Table D.118x119x5
Core weight [g]Textbook Table D.17.31.0
Cross section area Ac [cm2]Textbook Table D.10.4330.101
Winding (window) area WA [cm2]Textbook Table D.10.1870.034
Magnetic path length lm [cm]Textbook Table D.12.61.26
Air gap lg [mm] L (π0/Ac) (Imax / Bmax)2 2.9 1.24
Number of winding Nround((L Imax ) / (Bmax Ac))2310
Inductance per turn AL [nH]L 109 / N218.710.2
Bscale [mT/A]π0 (N / lg)1010
Maximum Awire [cm2]Ku WA / N0.1870.097
AWG chosen< Maximum Awire #22#25
AWG Awire [cm2]
3.2E-31.6E-3
AWG diameter [mm]
0.70.5
Winding resistance Rser [mπ]π N (MLT) / Awire6027
In the schematic, I now replace the numeric value of L1 and L2 with SPICE model that includes Hc, Br, Bs, Aclmlg, N, and Rser:
• L1 node1 node2 Hc=12 Br=0.12 Bs=0.33 A=43u Lm=26m Lg=2.9m N=23 Rser=60m
• L2 node1 node2 Hc=12 Br=0.12 Bs=0.33 A=10u Lm=12.6m Lg=1.24m N=10 Rser=27m
Here is an example of entering the SPICE line into the inductor component in LTSpice (^ + right click on the inductor component):
 SPICE works in MKS unit, so some mental conversion from the above table is required.  I actually found that the "Value" line would not be deleted, so I wrote the SpiceLine on the Value line instead.
And here is the result of simulation that uses the non-linear, lossy inductors thus specified:
 Vbus and Ibus settles quickly to 5 V/2 A and the 20 V/3 A steady state conditions for the buck and boost modes, and the maximum inductor current stays below the saturating current.

## Extra feature: current limiting

Although not plotted above, I found that the current spikes on the battery was approaching 200 A--clearly undesirable (and also physically impossible, due to the huge capacitance that should normally accompany a regular LiPo battery), so I came up with  current limiting circuit.  The current sense chip I've been drawing above (but not utilizing yet) only detect current in the forward direction, so I threw in another one, like this:
 Vcs measures the current in the forward direction, and Mcs measures in the opposite direction.
I then low pass (1 pole) it before throwing that into a current limit comparator:
 The comparator has a large input impedance, so should not load the current sense chip.
The opposite direction works exactly like above, except for using Mcs as the input.  The current limit (iLlim) is hard wired to 90 % of 5 V, or 4.5 V, which should means I will cap the limit to 4.5 V / (20 x 0.01 π) = 22.5 A, which is well below the 30 A saturation limit picked for the main inductor above.
To actually turn off the MOSFETs correctly when the current exceeds the threshold, the digital logic must be changed as follows:
 Buck stage complementary signal pair modified for current limitation.
 Boost stage complementary signal pair modified for current limitation.
I verified in LTSpice that the battery current is now capped at around 25 A, and the inductor current in either direction is also capped at about the same value.