987.1 Can Bus Mapping

Discussions on CAN mapping for ECUs, sensors, PDMs, and more.
boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

Very cool.

Good little community pushing things forward.

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

Played around with the steering tonight.

I'm a bit stumped.

Here is the log from a few days back, full sweep ~900 degrees.
https://docs.google.com/spreadsheets/d/ ... sp=sharing

Here is the log from tonight, roughly ~45 degree sweep. Clockwise, center, counter clockwise, center.

https://docs.google.com/spreadsheets/d/ ... sp=sharing

The 900 degree sweep was done relatively quickly, with the car on; power assisted. The 45 degree sweep was done with the car off, power steering off.

A few things are apparent. Offset 0,2 change quickly. Offset 1, seems to be slow. If we think that Offset 1 is the byte that signifies counter clockwise or clock wise, it appears to be a decent assumption.

The problem comes with offset 0,2. Without having seen a datalog of steering from any other car, my assumption would be that one of the byte(s) would equal degrees of rotation. Scaling from 0 to 900 along 0 to 255. However this is not the case, instead for the Offset 0,2 cycle through 0-255 too quickly.

Offset 3, cycles relatively slow in the 45 degree sweep and quickly in the 900 degree sweep. Offset 3 also is different when compared to the value of Offset 1. They don't seem to correlate when you compare the 45 degree sweep and the 900 degree sweep. Perhaps offset 3 is some type or rate?

Any ideas?

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

Turns out the 987.1 has a variable steering rack. 13.8-17.1:1

That might explain Offset why Offset 3 didn't correlate to Offset 1 very well.

But..

128 - 171 = 43
0 - 41 = 42

900/45 = 20

Each decimal of Offset 1 = 20degrees of rotation. Could offset 0 represent more resolution between 0-20 degrees?

function processSteering(data)
local steer = 0
if data[1] = 128 then
steer = 1*(data[0]*.0780)
else
if data[1] > 128 then
steer = 1*((data[1]-128*10)+(data[2]*.0780))
else
steer = -1*((data[1]*10)+(data[2]*.0780))
end
setChannel(steerId, (steer*1))
end

Does that look feasible? I think it needs to be ironed out.

Take a peek at the 900 sweep sheet. I wrote a formula and used it in the steering angle predicted column.

dewittpayne
Posts: 67
Joined: Sun Dec 11, 2016 1:07 am

Post by dewittpayne »

Are you sure that steering angle is the steering wheel? For use by a stability control algorithm, I would think the actual wheel angle with respect to the center line of the car would be variable of interest. If the steering is variable ratio, then there won't be a linear relationship between steering wheel position and wheel angle.

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

I'm honestly not sure what it is. I'm guessing.

Once I iron the script out I'll try it. I want to see if the formula corresponds to what I think the angle of the wheel is. Then do it with the car moving and see what happens.

Ultimately what I want is to see my rate of input in relation to throttle, brake and g force. I'm not terribly concerned with what the angle actually is. What I want to know is, is my steering steady? Do I trade gas for steering correctly? If I oversteer I'd it due to throttle induced or steering induced? Etc etc.

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

Brent,

I modified the Lua script for the E46 found here:
https://wiki.autosportlabs.com/BMW_E46_CAN

I'm not a coder, so I copied what look like it needed to be there, and then made adjustments as I thought were required.

I'm not sure why:
Local = 0

I assumed data[x] where X was the offset.

The 10.714 is from 450deg/42. The .042 is from 10.714/255.

Can you take a peek at this and let me know if I'm on the write track? I assume I can just paste this into the scripting area, and then check the dashboard for a channel named "Steering"?

Thanks!
--how frequently we poll for CAN messages
tickRate = 50
--the CAN baud rate
CAN_baud = 500000
--CAN channel to listen on. 0=first CAN channel, 1=second
CAN_chan = 1
--1 for Big Endian (MSB) mode; 0 for Little Endian mode (LSB)
be_mode = 1


