init
This commit is contained in:
commit
f9789412da
209
DS1820.lua
Normal file
209
DS1820.lua
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- DS18B20 one wire module for NODEMCU
|
||||||
|
-- NODEMCU TEAM
|
||||||
|
-- LICENCE: http://opensource.org/licenses/MIT
|
||||||
|
-- @voborsky, @devsaurus, TerryE 26 Mar 2017
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
local modname = ...
|
||||||
|
|
||||||
|
-- Used modules and functions
|
||||||
|
local type, tostring, pcall, ipairs =
|
||||||
|
type, tostring, pcall, ipairs
|
||||||
|
-- Local functions
|
||||||
|
local ow_setup, ow_search, ow_select, ow_read, ow_read_bytes, ow_write, ow_crc8,
|
||||||
|
ow_reset, ow_reset_search, ow_skip, ow_depower =
|
||||||
|
ow.setup, ow.search, ow.select, ow.read, ow.read_bytes, ow.write, ow.crc8,
|
||||||
|
ow.reset, ow.reset_search, ow.skip, ow.depower
|
||||||
|
|
||||||
|
local node_task_post, node_task_LOW_PRIORITY = node.task.post, node.task.LOW_PRIORITY
|
||||||
|
local string_char, string_dump = string.char, string.dump
|
||||||
|
local now, tmr_create, tmr_ALARM_SINGLE = tmr.now, tmr.create, tmr.ALARM_SINGLE
|
||||||
|
local table_sort, table_concat = table.sort, table.concat
|
||||||
|
local file_open = file.open
|
||||||
|
local conversion
|
||||||
|
|
||||||
|
local DS18B20FAMILY = 0x28
|
||||||
|
local DS1920FAMILY = 0x10 -- and DS18S20 series
|
||||||
|
local CONVERT_T = 0x44
|
||||||
|
local READ_SCRATCHPAD = 0xBE
|
||||||
|
local READ_POWERSUPPLY= 0xB4
|
||||||
|
local MODE = 1
|
||||||
|
|
||||||
|
local pin, cb, unit = 3
|
||||||
|
local status = {}
|
||||||
|
|
||||||
|
local debugPrint = function() return end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Implementation
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
local function enable_debug()
|
||||||
|
debugPrint = function (...) print(now(),' ', ...) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function to_string(addr, esc)
|
||||||
|
if type(addr) == 'string' and #addr == 8 then
|
||||||
|
return ( esc == true and
|
||||||
|
'"\\%u\\%u\\%u\\%u\\%u\\%u\\%u\\%u"' or
|
||||||
|
'%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X '):format(addr:byte(1,8))
|
||||||
|
else
|
||||||
|
return tostring(addr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function readout(self)
|
||||||
|
local next = false
|
||||||
|
local sens = self.sens
|
||||||
|
local temp = self.temp
|
||||||
|
for i, s in ipairs(sens) do
|
||||||
|
if status[i] == 1 then
|
||||||
|
ow_reset(pin)
|
||||||
|
local addr = s:sub(1,8)
|
||||||
|
ow_select(pin, addr) -- select the sensor
|
||||||
|
ow_write(pin, READ_SCRATCHPAD, MODE)
|
||||||
|
local data = ow_read_bytes(pin, 9)
|
||||||
|
|
||||||
|
local t=(data:byte(1)+data:byte(2)*256)
|
||||||
|
-- t is actually signed so process the sign bit and adjust for fractional bits
|
||||||
|
-- the DS18B20 family has 4 fractional bits and the DS18S20s, 1 fractional bit
|
||||||
|
t = ((t <= 32767) and t or t - 65536) *
|
||||||
|
((addr:byte(1) == DS18B20FAMILY) and 625 or 5000)
|
||||||
|
local crc, b9 = ow_crc8(string.sub(data,1,8)), data:byte(9)
|
||||||
|
|
||||||
|
if unit == 'F' then
|
||||||
|
t = (t * 18)/10 + 320000
|
||||||
|
elseif unit == 'K' then
|
||||||
|
t = t + 2731500
|
||||||
|
end
|
||||||
|
local sgn = t<0 and -1 or 1
|
||||||
|
local tA = sgn*t
|
||||||
|
local tH=tA/10000
|
||||||
|
local tL=(tA%10000)/1000 + ((tA%1000)/100 >= 5 and 1 or 0)
|
||||||
|
|
||||||
|
if tH and (t~=850000) then
|
||||||
|
debugPrint(to_string(addr),(sgn<0 and "-" or "")..tH.."."..tL, crc, b9)
|
||||||
|
if crc==b9 then temp[addr]=t end
|
||||||
|
status[i] = 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
next = next or status[i] == 0
|
||||||
|
end
|
||||||
|
if next then
|
||||||
|
node_task_post(node_task_LOW_PRIORITY, function() return conversion(self) end)
|
||||||
|
else
|
||||||
|
--sens = {}
|
||||||
|
if cb then
|
||||||
|
node_task_post(node_task_LOW_PRIORITY, function() return cb(temp) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
conversion = (function (self)
|
||||||
|
local sens = self.sens
|
||||||
|
local powered_only = true
|
||||||
|
for _, s in ipairs(sens) do powered_only = powered_only and s:byte(9) ~= 1 end
|
||||||
|
if powered_only then
|
||||||
|
debugPrint("starting conversion: all sensors")
|
||||||
|
ow_reset(pin)
|
||||||
|
ow_skip(pin) -- skip ROM selection, talk to all sensors
|
||||||
|
ow_write(pin, CONVERT_T, MODE) -- and start conversion
|
||||||
|
for i, _ in ipairs(sens) do status[i] = 1 end
|
||||||
|
else
|
||||||
|
local started = false
|
||||||
|
for i, s in ipairs(sens) do
|
||||||
|
if status[i] == 0 then
|
||||||
|
local addr, parasite = s:sub(1,8), s:byte(9) == 1
|
||||||
|
if parasite and started then break end -- do not start concurrent conversion of powered and parasite
|
||||||
|
debugPrint("starting conversion:", to_string(addr), parasite and "parasite" or "")
|
||||||
|
ow_reset(pin)
|
||||||
|
ow_select(pin, addr) -- select the sensor
|
||||||
|
ow_write(pin, CONVERT_T, MODE) -- and start conversion
|
||||||
|
status[i] = 1
|
||||||
|
if parasite then break end -- parasite sensor blocks bus during conversion
|
||||||
|
started = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tmr_create():alarm(750, tmr_ALARM_SINGLE, function() return readout(self) end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function _search(self, lcb, lpin, search, save)
|
||||||
|
self.temp = {}
|
||||||
|
if search then self.sens = {}; status = {} end
|
||||||
|
local sens = self.sens
|
||||||
|
pin = lpin or pin
|
||||||
|
|
||||||
|
local addr
|
||||||
|
if not search and #sens == 0 then
|
||||||
|
-- load addreses if available
|
||||||
|
debugPrint ("geting addreses from flash")
|
||||||
|
local s,check,a = pcall(dofile, "ds18b20_save.lc")
|
||||||
|
if s and check == "ds18b20" then
|
||||||
|
for i = 1, #a do sens[i] = a[i] end
|
||||||
|
end
|
||||||
|
debugPrint (#sens, "addreses found")
|
||||||
|
end
|
||||||
|
|
||||||
|
ow_setup(pin)
|
||||||
|
if search or #sens == 0 then
|
||||||
|
ow_reset_search(pin)
|
||||||
|
-- ow_target_search(pin,0x28)
|
||||||
|
-- search the first device
|
||||||
|
addr = ow_search(pin)
|
||||||
|
else
|
||||||
|
for i, _ in ipairs(sens) do status[i] = 0 end
|
||||||
|
end
|
||||||
|
local function cycle()
|
||||||
|
if addr then
|
||||||
|
local crc=ow_crc8(addr:sub(1,7))
|
||||||
|
if (crc==addr:byte(8)) and ((addr:byte(1)==DS1920FAMILY) or (addr:byte(1)==DS18B20FAMILY)) then
|
||||||
|
ow_reset(pin)
|
||||||
|
ow_select(pin, addr)
|
||||||
|
ow_write(pin, READ_POWERSUPPLY, MODE)
|
||||||
|
local parasite = (ow_read(pin)==0 and 1 or 0)
|
||||||
|
sens[#sens+1]= addr..string_char(parasite)
|
||||||
|
status[#sens] = 0
|
||||||
|
debugPrint("contact: ", to_string(addr), parasite == 1 and "parasite" or "")
|
||||||
|
end
|
||||||
|
addr = ow_search(pin)
|
||||||
|
node_task_post(node_task_LOW_PRIORITY, cycle)
|
||||||
|
else
|
||||||
|
ow_depower(pin)
|
||||||
|
-- place powered sensors first
|
||||||
|
table_sort(sens, function(a, b) return a:byte(9)<b:byte(9) end) -- parasite
|
||||||
|
-- save sensor addreses
|
||||||
|
if save then
|
||||||
|
debugPrint ("saving addreses to flash")
|
||||||
|
|
||||||
|
local addr_list = {}
|
||||||
|
for i =1, #sens do
|
||||||
|
local s = sens[i]
|
||||||
|
addr_list[i] = to_string(s:sub(1,8), true)..('.."\\%u"'):format(s:byte(9))
|
||||||
|
end
|
||||||
|
local save_statement = 'return "ds18b20", {' .. table_concat(addr_list, ',') .. '}'
|
||||||
|
debugPrint (save_statement)
|
||||||
|
local save_file = file_open("ds18b20_save.lc","w")
|
||||||
|
save_file:write(string_dump(loadstring(save_statement)))
|
||||||
|
save_file:close()
|
||||||
|
end
|
||||||
|
-- end save sensor addreses
|
||||||
|
if lcb then node_task_post(node_task_LOW_PRIORITY, lcb) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cycle()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function read_temp(self, lcb, lpin, lunit, force_search, save_search)
|
||||||
|
cb, unit = lcb, lunit or unit
|
||||||
|
_search(self, function() return conversion(self) end, lpin, force_search, save_search)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set module name as parameter of require and return module table
|
||||||
|
local M = {
|
||||||
|
sens = {},
|
||||||
|
temp = {},
|
||||||
|
C = 'C', F = 'F', K = 'K',
|
||||||
|
read_temp = read_temp, enable_debug = enable_debug
|
||||||
|
}
|
||||||
|
_G[modname or 'ds18b20'] = M
|
||||||
|
return M
|
||||||
BIN
IMG_0412.jpg
Executable file
BIN
IMG_0412.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
IMG_0417.jpg
Executable file
BIN
IMG_0417.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Климатическая камера для брожения
|
||||||
|
|
||||||
|
* Объект контроля: Теплоизолированная камера, внутри которой находятся 10 кег с суслом. В каждую кегу непосредственно в жидкость погружен цифровой датчик DS1820.
|
||||||
|
* Проблема биологического тепла: В процессе брожения дрожжевые культуры активно выделяют собственное эндогенное тепло, из-за чего сусло внутри кеги греет само себя.
|
||||||
|
* Проблема высокой тепловой инерции: Масса сусла в кегах обладает огромной теплоемкостью. Измерять температуру воздуха внутри камеры или управлять нагревом по внешнему датчику воздуха «в лоб» не имеет смысла — это приводит к жестким перелетам температуры, температурному шоку и гибели дрожжей.
|
||||||
|
|
||||||
|
## Инженерное решение
|
||||||
|
|
||||||
|
1. Усреднение показаний (Многозонный контроль): Управляющим сигналом для исполнительных механизмов является строго среднее арифметическое значение, собранное непосредственно изнутри всех 10 кег.
|
||||||
|
2. Массивный теплоаккумулятор: Для мягкого сглаживания тепловых волн ТЭН залит в массивный бетонный блок. Это физически предотвращает резкие броски температуры.
|
||||||
|
3. Равномерный промес воздуха (Логика вентиляторов): Чтобы исключить застойные зоны, горячий воздух от бетона должен распределяться равномерно. Вентиляторы обдува включаются с задержкой в 5 минут после старта ТЭНа (когда бетон уже прогрелся) и продолжают работать еще 5 минут после его отключения (выбег для съема остаточного тепла). В режиме принудительного охлаждения обдув включается мгновенно.
|
||||||
|
4. Контроль и зонирование (HTTP): Для контроля ситуации (и на случай разделения камеры перегородками на разные температурные зоны в будущем) устройство поднимает Wi-Fi точку доступа. По адресу 192.168.4.1 в реальном времени выводятся точные данные по каждому «жбану» индивидуально.
|
||||||
|
|
||||||
|
## Ключевые аппаратные фичи
|
||||||
|
|
||||||
|
* Аппаратная полка безопасности (500 кОм): Вместо стандартного потенциометра на 10 кОм применен номинал 500 кОм. Входное сопротивление АЦП платы Wemos совместно с высоким номиналом резистора создает нелинейный шунтирующий эффект: в последней 1/5 части физического хода ручки график роста напряжения резко замедляется и уходит в горизонтальное насыщение («полку») ровно на отметке 28°C. Это гарантирует, что даже при случайном задевании ручки температура уставки физически не поднимется выше критического для жизни дрожжей порога.
|
||||||
|
* Разметка секторов: Управление полностью дискретное, без дисплея. Вокруг ручки нанесена физическая разметка на 7 секторов: 4°C – 8°C – 12°C – 16°C – 20°C – 24°C – 28°C. Крайний правый сектор стабильно удерживает безопасные 28°C.
|
||||||
2
flash.sh
Normal file
2
flash.sh
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
python3 -m esptool --port /dev/ttyUSB0 erase_flash
|
||||||
|
python3 -m esptool --port /dev/ttyUSB0 write_flash -fm dio 0x00000 nodemcu-release-10-modules-2026-08-18-07-13-33-integer.bin
|
||||||
184
init.lua
Normal file
184
init.lua
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
analog_pin = 0
|
||||||
|
t = require("DS1820")
|
||||||
|
num_readings = 8
|
||||||
|
readings = {}
|
||||||
|
read_index = 1
|
||||||
|
total = 0
|
||||||
|
timing = 6000
|
||||||
|
ds_pin = 1
|
||||||
|
|
||||||
|
pin_ten = 2
|
||||||
|
pin_fan = 6
|
||||||
|
pin_led_g = 7
|
||||||
|
pin_led_r = 5
|
||||||
|
|
||||||
|
adc_min = 0
|
||||||
|
adc_max = 1023
|
||||||
|
steps = {4, 8, 12, 16, 20, 24, 28}
|
||||||
|
current_t = 16
|
||||||
|
|
||||||
|
hysteresis = 1
|
||||||
|
ten_on_time = 0
|
||||||
|
ten_off_time = 0
|
||||||
|
ten_was_active = false
|
||||||
|
|
||||||
|
gpio.mode(pin_ten, gpio.OUTPUT)
|
||||||
|
gpio.mode(pin_fan, gpio.OUTPUT)
|
||||||
|
gpio.mode(pin_led_g, gpio.OUTPUT)
|
||||||
|
gpio.mode(pin_led_r, gpio.OUTPUT)
|
||||||
|
|
||||||
|
gpio.write(pin_ten, gpio.HIGH)
|
||||||
|
gpio.write(pin_fan, gpio.HIGH)
|
||||||
|
gpio.write(pin_led_g, gpio.LOW)
|
||||||
|
gpio.write(pin_led_r, gpio.LOW)
|
||||||
|
|
||||||
|
if adc.force_init_mode(adc.INIT_ADC) then node.restart() end
|
||||||
|
|
||||||
|
for i = 1, num_readings do readings[i] = 0 end
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
function read_smoothed_adc()
|
||||||
|
total = total - readings[read_index]
|
||||||
|
local raw = adc.read(analog_pin)
|
||||||
|
readings[read_index] = raw
|
||||||
|
total = total + raw
|
||||||
|
read_index = read_index + 1
|
||||||
|
if read_index > num_readings then read_index = 1 end
|
||||||
|
return total / num_readings
|
||||||
|
end
|
||||||
|
|
||||||
|
function control_logic(avg_temp)
|
||||||
|
local time_now = tmr.time()
|
||||||
|
local temp_ok = (avg_temp >= (current_t - hysteresis)) and (avg_temp <= (current_t + hysteresis))
|
||||||
|
|
||||||
|
if temp_ok then
|
||||||
|
gpio.write(pin_led_g, gpio.HIGH)
|
||||||
|
gpio.write(pin_led_r, gpio.LOW)
|
||||||
|
else
|
||||||
|
gpio.write(pin_led_g, gpio.LOW)
|
||||||
|
gpio.write(pin_led_r, gpio.HIGH)
|
||||||
|
end
|
||||||
|
|
||||||
|
if avg_temp < (current_t - hysteresis) then
|
||||||
|
if gpio.read(pin_ten) == gpio.HIGH then
|
||||||
|
gpio.write(pin_ten, gpio.LOW)
|
||||||
|
ten_on_time = time_now
|
||||||
|
ten_was_active = true
|
||||||
|
end
|
||||||
|
if ten_was_active and ten_on_time > 0 and (time_now - ten_on_time >= 300) then
|
||||||
|
if gpio.read(pin_fan) == gpio.HIGH then
|
||||||
|
gpio.write(pin_fan, gpio.LOW)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
gpio.write(pin_fan, gpio.HIGH)
|
||||||
|
end
|
||||||
|
elseif avg_temp > (current_t + hysteresis) then
|
||||||
|
gpio.write(pin_ten, gpio.HIGH)
|
||||||
|
ten_was_active = false
|
||||||
|
ten_on_time = 0
|
||||||
|
if gpio.read(pin_fan) == gpio.HIGH then
|
||||||
|
gpio.write(pin_fan, gpio.LOW)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if gpio.read(pin_ten) == gpio.LOW then
|
||||||
|
gpio.write(pin_ten, gpio.HIGH)
|
||||||
|
ten_off_time = time_now
|
||||||
|
end
|
||||||
|
if ten_was_active and ten_off_time > 0 then
|
||||||
|
if (time_now - ten_off_time >= 300) then
|
||||||
|
gpio.write(pin_fan, gpio.HIGH)
|
||||||
|
ten_was_active = false
|
||||||
|
ten_on_time = 0
|
||||||
|
ten_off_time = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
gpio.write(pin_fan, gpio.HIGH)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ten_state = gpio.read(pin_ten) == gpio.LOW and 1 or 0
|
||||||
|
local fan_state = gpio.read(pin_fan) == gpio.LOW and 1 or 0
|
||||||
|
print(string.format("T_cur: %2d C | T_set: %2d C | TEN: %d | FAN: %d | Act: %s",
|
||||||
|
avg_temp, current_t, ten_state, fan_state, tostring(ten_was_active)))
|
||||||
|
end
|
||||||
|
|
||||||
|
function readout(temp)
|
||||||
|
local count = 0
|
||||||
|
local sum = 0
|
||||||
|
for addr, t_val in pairs(temp) do
|
||||||
|
local short_id = string.format('%02X', addr:byte(8))
|
||||||
|
local t_ceil = t_val
|
||||||
|
if t_val > 1000 then t_ceil = t_val / 10000 end
|
||||||
|
|
||||||
|
results[short_id] = t_ceil
|
||||||
|
print("Sensor " .. short_id .. ": " .. t_ceil .. " C")
|
||||||
|
|
||||||
|
sum = sum + t_ceil
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
if count > 0 then
|
||||||
|
control_logic(sum / count)
|
||||||
|
else
|
||||||
|
print("No sensors found")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function read_sensors()
|
||||||
|
t:read_temp(readout, ds_pin, t.C)
|
||||||
|
end
|
||||||
|
|
||||||
|
function start_web_server()
|
||||||
|
wifi.setmode(wifi.SOFTAP)
|
||||||
|
local cfg = {}
|
||||||
|
cfg.ssid = "Thermostat-WiFi"
|
||||||
|
wifi.ap.config(cfg)
|
||||||
|
|
||||||
|
srv = net.createServer(net.TCP)
|
||||||
|
srv:listen(80, function(conn)
|
||||||
|
conn:on("receive", function(sck, payload)
|
||||||
|
local response = {"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"}
|
||||||
|
table.insert(response, "<html><head><meta http-equiv='refresh' content='5'></head><body>")
|
||||||
|
table.insert(response, "<h2>Thermostat Status</h2>")
|
||||||
|
table.insert(response, "<p><b>Target Temp:</b> " .. current_t .. " C</p><hr>")
|
||||||
|
|
||||||
|
for id, temp in pairs(results) do
|
||||||
|
table.insert(response, "<p><b>Tank [ID: " .. id .. "]:</b> " .. temp .. " C</p>")
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(response, "</body></html>")
|
||||||
|
|
||||||
|
local function send_chunk(sk)
|
||||||
|
if #response > 0 then
|
||||||
|
sk:send(table.remove(response, 1))
|
||||||
|
else
|
||||||
|
sk:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sck:on("sent", send_chunk)
|
||||||
|
send_chunk(sck)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
print("HTTP Server started on 192.168.4.1")
|
||||||
|
end
|
||||||
|
|
||||||
|
tmr.create():alarm(2000, tmr.ALARM_SINGLE, function()
|
||||||
|
collectgarbage()
|
||||||
|
start_web_server()
|
||||||
|
|
||||||
|
sensortimer = tmr.create()
|
||||||
|
sensortimer:register(timing, tmr.ALARM_AUTO, read_sensors)
|
||||||
|
sensortimer:start()
|
||||||
|
|
||||||
|
adctimer = tmr.create()
|
||||||
|
adctimer:register(300, tmr.ALARM_AUTO, function()
|
||||||
|
local avg_raw = read_smoothed_adc()
|
||||||
|
if avg_raw < adc_min then avg_raw = adc_min end
|
||||||
|
if avg_raw > adc_max then avg_raw = adc_max end
|
||||||
|
local step_index = 1 + ((avg_raw - adc_min) * (#steps - 1)) / (adc_max - adc_min)
|
||||||
|
if step_index > #steps then step_index = #steps end
|
||||||
|
if step_index < 1 then step_index = 1 end
|
||||||
|
current_t = steps[step_index]
|
||||||
|
end)
|
||||||
|
adctimer:start()
|
||||||
|
end)
|
||||||
BIN
nodemcu-release-10-modules-2026-08-18-07-13-33-integer.bin
Normal file
BIN
nodemcu-release-10-modules-2026-08-18-07-13-33-integer.bin
Normal file
Binary file not shown.
BIN
potentiometer_scale.png
Normal file
BIN
potentiometer_scale.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
37
schematic.md
Normal file
37
schematic.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# ТЕХНИЧЕСКАЯ СХЕМА ПОДКЛЮЧЕНИЯ ЭЛЕМЕНТОВ
|
||||||
|
|
||||||
|
### 1. Аналоговый узел задания температуры (Защитный барьер)
|
||||||
|
В качестве регулятора уставки используется потенциометр с высоким номиналом, формирующий нелинейную «полку» насыщения АЦП.
|
||||||
|
* **Переменный резистор (500 кОм):**
|
||||||
|
* Левый вывод ➡️ Пин **`3V3`** платы Wemos D1
|
||||||
|
* Правый вывод ➡️ Пин **`GND`** платы Wemos D1
|
||||||
|
* Средний вывод (бегунок) ➡️ Аналоговый пин **`A0`** платы Wemos D1
|
||||||
|
* *Примечание:* Полученный сигнал программно фильтруется в прошивке с помощью 8-кратного скользящего среднего для устранения наводок.
|
||||||
|
|
||||||
|
### 2. Измерительный контур сусла (Шина 1-Wire)
|
||||||
|
Все 10 герметичных датчиков, погруженных в кеги, подключаются параллельно по топологии «шина».
|
||||||
|
* **10 датчиков DS18B20:**
|
||||||
|
* Все красные провода (`VCC` / Питание) ➡️ Пин **`3V3`** платы Wemos D1
|
||||||
|
* Все черные провода (`GND` / Земля) ➡️ Пин **`GND`** платы Wemos D1
|
||||||
|
* Все желтые/белые провода (`DATA` / Данные) ➡️ Цифровой пин **`D1`** платы Wemos D1
|
||||||
|
* **Аппаратное требование:** Между линией `DATA` (пин `D1`) и линией `VCC` (пин `3V3`) необходимо впаять один жесткий подтягивающий резистор номиналом **4.7 кОм**. Без него из-за суммарной емкости длинных проводов от 10 кег шина 1-Wire перестанет читаться.
|
||||||
|
|
||||||
|
### 3. Силовая исполнительная часть (Модуль реле Low Level Trigger)
|
||||||
|
Блок реле управляется активным низким уровнем (`LOW` / Земля). При подаче логического нуля со стороны Wemos реле щелкает, и на модуле загорается соответствующий светодиод канала.
|
||||||
|
* **Реле ТЭНа (залит в бетон):**
|
||||||
|
* Вывод `IN1` (Вход управления 1) ➡️ Цифровой пин **`D2`** платы Wemos D1
|
||||||
|
* **Реле Вентиляторов (промес воздуха):**
|
||||||
|
* Вывод `IN2` (Вход управления 2) ➡️ Цифровой пин **`D6`** платы Wemos D1
|
||||||
|
* **Питание катушек модуля реле:**
|
||||||
|
* Вывод `VCC` модуля ➡️ Положительный вывод (**`+5В`**) внешнего блока питания
|
||||||
|
* Вывод `GND` модуля ➡️ Минусовой вывод (**`GND`**) внешнего блока питания *(общая земля с Wemos)*
|
||||||
|
|
||||||
|
### 4. Панель визуального мониторинга камеры (Светодиоды)
|
||||||
|
Светодиоды подключаются через токоограничивающие резисторы и управляются активным высоким уровнем (`HIGH` / 3.3В).
|
||||||
|
* **Зеленый светодиод (Температура сусла в норме):**
|
||||||
|
* Анод (длинная ножка) ➡️ Через резистор **220 Ом** на цифровой пин **`D7`** платы Wemos D1
|
||||||
|
* Катод (короткая ножка с плоским срезом) ➡️ Пин **`GND`** платы Wemos D1
|
||||||
|
* **Красный светодиод (Идет нагрев / Охлаждение камеры):**
|
||||||
|
* Анод (длинная ножка) ➡️ Через резистор **220 Ом** на цифровой пин **`D5`** платы Wemos D1
|
||||||
|
* Катод (короткая ножка с плоским срезом) ➡️ Пин **`GND`** платы Wemos D1
|
||||||
|
* *Внимание:* Пин `D5` выбран намеренно вместо дефолтного `D8`. Притягивание пина `D8` к высокому уровню во время старта намертво блокирует загрузку процессора ESP8266, переводя его в режим ожидания прошивки. С пином `D5` старт устройства всегда безопасен.
|
||||||
Loading…
x
Reference in New Issue
Block a user