Author Topic: Yet Another MCU Latching Relay True Bypass -- MSP430 based  (Read 2720 times)

Transmogrifox

Yet Another MCU Latching Relay True Bypass -- MSP430 based
« on: November 07, 2014, 11:45:41 AM »
Last weekend I just tested this circuit, and it works well.  The power supply section stores the charge locally to dump it into the latching relay on demand, so with a good layout it should be pretty mild in terms of pop and click.

If anybody is interested in building it, let me know.  I can help if you want to try a different MSP430 (for example something with lower pin count so you can use through-hole).  The surface mount version of this MSP430 is so small and the DIP package is so large it makes sense to do SMT.  This SMT chip is small even compared to 8-pin DIPs.

I used the MSP430F2013 on the USB EZ FET (the little usb stick with the uC board on the end).  I think this circuit bears making a tiny PCB and turning the whole thing into a package a little smaller than a normal DPDT switch (I think I can find SMT capacitors small enough here).

The relay used is a 3V latching relay:
KEMET EC2-3SNU

Here is the schematic:


And the code
hardware.h
Code: [Select]
#ifndef HARDWARE_H
#define HARDWARE_H

#include <msp430x20x3.h>

//Port Output Register 'P1OUT, P2OUT':
#define P1OUT_INIT      0                       // Init Output data of port1
#define P2OUT_INIT      0                       // Init Output data of port2

//Port Direction Register 'P1DIR, P2DIR':
#define P1DIR_INIT      0xfb                    // Init of Port1 Data-Direction Reg (Out=1 / Inp=0)
#define P2DIR_INIT      0xff                    // Init of Port2 Data-Direction Reg (Out=1 / Inp=0)

//Selection of Port or Module -Function on the Pins 'P1SEL, P2SEL'
#define P1SEL_INIT      0                       // P1-Modules:
#define P2SEL_INIT      0                       // P2-Modules:

//Interrupt capabilities of P1 and P2
#define P1IE_INIT       0                       // Interrupt Enable (0=dis 1=enabled)
#define P2IE_INIT       0                       // Interrupt Enable (0=dis 1=enabled)
#define P1IES_INIT      0                       // Interrupt Edge Select (0=pos 1=neg)
#define P2IES_INIT      0                       // Interrupt Edge Select (0=pos 1=neg)

#define WDTCTL_INIT     WDTPW|WDTHOLD

#endif //HARDWARE_H

and toggle_latch_relay.c
Code: [Select]
//
//  Switch Debouncing and toggle latching relay state with LED indication
//

//
// Schematic
//                      MSP430F2013           N/C MOMENTARY
//                    -------------           SWITCH
//         <+3.6V>-++| 1         8 |++-<GND>---\___\-----<SW>
//      -----------++| 2         9 |++                 |
//      |  <RL_N>--++| 3        10 |++                 ----/\/\/\---<+3.6V>
//      |    <SW>--++| 4        11 |++                       10k   |
// R  C |----------++| 5        12 |++                             |
// E  O =  <RL_N>--++| 6        13 |++     <RL_N>----/\/\/\---|<;---
// L  I =          ++| 7        14 |++                470     LED
// A  L =             -------------
// Y    =
//      |
//      ---<RL_N>
//
//  Relay contacts not shown.  Nominal is a DPDT relay and true bypass switching
//  circuit without LED indicator since this is powered by the MSP430
//

#include "hardware.h"

//  
//  Timing Constants
//      These constants were experimentally tuned using an oscilloscope to watch
//      delay from input edge to state change.  This comes to something
//      between 10 ms to 15 ms, also depending on amount of switch bounce
//

#define POLL_RATE   0x002F
#define THRS        0x001F
#define HOLD_OFF    0x4FC2
#define LATCH_DELAY 0x0400

// OUTPUT pin states
#define LATCH_OFF   0x0009
#define OFF_STATE   0x0000
#define LATCH_ON    0x0012
#define ON_STATE    0x001B

