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

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

Сообщение Rapra »

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

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

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

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

Сообщение Zapolyarny »

Rapra писал(а): Чт авг 20, 2026 21:20:37 Конечно, С и С++ несовершенны, но в мире ничего нет идеального.
Rust ;)
Реклама
Опытный кот
Сообщения: 700
Зарегистрирован: Пн фев 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 какая-то защита при компиляции от использования переменной с неопределенным значением.
Мучитель микросхем
Сообщения: 434
Зарегистрирован: Чт май 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, паскалю, аде и форту. А если ничего кроме бейсика и ассма не интересно, то и получаются ситуации когда до последнего держаться за давно устаревшие макро ассемблеры...
Реклама
Опытный кот
Сообщения: 700
Зарегистрирован: Пн фев 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, даже предупреждения не будет.
Встал на лапы
Аватара пользователя
Сообщения: 91
Зарегистрирован: Ср дек 06, 2023 23:51:29
Откуда: 48

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

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

Сообщение Rapra »

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

Лично я от любопытства тоже прибегал к помощи ИИ-ботов. И понял, что ИИ-бот может даже водить по кругу. Типа, ты ему указываешь, что вот здесь он допустил ошибку и на деле код не работает как надо. Бот такой "ах да, простите, я ошибся, вот правильный вариант". И снова с ошибкой. И так несколько раз, пока снова не приходит к первому варианту. Или же ты ему пишешь свой, правильно работающий вариант, а бот "ой-ой, простите, я ввел вас в заблуждение, вы совершенно правы, ваш вариант является абсолютно верным и показывает высокую вашу квалификацию" :) Вот и проси после этого ИИ-бот написать код.
Встал на лапы
Аватара пользователя
Сообщения: 91
Зарегистрирован: Ср дек 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()
Относись к людям так, как хочешь, чтобы они относились к тебе.
Опытный кот
Сообщения: 700
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

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

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

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

Сообщение Rapra »

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

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

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

Ибо, тут всякие ебанутые старички и мышьяки, они идиоты.
Относись к людям так, как хочешь, чтобы они относились к тебе.
Встал на лапы
Аватара пользователя
Сообщения: 91
Зарегистрирован: Ср дек 06, 2023 23:51:29
Откуда: 48

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

Псы! Фас!
Псы вшивые.
Относись к людям так, как хочешь, чтобы они относились к тебе.
Встал на лапы
Аватара пользователя
Сообщения: 91
Зарегистрирован: Ср дек 06, 2023 23:51:29
Откуда: 48

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

Самое главное, с самого начала определить для LLM роль: "Ты эксперт в программировании на языке "C" для 8-битных микроконтроллеров семейства AVR типа ATmega8 с использованием компилятора avr-gcc. Твоя задача - разрабатывать программы, анализировать исходные тексты и отвечать на вопросы, связанные с их программированием, используя правильную терминологию. Отвечай на русском языке." - это пример, как я начинаю диалог с LLM.
Если я в чем-то не прав, поправьте меня.
Буду очень рад услышать единомышленников.
Относись к людям так, как хочешь, чтобы они относились к тебе.
Опытный кот
Сообщения: 700
Зарегистрирован: Пн фев 16, 2026 17:30:02

Сообщение Rapra »

Последнего наезда про псов чет не понял. Нормально же общались, а потом началось... Вроде не пятница же.
Нефертити писал(а): Пн авг 24, 2026 23:42:22 определить для LLM роль: "Ты эксперт в программировании
...и бот в этом случае, как НАЗНАЧЕННЫЙ иксперд, нагенерит всякой ерунды. Только что же объяснял, как и почему.
Прорезались зубы
Сообщения: 246
Зарегистрирован: Сб июл 30, 2011 21:00:24

Сообщение AQ29 »

Rapra писал(а): Чт авг 20, 2026 14:31:53
AQ29 писал(а): Чт авг 20, 2026 13:27:39 похоже, вы плохо представляете, что такое опытная эксплуатация медицинского аппарата.
Полагаю, вы это вообще не представляете. И слава богу. Иначе у вас поцыенты дохли бы как мухи :) Не запустился бы какой-нить генератор во время "опытной эксплуатации", и ага. Memeto mori. Моментом в море :)

