Baphomet

19 December, 2016

Like a boss. The code for this is mainly on thingiverse.

20161219_021143.jpg


Wardstone with arduino

28 September, 2015

It ain’t finished yet, but here it is so far. I’m doing the electronics and the panels, Bevis is doing everything else 🙂 .

wardstone1-1

I was concerned that the LEDs wouldn’t be bright enough, that it would be “is that turned on?”

Holy shit.

You can’t look directly into them. I mean, you can, but then its several seconds before you can look at anything else. With a nice orangy-yellow, this thing looks molten.

The main thing is that you cannot drive something like that off a digital out – you will fry your arduino. I am driving these with the 5v off the arduino board via a darlington pair array. This means that power is coming off the regulator, not off the microprocessor.

Darlington pair array chips are designed for stepper motors and will happily handle 24v. They switch fast and have almost no resistance when driven (otherwise driving a motor would let the smoke out). The arduino analog out is pulse-width modulated, so no worries. Don’t need to use the flyback diodes, because the load is not inductive.

The darlington array shorts its output to ground when driven, so this means using common-anode LEDs.

Wardstone-layout-1I will etch some eldrich runes into the panels with the dremel. The idea is that not only will this catch the light, but with 2 differently-coloured LEDs in the base, different parts should catch different bis of the light. The arduino has six analog outs, so the LEDs are wired up as two sets of four on opposite corners.

I used single-core hookup wire for the loom, which was probably a mistake.

As for code – I just bodged something up this evening. light cycles between reddish and yellowish every second or so, using a slightly different period for the two sets. Won’t really know how it looks with the runes until we have runes. May need to make the colours more dramatic.

TODO:

  • move electronics onto a bit of veroboard, provide a controller of some kind – on/off at least. Might be nice to provide a pushbutton to make it strobe white, maybe some kind of “Oh Noes! The wardstobe has been corrupted!”. Our DM is being cagey, unfortunately, so I can’t be sure what he needs.
  • build obelisk that has been broken. Scribe runes first – we want the broken base to have enough runes to look kind of cool.

Jaycar taking all my money

9 May, 2015

My blind opener and closer seems to have bit rot, and I can’t get any feedback from it.

So I bought a shift register (“You bought a shift register?” “Yes, I bought a shift register.”)

Four of ’em. Here’s one in operation.

Yes, obviously it’s just counting. But the counting is done on the arduino, then I shift the 8 bits out to the register. The point being that I could have those bits be anything. Specifically, I could have them indicate the state of the various buttons and the internal state of the sketch.


Classic Yak Shaving. I want my blinds to work, so I need some feedback from my arduino, so I need some status LEDs (more than I already have), so I need a shift register, but I got four, so I have made up an array of 32 LEDs and now I need to wire them up, so I need solder, more gas for the iron, a dremel tool with a cutting wheel, a box to put the dremel tool bits in – you get the idea.

Yum! That soldering job!

The thing that redeems this excursion is that I am interested in shift registers and the general issue of buying ICs and putting them together to make a thing. Oh, and I’ve wanted a dremel tool for some time.

Anyway. I wanted to pack all the circuitry in that jiffy box, but the fact is that I am not going to be able to make it fit. So meh – I’ll just have some ribbon cable hanging out of it and put the shift registers on a small breadboard.

Once that’s done, I’ll be able to hook it up to three pins (plus power and earth) of my blind controller and get some reasonable explanation from it as to why my blinds are’nt opening and closing like the should.


Machine

3 April, 2015

Well – here’s the code for my “open the blinds when it’s daylight” project. It features time slicing – while the motor is winding the blinds, you can hit a button at any time to stop it.

Inputs:
PIN_UP, PIN_DN, PIN_ARM – buttons on my little control stick.
PIN_LDR – light sensor.

Outputs:
pin 13, of course. And pins A, A_, B and B_ which are the coils of the stepper.

A down on any button stops the motor.

A hold on PIN_UP/PIN_DN moves the motor until you release the button.

A hold on PIN_ARM arms and disarms the device.

A double-click on any button moves the blinds to the preset position for that button.

A triple-click sets the preset position for that button. This means that when setting, the motor will briefly activate (as if you had double-clicked), but meh.

Finally, the daylight sensor. If the device is armed, and A0 goes over the threshhold and stays over the threshold for a given time, then the motor moves the blinds to the position set on the ARM button.

The basic design is that each object has a loop() method which is called each time the main loop() runs. A boolean is returned to indicate if anything interesting happened. If nothing interesting happens for a while, then it would be reasonable to put the board to sleep, but meh – haven’t done this bit.

