The sensor below is connected to an arduino mega 2560 to measure rotational speed. Beam Sensor
To this end, an encoder wheel with 128 segments (width about 1.2 mm per segment) rotates between the two small, black pedestals. Ground and 5V for the sensor are taken from the arduino and the two sensor outputs A0 and D0 were wired to different input ports e.g. 11, which is in the pwm range, and 24. The program below was used to try and capture the sensor output, but returns only zeros. The encoder wheel does not seem to have any effect on the sensor, but when I completely block the slit between the pedestals with a piece of paper one of the green control lights of the sensor consistently goes off, but still the value sent to the PC does not change. Any ideas, why I do not get any return value other than zero from the sensor?
Any help is highly appreciated!
PS: the three value write routine was used because it is tried and tested with the python program that receives the data sent to the pc via USB, so two dummy values tZ are sent.
int inPin = 11;
unsigned long duration;
float tZ;
void sPrn_py(unsigned long tFlt , float msrdVal, float estVal) {
// serial print routine working with python program rdSerSmpl.py
Serial.print(tFlt,5);
Serial.print(',');
Serial.print(msrdVal,5);
Serial.print(',');
Serial.print(estVal,5);
Serial.print('\n');
}
void setup() {
Serial.begin(9600);
pinMode(inPin, INPUT);
tZ = 0.0;
}
void loop() {
duration = pulseIn(inPin, HIGH);
// Serial.write(duration);
if (duration < 0)
duration = -888.88;
sPrn_py(duration, tZ, tZ);
}
It takes nearly 30ms to do the serial printing you are asking it to do.
In the oscilloscope trace above I used a function generator to provide pulses to simulate the output of your photo interrupter.
At an input frequency of 30Hz, the code has only just finished printing before the next pulse comes along.
What is going to happen at higher frequencies when the next pulse comes along before you have finished printing?
Note:
Your encoder wheel with 128 segments will generate 64 pulses per revolution. That is a frequency of just over 1Hz.
The rotational speed at which it can't finish printing before the next pulse is going to be at just over 30 rpm. Not very fast for a motor.
Thank you for the great feedback Tom, and for your time and effort, John! The project I want to use the speed sensor for is this monocopter. The drawing shows the test rig which I want to tie the moncopter to until I have figured out how to control it. There are several points at which to measure the angular speed and the position. My idea was to calculate the position by integrating the angular speed. This will be needed at:
a) at the rotation block 7 where I want to know the wing speed (which will be something like 4-5 rev/sec ~ 300rpm)
b) the angular speed and position of the base beam 3. The arc through which the base beam rotates will at first be limited to 120°.
c) the elevation angle of the parallel beam 5
Since b and c do not require continuous motion it might be better to use potentiometers there so let us for now concentrate on a)
The printing is of course not needed, it is just a way to see what is happening. This will later be omitted. The 128 segments were by guess and by golly, but I feel I would need the (integrated) position to within 1 degree, so I will probably need a lot more segments.
The arduino program below now correctly records a change from the encoder wheel but there is something wrong with the serial data transmission. The arduino program, in my opinion, should send 1,2,3...etc but what is printed by my python program is something like this:
4194105596
4177459710
4194236668
4211014142
4227660541
4211080191
4227857149
4177525759
4194171132
On the arduino side the variable counter is unsigned long, so 4 byte, and arduino uses little endian representation, which I hoped would be read correctly using the python line:
sInt = int.from_bytes(ser.read(4), byteorder='little',signed=False)
In some forum I read something about arduino sending some hearder sequence but did not find anything about that. What am I doing wrong? Any help would be highly appreciated!
PS: note the "Captain Hook" style coupling between encoder wheel and the controllable motor on my high end test rig....
arduino ino porgram
volatile unsigned long counter, k = 0;
volatile long m, kOut=7777; // This variable will increase or decrease depending on the rotation of encoder
volatile int pn1, pN;
void setup() {
Serial.begin (19600);
Serial.flush();
counter = 0;
pn1 = 2;
m = 0;
pinMode(pn1, INPUT_PULLUP); // internal pullup input pin
//Setting up interrupt
//A rising pulse from encoder activates ai0().
// AttachInterrupt to DigitalPin No. 2 on Arduino.
noInterrupts();
pN = digitalPinToInterrupt(pn1);
attachInterrupt(pN, ai0, RISING);
interrupts();
}
void loop() {
// Send the value of counter
k = k+1;
if (k > 20000) {
//Serial.write(kOut + m);
// Serial.println(pN);
k=0;
m = m+1;
}
}
void ai0() {
noInterrupts();
// ai0 is activated if DigitalPin pn1 is going from LOW to HIGH
counter++;
if(counter > 4294967290) {
counter = 0;
}
Serial.write(counter);
interrupts();
}
python program on PC
import serial
import time
import csv
import os
from timeit import default_timer as timer
ser = serial.Serial('/dev/ttyACM0')
ser.flushInput()
k= -1;
m = 0;
dm = 5;
m1 = 0;
sInt = 0;
ser_bytes=""
outF = open("test_data.csv","w")
tStrt = timer()
while True:
if k < 1:
print('Program Started!')
else:
# ser_bytes = ser.readline()
sInt = int.from_bytes(ser.read(4), byteorder='little',signed=False)
if m > m1 + dm:
# print(m)
m1 = m
tStop=False
if sInt != 0:
outF.write(str(sInt) + '\n')
print(sInt)
sInt = 0
k= k+1
m= m+1
if k > 500:
tEnd = timer()
cmd = "aplay ~/Data/MorseCode/txt2morse-master/test/r.wav"
returned_value = os.system(cmd) # returns the exit code in unix
print('Max Count Reached!')
print('Time dltT ',tEnd-tStrt)
break
Here is a video of the serial output of your code from post #6.
The input to pin 2 is a 1Hz signal from a function generator (yellow trace).
On the blue trace, you will see that the value of Serial.write(counter) goes up to 0xFF and then jumps back to 0.
I've no idea why your Python code reads that as
4194105596
4177459710
4194236668
etc.
Hi John, thank you so much for your reply, it really did the trick! I was not aware, that Serial.write only sends one byte, now I have changed the arduino program to send the long value as four bytes (see below) and added a
ser.baudrate = 19200
statement to the python code and now it works like a charm. Next I will add some code to measure the time between encoder ticks to get the rotational speed and will probably use a Kalman filter to smooth the value. Right now I do not know, what the minimum width of an encoder segment is but I will probably first try to use the smoothed value of rotational speed to guess the encoder position to within 1°. I will place the whole project in a git repository at some point, there are a few things I have learned so far which I would like to share.
Thanks again and have a great day!
volatile unsigned long counter, k = 0;
volatile long m, kOut=7777; // This variable will increase or decrease depending on the rotation of encoder
volatile int pn1, pN;
union {
unsigned long position;
unsigned char bytes[4];
} CurPos;
void setup() {
Serial.begin (19200);
Serial.flush();
counter = 0;
pn1 = 2;
m = 0;
pinMode(pn1, INPUT_PULLUP); // internal pullup input pin
//Setting up interrupt
//A rising pulse from encoder activates ai0().
// AttachInterrupt to DigitalPin No. 2 on Arduino.
noInterrupts();
pN = digitalPinToInterrupt(pn1);
attachInterrupt(pN, ai0, RISING);
interrupts();
}
void loop() {
// Send the value of counter
k = k+1;
if (k > 20000) {
//Serial.write(kOut + m);
// Serial.println(pN);
k=0;
m = m+1;
}
}
void ai0() {
noInterrupts();
// ai0 is activated if DigitalPin pn1 is going from LOW to HIGH
counter++;
if(counter > 4294967290) {
counter = 0;
}
CurPos.position = counter;
for (int i=0; i<4; i++)
Serial.write(CurPos.bytes[i]);
interrupts();
}
I can foresee a problem when your motor gets above a certain speed.
Here is what happens for a 300Hz input frequency, period 3.33ms.
The Serial.write( ) has finished writing after just over 2ms, and there is >1ms before the next pulse comes along.
The counter operates as desired.
Now look what happens when the frequency is increased to 510Hz.
The period of the incoming waveform is 1.96ms and is now less than the time it took to do the serial writing.
Things have now gone wrong and it stops printing the correct count.
It is now writing a single byte, and not incrementing.
The writes do not coincide with the incoming pulses any more.
I'm predicting that you will see this problem when the motor speed gets up to 510/128 revolutions per second, around 240 r.p.m. (or at twice that speed - I wasn't sure whether your encoder wheel with 128 segments gives128 pulses per revolution or only 64.)
I think you will have to use an even higher baud rate, to delay the onset of the problem.
What is the maximum speed that the motor is likely to go to?
First, the serial write is for test purposes only, the data will be processed on the arduino later on. My idea is, that the encoder measures the rotational speed of the monowing, which rotates at bout 300rpm but the sensor is attached to the wing, so, as stated earlier, processing the rotational information takes place on the arduino that receives the data. The encoder will be fixed to the pivot pin below block 7 on the drawing and the beam sensor will be fixed to the rotating block. I will need some serial data transmission though, since the elevation angle of the parallel arms and the speed at which the arms travel left to right (and reverse) have to be transmitted to the arduino on the monowing, which is the unit that ultimately controls everything. Your review of possible rates of transmission will be helpful to determine what will be the maximum rate of travel of the parallel beams, because I think the change in elevation angle will be rather small.
This doesn't do anything other than roll over the counter 5(?) counts earlier than the uint32_t counter would roll over all by itself. Are you saving the 0xFFFFFFFB-0xFFFFFFFF bit patterns for some future use?
This was when I had no idea what was going on and received results that were close to the maximum value of an unsigned long, so I tried to make sure the variable did not have any random value. I am currently not sure how best to tackle the rotational speed so I might make this the number of slots in my encoder wheel. I would than reset after one complete revolution.
Currently I am surprised by the results from the program below for most of the times every other value of dT is 0 (zero) but now and then two consecutive values are not. This all seems a bit random.
volatile unsigned long counter, k = 0;
volatile long m, curTm, prvTm, dT;
volatile int pn1, pN, nSeg, i;
volatile float rpSec, pi, dFhi, dFhiSec;
typedef union {
unsigned long intNum;
uint8_t bytes[4];
} INTUNION_t;
INTUNION_t myInt;
typedef union {
float fltNum;
uint8_t bytes[4];
} FLOATUNION_t;
FLOATUNION_t myFlt;
void setup() {
Serial.begin (19200);
Serial.flush();
nSeg = 64;
pi= asin(1.0);
dFhi = 2*pi/nSeg;
prvTm = 0;
counter = 0;
pn1 = 2;
m = 0;
pinMode(pn1, INPUT_PULLUP); // internal pullup input pin
//Setting up interrupt
//A rising pulse from encoder activates ai0().
// AttachInterrupt to DigitalPin No. 2 on Arduino.
noInterrupts();
// the digitalPinToInterrupt function has been
// added hoping that the code will run on differen
// arduinos without requiering a change
pN = digitalPinToInterrupt(pn1);
attachInterrupt(pN, ai0, RISING);
interrupts();
}
void loop() {
// Send the value of counter
k = k+1;
if (k > 20000) {
//Serial.write(kOut + m);
// Serial.println(pN);
k=0;
m = m+1;
}
}
void ai0() {
noInterrupts();
// ai0 is activated if DigitalPin pn1 is going from LOW to HIGH
counter++;
curTm = millis();
dT = curTm - prvTm;
//dFhiSec = dFhi/dT;
if(counter > 4294967290) {
counter = 0;
}
myInt.intNum = counter;
for ( i=0; i<4; i++)
Serial.write(myInt.bytes[i]);
myFlt.fltNum = dT; // dFhiSec;
for ( i=0; i<4; i++)
Serial.write(myFlt.bytes[i]);
prvTm = curTm;
interrupts();
}