--add your virtual channels here
--format addChannel(<name>, <sample rate>, <precision>, <min>, <max>, [units])
steerId = addChannel("Steering", 25, 0, -450, 450, "Deg.")

function processSteering(data)
local steer = 0
if data[1] >= 128 then
steer = (((data[1]-128)*256)+(data[0]))
elseif data[1] <= 128 then
steer = -1((data[1]*256)+(data[0]))
end
setChannel(steerId, (steer*[23.8]))
end

--customize here for CAN channel mapping
--format is: [CAN Id] = function(data) map_chan(<channel id>, data, <CAN offset>, <CAN length>, <multiplier>, <adder>)
CAN_map = {
[194] = function (data) processSteering(data) end,
}

function onTick()
processCAN(CAN_chan)
processSteering()
end

--===========do not edit below===========
function processCAN(chan)
repeat
local id, e, data = rxCAN(chan)
if id ~= nil then
local map = CAN_map[id]
if map ~= nil then
map(data)
end
end
until id == nil
end

--Map CAN channel, little endian format
function map_chan_le(cid, data, offset, len, mult, add)
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
setChannel(cid, (value * mult) + add)
end

--Map CAN channel, big endian format
function map_chan_be(cid, data, offset, len, mult, add)
offset = offset + 1
local value = 0
while len > 0 do
value = (value * 256) + data[offset]
offset = offset + 1
len = len - 1
end
setChannel(cid, (value * mult) + add)
end

map_chan = (be_mode == 1) and map_chan_be or map_chan_le
initCAN(1, 500000)
setTickRate(30)
Last edited by boggie1688 on Thu Apr 27, 2017 9:05 pm, edited 3 times in total.

brentp
Site Admin
Posts: 6274
Joined: Wed Jan 24, 2007 6:36 am

Post by brentp »

Looks like you're attempting to scale the individual bytes before they're combined into a single value. You should allow the multi-byte data be combined, *then* scale it as you see fit.
Brent Picasso
CEO and Founder, Autosport Labs
Facebook | Twitter

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

I'd be lying if I said I understood your explanation as to why "*256" was used in the E46 Steering script. I just kept my mouth shut. :( Now with your reply and a few minutes of playing in excel, I think I understand! :D


I think what you are suggesting is:
function processSteering(data)
local steer = 0
if data[1] >= 128 then
steer = (((data[1]-128)*256)+(data[0]))
elseif data[1] <= 128 then
steer = -1((data[1]*256)+(data[0]))
end
setChannel(steerId, (steer*[some constant value]))
end
What I imagine happening is:
Offset 0 = 100(decimal)
Offset 1 = 100(decimal)

Offset 0 = 64(hexa)
Offset 1 = 64(hexa)

data[0]*256+data[1] =
25700 (decimal)
6464 (hexa)

Sorry, I know you know this. But half of typing this out is validating to myself that I get what your asking me to do.

I punched all this into my spreadsheet, and came up with a constant of 23.8X. The X place seems to keep changing.

I feel like I'm understanding this, can you confirm?

I also tried debugging the script a bit in an online debugger. I think its relatively close. :D To you see anything that should be changed in the rest of the script? I literally copied and pasted with a minimal understanding of what I was copying and pasting.

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

It took two hours but I think I finally got the script working. Man that stuff I posted before was total crap.
function onTick()
getSteering(1)
end

steerId = addChannel("Steering", 25, 0, -450, 450, "Deg.")

function getSteering(chan)
id, ext, data = rxCAN(chan,100)
if id == 194 then
processSteering(data)
end
end

function processSteering(data)
local steer = 0
if data[2] <= 128 then
steer = -1*((data[2]*256)+data[1])
else
steer = (((data[2]-128)*256)+data[1])
end
setChannel(steerId, (steer*.042))
end
Also when the steering wheel is dead center, there is a strange value. The value on the dashboard is slightly slow to update. Is this solved by increasing my tick rate? 1Hz is default, so maybe 50?

