Tic control function wanted

I am building an electronic focus device for telescopes. The tic500 has been adapted to this successfully but I think it needs the ability to have the control mode set by a button press.
Normally control is by computer via the USB. Occasionally, manual control via an encoder dial is desired. Can someone help me modify the tic control panel SW to read a switch press and switch from USB to Encoder mode?
Here is the original design’s github page. The author has not made source code for his interface to ASCOM (telescope control standard) available but has recommended I try to modify the Tic SW.

Hello.

I deleted one of your posts since they were mostly identical.

It is probably possible to modify the Tic Control Center software to behave the way you are describing (which would require some C++ programming). You can find the source code on our “Tic Stepper Motor Controller software” GitHub page. It isn’t clear what kind of help you’re looking for, but if you try making the modifications yourself and run into specific questions or problems, we would be glad to look into it with you.

Instead of changing the control mode, which is a setting stored in the Tic’s EEPROM, I recommend simply reading the encoder position and the state of the switch over USB, and then deciding what commands to send to the motor. The Tic provides encoder readings if you set the RX and TX pins to “Encoder input” mode in the “Advanced settings” tab; there is no need to change the control mode. The best place to add that logic would probably be in main_controller::update, after the call to handle_variables_changed().

Brandon

That really helps! The logic would need to lock out the encoder while the motor was actively responding to a USB command. That is the only complication I see right now. The method you propose frees up the switch to be used to toggle fast or slow control, which is great. the switch would need to be polled frequently to catch a momentary press, or an interrupr pin would need to be defined in the FW, right?

I’m sure to have lots of questions as I try to do this. progress is at the very start with the repo cloned to my git in preparation for code changes.

Tomm

Been looking most of the afternoon. Where in the code do I find how to allow the motor driver to act on encoder position while in serial mode? I cannot find how that is currently disabled. Thanks,
Tomm

The Tic software reads data from Tic every 50ms by default. That might be long enough to detect short pulses from a person touching the switch. If not, you can adjust the update rate by changing UPDATE_INTERVAL_MS in gui/qt/main_window.cpp. Modifying the Tic firmware is probably not practical for this application, but you might consider using a button or switch that mechanically latches.

To clarify, I was not suggesting that you should “allow the motor driver to act on encoder position”. I recommend that your code (running on the PC inside the modified Tic Control Center) should read the encoder inputs and the switch (and any other inputs you care about), then implement some logic that decides what you want the motor to do and send a “Set target position” or “Set target velocity” command to the Tic over USB. Those are the same commands that the Tic Control Center uses to control the motor when you use the “Status” tab. When you are writing code in main_controller class, you can send those commands using device_handle.set_target_position or device_handle.set_target_velocity. You can search for those strings in main_controller.cpp to see some examples.

Brandon

OK, I think.
For this to work, I will need to modify the code in main_controller.cpp so that the current position and motor speed can be modified by the encoder and a sensed switch press, right?

The place to add this code would be in main_controller::update, after the call to handle_variables_changed() as you said earlier.

Tomm

Yes, that sounds right.

Brandon

Here is where I am at so far:

diff --git a/gui/main_controller.cpp b/gui/main_controller.cpp
index abc1234..def5678 100644
--- a/gui/main_controller.cpp
+++ b/gui/main_controller.cpp
@@ -347,6 +347,33 @@ void main_controller::update()
     handle_variables_changed();



> +    // Enable encoder-based motion even when USB is connected
> +    {
> +        int32_t encoder_position = variables_.getVariableValue("Encoder position");
> +        static int32_t last_encoder_position = encoder_position;
> +
> +        if (encoder_position != last_encoder_position)
> +        {
> +            int32_t delta = encoder_position - last_encoder_position;
> +
> +            int step_mode = variables_.getVariableValue("Step mode");
> +            int pre = variables_.getVariableValue("Position prescaler");
> +            int post = variables_.getVariableValue("Position postscaler");
> +
> +            int64_t delta_steps = static_cast<int64_t>(delta) * pre * step_mode / post;
> +
> +            int32_t current_target = variables_.getVariableValue("Target position");
> +            int32_t new_target = current_target + static_cast<int32_t>(delta_steps);
> +
> +            int32_t min_pos = variables_.getVariableValue("Target min");
> +            int32_t max_pos = variables_.getVariableValue("Target max");
> +            new_target = std::clamp(new_target, min_pos, max_pos);
> +
> +            controller_.setTargetPosition(new_target);
> +            variables_.setVariableValue("Target position", new_target);
> +
> +            last_encoder_position = encoder_position;
> +            LOG_DEBUG("Encoder updated target: Δ%+lld → %d", delta_steps, new_target);
> +        }
> +    }
> 
>      tic_interface->update();

This commit allows the rotary encoder to continue driving the motor position
even when the USB connection is active. Previously, encoder control was
restricted to USB-disabled operation.

Key changes:

  • Integrated encoder movement reading using Encoder position from firmware.
  • Respected existing prescaler, postscaler, and step mode settings.
  • Clamped resulting position using configured target limits.
  • Avoided introducing new files or hardware-specific logic.

All functionality is contained in main_controller.cpp and uses
Tic’s built-in encoder support and GUI-configured behavior.

I added the switch handling to allow multiple encoder switch presses to change the pre and post scaling in a cyclical manner:

diff --git a/gui/main_controller.cpp b/gui/main_controller.cpp
index abc1234..fedcba9 100644
--- a/gui/main_controller.cpp
+++ b/gui/main_controller.cpp
@@ -347,6 +347,70 @@ void main_controller::update()
     handle_variables_changed();

+    // Handle switch press for encoder scaling mode
+    {
+        static int last_switch_state = 0;
+        static int switch_mode_index = 0;
+        static int encoder_prescaler = 1;
+        static int encoder_postscaler = 1;
+
+        // Read selected input pin set as "User Input" in Tic
+        int switch_state = variables_.getVariableValue("User input active"); // 1 if pressed
+
+        if (switch_state == 1 && last_switch_state == 0)
+        {
+            switch_mode_index = (switch_mode_index + 1) % 5;
+            LOG_INFO("Switch pressed. Encoder scaling mode: %d", switch_mode_index);
+        }
+        last_switch_state = switch_state;
+
+        // Select scaling parameters based on press count
+        switch (switch_mode_index)
+        {
+            case 0:
+                encoder_prescaler = 4;
+                encoder_postscaler = 1;
+                break;
+            case 1:
+                encoder_prescaler = 1;
+                encoder_postscaler = 1;
+                break;
+            case 2:
+                encoder_prescaler = 1;
+                encoder_postscaler = 4;
+                break;
+            case 3:
+                encoder_prescaler = 1;
+                encoder_postscaler = 16;
+                break;
+            case 4:
+                switch_mode_index = 0; // reset
+                encoder_prescaler = 4;
+                encoder_postscaler = 1;
+                break;
+        }

+        // Encoder position block
+        int32_t encoder_position = variables_.getVariableValue("Encoder position");
+        static int32_t last_encoder_position = encoder_position;
+        if (encoder_position != last_encoder_position)
+        {
+            int32_t delta = encoder_position - last_encoder_position;
+
+            int step_mode = variables_.getVariableValue("Step mode");
+            int64_t delta_steps = static_cast<int64_t>(delta) * encoder_prescaler * step_mode / encoder_postscaler;
+
+            int32_t current_target = variables_.getVariableValue("Target position");
+            int32_t new_target = current_target + static_cast<int32_t>(delta_steps);
+
+            int32_t min_pos = variables_.getVariableValue("Target min");
+            int32_t max_pos = variables_.getVariableValue("Target max");
+            new_target = std::clamp(new_target, min_pos, max_pos);
+
+            controller_.setTargetPosition(new_target);
+            variables_.setVariableValue("Target position", new_target);
+            last_encoder_position = encoder_position;
+
+            LOG_DEBUG("Encoder updated target: Δ%+lld → %d", delta_steps, new_target);
+        }
+    }

     tic_interface->update();

OK, I need some expert help please. I ran the commands

mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX
ninja
ninja install
cd ../..

In the local sw directory and got a lot of errors I then fixed. Attempting to run again on the fixed files I got this output:

Faraday@LAPTOP-2LVTTDJ0 UCRT64 /c/users/Faraday/pololu-tic-software/build
$  mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX
ninja
ninja install
cd ../..
-- Configuring done (0.2s)
-- Generating done (0.1s)
-- Build files have been written to: C:/Users/Faraday/pololu-tic-software/build
ninja: error: loading 'build.ninja': The system cannot find the file specified.

ninja: error: loading 'build.ninja': The system cannot find the file specified.

here is the code I inserted at line 348 of main_controller.cpp:

{
    static int last_switch_state = 0;
    static int switch_mode_index = 0;
    static int encoder_prescaler = 1;
    static int encoder_postscaler = 1;

    int switch_state = variables.getVariableValue("User input active");

    if (switch_state == 1 && last_switch_state == 0)
    {
        switch_mode_index = (switch_mode_index + 1) % 5;
        qDebug("Switch pressed. Encoder scaling mode: %d", switch_mode_index);
    }
    last_switch_state = switch_state;

    switch (switch_mode_index)
    {
        case 0: encoder_prescaler = 4; encoder_postscaler = 1; break;
        case 1: encoder_prescaler = 1; encoder_postscaler = 1; break;
        case 2: encoder_prescaler = 1; encoder_postscaler = 4; break;
        case 3: encoder_prescaler = 1; encoder_postscaler = 16; break;
        case 4: switch_mode_index = 0; encoder_prescaler = 4; encoder_postscaler = 1; break;
    }

    int32_t encoder_position = variables.getVariableValue("Encoder position");
    static int32_t last_encoder_position = encoder_position;

    if (encoder_position != last_encoder_position)
    {
        int32_t delta = encoder_position - last_encoder_position;
        int step_mode = variables.getVariableValue("Step mode");

        int64_t delta_steps = static_cast<int64_t>(delta) * encoder_prescaler * step_mode / encoder_postscaler;

        int32_t current_target = variables.getVariableValue("Target position");
        int32_t new_target = current_target + static_cast<int32_t>(delta_steps);

        int32_t min_pos = variables.getVariableValue("Target min");
        int32_t max_pos = variables.getVariableValue("Target max");

        new_target = (new_target < min_pos) ? min_pos : (new_target > max_pos ? max_pos : new_target);

        controller.setTargetPosition(new_target);
        variables.setVariableValue("Target position", new_target);
        last_encoder_position = encoder_position;

        qDebug("Encoder updated target: Δ%+lld → %d", delta_steps, new_target);
    }
}

Hi, could you try deleting everything in the build directory (or deleting the entire directory and recreating an empty one) and running cmake .. -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX again there? Then try the rest of the build process again.

If that still doesn’t work, could you post the full output you got from that cmake command (please make sure it’s the output you got from starting with an empty build directory), along with the output from running ls in the build directory afterwards?

Kevin

contents of build after attempt:

Faraday@LAPTOP-2LVTTDJ0 UCRT64 /c/users/Faraday/pololu-tic-software/build
$ ls
CMakeCache.txt  bootloader   cli                  gui      lib                  ticcmd.exe
CMakeFiles      build.ninja  cmake_install.cmake  include  libpololu-tic-1.dll  version.txt

make message with error in last line:

