AM And Cheap Encoders

Support for Arduino in combination with Air Manager and Air Player

Moderators: russ, Ralph

Post Reply
Message
Author
jedeen
Posts: 527
Joined: Fri Jan 13, 2017 5:41 pm

AM And Cheap Encoders

#1 Post by jedeen »

Hello Cockpitbuilders.

As we all now, building a cockpit is an or can be an expensive project.
One of the things that is expensive are the rotary encoders and dual rotary encoders.
I have tried the last few weeks to use simple and cheap rotary's with simple code........with no luck.
After a survey on the net I found a script that could be a solution.
I have made a PDF for this reason to share my findings with you.
In that PDF is the code for the Arduino Mega and for the Air Manager 3.3.
Cheap Encoder for Arduino and Air Manager.pdf
(1.23 MiB) Downloaded 241 times
Below is the same code:
For the Arduino:

Code: Select all

/*******Interrupt-based Rotary Encoder Sketch*******
by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence
Parts of the original code are copied below and changed by Jedeen for use with AM
*/


#include <SevenSeg.h>
#include <si_message_port.hpp>
#include <si_message_port.h>

// constants
SevenSeg disp(30, 31, 32, 33, 34, 35, 36); //only the segments should be here, without the DP 

const int numOfDigits=3; //number of digits for the display, max is 9
int digitPins[numOfDigits]={53, 51, 49};   //digit one on 53, digit 2 on 51 and digit 3 on pin 49
int cap_course = 0;

static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile int encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile int oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile int reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

SiMessagePort* messagePort;

static void new_message_callback(uint16_t message_id, struct SiMessagePortPayload* payload) 
{
  // Do something with a message from Air Manager or Air Player

  if (payload == NULL) 
  {
    messagePort->DebugMessage(SI_MESSAGE_PORT_LOG_LEVEL_INFO, (String)"Received without payload");
  }
  else 
  {
    switch(payload->type) 
    {
      case SI_MESSAGE_PORT_DATA_TYPE_BYTE:
        messagePort->DebugMessage(SI_MESSAGE_PORT_LOG_LEVEL_INFO, (String)"Received " + payload->len + " bytes: " + payload->data_byte[0]);
        break;
      case SI_MESSAGE_PORT_DATA_TYPE_STRING:
        messagePort->DebugMessage(SI_MESSAGE_PORT_LOG_LEVEL_INFO, (String)"Received string: " + payload->data_string);
        break;
      case SI_MESSAGE_PORT_DATA_TYPE_INTEGER:
        messagePort->DebugMessage(SI_MESSAGE_PORT_LOG_LEVEL_INFO, (String)"Received " + payload->len + " integers" + payload->data_int[0]);
        break;
      case SI_MESSAGE_PORT_DATA_TYPE_FLOAT:
        messagePort->DebugMessage(SI_MESSAGE_PORT_LOG_LEVEL_INFO, (String)"Received " + payload->len + " floats" + payload->data_float[0]);
        break;
    }
  }
}

void setup() 
{
  // Init library on channel A and Arduino type MEGA 2560
  messagePort = new SiMessagePort(SI_MESSAGE_PORT_DEVICE_ARDUINO_MEGA_2560, SI_MESSAGE_PORT_CHANNEL_A, new_message_callback);

  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, uses built in pullup
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, uses built in pullup
  attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  
  disp.setDigitPins(numOfDigits, digitPins);
  disp.setCommonCathode();  //if you have a common anode change this to disp.setCommonAnode();
  disp.setDPPin (37);   //on this pin there is the decimal point
  //disp.setDutyCycle(40);  //for dimming the displays max = 100%
  //Serial.begin(115200); // start the serial monitor link
}

void PinA()
{
  cli(); //stop interrupts happening before we read pin values
  reading = PINE & 0x30; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00110000 && aFlag) //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  { 
    encoderPos --; //decrement the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00010000) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

void PinB()
{
  cli(); //stop interrupts happening before we read pin values
  reading = PINE & 0x30; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00110000 && bFlag) //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  { 
    encoderPos ++; //increment the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00100000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
  sei(); //restart interrupts
}


void loop() 
{
  // Make sure this function is called regularly
  messagePort->Tick();

  if(oldEncPos != encoderPos) 
  {
	//uint8_t payload = encoderPos;
    messagePort->SendMessage(cap_course, (int32_t) encoderPos);  
    //Serial.println(encoderPos);
    oldEncPos = encoderPos;
  }
  disp.write(encoderPos);	//write the complete value to the displays
   
}
And the code for AM3.3:

Code: Select all

