Limbi Generation 1 Summary Blog Post

Author/s:

Julie Liner, MST

Daniel Trejo, E&C

Noah Roth, D&M and E&C

Ridel Samonte, PM and D&M

Table of Contents

Executive Summary

Program and Project Objectives

Program Objectives

The idea of Limbi is to be a crawling robotic space limb that would fix and/or maintain spacecraft to reduce (or eliminate) the need for astronauts to have tethered spacewalks to do such tasks.  Another foreseeable future of Limbi is to act as a reconfigurable robots called limboids (smaller limbis) to build unique space structures.

JPL designed a prototype that uses electromagnets for the docking mechanism which draws huge amounts of current. Our team’s contribution to the advancement of Limbi is the development of a low-power momentumless mechanical docking mechanism that is also capable of power transfer.

Project Objectives

Limbi is a Robotic arm that has 4 degrees of freedom (joints), each capable of moving 180 deg on one plane, as well as an end-over-end mobility. Each end of the arm is androgynous (universal connector for both arm-to-module and module-to-module interconnects) connector capable of mating with an identical pair. Part of Limbi is the “spacecraft” module that has the same androgynous connector on its faces. Each module is capable of supplying power to the arm.

Mission Profile

The objective of Limbi is to connect two “spacecraft” modules into a specified position using the Arxterra App to demonstrate in-space assembly. At the start of phase 0 one end of the arm will start connected to Module 1 which will supply power to the arm. Module 1 is in a fixed position. For phase 1 the other end of the arm will grab module 2 through the docking mechanism. At this point, both modules are both supplying power to the arm (make-before-break circuitry). During phase 2 the arm will bring module 2 and connect it to module 1 through the module-to-module faces of both modules. During phase 3 the arm will undock from module 1 and module 2 will solely supply power to the arm for missions success.  These phases can be found below in Figure 1.

A more detailed description can be found here.  

Figure 1: Limbi Mission Phases

Project Features

The goal of Limbi is to demonstrate in-space assembly that has a low-power momentumless mechanical docking mechanism with positive lock while having a continuous power supply from the module(s). Our team came up with a two layer docking design in the module side and a cross-shaped manipulator in the arm side that will initiate a 3 step sequence to dock: 45 degree turn, power transfer, and a actuate a positive lock.

Requirements

For the Limbi, requirements were an iterative process; they stemmed from the customer’s expectation, JPL’s model of the Limbi, and the conceptual operation of the final mission. Level 1 requirements are design independent requirements; for Limbi these were separated into requirements for the arm and the module. For the arm these then separated more into General, Mobility, Electronics and Control, and Special Features. These flowed down to Level 2 design dependent requirements which clarified the performance of separate systems and components. All requirements should be quantitative, verifiable, and realizable. To find more information on the creation of the requirements of the Limbi see: Creating Requirements and Servo Test.

Engineering Standards and Constraints

Applicable Engineering Standards and Environmental, Health, and Safety (EH&S) Standards

L 1.30 The Limbi shall employ a custom PCB to extend the functions of the arduino nano by allowing control of 6 servos, and logic levels compatible with the hm-11.

L2.1.19.1 The custom PCB will use surface mount technology including the Hm-11 and the SN74LV1T34 Logic Shifter

L 1.31 Disassemble and Reassemble of the robot shall be constrained to less than 20 minutes (10 minutes+10 minutes) (waived)

L 1.32 The Limbi shall be completed by the date of the final: May 14th 2019.

L 1.33 The robot shall be designed in such a way that there are no dangling or exposed wires.

L 1.34 The form factor of Limbi shall be constrained by the original JPL version (see requirement L1.2)

L 1.35 The usability of the Limbi shall be enhanced by use of the Arxterra phone and control panel application

L 2.1.35.1 The ArxRobot app shall allow control of all joint servos and docking servos (see requirement L 1.10)

L 1.36 Back of the envelope calculations and experiments shall be conducted to set the diameter of power carrying wires. Follow the American Wire Gauge (AWG) standard when defining the diameter of power carrying wires.

L 1.37 Manufacturability of 3D printed robots shall minimize the number of files to be printed when using the library’s Innovation Space to print the final robot (waived)

Out of the standards, codes, and regulations as defined in the “Engineering Standards and Constraints” Section, these are the ones that apply to Limbi

L 1.38 All Lithium (Li-ion, Li-polymer) batteries shall be stored, when not in use, in a fire and explosion proof battery bag.

L 1.39 Software shall be written in the Arduino De facto Standard scripting language and/or using the GCC C++ programming language, which is implements the ISO C++ standard (ISO/IEC 14882:1998) published in 1998, and the 2011 and 2014 revisions.

L 1.40 The Limbi shall be controlled via Bluetooth 4.0 in compliance with the Bluetooth Special Interest Group (SIG) Standard (supersedes IEEE 802.15.1).

Program and Project Level 1 Requirements

For Limbi the project unique functional requirements related to the form factor of the JPL Limbi and the fact that conceptually Limbi is meant to be used in space. This led to requirements related to end-over-end mobility, and docking mechanisms that could be implemented without pushing force.

Requirements (Arm):

General:

L1.1 Objective shall be demonstrated on a low friction surface

L 1.2 The Limbi project will be smaller than the JPL Limbi for cost and storage purposes.

Mobility:

L 1.3 The arm’s 4 joints shall move 4 of the 5 limbs (see Figure 2).

L 1.4  Each joint shall have 180 degrees of movement in one plane (x-y).

L 1.5 The arm shall be able to connect and disconnect with the modules

L 1.6 Docking mechanism shall keep the arm and module connected as the arm moves until it is meant to be disengaged.

L 1.7 The arm shall have a docking mechanism on each end to connect to two modules at once.

L 1.8 The arm shall be able to move module (in the same plane as the actuator planar scope).

L 1.9 The arm shall be able to link two modules together via permanent magnets.

Electronics and Control

L 1.10 The movement of the arm shall be controlled by the user with custom software.

L1.11 The Limbi shall be controlled with a microcontroller

Special Features

L 1.12 The arm should provide live video feed capability.

Requirements (Module):

L 1.13 Each module shall have the capability of providing power to the arm.

L 1.14 The arm shall only be powered with one power source (for extended amounts of time)

L 1.15 For demonstration purposes the module shall have docks on only 2 out of 6 faces

L 1.16 The module should indicate when secure connection is made between the Limbi and modules with an LED.

L 1.17 One module shall be defined as the base module and shall be stationary to represent a large, unmoving mass in space (such as the spacecraft). This requirement is based on Section 4 of “An Untethered Mobile Limb for Modular In-Space Assembly”.    

System/Subsystem/Specifications Level 2 Requirements

Our Level 2 requirements flow from our Level 1 requirements. They expand on performance specifications that allow the Level 1 requirements to be accomplished. Limbi’s Level 2 requirements include values such as servo, battery, and magnet specifications, size constraints, and how specific components will interact with each other.

L2.1.1.1 The arm will be supported with metallic ball casters on Limb 0 and Limb 4 to simulate the conditions where the arm will not be affected by gravity.