Faraday@LAPTOP-2LVTTDJ0 UCRT64 /c/users/Faraday/pololu-tic-software
$ cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX
ninja
ninja install
cd ../..
-- Configuring done (0.2s)
-- Generating done (0.1s)
-- Build files have been written to: C:/Users/Faraday/pololu-tic-software/build
[35/40] Building CXX object gui/CMakeFiles/gui.dir/main_controller.cpp.obj
FAILED: gui/CMakeFiles/gui.dir/main_controller.cpp.obj
C:\msys64\ucrt64\bin\c++.exe -DNTDDI_VERSION=0x06000000 -DQT_CORE_LIB -DQT_GUI_LIB -DQT_NO_DEBUG -DQT_WIDG
ETS_LIB -D_WIN32_WINNT=0x0600 -D__USE_MINGW_ANSI_STDIO=1 -IC:/Users/Faraday/pololu-tic-software/build/gui
-IC:/Users/Faraday/pololu-tic-software/gui -IC:/Users/Faraday/pololu-tic-software/build/gui/gui_autogen/in
clude -IC:/Users/Faraday/pololu-tic-software/include -IC:/Users/Faraday/pololu-tic-software/build/include
-IC:/Users/Faraday/pololu-tic-software/gui/qt -IC:/Users/Faraday/pololu-tic-software/bootloader -isystem C
:/msys64/ucrt64/include/QtWidgets -isystem C:/msys64/ucrt64/include/QtGui -isystem C:/msys64/ucrt64/includ
e/QtCore -isystem C:/msys64/ucrt64/share/qt5/mkspecs/win32-g++ -O3 -DNDEBUG -std=gnu++11 -IC:/msys64/ucrt6
4/include/libusbp-1 -DLIBUSBP_STATIC -MD -MT gui/CMakeFiles/gui.dir/main_controller.cpp.obj -MF gui\CMakeF
iles\gui.dir\main_controller.cpp.obj.d -o gui/CMakeFiles/gui.dir/main_controller.cpp.obj -c C:/Users/Farad
ay/pololu-tic-software/gui/main_controller.cpp
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp: In member function 'void main_controller::up
date()':
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:354:34: error: 'class tic::variables' has no
member named 'getVariableValue'
  354 |     int switch_state = variables.getVariableValue("User input active");
      |                                  ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:372:42: error: 'class tic::variables' has no
member named 'getVariableValue'
  372 |     int32_t encoder_position = variables.getVariableValue("Encoder position");
      |                                          ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:378:35: error: 'class tic::variables' has no
member named 'getVariableValue'
  378 |         int step_mode = variables.getVariableValue("Step mode");
      |                                   ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:382:44: error: 'class tic::variables' has no
member named 'getVariableValue'
  382 |         int32_t current_target = variables.getVariableValue("Target position");
      |                                            ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:385:37: error: 'class tic::variables' has no
member named 'getVariableValue'
  385 |         int32_t min_pos = variables.getVariableValue("Target min");
      |                                     ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:386:37: error: 'class tic::variables' has no
member named 'getVariableValue'
  386 |         int32_t max_pos = variables.getVariableValue("Target max");
      |                                     ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:390:9: error: 'controller' was not declared i
n this scope; did you mean 'main_controller'?
  390 |         controller.setTargetPosition(new_target);
      |         ^~~~~~~~~~
      |         main_controller
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:391:19: error: 'class tic::variables' has no
member named 'setVariableValue'
  391 |         variables.setVariableValue("Target position", new_target);
      |                   ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:398:6: error: 'tic_interface' was not declare
d in this scope; did you mean 'interface'?
  398 |      tic_interface->update();
      |      ^~~~~~~~~~~~~
      |      interface
