1"use strict";(self.webpackChunktechtoolbox=self.webpackChunktechtoolbox||[]).push([[9413],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>h});var o=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function i(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){a(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function l(e,t){if(null==e)return{};var n,o,a=function(e,t){if(null==e)return{};var n,o,a={},r=Object.keys(e);for(o=0;o<r.length;o++)n=r[o],t.indexOf(n)>=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(o=0;o<r.length;o++)n=r[o],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var d=o.createContext({}),s=function(e){var t=o.useContext(d),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=s(e.components);return o.createElement(d.Provider,{value:t},e.children)},u="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},m=o.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,d=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),u=s(n),m=a,h=u["".concat(d,".").concat(m)]||u[m]||p[m]||r;return n?o.createElement(h,i(i({ref:t},c),{},{components:n})):o.createElement(h,i({ref:t},c))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,i=new Array(r);i[0]=m;var l={};for(var d in t)hasOwnProperty.call(t,d)&&(l[d]=t[d]);l.originalType=e,l[u]="string"==typeof e?e:a,i[1]=l;for(var s=2;s<r;s++)i[s]=n[s];return o.createElement.apply(null,i)}return o.createElement.apply(null,n)}m.displayName="MDXCreateElement"},4843:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>i,default:()=>p,frontMatter:()=>r,metadata:()=>l,toc:()=>s});var o=n(7462),a=(n(7294),n(3905));const r={"sidebar-position":5},i="Deadwheel Odometry (Mecanum and Tank)",l={unversionedId:"odo/md",id:"odo/md",title:"Deadwheel Odometry (Mecanum and Tank)",description:"GM0's Odometry Page - Must Read!* (Explains deadwheel configurations and provides easy to implement pseudocode + a overview behind the for 3 wheel odometry)",source:"@site/docs/odo/md.md",sourceDirName:"odo",slug:"/odo/md",permalink:"/docs/odo/md",draft:!1,tags:[],version:"current",frontMatter:{"sidebar-position":5},sidebar:"tutorialSidebar",previous:{title:"Mecanum (No Deadwheels)",permalink:"/docs/odo/Mecanum"},next:{title:"VSLAM",permalink:"/docs/odo/vslam"}},d={},s=[{value:"2 Deadwheel Odometry",id:"2-deadwheel-odometry",level:2},{value:"3 Deadwheel Odometry",id:"3-deadwheel-odometry",level:2}],c={toc:s},u="wrapper";function p(e){let{components:t,...r}=e;return(0,a.kt)(u,(0,o.Z)({},c,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"deadwheel-odometry-mecanum-and-tank"},"Deadwheel Odometry (Mecanum and Tank)"),(0,a.kt)("admonition",{title:"Resources",type:"note"},(0,a.kt)("ul",{parentName:"admonition"},(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://gm0.org/en/latest/docs/software/concepts/odometry.html"},"GM0's Odometry Page")," - ",(0,a.kt)("strong",{parentName:"li"},"Must Read!")," (Explains deadwheel configurations and provides easy to implement pseudocode + a overview behind the for 3 wheel odometry)"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/acmerobotics/road-runner/blob/master/doc/pdf/Mobile_Robot_Kinematics_for_FTC.pdf"},"RoadRunner's Odometry Paper")))),(0,a.kt)("p",null,"Deadwheel odometry makes use of unpowered wheels connected to encoders to track the robot's current position. Essentially, a deadwheel odometry algorithm converts the returned encoder measurements into useful values."),(0,a.kt)("p",null,"For maximized accuracy, most teams make use of three deadwheel setups. However if a team needs an extra encoder slot, some will make do with a two-deadwheel setup and the IMU for angle calculation."),(0,a.kt)("p",null,"Why use 3-wheel odometry as opposed to 2-wheel: ",(0,a.kt)("a",{parentName:"p",href:"https://learnroadrunner.com/#what-is-the-difference-between-two-and-three-wheel-odometry"},"Answered in RoadRunner docs")),(0,a.kt)("h2",{id:"2-deadwheel-odometry"},"2 Deadwheel Odometry"),(0,a.kt)("p",null,"Intuitively speaking, we know that a mecanum drive train can move both forward and sideways. Therefore when making use of a two-wheel odometry setup we can see that you only need to use one of the parallel wheels."),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},"Note that because we are removing one of the parallel wheels, you cannot calculate the robot's current angle through odometry alone. Instead, you must make use of the IMU.")),(0,a.kt)("p",null,(0,a.kt)("img",{alt:"Example banner",src:n(1665).Z,width:"925",height:"477"})),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-java"},"public class Odometry {\n // Constants\n public final double ENCODER_WHEEL_DIAMETER = 1.37795; // diameter of the deadwheel\n private final double ENCODER_TICKS_PER_REVOLUTION = 8154; // ticks measured after\n // one full revolution of the deadwheel\n private final double ENCODER_WHEEL_CIRCUMFERENCE = Math.PI * 2.0 * (ENCODER_WHEEL_DIAMETER * 0.5);\n\n // Variables\n private double xPos, yPos;\n public double angle;\n private double lastLeftEnc = 0, lastNormalEnc = 0;\n\n public Odometry(double xPos, double yPos) {\n this.xPos = xPos;\n this.yPos = yPos;\n }\n\n // Two Deadwheel Odo\n /*\n l = ticks from the parallel odometry wheel\n r = ticks from the perpendicular odometry wheel\n ang = robot's angle (in degrees)\n */\n public void updatePosition(double l, double n, double ang) {\n double dL = l - lastLeftEnc;\n double dN = n - lastNormalEnc;\n lastNormalEnc = n;\n lastLeftEnc = l;
1\n \n double leftDist = -dL * ENCODER_WHEEL_CIRCUMFERENCE / ENCODER_TICKS_PER_REVOLUTION;\n double dyR = leftDist;\n double dxR = -dN * ENCODER_WHEEL_CIRCUMFERENCE / ENCODER_TICKS_PER_REVOLUTION;\n \n double cos = Math.cos((Angle.degrees_to_radians(ang)));\n double sin = Math.sin((Angle.degrees_to_radians(ang)));\n double dx = (dxR * sin) + (dyR * cos);\n double dy = (-dxR * cos) + (dyR * sin);\n \n angle = ang;\n xPos += dx;\n yPos += dy;\n }\n \n public double getX() {\n return xPos;\n }\n\n public double getY() {\n return yPos;\n }\n}\n")),(0,a.kt)("h2",{id:"3-deadwheel-odometry"},"3 Deadwheel Odometry"),(0,a.kt)("p",null,"Due to the extensive explanations written in the links provided at the top of the module, we feel that it would be redundant to provide our own. Instead, we will just give you an implementation."),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://github.com/Beta8397/virtual_robot/blob/master/TeamCode/src/org/firstinspires/ftc/teamcode/EncBot.java"},"The following code is from the samples of Beta8397's virtual robot simulator:\n")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-java"},'/**\n * Utility class that represents a robot with mecanum drive wheels and three "dead-wheel" encoders.\n */\npublic class EncBot {\n public final double ENCODER_WHEEL_DIAMETER = 2; // diameter of the deadwheel\n private final double ENCODER_TICKS_PER_REVOLUTION = 1120; // ticks measured after\n // one full revolution of the deadwheel\n private final double ENCODER_WIDTH = 12.0; // distance between parallel deadwheels\n private final double ENCODER_WHEEL_CIRCUMFERENCE = Math.PI * 2.0;\n \n public final DcMotorEx[] motors = new DcMotorEx[4]; //back_left, front_left, front_right, back_right\n public final DcMotorEx[] encoders = new DcMotorEx[3]; //right, left, X\n public int[] prevTicks = new int[3];\n public double[] pose = new double[3];\n\n public void init(HardwareMap hwMap){\n String[] motorNames = new String[]{"back_left_motor", "front_left_motor", "front_right_motor", "back_right_motor"};\n for (int i=0; i<4; i++) motors[i] = hwMap.get(DcMotorEx.class, motorNames[i]);\n motors[0].setDirection(DcMotorSimple.Direction.REVERSE);\n motors[1].setDirection(DcMotorSimple.Direction.REVERSE);\n // store deadwheels in an array. \n String[] encoderNames = new String[]{"enc_right", "enc_left", "enc_x"};\n for (int i=0; i<3; i++) encoders[i] = hwMap.get(DcMotorEx.class, encoderNames[i]);\n }\n\n public void setDrivePower(double px, double py, double pa){\n double[] p = new double[4];\n p[0] = -px + py - pa;\n p[1] = px + py - pa;\n p[2] = -px + py + pa;\n p[3] = px + py + pa;\n double max = Math.max(1, Math.max(Math.abs(p[0]), Math.max(Math.abs(p[1]), Math.max(Math.abs(p[2]), Math.abs(p[3])))));\n if (max > 1) for (int i=0; i<4; i++) p[i] /= max;\n for (int i=0; i<4; i++) motors[i].setPower(p[i]);\n }\n\n public void resetOdometry(double x, double y, double headingRadians){\n pose[0] = x;\n pose[1] = y;\n pose[2] = headingRadians;\n for (int i=0; i<3; i++) prevTicks[i] = encoders[i].getCurrentPosition();\n }\n\n // call updateOdometry everytime you set powers to your drive motors. \n public double[] updateOdometry(){\n int[] ticks = new int[3];\n for (int i=0; i<3; i++) ticks[i] = encoders[i].getCurrentPosition();\n int newRightTicks = ticks[0] - prevTicks[0];\n int newLeftTicks = ticks[1] - prevTicks[1];\n int newXTicks = ticks[2] - prevTicks[2];\n prevTicks = ticks;\n \n double rightDist = newRightTicks * ENCODER_WHEEL_CIRCUMFERENCE / ENCODER_TICKS_PER_REVOLUTION;\n double leftDist = -newLeftTicks * ENCODER_WHEEL_CIRCUMFERENCE / ENCODER_TICKS_PER_REVOLUTION;\n double dyR = 0.5 * (rightDist + leftDist);\n double headingChangeRadians = (rightDist - leftDist) / ENCODER_WIDTH;\n double dxR = -newXTicks * ENCODER_WHEEL_CIRCUMFERENCE / ENCODER_TICKS_PER_REVOLUTION;\n double avgHeadingRadians = pose[2] + headingChangeRadians / 2.0;\n \n double cos = Math.cos(avgHeadingRadians);\n double sin = Math.sin(avgHeadingRadians);\n pose[0] += dxR*sin + dyR*cos;\n pose[1] += -dxR*cos + dyR*sin;\n pose[2] = AngleUtils.normalizeRadians(pose[2] + headingChangeRadians);\n \n return pose;\n }\n\n public double[] getPose(){\n return pose;\n }\n\n}\n')))}p.isMDXComponent=!0},1665:(e,t,n)=>{n.d(t,{Z:()=>o});const o=n.p+"assets/images/img_2-85af102f7b25386da574a775dd8023cd.png"}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.