What is blocking the buttons?

This project lets me plug all the servos from a robot arm into an Adafruit PWM Servo driver. Then I can adjust the angle of a servo, click a button to go to the next one, adjust it, move back to the previous one if necessary, etc until the arm is in a good position. Then I can click through the servos and see the settings on an OLED and record it in a spreadsheet. Later I can use the settings in a function. Repeat for the next robot arm position.

I have this nearly done - the servo angles are stored in an array, the encoder adjusts the servo angle and shows it on the OLED. But there is one thing that's keeping it from being done - the buttons are blocked. I can't switch to another servo.

An earlier version of this code used a potentiometer, and I could flip through the servos, but that wasn't good because the servos always went to the position that the pot was in, so I swapped in the encoder. Somehow doing the encoder blocks the buttons from being recognized.

The pot code is still there but commented out. Any suggestions are appreciated!

/***************************************************
  Potentiometer  or Encoder + Adafruit PWM Servo Driver + OLED + Buttons
 ****************************************************/

#include <Wire.h>

// Encoder library setup
#include <Encoder.h>
Encoder myEnc(2, 3);  // Pins for encoder
long oldPosition  = -999;

// JC Button library
#include <JC_Button.h>
// JC Button pin assignments
const byte
DN_PIN(7),
       UP_PIN(8);  // connect a button from these pins to ground

Button btnUP(UP_PIN), btnDN(DN_PIN);    // define the buttons

const unsigned long
REPEAT_FIRST(500),              // ms required before repeating on long press
             REPEAT_INCR(100);               // repeat interval for long press
const int
MIN_COUNT(0),
          MAX_COUNT(16);


// OLED - U8g2 library
#include <Arduino.h>
#include <U8x8lib.h>

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);

// Adafruit PWM Servo Driver
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  150 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  600 // this is the 'maximum' pulse length count (out of 4096)

// Servos
const int potPin1 = A0;      // potentiometer1 pin
const int totalServos = 16;  // Total channels on Adafruit PWM board
int servoArray[totalServos]; // Array to track servo values
int servonum = 0;            // Select the specific servo in the array
const int neutral = 365;     // Neutral position for servo startup

// variables
int val;
int pulseLength;

///////////////////////
void setup() {
  btnUP.begin();
  btnDN.begin();

  u8x8.begin();
  u8x8.setPowerSave(0);

  pwm.begin();
  pwm.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  for (int servonum = 0; servonum < totalServos; servonum++) {  // Set servo neutral position in array.  
    servoArray[servonum] = neutral;
  }

  for (int servonum = 0; servonum < totalServos; servonum++) { // Copy array value to servos
    pwm.setPWM(servonum, 0, servoArray[servonum]);
  }

  yield();
}

