奇怪的结果试图计算脉冲

我现在绝望地被困住了,请求帮助。

我正在建造一个自动化的天文台,现在正处于试图控制天文台圆顶旋转的阶段。圆顶使用步进电机旋转,工作正常。圆顶的旋转也使旋转编码器旋转,以独立验证圆顶是否在移动。虽然RE是正交类型,但我只是计算来自两个通道的RE标记,因为我不需要方向信息。

我现在要做的是计算每对RE刻度之间发生的电机驱动脉冲数。这背后的原因是监视球机运动并检测卡纸。实际的电机脉冲将用于所有圆顶位置计算。

为实现这一目标,我建造了一块带有ULN2803A反相缓冲器和Arduino Nano的小板。 Nano使用引脚D11和D12上的引脚更改中断来计算RE标记。引脚D7也将用于监控原位传感器,尽管目前还不存在。电机脉冲通过缓冲器发送到引脚D2(INT0),电机方向通过缓冲器发送到引脚D3(INT1)。然后根据电机方向使用外部中断来计数电机脉冲,向上或向下计数。

有关信息,还有另一个板,一个Velleman VM110 USB分线板,也可以接收旋转编码器滴答。这让我有一个简单的方法来检查当我手动旋转旋转编码器轴时我的电路板给出相同的答案。

好的 - 对不起,这是如此漫长的啰嗦,但现在它变得奇怪了。

当我在没有电机运转的情况下旋转RE时,一切都很好。当我使用引脚变化时,我获得的脉冲数量是Velleman板的四倍,每次上升和下降都会触发,而不是编码器的完整周期。这确实像预测的那样发生,一切都很好而且稳定。

但是 - 当我开始运行电机时,每次移动编码器时都会得到数百个RE标记,有时我会在不触及编码器的情况下增加RE标记。

显而易见的答案是某种形式的RFI,但我已尽我所能消除这一点。电机驱动器和为其供电的Nano板位于屏蔽外壳中,连接电机的电缆是屏蔽电缆,我在四个电机引线的每一个上都有10uH扼流圈。最后,我为电机箱的输入电源安装了一个滤波器,以最大限度地减少返回电源线的RFI。

使用ULN2803A缓冲区是我最近尝试完成这项工作的尝试。以前信号直接发送到Nano引脚。使用缓冲器,我在输入端使用了20k的上拉电阻,在输出端使用了10k的上拉电阻。这是我知道工作没有问题的Velleman板输入电路的直接副本。

我已经查看了我的示波器上Nano输入引脚上的电机脉冲,它们看起来很漂亮,锋利的脉冲持续时间为70 uS,频率为497 Hz。不错,因为我使用Accelstepper库将脉冲率设置为500Hz。

我现在怀疑这是一个软件问题。这并不会让我感到惊讶,因为我对这一切都很陌生,只是想在每个阶段学到足够的东西来做我需要的事情。我附上了代码 - 这是一个简化版本,没有很多与我的问题无关的I2C内容。

再次,获取信息。我尝试过使用 attachInterrupt()函数并直接设置相关的寄存器 - 没有区别!

无论如何,我真的希望有人可以帮助我解决这个问题。

问候,休

/*          
ABoard3  
ROTATION MONITORING AND POSITION
Version AB3_stripped
*****************************PURPOSE*****************************************
This sketch is used on an Arduino Nano to count the motor pulses from ABoard1
and the Rotary Encoder ticks. The motor pulse count between encoder ticks is
used to detect dome jamming.
****************************INPUTS*******************************************
PIN CHANGE INTERRUPTS
**********ROTARY ENCODER INPUT*********
The rotary encoder is a Bourns ENA1J-B28 L00064L 1139M MEX 64 cycle per turn
optical encoder. This is connected to ABoard3 Pins D11 and D12. These pins
connect to Channel A and Channel B respectively. Pin change interrupts are used
to receive the rotary encoder output.
(The output pulses from the rotary encoder is also sent to the Velleman board
for use by LesveDomeNet for finding dome position)
*********HOME POSITION INPUT*********
The home position sensor is an Omron EESX671 Optical Sensor.
The sensor output is connected to ABoard3 Pin D7.
Pin change interrupt PCINT21 records activation/deactivation of the sensor.
EXTERNAL INTERRUPTS
*********MOTOR PULSE INPUT***********
The pulses sent to the G251X stepper driver are also sent to Aboard3 Pin D2.
This pin is the External Interrupt pin, vector INT0
*********MOTOR DIRECTION INPUT********
Motor direction is input to ABoard3 from ABoard2. ABoard2 pin, pnVmInRotMotor
(AB2:A0{54}) is connected to Velleman pins DI4 and DO2 and also to AB3:D3.
AB3:D3 is an External Interrupt pin, vector INT1.
*/