[39/40] Building CXX object gui/CMakeFiles/gui.dir/qt/main_window.cpp.obj
ninja: build stopped: subcommand failed.
[1/3] Building CXX object gui/CMakeFiles/gui.dir/main_controller.cpp.obj
FAILED: gui/CMakeFiles/gui.dir/main_controller.cpp.obj
C:\msys64\ucrt64\bin\c++.exe -DNTDDI_VERSION=0x06000000 -DQT_CORE_LIB -DQT_GUI_LIB -DQT_NO_DEBUG -DQT_WIDG
ETS_LIB -D_WIN32_WINNT=0x0600 -D__USE_MINGW_ANSI_STDIO=1 -IC:/Users/Faraday/pololu-tic-software/build/gui
-IC:/Users/Faraday/pololu-tic-software/gui -IC:/Users/Faraday/pololu-tic-software/build/gui/gui_autogen/in
clude -IC:/Users/Faraday/pololu-tic-software/include -IC:/Users/Faraday/pololu-tic-software/build/include
-IC:/Users/Faraday/pololu-tic-software/gui/qt -IC:/Users/Faraday/pololu-tic-software/bootloader -isystem C
:/msys64/ucrt64/include/QtWidgets -isystem C:/msys64/ucrt64/include/QtGui -isystem C:/msys64/ucrt64/includ
e/QtCore -isystem C:/msys64/ucrt64/share/qt5/mkspecs/win32-g++ -O3 -DNDEBUG -std=gnu++11 -IC:/msys64/ucrt6
4/include/libusbp-1 -DLIBUSBP_STATIC -MD -MT gui/CMakeFiles/gui.dir/main_controller.cpp.obj -MF gui\CMakeF
iles\gui.dir\main_controller.cpp.obj.d -o gui/CMakeFiles/gui.dir/main_controller.cpp.obj -c C:/Users/Farad
ay/pololu-tic-software/gui/main_controller.cpp
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp: In member function 'void main_controller::up
date()':
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:354:34: error: 'class tic::variables' has no
member named 'getVariableValue'
  354 |     int switch_state = variables.getVariableValue("User input active");
      |                                  ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:372:42: error: 'class tic::variables' has no
member named 'getVariableValue'
  372 |     int32_t encoder_position = variables.getVariableValue("Encoder position");
      |                                          ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:378:35: error: 'class tic::variables' has no
member named 'getVariableValue'
  378 |         int step_mode = variables.getVariableValue("Step mode");
      |                                   ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:382:44: error: 'class tic::variables' has no
member named 'getVariableValue'
  382 |         int32_t current_target = variables.getVariableValue("Target position");
      |                                            ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:385:37: error: 'class tic::variables' has no
member named 'getVariableValue'
  385 |         int32_t min_pos = variables.getVariableValue("Target min");
      |                                     ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:386:37: error: 'class tic::variables' has no
member named 'getVariableValue'
  386 |         int32_t max_pos = variables.getVariableValue("Target max");
      |                                     ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:390:9: error: 'controller' was not declared i
n this scope; did you mean 'main_controller'?
  390 |         controller.setTargetPosition(new_target);
      |         ^~~~~~~~~~
      |         main_controller
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:391:19: error: 'class tic::variables' has no
member named 'setVariableValue'
  391 |         variables.setVariableValue("Target position", new_target);
      |                   ^~~~~~~~~~~~~~~~
C:/Users/Faraday/pololu-tic-software/gui/main_controller.cpp:398:6: error: 'tic_interface' was not declare
d in this scope; did you mean 'interface'?
  398 |      tic_interface->update();
      |      ^~~~~~~~~~~~~
      |      interface
ninja: build stopped: subcommand failed.

Your cmake setup seems to be okay now and you’re at the point where it is at least trying to compile the software. However, there seem to be some issues with your code, and you should look carefully at the output of the ninja command to identify the problems.

For example, the first error is 'class tic::variables' has no member named 'getVariableValue', and I’m not sure why you’re trying to call a method by that name. The variables class has dedicated functions for each variable, so to get the encoder position, you would call variables.get_encoder_position(); you can find the definition for that method and the other variables’ methods in tic.hpp.

Kevin

Thanks. I will work on correcting these errors and report back.