Работа с портами пинам. Макросы, X-macro

Обсуждаем контроллеры компании Atmel.
Ответить
Вымогатель припоя
Сообщения: 693
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

Я вот тоже не понимаю, почему автор поднял тему медицины и причем здесь медсестра, которая почему-то вдруг не умеет кнопочки нажимать. Вроде ж обсуждали проблемы макросов и языков программирования.

Языки С и С++ давно уже стали стандартном в программировании микроконтроллеров. С одной стороны они достаточно близки к железу, чтобы непосредственно управлять работой микроконтроллера и распределением ресурсов, с другой стороны обладают достаточной абстракцией для построение общей логики. И это, можно сказать, золотая середина между железом микроконтроллера и логикой программы. Конечно, С и С++ несовершенны, но в мире ничего нет идеального. И по крайней мере у этих языков есть хорошая поддержка, они развиваются, хоть и медленно, а их компиляторы генерируют достаточно эффективный код. Вручную вот вряд ли лучше напишешь.

В противовес этому, автор говорит о каком-то самопальном "языке высокого уровня", построенном на черт знает какой базе (этот секрет он не раскрывает). Судя по скупым упоминаниям, этот "ЯВУ" в какой-то мере напоминает язык Си в альфа-версии до 89-го года или что-то типа того. Хотя до сих пор неизвестно, есть ли у автора какие-либо алгоритмические инструменты типа условного ветвления, циклов и тому подобного, какие есть типы данных. Известно только то, что есть какие-то макросы и какие-то подпрограммы с параметрами, которые позволяют написать Power_Led_On и SetPort одной строчкой. И это выделяется как главное преимущество.

Автор постоянно твердит, что с программой, написанной на языке Си, почему-то "пациент должен умереть". Странно конечно, почему он так решил. Но лично я больше доверяю С/С++ с почти полувековой историей, чем самодельной мутной конструкции от не слишком компетентного автора. Особенно после его фразы, что если программа вышла из одной точки и не пришла в другую точку, то "ясно дело, аппаратный сбой" из-за того, что на плате какой-то винтик был залит маской.
Реклама
Мучитель микросхем
Сообщения: 424
Зарегистрирован: Чт май 07, 2026 00:30:38

Сообщение Zapolyarny »

Rapra писал(а): Чт авг 20, 2026 21:20:37 Конечно, С и С++ несовершенны, но в мире ничего нет идеального.
Rust ;)
Реклама
Вымогатель припоя
Сообщения: 693
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

Тоже неидеален, весьма неидеален. Я бы назвал это пародией на C/C++. Взяли язык С/С++, поменяли в нем синтаксис до неузнаваемости и выплюнули в виде новой концепции. А новая концепция - это переставленная наоборот старая концепция. Как будто через зеркало переписали.
Взять например объявление переменной. В Си: int var; В Rust: let var: i32. В Си переменная по умолчанию является изменяемой, в Rust - неизменяемой.
Объявление функций. Си: int Foo(int a); Rust: fn foo(a: i32) -> i32
Оператор case заменили на match такого же содержания, но с измененным синтаксисом.
В структуре struct поменяли видимость полей по умолчанию.
namespace и using из С++ заменили на mod и use.
Понятный и компактный в C++ принцип организации классов с полями и методами заменили на отдельно структуры struct, отдельно методы impl и отдельно обобщенные/виртуальные методы trait. При этом ООП в полном смысле он не поддерживает.
Ну и так далее, так далее, так далее.
То есть, можно сказать, что Rust - отзеркалированный, разломанный и криво пересобранный С/С++. В результате получили несовместимый с С/С++ язык примерно похожего содержания. При этом он не является ООП-языком, у него ограниченная поддержка, а проблемы тоже отзеркалировались. Так что, по большому счету, Rust - это замена одних проблем другими.
С++ предоставляет больше свободы, которую при желании можно ограничить, а Rust создает видимость "безопасности", которую при желании можно обойти.

Что касается безопасности рассматриваемого здесь авторского "ЯВУ", тут это вопрос вообще даже никак не затрагивался. Хотя автор утверждает что там всё чики-пуки и "поциент не умрет", но никакого реального подтверждения этому нет. Поскольку инструментарий языка - "top secret", "большая страшная тайна", то и проверить ничего нельзя. Есть только голословные утверждения автора, что "на языке Си поциент точно умрет".
AQ29 писал(а): Чт авг 20, 2026 13:27:39
Adrift писал(а): Вс авг 16, 2026 13:34:37 Эзапрещается ли у вас использование не инициализированных переменных? Забыл программист переменную инициализировать, ...
Зачем в ЯВУ сделано возможность работы с необъявленными переменными - непонятно.
Здесь при наборе текста необъявленная переменная и номер строки красится в красный цвет, при компиляции появляется запись в таблице ошибок, hex-файл не создаётся.
Так что необъявленная переменная не пройдёт.
Вас, AQ29, спрашивают не про объявление переменной, а про её инициализацию, то есть, присвоение переменной конкретного значения перед использованием в выражениях. Не секрет, что в железе ОЗУ после сброса содержит случайные значения. И если сделать:

Код: Выделить всё

int a;    // просто объявление переменной без присваивания ей определенного значения
if(a > 10) { } // если эта переменная больше 10, то...
В этом случае программа может работать случайным образом от запуска к запуску. Об этом компилятор и предупреждает при компиляции.
Однако, в языке С/С++, если переменная объявлена в глобальной области видимости, то при компиляции она попадает в секцию .bss и обнуляется стартовым кодом инициализации.
Adrift спрашивал как раз именно про такое поведение неинициализованной переменной, есть ли у AQ29 какая-то защита при компиляции от использования переменной с неопределенным значением.
Мучитель микросхем
Сообщения: 424
Зарегистрирован: Чт май 07, 2026 00:30:38

Сообщение Zapolyarny »

Rapra писал(а): Пт авг 21, 2026 03:08:12 То есть, можно сказать, что Rust - отзеркалированный, разломанный и криво пересобранный С/С++.
Любопытно. У меня нет пока своего мнения о нём, но Ваше очень противоположно тому, что о нём пишут официально. Спасибо, попробую посмотреть на него с Вашей точки зрения, насколько мне хватит мозгов.
---
Мне несколько непонятно, почему решение проблем должно приводить к новому синтаксису. Это кошмар. Предположим, что Rust - идеален, благодаря безопасности, обеспечиваемой на уровне компиляции. Ну и оставили бы синтаксис С, расширив его, если необходимо, или просто оставив зарезервированным более неиспользуемое. А внутри компилятора творили бы всё, что хочется... :dont_know:
Последний раз редактировалось Zapolyarny Пт авг 21, 2026 12:44:38, всего редактировалось 2 раза.
Реклама
Эиком - электронные компоненты и радиодетали
Вымогатель припоя
Сообщения: 593
Зарегистрирован: Вт окт 01, 2024 15:22:33

Сообщение Adrift »

Rapra писал(а): Пт авг 21, 2026 03:08:12Adrift спрашивал как раз именно про такое поведение неинициализованной переменной, есть ли у AQ29 какая-то защита при компиляции от использования переменной с неопределенным значением.
Я больше про всякие MISRA C/C++ говорил, где запрещается использование не инициализированных переменных.

ps. Году так в 2000-м, когда только первые флешевые мк появляться начали, писал я подобный гибридный C+asm компилятор, но для PIC. Последние исходники которые нашел датированы 2004 годом, т.е. больше 20 лет назад я уже посчитал данное направление бесперспективным и пересел на С. И это когда у мк было пару KB флеша всего, компиляторы были несравнимо хуже сегодняшних и аппаратных отладчиков у любителей практически не было, потому было не важно, что моя поделка эту отладку не поддерживала. Но правда у меня в начале 90-х был спектрум и, естественно, я знал бейсик и z80 ассм, но также уже были книги по C, паскалю, аде и форту. А если ничего кроме бейсика и ассма не интересно, то и получаются ситуации когда до последнего держаться за давно устаревшие макро ассемблеры...
Реклама
Вымогатель припоя
Сообщения: 693
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

Zapolyarny писал(а): Пт авг 21, 2026 09:20:42 Любопытно. У меня нет пока своего мнения о нём, но Ваше очень противоположно тому, что о нём пишут официально.
Так получилось, что я познакомился с Rust уже после того, как изучил C++. Поэтому я рассматривал Rust через призму C++. Хотя конечно, для тех, кто переходит с чистого Си на Rust, он кажется чем-то волшебным и удивительным. В целом то да, по сравнению с чистым Си есть немало интересных моментов. Например те же трейты (аналоги виртуальных методов С++) дают интересные фичи. Из С++ взяты области видимости (названо это модулями), что позволяет разделить пространство имен функций и переменных. В чистом Си с этим проблематично. Так же из С++ взяли шаблоны, constexpr-ы (переназвали их макросами), контейнеры (векторы, очереди, списки и тд), сделали аналог try-catch (обработка ошибок). Ну и прочее там. За основу взят С++, разломан, перевернут и пересобран с другими именами и синтаксисом, который частично был надерган из других языков. Например, в циклах и условиях убрали скобки, а малопонятное сишное for(i = 0; i < 10; i++) , которое кстати не шибко то удобопечатаемое пальцами, заменили на словесное описание диапазона.
В части безопасности - это тоже весьма относительно. Поскольку обойти ограничения - можно, нужно только написать "волшебное слово" - и тут же появляются и приведения типов, и небезопасные операции.
В С++ тоже есть некоторая защита от дурака, которая точно так же обходится "волшебными словами".
А если сделать язык со слишком сильной защитой, то на нем нельзя будет решать некоторые специфические задачи, которые нередко встречаются в микроконтроллерах.
Чистый Си имеет наименьшую защиту от дурака. Хуже его только ассемблер (и макроассемблер), в котором можно вообще всё что угодно наворотить. Именно поэтому творение AQ29 я считаю наиболее опасным с точки зрения возможных ошибок при написании кода.

Поэтому, повторюсь, для человека, переходящего с чистого Си, Rust действительно даст вау-эффект. А для того, кто уже знал С++, уже не все так радостно будет. В одном месте - улучшения, в другом - ухудшение.
Что касается зеркального изменения синтаксиса, ну эт чисто маркетинговый ход, как мне кажется. Чтоб подчеркнуть свое отличие от С/С++ и создать впечатление чего-то принципиально нового. В целом, кому как, люди то все разные, кому-то быть может и хочется принципиально непохожего. В основном, так бывает по молодости, когда ищешь что-то, исследуешь. С возрастом это проходит и предпочитаешь проверенный инструмент.
Adrift писал(а): Пт авг 21, 2026 09:25:26 Я больше про всякие MISRA C/C++ говорил, где запрещается использование не инициализированных переменных.
Так компилятор в целом то и так предупредит, если с неинициализованной переменной попытаться проделать что-то, кроме присваивания ей значения. В настройках парсинга ошибок можно переключить с предупреждения ("горчичник") на ошибку ("красная карточка")
Реклама
Вымогатель припоя
Сообщения: 593
Зарегистрирован: Вт окт 01, 2024 15:22:33

Сообщение Adrift »

Rapra писал(а): Пт авг 21, 2026 10:18:16Так компилятор в целом то и так предупредит, если с неинициализованной переменной попытаться проделать что-то, кроме присваивания ей значения.
По умолчанию в gcc, без -Wall, даже предупреждения не будет.
Встал на лапы
Аватара пользователя
Сообщения: 88
Зарегистрирован: Ср дек 06, 2023 23:51:29
Откуда: 48

Сообщение Нефертити »

Заранее прошу меня простить, если кого-то задену, но.
Кмк, нет никакой разницы, на каком ЯВУ, "или высокоуровневом макроассемблере" вы пытаетесь сочинять, спорить и абстрагироваться.
LLM даже на десктопе выполняет поставленные задачи вполне качественно и без нытья.
Погромисты сами себя убили.
Относись к людям так, как хочешь, чтобы они относились к тебе.
Вымогатель припоя
Сообщения: 693
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

