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

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

Форум самогонщиков Автоматика
1 ... 98 99 100 101 102 103 104 ... 132 101
ruslan_ka Студент Железнодорожный 29 6
Отв.2000  22 Сент. 20, 15:47
Что-то я волнуюсь, автор уже пол года ничего тут не пишет...
OldBean Доцент Красноярск 1K 1.4K
Отв.2001  23 Сент. 20, 06:21
Да просто никаких существенных новинок не было. Вопросов тоже.

Сейчас вожусь с telegram ботом для управления установкой. Дело для меня новое, но оказалось очень интересным. Довольно компактно и прозрачно все получается. Все-таки python - это "наше все". Как и Александр Сергеевич :) Как "добью" бота и проведу парочку тестовых ректификаций с таким интерфейсом - расскажу в подробностях.
ruslan_ka Студент Железнодорожный 29 6
Отв.2002  23 Сент. 20, 09:44
Тогда у меня вопрос...
Линию RES мы вытащили в крейт, чтобы одной кнопкой перезагружать все модули, если хоть один из них подвис - это очень удобно.
На каждом модуле эта линия подтянута к +5 резистором на 10К.
Но если таких модулей будет 10 штук, то общее сопротивление между линиями должно составить 1К.
Ток 5 мА не критичен для пина №1 МК?

Может быть нужно в крейте притянуть линию RES к линии +3.3 резистором на 10К (аналогично линии INT)а на модулях убрать этот резистор?
+3.3 это больше 0 в пяти вольтовой логике и позволит подключить эту линию к малинке для того, чтобы нажимать эту кнопку из скрипта управления на Python?
Или на пине №1 МК должно быть обязательно +5?
Тогда, возможно, нужно увеличить сопротивление до 100К?
ekochnev Магистр Екатеринбург 206 54
Отв.2003  23 Сент. 20, 11:01
Я уже поднимал этот вопрос выше. Все согласились, что данная схемотехника не очень удачна, но пока (количество модулей не очень большое) решили оставить как есть.