///////////////////////
void loop() {

//  Serial.begin(9600);
  
  static int
  selectedServoPosition,                          // the number that is adjusted
  lastselectedServoPosition(-1);                  // previous value of count (initialized to ensure it's different when the sketch starts)
  static unsigned long
  rpt(REPEAT_FIRST);              // a variable time that is used to drive the repeats for long presses
  enum states_t {WAIT, INCR, DECR};   // states for the state machine
  static states_t STATE;              // current state machine state

  btnUP.read();                   // read the buttons
  btnDN.read();

  if (selectedServoPosition != lastselectedServoPosition)         // print the count if it has changed 
  {
    lastselectedServoPosition = selectedServoPosition;
    Serial.println(selectedServoPosition, DEC);
  }

  switch (STATE)
  {
    case WAIT:                              // wait for a button event
      if (btnUP.wasPressed())
        STATE = INCR;
      else if (btnDN.wasPressed())
        STATE = DECR;
      else if (btnUP.wasReleased())       // reset the long press interval
        rpt = REPEAT_FIRST;
      else if (btnDN.wasReleased())
        rpt = REPEAT_FIRST;
      else if (btnUP.pressedFor(rpt))     // check for long press
      {
        rpt += REPEAT_INCR;             // increment the long press interval
        STATE = INCR;
      }
      else if (btnDN.pressedFor(rpt))
      {
        rpt += REPEAT_INCR;
        STATE = DECR;
      }

      //////////////////

      // Potentiometer code - sends pot value directly to servo, does not store it.  
//      val = analogRead(potPin1);            // reads the value of the potentiometer (value between 0 and 1023)
//      pulseLength = map(val, 0, 1023, SERVOMIN, SERVOMAX);      //map potentiometer to SERVOMIN and SERVOMAX
//      pwm.setPWM(servonum, 0, pulseLength);

      // Encoder code - updates current servo array value and servo
      val = (servoArray[servonum]);         // get current setting for this servo
      long newPosition = myEnc.read();      // read encoder

        if (newPosition > oldPosition) {
          oldPosition = newPosition;
          val = (val + 10);
            if (val > 600){
              (val = 600);
            }
          (servoArray[servonum]) = val;
        } else if (newPosition < oldPosition) {
          oldPosition = newPosition;
          val = (val - 10);
            if (val < 150){
              (val = 150);
            }
          (servoArray[servonum]) = val;
        }
        else {
         // Do nothing
         }
        
      // Send current array value to servo
//      pulseLength = (servoArray[servonum]);  
//      pwm.setPWM(servonum, 0, pulseLength);
     pwm.setPWM(servonum, 0, (servoArray[servonum]));  // See if this is all you need instead of above two lines <<<<<<<<<<<<

      //u8x8.setFont(u8x8_font_courB18_2x3_f);  // This is good, fits single-digit servo
      u8x8.setFont(u8x8_font_inr21_2x4_f);  // Good one!


      // Display selected servo
      u8x8.setCursor(0, 0);
      u8x8.print("Servo:");

      if (servonum < 10) {
        u8x8.setCursor(14, 0);
        u8x8.print(" ");      // Need this to get rid of flickering & stray pixels from numbers over 10
        u8x8.setCursor(12, 0);
        u8x8.print(servonum);
      }
      else {                  // Handle longer numbers, no need to add the space
        u8x8.setCursor(12, 0);
        u8x8.print(servonum);
      }

      // Display Pulse Length of servo position
      u8x8.setCursor(0, 4);
      u8x8.print("Pul:");
      u8x8.setCursor(8, 4);
      //u8x8.print(pulseLength);  // display value of pot
      u8x8.print (servoArray[servonum]);  // read the current setting from the array

      break;

    case INCR:
      ++servonum;                            // increment the counter
      servonum = min(servonum, MAX_COUNT);      // but not more than the specified maximum
      STATE = WAIT;
      break;

    case DECR:
      --servonum;                            // decrement the counter
      servonum = max(servonum, MIN_COUNT);      // but not less than the specified minimum
      STATE = WAIT;
      break;
  }
}

Hi, @Jeff_Haas
Can you please include a circuit diagram of your project?

Thanks.. Tom.... :smiley: :+1: :coffee: :australia:

OK, I'll have to get to Fritzing and do that. For now, here are the connections:

Arduino Duelaminove
Adafruit PWM Servo Driver: 5V, GND, A4, A5 (i2c)
OLED: 5V, GND, A4, A5 (i2c)
Encoder: 2, 3, GND
Buttons: 7, 8, GND
Pot: A0 (just for testing against old verison, uncomment things in the code to see that behavior)

Servos are on slots 0 - 2 on the PWM board. It has its own 5V power supply.

Thanks!

Hi,

No... If you use the Fritzy diagram that uses images of components, please forget it.

If you have no diagram, you would be best to reverse engineer your project and draw it with pen(cil) and paper.

Tom.... :smiley: :+1: :coffee: :australia:

I second that. Fritzing-pictures are hard to analyse.

something handdrawn like this
grafik

using this principle

is very easy to analyse
inspite to something like this
cool looking joystick!

but how is it wired together???

2 Likes

Ok, here's a schematic.

I fudged a bit on the rotary encoder and the OLED. The OLED and the PWM board are both on the i2c bus with different addresses.

Not shown is the potentiometer, which can be connected to 5V, GND and A0 to test the other variation of the circuit.

2 Likes

You are writing off the end of the servo array when you get to servonum == 16. It only has 16 entries: 0 through 15.

Good catch! Classic late-night coding error on my part. I don't have 16 servos plugged into the PWM board so I never toggled the count that high with the pot attached.

That doesn't fix the issue, but in testing today I got a better description of the bug...

At boot, I can move the first servo's angle. Then if I click a button, the program freezes.

Found it! What's that expression, "The answer is in the question."? I realized that what might be blocking was one of the libraries. I looked at other implementations of rotary encoders and buttons, and this one helped me:

He's using Nick Gammon's non-blocking code for the buttons. I took out the button library and swapped in Nick's code, and it worked.

Nick's page on this is really well-written:
http://www.gammon.com.au/switches