L 2.1.2.1 The Limbi will follow the form factor of the JPL version; the lengths will be optimized in respect to inverse kinematics

L 2.1.3.1 Joint 1 shall control the motion between Limb 0 and Limb 1.

L 2.1.3.2 Joint 2 shall control the motion of between Limb 1 and Limb 2.

L 2.1.3.3 Joint 3 shall control the motion between Limb 2 and Limb 3.

L 2.1.3.4 Joint 4 shall control the movement between Limb 3 and Limb 4.

L 2.1.4.1 The Limbi will have 4 joints controlled by servos.

L 2.1.4.2 Each servo shall require no more than 7.4V and 500mA to run with a load

L 2.1.4.3 The servo shall be able to provide more than .237 Nm or 2.417kg/cm based on the force to move an object in a planar field

https://drive.google.com/file/d/1-dam0Q-eQ5knCC3htdMbFq1tB0Sh4aM8/view?usp=sharing

L 2.1.5.1 The docking mechanism shall consist of the interlocking mechanical device described in requirement L2.1.8.1 that allows the Limbi arm to successfully attach and detach to and from the module

L 2.1.5.2 The arm shall be able to attach and detach to and from a module without pushing the module away.

L 2.1.5.3 The module should have an androgynous connector.

L2.1.6.1 The slot for the cross shall be at least 1.52 mm larger than the cross based on the max vernier servo error of 1 degree.

Servo Error Test Blog Post

https://drive.google.com/file/d/19Th-GjUsLZENH-va1Bqef4a42NR5o-Wc/view?usp=sharing

L2.1.6.2 Only 1 docking servo shall be in motion at once so the module does not un-dock

    while the other module docks (this would cause power loss)

L2.1.7.1 The docking mechanisms on each end will be identical to each other

L2.1.8.1 The cross shall be between 7.19×7.19 cm and 8.89×8.89 cm in order to support the module-side dock and to fit within the module

https://drive.google.com/file/d/1Ozo8QtbmcpTTIGyFXx-LsOBe_Rc6u4hm/view?usp=sharing

L2.1.8.2 The depth of the cross shall be less than the depth of the module docking wall; the wall is based on the width of the modular contacts

L2.1.8.3 Copper tape will be used to conduct electricity between the cross and male modular contact

L 2.1.9.1 The magnets shall have between a .62lb pulling force and a 2.16lb pulling force. This will allow the magnets to be strong enough to hold together, but weak enough to not attract at further than 15mm away. https://docs.google.com/document/d/1DVFe5M6uZ-BYn5DWm9XuO3wccNZQ8Z0JOhozKRqyyIo/edit?usp=sharing

L2.1.10.1 The custom software will be implemented through the Arxterra App

L2.1.10.2 The user interface shall utilize wireless control of Limbi.

L2.1.10.3 The user interface shall have push buttons/toggles for pre-determined movements

L2.1.10.4 The user interface shall use simulated sliders for manual movements.

L2.1.11.1 The project will use an Arduino Nano mounted within the Limbi arm to allow the servos to be controlled directly by the microcontroller

L2.1.12.1 A TTL Serial Camera and TFT LCD display should be used to provide live video feed for alignment.

L2.1.13.1 The power provided from the module shall come from a battery via the docking mechanism

L2.1.13.2 The battery shall be capable of providing 1055 mA current (if needed) which will be used to power the MCU, 6 servos, Bluetooth Module, TFT LCD Display, and TTL Serial Camera.

https://docs.google.com/spreadsheets/d/14YAiEcqK4qWxwYl2tHo_f3VKfI4TdQ1mBcvUcpUTPt0/edit?usp=sharing (See power resource report)

L2.1.13.3 The battery used will be a Floureon LiPo rated at 7.4V, 1500mAh

L2.1.14.1 A make-before-break will be used to switch power

L2.1.15.1 One docking face shall be for module-to-module and the other shall be for arm-to-module connection.

L2.1.16.1 The LED on the module should be activated by one of the four power connections

L2.1.16.2 The LED should be on the corner so the person controlling the app can easily see that a secure connection has been made

Allocated Requirements / System Resource Reports

Mass Shared Resource Report / Allocation

The mass allocation came from a rough estimate of the weight of our Limbi compared to the JPL version. We did not know the weight of the JPL version, but we did know that they required 80lb pull force magnets to connect the module to the arm; this gave us the idea that the arm weighed roughly 80lbs. Knowing that our Limbi would be half the size and made out of ABS plastic we allocated 5lbs to the project. The measured weights of Limbi were much less because the Limbi was printed in PLA. We largely overestimated the weight of the magnets, causing the docking mechanisms to weight more than the modules; this was corrected in the actual measurements and in reality the modules weight more than the docking mechanism. Our contingency is high to allow the Limbi arm and modules to be printed out of different materials.

Mass Allocations
System Resource Total Expected Weight (g) Measured Weight (g) Uncertainty (%) Margin (g) (g)
Limbi Arm
3D Printed Plastic 450 321 5% ±22.5
HS-422 servos (4) 184 172 5% ±9.2
Arduino Nano 7 6 5% ±.35
Ball Casters (2) 83.6 256 5% ±4.18
Modules (2)
3D Printed Plastic 240 182 5% ±12
Batteries 176 179 5% ±8.8
Docking Mechanism (2)
3D Printed Plastic 394 138 5% ±19.7
Magnets (8) 200 78 20% ±40
HS-422 servos (2) 28 86 5% ±1.4
Miscellaneous

Components

50 28 40% ±20
Project Allocation: 2267.96
Total Margin: 138.13
Total Actual Weight: 1446
Total Expected Weight: 1812.6
Contingency: 317.23

Table 1: Mass Allocation

Power Shared Resource Report / Allocation

The power allocation came from the estimate of the amount of current draw our servos would need. We knew that the servos would draw the most power and that we would have 6 servos. Estimating that each servo would need about 250A we decided on a battery that would provide 1500mAh. This provided the allocation for the battery which we used for the whole project. When we actually decided on parts we realized that the servos could draw a max of approximately 300mA each, but only 3 servos needed to be used at once since only one side of the arm would be used at once and one docking mechanism would be used at once. For our expected values we had large uncertainty because we didn’t know how the load would affect the current draw, this resulted in large margin. For contingency we were trying to plan for the worst case scenario and found that if all parts ended up being over the expected values that we would still have room left from our allocated amount of 1500mAh. This gave us leeway in how much we could add in terms of electronics and how long our battery would be able to run. For our actual measured values we ended up getting significantly lower current draw than expected from the servo, but we added an solenoid that draws significantly high current for a very short amount of time which caused us to use more than our expected current.

Power Allocations

Resource Expected Current Draw (mA) Measured Current Draw (mA) Uncertainty (%) Margin (mA)
Joint Servos (4) 600 40% ±240
Vernier Servo (1) 104.9
Inner Joint Servo (2) 222.3
Inner Joint Servo (3) 234.6
Vernier Servo (4) 112.2
Docking Servos (2) 300 40% ±120
Dock 1 91
Dock 2 96.7
Hm-11 15 8.9 5% ±.75
Arduino Nano 40 19.5 5% ±2
TFT LCD Display 25 N/A 5% ±1.25
TTL Camera 75 N/A 5% ±3.75
Logic Shifter 7 7 0% ±0
Push-Pull Solenoid 700 700 0% ±0
Project Allocation 1500 mA
Total Margin 367.75 mA
Total Actual Current 1397.1mA
Total Expected Current 1055 mA
Contingency

