The Individual Coaster Control File

Here it is.  You can see source code for both sides below, here’s the input to make the motion platform pitch and roll at the right time:

NAME:Goliath
START:253
END:356
# Start of pitch and roll data
# Time(s) Pitch/Roll(0/1) position(0-14) speed(0-3) Fan (0/1)
254.0 1 7 0 0
254.2 0 7 0 0
258.7 0 0 0 0
285.5 0 7 0 0
287.5 0 14 0 0
292.0 0 14 0 1
295.0 0 7 2 1
296.1 1 8 1 1
296.2 0 2 1 0
297.4 0 0 2 0
297.6 1 7 1 0
299.1 0 12 1 0
302.7 0 0 1 1
303.5 0 12 2 0
305.7 0 2 1 0
309.5 0 3 1 1
312.4 0 10 2 0
315.1 0 4 1 1
315.6 1 12 0 1
316.6 0 7 0 1
317.3 1 2 2 1
321.6 1 7 2 1
321.7 0 9 2 1
322.4 1 9 1 1
323.4 1 7 0 1
324.0 0 3 1 1
325.8 0 9 1 0
328.9 0 5 1 1
330.6 0 9 1 0
333.4 0 6 0 1
335.1 0 8 0 0
337.9 0 6 0 0
338.9 1 11 1 1
339.9 1 3 1 1
340.0 0 9 1 1
341.8 1 7 1 1
341.9 0 5 1 1
343.4 1 3 1 1
344.7 0 9 1 1
345.3 1 7 1 1
347.1 0 5 1 1
348.6 0 9 1 0
349.9 0 6 1 0
350.4 0 8 1 0
351.1 0 9 2 0
354.0 0 7 0 0

Roller coaster Application Code

And while I’m posting code, here’s the application code.  Just plain ol’ posix C, grabbing a serial port for output the old fashioned way.  If you just want to see the results, scroll about two posts down to the video.  THAT was fun to make.

Next post, I’ll put up the text file that I used for the roller coaster you see in the video.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_TABLE_SIZE 255
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */





int main(int argc, char *argv[]) {
  int pipefds1[2];
  int pipefds2[2];
  int child;
  int i, buffercount=0;
  double video_progression=0;
  char fan[MAX_TABLE_SIZE];
  float trigger[MAX_TABLE_SIZE];
  char axis[MAX_TABLE_SIZE];
  char position[MAX_TABLE_SIZE];
  int table_size;
  char checksum;
  char speed[MAX_TABLE_SIZE];
  int action_step=0;
  float start,end;
  char coaster_name[10];
  FILE * fin;
  char tmp[200];
  char commandstring[100];
  char * pch;
  char * dch;

  switch (argc)
  {
    case 2:
        if ((fin = fopen(argv[1], "r")) == NULL)
        {
            /* First string (%s) is program name (argv[0]). */
            /* Second string (%s) is name of file that could */
            /* not be opened (argv[1]). */
            (void)printf("%s: Cannot open input file %s\n", argv[0], argv[1]);
            return(2);
        }
        break;
    default:
        printf("Usage: %s [file]\n", argv[0]);
        return(2);
  }

  // display what we've just read
  while(fin!=NULL && fgets(tmp, sizeof(tmp), fin)!=NULL)
  { //look for certain key words and grab the number
      // THEN if it's not a key word parse the control line
        pch = strstr(tmp, "START:");
        if (pch)
            {
                start=atof(pch + 6);
            }
        else
        {
            pch = strstr(tmp, "END:");
            if (pch)
                {
                    end=atof(pch + 4);
                }
            else
            {
                pch = strstr(tmp, "NAME:");
                if (pch)
                    {
                        strcpy(coaster_name,pch + 5);
                    }
                else //well, we're not name, end or start
                {

                    if (!(strstr(tmp, "#")) && strstr(tmp, " "))
                    { // looks like we've got a line of data
                        //printf(tmp);
                        dch = strtok (tmp," ");
                        if (dch != NULL)
                            { // first get the trigger time
                                trigger[buffercount]=atof(dch);
                                dch = strtok (NULL, " ");
                                axis[buffercount]=atoi(dch);
                                dch = strtok (NULL, " ");
                                position[buffercount]=atoi(dch);
                                dch = strtok (NULL, " ");
                                speed[buffercount]=atoi(dch);
                                dch = strtok (NULL, " ");
                                fan[buffercount]=atoi(dch);
                        //Troubleshooting debug type code
                                /*printf ("%f ",trigger[buffercount]);
                                printf ("%c ",axis[buffercount]);
                                printf ("%d ",position[buffercount]);
                                printf ("%d ",speed[buffercount]);
                                printf ("%d\n",fan[buffercount]);*/
                            }
                        buffercount++;
                    }
                }
            }
        }
  }
  table_size=buffercount;
  //printf("Start is:%f End is:%f Name is:%s \n",start,end,coaster_name);
  fclose(fin);
  pipe(pipefds1);
  pipe(pipefds2);
  child = fork();
  close(pipefds1[!!child]);
  close(pipefds2[!child]);
  if (!child) {
    dup2(pipefds1[1], 1);
    dup2(pipefds2[0], 0);
    execlp("mplayer", "mplayer", "-slave", "-quiet", "-fs", "raw_6_steel.mp4", NULL);
  }
  else
  {
    char buf[1000];
    char command_out[10] = "~";
    char timebuf[8];
    int r;
    sprintf(commandstring, "seek %f\n",start);
    write(pipefds2[1], commandstring, strlen(commandstring));
    sleep(1);
    write(pipefds2[1], "get_time_pos\n", 13);
    // new stuff
    int fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NDELAY);
      if (fd == -1)
      {
       /*
 * Could not open the port.
 */
      }
    struct termios options;

    /*
     * Get the current options for the port...
     */

    tcgetattr(fd, &options);

    /*
     * Set the baud rates to 115200...
     */

    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);


