Novak Conversions Jeep Wrangler TJ radiator

Custom microprocessor (Arduino, Pi, etc) Jeep project thread

freedom_in_4low

I'm a rooster illusion
Original poster
Supporting Member
Ride of the Month Winner
Joined
Sep 26, 2019
Messages
9,305
Location
Arcadia, OK
I started posting some stuff about this in @Steel City 06 's SPAL electric fan thread but rather than hi-jacking it I figured I'd start a new thread for this, because it has more applications than just electric fan control.

For context, what I'm doing is making a fan controller for a variable speed cooling fan using a PWM signal based on information from non-OE sensors running straight to the Arduino as well as some values from the OBD2 bus. The control code (how much fan speed is needed for a given temperature) is the easy part, by far, and probably will comprise less than 10% of the total code required.

Other applications might include a torque converter lockup controller for a 32RH using a manual trans PCM for those that would rather not use the 42RLE. Lots of possibilities.

Anyway, to the meat and potatoes.

Getting Arduino to talk to your PCM
First things first - you have to get the Arduino to talk to the PCM. There are a number of OBD2 protocols but from what I can tell, the TJ PCM uses J1850 VPW, which I believe is common among Chrysler and GM vehicles pre-CAN.

I found an Arduino library for communicating directly using J1850VPW but it required some circuitry in between and the documentation isn't really detailed enough for me to understand it. C/C#/C++ is not my "native" programming language - I have a general understanding of the syntax but I don't have a lot of knowledge on the deeper stuff.

Most inexpensive OBD2 code readers are based on an "ELM327" chip. I found a couple of ELM327 libraries that I can understand well enough to try to implement - one called ELMo which is pretty simple, you just send commands like 01 05 which is a request for live data and and then you get a response like 41 05 38 which would tell you the coolant temperature is 96C 205F. The other one is called ELMduino which hides the inner workings and lets you ask for PIDS by name, which makes the library more complex but makes your work a lot easier.

With the software figured out, you need the hardware to get from the Arduino serial port to the communication pins on the ELM327. Most ELM327's come in the form of a $20 Amazon reader that is either Bluetooth or USB. There are some Arduino models that do Bluetooth but I don't want to have to figure that out nor do I want my radiator fan to depend on a bluetooth connection. The Arduino Due is the only board that can be a USB host, but it's a much bigger board and overkill for what we're doing, plus I'm not sure I want to invest the time in figuring out USB, either. I want as direct a link as possible.

I have a UART to OBD2 board on backorder from Sparkfun, which will interface directly to the Arduino's serial port. If that backorder seems to be going on indefinitely, I may buy a cheap USB model and un-solder the USB chip to solder in some conductors straight from the Arduino serial port.

1729899031079.png


Then I'll have to find a way to tie into the J1850VPW pins right before they go to the DLC connector, because I want to tie directly to the harness so that I still have use of the DLC for my Torque app, etc without having to unplug the lifeline to my cooling fan, because that would be stupid. I don't want to try to build something from scratch with a naked ELM327 chip because there's a bunch of electronics BS in between that I want to be already put together for me.

1729899225382.png


I'm gonna refrain from posting code until I have something that works, but when I get to that point I will be sure to update.
 
Have you considered some of the more powerful, Arduino-compatible alternatives? E.g. SAMD21 boards, such as QTPY from Adafruit?
 
if you don't need to read OBD2, you can skip that stuff and just connect sensors directly to your Arduino.

A common, relatively inexpensive type of temperature sensor is an NTC thermistor. NTC stands for Negative Temperature Coefficient, which means the resistance decreases with increasing temperature.

The trickiest part is finding the data sheet for your sensor. This one is for a common GM coolant temp sensor that I'm going to put in the lower radiator hose to measure what I'm calling "Leaving Coolant Temp", leaving the radiator.

https://phoenixsensors.com/wp-content/uploads/2015/09/PTS40-Temperature-Probe-v2.pdf

The key pieces of information are the nominal resistance and the temperature at which that nominal resistance occurs, along with the Beta coefficient. From there you can calculate the curve.

1729899701217.png


R0 and T0 are the nominal resistance and temperature, respectively, and the beta is the beta coefficient. T is your measured temperature and R is the NTC's resistance at that temperature. It's kinda dumb that they present it this way because if you're reading the sensor you want it solved for T as a function of R.
1729900070123.png


For the GM sensor, beta is 3977, R0 is 10,000 ohms and T0 is 25C. The equation uses absolute temperature, so you want to add 273.15 to convert Celsius to Kelvin going into the equation and then subtract 273.15 from the result to convert back.

I wrote this little function to take the value straight out of the analogRead() function and output a temperature in Celsius.
1729901820261.png


"resistor" is the ohm value of the resistor Rs used in the circuit with the Rt temp sensor shown in the simple schematic below. A common choice for the resistor is 10kOhm, something similar to the nominal sensor resistance. Vin is the reference voltage from the board, usually 5V, and Vout is the output of the thermistor circuit, which will connect to the analog input pin on the board.

1729901937044.png


I'm gonna use a sensor from work for the AC liquid temperature, which interestingly has a beta of the same 3977 as the GM ECT sensor has, but it's 50,500Ohm at 25C so the whole temperature scale shifts up (it's designed for temps up to 150C).

Sensor readings tend to drift by miniscule amounts every program cycle but I don't really like that, so I'm gonna implement a filter that averages a number of samples over a period of time, like maybe 15 samples over 15 seconds. I'll post that code when I get it working. I'm close but I have something weird going on with the very last sample that's messing with the average.
 
Last edited:
  • Love
Reactions: hear
Have you considered some of the more powerful, Arduino-compatible alternatives? E.g. SAMD21 boards, such as QTPY from Adafruit?

I was looking at Ruggeduino specifically. I have an Arduino Uno so that's what I'm developing with for now. I admit that though I program controllers regularly as part of my job, it's my first real adventure into these little hobby microcontrollers so I'm not completely aware of what my options are.
 
I though I might be a stupid uneducated redneck and now I’m convinced I am right.




Chris, this needs moved to the Greek member subforum. 🤣

Hats off to you guys that can work that stuff out, no joke.
 
I was looking at Ruggeduino specifically. I have an Arduino Uno so that's what I'm developing with for now. I admit that though I program controllers regularly as part of my job, it's my first real adventure into these little hobby microcontrollers so I'm not completely aware of what my options are.

Those look like a solid option for durability, little pricy though.
And it turns out they're based 30 minutes from me 🤯
 
For my steering wheel control project, I first used an Arduino on a custom board with a voltage regulator. I couldn’t get the Arduino analog to digital pins working properly since the Jeep voltage fluctuated so much. I still don’t know if it was an issue with the Arduino or my circuit design. I swapped it over for a pic microcontroller and it’s been perfect.
 
  • Like
Reactions: freedom_in_4low
don't have the hardware yet for the OBD2 stuff but I got the rest of the program working -outputting a PWM signal based on radiator leaving coolant temp.

I took a couple of shots at different points to show it working at different LCT's by breathing on it. At this point I'm using the same type of sensor for both ambient and LCT but just put a +50 on LCT to get it into the range where it would use the fan.

PXL_20241027_003337669.MP.jpg



PXL_20241027_003334088.jpg


PXL_20241027_003502264.MP.jpg


PXL_20241027_003507371.jpg


One of my potential challenges is getting the PWM frequency to match what the fan expects. The Arduino defaults to 490Hz, the fan expects 100Hz. I can use a prescaler to slow it down but the denominators are all in powers of 2, so the closest I can get is 122Hz.

//sample rate and time constant for temperature sensor filters
#define NUMSAMPLES 15
#define SAMPLEINTERVAL 1000
int LCTsamples[NUMSAMPLES];
int TambSamples[NUMSAMPLES];
unsigned long filterprevms;
int i;
float AirVolumeDemand;
float AirVolumeDemandPrevious;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
uint8_t j;
//initialize temperature at something reasonable - 16.6C or 62F. Temp readings will fall or climb to match actual within 15 seconds of startup
for(j=0; j<NUMSAMPLES; j++){
LCTsamples[j] = 610;
TambSamples[j] = 610;
}
pinMode(3, OUTPUT);
//these registers configure the PWM on pin 3 to achieve 122Hz by setting prescale denominator to 256 and PWM mode to phase-correct
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
TCCR2B = _BV(CS22) | _BV(CS21);
}
void loop() {
// put your main code here, to run repeatedly:
const float LCTmin = 54;
const float LCTmax = 88;
const float Tstat = 93;
const float base = 35;
const float FanMinSpeed=12;
const int interval=5000;
float MinAirVolume = 0;
float Tambient;
float LCT;
float Tliq;

float LCTaverage;
float Tambaverage;
float FanSpeedCommand;
bool FanEnable;
bool ACStatus;
uint8_t j;
//every second, take a temperature reading and average all the samples
if (millis() >= filterprevms + SAMPLEINTERVAL) {
LCTsamples = analogRead(A0);
TambSamples = analogRead(A1);
i++;
if (i >= NUMSAMPLES){i=0;}
filterprevms = millis();
LCTaverage = 0.0;
Tambaverage = 0.0;
for (j=0; j < NUMSAMPLES; j++){
LCTaverage += LCTsamples[j];
Tambaverage+=TambSamples[j];
}
LCTaverage /= NUMSAMPLES;
Tambaverage /= NUMSAMPLES;
//convert 10 bit integer to Celsius temperature using NTC thermistor calc
LCT = 50+NTCsensor(LCTaverage, 10000, 25, 3977, 10000);
Tambient = NTCsensor(Tambaverage, 10000, 25, 3435, 10000);
Tliq = NTCsensor(analogRead(A2), 50500, 25, 3977, 10000);

}
//evaluate AC status based on AC liquid temperature
ACStatus = (Tliq > 120.0) && (LCT < LCTmin + 10.0);
//require at least 20% air volume if AC is on
MinAirVolume = ACStatus * 20;
//calculate air demand based on coolant temp
AirVolumeDemand = constrain(100*((Tstat - base)/(Tstat - Tambient))*((LCT - LCTmin)/(LCTmax - LCTmin)), MinAirVolume, 100);


//disable fan if air demand is below what the fan provides at minimum speed
FanEnable=hyster(AirVolumeDemand, FanMinSpeed, 1.0, FanEnable);// && VSS<37;
if (FanEnable){FanSpeedCommand = max(AirVolumeDemand, FanMinSpeed);}
else{FanSpeedCommand = 0;}
//override logic to full speed fan if LCT sensor reads out of range
if(LCT > 100 || LCT < -15){
FanSpeedCommand = 100;
}
//convert fan speed percent command to 8 bit PWM duty cycle matched to fan signal expection of 5%-90%
float dutycycle = map(FanSpeedCommand, 0, 100, 13, 230);
analogWrite(3, dutycycle);


}
float NTCsensor(float in, float R0, float T0, float B, float resistor){
return (1 / (1 / (T0 + 273.15) + log((resistor / (1023 / in-1)) / R0) / B) - 273.15);
}
bool hyster(float in, float cutin, float cutout, bool state){
bool out=state;
if(in>=cutin){out=true;}
else if(in<cutout){out=false;}
return out;
}



