Небольшой производитель контроллеров для теплиц получал звонки от фермеров примерно одного содержания: «Контроллер завис ночью, форточки не закрылись, рассада замёрзла». Прошивку, после которой такие звонки прекратились, переписывал Евгений Росляков. Программированием занимаюсь с 2016 года. Я самозанятый, работаю по договору, закрывающие документы оформляю на каждый этап. Нахожусь в Москве, работа шла удалённо: производитель прислал мне платы, датчики и стенд с клапанами.
Контроллер управляет теплицей среднего фермерского хозяйства. Он читает датчики температуры и влажности воздуха и почвы, открывает и закрывает форточки, включает полив по расписанию и досвечивание. В теплице жарко, влажно, напряжение в сети скачет, а ближайший сервисный инженер иногда в сотне километров.
Старая прошивка выросла из прототипа. Один большой цикл, в котором всё опрашивалось по очереди, задержки на ожиданиях, глобальные переменные. Если датчик почвы переставал отвечать, цикл ждал его и останавливал всё остальное. Сторожевой таймер был выключен, «чтобы не перезагружался». Расписание полива хранилось в памяти, которая стиралась при каждом скачке напряжения.
Обновить прошивку мог только инженер с программатором на месте. Поэтому ошибки, найденные весной, жили в теплицах до следующего сезона.
Начал с устройства самой системы, а не с функций. Embedded System в теплице должен в первую очередь не зависать и восстанавливаться, а уже потом уметь много.
Переписал основу как набор небольших задач с простым планировщиком. Опрос датчиков, управление форточками, полив, связь — у каждой задачи свой шаг и свой лимит времени. Если датчик молчит, задача помечает его как неисправный и идёт дальше, а контроллер переходит на безопасное правило: при отказе датчика температуры форточки закрываются к вечеру по времени.
Включил сторожевой таймер и сделал так, чтобы его сбрасывала не одна задача, а проверка, что все задачи живы. Если перезагрузка всё-таки происходит, контроллер поднимается за доли секунды и продолжает с того же места: расписание и состояние клапанов лежат в энергонезависимой памяти. Запись в неё распределил по ячейкам, чтобы память не выработалась за пару сезонов частых сохранений.
Детектор просадки питания предупреждает прошивку заранее. За оставшиеся миллисекунды она успевает сохранить состояние и перевести выходы в безопасное положение.
Код в основном на C, но несколько мест написал на ассемблере. Это код запуска до инициализации памяти, обработчик прерывания от расходомера полива, где импульсы идут часто и важен каждый такт, и подсчёт контрольной суммы образа прошивки. В документации на микроконтроллер этот низкий уровень описан как Assembly, и я держался её соглашений, чтобы следующему разработчику было легко читать.
Отдельной работой стала разработка системного ПО для обновлений. Написал загрузчик с двумя областями памяти: новая прошивка записывается во вторую область, проверяется контрольной суммой и подписью, и только потом контроллер на неё переключается. Если новая версия не стартовала или не подтвердила работу, загрузчик возвращает старую. Это классическое System Programming без права на ошибку: сломанный загрузчик превращает контроллер в кирпич.
Обновление делает сам фермер через USB-флешку или сервисный инженер с планшета по кабелю, без программатора.
Звонки про зависшие ночью контроллеры за прошедший сезон прекратились. Были отказы датчиков, но контроллер переходил на безопасное правило и сообщал об этом на экран.
Скачки напряжения больше не стирают расписания. Фермеры, у которых в хозяйстве старая проводка, заметили это первыми.
Обновления стали обычным делом. Исправление ошибки, найденной в мае, дошло до теплиц в мае, флешкой по почте, а не следующей весной с инженером.
Ограничение железа никуда не делось: в контроллере мало памяти, и для новых функций, например удалённого мониторинга через сотовую связь, места почти не осталось. Это задача для следующей ревизии платы, и производитель её уже проектирует с запасом.
Документация на прошивку, загрузчик и порядок выпуска обновлений передана разработчику производителя.
Если ваше устройство зависает в поле и обновляется только с программатором, напишите мне через freelance.ru. Посмотрю код и схему и скажу, что сделать в первую очередь.