Initial commit
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
// #include <Arduino.h>
|
||||
|
||||
// // --- 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -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).<br>`m1`: Set to **MANUAL** (moves once, then waits indefinitely). |
|
||||
| **Move Angle** | `a<degrees>` | Move `<degrees>` degrees relative to current position. Supports negative values for reverse direction.<br>*Example*: `a90` (move 90° CW), `a-45` (move 45° CCW). |
|
||||
| **Set Speed** | `s<speed>` | Set speed in degrees per second.<br>*Example*: `s10` (10 deg/s). |
|
||||
| **Set Wait Time** | `i<seconds>` | Set the duration (in seconds) to wait in `WAIT` state when in AUTO mode.<br>*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
|
||||
@@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user