Appendix

Appendix A

"The group approves this report for inclusion on the course website."
"The group approves the video for inclusion on the course youtube channel."

Appendix B


xylophone.c 

/**
* Self-Playing Xylophone
* Karina Melgar (kmm477), Zoe Chen (zmc6), and Harris Miller (hwm44)
*
* Based on code provided by V. Hunter Adams (vha3)
*
* HARDWARE CONNECTIONS
*   - GPIO 16 ---> VGA Hsync
*   - GPIO 17 ---> VGA Vsync
*   - GPIO 18 ---> 330 ohm resistor ---> VGA Red
*   - GPIO 19 ---> 330 ohm resistor ---> VGA Green
*   - GPIO 20 ---> 330 ohm resistor ---> VGA Blue
*   - GPIO  8 ---> PWM output 1 (4A) [pin 11]
*   - GPIO  9 ---> PWM output 2 (4B) [pin 12]
*   - GPIO 10 ---> PWM output 3 (5A) [pin 14]
*   - GPIO 11 ---> PWM output 4 (5B) [pin 15]
*   - GPIO 12 ---> PWM output 5 (6A) [pin 16]
*   - GPIO 13 ---> PWM output 6 (6B) [pin 17]
*   - GPIO 14 ---> PWM output 7 (7A) [pin 19]
*   - GPIO 15 ---> PWM output 8 (7B) [pin 20]
*
*  Notes: do not trigger more than 4 servos at once
*  - causes major instability + timing loss
*/

// Standard libraries
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
// Pico libraries
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/pwm.h"
#include "hardware/irq.h"
// Protothreads
#include "pt_cornell_rp2040_v1.h"
// Custom headers
#include "music.h"

// PWM wrap value and clock divide value
// For a CPU rate of 125 MHz, this gives
// a PWM frequency of 50 Hz.
#define WRAPVAL 50000
#define CLKDIV 50.0f
#define FREQ (50000/20)
#define NUM_SERVOS 8

// Xylophone servo state machine enum
typedef enum {
  WAIT = 0,
  STRIKE_DOWN,
  STRIKE_UP,
} xylo_state_t;

// PWM channel and state information
// ordered from lowest to highest pitch
uint slice_num[NUM_SERVOS];
volatile uint XYLO_STATE[NUM_SERVOS] = {WAIT,WAIT,WAIT,WAIT,WAIT,WAIT,WAIT,WAIT};
const uint PWM_CHAN[NUM_SERVOS]      = {PWM_CHAN_A, PWM_CHAN_B, PWM_CHAN_A, PWM_CHAN_B, PWM_CHAN_A, PWM_CHAN_B, PWM_CHAN_A, PWM_CHAN_B};
const uint UP[NUM_SERVOS]            = {4500, 3300, 3700, 4100, 3600, 2900, 3700, 3700};
const uint DOWN[NUM_SERVOS]          = {4900, 3700, 4100, 4550, 3200, 2500, 3300, 3200};

// PWM duty cycle storage
volatile int control[NUM_SERVOS]     = ;
volatile int old_control[NUM_SERVOS] = ;

// timer storage
volatile uint counter = 0;
volatile uint beat = 0;

// Current song pointer
volatile song_t* curr_song = NULL;

// PWM interrupt service routine
void on_pwm_wrap() {
    for(size_t i = 0; i < NUM_SERVOS; i++){
        // Update duty cycle
        if (control[i] != old_control[i]) {
            old_control[i] = control[i] ;
            // set specific PWM channel given slice, channel (A/B), and value
            pwm_set_chan_level(slice_num[i], PWM_CHAN[i], control[i]);
        }
    }
    for(size_t i = 0; i < NUM_SERVOS; i++){
        // clear all IRQs
        pwm_clear_irq(slice_num[i]);
    }
}

// Timer ISR
bool repeating_timer_callback(struct repeating_timer *t) {
    // increase counter on IRQ
    counter++;
    // if song loaded and beat period expired:
    if (curr_song != NULL && counter >= curr_song->beat_period) {      // 1000 --> 60 bpm, strike every second
        // reset counter
        counter = 0;
        // if beat period is valid (non-zero)
        if(curr_song->beat_period != 0) {
            // initiate strike for any servos for this beat
            for(size_t i = 0; i < NUM_SERVOS; i++) {
                if(curr_song->notes[i][beat]) {
                    XYLO_STATE[i] = STRIKE_DOWN;
                }
            }
            // increase beat counter
            beat++;
            // if no beats remaining
            if(beat >= curr_song->end_beat) {
                // destroy pointers
                curr_song = NULL;
                // reset beat counter
                beat = 0;
            }
        }
       
    }
    return true;
}

// User input thread
static PT_THREAD (protothread_serial(struct pt *pt)) {
    PT_BEGIN(pt);
    // stores user input
    static uint user_input ;
    // wait
    PT_YIELD_usec(1000000) ;
    // write song list:
    sprintf(pt_serial_out_buffer, "\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "0)  Scale\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "1)  Happy Birthday\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "2)  Wii Sports Theme\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "3)  Cornell Alma Mater\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "4)  Chopsticks\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "5)  Jingle Bells\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "6)  Old MacDonald\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "7)  Chords\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "8)  Baby Shark\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "9)  Dreidel\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "10) Surprise :)\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "11) Twinkle Twinkle Little Star\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "12) The Office Theme\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "13) I Want You Back\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "14) A Thousand Miles\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "15) A Whole New World\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "16) Carol of the Bells\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "17) Leave the Door Open\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "18) Love Story\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "19) Mario\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "20) Ode to Joy\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "21) Spring\r\n");
    serial_write ;
    sprintf(pt_serial_out_buffer, "22) Star Wars\r\n");
    serial_write ;

    while(1) {
        // print prompt
        sprintf(pt_serial_out_buffer, "Enter song number to play: ");
        // non-blocking write
        serial_write ;
        // spawn a thread to do the non-blocking serial read
        serial_read ;
        // convert input string to int
        sscanf(pt_serial_in_buffer, "%d", &user_input) ;
        // confirm value before indexing (prevents out-of-bounds)
        if(user_input < NUM_SONGS) {
            curr_song = song_list[user_input];
        }
    } // END WHILE(1)

    PT_END(pt);
} // serial thread

