
+++ README.md
... | ... | @@ -0,0 +1,1 @@ |
1 | +# 아두이노-5p1축-로봇-펌웨어 |
+++ firmware.ino
... | ... | @@ -0,0 +1,271 @@ |
1 | +/* | |
2 | + * Multi Servo Control via Serial (JSON-like format) | |
3 | + * | |
4 | + * Receives commands over Serial monitor to control multiple servos. | |
5 | + * Expected command format: {S<index>:<angle>,S<index>:<angle>,...} | |
6 | + * Example: {S0:90,S1:45,S5:180} | |
7 | + * | |
8 | + * - Validates the overall { } structure. | |
9 | + * - Parses comma-separated segments. | |
10 | + * - Validates each segment format (S<index>:<angle>). | |
11 | + * - Uses robust integer parsing (strtol) for index and angle. | |
12 | + * - Checks if servo index is within valid range. | |
13 | + * - Constrains requested angle to servo's min/max limits. | |
14 | + * - Controls the servos. | |
15 | + * - Provides detailed feedback and error messages via Serial. | |
16 | + * | |
17 | + * Refactored based on discussions to improve structure, robustness, and readability. | |
18 | + */ | |
19 | + | |
20 | +#include <Servo.h> // Include the Servo library | |
21 | +#include <errno.h> // For strtol error checking | |
22 | +#include <stdlib.h> // For strtol | |
23 | +#include <limits.h> // For INT_MIN, INT_MAX | |
24 | + | |
25 | +// --- User Defined Constants --- | |
26 | +const int SERVO_NUM = 6; // Total number of servos | |
27 | + | |
28 | +// Maximum angle limits for each servo (adjust as needed) | |
29 | +const int SERVO_MAX[SERVO_NUM] = { | |
30 | + 180, 180, 180, 180, 180, 180 | |
31 | +}; | |
32 | + | |
33 | +// Minimum angle limits for each servo (adjust as needed) | |
34 | +const int SERVO_MIN[SERVO_NUM] = { | |
35 | + 0, 0, 0, 0, 0, 0 | |
36 | +}; | |
37 | + | |
38 | +// Arduino pins connected to each servo's signal line | |
39 | +// Ensure these pins support PWM (usually marked with ~ on Arduino Uno/Nano) | |
40 | +const int SERVO_PIN[SERVO_NUM] = { | |
41 | + 3, 2, 8, 9, 12, 13 | |
42 | +}; | |
43 | +// --- End Constants --- | |
44 | + | |
45 | +// Array to hold the Servo objects | |
46 | +Servo myServos[SERVO_NUM]; | |
47 | + | |
48 | +// --- Helper Function for Robust Integer Parsing --- | |
49 | +// Attempts to parse the entire String s as an integer. | |
50 | +// Returns true on success, false otherwise. | |
51 | +// Stores the result in 'value' on success. | |
52 | +bool parseStringToInt(const String& s, int& value) { | |
53 | + if (s.length() == 0) { | |
54 | + return false; // Empty string is not a valid number | |
55 | + } | |
56 | + // Get C-style string for strtol | |
57 | + const char* s_cstr = s.c_str(); | |
58 | + char* endptr; | |
59 | + errno = 0; // Reset error number | |
60 | + | |
61 | + long result = strtol(s_cstr, &endptr, 10); // Base 10 | |
62 | + | |
63 | + // Check for errors: | |
64 | + // 1. Did strtol encounter an error (e.g., overflow)? | |
65 | + // 2. Was the entire string consumed? (endptr should point to the null terminator '\0') | |
66 | + // 3. Check if the result fits within an int range (strtol returns long) | |
67 | + if (errno != 0 || *endptr != '\0' || result < INT_MIN || result > INT_MAX) { | |
68 | + return false; // Conversion failed, didn't consume the whole string, or out of int range | |
69 | + } | |
70 | + | |
71 | + value = (int)result; | |
72 | + return true; | |
73 | +} | |
74 | + | |
75 | +// --- Function to Process a Single Command Segment (e.g., "S0:90") --- | |
76 | +// Modifies 'firstSegmentProcessed' flag to control comma printing in feedback. | |
77 | +void processSegment(const String& segment, bool& firstSegmentProcessed) { | |
78 | + // --- Flags for Validation --- | |
79 | + bool segmentIsValid = true; // Assume valid initially for this segment | |
80 | + String errorMessage = ""; | |
81 | + int servoIndex = -1; | |
82 | + int requestedAngle = 0; | |
83 | + int constrainedAngle = 0; | |
84 | + | |
85 | + // --- Basic Format Check --- | |
86 | + int colonIndex = segment.indexOf(':'); | |
87 | + | |
88 | + if (!(segment.length() > 2 && segment.startsWith("S"))) { | |
89 | + segmentIsValid = false; | |
90 | + errorMessage = "[Invalid Segment Format: Doesn't start with 'S' or too short]"; | |
91 | + } else if (!(colonIndex > 1 && colonIndex < segment.length() - 1)) { | |
92 | + segmentIsValid = false; | |
93 | + errorMessage = "[Malformed Segment: Missing or misplaced colon]"; | |
94 | + } else { | |
95 | + // --- Segment format looks okay, parse Index and Angle --- | |
96 | + String indexStr = segment.substring(1, colonIndex); | |
97 | + String angleStr = segment.substring(colonIndex + 1); | |
98 | + | |
99 | + int parsedIndex; | |
100 | + int parsedAngle; | |
101 | + | |
102 | + bool indexParsedOk = parseStringToInt(indexStr, parsedIndex); | |
103 | + bool angleParsedOk = false; // Parsed only if index is valid | |
104 | + bool indexInRange = false; // Check only if index parsing was okay | |
105 | + | |
106 | + if (!indexParsedOk) { | |
107 | + segmentIsValid = false; | |
108 | + errorMessage = "[Invalid Servo Index: Not a valid number]"; | |
109 | + } else { | |
110 | + // Index parsed, now check range | |
111 | + if (parsedIndex >= 0 && parsedIndex < SERVO_NUM) { | |
112 | + indexInRange = true; | |
113 | + // Index is valid and in range, try parsing the angle | |
114 | + angleParsedOk = parseStringToInt(angleStr, parsedAngle); | |
115 | + if (!angleParsedOk) { | |
116 | + segmentIsValid = false; | |
117 | + errorMessage = "[Invalid Angle: Not a valid number]"; | |
118 | + } | |
119 | + } else { | |
120 | + segmentIsValid = false; | |
121 | + errorMessage = "[Invalid Servo Index: Out of range (0-" + String(SERVO_NUM - 1) + ")]"; | |
122 | + } | |
123 | + } | |
124 | + | |
125 | + // If all checks passed so far, store values and constrain angle | |
126 | + if (segmentIsValid) { | |
127 | + servoIndex = parsedIndex; | |
128 | + requestedAngle = parsedAngle; | |
129 | + // Constrain the angle using servo-specific limits | |
130 | + constrainedAngle = constrain(requestedAngle, SERVO_MIN[servoIndex], SERVO_MAX[servoIndex]); | |
131 | + } | |
132 | + } | |
133 | + | |
134 | + // --- Act based on validation and Print Feedback --- | |
135 | + // Print comma separator if not the first valid/invalid segment processed | |
136 | + if (!firstSegmentProcessed) Serial.print(", "); | |
137 | + | |
138 | + if (segmentIsValid) { | |
139 | + // *** Robot Direct Control is Here *** | |
140 | + myServos[servoIndex].write(constrainedAngle); | |
141 | + | |
142 | + // Feedback for success | |
143 | + Serial.print("S"); Serial.print(servoIndex); Serial.print(":"); Serial.print(constrainedAngle); | |
144 | + if (requestedAngle != constrainedAngle) { | |
145 | + // Show if the angle was constrained | |
146 | + Serial.print("(<-"); Serial.print(requestedAngle); Serial.print(")"); | |
147 | + } | |
148 | + } else { | |
149 | + // Feedback for error | |
150 | + Serial.print(errorMessage); Serial.print(" '"); Serial.print(segment); Serial.print("'"); | |
151 | + } | |
152 | + // Mark that at least one segment (valid or invalid) has been processed and outputted | |
153 | + firstSegmentProcessed = false; | |
154 | +} | |
155 | + | |
156 | + | |
157 | +// --- Arduino Setup Function --- | |
158 | +void setup() { | |
159 | + // Start Serial communication (baud rate should match Serial Monitor setting) | |
160 | + Serial.begin(57600); | |
161 | + // Wait for Serial port to connect (needed for native USB like Leonardo, Micro) | |
162 | + // Add a timeout to prevent blocking forever if Serial isn't connected | |
163 | + unsigned long setupStartTime = millis(); | |
164 | + while (!Serial && (millis() - setupStartTime < 5000)) { // Wait max 5 seconds | |
165 | + delay(10); // Small delay while waiting | |
166 | + } | |
167 | + | |
168 | + Serial.println("\n--- Multi Servo Control via Serial ---"); | |
169 | + Serial.print("Initializing "); Serial.print(SERVO_NUM); Serial.println(" servos..."); | |
170 | + Serial.println("Command format: {S<index>:<angle>,S<index>:<angle>,...}"); | |
171 | + Serial.println("Example: {S0:90,S1:45,S5:180}"); | |
172 | + Serial.println("Valid servo indices: 0 to " + String(SERVO_NUM - 1)); | |
173 | + Serial.println("---------------------------------------"); | |
174 | + | |
175 | + // Attach each servo to its pin and set initial position | |
176 | + for (int i = 0; i < SERVO_NUM; i++) { | |
177 | + myServos[i].attach(SERVO_PIN[i]); | |
178 | + // Set servos to a defined starting position (e.g., minimum) | |
179 | + // Consider if middle (90) or another position is a safer start for your setup | |
180 | + myServos[i].write( (SERVO_MIN[i]+SERVO_MAX[i])/2 ); | |
181 | + delay(20); // Allow time for servo to potentially reach initial position | |
182 | + Serial.print("Servo "); Serial.print(i); | |
183 | + Serial.print(" attached to pin "); Serial.print(SERVO_PIN[i]); | |
184 | + Serial.print(" | Limits: ["); Serial.print(SERVO_MIN[i]); | |
185 | + Serial.print(", "); Serial.print(SERVO_MAX[i]); Serial.println("]"); | |
186 | + } | |
187 | + Serial.println("---------------------------------------"); | |
188 | + Serial.println("Initialization Complete. Ready for commands."); | |
189 | +} | |
190 | + | |
191 | +// --- Arduino Main Loop --- | |
192 | +void loop() { | |
193 | + // Check if data is available to read from Serial | |
194 | + if (Serial.available() > 0) { | |
195 | + // Read the incoming string until newline character | |
196 | + // NOTE: Using the String class extensively can lead to memory fragmentation | |
197 | + // on memory-constrained boards (like Arduino Uno/Nano/Mega) over long periods. | |
198 | + // If the device needs to run reliably for extended durations or handles | |
199 | + // very large commands, consider switching to C-style char arrays. | |
200 | + String command = Serial.readStringUntil('\n'); | |
201 | + command.trim(); // Remove leading/trailing whitespace & newline chars | |
202 | + | |
203 | + // Ignore empty commands after trimming | |
204 | + if (command.length() == 0) { | |
205 | + return; // Nothing to do, wait for next loop iteration | |
206 | + } | |
207 | + | |
208 | + // --- Validate overall command format FIRST --- | |
209 | + bool formatIsValid = command.startsWith("{") && command.endsWith("}"); | |
210 | + | |
211 | + // --- Guard Clause: If format is NOT valid, print error and exit this iteration --- | |
212 | + if (!formatIsValid) { | |
213 | + Serial.print("Error: Invalid command format '"); | |
214 | + Serial.print(command); | |
215 | + Serial.println("'. Expected {S<index>:<angle>,...}"); | |
216 | + return; // Stop processing this invalid command | |
217 | + } | |
218 | + | |
219 | + // --- Format IS Valid: Proceed with processing --- | |
220 | + | |
221 | + // Remove the braces to get the content string | |
222 | + String content = command.substring(1, command.length() - 1); | |
223 | + content.trim(); // Trim again in case of spaces like { S0:90 } | |
224 | + | |
225 | + // Handle empty content like {} | |
226 | + if (content.length() == 0) { | |
227 | + Serial.println("Received empty command: {}"); | |
228 | + return; // Nothing to process | |
229 | + } | |
230 | + | |
231 | + // Print command start confirmation | |
232 | + Serial.print("Processing: {"); | |
233 | + | |
234 | + // Process each command segment separated by commas | |
235 | + int startIndex = 0; | |
236 | + bool firstSegmentOutput = true; // Flag to manage comma printing in feedback | |
237 | + | |
238 | + while (startIndex < content.length()) { | |
239 | + // Extract the next segment based on comma delimiter | |
240 | + int commaIndex = content.indexOf(',', startIndex); | |
241 | + String currentSegment; | |
242 | + | |
243 | + if (commaIndex == -1) { | |
244 | + // No more commas - this is the last segment | |
245 | + currentSegment = content.substring(startIndex); | |
246 | + startIndex = content.length(); // Set index to exit loop after this segment | |
247 | + } else { | |
248 | + // Extract segment before the next comma | |
249 | + currentSegment = content.substring(startIndex, commaIndex); | |
250 | + startIndex = commaIndex + 1; // Move start index past the comma for next iteration | |
251 | + } | |
252 | + | |
253 | + currentSegment.trim(); // Clean up the individual segment (remove spaces) | |
254 | + | |
255 | + // Skip empty segments that might result from extra commas (e.g., {S0:90,,S1:90}) | |
256 | + if (currentSegment.length() == 0) { | |
257 | + continue; // Go to the next iteration of the while loop | |
258 | + } | |
259 | + | |
260 | + // Process the extracted segment using the helper function | |
261 | + processSegment(currentSegment, firstSegmentOutput); | |
262 | + | |
263 | + } // End while loop processing segments | |
264 | + | |
265 | + Serial.println("}"); // End the feedback line for the processed command | |
266 | + | |
267 | + } // End if Serial.available() | |
268 | + | |
269 | + // No delay() needed here in most cases. The loop runs again quickly. | |
270 | + // Other non-blocking code (reading sensors, checking buttons) could go here. | |
271 | +} |
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?