Lab #5: Generating sounds using PWM (100 pts)

Controlling sounds using PWM

In the template project for Lab 5, Timer Module TIMA1 is configured to drive the buzzer on the Simon PCB. In this lab, you will integrate this buzzer into the Lab 5 concept.

When the duty cycle is set to 50%, PWM creates a square wave signal at the frequency selected by the period register. Thus, In order to change the sound that is created, you need to change the value both of the period register (to change the frequency) and the duty cycle. On the Simon PCB, the buzzer is connected to TIMA1 channel C0. In init_timer.c, the TIMA1 module is configured to count based on the main SYSOSC BUSCLK (32 MHz) with a divider of 4, so at 8 MHz.

When the Lab 5 template code is run, the buzzer beeps briefly at startup. The code which configures the frequency of this beep is at the end of the TIMA1 initialization in init_timer.c. Note that both the period and the duty cycle need to be configured!

    TIMA1->COUNTERREGS.LOAD = 3999; // Period is LOAD+1 - this is 8000000/4000 = 2kHz
    // HEADS UP: This sets the frequency of the buzzer!
    TIMA1->COUNTERREGS.CC_01[0] = (TIMA1->COUNTERREGS.LOAD  + 1) / 2; // half of load to make this a square wave

There are two other lines of code which are relevant. First, the following line in init_timer.c enables the Timer Module’s counting function (enables the counter to tick). This is the standard way of effectively enabling the Timer Module’s functioning, and in this case, it turns the PWM, and thus sound, on.

    TIMA1->COUNTERREGS.CTRCTL |= (GPTIMER_CTRCTL_EN_ENABLED);

Second, at the top of the lab5.c function, we have the following lines of code which disables the Timer Module counting, which turns off the PWM / sound.

    // let the buzzer run for 0.1 s just so we know it's there!
    delay_cycles(1600000);
    TIMA1->COUNTERREGS.CTRCTL &= ~(GPTIMER_CTRCTL_EN_ENABLED); // Disable the buzzer

Your task in Lab 5

Your task in Lab 5 is twofold. First, conceptually, you can start with your button state machine code from Lab 3. For this lab, we do not care about long-presses or short-presses. Instead, the goal is that whenever a button is pressed (not bouncing), you should play a tone that corresponds to the button being depressed.

Second, similarly to how there were two highest-level states for the clock (ticking or setting), there should be two highest-level states. In the first, which is active upon reboot, your PCB should play the song “Mary Had a Little Lamb” repeatedly. When any button is depressed, you should transition out of the the initialization state into the button-tone state described above.

Adding sound to button-presses

When a button is depressed, set the frequency and duty cycle and enable the PWM counter to play the appropriate sound. (Hint: Begin by adding your state machine code to the lab template. Remove the long-press states, and modify the short-pressed state to enable PWM.)

From this website, we have the suggestion that the notes for Simon should be:

Our buzzer has a fundamental frequency in the 4-8 kHz range. Frequencies outside this range will induce more nonlinearities in the piezo mebrane. So using notes that are two octaves higher (i.e., G6, E6, C6, and G5) will sound a bit less mechanical.

Mary Had A Little Lamb

In order to play music, you need the ability to specify both the frequency and duration of the notes that you are playing. (Music comprises both tonal sound and silence!) If we notated music as tuples of note and duration, if “q” is a quarter note, “h” is a half note, and “w” is a whole note, Mary Had a Little Lamb could be represented as:

(E,q) (D,q) (C,q) (D,q)   (E,q) (E,q) (E,h)  (D,q) (D,q) (D,h)  (E,q) (E,q) (E,h)
(E,q) (D,q) (C,q) (D,q)   (E,q) (E,q) (E,q) (C,q)  (D,q) (D,q) (E,q) (D,q)  (C,w)

Here is the suggested approach.

  1. Create a pair of arrays corresponding to the notes and durations (or define a struct and create a struct array). For notes, you can use the values you will want to put in the LOAD (and CC_01[0] registers) to make the correct tones.
  2. Create a new “playing music” state. When in this state, cycle through the song array.
  3. When a button is pressed transition to the button-tone state.

Presumably quarter notes will correspond to something like 600-800 ms, while your interrupt ticks will occur every 10 ms. Thus, the “music playing” state will need to implement a counter that resets for each note and then counts down while the note should be being played. In order to play a note, you will need to set the approriate values for the frequncy and duty cycle of the PWM. Importantly, when no tone should be being played (e.g., during the brief periods between notes or during a musical rest), you can disable the counter using the CTRCTL register’s enable/disable bit.

Bonus

  1. The buzzer is also connected to a pin that can be controlled by the DAC output of the MSMPM0+. Rather than driving the sounds with PWM square-waves, implement more complex waveforms like sinusoids.

  2. Optimize your code for lower power. Configure the SYSOSC to use a 4 MHz frequency for both your code and timer. Implement at GPIO interrupt to additionally trigger wakeups with button state changes and timer G0 wakeups. Optimize the timer interrupt for debouncing. Ensure that you go into SLEEP while waiting for input. Measure power consumption before and after these changes and demonstrate the improvement.