Stackbot 1

It’s a long weekend and I figured I’d throw together a little robot.

image not found!

The main body is just from a robot kit I got from Adafruit.

The top disk has an Arduino UNO + Motor Shield sitting in a 3D printed holder. The front has a HCSR04 ultrasonic sensor also sitting in a 3D printed holder.

There’s a LiPo battery taped to the middle section.

3D Printed Parts

The UNO holder makes use of the vitamins provided by nopscadlib:

include <NopSCADlib/lib.scad>
include <BOSL/constants.scad>
include <BOSL/transforms.scad>
use <BOSL/shapes.scad>
include <NopSCADlib/vitamins/pcbs.scad>

PCB = ArduinoUno3;

pcb_shell_clearance = 3;
shell_thickess = 3;
mount_height = 10;

interface_length = 80;
interface_width = 50;

module arduino_uno_mount(pcb) {
    dims = pcb_size(pcb);

    inner_x = dims[0] + pcb_shell_clearance;
    outer_x = inner_x + shell_thickess;

    inner_y = dims[1] + pcb_shell_clearance;
    outer_y = inner_y + shell_thickess;

    outer_z = dims[2] + mount_height;

    interface_length = outer_x + 10;
    interface_height = 3;

    difference() {
        union() {
            cube(size=[outer_x, outer_y, outer_z], center=true);

            difference() {
                zmove(-(outer_z/2) + (interface_height/2)) cube(size=[10, interface_length, interface_height], center=true);
                
                mount_hole_radius = 3;
                ymove((interface_length /2) - mount_hole_radius - 2) cylinder(r=mount_hole_radius, h=50, center=true);
                ymove(-(interface_length /2) + (mount_hole_radius + 2)) cylinder(r=mount_hole_radius, h=50, center=true);
            }
        }
        union() {
            zmove(dims[2]) cube(size=[inner_x, inner_y, outer_z], center=true);
            pcb_cutouts(pcb);
        }
    }
}

module base_stl() {
    stl("base");
    arduino_uno_mount(PCB);
}

module main_assembly() {
assembly("main") {
    render()
        base_stl();
        pcb(PCB);
}
}

if ($preview) {
    main_assembly();
}

image not found!

The ultra sonic sensor:


include <BOSL/constants.scad>
include <BOSL/transforms.scad>
use <BOSL/shapes.scad>

sensor_dims = [10, 45.5, 16];
transceiver_radius = 16.1 / 2;
transceiver_edge_offset = 10;

attachment_size = 10;
attachment_thickness = 2;
attachment_hole_size = (3 / 2) * 1.4;

shell_thickess = 2.5;
clearance = 1.5;

b = 0.01;

module mount() {
    inner_x = sensor_dims[0] + clearance;
    inner_y = sensor_dims[1] + clearance;

    outer_x = inner_x + shell_thickess;
    outer_y = inner_y + shell_thickess;

    ix = inner_x / 2;
    iy = inner_y / 2;

    ox = outer_x / 2;
    oy = outer_y / 2;
    z = sensor_dims[2];

    union() {
        difference() {
            // Initial box to enclose the sensor
            span_cube([-ox, ox], [-oy, oy], [0, z]);
            // Hollow area for the sensor
            zmove(shell_thickess) span_cube([-ix, ix], [-iy, iy], [0, z]);
            // Room for the sensor transmitter and reciever
            cutout_left_offset = -(sensor_dims[1] / 2) + transceiver_edge_offset;
            cutout_right_offset = (sensor_dims[1] / 2) - transceiver_edge_offset;

            translate([ox, cutout_left_offset, (z / 2) + shell_thickess])
                yrot(90) cylinder(r=transceiver_radius, h=shell_thickess * 2, center=true);
            span_cube([ix-b, ox+b], [cutout_left_offset - transceiver_radius, cutout_left_offset + transceiver_radius], [(z / 2) + shell_thickess, z+b]);

            translate([ox, cutout_right_offset, (z / 2) + shell_thickess])
                yrot(90) cylinder(r=transceiver_radius, h=shell_thickess * 2, center=true);
            span_cube([ix-b, ox+b], [cutout_right_offset - transceiver_radius, cutout_right_offset + transceiver_radius], [(z / 2) + shell_thickess, z+b]);
        }
        difference() {
            span_cube([-ox, -ox - attachment_size], [-attachment_size/2, attachment_size/2], [0, attachment_thickness]);
            xmove(-ox - attachment_size / 2) cylinder(r=attachment_hole_size, h=attachment_thickness * 5, center=true, $fn=50);
        }
    }
}

mount();

image not found!

Quick and dirty. Still embracing OpenSCAD.

Firmware

This uses FTL from my last post.

// TODO: make generic
#include <avr/interrupt.h>

#include <ftl/logging/logger.hpp>
#include <ftl/comms/uart.hpp>
#include <ftl/drivers/adafruit/motor_shield.hpp>
#include <ftl/drivers/sensors/hcsr04.hpp>
#include <ftl/platform/avr/interfaces/timer.hpp>
#include <ftl/platform/platform.hpp>

#include <new>

#define DISTANCE_THRESHOLD 0.2
#define TURN_DELAY_MS 2000

using Hardware = ftl::platform::Hardware;

class SystemHardware
{
public:
    SystemHardware()
        : motors_{0x70}
    {
    };

    bool initialize()
    {
        // Initialize I2C
        Hardware::I2C0::initialize(ftl::comms::i2c::ClockMode::Fast);

        // Initialize motor driver
        if (!motors_.initialize(1000))
        {
            return false;
        }

        motors_.setSpeed(0, 0.5);
        motors_.setSpeed(1, 0.5);

        // Enable interrupts
        sei();

        return true;
    }

    void driveForward()
    {
        motors_.forward(0);
        motors_.forward(1);
    }

    void turnLeft()
    {
        motors_.backward(0);
        motors_.forward(1);
    }

    void turnRight()
    {
        motors_.forward(0);
        motors_.backward(1);
    }

    float readForwardRange()
    {
        return range_.read();
    }

private:
    ftl::drivers::MotorShield<Hardware::I2C0> motors_;
    ftl::drivers::Hcsr04<Hardware::GPIOD<7>, Hardware::InputCapture1, Hardware::Timer> range_;
};

enum class DriveState
{
    Driving, Turning,
};

ftl::logging::Logger<Hardware::UART0> logger{ftl::comms::uart::BaudRate::Rate_9600};

SystemHardware hw;


int main()
{
    ftl::logging::SystemLogger::instance().setLogger(&logger);

    if (!hw.initialize())
    {
        LOG_ERROR("failed to initialize system hardware");
        for(;;);
    }

    DriveState state = DriveState::Driving;

    for(;;)
    {
        const auto range = hw.readForwardRange();

        if (state == DriveState::Driving)
        {
            hw.driveForward();

            if (range >= -1.0 && range < DISTANCE_THRESHOLD)
            {
                if (ftl::timerOverflow() % 2)
                {
                    hw.turnRight();
                }
                else
                {
                    hw.turnLeft();
                }

                state = DriveState::Turning;
            }
        }
        else if (state == DriveState::Turning)
        {
            if (range > DISTANCE_THRESHOLD || range < 0)
            {
                state = DriveState::Driving;
            }
        }

        LOG_INFO("range: %02.04f", range);
        Hardware::Timer::delayMs(100);
    }

    return 0;
}

Just drivers around until the sensor detects something.