Page 1 of 1

Innovate Serial protocol

Posted: Fri Jul 07, 2017 4:30 pm
by Intel
So our Lemons team already had 4 of these Innovate Gauges daisy chained together. Looking to leverage this and input it into our Racecapture mk3

Trying to just get an initial connection/look at the output data as it comes from the serial stream.

http://www.innovatemotorsports.com/supp ... llog-2.pdf


8 data bits
1 stop bit
no parity
19.2 kBaud.

Code: Select all

--initialize Aux serial port to 19200, 8, N, 1
initSer(4,19200, 8, 0, 1)

function onTick()
 --read a line from the aux serial port with a 100ms timeout
 value = readSer(4, 200)
 if value ~= nil then
  println('read value: ' ..value)
 end
end
It is returning a blank read value over and over which seems to at least point to the connection taking place. Is there something easy I am missing here?

Currently I am just hooked up to one of these gauges on the out port. Once I get that one configured I will be adding the other 2 and the LC-2 wideband.

Posted: Fri Jul 07, 2017 7:17 pm
by brentp
It's hard to say without seeing the protocol directly, and we don't have experience with that protocol.

What I would recommend is using a basic script like you have, and then connecting it to your computer using a USB to serial adapter. Then, if you open a terminal program, like HyperTerminal or RealTerminal (both windows apps) - and then press a Key, like "A" - you should receive an "A" in the Lua script.

Once you can prove you can receive data correctly, then you can implement the innovate serial protocol.

Good luck!

Posted: Fri Jul 07, 2017 8:48 pm
by gizmodo
Just a word of caution here. I did the same thing with my Zeitronix ZT-2 and I found that even setting the tick rate at 30 it wasn't fast enough to keep up. Everything looked good with the car idling but revving and letting off the gas showed a noticeable lag in the data being output to the RaceCapture app vs the Zeitronix app and LCD. You might have better luck or I was doing something wrong, but that's what I saw.

Posted: Fri Jul 07, 2017 9:11 pm
by brentp
It really does depend on how the script is written. For example, if on every tick only one character is processed, it would be slower than having the script block until every character representing a message is received on the port - then that message can be parsed for values and virtual channels set.

Post the script as you make progress, we'll put an eye on it!

Posted: Fri Jul 07, 2017 10:32 pm
by gizmodo
Looking at the pdf it looks like you might be running into the same thing as I did at first. The readSer function assumes that each packet ends with a carriage return, which I don't see called out in the documentation. For my application the header is marked by the first three characters, which are always 0,1,2 and that sequence is guaranteed not to occur in each packet. The while loop is looking for those characters. I'd suggest just using readCSer to see if that gets you something meaningful.

Code: Select all

if initSer(4, 9600, 8, 0, 1) == true then
  println('initialized')
else
  println('failed to initialize')
end
setTickRate(30)

function onTick()
	headerFound = false
	while headerFound == false
	do
		first = readCSer(4, 10)
		if first == 0 then
			second = readCSer(4, 10)
			if second == 1 then
				third = readCSer(4, 10)
				if third == 2 then
					afr = readCSer(4, 10)
					egtLow = readCSer(4, 10)
					egtHigh = readCSer(4, 10)
					rpmLow = readCSer(4, 10)
					rpmHigh = readCSer(4, 10)
					mapLow = readCSer(4, 10)
					mapHigh = readCSer(4, 10)
					tps = readCSer(4, 10)
					user1 = readCSer(4, 10)
					config1 = readCSer(4, 10)
					config2 = readCSer(4, 10)
					--user2Low = readCSer(4, 10)
					--user2High = readCSer(4, 10)
					--user2Config = readCSer(4, 10)
					afrValue = afr / 10
					egtValue = egtLow + egtHigh * 256
					rpmValue = ((1000000 / (rpmLow + (rpmHigh * 256) ) ) * 4.59 ) / 4 -- 4 = number of cylinders
					mapValue = (mapLow + mapHigh * 256) / 10
					user1Value = ((5 / 256) * user1) * 37.5 - 18.7  -- values = 0 to 255, which correspond to 0Volts to 5Volts
					println(first ..', ' ..second ..', ' ..third ..', AFR: ' ..afrValue ..', EGT: ' ..egtValue ..', RPM: ' ..rpmValue ..', MAP: ' ..mapValue ..', TPS: ' ..tps ..', Fuel Pressure: ' ..user1Value ..', Config1: ' ..config1 ..', config2: ' ..config2)
					headerFound = true
				end
			end
		end
	end
end

Posted: Sat May 25, 2019 4:53 am
by lightningrod
It looks like I'm a couple of years too late to be helpful in getting ready for Lemons.
The aux serial port for my rcpMk3 is 6.
I expect this code should work at much slower tickRates than 60, but I haven't done much testing yet. It is designed to run with minimal delays at high tickRates, so never waits, nor needs to wait for serial input. While serial data is ready, it reads it all, so even at low tickRates it should keep up fine. I think 10 will be enough to keep up. It will depend on the size of the internal serial buffer. But it looks the Innovate AFM only generates about 6 bytes per update and looks like its going at about 10hz, so 60 bytes per second.

