Programming/qt2026. 1. 13. 14:39

qt quick 으로 작성된 계산기 어플 소스 분석

 

실행하면 평범한 계산기가 뜬다.

 

가로로 길게 늘리면 공학계산기로 바뀐다.

 

프로젝트는 아래와 같이 구성되어 있고

 

main.cpp

언제나 그렇듯(?) main.cpp 는 조촐하고 별 내용이 없다.

// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>

int main(int argc, char *argv[])
{
    QCoreApplication::setOrganizationName("QtProject");
    QCoreApplication::setApplicationName("Calqlatr");

    QGuiApplication app(argc, argv);

    QQuickStyle::setStyle("Basic");

    QQmlApplicationEngine engine;
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
            &app, []() { QCoreApplication::exit(-1); },
            Qt::QueuedConnection);
    engine.loadFromModule("demos.calqlatr", "Main");

    return app.exec();
}

 

Main.qml

전체 모양을 그리는 녀석인데, design 에서 보면 아래처럼 보인다.

 

다만 qt designer의 버그인지 ApplicationState 의 id: state 구문이 에러가 나는데

위지윅 에디터에서 보려면 해당 라인을 주석처리 하면 된다. (물론 실행하면 작동안하게 되는 버그 발생)

 

 

column, row Layout 으로 배치를 어떻게 하는것 같고

Keys.onPressed: function (event) {} 를 통해서 키 입력시 state에 추가하는 식으로 작동하게 된다.

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Layouts

Window {
    visible: true
    width: 320
    height: 480
    minimumWidth: Math.max(numberPad.portraitModeWidth, display.minWidth) + root.margin * 2
    minimumHeight: display.minHeight + numberPad.height + root.margin * 3
    color: root.backgroundColor

    Item {
        id: root
        anchors.fill: parent

        anchors.topMargin: parent.SafeArea.margins.top
        anchors.leftMargin: parent.SafeArea.margins.left
        anchors.rightMargin: parent.SafeArea.margins.right
        anchors.bottomMargin: parent.SafeArea.margins.bottom

        readonly property int margin: 18
        readonly property color backgroundColor: "#222222"
        readonly property int minLandscapeModeWidth: numberPad.landscapeModeWidth
                                                     + display.minWidth + margin * 3

        property bool isPortraitMode: root.width < root.minLandscapeModeWidth

        ApplicationState {
            id: state
            display: display
        }

        Display {
            id: display
            readonly property int minWidth: 210
            readonly property int minHeight: 60

            Layout.minimumWidth: minWidth
            Layout.fillWidth: true
            Layout.fillHeight: true
            Layout.margins: root.margin

            // remove the margin on the side that the numberPad is on, to prevent a double margin
            Layout.bottomMargin: root.isPortraitMode ? 0 : root.margin
            Layout.rightMargin: root.isPortraitMode ? root.margin : 0
        }

        NumberPad {
            id: numberPad
            Layout.margins: root.margin

            isPortraitMode: root.isPortraitMode
            state: state
        }

        // define the responsive layouts
        ColumnLayout {
            id: portraitMode
            anchors.fill: parent
            visible: root.isPortraitMode

            LayoutItemProxy {
                target: display
                Layout.minimumHeight: display.minHeight
            }
            LayoutItemProxy {
                target: numberPad
                Layout.alignment: Qt.AlignHCenter
            }
        }

        RowLayout {
            id: landscapeMode
            anchors.fill: parent
            visible: !root.isPortraitMode

            LayoutItemProxy {
                target: display
            }
            LayoutItemProxy {
                target: numberPad
                Layout.alignment: Qt.AlignVCenter
            }
        }

        Keys.onPressed: function (event) {
            switch (event.key) {
                case Qt.Key_0: state.digitPressed("0"); break;
                case Qt.Key_1: state.digitPressed("1"); break;
                case Qt.Key_2: state.digitPressed("2"); break;
                case Qt.Key_3: state.digitPressed("3"); break;
                case Qt.Key_4: state.digitPressed("4"); break;
                case Qt.Key_5: state.digitPressed("5"); break;
                case Qt.Key_6: state.digitPressed("6"); break;
                case Qt.Key_7: state.digitPressed("7"); break;
                case Qt.Key_8: state.digitPressed("8"); break;
                case Qt.Key_9: state.digitPressed("9"); break;
                case Qt.Key_E: state.digitPressed("e"); break;
                case Qt.Key_P: state.digitPressed("π"); break;
                case Qt.Key_Plus: state.operatorPressed("+"); break;
                case Qt.Key_Minus: state.operatorPressed("-"); break;
                case Qt.Key_Asterisk: state.operatorPressed("×"); break;
                case Qt.Key_Slash: state.operatorPressed("÷"); break;
                case Qt.Key_Enter:
                case Qt.Key_Return: state.operatorPressed("="); break;
                case Qt.Key_Comma:
                case Qt.Key_Period: state.digitPressed("."); break;
                case Qt.Key_Backspace: state.operatorPressed("bs"); break;
            }
        }
    }
}

 

ApplicationState.qml

말로는 qml  인데 design에서 보여지는 요소는 존재하지 않고

main.qml 에서 호출되는 operatorPressed() 나 digitPressed()와 같은 함수가 존재한다.

특이한건 import as 인데 js를 불러서 CalcEngine 으로 사용하는 부분 정도?

그 와중에 Display는 display.qml 에서 끌려오는건가?

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQml
import "calculator.js" as CalcEngine

QtObject {
    required property Display display

    function operatorPressed(operator) {
        CalcEngine.operatorPressed(operator, display);
    }
    function digitPressed(digit) {
        CalcEngine.digitPressed(digit, display);
    }
    function isButtonDisabled(op) {
        return CalcEngine.isOperationDisabled(op, display);
    }
}

 

