Monday, November 5, 2018

Messages

I 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.

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....

But first... A little about messaging....

 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.

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:

    // USB message function
    // if we have a new message, post the receive task
    // if the message isn't handled by the time this runs again
    //  we will get a second Task post, so get with it...
    // here we just check available()
    //  and hope we have a whole message
    // do we have more than one char available?
    byte nMsg = (Serial.available() > 1) ? 1 : 0;
    if( nMsg != 0 )
        postTask( 0, messageTask, nMsg );
        // note that this could also just execute the method....
        // messageTask( nMsg );

When executed, the messageTask() reads and parses values from the Serial input, then executes user functions as needed.

Message Example

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:

    r 1234 5678\n

The Message Task parses these parameters and get stuff rolling. Two interesting(?) things about functions like Serial.parseInt() are,
  1. 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;
  2. If they don't find anything they like, they block for up to one second waiting ...which is annoying...

But used judiciously these functions can collect parameter values, such as:

    /** Task posted when a serial message is found
     * @param n -- ignored...
     */
    void messageTask( uint16_t n )
    {
        // get the first command character
        char val = Serial.read();
   
        // see what we got, for debugging, etc
        //Serial.print( "cmd: " );
        //Serial.print( val );

        // Perform an action depending on the command
       switch( val )
      {
         case 'r': // run
         case 'R':
           // collect a variable number of arguments
           // -- up to 10 before crashing --
           // assuming that only space is used as a delimiter
           // and not handling trailing spaces very well at all...
           // ... if the next char is a space
           //   presume we have a new parameter...
           int args[10];
           byte count = 0;
           while( Serial.peek() == ' ' )
           {
                args[count] = (char) Serial.parseInt();

                // optional debuging
                //    Serial.print( ' ' );
                //    Serial.print( args[count] );

               ++count;
          }

          // do something with it all...
          runMe( args[0], args[1], ... );
        break;

        // .... //

        default:
            ; // de Nada
      }

      // optional debuging terminate
      //Serial.println();

      // consume anything else including the terminator
      // note: this is important, so we don't get called again
      // with just the terminator in the message input buffer
      do
      {
           val = Serial.read();

      } while( (val != '\n') && (val != -1) );

      return;
    }

Status Return

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.

So there you go...

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.


Next we'll look at the overall program structure.

Sunday, November 4, 2018

Analog to Digital!

When Worlds Collide

Another "feature" of my new improved Arduino architecture is ADC access...

Interrupts

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.

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.

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.

ADC Task

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.

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:

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

With NUM_ADCCHANNEL set to 4:

A four channel ADC cycle takes about 450uS (2222Hz sample rate) so 64 conversion cycles takes about 29mS.

The Leonardo seems to be a bit slower:

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.

With NUM_ADCCHANNEL set to 8 and SCHIPSKIPI2C defined for a total of 6 channels:

A six channel ADC cycle takes about 660uS (1515Hz sample rate) so 64 conversion cycles takes about 43mS.

Audio Data

A tertiary feature -- not used in the Variations 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 SoundBit for an example.

The time between buffer updates and the actual rates for various configurations are listed as the conversion-cycle and sample rate above.

'Kernel' hack

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.

setup()

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.

More Anon.

Friday, November 2, 2018

Task Scheduling

At 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 Software Architecture.

Task functions have the signature:

    typedef void(*PFV)(uint16_t);    // pointer to a function for task list

which looks like this when you define one:

    void myTask( uint16_t arg ) { return; }

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:

    #define USE_NumTasks 8

There are four user entry points to the library:

    void initScheduler(void);

initScheduler() should be called in the setup() method before any tasks or task related interrupts are posted or enabled.

    void scheduler(void);

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.

A task can be inserted into the list using;

    int postTask( uint16_t ticks, PFV func, uint16_t arg );

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:

    postTask( 100, myFunc, 0 );

It will return the task's index in the list, or -1 if it fails.

An executing task may call:

    void repostTask( uint16_t ticks, uint16_t arg );

to put itself back into the scheduler's list with the given execution delay and argument value.

There is, as of now, no removeTask(). Everything in the list gets executed when it's time comes...

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.

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.

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....


Next up: ADCs

Thursday, November 1, 2018

Software Architecture

The low level controller in the Variations Too 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 here...) 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.

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.

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.

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.

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.

On the other hand

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 NO ONE 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.

But I woolgather...

Here's the latest bolus of my improvements to the world:

http://www.etantdonnes.com/DATA/schipArduino.zip

Details at 11.

Sunday, October 21, 2018

Variations Too -- (semi-final) assembly

Semi-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.

But for now....

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:

top of the heavy steel base

Here's all the sensor and controller wiring before the motor assembly was mounted:
wiring underneath

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 ExpressPCB.com proprietary software because I ABSOLUTELY REFUSE to use the popular Program Which Shall Not Be Named):

RoboAssist files

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.

So here's the final:
Variations Too

And a little demo run:


Now on to the software....

Friday, October 19, 2018

Variations Too -- taxonomic exegesis

Lemme take a little timeout here to explain my naming scheme.

As I mentioned earlier, the ur-text for all this was my proposal for an installation called Variations For, which was a pun on the 1960s John Cage piece Variations IV. Get it? Ha. Ha.

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.

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 something. Thus came to be Variations Too. You may have realized this is a pun on Two, as well, as meaning, Also. Ha. Ha.

So. Should VToo 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 Variations 3.20, because it is the first 80% of Variations For, where the last 80% is "just" programming the industrial robots. That's an engineering joke. Ha. Ha.

Anyway.... Next time, some more about the final construction.

Thursday, October 18, 2018

Variations -- on a linkage

Here's another video of the Variations Too robot arm in action:


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:



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.

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.

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.

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...

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.

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.

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.

When set, the fixed-gear arm can be drilled for the 'anti-rotation' pins and they can be Gooped in place.

Sounds simple, eh?