Tilting Tables

Use sensors and actuators to make a mechanical maze.


Project: Tilting Tables

Difficulty: Moderate

Time: 16-20 hours


In this project, a three-axis accelerometer captures the tilt of the Kinoma Create, which processes the tilt data in software to control the rotation of two servo motors connected to the maze. The result is intuitive movement of the maze relative to the controller. Mechanical gears convert the 180 degrees of servo rotation into 16 degrees as needed for maze control.

If you get stuck along the way, try getting help from the Kinoma Create Forum.

Parts list

For control we use a triple-axis accelerometer, a servo controller board, a level shifter, metal gear-based servos, large drive gears, and a battery pack. For the frame we use assorted aluminum stock and some wood parts.

  1. Kinoma Create
  2. ADXL335 triple-axis accelerometer
  3. Servo motor (Hitec HS-625MG)
  4. Servo mount
  5. SSC-32U USB servo controller (Lynxmotion)
  6. Battery pack
  7. 12-tooth servo gear (Hitec)
  8. 128-tooth servo gear (Actobotics)
  9. Large-gear hub adapter
  10. Flanged ball bearings
  11. 1 1/2" x 1 1/2" x 1/8" aluminum angle stock
  12. 3/4" x 3/4" aluminum box-section stock
  13. 3/4" x 1/2" aluminum U-shaped stock

Basic measurements

This project proves that Kinoma Create can control multiple servos and that basic servos, when geared down, are strong enough to easily move the gimbled maze. The basic measurements of what we built are shown below and in this PDF. We would be delighted to see others push this concept further with larger and more complex mazes.


Outer frame

The outer frame needs to be rigid, because it carries the weight of the inner frame, maze, and inner servo and gears. In developing this project, we did everything we could to keep the parts moved by the servos both lightweight and stiff. After working out the part fit, we replaced nuts and bolts with rivets, and we used flanged bearings on all the pivot points. A sample of the aluminum stock is shown below.

We used a cut-off saw to miter the outer-box stock. We cut our own angle brackets from the 1 1/2" angle material.

We used two brackets in each corner for added stiffness. Be sure to offset your bolt/rivet holes so that there is internal clearance for the rivet backs.

The outer frame with rivet fasteners is shown below.

Center the large gear on the axle and attach it to the outer frame.

Because our outer frame has an interior dimension of 5/8" square, we could insert hardwood stock to make the axle area stiff and allow installation of threaded nutserts to greatly simplify assembly and disassembly.

Once both the tables and the maze are in place, balance the frame/maze assembly by adding weight to the outer frame on the side opposite the servo and gear. This reduces the strain on the outer-frame servo. We added weight inside the outer frame and put steel bar stock under the frame.


Inner frame

The inner frame is smaller in one dimension to allow space for the gear to control the rotation of the inner table. It is U-shaped stock, to capture the maze element, and has one end fastened with bolts so that the maze can be changed. Similar angle brackets set up the corners.


Servos, gears, and axles

Using threaded bolts as axles simplifies disassembly for travel. The axles are 1/4" bolts. With 1/4" bolts we can use standard bearings, which help smooth the motion. The servos need be mounted so that they can pivot away from the large gears for disassembly and setup. The servo mounts from SparkFun are very rigid and take up minimal space on the frame.

The large gears have oversized center openings. We use these gear hub adapters so that we can get them accurately centered on our axles, eventually fastening them directly onto the frames.

The servos need to pivot away from the large gears for disassembly and to adjust gear lash. For the inner frame we made a pivoting bracket out of angle material, with a threaded nutsert to fine-tune the servo gear engagement.

The outer-frame servo is mounted on a similar pivoting angle bracket on the inside of the table support. Oversizing the hole in the table support gives play for final adjustment.

Do what you can to keep all the holes straight and true.



We made the maze from PVC material (purchased at TAP Plastics in California). We had 1" strips cut out of 1/4" material so that we could easily make our wall sections. The base was 1/8" sheet. Working and gluing the PVC is simpler than with other materials, and it is fairly light and stiff. A PDF file of our maze layout can be found here, but we hope you will come up with more interesting challenges. We tried wooden, steel, and acrylic 1" balls, and the acrylic gave the best game play; we painted a clear off-the-shelf sphere.



The base and supports are wood. We added threaded inserts to make working with the axles simpler, and added nutserts on the bottom of the supports to make mounting cleaner. Once you have the supports and frames built, you can adjust final positioning with 1/4" nylon spacers.