// INPUT Pin
#define INPUT_MASK  0x0004

//
//  Delay function.
//  Sets crude basis for system tick and input polling
//

void delay(unsigned int d) {
    int i;
    for (i = 0; i<d; i++) {
        nop();
        nop();
        nop();
        nop();
    }
}

//
//  Toggle output states
//
//  Timing sequence to turn off:
//  P1OUT xxx11x11    <-Initial state when enters toggle function
//  P1OUT xxx01x01
//  delay
//  P1OUT xxx00x00
//  
//  Timing to turn on:
//  P1OUT xxx00x00    <-Initial state when enters toggle function
//  P1OUT xxx10x10
//  delay
//  P1OUT xxx11x11
//
unsigned int toggle(unsigned int on)
{
    if(on != 0)
    {
        P1OUT = LATCH_OFF;
        delay(LATCH_DELAY);
        P1OUT = OFF_STATE;
        return 0;
    } else {
        P1OUT = LATCH_ON;
        delay(LATCH_DELAY);
        P1OUT = ON_STATE;
        return 1;
    }
}

unsigned int run_poll_sequence()
{

    static volatile unsigned int onstate = 0;
    static volatile unsigned int edge = 1;
    static volatile unsigned int trgcnt = 1;
    static volatile unsigned int in_state = 0;
    
    //Start polling
    in_state = P1IN;    //copy it over where it can be
    in_state &= INPUT_MASK;   //manipulated without touching P1IN
                    
    // Switch bounce filtering
    // Ramp up or down.  Response time will be
    // determined by POLL_RATE and THRS
    if(in_state != 0) {
        trgcnt += 1;
    }
    else {
        trgcnt --;
    }
    
    //  This is the edge detection
    //  When trgcnt ramps up above 1, edge will be set to 1
    //  When trgcnt exceeds THRS, switch state will toggle
    //  then edge is reset so it doesn't toggle the switch
    //  for every cycle the input is high.  When input returns
    //  low and ramps to <1, then edge is reset
    if(trgcnt < 1) {
        trgcnt = 1;
        edge = 1;
    }
    else if (trgcnt > THRS) {
        if(edge == 1)
            onstate = toggle(onstate);
        edge = 0;
        trgcnt = THRS;
        delay(HOLD_OFF);  //don't do anything for a good bit
                        //so the jiggly foot doesn't retrigger
                        //immediately after positive state change
    }

    return 1;
}

//
//  System Initialization
//

void bootup_init()
{
    //Watchdog
    WDTCTL = WDTCTL_INIT;               //Init watchdog timer
    
    //Set up clock
DCOCTL = 0;
BCSCTL1 = CALBC1_1MHZ;
BCSCTL2 = 0x01;
DCOCTL  = CALDCO_1MHZ;

    //Initialize I/O
    P1OUT  = P1OUT_INIT;                //Init dataput data of port1
    P2OUT  = P2OUT_INIT;                //Init dataput data of port2

    P1SEL  = P1SEL_INIT;                //Select port function on port1
    P2SEL  = P2SEL_INIT;                //Select port function on port2

    P1DIR  = P1DIR_INIT;                //Init port direction register of port1
    P2DIR  = P2DIR_INIT;                //Init port direction register of port2

    //Interrupts
    P1IES  = P1IES_INIT;                //init port interrupts
    P2IES  = P2IES_INIT;
    P1IE   = P1IE_INIT;
    P2IE   = P2IE_INIT;

    //Initialize relay to known state
    P1OUT = 0x001B;
    toggle(1);
}

//
//  Main polling function
//

int main(void) {
    bootup_init();
    while(run_poll_sequence()) { //always returns 1, so this is forever loop
        // Crude loop timing
        delay(POLL_RATE);
    }
    
    //just to keep gcc happy
    return 0;
}

I built it using mspgcc, so here is the makefile:
Code: [Select]
# makfile configuration
NAME            = relay
OBJECTS         = toggle_latch_relay.o
CPU             = msp430f2013

CFLAGS          = -mmcu=${CPU} -O2 -Wall -g

#switch the compiler (for the internal make rules)
CC              = msp430-gcc

.PHONY: all FORCE clean download download-jtag download-bsl dist

#all should be the first target. it's built when make is run without args
all: ${NAME}.elf ${NAME}.a43 ${NAME}.lst

#confgigure the next line if you want to use the serial download
dl: download-jtag
#download: download-bsl

#additional rules for files
${NAME}.elf: ${OBJECTS}
${CC} -mmcu=${CPU} -o $@ ${OBJECTS}

${NAME}.a43: ${NAME}.elf
msp430-objcopy -O ihex $^ $@

${NAME}.lst: ${NAME}.elf
msp430-objdump -dSt $^ >$@

download-jtag: all
msp430-jtag --spy-bi-wire --lpt=COM5 -e ${NAME}.elf
download-bsl: all
msp430-bsl -e ${NAME}.elf

clean:
rm -f ${NAME}.elf ${NAME}.a43 ${NAME}.lst ${OBJECTS}

#backup archive
dist:
tar czf dist.tgz *.c *.h *.txt makefile

#dummy target as dependecy if something has to be build everytime
FORCE:

#project dependencies
toggle_latch_relay.o: toggle_latch_relay.c hardware.h
« Last Edit: November 07, 2014, 11:47:50 AM by Transmogrifox »
trans·mog·ri·fy
tr.v. trans·mog·ri·fied, trans·mog·ri·fy·ing, trans·mog·ri·fies To change into a different shape or form, especially one that is fantastic or bizarre.

cloudscapes

Re: Yet Another MCU Latching Relay True Bypass -- MSP430 based
« Reply #1 on: November 07, 2014, 11:56:51 AM »
You've got the bypass LED hooked up right to the relay coil. Does that mean you apply current to the coil at all times? For a latching relay, this seems pretty inefficient.  ;)

I made an MCU latching relay circuit recently as well. I had the MCU control the bypass LED with its own pin, but control the coil with a 5ms burst of current.
« Last Edit: November 07, 2014, 12:02:10 PM by cloudscapes »
~~~~~~~~~~~~~~~~~~~~~~
{DIY blog}
{www.dronecloud.org}

Transmogrifox

Re: Yet Another MCU Latching Relay True Bypass -- MSP430 based
« Reply #2 on: November 07, 2014, 12:39:55 PM »
If you look at the code you will see that both outputs go high, then both outputs go low -- only there is a 10 ms lead/lag so that it pulses the coil on the transition.

Like this:
Code: [Select]
IN    OUT-     OUT+
0     0            0
^     0            1          This state 10 to 15 ms
X     1            1          LED ON, ~200ms re-trigger hold-off
0     1            1          LED ON
^     1            0          This state 10 to 15 ms
X     0            0          ~200ms re-trigger hold-off
0     0            0

^ = Rising Edge
X = Don't Care

Also the 10-15 ms pulse was chosen per datasheet recommendation.  By best practice design, >10 ms is necessary, and driving from parallel sets of pins is needed to meet worst-case coil impedance.  In reality and most cases, single set of pins and 5 ms transition would work.  This is easy to tweak in the code defines if switch popping becomes a problem.

This was simulated in Spice and tweaked quite a bit in simulation to make sure worst-case drive and charge storage conditions are met.

In addition, timing characteristics and switch debouncing immunity were confirmed using an oscilloscope.  This wasn't a "slop-together" job.  

The switch debouncing and re-trigger hold-off make this immune even to touching wires together in an uncertain way.  To make it trigger you have to mean it.  A burst of RF noise like somebody's cell phone won't touch it off.
« Last Edit: November 07, 2014, 12:51:28 PM by Transmogrifox »
trans·mog·ri·fy
tr.v. trans·mog·ri·fied, trans·mog·ri·fy·ing, trans·mog·ri·fies To change into a different shape or form, especially one that is fantastic or bizarre.