"Погромисты" - да.
Однако, как думаете, кто придумал основы LLM? Тоже "погромисты"? :)
В принципе, использование ИИ-ботов для генерации программного кода - так себе затея, по крайней мере по состоянию на сегодняшний день. Да, он генерирует код. Но малограмотный "погромист" всё равно не может понять, насколько правильно он сгенерирован. Отсюда и уверенность, что ИИ-бот сделает за них всё правильно. Да нет, в лучшем случае он замаскирует ошибки так, что "погромист" и не поймет.

Лично я от любопытства тоже прибегал к помощи ИИ-ботов. И понял, что ИИ-бот может даже водить по кругу. Типа, ты ему указываешь, что вот здесь он допустил ошибку и на деле код не работает как надо. Бот такой "ах да, простите, я ошибся, вот правильный вариант". И снова с ошибкой. И так несколько раз, пока снова не приходит к первому варианту. Или же ты ему пишешь свой, правильно работающий вариант, а бот "ой-ой, простите, я ввел вас в заблуждение, вы совершенно правы, ваш вариант является абсолютно верным и показывает высокую вашу квалификацию" :) Вот и проси после этого ИИ-бот написать код.
Встал на лапы
Аватара пользователя
Сообщения: 88
Зарегистрирован: Ср дек 06, 2023 23:51:29
Откуда: 48

Сообщение Нефертити »

Rapra писал(а): Сб авг 22, 2026 04:35:09 Вот и проси после этого ИИ-бот написать код.
Ради интереса, несколько итераций провел с LLM qwen3.8 с промтом "что можно улучшить в этой программе?" с десктопа. На каждой итерации я выбирал, что конкретно "улучшить". Я изначально программу для py писал со словарем, и х.з. что получилось, т.к. ни разу не владею навыками программирования в Python.
Естественно, я конечный результат LLM не тестировал, но, мне кажется, что "его" словарного запаса программирования в Python более, чем достаточно.

И, я не считаю себя каким-либо специалистом в программировании на Python.

Моё творение со словарем.
Спойлер

Код: Выделить всё

import sys
import time
from PyQt6 import QtCore, QtGui
from PyQt6.QtWidgets import QMainWindow, QApplication
from PyQt6.QtSerialPort import QSerialPort, QSerialPortInfo
from PyQt6.QtCore import QIODevice, Qt
from pyqtgraph import PlotWidget, plot
import pyqtgraph as pg
from MainForm import Ui_MainWindow


