2017-03-08 16:47:33 +00:00
|
|
|
#include <limits.h>
|
2017-05-31 15:33:42 +00:00
|
|
|
#include <SPI.h>
|
|
|
|
#include <TFT.h>
|
|
|
|
//#include <SD.h>
|
|
|
|
// display setup
|
2017-03-08 16:47:33 +00:00
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
#define CS 18
|
|
|
|
#define DC 19
|
|
|
|
#define RESET 17
|
2017-03-08 16:47:33 +00:00
|
|
|
|
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
|
|
|
|
// create an instance of the library
|
|
|
|
TFT TFTscreen = TFT(CS, DC, RESET);
|
|
|
|
|
|
|
|
boolean setup_ok = true;
|
|
|
|
const int N = 16; // number of sample averages
|
2017-03-08 16:47:33 +00:00
|
|
|
volatile unsigned int deltas[N]; // array of time deltas between interrupt
|
|
|
|
volatile unsigned int n = 0; // run variable
|
|
|
|
unsigned long total;
|
|
|
|
float flow;
|
2017-05-31 15:33:42 +00:00
|
|
|
struct rgb {
|
|
|
|
int red=0;
|
|
|
|
int green=0;
|
|
|
|
int blue=0;
|
|
|
|
};
|
2017-03-08 16:47:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
// begin user configuration parameters
|
|
|
|
/*
|
|
|
|
This is the prescaler setting for timer1:
|
|
|
|
The time base of the timer counter TCNT1
|
|
|
|
t_step or seconds/tick = prescaler/clock
|
2017-03-08 16:47:33 +00:00
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
Note: Maximum TCNT1 value is 65535, i.e.
|
|
|
|
Maximum time lengths (and thus timeouts) are:
|
|
|
|
8 0.0327675 s
|
|
|
|
64 0.26214 s
|
|
|
|
256 1.04856 s
|
|
|
|
1024 4.19424 s
|
|
|
|
*/
|
|
|
|
#define PRESCALER 256 // todo: make this dependent on timeout
|
2017-03-08 16:47:33 +00:00
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
/*
|
|
|
|
The flow meter can measure from 0.24 to 17 l/min at 4 pulses/s per l/min
|
|
|
|
max period about 1s, i.e. min flow of 0.24l/min
|
|
|
|
min period about 15 ms i.e. max flow of 17l/min
|
2017-03-08 16:47:33 +00:00
|
|
|
*/
|
|
|
|
#define TIMEOUT 1.0 // measurement timeout in Hz (needs to be less than timer1 limits above)
|
|
|
|
#define DISPLAY_INTERVAL 1.0 // display update interval (and serial send interval)
|
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
const float conversion = 4.0; // conversion factor, pulses/s per l/min
|
|
|
|
const float flow_threshold = 0.5; // alarm threshold for flow
|
|
|
|
// end user configuration parameters
|
2017-03-08 16:47:33 +00:00
|
|
|
|
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
// begin hardware configuration / wiring setup
|
|
|
|
// Pin 13 has an LED connected on most Arduino boards.
|
|
|
|
// alarm LED pin
|
|
|
|
#define ALARM_LED_PIN 4
|
|
|
|
// interlock pin
|
|
|
|
#define INTERLOCK_PIN 5
|
|
|
|
#define OVERTEMP_PIN 14
|
2017-03-08 16:47:33 +00:00
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
// end hardware configuration / wiring setup
|
2017-03-08 16:47:33 +00:00
|
|
|
|
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
// temp. buffer for string manipulation/formatting
|
|
|
|
char buff[10];
|
|
|
|
char old_flow_string[32];
|
|
|
|
char new_flow_string[32];
|
|
|
|
char old_status_string[16] = "yyyyyyyyyyyyyyy";
|
|
|
|
char new_status_string[16];
|
|
|
|
|
|
|
|
|
|
|
|
boolean status_change = true;
|
|
|
|
boolean status_safety = false;
|
2017-03-08 16:47:33 +00:00
|
|
|
|
|
|
|
void setup()
|
|
|
|
{
|
2017-05-31 15:33:42 +00:00
|
|
|
SPI.setClockDivider(SPI_CLOCK_DIV2);
|
|
|
|
TFTscreen.begin();
|
|
|
|
// clear the screen with a black background
|
|
|
|
struct rgb bg;
|
|
|
|
bg.red = 0;
|
|
|
|
bg.green = 0;
|
|
|
|
bg.blue = 0;
|
|
|
|
TFTscreen.background(bg.red, bg.green, bg.blue);
|
|
|
|
// write the static text to the screen
|
|
|
|
// set the font color to white
|
|
|
|
TFTscreen.stroke(255, 255, 255);
|
|
|
|
// set the font size
|
|
|
|
TFTscreen.setTextSize(2);
|
|
|
|
// write the text to the top left corner of the screen
|
|
|
|
TFTscreen.text("PFG Safety", 0, 0);
|
|
|
|
|
|
|
|
|
2017-03-08 16:47:33 +00:00
|
|
|
Serial.begin(19200); // serial line speed
|
|
|
|
/* Interrupt pins
|
2017-05-31 15:33:42 +00:00
|
|
|
UNO:
|
|
|
|
interrupt 1 is pin 3
|
|
|
|
interrupt 0 is pin 2
|
2017-03-08 16:47:33 +00:00
|
|
|
*/
|
|
|
|
attachInterrupt(0, get_time, FALLING);
|
|
|
|
pinMode(ALARM_LED_PIN, OUTPUT);
|
|
|
|
pinMode(INTERLOCK_PIN, OUTPUT);
|
2017-05-31 15:33:42 +00:00
|
|
|
pinMode(OVERTEMP_PIN, INPUT);
|
2017-03-08 16:47:33 +00:00
|
|
|
|
2017-05-31 15:33:42 +00:00
|
|
|
// starting values for the deltas array
|
|
|
|
// otherwise the array values are maybe zero at the beginning and we get very high (wrong) flow values
|
|
|
|
for (int k = 0; k < N; k++) {
|
|
|
|
deltas[k] = UINT_MAX;
|
2017-03-08 16:47:33 +00:00
|
|
|
}
|
2017-05-31 15:33:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
timer1 setup
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2017-03-08 16:47:33 +00:00
|
|
|
noInterrupts(); // disable all interrupts
|
|
|
|
// clear timer1 registers
|
|
|
|
TCCR1A = 0;
|
|
|
|
TCCR1B = 0;
|
2017-05-31 15:33:42 +00:00
|
|
|
TCNT1 = 0; // timer1 counter, reset on every HW interrupt pulse from the flow meter (see get_time function)
|
|
|
|
OCR1A = (int)(16e6 / PRESCALER * TIMEOUT); // Clear timer on compare match 16MHz/PRESCALER*timeout(in s)
|
2017-03-08 16:47:33 +00:00
|
|
|
TCCR1B |= (1 << WGM12); // CTC mode (Clear Timer on Compare Match)
|
|
|
|
/*
|
2017-05-31 15:33:42 +00:00
|
|
|
Prescaler settings x=0,1,2 (Timer0, Timer1, Timer2)
|
|
|
|
CSx2 CSx1 CSx0 Beschreibung
|
|
|
|
0 1 0 Clk/8 (1 << CS11)
|
|
|
|
0 1 1 Clk/64 (1 << CS11)|(1 << CS10)
|
|
|
|
1 0 0 Clk/256 (1 << CS12)
|
|
|
|
1 0 1 Clk/1024 (1 << CS12)|(1 << CS10)
|
2017-03-08 16:47:33 +00:00
|
|
|
*/
|
2017-05-31 15:33:42 +00:00
|
|
|
if (PRESCALER == 8) {
|
2017-03-08 16:47:33 +00:00
|
|
|
TCCR1B |= (1 << CS11);
|
2017-05-31 15:33:42 +00:00
|
|
|
if (TIMEOUT > 0.0327675) {
|
|
|
|
interrupts();
|
|
|
|
//lcd.setCursor (0,1); // char 0 on line 0 or 1
|
|
|
|
//lcd.print ("CFGERR TIMEOUT");
|
|
|
|
setup_ok = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (PRESCALER == 64) {
|
|
|
|
if (TIMEOUT > 0.26214) {
|
|
|
|
interrupts();
|
|
|
|
//lcd.setCursor (0,1); // char 0 on line 0 or 1
|
|
|
|
//lcd.print ("CFGERR TIMEOUT");
|
|
|
|
setup_ok = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
TCCR1B |= (1 << CS11) | (1 << CS10);
|
|
|
|
}
|
|
|
|
else if (PRESCALER == 256) {
|
|
|
|
if (TIMEOUT > 1.04856) {
|
|
|
|
interrupts();
|
|
|
|
//lcd.setCursor (0,1); // char 0 on line 0 or 1
|
|
|
|
//lcd.print ("CFGERR TIMEOUT");
|
|
|
|
setup_ok = false;
|
|
|
|
return;
|
|
|
|
}
|
2017-03-08 16:47:33 +00:00
|
|
|
TCCR1B |= (1 << CS12);
|
2017-05-31 15:33:42 +00:00
|
|
|
}
|
|
|
|
else if (PRESCALER == 1024) {
|
|
|
|
if (TIMEOUT > 4.19424) {
|
|
|
|
interrupts();
|
|
|
|
//lcd.setCursor (0,1); // char 0 on line 0 or 1
|
|
|
|
//lcd.print ("CFGERR TIMEOUT");
|
|
|
|
setup_ok = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
TCCR1B |= (1 << CS12) | (1 << CS10);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
interrupts();
|
|
|
|
// lcd.setCursor (0,1); // char 0 on line 0 or 1
|
|
|
|
// lcd.print ("CFGERR prescaler");
|
|
|
|
setup_ok = false;
|
|
|
|
return;
|
|
|
|
}
|
2017-07-28 11:37:22 +00:00
|
|
|
//int prescaler = PRESCALER;
|
|
|
|
//snprintf(new_flow_string, sizeof(new_flow_string), "PRESCALER %i", prescaler);
|
|
|
|
//TFTscreen.text("PRESCALER",0,30);
|
2017-03-08 16:47:33 +00:00
|
|
|
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
|
|
|
|
interrupts(); // enable all interrupts
|
2017-07-28 11:37:22 +00:00
|
|
|
delay(1000);
|
2017-03-08 16:47:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// if after OCR1A seconds no pulse from flow meter occured an interrupt will force a very high delta value
|
2017-05-31 15:33:42 +00:00
|
|
|
ISR(TIMER1_COMPA_vect) { // function which will be called when an interrupt occurs at timer1
|
2017-03-08 16:47:33 +00:00
|
|
|
deltas[n] = UINT_MAX; // add maximum value in the current array position
|
|
|
|
n++;
|
2017-05-31 15:33:42 +00:00
|
|
|
if (n == N) n = 0; // wrap around
|
2017-03-08 16:47:33 +00:00
|
|
|
}
|
2017-05-31 15:33:42 +00:00
|
|
|
|
2017-03-08 16:47:33 +00:00
|
|
|
void loop()
|
|
|
|
{
|
2017-05-31 15:33:42 +00:00
|
|
|
struct rgb bg;
|
|
|
|
if (!setup_ok) return;
|
|
|
|
|
|
|
|
total = 0;
|
|
|
|
// sum up all deltas in array
|
|
|
|
for (int k = 0; k < N; k++) {
|
|
|
|
// timeout occured, break out of loop
|
|
|
|
if (deltas[k] == UINT_MAX) {
|
|
|
|
total = ULONG_MAX; // and set total to highest possible value
|
|
|
|
break;
|
2017-03-08 16:47:33 +00:00
|
|
|
}
|
2017-05-31 15:33:42 +00:00
|
|
|
total += deltas[k];
|
|
|
|
}
|
|
|
|
// and calculate average flow
|
|
|
|
flow = N / (float)total / conversion * 16e6 / PRESCALER;
|
|
|
|
|
|
|
|
// flow
|
|
|
|
//flow = total/N/conversion;
|
|
|
|
// format decimal value and save string in buff
|
|
|
|
dtostrf(flow, 5, 3, buff);
|
|
|
|
|
|
|
|
/* security logic
|
|
|
|
|
|
|
|
if flow is too small,i.e. flow < flow_threshold:
|
2017-03-08 16:47:33 +00:00
|
|
|
a) alarm LED on
|
|
|
|
b) interlock LOW
|
2017-05-31 15:33:42 +00:00
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
strcpy(old_flow_string, new_flow_string);
|
|
|
|
strcpy(old_status_string, new_status_string);
|
|
|
|
|
|
|
|
snprintf(new_flow_string, sizeof(new_flow_string), "%s l/min", buff );
|
|
|
|
Serial.println(new_flow_string); // transmit flow
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((flow < flow_threshold) ||digitalRead(OVERTEMP_PIN) ) { // flow too small, rise alarm
|
|
|
|
if (status_safety == true) {
|
|
|
|
status_change = true;
|
2017-03-08 16:47:33 +00:00
|
|
|
}
|
|
|
|
else {
|
2017-05-31 15:33:42 +00:00
|
|
|
status_change = false;
|
2017-03-08 16:47:33 +00:00
|
|
|
}
|
2017-05-31 15:33:42 +00:00
|
|
|
status_safety = false;
|
|
|
|
digitalWrite(ALARM_LED_PIN, HIGH);
|
|
|
|
digitalWrite(INTERLOCK_PIN, LOW);
|
|
|
|
//if (status_change) {
|
|
|
|
bg.red=255;
|
|
|
|
bg.green=0;
|
|
|
|
bg.blue=0;
|
|
|
|
TFTscreen.background(bg.red, bg.green, bg.blue);
|
|
|
|
//}
|
|
|
|
if (digitalRead(OVERTEMP_PIN))
|
|
|
|
snprintf(new_status_string, sizeof(new_status_string), "Status: %s", "TEMP!" );
|
|
|
|
else
|
|
|
|
snprintf(new_status_string, sizeof(new_status_string), "Status: %s", "FLOW!" );
|
|
|
|
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (status_safety == false) {
|
|
|
|
status_change = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
status_change = false;
|
|
|
|
}
|
|
|
|
status_safety = true;
|
|
|
|
digitalWrite(ALARM_LED_PIN, LOW);
|
|
|
|
digitalWrite(INTERLOCK_PIN, HIGH);
|
|
|
|
if (status_change) {
|
|
|
|
bg.red=0;
|
|
|
|
TFTscreen.background(bg.red, bg.green, bg.blue);
|
|
|
|
//TFTscreen.text("Status:", 0, 20);
|
|
|
|
//TFTscreen.text("l/min", 6 *((20+2)/2), 40);
|
|
|
|
}
|
|
|
|
snprintf(new_status_string, sizeof(new_status_string), "Status: %s", "OK" );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//Serial.begin(19200); // serial line speed
|
|
|
|
//Serial.println(new_status_string); // transmit flow
|
|
|
|
//Serial.end();
|
|
|
|
|
|
|
|
writeText(new_status_string, old_status_string, 2, 8 * ((20 + 2) / 2) * 0, 20, bg);
|
|
|
|
writeText(new_flow_string, old_flow_string, 2, 0, 40, bg);
|
|
|
|
delay((int) (DISPLAY_INTERVAL * 1000));
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeText(char* newtext, char* oldtext, int textWidth, int x, int y, struct rgb bg) {
|
|
|
|
TFTscreen.setTextSize(textWidth);
|
|
|
|
int n = 13; //max(strlen(newtext),strlen(oldtext));
|
|
|
|
int charWidth = textWidth * 10 / 2 + 2;
|
|
|
|
int curPos = x;
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
if ((oldtext[i] != newtext[i]) && (i < strlen(oldtext))) { // only write changed characters
|
|
|
|
// delete old char
|
|
|
|
TFTscreen.stroke(0, 0, 0);
|
|
|
|
TFTscreen.text(&oldtext[i], curPos, y);
|
|
|
|
}
|
|
|
|
// write new char
|
|
|
|
TFTscreen.stroke(255, 255, 255);
|
|
|
|
TFTscreen.text(&newtext[i], curPos, y);
|
|
|
|
// draw a rectangle to the end of the screen
|
|
|
|
if (i >= strlen(newtext)) {
|
|
|
|
TFTscreen.stroke(bg.red, bg.green, bg.blue);
|
|
|
|
TFTscreen.rect(curPos, y, TFTscreen.width() - curPos, textWidth * 10);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// advance position
|
|
|
|
curPos += charWidth;
|
|
|
|
|
|
|
|
}
|
2017-03-08 16:47:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void get_time() {
|
|
|
|
deltas[n] = TCNT1; // save current timer1 count
|
|
|
|
n++;
|
2017-05-31 15:33:42 +00:00
|
|
|
if (n == N) n = 0; // wrap around
|
2017-03-08 16:47:33 +00:00
|
|
|
TCNT1 = 0; // reset timer1 counter
|
|
|
|
}
|