Форум самогонщиков Сайт Барахолка Магазин Помощь солдатам

Ненавязчивая автоматизация ректификационной установки

Форум самогонщиков Автоматика
1 ... 40 41 42 43 44 45 46 ... 132 43
ZagAl Доцент Прибалтика 1.9K 916
Отв.840  01 Дек. 17, 21:33
Модули из комплекта nna_02. Поскольку я модифицирую скрипт под свои хотелки, то хотелось бы хорошо разбираться в том, что написано Вами. Я посмотрел nna_36, но там для меня гораздо больше вопросов, чем в nna_02. Python в стадии изучения, и по мере освоения буду переходить к более сложным скриптам.
PavelSaratov Доктор наук Саратов 622 80
Отв.841  02 Дек. 17, 06:46
Кстати, вопрос to all по IDE - я в растерянности от их изобилия. Уже успел запутаться, где что. В одном (или не в одном?) видео бал графический интерфейс с мнемосхемой для конфигурирования микроконтроллера. Выбираешь тип платы, навешиваешь обвязку, и, по меньшей мере, подключение библиотек и сетап генерятся автоматом. Приличного сравнения разных сред пока не нашел. Ткните носом пальцем, если знаете, где. Или поделитесь впечатлениями. Улыбающийся
Под чт тебе надо IDE? Выбор не такой большой - CubeMX предлагает только 3 если не изменяет склероз варианта. А без куба все это генерить - ну можно и сразу на ассемблере херачить.
По поводу операционки - я бы пробовал чето более популярное. Это собственно дорогая Keil RTX (но как пишут роялти фри)  или что более распространено, бесплатно, но всетаки псевдо реал тайм - FreeRTOS (обалденно написано тут easyelectronics.ru/img/ARM_kurs/FreeRTOS/Kurniz.pdf). И то и то полезно может быть для будущего. Почему выбор пал на scmRTOS - посвятите в личку, ибо оофтоп и холивар.

Или ты про ардуину и нормальную замену тому текстовому редакторк что к ней идет? (Кстати даже хуже тесктового редактора- просто жесть а не IDE)
OldBean Доцент Красноярск 1K 1.4K
Отв.842  02 Дек. 17, 09:37
Поскольку я модифицирую скрипт под свои хотелки,ZagAl, 01 Дек. 17, 21:33
И как мы будем разбираться с модифицированным скриптом, если исходник его есть только у Вас? :)
Выкладывайте текст - посмотрим. И, естественно, текст модулей, если они тоже модифицированы.


PS
В принципе, можно "пройтись" по софту с самого сначала. Я как раз в декабре собирался "перетряхнуть" весь код этой автоматизации. Чтобы до конца года завершить разработку этого варианта системы. Которая "не LITE". Ну, если, конечно, не прилетит какая-нибудь срочная суета по работе. В планах: 1) Повысить надежность обмена малинки с контроллерами по I2C. В простейшем случае - за счет дублирования посылок и последующим "голосованием" полученных данных. А там посмотрим. Может и этого хватит ;) 2) Сделать один файл модулей и "причесать" его. Так, судя по всему, будет проще новичкам; 3) Упростить и лучше структурировать базовый скрипт. Который nna_xx.py; 4) Возможно - сделать отдельный конфигурационный файл, чтобы пользователю не нужно было бы лазить в питоновский текст скрипта.

Дело в том, что, с самого начала, я совсем не рассчитывал, что кодом этой системы будут пытаться пользоваться новички в программировании (и, в частности, в питоне). Я рассматривал эти коды просто как примеры реализации. Которые кто-нибудь (в смысле - programmer), решающий аналогичные задачи, мог бы посмотреть. Как прототип или вариант решения. И не более! Поэтому количество проверок, защит, блокировок и диагностических сообщений там совершенно минимальное. По сути - только для защиты от собственного склероза ;). Но уж коли так сложилось, то могу во время "перетряхивания" выкладывать фрагменты кода с пояснениями что, как, к чему и зачем. Так чтобы можно было последовательно, по этапам, во все вникнуть, все понять и далее пользоваться кодом вполне осмысленно. Не знаю насколько меня хватит (в смысле - терпения и времени), но попробовать можно...
PavelSaratov Доктор наук Саратов 622 80
Отв.843  02 Дек. 17, 11:20
ОлдБин, просто предложение, ты бы лучше блок-диаграмму (структуру своей программы) в картинках нарисовал. А потом уже некоторый болки пояснения.

Поверь, это нереально толкнет творчество масс. Ибо мало кому охота реверсинжинирингом заниматься - работает и ладно.
ram78 Бакалавр Перловка 91 11
Отв.844  02 Дек. 17, 13:32
всем привет. провел примерно 5 перегонов, все работает как надо. Не будет ли модулька для НБК? Думаю многим было бы интересно.
OldBean Доцент Красноярск 1K 1.4K
Отв.845  02 Дек. 17, 14:05, через 33 мин
ты бы лучше блок-диаграмму (структуру своей программы) в картинках нарисовал.PavelSaratov, 02 Дек. 17, 11:20
А какой-нибудь смысл в этом есть? Python - язык сверхвысокого уровня с очень выразительным синтаксисом. Проверено не раз: подробно откомментированный код на питоне гораздо нагляднее диаграммного описания или псевдокода. Более того, код на питоне сразу и легко проверяется "на месте". Пошагово. В режиме интерпретатора. На любой машине и любой ОС ;) Хотя, возможно, это - дело вкуса и привычек.
Ибо мало кому охота реверсинжинирингом заниматься - работает и ладно.PavelSaratov, 02 Дек. 17, 11:20
Здесь речь идет не о реижиниринге, а просто об обучении программированию на C/python на конкретных практически полезных примерах.
всем привет. провел примерно 5 перегонов, все работает как надо. Не будет ли модулька для НБК?ram78, 02 Дек. 17, 13:32
Поздравляю! Про модулек для НБК. В общем-то тема "заточена" на ректификацию. А какой модулек хотелось бы для НБК?
ram78 Бакалавр Перловка 91 11
Отв.846  02 Дек. 17, 14:16, через 12 мин
Хотелось бы автоматическое поддержание подачи насоса по мощности тэнов и по давлению в кубе. датчик давления на ардуинке, а если бы на 3-х значном сегменте было бы вообще супер.
PavelSaratov Доктор наук Саратов 622 80
Отв.847  02 Дек. 17, 14:47, через 32 мин
 Я имел ввиду следующее - блок схема, flow charts и прочее - это по сути метаязык для техзадания. Его могут читать даже не программисты. Как только оно появляется в картинках - народное творчество начинает обсуждать и развивать. Поверь просто опыту человека, много лет разжевывающему КИПовцам и прочим процесс инженерам, как установки работают с точки зрения автоматики. Даже на языке FBD они плохо понимают, вместо того чтобы думать об алгоритме, они думают как перевести на человеческий язык что написано. А картинки понимают все, если не совсем гуманитарии Улыбающийся Аналогия - восприятие на слух английской речи. Вместо того чтобы понять о чем речь и принять участие, человек училенно в башке пытается все перевести. И только после некоторого уровня у него все на автомате и он становится собеседником а не слушателем.
