Jump to content

NLCbanner2024.jpg.2478be509670e60c2d6efd04834b8b47.jpg

Bahtinov Mask Automation


JSeaman

Recommended Posts

 

I finally got around to doing something I have wanted for ages, automating the Bahtinov mask on my ED80. 

I have tried the various autofocus routines but always got better results with a mask so wanted to stick with this method but without the back and forth of going outside to use it. I finally got around to designing a solution and it looks like this:

image.png.cc219b972392889fe55dff4b1bbf17ac.png

 

I have a bash script running on the raspberry pi 4b which toggles a GPIO for 1 second:

    #!/bin/bash
    gpio -g mode 4 out
    gpio -g write 4 0
    sleep 1s
    gpio -g mode 4 in
 

Although this is a 3.3V system, the IO pin is connected to an Arduino Uno's (5V) input which still sees logic high at 3.3V. I use the Arduino's internal weak pull up on the input and same on the Pi's output pin then pull to ground for 1 second to trigger it.

The Arduino will go to the open position if it is anywhere else, and moves to the close position if it is in the open position (so in normal use it opens or closes as a toggle). The code is fairly simple:

  
  #include <Stepper.h>
  #include <EEPROM.h>

//-------------------------------------------------------------------------------------------

  #define               STEPS_PER_REVOLUTION      2038                        //Resolution of the stepper motor
  #define               STEPS_PER_DEGREE          6                           //Number of steps to move ~1 degree (actual 5.6)
  #define               STEPPER_IN_1              8                           //Stepper motor control IO
  #define               STEPPER_IN_2              9                           //Stepper motor control IO
  #define               STEPPER_IN_3              10                          //Stepper motor control IO
  #define               STEPPER_IN_4              11                          //Stepper motor control IO
  
  #define               JOYSTICK_X                A0                          //Analogue input for the X position of the joystick
  #define               JOYSTICK_Y                A1                          //Analogue input for the Y position of the joystick
  #define               JOYSTICK_BUTTON           7                           //Digital pin to read the joystick button state from 
  
  #define               BUZZER_FREQUENCY          250                         //Frequency in Hz of the buzzer for beeps
  #define               BUTTON_FREQUENCY          2500                        //Frequency in Hz of the button press 
  #define               BUZZER_PIN                12                          //Pin used to drive a buzzer
  
  #define               IDLE_STATE                0                           //States for using the joystick button
  #define               WAITING_FOR_OPEN_POSITION  1
  #define               WAITING_FOR_CLOSED_POSITION 2
  
  #define               BUTTON_TIMEOUT_PERIOD     30000                       //Timeout when saving open/close position
  
  #define               BUTTON_PIN                6                           //Pin to use for the open/close triggering
  #define               RELAY_PIN                 5                           //Pin to drive the relay for the motor

  #define               SAVE_TIMEOUT              5000                        //Save 5 seconds after the last movement

//-------------------------------------------------------------------------------------------
  
  Stepper StepperMotor = Stepper(STEPS_PER_REVOLUTION, STEPPER_IN_1, STEPPER_IN_3, STEPPER_IN_2, STEPPER_IN_4);
  int JOYSTICK_STATE_MACHINE = IDLE_STATE;
  int CURRENT_POSITION = 0;
  int OPEN_POSITION = 0;
  int CLOSED_POSITION = 1019;
  long BUTTON_TIMEOUT = 0;
  long SAVE_TIMER=0;

//-------------------------------------------------------------------------------------------

void setup()
{
  Serial.begin (115200);
  Serial.println ("Starting up ...");

  //Load any previous positional settings
  ReadEEPROM();

  //Set up the joystick, buzzer, switch and I/O
  pinMode (JOYSTICK_BUTTON, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RELAY_PIN, OUTPUT);

  Serial.println ("Setting motor speed");
  StepperMotor.setSpeed(5);
}

//-------------------------------------------------------------------------------------------

void loop()
{
  //If there were no joystick movements to deal with
  if (HandleJoystick()==false)
  {
    //Handle the joystick button or switch presses
    HandleButton();
  }

  //If our timer is set to save settings after movement
  if (SAVE_TIMER!=0)
  {
    //If the timer has expired (a time passed since we last moved)
    if (millis()>=SAVE_TIMER)
    {
      //Save settings
      WriteEEPROM();
      
      //Cancel the timer
      SAVE_TIMER=0;

      Serial.println ("Depowering relay");
    
      //Drop the relay so we don't power the motor any more
      digitalWrite (RELAY_PIN, LOW);
    }
  }

}

//-------------------------------------------------------------------------------------------