To set up:
Click and hold one of the UP/DN buttons to close the blinds. Triple-click to remember the closed position.
Click and hold the other UP/DN button to open the blinds. Triple-click to remember the open position, and also triple-click the ARM button.

At bedtime:
double-click the ‘close the blinds’ button to block out the light from that damn streetlight right outside my bedroom window. (If it isn’t already closed, which it will be due to privacy.)
click and hold the ‘arm’ button. The LED will flash once.

At dawn(ish), the blinds will open to let in the glorious morning sun and hopefully my circadian rhythm will finally get the message that it’s ok to wake up in the morning.

const byte INFO_LED = 13; // blue wire - also dives on-board LED

const byte A = 2;
const byte A_ = 3;
const byte B = 4;
const byte B_ = 5;

#define STEPPER_PINS(v) for(int v=2;v<=5;v++)
#define STEPPER_OFF STEPPER_PINS(stepperOut) digitalWrite(stepperOut, LOW);

const byte PIN_UP = 12; // red or black wire
const byte PIN_DN = 11; // red or black wire
const byte PIN_ARM = 10; // white wire

const byte  PIN_LDR = 0; // analog 0

const int LDR_THRESHHOLD = 300; // seems to be about right.
const int DAYLIGHT_TIME_MS = 1000 * 60 * 10; // 10 minutes

//#define DEBUG true

#ifdef DEBUG
#define info(x) _info(x)
#else
#define info(x)
#endif


// first - library code (in effect)

// An object that knows how to listen for button presses and releases
// and to invoke callbacks in response
// call loop() to make it go.

class Button {
  protected:

    byte pin;

#ifdef DEBUG
    void _info(const char *s) {
      Serial.print(name);
      Serial.print(": ");
      Serial.println(s);
    }
#endif

    virtual void dn() {
      info("DOWN");
      if (dn_callback) {
        dn_callback();
      }
    }

    virtual void up() {
      info("UP");
      if (up_callback) {
        up_callback();
      }
    }

  public:

    void (*dn_callback)() = NULL;
    void (*up_callback)() = NULL;
    char *name = "A Button";

    boolean buttonDown = false;

    void setup(byte _pin) {
      pin = _pin;
      pinMode(pin, INPUT_PULLUP);
      buttonDown = !digitalRead(pin);
    }

    boolean loop() {
      boolean newState = !digitalRead(pin);
      if (newState == buttonDown) return false;
      buttonDown = newState;
      if (buttonDown) dn(); else up();
      return true;
    }

};

// An object that knows how to listen for clicks, double-clicks, and holds
// and to invoke callbacks in response
// call loop() to make it go.

class Clicker: public Button {
  public:

    const int DOUBLE_CLICK_MS = 1000;
    const int HOLD_MS = 500;

    void (*click_callback)(int);
    void (*startHold_callback)(int);
    void (*hold_callback)();
    void (*endHold_callback)();

    boolean loop() {
      Button::loop();

      if (buttonDown) {
        if (holding) {
          hold();
        }
        else if (timeExceeded(HOLD_MS)) {
          startHold(clickCount);
          holding = true;
          clickCount = 0;
          hold(); // an initial hold to produce consistent behaviour
        }
        return true;
      }
      else {
        if (clickCount > 0 && timeExceeded(DOUBLE_CLICK_MS)) {
          clickCount = 0;
        }

        // if clickcount is greater than zero, we are in a possible double-click situation
        // so we want the loop to be running fast.
        return clickCount > 0;
      }
    }

  protected:

#ifdef DEBUG
    void _info(const char *s) {
      Serial.print(name);
      Serial.print(": ");
      Serial.println(s);
    }
#endif

    unsigned long dnMillis = 0L;
    boolean holding = false;
    int clickCount = 0;

    boolean timeExceeded(int t) {
      // millis may wrap, but the math works out
      return (millis() - dnMillis) >= t;
    }

    void dn() {
      Button::dn();
      dnMillis = millis();
      clickCount ++;
    }

    void up() {
      Button::up();
      if (holding) {
        holding = false;
        endHold();
      }
      else {
        click(clickCount);
      }
    }

    virtual void click(int clickCount) {
      info("CLICK");
      if (click_callback) {
        click_callback(clickCount);
      }
    }

    virtual void startHold(int clickCount) {
      info("HOLD ON");
      if (startHold_callback) {
        startHold_callback(clickCount);
      }
    }

    virtual void hold() {
      if (hold_callback) {
        hold_callback();
      }
    }

    virtual void endHold() {
      info("HOLD OFF");
      if (endHold_callback) {
        endHold_callback();
      }
    }
};

