The scribblebot is a plotter that works similar to the human arm.

It can plot, but the writing is rather ragged, hence the robots name. Plotting with a pen was not looking really good. See the picture that is supposed to show a heart and an eight. Thus we have chosen a more forgiving and somewhat more poetic medium to write to. The scribblebot writes into the sand. In sand the raggedness of the plot is smoothed out well.

The bot has an arm with 2 parts that can be moved by actuators. The whole arm structure can be lifted with a lever to allow for movements of the arm without drawing. The two parts of the arm are of approximately equal length. This helps in controlling the movements as the equations are much simpler.

The "pen" is just a short stick attached to the tip of the arm that is pushed in the sand by the arm.

The controlling program reads a file that contains the plotting instructions. Each instruction consists of 3 elements (numbers). The first number tells the program whether the "pen" is up or down for the move and the two following numbers tell the robot where to move. The movement is first to move the upper arm and then the forearm. As the movement is in sequence in the two dimensions, it is necessary to make small steps so that the jitter of the movement remains small. (This is our first project with "bricxCC" by the way, which we like very much now.) As the plotfiles are difficult to prepare manually, we have written a Java program to prepare plotfile instructions (see below).

The bot calibrates before every plot by moving the forearm away, moving the upper arm until the touch sensor is pressed, move the forearm until it blocks. The lifter is also calibrated by pressing down the lever until it blocks and then moving back 270 degrees into the low position.

To create a file with plot instructions we wrote a simple program that allows us to draw a figure. The program translates the x,y coordinates of the drawing into angle values for the motors. The equations for the transformation are not too complicated as both our arms have the same length. For the technical minded, the equations in Java notation are, for the upper arm angle (the motor is geared down):

-(6.8 * (90.0 + 180.0 / Math.PI * Math.acos(0.5 * Math.sqrt(x * x + y * y)) - 180.0 / Math.PI * Math.atan(x / y)));

and for the forearm angle :

2.0 * 180.0 / Math.PI * Math.acos(0.5 * Math.sqrt(x * x + y * y))

(The equations are much easier digestible with a sketch of the geometry.) The bot does not expect absolute angular values but only movement instructions. Thus the instruction file that is finally written only contains the deltas between the step. The program is very simple so one needs to click a lot to get enough points so that the drawing does not become too ragged.

As one can see from the example figure, the drawings are far away from perfect (much different for example to the plotter by "nilsvoelker" on the Mindstorms NXTLog. Search for his projects too see a perfect plotter) and only simple shapes can be used. As can be seen the ships figure from the program screenshot is not really recognizable in Figure 2. (Well, some say it is not even recognizable from the screenshot).

NXC Source Code:

 
    
#define OBERARM OUT_C
#define UNTERARM OUT_B
#define HEBER OUT_A
#define OBERARM_POLLER_KANAL IN_1
#define OBERARM_POLLER SENSOR_1

#define HEBEWINKEL 150

#define GEHEZU_AKTION 1
#define LINIEZU_AKTION 2

sub playATone()
{
  SoundPlayToneType sptArgs;
  sptArgs.Frequency = 440;
  sptArgs.Duration = 400; // 1
  sptArgs.Loop = false;
  sptArgs.SoundLevel = 3;
  SysSoundPlayTone(sptArgs);
}

sub playErrorTone()
{
  SoundPlayToneType sptArgs;
  sptArgs.Frequency = 440;
  sptArgs.Duration = 400; // 1
  sptArgs.Loop = false;
  sptArgs.SoundLevel = 3;
  SysSoundPlayTone(sptArgs);
}

/*
  Hebt den Stift
*/
inline void stiftHoch()
{
   if (MotorRotationCount(HEBER) > -50)
   {
     RotateMotor (HEBER,-40,HEBEWINKEL);
   }
}

/*
  Senkt den Stift
*/
inline void stiftRunter()
{
   if (MotorRotationCount(HEBER) < -50)
   {
       RotateMotor (HEBER,30,HEBEWINKEL);
   }
}

inline void schreib(int A, int B)
{
      RotateMotor (OBERRARM,30,A);
      RotateMotor (UNTERARM,30,B);
}

task main ()
{
   byte plotDatei;
   int fSize;
   /*
     Kalibriere, Schreibarm an das Gehäuse.
     Heber gegen den Poller und dann nach unten
   */
   SetSensorTouch (OBERARM_POLLER_KANAL);
   // Unterarm zurück
   OnFwd (UNTERARM,-10);
   Wait (2000);
   Off(UNTERARM);
   // Oberarm an Poller
   OnFwd (OBERARM,30);
   while (OBERARM_POLLER==0)
   {
     Wait(100);
   }
   Off(OBERARM);
   // Unterarm anlegen
   OnFwd (UNTERARM,10);
   Wait (3000);
   Off(UNTERARM);
   // Heber zurück
   OnFwd(HEBER,20);
   Wait(4000);
   Off(HEBER);
   // Heber auf null
   RotateMotor (HEBER,-30,270);
   ResetRotationCount(OUT_ABC);
   stiftHoch();

   if (NO_ERR == OpenFileRead("plotdatei.txt",fSize,plotDatei))
   {
        int aktion;
        // Unterarmwinkel
        int A;
        // Oberarmwinkel
        int B;
        bool eof=false;
        while (true != eof)
        {
          string buf;
        if(ReadLnString(plotDatei,buf) != NO_ERR)
           {
              eof = true;
              break;
           }
           aktion=StrToNum(buf);
           if(ReadLnString(plotDatei,buf) != NO_ERR)
           {
              eof = true;
              break;
           }
           A=StrToNum(buf);
           if(ReadLnString(plotDatei,buf) != NO_ERR)
           {
              eof = true;
              break;
           }
           B=StrToNum(buf);
           switch (aktion)
           {
              case GEHEZU_AKTION :
                stiftHoch();
                schreib(A,B);
                break;
              case LINIEZU_AKTION :
                stiftRunter();
                schreib(A,B);
                break;
              default:
                eof=true;
           }
        }
   }
   else
   {
        playErrorTone();
   }
   CloseFile(plotDatei);
   playATone();
}