At this point it should work, but I don't have the vehicle speed shutoff at 37mph like I wanted, or the integral control mode based on ECT, until I get the hardware to talk to the OBD2. Not necessarily a critical feature because the LCT should drop and shut the fan off anyway, but it's a nice to have.
 
Last edited:
I've had trouble getting the PWM frequency I want out of various microcontrollers, especially on Arduinos.

It might be easier to generate your own signal. At 100 Hz it should be fairly easy. Set up a timer based on duty cycle * 0.01 second and flip the output pin whenever it times out, then do the remainder of the cycle with what's left over.

Found this discussion: https://forum.arduino.cc/t/control-pwm-frequency-set-it-at-100hz/671610/4 (but didn't read it all yet).
 
I've had trouble getting the PWM frequency I want out of various microcontrollers, especially on Arduinos.

It might be easier to generate your own signal. At 100 Hz it should be fairly easy. Set up a timer based on duty cycle * 0.01 second and flip the output pin whenever it times out, then do the remainder of the cycle with what's left over.

Found this discussion: https://forum.arduino.cc/t/control-pwm-frequency-set-it-at-100hz/671610/4 (but didn't read it all yet).

I've got that option in my back pocket. Now that I have an oscilloscope I can try it out.
 
I've had trouble getting the PWM frequency I want out of various microcontrollers, especially on Arduinos.

It might be easier to generate your own signal. At 100 Hz it should be fairly easy. Set up a timer based on duty cycle * 0.01 second and flip the output pin whenever it times out, then do the remainder of the cycle with what's left over.

Found this discussion: https://forum.arduino.cc/t/control-pwm-frequency-set-it-at-100hz/671610/4 (but didn't read it all yet).

I did the code using the timer and it works, until it doesn't.

1000002423.jpg


I did this before realizing I'd cleaned out all the serial print commands I was using for debugging so I had no way to verify the duty cycle was correct. When I added them in to see what the program wanted the duty cycle to be, I no longer had a waveform coming out. I'm thinking the serial traffic is doing something with the timing and screwing up the works, which gives me concern whether I'll be able to use this method to generate the wave while also talking to OBD2.