77.25 mA

Table 2: Power Allocation

Other Shared Resource Report(s) / Allocation

Assembly Print Hours (0.15 layer height and 50% fill)
1 1230 min
2 1200 min
3 1000 min
4 1440 min
5 720 min

Slicer Software used: Ultimaker Cura

3D Printer: Ultimaker S5

3D Printer Setting: 0.15 layer height, 50% fill, AA 0.4 print core

Material: PLA
Total: 5590 min (approximately 93 hours, 4 days)

Project Report

Project WBS and PBS

WBS:

Limbi’s Work Breakdown Structure is divided into three parts: Missions Systems and Test, Electronics and Control and Design and Manufacturing.

Missions Systems and Test is responsible for the System Requirement, Project Allocation, and Verification/Validation of tests performed by the E&C and D&M.

E&C is responsible for the electrical architecture of the arm and module, the power transfer design innovation and the PCB layout,

D&M is responsible for the arm, module mechanical design and the momentumless low-power mechanical docking mechanism design.

ADD DESCRIPTION

Figure 3: WBS

PBS:

The Limbi project breaks down into three separate components. It consists of the Limbi arm, the base module (Module 1), and the second module (Module 2). The arm further breaks down into 5 limb segments which are controlled by 4 joints, docking mechanisms on either end of the arm, and a power distribution system, communication system, and control system. The power control system, communication system, and control system are all implemented through the Arduino Nano, Hm-11, and a custom PCB and is located in the center of the Limbi arm. The docking mechanism is what interfaces the arm and the module. The docking mechanism on the arm connects to the docking mechanism on the module; this connection provides power from the battery in the modules to the power control system, communication system, and control system. Each module is identical and they both contain a battery and  two docking faces; one face is a module-to-arm face (which provides power) and one is a module-to-module face (which connects the two modules to each other). This is shown below in Figure 6.

Figure 4: PBS

Cost

For the cost we did not have a project allocation of $250 because we are not a 3DoT project. Our allocation was determined at CDR based on how much we had spent, but even with our contingency and margin, we ended up going over budget due to final design changes and extra print versions that we did not account for. Our main suppliers were Amazon and Mouser Electronics. We went through many suppliers and compared the costs, but chose to use Amazon mainly because the lack of shipping cost we achieved through using a Prime account lowered the cost compared to buying from separate suppliers.

The total cost for the Limbi Spring ’19 project is about 400$.

Limbi Latest BoM for final design iteration

Burndown Schedule

The project manager of the team have decided to use excel spreadsheet for the schedule burndown instead of targetprocess in which the whole team agreed on. The excel spreadsheet captures both action items and schedule breakdown in one whole matrix. Each task has 8 columns: description, priority (High, Medium, Low), assigned to (person responsible), status (open, percentage complete, pending, close), hours spent, expected and completed date, and comment. This way, we can easily keep track of what is holding the design process from moving forward.

Limbi Schedule Breakdown

Concept and Preliminary Design

Literature Review

We had three main literature resources for this project: JPL’s Limbi IEEE paper, and the previous Arxterra documentation for Research and the PDR.
The previous Arxterra PDR documentation consists of a project proposal for an interdepartmental project to improve the Limbi for use by JPL. It was proposed as a two semester senior design project, that would be a collaboration on work done by the MAE,CECS, and EE departments. The post laid out a potential schedule for the two semester course. The previous CSULB Mission Objective was to contribute to the advancement of the Limbi by adding mechanical interconnects for docking, and improving the power transfer.

The research overviewed the goal of the Limbi, the current design, and the power module design. Some key points it made were:

  • The goal of the Limbi is to create a multi-jointed, autonomous robot that can reconfigure itself, and attach itself to other limbiods to construct spacecrafts or fix space structures.
  • The current design is a JPL prototype that attaches itself to makeshift spacecraft modules and arranges them into a given position.
  • This design uses cube-shaped power modules where one of the modules provides power to all other modules and to the detachable Limbi arm.
  • The current docking mechanism utilizes electromagnets; however, this limits the rigidity of the modules.

The JPL Paper, “An Untethered Mobile Limbi for Modular In-Space Assembly” goes into detail about the current Limbi and the future of the Limbi. For the JPL mission objective it was to develop autonomous reconfigurable robots that can assemble and fix large space structures and spacecraft. The current JPL Limbi is not autonomous, but completes its mission using predefined movements. The JPL Limbi acts only on one plane and uses rotary actuators to control its movements. Other key factors that were discussed in this paper were the limb design dimensions, the symmetry of the arm, and the proportions  of the limb segments which had small vernier segments and a large center segment. Unfortunately, JPL used electromagnets as the primary docking mechanism which was incredibly inefficient in terms of power use, and also the electromagnet strength drops rapidly with small distances, making the mechanism prone to failure from high moments.

We used information from these three sources to imitate the Limbi design and improve it. Based on the information gathered from our literature review we decided against the use of electromagnets (at the cost of no longer having androgynous docking), replaced the docking mechanism with a mechanical interlocking, low momentum option, and kept the form factor of the Limbi only modifying the arm proportions for the use of inverse kinematics.

  1.  JPL LIMBI IEEE PAPER
  2. Spring ’18 research
  3. Spring ’18 PDR

Design Innovation

  1. Docking Mechanism – Momentumless docking mechanism for arm-to-module connection that is also compatible for module-to-module connection.    

    Docking Design

    (see Mechanical Design Blog post for final version)

    For the arm-to-module connection we used a momentumless docking mechanism. On the arm side, this mechanism includes a cross and a push-pull solenoid for auto lock. On the module side, the docking mechanism has two layers; the bottom layer is recessed and allows for the cross to be turned and the top layer covers the recessed part of the bottom layer so the cross can’t fall out. When the docking mechanism is in use the first stage includes the cross turning 45° and once in place the second stage of docking is the solenoid inserting into a fixed position (this is powerless). To undock, power must be applied to the solenoid to retract it and then the cross turns 45° counter-clockwise to complete undocking.

  2. Make-Before-Break Power Transfer – Continuous power supply to the arm pre, post and during module change.

    Power Transfer

    To provide continuous power to the arm a make-before-break circuit was used. The cross represents the switch in the make-before-break circuit. In a make-before-break circuit it allows both batteries to power the arm before disconnecting from the original battery. This method was used to prevent the microcontroller from disconnecting between transfer from Module 1 to Module 2.

  3. Docking-Power Transfer Interface – Interface between the docking mechanism and power supply.

Docking-Power transfer interface pre-CDR design

(see Mechanical Design Blog post for final version)

For power transfer between the modules and the arm male modular contacts and copper tape were used. On the module side the battery is connected to the male modular contacts. The copper tape is located on the cross and provides power to the circuitry located on the arm; when the cross is docked the copper tape touches the male modular contacts which allows power to transfer between the battery in the module and the circuitry on the arm.

