├── .github └── workflows │ ├── LibraryBuildWithAction.yml │ ├── LibraryBuildWithScript.yml │ └── PlatformIoPublish.yml ├── LICENSE.txt ├── README.md ├── examples ├── SimpleFrequencyDetector │ ├── AVRUtils.cpp │ ├── AVRUtils.h │ └── SimpleFrequencyDetector.ino └── WhistleSwitch │ ├── AVRUtils.cpp │ ├── AVRUtils.h │ ├── EasyButtonAtInt01.h │ ├── EasyButtonAtInt01.hpp │ ├── ShowInfo.cpp │ ├── ShowInfo.h │ ├── WhistleSwitch.ino │ └── digitalWriteFast.h ├── extras ├── SimpleFrequencyDetectorPlotterOutput.png ├── SimpleFrequencyDetector_MAX9814.jpg └── WhistleSwitchPlotterOutput.png ├── keywords.txt ├── library.json ├── library.properties └── src ├── DebugLevel.h ├── FrequencyDetector.h ├── FrequencyDetector.hpp ├── MillisUtils.cpp └── MillisUtils.h /.github/workflows/LibraryBuildWithAction.yml: -------------------------------------------------------------------------------- 1 | # LibraryBuildWithAction.yml 2 | # Github workflow script to test compile all examples of an Arduino library repository. 3 | # 4 | # Copyright (C) 2020 Armin Joachimsmeyer 5 | # https://github.com/ArminJo/Github-Actions 6 | # 7 | # Before being able to push to my .github\workflows directories, 8 | # I had to create a new personal token with workflow enabled at https://github.com/settings/tokens 9 | 10 | # This is the name of the workflow, visible on GitHub UI. 11 | name: LibraryBuildWithAction 12 | 13 | on: 14 | push: # see: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request 15 | paths: 16 | - '**.ino' 17 | - '**.cpp' 18 | - '**.h' 19 | - '**LibraryBuildWithAction.yml' 20 | pull_request: 21 | 22 | jobs: 23 | build: 24 | name: ${{ matrix.arduino-boards-fqbn }} - test compiling examples 25 | 26 | runs-on: ubuntu-latest # I picked Ubuntu to use shell scripts. 27 | 28 | env: 29 | # Comma separated list without double quotes around the list. 30 | REQUIRED_LIBRARIES: #ATtinySerialOut 31 | 32 | strategy: 33 | matrix: 34 | # The matrix will produce one job for each configuration parameter of type `arduino-boards-fqbn` 35 | # In the Arduino IDE, the fqbn is printed in the first line of the verbose output for compilation as parameter -fqbn=... for the "arduino-builder -dump-prefs" command 36 | # 37 | # Examples: arduino:avr:uno, arduino:avr:leonardo, arduino:avr:nano, arduino:avr:mega 38 | # arduino:sam:arduino_due_x, arduino:samd:arduino_zero_native" 39 | # ATTinyCore:avr:attinyx5:chip=85,clock=1internal, digistump:avr:digispark-tiny, digistump:avr:digispark-pro 40 | # STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 41 | # esp8266:esp8266:huzzah:eesz=4M3M,xtal=80, esp32:esp32:featheresp32:FlashFreq=80 42 | # You may add a suffix behind the fqbn with "|" to specify one board for e.g. different compile options like arduino:avr:uno|trace 43 | ############################################################################################################# 44 | arduino-boards-fqbn: 45 | - arduino:avr:uno 46 | - digistump:avr:digispark-tiny:clock=clock1 47 | - ATTinyCore:avr:attinyx5:chip=85,clock=1internal 48 | 49 | # Specify parameters for each board. 50 | # With sketches-exclude you may exclude specific examples for a board. Use a comma separated list. 51 | ############################################################################################################# 52 | include: 53 | - arduino-boards-fqbn: digistump:avr:digispark-tiny:clock=clock1 # ATtiny85 board @1 MHz 54 | platform-url: https://raw.githubusercontent.com/ArminJo/DigistumpArduino/master/package_digistump_index.json 55 | sketches-exclude: WhistleSwitch # 101% 56 | 57 | - arduino-boards-fqbn: ATTinyCore:avr:attinyx5:chip=85,clock=1internal 58 | platform-url: http://drazzy.com/package_drazzy.com_index.json 59 | 60 | # Do not cancel all jobs / architectures if one job fails 61 | fail-fast: false 62 | 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@master 66 | 67 | - name: Checkout master version of ATtinySerialOut 68 | uses: actions/checkout@master 69 | with: 70 | repository: ArminJo/ATtinySerialOut 71 | ref: master 72 | path: CustomLibrary # must contain string "Custom" 73 | 74 | # - name: Checkout second custom library # This name must be different from th one above 75 | # uses: actions/checkout@master 76 | # with: 77 | # repository: ArminJo/Arduino-Utils 78 | # ref: master 79 | # path: SecondCustomLibrary # This path must be different from theone above but also must contain string "Custom" 80 | 81 | - name: Compile all examples 82 | uses: ArminJo/arduino-test-compile@master 83 | with: 84 | arduino-board-fqbn: ${{ matrix.arduino-boards-fqbn }} 85 | required-libraries: ${{ env.REQUIRED_LIBRARIES }} 86 | platform-url: ${{ matrix.platform-url }} 87 | sketches-exclude: ${{ matrix.sketches-exclude }} 88 | -------------------------------------------------------------------------------- /.github/workflows/LibraryBuildWithScript.yml: -------------------------------------------------------------------------------- 1 | # LibraryBuildWithScript.yml 2 | # Github workflow script to test compile all examples of an Arduino library repository. 3 | # 4 | # Copyright (C) 2020-2022 Armin Joachimsmeyer 5 | # https://github.com/ArminJo/Github-Actions 6 | # 7 | # Before being able to push to my .github\workflows directories, 8 | # I had to create a new personal token with workflow enabled at https://github.com/settings/tokens 9 | 10 | # This is the name of the workflow, visible on GitHub UI. 11 | name: LibraryBuildWithScript 12 | on: 13 | workflow_dispatch: # To run it manually 14 | description: 'manual build check' 15 | push: # see: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request 16 | paths: 17 | - '**.ino' 18 | - '**.cpp' 19 | - '**.h' 20 | - '**LibraryBuildWithScript.yml' 21 | pull_request: 22 | 23 | jobs: 24 | build: 25 | name: ${{ matrix.arduino-boards-fqbn }} - test compiling examples 26 | 27 | runs-on: ubuntu-latest # I picked Ubuntu to use shell scripts. 28 | 29 | env: 30 | # Comma separated list without double quotes around the list. 31 | REQUIRED_LIBRARIES: #ATtinySerialOut # is loaded as custom library below 32 | 33 | strategy: 34 | matrix: 35 | # The matrix will produce one job for each configuration parameter of type `arduino-boards-fqbn` 36 | # In the Arduino IDE, the fqbn is printed in the first line of the verbose output for compilation as parameter -fqbn=... for the "arduino-builder -dump-prefs" command 37 | # 38 | # Examples: arduino:avr:uno, arduino:avr:leonardo, arduino:avr:nano, arduino:avr:mega 39 | # arduino:sam:arduino_due_x, arduino:samd:arduino_zero_native" 40 | # ATTinyCore:avr:attinyx5:chip=85,clock=1internal, digistump:avr:digispark-tiny, digistump:avr:digispark-pro 41 | # STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 42 | # esp8266:esp8266:huzzah:eesz=4M3M,xtal=80, esp32:esp32:featheresp32:FlashFreq=80 43 | # You may add a suffix behind the fqbn with "|" to specify one board for e.g. different compile options like arduino:avr:uno|trace 44 | ############################################################################################################# 45 | arduino-boards-fqbn: 46 | - arduino:avr:uno 47 | - arduino:avr:uno|All-DEBUG 48 | - digistump:avr:digispark-tiny:clock=clock1 49 | # - ATTinyCore:avr:attinyx5:chip=85,clock=1internal 50 | 51 | # Specify parameters for each board. 52 | # With sketches-exclude you may exclude specific examples for a board. Use a comma separated list. 53 | ############################################################################################################# 54 | include: 55 | - arduino-boards-fqbn: arduino:avr:uno|All-DEBUG # Uno board with -DDEBUG for all examples 56 | sketches-exclude: 50Hz # Comma separated list of example names to exclude in build 57 | build-properties: 58 | All: -DDEBUG -DINFO 59 | WhistleSwitch: -DDEBUG 60 | 61 | - arduino-boards-fqbn: digistump:avr:digispark-tiny:clock=clock1 # ATtiny85 board @1 MHz 62 | platform-url: https://raw.githubusercontent.com/ArminJo/DigistumpArduino/master/package_digistump_index.json 63 | sketch-names: SimpleFrequencyDetector.ino,50Hz.ino # Comma separated list of sketch names (without path, but with extension) or patterns to use in build 64 | build-properties: 65 | All: -DINFO 66 | # WhistleSwitch: is 6 bytes too big :-( 67 | 68 | # - arduino-boards-fqbn: ATTinyCore:avr:attinyx5:chip=85,clock=1internal 69 | # platform-url: http://drazzy.com/package_drazzy.com_index.json 70 | 71 | # Do not cancel all jobs / architectures if one job fails 72 | fail-fast: false 73 | 74 | steps: 75 | - name: Checkout 76 | uses: actions/checkout@master 77 | 78 | - name: Checkout master version of ATtinySerialOut 79 | uses: actions/checkout@master 80 | with: 81 | repository: ArminJo/ATtinySerialOut 82 | ref: master 83 | path: CustomLibrary # must contain string "Custom" 84 | 85 | # - name: Checkout second custom library # This name must be different from th one above 86 | # uses: actions/checkout@vmaster 87 | # with: 88 | # repository: ArminJo/Arduino-Utils 89 | # ref: master 90 | # path: SecondCustomLibrary # This path must be different from theone above but also must contain string "Custom" 91 | 92 | # Use the arduino-test-compile script, because it is faster 93 | - name: Compile all examples using the bash script arduino-test-compile.sh 94 | env: 95 | # Passing parameters to the script by setting the appropriate ENV_* variables. 96 | ENV_ARDUINO_BOARD_FQBN: ${{ matrix.arduino-boards-fqbn }} 97 | ENV_PLATFORM_URL: ${{ matrix.platform-url }} 98 | ENV_REQUIRED_LIBRARIES: ${{ env.REQUIRED_LIBRARIES }} # is empty here 99 | ENV_SKETCHES_EXCLUDE: ${{ matrix.sketches-exclude }} 100 | ENV_BUILD_PROPERTIES: -DDEBUG,-DINFO # is converted to '{ "All": "-DDEBUG -DINFO" }' 101 | ENV_SKETCH_NAMES: ${{ matrix.sketch-names }} 102 | ENV_SKETCH_NAMES_FIND_START: . 103 | ENV_DEBUG_INSTALL: true 104 | ENV_DEBUG_COMPILE: true 105 | 106 | run: | 107 | wget --quiet https://raw.githubusercontent.com/ArminJo/arduino-test-compile/master/arduino-test-compile.sh 108 | ls -l arduino-test-compile.sh 109 | chmod +x arduino-test-compile.sh 110 | ./arduino-test-compile.sh 111 | -------------------------------------------------------------------------------- /.github/workflows/PlatformIoPublish.yml: -------------------------------------------------------------------------------- 1 | # PlatformIoPublish.yml 2 | # Github workflow script to publish a release to PlatformIo. 3 | # 4 | # Copyright (C) 2021-2023 Armin Joachimsmeyer 5 | # https://github.com/ArminJo/Github-Actions 6 | # 7 | 8 | # This is the name of the workflow, visible on GitHub UI. 9 | name: PlatformIo publishing 10 | on: 11 | workflow_dispatch: # To run it manually 12 | description: manual PlatformIo publishing 13 | release: # see: https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#example-using-multiple-events-with-activity-types-or-configuration 14 | types: 15 | - created 16 | 17 | jobs: 18 | publish: 19 | runs-on: ubuntu-latest 20 | steps: 21 | 22 | - name: Checkout 23 | uses: actions/checkout@master 24 | 25 | - name: Set up Python 26 | uses: actions/setup-python@master 27 | with: 28 | python-version: '3.x' 29 | 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install platformio 34 | 35 | - name: Build and publish 36 | env: 37 | PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_TOKEN }} 38 | run: | 39 | pio package publish --owner arminjo --non-interactive 40 | # run: | 41 | # pio package pack 42 | # pio package publish --owner arminjo --non-interactive 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # [Frequency Detector](https://github.com/ArminJo/Arduino-FrequencyDetector) Library for Arduino and ATtinys 4 | 5 | Detects frequency **from 38 Hz to 9612 Hz** and works even on an ATTiny85 with 1 MHz up to 4806 Hz.
6 | The input signal can be plotted to the Arduino Serial Plotter resulting in a **simple Oscilloscope** to test the internal signal.
7 | Only tested on ATtiny85 and ATmega328P. 8 | 9 | [![Badge License: GPLv3](https://img.shields.io/badge/License-GPLv3-brightgreen.svg)](https://www.gnu.org/licenses/gpl-3.0) 10 |     11 | [![Badge Version](https://img.shields.io/github/v/release/ArminJo/Arduino-FrequencyDetector?include_prereleases&color=yellow&logo=DocuSign&logoColor=white)](https://github.com/ArminJo/Arduino-FrequencyDetector/releases/latest) 12 |     13 | [![Badge Commits since latest](https://img.shields.io/github/commits-since/ArminJo/Arduino-FrequencyDetector/latest?color=yellow)](https://github.com/ArminJo/Arduino-FrequencyDetector/commits/master) 14 |     15 | [![Badge Build Status](https://github.com/ArminJo/Arduino-FrequencyDetector/workflows/LibraryBuildWithAction/badge.svg)](https://github.com/ArminJo/Arduino-FrequencyDetector/actions) 16 |     17 | ![Badge Hit Counter](https://visitor-badge.laobi.icu/badge?page_id=ArminJo_Arduino-FrequencyDetector) 18 |
19 |
20 | [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) 21 | 22 | Available as [Arduino library "Arduino-FrequencyDetector"](https://www.arduinolibraries.info/libraries/frequency-detector). 23 | 24 | [![Button Install](https://img.shields.io/badge/Install-brightgreen?logoColor=white&logo=GitBook)](https://www.ardu-badge.com/Arduino-FrequencyDetector) 25 |     26 | [![Button Changelog](https://img.shields.io/badge/Changelog-blue?logoColor=white&logo=AzureArtifacts)](https://github.com/ArminJo/Arduino-FrequencyDetector?tab=readme-ov-file#revision-history) 27 | 28 |
29 | 30 | #### If you find this library useful, please give it a star. 31 | 32 | 🌎 [Google Translate](https://translate.google.com/translate?sl=en&u=https://github.com/ArminJo/Arduino-FrequencyDetector) 33 | 34 |
35 | 36 | YouTube video of whistle switch example in action. 37 | 38 | [![Demonstration of 3 whistle switches in one room](https://i.ytimg.com/vi/_e2mElB8zJs/hqdefault.jpg)](https://www.youtube.com/watch?v=_e2mElB8zJs) 39 | 40 |
41 | 42 | # Internal operation 43 | This library analyzes a (microphone) signal and outputs the detected frequency. It simply counts zero crossings and **it does not use FFT**. 44 | The ADC sample data is **not** stored in RAM, only the period lengths (between triggers) are stored in the `PeriodLength[]` array, 45 | which is a byte array and has the size of `NUMBER_OF_SAMPLES / 8`.
46 | It is like in the [Arduino Simple Audio Frequency Meter](https://www.arduino.cc/en/Tutorial/SimpleAudioFrequencyMeter) but includes additional noise check.
47 | The **timer 0 interrupt**, which counts the milliseconds, **is disabled during reading** and enabled afterwards! 48 | The value of millis() is adjusted after reading.
49 | The alternative to disabling the interrupt is getting partially invalid results! 50 | 51 | There are 3 detection ranges available: 52 | - `FREQUENCY_RANGE_HIGH` -> 13 µs/sample -> **300 to 9612** Hz with 1024 samples and **600 to 9612** Hz with 512 samples. 53 | - `FREQUENCY_RANGE_DEFAULT` -> 52 µs/sample -> **75 to 2403 Hz with 1024 samples** and **150 to 2403** Hz with 512 samples. 54 | - `FREQUENCY_RANGE_LOW` -> 104 µs/sample -> **38 to 1202** Hz with 1024 samples and **75 to 1202** Hz with 512 samples. 55 | 56 | ### `readSignal()` is the ADC read routine, which reads 1024 samples (512 for ATtinies) and computes the following values: 57 | 1. Frequency of signal `uint16_t FrequencyRaw` 58 | 2. Amplitude = (MaxValue - MinValue) `uint16_t SignalDelta` 59 | 3. Average = (SumOfSampleValues / NumberOfSamples) `uint16_t AverageLevel` 60 | 4. The length of each period (between 2 trigger conditions) in the `PeriodLength[]` array. 61 | 62 | ### `doEqualDistributionPlausi()` checks if the signal in the `PeriodLength[]` array is valid / not noisy. 63 | It checks if at maximum 1/8 of the periods are greater than 1.5 or less than 0.75 of the average period. 64 | If not, the value of `FrequencyRaw` is overwritten with the error code `SIGNAL_DISTRIBUTION_PLAUSI_FAILED`. 65 | 66 | ### `computeDirectAndFilteredMatch()` waits for n matches within a given frequency range (FrequencyMatchLow - FrequencyMatchHigh) 67 | and also low pass filters the result for smooth transitions between the 3 match states (lower, match, greater). It computes the following values: 68 | 1. Low pass filtered frequency of signal `uint16_t FrequencyFiltered;` 69 | 2. Match result `MatchStateEnum FrequencyMatchDirect;` 70 | 3. Low pass filtered match result `MatchStateEnum FrequencyMatchFiltered` 71 | 72 |
73 | 74 | # Convenience functions 75 | There are a lot of extra functions available to make using this library easier. 76 | - Functions to set parameters. 77 | - Functions for printing the internal data 78 | - Functions for printing data for the Arduino Plotter from Arduino 1.8.19. 79 | 80 |
81 | 82 | # Compile options / macros for this library 83 | To customize the library to different requirements, there are some compile options / macros available.
84 | These macros must be defined in your program **before** the line `#include "FrequencyDetector.hpp"` to take effect.
85 | Modify them by enabling / disabling them, or change the values if applicable. 86 | 87 | | Name | Default value | Description | 88 | |-|-:|-| 89 | | `PRINT_INPUT_SIGNAL_TO_PLOTTER` | disabled | Signal input data is stored and can be printed together with trigger levels using `printInputSignalValuesForArduinoPlotter()` like in the *SimpleFrequencyDetector* example to implement a simple digital oscilloscope using the Arduino 1.8.19 Serial Plotter. | 90 | | `ADC_PRESCALE_VALUE_IS_NOT_CONSTANT` | disabled | Enable if you do not use the constant `PRESCALE_VALUE_DEFAULT` for parameter ADCPrescalerValue in the call of `setFrequencyDetectorReadingValues()` or `setFrequencyDetectorReadingPrescaleValue()`. | 91 | 92 | ### Arduino Plotter output of SimpleFrequencyDetector example with PRINT_INPUT_SIGNAL_TO_PLOTTER enabled 93 | ![Arduino Plotter output of SimpleFrequencyDetector example with `PRINT_INPUT_SIGNAL_TO_PLOTTER` enabled](https://github.com/ArminJo/Arduino-FrequencyDetector/blob/master/extras/SimpleFrequencyDetectorPlotterOutput.png) 94 | 95 | ### Arduino Plotter output of SimpleFrequencyDetector example with PRINT_RESULTS_TO_SERIAL_PLOTTER enabled 96 | Above you see the `FrequencyRaw` I whistled with all the dropouts and the `FrequencyFiltered` without dropouts but with a slight delay if the `FrequencyRaw` changes.
97 | The WhistleSwitch example uses the `FrequencyMatchFiltered` value, to decide if a match happens. The value for a valid match is **200**. At 80 and 140 you see 2 short and at 300 you see a long valid match. 98 | 99 | ![Arduino Plotter output of whistle switch in action](https://github.com/ArminJo/Arduino-FrequencyDetector/blob/master/extras/WhistleSwitchPlotterOutput.png) 100 | 101 |
102 | 103 | # Dependencies 104 | The [ATtinySerialOut library](https://github.com/ArminJo/ATtinySerialOut) is required for [print functions](src/FrequencyDetector.h#L312) on ATtinies. 105 | 106 |
107 | 108 | # SimpleFrequencyDetector example 109 | This example reads analog signal e.g. from MAX9814 Module at A1 and computes the frequency. 110 | If frequency is in the range of 1400 to 1700 Hz, the Arduino built in LED will light up. 111 | It prints the detected frequency as well as plausibility errors. 112 | For frequency below 500 Hz it might be good to change `FREQUENCY_RANGE_DEFAULT` to `FREQUENCY_RANGE_LOW`. 113 | 114 | By enabling `PRINT_INPUT_SIGNAL_TO_PLOTTER` you can convert the example to a simple DSO.
115 | By enabling `PRINT_RESULTS_TO_SERIAL_PLOTTER` you can watch the [generated output](https://github.com/ArminJo/Arduino-FrequencyDetector#arduino-plotter-output-of-whistleswitch-in-action) of the library.
116 | For both. the output must be displayed in the Arduino Plotter of Arduino 1.8.19. 117 | 118 | SimpleFrequencyDetector on breadboard with MAX9814 Module 119 | ![SimpleFrequencyDetector on breadboard with MAX9814 Module](https://github.com/ArminJo/Arduino-FrequencyDetector/blob/master/extras/SimpleFrequencyDetector_MAX9814.jpg) 120 | YouTube Demonstration of SimpleFrequencyDetector with MAX9812 Module 121 | 122 | [![Demonstration of SimpleFrequencyDetector](https://img.youtube.com/vi/tsxfSx0iY5s/0.jpg)](https://www.youtube.com/watch?v=tsxfSx0iY5s) 123 | 124 |
125 | 126 | # WhistleSwitch example 127 | The WhistleSwitch example analyzes a microphone signal (I'm using a MAX9814 module from Adafruit) and **toggles an output pin**, when the main **frequency is within a specified range for a specified time**. 128 | It works as a frequency detector for a whistle pitch that drives a power relay. By using different pitches, it is possible to control multiple relays in a single room.
129 | If the pitch is lower than the specified frequency, the feedback LED flashes slowly, if the pitch is higher it flashes fast.
130 | If the (low pass filtered) match from the FrequencyDetector library holds for `MATCH_TO_LONG_MILLIS` (1 second) after switching the output, 131 | the output switches again, to return to the previous state. 132 | This can be useful if a machine-generated signal (e.g. from a vacuum cleaner) matches the range.
133 | **This example is mainly created to run on an ATtiny85 at 1 MHz, but will work also on a plain Arduino.** 134 | 135 |
136 | 137 | ## PREDEFINED RANGES 138 | After power up or reset, the feedback LED echoes the range number. Range number 10 indicates an individual range, programmed by advanced selecting. 139 | The timeout state is signaled by short LED pulses after the range number feedback (no short pulse -> no timeout enabled). 140 | 141 | the following pitch ranges are predefined for easy selection: 142 | 1. 1700 - 2050 Hz -> 350 Hz A6-C6 143 | 2. 1500 - 1680 Hz -> 180 Hz FS6-GS6 144 | 3. 1300 - 1480 Hz -> 180 Hz E6-FS6 145 | 4. 1150 - 1280 Hz -> 130 Hz D6-DS6 146 | 5. 1000 - 1130 Hz -> 130 Hz C6-CS6 147 | 6. 900 - 990 Hz -> 90 Hz A5-B5 148 | 149 | 7. 1550 - 1900 Hz -> 350 Hz G6-AS6 150 | 8. 1250 - 1530 Hz -> 380 Hz DS6-G6 151 | 9. 1000 - 1230 Hz -> 230 Hz C6-DS6 152 | 153 | 10. Dummy range, if chosen, disables "relay on" timeout handling. 154 | 11. Dummy range, if chosen, sets "relay on" timeout to 2 hours. 155 | 12. Dummy range, if chosen, sets "relay on" timeout to 4 hours. 156 | 13. Dummy range, if chosen, sets "relay on" timeout to 8 hours. 157 | 158 | ## SELECTING the RANGE 159 | Selecting is started by a long press of the button. 160 | After `BUTTON_PUSH_ENTER_PROGRAM_SIMPLE_MILLIS` (1.5 seconds), the feedback LED flashes once for signaling simple programming mode. 161 | After `BUTTON_PUSH_ENTER_PROGRAM_ADVANCED_MILLIS` (4 seconds), the feedback LED flashes twice for signaling advanced programming mode. 162 | After releasing the button, the selected programming mode is entered. 163 | 164 | ### SIMPLE PROGRAMMING MODE 165 | Press the button once for range 1, twice for range 2 etc. Each button press is echoed by the feedback LED. 166 | Waiting for `PROGRAM_MODE_SIMPLE_END_DETECT_MILLIS` (1.5 seconds) ends the programming mode 167 | and the feedback LED echoes the number of button presses recognized. 168 | The duration of signal match to toggle the relay is fixed at `MATCH_MILLIS_NEEDED_DEFAULT` (1.2 seconds). 169 | 170 | ### ADVANCED PROGRAMMING MODE 171 | Whistle the pitch you want to detect, then press the button again. 172 | While you press the button, the pitch range is measured. i.e. the minimum and maximum of the pitch you are whistling is stored. 173 | 174 | If you press the button again before the `PROGRAM_MODE_ADVANCED_END_DETECT_MILLIS` (3 seconds) timeout 175 | the duration of this second press is taken as the required duration for the signal match to toggle the relay. 176 | Otherwise the `MATCH_MILLIS_NEEDED_DEFAULT` (1.2 seconds) are taken. 177 | After timeout of `PROGRAM_MODE_TIMEOUT_MILLIS` (5 seconds) the advanced programming mode is ended 178 | and the effective duration is echoed by the feedback LED. 179 | 180 | ## TIMEOUT 181 | After a timeout of `TIMEOUT_RELAY_ON_SIGNAL_MINUTES`_(1 to 3) (2, 4 or 8 hours) the relay goes OFF for 1 second. 182 | In the next `TIMEOUT_RELAY_SIGNAL_TO_OFF_MINUTES` (3) minutes you must then press the button or whistle the pitch to cancel the timeout, otherwise the relay will switch OFF afterwards. 183 | Cancellation of timeout is acknowledged by the LED flashing 5 times for 1 second on and off. Timeout can be switched on by selecting the dummy ranges 11 to 13 and off by selecting the dummy range 10. 184 | The setting is stored in EEPROM. Default is `TIMEOUT_RELAY_ON_SIGNAL_MINUTES_3` (8 hours). 185 | 186 | ## RESET 187 | A reset can be performed by power off/on or by pressing the button two times, each time shorter than `RESET_ENTER_BUTTON_PUSH_MILLIS` (0.12 seconds) within a `RESET_WAIT_TIMEOUT_MILLIS` (0.3 seconds) interval. 188 | 189 |
190 | 191 | # SCHEMATIC for external components of FrequencyDetector / WhistleSwitch 192 | ``` 193 | Discrete microphone amplifier with LM308 194 | 195 | + 5V _____ o--O PIN REF 196 | | o-|_____|--o | 197 | _ | 1M | _ 198 | | | | | | | 199 | | | 2k2 o---|\ | | | 1M 200 | |_| | 2| \____| |_| 201 | | _____ _____ | | /6 | ____ | | | 202 | o---|_____|----o----|_____|-------|/ o--|____|--| |--o--O PIN A1 203 | | 2k2 | 10k | 3 10k | | | 204 | --- |O MICROPHONE _ LM308 10-100nF _ 205 | --- 1 uF | | | | | 206 | | | | | 10k | | 1M 207 | _|_ _|_ |_| |_| 208 | | | 209 | | | 210 | --- _|_ 211 | --- 100 nF 212 | | 213 | _|_ 214 | ``` 215 | 216 | ``` 217 | External circuit for 1x amplification configuration on a Digispark board. 218 | 219 | + CPU 5V - * Schottky-diode 220 | o------------------------------------ o-----|<|--o-- USB 5V 221 | | | - | 222 | _ | | 223 | | | o / | 224 | 470k | | /=| Push button 225 | |_| / | 226 | 1n | ____ ____ o----------o 227 | >- | |--o--|____|--o--|____|--O PB4 550 mV | 228 | 500Hz | 3k3 | 10k to enable USB _ 229 | High _ | programming | | 230 | Pass | | --- | | * 1k5 pullup 231 | 100k | | --- 22n 2kHz Low |_| 232 | |_| | Pass | 233 | | | _____ | 234 | o----------o PB3 O---|_____|--o 235 | | * 68/22 | 236 | | __ 237 | | /\` * 3V6 Z-diode 238 | | -- 239 | | | * = assembled USB circuit on Digispark 240 | | | 241 | _|_ _|_ 242 | ``` 243 | 244 | ``` 245 | External circuit for 20x amplification configuration on a Digispark board. 246 | 247 | + CPU 5V - * Schottky-diode 248 | o------------------------------------ o-----|<|--o-- USB 5V 249 | | | - | 250 | _ | | 251 | | | o / | 252 | 680k | | /=| Push button 253 | |_| / | 254 | 100n | ____ ____ o----------o 255 | >- | |--o--|____|--o--|____|--O PB4 44 mV | 256 | 500Hz | 3k3 | 10k to enable USB _ 257 | High _ | programming | | 258 | Pass | | --- | | * 1k5 pullup 259 | 3k3 | | --- 22n 2kHz Low |_| 260 | |_| | Pass | 261 | | | _____ | 262 | o----------o--O PB3 22 mV----|_____|--o 263 | | * 68/22 | 264 | _ __ 265 | | | /\` * 3V6 Z-diode 266 | 3k3 | | -- 267 | |_| | * = assembled USB circuit on Digispark 268 | | | 269 | _|_ _|_ 270 | 271 | PB2 O-- Serial out 115200 baud 272 | PB1 O-- Feedback LED 273 | PB0 O-- Relay 274 | ``` 275 |
276 | 277 | # Revision History 278 | ### Version 2.0.2 279 | - Bug fix for overflow at `FREQUENCY_RANGE_HIGH`. 280 | 281 | ### Version 2.0.1 282 | - Updated SimpleFrequencyDetector example. 283 | - Renamed `printSignalValuesForArduinoPlotter()` to `printInputSignalValuesForArduinoPlotter()`, 284 | `printLegendForArduinoPlotter()` to `printResultLegendForArduinoPlotter()` 285 | and `printDataForArduinoPlotter()` to `printResultDataForArduinoPlotter()`. 286 | 287 | ### Version 2.0.0 288 | - Added plotter output of input signal. 289 | - Renamed `doPlausi()` to `doEqualDistributionPlausi()`. 290 | - Changed [error values](src/FrequencyDetector.h#L170) and computation. 291 | - Added documentation. 292 | - Added [`MEASURE_READ_SIGNAL_TIMING`](src/FrequencyDetector.h#L64) capability. 293 | - Refactored [WhistleSwitch example](examples/WhistleSwitch) and adapted to [`EasyButtonAtInt01`](https://github.com/ArminJo/EasyButtonAtInt01) library. 294 | - Removed blocking wait for ATmega32U4 Serial in examples. 295 | 296 | ### Version 1.1.1 297 | - Moved libraries for WhistleSwitch example. 298 | 299 | ### Version 1.1.0 300 | - Corrected formula for compensating millis(). 301 | - New field PeriodOfOneReadingMillis. 302 | - Now accept dropout values in milliseconds. 303 | - New functions `printResultLegendForArduinoPlotter()` and `printResultDataForArduinoPlotter()`. 304 | 305 | # CI 306 | The library examples are tested with GitHub Actions for the following boards: 307 | 308 | - arduino:avr:uno 309 | - digistump:avr:digispark-tiny1 310 | - ATTinyCore:avr:attinyx5:chip=85,clock=1internal 311 | -------------------------------------------------------------------------------- /examples/SimpleFrequencyDetector/AVRUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AVRUtils.cpp 3 | * 4 | * Stack, Ram and Heap utilities. 5 | * Sleep utilities. 6 | * 7 | * Copyright (C) 2016-2024 Armin Joachimsmeyer 8 | * Email: armin.joachimsmeyer@gmail.com 9 | * 10 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 11 | * 12 | * Arduino-Utils is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 20 | * See the GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | */ 26 | 27 | #include "Arduino.h" 28 | 29 | #if defined(__AVR__) && defined (SPMCSR) && !(defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)) 30 | #include "AVRUtils.h" 31 | #include 32 | #include 33 | #include 34 | #include // for __malloc_margin 35 | 36 | /* 37 | * Returns actual start of available / free heap 38 | * Usage for print: 39 | Serial.print(F("AvailableHeapStart=0x")); 40 | Serial.println((uint16_t) getAvailableHeapStart(), HEX); 41 | */ 42 | uint8_t* getAvailableHeapStart(void) { 43 | if (__brkval == 0) { 44 | // __brkval is 0 if no malloc() has happened before 45 | // __brkval = __malloc_heap_start; 46 | __brkval = &__heap_start; 47 | } 48 | return (uint8_t*) __brkval; 49 | } 50 | void printAvailableHeapStart(Print *aSerial) { 51 | aSerial->print(F("Heap start=")); 52 | aSerial->println((uint16_t) getAvailableHeapStart()); 53 | } 54 | 55 | /* 56 | * Initialize RAM between current stack and actual heap start (__brkval) with pattern 0x5A 57 | */ 58 | void initStackFreeMeasurement() { 59 | uint8_t *tHeapPtr = getAvailableHeapStart(); // This sets __brkval 60 | 61 | // Fill / paint stack 62 | do { 63 | *tHeapPtr++ = HEAP_STACK_UNTOUCHED_VALUE; 64 | } while (tHeapPtr < (uint8_t*) SP); 65 | } 66 | 67 | /* 68 | * @param aStackUnusedSizePointer points to variable which is written with amount of stack/heap not used/touched. 69 | * @return The amount of stack/heap touched since the last call to initStackFreeMeasurement() 70 | * -1 if stack was completely used 71 | * A downward search fails, because it finds an allocated variable / array on stack, which was unused! 72 | * An upward search may be wrong, and claiming too much stack used, because malloc does not initialize the memory 73 | * and the search fails with multiple mallocs and partial writing of allocated regions. 74 | * In this case you should initialize stack free measurement after releasing last heap block. 75 | */ 76 | //#include // for Serial 77 | int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer) { 78 | /* 79 | * Search for first touched value from end of current heap. 80 | */ 81 | uint16_t tStackUnused = 0; 82 | uint8_t *tHeapPtr = getAvailableHeapStart(); // __brkval 83 | while (*tHeapPtr == HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr <= (uint8_t*) SP) { 84 | tHeapPtr++; 85 | tStackUnused++; 86 | } 87 | 88 | int16_t tStackMaxUsedSize = (RAMEND + 1) - (uint16_t) tHeapPtr; 89 | 90 | *aStackUnusedSizePointer = tStackUnused; 91 | if (tStackUnused == 0) { 92 | return -1; 93 | } 94 | return tStackMaxUsedSize; 95 | } 96 | 97 | /* 98 | * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() 99 | * Example: "Stack unused=0, used=16" if stack runs into data 100 | */ 101 | void printStackMaxUsedAndUnusedSizes(Print *aSerial) { 102 | uint16_t tStackUnusedBytes; 103 | aSerial->print(F("Stack used=")); 104 | aSerial->print(RAMEND - SP); 105 | aSerial->print(F(", max used=")); 106 | aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); 107 | aSerial->print(F(", unused=")); 108 | aSerial->print(tStackUnusedBytes); 109 | aSerial->print(F(" of current total ")); 110 | aSerial->println((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); 111 | } 112 | 113 | /* 114 | * Search upwards the first two HEAP_STACK_UNTOUCHED_VALUE values after current begin of heap 115 | */ 116 | uint16_t getHeapMaxUsedSize() { 117 | uint8_t *tHeapPtr = getAvailableHeapStart(); 118 | while (*tHeapPtr != HEAP_STACK_UNTOUCHED_VALUE && *(tHeapPtr + 1) != HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr <= (uint8_t*) SP) { 119 | tHeapPtr++; 120 | } 121 | // tHeapPtr points now to lowest untouched stack position or to lowest current stack byte 122 | return tHeapPtr - (uint8_t*) __malloc_heap_start; 123 | } 124 | 125 | /* 126 | * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() 127 | * Print only if value changed. 128 | * @return true, if values changed 129 | */ 130 | bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial) { 131 | static int16_t tOldStackUsedBytes = 0; 132 | 133 | uint16_t tStackUnusedBytes; 134 | int16_t tStackMaxUsedBytes = getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes); 135 | if (tOldStackUsedBytes != tStackMaxUsedBytes) { 136 | tOldStackUsedBytes = tStackMaxUsedBytes; 137 | aSerial->print(F("Stack used=")); 138 | aSerial->print(RAMEND - SP); 139 | aSerial->print(F(", max used=")); 140 | aSerial->print(tStackMaxUsedBytes); 141 | aSerial->print(F(", unused=")); 142 | aSerial->println(tStackUnusedBytes); 143 | return true; 144 | } 145 | return false; 146 | } 147 | 148 | /* 149 | * Get amount of free Stack = current stackpointer - heap end 150 | */ 151 | uint16_t getCurrentAvailableStackSize(void) { 152 | uint16_t tAvailableHeapStart = (uint16_t) getAvailableHeapStart(); // __brkval 153 | if (tAvailableHeapStart >= SP) { 154 | return 0; 155 | } 156 | return (SP - tAvailableHeapStart); 157 | } 158 | void printCurrentAvailableStackSize(Print *aSerial) { 159 | aSerial->print(F("Currently available Stack[bytes]=")); 160 | aSerial->println(getCurrentAvailableStackSize()); 161 | } 162 | 163 | /* 164 | * Get amount of maximum available memory for malloc() 165 | * FreeRam - __malloc_margin (128 for ATmega328) 166 | */ 167 | uint16_t getCurrentAvailableHeapSize(void) { 168 | if (getCurrentAvailableStackSize() <= (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) { 169 | return 0; 170 | } 171 | // SP - __brkval - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) 172 | return getCurrentAvailableStackSize() - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); // (128) 173 | } 174 | 175 | void printCurrentAvailableHeapSize(Print *aSerial) { 176 | aSerial->print(F("Currently available Heap[bytes]=")); 177 | aSerial->println(getCurrentAvailableHeapSize()); 178 | } 179 | 180 | /* 181 | * Simple and short implementation, does not work before initStackFreeMeasurement() or first malloc() 182 | * The STACK required for this function is 4 bytes, so available numbers are 4 less than for caller. 183 | */ 184 | void printCurrentAvailableHeapSizeSimple(Print *aSerial) { 185 | aSerial->print(F("available=")); 186 | aSerial->println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); 187 | } 188 | 189 | // This define is in AVRUtils.h 190 | // #define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) 191 | 192 | void printBaseRAMData(Print *aSerial) { 193 | aSerial->print(F("__malloc_heap_start=")); 194 | aSerial->print((uint16_t) __malloc_heap_start); // = __bss_end, __heap_start in lst file 195 | aSerial->print(F("|0x")); 196 | aSerial->print((uint16_t) __malloc_heap_start, HEX); 197 | 198 | aSerial->print(F(", &__heap_start=")); 199 | aSerial->print((uint16_t) &__heap_start); 200 | aSerial->print(F("|0x")); 201 | aSerial->print((uint16_t) &__heap_start, HEX); 202 | 203 | aSerial->print(F(", __brkval=")); 204 | aSerial->print((uint16_t) __brkval); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 205 | aSerial->print(F("|0x")); 206 | aSerial->print((uint16_t) __brkval, HEX); 207 | 208 | aSerial->print(F(", __malloc_margin=")); 209 | aSerial->print((uint16_t) __malloc_margin); // =128 210 | 211 | aSerial->print(F(", SP=")); 212 | aSerial->print((uint16_t) SP); 213 | aSerial->print(F("|0x")); 214 | aSerial->print((uint16_t) SP, HEX); 215 | 216 | /* 217 | * The next 2 entries seems to be always 0 218 | */ 219 | aSerial->print(F(", __malloc_heap_end=")); 220 | aSerial->print((uint16_t) __malloc_heap_end); 221 | 222 | aSerial->print(F(", __flp=")); 223 | aSerial->print((uint16_t) __flp); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 224 | aSerial->println(); 225 | } 226 | 227 | /* 228 | * RAM starts with Data, i.e. variables initialized with values != 0, 229 | * followed by BSS, i.e. uninitalized variables (which are initialized with 0) 230 | * and variables not initialized by using attribute "__attribute__((section(".noinit")))". 231 | * It ends with the heap and the stack. 232 | * 233 | * The STACK required for this function is 8 bytes, so available numbers are 8 less than for caller. 234 | * 235 | * Sample output: 236 | * Data+BSS=445. Heap: used=770, max used=1096, available=663. Stack: available=791, used=42, max used=319, unused=188 of current total 833 237 | * Formulas: 238 | * Stack available + used = current total 239 | * Heap available + __malloc_margin (128) = Stack available 240 | * Data+BSS + Heap max used + Stack unused + Stack max used = RAMSIZE 241 | */ 242 | void printRAMInfo(Print *aSerial) { 243 | 244 | aSerial->print(F("Data+BSS=")); 245 | aSerial->print((uint16_t) &__heap_start - RAMSTART); 246 | 247 | aSerial->print(F(". Heap: used=")); 248 | aSerial->print((uint16_t) getAvailableHeapStart() - (uint16_t) &__heap_start); 249 | aSerial->print(F(", max written=")); // if Stack uses total heap, we see the stack size here :-( 250 | aSerial->print(getHeapMaxUsedSize()); 251 | aSerial->print(F(", available=")); 252 | uint16_t tStackAvailable = SP - (uint16_t) getAvailableHeapStart() + 1; 253 | aSerial->print(tStackAvailable - (uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); 254 | 255 | aSerial->print(F(". Stack: available=")); 256 | aSerial->print(tStackAvailable); 257 | aSerial->print(F(", used=")); 258 | aSerial->print(RAMEND - SP); 259 | uint16_t tStackUnusedBytes; 260 | aSerial->print(F(", max used=")); 261 | aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); 262 | aSerial->print(F(", unused=")); 263 | aSerial->print(tStackUnusedBytes); 264 | aSerial->print(F(" of current total ")); 265 | aSerial->print((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); // getAvailableHeapStart() 266 | 267 | aSerial->println(); 268 | } 269 | 270 | void set__malloc_margin(uint8_t aNewMallocMargin) { 271 | __malloc_margin = aNewMallocMargin; 272 | } 273 | 274 | void reset__malloc_margin() { 275 | __malloc_margin = 128; 276 | } 277 | 278 | bool isAddressInRAM(void *aAddressToCheck) { 279 | return (aAddressToCheck <= (void*) RAMEND); 280 | } 281 | 282 | bool isAddressBelowAvailableHeapStart(void *aAddressToCheck) { 283 | return (aAddressToCheck < getAvailableHeapStart()); 284 | } 285 | 286 | /* 287 | * Test available heap by callocing 128 bytes chunks, 288 | * If no memory available, try with 64, 32 etc up to 2, 1 byte chunks 289 | */ 290 | void testCallocSizesAndPrint(Print *aSerial) { 291 | uint8_t *tLastMallocPtr; 292 | uint16_t tMallocSize = 128; 293 | while (true) { 294 | aSerial->print(F("SP=0x")); 295 | aSerial->print(SP, HEX); 296 | aSerial->print(F(" available=")); 297 | aSerial->print(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); 298 | uint8_t *tMallocPtr = (uint8_t*) calloc(tMallocSize, 1); 299 | 300 | aSerial->print(F(" -> calloc(")); 301 | aSerial->print(tMallocSize); 302 | aSerial->print(F(",1)")); 303 | 304 | if (tMallocPtr == nullptr) { 305 | aSerial->print(F("failed ->")); 306 | tMallocSize = tMallocSize >> 1; 307 | if (tMallocSize < 1) { 308 | aSerial->println(); 309 | break; 310 | } 311 | } else { 312 | tLastMallocPtr = tMallocPtr; 313 | aSerial->print(F("=0x")); 314 | aSerial->print((uint16_t) tLastMallocPtr, HEX); 315 | aSerial->print(F(" ->")); 316 | 317 | *tLastMallocPtr = HEAP_STACK_UNTOUCHED_VALUE; // For testing detection using 2 consecutive HEAP_STACK_UNTOUCHED_VALUE 318 | *(tLastMallocPtr + tMallocSize - 1) = 0x11; 319 | } 320 | printCurrentAvailableHeapSizeSimple(aSerial); 321 | } 322 | } 323 | /******************************************** 324 | * SLEEP AND WATCHDOG STUFF 325 | ********************************************/ 326 | 327 | #ifndef _MILLIS_UTILS_H 328 | // copied from MillisUtils.h 329 | /* 330 | * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. 331 | */ 332 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 333 | #define timer0_millis millis_timer_millis // The ATTinyCore libraries use other variable name in wiring.c 334 | #endif 335 | 336 | extern volatile unsigned long timer0_millis; 337 | #endif // MILLIS_UTILS_H_ 338 | 339 | /* 340 | * For sleep modes see sleep.h 341 | * SLEEP_MODE_IDLE 342 | * SLEEP_MODE_ADC 343 | * SLEEP_MODE_PWR_DOWN 344 | * SLEEP_MODE_PWR_SAVE 345 | * SLEEP_MODE_STANDBY 346 | * SLEEP_MODE_EXT_STANDBY 347 | */ 348 | // required only once 349 | void initSleep(uint8_t tSleepMode) { 350 | sleep_enable(); 351 | set_sleep_mode(tSleepMode); 352 | } 353 | 354 | /* 355 | * Watchdog wakes CPU periodically and all we have to do is call sleep_cpu(); 356 | * aWatchdogPrescaler (see wdt.h) can be one of 357 | * WDTO_15MS, 30, 60, 120, 250, WDTO_500MS 358 | * WDTO_1S to WDTO_8S 359 | */ 360 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler) { 361 | sleep_enable() 362 | ; 363 | set_sleep_mode(tSleepMode); 364 | MCUSR = ~_BV(WDRF); // Clear WDRF in MCUSR 365 | 366 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) \ 367 | || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) \ 368 | || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 369 | #define WDTCSR WDTCR 370 | #endif 371 | // Watchdog interrupt enable + reset interrupt flag -> needs ISR(WDT_vect) 372 | uint8_t tWDTCSR = _BV(WDIE) | _BV(WDIF) | (aWatchdogPrescaler & 0x08 ? _WD_PS3_MASK : 0x00) | (aWatchdogPrescaler & 0x07); // handles that the WDP3 bit is in bit 5 of the WDTCSR register, 373 | WDTCSR = _BV(WDCE) | _BV(WDE); // clear lock bit for 4 cycles by writing 1 to WDCE AND WDE 374 | WDTCSR = tWDTCSR; // set final Value 375 | } 376 | 377 | /* 378 | * @param aWatchdogPrescaler (see wdt.h) can be one of WDTO_15MS, 30, 60, 120, 250, WDTO_500MS, WDTO_1S to WDTO_8S 379 | * 0 (15 ms) to 3(120 ms), 4 (250 ms) up to 9 (8000 ms) 380 | */ 381 | uint16_t computeSleepMillis(uint8_t aWatchdogPrescaler) { 382 | uint16_t tResultMillis = 8000; 383 | for (uint8_t i = 0; i < (9 - aWatchdogPrescaler); ++i) { 384 | tResultMillis = tResultMillis / 2; 385 | } 386 | return tResultMillis + DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN; // + for the (default) startup time. !!! But this depends from Clock Source and sleep mode !!! 387 | } 388 | 389 | /* 390 | * @param aWatchdogPrescaler (see wdt.h) can be one of WDTO_15MS, 30, 60, 120, 250, WDTO_500MS, WDTO_1S to WDTO_8S 391 | * 0 (15 ms) to 3(120 ms), 4 (250 ms) up to 9 (8000 ms) 392 | * ! I have see + 30 % deviation from nominal WDT clock! 393 | * @param aAdjustMillis if true, adjust the Arduino internal millis counter the get quite correct millis() 394 | * results even after sleep, since the periodic 1 ms timer interrupt is disabled while sleeping. 395 | * Interrupts are enabled before sleep! 396 | * !!! Do not forget to call e.g. noTone() or Serial.flush(); to wait for the last character to be sent, and/or disable interrupt sources before !!! 397 | */ 398 | void sleepWithWatchdog(uint8_t aWatchdogPrescaler, bool aAdjustMillis) { 399 | MCUSR = 0; // Clear MCUSR to enable a correct interpretation of MCUSR after reset 400 | ADCSRA &= ~ADEN; // disable ADC just before sleep -> saves 200 uA 401 | 402 | // use wdt_enable() since it handles that the WDP3 bit is in bit 5 of the WDTCSR register 403 | wdt_enable(aWatchdogPrescaler); 404 | 405 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 406 | # if !defined(timer0_millis) 407 | #define timer0_millis millis_timer_millis // The ATTinyCore + Digispark libraries use millis_timer_millis in wiring.c 408 | # endif 409 | #define WDTCSR WDTCR 410 | #endif 411 | WDTCSR |= _BV(WDIE) | _BV(WDIF); // Watchdog interrupt enable + reset interrupt flag -> requires ISR(WDT_vect) 412 | sei(); // Enable interrupts, to get the watchdog interrupt, which will wake us up 413 | sleep_cpu(); // The watchdog interrupt will wake us up from sleep 414 | 415 | // We wake up here :-) 416 | wdt_disable(); // Because next interrupt will otherwise lead to a reset, since wdt_enable() sets WDE / Watchdog System Reset Enable 417 | ADCSRA |= ADEN; 418 | 419 | /* 420 | * Since timer clock may be disabled adjust millis only if not slept in IDLE mode (SM2...0 bits are 000) 421 | */ 422 | #if defined(SM2) 423 | if (aAdjustMillis && (SMCR & ((_BV(SM2) | _BV(SM1) | _BV(SM0)))) != 0) { 424 | #elif ! defined(SMCR) 425 | if (aAdjustMillis && (MCUCR & ((_BV(SM1) | _BV(SM0)))) != 0) { 426 | #else 427 | if (aAdjustMillis && (SMCR & ((_BV(SM1) | _BV(SM0)))) != 0) { 428 | #endif 429 | timer0_millis += computeSleepMillis(aWatchdogPrescaler); 430 | } 431 | } 432 | 433 | /* 434 | * 0 -> %1 435 | * _BV(CLKPS0) -> %2 436 | * _BV(CLKPS1) -> %4 437 | * _BV(CLKPS1) | _BV(CLKPS0) -> 8 etc. up to 256 438 | */ 439 | void setclockDivisionFactor(uint8_t aDivisionBits) { 440 | CLKPR = _BV(CLKPCE); 441 | CLKPR = aDivisionBits; 442 | } 443 | 444 | #endif // defined(__AVR__) 445 | -------------------------------------------------------------------------------- /examples/SimpleFrequencyDetector/AVRUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AVRUtils.h 3 | * 4 | * Copyright (C) 2016-2024 Armin Joachimsmeyer 5 | * Email: armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * Arduino-Utils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #include "Arduino.h" 25 | 26 | #if defined(__AVR__) && defined (SPMCSR) && !(defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)) 27 | #ifndef _AVR_UTILS_H 28 | #define _AVR_UTILS_H 29 | 30 | #include 31 | #include 32 | #include 33 | #include "avr/boot.h" 34 | 35 | /* 36 | * The largest address just not allocated so far 37 | * Under Unix, the "break value" was the end of the data 38 | * segment as dynamically requested from the operating system. 39 | * Since we don't have an operating system, just make sure 40 | * that we don't collide with the stack. 41 | */ 42 | extern void *__brkval; // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 43 | extern void *__flp; // 44 | extern char __heap_start; // = __bss_end, the linker address of heap start 45 | #define HEURISTIC_ADDITIONAL_MALLOC_MARGIN 14 // No malloc() possible if size is lower than (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) 46 | 47 | /* 48 | * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. 49 | */ 50 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 51 | # if !defined(_MILLIS_UTILS_H) 52 | #define timer0_millis millis_timer_millis // The ATTinyCore libraries use other variable name in wiring.c - copied from MillisUtils.h 53 | # endif 54 | # if !defined(DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN) 55 | #define DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN 65 56 | # endif 57 | #else 58 | # if !defined(DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN) 59 | #define DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN 0 // default for Uno / Nano etc. 60 | # endif 61 | #endif 62 | 63 | extern volatile unsigned long timer0_millis; 64 | 65 | void initSleep(uint8_t tSleepMode); 66 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler); 67 | uint16_t computeSleepMillis(uint8_t aWatchdogPrescaler); 68 | void sleepWithWatchdog(uint8_t aWatchdogPrescaler, bool aAdjustMillis = false); 69 | 70 | #include 71 | 72 | uint8_t* getAvailableHeapStart(); 73 | void printAvailableHeapStart(Print *aSerial); 74 | uint16_t getCurrentAvailableStackSize(void); 75 | void printCurrentAvailableStackSize(Print *aSerial); 76 | uint16_t getCurrentAvailableHeapSize(void); 77 | void printCurrentAvailableHeapSize(Print *aSerial); 78 | void printCurrentAvailableHeapSizeSimple(Print *aSerial); 79 | #define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) 80 | 81 | #define HEAP_STACK_UNTOUCHED_VALUE 0x5A 82 | void initStackFreeMeasurement(); 83 | 84 | int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer); 85 | void printStackMaxUsedAndUnusedSizes(Print *aSerial); 86 | bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial); 87 | 88 | void printBaseRAMData(Print *aSerial); 89 | void printRAMInfo(Print *aSerial); 90 | 91 | bool isAddressInRAM(void *aAddressToCheck); 92 | bool isAddressBelowAvailableHeapStart(void *aAddressToCheck); 93 | 94 | void set__malloc_margin(uint8_t aNewMallocMargin); 95 | void reset__malloc_margin(); 96 | 97 | void testCallocSizesAndPrint(Print *aSerial); 98 | 99 | #endif // _AVR_UTILS_H 100 | #endif // defined(__AVR__) 101 | -------------------------------------------------------------------------------- /examples/SimpleFrequencyDetector/SimpleFrequencyDetector.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * SimpleFrequencyDetector.cpp 3 | * 4 | * SimpleFrequencyDetector reads an analog signal e.g. from a MAX9814 Module at A1 and computes the frequency. 5 | * If frequency is in the range of 1400 to 1700 Hz, the Arduino built in LED will light up. 6 | * 7 | * 8 | * Copyright (C) 2014-2025 Armin Joachimsmeyer 9 | * Email: armin.joachimsmeyer@gmail.com 10 | * 11 | * This file is part of Arduino-FrequencyDetector https://github.com/ArminJo/Arduino-FrequencyDetector. 12 | * 13 | * Arduino-FrequencyDetector is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | * See the GNU General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | */ 27 | 28 | /* 29 | * + 5 V / 3.3 V o--O PIN REF 30 | * | | 31 | * | _ 32 | * | | | 33 | * MAX4466 / 9814 MICROPHONE | | 1 M 34 | * AMPLIFIER / MODULE |_| 35 | * | | 36 | * |O ---------||---------o--O PIN A1 37 | * | 10 nF | 38 | * | _ 39 | * | | | 40 | * | | | 1 M 41 | * ___ |_| 42 | * | 43 | * | 44 | * ___ 45 | * 46 | */ 47 | 48 | #include 49 | 50 | /* 51 | * Pin definitions for real time status info with LEDs 52 | */ 53 | #define LED_PIN_NO_TRIGGER 5 54 | #define LED_PIN_SIGNAL_STRENGTH_LOW 6 55 | #define LED_PIN_FREQUENCY_TOO_LOW_OR_HIGH 7 56 | #define LED_PIN_PLAUSI_DISTRIBUTION_FAILED 8 57 | 58 | //#define INFO 59 | #if ! defined(LED_BUILTIN) && defined(ARDUINO_AVR_DIGISPARK) 60 | # if defined(DIGISTUMPCORE) 61 | #define LED_BUILTIN PB1 62 | # else 63 | #define LED_BUILTIN PIN_PB1 64 | # endif 65 | #endif 66 | 67 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 68 | # if defined(DIGISTUMPCORE) 69 | #define TX_PIN PB2 // (package pin 7 on Tiny85) - can use one of PB0 to PB4 (+PB5) here 70 | # endif 71 | #include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut" 72 | #endif 73 | 74 | //#define PRINT_INPUT_SIGNAL_TO_PLOTTER // If enabled, store SIGNAL_PLOTTER_BUFFER_SIZE input samples for printing to Arduino Plotter 75 | #include "FrequencyDetector.hpp" 76 | 77 | //#define PRINT_RESULTS_TO_SERIAL_PLOTTER // If enabled, this example program prints generated output values to Arduino Serial Plotter (Ctrl-Shift-L) 78 | #if defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) && defined(PRINT_RESULTS_TO_SERIAL_PLOTTER) 79 | #error Please define only one of PRINT_INPUT_SIGNAL_TO_PLOTTER or PRINT_RESULTS_TO_SERIAL_PLOTTER 80 | #endif 81 | 82 | #if defined(INFO) 83 | #include "AVRUtils.h" // for printRAMInfo() 84 | #endif 85 | 86 | void setup() { 87 | pinMode(LED_BUILTIN, OUTPUT); 88 | 89 | // initialize the digital real time LED status info pins as output 90 | pinMode(LED_PIN_NO_TRIGGER, OUTPUT); 91 | pinMode(LED_PIN_SIGNAL_STRENGTH_LOW, OUTPUT); 92 | pinMode(LED_PIN_FREQUENCY_TOO_LOW_OR_HIGH, OUTPUT); 93 | pinMode(LED_PIN_PLAUSI_DISTRIBUTION_FAILED, OUTPUT); 94 | 95 | Serial.begin(115200); 96 | #if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/ \ 97 | || defined(SERIALUSB_PID) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_attiny3217) 98 | delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! 99 | #endif 100 | // Just to know which program is running on my Arduino 101 | #if !defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 102 | Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_FREQUENCY_DETECTOR)); 103 | Serial.println( 104 | F( 105 | "LED for no trigger at pin " STR(LED_PIN_NO_TRIGGER) ", for signal strength too low at pin " STR(LED_PIN_SIGNAL_STRENGTH_LOW) ", for frequency too low or too high at pin " STR(LED_PIN_FREQUENCY_TOO_LOW_OR_HIGH) ", for distribution plausi fail at pin " STR(LED_PIN_PLAUSI_DISTRIBUTION_FAILED))); 106 | 107 | #endif 108 | 109 | /* 110 | * initialize default values for dropout counts for frequency detector. 111 | */ 112 | setFrequencyDropoutDefaults(); 113 | 114 | /* 115 | * Set channel, reference, sample rate and threshold for low signal detection. 116 | * Set reference to 1.1Volt for AC coupled signal. 117 | * This is equivalent to an additional signal amplification of around 4. 118 | */ 119 | setFrequencyDetectorReadingValues(ADC_CHANNEL_DEFAULT, INTERNAL, PRESCALE_VALUE_DEFAULT, RAW_VOLTAGE_MIN_DELTA_DEFAULT); 120 | 121 | // set my Frequency range 122 | setFrequencyDetectorMatchValues(1400, 1700); 123 | #if !defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 124 | printFrequencyMatchValues(&Serial); 125 | #endif 126 | #if defined(INFO) 127 | // do not use printCurrentAvailableStackSize(&Serial) because we may use the ATtinySerialOut as Serial 128 | Serial.print(F("Currently available Stack[bytes]=")); 129 | Serial.println(getCurrentAvailableStackSize()); 130 | #endif 131 | } 132 | 133 | void loop() { 134 | /* 135 | * Read samples and compute and output frequency and do plausi. 136 | */ 137 | uint16_t tFrequency = readSignal(); 138 | tFrequency = doEqualDistributionPlausi(); 139 | computeDirectAndFilteredMatch(tFrequency); 140 | // printPeriodLengthArray(&Serial); 141 | 142 | /* 143 | * Show (error) status on LED's 144 | */ 145 | digitalWrite(LED_PIN_SIGNAL_STRENGTH_LOW, LOW); 146 | digitalWrite(LED_PIN_FREQUENCY_TOO_LOW_OR_HIGH, LOW); 147 | digitalWrite(LED_PIN_PLAUSI_DISTRIBUTION_FAILED, LOW); 148 | digitalWrite(LED_PIN_NO_TRIGGER, LOW); 149 | 150 | if (tFrequency == SIGNAL_STRENGTH_LOW) { 151 | digitalWrite(LED_PIN_SIGNAL_STRENGTH_LOW, HIGH); 152 | } 153 | if (tFrequency == SIGNAL_FREQUENCY_TOO_LOW || tFrequency == SIGNAL_FREQUENCY_TOO_HIGH) { 154 | digitalWrite(LED_PIN_FREQUENCY_TOO_LOW_OR_HIGH, HIGH); 155 | } 156 | if (tFrequency == SIGNAL_DISTRIBUTION_PLAUSI_FAILED) { 157 | digitalWrite(LED_PIN_PLAUSI_DISTRIBUTION_FAILED, HIGH); 158 | } 159 | if (tFrequency == SIGNAL_NO_TRIGGER) { 160 | digitalWrite(LED_PIN_NO_TRIGGER, HIGH); 161 | } 162 | 163 | #if defined(PRINT_RESULTS_TO_SERIAL_PLOTTER) 164 | /* 165 | * Print computed values to Arduino Serial Plotter 166 | */ 167 | printDataForArduinoPlotter(&Serial); 168 | #endif 169 | 170 | #if defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 171 | printInputSignalValuesForArduinoPlotter(&Serial); 172 | #endif 173 | 174 | //reset match indicator led 175 | digitalWrite(LED_BUILTIN, LOW); 176 | 177 | if (tFrequency > SIGNAL_MAX_ERROR_CODE) { 178 | Serial.print(tFrequency); 179 | Serial.print(F("Hz ")); 180 | /* 181 | * No signal errors here -> compute match 182 | */ 183 | if (FrequencyDetectorControl.FrequencyMatchDirect == FREQUENCY_MATCH) { 184 | // signal match 185 | digitalWrite(LED_BUILTIN, HIGH); 186 | } else { 187 | Serial.print(F("no ")); 188 | } 189 | Serial.println(F("match")); 190 | 191 | #if !defined(PRINT_RESULTS_TO_SERIAL_PLOTTER) && !defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 192 | } else { 193 | // incompatible with Serial Plotter 194 | Serial.println(reinterpret_cast(ErrorStrings[tFrequency])); 195 | #endif 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /examples/WhistleSwitch/AVRUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AVRUtils.cpp 3 | * 4 | * Stack, Ram and Heap utilities. 5 | * Sleep utilities. 6 | * 7 | * Copyright (C) 2016-2024 Armin Joachimsmeyer 8 | * Email: armin.joachimsmeyer@gmail.com 9 | * 10 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 11 | * 12 | * Arduino-Utils is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 20 | * See the GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | */ 26 | 27 | #include "Arduino.h" 28 | 29 | #if defined(__AVR__) && defined (SPMCSR) && !(defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)) 30 | #include "AVRUtils.h" 31 | #include 32 | #include 33 | #include 34 | #include // for __malloc_margin 35 | 36 | /* 37 | * Returns actual start of available / free heap 38 | * Usage for print: 39 | Serial.print(F("AvailableHeapStart=0x")); 40 | Serial.println((uint16_t) getAvailableHeapStart(), HEX); 41 | */ 42 | uint8_t* getAvailableHeapStart(void) { 43 | if (__brkval == 0) { 44 | // __brkval is 0 if no malloc() has happened before 45 | // __brkval = __malloc_heap_start; 46 | __brkval = &__heap_start; 47 | } 48 | return (uint8_t*) __brkval; 49 | } 50 | void printAvailableHeapStart(Print *aSerial) { 51 | aSerial->print(F("Heap start=")); 52 | aSerial->println((uint16_t) getAvailableHeapStart()); 53 | } 54 | 55 | /* 56 | * Initialize RAM between current stack and actual heap start (__brkval) with pattern 0x5A 57 | */ 58 | void initStackFreeMeasurement() { 59 | uint8_t *tHeapPtr = getAvailableHeapStart(); // This sets __brkval 60 | 61 | // Fill / paint stack 62 | do { 63 | *tHeapPtr++ = HEAP_STACK_UNTOUCHED_VALUE; 64 | } while (tHeapPtr < (uint8_t*) SP); 65 | } 66 | 67 | /* 68 | * @param aStackUnusedSizePointer points to variable which is written with amount of stack/heap not used/touched. 69 | * @return The amount of stack/heap touched since the last call to initStackFreeMeasurement() 70 | * -1 if stack was completely used 71 | * A downward search fails, because it finds an allocated variable / array on stack, which was unused! 72 | * An upward search may be wrong, and claiming too much stack used, because malloc does not initialize the memory 73 | * and the search fails with multiple mallocs and partial writing of allocated regions. 74 | * In this case you should initialize stack free measurement after releasing last heap block. 75 | */ 76 | //#include // for Serial 77 | int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer) { 78 | /* 79 | * Search for first touched value from end of current heap. 80 | */ 81 | uint16_t tStackUnused = 0; 82 | uint8_t *tHeapPtr = getAvailableHeapStart(); // __brkval 83 | while (*tHeapPtr == HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr <= (uint8_t*) SP) { 84 | tHeapPtr++; 85 | tStackUnused++; 86 | } 87 | 88 | int16_t tStackMaxUsedSize = (RAMEND + 1) - (uint16_t) tHeapPtr; 89 | 90 | *aStackUnusedSizePointer = tStackUnused; 91 | if (tStackUnused == 0) { 92 | return -1; 93 | } 94 | return tStackMaxUsedSize; 95 | } 96 | 97 | /* 98 | * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() 99 | * Example: "Stack unused=0, used=16" if stack runs into data 100 | */ 101 | void printStackMaxUsedAndUnusedSizes(Print *aSerial) { 102 | uint16_t tStackUnusedBytes; 103 | aSerial->print(F("Stack used=")); 104 | aSerial->print(RAMEND - SP); 105 | aSerial->print(F(", max used=")); 106 | aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); 107 | aSerial->print(F(", unused=")); 108 | aSerial->print(tStackUnusedBytes); 109 | aSerial->print(F(" of current total ")); 110 | aSerial->println((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); 111 | } 112 | 113 | /* 114 | * Search upwards the first two HEAP_STACK_UNTOUCHED_VALUE values after current begin of heap 115 | */ 116 | uint16_t getHeapMaxUsedSize() { 117 | uint8_t *tHeapPtr = getAvailableHeapStart(); 118 | while (*tHeapPtr != HEAP_STACK_UNTOUCHED_VALUE && *(tHeapPtr + 1) != HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr <= (uint8_t*) SP) { 119 | tHeapPtr++; 120 | } 121 | // tHeapPtr points now to lowest untouched stack position or to lowest current stack byte 122 | return tHeapPtr - (uint8_t*) __malloc_heap_start; 123 | } 124 | 125 | /* 126 | * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() 127 | * Print only if value changed. 128 | * @return true, if values changed 129 | */ 130 | bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial) { 131 | static int16_t tOldStackUsedBytes = 0; 132 | 133 | uint16_t tStackUnusedBytes; 134 | int16_t tStackMaxUsedBytes = getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes); 135 | if (tOldStackUsedBytes != tStackMaxUsedBytes) { 136 | tOldStackUsedBytes = tStackMaxUsedBytes; 137 | aSerial->print(F("Stack used=")); 138 | aSerial->print(RAMEND - SP); 139 | aSerial->print(F(", max used=")); 140 | aSerial->print(tStackMaxUsedBytes); 141 | aSerial->print(F(", unused=")); 142 | aSerial->println(tStackUnusedBytes); 143 | return true; 144 | } 145 | return false; 146 | } 147 | 148 | /* 149 | * Get amount of free Stack = current stackpointer - heap end 150 | */ 151 | uint16_t getCurrentAvailableStackSize(void) { 152 | uint16_t tAvailableHeapStart = (uint16_t) getAvailableHeapStart(); // __brkval 153 | if (tAvailableHeapStart >= SP) { 154 | return 0; 155 | } 156 | return (SP - tAvailableHeapStart); 157 | } 158 | void printCurrentAvailableStackSize(Print *aSerial) { 159 | aSerial->print(F("Currently available Stack[bytes]=")); 160 | aSerial->println(getCurrentAvailableStackSize()); 161 | } 162 | 163 | /* 164 | * Get amount of maximum available memory for malloc() 165 | * FreeRam - __malloc_margin (128 for ATmega328) 166 | */ 167 | uint16_t getCurrentAvailableHeapSize(void) { 168 | if (getCurrentAvailableStackSize() <= (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) { 169 | return 0; 170 | } 171 | // SP - __brkval - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) 172 | return getCurrentAvailableStackSize() - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); // (128) 173 | } 174 | 175 | void printCurrentAvailableHeapSize(Print *aSerial) { 176 | aSerial->print(F("Currently available Heap[bytes]=")); 177 | aSerial->println(getCurrentAvailableHeapSize()); 178 | } 179 | 180 | /* 181 | * Simple and short implementation, does not work before initStackFreeMeasurement() or first malloc() 182 | * The STACK required for this function is 4 bytes, so available numbers are 4 less than for caller. 183 | */ 184 | void printCurrentAvailableHeapSizeSimple(Print *aSerial) { 185 | aSerial->print(F("available=")); 186 | aSerial->println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); 187 | } 188 | 189 | // This define is in AVRUtils.h 190 | // #define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) 191 | 192 | void printBaseRAMData(Print *aSerial) { 193 | aSerial->print(F("__malloc_heap_start=")); 194 | aSerial->print((uint16_t) __malloc_heap_start); // = __bss_end, __heap_start in lst file 195 | aSerial->print(F("|0x")); 196 | aSerial->print((uint16_t) __malloc_heap_start, HEX); 197 | 198 | aSerial->print(F(", &__heap_start=")); 199 | aSerial->print((uint16_t) &__heap_start); 200 | aSerial->print(F("|0x")); 201 | aSerial->print((uint16_t) &__heap_start, HEX); 202 | 203 | aSerial->print(F(", __brkval=")); 204 | aSerial->print((uint16_t) __brkval); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 205 | aSerial->print(F("|0x")); 206 | aSerial->print((uint16_t) __brkval, HEX); 207 | 208 | aSerial->print(F(", __malloc_margin=")); 209 | aSerial->print((uint16_t) __malloc_margin); // =128 210 | 211 | aSerial->print(F(", SP=")); 212 | aSerial->print((uint16_t) SP); 213 | aSerial->print(F("|0x")); 214 | aSerial->print((uint16_t) SP, HEX); 215 | 216 | /* 217 | * The next 2 entries seems to be always 0 218 | */ 219 | aSerial->print(F(", __malloc_heap_end=")); 220 | aSerial->print((uint16_t) __malloc_heap_end); 221 | 222 | aSerial->print(F(", __flp=")); 223 | aSerial->print((uint16_t) __flp); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 224 | aSerial->println(); 225 | } 226 | 227 | /* 228 | * RAM starts with Data, i.e. variables initialized with values != 0, 229 | * followed by BSS, i.e. uninitalized variables (which are initialized with 0) 230 | * and variables not initialized by using attribute "__attribute__((section(".noinit")))". 231 | * It ends with the heap and the stack. 232 | * 233 | * The STACK required for this function is 8 bytes, so available numbers are 8 less than for caller. 234 | * 235 | * Sample output: 236 | * Data+BSS=445. Heap: used=770, max used=1096, available=663. Stack: available=791, used=42, max used=319, unused=188 of current total 833 237 | * Formulas: 238 | * Stack available + used = current total 239 | * Heap available + __malloc_margin (128) = Stack available 240 | * Data+BSS + Heap max used + Stack unused + Stack max used = RAMSIZE 241 | */ 242 | void printRAMInfo(Print *aSerial) { 243 | 244 | aSerial->print(F("Data+BSS=")); 245 | aSerial->print((uint16_t) &__heap_start - RAMSTART); 246 | 247 | aSerial->print(F(". Heap: used=")); 248 | aSerial->print((uint16_t) getAvailableHeapStart() - (uint16_t) &__heap_start); 249 | aSerial->print(F(", max written=")); // if Stack uses total heap, we see the stack size here :-( 250 | aSerial->print(getHeapMaxUsedSize()); 251 | aSerial->print(F(", available=")); 252 | uint16_t tStackAvailable = SP - (uint16_t) getAvailableHeapStart() + 1; 253 | aSerial->print(tStackAvailable - (uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); 254 | 255 | aSerial->print(F(". Stack: available=")); 256 | aSerial->print(tStackAvailable); 257 | aSerial->print(F(", used=")); 258 | aSerial->print(RAMEND - SP); 259 | uint16_t tStackUnusedBytes; 260 | aSerial->print(F(", max used=")); 261 | aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); 262 | aSerial->print(F(", unused=")); 263 | aSerial->print(tStackUnusedBytes); 264 | aSerial->print(F(" of current total ")); 265 | aSerial->print((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); // getAvailableHeapStart() 266 | 267 | aSerial->println(); 268 | } 269 | 270 | void set__malloc_margin(uint8_t aNewMallocMargin) { 271 | __malloc_margin = aNewMallocMargin; 272 | } 273 | 274 | void reset__malloc_margin() { 275 | __malloc_margin = 128; 276 | } 277 | 278 | bool isAddressInRAM(void *aAddressToCheck) { 279 | return (aAddressToCheck <= (void*) RAMEND); 280 | } 281 | 282 | bool isAddressBelowAvailableHeapStart(void *aAddressToCheck) { 283 | return (aAddressToCheck < getAvailableHeapStart()); 284 | } 285 | 286 | /* 287 | * Test available heap by callocing 128 bytes chunks, 288 | * If no memory available, try with 64, 32 etc up to 2, 1 byte chunks 289 | */ 290 | void testCallocSizesAndPrint(Print *aSerial) { 291 | uint8_t *tLastMallocPtr; 292 | uint16_t tMallocSize = 128; 293 | while (true) { 294 | aSerial->print(F("SP=0x")); 295 | aSerial->print(SP, HEX); 296 | aSerial->print(F(" available=")); 297 | aSerial->print(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); 298 | uint8_t *tMallocPtr = (uint8_t*) calloc(tMallocSize, 1); 299 | 300 | aSerial->print(F(" -> calloc(")); 301 | aSerial->print(tMallocSize); 302 | aSerial->print(F(",1)")); 303 | 304 | if (tMallocPtr == nullptr) { 305 | aSerial->print(F("failed ->")); 306 | tMallocSize = tMallocSize >> 1; 307 | if (tMallocSize < 1) { 308 | aSerial->println(); 309 | break; 310 | } 311 | } else { 312 | tLastMallocPtr = tMallocPtr; 313 | aSerial->print(F("=0x")); 314 | aSerial->print((uint16_t) tLastMallocPtr, HEX); 315 | aSerial->print(F(" ->")); 316 | 317 | *tLastMallocPtr = HEAP_STACK_UNTOUCHED_VALUE; // For testing detection using 2 consecutive HEAP_STACK_UNTOUCHED_VALUE 318 | *(tLastMallocPtr + tMallocSize - 1) = 0x11; 319 | } 320 | printCurrentAvailableHeapSizeSimple(aSerial); 321 | } 322 | } 323 | /******************************************** 324 | * SLEEP AND WATCHDOG STUFF 325 | ********************************************/ 326 | 327 | #ifndef _MILLIS_UTILS_H 328 | // copied from MillisUtils.h 329 | /* 330 | * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. 331 | */ 332 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 333 | #define timer0_millis millis_timer_millis // The ATTinyCore libraries use other variable name in wiring.c 334 | #endif 335 | 336 | extern volatile unsigned long timer0_millis; 337 | #endif // MILLIS_UTILS_H_ 338 | 339 | /* 340 | * For sleep modes see sleep.h 341 | * SLEEP_MODE_IDLE 342 | * SLEEP_MODE_ADC 343 | * SLEEP_MODE_PWR_DOWN 344 | * SLEEP_MODE_PWR_SAVE 345 | * SLEEP_MODE_STANDBY 346 | * SLEEP_MODE_EXT_STANDBY 347 | */ 348 | // required only once 349 | void initSleep(uint8_t tSleepMode) { 350 | sleep_enable(); 351 | set_sleep_mode(tSleepMode); 352 | } 353 | 354 | /* 355 | * Watchdog wakes CPU periodically and all we have to do is call sleep_cpu(); 356 | * aWatchdogPrescaler (see wdt.h) can be one of 357 | * WDTO_15MS, 30, 60, 120, 250, WDTO_500MS 358 | * WDTO_1S to WDTO_8S 359 | */ 360 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler) { 361 | sleep_enable() 362 | ; 363 | set_sleep_mode(tSleepMode); 364 | MCUSR = ~_BV(WDRF); // Clear WDRF in MCUSR 365 | 366 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) \ 367 | || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) \ 368 | || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 369 | #define WDTCSR WDTCR 370 | #endif 371 | // Watchdog interrupt enable + reset interrupt flag -> needs ISR(WDT_vect) 372 | uint8_t tWDTCSR = _BV(WDIE) | _BV(WDIF) | (aWatchdogPrescaler & 0x08 ? _WD_PS3_MASK : 0x00) | (aWatchdogPrescaler & 0x07); // handles that the WDP3 bit is in bit 5 of the WDTCSR register, 373 | WDTCSR = _BV(WDCE) | _BV(WDE); // clear lock bit for 4 cycles by writing 1 to WDCE AND WDE 374 | WDTCSR = tWDTCSR; // set final Value 375 | } 376 | 377 | /* 378 | * @param aWatchdogPrescaler (see wdt.h) can be one of WDTO_15MS, 30, 60, 120, 250, WDTO_500MS, WDTO_1S to WDTO_8S 379 | * 0 (15 ms) to 3(120 ms), 4 (250 ms) up to 9 (8000 ms) 380 | */ 381 | uint16_t computeSleepMillis(uint8_t aWatchdogPrescaler) { 382 | uint16_t tResultMillis = 8000; 383 | for (uint8_t i = 0; i < (9 - aWatchdogPrescaler); ++i) { 384 | tResultMillis = tResultMillis / 2; 385 | } 386 | return tResultMillis + DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN; // + for the (default) startup time. !!! But this depends from Clock Source and sleep mode !!! 387 | } 388 | 389 | /* 390 | * @param aWatchdogPrescaler (see wdt.h) can be one of WDTO_15MS, 30, 60, 120, 250, WDTO_500MS, WDTO_1S to WDTO_8S 391 | * 0 (15 ms) to 3(120 ms), 4 (250 ms) up to 9 (8000 ms) 392 | * ! I have see + 30 % deviation from nominal WDT clock! 393 | * @param aAdjustMillis if true, adjust the Arduino internal millis counter the get quite correct millis() 394 | * results even after sleep, since the periodic 1 ms timer interrupt is disabled while sleeping. 395 | * Interrupts are enabled before sleep! 396 | * !!! Do not forget to call e.g. noTone() or Serial.flush(); to wait for the last character to be sent, and/or disable interrupt sources before !!! 397 | */ 398 | void sleepWithWatchdog(uint8_t aWatchdogPrescaler, bool aAdjustMillis) { 399 | MCUSR = 0; // Clear MCUSR to enable a correct interpretation of MCUSR after reset 400 | ADCSRA &= ~ADEN; // disable ADC just before sleep -> saves 200 uA 401 | 402 | // use wdt_enable() since it handles that the WDP3 bit is in bit 5 of the WDTCSR register 403 | wdt_enable(aWatchdogPrescaler); 404 | 405 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 406 | # if !defined(timer0_millis) 407 | #define timer0_millis millis_timer_millis // The ATTinyCore + Digispark libraries use millis_timer_millis in wiring.c 408 | # endif 409 | #define WDTCSR WDTCR 410 | #endif 411 | WDTCSR |= _BV(WDIE) | _BV(WDIF); // Watchdog interrupt enable + reset interrupt flag -> requires ISR(WDT_vect) 412 | sei(); // Enable interrupts, to get the watchdog interrupt, which will wake us up 413 | sleep_cpu(); // The watchdog interrupt will wake us up from sleep 414 | 415 | // We wake up here :-) 416 | wdt_disable(); // Because next interrupt will otherwise lead to a reset, since wdt_enable() sets WDE / Watchdog System Reset Enable 417 | ADCSRA |= ADEN; 418 | 419 | /* 420 | * Since timer clock may be disabled adjust millis only if not slept in IDLE mode (SM2...0 bits are 000) 421 | */ 422 | #if defined(SM2) 423 | if (aAdjustMillis && (SMCR & ((_BV(SM2) | _BV(SM1) | _BV(SM0)))) != 0) { 424 | #elif ! defined(SMCR) 425 | if (aAdjustMillis && (MCUCR & ((_BV(SM1) | _BV(SM0)))) != 0) { 426 | #else 427 | if (aAdjustMillis && (SMCR & ((_BV(SM1) | _BV(SM0)))) != 0) { 428 | #endif 429 | timer0_millis += computeSleepMillis(aWatchdogPrescaler); 430 | } 431 | } 432 | 433 | /* 434 | * 0 -> %1 435 | * _BV(CLKPS0) -> %2 436 | * _BV(CLKPS1) -> %4 437 | * _BV(CLKPS1) | _BV(CLKPS0) -> 8 etc. up to 256 438 | */ 439 | void setclockDivisionFactor(uint8_t aDivisionBits) { 440 | CLKPR = _BV(CLKPCE); 441 | CLKPR = aDivisionBits; 442 | } 443 | 444 | #endif // defined(__AVR__) 445 | -------------------------------------------------------------------------------- /examples/WhistleSwitch/AVRUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AVRUtils.h 3 | * 4 | * Copyright (C) 2016-2024 Armin Joachimsmeyer 5 | * Email: armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * Arduino-Utils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #include "Arduino.h" 25 | 26 | #if defined(__AVR__) && defined (SPMCSR) && !(defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)) 27 | #ifndef _AVR_UTILS_H 28 | #define _AVR_UTILS_H 29 | 30 | #include 31 | #include 32 | #include 33 | #include "avr/boot.h" 34 | 35 | /* 36 | * The largest address just not allocated so far 37 | * Under Unix, the "break value" was the end of the data 38 | * segment as dynamically requested from the operating system. 39 | * Since we don't have an operating system, just make sure 40 | * that we don't collide with the stack. 41 | */ 42 | extern void *__brkval; // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 43 | extern void *__flp; // 44 | extern char __heap_start; // = __bss_end, the linker address of heap start 45 | #define HEURISTIC_ADDITIONAL_MALLOC_MARGIN 14 // No malloc() possible if size is lower than (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) 46 | 47 | /* 48 | * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. 49 | */ 50 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 51 | # if !defined(_MILLIS_UTILS_H) 52 | #define timer0_millis millis_timer_millis // The ATTinyCore libraries use other variable name in wiring.c - copied from MillisUtils.h 53 | # endif 54 | # if !defined(DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN) 55 | #define DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN 65 56 | # endif 57 | #else 58 | # if !defined(DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN) 59 | #define DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN 0 // default for Uno / Nano etc. 60 | # endif 61 | #endif 62 | 63 | extern volatile unsigned long timer0_millis; 64 | 65 | void initSleep(uint8_t tSleepMode); 66 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler); 67 | uint16_t computeSleepMillis(uint8_t aWatchdogPrescaler); 68 | void sleepWithWatchdog(uint8_t aWatchdogPrescaler, bool aAdjustMillis = false); 69 | 70 | #include 71 | 72 | uint8_t* getAvailableHeapStart(); 73 | void printAvailableHeapStart(Print *aSerial); 74 | uint16_t getCurrentAvailableStackSize(void); 75 | void printCurrentAvailableStackSize(Print *aSerial); 76 | uint16_t getCurrentAvailableHeapSize(void); 77 | void printCurrentAvailableHeapSize(Print *aSerial); 78 | void printCurrentAvailableHeapSizeSimple(Print *aSerial); 79 | #define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) 80 | 81 | #define HEAP_STACK_UNTOUCHED_VALUE 0x5A 82 | void initStackFreeMeasurement(); 83 | 84 | int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer); 85 | void printStackMaxUsedAndUnusedSizes(Print *aSerial); 86 | bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial); 87 | 88 | void printBaseRAMData(Print *aSerial); 89 | void printRAMInfo(Print *aSerial); 90 | 91 | bool isAddressInRAM(void *aAddressToCheck); 92 | bool isAddressBelowAvailableHeapStart(void *aAddressToCheck); 93 | 94 | void set__malloc_margin(uint8_t aNewMallocMargin); 95 | void reset__malloc_margin(); 96 | 97 | void testCallocSizesAndPrint(Print *aSerial); 98 | 99 | #endif // _AVR_UTILS_H 100 | #endif // defined(__AVR__) 101 | -------------------------------------------------------------------------------- /examples/WhistleSwitch/EasyButtonAtInt01.h: -------------------------------------------------------------------------------- 1 | /* 2 | * EasyButtonAtInt01.h 3 | * 4 | * Arduino library for handling push buttons connected between ground and INT0 and / or INT1 pin. 5 | * INT0 and INT1 are connected to Pin 2 / 3 on most Arduinos (ATmega328), to PB6 / PA3 on ATtiny167 and on ATtinyX5 we have only INT0 at PB2. 6 | * The library is totally based on interrupt. 7 | * Debouncing is implemented in a not blocking way! It is merely done by ignoring a button change within the debouncing time. 8 | * So button state is instantly available without debouncing delay! 9 | * 10 | * Usage: 11 | * #define USE_BUTTON_0 12 | * #include "EasyButtonAtInt01.h" 13 | * EasyButton Button0AtPin2(true); 14 | * The macros INT0_PIN and INT1_PIN are set after the include. 15 | * 16 | * Copyright (C) 2018-2024 Armin Joachimsmeyer 17 | * armin.joachimsmeyer@gmail.com 18 | * 19 | * This file is part of EasyButtonAtInt01 https://github.com/ArminJo/EasyButtonAtInt01. 20 | * 21 | * EasyButtonAtInt01 is free software: you can redistribute it and/or modify 22 | * it under the terms of the GNU General Public License as published by 23 | * the Free Software Foundation, either version 3 of the License, or 24 | * (at your option) any later version. 25 | * 26 | * This program is distributed in the hope that it will be useful, 27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 29 | * See the GNU General Public License for more details. 30 | * 31 | * You should have received a copy of the GNU General Public License 32 | * along with this program. If not, see . 33 | */ 34 | 35 | #ifndef _EASY_BUTTON_AT_INT01_H 36 | #define _EASY_BUTTON_AT_INT01_H 37 | 38 | #define VERSION_EASY_BUTTON "3.4.0" 39 | #define VERSION_EASY_BUTTON_MAJOR 3 40 | #define VERSION_EASY_BUTTON_MINOR 4 41 | #define VERSION_EASY_BUTTON_PATCH 0 42 | // The change log is at the bottom of the file 43 | 44 | /* 45 | * Macro to convert 3 version parts into an integer 46 | * To be used in preprocessor comparisons, such as #if VERSION_EASY_BUTTON_HEX >= VERSION_HEX_VALUE(3, 0, 0) 47 | */ 48 | #define VERSION_HEX_VALUE(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) 49 | #define VERSION_EASY_BUTTON_HEX VERSION_HEX_VALUE(VERSION_EASY_BUTTON_MAJOR, VERSION_EASY_BUTTON_MINOR, VERSION_EASY_BUTTON_PATCH) 50 | #if defined(__AVR__) 51 | #include 52 | 53 | /* 54 | * Usage: 55 | * #define USE_BUTTON_0 // Enable code for button at INT0 56 | * #define USE_BUTTON_1 // Enable code for button at INT1 (PCINT0 for ATtinyX5) 57 | * #include "EasyButtonAtInt01.h" 58 | * EasyButton Button0AtPin2(true); // true -> Button is connected to INT0 59 | * EasyButton Button0AtPin3(false, &Button3CallbackHandler); // false -> button is not connected to INT0 => connected to INT1 60 | * ... 61 | * digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState); 62 | * ... 63 | * 64 | */ 65 | 66 | // Return values for button state 67 | #define BUTTON_IS_ACTIVE true 68 | #define BUTTON_IS_INACTIVE false 69 | 70 | /* 71 | * Enable this if you buttons are active high. 72 | */ 73 | //#define BUTTON_IS_ACTIVE_HIGH 74 | /* 75 | * Define USE_ATTACH_INTERRUPT to force use of the arduino function attachInterrupt(). 76 | * It is required if you get the error " multiple definition of `__vector_1'" (or `__vector_2'), because another library uses the attachInterrupt() function. 77 | * For one button it needs additional 160 bytes program memory, for 2 buttons it needs additional 88 bytes. 78 | */ 79 | //#define USE_ATTACH_INTERRUPT 80 | // 81 | /* 82 | * You can define your own value if you have buttons which are worse or better than the one I have. 83 | * Since debouncing is not done with blocking wait, reducing this value makes not much sense, except you expect regular short button presses, 84 | * which durations are shorter than BUTTON_DEBOUNCING_MILLIS. 85 | * Press duration below 50 ms are almost impossible to generate by normal button pressing, but they can generated by just hitting the button. 86 | * 87 | * Test your own new value with the DebounceTest example 88 | * 89 | * Analyze the button actual debounce value with defining ANALYZE_MAX_BOUNCING_PERIOD and looking at MaxBouncingPeriodMillis. 90 | * Defining ANALYZE_MAX_BOUNCING_PERIOD computes the maximum bouncing period. 91 | * this is the time between first level change and last bouncing level change during BUTTON_DEBOUNCING_MILLIS 92 | */ 93 | //#define ANALYZE_MAX_BOUNCING_PERIOD 94 | #if !defined(BUTTON_DEBOUNCING_MILLIS) 95 | #define BUTTON_DEBOUNCING_MILLIS 50 // 35 millis measured for my button :-). 96 | #endif 97 | 98 | /* 99 | * Activating this enables save 2 bytes RAM and 64 bytes program memory 100 | */ 101 | //#define NO_BUTTON_RELEASE_CALLBACK 102 | // 103 | /* 104 | * Return values for checkForLongPress() 105 | */ 106 | #define EASY_BUTTON_LONG_PRESS_STILL_POSSIBLE 0 107 | #define EASY_BUTTON_LONG_PRESS_ABORT 1 // button was released, no long press detection possible 108 | #define EASY_BUTTON_LONG_PRESS_DETECTED 2 109 | 110 | #define EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS 400 111 | #define EASY_BUTTON_DOUBLE_PRESS_DEFAULT_MILLIS 400 112 | 113 | /* 114 | * This activates LED_BUILTIN as long as button is pressed 115 | */ 116 | //#define BUTTON_LED_FEEDBACK 117 | #if defined(BUTTON_LED_FEEDBACK) 118 | # if !defined(BUTTON_LED_FEEDBACK_PIN) 119 | # if defined(LED_BUILTIN) 120 | # define BUTTON_LED_FEEDBACK_PIN LED_BUILTIN // if not specified, use built in led - pin 13 on Uno board 121 | # else 122 | # error "BUTTON_LED_FEEDBACK is defined but neither BUTTON_LED_FEEDBACK_PIN nor LED_BUILTIN is defined" 123 | # endif 124 | # endif 125 | #endif 126 | 127 | // For external measurement of code timing 128 | //#define MEASURE_EASY_BUTTON_INTERRUPT_TIMING 129 | 130 | #if defined(MEASURE_EASY_BUTTON_INTERRUPT_TIMING) 131 | # if !defined(INTERRUPT_TIMING_OUTPUT_PIN) 132 | #define INTERRUPT_TIMING_OUTPUT_PIN 6 // use pin 6 133 | //#define INTERRUPT_TIMING_OUTPUT_PIN 12 // use pin 12 134 | # endif 135 | #endif 136 | 137 | //#define TRACE 138 | #if defined(TRACE) 139 | #warning If using TRACE, the timing of the interrupt service routine changes, e.g. you will see more spikes, than expected! 140 | #endif 141 | 142 | /* 143 | * These defines are here to enable saving of 150 bytes program memory if only one button is needed 144 | */ 145 | //#define USE_BUTTON_0 146 | //#define USE_BUTTON_1 147 | #if ! (defined(USE_BUTTON_0) || defined(USE_BUTTON_1)) 148 | #error USE_BUTTON_0 and USE_BUTTON_1 are not defined, please define them or remove the #include "EasyButtonAtInt01.h" 149 | #endif 150 | // Can be be used as parameter 151 | #define BUTTON_AT_INT0 ((bool)true) 152 | #define BUTTON_AT_INT1_OR_PCINT ((bool)false) 153 | /* 154 | * Pin and port definitions for Arduinos 155 | */ 156 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 157 | #define INT0_PIN 2 158 | #define INT0_DDR_PORT (DDRB) 159 | #define INT0_IN_PORT (PINB) 160 | #define INT0_OUT_PORT (PORTB) 161 | 162 | # if defined(USE_BUTTON_1) 163 | # if !defined(INT1_PIN) 164 | #define INT1_PIN 3 165 | # elif (INT1_PIN != 2) && (INT1_PIN > 5) 166 | #error INT1_PIN (for PCINT0 interrupt) can only be 0,1,3,4,5 167 | # endif 168 | #define INT1_DDR_PORT (DDRB) 169 | #define INT1_IN_PORT (PINB) 170 | #define INT1_OUT_PORT (PORTB) 171 | # endif // defined(USE_BUTTON_1) 172 | 173 | #elif defined(USE_INT2_FOR_BUTTON_0) // Hack for ATmega 644 174 | # if defined(USE_BUTTON_1) 175 | #error If USE_INT2_FOR_BUTTON_0 is defined, only USE_BUTTON_0 is allowed, USE_BUTTON_1 must be disabled! 176 | # endif 177 | // dirty hack, but INT0 and INT1 are occupied by second USART 178 | #define INT0_PIN 2 // PB2 / INT2 179 | #define INT0_DDR_PORT (DDRB) 180 | #define INT0_IN_PORT (PINB) 181 | #define INT0_OUT_PORT (PORTB) 182 | 183 | #elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 184 | // from here we use only ATtinyCore / PAx / PBx numbers, since on Digispark board and core library there is used a strange enumeration of pins 185 | #define INT0_PIN 14 // PB6 / INT0 is connected to USB+ on DigisparkPro boards and labeled with 3 (D3) 186 | #define INT0_DDR_PORT (DDRB) 187 | #define INT0_IN_PORT (PINB) 188 | #define INT0_OUT_PORT (PORTB) 189 | 190 | 191 | # if defined(USE_BUTTON_1) 192 | # if !defined(INT1_PIN) 193 | #define INT1_PIN 3 // PA3 labeled 9 on DigisparkPro boards 194 | # endif // !defined(INT1_PIN) 195 | 196 | // Check for pin range and map digispark to PA pin number 197 | # if defined(ARDUINO_AVR_DIGISPARKPRO) 198 | # if INT1_PIN == 5 199 | #undef INT1_PIN 200 | #define INT1_PIN 7 // PA7 201 | # elif INT1_PIN == 6 202 | #undef INT1_PIN 203 | #define INT1_PIN 0 // PA0 204 | # elif INT1_PIN == 7 205 | #undef INT1_PIN 206 | #define INT1_PIN 1 // PA1 207 | # elif INT1_PIN == 8 208 | #undef INT1_PIN 209 | #define INT1_PIN 2 // PA2 210 | # elif INT1_PIN == 9 211 | #undef INT1_PIN 212 | #define INT1_PIN 3 // PA3 213 | # elif INT1_PIN == 10 214 | #undef INT1_PIN 215 | #define INT1_PIN 4 // PA4 216 | # elif INT1_PIN == 11 217 | #undef INT1_PIN 218 | #define INT1_PIN 5 // PA5 219 | # elif INT1_PIN == 12 220 | #undef INT1_PIN 221 | #define INT1_PIN 6 // PA6 222 | # else 223 | #error INT1_PIN (for PCINT0 interrupt) can only be 5 to 12 224 | # endif 225 | # else // defined(ARDUINO_AVR_DIGISPARKPRO) 226 | # if (INT1_PIN > 7) 227 | #error INT1_PIN (for PCINT0 interrupt) can only be 0 to 7 228 | # endif 229 | # endif // defined(ARDUINO_AVR_DIGISPARKPRO) 230 | #define INT1_DDR_PORT (DDRA) 231 | #define INT1_IN_PORT (PINA) 232 | #define INT1_OUT_PORT (PORTA) 233 | # endif // defined(USE_BUTTON_1) 234 | #else 235 | 236 | // other AVR MCUs 237 | #define INT0_PIN 2 238 | #define INT0_DDR_PORT (DDRD) 239 | #define INT0_IN_PORT (PIND) 240 | #define INT0_OUT_PORT (PORTD) 241 | 242 | # if defined(USE_BUTTON_1) 243 | # if !defined(INT1_PIN) 244 | #define INT1_PIN 3 245 | # elif (INT1_PIN > 7) 246 | #error INT1_PIN (for PCINT2 interrupt) can only be Arduino pins 0 to 7 (PD0 to PD7) 247 | # endif 248 | #define INT1_DDR_PORT (DDRD) 249 | #define INT1_IN_PORT (PIND) 250 | #define INT1_OUT_PORT (PORTD) 251 | # endif // defined(USE_BUTTON_1) 252 | #endif // defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 253 | 254 | #if defined(USE_BUTTON_1) 255 | #define INT1_BIT INT1_PIN 256 | #endif 257 | 258 | #if defined(USE_BUTTON_1) && ((!defined(ISC10)) || ((defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && INT1_PIN != 3)) \ 259 | && !defined(INTENTIONALLY_USE_PCI0_FOR_BUTTON1) && !(defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)) 260 | #warning Using PCINT0 interrupt for button 1 261 | #endif 262 | 263 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 264 | #define EICRA MCUCR 265 | #define EIFR GIFR 266 | #define EIMSK GIMSK 267 | #endif 268 | 269 | #if (INT0_PIN >= 8) 270 | #define INT0_BIT (INT0_PIN - 8) 271 | #else 272 | #define INT0_BIT INT0_PIN 273 | #endif 274 | 275 | class EasyButton { 276 | 277 | public: 278 | 279 | /* 280 | * These constructors are deterministic if only one button is enabled 281 | * If two buttons are enabled they can be taken for the 1. button at INT0 282 | */ 283 | EasyButton(); 284 | EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState)); 285 | #if !defined(NO_BUTTON_RELEASE_CALLBACK) 286 | EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState), 287 | void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)); 288 | #endif 289 | /* 290 | * These constructors use the first (bool) parameter to decide which button to take. 291 | */ 292 | EasyButton(bool aIsButtonAtINT0); 293 | EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState)); 294 | #if !defined(NO_BUTTON_RELEASE_CALLBACK) 295 | EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState), 296 | void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)); 297 | #endif 298 | 299 | void init(bool aIsButtonAtINT0); 300 | bool enablePCIInterrupt(); 301 | 302 | /* 303 | * !!! checkForDoublePress() works only reliable if called in button press callback function !!! 304 | */ 305 | bool checkForDoublePress(uint16_t aDoublePressDelayMillis = EASY_BUTTON_DOUBLE_PRESS_DEFAULT_MILLIS); 306 | 307 | bool readButtonState(); 308 | bool getButtonStateIsActive(); // get private member 309 | bool readDebouncedButtonState(); 310 | bool updateButtonState(); 311 | uint16_t updateButtonPressDuration(); // Updates the ButtonPressDurationMillis by polling, since this cannot be done by interrupt. 312 | 313 | bool checkForForButtonNotPressedTime(uint16_t aTimeoutMillis); 314 | 315 | //!!! Consider to use button release callback handler and check the ButtonPressDurationMillis 316 | uint8_t checkForLongPress(uint16_t aLongPressThresholdMillis = EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS); 317 | bool checkForLongPressBlocking(uint16_t aLongPressThresholdMillis = EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS); 318 | 319 | void handleINT01Interrupts(); // internal use only 320 | 321 | bool LastBounceWasChangeToInactive; // Internal state, reflects actual reading with spikes and bouncing. Negative logic: true / active means button pin is LOW 322 | volatile bool ButtonToggleState; // Toggle is on press, not on release - initial value is false 323 | 324 | /* 325 | * Flag to enable action only once. Only set to true by library. 326 | * Can be checked and set to false by main program to enable only one action per button press. 327 | */ 328 | volatile bool ButtonStateHasJustChanged; 329 | 330 | /* 331 | * Duration of active state. Is is set at button release. Can in theory not be less than BUTTON_DEBOUNCING_MILLIS. 332 | * By definition, shorter presses are recognized as bouncing. 333 | * To cover this case you can call updateButtonState() from an outside loop which updates the button state in this case. 334 | */ 335 | volatile uint16_t ButtonPressDurationMillis; 336 | 337 | /* 338 | * Milliseconds of last button change, going active or inactive. 339 | * If bouncing occurs the time is not updated with the time of the bouncing. 340 | * So ButtonPressDurationMillis is the complete time and not the time after the last bounce. 341 | */ 342 | volatile unsigned long ButtonLastChangeMillis; 343 | 344 | /* 345 | * If last button change was going inactive, ButtonReleaseMillis contains the same value as ButtonLastChangeMillis 346 | * It is required for double press recognition, which is done when button is active and ButtonLastChangeMillis has just changed. 347 | * Be aware, that the first press after booting may be detected as double press! 348 | * This is because ButtonReleaseMillis is initialized with 0 milliseconds, which is interpreted as the first press happened at the beginning of boot. 349 | */ 350 | volatile unsigned long ButtonReleaseMillis; 351 | 352 | #if defined(ANALYZE_MAX_BOUNCING_PERIOD) 353 | volatile unsigned int MaxBouncingPeriodMillis = 0; // Maximum bouncing period. Time between first level change and last bouncing level change during BUTTON_DEBOUNCING_MILLIS 354 | #endif 355 | 356 | volatile bool isButtonAtINT0; 357 | void (*ButtonPressCallback)(bool aButtonToggleState) = nullptr; // If not null, is called on every button press with ButtonToggleState as parameter 358 | #if !defined(NO_BUTTON_RELEASE_CALLBACK) 359 | void (*ButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis) = nullptr; // If not null, is called on every button release with ButtonPressDurationMillis as parameter 360 | #endif 361 | 362 | #if defined(USE_BUTTON_0) 363 | static EasyButton *sPointerToButton0ForISR; 364 | #endif 365 | #if defined(USE_BUTTON_1) 366 | static EasyButton *sPointerToButton1ForISR; 367 | #endif 368 | 369 | private: 370 | /* 371 | * If last press duration < BUTTON_DEBOUNCING_MILLIS it holds wrong value (true instead of false), therefore it is private. 372 | * To get current state, use readButtonState(). 373 | */ 374 | volatile bool ButtonStateIsActive; // State at last change. Negative logic: true / active means button pin is LOW. 375 | }; 376 | // end of class definition 377 | 378 | void handleINT0Interrupt(); 379 | void handleINT1Interrupt(); 380 | 381 | /* 382 | * This functions are weak and can be replaced by your own code 383 | */ 384 | #if defined(USE_BUTTON_0) 385 | void __attribute__ ((weak)) handleINT0Interrupt(); 386 | #endif 387 | 388 | #if defined(USE_BUTTON_1) 389 | void __attribute__ ((weak)) handleINT1Interrupt(); 390 | #endif 391 | 392 | #endif // defined(__AVR__) 393 | 394 | /* Version 3.4.1 - 12/2023 395 | * - Avoid wrong double press detection if calling checkForDoublePress() after release of button. 396 | * - Hack for ATmega 644. 397 | * 398 | * Version 3.4.0 - 10/2023 399 | * - Added NO_INITIALIZE_IN_CONSTRUCTOR macro to enable late initializing. 400 | * - ButtonStateIsActive is now private, since it is not reliable after bouncing. Use readButtonState() or readDebouncedButtonState() instead. 401 | * 402 | * Version 3.3.1 - 2/2022 403 | * - Avoid mistakenly double press detection after boot. 404 | * 405 | * Version 3.3.0 - 9/2021 406 | * - Renamed EasyButtonAtInt01.cpp.h to EasyButtonAtInt01.hpp. 407 | * 408 | * Version 3.2.0 - 1/2021 409 | * - Allow button1 on pin 8 to 13 and A0 to A5 for ATmega328. 410 | * 411 | * Version 3.1.0 - 6/2020 412 | * - 2 sets of constructors, one for only one button used and one for the second button if two buttons used. 413 | * - Map pin numbers for Digispark pro boards, for use with with digispark library. 414 | * 415 | * Version 3.0.0 - 5/2020 416 | * - Added button release handler and adapted examples. 417 | * - Revoke change for "only one true result per press for checkForLongPressBlocking()". It is superseded by button release handler. 418 | * - Support buttons which are active high by defining BUTTON_IS_ACTIVE_HIGH. 419 | * - Improved detection of maximum bouncing period used in DebounceTest. 420 | * 421 | * Version 2.1.0 - 5/2020 422 | * - Avoid 1 ms delay for checkForLongPressBlocking() if button is not pressed. 423 | * - Only one true result per press for checkForLongPressBlocking(). 424 | * 425 | * Version 2.0.0 - 1/2020 426 | * - Ported to ATtinyX5 and ATiny167. 427 | * - Support also PinChangeInterrupt for button 1 on Pin PA0 to PA7 for ATtiniy87/167. 428 | * - Long press detection support. 429 | * - Double press detection support. 430 | * - Renamed to EasyButtonAtInt01.hpp 431 | */ 432 | 433 | #endif // _EASY_BUTTON_AT_INT01_H 434 | -------------------------------------------------------------------------------- /examples/WhistleSwitch/ShowInfo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ShowInfo.cpp 3 | * 4 | * Copyright (C) 2018-2019 Armin Joachimsmeyer 5 | * armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * Arduino-Utils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #if defined(__AVR__) 25 | 26 | #include 27 | 28 | #if defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 29 | #include "ATtinySerialOut.h" 30 | #endif 31 | 32 | // Based on https://playground.arduino.cc/Main/ShowInfo 33 | 34 | #include 35 | 36 | float GetTemp(void) { 37 | unsigned int wADC; 38 | float t; 39 | 40 | // The internal temperature has to be used 41 | // with the internal reference of 1.1V. 42 | // Channel 8 can not be selected with 43 | // the analogRead function yet. 44 | 45 | // This code is not valid for the Arduino Mega, 46 | // and the Arduino Mega 2560. 47 | 48 | #if defined(THIS_MIGHT_BE_VALID_IN_THE_FUTURE) 49 | analogReference (INTERNAL); 50 | delay(20); // wait for voltages to become stable. 51 | wADC = analogRead(8);// Channel 8 is temperature sensor. 52 | #else 53 | // Set the internal reference and mux. 54 | ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3)); 55 | ADCSRA |= _BV(ADEN); // enable the ADC 56 | 57 | delay(20); // wait for voltages to become stable. 58 | 59 | ADCSRA |= _BV(ADSC); // Start the ADC 60 | 61 | // Detect end-of-conversion 62 | while (bit_is_set(ADCSRA, ADSC)) 63 | ; 64 | 65 | // Reading register "ADCW" takes care of how to read ADCL and ADCH. 66 | #if defined(__AVR_ATmega32U4__) 67 | wADC = ADC; // For Arduino Leonardo 68 | #else 69 | wADC = ADCW; // 'ADCW' is preferred over 'ADC' 70 | #endif 71 | #endif 72 | 73 | // The offset of 337.0 could be wrong. It is just an indication. 74 | t = (wADC - 337.0) / 1.22; 75 | 76 | return (t); 77 | } 78 | 79 | // Helper function for sketch size. 80 | // The sketch size is runtime calculated. 81 | // From user "Coding Badly" in his post: 82 | // http://arduino.cc/forum/index.php/topic,115870.msg872309.html#msg872309 83 | // Changed into unsigned long for code size larger than 64kB. 84 | // 85 | // This function returns the sketch size 86 | // for a size between 0 and 32k. If the code 87 | // size is larger (for example with an Arduino Mega), 88 | // the return value is not valid. 89 | // 90 | unsigned long sketchSize(void) { 91 | extern int _etext; 92 | extern int _edata; 93 | 94 | return ((unsigned long) (&_etext) + ((unsigned long) (&_edata) - 256L)); 95 | } 96 | 97 | void Information(void) { 98 | #if !defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 99 | int data1, data2, data3, data4; 100 | unsigned long ul; 101 | float percentage; 102 | 103 | Serial.println(F("")); 104 | #if !defined(__AVR_ATmega32U4__) 105 | Serial.println(F("Information")); 106 | Serial.println(F("-----------")); 107 | 108 | Serial.print(F("sketch Size = ")); 109 | ul = sketchSize(); 110 | Serial.print(ul, DEC); 111 | Serial.print(F(" (")); 112 | percentage = (float) ul / ((float) FLASHEND + 1.0) * 100.0; 113 | Serial.print(percentage, 0); 114 | Serial.println(F("%)")); 115 | #endif 116 | 117 | #if defined(ARDUINO) 118 | Serial.print(F("ARDUINO = ")); 119 | Serial.print(ARDUINO); 120 | Serial.print(F(" (Arduino version ")); 121 | Serial.print((float) ARDUINO / 100.0, 2); 122 | Serial.println(F(")")); 123 | #endif 124 | 125 | Serial.print(F("__VERSION__ = ")); 126 | Serial.println(F(__VERSION__)); 127 | 128 | Serial.print(F("__DATE__ = ")); 129 | Serial.println(F(__DATE__)); 130 | 131 | Serial.print(F("__TIME__ = ")); 132 | Serial.println(F(__TIME__)); 133 | 134 | Serial.print(F("__AVR_LIBC_VERSION_STRING__ = ")); 135 | Serial.println(F(__AVR_LIBC_VERSION_STRING__)); 136 | 137 | Serial.print(F("__FILE__ = ")); 138 | Serial.println(F(__FILE__)); 139 | 140 | Serial.print(F("__STDC__ = ")); 141 | Serial.println(__STDC__, DEC); 142 | 143 | #if !defined(__AVR_ATmega32U4__) 144 | Serial.print(F("OSCCAL = ")); 145 | Serial.println(OSCCAL, DEC); 146 | 147 | Serial.print(F("GPIOR0 = 0x")); 148 | Serial.println(GPIOR0, HEX); 149 | 150 | Serial.print(F("GPIOR1 = 0x")); 151 | Serial.println(GPIOR1, HEX); 152 | 153 | Serial.print(F("GPIOR1 = 0x")); 154 | Serial.println(GPIOR1, HEX); 155 | #endif 156 | 157 | Serial.print(F("RAMEND = 0x")); 158 | Serial.println(RAMEND, HEX); 159 | 160 | Serial.print(F("XRAMEND = 0x")); 161 | Serial.println(XRAMEND, HEX); 162 | 163 | Serial.print(F("E2END = 0x")); 164 | Serial.println(E2END, HEX); 165 | 166 | Serial.print(F("FLASHEND = 0x")); 167 | Serial.println(FLASHEND, HEX); 168 | 169 | noInterrupts(); 170 | data1 = boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS); 171 | data2 = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS); 172 | data3 = boot_lock_fuse_bits_get(GET_EXTENDED_FUSE_BITS); 173 | data4 = boot_lock_fuse_bits_get(GET_LOCK_BITS); 174 | interrupts(); 175 | 176 | Serial.print(F("LOW FUSE = 0x")); 177 | Serial.println(data1, HEX); 178 | 179 | Serial.print(F("HIGH FUSE = 0x")); 180 | Serial.println(data2, HEX); 181 | 182 | Serial.print(F("EXTENDED FUSE = 0x")); 183 | Serial.println(data3, HEX); 184 | 185 | Serial.print(F("LOCK BITS = 0x")); 186 | Serial.println(data4, HEX); 187 | 188 | Serial.print(F("Processor according to compiler = ")); 189 | #if defined(__AVR_ATtiny45__) 190 | Serial.println(F("__AVR_ATtiny45__")); 191 | #elif defined(__AVR_ATtiny85__) 192 | Serial.println(F("__AVR_ATtiny85__")); 193 | #elif defined(__AVR_ATtiny87__) 194 | Serial.println(F("__AVR_ATtiny87__")); 195 | #elif defined(__AVR_ATtiny167__) 196 | Serial.println(F("__AVR_ATtiny167__")); 197 | #elif defined(__AVR_ATtiny2313__) 198 | Serial.println(F("__AVR_ATtiny2313__")); 199 | #elif defined(__AVR_ATtiny2313A__) 200 | Serial.println(F("__AVR_ATtiny2313A__")); 201 | #elif defined(__AVR_ATmega48__) 202 | Serial.println(F("__AVR_ATmega48__")); 203 | #elif defined(__AVR_ATmega48A__) 204 | Serial.println(F("__AVR_ATmega48A__")); 205 | #elif defined(__AVR_ATmega48P__) 206 | Serial.println(F("__AVR_ATmega48P__")); 207 | #elif defined(__AVR_ATmega8__) 208 | Serial.println(F("__AVR_ATmega8__")); 209 | #elif defined(__AVR_ATmega8U2__) 210 | Serial.println(F("__AVR_ATmega8U2__")); 211 | #elif defined(__AVR_ATmega88__) 212 | Serial.println(F("__AVR_ATmega88__")); 213 | #elif defined(__AVR_ATmega88A__) 214 | Serial.println(F("__AVR_ATmega88A__")); 215 | #elif defined(__AVR_ATmega88P__) 216 | Serial.println(F("__AVR_ATmega88P__")); 217 | #elif defined(__AVR_ATmega88PA__) 218 | Serial.println(F("__AVR_ATmega88PA__")); 219 | #elif defined(__AVR_ATmega16__) 220 | Serial.println(F("__AVR_ATmega16__")); 221 | #elif defined(__AVR_ATmega168__) 222 | Serial.println(F("__AVR_ATmega168__")); 223 | #elif defined(__AVR_ATmega168A__) 224 | Serial.println(F("__AVR_ATmega168A__")); 225 | #elif defined(__AVR_ATmega168P__) 226 | Serial.println(F("__AVR_ATmega168P__")); 227 | #elif defined(__AVR_ATmega32__) 228 | Serial.println(F("__AVR_ATmega32__")); 229 | #elif defined(__AVR_ATmega328__) 230 | Serial.println(F("__AVR_ATmega328__")); 231 | #elif defined(__AVR_ATmega328P__) 232 | Serial.println(F("__AVR_ATmega328P__")); 233 | #elif defined(__AVR_ATmega32U2__) 234 | Serial.println(F("__AVR_ATmega32U2__")); 235 | #elif defined(__AVR_ATmega32U4__) 236 | Serial.println(F("__AVR_ATmega32U4__")); 237 | #elif defined(__AVR_ATmega32U6__) 238 | Serial.println(F("__AVR_ATmega32U6__")); 239 | #elif defined(__AVR_ATmega128__) 240 | Serial.println(F("__AVR_ATmega128__")); 241 | #elif defined(__AVR_ATmega1280__) 242 | Serial.println(F("__AVR_ATmega1280__")); 243 | #elif defined(__AVR_ATmega2560__) 244 | Serial.println(F("__AVR_ATmega2560__")); 245 | #endif 246 | 247 | #if defined(SIGRD) 248 | Serial.print(F("SIGRD = ")); 249 | Serial.println(SIGRD, DEC); 250 | #else 251 | Serial.println(F("SIGRD : not defined(let's make it 5 and see what happens).")); 252 | #define SIGRD 5 253 | #endif 254 | 255 | Serial.print(F("Signature = 0x")); 256 | 257 | data1 = boot_signature_byte_get(0x00); 258 | data2 = boot_signature_byte_get(0x02); 259 | data3 = boot_signature_byte_get(0x04); 260 | data4 = boot_signature_byte_get(0x01); 261 | 262 | Serial.print(data1, HEX); 263 | Serial.print(F(", 0x")); 264 | Serial.print(data2, HEX); 265 | Serial.print(F(", 0x")); 266 | Serial.println(data3, HEX); 267 | 268 | Serial.print(F("calibration = ")); 269 | Serial.println(data3, DEC); 270 | 271 | #if !defined(__AVR_ATmega32U4__) 272 | Serial.print(F("Number of seconds since start = ")); 273 | Serial.println(millis() / 1000L, DEC); 274 | #endif 275 | 276 | #if defined(__AVR_ATmega328P__) 277 | Serial.print(F("Internal Temperature = ")); 278 | Serial.print(GetTemp(), 1); 279 | Serial.println(F(" Celsius (the offset could be wrong).")); 280 | #endif 281 | 282 | Serial.println(F("UTF-8 test:")); 283 | Serial.println(F(" Micro �")); 284 | Serial.println(F(" Euro �")); 285 | Serial.println(F(" (c) �")); 286 | 287 | Serial.println(F("-----------")); 288 | #endif 289 | } 290 | 291 | void printMCUSR(uint8_t aMCUSRContent) { 292 | #if defined(WDRF) 293 | if (aMCUSRContent & (1 << WDRF)) { 294 | Serial.print(F(" Watchdog")); 295 | } 296 | #endif 297 | #if defined(BORF) 298 | if (aMCUSRContent & (1 << BORF)) { 299 | Serial.print(F(" Brownout")); 300 | } 301 | #endif 302 | #if defined(EXTRF) 303 | if (aMCUSRContent & (1 << EXTRF)) { 304 | Serial.print(F(" Reset")); 305 | } 306 | #endif 307 | #if defined(PORF) 308 | if (aMCUSRContent & (1 << PORF)) { 309 | Serial.print(F(" PowerOn")); 310 | } 311 | #endif 312 | Serial.println(); 313 | } 314 | 315 | void printBODLevel(uint8_t aHighFuseBits) { 316 | Serial.print(F("Brown-out=")); 317 | #if defined(FUSE_BODLEVEL2) && defined(FUSE_BODLEVEL1) && defined(FUSE_BODLEVEL0) 318 | uint8_t tBrownOutDetectionBits = aHighFuseBits & (~FUSE_BODLEVEL2 | ~FUSE_BODLEVEL1 | ~FUSE_BODLEVEL0 ); 319 | switch (tBrownOutDetectionBits) { 320 | // 0-3 are reserved codes (for ATtiny) 321 | case 4: 322 | Serial.print(F("4.3V")); 323 | break; 324 | case 5: 325 | Serial.print(F("2.7V")); 326 | break; 327 | case 6: 328 | Serial.print(F("1.8V")); 329 | break; 330 | case 7: 331 | Serial.print(F("disabled")); 332 | break; 333 | default: 334 | break; 335 | } 336 | #else 337 | Serial.print(F("FUSE_BODLEVEL2 and FUSE_BODLEVEL1 and FUSE_BODLEVEL0 not defined")); 338 | #endif 339 | Serial.println(); 340 | } 341 | 342 | void printBODLevel() { 343 | #if defined(GET_HIGH_FUSE_BITS) 344 | uint8_t tHighFuseBits = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS); 345 | printBODLevel(tHighFuseBits); 346 | #else 347 | Serial.print(F("GET_HIGH_FUSE_BITS not defined")); 348 | #endif 349 | } 350 | 351 | /* 352 | * Output description for all fuses except "DebugWire enabled" 353 | */ 354 | void printFuses(void) { 355 | uint8_t tLowFuseBits = boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS); 356 | Serial.println(); 357 | Serial.print(F("LowFuses=0x")); 358 | Serial.println(tLowFuseBits, HEX); 359 | 360 | Serial.print(F("Clock divide by 8")); 361 | if (tLowFuseBits & ~FUSE_CKDIV8) { 362 | Serial.print(F(" not")); 363 | } 364 | Serial.println(F(" enabled")); // enabled is default 365 | 366 | Serial.print(F("Clock output")); 367 | if (tLowFuseBits & ~FUSE_CKOUT) { 368 | Serial.print(F(" not")); 369 | } 370 | Serial.println(F(" enabled")); // enabled is default 371 | 372 | Serial.print(F("Clock select=")); 373 | uint8_t tClockSelectBits = tLowFuseBits & (~FUSE_CKSEL3 | ~FUSE_CKSEL2 | ~FUSE_CKSEL1 | ~FUSE_CKSEL0 ); 374 | switch (tClockSelectBits) { 375 | case 1: 376 | Serial.print(F("16MHz PLL")); 377 | break; 378 | case 2: 379 | Serial.print(F("8MHz")); // default 380 | break; 381 | case 3: 382 | Serial.print(F("6.4MHz")); 383 | break; 384 | case 4: 385 | Serial.print(F("128kHz")); 386 | break; 387 | case 6: 388 | Serial.print(F("32.768kHz")); 389 | break; 390 | default: 391 | Serial.print(F("External")); 392 | break; 393 | } 394 | 395 | Serial.println(); 396 | Serial.print(F("Start-up time=")); 397 | uint8_t tStartUptimeBits = tLowFuseBits & (~FUSE_SUT1 | ~FUSE_SUT0 ); 398 | if (tClockSelectBits == 1) { 399 | /* 400 | * PLL Clock has other interpretation of tStartUptimeBits 401 | */ 402 | Serial.print(F("(4ms from reset) + 14 CK ")); 403 | if (tLowFuseBits & ~FUSE_SUT0) { 404 | Serial.print(F("+ 16384 CK")); // -> 1 ms for 16 MHz clock 405 | } else { 406 | Serial.print(F(" + 1024 CK")); 407 | } 408 | if (tLowFuseBits & ~FUSE_SUT1) { 409 | Serial.print(F(" + 64ms")); // default 410 | } else { 411 | Serial.print(F(" + 4ms")); 412 | } 413 | } else { 414 | /* 415 | * Internal Calibrated RC Oscillator Clock 416 | */ 417 | Serial.print(F("6 CK (+ 14 CK")); 418 | switch (tStartUptimeBits) { 419 | case 0x10: 420 | Serial.print(F(" + 4ms")); 421 | break; 422 | case 0x20: 423 | Serial.print(F(" + 64ms")); // default 424 | break; 425 | default: 426 | break; 427 | } 428 | Serial.print(F(" from reset)")); 429 | 430 | } 431 | 432 | uint8_t tHighFuseBits = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS); 433 | Serial.println(); 434 | Serial.println(); 435 | Serial.print(F("HighFuses=0x")); 436 | Serial.println(tHighFuseBits,HEX); 437 | 438 | Serial.print(F("Reset")); 439 | if (tHighFuseBits & ~FUSE_RSTDISBL) { 440 | Serial.print(F(" not")); 441 | } 442 | Serial.println(F(" disabled")); 443 | 444 | Serial.print(F("Serial programming")); 445 | if (tHighFuseBits & ~FUSE_SPIEN) { 446 | Serial.print(F(" not")); 447 | } 448 | Serial.println(F(" enabled")); 449 | 450 | Serial.print(F("Watchdog always on")); 451 | if (tHighFuseBits & ~FUSE_WDTON) { 452 | Serial.print(F(" not")); 453 | } 454 | Serial.println(F(" enabled")); 455 | 456 | Serial.print(F("Preserve EEPROM")); 457 | if (tHighFuseBits & ~FUSE_EESAVE) { 458 | Serial.print(F(" not")); 459 | } 460 | Serial.println(F(" enabled")); 461 | 462 | printBODLevel(tHighFuseBits); 463 | 464 | uint8_t tExtFuseBits = boot_lock_fuse_bits_get(GET_EXTENDED_FUSE_BITS); 465 | Serial.println(); 466 | Serial.print(F("ExtFuses=0x")); 467 | Serial.println(tExtFuseBits,HEX); 468 | #if defined(FUSE_SELFPRGEN) 469 | Serial.print(F("Self programming")); 470 | if (tExtFuseBits & ~FUSE_SELFPRGEN) { 471 | Serial.print(F(" not")); 472 | } 473 | Serial.println(F(" enabled")); 474 | #endif 475 | Serial.println(); 476 | } 477 | 478 | void printBODSFlagExistence() { 479 | /* 480 | * Turn off the brown-out detector - this works only for ATtini85 revision C, which I have not seen in the wild. 481 | */ 482 | uint8_t tMcucrValue = MCUCR | _BV(BODS) | _BV(BODSE); // set to one 483 | MCUCR = tMcucrValue; // set both flags to one 484 | MCUCR = tMcucrValue & ~_BV(BODSE); // reset BODSE within 4 clock cycles 485 | tMcucrValue = MCUCR; 486 | Serial.print(F("BODS flag")); 487 | if (!(tMcucrValue & _BV(BODS))) { 488 | Serial.print(F(" not")); 489 | } 490 | Serial.println(F(" existent")); 491 | } 492 | #endif // defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 493 | 494 | #if !defined(__AVR_ATmega32U4__) 495 | #if defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 496 | /* 497 | * Short version using printHex and saving Flash 498 | */ 499 | void TimerCommonRegisterDump(void) { 500 | #if defined(TIMSK) 501 | Serial.print(F("TIMSK=")); 502 | Serial.printlnHex(TIMSK); 503 | #endif 504 | #if defined(TIFR) 505 | Serial.print(F("TIFR=")); 506 | Serial.printlnHex(TIFR); 507 | #endif 508 | Serial.print(F("GTCCR=")); 509 | Serial.printlnHex(GTCCR); 510 | } 511 | 512 | void Timer0RegisterDump(void) { 513 | Serial.println(F("Timer0 register dump:")); 514 | Serial.print(F("TCCR0A=")); 515 | Serial.printlnHex(TCCR0A); 516 | Serial.print(F("TCCR0B=")); 517 | Serial.printlnHex(TCCR0B); 518 | Serial.print(F("OCR0A=")); 519 | Serial.printlnHex(OCR0A); 520 | #if defined(OCR0B) 521 | Serial.print(F("OCR0B=")); 522 | Serial.printHex(OCR0B); 523 | #endif 524 | #if defined(ASSR) 525 | Serial.print(F("ASSR=")); 526 | Serial.printlnHex(ASSR); 527 | #endif 528 | 529 | Serial.print(F("TCNT0=")); 530 | Serial.printlnHex(TCNT0); 531 | 532 | #if defined(TIMSK0) 533 | Serial.print(F("TIMSK0=")); 534 | Serial.printlnHex(TIMSK0); 535 | #endif 536 | #if defined(TIFR0) 537 | Serial.print(F("TIFR0=")); 538 | Serial.printlnHex(TIFR0); 539 | #endif 540 | 541 | TimerCommonRegisterDump(); 542 | 543 | Serial.println(); 544 | } 545 | 546 | void Timer1RegisterDump(void) { 547 | Serial.println(F("Timer1 register dump:")); 548 | #if defined(TCCR1) 549 | Serial.print(F("TCCR1=")); 550 | Serial.printlnHex(TCCR1); 551 | #endif 552 | #if defined(TCCR1A) 553 | Serial.print(F("TCCR1A=")); 554 | Serial.printlnHex(TCCR1A); 555 | #endif 556 | #if defined(TCCR1B) 557 | Serial.print(F("TCCR1B=")); 558 | Serial.printlnHex(TCCR1B); 559 | #endif 560 | #if defined(TCCR1C) 561 | Serial.print(F("TCCR1C=")); 562 | Serial.printlnHex(TCCR1C); 563 | #endif 564 | #if defined(TCCR1D) 565 | Serial.print(F("TCCR1D=")); 566 | Serial.printlnHex(TCCR1D); 567 | #endif 568 | #if defined(TCCR1E) 569 | Serial.print(F("TCCR1E=")); 570 | Serial.printlnHex(TCCR1E); 571 | #endif 572 | Serial.print(F("OCR1A=")); 573 | Serial.printlnHex(OCR1A); 574 | Serial.print(F("OCR1B=")); 575 | Serial.printlnHex(OCR1B); 576 | #if defined(OCR1C) 577 | Serial.print(F("OCR1C=")); 578 | Serial.printlnHex(OCR1C); 579 | #endif 580 | Serial.print(F("TCNT1=")); 581 | Serial.printlnHex(TCNT1); 582 | #if defined(TIMSK1) 583 | Serial.print(F("TIMSK1=")); 584 | Serial.printlnHex(TIMSK1); 585 | #endif 586 | #if defined(TIFR1) 587 | Serial.print(F("TIFR1=")); 588 | Serial.printlnHex(TIFR1); 589 | #endif 590 | 591 | TimerCommonRegisterDump(); 592 | 593 | Serial.println(); 594 | } 595 | 596 | void TimerRegisterDump(void) { 597 | Timer0RegisterDump(); 598 | Timer1RegisterDump(); 599 | } 600 | 601 | #if ! defined(ARDUINO_AVR_DIGISPARKPRO) 602 | void ADCChannelDump(void) { 603 | Serial.println(F("ADC channel dump:")); 604 | #if defined(A0) 605 | Serial.print(F("A0=")); 606 | Serial.println(analogRead(A0)); 607 | #endif 608 | #if defined(A1) 609 | Serial.print(F("A1=")); 610 | Serial.println(analogRead(A1)); 611 | #endif 612 | #if defined(A2) 613 | Serial.print(F("A2=")); 614 | Serial.println(analogRead(A2)); 615 | #endif 616 | Serial.print(F("A3=")); 617 | Serial.println(analogRead(A3)); 618 | Serial.print(F("1.1V=")); 619 | Serial.println(analogRead(12)); 620 | Serial.print(F("GND=")); 621 | Serial.println(analogRead(13)); 622 | Serial.print(F("Tmp=")); 623 | Serial.println(analogRead(15)); 624 | Serial.print(F("ADMUX=")); 625 | Serial.printlnHex(ADMUX); 626 | } 627 | #endif 628 | #else // defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 629 | void Timer0RegisterDump(void) { 630 | Serial.println(F("Timer0 Register dump:")); 631 | Serial.print(F("TIMER0 TCCR0A = 0x")); 632 | Serial.println(TCCR0A, HEX); 633 | Serial.print(F("TIMER0 TCCR0B = 0x")); 634 | Serial.println(TCCR0B, HEX); 635 | Serial.print(F("TIMER0 OCR0A = 0x")); 636 | Serial.println(OCR0A, HEX); 637 | Serial.print(F("TIMER0 OCR0B = 0x")); 638 | Serial.println(OCR0B, HEX); 639 | Serial.print(F("TIMER0 TIMSK0 = 0x")); 640 | Serial.println(TIMSK0, HEX); 641 | Serial.print(F("TIMER0 TCNT0 = 0x")); 642 | Serial.println(TCNT0, HEX); 643 | Serial.print(F("TIMER0 TIFR0 = 0x")); 644 | Serial.println(TIFR0, HEX); 645 | } 646 | 647 | #if defined(TCCR1A) 648 | void Timer1RegisterDump(void) { 649 | Serial.println(F("Timer1 Register dump:")); 650 | Serial.print(F("TIMER1 TCCR1A = 0x")); 651 | Serial.println(TCCR1A, HEX); 652 | Serial.print(F("TIMER1 TCCR1B = 0x")); 653 | Serial.println(TCCR1B, HEX); 654 | Serial.print(F("TIMER1 TCCR1C = 0x")); 655 | Serial.println(TCCR1C, HEX); 656 | Serial.print(F("TIMER1 OCR1A = 0x")); 657 | Serial.println(OCR1A, HEX); 658 | Serial.print(F("TIMER1 OCR1B = 0x")); 659 | Serial.println(OCR1B, HEX); 660 | Serial.print(F("TIMER1 TIMSK1 = 0x")); 661 | Serial.println(TIMSK1, HEX); 662 | Serial.print(F("TIMER1 TCNT1 = 0x")); 663 | Serial.println(TCNT1, HEX); 664 | Serial.print(F("TIMER1 ICR1 = 0x")); 665 | Serial.println(ICR1, HEX); 666 | Serial.print(F("TIMER1 TIFR1 = 0x")); 667 | Serial.println(TIFR1, HEX); 668 | } 669 | #endif 670 | 671 | #if defined(TCCR2A) 672 | void Timer2RegisterDump(void) { 673 | Serial.println(F("Timer2 Register dump:")); 674 | Serial.print(F("TIMER2 TCCR2A = 0x")); 675 | Serial.println(TCCR2A, HEX); 676 | Serial.print(F("TIMER2 TCCR2B = 0x")); 677 | Serial.println(TCCR2B, HEX); 678 | Serial.print(F("TIMER2 OCR2A = 0x")); 679 | Serial.println(OCR2A, HEX); 680 | Serial.print(F("TIMER2 OCR2B = 0x")); 681 | Serial.println(OCR2B, HEX); 682 | Serial.print(F("TIMER2 TIMSK2 = 0x")); 683 | Serial.println(TIMSK2, HEX); 684 | Serial.print(F("TIMER2 TCNT2 = 0x")); 685 | Serial.println(TCNT2, HEX); 686 | Serial.print(F("TIMER2 TIFR2 = 0x")); 687 | Serial.println(TIFR2, HEX); 688 | Serial.print(F("TIMER2 ASSR = 0x")); 689 | Serial.println(ASSR, HEX); 690 | Serial.print(F("TIMERn GTCCR = 0x")); 691 | Serial.println(GTCCR, HEX); 692 | } 693 | #endif 694 | 695 | void TimerRegisterDump(void) { 696 | Timer0RegisterDump(); 697 | Timer1RegisterDump(); 698 | Timer2RegisterDump(); 699 | } 700 | #endif // AVR 701 | 702 | #endif // !defined(__AVR_ATmega32U4__) 703 | -------------------------------------------------------------------------------- /examples/WhistleSwitch/ShowInfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ShowInfo.h 3 | * 4 | * Copyright (C) 2018-2019 Armin Joachimsmeyer 5 | * armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * Arduino-Utils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #ifndef _SHOW_INFO_H 25 | #define _SHOW_INFO_H 26 | 27 | #include 28 | 29 | // Based on https://playground.arduino.cc/Main/ShowInfo 30 | 31 | float GetTemp(void); 32 | 33 | // Helper function for free ram. 34 | // With use of http://playground.arduino.cc/Code/AvailableMemory 35 | // 36 | int freeRam(void); 37 | 38 | 39 | // Helper function for sketch size. 40 | // The sketch size is runtime calculated. 41 | // From user "Coding Badly" in his post: 42 | // http://arduino.cc/forum/index.php/topic,115870.msg872309.html#msg872309 43 | // Changed into unsigned long for code size larger than 64kB. 44 | // 45 | // This function returns the sketch size 46 | // for a size between 0 and 32k. If the code 47 | // size is larger (for example with an Arduino Mega), 48 | // the return value is not valid. 49 | // 50 | unsigned long sketchSize(void); 51 | 52 | void Information(void); 53 | 54 | void printMCUSR(uint8_t aMCUSRContent); 55 | 56 | void printBODLevel(uint8_t aHighFuseBits); 57 | void printBODLevel(); 58 | 59 | #if defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 60 | void printFuses(void); 61 | void printBODSFlagExistence(); 62 | #endif // defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 63 | 64 | #if defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 65 | /* 66 | * Short version using printHex and saving Flash 67 | */ 68 | void TimerCommonRegisterDump(void); 69 | void Timer0RegisterDump(void); 70 | void Timer1RegisterDump(void); 71 | void TimerRegisterDump(void); 72 | 73 | # if ! defined(ARDUINO_AVR_DIGISPARKPRO) 74 | void ADCChannelDump(void); 75 | # endif 76 | 77 | #elif !defined(__AVR_ATmega32U4__) // defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny167__) || defined(__AVR_ATtiny87__) 78 | void Timer0RegisterDump(void); 79 | # if defined(TCCR1A) 80 | void Timer1RegisterDump(void) ; 81 | # endif 82 | 83 | # if defined(TCCR2A) 84 | void Timer2RegisterDump(void); 85 | # endif 86 | 87 | void TimerRegisterDump(void); 88 | #endif 89 | 90 | #endif // _SHOW_INFO_H 91 | -------------------------------------------------------------------------------- /examples/WhistleSwitch/digitalWriteFast.h: -------------------------------------------------------------------------------- 1 | #define BIT_READ(value, bit) ((value) & (1UL << (bit))) 2 | #define BIT_SET(value, bit) ((value) |= (1UL << (bit))) 3 | #define BIT_CLEAR(value, bit) ((value) &= ~(1UL << (bit))) 4 | #define BIT_WRITE(value, bit, bitvalue) (bitvalue ? BIT_SET(value, bit) : BIT_CLEAR(value, bit)) 5 | 6 | #if !defined(digitalPinToPortReg) 7 | #if (defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || \ 8 | defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__)) 9 | // Arduino Mega Pins 10 | #define __digitalPinToPortReg(P) \ 11 | (((P) >= 22 && (P) <= 29) ? &PORTA : \ 12 | ((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PORTB : \ 13 | (((P) >= 30 && (P) <= 37) ? &PORTC : \ 14 | ((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PORTD : \ 15 | ((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PORTE : \ 16 | (((P) >= 54 && (P) <= 61) ? &PORTF : \ 17 | ((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PORTG : \ 18 | ((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PORTH : \ 19 | (((P) == 14 || (P) == 15) ? &PORTJ : \ 20 | (((P) >= 62 && (P) <= 69) ? &PORTK : &PORTL)))))))))) 21 | 22 | #define __digitalPinToDDRReg(P) \ 23 | (((P) >= 22 && (P) <= 29) ? &DDRA : \ 24 | ((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &DDRB : \ 25 | (((P) >= 30 && (P) <= 37) ? &DDRC : \ 26 | ((((P) >= 18 && (P) <= 21) || (P) == 38) ? &DDRD : \ 27 | ((((P) >= 0 && (P) <= 3) || (P) == 5) ? &DDRE : \ 28 | (((P) >= 54 && (P) <= 61) ? &DDRF : \ 29 | ((((P) >= 39 && (P) <= 41) || (P) == 4) ? &DDRG : \ 30 | ((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &DDRH : \ 31 | (((P) == 14 || (P) == 15) ? &DDRJ : \ 32 | (((P) >= 62 && (P) <= 69) ? &DDRK : &DDRL)))))))))) 33 | 34 | #define __digitalPinToPINReg(P) \ 35 | (((P) >= 22 && (P) <= 29) ? &PINA : \ 36 | ((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PINB : \ 37 | (((P) >= 30 && (P) <= 37) ? &PINC : \ 38 | ((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PIND : \ 39 | ((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PINE : \ 40 | (((P) >= 54 && (P) <= 61) ? &PINF : \ 41 | ((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PING : \ 42 | ((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PINH : \ 43 | (((P) == 14 || (P) == 15) ? &PINJ : \ 44 | (((P) >= 62 && (P) <= 69) ? &PINK : &PINL)))))))))) 45 | 46 | #define __digitalPinToBit(P) \ 47 | (((P) >= 7 && (P) <= 9) ? (P) - 3 : \ 48 | (((P) >= 10 && (P) <= 13) ? (P) - 6 : \ 49 | (((P) >= 22 && (P) <= 29) ? (P) - 22 : \ 50 | (((P) >= 30 && (P) <= 37) ? 37 - (P) : \ 51 | (((P) >= 39 && (P) <= 41) ? 41 - (P) : \ 52 | (((P) >= 42 && (P) <= 49) ? 49 - (P) : \ 53 | (((P) >= 50 && (P) <= 53) ? 53 - (P) : \ 54 | (((P) >= 54 && (P) <= 61) ? (P) - 54 : \ 55 | (((P) >= 62 && (P) <= 69) ? (P) - 62 : \ 56 | (((P) == 0 || (P) == 15 || (P) == 17 || (P) == 21) ? 0 : \ 57 | (((P) == 1 || (P) == 14 || (P) == 16 || (P) == 20) ? 1 : \ 58 | (((P) == 19) ? 2 : \ 59 | (((P) == 5 || (P) == 6 || (P) == 18) ? 3 : \ 60 | (((P) == 2) ? 4 : \ 61 | (((P) == 3 || (P) == 4) ? 5 : 7))))))))))))))) 62 | 63 | // 15 PWM 64 | #define __digitalPinToTimer(P) \ 65 | (((P) == 13 || (P) == 4) ? &TCCR0A : \ 66 | (((P) == 11 || (P) == 12) ? &TCCR1A : \ 67 | (((P) == 10 || (P) == 9) ? &TCCR2A : \ 68 | (((P) == 5 || (P) == 2 || (P) == 3) ? &TCCR3A : \ 69 | (((P) == 6 || (P) == 7 || (P) == 8) ? &TCCR4A : \ 70 | (((P) == 46 || (P) == 45 || (P) == 44) ? &TCCR5A : 0)))))) 71 | #define __digitalPinToTimerBit(P) \ 72 | (((P) == 13) ? COM0A1 : (((P) == 4) ? COM0B1 : \ 73 | (((P) == 11) ? COM1A1 : (((P) == 12) ? COM1B1 : \ 74 | (((P) == 10) ? COM2A1 : (((P) == 9) ? COM2B1 : \ 75 | (((P) == 5) ? COM3A1 : (((P) == 2) ? COM3B1 : (((P) == 3) ? COM3C1 : \ 76 | (((P) == 6) ? COM4A1 : (((P) == 7) ? COM4B1 : (((P) == 8) ? COM4C1 : \ 77 | (((P) == 46) ? COM5A1 : (((P) == 45) ? COM5B1 : COM5C1)))))))))))))) 78 | 79 | #elif (defined(__AVR_ATmega644__) || \ 80 | defined(__AVR_ATmega644P__)) 81 | // Arduino 644 Pins 82 | #define __digitalPinToPortReg(P) \ 83 | (((P) >= 0 && (P) <= 7) ? &PORTB : (((P) >= 8 && (P) <= 15) ? &PORTD : (((P) >= 16 && (P) <= 23) ? &PORTC : &PORTA))) 84 | #define __digitalPinToDDRReg(P) \ 85 | (((P) >= 0 && (P) <= 7) ? &DDRB : (((P) >= 8 && (P) <= 15) ? &DDRD : (((P) >= 8 && (P) <= 15) ? &DDRC : &DDRA))) 86 | #define __digitalPinToPINReg(P) \ 87 | (((P) >= 0 && (P) <= 7) ? &PINB : (((P) >= 8 && (P) <= 15) ? &PIND : (((P) >= 8 && (P) <= 15) ? &PINC : &PINA))) 88 | #define __digitalPinToBit(P) \ 89 | (((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 15) ? (P) - 8 : (((P) >= 16 && (P) <= 23) ? (P) - 16 : (P) - 24))) 90 | 91 | // 6 PWM 92 | #define __digitalPinToTimer(P) \ 93 | (((P) == 3 || (P) == 4) ? &TCCR0A : \ 94 | (((P) == 12 || (P) == 13) ? &TCCR1A : \ 95 | (((P) == 14 || (P) == 15) ? &TCCR2A : 0))) 96 | #define __digitalPinToTimerBit(P) \ 97 | (((P) == 3) ? COM0A1 : (((P) == 4) ? COM0B1 : \ 98 | (((P) == 13) ? COM1A1 : (((P) == 12) ? COM1B1 : \ 99 | (((P) == 15) ? COM2A1 : COM2B1))))) 100 | 101 | // --- Arduino Leonardo --- 102 | #elif (defined(ARDUINO_AVR_LEONARDO) || \ 103 | defined(__AVR_ATmega16U4__) || \ 104 | defined(__AVR_ATmega32U4__)) 105 | 106 | #define UART_RX_PIN (0) //PD2 107 | #define UART_TX_PIN (1) //PD3 108 | 109 | #define I2C_SDA_PIN (2) //PD1 110 | #define I2C_SCL_PIN (3) //PD0 111 | 112 | #define SPI_HW_SS_PIN (17) //PB0 113 | #define SPI_HW_MOSI_PIN (16) //PB2 114 | #define SPI_HW_MISO_PIN (14) //PB3 115 | #define SPI_HW_SCK_PIN (15) //PB1 116 | 117 | #define __digitalPinToPortReg(P) \ 118 | ((((P) >= 0 && (P) <= 4) || (P) == 6 || (P) == 12 || (P) == 24 || (P) == 25 || (P) == 29) ? &PORTD : (((P) == 5 || (P) == 13) ? &PORTC : (((P) >= 18 && (P) <= 23)) ? &PORTF : (((P) == 7) ? &PORTE : &PORTB))) 119 | #define __digitalPinToDDRReg(P) \ 120 | ((((P) >= 0 && (P) <= 4) || (P) == 6 || (P) == 12 || (P) == 24 || (P) == 25 || (P) == 29) ? &DDRD : (((P) == 5 || (P) == 13) ? &DDRC : (((P) >= 18 && (P) <= 23)) ? &DDRF : (((P) == 7) ? &DDRE : &DDRB))) 121 | #define __digitalPinToPINReg(P) \ 122 | ((((P) >= 0 && (P) <= 4) || (P) == 6 || (P) == 12 || (P) == 24 || (P) == 25 || (P) == 29) ? &PIND : (((P) == 5 || (P) == 13) ? &PINC : (((P) >= 18 && (P) <= 23)) ? &PINF : (((P) == 7) ? &PINE : &PINB))) 123 | #define __digitalPinToBit(P) \ 124 | (((P) >= 8 && (P) <= 11) ? (P) - 4 : (((P) >= 18 && (P) <= 21) ? 25 - (P) : (((P) == 0) ? 2 : (((P) == 1) ? 3 : (((P) == 2) ? 1 : (((P) == 3) ? 0 : (((P) == 4) ? 4 : (((P) == 6) ? 7 : (((P) == 13) ? 7 : (((P) == 14) ? 3 : (((P) == 15) ? 1 : (((P) == 16) ? 2 : (((P) == 17) ? 0 : (((P) == 22) ? 1 : (((P) == 23) ? 0 : (((P) == 24) ? 4 : (((P) == 25) ? 7 : (((P) == 26) ? 4 : (((P) == 27) ? 5 : 6 ))))))))))))))))))) 125 | 126 | #else 127 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 128 | // we have only PORTB - Extension 01.03.2018 129 | #define __digitalPinToPortReg(P) (&PORTB) 130 | #define __digitalPinToDDRReg(P) (&DDRB) 131 | #define __digitalPinToPINReg(P) (&PINB) 132 | #define __digitalPinToBit(P) \ 133 | (((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 13) ? (P) - 8 : (P) - 14)) 134 | #else 135 | // Standard Arduino Pins 136 | #define __digitalPinToPortReg(P) \ 137 | (((P) >= 0 && (P) <= 7) ? &PORTD : (((P) >= 8 && (P) <= 13) ? &PORTB : &PORTC)) 138 | #define __digitalPinToDDRReg(P) \ 139 | (((P) >= 0 && (P) <= 7) ? &DDRD : (((P) >= 8 && (P) <= 13) ? &DDRB : &DDRC)) 140 | #define __digitalPinToPINReg(P) \ 141 | (((P) >= 0 && (P) <= 7) ? &PIND : (((P) >= 8 && (P) <= 13) ? &PINB : &PINC)) 142 | #define __digitalPinToBit(P) \ 143 | (((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 13) ? (P) - 8 : (P) - 14)) 144 | #endif 145 | 146 | #if defined(__AVR_ATmega8__) 147 | // 3 PWM 148 | #define __digitalPinToTimer(P) \ 149 | (((P) == 9 || (P) == 10) ? &TCCR1A : (((P) == 11) ? &TCCR2 : 0)) 150 | #define __digitalPinToTimerBit(P) \ 151 | (((P) == 9) ? COM1A1 : (((P) == 10) ? COM1B1 : COM21)) 152 | #else //168,328 153 | 154 | // 6 PWM 155 | #define __digitalPinToTimer(P) \ 156 | (((P) == 6 || (P) == 5) ? &TCCR0A : \ 157 | (((P) == 9 || (P) == 10) ? &TCCR1A : \ 158 | (((P) == 11 || (P) == 3) ? &TCCR2A : 0))) 159 | #define __digitalPinToTimerBit(P) \ 160 | (((P) == 6) ? COM0A1 : (((P) == 5) ? COM0B1 : \ 161 | (((P) == 9) ? COM1A1 : (((P) == 10) ? COM1B1 : \ 162 | (((P) == 11) ? COM2A1 : COM2B1))))) 163 | #endif //defined(__AVR_ATmega8__) 164 | 165 | #endif 166 | #endif //#if !defined(digitalPinToPortReg) 167 | 168 | #if !defined(digitalWriteFast) 169 | #define digitalWriteFast(P, V) \ 170 | if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ 171 | BIT_WRITE(*__digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \ 172 | } else { \ 173 | digitalWrite((P), (V)); \ 174 | } 175 | #endif 176 | 177 | #if !defined(pinModeFast) 178 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 179 | // we have only PORTB - Extension 01.03.2018 180 | #define pinModeFast(P, V) \ 181 | if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ 182 | if (V == INPUT_PULLUP) {\ 183 | BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (INPUT)); \ 184 | BIT_WRITE(*__digitalPinToPortReg(P), __digitalPinToBit(P), (HIGH)); \ 185 | } else { \ 186 | BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (V)); \ 187 | } \ 188 | } else { \ 189 | pinMode((P), (V)); \ 190 | } 191 | #else 192 | #define pinModeFast(P, V) \ 193 | if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ 194 | if (digitalPinToTimer(P)) \ 195 | BIT_CLEAR(*__digitalPinToTimer(P), __digitalPinToTimerBit(P)); \ 196 | if (V == INPUT_PULLUP) {\ 197 | BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (INPUT)); \ 198 | BIT_WRITE(*__digitalPinToPortReg(P), __digitalPinToBit(P), (HIGH)); \ 199 | } else { \ 200 | BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (V)); \ 201 | } \ 202 | } else { \ 203 | pinMode((P), (V)); \ 204 | } 205 | #endif 206 | #endif 207 | 208 | #if !defined(digitalReadFast) 209 | #define digitalReadFast(P) ( (int) __digitalReadFast((P)) ) 210 | #define __digitalReadFast(P) \ 211 | (__builtin_constant_p(P)) \ 212 | ? BIT_READ(*__digitalPinToPINReg(P), __digitalPinToBit(P)) \ 213 | : digitalRead((P)) 214 | #endif 215 | 216 | // Extension 01.03.2018 217 | #if !defined(digitalToggleFast) 218 | #define digitalToggleFast(P) BIT_SET(*__digitalPinToPINReg(P), __digitalPinToBit(P)) 219 | #endif 220 | 221 | -------------------------------------------------------------------------------- /extras/SimpleFrequencyDetectorPlotterOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/Arduino-FrequencyDetector/2f30fc9a3ab8da1b98e23973bdbc3da42f1a8d5d/extras/SimpleFrequencyDetectorPlotterOutput.png -------------------------------------------------------------------------------- /extras/SimpleFrequencyDetector_MAX9814.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/Arduino-FrequencyDetector/2f30fc9a3ab8da1b98e23973bdbc3da42f1a8d5d/extras/SimpleFrequencyDetector_MAX9814.jpg -------------------------------------------------------------------------------- /extras/WhistleSwitchPlotterOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/Arduino-FrequencyDetector/2f30fc9a3ab8da1b98e23973bdbc3da42f1a8d5d/extras/WhistleSwitchPlotterOutput.png -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For FrequencyDetector 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | ####################################### 10 | # Methods and Functions (KEYWORD2) 11 | ####################################### 12 | 13 | setFrequencyDetectorControlDefaults KEYWORD2 14 | setFrequencyDetectorReadingDefaults KEYWORD2 15 | setFrequencyDetectorReadingValues KEYWORD2 16 | setFrequencyDetectorMatchValues KEYWORD2 17 | setFrequencyDetectorDropoutValues KEYWORD2 18 | readSignal KEYWORD2 19 | doPlausi KEYWORD2 20 | computeDirectAndFilteredMatch KEYWORD2 21 | 22 | setFrequencyDetectorControlDefaults KEYWORD2 23 | setFrequencyDropoutDefaults KEYWORD2 24 | setFrequencyDetectorReadingDefaults KEYWORD2 25 | setFrequencyDetectorReadingValues KEYWORD2 26 | setFrequencyDetectorReadingPrescaleValue KEYWORD2 27 | setFrequencyDetectorMatchValues KEYWORD2 28 | setFrequencyDetectorDropoutCounts KEYWORD2 29 | setFrequencyDetectorDropoutTimes KEYWORD2 30 | 31 | printTriggerValues KEYWORD2 32 | printFrequencyMatchValues KEYWORD2 33 | printPeriodLengthArray KEYWORD2 34 | printResultLegendForArduinoPlotter KEYWORD2 35 | printResultDataForArduinoPlotter KEYWORD2 36 | printInputSignalValuesForArduinoPlotter KEYWORD2 37 | 38 | ####################################### 39 | # Constants (LITERAL1) 40 | ####################################### 41 | 42 | NUMBER_OF_SAMPLES LITERAL1 43 | ADC_CHANNEL_DEFAULT LITERAL1 44 | PRESCALE_VALUE_DEFAULT LITERAL1 45 | RAW_VOLTAGE_MIN_DELTA_DEFAULT LITERAL1 46 | FREQUENCY_MATCH_INVALID LITERAL1 47 | FREQUENCY_MATCH_LOWER LITERAL1 48 | FREQUENCY_MATCH LITERAL1 49 | FREQUENCY_MATCH_HIGHER LITERAL1 50 | SIGNAL_NO_TRIGGER LITERAL1 51 | SIGNAL_STRENGTH_LOW LITERAL1 52 | SIGNAL_FIRST_LAST_PLAUSI_FAILED LITERAL1 53 | SIGNAL_DISTRIBUTION_PLAUSI_FAILED LITERAL1 54 | RAW_VOLTAGE_MIN_DELTA_DEFAULT LITERAL1 55 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FrequencyDetector", 3 | "version": "2.0.2", 4 | "keywords": "frequency, pitch, meter, Hz, detector", 5 | "description": "Measure the main frequency of analog captured signal connected to an Arduino and check for noise and mute.", 6 | "homepage": "https://github.com/ArminJo/Arduino-FrequencyDetector", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/ArminJo/Arduino-FrequencyDetector.git" 10 | }, 11 | "authors": { 12 | "name": "Armin Joachimsmeyer", 13 | "email": "armin.arduino@gmail.com", 14 | "maintainer": true 15 | }, 16 | "license": "GPL-3.0-or-later", 17 | "frameworks": "arduino", 18 | "platforms": ["atmelavr"], 19 | "headers": "FrequencyDetector.hpp", 20 | "examples": "examples/*/*.ino", 21 | "export": {"exclude": [".github", "pictures"]} 22 | } -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=FrequencyDetector 2 | version=2.0.2 3 | author=Armin Joachimsmeyer 4 | maintainer=Armin Joachimsmeyer 5 | sentence=Measure the main frequency of analog captured signal connected to an Arduino and check for noise and mute.
6 | paragraph=Detects frequency from 38 Hz to 9612 Hz and works even on an ATTiny85 with 1 MHz up to 4806 Hz. Signal can be delivered e.g. by a microphone. New: Bug fix for overflow at FREQUENCY_RANGE_HIGH. 7 | category=Signal Input/Output 8 | url=https://github.com/ArminJo/Arduino-FrequencyDetector 9 | depends=ATtinySerialOut 10 | architectures=avr 11 | includes=FrequencyDetector.h -------------------------------------------------------------------------------- /src/DebugLevel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DebugLevel.h 3 | * Include to propagate debug levels to each other 4 | * 5 | * TRACE // Information you need to understand details of a function or if you hunt a bug. 6 | * DEBUG // Information need to understand the operating of your program. E.g. function calls and values of control variables. 7 | * INFO // Information you want to see in regular operation to see what the program is doing. E.g. "Now playing Muppets melody". 8 | * WARN // Information that the program may encounter problems, like small Heap/Stack area. 9 | * ERROR // Informations to explain why the program will not run. E.g. not enough Ram for all created objects. 10 | * 11 | * 12 | * Copyright (C) 2016-2024 Armin Joachimsmeyer 13 | * Email: armin.joachimsmeyer@gmail.com 14 | * 15 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 16 | * 17 | * Arduino-Utils is free software: you can redistribute it and/or modify 18 | * it under the terms of the GNU General Public License as published by 19 | * the Free Software Foundation, either version 3 of the License, or 20 | * (at your option) any later version. 21 | * 22 | * This program is distributed in the hope that it will be useful, 23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 25 | * See the GNU General Public License for more details. 26 | * 27 | * You should have received a copy of the GNU General Public License 28 | * along with this program. If not, see . 29 | * 30 | */ 31 | 32 | #ifndef _DEBUGLEVEL_H 33 | #define _DEBUGLEVEL_H 34 | 35 | // Propagate different debug level 36 | #if defined(TRACE) // Information you need to understand details of a function or if you hunt a bug. 37 | # if !defined(DEBUG) 38 | #define DEBUG // Information need to understand the operating of your program. E.g. function calls and values of control variables. 39 | # endif 40 | #endif 41 | #if defined(DEBUG) 42 | # if !defined(INFO) 43 | #define INFO // Information you want to see in regular operation to see what the program is doing. E.g. "START ../src/LightToTone.cpp Version 1.2 from Dec 31 2019" or "Now playing Muppets melody". 44 | # endif 45 | #endif 46 | #if defined(INFO) 47 | # if !defined(WARN) 48 | #define WARN // Information that the program may encounter problems, like small Heap/Stack area. 49 | # endif 50 | #endif 51 | #if defined(WARN) 52 | # if !defined(ERROR) 53 | #define ERROR // Informations to explain why the program will not run. E.g. not enough Ram for all created objects. 54 | # endif 55 | #endif 56 | 57 | #endif // _DEBUGLEVEL_H 58 | -------------------------------------------------------------------------------- /src/FrequencyDetector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * FrequencyDetector.h 3 | * 4 | * Analyzes a microphone signal and outputs the detected frequency. 5 | * 6 | * Copyright (C) 2014-2025 Armin Joachimsmeyer 7 | * Email: armin.joachimsmeyer@gmail.com 8 | * 9 | * This file is part of Arduino-FrequencyDetector https://github.com/ArminJo/Arduino-FrequencyDetector. 10 | * 11 | * Arduino-FrequencyDetector is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 19 | * See the GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program. If not, see . 23 | * 24 | */ 25 | 26 | #ifndef _FREQUENCY_DETECTOR_H 27 | #define _FREQUENCY_DETECTOR_H 28 | 29 | #define VERSION_FREQUENCY_DETECTOR "2.0.2" 30 | #define VERSION_FREQUENCY_DETECTOR_MAJOR 2 31 | #define VERSION_FREQUENCY_DETECTOR_MINOR 0 32 | #define VERSION_FREQUENCY_DETECTOR_PATCH 2 33 | 34 | // The change log is at the bottom of the file 35 | 36 | /* 37 | * Enable ADC_PRESCALE_VALUE_IS_NOT_CONSTANT if you do not use the constant PRESCALE_VALUE_DEFAULT for parameter ADCPrescalerValue 38 | * in the call of setFrequencyDetectorReadingValues() or setFrequencyDetectorReadingPrescaleValue() 39 | */ 40 | //#define ADC_PRESCALE_VALUE_IS_NOT_CONSTANT 41 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 42 | #include "ATtinySerialOut.h" // For redefining Print. Available as Arduino library 43 | #define NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION 512L // This does NOT occupy RAM, only (NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION / MIN_SAMPLES_PER_PERIOD) bytes are required. 44 | #else 45 | #define NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION 1024L 46 | #endif 47 | 48 | //#define PRINT_INPUT_SIGNAL_TO_PLOTTER // If enabled, store SIGNAL_PLOTTER_BUFFER_SIZE input samples for printing to Arduino Plotter 49 | 50 | #if ((RAMEND - RAMSTART) < 1023) 51 | #define SIGNAL_PLOTTER_BUFFER_SIZE 100 // ATtiny85 -> Store only start of signal in plotter buffer 52 | #elif (NUMBER_OF_SAMPLES < 1024) 53 | #define SIGNAL_PLOTTER_BUFFER_SIZE NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION // ATmega328 -> Can store complete signal in plotter buffer 54 | #else 55 | #define SIGNAL_PLOTTER_BUFFER_SIZE 512 56 | #endif 57 | 58 | //#define FREQUENCY_RANGE_LOW // use it for frequencies below approximately 500 Hz 59 | //#define FREQUENCY_RANGE_HIGH // use it for frequencies above approximately 2000 Hz 60 | #if ! defined(FREQUENCY_RANGE_LOW) && ! defined(FREQUENCY_RANGE_HIGH) 61 | #define FREQUENCY_RANGE_DEFAULT // good for frequencies from 100 to 2200 Hz 62 | #endif 63 | 64 | /* 65 | * Global settings which are required at compile time 66 | */ 67 | /* 68 | * Number of samples used for detecting the frequency of the signal. 69 | * 1024 16 MHz clock with prescaler 128: 106.496 milliseconds / 9.39 Hz. With 13 cycles/sample (=> 104 usec/sample | 9615 Hz sample rate) 70 | * 1024 16 MHz clock with prescaler 64: 53.248 milliseconds / 18.78 Hz. With 13 cycles/sample (=> 52 usec/sample | 19230 Hz sample rate) 71 | * 1024 16 MHz clock with prescaler 16: 13.312 milliseconds / 75.12 Hz. With 13 cycles/sample (=> 13 usec/sample | 76923 Hz sample rate) 72 | * 512 1 MHz clock with prescaler 4: 26.624 milliseconds / 37.56 Hz. With 13 cycles/sample (=> 52 usec/sample | 19230 Hz sample rate) 73 | * FREQUENCY_RANGE_HIGH -> 13 usec/sample -> 300 to 9612 Hz with 1024 samples and 600 to 9612 Hz with 512 samples. 74 | * FREQUENCY_RANGE_DEFAULT -> 52 usec/sample -> 75 to 2403 Hz with 1024 samples and 150 to 2403 Hz with 512 samples. 75 | * FREQUENCY_RANGE_LOW -> 104 usec/sample -> 38 to 1202 Hz with 1024 samples and 75 to 1202 Hz with 512 samples. 76 | */ 77 | 78 | /* 79 | * Default values for plausibility, you may change them if necessary 80 | */ 81 | #define MIN_SAMPLES_PER_PERIOD 8 // => Max frequency is 2403 Hz at 52 usec/sample, 9612 Hz at 13 usec/sample 82 | /* 83 | * For n signal periods we have 2n or (2n + 1) trigger 84 | * 8 trigger per buffer = 512/4=128 samples per period => 150 Hz for 52 usec/sample, 600 Hz for 13 usec/sample at 512 samples. 85 | * So we have 150 to 2403 Hz at 52 usec/sample and 512 buffer 86 | */ 87 | #define MINIMUM_NUMBER_OF_TRIGGER_PER_BUFFER 8 // => Min frequency is 150 Hz at 52 usec/sample, 600 Hz at 13 usec/sample for 512 buffer size 88 | #define SIZE_OF_PERIOD_LENGTH_ARRAY_FOR_PLAUSI (NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION / MIN_SAMPLES_PER_PERIOD) // 512 / 8 = 64 89 | 90 | /* 91 | * Defaults for reading 92 | */ 93 | #define ADC_CHANNEL_DEFAULT 1 // Channel ADC1 (PB2 on ATtiny85) 94 | #define RAW_VOLTAGE_MIN_DELTA_DEFAULT 0x40 // 1/16 of max amplitude for minimum signal strength 95 | 96 | /* 97 | * Defaults for match 98 | */ 99 | #define FREQUENCY_MIN_DEFAULT 1000 100 | #define FREQUENCY_MAX_DEFAULT 2000 101 | 102 | /* 103 | * Milliseconds (converted to number of readings) of allowed error (FrequencyRaw <= SIGNAL_MAX_ERROR_CODE) conditions, before match = FREQUENCY_MATCH_INVALID 104 | */ 105 | #define MAX_DROPOUT_MILLIS_BEFORE_NO_FILTERED_MATCH_DEFAULT 200 106 | 107 | /* 108 | * Milliseconds (converted to number of readings) of required valid readings (FrequencyRaw > SIGNAL_MAX_ERROR_CODE) before any (lower, match, higher) match - to avoid short flashes at random signal input 109 | */ 110 | #define MIN_NO_DROPOUT_MILLIS_BEFORE_ANY_MATCH_DEFAULT 150 111 | 112 | // sample time values for Prescaler for 16 MHz 4(13*0,25=3,25 us), 8(6,5 us), 16(13 us), 32(26 us), 64(52 us), 128(104 us) 113 | #define ADC_PRESCALE2 1 114 | #define ADC_PRESCALE4 2 115 | #define ADC_PRESCALE8 3 116 | #define ADC_PRESCALE16 4 117 | #define ADC_PRESCALE32 5 118 | #define ADC_PRESCALE64 6 119 | #define ADC_PRESCALE128 7 120 | 121 | #if !defined(ADC_PRESCALE_VALUE_IS_NOT_CONSTANT) 122 | 123 | /* 124 | * Default timing for reading is 19,23 kHz sample rate, giving a range from 300 to 2403 Hz at 1024 samples. 125 | * Formula is F_CPU / (PrescaleFactor * 13) 126 | * For frequency below 500 Hz it might be good to use FREQUENCY_RANGE_LOW which increases the ADC clock prescale value by factor 2. 127 | * For frequencies above 1 kHz it might be good to use FREQUENCY_RANGE_HIGH which decreases the ADC clock prescale value by factor 4. 128 | */ 129 | #if defined(FREQUENCY_RANGE_DEFAULT) 130 | // 52 usec/sample -> 75 to 2403 Hz with 1024 samples and 150 to 2403 Hz with 512 samples. 131 | # if F_CPU == 16000000L 132 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE64 // 52 microseconds per ADC sample at 16 MHz Clock => 19.23 kHz sample rate 133 | # elif F_CPU == 8000000L 134 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE32 // 52 microseconds per ADC sample at 8 MHz Clock => 19.23 kHz sample rate 135 | # elif F_CPU == 1000000L 136 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE4 // 52 microseconds per ADC sample at 1 MHz Clock => 19.23 kHz sample rate 137 | # endif 138 | #define MICROS_PER_SAMPLE 52 139 | #elif defined(FREQUENCY_RANGE_LOW) 140 | // 104 usec/sample -> 38 to 1202 Hz with 1024 samples and 75 to 1202 Hz with 512 samples. 141 | # if F_CPU == 16000000L 142 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE128 // 104 microseconds per ADC sample at 16 MHz Clock => 9.615 kHz sample rate 143 | # elif F_CPU == 8000000L 144 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE64 // 104 microseconds per ADC sample at 8 MHz Clock => 9.615 kHz sample rate 145 | # elif F_CPU == 1000000L 146 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE8 // 104 microseconds per ADC sample at 1 MHz Clock => 9.615 kHz sample rate 147 | # endif 148 | #define MICROS_PER_SAMPLE 104 149 | #elif defined(FREQUENCY_RANGE_HIGH) 150 | // 13 usec/sample -> 300 to 9612 Hz with 1024 samples and 600 to 9612 Hz with 512 samples. 151 | # if F_CPU == 16000000L 152 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE16 // 13 microseconds per ADC sample at 16 MHz Clock => 76.923 kHz sample rate 153 | #define MICROS_PER_SAMPLE 13 154 | # elif F_CPU == 8000000L 155 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE8 // 13 microseconds per ADC sample at 8 MHz Clock => 76.923 kHz sample rate 156 | #define MICROS_PER_SAMPLE 13 157 | # elif F_CPU == 1000000L 158 | # error "At 1 MHz a sampling time of 26 microseconds can not be achieved. Increase F_CPU to 8000000." 159 | #define PRESCALE_VALUE_DEFAULT ADC_PRESCALE2 // 26 microseconds per ADC sample at 1 MHz Clock => 38.461 kHz sample rate 160 | #define MICROS_PER_SAMPLE 26 161 | # endif 162 | #endif // defined(FREQUENCY_RANGE_DEFAULT) 163 | #if !defined(PRESCALE_VALUE_DEFAULT) 164 | # error "F_CPU is not one of 16000000, 8000000 or 1000000" 165 | #endif 166 | 167 | #define MICROS_PER_BUFFER_READING (MICROS_PER_SAMPLE * NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION) 168 | #else 169 | #define MICROS_PER_BUFFER_READING (FrequencyDetectorControl.PeriodOfOneSampleMicros * NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION) 170 | #endif // !defined(ADC_PRESCALE_VALUE_IS_NOT_CONSTANT) 171 | 172 | #define CLOCKS_FOR_READ_SIGNAL_NO_LOOP 625 // Extra clock cycles outside of the loop for one signal reading. 173 | #define MICROS_FOR_READ_SIGNAL (MICROS_PER_BUFFER_READING + (CLOCKS_FOR_READ_SIGNAL_NO_LOOP / (F_CPU / 1000000))) 174 | 175 | // number of allowed error (FrequencyRaw <= SIGNAL_MAX_ERROR_CODE) conditions, before match = FREQUENCY_MATCH_INVALID 176 | #define MAX_DROPOUT_COUNT_BEFORE_NO_FILTERED_MATCH_DEFAULT ((MAX_DROPOUT_MILLIS_BEFORE_NO_FILTERED_MATCH_DEFAULT * 1000L) / MICROS_FOR_READ_SIGNAL) 177 | // number of required valid readings (FrequencyRaw > SIGNAL_MAX_ERROR_CODE) before any (lower, match, higher) match - to avoid short flashes at random signal input 178 | #define MIN_NO_DROPOUT_COUNT_BEFORE_ANY_MATCH_DEFAULT ((MIN_NO_DROPOUT_MILLIS_BEFORE_ANY_MATCH_DEFAULT * 1000L) / MICROS_FOR_READ_SIGNAL) 179 | 180 | /* 181 | * FrequencyRaw error values 182 | */ 183 | #define SIGNAL_NO_TRIGGER 0 184 | #define SIGNAL_STRENGTH_LOW 1 185 | // TOO_LOW - You get this error code if less than MINIMUM_NUMBER_OF_TRIGGER_PER_BUFFER (8) trigger occurs in the buffer 186 | #define SIGNAL_FREQUENCY_TOO_LOW 2 187 | // TOO_HIGH - You get this error code if more than SIZE_OF_PERIOD_LENGTH_ARRAY_FOR_PLAUSI trigger occurs in the buffer, i.e. less than MIN_SAMPLES_PER_PERIOD used for each sample 188 | #define SIGNAL_FREQUENCY_TOO_HIGH 3 189 | // You get this error code if more than 1/8 of the samples are greater than 1.5 or less than 0.75 of the average period 190 | #define SIGNAL_DISTRIBUTION_PLAUSI_FAILED 4 191 | #define SIGNAL_MAX_ERROR_CODE 4 // the highest error value 192 | 193 | const char ErrorString_0[] PROGMEM = "No trigger"; 194 | const char ErrorString_1[] PROGMEM = "Signal low"; 195 | const char ErrorString_2[] PROGMEM = "Frequency too low"; 196 | const char ErrorString_3[] PROGMEM = "Frequency too high / noise"; 197 | const char ErrorString_4[] PROGMEM = "Periods too different"; 198 | extern const char *ErrorStrings[SIGNAL_MAX_ERROR_CODE + 1]; 199 | 200 | // Result values for Match*. Use compiler switch -fshort-enums otherwise it inflates the generated code 201 | enum MatchStateEnum { 202 | FREQUENCY_MATCH_INVALID /*Errors have happened*/, FREQUENCY_MATCH_TOO_LOW, FREQUENCY_MATCH, FREQUENCY_MATCH_TOO_HIGH 203 | }; 204 | 205 | /* 206 | * Values for MatchLowPassFiltered 207 | * Valid values are filtered values from 50 to 150 208 | */ 209 | #define FILTER_VALUE_MAX 200 210 | #define FILTER_VALUE_MIN 0 211 | #define FILTER_VALUE_MIDDLE ((FILTER_VALUE_MAX + FILTER_VALUE_MIN)/2) 212 | #define FILTER_VALUE_THRESHOLD (FILTER_VALUE_MIDDLE/2) 213 | #define FILTER_VALUE_MATCH FILTER_VALUE_MIDDLE 214 | #define FILTER_VALUE_MATCH_HIGHER_THRESHOLD (FILTER_VALUE_MAX - FILTER_VALUE_THRESHOLD) 215 | #define FILTER_VALUE_MATCH_LOWER_THRESHOLD (FILTER_VALUE_MIN + FILTER_VALUE_THRESHOLD) 216 | 217 | struct FrequencyDetectorControlStruct { 218 | 219 | /********************************************** 220 | * All values are used or set by readSignal() 221 | *********************************************/ 222 | // INPUT 223 | /* 224 | * 3 Values set by setFrequencyDetectorReadingPrescaleValue() 225 | */ 226 | uint8_t ADCPrescalerValue; 227 | #if defined(ADC_PRESCALE_VALUE_IS_NOT_CONSTANT) 228 | uint32_t FrequencyEquivalentOfOneSample; // 1000000L / MICROS_PER_SAMPLE. 76923 for 13 us sample time 229 | uint16_t PeriodOfOneSampleMicros; // MICROS_PER_SAMPLE 230 | uint16_t PeriodOfOneReadSignalMillis; // to compute counts used for match timing 231 | #endif 232 | /* 233 | * Value set by setFrequencyDetectorReadingValues() 234 | * Minimum signal strength value to produce valid output and do new trigger level computation. Otherwise return SIGNAL_STRENGTH_LOW 235 | * Threshold for minimum SignalDelta of raw ADC value for valid signal strength. 0x40=312 mV at 5 V and 68.75 mV at 1.1 V, 0x20=156/34,37 mV 236 | */ 237 | uint16_t RawVoltageMinDelta; 238 | 239 | // INTERNALLY 240 | /* 241 | * internally computed values for automatic trigger level adjustment 242 | */ 243 | uint16_t TriggerLevel; // = MinValue + ((MaxValue - MinValue)/2) 244 | uint16_t TriggerLevelLower; // = TriggerLevel - (tDelta / 8) - for Hysteresis 245 | 246 | // OUTPUT 247 | /* 248 | * Values of sampled signal input 249 | */ 250 | uint16_t SignalDelta; // MaxValue - MinValue 251 | uint16_t AverageLevel; // = SumOfSampleValues / NumberOfSamples 252 | 253 | /* 254 | * Values computed by readSignal() to be used by doEqualDistributionPlausi() 255 | */ 256 | uint16_t FrequencyRaw; // Frequency in Hz set by readSignal() or "error code" SIGNAL_... set by doEqualDistributionPlausi() 257 | uint8_t PeriodCount; // Count of periods in current reading - !!! cannot be greater than SIZE_OF_PERIOD_LEGTH_ARRAY_FOR_PLAUSI - 1) (=63) !!! 258 | uint8_t PeriodLength[SIZE_OF_PERIOD_LENGTH_ARRAY_FOR_PLAUSI]; // Array of period length of the signal for plausi, size is NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION(512) / 8 (= 64) 259 | uint16_t TriggerFirstPosition; // position of first detection of a trigger in all samples 260 | uint16_t TriggerLastPosition; // position of last detection of a trigger in all samples 261 | 262 | /************************************************** 263 | * 9 Parameters for computeDirectAndFilteredMatch() 264 | *************************************************/ 265 | // INPUT 266 | uint16_t FrequencyMatchLow; // Thresholds for matching 267 | uint16_t FrequencyMatchHigh; 268 | 269 | uint8_t MaxMatchDropoutCount; // number of allowed error (FrequencyRaw <= SIGNAL_MAX_ERROR_CODE) conditions, before match = FREQUENCY_MATCH_INVALID 270 | uint8_t MinMatchNODropoutCount; // number of required valid readings (FrequencyRaw > SIGNAL_MAX_ERROR_CODE) before any (lower, match, higher) match - to avoid short flashes at random signal input 271 | // INTERNALLY 272 | // Clipped at MaxMatchDropoutCount + MinMatchNODropoutCount, so at least MinMatchNODropoutCount matches must happen to set FrequencyMatchFiltered not to FREQUENCY_MATCH_INVALID 273 | uint8_t MatchDropoutCount; // Current dropout count. If value falls below MaxMatchDropoutCount, filtered match is valid. 274 | 275 | // OUTPUT 276 | uint16_t FrequencyFiltered; // Low pass filter value for frequency, e.g. to compute stable difference to target frequency. 277 | 278 | uint8_t FrequencyMatchDirect; // Result of match: 0 to 3, FREQUENCY_MATCH_INVALID, FREQUENCY_MATCH_TOO_LOW, FREQUENCY_MATCH, FREQUENCY_MATCH_TOO_HIGH 279 | uint8_t FrequencyMatchFiltered; // same range asFrequencyMatchDirect. Match state processed by low pass filter 280 | // INTERNALLY 281 | uint8_t MatchLowPassFiltered; // internal value 0 to FILTER_VALUE_MAX/200. Low pass filter value for computing FrequencyMatchFiltered 282 | }; 283 | 284 | /* 285 | * Reference shift values are complicated for ATtinyX5 since we have the extra register bit REFS2 286 | * in ATTinyCore, this bit is handled programmatical and therefore the defines are different. 287 | * To keep my library small, I use the changed defines. 288 | * After including this file you can not call the ATTinyCore readAnalog functions reliable, if you specify references other than default! 289 | */ 290 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 291 | // defines are for ADCUtils.cpp, they can be used WITHOUT bit reordering 292 | #undef DEFAULT 293 | #undef EXTERNAL 294 | #undef INTERNAL1V1 295 | #undef INTERNAL 296 | #undef INTERNAL2V56 297 | #undef INTERNAL2V56_EXTCAP 298 | 299 | #define DEFAULT 0 300 | #define EXTERNAL 4 301 | #define INTERNAL1V1 8 302 | #define INTERNAL INTERNAL1V1 303 | #define INTERNAL2V56 9 304 | #define INTERNAL2V56_EXTCAP 13 305 | #endif 306 | 307 | #if defined(INTERNAL1V1) && !defined(INTERNAL) 308 | #define INTERNAL INTERNAL1V1 // for ATmega2560 309 | #endif 310 | 311 | extern FrequencyDetectorControlStruct FrequencyDetectorControl; 312 | 313 | void setFrequencyDetectorControlDefaults(); 314 | void setFrequencyDropoutDefaults(); 315 | void setFrequencyDetectorReadingDefaults(); 316 | void setFrequencyDetectorReadingValues(uint8_t aADCChannel, uint8_t aADCReference, uint8_t aADCPrescalerValue, 317 | uint16_t aRawVoltageMinDelta); 318 | void setFrequencyDetectorReadingPrescaleValue(uint8_t aADCPrescalerValue); 319 | void setFrequencyDetectorMatchValues(uint16_t aFrequencyMin, uint16_t aFrequencyMax); 320 | void setFrequencyDetectorDropoutCounts(uint8_t aMinMatchNODropoutCount, uint8_t aMaxMatchDropoutCount); 321 | bool setFrequencyDetectorDropoutTimes(uint16_t aMinMatchNODropoutMillis, uint16_t aMaxMatchDropoutMillis); 322 | 323 | uint16_t readSignal(); 324 | uint16_t doEqualDistributionPlausi(); 325 | void computeDirectAndFilteredMatch(uint16_t aFrequency); 326 | 327 | void printTriggerValues(Print *aSerial); 328 | void printFrequencyMatchValues(Print *aSerial); 329 | void printPeriodLengthArray(Print *aSerial); 330 | void printLegendForArduinoPlotter(Print *aSerial) __attribute__ ((deprecated ("Renamed to printResultLegendForArduinoPlotter()."))); 331 | void printDataForArduinoPlotter(Print *aSerial) __attribute__ ((deprecated ("Renamed to printResultDataForArduinoPlotter()."))); 332 | void printResultLegendForArduinoPlotter(Print *aSerial); 333 | void printResultDataForArduinoPlotter(Print *aSerial); 334 | #if defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 335 | void printSignalValuesForArduinoPlotter(Print *aSerial) __attribute__ ((deprecated ("Renamed to printInputSignalValuesForArduinoPlotter()."))); 336 | void printInputSignalValuesForArduinoPlotter(Print *aSerial); 337 | #endif 338 | 339 | /* 340 | * Version 2.0.2 - 4/2025 341 | * - Bug fix for overflow at FREQUENCY_RANGE_HIGH. 342 | * 343 | * Version 2.0.1 - 5/2023 344 | * - Renamed printSignalValuesForArduinoPlotter() to printInputSignalValuesForArduinoPlotter(), 345 | * printLegendForArduinoPlotter() to printResultLegendForArduinoPlotter() 346 | * and printDataForArduinoPlotter() to printResultDataForArduinoPlotter(). 347 | * 348 | * Version 2.0.0 - 9/2020 349 | * - Renamed `doPlausi()` to `doEqualDistributionPlausi()`. 350 | * - Changed error values and computation. 351 | * - Added documentation. 352 | * - Added MEASURE_READ_SIGNAL_TIMING capability. 353 | * - Added plotter output of input signal. 354 | * - Removed blocking wait for ATmega32U4 Serial in examples. 355 | * 356 | * 357 | * Version 1.1.0 - 1/2020 358 | * - Corrected formula for compensating millis(). 359 | * - New field PeriodOfOneReadSignalMillis. 360 | * - Now accept dropout values in milliseconds. 361 | * - New functions printResultLegendForArduinoPlotter() and printResultDataForArduinoPlotter(). 362 | */ 363 | 364 | #endif // _FREQUENCY_DETECTOR_H 365 | -------------------------------------------------------------------------------- /src/FrequencyDetector.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * FrequencyDetector.hpp 3 | * 4 | * Analyzes a microphone signal and outputs the detected frequency. It simply counts zero crossings and do not use FFT. 5 | * The ADC sample data is NOT stored in RAM, only the period lengths are stored in the PeriodLength[] array, 6 | * which is a byte array and has the size of NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION / 8. 7 | * 8 | * The timer 0 interrupt, which counts the milliseconds, is disabled during reading and enabled afterwards! 9 | * The value of millis() is adjusted after reading. 10 | * The alternative to disabling the interrupt is getting partially invalid results! 11 | * 12 | * By enabling PRINT_INPUT_SIGNAL_TO_PLOTTER it can be used as simple oscilloscope. 13 | * 14 | * Copyright (C) 2014-2025 Armin Joachimsmeyer 15 | * Email: armin.joachimsmeyer@gmail.com 16 | * 17 | * This file is part of Arduino-FrequencyDetector https://github.com/ArminJo/Arduino-FrequencyDetector. 18 | * 19 | * Arduino-FrequencyDetector is free software: you can redistribute it and/or modify 20 | * it under the terms of the GNU General Public License as published by 21 | * the Free Software Foundation, either version 3 of the License, or 22 | * (at your option) any later version. 23 | * 24 | * This program is distributed in the hope that it will be useful, 25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 27 | * See the GNU General Public License for more details. 28 | * 29 | * You should have received a copy of the GNU General Public License 30 | * along with this program. If not, see . 31 | * 32 | * FEATURES; 33 | * readSignal() is the ADC read routine, which reads 1024/512 samples and computes frequency of signal. 34 | * 35 | * doEqualDistributionPlausi() checks if the signal is not noisy and valid. It uses the following plausibility rules: 36 | * 1. A trigger must be detected in first and last 1/8 of samples 37 | * 2. Only 1/8 of the samples are allowed to be greater than 1.5 or less than 0.75 of the average period 38 | * 39 | * computeDirectAndFilteredMatch() wait for n matches within a given frequency range (FrequencyMatchLow - FrequencyMatchHigh) 40 | * and also low pass filters the result for smooth transitions between the 3 match states (lower, match, greater) 41 | * 42 | */ 43 | 44 | #ifndef _FREQUENCY_DETECTOR_HPP 45 | #define _FREQUENCY_DETECTOR_HPP 46 | 47 | #include 48 | 49 | #include "FrequencyDetector.h" 50 | 51 | //#define TRACE 52 | //#define DEBUG 53 | //#define INFO 54 | #include "DebugLevel.h" // to propagate debug levels 55 | 56 | // For external measurement of code timing. 57 | //#define MEASURE_READ_SIGNAL_TIMING 58 | #if defined(MEASURE_READ_SIGNAL_TIMING) 59 | #include "digitalWriteFast.h" 60 | // Pin is initialized at setFrequencyDetectorReadingValues(). 61 | # if !defined(READ_SIGNAL_TIMING_OUTPUT_PIN) 62 | # if (defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)) 63 | #define READ_SIGNAL_TIMING_OUTPUT_PIN 1 // use pin 1 / LED_BUILTIN for Digispark 64 | # else 65 | #define READ_SIGNAL_TIMING_OUTPUT_PIN 12 // use pin 12 66 | # endif 67 | # endif 68 | #endif 69 | 70 | // definitions from 71 | #if !defined(cbi) 72 | #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 73 | #endif 74 | #if !defined(sbi) 75 | #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) 76 | #endif 77 | 78 | #if !defined(STR) 79 | #define STR_HELPER(x) #x 80 | #define STR(x) STR_HELPER(x) 81 | #endif 82 | 83 | #include "MillisUtils.h" // for timer0_millis 84 | 85 | // 86 | #define maximumAllowableCountOf(aPeriodCountTotal) (aPeriodCountTotal / 8) 87 | 88 | FrequencyDetectorControlStruct FrequencyDetectorControl; 89 | 90 | const char *ErrorStrings[] = { ErrorString_0, ErrorString_1, ErrorString_2, ErrorString_3, ErrorString_4 }; 91 | 92 | // Union to speed up the combination of low and high bytes to a word 93 | // it is not optimal since the compiler still generates 2 unnecessary moves 94 | // but using -- value = (high << 8) | low -- gives 5 unnecessary instructions 95 | union Myword { 96 | struct { 97 | uint8_t LowByte; 98 | uint8_t HighByte; 99 | } byte; 100 | uint16_t UWord; 101 | int16_t Word; 102 | uint8_t *BytePointer; 103 | }; 104 | 105 | /**************************************************************** 106 | * Code starts here 107 | ***************************************************************/ 108 | /* 109 | * aADCChannel can be 0 to 7 or A0 to A7 110 | * aADCReference can be DEFAULT (VCC) or INTERNAL (1.1 volt) !! use the definitions 111 | * aADCPrescalerValue can be one of PRESCALE4, PRESCALE8, PRESCALE32, PRESCALE64 or PRESCALE128 112 | * aFrequencyOfOneSampleTimes100 depends on value of aADCPrescalerValue 113 | */ 114 | void setFrequencyDetectorReadingValues(uint8_t aADCChannel, const uint8_t aADCReference, uint8_t aADCPrescalerValue, 115 | uint16_t aRawVoltageMinDelta) { 116 | 117 | // Setup ADC, setMUX + Reference 118 | if (aADCChannel >= 14) { 119 | aADCChannel -= 14; // allow for channel or pin numbers 120 | } 121 | 122 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 123 | ADMUX = (aADCReference << REFS2) | aADCChannel; 124 | #else 125 | ADMUX = (aADCReference << REFS0) | aADCChannel; 126 | #endif 127 | 128 | setFrequencyDetectorReadingPrescaleValue(aADCPrescalerValue); 129 | FrequencyDetectorControl.RawVoltageMinDelta = aRawVoltageMinDelta; 130 | #if defined(MEASURE_READ_SIGNAL_TIMING) 131 | pinModeFast(READ_SIGNAL_TIMING_OUTPUT_PIN, OUTPUT); 132 | #endif 133 | } 134 | 135 | void setFrequencyDetectorReadingPrescaleValue(uint8_t aADCPrescalerValue) { 136 | FrequencyDetectorControl.ADCPrescalerValue = aADCPrescalerValue; 137 | #if defined(ADC_PRESCALE_VALUE_IS_NOT_CONSTANT) 138 | //Formula is F_CPU / (PrescaleFactor * 13) 139 | FrequencyDetectorControl.PeriodOfOneSampleMicros = ((1 << aADCPrescalerValue) * 13) / (F_CPU / 1000000L); 140 | FrequencyDetectorControl.PeriodOfOneReadSignalMillis = ((FrequencyDetectorControl.PeriodOfOneSampleMicros 141 | * (uint32_t) NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION) + (CLOCKS_FOR_READ_SIGNAL_NO_LOOP / (F_CPU / 1000000))) 142 | / 1000; 143 | FrequencyDetectorControl.FrequencyEquivalentOfOneSample = 1000000L / FrequencyDetectorControl.PeriodOfOneSampleMicros; 144 | 145 | # if defined(INFO) 146 | Serial.print(F("SamplePeriod=")); 147 | Serial.print(FrequencyDetectorControl.PeriodOfOneSampleMicros); 148 | Serial.print(F("us, samples per detection=" STR(NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION) ", SampleDuration=")); 149 | Serial.print(FrequencyDetectorControl.PeriodOfOneSampleMicros * (uint32_t) NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION); 150 | Serial.println(F("us")); 151 | # endif 152 | #else 153 | # if defined(INFO) 154 | Serial.print( 155 | F( 156 | "SamplePeriod=" STR(MICROS_PER_SAMPLE) "us, samples per detection=" STR(NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION) ", SampleDuration=")); 157 | Serial.print(MICROS_PER_SAMPLE * NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION); 158 | Serial.println(F("us")); 159 | # endif 160 | #endif 161 | } 162 | 163 | void setFrequencyDetectorMatchValues(uint16_t aFrequencyMin, uint16_t aFrequencyMax) { 164 | FrequencyDetectorControl.FrequencyMatchLow = aFrequencyMin; 165 | FrequencyDetectorControl.FrequencyMatchHigh = aFrequencyMax; 166 | } 167 | 168 | void setFrequencyDetectorDropoutCounts(uint8_t aMinMatchNODropoutCount, uint8_t aMaxMatchDropoutCount) { 169 | FrequencyDetectorControl.MinMatchNODropoutCount = aMinMatchNODropoutCount; 170 | FrequencyDetectorControl.MaxMatchDropoutCount = aMaxMatchDropoutCount; 171 | // set initial to maximum dropouts 172 | FrequencyDetectorControl.MatchDropoutCount = aMinMatchNODropoutCount + aMaxMatchDropoutCount; 173 | } 174 | 175 | /* 176 | * Computes MinMatchNODropoutCount and MaxMatchDropoutCount. 177 | * If program size matters, use setFrequencyDetectorDropoutCounts() instead or set them directly. 178 | * @return true if values set, false if PeriodOfOneReadSignalMillis == 0 179 | */ 180 | bool setFrequencyDetectorDropoutTimes(uint16_t aMinMatchNODropoutMillis, uint16_t aMaxMatchDropoutMillis) { 181 | bool tRetval = false; 182 | #if defined(ADC_PRESCALE_VALUE_IS_NOT_CONSTANT) 183 | if (FrequencyDetectorControl.PeriodOfOneReadSignalMillis != 0) { 184 | FrequencyDetectorControl.MinMatchNODropoutCount = aMinMatchNODropoutMillis 185 | / FrequencyDetectorControl.PeriodOfOneReadSignalMillis; 186 | FrequencyDetectorControl.MaxMatchDropoutCount = aMaxMatchDropoutMillis / FrequencyDetectorControl.PeriodOfOneReadSignalMillis; 187 | // set initial to maximum dropouts 188 | FrequencyDetectorControl.MatchDropoutCount = FrequencyDetectorControl.MinMatchNODropoutCount 189 | + FrequencyDetectorControl.MaxMatchDropoutCount; 190 | 191 | tRetval = true; 192 | } else { 193 | #if defined(INFO) 194 | Serial.println(F("Error. Values not set! Must call setFrequencyDetectorReadingPrescaleValue() before!")); 195 | #endif 196 | } 197 | 198 | #else 199 | FrequencyDetectorControl.MinMatchNODropoutCount = aMinMatchNODropoutMillis 200 | / (((MICROS_PER_SAMPLE * NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION) 201 | + (CLOCKS_FOR_READ_SIGNAL_NO_LOOP / (F_CPU / 1000000))) / 1000); 202 | 203 | FrequencyDetectorControl.MaxMatchDropoutCount = aMaxMatchDropoutMillis 204 | / (((MICROS_PER_SAMPLE * NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION) 205 | + (CLOCKS_FOR_READ_SIGNAL_NO_LOOP / (F_CPU / 1000000))) / 1000); 206 | // set initial to maximum dropouts 207 | FrequencyDetectorControl.MatchDropoutCount = FrequencyDetectorControl.MinMatchNODropoutCount 208 | + FrequencyDetectorControl.MaxMatchDropoutCount; 209 | #endif 210 | #if defined(INFO) 211 | Serial.print(F("MinMatchNODropoutCount=")); 212 | Serial.print(FrequencyDetectorControl.MinMatchNODropoutCount); 213 | Serial.print(F(" MaxMatchDropoutCount=")); 214 | Serial.println(FrequencyDetectorControl.MaxMatchDropoutCount); 215 | #endif 216 | return tRetval; 217 | } 218 | 219 | /* 220 | * Initialize default values for dropout counts for frequency detector. 221 | */ 222 | void setFrequencyDropoutDefaults() { 223 | setFrequencyDetectorDropoutCounts(MIN_NO_DROPOUT_COUNT_BEFORE_ANY_MATCH_DEFAULT, 224 | MAX_DROPOUT_COUNT_BEFORE_NO_FILTERED_MATCH_DEFAULT); 225 | } 226 | 227 | /* 228 | * Initialize default values for high and low frequency and dropout counts for frequency detector. 229 | */ 230 | void setFrequencyDetectorControlDefaults() { 231 | setFrequencyDetectorMatchValues(FREQUENCY_MIN_DEFAULT, FREQUENCY_MAX_DEFAULT); 232 | setFrequencyDropoutDefaults(); 233 | } 234 | 235 | /* 236 | * Set channel, reference, sample rate and threshold for low signal detection. 237 | * Set reference to 5 volt for AC coupled signal. 238 | */ 239 | void setFrequencyDetectorReadingDefaults() { 240 | // for DC coupled signal 241 | setFrequencyDetectorReadingValues(ADC_CHANNEL_DEFAULT, DEFAULT, PRESCALE_VALUE_DEFAULT, RAW_VOLTAGE_MIN_DELTA_DEFAULT); 242 | // If you set reference to 1.1 volt for AC coupled signal you have the equivalence to an additional signal amplification of around 4 243 | //setFrequencyDetectorReadingValues(ADC_CHANNEL_DEFAULT, INTERNAL1V1, PRESCALE_VALUE_DEFAULT, RAW_VOLTAGE_MIN_DELTA_DEFAULT); 244 | } 245 | 246 | #if defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 247 | uint16_t sReadValueBuffer[SIGNAL_PLOTTER_BUFFER_SIZE]; 248 | #endif 249 | /* 250 | * ADC read routine reads NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION (1024/512) samples and computes: 251 | * - FrequencyDetectorControl.FrequencyRaw - Frequency of signal 252 | * or error value SIGNAL_STRENGTH_LOW if signal is too weak 253 | * 254 | * The values are NOT stored, only the trigger are detected and the 255 | * FrequencyDetectorControl.PeriodLength array is filled. 256 | * 257 | * Sets the next trigger levels for hysteresis of 1/8 peak to peak value: 258 | * - FrequencyDetectorControl.TriggerLevelLower - for next reading 259 | * - FrequencyDetectorControl.TriggerLevelUpper - for next reading 260 | * 261 | * Sets values for plausibility check: 262 | * - FrequencyDetectorControl.PeriodLength[] 263 | * - FrequencyDetectorControl.PeriodCount 264 | * - FrequencyDetectorControl.TriggerFirstPosition 265 | * - FrequencyDetectorControl.TriggerLastPosition 266 | * Triggering value for next reading, trigger hysteresis is 1/8 peak to peak value 267 | * 268 | * !!! Timer0 interrupt, which counts the milliseconds is disabled during reading and enabled afterwards!!! 269 | * The alternative of using disable interrupt is getting wrong results!!! 270 | * The value of millis() is adjusted manually after reading. 271 | * 272 | * MICROS_PER_BUFFER_READING + 625 clocks for each call of readSignal() 273 | * 274 | * @return the frequency as detected during the reading 275 | */ 276 | uint16_t readSignal() { 277 | Myword tUValue; 278 | 279 | disableMillisInterrupt(); // disable Timer0 (millis()) overflow interrupt 280 | 281 | // ADCSRB = 0; // free running mode - not required, since it is default 282 | ADCSRA = ((1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADIF) | FrequencyDetectorControl.ADCPrescalerValue); 283 | 284 | // Do initialization while first conversion is running 285 | bool tTriggerSearchStart = true; 286 | bool tSignalFirstTriggerFound = false; 287 | uint16_t tPeriodCountPosition = 0; 288 | 289 | // Initialize max and min 290 | uint16_t tValueMax = 0; 291 | uint16_t tValueMin = 1024; 292 | uint32_t tSumOfSampleValues = 0; 293 | 294 | uint8_t tPeriodCount = 0; // Next index in PeriodLength[] array to write -> number of valid entries in PeriodLength[] array 295 | // requires 30 bytes more program memory but speeds up loop by 9 cycles 296 | uint16_t tTriggerLevelLower = FrequencyDetectorControl.TriggerLevelLower; 297 | uint16_t tTriggerLevel = FrequencyDetectorControl.TriggerLevel; 298 | 299 | /* 300 | * Read 512/1024 samples but only store periods 301 | */ 302 | for (unsigned int i = 0; i < NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION; i++) { 303 | // loop takes around 39 cycles at least and we have 52 us between each conversion for FREQUENCY_RANGE_DEFAULT 304 | /* 305 | * wait for free running conversion to finish. 306 | * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. 307 | */ 308 | #if defined(MEASURE_READ_SIGNAL_TIMING) 309 | digitalWriteFast(READ_SIGNAL_TIMING_OUTPUT_PIN, HIGH); 310 | #endif 311 | loop_until_bit_is_set(ADCSRA, ADIF); 312 | #if defined(MEASURE_READ_SIGNAL_TIMING) 313 | digitalWriteFast(READ_SIGNAL_TIMING_OUTPUT_PIN, LOW); 314 | #endif 315 | // Get value 316 | tUValue.byte.LowByte = ADCL; 317 | tUValue.byte.HighByte = ADCH; 318 | ADCSRA |= (1 << ADIF); // clear bit to recognize next conversion has finished with "loop_until_bit_is_set(ADCSRA, ADIF)". 319 | 320 | #if defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 321 | if (i < SIGNAL_PLOTTER_BUFFER_SIZE) { 322 | sReadValueBuffer[i] = tUValue.UWord; 323 | } 324 | #endif 325 | 326 | /* 327 | * Detect trigger 328 | */ 329 | if (tTriggerSearchStart) { 330 | // rising slope - wait for value below 1. threshold 331 | if (tUValue.UWord < tTriggerLevelLower) { 332 | tTriggerSearchStart = false; 333 | } 334 | } else { 335 | // rising slope - wait for value to rise above 2. threshold 336 | if (tUValue.UWord > tTriggerLevel) { 337 | /* 338 | * Trigger found but skip first (incomplete period) 339 | */ 340 | if (tSignalFirstTriggerFound) { 341 | if (tPeriodCount >= SIZE_OF_PERIOD_LENGTH_ARRAY_FOR_PLAUSI - 1) { 342 | // Frequency too high 343 | FrequencyDetectorControl.FrequencyRaw = SIGNAL_FREQUENCY_TOO_HIGH; 344 | } else { 345 | FrequencyDetectorControl.PeriodLength[tPeriodCount] = i - tPeriodCountPosition; 346 | } 347 | tPeriodCount++; // we can have a roll over if buffer > 512 and extreme high frequency 348 | tPeriodCountPosition = i; 349 | } else { 350 | // skip first (incomplete period) 351 | FrequencyDetectorControl.TriggerFirstPosition = i; 352 | tPeriodCountPosition = i; 353 | } 354 | tSignalFirstTriggerFound = true; 355 | tTriggerSearchStart = true; 356 | } 357 | } 358 | 359 | // Get average for external statistics 360 | tSumOfSampleValues += tUValue.UWord; 361 | 362 | // get max and min for automatic triggering 363 | if (tUValue.UWord > tValueMax) { 364 | tValueMax = tUValue.UWord; 365 | } else if (tUValue.UWord < tValueMin) { 366 | tValueMin = tUValue.UWord; 367 | } 368 | } 369 | 370 | /* 371 | * Enable millis timer (0|1) overflow interrupt and compensate for disabled timer, if still disabled. 372 | */ 373 | enableMillisInterrupt(MICROS_PER_BUFFER_READING / 1000); 374 | 375 | ADCSRA &= ~(1 << ADATE); // Disable ADC auto-triggering 376 | 377 | FrequencyDetectorControl.AverageLevel = tSumOfSampleValues / NUMBER_OF_SAMPLES_USED_FOR_FREQUENCY_DETECTION; 378 | FrequencyDetectorControl.TriggerLastPosition = tPeriodCountPosition; 379 | FrequencyDetectorControl.PeriodCount = tPeriodCount; 380 | 381 | uint16_t tDelta = tValueMax - tValueMin; 382 | FrequencyDetectorControl.SignalDelta = tDelta; 383 | // Take middle between min and max 384 | uint16_t tTriggerValue = tValueMin + (tDelta / 2); 385 | FrequencyDetectorControl.TriggerLevel = tTriggerValue; 386 | 387 | /* 388 | * check for signal strength 389 | */ 390 | if (tDelta < FrequencyDetectorControl.RawVoltageMinDelta) { 391 | // don't compute new TriggerHysteresis value because signal is too low!!!! 392 | FrequencyDetectorControl.FrequencyRaw = SIGNAL_STRENGTH_LOW; 393 | } else { 394 | 395 | // set hysteresis 396 | uint8_t TriggerHysteresis = tDelta / 8; 397 | FrequencyDetectorControl.TriggerLevelLower = tTriggerValue - TriggerHysteresis; 398 | 399 | if (tPeriodCount == 0) { 400 | FrequencyDetectorControl.FrequencyRaw = SIGNAL_NO_TRIGGER; 401 | } else if (tPeriodCount < MINIMUM_NUMBER_OF_TRIGGER_PER_BUFFER) { 402 | FrequencyDetectorControl.FrequencyRaw = SIGNAL_FREQUENCY_TOO_LOW; // Frequency too high is checked above 403 | } else { 404 | /* 405 | * Must use long intermediate value to avoid 16 Bit overflow 406 | * Average number of samples between triggers = (TriggerLastPosition - TriggerFirstPosition) / tPeriodCount 407 | * (FrequencyDetectorControl.FrequencyEquivalentOfOneSample / Average number of samples between triggers) => frequency for one period in number of samples 408 | */ 409 | #if defined(ADC_PRESCALE_VALUE_IS_NOT_CONSTANT) 410 | FrequencyDetectorControl.FrequencyRaw = ((long) tPeriodCount * FrequencyDetectorControl.FrequencyEquivalentOfOneSample) 411 | / (FrequencyDetectorControl.TriggerLastPosition - FrequencyDetectorControl.TriggerFirstPosition); 412 | #else 413 | FrequencyDetectorControl.FrequencyRaw = ((long) tPeriodCount * (1000000L / MICROS_PER_SAMPLE)) 414 | / (FrequencyDetectorControl.TriggerLastPosition - FrequencyDetectorControl.TriggerFirstPosition); 415 | #endif 416 | 417 | #if defined(DEBUG) 418 | Serial.print(F("Delta U=")); 419 | Serial.print(tDelta); 420 | Serial.print(F(" TriggerValue=")); 421 | Serial.print(tTriggerValue); 422 | Serial.print(F(" PeriodCount=")); 423 | Serial.print(FrequencyDetectorControl.PeriodCount); 424 | Serial.print(F(" Samples=")); 425 | Serial.print(FrequencyDetectorControl.TriggerLastPosition - FrequencyDetectorControl.TriggerFirstPosition); 426 | Serial.print(F(" Freq=")); 427 | Serial.println(FrequencyDetectorControl.FrequencyRaw); 428 | #endif 429 | } 430 | } 431 | return FrequencyDetectorControl.FrequencyRaw; 432 | } 433 | 434 | /** Overwrite FrequencyDetectorControl.FrequencyRaw with SIGNAL_DISTRIBUTION_PLAUSI_FAILED 3, if plausibility check fails. 435 | * Used plausibility rules are: 436 | * 1. A trigger must be detected in first and last 1/8 of samples 437 | * 2. Only 1/8 of the samples are allowed to be greater than 1.5 or less than 0.75 of the average period 438 | * @return the (changed) FrequencyDetectorControl.FrequencyRaw 439 | */ 440 | uint16_t doEqualDistributionPlausi() { 441 | // Only check if no error was detected before 442 | if (FrequencyDetectorControl.FrequencyRaw > SIGNAL_FREQUENCY_TOO_HIGH) { 443 | uint_fast8_t tPeriodCount = FrequencyDetectorControl.PeriodCount; // 64 for 512, 128 for 1024 samples 444 | /* 445 | * check if not more than 1/8 of periods are out of range - less than 0.75 or more than 1.5 446 | */ 447 | // average can be between 8 an 64 448 | uint8_t tAveragePeriod = (FrequencyDetectorControl.TriggerLastPosition - FrequencyDetectorControl.TriggerFirstPosition) 449 | / tPeriodCount; // period count is >= 8 450 | uint8_t tPeriodMax = tAveragePeriod + (tAveragePeriod / 2); 451 | uint8_t tPeriodMin = tAveragePeriod - (tAveragePeriod / 4); 452 | uint8_t tErrorCount = 0; 453 | #if defined(TRACE) 454 | Serial.print(tAveragePeriod); 455 | Serial.print(" "); 456 | printPeriodLengthArray(&Serial); 457 | #endif 458 | for (uint_fast8_t i = 0; i < tPeriodCount; ++i) { 459 | if (FrequencyDetectorControl.PeriodLength[i] > tPeriodMax || FrequencyDetectorControl.PeriodLength[i] < tPeriodMin) { 460 | tErrorCount++; 461 | } 462 | } 463 | if (tErrorCount > maximumAllowableCountOf(tPeriodCount)) { 464 | FrequencyDetectorControl.FrequencyRaw = SIGNAL_DISTRIBUTION_PLAUSI_FAILED; 465 | } 466 | #if defined(TRACE) 467 | Serial.print(tErrorCount); 468 | Serial.print(F(" #=")); 469 | Serial.print(tPeriodCount); 470 | Serial.print(F(" F=")); 471 | Serial.print(FrequencyDetectorControl.FrequencyRaw); 472 | Serial.println(F("Hz")); 473 | #endif 474 | } 475 | return FrequencyDetectorControl.FrequencyRaw; 476 | } 477 | 478 | /** 479 | * simple low-pass filter over 15 values 480 | */ 481 | uint16_t LowPassFilterWith16Values(int16_t aFilteredValue, int16_t aValue) { 482 | return (aFilteredValue + (((aValue - aFilteredValue) + (1 << 3)) >> 4)); // (tValue+8)/16 (+8 to avoid rounding errors) 483 | } 484 | 485 | /** 486 | * handles dropouts / no signal 487 | * dropout count is between 0 and MaxMatchDropoutCount and on latter the match will be reset. 488 | * determine direct match state - FrequencyDetectorControl.FrequencyMatch 489 | * computes low-pass filtered match state FrequencyMatchFiltered and frequency FrequencyMatchFiltered 490 | */ 491 | void computeDirectAndFilteredMatch(uint16_t aFrequency) { 492 | 493 | if (aFrequency <= SIGNAL_MAX_ERROR_CODE) { 494 | /* 495 | * dropout / no signal handling 496 | */ 497 | FrequencyDetectorControl.FrequencyMatchDirect = FREQUENCY_MATCH_INVALID; 498 | FrequencyDetectorControl.MatchDropoutCount++; 499 | if (FrequencyDetectorControl.MatchDropoutCount > FrequencyDetectorControl.MaxMatchDropoutCount) { 500 | FrequencyDetectorControl.FrequencyMatchFiltered = FREQUENCY_MATCH_INVALID; 501 | 502 | /* 503 | * clip value at MaxMatchDropoutCount + MinMatchNODropoutCount, so at least MinMatchNODropoutCount matches must happen 504 | * to set FrequencyMatchFiltered not to FREQUENCY_MATCH_INVALID 505 | */ 506 | if (FrequencyDetectorControl.MatchDropoutCount 507 | >= FrequencyDetectorControl.MaxMatchDropoutCount + FrequencyDetectorControl.MinMatchNODropoutCount) { 508 | FrequencyDetectorControl.MatchDropoutCount = FrequencyDetectorControl.MaxMatchDropoutCount 509 | + FrequencyDetectorControl.MinMatchNODropoutCount; 510 | } 511 | } 512 | } else { 513 | /* 514 | * Valid signal 515 | * decrement dropout count until 0 516 | */ 517 | if (FrequencyDetectorControl.MatchDropoutCount > 0) { 518 | FrequencyDetectorControl.MatchDropoutCount--; 519 | } 520 | 521 | /* 522 | * Determine direct match state and tNewFilterValue for low pass filter 523 | */ 524 | uint8_t tNewFilterValue; 525 | if (aFrequency < FrequencyDetectorControl.FrequencyMatchLow) { 526 | // Frequency too low 527 | FrequencyDetectorControl.FrequencyMatchDirect = FREQUENCY_MATCH_TOO_LOW; 528 | tNewFilterValue = FILTER_VALUE_MIN; 529 | } else if (aFrequency > FrequencyDetectorControl.FrequencyMatchHigh) { 530 | // Frequency too high 531 | FrequencyDetectorControl.FrequencyMatchDirect = FREQUENCY_MATCH_TOO_HIGH; 532 | tNewFilterValue = FILTER_VALUE_MAX; 533 | } else { 534 | // Frequency matches 535 | FrequencyDetectorControl.FrequencyMatchDirect = FREQUENCY_MATCH; 536 | tNewFilterValue = FILTER_VALUE_MATCH; 537 | } 538 | 539 | /* 540 | * Low pass filter for smooth transitions between the 3 match states (lower, match, higher) 541 | */ 542 | if (FrequencyDetectorControl.MatchDropoutCount == FrequencyDetectorControl.MaxMatchDropoutCount) { 543 | // init filter at first time when signal is valid 544 | FrequencyDetectorControl.MatchLowPassFiltered = tNewFilterValue; 545 | FrequencyDetectorControl.FrequencyFiltered = aFrequency; 546 | } else if (FrequencyDetectorControl.MatchDropoutCount < FrequencyDetectorControl.MaxMatchDropoutCount) { 547 | /* 548 | * Low pass filter the frequency and the match 549 | */ 550 | FrequencyDetectorControl.FrequencyFiltered = LowPassFilterWith16Values(FrequencyDetectorControl.FrequencyFiltered, 551 | aFrequency); 552 | FrequencyDetectorControl.MatchLowPassFiltered = LowPassFilterWith16Values(FrequencyDetectorControl.MatchLowPassFiltered, 553 | tNewFilterValue); 554 | } 555 | 556 | /* 557 | * determine new low pass filtered match state 558 | */ 559 | if (FrequencyDetectorControl.MatchDropoutCount > FrequencyDetectorControl.MaxMatchDropoutCount) { 560 | // too much dropouts 561 | FrequencyDetectorControl.FrequencyMatchFiltered = FREQUENCY_MATCH_INVALID; 562 | } else { 563 | if (FrequencyDetectorControl.MatchLowPassFiltered > FILTER_VALUE_MATCH_HIGHER_THRESHOLD) { 564 | FrequencyDetectorControl.FrequencyMatchFiltered = FREQUENCY_MATCH_TOO_HIGH; 565 | } else if (FrequencyDetectorControl.MatchLowPassFiltered < FILTER_VALUE_MATCH_LOWER_THRESHOLD) { 566 | FrequencyDetectorControl.FrequencyMatchFiltered = FREQUENCY_MATCH_TOO_LOW; 567 | } else { 568 | FrequencyDetectorControl.FrequencyMatchFiltered = FREQUENCY_MATCH; 569 | } 570 | } 571 | } 572 | } 573 | 574 | void printPeriodLengthArray(Print *aSerial) { 575 | /* 576 | * Print frequency or error code 577 | */ 578 | if (FrequencyDetectorControl.FrequencyRaw > SIGNAL_MAX_ERROR_CODE) { 579 | aSerial->print(F("Frequency=")); 580 | aSerial->print(FrequencyDetectorControl.FrequencyRaw); 581 | } else { 582 | aSerial->print(reinterpret_cast(ErrorStrings[FrequencyDetectorControl.FrequencyRaw])); 583 | } 584 | 585 | /* 586 | * Print array size and content 587 | */ 588 | aSerial->print(F(" PeriodLengthArray: size=")); 589 | aSerial->print(FrequencyDetectorControl.PeriodCount); 590 | aSerial->print(F(" of " STR(SIZE_OF_PERIOD_LENGTH_ARRAY_FOR_PLAUSI) " content=")); 591 | 592 | for (uint_fast8_t i = 0; i < FrequencyDetectorControl.PeriodCount; ++i) { 593 | aSerial->print(FrequencyDetectorControl.PeriodLength[i]); 594 | aSerial->print(","); 595 | } 596 | aSerial->println(); 597 | } 598 | 599 | #if defined(PRINT_INPUT_SIGNAL_TO_PLOTTER) 600 | /* 601 | * Prints input signal, upper and lower trigger levels and the value of FrequencyRaw (which also codes the errors) 602 | * as well as caption (for 1.x IDE plotter) 603 | * Requires sReadValueBuffer 604 | */ 605 | void printSignalValuesForArduinoPlotter(Print *aSerial) { 606 | printInputSignalValuesForArduinoPlotter(aSerial); 607 | } 608 | void printInputSignalValuesForArduinoPlotter(Print *aSerial) { 609 | aSerial->print(F("InputValue TriggerLevel=")); 610 | aSerial->print(FrequencyDetectorControl.TriggerLevel); 611 | aSerial->print(F(" TriggerLevelLower=")); 612 | aSerial->print(FrequencyDetectorControl.TriggerLevelLower); 613 | aSerial->print(F(" Frequency=")); 614 | aSerial->println(FrequencyDetectorControl.FrequencyRaw); 615 | 616 | 617 | aSerial->println(0); // To signal start of new buffer and to display the 0 line 618 | for (unsigned int i = 0; i < SIGNAL_PLOTTER_BUFFER_SIZE; ++i) { 619 | aSerial->print(sReadValueBuffer[i]); 620 | aSerial->print(' '); 621 | aSerial->print(FrequencyDetectorControl.TriggerLevel); 622 | aSerial->print(' '); 623 | aSerial->print(FrequencyDetectorControl.TriggerLevelLower); 624 | aSerial->print(' '); 625 | aSerial->println(FrequencyDetectorControl.FrequencyRaw); 626 | } 627 | aSerial->println(); 628 | } 629 | #endif 630 | 631 | void printTriggerValues(Print *aSerial) { 632 | aSerial->print(F("TriggerLower=")); 633 | aSerial->print(FrequencyDetectorControl.TriggerLevelLower); 634 | aSerial->print(" Upper="); 635 | aSerial->println(FrequencyDetectorControl.TriggerLevel); 636 | } 637 | 638 | void printFrequencyMatchValues(Print *aSerial) { 639 | aSerial->print(F("Frequency min=")); 640 | aSerial->print(FrequencyDetectorControl.FrequencyMatchLow); 641 | aSerial->print(F("Hz max=")); 642 | aSerial->print(FrequencyDetectorControl.FrequencyMatchHigh); 643 | aSerial->println(F("Hz")); 644 | } 645 | 646 | void printLegendForArduinoPlotter(Print *aSerial) { 647 | printResultLegendForArduinoPlotter(aSerial); 648 | } 649 | void printResultLegendForArduinoPlotter(Print *aSerial) { 650 | aSerial->println( 651 | F( 652 | "FrequencyMatchDirect*95 MatchDropoutCount*13 MatchLowPassFiltered*2 FrequencyMatchFiltered*100 FrequencyRaw FrequencyFiltered")); 653 | } 654 | 655 | void printDataForArduinoPlotter(Print *aSerial) { 656 | printResultDataForArduinoPlotter(aSerial); 657 | } 658 | void printResultDataForArduinoPlotter(Print *aSerial) { 659 | static uint8_t sConsecutiveErrorCount = 0; // Print only 10 errors, then stop 660 | 661 | if (sConsecutiveErrorCount >= 10) { 662 | // check for error condition 663 | if (FrequencyDetectorControl.FrequencyRaw <= SIGNAL_MAX_ERROR_CODE) { 664 | return; 665 | } else { 666 | // no error any more, start again with print 667 | sConsecutiveErrorCount = 0; 668 | printResultLegendForArduinoPlotter(aSerial); 669 | } 670 | } 671 | /* 672 | * Print values for Arduino Serial Plotter 673 | */ 674 | // FrequencyMatchDirect 0 to 3 675 | aSerial->print(FrequencyDetectorControl.FrequencyMatchDirect * 95); 676 | aSerial->print(' '); 677 | 678 | // MatchDropoutCount 0 to MaxMatchDropoutCount + MinMatchNODropoutCount 679 | aSerial->print(FrequencyDetectorControl.MatchDropoutCount * (97 / MAX_DROPOUT_COUNT_BEFORE_NO_FILTERED_MATCH_DEFAULT)); 680 | if (FrequencyDetectorControl.MatchDropoutCount 681 | == FrequencyDetectorControl.MaxMatchDropoutCount + FrequencyDetectorControl.MinMatchNODropoutCount) { 682 | sConsecutiveErrorCount++; 683 | } 684 | aSerial->print(' '); 685 | 686 | // MatchLowPassFiltered 0 to 200 687 | aSerial->print(FrequencyDetectorControl.MatchLowPassFiltered * 2); 688 | aSerial->print(' '); 689 | 690 | // FrequencyMatchFiltered 0 to 3 691 | aSerial->print(FrequencyDetectorControl.FrequencyMatchFiltered * 100); 692 | aSerial->print(' '); 693 | 694 | // print them last to leave the bright colors for the first values 695 | uint16_t tFrequencyForPlot = FrequencyDetectorControl.FrequencyRaw; 696 | // We can detect frequencies below 600 Hz, so this value may be not always significant but it is a first guess 697 | if (tFrequencyForPlot <= SIGNAL_MAX_ERROR_CODE) { 698 | tFrequencyForPlot = 600 + (tFrequencyForPlot * 20); 699 | } 700 | aSerial->print(tFrequencyForPlot); 701 | aSerial->print(' '); 702 | 703 | aSerial->print(FrequencyDetectorControl.FrequencyFiltered); 704 | aSerial->println(); 705 | } 706 | 707 | #endif // _FREQUENCY_DETECTOR_HPP 708 | -------------------------------------------------------------------------------- /src/MillisUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MillisUtils.cpp 3 | * 4 | * Unifies millis() timer handling for Digispark, AttinyCore and Arduino cores. 5 | * - Start, stop and modify milliseconds timer and value. 6 | * - Functions to compensate millis() timer value after long lasting ISR etc.. 7 | * - Blocking delayMilliseconds() function for use in noInterrupts context like ISR. 8 | * 9 | * Copyright (C) 2016-2020 Armin Joachimsmeyer 10 | * Email: armin.joachimsmeyer@gmail.com 11 | * 12 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 13 | * 14 | * ArduinoUtils is free software: you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation, either version 3 of the License, or 17 | * (at your option) any later version. 18 | * 19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | * See the GNU General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with this program. If not, see . 26 | * 27 | */ 28 | 29 | #include 30 | #include "MillisUtils.h" 31 | 32 | #if defined(__AVR__) 33 | 34 | #if !defined(cbi) 35 | #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 36 | #endif 37 | #if !defined(sbi) 38 | #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) 39 | #endif 40 | 41 | void delayAndCallFunctionEveryMillis(unsigned int aDelayMillis, void (*aDelayCallback)(void)) { 42 | uint32_t tStartMillis = millis(); 43 | do { 44 | if (aDelayCallback != nullptr) { 45 | aDelayCallback(); 46 | } 47 | delay(1); 48 | } while (millis() - tStartMillis <= aDelayMillis); 49 | } 50 | 51 | /* 52 | * 53 | */ 54 | void addToMillis(uint16_t aMillisToAdd) { 55 | timer0_millis += aMillisToAdd; 56 | } 57 | 58 | #if (defined(TIMSK) && defined(TOIE)) || (defined(TIMSK0) && defined(TOIE0)) 59 | /* 60 | * disable Timer0 (millis()) overflow interrupt 61 | * since the loop last exactly a multiple of 1024 micros, add a few statements between disabling and enabling 62 | */ 63 | void disableMillisInterrupt() { 64 | #if defined(TIMSK) && defined(TOIE) 65 | cbi(TIMSK, TOIE); 66 | #elif defined(TIMSK0) && defined(TOIE0) 67 | cbi(TIMSK0, TOIE0); // e.g. ATmega328 68 | #else 69 | #error Timer 0 overflow interrupt not disabled correctly 70 | #endif 71 | } 72 | 73 | /* 74 | * Enable timer 0 overflow interrupt and compensate for disabled timer, if still disabled. 75 | */ 76 | void enableMillisInterrupt(uint16_t aMillisToAddForCompensation) { 77 | #if defined(TIMSK) && defined(TOIE) 78 | if ((TIMSK & _BV(TOIE)) == 0) { 79 | // still disabled -> compensate 80 | timer0_millis += aMillisToAddForCompensation; 81 | } 82 | sbi(TIMSK, TOIE); 83 | #elif defined(TIMSK0) && defined(TOIE0) 84 | if ((TIMSK0 & _BV(TOIE0)) == 0) { 85 | // still disabled -> compensate 86 | timer0_millis += aMillisToAddForCompensation; 87 | } 88 | sbi(TIMSK0, TOIE0); // e.g. ATmega328 89 | #else 90 | #error Timer 0 overflow interrupt not enabled correctly 91 | #endif 92 | } 93 | 94 | #endif // (defined(TIMSK) && defined(TOIE)) || (defined(TIMSK0) && defined(TOIE0)) 95 | #endif // defined(__AVR__) 96 | 97 | #if ! defined(TEENSYDUINO) 98 | void delayMilliseconds(unsigned int aMillis) { 99 | for (unsigned int i = 0; i < aMillis; ++i) { 100 | delayMicroseconds(1000); 101 | } 102 | } 103 | 104 | /* 105 | * returns true if aMillis were gone after the last return of true 106 | * Can be used as a correct non blocking replacement for delay() 107 | * Simple version, which can only be used at one place in code because of static variable. 108 | */ 109 | bool areMillisGone(unsigned int aMillis) { 110 | static unsigned long sLastMillis; 111 | if (millis() - sLastMillis >= aMillis) { 112 | sLastMillis = millis(); 113 | return true; 114 | } 115 | return false; 116 | } 117 | 118 | bool areMillisGone(unsigned int aMillis, unsigned long *aLastMillisPtr) { 119 | if (millis() - *aLastMillisPtr >= aMillis) { 120 | *aLastMillisPtr = millis(); 121 | return true; 122 | } 123 | return false; 124 | } 125 | #endif // ! defined(TEENSYDUINO) 126 | 127 | /* 128 | * Function for speedTest 129 | * calling a function consisting of just __asm__ volatile ("nop"); gives 0 to 1 micro second 130 | * Use of Serial. makes it incompatible with BlueDisplay library. 131 | */ 132 | void speedTestWith1kCalls(Print *aSerial, void (*aFunctionUnderTest)(void)) { 133 | uint32_t tMillisStart = millis(); 134 | for (uint_fast8_t i = 0; i < 100; ++i) { 135 | // unroll 10 times 136 | aFunctionUnderTest(); 137 | aFunctionUnderTest(); 138 | aFunctionUnderTest(); 139 | aFunctionUnderTest(); 140 | aFunctionUnderTest(); 141 | aFunctionUnderTest(); 142 | aFunctionUnderTest(); 143 | aFunctionUnderTest(); 144 | aFunctionUnderTest(); 145 | aFunctionUnderTest(); 146 | } 147 | uint32_t tMillisRequired = millis() - tMillisStart; 148 | aSerial->print(F("Function call takes ")); 149 | if (tMillisRequired > 1000000) { 150 | aSerial->print(tMillisRequired / 1000); 151 | aSerial->print(","); 152 | aSerial->print((tMillisRequired % 1000) / 100); 153 | aSerial->print(F(" milli")); 154 | } else { 155 | aSerial->print(tMillisRequired); 156 | aSerial->print(F(" micro")); 157 | } 158 | aSerial->println(F(" seconds.")); 159 | } 160 | -------------------------------------------------------------------------------- /src/MillisUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MillisUtils.h 3 | * 4 | * Copyright (C) 2016-2020 Armin Joachimsmeyer 5 | * Email: armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * Arduino-Utils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #ifndef _MILLIS_UTILS_H 25 | #define _MILLIS_UTILS_H 26 | 27 | #include 28 | #if defined(__AVR__) 29 | 30 | //void speedTestWith1kCalls(void (*aFunctionUnderTest)(void)); 31 | 32 | /* 33 | * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. 34 | */ 35 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 36 | #define timer0_millis millis_timer_millis // The ATTinyCore + Digispark libraries use this variable name in wiring.c 37 | #endif 38 | 39 | #if defined(TIMSK0) && !defined(TIMSK) // some ATtinys and ATmega328 40 | #define TIMSK TIMSK0 41 | #endif 42 | 43 | #if defined(ARDUINO_AVR_DIGISPARK) 44 | // Digispark uses timer1 for millis() 45 | #define TOIE TOIE1 46 | #else 47 | #define TOIE TOIE0 // Assume timer 0 for millis() 48 | #endif 49 | 50 | extern volatile unsigned long timer0_millis; 51 | 52 | void delayAndCallFunctionEveryMillis(unsigned int aDelayMillis, void (*aDelayCallback)(void)); 53 | 54 | #if (defined(TIMSK) && defined(TOIE)) || (defined(TIMSK0) && defined(TOIE0)) 55 | void disableMillisInterrupt(); 56 | void enableMillisInterrupt(uint16_t aMillisToAddForCompensation = 0); 57 | #endif 58 | void addToMillis(uint16_t aMillisToAdd); 59 | 60 | void speedTestWith1kCalls(Print *aSerial, void (*aFunctionUnderTest)(void)); 61 | #endif // defined(__AVR__) 62 | 63 | void delayMilliseconds(unsigned int aMillis); 64 | bool areMillisGone(unsigned int aMillis); 65 | bool areMillisGone(unsigned int aMillis, unsigned long * aLastMillisPtr); 66 | 67 | 68 | #endif // _MILLIS_UTILS_H 69 | --------------------------------------------------------------------------------