RChadwick7 / ParkinsonsGloves

Parkinson's Therapy vibrating gloves
8 stars 0 forks source link

Using electromagnets for generating vibrations #3

Open HackyDev opened 1 year ago

HackyDev commented 1 year ago

Hey!

I'm here to report some findings on the use of electromagnets. I was able to achieve 250Hz frequency and a rise time of 10 - 20ms using an electromagnet from a simple arduino relay and a neodimium magnet. I measured the frequency by pressing the contactor against the microphone and using this service to detect the pitch. The vibrations are very mild, but noticeable. The exact amplitude is not known, because I have no idea how to measure it yet. I'm sure the rise time can be improved using other electromagnets/code/etc.

Here's the representation of the concept.

concept

Here's a relay.

relay

Here's a relay without latching mechanism and the casing.

relay-bare

Here's new casing Untitled

Here's its all together relay-case

Here's Arduino code:

  enum State {
    NULL_STATE,
    PAUSE_STATE,
    RELAY_ON_STATE,
    RELAY_OFF_STATE,
  };

  int frequencyHz = 260; // produces 252Hz vibration

  unsigned long relayOnDurationUs = 1000000 / (frequencyHz * 2);
  unsigned long relayOffDurationUs = 1000000 / (frequencyHz * 2);
  unsigned long runDurationUs = 1000000; // 1 second
  int pauseDurationMs = 1000; // 1 second
  int relayPin = 13;

  void setup() {
    pinMode(relayPin, OUTPUT);
  }

  class Timer {
    private:
      unsigned long _startedAt;
      unsigned long _endsAt;
      unsigned long _duration;
      bool _hasOverflowed;
      bool _isMillis;
    unsigned long getTime () {
      if (_isMillis) {
        return millis();
      } else {
        return micros();
      }
    }
    public:
      Timer (bool isMillis) {
        _isMillis = isMillis;
      }
      const unsigned long& startedAt = _startedAt;
      void start(unsigned long duration) {
        _startedAt = getTime();
        _endsAt = _startedAt + duration;
        _duration = duration;
        _hasOverflowed = _endsAt < _startedAt;
      }
      bool isStarted () {
        return _duration > 0;
      }
      unsigned long getRunTime () {
        if (isStarted()) {
          return getTime() - _startedAt;
        } else {
          return 0;
        }
      }
      unsigned long isDone () {
        if (_duration == 0) {
          return false;
        }
        unsigned long now = getTime();
        if (_hasOverflowed) {
          return now + _duration > _startedAt;
        } else {
          return now > _endsAt;
        }
      }
      void reset () {
        _startedAt = 0;
        _endsAt = 0;
        _duration = 0;
        _hasOverflowed = false;
      }
  };

  Timer relayTimer = Timer(false);
  Timer runTimer = Timer(false);
  State currentState = NULL_STATE;

  void loop() {
    bool isStateChanged = updateState();
    if (isStateChanged) {
      switch (currentState) {
        case PAUSE_STATE:
          digitalWrite(relayPin, LOW);
          delay(pauseDurationMs);
          break;
        case RELAY_ON_STATE:
          digitalWrite(relayPin, HIGH);
          break;
        case RELAY_OFF_STATE:
          digitalWrite(relayPin, LOW);
          break;
      }
    }
  }

  bool updateState () {
    State currentStateSaved = currentState;
    switch (currentState) {
      case NULL_STATE:
        currentState = RELAY_ON_STATE;
        break;
      case PAUSE_STATE:
        currentState = RELAY_ON_STATE; 
        break;
      case RELAY_ON_STATE:
        if (relayTimer.isStarted()) {
          if (relayTimer.isDone()) {
            currentState = RELAY_OFF_STATE;
            relayTimer.reset();
          }
        } else {
          relayTimer.start(relayOnDurationUs);
        }
        break;
      case RELAY_OFF_STATE:
        if (relayTimer.isStarted()) {
          if (relayTimer.isDone()) {
            currentState = RELAY_ON_STATE;
            relayTimer.reset();
          }
        } else {
          relayTimer.start(relayOffDurationUs);
        }
        break;
    }

    if (runTimer.isStarted()) {
      if (runTimer.isDone()) {
        currentState = PAUSE_STATE;
        runTimer.reset();
      }
    } else {
      runTimer.start(runDurationUs);
    }

    return currentStateSaved != currentState;
  }

Here's the rise time (screenshot from audio editor)

image140

RChadwick7 commented 1 year ago

That looks promising! They make smaller relays, but the mechanical output will be weaker. You might get a stronger output by finding a low voltage relay, say 3V (I don't know if these exist, I've only seen 5V), then drive them at a higher voltage. If you're driving them directly and not using the contacts, you can try a mechanical buzzer, which is essentially a relay wired to oscillate, connected to a large diaphragm. Those might have even more powerful coils, I'm excited to hear how these work.

phil-barrett commented 1 year ago

Hello, sorry for intruding but this is an area of deep interest for me - my wife has early onset PD. I am an SDE and EE. Very familiar with design and manufacture of electronic circuits. I have a small business designing and selling circuit boards for the CNC marketplace.

The first thing that struck me is the use of relay coils to make the actuators. Coil winding is not hard, no need to crack open cheap relays. Plus the coil can be made to meet the exact needs - though, the hard part is knowing exactly what the needs are. That's not my area of expertise unfortunately. The relay coils look pretty big, I would think lower profile ones would fit better in the gloves. A coil winding machine is pretty easy to build, ham radio people have been doing it for about 100 years now. Also, the relay coils probably draw a lot of current (likely 70 mA for the 5V ones), especially when multiplied by 8. I assume the gloves are battery powered so power consumption is probably a big deal. Not sure if much lower current draw is even possible, though. I suspect that's why there seems to be a lot interest in piezo actuators.

For driving the coils, the uln2803 is ok but you might want to consider using mosfet H-bridges. The uln2803 is pretty old and the new mosfet drivers are much more efficient, important for battery power. An example is the DMHC3025, it comes in a small 8 pin surface mount package and could be mounted right at the coil. It costs about $0.70 each so <$5.00. I'm sure there are even smaller packages.

Anyway, just wanted to chime in to say I'm following and am interested in building/testing/experimenting on this front.

Phil Barrett

By the way, have you thought about turning on the discussion feature? Typically on github, issues are where people report bugs. The discussions section is where a lot of people do brainstorming.

HackyDev commented 1 year ago

@phil-barrett, Hey! Thank you for the tips. Additionally, there are these tiny audio exciters available, such as the Tectonic TEAX09C005-8. Although I've never worked with them, I believe they would be perfect for this application and could save a lot of time for DIYers. Anyway, I've managed to assemble a working prototype using this approach. It was a real pain to put everything together though. If you're curious here's the repo

phil-barrett commented 1 year ago

I looked at the TEAX09C005-8 and it appears to be driven like a speaker. If it works well, then great! But, after reading a bit more, I wonder if a tiny solenoid is a good fit for this application. I'm using quotes because I think my terminology is wrong for the application space.

I'm very interested to see how your proto works. Basically, it uses the solenoid approach. While I get it's a pain to use the coils, I'd like to try winding my own. I need to bone up on solenoid construction though.