Conceptual Design / Proposed Solution

Our team came up with a two layer docking design in the module side and a cross-shaped manipulator in the arm side that will initiate a 3 step sequence to dock: 45 degree turn for the cross, power transfer (or signal line for feedback) through the modular contact, and a actuate a normally open (lock position) push-pull solenoid valve for low power positive lock.

*picture of the actual design together

Figure 8: Exploded view of 3D Model

System Design / Final Design and Results

System Block Diagram

  • System Block Diagram

The system block diagram shows how different components of the Limbi interface with each other. The power starts with the battery in Module 1 and powers the components within the Limbi arm through the docking mechanisms on the module and the arm. This power is then distributed to the Arduino and an external Buck Converter which distributes the power appropriately. The Custom PCB powers the servos and the Arduino sends commands to the servos using the Hm-11. The Hm-11 communicates with the Arxterra app and this then controls the servos which move the arm segments. A make before break system is set up so the arm connects to Module 2 and secures power before undocking from Module 1. This allows the Limbi arm to move from end to end while maintaining consistent power from one module or the other.

Figure 9: System Block Diagram

For previous iterations of the System Block Diagram see: System Block Diagram Iterations Blog Post

Interface Definition

For interface we have the interface matrix which shows connections between the Arduino Nano, Custom PBC, and components. This can be found in Figure below.

Table 5: Interface Matrix

Below is the cable tree which shows how the wires will be routed through the Limbi. Power between the batteries within the modules and the cross will be transferred between aluminum and male modular contacts. (No wires will directly connect the module and arm). The cross will have grooves routed through the back to allow the wires to travel through, the wires will use a wire wrap around the connection of the cross to prevent them from tugging. The module wiring harness will consist of only the power and ground which will be connected to aluminum. The servo power  wires will be connected to the custom PCB using JST connectors. The servos will implement keyed 3 pin molex connectors. The power wires will be connected to the arduino and the DC/DC buck converter. The buck converter will provide power to the PCB. The wires will be routed through the center of either side of the blue braces shown in the picture below.

Figure 10: Cable Tree

Modeling and Experimental Results

The following are the experiments performed to tests design concepts:

  1. Magnet Test
    Objective: To test if the magnets are strong enough to dock but weak enough to not cause stalled joint servos to rotate.
    Magnets Test
  2. Press Fit Test
    Objective: To test tolerance to implement press fit design.
    Press fit Test 
  3. Inverse Kinematics Test
    Objective: To test code for a forward motion to align cross for docking.
    Inverse Kinematics Test 
  4. Docking-Power Transfer Interface Test
    Objective: To test continuity and resistance changes of the docking-power transfer interface design while the arm is moving the module.
    Docking Video Continuity Test
  5. Power Transfer Test
    Objective: To test continuous power and power surges pre, post and during docking.
    Power Transfer Breadboard Test Video
  6.  Servo Error Test
    Objective: To the test the minimum servo rotation for our Vernier limb (L0 and L4) to calculate the slot tolerance for docking layer 2.
    Servo Error Test

Mission Command and Control

Figure 11: Arxterra Control on Mobile App

The Limbi will be mainly controlled through the Arxterra App or Control Panel. All the control commands are custom commands, that are explained more in depth in the separate Firmware Blog Post. The custom commands that are used to control the Limbi are shown in the figure above. Below is a brief overview of the command, it’s address, and a general description.

  • MOVE (0x01) – The move command has been modified to control the middle and end servos (on either side), and performs inverse kinematics to move the Limbi forwards, backwards, left or right.
  • DOCK 1 (0x40) – The Dock 1 custom command is an ON/OFF button that docks and undocks the Limbi on side 1 of the Limbi.
  • DOCK 2 (0x41) – The Dock 2 custom command is an ON/OFF button docks and undocks the Limbi on side 2 of the Limbi
  • VERNIER CONTROL 1 (0x42) – The Vernier Control 1 custom command is a slider with values between 1 and 180, and it writes an angle to the vernier control on side 1 of the limbi
  • VERNIER CONTROL 2 (0x43) – The Vernier Control 2 custom command is a slider with values between 1 and 180, and it writes an angle to the vernier control on side 2 of the limbi
  • MIDDLE CONTROL 1 (0x44) – The Middle Control 1 custom command is a slider with values between 1 and 180, and it writes an angle to the middle joint on side 1 of the limbi
  • MIDDLE CONTROL2 (0x45) – The Middle Control 2 custom command is a slider with values between 1 and 180, and it writes an angle to the middle joint on side 2 of the limbi
  • INVERSE KINEMATICS (0x46) – The Inverse Kinematics custom command is a select with two options (END 1 and END 2). Selecting either end will determine what end the inverse kinematics will be performed on.

Electronic Design

Our E&C team went through quite a few design iteration for the PCB and the firmware to meet design requirement and specification.

PCB Design

We decided to make use of the Arduino Nano microcontroller due to its small scale and ease of integration with a custom PCB shield that would allow a better organization of wires in a small enclosed space located within the center limb of the arm.

The servos utilized were rated to operate at a maximum of 6V so we needed to implement a DC-DC buck down converter to drop the voltage down from the 7.4V Lipo Batteries we would be using. This means the board would have an input of 7.4V directly from the battery to power the MCU, a 6V to feed towards the servos, and naturally a GND. To serve as protection from potential short circuit dangers, 4.5A polyfuses were used on the power lines.

In addition to servo control, a solenoid valve would also need to be controlled as a positive locking mechanism at each end of the arm for docking. Since the solenoid pin was able to be compressed on its spring with very low amount of force the design was implemented in a way such that the solenoid would only need to be actively powered during the undocking phase in which it should retract thus allowing for free rotation of the docking servo. To achieve this a simple MOSFET switching circuit was implemented which allowed for the solenoid to be powered by the 6V line when the digital output D12 from the MCU applied voltage on the gate of the MOSFET. In addition to this a diode was connected between both solenoid lines which would prevent current discharging backwards when the solenoid coil disharged. Naturally this circuit was implemented symmetrically for both sides.

To facilitate wireless communication between the operating application and the arm an HM-11 BLE module was utilized. The bluetooth module requires a 3.3V power supply which is provided by the MCU 3.3V output. Since the MCU operated on a 5V logic and the HM-11 3.3V logic, translation between the devices was necessary. This was achieved through utilizing single buffer gate logic level shifters. These IC’s functioned to provide up and down translation based on the power supplied to them. So for instance the Tx pin on the MCU operates at a 5V logic level, by providing the buffer with a 3.3V power supply from the MCU, the logic is translated to a 3.3V level that can be properly interpreted by the HM-11 module, and vice-versa.

The schematic can be seen below:

Figure 12: PCB Schematic

The general design philosophy behind the board would be to have a symmetric output headers on the left and right side which would provide power and control signals to the 3 servos and locking solenoid on each side of the arm. At the top of the board would be the inputs for the direct battery, as well as the regulated 6V output from the buck converters. All components on the board were SMD with the exception of the diodes. Finally all four corners of the board contain screw holes to mount on the arm. The component list and board layout can be seen below, as well as a picture of the final assembled version and the buck converter used.

