Building the first Electro-Mechanical Pedal Steel Guitar

So I installed Brett’s PID library mentioned in this thread. The documentation for the constructor looks like this:

 PID()
Description
Creates a PID controller linked to the specified Input, Output, and Setpoint. The PID algorithm is in parallel form.
Syntax
PID(&Input, &Output, &Setpoint, Kp, Ki, Kd, Direction)
PID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, Direction)
Parameters
Input: The variable we're trying to control (double)
Output: The variable that will be adjusted by the pid (double)
Setpoint: The value we want to Input to maintain (double)
Kp, Ki, Kd: Tuning Parameters. these affect how the pid will change the output. (double>=0)
Direction: Either DIRECT or REVERSE. determines which direction the output will move when faced with a given error. DIRECT is most common.
POn: Either P_ON_E (Default) or P_ON_M. Allows Proportional on Measurement to be specified.
Returns
None

So if I understand correctly, Setpoint is my pedal position, input comes from the encoder, and output goes to the PWM pin for the motor.

Here’s some test code. The first time I upload it, the motor moves briefly in response to the first movement of the pot, but subsequently does nothing. The teensy is correctly showing values from the pot. However, if I move the motor a bit (to make sure the encoder is working) it suddenly moves for a quarter second or so… So basically nothing is happening without a change in encoder values… which makes me wonder if I have the variables assigned wrong.

Edit: on startup, the encoder is moving to a value around 1000…

#include <PID_v1.h>
#include <Encoder.h>

int wiperPin = 14;
int pwmPin = 16;
int motor1pin1 = 17;
int motor1pin2 = 18;
int encoderPin1 = 8;
int encoderPin2 = 9;
int value = 0;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID pid1(&Input, &Output, &Setpoint,2,5,1, P_ON_M, DIRECT);

Encoder enc(encoderPin1,encoderPin2);

void debug(String var, int val) {
  Serial.print(var);
  Serial.print(" = ");
  Serial.println(val);
}

void drive(int vel) {
  //Serial.print("vel = ");
  //Serial.println(vel);
  if (vel > 10) {
    digitalWrite(motor1pin1, HIGH);
    digitalWrite(motor1pin2, LOW);
    analogWrite(pwmPin, vel); //ENA pin
  } 
  else if (vel < -10) {
    digitalWrite(motor1pin1, LOW);
    digitalWrite(motor1pin2, HIGH);
    analogWrite(pwmPin, vel); //ENA pin
  }
  else {
    digitalWrite(motor1pin1, LOW);
    digitalWrite(motor1pin2, LOW); 
  }
}

void setup() {
  // put your setup code here, to run once:
  pinMode(motor1pin1, OUTPUT);
  pinMode(motor1pin2, OUTPUT);
  
  Serial.begin(9600); // Other baud rates can be used...
  Serial.println("encoder test");
  Setpoint = 0;
  pid1.SetOutputLimits(-255,255);
  pid1.SetMode(AUTOMATIC);
}

long encPosition = 0;