class MainWindow(QMainWindow, Ui_MainWindow):
    sbdelay = 5000
    strStop = "Stop"
    strEnable = "Run!"

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.set = int(0.0)
        self.tc = float(1.0)
        self.kp = float(0.0)
        self.ki = float(0.0)
        self.kd = float(0.0)
        self.ni = float(0.0)
        self.na = float(0.0)
        self.points = int(0)
        self.maxpoints()

        self.startme = time.time()
        self.tim = list()
        self.msr = list()
        self.spt = list()
        self.out = list()
        self.cleargraph()

        self.serial = QSerialPort(self)
        self.serial.setBaudRate(250000)
        self.serial.setDataBits(QSerialPort.DataBits.Data8)
        self.serial.setParity(QSerialPort.Parity.NoParity)
        self.serial.setStopBits(QSerialPort.StopBits.OneStop)
        self.serial.setFlowControl(QSerialPort.FlowControl.NoFlowControl)
        self.serial.readyRead.connect(self.receive)

        self.btnPort.clicked.connect(self.portclick)
        self.cmbPorts.currentTextChanged.connect(self.descr_port)

        self.pbReset.clicked.connect(self.reset)
        self.pbSaveEEPROM.clicked.connect(self.saveeeprom)
        self.pbLoadEEPROM.clicked.connect(self.loadeeprom)
        self.pbSetKpid.clicked.connect(self.setsettings)
        self.pbRun.clicked.connect(self.outputena)
        self.pbSetPoint.clicked.connect(self.setpoint)

        self.pbSet160.clicked.connect(self.set160)
        self.pbSet180.clicked.connect(self.set180)

        self.controldis()
        self.btnPort.setFocus()

    def maxpoints(self):
        # self.points = int(5.0 * 60 / self.tc)    # 5 min
        self.points = int(10.0 * 60 / 0.25)    # 10 min

    def cleargraph(self):
        self.startme = time.time()
        self.tim.clear()
        self.msr.clear()
        self.spt.clear()
        self.out.clear()
        self.Graph.clear()

    def showgraph(self):
        self.tim = self.tim[-self.points:]
        self.spt = self.spt[-self.points:]
        self.msr = self.msr[-self.points:]
        self.out = self.out[-self.points:]
        #
        self.Graph.clear()
        pen = pg.mkPen(color='green', style=QtCore.Qt.PenStyle.DashLine)
        self.Graph.plot(self.tim, self.spt, pen=pen)
        pen = pg.mkPen(color='red')
        self.Graph.plot(self.tim, self.msr, pen=pen)
        pen = pg.mkPen(color='yellow')
        self.Graph.plot(self.tim, self.out, pen=pen)

    def controldis(self):
        self.spKp.setDisabled(True)
        self.spKi.setDisabled(True)
        self.spKd.setDisabled(True)
        self.spIntMinLim.setDisabled(True)
        self.spIntMaxLim.setDisabled(True)
        self.pbReset.setDisabled(True)
        self.pbSetKpid.setDisabled(True)
        self.cbTcycle.setDisabled(True)
        #
        self.pbRun.setDisabled(True)
        self.sbSetPoint.setDisabled(True)
        self.pbSetPoint.setDisabled(True)
        #
        self.pbSaveEEPROM.setDisabled(True)
        self.pbLoadEEPROM.setDisabled(True)
        #
        self.pbSet160.setDisabled(True)
        self.pbSet180.setDisabled(True)

    def controlena(self):
        self.spKp.setDisabled(False)
        self.spKi.setDisabled(False)
        self.spKd.setDisabled(False)
        self.spIntMinLim.setDisabled(False)
        self.spIntMaxLim.setDisabled(False)
        self.pbReset.setDisabled(False)
        self.pbSetKpid.setDisabled(False)
        self.cbTcycle.setDisabled(False)
        #
        self.pbRun.setDisabled(False)
        self.sbSetPoint.setDisabled(False)
        self.pbSetPoint.setDisabled(False)
        #
        self.pbSaveEEPROM.setDisabled(False)
        self.pbLoadEEPROM.setDisabled(False)
        #
        self.pbSet160.setDisabled(False)
        self.pbSet180.setDisabled(False)

    def reset(self):
        self.send('Rs')

    def saveeeprom(self):
        self.send('Sv')

    def loadeeprom(self):
        self.send('Ld;G')

    def setsettings(self):
        cmd = str('')
        tc = round(float(self.cbTcycle.currentText()), 2)
        if tc != self.tc:
            cmd += f'Tc={tc};'
        kp = round(self.spKp.value(), 3)
        if kp != self.kp:
            cmd += f'Kp={kp};'
        ki = round(self.spKi.value(), 3)
        if ki != self.ki:
            cmd += f'Ki={ki};'
        kd = round(self.spKd.value(), 3)
        if kd != self.kd:
            cmd += f'Kd={kd};'
        ni = self.spIntMinLim.value()
        if ni != self.ni:
            cmd += f'Ni={ni};'
        na = self.spIntMaxLim.value()
        if na != self.na:
            cmd += f'Na={na};'
        if len(cmd) > 0:
            self.send(cmd)

    def setpoint(self):
        sp = self.sbSetPoint.value()
        self.send(f"St={sp}")

    def set160(self):
        self.send(f"St=160")

    def set180(self):
        self.send(f"St=180")

    def outputena(self):
        if self.pbRun.text() == MainWindow.strStop:
            self.send("En=0")
        else:
            self.send("En=1")

    @QtCore.pyqtSlot()
    def descr_port(self):
        port = QSerialPortInfo(self.cmbPorts.currentText())
        self.statusbar.showMessage(port.description(), MainWindow.sbdelay)

    @QtCore.pyqtSlot()
    def show(self):
        super().show()
        self.getports()

    def getports(self):
        self.cmbPorts.clear()
        self.cmbPorts.setDisabled(False)
        ports = QSerialPortInfo().availablePorts()
        avlist = [port.portName() for port in ports]
        if len(avlist) > 0:
            self.cmbPorts.addItems(avlist)
            self.btnPort.setText("Connect")
        else:
            self.btnPort.setText("Refresh")

    @QtCore.pyqtSlot()
    def portclick(self):
        if self.serial.isOpen():
            self.serial.close()
            self.getports()
            self.statusbar.showMessage("Disconnected", MainWindow.sbdelay)
            self.controldis()
        else:
            ports = QSerialPortInfo().availablePorts()
            if self.cmbPorts.currentText() in [port.portName() for port in ports]:
                self.serial.setPortName(self.cmbPorts.currentText())
                if self.serial.open(QIODevice.OpenModeFlag.ReadWrite):
                    # reset
                    self.serial.setDataTerminalReady(False)
                    time.sleep(0.5)
                    self.serial.setDataTerminalReady(True)
                    #
                    self.serial.clear()
                    self.btnPort.setText("Disconnect")
                    self.cmbPorts.setDisabled(True)
                    self.cleargraph()
                else:
                    self.getports()
                    self.statusbar.showMessage(self.serial.errorString(), MainWindow.sbdelay)
            else:
                self.getports()

    @QtCore.pyqtSlot()
    def onreceive(self, data: dict):
        if 'Tc' in data:    # Tcycle
            self.tc = data['Tc']
            # Logger every 0.25 sec fixed
            # self.maxpoints()
            self.cbTcycle.setCurrentText('{:.2f}'.format(self.tc))
        if 'St' in data:    # SetPoint
            self.set = round(data['St'])
            self.sbSetPoint.setValue(int(data['St']))
        #
        if 'Ms' in data and 'Ot' in data:   # Measurement & out
            self.edMeasurement.setText('{:.2f}'.format(data['Ms']))
            self.edPidOutput.setText('{:.2f}'.format(data['Ot']))
            #
            self.tim.append(time.time() - self.startme)
            self.msr.append(data['Ms'])
            self.out.append(data['Ot'])
            self.spt.append(self.set)
            self.showgraph()
        #
        if 'Kp' in data:    # Kp
            self.kp = data['Kp']
            self.spKp.setValue(data['Kp'])
        if 'Ki' in data:    # Ki
            self.ki = data['Ki']
            self.spKi.setValue(data['Ki'])
        if 'Kd' in data:    # Kd
            self.kd = data['Kd']
            self.spKd.setValue(data['Kd'])
        #
        if 'Ni' in data:    # limMinInt
            self.ni = data['Ni']
            self.spIntMinLim.setValue(int(data['Ni']))
        if 'Na' in data:    # limMaxInt
            self.na = data['Na']
            self.spIntMaxLim.setValue(int(data['Na']))
        #
        if 'Pr' in data:    # proportional
            self.edProportional.setText('{:.2f}'.format(data['Pr']))
        if 'Nt' in data:    # integrator
            self.edIntegrator.setText('{:.2f}'.format(data['Nt']))
        if 'Df' in data:    # differentiator
            self.edDifferentiator.setText('{:.2f}'.format(data['Df']))
        #
        if 'En' in data:    # OutputEna
            if data['En'] > 0.0:
                self.pbRun.setText(MainWindow.strStop)
            else:
                self.pbRun.setText(MainWindow.strEnable)

    @QtCore.pyqtSlot()
    def receive(self):
        while self.serial.canReadLine():
            text = self.serial.readLine().data().decode(encoding='ascii').strip(" \r\n")
            # print(text)
            if 'Start' in text:
                self.statusbar.showMessage("Connected", MainWindow.sbdelay)
                # On connected get current params
                self.send("G")
                self.controlena()
            elif 'Fail' in text:
                self.statusbar.showMessage("Unable to communicate with MAX6675", MainWindow.sbdelay)
            elif 'Open' in text:
                self.statusbar.showMessage("Thermocouple input is open", MainWindow.sbdelay)
            elif 'Error' in text:
                self.statusbar.showMessage("Error in request", MainWindow.sbdelay)
            elif len(text):
                terms = list()
                if ";" in text:
                    terms = text.split(";")
                elif "," in text:
                    terms = text.split(",")
                elif "\t" in text:
                    terms = text.split("\t")
                elif "=" in text:
                    terms = [text]
                if len(terms):
                    data = dict()
                    for term in terms:
                        word = term.split("=", 1)
                        if len(word) == 2:
                            try:
                                data[word[0]] = float(word[1])
                            finally:
                                pass
                    if len(data):
                        self.onreceive(data)

    @QtCore.pyqtSlot()
    def send(self, data: str):
        # print(data)
        if data[-1] != ";" and data[-1] != "\n":
            data += "\n"
        self.serial.write(data.encode(encoding='ascii'))
        self.serial.flush()


