

아두이노 PID 제어
@d9e74fa274f5e72c74728e5e8bc7a802aa4606b4
+++ 2Motor_CTRL.ino
... | ... | @@ -0,0 +1,213 @@ |
1 | +// July 30th 10:17 Updated | |
2 | + | |
3 | +// === 1번 모터 핀 정의 === | |
4 | +#define DIR_1 8 // DIR1 (드라이버 갈색 2번, 점퍼선 주황색) | |
5 | +#define ST_1 9 // START/STOP1 (드라이버 핑크 4번, 점퍼선 초록색) | |
6 | +#define PWM_1 45 // SPEED_IN1 (드라이버 주황색 6번, 점퍼선 노란색) | |
7 | +#define ENCA_1 2 // A상 (드라이버 보라색, 점퍼선 파란색) | |
8 | +#define ENCB_1 3 // B상 (드라이버 회색, 점퍼선 흰색) | |
9 | + | |
10 | +// === 2번 모터 핀 정의 === | |
11 | +#define DIR_2 22 // DIR2 (드라이버 갈색 16번, 점퍼선 주황색) | |
12 | +#define ST_2 24 // START/STOP2 (드라이버 핑크 17번, 점퍼선 초록색) | |
13 | +#define PWM_2 44 // SPEED_IN2 (드라이버 주황색 8번, 점퍼선 노란색) | |
14 | +#define ENCA_2 18 // A상 (드라이버 보라색, 점퍼선 파란색) | |
15 | +#define ENCB_2 19 // B상 (드라이버 회색, 점퍼선 흰색) | |
16 | + | |
17 | +// === 목표 RPM (부호 포함) === | |
18 | +int setRPM1 = 100; | |
19 | +int setRPM2 = 0; | |
20 | + | |
21 | +// === PID 계수 (모터별 개별 설정) === | |
22 | +double kp1 = 0.5, ki1 = 3.0, kd1 = 0.085; | |
23 | +double kp2 = 0.0, ki2 = 0.0, kd2 = 0.0; | |
24 | + | |
25 | +// === 모터 상태 변수 === | |
26 | +// 모터 1 | |
27 | +volatile long encoderPos1 = 0; | |
28 | +double rpm1 = 0; | |
29 | +int calPWM1 = 0; | |
30 | +double error1 = 0, prevError1 = 0, integral1 = 0, derivative1 = 0; | |
31 | + | |
32 | +// 모터 2 | |
33 | +volatile long encoderPos2 = 0; | |
34 | +double rpm2 = 0; | |
35 | +int calPWM2 = 0; | |
36 | +double error2 = 0, prevError2 = 0, integral2 = 0, derivative2 = 0; | |
37 | + | |
38 | +// 제어 주기 | |
39 | +const unsigned long interval = 100; // 단위: ms | |
40 | +unsigned long prevTime = 0; | |
41 | +double dt = interval / 1000.0; // ms → 초(s) 단위로 변경 | |
42 | + | |
43 | +// === 인터럽트 핸들러 === | |
44 | +// 모터 1 | |
45 | +void doEncoderA1() { | |
46 | + if (digitalRead(ENCB_1) == LOW) encoderPos1++; | |
47 | + else encoderPos1--; | |
48 | +} | |
49 | +void doEncoderB1() { | |
50 | + if (digitalRead(ENCA_1) == HIGH) encoderPos1++; | |
51 | + else encoderPos1--; | |
52 | +} | |
53 | + | |
54 | +// 모터 2 | |
55 | +void doEncoderA2() { | |
56 | + if (digitalRead(ENCB_2) == LOW) encoderPos2--; | |
57 | + else encoderPos2++; | |
58 | +} | |
59 | +void doEncoderB2() { | |
60 | + if (digitalRead(ENCA_2) == HIGH) encoderPos2--; | |
61 | + else encoderPos2++; | |
62 | +} | |
63 | + | |
64 | +// === RPM 계산 함수 === | |
65 | +double calculateRPM(long counts) { | |
66 | + return ((double)counts / (4096.0 * 2)) * (60.0 / dt); | |
67 | +} | |
68 | + | |
69 | +// === PID 계산 함수 (모터별 계수 전달) === | |
70 | +int computePID(double currentRPM, double &error, double &prevError, | |
71 | + double &integral, double &derivative, int targetRPM, | |
72 | + double kp, double ki, double kd) { | |
73 | + error = targetRPM - currentRPM; | |
74 | + integral += (error * dt); | |
75 | + derivative = (error - prevError) / dt; | |
76 | + | |
77 | + double output = (kp * error) + (ki * integral) + (kd * derivative); | |
78 | + prevError = error; | |
79 | + return (int)output; | |
80 | +} | |
81 | + | |
82 | +// === 실시간 입력 (R숫자 L숫자 형식으로 입력, 음수 가능) === | |
83 | +void serialSetRPM() { | |
84 | + if (Serial.available()) { | |
85 | + String input = Serial.readStringUntil('\n'); | |
86 | + input.trim(); | |
87 | + | |
88 | + int rIndex = input.indexOf('R'); | |
89 | + int lIndex = input.indexOf('L'); | |
90 | + | |
91 | + if (rIndex == -1 && lIndex == -1) { | |
92 | + Serial.println("입력 오류: 'R' 또는 'L'이 포함되어야 합니다. 예: R100 L-80"); | |
93 | + return; | |
94 | + } | |
95 | + | |
96 | + bool validInput = false; | |
97 | + | |
98 | + // === R 처리 === | |
99 | + if (rIndex != -1) { | |
100 | + int end = (lIndex != -1 && lIndex > rIndex) ? lIndex : input.length(); | |
101 | + String rValStr = input.substring(rIndex + 1, end); | |
102 | + rValStr.trim(); | |
103 | + | |
104 | + if (rValStr.length() == 0) { | |
105 | + Serial.println("오류: R 뒤에 숫자가 없습니다. 예: R100"); | |
106 | + } else { | |
107 | + int rpmVal = rValStr.toInt(); | |
108 | + if (String(rpmVal) != rValStr) { | |
109 | + Serial.print("오류: R에 잘못된 숫자 형식 → "); Serial.println(rValStr); | |
110 | + } else if (abs(rpmVal) > 400) { | |
111 | + Serial.println("오류: R 값은 -400 ~ 400 사이여야 합니다."); | |
112 | + } else { | |
113 | + setRPM1 = rpmVal; | |
114 | + Serial.print("모터1 (R) 목표 RPM → "); Serial.println(setRPM1); | |
115 | + validInput = true; | |
116 | + } | |
117 | + } | |
118 | + } | |
119 | + | |
120 | + // === L 처리 === | |
121 | + if (lIndex != -1) { | |
122 | + int end = (rIndex != -1 && rIndex > lIndex) ? rIndex : input.length(); | |
123 | + String lValStr = input.substring(lIndex + 1, end); | |
124 | + lValStr.trim(); | |
125 | + | |
126 | + if (lValStr.length() == 0) { | |
127 | + Serial.println("오류: L 뒤에 숫자가 없습니다. 예: L-80"); | |
128 | + } else { | |
129 | + int rpmVal = lValStr.toInt(); | |
130 | + if (String(rpmVal) != lValStr) { | |
131 | + Serial.print("오류: L에 잘못된 숫자 형식 → "); Serial.println(lValStr); | |
132 | + } else if (abs(rpmVal) > 400) { | |
133 | + Serial.println("오류: L 값은 -400 ~ 400 사이여야 합니다."); | |
134 | + } else { | |
135 | + setRPM2 = rpmVal; | |
136 | + Serial.print("모터2 (L) 목표 RPM → "); Serial.println(setRPM2); | |
137 | + validInput = true; | |
138 | + } | |
139 | + } | |
140 | + } | |
141 | + | |
142 | + if (!validInput) { | |
143 | + Serial.println("R숫자 L숫자 형식으로 입력해주세요. 예: R100 L-80"); | |
144 | + } | |
145 | + } | |
146 | +} | |
147 | + | |
148 | + | |
149 | +void setup() { | |
150 | + Serial.begin(57600); | |
151 | + | |
152 | + // 모터 1 핀 설정 | |
153 | + pinMode(DIR_1, OUTPUT); pinMode(PWM_1, OUTPUT); pinMode(ST_1, OUTPUT); | |
154 | + pinMode(ENCA_1, INPUT_PULLUP); pinMode(ENCB_1, INPUT_PULLUP); | |
155 | + attachInterrupt(digitalPinToInterrupt(ENCA_1), doEncoderA1, RISING); | |
156 | + attachInterrupt(digitalPinToInterrupt(ENCB_1), doEncoderB1, RISING); | |
157 | + | |
158 | + // 모터 2 핀 설정 | |
159 | + pinMode(DIR_2, OUTPUT); pinMode(PWM_2, OUTPUT); pinMode(ST_2, OUTPUT); | |
160 | + pinMode(ENCA_2, INPUT_PULLUP); pinMode(ENCB_2, INPUT_PULLUP); | |
161 | + attachInterrupt(digitalPinToInterrupt(ENCA_2), doEncoderA2, RISING); | |
162 | + attachInterrupt(digitalPinToInterrupt(ENCB_2), doEncoderB2, RISING); | |
163 | + | |
164 | + // 브레이크 해제 | |
165 | + digitalWrite(ST_1, LOW); | |
166 | + digitalWrite(ST_2, LOW); | |
167 | + | |
168 | + prevTime = millis(); | |
169 | + | |
170 | + Serial.println("R±RPM | L±RPM 입력"); | |
171 | +} | |
172 | + | |
173 | +void loop() { | |
174 | + unsigned long now = millis(); | |
175 | + | |
176 | + if (now - prevTime >= interval) { | |
177 | + prevTime += interval; | |
178 | + | |
179 | + // 모터 1 | |
180 | + noInterrupts(); long currentPos1 = encoderPos1; encoderPos1 = 0; interrupts(); | |
181 | + rpm1 = calculateRPM(currentPos1); | |
182 | + calPWM1 = computePID(rpm1, error1, prevError1, integral1, derivative1, abs(setRPM1), kp1, ki1, kd1); | |
183 | + digitalWrite(DIR_1, setRPM1 >= 0 ? HIGH : LOW); | |
184 | + calPWM1 = constrain(abs(calPWM1), 0, 255); | |
185 | + analogWrite(PWM_1, calPWM1); | |
186 | + | |
187 | + // 모터 2 | |
188 | + noInterrupts(); long currentPos2 = encoderPos2; encoderPos2 = 0; interrupts(); | |
189 | + rpm2 = calculateRPM(currentPos2); | |
190 | + calPWM2 = computePID(rpm2, error2, prevError2, integral2, derivative2, abs(setRPM2), kp2, ki2, kd2); | |
191 | + digitalWrite(DIR_2, setRPM2 >= 0 ? HIGH : LOW); | |
192 | + calPWM2 = constrain(abs(calPWM2), 0, 255); | |
193 | + analogWrite(PWM_2, calPWM2); | |
194 | + | |
195 | + // 시리얼 출력 | |
196 | + // Serial.print("R:"); Serial.println(setRPM1); | |
197 | + // Serial.print("RPM:"); Serial.println(rpm1, 2); | |
198 | + // Serial.print("PWM:"); Serial.println(calPWM1); | |
199 | + // Serial.println(" "); | |
200 | + // Serial.print("L:"); Serial.println(setRPM2); | |
201 | + // Serial.print("RPM:"); Serial.println(rpm2, 2); | |
202 | + // Serial.print("PWM:"); Serial.println(calPWM2); | |
203 | + // Serial.println("--------------------"); | |
204 | + | |
205 | + // Serial Plotter용 출력 (탭으로 구분) | |
206 | + Serial.print(setRPM1); Serial.print('\t'); | |
207 | + Serial.println(rpm1, 2); | |
208 | + // Serial.print(setRPM2); Serial.print('\t'); | |
209 | + // Serial.println(rpm2, 2); | |
210 | + } | |
211 | + | |
212 | + serialSetRPM(); | |
213 | +} |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?