-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
462 lines (381 loc) · 15.4 KB
/
main.cpp
File metadata and controls
462 lines (381 loc) · 15.4 KB
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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
// #define TEST // uncomment to enable test mode for simulation
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <math.h>
#include "MAX7219_attiny.h"
#include "font.h"
#include "errors.h"
#include "ShiftRegister.h"
#include "sevenSegmentsFSM.h"
// Pins definition
#define HB_LED PD3 // heartbeat LED
#define PIN_UART_TX PD1
#define PIN_I2C_SDA PC4
#define PIN_I2C_SCL PC5
// VL53L0X sensor library
#include "vl53l0x-non-arduino/VL53L0X.h"
#include "vl53l0x-non-arduino/util/i2cmaster.h"
#include "vl53l0x-non-arduino/util/debugPrint.h"
#include "vl53l0x-non-arduino/util/millis.h"
#define __COMPILING_AVR_LIBC__ 1 // just to avoid some intellisence annoying messages
#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif // !FALSE
// some constants
#define SENSOR_HEIGHT 10
#define FULL_WATER 190
#define TOO_FAR_HEIGHT (FULL_WATER + SENSOR_HEIGHT + 10)
#define TOO_CLOSE_HEIGHT 10
#define HYSTERESIS 5
uint16_t measureDistance(uint16_t *output);
void flashValue(uint16_t value);
void pushError(uint8_t errorValue);
#include "calibrate.h"
void pushError(uint8_t errorValue);
uint16_t EEMEM EE_FullHeight = FULL_WATER + SENSOR_HEIGHT; // eeprom variable in case we calibrate and save the actual values
volatile uint16_t FullHeight = 0;
volatile uint16_t distance; // decalred as global for debug
volatile uint16_t levelInPercent; // same as above, but this is a claculated percentage of water level
// calculate a bargraph representation of a value then shift it to fit the shift register
// the bargraph contains 10 leds, the percentage is rounded up to 10s, example : 51 is displayed as 6leds, 0 is 1 led, 34 is 4 leds..etc
uint32_t calculateBarGraph(uint16_t valueInPercent, uint8_t shift)
{
if (valueInPercent > 100) // check limits
valueInPercent = 100;
uint32_t result = 0;
uint8_t valueInTens = ((valueInPercent / 10) + 1) % 11; // round up the percents to tens
while (valueInTens--) // for each "1" in tens (i.e for each 10 in percents) light an led
result = (result << 1) | 1;
return result << shift; // shift the final result to the desired bit position (see the call lcoation for more info)
}
// a stack of detected errors to be displayed in in the seven segment
uint8_t errors[4] = {0};
// are we displaying errors or level percentage?
enum
{
SevenSegDisplayingError,
SevenSegDisplayingWaterLevel
} StateOfSevenSeg = SevenSegDisplayingWaterLevel;
// swap the order of bits, due to the way the bargraph is wired in the PCB
uint8_t swapBits(uint8_t input)
{
uint8_t result = 0;
for (int i = 0; i < 8; i++)
result = (result << 1) | ((input >> i) & 1);
return result;
}
// the seven segment display system is based on a finite state machine
// first either the character "P" (for percents) or "F" (for faults) is displayed
// then follwed by each number of the percentage of water the error code resp.
// each character is displayed for 500ms then a 100ms of blank to mark the transition between characters,
// for example 33% is displayed as : 'P' [short_blank] '0' [short_blank] '3' [short_blank] '3' [long_blank], this way the two identical numbers can be told apart
//
// the use of a finite state machine makes easy to keep track of what should be dispalyed next since the updated is called by
// a timer interrupt function, and no loop should block the interrupt thread
SevenSegmentsFSM levelDisplayFSM(3, SevenSegP); // initialize the level display finite state machine as 3 character display
SevenSegmentsFSM errorDisplayFSM(2, SevenSegF); // ...as a 2 character dispaly
// event handler executed when the error display fsm finishes executing the last phase
void errorDisplayFSM_OnLastPhaseDoneHandler()
{
StateOfSevenSeg = SevenSegDisplayingWaterLevel; // switch to display level, if an other error is still in the stack the display update function will automatically switch back to displaying errors the next time it's called
levelDisplayFSM.Reinit(); // reinit the water level display fsm (to display the message from the beginning)
}
// even handler executed when the level display fms starts executing the first phase
void levelDisplayFSM_OnFirstPhaseStarted()
{
levelDisplayFSM.value = levelInPercent; // set the value to be displayed
}
volatile uint32_t lastSevenSegUpdate = 0; // the last time when the seven segment value was updated
volatile uint8_t previous7SegValue = 0; // since the seven segment is updated at slower pace, its previous state is being buffered until next update
// this function calculates the value of 7seg display whether it's an error code or percentage
uint8_t calculate7Seg()
{
// code to select the FSM currently being executed
SevenSegmentsFSM *activeFSM = StateOfSevenSeg == SevenSegDisplayingWaterLevel ? &levelDisplayFSM : &errorDisplayFSM;
#ifndef TEST // if test mode is enabled this timing system is bypassed
// update the seven segment value every 500ms (or 100ms in case of flicker), during that time the prvious value is returned
uint32_t now = millis();
if (lastSevenSegUpdate && (now - lastSevenSegUpdate) < (activeFSM->isFlickerDone(1) ? 100 : 500))
return previous7SegValue;
lastSevenSegUpdate = now;
#endif
if (StateOfSevenSeg == SevenSegDisplayingWaterLevel) // if we're displaying level we check for the presence of error codes, if we're already displaying an error we let it finish first, and when it's done it will switch back to display levels, then we can proced to display the next error (if it exists)
{
if (errors[0]) // we have an error? then reset the display state to prepare for a new display of error code
{
StateOfSevenSeg = SevenSegDisplayingError;
errorDisplayFSM.value = errors[0];
activeFSM = &errorDisplayFSM;
for (uint8_t i = 0; i < sizeof(errors) - 1; i++) // pop the error being displayed from the stack
errors[i] = errors[i + 1];
errors[sizeof(errors) - 1] = 0;
}
}
activeFSM->Execute();
return (previous7SegValue = activeFSM->LastResult());
}
// macro to set/clear the 10th bit of the bargraph
#define setA9 PORTD |= _BV(PD4)
#define clearA9 PORTD &= ~_BV(PD4)
// Atmega328 ][ seconds shift rehister ][ first shift register ]
// PD4 ][ Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 ][ Q7 Q6 Q5 Q4 Q3 Q2 Q1 Q0 ]
// | | | | | | | | | | | | | | | | |
// [ A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 ][ G F E D C B A ]
// [ bargraph ][ seven segments ]
// the first shift register has the data of the seven segment display and the the first bit of the bargraph
// the second shift register has 8 bits of the data of the bargraph swapped (because of schematic)
// the 10 bit of the bargraph is conencted to pin PD4 of the micro controller
void displayInShiftRegister()
{
uint32_t bargraphValue = calculateBarGraph(levelInPercent, 7);
uint8_t sevenSegValue = calculate7Seg();
uint32_t shiftRegisterValue = bargraphValue | sevenSegValue;
WriteRegister(swapBits(((uint8_t *)&shiftRegisterValue)[1])); // write the data of the bargraph
WriteRegister(((uint8_t *)&shiftRegisterValue)[0]); // write the data to be displayed by the seven seg
if ((shiftRegisterValue >> 16) & 1) // complete the data of the bargraph
setA9;
else
clearA9;
UpdateRegister(); // latch
}
uint16_t measureDistance(uint16_t *output) // in cm
{
statInfo_t xTraStats;
uint16_t result = readRangeSingleMillimeters(&xTraStats);
if ((result | 1) == 8191) // in case of error
{
*output = result;
return false;
}
*output = result / 10;
debug_dec(*output);
debug_str("cm ");
return true;
}
#define N_SAMPLES 10
uint16_t data[N_SAMPLES] = {0};
uint16_t calculateMean(uint16_t *data, uint16_t len)
{
uint16_t mean = 0;
for (uint16_t i = 0; i < len; i++)
mean += data[i];
return mean / len;
}
// display integer value in MAX7219 display
volatile uint8_t AutoRefreshCounter = 0; // for some reason my display shows weird character that I had to reinit it regularly
void displayInt(uint16_t value, uint8_t isPercent)
{
if (++AutoRefreshCounter == 20) // reinit the display every 20 call
{
AutoRefreshCounter = 0;
Max7219_Init();
}
uint8_t oldSreg = SREG;
cli(); // disable all interrupts
uint8_t *data[5];
// the display has 4 screens, the characters are sent in reverse (last one sent first)
// the number is coded as a percent charater followed by 4 digits
// if the dispalyed number is a percentage the percent character is the first one and only three digits are displayed,
// otherwise its probably an error code so 4 digits are displayed
data[0] = (uint8_t *)(percent);
data[1] = (uint8_t *)(numbers[(value) % 10]);
data[2] = (uint8_t *)(numbers[(value / 10) % 10]);
data[3] = (uint8_t *)(numbers[(value / 100) % 10]);
data[4] = (uint8_t *)(numbers[(value / 1000) % 10]);
uint8_t **first_data = &data[isPercent ? 0 : 1]; // if it's percentage start from the percent character, otherwsise skip it
for (int col = 0; col < 8; col++)
{
Write_Max7219(col + 1,
get_line_from_8x8_matrix(first_data[0], col),
get_line_from_8x8_matrix(first_data[1], col),
get_line_from_8x8_matrix(first_data[2], col),
get_line_from_8x8_matrix(first_data[3], col));
}
SREG = oldSreg;
}
// display a number (not in percent) for one second then clear the screen for 200ms
// usually used to display constants (Fullheight is flashed at startup for example)
void flashValue(uint16_t value)
{
displayInt(value, FALSE);
_delay_ms(1000);
Clear_Max7219();
_delay_ms(200);
}
// write the duty cycle of the heart beat led
void pwmWrite(uint8_t value)
{
OCR2B = value;
}
// initialize the timer1 for 10ms interrupt
void initTimer1()
{
//--------------------------------------------------
// Timer1 for the heartbeat update
//--------------------------------------------------
TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10); // Clear Timer on Compare Match (Mode 4), no pin output, TOP=OCR1A, 64 prescaler
OCR1A = ((F_CPU / 64) / 100); // every 10ms
TIMSK1 = (1 << OCIE1A);
}
// init the pwm pin
void pwmInit()
{
DDRD |= _BV(HB_LED);
TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); // Fast pwm mode, output in OC2B pin (PD3)
TCCR2B = _BV(CS20); // no prescaling for the timer
pwmWrite(0); // initially set the duty cycle at 0
}
// send signal on TX and read in RX at startup,
// if it's the same then RX and TX are shorted and calibration is done
extern volatile uint8_t calibrateRequested;
void CheckCalibrationOnStartup()
{
uint8_t readValue = 0;
DDRD = _BV(PD1); // TX as output
for (uint8_t i = 0; i < 8; i++)
{
if (('C' >> i) & 1)
PORTD |= 1;
else
PORTD &= ~1;
_delay_ms(10);
if (PIND & _BV(PD0))
readValue |= 1 << i;
}
calibrateRequested = readValue == 'C';
}
// initialization function, to init several peripherals, GPIOs and timers
void init()
{
CheckCalibrationOnStartup();
debugInit();
//--------------------------------------------------
// GPIOs
//--------------------------------------------------
UCSR0B &= ~_BV(RXEN0); // Disable UART RX
DDRD = _BV(PIN_UART_TX);
DDRD |= _BV(PD4); // PD4 as output for A9 of bargraph
i2c_init();
initMillis();
#ifndef TEST
initTimer1();
#endif
pwmInit();
InitShiftRegister();
errorDisplayFSM.OnLastPhaseDone = errorDisplayFSM_OnLastPhaseDoneHandler;
levelDisplayFSM.OnFirstPhaseStarted = levelDisplayFSM_OnFirstPhaseStarted;
sei();
}
volatile uint8_t heartBeatValue = 20; // the initial value of the hearbeat pwm
volatile int8_t heartBeatDelta = 1; // the step to progress the pwm value
void heartBeat()
{
if ((heartBeatValue > 100) | (heartBeatValue < 20)) // every time a max/min value is reached change the direction of the pwm
heartBeatDelta = -heartBeatDelta;
heartBeatValue += heartBeatDelta; // increase (or decrease) the pwm value
pwmWrite(heartBeatValue);
}
// interrupt handler for the timer1 compare match that ticks every 10ms
ISR(TIMER1_COMPA_vect)
{
heartBeat(); // update the heartbeat pwm
displayInShiftRegister(); //
}
// push an error code to the errors stack
void pushError(uint8_t errorValue)
{
for (int i = sizeof(errors) - 1; i > 0; i--) // shift the values inside the errors stack
errors[i] = errors[i - 1];
errors[0] = errorValue; // the first element is the latest error
}
// #define TROUBLE
int main(void)
{
#ifdef TROUBLE
// DDRD |= _BV(HB_LED);
// initTimer1();
// pwmInit();
// sei();
init();
Max7219_Init();
while (1)
{
flashValue(1234);
}
#endif
init();
// this code is used for tests in simulator, so the measurments are generated and the timers are replaced by delays
#ifdef TEST
levelInPercent = 11;
int8_t step = 6;
int8_t errorCounter = 20;
while (1)
{
if (errorCounter++ == 20)
{
errors[0] = 12;
errors[1] = 24;
errorCounter = 0;
}
if (levelInPercent > 90)
step = -6;
else if (levelInPercent < 11)
step = 6;
levelInPercent += step;
displayInShiftRegister();
debug_dec(levelDisplayFSM.value);
debug_putc(' ');
if ((StateOfSevenSeg == SevenSegDisplayingWaterLevel ? levelDisplayFSM : errorDisplayFSM).isFlickerDone(1))
_delay_ms(100);
else
_delay_ms(500);
}
#endif
Max7219_Init();
flashValue(0);
initVL53L0X(0);
flashValue(1);
// configure the VLC53L0X sensor
setSignalRateLimit(0.1);
setVcselPulsePeriod(VcselPeriodPreRange, 18);
setVcselPulsePeriod(VcselPeriodFinalRange, 14);
setMeasurementTimingBudget(500 * 1000UL);
// read the fullheight from EEPROM, it's eventually updated if calibration is enabled
FullHeight = eeprom_read_word(&EE_FullHeight);
flashValue(FullHeight); // display full height for 1 sec then clear sceen
calibrate(); // do the calibration (if enable)
while (1) // main loop
{
/* uint16_t distance = measureMeanDistance(); // start the distance measurement and return the result */
uint16_t result = 0;
if (!measureDistance(&result))
{
pushError(result - 8100); // display the error code in the seven segments (either 90 or 91)
continue;
}
distance = result;
#define FILTER
#ifdef FILTER
#define alpha 0.4f
// low pass filter
static float prevValue = 0.0f; // inititalized at 0
prevValue = alpha * distance + (1.0 - alpha) * prevValue;
distance = (uint16_t)prevValue;
#endif
levelInPercent = ((FullHeight - distance) * 100 / FULL_WATER);
if (levelInPercent > 100) // maximum 100%
{
levelInPercent = 100;
// FullHeight = FULL_WATER + mean_distance; // update fullheight
}
debug_dec(levelInPercent); // send value through serial
debug_str("% ");
displayInt(levelInPercent, TRUE); // update the MAX7219
_delay_ms(100);
}
}