void PowerRelay()
{
  Serial.print ("millis: ");
  Serial.println (millis());
  Serial.print ("SAVE_TIMER: ");
  Serial.println (SAVE_TIMER);
  
  //If we aren't already powered the relay
  if (millis()>=SAVE_TIMER)
  {
    Serial.println ("Powering relay");
    //Switching on the relay
    digitalWrite (RELAY_PIN, HIGH);
  }
}

//-------------------------------------------------------------------------------------------

bool HandleJoystick()
{  
  bool ReturnValue=false;
  int XPos = 0, YPos = 0;
  long Timeout = 0;

  //Read the X and Y positions of the joystick
  XPos = analogRead(JOYSTICK_X);
  YPos = analogRead(JOYSTICK_Y);

  //If they are moving us in the close direction
  if (XPos <= 400 || YPos >= 600)
  {
    PowerRelay();

    StepperMotor.step(-STEPS_PER_DEGREE);
    CURRENT_POSITION -= STEPS_PER_DEGREE;

    //We will save settings a number of mS after the last movement
    SAVE_TIMER=millis()+SAVE_TIMEOUT;
    Serial.println ("Saving in 5 seconds due to negative joystick movement");
  
    //Return there was a movement
    ReturnValue=true;

    Serial.print ("Current position : ");
    Serial.println (CURRENT_POSITION);
  }
  //If they are moving us in the open direction
  else if (XPos >= 600 || YPos <= 400)
  {
    PowerRelay();

    StepperMotor.step(STEPS_PER_DEGREE);
    CURRENT_POSITION += STEPS_PER_DEGREE;

    //We will save settings a number of mS after the last movement
    SAVE_TIMER=millis()+SAVE_TIMEOUT;
    Serial.println ("Saving in 5 seconds due to positive joystick movement");

    //Return there was a movement
    ReturnValue=true;
    
    Serial.print ("Current position : ");
    Serial.println (CURRENT_POSITION);
  }

  return ReturnValue;
}

//-------------------------------------------------------------------------------------------

void HandleButton()
{
  int ButtonPos=0;
  long Timeout = 0;
  ButtonPos = digitalRead (JOYSTICK_BUTTON);

  //If joystick the button is pressed
  if (ButtonPos == 0)
  {
    //Give up to 2 seconds to let go
    Timeout = millis() + 2000;
    do
    {
      delay(20);
      ButtonPos = digitalRead (JOYSTICK_BUTTON);
    }
    //Loop while the button is held and timeout hasn't happened
    while (ButtonPos == 0 && Timeout > millis());
  
    //If they released it
    if (ButtonPos == 1)
    {
      HandleStateMachine();
    }
    else
    {
      Serial.println ("Button held too long, ignoring");
    }
  }
  else
  {
    //If we are part way through saving positions
    if (JOYSTICK_STATE_MACHINE != IDLE_STATE)
    {
      //If timeout occurred
      if (millis() >= BUTTON_TIMEOUT)
      {
        Serial.println ("Button timeout occurred");
        JOYSTICK_STATE_MACHINE = IDLE_STATE;

        for (int Counter = 0; Counter < 10; Counter++)
        {
            Beep(BUZZER_FREQUENCY, 50);
            delay(50);
        }
      }
    }

    HandlePushButton();
  }
}

//-------------------------------------------------------------------------------------------

void HandleStateMachine()
{
  switch (JOYSTICK_STATE_MACHINE)
  {
    case IDLE_STATE:
      {
        Serial.println ("Button pressed from idle, set open position");
        JOYSTICK_STATE_MACHINE = WAITING_FOR_OPEN_POSITION;

        //Beep once to show we are starting the process
        Beep(BUZZER_FREQUENCY, 50);

        //20 second timeout starts ticking
        BUTTON_TIMEOUT = millis() + BUTTON_TIMEOUT_PERIOD;

        break;
      }
    case WAITING_FOR_OPEN_POSITION:
      {
        OPEN_POSITION = CURRENT_POSITION;
        Serial.println ("Open position set, set closed position");

        //Beep twice to save the open position
        Beep(BUZZER_FREQUENCY, 50);
        delay(50);
        Beep(BUZZER_FREQUENCY, 50);

        JOYSTICK_STATE_MACHINE = WAITING_FOR_CLOSED_POSITION;

        //20 second timeout starts ticking
        BUTTON_TIMEOUT = millis() + BUTTON_TIMEOUT_PERIOD;

        break;
      }
    case WAITING_FOR_CLOSED_POSITION:
      {
        CLOSED_POSITION = CURRENT_POSITION;

        Serial.println ("Closed position set");
        JOYSTICK_STATE_MACHINE = IDLE_STATE;

        //Thress beeps to complete
        tone (BUZZER_PIN, BUZZER_FREQUENCY);
        delay(50);
        noTone (BUZZER_PIN);
        delay(50);
        tone (BUZZER_PIN, BUZZER_FREQUENCY);
        delay(50);
        noTone (BUZZER_PIN);
        delay(50);
        tone (BUZZER_PIN, BUZZER_FREQUENCY);
        delay(50);
        noTone (BUZZER_PIN);

        Serial.println ("Saving positions");
        WriteEEPROM();

        break;
      }
    //Unexpected
    default:
      {
        Serial.print ("Weird state: ");
        Serial.print (JOYSTICK_STATE_MACHINE);
        Serial.println ("");
        JOYSTICK_STATE_MACHINE = IDLE_STATE;

        for (int Counter = 0; Counter < 10; Counter++)
        {
          tone (BUZZER_PIN, BUZZER_FREQUENCY);
          delay(50);
          noTone (BUZZER_PIN);
          delay(50);
        }

        break;
      }
  }
}