brentp
Site Admin
Posts: 6274
Joined: Wed Jan 24, 2007 6:36 am

Post by brentp »

Default tick rate is 1Hz. You can try something higher, like 10/25/30/50.
Brent Picasso
CEO and Founder, Autosport Labs
Facebook | Twitter

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

Brent for some reason the steering updates with a bit of lag.

The analog sensor is fast, and the rest of the can mapping is fast too: throttle and rpm. Tick rate is set to 50.

I compared the raw datalog of the can: rpm, throttle and steering and over the same time there are equal number of data points. The data from the can bus appears to have enough resolution and does not have gaps in the angle.

Any ideas?

brentp
Site Admin
Posts: 6274
Joined: Wed Jan 24, 2007 6:36 am

Post by brentp »

Normally the CAN mapping in lua is reasonably fast - not as fast as the direct mapping, however. Would have to see your current script in it's entirety to tell for sure to make sure it's not hanging up somewhere in the code, causing a delay in processing CAN messages.
Brent Picasso
CEO and Founder, Autosport Labs
Facebook | Twitter

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

setTickRate(50)


function onTick()
checkGoPro()
getSteering(1)
if getGpsSpeed() >= 1 then
startLogging()
end
end

steerId = addChannel("Steering", 50, 0, -450, 450, "Deg.")

function getSteering(chan)
id, ext, data = rxCAN(chan,100)
if id == 194 then
processSteering(data)
end
end

function processSteering(data)
local steer = 0
if data[2] < 128 then
steer = -1*((data[2]*256)+data[1])
else
steer = (((data[2]-128)*256)+data[1])
end
setChannel(steerId, (steer*.0436))
end


--Specify your GoPro wifi password here
goproPwd = "blahblah"

--Specify your GoPro SSID here
goproSsid = "RCP3WIFI"

--Speed threshold to start recording
goproStart = 5

--Speed threshold to stop recording
goproStop = 1

--How fast we check, in Hz
tickRate = 10

--Set this to 1 to log communications between RCP & WiFi
debug = 1
-----------------------------
--DO NOT EDIT BELOW
-----------------------------
--the serial port where the WiFi is connected
port = 4
--indicates wifiStatus
--0 = not init, 1 = init sent, 2 = got IP, 3 = ready
wifiStatus = 0
lastInitTime = 0
initTimeout = 20000

function logMsg(msg)
println('[GoProWiFi] ' ..msg)
end

function sendCrlf()
writeCSer(port, 13)
writeCSer(port, 10)
end

function sendRaw(val)
for i=1, #val do
local c = string.sub(val, i, i)
writeCSer(port, string.byte(c))
end
end

function sendAt(val)
if debug == 1 then logMsg('send: ' ..val) end
sendRaw(val)
sendCrlf()
end

function toInt(val)
return string.sub(val, 1, -3)
end

