├── .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 | [](https://www.gnu.org/licenses/gpl-3.0)
10 |
11 | [](https://github.com/ArminJo/Arduino-FrequencyDetector/releases/latest)
12 |
13 | [](https://github.com/ArminJo/Arduino-FrequencyDetector/commits/master)
14 |
15 | [](https://github.com/ArminJo/Arduino-FrequencyDetector/actions)
16 |
17 | 
18 |
19 |
20 | [](https://stand-with-ukraine.pp.ua)
21 |
22 | Available as [Arduino library "Arduino-FrequencyDetector"](https://www.arduinolibraries.info/libraries/frequency-detector).
23 |
24 | [](https://www.ardu-badge.com/Arduino-FrequencyDetector)
25 |
26 | [](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 | [](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 | 
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 | 
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 | 
120 | YouTube Demonstration of SimpleFrequencyDetector with MAX9812 Module
121 |
122 | [](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 |
--------------------------------------------------------------------------------