Skip to main content

Laser pointer arm

Overview

Not really an audio project, but still fun. The entire purpose of  this project was to make a silly cat toy.

It uses a joystick to control two sg90 servos to move around a laser pointer. This is actually a pretty easy project to setup. The joystick is essentially just two potentiometers, one controls the x axis servo, the other controls the y axis. All you need to do is read the analog values from each of the potentiometers, and use the Arduino map() function to map them to the angles of the servos.

This has two modes, on where you control the servos with the joystick, and another where the laser randomly moves around. For the random mode, I thought that just mapping random values directly to the servos would make it too erratic.  Instead of implementing  the random mapping in that way, I made an array of coordinates. This array essentially broke the throw of the laser into a 3x3 grid. Then different positions are randomly chosen from this array, and the laser is set to the chosen positions.

There were a couple of other features I added, a play/pause button for the random mode, a speed control, which controls how fast the laser moves in the random mode, and also a button which controls the width of the throw of the laser.

Code

#include <Servo.h>
#include "my_structs.h"
uint8_t disp_offset = 15;
/* For quickly moving laser without having to input angles manually each time */
extern const Pos quick_pos[NUM_POSITIONS] = {{MIN_ANGLE - disp_offset, MAX_ANGLE + disp_offset}, // top left
{MID_ANGLE, MAX_ANGLE + disp_offset}, // top middle
{MAX_ANGLE + disp_offset, MAX_ANGLE + disp_offset}, // top right
{MIN_ANGLE - disp_offset, MID_ANGLE}, // mid left
{MID_ANGLE, MID_ANGLE}, // mid centre
{MAX_ANGLE + disp_offset, MID_ANGLE}, // mid right
{MIN_ANGLE - disp_offset, MIN_ANGLE}, // bottom left
{MID_ANGLE, MIN_ANGLE - disp_offset}, // bottom middle
{MAX_ANGLE + disp_offset, MIN_ANGLE - disp_offset}}; // bottom right
/* Pin Defines */
const JoyStick JOY_STICK = {.x_pin = A5, .y_pin = A4, .btn_pin = 5};
const uint8_t LED_PINS[NUM_LEDS] = {3, 4, 6, 7};
const uint8_t BTN_PINS[NUM_BUTTONS] = {2, 5, 8};
const uint8_t SPEED_POT = A3;
const uint8_t SERVO_X_PIN = 9;
const uint8_t SERVO_Y_PIN = 10;
Servo servo_x;
Servo servo_y;
/* Variables */
Pos curr_pos = {.x = 90, .y = 90};
Mode current_mode = JOYSTICK;
PlayState play_state = PLAYING;
int play_speed = 100;
void setup()
{
servo_x.attach(SERVO_X_PIN);
servo_y.attach(SERVO_Y_PIN);
for(uint8_t btn = 0; btn < NUM_BUTTONS; ++btn)
{
pinMode(BTN_PINS[btn], INPUT);
}
for(uint8_t led = 0; led < NUM_LEDS; ++led)
{
pinMode(LED_PINS[led], OUTPUT);
}
pinMode(JOY_STICK.x_pin, INPUT);
pinMode(JOY_STICK.y_pin, INPUT);
pinMode(SPEED_POT, INPUT);
attachInterrupt(digitalPinToInterrupt(BTN_PINS[PLAY]), toggle_playstate, RISING);
set_pos(curr_pos);
/* Init leds*/
digitalWrite(LED_PINS[JOYSTICK], HIGH);
digitalWrite(LED_PINS[PLAYING], HIGH);
}
void loop()
{
play_speed = analogRead(SPEED_POT);
play_speed = map(play_speed, 0, 1023, 200, 1000);
poll_buttons();
switch(current_mode)
{
case JOYSTICK:
read_joystick();
break;
case RANDOM:
play_random();
break;
}
}
void read_joystick(void)
{
curr_pos.x = analogRead(JOY_STICK.x_pin);
curr_pos.y = analogRead(JOY_STICK.y_pin);
uint8_t min_angle = MIN_ANGLE - disp_offset + 5;
uint8_t max_angle = MAX_ANGLE + disp_offset - 5;
curr_pos.x = map(curr_pos.x, 0, 1023, min_angle, max_angle);
curr_pos.y = map(curr_pos.y, 0, 1023, min_angle, max_angle);
set_pos(curr_pos);
}
void play_random(void)
{
if(play_state == PLAYING)
{
Pos randpos = quick_pos[random(0, NUM_POSITIONS)];
set_pos(randpos);
delay(play_speed);
}
}
void set_pos(Pos p)
{
servo_x.write(p.x);
servo_y.write(p.y);
}
void poll_buttons(void)
{
if(digitalRead(BTN_PINS[MODE]) == HIGH)
{
cycle_mode();
delay(250);
}
if(digitalRead(BTN_PINS[OFFSET]) == HIGH)
{
set_width();
delay(250);
}
}
/* Set the width of the throw of the laser */
void set_width(void)
{
if(disp_offset < 70)
disp_offset = disp_offset + 10;
if(disp_offset == 70)
disp_offset = disp_offset - 10;
}
void toggle_playstate(void)
{
if(play_state == PLAYING)
{
play_state = STOPPED;
digitalWrite(LED_PINS[STOPPED], HIGH);
digitalWrite(LED_PINS[PLAYING], LOW);
}
else if(play_state == STOPPED)
{
play_state = PLAYING;
digitalWrite(LED_PINS[PLAYING], HIGH);
digitalWrite(LED_PINS[STOPPED], LOW);
}
delay(250);
}
void cycle_mode(void)
{
switch(current_mode)
{
case JOYSTICK:
current_mode = RANDOM;
digitalWrite(LED_PINS[JOYSTICK], LOW);
digitalWrite(LED_PINS[RANDOM], HIGH);
break;
case RANDOM:
current_mode = JOYSTICK;
digitalWrite(LED_PINS[RANDOM], LOW);
digitalWrite(LED_PINS[JOYSTICK], HIGH);
break;
}
}

 

