Resistance to Voltage Converter for Gen 3 ECU Logging
Posted: Sat Nov 19, 2022 4:29 am
Hey there all,
decided to use this as a place to put this tool development notes and what not.
What does the tool do:
This tool is designed to take 2 separate inputs for fluid thermistors (specifically GM/Delphi thermistors), and output an analog voltage to the ECU for datalogging over the unused secondary O2 sensor inputs.
Why?
Most C5s that get tracked seriously will not have catalytic converters, and the O2 sensors for the post-cat sensors are not connected or installed. The OEM ECU doesn't have many inputs that can be used to recording voltage via OBD logging tools (such as race chrono, for lap data acquisition). This is an idea I had to convert the 2 unused O2 post-cat O2 sensor inputs to temp sensor inputs that I could datalog the voltage for and then convert the voltage back to temperature.
For me, I'm wanting to use these inputs to record differential and transmission temperatures in race chrono by datalogging the post-cat O2 voltages and doing some math on them to derive the temperature.
The challenges:
The O2 sensor input range is 0 to 1 V, so the range is fairly narrow. There also is typically some sort of biasing network with an op amp to 450 mV. I don't believe this to be a problem if you have a strong enough output driver. The other challenge is that finding a purely analog way to convert the resistance to a predictable output voltage that linearized temperature readings was extremely difficult. Due to this, I opted to add a small MCU that would sample the resistance value, calculate the resistance and then could determine the temperature and generate a PWM output that could be filtered to be make an analog DC output voltage.
There are 2 parts to this project: The schematic/board, and the firmware for the MCU.
Circuit design: I cranked this design out in about 1 hour. It has 4 main components
[*]Power stage: Take 4-28V DC input and regulate it down to 3.3V for MCU and component use
[*]Thermistor analog front end: A simple voltage divider network to sample the thermistor resistance, as well as provide some protection to the MCU I/Os
[*]MCU: The MSP430FR2111 is the brains of the board, sampling the resistance input and generating a PWM output
[*]DAC: Using a 10-bit PWM timer with a 2nd order low pass filter set below 150 Hz to generate a buffered DC analog voltage that the ECU can understand
The power stage uses a DC/DC buck regulator (TI LMR16006YQ3). This is capable of taking all input voltages from 4 V to 40 V and regulating it down to a 3.3V output efficiently. An LDO would generate too much heat due to the power draw of the op amps. It also features a schottky diode to provide reverse battery protection up to 20V (flipped power and ground wires).
The thermistor analog front end uses a 2.49K pull up resistor to 3.3V. This value is selected because this is the nominal value of a delphi temperature sensor at room temp (25C). It provides decent voltage range across temperature that the MCU should be able to sample. The 2.2K series resistor with 1nF capacitor is used to provide a current limiting functionality for over-voltage conditions where the ESD cell will trigger. While it technically provides a low pass filter functionality (-3 DB at ~78 kHz), that is not the primary function). There is a 3.9V zener diode designed to provide protection for the IOs in over-voltage conditions. This will require some testing, the datasheets show that it could have leakages as high as 10 uA at 1V, which would introduce a fairly large sampling error. I will play with this when I get the system in. It might end up being DNPed.
The MCU hardware is fairly simply. It has the standard decoupling capacitors needed, as well as a SPI By Wire (2 wire JTAG like programming interface TI uses) header for debug/programming. P1.3 and P1.4 are used as ADC inputs in the MCU (10-bit ADC right now, but it supports 12-bit. This will be something I have to test). The MCU utilizes P1.0 and P1.2 as ADC reference inputs (at least the option to). There are 2 pairs of outputs for the GPIO usage. P1.6/P2.0 and P1.7/P2.1. These pins are used by the timer module to generate a PWM output. Using the solder/bridge input for 2 GPIO to 1 output gives me the flexibility to choose different GPIOs if needed (due to functionality needs).
DAC stage is the more interesting part of the circuit. The target PWM frequency at 10-bit resolution will be somewhere in the 2 to 8 kHz range. We obviously do not want to see the on/off switching of the GPIO at the ECU. Utilizing a 2nd order low pass filter with a cut off frequency of about 30 Hz. Selecting the resistance and capacitance values is fairly straight forward, but the goal is to keep resistance low enough such that the series resistance does not affect the voltage seen by the buffer too much (since all op amps have an input bias current, which is the amount of current they "consume" on the + or - pins). The other balancing act is keeping capacitors small enough to fit on the board with ease. The output of the 2nd stage low pass filter is fed into a non-inverting op amp (the LMV358, a dual channel op amp package supporting 3.3V single ended supply with rail-to-rail capabilities). The Op amp is another choice because I need an op amp that can be supplied with a 0V and 3.3V input (instead of the usual +/- supply requirements) and can drive rail to rail. The output of the op amp has 2 100-ohm series resistors and a clamping zener diode to provide some protection to the op amp's driver, since this pin will be directly connected to a long wire that runs to the ECU.
Cranking out a quick and dirty layout in 1-2 hours resulted in this: It's far from ideal, with the long narrow power wire trace that breaks up the ground plane, but it is a 2 layer board that's small and meets the requirements for JLCPCB's $2/5 board manufacturing. We'll see how it performs.
Firmware
https://github.com/jonofmac/res_2_voltage
Firmware wise, it's pretty simple. I cranked out the code last night, and it's less 400 lines of actual code. My github repository for this project is a code composer studio project (for TI MCUs) that is pretty straight forward. There's the main.c file, and it does everything. The easiest way to break it down is as follows
Timers: This MCU has only 1 timer, but has 3 capture and compare registers. This means I have to use this timer to not only start the sample of the ADC, but also the PWM generation. It was not possible to meet both requirements nicely, so I instead have a lazy software counter each time the timer overflows, and once 100 of them occur, we'll have about 100 ms of time elapsed, and then I start an ADC sample. The ADC sampling for each channel happens once every ~200 ms, alternating which channel is sampled every 100 ms.
ADC: Once the ADC samples the input, it copies the ADC value to a global variable and sets a flag for the software to do some math.
The measurement to voltage process: The ADC value samples an analog voltage, knowing the pull up resistance, we can easily calculate the resistance of the thermistor at the input.
The ADC to resistance calculation happens in the calculateResistance() function. It returns a uint32_t resistance value.
Then the resistance value is ran through a look up table with interpolation in the calculateTemperature() function. This returns a temperature (in F, for more resolution, since I do not have floats/doubles). The LUT is set up with various points across the GM delphi sensor calibration range.
Once the temperature is known, the calculateTargetOutputVoltageFromTemp() function will scale the temperature to a target output voltage (in mv) to represent a temperature based on the defines at the top of the file. In my initial version, I use 0C/32F at 0V output and 150C/304F at 1V scale. This function will provide a target mV that's linear across this range for the given temperature value.
The last function, calculatePWMDutyCycleFromTargetOutputVoltage(), takes the target output voltage and converts it into a PWM count based on the PWM resolution and the GPIO voltage defines at the top of the file. It will return the final count value needed to achieve the target mV output.
Once this process takes place, it clears the new sample flag and the ADC is free to update again in the future.
There are a few downsides to this MCU:
1) 1 timer, so i'm fairly limited in being able to efficiently/hardware trigger the ADC and meet my output PWM requirements
2) No hardware multiply or divide functionality, so we'll see how slow these functions are
3) No hardware floating point math. I tried to add floats or doubles, and the resulting supporting library that had to be linked blew the binary size up by >1.8 kB. It no longer fit in memory, so I had to do everything as integer math and be careful about multiplying before i divide and making sure I can't overflow.
Right now everything in the mail to me, so I have yet to try any of this, but I'm hoping it'll work once I get it in.
Git hub links:
PCB/Schematic: https://github.com/jonofmac/res_2_voltage_pcb
Firmware: https://github.com/jonofmac/res_2_voltage
decided to use this as a place to put this tool development notes and what not.
What does the tool do:
This tool is designed to take 2 separate inputs for fluid thermistors (specifically GM/Delphi thermistors), and output an analog voltage to the ECU for datalogging over the unused secondary O2 sensor inputs.
Why?
Most C5s that get tracked seriously will not have catalytic converters, and the O2 sensors for the post-cat sensors are not connected or installed. The OEM ECU doesn't have many inputs that can be used to recording voltage via OBD logging tools (such as race chrono, for lap data acquisition). This is an idea I had to convert the 2 unused O2 post-cat O2 sensor inputs to temp sensor inputs that I could datalog the voltage for and then convert the voltage back to temperature.
For me, I'm wanting to use these inputs to record differential and transmission temperatures in race chrono by datalogging the post-cat O2 voltages and doing some math on them to derive the temperature.
The challenges:
The O2 sensor input range is 0 to 1 V, so the range is fairly narrow. There also is typically some sort of biasing network with an op amp to 450 mV. I don't believe this to be a problem if you have a strong enough output driver. The other challenge is that finding a purely analog way to convert the resistance to a predictable output voltage that linearized temperature readings was extremely difficult. Due to this, I opted to add a small MCU that would sample the resistance value, calculate the resistance and then could determine the temperature and generate a PWM output that could be filtered to be make an analog DC output voltage.
There are 2 parts to this project: The schematic/board, and the firmware for the MCU.
Circuit design: I cranked this design out in about 1 hour. It has 4 main components
[*]Power stage: Take 4-28V DC input and regulate it down to 3.3V for MCU and component use
[*]Thermistor analog front end: A simple voltage divider network to sample the thermistor resistance, as well as provide some protection to the MCU I/Os
[*]MCU: The MSP430FR2111 is the brains of the board, sampling the resistance input and generating a PWM output
[*]DAC: Using a 10-bit PWM timer with a 2nd order low pass filter set below 150 Hz to generate a buffered DC analog voltage that the ECU can understand
The power stage uses a DC/DC buck regulator (TI LMR16006YQ3). This is capable of taking all input voltages from 4 V to 40 V and regulating it down to a 3.3V output efficiently. An LDO would generate too much heat due to the power draw of the op amps. It also features a schottky diode to provide reverse battery protection up to 20V (flipped power and ground wires).
The thermistor analog front end uses a 2.49K pull up resistor to 3.3V. This value is selected because this is the nominal value of a delphi temperature sensor at room temp (25C). It provides decent voltage range across temperature that the MCU should be able to sample. The 2.2K series resistor with 1nF capacitor is used to provide a current limiting functionality for over-voltage conditions where the ESD cell will trigger. While it technically provides a low pass filter functionality (-3 DB at ~78 kHz), that is not the primary function). There is a 3.9V zener diode designed to provide protection for the IOs in over-voltage conditions. This will require some testing, the datasheets show that it could have leakages as high as 10 uA at 1V, which would introduce a fairly large sampling error. I will play with this when I get the system in. It might end up being DNPed.
The MCU hardware is fairly simply. It has the standard decoupling capacitors needed, as well as a SPI By Wire (2 wire JTAG like programming interface TI uses) header for debug/programming. P1.3 and P1.4 are used as ADC inputs in the MCU (10-bit ADC right now, but it supports 12-bit. This will be something I have to test). The MCU utilizes P1.0 and P1.2 as ADC reference inputs (at least the option to). There are 2 pairs of outputs for the GPIO usage. P1.6/P2.0 and P1.7/P2.1. These pins are used by the timer module to generate a PWM output. Using the solder/bridge input for 2 GPIO to 1 output gives me the flexibility to choose different GPIOs if needed (due to functionality needs).
DAC stage is the more interesting part of the circuit. The target PWM frequency at 10-bit resolution will be somewhere in the 2 to 8 kHz range. We obviously do not want to see the on/off switching of the GPIO at the ECU. Utilizing a 2nd order low pass filter with a cut off frequency of about 30 Hz. Selecting the resistance and capacitance values is fairly straight forward, but the goal is to keep resistance low enough such that the series resistance does not affect the voltage seen by the buffer too much (since all op amps have an input bias current, which is the amount of current they "consume" on the + or - pins). The other balancing act is keeping capacitors small enough to fit on the board with ease. The output of the 2nd stage low pass filter is fed into a non-inverting op amp (the LMV358, a dual channel op amp package supporting 3.3V single ended supply with rail-to-rail capabilities). The Op amp is another choice because I need an op amp that can be supplied with a 0V and 3.3V input (instead of the usual +/- supply requirements) and can drive rail to rail. The output of the op amp has 2 100-ohm series resistors and a clamping zener diode to provide some protection to the op amp's driver, since this pin will be directly connected to a long wire that runs to the ECU.
Cranking out a quick and dirty layout in 1-2 hours resulted in this: It's far from ideal, with the long narrow power wire trace that breaks up the ground plane, but it is a 2 layer board that's small and meets the requirements for JLCPCB's $2/5 board manufacturing. We'll see how it performs.
Firmware
https://github.com/jonofmac/res_2_voltage
Firmware wise, it's pretty simple. I cranked out the code last night, and it's less 400 lines of actual code. My github repository for this project is a code composer studio project (for TI MCUs) that is pretty straight forward. There's the main.c file, and it does everything. The easiest way to break it down is as follows
Timers: This MCU has only 1 timer, but has 3 capture and compare registers. This means I have to use this timer to not only start the sample of the ADC, but also the PWM generation. It was not possible to meet both requirements nicely, so I instead have a lazy software counter each time the timer overflows, and once 100 of them occur, we'll have about 100 ms of time elapsed, and then I start an ADC sample. The ADC sampling for each channel happens once every ~200 ms, alternating which channel is sampled every 100 ms.
ADC: Once the ADC samples the input, it copies the ADC value to a global variable and sets a flag for the software to do some math.
The measurement to voltage process: The ADC value samples an analog voltage, knowing the pull up resistance, we can easily calculate the resistance of the thermistor at the input.
The ADC to resistance calculation happens in the calculateResistance() function. It returns a uint32_t resistance value.
Then the resistance value is ran through a look up table with interpolation in the calculateTemperature() function. This returns a temperature (in F, for more resolution, since I do not have floats/doubles). The LUT is set up with various points across the GM delphi sensor calibration range.
Once the temperature is known, the calculateTargetOutputVoltageFromTemp() function will scale the temperature to a target output voltage (in mv) to represent a temperature based on the defines at the top of the file. In my initial version, I use 0C/32F at 0V output and 150C/304F at 1V scale. This function will provide a target mV that's linear across this range for the given temperature value.
The last function, calculatePWMDutyCycleFromTargetOutputVoltage(), takes the target output voltage and converts it into a PWM count based on the PWM resolution and the GPIO voltage defines at the top of the file. It will return the final count value needed to achieve the target mV output.
Once this process takes place, it clears the new sample flag and the ADC is free to update again in the future.
There are a few downsides to this MCU:
1) 1 timer, so i'm fairly limited in being able to efficiently/hardware trigger the ADC and meet my output PWM requirements
2) No hardware multiply or divide functionality, so we'll see how slow these functions are
3) No hardware floating point math. I tried to add floats or doubles, and the resulting supporting library that had to be linked blew the binary size up by >1.8 kB. It no longer fit in memory, so I had to do everything as integer math and be careful about multiplying before i divide and making sure I can't overflow.
Right now everything in the mail to me, so I have yet to try any of this, but I'm hoping it'll work once I get it in.
Git hub links:
PCB/Schematic: https://github.com/jonofmac/res_2_voltage_pcb
Firmware: https://github.com/jonofmac/res_2_voltage