Другой пример - вопросы тут периодами - а где найти ПИД регулятор и чего это он там делает , как так?!

Другой нюанс - хорошая блок схема дает овервью. Мне не надо читать всю программу чтобы понять как она работает в целом, если у меня есть сей дизайн документ.
Никто здесь не говорит про блоксхему, в которой разжевано все до последнего IF. Есть уровень абстракции повыше.  Из хорошей блоксхемы явно видно как запроектирована программа, где что искать сразу понятно. Вобщем это само по себе исскуство, сделать ее не менее сложно чем выгнать хороший спирт Улыбающийся
ZagAl Доцент Прибалтика 1.9K 916
Отв.848  02 Дек. 17, 14:58, через 11 мин
И как мы будем разбираться с модифицированным скриптом, если исходник его есть только у Вас? :)OldBean, 02 Дек. 17, 09:37
Я уже выкладывал информацию по шаговому двигателю: Контроллер шагового двигателя.
Но никого это похоже не заинтересовало, так как обсуждения не последовало. В том сообщении приложен файл класса-обертки для шагового двигателя - contrSTPR.py
А поскольку автоматизация, предлагаемая в разных темах, нацелена на жидкостный отбор, а у меня отбор по пару, то приходится самому пытаться сделать то, что мне нужно, отталкиваясь от уже сделанного Вами.
Вот несколько модифицированная версия файла nna_02.py (в Скрытом тексте, так как это всего лишь неготовый вариант) Здесь для анализа степени изменения температуры я применил списки, но анализ ведется по двум значениям, что не очень хорошо (см. график). Нужно доработать алгоритм для анализа по трем точкам, тогда можно будет отслеживать экстремумы. Что-то произошло на 50ой минуте, что рост температуры остановился. Но несмотря на это, колонна все же спокойно вошла в покапельный режим на 75-76 минуте. Это приблизительно 55тая минута с момента стабилизации колонны.  В окончательном варианте время выхода на капельный режим должно быть в районе 30 минут с момента стабилизации колонны.
В скрипте выражение if Deflegmator.N < 73: Deflegmator.N = 73 добавлено для страховки, т.к. один раз было так, что вместо значения положения шагового двигателя скажем 128, было передано по i2c 12 или 18, точно не помню. И сегодня это сыграло злую шутку, так как для более быстрого выхода на режим нужны были более низкие значения положения шагового двигателя. Но я не хотел вмешиваться в процесс и ждал с интересом как все разрешится.
Скрытый текст#coding:utf-8
import sys, os, time
import collections
"""Архив с модулями  sens, contr и kbh находятся в приложении к
  данному топику"""
import sens, contr, kbh, contrSTPR
#step_number=1
#steps_in_circle=5
Tcol_list=[]
Tdef_list=[]
Column_stabilized = False
Tcol_max = False

"""Сначала создаем объекты, соответствующие датчикам и контроллерам.
  Параллельно производится проверка их наличия и работоспособности.

  Начнем с датчиков температуры (DS18B20). Идентификаторы датчиков,
  установленных в системе известны. Определяем соответствующие
  переменные"""
hid = 0x8000001ee27b # ID датчика температуры куба
cid = 0x8000001ef6e6 # ID датчика температуры колонны
did = 0x000006273720 # ID датчика температуры дефлегматора
wid = 0x8000000419af # ID датчика температуры выходящей воды
"""Создаем вспомогательный список tids, для удобства коллективной
  работы со всеми датчиками"""
tids = (hid, cid, did, wid)
"""Статический метод Ts() класса DS18B20 возвращает словарь, содержащий
  пары {идентификатор датчика : его температура} для всех датчиков,
  подключенных к шине 1-Wire"""
Ts = sens.DS18B20.Ts()
"""Проверим, все ли датчики, с идентификаторами, перечисленными выше,
  есть в этом словаре (т.е. висят на шине и исправны)"""
flag = False
for id in tids: # Пробегаем по списку нужных нам датчиков
 if Ts.get(id) != None: # Соответствующий ключ (id) есть есть в словаре
   print "0x%012x :% 7.3f°C" % (id, Ts[id]) # Доложим об этом...
 else: # Увы...
   print "Датчик с ID = 0x%012x отсутствует или неисправен" % (id)
   flag = True # Выставляем флаг, что нужного датчика нет на шине
if flag:
 print "Один или более датчиков DS18B20 отсутствуют или неисправны. Завершение работы."
 sys.exit(1)

"""С датчиками температуры разобрались. Теперь займемся датчиком
  RMS (действующее напряжение сети)"""
vs = sens.RMS() # Создаем объект - датчик RMS
if vs != None: # Датчик в наличии и функционирует
 V = vs.V
 if V > 100: print "Действующее напряжение сети %d В" % (V)
 else:
   print "Слишком низкое напряжение сети. Завершение работы."
   sys.exit(2)
else: pass # Ну на нет и суда нет. Можно работать и без датчика RMS
"""Датчик атмосферного давления - полезная вещь. Проверим его наличие
  и работоспособность. Если его нет - не беда. Будем работать без него"""
ps = sens.BMP180() # Создаем объект - датчик атмосферного давления
if ps != None: # Датчик в наличии и функционирует
 print "Атмосферное давление: \t%5.1f мм.рт.ст." % (ps.P)
 print "Температура окружающей среды:\t%5.2f°C" % (ps.T)

"""Создаем объекты-контроллеры исполнительных устройств"""
teh = contr.TEH(vs) # Создадим объект "ТЭН"
if teh == None:
 print "Контроллер ТЭНа отсутствует или неисправен. Завершение работы."
 sys.exit(3)