Вот ссылка на предыдущее обсуждение:
[сообщение #13633108]
OldBean Доцент Красноярск 1K 1.4K
Отв.2004  23 Сент. 20, 12:12
Может быть нужно в крейте притянуть линию RES к линии +3.3 резистором на 10К (аналогично линии INT)а на модулях убрать этот резистор?
+3.3 это больше 0 в пяти вольтовой логике и позволит подключить эту линию к малинке для того, чтобы нажимать эту кнопку из скрипта управления на Python?
Или на пине №1 МК должно быть обязательно +5?
Тогда, возможно, нужно увеличить сопротивление до 100К?ruslan_ka, 23 Сент. 20, 09:44
В принципе можно. Но такие решения неизбежно будут снижать помехоустойчивость модулей.

С другой стороны, я не вижу каких-то проблем, связанных параллельным включением подтягивающих резисторов, и в существующем решении. Естественно, при каком-то разумном количестве модулей (порядка 10-20). Вот почему.
1. В обычном (рабочем) состоянии пин 1 (Reset) МК подтянут к +5 и ток через него практически не течет. Это вход.
2. При сбросе кнопкой, линия Res притягивается к земле и через кнопку потечет 5 мА. Но вряд ли обычная кнопочка будет испытывать какие-то затруднения от такого тока.
3. При прошивке, модуль все равно лучше вытаскивать из крейта. Поэтому для программатора это будет те же самые 10 кОм.
4. Единственный режим, при котором могут возникнуть проблема - это программный общий сброс модулей от пина GPIO малинки. Но этот пин малинки (3.3-вольтовая логика) все равно нужно согласовывать с 5-вольтовой логикой. Самый простой вариант согласования (в одном направлении от малинки к модулям) - поставить небольшой MOSFET. Но, даже для самого дохленького MOSFET-ика 5 мА вряд ли будет представлять проблемы.
ruslan_ka Студент Железнодорожный 29 6
Отв.2005  23 Сент. 20, 12:59, через 48 мин
Понятно, я тогда спаяю пока все как есть.
Если все заработает, то эксперименты с подтягивающими резисторами будем потом ставить...

Добавлено через 34дн. 2ч. 24мин.:

Небольшой отчет...
С 12 октября наш отдел перевели на "удаленку", и сразу дела по этому проекту (LITE) пошли в гору!
Собрал пока минимальную конфигурацию (RMS, HAB) с тремя силовыми модулями (вода, спирт, тэн) в боксе для автоматов на 12 элементов.
(Осталось в запасе место еще под два силовых модуля!)
Вчера вечером, 26 октября, отнес все это в гараж, подключил к своей колонне и получил первые 10 миллилитров голов (очень ароматных) Улыбающийся
Теперь с нетерпением жду выходных...

Добавлено через 20ч. 57мин.:

Вчера вечером отобрал 200 мл голов Улыбающийся
График.png
График. Ненавязчивая автоматизация ректификационной установки. Автоматика.


Добавлено через 5мин.:

Напряжение проседает на разгоне, да и в процессе отбора немного гуляет.
Коррекция мощности по напряжению в тестовом скрипте (test03.py) не реализован ведь?
Может формулу коррекции кто подскажет...
OldBean Доцент Красноярск 1K 1.4K
Отв.2006  01 Нояб. 20, 13:29
Может формулу коррекции кто подскажет...ruslan_ka, 23 Сент. 20, 12:59
Формула очень проста k = 100*(W/W0)*(220/U)**2, где k - искомый коэффициент модуляции в % (это то число, что мы посылаем контроллеру), W - требуемая мощность нагрева, W0 - номинальная мощность ТЭНа (это при 220В), U - текущее (измеренное) напряжение сети.

Пример. Пусть номинальная мощность ТЭНа 1500 Вт, нам нужна мощность нагрева 600 Вт. Измерили напряжение U = 210 В. Вычисляем коэффициент модуляции: k = 100*(600/1500)*(220/210)**2 = 43.9%. Т.е. отправляем в контроллер 44%. И так далее в цикле.
gindos Студент Южно-Сахалинск 39 12
Отв.2007  02 Нояб. 20, 10:32
OldBean, доброго дня.
Вопрос по самой первой редакции системы, которая с семисегментными индикаторами, а именно по питоновскому классу контроллера клапана отбора и его коэффициенту для пересчета % ШИМ в мл/час. Залил в куб СС крепостью около 35%, закипел его и на максимальной мощности ТЭН-а, открыв клапан на 100%, замерил скорость отбора и получил неслабый такой коээфициент - 22 с лихом единицы, указал его в коде класса. Заметил, в дальнейшем, что при мощности, например, в 37%, подаваемой на ТЭН, скорость отбора изменилась и соответствует, примерно, коэффициенту 13 единиц, если мощность поднять до 50%, то коэффициент этот подымается уже до 17 единиц.
Может подскажете, где ошибаюсь?
Либо мне нужно делать калибровочные замеры на разной мощности (со 100% открытым клапаном), но тогда код класса не предусматривает "на лету" менять этот коэффициент - ведь мне нужно будет, при смене мощности, подаваемой на ТЭН, менять и этот коэффициент в классе клапана отбора, т.к. усреднённый коэффициент не будет отражать истинной картины. Либо я напортачил с пропускной способностью самого механизма клапана (вставка из фольги, в которой сделано отверстие сместилась, когда собирал конструкцию и там не тоненькое отверстие, а щель непонятных размеров) и это влияет на скорость отбора. Либо, что-то ещё, что не замечаю.
BogAD Кандидат наук Красногорск - Белово 403 184
Отв.2008  02 Нояб. 20, 12:49
Интересный форм-фактор
https://3dnews.ru/...uri-po-tsene-70
ekochnev Магистр Екатеринбург 206 54
Отв.2009  02 Нояб. 20, 13:53
gindos, там от подводимой мощности вообще ничего зависеть не должно,ни в старом варианте системы, ни в новом. Я вообще калибровку для клапана делал отдельно от всего, т.е. просто соединил клапан трубочкой с мелкой емкостью, в емкость налил спирт, тупо подал напряжение из розетки на клапан и засек сколько спирта протекает через него за определенный промежуток времени. Все это на-холодную. В дальнейшем никаких коррекций не потребовалось ни при каком уровне ШИМ и мощности.

Изменение объема проходимой через клапан жидкости при одном и том же уровне ШИМ имхо возможно только в двух случаях:
1. Если, допустим, клапан у вас отградуирован с максимальной пропускной способностью, допустим, 5 литров в час (ШИМ 100%), но стоит слабый нагреватель, который не может обеспечить перенос жидкости из куба в дефлегматор в таком объеме (допустим, может испарить только 3 литра в час). Тогда конечно вы получите при 100% ШИМ меньший поток (вместо ожидаемых 5 литров только 3), который еще и будет меняться от мощности.
2. Если шток клапана иногда заедает в том или ином положении. Вот такое у меня было. Вылечилось заменой клапана.
gindos Студент Южно-Сахалинск 39 12
Отв.2010  02 Нояб. 20, 14:23, через 30 мин
ekochnev, спасибо за идеи, но пока не вижу связи со своим оборудованием.
Клапан, при 100% ШИМ, пропускает чуть более полутора литров спирта.
ТЭН мощностью 2 КВт, если бы он не справлялся с нагревом содержимого куба, тогда бы не было постоянного столбика спирта перед клапаном.
Заедания клапана не замечал - хотя вполне себе возможная ситуация, я ведь не могу контролировать срабатывает он или нет постоянно (но когда могу, то отчётливо вижу, как прерывается струйка спирта после клапана), щелчки, особенно, когда шток втягивается, слышны отчётливо - на освобождении штока уже не так громко.
Попробую провести калибровку на холодную.
OldBean Доцент Красноярск 1K 1.4K
Отв.2011  03 Нояб. 20, 09:59
gindos, Евгений Вам все правильно рассказал. В том числе и про калибровку. Добавлю только, что здесь речь идет о ректификации, а не о дистилляции. Поэтому разумный уровень флегмового числа обеспечивать необходимо. Поэтому и уровень жидкости в узле отбора должен быть более-менее стабильным, да и давление пара в колонне должно быть близко к атмосферному (ТСА открыта). Тогда все будет работать четко, надежно и стабильно.

Интересный форм-факторBogAD, 02 Нояб. 20, 12:49
Да, Александр... Все новое - хорошо забытое старое. Сразу вспомились далекие 80-е (спектрумы и моя любимая атари с внешним винтом на 20М). :)
ekochnev Магистр Екатеринбург 206 54
Отв.2012  03 Нояб. 20, 12:21
Тоже поглядываю на новую Малинку... С двумя гигами памяти брать не хочется, а с 4 дороговато пока. На самом деле, хотелось бы вообще с 8, но такие хоть и анонсированы, но их почему-то пока даже у китайцев в продаже не вижу...
По форм-фактору: да, в 80-е действительно такой очень популярен был. Я сам начинал возиться с компьютерами с РК-86
gindos Студент Южно-Сахалинск 39 12
Отв.2013  04 Нояб. 20, 13:11
Евгений Вам все правильно рассказал. В том числе и про калибровку. Добавлю только, что здесь речь идет о ректификации, а не о дистилляцииOldBean, 03 Нояб. 20, 09:59
Вы правы, у меня дистилляция.
Вчера нашёл время и занялся замерами, на мощности от 500 до 2000 Ватт с шагом 100 Ватт, перед тем как открывать клапан давал поработать колонне на себя 5 минут, потом забирал 50 мл в пробирку. Только на 500 Ватт скорость была 186 секунд, на всех остальных режимах получилось почти ноздря в ноздрю - от 164 до 162 секунд. На 50 мл это кажется небольшим разбегом, но на больших объёмах он будет более заметен, ну да "нам не ракеты запускать", надеюсь этой точности хватит.

