3

I'm kind of new to Arduino and C, and I was having trouble with this sketch. I'm working on a project to control 6 servos using virtual buttons. When I hold down the button, the servo moves forward smoothly to a certain fixed point, and when I release it, it comes back to a fixed point. I initially tested this with a single servo and an actual button on my breadboard using the following sketch.

#include<Servo.h>
int switch1;
float switch1Smoothed;
float switch1Prev;
Servo myservo1;
float a=1200.0; //lower angle limit

void setup() {

  Serial.begin(115200);
  pinMode(A1,INPUT_PULLUP);
  myservo1.attach(9);
  myservo1.writeMicroseconds(a);
  switch1Prev=a;
}

void loop() {
//Motor 1
  switch1 = digitalRead(A1);// read switch | will return 1 or 0 continuously
  //Serial.println(switch1);
  switch1 = switch1 * 2400;        // multiply by angle limit

  // *** smoothing ***

  switch1Smoothed = (switch1 * 0.05) + (switch1Prev * 0.95);
  if(switch1Smoothed>=a){
  switch1Prev = switch1Smoothed;
  }
  else{
    switch1Prev = a;
  }
  // *** end of smoothing ***

//  Serial.print(switch1);                  // print to serial terminal/plotter
//  Serial.print(" , ");   
//  Serial.println(switch1Smoothed);
    myservo1.writeMicroseconds(switch1Smoothed) ;
    delay(10);                               // repeat loop 100 times per second
}

This works out just fine. Now, I'm working with NodeRed, a low code JavaScript flow creator, which is where my virtual button is, and I've set it up to send 0s or 1s as strings based on whether the button is pressed or not via serial communication. This is the config of the Serial Node on NodeRed:

Serial Port Config on NodeRed

And I wrote a short function on Arduino to parse the incoming string from the serial connection, convert it back to an integer, and then use that as my "button" input. However, it seems that either my logic is flawed, or my code is because my servo does not behave the same way it does with the physical button.

Here's the modified sketch:

#include <Servo.h> 
int switch1;
float switch1Smoothed;
float switch1Prev;
Servo myservo1;
float a=1200.0;
String readString, mot1, mot2;

void setup() {
  Serial.begin(9600);
  myservo1.attach(9);
  myservo1.writeMicroseconds(a);
  switch1Prev=a;
}

void captureChar(int& n1){ //using reference to pass the value

  while (Serial.available()) {
    delay(3);  //delay to allow buffer to fill 
    if (Serial.available() >0) {
      char c = Serial.read();  //gets one byte from serial buffer
      readString += c; //makes the string readString
    } 
  }

  if (readString.length() >0) {

      
    
      mot1 = readString.substring(0,1); 
      mot2 = readString.substring(1,2); 
      n1 = mot1.toInt();
      //n2 = mot2.toInt();
    readString="";
    
  } 
}

void loop(){
  captureChar(switch1);
  switch1 = switch1 * 2400;        // multiply by maximum angle limit

  // *** smoothing ***

  switch1Smoothed = (switch1 * 0.05) + (switch1Prev * 0.95);
  if(switch1Smoothed>=a){
  switch1Prev = switch1Smoothed;
  }
  else{
    switch1Prev = a;
  }
  // *** end of smoothing ***

//  Serial.print(switch1);                  // print to serial terminal/plotter
//  Serial.print(" , ");   
//  Serial.println(switch1Smoothed);
  myservo1.writeMicroseconds(switch1Smoothed);
  delay(15);
}

When I load this sketch and hold down the virtual button, i.e., send a continuous stream of string "1", the motor simply goes back and forth really fast, rather than slowly moving to the set maximum position and stopping there while the button is held.

2
  • first thing, insert a layer of separation between the button press and the servo activation ... a button press sets a buttonPressed flag and releasing a button sets a buttonReleased flag ... no other action is done ... the servo motion part of the program moves the servo only in response to the two flags ... it does not respond to anything else ... when that works correctly then add code that sets the flags in response to the data received from serial
    – jsotola
    Mar 18, 2022 at 19:22
  • I will try this and get back to you ASAP. If I'm being completely honest, I'm way out of my depth here, and I appreciate any feedback. It took me a week just to come up with what I have now.
    – rayank97
    Mar 18, 2022 at 23:35