// Strike down/up thread
static PT_THREAD (protothread_play(struct pt *pt)) {
    PT_BEGIN(pt) ;
    // play control loop
    while(1) {
        // loop over all servos
        for(size_t i = 0; i < NUM_SERVOS; i++){
            // initiate strike when commanded
            if(XYLO_STATE[i] == STRIKE_DOWN){
                control[i] = DOWN[i];
                XYLO_STATE[i] = STRIKE_UP;
            // initiate lift when timer expires
            } else if(XYLO_STATE[i] == STRIKE_UP && counter >= 70){
                control[i] = UP[i];
                XYLO_STATE[i] = WAIT;
            }
        }
        // yield to allow serial input
        PT_YIELD_usec(10000) ;
    }
    PT_END(pt) ;
}

int main() {
    // Initialize stdio
    stdio_init_all();

    // init servo PWM values to resting position
    for(size_t i = 0; i < NUM_SERVOS; i++){
        control[i] = UP[i];
    }

    ////////////////////////////////////////////////////////////////////////
    ///////////////////////// PWM CONFIGURATION ////////////////////////////
    ////////////////////////////////////////////////////////////////////////
    uint32_t mask = 0;
    for(size_t i = 0; i < NUM_SERVOS; i++){
        // Tell GPIO that it is allocated to the PWM
        gpio_set_function(i+8,  GPIO_FUNC_PWM);

        // Find out which PWM slice is connected to GPIO 8-15
        slice_num[i] = pwm_gpio_to_slice_num(i+8);

        // Mask our slice's IRQ output into the PWM block's single interrupt line,
        // and register our interrupt handler
        pwm_clear_irq(slice_num[i]);
        pwm_set_irq_enabled(slice_num[i], true);

        // This section configures the period of the PWM signals
        pwm_set_wrap(slice_num[i], WRAPVAL) ;
        pwm_set_clkdiv(slice_num[i], CLKDIV) ;

        // This sets duty cycle
        pwm_set_chan_level(slice_num[i], PWM_CHAN[i], FREQ);
        mask |= (1u << slice_num[i]);
    }

    irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
    irq_set_enabled(PWM_IRQ_WRAP, true);

    // Start the channel
    pwm_set_mask_enabled(mask);

    // Create a repeating timer that calls repeating_timer_callback.
    // If the delay is > 0 then this is the delay between the previous callback ending and the next starting.
    // If the delay is negative (see below) then the next call to the callback will be exactly x us after the
    // start of the call to the last callback
    struct repeating_timer timer;

    // Negative delay so means we will call repeating_timer_callback, and call it again
    // 1000us (1kHz) later regardless of how long the callback took to execute
    add_repeating_timer_us(-1000, repeating_timer_callback, NULL, &timer);

    // start protothreads
    pt_add_thread(protothread_serial) ;
    pt_add_thread(protothread_play) ;
    pt_schedule_start ;
}

music. h

#ifndef _MUSIC_H_
#define _MUSIC_H_

#include "pico/stdlib.h"

// Song definition macros
#define MAX_LEN 160
#define NUM_SERVOS 8
#define NUM_SONGS 23

// Song struct definition
typedef struct {
    uint beat_period;
    uint notes[NUM_SERVOS][MAX_LEN];
    uint end_beat;
} song_t;

// store song pointers in array
extern song_t* song_list[NUM_SONGS];

// songs as extern (defined by c file)
// allows inclusion into xylophone.c
extern song_t alma_mater;
extern song_t baby_shark;
extern song_t carol_of_the_bells;
extern song_t chopsticks;
extern song_t chords;
extern song_t dreidel;
extern song_t happy_birthday;
extern song_t jingle_bells;
extern song_t leave_the_door_open;
extern song_t love_story;
extern song_t mario;
extern song_t ode_to_joy;
extern song_t office;
extern song_t old_mcdonald;
extern song_t rick_roll;
extern song_t scale;
extern song_t spring;
extern song_t star_wars;
extern song_t thousand_miles;
extern song_t twinkle;
extern song_t whole_new_world;
extern song_t wii_sports;
extern song_t want_u_back;

#endif

music. c

#include "music.h"

// store song pointers in array
// indexable by program selector
song_t* song_list[NUM_SONGS] = {
    &scale,
    &happy_birthday,
    &wii_sports,
    &alma_mater,
    &chopsticks,
    &jingle_bells,
    &old_mcdonald,
    &chords,
    &baby_shark,
    &dreidel,
    &rick_roll,
    &twinkle,
    &office,
    &want_u_back,
    &thousand_miles,
    &whole_new_world,
    &carol_of_the_bells,
    &leave_the_door_open,
    &love_story,
    &mario,
    &ode_to_joy,
    &spring,
    &star_wars,
};

Appendix C

Figure 11. Electrical schematic for our final project. 

Appendix D

Karina
Music arrangement
Optoisolator circuit assembly

Zoe
Mechanical design
Servo electrical integration
Servo damping

Harris
Mechanical fabrication
Music data management
Timing management

Appendix E

References used: 
Servo motors
Raspberry Pi Pico

PWM signal code from Lab 3