#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <SFE_BMP180.h>
#include <Wire.h>
//#define SOLID_GRAPH // если нравятся "залитые" графики, раскоментируйте
#define MAGIC "alcodistillers.ru"
#define MAX_DS18 3
#define AVERAGING_COUNT 3
#define PIN_DS18 10
#define PIN_BUTTON 2
#define PIN_SDA 5 // useless on *duino
#define PIN_SCL 4
OneWire *ds; // on pin 10 (a 4.7K resistor is necessary)
SFE_BMP180 pressure;
LiquidCrystal_I2C lcd(0x27, 20, 4);
#define VAL_COUNT 5*8
float t[VAL_COUNT];
uint16_t tpos = 0;
#define P(x) Serial.print(x)
#define PL(x) Serial.println(x)
struct ds18ScratchPad_struct {
union {
uint8_t bytes[9];
struct {
uint8_t tempLsb, tempMsb, alarmHiTemp, alarmLoTemp, config,
internalByte, countRemain, countPerC, crc;
} regs;
};
};
typedef struct ds18ScratchPad_struct ds18ScratchPad_t;
struct ds18DeviceRom_struct {
union {
uint8_t bytes[8];
struct {
uint8_t family, serial[6], crc;
} regs;
uint64_t dword;
uint32_t u32[2];
};
};
typedef struct ds18DeviceRom_struct ds18DeviceRom_t;
typedef struct {
ds18DeviceRom_t addr;
float aTemp[AVERAGING_COUNT];
float cTemp;
uint8_t aCount;
uint8_t errorCount;
} ds18;
ds18 ds18s[MAX_DS18];
double bmp180Temp = 0, bmp180Pressure = 0;
uint8_t hasBmp180 = 0;
uint32_t nextLcdUpdate = 0;
//
#define abs(a) (a>0?a:-a)
void graph( float temp) {
int16_t i;
float tmin, tmax, tmin0;
t[tpos++] = tmin = tmax = temp;
tpos %= VAL_COUNT;
for (i = 0; i < VAL_COUNT; i++) {
if (t[i] == INT16_MAX) continue;
if (tmin > t[i]) tmin = t[i];
if (tmax < t[i]) tmax = t[i];
}
float tdiv = (tmax - tmin) / 8;
if (tdiv < 0.0625) { //1/16 temp step && 8 steps per line
tdiv = 0.0625;
tmin0 = (tmax + tmin) / 2 - 0.0625 * 3;
} else {
tmin0 = tmin;
}
uint8_t c[8];
uint8_t y, x, py = 100;
P("+++++ Temp:"); P(temp); P(" tpos="); P(tpos); P(" min="); P(tmin); P(" max="); P(tmax); P(" div="); PL(tdiv);
int p;
for (i = 0; i < VAL_COUNT; i++) {
p = (i + (int)tpos);// % VAL_COUNT;
p %= VAL_COUNT;
if (i % 5 == 0) {
memset(c, 0, sizeof(c));
}
if (t[p] < INT16_MAX) {
x = 1 << (4 - i % 5);
y = (uint8_t)((t[p] - tmin0) / tdiv) + 1;
if (y > 8) y = 8;
#ifdef SOLID_GRAPH
for (int y1 = 8 - y; y1 < 8; y1++)
c[y1] |= x;
#else
// P(p); P(" "); P(t[p]); P(" "); P(x); P(" "); P(y); P(" "); P(py); PL(";");
if (py > 8 || py == y) {
if (y) c[8 - y] |= x;
} else if (py > y) {
for (int y1 = y; y1 < py; y1++)
c[8 - y1] |= x;
} else {
for (int y1 = py + 1; y1 <= y; y1++)
c[8 - y1] |= x;
}
py = y;
#endif
}
if (i % 5 == 4)
lcd.createChar(i / 5, c);
}
char str[21];
snprintf(str, 21, "%2d.%02d \010\011\012\013\014\015\016\017 %2d.%02d", (int)tmin , (int)(tmin * 100) % 100,
(int) tmax , (int)(tmax * 100) % 100);
lcd.setCursor(0, 3);
lcd.print(str);
}
//
int readConfig() {
char t[sizeof(MAGIC)];
EEPROM.get(0, t);
Serial.println("Read config");
if (memcmp(MAGIC, t, sizeof(MAGIC))) {
Serial.println("no magic");
resetConfig();
return 0;
}
for (int i = 0; i < MAX_DS18; i++)
EEPROM.get(sizeof(MAGIC) + i * sizeof(ds18DeviceRom_t), ds18s[i].addr.bytes);
}
int saveConfig() {
Serial.println("Save Config");
EEPROM.put(0, MAGIC);
for (int i = 0; i < MAX_DS18; i++) {
EEPROM.put(sizeof(MAGIC) + i * sizeof(ds18DeviceRom_t), ds18s[i].addr.bytes);
}
}
int resetConfig() {
Serial.println("Reset config");
memset (ds18s, 0, sizeof(ds18s));
saveConfig();
}
int dumpConfig() {
int i, i1;
for (i1 = 0; i1 < MAX_DS18; i1++) {
Serial.print("ROM =");
Serial.print(i1 + 1);
for ( i = 0; i < 8; i++) {
Serial.write(' ');
Serial.print(ds18s[i1].addr.bytes[i], HEX);
}
Serial.println();
}
}
int scanDs18() {
int i, empty, saveFlag = 0;
ds18DeviceRom_t addr, addr_zero;
memset(&addr_zero, 0, sizeof(ds18DeviceRom_t));
ds->reset_search();
while ( ds->search((void*)&addr.bytes)) {
empty = -1;
for (i = 0; i < MAX_DS18; i++) {
if (!memcmp(&ds18s[i].addr, &addr, sizeof(ds18DeviceRom_t))) {
break;
}
if (empty == -1 && !memcmp(&ds18s[i].addr, &addr_zero, sizeof(ds18DeviceRom_t))) {
empty = i;
}
}
if (i == MAX_DS18) {
if (empty >= 0) {
memcpy(&ds18s[empty].addr, &addr, sizeof(ds18DeviceRom_t));
saveFlag = 1;
} else {
Serial.println("Unable to save new ds18, manual reset needed");
}
}
}
if (saveFlag) {
saveConfig();
}
}
uint8_t bak[] = {219, 191, 165, 143, 122, 102, 85, 69, 53, 39, 25, 12};
uint16_t par[] = {689, 667, 641, 613, 579, 536, 490, 436, 368, 295, 207, 108};
float getParVolPercent(int temp) {
if (temp >= 100 || temp < 88) return 0;
temp -= 88;
return ((float)par[temp]) / 10;
}
float getBakVolPercent(int temp) {
if (temp >= 100 || temp < 88) return 0;
temp -= 88;
return ((float)bak[temp]) / 10;
}
void setup(void) {
Serial.begin(115200);
Serial.println(MAGIC);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print(">>>>>>>>> <<<<<<<<<");
lcd.setCursor(2, 1);
lcd.print(MAGIC);
lcd.setCursor(3, 2);
lcd.print("samogon powered!");
pinMode(PIN_BUTTON, INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
pinMode(PIN_DS18, INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
ds = new OneWire(PIN_DS18);
if (!digitalRead(PIN_BUTTON)) {
if (!digitalRead(PIN_BUTTON))
{
lcd.clear();
lcd.setCursor(0, 3);
lcd.print("reset ds18b20 config");
Serial.println("Reset");
resetConfig();
}
}
readConfig();
scanDs18();
dumpConfig();
// Wire.pins(PIN_SDA, PIN_SCL);
if (pressure.begin()) {
Serial.println("BMP180 init success");
hasBmp180 = 1;
} else {
Serial.println("BMP180 init fail\n\n");
}
for (int i = 0; i < VAL_COUNT; i++) {
t[i] = INT16_MAX;
}
float q = 77;
for (int i = 0; i < 50; i++) {
q += 0.125 * (1 - random(0, 3));
graph(q);
delay(50);
}
lcd.clear();
for (int i = 0; i < VAL_COUNT; i++) {
t[i] = INT16_MAX;
}
}
// coRoutines
#define crBegin switch(crStaticVar) { case 0:
#define crReturn(x) do { crStaticVar=__LINE__; return x ; case __LINE__:; } while (0)
#define crFinish }
#define MIN_POLL_DELAY 40 //ms
#ifdef crStaticVar
#undef crStaticVar
#endif
#define crStaticVar _dsStatus
int _dsStatus = 0;
int dsCnt;
int readTemp() {
ds18ScratchPad_t scp;
crBegin;
ds->reset();
ds->skip();
ds->write(0x44, 0);
crReturn(750); // time to calculate temp
for (dsCnt = 0; dsCnt < MAX_DS18; dsCnt++) {
ds->reset();
ds->select((void*)&ds18s[dsCnt].addr.bytes);
ds->write(0xBE); // Read Scratchpad
for (int i = 0; i < 9; i++) { // we need 9 bytes
scp.bytes[i] = ds->read();
}
if (OneWire::crc8((void*)&scp.bytes, 8) != scp.regs.crc) {
if (ds18s[dsCnt].errorCount < 10)
ds18s[dsCnt].errorCount++;
else
ds18s[dsCnt].cTemp = 0;
Serial.print("Error!!!!");
} else {
ds18s[dsCnt].errorCount = 0;
int TReading, SignBit;
float temp = 11.11;
TReading = (scp.regs.tempMsb << 8) + scp.regs.tempLsb;
SignBit = TReading & 0x8000; // test most sig bit
if (SignBit) // negative
TReading = (TReading ^ 0xffff) + 1; // 2's comp
temp = 1.0 * (SignBit ? (-TReading) : TReading) / 16;
ds18s[dsCnt].aTemp[ds18s[dsCnt].aCount++] = temp;
if (ds18s[dsCnt].aCount == AVERAGING_COUNT) {
ds18s[dsCnt].aCount = 0;
temp = 0;
for (int i = 0; i < AVERAGING_COUNT; i++) {
temp += ds18s[dsCnt].aTemp[i];
}
ds18s[dsCnt].cTemp = temp / AVERAGING_COUNT;
}
}
crReturn(MIN_POLL_DELAY);
}
crFinish;
crStaticVar = 0;
nextLcdUpdate = 0;
return MIN_POLL_DELAY;
}
#ifdef crStaticVar
#undef crStaticVar
#endif
#define crStaticVar _bmp180Status
int _bmp180Status = 0;
int readPressure() {
if (!hasBmp180)
return 5000;
crBegin
pressure.startTemperature();
crReturn(MIN_POLL_DELAY);
pressure.getTemperature(bmp180Temp);
crReturn(MIN_POLL_DELAY);
pressure.startPressure(3);
crReturn(MIN_POLL_DELAY);
pressure.getPressure(bmp180Pressure, bmp180Temp);
bmp180Pressure /= 1.333;
crFinish;
crStaticVar = 0;
nextLcdUpdate = 0;
return 2000;
}
int dumpTemp() {
if (ds18s[0].cTemp > 0)
graph(ds18s[0].cTemp);
return 5000;
}
int pos = 0;
int updateLcd() {
char str[21];//,f1[6],f2[6],f3[6];
if (nextLcdUpdate < millis()) {
Serial.println("----------");
// строка 1
snprintf(str, 21, "n:%02d.%02d o:%2d ", (int)ds18s[0].cTemp, (int)(ds18s[0].cTemp * 100) % 100,
(int)ds18s[2].cTemp);
lcd.setCursor(0, 0);
lcd.print(str);
Serial.println(str);
// строка 2
float pt = (78.91 - (780 - bmp180Pressure ) * 0.038);
// strcpy(str,"To=55 P=777.7 Tc=77.7f");
if (hasBmp180) {
snprintf(str, 21, "k:%02d.%02d c:%2d.%1d/%3d.%1d ", (int)ds18s[1].cTemp, (int)(ds18s[1].cTemp * 100) % 100,
(int)pt, (int)(pt * 10) % 10, (int)bmp180Pressure, (int)(bmp180Pressure * 10) % 10);
} else {
snprintf(str, 21, "o:%2d ", (int)ds18s[2].cTemp);
}
lcd.setCursor(0, 1);
lcd.print(str);
Serial.println(str);
// строка 3
if (ds18s[0].cTemp > 88 || ds18s[1].cTemp > 88) { // дистилляция
int vpp = getParVolPercent(ds18s[0].cTemp) * 10;
int vpb = getBakVolPercent(ds18s[1].cTemp) * 10;
snprintf(str, 20, "%par=%2d.%1d %bak=%2d.%1d ", vpp / 10, vpp % 10, vpb / 10, vpb % 10);
} else {
memset(str, ' ', 20);
str[20] = '\0';
}
lcd.setCursor(0, 2);
lcd.print(str);
Serial.println(str);
// строка 4 - графики
nextLcdUpdate = millis() + 2000;
}
return 200;
}
uint32_t prev_time = 0;
enum {POLL_DS, POLL_BMP, POLL_DUMP, POLL_LCD, POLL_END};
#define MAX_POLL_MS 10000
uint32_t polls[POLL_END] = {0};
void loop(void) {
uint32_t timems = millis();
uint32_t minms = timems + MAX_POLL_MS;
uint32_t x;
for (int i = 0; i < POLL_END; i++) {
if (polls[i] < timems) {
switch (i) {
case POLL_DS:
x = readTemp();
break;
case POLL_DUMP:
x = dumpTemp();
break;
case POLL_BMP:
x = readPressure();
break;
case POLL_LCD:
// x = dumpTemp();
x = updateLcd();
break;
}
polls[i] = timems + x;
} else {
}
if (minms > polls[i] ) {
minms = polls[i];
}
}
delay(minms - timems);
}