sd = contr.SD() # Создадим объект "устройство отбора"
"""В принципе можно работать и без клапана отбора. Например, с ручной
  "пережимкой". Если ситуация именно такова, то закомментируйте следующие
  три строчки"""
#if sd == None:
#  print "Контроллер клапана отбора отсутствует или неисправен. Завершение работы."
#  sys.exit(3)
Deflegmator=contrSTPR.STPR(addrSTPR=0x07)
if Deflegmator == None:
 print "Контроллер Дефлегматора отсутствует или неисправен. Завершение работы."
 sys.exit(4)
Holodilnik=contrSTPR.STPR(addrSTPR=0x08)
if Holodilnik == None:
 print "Контроллер Холодильника отсутствует или неисправен. Завершение работы."
 sys.exit(5)

"""Все. С датчиками и исполнительными устройствами мы разобрались.
  Теперь переведем консоль в неблокирующий режим работы. Это нужно
  для того, чтобы можно было бы в главном цикле приложения обрабатывать
  нажатие клавиш клавиатуры. Необходимый для этого сервис организован
  в виде класса TT, который находится в модуле kbh. При создании объекта
  (экземпляра этого класса) как раз и происходит переход консоли
  в неблокирующий режим. При уничтожении объекта (в деструкторе)
  консоль возвращается к обычному режиму работы.

  Параметр timeout задает время ожидания нажатия клавиши (он может быть
  равен 0). В данном случае мы используем этот параметр для регулирования
  длительности одного такта главного цикла приложения. При timeout = 3000
  длительность такта составляет около 4 сек"""
timeout = 3000 # В мс
tt = kbh.TT(timeout)

"""Теперь сформируем карту режимов, доступных в данном приложении. Режимы
  пронумерованы от 0 до 6. Карта режимов представляет собой список
  именованных кортежей (т.е. к элементам кортежа можно обращаться
  по имени). Это очень удобно. Имена элементов кортежа:
  W     - мощность нагрева ТЭНа, Вт (вещественное число)
  stab  - стабилизация мощности нагрева ТЭНа (True/False)
  Q     - скорость отбора, мл/час (вещественное число)
  ststp - режим старт-стопа (True/Flase)"""
#-------------------------------------------------------------------------------
mrec = collections.namedtuple('mrec', 'W stab Q ststp')
modes = [] # Собственно список режимов
modes.append(mrec(0, False, 0, False))        # 0 - Только мониторинг
modes.append(mrec(2000, False, 0, False))     # 1 - Разгон
modes.append(mrec(650, True, 0, False))       # 2 - Стабилизация с выходом на отбор голов
modes.append(mrec(600, True, 40, False))      # 3 - Отбора голов
modes.append(mrec(600, True, 400, True))      # 4 - Отбора тела
modes.append(mrec(600, True, 400, False))     # 5 - Отбор хвостов
modes.append(mrec(2000, False, 1000, False))  # 6 - Пропарка колонны

mode = old_mode = 0 # Начинаем работать всегда с нулевого режима

"""Это начало общего отсчета времени (относительное время программы)
  и момент начала текущего режима работы"""
tst = rst = time.time()

"""Теперь откроем файл журнала. Здесь возможны две ситуации. В первом
  варианте мы продолжаем прерванный ранее процесс ректификации. В этом
  случае нам удобно продолжить вести уже существующий файл журнала и
  отсчет времени. Т.е. если файл журнала есть и он не слишком мал,
  то мы так и поступим. Если же файла нет или он мал - то открываем
  новый файл и вдем отсчет локального времени процесса с нуля."""
if os.path.exists("log") and os.path.getsize("log") > 127:
 log = open("log", "r") # Нужно прочитать последнюю строку
 log.seek(-127, 2) # Встанeм немного недоходя до конца файла
 for line in open("log", "r"): # и считаем последнюю строку
   last_line = line
 prevStopTime = float(last_line.split()[1])
 tst -= prevStopTime*60.0 # Локальное время будет продолжаться с конца
                          # предыдущей сессии
 log.close()
 log = open("log", "a") # А теперь откроем его уже для добавления
else: # В противном случае создаем новый файл журнала а нулевое
 log = open("log", "w") # локальное время уже установлено выше

"""Все. Все подготовительные операции закончены. Входим в главный
  цикл приложения"""
while True:

 """Сначала посмотрим на состояние клавиатуры"""
 if tt.kbhit(): # Какая-то клавиша уже была нажата
   ch = tt.getch() # Смотрим что было нажато....
   if ch == 'q': break # Выходим из цикла - конец работы
   elif ord(ch) >= 48 and ord(ch) <= 57: # Ввели номер режима
     old_mode = mode; mode = int(ch)

 """Теперь довольно громоздкий блок в котором мы измеряем все доступные
    текущие параметры процесса и формируем две строки. Одна (ss) краткая
    и раскрашенная для вывода информации на консоль. Вторая (s)
    "ненакрашенная" и (обычно) более подробная - для записи в журнал"""

 """Сначала общая длительность процесса на данный момент и длительность
    текущего режима"""
 now = time.time(); dtm = (now - tst)/60.0; dtrm = (now - rst)/60.0
 ss = "% 7.2f % 7.2f\x1b[33m %d\x1b[0m" % (dtm, dtrm, mode)
 """Для журнала добавляем еще и глобальное время"""
 s = time.strftime("%y.%m.%d.%H.%M.%S")
 s += "% 7.2f % 7.2f %d" % (dtm, dtrm, mode)

 """Добавим для выдачи текущие значения температур с датчиков"""
 Ts = sens.DS18B20.Ts() # Сначала их измерим
 for id in tids: # Теперь добавим в строки для выдачи
   if id == cid: # Подкрасим температуру в колонне для консоли
     ss += "\x1b[32m% 7.2f\x1b[0m" % (Ts[id])
   else: ss += "% 7.2f" % (Ts[id])
   s +=  "% 7.3f" % (Ts[id])

 """Измеряем и выводим текущую мощность нагрева"""
 cW = teh.W
 ss += "\x1b[31m %5d\x1b[0m" % (cW); s +=  " %5d" % (cW)

 """Если есть датчик RMS, то в журнал выведем напряжение в сети"""
 if vs != None:
   V = vs.V; ss +=  " %4d" % (V); s +=  " %4d" % (V)

 """Если есть датчик атмосферного давления - выводим давление"""
 if ps != None:
   P = ps.P; ss += "% 6.1f" % (P); s += "% 5.1f" % (P)

