Translate

Saturday, May 23, 2020

Old PSTN entry phone - door opening - #3

In this article I will discuss  about how to design a circuit capable to "read" the rotary dial and relative code.

The circuit

I decided to start with a Trinket as system to use.
Is cheap, small, powerful enough for the task, easy programmable Arduino-style, so a quick prototyping can be easily done in this way.

The idea is to connect thru some pull up the rotary dial.
We could use both switches for accuracy and speed, but it can works also with only the pulse switch (Switch 2).
However, I prefer to use both switches for a better and safe decoding.

Let see a potential schematic :


To debug (and fun) I used a Neopixel bar 8 LED from Adafruit to show some feedback from the dial reading and later a Neopixel round 16 LED, also from Adafruit.
On the final version the bar led will not be present.
A first fast prototype was done on a breadboard.


As is possible to see, the system is powered by the USB port of the Trinket rather than an external source.
In the final prototype a DC/DC converter will be used.

The software


Definitively a trivial code.
To test the system the first draft of the code simply read the rotary dial and lit up the Neopixel bar with the read number (only from 1 to 8 of course in the 8 LED bar, full number in the 16 LED round).
The core of the reading is a state machine and because the involved timing (very slow stuff) I did not use any interrupt capability.

Let see a flow of the states :


Let see briefly this state machine.
  • Start State
    When the code start the state machine is in the Start state.
    The start state simply initialize the system and then goes in the Idle state.
  • Idle State
    In this state the code is waiting for the signalling switch to go low indicating the starting of the cycle.
    A timer is set up to detect when the user terminated to compose the numbers and then change state going on the Count state.
  • Count State
    In this state we read both switches.
    The signalling one (SW1) to see when the digit is completed and the pulse switch to increase a counter on every rising of the signal.
    Very important to remember to add some bouncing code when reading the pulse switch since is very easy to have noise because the mechanical nature of the switch.
    When the signaling switch goes back high it means the dial finished the rotation so we change state and go in the Check state.
  • Check State
    Here we save the read number and wait to see if the timer for the number is expired.
    If expired we force the End state, if not we check the signalling switch to see if another number is coming. In that case we are doing like for Idle, restart the timer, force the counter to zero and go in Count to acquire another digit.
  • End State
    In the End state we "use" the acquired number and then restart from Start.

Here a quick video showing the reading of a single digit and another here (It uses a round 16 LED neopixel for better see the numbers).
The Neopixel bar is set all green in the Start state.
When in Count state become red, indicating the acquisition phase.
When in Check the bar is showing for 1 second the read digit and then forced back to Start  (is just a quick test to determine how accurate is the reading).

So far I have to say the reading is precise.
A fun modification was to use a round Neopixel instead the bar in order to show better the result.

The next step is to build the real prototype but in order to do so I need more information about the environment.
Specifically I need to know what type of power supply to use to power up the circuit.
I have different DC/DC converter so is important to pick up the correct one.

Since the code is still small, I copy it here.
Will be placed somewhere in the final form (Bitbucket probably)

// Rotary Dial by TheFwGuy
// The program use a Neopixel strip as feedback about reading a rotary dial

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

// Pinout
#define LED_PIN    1
#define SW1        0    // Signalling switch
#define SW2        2    // Pulse dial switch

// State machine to read rotary dial
#define RD_START  0
#define RD_IDLE   1
#define RD_COUNT  2
#define RD_CHECK  3
#define RD_END    4


// Number of LED to drive
#define LED_COUNT 16

// Global variables 

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)

char rd_status;       // Rotary dial reading state machine 
char active_number;   // Number being read       
char number[10];       // Array to store read numbers (max 10 digits)


// System Initialization

void setup() {
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
// Needed fro Trinket 5V
  clock_prescale_set(clock_div_1);
#endif

  rd_status = RD_START;   // Initialize Rotary Dial state machine

  pinMode(SW1,INPUT);
  pinMode(SW2,INPUT);
  
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(15); // Set BRIGHTNESS (max = 255)

}


// Main loop function 

void loop() {
  rd_state_machine();
  
}

// Rotary Dial State machine
void rd_state_machine () {
  switch (rd_status) {
    case RD_START:
    default:
      active_number = 0;
      strcpy(number, "-");
      colorWipe(strip.Color(  0, 255,   0), 10); // Green
      rd_status = RD_IDLE;
      break;

    case RD_IDLE:
      if (digitalRead(SW1) == LOW) {
        colorWipe(strip.Color(255,   0,   0), 5); // Red
        // Set timer
        rd_status = RD_COUNT;
      }
      break;  

    case RD_COUNT:
      if (digitalRead(SW1) == LOW) {
        
        if (readSwitch(SW2) ) { 
          active_number++;
          while (readSwitch(SW2));   // Wait the pulse to go down
          colorNumber(strip.Color(  0, 0, 255), 0, active_number);        }
      } else {
        // Number finished
        colorWipe(strip.Color(  0, 0, 0), 0); // turn off display
        colorNumber(strip.Color(  0, 0, 255), 0, active_number);
        rd_status = RD_CHECK;
      }
      break;

      case RD_CHECK:
        // TBD - the state machine ends here for this specific demo with neopixel
        delay(1000);
        rd_status = RD_START;
        break;
   }  
}

// The function read a button with some debouncing - return true if the pin goes high.
// returns false otherwise
boolean readSwitch(int pin)
{
   if(digitalRead(pin))
   {
      delay(1);
      if(digitalRead(pin))
      {
         return true;
      }
   }
   return false;
}

// Neopixel functions - derived from Adafruit example

// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

// Display a number (from 0 to 8)
void colorNumber(uint32_t color, int wait, char number) {
  for(int i=0; i<number; i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}



No comments:

Post a Comment