I am now hopelessly stuck and need help please.
I am building an automated observatory and now at the stage of trying to control the observatory dome rotation. The dome is rotated using a step motor and it works fine. The rotation of the dome also turns a rotary encoder to give an independent verification that the dome is moving. Although the RE is a quadrature type I am simply counting RE ticks from both channels as I don't need direction information.
What I am trying to do now is to count how many motor driving pulses occur between each pair of RE ticks. The reasoning behind this is to monitor the dome movements and detect jamming. The actual motor pulses will be used for all dome position calculations.
To achieve this, I have built a small board with a ULN2803A inverting buffer and an Arduino Nano. The Nano uses Pin Change interrupts on pins D11 and D12 to count the RE ticks. Pin D7 will also be used to monitor a Home Position sensor although this doesn't exist at present. The motor pulses are sent via the buffer to Pin D2 (INT0) and the motor direction is sent via the buffer to pin D3 (INT1). The external interrupts are then used to count the motor pulses, up or down depending on the motor direction.
For information, there is another board present, a Velleman VM110 USB breakout board, that also receives the rotary encoder ticks. This gives me a simple way to check that my board gives the same answer when I rotate the rotary encoder shaft by hand.
OK - sorry this is so long winded but now it gets weird.
When I rotate the RE without the motor running everything is fine. I get four times as many pulses as the Velleman board as I am using Pin Changes, triggering on each rise and fall, not on full cycles of the encoder. This does happen as predicted and everything is nice and stable.
BUT - when I start the motor running, I get hundreds of RE ticks every time I move the encoder and sometimes I get increasing RE ticks without touching the encoder at all.
The obvious answer is some form of RFI but I have done everything I can to eliminate this. The motor driver and the Nano board that powers it are in a shielded enclosure, the cable to the motor is a shielded cable and I have 10uH chokes on each of the four motor leads. Finally, I have fitted a filter to the incoming power supply to the motor box to minimise any RFI going back down the power lines.
The use of the ULN2803A buffer was my latest attempt to make this work. Previously the signals went direct to the Nano pins. With the buffer, I have used 20k pull-ups on the input side and 10k pull-ups on the outputs. This was a direct copy of the Velleman board input circuit that I knew worked without problems.
I have looked at the motor pulses at the input pin to the Nano on my scope and they are nice looking, sharp edged pulses of duration 70 uS and a frequency of 497 Hz. Not bad as I set the pulse rate using the Accelstepper library to 500Hz.
I now suspect that this is a software problem. This wouldn't surprise me as I am very new to all this, just trying to learn enough at each stage to do what I need. I have attached the code - this is a stripped down version without a lot of I2C stuff that is not relevant to my problems.
Again, for information. I have tried using the attachInterrupt()
function and by directly setting the relevant registers - no difference!
Anyway, I really really hope someone can help me sort this out.
Regards, Hugh
/*
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 <Wire.h>
#include "streaming.h"
#include "I2C_AnythingHEG.h"
#include <EEPROMex.h>
//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;
}
PCMSK0 &= 0b00000000; //Clear all bits
- wouldn'tPCMSK0 = 0;
be a lot clearer? Meanwhile I agree with Gerben's answer. Try "guarding" your access to multi-byte variables set by interrupts. See Interrupts.PCMSK0 = _BV(PCINT3) | _BV(PCINT4);
, which looks clearer thanPCMSK0 &= 0b00000000; PCMSK0 |= 0b00011000;
, at least to those familiar with AVR programming.