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

I've just released my first build in open source. It's reference device to showcase the new Bluetooth LE "do it yourself" APIs for GPS and CAN-Bus data in RaceChrono Pro v6.0 (beta). Let me know what you think.

https://github.com/aollin/racechrono-ble-diy-device/
«134

Comments

  • I will follow this post, it's interesing for programmable ECU
  • Great work and good documentation!
    Thumbs up :)
  • Hello, I have a DTA ECU with CAN-bus, I try to understand your code => you make the choice of the IDs readed directly in the app ?
    I would like to build it this winter.
    Thanks
  • I'm not sure if I understand the question. The app writes to the UUID 2 to create a filter. This selects which packet IDs are read and which are just dropped.
  • Ok, I understand now. In the app, we see the CAN-IDs available and the filters are sent through the BLE. Am I right ?
    Your code is very nice but complicated for me.
    I read some documentations on the BLE Characteristics but I have also to understand the CAN Bus documentation.
    Somes years ago, I read the Serial Stream on a old ECU with an Arduino, it was easier. :)
    Thanks for your explanations, I will order the parts and try it.
  • Hello,
    I received my parts and I look at more your code.
    The Arduino IDE doesn't want to compile because the Adafruit's library has been modify.
    I made two corrections but I can't test it for the moment.
    1°:
    void canBusFilterWriteCallback(BLECharacteristic& chr, uint8_t* data, uint16_t len, uint16_t offset) {
    to
    void canBusFilterWriteCallback(uint16_t offset,BLECharacteristic* chr, uint8_t* data, uint16_t len ) {

    2°:
    void bluetoothStart() {
    Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
    Bluefruit.begin();
    uint8_t mac[6] = { 0, 0, 0, 0, 0, 0 };
    Bluefruit.Gap.getAddr(mac);
    to
    void bluetoothStart() {
    Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
    Bluefruit.begin();
    uint8_t mac[6] = { 0, 0, 0, 0, 0, 0 };
    Bluefruit.getAddr(mac);

    It compile now, I don't use the GPS'code.
    If you can look at and say me if I am in the good way.

    Best Regards
  • Probably just different version of the Adafruit libraries. I believe your change mirrors the functionality exactly. I will revisit the project at some point.
  • I will try on my car ASAP and keep you informed.
  • Hi everyone!

    I would like to briefly introduce my project to you. I have an old Kawasaki ZX6R (2002, carburetor) which has neither OBD-II nor CAN-Bus. Instead I collect my data using some sensors and an Arduino with Bluetooth LE. I have a 9-DOF IMU to read accelerations and calculate lean angle. By counting pulses (frequency measurement) I get the pretended speedometer value and RPM and calculate the gear form that. There´s an external GPS board connected to the Arduino, so I know the real speed and can send a corrected pulse signal to the speedometer. The throttle grip position can be determined by measuring the voltage of throttle valve sensor and so on... quite a bit of stuff already.

    Thanks to the documentation on GitHub from the first post, I was able to get all the Bluetooth LE stuff to work already! (I haven't tested GPS yet).

    So far I have 3 questions/remarks:

    1. OBD-II channels vs. CAN-Bus channels
    I see in OBD-II channels I can define a postfix, so for example I can separate suspension travel front and rear.
    Is that option missing for CAN-Bus channels? I couldn´t figure out how this can be done.

    2. Combined acceleration
    I know the combined acceleration is usually calculated by RaceChrono from the GPS data. As I can directly measure all accelerations very fast and reliable, I want to use it rather than GPS data. I can send longitudinal and lateral acceleration in separate channels, but how to combine them? There is an entry "combined acceleration" in the list of channels that can be created, but how is it meant to use?

    3. Bluetooth LE payload
    To increase the data throughput, I packed several data values under the same PID. For example Gear and RPM can be packed in only 2 bytes (3 bit for Gear, 13 bit for RPM/2). Throttle and Brake position need only one byte each and so on. You can put a lot of different data into a single 8-byte parameter. This way I achieve much higher update rate because I don´t have to send the BLE characteristic everytime for each single value.
    Actually a BLE characteristic can transfer 20 byte, 4 bytes are used for the PID, so 16 bytes of payload would be possible. Unfortunately only 8 bytes can be accessed in the channel. Is it worth considering increasing the bytes per chanel?

    Thanks for your great work and documentation!

    Best regards
  • Hi @GiuseppeBinomi

    Sounds like a great project! I've been thinking adding a dedicated IMU input API, but it will work through CAN-Bus API too :)

    1. I will check that there's no bug related to this. It should work just same as OBD-II...

    2. Combined acceleration is basically sqrt(pow(lateralAcc) + pow(longitudinalAcc)). You can easily add that channel, if you have both accelerations available.

    3. Yep, I don't see a reason why it could not be variable packet length. I will experiment with it, and if it works will roll out to next major version.



  • Thanks for your fast reply!

    2. Ah now I see the combined acc channel is a scalar value. I misunderstood that.
    I thought about the video overlay for combined acceleration which is a 2-D chart.
    I can show longitudinal acc and lateral acc channel in two charts, then the red dot is bouncing up and down in one chart and bouncing side to side in the other chart.

    When I add the combined acc channel from GPS to the overlay this is obviously a 2-D value.

    So my problem is, I have 2 scalar values (longitudinal and lateral acc) from CAN-Bus channels and I want them to show up combined in the 2-D chart. I can calculate the vector length like you suggested, but then it is still a scalar (1-D value) value, not showing the direction of the force.

    Best regards
  • edited January 2020
    1. Looks like a bug, a wrong field is showing up when you press "show more"

    2. That gauge uses three different channels. The number is "combined" and y-axis is "longitudinal" and x-axis is "lateral". What you need to do is to calculate lateral and longitudinal accelerations from your 3D IMU. For a car it would be easy, just point your sensor so that one axis is aligned to lateral and one is longitudinal. For a motorbike longitudinal would work same way, but to get lateral acceleration, you need to rotate the 3D matrix according to the lean angle...

    PS. Both 1. and 3. will be addressed in the next release, minor or major which ever comes first.
  • I agree, I have to care about correct calculation for my accerleration values. In theory I should know how to do the mathematical part, it will be more difficult to verify the results in praxis. For now, I will just simulate reasonable values for testing.

    I created 3 CAN-Bus channels, lateral acc, longitudinal acc and combined acc. I add a new analog gauge to the video overlay, device is CAN-Bus and channel is combined accerleration. What I get is an empty gauge (no red dot) with just the number G (combined). Additionally I can add a gauge for lateral and longitudinal acceleration. So I have 3 gauges confirming that I send 3 reasonalbe values but I can´t get them all into a single gauge.

    When the device is changed to GPS and channel is combined acceleration, I get the numger G and the red dot moving around x and y axis according to the accelerations calculated form the GPS. This is what I want to achieve but with my CAN-Bus values, not from GPS.

    I think the combined acceleration gauge has no idea where to get the x and y data from when the device is different from GPS.

  • @GiuseppeBinomi don't worry about the overlay. The editor is too restrictive, and the gauges them selves have too much hardcoded stuff. I'm rewriting all this for the spring release, allowing much more flexibility and configurability.
  • @GiuseppeBinomi your project sounds similar to my first RC project in 2016. I put my first data logger to RC using BT and RC3 strings on my 2000 ZX-6R. Read speed and RPM from the raw signals. Moved up to a 2009 ZX-6R for the track now but still no bike provided channels to read

    @GiuseppeBinomi / @aol What kind of update rate can you do with the BLE solution (Android)? I am still using RC3 over RFCOMM with my solution but I am starting to figure out what I want to change data logger wise for next season. Trying to figure out if I want to do my 100Hz logging to SD Card still and just pass minimum data to RC. Or can I use RC as the main logging and pass all the data to it?

    Thanks, Jeff
  • @J_D_W about 20 CAN-Bus (or "CAN-Bus") messages per second with a device built according to my DIY instructions. Each CAN-Bus message can hold several channels.

    I'm _thinking_ about a RC4 protocol that would be RFCOMM (or TCP/IP) and ASCII just like RC3, but interpreted from hexadecimal values same way as the CAN-Bus messages - with an equation and channel settings. This way you could transmit anything and translate the data in RaceChrono just like you wish...
  • @aol thanks for the information. I also took some time last year to implement the RaceTech binary format you added but never got it working well enough to use. Some of my encodings were definitely off, part of my thinking at the moment is if I debug that. I didn't even get to sending it to RC, I just logged it to my SD card and tried to load with Race Tech and had some issues.

    My current thinking is to continue with my path from last season. Log enough for timing and at track analysis over RC3 which already includes what I need at 10-20hz. And log high bandwidth (brake sensor, suspension movement) directly to SD Card in CSV or Circuit Tools format for later looking at if I see something I want to zoom into (ie chatter or specific issue in a corner). Thinking about possibilities of playing with GPS RTK modules to see if I can get GPS tracking into the sub-meter range.

    Thanks for all the hard work you put into the app!
    Jeff
  • edited February 2020
    @J_D_W Sorry for not following up with the Race Tech format better. I got maybe carried away with the possibilities of it, but later I learned that the format specification is just too ambiguous. The main issue with their format is, that the order of the packets is not clearly defined. So you will have to make big assumptions and some (or most) of them will be wrong, in terms of compatibility with Race Tech's own products.

    Now my DIY roadmap is to add more Bluetooth LE APIs, and maybe introduce the new RC4 format.
  • @aol after digging into the Race Tech stuff myself I agree with your plan to move forward. Your new direction is better documented and fully in your control, I would do the same. There format is documented but how they use it is not. So no need to say sorry. I'm kinda glad you say the above as it makes me feel better about not getting it to work and won't feel so bad when I remove it from my code :)

    Jeff
  • Hello all! I have a couple of stupid questions.... is it possible to use some other board for communication? If I would like to use bluetooth only for external GPS antenna that would be connected to my cell phone?
    I would like to get rpm etc. signals from CBR 1000rr so I guess those ”optional” arduino components are for that purpose since that bike has only k-line? Can I get data from it with that MCP2515 board? Sorry for this. I’m interested in getting data to racechrono to support my driving but my wallet is far away from mr. Trumps wallet 😄
  • @Camshaft77 a MCP2515 board is just a CAN bus break-out board is it not? You will need something that can read the K-line protocol, decode it and then send to RC. In your case CAN bus is not involved IMO.

    As for your wallet, the components to do much of this are not overly expensive. Its the development of the logic to gather the data and send it to RC that takes the time etc.

    Jeff
  • edited February 2020
    @Camshaft77 Yes, @J_D_W is correct that you do not want the MCP2515 board because that's for CAN-Bus, not K-Line. You'd need to adapt the design to use a K-line chip (something like STM11xx maybe?).

    And if you do not want to use the Adafruit nRF motherboard ($25), you'll need to replace the Bluetooth LE library (and usage), as this library is specific for the board. I selected Adafruit as the Bluetooth LE stack works very reliably on it.

    I think all the selected parts are relatively inexpensive, with the exception of the GPS board. At $40 it's very expensive, and you should be able to find better and cheaper alternatives. You can also leave it out, as it was added just as an afterthought. You could use separate Bluetooth GPS instead that you already may own.
  • edited February 2020
    @J_D_W In theory it is possible to achieve an update rate of 100 Hz. BLE has a parameter called connection interval which is defined from 7.5ms to 4s with increments of 1.5ms. The peripheral (your DIY device) can request a connection interval but the central (smartphone) decides in the end. It depends highly on both of your hardware. I think Android allows smaller intervals than IOS. I use a Samsung Galaxy S7 and an Arduino Nano 33 BLE and encountered connection problems when I tried to update the data with too little pauses in between. Some packets are lost and every time I reconnect, the update rate drops by about half until finally no transmission takes place. This is clearly an issue of the Arduino BLE library. I haven't followed that up yet. After restarting the Arduino everything is fine again. I achieved update rates of more than 40Hz but thats beyond what I need (so far). 20Hz seems to be stable and reliable.

    @aol Meanwhile I tried to transmit my GPS data. Thanks to the various connection options, I can read out the GPS module and send data to the serial monitor at the same time. So I can assure my GPS data look totally reasonable and valid. But no matter what I tried yet, RaceChrono tells me there is no connection to satellites and the GPS receiver displays "Waiting for data". At the same time the CAN Bus channels receive all their data.
    Actually I thought feeding the gpsMainCharacteristic and gpsTimeCharacteristic should be the easiest part. I created main and time data exactely as you did in your code. Are there any requirements that I've overlooked so far? Like minimum update rate or minimum count of satellites? I even tried to send hard coded valid data with only timestamp updated via micros() function. If I could not successfully transmit my other data, I would doubt whether data will be received at all.
    My service UUID is 00001ff8-0000-1000-8000-00805f9b34fb as you wrote in your protocol description. In your code it is 0x00000001000000fd8933990d6f411ff8. I have not yet checked what this is about because the two CanBus characteristics work fine (I also receive the filter messages even if I ignore them).

    I am grateful for any hints.
  • edited February 2020
    @GiuseppeBinomi If RaceChrono stays connected and is not cycling between connected and connecting states, it means your services and characteristics are correctly configured. If its disconnecting regularly, it means it cannot find correct ones with the UUIDs.

    Did you click the GPS checkbox when you configured the "DIY device" in RaceChrono settings? If you didn't then RaceChrono is not listening to the GPS characteristics.

  • Yep the DIY device is configured as both a CAN-Bus and a GPS device. When I start a new session, the app connects immediately and I see continuous CAN-Bus readings while GPS receiver displays "Waiting for data" forever.
    But it stays connected all the time. This means RaceChrono can find my two GPS characteristics and listens to them, right? At least that's something. So I know that something must be wrong within my data.
    Apart from a missing fix, what would cause the app to discard the data? Probably incorrect/missing time synchronization? It´s a bit of a shame that the packet limit is 20 bytes, 24 would be just perfect... Anyway I will double check everything in my code and use the nRF connect App to verify what is actually sent.

    Thanks a lot!
  • edited February 2020
    @GiuseppeBinomi yep, if it stays connected, then all characteristics and services are good.

    You should see the "gauges" instead of the "Waiting for data..." when these preconditions are valid:
    1) Last data from UUID 3 (GPS main) is exactly 20 bytes
    2) Last data from UUID 4 (GPS time) is exactly 3 bytes
    3) Sync bits (3 first bits of both data) match

    RaceChrono does a read and registers for notifications for UUID 3. The UUID 4 is only read when needed. This saves bandwidth on nRF chipset for some reason, even if the UUID 4 is never notified.
  • @aol I checked with nRF connect App if my UUID 3 and 4 data comply with the 3 preconditions and everything looks just fine.
    Now this might be of some interest: When I delete my DIY device and create it again configured only with checkbox GPS, it works! I started several sessions, I did several reconnects, it works as intended. My Arduino code is the same for both DIY configurations, so what could be different when CAN-Bus is checked or not?

    We use differenct hardware (Arduino nRF52840, Adafruit nRF52832) and BLE libraries, but in the end that shouldn´t matter.

    My Arduino gets data from the GPS module with 10Hz Update rate. Everytime new set of GPS data is ready, it writes all 3 BLEcharacteristics, canBusMain, gpsMain and gpsTime at the same time (actually one after another with no delay). With only GPS checked in DIY device, RaceChrono can always find the GPS data and receives it reliable with 10Hz. After that I tried it over and over again with both GPS and CAN-Bus checked and it looks like it work once out of 10 or 15 attempts.
    That means when RaceChrono does not show my GPS data from the beginning, it never will. But when GPS is found at the start, it´s running like a charm. Both GPS and CAN data came in exactly with 10Hz.
    This connection lottery is the same when I write the characteristics in different order or when GPS and CAN-BUS data is written at different times (like in your code, writing CAN-Bus and GPS data individually when they are availalbe).

    I am not sure if I understood how UUID 4 is read. I understand that it rarely contains necessary new data and is therefore not read continuously. Is it re-read whenever the sync bits differ?
  • edited February 2020
    @GiuseppeBinomi I just tested it and here's my findings:

    1) Device built with HAS_CAN_BUS seems to work fine. This device configured in RaceChrono settings as "RaceChrono DIY" with "CAN-Bus" seems to work fine.

    2) Device built with HAS_CAN_BUS and HAS_GPS seems to work fine. This device configured in RaceChrono settings as "RaceChrono DIY" with "CAN-Bus" and "GPS" seems to work fine. Also when configured with "CAN-Bus" alone and "GPS" alone it seems to work fine.

    3) When adding DIY device in RaceChrono settings, make sure to delete the old one first. The GPS and CAN-Bus checkbox state is not changed, if you just add the new one on top of the old one. This is a bug and should be fixed.

    Maybe you had problem 3) with your setup? Please try again. UUID 4 is read whenever the sync bits don't match.

    PS. Updated the git repository for latest libraries and board software.
  • @aol I noticed problem 3) earlier and always delete my DIY device when I want to change it.

    My Arduino is running all the time while I play around with RaceChrono and change the DIY device configuration. It always sends the 3 characteristics in the same way, as it can´t see which of them I actually configured in RaceChrono.
    So for me CAN-Bus alone and GPS alone works always. When I configure both, then the CAN-Bus works always but GPS only in maybe 10% of the attempts. It works either directly from the start or never.

    Anyway your answers are always helpful because everytime I understand a little bit more of what´s going on on the App side. So I´ll try what happens when I increment the sync bit just after RaceChrono has connected.

    We are getting closer to it :)
  • @GiuseppeBinomi Weird problem, but I could not reproduce it here as my build just worked right out. I wired the Adafruit GPS module to an existing CAN-Bus build, and compiled and installed the latest version from GitHub.

    I must assume it's some problem at your device side...
Sign In or Register to comment.