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
|
/*
* fsm-adc.c: ADC (voltage, temperature) functions for SpaghettiMonster.
*
* Copyright (C) 2017 Selene ToyKeeper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FSM_ADC_C
#define FSM_ADC_C
inline void ADC_on()
{
// read voltage on VCC by default
// disable digital input on VCC pin to reduce power consumption
//DIDR0 |= (1 << ADC_DIDR); // FIXME: unsure how to handle for VCC pin
// VCC / 1.1V reference
ADMUX = ADMUX_VCC;
// enable, start, prescale
ADCSRA = (1 << ADEN) | (1 << ADSC) | ADC_PRSCL;
}
inline void ADC_off() {
ADCSRA &= ~(1<<ADEN); //ADC off
}
// Each full cycle runs 7.8X per second with just voltage enabled,
// or 3.9X per second with voltage and temperature.
#if defined(USE_LVP) && defined(USE_THERMAL_REGULATION)
#define ADC_CYCLES_PER_SECOND 4
#else
#define ADC_CYCLES_PER_SECOND 8
#endif
// TODO: is this better done in main() or WDT()?
ISR(ADC_vect) {
static uint8_t adc_step = 0;
// LVP declarations
#ifdef USE_LVP
#ifdef USE_LVP_AVG
#define NUM_VOLTAGE_VALUES 4
static int16_t voltage_values[NUM_VOLTAGE_VALUES];
#endif
static uint8_t lvp_timer = 0;
static uint8_t lvp_lowpass = 0;
#define LVP_TIMER_START (VOLTAGE_WARNING_SECONDS*ADC_CYCLES_PER_SECOND) // N seconds between LVP warnings
#define LVP_LOWPASS_STRENGTH ADC_CYCLES_PER_SECOND // lowpass for one second
#endif
// thermal declarations
#ifdef USE_THERMAL_REGULATION
#define NUM_THERMAL_VALUES 8
#define NUM_THERMAL_VALUES_HISTORY 16
#define NUM_THERMAL_PROJECTED_HISTORY 8
#define ADC_STEPS 4
static int16_t temperature_values[NUM_THERMAL_VALUES]; // last few readings in C
static int16_t temperature_history[NUM_THERMAL_VALUES_HISTORY]; // 14.1 fixed-point
static int16_t projected_temperature_history[NUM_THERMAL_PROJECTED_HISTORY]; // 14.1 fixed-point
static uint8_t projected_temperature_history_counter = 0;
static uint8_t temperature_timer = 0;
static uint8_t overheat_lowpass = 0;
static uint8_t underheat_lowpass = 0;
#define TEMPERATURE_TIMER_START (THERMAL_WARNING_SECONDS*ADC_CYCLES_PER_SECOND) // N seconds between thermal regulation events
#define OVERHEAT_LOWPASS_STRENGTH ADC_CYCLES_PER_SECOND // lowpass for one second
#define UNDERHEAT_LOWPASS_STRENGTH ADC_CYCLES_PER_SECOND // lowpass for one second
#else
#define ADC_STEPS 2
#endif
uint16_t measurement = ADC; // latest 10-bit ADC reading
adc_step = (adc_step + 1) & (ADC_STEPS-1);
#ifdef USE_LVP
// voltage
if (adc_step == 1) {
#ifdef USE_LVP_AVG
// prime on first execution
if (voltage == 0) {
for(uint8_t i=0; i<NUM_VOLTAGE_VALUES; i++)
voltage_values[i] = measurement;
voltage = 42; // Life, the Universe, and Everything (*)
} else {
uint16_t total = 0;
uint8_t i;
for(i=0; i<NUM_VOLTAGE_VALUES-1; i++) {
voltage_values[i] = voltage_values[i+1];
total += voltage_values[i];
}
voltage_values[i] = measurement;
total += measurement;
total = total >> 2;
voltage = (uint16_t)(1.1*1024*10)/total + VOLTAGE_FUDGE_FACTOR;
}
#else // no USE_LVP_AVG
// calculate actual voltage: volts * 10
// ADC = 1.1 * 1024 / volts
// volts = 1.1 * 1024 / ADC
voltage = (uint16_t)(1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR;
#endif
// if low, callback EV_voltage_low / EV_voltage_critical
// (but only if it has been more than N ticks since last call)
if (lvp_timer) {
lvp_timer --;
} else { // it has been long enough since the last warning
if (voltage < VOLTAGE_LOW) {
if (lvp_lowpass < LVP_LOWPASS_STRENGTH) {
lvp_lowpass ++;
} else {
// try to send out a warning
//uint8_t err = emit(EV_voltage_low, 0);
//uint8_t err = emit_now(EV_voltage_low, 0);
emit(EV_voltage_low, 0);
//if (!err) {
// on successful warning, reset counters
lvp_timer = LVP_TIMER_START;
lvp_lowpass = 0;
//}
}
} else {
// voltage not low? reset count
lvp_lowpass = 0;
}
}
}
#endif // ifdef USE_LVP
#ifdef USE_THERMAL_REGULATION
// temperature
else if (adc_step == 3) {
// Convert ADC units to Celsius (ish)
int16_t temp = measurement - 275 + THERM_CAL_OFFSET;
// prime on first execution
if (reset_thermal_history) {
reset_thermal_history = 0;
for(uint8_t i=0; i<NUM_THERMAL_VALUES; i++)
temperature_values[i] = temp;
for(uint8_t i=0; i<NUM_THERMAL_VALUES_HISTORY; i++)
temperature_history[i] = temp<<2;
for(uint8_t i=0; i<NUM_THERMAL_PROJECTED_HISTORY; i++)
projected_temperature_history[i] = temp<<2;
temperature = temp<<2;
} else { // update our current temperature estimate
uint8_t i;
int16_t total=0;
// rotate array
// FIXME: just move the index, don't move the values?
for(i=0; i<NUM_THERMAL_VALUES-1; i++) {
temperature_values[i] = temperature_values[i+1];
total += temperature_values[i];
}
temperature_values[i] = temp;
total += temp;
// Divide back to original range:
//temperature = total >> 2;
// More precise method: use noise as extra precision
// (values are now basically fixed-point, signed 13.2)
//temperature = total;
// 14.1 is less prone to overflows
temperature = total >> 2;
}
// guess what the temperature will be in a few seconds
{
uint8_t i;
int16_t diff;
int16_t t = temperature;
// algorithm tweaking; not really intended to be modified
// how far ahead should we predict?
#define THERM_PREDICTION_STRENGTH 3
// how proportional should the adjustments be?
#define THERM_DIFF_ATTENUATION 2
// acceptable temperature window size in C
#define THERM_WINDOW_SIZE 8
// highest temperature allowed
// (convert configured value to 14.1 fixed-point)
#define THERM_CEIL (((int16_t)therm_ceil)<<1)
// bottom of target temperature window (14.1 fixed-point)
#define THERM_FLOOR (THERM_CEIL - (THERM_WINDOW_SIZE<<1))
// rotate measurements and add a new one
for (i=0; i<NUM_THERMAL_VALUES_HISTORY-1; i++) {
temperature_history[i] = temperature_history[i+1];
}
temperature_history[NUM_THERMAL_VALUES_HISTORY-1] = t;
// guess what the temp will be several seconds in the future
// diff = rate of temperature change
//diff = temperature_history[NUM_THERMAL_VALUES_HISTORY-1] - temperature_history[0];
diff = t - temperature_history[0];
// projected_temperature = current temp extended forward by amplified rate of change
//projected_temperature = temperature_history[NUM_THERMAL_VALUES_HISTORY-1] + (diff<<THERM_PREDICTION_STRENGTH);
projected_temperature = t + (diff<<THERM_PREDICTION_STRENGTH);
// store prediction for later averaging
projected_temperature_history[projected_temperature_history_counter] = projected_temperature;
projected_temperature_history_counter = (projected_temperature_history_counter + 1) & (NUM_THERMAL_PROJECTED_HISTORY-1);
}
// average prediction to reduce noise
int16_t avg_projected_temperature = 0;
for (uint8_t i = 0; i < NUM_THERMAL_PROJECTED_HISTORY; i++)
avg_projected_temperature += projected_temperature_history[i];
avg_projected_temperature /= NUM_THERMAL_PROJECTED_HISTORY;
// cancel counters if appropriate
if (avg_projected_temperature > THERM_FLOOR) {
underheat_lowpass = 0; // we're definitely not too cold
} else if (avg_projected_temperature < THERM_CEIL) {
overheat_lowpass = 0; // we're definitely not too hot
}
if (temperature_timer) {
temperature_timer --;
} else { // it has been long enough since the last warning
// Too hot?
if (avg_projected_temperature > THERM_CEIL) {
if (overheat_lowpass < OVERHEAT_LOWPASS_STRENGTH) {
overheat_lowpass ++;
} else {
// how far above the ceiling?
int16_t howmuch = (avg_projected_temperature - THERM_CEIL) >> THERM_DIFF_ATTENUATION;
if (howmuch > 0) {
// try to send out a warning
emit(EV_temperature_high, howmuch);
}
// reset counters
temperature_timer = TEMPERATURE_TIMER_START;
overheat_lowpass = 0;
}
}
// Too cold?
else if (avg_projected_temperature < THERM_FLOOR) {
if (underheat_lowpass < UNDERHEAT_LOWPASS_STRENGTH) {
underheat_lowpass ++;
} else {
// how far below the floor?
int16_t howmuch = (THERM_FLOOR - avg_projected_temperature) >> THERM_DIFF_ATTENUATION;
if (howmuch > 0) {
// try to send out a warning (unless voltage is low)
// (LVP and underheat warnings fight each other)
if (voltage > VOLTAGE_LOW)
emit(EV_temperature_low, howmuch);
}
// reset counters
temperature_timer = TEMPERATURE_TIMER_START;
underheat_lowpass = 0;
}
}
}
}
#endif // ifdef USE_THERMAL_REGULATION
// start another measurement for next time
#ifdef USE_THERMAL_REGULATION
#ifdef USE_LVP
if (adc_step < 2) ADMUX = ADMUX_VCC;
else ADMUX = ADMUX_THERM;
#else
ADMUX = ADMUX_THERM;
#endif
#else
#ifdef USE_LVP
ADMUX = ADMUX_VCC;
#endif
#endif
}
#ifdef USE_BATTCHECK
#ifdef BATTCHECK_4bars
PROGMEM const uint8_t voltage_blinks[] = {
30, 35, 38, 40, 42, 99,
};
#endif
#ifdef BATTCHECK_6bars
PROGMEM const uint8_t voltage_blinks[] = {
30, 34, 36, 38, 40, 41, 43, 99,
};
#endif
#ifdef BATTCHECK_8bars
PROGMEM const uint8_t voltage_blinks[] = {
30, 33, 35, 37, 38, 39, 40, 41, 42, 99,
};
#endif
void battcheck() {
#ifdef BATTCHECK_VpT
blink_num(voltage);
#else
uint8_t i;
for(i=0;
voltage >= pgm_read_byte(voltage_blinks + i);
i++) {}
#ifdef DONT_DELAY_AFTER_BATTCHECK
blink_digit(i);
#else
if (blink_digit(i))
nice_delay_ms(1000);
#endif
#endif
}
#endif
#endif
|