🛠 Главный файл шаблона для DWIN + UART5 + RS-485 + Modbus

😌 Не надо бояться Keil и TL51
У многих при виде Keil, T5L или TL51 сразу включается внутренний хтонический ужас, будто впереди не работа с микроконтроллером, а какое-то особо извращенное инженерное испытание. Я лично и сам мандражировал как девственница перед гэнг-бэнгом. На самом деле, все оказалось довольно просто. TL51 — это не магия, не инопланетная технология и не черный ящик. Это вполне обычный микроконтроллер, и, насколько можно судить по логике работы, речь идет о вполне понятной 8-битной архитектуре, со своими регистрами, UART, таймерами, прерываниями и прочими знакомыми вещами.
То есть подход здесь примерно такой же, как и в старых добрых проектах под AVR или ту же ATmega: настраиваем периферию, инициализируем порты, запускаем UART, крутим основной цикл, обрабатываем события. Никакой принципиально новой вселенной тут нет. Если человек уже работал с микроконтроллерами не только через яркие кнопочки Arduino IDE, а понимает, что такое регистры, биты, таймеры и обмен по последовательному интерфейсу, то и Keil, и TL51 он вполне осилит без мистических обрядов и кровавых жертвоприношений.
Да, местами тут есть свои странности: названия регистров могут быть непривычными, документация — мутной, а некоторые вещи приходится выяснять почти археологическими методами. Но это не делает платформу какой-то запредельно сложной. Порог входа здесь психологически завышен сильнее, чем технически. И если не страдать недугом «Arduino головного мозга», где любой шаг вне готовой библиотеки вызывает экзистенциальный кризис, то с этой системой можно работать совершенно нормально.
Поэтому правильный настрой в начале такой: перед нами обычный микроконтроллер с UART, таймерами и памятью, а не древнее проклятие инженеров DWIN. Нужно просто спокойно разобрать, что и где инициализируется, какие функции за что отвечают, и как связаны между собой экран, UART5, RS-485 и Modbus. А дальше всё становится гораздо менее страшным и гораздо более похожим на обычную прикладную разработку под железо.
/****************************************************************************** * Project : TEMPLATE * File : APP.c * Author : Andrey madmentat * Site : https://madmentat.ru * Year : 2026 * * Brief: * Главный файл шаблона приложения для DWIN/DGUS. * Содержит базовую прикладную логику проекта: * инициализацию UART5, RS-485, Modbus RTU и основной цикл обработки. * modbus rtu slave 8n1 115200 * Notes: * - Используется как стартовая точка для проектов с DWIN HMI * - Подходит для обмена данными по Modbus RTU Slave * - Может быть расширен пользовательской логикой страниц, VP и меню ******************************************************************************/ #include "APP.h" #include "SYSTEM.h" #include "UART.h" #include "TIMER.h" #include "modbus.h" #include <string.h> // ==================== ОСНОВНЫЕ НАСТРОЙКИ ==================== // Примеры часто используемых VP (можно добавлять свои) #define STATUS_VP 0x3000 // Статус/сообщение для STM32 #define CONTROL_VP 0x3002 // Команда от STM32 к DWIN #define VALUE_VP 0x3100 // Пример числового значения #define TEXT_VP 0x1005 // Пример текстового VP (26 символов) // ==================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ==================== xdata uint32_t SysTimCnt = 0; // Счётчик системного таймера (инкремент в ISR) xdata uint32_t Uptime = 0; // Время работы в секундах xdata uint32_t UptimeOld = 0; // Для сравнения (периодические задачи) xdata uint16_t CurrentPage = 0; // Текущая страница (для отслеживания смены) // ==================== ПРОТОТИПЫ ==================== static void WriteDgusByte(uint16_t vp, uint8_t offset, uint8_t value); static void ClearText(uint16_t vp); static void WriteText(uint16_t vp, const char* str); // ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ DGUS ==================== static void WriteDgusByte(uint16_t vp, uint8_t offset, uint8_t value) { uint16_t current_word = ReadDgus(vp + (offset / 2)); if (offset % 2 == 0) current_word = (current_word & 0x00FF) | ((uint16_t)value << 8); else current_word = (current_word & 0xFF00) | value; WriteDgus(vp + (offset / 2), current_word); } static void ClearText(uint16_t vp) { uint8_t i; WriteDgus(vp, 0xFFFF); // Очистка первого слова for (i = 0; i < 26; i++) // 26 символов — стандарт для DWIN { WriteDgusByte(vp, i, 0x20); // Пробел } } static void WriteText(uint16_t vp, const char* str) { uint8_t len = 0; uint8_t i; while (str[len] && len < 26) len++; ClearText(vp); for (i = 0; i < len; i++) { WriteDgusByte(vp, i, (uint8_t)str[i]); } } // ==================== ИНИЦИАЛИЗАЦИЯ ==================== void AppInit(void) { UartInit(); TimerInit(); ModbusInit(); SysTimCnt = 0; Uptime = 0; UptimeOld = 0; CurrentPage = 0; // Пример начальной инициализации VP WriteText(TEXT_VP, "READY"); WriteDgus(STATUS_VP, 0x0000); // Статус = 0 WriteDgus(CONTROL_VP, 0x0000); // Можно сразу перейти на главную страницу // PageChange(0); } // ==================== ОСНОВНОЙ ЦИКЛ ==================== void AppProcess(void) { uint16_t new_page; ModbusProcess(); // ← Обработка Modbus RTU Slave (самое важное!) new_page = GetPageID(); // Отслеживаем смену страницы (удобно для реакций) if (new_page != CurrentPage) { CurrentPage = new_page; // Здесь можно добавить действия при смене страницы } // Пример периодической задачи раз в секунду if (Uptime != UptimeOld) { UptimeOld = Uptime; // Можно обновлять STATUS_VP или делать heartbeat // WriteDgus(STATUS_VP, (uint16_t)Uptime); } // Если используете тач/клавиатуру — сюда можно добавить обработчик // ProcessTouch() или аналог UartProcess(); // Если нужно (обычно пустой или для отладки) }
Этот файл — главный прикладной файл шаблона. Именно здесь собирается базовая логика работы программы: инициализация UART, таймера, Modbus, работа с DGUS/DWIN-переменными и выполнение основного цикла.
Если говорить совсем по-простому, то этот файл отвечает за следующее:
- 📡 запускает связь по UART5;
- 🔁 готовит программу к работе по RS-485;
- 📘 подключает обработку Modbus RTU Slave;
- 🖥 умеет писать текст и значения в DWIN/DGUS;
- ⏱ выполняет периодические действия в основном цикле.
📂 Какие файлы подключаются
В начале файла подключаются заголовки:
#include "APP.h" #include "SYSTEM.h" #include "UART.h" #include "TIMER.h" #include "modbus.h" #include <string.h>
Что это значит:
- APP.h — заголовок самого прикладного модуля;
- SYSTEM.h — общие системные объявления;
- UART.h — работа с UART, в том числе UART5;
- TIMER.h — системный таймер;
- modbus.h — поддержка протокола Modbus;
- string.h — стандартные функции для строк.
💡 То есть этот файл сам по себе не делает всё в одиночку. Он скорее выступает как дирижёр, который вызывает нужные модули.
⚙ Основные настройки VP
#define STATUS_VP 0x3000 #define CONTROL_VP 0x3002 #define VALUE_VP 0x3100 #define TEXT_VP 0x1005
Это адреса переменных VP в памяти DWIN/DGUS.
- 📌 STATUS_VP — адрес для статуса или сообщения;
- 📌 CONTROL_VP — адрес для управляющей команды;
- 📌 VALUE_VP — адрес для числового значения;
- 📌 TEXT_VP — адрес текстового поля на экране.
Проще говоря, это ячейки экрана, в которые программа может записывать данные.
🧠 Глобальные переменные
xdata uint32_t SysTimCnt = 0; xdata uint32_t Uptime = 0; xdata uint32_t UptimeOld = 0; xdata uint16_t CurrentPage = 0;
Они нужны для хранения текущего состояния программы:
- ⏱ SysTimCnt — системный счётчик тиков таймера;
- ⌛ Uptime — время работы программы;
- 🔄 UptimeOld — предыдущее значение времени, чтобы понимать, прошла ли секунда;
- 📄 CurrentPage — текущая страница DWIN, чтобы отслеживать переключения.
То есть это не какие-то «магические числа», а обычные служебные переменные, нужные для контроля времени и экрана.
🖊 Вспомогательные функции для DGUS
В файле есть три важных служебных функции:
- WriteDgusByte()
- ClearText()
- WriteText()
1️⃣ WriteDgusByte()
Эта функция записывает один байт в текстовую область DWIN. Поскольку DGUS обычно работает словами по 16 бит, приходится:
- сначала прочитать текущее слово,
- поменять в нём только нужный байт,
- записать слово обратно.
🔧 Это нужно потому, что текст на DWIN часто хранится не как обычная C-строка, а как набор байтов внутри 16-битных слов.
2️⃣ ClearText()
Эта функция очищает текстовое поле. Сначала записывает специальное значение, а потом заполняет область пробелами.
🧹 Проще говоря: было что-то написано на экране — функция стирает это и делает поле пустым.
3️⃣ WriteText()
Эта функция выводит текст в заданный VP. Она:
- считает длину строки;
- очищает текстовое поле;
- записывает символы один за другим.
🖥 Благодаря этому можно делать, например, такие надписи:
WriteText(TEXT_VP, "READY");
То есть на экране появится слово READY.
🚀 Что делает AppInit()
Функция AppInit() — это начальная инициализация программы.
void AppInit(void)
{
UartInit();
TimerInit();
ModbusInit();
SysTimCnt = 0;
Uptime = 0;
UptimeOld = 0;
CurrentPage = 0;
WriteText(TEXT_VP, "READY");
WriteDgus(STATUS_VP, 0x0000);
WriteDgus(CONTROL_VP, 0x0000);
}
По шагам:
- 📡 UartInit() — запускает UART;
- ⏱ TimerInit() — запускает таймер;
- 📘 ModbusInit() — подготавливает Modbus;
- 🧹 обнуляет служебные переменные;
- 🖥 пишет на экран слово READY;
- 📌 сбрасывает значения статуса и управления.
То есть после включения устройство приводит всё в понятное начальное состояние.
✅ Иными словами: AppInit() — это «подготовить железку к жизни».
🔁 Что делает AppProcess()
Функция AppProcess() — это основной рабочий цикл программы. Она вызывается снова и снова, пока устройство включено.
void AppProcess(void)
{
uint16_t new_page;
ModbusProcess();
new_page = GetPageID();
if (new_page != CurrentPage)
{
CurrentPage = new_page;
}
if (Uptime != UptimeOld)
{
UptimeOld = Uptime;
}
UartProcess();
}
Разберём по-человечески.
1️⃣ ModbusProcess()
Это самая важная часть цикла. Здесь идёт обработка обмена по Modbus RTU Slave.
📡 Именно тут устройство:
- принимает запросы от мастера;
- разбирает пакеты;
- отвечает по RS-485;
- обновляет регистры и данные.
Если убрать или сломать эту часть, то Modbus просто работать не будет.
2️⃣ GetPageID()
Дальше программа узнаёт, какая сейчас открыта страница на DWIN.
Это полезно, если нужно делать разную логику для разных экранов. Например:
- на главной странице читать только основные параметры;
- в инженерном меню читать другой набор переменных;
- не опрашивать всё подряд без необходимости.
💡 Это как раз тот путь, который помогает уменьшить лишние задержки и нагрузку.
3️⃣ Контроль времени
Дальше идёт проверка:
if (Uptime != UptimeOld)
{
UptimeOld = Uptime;
}
Это значит: если прошла ещё одна секунда — можно выполнить какое-нибудь периодическое действие.
Например:
- 💓 послать heartbeat;
- 🧾 обновить статус;
- 🔍 проверить наличие связи;
- 🖥 вывести диагностическую информацию на экран.
4️⃣ UartProcess()
В конце вызывается UartProcess(). В зависимости от проекта там может быть:
- обработка буферов приёма и передачи;
- отладка;
- или вообще почти ничего, если всё уже делается в других местах.
📡 Как тут связаны UART5, RS-485 и Modbus
Сам этот файл не содержит всей низкоуровневой магии UART5 в явном виде, но он управляет общей логикой через вызовы:
- UartInit() — инициализация UART;
- UartProcess() — обработка UART;
- ModbusInit() — запуск Modbus;
- ModbusProcess() — работа протокола Modbus.
То есть реальная работа с UART5 и RS-485 обычно находится в модулях UART.c и modbus.c, а данный файл управляет этим на уровне приложения.
📘 Упрощённая схема такая:
Modbus master (например Modbus Poll)
↓
RS-485
↓
UART5
↓
modbus.c / UART.c
↓
APP.c
↓
переменные и экран DWIN
🖥 Зачем вообще нужен этот файл
Этот файл удобен как главный шаблон проекта, потому что в нём уже есть:
- ✅ базовая структура программы;
- ✅ инициализация всех нужных подсистем;
- ✅ работа с текстом на DWIN;
- ✅ основной цикл обработки;
- ✅ место для добавления своей логики.
То есть можно не начинать с полного нуля, а взять этот шаблон и постепенно наращивать функциональность.
🔍 Что в этом шаблоне пока простое, а что можно улучшить
Сейчас шаблон хороший как основа, но он ещё базовый.
Что уже есть:
- ✔ понятная инициализация;
- ✔ вывод текста в DWIN;
- ✔ вызов Modbus в основном цикле;
- ✔ отслеживание текущей страницы;
- ✔ заготовка для периодических действий.
Что можно развить дальше:
- 🔧 добавить реакцию на конкретные страницы;
- 🔧 опрашивать только нужные VP в зависимости от экрана;
- 🔧 сделать индикацию потери связи;
- 🔧 добавить heartbeat в Modbus или DGUS;
- 🔧 реализовать меню, кнопки, ввод пароля и т. д.
🧾 Итог простыми словами
Если совсем по-простому, то этот файл делает следующее:
- 🚀 запускает UART, таймер и Modbus;
- 🖥 подготавливает DWIN-экран;
- 🔁 в бесконечном цикле обрабатывает обмен по Modbus;
- 📄 следит за текущей страницей экрана;
- ⏱ позволяет выполнять действия раз в секунду или по событиям.
То есть это не «весь Modbus целиком», а главный управляющий файл приложения, в котором сходятся вместе: UART5, RS-485, DWIN/DGUS и Modbus RTU Slave.
💬 Пояснение для маленьких и тупых (все, как мы любим)
- 🤖 AppInit() — это «включили всё нужное»;
- 🔁 AppProcess() — это «крутимся по кругу и работаем»;
- 🖥 WriteText() — это «пишем текст на экран»;
- 📡 ModbusProcess() — это «слушаем мастера и отвечаем ему»;
- 📄 GetPageID() — это «смотрим, какой экран открыт».
Вот и все. Не магия. Просто аккуратно разложенная по функциям логика.
P.S. ⚠ Подготовка шаблона под DMG80480C043_01WTR
При подготовке этого шаблона к работе с конкретной панелью DMG80480C043_01WTR пришлось столкнуться с довольно типичной для таких устройств проблемой: исходный примерный проект был рассчитан под другую частоту микроконтроллера. А это значит, что настройки UART из чужого шаблона нельзя было просто взять и бездумно оставить как есть.
Проблема заключалась в том, что реальная частота моего микроконтроллера фактически была неизвестна. Документация на такие панели часто не даёт нормальной уверенности в подобных деталях, а если и даёт, то далеко не всегда понятно, насколько этим данным можно доверять на практике. В результате обычный путь вида «вписал стандартное значение и пошёл дальше» здесь не сработал.
Поэтому пришлось действовать более практичным, инженерным и местами слегка шаманским методом: была написана специальная тестовая программа, задача которой состояла в том, чтобы методом перебора подбирать нужное значение переменной i для функции static void Uart5Init(void) из файла UART.c.
Смысл теста был предельно простой:
- ⚙ программа выставляла очередное значение i;
- 📡 запускала UART5 с этими настройками;
- 🖥 отправляла текст через UART5 примерно раз в одну секунду;
- 👀 оставалось смотреть, в какой момент на экране появится нормально читаемый текст, а не мусор и абракадабра.
Как только на экране начал отображаться читаемый текст, стало ясно, что найденное значение i является близким к правильному, а значит, параметры инициализации UART5 наконец-то совпали с реальными условиями работы данной панели. Любопытно, что подходящим оказался не один-единственный параметр. В процессе перебора был найден довольно широкий рабочий диапазон — примерно около 16 значений, при которых текст уже становился читаемым. Это полезное наблюдение, потому что оно косвенно показывает: даже при неизвестной точной частоте МК можно эмпирически подобрать достаточно устойчивую настройку UART.
Иными словами, в данном случае настройка UART5 выполнялась не от красивой теории из документации, а от практического результата. Сначала подбиралось значение, при котором связь реально начинала работать, и только после этого шаблон можно было считать пригодным для дальнейшего использования.
✅ Именно таким способом и удалось определить оптимальное значение переменной i, которое затем было использовано в рабочем варианте шаблона.
Ну что, уже ручки чешутся посмотреть поближе, о чем речь? Ну вот мы и добрались до этого момента!