I need to look into interrupts to see if there's something there I can use to get both serial comms and the PWM to work. That's not really a thing in HVACR controls so I've never dabbled in it. EDIT: looks like this is a dead end so far, as the timer functions and serial communications both depend on interrupts, so you can't use them inside an interrupt service routine (ISR).

This is the code I used:

if(micros() < prevmicros + period*map(FanSpeedCommand, 0, 100, 5, 90)/100){
digitalWrite(3, HIGH);
//reset previous time to current time if previous time is later than current time, indicating micros function has overflowed and wrapped around.
if(micros() < prevmicros){
prevmicros = micros();
}
}
else if (micros() < prevmicros + period){
digitalWrite(3, LOW);
}
else {
prevmicros = micros();
}

I find it odd and frustrating how so many code examples on the internet use the delay() function. That blocks the whole program so everything else stops while it waits for that delay. When I do these sorts of functions I write them non-blocking, which is why you will always see me using compares with the millis() and micros() functions, as appropriate.
 
Last edited:
  • Like
Reactions: Pascal25
A stupid question:

I know the square root of fuck-all about Arduino, and I find myself needing to buy one to power an EPROM programmer board. The board's dox requires a "mega 2560 R3" version. I'm finding a lot of apparent clones, such as this one:

https://www.ebay.com/itm/2558771120...pid=5337789113&customid=&toolid=10001&mkevt=1

I'm ASS-U-ME-ing that there's little problem to using a clone as I'm not finding any real Arduino branded boards... (????)
 
This site contains affiliate links for which Jeep Wrangler TJ Forum may be compensated.
A stupid question:

I know the square root of fuck-all about Arduino, and I find myself needing to buy one to power an EPROM programmer board. The board's dox requires a "mega 2560 R3" version. I'm finding a lot of apparent clones, such as this one:

https://www.ebay.com/itm/2558771120...pid=5337789113&customid=&toolid=10001&mkevt=1

I'm ASS-U-ME-ing that there's little problem to using a clone as I'm not finding any real Arduino branded boards... (????)

I don't see a good reason a clone wouldn't work but if you want the real thing and want to spend 3x that, it's on Amazon.

Arduino Mega 2560 REV3 [A000067] https://www.amazon.com/dp/B0046AMGW0?tag=wranglerorg-20
 
  • Like
Reactions: Zorba
This site contains affiliate links for which Jeep Wrangler TJ Forum may be compensated.
A stupid question:

I know the square root of fuck-all about Arduino, and I find myself needing to buy one to power an EPROM programmer board. The board's dox requires a "mega 2560 R3" version. I'm finding a lot of apparent clones, such as this one:

https://www.ebay.com/itm/2558771120...pid=5337789113&customid=&toolid=10001&mkevt=1

I'm ASS-U-ME-ing that there's little problem to using a clone as I'm not finding any real Arduino branded boards... (????)

Some of the clones are great, some not so much. It's hard to guess which ones are good. I've had good luck with Inland brand from MicroCenter. The one in Miami has 5 in stock, and they'll ship, but I don't know what shipping costs will be. I'm lucky to have a MicroCenter in Denver.

https://www.microcenter.com/product/486545/inland-mega-2560-mainboard-arduino-compatible
 
  • Like
Reactions: Zorba
This site contains affiliate links for which Jeep Wrangler TJ Forum may be compensated.
I did this before realizing I'd cleaned out all the serial print commands I was using for debugging so I had no way to verify the duty cycle was correct. When I added them in to see what the program wanted the duty cycle to be, I no longer had a waveform coming out. I'm thinking the serial traffic is doing something with the timing and screwing up the works, which gives me concern whether I'll be able to use this method to generate the wave while also talking to OBD2.

I need to look into interrupts to see if there's something there I can use to get both serial comms and the PWM to work. That's not really a thing in HVACR controls so I've never dabbled in it. EDIT: looks like this is a dead end so far, as the timer functions and serial communications both depend on interrupts, so you can't use them inside an interrupt service routine (ISR).
Ugh, that's nasty. I've run into that too. I always liked writing my own code from scratch, and getting everything to work together. But with Arduino, where you're kind of dumped into a giant pile of libraries, you don't get visibility into all that nitty gritty stuff, and it blindsides you. The dumb things are so convenient, with good cheap powerful hardware and a zillion libraries for everything. And so stinking frustrating at the same time.