1 Answer 1

3

There may be a problem with your smoothing logic, which is quite convoluted. However, if the first code works fine, you can keep it almost as-is, and only replace digitalRead() with a version that reads the virtual button. It looks like that is what captureChar() attempts to achieve. This function, however, has a couple of problems:

  • it has a loop and a delay, thus breaking the timing of the original code
  • its interface is very different from the one of digitalRead(), and thus cannot be used as a drop-in replacement.

Here is what I would propose as a digitalRead() replacement. It has the same signature as the original digitalRead(), and can be used as a drop-in replacement:

int virtual_switch_reading = LOW;

int virtualDigitalRead(uint8_t /* pin number ignored */) {
    int c = Serial.read();
    if (c == '0')
        virtual_switch_reading = LOW;
    else if (c == '1')
        virtual_switch_reading = HIGH;
    return virtual_switch_reading;
}

Note that calling Serial.available() is not useful: if no data is available, Serial.read() returns -1, which is neither equal to '0' nor equal to '1'.


Edit: addressing the requirement to have six virtual switches.

As stated in a comment, I suggest you encode the states of all the switches, within NodeRed, as a single byte, with one bit per switch. You would then send this byte to the Arduino, either periodically or whenever any switch changes state.

On the Arduino, in order to simulate digitalRead() on a virtual switch, you first have to map the pin numbers to the virtual switches:

// Map a pin number to a virtual switch.
const uint8_t virtual_switch_masks[NUM_DIGITAL_PINS] = {
    0,     // pin 0: not virtual
    0,     // pin 1: not virtual
    1<<0,  // pin 2: virtual switch 0
    1<<1,  // pin 3: virtual switch 1
    // etc...
};

Note that this table doesn't store switch numbers. It instead stores bit masks. For example, virtual switch 1 is stored as 1<<1 = 0b00000010, i.e. all bits are clear except for bit number 1 (rightmost being zero), which is set.

You can then simulate digitalRead() by extracting the relevant bit from the byte you last received from NodeRed, which is done by a bitwise and with the relevant mask:

// States of all virtual switches, one bit per switch.
uint8_t virtual_switch_states = 0;

int virtualDigitalRead(uint8_t pin) {
    uint8_t mask = virtual_switch_masks[pin];

    // If this is not a virtual switch, do a regular digitalRead().
    if (!mask)
        return digitalRead(pin);

    // For a virtual switch, extract the relevant bit
    // from virtual_switch_states.
    if (virtual_switch_states & mask)
        return HIGH;
    else
        return LOW;
}

In order to update the states of the virtual switches from the data received, you just have to add this somewhere within loop():

if (Serial.available())
    virtual_switch_states = Serial.read();
6
  • This would work if I were using a single motor only though, right? I plan to use 6 motors, and I was considering sending a string of the form "000000" with each character representing the state of 1 of 6 motors (I would basically have 6 virtual switches on NodeRed)
    – rayank97
    Mar 18, 2022 at 23:26
  • Also, regarding the smoothing, I do agree it's not very refined, but it's the easiest bit of code I could come up with and felt that would be sufficient for the purposes of my project. However, I'm definitely open to suggestions or ideas on improving it, and more importantly, help with maybe improving the virtual button part.
    – rayank97
    Mar 18, 2022 at 23:32
  • 1
    @rayank97: A string like "000000" wouldn't work very well, as you cannot know where one string ends and the next one begins. You would have to add some sort of delimiter. It would be simpler to send a single byte, where the six switch positions are encoded as individual bits. Mar 19, 2022 at 0:11
  • @rayank97: Regarding the smoothing: you can simplify it by removing switch1Prev and using switch1Smoothed instead. Also, if you set switch1 = 1200 + 1200*digitalRead(A1);, then switch1Smoothed will always be between 1200 and 2400: no need to explicitly clamp it. Mar 19, 2022 at 10:44
  • 1
    @rayank97: There are no separate loops. virtualDigitalRead() would be O(1) even with six switches. See amended answer. Mar 19, 2022 at 23:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.