void loop() { 
  long newEnc;
  newEnc = enc.read();
  if (newEnc != encPosition) {  
    encPosition = newEnc;
    debug("encoder",encPosition);
  }
  Input = encPosition;
  int wiper;
  wiper = analogRead(wiperPin);
  if (wiper != Setpoint) {
     Setpoint = wiper;
     debug("wiper",Setpoint); //this value is changing 0-1024
  }
  pid1.Compute();
  drive(Output);

Beyond that, I’m trying to get my head around what I need the PID to do actually, never mind the values for tuning it… Here are some thoughts that I don’t yet have any idea how to approach in code…

The idea is to track as closely as possible the movement of the pedal. This movement might be fast, faster than the maximum speed of the motor, so there will be some lag. But sometimes the movement will be a good bit slower than the maximum rate that the motor can change the pitch, and the algorithm should be able to follow the movement more accurately. I’m concerned that if the movement is TOO slow, the motor may struggle to move slowly enough. With narrow PWM values, the motor sure does whine loudly! Doesn’t sound good for the motors… but go too slowly, and the motor doesn’t move at all.

Both end points of the travel will be known positions, (one of which will be the startup position, zero) and I’m wondering if this information could be useful to the PID algorithm, and if so, how that is expressed…

I think I do want P_ON_M, as that is used to address overshoot, and that seems desirable in this situation. Overshoot is not going to be musical at all.

The encoder has at least 10x the accuracy (possibly 100x?) I’ll need, so I want to make sure that it doesn’t oscillate around needlessly if the error is small.

Input will be the encoder-informed absolute position, setpoint will be the desired encoder-informed absolute position, and output will control PWM.

I haven’t read your code yet in detail, so just making sure it’s clear.

The inertia in the motor and screw is low. You want to move as fast as you can. This means that you want a large P term. This screw won’t back-drive, so I would expect a 0 D term. Then make the I term as small as you can and be happy with the resulting sound. Debug that by ear not by Serial.print :grin: Edit: I’d suggest starting with 0 I and D terms and seeing whether you get audible overshoot.

You can also establish a minimum speed at which you try to move. Map the outputs from the algorithm into that range. So if you have a slowest PWM value that works reliably to move, map that to 0. Arduino has a map function you can use to calculate this, or you can use a function if you want a non-linear mapping. Edit: note that map only works on integers; if you need to work in double then it’s just a simple linear equation. In any case, rather than ignoring vel values in the [-10, 10] range you should make it move at the minimum speed when you aren’t at the target. Ignoring low vel values will just make it inaccurate.

The whine isn’t bad for the motor. It is in a range that won’t be useful for your end goal, but you aren’t hurting the motor with experiments in that range.

Doing PWM on a negative number is not meaningful (See the analogWrite() documentation). Take a look at this forum post:

You’ll notice that you need to invert the sign of vel before passing it to analogWrite().

I don’t actually know what happens when you pass a negative number, but if it’s just using bit masks you might get a lot of sign-extended 1 bits in the negative number and go faster than you intend. (See Two's complement - Wikipedia for how this works if you don’t already know.)

okay, so the range of the PID is -255 to 255, but I have to convert that to a combination of motor control and pwm. Okay, I get that. Just invert negative velocity. But the -255 to 255 range does make sense then…

What is a ‘large’ P? 255? or more like 10?

Here’s some code with a P of 5. It is just oscillating constantly, which is better than nothing I guess. Goes from min to max about 4 times a second. At either extreme of the pot, the rate of oscillation doubles.

#include <PID_v1.h>
#include <Encoder.h>

int wiperPin = 20;
int pwmPin = 16;
int motor1pin1 = 17;
int motor1pin2 = 18;
int encoderPin1 = 8;
int encoderPin2 = 9;
int value = 0;
int minSpeed = 30;
int pwmVal = 0;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID pid1(&Input, &Output, &Setpoint,5,0,0,DIRECT);

Encoder enc(encoderPin1,encoderPin2);

void debug(String var, int val) {
  Serial.print(var);
  Serial.print(" = ");
  Serial.println(val);
}

void drive(int vel) {
  // oscillating from min to max constantly...
  if (vel > 0) {
    pwmVal = map(vel,0,255,minSpeed,255);
    digitalWrite(motor1pin1, HIGH);
    digitalWrite(motor1pin2, LOW);
    analogWrite(pwmPin, pwmVal); //ENA pin
  } 
  else if (vel < 0) {
    pwmVal = 0 - map(vel,-255,0,-255,-minSpeed);
    digitalWrite(motor1pin1, LOW);
    digitalWrite(motor1pin2, HIGH);
    analogWrite(pwmPin, pwmVal); //ENA pin
  }
  else {
    digitalWrite(motor1pin1, LOW);
    digitalWrite(motor1pin2, LOW); 
  }
}

void setup() {
  // put your setup code here, to run once:
  pinMode(motor1pin1, OUTPUT);
  pinMode(motor1pin2, OUTPUT);
  
  Serial.begin(9600); // Other baud rates can be used...
  Serial.println("encoder test");
  Setpoint = analogRead(wiperPin);
  pid1.SetOutputLimits(-255,255);
  pid1.SetMode(AUTOMATIC);
}

long encPosition = 0;

void loop() { 
  long newEnc;
  newEnc = enc.read();
  if (newEnc != encPosition) {  
    encPosition = newEnc;
    debug("encoder",encPosition);
  }
  Input = encPosition;
  int wiper;
  wiper = analogRead(wiperPin); 
  if (wiper != Setpoint) {
     Setpoint = wiper;
    // debug("wiper",wiper); //this value is changing 0-1024 
  }
  pid1.Compute();
  drive(Output);
}

so I think I’ve got it set up wrong somehow.

Future thought:

Once we have a good enough prototype, we could help spread the word by setting it up in a music store in Nashville and letting steel players use it to demo new copedents. A single guitar could have a lot of value to a lot of musicians.

well, if someone is interested in manufacturing these, let me know. Not really concerned about spreading the word, I’m just looking to build one for myself :wink:

Why not:

pwmVal =  map(vel, -255, 0, minSpeed, 255);

Does it do this regardless of the wiper setting? Have you tried printing both the encoder and wiper values and comparing what it is doing?

I suggest setting speed 115200 rather than 9600 for debugging.

would that work? Doesn’t it map -255 to the slowest speed?

Boy I already have thousands of entries to sort through in the serial monitor. Do I really want 10x that number?

I have looked at both, but as far as comparing? the wiper moves predictably as I move the pot, the encoder value oscillates continuously regardless, but if P is low enough, it will double the frequency of oscillation when wiper is below about 150 or above about 850. Maybe I could sort through thousands of lines of monitor to tell you those values more precisely, would that help??? If P is above 5, it just goes full tilt all the time. I’m at a bit of a loss to say more… nothing in the printout seems at odds with what the motor or what the pot are doing…

I found the google group for Brett’s PID, I’ll try asking there…

I just failed to finish inverting arguments… I meant

pwmVal =  map(vel, 0, -255, minSpeed, 255);

You get the same number of console messages, just faster; it’s just making it less likely that the system will be waiting for I/O and affecting your results.

Makes sense to ask for PID help from folks with experience with the library! :smiling_face:

Hi @woodslanding! Just wondering how this project is going. :relaxed:

1 Like

Wow, it’s 29 days later!

My work got busy, which is great, but it means I have not had much time to put into this…

I did install the new motor and set up some pots to control PID settings, and 3d printed a bracket to mount it all. I’ve had time to plug it in and ascertain that it does not work. So now I need to go through and check my connections.

I also have learned a great deal of fusion 360, and modeled the mechanism about as far as makes sense until I know how/whether the motor is going to work.

Meantime, I need to do my taxes, which I am putting off by responding to emails, such as the one about your post above :wink:

In short, I have not given up, but progress has definietly slowed! Hoping to get some more time early April, but may not be much happening before then. I will keep you posted!

2 Likes

There are way worse things than work being busy! I was just curious and really enjoyed the thread so far. :grin:

1 Like

Slightly off topic but related.

A 3 string steel guitar made from Harley Davidson motorcycle parts!

2 Likes

Hi Eric, Just wondering how you went with your project. Did you ended up completing it?

It’s pretty backburnered right now. I do still have hope to get back to work on it. Maybe in the fall…

2 Likes

So the reason I asked is because I just completed a Servo controlled Pedal steel guitar. I came across your project a couple of weeks back and realized I’m not the only one with the same idea. I use a ESP32 controller with 10 servo’s. I’m currently using 3 pedals but will have 5 soon as I am upgrading. I will ever only use 5 Pedals as on E9 I use the Pedals as so-called A,B,C, and D, but on C6 mode, I use it as 1, 2, 3, 4 as on the universal chart. Also have Left and Right knee levers. It is setup for E9 but with a flick of a switch it change to C6. I use ESP32 WIFI to communicate between the Pedals and main guitar. Attach some photos.



4 Likes

WOW!!! That is beautiful! I have 100 questions I want to ask. Do you have any video of you playing it? How is the responsiveness? How do those knee levers work? Did you build it from scratch, or modify an existing instrument? How does it work to change copedents?

Would you build one for me? :wink:

Can you post more pics?

That’s absolutely incredible!

Hi Eric. I firstly play Lap Steel on tuning C6. I thought to build another Lap Steel that I can Set to E9. So after I completed it I found you can’t do much with E9 on a Lap steel so I decided to upgrade it to a Pedal Steel.
So I basically done the same as you, started with a solenoid but didn’t work. I then went the servo root. The response is as quick as you operate the Pedal or knee levers. I done the code to work on % range, the servos follow the change in %. One % Pedal travel is equal to one % travel of the servos. I changed my mind about 100 times to get it up to where it work as expected. The way I done it make it easy to modify as you desire.
The way I have it now it set on E9 and flicking a switch change to C6 within a second. Servo accuracy is fairly good. I still fine tuning but only on the code not on the guitar. I use two esp32 modules, one on the Pedals below that communicate through WIFI to the main ESP32 controller in the main frame.
If you go on “STEEL GUITAR BUILDERS” on Facebook you will find more photos and video. The knee levers operate by two Joy Stick potentiometers. (0 to 90% = LL, 90 to 180% = LR and the same principal on the right. But on the right I use another pot as RF lever.
Keep in mind I can’t play E9 Pedal Steel yet. I only started learning to play it 2 weeks ago, but will post a video during the weekend.


The first photo is the Lap steel before I started modifying, the rest is progress until completion.





4 Likes

Nice! LEDs and everything.

How did you figure out what strength servos to use? I guess you just have 1 per string, and then software figures out the copedant.

How does the changer work? It looks like there are stops for max pull? Do you need to tune the string against those? Looking further, looks like stops for minimum pull also?

Trying to imagine how you tune a string that can be lowered or raised… and it looks like you do both with one lever, correct? I looked closely at the bridge, but it’s hard to tell what’s going on there. How do the strings attach to the levers?

You have sure figured out solutions to a lot of issues!! Amazing!

Hi again. Yes I have one servo per string , 10 strings. I tried small servo first which was strong enough but I struggle to control it so I chuck it out and stick with the 20kg servos. The first lot was too slow but the red ones in the guitar do the job.

No I don’t use stops as the servos do the locations, I removed all stops.
Yes I use one lever, servo to lower and raise. The servo are set to a neutral point called (90%). If the potentiometers are at their resting points, also (90%) I tune the strings to E9.
C6 I tune by changing the servo neutral points to use the servos to pull it to their C6 positions.

Each position have a couple of secondary positions which change when you change the values of the potentiometers when you use the Pedals or Knee levers.
Attach a photo of the levers and strings. It only hook on a pin.
I will upload a video just now to “Steel Guitar Builders” forum. In the video I play on C6 and swap to E9 directly. Every body ask for a video so I made a quick one. I cannot upload videos on this forum.

I have a lot of upgrades I intend to do, WIFI between guitar and Amp, WIFI headset, use frequency counter to adjust the strings while playing, I already have the small screen that will show the note I am playing, by using the frequency counter, effects. All possible by using the ESP32 controller.

2 Likes

I see, I was looking at the bridge of the original lap steel. Yeah, those look like changer fingers all right.

Wow, it just seems like you have solved it all.

I am only passingly familiar with the EPS32. I have done some programming on teensy. Is it an arduino-type device?

Does using the servo mean you’ve been able to bypass PID tuning? That’s where I ran out of steam.

Where did you source your servos?

I’ve applied to join the PSG builders forum on facebook. Hope to check out what you have posted there.

1 Like