-
Current sensing analysis - (Part 2/2)
04/18/2024 at 06:20 • 0 commentsFinally, I had some time (and friends) to investigate what's happening with current sensing.
In Part 1, I made four interesting observations, but only deserves a response: Yes, it's normal for the voltage on the current shunt to be non-zero. (At least, that's what I was told) This is still problematic and has to be investigate further.
This time, I've altered the way the H-bridge is controlled, conducted additional measurements, and added even more measurements on top of those.
Switching mode
Let's summarize how the H-bridge controlled the motor: it drives the motor based on two digital input pins. The specifics vary with each H-bridge; what we used was:
- 01 input drives the motor in one direction.
- 10 input drives the motor in the opposite direction.
- 00 input is used when the motor should be idle.
I realized he might be onto something, as this is exactly what the datasheet recommends. Why? In 00 mode, the motor pins are not connected to anything. In 11 mode, the motor pins are shorted together.
To achieve gradual control, we use PWM. With a 25% duty cycle, we spend 25% of the time in the 10 state and 75% of the time in the 00 state.
A friend of mine, J. Mrazek (thanks!), suggested that this was not optimal and that I should switch between the 10 and 11 states instead. That is, one more option:
4. 11 input when the motor should not be idle.
After reviewing the datasheet of the DRV8251A, I realized he might be onto something, as this is exactly what the datasheet recommends. Why? In 00 mode, the motor pins are not connected to anything. In 11 mode, the motor pins are shorted together.
I won't detail all the consequences, as I'm not certain about them, but there is one practical aspect I can work with: in 11 mode, I can measure the current flowing through the motor (thanks to the internal current sensing of the H-bridge).
The way I see it, switching between 01 and 11 modes allows the 01 mode to let the current flow into the motor, while the 11 mode shows how it circulates inside—both providing good insight into the torque applied by the motor, which is the real value I'm interested in.
Current readings with H-bridge in 01/11 mode
Let’s measure all the data again! We'll use the same table as in Part 1, so please refer there for an explanation.
power 0% 25% 50% 75% 100% power source current 14mA 45mA 83mA 130mA 170mA R7 voltage 25mV 172mV 210mV 245mV 267mV calculated motor current 15mA 109mA 131mA 156mA 169mA This time, the data table showed anomalies; the voltage on pin R7 no longer scaled linearly with the PWM duty cycle, which was baffling. This issue perplexed me for a while, and I was on the verge of discarding the H-bridge as this seemed bonkers.
In an attempt to understand what was happening, I also captured a screenshot of the voltage profile to observe how the voltage on R7 changed during the duration of one pulse.
As my modest drawing skills illustrate, the new measurements reveal significant changes in the voltage profile. The large red shape on the left, representing the current state, shows a symmetric rise and fall in voltage. On the right side, you can see the profile from previous measurements (using 01/00 mode). The difference is striking: in 11 mode, the voltage on R7 decreases much more slowly than in 00 mode, resulting in higher measured current per period.
The central question remains: why has the current stopped behaving linearly?
I understand that the PWM duty cycle and current might not scale linearly, but the non-linearty is too big.
Current readings with H-Bridge in 01/11 mode + current probe on scope + different power source
We managed to borrow a high-quality current sensing probe from a friend—far more expensive than I'd typically invest in hobby equipment. With this, we were able to measure the current flowing through the motor. Unfortunately, I didn't take any pictures of the experiment, so there are no images to share this time.
power 0% 25% 50% 75% 100% power source current 0.01A 0.05A 0.09A 0.13A 0.18A R7 voltage 20mV 190mV 232mV 262mV 290mV current probe 0 A 80mA 100mA 108mA 115mA Note that the current probe showed 0A at 0% duty cycle simply because we calibrated it this way. Nevertheless, it's clear that what I observed on R7 isn’t as nonsensical as it initially seemed; in fact, it accurately reflects reality. The current flowing through the motor does not scale linearly with the PWM duty cycle—or does it
Thanks goes to J.M. who realized what is going on: physics
As you can see, in my setup, the motor is free-spinning—there is no load opposing the rotation. The motor has a natural limit of rotational velocity based on voltage. Typically, free-spinning motors easily reach their maximum velocity.
This is precisely what occurs in my measurements: the current flowing through the motor does not scale linearly with the duty cycle because the servo motor achieves its maximum velocity too easily, using only a small amount of current. Once it reaches this velocity, no additional current is drawn, regardless of increases in the duty cycle.
How can we be sure? Well, J.M. had a brilliant idea to test this. We took a small DC motor and fixed its shaft so it couldn’t move. Sure enough, with this setup, we observed that the current scales linearly with the duty cycle. (Of course, I didn't take screenshots of this data either).
Conclusion? Current sensing works! \o/ (kinda)
Takeaway
Although we confirmed that everything functions well, there are some crucial observations I need to consider for future work.
First and foremost—running the servo motor free-spinning is tricky. It doesn’t mean that things won’t work, but one must be aware of the consequences, especially when current sensing is involved. I believe the servo should perform well without load, but I don’t think I want to develop it in a free-spinning state. It might be easier to start with some load on the motor to ensure everything functions correctly, and then later address the free-spinning scenario as a specific case.
Secondly, during the numerous measurements we made that night, we realized one thing: the current sensing is extremely sensitive, meaning it picks up every electrical disturbance. Whether it’s because the system is inherently noisy, or because we use brushed motors where I can detect the brushes passing by on the spinning axis, none of these factors simplify the task for the PID controller.
We discussed this issue, and I plan to explore how to increase the load on the motor and possibly add a filter to the current sensing to prevent the PID controller from being overwhelmed by these disturbances.
-
Current sensing analysis - (Part 1/2)
04/14/2024 at 19:27 • 0 commentsSo, 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 :)
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:
power 0% 25% 50% 75% 100% power source current 11 mA 31 mA 57mA 81mA 106mA R7 voltage 18 mV 50 mV 155mV 177mV 224mV calculated motor current 12 mA 32 mA 98mA 112mA 142mA What do the measured values mean? I have no conclusions yet, and frankly, I would prefer to consult this with someone more skilled in EE to ensure that I'm not missing something. The interesting observations are:
- I have no clue how well my power source or the AD2 is calibrated.
- The MCU and other non-motor-related components might be consuming around 11mA, which seems reasonable.
- The voltage on R7 is increasing at a faster rate than expected—the current at 25% is identical, but at 100%, it is 106mA versus 142mA. This might suggest that the current-scaling constant for the H-bridge differs significantly, but that is not entirely unexpected. But, what if the capacitor in the circuit is causing some issues?
- For some reason, the voltage on R7 when the power is at 0 is non-zero.
Out of all of this, only point 4 is troubling me, as it might disturb the control loops more than I would want.
Time to bother my friends and ask them some questions!
More photos
-
Servio Overview (Part 5/5) - Control
03/16/2024 at 22:57 • 0 commentsFrom 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:
- 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.
- 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.
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 estimate velocity. We employ a PID regulator that takes the actual velocity and the desired velocity as inputs and outputs the desired current to achieve the given velocity.
The challenging part was the estimation of velocity, which is why we introduced a Kalman filter into the system for processing the position readings. We configured the filter to take position as an input and output estimates of both position and velocity. The velocity estimate from the Kalman filter is what we use for velocity control. So far, it seems to be good enough and stable, although we have not yet conducted satisfactory tests for it.
This also proved to be a challenge a bit, as it required some time to modify the filter appropriately to work with continuous values, such as the angle of rotation.
In the schema below, you can see which parts of the system are active in velocity mode.
Limits
What consistently emerged as a challenge were the limits. I had a strict requirement that the configuration options for the servo should include limits for maximum and minimum position, velocity, and current. This means that users should be able to set restrictions on all these parameters, and the servo should adhere to them under all conditions. We considered these to be soft limitations - exceeding the range is not viewed as an error, but rather something that needs to be rectified.
Ensuring adherence to current limits while in current control mode is straightforward, and the same is true for other variables within their respective control modes. It is also simple to maintain awareness of current limits during position or velocity modes, as both use the current control loop, thus honoring the current limits.
The challenging part is: How do we ensure adherence to position and velocity limits in a the current mode that only activates the current loop? How can we maintain position limits in velocity mode? And how do we maintain velocity limits in position mode?
We devised a solution that adjusts the current limits based on the current position relative to the position limits. This concept is illustrated in the figure above. The red line symbolizes the current limit for the servo; as the position (`pos`) approaches the maximum position limit (`max`), the current limit decreases. When `pos` reaches the `max` value, the maximum current limit will be `0 A`.
The implication of this approach is that the servo will not be able to generate maximum current when nearing the position limits, but this was considered an acceptable compromise.
-
Servio Overview (Part 4/5) - Continuous Integration
02/16/2024 at 21:38 • 0 commentsWe 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.
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.
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.
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.
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
01/28/2024 at 21:01 • 0 commentsHardware
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:
- Make it easy for debugging (hence its large size, we made it fit within 10x10cm).
- Experiment with various components (we included extra SPI encoders and used WS2812 LEDs because we initially thought they were necessary).
- Integrate the STLINK V3-mini.
- 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 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:
- Use the STM32G431KC - it works just fine and we actually use the computing power.
- Use the DRV8870DDA as the H-bridge.
- Switch from WS2812 to standard LEDs.
- Rely on the STLINK-V3 mini.
Essentially, the second iteration intentionally narrowed the scope, and made stuff smaller.
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:
- The test jig was quite beneficial to development.
- There may be a need for various types of test jigs.
- 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.
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:
- Make it as small as reasonably possible - small enough for production use.
- Switch to the STM32H503 (practical reasons: a more readily available chip at a lower price, with higher computing power as a nice bonus).
- Switch to the DRV8251ADDAR as the H-bridge.
Apart from these different goals, we also changed the PCB designer; this time, the PCB was designed by Tomas Vavrinec. He kindly offered his services, as yaqwsx simply ran out of free time.
We just got the boards fresh out of factory!
-
Servio Overview (Part 2/5) - Software Architecture
01/27/2024 at 19:25 • 0 commentsSoftware 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 ................ Passed 0.00 sec Start 5: metrics_utest_test 5/5 Test #5: metrics_utest_test ............... Passed 0.00 sec
Simulation tests assess control loops. They involve test scenarios that provide inputs like "start at velocity `0rad/s`, after 1 second move to velocity `0.7rad/s`, and remain steady," along with criteria for successful outcomes. By integrating our control loop with a simple motor simulator, we can test without hardware, focusing solely on code from the independent layer.
Firmware tests, similar in nature to unit tests, are conducted exclusively on the embedded device. They are ideal for evaluating aspects closely tied to the hardware, such as the reliability of periodic timers. These tests are beneficial for both long-term maintenance and assessing new PCB designs.
Benchmarks form part of the firmware tests, analyzing quantitative properties like control loop frequency or CPU time spent in interrupts. These benchmarks do not directly influence test outcomes but are monitored to ensure modifications do not introduce issues. They primarily focus on the platform or board layers but also include the independent layer due to its significant impact.
Control tests, which I highly value, are essentially integration tests that employ real hardware and control loops. We use test scenarios from the simulation tests on fully powered servos, allowing us to observe the hardware’s response to various scenarios in real-time. A future blog post will cover this in detail.
Blackbox tests are conducted on the final firmware build. They involve interfacing with the servo via protobuf messages to test the frontend. The focus here is on the integration of different components.
The public repository includes unit and blackbox tests, while simulation, firmware, and control tests are in a private repository.
Test Orchestration
To manage our diverse range of tests, we developed a unified approach for test execution, crucial for effective CI integration. We created joque, a library that orchestrates test execution by concurrently running tasks with respect to their dependencies.
The test_orchestrator binary scans the servio build for unit tests, generates simulation tests, identifies firmware tests, produces control tests, and detects blackbox tests. These tasks are then executed by joque. Post-execution, the orchestrator compiles a comprehensive report from all tests.
We will provide more details on this later, as each test type requires a distinct approach within the orchestration framework. The key is that joque's abstraction is versatile enough to optimally manage each test type.
-
Servio Overview (Part 1/5) - Projects
01/26/2024 at 07:00 • 0 commentsGiven 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
01/04/2024 at 16:27 • 0 commentsThis 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:
- The firmware is written in C++.
- We use CMake as a build system, following standard practices as closely as possible.
- 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.
- Three control modes: position control, velocity control, and current control.
- A minimum of a 10kHz control loop for current control.
- Develop basic automated tests.
- The end goal is to create a replacement PCB for Lewansoul LX15D.
- Support for potentiometer, quadratic encoder, or magnetic encoder as sensory input.
Let's discuss the goals in more details:
- We chose C++ primarily because we are proficient in it. Additionally, we believe the language is well-suited for our needs.
- Our choice of CMake was less about the tool itself (which I sometimes find frustrating) and more about avoiding alternatives, such as IDEs provided by manufacturers.
- The basic principle is a solid goal as it helps filter out impractical ideas. For instance, if we consider adding battery management, we can assess its relevance to the primary function.
- The control modes define how we envision controlling the servo - position control for moving to a specific position, velocity control for moving at a certain speed, and current control for maintaining a specific current.
- Based on knowledge/discussions with more experienced people in the industry, we got a hint that the sweet magic trick to make PID control loops work reliably (which we wanted to use) is not perfect fine-tuning of parameters, but a high-frequency loop. When we asked "what is high?" we were told that we want 10kHz for the current loop and only 1kHz for velocity control loop is enough. Do we understand WHY this is so? Nah, but we went for 10kHz anyway.
- As software developers, automated testing is a natural part of our process, a practice not always common in embedded systems.
- We decided to focus solely on software and PCB, sourcing other components, like metal gears or casings, from our favorite cheap servo, the LX15D.
- The last goal was actually a point of controversy among us. Yaqwsx correctly argued that magnetic encoders are superior to potentiometers, as they are more reliable, support 360-degree rotation without a dead zone, etc. I mostly argued that that might be true, but unless I can buy a servo with a magnetic encoder mounted and positioned inside, I am not interested, as I refuse to try positioning a magnetic encoder where a potentiometer used to be. We agreed to support both. (And quadrature encoders just for the sake of completeness).
So it begins
Well, this is primarily what we began with. I recommend focusing more on the essence of the text rather than the minutiae. The overall nature of it still remains valid. In subsequent logs, I will switch modes and describe the entire project from top to bottom, inside and out, including a few tangential details along the way.
P.S: This text was NOT written by ChatGPT, that unwieldy stubborn thing was used only as glorified spellchecker.