Mill Z axis motor drive, PWM and ChatGPT

Mill Z axis motor drive, PWM and ChatGPT

Home Forums Electronics in the Workshop Mill Z axis motor drive, PWM and ChatGPT

Viewing 17 posts - 1 through 17 (of 17 total)
  • Author
    Posts
  • #829761
    Joseph Noci 1
    Participant
      @josephnoci1

      About 8 years ago I made up a discrete, analogue based PWM controller for a DC motor I fitted to the Z axis of my Mill. Based on a LM3524 Power Supply PWM controller, its output was single ended which meant the motor wires sat between power supply +ve and the collector of the PWm switching transistor. This meant that for the motor to reverse, the wires had to be swapped. So it ended up an electromechanical solution – cams, microswitches, relays, etc…It worked well, although I did replace one microswitch in those 8 years.

      The drive uses another small DC motor as a TachoGen for clean smooth low speed control with lots of torque.

      This was the circuit:

       

      3524_Controller

       

      And the Old PCB’s with relays..

       

      Old PWM

       

       

      Meantime, I had been playing with ChatGPT on other subjects, and that same microswitch now caused me to smack an endmill into the workpiece when the Head should have gone up instead…

      So I thought to replace the whole thing with some thing ‘better’..

      I found a neat PWM full bridge ( no motor wire reverse relays needed..) driver – PWM and direction control inputs.

       

      MD20A

      Locally it cost R400.00 – about 17 pounds.

       

      I made up a little board carrying the MD20, and Ardunio Nano, diode bride for the TachoGen so its wires don’t have to be reversed, 5volts regulator and a few pots…

       

      Here’s the circuit: ( the MD20 is not shown – it gets Motor Power and drives the motor. Also PWM and DIR from the Nano.

       

       

      Arduino_PWM

       

       

      And the PCB Carrier:

       

      Drive_Motor

      The two motors shaft coupled above were a test motor and tachogen to get it working on the bench.

       

      Now we needed software…So I decided to see what ChatGPT could deliver…

      What I wanted was to use a Pot , when centered the motor would stop, have a dead band either side of center, and one way would go up, the other down, and speed would vary per pot position. So I asked ChatGPT for that :

      Objective statement to ChatGPT:

      generate arduino nano code for a PWM motor controller with the following requirements. Speed command is from a variable DC voltage, 0-3v . A single Pin for PWM output. PWM is 0 between 1.4v to 1.6v . When the voltage is between 0 to 1.4v a single direction pin is set logic LOW and the PWM varies from MAX at 0v to min at 1.4v. When the voltage is between 1.6v to 3v, the same direction pin is set HIGH and the PWM varies from MIN at 1.6v to MAX at 3v. PWM frequency can be between 1000Hz and 5000Hz. Motor speed regulation is by reading the voltage from a Tach-generator on the driven motor shaft. The Tachogenerator out voltage is 0v as zero speed and 3v at max speed which is where the PWM is also max

      ChapGPT generated some code which I tested.

      The code contained a PID loop for the motor PWM control-

      I asked for these mods:

      add serial monitor output for the PID loop

      This gave me output data to the Arduino serial terminal so I could see the PID values in real time, and change them to get the motor smooth.

      Turns out I and D were not needed at all and a P gain of 2 worked perfectly.

      Then I wanted the dead-band expanded and needed a different voltage range for the TachGen output, so I asked for that:

      Change the voltage ranges as follows: Speed command voltage varies from 0v to 5v. Dead band is from 2.3v to 2.7v. PWM output is 0 at 2.7v and max at 5v, and 0 at 2.3v and max at 0v. Direction pin is low between 0v to 2.4v and HIGH between 2.6v to 5v. Tachogen output is 0v a zero RPM and 5v at max RPM

      The code as generated was used AS IS – I did not touch or modify anything other than to set Pgain =2 and hard code it as such.

      That is quite amazing – anyone could do this, with no coding experience at all – the key is to define the requirement properly and specify the changes clearly..It works amazingly well!

       

      This shows the ‘control’ lever – fitted to the centered pot, which it spring loaded with a zero detent – ball and spring..

      Up_Down lever

       

       

      The Z drive motor with the Tacho Gen slung underneath:

       

      Zmotor1

       

       

      And Finally, the code :

       

      /*
      * Bidirectional PWM Motor Controller with Analog Speed Command and Tach Feedback
      * Serial Monitor Output + Live PID Tuning + Debug Toggle
      *
      * Command voltage: 0–5V
      * Dead band: 2.3–2.7V
      * Direction pin:
      * LOW for 0–2.4V
      * HIGH for 2.6–5V
      * Tachogenerator: 0–5V = 0–max speed
      */

      const int pinCommand = A0;
      const int pinTacho = A1;
      const int pinPWM = 9;
      const int pinDir = 8;

      // — Voltage calibration —
      const float Vmax = 5.0;
      const float deadLow = 2.3;
      const float deadHigh = 2.7;
      const float dirLow = 2.4;
      const float dirHigh = 2.6;

      // — ADC reference constants —
      const float ADCref = 5.0; // Arduino Nano analog reference
      const int ADCmax = 1023;

      // — PID control constants —
      float Kp = 2.0; //was 80
      float Ki = 0; //was 2
      float Kd = 0; //was 10

      float integral = 0;
      float prevError = 0;
      unsigned long lastTime = 0;

      // // — Debug toggle flag —
      // bool debugEnabled = true; // default: ON after reset

      // — Debug toggle flag —
      bool debugEnabled = false; // default: OFF after reset

      void setup() {
      pinMode(pinPWM, OUTPUT);
      pinMode(pinDir, OUTPUT);

      Serial.begin(115200);
      Serial.println(“PWM Motor Controller Starting…”);
      Serial.println(“Commands:”);
      Serial.println(” Kp <val>, Ki <val>, Kd <val>”);
      Serial.println(” DEBUG ON / DEBUG OFF”);
      Serial.println(” SHOW”);

      // Set timer1 for ~2kHz PWM on D9
      TCCR1A = _BV(COM1A1) | _BV(WGM10);
      TCCR1B = _BV(WGM12) | _BV(CS11); // Prescaler 8 → ~2 kHz

      analogWrite(pinPWM, 0);
      digitalWrite(pinDir, LOW);
      }

      void loop() {
      handleSerialInput();

      // — Read analog inputs —
      float cmdRaw = analogRead(pinCommand);
      float tachRaw = analogRead(pinTacho);

      float cmdV = cmdRaw * (ADCref / ADCmax);
      float tachV = tachRaw * (ADCref / ADCmax);

      // — Direction control based on command voltage —
      if (cmdV < dirLow) {
      digitalWrite(pinDir, LOW);
      } else if (cmdV > dirHigh) {
      digitalWrite(pinDir, HIGH);
      }

      // — Determine target speed voltage for PID —
      float target = 0;
      int direction = 0;

      if (cmdV < deadLow) {
      direction = -1; // reverse
      target = mapf(cmdV, 0.0, deadLow, 5.0, 0.0); // 0 V→max, 2.3 V→0
      }
      else if (cmdV > deadHigh) {
      direction = 1; // forward
      target = mapf(cmdV, deadHigh, Vmax, 0.0, 5.0); // 2.7 V→0, 5 V→max
      }
      else {
      direction = 0; // stop / dead zone
      target = 0;
      }

      if (direction == 0) {
      analogWrite(pinPWM, 0);
      if (debugEnabled) printStatus(cmdV, tachV, direction, target, 0, 0);
      delay(50);
      return;
      }

      // — PID Speed Regulation —
      unsigned long now = millis();
      float dt = (now – lastTime) / 1000.0;
      if (dt <= 0) dt = 0.001;
      lastTime = now;

      float error = target – tachV;
      integral += error * dt;
      float derivative = (error – prevError) / dt;
      prevError = error;

      float output = Kp * error + Ki * integral + Kd * derivative;

      // — Constrain and apply PWM —
      int pwmVal = constrain(int(output * 51), 0, 255); // 5 V×51 ≈ 255
      analogWrite(pinPWM, pwmVal);

      if (debugEnabled) printStatus(cmdV, tachV, direction, target, error, pwmVal);
      delay(50);
      }

      // — Utility map function for floats —
      float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
      return (x – in_min) * (out_max – out_min) / (in_max – in_min) + out_min;
      }

      // — Serial print helper —
      void printStatus(float cmdV, float tachV, int direction, float target, float error, int pwmVal) {
      Serial.print(“CmdV: “); Serial.print(cmdV, 2);
      Serial.print(“V | Tach: “); Serial.print(tachV, 2);
      Serial.print(“V | DirPin: “);
      Serial.print(digitalRead(pinDir) == HIGH ? “HIGH” : “LOW”);
      Serial.print(” | Motion: “);
      if (direction == 1) Serial.print(“FWD”);
      else if (direction == -1) Serial.print(“REV”);
      else Serial.print(“STOP”);
      Serial.print(” | Target: “); Serial.print(target, 2);
      Serial.print(“V | Err: “); Serial.print(error, 2);
      Serial.print(” | PWM: “); Serial.print(pwmVal);
      Serial.print(” | PID(Kp/Ki/Kd): “);
      Serial.print(Kp, 1); Serial.print(“/”);
      Serial.print(Ki, 1); Serial.print(“/”);
      Serial.println(Kd, 1);
      }

      // — Serial input parser for PID + Debug control —
      void handleSerialInput() {
      static String input = “”;

      while (Serial.available()) {
      char c = Serial.read();
      if (c == ‘\n’ || c == ‘\r’) {
      input.trim();
      if (input.length() > 0) processCommand(input);
      input = “”;
      } else {
      input += c;
      }
      }
      }

      void processCommand(String cmd) {
      cmd.trim();
      cmd.toUpperCase();

      if (cmd.startsWith(“KP “)) {
      Kp = cmd.substring(3).toFloat();
      Serial.print(“Kp updated to “); Serial.println(Kp, 3);
      }
      else if (cmd.startsWith(“KI “)) {
      Ki = cmd.substring(3).toFloat();
      Serial.print(“Ki updated to “); Serial.println(Ki, 3);
      }
      else if (cmd.startsWith(“KD “)) {
      Kd = cmd.substring(3).toFloat();
      Serial.print(“Kd updated to “); Serial.println(Kd, 3);
      }
      else if (cmd == “SHOW”) {
      Serial.print(“Current PID: Kp=”); Serial.print(Kp, 3);
      Serial.print(” Ki=”); Serial.print(Ki, 3);
      Serial.print(” Kd=”); Serial.println(Kd, 3);
      }
      else if (cmd == “DEBUG ON”) {
      debugEnabled = true;
      Serial.println(“Debug output ENABLED.”);
      }
      else if (cmd == “DEBUG OFF”) {
      debugEnabled = false;
      Serial.println(“Debug output DISABLED.”);
      }
      else {
      Serial.println(“Unknown command. Use: Kp <val>, Ki <val>, Kd <val>, SHOW, DEBUG ON/OFF”);
      }
      }

      #829768
      Robert Atkinson 2
      Participant
        @robertatkinson2

        And how does this stop the orginal issues of faulty microswitches and tool crashes?
        You could have replaced the existing PWM output and relays with the Bridge module. Replacing miroswitches with slotted optos or inductive sensors would increase reliability.
        Nothing wrong with using software but it is not automatically “better”.
        Adding limit switches, and maybe calculated (speed/time) software limits.
        Having maximum speed for 0V input is not good design. A faulty pot or connection will result in unexpected travel at full speed which is not safe. Better design would be to have a fixed resistor or silicon diode at each end of the pot so “full” speed is say around half a volt in one direction and half a volt below reference for the other. voltage below or above these limits should result in a emergency brake condition. The code also has no start-up routine to ensure safe operation if power is applied or the controller resets with the handle not at “zero”. Ther is no error trapping, watchdog etc. For example if the motor is commanded to move and no feedback is detected from the tachogenerator turning within a reasonable time the motor should be turned off and a fault flagged. No point in using a processor if you don’t take advantage of it’s capabilities.

        Robert.

        #829778
        Nealeb
        Participant
          @nealeb

          If the point of the post was to show how good AI systems are at generating code, then I have to say that I’m impressed. I wonder how long a spec ChatGPT could handle to include Robert’s enhancements?

          #829819
          Versaboss
          Participant
            @versaboss

            Well, same as Nealeb – impressed too.

            But this line left me stumped:

            handleSerialInput();

            I did search in the Arduino Reference, but no luck…

            Season’s greetings
            Hans

            #829820
            Joseph Noci 1
            Participant
              @josephnoci1

              Such a wet blanket Robert..This was an exercise to see what ChatGPT had to offer – not a mission critical application for the Eurofighter.

              Of course your hindsight can always do things better – If I wanted all the guff you mention I would have thrown DO178A or C at ChatGPT…

              And just because a processor can do all the extras does not mean you have to..

              You missed the point altogether, but hey, whatever floats your boat…

               

              > And how does this stop the orginal issues of faulty microswitches and tool crashes?

              Rather obvious, innit? no more microswitches…

              Its about Electronics in the workshop and how anyone can have fun playing with this stuff – not about how good you were in your Job as Safety Officer.

              #829831
              Robert Atkinson 2
              Participant
                @robertatkinson2

                I may have misundersttod the microswitch function. I assumed they were limit switches but now see they must have been part the manual control inputs.
                So you have replaced fairly reliable microswitches with an unreliable potentiometer and software without the additional protections software could bring. Personally I’d want a limit switch on any system like this, software controlled or otherwise.

                I never mentioned software reliability or verification, just functionality. I certainly didn’t mention RTCA DO-178 (a standard for determining software meets the required level of integrity for aerospace applications) even though it is part of my day job.

                Robert.

                #829832
                john fletcher 1
                Participant
                  @johnfletcher1

                  Please tell me Joseph, did you have to type the above program above in manually ?  Ted

                  #829833
                  Bazyle
                  Participant
                    @bazyle

                    By chance I was just looking last night at Arduino beginners kits. There are so many versions but the common ones seem to be 32k flash. Do you know how much the above program took when loaded, to give an idea of what can fit in these devices.

                    #829852
                    John Haine
                    Participant
                      @johnhaine32865

                      I am constantly amazed by how little space apparently long c code programs used once compiled, I suspect that would comfortably fit in 32k. Also inputting the code above would be a copy and paste job into the IDE.

                      #829867
                      Joseph Noci 1
                      Participant
                        @josephnoci1

                        On Robert Atkinson 2 Said:

                        I may have misundersttod the microswitch function. I assumed they were limit switches but now see they must have been part the manual control inputs.
                        So you have replaced fairly reliable microswitches with an unreliable potentiometer and software without the additional protections software could bring. Personally I’d want a limit switch on any system like this, software controlled or otherwise.

                        I never mentioned software reliability or verification, just functionality. I certainly didn’t mention RTCA DO-178 (a standard for determining software meets the required level of integrity for aerospace applications) even though it is part of my day job.

                        Robert.

                        Well, not to get into a pi**ing contest here..but..The potentiometer was always in the equation, software or not, the reliable microswitch is what caused the end mill crash, and so on. But , again, you miss the point – this was not an exercise in system design to meet your day job requirements –  merely to see how ChatGPT translated my requirement statement into code, so please lets get off the design integrity bandwagon.

                        #829871
                        Joseph Noci 1
                        Participant
                          @josephnoci1
                          On john fletcher 1 Said:

                          Please tell me Joseph, did you have to type the above program above in manually ?  Ted

                          No, on the ChatGPT web page you type in your question and the AI software responds with answers and in this case the Arduino code – just copy and paste into the Arduino IDE and compile. Rather neat…

                          #829874
                          Joseph Noci 1
                          Participant
                            @josephnoci1
                            On Bazyle Said:

                            By chance I was just looking last night at Arduino beginners kits. There are so many versions but the common ones seem to be 32k flash. Do you know how much the above program took when loaded, to give an idea of what can fit in these devices.

                            Hi Bazyle,

                            The compiler says it fills 9333 bytes, about 30% of the flash – One can certainly fit a lot of functionality in these devices!

                            #829887
                            Robert Atkinson 2
                            Participant
                              @robertatkinson2

                              That is actually quite large. The last similar bit of code I wrote (full PID servo control of a torque motor driven 70mm butterfly valve at up to 2Hz) was only 2.8 kB. That was written in complied BASIC (PBP 3) and ran on a PIC16F873. That wasn’t aviation but was very literally a safety of life application. The PID function in the ChatGPT code seems a little basic but Ive not dug into it fully.

                              Robert.

                              #829895
                              Bazyle
                              Participant
                                @bazyle

                                Thanks, JN.

                                Of course even compiled code from high level languages is less compact than hand crafted machine code but who does that since about 1980. Back then my word processor, including justification, ran in 2k.

                                #829899
                                Fulmen
                                Participant
                                  @fulmen
                                  On Bazyle Said:

                                  but who does that since about 1980.

                                  A friend of mine did, back in 1990. He was a C64-enthusiast and coded that thing in straight hex. That guy was scary smart.

                                  #829904
                                  Joseph Noci 1
                                  Participant
                                    @josephnoci1
                                    On Bazyle Said:

                                    Thanks, JN.

                                    Of course even compiled code from high level languages is less compact than hand crafted machine code but who does that since about 1980. Back then my word processor, including justification, ran in 2k.

                                    Interesting, If I remove all the serial output functions and the live PID control input, ie, all serial I/O to the Arduino terminal, the compiled size reduces to 5784 bytes. So once you have done with testing and tuning, eliminate the terminal I/O and the code is quite small.

                                    #829922
                                    Robert Atkinson 2
                                    Participant
                                      @robertatkinson2

                                      Not unusual. A lot of serial communication libaries and resulting code are quite large. They make source code simpler but you pay the price in object code size. If the processor has a hardware UART writing data directly to it resullts in much more compact code than a libary that can drive any pin.

                                      Robert.

                                    Viewing 17 posts - 1 through 17 (of 17 total)
                                    • Please log in to reply to this topic. Registering is free and easy using the links on the menu at the top of this page.

                                    Latest Replies

                                    Viewing 25 topics - 1 through 25 (of 25 total)
                                    Viewing 25 topics - 1 through 25 (of 25 total)

                                    View full reply list.