"Плохо, когда не знаешь, да еще и забыл"…
Судя по вашему посту о большом количестве отказов при опытной эксплуатации медицинского аппарата, вы не знаете, как это проводится.
Опытная эксплуатация – финишный этап разработки, а уйма отказов – это где-то начало.
С такими отказами, как вы пишите, в нормальной организации аппарат не дойдёт до опытной эксплуатации. Может, пишите про какую-нибудь шарашкину контору, но её не должны допускать к медицинской технике.
Да и разработчик в здравом уме не отправит аппарат с такими проблемами на официальные испытания, не говоря уже об опытной эксплуатации.
Я не испытатель, почему я должен помнить про все испытания, тем более, много лет спустя.
Их полно, вот примерный список:
ПСИ – приёмо-сдаточные, квалификационные, периодические, типовые,
сертификационные, предъявительские, предварительные, приёмочные,
испытания на надёжность, наработка на отказ.
Перед отправкой на опытную эксплуатацию аппарат должен пройти многие из этого списка.
Для сведения, как проводится опытная эксплуатация. У меня при опытной эксплуатации в течение около года не было ни одного отказа из-за ошибок схемотехники и программ МК.
Rapra писал(а): Чт авг 20, 2026 14:31:53 Совершенно НЕВЕРНЫЙ подход. Абсолютно неверный. Программист - это программист, схемотехник - это схемотехник. Потому что кроме микроконтроллера бывают еще и аналоговые цепи и цепи питания, и в разводке платы бывает множество нюансов, так же как и в программном коде. Чисто программист не обязан владеть всей тематикой. Так же как и схемотехник не должен знать программирование. "За двумя зайцами погонишься - ..." Понятно, да?
Я привёл пример отказа, это реальный случай.
Как программист и электронщик будут определять, у кого ошибка.
Программист утверждает, что у него всё правильно, это действительно так.
Электронщик заявляет, что схемотехника хорошо отработана и проверена на многих аппаратах, тоже верно.
А единый разработчик относительно быстро разобрался указанным способом.
Rapra писал(а): Чт авг 20, 2026 14:31:53 А вот как раз и ничего не ясно. Если программа не пришла в другую точку, то скорее всего виновата сама программа, а не какой-то там генератор. А грамотно написанная программа должна обнаруживать аппаратные сбои и сигнализировать об этом, раз уж аппаратный сбой может привести к сбою программы. Так что ваш пример придуман плохо. И если бы вы в достаточной степени владели программированием, то знали бы это.
Возможно, на Си неясен путь программы. У ассемблера существенный плюс – хороший контроль за ходом программы. Программа должна была прийти в указанную точку, свернуть на том участке некуда, но не дошла, генератор выключился. Можно было не тратить время впустую на анализ программы.
Осмотр цепей защиты от помех указал на ошибку монтажа.
Что даст сигнализация об аварии? Это и так ясно, генератор выключился. Возможно, даже и была какая-нибудь надпись, не помню.
Zapolyarny писал(а): Чт авг 20, 2026 15:30:43
AQ29 писал(а): Чт авг 20, 2026 13:27:39 На мой взгляд, лучше, чтобы схему и программу разрабатывал один человек.
Да. Например всю электронику какого-нить марсохода :) Там, наверное, несколько десятков тысяч человекочасов работы специалистов охрененного количества профилей, но лучше бы, конечно, если б это был один. Радиолюбитель, притом.
Вы, наверно, не внимательно прочитали мой пост. У меня написано про разработку схемы, а не всей электроники сложного агрегата. Схема – это, например, какой-нибудь регулятор расхода. Микроконтроллер, несколько датчиков и клапанов. Для разработки не потребуется десятки тысяч человеко-часов.
Мучитель микросхем
Сообщения: 434
Зарегистрирован: Чт май 07, 2026 00:30:38

Сообщение Zapolyarny »

Да, наверное, я невнимательно прочитал. Предположение, что Вы неоднозначно высказали мысль, имея ввиду под термином "схема" что-то не сложнее чего-то, наверное, не рассматривается?
Но даже если взять, например, какой-нибудь регулятор расхода, то однажды я участвовал в разработке такого. Команда была из: отдел электронщиков (6 человек и начальник отдела), отдел программистов (4 человека), главный конструктор. Плюс снабженцы. Это если не учитывать ещё инженеров завода, производящего то изделие, для которого регулятор расхода делался. Потому что далеко не всё можно регулировать абы как.

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

Сообщение Rapra »

