/*
Forum: https://forum.arduino.cc/t/arduino-stop-working-or-automatic-reset/1404103
Wokwi: https://wokwi.com/projects/440279150495983617
2025/08/25
ec2021
The function simulateRPM() was added to simulate the encoder pulses that usually come from
a motor encoder.
This is a commented version
*/
#include <util/atomic.h>
#include <PinChangeInterrupt.h>
// As #defines can be redefined during the code without notice
// it is recommended to use const or constexpr variables instead
// of #define
#define ENCODEROUTPUT_MOTOR 48
#define EN_MOTOR 3
#define IN1_MOTOR 2
#define IN2_MOTOR 4
#define ENCA_MOTOR 10
#define ENCB_MOTOR 11
unsigned long prevT = 0;
unsigned long currT = 0;
int posPrev = 0; // posPrev is ment to hold the previous value of long pos_i
// therefore it must have the same type!
// The code runs into trouble as the following lines show
// when posPrev overflows:
/*
RPM:599.98
RPM:600.00
RPM:587.45
RPM:599.95
RPM:819603.25
RPM:819701.68
RPM:819787.50
RPM:819800.00
*/
volatile long pos_i = 0;// In the Arduino environment volatile is only required for hardware registers
// where the compiler cannot see any reason that the variable would change
// and its "optimization" would remove the variable access.
// The ATOMIC_BLOCK macro is exactly what's relevant here for two reasons:
// - It makes sure that the variable is read from memory (not from a procesor register)
// - Interrupts are disabled while reading the variable so that no other access can
// interfere with its content and interrupts are safely enabled again at the end of
// the macro
void setup() {
// Today Serial communication to a PC is better done with 115200 Bd
Serial.begin(9600);
// delay(5000);
pinMode(EN_MOTOR, OUTPUT);
pinMode(IN1_MOTOR, OUTPUT);
pinMode(IN2_MOTOR, OUTPUT);
pinMode(ENCA_MOTOR, INPUT_PULLUP);
pinMode(ENCB_MOTOR, INPUT_PULLUP);
attachPCINT(digitalPinToPCINT(ENCA_MOTOR), readEncoderMotor, RISING);
prevT = micros();
}
void loop() {
/****************************/
simulateRPM(600);
/****************************/
// As the value of pos_i is only relevant inside the function
// if (currT - prevT >= 100000) {}
// It does not make sense to copy pos_i here with each loop
// It is sufficient to call the ATOMIC_BLOCK copy function inside the following
// if-statement only every 100 ms
long pos;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
pos = pos_i;
}
currT = micros();
// update after 100ms
if (currT - prevT >= 100000) { // 100000 us = 100 ms
float deltaT = (currT - prevT) / 1.0e6;
long deltaPos = pos - posPrev;
// currT - prevT must be always(!) greater or equal to 100000 at this line
// due to the if-statement above
// Therefore deltaT = (currT - prevT) / 1.0e6 = 100000/ 1.0e6 = 1.0e5/1.0e6 = 0.1
// 0.1 is always larger then 0.0001
// As the following if-statement will always be true (unless one reduces the timing interval)
// it could be removed
if (deltaT > 0.0001) {
float velocity = deltaPos / deltaT; //pulse/s
float v = velocity / ENCODEROUTPUT_MOTOR * 60.0; // RPM
Serial.print("RPM:");
Serial.println(v);
}
prevT = currT;
posPrev = pos;
}
// Calling the following function in every loop is wasting controller time
// as the data and therefore the behaviour of the motors do not change
// It would save valuable processor time if it was only called
// - once in the beginning (e.g. in setup())
// - once everytime when either direction or speed or both are changing
setMotor(1, 15, EN_MOTOR, IN1_MOTOR, IN2_MOTOR);
}
void setMotor(int dir, int pwmVal, int EN, int IN1, int IN2) {
analogWrite(EN, pwmVal);
if (dir == 1) {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
} else if (dir == -1) {
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
} else {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
}
}
void readEncoderMotor() {
// This function is called on a RISING edge on pin ENCA_MOTOR but
// reads the state of ENCB_MOTOR
// There is no(!) synchronisation with ENCB_MOTOR!
// It would be pure coincidence if both encoders work synchronized
//
// If you change from ENCB_MOTOR to ENCA_MOTOR then
// ENCA_MOTOR must always be HIGH when this ISR function
// is called on a RISING edge. It would be different if it was called on FALLING or
// - for both possibilities - with CHANGE mode however than pos_i would
// count 1 up and down and always return zero
if (digitalRead(ENCB_MOTOR) == HIGH) {
pos_i++;
} else {
pos_i--;
}
}
/* The following function creates pulses in pulseOutPin that */
/* simulate the encoder pulses of a given motor */
void simulateRPM(uint16_t RPM) {
constexpr byte pulseOutPin {5};
static unsigned long lastPulse = 0;
if (lastPulse == 0) {
pinMode(pulseOutPin, OUTPUT);
digitalWrite(pulseOutPin, HIGH);
}
unsigned long noOfPulses = ENCODEROUTPUT_MOTOR * RPM / 60;
unsigned long microsInterval = 1000000UL / noOfPulses;
if (micros() - lastPulse >= microsInterval) {
lastPulse = micros();
digitalWrite(pulseOutPin, LOW);
delayMicroseconds(5);
digitalWrite(pulseOutPin, HIGH);
}
}