The last thing you would need, is the raw_steel_6.mp4 available from Theme Park Review
These guys supply the first person camera view of a slew of roller coasters, and it’s the source material that I used for creating my entry.
The last thing you would need, is the raw_steel_6.mp4 available from Theme Park Review
These guys supply the first person camera view of a slew of roller coasters, and it’s the source material that I used for creating my entry.
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
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;
}
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);
}
}
}
}
}
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
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.
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!
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!
I should probably attach a rideable shark to the top. Subsequent tests were slower, because this speed is gonna toss some kids!