Коэффициент получил равным 18,51851852. Почему получил при первой калибровке, которую проводил при мощности 2000 Ватт и с полностью открытым клапаном, коэффициент 22 с лихом единицы - не могу ни понять ни объяснить, спишу на "пресловутый человеческий фактор".

Специально выполнил замер при ШИМ 50% на мощности 800 и 1000 Ватт, получил время не ровно в два раза больше, а чуть меньше чем в два раза, а именно - 318 секунд.

На практике эти замеры ещё не проверял, надеюсь в предстоящее воскресенье представится возможность.
ruslan_ka Студент Железнодорожный 29 6
Отв.2014  05 Нояб. 20, 10:41
gindos,
Наверное, напишу банальность, про которую все знают, но для меня это было удивительно...

Замерил расход на полностью открытом клапане с ограничительной мембраной на штативе с воронкой (как описали чуть выше).
Откалибровал (q0.calibr=...), еще раз проверил (Q0.v и реальные показатели в мензурке) - все четко!
Поставил клапан на колонну - на головах (отбор 50 мл/час) - отлично.
Начал отбор тела (500 мл/час) - расход поплыл. И чем дальше, тем больше.
Когда расход вырос в 3 раза (!) от калибровочного, снял клапан (думаю, может с мембраной что-то не так...), проверил на штативе с воронкой - все так как и было раньше!
Ставлю на колонну, начинается все как положено, а потом расход начинает плавно (!) расти...

Думал, может мимо мембраны просачивается или у горячего спирта текучесть выше (но не в три же раза)...
А оказалось, что в трубке от клапана до банки (около 1,5 метра) около банки был изгиб, образовался "водяной клапан" как в сифоне под умывальником.
Столб жидкости в трубке плавно рос и своим весом создавал вакуум в трубке, под действием которого расход через клапан тоже плавно рос.

Решение простое, после клапана ставим тройник, один конец в банку, другой наверх - выше дефлегматора.
Если канал разгерметизирован то расход через клапан совершенно стабилен!
ekochnev Магистр Екатеринбург 206 54
Отв.2015  05 Нояб. 20, 10:50, через 10 мин
Вот видите как все просто оказалось! У меня такой ситуации быть не может, т.к. в клапан вставляю Шульмановский доохладитель ХД-2, а у него прямо на входе трубка связи с атмосферой сделана чтобы не подсасывало из клапана подобным образом.
ruslan_ka Студент Железнодорожный 29 6
Отв.2016  05 Нояб. 20, 11:11, через 21 мин
Формула очень проста k = 100*(W/W0)*(220/U)**2, где k - искомый коэффициент модуляции в % (это то число, что мы посылаем контроллеру), W - требуемая мощность нагрева, W0 - номинальная мощность ТЭНа (это при 220В), U - текущее (измеренное) напряжение сети.OldBean, 01 Нояб. 20, 13:29

В питоновском скрипте (вариант Lite) я задаю мощность в Ваттах (w0.v), поэтому формула у меня получилась такая:
w0.v = Wset + Wmax*(220**2 - U0.v**2)/(220**2) , где
Wset - мощность при равновесии в колонне
Wmax - номинальная мощность тэна при 220В в сети
U0.v - текущее напряжение в сети.

На моём примере: Wset = 1000 Вт, Wmax = 3000 Вт, U0.v скакнуло до 230В, тогда
w0.v = 1000 + 3000*(220**2 - 230**2)/(220**2) = 721 Вт
Так ведь верно?
На каждом шаге (где-то раз в 2 секунды) менять мощность тэна не слишком часто?
gindos Студент Южно-Сахалинск 39 12
Отв.2017  05 Нояб. 20, 14:52
Если канал разгерметизирован то расход через клапан совершенно стабилен!ruslan_ka, 05 Нояб. 20, 10:41

Сифона нет, слежу за этим всегда, если и в следующую перегонку расход поплывёт, то попробую поставить тройник.
OldBean Доцент Красноярск 1K 1.4K
Отв.2018  06 Нояб. 20, 04:14
то попробую поставить тройник.gindos, 05 Нояб. 20, 14:52
Обязательно поставьте. "Висячий" столб жидкости в сливной трубке (ниже клапана) сильно влияет на реальный расход. Можно просто воткнуть иглу от шприца в сливную трубку прямо под клапаном для доступа воздуха. А лучше всего, прямо под клапаном организовать что-то типа капельницы. Очень удобно для визуального контроля. Можно обычную медицинскую приспособить. А можно и специально сделать (я приводил пример в конце этого топика).
gindos Студент Южно-Сахалинск 39 12
Отв.2019  06 Нояб. 20, 12:41
А можно и специально сделатьOldBean, 06 Нояб. 20, 04:14
помню эту конструкцию - завидки берут, что есть у вашем институте стеклодув Сергей :-)
За совет спасибо - испробую и отпишусь по результату.

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

Поэтому введение контрольной суммы в протокол каждый раз откладывалосьOldBean, 16 Апр. 20, 07:16
Использую покупную автономную систему охлаждения, решил доработать её, связав с системой ненавязчивой ректификации, при чём так, чтобы обеспечивался плавный пуск и остановка двигателя насоса, чтобы не было гидроудара в системе охлаждения. Пока воял её, понял, что без контрольной суммы пакета передаваемых данных получится плохо, поэтому реализовал передачу данных с контрольной суммой. В дальнейшем этот механизм внедрил во все классы первой редакции системы (той, что с индикаторами). Почему в ней - потому что был у меня двухгодичный перерыв в моём хобби, а выбрасывать то, что уже сделал и делать Lite-версию не захотел, да и нравятся мне эти красненькие индикаторы.

Здесь опишу как работает реализованный алгоритм на примере класса по управлению клапаном отбора (т.к. он ближе читателя топика, чем класс по управлению автономной системой охлаждения), может кому и пригодится, в качестве академических знаний, т.к. версия на индикаторах уже, как понимаю, устарела.

Последовательность действий следующая:
1. Мастер (т.е. сценарий на python) передаёт пакет данных контроллеру (три последних байта в пакете - это контрольная сумма пакета)
2. После передачи делается короткая пауза (на 0.1 секунды - пока установил такую, в дальнейшем посмотрю, может уменьшу её), для того, чтобы контроллер успел принять пакет данных и распаковать его.
3. Контроллер, распаковав пакет, вычисляет его контрольную сумму и сравнивает с той, что была передана вместе с пакетом от мастера. Если есть ошибка, то в ответном пакете данных определённый байт устанавливает в состояние 1, если нет, то в состояние 0. Не стал реализовывать эту логику на битах, потому как максимального размера пакета, передаваемого по i2c-шине, пока достаточно (если правильно понял документацию, то это 16 байт).
4. Мастер запрашивает у контроллера его состояние, и в полученном пакете данных узнаёт была ли ошибка контрольной суммы пакета при получении пакета от него или нет (по тому самому байту, о котором написал в пункте 3)
5. Если ошибки не было, тогда считается, что команда переданная контроллеру была успешно завершена.
6. При запросе мастером состояния контроллера, так же рассчитывается контрольная сумма пакета и если она не совпадает с переданной от контроллера, то попытка получить данные повторяется.

Класс SD мной был доработан (по-мимо использования контрольной суммы) в следующих моментах:
1. Появилась возможность изменить период ШИМ (ранее он был задан константой в 10 секунд) передав соответствующую команду от мастера к контроллеру (может быть позже реализую возможность изменять период ШИМ при помощи кнопок на самом контроллере). Сделал это скорее из азарта, чем из практических соображений.
2. На стороне контроллера сам период ШИМ теперь задаётся в секундах (ранее это были миллисекунды)
3. На стороне контроллера, если размер ШИМ меньше 100%, то в левой ячейке индикатора отображается либо значок "клапан открыт" (подсвечивается сегмент G), либо "клапан закрыт" (подсвечивается сегмент F). Сделано это для удобства оценки состояния клапана.

Использую алгоритм вычисления контрольной суммы CRC-16 CCITT. Сам код для его вычисления на основе табличных данных взят на просторах сети (как для python, так и для arduino). И реализован в виде библиотек, которые необходимо импортировать в сценарий и программу для контроллера.

Начну с описания python-скрипта (всё написано для версии python 2, для использования в версии python 3 нужно будет исправить вызовы print - кажется этого будет достаточно).

0. Импорт библиотеки для расчёта контрольной суммы

// импорт библиотеки для расчёта контрольной суммы
import crc16lib


далее в конструкторе класса добавлено:

1. Описание "констант"
   # максимальный номер пакета, по его достижении переходим на пакет №1
        self.MAX_NUM_PACKET = 127

        # есть или нет ошибка контрольной суммы
        # в переданном пакете данных
        self.NOT_ERROR_CRC16_D_S = 0
        self.YES_ERROR_CRC16_D_S = 1
        # в полученном пакете данных
        self.NOT_ERROR_CRC16_D_R = 2
        self.YES_ERROR_CRC16_D_R = 3

        # есть или нет ошибки получения пакета данных от контроллера
        self.NOT_ERROR_RECEIVED_PACKET = 5
        self.YES_ERROR_RECEIVED_PACKET = 6

2. Описание структуры получаемого и передаваемого пакета данных
   # структура получаемого от контроллера пакета
        # размер получаемого пакета
        self.SIZE_DATA_RECEIVED = 10
        # количество байт для передачи контрольной суммы
        self.SIZE_D_R_CRC16 = 3
        # адрес контроллера
        self.I_ADDR_RECEIVED = 0
        # текущий ШИМ клапана
        self.I_D_R_PWM = 1
        # длительность ШИМ-сигнала в секундах
        self.I_D_R_T = 2
        # номер получаемого пакета
        self.I_D_R_NUM_PACKET = self.SIZE_DATA_RECEIVED - 7
        # была или нет ошибка контрольной суммы ранее отправленного пакета
        self.I_D_S_ERROR_CRC16 = self.SIZE_DATA_RECEIVED - 6
        # или ошибка полученного пакета данных
        self.I_D_R_ERROR_CRC16 = self.SIZE_DATA_RECEIVED - 5
        # ошибка получения пакета
        self.I_ERROR_RECEIVED_PACKET = self.SIZE_DATA_RECEIVED - 4
        # контрольная сумма пакета, первые (младшие) 7 бит
        self.I_D_R_CRC1 = self.SIZE_DATA_RECEIVED - 3
        # контрольна сумма пакета, вторые 7 бит
        self.I_D_R_CRC2 = self.SIZE_DATA_RECEIVED - 2
        # контрольная сумма пакета, третие (старшие) 7 бит
        self.I_D_R_CRC3 = self.SIZE_DATA_RECEIVED - 1

        # структура отправляемого контроллеру пакета
        # размер отправлемого пакета
        self.SIZE_DATA_SEND = 7
        # количество байт для передачи контрольной суммы
        self.SIZE_D_S_CRC16 = 3
        # адрес контроллера
        self.I_ADDR_SEND = 0
        # какой ШИМ клапана хотим установить
        self.I_D_S_PWM = 1
        # длительность ШИМ-сигнала в секундах
        self.I_D_S_T = 2
        # номер отправляемого пакета
        self.I_D_S_NUM_PACKET = self.SIZE_DATA_SEND - 4
        # контрольная сумма пакета, первые (младшие) 7 бит
        self.I_D_S_CRC1 = self.SIZE_DATA_SEND - 3
        # контрольна сумма пакета, вторые 7 бит
        self.I_D_S_CRC2 = self.SIZE_DATA_SEND - 2
        # контрольная сумма пакета, третие (старшие) 7 бит
        self.I_D_S_CRC3 = self.SIZE_DATA_SEND - 1


3. После чего инициализируем переменные для формирования пакетов данных
   # инициализируем пакет полученных от контроллера данных
        self.data_received = []
        for i in range(self.SIZE_DATA_RECEIVED):
            self.data_received.append(0)

        # инициализируем пакет данных отправляемых контроллеру
        self.data_send = []
        for i in range(self.SIZE_DATA_SEND):
            self.data_send.append(0)

        # текущий номер пакета данных
        self.num_packet = 1


4. Описание функции, которая сообщает мастеру была ли ошибка контрольной суммы при отправке пакета

    # узнаем, была ли ошибка контрольной суммы при отправке пакета
    @property
    def get_error_crc16_packet(self):
        data = self.get_record_status_system
        if self.YES_ERROR_RECEIVED_PACKET == data[self.I_ERROR_RECEIVED_PACKET]:
            # если была ошибка в получении пакет, то сообщим имено о ней
            return data[self.I_ERROR_RECEIVED_PACKET]
        else:
            # иначе, скажем о состоянии ошибки контрольной суммы
            return data[self.I_D_S_ERROR_CRC16]


5. Далее идут функции получения состояния контроллера

    # получаем от контроллера информацию о состоянии системы
    @property
    def get_record_status_system(self, size_delay=0.1):


6. И отправки команды контроллеру

    # отправим команду контроллеру
    def send_command(self, pwm, T, size_delay=0.1):


Их полный текст здесь приводить не буду - все файлы есть в архиве к этому сообщению. Ниже немного разберу, как они работают.

Как рассчитывается номер пакета, и как рассчитывается и добавляется к пакету контрольная сумма.

Помятуя о том, что здесь говорилось, причём ни один раз, о том, что кто-то ловит ошибку пропадания старшего бита в байте переданного по i2c, максимальный номер пакета - 127, далее он заново начинает отсчёт с единицы. Сама контрольная сумма, хоть и занимает два байта, но запаковывается в три байта, в двух первых используются только младшие семь бит, в третьем только два младших бита. После чего эти три байта добавляются к пакету передаваемых данных. Номер пакета является элементом передаваемых данных (это видно из вышепредложенного описания структуры пакетов).
Выглядит на питоне это так:
   # сперва соберём пакет данных для контроллера
        self.data_send[self.I_ADDR_SEND] = self._addr
        self.data_send[self.I_D_S_PWM] = pwm
        self.data_send[self.I_D_S_T] = T
       
        # номер отправляемого пакета
        self.data_send[self.I_D_S_NUM_PACKET] = self.num_packet
        # и подсчитаем его контрольную сумму
        crc16_data_send = 0
        crc16_data_send = crc16lib.get_crc16(self.data_send, self.SIZE_DATA_SEND - self.SIZE_D_S_CRC16)
        self.data_send[self.I_D_S_CRC1] = crc16_data_send & 0x7F;
        self.data_send[self.I_D_S_CRC2] = (crc16_data_send >> 7) & 0x7F;
        self.data_send[self.I_D_S_CRC3] = (crc16_data_send >> 14) & 0x03;


После чего отправляем пакет данных контроллеру
           self._bus.write_i2c_block_data(self._addr, 0x00, 
                                              [
                                                self.data_send[self.I_ADDR_SEND],
                                                self.data_send[self.I_D_S_PWM],
                                                self.data_send[self.I_D_S_T],
                                                self.data_send[self.I_D_S_NUM_PACKET],
                                                self.data_send[self.I_D_S_CRC1],
                                                self.data_send[self.I_D_S_CRC2],
                                                self.data_send[self.I_D_S_CRC3]
                                              ]
                                              )


И выдержав небольшую паузу, спросим у контроллера была ли ошибка контрольной суммы
           # пакет отправили
                if 0 < size_delay:
                    # выдержим небольшую паузу, предоставим контроллеру возможность отработать комнаду
                    now = time.time()
                    tle = now + size_delay
                    while time.time() < tle: pass

                # проверим его контрольную сумму
                if self.NOT_ERROR_CRC16_D_S == self.get_error_crc16_packet:
                    # перейдём к следующему номеру пакета
                    if self.MAX_NUM_PACKET <= self.num_packet:
                        self.num_packet = 1
                    else:
                        self.num_packet +=1
                    break # ошибки контрольной суммы нет - выходим из цикла


Если были ошибки передачи данных (выловленные исключением) или ошибка контрольной суммы то повторяем попытки отправить данных заданное количество раз (переменная tries).

При получении пакета о состоянии контроллера сперва, намеренно, делаем значение переменных переданной контрольной суммы (crc16_data_received = 0) и вычисленной контрольной суммы (calc_crc16_data_received = 1) различными, чтобы попасть в цикл получения данных

инициализируем начальные значения переменных, используемых в процессе получения данных от контроллера
   # инициализируем начальные значения переменных, используемых в процессе получения данных от контроллера
        crc16_data_received = 0
        calc_crc16_data_received = 1
        data_received = []
        for i in range(self.SIZE_DATA_RECEIVED):
            data_received.append(0)


после чего, получив данные от контроллера
                  data_received = self._bus.read_i2c_block_data(self._addr, 0x00, self.SIZE_DATA_RECEIVED)


и убедившись, что пакет данных пришёл от нашего контроллера, подсчитаем контрольную сумму пакета
       if self._addr == data_received[self.I_ADDR_RECEIVED]:
                # соберём из трёх последних байт, передаваемых в пакете, контрольную сумму пакета
                crc16_data_received = (data_received[self.I_D_R_CRC3] << 14) + (data_received[self.I_D_R_CRC2] << 7) + data_received[self.I_D_R_CRC1]
                # вычислим контрольную сумму по пакету полученных данных
                calc_crc16_data_received = crc16lib.get_crc16(data_received, self.SIZE_DATA_RECEIVED - self.SIZE_D_R_CRC16)
                # ещё одну попытку исчерпали
                tries_crc16 -= 1


Если есть неисправимые ошибки при получении пакета данных, то зафиксируем это
   if (0 == tries_crc16 or 0 == tries_read) and self._addr == data_received[self.I_ADDR_RECEIVED]:
            # были исчерпаны все попытки получить данных от контроллера
            if crc16_data_received <> calc_crc16_data_received:
                data_received[self.I_D_R_ERROR_CRC16] = self.YES_ERROR_CRC16_D_R;
                print "\tошибка контрольной суммы при получении пакета данных от контроллера клапана отбора"
            else:
                data_received[self.I_ERROR_RECEIVED_PACKET] = self.YES_ERROR_RECEIVED_PACKET;
                print "\tбыли исчерпаны все попытки получить пакет данных от контроллера клапана отбора"


На стороне контроллера всё так же не сложно.
Сперва нужно скопировать в папку с библиотеками библиотеку crc16lib (либо запаковав в архив zip импортировать её в библиотеку через IDE-arduino).
Потом подключить эту библиотеку в скетче

#include <crc16lib.h>


Кстати, в скетче есть константа DEBUG_ENABLE и макрос, которые используются для отладки скетча на плате ардуино с com-портом

// если расскоментировать следующую строку, то в com-порт будет производиться отладочный вывод
//#define DEBUG_ENABLE

// макросы для отладки через com-порт
#ifdef DEBUG_ENABLE
#define DEBUG(x) Serial.println(x); delay(100);
#define DEBUG_DEC(x) Serial.println(x, DEC); delay(100);
#else
#define DEBUG(x)
#define DEBUG_DEC(x)
#endif


Собираем пакет данных для мастера и добавляем к нему контрольную сумму

void sendData() { // callback для передаваемых по i2c байтов
  data_send[I_ADDR_SEND] = SLAVE_ADDRESS;
  data_send[I_D_S_PWM] = pwm;
  data_send[I_D_S_T] = int(T);
  data_send[I_D_S_NUM_PACKET] = data_received[I_D_R_NUM_PACKET];
  data_send[I_D_R_ERROR_CRC16] = 0;
  data_send[I_ERROR_RECEIVED_PACKET] = 0;

  // потом вычислим его контрольную сумму и добавим в пакет данных для отправки
  crc16_data_send = get_crc16(data_send, SIZE_DATA_SEND - SIZE_D_S_CRC16);
  data_send[I_D_S_CRC1] = crc16_data_send & 0x7F;
  data_send[I_D_S_CRC2] = (crc16_data_send >> 7) & 0x7F;
  data_send[I_D_S_CRC3] = (crc16_data_send >> 14) & 0x03;

  // и отправим
  Wire.write(data_send, SIZE_DATA_SEND);
  // подымем флаг отправки, для постобработки факта отправки
  #ifdef DEBUG_ENABLE
  flag_data_send = true;
  #endif
}


А здесь получаем пакет данных от мастера