Figure 13: Components List


Figure 14: PCB Layout


Figure 15: PCB Physical


Figure 16: External DC/DC Buck Converter

Firmware

Before I begin explaining the firmware, and how it controls each joint, I believe it is important for the reader to grasp how the Limbi looks, and the name of each joint. This will help the reader understand what I’m referring to in the rest of this section.

Limbi 2DAs seen in the Figure above, the Limbi has 4 joints. In this report, I will be referring to Joint 1 as Vernier Control 1, Joint 2 as Middle Control 1, Joint 3 as Middle Control 2, and Joint 4 as Vernier Control 2.

The firmware for our project contains 7 custom commands, and 1 command redefinition. These custom commands are: Move, Dock 1, Dock 2, Vernier Control 1, Vernier Control 2, Middle Control 1, Middle Control 2, and Inverse Kinematics Selector. These are briefly described in the Mission Command and Control Section above.

Below is the most recent code for Limbi.




#define DOCK1       0x40  // Dock 1 Custom Command Address

#define DOCK2       0x41  // Dock 2 Custom Command Address

#define VERNIER1    0x42  // Vernier 1 Custom Command Address

#define VERNIER2    0x43  // Vernier 2 Custom Command Address

#define MIDDLE1     0X44  // Middle 1 Custom Command Address

#define MIDDLE2     0X45  // Middle 2 Custom Command Address

#define IKSELECTOR  0x46 // Inverse Kinematics Selector Custom Command Address

#define L1 154L           // Define the length of length 1 (measurement is in mm)       

#define L2 125L           // Define the length of length 2 (measurement is in mm)     


const uint8_t CMD_LIST_SIZE = 8;   

// Adding 8 commands (MOVE, DOCK1, DOCK2, VERNIER1, VERNIER2, MIDDLE1, MIDDLE2, IKSELECTOR)


int globalIKSelector = 0;

int globalPhi1;         // Saves angle last written to mid1 Servo for use in Inverse Kinematics

int globalTheta1;       // Saves angle last written to end1 Servo for use in Inverse Kinematics

int globalPhi2;         // Saves angle last written to mid2 Servo for use in Inverse Kinematics

int globalTheta2;       // Saves angle last written to end2 Servo for use in Inverse Kinematics

int globalCount = 1;    // Used for conditional statements to prevent both docks from being undocked at the same time




// Create variable speed servo objects

VarSpeedServo mid1;     // Joint 2

VarSpeedServo mid2;     // Joint 3

VarSpeedServo end1;     // Joint 1

VarSpeedServo end2;     // Joint 4

VarSpeedServo dock1;    // Dock 1

VarSpeedServo dock2;    // Dock 2




void dock1Handler (uint8_t cmd, uint8_t param[], uint8_t n)

{

   dock1.attach(2);            // Attach dock1 servo to pin 2

   if(param[0] == 1 ){         // Using boolean: if button = 1 = ON DOCK TO MODULE

     dock1.slowmove(41,40);    // Move dock 1 Servo to angle of 41 degrees at speed 40/255

     delay(2000);              // Delay allows servo to get to position before detaching

     globalCount = 1;          // Set Global Count to 1 when Dock 1 is connected

   }

   else{                       // Else: button = 0 = OFF UNDOCK FROM MODULE

     if(globalCount%2==0){     // Takes remainder of division globalCount/2, to check if it's

 // odd or even

                               // If it's even, it allows the dock to be undocked

       digitalWrite(10,HIGH);  // Set's Push Pull Solenoid to High so that it retracts

       delay(1000);

       dock1.slowmove(90,40);  // Move dock 1 Servo to angle of 90 degrees at speed 40/255

       delay(1000);

       digitalWrite(10,LOW);    // Set's Push Pull Solenoid to Low so power is not being drawn

       delay(500);

     }

   }

   dock1.detach();             // Detach dock1 servo from pin

}  // dock 1 Handler




void dock2Handler (uint8_t cmd, uint8_t param[], uint8_t n)

{

   dock2.attach(4);           // Attach dock2 servo to pin 4

   if(param[0] == 1){          // Using boolean: if button = 1 = ON DOCK TO MODULE

     dock2.slowmove(41,40);    // Move dock 2 Servo to angle of 41 degrees at speed 40/255

     delay(2000);              // Delay allows servo to get to position before detaching

     globalCount = 2;          // Set Global Count to 2 when Dock 2 is connected

   }

   else{                       // Else: button = 0 = OFF UNDOCK FROM MODULE

     if(globalCount%2==1){     // Takes remainder of division globalCount/2

                               // If it's odd, it allows the dock to be undocked

       digitalWrite(9,HIGH);   // Set's Push Pull Solenoid to High so that it retracts

       delay(1000);

       dock2.slowmove(90,40);  // Move dock 2 Servo to angle of 90 degrees at speed 40/255

       delay(1000);

       digitalWrite(9,LOW);   // Set's Push Pull Solenoid to Low so power is not being drawn

       delay(500);

     }

   }

   dock2.detach();             // Detach dock2 servo from pin

}  // dock 2 Handler




void vernier1Handler (uint8_t cmd, uint8_t param[], uint8_t n)//param[0] = unsigned byte (1-180)

{

   end1.attach(3);             // Attach end1 servo to pin 3

   end1.slowmove(param[0],40); // Move end1 Servo to angle of param[0] degrees at speed 40/255

   delay(2500);

   end1.detach();              // Detach end1 servo from pin 3

   globalTheta1 = param[0];    // Saves end1 servo angle for use in Inverse Kinematics

}  //  vernier 1 Handler




void vernier2Handler (uint8_t cmd, uint8_t param[], uint8_t n)//param[0] = unsigned byte (1-180)

{

   end2.attach(5);             // Attach end2 servo to pin 5

   end2.slowmove(param[0],40); // Move end2 Servo to angle of param[0] degrees at speed 40/255

   delay(2500);

   end2.detach();              // Detach end2 servo from pin 5

   globalTheta2 = param[0];    // Saves end2 servo angle for use in Inverse Kinematics

}  // vernier 2 Handler




void middle1Handler (uint8_t cmd, uint8_t param[], uint8_t n)//param[0] = unsigned byte (1-180)

{

   mid1.attach(12);             // Attach mid1 servo to pin 12

   mid1.slowmove(param[0],30);  // Move mid1 Servo to angle of param[0] degrees at speed 30/255

   delay(2500);

   mid1.detach();               // Detach mid1 servo from pin 12

   globalPhi1 = param[0];       // Saves mid1 servo angle for use in Inverse Kinematics

}  // middle 1 Handler




void middle2Handler (uint8_t cmd, uint8_t param[], uint8_t n)//param[0] = unsigned byte (1-180)

{

   mid2.attach(11);             // Attach mid2 servo to pin 11

   mid2.slowmove(param[0],30); // Move mid2 Servo to angle of param[0] degrees at speed 30/255

   delay(2500);

   mid2.detach();              // Detach mid2 servo from pin 11

   globalPhi2 = param[0];      // Saves mid2 servo angle for use in Inverse Kinematics

}  // middle 2 Handler




