Ah. I was still thinking in terms of having a UPS or battery backup.
That's ok. I think the decision of what to use hasn't been made yet.
Leo..
Thanks to both of you. An UPS set up would give us more room ?
You guys decide, I follow.
Spent some time searching and found this
it says " No need for complex power management circuitry ... "
What do you think ?
Is this what you had in mind? I've been meaning to test out this method and have built the zero-crossing detector circuit using 2 * 100K (1/4W) resistors and a K814P optocoupler. There's an optional diode and capacitor to block any external load from draining the capacitor on power loss, which might buy the Arduino more time. Maybe you could just rely on the charge in the PSU's filter capacitors for the EEPROM backup.
edit: changed 5W to 1/4W
0.5W were sufficient.
That would not be helpful. However, it occurs to me that we may be overcomplicating this.
To summarize:
If power is turned off right now, the entire system stops and you lose position information. A UPS with power failure indication will give you time to stop the system and save motor position so you can restart properly when power is restored. Others have suggested variations on this basic concept.
However:
Realistically you probably only need to back up the arduino. When the motors lose power, they will of course stop moving. But if the arduino itself still has power, it will continue to send motion pulses as before. What this means is that when power is restored, the motors will continue on as if they had been running the entire time. Due to the nature of stepper motors, they may be a step or 4 out of sync, but as long as your system can absorb some motors not being perfectly step-for-step synchronized, it should be fine.
tl;dr: just backup the Arduino with a cheap UPS or even just a battery and you should be fine.
I thought of that too, but worked out that you need at least 10,000uF to write 60 motor positions to the Nano's internal Eeprom (400ms).
Curious if that opto circuit will work with 2*100k, and at what point you don't get false triggers anymore. With those high values it's not an ideal/narrow zero-crossing detector, but we don't need that. Will test next week myself, when I get the optos.
Leo..
how many bytes does all this data to be saved take? I can try run a few different tests. I have an I2C FRAM (mb85rc256v), which I believe is faster than EEPROM.
nowPos[] is an int array, so two bytes per motor position.
Leo..
I've run a test by plugging the zero-crossing detector in a separate outlet to my Arduino(ATmega328) and PC. The detector seems to work well so far in detecting the outage in 2.5 half-cycles and then the sketch immediately back up the data.
Using the I2C FRAM (MB85RC256V) I got around 3 millis to write the 60 stepper positions (120 bytes). I used a sketch found here to increase the speed, which doesn't use the wire library: Fast data logging using FRAM - #25 by RedIron.
I then tried adding a CRC-32 into the sketch, i.e. I calculated a CRC when the outage detection occurred and then added it to the end of the data, then saved. The CRC calculation added 1.2 millis to the total save time.
I hope this assists ![]()
EDIT: got high and low mixed up when detecting zero-crossing pulses. fixed now.
int pulseInputPin = 2;
const int numberOfPostions = 60;
struct STRUCT {
int stepperPositions[numberOfPostions];
unsigned long CRC;
} dataToSave;
int memaddr = 0; //the starting address of the FRAM we are writing to
unsigned long previousMillis = 0;
bool flag = false;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("<arduino ready>");
TWBR = 12;
pinMode(pulseInputPin, INPUT_PULLUP);
for (int i = 0; i <= numberOfPostions; i++) {
dataToSave.stepperPositions[i] = i;
}
}
void loop() {
if (digitalRead(pulseInputPin) == HIGH) { //zero-crossing detected. restart timer
if (flag == false) {
previousMillis = millis();
flag = true;
}
} else { //LOW
flag = false;
}
if (millis() - previousMillis >= 25) { //no zero-crossings for 2.5 half cycles @ 240VAC = power failure.
// back up variable's
writeToFRAM();
//read back
readFromFRAM();
while (1)
;
}
}
void writeToFRAM() {
unsigned long startTimer = micros();
//calculate CRC32
dataToSave.CRC = CRC_32(dataToSave);
//write to FRAM
i2c_write(0xA0, 789, (uint8_t *)&dataToSave, sizeof(dataToSave));
unsigned long endTimer = micros();
Serial.print("time to write = ");
Serial.print(endTimer - startTimer);
Serial.println(" micros");
}
void readFromFRAM() {
struct STRUCT dataToRead;
i2c_reads(0xA0, 789, (uint8_t *)&dataToRead, sizeof(dataToRead));
if (dataToRead.CRC == CRC_32(dataToRead)) {
Serial.println("CRC passed!");
//Serial.println(dataToRead.CRC, HEX);
for (int i = 0; i < numberOfPostions; i++) {
Serial.print("stepper #");
Serial.print(i);
Serial.print(" = ");
Serial.println(dataToRead.stepperPositions[i]);
}
} else {
Serial.println("CRC failed!");
}
}
int i2c_write(uint8_t slave, uint16_t fram_address, uint8_t *data, uint16_t length) {
uint8_t tmp = 0;
int rtn = 0;
// get FRAM memory address to start writing to
uint8_t MSB = (uint8_t)((fram_address & 0xFF00) >> 8);
uint8_t LSB = (uint8_t)((fram_address & 0x00FF) >> 0);
// send START condition
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while ((TWSR & (0x08)) == 0)
;
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// set MASTER WRITE ADDRESS
TWDR = slave;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0)
;
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// get status
tmp = TWSR;
if (tmp == 0x18) // if a SLAVE responded
{
/**********************
Address byte MSB
**********************/
TWDR = MSB;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0)
;
tmp = TWSR;
if (tmp == 0x28) // ACKed
{
rtn++;
} else if (tmp == 0x30) // NACKed
{
rtn = -1;
}
/**********************
Address byte LSB
**********************/
TWDR = LSB;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0)
;
tmp = TWSR;
if (tmp == 0x28) // ACKed
{
rtn++;
} else if (tmp == 0x30) // NACKed
{
rtn = -1;
}
// proceed to write bytes to SLAVE
for (uint16_t count = 0; count < length; count++) {
TWDR = data[count];
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0)
;
tmp = TWSR;
if (tmp == 0x28) { // ACKed
rtn++;
} else if (tmp == 0x30) { // NACKed
rtn = -1;
break;
}
}
// send STOP
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
} else if (tmp == 0x20) // no SLAVE response or bus error
{
// send STOP
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
rtn = -1;
} else {
rtn = tmp;
}
return rtn;
}
int i2c_reads(uint8_t slave, uint16_t fram_address, uint8_t *data, uint16_t length) {
uint8_t tmp = 0;
int rtn = 0;
// first send address to read from
i2c_write(slave, fram_address, &tmp, 0);
// maybe not needed
for (uint8_t count = 0; count < 64; count++) {
asm("NOP");
}
// send START condition
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while ((TWSR & (0x08)) == 0)
;
// maybe not needed
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// set MASTER READ ADDRESS
TWDR = slave | 0x01;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0)
;
// maybe not needed
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// get status
tmp = TWSR;
// SLAVE responded
if (tmp == 0x40) {
for (uint16_t count = 0; count < (length - 1); count++) {
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
while ((TWCR & 0x80) == 0)
;
data[count] = TWDR;
}
// last byte
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0)
;
data[length - 1] = TWDR;
// send STOP
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
} else if (tmp == 0x48) { // bus error
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}
return rtn;
}
template<typename T> unsigned long CRC_32(const T &value) {
const unsigned long crc_table[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
unsigned long crc = ~0L;
for (int index = 0; index < sizeof(value) - sizeof(uint32_t); ++index) { //crc from every byte except, minus the crc.
crc = crc_table[(crc ^ ((uint8_t *)&value)[index]) & 0x0f] ^ (crc >> 4);
crc = crc_table[(crc ^ (((uint8_t *)&value)[index] >> 4)) & 0x0f] ^ (crc >> 4);
crc = ~crc;
}
return crc;
}
Awesome. So now that we know that the data can be backed up in time, the only thing left is to rewrite OP's program so it can detect that it reset due to power failure and reload the last motor positions.
I'm waiting for delivery of the board suggested by cedarlakeinstruments
I couldn't find any instructions on how to use it but I'll see when I get it.
@GigaNerdTheReckoning thanks for chipping in and testing.
But I'm still in doubt about the second part of my original question. i.e.
once we chose how to detect power failure, are we able to save precise positions of the steppers ? Do we poll a flag after each step ?
The beauty of the whole setup (BMW Kinetic sculpture and or my project) is based on precision.
" So now that we know that the data can be backed up in time, the only thing left is to rewrite OP's program so it can detect that it reset due to power failure and reload the last motor positions."
... I'm hoping Leo will give us a hand there. His program, if I recall, writes 4 motors at a time. I wouldn't know where to start.
No problem, looks like a promising method ![]()
Those detectors you linked are slower than the zero-crossing detector we mentioned, but they might still work.
You just connect AC mains to L and N. OUT is connected to an Arduino input pin, GND is connected to Arduino ground. You can enable the internal input pull-up resistor on the Arduino, or you put Arduino's 5V+ to the VCC pin of the opto module (I think just using the internal pullup will suffice though).
I Haven't used steppers before, so I'll let the others chip in.
Did you test what I suggested in post #27?
@cedarlakeinstruments no, sorry I have not. but in the meantime I've ordered the board I mentioned in post 24. I was impressed by the specs of the chip on that board.
From what I understood, it does everything I need. I've already installed it's library which has a save and read example and will post results as soon as I get the board.
Of course this board is an overkill for the BMW project where Leo's program "homes" steppers. In that case all we need is just a byte on EEPROM that flags if there was a power failure, instructing the Arduino to home all steppers before running other instructions.
But in the "mirrored tiles project", as I mentioned before, I can't have a presumed "home" position and can not risk banging the tiles against the holding frame.
I just got two 4N25 opto couplers, cuz my local supplier (Jaycar) didn't have AC optos.
Connected the LEDs back2back and the transistors in parallel. 2*100k 1/4watt inline with each power wire (230V), heatshrinked (can barely feel them getting warm). Internal pull up on the pin. So far I can reliably lower detection thereshold to 2ms (1/20th of a 50Hz mains cycle), using test code of post#15.
Leo..
I actually got things mixed up in post #31 when I was trying to replicate the stack exchange answer, as I had thought that each zero-crossing pulse produced a low signal, rather than a high signal, so I have fixed that. Now my sketch is properly detecting the outage when there is no rising edge (low to high) detected for 25ms.
However, using your method in post #15 is also working for me with the 2ms threshold. From how I understand it, when the power is on, the pulse width of each zero crossing is around 1ms, so Arduino detects a high signal for about 1ms under normal circumstances. If the power is lost, then the pulses become stretched to greater than 2ms.
thanks
Yes, the way this can work is that it detects a >2ms failure anywhere during the mains sine wave, not just missing zero crossings. Currently soak-testing the opto code at 2ms. No trips yet after 5 hours, but mains is very clean here.
My motor code needs an absolute max time of 14ms to stop (and back-track after overshoot) all motors. That means the setup can be safely stopped 16ms after mains failure.
I now wonder how long a 5volt/20A (or 30A) supply will hold under that motor load.
Only half a cycle is needed.
Writing to eeprom could be done after motor have stopped and position is known.
I don't see why you shouldn't use the eeprom that's already inside the ATmega328.
Arduino supply doesn't matter, because it can be streched with a few caps.
Leo..
Just for testing, I tried Wawa's code and a push button. If button is pressed, stop all motors and save their current positions to EEPROM.
When the button is released, read nowPos from EEPROM.
But I'm lost ! Specially if all or some motors are running when the button is pressed. Help Pls.
Here's the code:
/*
Nano | TPIC6B595
5V | 2 (VCC)
GND | GND
D8 | 8 (SRCLR) - blue
D10 | 12 (RCK) - green
D11 | 3 (SER IN) of the first chip - yellow
D13 | 13 (SRCK) - orange
| 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
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
void setup() {
SPI.begin();
pinMode(clrPin, OUTPUT);
pinMode(latchPin, OUTPUT);
digitalWrite(clrPin, HIGH); // clear all shift register data
for (byte i = 0; i < motors; i++) nowPos[i] = 1024; // homing all motors 1/2 turn
}
void loop() {
switch (sequence) {
case 0: // break free from the top, all motors
if (nowPos[0] == newPos[0]) { // finished homing
for (byte i = 0; i < motors; i++) nowPos[i] = -128; // 1/16 turn
torque = true; // was low power during homing
sequence = 1; // case 0 is not called anymore
prevMillis = millis(); // so mark here
}
break;
case 1: // 4.5 turns down, all motors
if (millis() - prevMillis > 8000) {
for (byte i = 0; i < motors; i++) newPos[i] = 9216; // 4.5 * 2048
sequence = 2;
}
break;
case 2: // all return to zero, one after the other
if (millis() - prevMillis > 30000 + interval) {
if (index < motors) {
newPos[index] = 0;
interval += 500;
index += 1;
} else { // when done
index = 0;
interval = 0;
sequence = 3;
}
}
break;
case 3: // run sequence(s) again
if (millis() - prevMillis > 75000) {
prevMillis = millis();
sequence = 1;
}
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
}
