Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
9675548e2f | |||
a653f0e3d6 | |||
1c870374cd | |||
581749b7f7 | |||
73f8bd919a | |||
15be6b4674 | |||
acffaeef87 | |||
a343d97e3d | |||
1356d845b3 | |||
f8bc7a8360 | |||
be8e0b341e |
31
.clang-format
Normal file
31
.clang-format
Normal file
@ -0,0 +1,31 @@
|
||||
BasedOnStyle: LLVM
|
||||
AlignConsecutiveMacros: AcrossEmptyLines
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignTrailingComments: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
ColumnLimit: 0
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentWidth: 4
|
||||
MaxEmptyLinesToKeep: 2
|
||||
PointerAlignment: Middle
|
||||
SpaceAfterCStyleCast: true
|
||||
NamespaceIndentation: All
|
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
tab_width = 4
|
34
.gitea/workflows/pio_tests.yml
Normal file
34
.gitea/workflows/pio_tests.yml
Normal file
@ -0,0 +1,34 @@
|
||||
name: PlatformIO flow
|
||||
run-name: ${{ gitea.actor }} is testing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Configure caching
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.platformio/.cache
|
||||
key: ${{ runner.os }}-pio
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 'pypy3.9'
|
||||
- name: Install PlatformIO Core
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio
|
||||
- name: Run native unit tests
|
||||
run: |
|
||||
pio test
|
43
.gitignore
vendored
43
.gitignore
vendored
@ -1,32 +1,11 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
dist
|
||||
# C Lion
|
||||
.idea
|
||||
cmake-build-*
|
||||
CMakeLists.txt
|
||||
CMakeListsPrivate.txt
|
||||
|
45
README.md
45
README.md
@ -1,7 +1,38 @@
|
||||
# Interval
|
||||
Arduino knihovna pro časování pomocí intervalů
|
||||
Koncepce programové konstrukce aplikace pro Arduino spočívá ve dvou hlavních metodách – **setup()** a **loop()**, ve které program neustále běží.
|
||||
Pro pohodlnější práci s obsluhou periodických procesů jsem napsal jednoduchou knihovnu, která tyto úkoly umožňuje napsat velmi elegantním způsobem.
|
||||
Knihovna **Interval** vytváří časovací objekty a zpřístupňuje dvě metody – metodu **set** a metodu **expired**.
|
||||
Metoda **set** se používá k nastavení timeoutu a definici začátku časování, metoda **expired** pak slouží k ověření, zda nastavený interval již vypršel.
|
||||
Zajímavostí je, že knihovna korektně ošetřuje přetečení vnitřního milisekundového čítače, takže nehrozí nebezpečí špatného časování i při velmi dlouhé době chodu zařízení.
|
||||
# Interval - Arduino Library for Interval Timing
|
||||
|
||||