calculator.js

평범한(?) js로 작성된 코드가 존재한다.

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

let accumulator = 0
let pendingOperator = ""
let lastButton = ""
let digits = ""

function isOperationDisabled(op, display) {
    if (digits !== "" && lastButton !== "=" && (op === "π" || op === "e"))
        return true
    if (digits === "" && !((op >= "0" && op <= "9") || op === "π" || op === "e" || op === "AC"))
        return true
    if (op === "bs" && (display.isOperandEmpty() || !((lastButton >= "0" && lastButton <= "9")
                                                      || lastButton === "π" || lastButton === "e" || lastButton === ".")))
        return true
    if (op === '=' && pendingOperator.length != 1)
        return true
    if (op === "." && digits.search(/\./) != -1)
        return true
    if (op === "√" &&  digits.search(/-/) != -1)
        return true
    if (op === "AC" && display.isDisplayEmpty())
        return true

    return false
}

function digitPressed(op, display) {
    if (isOperationDisabled(op, display))
        return
    if (lastButton === "π" || lastButton === "e")
        return
    // handle mathematical constants
    if (op === "π") {
        lastButton = op
        digits = Math.PI.toPrecision(display.maxDigits - 1).toString()
        display.appendDigit(digits)
        return
    }
    if (op === "e") {
        lastButton = op
        digits = Math.E.toPrecision(display.maxDigits - 1).toString()
        display.appendDigit(digits)
        return
    }

    // append a digit to another digit or decimal point
    if (lastButton.toString().length === 1 && ((lastButton >= "0" && lastButton <= "9") || lastButton === ".") ) {
        if (digits.length >= display.maxDigits)
            return
        digits = digits + op.toString()
        display.appendDigit(op.toString())
    // else just write a single digit to display
    } else {
        digits = op.toString()
        display.appendDigit(digits)
    }
    lastButton = op
}

function operatorPressed(op, display) {
    if (isOperationDisabled(op, display))
        return

    if (op === "±") {
        digits = Number(digits.valueOf() * -1).toString()
        display.setDigit(display.displayNumber(Number(digits)))
        return
    }

    if (op === "bs") {
        digits = digits.slice(0, -1)
        if (digits === "-")
            digits = ""
        display.backspace()
        return
    }

    lastButton = op

    if (pendingOperator === "+") {
        digits = (Number(accumulator) + Number(digits.valueOf())).toString()
    } else if (pendingOperator === "−") {
        digits = (Number(accumulator) - Number(digits.valueOf())).toString()
    } else if (pendingOperator === "×") {
        digits = (Number(accumulator) * Number(digits.valueOf())).toString()
    } else if (pendingOperator === "÷") {
        digits = (Number(accumulator) / Number(digits.valueOf())).toString()
    }


    if (op === "+" || op === "−" || op === "×" || op === "÷") {
        pendingOperator = op
        accumulator = digits.valueOf()
        digits = ""
        display.displayOperator(pendingOperator)
        return
    }

    accumulator = 0
    pendingOperator = ""

    if (op === "=") {
        display.newLine("=", Number(digits))
    }

    if (op === "√") {
        digits = (Math.sqrt(digits.valueOf())).toString()
        display.newLine("√", Number(digits))
    } else if (op === "⅟x") {
        digits = (1 / digits.valueOf()).toString()
        display.newLine("⅟x", Number(digits))
    } else if (op === "x²") {
        digits = (digits.valueOf() * digits.valueOf()).toString()
        display.newLine("x²", Number(digits))
    } else if (op === "x³") {
        digits = (digits.valueOf() * digits.valueOf() * digits.valueOf()).toString()
        display.newLine("x³", Number(digits))
    } else if (op === "|x|") {
        digits = (Math.abs(digits.valueOf())).toString()
        display.newLine("|x|", Number(digits))
    } else if (op === "⌊x⌋") {
        digits = (Math.floor(digits.valueOf())).toString()
        display.newLine("⌊x⌋", Number(digits))
    } else if (op === "sin") {
        digits = Number(Math.sin(digits.valueOf())).toString()
        display.newLine("sin", Number(digits))
    } else if (op === "cos") {
        digits = Number(Math.cos(digits.valueOf())).toString()
        display.newLine("cos", Number(digits))
    } else if (op === "tan") {
        digits = Number(Math.tan(digits.valueOf())).toString()
        display.newLine("tan", Number(digits))
    } else if (op === "log") {
        digits = Number(Math.log10(digits.valueOf())).toString()
        display.newLine("log", Number(digits))
    } else if (op === "ln") {
        digits = Number(Math.log(digits.valueOf())).toString()
        display.newLine("ln", Number(digits))
    }

    if (op === "AC") {
        display.allClear()
        accumulator = 0
        lastButton = ""
        digits = ""
        pendingOperator = ""
    }
}

 

Display.qml

design 상에서는 별 내용이 없어 보이지만

 

코드에서는 라인별로 추가하는 등 제법 ui를 건드리는 작동을 많이 한다.

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma ComponentBehavior: Bound

import QtQuick