#  """Выводим информацию о количестве циклов."""
#  ss += "% 4d" % (step_number)

 """Выводим информацию об изменении температуры в колонне"""
 if len(Tcol_list) <= 4:
   Tcol_list.append(Ts[cid])
 else:
   del Tcol_list[0]
   Tcol_list.append(Ts[cid])
   #print (Tcol_list)
   dTcol = Tcol_list[4] - Tcol_list[0]
   ss += "% 6.2f" % (dTcol)
 """Выводим информацию об изменении температуры в дефлегматоре"""
 if len(Tdef_list) <= 4:
   Tdef_list.append(Ts[did])
 else:
   del Tdef_list[0]
   Tdef_list.append(Ts[did])
   #print (Tdef_list)
   dTdef = Tdef_list[4] - Tdef_list[0]
   ss += "% 6.2f" % (dTdef)


 """Далее собственно и следует вся ненавязчивая автоматизация :)))

    В данной задаче автоматическое переключение режимов нужно только
    для стадии разгона. Т.е. в режиме разгона (режим 1) после закипания,
    когда температура в 1/3 колонны превысит, скажем, 60°C, нужно
    перейти в режим 2 (сбросить мощность на "штатный" уровень).
    Ну так так и объясняем малинке."""
 if mode == 1: # Текущий режим - разгон
   if Ts[cid] >= 60: # Куб закипел - пора сбрасывать мощность  
     old__mode = mode; Deflegmator.N=150; mode = 2
 if mode == 2: # Текущий режим - стабилизация с выходом на отбор голов
   if Tdef_list[4] + 5 < Tcol_list[4]:  # Регулируем кран дефлегматора только до заданной температуры.
     if Column_stabilized == False:  # Если колонна не стабилизирована
       if Tcol_max == False and dTcol < -1: Tcol_max = True; print 'Максимум пройден'
       if Tcol_max == True and dTcol >= -0.1: Column_stabilized = True
     if Column_stabilized == True: # Началась стабилизация
       if dTdef >= 0.8: Deflegmator.N = Deflegmator.N + 1  # Увеличиваем охлаждение.
       if dTdef <= 0.5: Deflegmator.N = Deflegmator.N - 1  # Уменьшаем охлаждение.
       if Deflegmator.N < 73: Deflegmator.N = 73
   ss += "% 4d" % (Deflegmator.N)
   ss += "% 4d" % (Holodilnik.N)

 """Теперь обработаем ситуацию, когда произошла смена режима работы
    установки (автоматически, или по нажатию клавиши). Для обработки
    используем карту режимов"""
 if old_mode != mode:
   """Для режимов без стабилизации мощности (разгон, пропарка колонны и т.п.)
      мощность устанавливается только один раз при смене режима"""
   if not modes[mode].stab and mode != 0: teh.W = modes[mode].W              
#   sd.Q = modes[mode].Q # А вот для скорости отбора мы так делаем всегда
   old_mode = mode
   rst = now # Фиксируем момент начала нового режима
 """Для режимов со стабилизацией мощности (это написано в карте режимов),
    мощность обновляем на каждом такте главного цикла приложения
    (естественно, с учетом напряжения сети, если подключен датчик RMS)"""
 if modes[mode].stab:
   teh.W = modes[mode].W
#  """Включаем счетчик циклов. И если step_number>steps_in_circle
#     сбрасываем счетчик"""
#  step_number=step_number+1
#  if step_number>steps_in_circle:
#    step_number=1

 log.write(s + "\n"); log.flush() # Пишем данные в журнал
 print ss # и выводим на экран
P.S Сегодня запускал программу с iPad и работа прошла без сбоев. На компьютере с Ubuntu у меня USB WIFI адаптер linksys wusb600n v2. И работает под линуксом он как-то неустойчиво, постоянно теряет соединение. Заказал новый адаптер, а пока еще понаблюдаю.
P.S.S. Что-то в скрытый текст, редактор автоматически мордашки понаставил. Я там ничего не менял.
Screenshot from 2017-12-02 13-19-58.png
Screenshot from 2017-12-02 13-19-58.png Ненавязчивая автоматизация ректификационной установки. Автоматика.
OldBean Доцент Красноярск 1K 1.4K
Отв.849  02 Дек. 17, 18:51
Хотелось бы автоматическое поддержание подачи насоса по мощности тэнов и по давлению в кубе. датчик давления на ардуинке, а если бы на 3-х значном сегменте было бы вообще супер.ram78, 02 Дек. 17, 14:16
Модулек с I2C интерфейсом для датчиков давления (и других аналоговых датчиков, в том числе аналоговых температурных) предусмотрен в варианте LITE. Правда, без индикации и на базе ATMega328. Но, в принципе, потом его будет несложно адаптировать к ардуинке (Pro Mini) с индикатором. Насос же сильно выходит за рамки стоящих задач. Поэтому этой опции у меня в планах пока нет.
Я имел ввиду следующее - блок схема, flow charts и прочее - ...PavelSaratov, 02 Дек. 17, 14:47
Я хорошо понимаю что Вы имеете в виду. Но здесь речь идет не об алгоритмах управления ректификацией. В этом случае диаграммы могут быть полезны. Да и то, для задачи кубовой ректификации - это, скорее, стрельба из пушки по воробьям (всего пара-тройка-другая измеряемых параметров и две степени свободы). В данном же случае речь идет об описании объектной модели установки (в смысле ООП) и о навыках практического использования этих классов/объектов для своих задач. Из этих объектов (как из кирпичиков) каждый сможет собрать свою систему управления (в виде скрипта), в которой и реализовать свои алгоритмы управления (они у каждого могут отличаться). Система автодокументирования в питоне очень даже неплохо развита. Поэтому (еще и помня, что питон - интерпретируемый язык) предпочитаю (и не только я) использовать непосредственно исходники питоновского кода для документирования объектной модели, а не диаграммы (в том числе и UML).
P.S.S. Что-то в скрытый текст, редактор автоматически мордашки понаставил. Я там ничего не менял.ZagAl, 02 Дек. 17, 14:58
Включайте флажок "Не использовать смайлы" сразу под окном расширенного редактирования топика. Должно помочь от мордашек. По скрипту. Сегодня уже не успею внимательно его посмотреть. Завтра посмотрю. Если что-нибудь разумное придет в голову - напишу.


Добавлено через 1дн. 19ч. 6мин.:

ZagAl, а что же Вы свой модуль contrSTPR.py не приложили?
ZagAl Доцент Прибалтика 1.9K 916
Отв.850  04 Дек. 17, 15:27
ZagAl, а что же Вы свой модуль contrSTPR.py не приложили?OldBean, 02 Дек. 17, 18:51
Ну как же не выложил. Вот же: Контроллер шагового двигателя.
Там все: и плата ( stepper3.lay6), и скетч для ардуинки ( stepper_17.09.15.ino) и contrSTPR.rar
Вот еще в "Скрытом тексте".
Скрытый текст#coding:utf-8
"""Модуль contrSTPR содержит классы-обертки для контроллера
шагового двигателя, подключенного к шине I2C микрокомпьютера Raspberry Pi"""
import os
import smbus
#import sens
import time
#-------------------------------------------------------------------------------
class STPR(object): # Класс шагового двигателя
  """Класс-обертка для контроллера шагового двигателя"""
  def __new__(cls, addrSTPR = 0x07, bus = smbus.SMBus(1)):
    """Перед созданием объекта в методе __new__ производится
      тестирование контроллера (наличие и работоспособность).
      addrSTPR - адрес контроллера на шине I2C
      bus - объект SMBus - шина I2C."""
    try: # Проверим наличие и работоспособность контроллера
      bus.read_byte(addrSTPR)
      # Все в порядке - создаем объект - контроллер шагового двигателя.
      return super(STPR, cls).__new__(cls)
    except:
      print "Контроллер шагового двигателя отсутствует или неисправен"
      return None

  def __init__(self, addrSTPR = 0x07, bus = smbus.SMBus(1)):
    """addrSTPR - адрес контроллера на шине I2C
      bus - объект SMBus - шина I2C."""
    self._addr = addrSTPR
    self._bus = bus

  @property
  def N(self):
    """Свойство - считываем значение текущего положения шагового двигателя. N - количество шагов."""
    return self._bus.read_byte(self._addr)

  @N.setter
  def N(self, val): # val - значение положения шагового двигателя.
    """Свойство - задаваемое значение положения шагового двигателя в шагах.
      Необходимое положения шагового двигателя будет устанавливаться на контроллере
      каждый раз, когда этот атрибут используется
      в левой части выражения перед знаком присваивания."""
    if val < 0: npc = 0
    else: npc=int(val)
    if npc > 200: npc = 200 # Ограничение по количеству шагов за один оборот двигателя при полношаговом режиме.
                            # А точнее сохраняем угол перемещения 1.8 градуса, независимо от деления шага.
    # Попытаемся записать байт в ведомое устройство по шине I2C
    tries = 10; error = None
    while tries: # Если будет сбой при записи - сделаем tries попыток
      try: self._bus.write_byte(self._addr, npc)
      except IOError as e:
        error = e; tries -= 1
        print "tries = " + str(tries) + "; " + str(error)
      else: break # Нормально отправили байт - выходим из цикла
    if not tries: raise error # Сбой устойчивый - бросаем исключение и
                              # завязываем с этим делом

#-------------------------------------------------------------------------------
if __name__ == "__main__": # Тестирование классов модуля conrtSTPR.
  print("\nКонтроллер шагового двигателя.")
  time.sleep(3)  # Пауза
  stpr = STPR() # Создаем объект-контроллер шагового двигателя.
  if stpr != None: # Если все тип-топ то
    print "Текущее положение двигателя -%3d" % (stpr.N)
    time.sleep(3)  # Пауза
    print "Через 5 секунд будет перемещение на 50 шагов в сторону открытия вентиля."
    old_p=stpr.N  # Запоминаем старое положение.
    new_p=old_p+50  # Вычисляем новое положение
    time.sleep(3)  # Пауза
    stpr.N = new_p # Установим шаговый двигатель в новое положение.
    print "Двигатель перемещается в новое положение -%3d" % (stpr.N)
    time.sleep(5)
    print "Через 5 секунд вернется в прежнее положение."
    time.sleep(5) # Пауза   
    stpr.N = old_p # Установим шаговый двигатель в прежнее положение.
    print "Двигатель перемещается в прежнее положение -%3d" % (stpr.N)
#---------------------------------------------------------------------------------------


OldBean Доцент Красноярск 1K 1.4K
Отв.851  04 Дек. 17, 18:41
Sorry, что-то не сообразил заглянуть туда за модулем ;)

Просмотрел тексты. Увы, пока ничего подозрительного не нашел. Что могло бы грубо обнулить температуры и далеко "загнать" индекс датчика. Единственное что немного смущает - некоторое расхождение текста скрипта в котором реально произошел сбой и текста, который Вы выложили. Дело в том, что сбой (из-за выхода индекса за границы массива) произошел, по сообщению интерпретатора, в строке 169, а в выложенном тексте соответствующий оператор оператор находится в строке 171. Если это было какое-нибудь непринципиальное редактирование (типа убирание/добавление пустых строк), то это - ничего. Или что-то по сути изменяли?
ZagAl Доцент Прибалтика 1.9K 916
Отв.852  04 Дек. 17, 20:28
Ах, ну да, то был предыдущий вариант. Но я ничего кординально не поменял, кроме того, что счетчик циклов заменил на списки. Вот он:
Скрытый текст#coding:utf-8
import sys, os, time
import collections
"""Архив с модулями  sens, contr и kbh находятся в приложении к
  данному топику"""
import sens, contr, kbh, contrSTPR
step_number=1
steps_in_circle=5
Tcol_max = False
column_stabilized = False

"""Сначала создаем объекты, соответствующие датчикам и контроллерам.
  Параллельно производится проверка их наличия и работоспособности.

  Начнем с датчиков температуры (DS18B20). Идентификаторы датчиков,
  установленных в системе известны. Определяем соответствующие
  переменные"""
hid = 0x8000001ee27b # ID датчика температуры куба
cid = 0x8000001ef6e6 # ID датчика температуры колонны
did = 0x000006273720 # ID датчика температуры дефлегматора
wid = 0x8000000419af # ID датчика температуры выходящей воды
"""Создаем вспомогательный список tids, для удобства коллективной
  работы со всеми датчиками"""
tids = (hid, cid, did, wid)
"""Статический метод Ts() класса DS18B20 возвращает словарь, содержащий
  пары {идентификатор датчика : его температура} для всех датчиков,
  подключенных к шине 1-Wire"""