double forwardKinematicsX(){    // Calculates the current X Position of the endpoint on either end of Limbi

 int theta;

 int phi;

 if(globalIKSelector == 0){    // INVERSE KINEMATICS FOR SIDE 1

   theta = globalTheta1;       // Reads angle last written to end1 servo

   phi = 180 - globalPhi1;     // Reads angle last written to mid1 servo

 }

 else{                         // INVERSE KINEMATICS FOR SIDE 2

   theta = globalTheta2;       // Reads angle last written to end2 servo

   phi = 180 - globalPhi2;     // Reads angle last written to mid2 servo

 }




 if(theta<=90){                // Adjusts angle value, due to servo position on Limbi

   theta = 90 - theta;

 }

 else{

   theta = 360 - (theta - 90);

 }




 // Forward Kinematics equation for two DOF

 double xCalc = L1*cos(phi*PI/180) + L2*cos((phi+theta)*PI/180);  

 return xCalc;                   // Returns current X Position

}










double forwardKinematicsY(){      // Calculates the current Y Position of the endpoint on Side 1 of Limbi

 int theta;

 int phi;

 if(globalIKSelector == 0){      // INVERSE KINEMATICS FOR SIDE 1

   theta = globalTheta1;     // Reads angle last written to end1 servo

   phi = 180 - globalPhi1;   // Reads angle last written to mid1 servo

 }

 else{                           // INVERSE KINEMATICS FOR SIDE 2

   theta = globalTheta2;     // Reads angle last written to end2 servo

   phi = 180 - globalPhi2;   // Reads angle last written to mid2 servo

 }




 if(theta<=90){                // Adjusts angle value, due to servo position on Limbi

   theta = 90 - theta;

 }

 else{

   theta = 360 - (theta - 90);

 }




 // Forward Kinematics equation for two DOF

 double yCalc = L1*sin(phi*PI/180) + L2*sin((theta+phi)*PI/180);

 return yCalc;                 // Returns current Y Position

}




//  Inverse Kinematics Subroutine. Calculates Phi angle necessary for desired position

int inverseKinematicsPhi(double x, double y){ // Inputs are desired X and Y Position

 // Inverse Kinematics Variable Calculations

 double C = (x*x + y*y - L1*L1 - L2*L2)/(2*L1*L2);

 double S = sqrt(1 - C*C);

 double K1 = L1 + L2*C;

 double K2 = L2*S;




 //Inverse Kinematics equation. Calculates Phi Angle necessary for desired X,Y Position

 double phi = 180 - ((atan2(y, x) - atan2(K2, K1))*180/PI);




 phi = constrain(phi, 0, 180);   // Constrains the value of Phi between 1 and 180

 int phiPass = (int)round(phi);  // Rounds double to the closest integer

 return phiPass;                 // Returns angle to be written to mid1 Servo

}




//  Inverse Kinematics Subroutine. Calculates Theta angle necessary for desired position

int inverseKinematicsTheta(double x, double y){ // Inputs are desired X and Y Position

 // Inverse Kinematics Variable Calculations

 double C = (x*x + y*y - L1*L1 - L2*L2)/(2*L1*L2);

 double S = sqrt(1 - C*C);




 //Inverse Kinematics equation. Calculates Phi Angle necessary for desired X,Y Position

 double theta = (atan2(S,C))*180/PI;




 if(theta<=90){                      // Adjusts theta angle due to servo position on Limbi

   theta = 90 - theta;

 }

 else{

   theta = 360 - (theta - 90);

 }

 theta = constrain(theta, 0, 180);   // Constrains the value of Phi between 1 and 180

 int thetaPass = (int)round(theta);  // Rounds double to the closest integer

 return thetaPass;                   // Returns angle to be written to end1 Servo

}







void moveHandler (uint8_t cmd, uint8_t param[], uint8_t n){

 double x = forwardKinematicsX();      // Calls subroutine to calculate the current X Position

 double y = forwardKinematicsY();      // Calls subroutine to calculate the current Y Position

 if(param[0] == 1 && param[2] == 1){       // If the forward position on Joystick is pressed

   y =y + 5;                               // Increases the Y position, moving Limbi end piece forward

 }

 else if(param[0] == 2 && param[2] == 2){  // If the back position on Joystick is pressed

   y = y - 5;                              // Decreases the Y position, moving Limbi end piece backwards

 }

 else if(param[0] == 1 && param[2] == 2){  // If the right position on Joystick is pressed

   x = x + 5;                              // Increases the X position, moving Limbi end piece to the Right

 }

 else {   // If the left position on Joystick is pressed

   x = x - 5;                              // Decreases the X position, moving Limbi end piece to the Left

 }

 int phiFinal = inverseKinematicsPhi(x, y);      // Calls subroutine to calculate Phi necessary to get desired X,Y position

 int thetaFinal = inverseKinematicsTheta(x, y);  // Calls subroutine to calculate Theta necessary to get desired X,Y position

 if(globalIKSelector == 0){        // INVERSE KINEMATICS FOR SIDE 1

   end1.attach(3);                 // Attach end1 servo to Pin 3

   mid1.attach(12);                 // Attach mid1 servo to Pin 12

   end1.slowmove(thetaFinal, 20);  // Move end1 Servo to calculated Theta

   mid1.slowmove(phiFinal, 20);    // Move mid1 Servo to calculated Phi

   delay(1000);

   end1.detach();                  // Detach Servo

   mid1.detach();                  // Detach Servo

   globalTheta1 = thetaFinal;      // Saves value written to end1 servo for use in Inverse Kinematics

   globalPhi1 = phiFinal;          // Saves value written to mid1 servo for use in Inverse Kinematics

 }

 else{                             // INVERSE KINEMATICS FOR SIDE 2

   end2.attach(5);                 // Attach end2 servo to Pin 5

   mid2.attach(11);                 // Attach mid2 servo to Pin 11

   end2.slowmove(thetaFinal, 20);  // Move end2 Servo to calculated Theta

   mid2.slowmove(phiFinal, 20);    // Move mid2 Servo to calculated Phi

   delay(1000);

   end2.detach();                  // Detach Servo

   mid2.detach();                  // Detach Servo

   globalTheta2 = thetaFinal;      // Saves value written to end1 servo for use in Inverse Kinematics

   globalPhi2 = phiFinal;          // Saves value written to mid1 servo for use in Inverse Kinematics

 }

}  // moveHandler




void ikSelectHandler (uint8_t cmd, uint8_t param[], uint8_t n)

{

 globalIKSelector = param[0]; // Saves value of what end Inverse Kinematics will be performed on

}







ArxRobot::cmdFunc_t onCommand[CMD_LIST_SIZE] = {{MOVE,moveHandler}, {DOCK1,dock1Handler}, {DOCK2,dock2Handler},

   {VERNIER1,vernier1Handler}, {VERNIER2,vernier2Handler}, {MIDDLE1,middle1Handler}, {MIDDLE2,middle2Handler}, {IKSELECTOR, ikSelectHandler}};




Packet motorPWM(MOTOR2_CURRENT_ID);  // Initialize the packet properties to default values




