Arduino EL-wire sequencer - help appreciated!

The aim is to make an eye mask with concentric rings of el-wire that flash in sequence to make a zoom-in/out effect. It should be in time with the music, but instead of relying on a microphone I want a button on the battery pack that I can tap 4 times on the beat to synchronise it. Another button can cycle through different patterns.

I have to finish by 22nd of July

I bought one of these from a 3rd party seller (it’s retired from the sparkfun site)

https://www.sparkfun.com/products/retired/12781

I have never done anything with Arduino, but I think I can wrap my head round it.
It has two programming connection options, FTDI or ICSP
I think I want to use FTDI, which sounds like something we’d have in the space already?

What do I need to put on my laptop to start messing with the code?
I presume you can simulate the basic in/out pin behaviour in some sort of IDE?

I have some 3.7V lipo drone batteries which should be perfect for powering it, and then I’ll need to make/convert a case I can slip in my back pocket.

1 Like

Is this info what you’re looking for? I’ve not touched an Arduino in well over a decade, but it sounds like what you’re after is the IDE. You can then use it to write your sketch, compile it, then upload it to your board. See this for more info.

I am not sure what capabilities the IDE has these days, but I’d be surprised if there isn’t some level of simulation.

Surprised at how not-rusty my programming was.
I had it working in a simulator when it was simple, but now it’s complicated and it doesn’t work :smiley:

Pasting here for safe keeping

// Button handling variables

int buttonstate;                 // the current reading from the input pin

int last_buttonstate = LOW;      // the previous reading from the input pin

int debounce_time = 100;         // don't expect two pushes any quicker than this

// Beat counting variables

unsigned long prev_time = 0;     // system time at last button push, in ms

unsigned long current_time;      // system time at this button push, in ms

const int beats_max = 512;       // just in case you push the button too many times

int beat_lengths[beats_max];     // array to store the beat lengths

int beat_length;                 // calculated average of beat lengths

int beat_index = 0;              // number of button presses

const int beat_timeout = 10000;  // time after which beat_index resets, in ms

// Animation variables

int anim_time = 0;                    // time since last frame displayed

const int step_count = 8;                               // how many steps per beat

int step_length = 50;                                   // how long a step lasts in ms

int channel_count = 8;                                  // maximum 8 connected wires

const byte frames[step_count] = {1,2,4,8,16,32,64,128}; // each byte is a frame. Each bit of each byte is a channel.

int current_frame = 0;                                  // index to animation array

unsigned long anim_start = 0;             // system time at start of animation

int frame_threshold = 0;                  // when it decreases, we advance to the next frame

int frame_prev_threshold = 0;

/*******************Setup Loop***************************/

void setup() {                

  // Initialize elwire outputs

  pinMode(2, OUTPUT);  // channel A index 0

  pinMode(3, OUTPUT);  // channel B index 1

  pinMode(4, OUTPUT);  // channel C index 2

  pinMode(5, OUTPUT);  // channel D index 3  

  pinMode(6, OUTPUT);  // channel E index 4  

  pinMode(7, OUTPUT);  // channel F index 5  

  pinMode(8, OUTPUT);  // channel G index 6  

  pinMode(9, OUTPUT);  // channel H index 7  

 // Initialise button inputs

  pinMode(A1, INPUT_PULLUP); // BPM button

// Pin 13 is the onboard status LED if we need it

  pinMode(13, OUTPUT);    

}

/*******************Main Loop***************************/

void loop() {

  // read the current time and calculate time since last good button reading

  current_time = millis();

  int elapsed_time = current_time - prev_time;

  // read the state of the switch

  int reading = digitalRead(A1);

  // if it's the first button push in a while, reset the indexes and timers

    if (reading = HIGH && last_buttonstate == LOW && elapsed_time > beat_timeout){

      beat_index = 0;

      current_frame = 0;

      anim_start = current_time;

      prev_time = current_time;

    }

  // if it's been long enough, but not too long, then record a new beat.

    if (reading = HIGH && last_buttonstate == LOW && elapsed_time < beat_timeout && elapsed_time > debounce_time) {

    // record this as the new previous time

    prev_time = current_time;

    // increment the beat index and if we reach the end, go back round, just in case

    ++beat_index;

    if (beat_index == beats_max) beat_index = 0;

    // set the current beat to the measured time

    beat_lengths[beat_index] = elapsed_time;

    // calculate the new average beat length and step length

    int running_total = 0;

      for (int i=0; i<=beat_index; i++) {

        running_total += beat_lengths[i];

        }

      beat_length = running_total / beat_index;

      step_length = beat_length / step_count;

      // and set the button state to HIGH so we don't trigger this again too soon

      last_buttonstate == HIGH;      

      }

    else {

      last_buttonstate == LOW;

    }

  // update animation timers and see if we've passed a frame threshold

   anim_time = current_time - anim_start;

   frame_threshold = anim_time % step_length;

  // If a step has passed, increment the frame and set the outputs

  if ( frame_threshold < frame_prev_threshold) {

    ++current_frame;

    if (current_frame > step_count) current_frame = 0;

      int pinout=0;

      int channel_state=LOW;

      for (int i=0; i<= channel_count; i++){

        channel_state = bitRead(frames[current_frame], i);

        pinout = i+2;

        digitalWrite(pinout,channel_state);

      }

    }

// save the reading. Next time through the loop, it'll be the lastButtonState:

  last_buttonstate = reading;

}

Got the code working in a simulator :slight_smile:

https://wokwi.com/projects/368408733219128321

All it took was some sleep to spot the off-by-ones and = instead of == errors :smiley:

The animation is stored as an array of bytes, with each bit in the byte representing one of the eight output channels. The animation plays once per beat, and can have any number of steps.

