The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Day: December 17, 2012

  • Arduino Snippets: Vibrating Motor

    A small buzzer motor should come in handy for something. Perhaps alerting you to the presence of AC magnetic fields? Anyhow, driving a pager motor from one of the spare bits on the DL1414 display control shift register worked out well enough:

    Motor Driver with LED Character Display
    Motor Driver with LED Character Display

    These cute little surplus motors expect a 2.5 V supply and buzz overenthusiastically at 5 V; the 100 Ω resistor reduces the current to about 30 mA. That says the motor now runs on about 2 V and I admit picking the resistor became totally empirical, because starting even a little teeny motor requires more current than keeping it running and my first guess was far too high. The 1N4148 diode can handle a few tens of milliamps and will become inadequate for larger motors.

    The MOSFET driver resides between the LED displays, with the motor hanging in mid-air on a long wire and the diode hiding behind the motor terminals:

    Buzzer Motor Driver - breadboard
    Buzzer Motor Driver – breadboard

    Dropping the motor control bit into the DL1414 struct suggested that renaming the whole affair would be a Good Idea:

    union CONTROLBITS_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    		unsigned int Addr:2;
    		unsigned int NotWrite:1;
    		unsigned int Ctl3_6:4;			// unused bits
    		unsigned int Motor:1;			// buzzer motor drive
    		unsigned int Data:7;
    		unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    

    Controlling the motor requires changing only that single bit in the shift register:

    void MotorControl(byte State) {
    
    	ControlBits.ShiftBits.Motor = State ? 1 : 0;
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    }
    

    We assume that the DL1414 control bits remain properly configured from the previous operation. The variable holding that struct (actually, the union wrapped around it), must have global scope so everybody uses the most recent bits. Global variables are obviously fraught with peril; hide it inside a method or other fancy construct, as you prefer.

    The demo code alternates the motor between on and off as you press Button 1 and shows the current status on the DL1414 display. I mashed up the button demo code with the LED character code, then sprinkled the motor on top:

    	Button = ReadButtons(PIN_BUTTONS);
    
    	if (PrevButton != Button) {
    		if (Button == N_BUTTONS) {
    			printf("Button %d released\n",PrevButton);
    		}
    		else {
    			printf("Button %d pressed\n",Button);
    			if (Button == B_1) {
    				ControlBits.ShiftBits.Motor = ~ControlBits.ShiftBits.Motor;
    				sprintf(LEDCharBuffer,"%";,
    					ControlBits.ShiftBits.Motor?"ON  ":"OFF ");
    				WriteLEDString(LEDCharBuffer);
    			}
    		}
    		PrevButton = Button;
    	}
    
    	if (PrevKnobCounter != KnobCounter) {
    		printf("Knob count: %d\n",KnobCounter);
    		sprintf(LEDCharBuffer,"%c%3d",
    			ControlBits.ShiftBits.Motor?'*':'_',
    			KnobCounter);
    		WriteLEDString(LEDCharBuffer);
    		PrevKnobCounter = KnobCounter;
    	}
    

    The picture shows the motor sitting idle and the DL1414 reporting OFF.

    When you turn the knob, that display shows the value of the knob click counter, with the first character indicating the motor state.

    If you ran the motor directly from an Arduino PWM output, you might get some speed control, but I think the dynamic range wouldn’t justify the effort. Buzzing in patterns of a few hundred milliseconds over the course of a second might be more distinctive; you could even do Morse code.

    The Arduino source code:

    // Quadrature knob with switch
    // Ed Nisley - KE4ANU - November 2012
    // Based on:
    // https://softsolder.com/2009/03/03/reading-a-quadrature-encoded-knob-in-double-quick-time/
    
    //----------
    // Pin assignments
    
    const byte PIN_KNOB_A = 2;			// knob A switch - must be on ext interrupt 2
    const byte PIN_KNOB_B = 4;			//  .. B switch
    const byte PIN_BUTTONS = A5;		//  .. push-close momentary switch
    
    const byte PIN_MOSI = 8;			// data to shift reg
    const byte PIN_SCK  = 6;			// shift clock to shift reg
    const byte PIN_RCKB  = 7;			// latch clock for LED Bargraph
    const byte PIN_RCKC  = 12;			// latch clock for LED character display
    
    const byte PIN_SYNC = 13;			// scope sync
    
    //----------
    // Constants
    
    const int UPDATEMS = 10;				// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1};
    
    enum BUTTONS {SW_KNOB, B_1, B_2, B_3, B_4, N_BUTTONS};
    
    #define LED_SIZE		4				// chars per LED
    #define LED_DISPLAYS	1				// number of displays
    #define LED_CHARS		(LED_DISPLAYS * LED_SIZE)
    
    union CONTROLBITS_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    		unsigned int Addr:2;
    		unsigned int NotWrite:1;
    		unsigned int Ctl3_6:4;			// unused bits
    		unsigned int Motor:1;			// buzzer motor drive
    		unsigned int Data:7;
    		unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    
    
    //----------
    // Globals
    
    volatile char KnobCounter = 0;
    volatile char KnobState;
    
    char PrevKnobCounter = 0;
    
    byte Button, PrevButton;
    
    // ButtonThreshold must have N_BUTTONS elements, last = 1024
    
    word ButtonThreshold[] = {265/2, (475+265)/2, (658+475)/2, (834+658)/2, (1023+834)/2, 1024};
    
    union CONTROLBITS_ ControlBits;
    
    char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Pulse selected pin high
    
    void PulsePinHigh(byte PinID) {
    	digitalWrite(PinID,HIGH);
    	digitalWrite(PinID,LOW);
    }
    
    //-- Write single char to DL1414, other control bits as defined
    
    void WriteLEDChar(char Char,char CharID) {
    
    	ControlBits.ShiftBits.Data = Char & 0x7F;
    	ControlBits.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars
    
    	ControlBits.ShiftBits.NotWrite = 1;				// set up data and address
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	ControlBits.ShiftBits.NotWrite = 0;				// write the character
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	ControlBits.ShiftBits.NotWrite = 1;				// disable write
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    }
    
    void WriteLEDString(char *pString) {
    
    	for (byte i=0; (i < LED_CHARS) && *pString; ++i)
    		WriteLEDChar(*pString++,i);
    
    	return;
    }
    
    void MotorControl(byte State) {
    
    	ControlBits.ShiftBits.Motor = State ? 1 : 0;
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    }
    
    //-- Knob interrupt handler
    
    void KnobHandler(void)
    {
    	byte Inputs;
    	Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A);  // align raw inputs
    //	Inputs ^= 0x02;                             // fix direction
    
    	switch (KnobState << 2 | Inputs) {
    	case 0x00 : 				// 0 00 - glitch
            break;
    	case 0x01 : 				 // 0 01 - UP to 1
            KnobCounter++;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x03 : 				 // 0 11 - DOWN to 1
            KnobCounter--;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x02 : 				 // 0 10 - glitch
            break;
    	case 0x04 : 				 // 1 00 - DOWN to 0
            KnobCounter--;
    		KnobState = KNOB_CLICK_0;
    		break;
    	case 0x05 : 				 // 1 01 - glitch
            break;
    	case 0x07 : 				 // 1 11 - glitch
            break;
    	case 0x06 : 				 // 1 10 - UP to 0
            KnobCounter++;
    		KnobState = KNOB_CLICK_0;
    		break;
    	default :  					// something is broken!
            KnobCounter = 0;
    		KnobState = KNOB_CLICK_0;
    	}
    }
    
    
    //-- Read and decipher analog switch inputs
    //		returns N_BUTTONS if no buttons pressed
    
    byte ReadButtons(int PinNumber) {
    
    word RawButton;
    byte ButtonNum;
    
    	RawButton = analogRead(PinNumber);
    
    //	printf("RawButton: %d ",RawButton);
    
    	for (ButtonNum = 0; ButtonNum <= N_BUTTONS; ButtonNum++){
    //		printf(" (%d:%d)",ButtonNum,ButtonThreshold[ButtonNum]);
    		if (RawButton < ButtonThreshold[ButtonNum])
    			break;
    	}
    
    //	printf(" ButtonNum %d\n",ButtonNum);
    
    	return ButtonNum;
    
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_SYNC,OUTPUT);
    	digitalWrite(PIN_SYNC,LOW);	// show we arrived
    
    //	TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //	TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_KNOB_B,INPUT_PULLUP);
    	pinMode(PIN_KNOB_A,INPUT_PULLUP);
    
    	pinMode(PIN_MOSI,OUTPUT);
    	digitalWrite(PIN_MOSI,LOW);
    
    	pinMode(PIN_SCK,OUTPUT);
    	digitalWrite(PIN_SCK,LOW);
    
    	pinMode(PIN_RCKB,OUTPUT);
    	digitalWrite(PIN_RCKB,LOW);
    
    	pinMode(PIN_RCKC,OUTPUT);
    	digitalWrite(PIN_RCKB,LOW);
    
    	KnobState = digitalRead(PIN_KNOB_A);
    	Button = PrevButton = ReadButtons(PIN_BUTTONS);
    
    	attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Motor, knob, and buttons\r\nEd Nisley - KE4ZNU - December 2012\r\n");
    
    	ControlBits.ShiftWord = 0x0000;
    	WriteLEDString(LEDCharBuffer);
    
    	delay(1000);
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    
    		digitalWrite(PIN_SYNC,HIGH);
    
    		Button = ReadButtons(PIN_BUTTONS);
    
    		if (PrevButton != Button) {
    			if (Button == N_BUTTONS) {
    				printf("Button %d released\n",PrevButton);
    			}
    			else {
    				printf("Button %d pressed\n",Button);
    				if (Button == B_1) {
    					ControlBits.ShiftBits.Motor = ~ControlBits.ShiftBits.Motor;
    					sprintf(LEDCharBuffer,"%s",
    						ControlBits.ShiftBits.Motor?"ON  ":"OFF ");
    					WriteLEDString(LEDCharBuffer);
    				}
    			}
    			PrevButton = Button;
    		}
    
    		if (PrevKnobCounter != KnobCounter) {
    			printf("Knob count: %d\n",KnobCounter);
    			sprintf(LEDCharBuffer,"%c%3d",
    					ControlBits.ShiftBits.Motor?'*':'_',
    					KnobCounter);
    			WriteLEDString(LEDCharBuffer);
    			PrevKnobCounter = KnobCounter;
    		}
    
    		digitalWrite(PIN_SYNC,LOW);
    
    		MillisThen = MillisNow;
    	}
    }