RX8 Project – Part 20, Canbus #5, RX8 Odometer

So again for this post I’m going to concentrate on one specific aspect of the RX8 CANbus system which a few people have asked me about and that’s making the ODOmeter on the cluster tick up correctly without the factory PCM (ECU) being fitted to the car. This also allows the trip A and B meters to work correctly as they are all linked to the same register.

I’ve mentioned in previously to people that the odo requires a state change to update and I think this is why people haven’t been able to identify what controls it – simply writing a value doesn’t do anything. It is controlled via byte 1 of what I defined as statusMIL, this is CAN ID 0x420 which is the same ID which also controls things like the temperature and oil pressure displays on the dash.

I originally identified how this was controlled during some of my early reverse engineering work on the cluster when I was just sending blocks of values (initially cycling between 00 and FF for all bytes in the array) to CAN ID’s to see if I could make anything happen and I noticed at some stage the trip had increased. Clearly this meant one of the ID’s was doing something and because the same ID also controls various warning lights they also flashed on and off at the same time as the odo incremented which made the identification of the ID rather simple.

Next up I edited the program to count each byte in the CAN data up in order, so for example start with an array with only 00 in each byte then starting at byte 0 increment the value by 1, send the whole array, wait briefly and increment again and so on. When the top value of FF is reached set it back to 00 step on to byte 1 and so on. This allowed me to isolate the specific byte that was controlling the odo changes. Anyway the story goes on and after a lot of trial and error with values and timings I got to this bit of fairly horrible test code:

#include <mcp_can.h>
//#include <mcp_can_dfs.h>      //Declaration for Standard Can
#include "mcp2515_can.h"        //Declaration for Seeedstudio Can Library

#define CANint 7  // Normally 2
#define LED2 23   // Normally 8
#define LED3 0    // Normally 7

#define NOP __asm__ ("nop\n\t")
const int SPI_CS_PIN = 17;
mcp2515_can CAN0(SPI_CS_PIN);   // Declaration for Seeedstudio library
//MCP_CAN CAN0(SPI_CS_PIN);     // Set CS to pin 10 - Older Library version


void setup() {
  Serial.begin(115200);

    delay(1000);    //delay to allow for monitor
    Serial.println("Init…");
    Serial.println("Setup pins");
    pinMode(LED2, OUTPUT);
    pinMode(SS, OUTPUT);
    pinMode(LED3, OUTPUT);
    pinMode(CANint, INPUT);

    Serial.println("CAN init:");
    
    if (CAN0.begin(CAN_500KBPS) == CAN_OK) 
    {
        Serial.println("OK!"); 
    } 
    else 
    {
        Serial.println("fail :-(");
        while (1) 
        {
            Serial.println("Zzz… ");
            delay(1000);
        }
     }

Serial.println("Good to go!");
}

unsigned char statusPCM[8]  = {125,0,0,0,156,0,0,0};                            // Write to 201
unsigned char statusMIL[8]  = {140,0,0,0,1,0,0,0};                              // Write to 420

void loop() 
{
    
    unsigned int i=0;
    unsigned int j=0;
    unsigned int k=0;
    float mile=0;
    unsigned char num;
    
    
   
    //Warning Lights - 0=Error, 1=OK

        for(k=0;k<200;k++)
        {
          
          for(i=0;i<=206;i++)
          {
          
          statusPCM[0] = 50;           //RPM  Value*67 gives 8500 RPM Reading Redline is 127
          statusPCM[1] = 0;           
          statusPCM[2] = 0;
          statusPCM[3] = 0;
          statusPCM[4] = 93;           //Speed  Value=0.63*(Speed)+38.5
          statusPCM[5] = 0;
          statusPCM[6] = 0;
          statusPCM[7] = 0;

          statusMIL[0] = 145;         // Temp 91-0%; 96-10%; 107-25%; 152=50%; 158-75%; 164=100%    
          statusMIL[1] = i;           // Odo / Trip     
          statusMIL[2] = 0;
          statusMIL[3] = 0;
          statusMIL[4] = 1;           // Oil Pressure (0=Off, >=1 is OK)         
          statusMIL[5] = 0;           // Check Engine Light
          statusMIL[6] = 0;           // Battery Charge Light
          statusMIL[7] = 0;
          
            CAN0.sendMsgBuf(0x420, 0, 8, statusMIL);
            delay(20);
            CAN0.sendMsgBuf(0x201, 0, 8, statusPCM);
            delay(20);
          }
          
        Serial.println("Miles : ");
          mile=(k+1)*0.05;
          Serial.println(mile);
      
        } 
while(1){}
}

