With the sign flipped (i.e. voltage drop cancelled out, rather than doubled), the behavior is much better!

On the drive home I dreamed up the equation LiBCM might use to maximize power.

Basically we find the midpoint between the actual pack voltage and 120 volts (the maximum power point under assist):

Code:

```
vAdjustRange_mV = (vPackActual_V - 12 - 120) * 1000
vAdjustRange_mV = (vPackActual_mV - 132,000)
Example A (vPackActual_V = 190): vAdjustRange_mV = ( 190,000 - 132,000) = 58,000 mV
Example B (vPackActual_V = 144): vAdjustRange_mV = ( 144,000 - 132,000) = 12,000 mV
```

And then we find the 'midpoint' pack voltage between those values, except that we actually want the "2/3-point", since the assist current can be twice the regen current:

Code:

```
vPackTwoThirdPoint_mV = vAdjustRange_mV * 2 / 3 +120,000
Example A: vPackTwoThirdPoint_mV = 58 * 2 / 3 +120,000 = 159,000 mV
Example B: vPackTwoThirdPoint_mV = 12 * 2 / 3 +120,000 = 128,000 mV
```

Next we linearize the (constant) maximum possible assist+regen current (140 A +75 A = 215 A) across the (variable) spoofed voltage range:

Code:

```
#define TOTAL_CURRENT_RANGE_A 215
voltageAdjustment_mV_per_A = vAdjustRange_mV / TOTAL_CURRENT_RANGE_A
Example A: voltageAdjustment_mV_per_A = 58,000 / 215 = 270 mV/A
Example B: voltageAdjustment_mV_per_A = 12,000 / 215 = 56 mV/A
```

So now we put the last two equations together to determine the spoofed pack voltage:

Code:

```
spoofedVoltage_mV = vPackTwoThirdPoint_mV - actualCurrent_A * voltageAdjustment_mV_per_A
Example A: spoofedVoltage_mV = 159,000 - actualCurrent_A * 270 mV/A
Example B: spoofedVoltage_mV = 128,000 - actualCurrent_A * 56 mV/A
```

And now we know what voltage to spoof for any given actual pack voltage at any given current. Note that the MCM controls the assist/regen current, so LiBCM simply needs to get that current value, which is easy because LiBCM itself determines that value (via the battery current sensor).

Let's look at some "Example A" scenarios:

-no assist or idle: spoofedVoltage_mV = 159,000 - 0 A * 270 mV/A = 159,000 mV = 159 volts

- +50 A (assist) : spoofedVoltage_mV = 159,000 - 50 A * 270 mV/A = 145,500 mV = 145 volts

-+140 A (assist) : spoofedVoltage_mV = 159,000 - 140 A * 270 mV/A = 121,200 mV = 121 volts

- -50 A (regen) : spoofedVoltage_mV = 159,000 - -50 A * 270 mV/A = 172,500 mV = 172 volts
- -75 A (regen) : spoofedVoltage_mV = 159,000 - -75 A * 270 mV/A = 179,250 mV = 179 volts

Let's look at some "Example B" scenarios:

-no assist or idle: spoofedVoltage_mV = 128,000 - 0 A * 56 mV/A = 128,000 mV = 128 volts

- +50 A (assist) : spoofedVoltage_mV = 128,000 - 50 A * 56 mV/A = 125,200 mV = 125 volts

-+140 A (assist) : spoofedVoltage_mV = 128,000 - 140 A * 56 mV/A = 120,160 mV = 120 volts

- -50 A (regen) : spoofedVoltage_mV = 128,000 - -50 A * 56 mV/A = 130,800 mV = 130 volts
- -75 A (regen) : spoofedVoltage_mV = 128,000 - -75 A * 56 mV/A = 132,200 mV = 132 volts

So let's streamline the code (so it runs fast) and see how it works on the road.

spoofedVoltage_mV = ((vPackTwoThirdPoint_mV )) - actualCurrent_A * ((voltageAdjustment_mV_per_A ))

spoofedVoltage_mV = ((vAdjustRange_mV) * 2 / 3 + 120,000 ) - actualCurrent_A * ((vAdjustRange_mV ) / TOTAL_CURRENT_RANGE_A))

spoofedVoltage_mV = ((vAdjustRange_mV) * 2 / 3 + 120,000 ) - actualCurrent_A * ((vAdjustRange_mV ) / TOTAL_CURRENT_RANGE_A))

