My first build: CAN-Bus and GPS through Bluetooth LE



  • @aol I think I got it.
    So far I use only single CAN-Bus PID because I compress multiple data in a single packet. I implemented the canBusFilterWriteCallback but I ignored what´s written in the canBusFilterCharacteristic, because for me the PID selection was needless. I didn´t think about it anymore because there was no problem when I configured CAN-Bus alone in RaceChrono. But in order to get it work together with GPS, it seems that I MUST NOT send any CAN-Bus data until RaceChrono has written the canBusFilterCharacteristic.
    When I wait for this event to happen before I start sending my canBusMainCharacteristic everything works perfectly.

    One more question: In canBusFilterWriteCallback RaceChrono writes a value of 50 for the notifyIntervalMs, in other words 20Hz. Is that a limit that one should stick to? Is that value used somehow inside the App?

    Anyway I´ll try how far I can push the overall update rate until packets start to be lost.
    On GitHub you wrote: "The characteristic UUID 3 in notify mode (used for GPS data) will halve the performance of the characteristic UUID 1 (used for CAN-Bus data)."
    I think this is not true in general. It is possible to update multiple characteristics in the same connection interval. When they are not synchronized but written at different times, they will definitely occupy one connection interval each and thus half the update rate.

    If you are interested, I can share my findings about performance if I have any in the future.

    Best regards,
    Josef :)
  • @GiuseppeBinomi It may well be that my state machine breaks if you do something unexpected. You can ignore the 50ms, it's more related to the device design, not the app receiving the data. And sure, I'd like to hear all the performance tips you have for the nRF*!
  • edited February 2020
    @aol I had some thoughts about the GPS update rate. My current module is a u-blox CAM-M8Q ( It has a maximum update rate of 18Hz with GPS alone and 10Hz with combined GNSS systems.
    The time delivered with the $GPGGA sentence has format meaning the smallest accuracy that can be reported is 10ms. The GNSS module synchronizes the measurement timestamps to the top of a second. This means with n Hz update rate each n-th measurement will be aligned to a full second. So with 5Hz rate, one measurement is exactly aligned to a full second of utc time and the next 4 measurements have a distance of 200ms between each other, so the 5th measurement is at a full second again. Here is no limitation of timiestamp accuracy because the distance between measurements is always a multiple of 10ms.
    So let´s assume I use an update rate of 18Hz, one measurement is at full second, the next is 55.55ms away, but NMEA senctence can report either 50 or 60 ms. Am I right that I add a systematic uncertainty to the lap-time calculation using update rates wich can not devided by 10ms?

    However I did a lot of tests to improve the BLE update rate and at least in my case I found it is best practice to send all characteristics at the same time (GPS first, then CAN with no programmed delay). For all the tests I had my GNSS module configured at 10Hz, with fast reading (115200 baud) I used each new complete data set as a rough "clock pulse". I created a CAN channel that toggles between 0 and 100% with each BLE notify call and I did the same for the count of satellites (between 10 and 11). This way I get two nice zigzag patterns in the data recording. I can see where a packet was lost (if any) and I can analyse the distance between the timestamps of GPS and the CAN-Bus data as stored by RaceChrono. Sometimes there is no visible distance, usually its only 1 or 2 ms. In this case you have to assume that both characteristics have been read within the same connection interval. How does RaceChrono determine the timestamp of the CAN data? Is it just measuring the time elapsed between reading GPS and CAN?

    For the next test I doubled the CAN data rate by sending them alone between the "combined" update. Then I "doubled" the GPS rate virtually by just sending the same packet but with 50ms added.

    This way I successfully tested 10Hz GPS + 20Hz CAN, 20Hz both, 10Hz GPS + 30Hz Can and 30Hz both.
    For 30Hz you really have to care about time between the same characteristic being re-written. It looks like the read access from the Central can overlap with the write attempt and then the function can take several ms.
    At 40Hz both it was not stable anymore, the update rate was around 37-38 Hz most of the time, sometimes had break-in to only 20Hz.

    I think that's pretty impressive considering that BLE was not designed for high data throughput or fast update rates.
    I´ll have a real 20Hz GPS module in my final version. I'm curious how it will perform against the official timekeeping :)
  • edited February 2020
    Interesting research, thank you!

    Q: "Am I right that I add a systematic uncertainty to the lap-time calculation using update rates wich can not devided by 10ms?"

    A: Well, yes, if the sample is not taken at even 10 ms. But you'd need to do some statistical analysis to see if they take samples at equal 18 Hz (or 18.2 Hz, I think the update interval is 55ms), or even 10 ms... But I guess all this is easily lost in the noise as consumer GPS are not THAT accurate.

    Q: "How does RaceChrono determine the timestamp of the CAN data? Is it just measuring the time elapsed between reading GPS and CAN?"

    A: Good question. I created this originally as standalone CAN-Bus device, so I did not put timestamp to the message. It should be there of course if the device has GPS too, so the data would get properly timed against the GPS data, and eliminate the exact delay of the used GPS chip.
    It currently times the CAN-Bus messages on reception, using a non-trivial algorithm against GPS time, same as used for OBD-II in RaceChrono.
  • Hello, my test was almost successful.
    The RPM, air temp, water temp and TPS are refresh at 16.7 Hz but the gearbox rapport and the oil pressure are not or slowly refresh.
    I did not found those values in Fast or Slow channels.
    I saw an option "Unrestricted custom PIDs" is it the solution ?

    I am very happy with this feature, it is very easy to implent with a DTA ECU.

    Best Regards
  • edited February 2020
    @CedTurbo Not sure why you're experiencing problems with gear/oil pressure, but the OBD-II settings (at least for the most parts) do not affect this "CAN-Bus" API. Fast/Slow channels nor Unrestricted custom PIDs affect only OBD-II readers. RaceChrono records all CAN-Bus channels it has configured and received.
  • OK, I have to check with my analyser to see if all PIDs are sent at the same rate on the ECU side.
    I made some screenshots but I can't share on the forum.
  • @CedTurbo you can email screenshots to tracks(at)
  • @CedTurbo I got your email, thank you for screenshots.

    The code on RaceChrono app side is very simple, all channels are recorded the same. If you get value some times, the app itself should be functioning properly.

    So I think the problem is at the reader device side. Maybe there's a bug in my code... if you're using my code. If not then your code? :)

    Or maybe you're not receiving this packet from the CAN-Bus for some reason? I suggest you add some debug output to the code, maybe first to the packets that are sent to the app, to see if the problem is visible there too.
  • @aol I did some measurements only software for the moment.
    I use your last code from GitHub.
    I put the PacketId variable on the Serial Monitor and I wrote a filter to show only my PID (8192 to 8196) and I saw only even numbers.
    I have to use an easier program for to see if the filters are the problem.
    I don't know if I can use my Saleae analyser for that.

  • edited February 2020
    @CedTurbo That spot where you inserted the debug output (emailed screenshot), is before filtering, so it should output everything that is received from the bus in the range you set.

    1) Are you sure the these PIDs are transmitted on the bus?

    2) Do you have the resistor jumper on or off? You can see the jumper ON the MCP2515 board here . It's for terminating the bus, and It will affect reception!
  • @aol I do not have resistor activate on the device, I have checked (60 ohms between H and L). The CAN-Bus device is between the ECU and the DashBoard.
    For me, the PIDs are transmitted on the bus, I see the values on the DashBoard.
    I will try to use another program with another library. Maybe when we parse the packet ?
    Thanks to you to help me.
  • @CedTurbo Yep, I don't know, really. It might also be that the MCP2515 just gets too many packets and they are dropped. My build only filters the packets in software. You could try to set a hardware filter that is simply a mask:

    I noticed I didn't mention the libraries you're supposed to use on my ...
  • edited February 2020
    @aol News
    I use the good library (Can.h)
    I checked the device with another library and I used filters.(0x2000 to 0x200F)
    The result was very strange, I saw 0x2000, 0x2001 and 0x2007 without problem but impossible to see 0x2003.
    If I filter only 0x2003 => perfect, when I add another filter the value appears but with a third filter I loose data.
    My CAN-Bus baudrate is 1000k with a 8Mhz crystal on the MCP2515, I think that the device is too slow and miss some data.
    I have ordered a 16Mhz crystal to test.
  • @CedTurbo It could also be that the link between MCP2515 and Arduino is too slow for some vehicles with high bandwidth usage.
  • Hello, I changed the crystal to 16Mhz and the pb still the same.
    I have to investigate more (SPI connection ?)
  • edited March 2020
    @CedTurbo the Adafruit nRF52 on my example build runs at 64 Mhz. But cannot tell if that would help or if the bottleneck is with the bus between MCP2515 and the Arduino. If you want to use SPI then you'd have to use the old $RC2/RC3 API.
  • @aol Hello,
    When I said SPI, I though the connection between the MCP2515 and the nRF52.

    I tried to set filters in the code but it does not work.
    void canBusSetup() {
    // Ininitialize CAN board
    CAN.setPins(28, 29);

    I used the same syntax like in the other library, maybe I made a mistake.
    If you know how to set it, your help will be appriaceted.
    I am not able to change your code with the other library because it does not have the CAN.parsePacket command.

  • edited March 2020
    @CedTurbo cannot tell why it doesn't work without trying different stuff. Maybe try removing the filter for starters, and check the resistor jumper. The API usage is here:

    Notice there is a clock speed setting for SPI separately. Maybe you could make it faster?

    PS. Yep, I thought you meant Bluetooth SPI (RFCOMM), these acronyms are hard...
  • @aol hi, firstly thank you for your git hub code I was able to get my imu arduino board talking to rc via ble, however, I was wondering if you are thinking of supporting bleuart so that we can send the rc(x) sentences or if you are going to extend the configuration of the characteristics to support IMUs and or analog inputs?

  • edited May 2020
    @JmQ I've supported BLE UARTs for several OBD-II readers, but from my experience they are really low bandwidth. Barely enough for the OBD-II use, which is much less than RC3. I doubt it would be useful at all, as $RC3 sentences waste more than half of the bandwidth by being clear text. I recommend trying the CAN-Bus API on BLE: . There's no restriction of what data you transmit over it. You can define the channels as IMU if you like.
  • looking into it now thanks
  • Hello everyone,

    There's a us company that provides tiré température sensors :

    Altough it's quite expensive i may consider to acquire such a kit. Thé raw data will bé outputed in can-bus messages. Is there some robuste hardware as of today to provide thé gâteway between can-bus and bluetooth ? And thén they show a very nice overlay of tire temp on their vidéos. Is anyone aware of an open source program to generate this vidéo that could at some point bé integrated as overlays in RC ?

    I understand there's probably a lot of work yet undone but i'm asking just in case !!

    I also wrote to this company asking similar questions and will update thé post with their answers if any


  • @aol's git hub at the top of this does exactly that its a canbus ble bridge to race chrono.
  • edited May 2020
    There's also a dedicated API for temperatures. Also RaceChrono already does the visualisation. Please check it out: . You could build this exact project, or use the same API to provide the data from your own sensor.
  • Thanks a lot for providing an API for DIY devices! I've recently created a CAN reader for my car based on your example, and with a few tweaks it gives better results than OBDLink MX+. Here's a demo video:

    I need to figure out a couple of things and clean up the code, and then I plan to post it on GitHub.

    Based on my own experiments I've noticed a few things that can be improved in your example:
    1) You're not accounting for the RTR bit when receiving messages. You should ignore RTR messages.
    2) You're using pins A4 and A5 on Adafruit nRF52832 Feather for SC and INT, but the pinout for this feather specifically calls out that A4 and A5 should only be used for low frequency I/O. For my project I use A1 and A3 instead.
    3) Instead of re-sending packets over Bluetooth as soon as they are received, I put them in a map keyed by PID, and keep reading them from the MCP2515 until RX buffers are empty to prevent RX overflows. When the RX buffers are empty I pick the latest packet per PID to send over Bluetooth. This has greatly improved the MCP2515 stability for me, without noticeably sacrificing the refresh rate.
    4) The CAN library available in the Arduino IDE is surprisingly ineffective. I've significantly optimized the communication over SPI in my fork, along with a few more things, see
    5) You can use MCP2515's built-in filtering functionality to further improve performance and stability. My fork of the arduino-CAN library allows setting the filtering registers directly.
  • edited August 2020
    @timurrrr Very good, thank you for the improvements! They all make perfect sense to me. Would you mind doing a Pull Request to my repository? I think your improvements should be included, and since you're using the exact same hardware, there's no reason why they should not be committed.

    I'm planning a new build based on Adafruit Clue, and it would be great to base it on your improvements and your fork of the CAN-library.
  • As I had some issues with the MCP2515 breakout board I got from Amazon and had to do a lot of debugging, I ended up restructuring and rewriting most of your code for my project :lol: so creating a pull request is not trivial at this point.
    I do plan to open source my project, just need to find some time, and there's something else blocking me right now — working on resolving that.

    Interesting pointer about Adafruit Clue. Probably overkill for me, and slightly expensive for other folks given that most of its features aren't really useful in a car. Also makes connecting to the CAN bus harder? Doesn't seem to have a lot of I/O.
    I guess the biggest value is the onboard accelerometer? As far as I understand, for an accelerometer to be useful it should be mounted as close to CoG of the car, and also aligned fairly precisely. At least in my car (Subaru BRZ) it's hard to put an accelerometer close to the CoG while maintaining access to the CAN bus. Instead I can read the built-in accelerometer via CAN — personally I'd stick to that.

    For my v2 build I'm going to use Adafruit ItsyBitsy nRF52840 Express, as it's more capable that Adafruit nRF52832 Feather yet smaller.
  • @timurrrr I don't mind a PR that changes everything :) This was after all originally written in couple of days originally. I bet you made it much more polished.

    Adafruit Clue should have enough I/O connectors (the flat connectors at bottom) to connect CAN-Bus and GPS. I chose the board because I want to experiment with external display stuff and 9-DoF, and it has both.
  • I've started with factoring out the RaceChrono-specific bits into a reusable library with examples:

    Currently there's only one example that prints out the commands that the DIY device receives from the app, but I plan to add more.

    Using this demo app it's easy to see the bug I've reported previously: when opening the PID editor, RC sends two "ALLOW ALL" commands instead of one "DENY ALL" followed by one "ALLOW ".

    In the foreseeable future I'm focusing on the CAN-bus support only, as unfortunately, I don't currently own any Arduino-compatible GPS boards. Donations are welcome :smiley:
Sign In or Register to comment.