Hello!
I am working on a science project, where I need to turn a motor periodically so as to expose different Petri dishes at specified times. Ideally, the motor turns on its own but sometimes (in case something interesting happens), I want to be able to press a button on my remote and the motor turns automatically, skipping the wait.
For this, I am using an Arduino Leonardo, a Polulu Tic T825 motor driver (which is based on a TI DRV8825 driver), a standard stepper motor (reference 17HS3430) and an RF 433 reciever. The T825 recieves a 12V input, which it sends to the motor, as well as to the Arduino on its 5V output pin. The two communicate using I2C on the SCL and SCA pins. The RF reciever is on the Leonardo's 3rd pin (default RF pin). I've tested both the RF receiver and the motor driver using example sketches and they work fine, but putting it all together is an issue...
My code is this:
#include <RCSwitch.h>
#include <Wire.h>
#include <Tic.h>
TicI2C tic;
RCSwitch mySwitch = RCSwitch();
const int rf_delay = 500;
const unsigned long debounceDelay = 500;
const unsigned long waitBeforeShutdown = 5000;
// Values gotten from testing the remote using an example code from the RCSwitch library, works fine.
const unsigned long BUTTON_START = 5338371;
const unsigned long BUTTON_SKIP = 5330236;
const unsigned long BUTTON_STOP = 5338380;
unsigned long lastPressTime = 0;
// Change the command timeout on the tic which seems to be causing issues.
void setTicCommandTimeout(uint16_t timeout_ms) {
Wire.beginTransmission(14); // 14 is the I2C address of the Tic
Wire.write(0x8C); // Command byte for setting command timeout
Wire.write((byte)(timeout_ms >> 0)); // Lower byte of timeout_ms
Wire.write((byte)(timeout_ms >> 8)); // Upper byte of timeout_ms
Wire.endTransmission();
}
void setup() {
Wire.begin();
delay(20);
tic.setProduct(TicProduct::T825);
tic.setMaxAccel(100000);
tic.energize();
tic.exitSafeStart();
tic.resetCommandTimeout();
Serial.begin(9600);
mySwitch.enableReceive(0);
// Set the command timeout to 2000 ms (2 seconds)
setTicCommandTimeout(2000);
}
// Provided functino from tic
void resetCommandTimeout() {
tic.resetCommandTimeout();
tic.energize();
}
// This function is used whilst the motor is in motion and shouldn't be interupted, so no need to check for a button press
int delayduringmovement(unsigned long ms, unsigned long start) {
while ((millis() - start) < ms) {
tic.resetCommandTimeout();
delay(10);
}
Serial.print("First delay complete");
return 1;
}
// This function is used whilst the motor is in waiting and does need to check for a button press
int delayWhileResettingCommandTimeoutOrButtonPress(unsigned long ms, unsigned long start) {
while ((millis() - start) < ms) {
// I've printed the different waiting times to see where the program stops waiting
Serial.println(millis()-start);
tic.resetCommandTimeout();
delay(10);
if (mySwitch.available()) {
unsigned long receivedValue = mySwitch.getReceivedValue();
if (receivedValue == BUTTON_SKIP) {
Serial.println("Button pressed! Skipping delay.");
mySwitch.resetAvailable();
return 1;
}
mySwitch.resetAvailable(); // Ensure any received value is cleared
continue;
}
}
Serial.println("Exiting delay function without button press");
return 1;
}
// Main function which controls motor movement and waiting
void movement(unsigned long time_durations[], size_t size) {
for (size_t i = 0; i < size; i++) {
time_durations[i] *= 1000; // Convert to milliseconds
}
mySwitch.resetAvailable();
unsigned long time_diff[size - 1];
// Create a vector of time differences -> this will be how long I actually need to wait between movements
for (size_t i = 1; i < size; ++i) {
time_diff[i - 1] = time_durations[i] - time_durations[i - 1];
}
Serial.print("Movement function start");
for (size_t i = 0; i < size - 1; ++i) {
tic.resetCommandTimeout();
tic.exitSafeStart();
// Begin movement
tic.setTargetVelocity(2000000);
unsigned long start = millis();
// Keep turning for 2 seconds.
int returned = delayduringmovement(2000, start);
tic.haltAndHold(); // Stop the motor
// Now wait for the specified amount of time / if the skip button is pressed
unsigned long start1 = millis();
int returned1=delayWhileResettingCommandTimeoutOrButtonPress(time_diff[i], start1);
Serial.print("Completed iteration ");
Serial.println(i + 1);
}
}
// Function to add button debouncing delay to prevent signal spam
bool isButtonPressed(unsigned long buttonValue) {
if (mySwitch.getReceivedValue() == buttonValue) {
unsigned long currentTime = millis();
if (currentTime - lastPressTime > debounceDelay) {
lastPressTime = currentTime;
mySwitch.resetAvailable();
return true;
}
}
return false;
}
// Turn off the Arduino loop
void waitForShutdown() {
unsigned long start = millis();
Serial.println("Waiting before shutting down the program.");
while (millis() - start < waitBeforeShutdown) {
if (mySwitch.available()) {
unsigned long value = mySwitch.getReceivedValue();
if (value == BUTTON_START || value == BUTTON_SKIP) {
Serial.println("Button pressed, resuming program.");
mySwitch.resetAvailable();
return;
}
delay(debounceDelay);
}
}
Serial.println("Shutting down the program.");
}
void loop() {
// Specify when you want the motor to turn ( At t=5s, t=10s, ...)
unsigned long time_durations[] = {5, 10, 15, 20};
size_t size = sizeof(time_durations) / sizeof(time_durations[0]);
if (mySwitch.available()) {
if (isButtonPressed(BUTTON_START)) {
Serial.println("Turning on the program");
delay(rf_delay);
// Send the time durations to the movement function to actually start motor movement
movement(time_durations, size);
} else if (isButtonPressed(BUTTON_STOP)) {
Serial.println("Turning off the program.");
delay(rf_delay);
waitForShutdown();
}
} else Serial.print("Waiting ");
}
The loop works fine and waits until the button press. However, the Arduino seems to be having issues looping through the different waiting periods. Printing the time shows it sort of stops randomly in the delayWhileResettingCommandTimeoutOrButtonPress() function, sometimes going through several iterations then crashing, sometimes reaching about 400 (out of 5000) on the first iteration and just dying out.
I've tried monitoring the Polulu using usb and their software whilst the program was running, and it seems to show the motor becomes denergized and stops, so maybe the driver is causing the whole thing to crash? It should become energized though, as the resetcommandtimeout() function does this. Fiddling around with command timeout durations doesn't change much either. I also thought maybe the Leonardo was running out dynamic memory, but reducing Serial prints does nothing, and the IDE tells me I'm only using around 30% of dynamic or static memory.
If you have any ideas or pointers as to what could be going wrong I'd be infinitely grateful. Thank you!