📄 switches.c
字号:
//++
//switches.c - driver for the rotary encoder and push button ...
//
// Copyright (C) 2005 by Spare Time Gizmos. All rights reserved.
//
// This file is part of the Spare Time Gizmos' MP3 Player firmware.
//
// This firmware is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA.
//
//DESCRIPTION:
// This module implements a simplistic driver for the player's sole user
// interface control - the rotary encoder, which also includes a push button.
// The interface presented to the rest of the code is equally simplistic - the
// encoder has an associated single byte variable which counts from -128..+127.
// Each count represents one click of the encoder, and positive counts are CW
// rotation; negative counts are CCW. The push button also has an associated
// variable which counts the time, in 10ms units, that the button is held down.
// If the button is up, this count will be zero. This counter, rather than a
// simple flag, allows the background code to differentiate a "quick push and
// release" operation from a "push and hold".
//
// The implementation is a simple interrupt driven polling operation driven
// by timer 0 interrupts. Using another, separate, timer for this operation
// is grossly wasteful; we could just as well piggy back off the same timer
// used in the timer.c module, but the 8051's timer 0 is unused and would just
// go to waste if we don't give it something to do. Besides, using a separate
// timer makes the code easier by eliminating an unnecessary dependency between
// this module and the timer. It also makes it easy to adjust the polling
// interval without affecting the timer.
//
// Let's think a minute about the polling interval. The rotary encoder used
// as 16 steps per rotation, and one of the encoder outputs will change with
// each step. A little fooling around with an oscilloscope (I love my Tek!)
// reveals that the shortest pulse I can generate is around 4ms, but remember
// that the other phase changed in the middle of that, so we're talking about
// a sampling interval on the order of 2ms. As a practical matter we could
// probably get by with twice that for real world usage. For the push button
// we want a slower polling rate, something around 10-15ms, so we arrange to
// poll the push button every fourth or eighth (or whatever) interrupt.
//
// Notice that the polled approach gives us switch debouncing for "free".
// If the rotary encoder is only polled, say, every 4ms, then any bounce events
// shorter than that will never be noticed. For an optical encoder this is
// moot (since they don't bounce!), but optical encoders are expensive and the
// much cheaper mechanical encoders certainly do bounce. This also explains
// why we don't want to poll the push button too fast - 2ms, for example, is
// within the bouncing time range for a mechanical push button.
//
//REVISION HISTORY:
// dd-mmm-yy who description
// 9-Jun-05 RLA New file.
// 12-Oct-05 RLA Make SwitchesInterrupt() PUBLIC for SDCC.
//--
// Include files...
#include <stdio.h> // needed so DBGOUT(()) can find printf!
#include "standard.h" // standard types - BYTE, WORD, BOOL, etc
#include "reg66x.h" // declarations for Philips 89C66x processors
#include "player.h" // project wide (hardware configuration) declarations
#include "post.h" // power on self test (POST) failure codes
#include "switches.h" // declarations for this module
// These are the public interface for this module that I promised...
PUBLIC signed char DATA g_nEncoderCount;// number of clicks encoder has been turned
PUBLIC BYTE DATA g_bButtonTime; // time (10 ms units button has been pressed
// Local variables for this module...
PRIVATE BYTE DATA m_bLastEncoderState; // two bits with the last encoder state
PRIVATE BYTE DATA m_bButtonPollDelay; // count down until the button is polled
//++
//--
PUBLIC void SwitchesInterrupt (void) interrupt TIMER0_VECTOR
{
BYTE bCurrentState;
// First, reset and restart the timer...
TH0 = HIBYTE(TIMER0_RELOAD); TL0 = LOBYTE(TIMER0_RELOAD); TR0 = 1;
// Turn the current encoder state into a two bit value (A,B) ...
bCurrentState = ENCODER_A ? 2 : 0;
if (ENCODER_B) bCurrentState |= 1;
// This is basically a giant (well, sixteen entries) table that figures
// out what the encoder has done based on its current state and its last
// known state. There are four possibilities - 1) rotated CCW, 2) rotated CW,
// 3) not changed, or 4) illegal. Since the encoder uses gray code, any legal
// state change will change only 1 bit; any state change that changes two bits
// is illegal and (probably) indicates that we aren't sampling fast enough and
// missed an encoder change.
//
// This may seem like a wasteful implementation, but C51 is smart enough
// to generate a jump table for this case statement, so the actual number of
// instructions that are executed in any one case is fairly small...
switch (((m_bLastEncoderState<<2) | bCurrentState) & 0xF) {
// CCW (counter clockwise) rotation cases...
case 0x1: // 00 -> 01
case 0x7: // 01 -> 11
case 0x8: // 10 -> 00
case 0xE: // 11 -> 10
if (g_nEncoderCount != -128) --g_nEncoderCount; break;
// CW (clockwise) rotation cases...
case 0x2: /* 00 -> 10 */
case 0x4: /* 01 -> 00 */
case 0xB: /* 10 -> 11 */
case 0xD: /* 11 -> 01 */
if (g_nEncoderCount != 127) ++g_nEncoderCount; break;
// No change (i.e. new state == old state) cases...
case 0x0: /* 00 -> 00 */
case 0x5: /* 01 -> 01 */
case 0xA: /* 10 -> 10 */
case 0xF: /* 11 -> 11 */
break;
// And illegal (two bits changed) cases...
case 0x3: /* 00 -> 11 */
case 0x6: /* 01 -> 10 */
case 0x9: /* 10 -> 01 */
case 0xC: /* 11 -> 00 */
break; //POST(PC_ENCODER, FALSE);
}
// And now save the current state for next time around..
m_bLastEncoderState = bCurrentState;
// If we've had enough interrupts, then it's time to sample the push button too ...
if (--m_bButtonPollDelay == 0) {
m_bButtonPollDelay = BUTTON_COUNT;
// If the button is down now, then increment the button down timer up to 255,
// where we stop. Never allow the down timer to roll over from 255 to zero,
// because that makes it look like the button is released! However, if the button
// really is released, then zero the button timer for real. Remember that the
// PUSH_BUTTON input is active low (i.e. 0 -> button pressed).
if (!PUSH_BUTTON) {
if (g_bButtonTime != 255) ++g_bButtonTime;
} else
g_bButtonTime = 0;
}
}
//++
//--
PUBLIC void InitializeSwitches (void)
{
// Initialize the local variables...
m_bButtonPollDelay = BUTTON_COUNT;
g_nEncoderCount = g_bButtonTime = 0;
m_bLastEncoderState = ENCODER_A ? 2 : 0;
if (ENCODER_B) m_bLastEncoderState |= 1;
// All we really need to do to initialize timer 0 is to set the mode, enable
// timer 0 interrupts, and then set the timer 0 flag. This will cause an
// immediate interrupt, which will promptly reload the counter registers...
TMOD = (TMOD & 0xF0) | 0x01; TF0 = 1; ET0 = 1;
DBGOUT(("Encoder and button initialized ...\n"));
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -