Friday, 10 May 2019

On-board Data Logging

Since last year, I have wanted to implement an on-board data logging system for the bike for a number of reasons.

  1. On-board logging would mean all trips could be data logged automatically without needing to consciously connect to the ECU and start logging.
  2. Logging the engine data as broadcast via CAN-Bus would enable logging at a higher frequency than is available through the RS232 connection. 50Hz vs c.15Hz
  3. The CAN-Bus data broadcast from the ECU also gives access to far more data channels than are available to log via RS232 which includes many very useful diagnostics channels and calculated channels which I feel really should be included in the RS232 data stream such as injector deadtime.
  4. Having data log capabilities external to the ECU opens up the flexibility to add more data inputs either via CAN-Bus or analogue inputs. These could be additional engine sensors that are not currently required by the ECU to run the engine (oil pressure, oil temperature, EGT) or else they could also include chassis data such as brake pressure, GPS & IMU data, wheel speed, etc.

I found that most units on the market that would log CAN-Bus data were just too expensive for my needs so decided to use the opportunity to build on my existing skills and build my own.
I decided to use the Teensy 3.2 development board as the heart of the logger mainly due to the on-chip CAN-Bus interface but also because of its small package size and ease of programming via Arduino IDE.

The circuit layout and PCB design for the Teensy including the peripherals needed was relatively straight-forward. I was familiar with the CAN-Bus interface on the Teensy from other projects and, as a MicroSD socket and coin cell holder were the only other major components required, the physical aspect of the logger did not take long to complete.

Modelled Assembly

PCB Layout

PCB Board

Assembled Board

While the physical aspect was completed quite quickly, the software side of things took a bit of time to get to a state that worked well enough. While I was familiar with the CAN-Bus reading aspect, I had not had any experience with logging data to an SD Card so I went through a few iterations of code before settling on an approach that worked.
As I do not come from a software/coding background, any of my microcontroller projects always start with existing code that does something similar to or a particular aspect of what I want and then modify it to my needs.

The software workflow is relatively simple. On power-up, the microcontroller would read the date & time from the RTC and create a new data file on the SD card using the RTC timestamp in it's filename. This ensures unique filenames are used and makes the data easier to manage later.
Once the file is created, the controller reads any data contained in the CAN-Bus recieve buffer and adds it to the appropriate position in a data struct. At defined intervals corresponding to the desired logging frequency, the data contained in the struct is written to the SD card. In between writing the data to the SD card, the controller cycles through reading the CAN-Bus buffer and passing the recieved data to the struct as quickly as possible.
The cycle of data read & write is continued until power is removed from the module (i.e. ignition off). This does mean that there is the potential to lose some data not written to the card on power down but the period spanned by the lost data is unlikely to be more than 2-3 log intervals and is deemed acceptable for the purposes of the project.

Software Main Loop Basic Workflow

Writing data to the SD card was very straightforward as long as the number of parameters and logging frequency was kept low but problems started cropping up when more parameters were added to the datastream and the rate was pushed higher than c.10Hz.

I began by wanting to log data to a human readable .csv file on the SD card purely for convenience. However, with the number of data channels I wanted to log I ran into issues with SD card write latency at write speeds above c.10Hz which was causing me to lose a number of data points every now and then while the program waited for the SD card to finish writing. I could help the issue by adding data to a single long data string and then writing that to the SD in one go. However, the data string took up too much memory with the number of channels I was trying to log so it was not really a viable option. This also didn't completely eliminate the issue.

In the end, after experimenting with minor changes to the same basic method, I opted to write the data to the SD card in binary format and then post process the data after it had been downloaded from the SD card to create human readable .csv files with headers.
By writing the data in binary format, I was able to use a C struct to contain the data variables which was much easier to update within the software and also very easy to simply write the entire struct to the log file whenever it was needed. It also seems to have made the SD write latency issues disappear. I have not yet verified data frequency consistency over a long period of time and at higher logging rates at the time of writing but I have verified writing over 60 data channels to the card at 20Hz for several minutes without any loss of data. While logging directly to .csv file, I was getting data inconsistencies even with only 10off data channels and 10Hz logging rate.

The post processing script was written in GNU Octave which is an open source alternative to Matlab. The script is written in a way that it reads information about the C struct (variable names, units and precision) from a text configuration file so that it knows how to interpret the binary data. The script then reads the raw binary file, arranges the data into a matrix which matches the configuration file input and writes the processed data matrix to a .csv file along with header lines that define the channel names & units. The necessity to have a configuration file which matches the logger C struct means that I need to be particularly meticulous when it comes to documenting changes to the logger software and I need to have good version control.

Version 1.0 of the logger software & post processing script leaves some room for improvement but I am happy that I have an easy to use and reliable datalogger that I can fit to the bike and use while I work on further improvements.

Some improvements which I have in mind are:
  1. Remove the CAN message processing from the Teensy software and log raw message values. The post processing script will be capable of doing the data conversion as well as the binary to decimal conversion. Removing the processing from the logger just means that I can free up some processing power & main loop time to help increase reliability at higher data rates. Logging single bytes also will have the advantage of reducing the size of the struct as it currently pads variables out to the largest precision value (32-bit float). If all variables to be logged are full CAN message data sizes (64-bit) there will be no need for any additional padding.
  2. Add one or more data buffers to the logger software. This should help logging reliability and increase the potential logging rate by not requiring the SD card to be ready for writing on each data write loop. 

I 3D printed an enclosure for the board in ABS plastic. The case is not fully sealed as I have left a slot in the side to allow me to remove & insert the SD card without opening the case.

To complete the logger connection to the bike, I made up a short adapter harness which is sleeved & booted at the connector end. The adapter harness is then cable tied to the PCB board via 2off small slots which I had cut in the board for this very purpose. This just serves to provide some strain relief for the header pins & socket. I also printed a wire seal from TPU as an experiment to help seal the area where the harness exits the case. I also added a label to the harness to identify the module. Because.... well... it looks nice.

Bike interface connector

Adapter harness in position

Case with cable seal


wes said...

For best performance, make sure you're writing in chunks of exactly 512 bytes. Also you can take a look at the LowLatencyLogger sample project (google for it, it's in greiman's github). Some pretty good results are possible, well into the kHz range. 50Hz should be no problem.

motthomas said...

Cheers wes. Yes I have seen the LowLatencyLogger example and I intend to draw from it for the next iteration of software.

ThomasP said...

Have you thought about using a speeduino. Its like megasquirt but cheaper and open source!

motthomas said...

Hi Thomas, Speeduino didn't cross my path before the project started but I have looked at it since. I think the Speeduino would be a step backwards from the Microsquirt in terms of performance (8-bit 16MHz vs 16-bit 24MHz). I am currently looking to replace the Microsquirt with something more capable.
Are you running a Speeduino?