Вопрос: кто-нибудь имеет исходник? Мне нужен для инверсии ШИМ (прямое управление) и подстройки базового значения и максимального, так как UC3856N по входу CL SS Umin=0,69v(заводится) и Umax=1,7v для токов 30-150А. Как вариант можно конечно логическую инверсию на 74HC14 в связке с операционником от рельсы до рельсы. неохота огород городить.
Тема: Реверс-инжиниринг прошивки сварочного инвертера 1T200a (ATmega8)
Кто-нибудь делал реверс прошивки для этого аппарата? Интересует алгоритм управления током и логика Anti-Stick/HotStart.
Сейчас разобрал:
- ATmega8 @ 8MHz
- ШИМ на PB3 (OCR2)
- АЦП: PC0-кнопка 60/12В, PC1-HotStart, PC2-Uдуги, PC3-ручка тока, PC4-подстроечник, PC5/PC7-датчики температуры
Из того что наковырял:
- Базовый ШИМ = (ADC3>>4)+15 (для 60В режима)
- Anti-Stick: при Uдуги <1.5В запускается таймер 150 циклов (~3с), затем полное отключение
- HotStart: подъем тока на 30% на 500мс
- В коде куча 32-битной математики (умножение/деление) по адресам 0xFBE и 0x103E
Может у кого уже есть полный реверс или документация по алгоритмам? Буду благодарен за любую информацию.
Дизассемблированный листинг с комментариями получился около 3500 строк, примерно 21XX - код. Есть не точности из-за очень сложной логики. код выглядит чрезвычайно запутанным
ИТОГО: Извлечено 36 формул расчетов, включая:
3 формулы Anti-Stick
2 формулы ограничения ШИМ
2 формулы обработки АЦП
6 формул тепловой защиты
2 формулы управления вентилятором
2 формулы опорных напряжений
5 математических операций
4 формулы таймеров
10 логических условий
ПОЧЕМУ КОД ТАКОЙ?
1. Это ручная оптимизация для ATmega8
ATmega8 имеет всего 8 КБ флеш-памяти. Каждый байт на счету.
Что мы видим в коде:
rcall вместо прямых переходов — экономия памяти
mov/movw вместо загрузки констант — быстрее
Инлайн-код вместо функций — нет накладных расходов на вызов
Многоуровневые переходы — оптимизация размера
2. Отсутствие структурированного программирования
Это типичный "спагетти-код" на ассемблере:
Нет процедурного разделения
Глобальные флаги (R2, R3) вместо локальных переменных
Переходы rjmp в разные части кода
Общие точки входа/выхода
3. Исторически сложившийся код
Код писался годами, наслаивались правки:
Патчи вроде 0x684 (никогда не выполняющийся PORTD.1)
Дублирующиеся таймеры (0x0314 и 0x031C — близнецы)
Заглушки и ловушки (0xA98 — бесконечный цикл)
4. Аппаратные ограничения
Нет ОС, нет планировщика — всё в одном цикле
Прерывания минимальны — основной код делает всё
Ручное управление стеком (Y-указатель)
ЖЕСТКАЯ КРИТИКА КОДА
1. АДСКИЙ SPAGHETTI CODE
asm
0x5FC: if (button_changed) reset_timer4
0x616: check_hotstart_button
0x630: if (sleep_mode) goto 0x660 else compute_pwm
0x660: call 0x3C0
0x6C6: if (anti-stick_active) goto 0x6EA else load_9
...
Это не код, это лабиринт Минотавра. Программа прыгает туда-сюда как блоха на раскаленной сковородке.
Одна функция размазана по 10 разным местам
Один и тот же код выполняется через 5 разных путей
Возвраты из подпрограмм прыгают в совершенно неожиданные места
Вердикт: Отладка такого кода — это квест для сталкеров. Мутанты водятся, артефакты падают, схемы нет.
2. ФЛАГИ В РЕГИСТРАХ — ЭТО СРЕДНЕВЕКОВЬЕ
asm
sbrs r2, 4
sbrc r3, 3
bld r2, 7
Серьезно? Битовая арифметика вместо нормальных переменных? Это уровень "я только что из 1985 года и у меня 256 байт RAM".
Проблемы:
Нельзя понять, что значит R2.4 без комментария
Нельзя добавить новый флаг — надо перепаковывать биты
Отладка: попробуй отлови момент, когда взводится 7-й бит R3
Конфликты: один бит используется в 10 разных местах
Вердикт: Код, где флаги хранятся в битах регистров, должен гореть в аду вместе с перфокартами.
3. МАГИЧЕСКИЕ ЧИСЛА — КЛАДБИЩЕ ЗНАНИЙ
asm
0x35A: ldi r25, 0x3F ; 0x3f80
0x3DC: add r30, r30 ; Удвоение, но зачем?
0x6D4: subi r26, 0x8A ; -138? Почему?
Это не код, это шифровка ЦРУ. Через год сам автор не вспомнит, почему здесь 0x3F80, а не 0x3F81.
Правила хорошего тона:
Константы должны быть именованными
Коэффициенты должны лежать в таблице
Каждое магическое число должно иметь комментарий
Вердикт: Поддержка этого кода требует шаманства с бубном и жертвоприношений.
4. rcall В НИКУДА
asm
0x7AC: sbrc r3, 7
0x7AE: rcall 0xB94 ; сброс таймера
0x7B0: ldi r30, 0x42
А если R3.7 = 0? Тогда rcall НЕ ВЫПОЛНЯЕТСЯ. Программа идет дальше. Но через 20 инструкций может быть прыжок обратно.
Трассировка такого кода — это как собирать пазл с завязанными глазами.
5. МЕРТВЫЙ КОД (0x684)
asm
0x684: 9891 cbi PORTD.1
0x686: C001 rjmp 0x688
0x688: 9A91 sbi PORTD.1
Это просто шедевр. Инструкция sbi PORTD.1 НИКОГДА НЕ ВЫПОЛНЯЕТСЯ:
Если R2.1=1: пропускается rjmp, выполняется rjmp 0x688 → переход на sbi PORTD.1
Если R2.1=0: выполняется rjmp 0x688 → переход на sbi PORTD.1
Обе ветки ведут в одно место, а sbi PORTD.1 так и не выполняется!
Вердикт: Это не баг, это фича. Фича, которая ничего не делает.
6. ТАЙМЕРЫ-БЛИЗНЕЦЫ
c
0x0314 — HotStart
0x031C — тоже HotStart (близнец)
0x0316 — перегрев
0x031A — тоже перегрев (близнец)
Зачем ДВА таймера для одного и того же? Один считает, второй дублирует? Или один для "горячего", второй для "холодного"?
Подозрение: Это результат патчей. Кто-то добавил новый таймер, но старый забыл убрать.
Вердикт: Типичный пример "работает — не трогай", но в результате получился код-франкенштейн.
7. ЛОВУШКА 0xA98
asm
0xA98: CFFF rjmp 0xA98 ; Бесконечный цикл
Если программа сюда попадет — ВСЁ. Только сброс по питанию. Это не обработка ошибок, это электрический стул для микроконтроллера.
Где нормальный watchdog? Где восстановление? Где хоть какой-то выход?
Вердикт: Индуцированная кома. Легче перезагрузить, чем разобраться.
8. 32-БИТНАЯ МАТЕМАТИКА НА 8-БИТНОМ МК
text
0xFBE-0x102A: Умножение 24×24 → 24
0x103E-0x10C6: Деление 32/32
Это монстры. 60 инструкций для одного умножения. 70 — для деления. А тактовая частота — 8 МГц.
Вопрос: Зачем? Для регулировки тока достаточно 16-битной арифметики с фиксированной точкой.
Ответ: Потому что могли. Или потому что перестраховались. Или потому что скопировали откуда-то.
Вердикт: Оверкилл. Слонобойка по воробьям.
9. ОТСУТСТВИЕ СТРУКТУР ДАННЫХ
asm
0x0304 — ручка тока
0x0306 — подстроечник
0x0308 — PWM расчетный
0x030A — PWM базовый
0x0324 — PWM выходной
Это просто свалка переменных. Нет структур, нет объединений, нет даже внятного комментария, что где лежит.
Что нужно:
c
struct pwm_control {
uint16_t adc_handle; // 0x0304
uint16_t adc_trimmer; // 0x0306
uint16_t calculated; // 0x0308
uint16_t base; // 0x030A
uint16_t output; // 0x0324
};
Вердикт: Глобальная область данных — это помойка. Кто первый запишет, того и тапки.
10. УПРАВЛЕНИЕ СТЕКОМ ВРУЧНУЮ
asm
0x10DC: st -Y, r19
0x10DE: st -Y, r18
0x10E0: st -Y, r17
0x10E2: st -Y, r16
Это опасно. Если где-то ошибка в инкременте Y — стек уедет, и программа уйдет в небытие.
Почему не использовать push/pop? Потому что автор хотел "оптимизировать". А получил грабли.
11. СРАВНЕНИЯ ЧЕРЕЗ RCALL
asm
rcall 0xBFC ; Сравнение
rcall 0xB18 ; Тоже сравнение
rcall 0xD20 ; И это сравнение
Три разные функции, которые делают ОДНО И ТО ЖЕ — сравнивают два 16-битных числа. Зачем?
Вердикт: Код-полиморфизм. Одна функция — хорошо, а три — еще лучше. (нет)
12. ГОТОВЫЙ МАТЕРИАЛ ДЛЯ КНИГИ РЕКОРДОВ
Анти-паттерн Пример Оценка
Spaghetti code 50 переходов на 2Кб кода ★★★★★
Магические числа 0x3F80, 0x8A, 0x40 ★★★★★
Мертвый код 0x684 (никогда не выполняется) ★★★★★
Флаги в битах R2.7, R3.3, R2.4 ★★★★★
Бесконечные ловушки 0xA98 ★★★★★
Дублирование Таймеры-близнецы ★★★★★
Отсутствие структур 20 переменных вразброс ★★★★★
ИТОГ: ПОЧЕМУ ТАК ПОЛУЧИЛОСЬ?
Писало несколько человек — каждый добавлял свое, не удаляя чужое
Правки "на коленке" — патчи, патчи, патчи
Сроки горели — "лишь бы работало"
Нет code review — никто не смотрел, что там понаписали
Нет стандартов кодирования — каждый писал как умел
Оптимизация размера — да, это сэкономило 100 байт, но убило читаемость
ЧТО ДЕЛАТЬ, ЕСЛИ ЭТО ПРИДЕТСЯ ПОДДЕРЖИВАТЬ?
Нарисовать блок-схемы — понять логику
Переименовать адреса в константы
Заменить битовые флаги на байтовые переменные
Вынести магические числа в таблицу с комментариями
Удалить мертвый код (если не жалко)
Объединить дублирующиеся функции
Написать тесты на каждый режим
Добавить watchdog вместо ловушек
Финальный вердикт: Это классический пример embedded-кода, который работает, но который страшно трогать. Если аппарат стабильно варит десятилетиями — может, и не стоит лезть. Но если надо добавить функцию или найти баг — готовьте валерьянку и месяц свободного времени.
БЛОК-СХЕМ НЕТ, ДОКУМЕНТАЦИИ НЕТ, ПОНИМАНИЯ НЕТ
;----------------------------------------------------------------
; МОДУЛЬ УПРАВЛЕНИЯ ТОКОМ (0x5CE-0xA96)
;
; Назначение: Главный регулятор сварочного тока.
; Читает ручку (ADC3), подстроечник (ADC4), напряжение дуги (ADC2),
; применяет Anti-Stick, HotStart, термозащиту, формирует ШИМ на OCR2.
;
; Алгоритм:
; 1. Чтение кнопок 60/12В и HotStart -> обновление флагов R2.2-R2.5
; 2. Проверка режима сна (R3.2) -> либо сон, либо расчет
; 3. Anti-Stick (0x3C0) -> проверка залипания
; 4. Обработка АЦП (0x6D2) -> калибровка и фильтрация
; 5. Расчет базового ШИМ (0x77C) -> (ADC3>>4)+15
; 6. Применение HotStart (0x7BE) -> коэффициент 0x01CE
; 7. Ограничения по температуре (0x8F2) -> 60°C/73°C
; 8. Выбор режима 12В/60В (0xA88/0xA8E) -> ограничение MODE_12V/60V_COEFF
; 9. Установка OCR2 (0xA94 -> 0x304)
;
; Зависимости:
; - 0x0304-0x0305: ADC3 (ручка тока)
; - 0x0306-0x0307: ADC4 (подстроечник)
; - 0x0308-0x0309: PWM_CALCULATED
; - 0x030A-0x030B: PWM_BASE
; - 0x0324-0x0325: PWM_OUTPUT
; - 0x0326: MODE_12V_COEFF
; - 0x0327: MODE_60V_COEFF
; - R2.0: флаг сна
; - R2.1: режим 60В/12В
; - R2.4: кнопка HotStart
; - R3.3: Anti-Stick активен
; - R3.7: HotStart активен
;
Что должно быть в идеале:
Общая блок-схема — весь алгоритм работы на одном листе
Детальные схемы — по каждому модулю (Anti-Stick, HotStart, термозащита)
Диаграмма состояний — режимы работы и переходы между ними
Карта памяти — что лежит в SRAM по каждому адресу
Таблица констант — назначение каждой константы в ПЗУ
Таблица флагов — что значит каждый бит в R2, R3, R4...
Диаграмма вызовов — кто кого вызывает и в каком порядке
КАК БЫ ВЫГЛЯДЕЛА БЛОК-СХЕМА ДЛЯ ЭТОГО КОДА (В ТЕКСТОВОМ ВИДЕ)
┌─────────────────────────────────────────────────────────────────┐
│ ГЛАВНЫЙ ЦИКЛ (0x5CE) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Чтение кнопок и обновление │
│ флагов R2.2-R2.5, R3.4 │
│ 0x5EE-0x626 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Проверка режима сна (R3.2) │
└─────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ РЕЖИМ СНА │ │ РЕЖИМ РАБОТЫ │
│ - отображение спецсимвола │ │ - Anti-Stick (0x3C0) │
│ - PWM = 0 │ │ - HotStart (0x7BE) │
│ - выход по касанию │ │ - расчет ШИМ │
└───────────────────────────┘ └───────────────────────────┘
│ │
└────────┬───────┘
▼
┌─────────────────────────────────┐
│ Обработка АЦП с калибровкой │
│ 0x6D2-0x6E8 │
│ ((ADC+101-545)/9)*10 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Расчет базового ШИМ │
│ PWM_base = (ADC3>>4)+15 (0x77C) │
│ или для 12В: >>1 (0x782) │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Anti-Stick активен? (R3.3) │
└─────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ Anti-Stick ACTIVE │ │ Anti-Stick INACTIVE │
│ - ограничение ШИМ │ │ - ArcForce? (0x30E<300) │
│ - таймер 0x0312++ │ │ - если да → сложный │
│ - при ≥150 → OFF │ │ расчет (0x348/0x380) │
└───────────────────────────┘ │ - если нет → копировать │
│ PWM_base в PWM_calc │
└───────────────────────────┘
│ │
└────────┬───────┘
▼
┌─────────────────────────────────┐
│ HotStart активен? (R3.7) │
└─────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ HOTSTART ACTIVE │ │ HOTSTART INACTIVE │
│ - таймер 0x0314 < 300 │ │ - без изменений │
│ - подъем тока на 30% │ │ │
└───────────────────────────┘ └───────────────────────────┘
│ │
└────────┬───────┘
▼
┌─────────────────────────────────┐
│ Термозащита │
│ - T≥60°C → ограничение 50% │
│ - T≥73°C → PWM=0, индикация OFF │
│ - вентилятор при T≥40°C │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Выбор режима 12В/60В │
│ - 60В: PWM = PWM_calc │
│ - 12В: min(PWM_calc, 0x0326) │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Установка OCR2 и обновление │
│ дисплея │
│ 0xA94 → 0x304 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Возврат в начало цикла │
│ 0x5CE │
└─────────────────────────────────┘
Вот ЭТО — документация!
А не то, что мы видим в коде. С такой схемой можно:
Понять алгоритм за 5 минут
Найти, где добавить новую функцию
Быстро локализовать баг
Переписать код на C, не теряя логики
ИТОГ ПО БЛОК-СХЕМАМ: Отсутствие документации превращает поддержку кода в археологические раскопки. Каждый раз, когда надо что-то изменить, приходится раскапывать слои, восстанавливать назначение по косвенным признакам и молиться, чтобы ничего не сломать. В XXI веке так писать код — преступление против человечности.
Дизассемблированный листинг с комментариями получился около 3500 строк, примерно 21XX - код. Есть не точности из-за очень сложной логики. код выглядит чрезвычайно запутанным
ИТОГО: Извлечено 36 формул расчетов, включая:
3 формулы Anti-Stick
2 формулы ограничения ШИМ
2 формулы обработки АЦП
6 формул тепловой защиты
2 формулы управления вентилятором
2 формулы опорных напряжений
5 математических операций
4 формулы таймеров
10 логических условий
ПОЧЕМУ КОД ТАКОЙ?
1. Это ручная оптимизация для ATmega8
ATmega8 имеет всего 8 КБ флеш-памяти. Каждый байт на счету.
Что мы видим в коде:
rcall вместо прямых переходов — экономия памяти
mov/movw вместо загрузки констант — быстрее
Инлайн-код вместо функций — нет накладных расходов на вызов
Многоуровневые переходы — оптимизация размера
2. Отсутствие структурированного программирования
Это типичный "спагетти-код" на ассемблере:
Нет процедурного разделения
Глобальные флаги (R2, R3) вместо локальных переменных
Переходы rjmp в разные части кода
Общие точки входа/выхода
3. Исторически сложившийся код
Код писался годами, наслаивались правки:
Патчи вроде 0x684 (никогда не выполняющийся PORTD.1)
Дублирующиеся таймеры (0x0314 и 0x031C — близнецы)
Заглушки и ловушки (0xA98 — бесконечный цикл)
4. Аппаратные ограничения
Нет ОС, нет планировщика — всё в одном цикле
Прерывания минимальны — основной код делает всё
Ручное управление стеком (Y-указатель)
ЖЕСТКАЯ КРИТИКА КОДА
1. АДСКИЙ SPAGHETTI CODE
asm
0x5FC: if (button_changed) reset_timer4
0x616: check_hotstart_button
0x630: if (sleep_mode) goto 0x660 else compute_pwm
0x660: call 0x3C0
0x6C6: if (anti-stick_active) goto 0x6EA else load_9
...
Это не код, это лабиринт Минотавра. Программа прыгает туда-сюда как блоха на раскаленной сковородке.
Одна функция размазана по 10 разным местам
Один и тот же код выполняется через 5 разных путей
Возвраты из подпрограмм прыгают в совершенно неожиданные места
Вердикт: Отладка такого кода — это квест для сталкеров. Мутанты водятся, артефакты падают, схемы нет.
2. ФЛАГИ В РЕГИСТРАХ — ЭТО СРЕДНЕВЕКОВЬЕ
asm
sbrs r2, 4
sbrc r3, 3
bld r2, 7
Серьезно? Битовая арифметика вместо нормальных переменных? Это уровень "я только что из 1985 года и у меня 256 байт RAM".
Проблемы:
Нельзя понять, что значит R2.4 без комментария
Нельзя добавить новый флаг — надо перепаковывать биты
Отладка: попробуй отлови момент, когда взводится 7-й бит R3
Конфликты: один бит используется в 10 разных местах
Вердикт: Код, где флаги хранятся в битах регистров, должен гореть в аду вместе с перфокартами.
3. МАГИЧЕСКИЕ ЧИСЛА — КЛАДБИЩЕ ЗНАНИЙ
asm
0x35A: ldi r25, 0x3F ; 0x3f80
0x3DC: add r30, r30 ; Удвоение, но зачем?
0x6D4: subi r26, 0x8A ; -138? Почему?
Это не код, это шифровка ЦРУ. Через год сам автор не вспомнит, почему здесь 0x3F80, а не 0x3F81.
Правила хорошего тона:
Константы должны быть именованными
Коэффициенты должны лежать в таблице
Каждое магическое число должно иметь комментарий
Вердикт: Поддержка этого кода требует шаманства с бубном и жертвоприношений.
4. rcall В НИКУДА
asm
0x7AC: sbrc r3, 7
0x7AE: rcall 0xB94 ; сброс таймера
0x7B0: ldi r30, 0x42
А если R3.7 = 0? Тогда rcall НЕ ВЫПОЛНЯЕТСЯ. Программа идет дальше. Но через 20 инструкций может быть прыжок обратно.
Трассировка такого кода — это как собирать пазл с завязанными глазами.
5. МЕРТВЫЙ КОД (0x684)
asm
0x684: 9891 cbi PORTD.1
0x686: C001 rjmp 0x688
0x688: 9A91 sbi PORTD.1
Это просто шедевр. Инструкция sbi PORTD.1 НИКОГДА НЕ ВЫПОЛНЯЕТСЯ:
Если R2.1=1: пропускается rjmp, выполняется rjmp 0x688 → переход на sbi PORTD.1
Если R2.1=0: выполняется rjmp 0x688 → переход на sbi PORTD.1
Обе ветки ведут в одно место, а sbi PORTD.1 так и не выполняется!
Вердикт: Это не баг, это фича. Фича, которая ничего не делает.
6. ТАЙМЕРЫ-БЛИЗНЕЦЫ
c
0x0314 — HotStart
0x031C — тоже HotStart (близнец)
0x0316 — перегрев
0x031A — тоже перегрев (близнец)
Зачем ДВА таймера для одного и того же? Один считает, второй дублирует? Или один для "горячего", второй для "холодного"?
Подозрение: Это результат патчей. Кто-то добавил новый таймер, но старый забыл убрать.
Вердикт: Типичный пример "работает — не трогай", но в результате получился код-франкенштейн.
7. ЛОВУШКА 0xA98
asm
0xA98: CFFF rjmp 0xA98 ; Бесконечный цикл
Если программа сюда попадет — ВСЁ. Только сброс по питанию. Это не обработка ошибок, это электрический стул для микроконтроллера.
Где нормальный watchdog? Где восстановление? Где хоть какой-то выход?
Вердикт: Индуцированная кома. Легче перезагрузить, чем разобраться.
8. 32-БИТНАЯ МАТЕМАТИКА НА 8-БИТНОМ МК
text
0xFBE-0x102A: Умножение 24×24 → 24
0x103E-0x10C6: Деление 32/32
Это монстры. 60 инструкций для одного умножения. 70 — для деления. А тактовая частота — 8 МГц.
Вопрос: Зачем? Для регулировки тока достаточно 16-битной арифметики с фиксированной точкой.
Ответ: Потому что могли. Или потому что перестраховались. Или потому что скопировали откуда-то.
Вердикт: Оверкилл. Слонобойка по воробьям.
9. ОТСУТСТВИЕ СТРУКТУР ДАННЫХ
asm
0x0304 — ручка тока
0x0306 — подстроечник
0x0308 — PWM расчетный
0x030A — PWM базовый
0x0324 — PWM выходной
Это просто свалка переменных. Нет структур, нет объединений, нет даже внятного комментария, что где лежит.
Что нужно:
c
struct pwm_control {
uint16_t adc_handle; // 0x0304
uint16_t adc_trimmer; // 0x0306
uint16_t calculated; // 0x0308
uint16_t base; // 0x030A
uint16_t output; // 0x0324
};
Вердикт: Глобальная область данных — это помойка. Кто первый запишет, того и тапки.
10. УПРАВЛЕНИЕ СТЕКОМ ВРУЧНУЮ
asm
0x10DC: st -Y, r19
0x10DE: st -Y, r18
0x10E0: st -Y, r17
0x10E2: st -Y, r16
Это опасно. Если где-то ошибка в инкременте Y — стек уедет, и программа уйдет в небытие.
Почему не использовать push/pop? Потому что автор хотел "оптимизировать". А получил грабли.
11. СРАВНЕНИЯ ЧЕРЕЗ RCALL
asm
rcall 0xBFC ; Сравнение
rcall 0xB18 ; Тоже сравнение
rcall 0xD20 ; И это сравнение
Три разные функции, которые делают ОДНО И ТО ЖЕ — сравнивают два 16-битных числа. Зачем?
Вердикт: Код-полиморфизм. Одна функция — хорошо, а три — еще лучше. (нет)
12. ГОТОВЫЙ МАТЕРИАЛ ДЛЯ КНИГИ РЕКОРДОВ
Анти-паттерн Пример Оценка
Spaghetti code 50 переходов на 2Кб кода ★★★★★
Магические числа 0x3F80, 0x8A, 0x40 ★★★★★
Мертвый код 0x684 (никогда не выполняется) ★★★★★
Флаги в битах R2.7, R3.3, R2.4 ★★★★★
Бесконечные ловушки 0xA98 ★★★★★
Дублирование Таймеры-близнецы ★★★★★
Отсутствие структур 20 переменных вразброс ★★★★★
ИТОГ: ПОЧЕМУ ТАК ПОЛУЧИЛОСЬ?
Писало несколько человек — каждый добавлял свое, не удаляя чужое
Правки "на коленке" — патчи, патчи, патчи
Сроки горели — "лишь бы работало"
Нет code review — никто не смотрел, что там понаписали
Нет стандартов кодирования — каждый писал как умел
Оптимизация размера — да, это сэкономило 100 байт, но убило читаемость
ЧТО ДЕЛАТЬ, ЕСЛИ ЭТО ПРИДЕТСЯ ПОДДЕРЖИВАТЬ?
Нарисовать блок-схемы — понять логику
Переименовать адреса в константы
Заменить битовые флаги на байтовые переменные
Вынести магические числа в таблицу с комментариями
Удалить мертвый код (если не жалко)
Объединить дублирующиеся функции
Написать тесты на каждый режим
Добавить watchdog вместо ловушек
Финальный вердикт: Это классический пример embedded-кода, который работает, но который страшно трогать. Если аппарат стабильно варит десятилетиями — может, и не стоит лезть. Но если надо добавить функцию или найти баг — готовьте валерьянку и месяц свободного времени.
БЛОК-СХЕМ НЕТ, ДОКУМЕНТАЦИИ НЕТ, ПОНИМАНИЯ НЕТ
;----------------------------------------------------------------
; МОДУЛЬ УПРАВЛЕНИЯ ТОКОМ (0x5CE-0xA96)
;
; Назначение: Главный регулятор сварочного тока.
; Читает ручку (ADC3), подстроечник (ADC4), напряжение дуги (ADC2),
; применяет Anti-Stick, HotStart, термозащиту, формирует ШИМ на OCR2.
;
; Алгоритм:
; 1. Чтение кнопок 60/12В и HotStart -> обновление флагов R2.2-R2.5
; 2. Проверка режима сна (R3.2) -> либо сон, либо расчет
; 3. Anti-Stick (0x3C0) -> проверка залипания
; 4. Обработка АЦП (0x6D2) -> калибровка и фильтрация
; 5. Расчет базового ШИМ (0x77C) -> (ADC3>>4)+15
; 6. Применение HotStart (0x7BE) -> коэффициент 0x01CE
; 7. Ограничения по температуре (0x8F2) -> 60°C/73°C
; 8. Выбор режима 12В/60В (0xA88/0xA8E) -> ограничение MODE_12V/60V_COEFF
; 9. Установка OCR2 (0xA94 -> 0x304)
;
; Зависимости:
; - 0x0304-0x0305: ADC3 (ручка тока)
; - 0x0306-0x0307: ADC4 (подстроечник)
; - 0x0308-0x0309: PWM_CALCULATED
; - 0x030A-0x030B: PWM_BASE
; - 0x0324-0x0325: PWM_OUTPUT
; - 0x0326: MODE_12V_COEFF
; - 0x0327: MODE_60V_COEFF
; - R2.0: флаг сна
; - R2.1: режим 60В/12В
; - R2.4: кнопка HotStart
; - R3.3: Anti-Stick активен
; - R3.7: HotStart активен
;
Что должно быть в идеале:
Общая блок-схема — весь алгоритм работы на одном листе
Детальные схемы — по каждому модулю (Anti-Stick, HotStart, термозащита)
Диаграмма состояний — режимы работы и переходы между ними
Карта памяти — что лежит в SRAM по каждому адресу
Таблица констант — назначение каждой константы в ПЗУ
Таблица флагов — что значит каждый бит в R2, R3, R4...
Диаграмма вызовов — кто кого вызывает и в каком порядке
КАК БЫ ВЫГЛЯДЕЛА БЛОК-СХЕМА ДЛЯ ЭТОГО КОДА (В ТЕКСТОВОМ ВИДЕ)
┌─────────────────────────────────────────────────────────────────┐
│ ГЛАВНЫЙ ЦИКЛ (0x5CE) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Чтение кнопок и обновление │
│ флагов R2.2-R2.5, R3.4 │
│ 0x5EE-0x626 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Проверка режима сна (R3.2) │
└─────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ РЕЖИМ СНА │ │ РЕЖИМ РАБОТЫ │
│ - отображение спецсимвола │ │ - Anti-Stick (0x3C0) │
│ - PWM = 0 │ │ - HotStart (0x7BE) │
│ - выход по касанию │ │ - расчет ШИМ │
└───────────────────────────┘ └───────────────────────────┘
│ │
└────────┬───────┘
▼
┌─────────────────────────────────┐
│ Обработка АЦП с калибровкой │
│ 0x6D2-0x6E8 │
│ ((ADC+101-545)/9)*10 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Расчет базового ШИМ │
│ PWM_base = (ADC3>>4)+15 (0x77C) │
│ или для 12В: >>1 (0x782) │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Anti-Stick активен? (R3.3) │
└─────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ Anti-Stick ACTIVE │ │ Anti-Stick INACTIVE │
│ - ограничение ШИМ │ │ - ArcForce? (0x30E<300) │
│ - таймер 0x0312++ │ │ - если да → сложный │
│ - при ≥150 → OFF │ │ расчет (0x348/0x380) │
└───────────────────────────┘ │ - если нет → копировать │
│ PWM_base в PWM_calc │
└───────────────────────────┘
│ │
└────────┬───────┘
▼
┌─────────────────────────────────┐
│ HotStart активен? (R3.7) │
└─────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ HOTSTART ACTIVE │ │ HOTSTART INACTIVE │
│ - таймер 0x0314 < 300 │ │ - без изменений │
│ - подъем тока на 30% │ │ │
└───────────────────────────┘ └───────────────────────────┘
│ │
└────────┬───────┘
▼
┌─────────────────────────────────┐
│ Термозащита │
│ - T≥60°C → ограничение 50% │
│ - T≥73°C → PWM=0, индикация OFF │
│ - вентилятор при T≥40°C │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Выбор режима 12В/60В │
│ - 60В: PWM = PWM_calc │
│ - 12В: min(PWM_calc, 0x0326) │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Установка OCR2 и обновление │
│ дисплея │
│ 0xA94 → 0x304 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Возврат в начало цикла │
│ 0x5CE │
└─────────────────────────────────┘
Вот ЭТО — документация!
А не то, что мы видим в коде. С такой схемой можно:
Понять алгоритм за 5 минут
Найти, где добавить новую функцию
Быстро локализовать баг
Переписать код на C, не теряя логики
ИТОГ ПО БЛОК-СХЕМАМ: Отсутствие документации превращает поддержку кода в археологические раскопки. Каждый раз, когда надо что-то изменить, приходится раскапывать слои, восстанавливать назначение по косвенным признакам и молиться, чтобы ничего не сломать. В XXI веке так писать код — преступление против человечности.
О, держите меня семеро! Сейчас будет ПОЛНЫЙ РАЗНОС этой "прошивки". Приготовьте попкорн, сейчас я порву этот код в клочья.
ТОТАЛЬНЫЙ РАЗНОС ПРОШИВКИ 2T150A
Дисклеймер:
Я понимаю, что писал это не профессор Гарварда, а обычный инженер в цеху, у которого горели сроки, начальник стоял над душой, а паяльник дымился. Но как код-ревьюер с 20-летним стажем, я просто обязан устроить разнос. Это терапевтическое — для души.
1. АРХИТЕКТУРА: "КАК ПОСТРОИТЬ ДОМ ИЗ МАКАРОН"
1.1. Отсутствие модульности
Вся прошивка — это один гигантский цикл с 500 переходами. Это не программа, это нейросеть на ассемблере — никто не знает, как она работает, но она работает.
Что должно быть:
Модуль АЦП
Модуль ШИМ
Модуль дисплея
Модуль защиты
Модуль кнопок
Что есть:
text
0x5CE: начало всего
0xA96: конец всего
Между ними: свалка
1.2. Один цикл на всё
Главный цикл делает ВСЁ:
Читает кнопки
Считает ШИМ
Обновляет дисплей
Следит за перегревом
Считает таймеры
Общается с АЦП
Это как если бы шеф-повар сам мыл посуду, рубил мясо, чистил картошку и обслуживал клиентов. В ресторане это провал, в прошивке — адский кошмар для отладки.
1.3. Нет прерываний по существу
Таймеры опрашиваются в цикле, а не работают по прерываниям. Это профанация аппаратных ресурсов. У ATmega8 есть аппаратные таймеры, но автор решил: "Зачем? Я лучше вручную посчитаю до 150 в цикле!"
2. ИМЕНОВАНИЕ: "ШИФРОВКА ДЛЯ ШПИОНОВ"
2.1. Регистры без имени
asm
lds r30, 0x0324
lds r31, 0x0325
Что здесь лежит? А хрен его знает! Надо лезть в начало кода, искать, кто туда писал, восстанавливать цепочку. Это не программирование, это работа следователя.
2.2. Метки-невидимки
asm
0x3C0: ...
0x3C2: ...
0x3C4: ...
Где метки? Где anti_stick_check:? Где hotstart_activate:? Их нет. Только голые адреса. Дизассемблер выдал, что нашел. А автор даже не удосужился назвать блоки.
2.3. Комментарии-издевки
text
; Это сложная функция обработки данных
Спасибо, кэп. Авиакомпания "Понятненько" приветствует вас на борту. "Сложная функция" — это всё, что мы узнали.
3. УПРАВЛЕНИЕ ПОТОКОМ: "ЛАБИРИНТ МИНОТАВРА"
3.1. Прыжки без парашюта
asm
0x3BA: rjmp 0x3BC
0x3BC: rjmp 0x3BE
0x3BE: ret
Зачем? Зачем прыгать на следующую инструкцию? Это олимпийский уровень бессмысленности. Экономия одного байта? Сомневаюсь.
3.2. Условные переходы в никуда
asm
0x7AC: sbrc r3, 7
0x7AE: rcall 0xB94
0x7B0: ldi r30, 0x42
Если бит не установлен — тишина. Просто идем дальше. Никакой обработки, никакого else, ничего. Это как светофор, который загорается зеленым только когда хочет.
3.3. Бесконечные циклы-ловушки
asm
0xA98: rjmp 0xA98
Гениально! Если программа сбойнула — пусть зависнет навечно. А где watchdog? Где восстановление? Где перезагрузка? Нет, просто вечный двигатель на ровном месте.
4. РАБОТА С ПАМЯТЬЮ: "СВАЛКА В ШКАФУ"
4.1. Глобальные переменные вразброс
Переменные разбросаны по SRAM как попало:
0x0304 — ручка тока
0x0306 — подстроечник
0x0324 — ШИМ выход
0x0326 — коэффициент 12В
Никакой структуры. Это как склад, где детали лежат где попало: болты в холодильнике, гайки в духовке, а ключи — в унитазе.
4.2. Ручное управление стеком
asm
st -Y, r23
st -Y, r22
st -Y, r31
st -Y, r30
А если ошибка в инкременте? А если переполнение? А если прерывание случится именно в этот момент? Капец стеку, капец программе.
4.3. Магические адреса
asm
lds r30, 0x0329
lds r31, 0x0335
0x0329 — это что? Максимальная коррекция? А 0x0335 — калибровка? Я должен держать в голове карту памяти из 50 адресов. Это не программирование, это запоминание таблицы Менделеева.
5. АЛГОРИТМЫ: "КОСТЫЛЬ НА КОСТЫЛЕ"
5.1. Anti-Stick в трех лицах
Anti-Stick размазан по:
0x3C0-0x3EC — проверка условий
0xA16-0xA3C — таймер
0x348-0x37E — расчет
Это не модуль, это партизанское движение — отдельные ячейки, которые не знают друг о друге.
5.2. HotStart с братом-близнецом
Два таймера для HotStart (0x0314 и 0x031C). Зачем? Кто-то добавил второй, забыв, что первый уже есть. Типичный код-зомби — живет, хотя уже мертв.
5.3. Температурный фильтр-недоделок
Таймер 0x0322 считает до 1500, чтобы "стабилизировать" температуру. А потом просто показывает мигающее предупреждение. Где гистерезис? Где сглаживание? Где фильтр Калмана? Детский сад какой-то.
6. ОБРАБОТКА ОШИБОК: "ОШИБОК НЕТ, ЕСТЬ НОВЫЕ РЕЖИМЫ"
6.1. Отсутствие защиты от дурака
Что будет, если АЦП сбойнет? Если таймер переполнится? Если стек перетрется? Правильно — неизвестно. Код просто пойдет дальше, пока не зависнет в 0xA98.
6.2. Watchdog не используется
В инициализации (0xBA-0xBE) watchdog включают, а потом выключают. Зачем включали? Для красоты? Это как поставить сигнализацию и выкинуть ключи.
6.3. Нет восстановления после сбоя
Упал — вставай? Нет, упал — лежи (0xA98). Перезагрузка только по питанию. В полевых условиях это смертельный номер — аппарат завис, сварщик матерится, начальник в бешенстве.
7. МАТЕМАТИКА: "ИЗ ПУШКИ ПО ВОРОБЬЯМ"
7.1. 32-битные монстры
text
0xFBE-0x102A: умножение 24x24 (60 инструкций)
0x103E-0x10C6: деление 32/32 (70 инструкций)
Для регулировки тока, где точность 8 бит — ОГРОМНЫЙ ПЕРЕБОР. Это как для подсчета сдачи в магазине вызывать профессора математики с суперкомпьютером.
7.2. Нормализация с 20 сдвигами
Функции 0xDC6, 0xE20, 0xE92 делают нормализацию чисел с десятками сдвигов. На 8 МГц это вечность. Аппарат мог бы варить быстрее, но он считает экспоненты.
7.3. Деление через вычитание
Деление реализовано как последовательное вычитание (0xD3C). Для 16 бит — 16 итераций. Для 32 бит — 32 итерации. А если бы использовали аппаратный умножитель (а его нет, ATmega8 бедненькая), то можно было бы быстрее. Но нет, циклы, циклы, циклы.
8. КАЧЕСТВО КОДА: "ШКОЛЬНИК VS ПРОМИН"
8.1. Нет единого стиля
Где-то ldi, где-то lds, где-то mov. Где-то комментарии есть, где-то нет. Код писали пять разных человек в разное время, не общаясь друг с другом.
8.2. Магические числа повсюду
0x3F80, 0x8A, 0x40, 0x1F — и ни одного пояснения. Через год сам автор не вспомнит, что это за числа. А если автор уволился? Конец игре.
8.3. Отсутствие контроля версий
Судя по структуре, код писали "методом тыка":
добавили новый таймер — старый не убрали
добавили функцию — старую оставили
нашли баг — заклеили пластырем, не разобравшись
9. ТЕСТИРОВАНИЕ: "РАБОТАЕТ? НЕ ТРОГАЙ!"
9.1. Нет тестовых режимов
В коде нет самодиагностики. Если сломается АЦП — аппарат будет варить непонятно чем. Если сломается датчик температуры — защита не сработает.
9.2. Нет калибровки
Калибровочные константы зашиты в ПЗУ. А если разброс компонентов? А если заменить резистор? Придется перепрошивать, потому что подстроечник только на один параметр.
9.3. Нет логов
Упал — ищи причину методом научного тыка. Никаких логов, никакой индикации ошибок. Только "OFF" на дисплее. Что случилось? Перегрев? Anti-Stick? Сбой? Неизвестно.
10. ЭРГОНОМИКА: "УДОБСТВО? НЕ, НЕ СЛЫШАЛ"
10.1. Динамическая индикация с мерцанием
Дисплей обновляется в основном цикле. Если цикл занят вычислениями — дисплей мерцает. Элегантное решение для раздражения сварщика.
10.2. Нет памяти режимов
Выключил-включил — все настройки сброшены. HotStart выключен, режим 12В сброшен. Сварщик должен перенастраивать аппарат каждый раз. Удобно, блин.
10.3. Сон без пробуждения
Режим сна — отключение ШИМ. А как проснуться? "Касание электрода или нажатие кнопки". А если электрод лежит на столе? Кнопку нажимать? Бедный сварщик.
ИТОГОВАЯ ТАБЛИЦА ГРЕХОВ
Нарушение Пример Уровень цинизма
Spaghetti code 50 переходов на 2Кб ★★★★★
Magic numbers 0x3F80, 0x8A ★★★★★
Мертвый код 0x684 ★★★★★
Отсутствие комментариев Весь код ★★★★★
Глобальные флаги R2.7, R3.3 ★★★★★
Дублирование 2 таймера HotStart ★★★★★
Бесконечные ловушки 0xA98 ★★★★★
Ручной стек st -Y ★★★★★
Нет watchdog выключен после инициализации ★★★★★
32-битная математика на 8-битном МК ★★★★★
Нет модульности одна простыня кода ★★★★★
Нет обработки ошибок упал — лежи ★★★★★
ВЕРДИКТ: ЭТО НЕ ПРОШИВКА, ЭТО ГОЛОВНАЯ БОЛЬ НА ЧИПЕ
Если бы этот код сдавали в моей команде, я бы:
Заставил переписать с нуля — проще, чем рефакторить этот монстра
Отправил автора учить матчасть — неделю читать Страуструпа
Сжёг исходники — чтобы никому не досталось
Но! Если аппарат работает и варит — значит, автор достиг цели. Код уродлив, нечитаем, неподдерживаем, но он РАБОТАЕТ. И это единственное оправдание.
ФИНАЛЬНЫЙ АККОРД
Этот код — как старый гараж:
Стены кривые
Крыша течет
Пол в масле
Но машина в нем стоит и не ржавеет
Можно перестроить заново — красиво, ровно, по уму. А можно оставить как есть, потому что "и так сойдет".
Вопрос только в том, сколько времени вы готовы потратить на парковку в этом гараже.
P.S. Автору респект за работоспособность. Но за качество код-ревьюер бы убил. С любовью, ваш злой ревьюер.
О, держите меня семеро! Сейчас будет ПОЛНЫЙ РАЗНОС этой "прошивки". Приготовьте попкорн, сейчас я порву этот код в клочья.
ТОТАЛЬНЫЙ РАЗНОС ПРОШИВКИ 2T150A
Дисклеймер:
Я понимаю, что писал это не профессор Гарварда, а обычный инженер в цеху, у которого горели сроки, начальник стоял над душой, а паяльник дымился. Но как код-ревьюер с 20-летним стажем, я просто обязан устроить разнос. Это терапевтическое — для души.
1. АРХИТЕКТУРА: "КАК ПОСТРОИТЬ ДОМ ИЗ МАКАРОН"
1.1. Отсутствие модульности
Вся прошивка — это один гигантский цикл с 500 переходами. Это не программа, это нейросеть на ассемблере — никто не знает, как она работает, но она работает.
Что должно быть:
Модуль АЦП
Модуль ШИМ
Модуль дисплея
Модуль защиты
Модуль кнопок
Что есть:
text
0x5CE: начало всего
0xA96: конец всего
Между ними: свалка
1.2. Один цикл на всё
Главный цикл делает ВСЁ:
Читает кнопки
Считает ШИМ
Обновляет дисплей
Следит за перегревом
Считает таймеры
Общается с АЦП
Это как если бы шеф-повар сам мыл посуду, рубил мясо, чистил картошку и обслуживал клиентов. В ресторане это провал, в прошивке — адский кошмар для отладки.
1.3. Нет прерываний по существу
Таймеры опрашиваются в цикле, а не работают по прерываниям. Это профанация аппаратных ресурсов. У ATmega8 есть аппаратные таймеры, но автор решил: "Зачем? Я лучше вручную посчитаю до 150 в цикле!"
2. ИМЕНОВАНИЕ: "ШИФРОВКА ДЛЯ ШПИОНОВ"
2.1. Регистры без имени
asm
lds r30, 0x0324
lds r31, 0x0325
Что здесь лежит? А хрен его знает! Надо лезть в начало кода, искать, кто туда писал, восстанавливать цепочку. Это не программирование, это работа следователя.
2.2. Метки-невидимки
asm
0x3C0: ...
0x3C2: ...
0x3C4: ...
Где метки? Где anti_stick_check:? Где hotstart_activate:? Их нет. Только голые адреса. Дизассемблер выдал, что нашел. А автор даже не удосужился назвать блоки.
2.3. Комментарии-издевки
text
; Это сложная функция обработки данных
Спасибо, кэп. Авиакомпания "Понятненько" приветствует вас на борту. "Сложная функция" — это всё, что мы узнали.
3. УПРАВЛЕНИЕ ПОТОКОМ: "ЛАБИРИНТ МИНОТАВРА"
3.1. Прыжки без парашюта
asm
0x3BA: rjmp 0x3BC
0x3BC: rjmp 0x3BE
0x3BE: ret
Зачем? Зачем прыгать на следующую инструкцию? Это олимпийский уровень бессмысленности. Экономия одного байта? Сомневаюсь.
3.2. Условные переходы в никуда
asm
0x7AC: sbrc r3, 7
0x7AE: rcall 0xB94
0x7B0: ldi r30, 0x42
Если бит не установлен — тишина. Просто идем дальше. Никакой обработки, никакого else, ничего. Это как светофор, который загорается зеленым только когда хочет.
3.3. Бесконечные циклы-ловушки
asm
0xA98: rjmp 0xA98
Гениально! Если программа сбойнула — пусть зависнет навечно. А где watchdog? Где восстановление? Где перезагрузка? Нет, просто вечный двигатель на ровном месте.
4. РАБОТА С ПАМЯТЬЮ: "СВАЛКА В ШКАФУ"
4.1. Глобальные переменные вразброс
Переменные разбросаны по SRAM как попало:
0x0304 — ручка тока
0x0306 — подстроечник
0x0324 — ШИМ выход
0x0326 — коэффициент 12В
Никакой структуры. Это как склад, где детали лежат где попало: болты в холодильнике, гайки в духовке, а ключи — в унитазе.
4.2. Ручное управление стеком
asm
st -Y, r23
st -Y, r22
st -Y, r31
st -Y, r30
А если ошибка в инкременте? А если переполнение? А если прерывание случится именно в этот момент? Капец стеку, капец программе.
4.3. Магические адреса
asm
lds r30, 0x0329
lds r31, 0x0335
0x0329 — это что? Максимальная коррекция? А 0x0335 — калибровка? Я должен держать в голове карту памяти из 50 адресов. Это не программирование, это запоминание таблицы Менделеева.
5. АЛГОРИТМЫ: "КОСТЫЛЬ НА КОСТЫЛЕ"
5.1. Anti-Stick в трех лицах
Anti-Stick размазан по:
0x3C0-0x3EC — проверка условий
0xA16-0xA3C — таймер
0x348-0x37E — расчет
Это не модуль, это партизанское движение — отдельные ячейки, которые не знают друг о друге.
5.2. HotStart с братом-близнецом
Два таймера для HotStart (0x0314 и 0x031C). Зачем? Кто-то добавил второй, забыв, что первый уже есть. Типичный код-зомби — живет, хотя уже мертв.
5.3. Температурный фильтр-недоделок
Таймер 0x0322 считает до 1500, чтобы "стабилизировать" температуру. А потом просто показывает мигающее предупреждение. Где гистерезис? Где сглаживание? Где фильтр Калмана? Детский сад какой-то.
6. ОБРАБОТКА ОШИБОК: "ОШИБОК НЕТ, ЕСТЬ НОВЫЕ РЕЖИМЫ"
6.1. Отсутствие защиты от дурака
Что будет, если АЦП сбойнет? Если таймер переполнится? Если стек перетрется? Правильно — неизвестно. Код просто пойдет дальше, пока не зависнет в 0xA98.
6.2. Watchdog не используется
В инициализации (0xBA-0xBE) watchdog включают, а потом выключают. Зачем включали? Для красоты? Это как поставить сигнализацию и выкинуть ключи.
6.3. Нет восстановления после сбоя
Упал — вставай? Нет, упал — лежи (0xA98). Перезагрузка только по питанию. В полевых условиях это смертельный номер — аппарат завис, сварщик матерится, начальник в бешенстве.
7. МАТЕМАТИКА: "ИЗ ПУШКИ ПО ВОРОБЬЯМ"
7.1. 32-битные монстры
text
0xFBE-0x102A: умножение 24x24 (60 инструкций)
0x103E-0x10C6: деление 32/32 (70 инструкций)
Для регулировки тока, где точность 8 бит — ОГРОМНЫЙ ПЕРЕБОР. Это как для подсчета сдачи в магазине вызывать профессора математики с суперкомпьютером.
7.2. Нормализация с 20 сдвигами
Функции 0xDC6, 0xE20, 0xE92 делают нормализацию чисел с десятками сдвигов. На 8 МГц это вечность. Аппарат мог бы варить быстрее, но он считает экспоненты.
7.3. Деление через вычитание
Деление реализовано как последовательное вычитание (0xD3C). Для 16 бит — 16 итераций. Для 32 бит — 32 итерации. А если бы использовали аппаратный умножитель (а его нет, ATmega8 бедненькая), то можно было бы быстрее. Но нет, циклы, циклы, циклы.
8. КАЧЕСТВО КОДА: "ШКОЛЬНИК VS ПРОМИН"
8.1. Нет единого стиля
Где-то ldi, где-то lds, где-то mov. Где-то комментарии есть, где-то нет. Код писали пять разных человек в разное время, не общаясь друг с другом.
8.2. Магические числа повсюду
0x3F80, 0x8A, 0x40, 0x1F — и ни одного пояснения. Через год сам автор не вспомнит, что это за числа. А если автор уволился? Конец игре.
8.3. Отсутствие контроля версий
Судя по структуре, код писали "методом тыка":
добавили новый таймер — старый не убрали
добавили функцию — старую оставили
нашли баг — заклеили пластырем, не разобравшись
9. ТЕСТИРОВАНИЕ: "РАБОТАЕТ? НЕ ТРОГАЙ!"
9.1. Нет тестовых режимов
В коде нет самодиагностики. Если сломается АЦП — аппарат будет варить непонятно чем. Если сломается датчик температуры — защита не сработает.
9.2. Нет калибровки
Калибровочные константы зашиты в ПЗУ. А если разброс компонентов? А если заменить резистор? Придется перепрошивать, потому что подстроечник только на один параметр.
9.3. Нет логов
Упал — ищи причину методом научного тыка. Никаких логов, никакой индикации ошибок. Только "OFF" на дисплее. Что случилось? Перегрев? Anti-Stick? Сбой? Неизвестно.
10. ЭРГОНОМИКА: "УДОБСТВО? НЕ, НЕ СЛЫШАЛ"
10.1. Динамическая индикация с мерцанием
Дисплей обновляется в основном цикле. Если цикл занят вычислениями — дисплей мерцает. Элегантное решение для раздражения сварщика.
10.2. Нет памяти режимов
Выключил-включил — все настройки сброшены. HotStart выключен, режим 12В сброшен. Сварщик должен перенастраивать аппарат каждый раз. Удобно, блин.
10.3. Сон без пробуждения
Режим сна — отключение ШИМ. А как проснуться? "Касание электрода или нажатие кнопки". А если электрод лежит на столе? Кнопку нажимать? Бедный сварщик.
ИТОГОВАЯ ТАБЛИЦА ГРЕХОВ
Нарушение Пример Уровень цинизма
Spaghetti code 50 переходов на 2Кб ★★★★★
Magic numbers 0x3F80, 0x8A ★★★★★
Мертвый код 0x684 ★★★★★
Отсутствие комментариев Весь код ★★★★★
Глобальные флаги R2.7, R3.3 ★★★★★
Дублирование 2 таймера HotStart ★★★★★
Бесконечные ловушки 0xA98 ★★★★★
Ручной стек st -Y ★★★★★
Нет watchdog выключен после инициализации ★★★★★
32-битная математика на 8-битном МК ★★★★★
Нет модульности одна простыня кода ★★★★★
Нет обработки ошибок упал — лежи ★★★★★
ВЕРДИКТ: ЭТО НЕ ПРОШИВКА, ЭТО ГОЛОВНАЯ БОЛЬ НА ЧИПЕ
Если бы этот код сдавали в моей команде, я бы:
Заставил переписать с нуля — проще, чем рефакторить этот монстра
Отправил автора учить матчасть — неделю читать Страуструпа
Сжёг исходники — чтобы никому не досталось
Но! Если аппарат работает и варит — значит, автор достиг цели. Код уродлив, нечитаем, неподдерживаем, но он РАБОТАЕТ. И это единственное оправдание.
ФИНАЛЬНЫЙ АККОРД
Этот код — как старый гараж:
Стены кривые
Крыша течет
Пол в масле
Но машина в нем стоит и не ржавеет
Можно перестроить заново — красиво, ровно, по уму. А можно оставить как есть, потому что "и так сойдет".
Вопрос только в том, сколько времени вы готовы потратить на парковку в этом гараже.
P.S. Автору респект за работоспособность. Но за качество код-ревьюер бы убил. С любовью, ваш злой ревьюер.