// A stepper motor that keeps track of location. It can move the motor to a target, or just start and stop.
// it will only do 5 minutes max of movement before stopping unconditionally
// call loop() to make it go.

class MyStepper {
  public:

    int location = 0;
    char *name = "Stepper";

    void setup() {
      STEPPER_PINS(v) pinMode(v, OUTPUT);
    }

    boolean loop() {
      // it's ok to leave lastMove uninitialized, because it's unsigned

      // 4ms = 250/s = 10/s turns = 600rpm, which is about the maximum

      if (millis() - lastMove < 5) return movingUp || movingDown || moveToTarget;

      lastMove = millis();

      if (movingUp) {
        location++;
      }
      else if (movingDown) {
        location--;
      }
      else if (moveToTarget) {
        if (location == target) {
          stopMoving();
          return true;
        }

        location += target > location ? 1 : -1;
      }
      else {
        return false;
      }

      if (tripFailsafe()) return true;

      // this code may cause both A and A_ to be turned on at the same time, but meh -
      // it's only for a microsecond or so. It isn't going to break anything.
      digitalWrite(A,   (location & 2) ? HIGH : LOW);
      digitalWrite(A_, !(location & 2) ? HIGH : LOW);
      digitalWrite(B,   ((location ^ (location >> 1)) & 1) ? HIGH : LOW);
      digitalWrite(B_, !((location ^ (location >> 1)) & 1) ? HIGH : LOW);

      return true;
    }

    void moveUp() {
      info("moveUp()");
      stopMoving();
      setFailSafe();
      movingUp = true;
    }

    void moveDown() {
      info("moveDown()");
      stopMoving();
      setFailSafe();
      movingDown = true;
    }

    void moveTo(int _target) {
      info("moveTo(_)");
      stopMoving();
      setFailSafe();
      moveToTarget = true;
      target = _target;
    }

    void stopMoving() {
      info("stopMoving()");
      movingUp = false;
      movingDown = false;
      moveToTarget = false;
      STEPPER_OFF;
    }


  protected:
    const unsigned long FAILSAFE_MS = 1000 * 60 * 5; // 5 minutes max time running the motor

    unsigned long failsafe = 0;
    unsigned long lastMove = 0;
    boolean movingUp = false;
    boolean movingDown = false;
    boolean moveToTarget = false;
    int target = 0;

#ifdef DEBUG
    void _info(const char *s) {
      Serial.print(name);
      Serial.print(": ");
      Serial.println(s);
    }
#endif

    void setFailSafe() {
      failsafe = millis();
    }

    boolean tripFailsafe() {
      if (millis() - failsafe < FAILSAFE_MS) return false;
      if (!movingUp && !movingDown && !moveToTarget) return false;
      stopMoving();
      return true;
    }

};

// the control objects and their event handlers.
// the control schema is:
// if the 'set' button is on, then a click on up/down/arm will set the corresponding setpoint
// it the 'set' button is not down
// buttons up and dn will move the motor on a hold and move to the setpunt on a dclick
// single-click does nothing. It's just an 'oops'.
// arm button moves to the arm setpoint on a dclick, and arms/disarms on a hold.
// on an arm, the onboard led is flashed once. on a disarm, it's flashed three times.

MyStepper stepper;
Clicker buttonUp;
Clicker buttonDn;
Clicker buttonArm;

boolean armed = false;
int upSetpoint;
int dnSetpoint;
int armSetpoint;
boolean daylight = false;
unsigned long daylight_t = 0;


#ifdef DEBUG
void _info(const char *s) {
  Serial.print(": ");
  Serial.println(s);
}
#endif

void buttonUp_click(int clickCount) {
  info("click");
  if (clickCount == 2) {
    info("2 clicks. moving up");
    stepper.moveTo(upSetpoint);
  }
  else if (clickCount == 3) {
    info("3 clicks. Setting up");
    upSetpoint = stepper.location;
  }
}

void buttonUp_startHold(int clickCount) {
  stepper.moveUp();
}

void buttonUp_endHold() {
  info("stop moving the stepper");
  stepper.stopMoving();
}

void buttonDn_click(int clickCount) {
  if (clickCount == 2) {
    stepper.moveTo(dnSetpoint);
    info("2 clicks. movto dn.");
  }
  else if (clickCount == 3) {
    dnSetpoint = stepper.location;
    info("3 clicks. Setting dn");
  }
}

void buttonDn_startHold(int clickCount) {
  stepper.moveDown();
}

void buttonDn_endHold() {
  stepper.stopMoving();
}

void buttonArm_click(int clickCount) {
  if (clickCount == 2) {
    stepper.moveTo(armSetpoint);
  }
  else if (clickCount == 3) {
    armSetpoint = stepper.location;
  }
}

