Re: Настраиваемый термометр на Arduino
SLOG ©:Я уже зарядку 5-ти вольтовую прикрутил.
Если блок питания на 2 ампера, то вряд ли просядет.
у меня uno на 5 в проседает, а на 12 греется и начинает глючить.
надо наверно что то среднее
Форум самогонщиков, винокуров, виноделов, пивоваров, бондарей и очень хороших людей |
с 41 по 60 из 98
SLOG ©:Я уже зарядку 5-ти вольтовую прикрутил.
Если блок питания на 2 ампера, то вряд ли просядет.
у меня uno на 5 в проседает, а на 12 греется и начинает глючить.
надо наверно что то среднее
у меня uno на 5 в проседает
Если 5 вольт подавать в гнездо внешнего питания, то оно попадает на внутренний стабилизатор, соответственно на выходе на 1 вольт меньше. 5 вольт только в юсби гнездо, а внешнее работает от 7 до 12 вольт.
dmytry ©:у меня uno на 5 в проседает
Если 5 вольт подавать в гнездо внешнего питания, то оно попадает на внутренний стабилизатор, соответственно на выходе на 1 вольт меньше. 5 вольт только в юсби гнездо, а внешнее работает от 7 до 12 вольт.
В моем случае 12 для стабилизатора многовато, сильно греется.
если просядет ниже 4,2 вольта будет глючить, если ниже 4 - может ардуинка пыхнуть. Линейный стабилизатор вполне может греться, но это тепло нужно рассеивать т.к. китайские платки не для космоса делаются и от перегрева портятся.
Пока анонс.
Будет кнопка "стартЪ/стопЪ" с фиксацией дельты температуры в +.1*.
По фиксации температуры будет завязан алгоритм изменения состояния выводов (D5,D6)
D5 = отбор с реле и нормально закрытым клапаном
D6 = сигнальный (биппер)
1. Начальная температура Тстарт задается при нажатии кнопки верхнего и среднего датчиков. D5=1,D6=1 на 0.5 сек
2. Измеряем температуру со всех трех датчиков
3. Если D5==1 и любой датчик колонны превышает пороговое значение Тстарт+0.1, то D5=0, D6=1 на 0.5 сек
4. Если D5==0 и сработавшие датчики вернулись к T<=Тстарт, то D5=1, D6=1 на 0.5 сек
5. цикл на 2.
Версия 2.2
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <SFE_BMP180.h>
#include <Wire.h>
//#define SOLID_GRAPH // если нравятся "залитые" графики, раскоментируйте
#define VERSION "v2.2"
#define MAGIC "alcodistillers.ru"
// пины
#define DS18_PIN 10 // DS18B20
#define BEEPER_PIN 13 // гудок
#define PUMP_PIN 11 // перистальтический насос или клапан в импульсном режиме
#define SOLENOID_VALVE_PIN 12 // электрический клапан и/или светодиод состояния
enum {KEY_START, KEY_PLUS, KEY_MINUS, KEY_5 }; // функция кнопки
int8_t keysGpios[] = {5, 6, 7, 8}; // пины кнопок
// дельта по пару/колонне
#define TEMP_DELTA 0.1
// настройки сигнализации температуры воды
#define TV_C_MAX 65
#define TV_C_MIN 45
#define TV_BEEP // бипать при выходе температуры воды за пределы
// настройки перистальтического насоса или клапана в импульсном режиме
#ifdef PUMP_PIN
#define PUMP_CYCLE 1000 // цикл насоса в 1/1000 сек
#define PUMP_STEP 50 // шаг настройки насоса
#define MAX_PUMP_SPEED 3500 // реальная производительность насоса
#define MIN_PUMP_SPEED 200 // меньше чего не нужно
#define START_PUMP_SPEED 200 // установка при включении
#endif
// --------------------------------------------------------
// дальше менять нечего :)
// --------------------------------------------------------
typedef struct button {
uint8_t scnt;
uint8_t rcnt;
} button_t;
#define PIN_BUTTON (keysGpios[0])
#define KEY_COUNT (sizeof(keysGpios))
button_t buttons[KEY_COUNT];
#define LONG_PRESS_COUNT 500/MIN_POLL_DELAY // 500/40 = 12 опросов длинное нажатие
#define NORM_PRESS_COUNT 160/MIN_POLL_DELAY // 160/40= 4 опроса вподряд обычное нажатие
#define RESET_PRESS_COUNT 2 // три опроса вподряд "0" - клавиша отпущена
void beep();
#define MAX_DS18 3
#define AVERAGING_COUNT 3
#define MIN_POLL_DELAY 40 //ms
typedef struct {
uint8_t pausedState;
float t;
} termo_t;
termo_t termFSM[2];
float initPressure;
#ifdef PUMP_PIN
int pumpSpeed = START_PUMP_SPEED;
#endif
uint8_t tvStateNormal = 1;
OneWire *ds;
SFE_BMP180 pressure;
LiquidCrystal_I2C lcd(0x27, 20, 4);
#define VAL_COUNT 5*8
float t0[VAL_COUNT];
float t1[VAL_COUNT];
uint8_t t0ChCnt = 99;
uint8_t t1ChCnt = 99;
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 temp0, float temp1) {
int16_t i;
float tmin, tmax, tmin0;
t0[tpos] = tmin = tmax = temp0;
t1[tpos] = temp1;
tpos++;
tpos %= VAL_COUNT;
int8_t dx0 = 0, dx1 = 0;
int p, p0;
// обработка пиков
t0ChCnt = 0;
t1ChCnt = 0;
for (i = 1; i < VAL_COUNT - 1; i++) {
p = (i + (int)tpos); // % VAL_COUNT;
p %= VAL_COUNT;
p0 = (i + (int)tpos - 1); // % VAL_COUNT;
p0 %= VAL_COUNT;
if (t0[p] == INT16_MAX || t0[p - 1] == INT16_MAX) continue;
if (t0[p] > t0[p0] && dx0 != 1) {
dx0 = 1;
if (dx0 != 0) t0ChCnt++;
}
if (t0[p] < t0[p0] && dx0 != -1) {
dx0 = -1;
}
if (t1[p] > t1[p0] && dx1 != 1) {
dx1 = 1;
if (dx1 != 0) t1ChCnt++;
}
if (t0[p] < t0[p0] && dx0 != -1) {
dx1 = -1;
}
}
P(t0ChCnt); P(";"); PL(t1ChCnt);
// график
for (i = 0; i < VAL_COUNT; i++) {
if (t0[i] == INT16_MAX) continue;
if (tmin > t0[i]) tmin = t0[i];
if (tmax < t0[i]) tmax = t0[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);
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 (t0[p] < INT16_MAX) {
x = 1 << (4 - i % 5);
y = (uint8_t)((t0[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();
}
}
// корректировка по давлению [url=https://alcodistillers.ru/forum/viewtopic.php?pid=10973#p10973]Автоматика на Arduino для ректификации и дистилляции (схема-прошивки)[/url]
uint8_t bak[] = {219, 191, 165, 143, 122, 102, 85, 69, 53, 39, 25, 12, 0};
uint16_t par[] = {689, 667, 641, 613, 579, 536, 490, 436, 368, 295, 207, 108, 0};
float getParVolPercent(float temp) {
if (hasBmp180 & bmp180Pressure > 0) {
temp += (760 - bmp180Pressure) * 0.04;
}
if (temp >= 100 || temp <= 88) return 0;
uint8_t idx = temp - 88;
float ret = 0.1 * par[idx] - 0.1 * (par[idx] - par[idx + 1]) * (temp - 88 - idx);
return ret;
}
float getBakVolPercent(float temp) {
if (hasBmp180 & bmp180Pressure > 0) {
temp += (760 - bmp180Pressure) * 0.04;
}
if (temp >= 100 || temp < 88) return 0;
uint8_t idx = temp - 88;
float ret = 0.1 * bak[idx] - 0.1 * (bak[idx] - bak[idx + 1]) * (temp - 88 - idx);
return ret;
}
typedef enum {PUMP_ON, PUMP_OFF, PUMP_STOP} pumpState_t;
pumpState_t pumpState = PUMP_STOP;
#ifdef PUMP_PIN
uint32_t next_change = 0, ms0, ms1, msc;
uint32_t prevPumpMillis = 0;
int pumpRun(int speed);
void ppump(int inc) {
// P(">>"); P(inc); P(" "); P(pumpSpeed); P(" "); PL(pumpSpeed + inc);
pumpSpeed += inc;
if (pumpSpeed > MAX_PUMP_SPEED)
pumpSpeed = MAX_PUMP_SPEED;
else if (pumpSpeed < MIN_PUMP_SPEED)
pumpSpeed = MIN_PUMP_SPEED;
ms0 = (float)PUMP_CYCLE * pumpSpeed / MAX_PUMP_SPEED;
ms1 = PUMP_CYCLE - ms0;
if (ms0 < 200) {
ms1 = 200 * (float)ms1 / (float)ms0;
ms0 = 200;
}
nextLcdUpdate = 0;
}
int pollPump() {
uint32_t m = MIN_POLL_DELAY;
if (termFSM[0].pausedState || termFSM[1].pausedState) {
#ifdef PUMP_PIM
digitalWrite(PUMP_PIN, 0);
#endif
digitalWrite(SOLENOID_VALVE_PIN, 0);
return (1000);
}
digitalWrite(SOLENOID_VALVE_PIN, pumpState == PUMP_STOP ? 0 : 1);
#ifdef PUMP_PIN
switch (pumpState) {
case PUMP_ON:
pumpState = PUMP_OFF;
digitalWrite(PUMP_PIN, 0);
m = ms1;
break;
case PUMP_OFF:
pumpState = PUMP_ON;
digitalWrite(PUMP_PIN, 1);
m = ms0;
break;
case PUMP_STOP:
digitalWrite(PUMP_PIN, 0);
m = MIN_POLL_DELAY;
break;
}
#endif
// P(pumpState);P(">");PL(millis());
return m;
}
#endif
void initkeys() {
for (int i = 0; i < KEY_COUNT; i++) {
pinMode(keysGpios[i], INPUT);
pinMode(keysGpios[i], INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
}
memset(&buttons, 0, sizeof(buttons));
}
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(BEEPER_PIN, OUTPUT);
digitalWrite(BEEPER_PIN, 0);
#ifdef PUMP_PIN
pinMode(PUMP_PIN, OUTPUT);
digitalWrite(PUMP_PIN, 0);
#endif
pinMode(SOLENOID_VALVE_PIN, OUTPUT);
digitalWrite(SOLENOID_VALVE_PIN, 0);
readConfig();
scanDs18();
dumpConfig();
initkeys();
pinMode(DS18_PIN, INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
ds = new OneWire(DS18_PIN);
if (!digitalRead(PIN_BUTTON)) {
if (!digitalRead(PIN_BUTTON)) {
lcd.clear();
lcd.setCursor(0, 3);
lcd.print("reset ds18b20 config");
Serial.println("Reset");
resetConfig();
}
}
// 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++) {
t0[i] = INT16_MAX;
}
float q = 77;
for (int i = 0; i < 44; i++) {
q += 0.125 * (1 - random(0, 3));
graph(q, INT16_MAX);
// delay(10);
}
lcd.clear();
for (int i = 0; i < VAL_COUNT; i++) {
t0[i] = INT16_MAX;
t1[i] = INT16_MAX;
}
}
// coRoutines
#define crBegin switch(crStaticVar) { case 0:
#define crReturn(x) do { crStaticVar=__LINE__; return x ; case __LINE__:; } while (0)
#define crFinish }
#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;
PL("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;
}
void keyPress(int keynum, int longpress) {
P("keypress:"); P(keynum); P(" "); PL(longpress);
switch (keynum) {
case KEY_START:
beep();
pumpState = pumpState != PUMP_STOP ? PUMP_STOP : PUMP_OFF;
digitalWrite(PUMP_PIN, 0);
if (pumpState == PUMP_OFF) {
for (int i = 0; i < 2; i++) {
termFSM[i].t = ds18s[i].cTemp;
termFSM[i].pausedState = 0;
}
initPressure = bmp180Pressure;
}
ppump(0);
break;
case KEY_PLUS:
ppump(!longpress ? PUMP_STEP : PUMP_STEP * 8);
break;
case KEY_MINUS:
ppump(!longpress ? -PUMP_STEP : -PUMP_STEP * 8);
break;
}
}
// todo отработка "отжатия"
int pollButtons() {
button_t *b;
for (int cnt = 0; cnt < KEY_COUNT; cnt++) {
b = &buttons[cnt];
int st = !digitalRead(keysGpios[cnt]);
if (st && b->scnt < LONG_PRESS_COUNT) {
b->scnt++;
b->rcnt = 0;
} else {
if (b->scnt > RESET_PRESS_COUNT) { // цикл нажатия
b->rcnt++;
if (b->scnt >= LONG_PRESS_COUNT) { // длинное нажатие может повторяться
b->rcnt = 0;
b->scnt = 0;
keyPress(cnt, 1);
} else if (b->scnt >= NORM_PRESS_COUNT && b->rcnt > RESET_PRESS_COUNT) { // короткое нажатие
b->rcnt = 0;
b->scnt = 0;
keyPress(cnt, 0);
}
}
}
}
return MIN_POLL_DELAY;
}
int dumpTemp() {
if (ds18s[0].cTemp > 0)
graph(ds18s[0].cTemp, ds18s[1].cTemp);
for (int i = 0; i < 3; i++) {
P(ds18s[0].cTemp); P(" ");
}
PL(bmp180Pressure);
if ((ds18s[2].cTemp > TV_C_MAX || ds18s[2].cTemp < TV_C_MIN) && pumpState != PUMP_STOP)
tvStateNormal = 0;
else
tvStateNormal = 1;
#ifdef TV_BEEP
if (!tvStateNormal)
beep();
#endif
return 5000;
}
int pos = 0;
int updateLcd() {
char str[21];
if (nextLcdUpdate < millis()) {
//Serial.println("----------");
// строка 1
#ifndef PUMP_PIN
snprintf(str, 21, "n:%02d.%02d o:%2d ", (int)ds18s[0].cTemp, (int)(ds18s[0].cTemp * 100) % 100,
(int)ds18s[2].cTemp);
#else
snprintf(str, 21, "n:%02d.%02d o:%2d p:%4d ", (int)ds18s[0].cTemp, (int)(ds18s[0].cTemp * 100) % 100,
(int)ds18s[2].cTemp, pumpSpeed);
#endif
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);
snprintf(str, 21, "Tn:%c[%c]Tk:%c[%c]To:[%c] ", t0ChCnt < 10 ? '0' + t0ChCnt : '~', termFSM[0].pausedState ? 'X' : 'v',
t1ChCnt < 10 ? '0' + t1ChCnt : '~', termFSM[1].pausedState ? 'X' : 'v', tvStateNormal ? 'v' : 'X');
str[20] = '\0';
}
lcd.setCursor(0, 2);
lcd.print(str);
//Serial.println(str);
// строка 4 - графики
nextLcdUpdate = millis() + 2000;
}
return 200;
}
int pollLogic() {
if (pumpState == PUMP_STOP)
return 1000;
for (int i = 0; i < 2 ; i++) {
float corrT = termFSM[i].t + (bmp180Pressure - initPressure) * 0.04;
// P(bmp180Pressure-initPressure);P("/");P(ds18s[i].cTemp);P(" ");PL(corrT);
if (termFSM[i].pausedState) {
if (ds18s[i].cTemp <= corrT) {
termFSM[i].pausedState = 0;
beep();
}
} else {
if (ds18s[i].cTemp > corrT + TEMP_DELTA) {
termFSM[i].pausedState = 1;
beep();
}
}
}
return 3000;
}
uint32_t beepStart = 0;
uint32_t beepDuration;
void beep() {
beepStart = millis();
beepDuration = 100;
digitalWrite(BEEPER_PIN, 1);
}
int pollBeep() {
if (beepStart == 0) {
return 100;
}
uint32_t m = millis();
if (m - beepStart >= beepDuration) {
beepStart = 0;
digitalWrite(BEEPER_PIN, 0);
}
return 50;
}
uint32_t prev_time = 0;
typedef int (*poll_cb)();
poll_cb pCb[] = {&readTemp, &dumpTemp, &readPressure, &pollButtons, &pollPump, &updateLcd, &pollBeep, &pollLogic, NULL};
#define MAX_POLL_MS 10000
uint32_t polls[sizeof(pCb) / sizeof(poll_cb) + 1] = {0};
void loop(void) {
uint32_t timems = millis();
uint32_t minms = timems + MAX_POLL_MS;
uint32_t x;
for (int i = 0; pCb[i] != NULL; i++) {
if (polls[i] < timems) {
x = pCb[i]();
polls[i] = timems + x;
}
if (minms > polls[i] ) {
minms = polls[i];
}
}
delay(minms - timems);
}
https://yadi.sk/d/Z7nJUdoD3KmFTw
Описание и функции (+к версии 1)
Добавлено:
-управление (отсечка) отбора по температуре по датчику пара и датчику в колонне
-подключение нормально закрытого электромагнитного клапана (режим открыто/закрыто)
-подключение узла отбора на перистальтическом насосе или клапане (импульсный режим)
-анализатор стабильности температуры
-вывод состояния датчиков и стабильности температуры
-корректировка отбора по давлению
-сигнализация об изменении состояния (биппер)
Изменено
- кнопка перенесена с D2 на D5!!!!
Электрическая часть:
-кнопки нормально разомкнутые без фиксации: старт/стоп отбора - D5, "+"/"-" - D6,D7
- электромотор постоянного тока перистальтического насоса. Подключается к D11 при помощи драйвера (например L298 Dual H-Bridge Motor Driver), бесконтактного реле или полевого транзистора (ищется поисковиками "dc motor arduino mosfet") (подробнее про такой узел отбора Узел отбора на перистальтическом насосе)
R1 200-500 ом, R2 10k, Диод и транзистор - любые из доступных, рассчитанные на напряжение питания и ток моторчика.
- электромагнитный клапан отбора. Подключается к D12 при помощи реле (традиционно). На эту же ножку можно установить светодиод, последовательно с резистором 4.7к, он будет включен при отборе и выключен на паузе или если температурные показатели выйдут за пределы дельты.
- Биппер - любой небольшой, подключаем к D13. Если орет громко или при сигнале мигает экран - поставьте последовательно резистор 1-4.7k
По электричеству - задавайте вопросы, отвечу.
Описание интерфейса
1я строка:
пар, охлаждение, скорость отбора (при управляемом отборе)
2я строка:
колонна, температура кипения спирта, давление
3я строка:
Тп:С[X]Tk:C[v]To[v]
C- коэффициент стабильности температуры. от 0 до 9, если больше 9, отображается "~". Обозначает количество пиков (горбов) за последние 10 минут.
[X]/[v] - состояние при отборе - "Х" - температуры вышла за пределы, "v" - галочка - нормальное состояние
Андрей М, Годится, единственная просьба нарисовать схемку.
Можно прям фото макетки без проводов. А в редакторе подрисовать что, куда подключать.
Было-бы совсем замечательно.
Рисунок на спине упитого друга
Отлично! Спасибо!
Попробовал, вроде работает. Давление по крайней мере показывает. Кнопка старт\стоп работает.
С датчиками не понял. Чтоб их добавить в первом посте на D2 кнопка была, здесь ее не надо или как?
И при компиляции и загрузке выдает вот такие предупреждения?
D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino:124:0: warning: "abs" redefined
#define abs(a) (a>0?a:-a)
^
In file included from sketch\alcodist-2.2.ino.cpp:1:0:
D:\arduino-1.8.1\hardware\arduino\avr\cores\arduino/Arduino.h:94:0: note: this is the location of the previous definition
#define abs(x) ((x)>0?(x):-(x))
^
D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino: In function 'int scanDs18()':
D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino:262:40: warning: invalid conversion from 'void*' to 'uint8_t* {aka unsigned char*}' [-fpermissive]
while ( ds->search((void*)&addr.bytes)) {
^
In file included from D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino:3:0:
D:\arduino-1.8.1\libraries\OneWire/OneWire.h:181:13: note: initializing argument 1 of 'uint8_t OneWire::search(uint8_t*)'
uint8_t search(uint8_t *newAddr);
^
D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino: In function 'int readTemp()':
D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino:464:47: warning: invalid conversion from 'void*' to 'const uint8_t* {aka const unsigned char*}' [-fpermissive]
ds->select((void*)&ds18s[dsCnt].addr.bytes);
^
In file included from D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino:3:0:
D:\arduino-1.8.1\libraries\OneWire/OneWire.h:135:10: note: initializing argument 1 of 'void OneWire::select(const uint8_t*)'
void select(const uint8_t rom[8]);
^
D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino:469:43: warning: invalid conversion from 'void*' to 'const uint8_t* {aka const unsigned char*}' [-fpermissive]
if (OneWire::crc8((void*)&scp.bytes, 8) != scp.regs.crc) {
^
In file included from D:\TempArduino\Arduino\alcodist-2.2\alcodist-2.2.ino:3:0:
D:\arduino-1.8.1\libraries\OneWire/OneWire.h:187:20: note: initializing argument 1 of 'static uint8_t OneWire::crc8(const uint8_t*, uint8_t)'
static uint8_t crc8(const uint8_t *addr, uint8_t len);
Чтоб их добавить в первом посте на D2 кнопка была, здесь ее не надо или как?
На D2 и D3 можно подключать прерывания. Они понадобятся дальше. По этому кнопка перенесена на D5.
Функционал остался.
Ошибка #define abs... решается комментированием (// #define ...)
остальные предупреждения на скорость не влияют. Но я бы рекомендовал перейти на 1.8.3.
Всем привет.
Есть вопрос по BMP180.
Давление прыгает от измерения к измерению в пределах 0,2-0,5 мм.рт.ст.
Меняется циферка младшего разряда.
Так и должно быть или где-то у меня косяк ?
Так и должно быть или где-то у меня косяк ?
Все таки в одной поре стоит только у молодого, в других случаях давление прыгает, имхо.
Все таки в одной поре стоит только у молодого, в других случаях давление прыгает,
имхо.
Как то это слишком мудрёно.
Либо стоит либо не стоит.
А вот ПРЫГАЕТ - это не очень распространенный факт.
Это ладно, а по существу вопрос можно снять.
Даташит почитал более внимательно:
"Low noise: 0.06hPa (0.5m) in ultra low power mode"
Вот эти 6 Па шумов и дают пляску младших разрядов.
Приношу извинения автору темы за офтоп.
Андрей, а что в скетче поменять для но клапана?
а что в скетче поменять для но клапана?
Нормально открытый, отсечка по превышению температуры?
int pollPump() {
uint32_t m = MIN_POLL_DELAY;
if (termFSM[0].pausedState || termFSM[1].pausedState) {
#ifdef PUMP_PIN
digitalWrite(PUMP_PIN, 0);
#endif
digitalWrite(SOLENOID_VALVE_PIN, 0);
return (1000);
}
digitalWrite(SOLENOID_VALVE_PIN, pumpState == PUMP_STOP ? 0 : 1);
на
int pollPump() {
uint32_t m = MIN_POLL_DELAY;
if (termFSM[0].pausedState || termFSM[1].pausedState) {
#ifdef PUMP_PIN
digitalWrite(PUMP_PIN, 0);
#endif
digitalWrite(SOLENOID_VALVE_PIN, 1);
return (1000);
}
digitalWrite(SOLENOID_VALVE_PIN, pumpState == PUMP_STOP ? 1 : 0);
Да. Спасибо!
Нормально открытый, отсечка по превышению температуры?
А тут не надо?
pinMode(BEEPER_PIN, OUTPUT);
digitalWrite(BEEPER_PIN, 0);
#ifdef PUMP_PIN
pinMode(PUMP_PIN, OUTPUT);
digitalWrite(PUMP_PIN, 0);
#endif
pinMode(SOLENOID_VALVE_PIN, OUTPUT);
digitalWrite(SOLENOID_VALVE_PIN, 0);
На - digitalWrite(SOLENOID_VALVE_PIN, 1); ?
У меня тоже нормально открытый клапан...
Это поведение при старте устройства. Краник обычно закрыт, так что не критично, но правильнее в 1 (закрыто) перевести
Это поведение при старте устройства. Краник обычно закрыт, так что не критично, но правильнее в 1 (закрыто) перевести
Ага, так и понял!
Теперь еще в программу управление тиристором добавить, импульсным или фазовым способом, по выбору. А если еще и с измерением и стабилизацией напряжения или тока... То получится идеальная и простая программа для самогонщика!!!
с 41 по 60 из 98