//-------------------------------------------------------------------------------------------

void HandlePushButton ()
{
  int ButtonState=0;
  long Timeout=0;
  
  //Joystick is dealt with, now check the push button
  ButtonState = digitalRead (BUTTON_PIN);

  //If the button is pressed
  if (ButtonState==0)
  {
    //Give up to 2 seconds to let go
    Timeout = millis() + 2000;
    do
    {
      delay(20);
      ButtonState = digitalRead (BUTTON_PIN);
    }
    //Loop while the button is held and timeout hasn't happened
    while (ButtonState == 0 && Timeout > millis());
    Serial.println (ButtonState);

    //Valid press, they released it 
    if (ButtonState==1)
    {
      Serial.println ("Button pressed!");
      Beep(BUTTON_FREQUENCY, 50);

      Serial.print ("Current: ");
      Serial.println (CURRENT_POSITION);

      Serial.print ("Open: ");
      Serial.println (OPEN_POSITION);
      Serial.print ("Closed: ");
      Serial.println (CLOSED_POSITION);

      if (CURRENT_POSITION==OPEN_POSITION)
      {
        Serial.println ("Closing");
        Close();
      }
      //Default to opening, whether in an intermediate or open position 
      else
      {
        Serial.println ("Opening");
        Open();
      }
    }
    else
    {
      //Beep 3 times to show this is a reset
      Beep(BUTTON_FREQUENCY, 50);
      delay (100);
      Beep(BUTTON_FREQUENCY, 50);
      delay (100);
      Beep(BUTTON_FREQUENCY, 50);

      //Reset to defaults
      CURRENT_POSITION = 0;
      OPEN_POSITION = 0;
      CLOSED_POSITION = 0;

     Serial.println ("Saving in 5 seconds due to reset");
 
     //Flag that we need to save settings
      SAVE_TIMER=millis()+SAVE_TIMEOUT;
    }
  }
}

//-------------------------------------------------------------------------------------------

void Beep (int Frequency, int Duration)
{
  tone (BUZZER_PIN, Frequency);
  delay(Duration);
  noTone (BUZZER_PIN);
}

//-------------------------------------------------------------------------------------------

void WriteEEPROM()
{
  Serial.print ("Writing location 0 with ");
  Serial.println (OPEN_POSITION>>8);
  EEPROM.write(0, OPEN_POSITION>>8);
  Serial.print ("Writing location 1 with ");
  Serial.print (OPEN_POSITION&0xff);
  EEPROM.write(1, OPEN_POSITION&0xff);
  Serial.print (" (");
  Serial.print (OPEN_POSITION);
  Serial.println (")");

  Serial.print ("Writing location 2 with ");
  Serial.println (CLOSED_POSITION>>8);
  EEPROM.write(2, CLOSED_POSITION>>8);
  Serial.print ("Writing location 3 with ");
  Serial.print (CLOSED_POSITION&0xff);
  EEPROM.write(3, CLOSED_POSITION&0xff);
  Serial.print (" (");
  Serial.print (CLOSED_POSITION);
  Serial.println (")");
  
  //Flag to show we have written EEPROM
  EEPROM.write(4, 0xAA);

  Serial.print ("Writing location 5 with ");
  Serial.println (CURRENT_POSITION>>8);
  EEPROM.write(5, CURRENT_POSITION>>8);
  Serial.print ("Writing location 6 with ");
  Serial.print (CURRENT_POSITION&0xff);
  EEPROM.write(6, CURRENT_POSITION&0xff);
  Serial.print (" (");
  Serial.print (CURRENT_POSITION);
  Serial.println (")");
}

