Sorting by colour with MeArm and TCS34725

Posted on
  • Me and @JGreen (@jsjgreen on Twitter) had a go at a demo for his GCSE Technology classes (for John Port School) yesterday - it's showing how a Microcontroller can use inputs to control outputs in order to do something useful...

    https://youtu.be/Jxz2aHnxqpI

    It's just a MeArm, Adafruit TCS34725, and one of the original Espruino boards...

    • The MeArm is on C4, B15, B14 and B13
    • WS2811 LEDs are on B5
    • TCS34725 is on B8(scl) /B9 (sda)

    And that's it - the arm just runs through a set of keyframes that are stored in an array that tell it how to move to pick up the token and place it in front of the sensor, and once the sensor has had time to average out the colour it then chooses from 1 of 3 sets of movements, which determine where the token is dropped.

    Source code should be attached - it's a bit too big to inline, but mostly because of the comments!

    Thanks to @DrAzzy for the TCS3472x module - docs for it will be on the website in the next day here


    1 Attachment

  • @Gordon woo working with my 7yr old we are part way building meArm she thinks this is "kool" Also helps with mechanical construction to see it up so close. Is colour disk dispenser a 3d printed item or sumit you found ebay.

    Thanks for sharing

  • Great! I'd love to see how you get on - let me know if you have any questions :)

    The disc dispenser is something @JGreen laser cut (along with discs out of some bits of scrap) - I could probably find the drawings for you... The original plan was to use coloured ping-pong balls though, and you could make a great little feed for those out of 2 lengths of bent coat hanger (I've seen the MeArm lift ping-pong balls really well.

    Only things I'd say to watch out for are:

    • Decent power supply - Even with capacitors, it was really pushing it trying to power the Arm and lights off of USB - we used a bench supply in the end.
    • The light sensor's great, but it gets thrown off by ambient light a lot. It'd be worth making a little black box with it in, so the only light it sees is what gets reflected from the ball/token/whatever.
  • @Gordon thanks for the heads up on power requirements.
    We're oh hols for 10 days but will be back at it on our return.

  • // Servo 0: base rotation
    // Servo 1: direct in/out movement
    // Servo 2: in/out movement via linkage
    // Servo 3: gripper
    
    // Servo pins
    var SERVOS = [C4,B15,B14,B13];
    // Servo home pulse widths - tweak these for different arms (depending on assembly)
    var SERVOHOME = [1.51,1.5,1.5,1.5];
    // gripper positions
    var JOPEN = 0;
    var JCLOSE = 0.45;
    
    // Keyframes for movement of arm. Each line
    // is a position, and we move between them
    // at 2fps
    
    // Pick up token, move it to the sensor
    var KF_PICKUP = [
      [ 0, 0, 0, JOPEN ], // default position
     // [ 0, 0.3, -0.1, JOPEN ],
      [ 0, 0.25, -0.1, JOPEN ],
      [ 0, 0.45, -0.3, JOPEN ], // over token here
      [ 0, 0.45, -0.3, JCLOSE ], // over token here
      [ 0, 0.25, -0.1, JCLOSE ],
      [ 0, 0, 0, JCLOSE ],
      [ 0.75, -0.3, 0, JCLOSE ], // colour sensor here
      [ 0.75, -0.3, 0, JCLOSE ],
    ];
    // Hold steady at the sensor
    var KF_TEST_RGB = [ // just hold steady for 2 secs
      [ 0.75, -0.3, 0, JCLOSE ],
      [ 0.75, -0.3, 0, JCLOSE ],
      [ 0.75, -0.3, 0, JCLOSE ],
      [ 0.75, -0.3, 0, JCLOSE ],
      [ 0.75, -0.3, 0, JCLOSE ],
    ];
    // move from the sensor to a location, release gripper, go home
    var KF_DOWN_LEFT = [
      [ 0.75, -0.3, 0, JCLOSE ], // colour sensor here
      [ 0.3, 0.45, -0.3, JCLOSE ], // down left
      [ 0.3, 0.45, -0.3, JOPEN ],
      [ 0, 0, 0, JOPEN ], // home
      [ 0, 0, 0, JOPEN ],
    ];
    var KF_DOWN_RIGHT = [
      [ 0.75, -0.3, 0, JCLOSE ], // colour sensor here
      [ -0.3, -0.3, 0, JCLOSE ],
      [ -0.3, 0.7, 0.0, JCLOSE ], // down left
      [ -0.3, 0.7, 0.0, JOPEN ],
      [ 0, 0, 0, JOPEN ], // home
      [ 0, 0, 0, JOPEN ],
    ];
    var KF_DOWN_RIGHT_NEAR = [
      [ 0.75, -0.3, 0, JCLOSE ], // colour sensor here
      [ -0.4, -0.3, 0, JCLOSE ],
      [ -0.4, -0.2, -0.4, JCLOSE ],
      [ -0.4, -0.2, -0.4, JOPEN ], // down left
      [ 0, 0, 0, JOPEN ], // home
      [ 0, 0, 0, JOPEN ],
    ];
    
    // variables for keyframe movement
    var keyInterval;
    var keyPos = 0;
    var keyEnd = 0;
    var keyFrames = [];
    var keyCallback;
    
    
    var rgbAvr = [0,0,0]; // average RGB value
    var tcs; // rgb sensor
    var isRunning = false; // for on/off button
    var ledRGBs = new Uint8Array(25*3); // values for LED strip
    
    // Called to animate between keyframes
    function step() {
      keyPos += 0.04; // 20ms * 2fps
      var n = 0|keyPos;
      var a = keyPos-n;
      var n2 = n+1;
      //print(keyPos,n,n2,a);
      for (var i in SERVOS) {
        // interpolate between keyframes
        var p = keyFrames[n][i]*(1-a) + keyFrames[n2][i]*a;
        // move servo
        digitalPulse(SERVOS[i], 1, SERVOHOME[i]+p);
      }
      // are we finished?
      if (keyPos >= keyEnd) {
        clearInterval(keyInterval);
        keyInterval = undefined;
        if (keyCallback) keyCallback();
      }
    }
    
    // Play back an array of keyframes, call callback when done
    function playback(frames, callback) {
      keyPos = 0 - 0.02;
      keyEnd = frames.length-2;
      keyFrames = frames;
      keyCallback = callback;
      keyInterval = setInterval(step, 20);
    }
    
    // Test a single token
    function testToken() {
      // pick it up
      playback(KF_PICKUP, function() {
        // then wait with it by the sensor for 2 secs
        playback(KF_TEST_RGB, function() {
          // print the colour values and work out if it's red, green or blue      
          // finally put it down in the right place and start again
          print(rgbAvr);
          if (rgbAvr[0] > rgbAvr[1] && rgbAvr[0] > rgbAvr[2]) { // red
            print("Red!");        
            playback(KF_DOWN_LEFT, testToken);
          } else if (rgbAvr[1] > rgbAvr[0] && rgbAvr[1] > rgbAvr[2]) { // green
            print("Green!");
            playback(KF_DOWN_RIGHT, testToken);
          } else {
            print("Blue!");
            playback(KF_DOWN_RIGHT_NEAR, testToken); // blue
          }
        });
      });
    }
    
    // Get RGB value from the sensor
    function getRGB() {
      // get the value
      var v = tcs.getValue();
      // re-scale it, to get full saturation and lightness (leaving just hue)
      var min = Math.min(v.red, v.green, v.blue);
      var range = Math.max(v.red, v.green, v.blue) - min;
      if (range<1) range=1;
      var rgb = [ // values between 0 and 255
        (v.red - min)*200/range, // tweak red - so we don't accidentally classify green sometimes
        (v.green - min)*255/range,
        (v.blue - min)*255/range,
      ];
      // handle the average
      rgbAvr[0] = rgbAvr[0]*0.9 + rgb[0]*0.1;
      rgbAvr[1] = rgbAvr[1]*0.9 + rgb[1]*0.1;
      rgbAvr[2] = rgbAvr[2]*0.9 + rgb[2]*0.1;
      // set the LED strip to the right colour
      for (var i=0;i<ledRGBs.length;i+=3)
        ledRGBs.set(rgbAvr,i);
      SPI1.send4bit(ledRGBs, 0b0001, 0b0011);
    }
    
    // Start the arm working - initialise RGB sensor and start movement
    function startArm() {
      SPI1.setup({baud:3200000, mosi:B5}); // LEDs
      I2C1.setup({scl:B8, sda:B9}); // RGB sensor
      tcs = require("TCS3472x").connect(I2C1, 
        1 /*integration cycles*/, 
        1 /*gain*/);
      testToken(); // start arm movement
      setTimeout(function() {
        // we seem to need a delay after initialisation before we use the RGB sensor
        setInterval(getRGB,20);
      },1000);
    }
    
    // On/off button
    setWatch(function() {
      if (!isRunning) {
        LED1.set();
        isRunning = true;
        startArm();
      } else {
        LED1.reset();
        isRunning = false;
        clearInterval();
      }
    }, BTN, {edge:"rising", repeat:true, debounce:50});
        
    
    // For testing - call startTest() and then change the
    // values in the test array to work out the positions
    // needed for the keyframes array
    var test = [0,0,0,0];
    function startTest() {
      setInterval(function() {
        for (var i in SERVOS) {
          digitalPulse(SERVOS[i], 1, SERVOHOME[i]+test[i]);
        }
      }, 20);
    }
    //startTest()
    
  • enter code here strong text


    1 Attachment

  • Hi - please could you explain what you have posted up here? Is the JS code changes somehow? And what is the Arduino code for?

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Sorting by colour with MeArm and TCS34725

Posted by Avatar for Gordon @Gordon

Actions