## Sharing Math Channels for E46 M3

Discussion on the Lua Scripting capabilities for RaceCapture/Pro. Also see the <a href="http://autosportlabs.net/RaceCapturePro_Lua_Scripting">Lua Scripting Guide</a>

Moderators: JeffC, stieg

Mustangkev
Posts: 27
Joined: Thu Apr 09, 2015 8:18 pm

### Sharing Math Channels for E46 M3

I've recently discovered how to add math channels to the E46 standard script that I have been using and wanted to share them here. The calculations have mostly come from the book "analysis techniques for racecar data acquisition" by Jorge Segers. I've condensed some of the calculations and scaling factors to reduce the length of the equations and so it may not be completely clear from the script.I have not tested these out on a track yet, just driving around the block. I make no claims to their accuracy or usefulness.

I will copy the whole script in the next post but wanted to explain each of the math channels individually. The channel Id should be added to you list of channels at the top of the script.
The calculation should be added after the "processCAN(CAN_chan)" line which can be found immediatly after the can map section.
i.e.
function onTick()
processCAN(CAN_chan)
ADD MATH CHANNELS HERE
end

Gear Calculator. This is for the E46 M3 with standard diff ratio (3.62)

gearId=addChannel("Gear",10,0,0,6)

local a1=getChannel(rpmId)
local a2=getChannel(clutchId)
local a3=getChannel(gearId)
local w3=getChannel(lrWheelId)
if a2<0.5 then
setChannel(gearId,(w3/a1)*240)
else
setChannel(gearId,a3)
end

This channel works by dividing the wheel speed by the rpm and multiplying that value by 240. Using no decimal places it matches the gear position. The gear calculator runs when the clutch is engaged. When the clutch is disengaged, the gear calculator remembers the last position. This will prevent erroneous numbers when blipping the throttle on a down shift.

Wheel Longitudinal slip.

This channel will calculate the forward/aft slip of the individual wheels. This will measure wheelspin during acceleration and wheel lockup under braking.

Channels:
lfslipId=addChannel("LFWheelSlip",10,1,0,100,"%")
rfslipId=addChannel("RFWheelSlip",10,1,0,100,"%")
lrslipId=addChannel("LRWheelSlip",10,1,0,100,"%")
rrslipId=addChannel("RRWheelSlip",10,1,0,100,"%")

Calculation:
local w1=getChannel(lfWheelId)
local w2=getChannel(rfWheelId)
local w3=getChannel(lrWheelId) -- not needed here is using with gear calculator above.
local w4=getChannel(rrWheelId)
if w4 > 10 then
setChannel(lfslipId,100-(50*(w3+w4))/w1)
setChannel(rfslipId,100-(50*(w3+w4))/w2)
setChannel(lrslipId,100-(50*(w1+w2))/w3)
setChannel(rrslipId,100-(50*(w1+w2))/w4)
end

This one works by tacking the average speed of the wheels on one axel and comparing that to the speed of a wheel on the other axel. The calculation is only performed at greater than 10 mph otherwise it provide lager slip angles when speeds are low. I also increased the wheel speed channel to 1 decimal place to get better accuracy with these calculations.

Mustangkev
Posts: 27
Joined: Thu Apr 09, 2015 8:18 pm
The brakeG channel is to separate out the areodynamic drag force from the decceleration G-Froce to leave just the braking effort. This channel was created as there is no brake pressure channel on the CANBUS. The math is from this website http://www.jameshakewill.com/brake-trace.pdf

The totalG channel is to assess the transition from braking to turing to ensure.

brakegId=addChannel("BrakeG",10,2,0,1.5,"G")
totalgId=addChannel("TotalG",10,2,0,1.5,"G")