void receiveData(int byteCount) { // callback для принимаемых по i2c байтов
  if ((SIZE_DATA_RECEIVED + 1) == Wire.available()) {
    // получили пакет данных нужно размера, прочитаем его
    // DEBUG("read received packet data");
    Wire.read();
    data_received[I_ADDR_RECEIVED] = Wire.read();
    if (SLAVE_ADDRESS == data_received[I_ADDR_RECEIVED]) {
      // если адрес контроллера совпадает с адресом в пакете
      // тогда считаем, что это наш пакет
      data_received[I_D_R_PWM] = Wire.read();
      data_received[I_D_R_T] = Wire.read();
      data_received[I_D_R_NUM_PACKET] = Wire.read();
      data_received[I_D_R_CRC1] = Wire.read();
      data_received[I_D_R_CRC2] = Wire.read();
      data_received[I_D_R_CRC3] = Wire.read();
      flag_data_received = true;
    } else {
      // пакет не для нашего контроллера, просто считаем всё из буфера
      while (Wire.available() > 0) {
        Wire.read();
      }
    }
  } else {
    // во всех остальных случаях считаем данных из буфера, чтобы опустошить его
    while (Wire.available() > 0) {
      Wire.read();
    }
  }
}


Но контрольную сумму уже считаем в цикле обработки, в случае с клапаном в функции прерывания

  if (flag_data_received) {
    // был получен пакет данных для контроллера
    // узнаем контрольную сумму пакета, переданную вместе с пакетом
    crc16_data_received = (data_received[I_D_R_CRC3] << 14) + (data_received[I_D_R_CRC2] << 7) + data_received[I_D_R_CRC1];
    crc16_calc_data_received = get_crc16(data_received, SIZE_DATA_RECEIVED - SIZE_D_R_CRC16);
    if (crc16_data_received != crc16_calc_data_received) {
      // DEBUG("Контрольные суммы НЕ совпали " + String(crc16_data_received) + " != " + String(crc16_calc_data_received));
      data_send[I_D_S_ERROR_CRC16] = YES_ERROR_CRC16_D_S;
    } else {
      // DEBUG("Контрольные суммы совпали " + String(crc16_data_received) + " = " + String(crc16_calc_data_received));
      data_send[I_D_S_ERROR_CRC16] = NOT_ERROR_CRC16_D_S;
    }

    if (NOT_ERROR_CRC16_D_S == data_send[I_D_S_ERROR_CRC16]) {
      // нет ошибки в пакете переданных данных, обработаем его
      if (pwm != data_received[I_D_R_PWM]) {
        pwm = data_received[I_D_R_PWM];
        pwmChangedFlag = 1;
      }
      if (T != data_received[I_D_R_T]) {
        T = data_received[I_D_R_T];
        T_change_flag = 1;
        DEBUG("T = " + String(T));
      }
    }
    flag_data_received = false;
  } // if (flag_data_received) {


Здесь же и устанавливаем соответствующий байт в отправляемом мастеру пакете о состоянии контрольной суммы полученного ранее пакета.

Избыточно добавил в пакет адрес контроллера, по идее контроллер должен будет получать только свой пакет данных, но в период отладки скетча возникли сомнения на этот счёт, поэтому добавил это поле в пакет данных и осуществляю проверку по нему, при получении пакета данных.

Т.к. вывод в отладочную консоль через com-порт в sendData невозможен, то на период отладки ввожу переменную flag_data_send и по ней ориентируюсь, и, если нужно, после отправки данных делаю вывод в консоль.


ISR (TIMER2_COMPA_vect) { // Обработчик прерывания от таймера TIMER2
  .
  .
  .

  #ifdef DEBUG_ENABLE
  if (flag_data_send) {
    // был отправлен пакет данных от контроллера
    // DEBUG("crc16_data_send = " + String(crc16_data_send));
    flag_data_send = false;
  }
  #endif


  .
  .
  .
}

.
.
.

void sendData() { // callback для передаваемых по i2c байтов
  .
  .
  .

  // и отправим
  Wire.write(data_send, SIZE_DATA_SEND);
  // подымем флаг отправки, для постобработки факта отправки
  #ifdef DEBUG_ENABLE
  flag_data_send = true;
  #endif

}


Да, если реализовываете контрольную сумму, то для всех классов, которые работают по шине i2c, без исключения.

В сценарии sens.py в классах датчиков добавил получение данных от них по исчерпанию попыток, вот пример получения температуры от DS18B20

    def T(self):
        """            Свойство - температура датчика. Цикл преобразования
            (около 750 мс) запускается при каждом использовании этого
            свойства в правой части выражения."""
        tries_read = 5;
        error_read = None
        size_delay = 0.1
        temperature = 0.0
        while tries_read:
            try:
                temperature = DS18B20.temperature(self._fn)
            except IOError as e:
                error_read = e
                tries_read -= 1
                if 0 < size_delay:
                    # выдержим небольшую паузу, прежде чем повторить попытку получения данных от датчика
                    now = time.time()
                    tle = now + size_delay
                    while time.time() < tle: pass
            else:
                break


Вот, в принципе и всё.
valve_crc16.zip 14.9 Кб