Leo, you are a clever and a very kind person.
You did post your complete code and I did ask for help on that code, only you answered.
For this project I could use the 24BYj's as bipolar (saving pins) and a couple of Mega's but the whole idea was to use your board, neat clean, and no drivers. IMHO chopping up your original code is a messy mistake.
That's why in #7 I asked you to please just show me the code to fill all the shift registers with the correct bits to move all steppers together to a certain position. No acceleration, or deceleration... etc.
Regards.
Can't do that.
No acceleration/deceleration almost certainly means missing steps.
But don't worry about that. The goPos() function takes care of that in the background.
As said, you just need to fill the newPos array with new positions, nothing else.
Or, what I think you want to do,
calculate the newPos array from a stored EEPROM positions array and some new move.
We can't advise you anything until you post your best code attempt.
Leo..
Ok here's my code, please see the commented out part "where I'm lost".
Thanks again.
/*
Nano | TPIC6B595
5V | 2 (VCC)
GND | GND
D8 | 8 (SRCLR)
D10 | 12 (RCK)
D11 | 3 (SER IN) of the first chip
D13 | 13 (SRCK)
| 9 to GND
| 10, 11,19 to GND
| 18 (SER OUT) to 3 of the next chip
*/
#include <SPI.h>
#include <EEPROM.h>
const byte motors = 41; // sets of four motors
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
int CurrPos; // current position
int NewPos; // new position to go to
int RandNum; // random number
int steps; // will hold number of steps to reach a target position
int sensor = 2; // the pin that the sensor is atteched to
int state = LOW; // by default, no motion detected
int val = 0; // variable to store the sensor status (value)
int TargetPos[] = {550, 1900, 3080, 2048, 5000, 5500, 6028, 7500}; // hypotetical positions, just for testing
void setup() {
SPI.begin();
pinMode(clrPin, OUTPUT);
pinMode(latchPin, OUTPUT);
digitalWrite(clrPin, HIGH); // clear all shift register data
EEPROM.get( 0, CurrPos ); // read tha last known position of the steppers
}
void loop() {
val = digitalRead(sensor); // read sensor value
if (val == HIGH) { // check if the sensor is HIGH
Go_To_Target_Pos();
}
}
void Go_To_Target_Pos() {
randomSeed(analogRead(0)); // generate a random number
RandNum = random(0, 7); // a numbers equal to numer of positions in array TargetPos
delay(50);
NewPos = (TargetPos[RandNum]);
steps = (NewPos - CurrPos); // number of steps needed to go from current position to new position
if (steps < 0) {
steps = steps * - 1; // go backwards
}
/* **********************************************
>>>>>> Here's where I'm lost <<<<<<<<<<
without shift registers I'd write direction pin HIGH/LOW to go forward
or backward, then :
for (int x = 0; x < steps + 1; x++) {
digitalWrite(step_pin, HIGH);
delay(10);
digitalWrite(step_pin, LOW);
}
>>>>> Using shift registers what is the procedure to send the correct bits
to all shift registers to meve all steppers to the NewPos ?
***********************************
*/
EEPROM.update(0, NewPos);
state = LOW; // update variable state to LOW
}
I don't know where to start. Your code is lacking the step sequence and SPI writes.
Again, stick with the goPos() function I wrote (see it as a library).
I also don't see you write random/new positions to all 41 motors.
Writing to EEPROM every loop is also not possible.
It's slow (affecting motor movements), and you will wear out the chip very fast.
Maybe you should look at battery power for the setup (no EEPROM writes in loop).
Current draw is very low (only the Arduino) after the motors have reached their destination,
because the goPos() function also sleeps the motors.
You could still write to EEPROM for a controlled shut down, with a button.
Please explain exactly what you want to do with the motors.
I can then try to advise you how to tackle the problem.
Leo.
That's where I'm lost!
I started by using a micro SD module but was told I could write to EEPROM 100,000 times before wearing it out.
I read the last known position of the steppers (it's the same for all motors, so just one integer) from EEPROM or SD card. I want to move all the steppers, at the same time, from their current position to a new position. If the new position > current position then turn forward else turn backwards.
I hope it's clear now.
The motors take one step each loop (~2ms), so if you move the motors one turn and write to EEPROM each loop, then you have already used 2000 writes.
So the chip could be failing after 50 motor moves. Same problem with an SD card.
If you want to write to EEPROM, you should do that once, after the move is completed.
For example in a next switch statement.
I would forget the EEPROM for now, and try to code the motor movements first.
If that works, then you can think of writing to EEPROM.
The goPos() function also takes care of that. Just enter a new position.
That new position can also be negative (forwards or backwards from the start position).
The goPos() function moves all motors at the same time once new positions are programmed.
I assume you want different (and random) new positions for all motors.
I will try to post some code when I have time.
Leo..
I programmed two cases here.
One that moves all motors to a different random position,
and one that moves them to a common random position.
Compiles, but not hardware tested.
Read all the comments.
Leo..
/*
Nano | TPIC6B595
5V | 2 (VCC)
GND | GND
D8 | 8 (SRCLR)
D10 | 12 (RCK)
D11 | 3 (SER IN) of the first chip
D13 | 13 (SRCK)
| 9 to GND
| 10, 11,19 to GND
| 18 (SER OUT) to 3 of the next chip
*/
#include <SPI.h>
const byte motors = 48; // sets of four motors
// must be sets of four motors for SPI.transfer16, so 44 for 41 motors !
// but why not keep it simply the board number of 48
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
const byte rampSteps = 7; // accel/decel steps
byte rampDelay[motors]; // accel/decel delay between steps
byte lag[motors]; // speed reduction
int velocity[motors]; // signed, for direction
int nowPos[motors], newPos[motors]; // int is 16 rotations max (2.5m string with a 50mm dia wheel)
unsigned long prevMillis, prevMicros, interval; // timing
byte val[4]; // TPIC write bytes
bool torque; // reduced during homing
byte sequence, index; // patterns, motor index
int randNumber;
void setup() {
SPI.begin();
pinMode(clrPin, OUTPUT);
pinMode(latchPin, OUTPUT);
digitalWrite(clrPin, HIGH); // clear all shift register data
randomSeed(analogRead(0));
// load 41 (or 48) eeprom nowPos[] integers (= two bytes) here
}
void loop() {
switch (sequence) {
case 0: // load random positions for each motor into the new position array
for (byte i = 0; i < motors; i++) {
newPos[i] = random(-500, 500); // about a quarter turn max, right or left from start position
}
// eepromSave(); // could call eeprom write function here
sequence = 1; // go to next case
prevMillis = millis(); // but first mark the time
break;
case 1: // move all motors the same random direction/steps
if (millis() - prevMillis > 8000) { // after 8 seconds
randNumber = random(-1024, 1024); // max half a turn
for (byte i = 0; i < motors; i++) {
newPos[i] = randNumber; // all motors the same (random) new position
}
// eepromSave(); // could call eeprom write function here
sequence = 2; // next case
prevMillis = millis();
}
break;
case 2: // run sequence(s) again
if (millis() - prevMillis > 20000) { // after 20 sec
sequence = 1;
prevMillis = millis(); // mark
}
break;
}
goPos(); // let's step, one step each loop
}
void goPos() {
while (micros() - prevMicros < 1953); // step interval, motor RPM limitation, 1/(15rpm/60sec*2048steps)= ~1953ms, wait if arrived here too early
prevMicros = micros(); // update, for next interval
for (int i = 0; i < motors; i++) {
if (rampDelay[i]) rampDelay[i]--; // subtract one, do nothing
else { // step
if (newPos[i] > nowPos[i]) { // request forwards
if (velocity[i] >= 0) { // if standing still, or going forwards
nowPos[i]++; // one step forward
if (newPos[i] - nowPos[i] < rampSteps) velocity[i]--; // reduce speed if almost there
else if (velocity[i] < min(rampSteps, rampSteps - lag[i])) velocity[i]++; // increase speed if not there yet
rampDelay[i] = max(lag[i], rampSteps - velocity[i]); // insert ramp delay
} else { // if wrong direction
velocity[i]++; // reduce reverse speed
if (velocity[i] < 0) nowPos[i]--; // if still reverse, keep on going the wrong way
rampDelay[i] = rampSteps - abs(velocity[i]); // insert ramp delay
}
}
if (newPos[i] < nowPos[i]) { // request reverse
if (velocity[i] <= 0) { // if standing still, or going reverse
nowPos[i]--; // one step reverse
if (nowPos[i] - newPos[i] < rampSteps) velocity[i]++; // reduce speed if almost there
else if (abs(velocity[i]) < min(rampSteps, rampSteps - lag[i])) velocity[i]--; // increase speed if not there yet
rampDelay[i] = max(lag[i], rampSteps + velocity[i]); // insert ramp delay
} else { // wrong direction
velocity[i]--; // reduce forward speed
if (velocity[i] > 0) nowPos[i]++; // if still reverse, keep on going the wrong way
rampDelay[i] = rampSteps - velocity[i]; // insert ramp delay
}
}
}
if (newPos[i] == nowPos[i])val[i & 3] = 0; // cut coil power when there
else {
if (torque) { // this block for full torque
switch (nowPos[i] & 3) { // the two LSB translated to motor coils (full step)
case 0: val[i & 3] = B00000011; break; // two coils at the time
case 1: val[i & 3] = B00000110; break;
case 2: val[i & 3] = B00001100; break;
case 3: val[i & 3] = B00001001; break;
}
} else { // this block for low power
switch (nowPos[i] & 3) {
case 0: val[i & 3] = B00000001; break; // one coil at the time
case 1: val[i & 3] = B00000010; break;
case 2: val[i & 3] = B00000100; break;
case 3: val[i & 3] = B00001000; break;
}
}
}
if ((i & 3) == 3) { // process set of four motors (faster)
unsigned int chain = val[0] << 12 | val[1] << 8 | val[2] << 4 | val[3]; // concat
SPI.transfer16(chain); // transfer 4-motor int
}
}
digitalWrite(latchPin, HIGH); // transfer to TPIC driver outputs
digitalWrite(latchPin, LOW); // latch time ~8us
}
Leo,
You are my Superman, thanks a million times.
Please forgive me for not explaining my project. I took it for granted that you knew what I was doing from another tread I posted. Please take a look (scroll down) at this art work in the following video.
I made a replica of this with 3D printed gears instead of laser cut wooden gears used by the artist and I wanted to motorize it. I couldn't find anyone to design and laser cut the gears and the 3D printing approach failed. The gear passes were too large, no smooth movement and there were alignment problems. So after multiple attempts and throwing away hundreds of 3D printed gears I had an idea. Since the patterns are made only by the white part of the rotating gears why not just 3D print half circles (no gears) in white with a shaft and attached them to a stepper. Now my problem was controlling 41 steppers and that's where I thought of using one of your boards. Please note that in the video, when the gears are turned, you get an interesting pattern only at certain positions. That's why I thought of an array of prefixed positions. And to make things more interesting I'd throw in a PIR sensor to sense someone in front of this hanging on the wall, then a random index number which will point to one of these prefixed positions. Please forgive my long thread but I owed it to you and I hope it clears up some doubts.
Back to your code, it works fine. I made a couple of changes which need your approval.
first I commented out tis part:
void loop() {
switch (sequence) {
case 0: // load random positions for each motor into the new position array
// for (byte i = 0; i < motors; i++) {
// newPos[i] = random(-500, 500); // about a quarter turn max, right or left from start position
// }
// eepromSave(); // could call eeprom write function here
sequence = 1; // go to next case
prevMillis = millis(); // but first mark the time
break;
Since I need all steppers to go to the same random position. works fine but is it ok ?
then I defined an array of prefixed TargetPositions[500, 850, 1020, etc.] and changed your code to generate a random number from 0 to n (number of the values in TargetPos. This random number points to / picks one of those positions as the new position from the array.
case 1: // move all motors the same random direction/steps
if (millis() - prevMillis > 8000) { // after 8 seconds
// randNumber = random(-1024, 1024); // max half a turn
randNumber = random(0, 7); // to be decided , just an example
for (byte i = 0; i < motors; i++) {
// newPos[i] = randNumber; // all motors the same (random) new position
newPos[i] = TargetPos[randNumber]; // all motors the same (random) new position
}
// eepromSave(); // could call eeprom write function here
sequence = 2; // next case
prevMillis = millis();
}
break;
and this also works fine.
If you agree with these changes, the next step is to check to see why I have a pretty long delay after the motors have reached their new position ? Any idea ? The ideal scenario will be for the motors to stay in their new position until the PIR sensor is triggered again. Logically the PIR sensor HIGH/LOW must be tested after the motors have reached the new position and not while they are moving.
I agree with you and was my intention to write the position to EEPROM not during the stepping loop but only after the motors are at their destination.
Maybe even better is to check if the PIR sensor is not triggered within a certain time (no one is if front of the panel) then write the last position to EEPROM , cut current to the motors and maybe even put Arduino in the sleep mode.
thank you.
The default mode for the steppers is "single coil drive", which is low (half) power (current).
unless the "torque" variable is set to "true" (dual coil drive),
which was done in case 0 of the original sketch (lifting heavy objects on a string).
The motors are not stressed in your project, so they can be left in low power mode.
As said before, the motors are individually switched off (zero current) when they have reached their destination, all done by the goPos() function.
if (newPos[i] == nowPos[i])val[i & 3] = 0; // cut coil power when there
I think I understand your requirement, and will try to upload some coding ideas for that tomorrow.
Leo..
About saving to eeprom.
I think you should include a setup button to your project,
so you can ignore the saved eeprom values during homing/resetting/zeroing of the motors.
Here are some (untested) ideas for "ignore/load" that you can add to setup().
And a function that you can use to write to eeprom.
Leo..
#include <EEPROM.h>
int eeAddress = 0; // address offset
const byte zeroPin = 6; // button between pin and ground
void setup() {
pinMode(zeroPin, INPUT_PULLUP);
if (digitalRead(zeroPin)) { // if not pressed
for (int i = 0; i < motors; i++) {
EEPROM.get(eeAddress, nowPos[i]);
eeAddress += 2; // int is 2 bytes
}
}
}
void eeSave() {
for (int i = 0; i < motors; i++) {
EEPROM.put(eeAddress, nowPos[i]);
eeAddress += 2;
}
}
I fail to see the use of a button. Let me explain what I have in mind.
I intend to just write 100 on EEPROM (without uploading your sketch) which will be my starting point and will be included in TargetPos[] as a position. Then before the very first run, I'll align all the half circles manually in the same position, let's say facing upwards. Then load your code and start the "show". A button will surely be helpful in case one or any of the steppers misstepped after a while and if I had 41 end stop/sensors , one on each motor. Otherwise, in case of a misstep pushing a button will not re align the motors in the 0 position.
I'm surely putting a lot of faith in cheap 24BYJ's because if any of them missteps the whole "show" will fail. I've tried only 10 motors, ordered the others, must finish 3D printing the half circles, get a panel laser cut with holes, mount everything, do the wiring with correct power supply. Only then I'll know if I face misstepping problem and waisted my time --- and yours (sigh !).
Changing EEPROM address is an excellent idea, thanks. Guess I'll have to put in an if statement to overcome the overflow ?
if (address == EEPROM.length()) { address = 0;}
Regards.
The button code is transparent, so no harm done if you include the code and don't install or press the button. I thought it would be handy to ignore any stored values if you need to re-align.
I have used my 48-stepper setup for hours at the time, and did not notice any missed steps. Even if I did slam the motors in reverse without stopping them first (goPos() should also take care of that).
PIR pause code could be inserted at the start of a case.
Leave both pots on the PIR sensor in the middle, so 'on' time is about 2-3minutes.
case 1: // move all motors the same random direction/steps
if (digitalRead(pirPin) && millis() - prevMillis > 8000) { // after 8 seconds
randNumber = random(-1024, 1024); // max half a turn
for (byte i = 0; i < motors; i++) {
newPos[i] = randNumber; // all motors the same (random) new position
}
// eepromSave(); // could call eeprom write function here
sequence = 2; // next case
prevMillis = millis();
}
I have the feeling you want the motors to behave as the mechanically linked "patturn".
Individually programmed motors can do so much more than that.
Have fun, and not too many headaches.
Leo..
Thanks again Leo, I hope to post a video of the finished project soon with credits to you.
Hello,
I finished the panel and here's the video:
Apart from a couple of motors misbehaving (I read somewhere that the 28BYJ's could misstep if ran backwards ?!? Don't know if that's my issue) everything else is working.
As you notice in the video the code is running in a loop picking random positions but not the positions I want. At first run, I need to move the motors from zero position to x position and if that position makes a nice pattern then I'll write it down and will then insert it in my TargetPos[] for the future. i.e. I have to fill my TargetPos[] with the only positions that make a nice pattern. I'm loading 3 foto's as examples. But I can't do what I said if the motors are running in auto in a loop and could not figure out how to move them just to one position and stop.
Also, it would be nice if I could do without millies. Just read a PIR sensor and if activated move to one of TargetPos[] and stop there util the PIR sensor is triggered again.
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.