Comments

Popular posts from this blog

4 Channel Audio Switcher Update

Update I've been trying to move away from using the arduino environment and work at the bare metal level for avr mcus. As an experiment I decided to rewrite the code for the audio switcher I wrote about here:   https://inverseaudio.blogspot.com/2020/01/4-channel-audio-switcher.html . Its only a small amount of code so I didn't really expect much difference in terms of code, but it seemed like a good starting exercise. The code compiled within the arduino environment used 1014 bytes (49%) of the program memory, and 15 bytes (11%) of the dynamic memory. I was incredibly surprised to find that the version written in pure c, compiled with atmel studio used only 220 btyes (2.7%) of the program memory, and 2 bytes (0.4%) of the dynamic memory. The only major changes made to the code were the omission of the enum classes, the gpio pin setup, and the setting of the multiplexer pins. In such a small project the space savings weren't entirely necessary. However, I think...

4 Channel Audio Switcher

Overview The purpose of this project was as a test to see if the 4051 mux would be usable in audio switching. I've been designing a programmable guitar pedal switcher and used this as a test project to see if there were any fatal issues with using them for this purpose. I also had some attiny85s around and hadn't found a project for them. Being able to use two pins to control the select lines of 3 muxes was a perfect use case. In a future iteration I'll try the 4052 mux, the only reason I didn't use one this time is because I didn't have any, and I have heaps of 4051s lying around. Bill of Materials Attiny85 3 x 4051 Multiplexers 2 x Momentary buttons 4 x Leds 5 x Stereo sockets 1 x 7805 Voltage regulator 2 x 10uF electrolytic capacitors 3 x 1k resistors Code The code for this is pretty basic, and self explanatory, with the attiny only using 2 inputs and 2 outputs. Each of the buttons either increments or decrements the current channel. Since ...

Ableton transport control

Overview The goal of this project for me is to learn more about midi protocol, and venture into ableton live remote midi scripts. This is a basic transport controller for ableton live, but it can be used for any DAW if you manually map the controls to the CC numbers sent by the arduino. The downside of using the arduino nano is that it uses a usb to serial chip to be able to program the atmega328p. As a result of this it requires the use of a serial to midi bridge to be able to communicate with midi devices. I used Hairless MIDI < - > Serial bridge to do this, which can be found here  https://projectgus.github.io/hairless-midiserial/ . If using windows you will also need some sort of virtual midi port to be able to route the midi messages to ableton, the one I use can be found here  http://www.tobias-erichsen.de/software/loopmidi.html . I only use windows so I'm not sure what needs to be done for mac or linux. The next version I work on will use atmega328u4 wh...