100hz from CanBus

My current project is to create a compact, low cost unit for next season on the track.

For context, I'm coming from this stack currently:

CANBUS Source: 2016 BMW S1000XR
CANBUS bluetooth transceiver: Obdlink MX+
GPS / RC2: Arduino Mega, HC-06, MATEK M9N-5883 (UBLOX NEO-M9N 25Hz 4 GNSS), various sensors (wheel IR temps, 9 axis sensor fusion)

In my current setup, the Obdlink MX+ can approach 50hz+ on 5 monitored parameters (spread across 4 PIDs). Most of the PIDs on the bike fire every 10ms.

I'd like to get as close to the max rate of 100hz on all PIDs as possible with its replacement.

Based on the forum discussions, it looked like the DIY BLE build is the new direction for hardware development.

Accordingly, I built a Adafruit nrf52840 based DIY BLE device. With just canbus, I was getting ~15 hz update rate on the same 5 pids (which is inline with the listed rates that people are getting with the base code)

To try to get higher rates I started tweaking the code:

>Dropping the shouldnofity and marknotify checks (minimal speed increase)
>Changing the Advertising set interval to the minimum my phone can see (minimal speed increase)
>Taking the 7 individual bytes across 4 pids, storing them in vars as the packets come in, and advertising a single "composite" pid with all 7 bytes (stale or just updated) to minimize notifies + modifications on the phone side to accommodate. (~ rate increase to 35 hz)

So yeah after a couple rounds of experimentation I feel stuck: 35hz is much lower than I thought I would be able to get.

(excuse the ignorance incoming, very new to BLE / nrf52840)

Question 1: Is BLE the way to go for high speed (will I ever see speeds as high as 100hz)?
I'm thinking not, as some of what I am reading is that the minimum notify interval anyways for android is 7.5ms (where as, in testing with my actual android phone, I am unable to see anything with an advertising interval below 20ms)

Question 2: In timing the program, the majority of the delay is in the notify (anywhere from 0 - 50ms). Is this my phone being slow, the nrf52840, or BLE? Why is there so much variability?

