{"id":976,"date":"2025-09-03T08:59:52","date_gmt":"2025-09-03T00:59:52","guid":{"rendered":"https:\/\/aimc.skyate.com\/?page_id=976"},"modified":"2025-09-03T09:10:33","modified_gmt":"2025-09-03T01:10:33","slug":"pendulum_pid_control_simulation","status":"publish","type":"page","link":"https:\/\/aimc.skyate.com\/index.php\/pendulum_pid_control_simulation\/","title":{"rendered":"\u3010AIMC LAB\u3011 PID\u5012\u7acb\u6446\u5728\u7ebf\u4eff\u771f\u7cfb\u7edf"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>\u5012\u7acb\u6446\u53ccPID\u63a7\u5236\u4eff\u771f<\/title>\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n    <link href=\"https:\/\/cdn.jsdelivr.net\/npm\/font-awesome@4.7.0\/css\/font-awesome.min.css\" rel=\"stylesheet\">\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.8\/dist\/chart.umd.min.js\"><\/script>\n    \n    <!-- Tailwind\u914d\u7f6e -->\n    <script>\n        tailwind.config = {\n            theme: {\n                extend: {\n                    colors: {\n                        primary: '#3B82F6',\n                        secondary: '#10B981',\n                        accent: '#F59E0B',\n                        danger: '#EF4444',\n                        dark: '#1E293B',\n                        light: '#F8FAFC'\n                    },\n                    fontFamily: {\n                        sans: ['Inter', 'system-ui', 'sans-serif'],\n                    },\n                }\n            }\n        }\n    <\/script>\n    \n    <style type=\"text\/tailwindcss\">\n        @layer utilities {\n            .content-auto {\n                content-visibility: auto;\n            }\n            .slider-thumb {\n                @apply appearance-none w-5 h-5 rounded-full bg-primary cursor-pointer;\n            }\n            .panel {\n                @apply bg-white rounded-xl shadow-md p-4 transition-all duration-300;\n            }\n            .control-panel {\n                @apply panel h-full;\n            }\n            .simulation-panel {\n                @apply panel;\n            }\n            .pid-section {\n                @apply border border-gray-200 rounded-lg p-3 mb-4;\n            }\n        }\n        \n        \/* \u6ed1\u5757\u6837\u5f0f *\/\n        input[type=\"range\"] {\n            @apply w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer;\n        }\n        \n        input[type=\"range\"]::-webkit-slider-thumb {\n            @apply slider-thumb;\n        }\n        \n        input[type=\"range\"]::-moz-range-thumb {\n            @apply slider-thumb;\n        }\n    <\/style>\n<\/head>\n<body class=\"bg-gray-100 font-sans text-dark min-h-screen\">\n    <div class=\"container mx-auto px-4 py-6 max-w-7xl\">\n        <!-- \u6807\u9898\u533a\u57df -->\n        <header class=\"mb-6 text-center\">\n            <h1 class=\"text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-dark mb-2\">\u5012\u7acb\u6446\u53ccPID\u63a7\u5236\u4eff\u771f<\/h1>\n            \n        <\/header>\n        \n        <!-- \u4e3b\u5185\u5bb9\u533a\u57df -->\n        <div class=\"grid grid-cols-1 lg:grid-cols-4 gap-6\">\n            <!-- \u5de6\u4fa7\u63a7\u5236\u9762\u677f - \u7d27\u51d1\u5e03\u5c40 -->\n            <div class=\"lg:col-span-1\">\n                <div class=\"control-panel\">\n                    <!-- \u76ee\u6807\u4f4d\u7f6e\u8bbe\u7f6e -->\n                    <div class=\"mb-6\">\n                        <h2 class=\"text-xl font-semibold mb-4 flex items-center\">\n                            <i class=\"fa fa-bullseye mr-2 text-primary\"><\/i> \u76ee\u6807\u4f4d\u7f6e\n                        <\/h2>\n                        \n                        <div>\n                            <div class=\"flex justify-between mb-1\">\n                                <label for=\"target-position\" class=\"font-medium\">\u671f\u671b\u4f4d\u7f6e (m)<\/label>\n                                <span id=\"target-position-value\" class=\"text-primary font-semibold\">0.00<\/span>\n                            <\/div>\n                            <input \n                                type=\"range\" \n                                id=\"target-position\" \n                                min=\"-2\" \n                                max=\"2\" \n                                step=\"0.1\" \n                                value=\"0\"\n                                class=\"mb-2\"\n                            >\n                            <div class=\"flex justify-between text-xs text-gray-500\">\n                                <span>-2<\/span>\n                                <span>0<\/span>\n                                <span>2<\/span>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                    \n                    <!-- \u89d2\u5ea6\u73afPID\u53c2\u6570\u63a7\u5236 -->\n                    <div class=\"pid-section\">\n                        <h3 class=\"text-lg font-semibold mb-3 flex items-center text-primary\">\n                            <i class=\"fa fa-sliders mr-2\"><\/i> \u89d2\u5ea6\u73afPID\n                        <\/h3>\n                        \n                        <!-- \u89d2\u5ea6\u73afKp\u53c2\u6570 -->\n                        <div class=\"mb-4\">\n                            <div class=\"flex justify-between mb-1\">\n                                <label for=\"angle-kp\" class=\"text-sm font-medium\">Kp<\/label>\n                                <span id=\"angle-kp-value\" class=\"text-primary text-sm font-semibold\">30.00<\/span>\n                            <\/div>\n                            <input \n                                type=\"range\" \n                                id=\"angle-kp\" \n                                min=\"0\" \n                                max=\"100\" \n                                step=\"0.1\" \n                                value=\"30\"\n                                class=\"mb-1\"\n                            >\n                        <\/div>\n                        \n                        <!-- \u89d2\u5ea6\u73afKi\u53c2\u6570 -->\n                        <div class=\"mb-4\">\n                            <div class=\"flex justify-between mb-1\">\n                                <label for=\"angle-ki\" class=\"text-sm font-medium\">Ki<\/label>\n                                <span id=\"angle-ki-value\" class=\"text-primary text-sm font-semibold\">0.50<\/span>\n                            <\/div>\n                            <input \n                                type=\"range\" \n                                id=\"angle-ki\" \n                                min=\"0\" \n                                max=\"5\" \n                                step=\"0.01\" \n                                value=\"0.5\"\n                                class=\"mb-1\"\n                            >\n                        <\/div>\n                        \n                        <!-- \u89d2\u5ea6\u73afKd\u53c2\u6570 -->\n                        <div>\n                            <div class=\"flex justify-between mb-1\">\n                                <label for=\"angle-kd\" class=\"text-sm font-medium\">Kd<\/label>\n                                <span id=\"angle-kd-value\" class=\"text-primary text-sm font-semibold\">5.00<\/span>\n                            <\/div>\n                            <input \n                                type=\"range\" \n                                id=\"angle-kd\" \n                                min=\"0\" \n                                max=\"20\" \n                                step=\"0.1\" \n                                value=\"5\"\n                                class=\"mb-1\"\n                            >\n                        <\/div>\n                    <\/div>\n                    \n                    <!-- \u4f4d\u7f6e\u73afPID\u53c2\u6570\u63a7\u5236 -->\n                    <div class=\"pid-section\">\n                        <h3 class=\"text-lg font-semibold mb-3 flex items-center text-secondary\">\n                            <i class=\"fa fa-sliders mr-2\"><\/i> \u4f4d\u7f6e\u73afPID\n                        <\/h3>\n                        \n                        <!-- \u4f4d\u7f6e\u73afKp\u53c2\u6570 -->\n                        <div class=\"mb-4\">\n                            <div class=\"flex justify-between mb-1\">\n                                <label for=\"pos-kp\" class=\"text-sm font-medium\">Kp<\/label>\n                                <span id=\"pos-kp-value\" class=\"text-secondary text-sm font-semibold\">5.00<\/span>\n                            <\/div>\n                            <input \n                                type=\"range\" \n                                id=\"pos-kp\" \n                                min=\"0\" \n                                max=\"20\" \n                                step=\"0.1\" \n                                value=\"5\"\n                                class=\"mb-1\"\n                            >\n                        <\/div>\n                        \n                        <!-- \u4f4d\u7f6e\u73afKi\u53c2\u6570 -->\n                        <div class=\"mb-4\">\n                            <div class=\"flex justify-between mb-1\">\n                                <label for=\"pos-ki\" class=\"text-sm font-medium\">Ki<\/label>\n                                <span id=\"pos-ki-value\" class=\"text-secondary text-sm font-semibold\">0.20<\/span>\n                            <\/div>\n                            <input \n                                type=\"range\" \n                                id=\"pos-ki\" \n                                min=\"0\" \n                                max=\"2\" \n                                step=\"0.01\" \n                                value=\"0.2\"\n                                class=\"mb-1\"\n                            >\n                        <\/div>\n                        \n                        <!-- \u4f4d\u7f6e\u73afKd\u53c2\u6570 -->\n                        <div>\n                            <div class=\"flex justify-between mb-1\">\n                                <label for=\"pos-kd\" class=\"text-sm font-medium\">Kd<\/label>\n                                <span id=\"pos-kd-value\" class=\"text-secondary text-sm font-semibold\">2.00<\/span>\n                            <\/div>\n                            <input \n                                type=\"range\" \n                                id=\"pos-kd\" \n                                min=\"0\" \n                                max=\"10\" \n                                step=\"0.1\" \n                                value=\"2\"\n                                class=\"mb-1\"\n                            >\n                        <\/div>\n                    <\/div>\n                    \n                    <!-- \u63a7\u5236\u6309\u94ae -->\n                    <div class=\"flex flex-col gap-3 mt-2\">\n                        <button id=\"reset-btn\" class=\"bg-accent hover:bg-amber-500 text-white py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center\">\n                            <i class=\"fa fa-refresh mr-2\"><\/i> \u91cd\u7f6e\u4eff\u771f\n                        <\/button>\n                        <button id=\"pause-btn\" class=\"bg-primary hover:bg-blue-600 text-white py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center\">\n                            <i class=\"fa fa-pause mr-2\"><\/i> \u6682\u505c\n                        <\/button>\n                    <\/div>\n                    \n                    <!-- \u7cfb\u7edf\u72b6\u6001\u663e\u793a - \u7d27\u51d1\u7248 -->\n                    <div class=\"mt-6 border-t border-gray-200 pt-4\">\n                        <h3 class=\"text-lg font-semibold mb-3 flex items-center\">\n                            <i class=\"fa fa-dashboard mr-2 text-dark\"><\/i> \u7cfb\u7edf\u72b6\u6001\n                        <\/h3>\n                        <div class=\"space-y-3 text-sm\">\n                            <div class=\"flex justify-between\">\n                                <span class=\"text-gray-600\">\u65f6\u95f4\uff1a<\/span>\n                                <span id=\"time-display\" class=\"font-mono\">0.00 \u79d2<\/span>\n                            <\/div>\n                            <div class=\"flex justify-between\">\n                                <span class=\"text-gray-600\">\u6446\u89d2\uff1a<\/span>\n                                <span id=\"angle-display\" class=\"font-mono\">0.00\u00b0<\/span>\n                            <\/div>\n                            <div class=\"flex justify-between\">\n                                <span class=\"text-gray-600\">\u4f4d\u7f6e\uff1a<\/span>\n                                <span id=\"position-display\" class=\"font-mono\">0.00 \u7c73<\/span>\n                            <\/div>\n                            <div class=\"flex justify-between\">\n                                <span class=\"text-gray-600\">\u63a7\u5236\u529b\uff1a<\/span>\n                                <span id=\"force-display\" class=\"font-mono\">0.00 \u725b<\/span>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n            \n            <!-- \u53f3\u4fa7\u4eff\u771f\u548c\u56fe\u8868\u533a\u57df -->\n            <div class=\"lg:col-span-3 space-y-6\">\n                <!-- \u4eff\u771f\u533a\u57df -->\n                <div class=\"simulation-panel h-[400px]\">\n                    <h2 class=\"text-xl font-semibold mb-4 flex items-center\">\n                        <i class=\"fa fa-cogs mr-2 text-primary\"><\/i> \u4eff\u771f\u6f14\u793a\n                    <\/h2>\n                    <div class=\"w-full h-[calc(100%-40px)] bg-gray-50 rounded-lg overflow-hidden relative\">\n                        <canvas id=\"simulation-canvas\" class=\"w-full h-full\"><\/canvas>\n                        \n                        <!-- \u4eff\u771f\u72b6\u6001\u6307\u793a -->\n                        <div id=\"paused-indicator\" class=\"absolute top-4 right-4 bg-danger text-white px-3 py-1 rounded-full text-sm font-medium hidden\">\n                            \u5df2\u6682\u505c\n                        <\/div>\n                    <\/div>\n                <\/div>\n                \n                <!-- \u56fe\u8868\u533a\u57df - \u7d27\u51d1\u5e03\u5c40 -->\n                <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n                    <!-- \u6446\u89d2\u548c\u5c0f\u8f66\u4f4d\u7f6e\u66f2\u7ebf -->\n                    <div class=\"panel h-[300px]\">\n                        <h2 class=\"text-lg font-semibold mb-2 flex items-center\">\n                            <i class=\"fa fa-line-chart mr-2 text-secondary\"><\/i> \u7cfb\u7edf\u54cd\u5e94\n                        <\/h2>\n                        <div class=\"h-[calc(100%-30px)]\">\n                            <canvas id=\"system-chart\"><\/canvas>\n                        <\/div>\n                    <\/div>\n                    \n                    <!-- \u89d2\u5ea6\u73afPID\u4e09\u4e2a\u73af\u8282\u7684\u63a7\u5236\u91cf\u66f2\u7ebf -->\n                    <div class=\"panel h-[300px]\">\n                        <h2 class=\"text-lg font-semibold mb-2 flex items-center\">\n                            <i class=\"fa fa-pie-chart mr-2 text-primary\"><\/i> \u89d2\u5ea6\u73afPID\u8d21\u732e\n                        <\/h2>\n                        <div class=\"h-[calc(100%-30px)]\">\n                            <canvas id=\"angle-pid-chart\"><\/canvas>\n                        <\/div>\n                    <\/div>\n                    \n                    <!-- \u4f4d\u7f6e\u73afPID\u4e09\u4e2a\u73af\u8282\u7684\u63a7\u5236\u91cf\u66f2\u7ebf -->\n                    <div class=\"panel h-[300px] md:col-span-2\">\n                        <h2 class=\"text-lg font-semibold mb-2 flex items-center\">\n                            <i class=\"fa fa-pie-chart mr-2 text-secondary\"><\/i> \u4f4d\u7f6e\u73afPID\u8d21\u732e\n                        <\/h2>\n                        <div class=\"h-[calc(100%-30px)]\">\n                            <canvas id=\"position-pid-chart\"><\/canvas>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n        \n        <!-- \u9875\u811a -->\n        <footer class=\"mt-8 text-center text-gray-500 text-sm\">\n            \n        <\/footer>\n    <\/div>\n\n    <script>\n        \/\/ \u7b49\u5f85DOM\u52a0\u8f7d\u5b8c\u6210\n        document.addEventListener('DOMContentLoaded', () => {\n            \/\/ \u83b7\u53d6DOM\u5143\u7d20\n            const canvas = document.getElementById('simulation-canvas');\n            const ctx = canvas.getContext('2d');\n            \n            \/\/ \u76ee\u6807\u4f4d\u7f6e\u63a7\u5236\n            const targetPositionSlider = document.getElementById('target-position');\n            const targetPositionValue = document.getElementById('target-position-value');\n            \n            \/\/ \u89d2\u5ea6\u73afPID\u63a7\u5236\n            const angleKpSlider = document.getElementById('angle-kp');\n            const angleKiSlider = document.getElementById('angle-ki');\n            const angleKdSlider = document.getElementById('angle-kd');\n            const angleKpValue = document.getElementById('angle-kp-value');\n            const angleKiValue = document.getElementById('angle-ki-value');\n            const angleKdValue = document.getElementById('angle-kd-value');\n            \n            \/\/ \u4f4d\u7f6e\u73afPID\u63a7\u5236\n            const posKpSlider = document.getElementById('pos-kp');\n            const posKiSlider = document.getElementById('pos-ki');\n            const posKdSlider = document.getElementById('pos-kd');\n            const posKpValue = document.getElementById('pos-kp-value');\n            const posKiValue = document.getElementById('pos-ki-value');\n            const posKdValue = document.getElementById('pos-kd-value');\n            \n            \/\/ \u63a7\u5236\u6309\u94ae\n            const resetBtn = document.getElementById('reset-btn');\n            const pauseBtn = document.getElementById('pause-btn');\n            const pausedIndicator = document.getElementById('paused-indicator');\n            \n            \/\/ \u72b6\u6001\u663e\u793a\n            const timeDisplay = document.getElementById('time-display');\n            const angleDisplay = document.getElementById('angle-display');\n            const positionDisplay = document.getElementById('position-display');\n            const forceDisplay = document.getElementById('force-display');\n            \n            \/\/ \u8bbe\u7f6eCanvas\u5c3a\u5bf8\n            function resizeCanvas() {\n                const container = canvas.parentElement;\n                canvas.width = container.clientWidth;\n                canvas.height = container.clientHeight;\n            }\n            \n            \/\/ \u521d\u59cb\u5316\u65f6\u8c03\u6574\u4e00\u6b21\u5c3a\u5bf8\uff0c\u5e76\u76d1\u542c\u7a97\u53e3\u5927\u5c0f\u53d8\u5316\n            resizeCanvas();\n            window.addEventListener('resize', resizeCanvas);\n            \n            \/\/ \u7269\u7406\u53c2\u6570\u548c\u63a7\u5236\u53d8\u91cf\n            const system = {\n                \/\/ \u7269\u7406\u53c2\u6570\n                g: 9.8,          \/\/ \u91cd\u529b\u52a0\u901f\u5ea6\n                L: 1.0,          \/\/ \u6446\u957f\n                m: 0.5,          \/\/ \u6446\u8d28\u91cf\n                M: 2.0,          \/\/ \u5c0f\u8f66\u8d28\u91cf\n                dt: 0.02,        \/\/ \u65f6\u95f4\u6b65\u957f\n                dampingPendulum: 0.1,  \/\/ \u6446\u963b\u5c3c\n                dampingCart: 0.1,      \/\/ \u5c0f\u8f66\u963b\u5c3c\n                \n                \/\/ \u72b6\u6001\u53d8\u91cf\n                x: 0.0,          \/\/ \u5c0f\u8f66\u4f4d\u7f6e\n                xDot: 0.0,       \/\/ \u5c0f\u8f66\u901f\u5ea6\n                theta: Math.PI * 0.1,  \/\/ \u6446\u89d2 (\u521d\u59cb\u7a0d\u5fae\u504f\u79bb)\n                thetaDot: 0.0,   \/\/ \u6446\u89d2\u901f\u5ea6\n                time: 0.0,       \/\/ \u4eff\u771f\u65f6\u95f4\n                targetPosition: 0.0,   \/\/ \u76ee\u6807\u4f4d\u7f6e\n                \n                \/\/ \u89d2\u5ea6\u73afPID\u53c2\u6570\n                anglePid: {\n                    kp: 30.0,\n                    ki: 0.5,\n                    kd: 5.0,\n                    integral: 0.0,\n                    prevError: 0.0,\n                    p: 0.0,  \/\/ \u6bd4\u4f8b\u9879\n                    i: 0.0,  \/\/ \u79ef\u5206\u9879\n                    d: 0.0   \/\/ \u5fae\u5206\u9879\n                },\n                \n                \/\/ \u4f4d\u7f6e\u73afPID\u53c2\u6570\n                posPid: {\n                    kp: 5.0,\n                    ki: 0.2,\n                    kd: 2.0,\n                    integral: 0.0,\n                    prevError: 0.0,\n                    p: 0.0,  \/\/ \u6bd4\u4f8b\u9879\n                    i: 0.0,  \/\/ \u79ef\u5206\u9879\n                    d: 0.0   \/\/ \u5fae\u5206\u9879\n                }\n            };\n            \n            \/\/ \u6570\u636e\u8bb0\u5f55\n            const data = {\n                time: [],\n                x: [],\n                theta: [],\n                targetX: [],\n                force: [],\n                \/\/ \u89d2\u5ea6\u73afPID\u6570\u636e\n                angleP: [],\n                angleI: [],\n                angleD: [],\n                \/\/ \u4f4d\u7f6e\u73afPID\u6570\u636e\n                posP: [],\n                posI: [],\n                posD: []\n            };\n            const maxDataPoints = 100;  \/\/ \u6700\u5927\u6570\u636e\u70b9\u6570\u91cf\n            \n            \/\/ \u4eff\u771f\u72b6\u6001\n            let isPaused = false;\n            let animationId = null;\n            \n            \/\/ \u521d\u59cb\u5316\u7cfb\u7edf\u54cd\u5e94\u56fe\u8868\n            const systemChart = new Chart(\n                document.getElementById('system-chart'),\n                {\n                    type: 'line',\n                    data: {\n                        labels: [],\n                        datasets: [\n                            {\n                                label: '\u6446\u89d2 (\u00b0)',\n                                data: [],\n                                borderColor: '#3B82F6',\n                                backgroundColor: 'rgba(59, 130, 246, 0.1)',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1,\n                                yAxisID: 'y'\n                            },\n                            {\n                                label: '\u5c0f\u8f66\u4f4d\u7f6e (m)',\n                                data: [],\n                                borderColor: '#EF4444',\n                                backgroundColor: 'rgba(239, 68, 68, 0.1)',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1,\n                                yAxisID: 'y1'\n                            },\n                            {\n                                label: '\u76ee\u6807\u4f4d\u7f6e (m)',\n                                data: [],\n                                borderColor: '#10B981',\n                                borderWidth: 2,\n                                borderDash: [5, 5],\n                                fill: false,\n                                tension: 0,\n                                yAxisID: 'y1'\n                            }\n                        ]\n                    },\n                    options: {\n                        responsive: true,\n                        maintainAspectRatio: false,\n                        scales: {\n                            x: {\n                                title: {\n                                    display: true,\n                                    text: '\u65f6\u95f4 (\u79d2)'\n                                }\n                            },\n                            y: {\n                                type: 'linear',\n                                display: true,\n                                position: 'left',\n                                title: {\n                                    display: true,\n                                    text: '\u89d2\u5ea6 (\u00b0)'\n                                },\n                                min: -90,\n                                max: 90\n                            },\n                            y1: {\n                                type: 'linear',\n                                display: true,\n                                position: 'right',\n                                title: {\n                                    display: true,\n                                    text: '\u4f4d\u7f6e (\u7c73)'\n                                },\n                                min: -3,\n                                max: 3,\n                                grid: {\n                                    drawOnChartArea: false\n                                }\n                            }\n                        },\n                        interaction: {\n                            mode: 'index',\n                            intersect: false\n                        }\n                    }\n                }\n            );\n            \n            \/\/ \u89d2\u5ea6\u73afPID\u8d21\u732e\u56fe\u8868\n            const anglePidChart = new Chart(\n                document.getElementById('angle-pid-chart'),\n                {\n                    type: 'line',\n                    data: {\n                        labels: [],\n                        datasets: [\n                            {\n                                label: '\u89d2\u5ea6\u73af P',\n                                data: [],\n                                borderColor: '#3B82F6',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1\n                            },\n                            {\n                                label: '\u89d2\u5ea6\u73af I',\n                                data: [],\n                                borderColor: '#10B981',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1\n                            },\n                            {\n                                label: '\u89d2\u5ea6\u73af D',\n                                data: [],\n                                borderColor: '#F59E0B',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1\n                            }\n                        ]\n                    },\n                    options: {\n                        responsive: true,\n                        maintainAspectRatio: false,\n                        scales: {\n                            x: {\n                                title: {\n                                    display: true,\n                                    text: '\u65f6\u95f4 (\u79d2)'\n                                }\n                            },\n                            y: {\n                                title: {\n                                    display: true,\n                                    text: '\u63a7\u5236\u8d21\u732e'\n                                }\n                            }\n                        },\n                        interaction: {\n                            mode: 'index',\n                            intersect: false\n                        }\n                    }\n                }\n            );\n            \n            \/\/ \u4f4d\u7f6e\u73afPID\u8d21\u732e\u56fe\u8868\n            const positionPidChart = new Chart(\n                document.getElementById('position-pid-chart'),\n                {\n                    type: 'line',\n                    data: {\n                        labels: [],\n                        datasets: [\n                            {\n                                label: '\u4f4d\u7f6e\u73af P',\n                                data: [],\n                                borderColor: '#3B82F6',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1\n                            },\n                            {\n                                label: '\u4f4d\u7f6e\u73af I',\n                                data: [],\n                                borderColor: '#10B981',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1\n                            },\n                            {\n                                label: '\u4f4d\u7f6e\u73af D',\n                                data: [],\n                                borderColor: '#F59E0B',\n                                borderWidth: 2,\n                                fill: false,\n                                tension: 0.1\n                            }\n                        ]\n                    },\n                    options: {\n                        responsive: true,\n                        maintainAspectRatio: false,\n                        scales: {\n                            x: {\n                                title: {\n                                    display: true,\n                                    text: '\u65f6\u95f4 (\u79d2)'\n                                }\n                            },\n                            y: {\n                                title: {\n                                    display: true,\n                                    text: '\u63a7\u5236\u8d21\u732e'\n                                }\n                            }\n                        },\n                        interaction: {\n                            mode: 'index',\n                            intersect: false\n                        }\n                    }\n                }\n            );\n            \n            \/\/ \u89d2\u5ea6\u73afPID\u63a7\u5236\u5668 - \u8f93\u51fa\u76ee\u6807\u89d2\u5ea6\n            function anglePidController(error) {\n                \/\/ \u6bd4\u4f8b\u9879\n                const p = system.anglePid.kp * error;\n                \n                \/\/ \u79ef\u5206\u9879 (\u5e26\u6297\u79ef\u5206\u9971\u548c)\n                system.anglePid.integral += error * system.dt;\n                system.anglePid.integral = Math.max(-5, Math.min(5, system.anglePid.integral)); \/\/ \u9650\u5e45\n                const i = system.anglePid.ki * system.anglePid.integral;\n                \n                \/\/ \u5fae\u5206\u9879\n                const d = system.anglePid.kd * (error - system.anglePid.prevError) \/ system.dt;\n                system.anglePid.prevError = error;\n                \n                \/\/ \u8bb0\u5f55\u5404\u73af\u8282\u503c\n                system.anglePid.p = p;\n                system.anglePid.i = i;\n                system.anglePid.d = d;\n                \n                \/\/ \u8f93\u51fa\u76ee\u6807\u89d2\u5ea6 (\u5e26\u9650\u5e45)\n                const targetAngle = p + i + d;\n                return Math.max(-0.3, Math.min(0.3, targetAngle)); \/\/ \u9650\u5236\u6700\u5927\u89d2\u5ea6\u53d8\u5316\n            }\n            \n            \/\/ \u4f4d\u7f6e\u73afPID\u63a7\u5236\u5668 - \u8f93\u51fa\u63a7\u5236\u529b\n            function positionPidController(error) {\n                \/\/ \u6bd4\u4f8b\u9879\n                const p = system.posPid.kp * error;\n                \n                \/\/ \u79ef\u5206\u9879 (\u5e26\u6297\u79ef\u5206\u9971\u548c)\n                system.posPid.integral += error * system.dt;\n                system.posPid.integral = Math.max(-10, Math.min(10, system.posPid.integral)); \/\/ \u9650\u5e45\n                const i = system.posPid.ki * system.posPid.integral;\n                \n                \/\/ \u5fae\u5206\u9879\n                const d = system.posPid.kd * (error - system.posPid.prevError) \/ system.dt;\n                system.posPid.prevError = error;\n                \n                \/\/ \u8bb0\u5f55\u5404\u73af\u8282\u503c\n                system.posPid.p = p;\n                system.posPid.i = i;\n                system.posPid.d = d;\n                \n                \/\/ \u603b\u63a7\u5236\u529b (\u5e26\u9650\u5e45)\n                const force = p + i + d;\n                return Math.max(-50, Math.min(50, force));\n            }\n            \n            \/\/ \u53cc\u73afPID\u63a7\u5236\n            function dualLoopControl() {\n                \/\/ \u89d2\u5ea6\u73af\uff1a\u4fdd\u6301\u6446\u6746\u7ad6\u76f4\uff0c\u8f93\u51fa\u76ee\u6807\u89d2\u5ea6\n                const angleError = -system.theta; \/\/ \u76ee\u6807\u662ftheta=0\uff08\u7ad6\u76f4\uff09\n                const targetAngle = anglePidController(angleError);\n                \n                \/\/ \u4f4d\u7f6e\u73af\uff1a\u63a7\u5236\u5c0f\u8f66\u5230\u8fbe\u76ee\u6807\u4f4d\u7f6e\uff0c\u4f7f\u7528\u89d2\u5ea6\u73af\u8f93\u51fa\u4f5c\u4e3a\u524d\u9988\n                const positionError = system.targetPosition - system.x;\n                const baseForce = positionPidController(positionError);\n                \n                \/\/ \u7ec4\u5408\u63a7\u5236\uff1a\u4f4d\u7f6e\u73af\u8f93\u51fa\u52a0\u4e0a\u89d2\u5ea6\u73af\u7684\u8865\u507f\n                const force = baseForce - targetAngle * 100; \/\/ \u89d2\u5ea6\u8865\u507f\u7cfb\u6570\n                \n                return force;\n            }\n            \n            \/\/ \u66f4\u65b0\u7269\u7406\u72b6\u6001\n            function updatePhysics(force) {\n                const sinTheta = Math.sin(system.theta);\n                const cosTheta = Math.cos(system.theta);\n                \n                \/\/ \u5c0f\u8f66\u52a0\u901f\u5ea6\n                const xDDotNumerator = force - system.m * system.L * Math.pow(system.thetaDot, 2) * sinTheta +\n                                      system.m * system.g * sinTheta * cosTheta;\n                const xDDenominator = system.M + system.m - system.m * Math.pow(sinTheta, 2);\n                const xDDot = xDDotNumerator \/ xDDenominator - system.dampingCart * system.xDot;\n                \n                \/\/ \u6446\u89d2\u52a0\u901f\u5ea6\n                const thetaDDotNumerator = -system.g * sinTheta - cosTheta * xDDot;\n                const thetaDDot = thetaDDotNumerator \/ system.L - system.dampingPendulum * system.thetaDot;\n                \n                \/\/ \u66f4\u65b0\u901f\u5ea6\n                system.xDot += xDDot * system.dt;\n                system.thetaDot += thetaDDot * system.dt;\n                \n                \/\/ \u66f4\u65b0\u4f4d\u7f6e\u548c\u89d2\u5ea6\n                system.x += system.xDot * system.dt;\n                system.theta += system.thetaDot * system.dt;\n                \n                \/\/ \u9650\u5236\u5c0f\u8f66\u4f4d\u7f6e\n                const bound = 3.0;\n                if (system.x < -bound) {\n                    system.x = -bound;\n                    system.xDot = 0;\n                } else if (system.x > bound) {\n                    system.x = bound;\n                    system.xDot = 0;\n                }\n            }\n            \n            \/\/ \u8bb0\u5f55\u6570\u636e\n            function recordData(force) {\n                data.time.push(system.time.toFixed(2));\n                data.x.push(system.x.toFixed(4));\n                data.theta.push((system.theta * 180 \/ Math.PI).toFixed(4));\n                data.targetX.push(system.targetPosition.toFixed(4));\n                data.force.push(force.toFixed(4));\n                \n                \/\/ \u8bb0\u5f55PID\u5404\u73af\u8282\u6570\u636e\n                data.angleP.push(system.anglePid.p.toFixed(4));\n                data.angleI.push(system.anglePid.i.toFixed(4));\n                data.angleD.push(system.anglePid.d.toFixed(4));\n                \n                data.posP.push(system.posPid.p.toFixed(4));\n                data.posI.push(system.posPid.i.toFixed(4));\n                data.posD.push(system.posPid.d.toFixed(4));\n                \n                \/\/ \u9650\u5236\u6570\u636e\u70b9\u6570\u91cf\n                if (data.time.length > maxDataPoints) {\n                    for (const key in data) {\n                        data[key].shift();\n                    }\n                }\n                \n                \/\/ \u66f4\u65b0\u56fe\u8868\n                updateCharts();\n            }\n            \n            \/\/ \u66f4\u65b0\u56fe\u8868\u6570\u636e\n            function updateCharts() {\n                \/\/ \u66f4\u65b0\u7cfb\u7edf\u54cd\u5e94\u56fe\u8868\n                systemChart.data.labels = [...data.time];\n                systemChart.data.datasets[0].data = [...data.theta];\n                systemChart.data.datasets[1].data = [...data.x];\n                systemChart.data.datasets[2].data = [...data.targetX];\n                systemChart.update();\n                \n                \/\/ \u66f4\u65b0\u89d2\u5ea6\u73afPID\u56fe\u8868\n                anglePidChart.data.labels = [...data.time];\n                anglePidChart.data.datasets[0].data = [...data.angleP];\n                anglePidChart.data.datasets[1].data = [...data.angleI];\n                anglePidChart.data.datasets[2].data = [...data.angleD];\n                anglePidChart.update();\n                \n                \/\/ \u66f4\u65b0\u4f4d\u7f6e\u73afPID\u56fe\u8868\n                positionPidChart.data.labels = [...data.time];\n                positionPidChart.data.datasets[0].data = [...data.posP];\n                positionPidChart.data.datasets[1].data = [...data.posI];\n                positionPidChart.data.datasets[2].data = [...data.posD];\n                positionPidChart.update();\n            }\n            \n            \/\/ \u7ed8\u5236\u4eff\u771f\n            function drawSimulation() {\n                \/\/ \u6e05\u7a7a\u753b\u5e03\n                ctx.clearRect(0, 0, canvas.width, canvas.height);\n                \n                \/\/ \u7ed8\u5236\u80cc\u666f\n                ctx.fillStyle = '#F8FAFC';\n                ctx.fillRect(0, 0, canvas.width, canvas.height);\n                \n                \/\/ \u7ed8\u5236\u5730\u9762\n                const groundY = canvas.height * 0.7;\n                ctx.fillStyle = '#E0F2FE';\n                ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY);\n                \n                \/\/ \u7ed8\u5236\u5730\u9762\u7ebf\n                ctx.strokeStyle = '#94A3B8';\n                ctx.lineWidth = 2;\n                ctx.beginPath();\n                ctx.moveTo(0, groundY);\n                ctx.lineTo(canvas.width, groundY);\n                ctx.stroke();\n                \n                \/\/ \u7ed8\u5236\u4e2d\u7ebf\u53c2\u8003\u7ebf\n                const midX = canvas.width \/ 2;\n                ctx.strokeStyle = '#94A3B8';\n                ctx.lineWidth = 1;\n                ctx.setLineDash([5, 5]);\n                ctx.beginPath();\n                ctx.moveTo(midX, canvas.height * 0.2);\n                ctx.lineTo(midX, groundY);\n                ctx.stroke();\n                \n                \/\/ \u7ed8\u5236\u76ee\u6807\u4f4d\u7f6e\u7ebf\n                const targetX = midX + system.targetPosition * (canvas.width * 0.1);\n                ctx.strokeStyle = '#10B981';\n                ctx.lineWidth = 2;\n                ctx.setLineDash([5, 5]);\n                ctx.beginPath();\n                ctx.moveTo(targetX, canvas.height * 0.2);\n                ctx.lineTo(targetX, groundY);\n                ctx.stroke();\n                ctx.setLineDash([]);\n                \n                \/\/ \u7f29\u653e\u548c\u8f6c\u6362\n                const scale = canvas.width * 0.1; \/\/ \u7f29\u653e\u56e0\u5b50\n                const cartWidth = 60;\n                const cartHeight = 30;\n                const pendulumLength = system.L * scale;\n                \n                \/\/ \u5c0f\u8f66\u4f4d\u7f6e\uff08\u5c45\u4e2d\uff09\n                const cartX = midX + system.x * scale;\n                \n                \/\/ \u7ed8\u5236\u5c0f\u8f66\n                \/\/ \u5c0f\u8f66\u5e95\u90e8\n                ctx.fillStyle = '#3B82F6';\n                ctx.fillRect(cartX - cartWidth\/2, groundY - cartHeight, cartWidth, cartHeight);\n                \n                \/\/ \u5c0f\u8f66\u9876\u90e8\uff083D\u6548\u679c\uff09\n                ctx.fillStyle = '#2563EB';\n                ctx.fillRect(cartX - cartWidth\/2, groundY - cartHeight, cartWidth, 5);\n                \n                \/\/ \u7ed8\u5236\u8f66\u8f6e\n                const wheelRadius = 8;\n                const wheelOffset = cartWidth \/ 3;\n                \n                \/\/ \u8f66\u8f6e\u9634\u5f71\n                ctx.fillStyle = '#64748B';\n                ctx.beginPath();\n                ctx.arc(cartX - wheelOffset, groundY + 2, wheelRadius + 2, 0, Math.PI * 2);\n                ctx.arc(cartX + wheelOffset, groundY + 2, wheelRadius + 2, 0, Math.PI * 2);\n                ctx.fill();\n                \n                \/\/ \u8f66\u8f6e\n                ctx.fillStyle = '#1E293B';\n                ctx.beginPath();\n                ctx.arc(cartX - wheelOffset, groundY, wheelRadius, 0, Math.PI * 2);\n                ctx.arc(cartX + wheelOffset, groundY, wheelRadius, 0, Math.PI * 2);\n                ctx.fill();\n                \n                \/\/ \u8f66\u8f6e\u8f6e\u6bc2\n                ctx.fillStyle = '#94A3B8';\n                ctx.beginPath();\n                ctx.arc(cartX - wheelOffset, groundY, wheelRadius \/ 3, 0, Math.PI * 2);\n                ctx.arc(cartX + wheelOffset, groundY, wheelRadius \/ 3, 0, Math.PI * 2);\n                ctx.fill();\n                \n                \/\/ \u6446\u7684\u7aef\u70b9\u4f4d\u7f6e\n                const pendulumX = cartX + pendulumLength * Math.sin(system.theta);\n                const pendulumY = groundY - cartHeight\/2 - pendulumLength * Math.cos(system.theta);\n                \n                \/\/ \u7ed8\u5236\u6446\u6746\n                ctx.strokeStyle = '#EF4444';\n                ctx.lineWidth = 4;\n                ctx.beginPath();\n                ctx.moveTo(cartX, groundY - cartHeight\/2);\n                ctx.lineTo(pendulumX, pendulumY);\n                ctx.stroke();\n                \n                \/\/ \u7ed8\u5236\u6446\u7403\n                const bobRadius = 12;\n                \n                \/\/ \u6446\u7403\u9634\u5f71\n                ctx.fillStyle = '#DC2626';\n                ctx.beginPath();\n                ctx.arc(pendulumX + 2, pendulumY + 2, bobRadius, 0, Math.PI * 2);\n                ctx.fill();\n                \n                \/\/ \u6446\u7403\u4e3b\u4f53\n                ctx.fillStyle = '#EF4444';\n                ctx.beginPath();\n                ctx.arc(pendulumX, pendulumY, bobRadius, 0, Math.PI * 2);\n                ctx.fill();\n                \n                \/\/ \u6446\u7403\u9ad8\u5149\n                ctx.fillStyle = '#F87171';\n                ctx.beginPath();\n                ctx.arc(pendulumX - 4, pendulumY - 4, bobRadius \/ 3, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            \n            \/\/ \u66f4\u65b0\u663e\u793a\n            function updateDisplays(force) {\n                timeDisplay.textContent = `${system.time.toFixed(2)} \u79d2`;\n                angleDisplay.textContent = `${(system.theta * 180 \/ Math.PI).toFixed(2)}\u00b0`;\n                positionDisplay.textContent = `${system.x.toFixed(2)} \u7c73`;\n                forceDisplay.textContent = `${force.toFixed(2)} \u725b`;\n            }\n            \n            \/\/ \u4eff\u771f\u5faa\u73af\n            function simulate() {\n                if (!isPaused) {\n                    \/\/ \u8ba1\u7b97\u63a7\u5236\u529b\uff08\u53cc\u73afPID\uff09\n                    const force = dualLoopControl();\n                    \n                    \/\/ \u66f4\u65b0\u7269\u7406\u72b6\u6001\n                    updatePhysics(force);\n                    \n                    \/\/ \u8bb0\u5f55\u6570\u636e\n                    recordData(force);\n                    \n                    \/\/ \u66f4\u65b0\u65f6\u95f4\n                    system.time += system.dt;\n                    \n                    \/\/ \u66f4\u65b0\u663e\u793a\n                    updateDisplays(force);\n                }\n                \n                \/\/ \u7ed8\u5236\u4eff\u771f\n                drawSimulation();\n                \n                \/\/ \u7ee7\u7eed\u5faa\u73af\n                animationId = requestAnimationFrame(simulate);\n            }\n            \n            \/\/ \u91cd\u7f6e\u4eff\u771f\n            function resetSimulation() {\n                system.x = 0.0;\n                system.xDot = 0.0;\n                system.theta = Math.PI * 0.1;\n                system.thetaDot = 0.0;\n                system.time = 0.0;\n                \n                \/\/ \u91cd\u7f6ePID\u53c2\u6570\n                system.anglePid.integral = 0.0;\n                system.anglePid.prevError = 0.0;\n                system.posPid.integral = 0.0;\n                system.posPid.prevError = 0.0;\n                \n                \/\/ \u91cd\u7f6e\u76ee\u6807\u4f4d\u7f6e\u4e3a\u5f53\u524d\u6ed1\u5757\u503c\n                system.targetPosition = parseFloat(targetPositionSlider.value);\n                \n                \/\/ \u6e05\u7a7a\u6570\u636e\n                for (const key in data) {\n                    data[key] = [];\n                }\n                \n                \/\/ \u66f4\u65b0\u56fe\u8868\n                updateCharts();\n                \n                \/\/ \u66f4\u65b0\u663e\u793a\n                updateDisplays(0);\n            }\n            \n            \/\/ \u6682\u505c\/\u7ee7\u7eed\u4eff\u771f\n            function togglePause() {\n                isPaused = !isPaused;\n                pauseBtn.innerHTML = isPaused \n                    ? '<i class=\"fa fa-play mr-2\"><\/i> \u7ee7\u7eed' \n                    : '<i class=\"fa fa-pause mr-2\"><\/i> \u6682\u505c';\n                \n                pausedIndicator.classList.toggle('hidden', !isPaused);\n            }\n            \n            \/\/ \u4e8b\u4ef6\u76d1\u542c\n            \/\/ \u76ee\u6807\u4f4d\u7f6e\n            targetPositionSlider.addEventListener('input', () => {\n                system.targetPosition = parseFloat(targetPositionSlider.value);\n                targetPositionValue.textContent = system.targetPosition.toFixed(2);\n            });\n            \n            \/\/ \u89d2\u5ea6\u73afPID\n            angleKpSlider.addEventListener('input', () => {\n                system.anglePid.kp = parseFloat(angleKpSlider.value);\n                angleKpValue.textContent = system.anglePid.kp.toFixed(2);\n            });\n            \n            angleKiSlider.addEventListener('input', () => {\n                system.anglePid.ki = parseFloat(angleKiSlider.value);\n                angleKiValue.textContent = system.anglePid.ki.toFixed(2);\n            });\n            \n            angleKdSlider.addEventListener('input', () => {\n                system.anglePid.kd = parseFloat(angleKdSlider.value);\n                angleKdValue.textContent = system.anglePid.kd.toFixed(2);\n            });\n            \n            \/\/ \u4f4d\u7f6e\u73afPID\n            posKpSlider.addEventListener('input', () => {\n                system.posPid.kp = parseFloat(posKpSlider.value);\n                posKpValue.textContent = system.posPid.kp.toFixed(2);\n            });\n            \n            posKiSlider.addEventListener('input', () => {\n                system.posPid.ki = parseFloat(posKiSlider.value);\n                posKiValue.textContent = system.posPid.ki.toFixed(2);\n            });\n            \n            posKdSlider.addEventListener('input', () => {\n                system.posPid.kd = parseFloat(posKdSlider.value);\n                posKdValue.textContent = system.posPid.kd.toFixed(2);\n            });\n            \n            resetBtn.addEventListener('click', resetSimulation);\n            pauseBtn.addEventListener('click', togglePause);\n            \n            \/\/ \u5f00\u59cb\u4eff\u771f\n            simulate();\n            \n            \/\/ \u6e05\u7406\u51fd\u6570\n            window.addEventListener('beforeunload', () => {\n                cancelAnimationFrame(animationId);\n            });\n        });\n    <\/script>\n<\/body>\n<\/html>\n    \n","protected":false},"excerpt":{"rendered":"<p>\u5012\u7acb\u6446\u53ccPID\u63a7\u5236\u4eff\u771f \u5012\u7acb\u6446\u53ccPID\u63a7\u5236\u4eff\u771f \u76ee\u6807\u4f4d\u7f6e \u671f\u671b\u4f4d\u7f6e (m) 0.00 -2 0 2 \u89d2\u5ea6\u73afPI [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-976","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/pages\/976","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/comments?post=976"}],"version-history":[{"count":3,"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/pages\/976\/revisions"}],"predecessor-version":[{"id":979,"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/pages\/976\/revisions\/979"}],"wp:attachment":[{"href":"https:\/\/aimc.skyate.com\/index.php\/wp-json\/wp\/v2\/media?parent=976"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}