ESP32 как эмулятор 4–20 мА для датчиков температуры, давления, влажности и кислорода
При разработке и отладке систем автоматизации, контроллеров (ПЛК) или SCADA-систем часто возникает необходимость проверить работу аналогового входа без реальных датчиков. Эмулятор токовой петли 4–20 мА на ESP32 решает эту задачу: он генерирует плавно изменяющиеся значения физических величин (температура, давление, влажность, O₂) и преобразует их в пропорциональный ток 4–20 мА с помощью ШИМ + внешнего преобразователя.
Как это работает
Код генерирует четыре независимых параметра, каждый со своим диапазоном. Общий режим GLOBAL_MODE (0…6) определяет, в какой части диапазона находятся значения: заниженные (mode 0), внутри одной из пяти равных частей (mode 1–5) или завышенные (mode 6). Это позволяет имитировать нештатные ситуации или стабильные показания.
Значения меняются плавно по синусоидальному закону, имитируя реальные флуктуации датчика. Для каждого параметра вычисляется требуемый ток (4–20 мА), который затем преобразуется в скважность ШИМ (0–65535). ESP32 выдаёт ШИМ-сигнал на заданные GPIO. Внешняя схема (RC-фильтр + преобразователь напряжение-ток) превращает этот ШИМ в стабильный ток 4–20 мА.
Параметры и их диапазоны
| Параметр | Диапазон | Единица | Выходной ток |
|---|---|---|---|
| Температура | 0 … 100 °C | °C | 4 мА → 0°C, 20 мА → 100°C |
| Давление | 0 … 10 бар | bar | 4 мА → 0 бар, 20 мА → 10 бар |
| Влажность | 0 … 100 % | % | 4 мА → 0%, 20 мА → 100% |
| Кислород (O₂) | 0 … 25 % | % | 4 мА → 0%, 20 мА → 25% |
Настройки в коде (в начале файла)
GLOBAL_MODE = 3 # 0..6 – общий режим для всех каналов
UPDATE_INTERVAL_MS = 200 # период обновления значений (мс)
LOG_INTERVAL_MS = 5000 # период вывода логов в консоль (мс)
PWM_FREQ = 1000 # частота ШИМ (Гц), 1000 Гц оптимальноРежимы GLOBAL_MODE:
0 – значения ниже минимального (имитация неисправности датчика)
1 – значения в первой пятой части диапазона (самые низкие)
2 – вторая пятая часть
3 – третья пятая часть (средние значения)
4 – четвёртая пятая часть
5 – пятая пятая часть (самые высокие)
6 – значения выше максимального (имитация перегрузки)
Подключение ESP32 к внешней схеме (4–20 мА)
ESP32 выдает ШИМ-сигнал (0–3.3 В, 1 кГц) на выбранные пины. Для получения тока 4–20 мА требуется внешний преобразователь. Простейший вариант – RC-фильтр + преобразователь напряжение-ток на LM358 и транзисторе.
Рекомендуемые пины (можно менять)
| Параметр | GPIO |
|---|---|
| Температура | 32 |
| Давление | 33 |
| Влажность | 25 |
| Кислород | 26 |
Схема подключения (один канал)
ESP32 GPIO (PWM) → резистор 10 кОм → конденсатор 10 мкФ → GND
(точка после RC-фильтра) → вход преобразователя напряжение-ток
Преобразователь (пример на LM358 + NPN транзистор) выдает ток 4–20 мА в нагрузку (обычно 250 Ом для входа ПЛК).
Готовые модули: можно использовать промышленные преобразователи «PWM to 4-20mA» (например, на чипе XTR115, GP8600, CP7121). Они подключаются напрямую к ШИМ-выходу ESP32 и питанию 24 В.
Логирование в REPL (через USB)
Каждые 5 секунд в последовательный порт (USB) выводятся текущие значения всех параметров и соответствующий им ток:

Это позволяет наблюдать динамику без дополнительных приборов.
Запуск и проверка
Загрузите код в ESP32 (файл main.py).
Подключите RC-фильтр и преобразователь к указанным пинам.
Подключите нагрузку (например, резистор 250 Ом) к выходу преобразователя.
Откройте монитор порта (115200 бод) – увидите логи.
Измерьте ток в цепи мультиметром – он должен плавно меняться в пределах 4–20 мА.
⚠️ Важные замечания
Питание преобразователя обычно требуется внешнее (12–24 В), ESP32 его не обеспечивает.
Частота ШИМ 1000 Гц подходит для большинства схем. При необходимости измените PWM_FREQ.
Резистор нагрузки в токовой петле обычно 250 Ом (соответствует входу 1–5 В ПЛК). Для других значений пересчитайте сопротивление.
Если после RC-фильтра напряжение нестабильно, увеличьте ёмкость конденсатора до 47 мкФ.
Полный код
# current_loop_emulator.py
# ESP32 эмулятор 4-20 мА для 4 параметров
# Параметры: T (0-100°C), P (0-10 bar), H (0-100%), O2 (0-25%)
# Глобальный режим 0-6 управляет положением значения внутри диапазона
# Выход ШИМ на заданных пинах -> после RC-фильтра и преобразователя даёт 4-20 мА
import machine
import time
import math
from machine import Pin, PWM
# ==================== НАСТРОЙКИ ====================
# Общий режим для всех каналов (0..6)
GLOBAL_MODE = 3
# Частота обновления значений (мс)
UPDATE_INTERVAL_MS = 200
# Частота логирования в REPL (мс)
LOG_INTERVAL_MS = 5000
# Частота ШИМ (Гц) – оптимально 1 кГц
PWM_FREQ = 1000
# Пины ШИМ для каждого параметра (можно любые, поддерживающие PWM)
# Рекомендуемые: GPIO32, GPIO33, GPIO25, GPIO26
PINS = {
"Temperature": 32,
"Pressure": 33,
"Humidity": 25,
"Oxygen": 26
}
# Диапазоны физических величин
RANGES = {
"Temperature": (0.0, 100.0), # °C
"Pressure": (0.0, 10.0), # bar
"Humidity": (0.0, 100.0), # %
"Oxygen": (0.0, 25.0) # %
}
# Единицы измерения
UNITS = {
"Temperature": "°C",
"Pressure": "bar",
"Humidity": "%",
"Oxygen": "%"
}
# ==================== ПРЕОБРАЗОВАНИЕ ====================
def physical_to_current(value, min_phy, max_phy, min_cur=4.0, max_cur=20.0):
"""Линейное преобразование физической величины в ток (мА)"""
if value <= min_phy:
return min_cur
if value >= max_phy:
return max_cur
return min_cur + (value - min_phy) * (max_cur - min_cur) / (max_phy - min_phy)
def current_to_duty(current_ma):
"""Преобразование тока (мА) в duty cycle ШИМ (0-65535 для 16-бит)"""
# Предполагаем, что ШИМ с размахом 0-3.3В, затем схема 0-3.3В -> 4-20 мА
# Для упрощения: duty = (current_ma - 4) / 16 * 65535
if current_ma <= 4.0:
return 0
if current_ma >= 20.0:
return 65535
return int((current_ma - 4.0) / 16.0 * 65535)
# ==================== КЛАСС ПАРАМЕТРА ====================
class Parameter:
def __init__(self, name, min_phy, max_phy, unit):
self.name = name
self.min_phy = min_phy
self.max_phy = max_phy
self.unit = unit
self.phase = 0.0
self.current_value = (min_phy + max_phy) / 2.0
def split_range(self):
step = (self.max_phy - self.min_phy) / 5.0
ranges = []
for i in range(5):
ranges.append((self.min_phy + i * step, self.min_phy + (i+1) * step))
return ranges
def generate_smooth(self, low, high):
amplitude = (high - low) / 4.0
middle = (low + high) / 2.0
value = middle + amplitude * math.sin(self.phase)
self.phase += 0.2
if self.phase > 2 * math.pi:
self.phase -= 2 * math.pi
if value < low:
value = low
if value > high:
value = high
return value
def update(self, mode):
if mode == 0:
low = self.min_phy - 0.1 * self.min_phy
high = self.min_phy
self.current_value = self.generate_smooth(low, high)
elif mode == 6:
low = self.max_phy
high = self.max_phy + 0.12 * self.max_phy
self.current_value = self.generate_smooth(low, high)
elif 1 <= mode <= 5:
ranges = self.split_range()
low, high = ranges[mode - 1]
self.current_value = self.generate_smooth(low, high)
else:
self.current_value = self.generate_smooth(self.min_phy, self.max_phy)
# Ограничим физическим диапазоном (на всякий случай)
if self.current_value < self.min_phy:
self.current_value = self.min_phy
if self.current_value > self.max_phy:
self.current_value = self.max_phy
# ==================== СОЗДАНИЕ ПАРАМЕТРОВ ====================
parameters = []
for name, (min_phy, max_phy) in RANGES.items():
parameters.append(Parameter(name, min_phy, max_phy, UNITS[name]))
# ==================== ИНИЦИАЛИЗАЦИЯ ШИМ-ВЫХОДОВ ====================
pwm_outputs = {}
for param in parameters:
pin_num = PINS[param.name]
pwm = PWM(Pin(pin_num), freq=PWM_FREQ, duty_u16=0)
pwm_outputs[param.name] = pwm
# ==================== ГЛАВНЫЙ ЦИКЛ ====================
last_update = time.ticks_ms()
last_log = time.ticks_ms()
led = Pin(2, Pin.OUT)
led.value(0)
print("=" * 50)
print("Эмулятор 4-20 мА запущен")
print("Режим (общий для всех каналов):", GLOBAL_MODE)
print("Параметры и пины:")
for param in parameters:
print(" {:10s} : pwm GPIO{} , диапазон {:3.0f}-{:3.0f} {}".format(
param.name, PINS[param.name], param.min_phy, param.max_phy, param.unit))
print("Обновление каждые {} мс, логирование каждые {} сек".format(UPDATE_INTERVAL_MS, LOG_INTERVAL_MS//1000))
print("=" * 50)
while True:
# 1. Обновление значений и ШИМ
if time.ticks_diff(time.ticks_ms(), last_update) >= UPDATE_INTERVAL_MS:
for param in parameters:
param.update(GLOBAL_MODE)
# Вычисляем ток и устанавливаем duty
current_ma = physical_to_current(param.current_value, param.min_phy, param.max_phy)
duty = current_to_duty(current_ma)
pwm_outputs[param.name].duty_u16(duty)
last_update = time.ticks_ms()
led.toggle() # мигаем при обновлении
# 2. Логирование в REPL
if time.ticks_diff(time.ticks_ms(), last_log) >= LOG_INTERVAL_MS:
print("\n[{}] Текущие значения (mode={}):".format(time.ticks_ms(), GLOBAL_MODE))
for param in parameters:
current_ma = physical_to_current(param.current_value, param.min_phy, param.max_phy)
print(" {:10s} = {:6.2f} {} -> {:5.2f} мА".format(
param.name, param.current_value, param.unit, current_ma))
print("----------------------------------")
last_log = time.ticks_ms()
time.sleep_ms(10)Итог
Этот код превращает ESP32 в мощный инструмент для тестирования аналоговых входов систем управления. Вы можете имитировать любые датчики с выходом 4–20 мА, быстро менять режимы работы и наблюдать за результатами через USB-консоль. Простота модификации позволяет добавить новые параметры или изменить диапазоны под свои задачи.
Только полноправные пользователи могут оставлять комментарии. Аутентифицируйтесь пожалуйста, используя сервисы.