// a complex functionality doing multiple things "in parallel"
// requires quite a lot of lines of code
// at the end of this file the structure of the code is epxlained
#define ProjectName "MobaTools serial receive Speed Start Stop Bounce "
#define LED_BUILTIN 2
// define IO-states for inputs with pull-up-resistors
// pull-up-resistors invert the logig
#define unPressed HIGH
//#define pressed LOW
const byte ToggleButtonPin = 4;
const byte dirPin = 5;
const byte stepPin = 6;
// ( 1600 steps / revolution = 1/8 Microstep )
const int STEPS_REVOLUTION = 1600;
const long baudrate = 115200;
#include <MobaTools.h> // stepper-motor-library
MoToStepper myStepper( STEPS_REVOLUTION, STEPDIR );
const long targetPos = 500; // stepper moves between 0 and targetpos
long nextPos;
//Array for storing serial received data
const unsigned int numChars = 128;
char receivedChars[numChars];
boolean newData = false;
unsigned int StepperSpeed = 1000;
bool activationMode = false;
void setup() {
Serial.begin(baudrate); // adjust baudrate in the serial monitor to match the number
Serial.println( F("Setup-Start") );
printFileNameDateTime();
pinMode (LED_BUILTIN, OUTPUT); // used for indicating logging active or not
digitalWrite(LED_BUILTIN, LOW);
// wire button between IO-pin and GND
// Pull-up-resistor inverts the logic
// unpressed: IO-pin detects HIGH
// pressed: IO-Pin detects LOW
pinMode(ToggleButtonPin, INPUT_PULLUP);
SetUpStepperMotor();
Serial.print( F("start / stop button must be connected to IO-PIN no ") );
Serial.println(ToggleButtonPin);
Serial.println( F("send speed-commands through serial monitor with <speed>") );
Serial.println( F("example <500>") );
Serial.println( F("press button to start / stop steppermotor") );
}
void loop () {
recvWithStartEndMarkers(); // non-blocking checking if serial data is received
// receive with Start- / Endmarker means the data must have
// a leading "<" and a trailing ">" for switching newData to true
if (newData) { // if a valid command is received
newData = false;
SetStepperMotorSpeed();
}
activationMode = GetToggleSwitchState(); // must be executed all the time
execute_if_Active(activationMode); // function that does what its name says
}
void SetUpStepperMotor() {
myStepper.attach( stepPin, dirPin );
//myStepper.attachEnable( enaPin, 10, LOW ); // Enable Pin aktivieren ( LOW=aktiv )
// the maximum steprate pulses per second is limited
// due to the kind of how the step-pulses are created with a timer-interrupt
// Arduino Uno, Mega Nano 2500 steps / second
// ESP8266 6250 steps / second
// STM32F103 20000 steps / second
// ESP32 30000 steps / second
Serial.print("myStepper.setSpeed( ");
Serial.print(StepperSpeed);
Serial.println(" );");
myStepper.setSpeed( StepperSpeed );
//myStepper.setRampLen( StepperSpeed / 2 ); // Rampenlänge 100 Steps bei 20U/min
myStepper.setRampLen( 200 );
Serial.print( F("step-pin is IO-pin no ") );
Serial.println(stepPin);
Serial.print( F("dir-pin is IO-pin no ") );
Serial.println(dirPin);
}
bool GetToggleSwitchState() {
// "static" makes variables persistant over function calls
static bool toggleState = false;
static byte buttonStateOld = unPressed;
unsigned long buttonDebounceTime = 100;
unsigned long buttonDebounceTimer = 0;
byte buttonStateNew;
if ( TimePeriodIsOver(buttonDebounceTimer, buttonDebounceTime) ) {
// if more time than buttonDebounceTime has passed by
// this means let pass by some time until
// bouncing of the button is over
buttonStateNew = digitalRead(ToggleButtonPin);
if (buttonStateNew != buttonStateOld) {
// if button-state has changed
buttonStateOld = buttonStateNew;
if (buttonStateNew == unPressed) {
// if button is released
toggleState = !toggleState; // toggle state-variable
} // the attention-mark is the NOT operator
} // which simply inverts the boolean state
} // !true = false NOT true is false
// !false = true NOT false is true
return toggleState;
}
void SetStepperMotorSpeed() {
Serial.print( F("I received #") );
Serial.print(receivedChars);
Serial.println( F("#") );
StepperSpeed = atoi (receivedChars);
Serial.print("myStepper.setSpeed( ");
Serial.print(StepperSpeed);
Serial.println(" );");
if (StepperSpeed > 0) {
myStepper.setSpeed( StepperSpeed );
Serial.print( F("set speed to ") );
Serial.print(StepperSpeed);
Serial.println();
}
else {
Serial.println( F("no valid speed-data") );
}
}
void RunStepperMotor() {
// if steppermotor has reached target-position and
// does NOT move anymore
if ( !myStepper.moving() ) {
Serial.print( F("arrived at position ") );
Serial.println(myStepper.currentPosition() );
Serial.println();
if ( nextPos == 0 ) { // if is at startposition 0
nextPos = targetPos; // drive to position max
}
else { // if is at endposition
nextPos = 0; // drive to position zero
}
Serial.print( F("moving to position ") );
Serial.println(nextPos);
myStepper.moveTo( nextPos ); // start creating step-pulses
}
}
void execute_if_Active(bool p_IsActivated) {
if (p_IsActivated) {
RunStepperMotor();
}
else { // DE-activated
myStepper.stop();
}
PrintToSerialMonitor(p_IsActivated); // deactivate through commenting if not wanted
}
// helper-function ignore at first
void printFileNameDateTime() {
Serial.print( F("File : ") );
Serial.println( F(__FILE__) );
Serial.print( F("Date : ") );
Serial.println( F(__DATE__) );
Serial.print( F("Project: ") );
Serial.println( F(ProjectName) );
}
// ignore at first
// helper-function for easy to use non-blocking timing
boolean TimePeriodIsOver (unsigned long & expireTime, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - expireTime >= TimePeriod ) {
expireTime = currentMillis; // set new expireTime
return true; // more time than TimePeriod) has elapsed since last time if-condition was true
}
else return false; // not expired
}
// helper-function
void PrintToSerialMonitor(boolean p_IsActivated) {
static bool lastIsActivated;
// only in case the activation-mode has CHANGED print ONE time
if (p_IsActivated != lastIsActivated) {
// only if state of parameter p_logIsActivated has changed
if (p_IsActivated) {
Serial.println();
Serial.println( F("start executing") );
Serial.println();
digitalWrite(LED_BUILTIN, HIGH);
}
else { // not activated
Serial.println();
Serial.println( F("stopp executing") );
Serial.println();
digitalWrite(LED_BUILTIN, LOW);
}
lastIsActivated = p_IsActivated; // update variable lastSDlogActive
}
}
void recvWithStartEndMarkers() {
static boolean recvInProgress = false;
static unsigned int ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
// If data is in the receive-buffer .available() > 0
// .available() returns how MANY bytes are in the receive-buffer
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
if (recvInProgress == true) { // if startmarker is found set flag to true
if (rc != endMarker) { // if the received byte is NOT the endmarker
receivedChars[ndx] = rc; // append the received byte at the end of char-array
ndx++; // increase array-index by 1
if (ndx >= numChars) { // if message is longer than max number of chars the arraybuffer can hold
ndx = numChars - 1; // reduce array-index => byte is trhown away
}
} // if (rc != endMarker) {
else { // it IS the endmarker
receivedChars[ndx] = '\0'; // terminate the string through adding a zero
recvInProgress = false; // receiving of command is finished
ndx = 0;
newData = true; // command is ready to be used
}
} // if (recvInProgress == true) {
else if (rc == startMarker) { // if the byte is the startmarker
recvInProgress = true; // set boolean flag receive in progress to true
}
} // while (Serial.available() > 0 && newData == false) {
}
/* OVERVIEW: ###################################################
this demo-code does quite a lot of things
the code has a hierachical structure
the code is organised in SUB-units where each SUB-unit is
doing a senseful unit of things like
- receive serial data
- Set StepperMotorSpeed
- check if start/stop-button is pressed
- depending on beeing in state "active" or "inactive"
drive stepper-motor or not
- create step-pulses to make the stepper-motor drive back and forth
- helper-functios for
- print source-code-filename to serial monitor
- measure how much time has passed by since a referencetime
(non-blocking timing)
- print info to serial monitor to make visible what the code is doing
in more Detail: ################################################
toggle-switch:
there is the function GetToggleSwitchState()
GetToggleSwitchState() returns a "true" or a "false"
this transforms a momentary pushbutton into a toggle-switch
press push-button => toggle-switch is in ON-position
press push-button => toggle-switch is in OFF-position
push-ON, Push-OFF, Push-ON, Push-OFF etc. etc.
In function loop the state of GetToggleSwitchState()
is checked ALL the time. Depending on variable activationMode
beeing true or false the
function execute_if_Active() is creating step-pulses
or just pausing -----------------------------------------------
receive serial data:
The function recvWithStartEndMarkers()
is checking for newly arrived serial data
it is doing this in a receive byte for byte
and NON-blocking manner.
NON-blocking is important that the code stays responsive to
button-presses ALL the time
If the code receives bytes that do not follow the pattern
leading startmarker "<" trailing endmarker ">" the received bytes
are just thrown away -------------------------------------------
set steppermotor speed:
If a complete command like "<500>" is received the variable newData
is set true and then the function SetStepperMotorSpeed()
processes the command.
In case the command is invalid the steppermotor-speed stays the same
in case the command is valid the stepper-motor-speed is changed
-----------------------------------------------------------------
some helper-functions:
*/