Basically much of it is the includes and setup which is common on all CAN code but the more interesting bit is the main loop. In essence it just uses two nested loops to count up to a certain value and transmit the new value each time. All the other CAN is just static values to turn off warning lights on the cluster – the the ID 201 isn’t actually required here but it stops extra things blinking at you! Additionally ignore the comments on the statusPCM, while these sort of work I realised later that the speed and RPM use 2 byte blocks rather than a single byte to display all values.

So in the code there are 2 loops, the first is counter “i” which in this counts up to 206 (which is 207 steps including 0) and the second outer loop “k” which counts up to 200 and for each increment of “i” the new byte value is sent over CAN. Now through experimenting with various values for counters in earlier versions of this code I’d come up with the values you see here which seem to be consistently accurate for me. Basically 207 changes of the byte sent to the dash = 0.05 Miles counted so the extra loop “k” multiplies this up to a useful mileage change we can measure on the cluster. every time “k” increments it also sends and update to the serial monitor so we can keep track of it. We can see that 200 loops of 0.05 miles should result in a count of 10 miles and that’s exactly what we get on the trip meter.

The very last line is one you don’t see in many Arduino programs, “while(1){};” makes the program hang at this step. Basically what it’s doing is a normal while loop except using a static 1 as the condition makes it always true but there’s no code in the curly brackets so it will just sit there endlessly doing nothing rather than starting the main loop again as it would normally. This was simply so I could leave the code running to completion (at this stage I was sending CAN data with much wider time delays than needed to make sure none got missed) without it looping back round without me noticing.

So now we can count it up which is interesting but not especially useful as it is but since my car didn’t move I left it here for a long time with the plan to sort it out later. Anyway following my recent posts resurrecting this project there’s been some interest in how to sort it out so I started working on it within my already butchered about derivative of Dave Blackhursts code, which in itself was a further development of much of my earlier work. I’m aiming to post a version of my finalised code for the complete system once I’m happy I’ve got all the features I want but this should give you enough info to understand how this bit works.

Updates by Timer1 Interrupt

First off lets just say this didn’t work quite right for this task so I moved away from timer interrupts but it does include some interesting bits of timer manipulation so I’m including it anyway.

The best idea I had initially was to use the data from the ABS system which gave an accurate speed for all the wheels individually but the problem was I would need to vary the frequency at which the ODO byte was changed such that the ODO rate matched the speed. My first idea here was to use a hardware timer on the Arduino and use a hardware interrupt to increment and send the CAN packet. This would require the timer count value to be calculated on the fly from the ABS data. First I built and Excel spreadsheet which firstly calculated the frequency we need to update and send CAN data at to give the right ODO reading for a given wheel speed.

excel mph to freq conversion

So all this does is some basic calculation,

Pulses per mile * mph = pulses per hour

pulses per hour / 3600 = Hz (updates per second)

1000 / Hz = Millisecond interval per update (used to cross reference from the CAN logger)

From this we can fiddle about with values to see what gives enough range on timer1 (because it’s 16 bit which gives us a much wider range of intervals, timer 0 is only 8 bit and usually used elsewhere in the Arduino IDE) to make this work, or for that matter if it even is possible without dynamically changing the pre-scaler value.

Timer 1 is controlled primarily by register OCR1A which is the value it counts to before the timer resets so we need to work out the correct value for the register to do what we want. Rearranging the standard equation gives us this:

OCR1A = (( Clock_Freq / Required_Freq ) / Prescale ) – 1

Now because the count is a 16 bit integer there will be some error created by rounding on the count value so I added another section which updates to show the error vs the required value.

Excel OCR1A count value calculation