void buttonArm_endHold() {
  armed = !armed;
  info_flash(armed ? 1 : 3);
}

void stopStepper() {
  stepper.stopMoving();
}

void setup() {
  pinMode(INFO_LED, OUTPUT);

#ifdef DEBUG
  Serial.begin(9600);
#endif


  buttonUp.setup(PIN_UP);
  buttonDn.setup(PIN_DN);
  buttonArm.setup(PIN_ARM);
  stepper.setup();

  buttonUp.name = "BtnUp";
  buttonUp.click_callback = buttonUp_click;
  buttonUp.startHold_callback = buttonUp_startHold;
  buttonUp.endHold_callback = buttonUp_endHold;

  buttonDn.name = "BtnDn";
  buttonDn.click_callback = buttonDn_click;
  buttonDn.startHold_callback = buttonDn_startHold;
  buttonDn.endHold_callback = buttonDn_endHold;

  buttonArm.name = "BtnArm";
  buttonArm.click_callback = buttonArm_click;
  buttonArm.endHold_callback = buttonArm_endHold;

  // all buttons, irrespective of anything else they do, stop
  // the stepper when they are hit.

  buttonUp.dn_callback = stopStepper;
  buttonDn.dn_callback = stopStepper;
  buttonArm.dn_callback = stopStepper;

}

// don't need a class to encapsulate this: it's pretty simple
boolean checkLightSensor() {
  if (!armed) return false; // nothing to see, here.

  boolean _daylight = analogRead(PIN_LDR) > LDR_THRESHHOLD;

  if (!_daylight) {
    if (!daylight) {
      // still naptime
      return false;
    }
    else {
      info("Just a passing car");
      // just a passing car.
      daylight = false;
      return true;
    }
  }
  else {
    // ok. it's daylight now, and we are armed.

    if (!daylight) {
      info("Daylight sensed");
      // this was the first sensing
      daylight = true;
      daylight_t = millis();
      return true;
    }
    else if (millis() - daylight_t < DAYLIGHT_TIME_MS ) {
      // waiting to see if the sun remains out for a bit
      return false;
    }
    else {
      info("It's daytime. Moving.");
      // we are armed, it's daylight, and it's been daylight for some time
      info_flash(10);
      armed = false;
      stepper.moveTo(armSetpoint);
      return true;
    }
  }

}

void loop() {
  // put your main code here, to run repeatedly:

  boolean somethingHappened = false;

  somethingHappened = buttonUp.loop() || somethingHappened;
  somethingHappened = buttonDn.loop() || somethingHappened;
  somethingHappened = buttonArm.loop() || somethingHappened;
  somethingHappened = stepper.loop() || somethingHappened;
  somethingHappened = checkLightSensor() || somethingHappened;

  if (!somethingHappened) {
    // TODO! Put the arduino to sleep, wake on interrupt.
    // for now - meh. It's ok to run a tight loop.
  }

}

void info_flash(int n) {
  while (n--) {
    digitalWrite(INFO_LED, HIGH);
    delay(200);
    digitalWrite(INFO_LED, LOW);
    delay(200);
  }
  delay(200); // to demarcate the phrase
}


Machine

23 March, 2015

The Device is complete.

Oh, there are issues. Most particularly, I damaged a part with excessive heat, but the part that was damaged was part of my hand-held controller – for the particular function needed, I am content to use the probe mounted onto the brain itself.

There is no special reason to suppose the device will fail to perform its function. I fully expect that I shall be shielded from the annoying light from the streetlamp outside all night, and that at dawn my new domestic servant shall open the blinds for me.

(PS: Mad! Mad! They called me mad! I’ll show them who’s mad, the brutes, the ignorant philistines – what do they know of science etc etc. Grandfather Freemont might have dared the Bravarian torches and pitchforks – I am a little more circumspect.)


Machine

21 March, 2015

Progress. I have successfully moved the interface onto its own board, which shortens the lines needed to attach the interface to the motor.

I have also found sources of Coulombic fluid separate for the mind and the matter – 6v and 9v respectively. The 9v supply, I suspect, will not suffice to operate the device when it is attached to its load: if has force, but not capacity.

Best of all, I found a purveyor of parts for model flying machines, who had a part that would fit the 3mm shaft of my motor. Now I shall be able to attach it to its load without difficulty.

I enclose a moving photolithograph (the wonders of technology!) of the device thus far in operation.

— ADDENDUM —

I have rebuilt the gearing. The result is simpler and more compact, which is usually the way. Just now I tested the device against the load it is intended to move. The coulombic nine-volt source is not sufficent for the task. Using my bulkier source, it moved the load handsomely, and above all reasonably quietly.

