旋转或编码器是一个角度测量装置. 他用作精确测量电机的旋转角度或者用来控制控制轮子(可以无限旋转,而电位器只能旋转到特定位置)。其中有一些还安装了一个可以在轴上按的按钮,就像音乐播放器的控制按钮。Some of them are also equipped with a pushbutton when you press on the axis (like the ones used for navigation on many music controllers). 它们的精度多种多样,有每圈16步到1024步的各种,价格也从2到200欧元不等。
我写了一个小例子去读旋转编码器,并且使将读数通过RS232显示。我们很容易实现当编码器每走一步更新一下计数,并且将它通过串口显示在电脑上(通过串口监视器)。这个程序在ALPS STEC12E08编码器(每圈有24步)上运行良好。但是我认为当它使用在一个有更高精度的编码器上时有可能就会失效或者当电机旋转很快,或者你拓展这个程序以适应多个编码器。请先试试他吧。
我在Arduino distribution(AVRLib的一部分)的encoder.h中学会了怎样操作编码器。谢谢作者:Pascal Stang,感谢他对每一个函数友好而详细的解释。如下:
/* Read Quadrature Encoder
* Connect Encoder to Pins encoder0PinA, encoder0PinB, and +5V.
*
* Sketch by max wolf / www.meso.net
* v. 0.1 - very basic functions - mw 20061220
*
*/
int val;
int encoder0PinA = 3;
int encoder0PinB = 4;
int encoder0Pos = 0;
int encoder0PinALast = LOW;
int n = LOW;
void setup() {
pinMode (encoder0PinA,INPUT);
pinMode (encoder0PinB,INPUT);
Serial.begin (9600);
}
void loop() {
n = digitalRead(encoder0PinA);
if ((encoder0PinALast == LOW) && (n == HIGH)) {//上升沿
if (digitalRead(encoder0PinB) == LOW) {
encoder0Pos--;
} else {
encoder0Pos++;
}
Serial.print (encoder0Pos);
Serial.print ("/");
}
encoder0PinALast = n;
}
要注意的几点:
encoder0Pos
会一直记数,那也就意味着如果电机一直向一个方向进行旋转,那么串口消息会变的很长(最多6个字符),这样的话就会画更多的时间去转换。你需要保证当encoder0Pos
溢出的时候,在你的PC端不会发生bugs-如果它的值大于INT的最大值(32,767)时,它会突变为-32,768!反之亦然。改进建议: 仅当上位机需要读数的时候,将计数相加,即只计数发送周期之间的数据。当然,如果你在loop()中添加更多的代码,或者使用更高精度的编码器,就有可能丢失某一步(少计数)。更好的解决办法是使用中断(当检测到信号的突变时)。我上面提到库文件就是这么去做的,但是现在(2006-12) 无法再Arduino环境下编译,或者我不知道怎么去做…… 。
我对于编码器的时序的原理不怎么确定,但是还是将这些内容添加到这里吧. Paul Badger
下图,是 编码器A & B两通道的时序图。
下面的描述会更好的解释编码器是怎么运行的。当代码检测到A通道一个上升沿的时候,他会接着去检查B通道是高电平或者是低电平,因为方向不同,接着它会将计数增加或者减少。为了检测到波形,编码器必须旋转。
上面代码的优势是,他只检测了关于编码器的一种可能性(共4种分别是检测A(上升沿,下降沿)(B+,B-),检测B(上升沿,下降沿)(A+,A-))。为了方便解释,不管红色还是绿色虚线标注的变换,都是根据编码器的旋转方向变化而变化的。
下面是使用中断的代码. 当Arduino检测到A通道有变化(上升沿或下降沿), 它立刻跳转到 “doEncoder” 函数, 中断函数会在上升沿和下降沿都会被调用,所以每个一步都会被计两次。我不想使用另一个中断去检查B通道的2个变换 ( 上图紫色和青色线标注处),但是即使调用了,也不会很麻烦.
使用中断去操作旋转编码器比较不错,因为中断响应时间很快,因为它不用操作很多任务。
I used the encoder as a “mode selector” on a synthesizer made solely from an Arduino chip(译者
作者可能是说他将这个旋转编码器用作一个模式选择用途,就是如同按钮形式的东西). 这是一个比较随意的程序,因为用户对单片机丢失一些脉冲并不在意. 中断方法比较重要的应用是在伺服电机或者机器人的轮子上,在这些应用中,单片机不能丢失任何一个脉冲,不然运动的精度就无法保证了。
要注意的另一点是: I used the Arduino’s pullup resistors to “steer” the inputs high when they were not engaged by the encoder. Hence the encoder common pin is connected to ground. (译者
作者使用Arduino内部上拉电阻使输入端的常态是高电平,因此编码器的公共端是连接到地上)上面的程序没有提到的一点是:输入端需要串联上拉电阻(10K欧就好),因为编码器的公共端连接是+5V.
/* read a rotary encoder with interrupts
Encoder hooked up with common to GROUND,
encoder0PinA to pin 2, encoder0PinB to pin 4 (or pin 3 see below)
it doesn't matter which encoder pin you use for A or B
uses Arduino pullups on A & B channel outputs
turning on the pullups saves having to hook up resistors
to the A & B channel outputs
*/
#define encoder0PinA 2
#define encoder0PinB 4
volatile unsigned int encoder0Pos = 0;
void setup() {
pinMode(encoder0PinA, INPUT);
digitalWrite(encoder0PinA, HIGH); // turn on pullup resistor
pinMode(encoder0PinB, INPUT);
digitalWrite(encoder0PinB, HIGH); // turn on pullup resistor
attachInterrupt(0, doEncoder, CHANGE); // encoder pin on interrupt 0 - pin 2
Serial.begin (9600);
Serial.println("start"); // a personal quirk
}
void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}
void doEncoder() {
/* If pinA and pinB are both high or both low, it is spinning
* forward. If they're different, it's going backward.
*
* For more information on speeding up this process, see
* [Reference/PortManipulation], specifically the PIND register.
*/
if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
encoder0Pos++;
} else {
encoder0Pos--;
}
Serial.println (encoder0Pos, DEC);
}
/* See this expanded function to get a better understanding of the
* meanings of the four possible (pinA, pinB) value pairs:
*/
void doEncoder_Expanded(){
if (digitalRead(encoder0PinA) == HIGH) { // found a low-to-high on channel A
if (digitalRead(encoder0PinB) == LOW) { // check channel B to see which way
// encoder is turning
encoder0Pos = encoder0Pos - 1; // CCW
}
else {
encoder0Pos = encoder0Pos + 1; // CW
}
}
else // found a high-to-low on channel A
{
if (digitalRead(encoder0PinB) == LOW) { // check channel B to see which way
// encoder is turning
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
Serial.println (encoder0Pos, DEC); // debug - remember to comment out
// before final program run
// you don't want serial slowing down your program if not needed
}
/* to read the other two transitions - just use another attachInterrupt()
in the setup and duplicate the doEncoder function into say,
doEncoderA and doEncoderB.
You also need to move the other encoder wire over to pin 3 (interrupt 1).
*/
BY:dskv 注意!!!
在中断程序中使用Serial.Print要特别注意,大多数情况下它会失败, 但是有时它会成功, 这是个程序比较糟糕的bugs. 下面连接是解释文档:
“https://groups.google.com/a/arduino.cc/forum/#!topic/developers/HKzEcN6gikM”
“http://forum.arduino.cc/index.php?topic=94459.0”
“http://forum.jeelabs.net/node/1188.html”
读编码器,使用2个中断 pin 2 & pin3
注意:下面的程序使用两个中断来使用编码器的最高精度(就像步进电机的细分. 上文的程序使用1个中断. 它仅仅读取一半精度,通过检测EncoderPin A的位置,但是它省一个中断程序。
#define encoder0PinA 2
#define encoder0PinB 3
volatile unsigned int encoder0Pos = 0;
void setup() {
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
Serial.begin (9600);
}
void loop(){ //Do stuff here }
void doEncoderA(){
// look for a low-to-high on channel A
if (digitalRead(encoder0PinA) == HIGH) {
// check channel B to see which way encoder is turning
if (digitalRead(encoder0PinB) == LOW) {
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
else // must be a high-to-low edge on channel A
{
// check channel B to see which way encoder is turning
if (digitalRead(encoder0PinB) == HIGH) {
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
Serial.println (encoder0Pos, DEC);
// use for debugging - remember to comment out
}
void doEncoderB(){
// look for a low-to-high on channel B
if (digitalRead(encoder0PinB) == HIGH) {
// check channel A to see which way encoder is turning
if (digitalRead(encoder0PinA) == HIGH) {
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
// Look for a high-to-low on channel B
else {
// check channel B to see which way encoder is turning
if (digitalRead(encoder0PinA) == LOW) {
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
}
类包装 by mikkoh [01/2010]
将上面的例子包装 (一个中断的例程) 进类并且减少一点doEncoder 函数代码的体积(希望代码对你们还是可读的). 在这个类的文档中一个例子.
#ifndef __ENCODER_H__
#define __ENCODER_H__
#include "WProgram.h"
class Encoder {
/*
wraps encoder setup and update functions in a class
!!! NOTE : user must call the encoders update method from an
interrupt function himself! i.e. user must attach an interrupt to the
encoder pin A and call the encoder update method from within the
interrupt
uses Arduino pullups on A & B channel outputs
turning on the pullups saves having to hook up resistors
to the A & B channel outputs
// ------------------------------------------------------------------------------------------------
// Example usage :
// ------------------------------------------------------------------------------------------------
#include "Encoder.h"
Encoder encoder(2, 4);
void setup() {
attachInterrupt(0, doEncoder, CHANGE);
Serial.begin (115200);
Serial.println("start");
}
void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}
void doEncoder(){
encoder.update();
Serial.println( encoder.getPosition() );
}
// ------------------------------------------------------------------------------------------------
// Example usage end
// ------------------------------------------------------------------------------------------------
*/
public:
// constructor : sets pins as inputs and turns on pullup resistors
Encoder( int8_t PinA, int8_t PinB) : pin_a ( PinA), pin_b( PinB ) {
// set pin a and b to be input
pinMode(pin_a, INPUT);
pinMode(pin_b, INPUT);
// and turn on pullup resistors
digitalWrite(pin_a, HIGH);
digitalWrite(pin_b, HIGH);
};
// call this from your interrupt function
void update () {
if (digitalRead(pin_a)) digitalRead(pin_b) ? position++ : position--;
else digitalRead(pin_b) ? position-- : position++;
};
// returns current position
long int getPosition () { return position; };
// set the position value
void setPosition ( const long int p) { position = p; };
private:
long int position;
int8_t pin_a;
int8_t pin_b;
};
#endif // __ENCODER_H__
(译者
上面的代码使用long int
类型,关于Arduino 数据类型Arduino 数据类型)
使用两个外部中断,仅仅计算一个方向的脉冲.
注意: 尽管代码感觉比较有效率, 但是由于使用了digitalRead()这个库函数,根据 Pin I/O performance(译者地址
Arduino 端口性能讲解)上说的它会比直接读端口要慢50倍。
最优效率的旋转编码器计数 by m3tr0g33k
Paul Badger 的工作和原博客很有启发性并且很有用,在看代码之前,请理解他们是怎么说的(希望你能看明白)。
My project is a data loger where three analogue inputs are sampled each time a rotary encoder pulse steps clockwise. On an Arduino, time is of the essence to get this data sampled and saved somewhere (I have not included the ‘saving somewhere’ part of this project yet.) (译者
大体意思是当旋转编码器旋转一周将有3个输入量出现,但是在Arduino中处理器资源是有限的)为了节约一些处理器资源,我对终端系统做了稍微的修改,去维持在中断循环外的一对布尔声明。
我的想法是改变一个A或B的布尔变量,当在A或B口收到一个有效的跳变沿. 当你因A端口中断,并且是有效的,你将A_set=true. 然后检测B_set是不是false.如果是, 那么 A 比 B 的相位靠前,这表明是顺时针 (计数++)。同理,当你收到有效的因端口B中断, 你会改变set B_set=true. 然后你检查 A_set 是否是 false. 如果是, 那么 B 比A的相位靠前 , 那么意味着是逆时针旋转 (计数–)。
此代码与以前的代码不同的地方是:当A或B发生中断的时候(中断标志是CHANGE),检测A和B口的状态,如果A或B是0则设置A_set或B_set为false,其他的工作就不需要了,减少了中断的占用时间(因为以前在一次中断需要读取A和B口的状态,而这个仅仅需要读取一个口的状态)。
不管如何,在代码中有足够的解释,代码如下:
#define encoder0PinA 2
#define encoder0PinB 3
volatile unsigned int encoder0Pos = 0;
unsigned int tmp_Pos = 1;
unsigned int valx;
unsigned int valy;
unsigned int valz;
boolean A_set;
boolean B_set;
void setup() {
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
Serial.begin (9600);
}
void loop(){
//Check each second for change in position
if (tmp_Pos != encoder0Pos) {
Serial.print("Index:"); Serial.print(encoder0Pos, DEC); Serial.print(", Values: ");
Serial.print(valx, DEC); Serial.print(", ");
Serial.print(valy, DEC); Serial.print(", ");
Serial.print(valz, DEC); Serial.println();
tmp_Pos = encoder0Pos;
}
delay(1000);
}
// Interrupt on A changing state
void doEncoderA(){
// Low to High transition?
if (digitalRead(encoder0PinA) == HIGH) {
A_set = true;
if (!B_set) {
encoder0Pos = encoder0Pos + 1;
valx=analogRead(0);
valy=analogRead(1);
valz=analogRead(2);
}
}
// High-to-low transition?
if (digitalRead(encoder0PinA) == LOW) {
A_set = false;
}
}
// Interrupt on B changing state
void doEncoderB(){
// Low-to-high transition?
if (digitalRead(encoder0PinB) == HIGH) {
B_set = true;
if (!A_set) {
encoder0Pos = encoder0Pos - 1;
}
}
// High-to-low transition?
if (digitalRead(encoder0PinB) == LOW) {
B_set = false;
}
}
环绕中断代码的其他部分仅仅是为了展示它是怎么工作的。就像我说的,我仅仅希望演示电机正传(在我的电车例子中就是向前走)。当检测到电机是反转的时候,我仅仅是更新那个计数变量。在 loop{} 循环体中,我们每一秒显示一次当前编码器的位置和相应引脚读数数据。但是仅当编码器位置发生改变的时候才去更新,如果位置没有发生变化,则就不更新。你可以试试一秒钟你能转你的编码器多少圈。我的记录是300步或者是1.5圈每秒。
这个代码有一个逻辑问题,如果你频繁改变方向,那么你也许会想知道在每步的中间位置是否改变了方向,而且在那个位置,你的计数参数是不发生改变的。这就是半步滞后现象。当然,在绝大多数情况下,这并不能观察得到或者是重要的,但是思考一下这个问题是比较重要的。
希望这个运行速度的提升能够帮助一些人!m3tr0g33k
中断例子(编码器中断主线程),使用两个中断,简化上面代码的中断响应函数
简化中断程序
按照A_set == B_set
来判断滞后或提前,你可以简化相当一部分中断程序的代码。
中断程序变为:
// Interrupt on A changing state
void doEncoderA(){
// Test transition
A_set = digitalRead(encoderPinA) == HIGH;
// and adjust counter + if A leads B
encoderPos += (A_set != B_set) ? +1 : -1;
}
// Interrupt on B changing state
void doEncoderB(){
// Test transition
B_set = digitalRead(encoderPinB) == HIGH;
// and adjust counter + if B follows A
encoderPos += (A_set == B_set) ? +1 : -1;
}
其基本原理为:当当前的引脚的改变的状态和另一个引脚的状态一致,那么,这个引脚就比另一个引脚滞后。如果状态不一致,那么当前引脚就超前。
最终结果:两行代码便构成了中断程序。
全部代码为:
enum PinAssignments {
encoderPinA = 2,
encoderPinB = 3,
clearButton = 8
};
volatile unsigned int encoderPos = 0;
unsigned int lastReportedPos = 1;
boolean A_set = false;
boolean B_set = false;
void setup() {
pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);
pinMode(clearButton, INPUT);
digitalWrite(encoderPinA, HIGH); // turn on pullup resistor
digitalWrite(encoderPinB, HIGH); // turn on pullup resistor
digitalWrite(clearButton, HIGH);
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
Serial.begin(9600);
}
void loop(){
if (lastReportedPos != encoderPos) {
Serial.print("Index:");
Serial.print(encoderPos, DEC);
Serial.println();
lastReportedPos = encoderPos;
}
if (digitalRead(clearButton) == LOW) {
encoderPos = 0;
}
}
// Interrupt on A changing state
void doEncoderA(){
// Test transition
A_set = digitalRead(encoderPinA) == HIGH;
// and adjust counter + if A leads B
encoderPos += (A_set != B_set) ? +1 : -1;
}
// Interrupt on B changing state
void doEncoderB(){
// Test transition
B_set = digitalRead(encoderPinB) == HIGH;
// and adjust counter + if B follows A
encoderPos += (A_set == B_set) ? +1 : -1;
}
其他关于编码器的链接:
a good explanation of grey codes and absolute encoders
this code did work better for me than most others, with good explanation
更快读编码器:仅使用中断
我也在应用中使用到了读旋转编码器,经过多次尝试,我很高兴高速大家有一个新的方式处理他们。根据前任的建议,我在AMT旋转编码器上使用了它并且运行真的很好。不幸的是,其他的方法失败了,因为计数太快了。为了避免读Arduino引脚的,我想也许有更快的方法,通过仅仅使用中断。下面是它的代码:
//PIN's definition
#define encoder0PinA 2
#define encoder0PinB 3
volatile int encoder0Pos = 0;
volatile boolean PastA = 0;
volatile boolean PastB = 0;
void setup()
{
pinMode(encoder0PinA, INPUT);
//turn on pullup resistor
//digitalWrite(encoder0PinA, HIGH); //ONLY FOR SOME ENCODER(MAGNETIC)!!!!
pinMode(encoder0PinB, INPUT);
//turn on pullup resistor
//digitalWrite(encoder0PinB, HIGH); //ONLY FOR SOME ENCODER(MAGNETIC)!!!!
PastA = (boolean)digitalRead(encoder0PinA); //initial value of channel A;
PastB = (boolean)digitalRead(encoder0PinB); //and channel B
//To speed up even more, you may define manually the ISRs
// encoder A channel on interrupt 0 (arduino's pin 2)
attachInterrupt(0, doEncoderA, RISING);
// encoder B channel pin on interrupt 1 (arduino's pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
}
void loop()
{
//your staff....ENJOY! :D
}
//you may easily modify the code get quadrature..
//..but be sure this whouldn't let Arduino back!
void doEncoderA()
{
PastB ? encoder0Pos--: encoder0Pos++;
}
void doEncoderB()
{
PastB = !PastB;
}
希望能帮助到大家! by carnevaledaniele [04/2010]
loop()
中使用,没有示例代码下面是完整的旋转编码器操作库代码(译者
下面的代码没有使用中断,将占用较多的处理器资源):
#include
#include "HardwareSerial.h"
// 12 Step Rotary Encoder with Click //
// http://www.sparkfun.com/products/9117 //
#define EncoderPinA 20 // Rotary Encoder Left Pin //
#define EncoderPinB 19 // Rotary Encoder Right Pin //
#define EncoderPinP 21 // Rotary Encoder Click //
// ======================================================================================= //
class Encoder
{
public:
Encoder()
{
pinMode(EncoderPinA, INPUT);
digitalWrite(EncoderPinA, HIGH);
pinMode(EncoderPinB, INPUT);
digitalWrite(EncoderPinB, HIGH);
pinMode(EncoderPinP, INPUT);
digitalWrite(EncoderPinP, HIGH);
Position = 0;
Position2 = 0;
Max = 127;
Min = 0;
clickMultiply = 10;
}
void Tick(void)
{
Position2 = (digitalRead(EncoderPinB) * 2) + digitalRead(EncoderPinA);;
if (Position2 != Position)
{
isFwd = ((Position == 0) && (Position2 == 1)) || ((Position == 1) && (Position2 == 3)) ||
((Position == 3) && (Position2 == 2)) || ((Position == 2) && (Position2 == 0));
if (!digitalRead(EncoderPinP)) { if (isFwd) Pos += clickMultiply; else Pos -= clickMultiply; }
else { if (isFwd) Pos++; else Pos--; }
if (Pos < Min) Pos = Min;
if (Pos > Max) Pos = Max;
}
Position = Position2;
}
int getPos(void)
{
return (Pos/4);
}
void setMinMax(int _Min, int _Max)
{
Min = _Min*4;
Max = _Max*4;
if (Pos < Min) Pos = Min;
if (Pos > Max) Pos = Max;
}
void setClickMultiply(int _clickMultiply)
{
clickMultiply = _clickMultiply;
}
private:
int clickMultiply;
int Max;
int Min;
int Pos;
int Position;
int Position2;
int isFwd;
};
使用两个外部中断端口,主要根据Circuits@home代码,不防抖动。 注意: Serial.print()
函数会占用几毫秒,所以此代码中的中断程序运行时间会相对比较长,当将这些声明去掉的时候,那么就会提升程序的性能。(因为当中断程序占用足够长的时间,那么信号抖动甚至一些信号将会被丢失)。-ED
代码:
/*
RotaryInterrupts - a port-read and interrupt based rotary encoder sketch
Created by Joshua Layne (w15p), January 4, 2011.
based largely on: http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino
Released into the public domain.
*/
#define ENC_A 2
#define ENC_B 3
#define ENC_PORT PIND
uint8_t bitShift = 2; // change to suit your pins (offset from 0,1 per port)
// Note: You need to choose pins that have Interrupt capability.
int counter;
boolean ticToc;
void setup()
{
pinMode(ENC_A, INPUT);
digitalWrite(ENC_A, HIGH);
pinMode(ENC_B, INPUT);
digitalWrite(ENC_B, HIGH);
Serial.begin (115200);
Serial.println("Start");
counter = 0;
ticToc = false;
// Attach ISR to both interrupts
attachInterrupt(0, read_encoder, CHANGE);
attachInterrupt(1, read_encoder, CHANGE);
}
void loop()
{
// do some stuff here - the joy of interrupts is that they take care of themselves
}
void read_encoder()
{
int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
static uint8_t encoderState = 0;
static uint8_t stateIndex = 0;
static uint8_t filteredPort = 0;
uint8_t filter = 0x03; // base filter: 0b00000011
filter <<= bitShift;
Serial.print("raw port value: ");
Serial.println(ENC_PORT, BIN);
Serial.print("filter bitmask: ");
Serial.println(filter, BIN);
filteredPort = ENC_PORT & filter;
Serial.print("filtered port state: ");
Serial.println(filteredPort, BIN);
Serial.print("old encoder state: ");
Serial.println(encoderState, BIN);
encoderState &= filter; // filter out everything except the rotary encoder pins
Serial.print("filtered old encoder state: ");
Serial.println(encoderState, BIN);
encoderState <<= 2; // shift existing value two bits to the left
Serial.print("filtered and shifted (<<2) old encoder state: ");
Serial.println(encoderState, BIN);
encoderState |= filteredPort; // add filteredport value
Serial.print("old encoder state + port state: ");
Serial.println(encoderState, BIN);
stateIndex = encoderState >> bitShift;
Serial.print("encoder state index: ");
Serial.println(stateIndex, DEC);
if (ticToc) {
Serial.print("counter tic: ");
Serial.println(enc_states[stateIndex], DEC);
counter += enc_states[stateIndex];
Serial.print("counter: ");
Serial.println(counter, DEC);
}
ticToc = !ticToc;
Serial.println("----------");
}
使用1个中断,这样就丢失了一半的精度,作者说速度比较快但是却用了digitalRead()
函数而不是直接去操作引脚。(具体请看: http://jeelabs.org/2010/01/06/pin-io-performance/))<< 2011/06 >>
通过尝试所有的例子,我觉得我的代码对于SaprkFun的旋转编码器(SaprkFunRotary encoder)操作地比较快了。但是请注意:需要在编码器的中间引脚和B引脚之间串连一个电容(译者
就是在输出口和GND之间串联一个电容)。
程序:
#include
LiquidCrystal lcd(12,11,5,4,8,7);
#define encoder0PinA 2
#define encoder0PinB 3
volatile int encoder0Pos = 0;
volatile boolean PastB = 0;
volatile boolean update = false;
void setup()
{
lcd.begin(16,2);
lcd.print("Pos:");
pinMode(encoder0PinA, INPUT);
//turn on pullup resistor
digitalWrite(encoder0PinA, HIGH);
pinMode(encoder0PinB, INPUT);
//turn on pullup resistor
digitalWrite(encoder0PinB, HIGH);
attachInterrupt(1, doEncoderB, FALLING);
}
void loop()
{
if (update){
update = false;
PastB? encoder0Pos++:encoder0Pos--;
lcd.setCursor(7,0);
lcd.print(" ");
lcd.setCursor(7,0);
lcd.print(encoder0Pos,DEC);
}
}
void doEncoderB()
{
PastB=(boolean)digitalRead(encoder0PinA);
update = true;
}
7.8.2011 deif
通过许多尝试,我建立了一个电路消除大多数的抖动。
下面是我最简形式的代码:
ISR(INT0_vect){
int a;
a = PIND & 0x0c;
if ((a == 0x0c) || (a == 0)){
encoderCount++;
}
else {
encoderCount--;
}
}
void setupPinInterrupt(){
EICRA = 0x01; //int0 on pinchange
EIMSK = 0x01; //enable interrupt 0
EIFR = 0; //clear flags
}
2011-12-11 by _bse_
和上拉电阻一起使用(还是添加电容)
注意:在中断程序中,使用digitalRead()
而不是直接操作端口。
使用XOR(异或)的方法。
通过异或的方法,将当前B口的状态和前一次端口A的状态进行比较,得出是应该减少还是增加计数(即正反转)。较少的代码,编码器的全精度并且达到了Arduino最大采集速率。
使用愉快!
15 August 2011 by Bruno Chaparro
#define encoder0PinA 2
#define encoder0PinB 3
volatile unsigned int encoder0Pos = 0;
unsigned int tmp = 0;
unsigned int Aold = 0;
unsigned int Bnew = 0;
void setup() {
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
// set up the Serial Connection
Serial.begin (115200);
}
void loop(){
//Check each changes in position
if (tmp != encoder0Pos) {
Serial.println(encoder0Pos, DEC);
tmp = encoder0Pos;
}
delay(500);
}
// Interrupt on A changing state
void doEncoderA(){
Bnew^Aold ? encoder0Pos++:encoder0Pos--;
Aold=digitalRead(encoder0PinA);
}
// Interrupt on B changing state
void doEncoderB(){
Bnew=digitalRead(encoder0PinB);
Bnew^Aold ? encoder0Pos++:encoder0Pos--;
}
我不知道上面代码是怎么运行的(就是XOR方法)(译者
其实没啥神秘的就是将是否相等变成使用异或的方法进行实现,但是我不知道这样是否会对代码的运行速度带来提升。),但是当我将代码和PinChangeIntlibrary
(这样我可以使用所有的I/O口作为中断口)进行联合使用时,我在的小车上运行了两个增量编码器而且运行良好。
I am using a Solarbotics SB Freeduino board with an ATMEGA 328P.(译者
没使用过这个板子,不清楚)。
我希望我对上面的XOR代码是怎么运行了解更多。我不得写点什么在其他时间,但是这个代码很棒。
谢谢你!
使用XOR方法驱动一个LCD和一个LED
使用上面Bruno Chaparro所提到XOR方法,我将一个驱动LCD bargraph和LED的例子和它结合到了一起。
29 December 2011 Mark Amos
// increments/decrements a counter based on the movement of a rotary encoder and
// displays the results on an LCD in digital and bargraph form.
// rotary encoder is a 5 pin. Pins, left to right:
// Encoder pin B - connect to D2 (interrupt 0)
// +5 VDC
// Encoder pin A - connect to D3 (interrupt 1)
// NC
// Ground
// Pin D5 is used to light up an LED connected to ground with a 1K resistor
// using PWM with brightness proportional to the encoder position.
#include
#define encoder0PinA 3
#define encoder0PinB 2
#define analogOutPin 5
LiquidCrystal lcd(13,12,11,10,9,8,7);
volatile unsigned int encoder0Pos = 0;
unsigned int tmp = 0;
unsigned int Aold = 0;
unsigned int Bnew = 0;
void setup() {
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
lcd.begin(20,2);
lcd.clear();
// encoder A on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderA, CHANGE);
// encoder B on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderB, CHANGE);
// set up the Serial Connection
Serial.begin (115200);
Serial.println("Starting");
}
void loop(){
//if position has changed, display it on serial and bargraph
if (tmp != encoder0Pos) {
tmp = encoder0Pos;
Serial.println(tmp, DEC);
lcd.setCursor(0,0);
lcd.print(tmp);
lcd.print(" ");
lcd.setCursor(0,1);
//scale the range of the LCD bargraph from 0-1023 to 0-20 by dividing by 50
for (int loopCnt = 0; loopCnt < (tmp / 50) +1 ; loopCnt++) {
lcd.write(1);
}
lcd.print(" ");
//scale the encorer0Pos from 0 - 1023 to the range of the PWM (0 - 255) by dividing by 4.
analogWrite(analogOutPin, tmp / 4);
}
}
// Interrupt on A changing state
void doEncoderA(){
// if Bnew = Aold, increment, otherwise decrement
Bnew^Aold ? encoder0Pos++:encoder0Pos--;
Aold=digitalRead(encoder0PinA);
// check for underflow (< 0)
if (bitRead(encoder0Pos, 15) == 1) encoder0Pos = 0;
// check for overflow (> 1023)
if (bitRead(encoder0Pos, 10) == 1) encoder0Pos = 1023;
constrain(encoder0Pos, 0, 1023);
}
// Interrupt on B changing state
void doEncoderB(){
Bnew=digitalRead(encoder0PinB);
// if Bnew = Aold, increment, otherwise decrement
Bnew^Aold ? encoder0Pos++:encoder0Pos--;
// check for underflow (< 0)
if (bitRead(encoder0Pos, 15) == 1) encoder0Pos = 0;
// check for overflow (> 1023)
if (bitRead(encoder0Pos, 10) == 1) encoder0Pos = 1023;
}
使用PinChangeInt
库来利用ATmega328p的所有的引脚作为中断口。by GreyGnome
AdaEncoder
库适用于2个基本端口的增量旋转编码器,就像下面链接中所示:
https://www.adafruit.com/products/377
http://www.sparkfun.com/products/9117
通过介绍我们知道:
下面翻译是2016年9月6日15:03:10后翻译,所以有可能翻译没有以前那么准确了。(时间太长了)-译者
这个库适用于2端口编码器(一个A口一个B口还有一个公共口C口)。这个库不会通知每一个状态的改变,而是仅当编码器从一个定位点变化到另一个的时候才会通知用户(这个没有看懂)。 It does not indicate every state change, rather, it reports only when the decoder is turned from one detent position to the next. 它是通过中断触发的并且设计的尽量反应快和好用。 The interrupt routine is lightweight, and the programmer is then able to read the direction the encoder turned at their leisure (within reason; what’s important is that the library is reasonably forgiving). The library is designed to be easy to use (it bears repeating :-) ) and it is reasonably immune to switch bounce.
See the project page at: http://code.google.com/p/adaencoder/
See a speed discussion at: http://code.google.com/p/adaencoder/wiki/Speed
See the PinChangeInt library project page at: http://code.google.com/p/arduino-pinchangeint/
Here’s an example with two encoders connected. Encoder a is connected to pins 2 and 3, b is connected to 5 and 6:
#include // necessary otherwise we get undefined reference errors.
#include
#define a_PINA 2
#define a_PINB 3
#define b_PINA 5
#define b_PINB 6
int8_t clicks=0;
char id=0;
void setup()
{
Serial.begin(115200);
AdaEncoder::addEncoder('a', a_PINA, a_PINB);
AdaEncoder::addEncoder('b', b_PINA, b_PINB);
}
void loop()
{
encoder *thisEncoder;
thisEncoder=AdaEncoder::genie(&clicks, &id);
if (thisEncoder != NULL) {
Serial.print(id); Serial.print(':');
if (clicks > 0) {
Serial.println(" CW");
}
if (clicks < 0) {
Serial.println(" CCW");
}
}
}
by rafbuff
(译者
这个有点吹了,他在中断程序中使用delay(1)
,这也就是说计数的周期应该远大于1ms,但是别人也有小于1ms的啊!!)
我尝试了上面大多数的程序,但是发现它们的计数不是那么的可靠,大多数不能防抖动。同时我尝试了许多技巧性的功能,如中断的使用和提高效率的办法,当要求精确计数的时候,我发现下面的这个运行最好。
/* interrupt routine for Rotary Encoders
tested with Noble RE0124PVB 17.7FINB-24 http://www.nobleusa.com/pdf/xre.pdf - available at pollin.de
and a few others, seems pretty universal
The average rotary encoder has three pins, seen from front: A C B
Clockwise rotation A(on)->B(on)->A(off)->B(off)
CounterCW rotation B(on)->A(on)->B(off)->A(off)
and may be a push switch with another two pins, pulled low at pin 8 in this case
[email protected] 20120107
*/
// usually the rotary encoders three pins have the ground pin in the middle
enum PinAssignments {
encoderPinA = 2, // rigth
encoderPinB = 3, // left
clearButton = 8 // another two pins
};
volatile unsigned int encoderPos = 0; // a counter for the dial
unsigned int lastReportedPos = 1; // change management
static boolean rotating=false; // debounce management
// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;
void setup() {
pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);
pinMode(clearButton, INPUT);
// turn on pullup resistors
digitalWrite(encoderPinA, HIGH);
digitalWrite(encoderPinB, HIGH);
digitalWrite(clearButton, HIGH);
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
Serial.begin(9600); // output
}
// main loop, work is done by interrupt service routines, this one only prints stuff
void loop() {
rotating = true; // reset the debouncer
if (lastReportedPos != encoderPos) {
Serial.print("Index:");
Serial.println(encoderPos, DEC);
lastReportedPos = encoderPos;
}
if (digitalRead(clearButton) == LOW ) {
encoderPos = 0;
}
}
// Interrupt on A changing state
void doEncoderA(){
// debounce
if ( rotating ) delay (1); // wait a little until the bouncing is done
// Test transition, did things really change?
if( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos += 1;
rotating = false; // no more debouncing until loop() hits again
}
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
if ( rotating ) delay (1);
if( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
// adjust counter - 1 if B leads A
if( B_set && !A_set )
encoderPos -= 1;
rotating = false;
}
}
Uses a single External Interrupt pin and also requires loop() for debouncing.
Software Debounce
By jonfraz 09/11
First post from me also, hope it’s useful.
I’ve been playing around with a pretty cheap mechanical encoder and found bouncing was a big issue when I experimented with the other code above.Ideally I would debounce it with hardware, but I’m a noob and lack the knowledge/components. However, thanks to Hifiduino I managed to get my encoder working pretty well with a simple bit of software debouncing.The code uses an interrupt to detect any signal change from the encoder, but then waits 2 milliseconds before calculating the encoder position:
*/ Software Debouncing - Mechanical Rotary Encoder */
#define encoder0PinA 2
#define encoder0PinB 4
volatile unsigned int encoder0Pos = 0;
static boolean rotating=false;
void setup() {
pinMode(encoder0PinA, INPUT);
digitalWrite(encoder0PinA, HIGH);
pinMode(encoder0PinB, INPUT);
digitalWrite(encoder0PinB, HIGH);
attachInterrupt(0, rotEncoder, CHANGE);
Serial.begin (9600);
}
void rotEncoder(){
rotating=true;
// If a signal change (noise or otherwise) is detected
// in the rotary encoder, the flag is set to true
}
void loop() {
while(rotating) {
delay(2);
// When signal changes we wait 2 milliseconds for it to
// stabilise before reading (increase this value if there
// still bounce issues)
if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
encoder0Pos++;
}
else {
encoder0Pos--;
}
rotating=false; // Reset the flag back to false
Serial.println(encoder0Pos);
}
}
J.Carter(of Earth)
This tiny amount of code is focused on keeping the interrupts fast and totally responsible for encoder position
// 'threshold' is the De-bounce Adjustment factor for the Rotary Encoder.
//
// The threshold value I'm using limits it to 100 half pulses a second
//
// My encoder has 12 pulses per 360deg rotation and the specs say
// it is rated at a maximum of 100rpm.
//
// This threshold will permit my encoder to reach 250rpm so if it was connected
// to a motor instead of a manually operated knob I
// might possibly need to adjust it to 25000. However, this threshold
// value is working perfectly for my situation
//
volatile unsigned long threshold = 10000;
// 'rotaryHalfSteps' is the counter of half-steps. The actual
// number of steps will be equal to rotaryHalfSteps / 2
//
volatile long rotaryHalfSteps = 0;
// Working variables for the interrupt routines
//
volatile unsigned long int0time = 0;
volatile unsigned long int1time = 0;
volatile uint8_t int0signal = 0;
volatile uint8_t int1signal = 0;
volatile uint8_t int0history = 0;
volatile uint8_t int1history = 0;
void int0()
{
if ( micros() - int0time < threshold )
return;
int0history = int0signal;
int0signal = bitRead(PIND,2);
if ( int0history==int0signal )
return;
int0time = micros();
if ( int0signal == int1signal )
rotaryHalfSteps++;
else
rotaryHalfSteps--;
}
void int1()
{
if ( micros() - int1time < threshold )
return;
int1history = int1signal;
int1signal = bitRead(PIND,3);
if ( int1history==int1signal )
return;
int1time = micros();
}
void setup()
{
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
attachInterrupt(0, int0, CHANGE);
attachInterrupt(1, int1, CHANGE);
}
void loop()
{
long actualRotaryTicks = (rotaryHalfSteps / 2);
}
by GreyGnome
The ATmega328p has two different kinds of interrupts: “external”, and “pin change”. There are only two external interrupt pins, INT0 and INT1, and they are mapped to Arduino pins 2 and 3. These interrupts can be set to trigger on RISING or FALLING signal edges, or on low level. Most of the sketches and libraries given on this page use one or two of the External Interrupt pins. The interrupt is theoretically very quick because you can set the hardware to tell you how you want the interrupt to trigger. Each pin can have a separate interrupt routine associated with it.
On the other hand the pin change interrupts can be enabled on any or all of theATmega328p’s signal pins. They are triggered equally on RISING or FALLING signal edges, so it is up to the interrupt code to determine what happened (did the signal rise, or fall?) and handle it properly. Furthermore, the pin change interrupts are grouped into 3 “port”s on the MCU, so there are only 3 interrupt vectors (subroutines) for the entire body of 19 pins. This makes the job of resolving the action on a single interrupt even more complicated. The interrupt routine should be fast, but complication is the enemy of speed.
Interrupts disrupt the normal flow of the program. This can be a bit of a curse, although the compiler adds some code for you in order to take away much of the pain of the interrupt. The benefit is that, unlike polling in loop(), you have a better chance of getting all the transitions from your device.
What if, for example, the Arduino is talking to an I2C device via the Wire library? You may not realize that the I2C library has a busy wait portion in it, which means that your 16MHz processor is busy waiting on a 100khz serial communication. That communication is actually being performed in hardware, so there’s no reason that the processor must wait, but that’s how the library was designed. So if you use polling in loop() to check on your rotary encoder, you may miss a quick transition if your code is waiting for a communication to end.
This is not an issue with an interrupt, because your rotary encoder will trigger the interrupt routine even if the Wire library is busy in a while() loop. For this reason, you should be careful using code that polls a switch in loop() on more complicated projects, and understand how long each section of your code will take in worse case scenarios.
by GreyGnome
If your application is speed-critical, know that digitalRead() and digitalWrite() are relatively slow. See http://jeelabs.org/2010/01/06/pin-io-performance/.
From that link:
Let me just conclude with: there are order-of-magnitude performance implications, depending on how you do things. As long as you keep that in mind, you’ll be fine.
To get a closer look at the implications, I actually measured the speed.
Speed Tests
From http://arduino-pinchangeint.googlecode.com/files/PinChangeInt%20Speed%20Test-1.3.pdf Tested External Interrupts against Pin Change Interrupt, to look at the relative speeds vs. the External Interrupts, and also to better judge the performance hit of using digitalRead() and digitalWrite().
Test Pin | triggered | Interrupt Type | Loop time, 100,000 iterations, ms. | Average time per loop (us) |
---|---|---|---|---|
1 | 2 | External | 1420 | 14.20 |
2 | 2 | Pin Change | 2967 | 29.67 |
Next, we turned on the LED Pin using direct port manipulation, then turned it on using digitalWrite() under Arduino 022 and Arduino 1.0. This test is enabled by #define’ing the COMPAREWRITES compiler directive in the source code. If DIRECTPORT is #define’ed the LED pin is turned on and off using direct port manipulation. If #undef’ed, digitalWrite() is used to manipulate the LED pin.
Test | Pin triggered | Interrupt Type | Arduino Version | LED pin turned on using… | Loop time, 100,000 iterations, ms. | Average time per loop (us) |
---|---|---|---|---|---|---|
1 | 2 | External | 022 | Direct Port Manipulation | 1603 | 16.03 |
2 | 2 | External | 022 | digitalWrite() | 2232 | 22.32 |
3 | 2 | External | 1.0 | digitalWrite() | 2245 | 22.45 |
Now read the LED Pin using direct port manipulation, then read it using digitalWrite() under Arduino 022 and Arduino 1.0. This test is enabled by #define’ing the COMPAREREADS compiler directive in the source code. If DIRECTPORT is #define’ed the LED pin is read using direct port manipulation. If #undef’ed, digitalREAD() is used to manipulate the LED pin.
Test | Pin triggered | Interrupt Type | Arduino Version | LED pin turned on using… | Loop time, 100,000 iterations, ms. | Average time per loop (us) |
---|---|---|---|---|---|---|
1 | 2 | External | 022 | Direct Port Manipulation | 1559 | 15.59 |
2 | 2 | External | 022 | digitalRead() | 2188 | 21.88 |
3 | 2 | External | 1.0 | digitalRead() | 2189 | 21.89 |
PLUS:这是第二次使用MarkDown进行博客的编辑,先比较上一次,速度上有所提升,但是感觉比较不好的是,代码的渲染和CSDN那个传统编辑器比太low!另外正文的字体稍微小了一点。不知道是否可以更改。文章结尾是MarkDown的demo,现在先不删了,以后还要仿照着用
关于翻译这篇文章:由于译者(xuanyuanlei1020)的水平有限,有些地方还没有找到比较好的翻译或有点地方还没翻译,我也是用到的时候再去翻译的,也就是说不一定什么时候翻译完,请谅解。另外本文各类应用的程序较多,看完本文也是有较大的启发,但是也耗费了大量的时间,希望对大家有所帮助。
关于文章的解释:带译者
的部分是本人(xuanyuanlei1020)的理解,其他的都是翻译的原话
巨人正在奔跑!!
Ctrl + B
Ctrl + I
Ctrl + Q
Ctrl + L
Ctrl + K
Ctrl + G
Ctrl + H
Ctrl + O
Ctrl + U
Ctrl + R
Ctrl + Z
Ctrl + Y
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
定义 D
定义D内容
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
生成一个脚注1.
用 [TOC]
来生成目录:
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
更多LaTex语法请参考 这儿.
可以渲染序列图:
或者流程图:
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。