def main():
    app = QApplication(sys.argv)
    app.setStyle("fusion")
    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()
Предложенное LLM.
Спойлер

Код: Выделить всё

import sys
import time
import math
import logging
from enum import Enum
from dataclasses import dataclass
from typing import Optional, Dict, Any, List, Tuple
import queue

import numpy as np
from PyQt6 import QtCore, QtGui
from PyQt6.QtWidgets import QMainWindow, QApplication, QMessageBox
from PyQt6.QtSerialPort import QSerialPort, QSerialPortInfo
from PyQt6.QtCore import QIODevice, Qt, QTimer, QThread, pyqtSignal, pyqtSlot
import pyqtgraph as pg

# --- Настройка логирования ---
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# --- Константы и модели данных ---

class CommandType(Enum):
    """Перечисление команд для контроллера."""
    RESET = "Rs"
    SAVE_EEPROM = "Sv"
    LOAD_EEPROM = "Ld;G"
    GET_ALL = "G"
    ENABLE_ON = "En=1"
    ENABLE_OFF = "En=0"

@dataclass
class PidState:
    """Хранилище состояния PID-регулятора."""
    tc: float = 1.0
    kp: float = 0.0
    ki: float = 0.0
    kd: float = 0.0
    ni: int = 0
    na: int = 0

# --- Класс кольцевого буфера для оптимизации графика ---

class CircularBuffer:
    """
    Кольцевой буфер на основе NumPy массива.
    Позволяет эффективно добавлять данные и получать текущее состояние без копирования всего массива.
    """
    def __init__(self, size: int):
        self.size = size
        self.buffer = np.zeros(size, dtype=np.float32)
        self.write_index = 0
        self.count = 0

    def append(self, value: float):
        """Добавляет значение в буфер."""
        self.buffer[self.write_index] = value
        self.write_index += 1
        if self.write_index >= self.size:
            self.write_index = 0
        
        if self.count < self.size:
            self.count += 1

    def clear(self):
        """Очищает буфер."""
        self.buffer.fill(0)
        self.write_index = 0
        self.count = 0

    def get_data(self) -> Tuple[np.ndarray, np.ndarray]:
        """
        Возвращает (x_data, y_data) для отрисовки.
        Данные возвращаются в хронологическом порядке (от старых к новым).
        """
        if self.count == 0:
            return np.array([], dtype=np.float32), np.array([], dtype=np.float32)

        # Если буфер еще не заполнен полностью, данные идут от 0 до count-1
        if self.count < self.size:
            x = np.arange(self.count, dtype=np.float32) * 0.25 
            y = self.buffer[:self.count]
        else:
            # Буфер заполнен. Данные начинаются с write_index и идут до конца, затем с начала до write_index-1
            x = np.arange(self.size, dtype=np.float32) * 0.25
            # Переставляем массив так, чтобы старые данные были в начале
            y = np.concatenate((self.buffer[self.write_index:], self.buffer[:self.write_index]))
            
        return x, y

# --- Слой логики (Business Logic) ---

class ControllerManager:
    """Обработка бизнес-логики и состояния контроллера."""
    
    # Карта соответствия ключей протокола атрибутам PidState и их типам
    PARAM_TYPES = {
        'Tc': ('tc', float),
        'Kp': ('kp', float),
        'Ki': ('ki', float),
        'Kd': ('kd', float),
        'Ni': ('ni', int),
        'Na': ('na', int)
    }

    def __init__(self):
        self.state = PidState()
        self.set_point: int = 0

    def update_from_dict(self, data: Dict[str, Any]) -> List[Tuple[str, float]]:
        """
        Обновляет состояние контроллера из словаря данных.
        Возвращает список обновленных параметров для UI.
        """
        updates = []
        
        for key, val in data.items():
            # Обработка SetPoint отдельно
            if key == 'St':
                try:
                    self.set_point = int(round(float(val)))
                except (ValueError, TypeError):
                    logger.warning(f"Invalid SetPoint value: {val}")
                continue

            # Обработка PID параметров
            if key in self.PARAM_TYPES:
                attr_name, target_type = self.PARAM_TYPES[key]
                try:
                    converted_val = target_type(val)
                    setattr(self.state, attr_name, converted_val)
                    updates.append((key, converted_val))
                except (ValueError, TypeError) as e:
                    logger.warning(f"Type conversion error for {key}={val}: {e}")
            else:
                pass 

        return updates

# --- Слой протокола (Protocol Layer) ---