void setup()

{

 Serial.begin(9600);               // Default = 115200

 ArxRobot.begin();

 ArxRobot.setOnCommand(onCommand, CMD_LIST_SIZE);




 motorPWM.setAccuracy(1);          // Change sensor accuracy from +/-2 DN to +/-1 DN

 motorPWM.setSamplePeriod(500);    // Change sample period from 1 second to 0.5 seconds




 // Initialize servos

 dock2.attach(4);       // Attach Servos to corresponding Pin

 end1.attach(3);

 end2.attach(5);

 mid1.attach(12);

 mid2.attach(11);

 dock2.slowmove(90,40);  // Move Servo to Initial Position

 end1.slowmove(90,40);

 end2.slowmove(90,40);

 mid1.slowmove(90,40);

 mid2.slowmove(90,40);

 delay(5000);      

 dock2.detach();         // Detach Servos from pin

 end1.detach();

 end2.detach();

 mid1.detach();

 mid2.detach();

 pinMode(9, OUTPUT);     // Set Pins as Outputs for Solenoid Signal

 pinMode(10, OUTPUT);

 digitalWrite(9, LOW);   // Initialize Solenoids to LOW or Off

 digitalWrite(10, LOW);

 delay(1000);

 globalPhi1 = 90;

 globalTheta1 = 90;

 globalPhi2 = 90;

 globalTheta2 = 90;

}




void loop(){

 ArxRobot.loop();




#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)

   uint16_t pwm_reading = (uint16_t) OCR4D;  // read 8-bit Output Compare Register Timer 4D and cast to 16-bit signed word

   motorPWM.sendSensor(pwm_reading);

 #else

   // Timer/Counter 0 registers set by UNO Bootloader (freq. = 1 Khz)

   // Timer/Counter Control Registers TCCR0B = 0x03 (Prescaler of 64) and TCCR0A (Fast PWM, TOP = 0xFF) = 0x03

   uint16_t pwm_reading = (uint16_t) OCR0B;

   motorPWM.sendSensor(pwm_reading);

 #endif

}

Libraries, Global Variables, and Initialization

Lets begin with the libraries being used in this code. The first library that is being used in this code is the “ArxRobot.h” library. This library is being used for it’s bluetooth capabilities, and ability to connect with the Arxterra App/Control Panel. The second Library being used is the “EEPROM” library. This library allows the Arduino to keep certain values when the board is turned off. The third library being used is the “Wire.h” library which allows for communication with I2C devices. The fourth library being used is the “VarSpeedServo.h” library. This library is being used to control the servos. The reason this library was chosen instead of the “Servo.h” library, is that this library allows the servos to be controlled with variable speed, and this way there wasn’t a need for a custom code to be written to control the speed of the servos. The “Servo.h” library supports up to 8 servos, and allows for simultaneous, asynchronous movement of all servos. The variable speed is from 1 to 255, with 1 being the slowest, and 255 being the fastest. The last library being used is the Math.h Library. This library allows for math functions and operations, and is used in the forward/inverse kinematics section of the code.

After the libraries were added to the code, we have our definitions, and creation of servos as objects. In this section, the addresses of our custom commands are defined. Two other definitions that were added are L1 and L2. L1 and L2 are the lengths of length 1 and length 2 of the Limbi in mm. Also in this section, all 6 servos were created as objects using the VarSpeedServo.h library.

Finally, the command list size was set to 8, and the global variables were set. The command list size is the number of custom commands that we are using, and it ties back the address of the command to the subroutine name. The global variables being used are the following. globalIKSelector–stores the value of the ikSelectHandler subroutine. This subroutine is a custom command that allows the user to choose what end of the Limbi the inverse kinematics will be performed on. GlobalPhi1, globalTheta1, globalPhi2, and globalTheta2 are place holders that hold the angle last written to the middle1 servo, end1  servo, middle2 servo, and end2 servo respectively. These global variables are being used because the angles are used over several subroutines for the inverse kinematics. The last global variable is globalCount. This variable is used for the docking subroutines and is used to ensure that both docks aren’t undocked at the same time.

During the initialization of the code, all of the pins are set to their corresponding servo, or are set as Output/Inputs. All of the joint servos are set to 90 degrees, which will result in the arm being completely straight. Dock 1 servo is set to a docked position, while Dock 2 servo is set to an undocked position. Pins 9 and 10 are set as Outputs, and set to LOW. These two pins are the pins that will be used for the solenoids on the docking mechanism.

Dock1Handler and Dock2Handler Subroutines

The Dock 1 command and Dock 2 commands perform the same function just on opposite sides of the Limbi.The Dock 1 code and Dock 2 code are almost identical. These two subroutines are called whenever the user presses the ON/OFF button with the command address 0x40, and 0x41 for Dock 1 and Dock 2 respectively. When the user presses the button, the Arxterra App or Control Panel send a 0 for OFF and a 1 for ON.

The first thing that happens when this is performed, is that the respective servo is attached to it’s corresponding digital pin.

Then there is a conditional statement that depends on the parameter sent by the user through the app or control panel. If the user sent a parameter of 1 (Or pressed the ON button), then this causes the Dock Servo to turn and therefore Lock the Limbi to the module. The dock servo turns to an angle of 41 degrees at a speed of 40/255. If the conditional statement reads a 1, it also causes the globalCount variable to set to 1 when Dock 1 is docked, and it sets the globalCount to 2 when Dock 2 is docked. This is important to note, because this is what prevents both docks from being undocked at the same time.

The second half of the conditional statement (the else part), will run if the user sent a parameter of 0 (Or pressed the OFF button). This section controls the undocking (or unlocking) between the Limbi and the module. However, as was explained in the previous paragraph, the undocking is controlled and both docks can’t be undocked at the same time. This means that in order for one dock to undock, the other dock must have been connected previously. As stated earlier, when Dock 1 is ON, it saves a value of 1 in globalCount, and when Dock 2 is ON, it saves a value of 2 in globalCount. In order for the undocking to take place, Dock 1 has another conditional statement that takes the remaider of a division between globalCount, and 2. If the answer is 0 (no remainder) then the actual undocking occurs. If thats not the case, then it doesn’t undock. The reason this is set this way is because it’s set this way for both Dock 1 and Dock 2, and it helps prevent both docks from being disconnected at the same time which would result in a loss of power.

Vernier 1 and 2 Handler, and Middle 1 and 2 Handler

These four subroutines all work the same way, and essentially do the same thing. On the Arxterra App/Control Panel, these commands were made into sliders with a value between 0 and 180. When the user moves any of these sliders, the respective subroutine is called, and the value sent from the slider is received in the subroutine as the zeroth element in the vector “param” (param[0]). Param[0] is the angle that we want the servo to move to. The respective servo is attached to it’s corresponding pin, and the angle is written to the servo, with a certain speed using the slowmove() command from the VarSpeedServo library. The servo is then detached, and the angle that was written to the servo is then saved in it’s corresponding globalVariable.

Forward/Inverse Kinematics