Item {
    id: display
    property int fontSize: 22
    readonly property int maxDigits: Math.min((width / fontSize) + 1, 9)
    readonly property color backgroundColor: "#262626"
    readonly property color qtGreenColor: "#2CDE85"
    property string displayedOperand: ""
    readonly property string errorString: qsTr("ERROR")
    readonly property bool isError: displayedOperand === errorString
    property bool enteringDigits: false

    function displayOperator(operator) {
        calculationsListView.model.append({
                                              "operator": operator,
                                              "operand": ""
                                          });
        enteringDigits = true;
        calculationsListView.positionViewAtEnd();
    }

    function newLine(operator, operand) {
        displayedOperand = displayNumber(operand);
        calculationsListView.model.append({
                                              "operator": operator,
                                              "operand": displayedOperand
                                          });
        enteringDigits = false;
        calculationsListView.positionViewAtEnd();
    }

    function appendDigit(digit) {
        if (!enteringDigits)
            calculationsListView.model.append({
                                                  "operator": "",
                                                  "operand": ""
                                              });
        const i = calculationsListView.model.count - 1;
        calculationsListView.model.get(i).operand = calculationsListView.model.get(i).operand
                + digit;
        enteringDigits = true;
        calculationsListView.positionViewAtEnd();
    }

    function setDigit(digit) {
        const i = calculationsListView.model.count - 1;
        calculationsListView.model.get(i).operand = digit;
        calculationsListView.positionViewAtEnd();
    }

    function backspace() {
        const i = calculationsListView.model.count - 1;
        if (i >= 0) {
            let operand = calculationsListView.model.get(i).operand.toString().slice(0, -1);
            if (operand === "-")
                operand = "";
            calculationsListView.model.get(i).operand = operand;
            return;
        }
        return;
    }

    function isOperandEmpty() {
        const i = calculationsListView.model.count - 1;
        return i >= 0 ? calculationsListView.model.get(i).operand === "" : true;
    }

    function isDisplayEmpty() {
        const i = calculationsListView.model.count - 1;
        return i == -1 ? true : (i == 0 ? calculationsListView.model.get(0).operand === "" : false);
    }

    function clear() {
        displayedOperand = "";
        if (enteringDigits) {
            const i = calculationsListView.model.count - 1;
            if (i >= 0)
                calculationsListView.model.remove(i);
            enteringDigits = false;
        }
    }

    function allClear() {
        display.clear();
        calculationsListView.model.clear();
        enteringDigits = false;
    }

    // Returns a string representation of a number that fits in
    // display.maxDigits characters, trying to keep as much precision
    // as possible. If the number cannot be displayed, returns an
    // error string.
    function displayNumber(num) {
        if (typeof (num) !== "number")
            return errorString;

        // deal with the absolute
        const abs = Math.abs(num);

        if (abs.toString().length <= maxDigits) {
            return isFinite(num) ? num.toString() : errorString;
        }

        if (abs < 1) {
            // check if abs < 0.00001, if true, use exponential form
            // if it isn't true, we can round the number without losing
            // too much precision
            if (Math.floor(abs * 100000) === 0) {
                const expVal = num.toExponential(maxDigits - 6).toString();
                if (expVal.length <= maxDigits + 1)
                    return expVal;
            } else {
                // the first two digits are zero and .
                return num.toFixed(maxDigits - 2);
            }
        } else {
            // if the integer part of num is greater than maxDigits characters, use exp form
            const intAbs = Math.floor(abs);
            if (intAbs.toString().length <= maxDigits)
                return parseFloat(num.toPrecision(maxDigits - 1)).toString();

            const expVal = num.toExponential(maxDigits - 6).toString();
            if (expVal.length <= maxDigits + 1)
                return expVal;
        }
        return errorString;
    }

    Item {
        anchors.fill: parent

        Rectangle {
            anchors.fill: parent
            radius: 8
            color: display.backgroundColor

            ListView {
                idcalculationsListView
                x: 5
                y: 10
                width: parent.width
                height: parent.height - 2 * y
                clip: true
                delegate: Item {
                    height: display.fontSize * 1.1
                    width: calculationsListView.width

                    required property string operator
                    required property string operand

                    Text {
                        x: 6
                        font.pixelSize: display.fontSize
                        color: display.qtGreenColor
                        text: parent.operator
                        Accessible.name: parent.operator
                    }
                    Text {
                        font.pixelSize: display.fontSize
                        anchors.right: parent.right
                        anchors.rightMargin: 16
                        text: parent.operand
                        Accessible.name: parent.operand
                        color: "white"
                    }
                }
                model: ListModel {}
                onHeightChanged: positionViewAtEnd()
            }
        }
    }
}

 

 

Numpad.qml

계산기의 버튼 부분인것 같은데 rectangle 안에 Rowlayout  안에 GridLayout이 있는데 그래서 그런가 이상하게 보여지는 느낌.

 

그나저나 제곱이나 sin tan 같은 과학계산기도 넣으려다가 흔적만 남은건가?

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Layouts