Question 3: Is there a way to make a obdlink analog (for lack of term)? Can i use an arduino, mcp2515 and hc-06 to serial stream the 4-5 PIDs i need? I tried viewing the raw canbus from the obdlink with ATMA but kept gettings errors. Also don't have the right google term for the raw CANBUS bluetooth stream format. Is it just serial / hex / what is it?


  • BLE has limited total bandwidth, yes.
    Have you seen https://github.com/timurrrr/arduino-RaceChrono/blob/master/examples/SendSomeData/SendSomeData.ino ?
    With a few simple tweaks you can turn it into a stress test how many different messages you can send.

    One possible optimization you can do is group data from different CAN IDs into a single message, and send those messages over BLE.

    On a higher level, may I ask why you need 100 Hz? I'm logging most data channels at 15–30 Hz, and in my experience this has been more than enough — with a notable exception of brake pressure that I log at 50 Hz.
  • The nRF boards seems to have some limitations on the transfer rate. I hope to test ESP32 boards soon see if they're any faster to send.
  • edited October 2021
    You have some ESP32 now ;-)

    There are multiple ESP32 BLE libraries to choose from, wouldn't be surprised if they differ in what they can manage?
    I've tried these (no load tests though)
  • edited October 2021
    I didn't see your send example (I should have taken a closer look at your github) but did write a similar tester program which resulted in a highly variable update range that fluctuated wildly between 75Hz to 700Hz. Either way, I was still seeing the highly variable "notify" times of 0ms to 50ms which would prevent me from hitting my goal.

    I also did group my data into a single message, as detailed in my original post, which did improved my rate from ~20Hz to ~35Hz.

    As for why 100Hz: it's the native rate of my motorcycle and I'd like to capture detailed traction control / ABS data.

    Also, as an aside, I want to thank you specifically for your canbus library fork with fixes / implementing of the filters correctly. Quick note for others trying to use his racechrono ble fork: its clearly labeled and the code is set to use 16Mhz crystals. Change the code accordingly if you are using the 8Hz crystals (more common with the cheap shields).

    Since I was seeing such variability with BLE. I started down the path for building an OBDLink MX emulator with an arduino, MCP2515 and HC-05. I have it working now but have some questions:

    I am hitting 100Hz (verified with the obd device output file and my debug serial output to my computer) but there seems to be a weird buffering / parsing issue and racechrono is only reporting 40Hz.

    1. Can I send you and device OBD file for you to look at? I also uploaded to pastebin (https://pastebin.com/8J3EvHAp)

    This is a single fake PID 320 that I am updating with various bytes from other real PIDs on the arduino side.

    2. What generates a new line in the obd device output file? Does the new line in the obd device output mean anything for racechrono? Does it only parse one canbus message per line?

    After each CANBUS message, I send the ASCII char 13 (carriage return) as specified in the protocol (ATL0)

    The OBD file, though, shows a bunch of CANBUS messages on the same "IN" line.

    I think that this is ok, as the OBDLink also shows similar behavior, but only 1-2 canbus messages per line. My device is updating much quicker I think and dozens of canbus messages are "accumulating" on the same line. Is that OK?

    3. What are the carriage returns that racechrono sends occasionally after the configuration stage? Should I be doing anything when I see them?

    4. Is there a way to just have racechrono start accepting canbus data without the AT commands replies? Currently I have an "answer file" that replies to the ATZ / ATL / STM etc but it is all hardcoded / very rudimentary.

  • Wait, which protocol do you use to send data to RaceChrono?

    Are you saying you're encoding data into ASCII strings and send those strings over BLE? That sounds very inefficient.
  • > As for why 100Hz: it's the native rate of my motorcycle and I'd like to capture detailed traction control / ABS data.

    That makes some sense, but I still don't fully understand what you need that data at that level of timing precision for. Can you elaborate further?
  • @cohberg I don't think the ELM327 protocol is a right choice for creating a DIY device... I mean it can be made work, but I would not recommend it, and I'm not prepared to support such use case.
  • @timurrrr

    I'm apparently using the ELM327 protocol (which until now I thought was for OBD only) and sending canbus packets via serial port emulation / Bluetooth RFCOMM at 115200 bps, not BLE. The arduino is programmed to act like an OBDLink MX bluetooth dongle (except it also combines multiple PIDs and their relevant bytes into a single PID)

    100 Hz seems to be a real rubbing point for you. Here it is simply: I don't want to lose any data and I want to do it for the sake of doing it.

    I already have another arduino module that logs the entire canbus at near line rate (dozens of pids at 100hz, 10hz etc). It is just very painful to process that data (either via excel / or visually via dashware) + no timing data. I'd like to sit in the pits and view the data from my bike easily vs use a laptop and excel.

    I've picked up tenths or even ~.5 seconds in certain places by adjusting my brake point / pressure on corner entry by watching ABS / rear wheel lift detection between sessions to nail a difficult corner. Same with traction control values on exit. This app is a far better coach than any person because it is not subjective, but instead entirely data driven.

    Am I hurting myself with higher update rates for the various parameters? No.

    Am I taking advantage of the higher rates? Probably not fully, but I have seen momentary TC and ABS spikes that would have otherwise been missed at 25Hz. Motorcycles are a bit more sensitive than cars: our traction to no traction margins are smaller. (This is coming from a fellow 86 owner who lives in a wet area.)

    Racechrono is just a means to attaching timing data to the canbus messages. Since there isn't much interest in higher rates, I'll likely move to adding a GPS source to this data logger and attaching my own timing data to the canbus messages (currently relying on 10ms between frames which leads to drift).


    If that is the case, is there a timeline on RC4 messages? If there isn't, can this serve as a "vote" for RC4?

    Also wouldn't it be cool to be able to build our own canbus Obdlink MX for 1/4 of the price?

  • edited November 2021
    @cohberg Did you code your Arduino based ELM327 from scratch or did you find any code on for example github as a base? Since I have a few extra of my own RejsaCAN ESP32 boards with Bluetooth and CAN I thought I'd try to make an ELM327 clone out of one just for fun.
  • @MagnusThome It's from scratch but very rudimentary in its current state.

    It's also likely to change quite a bit as I create the real ELM327 protocol subroutines and do more reverse engineering to see how racechrono interacts with it.

    Right now I am using a static "answer file" that I'm using to get past the configuration interrogative stage. It doesn't actually have logic to respond to the various AT commands but rather knows what answers to reply to based on the beginning "ATZ" request from RC.

    Also there is the major issue of it not being parsed correctly by RC currently (update rate reported in the app far below what i'm sending and the rc android device is logging).

    I've got some other HC-05 modules on order with the better 3.0-20170601 firmware that I think may fix some of the (suspected) buffering issues that I see in the Obd device output. I'll test this weekend and report back.

    I also wonder if faking a Obdlink MX Wifi is the way to go ... tons more bandwidth but doesn't look like we can get any developer support for this path.

  • A text based protocol is never the best choice for maximizing throughput.

    But since an ESP32 plus a CAN transceiver chip is less then 10€ I thought somebody would have made an ELM327 clone 🙂 AFAIK it is not a very complicated repackaging of request and reply data it does? It would be cool to do a clone, we'll see if I look into it more just for fun.

  • edited November 2021
    @cohberg RC4 is on my to-do list, but also is not priority because it's RFCOMM which is not supported by iOS.

    About the ELM327, make sure to abort your output often, and let RaceChrono do another request. The linefeeds are supposed to do that (abort), but you can also do it proactively.

    The ELM327 protocol is not very nice, and not even the ST extensions help much (the company that makes OBDLink). Would be nice to have a protocol that would support CAN-Bus monitoring while polling for OBD-II.

    Also yeah, the data needs to be binary (in perfect world). CAN-Bus can transfer 512kbit/s, and if you encode everything in hexadecimal it's already 1024kbit/s + any overhead. No way any Bluetooth can do that reliably.
  • Just a clarification, I was thinking just having it reply on queries for general OBD2 data, nothing more.

  • edited November 2021
    @MagnusThome Yes, for your project it makes sense, sort of. You can support other apps as well like that. ELM327 protocol is nothing special in theory, but please note there's a lot of border cases! One app may use ATH1 and other ATH0 etc, and theres many other commands to control such modes. Also you need to support weird things such as multi-frame responses, and display them correctly in all the modes.
  • Only aim it to work with Racechrono. Just because it would be cool 😁😁😁

  • edited November 2021
    Then it's easier... My "dream" would be to extend the current CAN-Bus protocol to be able to poll OBD-II as well... I think that a single device being able to do both at the same time would be even cooler :smiley:

    Reverse engineering CAN-Bus is hard, and you don't always find everything you need... So polling OBD-II would complement nicely.
  • Well, you decide the combo-protocol and I'll implement it on the ESP32? ;-)
  • @cohberg Thanks for the detailed response, your use case makes more sense to me.
    I wasn't particularly against 100 Hz, I just wanted to better understand what exactly you need it for.

    Are you trying to log everything at "native" rate, or have you considered rate limiting data channels other than ABS/TC and wheel speeds?

    Totally with you that "data available in RaceChrono right after the session" is miles better than logging the data to an SD card and having to manually import it...
    +1 to data analysis vs subjective coaching, to an extent.

    > In timing the program, the majority of the delay is in the notify (anywhere from 0 - 50ms).

    Have you set up the device for max performance?

    I'm no expert, but I ended up with this code in the past:

    > there seems to be a weird buffering / parsing issue and racechrono is only reporting 40Hz.

    What device do you run RaceChrono on?

    > Also, as an aside, I want to thank you specifically for your canbus library fork with fixes / implementing of the filters correctly.

    You're very welcome :)
  • edited November 2021

    >Are you trying to log everything at "native" rate

    Yes, I don't want to lose any data on any interesting channel.

    Again, I am not interested in rate limiting anything. Its already working when rate limiting so why bother with this thread if we are just going to rate limit to fix.

    >Have you set up the device for max performance?

    Yes, have used your exact BLE build + RC library (minus the adjusting for 8mhz crystal) and also @aol 's original with setInterval testing, tx power at various speeds / power + the described mods for pid combining, removing the should notify checks etc.

    >What device do you run RaceChrono on?

    Have been testing the following:

    mega: ELM327 emulator bluetooth rfcomm
    esp32: ELM327 emulator, new wifi tcp mode (more on that later)

    > weird buffering

    The weird buffering issue was fixed with switching to an hc-05 running FW 3.0-20170601 for those following in the future. FW 4.XX is garbage: it seemingly waits until its buffer is full to send. You can't change behavior via AT commands (at least that I can find).

    > parsing issue
    > About the ELM327, make sure to abort your output often,

    I tested aborting on my own. But even before the aborts, the output would stop randomly in the obd device output logs (1-2 second gaps).

    I could not replicate this behavior with a serial monitor app running on the same phone. Payloads via the serial monitor app would consistently and constantly continue to come in at regular intervals without stopping for 10s of minutes of runtime. In contrast, the obd device output for race chrono would show 1-2 second gaps every 15 seconds or so.

    I can't confirm, but I think I may be filling up a buffer somewhere on the android side (android or racechrono).

    Either way, I switched yet again to a new hardware platform (esp32) and wireless transmission (wifi / tcp) to test out a theory.

    I'm seeing encouraging sustained high rate results with the "OBDLink MX Wifi" canbus device!

    Currently using an esp32 statically set to and sending data over tcp wifi. I then ported over the answerfile code from my mega and am seeing sustained rates at 100Hz!


    So far there are no weird parsing issues / dead streams.

    Super early so far but next steps are:

    Figure out if I go with an mcp2515 or just a transceiver. Likely just transceiver but looks like they may be hard to get via prime right now.
    More testing for max sustained rates
    Turn the ESP32 into a wifi station and broadcast a ssid (for the phone to connect directly to) and eliminate the testing wifi router
    Have it issue 1-2 DHCP leases for my phone and a debugging device
    Change from answer file to real logic for ELM327 protocol

  • >What device do you run RaceChrono on?

    @timurrrr Brain fart

    Running RaceChrono on Pixel 6 Pro (formerly Pixel 4XL). Tested on both Android 11 and 12.

    Non ios user (actively avoid) but have an ipad / iphone6s if something compelling is brought to my attention.
  • edited November 2021
    @cohberg quick comment before taking the time to read your message with proper attention; if you're getting 1-2s delays, there's a buffer overrun somewhere with 100% certainty. RaceChrono sends a new command immediately when ">" is received. If it's not received, and no inbound other data, it will timeout and send a carriage return (if I remember correctly).

    If you want to see what RC is receiving in reality, you can turn on the "Save device output" from expert settings. After that the recorded session will contain the raw output in a file. Just share the session as .rcz, rename to .zip and decompress the archive.

    I bet the ">" is missing before 1-2 s gap in the data :wink:
  • @aol This is not during the initial configuration (ATL / filters etc) but after, during the canbus streaming. For the initial setup I am outputing the > char and getting an immediate response during the initial config phase.

    I already provided obd device output in the original post. https://pastebin.com/raw/8J3EvHAp

    The stream will stop and pick up again like this (truncated snipit below), note the timestamps

    <--08:17:36.474 IN -->3200200107EFD24170F\r
    <--08:17:36.616 IN -->3200200107EFD24170F\r
    <--08:17:36.753 IN -->EFD24170F\r...truncated...3200200107EFD24170F\r
    <--08:17:37.559 OUT-->\r
    <--08:17:37.671 IN -->3200200107EFD24170F\r

    Please note that when using a serial monitor app there is no hiccupping like this, only in the race chrono logs

    Either way, it doesn't exhibit the same behavior in the wifi / tcp which is the focus of my current efforts
  • edited November 2021
    It timeouts because there's no ">" for the prompt, nor there's anything coming in between 08:17:36.753 and 08:17:37.559 (when it timeouts and RC sends a line feed). I'm pretty sure it's not RC that's dropping bytes, as the code has seen huge amount of testing. You're just slightly messing up something with the RFCOMM flow control. But I have no interest in arguing about this :smile:
  • >>What device do you run RaceChrono on?
    > Brain fart [...] Pixel 6 Pro (formerly Pixel 4XL)

    Haha no worried :)

    > Either way, I switched yet again to a new hardware platform (esp32)
    > and wireless transmission (wifi / tcp) to test out a theory.
    > I'm seeing encouraging sustained high rate results with the "OBDLink MX Wifi" canbus device!

    Huh, interesting!

    > Again, I am not interested in rate limiting anything.
    > Its already working when rate limiting so why bother with this thread if we are just going to rate limit to fix.

    Well, at least in my case I was able to get high enough refresh rate by using an nRF52840 and slightly rate limiting channels where full update rate wasn't needed anyways. As an example, I don't need to record my fuel level 50 times per second :)
    But sure, if you can achieve consistent update rate with no rate limiting, no data loss, and don't mind excessive session file sizes — why not.

    If you end up writing a library similar to https://github.com/timurrrr/arduino-RaceChrono/ to communicate with RaceChrono via TCP/IP from a ESP32, I might even update my DIY data logger to ESP32 and your library :)
  • edited November 2021
    @MagnusThome Please add an example of a hard coded OBD-II polling feature for your board, while monitoring CAN-Bus with the existing API, and I will add that to the API when I have the time.
Sign In or Register to comment.