function httpGet(url)
sendAt('AT+CIPSTART="TCP","10.5.5.9",80')
sleep(500)
local crlf = string.char(13) ..string.char(10)
local get = 'GET ' ..url ..' HTTP/1.0' ..crlf ..crlf
sendAt('AT+CIPSEND=' ..toInt(#get))
sleep(100)
sendRaw(get)
sleep(100)
sendAt("AT+CIPCLOSE")
end

function sendGoProShutter(cmd)
httpGet('/bacpac/SH?t=' ..goproPwd ..'&p=%' ..cmd)
end

function startGoPro()
logMsg('start GoPro')
sendGoProShutter('01')
end

function stopGoPro()
logMsg('stop GoPro')
sendGoProShutter('00')
end

function initWiFi()
logMsg('initializing')
sendAt('AT+RST')
sleep(2000)
sendAt('AT+CWMODE_CUR=1')
sleep(1000)
sendAt('AT+CWJAP_CUR="' ..goproSsid ..'","' ..goproPwd ..'"')
wifiStatus = 1
end

function processIncoming()
local line = readSer(port, 100)
if line ~= '' and debug == 1 then print(line) end
if string.match(line, 'WIFI GOT IP') then
wifiStatus = 2
end
if wifiStatus == 2 and string.match(line, 'OK') then
wifiStatus = 3
sleep(2000)
if power == nil then
power = 0
end
if power == 0 then
logMsg('Powering up GoPro')
sleep(2000)
sendPowerUp('01')
sleep(1000)
logMsg('GoPro On')
power = 1
end
if cameramode == nil then
cameramode = 0
end
if cameramode == 0 then
logMsg('Switching to Camera Mode')
sleep(1000)
modeCameraGoPro('00')
sleep(1000)
logMsg('Camera Mode Set')
cameramode = 1
end
if orientation == nil then
orientation = 0
end
if orientation == 0 then
logMsg('Setting Orientation Up')
sleep(1000)
orientationupGoPro('00')
sleep(1000)
logMsg('Orientation is Up')
orientation = 1
end
if resolution == nil then
resolution = 0
end
if resolution == 0 then
logMsg('Setting Resolution')
sleep(1000)
setresolGoPro('06')
sleep(1000)
logMsg('Resolution Set')
resolution = 1
end
if fov == nil then
fov = 0
end
if fov == 0 then
logMsg('Setting FOV')
sleep(1000)
setfovGoPro('01')
sleep(1000)
logMsg('FOV Set')
fov = 1
end
end
end


function sendPowerUp(cmd)
httpGet('/bacpac/PW?t=' ..goproPwd ..'&p=%' ..cmd)
end

function modeCameraGoPro(cmd)
httpGet('/camera/CM?t=' ..goproPwd ..'&p=%' ..cmd)
end

function orientationupGoPro(cmd)
httpGet('/camera/UP?t=' ..goproPwd ..'&p=%' ..cmd)
end

function setresolGoPro(cmd)
httpGet('/camera/VR?t=' ..goproPwd ..'&p=%' ..cmd)
end

function setfovGoPro(cmd)
httpGet('/camera/FV?t=' ..goproPwd ..'&p=%' ..cmd)
end

function checkGoPro()
if wifiStatus == 0 then
initWiFi()
lastInitTime = getUptime()
return
end
if wifiStatus == 1 and getUptime() > lastInitTime + initTimeout then
logMsg('could not connect to GoPro')
wifiStatus = 0
end
processIncoming()
if wifiStatus ~= 3 then
return
end
trigger = getGpsSpeed()
if recording == 0 and trigger > goproStart then
startGoPro()
recording = 1
end
if recording == 1 and trigger < goproStop then
stopGoPro()
recording = 0
end
end
This is currently what is loaded.

Majority of it is the GoPro. I did program some delays into the GoPro script to facilitate the configuration commands, but I thought I put the script inside of a function that would only execute when the GoPro was successfully connected.

Please let me know what you think. I'm still figuring out the scripting, so chances are it is inefficient.

brentp
Site Admin
Posts: 6274
Joined: Wed Jan 24, 2007 6:36 am

Post by brentp »

So first question, how's the response if you temporarily disable checkGoPro()? Then it would only be processing the steering data.

If it's still slow, then you may want to investigate if if the steering angle CAN messages are coming more slowly than you expect. you could also put some metrics in the script to see how fast you're getting those CAN messages.
Brent Picasso
CEO and Founder, Autosport Labs
Facebook | Twitter

boggie1688
Posts: 138
Joined: Fri Apr 07, 2017 3:47 pm
Location: Oakland, CA

Post by boggie1688 »

I'll try disabling the gopro script when I go home.

Here is sheet that has both two can IDs. One that contains RPM and Throttle (578) and one that contains the Steering (194).

https://docs.google.com/spreadsheets/d/ ... p=drivesdk

From my understanding 194 is lower than 578 so it carries priority over the RPM/Throttle message. It you look at the total number of messages, it appears to be the same.

I'm a bit baffled.

Post Reply