Config file for driving a motor with encoder

Hello,
I saw the HeroTech lightsaber and was inspired to improve it with a stronger motor (625RPM, 12V, 4.9A stall current) that has a quadrature encoder attached for PID extension/retraction. I also plan to use 2 LED strips instead of one to cover four sides of the blade. Lastly, I am hoping to run everything off the Proffieboard instead of using a separate controller (Xiao RP2040) like they did.

I have attached a picture of my wiring diagram. Essentially the Proffieboard is controlling an H-bridge motor controller, two buttons, a speaker, and is receiving encoder data. The LED strips are NOT controlled by the Proffie and are instead directly connected to the battery.

I have a few questions as I am very confused about how the Proffie works especially regarding how the config file is formatted:

  1. First, is it possible to drive all of this from the Proffie?
  2. How would I create my config file for the motor and encoder? I’ve read that they would be blades?
  3. Does the code for the PID and button logic go into the ProffieOS.ino file in the setup/loop and/or how do I run custom code?
  4. The only sounds I want are the blade ignition (when the extend button is pressed), blade retraction (when the retract button is pressed), and swinging sounds. How do code when these sounds are played?

Thank you in advance!

Electrical Diagram V2_3

  1. Probably
  2. ProffieOS does not currently have support for H-bridge motors. There is some rotary encoder support, but because it’s all software it might not work well if the motor is spinning quickly. (I think the chip can read quadrature encoders in hardware, but support would need to be added in the code, and I don’t know which pins are capable of doing that offhand…)
  3. That is one way to do it. Another way would be to write a class, and inherit from Looper(), which has Setup() and Loop() methods that work similarly. Note that ProffieOS needs most/all code to run in async mode, so if you call delay() or wait for things to happen, you will block other things that ProffieOS needs to do. The class can be written and instantiated in the config file, in just about any of the sections, I would probably use CONFIG_BOTTOM. Example of how this could look below…
  4. It seems easier to just leave the sound system alone, then all you need to do is to change the sound files to make it sound the way you want.

Example of putting custom code in a config file:

#ifdef CONFIG_BOTTOM
class BladeExtender : public Looper, public RotaryReceiver  {
 public:
  const char* name() override { return "BladeExtender"; }
  void Setup() override {
     pinMode(9, MODE_OUTPUT);
     pinMode(10, MODE_OUTPUT);
  }
  void Loop() override {
      // Do PID stuff based on position_
  }
  // RotaryReceiver implementation
  void Update(int delta) override {
    position_ += delta;
  }
private:
  int position_ = 0;
};

BladeExtender blade_extender;
Rotary<blade2Pin, blade3Pin, 1> rotary(&blade_extender);

#endif

Note that Loop() might not be called often enough for this to work well, in which case you would need to do something interrupt-driven, or use a timer to drive the H-bridge. Maybe you can use analogWrite & analogWriteFrequency?

Thanks for the information. I’ll have to try if it works, if not I’ll use a separate microcontroller.

I assume the H-bridge works like a stepper driver, with one high-low for each step, and one wire is left and the other is right? (Maybe it is in fact a stepper driver?)

There is a truth table halfway down the page that shows how to control speed and direction with a PWM signal.

As for the encoder I believe the pins have to be interupt capable. I would use the Encoder.h Arduino library to read values.

The Rotary class already uses interrupts, not sure if Encode.h would be any better or worse. I think all GPIO pins on the proffieboard are interrupt capable.
The motor driver just needs pwm, so analogWrite() should work just fine.
I think this should work. If you have trouble with it, let me know.

Sounds good. How would I create the config file though? Would I use a SimpleBladePtr in blades for both the motor and encoder, and if so what would the arguments be for that. I see there are 8 total arguments.

I think I’m a bit confused as to what the config file is actually doing. Sorry if they are ignorant questions.

The config file customizes ProffieOS for a particular saber, or some other prop. This makes it easy to upgrade ProffieOS, you just copy the config file over to the new ProffieOS, upload and you’re done.

More information about how the config file actually works can be found here:

Ultimately, it’s just included several times with different defines…

For your purposes, just start with the simplest possible file you can generate here:

https://fredrik.hubbe.net/lightsaber/v6/configurator.html

This will have neopixel output on data1, but just ignore that for now and leave data1 alone. Later we can optimize this away, or figure out a way for ProffieOS to control the lights for clash and flicker effects.

Thanks for your time. I’ll probably end up using a separate microcontroller as I believe I don’t have enough programming experience to adapt the Proffieboard given the limited documentation for running a motor instead of an LED.

Do you have the code that you would run on a separate processor already?
If so, could you post a link or something, I might be able to rewrite it for a Proffie for you.

Saber Code.txt (3.2 KB)

