// This software, known as CarbOnBal is
// Copyright, 2017-2020 L.L.M. (Dennis) Meulensteen. [email protected]
//
// This file is part of CarbOnBal. A combination of software and hardware.
// I hope it may be of some help to you in balancing your carburetors and throttle bodies.
// Always be careful when working on a vehicle or electronic project like this.
// Your life and health are your sole responsibility, use wisely.
//
// CarbOnBal hardware is covered by the CERN Open Hardware License v1.2
// a copy of the text is included with the source code.
//
// CarbOnBal 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.
//
// CarbOnBal 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 CarbOnBal. If not, see <http://www.gnu.org/licenses/>.
#define DEBUG
#include "Arduino.h"
#include "EEPROM.h"
#define USE_ARDUINO_INTERRUPTS true // Set-up low-level interrupts for most acurate BPM math.
#include <PulseSensorPlayground.h> // Includes the PulseSensorPlayground Library.
#include "functions.h"
#include "globals.h"
#include LANGUAGE
#include "lang_generic.h"
#include "lcdWrapper.h"
#include "menu.h"
#include "menuActions.h"
#include "utils.h"
settings_t settings;
uint8_t settingsOffset = 2;
// The software uses a lot of global variables. This may not be elegant but its one way of writing non-blocking code
int refresh_r = 100;
int inputPin[NUM_SENSORS] = { A1, A2 }; //used as a means to access the sensor pins using a counter
int timeBase = 0; //allows us to calculate how long it took to measure and update the display (only used for testing)
long sums[NUM_SENSORS] = { 0, 0 }; //tracks totals for calculating a numerical average
bool freezeDisplay = false; //used to tell when a user wants to freeze the display
unsigned int rpm; //stores the current RPM
const int crete = 500;
unsigned int average[NUM_SENSORS]; //used to share the current average for each sensor
unsigned int min_val[NUM_SENSORS] = {crete,crete}; //used to share the current average for each sensor
int ambientPressure; //stores current ambient pressure for negative pressure display
unsigned long lastUpdate;
int emaTarget = -1;
unsigned long emaMillis = 0;
longAverages avg[NUM_SENSORS];
uint8_t labelPosition = 0;
unsigned long startTime;
PulseSensorPlayground pulseSensor(2);
//this does the initial setup on startup.
void setup() {
lcd_begin(DISPLAY_COLS, DISPLAY_ROWS);
pinMode(LED_BUILTIN, OUTPUT);
settings = loadSettings(settings); //load saved settings into memory from FLASH
pulseSensor.analogInput(inputPin[0], 0);
pulseSensor.setThreshold(crete, 0);
pulseSensor.analogInput(inputPin[1], 1);
pulseSensor.setThreshold(crete, 1);
if (!pulseSensor.begin()) {
/*
PulseSensor initialization failed,
likely because our Arduino platform interrupts
aren't supported yet.
If your Sketch hangs here, try changing USE_ARDUINO_INTERRUPTS to false.
*/
for (;;) {
// Flash the led to show things didn't work.
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
}
#ifdef DEBUG
Serial.begin(BAUD_RATE);
#endif
setInputActiveLow(SELECT); //set the pins connected to the switches
setInputActiveLow(LEFT); //switches are wired directly and could
setInputActiveLow(RIGHT); //short out the pins if setup wrong
setInputActiveLow(CANCEL);
analogWrite(brightnessPin, settings.brightness); //brightness is PWM driven
analogWrite(contrastPin, settings.contrast); //contrast is PWM with smoothing (R/C network) to prevent flicker
ambientPressure = detectAmbient(); //set ambient pressure (important because it varies with weather and altitude)
}
void loop() {
int factor = settings.damping;
int rpm_factor = settings.rpmDamping;
longAverages rpmAverage;
startTime = micros();
if (emaTarget >= 0) {
if ((millis() - emaMillis) >= 125) {
if (emaTarget > settings.damping) {
settings.damping++;
} else {
emaTarget = -1;
}
emaMillis = millis();
}
}
switch (buttonPressed()) { //test if a button was pressed
case SELECT:
actionDisplayMainMenu();
break; //the menu is the only function that does not return asap
case LEFT:
settings.damping = (int8_t) (doBasicSettingChanger(txtDampingPerc,0, 100, (int8_t) settings.damping * 6.25, 6) / 6.25);
actionSaveSettings();
lcd_clear();
break;
case RIGHT:
settings.rpmDamping = (int8_t) (doBasicSettingChanger(txtRpmDampingPerc, 0, 100,(int8_t) settings.rpmDamping * 6.25, 6) / 6.25);
actionSaveSettings();
lcd_clear();
break;
case CANCEL:
lcd_clear();
do_raw_data();
// freezeDisplay = !freezeDisplay; //toggle the freezeDisplay option
// freezeDisplay = false;
// doRevs();
break;
}
for (int sensor = 0; sensor < 2; sensor++) { //loop over all sensors
min_val[sensor] = min (min_val[sensor], pulseSensor.getLatestSample(sensor));
if (pulseSensor.sawStartOfBeat(sensor)){
avg[sensor].longVal = longExponentialMovingAverage(factor, avg[sensor].longVal, min_val[sensor]);
average[sensor] = avg[sensor].intVal[1];
min_val[sensor]=1024;
}
if (sensor == 0) {
// average[sensor] += (int8_t) EEPROM.read(getCalibrationTableOffsetByValue(sensor, average[sensor]));
rpmAverage.longVal = longExponentialMovingAverage(rpm_factor,
rpmAverage.longVal, pulseSensor.getBeatsPerMinute(0)*2);
rpm = rpmAverage.intVal[1];
}
}
//if (millis() - peak > 2000)
//rpm = 0;
#ifdef DEBUG
// Serial.print(value[0]);
// Serial.print(min_val[0]);
// Serial.print(average[0]);
// Serial.print(descentCount[0]);
// Serial.println(descending[0]*1000);
// Serial.print(" ");
// Serial.println(descending[1]*1000);
// Serial.println(value[1]);
// Serial.println(min_val[1]);
// Serial.println(average[1]);
// Serial.println(rpm);
// Serial.println(delta);
#endif
if (!freezeDisplay
&& (settings.graphType == 2 || (millis() - lastUpdate) > refresh_r)) {//only update the display every 100ms or so to prevent flickering
switch (settings.graphType) { //there are two types of graph. bar and centered around the value of the master carb
case 0:
lcdBarsSmooth(average);
lastUpdate = millis();
break; //these functions both draw a graph and return asap
case 1:
lcdBarsCenterSmooth(average);
lastUpdate = millis();
break; //
}
printLcdInteger(rpm, 5, 3, 6);
lcd_setCursor(11, 3);
lcd_print(F("rpm"));
} else if (freezeDisplay) {
//show a little snow flake to indicate the frozen state of the display
drawSnowFlake();
}
timeBase = micros() - startTime; //monitor how long it takes to traverse the main loop
}
void do_raw_data() {
const int readings = 5;
unsigned int reading[NUM_SENSORS][readings];
unsigned int value[NUM_SENSORS];
unsigned int total[NUM_SENSORS];
int cur_reading = 0;
unsigned long lastUpdateTime = millis();
while (!(buttonPressed() == CANCEL)) {
for (int sensor = 0 ; sensor < 2; sensor++){
total[sensor] -= reading[sensor][cur_reading];
reading[sensor][cur_reading] = pulseSensor.getLatestSample(sensor);
total[sensor] += reading[sensor][cur_reading];
value[sensor]= total[sensor]/readings;
if (sensor != settings.master-1) value[sensor] += (int8_t) EEPROM.read(getCalibrationTableOffsetByValue(sensor, value[sensor]));
}
cur_reading += 1;
if (cur_reading == readings) cur_reading = 0;
if (millis() - lastUpdateTime > refresh_r) {
lcdBarsSmooth (value);
lastUpdateTime = millis();
}
}
}
void lcdBarsCalib(unsigned int delta) {
const uint8_t segmentsInCharacter = 5; //we need this number to make the display smooth
byte bar[4][8]; //store one custom character per bar
char bars[DISPLAY_COLS + 1]; //store for the bar of full characters
int range; //store the range between the highest and lowest sensors
int zoomFactor; //store the zoom of the display
const uint8_t hysteresis = 4;
// int TotalNumberOfLitSegments = delta; //determine the number of lit segments
int TotalNumberOfLitSegments = constrain(delta,-50,50); //determine the number of lit segments
int numberOfLitBars = TotalNumberOfLitSegments / segmentsInCharacter; //determine the number of whole characters
int numberOfLitSegments = TotalNumberOfLitSegments
% segmentsInCharacter; //determine the remaining stripes
makeCenterBars(bars, numberOfLitBars); //give us the bars of whole characters
lcd_setCursor(0, 1);
lcd_print(bars); //place the bars in the display
makeChar(bars, numberOfLitSegments); //make a custom char for the remaining stripes
lcd_createChar(1, bars);
if (numberOfLitSegments > 0) {
lcd_setCursor(10 + numberOfLitBars, 1); //place it on the right
} else {
lcd_setCursor(9 + numberOfLitBars, 1); //or in the center
}
if (numberOfLitBars < 10)
lcd_write(byte(2));
}
// display centered bars, centered on the reference carb's reading, because that's the target we are aiming for
//takes an array of current average values for all sensors as parameter
void lcdBarsCenterSmooth(unsigned int value[]) {
const uint8_t segmentsInCharacter = 5; //we need this number to make the display smooth
byte bar[4][8]; //store one custom character per bar
char bars[DISPLAY_COLS + 1]; //store for the bar of full characters
unsigned int maximumValue = maxVal(value); //determine the sensor with the highest average
unsigned int minimumValue = minVal(value); //determine the lowest sensor average
int range; //store the range between the highest and lowest sensors
int zoomFactor; //store the zoom of the display
const uint8_t hysteresis = 4;
//the range depends on finding the reading farthest from the master carb reading
if (maximumValue - value[settings.master - 1]
>= value[settings.master - 1] - minimumValue) {
range = maximumValue - value[settings.master - 1];
} else {
range = value[settings.master - 1] - minimumValue;
}
//sets the minimum range before the display becomes 'pixelated' there are 100 segments available, 50 on either side of the master
int ranges[] = { 50, 100, 150, 300, 512 };
if (range < ranges[settings.zoom]) {
range = ranges[settings.zoom];
}
zoomFactor = range / 50;
for (int sensor = 0; sensor < 2; sensor++) { //for each of the sensors the user wants to use
int delta = value[sensor] - value[settings.master - 1]; //find the difference between it and master
int TotalNumberOfLitSegments = delta / zoomFactor; //determine the number of lit segments
int numberOfLitBars = TotalNumberOfLitSegments / segmentsInCharacter; //determine the number of whole characters
int numberOfLitSegments = TotalNumberOfLitSegments
% segmentsInCharacter; //determine the remaining stripes
if (sensor != settings.master - 1) { //for all sensors except the master carb sensor
makeCenterBars(bars, numberOfLitBars); //give us the bars of whole characters
lcd_setCursor(0, 1);
lcd_print(bars); //place the bars in the display
makeChar(bar[sensor], numberOfLitSegments); //make a custom char for the remaining stripes
lcd_createChar(sensor + 2, bar[sensor]);
if (numberOfLitSegments > 0) {
lcd_setCursor(10 + numberOfLitBars, 1); //place it on the right
} else {
lcd_setCursor(9 + numberOfLitBars, 1); //or in the center
}
if (numberOfLitBars < 10)
lcd_write(byte(sensor + 2));
//hysteresis gives the display more stability and prevents the labels from flipping from side to side constantly.
// if (!settings.silent) {
// if (numberOfLitBars < -hysteresis)
// labelPosition = 15;
// if (numberOfLitBars > hysteresis)
// labelPosition = 0;
printLcdSpace(8, 2, 5);
lcd_printFormatted(delta); //display the difference between this sensor and master
// }
}
}
}
// this is used to display four plain non-zoomed bars with absolute pressure readings
void lcdBarsSmooth(unsigned int value[]) {
const uint8_t segmentsInCharacter = 5;
const uint8_t hysteresis = 2;
byte bar[4][8];
char bars[DISPLAY_COLS + 1];
for (int sensor = 0; sensor < 2; sensor++) {
//int TotalNumberOfLitSegments = 100000L / 1024 * value[sensor] / 1000; // integer math works faster, so we multiply by 1000 and divide later, powers of two would be even faster
int TotalNumberOfLitSegments = constrain(map(value[sensor],300,600,0,65),0,65); // integer math works faster, so we multiply by 1000 and divide later, powers of two would be even faster
int numberOfLitBars = TotalNumberOfLitSegments / segmentsInCharacter;
int numberOfLitSegments = TotalNumberOfLitSegments
% segmentsInCharacter;
makeBars(bars, numberOfLitBars, 0); //skip function probably no longer needed
lcd_setCursor(0, sensor);
lcd_print(bars);
makeChar(bar[sensor], numberOfLitSegments);
lcd_createChar(sensor + 2, bar[sensor]);
lcd_setCursor(numberOfLitBars, sensor);
lcd_write(byte(sensor + 2));
//if (!settings.silent) {
// if (numberOfLitBars < 10 - hysteresis)
labelPosition = 14;
// if (numberOfLitBars > 10 + hysteresis)
// labelPosition = 0;
printLcdSpace(labelPosition, sensor, 5);
lcd_printFormatted(value[sensor]);
//}
}
}
//compares freshly loaded settings to the freshly saved verion, if there is a difference the save must have failed
//fail on write is the most common NVRAM failure by far
bool verifySettings() {
settings_t settingsCopy = settings;
settings = loadSettings(settingsCopy);
return memcmp(&settings, &settingsCopy, sizeof(settings));
}
//saves our settings struct
void actionSaveSettings() {
EEPROM.put(0, versionUID); //only saves changed bytes!
EEPROM.put(1, settingsOffset);
EEPROM.put(settingsOffset, settings); //only saves changed bytes!
delay(500); //eeprom settle time
//Move our settings up 1 position and retry while memory lasts!
if (0 != verifySettings()) {
//if (settingsOffset + sizeof(settings) < 255)
// settingsOffset += sizeof(settings);
//EEPROM.put(1, settingsOffset);
actionSaveSettings();
lcd_clear();
lcd_setCursor(0, 1);
lcd_print(F("SETTINGS WRITE ERROR"));
lcd_setCursor(0, 2);
lcd_print(F("SETTINGS RELOCATED"));
waitForAnyKey();
}
}
//loads the settings from EEPROM (Flash)
settings_t loadSettings(settings_t settings) {
uint8_t compareVersion = 0;
EEPROM.get(0, compareVersion);
EEPROM.get(1, settingsOffset);
if (compareVersion == versionUID) { //only load settings if saved by the current version, otherwise reset to 'factory' defaults
settings = EEPROM.get(settingsOffset, settings);
} else {
settingsOffset = 2;
settings = fetchFactoryDefaultSettings();
}
doContrast(settings.contrast);
doBrightness(settings.brightness);
return settings;
}
//does the display while clearing the calibration array
void doZeroCalibrations() {
lcd_clear();
lcd_setCursor(3, 1);
lcd_print(txtWiping);
zeroCalibrations();
doConfirmation();
}
//determine where the calibration value is stored in EEPROM depending on the sample value
int getCalibrationTableOffsetByValue(int sensor, int value) {
return calibrationOffset + ((sensor - 1) * numberOfCalibrationValues)
+ (value >> 2);
}
//determine where in EEPROM the calibration value is stored depending on the position
int getCalibrationTableOffsetByPosition(int sensor, int pos) {
return calibrationOffset + ((sensor - 1) * numberOfCalibrationValues) + pos;
}
//only write if the value needs writing (saves write cycles)
void eepromWriteIfChanged(int address, int8_t data) {
if ((uint8_t) data != EEPROM.read(address)) {
EEPROM.write(address, (uint8_t) data); //write the data to EEPROM
}
}
int readSensorRaw(int sensor) {
return (analogRead(inputPin[sensor]));
}
//clear the flash for a single sensor
void doClearCalibration(int sensor) {
for (int i = 0; i < numberOfCalibrationValues; i++) {
eepromWriteIfChanged(getCalibrationTableOffsetByPosition(sensor, i), 0); //write the data directly to EEPROM
}
}
//actually clears the flash for all the sensors
void zeroCalibrations() {
for (uint8_t sensor = 1; sensor < (NUM_SENSORS); sensor++) {
doClearCalibration(sensor);
}
}
void doClearCalibration1() {
doClearCalibration(1);
doConfirmation();
}
void doViewCalibration1() {
doViewCalibration(1);
}
void doCalibrate1() {
doCalibrate(1);
}
void doCalibrate(int sensor) {
const int shift = 5;
const int factor = 4;
int maxValue = -127;
int minValue = 127;
int lowestCalibratedValue = 4096;
int readingStandardPre, readingSensor, readingStandardPost;
unsigned long lastUpdateTime = millis();
unsigned long textChange = millis();
bool calText = true;
setInterrupt(false);
lcd_clear();
lcd_setCursor(0, 0);
lcd_print(txtCalibrationBusy);
lcd_setCursor(0, 3);
lcd_print(txtPressAnyKey);
displayKeyPressPrompt();
delay(500); //otherwise key still pressed, probably need a better solution
//initialize temp values array, note full ints (16 bits) used
int values[numberOfCalibrationValues];
//read existing values from EEPROM and pre-shift them
//shifting an int left by n bits simply gives us n bits of 'virtual' decimal places
// this is needed for accuracy because EMA calculation works by adding or subtracting relatively small values
// which would otherwise all be truncated to '0'
for (int i = 0; i < numberOfCalibrationValues; i++) {
values[i] = (int8_t) EEPROM.read(
getCalibrationTableOffsetByPosition(sensor, i));
values[i] <<= shift;
}
while (!buttonPressed()) {
readingStandardPre = pulseSensor.getLatestSample(0); //read master
readingSensor = pulseSensor.getLatestSample(1); //read calibration sensor
readingStandardPost = pulseSensor.getLatestSample(0); //read master again
int readingStandard = (readingStandardPre + readingStandardPost) >> 1; //average both to increase accuracy on slopes
int calibrationValue = readingStandard - readingSensor;
//record some basic quality statistics
maxValue = max (calibrationValue , maxValue);
minValue = min (calibrationValue , minValue);
lowestCalibratedValue = min (readingSensor , lowestCalibratedValue);
values[(readingSensor >> 2)] = intExponentialMovingAverage(shift,
factor, values[(readingSensor >> 2)], calibrationValue);
if (millis() - textChange > 2000){
calText = !calText;
printLcdSpace(0, 0, 19);
if (calText) lcd_print(txtCalibrationBusy);
else lcd_print(txtCalibrationBusy2);
textChange = millis();
}
if (millis() - lastUpdateTime > refresh_r) {
lcdBarsCalib(calibrationValue);
lcd_setCursor(0, 0);
printLcdSpace(1, 2, 4);
lcd_printFormatted(readingStandard); //display the difference between this sensor and master
printLcdSpace(8, 2, 4);
lcd_printFormatted(readingSensor); //display the difference between this sensor and master
printLcdSpace(15, 2, 4);
lcd_printFormatted(calibrationValue); //display the difference between this sensor and master
lastUpdateTime = millis();
}
}
//post_shift the values in preparation of writing back to EEPROM
// we don't need to save the 'decimal places' because they are not needed anymore.
// so we lose them by shifting them out of range to the right
for (int i = 0; i < numberOfCalibrationValues; i++) {
values[i] >>= shift;
}
//save calibrations
for (int i = 0; i < numberOfCalibrationValues; i++) {
eepromWriteIfChanged(getCalibrationTableOffsetByPosition(sensor, i),
(int8_t) values[i]);
}
lcd_clear();
lcd_setCursor(0, 0);
lcd_print(txtCalibrationDone);
lcd_setCursor(0, 1);
lcd_print(txtLowestPressure);
printLcdInteger(lowestCalibratedValue, 15, 1, 4);
lcd_setCursor(0, 2);
lcd_print(txtMinAdjust);
printLcdInteger(minValue, 16, 2, 3);
lcd_setCursor(0, 3);
lcd_print(txtMaxAdjust);
printLcdInteger(maxValue, 16, 3, 3);
waitForAnyKey();
displayCalibratedValues(values);
setInterrupt(true);
}
void doViewCalibration(int sensor) {
setInterrupt(false);
int values[numberOfCalibrationValues];
for (int i = 0; i < numberOfCalibrationValues; i++) {
values[i] = (int8_t) EEPROM.read(
getCalibrationTableOffsetByPosition(sensor, i));
}
displayCalibratedValues(values);
setInterrupt(true);
}
//display indicator arrows and numeric offsets so we don't get lost in the graph of calibration values.
void displayNavArrowsAndOffsets(int valueOffset,
bool topLeftArrowPositionAvailable,
bool topRightArrowPositionAvailable) {
if (valueOffset == 0) {
(topLeftArrowPositionAvailable) ?
lcd_setCursor(0, 0) : lcd_setCursor(0, 3);
lcd_printChar('[');
lcd_printInt(valueOffset);
}
if (valueOffset > 0) {
(topLeftArrowPositionAvailable) ?
lcd_setCursor(0, 0) : lcd_setCursor(0, 3);
lcd_printChar(char(MENUCARET + 1)); //little arrow to the left
lcd_printInt(valueOffset);
}
if (valueOffset == numberOfCalibrationValues - 20) {
(topRightArrowPositionAvailable) ?
lcd_setCursor(16, 0) : lcd_setCursor(16, 3);
lcd_printInt(valueOffset + 20);
lcd_printChar(']');
}
if (valueOffset < numberOfCalibrationValues - 20) {
(topRightArrowPositionAvailable) ?
lcd_setCursor(16, 0) : lcd_setCursor(16, 3);
if ((valueOffset + 20) < 100)
lcd_printChar(' ');
lcd_printInt(valueOffset + 20);
lcd_printChar(char(MENUCARET)); //little arrow to the right
}
}
// Show a graph of the computed calibration values so the user can get an idea of the quality of the sensors
// and of the calibration. Repeated calibration increases the accuracy.
// Note: if all sensors are showing the same type of displacement that means that sensor 0
// is off by that much in the opposite direction.
void displayCalibratedValues(int values[]) {
int valueOffset = 0;
int numberOfColumns = 20;
int segmentsPerCharacter = 8;
int numberOfCharacters = 4;
int numberOfSegments = segmentsPerCharacter * (numberOfCharacters / 2);
int valuePerSegment = settings.calibrationMax / numberOfSegments; //128 / 16 = 8
int pressedButton = 0;
bool dataChanged = true;
bool topLeftArrowPositionAvailable, topRightArrowPositionAvailable = true;
makeCalibrationChars();
while (pressedButton != CANCEL) {
if (dataChanged) {
lcd_clear();
for (int column = 0; column < numberOfColumns; column++) {
int valueInSegments = values[valueOffset + column]
/ valuePerSegment;
if (valueInSegments <= segmentsPerCharacter
&& valueInSegments > 0) {
lcd_setCursor(column, 1);
lcd_write(byte((byte) 8 - valueInSegments));
} else if (valueInSegments > 2 * segmentsPerCharacter) {
lcd_setCursor(column, 0);
lcd_printChar('|');
lcd_setCursor(column, 1);
lcd_printChar('|');
} else if (valueInSegments > segmentsPerCharacter) {
lcd_setCursor(column, 0);
lcd_write(
byte(
(byte) 8
- (valueInSegments
% segmentsPerCharacter)));
} else if (valueInSegments < (2 * -segmentsPerCharacter)) {
lcd_setCursor(column, 2);
lcd_printChar('|');
lcd_setCursor(column, 3);
lcd_printChar('|');
} else if (valueInSegments < 0
&& (valueInSegments >= -segmentsPerCharacter)) {
lcd_setCursor(column, 2);
lcd_write(byte((byte) (-valueInSegments) - 1));
} else if (valueInSegments < 0
&& (valueInSegments < -segmentsPerCharacter)) {
lcd_setCursor(column, 3);
lcd_write(
byte(
(byte) 8
- ((valueInSegments + 1)
% segmentsPerCharacter)));
}
if (column == 0)
topLeftArrowPositionAvailable = (valueInSegments <= 0);
if (column == 19)
topRightArrowPositionAvailable = (valueInSegments <= 0);
}
//show arrows to indicate scrolling and our location in the calibration array
displayNavArrowsAndOffsets(valueOffset,
topLeftArrowPositionAvailable,
topRightArrowPositionAvailable);
}
//allow the user to scroll through the values from left to right and vice versa
pressedButton = buttonPressed();
if ((pressedButton == LEFT) && (valueOffset > 20)) {
valueOffset -= 20;
dataChanged = true;
} else if ((pressedButton == LEFT) && (valueOffset <= 20)) {
valueOffset = 0;
dataChanged = true;
} else if ((pressedButton == RIGHT)
&& (valueOffset < numberOfCalibrationValues - 20 - 20)) {
valueOffset = (valueOffset + 20);
dataChanged = true;
} else if ((pressedButton == RIGHT)
&& (valueOffset >= numberOfCalibrationValues - 20 - 20)) {
valueOffset = (numberOfCalibrationValues - 20);
dataChanged = true;
} else {
dataChanged = false;
}
}
}
//create special characters in LCD memory these contain the horizontal lines
// we use to generate a graph of our calibration data
// using simple lines instead of full bars means we can use the same characters above and below zero
// because we only have 8 special chars, we would run out if we used bars!
void createSpecialCharacter(int number) {
byte specialCharacter[8];
for (int i = 0; i < 8; i++) {
specialCharacter[i] = 0b00000;
}
specialCharacter[number - 1] = 0b11111;
lcd_createChar(number - 1, specialCharacter);
}
// we need 8 characters to use each line in a 5x8 pixel character cell
void makeCalibrationChars() {
for (int i = 1; i <= 8; i++) {
createSpecialCharacter(i);
}
}
void makeCenterBars(char *bars, int8_t number) {
if (number > 10)
number = 10;
if (number < -10)
number = -10;
if (number < 0) {
for (int8_t i = 0; i < 10 + number; i++) {
bars[i] = ' ';
}
for (int8_t i = 10 + number; i < 10; i++) {
bars[i] = 0xff;
}
for (int8_t i = 10; i < 20; i++) {
bars[i] = ' ';
}
}
if (number == 0) {
for (int8_t i = 0; i <= 20; i++) {
bars[i] = ' ';
}
}
if (number > 0) {
for (int8_t i = 0; i <= 10; i++) {
bars[i] = ' ';
}
for (int8_t i = 10; i <= 10 + number; i++) {
bars[i] = 0xff;
}
for (int8_t i = 10 + number; i < 20; i++) {
bars[i] = ' ';
}
}
bars[DISPLAY_COLS] = 0x00;
}
//used to detect ambient pressure for readings that ascend with vacuum in stead of going down toward absolute 0 pressure
int detectAmbient() {
unsigned long total = 0;
uint8_t numberOfSamples = 200; //set the number of samples to average
for (int i = 0; i < numberOfSamples; i++) {
total += readSensorRaw(settings.master - 1); //add the reading we got
}
return total / numberOfSamples; //divide the result by the number of samples for the resulting average
}