class ProtocolParser:
    """Изолированная логика парсинга строк устройства."""
    
    @staticmethod
    def parse_line(line: str) -> Dict[str, float]:
        data = {}
        
        if "=" not in line:
            return data

        sep = None
        for char in [';', ',', '\t']:
            if char in line:
                sep = char
                break
        
        terms = line.split(sep) if sep else [line]

        for term in terms:
            parts = term.split("=", 1)
            if len(parts) == 2:
                key = parts[0].strip()
                val_str = parts[1].strip()
                
                if not key or not val_str:
                    continue

                try:
                    data[key] = float(val_str)
                except ValueError:
                    logger.debug(f"Skipping non-numeric value for key '{key}': {val_str}")
        return data

    @staticmethod
    def interpret_status(text: str) -> Optional[str]:
        mapping = {
            'Start': "Connected",
            'Fail': "Communication Fail",
            'Open': "Thermocouple Open",
            'Error': f"Device Error: {text}"
        }
        
        for key, msg in mapping.items():
            if key in text: 
                return msg
        return None

# --- Слой связи (Communication Layer) ---

class SerialWorker(QThread):
    """
    Поток для работы с последовательным портом.
    Использует блокирующее чтение с таймаутом внутри потока для надежности,
    эмитируя сигналы в главный поток при получении данных.
    Команды отправляются через очередь для обеспечения потокобезопасности.
    """
    data_received = pyqtSignal(dict)
    error_occurred = pyqtSignal(str)
    status_message = pyqtSignal(str)

    def __init__(self, port_name: str, baud_rate: int = 250000):
        super().__init__()
        self.port_name = port_name
        self.baud_rate = baud_rate
        self.serial = QSerialPort()
        self._is_running = True
        self._buffer = bytearray()
        # Очередь команд для потокобезопасной отправки
        self.cmd_queue: queue.Queue[str] = queue.Queue()

    def run(self):
        """Основной цикл потока."""
        self.serial.setPortName(self.port_name)
        self.serial.setBaudRate(self.baud_rate)
        self.serial.setDataBits(QSerialPort.DataBits.Data8)
        self.serial.setParity(QSerialPort.Parity.NoParity)
        self.serial.setStopBits(QSerialPort.StopBits.OneStop)
        self.serial.setFlowControl(QSerialPort.FlowControl.NoFlowControl)

        if not self.serial.open(QIODevice.OpenModeFlag.ReadWrite):
            err_msg = f"Failed to open port: {self.serial.errorString()}"
            logger.error(err_msg)
            self.error_occurred.emit(err_msg)
            return

        logger.info(f"Port {self.port_name} opened.")
        
        # Инициализация DTR (Reset контроллера)
        self.serial.setDataTerminalReady(False)
        QThread.msleep(500)
        self.serial.setDataTerminalReady(True)
        self.serial.clear()

        while self._is_running:
            # 1. Отправляем накопленные команды из очереди
            try:
                while not self.cmd_queue.empty():
                    cmd = self.cmd_queue.get_nowait()
                    if self.serial.isOpen():
                        self.serial.write(cmd.encode('ascii'))
                        self.serial.flush()
            except queue.Empty:
                pass

            # 2. Читаем данные из порта
            if self.serial.waitForReadyRead(10): # Таймаут 10 мс
                new_data = bytes(self.serial.readAll())
                if new_data:
                    self._process_data(new_data)
            
            # Небольшая задержка для контроля остановки, если waitForReadyRead возвращает False быстро
            QThread.msleep(1)

        self.serial.close()
        logger.info("Port closed.")

    def _process_data(self, new_data: bytes):
        """Обрабатывает входящие байты, накапливая их до конца строки."""
        self._buffer.extend(new_data)
        
        # Защита от переполнения буфера (например, 1024 байта)
        if len(self._buffer) > 1024:
            logger.warning("Buffer overflow detected, clearing buffer.")
            self._buffer.clear()
            return

        # Ищем полные строки (оканчивающиеся \n или \r\n)
        while b'\n' in self._buffer or b'\r' in self._buffer:
            idx = -1
            for i, byte in enumerate(self._buffer):
                if byte == 0x0A or byte == 0x0D: # \n or \r
                    idx = i
                    break
            
            if idx == -1:
                break

            line_bytes = self._buffer[:idx]
            del self._buffer[:idx+1]
            
            try:
                text = line_bytes.decode('ascii').strip()
                if not text:
                    continue

                status = ProtocolParser.interpret_status(text)
                if status:
                    # Эмитим сигнал в главный поток (thread-safe)
                    self.status_message.emit(status)
                else:
                    data = ProtocolParser.parse_line(text)
                    if data: 
                        self.data_received.emit(data)
                        
            except UnicodeDecodeError:
                logger.warning("Non-ASCII data received, skipping.")

    def send_command(self, cmd: str):
        """
        Отправляет команду в порт через очередь.
        Этот метод можно вызывать из любого потока (обычно из GUI).
        """
        if not self._is_running:
            return
            
        if not cmd.endswith(("\n", ";")):
            cmd += "\n"
            
        # Кладем команду в очередь, поток run() обработает ее
        self.cmd_queue.put(cmd)

    def stop(self):
        """
        Останавливает поток и закрывает порт.
        Устанавливает флаг _is_running в False и ожидает завершения цикла run().
        """
        self._is_running = False
        
        # Ожидаем, пока поток не завершится сам (максимум 2 секунды)
        # Метод wait() блокирует текущий поток до завершения целевого потока
        if not self.wait(2000):
            logger.warning("SerialWorker did not finish cleanly within timeout. The thread may still be active.")
            # Важно: НЕ вызываем self.terminate() здесь, так как это небезопасно.
            # В реальном приложении лучше логировать эту ошибку и проверять состояние позже,
            # или убедиться, что цикл run() гарантированно прерывается по флагу _is_running.

# --- Слой представления (Presentation Layer) ---

try:
    from MainForm import Ui_MainWindow
except ImportError:
    class Ui_MainWindow:
        def setupUi(self, MainWindow): pass