#include                        
#include "streaming.h"                        
#include "I2C_AnythingHEG.h"   
#include                     

//CONSTANTS                       
//PIN ASSIGNMENTS For ABOARD3
const uint8_t pnMotorPulse = 2;      //Port PD2, INT0, ISR(INT0_vect){}
const uint8_t pnMotorDirection = 3;  //Port PD3, INT1, ISR(INT1_vect){}
const uint8_t pnDomeAtHome = 7;      //Port PD7, PCINT23,ISR(PCINT2_vect){}
const uint8_t pnREChanA = 11;        //Port PB3, PCINT3, ISR(PCINT0_vect){}
const uint8_t pnREChanB = 12;        //Port PB4, PCINT4, ISR(PCINT0_vect){}

//*****************EXPERIMENTAL STUFF FOR PULSE COUNTING*******************************                  
uint16_t volatile myTest = 0;
int32_t volatile ratioCount = 0L;
int32_t volatile totalCount = 0L;
int32_t volatile tickCount = 0L;
uint8_t volatile halftickCount = 0;
int32_t volatile addPulse = 0L; 

void setup() {
  //**********************************SERIAL FOR DEBUG ONLY************************
  Serial.begin(115200);
  //*************************INPUT PIN MODE SETUP**********************************
  //NOTE Set all UNUSED PCINT0:5 pins (D8, D9, D10, D11) to OUTPUT.
  //and set the value to LOW
  //The actual pins used to receive interrupts have external 10k pull-ups.
  //This is to reduce susceptibility to interference causing false triggering.
  pinMode(pnMotorPulse, INPUT); //D2
  pinMode(pnMotorDirection, INPUT); //D3
  pinMode(pnDomeAtHome, INPUT); //D7
  pinMode(pnREChanA, INPUT); //D11
  pinMode(pnREChanB, INPUT); //D12
  pinMode(4, OUTPUT); //D4
  digitalWrite(4, LOW);
  pinMode(8, OUTPUT); //D8
  digitalWrite(8, LOW);
  pinMode(9, OUTPUT); //D9
  digitalWrite(9, LOW);
  pinMode(10, OUTPUT); //D10
  digitalWrite(10, LOW);
  //******************SET UP AddPulse According to Motor Direction******************
  //This is needed to make sure the pulse counting starts in the correct direction
  //as the direction is only checked in the ISR after a change has occurred.
  //If Motor Direction is AntiClockwise, change to Subtract a pulse
  if( digitalRead(pnMotorDirection)) {
    addPulse = 1L;
  } else {
    addPulse = -1L;
  }
  //**************************SET UP PIN CHANGE INTERRUPTS**************************
  //Set the Pin Change Interrupt Masks
  //Clear all bits to start with
  PCMSK0 &= 0b00000000; //Clear all bits
  PCMSK1 &= 0b00000000; //Clear all bits
  PCMSK2 &= 0b00000000; //Clear all bits
  //Mask for PCINTs 0 through 7 is PCMSK0 (Rotary Encoder Pulses)
  //Need to allow interrupts on PCINT3 and PCINT4, so set bits 3 and 4
  //PCINT3 is Nano  pin D11 and PCINT4 is Nano pin D12
  //Use a compound bitwise OR to set the bits
  PCMSK0 |= 0b00011000; //Enable PCINT3 ONLY (bit 3)
  //Interrupt on pins 11 and 12, RE Ticks
  //Mask for PCINTs 16through 23 is PCMSK2 (Home Position)
  //Need to allow interrupts on PCINT23 so set bit 7
  PCMSK2 |= 0b10000000; //Interrupt on pin 7, Home Position Sensor activated
  //Now enable the interrupts (TURN THEM ON) by setting bits in PCICR
  //PCICR is Pin Change Interupt Control Register.Set bit 0 (PCIE0)  
  //to enable interrupts PCINT0:7 This catches PCINT3 and 4 - RE Ticks
  //Set bit 2 (PCIE2) to enable interrupts PCINT16:23. Catches PCINT21 - Home Position Sensor
  PCICR &= 0b00000000; //Clear PCICR register 
  PCICR |= 0b00000001; //Set bit 0 - Catches PCINT3 & PCINT4 - RE Ticks
  PCICR |= 0b00000100; //Set bit 2 - Catch PCINT21 - Home Position Sensor
  //**************************SET UP 'EXTERNAL INTERRUPTS'**************************
  //Interupts on External Interrupt Pins D2 (INT0) and D3 (INT1).
  //INT0 (Nano pin D2)occurs when a stepper motor driver pulse is received
  //INT1 (Nano pin D3)occurs when the ABoard2 pin, pnVmInRotMotor toggles 
  //indicating a motor direction change.
  //To use the 'External Interrupts' the following registers need to be set-up:         
  //EICRA External Interrupt Control Register A       
  //Need to set Interrupt Sense Control bits ISC11 .. ISC00 (bits 3:0 in EICRA)
  //ISC01     ISC00 (INT0)    Interrupt
  //ISC11     ISC01 (INT1)    Generated by
 // 0         0             Low level on Pin
 // 0         1             Any Logical Change
 // 1         0             Falling Edge
 // 1         1             Rising Edge
  //First clear all bits, then set as follows:  
  //For INT1 - Motor Direction - Generate on ANY LOGICAL CHANGE     
  //bit 3 ISC11 0     
  //bit 2 ISC10 1 This combination = Any logical change causes interrupt 
  //For INT0 - Stepper Motor Pulses  - Generate on RISING EDGE      
  //bit 1 ISC01 1     
  //bit 0 ISC00 1 This combination = Rising edge of pulse causes interrupt
  //NOTE: To provide some immunity to RFI, Aboard3:Pins 2 & 3 are pulled high
  //using 10k resistors. 
  //So, code is
  EICRA &= 0b00000000; //Clear EICRA register
  EICRA |= 0b00000111;//Set bits 0,1 and 2 in EICRA register
  //EIMSK External Interrupt Mask Register        
  //Need to set External Interrupt Request Enables INT1 & INT0  (bits 1:0)          
  //First clear both bits, then set as follows: 
  //bit 1  INT1  1 External interrupt pin (D3) enabled   
  //bit 0  INT0  1 External interrupt pin (D2) enabled   
  //So, code is
  EIMSK &= 0b00000000; //Clear EIMSK register
  EIMSK |= 0b00000011;//Set bits 0 and 1 in EIMSK register
  //NOTE: Setting EIMSK TURNS ON THE EXTERNAL INTERRUPTS
  //************VARIABLE INITIALISATION*********
  myCommand = 0;
  myTest = 0;
  tickCount = 0L;
  totalCount = 0L;
} //END of setup

void loop() {
  //******************************COMMAND ACTIONS******************************
  if (myTest == 3) (
    //RE tick
    Serial << "Tick Count = " << tickCount << "  totalCount = " << totalCount << "\n";
    myTest = 0;
  }
}
//*************************FUNCTIONS/ISRs FROM HEREON*************************
ISR(INT0_vect) {
  //Triggered by stepper motor drive pulse from ABoard1
  totalCount = totalCount + addPulse;
}
ISR(INT1_vect) {
  //Triggered by a change in motor direction
  if(digitalRead(pnMotorDirection)) {
    addPulse = 1L;
  } else {
    addPulse = -1L;
  }
}
ISR(PCINT0_vect) {
  //Triggered by a ROTARY ENCODER TICK
  halftickCount++;
  if (halftickCount == 2) {
    //Make count same as Velleman
    tickCount++;
    halftickCount = 0;
    myTest = 3;
  }
}
ISR(PCINT2_vect) {
  //Triggered by activation of Home Position Sensor
  myTest = 4;
}
0
1)你有一个示波器,用它来观察电机脉冲。你还看过编码器脉冲吗?它们看起来怎么样? 2)在正交编码器上,我预计在任何给定时间最多只有一个通道是“有弹性的”。然后,如果你以通常的方式使用编码器,你应该获得+1,-1,+ 1,-1 ......的序列,加起来为零。 3)设置AVR I/O寄存器的标准习惯用法是 PCMSK0 = _BV(PCINT3)| _BV(PCINT4); ,看起来比 PCMSK0&= 0b00000000更清晰; PCMSK0 | = 0b00011000; ,至少对那些熟悉AVR编程的人来说。
额外 作者 Sprogz,
PCMSK0&= 0b00000000; //清除所有位 - 不会 PCMSK0 = 0; 会更清楚吗?同时我同意Gerben的回答。尝试“保护”您对中断设置的多字节变量的访问。请参阅中断
额外 作者 Nick Gammon,
在正交编码器输出和Arduino之间是否有适当的共用接地?如果是这样,那么@EdgarBonet是对的。正交编码器可以像疯了一样弹跳,因此您确实需要正确读取编码器,确定步进方向以及每次读取的所有步骤。这样,如果它连续100次反弹+1然后-1或任何其他序列,你最终知道新位置是什么,从而你也已经去掉了它。尝试在每个正交输出上添加一个2.2uF的上限,以帮助一点点硬件去抖。
额外 作者 meepsh,
正确阅读编码器是您真正需要的软件。
额外 作者 meepsh,
您的旋转编码器是电气还是光学?我问,因为我不知道你是否有关于开关弹跳或光接收器在边界处开关的问题。如果是这种情况,可以帮助解决它的一种方法是正确解码四编码脉冲。这篇博文显示了解码四编码脉冲的可靠方法: thewanderingengineer.com/2014/08/11/… Regads,
额外 作者 CMaster,
在处理器中进行去抖动的替代方案是使用为此目的而设计的芯片。这是一个可能适合的: elmelectronics.com/ic/elm402 问候,
额外 作者 CMaster,