The top two lines are just the standard calculation the middle two lines are where the clock and prescaler values are entered and the OCR1A value is calculated. The 230 and 1.15 come from the previous step automatically. The bottom two lines then return the degree of error resulting.

It looks like we can cover the range we want with a prescaler of 1024 however as the speed goes down below 1mph the period between CAN sends becomes excessively long and beyond the range of the timer (max value of OCR1A is 65535), the limit is approximately 0.21 mph which seems reasonable. This means only 1 update over CAN every 4.1s which it turns out is about the limit anyway because if you make the updates longer than about 4.5s for this ID all the warning lights turn on again between updates but we can handle this case seperately.

Now to get the timer working we need to set up a few other things

TCCR1B options

We want to use CTC mode resets the count value at the top of the count to make it loop round constantly so WGM12(CTC1) in register TCCR1B should be set to 1.

TCCR1B options

Next to get the prescaler value of 1024 we need we set CS12 and CS10 to 1. Lastly OCIE1A in TIMSK1 needs to be set to 1 to trigger the interrupt every time the count reaches the count value set bring it all together and we get something like this :

void setupTimer1() {
  noInterrupts();
  // Clear registers
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;

  // Freq (Hz) (Clock (Hz) /((OCR1A + 1) * Prescaler))
  OCR1A = 64700;
  // Set CTC mode - trigger interrupt on TOP
  TCCR1B |= (1 << WGM12);
  // Prescaler 1024
  TCCR1B |= (1 << CS12) | (1 << CS10);
  // Output Compare Match A Interrupt Enable
  TIMSK1 |= (1 << OCIE1A);
  // Enable Interrupts
  interrupts();
}

// Send update to cluster on interrupt
ISR(TIMER1_COMPA_vect) {
    // Increment ODO byte each time function is called
  // this makes ODO update based on ISR frequency call.
  // Check speed is > 0.21 mph ( (left+right/2) * 0.337962 * 100
  if ( (frontLeft + frontRight) >= 68){
    send420[1]++;   
  }
   
  CAN0.sendMsgBuf(0x420, 0, 7, send420);
 
  return;
}

Next up to convert from a speed to a timer count value we need to do the same thing on the Arduino that we did in Excel earlier so here’s a quick function which just calculates count.

int calcTimer1Count(float speedKMH){
  float freq;
  int count;
  float speedMPH;
  
  //Serial.print("speed = ");
  //Serial.println(speedKMH);
  speedMPH = speedKMH / 160.934;
  // Required frequency for timer 1 ISR
  //  1.15 is 4140 (Pulse per Mile) / 3600 (1hr in seconds)
  //  0.7146 is 2572.5 (pulse per KM) / 3600
  freq = speedMPH * 1.15; 
  //Serial.print("freq = ");
  //Serial.println(freq);
  // Required OCR1A value based on 16Mhz clock, works best with 1024x prescaler giving minimum 0.21mph
  count = ((16000000 / freq) / 1024) - 1;
  //Serial.print("count = ");
  //Serial.println(count);
  return count;
}

The input here is km/h because that’s what the ABS values are given in so we might as well convert that here as well.

Now in the main loop we’re scanning for incoming data on CAN and checking what the ID is already for the bits to manage the immobiliser exchange and handily in this Dave Blackhurst has already added code to read the ABS data which we know is coming in on ID 4B0 in two byte blocks so we can just update OCR1A with a function call to our new calculation function with the average of the front wheel speeds whenever new ABS data is received.

    if(ID == 0x4B0) {
      frontLeft = (buf[0] * 256) + buf[1] - 10000;
      frontRight = (buf[2] * 256) + buf[3] - 10000;
      rearLeft = (buf[4] * 256) + buf[5] - 10000;
      rearRight = (buf[6] * 256) + buf[7] - 10000;
      
      //Going to check front wheel speeds for any issues, ignoring the rears due to problems created by wheelspin
      if (frontLeft - frontRight > 500 || frontLeft - frontRight < -500) { //more than 5kph difference in wheel speed
        checkEngineMIL = 1; //light up engine warning light and set speed to zero
        vehicleSpeed = 0;
      } else {
        vehicleSpeed = (((frontLeft + frontRight) / 2) / 100); //Get average of front two wheels.
      }
      //Update timer count value with live speed for ODO
      OCR1A = calcTimer1Count((frontLeft + frontRight) / 2);
      
      // Dump speed to serial for debug - this is just a cropped int.
      //Serial.println(vehicleSpeed);
      
    }