This is perhaps the most complex section of the code, and it involves several subroutines working in unison. These subroutines allow the forward or inverse kinematics to be performed on either the middle 1 and end 1 joints or the middle 2 and end 2 joints. One thing to note about this section is that whenever Phi corresponds to the middle 1 or middle 2 joint, and Theta corresponds to the end 1 or end 2 servo joints. This is seen in the figure below.

Inverse Kinematics Spreadsheet to calculate angles

The first subroutine I will explain is the ikSelectHandler subroutine, as it is the simplest. This subroutine is a custom command that uses the Arxterra app/control panel. This is a select button that has an option between Side 1 and Side 2. When the subroutine is called, and the user selects an option, the vector element “param[0]” recieves either a 0 or a 1. This element value is saved in the global variable “globalIKSelector”. If globalIKSelector is a 0 that corresponds to side 1, and if it’s a 1 it corresponds to side 2. This is used later for determining what side to perform the Inverse Kinematics on.

MoveHandler Subroutine

The second subroutine that deals with the inverse kinematics is the MoveHandler subroutine. The overall goal of this subroutine is to move the limbi, with respect to either the middle 1 joint or middle 2 joint, in the forward, backwards, left or right direction. If globalIKSelector is a 0, then the reference point will be the middle 1 joint and the end point will be the end of the length on the end 1 joint. If the globalIKSelector is a 1, then the reference point will be the middle 2 joint, and the end point will be the end of the length on the end 2 joint.

I will only explain how the inverse Kinematics works on one side, as it is the same on both sides, and this way it will be easier to explain and understand. Using the middle 1 joint as our reference point, the location of this joint becomes (0,0) in an X,Y Plane, with the position of the end point on the end 1 joint being in the positive Y direction when fully extended.

The move command is called whenever the user presses either of the buttons on the joystick in the control panel/app. When this happens, the first thing that happens is that the MoveHandler calls the subroutines forwardKinematicsX and forwardKinematicsY. These subroutines use the globalPhi and globalTheta which are saved each time an angle is written to middle1 servo and end1 servo. Some arithmetic is performed on the angles to get the desired angle for the equation, and then the forward kinematics are calculated using these angles to determine what the current X,Y position of the end point is. The X and Y values are returned back to the MoveHandler.

Once the X and Y values are in the moveHandler, the code determines what position we wish to go to. The joystick in the app/control panel is meant for motors, and determines the direction and speed of the motor. The limbi code only utilizes the direction to determine what our desired X and Y position is. The moveHandler then has a conditional statement to determine whether we will be increasing or decreasing in the X or Y position. The vector param, has a length of 4, however the elements param[0] and param[2] contain the direction of the joysticks. Param[0] = 1 & param[2]  = 1 corresponds to forward, param[0] = 2 & param[2]  = 2 corresponds to backwards, param[0] = 1 & param[2]  = 2 corresponds to right, and param[0] = 2 & param[2]  = 1 corresponds to left. The conditional statement uses these parameters, and if it’s forward it will add only to the Y position, if it’s backwards, it will subtract only from the Y position, if it’s left it will subtract from the X position, and if it’s right it will add to the X position.

This X and Y position are now sent to the subroutines inverseKinematicsPhi and inverseKinematicsTheta. These subroutines perform inverse kinematics calculations on the X and Y position that was determined we desire the limbi to go to, and then calculate and angles for Phi and Theta that will move the endpoint to that location. These values are then returned to the Move Handler.

Once the MoveHandler has the final angles calculated by the inverse kinematics, it then writes the angles to the corresponding servos.

Mechanical/Hardware Design

There are three main mechanical assembly for Limbi: Arm, Docking mechanism, and the module. Rapid prototyping lead to several critical design changes.

Limbi Final Mechanical Design Blog Post

Step by Step instruction on how to assemble Limbi

Pre-PDR androgynous docking ideas

Verification & Validation Test Plan

For more details see the Test Plan Document

For the Limbi project we will verify that our design meets design requirements through the Verification Test Plan. We have four separate test cases. Each test case will have a test plan that walks the tester through how to test each requirement and provides a detailed success criteria which determines if the requirement passes verification. The test cases include TC-1, TC-2,TC-3, and TC-4. Test Case 1 is a measurement and inspection test that verifies easily verifiable requirements such as “will” requirements, size requirements, and requirements verifiable by data sheet. Test Case 2 is a test that verifies servo movement and mobility in simple tasks; this test will verify that the servos meet the requirements of basic movement and that the Arxterra app is correctly controlling each servo and moving it the desired amount. Test Case 3 tests complex servo movement which relies on inverse kinematics and includes getting the Limbi arm into the docking slot. Test Case 4 starts with the assumption that the cross has been inserted into Module 2 and is a test that verifies the ability of the Limbi to securely dock and transfer power between the Module and the arm. Test Case 2-4 will represent our mission profile and complete the task of validating that the right product was built for the mission. This mission plan will start with the assumption that the Limbi arm is docked to a secured base module. From there the Limbi arm will be maneuvered using the Arxterra app to come close to Module 2, insert the cross into Module 2, and then dock with Module 2 by rotating the cross and inserting the solenoid. Once the arm is docked to Module 2 (or with the assumption that the arm starts docked to Module 2) the Limbi arm will then maneuver Module 2 to be next to Module 1. Using the magnets on the module-to-module faces Module 1 and Module 2 will dock. Once Module 1 and Module 2 are docked, the Limbi arm will undock from Module 1 while keeping docked to Module 2. It will then move away from Module 1 to visually demonstrate that it is no longer being powered by Module 1 and has transferred to Module 2 as a power source.

Concluding Thoughts and Future Work

Future Work

  1. 3DOT version
    • Scaled down (One-print assembly)
    • 3DOT MCU
  2. Live video feed; re-design docking cross around TTL serial camera and add it to Mission Control Center
  3. Full range actuator (vs servo with just 180 deg range) for joints for an easier inverse kinematic
  4. Photo interrupter for feedback for the solenoid valve positive lock
  5. A better low power momentumless docking mechanism design that is closer to androgynous/universal

Rapid prototyping helped our team tremendously. Though we did not caught all (ie the limited range of servo restricted some of the movement of the arm causing last minute change with the dimensions of the limbs), we saw design flaws early in the design process because of rapid prototyping that saved us time and resources. We are also able to make adjustment such as tolerances and placement of parts early that help us kept the design process going.

Top 3 tips

1)  Take ownership of the project (especially PM)

2)  Don’t be afraid to spend money on sensors/actuators/modules and try your ideas. If you don’t get reimburse, keep it and use it for a different project.

3)  Always acknowledge customer’s feedback/suggestions.

References/Resources

These are the starting resource files for the next generation of robots. All documentation shall be uploaded, linked to, and archived in to the Arxterra Google Drive. The “Resource” section includes links to the following material.

  1. Program and Project Objective and Mission Profile 
  2. JPL Limbi Paper
  3. Limbi Video
  4. CDR
  5. PDR
  6. Schedule Breakdown and Action Items
  7. Verification and Validation Plan
  8. Solidworks File
    Limbi Mechanical Design Blog Post
  9. Fritzing Files
  10. EagleCAD files
  11. Arduino and/or C++ Code
  12. Limbi Final BoM