John Engelbrecht June 27 2017
Vocabulary: Arduino, C language, microcontroller, stepper motor, sketch, shield, Uno, Due, I/O, Processing (programming language) (look this up in Wikipedia)
Comment about Italy: Arduino is designed in Italy, and Uno and Due are Italian names. All these Italian words have Italian pronunciations.
Students programming for Arduino: you will not be building a full robot, please be patient and aim at simpler goals.
Sources for Arduino: Amazon, Adafruit, Jameco
Topics in the www.arduino.cc C Language Reference
The delay function is fine for little demonstration programs like blinking an LED. But the processor can do nothing else when it is delay-ing, so delay is not used in sophisticated programs.
There are two alternatives to delay. One is "polling" using the millis function. "Millis" means millisecond, and it returns the number of milliseconds that have elapsed since the program was first launched. You use an unsigned long variable for millis. How much time can elapse? An unsigned long variable can reach 4.3 billion, so the elapsed time can be 4.3 billion / 1000 = 4.3 million seconds! That is 1194 hours.
The other alternative to the delay function is interrupts, using the attachInterrupt function. Interrupts have been around since the 1970s and they work fine with Arduino, you just have to spend time with the documents and examples to learn how to do it. But interrupts are not for Arduino beginners, who should go ahead and use the delay function.
A web site that talks you through this: http://www.makeuseof.com/tag/arduino-delay-function-shouldnt-use/
Here are some documents for interrupts.
https://www.arduino.cc/en/Reference/AttachInterrupt
https://gonium.net/md/2006/12/20/handling-external-interrupts-with-arduino/
https://www.arduino.cc/en/Reference/HomePage which is a general C language reference suited to Arduino
http://old.endurancerobots.com/wp-content/uploads/2016/04/Arduino-Reference.pdf which is 912 pages as .PDF
Here is how interrupts affect students using Arduino with mechanisms like stepper motors. The $28 Arduino Uno Rev3 SMD has only two interrupt pins. The $45 Arduino Due has lots of interrupt pins and loads of digital I/O, is 32 bit, and clocks 4.7 times faster, and so is better for stepper motors.
But there are three reasons why you might use the Uno rather than Due: lower price, 5 volt logic, and compatibility with more "shields." Shields are add-on circuit boards that increase the usefulness of Arduino.
The point about logic voltage needs some more explaining. 5 volt logic has been the standard for microcontrollers for a long time, but new microprocessors and microcontrollers get their powers by using 3.3 volt logic. But the older shields need 5 volts. Therefore, the choice to go with the newer, faster Due is not a slam dunk.
Using Arduino Due with stepper motors and interrupts has a potential to make a full robot with one Arduino Due, even if three steppers are used.
The way Mr. Engelbrecht does the stepper timing is to build his own shield that generates the step timing. It is easy to let the sketch control the step timing, using 2, 3, or 4 digital outputs to set a digital-to-analog control voltage, which controls the timing. (This is not explained here, it just works, and if a stepper needs to accelerate a heavy load, the step timing needs to start the acceleration with steps around 60 steps per second, then gradually work up to 400 steps per second. This is to avoid stalling the motor, which is not destructive, it is just embarrassing when you assume your stepper can do instant acceleration. The same story plays out for deceleration, by the way.) The sketch can use three interrupts, one per motor, and the loop function boils down to paying attention for position sensors and deciding how fast the motors need to go. The step timings, being done by the custom shield and interrupts, take care of themselves, they don't burden the loop function.
Arduino is not fast. but the Due is so much better than Mega 2560. Even though the common Arduino clock is 16MHz (Due has 84MHz with 32-bit ARM core), there is a lot of microcode (look up this word on Wikipedia) per line of C program.
These times are all in microseconds, which makes it sound fast, but really they are quite slow. Reading an analog pin on Mega 2560 takes a third of a millisecond, so you can only do 3000 analog reads per second.
These measurements were done by Mr. Engelbrecht on his Mega2560 and Due by making a loop to repeatedly do the function, for 10000 times or so, and flash the pin13 LED when the loop was complete. I just counted the seconds for the 10000 repetitions to complete, and divided by the number of repetitions.
Without any further measurements, it is easy to estimate your program speed by guessing the time for each line of your program, using hints from the table. You will find that your program takes a lot longer than you think. What is the effect on a robot when the program runs out of slack time? The robot motions get jerky and motions might suffer overshoot.
Due is a special microcontroller, notice how fast it is in the table, compared to Mega 2560. The Due core is ARM Cortex-M3. The RM in ARM means RISC Machine, and RISC means reduced instruction set, which means faster, simpler, and less silicon needed. https://developer.arm.com/products/processors/cortex-m/cortex-m3
Very fast digital reading and writing can be done using page 79 to 81 in Monk's Programming Arduino Next Steps 2014. He gets 4MHz on an output pin of an Uno by coding for the pins directly rather than through digitalRead, digitalWrite, and pinMode.
Not that many, really, about 11 digital I/O. But the analog pins may be set up for digital use. That gets you about 18 digital I/O on Uno. On Uno, you see tilde "~" at some pins, those are the ones that can also do PWM. But they are valid digital I/O, too.
Warning: putting the wrong connections onto your Arduino can burn it up. Static on your body can also burn it up, this is a problem when humidity is low. Discharge yourself to the power-supply, either ground or 5V or 12V.
The biggest wire that can insert without overstress is 22 gauge solid. 24 gauge is easier. Do not purchase stranded wire for Arduino. If you are inserting square pins, they are .025" square. A square-pin header has a pin every .1". Warning: there is extra space from pin 7 to pin 8 on Uno, and at that location on other Arduinos. The spacing there is .16", not .1". So you can't stuff in a pin header that bridges across 7 to 8. Adafruit has premium jumpers at a good price, https://www.adafruit.com/product/758.
Uno Rev3 Surface Mount Device edition above, Due below at half size
Both have the reset button at upper left, Due adds an erase button in upper right. Both have coaxial 7-12V power input jack at lower left. The size of the microcontroller chip is tiny on Uno, giant on Due.
Uno has a large, silvery, USB type B jack at upper left, Due has two micro-B USB jacks, one being Native and one being Program.
Due at lower right has two Digital-to-Analog Converter outputs and two CAN bus pins. Due at upper right has four serial ports whereas Uno has one.
Due has the double row of digital I/O along the right edge that increases Due's capabilities so much.
Price depends greatly on whether it is sourced through https://store.arduino.cc/usa/ or made by a clone maker. Arduinos used to be made in Italy but it looks like the Arduino store (https://store.arduino.cc/usa/) is selling Chinese-made Arduinos, built by a quality China vendor. Mr. Engelbrecht received three Mega 2560 on July 13 2017 that were sold at $12 through Walmart online. $12 is about a fourth of the Arduino-store price. If you purchase through other than the Arduino store, Adafruit, Sparkfun, or another reliable vendor, you may be purchasing low quality. It might work, and it might not. I have had success with Elegoo, but they seem to only make the Mega 2560.
Walmart.com and Amazon.com both have a bewildering assortment of starter packs, most with Uno or Mega 2560. They are $25 to $70 and have a great range of sensors and breadboarding. Mr. Engelbrecht has a $53 pack and will sell bits and pieces at good prices. These neat starter packs tend to come through Hong Kong and have prices about 1/3 what you pay for the sensors individually. Be aware that Arduino shields and sensors are mass-produced items that are quite cheap to manufacture, mostly $1. If you see an advertised price of $15, it is mostly markup and handling. The big starter packs are where a company orders items in quantities of thousands and splits the items out to bags or boxes.
The USB cables used to connect Arduino to your computer are the Type A to Type B for Uno and Mega 2560, and the Type A to micro B for Due. Do not buy a mini USB cable, it doesn't fit any Arduino. Note: Type B for Uno and Mega 2560, also for many printers, is the bulky 2x2 style.
Through Walmart.com, these cables are $1.50 to $2.50, no need to spend $6! Mr. Engelbrecht has 5 each of the Type B and micro B, as of July 7.
Just a word about the big, black power connector at the lower left of Arduino boards: it can accept 7 to 12V and series regulates that down to 5V and further to 3.3V. The black, "coaxial" connector is 5.5mm OD, 2.1mm pin. Jameco and Marlin P. Jones Associates have the plug so you can power your Arduino project separately from the power that comes from the programming computer.
7V to 12V can be had from a "wall pack" power module. It might also come from the power source for relays or motors that you may be using. Mr. Engelbrecht likes to build a 7.5V series regulator into his motor drivers.
John Engelbrecht July 19 2017 South Austin
johnenge@earthlink.net (512) 202-2042
Research can be 100% on Internet. If you are directly quoting a web page (you may copy-paste if you like), copy-paste the address of the web page, such as http://makezine.com/2013/01/31/maker-shed-microcontroller-quick-reference-chart/
Use some sources other than Wikipedia.
If you are using a word processor for your paper, put in some illustrations from Internet by screen printing, cropping, pasting.
If your word processor can do it, make a .PDF output to submit to your instructor, rather than .DOC or .DOCX.
Student Paper About Maker Microcontrollers
to answer questions:
What is a Maker?
What advantage does a Maker have in college and for employment?
What is an integrated circuit?
What is the difference between a microprocessor and a microcontroller? How much does each of these integrated circuits cost?
How would you explain Arduino to your grandmother? What type of Arduino project would she be most interested in?
What are popular programming languages for microcontrollers?
What are alternatives to Arduino?
How many book titles about Arduino are at Barnes and Noble, either on-line or in-store?
What are some on-line sources for Arduino?
How does one program an Arduino? How expensive is the programming environment, called "integrated development environment"?
If you hold a relay and a power MOSFET transistor in your hands, how can you tell which is which?
Mr. Engelbrecht did a program to drive a stepper motor in both directions and at two different speeds on July 4, 2017. This program does not depend on the stepper library, so the simple commands to get the stepper working are right there to see. The commenting gives a briefing about stepper phases. The program looks long but it is repetitions of just a few things.
/* John Engelbrecht South Austin johnenge@earthlink.net
June 30 2017 stepper_bidi_onOff on July 4, it worked, I had been Compiling but failing to Upload
NOTE: pay attention to upper/lower case, digitalRead is OK but DigitalRead is invalid
The program verifies and compiles, sketch size 1906 bytes of 258048 bytes max in Mega2560
-----Before taking off on stepper programming, it is easy to put 1V on a stepper motor's two
coils and see there is holding torque, then by reversing one coil at a time, you can get
consistent motion, both CW and CCW. This gives students confidence that the motor is
going to work, and helps them make some sense of the following 2-bit binary codes.-----
My first stepper program, use delay(), just use one motor with 3 inputs: direction, fast, and on/off.
A later program needs to get into interrupts so I can do two motors at once.
00 01
10 1 2
4 <--CCW 3 11 This depicts a 12-step stepper motor seen
11 3 CW-> looking into the shaft.
01 2 4 10 The numbers 1-4 denote shorthand for the
two-bit phase states.
1 1 00 (Note that the binary numbers are not a binary
00 2 countup. They are gray code for the two phases,
4 3 01 which we can call AB.)
10 11 If the motor is idle at location 3, and you change
phase B from 1 to 0, the motor turns clockwise to
location 4. On the other hand, from location 3,
if you change phase A from 1 to 0, the motor turns
counter-clockwise to location 2.
The idea of the program is to look at two inputs, direction and rotation. If the rotation input is low, do not move the motor.
If rotation is high and direction is low, move the motor CCW.
If rotation is high and direction is high, move CW.
The variable that tracks location is num, an integer from 1 to 4.
The Loop function starts by reading the inputs and deciding any change to num.
Then you translate num to phase outputs phaseA and phaseB. Lastly, there is a delay
before looping, the delay depending on the fast input.
The diagram above shows a 12-step motor. Any stepper is going to have a number of steps that is a multiple of 4, so the 12-step diagram is valid for steppers with any number of steps.
*/
const int ledPin = 13; // the number of the LED pin
const int delayVal = 800;
const int dirPin = 12;
const int rotPin = 11;
const int phAPin = 10;
const int phBPin = 9;
const int fastPin = 8; //if this pin is low, stepper goes slowly
int num=1; //start at this location, num is 1 to 4 as in above diagram
boolean direc; //declare global variables, these three will be read in from switches
boolean rotation;
boolean fast;
void setup() {
pinMode(ledPin,OUTPUT);
pinMode(dirPin, INPUT);
pinMode(rotPin,INPUT);
pinMode(fastPin,INPUT);
pinMode(phAPin,OUTPUT);
pinMode(phBPin,OUTPUT);
}
void loop() {
direc = digitalRead(dirPin);
rotation = digitalRead(rotPin);
digitalWrite(ledPin, direc); //eye candy
fast = digitalRead(fastPin); //at this point, all three switches are read, now decide if the motor needs to turn CW or CCW, or just stay put
if (rotation && direc) { //if rotation is true and direction is true
num++; //num++ is C language for "increment num"
if (num > 4) {
num = 1; //if num goes higher than 4, it rolls back to 1, consistent with diagram
}
}
if (rotation && !direc) { //if rotation is true and direction is false !direc reads "not direction," ! is a logical inversion
num--;
if (num < 1) {
num = 4; //if num goes lower than 1, it rolls to 4
}
}
//code above: if rotation is the logical value false, it keeps num the same and stepper stands still.
//Now that num is known, translate it into the phase signals, in keeping with the diagram
if (num == 1) {
digitalWrite(phAPin,LOW);
digitalWrite(phBPin,LOW);
}
if (num == 2) {
digitalWrite(phAPin,LOW);
digitalWrite(phBPin,HIGH);
}
if (num == 3) {
digitalWrite(phAPin,HIGH);
digitalWrite(phBPin,HIGH);
}
if (num == 4) {
digitalWrite(phAPin,HIGH);
digitalWrite(phBPin,LOW);
}
delay(!fast*delayVal + fast*delayVal/30); //delay this number of milliseconds, if it is 50 milliseconds then stepper will step at 20 steps per second
//note: in the delay() command, a math combination of boolean and integer types works
} //end of loop function
// Here is how the phaseA and phaseB logical outputs translate to the stepper motor: the motor has four wires. There are two electromagnet coils in the motor, each coil is two wires. If phaseA is high, send positive current through the A coil. If phaseA is low, reverse the current. The current is typically 1.5 amps at 12V, which is lots of power. Mr. Engelbrecht has a PCB which does the power boosting, and he uses a 12V power supply rated at 3 to 4 amps, $14.
On July 9, I am most of the way through laying out a custom PCB that will offer interrupts to Arduino, to free Arduino from the onerous delay( ) function. The PCB has three timers, and each timer is programmable to 16 values by digital pins on Arduino. If this turns out to work, it will be a very nice device. Yet for me to do is trying out interrupts. They are different for Mega 2560 and ARM-based (Due) Arduinos. The ISR will issue a handshake signal back to Arduino, insuring that Arduino will not miss interrupts.
/* John Engelbrecht South Austin johnenge@earthlink.net
July 15 2017 int_gen_test1_poll.ino
/* Custom PCB with 3 oscillators for stepper-motor interupts is built up for 1 osc, and the
"Little PCB" shield for pins 36-55 is built up. Need to test them in polling mode before
trying interrupts. At 3:30 PM, the system works: interrupt oscillator doesn't have the R-2R
resistors yet but I have a fixed voltage on the DAC output, and the oscillator works, the
little shield on Mega 2560 filters the oscillator's output pulse and the Mega returns a
handshake, which does the resetting of C1 as intended. The program polls the oscillator's pulse
and advances the num variable just right. The 1.5 amp stepper driver gets the two phases and
the stepper turns, using the timing that the DAC provides. (Changing the DAC voltage changes the
step rate.) Adjusting either XOR DIP switch for stepper C (on the little shield)
reverses the motor.
00 01
10 1 2
4 <--CCW 3 11 This depicts a 12-step stepper motor seen
11 3 CW-> looking into the shaft.
01 2 4 10 The numbers 1-4 denote shorthand for the
two-bit phase states.
1 1 00 (Note that the binary numbers are not a binary
00 2 countup. They are gray code for the two phases,
4 3 01 which we can call AB.)
10 11 If the motor is idle at location 3, and you change
phase B from 1 to 0, the motor turns clockwise to
location 4. On the other hand, from location 3,
if you change phase A from 1 to 0, the motor turns
counter-clockwise to location 2.
/* The Motor System
12 volt power supply
| |
| 1.5 amp stepper driver circuit with 7.5 volt regulator for Arduinos
| \----<---- |
Oscillator to generate interrupts | |
| | |
custom shield on Mega 2560 or Due | |
| | |
| /---->------------------------ |
Arduino <-------------------------------------
*/
//Aliases for the Arduino pin numbers, it is best to not put pin numbers in the Setup and Loop functions
const int ledPin = 13; // the number of the LED pin
const int delayVal = 10;
const int interruptCPin = 53; // just do polling, not interrupt yet
const int hndshkCPin = 41; // the oscillator ckt. does need this handshake
const int xorCPin = 44; // XOR is like a programmable inversion, this controls motor direction
const int xorOtherCPin = 47;
const int dacCmsbPin = 46; // DAC is digital to analog converter, the DAC in this system
// is in the oscillator ckt and controls the oscillator's period with up
// to 4 bits, under control of Arduino
const int dacC2Pin = 48;
const int dacC1Pin = 50;
const int dacClsbPin = 52; //end of four DAC pins
const int phAPin = 10; // phases are the two phases of the motor; "phase" translates to "coil"
//in the motor
const int phBPin = 9;
//begin other constants
const boolean direc = true;
const boolean rotation = true;
//begin global variables
int num=1; //start at this location, num is 1 to 4 as in above diagram
boolean itIsTimeToChange = false; //poll interruptCPin, if high then it is time to move the motor
void setup() {
pinMode(ledPin,OUTPUT);
pinMode(interruptCPin, INPUT);
pinMode(hndshkCPin,OUTPUT);
pinMode(dacCmsbPin,INPUT);
pinMode(dacC2Pin,INPUT);
pinMode(dacC1Pin,INPUT);
pinMode(dacClsbPin,INPUT);
pinMode(xorCPin,INPUT);
pinMode(xorOtherCPin,INPUT);
pinMode(phAPin,OUTPUT);
pinMode(phBPin,OUTPUT);
}
void loop() {
itIsTimeToChange = digitalRead(interruptCPin); //this is the poll, see Wikipedia "polling computer science"
digitalWrite(ledPin, itIsTimeToChange); //eye candy
if (rotation && direc && itIsTimeToChange) { // && is bitwise AND
num++;
if (num > 4) {
num = 1; //if num goes higher than 4, it rolls back to 1, consistent with diagram above
}
}
if (rotation && !direc && itIsTimeToChange) { //if direction is false
num--;
if (num < 1) {
num = 4; //if num goes lower than 1, it rolls to 4
}
}
//code above: if rotation is the logical value false or interruptCPin is LOW,
// it keeps num the same and stepper stands still.
//Now that num is known, translate it into the phase signals, in keeping with the diagram
if (itIsTimeToChange) { //begin the long routine to move the motor, this could be shortened
// a lot by writing a function
// if it is not yet time to change the motor, poll again
if (num == 1) {
digitalWrite(phAPin,digitalRead(xorCPin)^LOW);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^LOW);
}
if (num == 2) {
digitalWrite(phAPin,digitalRead(xorCPin)^LOW); // ^ is bitwise exclusive OR (XOR), read it in from
// the input pin, it lets the user reverse the direction of the motor if
// the direction is wrong. The 6 XOR bits for 3 steppers come from the
// DIP switch on the custom shield that sits on pins 36 to 55 of Due or Mega 2560.
digitalWrite(phBPin,digitalRead(xorOtherCPin)^HIGH);
}
if (num == 3) {
digitalWrite(phAPin,digitalRead(xorCPin)^HIGH);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^HIGH);
}
if (num == 4) {
digitalWrite(phAPin,digitalRead(xorCPin)^HIGH);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^LOW);
}
digitalWrite(hndshkCPin,HIGH); //oscillator ckt requires this handshake from Arduino
itIsTimeToChange = false; //the stepper has moved, reset this variable
delay(delayVal); //note: in the delay() command, a math combination of boolean and integer types is allowed
digitalWrite(hndshkCPin,LOW); // return the handshake wire to a low state
} //end of the routine to move the motor
} //end of loop function
/* John Engelbrecht South Austin johnenge@earthlink.net
July 15 2017 int_gen_test2.ino
/* Custom PCB with 3 oscillators for stepper-motor interupts is built up for 1 osc, and the
"Little PCB" shield for pins 36-55 is built up. The test in polling mode worked, time to
go ahead to interrupt. Interrupts work 3:30 PM July 16 2017.
00 01
10 1 2
4 <--CCW 3 11 This depicts a 12-step stepper motor seen
11 3 CW-> looking into the shaft.
01 2 4 10 The numbers 1-4 denote shorthand for the
two-bit phase states.
1 1 00 (Note that the binary numbers are not a binary
00 2 countup. They are gray code for the two phases,
4 3 01 which we can call AB.)
10 11 If the motor is idle at location 3, and you change
phase B from 1 to 0, the motor turns clockwise to
location 4. On the other hand, from location 3,
if you change phase A from 1 to 0, the motor turns
counter-clockwise to location 2. */
/* The Motor System
12 volt power supply
| |
| 1.5 amp stepper driver circuit with 7.5 volt regulator for Arduinos
| \----<---- |
Oscillator to generate interrupts | |
| | |
custom shield on Mega 2560 or Due | |
| | |
| /---->------------------------ |
Arduino <-------------------------------------
*/
// The way to get into interrupts is to copy from an example on the Internet
//Aliases for the Arduino pin numbers, it is best to not put pin numbers in the Setup and Loop functions
const int ledPin = 13; // the number of the LED pin
const int delayVal = 2;
const int interruptCPin = 21; // for Mega 2560, interrupts allowed on pins 2, 3, 18-21
// the interrupt shield is designed for interrupt C on 53 on a Due, so for a Mega 2560
// make 53 not an I/O and instead add a wire to 21 for the interrupt
const int hndshkCPin = 41;
const int xorCPin = 44; // XOR is like a programmable inversion, this controls motor direction
const int xorOtherCPin = 47;
const int dacCmsbPin = 46; // DAC is digital to analog converter, the DAC in this system
// is in the oscillator ckt and controls the oscillator's period with up
// to 4 bits, under control of Arduino
const int dacC2Pin = 48;
const int dacC1Pin = 50;
const int dacClsbPin = 52; //end of four DAC pins
const int phAPin = 10; // phases are the two phases of the motor; "phase" translates to "electromagnet coil"
//in the motor
const int phBPin = 9;
//begin other constants
const boolean direc = true;
const boolean rotation = true;
//begin global variables
volatile int num=1; //start at this location, num is 1 to 4 as in above diagram
// volatile is for global variables that serve needs of ISRs
void setup() {
pinMode(ledPin,OUTPUT);
pinMode(interruptCPin, INPUT);
pinMode(hndshkCPin,OUTPUT);
pinMode(dacCmsbPin,INPUT);
pinMode(dacC2Pin,INPUT);
pinMode(dacC1Pin,INPUT);
pinMode(dacClsbPin,INPUT);
pinMode(xorCPin,INPUT);
pinMode(xorOtherCPin,INPUT);
pinMode(phAPin,OUTPUT);
pinMode(phBPin,OUTPUT);
attachInterrupt(digitalPinToInterrupt(interruptCPin),cOscillatorIsr,RISING); //digitalPinToInterrupt was not allowed on computer GRID (Win XP pro) which has
// Arduino IDE 1.0.5 r3 (not in scope error). I did new Arduino install on computer GETTER Win 8.1, where it is 1.8.3. No problem compiling, now.
digitalWrite(hndshkCPin,HIGH); //July 16 2017 Must have this pulse on handshake to get C1 on the oscillator board high, and interrupt low, so that
digitalWrite(hndshkCPin,LOW); // Mega 2560 can sense the first rise of interrupt. Would be better if Due is in use, Due can sense interrupt as a high level
// whereas 2560 only senses falling, rising, and low.
} //end of setup
void loop() {
// loop function is freed up to handle step counting, oscillator's DAC, home sensor, other steppers, etc. Note how small Loop has become, now that the ISR handles the motor.
} //end of loop function
void cOscillatorIsr() { //this is the interrupt service routine, see Wikipedia "interrupt"
//this ISR is for interrupt oscillator C, the other two oscillators A and B are available for other steppers
digitalWrite(ledPin, HIGH); //turn on the LED to mark that the ISR is being worked
if (rotation && direc) { // && is bitwise AND
num++;
if (num > 4) {
num = 1; //if num goes higher than 4, it rolls back to 1, consistent with diagram above
}
}
if (rotation && !direc) { //if direction is false
num--;
if (num < 1) {
num = 4; //if num goes lower than 1, it rolls to 4
}
}
//code above: if rotation is the logical value false or interruptCPin is LOW,
// it keeps num the same and stepper stands still.
//Now that num is known, translate it into the phase signals, in keeping with the diagram
if (num == 1) {
digitalWrite(phAPin,digitalRead(xorCPin)^LOW);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^LOW);
}
if (num == 2) {
digitalWrite(phAPin,digitalRead(xorCPin)^LOW); // ^ is bitwise exclusive OR (XOR), read it in from
// the input pin, it lets the user reverse the direction of the motor if
// the direction is wrong. The 6 XOR bits for 3 steppers come from the
// DIP switch on the custom shield that sits on pins 36 to 55 of Due or Mega 2560.
digitalWrite(phBPin,digitalRead(xorOtherCPin)^HIGH);
}
if (num == 3) {
digitalWrite(phAPin,digitalRead(xorCPin)^HIGH);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^HIGH);
}
if (num == 4) {
digitalWrite(phAPin,digitalRead(xorCPin)^HIGH);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^LOW);
}
digitalWrite(hndshkCPin,HIGH); //oscillator ckt requires this handshake from Arduino
digitalWrite(hndshkCPin,LOW); // return the handshake wire to a low state
digitalWrite(ledPin, LOW);
} //end of ISR This ISR would benefit a lot from adding functions to streamline the code. But adding functions makes it harder for students to follow.
/* John Engelbrecht South Austin johnenge@earthlink.net
July 21 2017 int_gen_test5.ino
/* Custom PCB with 3 oscillators for stepper-motor interupts is built up for 1 osc, and the
"Little PCB" shield for pins 36-55 is built up. The test in polling mode worked, interrupt worked.
Go ahead to speed control through four DAC lines. Doesn't work, take some lines out, found around 3 errors.
It is built back up to int_gen_test3 by July 22 2017 12:10PM.
The idea of Loop() is to run step rate up and down, over and over again. This is done purely by controlling the DAC
in the interrupt oscillator; no timing is done in this program for step rate.
Int_gen_test5 is to factor in the timea[] scaling.
At the same time, change interrupt oscillator's R1 current-set resistor to 33k paralleled by 10k, for a peak speed
around 626 steps per second. But there was stalling and even wrong direction at low speed, so I put 5.1k in series
with the 10k branch. At this step rate, the Rdamping resistors, 30 ohms .25W, across the motor coils
burned out, I graduated them to 20 ohms 1W.
00 01
10 1 2
4 <--CCW 3 11 This depicts a 12-step stepper motor seen
11 3 CW-> looking into the shaft.
01 2 4 10 The numbers 1-4 denote shorthand for the
two-bit phase states.
1 1 00 (Note that the binary numbers are not a binary
00 2 countup. They are gray code for the two phases,
4 3 01 which we can call AB.)
10 11 If the motor is idle at location 3, and you change
phase B from 1 to 0, the motor turns clockwise to
location 4. On the other hand, from location 3,
if you change phase A from 1 to 0, the motor turns
counter-clockwise to location 2. */
/* The Motor System
12 volt power supply
| |
| 1.5 amp stepper driver circuit with 7.5 volt regulator for Arduinos
| \----<---- |
Oscillator to generate interrupts | |
| | |
custom shield on Mega 2560 or Due | |
| | |
| /---->------------------------ |
Arduino <-------------------------------------
*/
//Aliases for the Arduino pin numbers, it is best to not put pin numbers in the Setup and Loop functions
const int ledPin = 13; // the number of the LED pin
const unsigned int BAUD_RATE = 9600;
const int delayVal = 2;
const int interruptCPin = 21; // for Mega 2560, interrupts allowed on pins 2, 3, 18-21
// the interrupt shield is designed for interrupt C on 53 on a Due, so for a Mega 2560
// make 53 not an I/O and instead add a wire to 21
const int hndshkCPin = 41; // this handshake signal back to the oscillator board insures no interrupt pulse gets missed
const int xorCPin = 44; // XOR is like a programmable inversion, this controls motor direction
const int xorOtherCPin = 47;
const int dacCmsbPin = 46; // DAC is digital to analog converter, the DAC in this system
// is in the separate circuit, an oscillator ckt, and controls the oscillator's period with up
// to 4 bits of DAC, under control of Arduino. The oscillator frequency is the motor step rate.
const int dacC2Pin = 48;
const int dacC1Pin = 50;
const int dacClsbPin = 52; //end of four DAC pins
const int phAPin = 10; // phases are the two phases of the motor; "phase" translates to "electromagnet coil"
//in the motor. Each phase is two wires at the motor.
const int phBPin = 9;
//begin other constants
const boolean direc = true;
const boolean rotation = true;
//begin global variables
volatile int num=1; //start at this motor location, num is 1 to 4 as in diagram at top of code
// volatile is for global variables that serve needs of ISRs
int timea[16]; // will be populated at start of Setup()
int timeb[]={30,90,150,210,270,330,390,450,510,570,630,690,750,810,870,930}; //30ms is allowed for the slowest motor
//speed transitioning to the next faster speed. 90ms is allowed for the next transition faster...870ms is allowed
//near the upper speed range, it takes the motor more time to add kinetic energy to the load when the motor is
//going fast.
int arraypoint = 1; //can be anything from 0 to 15, this is the index into the timea array.
float scaletimea = .40; //if you want to transition motor speeds faster or slower, use this scaling factor.
//But remember this isn't the stepping rate, the stepping rate is set by the
//separate interrupt oscillator.
long timebasis; //helps decide when the motor speed has been steady long enough, the load is accelerating
//OK, no stall
boolean freqUp = true; // can be for going up or down as the program starts
void setup() {
const int d=15;
const int minTimeAtALevel = 100; //time in milliseconds
for (int e = 0 ; e <= d ; e++){ //populate the timea[] array with final values
timea[e]=int(scaletimea * timeb[e]); //scale each value of timeb[]
if (timea[e] < minTimeAtALevel){
timea[e] = minTimeAtALevel; //if you want to transition more gradually, set minTimeAtALevel higher
}
}
pinMode(ledPin,OUTPUT);
Serial.begin(BAUD_RATE); //for debug
pinMode(interruptCPin, INPUT);
pinMode(hndshkCPin,OUTPUT);
pinMode(dacCmsbPin,OUTPUT);
pinMode(dacC2Pin,OUTPUT);
pinMode(dacC1Pin,OUTPUT);
pinMode(dacClsbPin,OUTPUT);
pinMode(xorCPin,INPUT);
pinMode(xorOtherCPin,INPUT);
pinMode(phAPin,OUTPUT);
pinMode(phBPin,OUTPUT);
attachInterrupt(digitalPinToInterrupt(interruptCPin),cOscillatorIsr,RISING);
digitalWrite(hndshkCPin,HIGH); //July 16 2017 Must have this pulse on handshake to get C1 on the oscillator board high, and interrupt low, so that
digitalWrite(hndshkCPin,LOW); // Mega 2560 can sense the first rise of interrupt. Would be better if Due is in use, Due can sense interrupt as a high level
// whereas 2560 only senses falling, rising, and low.
timebasis=millis(); // millis() is milliseconds since the Arduino powered up
} //end of setup
void loop() {
// since the interrupt service routine handles motor stepping, in conjunction with interrupts coming from the
// separate oscillator circuit, this loop function is freed up to handle step counting, oscillator's DAC,
// home sensor, other steppers, etc.
//Serial.println(millis());
// Intent of int_gen_test3 and 4 is to vary motor speed, so that each change in motor speed gives some time
// (a good fraction of a second) for the load to get up to speed. This reduces the incidence of stalling when
// the motor load is high mass.
// Speed control is done by controlling the 4-bit DAC built into the interrupt oscillator. Notice that
// the DAC writing is at the end of Loop(), after timing decisions.
if (millis()>timebasis+timea[arraypoint]) { //enter the IF only when millis() has aged enough
timebasis=millis(); //get a fresh time basis
if (freqUp) {
arraypoint++;
if (arraypoint>15) {
arraypoint=15;
freqUp = false; //max speed has been reached, make the speed go back down
}
//Serial.println(timebasis);
Serial.println(freqUp);
Serial.println(arraypoint);
}
if (!freqUp) {
arraypoint--;
if (arraypoint<0) {
arraypoint=0;
freqUp = true;
}
Serial.println(freqUp);
Serial.println(arraypoint);
}
digitalWrite(dacClsbPin,arraypoint&B0001); //this is bitwise ANDing, since arraypoint was chosen to be 0 to 15
digitalWrite(dacC1Pin,arraypoint&B0010); // it is quite easy to go from arraypoint to binary bits
digitalWrite(dacC2Pin,arraypoint&B0100);
digitalWrite(dacCmsbPin,arraypoint&B1000);
}
} //end of loop function
/* DEBUG NOTES for int_gen_test4 DAC digital inputs look OK when they aren't connected to interrupt board. But when I connnect the 16-pin IDC connector,
* levels are wrong. High might be OK but low is about 3V. With no connection to interrupt board, but 6.2k to ground, levels on dig pin 46 are 0 and .727V. Why is the
* drive so weak? Well, look at Setup, I called those DAC pins Inputs! Change to outputs. Put interrupt back in.
* Found the IF inequality at line 87 to be backwards, correct one is <millis().
* Now have some DAC control of speed but something is still wrong. Well, I had timea[arraypoint]&B0010 instead
* of arraypoint&B0010. Correct that, now it works as intended. July 22 2017 12:09PM I think the program is built
* back up to int_gen_test3. I need to try out the time-scaling, now. That will be int_gen_test5.
*
* It is admitedly confusing to bear in mind at the same time 1) this program's speed changing and 2) the interrupt
* oscillator's pulsing. The former is an overarching goal, and the latter is the step-to-step housekeeping.
*/
void cOscillatorIsr() { //this is the interrupt service routine, see Wikipedia "interrupt"
//this ISR is for interrupt oscillator C, the other two
//oscillators A and B are available for other steppers
digitalWrite(ledPin, HIGH); //turn on the LED to mark that the ISR is being worked, the brighter the LED
// the more the time being spent in the ISR
if (rotation && direc) { // && is logical AND
num++;
if (num > 4) {
num = 1; //if num goes higher than 4, it rolls back to 1, consistent with diagram above
}
}
if (rotation && !direc) { //if direction is false
num--;
if (num < 1) {
num = 4; //if num goes lower than 1, it rolls to 4
}
}
//Code above: if rotation is the logical value false or interruptCPin is LOW,
// it keeps num the same and stepper stands still.
//Now that num is known, translate it into the phase signals, in keeping with the diagram
if (num == 1) {
digitalWrite(phAPin,digitalRead(xorCPin)^LOW);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^LOW);
}
if (num == 2) {
digitalWrite(phAPin,digitalRead(xorCPin)^LOW); // ^ is bitwise exclusive OR (XOR), read it in from
// the input pin, it lets the user reverse the direction of the motor if
// the direction is wrong. The 6 XOR bits for 3 steppers come from the
// DIP switch on the custom shield that sits on pins 36 to 55 of Due or Mega 2560.
digitalWrite(phBPin,digitalRead(xorOtherCPin)^HIGH);
}
if (num == 3) {
digitalWrite(phAPin,digitalRead(xorCPin)^HIGH);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^HIGH);
}
if (num == 4) {
digitalWrite(phAPin,digitalRead(xorCPin)^HIGH);
digitalWrite(phBPin,digitalRead(xorOtherCPin)^LOW);
}
digitalWrite(hndshkCPin,HIGH); //oscillator ckt requires this handshake from Arduino
digitalWrite(hndshkCPin,LOW); // return the handshake wire to a low state
digitalWrite(ledPin, LOW);
} //end of ISR
/* John Engelbrecht South Austin johnenge@earthlink.net
Sept 28 2017 stepper_test11_rateupdown.ino 11 goes beyond 10 by adding servomotor for golf ball drop.
Using Mega 2560, there is so much processing to get the "dd" if's done at lines 72 and 79 that once ddlo is reached,
the motor seems to speed up suddenly. This especially shows up when ddlo is <10000us or Serial.print is being done.
The application is my mechanism to accept a golf ball, rotate to any of a number of exit paths, and release the ball
using a servomotor. There is an annotated photo of the mechanism on GETTER computer.
This sketch is using delay() to show
bidirectional motion with rate ramping up and down, as demo for TOBC Tech & Truth and One Day Academy.
Break through the 1ms delay by using delayMicroseconds() but if >16000 use divide by
1000 and use delay() since delayMicroseconds() doesn't like the delay parameter being >16383.
00 01
10 1 2
4 <--CCW 3 11 This depicts a 12-step stepper motor seen
11 3 CW-> looking into the shaft.
01 2 4 10 The numbers 1-4 denote shorthand for the
two-bit phase states.
1 1 00 (Note that the binary numbers are not a binary
00 2 countup. They are gray code for the two phases,
4 3 01 which we can call AB.)
10 11 If the motor is idle at location 3, and you change
phase B from 1 to 0, the motor turns clockwise to
location 4. On the other hand, from location 3,
if you change phase A from 1 to 0, the motor turns
counter-clockwise to location 2. */
/* The Motor System
12 volt power supply
| |
| 1.5 amp stepper driver circuit with 7.5 volt regulator for Arduinos
| |
| |
Arduino <-------------------------------------
*/
#include <Servo.h>
Servo myservo; // create servo object to control a servo
//Aliases for the Arduino pin numbers, it is best to not put pin numbers in the Setup and Loop functions
const int ledPin = 13; // the number of the LED pin
const int phAPin = 10; // phases are the two phases of the motor; "phase" translates to "coil"
//in the motor
const int phBPin = 9;
//begin other constants
const long ddhi = 50000; //the max usec delay to use in delayMicroseconds(), try 50000us
const long ddlo = 13000; // the minimum delay in usec, try 13000us
const long dddif = ddhi-ddlo; // difference, to be used at ramp-up or -down time
const long ddincrement = 1000; // how much the delay can change at a time, in microseconds, like 1000us
const boolean rotation = true;
int delayVal = 600 ; //for the servo action, in milliseconds
const int higherang = 170; //CCW try 170 degrees
const int lowerang = 50; //CW try 50 degrees
//begin global variables
boolean direc = true;
int countmax = 185; //since the motor is a 200-step motor, this is almost a full circle
long dd = ddhi; //dd is a value for delayMicroseconds(), start at high delay=slow rate
long cc = 0; //cc is a step counter
int num=1; //start at this location, num is 1 to 4 as in above diagram
void setup() {
pinMode(ledPin,OUTPUT);
pinMode(phAPin,OUTPUT);
pinMode(phBPin,OUTPUT);
Serial.begin(9600);
myservo.attach(11); //the servo signal pin
}
void loop() {
cc++; //whether CW or CCW rotation, use cc for a step counter. It is initiated as 0 before setup().
if ((dd>ddlo) && (countmax-cc)>(int(dddif/ddincrement))) {
dd-=ddincrement; //when step rate is low and you aren't nearing the end of motion, gradually reduce the delay
if (dd < ddlo) dd = ddlo;
digitalWrite(ledPin,HIGH);
//Serial.print("decreasing delay ");
//Serial.print(dd);Serial.print(" ");Serial.print(countmax-cc);Serial.print(" ");Serial.println(cc);
}
if ((countmax-cc)<(int(dddif/ddincrement))) {
dd+=ddincrement; //when cc approaches the max count, gradually increase the delay
if (dd > ddhi) dd = ddhi;
//Serial.print("increasing delay ");
//Serial.print(dd);Serial.print(" ");Serial.println(cc);
}
if (dd>16000) {
delay(int(dd/1000.0));
}
else delayMicroseconds(dd); // this is the delay between stepper steps, to get gradual acceleration and deceleration
if (rotation && direc ) { // && is bitwise AND
num++;
if (num > 4) {
num = 1; //if num goes higher than 4, it rolls back to 1, consistent with diagram above
}
}
if (rotation && !direc ) { //if direction is false
num--;
if (num < 1) {
num = 4; //if num goes lower than 1, it rolls to 4
}
}
//code above: if rotation is the logical value false,
// it keeps num the same and stepper stands still.
//Now that num is known, translate it into the phase signals, in keeping with the diagram above
//begin the long routine to move the motor, this could be shortened a lot by writing a function
if (num == 1) {
digitalWrite(phAPin,LOW);
digitalWrite(phBPin,LOW);
}
if (num == 2) {
digitalWrite(phAPin,LOW);
digitalWrite(phBPin,HIGH);
}
if (num == 3) {
digitalWrite(phAPin,HIGH);
digitalWrite(phBPin,HIGH);
}
if (num == 4) {
digitalWrite(phAPin,HIGH);
digitalWrite(phBPin,LOW);
}
//end of the routine to move the motor
if (cc>=countmax) {
direc = !direc; //if the step counter reaches countmax, go in opposite direction
cc = 0; //reset the step counter
dd = ddhi; //start with max delay, which means slowest step rate
if (direc) {
myservo.write(lowerang); // release the golf ball
delay(delayVal);
myservo.write(higherang); // raise the ball stop on the mechanism
}
delay(delayVal);
}
} //end of loop function
This is the first state machine I have done in Arduino. April 2018. The project is the oscilloscope static-immune probes that protect my new digital oscilloscope, Tektronix DPO 2002B. This state machine monitors the raw power for the probe system, which is -48V. The machine generates the on-off signal for the DC-DC converter that generates +24V. See a neat chart of what this state machine does, project #32 on web page https://sites.google.com/site/solderandcircuits/home/more-circuit-design/project-blog-1
State machines that are done haphazardly are a nightmare to debug. I was careful for this one and it went very well.
p24V = .02655*avgFrom4Inputs(analogRead(mon24VPin),analogRead(mon24VPin),analogRead(mon24VPin),analogRead(mon24VPin));
m48V = -58.68+.06225*avgFrom4Inputs(analogRead(monNeg48VPin),analogRead(monNeg48VPin),analogRead(monNeg48VPin),analogRead(monNeg48VPin));
//above are plus 24V and minus 48V being reconstructed from analogReads, which are restricted to 0 to 5V. Four reads are done, and averaging by function avgFrom4Inputs() does some averaging.
widetolerance=digitalRead(widetolerancePin);
if (widetolerance) {
if ((p24V>24*.93) && (p24V<24*1.1)) p24good = true;
else p24good = false;
}
else {
if ((p24V>24*.96) && (p24V<24*1.08)) p24good = true;
else p24good = false;
}
if (widetolerance) {
if ((m48V>-48*1.1) && (m48V<-48*.93)) m48good = true;
else m48good = false;
}
else {
if ((m48V>-48*1.08) && (m48V<-48*.96)) m48good = true;
else m48good = false;
}
goto rawStateMachine;
rawStateMachine: //a pity, you have to put goto statements within the function that has the goto label; , not after other functions
// Internet has many examples of state machine using switch case
switch(pstate) {
case 0:
if (printpstate) printpstateroutine();
pstate=1;enable24V=false;p24fault=false;m48fault=false;break;
case 1:
if (printpstate) printpstateroutine();
if (m48good) pstate=2;
enable24V=false;break;
case 2:
if (printpstate) printpstateroutine();
if (m48good) pstate=3;
if (!m48good) pstate=1;
pstateTimeStamp=millis();enable24V=false;break;
case 3:
if (printpstate) printpstateroutine();
if (!m48good) pstate=1;
//if (m48good && (millis()<300+pstateTimeStamp)) pstate=3; it goes without saying ; to stay in the same state, just refrain from changing the state number
if (m48good && (millis()>300+pstateTimeStamp)) pstate=4;
break;
case 4:
if (printpstate) printpstateroutine();
enable24V = true; m48fault = false;pstateTimeStamp=millis();
if (!m48good) pstate=1;
if (m48good) pstate=5;
break;
case 5:
if (printpstate) printpstateroutine();
if (!m48good) pstate=1;
//if (m48good && (millis()<100+pstateTimeStamp)) pstate=5; it goes without saying
if(m48good && p24good && (millis()>100+pstateTimeStamp)) pstate = 6;
if(m48good && !p24good && (millis()>100+pstateTimeStamp)) pstate = 22;
break;
case 6: //both supplies are good, things are stable, this is the steady state
if (printpstate) printpstateroutine();
p24fault=false;pstateTimeStamp=millis(); //do the timestamp over and over in case there is a transient on either supply
if (!m48good || !p24good ) pstate=7;
break;
case 7:
if (printpstate) printpstateroutine();
if (m48good && p24good ) pstate=6;
if (!m48good && (millis()>50+pstateTimeStamp)) pstate=21;
if (!p24good && (millis()>50+pstateTimeStamp)) pstate=22;
break;
case 21: //it doesn't matter that states 8 through 20 are skipped, it just matters that the state names are integers. The state names could be negative integers, even.
if (printpstate) printpstateroutine();
if (!p24good) p24fault=true;
m48fault=true;enable24V=false;pstate=1;break;
case 22:
if (printpstate) printpstateroutine();
if (!m48good) m48fault=true;
p24fault=true;enable24V=false;pstate=2;break;
default: break;
}
digitalWrite(enable24VPin,enable24V);
goto afterrawStateMachine;
Concerning the Arduino listing and this state diagram, they both say the same thing. The former is code, and the latter is visually oriented and much easier to understand. Engineering in college is partly about the logical thinking that lets a person do this type of design. I did well in college because I had done some of this sort of thing in high school, and when the college professors showed us how to do logic, I was all ears and had a background that gave me a lot of interest. On the other hand, English courses didn't interest me so much and I had trouble with that. Each person who goes to college brings different interests.