// 8N1
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

// no hw flow control
    options.c_cflag &= ~CRTSCTS;
    /*
     * Enable the receiver and set local mode...
     */

    options.c_cflag |= (CLOCAL | CREAD);

    /*
     * Set the new options for the port...
     */


    tcsetattr(fd, TCSANOW, &options);


    // end of new stuff
    while ((r = read(pipefds1[0], buf, 1000)) > 0) {
      buf[r] = 0;
      if (strstr(buf,"ANS_TIME_POSITION"))
      {
        for (i=0; i<6; i++)
        {
          timebuf[i]=buf[i + 18];
          if (timebuf[i-1]==46)
          {
              timebuf[i+1]=0;
              i=9;  //big enough to get out of the for loop
          }
        }
        video_progression=atof(timebuf);
        if (video_progression > end)
            write(pipefds2[1], "quit\n", 13);
        //printf("MPlayer out: %f\n", video_progression);
        if (video_progression > trigger[action_step])
        {
            if((action_step == 0) || ((action_step > 0) && (trigger[action_step] > trigger[action_step - 1])))
            {
                checksum=0;
                printf("~");
                printf ("%x",axis[action_step]);
                command_out[1]=axis[action_step];
                checksum+= axis[action_step];
                printf ("%x",position[action_step]);
                command_out[2]=position[action_step];
                checksum+= position[action_step];
                printf ("%x",speed[action_step]);
                command_out[3] = speed[action_step];
                checksum+= speed[action_step];
                printf ("%x",fan[action_step]);
                command_out[4] = fan[action_step];
                checksum+= fan[action_step];
                printf ("%x\n",checksum);
                command_out[5]=(char)checksum;
                write(fd, command_out, 6 );
                action_step++;
            }
        }
      //sleep(1);
      }
      write(pipefds2[1], "get_time_pos\n", 13);

    }
  }
  return 0;
}

Bullduino source code

Thought I would post the sketch for the bullduino.  You can see it’s tailored to my application, but I could easily change it to be a serially controlled anything that needs a pair of servos and a few digital outputs.  You can see that it’s not a complicated sketch, but I did hijack timer1 for pid interrupts:

 

/* coaster.  The bullduino sketch created for #rbcreation 2012, and for my kids!
Author:  Dave Barrett
*/

#define ledPin 13
#define FAN 2
#define Kp 6    // these are the factors used for pid tuning.  A little ziegler-nichols followed by trial and error
#define Kd 10
#define DEADZONE 3

#define FORWARD   350
#define BACK      720
#define LEFT      670
#define RIGHT     350

#define TOP_SPEED  255

bool escapeflag=false, PIDflag1=false, PIDflag2=false, timerflag=false;
char incomingByte[5];   // for incoming serial data
int total=0, maxspeed=250;
int error=0, previous_error=0, previous_error2=0, servogoal_1 = 510, servopos_1=0, topspeed_1 = 255, servogoal_2 = 512, servopos_2=0, topspeed_2 = 255;
int currentspeed=0, command_index=0, checksum=0;
bool directionflag, last_directionflag;


#define servo1feedback 0
#define servo2feedback 1

void setup()
{
  pinMode(ledPin, OUTPUT);
  pinMode(FAN, OUTPUT);
  digitalWrite(FAN, LOW);
  pinMode(7, OUTPUT);
  digitalWrite(7,HIGH);
  pinMode(8, OUTPUT);
  digitalWrite(8,HIGH);
 
  // initialize timer1
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 625;            // compare match register 16MHz/256/100Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
  Serial.begin(9600);     // opens serial port, sets data rate to 9600 bps
}

void go(int howfast)
{
      if (howfast >= 0)
      {
        if (howfast > maxspeed)
          howfast = maxspeed;
        analogWrite (3, 0);
        analogWrite (11,howfast);
      }
      else
      {
        if (howfast < -maxspeed)
          howfast = -maxspeed;
        analogWrite (11,0);
        analogWrite (3, -howfast);
      }
}