Ts = sens.DS18B20.Ts()
"""Проверим, все ли датчики, с идентификаторами, перечисленными выше,
  есть в этом словаре (т.е. висят на шине и исправны)"""
flag = False
for id in tids: # Пробегаем по списку нужных нам датчиков
  if Ts.get(id) != None: # Соответствующий ключ (id) есть есть в словаре
    print "0x%012x :% 7.3f°C" % (id, Ts[id]) # Доложим об этом...
  else: # Увы...
    print "Датчик с ID = 0x%012x отсутствует или неисправен" % (id)
    flag = True # Выставляем флаг, что нужного датчика нет на шине
if flag:
  print "Один или более датчиков DS18B20 отсутствуют или неисправны. Завершение работы."
  sys.exit(1)

"""С датчиками температуры разобрались. Теперь займемся датчиком
  RMS (действующее напряжение сети)"""
vs = sens.RMS() # Создаем объект - датчик RMS
if vs != None: # Датчик в наличии и функционирует
  V = vs.V
  if V > 100: print "Действующее напряжение сети %d В" % (V)
  else:
    print "Слишком низкое напряжение сети. Завершение работы."
    sys.exit(2)
else: pass # Ну на нет и суда нет. Можно работать и без датчика RMS
"""Датчик атмосферного давления - полезная вещь. Проверим его наличие
  и работоспособность. Если его нет - не беда. Будем работать без него"""
ps = sens.BMP180() # Создаем объект - датчик атмосферного давления
if ps != None: # Датчик в наличии и функционирует
  print "Атмосферное давление: \t%5.1f мм.рт.ст." % (ps.P)
  print "Температура окружающей среды:\t%5.2f°C" % (ps.T)

"""Создаем объекты-контроллеры исполнительных устройств"""
teh = contr.TEH(vs) # Создадим объект "ТЭН"
if teh == None:
  print "Контроллер ТЭНа отсутствует или неисправен. Завершение работы."
  sys.exit(3)
sd = contr.SD() # Создадим объект "устройство отбора"
"""В принципе можно работать и без клапана отбора. Например, с ручной
  "пережимкой". Если ситуация именно такова, то закомментируйте следующие
  три строчки"""
#if sd == None:
#  print "Контроллер клапана отбора отсутствует или неисправен. Завершение работы."
#  sys.exit(3)
Deflegmator=contrSTPR.STPR(addrSTPR=0x07)
if Deflegmator == None:
  print "Контроллер Дефлегматора отсутствует или неисправен. Завершение работы."
  sys.exit(4)
Holodilnik=contrSTPR.STPR(addrSTPR=0x08)
if Holodilnik == None:
  print "Контроллер Холодильника отсутствует или неисправен. Завершение работы."
  sys.exit(5)

"""Все. С датчиками и исполнительными устройствами мы разобрались.
  Теперь переведем консоль в неблокирующий режим работы. Это нужно
  для того, чтобы можно было бы в главном цикле приложения обрабатывать
  нажатие клавиш клавиатуры. Необходимый для этого сервис организован
  в виде класса TT, который находится в модуле kbh. При создании объекта
  (экземпляра этого класса) как раз и происходит переход консоли
  в неблокирующий режим. При уничтожении объекта (в деструкторе)
  консоль возвращается к обычному режиму работы.

  Параметр timeout задает время ожидания нажатия клавиши (он может быть
  равен 0). В данном случае мы используем этот параметр для регулирования
  длительности одного такта главного цикла приложения. При timeout = 3000
  длительность такта составляет около 4 сек"""
timeout = 3000 # В мс
tt = kbh.TT(timeout)

"""Теперь сформируем карту режимов, доступных в данном приложении. Режимы
  пронумерованы от 0 до 6. Карта режимов представляет собой список
  именованных кортежей (т.е. к элементам кортежа можно обращаться
  по имени). Это очень удобно. Имена элементов кортежа:
  W    - мощность нагрева ТЭНа, Вт (вещественное число)
  stab  - стабилизация мощности нагрева ТЭНа (True/False)
  Q    - скорость отбора, мл/час (вещественное число)
  ststp - режим старт-стопа (True/Flase)"""
#-------------------------------------------------------------------------------
mrec = collections.namedtuple('mrec', 'W stab Q ststp')
modes = [] # Собственно список режимов
modes.append(mrec(0, False, 0, False))        # 0 - Только мониторинг
modes.append(mrec(2000, False, 0, False))    # 1 - Разгон
modes.append(mrec(650, True, 0, False))      # 2 - Стабилизация с выходом на отбор голов
modes.append(mrec(600, True, 40, False))      # 3 - Отбора голов
modes.append(mrec(600, True, 400, True))      # 4 - Отбора тела
modes.append(mrec(600, True, 400, False))    # 5 - Отбор хвостов
modes.append(mrec(2000, False, 1000, False))  # 6 - Пропарка колонны

mode = old_mode = 0 # Начинаем работать всегда с нулевого режима

"""Это начало общего отсчета времени (относительное время программы)
  и момент начала текущего режима работы"""
tst = rst = time.time()

"""Теперь откроем файл журнала. Здесь возможны две ситуации. В первом
  варианте мы продолжаем прерванный ранее процесс ректификации. В этом
  случае нам удобно продолжить вести уже существующий файл журнала и
  отсчет времени. Т.е. если файл журнала есть и он не слишком мал,
  то мы так и поступим. Если же файла нет или он мал - то открываем
  новый файл и вдем отсчет локального времени процесса с нуля."""
if os.path.exists("log") and os.path.getsize("log") > 127:
  log = open("log", "r") # Нужно прочитать последнюю строку
  log.seek(-127, 2) # Встанeм немного недоходя до конца файла
  for line in open("log", "r"): # и считаем последнюю строку
    last_line = line
  prevStopTime = float(last_line.split()[1])
  tst -= prevStopTime*60.0 # Локальное время будет продолжаться с конца
                          # предыдущей сессии
  log.close()
  log = open("log", "a") # А теперь откроем его уже для добавления
else: # В противном случае создаем новый файл журнала а нулевое
  log = open("log", "w") # локальное время уже установлено выше

"""Все. Все подготовительные операции закончены. Входим в главный
  цикл приложения"""
while True:

  """Сначала посмотрим на состояние клавиатуры"""
  if tt.kbhit(): # Какая-то клавиша уже была нажата
    ch = tt.getch() # Смотрим что было нажато....
    if ch == 'q': break # Выходим из цикла - конец работы
    elif ord(ch) >= 48 and ord(ch) <= 57: # Ввели номер режима
      old_mode = mode; mode = int(ch)

  """Теперь довольно громоздкий блок в котором мы измеряем все доступные
    текущие параметры процесса и формируем две строки. Одна (ss) краткая
    и раскрашенная для вывода информации на консоль. Вторая (s)
    "ненакрашенная" и (обычно) более подробная - для записи в журнал"""

  """Сначала общая длительность процесса на данный момент и длительность
    текущего режима"""
  now = time.time(); dtm = (now - tst)/60.0; dtrm = (now - rst)/60.0
  ss = "% 7.2f % 7.2f\x1b[33m %d\x1b[0m" % (dtm, dtrm, mode)
  """Для журнала добавляем еще и глобальное время"""
  s = time.strftime("%y.%m.%d.%H.%M.%S")
  s += "% 7.2f % 7.2f %d" % (dtm, dtrm, mode)

  """Добавим для выдачи текущие значения температур с датчиков"""
  Ts = sens.DS18B20.Ts() # Сначала их измерим
  for id in tids: # Теперь добавим в строки для выдачи
    if id == cid: # Подкрасим температуру в колонне для консоли
      ss += "\x1b[32m% 7.2f\x1b[0m" % (Ts[id])
    else: ss += "% 7.2f" % (Ts[id])
    s +=  "% 7.3f" % (Ts[id])

  """Измеряем и выводим текущую мощность нагрева"""
  cW = teh.W
  ss += "\x1b[31m %5d\x1b[0m" % (cW); s +=  " %5d" % (cW)

  """Если есть датчик RMS, то в журнал выведем напряжение в сети"""
  if vs != None:
    V = vs.V; ss +=  " %4d" % (V); s +=  " %4d" % (V)

  """Если есть датчик атмосферного давления - выводим давление"""
  if ps != None:
    P = ps.P; ss += "% 6.1f" % (P); s += "% 5.1f" % (P)

#  """Если есть клапан отбора - выводим скорость отбора и общий расход"""
#  if sd != None:
#    Q = sd.Q; ss += " %4d" % (Q); s += " %4d" % (Q)
#    F = sd.F; ss += " %5d" % (F); s += " %5d" % (F)

  """Выводим информацию о количестве циклов."""
  ss += "% 4d" % (step_number)

  """Выводим информацию о разности температур в колонне и дефлегматоре"""
  if step_number == 1:
    T1col = Ts[cid]
    T1def = Ts[did]
  if step_number == steps_in_circle:
    T2col = Ts[cid]
    T2def = Ts[did]
    dTcol = T2col - T1col
    dTdef = T2def - T1def
    ss += "% 6.2f" % (dTcol)
    ss += "% 6.2f" % (dTdef)



#  log.write(s + "\n"); log.flush() # Пишем данные в журнал
#  print ss # и выводим на экран

  """Далее собственно и следует вся ненавязчивая автоматизация :)))

    В данной задаче автоматическое переключение режимов нужно только
    для стадии разгона. Т.е. в режиме разгона (режим 1) после закипания,
    когда температура в 1/3 колонны превысит, скажем, 60°C, нужно
    перейти в режим 2 (сбросить мощность на "штатный" уровень).
    Ну так так и объясняем малинке."""
  if mode == 1: # Текущий режим - разгон
    if Ts[cid] >= 60: # Куб закипел - пора сбрасывать мощность 
      old__mode = mode; Deflegmator.N=140; mode = 2
  if mode == 2: # Текущий режим - стабилизация с выходом на отбор голов
    if step_number == steps_in_circle:  # Проверяем на последнем шаге цикла.
      if column_stabilized == False: # Если колонна не стабилизирована
        if (T2col - T1col) < -1: Tcol_max = True
        if Tcol_max == True and dTcol >= -0.2: column_stabilized = True # Началась стабилизация.
      if column_stabilized == True:
        if T2def + 4 < T2col: # Регулируем кран дефлегматора пока до начала капельного отбора
          # не останется около 3х градусов.
          if dTdef >= 0.9: Deflegmator.N=Deflegmator.N+2 # Увеличиваем охлаждение.
          if dTdef <= 0.5: Deflegmator.N = Deflegmator.N - 1  # Уменьшаем охлаждение.
          if Deflegmator.N < 73: Deflegmator.N = 73
      ss += "% 4d" % (Deflegmator.N)
      ss += "% 4d" % (Holodilnik.N)

  """Теперь обработаем ситуацию, когда произошла смена режима работы
    установки (автоматически, или по нажатию клавиши). Для обработки
    используем карту режимов"""
  if old_mode != mode:
    """Для режимов без стабилизации мощности (разгон, пропарка колонны и т.п.)
      мощность устанавливается только один раз при смене режима"""
    if not modes[mode].stab and mode != 0: teh.W = modes[mode].W             
#  sd.Q = modes[mode].Q # А вот для скорости отбора мы так делаем всегда
    old_mode = mode
    rst = now # Фиксируем момент начала нового режима
  """Для режимов со стабилизацией мощности (это написано в карте режимов),
    мощность обновляем на каждом такте главного цикла приложения
    (естественно, с учетом напряжения сети, если подключен датчик RMS)"""
  if modes[mode].stab:
    teh.W = modes[mode].W
  """Включаем счетчик циклов. И если step_number>steps_in_circle
    сбрасываем счетчик"""
  step_number=step_number+1
  if step_number>steps_in_circle:
    step_number=1

  log.write(s + "\n"); log.flush() # Пишем данные в журнал
  print ss # и выводим на экран
gindos Студент Южно-Сахалинск 39 12
Отв.853  05 Дек. 17, 00:28
кроме того, что счетчик циклов заменил на спискиZagAl, 04 Дек. 17, 20:28
в этом, скорее всего и ошибка, не факт, что в строке 165
Ts = sens.DS18B20.Ts() # Сначала их измерим

возвращается массив такой же размерностью, как и список tids, который используется в этом цикле. Для проверки, на период отладки, предлагаю выводить на экран, перед
if id == cid: # Подкрасим температуру в колонне для консоли

размерность массива Ts. И из этого уже делать выводы.
OldBean Доцент Красноярск 1K 1.4K
Отв.854  05 Дек. 17, 03:04
размерность массива Ts. И из этого уже делать выводы.gindos, 05 Дек. 17, 00:28
Предложение о проверке вполне разумное.

