Close
0%
0%

Servio

Open-source DC servomotor with extensive testing infrastructure.

Public Chat
Similar projects worth following
Servio is an open-source servo designed for DC motors and programmed in C++20. It includes firmware and PCB designs, with the remaining assembly left to the user.

It undergoes rigorous testing through our private setup, which is our behind-the-scenes workhorse. This testing includes unit, simulation, control loop, and black box tests, all conducted to ensure that Servio functions optimally.

See a series of logs with overview of the project:

  • Current sensing analysis - (Part 1/2)

    veverak04/14/2024 at 19:27 0 comments

    So, given that prototype v3 has been on my table for a while and I've managed to mostly make it work, the time has come to go through the 'current sensing analysis' ritual.

    What is this all about? The crucial functionality of the servo is its ability to sense the current flowing through the motor. If this feature has bugs or doesn't work perfectly, none of the control loops will function properly either. Most importantly, it would be difficult to determine that the issue lies with the current sensing and not with something else. (Trust me, I learned this the hard way with previous prototypes.)

    To visualize this:

    Situation

    The first step is to summarize what we are working with. Here's the simplified circuit:

    Our H-bridge, DRV8251A, has current-sensing capability, as documented here. What happens is that based on the current flowing through the bridge, current flows from |PROP| to GND. This is what is measured by the MCU STM32H5 for current sensing. We use a 1k resistor R7 as a shunt resistor.

    Tom V. also recommended using a 10n capacitor C15 to handle noise. Frankly, this was a bit troublesome for my CS-focused brain, which has a complete lack of EE knowledge. The question is:

    Let's say you sense a current from a PWM pulse, how will the capacitor affect the values read by ADC relative to it being absent?

    (Yes, this should be easy to answer for any EE graduate, but here we are.)

    Math

    The current from PROP is linearly scaled with a known fixed ratio: 1575 µA/A

    Given that, we can formulate the basic formula:

    V_R7 = I_R7 * R_R7
    I_R7 = I * COEFF
    

    What we want to know is: given some fixed voltage `V` measured on `R7`, what is the current flowing through the motor?

    The answer to that is in the formula: `V_R7 = I * COEFF * R_R7`

    Which, once we fill in the known information, gets simplified to: `V_R7 = I * 1.575`

    Note that the resistor has quite a resistance for a shunt resistor, but given the drastic ratio between the real current and the current flowing through |PROP|, the values cancel each other out.

    Baseline

    Given that we know this, let's take a scope and measure some things. What we can do is attach a motor to the servo, flash the production firmware, and use a control utility to set the servo to a fixed PWM duty cycle.

    The motor is a scrapped Lewansoul LX15D. Since the motor was without any load, the expected current values should be low, as without resistance it won’t need much.

    My trusted laboratory power source is powering all of this. The power source has the capability to measure current, so I relied on that for comparison values.

    There are multiple scenarios during which I've taken data. In all cases, I relied on the Analog Discovery 2 for the measurements, as that device is my daily tool for such tasks. As you can see, I can even make fancy screenshots of the view from it :)

    50% duty cycle on AD2

    The interesting data is on Channel 2, which was attached to the current sense pin—effectively connected to the PA4 pin of the STM32H5 on the circuit above. I also made sure that I could observe the PWM duty cycle on the logic analyzer of the AD2; you can see it in the bottom row. (14kHz PWM frequency is about right)

    Channel 2 gives us the voltage across the R7 resistor. I've also added a virtual channel,`Math 1`, that converts the values from Ch2 into the actual current flowing through the motor.

    It should be noted that the current reported by the power source includes the power drained by the MCU and other peripherals, not just the motor, and at this point, there is no way for me to separate them. However, it should be manageable.

    After all that was done and prepared, I measured relevant values for multiple scenarios. Note that I always used the `average` value for each channel:

    power0%25%50%75%100%
    power source current11 mA31 mA57mA81mA106mA
    R7 voltage18 mV50 mV155mV177mV224mV
    calculated motor current12 mA32 mA98mA112mA142mA

    What do the measured values mean?...

    Read more »

  • Servio Overview (Part 5/5) - Control

    veverak03/16/2024 at 22:57 0 comments

    From an algorithmic perspective, at its core, the main task of a servomotor is to take the desired control variable (e.g., position) and command the connected DC motor in such a way that the desired position is achieved.

    For Servio, we aimed for somewhat advanced forms of motion planning and control. In the first milestone, we chose basic control loops for three variables: current, position, and velocity.

    We implemented three modes for Servio, in which the firmware focuses on controlling the respective variable - current mode, position mode, and velocity mode. In each mode, multiple control loops can be active:

    • Current mode - uses just the current control loop.
    • Position mode - uses both the current control loop and the position control loop.
    • Velocity mode - uses both the current control loop and the velocity control loop.

    To make the loops work, we introduced a Kalman filter into the system to estimate the current velocity/position based on the sensor used for the axis angle. This was necessary as we needed some way to estimate the velocity accurately.

    All of this is illustrated in the following diagram:

    Current control loop

    The main control loop, the only one in direct contact with the hardware, takes a desired current as an input and controls the power sent to the motor based on the currently sensed current.

    It operates as a standard PI control loop, using current as the input values to produce the desired power for the motor. The power is represented by a value ranging from -100% to 100%. (The current loop is not aware that PWM is used.) This output is then fed into timers, which generate PWM pulses to the H-bridge used by Servio.

    Based on some insights from the industry, we decided to use a PI controller at 10kHz, as we were informed that the key to making the control work correctly lies in the frequency. That is, a high-frequency PID will be more tolerant to PID coefficient tuning.

    To achieve this, we generate PWM at a 20kHz frequency. Each control loop iteration spans two PWM periods:

    1. Odd cycle - We sample the current flowing through the H-bridge. In the diagram, the sampled values are represented by blue dots. In the real system, we can sample up to 30 values.
    2. Even cycle - The average of these values is calculated, and this average is given to the control loop, and a new PWM value is calculated.
    Current sensing scheme

    Note that the exact frequencies of the control loop and the PWM are not fixed; we have just fixed the ratio between them. In the future, we want to experiment with changing the frequency, ideally to try a higher value. In the control loops schema below, you can see which parts of the system are active in control mode.

    Position control loop

    We sense the position from a potentiometer at a frequency higher than 1kHz, which governs the frequency of the position control loop. This loop uses a PID controller that takes the measured position and the goal position as inputs and calculates the desired current flow through the system. Thus, the position control serves as an input to our current control loop, which handles the rest.

    We don't use the measured position directly; instead, we rely on a Kalman filter to perform some filtering on the measured value.

    Given the high static friction in our test system, there were issues with the robustness of the loop. To address this, we decided to implement an extra bias. If the servomotor is not moving, the current output from the position control loop is multiplied by a scale value (2.0). This scale value linearly degrades once the servo starts moving and reappears linearly once the servo stops. While not a perfect solution, it has proven to be good enough as it is quite intuitive to configure. (This bias is not visible in the schema.)

    In the schema below, you can see which parts of the system are active in position mode.

    Velocity control loop

    Velocity control operates on the same principle as position control; we use position readings to...

    Read more »

  • Servio Overview (Part 4/5) - Continuous Integration

    veverak02/16/2024 at 21:38 0 comments

    We can think of Continuous Integration (CI) in two ways: as an approach to development and as infrastructure. For Servio, both aspects are relevant, as we heavily utilize CI for development.

    As a development approach, the philosophy centers on continuously testing new changes to ensure they do not disrupt the system as a whole. This contrasts with making large changes and integrating them into the system sporadically to see if anything breaks. Essentially, it emphasizes incremental updates.

    Ater we re-factored code for static-friction compensation, we run step velocity test... to find out immediatly that the refactoring has some issues. (Executed on real hardware)

    From a coding perspective, we often use a powered test jig on the desk and periodically run the full test suite with it. This practice helps us identify any errors or issues introduced by changes—sometimes these are bugs, and sometimes they are expected outcomes.

    Technically, we hide the testjig in a shelf in some cases, so it's not on the table

    Another perspective involves the backend connected to the server. Once any change is committed and merged into the main git repository, the same testing process automatically occurs in the background for each commit.

    Screenshot of HTML report produced by GitHub runner.

    emlabcpp and joque

    Regarding the library subprojects, emlabcpp and joque, each has its own CI setup and pipelines.

    From Servio's perspective, these libraries should be tested through their own mechanisms without any explicit testing on Servio's part.

    Docker container

    Regarding Docker containers, we opted for an automated build and testing environment by creating a Docker image based on the official Arch Linux distribution. We simply add our build tools and dependencies to this image and use it across all our pipelines for repositories. This image is publicly available, and we encourage its use. Dockerfile is here.

    Public repo

    Our public repository builds everything in CI and runs unit tests to ensure the build is independent of any specific details of the developers' environment. The unit tests check basic functionality, but due to their limited coverage, more comprehensive testing is conducted in the private repository.

    Private repo

    For the private repository, we have a GitHub runner for pipelines, equipped with a smaller version of the test jig with a Servio PCB permanently connected. Currently, this test jig is unpowered and disconnected, as leaving a power source permanently enabled presents a risk. This setup resides in my living room, and I have yet to decide to leave any power source permanently on for an automatically starting device.

    PCB For GitHub runner - Is it just a PCB laying on shelf somewhere close to USB hub? yes!

    For each commit in the private repository, we initiate a build of all firmware and tests on our runner and execute a full suite of tests. The test suite can be adjusted for an unpowered scenario, affecting many tests, some skip themselves, while others simply disable evaluation.

  • Servio Overview (Part 3/5) - Hardware

    veverak01/28/2024 at 21:01 0 comments

    Hardware

    Relative to software, the hardware might be underdeveloped. This could be because the project is intentionally more focused on software. Importantly, from a hardware perspective, the Servio project only includes PCB designs, without any casing or mechanical designs. The general idea is that the casing, metal gears, DC motor, or potentiometer must be provided by the user. Servio essentially comprises just the PCB with its software.

    For the project's development, we have so far created three iterations of the PCB and have a test jig in an unfinished state.

    General PCB Requirements

    Generally, the requirements for Servio PCBs are straightforward: The PCB needs a compatible MCU and an H-bridge to control the motor. It would also be practical to include connections for a rotation sensor (at a minimum, a potentiometer), a power source to regulate voltage for components, LEDs for indication, and some connectors.

    We intentionally practice minimalism in this way to ensure that designing a new PCB is as simple as possible, which allows for high flexibility in PCB design.

    Prototype v1

    The first iteration was designed with the following goals:

    1. Make it easy for debugging (hence its large size, we made it fit within 10x10cm).
    2. Experiment with various components (we included extra SPI encoders and used WS2812 LEDs because we initially thought they were necessary).
    3. Integrate the STLINK V3-mini.
    4. Use the STM32G431KC - 170 MHz, 128KB of FLASH, 32KB of RAM.

    We began development with this board and conducted most of the development on it. The only major issue was with the H-bridge, which led yaqwsx to hand-solder an alternative H-bridge to circumvent the limitation.

    There was some debate about the kind of MCU we needed; we intentionally chose a more powerful one than we thought necessary. Why? To reduce development time by minimizing the need for performance optimizations. 

    Prototype v1
    First version of development board

    Prototype v2

    The second iteration was intentionally made smaller, mostly at my request, so I could more easily integrate it into actual applications. The board was designed as two connected PCBs: one being the servo itself and the other a debug board.

    The goals here were to:

    1. Use the STM32G431KC - it works just fine and we actually use the computing power. 
    2. Use the DRV8870DDA as the H-bridge.
    3. Switch from WS2812 to standard LEDs.
    4. Rely on the STLINK-V3 mini.

    Essentially, the second iteration intentionally narrowed the scope, and made stuff smaller.

    A bunch of prototype v2 boards waiting for usage, you can see the v1 below it.
    v2 devel board in first iteration of testjig, we used standard 40x20x20mm servo

    Prototype v2 - Test Jig

    With the second iteration of the development board, I put more effort into a test jig - a test harness that could be used for any development done by me or for further development automation.

    The test jig contains a DC servomotor without a connected PCB to an external Servio PCB (v2) in a DIY-made box. There are other external boards that allow the addition of more sensors around the actual servo, such as an encoder directly connected to the servo shaft. During development, the servo was powered by a standard laboratory power source.

    Key takeaways from this experience:

    1. The test jig was quite beneficial to development.
    2. There may be a need for various types of test jigs.
    3. The extra-sensory part of the test jig was unfinished, and there was no immediate motivation to complete it.

    All in all, this part of the project will definitely have its own detailed log in the future.

    The base of testjig is fairly sized box made out of totemmaker
    The testjig contains v2 board, servo mounted on stand with encoder, usb hub with external UART, and another STM32H7 for potential extra stuff

    Prototype v3

    Generally, v2 was almost adequate, but we wanted to add some final touches to the board and decided to create v3, with goals to:

    1. Make it as small as reasonably possible - small enough for production...
    Read more »

  • Servio Overview (Part 2/5) - Software Architecture

    veverak01/27/2024 at 19:25 0 comments

    Software Architecture Perspective

    The software architecture of the system could be analyzed in depth, and we will address this in the future. However, considering that it is "just a servo," its complexity and size are not significant. Most of the effort is devoted to perfecting it. When discussing software architecture, we should consider the structure of the firmware, the requirements from the controlling side, how tests fit into this framework, and what testing orchestration looks like.

    Firmware

    The firmware can be described in following simple terms: Servo firmware should be a sophisticated control loop governed by messages. This simplification forms the basis of our firmware.

    For effective testing, we focused on abstraction and decomposition. Our goal was to ensure that details specific to embedded hardware do not excessively infiltrate the codebase, enabling extensive testing on our computers.

    The firmware structure comprises three layers. The bottom layer includes platform-specific code, which is minimal. This layer contains the Hardware Abstraction Layer (HAL) from the manufacturer and our setup functions for that platform. Each firmware version includes precisely one platform module in its source code.

    The second layer is board-specific code, ideally encapsulated in a single .cpp file with pin and peripheral configurations. This layer typically invokes the platform-specific functions configured for the specific board.

    The remainder resides in an independent layer, ideally agnostic to any platform or board-specific code. Currently, we cannot run 100% of this code on non-embedded hardware, but the percentage that requires embedded execution is extremely low. This limitation partly results from my laxity and our use of nanopb exclusively on the embedded side.

    The independent layer contains the majority of the code, but we will delve into this in a future blog post, as it is not immediately relevant.

    Interfacing

    We chose protobuf messages as the format for communicating with the servo. Our long-term goal is flexibility in the underlying layer used for transmitting these messages. Currently, we support framing protobuf messages in COBS and transmitting them via full-duplex UART.

    This approach means that software interfacing with the servo must create a valid protobuf message, encapsulate it in COBS, send it over UART, and await a response.

    The servio repository includes the scmdio utility binary, facilitating communication with the servo through a CLI interface. For example, we can command the servo to switch to position control mode and move to position 0. This utility is primarily for configuration, as it implements a frontend for the configuration API in the protobufs.

    Bash command would look like this:

    $ smcido mode position 0 

    C++ code using boost asio could look like this:

    boost::asio::awaitable< void > set_mode_position( cobs_port& port, float angle )
    {
            servio::Mode m;
            m.set_position( angle );
    
            servio::HostToServio hts;
            hts.mutable_set_mode()->
    
            co_await exchange( port, hts );
    } 

    Tests

    We employ various tests: unit tests, simulation tests, firmware tests, control tests, and blackbox tests, each with a unique focus.

    Unit tests evaluate small, independent code segments on host devices (our laptops) without hardware access. For instance, we test our algorithm for storing configurations in flash memory using a raw memory buffer. Currently, the number of unit tests is lower than desired, but our extensive testing through other methods mitigates the need for more unit tests. Only code from the independent layer is tested here.

        Start 1: cfg_utest_test
    1/5 Test #1: cfg_utest_test ...................   Passed    0.02 sec
        Start 2: cfg_storage_utest_test
    2/5 Test #2: cfg_storage_utest_test ...........   Passed    0.01 sec
        Start 3: control_utest_test
    3/5 Test #3: control_utest_test ...............   Passed    0.01 sec
        Start 4: kalman_utest_test
    4/5 Test #4: kalman_utest_test ...................
    Read more »

  • Servio Overview (Part 1/5) - Projects

    veverak01/26/2024 at 07:00 0 comments

    Given that we understand the motivation behind Servio (as gleaned from the first log), let's delve deeper into its anatomy and other relevant aspects. These logs will contain some redundancy, as it does not neatly divide into parts but instead examines the subject from multiple perspectives.

    This comprehensive overview will be released in five parts to avoid overwhelming readers with one lengthy log. Enjoy!

    Project's Perspective

    Servio can be viewed as a set of projects with varying degrees of independence. Each project has its own repository. There are two core projects: servio and servio(private). Servio is formed by an open-source public component (firmware, some utilities, some tests) and a private component (numerous tests). We tend to view these components as two intertwined projects that need to coexist.

    In addition to these, we maintain some C++ libraries that we developed. These include `emlabcpp`, a generic embedded-related library, and `joque`, an utility library used by the private part of Servio. The rest of this section will provide more detailed descriptions of each project.

    During early development, yaqwsx provided me with two PCB designs for prototyping (versions 1 and 2), both available in a standalone PCBs repository. Recently, Tomas Vavrinec agreed to design another prototype board. Overall, various PCB design projects are associated with Servio.

    servio

    This is the public component of Servio and the project's central part (link). It includesthe entire firmware. The firmware depends only on the emlabcpp library and the standard C++ library, making it fully open-source. Additionally, there is a command-line utility for working with the servo `scmdio`, a basic set of unit tests, and black-box tests.

    This should suffice for users who wish to modify the servo's code or inspect what they are using in their hardware. We will discuss the source code structure in greater detail later in another logs.

    servio(private)

    Our private repository contains our testing infrastructure and some glue that connects everything together.

    Our testing infrastructure is capable of executing various types of tests (currently including unit tests, firmware tests, control tests, simulation tests, and black-box tests) adn generate HTML reports for analysis.

    The challenge with both servio repositories is their separate yet tightly coupled nature. This requires us to maintain tight synchronization, which can be challenging.

    In a future log, we will explain why we undergo this public/private separation.

    emlabcpp

    emlabcpp, a few years older than Servio, is an opinionated C++20 library focused on embedded-related utilities. It emphasizes providing numerous small utilities or mini-libraries for embedded code.

    The embedded-related aspect implies code with a low footprint that avoids dynamic memory and exceptions. Servio is built upon this library, which was expanded with utilities necessary for Servio during its development. Most notably, it includes an embedded-focused testing library for tests executed on the target hardware.

    joque

    joque is a small library designed to parallelly execute a set of tasks while respecting their dependencies. It's what we use to orchestrate tests.

    Generally, when feasible, I prefer to create a few smaller libraries from the project for future reuse.

    PCBs v1, v2, and v3

    We have separate repositories for PCB designs, primarily for development boards. These are somewhat standalone, especially the latest version, v3, designed by T. Vavrinec for us, though the repository remains his. v2 exists here, and v3 is here.

    This independence applies only to the hardware design files. The software side must be compatible with the boards (pin assignments) and is currently stored in the main repository.

  • Inception and motivaton

    veverak01/04/2024 at 16:27 0 comments

    This is the first log recording for Servio, written retrospectively, as in the early days, I was preoccupied with developing the project and did not think about documenting the process. (A habit I find problematic, as writing things down generally helps in not forgetting something or passing information to others.)

    Let's start by describing how and why it all began, focusing more on the why rather than the exact details.

    What is smart servomotor?

    First things first, what do we mean by a "smart servomotor"? It's a good idea to establish expectations and an understanding of basic terminology.

    In our minds, a servomotor is a physical part with an output axis. The servomotor takes an input and, based on that, rotates the axis to a specific position determined by the input (with position being an angle of the axis).

    While this is a predominant use case, there might be instances where we want the servo to spin the axis and hold angular velocity. Instead of focusing on the position of the axis, we might want to control how much force the axis should apply to spinning.

    This component is fundamental for modern robotics, allowing us to use servos to articulate joints of robotic arms, spin wheels, or perform other actuating tasks.

    What Makes a Servo Smart?

    As far as I know, there is no strict definition, but generally, we can say that one can interactively communicate with a smart servo (we can send it a message, and it will respond), while simple servos just receive input in some form (such as PWM, for example).

    The benefit of a servo being smart is that, apart from telling it what to do, we can inquire about its success. With some servos, we can also monitor how well it is executing operations or how much force it required to do so.

    Motivation

    We embarked on this journey with myself, Jan Veverak Koniarik, having the most available free time, and a friend of mine (yaqswx - Jan Mrazek). At the time, we were both Ph.D. students in robotics, sharing a common need for decent open-source servomotors in our research projects. Given that my friend was also a member of the local robotics club, there were more people interested in open source servo.

    At that time, there were no open-source alternatives (though this might have changed over time; unfortunately, I lost track of other projects as I became engrossed in my own). Proprietary alternatives could be narrowed down to two options:

    Dynamixel or similar: Well known brand (at least in the academia) with well equipped features, whitch is also well priced. We did liked our experience with dynamixels we just found them too pricey for open-source robots. Traditional industry uses different brands of servos, with even much higher price than Dynamixels, so that was no way to go.

    Lewansoul (renamed to Hiwonder): Much well priced servomotors with quite a decent hardware quality (servomotor with metal gears for +-17$ at the time (2021) was a steal), however the software quality was kinda disappoiting, or more precisely: The quality of the smart part. The control loops of the servomotor were not the best and there was lack of configurability over communication protocol. That is: this was cheap enough but not capable enough.

    So, we said: what the hell, it can't be THAT hard, how about making our own servo? (Obviously this assumption failed miserably)

    Early goals

    We began by agreeing on the specifications and expected milestones. Initially, we were ambitious, but eventually, we set on set of goals that we did not fully met, but turned out to be practical enough:

    1. The firmware is written in C++.
    2. We use CMake as a build system, following standard practices as closely as possible.
    3. Basic principle: Servio is a device that responds to messages sent over a communication bus and executes a closed-loop control loop for motor and encoder.
    4. Three control modes: position control, velocity control, and current control.
    5. A minimum of a 10kHz control loop for current control.
    6. Develop basic...
    Read more »

View all 7 project logs

Enjoy this project?

Share

Discussions

Shakezzz wrote 01/26/2024 at 09:23 point

Nice project! What is this frame?

  Are you sure? yes | no

veverak wrote 01/27/2024 at 09:02 point

You mean the big frame box? It's a testjig assembled out of totem: https://totemmaker.net/

  Are you sure? yes | no

Shakezzz wrote 01/29/2024 at 07:17 point

Yes. Thank you!

  Are you sure? yes | no

veverak wrote 01/29/2024 at 17:08 point

@Shakezzz one more warning: the totem itself is lighweight plastic, it's extremely practical in the sense that it is easy to cut it to the length you want and assemble something fast (which is easy to modify)

The downside is that it is light, adding a 3mm aluminium baseplate on which it is mounted increased the rigidity a lot and it being heavy made the testjig less noisy too.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates