{"id":20989865,"url":"https://github.com/robtillaart/runningangle","last_synced_at":"2025-05-14T18:32:21.534Z","repository":{"id":39653436,"uuid":"308080041","full_name":"RobTillaart/runningAngle","owner":"RobTillaart","description":"Library to average angles by means of low pass filtering","archived":false,"fork":false,"pushed_at":"2024-04-13T09:17:24.000Z","size":36,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-08-07T18:27:06.676Z","etag":null,"topics":["angle","arduino","lowpass-filter"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobTillaart.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"github":"RobTillaart","custom":"https://www.paypal.me/robtillaart"}},"created_at":"2020-10-28T16:40:39.000Z","updated_at":"2023-10-13T20:24:17.000Z","dependencies_parsed_at":"2023-11-21T15:51:06.726Z","dependency_job_id":"373c4681-89bd-44bc-989d-17a227f00c0f","html_url":"https://github.com/RobTillaart/runningAngle","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FrunningAngle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FrunningAngle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FrunningAngle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FrunningAngle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobTillaart","download_url":"https://codeload.github.com/RobTillaart/runningAngle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225305753,"owners_count":17453441,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angle","arduino","lowpass-filter"],"created_at":"2024-11-19T06:26:30.359Z","updated_at":"2024-11-19T06:26:31.016Z","avatar_url":"https://github.com/RobTillaart.png","language":"C++","funding_links":["https://github.com/sponsors/RobTillaart","https://www.paypal.me/robtillaart"],"categories":[],"sub_categories":[],"readme":"\n[![Arduino CI](https://github.com/RobTillaart/runningAngle/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)\n[![Arduino-lint](https://github.com/RobTillaart/runningAngle/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/runningAngle/actions/workflows/arduino-lint.yml)\n[![JSON check](https://github.com/RobTillaart/runningAngle/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/runningAngle/actions/workflows/jsoncheck.yml)\n[![GitHub issues](https://img.shields.io/github/issues/RobTillaart/runningAngle.svg)](https://github.com/RobTillaart/runningAngle/issues)\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/runningAngle/blob/master/LICENSE)\n[![GitHub release](https://img.shields.io/github/release/RobTillaart/runningAngle.svg?maxAge=3600)](https://github.com/RobTillaart/runningAngle/releases)\n[![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/runningAngle.svg)](https://registry.platformio.org/libraries/robtillaart/runningAngle)\n\n\n# runningAngle\n\nArduino library to calculate the running average of a series of angles.\n\n\n## Description\n\nThis library provides a class, `runningAngle`, that computes an\nexponentially weighted running average of a series of angles, such as\ncompass readings. It is aware of how angles wrap modulo 360°.\n\nThe [exponentially weighted running average][ewra] is a type of [running\naverage][ra] that averages all the past inputs with weights that\ndecrease exponentially as the inputs get older. It is a type of digital\nfilter very commonly used for smoothing noisy sensor readings. It is\nmore memory efficient than the simple running average, while providing\nsimilar smoothing capability.\n\nComputing an “average” of angular data, such as headings, is inherently\nan ambiguous problem. For example, given the headings 350° and 10°,\nthere are two possible “averages” that lie halfway between them, namely\n0° and 180°. This library assumes that the “correct” average is the one\nthat lies in the middle of the shorter arc joining the initial headings,\nthus 0°. This is the right choice for smoothing noisy sensor readings,\nassuming the peak-to-peak amplitude of the noise is not too large. Note\nthat the regular average of the numbers 350 and 10 is 180, which is not\nthe result we expect when averaging angles.\n\nThis library is a spin off of [AverageAngle][], based on [an issue][]\nraised by Edgar Bonet.\n\n[ewra]: https://en.wikipedia.org/wiki/Exponential_smoothing\n[ra]: https://en.wikipedia.org/wiki/Moving_average\n[AverageAngle]: https://github.com/RobTillaart/AverageAngle\n[an issue]: https://github.com/RobTillaart/AverageAngle/issues/1\n\n\n#### Related\n\n- https://github.com/RobTillaart/Angle\n- https://github.com/RobTillaart/AngleConvertor\n- https://github.com/RobTillaart/AverageAngle\n- https://github.com/RobTillaart/RunningAngle\n- https://github.com/RobTillaart/RunningAverage\n- https://github.com/RobTillaart/RunningMedian\n\n\n## Smoothing coefficient\n\nThe output of the filter is efficiently computed as a weighted average\nof the current input and the previous output:\n\noutput = α × current\\_input + (1 − α) × previous\\_output\n\nThe smoothing coefficient, α, is the weight of the current input in the average. \nIt is called “weight” within the library, and should be set to a value between \n0.001 and 1. The larger the weight (closer to 1), the weaker the smoothing,\nso noise will affect the average quite a bit.\nCloser to zero, new values only affect the average minimal.\n- A weight α = 1 provides no smoothing at all, as the\nfilter's output will be a copy of the input.\n- A weight α = 0 will return only the first value added. \nTherefore the weight should be minimal 0.001.\n\nThe filter has a smoothing performance similar to a simple running\naverage over N = 2/α − 1 samples. For example, α = 0.2 is similar to\naveraging over the last 9 samples.\n\nIt is important to do test runs to find the optimal coefficient (range)\nfor your application. One should be aware that the frequency of adding \nnew angles will affect the time to stabilize the output.\n\nNote also that it is possible to change the weight run-time. \nThis can be needed e.g. if the accuracy of the input improves over time.\n\n\n## Usage\n\nFirst, create a filter as an instance of `runningAngle`:\n\n```c++\nrunningAngle my_filter(runningAngle::DEGREES);\n```\n\nThe parameter of the constructor should be\n`runningAngle::DEGREES`, `runningAngle::RADIANS` or `runningAngle::GRADIANS`. \nThis parameter is optional and defaults to degrees.\n\nThen, set the “weight” smoothing coefficient:\n\n```c++\nmy_filter.setWeight(0.2);\n```\nNote: the weight defaults to 0.80 so no need to set \n\nFinally, within the main sketch's loop, feed the raw angle readings to\nthe filter's `add()` method:\n\n```c++\nfloat heading = get_a_compass_reading_somehow();\nfloat smoothed_heading = my_filter.add(heading);\n```\n\nThe method returns the smoothed reading within ± 180° (i.e. ± π radians) by default.\n\nThe returned value is easily mapped upon 0..360 by adding 360 degrees ( 2π )\nto the average if the average \u003c 0.\nOther mappings including scaling are of course possible.\n\nNote: Degree character ° = ALT-0176 (windows)\n\n\n## Interface\n\n```cpp\n#include \"runningAngle.h\"\n```\n\n#### AngleType\n\n- **enum AngleType { DEGREES, RADIANS, GRADIANS }** used to get type math right.\n\nA full circle is defined as:\n- DEGREES = 360°\n- RADIANS = 2 π = 6.283...\n- GRADIANS = 400°\n\nGRADIANS are sometimes called GON.\n\nThere also exists a type milli-radians which is effectively the \nsame as RADIANS \\* 1000. This won't be supported.\nmRad and other angle-types can be converted here - https://github.com/RobTillaart/AngleConvertor\n\n\n#### runningAngle\n\n- **runningAngle(AngleType type = DEGREES)** constructor, default to DEGREES.\n- **float add(float angle)** adds a new value using a defined weight. \nExcept for the first value after a reset, which is used as initial value. \nThe **add()** function returns the new average.\n- **void reset()** resets the internal average to 0 and weight to start \"clean\" again. \nIf needed one should call **setWeight()** again!\n- **float getAverage()** returns the current average value.\n- **void setWeight(float weight = RA_DEFAULT_WEIGHT)** sets the weight of the new added value. \nValue will be constrained between 0.001 and 1.0.\nDefault weight = RA_DEFAULT_WEIGHT == 0.80.\n- **float getWeight()** returns the current / set weight.\n- **AngleType type()** returns DEGREES, RADIANS or GRADIANS.\n- **float wrap(float angle)** wraps an angle to \\[-180..+180\u003e  \\[-PI..PI\u003e \nor \\[-200..200\u003e depending on the type set.\n\n\n#### Mode \n\n- **void setMode0()** average interval = \\[-180..180\u003e\n- **void setMode1()** average interval = \\[0..360\u003e\n- **uint8_t getMode()** returns current mode = 0 or 1.\n\n\n## Performance add()\n\nBeing the most important worker function, doing float math.\n(based on time-add.ino on UNO)\n\n|  version  |  mode  |  CPU cycles  |  us per add  |  relative  |\n|:---------:|:------:|-------------:|-------------:|-----------:|\n|   0.1.5   |    0   |      681     |  42.5625 us  |   100%     |\n|   0.2.0   |    0   |      681     |  42.5625 us  |   100%     |\n|   0.2.0   |    1   |      681     |  42.5625 us  |   100%     |\n\n\n## Future\n\n#### Must\n\n- improve documentation.\n\n#### Should\n\n- should **add()** return the average? (yes)\n  - or make a **fastAdd()** that doesn't?\n\n#### Could\n\n- add examples.\n  - compass HMC6352 lib or simulator.\n  - AS5600 angle measurement sensor\n- update unit tests.\n\n#### Wont\n\n- get statistics about the noise in the angles (stats on the delta?).\n  - not the goal of this lib ==\u003e use statistics library\n- optimize **wrap()** to be generic =\u003e no loop per type.\n  - needs variables for -180 / 180 / 360  (RAM vs PROGMEM)\n- derived class for degrees only? (max optimization)\n- runtime change of type \n  - no, too specific scenario.\n  - conversion needed?\n  - add mixed types.  45° + 3 radians = ??\n  ==\u003e user can do this. \n\n\n## Support\n\nIf you appreciate my libraries, you can support the development and maintenance.\nImprove the quality of the libraries by providing issues and Pull Requests, or\ndonate through PayPal or GitHub sponsors.\n\nThank you,\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobtillaart%2Frunningangle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobtillaart%2Frunningangle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobtillaart%2Frunningangle/lists"}