1 答案

对于某些变量,不需要使用int32_t。在8位处理器上使用大于8位的变量的问题是处理器需要4次读取才能获得该值。在这些读取过程中,可能会发生中断,从而产生一个具有旧值的一些位的值,以及新的一些位。

一些变量只需要8位。

uint8_t volatile myTest = 0;
int32_t volatile totalCount = 0L;
int32_t volatile tickCount = 0L;
uint8_t volatile halftickCount = 0;
int8_t volatile addPulse = 0L; 

接下来就是将值读取为原子。

void loop()                                             
{                                               
//******************************COMMAND ACTIONS******************************  

if (myTest == 3)   //RE tick
{
    noInterrupts();
    int32_t tickCountCopy = tickCount;
    int32_t totalCountCopy = totalCount;
    interrupts();
    Serial << "Tick Count = " << tickCountCopy << "  totalCount = " << totalCountCopy << "\n"; 
    myTest = 0; 
}
}   

PS。这不一定是答案,但它不符合评论。

1
额外
这里没有真正的反弹,但可能并不像你期望的那样清晰。我的猜测是施密特触发器单独起作用。即使我的答案无法解决您的问题,但仍然指出您可能会对代码进行说明的问题。
额外 作者 Al.,
嗨,谢谢Gerben和Nick。我做了你推荐的改变,但它们实际上似乎让事情变得更糟,而不是更好,因为即使没有步进电机运行,我也会在每个RE通道上获得多次触发。
额外 作者 SeaDrive,
哎呀 - 想要一条新线并按下返回但是在我完成之前发送了评论。所以,原谅从现在开始缺乏格式化。我现在得出结论,我需要去抖动旋转编码器。我已经假设它是一个光学编码器,它不需要去抖动,但我现在认为我的假设是错误的。我将使用RC时序和74HC14十六进制反相施密特触发器IC制作小型硬件去抖器。希望帽子能解决问题。好消息是实际的步进电机脉冲没有任何问题。谢谢,休。
额外 作者 SeaDrive,
嗨,我试图使用我的示波器来查看编码器输出,但我不知道如何正确使用它并且无法获得有意义的结果。我已经制作了debouncer板,在每个编码器通道上使用了大约5 mS的延迟,最后,它都按预期运行。我在12月初第一次遇到这个问题,所以它花了很长时间才解决了这个问题。实际上买了我的范围来帮助解决这个具体问题。再次感谢所有回答和评论的人。关心休
额外 作者 SeaDrive,
@HughGilhespie,如果现在问题解决了,请回答你自己的问题并将其标记为已解决。
额外 作者 meepsh,