Treefrog
Book Wheeler
Probably best to do an average or weighted average
Way to fuckin goI ended up leaving the inverted code, adding a stanrdard (non inverted) code, and just commented out the one I’m not using. With a second comment just to articulate how to choose between the two. Quick and dirty, but meh haha.
It’s in the jeep and it works. I’m probably going to broaden the range available on the knob, and I really need to add some smoothing to how frequently the temperature updates. I don’t know if I should make it update like once per second, or have it do some averaging. Averaging is probably better, but open to ideas there. Very happy with how everything is working though, just need to finalize the wiring and do some driving.
Probably best to do an average or weighted average
A simple filter like this is handy. Make FF easily adjustable using a #define or const.
FILT <-- FILT + FF(NEW - FILT)
const int RunningAverageCount = 16;
float RunningAverageBuffer[RunningAverageCount];
int NextRunningAverage;
void loop()
{
float RawTemperature = MeasureTemperature();
RunningAverageBuffer[NextRunningAverage++] = RawTemperature;
if (NextRunningAverage >= RunningAverageCount)
{
NextRunningAverage = 0;
}
float RunningAverageTemperature = 0;
for(int i=0; i< RunningAverageCount; ++i)
{
RunningAverageTemperature += RunningAverageBuffer[i];
}
RunningAverageTemperature /= RunningAverageCount;
delay(100);
}
Way to fuckin go
You gonna put the controller in the cab within view of the driver?Sweet. Johnny, could you break that concept/those terms down for me? I'm a little rusty, playing some catch up.
I've done some initial searching and a running average looks appealing. It's slower in regards to response time, but for this application a second or less realistically won't be detrimental. But still have more research to do.
Three Methods to Filter Noisy Arduino Measurements - Coding - MegunoLink
C-like:const int RunningAverageCount = 16; float RunningAverageBuffer[RunningAverageCount]; int NextRunningAverage; void loop() { float RawTemperature = MeasureTemperature(); RunningAverageBuffer[NextRunningAverage++] = RawTemperature; if (NextRunningAverage >= RunningAverageCount) { NextRunningAverage = 0; } float RunningAverageTemperature = 0; for(int i=0; i< RunningAverageCount; ++i) { RunningAverageTemperature += RunningAverageBuffer[i]; } RunningAverageTemperature /= RunningAverageCount; delay(100); }
Thanks man, hell of a learning experience to get this far haha.
So the temperature scaling isn't perfect yet, needs a little tuning. I went for a drive and got everything up to temp, and it bumped the fan up to full duty cycle even though things were at a healthy operating temp. Pretty satisfying amount of suction/air volume flowing through the cooling stack though, I'm pumped.
You gonna put the controller in the cab within view of the driver?
This project if I was good enough.. could use many sensors and just display the readings for troubleshooting purposes.
That might not be useful for the actual fan controller but a separate tool with just pressure/temp sensors.
The sensors I bought are addressable and are cheap under $20 for 5. I think they can all run on one sensor wire at different addresses making wiring simple.
Thermal epoxy them into brass 1/4 plugs to take radiator delta, engine delta etc. temperatures.
const float eng_temp_filter_coeff = 0.05;
float eng_temp;
float eng_temp_filtered;
eng_temp_filtered = eng_temp_filtered + (eng_temp_filter_coeff * (eng_temp - eng_temp_filtered));
No need to save off a buffer of data.
<< some sort of code of that converts A/D counts to Temp and puts it into eng_temp. Going from there:
Code:const float eng_temp_filter_coeff = 0.05; float eng_temp; float eng_temp_filtered;
in your time based task / main loop / whatever you got for scheduling:
Code:eng_temp_filtered = eng_temp_filtered + (eng_temp_filter_coeff * (eng_temp - eng_temp_filtered));
From there, fiddle around with the coeff to adjust the filter. There's a way to turn it into the frequency, but the reality is that you just fiddle with the number between 0..1 and make yourself happy with the noise rejection.
These are the sensors I used, different from normal resistance to ground types. Very cheap (maybe a bad thing) and since they can be connected in series they would make really simple wiring. Of course I bailed on my project so no idea if they are worth a shit...
An example of their use.
Interfacing Multiple DS18B20 Digital Temperature Sensors with Arduino
Learn 2 Methods(by Index & by Address) to Interface Multiple DS18B20 Digital Temperature Sensors On Single Bus along with its Wiring & Arduino Codelastminuteengineers.com
Ahhhh super interesting. So basically for each update of the data, it only allows the output to vary from the previous update by a certain small or large amount, depending on the chosen filter coefficient? Thereby softening any dramatic swings from one update to the next? I like it.
On your second statement, it starts with "eng temp filtered = engine temp filtered...", is that second supposed to be a reference to the previous update? If so, is that understood automatically or does it need something in the code?
const float filter_temp_coeff = 0.05;
float filtered_temp ;
void loop() {
// Read current temperature sensor resistance and calculate temperature
float sensor_voltage = analogRead(TEMP_SENSOR_PIN) * 5.0 / 1023.0;
float sensor_resistance = (5.0 - sensor_voltage) / sensor_voltage * 1000.0;
current_temp = calc_temp(sensor_resistance);
filtered_temp = filtered_temp + (filter_temp_coeff * (current_temp - filtered_temp));
// Read current potentiometer value
current_pot_value = analogRead(POT_PIN);
// Check if switch is pressed and update current screen
if (digitalRead(SWITCH_PIN) == LOW) {
current_screen++;
Yep. Keep it simple.
That's the code. It's that easy.
Example use Inserted in your code from page 1:
Code:const float filter_temp_coeff = 0.05; float filtered_temp ; void loop() { // Read current temperature sensor resistance and calculate temperature float sensor_voltage = analogRead(TEMP_SENSOR_PIN) * 5.0 / 1023.0; float sensor_resistance = (5.0 - sensor_voltage) / sensor_voltage * 1000.0; current_temp = calc_temp(sensor_resistance); filtered_temp = filtered_temp + (filter_temp_coeff * (current_temp - filtered_temp)); // Read current potentiometer value current_pot_value = analogRead(POT_PIN); // Check if switch is pressed and update current screen if (digitalRead(SWITCH_PIN) == LOW) { current_screen++;
filtered_temp is defined as a global, so it's value is kept while the unit has powered. The filter updates the value every time loop is called. It works best if loop is called at a periodic rate, otherwise you have a variable filter frequency.
Also, filtering will help the FET live longer. They don't like wild changes. Most stuff I've worked at is controlled around 100-200Hz, and inputs have filters to keep from rapid changes to the outputs.
If you plan to PWM control a FET, I'd recommend turning it 100% to start during the avalanche (or in rush) current spike, then once it's past that you move to frequency. Generally want a lower bound on your frequency as well, as if the thing you're driving starts to stall, current on the driver will increase. The bigger the actuator the FET is driving, the longer 100% start.
// Include required libraries
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <PWM.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
// Initialize OLED display object
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Define constants for pins and values
const int FAN_PIN = 9; //Fan output pin
const int AC_PIN = 8; //AC detection pin
const int TEMP_SENSOR_PIN = A1; //Temp sensor pin
const int POT_PIN = A2; //Potentiometer pin
const int buttonPin = 11; //Momentary switch pin
const int MIN_TEMP = 160; //Minimum duty cycle temperature
const int MAX_TEMP = 230; //Maximum duty cycle temperature
const int MIN_DUTY = 9; //Minimum duty cycle percentage
const int MAX_DUTY = 85; //Maximum duty cycle percentage
const int DUTY_INCREMENT = 1; //Duty cycle percentage increments
const int POT_RANGE = 10000; //Potentiometer resistance
// Define variables for current values
int current_duty = 0; //Current duty cycle
int current_screen = 1; //Current screen (may be deleted)
int current_pot_value = 0; //Current raw potentiometer value
int mapped_pot = 0; //Potentiometer values mapped to range of pMin to pMax
int pMin = -30; //Minimum temperature variation for minimum potentiometer value
int pMax = 30; //Maximum temperature variation for maximum potentiometer value
int current_temp_sensor = 0; //Current raw temperature sensor output
int mapped_temp = 0; //Temperature sensor values mapped to Fahrenheit range
int tuned_MIN_TEMP = 0; //Minimum temp range after modification by potentiometer value
int tuned_MAX_TEMP = 0; //Maximum temp range after modification by potentiometer value
int buttonPushCounter = 0; //Counter for the number of button presses
boolean buttonState = LOW; //Current state of the button
boolean lastButtonState = LOW; //Previous state of the button
// Set PWM Frequency
int32_t frequency = 100; //Frequency (in Hz)
void setup() {
// Initialize serial communication
//Serial.begin(9600);
// Set up momentary switch as input
pinMode(buttonPin, INPUT_PULLUP);
// Set up AC pin to be grounded for activation
pinMode(AC_PIN, INPUT_PULLUP);
// Initialize OLED display
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
//Prepare Timers for PWM frequency on FAN_PIN, activate onboard LED (pin 13) if successful
InitTimersSafe();
bool success = SetPinFrequencySafe(FAN_PIN, frequency);
if(success) {
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
}
}
void loop() {
// Read current potentiometer value
current_pot_value = analogRead(POT_PIN);
//Map potentiometer data to a range from negative (-)30 to +30, a simple variable added to the commanded temperature range
mapped_pot = map(current_pot_value, 1023, 0, pMin, pMax);
tuned_MIN_TEMP = (MIN_TEMP + mapped_pot);
tuned_MAX_TEMP = (MAX_TEMP + mapped_pot);
// Read current temperature sensor value
current_temp_sensor = analogRead(TEMP_SENSOR_PIN); //Raw unmapped temp sensor data
//Map voltages from temp sensor to Fahrenheit. First two numbers are observed resistance range, second two numbers are correlating temp in Fahrenheit
mapped_temp = map(current_temp_sensor, 600, 0, 45, 230);
// Define function for updating the duty cycle based on temperature and potentiometer
current_duty = MIN_DUTY + (MAX_DUTY - MIN_DUTY) * (mapped_temp - tuned_MIN_TEMP) / (tuned_MAX_TEMP - tuned_MIN_TEMP);
current_duty = round(current_duty / DUTY_INCREMENT) * DUTY_INCREMENT;
//Detect if AC is engaged, add 20% duty cycle
if (digitalRead(AC_PIN) == HIGH) {current_duty = current_duty + 20;}
//Duty cycle under minimum = zero, above max = defined maximum
if (current_duty < MIN_DUTY) {current_duty = 0;}
else if (current_duty > MAX_DUTY) {current_duty = MAX_DUTY;}
//Output PWM to FAN_PIN based on calculated duty cycle. Use Regular OR Inverted, comment out unused option.
//Regular:
pwmWrite(FAN_PIN, round(current_duty * 255.0 / 100.0));
//Inverted:
//pwmWrite(FAN_PIN, round((100 - current_duty) * 255.0 / 100.0));
// Read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);
//Serial.println(current_temp_sensor);
//Serial.println(mapped_temp);
switch (buttonPushCounter) // choose what to display based on buttonPushCounter value
{
case 0:
display.setTextColor(WHITE);
display.clearDisplay();
display.setCursor(0, 10);
display.print("RG:");
display.print(tuned_MIN_TEMP) ;
display.print("-") ;
display.println(tuned_MAX_TEMP);
display.display();
break;
case 1:
display.setTextColor(WHITE);
display.clearDisplay();
display.setCursor(0, 10);
// Display static text
display.print("C.Temp:");
display.println(mapped_temp);
display.display();
break;
case 2:
display.setTextColor(WHITE);
display.clearDisplay();
display.setCursor(0, 10);
// Display static text
display.print("C.Duty:");
display.println(current_duty);
display.display();
break;
}
if (buttonState != lastButtonState)
{
if (buttonState == HIGH)
{
// if the current state is HIGH then the button
// went from off to on:
buttonPushCounter++; // add one to counter
display.clearDisplay();
display.display();
if (buttonPushCounter > 2)
{
buttonPushCounter = 0;
}
}
// save the current state as the last state,
//for next time through the loop
lastButtonState = buttonState;
}
}
// Define function for updating the duty cycle based on temperature and potentiometer
current_duty = MIN_DUTY + (MAX_DUTY - MIN_DUTY) * (filtered_temp - tuned_MIN_TEMP) / (tuned_MAX_TEMP - tuned_MIN_TEMP);
current_duty = round(current_duty / DUTY_INCREMENT) * DUTY_INCREMENT;
What do you have MIN_DUTY set as?
Just got a chance to actually implement the filter. Fawking fantastic results . The temp is so smooth and stable now, I couldn't be happier. You had filtered_temp called out as a float value, but that ended up adding zeros where the screen doesn't have room so I'm going to make that an int instead, which I think should work and not cause issues?
Yes and no. Float is easier because it takes care of the fractional bits for you. I have seen stalling (filtered never reaching unfiltered) with integer math. It can be minimized, but it takes some fiddly bits.
The easiest way to do this is utilizing fixed point math. Not sure if you have messed with it. It works, but floats are miles easier.
What about keeping the filtered value in float, then converting back to int for display?
article on fixed point math: https://www.embedded.com/fixed-point-math-in-c/
Sweet. Johnny, could you break that concept/those terms down for me? I'm a little rusty, playing some catch up.
I've done some initial searching and a running average looks appealing. It's slower in regards to response time, but for this application a second or less realistically won't be detrimental. But still have more research to do.
Three Methods to Filter Noisy Arduino Measurements - Coding - MegunoLink
C-like:const int RunningAverageCount = 16; float RunningAverageBuffer[RunningAverageCount]; int NextRunningAverage; void loop() { float RawTemperature = MeasureTemperature(); RunningAverageBuffer[NextRunningAverage++] = RawTemperature; if (NextRunningAverage >= RunningAverageCount) { NextRunningAverage = 0; } float RunningAverageTemperature = 0; for(int i=0; i< RunningAverageCount; ++i) { RunningAverageTemperature += RunningAverageBuffer[i]; } RunningAverageTemperature /= RunningAverageCount; delay(100); }
Thanks man, hell of a learning experience to get this far haha.
So the temperature scaling isn't perfect yet, needs a little tuning. I went for a drive and got everything up to temp, and it bumped the fan up to full duty cycle even though things were at a healthy operating temp. Pretty satisfying amount of suction/air volume flowing through the cooling stack though, I'm pumped.
AWESOME work on this.
But... That TP looked used...
The sluggish operation is a clue to the Arduino problem not really a fan power problem?
PWM Operation:A PWM signal can be used in the Solid-State Relay Module with a maximum frequency of 150 hertz and a duty cycle range from 50% to 90%. The maximum time in PWM mode should not exceed 30 minutes.Duty cycle values below 50% may cause a faster thermal increase and trigger over-temp protection, especially with inductive loads (i.e. big fans).If you run below a 50% duty cycle, you may experience thermal over-temp protection within a few minutes.If operating in PWM mode for only a few seconds, the range can be extended from 30% to 90%.
Based on this I'd say it's HOT lolWhere is the arduino mounted? is it in a place you can hit it with a IR temp gun easily? If you can, might be good to use it on the micro, the mosfet, and if it's a IC based power supply, that too. Might grab temps at various times, see if it's getting warm. With extended PWM, it could be warming up that mosfet.
Can you find anything in an Arduino manual if they throttle back the micro in the event of higher temps?
I'm working on putting an MSD solid state relay in the buggy, and noticed this in the instructions. Shows even commercial stuff has limits.
I figured driving rain would be as much of a problem as anything...
That's what I figured, all that hard work I'd want to be watching that thing full timeWhy not have it inside where you can play with the knob? :D