local g1=getImu(0)
local u3=getGpsSpeed()
local b2=(-1*g1)-((u3^2/10000)
if b2>0 then
setChannel(brakegId,b2)
end
local g2=getImu(1)
local g3=((g1^2)+(g2^2))^(0.5)
setChannel(totalgId,g3)

And finally Calculated Power. This uses a lot of numbers about the car that may be slightly different on mine than others. For example, this calculation uses the weight (3500lbs including me), tyre size (255/40/17 for me) as well as Cd, frontal, area, gear ratios and a few other things that i have probably forgotten. There were a lot of individual calculations that I tried to reduce to the bare minimum to keep the code as small as possible.

Channel:
powerId=addChannel("power",10,0,0,400,"WHP")

Calculation:
local p1=468.2+(0.080845*(u3)^2)
local p2=(16232+0.22125*(a1/u3)^2)*g1
if g1>0 then
setChannel(powerId,(p1+p2)*(u3/1677.85))
end

Mustangkev
Posts: 27
Joined: Thu Apr 09, 2015 8:18 pm
tickRate=30

CAN_baud=500000

CAN_chan=0

tpsId=addChannel("TPS",10,0,0,100,"%")

coolantId=addChannel("Coolant",1,0,0,255,"C")

oilTempId=addChannel("OilTemp",1,0,0,255,"C")

rpmId=addChannel("RPM",10,1,0,10000)

steerId=addChannel("Steering",10,1,-360,360,"Deg.")

brakeId=addChannel("BrakeSw",10,0,0,1)

clutchId=addChannel("Clutch",10,0,0,1)

lfWheelId=addChannel("LFWheelSpd",10,1,0,200,"MPH")

rfWheelId=addChannel("RFWheelSpd",10,1,0,200,"MPH")

lrWheelId=addChannel("LRWheelSpd",10,1,0,200,"MPH")

rrWheelId=addChannel("RRWheelSpd",10,1,0,200,"MPH")

fuelId=addChannel("Fuel",1,0,0,100,"%")

extTempId=addChannel("ExtTemp",1,0,0,120,"F")

gearId=addChannel("Gear",10,0,0,6)

lfslipId=addChannel("LFWheelSlip",10,1,0,100,"%")

rfslipId=addChannel("RFWheelSlip",10,1,0,100,"%")

lrslipId=addChannel("LRWheelSlip",10,1,0,100,"%")

rrslipId=addChannel("RRWheelSlip",10,1,0,100,"%")

brakegId=addChannel("BrakeG",10,2,0,1.5,"G")

totalgId=addChannel("TotalG",10,2,0,1.5,"G")

powerId=addChannel("power",10,0,0,400,"WHP")

function rpmFilter(value)

if value > 500 then startLogging() else stopLogging() end

return value

end

function brakeFilter(value)

return bit.rshift(bit.band(value,0x10),4)

end

function clutchFilter(value)

return bit.band(value,0x01)

end

function processWheel(id,data,offset)

local highByte = bit.band(data[offset+2], 0x1F)

local lowByte = data[offset+1]

local value = highByte*256+lowByte

value = (value*0.0388357)-0.3

setChannel(id, value)

end

function processSteering(data)

local steer=0

if data>127 then

steer=-1*(((data-128)*256)+data)

else

steer=(data*256)+data

end

setChannel(steerId,(steer*0.045))

end

function fuelFilter(value)

value = bit.band(value,0x7F)

return value/0.6283

end

function extTempFilter(value)

local temp = bit.band(value,0x7F)

if value>127 then

temp =-1*temp

end

end

CAN_map = {

 = function(data) processWheel(lfWheelId,data,0) processWheel(rfWheelId,data,2) processWheel(lrWheelId,data,4) processWheel(rrWheelId,data,6) end,

 = function(data) map_chan(brakeId,data,0,1,1,0,brakeFilter) end,

 = function(data) map_chan(tpsId,data,5,1,0.392156863,0) map_chan(coolantId,data,1,1,0.75,-48) map_chan(clutchId,data,3,1,1,0,clutchFilter) map_chan(cruiseId,data,3,1,1,0) end,

 = function(data) map_chan(oilTempId,data,4,1,1,-48) end,

 = function(data) map_chan(rpmId,data,2,2,0.15625,0,rpmFilter) end,

 = function (data) processSteering(data) end,

 = function (data) map_chan(fuelId,data,2,1,1,0,fuelFilter) end,

 = function (data) map_chan(extTempId,data,3,1,1,0,extTempFilter) end

}

function onTick()

processCAN(CAN_chan)

local a1=getChannel(rpmId)

local a2=getChannel(clutchId)

local a3=getChannel(gearId)

local w3=getChannel(lrWheelId)

if a2<0.5 then

setChannel(gearId,(w3/a1)*240)
else

setChannel(gearId,a3)

end

local w1=getChannel(lfWheelId)

local w2=getChannel(rfWheelId)

local w4=getChannel(rrWheelId)

if w4 > 10 then

setChannel(lfslipId,100-(50*(w3+w4))/w1)

setChannel(rfslipId,100-(50*(w3+w4))/w2)

setChannel(lrslipId,100-(50*(w1+w2))/w3)

setChannel(rrslipId,100-(50*(w1+w2))/w4)

end

local g1=getImu(0)

local u3=getGpsSpeed()

local b2=(-1*g1)-((u3^2/10000)

if b2>0 then

setChannel(brakegId,b2)

end

local g2=getImu(1)

local g3=((g1^2)+(g2^2))^(0.5)

setChannel(totalgId,g3)

local p1=468.2+(0.080845*(u3)^2)

local p2=(16232+0.22125*(a1/u3)^2)*g1

if g1>0 then

setChannel(powerId,(p1+p2)*(u3/1677.85))

end

end

function processCAN(chan)

local msg = 0

repeat

local id, e, data = rxCAN(chan, 0)

if id ~= nil then

local map = CAN_map[id]

if map ~= nil then

map(data)
end

end

msg = msg+1

until id == nil or msg>100

end

function map_chan(cid, data, offset, len, mult, add, filter)

if offset + len > #data then return end

offset = offset + 1

local value=0

local shift=1

while len>0 do

value=value+(data[offset]*shift)

shift=shift*256

offset=offset+1

len=len-1

end

local cv = value * mult + add

if filter ~= nil then cv = filter(cv) end

setChannel(cid, cv)
end

initCAN(CAN_chan, CAN_baud)

setTickRate(tickRate)

brentp
Site Admin
Posts: 5986
Joined: Wed Jan 24, 2007 6:36 am
This is quite fantastic, thank you for sharing. How is the data working for you so far?

We are soon introducing the next-gen CAN mapping capabilities, which will offload the raw CAN mapping out of Lua, and allow Lua to do what it's best at - creating calculated / math channels, just like you've done here.

Video preview: https://www.youtube.com/watch?v=zc1Bi__0IDk

Thanks again,
Brent Picasso
CEO and Founder, Autosport Labs
Facebook | Twitter

Mustangkev
Posts: 27
Joined: Thu Apr 09, 2015 8:18 pm
That's good to know. Will that mean that there will be more memory available or will it still be the combination of can channels and math channels?

brentp
Site Admin
Posts: 5986
Joined: Wed Jan 24, 2007 6:36 am
You'll have more memory because you won't need write all of the code to do the CAN mapping in Lua. It's a performance improvement and memory saving combo win-win. Brent Picasso
CEO and Founder, Autosport Labs
Facebook | Twitter

TXBDan
Posts: 68
Joined: Sat Mar 25, 2017 1:43 am
Mustangkev, is your BrakeG code working? As written i don't think the value will update when the value swings negative and is set to zero. Mine looks like this:

Code: Select all

``````function updateBrakeG&#40;&#41;
local accel_x = getImu&#40;0&#41;
local brake = -accel_x - speed^2/50000      --maybe 10000
if brake < 0 then
brake = 0
end
setChannel&#40;brakeg_id, brake&#41;
end``````
This way setChannel is called no matter what.

Mustangkev
Posts: 27
Joined: Thu Apr 09, 2015 8:18 pm
The brakeG would go to zero if the value went negative.
Although I haven't been using it recently, I've tried to implement the new can mapping and can't get it to read the can channels in Lua yet.