This commit is contained in:
2022-09-11 22:35:41 +08:00
commit 10274cbb4c
144 changed files with 36625 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
/*
Copyright 2021 Peter Harrison
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
------------------------------------------------------------------------------
BasicEncoder provides a class for reading a rotary encoder knob.
It is not suited to motor encoders.
The switch, if present, needs a deparate button library .
Decoding logic based on https://www.mikrocontroller.net/articles/Drehgeber
*/
#ifndef BASIC_ENCODER_H_
#define BASIC_ENCODER_H_
#include <Arduino.h>
uint8_t SREG;
class BasicEncoder
{
public:
BasicEncoder(int8_t pinA, int8_t pinB, uint8_t active_state = LOW, uint8_t steps = 4)
: m_pin_a(pinA), m_pin_b(pinB), m_pin_active(active_state), m_steps_per_count(steps) {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
m_previous_state - pin_state();
m_change = 0;
}
~BasicEncoder() {}
void begin() { reset(); }
int8_t pin_state() {
int8_t state_now = 0;
if (digitalRead(m_pin_a) == m_pin_active) {
state_now |= 2;
}
if (digitalRead(m_pin_b) == m_pin_active) {
state_now |= 1;
}
return state_now;
}
// to update the encoder changes
// call this method in a timmer interrupt for best performance
// it could also be called in the main loop
// this takes about 10-15us using digitalRead on Arduino Nano
void service() {
int8_t state_now = pin_state();
state_now ^= state_now >> 1; // two bit gray-to-binary
int8_t difference = m_previous_state - state_now;
// bit 1 has the direction, bit 0 is set if changeed
if (difference & 1) {
m_previous_state = state_now;
int delta = (difference & 2) - 1;
if (m_reversed) {
delta = -delta;
}
m_change += delta;
m_steps += delta;
}
}
/****************************************************************
// try this if there are unwanted transitions at the detents
// it is half resolution so counts are doubled
const int8_t decode_table[16] PROGMEM = {0, 0, -2, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, -2, 0, 0};
void flaky_encoder_service(void) {
// just get the bits - no gray-to-binary conversion needed
int8_t state_now = pin_state();
static int8_t encoder_state = state_now;
encoder_state = ((encoder_state << 2) | state_now) & 0x0f;
encoder_change += (int8_t)pgm_read_byte(&decode_table[encoder_state]);
}
****************************************************************/
// Read changes frequently enough that overflows cannot happen.
int8_t get_change() {
uint8_t sreg = SREG; // save the current interrupt enable flag
noInterrupts();
int8_t change = m_change;
// the switch statement can make better code because only optimised
// operations are used instead of generic division
switch (m_steps_per_count) {
case 4:
m_change %= 4;
change /= 4;
break;
case 2:
m_change %= 2;
change /= 2;
break;
default:
m_change = 0;
break;
}
SREG = sreg; // restore the previous interrupt enable flag state
return change;
}
int get_count() {
uint8_t sreg = SREG; // save the current interrupt enable flag
noInterrupts();
int count = m_steps / m_steps_per_count;
SREG = sreg; // restore the previous interrupt enable flag state
return count;
}
void reset() {
uint8_t sreg = SREG; // save the current interrupt enable flag
noInterrupts();
m_steps = 0;
m_change = 0;
SREG = sreg; // restore the previous interrupt enable flag state
}
void set_reverse() { m_reversed = true; }
void set_forward() { m_reversed = false; }
private:
int8_t m_pin_a = 0;
int8_t m_pin_b = 0;
uint8_t m_pin_active = LOW;
uint8_t m_steps_per_count = 4;
bool m_reversed = false;
volatile int m_change = 0;
int8_t m_previous_state = 0;
int m_steps = 0;
};
#endif // BASIC_ENCODER_H_
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+137
View File
@@ -0,0 +1,137 @@
# BasicEncoder
A simple library class for reading encoders as used in control knobs.
The complete code is in a single header file ```BasicEncoder.h``` so that you can just add the file to your project directly ratrher than instal a complete library.
This class does not handle the pushbutton usually found on these rotary encoders. For that it is better to use a separate pushbutton library.
Most of the logic in this code was derived from this page:
https://www.mikrocontroller.net/articles/Drehgeber
## Initialisation
The simplest constructor needs only the pin names for the encoder A and B lines.
#include <BasicEncoder.h>
BasicEncoder encoder(2,3);
This will create an encoder object using pins 2 and 3 for the two required input lines.
The pins will be initialised as inputs with pullups.
The encoder object will assume that the resting state of the inputs is high and that there are 4 signal steps per detent. That is typical for the most common type of inexpensive encoder knob.
You can optionally specify that the pins rest in the low state and that the number of steps per detent is different with constructors like
#include <basicEncoder.h>
BasicEncoder encoder1(2,3,HIGH,2) / pins rest low, 2 steps per detent
If the count is reversed then just swap the two pins. Alternatively, the direction of counting can be reversed or restored to normal in software with
encoder.set_reverse();
encoder.set_forward();
If the encoder will be polled (see below) then you are free to choose any two digital inputs pins.
If a pin change interrupt (see interrupts below)will be used then both channels should be in the same group so that they are serviced by the same interrupt. Pick pairs of pins from the following lists.
* PCINT0 - D8, D9, D10, D11, D12, D13,
* PCINT1 - A0, A1, A2, A3, A4, A5
* PCINT2 - D0, D1, D2, D3, D4, D5, D6, D7
Although it is possible to put more than one encoder on the same group the code may get a little messy so it is best to use different groups if more than pne encoder is to be connected.
With polling, it will not matter except that the interrupt service routine may start to get a little time-consuming.
## Update the counts
To make the encoder class react to any movement of the actual encoder device, it must regularly, and frequently examine the state of the input lines.
To have the encoder check the lines and respond to changes, you must call the ```service()``` method. e.g.
encoder.service();
## Polling
Polling is the name given to methods where the code is made to go and look for any changes. Many Arduino programs poll for changes by calling a function at the beginning of ```loop()```. So long as the rest of the code in ```loop()``` is short and executes quickly, this is likely to be adequate. The encoder should be polled as frequently as possible and certainly often enough to reliably detect changes.
## Polling with a timer interrupt
To be sure that the encoder is polled frequently enough, it is probably best to call the ```service()``` method from a timer interrupt running at seveeral hudreds of kHertz or more. If you use the ```TimerOne``` library, then the polling might be set up like this:
#include <Arduino.h>
#include <BasicEncoder.h>
#include <TimerOne.h>
BasicEncoder encoder(2, 3);
void timer_service() {
encoder.service();
}
void setup() {
Serial.begin(115200);
Timer1.initialize(1000);
Timer1.attachInterrupt(timer_service);
}
void loop() {
int encoder_change = encoder.get_change();
if (encoder_change) {
Serial.println(encoder.get_change());
}
}
In this example the TimerOne library is used to generate an interrupt every 1000 microseconds (1kHz). Changes are accumulated in the service routine and processed as needed by the main program loop. This works well even on switches with a lot of bounce. The entire function is compact and could be faster with hard coded pin reads or by using digitalReadFast if you wanted to modify the library source code.
## Reading changes
The encoder object tracks changes in the actual encoder and keeps a tally of the number of steps and the direction of rotation. There are two ways to get at this information:
#### ```get_change()```
By calling the ```get_change()``` method your program can receive the number of counts since the last call to the method as a signed integer. This is a destructive call in that the change count is reset to zero during the call. Thus you should assign the change to a local variable so that you can use the value later in the code. The example above shows this method. The default contructor assums that there are four steps per detent, or click, of the encoder knob so the number returned by ```get_change()``` is the number of clicks, not the number of signal changes. In that example the printed value will almost certainly be just +1 or -1 because the loop executes very quickly. Try adding a delay in ```loop()``` to see larger changes being reported.
#### ```get_count()```
The ```get_count()``` method will also return the number of clicks (not signal changes) recorded by the encoder object. This time however, the number returned will be the accumulated count since the last time the ```reset()``` method was called. The value is not cleared when read.
### Motor encoders
Polled encoders are not likely to work well for motor applications. If you specifically want motor applications there are many ways to optimise the code for better performance at high frequencies. Such optimisations may rely on the encoder channels being clean. That is, the pulses switch reliably without any contact bounce. The technique used in this code is reliable even with low quality encoders that have considerable contact bounce.
## Bouncing encoders
On the subject of contact bounce, the code assumes that the detents of a typical control knob coincide with stable states of the control signals. It is possible that some controls have detents that coincide with transitions and there may be some jitter in the output even when the knob is at rest. Encoder controls without detents may come to rest at such a position by chance.
If this is a problem in your application the article linked in the comments provides an alternative solution that uses a lookup table to decode the state transitions.
https://www.mikrocontroller.net/articles/Drehgeber
## Pin Change Interrupts
If you like, this service routine could be called from the pin change interrupt.
See the examples for code setup to use pin change interrupt operation.
See also:
https://playground.arduino.cc/Main/PinChangeInterrupt/
https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
## Multiple Encoders
The library should work with multiple encoder instances. There is an example `two-encoders` that demonstrates this using a timer interrupt for polling. Pin-change interrupt implementations may also be fine if the encoders are in different groups though that has not yet been tested.
## A note about interrupts
In a few places, the code needs to disable interrupts to ensure that data values are not corrupted. Immediately before a call to `noInterrupts()`, the current value of the status register, `SREG`, is saved into a local variable. Once the critical code section is complete, the saved state of the interrupt enable flag is restored by simply copying the saved value of the status register back. The other flags are not critical in this context.
This method is used rather than simply enabling interrupts because interrupts may already have been disabled at the time the function is called and to re-enable interrupts when they should not be enabled might cause hard to track bugs in the user code.
@@ -0,0 +1,48 @@
/*
Copyright 2021 Peter Harrison - Helicron
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This example creates a single encoder instance that is updated by
running the service method directly in the main program loop. This
must be called frequently enough to avoid missing any pin changes. That
means several hundred times a second in many cases. If a change is detected
in the main loop, the current count is sent to the serial port.
You can add a delay into the loop in this eample to see just how often you
need to call the service method and still get reliable counts.
*/
#include <Arduino.h>
#include <BasicEncoder.h>
const int8_t pinA = 2;
const int8_t pinB = 3;
BasicEncoder encoder(pinA, pinB);
void setup() {
Serial.begin(115200);
Serial.println(F("Polling in loop()"));
}
void loop() {
encoder.service();
int encoder_change = encoder.get_change();
if (encoder_change) {
Serial.println(encoder.get_count());
}
// Even a short delay here will affect performance.
// Uncomment and change the delay to see what happens.
//delay(10);
}
@@ -0,0 +1,50 @@
/*
Copyright 2021 Peter Harrison - Helicron
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This example creates two independent encoders and samples them using a
timer interrupt. If either changes, the current count for both is sent to
the serial port.
*/
#include <Arduino.h>
#include <BasicEncoder.h>
#include <TimerOne.h>
BasicEncoder encoderA(2, 3);
BasicEncoder encoderB(10, 11);
/****************************************************************/
void timer_service() {
encoderA.service();
encoderB.service();
}
void setup() {
Serial.begin(115200);
Timer1.initialize(1000);
Timer1.attachInterrupt(timer_service);
}
void loop() {
int encoder_a_change = encoderA.get_change();
int encoder_b_change = encoderB.get_change();
if (encoder_a_change || encoder_b_change) {
Serial.print(encoderA.get_count());
Serial.print(' ');
Serial.print(encoderB.get_count());
Serial.println();
}
}
@@ -0,0 +1,65 @@
/*
Copyright 2021 Peter Harrison - Helicron
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This example creates a single encoder instance that is updated by
pin-change interrupts associated with the two pins. If a change is detected
in the main loop, the current count is sent to the serial port.
Note that if you enable pin change interrupts for other pins in the same
group, you may cause conflicts.
*/
#include <Arduino.h>
#include <BasicEncoder.h>
const int8_t pinA = 2;
const int8_t pinB = 3;
BasicEncoder encoder(pinA, pinB);
void pciSetup(byte pin) // Setup pin change interupt on pin
{
*digitalPinToPCMSK(pin) |= bit(digitalPinToPCMSKbit(pin)); // enable pin
PCIFR |= bit(digitalPinToPCICRbit(pin)); // clear outstanding interrupt
PCICR |= bit(digitalPinToPCICRbit(pin)); // enable interrupt for group
}
void setup_encoders(int a, int b) {
uint8_t old_sreg = SREG; // save the current interrupt enable flag
noInterrupts();
pciSetup(a);
pciSetup(b);
encoder.reset();
SREG = old_sreg; // restore the previous interrupt enable flag state
}
ISR(PCINT2_vect) // pin change interrupt for D0 to D7
{
encoder.service();
}
void setup() {
Serial.begin(115200);
Serial.println("Interrupts");
setup_encoders(pinA,pinB);
encoder.set_reverse();
}
void loop() {
int encoder_change = encoder.get_change();
if (encoder_change) {
Serial.println(encoder.get_count());
}
}
@@ -0,0 +1,49 @@
/*
Copyright 2021 Peter Harrison - Helicron
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This example creates a single encoder instance that is updated by
a timer interrupt running at 1kHz. If a change is detected
in the main loop, the current count is sent to the serial port.
Be sure that the timer interrupt is frequent enough that it cannot miss
a change in pin state.
*/
#include <Arduino.h>
#include <BasicEncoder.h>
#include <TimerOne.h>
BasicEncoder encoder(2, 3);
/****************************************************************/
void timer_service() {
encoder.service();
}
void setup() {
Serial.begin(115200);
Timer1.initialize(1000);
Timer1.attachInterrupt(timer_service);
encoder.set_reverse();
}
void loop() {
int encoder_change = encoder.get_change();
if (encoder_change) {
Serial.println(encoder.get_count());
}
}
+4
View File
@@ -0,0 +1,4 @@
BASICENCODER KEYWORD1
Encoder KEYWORD2
Rotary KEYWORD2
+22
View File
@@ -0,0 +1,22 @@
{
"name": "BasicEncoder",
"version": "1.1.3",
"description": "BasicEncoder counts pulses from one or more simple rotary encoder control knobs.",
"keywords": "encoder, rotary encoder, quadrature",
"repository":
{
"type": "git",
"url": "https://github.com/micromouseonline/BasicEncoder.git"
},
"authors":
[
{
"name": "Peter Harrison",
"url": "http://micromouseonline.com",
"maintainer": true
}
],
"license": "Apache-2.0",
"frameworks": ["arduino"],
"platforms": ["atmelavr"]
}
+9
View File
@@ -0,0 +1,9 @@
name=BasicEncoder
version=1.1.3
author=Peter Harrison
maintainer=Peter Harrison
sentence=BasicEncoder counts pulses from one or more simple rotary encoder control knobs.
paragraph=Could also be used for low freuency odometry encoders but not suited to motor encoders.
category=Signal Input/Output
url=https://github.com/micromouseonline/BasicEncoder
architectures=*