This was quickly made but it should work. Sound capabilities are not added.

If rewriting it for a Proffie takes too much of your time, seriously don’t worry about it. I can definitely code the essential controls/sounds on another microcontroller if needed. I’m a MechE student whose strength is CAD and I’m doing this project for fun to learn electrical/programming.

Ok, here is a first draft.
It’s completely untested so far, but it should be close to something that works:

#ifdef CONFIG_BOTTOM

#define CPR 230.7           // Counts per revolution of motor encoder                                                                                                                                                                                          
#define REVOLUTIONS 5       // Number of revolutions for max blade extension                                                                                                                                                                                   
#define DEADZONE 10         // Stops PID loop if error is within this many encoder ticks                                                                                                                                                                       
#define PWM_MAX 255         // PWM range                                                                                                                                                                                                                       
#define P 1.0
#define I 0.01
#define D 0.1
#define DEBOUNCE_DELAY 50   // Debounce delay in ms                                                                                                                                                                                                            
#define PID_LOOP_TIME 10    // PID loop time in ms                                                                                                                                                                                                             

#define ENCODER_CH_A data2Pin
#define ENCODER_CH_B data3Pin
#define MOTOR_IN1 8
#define MOTOR_IN2 9

class BladeExtender : public Looper, public RotaryReceiver  {
public:
  const char* name() override { return "BladeExtender"; }
  void Setup() override {
    pinMode(MOTOR_IN1, MODE_OUTPUT);
    pinMode(MOTOR_IN2, MODE_OUTPUT);
  }
  void Loop() override {
    float targetCount = SaberBase::IsOn() ? 0 : REVOLUTIONS * CPR;

    // PID Loop                                                                                                                                                                                                                                                
    uint32_t currentTime = millis();
    uint32_t elapsedTime = currentTime - lastTime;

    if (elapsedTime >= PID_LOOP_TIME) {
      error = targetCounts - position_;
      float output = 0.0;
      if (abs(error) > DEADZONE) {
        integral += error * (elapsedTime / 1000.0);
        derivative = (error - previousError) / (elapsedTime / 1000.0);
        output = P * error + I * integral + D * derivative;
      }
      analogWrite(MOTOR_IN1, clamp(output, 0, PWM_MAX));
      analogWrite(MOTOR_IN2, clamp(-output, 0, PWM_MAX));

      previousError = error;
      lastTime = currentTime;
    }
  }
  // RotaryReceiver implementation                                                                                                                                                                                                                             
  void Update(int delta) override {
    position_ += delta;
  }
private:
  float itegral = 0.0;
  float previousError = 0.0;
  uint32_t lastTime;
  int position_ = 0;
};

BladeExtender blade_extender;
Rotary<ENCODER_CH_A, ENCODER_CH_B, 1> rotary(&blade_extender);

#endif

If you stick this at the bottom of a config file (and I didn’t create too many bugs…) then it should control the motor, while ProffieOS is also doing all the other things that it likes to do…

It’s basically the code you provided, slightly ProffieOS-ified.
I replaced the button stuff you had with a check to see if ProffieOS thinks the saber is on, and made the analogWrite calculations a bit simpler, but they do the same thing.

I’m a Software Engineer who know enough EE to fake it. I’ve been writing software for ~40 years, this stuff pretty much writes itself to me. I like it when people use the stuff I make, so of course I want you to use a Proffieboard if that will work for you. :slight_smile:

Changing the analogWrite to the clamp() with the + or - output is really cool coding.

For now, TeensySF, saber.h, and no background track is fine for now. I am assuming the rest of the ProffieOS would handle the button logic/events for the “blade/motor” poweron/off as well as the ignition/retraction/swing/other sounds.

With this current config file test_config.h (4.1 KB), if the motor is being controlled in CONFIG_BOTTOM, would I not need any blades? i.e. would NUM_BLADES be 0? How would I setup CONFIG_PRESETS to implement this?

NUM_BLADES can be zero
I didn’t originally recommend it, because it is possible that there is some code somewhere that will not work properly. You still need an entry i the blades array, but no styles, it would look like this:

#ifdef CONFIG_PRESETS
Preset presets[] = {
   { "TeensySF", "", "Preset 1"},
};
BladeConfig blades[] = {
  { 0, CONFIGARRAY(presets) }
};
#endif

You also have some line breaks in your comments that will need to be fixed before it will compile.

Sorry to barge-in, what are line breaks ?

Thanks. Hopefully all the code still runs. Everything is in the mail right now so I’ll update later

Hitting enter/return at the end of a line.

// this will be fine if
// you comment the second line as well

/* so will this because
the comment block continues until 
it's closed with asterisk slash */

// this comment has a line break here
and this part will cause a compile error.
1 Like