First of all, I hope have not opened an already existing thread again...
My problem is: I'd like to measure the RPM of my motors in every 10ms for build a speed holder regulation for my robot.
I have found a basic sketch for calculate RPM, (I'll attach that later). The problem with that is, that is calculating in every sec. So I need to change it to calc in every 10ms, but I'm in trouble cause I can't do that.
I hope someone can help me out with some idea in how should I do that.
Big thanks in advance!
#include "TimerOne.h"
const byte MOTOR1 = 2; // Motor 1 Interrupt Pin - INT 0
const byte MOTOR2 = 3; // Motor 2 Interrupt Pin - INT 1
unsigned int counter1 = 0;
unsigned int counter2 = 0;
float diskslots = 20;
void ISR_count1()
{
counter1++; // increment Motor 1 counter value
}
void ISR_count2()
{
counter2++; // increment Motor 2 counter value
}
// TimerOne ISR
void ISR_timerone()
{
Timer1.detachInterrupt(); // Stop the timer
Serial.print("Motor Speed 1: ");
float rotation1 = (counter1 / diskslots) * 60.00; // calculate RPM for Motor 1
Serial.print(rotation1);
Serial.print(" RPM - ");
counter1 = 0; // reset counter to zero
Serial.print("Motor Speed 2: ");
float rotation2 = (counter2 / diskslots) * 60.00; // calculate RPM for Motor 2
Serial.print(rotation2);
Serial.println(" RPM");
counter2 = 0; // reset counter to zero
Timer1.attachInterrupt( ISR_timerone ); r
}
void setup()
{
Serial.begin(9600);
Timer1.initialize(1000000); // set timer for 1sec
attachInterrupt(digitalPinToInterrupt (MOTOR1), ISR_count1, RISING);
attachInterrupt(digitalPinToInterrupt (MOTOR2), ISR_count2, RISING);
Timer1.attachInterrupt( ISR_timerone );
}
void loop(){}
The problem is you need to have something to determine the rotation of the shaft with a resolution fine enough to count (rule of thumb) 10 tics of rotation every 10mS giving an accuracy of +/- 1mS because you could sample immediately after a slit or immediately before a slit. If you have 100 tics every 10mS your resolution is an order-of-magnitude better.
Consider 360 degrees = 1 revolution and an optical encoder with 360 slits would have a resolution of 1 part per Rev. An encoder with 0.1 degree would have 10 openings per degree.
So, you first have to know the worst case RPM, your required accuracy, and a sensor that has sufficient resolution & accuracy to provide enough counts in 10mS to do the RPM math.
For moderate RPM, below some thousand RPM, I suggest measuring the time between pulses. One pulse per revolution is good.
At every pulse pick up microSeconds(), store it and subtract the previous microS. Load previosMicroSeconds with the last reading.
Know to disable interrupts when reading the recorded data and then enable interrupts.
I'd use a 555 (or 556) monostable to generate pulses with a regular width, and filter the output to get a dc reading you can take at any time via an analog input.
Your encoders have 20 slots. If one slot goes by every 10 milliseconds it will take 200 milliseconds for a turn of the wheel. That's 5 revolutions per second or 300 RPM. For speeds lower than that you can't know the speed every 10 milliseconds.
I recommend measuring microseconds between slots:
volatile unsigned long MicrosecondsBetweenSlots1;
void ISR_count1()
{
static unsigned long lastInterruptTime = 0;
unsigned long interruptTime = micros();
MicrosecondsBetweenSlots1 = interruptTime - lastInterruptTime;
lastInterruptTime = interruptTime;
}
void loop()
{
noInterrupts();
unsigned long microsecondsPerRevolution = MicrosecondsBetweenSlots1 * 20;
interrupts();
unsigned long RPM = 60000000ul / microsecondsPerRevolution;
}
Ok, thank you everyone for answering me, but I guess some information are missing here.
(The sketch that I attached is an example that I found, is not my code.)
I am using a 3V 155RPM N20 micro gear motor with encoder. The motor is equipped with a gearbox (1:50). The encoder gives 7 impulses for one turn, so while the motor shaft turns one the encoder gives 350 impulses (7x50 - if I am thinking correctly).
While the robot moves the motors should rotate around 90RPM. Moreover the speed (RPM) will change dynamically because of the specific task.
If there is other easier option/method to make a speed holder reg for wheeled robots please don't hesitate to share with me.
Well, the output shaft turns one turn the encoder will give 350 but not the motor shaft.
Why is it necessary to regulate the motor speed to any degree.?
If you feed the motor with a fairly constant battery supply as in say a sufficient size battery, and the load is constant, ie not going up and down hills for example then the motor speed should stay relatively the same.
Other than that, there is no mystery, usual approach is to feed the motor with pwm, check the required pulses in a given time, adjust pwm up or down to suit.
However, as I said earlier, motors and gearboxes are mechanical devices and take time to respond. One would need to apply some deadband so motor was no hunting up and down in response to signals that were way ahead of the ability of the motor to react.
10ms is a nonsense.
So your task is to hold a speed. Isn't this a PID controller task? The discrete pulses from the encoder is the fundamental input you have. Work with that. Not with a calculated RPM every 10 ms, which deals with two separate time chunks, one minute and ten milliseconds, neither which equals the time chunks the input data is.
If the encoder input triggers an interrupt routine, this routine could update a global variable with the time difference since previous interrupt. I believe this is standard approach. This is the fundamental measurement of the rotation rate.
So if the wheel should rotate 2 times per second, you want 700 pulses per second. That's 1429 μs per pulse. Set that as a target in your PID controller.
Yes, if your RTC has an output pin that pulses once per second or faster. Connect the pulse from the RTC to the Input Capture pin. That will update ICR1 with the Timer1 value at the time the second started. Subtract that from the TCNT1 value to get the number of clock ticks since the second started.
yes i have sq wave pin in my module(ds3231),but i am totally lost
here is the code that i am using ,i hope to get a time stamp
hh:mm:ss:mss
#include "RTClib.h"
const byte SQWinput = 2; //Arduino pin used to monitor the SQW 1Hz output from the DS3231. Pin 2 is an "interrupt-capable" pin on the Arduino Uno. Note that different Arduino
//boards have specific pins which are inerrupt capable. Refer to www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/ for a complete list.
volatile uint16_t isrUTC; // ISR's copy of current time in UTC
volatile uint16_t millisecondsFromISR; // ISR's copy of current
unsigned long ms;
void incrementTime()
{
millisecondsFromISR = millis(); //do first to get most accuracy
++isrUTC;
}
int getMilliseconds() {
// need to make an atomic copy:
noInterrupts();
unsigned long t = millisecondsFromISR;
interrupts();
ms=millis() - t;
Serial.print(':');
Serial.print(ms, DEC);
Serial.println();
}
volatile int EDGE; //A variable to store when a falling 1Hz clock edge has occured on the SQW pin of the DS3231.
//It's recommended to make a variable volatile if it is to be shared with an Interrupt Service Routine (ISR)
//Setup the Real Time Clock, DS3231
RTC_DS3231 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
long milliseconds;
void setup () {
Serial.begin(57600); //Make sure to set the serial monitor to 57600 baud to properly read the time and date transmissions
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, let's set the time!");
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2022, 4, 03, 21, 14, 0));
}
//Configure SQW pin on the DS3231 to output a 1Hz squarewave to Arduino pin 2 (SQWinput) for timing
rtc.writeSqwPinMode(DS3231_SquareWave1Hz);
pinMode(SQWinput, INPUT); //Configure the SQWinput pin as an INPUT to monitor the SQW pin of the DS3231.
digitalWrite (SQWinput, HIGH); //Enable the internal pull-up resistor, since the SQW pin on the DS3231 is an Open Drain output.
attachInterrupt(digitalPinToInterrupt(SQWinput), Isr, FALLING); //Configure SQWinput (pin 2 of the Arduino) for use by the Interrupt Service Routine (Isr)
EDGE = 1; //Initialize EDGE equal to 1. The Interrupt Service Routine will make EDGE equal to zero when triggered by a falling clock edge on SQW
}
//Interrupt Service Routine - This routine is performed when a falling edge on the 1Hz SQW clock from the RTC is detected
void Isr () {
EDGE = 0; //A falling edge was detected on the SQWinput pin. Now set EDGE equal to 0.
}
void loop (){
//Test if EDGE has been made equal to zero by the Interrrupt Service Routine(ISR). If it has, then update the time displayed on the clock
if (EDGE == 0){
displayTime ();
EDGE = 1; //Now set EDGE equal to 1 after returning from the displayTime function. The time will not be updated again until another
//falling clock edge is detected on the SQWinput pin of the Arduino.
getMilliseconds();}
}
void displayTime () {
DateTime now = rtc.now();
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.print(") ");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
Serial.print(':');
Serial.print(ms, DEC);
Serial.println();
It looks like you defined twi ISRs but only used one. Here is a version that might work.
#include "RTClib.h"
//Setup the Real Time Clock, DS3231
RTC_DS3231 rtc;
// Arduino pin used to monitor the SQW 1Hz output from the DS3231.
// Pin 2 is an "interrupt-capable" pin on the Arduino Uno. Note
// that different Arduino boards have specific pins which are
// inerrupt capable. Refer to:
// www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
// for a complete list.
const byte SQWinput = 2; // Must be External Interrupt
volatile uint32_t MillisecondsAtStartOfSecond = 0;
const char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
// The SQW pin, when set to 1 Hz mode, has a falling edge at the beginning of every second.
void SQWFallingISR()
{
MillisecondsAtStartOfSecond = millis();
}
void setup ()
{
Serial.begin(115200); // Set Serial Monitor to 115200
delay(200);
pinMode(SQWinput, INPUT_PULLUP);
if (! rtc.begin())
{
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
if (rtc.lostPower())
{
Serial.println("RTC lost power, let's set the time!");
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2022, 4, 03, 21, 14, 0));
}
// Configure SQW pin on the DS3231 to output a 1Hz squarewave
rtc.writeSqwPinMode(DS3231_SquareWave1Hz);
// Get MillisecondsAtStartOfSecond on the falling edge of SQW.
attachInterrupt(digitalPinToInterrupt(SQWinput), SQWFallingISR, FALLING);
}
void loop ()
{
DateTime now = rtc.now();
unsigned long ms = millis();
noInterrupts();
// How long since the second started?
ms -= MillisecondsAtStartOfSecond;
interrupts();
displayTime (now, ms);
delay(950);
}
void displayTime(DateTime &now, unsigned long ms)
{
ms = ms % 1000; // Ignore full seconds
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.print(") ");
Serial.print(now.hour(), DEC);
Serial.print(':');
if (now.minute() < 10)
Serial.print('0');
Serial.print(now.minute(), DEC);
Serial.print(':');
if (now.second() < 10)
Serial.print('0');
Serial.print(now.second(), DEC);
Serial.println();
Serial.print(':');
if (ms < 100)
Serial.print('0');
if (ms < 10)
Serial.print('0');
Serial.print(ms, DEC);
Serial.println();
}