-- This function will be called when a message is received from the Arduino.
function new_message(cap_course, payload)
  -- Do something with the message from the Arduino
--print("received new message with id " .. id .. " and payload saying " .. payload)
	xpl_dataref_write("sim/cockpit/radios/nav1_obs_degm", "FLOAT", payload)
end

cap_course = hw_message_port_add("ARDUINO_MEGA2560_A", new_message)
I have also made a video to show the above code in action, you can find the video here:
https://drive.google.com/open?id=1fB_jk ... xAjPwVKr5f

Have fun
Jedeen
Last edited by jedeen on Fri Jul 13, 2018 3:31 pm, edited 3 times in total.

JackZ
Posts: 2264
Joined: Mon Feb 22, 2016 1:02 pm

Re: AM And Cheap Encoders

#2 Post by JackZ »

Nice contributive work Jeeden, very much appreciated.
Will have a look at it tonight and give you some feedback if needed.

Jacques
My YouTube Chanel on the A320 (Real SOPs by an Airline Pilot IRL):
https://www.youtube.com/playlist?list=P ... 0Q6SBASRqJ

jedeen
Posts: 527
Joined: Fri Jan 13, 2017 5:41 pm

Re: AM And Cheap Encoders

#3 Post by jedeen »

No problems Jacques.

But keep the following in mind:
I used a normal INT for the "encoderPos", that means that also negative numbers can be seen.
I did not give a MAX for the shown numbers, that means that the numbers are going higher then 360.
It's only a simple up- down counter ;)
If you want to use DOUBLE or LONG in this script than keep in mind that you should close/inhibit the interrupt first
and then read out the numbers. If you don't close the interrupt first than you have the chance that
the second half of the value has already changed before you have completed the read out.

Have fun
Jedeen

jedeen
Posts: 527
Joined: Fri Jan 13, 2017 5:41 pm

Re: AM And Cheap Encoders

#4 Post by jedeen »

Sorry guys,

I did found an error in the Arduino and AM code :oops:
I forgot to declare cap_course and forget to use that new id in the AM code
So ad in line 16 from the Arduino code the following........int cap_course = 0;
Replace the word "id" in the AM code with cap_course.
That's all. The error in the above code is repaired but not in the .pdf
So you can download the code above and all should work.

Have fun
Jedeen
Last edited by jedeen on Thu Jul 12, 2018 8:16 pm, edited 1 time in total.

User avatar
Corjan
Posts: 2939
Joined: Thu Nov 19, 2015 9:04 am

Re: AM And Cheap Encoders

#5 Post by Corjan »

Hi,


It might be nice to add support for regular 8-segment displays out of the box.

Would probably be something along these lines:

Code: Select all

-- Bind to the Arduino MEGA 2560 on channel A.
-- 3 characters
-- hw_id 1, 2, 3 : character cathode pins
-- hw_id 4 .. 12 : segment anode pins
display_chr_id = hw_chr_display_add("8SEGMENT", 3, "ARDUINO_MEGA2560_A_D4", "ARDUINO_MEGA2560_A_D5", "ARDUINO_MEGA2560_A_D6", ...)

-- Set text "123" on display
hw_chr_display_set_text(display_chr_id, "123")
This would be a nice addition to AM 3.5 I feel. It would make your code a bit unnecessary, sorry for that!


Corjan

jedeen
Posts: 527
Joined: Fri Jan 13, 2017 5:41 pm

Re: AM And Cheap Encoders

#6 Post by jedeen »

Hi Corjan
:( :( :( :( :( :( :( :( :( :( :( :( .....kidding
It would be nice if that could be incorporated in 3.5.
I made this code because in an earlier communication we talked about time critical events in AM and that it would
be better to do such things in the code for the Arduino.
And as it turns out, that was indeed the bottleneck. Turning cheap encoders very fast resulted in loss of numbers.
And this code prevents that.

So support for regular 6 x 7segments displays or 3 x 7segments displays is fine.......it will make things simpler for the user ;)
But one has still to deal with those cheap encoders....so part of "my" code will still be useful :)

Have fun
Jedeen

jedeen
Posts: 527
Joined: Fri Jan 13, 2017 5:41 pm

Re: AM And Cheap Encoders

#7 Post by jedeen »

Hey Guys,

I just replaced the .pdf in my post.
The old one was already downloaded 13 times, but there were some errors in it.
I corrected the errors and uploaded a new .pdf.

The next update will be how to change the bitmask's when you want to use other inputs for interrupt, like pin 18 and 19.
Also at the same time how to connect a 6 x 7 segments displays and 2 encoders, for a NAV1 panel.

Have fun
Jedeen

Post Reply