tag:blogger.com,1999:blog-7870846282286990512024-03-18T22:44:31.015-07:00Ich bin un Bricoleurif the big hammer doesn't work<br>
we give it the giant screwMichael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.comBlogger221125truetag:blogger.com,1999:blog-787084628228699051.post-80015284442514429662020-01-04T16:53:00.000-07:002020-01-04T16:53:15.813-07:00Neo-Non-Conceptual Art<span style="font-size: large;"><b>If....</b></span><br />
<br />* <a href="http://www.jeffkoons.com/sites/default/files/artwork-images/lady-gaga-artrave_0.jpg" target="_blank">Jeff Koons</a> can afford an atelier of assistants to polish his balls;<br /><br />* <a href="https://www.thisiscolossal.com/2018/09/play-urs-fischer/" target="_blank">Urs Fischer</a> can hire someone to construct a set of autonomous office chairs;<br /><br />* <a href="https://www.nytimes.com/2019/12/06/arts/design/banana-art-basel-miami.html" target="_blank">Mauricio Catellan</a> can be praised for his Duchampian wit, a century on;<br /><br />* <a href="https://www.nytimes.com/2018/08/13/t-magazine/darren-bader-art.html" target="_blank">Darren Bader</a> can get a warm review in the Times for his re-heated thoughts;<br /><br /><span style="font-size: large;"><b>Then....</b></span><br />
<br />
It seems that even the <b>ideas</b> of post-post-modern artists' are of little utility.<br /><br /><b><span style="font-size: large;">Therefore....</span></b><br />
<br />
Along with no longer producing anything of intrinsic value, I should also outsource the conceptualizing. Surely there are artists in other countries who can have ideas much more economically than me?<br /><br />After that, given its glaringly short supply in the developed world, I might as well suspend my judgement as well.<br /><br />So, from here on in, just assume that, if I had thought it was worth the effort, I would have done it.<br />
<br />
<span style="font-size: large;"><b>Already....</b></span><br /><br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-55493013285403706592019-06-19T12:25:00.000-06:002019-06-19T12:25:29.352-06:00and for completness sakeA short Variations Too documentation video from Currents 2019 in Santa Fe:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/Hr0DTnd2q_w/0.jpg" src="https://www.youtube.com/embed/Hr0DTnd2q_w?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
<div style="text-align: center;">
<span style="font-size: x-small;">(many thanks to my spokes-model, Raina Wellman!)</span></div>
Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-5250578004596314332019-04-29T13:07:00.000-06:002019-04-29T13:07:41.511-06:00Variations Too -- The ProgramI seem to have (finally) come to a rest in the development cycle of the Variations project so I thought I should post the actual Arduino code, for completeness sake....<br />
<br />
<a href="http://www.etantdonnes.com/DATA/VariationsToo.zip" target="_blank">VariationsToo.zip</a><br />
<br />
You will also need all (or most of) my previously described libraries:<br />
<br />
<a href="http://www.etantdonnes.com/DATA/schipArduino.zip">schipArduino.zip</a><br />
<br />
I had to give up on the HCSR04 sonar distance detector because it was unreliable, noisy, and sensitive to vibrations when the arm servo motors were running. I replaced it with my old favorite, the GP2Y0A02YK (or equivalent) IR distance sensor that triggers out at about 2 meters. This only needs an ADC channel to interface, so it's a lot simpler anyway.<br />
<br />
<b>Variations Too</b> will be in:<br />
<h2 class="heading" style="text-align: center;">
<a href="https://currentsnewmedia.org/festivals/currents-new-media-2019/" target="_blank">CURRENTS NEW MEDIA 2019</a></h2>
<div class="subtitle">
in Santa Fe, NM, from June 7 - 23. So come on by!<br />
<br />
<span style="font-size: x-small;">...You and I both have to wait until the piece is installed in the
gallery before I can make a decent video because I have run out of room
for clean backdrops in my home/studio... </span><br />
<br /></div>
Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-46229817931061632062019-03-19T11:35:00.000-07:002019-03-19T11:35:31.614-07:00Random ThoughtI have to say, that, in general, I do not believe in <b>randomness</b>. I'm sure there are some Quantum Mechanics (Maniacs?) out there who will beg to differ and provide supporting arguments, but until then....<br /><br />Let's say I flip a coin. This particular flip comes up HEADS. Can you provide me with a proof that it <u>could</u> have been TAILS? Sure, sure, you can show that the <u>next</u> few flips might have different outcomes, and further that the next 1 billion flips will <u>average</u> dangnabbitedly close to 50% each. But that's not what I asked. I want <b>proof</b> that the original action might have taken a turn to the T-side. Since that has already (not) happened it is in the -- still apparently -- inviolable past and cannot be changed. So maybe it wasn't random at all?<br />
<br />
Don't get me wrong, I'm not trying to argue that we can predict the future. Both complicatedness (many moving parts) and complexity (intersecting feedback loops) make that practically and theoretically impossible.<br />
<br />
I'm just saying that we can predict the past.Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com1tag:blogger.com,1999:blog-787084628228699051.post-31804486766833798602019-02-04T15:12:00.000-07:002019-02-04T15:12:34.642-07:00Fixed Point Failure<h4 style="text-align: center;">
A Fixed Point math library and Neural Net demo<br />
for the Arduino...</h4>
<h4 style="text-align: center;">
Or: Multiple cascading failures all in one place!</h4>
<br />
Last year I found a simple self-contained Artificial Neural Net demo written for the Arduino at: <a href="http://robotics.hobbizine.com/arduinoann.html" target="_blank">robotics.hobbizine.com/arduinoann.html</a> and spent a goodly amount of time futzing around with it. I now, almost, understand <b>HOW</b> they work, but have only a glimmering of insight into <b>WHY</b>. The demo does something really silly: The inputs are an array of bit patterns used to drive a 7-segment numeric display and the outputs are the binary bit pattern for that digit (basically the reverse of a binary to 7-segment display driver). Someone not totally under the influence of ANNs could do this with a simple 10 byte lookup table. But that is not us. On the plus side it _learns_ how to do the decoding by torturous example, so we don't have to bother our tiny brains with the task of designing the lookup table.<br />
<br />
<i>HOW</i> ANNs work on the Arduino is:<br />
<ul>
<li> a) Extremely slowly, because they use a metric shit-ton of floating point arithmetic; and,</li>
<li>b) Not very interestingly, because each weight takes up 4 bytes of RAM and there is only about 1Kb kicking around after the locals and stack and whatever else is accounted for -- the simple demo program illustrated here uses about half of that 1K just for the forwardProp() node-weights and then the backProp() demo uses the other half for temporary storage. Leaving just about nothing to implement an actually interesting network.</li>
</ul>
But. I thought I could make a small contribution by replacing the floating point -- all emulated in software -- with an integer based Fixed Point implementation -- whose basic arithmetic is directly supported by the ATMEGA hardware. This would also halve the number of bytes used by each weight value. Brilliant yes?<br />
<br />
And in fact. My FPVAL class works (see below for zip file). Except, err, well, it doesn't save any execution time. But more on that later....<br />
<br />
Anyway. The FPVAL implementation uses a 2-byte int16_t as the basic storage element (half the size of the float) and pays for this with a very limited range and resolution. The top byte of the int16 is used as the "integer" portion of the value -- so the range is +/- 128. The bottom byte is used as the fraction portion -- so the resolution is 1/256 or about .0039 per step. On first blush, and seemingly also in fact, this is just about all you need for ANN weights.<br />
<br />
As it turns out, simple 16 bit integer arithmetic Just Works(TM) to manipulate values, with the proviso that some judicious up and down shifting is used to maintain Engineering Tolerances. This is wrapped in a C++ class which overrides all the common arithmetic and logic operators such that FPVALs can be dropped into slots where floats were used without changing (much of) the program syntax. This is illustrated in the neuralNetFP.cpp file, where you can switch between using real floats and FPVALs with the "USEFLOATS" define in netConfig.h.<br />
<br />
Unfortunately it appears that a lot of buggering around is also needed to do the shifting, checking for overflow, and handling rounding errors. This can all be seen in the fpval.cpp implementation file. An interesting(?) aside: I found that I had to do value rounding in the multiply and divide methods -- otherwise the backProp() functions just hit the negative rail without converging.<br />
<br />
I also replaced the exponential in the ANN sigmoid activation function with a stepwise linear extrapolation, which rids the code of float dependencies.<br />
<br />
I forged ahead and got the danged ANN demo to work with either floats or FPVALs. And that's when I found that I wasn't saving any execution time. (Except, for some as yet unexplained reason, the number of FPVAL backprop learning cycles seems to be about 1/4 of that needed when using floats[??]).<br />
<br />
After a lot of quite painful analysis I determined that calling the functions which implement the FPVAL arithmetic entail enough overhead that they are almost equal in execution time to the optimized GCC float library used on the ATMEGA. Most of the painful part of the analysis was in fighting the optimizer, tooth-and-nail, but I will not belabor that process.<br />
<br />
On the other hand, if you are careful to NOT use any floating point values or functions, you can save two bytes per value and around 1Kb of program space. Which might be useful, to someone, sometime.<br />
<br />
<br />
So. What's in this bolus then is the result of all this peregrination. It is not entirely coherent because I just threw in the towel as described above. But. Here it is:<br />
<br />
<center>
<a href="http://www.etantdonnes.com/DATA/schipAANN.zip" target="_blank">http://www.etantdonnes.com/DATA/schipAANN.zip</a><br />
</center>
Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-69895896819260634852019-01-31T14:58:00.000-07:002019-01-31T14:58:30.396-07:00Some Driveline Enhancements<h3>
<i>Variations Too</i>, again</h3>
<h3>
<br /></h3>
So. I've been dragging my feet -- once again -- because everything just seemed too <u>hard</u> over the holidays, but I have made progress none-the-less....<br /><br />While doing limited in-camera demos I found that the <i>Variations</i> second arm linkage just tore itself apart pretty consistently. This was due to there being nothing but a bit of stickyness holding the axle into the arm. I originally used two pins <b>through</b> the whole sandwich to keep the gear from spinning, but I didn't have anything really holding the layers together, and there was too much torque for the sticky to manage.<br />
<br />
This has, perhaps, been remedied:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKFQW6uFrnFvOvQ18TN4dEV2m40jjs7WoNbAnPwiyKn5_MrKulKo6HZtJTuv9casQyB0TTPv7okq-o2lESen69nBtcQmtHUw2mcKpRmzeFnfpcylhF8ZgVjy-WgeHGYKV2bcJ0eQNTbxqX/s1600/axlePins_note.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="751" data-original-width="960" height="311" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKFQW6uFrnFvOvQ18TN4dEV2m40jjs7WoNbAnPwiyKn5_MrKulKo6HZtJTuv9casQyB0TTPv7okq-o2lESen69nBtcQmtHUw2mcKpRmzeFnfpcylhF8ZgVjy-WgeHGYKV2bcJ0eQNTbxqX/s400/axlePins_note.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-size: large;">Improved(?) axle mounting</span></td></tr>
</tbody></table>
<br />
After the arm was all re-assembled, I drilled a hole longitudinally through the circular backing plate and the axle, and glued a 1" long by ~1/32" diameter nail into the hole. This of course requires solid drill press, or mill, mounting and careful attention to not breaking the (@$!@#) miniature drill. Here you can also see the two pins (little brass brads, also about 1/32" dia) that pierce the entire sandwich to prevent the gear from spinning on it's own.<br />
<br />
Compare to the previous layout, where the above photo is looking straight on from the bottom:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQBa5te3VreaPByBy3tY1fPvFq8qQMyl3w2NJ-giPw1s5nkk0ACnHA50hdHNKKQFOiaR8Gr6uL8lZxycq_9gCJAPS5gMyBs_H-gVIWLU4RKpmGNyMSrRiyDPPs_w_6sj11pP3bUWAOJ5OP/s1600/gearLinkage.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="770" data-original-width="952" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQBa5te3VreaPByBy3tY1fPvFq8qQMyl3w2NJ-giPw1s5nkk0ACnHA50hdHNKKQFOiaR8Gr6uL8lZxycq_9gCJAPS5gMyBs_H-gVIWLU4RKpmGNyMSrRiyDPPs_w_6sj11pP3bUWAOJ5OP/s400/gearLinkage.jpg" width="400" /></a></div>
So. After assembling and gluing all the little bits into their sandwich, one needs to fire up the machine shop and drill two transverse holes <u>almost</u> through all of plate-arm-gear layers -- the almost part being that we don't want to completely pierce the gear itself, thus the pins need to be shorter than the full thickness (which may vary according to the arm material). Then rotate the arm and drill a longitudinal hole through the backing plate and axle -- basically straight down, centered where the "Plexiglas backing plate" arrow points in the above -- Gear Linkage -- photo. THEN glue the relevant pins into the holes. I've tried both: Goop, which is a bit hard to get schmushed into the holes but sticks to the pins; and: filled acrylic-solvent glue, which can be squirted into the holes but only sticks to the pins in an advisory way. Fortunately the sticky provides little in the way of mechanical advantage, it only needs to keep the pins in place.<br />
<br />
I did this for the two lower arm linkages and made the executive decision that the torque on the smallest, upper, arm did not merit the extra effort. YMMV...<br />
<h4>
</h4>
<h4>
So.</h4>
I think this may be the end of the mechanical portion of our time together, save perhaps for cable routing which is still rather ad-hoc.Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-79454658739458715472018-11-13T16:47:00.000-07:002018-11-13T16:47:33.772-07:00Sonar! The HCSR04 LibraryFor <i>Variations Too</i> I need some kind of distance sensor to see if there is anyone watching and how interested they might be. For the 'real' <i>Variations</i> I am planning to use a video camera and image processing, but this is the kiddie version.<br />
<br />
<h3>
So...</h3>
I thought I had it all worked out because I've used this cheapo, err, inexpensive, HC-SR04 (aka 19605-UT) ultrasonic sensor, from mpja.com amongst, in other projects using my MSCapture library which turns Arduino Pin 8 into a Timer 1 counting input -- remind me to rant about this sometime, especially since my library is perfect for grabbing IR remote control signals -- but for now.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHiOOmy-UBbPC2YXHEGPd8JWXFgBUdU19VLw23fZp9tZcf5kYI2jvWmQ9Wl4FzGNn52MN56DbnbWPoqZVEpv4ryhExwutMW-MDRZ_9lKCZ5o22L8A6NLg3u16eMngzpCJk40ZyQemNIfVp/s1600/HCSR04front.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="248" data-original-width="409" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHiOOmy-UBbPC2YXHEGPd8JWXFgBUdU19VLw23fZp9tZcf5kYI2jvWmQ9Wl4FzGNn52MN56DbnbWPoqZVEpv4ryhExwutMW-MDRZ_9lKCZ5o22L8A6NLg3u16eMngzpCJk40ZyQemNIfVp/s320/HCSR04front.png" width="320" /></a></div>
<br />
<br />
See the data sheet <a href="http://www.mpja.com/download/HC-SR04.pdf" target="_blank">here</a>.<br />
<br />
But, if you've been reading along, you know that the Servo motor control library is also a big fan of Timer 1 and thus that acre of digital real estate is no longer available. So I had to reinvent the wheel using a different mechanism with Timer 2 which has only an 8 bit resolution and no external count input.<br />
<br />
Therein lies the HCSR04 library in my <a href="http://www.etantdonnes.com/DATA/schipArduino.zip" target="_blank">code bolus</a>.<br />
<br />
It uses three interrupts (you are surprised?), two from Timer 2 and one from an external pin change signal. The counter is run with the maximum pre-scale of /1024, giving a 64 micro-second resolution which is not quite as fine as one would like, but it turns out that the sensor itself is not quite as fine as one would like either, so it sorta works out. It counts the timer overflow interrupts to add 4 more bits to the timing range, which is somewhat more than enough to detect the SR04's no-signal failure signal.<br />
<br />
Two digital pins, and power, are connected to the sensor. One pin is the Trigger output which can be any available digital output pin. It sends a short positive pulse, where the falling edge starts a sonar sample cycle. The other is the Ping input pin which goes high from the end of the sonar ping until it gets an echo response -- or for a loooong time if it misses the echo (more below). The Ping input currently has to be Arduino Pin 2 or 3 -- because I couldn't make sense of the doc for attachInterrupt(), I think one can use other pins but the code will need a light re-wanking.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikUp0nnYx3OkHn172ujMr5yjOdT15uwIBA_QHqO4HiXYH3S7UkNHREZChI0oL1N402ZB4TRL01W028gKg-NTk_A8bgiql2I8nff91yYalFT8pCVvXBEcIna_zUkBam1gVe5jt7iLpYjfYD/s1600/HC-SR04waveform.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="354" data-original-width="797" height="142" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikUp0nnYx3OkHn172ujMr5yjOdT15uwIBA_QHqO4HiXYH3S7UkNHREZChI0oL1N402ZB4TRL01W028gKg-NTk_A8bgiql2I8nff91yYalFT8pCVvXBEcIna_zUkBam1gVe5jt7iLpYjfYD/s320/HC-SR04waveform.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">HC-SR04 signals</td></tr>
</tbody></table>
<br />
<h3>
The library class has these methods:</h3>
<span style="font-size: x-small;"><span style="font-family: "Courier New", Courier, monospace;"> /** default constructor **/<br /> HCSR04(); <br /><br /> /** initialize the HC-SR04 sensor pins and timer interrupts<br /> ** leaves all the interrupts disabled<br /> ** use SR04.startPing() to start a sample cycle **/<br /> void init( uint8_t trigpin, uint8_t pingpin );<br /><br /> /** Start a sensor ping cycle<br /> ** turns on TRIGPIN and enables interrupts<br /> ** Presumes that SR04init() initTimer2() have already been called. **/<br /> void startPing(void);<br /><br /> /** return true if there is a new distance value available<br /> ** will clear itself, so a second call will return false... **/<br /> bool available(void);<br /><br /> /** return the last distance value from the sensor<br /> ** if it's 0x0000 we didn't get anything... **/<br /> int16_t getDistance(void);<br /></span></span><br />
After the class is initialized, a call to startPing() will send a trigger pulse and wait patiently for the results. Under the covers, the Trigger output pin is set high and Timer 2 is started, a count interrupt is fired after two counts and used to turn off the Trigger pin, thus starting the Ping cycle. When, and if, the timer wraps around on 256 64uS counts (~16.4mS), the overflow interrupt fires and a global status variable is incremented -- this allows for an extended count range, in this case up to 4 bits or x16. When the Echo input pin goes low, the input pin interrupt fires, all the counts are counted up, and the available() interface will signal by returning true -- just once. When that happens getDistance() can return something useful.<br />
<br />
<h3>
Results</h3>
<br />
The speced range of good distance data is from about 10 to 360 (in 64uS increments). I did not subtract the two-four initial trigger counts so you can do that if you want a bit more accurate close range measurement. If you multiply the count value by 1.1 you can get fairly close to the actual distance in CM.<br />
<br />
However in a spot check, I did not get reliable distance counts beyond about 180, i.e. 200cm, so YMMV. Also it jumps around, failing for a number of cycles before coughing up an occasional good value.<br />
<br />
A note on the bad values.... If there is no ping return received the sensor just keeps going until it gets tired. The spec says this should be 38mS after the trigger, but the reality seems to be closer to 150mS. So when there is nothing to sense, the time between cycles is quite long. When the ping goes missing, available() will eventually return true and the getDistance() method will return 0 -- this just makes it easier to see on a data plot. Should everything fail -- the counter will just keep counting up to it's 16x maximum and getDistance() will return 0x8000 (a negative number).<br />
<br />
For a usage example look to the test.ino file. Connect the sensor to Gnd and +5v power, Trigger to Pin 2, and the Ping to Pin 3. Create a global HCSR04 object and call init(pinT, pinP) in setup(). Then call startPing(), available() in a loop, and getDistance() when available returns true.<br />
<br />
<h3>
So simple. Even I could do it!</h3>
<br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-4262945661858393432018-11-11T16:23:00.000-07:002018-11-13T16:19:40.148-07:00SCHervo Library and ServoTaskI made some of my usual "improvements" to the standard Arduino Servo motor control library. Building on my Task Scheduler I've added a speed control, so the time it takes a Hobby Servo motor to move from it's current position to a new one can be controlled over a fairly large range.<br />
<br />
<h4>
It did take some reverse engineering...</h4>
The regular Servo library uses Timer 1 (on the 'standard' Arduino ATMega 328's -- I haven't worked this out all for other chips) to operate up to 8 (they say 12 but I think it gets a bit slippery after 8) servo motors. It does this by sequencing the ON pulses for each motor, one-after-the-other, which is pretty slick. Or would be <b>IF</b> the authors had mentioned what they were doing someplace. There's very little internal documentation -- Comments, Please! -- in the code. But I persisted.<br />
<br />
My SCHervo library comprises a cleaned up and (hopefully accurately) commented original version, with the usual Schip Secret Sauce additions. A simple addition is a turnOff() method which just shuts the motor off so it isn't using power trying to hold a position (or speed) that you don't care about. The more complicated addition is a Task to update the position -- and thus speed -- of each motor over a fixed interval. This allows the motor to transit in pseudo-continuous increments over a fairly extended period (up to about 1 minute). The motion is initiated using the startMove() method, can be monitored with ready() which returns TRUE when the motor has (just about) reached its new position, and stopMove() to make it stop at any time in-between.<br />
<br />
<h4>
But first lets review just this much: How do Servos work?</h4>
The basic idea is that you send the motor a positive pulse every 20mS (big T in the picture below),
where the width of that pulse (little t) is proportional to the position one wants
the motor shaft to take -- usually defined as from 0 to 180 degrees.
Each degree position translates to a pulse width, which generally varies
from 500 to 2500 micro-seconds, where 1500uS is a nominal 90 deg center
position. Each motor is a bit different in it's range and widths, but
that's the general scope.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://a.pololu-files.com/picture/0J3285.600.jpg?54c557f3844071a9f1455b51bb518681" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="241" data-original-width="600" height="160" src="https://a.pololu-files.com/picture/0J3285.600.jpg?54c557f3844071a9f1455b51bb518681" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">picture of a servo drive pulse train</td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
And <a href="https://www.pololu.com/blog/17/servo-control-interface-in-detail" target="_blank">here</a> is a better description.<br />
<br />
Another thing to remember is that the motor doesn't just suddenly go to the new position, but has a finite slew rate, generally something like 60 degrees in 200mS. This works out to 5 or 6 degrees in each 20mS refresh period, which is also the fastest the motor can get any new position input.<br />
<br />
So... If we change the delivered pulse width in every 20ms period we can control the motor's angle change speed. And that's what the ServoTask() does. Recall that all of the 8 controllable motor's pulses are sequential, stacked such that the next starts after the previous finishes, and when they are all done there is a <i>slack</i> period until the 20mS refresh times-out. The ServoTask() is posted from the timer interrupt service routine at the beginning of the slack period, and -- in theory -- it will execute and update the desired pulse widths for the next period.<br />
<br />
When a motor is started using startMove( endPosition, time, offFlag ) the code calculates how many micro-seconds should be added to the current pulse width in order to transition from the starting to end positions in the given amount of time. I got a little tricky and used a Fixed Point calculation with 3 bits of "sub-precision", to handle longer moves, but that's just between you, me, and the code.<br />
<br />
<h4>
However ... Fixed Point</h4>
If you know me, you know, I can't resist a few, more, comments. The Arduino using an ATMega 328 has no hardware support for Floating Point math. Should you make the mistake of including a float value in your program the linker will pull in about 1kB of code to support it. And further, should you blunder into actually using the float in a math-like-way, the result will take (relatively) forever. It's actually even worse, as the ATMega has only a small set of 16bit integer Multiply instructions (along with ADD and SUBTRACT) and NO Divide at all.<br />
<br />
So what do you do when you would like to maintain some fractional components in your arithmetic? Why Fixed Point of course! I'll leave it to wikiP to explain: <a href="https://en.wikipedia.org/wiki/Fixed-point_arithmetic">https://en.wikipedia.org/wiki/Fixed-point_arithmetic</a><br />
<br />
The tldr; of it is that you shift int values up by a consistent number of bits, do your arithmetic, and then shift them back down when you want to get a nice truncated integer again. This needs to be done judiciously because you only gain the precision of the up-bit-shift, and this number of bits is removed from the range of your values. In the case of my 3bit FixP values using a 16bit int, you get a precision of 1/8th or .125 in decimal and a range of +/- 0 to 4096 (12 bits plus sign). Which turns out to be just fine for calculating the micro-second values needed by the servos.<br />
<br />
TBD<br />
<br />
Note that the old-fashioned Servo.write() interface does not provide a way to determine when the motor is (almost) done moving. My startMove() method tries to account for the slew rate during fast motions by adding some guess at the amount of time needed. But this runs into a bit of trouble from the internal motor controller, which usually treats smaller moves as slower changes (this is how you are <u>sometimes</u> able to get speed control from motors that have been modified for continuous rotation). This behavior also slows the motor down when using the ServoTask() incremental stepping, so the internal <i>how-long?</i> guess is not always right.<br />
<br />
I could do a little more hacking to better integrate faster slews -- it shouldn't be THAT hard, heh -- and this would also eliminate the need for the 'regular' write() interface (or draw it into the fold) such that, in all cases, the ready() method will really tell you when the motor is done moving<br />
<br />
<h3>
But. Otherwise. You should be able to just go ahead and use it now...</h3>
<br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-44525628405240420012018-11-10T18:49:00.001-07:002018-11-10T18:49:48.948-07:00Possible employment opportunity!<p dir="ltr">FB is trying to help by getting me job selling popcorn! If I could only find 5 of these I could forgo my Socialist Social security....</p>
<div class="separator" style="clear: both; text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPx2ZY-JOc49SWhGoehnE1zAmM6N0SvftTvr0VLGQW-R4i_LWlOSawADSyXTrHkorPjzuFgNwt2j35BxIPbC_FSDClNuSfCwmyu00cVAVDjm8ziewsHbrccS_WV00CSNome6vJ__yM30P0/s1600/Screenshot_20181110-184647.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"> <img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPx2ZY-JOc49SWhGoehnE1zAmM6N0SvftTvr0VLGQW-R4i_LWlOSawADSyXTrHkorPjzuFgNwt2j35BxIPbC_FSDClNuSfCwmyu00cVAVDjm8ziewsHbrccS_WV00CSNome6vJ__yM30P0/s640/Screenshot_20181110-184647.png"> </a> </div>Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-57397747586961075972018-11-08T15:40:00.000-07:002018-11-08T15:40:48.688-07:00Program StructureOK then. Now here's some Architecture...<br />
<br />
I've made a template file for the Arduino main program that uses the libraries and functions that we have so recently been discussing:<br />
<br />
<a href="http://www.etantdonnes.com/DATA/Arduino/template/template.ino" target="_blank"> http://www.etantdonnes.com/DATA/Arduino/template/template.ino</a><br />
<br />
As you know, the Arduino system shields you from some of the nitty gritty by requiring only two methods:<br />
<ul>
<li>setup() -- runs once at the beginning of time (after a reset);</li>
<li>loop() -- is run repeatedly thereafter;</li>
</ul>
In my template setup() calls methods to initialize messaging, any output devices that will be used, and the ADC and other inputs:<br />
<br />
<blockquote class="tr_bq">
<span style="font-size: x-small;"><span style="font-family: "courier new" , "courier" , monospace;">// the setup routine runs once when you press reset:<br />
void setup()<br />
{<br />
MessageTask_init(); // init the message system<br />
RunTask_init(); // init the output system<br />
ADCTask_init(); // init the input system<br />
<br />
return;<br />
}</span></span></blockquote>
Where those methods are declared like this:<br />
<br />
<blockquote class="tr_bq">
<span style="font-size: x-small;"><span style="font-family: "Courier New", Courier, monospace;">/** Do whatever necessary to initialize the message system<br /> */<br />void MessageTask_init()<br />{<br /> // everyone uses comms<br /> Serial.begin( 9600 );<br /> // set message terminator to newline for text input<br /> Serial.setMsgTerm('\n');<br /> return;<br />}<br /><br />// a global system state, just for Sudhu...<br />// actually... to keep track of what the system is doing<br />word runState;<br /><br />/** Do whatever necessary to initialize the system output devices.<br /> */<br />void RunTask_init()<br />{<br /> // set output modes on pins<br /> pinMode( BPIN, OUTPUT );<br /> // initialize running state<br /> runState = 0;<br /> return;<br />}<br /><br />/** Do whatever necessary to initialize the system input devices.<br /> */<br />void ADCTask_init()<br />{<br /> // set input modes on pins<br /> pinMode( APIN, INPUT_PULLUP );<br /> // start the ADC interrupt cycle<br /> analogRead( 0 );<br /> return;<br />}</span></span></blockquote>
<br />
A word about <span style="font-family: "courier new" , "courier" , monospace;">runState</span> ... I insist on keeping track of the internal system state in order to execute sequences of behaviors and respond appropriately to inputs. So each of my programs has a global <i>state</i> variable which is manipulated by all the Task functions. The use of this will be more apparent if/when we get to the actual <i>Variations Too</i> code, but Sudhu was always teasing me about it so he gets credit here....<br />
<br />
<br />
The loop() function does two things. Look for messages and post the MessageTask, and then do a pass through the scheduler's list of things to do. If any Tasks are ready to run, they get executed here, and then the scheduler() returns, allowing loop() to return, which then repeats itself. Note that interrupts will execute (except for brief elisions) throughout this, so new functions may be entered on the Task list at anytime.<br />
<br />
<blockquote class="tr_bq">
<span style="font-size: x-small;"><span style="font-family: "courier new" , "courier" , monospace;">// the loop() routine runs over and over and again forever:<br />
void loop()<br />
{<br />
// see if we have a new message and post the receive task<br />
postTask( 0, MessageTask, nMsg );<br />
// execute the schip task scheduler<br />
scheduler(); // schedule the world<br />
return;<br />
}</span></span></blockquote>
<br />
The actual tasks that will be executed are declared like this:<br />
<blockquote class="tr_bq">
<span style="font-size: x-small;"><span style="font-family: "Courier New", Courier, monospace;"> /** Task posted when there is a serial input message<br /> * @param nmsg -- ignored...<br /> */<br />void MessageTask( word nmsg )<br />{<br /> // read the message string including '\n' terminator<br /> // and execute user functions<br /> return;<br />}<br /><br />/** Posted from ADCTask to do something with new sensor values<br /> * sState -- condensed bitmap of stuff that happened<br /> */<br />void RunTask( word sState )<br />{<br /> // look at run and sensor States and do appropriate stuff<br /> return;<br />}<br /><br />/** Task posted when ADC averaging gets a new set of values.<br /> **/<br />void ADCTask( word numADC )<br />{<br /> // rummage through input stuff and set flag bits in sState<br /> // execute RunTask(sState) after 0 millis (like real soon now) <br /> return;<br />}</span></span></blockquote>
And the rest is, as they say, just implementing stuff....<br />
<br />
<br />
We'll talk about some of that stuff anon.Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-51848570804601078032018-11-05T13:17:00.000-07:002018-11-05T13:17:02.637-07:00MessagesI added an a couple of interface methods to the underlying HardwareSerial class -- it's possible that I could have done this by extension rather than hacking, but it might have been even harder to integrate. The msgAvailable() method returns how many of some specified character -- which should be set using setMsgTerm(uint8_t) -- have been received. In usual practice this is a count of newlines, where a newline means that the source has completed a text formatted message. This makes certain that the receiving Arduino isn't waiting around for characters when processing messages, which is a bit of an improvement on the standard Serial.available() method.<br /><br />The msgAvailable() method is probably not really useful unless you are implementing a higher speed call-and-response system of some kind. It requires some heavier internals hacking than the scheduler and ADC fixes -- which have to be done on every new Arduino version -- so I would recommend waiting until your mileage indicates that it's needed before trying it out....<br /><br />
<h4>
But first... A little about messaging....</h4>
I puzzled over I/O text parsing methods like Serial.parseInt() and finally realized that I could find no way to use that parsing on my own message strings. Therefore I gave in and tried to make it work as is. One doesn't necessarily need my Task Scheduler code to do this, but it fits in fairly nicely.<br />
<br />Each pass through the loop() method looks to see if there are some characters available -- hopefully a full message, where one could use msgAvailable() to be absolutely sure -- and posts (or just executes) a message receiving task:<br /><br /> // USB message function<br /> // if we have a new message, post the receive task<br /> // if the message isn't handled by the time this runs again<br /> // we will get a second Task post, so get with it...<br /> // here we just check available()<br /> // and hope we have a whole message<br /> // do we have more than one char available?<br /> byte nMsg = (Serial.available() > 1) ? 1 : 0;<br /> if( nMsg != 0 )<br /> postTask( 0, messageTask, nMsg );<br /> // note that this could also just execute the method....<br /> // messageTask( nMsg );<br /><br />When executed, the messageTask() reads and parses values from the Serial input, then executes user functions as needed.<br /><br />
<h4>
Message Example</h4>
I usually use messages with a single character command followed by integer or string parameters, all ASCII with white-space (space or tab) delimiters, followed by a newline ('\n' in C) terminator that says it's all done, ala:<br /><br /><b> r 1234 5678\n</b><br /><br />The Message Task parses these parameters and get stuff rolling. Two interesting(?) things about functions like Serial.parseInt() are,<br />
<ol>
<li>They skip white-space until they find characters they like, then consume those chars until they find more white-space or chars they don't like, and stop there;</li>
<li>If they don't find anything they like, they block for up to one second waiting ...which is annoying...</li>
</ol>
<br />But used judiciously these functions can collect parameter values, such as:<br /><br /> /** Task posted when a serial message is found<br /> * @param n -- ignored...<br /> */<br /> void messageTask( uint16_t n )<br /> {<br /> // get the first command character<br /> char val = Serial.read();<br /> <br /> // see what we got, for debugging, etc<br /> //Serial.print( "cmd: " );<br /> //Serial.print( val );<br /><br /> // Perform an action depending on the command<br /> switch( val )<br /> {<br /> case 'r': // run<br /> case 'R':<br /> // collect a variable number of arguments<br /> // -- up to 10 before crashing --<br /> // assuming that only space is used as a delimiter<br /> // and not handling trailing spaces very well at all...<br /> // ... if the next char is a space<br /> // presume we have a new parameter...<br /> int args[10];<br /> byte count = 0;<br /> while( Serial.peek() == ' ' )<br /> {<br /> args[count] = (char) Serial.parseInt();<br /><br /> // optional debuging<br /> // Serial.print( ' ' );<br /> // Serial.print( args[count] );<br /><br /> ++count;<br /> }<br /><br /> // do something with it all...<br /> runMe( args[0], args[1], ... );<br /> break;<br /><br /> // .... //<br /><br /> default:<br /> ; // de Nada<br /> }<br /><br /> // optional debuging terminate<br /> //Serial.println();<br />
<br /> // consume anything else including the terminator<br /> // note: this is important, so we don't get called again<br /> // with just the terminator in the message input buffer<br /> do<br /> {<br /> val = Serial.read();<br /><br /> } while( (val != '\n') && (val != -1) );<br /><br /> return;<br /> }<br /><br />
<h4>
Status Return</h4>
For return status messages the same format can be used and the Serial.print() or printf() functions work fine. It's not imperative that you use the newline terminator but it probably makes things much easier at the other end.<br /><br />
<h4>
So there you go...</h4>
I've putzed around with raw binary (non-ASCII) messages, but the parsing is less flexible, terminators are hard to come by, byte ordering can still be problematic, and just plain text is much easier to monitor on the host side during development.<br />
<br /><br /><b>Next we'll look at the overall program structure.</b>Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-90879031933763871242018-11-04T17:13:00.001-07:002018-11-04T17:13:27.166-07:00Analog to Digital!<h3>
When Worlds Collide</h3>
Another "feature" of my new improved Arduino <a href="http://schip666.blogspot.com/2018/11/software-architecture.html" target="_blank">architecture</a> is ADC access...<br />
<br />
<h4>
Interrupts</h4>
Due to my antipathy for busy waiting -- rather than looking busy I'd prefer to just not do anything -- I implemented an interrupt driven ADC interface.<br /><br />In the standard Arduino system, calls to analogRead() execute a conversion in place (and block for the time it takes to complete). In my new improved system, conversions free run at the slowest rate available on the Atmega chip in the "background". And the standard interface calls are redefined to just return the most recent value from the requested channel. This takes a bit of processor -- not user -- attention but makes the getting of values almost immediate. If one is doing data acquisition one is probably getting ADC values fairly frequently, so it's likely the number of instruction cycles tradeoff is not a big issue.<br /><br />The number of channels accessed by the background conversion cycle can be set in the schip_analog.h file. Usually 4 is sufficient, but a total of 8 are available on many Atmega chips. Note that I2C communication uses A4 and A5, so if you need them there is a provision for skipping those channels in the sample loop.<br /><br />
<h4>
ADC Task</h4>
A second feature of my interrupt driven conversion code is an averaging algorithm which squeezes 64 samples into one smoothed 8-bit value. This allows a lot of noise and nonsense to be filtered out of the individual conversions. Specifically, for instance, 50-60Hz hum... These values are accessed using analogReadAvg() which will return the most recent average for each channel.<br /><br />When enabled, the averaging cycle also creates a reasonably timed -- in the multi-millisecond range -- repetitive sample cycle. At the end of each averaging period the ADCTask( num_channels ) function is posted to the task scheduler, which will then execute it very close to the time that all the channel averages become available. The exact timing of this posting is determined by the number of ADC channels being converted:<br /><br />For the Arduino Uno and PRO-Mini with a 16Mhz Atmega 328, the ADC prescale is set at the maximum 128 clocks, and a single ADC conversion takes about 110uS<br /><br />With NUM_ADCCHANNEL set to 4:<br /><br />A four channel ADC cycle takes about 450uS (2222Hz sample rate) so 64 conversion cycles takes about 29mS.<br /><br />The Leonardo seems to be a bit slower:<br /><br />A single ADC conversion takes about 120uS, and a four channel ADC cycle takes about 480uS (2085Hz sample rate) so 64 conversion cycles takes about 30.7mS.<br /><br />With NUM_ADCCHANNEL set to 8 and SCHIPSKIPI2C defined for a total of 6 channels:<br /><br />A six channel ADC cycle takes about 660uS (1515Hz sample rate) so 64 conversion cycles takes about 43mS.<br /><br />
<h4>
Audio Data</h4>
A tertiary feature -- not used in the <i>Variations</i> context -- is a double buffering scheme for ADC channel 0 which allows 64 10-bit samples to be collected into a local buffer. When the buffer fills it's address is put into the global pointer SchipBUF0, from whence the user can get and manipulate the data. While the user is playing with buffer one, a second buffer is being filled and it's pointer is alternately placed into SchipBUF0. So if you can keep up, you can do some audio processing at a reasonable sample rate. See the <a href="http://www.etantdonnes.com/DATA/Arduino/soundBit/SoundBit/" target="_blank">SoundBit</a> for an example.<br /><br />The time between buffer updates and the actual rates for various configurations are listed as the <i>conversion-cycle </i>and<i> sample rate</i> above.<br /><br />
<h4>
'Kernel' hack</h4>
In keeping with not being able to leave anything alone I have made some insertions in the core "kernel" ADC code to remove the old-fashioned analogRead() function. This is included in the "cores" directory of the code bolus. It is not strictly necessary, but is sufficient to avoid confusion and save a few bytes of program memory.<br /><br />
<h4>
setup()</h4>
In all cases, when using the new-improved-interrupt code one needs to kick things off by calling analogRead( n ) once in the setup() method. This will usually return 0, so the value should be ignored. Too bad, in the old world we could have used the value as a random number seed but now have to find some other workaround for that.<br /><br />
<h4>
More Anon.</h4>
Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-44493356052083073372018-11-02T13:21:00.000-07:002018-11-02T13:21:29.594-07:00Task SchedulingAt the bottom of my new-improved-code pile is the scheduler library which makes possible a semblance of non-pre-emptive multi-tasking. It maintains a list of Task functions to be executed with a single argument (which can be cast to be an arbitrary pointer) and the number of milli-seconds in the future that the function should be fired off. Using this system you will never use the busy-waiting delay() again. It is contained in the schip_scheduler library, and optimally linked through the Arduino cores 'kernel' code. See those directories in my code bolus ref'd in <a href="http://schip666.blogspot.com/2018/11/software-architecture.html" target="_blank">Software Architecture</a>.<br /><br />Task functions have the signature:<br /><br /> <b>typedef void(*PFV)(uint16_t);</b> // pointer to a function for task list<br /><br />which looks like this when you define one:<br /><br /> <b>void myTask( uint16_t arg ) { return; }</b><br /><br />By default, the function list has 8 entries and attempt to enter more will fail with an error code. The size can be changed in the header file with:<br /><br /> <b>#define USE_NumTasks 8</b><br /><br />There are four user entry points to the library:<br />
<br /> <b>void initScheduler(void);</b><br /><br />initScheduler() should be called in the setup() method before any tasks or task related interrupts are posted or enabled. <br /><br /> <b>void scheduler(void);</b><br /><br />scheduler() should be called in the loop() method, and may be the only function there -- modulo a serial message checking block if one uses such -- see subsequent blogging about message Tasks. This is where the busy-waiting is concentrated, but it is only waiting busily when there is nothing else to do.<br /><br />A task can be inserted into the list using;<br /><br /> <b>int postTask( uint16_t ticks, PFV func, uint16_t arg );</b><br />
<b></b> <br />
where 'ticks' is the number of milli-seconds in the future that the task should be executed, 'func' is the task method to execute, and 'arg' is an arbitrary 16 bit sized value to pass when executed, ala: func(arg). A post call might look like this:<br /><br /> <b>postTask( 100, myFunc, 0 );</b><br /><br />It will return the task's index in the list, or -1 if it fails.<br /><br />An executing task may call:<br /><br /> <b>void repostTask( uint16_t ticks, uint16_t arg );</b><br /><br />to put itself back into the scheduler's list with the given execution delay and argument value.<br /><br />There is, as of now, no removeTask(). Everything in the list gets executed when it's time comes...<br /><br />A user task should not run for a long time and should never call delay() or other blocking functions as this will prevent other tasks from running. If the task is going to do a lot of nonsense it should be broken into smaller functions that post each other in succession. This allows 'background' tasks, such as ADCTask() and ServoTask() to get an execution in edgewise. All tasks run to completion in "user memory space" so there are minimal worries about concurrency. However interrupt service routines can both interrupt and post tasks during execution, so some attention should be paid to shared variables in those contexts.<br /><br />There are two versions of the scheduler library. One is fully in the "user program space" and uses millis() to calculate elapsed time between calls. This has a bit more overhead and can be less precise in it's execution delays. The 'real' one can be hacked into the Arduino cores "kernel" and uses a callout from the milli() tick interrupt to manage the times in the task execution list. It is somewhat more efficient but the drawback is you have to hack it into each kernel version as it comes off the press. There is some discussion in the library header file and in the cores/readme.txt about how to install it.<br /><br />A secondary advantage to the "kernel" version is that it can contain another callout to a user function on every milli() tick. And THIS can be used to implement, e.g., a stepper motor step sequence. See SCHIPTICK in the files....<br />
<br />
<br />
Next up: ADCs<br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-85995755939700455932018-11-01T12:43:00.000-07:002018-11-01T12:43:03.286-07:00Software ArchitectureThe low level controller in the <i>Variations Too</i> project is an Arduino, beloved by LED Flashers around the world. But my last actual job title in the Software Industrial Complex was Extensibility Architect (...see how well that went <a href="http://www.etantdonnes.com/TEXT/TheCompany.txt" target="_blank">here</a>...) so I can't leave things alone without extending upon them. And the Arduino system is badly in need of some Software Architecture. Since it's simple enough, in most respects, that I can almost understand it, that's what I've been doing by fits and starts lately.<br /><br />First, there's a lot of busy-waiting going on. The Analog to Digital Converter (ADC) code just starts a conversion and sits there waiting for it to complete. This is not so bad as it usually comes through in a tenth of a millisecond, but that's about 400 instruction cycles that could be used for doing something useful.<br /><br />Then... delay() is the favored way to make something happen sometime in the future. This is just a bad idea in a system that might be responding to a buncha stuff in a realish time frame.<br /><br />And. Then. Speaking of responding. The "usual" way to do responsible data acquisition is to sample at some fixed rate. There is no provision for doing that in the system as it is constructed.<br /><br />Plus. Communication. One often wants to send and receive messages from the rest of the computing planet in some reliable manner. Unfortunately the Serial interface has hidden it's underlying structure and parsing mechanisms such that this is difficult. Doubly impossible if one needs to implement a message format that contains framing, headers, and/or checksums, which might, for instance, be of use in less than predictable mediums like radio.<br /><br />
<h4>
On the other hand</h4>
I was making amazing claims about the Arduino system, such as, "It just seems to work!" -- in comparison to a previous ATmega based development system, constructed by grad students that needed thesis topics, I used, lets just say, 20 years ago, where my strongly held belief was that <b>NO ONE</b> knew how the build system worked, such that the way to get it running was to reinstall different versions until a compile stuck to the wall. But. Of course. The one time I tried to teach a class using Arduinos, three of the six students had varying levels of failure, from port configuration (on a Mac, so, well, yeah, sure...) to execution failure (on what appeared to be a garden variety Widows machine). So I've forsworn further involvement of that nature.<br /><br />
<h4>
But I woolgather...</h4>
Here's the latest bolus of my improvements to the world:<br /><br /><a href="http://www.etantdonnes.com/DATA/schipArduino.zip" target="_blank">http://www.etantdonnes.com/DATA/schipArduino.zip</a><br /><br />Details at 11.<br /><br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-90152716916475255332018-10-21T14:29:00.000-06:002018-10-21T14:29:22.883-06:00Variations Too -- (semi-final) assemblySemi-final, in theory, as I might get around to integrating a rasPi with video detection to run some smarter pattern development and to be able to communicate amongst a group of these guys in order to pass steps around the dance circle. Thus implementing Variations 3.20. Someday.<br />
<br />
But for now....<br />
<br />
I went for a weighty base made of a 20" long piece of 6x2x1/4" steel U-channel that I had lying around in another of my massively parallel storage areas and drilled mounting holes (or mis-drilled in some cases) for everything I could think of:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxUqK2u-w6TLsuaSakDBSSVlZCUX5ky1hJurIcSKIJFYkBel8bPcEn5YUWEy0UEM4poVNs31lU1qlJ8bfwhbTcA_Qy-Xz0WWhh5kUBhEI7E6CZnvBjmDqxh4aWUpZMhdHzYLL_fRLZOHOh/s1600/saBaseTop.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="265" data-original-width="800" height="106" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxUqK2u-w6TLsuaSakDBSSVlZCUX5ky1hJurIcSKIJFYkBel8bPcEn5YUWEy0UEM4poVNs31lU1qlJ8bfwhbTcA_Qy-Xz0WWhh5kUBhEI7E6CZnvBjmDqxh4aWUpZMhdHzYLL_fRLZOHOh/s320/saBaseTop.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">top of the heavy steel base</td></tr>
</tbody></table>
<br />
Here's all the sensor and controller wiring before the motor assembly was mounted:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1d8AE95TuBkUM9zTHHBocIfuEvZj-cpOTSSyJuKT6cOAcmXW8pW58HINYe9rNUOarjmPOcqDzRbW7WV1NGu3yE8GciqgDjklpy_BsOjtqogvq9ksUxJq-vJZe6DBMaC6Sx83mMx-KawNe/s1600/saBaseWiring.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="584" data-original-width="1600" height="116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1d8AE95TuBkUM9zTHHBocIfuEvZj-cpOTSSyJuKT6cOAcmXW8pW58HINYe9rNUOarjmPOcqDzRbW7WV1NGu3yE8GciqgDjklpy_BsOjtqogvq9ksUxJq-vJZe6DBMaC6Sx83mMx-KawNe/s320/saBaseWiring.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">wiring underneath</td></tr>
</tbody></table>
<br />
The controller is an Arduino Pro-Mini mounted on a carrier board of my own design that provides powered connectors for most I/O and some other accessory features. If you're wondering, here are the files (the design is done using the <a href="http://expresspcb.com/" target="_blank">ExpressPCB.com</a> proprietary software because I ABSOLUTELY REFUSE to use the popular Program Which Shall Not Be Named):<br />
<br />
<a href="http://www.etantdonnes.com/DATA/roboAssist14.zip" target="_blank">RoboAssist files</a><br />
<br />
Wiring the motors around the arm rotation points is ad-hoc, and a Royal PITA. I left enough slack at each junction to allow rotation without dragging the wires into the gears. It would have been easier if I had just bitten the bullet and bought servo extension cords. Maybe.<br />
<br />
So here's the final:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihoRUS9scICfQO-FKGCTOrHDmxbco4Qd4vvJOJ2xw35l3OeknronikUlkqOkTNLVWy4wJBmVtY16rMLMZpocLxbTGdwBs-bXrvqEEbOS9J6SVVu1SDF9lVOWlXXxECLE91QPx3GXftmpnu/s1600/VToo00.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1024" data-original-width="685" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihoRUS9scICfQO-FKGCTOrHDmxbco4Qd4vvJOJ2xw35l3OeknronikUlkqOkTNLVWy4wJBmVtY16rMLMZpocLxbTGdwBs-bXrvqEEbOS9J6SVVu1SDF9lVOWlXXxECLE91QPx3GXftmpnu/s400/VToo00.jpg" width="267" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Variations Too</td></tr>
</tbody></table>
<br />
And a little demo run:<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/eAmCF4pdUsQ/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/eAmCF4pdUsQ?feature=player_embedded" width="320"></iframe></div>
<br />
<br />
Now on to the software....Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-80815745862037121042018-10-19T12:15:00.000-06:002018-10-19T12:20:05.893-06:00Variations Too -- taxonomic exegesisLemme take a little timeout here to explain my naming scheme.<br />
<br />
As I mentioned <a href="http://schip666.blogspot.com/2018/10/variations-more.html" target="_blank">earlier</a>, the ur-text for all this was my proposal for an installation called <i>Variations For</i>, which was a pun on the 1960s John Cage piece <i>Variations IV</i>. Get it? Ha. Ha.<br />
<br />
Failing to make headway on that, for lack of tens of thousands of dollars and square feet to house and feed Industrial Robots, I realized that I might be able to make my own set of simpler robot arms along the lines of the sketch in the first of these blog entries. That also faltered along technical and financial lines.<br />
<br />
Recently, working on reducing my horizons, it occurred that I might at least be able to make ONE damn robot arm and get it to do <u>something</u>. Thus came to be <i>Variations Too</i>. You may have realized this is a pun on Two, as well, as meaning, Also. Ha. Ha.<br />
<br />
So. Should <i>VToo</i> work, I will have a base model from which to construct four more. (For, more, still with me? Ha. Ha.) And that will be the basis for developing the real idea of making a set of robots that choreograph a dance together under their own direction -- even if it is still in only two dimensions. That version I would call <i>Variations 3.20</i>, because it is the first 80% of <i>Variations For</i>, where the last 80% is "just" programming the industrial robots. That's an engineering joke. Ha. Ha.<br />
<br />
Anyway.... Next time, some more about the final construction.<br />
<br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-70096772335572982532018-10-18T14:34:00.001-06:002018-10-19T12:20:39.254-06:00Variations -- on a linkageHere's another video of the <i>Variations Too</i> robot arm in action:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/m5ZuUbwOLlc/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/m5ZuUbwOLlc?feature=player_embedded" width="320"></iframe></div>
<br />
For aesthetic reasons I fixated on being able to get the arms to fold up in the "parked" position as seen at the beginning and end of that video. Since almost all hobby servos only travel around 180 degrees, and ones that do travel further are more expensive, less powerful, and slower, I needed a 1::2 rotation step up; and, to maintain the sense of servo position it needs to be a timing belt or gear. Since timing belts are even harder to come-by, gears were the choice. Using a gear linkage also isolates the weight of the driven arms from the bearings in the motors themselves. It also allows the motors to be used as sorta-counter-weights, being mounted on the opposite side of the rotational bearing from the main weight of the arm. At this writing it remains to be seen how this all plays out, but here's a picture of the mechanism:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9RaLM7Z3FwbAPA2ZJx5kZLA49qguUJpQw3Mm5B2vtRgWSNGNOGCZpugTpTpVHBG3umO1i9qP4C2V4Jf22H3COnPIowmVO9Q0wOGihVT6fWbHuqMKDdKCgyekah76GCskfLIjCtc9lOpkZ/s1600/gearLinkage.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="770" data-original-width="952" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9RaLM7Z3FwbAPA2ZJx5kZLA49qguUJpQw3Mm5B2vtRgWSNGNOGCZpugTpTpVHBG3umO1i9qP4C2V4Jf22H3COnPIowmVO9Q0wOGihVT6fWbHuqMKDdKCgyekah76GCskfLIjCtc9lOpkZ/s320/gearLinkage.jpg" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxFIyDZ4KxMUyMyDWf4LXA8-y7ySEVA3SrG2eqOiffpNo-AuLQQI9oM-AN7jBRuuJjoEt-2EE__h3PvyV3R7NvDH920or3Cew4NzP5GOWStPGVaVyzzRpAGHWuv37QRZ62jUMAhtKbfX2Q/s1600/gearHalves.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="801" data-original-width="820" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxFIyDZ4KxMUyMyDWf4LXA8-y7ySEVA3SrG2eqOiffpNo-AuLQQI9oM-AN7jBRuuJjoEt-2EE__h3PvyV3R7NvDH920or3Cew4NzP5GOWStPGVaVyzzRpAGHWuv37QRZ62jUMAhtKbfX2Q/s320/gearHalves.jpg" width="320" /></a></div>
<br />
<br />
The arms are made from two different thicknesses of plexiglas, as can be somewhat discerned from the linkage photo. The lowest arm is .170" and the others are .120". In retrospect, for rigidity, I might use the thicker material for the second lowest arm as well.<br />
<br />
The motors each have different 'vertical' spacing from the mounts to where a gear might be conveniently centered on the spline. In all cases I used the little rubber bushings that come with each motor for spacing and isolation. In addition to the bushings the HS-645MG motor needs about .250" more space, which is conveniently just about two thicknesses of the .120" plastic. The HS-755HB only needed an extra .180", so I cut a spacer from .060" material. The HS-225BB was just about right with only it's bushings mounted on .120" arm. These spacers also allow for a stronger motor and bushing mount.<br />
<br />
All the motors are attached using #4-40 bolts, and it turns out that standard 3/8" and 1/2" lengths are nearly perfect -- for once. I found that one needs to be fairly careful with hole sizes when press fitting and bolting the various shafts, bushings, and bolts. So I used appropriate reamers and taps to make the holes work -- as cut they all seemed to be a bit undersized. Also note that the 645's will need to have some of their rubber bushings trimmed to clear the E-ring that binds, and the 755 needs to have a bit of the mounting bracket bored out as well, to clear the main bushing. These things will become apparent as you mock-up the assembly.<br />
<br />
I've changed the design of the fixed (small, black) gear mounting slightly so I'll just describe the idea. The gear needs a spacer to the arm on which it is attached, and .120" is just about right. I cut some rings that -- should have -- fit over the gear flange while remaining clear of the teeth, with the idea that the ring would be glued to the gear (thank god for Goop!). If you try to copy this mechanism you will need to fiddle with the sizes to get it right, a close, but not tight fit is needed. Once the ring is set in place the excess gear flange needs to be cut away, so in the final assembly the gear and spacer are flush with the mounting arm, and the axle goes all the way through arm and backing plate. BTW, I used the backing plates to strengthen the axle mounting area and to locate two pins that (hopefully) will tie the whole room together. Those pins are not shown in any of the photos. They are small brass brads that run through backing, arm, and most of the gear. But I'm getting a bit ahead...<br />
<br />
For the axle I used 1/4" DOM tube with a 3/16" hole, thinking to minimize weight and that I might run wires though the tube. The tube tends to run larger in O.D., so the bushings and gears need to be reamed out a bit. I think using tube doesn't matter, so you could easily find some nice 1/4" drill rod or something instead. The axle needs to have a notch for the E-ring clip that holds everything together. The clips are about .020" thick, but the thinnest cutting tool I could find was .040" so there's a bit of slop. The notch is around .030" deep. Careful attention needs to be paid to removing flashing from the ends and the notch edges, so the whole axle slides through the bushing cleanly. The bushing is a standard 1/4" I.D, 1/2" long, flange, that presses into a 3/8" hole.<br />
<br />
I press fit the gear onto the axle with the bushing and E-clip in place PLUS a .015" (or so) shim between the gear and bushing to maintain some operating clearance. When the gear is positioned correctly you can remove the bushing and shim. If you haven't yet trimmed the gear flange you can put the axle in the lathe and use the clip notching tool to get it all nice.<br />
<br />
When everything is ready the, axle can be pressed into the holding arm and backing plate, and the the bushing pressed into the other arm where it belongs -- I think a slight countersink to the receiving side of that mounting hole will help the bushing seat. Once pressed together use some of the "water thin" plexiglas adhesive (Methylene Chloride) to wick into any place where two layers of anything should not move, and clamp lightly until set.<br />
<br />
When set, the fixed-gear arm can be drilled for the 'anti-rotation' pins and they can be Gooped in place.<br />
<br />
Sounds simple, eh?Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-64348799484956426022018-10-15T14:48:00.000-06:002018-10-19T12:20:54.191-06:00Variations Too -- getting it up<br />
Having done all the previously described hacking and hewing of robot arms, I finally assembled them and cut out a mounting bracket to hold the lowest and largest of the motors. Since the base motor only has to travel 180 degrees I dispensed with gearing and invented an inline bushing support. This is fortunate because the lower motor also has a completely different spline, for which the included wheel is the only mating component I could find -- I really am not sure what these people are thinking, but I guess the whole after-market of robot builders was not on their radar when the servo motor folks were planning their product lines.<br />
<br />
For the lower arm support I used a bronze bushing centered on the motor wheel and inserted through the arm which is sandwiched to the wheel. The wheel is then bolted onto the motor spline. A short axle inserted into the bushing supports the arm and is screwed to the motor mount U bracket in a manner that allows a bit of fiddling to get everything centered. And speaking of centering to start with ... I got the spline-wheel centered and bored a 3/8" indent into which I could locate the end of the bushing, which was then press fit through the whole sandwich.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMj90pbx4EKhKOpWBcJkHFLEyW3Jrs6r-DpOngh_-mH9MlYaAV_yO7UipVZWbQfI-fcGfbzl_u2x_Jp8g9CebD0xGEgd3Hy_mlzZEo-GIZuixC38I6cjKVTE6wNG7A97ugD6jyEs8q0spT/s1600/mountingBracket.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="744" data-original-width="876" height="338" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMj90pbx4EKhKOpWBcJkHFLEyW3Jrs6r-DpOngh_-mH9MlYaAV_yO7UipVZWbQfI-fcGfbzl_u2x_Jp8g9CebD0xGEgd3Hy_mlzZEo-GIZuixC38I6cjKVTE6wNG7A97ugD6jyEs8q0spT/s400/mountingBracket.jpg" width="400" /></a></div>
<br />
Then I re-re-hacked an Arduino servo motor driving program I had from a previous attempt -- with the MeArm, which is a nice design but unfortunately both under-powered and under-whelming. But have a look anyway:<br />
<br />
<a href="https://shop.mime.co.uk/collections/mearm" target="_blank">https://shop.mime.co.uk/collections/mearm</a><br />
<br />
I clamped the whole thing down on the workbench and fired it up. And. It kinda works! But it was too flimsy with five arms, so the final product uses only four. Here's the first recorded run:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/jEqgue73oTM/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/jEqgue73oTM?feature=player_embedded" width="320"></iframe></div>
<br />
This means that I'm going to have to find something else to waste $200 on in order to be really disappointed.<br />
<br />
Maybe next time...Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-56667846569062139422018-10-13T17:22:00.001-06:002018-10-21T16:18:02.414-06:00Variations Too -- getting armedTo build my robot arms I stole an idea from one of my Make Santa Fe cohorts and generated a cutout pattern -- to reduce weight, certainly <b>NOT</b> for any decorative purpose whatsoever -- from a photo of cholla cactus trunks which I stole from an online aquarium supply store -- ??? who knew fish like to hide in cholla remains ???.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://www.etantdonnes.com/Media/cholla/cholla3orig.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://www.etantdonnes.com/Media/cholla/cholla3orig.jpg" data-original-height="798" data-original-width="800" height="199" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">cholla chunk</td></tr>
</tbody></table>
<br />
<br />
Some detail about this process might prove useful to our future selves...<br />
<br />
I found a decent photograph online and a web site that does "free" Raster-to-Vector conversions, if you have good images. I don't know how scammish this site is but, NoThanks-ing various pleading messages got me to where I could use it to convert images to vector outlines in DXF format.<br />
<br />
<a href="https://online.rapidresizer.com/tracer.php" target="_blank"> https://online.rapidresizer.com/tracer.php</a><br />
<br />
Strangely it worked well. I massaged the vector files to be useful to my purposes -- removing decorative and practical bits of material from some robot arms in order to slightly reduce their weight.<br />
<br />
All the cactus files are here: <a href="http://www.etantdonnes.com/Media/cholla/" target="_blank">My cholla files</a><br />
<br />
<ul>
<li>cholla2orig*.jpg are the original and P-shop massaged images.</li>
<li>cholla3.* are the resulting vector data, and a jpg image thereof.</li>
<li>cholla3ed.* are my editing of that data for use on the robot arms.</li>
</ul>
<br />
The non-orig JPG images are for reference purposes, and the .dxf files are what you need to load into a vector editing or cutter driving program. The DXF files may have bizarre scales -- the conversion came out to look like it was 64 feet wide -- so pay attention to the sizes... The .CAD files are the native format for the ancient drawing program I have.<br />
<br />
Combining all the data, I cobbled together designs for five arms of different lengths using the different motors. So. Here. After all the measuring and converting is a motor with gears, mounted in a laser-cut robot-arm-like contraption:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirGW08LBBIhVgRoHLzIfZShVcE5O-oG1r43b425nHMGX0FHrObe_P07IoTuI7TdUINf3yZ9H25QVyPIU-ITSWI7Wmr_hZnRoTtC6TbqjHnQ7OLfWPov2xKfLUrQILgWBxa4WdnCB2tztkq/s1600/servoArmDetail.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="396" data-original-width="800" height="158" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirGW08LBBIhVgRoHLzIfZShVcE5O-oG1r43b425nHMGX0FHrObe_P07IoTuI7TdUINf3yZ9H25QVyPIU-ITSWI7Wmr_hZnRoTtC6TbqjHnQ7OLfWPov2xKfLUrQILgWBxa4WdnCB2tztkq/s320/servoArmDetail.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">arm with motor and gears</td></tr>
</tbody></table>
<br />
And the DXF files: <a href="http://www.etantdonnes.com/Media/cholla/arm01.DXF" target="_blank">Long arm</a> and <a href="http://www.etantdonnes.com/Media/cholla/arm02.DXF" target="_blank">Shorter arms</a>. (Note that these files are preliminary and some finessing is still TBD.)<br />
<br />
<EDIT 10/21/18> I think I fixed the files -- added spacers and other odd bits to the "shorter" one -- but still, check the mechanical hole sizes: <a href="http://www.etantdonnes.com/Media/cholla/arm01.DXF" target="_blank">Long arm</a> and <a href="http://www.etantdonnes.com/Media/cholla/arm02a.DXF" target="_blank">Shorter arms</a>.</EDIT><br />
<br />
I cut the arms using the MSFe Zing laser cutter, which has some quirks. Chief among these for purposes of mechanical design is a somewhat variable kerf and a reasonable but unmentioned assumption about circular hole sizes. Fortunately the centering of holes is pretty spot on and the size assumption is that the drawn hole size is an ID -- so holes come out just about as you would expect. However, if one is cutting out round plugs, the kerf needs to be considered. Thus YMMV when using my files to try to make a copy of my arms, and test runs are advised. Also, judicious reaming and taping of holes is indicated during assembly.<br />
<br />
More discussion of the design and gear linkage will follow...<br />
<br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-29168777788530393862018-10-10T15:49:00.001-06:002018-10-19T12:21:51.457-06:00Variations Too -- run up<br />
And so, a couple months ago I came down with a (tiny) fit of organizing fever and cleared up a small portion of a pathway in (one of) my massively parallel parts storage areas. I found some crappy motors I had gotten (cheap) from a surplus place that I had attempted to make into a 2-d swinging robot arm, to make even some small progress on the big proposal.<br />
<br />
They didn't work worth a damn:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjflhqNtUXU-T5ekdHucI2fWId4HSGO5hrcKdDnlYR2n1xlYiHuXj-ea8U923Kj8PvJlMN7_tUWbmNVDYlUgqR5d5XTNg-ecVep-tLwUPhTs2KsjUyR2CcPI89VHe4Zxt3GshYY0s1UPcNi/s1600/funkyMotors.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="374" data-original-width="800" height="149" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjflhqNtUXU-T5ekdHucI2fWId4HSGO5hrcKdDnlYR2n1xlYiHuXj-ea8U923Kj8PvJlMN7_tUWbmNVDYlUgqR5d5XTNg-ecVep-tLwUPhTs2KsjUyR2CcPI89VHe4Zxt3GshYY0s1UPcNi/s320/funkyMotors.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">picture of crappy motors</td></tr>
</tbody></table>
<br />
But, out of curiosity, I found a sketch of my intentions in an old notebook from 2014 (where you can clearly see why I do not practice drawing and painting as a profession): <br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPjgWgqNYqO2K9nDNLQyzlKwxAAeDaVDAIMv65YPcet8ONM8I3_lGcn9YgzQPJ472gUWk3AslxzchbYS3XjDXN17CLSoha2FM8zZ0cY1nRXbfFRPaUGjB825SV1BXPzH-fI3Xh4c5s_AWP/s1600/swingArms2014.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1600" data-original-width="1203" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPjgWgqNYqO2K9nDNLQyzlKwxAAeDaVDAIMv65YPcet8ONM8I3_lGcn9YgzQPJ472gUWk3AslxzchbYS3XjDXN17CLSoha2FM8zZ0cY1nRXbfFRPaUGjB825SV1BXPzH-fI3Xh4c5s_AWP/s320/swingArms2014.jpg" width="240" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"> picture of robot arm crappy drawing</td></tr>
</tbody></table>
<br />
<br />
I got to thinking that maybe I could re-start the whole project by actually spending money to get motors and components that might work. (Over this course I realized that I could spend $200 on oysters and gin and have a fine time with friends for an evening, or I could spend -- well, more, but in the ballpark -- on motors and gears, and get in a week or two of mostly enjoyable fiddling around before discovering that things weren't going to work; so, bang for buck over hours this is a way more economically efficient alternative.)<br />
<br />
There ensued a couple weeks of web surfing trying to figure out what "hobby servo" motors to get, even though I already have about 50 random ones.<br />
<br />
There are a number of vendors.<br />
<br />
Each has around 100 choices.<br />
<br />
The choices are mostly incommensurate.<br />
<br />
Comparing across the sources, and sometimes within a single source, is problematic. Plus there are around 10 different output shaft "spline" configurations, that are often not even specified in the literature. I finally realized that I had to stick to one source (servocity.com, the high price spread, but has reasonably complete spec sheets). And this lead me to realize that I had only two choices of splines because they were the only two that had available matching gears to be attached. So I got an assortment of the highest power motors with the same matching splines, and a buncha gears:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3CZYsm6r1nTZXpW2Rm4FnModkTo9jpSYEsR-vpNx_1G6H-QW0xbhb65XsOsOvgUKz6X6jXSmwGdXswAJXd6miCOzfyNeSa4oKAqXJeyZ_if9OesPMw3MhoUOtxkKDh0I8oBsLVSK45w0_/s1600/servoMotors.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="800" data-original-width="425" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3CZYsm6r1nTZXpW2Rm4FnModkTo9jpSYEsR-vpNx_1G6H-QW0xbhb65XsOsOvgUKz6X6jXSmwGdXswAJXd6miCOzfyNeSa4oKAqXJeyZ_if9OesPMw3MhoUOtxkKDh0I8oBsLVSK45w0_/s320/servoMotors.jpg" width="170" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">servo boxes</td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
<br />
Then, waiting on UPS Ground cheap(er) shipping, I thought I would just go ahead and design the mounting brackets and all. Thus I found that not even the motor manufacturer has mechanical drawings of their products (I did find one 3d rendering of one that I could rotate around in virtual space, which was kinda fun). Of course, one of the main things one wants to know when designing mounting plates for geared mechanisms is: How far is the output shaft from the mounting holes?<br />
<br />
So. After the motors arrived I managed to measure and estimate the necessary and the gears seem to mesh OK. Just in case, here is my DXF drawing of the Hitek HS-225, HS-645, HS-755, and HS-805 motor mounts from which you may be able to extract relevant measurements. These are laid out to mesh two 32 pitch gears:<br />
<ul>
<li>40 Tooth 1.250" Pitch diameter C1 Spline mount on the motor</li>
<li>20 Tooth 0.625" Pitch diameter plain bore on the mating arm</li>
</ul>
<br />
<div style="text-align: center;">
<a href="http://www.etantdonnes.com/Media/cholla/servoGears.DXF">http://www.etantdonnes.com/Media/cholla/servoGears.DXF</a> </div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMgFNo8zn8q09tPPFA2ys-TgVsMeQMA_qb72WM9aoK_Ll1nL23oAc1HwzA_nT0zjaTv6b5hr5crW-TtfqMxaB2FXDcjE_Ir2VopFb3fc09SXTWbnlr4v_9ebt16eBW4pctDGFgwuyJIF0j/s1600/servoGears.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="730" data-original-width="497" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMgFNo8zn8q09tPPFA2ys-TgVsMeQMA_qb72WM9aoK_Ll1nL23oAc1HwzA_nT0zjaTv6b5hr5crW-TtfqMxaB2FXDcjE_Ir2VopFb3fc09SXTWbnlr4v_9ebt16eBW4pctDGFgwuyJIF0j/s320/servoGears.JPG" width="217" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="color: #0000ee;"><u>image of DXF for scale</u></span></td></tr>
</tbody></table>
Next up... design arms....<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-72201418455587062022018-10-10T14:53:00.000-06:002018-10-19T12:22:11.588-06:00Variations MoreAnyway. A few years ago the LA County Museum of Art (LACMA) re-started an Art and Technology program that had slipped under the waves in the early 70's. So I hacked and hewed a proposal to make a set of robot arms that Learned to Dance. I called the work Variations For as a pun on John Cage's, Variations IV, the canonical recording of which was made in LA in the mid-60's<br />
<br />
<a href="http://www.etantdonnes.com/KINETIC14/VFor/proposal.html" target="_blank">Variations For Proposal</a><br />
<br />
tldr; The arms solved the problem of knowing their own orientation and could just as well use the same sort of algorithms as my RoboCars to create a sequence of motions -- Teach Themselves to Dance -- AND could be influenced by viewer input via video. Plus the LACMA program was set up to put artists together with technological support -- which I could, certainly, use. That and money to pay for stuff...<br />
<br />
Due to my obvious lack of Charm, Cachet, Credential, and Competence, they wisely declined my proposal. It was however a high point of my applying for things because I got BOTH an acknowledgement of receipt AND a politely encouraging rejection letter.<br />
<br />
Funnily(?) enough. A couple years ago I ran across another artist who had subsequently applied for the same program with a proposal (in my loose interpretation) to make video game characters able to determine their own next moves rather than always having pre-programmed responses. Since he clearly was in possession of all the above 'C' qualities his project was accepted, and I noticed a recent announcement of some show of something in progress, up on which I did not follow.Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-38395148013682190442018-10-10T14:46:00.001-06:002018-10-19T12:22:32.373-06:00N Variations on a ThemeBack in pre-history I had a job doing robotics research which lead me to make some small autonomous vehicles that, thanks to my friend Tori's support in showing something at her Dactyl space in Soho, developed a sequence of motions for themselves -- taught themselves a dance step.<br />
<br />
Here's the whole presentation from a time when the internet sort of worked for video, which it now does in a completely differently-abled way (I think the video files are still there but they are in Quicktime which seems to have been disappeared):<br />
<br />
<a href="http://www.etantdonnes.com/ROBOCAR/Collective/index.html" target="_blank">ROBOCAR Collective</a><br />
<br />
tldr; Each robot had a set of motions it could do -- forward, backward, and turns. It would pick a partner and each would select a motion and then adjudicate who's movement to execute. This would make that motion more likely to be selected, by both partners, in future situations. Then they would pick a new partner and repeat the exercise until, eventually, all five of the vehicles would be performing the same sequence. This was programmed using a Stochastic Finite State Automata, which is a fine state of affairs if you are not jargon enabled.<br />
<br />
Unfortunately, I (they) had no way of knowing where each vehicle was and which way it was facing. So the net result still looked a bit of a jumble. But. Here is the U-Tub money shot from when "they" all happened to synchronize (at https://youtu.be/slfprpe848s):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/slfprpe848s/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/slfprpe848s?feature=player_embedded" width="320"></iframe></div>
<br />
<br />
I tried to fix the position insensitivity a couple times but eventually lost interest due to many of my pre-existing conditions.<br />
<br />
Funny(?) aside. Now, ten or so years later, it appears that Urs Fischer has (hired folks to) solve this problem using a set of office chairs, as displayed at the Gagosian Gallery in Chelsea:<br />
<br />
<a href="https://www.artsy.net/article/artsy-editorial-urs-fischer-9-office-chairs-dancing-gagosian-join" target="_blank">Gagosian -- Fischer -- Chairs</a>Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-20386946348924710712016-01-05T13:20:00.000-07:002016-01-05T13:20:17.486-07:00More SpamGot this, nearly, irresistible offer yesterday in my inbox -- which these days rarely gets anything but unsolicited crap and online sales receipts....<br />
<div class="MsoNormal">
<blockquote class="tr_bq">
<span style="color: #394685; font-size: 16.0pt;"> <span style="font-size: x-small;">Good morn͒in̹g my āss pun͈isȟeͪr...</span></span><span style="font-size: x-small;"><br />
<span style="color: #394685;"> </span><span style="color: #394685;"> arֱe yo̍u ready to f#ck? i'm sͤo wet right noٍw.</span><br />
<span style="color: #394685;"> lͫetِ's chat a͡nd h̭00֪kup</span><br />
<span style="color: #394685;"> </span><span style="color: #394685;"> My nic̽k̴name is <b>Guadٟalupeָ1981</b></span></span></blockquote>
What a great name for a death metal band, eh? All those special letters are html escape sequences that I had no idea one could use...maybe worth looking into getting a trademark?.<br />
</div>
<div style="text-align: center;">
<span style="color: magenta;"><span style="font-size: x-large;">my āss pun͈isȟeͪr</span></span></div>
<div style="text-align: left;">
<br /></div>
Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-90328444781358579552015-10-30T12:20:00.001-07:002015-10-30T12:20:57.061-07:00Local Color XX++Long time, sorry, I've been uninspired by life the universe and everything...<br />
<br />
Anyway.<br />
<br />
Two back-to-back only-in-NM news items have forced me out of forced retirement:<br />
<blockquote class="tr_bq">
<span style="font-size: large;"><a href="http://bossip.com/1245791/fear-the-living-new-mexico-man-beat-zombie-roommate-to-death-after-watching-the-walking-dead-43081/" target="_blank">Man Beats “Zombie” Roommate To Death After Watching The Walking Dead</a></span></blockquote>
<blockquote class="tr_bq">
A New Mexico man who had been watching TV’s “The Walking Dead” says he fatally beat his friend before he could become a zombie. Grants police spokesman Moses Marquez said Sunday that 23-year-old
Christopher Paquin was beaten and that 23-year-old Damon Perry is being
held on a murder charge.</blockquote>
<br />
Sadly it appears that the above was only alcohol related rather than crank driven.<br />
<br />
However this one is definitely ETOH^2:<br />
<blockquote class="tr_bq">
<span style="font-size: large;"><a href="http://www.santafenewmexican.com/news/local_news/mother-and-daughter-struck-and-killed-on-n-m/article_dd392bd2-13ac-59ca-9fd0-6fdff1bc7ea8.html" target="_blank"><span class="blox-headline entry-title">Mother and daughter struck and killed [while fighting about excess drinking] on N.M. 599</span></a></span></blockquote>
<blockquote class="tr_bq">
Lt. Andrea Dobyns of the Santa Fe Police Department said the two women
were traveling with a female friend when they stopped their vehicle and
got into an altercation that escalated to violence.</blockquote>
<br />
Maybe this will put me over the edge...Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0tag:blogger.com,1999:blog-787084628228699051.post-87929526808521024692015-05-10T14:01:00.000-06:002015-05-10T14:01:57.825-06:00Art WorkFollowing on from thought #1, <a href="http://schip666.blogspot.com/2015/04/residue.html" target="_blank">Residue</a>, this is thought #2 from my failed nap a couple weeks ago. If Art is always the useless bits leftover from cultural development, what comprises those bits right now? I'm not smart enough to jump the entire paradigm, but I do have this much:<br />
<br />
Harking back to my whole schtick with the <a href="http://schip666.blogspot.com/2011/11/ai-class-6-game-theory.html" target="_blank">Prisoner's Dilemma</a> where the "rational" solution -- defect -- is the "obvious" strategy which maximizes-reward and minimizes-risk over the short term. This optimizes well for evolutionary natural selection in a scarce and hostile environment, but results in a slightly less beneficial overall outcome -- versus cooperate -- for both parties over the long term.<br />
<br />
And the long term is significant these days.<br />
<br />
Thanks to over fitting, the Social Darwinists of the 19th century are no longer with us -- except in the guise of Libertarian economists -- but we still don't really think outside of the risk/reward box.<br />
<br />
I recently attended an SFI talk by an evolutionary roboticist who, as an aside, complained that his evolved robots did not do so well in the long term. When I suggested that other fitness functions might be tried he pretty much dismissed the idea because they would be out-competed in the short term (in my own self-serving paraphrase of the interchange...).<br />
<br />
But what if we try to do evolution with different utility functions?<br />
<ul>
<li>What's the best option for the common good?</li>
<li>How can we all have the most fun?</li>
<li>Can I be the best improvisational drummer in the ensemble?</li>
<li>What would make the prettiest rainbows come out of my unicorn's butt?</li>
</ul>
In a our real, resource limited, and hostile environment these would be considered counter-fitness functions. However Artificial Life can evolve in a plentiful and benevolent environment of our own making. The problem is that this has no application to the 'real world' (thus far?) and so it is not of interest as a scientific research topic. (There is work on cooperation and altruism, from plants to humans, but the central question usually reverts to the basic economic risk/reward formula, "How does this wasteful behavior contribute to the improvement of the individual's position?")<br />
<br />
If it's not Science, then what is it?<br />
<br />
<center>
<b>...Art...</b></center>
<center>
<br />
</center>
Michael Schipplinghttp://www.blogger.com/profile/11477629259660365398noreply@blogger.com0