//-------------------------------------------------------------------------------------------

void ReadEEPROM()
{
  byte ErrorFlag=0;
  
  //Flag to show we have written EEPROM
  ErrorFlag=EEPROM.read(4);
  
  if (ErrorFlag==0xAA)
  {
    Serial.println ("EEPROM initialised");
    
    OPEN_POSITION = (int)(EEPROM.read(0)<<8) + EEPROM.read(1);
    Serial.print ("EEPROM locations 1 and 2 = ");
    Serial.println (OPEN_POSITION);
  
    CLOSED_POSITION = (int)(EEPROM.read(2)<<8) + EEPROM.read(3);
    Serial.print ("EEPROM locations 2 and 3 = ");
    Serial.println (CLOSED_POSITION);
  
    CURRENT_POSITION = (int)(EEPROM.read(5)<<8) + EEPROM.read(6);
    Serial.print ("EEPROM locations 5 and 6 = ");
    Serial.println (CURRENT_POSITION);
  }
  else
  {
    Serial.println ("EEPROM uninitialised");
  }
}

//-------------------------------------------------------------------------------------------

void Open()
{
  if (CURRENT_POSITION<OPEN_POSITION)
  {
    PowerRelay();
    while (CURRENT_POSITION<OPEN_POSITION)
    {
      StepperMotor.step(1);
      CURRENT_POSITION++;
      SAVE_TIMER=millis()+SAVE_TIMEOUT;
      Serial.println ("Saving in 5 seconds due to positive open occurring");
    }
  }
  else if (CURRENT_POSITION>OPEN_POSITION)
  {
    PowerRelay();
    while (CURRENT_POSITION>OPEN_POSITION)
    {
      StepperMotor.step(-1);
      CURRENT_POSITION--;
      SAVE_TIMER=millis()+SAVE_TIMEOUT;
      Serial.println ("Saving in 5 seconds due to negative open occurring");
    }
  }
}

//-------------------------------------------------------------------------------------------

void Close()
{
  if (CURRENT_POSITION<CLOSED_POSITION )
  {
    PowerRelay();
    while (CURRENT_POSITION<CLOSED_POSITION )
    {
      StepperMotor.step(1);
      CURRENT_POSITION++;
      SAVE_TIMER=millis()+SAVE_TIMEOUT;
      Serial.println ("Saving in 5 seconds due to positive close occurring");
   }
  }
  else if (CURRENT_POSITION>CLOSED_POSITION )
  {
    PowerRelay();
    while (CURRENT_POSITION>CLOSED_POSITION )
    {
      StepperMotor.step(-1);
      CURRENT_POSITION--;
      SAVE_TIMER=millis()+SAVE_TIMEOUT;
      Serial.println ("Saving in 5 seconds due to negative close occurring");
    }
  }
}

The I/O pin is multiplexed to a push button which I use in the video below to trigger an open/close event so I can toggle it outside if needed. There is also a joystick attached which allows me to move the mask manually to save open/close positions respectively which are written to the Arduino EEPROM. The stepper motor is attached to the Bahtinov mask through two grub screws I drilled and tapped in some solid bar which is in turn connected to the 3D frame

 

 

The whole thing is powered by a 5V 30A supply with three outputs, this allows me to switch in the stepper motor via a relay to prevent the holding torque of the motor causing things to heat up. I tested it all last night and it worked perfectly, now to print a housing and automate the supply switch on.

The mask itself is an 80mm diameter circle with 20 degree slits and 2mm equispaced cuts which I had to make because, in the process of making this, I dropped my existing mask and broke it, happy to say this one works well!

James
 

  • Like 2
Link to comment
Share on other sites

  • 4 weeks later...
11 hours ago, JSeaman said:

Hi, for focus I use Kstars/Ekos. The autofocus with Bahtinov Mask is a bit hit and miss so I tend to do that myself

I love the way you have got the mask to be placed and removed. 👍

Are you finding that the Ekos focus module is NOT giving you the right focus without Bahtinov?

Link to comment
Share on other sites

I was finding the Ekos focus was very erratic, simply running it over and over gave me different results every time. I also found that it rarely met the focus that the Bahtinov showed so it's more reliable

Link to comment
Share on other sites

13 hours ago, JSeaman said:

I was finding the Ekos focus was very erratic, simply running it over and over gave me different results every time. I also found that it rarely met the focus that the Bahtinov showed so it's more reliable

Its worth posting your settings on the INDI forum so others maybe able to help you. The Focus module has been improved considerably in the last few months so it will be good feedback to the author(s).

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. By using this site, you agree to our Terms of Use.