Software Overview  |  Sitemap  |  Downloads  |  Developers  |  Forums
Small_clear_logo_llc

Omniwheel Bot Program for a Bot with 2 Joysticks

###############################################################################
# Description: Virtual Device Module that controls an Omniwheel
# Bot using a joystick.  Assumes a Wicked Device V1 Motor Shield or an
# Adafruit V2 Motor Shield.
# Modified original code so uses a second joystick - causes bot to rotate.
###############################################################################
require "thread"

module VdOmniWheelBotController

  private

  #############################################################################
  # Create and return the thread that will control the Omniwheel Bot.
  # This thread polls the event queue and uses the events it finds to control
  # the Bot.
  #############################################################################
  def bot_thread
    return Thread.new do
      begin
        @x1, @y1, @x2, @y2 = nil,nil, nil, nil # init joystick values

        # main loop - process joystick events and control the Bot
        loop do
          event = @event_queue.deq
          if val = event["joy_x"]
            @x1 = val
          elsif val = event["joy_y"]
            @y1 = val
          # check for values from our 2nd joystick
          elsif val = event["joy2_x"]
            @x2 = val
          elsif val = event["joy2_y"]
            # we don't use this value - available for a new function
            @y2 = val
          else
            p "unexpected event: #{event.inspect}"
          end
          # if have all x & ys from joysticks, can run things
          if @x1 && @y1 && @x2
            x1, y1, x2 = normalize(@x1, @y1, @x2)
            # set the motor speeds
            set_speed(x1, y1, x2)
          end
        end
      rescue Exception
        # say why we crashed in the log
        p "Error: bot thread has terminated: #{$!}"
      end
    end
  end

  #############################################################################
  # Takes raw joystick values and normalizes them to work with the Bot.
  # This Bot code wants pwm values in the range -100 to +100.
  # Uses the first samples to calibrate the joysticks.
  #############################################################################
  def normalize(x1, y1, x2)
    if !@calibrated
      @calibrated = true
      @x1_center, @y1_center, @x2_center = x1, y1, x2
    end
    # subtract out the calibration
    x1, y1, x2 = (x1 - @x1_center), (y1 - @y1_center), (x2 - @x2_center)
    # ignore small deviations when idling (noise)
    (x1.abs == 1) && (x1 = 0)
    (y1.abs == 1) && (y1 = 0)
    (x2.abs == 1) && (x2 = 0)
    # scale value so full range and return the result
    [4 * x1, 4 * y1, 4 * x2]
  end

  #############################################################################
  # Set the bot's speed and direction based on an (x1,y1,x2) vector.  x2 is
  # the x coordinate of a 2nd joystick - we use it for rotation.
  # Works for Wicked Motor Shield.
  # Speed magnitude will be between 0 and 100.
  #############################################################################
  def wickeddevice_set_speed(x1,y1,x2)
    # set the shift_reg and m1-3 variables
    # initialize to hard stop
    shift_reg = 0xffff
    m1 = m2 = m3 = 0
    # see if moving
    if x1 != 0 || y1 != 0 || x2 != 0
      # moving
      angle, mag = get_angle_and_mag(x1, y1)

      # get motor speeds, given speed and direction (angle)
      # add in rotation
      m1 = (Math.cos(angle) * mag).round - x2
      m2 = (Math.cos(angle + Math::PI*2/3) * mag).round - x2
      m3 = (Math.cos(angle + Math::PI*4/3) * mag).round - x2

      # set direction in the shift register - on Wicked Device Motor Shield
      shift_reg = 0
      shift_reg |= 0x2000 if m1 < 0
      shift_reg |= 0x800 if m2 < 0
      shift_reg |= 0x200 if m3 < 0

      # make motor speeds positive integers
      m1 = m1.abs
      m2 = m2.abs
      m3 = m3.abs
    end

    # send information to the bot, if it's changed
    generate_event("shift_reg" => shift_reg) if @shift_reg != shift_reg
    generate_event("m1_pwm"=>m1) if m1 != @m1_pwm
    generate_event("m2_pwm"=>m2) if m2 != @m2_pwm
    generate_event("m3_pwm"=>m3) if m3 != @m3_pwm

    # remember what we've sent
    @shift_reg = shift_reg
    @m1_pwm = m1
    @m2_pwm = m2
    @m3_pwm = m3
  end

  #############################################################################
  # Set the bot's speed and direction based on an (x1,y1,x2) vector.  x2 is
  # the x coordinate of a 2nd joystick - we use it for rotation.
  # Works for Adafruit Motor Shield.
  # Speed magnitude will be between 0 and 100.
  #############################################################################
  def adafruit_set_speed(x1,y1,x2)
    # set the shift_reg and m1-3 variables
    # init with hard stop
    m1_pwm, m1_in1, m1_in2 = 0, "off", "off"
    m2_pwm, m2_in1, m2_in2 = 0, "off", "off"
    m3_pwm, m3_in1, m3_in2 = 0, "off", "off"
    # see if moving
    if x1 != 0 || y1 != 0 || x2 != 0
      angle, mag = get_angle_and_mag(x1, y1)

      # get motor speeds, given speed and direction (angle)
      # add in rotation
      m1 = (Math.cos(angle) * mag).round - x2
      m2 = (Math.cos(angle + Math::PI*2/3) * mag).round - x2
      m3 = (Math.cos(angle + Math::PI*4/3) * mag).round - x2

      # set direction
      m1_in1, m1_in2 = "on", "off" if m1 > 0
      m1_in1, m1_in2 = "off", "on" if m1 < 0
      m2_in1, m2_in2 = "on", "off" if m2 > 0
      m2_in1, m2_in2 = "off", "on" if m2 < 0
      m3_in1, m3_in2 = "on", "off" if m3 > 0
      m3_in1, m3_in2 = "off", "on" if m3 < 0
      # set speeds
      m1_pwm, m2_pwm, m3_pwm = m1.abs, m2.abs, m3.abs
    end

    # update terminals, if changes
    generate_event("m1_pwm" => m1_pwm) if m1_pwm != @m1_pwm
    generate_event("m1_in1" => m1_in1) if m1_in1 != @m1_in1
    generate_event("m1_in2" => m1_in2) if m1_in2 != @m1_in2
    generate_event("m2_pwm" => m2_pwm) if m2_pwm != @m2_pwm
    generate_event("m2_in1" => m2_in1) if m2_in1 != @m2_in1
    generate_event("m2_in2" => m2_in2) if m2_in2 != @m2_in2
    generate_event("m3_pwm" => m3_pwm) if m3_pwm != @m3_pwm
    generate_event("m3_in1" => m3_in1) if m3_in1 != @m3_in1
    generate_event("m3_in2" => m3_in2) if m3_in2 != @m3_in2

    # remember
    @m1_pwm, @m1_in1, @m1_in2 = m1_pwm, m1_in1, m1_in2
    @m2_pwm, @m2_in1, @m2_in2 = m2_pwm, m2_in1, m2_in2
    @m3_pwm, @m3_in1, @m3_in2 = m3_pwm, m3_in1, m3_in2
  end

  #############################################################################
  # Get and angle and magnitude vector given an (x,y) point.
  # Returns an array with the angle and magnitude
  #############################################################################
  def get_angle_and_mag(x, y)
    angle = Math.atan2(y,x)
    # get magnitude
    original_mag = (x**2 + y**2)**(0.5)

    # normalize joystick to a circle, given x/y's follow a square
    new_a = angle.abs
    new_a = Math::PI - new_a if new_a >= Math::PI/2
    new_a = Math::PI/2 - new_a if new_a >= Math::PI/4
    mag = [original_mag * Math.cos(new_a), 100].min # 100 max
    [angle, mag]
  end


  #############################################################################
  # Virtual device method for processing terminal events.
  # Inputs are a hash where keys are terminal names and values are terminal
  # values.
  # Keep this method short and fast, so we don't hold up the callback thread.
  #
  # Return value must be an event hash.  Return an empty hash if no event,
  # and a terminal/value hash (like the inputs hash) for creating events.
  #############################################################################
  def respond_to_inputs(inputs, outputs = {})

    # put all events of interest into our event queue
    # bot thread will read them out when it can
    inputs.each do |key, val|
      if ["joy_x", "joy_y", "joy2_x", "joy2_y"].include?(key)
        @event_queue.enq({key=>val})
      else
        if terminal_dids.include?(key)
          # ok, we generated something we don't care about
        else
          print "Unexpected input: #{key.inspect}\n" # log this
        end
      end
    end

    outputs # return nothing - no events generated from here
  end

  #############################################################################
  # Define the terminals that a Wicked Device Shield uses.
  #############################################################################
  def wickeddevice_terminal_dids
    %W(joy_x joy_y joy2_x joy2_y m1_pwm m2_pwm m3_pwm shift_reg)
  end

  #############################################################################
  # Define the terminals that an Adafruit Shield uses.
  #############################################################################
  def adafruit_terminal_dids
    %W(joy_x joy_y joy2_x joy2_y
      m1_pwm m1_in1 m1_in2
      m2_pwm m2_in1 m2_in2
      m3_pwm m3_in1 m3_in2)
  end

  #############################################################################
  # Set up for an Adafruit motor shield.
  # Define Adafruit specific methods as generic ones.
  #############################################################################
  def adafruit_shield_init
    class << self
      alias set_speed adafruit_set_speed
      alias terminal_dids adafruit_terminal_dids
    end
    controller_init()
  end

  #############################################################################
  # Set up for a Wicked Device motor shield.
  # Define Wicked Device specific methods as generic ones.
  #############################################################################
  def wickeddevice_shield_init
    class << self
      alias set_speed wickeddevice_set_speed
      alias terminal_dids wickeddevice_terminal_dids
    end
    controller_init()
  end

  #############################################################################
  # Generic initialization routine for setting up this controller.
  #############################################################################
  def controller_init
    @event_queue = Queue.new()
    set_speed(0,0,0) # stop, initialize instance variables
    # start up bot thread
    @bot_thread = bot_thread()
  end 

end # module

Catalina Computing, LLC.

Copyright © Catalina Computing, LLC. (2013-2018)




Page last updated: Thu Oct 23 17:01:08 2014 (UTC)