Item {
    id: controller

    required property bool isPortraitMode
    required property ApplicationState state

    readonly property color qtGreenColor: "#2CDE85"
    readonly property color backspaceRedColor: "#DE2C2C"
    readonly property int spacing: 5

    property int portraitModeWidth: mainGrid.width
    property int landscapeModeWidth: scientificGrid.width + mainGrid.width

    implicitWidth: isPortraitMode ? portraitModeWidth : landscapeModeWidth
    implicitHeight: mainGrid.height

    function updateDimmed() {
        for (let i = 0; i < mainGrid.children.length; i++) {
            mainGrid.children[i].dimmed = state.isButtonDisabled(mainGrid.children[i].text);
        }
        for (let j = 0; j < scientificGrid.children.length; j++) {
            scientificGrid.children[j].dimmed = state.isButtonDisabled(
                        scientificGrid.children[j].text);
        }
    }

    component DigitButton: CalculatorButton {
        onClicked: {
            controller.state.digitPressed(text);
            controller.updateDimmed();
        }
    }

    component OperatorButton: CalculatorButton {
        dimmable: true
        implicitWidth: 48
        textColor: controller.qtGreenColor

        onClicked: {
            controller.state.operatorPressed(text);
            controller.updateDimmed();
        }
    }

    Component.onCompleted: updateDimmed()

    Rectangle {
        id: numberPad
        anchors.fill: parent
        radius: 8
        color: "transparent"

        RowLayout {
            spacing: controller.spacing

            GridLayout {
                id: scientificGrid
                columns: 3
                columnSpacing: controller.spacing
                rowSpacing: controller.spacing
                visible: !controller.isPortraitMode

                OperatorButton {
                    text: "x²"
                    Accessible.name: "x squared"
                }
                OperatorButton {
                    text: "⅟x"
                    Accessible.name: "one over x"
                }
                OperatorButton { text: "√" }
                OperatorButton {
                    text: "x³"
                    Accessible.name: "x cubed"
                }
                OperatorButton {
                    text: "sin"
                    Accessible.name: "sine"
                }
                OperatorButton {
                    text: "|x|"
                    Accessible.name: "absolute value"
                }
                OperatorButton { text: "log" }
                OperatorButton {
                    text: "cos"
                    Accessible.name: "cosine"
                }
                DigitButton {
                    text: "e"
                    dimmable: true
                    implicitWidth: 48
                }
                OperatorButton { text: "ln" }
                OperatorButton { text: "tan" }
                DigitButton {
                    text: "π"
                    dimmable: true
                    implicitWidth: 48
                }
            }

            GridLayout {
                id: mainGrid
                columns: 5
                columnSpacing: controller.spacing
                rowSpacing: controller.spacing

                BackspaceButton {
                    onClicked: {
                        controller.state.operatorPressed(this.text);
                        controller.updateDimmed();
                    }
                }

                DigitButton { text: "7" }
                DigitButton { text: "8" }
                DigitButton { text: "9" }
                OperatorButton {
                    text: "÷"
                    implicitWidth: 38
                }

                OperatorButton {
                    text: "AC"
                    textColor: controller.backspaceRedColor
                    accentColor: controller.backspaceRedColor
                }
                DigitButton { text: "4" }
                DigitButton { text: "5" }
                DigitButton { text: "6" }
                OperatorButton {
                    text: "×"
                    implicitWidth: 38
                }

                OperatorButton {
                    text: "="
                    implicitHeight: 81
                    Layout.rowSpan: 2
                }
                DigitButton { text: "1" }
                DigitButton { text: "2" }
                DigitButton { text: "3" }
                OperatorButton {
                    text: "−"
                    implicitWidth: 38
                }

                OperatorButton {
                    text: "±"
                    implicitWidth: 38
                }
                DigitButton { text: "0" }
                DigitButton {
                    text: "."
                    dimmable: true
                }
                OperatorButton {
                    text: "+"
                    implicitWidth: 38
                }
            }
        } // RowLayout
    }
}

 

 

BackspaceButton.qml

numpad 에서 호출되는 버튼. 그 외에는 크게 눈에 띄진 않네

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls

RoundButton {
    id: button
    implicitWidth: 48
    implicitHeight: 38
    radius: buttonRadius
    icon.source: getIcon()
    icon.width: 38
    icon.height: 38
    icon.color: getIconColor()
    // include this text property as the calculator engine
    // differentiates buttons through text. The text is never drawn.
    text: "bs"
    Accessible.name: "backspace"

    property bool dimmable: true
    property bool dimmed: false
    readonly property color backgroundColor: "#222222"
    readonly property color borderColor: "#A9A9A9"
    readonly property color backspaceRedColor: "#DE2C2C"
    readonly property int buttonRadius: 8

    function getBackgroundColor() {
        if (button.dimmable && button.dimmed)
            return backgroundColor;
        if (button.pressed)
            return backspaceRedColor;
        return backgroundColor;
    }

    function getBorderColor() {
        if (button.dimmable && button.dimmed)
            return borderColor;
        if (button.pressed || button.hovered)
            return backspaceRedColor;
        return borderColor;
    }

    function getIconColor() {
        if (button.dimmable && button.dimmed)
            return Qt.darker(backspaceRedColor);
        if (button.pressed)
            return backgroundColor;
        return backspaceRedColor;
    }

    function getIcon() {
        if (button.dimmable && button.dimmed)
            return "images/backspace.svg";
        if (button.pressed)
            return "images/backspace_fill.svg";
        return "images/backspace.svg";
    }

    background: Rectangle {
        radius: button.buttonRadius
        color: button.getBackgroundColor()
        border.color: button.getBorderColor()
    }
}

 

CalculatorButton.qml

numpad 에서 호출되는 버튼. 그 외에는 크게 눈에 띄진 않네 2

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls

RoundButton {
    id: button
    implicitWidth: 38
    implicitHeight: 38
    radius: buttonRadius

    property bool dimmable: false
    property bool dimmed: false
    readonly property int fontSize: 22
    readonly property int buttonRadius: 8
    property color textColor: "#FFFFFF"
    property color accentColor: "#2CDE85"
    readonly property color backgroundColor: "#222222"
    readonly property color borderColor: "#A9A9A9"

    function getBackgroundColor() {
        if (button.dimmable && button.dimmed)
            return backgroundColor;
        if (button.pressed)
            return accentColor;
        return backgroundColor;
    }

    function getBorderColor() {
        if (button.dimmable && button.dimmed)
            return borderColor;
        if (button.pressed || button.hovered)
            return accentColor;
        return borderColor;
    }

    function getTextColor() {
        if (button.dimmable && button.dimmed)
            return Qt.darker(textColor);
        if (button.pressed)
            return backgroundColor;
        if (button.hovered)
            return accentColor;
        return textColor;
    }

    background: Rectangle {
        radius: button.buttonRadius
        color: button.getBackgroundColor()
        border.color: button.getBorderColor()
    }

    contentItem: Text {
        text: button.text
        font.pixelSize: button.fontSize
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        color: button.getTextColor()
        Behavior on color {
            ColorAnimation {
                duration: 120
                easing.type: Easing.OutElastic
            }
        }
    }
}

