1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
|
/*
* Fan.cpp
*
* Created on: 29 Jun 2016
* Author: David
*/
#include "Fan.h"
#include "Platform.h"
#include "RepRap.h"
#include "GCodes/GCodeBuffer.h"
#include "Heating/Heat.h"
void Fan::Init(Pin p_pin, bool hwInverted)
{
val = lastVal = 0.0;
minVal = 0.1; // 10% minimum fan speed
blipTime = 100; // 100ms fan blip
freq = DefaultFanPwmFreq;
pin = p_pin;
hardwareInverted = hwInverted;
inverted = blipping = false;
heatersMonitored = 0;
triggerTemperatures[0] = triggerTemperatures[1] = HOT_END_FAN_TEMPERATURE;
lastPwm = -1.0; // force a refresh
Refresh();
}
// Set or report the parameters for this fan
// If 'mcode' is an M-code used to set parameters for the current kinematics (which should only ever be 106)
// then search for parameters used to configure the fan. If any are found, perform appropriate actions and return true.
// If errors were discovered while processing parameters, put an appropriate error message in 'reply' and set 'error' to true.
// If no relevant parameters are found, print the existing ones to 'reply' and return false.
// Exceptions:
// 1. Only process the S parameter unless other values were processed.
// 2. Don't process the R parameter, but if it is present don't print the existing configuration.
bool Fan::Configure(unsigned int mcode, int fanNum, GCodeBuffer& gb, StringRef& reply, bool& error)
{
if (!IsEnabled())
{
reply.printf("Fan %d is disabled", fanNum);
error = true;
return true; // say we have processed it
}
bool seen = false;
if (mcode == 106)
{
if (gb.Seen('I')) // Invert cooling
{
seen = true;
const int invert = gb.GetIValue();
if (invert < 0)
{
Disable();
}
else
{
inverted = (invert > 0);
}
}
if (gb.Seen('F')) // Set PWM frequency
{
seen = true;
freq = (PwmFrequency)constrain<int>(gb.GetIValue(), 1, 65535);
lastPwm = -1.0; // force the PWM to be updated
Refresh();
}
if (gb.Seen('T'))
{
seen = true;
size_t numTemps = 2;
gb.GetFloatArray(triggerTemperatures, numTemps, true);
}
if (gb.Seen('B')) // Set blip time
{
seen = true;
blipTime = (uint32_t)(max<float>(gb.GetFValue(), 0.0) * SecondsToMillis);
}
if (gb.Seen('L')) // Set minimum speed
{
seen = true;
float speed = gb.GetFValue();
if (speed > 1.0)
{
speed /= 255.0;
}
minVal = constrain<float>(speed, 0.0, 1.0);
}
if (gb.Seen('H')) // Set thermostatically-controlled heaters
{
seen = true;
int32_t heaters[Heaters + MaxVirtualHeaters]; // signed because we use H-1 to disable thermostatic mode
size_t numH = ARRAY_SIZE(heaters);
gb.GetIntArray(heaters, numH);
// Note that M106 H-1 disables thermostatic mode. The following code implements that automatically.
heatersMonitored = 0;
for (size_t h = 0; h < numH; ++h)
{
const int hnum = heaters[h];
if (hnum >= 0 && hnum < (int)Heaters)
{
SetBit(heatersMonitored, (unsigned int)hnum);
}
else if (hnum >= (int)FirstVirtualHeater && hnum < (int)(FirstVirtualHeater + MaxVirtualHeaters))
{
// Heaters 100, 101... are virtual heaters i.e. CPU and driver temperatures
SetBit(heatersMonitored, Heaters + (unsigned int)hnum - FirstVirtualHeater);
}
}
if (heatersMonitored != 0)
{
SetValue(1.0); // default the fan speed to full for safety
}
}
// We only act on the 'S' parameter here if we have processed other parameters
if (seen && gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on
{
const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0);
SetValue(f);
}
if (seen)
{
Refresh();
}
else if (!gb.Seen('R') && !gb.Seen('S'))
{
// Report the configuration of the specified fan
reply.printf("Fan%i frequency: %uHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s",
fanNum,
(unsigned int)freq,
(int)(val * 100.0),
(int)(minVal * 100.0),
(double)(blipTime * MillisToSeconds),
(inverted) ? "yes" : "no");
if (heatersMonitored != 0)
{
reply.catf(", temperature: %.1f:%.1fC, heaters:", (double)triggerTemperatures[0], (double)triggerTemperatures[1]);
for (unsigned int i = 0; i < Heaters + MaxVirtualHeaters; ++i)
{
if (IsBitSet(heatersMonitored, i))
{
reply.catf(" %u", (i < Heaters) ? i : FirstVirtualHeater + i - Heaters);
}
}
}
}
}
return seen;
}
void Fan::SetValue(float speed)
{
if (speed > 1.0)
{
speed /= 255.0;
}
val = constrain<float>(speed, 0.0, 1.0);
Refresh();
}
// Set the hardware PWM
// If you want make sure that the PWM is definitely updated, set lastPWM negative before calling this
void Fan::SetHardwarePwm(float pwmVal)
{
if (pin != NoPin)
{
bool invert = hardwareInverted;
if (inverted)
{
invert = !invert;
}
if (invert)
{
pwmVal = 1.0 - pwmVal;
}
// Only set the PWM if it has changed, to avoid a lot of I2C traffic when we have a DueX5 connected
if (pwmVal != lastPwm)
{
lastPwm = pwmVal;
IoPort::WriteAnalog(pin, pwmVal, freq);
}
}
}
void Fan::SetHeatersMonitored(HeatersMonitoredBitmap h)
{
heatersMonitored = h;
Refresh();
}
// Refresh the fan PWM
// If you want make sure that the PWM is definitely updated, set lastPWM negative before calling this
void Fan::Refresh()
{
float reqVal;
#if HAS_SMART_DRIVERS
uint32_t driverChannelsMonitored = 0;
#endif
if (heatersMonitored == 0)
{
reqVal = val;
}
else
{
reqVal = 0.0;
const bool bangBangMode = (triggerTemperatures[1] <= triggerTemperatures[0]);
for (size_t h = 0; h < Heaters + MaxVirtualHeaters; ++h)
{
// Check if this heater is both monitored by this fan and in use
if ( IsBitSet(heatersMonitored, h)
&& (h < reprap.GetToolHeatersInUse() || (h >= Heaters && h < Heaters + MaxVirtualHeaters) || reprap.GetHeat().IsBedOrChamberHeater(h))
)
{
// This heater is both monitored and potentially active
if (h < Heaters && reprap.GetHeat().IsTuning(h))
{
reqVal = 1.0; // when turning the PID for a monitored heater, turn the fan on
}
else
{
const size_t heaterHumber = (h >= Heaters) ? (h - Heaters) + FirstVirtualHeater : h;
TemperatureError err;
const float ht = reprap.GetHeat().GetTemperature(heaterHumber, err);
if (err != TemperatureError::success || ht < BAD_LOW_TEMPERATURE || ht >= triggerTemperatures[1])
{
reqVal = max<float>(reqVal, (bangBangMode) ? max<float>(0.5, val) : 1.0);
}
else if (!bangBangMode && ht > triggerTemperatures[0])
{
// We already know that ht < triggerTemperatures[1], therefore unless we have NaNs it is safe to divide by (triggerTemperatures[1] - triggerTemperatures[0])
reqVal = max<float>(reqVal, (ht - triggerTemperatures[0])/(triggerTemperatures[1] - triggerTemperatures[0]));
}
else if (lastVal != 0.0 && ht + ThermostatHysteresis > triggerTemperatures[0])
{
// If the fan is on, add a hysteresis before turning it off
reqVal = max<float>(reqVal, (bangBangMode) ? max<float>(0.5, val) : minVal);
}
#if HAS_SMART_DRIVERS
const unsigned int channel = reprap.GetHeat().GetHeaterChannel(heaterHumber);
if (channel >= FirstTmcDriversSenseChannel && channel < FirstTmcDriversSenseChannel + NumTmcDriversSenseChannels)
{
driverChannelsMonitored |= 1 << (channel - FirstTmcDriversSenseChannel);
}
#endif
}
}
}
}
if (reqVal > 0.0)
{
if (reqVal < minVal)
{
reqVal = minVal;
}
if (lastVal == 0.0)
{
// We are turning this fan on
#if HAS_SMART_DRIVERS
if (driverChannelsMonitored != 0)
{
reprap.GetPlatform().DriverCoolingFansOn(driverChannelsMonitored); // tell Platform that we have started a fan that cools drivers
}
#endif
if (reqVal < 1.0 && blipTime != 0)
{
// Starting the fan from standstill, so blip the fan
blipping = true;
blipStartTime = millis();
}
}
if (blipping)
{
if (millis() - blipStartTime < blipTime)
{
reqVal = 1.0;
}
else
{
blipping = false;
}
}
}
SetHardwarePwm(reqVal);
lastVal = reqVal;
}
bool Fan::Check()
{
if (heatersMonitored != 0 || blipping)
{
Refresh();
}
return heatersMonitored != 0 && lastVal != 0.0;
}
void Fan::Disable()
{
if (pin != NoPin)
{
inverted = false;
lastPwm = -1;
SetHardwarePwm(0.0);
}
pin = NoPin;
}
// Save the settings of this fan if it isn't thermostatic
bool Fan::WriteSettings(FileStore *f, size_t fanNum) const
{
if (heatersMonitored == 0)
{
char bufSpace[50];
StringRef buf(bufSpace, ARRAY_SIZE(bufSpace));
buf.printf("M106 P%u S%.2f\n", fanNum, (double)val);
return f->Write(buf.Pointer());
}
return true;
}
// End
|