Только, строго говоря, Ts - это не массив, а словарь.
Sorry, сам вчера забыл об этом (теперь понятно откуда такие дикие значения "как бы индекса"). Просто сейчас много всего из разных проектов в голове болтается... :).
В словаре возвращаются показания всех датчиков DS18B20, висящих на шине. В формате [ключ (это ID датчика) : температура датчика]. Возможно просто "отвалился" датчик температуры. В этом случае элемента словаря с ключом, равным ID отвалившегося датчика, в словаре Ts просто не будет и интерпретатор, естественно, ругнется и бросит исключение. Поэтому вставить проверку наличия ключа - весьма полезно. Ну типа
if id in Ts: делаем что-то

OldBean Доцент Красноярск 1K 1.4K
Отв.855  05 Дек. 17, 05:54
Вариант LITE. "Первая примерка"

Появилось немножко времени повозиться с вариантом LITE - наконец-то собрал "до кучи" почти все железо. Еще не все дырки просверлены в радиаторе и не все винты закручены. Поэтому это - просто как бы "первая примерка". Сильно захотелось посмотреть как все это будет выглядеть в сборе. Ну и заодно показать уважаемым коллегам.

На первых двух снимках показан собственно сам крейт с несколькими модулями, малинкой, общим радиатором, дифавтоматом, контактором, индикаторами и розеткой для 5-вольтового блока питания. На третьем снимке - несколько унифицированных силовых модулей и платка тоже унифицированного цифрового модуля, к которому можно подключить до 8 датчиков DS18B20. Такие же модули можно использовать для других цифровых датчиков, концевиков, термоконтактов и пр. Только, возможно подтягивающие резисторы (и, естественно, firmware) нужно поставить соответствующие. Датчик RMS с детектором нуля и модуль ввода аналоговых сигналов еще не готовы. Пока живут на макетках. А унифицированный силовой модуль по сути представляет собой электронный ключ/твердотельное реле с I2C интерфейсом. Меняются только параметры снабберной цепочки и предохранителя. В зависимости от нагрузки (ТЭН, клапан устройства отбора, клапан воды охлаждения и т.п.)

Малинка превращена в почти "взрослую" рабочую станцию под LInux ;) К ней подключен обычный монитор с HDMI-интерфейсом и самые, что ни на есть, обычные USB клавиатура и мышка. На этой "повзрослевшей" малинке мы и будем "перетряхивать" софт для не LITE и писать новый софт для LITE. Ну и заодно осваивать элементы программирования. Как на Си (для ардуинок и АВРок), так и на Python для самой малинки, как управляющего микрокомпьютера. И, естественно, через эту же малинку мы будем прошивать ардуинки и МК.

Никогда не работал с малинкой в таком качестве, но первые пробы понравились. Совсем не тормозит. Даже вполне работоспособный Libre Office есть! Есть и игрушки (в том числе и на питоне). Поиграл в старый-добрый тетрис. Тронул до слез...
03_LITE_modules.JPG
03_LITE_modules.JPG Ненавязчивая автоматизация ректификационной установки. Автоматика.
01_LITE_crate.JPG
01_LITE_crate.JPG Ненавязчивая автоматизация ректификационной установки. Автоматика.
02_LITE_crate.JPG
02_LITE_crate.JPG Ненавязчивая автоматизация ректификационной установки. Автоматика.
BogAD Кандидат наук Красногорск - Белово 403 184
Отв.856  05 Дек. 17, 09:17
Вау! 
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

Слов нет...
OldBean, будь ласка, сфоткай базу без модулей, сила очень интересует. .
Классное решение, использовать шинки PE (N). А я голову грел, а тут "Все элементарно, Ватсон..." 
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

Да, наборчик *.lay бы, если не жалко.
mak Модератор Екатеринбург 6.3K 1.8K
Отв.857  05 Дек. 17, 10:44
OldBean, мне кажется более практично было бы слоты использовать
типа таких
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

Основной слот и 6-ти контактные для силовой части
штыревые разъемы существенно менее надежны для таких задач
gol_avto Доцент Москва - Серпухов - Анапа 1.3K 458
Отв.858  05 Дек. 17, 10:46, через 3 мин
В планах: 1) Повысить надежность обмена малинки с контроллерами по I2C.OldBean, 02 Дек. 17, 09:37
Ради любопытства решил посмотреть сигналы на шине I2C. Честно говоря не понял, почему такты идут неравномерно, разве так должно быть?
желтый цвет - сигнал SDA
зеленый цвет - сигнал SLC
Разумеется обмен пошел после запуска nna_36.py
====================================
И если перепутать их местами, то обмен (работоспособность шины) сохраняется, но иногда возникают ошибки, что у меня и имело место быть с датчиком RMS.
1.jpg
1.jpg Ненавязчивая автоматизация ректификационной установки. Автоматика.
2.jpg
2.jpg Ненавязчивая автоматизация ректификационной установки. Автоматика.
3.jpg
3.jpg Ненавязчивая автоматизация ректификационной установки. Автоматика.
OldBean Доцент Красноярск 1K 1.4K
Отв.859  05 Дек. 17, 13:23
сфоткай базу без модулейBogAD, 05 Дек. 17, 09:17
Сфоткал. Я позже собирался рассказывать некоторые подробности. По мере изготовления варианта LITE. Ну а пока вот фотография и блок-схема в приложении к топику. Там в принципе все просто и понятно без описания.
мне кажется более практично было бы слоты использоватьmak, 05 Дек. 17, 10:44
Вариантов много. Я сначала примерно такие и хотел ставить. А потом как-то взгляд упал на старый КАМАКовский крейт, вру - все-таки - на ВУП (они рядом) - вот и решил по-старинке, с винтами и штырями сделать ;). Посмотрим как крейт поведет себя в реальной работе.
Ради любопытства решил посмотреть сигналы на шине I2C. Честно говоря не понял, почему такты идут неравномерно, разве так должно быть?gol_avto, 05 Дек. 17, 10:46
В этом тощем импульсе - малинкины проблемы с I2C и наш головняк. Вот здесь мы с этим разбирались. Посмотрите мои тесты для сравнения.
04_LITE_crate_empty.JPG
04_LITE_crate_empty.JPG Ненавязчивая автоматизация ректификационной установки. Автоматика.
05_bs_17.08.24.8.png
05_bs_17.08.24.8.png Ненавязчивая автоматизация ректификационной установки. Автоматика.