class MainWindow(QMainWindow, Ui_MainWindow):
    sbdelay = 5000
    strStop = "Stop"
    strEnable = "Run!"

    def __init__(self, controller: ControllerManager):
        super().__init__()
        self.setupUi(self)
        
        self.controller = controller
        
        # Настройка кольцевых буферов (4 минуты при 4 Гц = 960 точек)
        self.max_points = int(10.0 * 60 / 0.25) 
        self.buf_time = CircularBuffer(self.max_points)
        self.buf_msr = CircularBuffer(self.max_points)
        self.buf_spt = CircularBuffer(self.max_points)
        self.buf_out = CircularBuffer(self.max_points)
        
        self.startme = time.time()

        # Маппинг виджетов
        self.widget_map = {
            'Tc': self.cbTcycle,
            'Kp': self.spKp, 
            'Ki': self.spKi, 
            'Kd': self.spKd,
            'Ni': None, 
            'Na': None 
        }

        self._init_graph_items()
        self.controldis()

        # Поток работы с портом
        self.worker: Optional[SerialWorker] = None
        
        # Сигналы и слоты
        self.btnPort.clicked.connect(self.portclick)
        self.cmbPorts.currentTextChanged.connect(self.descr_port)
        self.pbReset.clicked.connect(lambda: self.send_cmd(CommandType.RESET.value))
        self.pbSaveEEPROM.clicked.connect(lambda: self.send_cmd(CommandType.SAVE_EEPROM.value))
        self.pbLoadEEPROM.clicked.connect(lambda: self.send_cmd(CommandType.LOAD_EEPROM.value))
        self.pbSetKpid.clicked.connect(self.setsettings)
        self.pbRun.clicked.connect(self.outputena)
        self.pbSetPoint.clicked.connect(self.setpoint)
        self.pbSet160.clicked.connect(lambda: self.send_cmd("St=160"))
        self.pbSet180.clicked.connect(lambda: self.send_cmd("St=180"))

    def _init_graph_items(self):
        """Инициализация элементов графика."""
        self.Graph.clear()
        self.graph_item_spt = self.Graph.plot(pen=pg.mkPen(color='green', style=Qt.PenStyle.DashLine), name="SetPoint")
        self.graph_item_msr = self.Graph.plot(pen=pg.mkPen(color='red'), name="Measurement")
        self.graph_item_out = self.Graph.plot(pen=pg.mkPen(color='yellow'), name="Output")

    def cleargraph(self):
        """Очистка буферов и графика."""
        self.startme = time.time()
        self.buf_time.clear()
        self.buf_msr.clear()
        self.buf_spt.clear()
        self.buf_out.clear()

        if hasattr(self, 'graph_item_spt'):
            self.graph_item_spt.setData([], [])
            self.graph_item_msr.setData([], [])
            self.graph_item_out.setData([], [])

    def _update_graph_from_buffers(self):
        """Обновляет график из кольцевых буферов."""
        # Получаем данные времени (X) и значений (Y)
        t_data, _ = self.buf_time.get_data()
        
        if len(t_data) == 0:
            return

        _, msr_data = self.buf_msr.get_data()
        _, spt_data = self.buf_spt.get_data()
        _, out_data = self.buf_out.get_data()

        # Обновляем виджеты pyqtgraph
        self.graph_item_spt.setData(t_data, spt_data)
        self.graph_item_msr.setData(t_data, msr_data)
        self.graph_item_out.setData(t_data, out_data)

    def send_cmd(self, cmd: str):
        """Отправка команды контроллеру."""
        if self.worker and self.worker.isRunning():
            self.worker.send_command(cmd)
        else:
            logger.warning("Attempted to send command when port is not connected.")

    def setpoint(self):
        sp = self.sbSetPoint.value()
        self.send_cmd(f"St={sp}")

    def outputena(self):
        cmd = CommandType.ENABLE_ON if self.pbRun.text() == self.strStop else CommandType.ENABLE_OFF
        self.send_cmd(cmd)

    @pyqtSlot()
    def descr_port(self):
        port_name = self.cmbPorts.currentText()
        if port_name:
            info = QSerialPortInfo(port_name)
            if info.isValid():
                self.statusbar.showMessage(info.description(), self.sbdelay)

    @pyqtSlot()
    def show(self):
        super().show()
        self.getports()

    def getports(self):
        self.cmbPorts.clear()
        ports = QSerialPortInfo.availablePorts()
        if ports:
            self.cmbPorts.addItems([p.portName() for p in ports])
            self.btnPort.setText("Connect")
            self.cmbPorts.setDisabled(False)
        else:
            self.btnPort.setText("Refresh")
            self.cmbPorts.setDisabled(True)

    @pyqtSlot()
    def portclick(self):
        if self.worker and self.worker.isRunning():
            logger.info("Disconnecting...")
            self.worker.stop()
            
            # После вызова stop(), мы уже ждали внутри stop().
            # Теперь проверяем, завершился ли он.
            if not self.worker.isFinished():
                 # Если он все еще не закончился (редкий случай), можно дать еще немного времени или просто логировать
                 logger.warning("Worker thread is still running after stop() call.")
            
            self.worker.deleteLater()
            self.worker = None
            self.getports()
            self.statusbar.showMessage("Disconnected", self.sbdelay)
            self.controldis()
        else:
            selected_port = self.cmbPorts.currentText()
            if selected_port:
                try:
                    self.worker = SerialWorker(selected_port)
                    # Подключаем сигналы ДО запуска потока, чтобы не пропустить первые данные
                    self.worker.data_received.connect(self.onreceive)
                    self.worker.error_occurred.connect(lambda e: self.statusbar.showMessage(e, self.sbdelay))
                    self.worker.status_message.connect(lambda m: self.statusbar.showMessage(m, self.sbdelay))
                    
                    self.worker.start()
                    
                    if not self.worker.isRunning():
                        raise RuntimeError("Failed to start worker thread")

                    self.btnPort.setText("Disconnect")
                    self.cmbPorts.setDisabled(True)
                    self.cleargraph()
                except Exception as e:
                    logger.error(f"Failed to start worker: {e}")
                    QMessageBox.critical(self, "Error", f"Could not connect to port: {str(e)}")

    @pyqtSlot(dict)
    def onreceive(self, data: Dict[str, float]):
        """Обработка данных через ControllerManager с обновлением UI."""
        updates = self.controller.update_from_dict(data)
        
        # Обновление PID параметров в UI
        for key, val in updates:
            widget = self.widget_map.get(key)
            if widget:
                if hasattr(widget, 'setCurrentText'): 
                    widget.setCurrentText(f'{val:.2f}')
                elif hasattr(widget, 'setValue'):
                    try:
                        if isinstance(val, int):
                            widget.setValue(int(val))
                        else:
                            widget.setValue(float(val))
                    except (ValueError, TypeError):
                        pass

        # Обработка телеметрии
        if 'Ms' in data and 'Ot' in data:
            self._handle_telemetry(data['Ms'], data['Ot'])

        # Отладочные данные
        debug_map = {'Pr': 'edProportional', 'Nt': 'edIntegrator', 'Df': 'edDifferentiator'}
        for key, widget_name in debug_map.items():
            if key in data:
                widget = getattr(self, widget_name, None)
                if widget:
                    try:
                        widget.setText(f'{data[key]:.2f}')
                    except (ValueError, TypeError):
                        pass

        # Статус выхода
        if 'En' in data:
            self.pbRun.setText(self.strStop if data['En'] > 0 else self.strEnable)

    def _handle_telemetry(self, ms_val: float, ot_val: float):
        """Обработка текущих значений измерения и выхода."""
        try:
            self.edMeasurement.setText(f'{ms_val:.2f}')
            self.edPidOutput.setText(f'{ot_val:.2f}')
            
            current_time = time.time() - self.startme
            
            # Добавляем данные в кольцевые буферы
            self.buf_time.append(current_time)
            self.buf_msr.append(ms_val)
            self.buf_out.append(ot_val)
            self.buf_spt.append(float(self.controller.set_point))

            # Обновляем график ТОЛЬКО сейчас, когда пришли новые данные
            self._update_graph_from_buffers()
            
        except Exception as e:
            logger.error(f"Error handling telemetry: {e}")

    def setsettings(self):
        cmd_parts = []
        
        params = [
            ('Tc', 'tc', float, round, 2),
            ('Kp', 'kp', float, round, 3),
            ('Ki', 'ki', float, round, 3),
            ('Kd', 'kd', float, round, 3),
            ('Ni', 'ni', int, int, 0),
            ('Na', 'na', int, int, 0)
        ]
        
        for key, attr, dtype, func, prec in params:
            widget = self.widget_map.get(key)
            if not widget:
                continue

            try:
                raw_val = widget.value()
                
                # Проверка на отрицательные значения для PID коэффициентов
                if dtype == float and key in ['Kp', 'Ki', 'Kd'] and raw_val < 0:
                    logger.warning(f"Invalid negative value for {key}: {raw_val}")
                    continue
                
                # Проверка на NaN/Inf
                val = func(dtype(raw_val), prec) if dtype != int else int(raw_val)
                
                if not math.isfinite(val):
                    logger.warning(f"Non-finite value for {key}: {val}")
                    continue

                current_val = getattr(self.controller.state, attr)
                
                if not math.isclose(val, current_val, abs_tol=1e-9):
                    cmd_parts.append(f'{key}={val}')
                    
            except Exception as e:
                logger.error(f"Error processing param {key}: {e}")

        if cmd_parts:
            self.send_cmd(";".join(cmd_parts))

    def controldis(self, disabled=True):
        controls = [
            self.spKp, self.spKi, self.spKd, self.spIntMinLim, self.spIntMaxLim,
            self.pbReset, self.pbSetKpid, self.cbTcycle,
            self.pbRun, self.sbSetPoint, self.pbSetPoint,
            self.pbSaveEEPROM, self.pbLoadEEPROM,
            self.pbSet160, self.pbSet180
        ]
        for ctrl in controls:
            if hasattr(ctrl, 'setDisabled'): 
                ctrl.setDisabled(disabled)

    def closeEvent(self, event):
        logger.info("Closing application...")
        
        # Останавливаем поток порта и ждем его завершения
        if self.worker and self.worker.isRunning():
            self.worker.stop()
            
        super().closeEvent(event)