|
||||
|
||||
The design concept of an Arduino application is based on two main methods – **setup()** and **loop()**, in which the program runs continuously.
|
||||
For more convenient handling of periodic processes, I have written a simple library that allows these tasks to be executed in a very elegant way.
|
||||
|
||||
The **Interval** library creates timer objects and allows you to control them using the following methods.
|
||||
|
||||
## **set**
|
||||
|
||||
The **set** method is used to set the timeout and define the start of the timing.
|
||||
|
||||
## **expired**
|
||||
|
||||
The **expired** method is used to check whether the set interval has already elapsed.
|
||||
|
||||
## **setOneshot**
|
||||
|
||||
The **setOneshot** method configures the timer for a one-shot run. After it expires, the timer is stopped.
|
||||
|
||||
## **setPeriodic**
|
||||
|
||||
The **setPeriodic** method configures the timer for a periodic run. After expiration, the timer is automatically reset to the specified time.
|
||||
|
||||
## **reload**
|
||||
|
||||
The **reload** method restarts the timer with the last specified time constant.
|
||||
|
||||
## **elapsed**
|
||||
|
||||
The **elapsed** method returns the time that has elapsed since the timer started.
|
||||
|
||||
## **remains**
|
||||
|
||||
The **remains** method returns the time remaining until the timer finishes, or until a new cycle begins.
|
||||
|
||||
An interesting feature is that the library correctly handles the overflow of the internal millisecond counter, so there is no risk of incorrect timing even during very long operation periods.
|
||||
|
@ -10,9 +10,9 @@
|
||||
"repository":
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/Pablo2048/Interval.git"
|
||||
"url": "https://git.xpablo.cz/pablo2048/Interval.git"
|
||||
},
|
||||
"version": "0.0.2",
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "*",
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=Interval
|
||||
version=0.0.2
|
||||
version=2.0.0
|
||||
author=Pavel Brychta
|
||||
maintainer=Pavel Brychta
|
||||
sentence=Make timing by using intervals instead of delay()
|
||||
|
17
platformio.ini
Normal file
17
platformio.ini
Normal file
@ -0,0 +1,17 @@
|
||||
[platformio]
|
||||
default_envs = native
|
||||
|
||||
[env:native]
|
||||
; build for desktop, not embedded device
|
||||
; expects GCC to be installed and available on the computer!
|
||||
platform = native
|
||||
; build source code in src/ too
|
||||
test_build_src = yes
|
||||
;test_framework = custom ; uncomment this if you need debugging messages to be shown (or else run with `pio test -v`)
|
||||
debug_test = test_interval
|
||||
lib_deps =
|
||||
https://github.com/FabioBatSilva/ArduinoFake/archive/refs/heads/master.zip
|
||||
build_flags =
|
||||
-std=gnu++17
|
||||
-DUNITY_OUTPUT_COLOR
|
||||
build_type = debug
|
@ -1,44 +1,82 @@
|
||||
extern "C" {
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
}
|
||||
#include <Arduino.h>
|
||||
#include "interval.h"
|
||||
|
||||
// Public Methods //////////////////////////////////////////////////////////////
|
||||
uint32_t Interval::remains(void)
|
||||
{
|
||||
|
||||
return _timeout - (millis() - _timefrom);
|
||||
// Returns the remaining time until expiration, correctly handling overflow
|
||||
uint32_t Interval::remains() const {
|
||||
uint32_t currentTime = millis();
|
||||
return _timeout - (currentTime - _timefrom);
|
||||
}
|
||||
|
||||
uint32_t Interval::elapsed(void)
|
||||
{
|
||||
|
||||
// Returns the elapsed time since the interval started
|
||||
uint32_t Interval::elapsed() const {
|
||||
return millis() - _timefrom;
|
||||
}
|
||||
|
||||
bool Interval::expired(void)
|
||||
{
|
||||
// Checks if the interval has expired based on the mode
|
||||
bool Interval::expired() {
|
||||
uint32_t currentTime = millis();
|
||||
uint32_t elapsedTime = currentTime - _timefrom;
|
||||
|
||||
if ((millis() - _timefrom) >= _timeout)
|
||||
switch (_mode) {
|
||||
case ONESHOT:
|
||||
// In oneshot mode, return true only once upon expiration
|
||||
if (elapsedTime >= _timeout) {
|
||||
if (_active) {
|
||||
_active = false;
|
||||
return true;
|
||||
else
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PERIODIC:
|
||||
// In periodic mode, return true and reset the timer on expiration
|
||||
if (elapsedTime >= _timeout) {
|
||||
_timefrom += _timeout;
|
||||
if (currentTime - _timefrom >= _timeout) {
|
||||
_timefrom = currentTime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case COMPATIBILITY:
|
||||
default:
|
||||
// In compatibility mode, always return true if the interval has expired
|
||||
if (_active) {
|
||||
if (elapsedTime >= _timeout) {
|
||||
_active = false;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true; // already expired
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Interval::set(uint32_t tmout)
|
||||
{
|
||||
|
||||
_reload = tmout;
|
||||
// Sets the interval in compatibility mode
|
||||
void Interval::set(const uint32_t tmout) {
|
||||
_timefrom = millis();
|
||||
_timeout = _reload;
|
||||
_timeout = tmout;
|
||||
_mode = COMPATIBILITY;
|
||||
_active = true;
|
||||
}
|
||||
|
||||
void Interval::reload(void)
|
||||
{
|
||||
|
||||
// Sets the interval in oneshot mode
|
||||
void Interval::setOneshot(const uint32_t tmout) {
|
||||
_timefrom = millis();
|
||||
_timeout = _reload;
|
||||
_timeout = tmout;
|
||||
_mode = ONESHOT;
|
||||
_active = true;
|
||||
}
|
||||
|
||||
// Sets the interval in periodic mode
|
||||
void Interval::setPeriodic(const uint32_t tmout) {
|
||||
_timefrom = millis();
|
||||
_timeout = tmout;
|
||||
_mode = PERIODIC;
|
||||
}
|
||||
|
||||
// Reloads the interval timer (resets the starting time)
|
||||
void Interval::reload() {
|
||||
_timefrom = millis();
|
||||
_active = true;
|
||||
}
|
||||
|
@ -1,28 +1,52 @@
|
||||
|
||||
#ifndef interval_h
|
||||
#define interval_h
|
||||
|
||||
/* Interval
|
||||
* Copyright (C) 2014, 2016, 2018 Pavel Brychta http://www.xpablo.cz
|
||||
#pragma once
|
||||
/*
|
||||
* Interval
|
||||
* Copyright (C) 2014, 2016, 2018, 2019, 2023, 2025 Pavel Brychta http://www.xpablo.cz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the MIT License
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
class Interval
|
||||
{
|
||||
protected:
|
||||
uint32_t _timefrom;
|
||||
uint32_t _timeout;
|
||||
uint32_t _reload;
|
||||
class Interval {
|
||||
public:
|
||||
bool expired(void);
|
||||
void set(uint32_t tmout);
|
||||
void reload(void);
|
||||
uint32_t elapsed(void);
|
||||
uint32_t remains(void);
|
||||
};
|
||||
// Enumeration for interval modes
|
||||
enum IntervalMode {
|
||||
COMPATIBILITY = 0,
|
||||
ONESHOT = 1,
|
||||
PERIODIC = 2
|
||||
};
|
||||
|
||||
#endif
|
||||
// EOF
|
||||
protected:
|
||||
uint32_t _timefrom = 0; // Start time of the interval
|
||||
uint32_t _timeout = 0; // Timeout duration in milliseconds
|
||||
IntervalMode _mode = COMPATIBILITY; // Current mode of operation
|
||||
bool _active = true; // Active flag for oneshot and compatibilitymode
|
||||
public:
|
||||
Interval() = default;
|
||||
|
||||
// Constructor for periodic mode with autostart
|
||||
explicit Interval(const uint32_t tmout)
|
||||
: _timefrom(millis()), _timeout(tmout), _mode(PERIODIC) {
|
||||
}
|
||||
|
||||
// Checks if the interval has expired based on the current mode
|
||||
bool expired();
|
||||
|
||||
// Sets the interval in compatibility mode
|
||||
void set(uint32_t tmout);
|
||||
|
||||
// Sets the interval in oneshot mode
|
||||
void setOneshot(uint32_t tmout);
|
||||
|
||||
// Sets the interval in periodic mode
|
||||
void setPeriodic(uint32_t tmout);
|
||||
|
||||
// Reloads the interval timer without changing mode or timeout
|
||||
void reload();
|
||||
|
||||
// Returns the elapsed time since the interval started
|
||||
[[nodiscard]] uint32_t elapsed() const;
|
||||
|
||||
// Returns the remaining time until the interval expires
|
||||
[[nodiscard]] uint32_t remains() const;
|
||||
};
|
||||
|
148
test/test_interval/main.cpp
Normal file
148
test/test_interval/main.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include <ArduinoFake.h>
|
||||
#include <unity.h>
|
||||
#include "interval.h"
|
||||
|
||||
using namespace fakeit;
|
||||
|
||||
// Global variable to simulate time in milliseconds
|
||||
static uint32_t fakeMillis = 0;
|
||||
|
||||
// Fake function to override millis()
|
||||
uint32_t fakeMillisFunc() {
|
||||
return fakeMillis;
|
||||
}
|
||||
|
||||
// setUp() is called before each test
|
||||
void setUp(void) {
|
||||
// Reset fake time
|
||||
fakeMillis = 0;
|
||||
ArduinoFakeReset();
|
||||
// Override millis() to use our fakeMillisFunc()
|
||||
When(Method(ArduinoFake(), millis)).AlwaysDo(fakeMillisFunc);
|
||||
}
|
||||
|
||||
// tearDown() is called after each test (if needed)
|
||||
void tearDown(void) {
|
||||
// No teardown actions required
|
||||
}
|
||||
|
||||
// Test for Compatibility Mode (using set() method)
|
||||
void test_CompatibilityMode(void) {
|
||||
Interval interval;
|
||||
interval.set(1000); // Set timeout to 1000ms in compatibility mode
|
||||
|
||||
fakeMillis = 500;
|
||||
TEST_ASSERT_FALSE(interval.expired()); // Should not be expired at 500ms
|
||||
|
||||
fakeMillis = 1000;
|
||||
TEST_ASSERT_TRUE(interval.expired()); // Should be expired at 1000ms
|
||||
|
||||
// In compatibility mode, subsequent calls should still return true once expired
|
||||
TEST_ASSERT_TRUE(interval.expired());
|
||||
}
|
||||
|
||||
// Test for One-shot Mode (using setOneshot() method)
|
||||
void test_OneshotMode(void) {
|
||||
Interval interval;
|
||||
interval.setOneshot(1000); // Set timeout to 1000ms in oneshot mode
|
||||
|
||||
fakeMillis = 500;
|
||||
TEST_ASSERT_FALSE(interval.expired()); // Not yet expired
|
||||
|
||||
fakeMillis = 1000;
|
||||
TEST_ASSERT_TRUE(interval.expired()); // Expired at 1000ms
|
||||
|
||||
// Subsequent calls should return false because oneshot mode triggers only once
|
||||
TEST_ASSERT_FALSE(interval.expired());
|
||||
}
|
||||
|
||||
// Test for Periodic Mode (using setPeriodic() method)
|
||||
void test_PeriodicMode(void) {
|
||||
Interval interval;
|
||||
interval.setPeriodic(1000); // Set periodic mode with 1000ms timeout
|
||||
|
||||
fakeMillis = 500;
|
||||
TEST_ASSERT_FALSE(interval.expired()); // Not yet expired
|
||||
|
||||
fakeMillis = 1000;
|
||||
TEST_ASSERT_TRUE(interval.expired()); // First period expired
|
||||
|
||||
// After expiration, timer is reset; simulate next period
|
||||
fakeMillis = 1500;
|
||||
TEST_ASSERT_FALSE(interval.expired()); // Still within new period
|
||||
|
||||
fakeMillis = 2000;
|
||||
TEST_ASSERT_TRUE(interval.expired()); // Second period expired
|
||||
}
|
||||
|
||||
// Test for elapsed() and remains() functions
|
||||
void test_elapsed_and_remains(void) {
|
||||
Interval interval;
|
||||
interval.set(1000); // Using compatibility mode, timeout set to 1000ms
|
||||
|
||||
fakeMillis = 0;
|
||||
TEST_ASSERT_EQUAL_UINT32(0, interval.elapsed());
|
||||
TEST_ASSERT_EQUAL_UINT32(1000, interval.remains());
|
||||
|
||||
fakeMillis = 500;
|
||||
TEST_ASSERT_EQUAL_UINT32(500, interval.elapsed());
|
||||
TEST_ASSERT_EQUAL_UINT32(500, interval.remains());
|
||||
|
||||
fakeMillis = 1100;
|
||||
TEST_ASSERT_EQUAL_UINT32(1100, interval.elapsed());
|
||||
// remains() uses unsigned arithmetic, so it will underflow if elapsed > timeout
|
||||
TEST_ASSERT_EQUAL_UINT32(1000 - 1100, interval.remains());
|
||||
}
|
||||
|
||||
// Test for handling overflow of millis() value
|
||||
void test_overflow(void) {
|
||||
Interval interval;
|
||||
// Set fakeMillis to a value near the maximum of 32-bit unsigned int
|
||||
fakeMillis = 0xFFFFFFF0;
|
||||
interval.set(100); // Set timeout to 100ms in compatibility mode; _timefrom = 0xFFFFFFF0
|
||||
|
||||
// Simulate time just before expiration after overflow.
|
||||
// After overflow, fakeMillis is low. For example, fakeMillis = 0x00000050.
|
||||
// Expected elapsed time = 0x00000050 + (0x100000000 - 0xFFFFFFF0) = 0x50 + 0x10 = 96ms.
|
||||
fakeMillis = 0x00000050;
|
||||
TEST_ASSERT_FALSE(interval.expired()); // Not yet expired
|
||||
|
||||
// Now simulate time after expiration:
|
||||
// Set fakeMillis = 0x00000070. Expected elapsed time = 0x70 + 0x10 = 128ms, which is >= 100ms.
|
||||
fakeMillis = 0x00000070;
|
||||
TEST_ASSERT_TRUE(interval.expired()); // Should be expired due to overflow handling
|
||||
}
|
||||
|
||||
// Test for reload() functionality in oneshot mode
|
||||
void test_reload(void) {
|
||||
Interval interval;
|
||||
interval.setOneshot(1000); // Set oneshot mode with 1000ms timeout
|
||||
|
||||
fakeMillis = 0;
|
||||
// Simulate expiration
|
||||
fakeMillis = 1100;
|
||||
TEST_ASSERT_TRUE(interval.expired()); // Expired at 1100ms
|
||||
|
||||
// Reload the interval to reset the timer and reactivate oneshot mode
|
||||
fakeMillis = 1100;
|
||||
interval.reload();
|
||||
|
||||
// Immediately after reload, should not be expired
|
||||
fakeMillis = 1200;
|
||||
TEST_ASSERT_FALSE(interval.expired());
|
||||
|
||||
// After passing the timeout from reload, it should expire again
|
||||
fakeMillis = 2200;
|
||||
TEST_ASSERT_TRUE(interval.expired());
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_CompatibilityMode);
|
||||
RUN_TEST(test_OneshotMode);
|
||||
RUN_TEST(test_PeriodicMode);
|
||||
RUN_TEST(test_elapsed_and_remains);
|
||||
RUN_TEST(test_overflow);
|
||||
RUN_TEST(test_reload);
|
||||
return UNITY_END();
|
||||
}
|
Reference in New Issue
Block a user