Here's the final code. To anyone reading in the future, this code allows you to set all positions of a robot arm. Hook the servos up to the PWM board, then adjust the first one, click the NEXT button to go to the next servo, adjust it, and so on. You can use the PREV button to go back and tweak an earlier one. When you're done, you can flip through all the PWM settings and record them for later use in a function. Note that these numbers are the PWM numbers for the servos, not the angle from 0 - 180. See the Adafruit docs for details on controlling this many servos with the board.

/***************************************************
  Encoder + Buttons + Adafruit PWM Servo Driver + OLED 

  Connections:
  Adafruit PWM - 5V, GND, SDA and SCL pins
  OLED - 5V, GND, SDA and SCL pins
  Encoder - 2, 3, GND
  Buttons - 6, 7, GND

 ****************************************************/

#include <Wire.h>

// Encoder library setup
#include <Encoder.h>
Encoder myEnc(2, 3);  // Pins for encoder
long oldPosition  = -999;

// Number of servos
const int
MIN_COUNT(0),
          MAX_COUNT(15);

// Nick Gammon button handling
// Button reading, including debounce without delay function declarations
//NEXT Button
const byte NEXT_button = 7; // this is the Arduino pin we are connecting the push button to
byte NextOldButtonState = HIGH;  // assume switch open because of pull-up resistor
boolean NextButtonPressed = 0; // a flag variable

//PREV button
const byte PREV_button = 6; // this is the Arduino pin we are connecting the push button to
byte PrevOldButtonState = HIGH;  // assume switch open because of pull-up resistor
boolean PrevButtonPressed = 0; // a flag variable

// Button debounce
const unsigned long debounceTime = 10;  // milliseconds
unsigned long buttonPressTime;  // when the switch last changed state

// OLED - U8g2 library
#include <Arduino.h>
#include <U8x8lib.h>

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);

// Adafruit PWM Servo Driver
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  150 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  600 // this is the 'maximum' pulse length count (out of 4096)

// Servos
//const int potPin1 = A0;      // potentiometer1 pin
const int totalServos = 16;  // Total channels on Adafruit PWM board
int servoArray[totalServos]; // Array to track servo values
int servonum = 0;            // Select the specific servo in the array
const int neutral = 365;     // Neutral position for servo startup

// variables
int val;
//int pulseLength;

///////////////////////
void setup() {
  pinMode (PREV_button, INPUT_PULLUP); // setup the button pin
  pinMode (NEXT_button, INPUT_PULLUP); // setup the button pin

  u8x8.begin();
  u8x8.setPowerSave(0);

  pwm.begin();
  pwm.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  for (int servonum = 0; servonum < totalServos; servonum++) {  // Set servo neutral position in array.  
    servoArray[servonum] = neutral;
  }

  for (int servonum = 0; servonum < totalServos; servonum++) { // Copy array value to servos
    pwm.setPWM(servonum, 0, servoArray[servonum]);
  }

  yield();
}

///////////////////////
void loop() {

//  Serial.begin(9600);
  
  static int
  selectedServoPosition,                          // the number that is adjusted
  lastselectedServoPosition(-1);                  // previous value of count (initialized to ensure it's different when the sketch starts)

  // Button reading with non-delay() debounce - thank you Nick Gammon!
  byte NextButtonState = digitalRead (NEXT_button);
  if (NextButtonState != NextOldButtonState) {
    if (millis () - buttonPressTime >= debounceTime) { // debounce
      buttonPressTime = millis ();  // when we closed the switch
      NextOldButtonState =  NextButtonState;  // remember for next time
      if (NextButtonState == LOW) {
        Serial.println ("Button closed"); // DEBUGGING: print that button has been closed
        NextButtonPressed = 1;
      }
      else {
        //Serial.println ("Button opened"); // DEBUGGING: print that button has been opened
        NextButtonPressed = 0;
      }
    }  // end if debounce time up
  } // end of state change

   if (NextButtonPressed) {
      ++servonum;                            // increment the counter
      servonum = min(servonum, MAX_COUNT);      // but not more than the specified maximum
      NextButtonPressed = 0; // reset the button status so one press results in one action
   }
   

  byte PrevButtonState = digitalRead (PREV_button);
  if (PrevButtonState != PrevOldButtonState) {
    if (millis () - buttonPressTime >= debounceTime) { // debounce
      buttonPressTime = millis ();  // when we closed the switch
      PrevOldButtonState =  PrevButtonState;  // remember for next time
      if (PrevButtonState == LOW) {
        Serial.println ("Button closed"); // DEBUGGING: print that button has been closed
        PrevButtonPressed = 1;
      }
      else {
        //Serial.println ("Button opened"); // DEBUGGING: print that button has been opened
        PrevButtonPressed = 0;
      }
    }  // end if debounce time up
  } // end of state change

   if (PrevButtonPressed) {
      --servonum;                               // decrement the counter
      servonum = max(servonum, MIN_COUNT);      // but not more than the specified maximum
      PrevButtonPressed = 0; // reset the button status so one press results in one action
   }

      //////////////////

      // Potentiometer code - sends pot value directly to servo, does not store it.  
//      val = analogRead(potPin1);            // reads the value of the potentiometer (value between 0 and 1023)
//      pulseLength = map(val, 0, 1023, SERVOMIN, SERVOMAX);      //map potentiometer to SERVOMIN and SERVOMAX
//      pwm.setPWM(servonum, 0, pulseLength);

      // Encoder code - updates current servo array value and servo
      val = (servoArray[servonum]);         // get current setting for this servo
      long newPosition = myEnc.read();      // read encoder

        if (newPosition > oldPosition) {
          oldPosition = newPosition;
          val = (val + 10);
            if (val > 600){
              (val = 600);
            }
          (servoArray[servonum]) = val;
        } else if (newPosition < oldPosition) {
          oldPosition = newPosition;
          val = (val - 10);
            if (val < 150){
              (val = 150);
            }
          (servoArray[servonum]) = val;
        }
        else {
         // Do nothing
         }
        
      // Send current array value to servo
     pwm.setPWM(servonum, 0, (servoArray[servonum]));  

      //u8x8.setFont(u8x8_font_courB18_2x3_f);  // This is good, fits single-digit servo.  68% memory filled.
      u8x8.setFont(u8x8_font_inr21_2x4_f);  // Good one, but gets to 80% memory filled.


      // Display selected servo
      u8x8.setCursor(0, 0);
      u8x8.print("Servo:");

      if (servonum < 10) {
        u8x8.setCursor(14, 0);
        u8x8.print(" ");      // Need this to get rid of flickering & stray pixels from numbers over 10
        u8x8.setCursor(12, 0);
        u8x8.print(servonum);
      }
      else {                  // Handle longer numbers, no need to add the space
        u8x8.setCursor(12, 0);
        u8x8.print(servonum);
      }

      // Display Pulse Length of servo position
      u8x8.setCursor(0, 4);
      u8x8.print("Pul:");
      u8x8.setCursor(8, 4);
      //u8x8.print(pulseLength);  // Used with pot
      u8x8.print (servoArray[servonum]);  // read the current setting from the array  
}

Not at all!

We all know that a bus is a bus, dunno how not “fudg[ing]” that would even look.

You don’t show the connection of 5 volts to the Arduino, and you don’t say where the 5 volts is coming from, small details.

a7

Sorry! The Arduino has its usual power supply. The PWM board has a different one, which is bigger, to power the servos. I noticed one mistake - the VCC on the servos goes to the 5V supplied by the PWM board, so they do not draw that from the Arduino (which would brown out with that kind of load). The servos plug directly into that board with a servo cable, which makes this really easy to set up.

What I meant by "fudging" was that I couldn't find schematic symbols for the OLED and the rotary encoder, so I just drew pictures of what they really look like.

1 Like

Perhaps connecting a 3.3V display to a 5V bus, would qualify.

Hi,

What you have done is perfectly valid, you have labelled the relative components and included the wiring connection names, just need to name the pins connections on the encoder.

Tom... :smiley: :+1: :coffee: :australia:

Funny thing about the encoder library...it doesn't care about the order of the outer pins. If you don't like the way the dial turns, just swap them. My encoder is by Grayhill and labels the pins A C B. Center is GND (or common).

Exchanging them makes the encoder go backwards as you observed, so it’s kinda like it does care. :expressionless:

If you look at the innards of the encoder you would see that the two channels are identical, just out of phase which is what makes it work at all.

Similar you might say a potentiometer doesn’t care. Again, swapping the outer pins makes the pot go backwards, so while it might not care it does matter.

a7

Yes, of course. But this isn't SDA and SCL, where if you get them backwards the device won't work at all! I assume someone putting this together will hook it all up, turn the knob, decide they want it to go the other way, and then swap the connections. It will depend on the orientation of the servos and how they're mounted.

To use this properly, you should understand servos, the Adafruit PWM board (How does it work differently than the regular servo commands? Why do you want to use it?), the OLED (What if you want a different font? What if you want to swap in an LCD?), and then the buttons and a rotary encoder. Someone doing that is likely building a robot, and knows enough to read the library docs. A bit more than making their first LEDs blink.