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. ... 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)
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('failed to initialize')

function onTick()
	headerFound = false
	while headerFound == false
		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

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:
-- documentation for ISP2 here:

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

-- identify header with bits 15,13,9,7

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

function ispHandleHh( h )
  if,hMaskH) == hMaskH then
    ispHh = h
    ispNextByteHandler = ispHandleHl

function ispHandleHl( l )
  if,hMaskL) == hMaskL then
   ispHl = l
    ispNextByteHandler = ispHandleAFh
    ispPkt =,0x7F) + 0x80*,1)

function ispHandleAFh( h )
  ispAFh = h
  ispNextByteHandler = ispHandleAFl

function ispHandleAFl( l )
  ispAFl = l
  ispNextByteHandler = ispHandleLh

function ispHandleLh( h )
  ispLh = h
  ispNextByteHandler = ispHandleLl

function ispHandleLl( l )
  ispLl = l

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

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


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

function onTick()
  if afInitd then
    afReadISP2nb( afSerNum )


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

function G()
local b=readCSer(6,0)
while b~=nil do
if D==1 and,(0xA2))==(0xA2)then
elseif D==2 and,(0x80))==(0x80)then
elseif D==4 then,2),7),1)*0x80+,0x7F)
elseif D==6 then
if E==0 then
elseif E==1 then
elseif D~=3 and D~=5 then
if A~=nil then
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()