'Programming > qt' 카테고리의 다른 글

qt qml loader  (0) 2026.01.14
qstackedwidget, qstackedlayout  (0) 2026.01.13
qt qml view  (0) 2026.01.13
qt quick 이미지 클릭  (0) 2026.01.12
qt creator 18.0.1 design 활성화 하기  (0) 2026.01.09
Posted by 구차니
하드웨어/모터(motor)2026. 1. 13. 14:08

협동로봇에 많이 사용된다는 고단 변속 드라이브.. 라고 해야하나?

 

왼쪽이 하모닉 드라이브, 오른쪽으 사이클로이드 드라이브

 하모닉 드라이브는 총 3개의 부품으로 끝나고(크게) 사이클로이드는 부품이 좀 많이 들어간다.

높은 토크가 필요한게 아니라면 하모닉 드라이브도 괜찮아 보이지만 중간의 유연성을 지는 부품이 핵심일 것 같은데

아무래도 변형되는 녀석이 있다 보니 내구도가 좋지 않을것 같긴한데.. 알아서 잘 해결했겠지 머

 

[링크 : https://youtu.be/IXmCze1GsGU?si=fi6Ni3ONYKYr8duc]

'하드웨어 > 모터(motor)' 카테고리의 다른 글

스텝 모터 2상 4상  (0) 2025.11.28
NEMA 17 스텝 모터?  (0) 2025.02.11
MG-996R 하이토크 디지털 서보  (0) 2025.01.07
로봇 다리  (0) 2021.07.21
무한회전 서보모터?  (0) 2021.07.13
Posted by 구차니
Programming/qt2026. 1. 13. 10:59

전체적인 레이아웃 잡는데에는 split view 나 stack view 나 tabview 정도가 적절해 보이는군.

 

ScrollView Provides a scrolling view within another Item
SplitView Lays out items with a draggable splitter between each item
StackView Provides a stack-based navigation model
TabView A control that allows the user to select one of multiple stacked items
TableView Provides a list view with scroll bars, styling and header sections
TreeView Provides a tree view with scroll bars, styling and header sections

[링크 : https://doc.qt.io/archives/qt-5.15/qtquick-controls-qmlmodule.html]

[링크 : https://doc.qt.io/archives/qt-5.15/qml-qtquick-controls-stackview.html]

 

onclicked 이벤트에서 stackview.push 함수를 이용하여 qml을 넣어주면 끝인듯. 편하겠네

넣는건 push 빼는건 pop. 직관적이네~

onClicked: {
             stackView.push(Qt.resolvedUrl("qrc:/screen2.qml"))//다음 화면을 출력하기 위해 stack에 화면을 쌓는 코드
             //stack에 메인화면 두번째화면 세번째 화면이 쌓이게 된다.
        }

onClicked: {
            stackView.pop();//이전화면을 호출하기위해 stack의 제일 위에 화면을 밖으로 빼주어 첫화면을 보여준다.
        }

[링크 : https://youonlyliveonce1.tistory.com/16]

 

qt creator 에서 찾아보니 swipe view라는게 추가되어 보이고, 다른 tab 이나 split view가 안보인다.

Posted by 구차니
Programming/qt2026. 1. 12. 17:58

2026.01.13

다시보다 보니 기본 컴포넌트에 mouse area 라는 것이 있다.

이미지 하나 띄우고 마우스 영역을 지정하고 원하는 크기로 조절하면

과거 html 에서 이미지 맵 하듯 가능해진다.

 

mouse area에서 area는 활성화 되는데 hover는 활성화 되어있지 않아서 체크를 해줘야 하고

체크를 해주면 의도한 대로 작동되게 된다.

----

 

qt quick 으로 생성해서 이미지를 넣고 사진을 띄우는것 까진 성공!(프로젝트 외부 디렉토리에 이미지는 안됨)

그래서 이미지를 클릭하면 무언가 하려고 하는데

connections 라던가 event list 에서 무언가 쓸만해 보이는게 뜨지 않는다.

 

 

그래서 우클릭 후 Add Mouse Area를 해주면

 

 

 image가 아니라 mouseArea로 잡히고

 

평범하게 보던 mouse 클릭 이벤트 들을 사용할 수 있게 된다.

 

확인필요

[링크 : https://studiodoc.tistory.com/171]

'Programming > qt' 카테고리의 다른 글

qt quick 예제 calqlatr 코드분석  (0) 2026.01.13
qt qml view  (0) 2026.01.13
qt creator 18.0.1 design 활성화 하기  (0) 2026.01.09
qt 변수 초기화 문법, cpp 초기화 리스트  (0) 2021.12.08
qt5 fb reset  (0) 2021.02.23
Posted by 구차니
embeded/esp322026. 1. 12. 16:44

문득 남에건 빠른데 내껀 왜 느리지 싶어서 찾아보는 중

내가 가진건 st7735 칩셋이고 얘가 좀 많이 느린듯

[링크 : https://minimonk.tistory.com/524246]

 

라즈베리 파이용으로 구했던건 dtb 상으로 ili9486 같다. (3.5inch rpi lcd (a) v3)

돌이켜 보면 mame 하기 버거울 정도로 느렸던것 같은데. 아무튼 16MHz 였군

waveshare35a: waveshare35a@0{
                                compatible = "ilitek,ili9486";
                                reg = <0>;
                                pinctrl-names = "default"; 
                                pinctrl-0 = <&waveshare35a_pins>;
                                spi-max-frequency = <16000000>;
                                rotate = <90>;
                                bgr;
                                fps = <30>;
                                buswidth = <8>;
                                regwidth = <16>;

[링크 : https://minimonk.tistory.com/5694]

 

그래서 나중에 구매한 3.5inch rpi lcd (c) 고속 lcd 인데 칩셋 이름이 없다.

Supports 125MHz high-speed SPI signal transmission

 

dtb 받아서 확인해보니 클럭이 어마어하게 올랐다. 어우 115Mhz 라니 기존대비 7배 올랐네

$ dtc -I dtb -O dts waveshare35c.dtbo 
<stdout>: Warning (unit_address_vs_reg): /fragment@0/__overlay__/spidev@0: node has a unit name, but no reg or ranges property
<stdout>: Warning (unit_address_vs_reg): /fragment@0/__overlay__/spidev@1: node has a unit name, but no reg or ranges property
<stdout>: Warning (gpios_property): /fragment@2/__overlay__/tft35a@0:reset-gpios: Could not get phandle node for (cell 0)
<stdout>: Warning (gpios_property): /fragment@2/__overlay__/tft35a@0:dc-gpios: Could not get phandle node for (cell 0)
<stdout>: Warning (gpios_property): /fragment@2/__overlay__/tft35a-ts@1:pendown-gpio: Could not get phandle node for (cell 0)
<stdout>: Warning (gpios_property): /__fixups__:gpio: property size (218) is invalid, expected multiple of 4
<stdout>: Warning (interrupts_property): /fragment@2/__overlay__/tft35a-ts@1:interrupt-parent: Bad phandle
/dts-v1/;

/ {
compatible = "brcm,bcm2835\0brcm,bcm2708\0brcm,bcm2709";

fragment@0 {
target = <0xdeadbeef>;

__overlay__ {
status = "okay";

spidev@0 {
status = "disabled";
};

spidev@1 {
status = "disabled";
};
};
};

fragment@1 {
target = <0xdeadbeef>;

__overlay__ {

tft35a_pins {
brcm,pins = <0x11 0x19 0x18>;
brcm,function = <0x00 0x00 0x00>;
linux,phandle = <0x01>;
phandle = <0x01>;
};
};
};

fragment@2 {
target = <0xdeadbeef>;

__overlay__ {
#address-cells = <0x01>;
#size-cells = <0x00>;

tft35a@0 {
compatible = "ilitek,ili9486";
reg = <0x00>;
pinctrl-names = "default";
pinctrl-0 = <0x01>;
spi-max-frequency = <0x6dac2c0>;
txbuflen = <0x8000>;
rotate = <0x5a>;
bgr = <0x00>;
fps = <0x1e>;
buswidth = <0x08>;
regwidth = <0x10>;
reset-gpios = <0xdeadbeef 0x19 0x01>;
dc-gpios = <0xdeadbeef 0x18 0x00>;
debug = <0x00>;
init = <0x10000f1 0x36 0x04 0x00 0x3c 0x0f 0x8f 0x10000f2 0x18 0xa3 0x12 0x02 0xb2 0x12 0xff 0x10 0x00 0x10000f8 0x21 0x04 0x10000f9 0x00 0x08 0x1000036 0x08 0x10000b4 0x00 0x10000c1 0x41 0x10000c5 0x00 0x91 0x80 0x00 0x10000e0 0x0f 0x1f 0x1c 0x0c 0x0f 0x08 0x48 0x98 0x37 0x0a 0x13 0x04 0x11 0x0d 0x00 0x10000e1 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 0x100003a 0x55 0x1000011 0x1000036 0x28 0x20000ff 0x1000029>;
linux,phandle = <0x02>;
phandle = <0x02>;
};

tft35a-ts@1 {
compatible = "ti,ads7846";
reg = <0x01>;
spi-max-frequency = <0x1e8480>;
interrupts = <0x11 0x02>;
interrupt-parent = <0xdeadbeef>;
pendown-gpio = <0xdeadbeef 0x11 0x01>;
ti,x-plate-ohms = [00 3c];
ti,pressure-max = [00 ff];
linux,phandle = <0x03>;
phandle = <0x03>;
};
};
};

__overrides__ {
speed = <0x02 0x7370692d 0x6d61782d 0x66726571 0x75656e63 0x793a3000>;
txbuflen = [00 00 00 02 74 78 62 75 66 6c 65 6e 3a 30 00];
rotate = [00 00 00 02 72 6f 74 61 74 65 3a 30 00];
fps = [00 00 00 02 66 70 73 3a 30 00];
bgr = [00 00 00 02 62 67 72 3a 30 00];
debug = <0x02 0x64656275 0x673a3000>;
swapxy = <0x03 0x74692c73 0x7761702d 0x78793f00>;
};

__symbols__ {
tft35a_pins = "/fragment@1/__overlay__/tft35a_pins";
tft35a = "/fragment@2/__overlay__/tft35a@0";
tft35a_ts = "/fragment@2/__overlay__/tft35a-ts@1";
};

__fixups__ {
spi0 = "/fragment@0:target:0\0/fragment@2:target:0";
gpio = "/fragment@1:target:0\0/fragment@2/__overlay__/tft35a@0:reset-gpios:0\0/fragment@2/__overlay__/tft35a@0:dc-gpios:0\0/fragment@2/__overlay__/tft35a-ts@1:interrupt-parent:0\0/fragment@2/__overlay__/tft35a-ts@1:pendown-gpio:0";
};

__local_fixups__ {
fixup = "/fragment@2/__overlay__/tft35a@0:pinctrl-0:0\0/__overrides__:speed:0\0/__overrides__:txbuflen:0\0/__overrides__:rotate:0\0/__overrides__:fps:0\0/__overrides__:bgr:0\0/__overrides__:debug:0\0/__overrides__:swapxy:0";
};
};

[링크 : https://www.waveshare.com/wiki/3.5inch_RPi_LCD_(C)?srsltid=AfmBOoqSBx9MWwgfxNGqcf86r2X4O_xpUVpX6gSELU31q2-5_RCjE0z1

[링크 : https://minimonk.tistory.com/9776]

 

예전에 2.4만원 넘어서 안샀던 것들이 ili9341 계열인데, 아래 보면 65fps까지 되는 쩌는 애들이네

[링크 : https://minimonk.tistory.com/12398]

 

아무튼 저런 애들을 써야 성능이 잘 나온다~ 라는거군

ESP32 LCDs performance report
1. ST7796 4-wire SPI   16fps
2. ST7796 8-bit   34fps
3. ILI9488 4-wire SPI  11fps
4. ILI9488 8-bit  34fps
5. ST7789 4-wire SPI  65fps
6. ST7789 8-bit  5xfps(weird)
7. ILI9341 4-wire SPI  65fps
8. GC9A01  8xfps
9. ST7789 240x240  8xfps

[링크 : https://www.facebook.com/groups/797613180807626/posts/1048617505707191/]

'embeded > esp32' 카테고리의 다른 글

esp32-2432S028 데이터 시트  (0) 2026.01.17
esp32-2432S028 보드 st7789 spi lcd driver?  (0) 2026.01.16
esp32 c6 zigbee thread matter  (0) 2026.01.10
esp32 benchmark  (0) 2025.11.18
esp32 lvgl  (0) 2025.11.15
Posted by 구차니
이론 관련/전기 전자2026. 1. 12. 10:47

오실로스코프에 대충 연결하고

파워서플라이에서 3.3V 100mA 만들어서 넣음

1채널 노란색(프로브 노란색) - CLK(보라색)

2채널 파란색(프로브 빨간색) - DT(파란색)

 

시계방향(CW)로 돌리니 CLK가 먼저 반응하고

 

반시계방향(CCW)로 돌리니 DT가 먼저 반응한다.

 

위치에 따라서는 falling이 될수도  rising이 될수도 있긴한데..

이걸 어떻게 해석해야 한 틱 옮겨지고, 갑자기 방향이 바뀌는지 알 수 있을까?

'이론 관련 > 전기 전자' 카테고리의 다른 글

엔코더 채터링?  (0) 2026.01.11
쇼트키 다이오드  (0) 2025.11.24
엔코더 관련글  (0) 2025.10.28
오디오 녹음 재머  (0) 2025.09.15
pwm 화음 출력  (0) 2025.08.06
Posted by 구차니
이론 관련/전기 전자2026. 1. 11. 21:42

stm32f103 으로 인터럽트 통해서 처리하려고

rising / falling edge에서 하게 해놨더니, 도대체 어떻게 뜨는건지 모르게 많이 뜬다.

물론 귀차니즘으로 usb cdc를 통해 그냥 바로 출력하게 해놔서 놓치는게 있을 것 같기도 하지만

high가 연속 두번 뜨지 않나 먼가.. 놓치는 느낌인데

USB CDC TEST 1528                                                               
5H                                                                              
4H                                                                              
5H                                                                              
USB CDC TEST 1529                                                               
USB CDC TEST 1530                                                               
4H                                                                              
5L                                                                              
5L                                                                              
4L                                                                              
USB CDC TEST 1531                                                               
USB CDC TEST 1532                                                               
5L                                                                              
4H                                                                              
USB CDC TEST 1533                                                               
USB CDC TEST 1534                                                               
4H                                                                              
5L                                                                              
4H          

                                                              
USB CDC TEST 1622                                                               
USB CDC TEST 1623                                                               
5L                                                                              
5H                                                                              
4H                                                                              
4H                                                                              
5L                                                                              
USB CDC TEST 1624                                                               
USB CDC TEST 1625                                                               
4L                                                                              
USB CDC TEST 1626      

 

A,B,C 모두 내부 풀업으로 하고 있는데, 외부 풀업과 0.1uF 을 달아줘서 해봐야겠다.

 

[링크 : https://retromakers.tistory.com/29] 하드웨어 채터링 방지

[링크 : https://blog.naver.com/kiatwins/221087863980] 하드웨어 채터링 방지

[링크 : https://eteo.tistory.com/138] sw 디바운스

'이론 관련 > 전기 전자' 카테고리의 다른 글

엔코더 파형  (0) 2026.01.12
쇼트키 다이오드  (0) 2025.11.24
엔코더 관련글  (0) 2025.10.28
오디오 녹음 재머  (0) 2025.09.15
pwm 화음 출력  (0) 2025.08.06
Posted by 구차니
개소리 왈왈/컴퓨터2026. 1. 11. 21:20

cox ck87 게이트론 황축

오테뮤와 체리를 들어봤는데 게이트론은 첨 들어보네..

 

뽑아보니 노란색~ LED 이긴 한데 RGB는 아니고 white 단색

 

2021년 제품이라..

 

오테뮤 적축보다는 약간 시끄럽긴 한데 나쁘지 않은 수준.

그나저나.. 텐키리스 이고 멀쩡한데 왜 버려졌지?

꽂아보니 USB A 쪽이 조금 오락가락 하는듯. 커넥터가 헐거워져서 인가..

[링크 : https://m.blog.naver.com/starscatch/222702423137]

Posted by 구차니
embeded/Cortex-M3 STM2026. 1. 11. 16:03

프로젝트 생성해서 좀 가지고 놀려고 하는데, 어... ioc파일이 없다? 

 

그래서 new.. 눌러서 생성가능한걸 보니

stm32 관련해서 많이 사라진 느낌이 든다.

 

검색해보니, cubeide 2.0 되면서 cubeMX 통합이 풀린 최초의 버전이 되었다는데

[링크 : https://community.st.com/t5/stm32cubeide-mcus/cubeide-2-0-does-not-open-ioc-files/td-p/857295]

 

전반적으로 다들 분노에 찬 상황 ㅋㅋㅋ

느린(?) eclipse 사용을 감수하는 이유가 cubeMX 설치없이 cubeIDE 에서 다할수 있어서 였는데

그 장점을 포기한 분리라.. 왜 이런 쓰레기 결정이 통과된거지?

We are pleased to announce that STM32CubeIDE 2.0.0 release is available.
STM32CubeIDE is now independent from STM32CubeMX.
With this release, device configuration, previously handled within STM32CubeIDE via STM32CubeMX, is now exclusively available through the stand-alone STM32CubeMX tool.
Users will configure microcontroller peripherals and generate initialization code separately in STM32CubeMX, then import the generated projects into STM32CubeIDE for coding and debugging.  
STM32CubeIDE, is now a stand-alone tool, offers the possibility to create empty project through its own MCU/Board selectors now present inside.
What does this change mean for developers? 
  • Separate download and installation: STM32CubeMX is no more integrated in STM32CubeIDE, requiring separate download alongside STM32CubeIDE 2.0.0 for configuration and code generation. 
  • Tool update flexibility: Developers can update and freeze STM32CubeMX and STM32CubeIDE independently, with multiple versions of each installed side-by-side. 
  • Memory footprint and performance: Removing STM32CubeMX integration reduces installation size (~3.7GB to 2.9GB) and lowers CPU/RAM usage, improving stability especially on Linux and Mac. 
Other relevant updates in STM32CubeIDE 2.0.0 
  • Microcontroller, microprocessor, and board lists aligned with STM32CubeMX v6.16.0:
    • Added the support for new microcontrollers in the STM32N6 series
    • Added the support for new microcontrollers in the STM32H5 series
    • Added the support for new microcontrollers in the STM32WBA series
    • Added the support for new microcontrollers in the STM32WL3x product line
    • Added the support for new boards: NUCLEO-WL3RKB1 and NUCLEO-WL3RKB2
  • User authentication removed
  • ST-MCU-FINDER-PC removed
  • Support for ST-ARM-CLANG, STMicroelectronics LLVM-based toolchain for Arm®, through site update mechanism
  • Support for GCC 14 toolchain through site update mechanism
  • Support for semihosting feature through debug via ST-LINK GDB server
  • Support for bundled CMake/Ninja binaries
Main fixed issues in 2.0.0:
Refer to the STM32 microcontroller wiki at:
https://wiki.st.com/stm32mcu/wiki/STM32CubeIDE:STM32CubeIDE_errata_2.0.x
Known problems and limitations in 2.0.0:
Refer to the STM32 microcontroller wiki at:
https://wiki.st.com/stm32mcu/wiki/STM32CubeIDE:STM32CubeIDE_errata_2.0.x
How to get STM32CubeIDE 2.0.0:
  • Under this Link 
  • Or update your current installation of STM32CubeIDE using: Help > Check for updates (The in-tool update does not allow updating the drivers and STLINK server tool. Full installation is required for this to be done).

[링크 : https://community.st.com/t5/stm32cubeide-mcus/stm32cubeide-2-0-0-released/td-p/857110#:~:text=With%20this%20release%2C%20device%20configuration%2C%20previously%20handled%20within%C2%A0STM32CubeIDE%C2%A0via%C2%A0STM32CubeMX%2C%20is%20now%20exclusively%20available%20through%20the%20stand%2Dalone%C2%A0STM32CubeMX%C2%A0tool.]

 

현재로선 1.19로 돌아가는게 방법일듯.

Posted by 구차니
embeded/esp322026. 1. 10. 21:24

esp32 c6 에는 특이하게도 802.15.4 radio connectivity 라고 추가되어있는데

통상적으로 zigbee라고 불리던 녀석이라고 한다.

[링크 :https://ko.wikipedia.org/wiki/IEEE_802.15.4]

 

[링크 : https://www.espressif.com/en/products/socs/esp32-c6]

 

zigbee는 대~~충 알겠는데 thread는 먼가 했더니 ipv6 기반 인것 같고

[링크 : https://m.blog.naver.com/icbanq/223718993888]

[링크 : https://m.blog.naver.com/varofla_blog/223035721389]

[링크 : https://techblog.samsung.com/blog/article/30]

 

matter는 L3 / L4 / L7 이라고해야하나, 물리 계층은 아니고 상위 계층 프로토콜이라고 보면 될듯

그래서 zigbee + Thread + matter 라고 언급이 되는 것 같다.

[링크 : https://developers.home.google.com/matter/primer?hl=ko]

[링크 : https://www.varofla.com/ce36796b-438f-4603-9c3a-3b8e647d7783]

 

 

'embeded > esp32' 카테고리의 다른 글

esp32-2432S028 보드 st7789 spi lcd driver?  (0) 2026.01.16
esp32 와 spi lcd 성능 비..교?  (0) 2026.01.12
esp32 benchmark  (0) 2025.11.18
esp32 lvgl  (0) 2025.11.15
esp32 계열 정리  (0) 2025.07.08
Posted by 구차니