Control boards

The Lynxmotion servo controller generates very stable PWM signals. We made a small protoboard to shift the voltage of our signal from Kinoma Create's 3.3V to the 5V expected by the Lynxmotion controller. Depending on the servos you use, supply the needed motor power; we used 9V with the Hitec HS-625MG servos. We added some strain relief to the connection cable on the base and in the Kinoma Create.


Accelerometer module

Install the accelerometer module in the front sensor bay. Below we used the left side. Configure the pins to match the placement of the module using the Front Pins app.


Kinoma Create app

You can download the source code here, as LabAccel.zip. It contains the app code and required BLLs. Leave it as a .zip file.

Next, import the app and BLLs into Kinoma Studio. Start by selecting Import from Studio's File menu.

Then select Existing Projects into Workspace and click Next.

Choose the Select archive file option and navigate to the downloaded LabAccel.zip file. The project and associated BLL directories should show in the Import dialog. Click the Finish button to complete the import.

At this point you can edit and run the main application file, src/main.js.

To run the app, click the Run button on the LabyrinthAccel/application.xml page. You will see your Kinoma Create device if it is on the same Wi-Fi network.

Tip: If you select the gear next to your Kinoma Create on the LabyrinthAccel/application.xml page, you can check the Install on launch box to install the app on your Kinoma Create for future use without rerunning from the Studio IDE. If you do not check that box, the app is only temporarily installed.

After launching the app, put the Kinoma Create on a flat surface and single-tap on the axis of the crosshairs to center the servo motors.


Code snippets

The KinomaJS application performs two essential tasks: monitoring the accelerometer and translating the sensed acceleration data into matching motor commands. A basic configuration and reading of the analog accelerometer data could be accomplished in a BLL like this:

exports.pins = {
    x: {type: "A2D"},
    y: {type: "A2D"},
    z: {type: "A2D"}

exports.configure = function( parameters ) {

exports.read = function( smooth ) {
    var x = this.x.read();
    var y = this.y.read();
    var z = this.z.read();    
    return {x: x, y: y, z: z};

It would be better, however, to add a couple of features to the accelerometer BLL: calibration, the option to tell the BLL when the device is level; and smoothing, reducing the noisiness of the analog input to prevent jitter of the maze. The full project source includes implementations of these two features. The smoothing is accomplished using a simple low-pass filter.

Sending commands to the motors is accomplished in two parts. The BLL side listens for commands from the app and converts them into serial messages following the SSC-32U's communication protocol (as specified in its user guide). The BLL looks like this:

exports.pins = {
    controller: { type: "Serial", baud: 9600}

exports.configure = function(data){
    this.speed = 5000;
    if ("speed" in data) this.speed = data.speed;

exports.sendDualCommand = function( parameters ){
    var toSend = "#" + parameters.channel1 + "P" + parameters.pulseWidth1 + "S" 
    	+ this.speed;
    toSend += "#" + parameters.channel2 + "P" + parameters.pulseWidth2 + "S" 
    	+ this.speed;
    toSend += "\r";

The app communicates with the motor BLL every time it gets data back from the accelerometer BLL. This begins when the data handler of the app is invoked as the accelerometer's callback. This handler updates the display, calculates the proper angles for the two servo motors, and then calls doDualMotorControl:

Handler.bind( "/data", {
    onInvoke: function ( handler, message ){
        var j = message.requestObject;  
        x = ( ( j.x ) * MAGNITUDE ) + .5;
        y = ( ( j.y ) * MAGNITUDE ) + .5;
        z = ( ( j.z ) * MAGNITUDE ) + .5;
        display.delegate( "draw" );
        if startSendingMotor){
        	var xAngle = MIDANGLE + (j.x * MAGNITUDE * RANGE);
        	var yAngle = MIDANGLE + (j.y * -MAGNITUDE * RANGE);
        	doDualMotorControl(MOTORPORT1, xAngle, MOTORPORT2, yAngle);

TThe doDualMotorControl function invokes the sendDualCommand function of the motor BLL.

var doDualMotorControl = function(channel1, angle1, channel2, angle2){
    application.invoke(new MessageWithObject("pins:/ssc/sendDualCommand", {
        channel1: channel1,
        pulseWidth1: angle1,
        channel2: channel2,
        pulseWidth2: angle2,


You have successfully made a mechanical maze!