Using a rotary encoder with AVR Micro controller - microcontroller

I'm having trouble getting a rotary encoder to work properly with AVR micro controllers. The encoder is a mechanical ALPS encoder, and I'm using Atmega168.
Clarification
I have tried using an External Interrupt to listen to the pins, but it seems like it is too slow. When Pin A goes high, the interrupt procedure starts and then checks if Pin B is high. The idea is that if Pin B is high the moment Pin A went high, then it is rotating counter clock-wise. If Pin B is low, then it is rotating clock-wise. But it seems like the AVR takes too long to check Pin B, so it is always read as high.
I've also tried to create a program that simply blocks until Pin B or Pin A changes. But it might be that there is too much noise when the encoder is rotated, because this does not work either. My last attempt was to have a timer which stores the last 8 values in a buffer and checks if it is going from low to high. This did not work either.
I have tried scoping the encoder, and it seems to use between 2 and 4ms from the first Pin changes till the other Pin changes.

I have a webpage about rotary encoders and how to use them, which you might find useful.
Unfortunately without more information I can't troubleshoot your particular problem.
Which microcontroller pins are connected to the encoder, and what is the code you're currently using to decode the pulses?
Ok, you're dealing with a few different issues, the first issue is that this is a mechanical encoder, so you have to deal with switch noise (bounce, chatter). The data sheet indicates that it may take up to 3mS for the parts to stop bouncing and creating false outputs.
You need to create a debounce routine. The simplest of which is to continuously check to see if A goes high. If it does, start a timer and check it again in 3 ms. If it's still high, then you can check B - if it's not high then you ignore the spurious pulse and continue looking for A high. When you check B, you look at it, start a timer for 3 ms, and then look at B again. If it was the same both times, then you can use that value - if it changes within 3 ms then you have to do it again (read B, wait 3 ms, then read it again and see if it matches).
The atmega is fast enough that you shouldn't have to worry about these checks going slowly, unless you're also running a slow clock speed.
Once you deal with the mechanical noise, then you want to look at a proper gray code routine - the algorithm you're following won't work unless you also decrement if A is high when B goes low. Generally people store the last value of the two inputs, and then compare it to the new value of the two inputs and use a small function to increment or decrement based on that. (Check out the heading "high resolution reading" on the website I mentioned above for the table). I combine the two readings into a four bit number and use a simple array to tell me whether I increment or decrement the counter, but there are solutions that are even more advanced, and optimize for code size, speed, or ease of code maintenance.

Adding an analog lowpass filter greatly improves the signal. With the lowpass filter, the code on the AVR was really simple.
_________
| |
| Encoder |
|_________|
| | |
| | |
100n | O | 100n
GND O-||-+ GND +-||-O GND
| |
\ /
3K3 / \ 3K3
\ /
| |
VCC O-/\/-+ +-\/\-O VCC
15K | | 15K
| |
O O
A B
Ah, the wonders of ASCII art :p
Here is the program on the AVR. Connect A and B to input PORTB on the avr:
#include <avr/io.h>
#define PIN_A (PINB&1)
#define PIN_B ((PINB>>1)&1)
int main(void){
uint8_t st0 = 0;
uint8_t st1 = 0;
uint8_t dir = 0;
uint8_t temp = 0;
uint8_t counter = 0;
DDRD = 0xFF;
DDRB = 0;
while(1){
if(dir == 0){
if(PIN_A & (!PIN_B)){
dir = 2;
}else if(PIN_B & (!PIN_A)){
dir = 4;
}else{
dir = 0;
}
}else if(dir == 2){
if(PIN_A & (!PIN_B)){
dir = 2;
}else if((!PIN_A) & (!PIN_B)){
counter--;
dir = 0;
}else{
dir = 0;
}
}else if(dir == 4){
if(PIN_B & (!PIN_A)){
dir = 4;
}else if((!PIN_A) & (!PIN_B)){
counter++;
dir = 0;
}else{
dir = 0;
}
}else if(PIN_B & PIN_A){
dir = 0;
}
PORTD = ~counter;
}
return 0;
}
This code works unless you rotate the encoder really fast. Then it might miss a step or two, but that is not important, as the person using the encoder won't know how many steps they have turned it.

Speed should not be a problem. Mostly all mechanical switches need debounce routines. If you wanna do this with interrupts turn off the interrupt when it triggers, start a timer that will turn it back on after a couple of ms. Will keep your program polling-free >:)

What exactly are you having problems with? I assume you've been able to hook the pins of the encoder to your PIC as per the technical specifications linked on the Farnell page you gave, so is the problem with reading the data? Do you not get any data from the encoder? Do you not know how to interpret the data you're getting back?

/* into 0 service rutine */
if(CHB)
{
if(flagB)
Count++;
FlagB=0;
}
else
{
if(FlagB)
count--:
FlagB=0:
}
/* into 1 service rutine */
FlagB=1;
/* make this give to you a windows time of 1/4 of T of the encoder resolution
that is in angle term: 360/ (4*resolution)
*/

Related

How to Get Data from Rotary Encoder at VERY High Speeds (>4500 RPM)

Equipment:
Arduino ATmega2560
Rotary Encoder: https://www.amazon.ca/gp/product/B08RS6M32J/ref=ppx_yo_dt_b_asin_title_o06_s01?ie=UTF8&psc=1
Manual Curved Treadmill (what it looks like if you've never seen one before): https://www.youtube.com/watch?v=0oil6cmUCog&ab_channel=ResolutionFitness
Background:
I have a manual treadmill and I want to use my Arduino to capture distance and speed data. To do this, my approach was to use a rotary encoder, attach it to a skateboard wheel (older picture is attached, I'm using a bigger wheel now and there's no photo for that right now) such that when I run on the treadmill, the wheel spins proportionately.
In the code below, whenever I run it, the counter variable would accumulate to about 2000 (pulses) every time the wheel makes a full rotation. The wheel diameter = 6cm, therefore, it's circumference = 18.8495559215 cm.
This means for every centimeter I travel, there are about 106 pulses. (2000 pulses/18.8495559215 cm = ~106 pulses/cm).
Here's the code: (I got it from this website and only changed 2 lines of code because my sensor doesn't need to go in reverse; the treadmill only travels in one direction - https://electricdiylab.com/how-to-connect-optical-rotary-encoder-with-arduino/)
volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder
void setup() {
Serial.begin (9600);
pinMode(2, INPUT_PULLUP); // internal pullup input pin 2
pinMode(3, INPUT_PULLUP); // internalเป็น pullup input pin 3
//Setting up interrupt
//A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
attachInterrupt(0, ai0, RISING);
//B rising pulse from encodenren activated ai1(). AttachInterrupt 1 is DigitalPin nr 3 on moust Arduino.
attachInterrupt(1, ai1, RISING);
}
void loop() {
// Send the value of counter
if( counter != temp && counter % 106 == 0 ){ // Only print something if the wheel travels in increments of 1 cm.
Serial.println (counter);
temp = counter;
}
}
void ai0() {
// ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
// Check pin 3 to determine the direction
if(digitalRead(3)==LOW) {
counter++;
}else{
counter++; // The original code said counter-- but my treadmill only goes one direction and the this variable starts to decrease when I reach a certain speed (around 5 kmk/h or (3.1 mph) or so). After I changed it to counter++, this issue was resolved however, the arduino serial monitor kept freezing at high speeds.
}
}
void ai1() {
// ai0 is activated if DigitalPin nr 3 is going from LOW to HIGH
// Check with pin 2 to determine the direction
if(digitalRead(2)==LOW) {
counter++; //// The original code said counter-- but my treadmill only goes one direction and the this variable starts to decrease when I reach a certain speed (around 5 kmk/h or (3.1 mph) or so). After I changed it to counter++, this issue was resolved however, the arduino serial monitor kept freezing at high speeds.
}else{
counter++;
}
}
The Problem
*To get the speed is easy, it's just not in this code right now. Accomplished by using t1 and t2 variables and the Millis() function.
When I walk on the treadmill, I typically walk at about 4 or 5 km/h (3.1 mph). This works fine. However, the issue is I want to be able to sprint over 30 km/h (18.6 mph) and have the code be able to support up to 50 km/h (31 mph). Right now, I'm unable to run at speeds over 5 km/h without the serial monitor freezing periodically, or there being inaccurate data.
At these speeds, I would need the sensor to support the wheel moving at over 4500 RPM -> 75 RPS or (75 RPS x 2000 P/R = 150,000 Pulses/Second)
I have absolutely no idea why this issue is happening and how I should approach this to achieve my desired speeds.
Possible reasons why this is happening:
I'm not an expert at Arduino at all and am just starting to understand things like interrupts. I have my pins in 2 and 3. Should I leave this alone or change it?
If you click the Amazon link, you'll see that there is a 3rd Orange "Z" wire which I have absolutely no idea what it does. The few tutorials I could find only involve the two "A" and "B" wires. Maybe incorporating the "Z" wire would point me in the right direction?
My Rotary encoder sensor is using the Arduino's 5V. If you click on the Amazon link, you'd see that it actually supports up to 26V. Should I find an external power source that allows up to 26V and pair it with a relay? Not sure if the extra voltage will help me.
I'm not sure why by default, the sensor counted a full rotation for the wheel to be 2000 pulses. On Amazon, you'll see that it supports the following Pulses/Revolution:
Resolution (pulse/rotation) : 100, 200, 300, 360, 400, 500, 600, 1000, 1024, 1200, 2000, 2500, 3600, 5000 (optional)
How can I change my 2000 P/R to 5000 P/R for example? Would this help?
Summary
I want to use my Arduino and rotary sensor to collect speed and distance data from my manual treadmill. At slow speeds (up to 5 km/h), the code works fine, but at high speeds, the data is highly inaccurate, and the serial monitor freezes every few seconds. I want the Arduino to support speeds up to 50 km/h which is about 4500 RPM. If my solution with the rotary sensor is not feasible, I am 100% open to other ideas as I just want the speed and distance data. I'll purchase any new equipment that's necessary but ideally, I'd like to work with what I have right now.
Thank you for your help!
Well I looked at the datasheet of your encoder and I was also very confused regarding the multiple values of resolution... what I suspect is that maybe there are different E6B2-CWZ6C models with differnt ppr's...
But what was useful was that I found out that your encoder is an incermental rotary encoder (this video explains better the inner workings of your type of encoder) what this means is that you have three wires A black, B white and Z the orange one.
As described on the video when your shaft completes 1 full rotation you will get N quantity of pulses from A and B, but depending on which pulse you get first would determines the rotation, according to the diagram below, if you first get a pulse from A it would mean that the rotation is CW but if you get B first it would then be CCW.
So for example lets say you start with a pulse_count=0 and start turning your shaft CW and you start counting the pulses on A, the count would go 0,1,2... and so on, but how would you know when to stop counting? that's when the Z orange cable comes in place, because it makes a pulse only after a whole rotation has been made so let's say you counted up to 200 and then you get a pulse from Z then you would know that you should restart your count back to the begining.
So to solve your problem and if you are only interested in the measured speed i would sugest to only measure how long does it take from one Z pulse to the next one, which would tell you how long it took for your encoder to complete one rotation, which could then be use to calculate the speed.
If we solve for rpms using this formula we get that RPM=(V60)/(2pir) if V=50km/h=13.88 m/s and r=3cm=0.03m we get that at 50km/h we would get 4418.14 rpm or 4418/60= 73.63 revs per sec what means 73 pulses of Z every second.
What was maybe happening is that for example if A had a resolution of 2000 ppr would imply that for every second you were receiving 73*2000 = 146000 pulses every second and as you configured your baudrate for your serial communication at Serial.begin(9600) you were only capable of reading at most 9600 pulses for each second which is way less than your 146000 pulses in A. However there are different baudrates you can set: 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, or 115200. Just be sure to select the same audrate when using the serial monitor.
For extremely high speeds you have to use bit operators and port registers.
Try this code with the equivalent pins CLK and DT connected to A2 and A1 receptively.
// Rotary Encoder Inputs
#define CLK A2
#define DT A1
int counter = 0;
String currentDir ="";
unsigned char currentPairBit = 0b0; // 8 bits
unsigned char lastPairBit = 0b0; // 8 bits
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
DDRC = 0b00000000; // Set Analog(C) encoder pins as inputs
}
void loop() {
while(true) { // while cycle is faster than loop!
// reads the analog states!
currentPairBit = PINC >> 1 & 0b11; // Gets A2(PC2) and A1(PC1) bits on C (analog input pins) (>> 1 = Jumps pin 0)
if ((lastPairBit & 0b11) != currentPairBit) {
lastPairBit = lastPairBit << 2 | currentPairBit;
// Bit Pairs Cyclic Sequence:
// 1. 2. 3. 4. 5.
// 11 | 01 | 00 | 10 | 11 for CCW
// 11 | 10 | 00 | 01 | 11 for CW
if (lastPairBit == 0b01001011 || lastPairBit == 0b10000111) {
if (lastPairBit == 0b01001011) {
currentDir = "CCW";
counter--;
} else {
currentDir = "CW";
counter++;
}
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
}
}
}
Then check the Serial Monitor to see how fast it is. Note that you should avoid the delay() function in your code or any other that interrupts the cycle by too much time. Consider using a second auxiliary Arduino for anything else than counting.
Resulting Serial Output:

TCP WiFi python-arduino communication problem

I try to steer a drone with a joytick from pc over WiFi. On board I have an Aruino like board called Particle Photon. I want to send 3 floats (pitch, roll, throttle) via TCP and I contocted this sort of thing:
On the PC - python - client side of things, it just sends this with 20 FPS/50ms frequency:
try:
s.sendall(bytearray(struct.pack("f", float(-20*axis0))))
s.sendall(bytearray(struct.pack("f", float(20*axis1))))
s.sendall(bytearray(struct.pack("f", float(axis2))))
except socket.error as e:
print("error while sending :: " + str(e))
where s is my socket.
On the server-drone side I have this:
if (myIMU.delt_t >= 25)
{
if (TCPcomms && client.connected()){
// Check for 12 bytes (3 floats) from joystick input --
// if we have them, update roll, pitch and throttle references
if(client.available() >= 12){
byte tempBuff[4];
float newInput[3];
for(int j = 0; j < 3; j++){
for(int i = 0; i < 4; i++)
{
tempBuff[i] = client.read();
}
newInput[j] = *((float*)(tempBuff));
}
roll_reference = newInput[0];
pitch_reference = newInput[1];
throttle_reference = newInput[2];
...
Drone loop works a lot faster hence the time check at the top (no need to check too often).
Now, I print-debugged it on slower speeds and everything seems fine. Whenever there are 3 floats ready, drone code reads them, if not it just continues.
But half the times I try to run it with normal speeds Particle board just checks out after some time and disconnects entirely. Apparently it does that whenever there is any problem, but it doesn't make a good job of communiocating what it is...
Earlier I also had a mechanism that disconnects after X seconds but it also dropepd connections often. I checked with Wireshark that it always coincided with retransmitting packets.
I guess my question is is it WiFi/TCP's fault and it's just not good for this type of a task or am I doing something stupid? Did anyone have a simillar issue with Arduino or Particle?

How to remove noise from PWM read from a radio receiver?

I am using a Remote Control from FlySky. For my robotics project, I want to read PWM from the receiver on an Arduino. I came across 2 options:
pulseIn() arduino function
ISR(PCINTx_vect) (interrupt)
I cant use the first option of pulseIn() because I want my robot to continue with the operation if receiver signal are not coming (Tx not available etc.) So I used ISR.
Most reliable source : Mr. Brookings channel on YouTube.
Here is what I did (Only the required part for 1 axis):
// [R] where R is defined as 0 => [R] == [0]
volatile long CH[4]; //4 pwms to read so array of 4
float IN[3]={0,0,0}; // throttle is directly written
unsigned long timer[4],curr_time;
byte last[4];
void setup(){
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << PCINT0);
PCMSK0 |= (1 << PCINT1);
PCMSK0 |= (1 << PCINT2);
PCMSK0 |= (1 << PCINT3);
/* There is some more code here */
Serial.begin(115200);
}
void loop(){
/* There is some more code here */
IN[R] = ((CH[ROLL] - (1500 + R_TRIM))/11.0); // eg.: (1200 - (1500 + 8))/11.0 = -28 (interpreted as setpoint of -28° by the robot)
Serial.println(IN[R]);
}
ISR(PCINT0_vect){
curr_time = micros();
//channel 1 roll
if(PINB & B00000001){
if(last[ROLL] == 0){
last[ROLL] = 1;
timer[ROLL] = curr_time;
}
}
else if(last[ROLL] == 1){
last[ROLL] = 0;
CH[ROLL] = ((curr_time - timer[ROLL]));
}
}
I can read the PWM actually, but the robot keeps showing random twitches in its control at a given set point. I managed to trace the reason and found out that the PWM is insanely ridden by noise. Its not stable like it should be - steady. I have a MATLAB plot I used for analysis:
Signal (IN[R]):
Close up (when Tx stick was in the middle w/o movement) :
There are such spikes coming which is adding up to the control signal eventually making my robot to twitch. I tried some filtering techniques like 'moving average' and '1st and 2nd order exponential filters'. Also checked if it was due to power supplied to it - tried putting a capacitor or an iron core to the power lines but in vain. I can figure out how to remove them as their some constrains :
platform is Arduino Uno (slower in heavy computation)
Control loop shall not go below 100Hz (Currently its at 108Hz exponential filters on 4 axes took it to
~85Hz)
I would appreciate some guidance!
There's no way of telling from this if the input is noisy, or if your code is reading the PWM wrong, of if something else is going on, like external noise on the line, the Arduino's clock jitter, or other interrupts taking time. Also note that micros() on an Arduino Uno only has a resolution of 4µs, not 1µs.
You should check the input for jitter and noise, and try fast code that isn't influenced by other interrupts.
A fairly simple and fast way of getting the PWM pulse width is something like this, preferably without using anything else that uses interrupts:
volatile int pwmPulseWidth = 0;
volatile unsigned long int previousTime = 0;
void setup() {
attachInterrupt(0, rising, RISING);
}
void loop() {
// pwmPulseWidth is available here.
}
void rising() {
attachInterrupt(0, falling, FALLING);
previousTime = micros();
}
void falling() {
attachInterrupt(0, rising, RISING);
pwmPulseWidth = micros() - previousTime;
}
Untested, but it should give you an idea. This will return the width of the PWM pulse.
There are other ways of doing this, of course, like using a timer in capture mode.
Knowing the PWM frequency and the width of the PWM pulse is enough to reconstruct the PWM signal, should you want to.

In Arduino, how do you write to port when port is a variable?

Examples of writing to a port seem to always use the port number as a constant, eg,
OCR2A = 180;
How do you write to the port when the port is unknown until run time. For example,
int port = (buttonPressed) ? 0x3b : 0x3c;
portWrite( port, 180 );
What I cannot find is the funtion portWrite(). Does something like that exist?
Robert's answer has some imprecise assertions and an incomplete answer.
Writing directly to port registers you can ruin other settings of the port and sometimes cause permanent damage to controller.
Can ruin other settings: true, you have to know what you are doing (for instance what pins are on the port you are manipulating, and know what are the functions you want to keep.
Can cause permanent damage: not really, or better not because of the port manipulation. If you wire a short circuit to ground and then set it as an output to 1, you can damage it whether you are using the port register or the digitalwrite. You have to be careful in both ways.
Now, returning to your problem, the enumeration is one way, but since the PORTB, PORTC, PORTD are just short name for values, you can set a variable and then use it to indirectly access it.
The type for this kind of variable is a volatile pointer to a byte (volatile means that write and read operations cannot be optimized by the compiler, since the value can change even between two operations):
volatile uint8_t *variablePortRegister;
You just have to load it with the address (so with the & symbol) of the register you want to change:
variablePortRegister = &PORTC;
then use the pointer to change the value
PORTC = 0x12;
becomes
(*variablePortRegister) = 0x12;
This is a short example. For it to work, connect a LED with resistor on arduino pin 5 (bit 5 of PORTD). The LED on the board (labeled L) is connected to pin 13 (bit 5 of PORTB).
The sketch will make one of the two leds blink for five times, then switch to the other. Only port manipulation instructions are used, and you can find that the way to read the port is the same as the one to write it.
volatile uint8_t *myportreg;
unsigned long lastTime;
uint8_t counter;
void setup() {
DDRB |= 0x20;
DDRD |= 0x20;
PORTB = 0;
PORTD = 0;
counter = 99; // trigger the register change immediately
}
void loop() {
if (counter >= 10)
{
counter = 0;
if (myportreg == &PORTD)
myportreg = &PORTB;
else
myportreg = &PORTD;
}
if ((millis() - lastTime) > 500)
{
lastTime = millis();
// change bit 5 of register
*myportreg = 0x20 ^ (*myportreg);
counter++;
}
}
EDIT: as Robert pointed out, it's much better to "use" just the pins you need (in this case, bit 5 of port B and D) rather than setting the whole port; this way you minimize the risk of screwing up something else. This edit is already included in the above code, so the code is correct
The port is a bit in one particular register. If you know the register and the position of the port in that particular register you can try this:
register = (1<<port) || register
to set the port to 1 and
register = (1<<port)^-1 && register
to set the port to 0.
Of course, you will need a switch somewhere to determine the register and the bit of the port in the register given the port name.

Power monitoring on a three phase system with Arduino Uno

Hello
I am currently working on a project, where I want to measure the voltage and current in a 3-phase system with an Arduino Uno.
This is a small schoolproject and I've had the necessary course on AC-systems to know about safety around higher voltages. I've also have a little bit experience with microcontroller but I've never used ADC.
I have a problem when reading from the analog pins of the Arduino Uno. It seems like the analog pins are mixed which i believe is called ghosting. I've been searching the internet for some answers to this matter, but the proposed solutions didn't work for me. I tried to make a dummy measurement and also to make a small time delay between measurements but since it's about power monitoring timing is critical. I need at minimum 20 readings which needs to be done in 20ms
To test the code I used two function generators. Is this even possible or allowed? Is it best to have at minimum a resistance in between and maybe a capacitor to remove noise?
Is there something in the circuit when transforming the voltage/current to be between 0V-5V there can be done to prevent this ghosting-effect?
I am using a voltagetransformer for the voltage and a Hall-effect sensor for the current. Both circuits need offset.
This is the code that makes the measurements.
void measure(char pin_volt, char pin_curr, int *volt_rms, int *curr_rms, float *theta){
int i;
long squared_v, squared_c, sum_squared_v = 0, sum_squared_c = 0, inst_v, inst_c, mean_squared_v, mean_squared_c;
unsigned long time_v, time_c;
for(i = 0; i < samples; i++){
inst_v = analogRead(pin_volt) - volt_offset;
if(inst_v > -volt_varying && inst_v < volt_varying) {
time_v = micros();
}
inst_c = analogRead(pin_curr) - curr_offset;
if(inst_c >= -curr_varying && inst_c <= curr_varying) {
time_c = micros();
}
squared_v = inst_v * inst_v;
squared_c = inst_c * inst_c;
sum_squared_v += squared_v;
sum_squared_c += squared_c;
delayMicroseconds(80);
}
mean_squared_v = sum_squared_v / samples;
mean_squared_c = sum_squared_c / samples;
*volt_rms = sqrt(mean_squared_v);
*curr_rms = sqrt(mean_squared_c);
*theta = calculate_phase_difference(time_v,time_c);
}
Adding a capacitor can lower the problem.
Try to do the following:
No current or tension on the circuit, so the arduino should measure 0 values.
Run a sketch that reads values and prints max and min values to serial monitor; you will see that values will not be zero as expected, those are interferences.
Try and find a capacitor that can lower those values but don't exagerate.

Resources