commit a93e3a8878341fb21d89c89d0235b1292b5ae565 Author: rsformoso Date: Tue Jul 7 12:33:24 2026 +0800 Initial commit diff --git a/rotating-display-controller.ino.old b/rotating-display-controller.ino.old new file mode 100644 index 0000000..de1837e --- /dev/null +++ b/rotating-display-controller.ino.old @@ -0,0 +1,170 @@ +// #include + +// // --- Hardware Configuration --- +// #define IN1 9 +// #define IN2 10 +// #define IN3 11 +// #define IN4 12 + +// // The step sequence (standard for 28BYJ-48 motors) +// int stepSequence[8][4] = { +// { 1, 0, 0, 0 }, +// { 1, 1, 0, 0 }, +// { 0, 1, 0, 0 }, +// { 0, 1, 1, 0 }, +// { 0, 0, 1, 0 }, +// { 0, 0, 1, 1 }, +// { 0, 0, 0, 1 }, +// { 1, 0, 0, 1 } +// }; + +// // --- Configuration Constants --- +// const int STEPS_PER_REV = 4096; +// int stepInterval = 1; +// bool autoMode = true; + +// // --- State Variables --- +// long targetSteps = 0; +// long currentStepCount = 0; +// unsigned long lastMoveTime = 0; // Timer for movement steps +// int speedScale = 1; + +// // --- Auto Mode Specific Variables --- +// unsigned long autoTimer = 0; // Tracks time since last auto-move +// unsigned long autoInterval = 5000; // 5 seconds delay between moves +// int autoState = 0; // 0 = Waiting, 1 = Moving +// int autoStepsToMove = 0; // Calculated steps for 15 degrees +// int autoStepIndex = 0; // Current step index within the move + +// void setStep(int a, int b, int c, int d) { +// digitalWrite(IN1, a); +// digitalWrite(IN2, b); +// digitalWrite(IN3, c); +// digitalWrite(IN4, d); +// } + +// void setup() { +// pinMode(IN1, OUTPUT); +// pinMode(IN2, OUTPUT); +// pinMode(IN3, OUTPUT); +// pinMode(IN4, OUTPUT); + +// Serial.begin(9600); +// while (!Serial) {}; + +// stepInterval = map(speedScale, 1, 50, 1, 100); + +// // Calculate steps for 15 degrees once +// autoStepsToMove = (int)((15.0 / 360.0) * STEPS_PER_REV); + +// autoTimer = millis(); // Start the 5s timer immediately +// Serial.println("System Started."); +// Serial.println("Current Mode: AUTO (15 degrees every 5 seconds)"); +// Serial.println("Send a number or 's[1-50]' to switch to MANUAL mode."); +// } + +// void loop() { +// // --- 1. SERIAL INPUT PROCESSING (HIGHEST PRIORITY) --- +// // If ANY input is received, switch to manual mode IMMEDIATELY. +// if (Serial.available() > 0) { +// String input = Serial.readString(); +// input.trim(); + +// if (input.length() > 0) { +// if (input.startsWith("s")) { +// // Handle Speed change +// int newSpeed = input.substring(1).toInt(); +// if (newSpeed >= 1 && newSpeed <= 50) { +// speedScale = newSpeed; +// stepInterval = map(speedScale, 1, 50, 1, 100); + +// // If we were moving, stop immediately +// currentStepCount = targetSteps; +// autoStepIndex = autoStepsToMove; // Force auto move to finish logic (or reset) + +// Serial.print("Success: Speed set to "); +// Serial.println(speedScale); +// } else { +// Serial.println("Error: Use scale 1-50."); +// } +// } else { +// // Handle Degree change +// float val = input.toFloat(); +// if (val != 0) { +// targetSteps = (long)((val / 360.0) * STEPS_PER_REV); +// currentStepCount = 0; // Reset count to start from zero +// lastMoveTime = millis(); +// autoMode = false; // SWITCH TO MANUAL MODE +// autoState = 0; // Reset auto state machine + +// Serial.print("Switching to Manual Mode. Target: "); +// Serial.print(val); +// Serial.println(" degrees."); +// } +// } +// } +// } + +// // --- 2. EXECUTION LOGIC --- + +// if (autoMode) { +// /* +// NON-BLOCKING AUTO MODE: +// State Machine: +// 0: Waiting for 5 seconds +// 1: Moving the 15 degrees +// */ + +// if (autoState == 0) { +// // Check if 5 seconds have passed +// if (millis() - autoTimer >= autoInterval) { +// autoTimer = millis(); // Reset timer +// autoState = 1; // Start moving +// autoStepIndex = 0; // Reset step counter for this move +// lastMoveTime = millis(); // Start the step timer +// } +// } +// else if (autoState == 1) { +// // Move one step if enough time passed +// if (millis() - lastMoveTime >= (unsigned long)stepInterval) { +// lastMoveTime = millis(); + +// // Calculate sequence index +// int seqIdx = autoStepIndex % 8; +// setStep(stepSequence[seqIdx][0], stepSequence[seqIdx][1], +// stepSequence[seqIdx][2], stepSequence[seqIdx][3]); + +// autoStepIndex++; + +// // Check if we finished the 15-degree move +// if (autoStepIndex >= autoStepsToMove) { +// autoState = 0; // Back to waiting +// Serial.println("Auto move complete. Waiting 5s..."); +// } +// } +// } +// } +// else { +// /* +// MANUAL MODE: +// Non-blocking movement toward target. +// */ +// if (currentStepCount != targetSteps) { +// if (millis() - lastMoveTime >= (unsigned long)stepInterval) { +// lastMoveTime = millis(); + +// // Determine direction +// long direction = (targetSteps > currentStepCount) ? 1 : -1; + +// // Calculate sequence index (handling negative modulo correctly) +// int seqIdx = (int)((currentStepCount % 8 + 8) % 8); +// setStep(stepSequence[seqIdx][0], +// stepSequence[seqIdx][1], +// stepSequence[seqIdx][2], +// stepSequence[seqIdx][3]); + +// currentStepCount += direction; +// } +// } +// } +// } \ No newline at end of file diff --git a/rotating-display-controller/README.md b/rotating-display-controller/README.md new file mode 100644 index 0000000..033b89d --- /dev/null +++ b/rotating-display-controller/README.md @@ -0,0 +1,156 @@ +# Arduino Stepper Motor Controller + +A state-machine based Arduino sketch for controlling a 28BYJ-48 stepper motor via Serial communication. The system supports both **Manual** and **Automatic** modes, allowing for relative angle movement, speed control, and configurable wait intervals. + +## Hardware Requirements +- **Microcontroller**: Arduino Uno / Mega / Nano +- **Motor Driver**: ULN2003 (or compatible) +- **Stepper Motor**: 28BYJ-48 (or compatible bipolar/unipolar stepper) + +### Pin Connections +| Pin | Function | Arduino Pin | +|-----|----------|-------------| +| IN1 | Step 1 | 9 | +| IN2 | Step 2 | 10 | +| IN3 | Step 3 | 11 | +| IN4 | Step 4 | 12 | + +## System Configuration + +### Constants +- **STEPS_PER_REVOLUTION**: `2048` (Standard for 28BYJ-48 with gear reduction) +- **DEGREES_PER_STEP**: Calculated as `360.0 / 2048` (~0.1758°) + +### Global Variables +| Variable | Type | Description | +|----------|------|-------------| +| `autoMode` | `enum` | `AUTO` or `MANUAL` | +| `targetAngle` | `float` | Target angle in degrees (relative to current position) | +| `turnSpeed` | `float` | Speed in degrees per second | +| `_currState` | `enum` | `MOVE` or `WAIT` | +| `waitInterval` | `float` | Time in seconds to wait in `WAIT` state (only active in AUTO mode) | +| `_waitTime` | `unsigned long` | Timestamp of when WAIT state began | +| `_stepInterval` | `float` | Time in seconds between individual steps | +| `_stepTime` | `unsigned long` | Timestamp of the last step | +| `_stepCount` | `int` | Total steps to move for the current target | +| `_stepsRemaining` | `int` | Steps remaining in the current move sequence | +| `_directionCW` | `bool` | `true` for Clockwise, `false` for Counter-Clockwise | + +## Serial Command Interface + +The controller listens to the Serial port (9600 baud). Commands must start with a letter followed by a value. No spaces are required. + +| Command | Format | Description | +|---------|--------|-------------| +| **Mode Switch** | `m0` / `m1` | `m0`: Set to **AUTO** (cycles MOVE/WAIT).
`m1`: Set to **MANUAL** (moves once, then waits indefinitely). | +| **Move Angle** | `a` | Move `` degrees relative to current position. Supports negative values for reverse direction.
*Example*: `a90` (move 90° CW), `a-45` (move 45° CCW). | +| **Set Speed** | `s` | Set speed in degrees per second.
*Example*: `s10` (10 deg/s). | +| **Set Wait Time** | `i` | Set the duration (in seconds) to wait in `WAIT` state when in AUTO mode.
*Example*: `i5` (wait 5 seconds). | + +## Operational Logic + +### State Machine +The system operates in two primary states: + +1. **MOVE**: + - The motor steps at the calculated `_stepInterval`. + - `_stepsRemaining` decrements with each step. + - When `_stepsRemaining` hits 0, the system transitions to **WAIT**. + +2. **WAIT**: + - The motor stops. + - `_waitTime` is recorded. + - **In AUTO Mode**: If `millis() - _waitTime > waitInterval`, the system transitions back to **MOVE** (repeats cycle). + - **In MANUAL Mode**: The system stays in **WAIT** indefinitely until a new Serial command is received. + +```cpp +autoMode // AUTO or MANUAL. If AUTO, cycles between MOVE and WAIT state. If MANUAL, goes into MOVE state, but stays in WAIT indefinitely until a new command is sent +targetAngle // The target angle in degrees, relative to the current position +turnSpeed // The turn speed in degrees per second + +_currState // MOVE or WAIT + +waitInterval // Time to stay in wait state in AUTO mode, in seconds +_waitTime // Variable to compare against millis() + +_stepInterval // Time between motor steps, in seconds +_stepTime // Variable to compare against millis() + +_stepCount // Number of steps remaining +_stepRemaining // + +LOOP + +--- READ SERIAL --- +if serial.Available + + string = serial.readString + + switch(string[0]) + + case 'm': + if autoMode != string[1] + autoMode = AUTO if string[1] == 0, MANUAL if string[1] == 1 + // Start moving following existing parameters, relative to current position + _currState = MOVE + _waitTime = 0 + _stepTime = millis() + _stepsRemaining = _stepCount + + case 'a': + // Move this many degrees relative to current position + targetAngle = strToFloat(string[1:]) + // TODO: Recalculate step count + _currState = MOVE + _waitTime = 0 + _stepTime = millis() + _stepsRemaining = _stepCount + + case 's': + // Sets the step interval + turnSpeed = strToFloat(string[1:]) + // TODO: Recalculate step interval + + case 'i': + // Sets the wait interval + waitInterval = strToFloat(string[1:]) + + +--- Execution Logic --- + +if _stepsRemaining == 0 + _currState = WAIT + _waitTime = millis() + +if autoMode = AUTO and _currState == WAIT and millis() - _waitTime > waitInterval + _currState = MOVE + _stepsRemaining = _stepCount + _stepTime = millis() + +if currState == MOVE + if millis() - _stepTime > _stepInterval + RunStep + _stepTime = millis() + _stepsRemaining -= 1 +``` + +### Initialization +- **Default Mode**: `AUTO` +- **Default State**: `MOVE` +- **Default Wait Interval**: `8.0` seconds +- **Default Speed**: `5.0` degrees/second + +## Step Sequence Logic + +The controller supports bidirectional movement by using two sequences: +- **Clockwise (CW)**: Standard 4-step sequence. +- **Counter-Clockwise (CCW)**: Reverse of the CW sequence. + +The direction is determined by the sign of the `targetAngle` when the `a` command is received. + +## Usage Examples + +1. **Move 90 degrees clockwise at 10 deg/s**: + ```text + s10 + a90 \ No newline at end of file diff --git a/rotating-display-controller/rotating-display-controller.ino b/rotating-display-controller/rotating-display-controller.ino new file mode 100644 index 0000000..6086e50 --- /dev/null +++ b/rotating-display-controller/rotating-display-controller.ino @@ -0,0 +1,229 @@ +// --- Hardware Configuration --- +#define IN1 9 +#define IN2 10 +#define IN3 11 +#define IN4 12 + +// Step sequences for 28BYJ-48 +// Clockwise sequence +int stepSequenceCW[4][4] = { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 } +}; + +// Counter-Clockwise sequence (reverse of CW) +int stepSequenceCCW[4][4] = { + { 0, 0, 0, 1 }, + { 0, 0, 1, 0 }, + { 0, 1, 0, 0 }, + { 1, 0, 0, 0 } +}; + +// --- Configuration Constants --- +const int STEPS_PER_REVOLUTION = 2048; // 28BYJ-48 standard (512 steps * 4 gear reduction) +const float DEGREES_PER_STEP = 360.0 / STEPS_PER_REVOLUTION; + +// --- Global Variables --- +enum State { MOVE, WAIT }; +enum Mode { AUTO, MANUAL }; + +Mode autoMode = AUTO; // Default: AUTO +float targetAngle = 0.0; // Target angle in degrees +float turnSpeed = 5.0; // Speed in degrees per second + +State _currState = MOVE; // Default: MOVE + +float waitInterval = 8.0; // Wait time in seconds (default 8s) +unsigned long _waitTime = 0; // Timestamp for wait state + +float _stepInterval = 0.0; // Time between steps in seconds +unsigned long _stepTime = 0; // Timestamp for last step + +int _stepCount = 0; // Total steps to move +int _stepsRemaining = 0; // Steps left in current move +int _currentStepIndex = 0; // Current index in step sequence (0-3) +bool _directionCW = true; // true = CW, false = CCW + +// --- Helper Functions --- + +void setStep(int a, int b, int c, int d) { + digitalWrite(IN1, a); + digitalWrite(IN2, b); + digitalWrite(IN3, c); + digitalWrite(IN4, d); +} + +void updateStepInterval() { + if (turnSpeed <= 0) { + _stepInterval = 0.1; // Fallback to avoid div by zero + return; + } + // Calculate steps per second: speed / degrees_per_step + float stepsPerSecond = turnSpeed / DEGREES_PER_STEP; + _stepInterval = 1.0 / stepsPerSecond; // Seconds per step +} + +void calculateStepsForAngle(float angle) { + // Determine direction + if (angle >= 0) { + _directionCW = true; + } else { + _directionCW = false; + angle = -angle; // Work with absolute value + } + + // Calculate steps + _stepCount = (int)(angle / DEGREES_PER_STEP); + + // Update speed interval immediately + updateStepInterval(); + + // Reset state to start moving immediately + _currState = MOVE; + _stepsRemaining = _stepCount; + _stepTime = millis(); + _currentStepIndex = 0; + + // Reset wait timer just in case + _waitTime = 0; +} + +void runStep() { + int* sequence = _directionCW ? stepSequenceCW[0] : stepSequenceCCW[0]; // Placeholder logic + + // Actually, we need to access the 2D array correctly + int (*seq)[4] = _directionCW ? stepSequenceCW : stepSequenceCCW; + + setStep(seq[_currentStepIndex][0], + seq[_currentStepIndex][1], + seq[_currentStepIndex][2], + seq[_currentStepIndex][3]); + + _currentStepIndex++; + if (_currentStepIndex >= 4) { + _currentStepIndex = 0; + } +} + +// --- Setup --- +void setup() { + // Initialize pins + pinMode(IN1, OUTPUT); + pinMode(IN2, OUTPUT); + pinMode(IN3, OUTPUT); + pinMode(IN4, OUTPUT); + + // Initialize Serial + Serial.begin(9600); + while (!Serial) { ; } // Wait for serial port (optional for native USB) + + // Initial State Setup + autoMode = AUTO; + waitInterval = 8.0; + turnSpeed = 5.0; + _currState = MOVE; + + // Initialize timers to current time to prevent immediate first step + _stepTime = millis(); + _waitTime = millis(); + + // Initial calculation (move 0 degrees to set defaults) + calculateStepsForAngle(0.0); + // Ensure we start in MOVE if we want immediate action, or WAIT if idle + // Based on your logic: "initialized in MOVE state" + _currState = MOVE; +} + +// --- Loop --- +void loop() { + // --- READ SERIAL --- + if (Serial.available()) { + String command = Serial.readString(); + command.trim(); // Remove whitespace + + if (command.length() < 2) return; // Need at least 'a' + number + + char cmdType = command.charAt(0); + String payload = command.substring(1); + + switch (cmdType) { + case 'm': + // 'm0' for AUTO, 'm1' for MANUAL + if (payload.length() > 0) { + char modeChar = payload.charAt(0); + if (modeChar == '0' && autoMode != AUTO) { + autoMode = AUTO; + _currState = MOVE; + _stepsRemaining = _stepCount; + _stepTime = millis(); + } else if (modeChar == '1' && autoMode != MANUAL) { + autoMode = MANUAL; + _currState = MOVE; // Start moving immediately on switch + _stepsRemaining = _stepCount; + _stepTime = millis(); + } + } + break; + + case 'a': + // 'a90' -> move 90 degrees + { + float angle = payload.toFloat(); + calculateStepsForAngle(angle); + } + break; + + case 's': + // 's10' -> 10 deg/s + { + float speed = payload.toFloat(); + if (speed > 0) { + turnSpeed = speed; + updateStepInterval(); + // If currently in MOVE, the new interval applies on next step + } + } + break; + + case 'i': + // 'i5' -> 5 seconds wait + { + float interval = payload.toFloat(); + if (interval >= 0) { + waitInterval = interval; + } + } + break; + } + } + + // --- EXECUTION LOGIC --- + + // 1. Check if move sequence is finished + if (_currState == MOVE && _stepsRemaining == 0) { + _currState = WAIT; + _waitTime = millis(); + } + + // 2. Check if we should restart in AUTO mode + if (_currState == WAIT) { + if (autoMode == AUTO && (millis() - _waitTime > (unsigned long)(waitInterval * 1000))) { + _currState = MOVE; + _stepsRemaining = _stepCount; + _stepTime = millis(); // Reset step timer to prevent burst + } + // If MANUAL: Stay in WAIT indefinitely until new command + } + + // 3. Execute Step if in MOVE mode + if (_currState == MOVE) { + unsigned long currentTime = millis(); + if (currentTime - _stepTime > (unsigned long)(_stepInterval * 1000)) { + runStep(); + _stepTime = currentTime; + _stepsRemaining--; + } + } +} \ No newline at end of file