spoofedVoltage_mV = ((vPackActual_mV - 132,000) * 2 / 3 + 120,000 ) - actualCurrent_A * ((vPackActual_mV - 132,000) / 215 )

spoofedVoltage_mV = vPackActual_mV * ( 2 / 3 - actualCurrent_A / 215 ) + 614 * actualCurrent_A + 32,000

...hmm, doesn't exactly reduce more than that... time to approximate:

spoofedVoltage_mV = (vPackActual_mV * (0.667 - actualCurrent_A / 256 ) + 614 * actualCurrent_A + 32,000)

spoofedVoltage_V = (vPackActual_mV * (0.667 - actualCurrent_A / 256 ) + 614 * actualCurrent_A + 32,000) / 1000

spoofedVoltage_V = (vPackActual_mV * (0.667 - actualCurrent_A >> 8 ) + 614 * actualCurrent_A + 32,000) >> 10

(^^ always rounds to zero ^^^)

...so that doesn't work because our fixed point math truncates a major portion of the equation to zero.

So going back to this equation (two equations above):

spoofedVoltage_V = (vPackActual_mV * (0.667 - actualCurrent_A / 256 ) + 614 * actualCurrent_A + 32,000) / 1000

Let's multiply that zeroing term by 1000/1000 (i.e. '1'):

spoofedVoltage_V = (vPackActual_mV * (667 - actualCurrent_A / 256 * 1000 )/1000 + 614 * actualCurrent_A + 32,000) / 1000

spoofedVoltage_V = (vPackActual_V * (667 - actualCurrent_A / 256 * 1000 )/1 + 614 * actualCurrent_A + 32,000) / 1000

Approximate some more:

spoofedVoltage_V = (vPackActual_V * (667 - actualCurrent_A / 256 * 1024 )/1 + 614 * actualCurrent_A + 32,000) / 1024

Putting this into computer speak yields:

spoofedVoltage_V = (vPackActual_V * (667 - actualCurrent_A >> 8 << 10) + 614 * actualCurrent_A + 32,000) >> 10

spoofedVoltage_V = (vPackActual_V * (667 - actualCurrent_A << 2 ) + 614 * actualCurrent_A + 32,000) >> 10

And so the equation is:

spoofedVoltage_V = (vPackActual_V * (667 - actualCurrent_A << 2 ) + 614 * actualCurrent_A + 32,000) >> 10

To prevent overflowing a uint16_t, we can divide each half of the above equation by 4 (prior to adding), and then divide by 256 (instead of 1024):

spoofedVoltage_V = (vPackActual_V * (667 - actualCurrent_A << 2 ) / 4 + ( 614 * actualCurrent_A + 32,000) / 4) >> 8

spoofedVoltage_V = ((vPackActual_V * (667 - actualCurrent_A << 2 ) >> 2 ) + 154 * actualCurrent_A + 8,000) >> 8

**spoofedVoltage_V = ((vPackActual_V * (167 - actualCurrent_A) + 154 * actualCurrent_A + 8,000) >> 8**
That's QTY8 shifts, QTY2 multiplies, QTY3 adds. Not great, but not terrible either. At least we got rid of the division.

So let's compare our CPU_optimized equation to the non-approximated equation:

You can see that our CPU optimized function doesn't have enough gain to get us to the magical 120 volts we want (135 volts calculated, versus 121 expected). This is primarily due to our approximating 215 as 256, which we did because bit-shifting is considerably faster than division ("x/256" is equivalent to "x>>8").

So now let's manually massage the constants to see if we can get closer to the ideal 120 volt value during wide open throttle.

*<Beep boop boop>* Sure enough, changing the equation to:

**spoofedVoltage_V = ((vPackActual_V * (167 - actualCurrent_A) + 135 * actualCurrent_A + 8,000) >> 8**
yields the following results:

You can see we're much closer to 120 volts during hard assist, with the tradeoff being we don't spoof as high a voltage during hard regen. I doubt that matters.

So tomorrow I'll add this code and go for a test lap up Lookout Mountain. Lots of thinking for a single line equation.

...

For now I've just hardcode the spoofed voltage to always be 150 volts, which LiBCM can spoof across the entire usable SoC. Specifically, that works out to 3.375 volts per cell, which is less than 10% SoC; LiBCM now disables assist below 3.500 volts (20% cell SoC), so there shouldn't be any limitations beyond not achieving peak power.

This will let our beta testers implement a small hardware change and then play around with the latest firmware.