Button input is debounced

The first press of the button sets the animation to frame 0
Consequent presses on the beat will synch it to that BPM

After a longer time (currently set to 2 seconds) a single press will reset the animation back to frame 0, but the timing remains the same. Or you can keep pushing the button to set a new BPM

Now to implement pattern switching

1 Like

Ok, so why am I getting a compiler error here?

image

pattern_matrix is an 18x3 array of animation data (each row is 2 metadata bytes followed by 16 frames of data)
pattern_index is an INT
frames is a linear array of 18 bytes

should work, right?

all code here:

https://wokwi.com/projects/368408393927186433

Well done!

Two small things:

  1. assignment rather than comparison
if (pattern_index >= pattern_count) pattern_index == 0;

I think this should be pattern_index = 0;

  1. A slightly different approach to assigning valuess to the frames array.

When i replace

frames = pattern_matrix[pattern_index];

with

for (int i = 0; i < max_pattern_length; i++)
  {frames[i] = pattern_matrix[pattern_index][i];
}

it compiles fine.

(I’m an opera singer, not a software developer, please take this into account when applying criticism.)

1 Like

Just skimming through your code, I see on line 138 you probably want = instead of ==, and instead of shuffling array data around as frames, just reference to pattern_matrix directly?

Edit: Mark got there before me.

1 Like

Thanks for the tips :+1:
If it wasn’t clear already, I am not a programmer!

(Also, if I had a time machine, I would go back to the invention of C and tell them how many errors = Vs == would cause in the future)

I’d tell the person who insisted on curly braces to go and take a look at python…

:smiley: of course

I think the coding is done. Which I did not expect to say after only two days.
Now my animations can have arbitrary frame rates and durations, and a second button cycles through them.
(still at https://wokwi.com/projects/368408393927186433)

Currently, if you switch from a long pattern to a short one, it’s highly likely the starting frame will no longer be “on the one” of the music, requiring a resynch. If I hit the pattern button near the “one” it will barely be noticeable. I can live with this for now.

Time to try it on real hardware! So do we have an FTDI interface in the space, @electrotechs?
I had a rummage in the drawers and boxes but couldn’t see anything obvious…

I don’t think so, I might have some spares


here’s ch340g in top right drawer with usb micro

3 Likes

The physical design:

(the colours are just to keep track of which wire is which. the actual mask will be monochrome)

The figure-eight layout does several things:

  1. Each channel is approximately the same length of wire (225-255mm). With a simple concentric design, the inner and outer chanels would be wildly different lengths. This matters because longer wires are dimmer and I want consistency.
  2. All the connection points can be at the outside edges. A simple concentric design would need to run connecting wires into the center on each side, which would spoil the looks (and proabbly the comfort).
  3. It looks cool! When one eye zooms in, the other zooms out! Woah dude!

Hopefully, there won’t be any secondary structure to spoil the looks. The EL wire sheath is easily melted (which I know well from soldering the stuff!) so I will use an old soldering iron tip on a low temperature to weld the inside face of all the wires together.

16 wires is a lot to run up from the battery/control box though. Two ethernet cables’ worth! Will have to think how best to do that without being too cumbersome or fragile.

1 Like

FYI bear in mind the EL wires runs at ungodly voltage/frequency expect RF noise…

It’s not too bad really. 100V and 1-2kHz. I don’t think even the really low frequency radios they have on submarines go that low :smiley: More important is making sure there’s no bare wire. There’s no danger from it, but 100V even at 10mA is a nasty bite!

No danger but if you find yourself with wonky i2c or SPI that could be why

Good call. I’ll make sure the inverter is disconnected while programming.

I successfully programmed the board on the first try, and everything more or less works! Some patterns act weird so I need to do a little debugging*, but the core functionality is there. Even if I have to leave it on a single known working pattern, I consider the software DONE.

To make the actual mask, I’ve ended up weaving it like a basket. Polyamide thread for the weft, and alternating elwire and white wire for the warp. Only enough to get the topology for now. I’ll tweak and pull on it to get it in the right shape, then redo the thread more neatly.

So why does this pattern work?
(the first 2 values in each array are “frames per beat” and “frame count” metadata)

{7,14,        //Zoom in and out every two beats
  B10000000,
  B01000000,
  B00100000,
  B00010000,
  B00001000,
  B00000100,
  B00000010,
  B00000001,
  B00000010,
  B00000100,
  B00001000,
  B00010000,
  B00100000,
  B01000000,
  },

but this one doesn’t?

{8,8,        // Zoom out once per beat
  B00000001,
  B00000010,
  B00000100,
  B00001000,
  B00010000,
  B00100000,
  B01000000,
  B10000000
  },

The animation code looks like this: (note that the 2s are there to skip over the 2 metadata values)

    // update animation timers and see if we've passed a frame threshold
       anim_time = current_time - anim_start;           // how long since the animation started
       frame_threshold = anim_time % frame_length;       // divide by frame length and find remainder
      // If a frame has passed, increment the frame and set the outputs
      if ( frame_threshold < frame_prev_threshold) {    // when the remainder goes from high to low, we have started a new frame
          if (current_frame >= pattern_matrix[pattern_index][1]+2) current_frame = 2;
          int pinout=0;
          int channel_state=LOW;
          for (int i=0; i< channel_count; i++){
            channel_state = bitRead(pattern_matrix[pattern_index][current_frame], i);
            pinout = i+2;
            digitalWrite(pinout,channel_state);
          }
           ++current_frame;
        }

It’s really got me scratching my head
EDIT: It can’t possibly be a missing comma can it…

Try to print out channel_state,pinout and i while you are in the loop or just save them and print them out of the loop