I find it odd and frustrating how so many code examples on the internet use the delay() function. That blocks the whole program so everything else stops while it waits for that delay. When I do these sorts of functions I write them non-blocking, which is why you will always see me using compares with the millis() and micros() functions, as appropriate.

Yes, likewise. The code examples have to be simple to show a concept, but do they have to be dumb, too? Lots of people probably pick up bad coding habits from those examples. I've used code just like yours to get around the delay() blocking.

I wish I had a suggestion for you, but...
Maybe just try the fan with 122 Hz PWM? Maybe it will like it. I suppose it depends on how the fan reads and uses that signal.
 
  • Like
Reactions: Pascal25
I did the code using the timer and it works, until it doesn't.

View attachment 568327

I did this before realizing I'd cleaned out all the serial print commands I was using for debugging so I had no way to verify the duty cycle was correct. When I added them in to see what the program wanted the duty cycle to be, I no longer had a waveform coming out. I'm thinking the serial traffic is doing something with the timing and screwing up the works, which gives me concern whether I'll be able to use this method to generate the wave while also talking to OBD2.

I need to look into interrupts to see if there's something there I can use to get both serial comms and the PWM to work. That's not really a thing in HVACR controls so I've never dabbled in it. EDIT: looks like this is a dead end so far, as the timer functions and serial communications both depend on interrupts, so you can't use them inside an interrupt service routine (ISR).

This is the code I used:



I find it odd and frustrating how so many code examples on the internet use the delay() function. That blocks the whole program so everything else stops while it waits for that delay. When I do these sorts of functions I write them non-blocking, which is why you will always see me using compares with the millis() and micros() functions, as appropriate.

Problem: solved. I can use the Arduino Nano ESP32, it uses a different chip which is quite a bit more powerful and can accommodate whatever PWM frequency I want to use, with standard libraries and none of this hacker stuff.

Bonus is that it's quite a bit smaller so packaging will be easier.
 
Problem: solved. I can use the Arduino Nano ESP32, it uses a different chip which is quite a bit more powerful and can accommodate whatever PWM frequency I want to use, with standard libraries and none of this hacker stuff.

Bonus is that it's quite a bit smaller so packaging will be easier.

Excellent! I've used some of the SAMD boards, and the Pico, but not the ESP32. I suppose they might do the same. I should have thought of that, but I'm getting a bit rusty, like an east coast TJ. :(
 
Excellent! I've used some of the SAMD boards, and the Pico, but not the ESP32. I suppose they might do the same. I should have thought of that, but I'm getting a bit rusty, like an east coast TJ. :(

I have an MKR WiFi 1010 which uses a SAMD board...i looked it up for that one and it looks like it might be doable but a lot more complex than the ESP32.

Bonus with the nano ESP32 is that it has Bluetooth and wifi so if I feel really ambitious and want to learn more software/web development I could build an interface to see what's going on with my phone.

I have a great deal of experience in programmable logic controls like what is used to control a machine or a piece of equipment, like in commercial and industrial HVAC and refrigeration. Even though the principles are generally similar, it's more different than I think most people would suspect, so my learning curve here probably is pretty big.
 
Last edited:
Bonus with the nano ESP32 is that it has Bluetooth and wifi so if I feel really ambitious and want to learn more software/web development I could build an interface to see what's going on with my phone.

I'll have to check out the nano ESP32. I've used the similar-ish NodeMCU ESP8266 with some success, but it can be temperamental, at least when programming with the Arduino IDE. The Raspberry Pi Pico W has Wi-Fi too, and seems better behaved. I haven't needed Bluetooth yet, since I'm in the @Zorba mindset where cell phones should be turned on no more than once every 6 months, and then for emergency calls only. :D

I have a great deal of experience in programmable logic controls like what is used to control a machine or a piece of equipment, like in commercial and industrial HVAC and refrigeration. Even though the principles are generally similar, it's more different than I think most people would suspect, so my learning curve here probably is pretty big.

The one time I looked into programmable logic controls, I realized that they weren't what I expected. You're right, some of the knowledge and experience crosses over, but not as much as I thought.

I'm glad you started this thread. I've thought about tapping into OBD2 data for extra dash instruments, like vacuum and load factor for trying to improve gas mileage, and real numbers for volts, gas gallons, water temp. Maybe you'll spur me to work on it again.

I'll be interested to know how your SparkFun OBD-II UART works out.
 
Novak Conversions Jeep Wrangler TJ radiator