embedded_time_mgmt. Move relevant posts to 'dev' category

This commit is contained in:
len0rd 2025-08-08 15:51:38 -04:00
parent 3dadcccc65
commit 4f26214d56
10 changed files with 259 additions and 175 deletions

View file

@ -1,124 +0,0 @@
.. darkstar:
Darkstar Quadcopter
===================
.. post:: 31, July 2018
:tags: drone, diy
:category: Projects
:author: len0rd
:blogpost: false
The Darkstar is a RC quadcopter with the ability to fly autonomously through pre-designated waypoints, using advanced estimation techniques and object avoidance.
At least that is the end-goal. Getting there however requires resources I simply do not have as a college student (read: money). Given such constraints, building an advance copter on the cheap sounded like a good challenge. Perhaps you can learn from my mistakes if you're interested in such a venture.
Having landed a gig at an autonomous flight research center for the summer, I figured now was as good a time as any to dive into the exciting world of quadrotors - a world I've always been interested in, but never had the time to sit down and learn.
Part 1: Part Selection
----------------------
As is the case with most hobbies, much of my money was spent on initial startup costs: an RC transmitter and battery charger, while essential, would only need to be purchased once. If I want to build more rc-craft in the future, this will make it a lot easier.
.. image:: ../assets/img/writeup/darkstar/frsky-controller.jpg
**Transmitter:** I went with a `FrSky Taranis QX7 <https://www.amazon.com/dp/B06XC4C4WH>`_ for a few reasons:
1. It's "cheap": This is a pretty feature-rich transmitter for the price. Amazon has it priced fairly high, but I managed to pick mine up from `ProgressiveRC <https://www.progressiverc.com/>`_ for $105, shipping was free and fast.
2. Experience: I've been using/fiddling around with this transmitter at work while building some vehicles, so I had at least an introduction to the system, leaving me with one less thing to learn.
3. Aesthetic: From what I've seen, there are few transmitters out there that have any semblance of competent industrial design. This happens to be one of them. It's well-built with decent ergonomics and doesn't look overly tacky or intimidating. Plus it comes in white, which I really have been enjoying lately for some reason.
**Frame:** Really nothing special here. I got a `Reptile 500 <https://www.ebay.com/sch/i.html?_nkw=reptile500+v3+quadcopter+frame>`_ frame... or something. Honestly not sure on the name here. Basically my strategy was to browse `hobbyking <https://hobbyking.com>`_ til I found something I liked, then I headed over to ebay, to buy something similar. You can get frames for pretty cheap off there if you're willing to wait for it to ship from China. I wasn't, so I paid an extra $10 to get it from a US seller.
As Shipped:
.. image:: ../assets/img/writeup/darkstar/frame-boxed-sm.jpg
(Mostly) Assembled:
.. image:: ../assets/img/writeup/darkstar/frame-assembled-sm.jpg
Some of the arms were a pain to secure:
.. image:: ../assets/img/writeup/darkstar/frame-armtrouble-sm.jpg
Overall I'm pretty happy with the frame. It's simple and it was cheap. At first I thought 500mm between motors would be huge, but I've grown to like it and how much space it gives me. I have plenty of room to jam all of my various gizmos throughout. Being cheap and from ebay, it was a bit of an effort to secure all the arms to the frame; aligning the holes was more difficult than anticipated. But once mounted they're pretty solid, and have already survived a few crashes with ease.
**Motors:** `LHI 2212 920KV <https://www.amazon.com/dp/B00XQYTZQ2>`_ motors. Again, cheap and functional. The product I ordered from Amazon came with ESC's, which I thought was great, but I eventually had to swap out those ESC's, so overall, not worth the 'savings' I thought I was getting. I didn't have any idea as to what speed or motor rating I wanted, and initially I was worried that 920KV wouldn't be fast enough. However, seeing that 920 is used by the phantom reassured me and they work great. The copter isn't too acrobatic, but still has some 'umph'. Another great thing about these motors is they're built as DJI replacements, which means they also work with DJI's self-tightening props. Thank goodness! That makes portability/replacement so much easier.
I was stupid and tried to screw the motor in through all 4 holes on the arm with some aggressive dremeling, before realizing there were two holes for my motor size, and two for a different size.
.. image:: ../assets/img/writeup/darkstar/motor-attached-sm.jpg
DJI props! So much easier than the other nightmares I've worked with in the past.
.. image:: ../assets/img/writeup/darkstar/motor-djiprops-sm.jpg
**Receiver:** `FrSky D4R-II <https://www.amazon.com/gp/product/B00SWHWFWO/>`_ Cheap, compatible, capable. I would be comfortable with any FrSky CPPM receiver here.
**ESC:** `Makerfire 20A <https://www.amazon.com/gp/product/B01DEN46I6>`_ As I mentioned above, the ESC's that came with my motors had some weird issues... Actually come to think of it, it was likely my own stupidity that was the issue. It's okay though, the makerfire esc's get the job done, and are a factor of magnitude smaller/lighter than my original esc's, so I'll consider that a win.
**Flight Controller:** Flip32+ This is one of those parts that I didn't want to skimp out on or mess around with. This is the board that we use fairly exclusively at work, so I'm familiar with it, and it's a reasonable price. The cheaper Chinese versions of these have been known to have some IMU/Gyro issues, so we only buy these from `readytoflyquads <http://www.readytoflyquads.com/the-flip32-187>`_
**Battery:** Currently I'm using a 2200mAh 3S LiPo battery, but as of writing this, I'm looking at stepping up to a 4 or 5000mAh. 2200 is adequate in terms of flight time, but as I throw more gear on this thing, it'd be good to have something a bit larger.
Assembly
^^^^^^^^
Mostly I just added things on here and there as I got them in the mail. I had most of the frame pieces setup and ready to go by the time I had my big 'assembly party'... alone... on a Friday night.... Help me:
Receiver mounted on top with a twist tie, ESC's secured on the arm with some good velcro/zip ties:
.. image:: ../assets/img/writeup/darkstar/assembly-1-sm.jpg
This plate+anti-shock mount combo was intended to go in the front of the drone for fpv. While that is something I would like to eventually add, this plate also happened to be the perfect size for the Flip32. So I drilled a few holes, allowing me to mount the naze as close to the center of gravity as was reasonable:
.. image:: ../assets/img/writeup/darkstar/assembly-2-sm.jpg
Assembly can get messy:
.. image:: ../assets/img/writeup/darkstar/assembly-3-sm.jpg
This is the handiest edition I think I've made. This allows me to plug in the battery with the confidence that the motors aren't going to immediately attack me. Currently I only use one side of the switch, but in the future I plan on having one side turn everything on, while the other side only turns on the small electronics(and not the finger-slicing motors):
.. image:: ../assets/img/writeup/darkstar/assembly-4-sm.jpg
Power distribution soldered and mounted! The velcroed piece at the top is my 5V BEC:
.. image:: ../assets/img/writeup/darkstar/assembly-5-sm.jpg
Naze (aka Flip32) mounted! This is all a bit tighter than anticipated:
.. image:: ../assets/img/writeup/darkstar/assembly-6-sm.jpg
Todo: cable management
.. image:: ../assets/img/writeup/darkstar/assembly-7-sm.jpg
Also Todo: Secure the battery in a non-terrible way
.. image:: ../assets/img/writeup/darkstar/assembly-8-sm.jpg
Part 2: Fixes and Tweaks
------------------------
Its been a few weeks since writing part 1 and a lot has changed. For starters the thing actually flies now. As I mentioned in part 1 I had some troubles getting my first set of esc's to work correctly. So I replaced them with new, smaller ones, and still had trouble with them. That is until I finally sat down and figured out how to calibrate them (protip: read the instructions that come with your products!). With that squared away, this hunk of junk finally became a flyable drone as opposed to a 180deg flipping machine, as shown in the video below.
.. youtube:: TKvzu6X0z1E
Unfortunately I dont have any footage of the first successful flights(I was out by myself, as usual), but just know it was legendary. Flight 1 went well until I accidentally crashed into a pine tree, which provided a surprisingly cushy landing. Flight 2 was absolutely beautiful until I somehow managed to clothesline my drone on literally the only power line in the immediate vicinity. The rest of the initial flights followed a similar pattern. All-in-all, I ended up buying another 4 sets (with 2/set) of props, after breaking 4 of them. Seeing as this was my first real drone-flying experience, and I was still tweaking some of the settings, I chalk it up to the cost of learning.
.. image:: ../assets/img/writeup/darkstar/tweaks-1-sm.jpg
Sim Practice
^^^^^^^^^^^^
Around this time I also discovered a nifty, cheap piece of software: `fpv-freedrider <https://fpv-freerider.itch.io/fpv-freerider>`_ . This is simple but functional simulator that is handy to practice on. I simply plugged my transmitter via usb into my machine and it recognized it straight away. All I had to do was calibrate once in the software and I was off to the sim. This definitely helps with mastering some basic flight skills, but naturally its not as difficult as the real thing. And I've found the best way to learn to fly is having the constant threat of a catastrophic crash looming over you and every decision you make (while flying that is).
LED Upgrade
^^^^^^^^^^^
These days its basically an undeniable fact that RGB LEDs make everything in this world better. And you'd be a fool to think I wasn't planning on adding them from the beginning. With the quad finally getting up in the air, I needed something to make my crashes look cooler. and boy-oh-boy did the LEDs fit the bill. The LEDs are programmed through an Arduino and change state/pattern based on the CPPM input coming from the rc receiver!
I've been planning for some time to integrate an Arduino nano into the copter because of the rapid prototyping I'll be able to do with various sensors that work with it. LED control was the perfect first-step to get the Arduino project online. This was also my first time working with the Arduino and LED control is the simple (and traditional!) place to start. Here's an overview of how the LED control works as of this writing:
- When the craft is disarmed, the LEDs fade in and out
- When armed, the LEDs switch to solid illumination
- If armed, and not in autopilot mode, if the incoming CPPM command does change enough (ie the current command hasn't deviated by some delta compared to multiple past commands), then the LEDs alternate from solid to blinking about one every 1.5 seconds, until a new unique command is read.
The backbone of this project is an absolutely killer `CPPM library <https://github.com/jmparatte/CPPM>`_ developed by Jean-Marc Paratte. The library is very simple to use, and the examples are self-explanatory. It was pretty accurate with my 8-channel FrSky receiver. With that library in place, the rest was just some good 'n simple state machine logic. If you're interested in taking a peek, here's `the repository <https://github.com/len0rd/darkstar_copter>`_ . Release 0.1 has the basic LED state machine using CPPM. After that release I've added some more features, making it a bit more complex (I'll write about those later, when I know they all work properly).
Assembly was easy. For now, I'm running all 4 LEDs off one MOSFET and 1 pin on the Arduino. This is so I have more pins available for other sensors in the future, but it would also be cool to have each arm individually controlled. `Here <https://www.amazon.com/gp/product/B017X92K9Y>`_ are the LEDs I used. `This guy <https://www.youtube.com/watch?v=sVyi7yWuXxs>`_ is pretty helpful if you need help figuring out how to use a MOSFET + Arduino to control 12V LEDs. These LEDs are actually a really good reason to use a 3S LiPo battery, since its standard voltage is ~12V ish.

View file

@ -1,40 +0,0 @@
.. _ls1synth:
LS-1: Modular Synth
===================
.. post:: 04, September 2018
:tags: audio, synthesizer, diy, breadboard
:category: Projects
:author: len0rd
:blogpost: false
The LS-1 is a modular oscillator and sequencer, and includes 2 LFOs (low-frequency oscillators), one external oscillator, and the oscillator attached to the sequencer. The sequencer itself is made up of a counter which acts as a LFO/clock divider, and dual muxes to select the feedback resistance and led to display. The counter outputs and mux select inputs have ports on the front-panel allowing the user to mix and match LFO divisions with mux selects, thus creating custom sequences.
This build included a lot of firsts for me. This is the first time Ive used Eagle to create a PCB/schematic (which should honestly be considered an atrocity given I am a Computer Engineering student), my first in creating a metal case using a water jet, and in general this is my first large-scale hobby project.
Case
----
Design
^^^^^^
Originally I was hoping to pack everything into a 1U 19″ package, using the case of an old network switch I had laying around, but I soon realized to include all the I/O I wanted I would have to increase the size, so I made the logical step up to 2U. Even with the increased size, its a pretty cozy fit for the front panel. I did some prototype configurations for different control sections using cardboard and the components I was planning on using, testing which layout I found to be the most natural.
.. image:: ../assets/img/writeup/ls1synth/case-1-sm.jpg
Following this, I designed the case in Autodesk Inventor (since its free to students, Im more of a Solidworks guy personally). It had been a few years since I had needed to touch Inventor, so it was a little rough, but I got the job done. Going in I knew I was planning on cutting this on my universitys water jet, so I built it all off a single sketch, taking into account how the faces would link together. I also decided to make a timelapse of the process, mostly for my own enjoyment, but feel free to watch and mock my terrible CAD skills:
.. youtube:: iJbLcks4f_g
This timelapse shows the majority of the design, but not all of it. Following this I consulted with my ME friend about how to build a proper metal case. He suggested cutting small circles where corners would fold to make sure excess material didn't get in the way. Before the water jet, I also made some slight modifications the the front panel layout - mostly related to spacing between components.
Build
^^^^^
With the panel designed, I simply needed to export the sketch face to a format the water jet would understand. I purchased a decently high gauge sheet of steel (maybe 12? I cannot remember) from my university and they cut it to roughly the dimensions I would need. Despite being a student, many of the resources I used for this part of the project are open to the public. If you're looking to do something similar, it never hurts to check with your local university's engineering/technology department to see what resources they have available.
.. image:: ../assets/img/writeup/ls1synth/case-2-sm.jpg
With the metal sheet cut, I was ready to cut out the case with the water jet. My university charges for how much time you use a product, and since the case is not too large, this only cost me around $20. Putting the case portion of the project at ~$30 total! This was my first time using a water jet, so it was a very big deal for me. Computer Engineering is fun and all, but you don't get enough chances to play with big toys like other disciplines do.

View file

@ -5,7 +5,7 @@ Defining CANBus messages with a DBC file
.. post:: 03, December 2024
:tags: embedded, development, toolchain
:category: Projects
:category: Dev
:author: len0rd

View file

@ -4,7 +4,7 @@ Spying on a CANOpen bus
.. post:: 13, December 2024
:tags: embedded, development
:category: Projects
:category: Dev
:author: len0rd
`CANOpen <https://en.wikipedia.org/wiki/CANopen>`_ is a protocol stack that sits on top of a normal CANBus.

View file

@ -3,7 +3,7 @@ Docker for SW Development
.. post:: 01, February 2025
:tags: development, infrastructure, tools, lunch-n-learn
:category: Projects
:category: Dev
:author: len0rd
This is from a lunch-n-learn I hosted which aimed to showcase the features and advantages of using

View file

@ -5,7 +5,7 @@ Embedded Development Primer
.. post:: 05, January 2023
:tags: diy, docker, embedded, development, toolchain, advice
:category: Projects
:category: Dev
:author: len0rd
This post is meant to be an overview of some principles and practices I have found helpful for professional baremetal embedded development. This is meant for people who want to have total control over their embedded development environment and codebase.

View file

@ -3,12 +3,8 @@
Embedded Logging
================
.. post:: 10, June 2025
:tags: diy, docker, embedded, development, advice
:category: Projects
:author: len0rd
I often encounter situations in embedded development where I need to roll my own solution for logging on an MCU. The standard quick-fix to this challenge is for your application to tie a UART to printf and listen there during runtime. While this is a good start, getting embedded code to a production state will often require more.
I often encounter situations in embedded development where I need to roll my own solution for logging on an MCU. The standard quick-fix to this challenge is for your application to tie a UART to printf and then listen there during runtime. While this is a good start, getting embedded code to a production state will often require more.
In my first fulltime job out of college, the senior engineers who mentored me came up with a simple but flexible solution for logging on an embedded platform which I still use to this day. Here's a quick summary of how it works and how you can implement it.

View file

@ -0,0 +1,252 @@
.. embedded_time_management:
Embedded Time Management
========================
.. post:: 8, August 2025
:tags: embedded, development, advice
:category: Dev
:author: len0rd
As an embedded project matures, having methods to track timing and durations become necessary. In this post I'll describe how I typically implement time in an embedded project so its cross platform while also not slowing down unit tests.
Implementing monotonic time
---------------------------
Having a monotonic clock in your application is useful for a variety of situations:
- **Duration tracking**: How long has it been since an event occurred or how long did an event take?
- **Simple reset detection**: If monotonic time reported by a subsystem suddenly resets or goes backwards, the subsystem crashed and reset
- **Managing task periods**: Especially on bare-metal, if something needs to occur every X milliseconds, a monotonic clock enables this (there are only so many hardware timers/interrupts).
- **Tracking event timings**: With logging, you want a way to place events in the order the system saw them, espcially if its possible that those events are reported to you in a different order (due to interrupts, context switches, etc).
Implementation requirements
- **Global access** Timestamps should be easily accessible from anywhere on the system as soon as possible after startup
- **Abstract interface** Clock getting functions should not be tied to any particular platform. An application should be able to choose its desired clock implementation (justification for this later)
Time interface
^^^^^^^^^^^^^^
.. code-block:: cpp
/// @brief Get the applications monotonic uptime in milliseconds
///
/// "Uptime" is how long the application has been running
TimeMs uptime_ms();
/// @brief Get the applications monotonic uptime in microseconds
TimeUs uptime_us();
/// @brief Timer driver interface, a TimerDriver is used internally by the global @ref uptime_ms,
/// etc functions. Its done this way so users have the option to choose a specific timer
/// implementation early on if desired. Methods here should work exactly
/// the same as their global counterparts
class ITimeDriver {
public:
virtual TimeMs uptime_ms() = 0;
virtual TimeUs uptime_us() = 0;
};
/// @brief Set the time driver this application should use
void setTimeDriver(ITimeDriver& timeDriver);
/// @brief Pointer to the global time driver. Must be defined and set to a default value by each
/// hardware platform
extern ITimeDriver* g_timeDriver;
Uptime on ARM MCUs (Cortex-M chips)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Interfaces are great and all, but how would you implement ``ITimeDriver`` on that ARM Cortex chip in your project? See: SysTick.
SysTick is a feature/peripheral that is part of the core register set on all ARM Cortex-M MCUs (and maybe more?). SysTick is a configurable prescaled timer which has an interrupt tied to it. Given its general availability, its a great option for implementing timing information. You will find projects like FreeRTOS utilize SysTick to run its scheduler.
To keep it brief, SysTick has 3 registers we care about:
- ``SYST_CSR``: Control and Status Register. Use to enable/disable the counter and interrupt.
- ``SYST_RVR``: Reload Value Register. Also called "Load" register. This is the value the counter will start at, count down from, and roll-under to once it reaches 0
- ``SYST_CVR``: Current Value Register. The current count.
Timing-wise SysTick will usually be tied to the core clock, so you can use that rate to calculate the RVR value for your required timing accuracy. SysTick should have its own ISR which is triggered at the end of every count. Keep it at a high priority and use it to add to your own counter to keep track of overall ticks.
To bring this all together, here's a simple ``ITimeDriver`` implementation for an application running bare metal on a Microchip ATSAMD20
.. code-block:: cpp
// have Systick rollunder and generate an interrupt at 1000Hz. Makes it easy to count milliseconds
const uint32_t TIME_TICK_RATE_HZ = 1000;
void TimeDriverMcSamd20::setup() {
SysTick->CTRL = 0;
SysTick->VAL = 0;
// load is the value the counter counts from before rolling under and generating a tick. Counts at
// the core clock rate.
// You either need to solve for your CLK_PLL_VAL_HZ or have it hard-coded
SysTick->LOAD = (CLK_PLL_VAL_HZ / TIME_TICK_RATE_HZ) - 1;
// - enable TICK interrupt
// - use processor clock for clock source
// - Enable the SysTick counter
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
}
static uint32_t s_tickCount = 0;
extern "C" {
/// SysTick ISR
void SysTick_Handler(void) {
// Reading control register clears the count flag
(void)SysTick->CTRL;
s_tickCount++;
}
}
TimeMs TimeDriverMcSamd20::uptime_ms() {
return s_tickCount;
}
What about microsecond resolution? Well we can use the current value of the counter along with our knowledge of how often its rolling under:
.. code-block:: cpp
TimeUs TimeDriverMcSamd20::uptime_us()
// Calculating best estimate of current microseconds:
//
// now_us = (now_ms * 1000) + SysTickCounts * tickCountsPerMicro
// = (base_us) + (SysTick.LOAD - SysTick.Count) * (microseconds_per_SysTick /
// counts_per_SysTick)
// = (base_us) + (SysTick.LOAD - SysTick.Count) * (1E6 / TIME_TICK_RATE_HZ) / SysTick.LOAD
TimeUs base_us = this->uptime_ms() * 1000;
// per the TRM current/reload values are 24bit
uint32_t tickCounterValue = (SYSTICK_CURRENT_VALUE_REG) & 0x00ffffff;
uint32_t tickCounterReload = (SYSTICK_LOAD_REG) & 0x00ffffff;
TimeUs sub_us = ((tickCounterReload - tickCounterValue) * (1000000 / TIME_TICK_RATE_HZ)) / tickCounterReload;
return base_us + sub_us;
}
Timing requirements in unit tests
---------------------------------
By abstracting the hardware time driver with ``ITimeDriver``, any implementation can be substituted in. My favorite use-case of this is creating an implementation that fakes time elapsing with a variable. For instance:
.. code-block:: cpp
class TimeDriverFake : public ITimeDriver {
public:
TimeMs uptime_ms() override { return m_currentUs / 1000; }
TimeUs uptime_us() override { return m_currentUs; }
void elapse_ms(TimeMs msToElapse) { m_currentUs += (msToElapse * 1000); }
void elapse_us(TimeUs usToElapse) { m_currentUs += usToElapse; }
void setUptime(TimeUs uptimeUs) { m_currentUs = uptimeUs; }
private:
TimeUs m_currentUs = 0;
};
This simple abstraction + fake provides a lot of benefits:
- Decrease test execution time. No more literal delays/sleeps in your tests
- You have full control of time. If you need literal sleeps, you dont have to use TimeDriverFake in that test.
- Tests of modules that have timing requirements will be more repeatable across environments/systems
On repeatability
^^^^^^^^^^^^^^^^
Unit test repeatability is one of large motivators for why this time management system was implemented in my first job. I worked on a large embedded project that was thoroughly unit tested, to the order of a couple thousand unit tests.
Occassionally we would have random failures in CI when running the test suite. We discovered that these failures always occurred in tests that had some type of timing requirement. We tried loosening timing requirements in these tests, but the random errors persisted. This was puzzling since the test suite and individual tests ran locally without issue.
The issue was rooted in our CI machine being configured to run multiple jobs simultaneously. For instance, if one job's test suite was run while another job was building, the CPU would be pegged and our tight millisecond timing requirements in tests could fail. The solution? Take real hardware timing out of the picture so that we could verify the actual behavior under test. This not only made our test suite pass more reliably, but it also sped up running the entire suite significantly.
Since this lesson, my environment has contained a couple helpful bash functions that allow me to place a system under heavy load and run a process until it fails.
.. code-block:: bash
# max out all cpu cores with arbitrary task. Good for tests that need to be performed under heavy load
function burncpu() {
numProc="$(getconf _NPROCESSORS_ONLN)"
openssl speed -multi ${numProc}
}
# run whatever is passed in until it returns a non-zero exit code
function untilfail() {
while "$@"; do :; done
}
Duration and period handling
----------------------------
Once a system has monotonic uptime, its easy to add duration calculation and period checking. Having a software timer is useful for the many aspects of embedded development that have looser timing requirements. Of course anything thats especially hard-realtime should try and use a hardware timer. Using the above time functions, you can quickly create a helper class that provides elapsed time and rough periodic interval checking:
.. code-block:: cpp
/// @brief SwStopwatch base class. Typically you should use the already-extended
/// @ref SwStopwatchMs or @ref SwStopwatchUs
template <typename T>
class SwStopwatch {
public:
/// @brief Start this stopwatch
void start() { m_startTick = now(); }
/// @brief Get how much time has elapsed since the stopwatch was last
/// started via @ref start
/// @return The amount of time since the stopwatch was started
T elapsedTime() const {
T currentTs = now();
if (currentTs < m_startTick) {
// rollover case:
// + 1 since the rollover from MAX -> 0 is a tick
return (std::numeric_limits<T>::max() - m_startTick) + currentTs + 1;
}
else {
return currentTs - m_startTick;
}
}
/// @brief Returns true if at least \p stopwatchPeriod time has elapsed
/// since the last time this stopwatch was started. If the period has
/// elapsed, this stopwatch is started again Example usage:
/// @verbatim
/// if (myMsStopwatch.isPeriodElapsed(500)) {
/// // 500ms or more has elapsed, run my periodic task
/// }
/// @endverbatim
/// @param stopwatchPeriod Period to check
/// @return true if \p stopwatchPeriod has elapsed, otherwise false.
bool isPeriodElapsed(T stopwatchPeriod) {
if (elapsedTime() > stopwatchPeriod) {
start();
return true;
}
return false;
}
protected:
T m_startTick;
virtual T now() const = 0;
};
/// @brief Millisecond software stopwatch. Keeps track of milliseconds elapsed
/// since it was started
class SwStopwatchMs : public SwStopwatch<TimeMs> {
protected:
inline TimeMs now() const override { return uptime_ms(); }
};
/// @brief Microsecond software stopwatch. Keeps track of microseconds elapsed
/// since it was started. Accuracy/resolution varies by platform
class SwStopwatchUs : public SwStopwatch<TimeUs> {
protected:
inline TimeUs now() const override { return uptime_us(); }
};
From here, implementing a module that enables running callbacks periodically is trivial. However at this point its good to keep your target platforms in mind. If you are running on an RTOS or OS, a better mechanism for periodic tasks likely already exists. For instance FreeRTOS has [TimerSVC](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/05-Software-timers/02-Timer-service-daemon-task). So this is a spot where its good to think about more OS abstractions like what was done with ``ITimeDriver`` above.

View file

@ -5,7 +5,7 @@ Release Versioning In C/C++ Projects
.. post:: 10, June 2025
:tags: diy, docker, embedded, development, advice
:category: Projects
:category: Dev
:author: len0rd

View file

@ -8,7 +8,7 @@ Writing Embedded Device Drivers
.. post:: 27, May 2025
:tags: embedded, development, advice
:category: Projects
:category: Dev
:author: len0rd
In this post I wanted to quickly describe some of the guiding principles I follow when writing device drivers in C++ for an embedded project. By "device driver" I mean the code that provides an interface between your application and some hardware device on an embedded system. For instance: an IMU, a temperature sensor, an ADC sensor, a motor controller, etc.