I’m trying to understand how I should program the projector.
This is a program to determine the speed of the motor, using the photosensor:
const int transistorPin = 9; // connected to the base of the transistor
float rpm = 0;
unsigned long lastRotTime = 0;
unsigned long timeSinceLastRot = 0;
boolean overPhotoSensor = false;
boolean lastOverPhotoSensor = false;
void setup() {
pinMode(transistorPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
int potValue = analogRead(A0);
int outputValue = potValue/4;
analogWrite(transistorPin, outputValue);
timeSinceLastRot = micros() - lastRotTime;
int photoSensorVal = analogRead(A1);
// Serial.println(photoSensorVal);
// turn on laser if something is close to the photosensor
overPhotoSensor = photoSensorVal < 100;
if(overPhotoSensor != lastOverPhotoSensor){
if(overPhotoSensor){
rpm = 60000 * 1000 / timeSinceLastRot;
Serial.println(rpm);
timeSinceLastRot = 0;
lastRotTime = micros();
}
}
lastOverPhotoSensor = overPhotoSensor;
}
This is basically the StateChangeDetection example that comes with Arduino.
Here are some numbers. I’m still using 5V, and I’m getting top speeds around 1.800 rounds per minute, or 30 per second (= 1.800/60). That means one rotation takes around 32-33 ms (= 1/30 * 1.000).
I counted how many times the Arduino goes through the loop() function during each rotation: I get values around 125. And here I have a problem. Each time my code in loop() gets executed, I can draw 1 “pixel”. If the Arduino can only do the loop() 125 times then I can only draw 125 “pixels”, which is not enough to draw something interesting. I probably don’t need 1.800 rpm, but even at half that speed I would only get 250 pixels.
So the chip needs around 0.264 ms (= 125 executions per rotation / 33 ms per rotation) to execute my code in loop(). I confirmed this with this piece of code:
timeSpentInFrame = micros() - now;
Serial.println("timeSpentInFrame");
Serial.println(timeSpentInFrame);
now = micros();
Then I created a minimal Arduino program to see how much time it would to loop():
unsigned long timeSpentInLoop;
unsigned long now;
void setup(){
Serial.begin(115200);
}
void loop(){
timeSpentInLoop = micros() - now;
Serial.println("timeSpentInLoop");
Serial.println(timeSpentInLoop);
now = micros();
}
Here, is some sample output:
timeSpentInLoop
8
timeSpentInLoop
8
timeSpentInLoop
4
timeSpentInLoop
8
timeSpentInLoop
8
This is in microseconds. The number is not stable and varies between 4 and 16. Let’s say 0.008 is the average, then is this around 30 times faster than my code. I think I have some optimizing to do.
I asked Alex and in the Arduino forums there’s a way to make analogRead() to its thing a bit faster by trading a bit of resolution for speed. I tried it out, and it was indeed faster. The Arduino needed only around 65 microseconds to through loop() instead of 264.
At a speed of 1.600 rpm this gives me 526 pixels to work with. After slowing down to 1.500 rpm, the Arduino managed to 642 loop() iterations per rotation. That still wasn’t much, and looked like interrupts might the solution. I was a bit afraid of going into that topic, because it seemed to bring me a step closer to the hardware, with the danger of loosing lots of time in uncharted territory. However, because timing is crucial for this project I decided to check it out.
Turns out that there’s two types of interrupts. One is for timers (feels a bit similar to threads), and that’s the hard one. The other types of interrupts are a bit similar to events, for example a JavaScript click event: you define a callback that get executed when a pin detects a change in value. This run directly on the hardware, outside of the loop, so its very fast. More details about interrupts can be found in the documentation.
Here is the new version of the code:
const int transistorPin = 9; // connected to the base of the transistor
float timePerRot = 0;
float rpm = 0;
volatile int rotations = 0;
unsigned long now = 0;
unsigned long prevNow = 0;
unsigned long timeSpentInFrame = 0;
int potValue = 0;
int outputValue = 0;
void setup() {
pinMode(transistorPin, OUTPUT);
attachInterrupt(0, photoSensorActivated, FALLING);
Serial.begin(115200);
}
void loop() {
potValue = analogRead(A0);
outputValue = potValue/4;
analogWrite(transistorPin, outputValue);
if(rotations >= 1){
timePerRot = (micros() - prevNow)/ rotations;
prevNow = micros();
rpm = 60 / (timePerRot / (1000.0 * 1000.0));
Serial.println("rpm");
Serial.println(rpm);
rotations = 0;
}
now = micros();
timeSpentInFrame = micros() - now;
Serial.println("timeSpentInFrame");
Serial.println(timeSpentInFrame);
}
void photoSensorActivated(){
rotations ++;
}
I only fully understood the implication of what I just did by looking at the sourcecode from a similar project. (Michiel, I don’t know your lastname so I can’t credit you properly, but thanks for posting your code.) Because the main loop() function was now free from it’s job of checking the photosensor, I could now use delay() again. I was supposing that I could only draw one pixel per execution of loop() – and that was true in the version without an interrupt. Now however, I could draw all pixels in one loop().
I made a quick test and was able to draw a fine pattern of pixels. The image was very unsteady, so I realized I couldn’t depend on the motor to maintain a constant speed. The Arduino forum had a simple algoritm to keep the speed constant:
if (rpm setRpm) {
motorValue -= 1;
}
motorValue = constrain(motorValue, 0, 255);
analogWrite(transistorPin, motorValue);
Far from perfect, but good enough to continue testing.