We are currently working on controlling stepper motors in Arduino by sending the speed of rotation of the stepper motor from a program written in Delphi, through the COM port to Arduino.
So far, it is not possible to get the number in the sketch from Optel3d via COM. Can't help? Some kind of blockage.
I have three sketches:
When this sketch is running, when sending a byte from the program, the arduino catches it, but does not distinguish its value - the stepper motor rotates at a constant speed.
When this sketch runs, a byte is sent from the program - but the sketch doesn't work. I mean, nothing moves.
The number is sent from the COM port monitor - everything works, the engine spins as it should.
Our Delphi program has a special button that, when pressed, calls a procedure with arguments SendToCOMPort(ComPort, Speed), where Speed is the rotation speed that is set in a special field on our form, and when the button is released, SendToCOMPort(ComPort, 0) is called ).
First of all, you must ensure that both ends uses the same baudrate and instead of sending strings, try to send bytes..
//Delphi
Code: byte = 5;
ComPort.Write(Code, 1); //Send one byte
//Arduino
if (Serial.available() > 0)
{
uint8_t code = Serial.read(); //Receive one byte
//Do something with code
}
Another thing is that the line stepper.begin(RPM, MICROSTEPS); should only be called once in void setup(). To change the RPM, you should call stepper.setRPM(n), increase RPM with 20: stepper.setRPM(stepper.getRPM() + 20) and decrease is just changing "+" to "-".
In general, the whole point turned out to be that it was not a number that was transmitted from Delphi to the COM port, but a character that needed to be read in Arduino as a character.
I'm currently working with the NonBlocking.ino sketch. Our task is to start the rotation of the motor from Delphi and during its rotation to control its speed also from Delphi. From the StepperDriver library I tried to download the NonBlocking.ino sketch for testing - the stepper motor does not spin. What's wrong?
/*
* Example using non-blocking mode to move until a switch is triggered.
*
* Copyright (C)2015-2017 Laurentiu Badea
*
* This file may be redistributed under the terms of the MIT license.
* A copy of this license has been included with this distribution in the file LICENSE.
*/
#include <Arduino.h>
// this pin should connect to Ground when want to stop the motor
#define STOPPER_PIN 4
// Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step
#define MOTOR_STEPS 200
#define RPM 120
// Microstepping mode. If you hardwired it to save pins, set to the same value here.
#define MICROSTEPS 16
#define DIR 8
#define STEP 9
#define SLEEP 13 // optional (just delete SLEEP from everywhere if not used)
/*
* Choose one of the sections below that match your board
*/
#include "DRV8834.h"
#define M0 10
#define M1 11
DRV8834 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, M0, M1);
// #include "A4988.h"
// #define MS1 10
// #define MS2 11
// #define MS3 12
// A4988 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MS1, MS2, MS3);
// #include "DRV8825.h"
// #define MODE0 10
// #define MODE1 11
// #define MODE2 12
// DRV8825 stepper(MOTOR_STEPS, DIR, STEP, SLEEP, MODE0, MODE1, MODE2);
// #include "DRV8880.h"
// #define M0 10
// #define M1 11
// #define TRQ0 6
// #define TRQ1 7
// DRV8880 stepper(MOTORS_STEPS, DIR, STEP, SLEEP, M0, M1, TRQ0, TRQ1);
// #include "BasicStepperDriver.h" // generic
// BasicStepperDriver stepper(DIR, STEP);
void setup() {
Serial.begin(115200);
// Configure stopper pin to read HIGH unless grounded
pinMode(STOPPER_PIN, INPUT_PULLUP);
stepper.begin(RPM, MICROSTEPS);
// if using enable/disable on ENABLE pin (active LOW) instead of SLEEP uncomment next line
// stepper.setEnableActiveState(LOW);
stepper.enable();
// set current level (for DRV8880 only). Valid percent values are 25, 50, 75 or 100.
// stepper.setCurrent(100);
Serial.println("START");
// set the motor to move continuously for a reasonable time to hit the stopper
// let's say 100 complete revolutions (arbitrary number)
stepper.startMove(100 * MOTOR_STEPS * MICROSTEPS); // in microsteps
// stepper.startRotate(100 * 360); // or in degrees
}
void loop() {
// first, check if stopper was hit
if (digitalRead(STOPPER_PIN) == LOW){
Serial.println("STOPPER REACHED");
/*
* Choosing stop() vs startBrake():
*
* constant speed mode, they are the same (stop immediately)
* linear (accelerated) mode with brake, the motor will go past the stopper a bit
*/
// stepper.stop(); // Закомментировал временно
// stepper.startBrake();
}
// motor control loop - send pulse and return how long to wait until next pulse
unsigned wait_time_micros = stepper.nextAction();
// 0 wait time indicates the motor has stopped
if (wait_time_micros <= 0) {
stepper.disable(); // comment out to keep motor powered
delay(3600000);
}
// (optional) execute other code if we have enough time
if (wait_time_micros > 100){
// other code here
}
}
This code does not work - SD does not spin. What's wrong?
#define LED_BUILTIN D11
#define LED_RED D12
#define adc_pin0 A0
bool LOW = false;
bool LED = LOW;
#include <Arduino.h>
#include "BasicStepperDriver.h"
// Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step
#define MOTOR_STEPS 3600
byte RPM = 60;
byte Angle = 10;
// Since microstepping is set externally, make sure this matches the selected mode
// If it doesn't, the motor will move at a different RPM than chosen
// 1=full step, 2=half step etc.
#define MICROSTEPS 1
// All the wires needed for full functionality
#define DIR 6
#define STEP 5
BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP);
uint8_t incomingByte = 0;
// the setup function runs once when you press reset or power the board
void setup() {
Serial.begin(250000);
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
pinMode(LED_RED, OUTPUT);
stepper.begin(RPM, MICROSTEPS);
}
// the loop function runs over and over again forever
void loop() {
if (Serial.available() < 1) return;
incomingByte = Serial.read();
switch( incomingByte )
{
case '0': RPM = 0; break;
case '1': RPM = 10; break;
case '2': RPM = 20; break;
case '3': RPM = 30; break;
case '4': RPM = 40; break;
case '5': RPM = 50; break;
case '6': RPM = 60; break;
case '7': RPM = 70; break;
case '8': RPM = 80; break;
case '9': RPM = 90; break;
default: break;
}
// RPM = incomingByte;
// stepper.begin(RPM, MICROSTEPS);
// stepper.rotate( 100 );
stepper.setRPM(RPM);
//delay(1);
}
I can see you have forever blocked yourself from using serial.Print() to help in debugging your code. Why did you do this and not use an Arduino with more than one serial port?
You never tell the stepper to move, no calls to stepper.move() or stepper.rotate(). Calling stepper.setRPM() changes the speed of the stepper but does not move it.
How can I make it so that at the beginning the stepper is launched, this stepper works in parallel with the program, and then, using the COM port, the rotation speed of the stepper motor is set and changed?
change over to use a different steppermotor-library.
The steppermotor-library MobaTools written by user @MicroBahner
is creating the step-pulses in the background. This means you can execute code in parallel to the step-pulse creation.
the complete code is well organised in functions where each function does a senseful SUB-unit of things:
using the MobaTools-library for creating the step-pulses
check serial inputbuffer if serial data has arrived
if serial data has arrived process data to set a new stepper-motorspeed
check a momentary pushbutton to switch a variable between "true" and "false" which makes the steppermotor move / stop
All this is done in a non-blocking way.
This means the whole code is all the time responsive to
check if new data has arrived
check if the start / stop-button has been pressed
creating the step-pulses based on a timer--interrupt "in the background"
(this is done by the MobaTools-library automatically)
writing non-blocking code
This non-blocking is achieved through
let do function loop() all looping
all other functions work in a quickly jump in / quickly jump out manner
If really all functions can rely on "the code will call me in about 100 microseconds again and again and again"
These functions do not need any inner while-loop or for-loop
And zero while-loops and zero-for-loops are allowed
special case creating step-pulses
step-pulses need an extreme tight timing ! Variating a single microsecond between two pulses will make the stepper-motor "stuttering"
The MobaTools-library achives this requirement through a timer-interrupt that gets called absolutely regulary. Offering this for up to 6 stepper-motors limits the maximum achievable speed. As long as you don't use high resolution microstepping the achievable speeds with using an arduino is sufficient.
If you need indeed more than 1/4 microstepping at high rpms you must use a faster microcontroller like an ESP32
// a comlex 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 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 = 8000; // 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 conected 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
myStepper.setSpeed( StepperSpeed );
myStepper.setRampLen( 100 ); // Rampenlänge 100 Steps bei 20U/min
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);
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:
*/
We are using single board computer "ODYSSEY - X86J4105800" with "Arduino Coprocessor ATSAMD21 Core ARM Cortex-M0+". Can it work with the MobaTools library?
And another stupid question: how can I download and install Mobatools from Github? Can I download the corresponding cpp files and compile them myself?
I tried Mobatools in Arduino MKR Wifi 1010(ATSAMD21 Core) but the library did not work in it. However, using timer interrupts as in that library is a good way to go.
I have written the code based on the following thread on timer interrupts.
#include <Arduino.h>
// Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step
#define MOTOR_STEPS 3600
byte RPM = 60;
byte Angle = 10;
// All the wires needed for full functionality
#define DIR 6
#define STEP 7
uint8_t incomingByte = 0;
// Set timer TC4 to call the TC4_Handler every 5ms: (8 * (29999 + 1)) / 48MHz = 5ms
void setup() {
PORT->Group[PORTA].DIRSET.reg = PORT_PA21; // Set D7 as a digital output
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK0 for TC4 and TC5
GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TC4_TC5; // Feed GCLK0 output to TC4 and TC5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
TC4->COUNT16.CC[0].reg = 29999; // Set the TC4 CC0 register as the TOP value in match frequency mode
while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
NVIC_SetPriority(TC4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
NVIC_EnableIRQ(TC4_IRQn); // Connect TC4 to Nested Vector Interrupt Controller (NVIC)
TC4->COUNT16.INTENSET.reg = TC_INTENSET_OVF; // Enable TC4 overflow (OVF) interrupts
TC4->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCSYNC_PRESC | // Reset timer on the next prescaler clock
TC_CTRLA_PRESCALER_DIV8 | // Set prescaler to 8, 48MHz/8 = 6MHz
TC_CTRLA_WAVEGEN_MFRQ | // Put the timer TC4 into match frequency (MFRQ) mode
TC_CTRLA_MODE_COUNT16; // Set the timer to 16-bit mode
while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
// TC4->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC4 timer
// while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
Serial.begin(250000);
// initialize digital pin LED_BUILTIN as an output.
pinMode(DIR, OUTPUT);
}
void TC4_Handler() // Interrupt Service Routine (ISR) for timer TC4
{
//if (TC4->COUNT16.INTFLAG.bit.OVF && TC4->COUNT16.INTENSET.bit.OVF) // Optionally check for overflow (OVF) interrupt
//{
PORT->Group[PORTA].OUTTGL.reg = PORT_PA21; // Toggle the D7 output
TC4->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF; // Clear the OVF interrupt flag
//}
}
// the loop function runs over and over again forever
void loop() {
if (Serial.available() < 1) return;
incomingByte = Serial.read();
switch( incomingByte )
{
case '0': RPM = 0; break;
case '1': RPM = 10; break;
case '2': RPM = 20; break;
case '3': RPM = 30; break;
case '4': RPM = 40; break;
case '5': RPM = 50; break;
case '6': RPM = 60; break;
case '7': RPM = 70; break;
case '8': RPM = 80; break;
case '9': RPM = 90; break;
default: break;
}
// stepper.begin(RPM, MICROSTEPS);
// stepper.rotate( 100 );
//stepper.setRPM(RPM);
if (RPM == 0)
{
TC4->COUNT16.CTRLA.bit.ENABLE = 0; // Disable the TC4 timer
while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
else
{
if (RPM > 0)
{
digitalWrite(DIR, LOW);
}
else
{
digitalWrite(DIR, HIGH);
}
float step_width_sec = 60.0f / abs(RPM) / MOTOR_STEPS;
int request_count = step_width_sec * 3000000;
TC4->COUNT16.CC[0].reg = request_count;
TC4->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC4 timer
while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
//delay(1);
}
I have not been able to verify the operation on the actual device, but it appears to be working correctly as measured with an oscilloscope.
Note that the code does not rotate initially for safety reasons and that pulses are output on PIN 7.
I hope this help you.
EDIT: I forgot to make RPM absolute when calculating the step period.