Now when I ran the whole program I hit a minor issue, it worked initially and the timer speed changed correctly but after a small number of can packets the Arduino stopped sending any other CAN data. I don’t know what caused it – best guess is when the interrupt was called it was blocking something and causing a buffer overrun somewhere else making the Arduino crash but either way I felt a different approach would probably be quicker than solving that so I moved on.

Updates by Delay

So the alternate approach relies on the main loop running quite quickly and simply counts how much time has elapsed since a function was last called and if over the set value calls the function. This basically allows functions to be called in the loop without needing to slow the loop itself down. The down side here vs interrupts is the accuracy of this is entirely based on the speed of the main loop so if the loop slows down the events will move about in time. Also obviously if the loop is only doing something like 10ms per iteration you will never get something to go faster than that!

void loop() {
  //Send information on the CanBus every 100ms to avoid spamming the system.
  if(millis() - lastRefreshTime >= 100) {
    lastRefreshTime += 100;
    sendOnTenth();
  }

So this is the initial version Dave used in his code to keep the scanning at a decent pace for the CAN reads. The function millis() just returns a counter value in milliseconds since startup. Unfortunately scanning at millisecond rates isn’t going to cut it for the ODO because at say 200mph the update period is 4.35ms so if we work in ms the best we could do assuming the loop ran fast enough is 5ms which means the ODO would be going 15% slower than the vehicle speed. Luckily Arduino also has a similar built in function ‘micros()’ which does the same thing but counts in microseconds. Some of you familiar with binary might be thinking an integer counting up in microseconds will hit its limit very quickly and then it is likely to cause a problem with the calculation however we get round this because the returned value is of type ‘unsigned long’ which gives us a range of up to about 70 minutes before the count resets. Interestingly because of the way data types wrap round like this the variable “lastRefreshTime” can actually be either signed or unsigned and it works the same.

long calcMicrosecODO(float speedKMH){
  long uS;
  float freq;
  float speedMPH;
   
  Serial.print("Speed = ");
  Serial.print(speedKMH/100);
  Serial.println(" km/h");
  speedMPH = speedKMH / 160.934;
  // Required frequency for timer 1 ISR
  //  1.15 is 4140 (Pulse per Mile) / 3600 (1hr in seconds)
  //  0.7146 is 2572.5 (pulse per KM) / 3600
  freq = speedMPH * 1.15; 
  Serial.print("Freq = ");
  Serial.print(freq);
  Serial.println(" Hz");
  uS = 1000000/freq;
  if(uS < 4500000 && uS > 0){
    return (uS);}
  else {
    return (4500000);
  }

Similarly to the previous interrupt version we have a function which calculates the correct delay required to keep the ODO at the right frequency, there are some minor differences to work in microseconds and if the speed is too slow cap the output at 4500000us (4.5s). Otherwise it’s much the same.

    if(ID == 0x4B0) {
      frontLeft = (buf[0] * 256) + buf[1] - 10000;
      frontRight = (buf[2] * 256) + buf[3] - 10000;
      rearLeft = (buf[4] * 256) + buf[5] - 10000;
      rearRight = (buf[6] * 256) + buf[7] - 10000;
      
      //Going to check front wheel speeds for any issues, ignoring the rears due to problems created by wheelspin
      if (frontLeft - frontRight > 500 || frontLeft - frontRight < -500) { //more than 5kph difference in wheel speed
        checkEngineMIL = 1; //light up engine warning light and set speed to zero
        vehicleSpeed = 0;
      } else {
        vehicleSpeed = (((frontLeft + frontRight) / 2) / 100); //Get average of front two wheels.
      }
      //Update timer count value with live speed for ODO
      ODOus = calcMicrosecODO((frontLeft + frontRight) / 2);
      
      Serial.print("ODO Step : ");
      Serial.print(ODOus);
      Serial.println("us");
      // Dump speed to serial for debug - this is just a cropped int.
      //Serial.println(vehicleSpeed);
      
    }

Above we see the call for ‘calcMicrosecODO’ which is basically the same as the interrupt version but gives an actual time delay in microseconds rather than a timer count. It is called in this way with a calculation in the parameter field because if it is stored in a variable in between the number gets cropped off at the decimal point even if the variable is a float – I believe this is due to the way Arduino processes floats.

void loop() {
  //Send information on the CanBus every 100ms to avoid spamming the system.
  if(micros() - lastRefreshTime >= 100000) {
    lastRefreshTime += 100000;
    sendOnTenth();
  }
  // Call function to updateMIL on variable timebase
   if(micros() - ODORefreshTime >= ODOus) {
   ODORefreshTime += ODOus;
    sendOnClock();
  }

So our extra call for the variable timed refresh is above, it varies a bit from the first one because we have a varying time delay depending on the frequency required which here is a long we are calling ‘ODOus’ which we calculated previously. Obviously because we are using two comparisons we need another refresh time variable to keep them distinct, this is also a long. Also the second one calls a specific function for the variable time refreshes ‘sendOnClock()’

void sendOnClock(){
  // Do not increment ODO byte when step is = 4.5s
  // slower than this updateMIL must still be called so 
  // warning lights don't turn on but speed may be zero!
  if ( ODOus < 4500000){
    send420[1]++;   
  }
  updateMIL();
  CAN0.sendMsgBuf(0x420, 0, 7, send420);
}

This function is pretty basic, all it’s doing is checking the resulting ODOus value isn’t 4.5s because if it is this value is means the calculation is at it’s upper limit and the car isn’t moving (specifically the speed is below 0.21 mph). In this state it still updates the warning light registers and sends the CAN packet to keep the warning lights from blinking on but it doesn’t increment the ODO byte value so the ODO doesn’t continue to tick on.

// Time delay for Odo
long ODOus = 4500000;      

// Set to max 4,500,000 to keep dash 
// MIL warning lights awake at 0 speed

The declaration for ODOus is set as 4,500,000 at start up so it correctly. The only downside to this is it takes a few seconds for the dash lights to go out initially and the oil pressure gauge goes to normal in two steps. This could be dramatically improved either by setting a higher minimum speed at which the ODO ticks because even increasing to 0.5 mph would improve the maximum duration to 1.7 seconds. Another option to avoid this is just to send an update to 0x420 in between the ODO calls. If we keep the one to the other loop ‘sendOnTenth’ without incrementing the ODO value it will just display any changes to the warning lights almost instantly while keeping the ODO working correctly.

void sendOnTenth() {
  //  PCM Status's to mimic the PCM being there, these may be different for different cars, 
  //  and not all are always required, better safe and include them all.
  CAN0.sendMsgBuf(0x203, 0, 7, send203);
  CAN0.sendMsgBuf(0x215, 0, 8, send215);
  CAN0.sendMsgBuf(0x231, 0, 8, send231);
  CAN0.sendMsgBuf(0x240, 0, 8, send240);
  CAN0.sendMsgBuf(0x620, 0, 7, send620);
  CAN0.sendMsgBuf(0x630, 0, 8, send630);
  CAN0.sendMsgBuf(0x650, 0, 1, send650);
  
  updateMIL();
  CAN0.sendMsgBuf(0x420, 0, 7, send420);    // Duplicated in sendOnClock to update ODO

  updatePCM();
  CAN0.sendMsgBuf(0x201, 0, 8, send201);

So that’s it but I should probably mention that I’ve not tested this on a live car however I have a program which works out the correct CAN byte values and then builds a correctly formatted string which can be pasted into USBtinViewer and sent to simulate an update from the ABS and the Arduino and cluster all seem to respond correctly:

Labview speed to CAN bytes converter  front panel
Labview speed to CAN bytes converter  front panel code

In this code you will see a scaling factor of 100 which should be correct but on my cluster I seem to get a speedometer reading 1 mph higher than intended from a calculated 5-185mph I actually see 6-186 mph on the cluster. I’m not sure why this is but it may be on purpose due to the common requirement for speedometers to never read lower than true speed. Indicated speed is required to be within -0% / +10% + 4km/h of actual speed in the EU. Technically we could correct this by doing two vehicle speed calculations on the Arduino, one for the odometer calculation using the proper scaling factors to keep it accurate and one for the displayed vehicle speed using a scaling factor which corrects this error. I do not intend to do this but I have found a scaling factor of 99 rather than 100 makes the speedometer match up if required.

As ever your mileage may vary, but hopefully it’ll closer than it was before! As I said earlier this is part of another major overhaul of the code and I’m intending to publish the final version as a complete code when I’m happy I’ve got everything working but that might take some time.

14 thoughts on “RX8 Project – Part 20, Canbus #5, RX8 Odometer”

    1. Hi,

      Thanks, there should be plenty more to come!

      A really long time ago I bought a Megasquirt 3 kit with the extra IO option for this project. I know it wouldn’t be everyone’s choice but at the time it was the cheapest way to control 6 cylinder sequential injection, coil on plug and dual VVT (and do various other things) at the same time I could find. Plus for most people the idea of soldering together a kit like that would be a nightmare! There are quite a few complexities to it and places to go wrong so I’d certainly not suggest one as a beginner project!

      Obviously it’s not been used in anger yet but other than a few odd design choices in the unit (like installing the CPU board on a stack of chip sockets as a riser) it seems like it should be reasonable and should be fine.

      Jon

      1. I’m running an MS3X on my current racecar and I have a second on the shelf waiting for anther project. I assume at some point you’re going to set the Megasquirt up to broadcast its values to the Arduino over CAN and then have the Arduino translate those to the dash, no?

        I have also seen a couple people that were able to set up the Megasquirt to listen for the ABS module to send all four wheel speed sensor speeds over CAN to enable traction control in the Megasquirt. That would be much easier than hijacking the physical speed sensors and routing them through an intermediate VR conditioning board like I had to do on my M3!

        1. Hi,

          That’s good to know, sounds like it must be working well for you if you’ve bought another one? I’ve been a bit concerned because I’ve not actually done a custom ECU project before and starting on this one with every option may not have been sensible but it can only go so wrong right? In the mean time I got offered a Speeduino for a good price and have built that up for my kit car, it’s considerably more basic but should do the job since it’s a 4cyl with wasted spark. Unfortunately on trying it I realised the inlet manifold (which someone hand made out aluminium sheet many years ago) had finally distorted so much even the 6mm thick gasket wasn’t good enough to seal it anymore and it was excessively lean. Long story short sorting it out either involves a custom flange being designed, made and welded or replacing the whole intake, fuel rail, etc..then winter happened and the whole thing went on hold!

          Yes that’s exactly what I plan to do! Also depending how flexible the Megasquirt is it should be fairly simple to directly use the ABS values although I think the way to do it is going to be via the 4B1 transmission because from the info I’ve found it’s the same data as the 4B0 values I use for the speed/odo etc (both come from ABS) but simply scaled slightly differently and with a much higher frequency (by about a factor of 10) which might be more useful for traction control. Worst case because I can capture the ABS data on the Arduino I can always just retransmit it in whatever format I need for the Megasquirt.

          In a similar vein I’m planning to read the RPM value from the Megasquirt and use it for the cluster and similarly the coolant temp (as questionable as the indicator on the dash is it’s at least a reasonable indication of broken vs not broken!) and also all the relevant warning lights if I can like the main engine warning, traction control etc. I can probably come up with some other creative ways of (ab)using the system – I’m wondering if I can use the speedometer on the dash to display the AFR from the wideband.

          Something else to experiment with!

          1. Compared to what you’ve already accomplished here, I think the Megasquirt will be a piece of cake for you. I’ve been running mine for six years now, very flexible and frankly more reliable than I expected. My only issues have been dying ignition coils (used LS coils off of eBay) and dying oil pressure senders (no-name brand off of Amazon). The msextra forums are very helpful, lots of knowledge there.

    1. Hi, thanks!

      As I mentioned in the post I’m currently still testing various bits I’m developing so much of it is still a bit experimental but if I get chance over the coming days I’ll tidy up what I have and do a post on it while I keep working on it. Out of curiosity what’s your project?

      Jon

  1. Hey Jon I’m back – I’ve bought a 2009 RX-8 and I have a 3.7L V6 from a 2012 Mustang that I want to put in it.

    I just received the HobbyTronics CAN module you recommended from the UK and would like to duplicate some of your efforts here to see if the S2 gauge cluster is the same as the S1 that you have.

    If I can get ABS, steering, and the gauge cluster to work without using the stock ECU for anything that’d be ideal.

    The original engine is still in place and runs, what kind of data would you recommend trying to capture before yanking everything apart?

  2. Jon I’m bugging you again.

    Have you thought about maintaining cruise control? I’d like to be able to keep cruise functional. Do you think that if the stock ECU stayed connected and that the stock DBW throttle body was used on the new engine, that factory cruise could still work? I assume you’d have to feed the factory ECU an RPM reading so it would see the engine running.

    1. Hi,

      Sorry for the delay, I’ve had a lot going on lately so haven’t really had chance to do anything on the car. If I get a moment tomorrow I’ll try to get my test code posted here somewhere for you to work from. Hopefully it should just work but for the steering and all gauges etc but it’ll be interesting to hear how you get on. The ABS light on mine is still on but it stays on with the stock ECU – some of the forum posts I’ve seen say it takes some driving to clear that one but with no engine I can’t do that so currently no idea.

      If you’re looking to log data out the more you can get the better as you never know what might be handy. If you can be bothered try to log everything while noting what you’re doing (do idle/running/pressing button X/etc). One particular thing that’s interesting is the exchange between the immobiliser module and stock ECU (you’ll probably have seen my hugely long post about making this authenticate ok to clear the warning light) but you can do that without the engine in place.

      In terms of the DBW throttle I’d never considered using the stock ECU to make it work but the problem is if you connect it to the CAN bus you’ll have conflicting data since the Arduino is sending fake ECU data to fool other hardware if you add it back in all sorts of weird things will happen (gauges flickering etc). You can’t feed the ECU an RPM signal because this comes from the ECU in normal operation so in effect it would be broadcasting 0RPM and the Arduino will be broadcasting whatever is being translated from your other ECU if you take that approach (assuming you’re going to try to reformat other data such as from a megasquirt ecu) and you just end up with you dash flickering between the two.

      Actually directly driving the DBW throttle is comparatively simple as it’s usually just a motor with a return spring so the % open is just controlled by a PWM signal. I had a preliminary go at this with another Arduino reading the RX8 pedal (just a pair of variable resistors so voltage = position output on each but two for redundancy) and a MOSFET and it works reasonably well, that said I think it might need driving both open and closed for performance rather than relying on the return spring but that’s another experiment. I haven’t decided if I’ll go that way eventually or just use a cable throttle.

      Sounds like yours ought to go well with that lump in it!

      Jon

      1. I see what you mean about two sources trying to broadcast the same data causing confusion. What I’ve seen a lot of the off-the-shelf RX-8 swap kits do is leave the factory ECU in place and then add a trigger wheel to the new engine and bolt up the RX-8 crank sensor so that the RX-8 ECU thinks its engine is still there. That’s usually enough to manage getting the HVAC and steering working, and I’m wondering if it would keep the throttle moving, and by extension allow cruise control, too. I haven’t seen anyone get cruise working, and that’s a must-have for me after my last race car/daily didn’t have it.

        The DBW has always worried me a bit, as although it’s simple in theory, the OEMs do some smart self checking to ensure the signal is valid and that if it fails, it fails in a safe way. There is a standalone DBW controller made by a third party that has been integrated into the Megasquirt and its tuning software: https://www.dbwx2.com/

        There’s quite a long thread on it here:
        https://www.msextra.com/forums/viewtopic.php?f=67&t=70530

        My engine came with an upgraded throttle body that retails for $480, and the RX-8 has a DBW pedal, so the most straightforward method of throttle control would be to implement DBW. But adding the cost and complexity of the DBW controller would make everything much more expensive than selling my fancy throttle body and buying a simple cable body for cheap.

        Decisions, decisions.

        1. Hacking in the old ECU seems a pretty sketchy way of making stuff work but it might. To be honest these “clever” systems really aren’t as clever as they my suggest.
          The checking is normally basically just using redundant paths – the DBW pedal is just two resistors and the ECU compares the values from each to see if either channel has failed. From what I’ve seen of the throttle bodies they still have a return spring to make sure if everything fails the throttle just shuts regardless. The throttle valve feedback is monitored in a similar way to the pedal to look for mismatches or open/short circuits based on the voltage on the resistors and in the event anything doesn’t match it just stops driving the throttle valve. That DBWX2 is a nice looking unit but has additional functionality you don’t need such as controlling dual TB’s.

          I can’t see any reason you couldn’t build a suitable controller as an add-on to the Leonardo board you have with a bit of thought and some electronics, something like an L293 or L298 dual H bridge driver would probably do it. I might have to give it another go when I get a minute as I’ve got a spare pedal and all the electronics about from last time. I’ve actually been working on another CAN device which includes two CAN interfaces lately. It’s basically an amped up version of the Hobbytronics Leonardo CAN with some extras with the idea of being able to sit it between two buses and convert data on the fly or edit certain bits for disabling warning lights and things. I got the PCB’s the other day and have a pile of bits arriving next week so we’ll have to wait and see if it works! Maybe a cheap DBW expansion should be my next development! I’ll do some more research into the cruise wiring as well and see if I can come up with anything, I’d not considered it before because my car doesn’t have the buttons on the wheel for it anyway.

          Related to this you will see my latest post includes my current work in progress code here which you asked about recently. If you want any of it clarified just message me.

          Jon

  3. I see the message from ID 0x4c0 matches bit 1 of 0x420. is there a possibility that the odo increments originate from 4c0 (which I assume is sent by the ABS module) instead of the PCM/0x420?

    I have a 2010 RX8 GT and can confirm that the IDs are the same. I have a few additional ones I havent figured out (car doesnt run so I have to get creative): 0x274 (only seen when key is in the ignition but not “ON”), 0x218, 0x4ec, 0x90, and 0x85.

    1. Hi,

      Both 4B0,4B1 and 4C0 are all data from the ABS as far as I can tell mostly based on knowing and 4B0 and 4B1 are both the wheel speeds in slightly different formats. 4C0 I’m not fully sure about the purpose of but on the cars I’ve seen it only occurs rarely (about a factor of 10 less than 4B0 and about 100x less than 4B1) but the dash ignores any data with these ID’s. Normally 420 is broadcast by the PCM (I’ve checked this) and this is the only ID the dash accepts from all the the ones I’ve tried. I think all that’s going on normally is the factory PCM (ecu) is doing the same as the code does and sampling either 4B0 or 4B1 and then averaging the speeds for the four wheels to get a overall speed for the car.

      Certainly on the data I have the frequency of changes and the values for 4C0 and 420 don’t line up as far as I can tell. There’s also an image of a page of a Mazda technical manual I have which shows “travelled distance” as an output from the PCM and input to the cluster which seems to confirm the theory.

      Your extra fields are interesting and I’d be happy to add them to the information posted here if you manage to work out what any of them do. Unfortunately I don’t get any of those on my car or in the logs I’ve seen from others so I imagine they’re things that were added during the facelift or just higher options neither me or the guy my other logs came from have so to work out they’re purpose I’d probably start with those sorts of features. I know TPMS was connected to the CAN and I don’t have it so that might be one of them. Also if yours is an automatic there’s a TCM module which manages the gearbox and so sends out things like “idle speed up” commands to the PCM (ecu) and a whole host of other data (ratio/shaft speed/torque…) . Mine being manual I wouldn’t have any ID’s for this. I suggest live displaying the data values for all the ID’s if you can then do things like change gear and see if they change.

      It’s great to have more information, please record everything you find out and let me know!

      Jon

Leave a Reply

Your email address will not be published. Required fields are marked *