I'm tossing out all the data except the air fuel ratio, so if your Innovate is logging more data, you'll have to add code to read that.

I'm pretty new to lua, so feel free to offer suggestions for improvements.

Code: Select all


-- documentation for RCP serial interface here: https://wiki.autosportlabs.com/RaceCapturePro_Lua_Scripting
-- documentation for ISP2 here: http://www.innovatemotorsports.com/support/downloads/Seriallog-2.pdf
afSerNum=6
afInitd=false
afChan=-1

function afInit()
  afInitd = initSer(afSerNum, 19200, 8, 0, 1)
  if afInitd then
    println('serial initialized')
    ispInit()
  else
    println('failed to initialize serial')
  end
end

-- identify header with bits 15,13,9,7
hMaskH=0xA2
hMaskL=0x80

ispAFR = nil

-- this is a non-blocking implementation of the isp2 reader.
-- the following functions, when triggered will put the subsequent function
-- into the ispNextByteHandler variable, so when everything goes smoothly they will each find the
-- byte they are expecting.
-- the ispHandleHh function is responsible for consuming any unused packets (there is  packet count
-- detected, but I am ignoring it for now.  If you expect or need more packets, you'll need to add
-- functions to this sequence to process them.

function ispInit()
  ispHl = nil
  ispHh = nil
  ispNextByteHandler = ispHandleHh
end

function ispHandleHh( h )
  if bit.band(h,hMaskH) == hMaskH then
    ispHh = h
    ispNextByteHandler = ispHandleHl
  end
end

function ispHandleHl( l )
  if  bit.band(l,hMaskL) == hMaskL then
   ispHl = l
    ispNextByteHandler = ispHandleAFh
    ispPkt = bit.band(l,0x7F) + 0x80*bit.band(ispHh,1)
  else
    ispInit()
  end
end

function ispHandleAFh( h )
  ispAFh = h
  ispNextByteHandler = ispHandleAFl
end

function ispHandleAFl( l )
  ispAFl = l
  ispNextByteHandler = ispHandleLh
end

function ispHandleLh( h )
  ispLh = h
  ispNextByteHandler = ispHandleLl
end

function ispHandleLl( l )
  ispLl = l

  local func = bit.band(bit.rshift(ispAFh,2),7)
  -- this is the afr multiplier (not the afr)
  local af = bit.band( ispAFh, 1) * 0x80 +
       bit.band( ispAFl, 0x7F)
  local lambda = bit.band(ispLh,0x7F)*0x80 + bit.band(ispLl,0x7F)

  if func == 0 then
    ispAFR = (lambda+500)*af/10000
  else
    println( "afr: func"..func.." mult:"..af.." lambda:"..lambda )
  end

  ispInit()
end

function afReadISP2nb(serPort)
  c = readCSer( serPort,0 )
  while c ~= nil do
    ispNextByteHandler( c )
    c = readCSer( serPort,0 )
  end
  if ispAFR ~= nil then
    setChannel( afChan, ispAFR )
    ispAFR = nil
  end
end


function onTick()
  if afInitd then
    afReadISP2nb( afSerNum )
 end
end

afInit()
afChan=addChannel("AFR",10,1,8,23,"%")
setTickRate(60)


Posted: Sat May 25, 2019 2:58 pm
by brentp
Nice job! I'm sure people will find this handy.

Posted: Sun Jun 02, 2019 5:19 am
by lightningrod
After wrestling with memory issue while trying to integrate this module with some others, here is a slimmed down version. I apologise for the loss in legibility, but the memory footprint reduction is significant over the previous listing. The original was 2500 bytes (1350 after lua minifier). This version is 647 bytes (630 after minifier). It also uses less memory when loaded due to a reduction in function declarations. I'm happy to share my original code if anyone wants to use/modify it, but it may not be very usable without my hacky pipeline. This version has been run through cpp and my own very simplified cleaning code to remove the cpp clutter, comments and whitespace as well as reduce variable names from meaningful to single characters. (You can save another 17 bytes by running this through lua minifier, and a few more bytes I haven't counted by removing the check and print around the initSer call).

Code: Select all

A=nil
B=nil
C=nil
function G()
local b=readCSer(6,0)
while b~=nil do
D=D+1
if D==1 and bit.band(b,(0xA2))==(0xA2)then
elseif D==2 and bit.band(b,(0x80))==(0x80)then
elseif D==4 then
E=bit.band(bit.rshift(B,2),7)
C=bit.band(B,1)*0x80+
bit.band(b,0x7F)
elseif D==6 then
local F=bit.band(B,0x7F)*0x80+bit.band(b,0x7F)
if E==0 then
A=(F+500.0)*C*0.0001
elseif E==1 then
A=F*0.1
else
end
D=0
elseif D~=3 and D~=5 then
D=0
end
B=b
b=readCSer(6,0)
end
if A~=nil then
setChannel(afChan,A)
A=nil
end
end
if initSer(6,19200,8,0,1)then else println('initSer failed:port'..6)end afChan=addChannel("AFR",10,1,8,23)D=0
function onTick()
G()
end
setTickRate(60)