void go2(int howfast)
{
      if (howfast >= 0)
      {
        if (howfast > maxspeed)
          howfast = maxspeed;
        analogWrite (5, 0);
        analogWrite (6,howfast);
      }
      else
      {
        if (howfast < -maxspeed)
          howfast = -maxspeed;
        analogWrite (6,0);
        analogWrite (5, -howfast);
      }
}
        
int doPid(void)
{
  int Dterm,Pterm;
  error=(servopos_1 - servogoal_1);
  Dterm= Kd * (previous_error - error);
  Pterm = Kp * error;
  previous_error = error;
  if (abs(error) > DEADZONE)
    return Pterm - Dterm;
  else
    return 0;
}

int doPid2(void)
{
  int Dterm,Pterm;
  error=(servopos_2 - servogoal_2);
  Dterm= Kd * (previous_error2 - error);
  Pterm = Kp * error;
  previous_error2 = error;
  if (abs(error) > DEADZONE)
    return Pterm - Dterm;
  else
    return 0;
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine - also the pid loop flag
{
  if (timerflag)
  {
      servopos_2=analogRead(servo2feedback);
      timerflag=false;
      //digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
      analogRead(servo1feedback); //prep for other servo feedback
      PIDflag2=true;
  }
  else
  {
      servopos_1=analogRead(servo1feedback);
      timerflag=true;
      //digitalWrite(ledPin, 0);   // toggle LED pin
      analogRead(servo2feedback); //prep for other servo feedback
      PIDflag1=true;
  }
}

void loop()
{
  int i;
  char command;
 
  if (PIDflag1)
  {
    PIDflag1=false;
    currentspeed=doPid();
    go(currentspeed);
    //Serial.print(servopos_1,DEC);
    //Serial.println(servogoal_1,DEC);
  }
 
  if (PIDflag2)
  {
    PIDflag2=false;
    currentspeed=doPid2();
    go2(currentspeed);
  }
 
  if (Serial.available())
  {
      command=Serial.read();
      if (command==126)
        command_index=0;
      else
      {
        incomingByte[command_index]=command;
        command_index++;
        if (command_index > 4) //then we should know where to go
        {
          checksum=0;
          for (i=0; i < 4; i++) //just make sure the checksum matches
          {
            checksum+=incomingByte[i];
          }
          if (checksum == incomingByte[4])  //ding ding ding we have a winner!
          {
            //digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
            if (incomingByte[0] == 0)  // then we're pitch
            {
              servogoal_2 = (BACK - (incomingByte[1] * ((BACK - FORWARD) / 14)));
            }
            else  //we're roll
            {
              servogoal_1 = (LEFT - (incomingByte[1] * ((LEFT - RIGHT) / 14)));
            }
            
            maxspeed = TOP_SPEED - ((3 - incomingByte[2]) * 40);
            if (!incomingByte[3])
              digitalWrite(FAN, HIGH);
            else
              digitalWrite(FAN, LOW);
          }
        }
        
              
      }
  }
 
}


Submitted VIdeo

Yep- it’s got plenty of cheese, but here’s our final submission to rbcreation. Hope you like it! I’ve seen some great entries running past on the master feed, you can check it out at http://creation.redbullusa.com/

Give me a day or two to post all the source code and build plans. Right now, I need a night of sleep. Also – Jacob’s birthday party is this weekend, and I’ve got a video game to finish for his birthday!
http://irrlicht.sourceforge.net/forum/viewtopic.php?f=6&t=46337

It’s done!

It's done!

So I finished. Boys had a blast on it, and I’ve got it all on video. I’ll post the video here, along with all the build details on July 4th. Now to do some editing and upload it all to Red Bull! And yes, that is pipe insulation on the handle and the front wall.

It’s Octagonal!

It's Octagonal!

Apparently, I didn’t have enough challenges, so now I have a cold. Great. Anyway, it’s driving me to bed a lot earlier than usual (it’s midnight now, and I’m up with the kids at about 6) and maybe I’ll pop some nyquil on the way. So, tonight, I cut the corners off of the table. This allows for a greater range of motion when two axis are active (i.e. towards the CORNERS). I also turned up the power and speed a bit and sat on the platform for a ride. Finally I moved the pitch heim joint to a new location to help accommodate the further range of motion. Success!

First full test

Finished the software and ran it for the first time. I need to adjust the text file that contains the pitch and roll (and time) data for a better experience, and I need to increase the throw a little. In order to do that, I need to trim the corners, but it looks good!

Enough wiring for one night

Just about done.  Those .1″ sockets aren’t really suited to permanent wiring.  Still need to wire up:

  • Output to fan control
  • Output to motor control
  • Rewire on/off switch (my wire is too short)

Should make for quick work tomorrow night.  Then I can move on to either making the cart seat or programming!

Image