def main():
    app = QApplication(sys.argv)
    
    try:
        app.setStyle("fusion")
    except Exception:
        pass
        
    controller = ControllerManager()
    window = MainWindow(controller)
    window.show()
    
    sys.exit(app.exec())

if __name__ == '__main__':
    main()
Относись к людям так, как хочешь, чтобы они относились к тебе.
Вымогатель припоя
Сообщения: 693
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

Нефертити писал(а): Пн авг 24, 2026 18:39:06 ни разу не владею навыками программирования в Python.
Потому и не можете реально оценить правильность. Об этом я и говорил выше. Понять, где ИИ нагенерил ошибок и неточностей, может только тот, кто сам хорошо разбирается в этом.
С таким же запросом "что можно улучшить" или "оцени правильность" я тоже гонял разных ИИ-ботов. Зачастую, их советы были ужасными, основанными на некоторых общих представлениях, а не на конкретном применении. А когда начинаешь направлять их в нужное русло, они в большинстве приходят к выводу что "ваш код - пример профессионального решения описанной вами задачи и не требует улучшений".
Встал на лапы
Аватара пользователя
Сообщения: 88
Зарегистрирован: Ср дек 06, 2023 23:51:29
Откуда: 48

Сообщение Нефертити »

Ну, раз Вы "не можете реально оценить правильность" кода, зачем этот код оценивать с Вашей точки зрения?
Относись к людям так, как хочешь, чтобы они относились к тебе.
Вымогатель припоя
Сообщения: 693
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

Тут пример как с высшей математикой - если не понимаете её, то перед вами просто набор символов, который вы принимаете на веру.
Встал на лапы
Аватара пользователя
Сообщения: 88
Зарегистрирован: Ср дек 06, 2023 23:51:29
Откуда: 48

Сообщение Нефертити »

Я рад, что Вы восприняли мои аргументы не как оскорбление личности. Спасибо!

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

Вернуться в «AVR»