I shall restore to the brain its previous intelligence. Then I shall investigate adding its eye. Finally, I shall need to think carefully with respect to the logic behind its mind, and perhaps organise a somehat more robust control device. At present, it is controlled by switches mounted into its skull. They are tiny and prone to come loose – something a shade more robust would serve better.

Exciting times, although I must give some few hours to another task needing to be done.


Machine

18 March, 2015

As I mentioned previously, the subtle currents of the mind cannot animate the gross matter of the body directly: an intermediary is needed. The mind – physically – exists as a gas, or perhaps smoke confined within the brain. The currents of thought are breezes blowing though the brain, moderated and manipulated by its – shall we say – shape. And as the subtle currents of the mind cannot animate the gross motions of the body, should the situation be reversed, the brain will be overwhelmed and the mind will escape it, leaving a mere husk, an empty shell.

While not obvious to the eye, nevertheless this catastrophe may be detected by the nose, as the smoke that is the substance of mind has a characteristic odour.

Which brings me to my latest experiments.

I had grown impatient with the device to move the limbs – it was noisy, it shook and howled. The motive power for it required far too much Coulombic Fluid. Although I have a source of such, it is bulky and I did not want to use it for the final installation. The motive heart spun at a furious rate, requiring mechanical reducing, and lacked a way to delicately control it. I attempted to grant my machine a kinesthetic sense, but the shaking of the whole device rendered these organs worthless: they could not distinguish between the motion of the shaft and the overall vibration.

I was aware, of course, of a different kind of motive power – a “stepper” which, rather than merely spinning, could be made to advance forwards in fine steps (one forty-eighth of a revolution, in fact). I had avoided these, as the other motors (‘motor’: that which provides motive power) were far simpler. Additionally, these “steeper motors” required a mind/body barrier.

But I had needed a barrier anyway when working with the simpler motor, so it was much of a muchness. And although the stepper is more complex, the mathematics of it were by no means beyond me. It was the correct tool for the task at hand, and I simply felt up to the challenge. And so, once more to the graveyard, coin in hand, for the purchase of a stepper motor and a darlington array chip.

First, the mind! I made use of a certain well-known library for such problems, the one that the italian gentlemen who manufacture my mechanical brain supply. I desired to see what manipulations it would attempt to perform on the motor, and so I attached to the brain certain devices (light-emitting diodes, or LEDs) that glow in the presence of motion in the electrical fluid, and which are sensitive enough to do so in response to the subtle currents of the mind.

Observing the behaviour of the brain, and consulting with the excellent information with which the motor came supplied, I determined how to instruct the mind to operate the motor.

At which point it came time to attempt to use the mind/body mechanism, the “darlington array”. I reasoned that to start with, rather than using the powerful currents that animate the body, I could use the potential of the brain – five voltaic units – as its output, as I merely wanted to see that the damn thing worked at all. I moved the taps that were connected to the LEDs to the array, and put the LEDs on the output of the array.

And all was as it should be! When I directed the mind to move its limb, the LEDs illuminated in correct sequence.

Now it is usual, when LEDs are used, to equip them with a prophylactic whose function is to resist an excess flow of Coulombic Fluid. Such a prophylactic reduces the brightness of the LEDs, but not by a great amount. However, I had disdained this, principally because it is rather fiddly to install the devices – my eyes and fingers are not so precise as they once were. As the darlington array was connected to a mere five volts, this was not an issue.

But the whole point of the array is that it can manage a far greater voltage. That it whole point of it. And so I connected the array to the full unmoderated nine volts of input potential. Within seconds, the LEDs flickered and went dark, and I smelled the smoke. Something had died.

But as you have gathered, I had merely fried the LEDs. One of them still worked, although barely, and was illuminating and darkening in the correct sequence, and so I saw immediately that the brain was uninjured. On the whole, a cheap and instructive lesson.

After this, I connected the motor to the mind via the array (the motor must not be connected directly to the mind under any circumstances due to the phenomenon of back-EMF), and the brain controlled it quite handsomely. I commanded it to spin the motor forward and back by one revolution pausing for one second in between, and the motor accurately turned by a revolution and returned precisely to its starting point. Formidable!

And so matters stand. I believe the motor is quite a bit stronger than the previous ones, which rely on rotational speed. Thus, I shall be able to remove quite a bit of the gearing from The Machine. I suspect I could connect the motor directly to its destined load, but I have been to quite a bit of trouble to source the “Meccano” and damn it – I intend to use it irrespective of whether it is actually necessary.

Further developments anon.