AQ29 писал(а): Вт авг 25, 2026 11:16:19 Судя по вашему посту о большом количестве отказов при опытной эксплуатации
Я ТАКОГО НЕ ПИСАЛ! Просьба внимательно читать контекст.
Я изначально писал про МОДУЛЬНОЕ тестирование (unit-тесты). Оно выполняется во время написания самой программы как средство самоконтроля этапов создания программы, промежуточное тестирование.
А вы сами писали, что при "опытной эксплуатации медсестра нажала на кнопку, пошла перезагрузка и пациент умер". Но это результат непроведения юнит-тестов.
Вы сначала пишите одно, потом другое. И откуда вы вообще придумали эту "опытную эксплуатацию" медицинских приборов? Знаете ли, вас, с вашим "ЯВУ" вообще втуда не допустят.
AQ29 писал(а): Вт авг 25, 2026 11:16:19 Я привёл пример отказа, это реальный случай.
Какой случай? С ошибкой на печатной плате? А какое отношение она имеет к программе? А если из-за ошибки на плате программа зависла, значит, это плохо написанная программа.

AQ29 писал(а): Вт авг 25, 2026 11:16:19 Я не испытатель, почему я должен помнить про все испытания
А почему ж тогда пишите про испытания? :) Эдак каждый может насочинять сказок.

AQ29 писал(а): Вт авг 25, 2026 11:16:19 Как программист и электронщик будут определять, у кого ошибка.
Схемотехник (не "электронщик!" схемотехник правильно) еще до сдачи в производство проверяет плату в САПР-е, так же как и программист свою программу.
Изготовитель проверяет плату и на входе на соответствие допускам и требованиям производства, и на выходе - визуальный контроль, электротест, рентген, если надо.
Монтажник компонентов так же проверяет плату и на входе (визуально), и на выходе - визуально, приборами, рентгеном.
В финале остается залить прошивку. Как правило это делает не тот, кто писал программу, а сборщик с соответствующей квалификацией. Ему не нужно знать языки программирования и путь программы. Ему нужно только знать, куда подключить контакты и как залить готовую прошивку. Дальше плата отправляется на финальную сборку устройства и пуско-наладку. Это всё делают разные люди, и быть может даже в очень разных помещениях.

Хорошо написанная программа должна не зависнуть, а просигнализировать об отказе внешнего окружения и иных проблемах. Пуско-наладчику остается подключиться к диагностическому разъему и считать логи.
Таким образом, каждый выполняет только свою работу, за которую и отвечает. И если кто-то проморгал выходной контроль на своем посту, то он и виноват.

А вот на опытной эксплуатации проверяются не баги программиста или ошибки монтажника, а общая концепция устройства - насколько оно вообще грамотно спроектировано, насколько удобно им пользоваться, где какие возникают сложности у пользователя, насколько заявленные характеристики удовлетворяют требованиям эксплуатации.
Так что вы чето сильно перепутали определения. То ли не знали, то ли еще и забыли.
AQ29 писал(а): Вт авг 25, 2026 11:16:19 Возможно, на Си неясен путь программы. У ассемблера существенный плюс – хороший контроль за ходом программы.
Опять голословное и непрофессиональное утверждение. На ассемблере как раз-таки путь программы совсем не ясен из-за его специфики. Конечно, когда программа линейная, с короткими переходами, тогда еще более-менее понятно, просто листай сверху вниз и всё видно. Но таких программ очень мало, только самые примитивные.

Вот вам небольшой кусочек реальной программы на АССЕМБЛЕРЕ, попробуйте по нему отследить "путь программы", если сможете:
Спойлер

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

 8000994:	ldr	r0, [sp, #0]
 8000996:	strh	r1, [r2, #26]
 8000998: 	rsb	r1, r3, #1024	; 0x400
 800099c:	uxth	r1, r1
 800099e:	pkhbt	r2, r1, r6, lsl #16
 80009a2:	uqsub16	r2, r0, r2
 80009a6: 	uxth	r0, r2
 80009a8:	lsrs	r2, r2, #16
 80009aa:	pkhbt	r2, r0, r2, lsl #16
 80009ae:	ldr	r0, [sp, #4]
 80009b0:	uqadd16	r2, r2, r0
 80009b4:	movs	r0, #2
 80009b6:	mov.w	ip, #4
 80009ba:	mov.w	lr, #3
 80009be:	ldr.w	r5, [r8, #44]	; 0x2c
 80009c2:	add.w	fp, sp, #160	; 0xa0
 80009c6:	strb.w	sl, [sp, #157]	; 0x9d
 80009ca:	strb.w	sl, [sp, #158]	; 0x9e
 80009ce:	ubfx	r5, r5, #16, #13
 80009d2:	strb.w	ip, [sp, #152]	; 0x98
 80009d6:	uxth	r4, r2
 80009d8:	strb.w	lr, [sp, #153]	; 0x99
 80009dc:	strb.w	r0, [sp, #154]	; 0x9a
 80009e0:	strb.w	r0, [sp, #155]	; 0x9b
 80009e4:	strb.w	r0, [sp, #156]	; 0x9c
 80009e8:	strb.w	r0, [sp, #159]	; 0x9f
 80009ec:	ldr.w	r7, [r8, #16]
 80009f0:	uxtab	r7, fp, r7
 80009f4:	ldrb.w	r7, [r7, #-8]
 80009f8:	sdiv	r5, r5, r7
 80009fc:	cmp	r4, r5
 80009fe:	ble.w	8001c56 <main+0x197e>
 8000a02:	ldr.w	r2, [r9, #36]	; 0x24
 8000a02:	ldr.w	r2, [r9, #36]	; 0x24
 8000a06:	orr.w	r2, r2, #2
 8000a0a:	str.w	r2, [r9, #36]	; 0x24
 8000a0e:	ldr.w	r2, [r9, #36]	; 0x24
 8000a12:	cmp	r2, #0
 8000a14:	bne.n	8000a0e <main+0x736>
 8000a16:	sub.w	r2, r3, #11
 8000a1a:	ldr	r1, [pc, #556]	; (8000c48 <main+0x970>)
 8000a1c:	mov.w	r0, #8192	; 0x2000
 8000a20:	cmp.w	r2, #468	; 0x1d4
 8000a24:	strh	r0, [r1, #24]
 8000a26:	bhi.n	8000a2c <main+0x754>
 8000a28:	subs	r3, #1
 8000a2a:	uxth	r3, r3
скажу честно - дануевонахрен, такое занятие! :) Там подобных строк - десятки тысяч! Вы че, издеваетесь чтоль, разгребать такое?? Какой, кчерту, ассемблер??
Не, конечно, если очень надо, небольшие кусочки разгрести можно. Особенно когда отыскиваешь какую-то нестандартную проблему. Но искать, куда пошел "путь программы" в этом месиве - эт мазохизм какой-то

Языки Си-группы предназначены для гораздо более сложных программ с ветвлениями. Но даже при этом он значительно нагляднее показывает "путь программы":
Да и вообще, в сложных устройствах "путь джедая программы" может быть очень сложен, нелинеен, прерван прерываниями и прочими переключениями. Но и выполняемые программой действия тоже не примитивное "вкл/выкл светодиода и реле по нажатию кнопки".
AQ29 писал(а): Вт авг 25, 2026 11:16:19 Программа должна была прийти в указанную точку, свернуть на том участке некуда, но не дошла, генератор выключился.
А куда ж она тогда делась? Где она потеряла свой путь джедая? Программа не может просто так взять и бесследно исчезнуть, особенно на ассемблере. Она конечно может уйти не туда, куда ожидается, из-за того, что были приняты неверные данные и перебросили её в случайное место.
Но такое поведение для программы - потенциально опасное. Особенно в свете этих ваших "медицинских приборов". Вот именно с таким неожиданным пропаданием пути программы у вас и помрет поциент под ИВЛ. Потому что вы в своей программе не предусмотрели определенного поведения при внешних отказах и помехах. Понимаете? Выключился генератор = поциент умер! :) А всё из-за непредсказуемого поведения программы из-за внешних помех. Всего лишь помехи - и пациент умер. Кто виноват? ВЫ! Вы написали плохую программу с непредсказуемым поведением
AQ29 писал(а): Вт авг 25, 2026 11:16:19 У меня написано про разработку схемы, а не всей электроники сложного агрегата. Схема – это, например, какой-нибудь регулятор расхода.
При очень простой схеме возможно совместить программиста и схемотехника в одном лице. Большинство в любительском сообществе так и делают. Да и то не всегда. Предпочитают покупать готовые платы с микроконтроллерами, датчиками и дисплеями и соединять их проводками.
Но любительское сообщество - это совсем не промышленность, особенно как вы любите про медицину. Хотя, смотря что вы медициной называете :) Электронный термометр - тоже медицина
Ответить

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