-
-
Save JamesNewton/c8598878736442c440bbe41d086291ac to your computer and use it in GitHub Desktop.
// title : OpenJSCAD.org encoder disk | |
// author : James Newton | |
// license : MIT License | |
// revision : 0 | |
// tags : hdrobotics.com | |
// file : encoderdisk.jscad | |
// http://openjscad.com/#https://gist.githubusercontent.com/JamesNewton/c8598878736442c440bbe41d086291ac/raw//encoderdisk.jscad | |
function getParameterDefinitions() { | |
return [ | |
{ name: 'disk', type: 'float', initial: 77, caption: "disk diameter:" }, | |
{ name: 'hub', type: 'float', initial: 4, caption: "hub diameter:" }, | |
{ name: 'slots', type: 'int', initial: 80, caption: "number of slots:" }, | |
{ name: 'slotd', type: 'float', initial: 11, caption: "slots inset:" }, | |
{ name: 'slotlength', type: 'float', initial: 3.5, caption: "slot length:" }, | |
{ name: 'mask', type: 'float', initial: 0.8, caption: "slot mask width:" }, | |
{ name: 'cutfudge', type: 'float', initial: 0.4, caption: "laser cut width or<br> (-) filament spread:" }, | |
{ name: 'thick', type: 'float', initial: 2, caption: "material thickness:" }, | |
{ name: 'othick', type: 'float', initial: 0.5, caption: "opaque thickness:" }, | |
{ name: 'output', type: 'choice', caption: 'Output:', values: [0, 1], captions: ["Assembly", "Parts"], initial: 0 } ]; | |
} | |
function main () { | |
const M3r = 1.5 //M3 diameter | |
var thick = params.thick | |
var thin = params.othick | |
var slotwidth = Math.PI*params.disk/params.slots/2 - params.cutfudge; | |
// LED / photodiodes are T3, (3mm) so center to center ~4mm min | |
var senseslots = Math.ceil(4.0 / Math.PI*params.disk/params.slots)*2 | |
var sensespace = senseslots*1.5 | |
var slotinc = 360.0/params.slots | |
var slotd = params.disk - params.slotd //disk diameter less slot inset | |
var basesize=params.disk+10 | |
var edge = (basesize-params.disk)/2 | |
var supsize = basesize/2+params.hub | |
var slots = [] | |
for(i=0.0; i<360; i+=slotinc) { | |
slots.push( | |
rotate([0,0,i], | |
translate([slotd/2,-slotwidth/2,-1], | |
square({size: [params.slotlength,slotwidth]}) | |
) | |
) | |
); | |
} | |
var disk = circle({r: params.disk/2-0.5, fn: 50, center: true}) | |
.subtract(circle({r: params.hub/2, h:2, center: true})) | |
.subtract(slots) | |
var disksup = circle({r: params.disk/6, center: true}) | |
.subtract(circle({r: params.hub/2, h:2, center: true})) | |
var base = square({size: [basesize, basesize], center:true}) | |
.subtract(translate([slotd/2+params.slotlength/2,+sensespace/2,0],circle({r:1.5, center:true}))) | |
.subtract(translate([slotd/2+params.slotlength/2,-sensespace/2,0],circle({r:1.5, center:true}))) | |
.subtract(translate([basesize/2-3,+sensespace/2,0],circle({r: M3r, center:true}))) | |
.subtract(translate([basesize/2-3,-sensespace/2,0],circle({r: M3r, center:true}))) | |
.subtract(circle({r: params.hub/2, center: true})) | |
var washer = circle({r:(params.hub/2+0.8), center: true}) | |
.subtract(circle({r:params.hub/2, h:2, center: true})); | |
var washertop = washer.translate([0,0,0]) | |
var mask = square({size:[slotd-basesize,sensespace+6], center:true}) | |
.subtract(translate([params.slotlength/2,+sensespace/2,0],rotate([0,0,+slotinc],square({size: [params.slotlength,params.mask], center:true})))) | |
.subtract(translate([params.slotlength/2,-sensespace/2,0],rotate([0,0,-slotinc],square({size: [params.slotlength,params.mask], center:true})))) | |
.subtract(translate([-(slotd-basesize)/2-3,+sensespace/2,0],circle({r: 1.5, center:true}))) | |
.subtract(translate([-(slotd-basesize)/2-3,-sensespace/2,0],circle({r: 1.5, center:true}))) | |
var masktop = mask.translate([0,0,0]) | |
var riser = square({size:[edge,sensespace+6], center:true}) | |
.subtract(translate([-0.5,+sensespace/2,0],circle({r: 1.5, center:true}))) | |
.subtract(translate([-0.5,-sensespace/2,0],circle({r: 1.5, center:true}))) | |
var arm = square({size: [slotd,sensespace+6], center:true}) | |
.subtract(circle({r: 1, center: true}).translate([-slotd/3,0])) | |
.subtract(circle({r: params.hub/2, center: true}).translate([slotd/2-params.hub,0])) | |
.translate([-slotd/2+params.hub,0]) | |
var sensors = square({size:[slotd-basesize,sensespace+6], center:true}) | |
.subtract(translate([params.slotlength/2,+sensespace/2,0],circle({r: 1.5, center:true}))) | |
.subtract(translate([params.slotlength/2,-sensespace/2,0],circle({r: 1.5, center:true}))) | |
.subtract(translate([-(slotd-basesize)/2-3,+sensespace/2,0],circle({r: M3r, center:true}))) | |
.subtract(translate([-(slotd-basesize)/2-3,-sensespace/2,0],circle({r: M3r, center:true}))) | |
var support = square({size: [supsize,sensespace+6], center:true}) | |
.subtract(circle({r: params.hub/2, center: true}).translate([-(basesize/2+params.hub)/2+params.hub,0])) | |
.subtract(translate([supsize/2-edge/2-0.5,+sensespace/2,0],circle({r: M3r, center:true}))) | |
.subtract(translate([supsize/2-edge/2-0.5,-sensespace/2,0],circle({r: M3r, center:true}))) | |
.subtract(translate([supsize/2-edge-params.slotd/2+params.slotlength/2,+sensespace/2,0],circle({r: 1.5, center:true}))) | |
.subtract(translate([supsize/2-edge-params.slotd/2+params.slotlength/2,-sensespace/2,0],circle({r: 1.5, center:true}))) | |
.translate([supsize/2-params.hub,0]) | |
var assembly = [ | |
linear_extrude({height: thick},base).setColor(1,0.5,0.3), | |
linear_extrude({height: thick},washer).translate([0,0,thick]), | |
linear_extrude({height: thin},mask).translate([slotd/2,0,thick]).setColor([.2,.2,.2]), | |
linear_extrude({height: thick*2},riser).translate([basesize/2-edge/2,0,thick+params.othick]).setColor(1,0.5,0.3), | |
linear_extrude({height: thin},disksup).translate([0,0,thick*2]).setColor([.2,.2,.2]), | |
linear_extrude({height: thin},disk).translate([0,0,thick*2+thin]).setColor([.2,.2,.2]), | |
linear_extrude({height: thick},arm).translate([0,0,thick*2+thin*2]) | |
.setColor(1,0.5,0.3), | |
linear_extrude({height: thin},masktop).translate([slotd/2,0,thick*3+thin]).setColor([.2,.2,.2]), | |
linear_extrude({height: thick},washertop).translate([0,0,thick*3+thin*2]), | |
linear_extrude({height: thick},sensors).translate([slotd/2,0,thick*3+thin*2]).setColor(1,0.5,0.3), | |
linear_extrude({height: thick},support).translate([0,0,thick*4+thin*2]).setColor(1,0.5,0.3), | |
] | |
var cut = [] | |
cut.push(base) | |
cut.push(arm) | |
cut.push(support) | |
cut.push(riser) //need 2 of these... | |
cut.push(riser.translate([0,0,0])) | |
cut.push(sensors) | |
cut.push(washer) | |
cut.push(washertop) | |
//these should be in a separate cut. | |
cut.push(disk) | |
cut.push(disksup) | |
cut.push(mask) | |
cut.push(masktop) | |
if (0 == params.output) out = assembly; | |
else { out = binPack(cut); } | |
return out; | |
} | |
function binPack(pieces2D) { | |
var packingEpsilon = 3; //default spacing. | |
// add .w and .h to each piece based on bounds + spacing | |
for (var i = 0; i < pieces2D.length; i++) { | |
var bounds = pieces2D[i].getBounds(); | |
pieces2D[i].w = bounds[1].x - bounds[0].x + packingEpsilon; | |
pieces2D[i].h = bounds[1].y - bounds[0].y + packingEpsilon; | |
} | |
//sort by largest dimension | |
// https://github.com/jakesgordon/bin-packing/pull/3/commits/a9c72459a968f2be622917f0e05e3dfbeb919720 | |
pieces2D.sort(function(a,b) { return (max(b.w, b.h) - max(a.w, a.h)); }); | |
//pack | |
var packer = new GrowingPacker(); | |
packer.fit(pieces2D); | |
//check, output | |
var out = [] | |
for (var i = 0; i < pieces2D.length; ++i) { | |
var p2D = pieces2D[i]; | |
if (!p2D.fit) { | |
die("Couldn't fit: " + JSON.stringify(p2D)); | |
} | |
var b = p2D.getBounds(); | |
out.push(p2D.translate([p2D.fit.x - b[0].x, p2D.fit.y - b[0].y, 0])); | |
} | |
return union(out) | |
} | |
////////////////////////////////////////////////////////////////////// | |
// start include('packer.growing.js'); | |
// Ami NOTE: grabbed from https://github.com/jakesgordon/bin-packing/blob/master/js/packer.growing.js on 20160803 (not modified since 2011) | |
/****************************************************************************** | |
This is a binary tree based bin packing algorithm that is more complex than | |
the simple Packer (packer.js). Instead of starting off with a fixed width and | |
height, it starts with the width and height of the first block passed and then | |
grows as necessary to accomodate each subsequent block. As it grows it attempts | |
to maintain a roughly square ratio by making 'smart' choices about whether to | |
grow right or down. | |
When growing, the algorithm can only grow to the right OR down. Therefore, if | |
the new block is BOTH wider and taller than the current target then it will be | |
rejected. This makes it very important to initialize with a sensible starting | |
width and height. If you are providing sorted input (largest first) then this | |
will not be an issue. | |
A potential way to solve this limitation would be to allow growth in BOTH | |
directions at once, but this requires maintaining a more complex tree | |
with 3 children (down, right and center) and that complexity can be avoided | |
by simply chosing a sensible starting block. | |
Best results occur when the input blocks are sorted by height, or even better | |
when sorted by max(width,height). | |
Inputs: | |
------ | |
blocks: array of any objects that have .w and .h attributes | |
Outputs: | |
------- | |
marks each block that fits with a .fit attribute pointing to a | |
node with .x and .y coordinates | |
Example: | |
------- | |
var blocks = [ | |
{ w: 100, h: 100 }, | |
{ w: 100, h: 100 }, | |
{ w: 80, h: 80 }, | |
{ w: 80, h: 80 }, | |
etc | |
etc | |
]; | |
var packer = new GrowingPacker(); | |
packer.fit(blocks); | |
for(var n = 0 ; n < blocks.length ; n++) { | |
var block = blocks[n]; | |
if (block.fit) { | |
Draw(block.fit.x, block.fit.y, block.w, block.h); | |
} | |
} | |
******************************************************************************/ | |
GrowingPacker = function() { }; | |
GrowingPacker.prototype = { | |
fit: function(blocks) { | |
var n, node, block, len = blocks.length; | |
var w = len > 0 ? blocks[0].w : 0; | |
var h = len > 0 ? blocks[0].h : 0; | |
this.root = { x: 0, y: 0, w: w, h: h }; | |
for (n = 0; n < len ; n++) { | |
block = blocks[n]; | |
if (node = this.findNode(this.root, block.w, block.h)) | |
block.fit = this.splitNode(node, block.w, block.h); | |
else | |
block.fit = this.growNode(block.w, block.h); | |
} | |
}, | |
findNode: function(root, w, h) { | |
if (root.used) | |
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h); | |
else if ((w <= root.w) && (h <= root.h)) | |
return root; | |
else | |
return null; | |
}, | |
splitNode: function(node, w, h) { | |
node.used = true; | |
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h }; | |
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h }; | |
return node; | |
}, | |
growNode: function(w, h) { | |
var canGrowDown = (w <= this.root.w); | |
var canGrowRight = (h <= this.root.h); | |
var shouldGrowRight = canGrowRight && (this.root.h >= (this.root.w + w)); // attempt to keep square-ish by growing right when height is much greater than width | |
var shouldGrowDown = canGrowDown && (this.root.w >= (this.root.h + h)); // attempt to keep square-ish by growing down when width is much greater than height | |
if (shouldGrowRight) | |
return this.growRight(w, h); | |
else if (shouldGrowDown) | |
return this.growDown(w, h); | |
else if (canGrowRight) | |
return this.growRight(w, h); | |
else if (canGrowDown) | |
return this.growDown(w, h); | |
else | |
return null; // need to ensure sensible root starting size to avoid this happening | |
}, | |
growRight: function(w, h) { | |
this.root = { | |
used: true, | |
x: 0, | |
y: 0, | |
w: this.root.w + w, | |
h: this.root.h, | |
down: this.root, | |
right: { x: this.root.w, y: 0, w: w, h: this.root.h } | |
}; | |
if (node = this.findNode(this.root, w, h)) | |
return this.splitNode(node, w, h); | |
else | |
return null; | |
}, | |
growDown: function(w, h) { | |
this.root = { | |
used: true, | |
x: 0, | |
y: 0, | |
w: this.root.w, | |
h: this.root.h + h, | |
down: { x: 0, y: this.root.h, w: this.root.w, h: h }, | |
right: this.root | |
}; | |
if (node = this.findNode(this.root, w, h)) | |
return this.splitNode(node, w, h); | |
else | |
return null; | |
} | |
} | |
// end include('packer.growing.js'); | |
die = function(msg) { | |
throw "error: " + msg; | |
} |
Cycloydal drive: input stator (on motor body, with outer ring of fixed pins), crank on shaft w/offset pin to center of..., cam, output disk spinning on bearing riding motor shaft with inner ring of pins, output stator holds output shaft. Cam is a rotated set of thin "rectangles" with length defined by magic equation.
Version 2: Use two sets of slits, one inside the other, with a slightly different number of slits in each. The phase changes, but that gives you absolute position.
Possible choices for uC
http://www.cypress.com/part/cy8c4247azi-m485 with 8 channel, 12 bit ADC 1-Msps, programmable analog front end (no need for external opamps or manual level adj), 48macrocells for some FPGA ability. 128K FLASH, 16K RAM, 48 Mhz, 32 bit ARM Cortex-M0.
as featured in these development systems:
http://www.cypress.com/documentation/development-kitsboards/psoc-4-cy8ckit-049-4xxx-prototyping-kits $4 or...
http://www.cypress.com/documentation/development-kitsboards/cy8ckit-043-psoc-4-m-series-prototyping-kit $10 with a debugger.
IDE includes schematic capture FPGA designer, etc... Windows only IDE. Closed source (of course)
Encoder: base, bearing, washer (on inner race), opaque code disk, moving arm, washer, bearing, top support arm. At edge: holes in base for emitters, opaque mask, (code disk edge), opaque detector mount, top support arm. Detector to support mount should be adjustable.