Lab 4

reflection
labreport
Digital Audio
Author

Madeleine Kan

Published

October 6, 2025

Introduction

The purpose of Lab 4 was to program an STM32 MCU to play a sequence of specified frequencies and durations. Currently, it is capable of playing excerpts from Fur Elise by Beethoven and Hopes and Dreams by Toby Fox.

Schematic

The circuit diagram is as follows:

Calculations

Minimum and maximum duration and frequencies:

Frequency calculation for 220, 660, and 100 Hz (within 1% error):

Code:

Updated GPIO.h library header file:

// STM32L432KC_GPIO.h
// Header for GPIO functions
// Madeleine Kan
// mkan@hmc.edu
// 10/6/25
#ifndef STM32L4_GPIO_H
#define STM32L4_GPIO_H

#include <stdint.h> // Include stdint header

///////////////////////////////////////////////////////////////////////////////
// Definitions
///////////////////////////////////////////////////////////////////////////////

// Values for GPIO pins ("val" arguments)
#define GPIO_LOW    0
#define GPIO_HIGH   1

// Base addresses for GPIO ports
#define GPIOB_BASE  (0x48000400UL)
#define GPIOA_BASE  (0x48000000UL)
// see button interrupt tutorial, pin A + more generality

// Arbitrary GPIO functions for pinMode()
#define GPIO_INPUT  0
#define GPIO_OUTPUT 1
#define GPIO_ALT    2
#define GPIO_ANALOG 3

///////////////////////////////////////////////////////////////////////////////
// Bitfield struct for GPIO
///////////////////////////////////////////////////////////////////////////////

// GPIO register structs here
typedef struct {
    volatile uint32_t MODER;   // GPIO Offset 0x00 GPIO port mode register
    volatile uint32_t OTYPER;  // GPIO Offset 0x04
    volatile uint32_t OSPEEDR; // GPIO Offset 0x08
    volatile uint32_t PURPDR;  // GPIO Offset 0x0C
    volatile uint32_t IDR;     // GPIO Offset 0x10
    volatile uint32_t ODR;     // GPIO Offset 0x14
    volatile uint32_t BSRR;    // GPIO Offset 0x18
    volatile uint32_t LCKR;    // GPIO Offset 0x1C
    volatile uint32_t AFRL;    // GPIO Offset 0x20
    volatile uint32_t AFRH;    // GPIO Offset 0x24
} GPIO;

// Pointers to GPIO-sized chunks of memory for each peripheral
#define GPIOB ((GPIO *) GPIOB_BASE)

#define GPIOA ((GPIO *) GPIOA_BASE)

#define GPIO GPIOA

///////////////////////////////////////////////////////////////////////////////
// Function prototypes
///////////////////////////////////////////////////////////////////////////////

void pinMode(int pin, int function);

int digitalRead(int pin);

void digitalWrite(int pin, int val);

void togglePin(int pin);


#endif

Updated RCC.c library file:

// STM32L432KC_RCC.c
// Source code for RCC functions
// Madeleine Kan
// mkan@hmc.edu
// 10/6/25

#include "STM32L432KC_RCC.h"

void configureClock(){
    // MSI is automatically set as systemclock
    RCC->APB2ENR |= (1 << 17); // tim16 timer clk enable
    RCC->APB2ENR |= (1 << 16); // tim15 timer clk enable

}

New TIMER.c library file:

// TIMER.c
// Source code for Timer functions (delay and output 50% PWM at desired frequency)
// Madeleine Kan
// mkan@hmc.edu
// 10/6/25

#include "STM32L432KC_TIMER.h"

void configureTimer(TIM_TypeDef* TIMx, int PWM) {
  if (PWM == 1) {
    TIMx->CCMR1 |= (0b110 << 4);    // set PWM mode 1 (active high)
    TIMx->CCMR1 |= (1 << 3);    // output compare 1 preload enable
    TIMx->BDTR |= (1 << 15);    // main output enable
    // TIMx->CCER |= (1 << 1); // CC1E, enable OC1 signal 
    TIMx->CCER |= (1 << 0); // CC1E, enable OC1 signal 
  }
  // preload value copied into active capture/compare register when update event occurs
  // update generation... cleared by hardware, care of enable and disabling counter
  TIMx->EGR |= (1 << 0);    // reinitialize counter, generate update of registers
  // enable counter
  TIMx->CR1 |= (1 << 0);    // write 1 to CEN
  // set URS to overflow/underflow only ... maybe relevant for upcout nmode?
  // TIMx->CR1 |= (1 << 2);
}

// output PWM signal at desired frequency
void setTimerFreq(TIM_TypeDef* TIMx, uint16_t freq, uint16_t prescale_val) {
  // (4 MHz) / (prescaler_val * auto_reload_val)
  // must span 220-1000 Hz with 1% accuracy
  //uint16_t prescale_val;
  
  uint16_t auto_reload_val;
  if (freq > 0) {
    auto_reload_val = 4000000 / freq;
  } else {
    auto_reload_val = 0;
  }

  uint16_t duty_cycle = auto_reload_val / 2;
  //// clear counter
  //TIMx->CNT &= ~(0b1111111111111111 << 0);

  // wite ARR
  TIMx->ARR &= ~(0b1111111111111111 << 0);    // clear ARR
  TIMx->ARR |= (auto_reload_val << 0);        // set ARR
  // counter clock frequency (CK_CNT) = f_CKPSC / (PSC[15:0] + 1)
  TIMx->PSC |= (prescale_val << 0);    // set psc3q

  // value is compared to timx_cnt, signaled on oc1 output
  TIMx->CCR1 &= ~(0b1111111111111111 << 0);    // clear CCR1
  TIMx->CCR1 |= (duty_cycle << 0);             // set PWM count

  // update generation... cleared by hardware, care of enable and disabling counter
  TIMx->EGR |= (1 << 0);    // reinitialize counter, generate update of registers
  // clear counter
  TIMx->CNT &= ~(0b1111111111111111 << 0);
}

// delay for speceified number of milliseconds
void delay_millis(TIM_TypeDef* TIMx, uint16_t ms, uint16_t prescale_val) {
  // (4 MHz) / (prescaler_val * auto_reload_val)
  // must span 220-1000 Hz with 1% accuracy

  uint16_t auto_reload_val;
  auto_reload_val = ms;


  // wite ARR
  TIMx->ARR &= ~(0b1111111111111111 << 0);    // clear ARR
  TIMx->ARR |= (auto_reload_val << 0);        // set ARR
  // counter clock frequency (CK_CNT) = f_CKPSC / (PSC[15:0] + 1)
  TIMx->PSC |= (prescale_val << 0);    // set psc3q
  // update generation... cleared by hardware, care of enable and disabling counter
  TIMx->EGR |= (1 << 0);    // reinitialize counter, generate update of registers
  TIMx->SR &= ~(1 >> 0); // reset duration UIF
  // clear counter
  TIMx->CNT &= ~(0b1111111111111111 << 0);

  while ((TIMx->SR >> 0 & 1) != 1); // wait for UIF to be 1
  TIMx->SR &= ~(1 >> 0); // reset duration UIF
}

New TIMER.h library header file

// TIMER.h
// Header for TIMER functions

#ifndef STM32L4_TIMER_H
#define STM32L4_TIMER_H

#include <stdint.h>

///////////////////////////////////////////////////////////////////////////////
// Definitions
///////////////////////////////////////////////////////////////////////////////

#define __IO volatile

// Base addresses... programmers manual p60
#define TIM15_BASE (0x40014000UL) // base address of TIM15 
#define TIM16_BASE (0x40014400UL) // base address of TIM16 

/**
  * @brief Reset and Clock Control
  */

typedef struct
{
  __IO uint32_t CR1;         /*!< TIM control register 1,                   Address offset: 0x00 */
  __IO uint32_t CR2;         /*!< TIM control register 2,                   Address offset: 0x04 */
  __IO uint32_t SMCR;        /*!< TIM slave mode control register,          Address offset: 0x08 */
  __IO uint32_t DIER;        /*!< TIM DMA/interrupt enable register,        Address offset: 0x0C */
  __IO uint32_t SR;          /*!< TIM status register,                      Address offset: 0x10 */
  __IO uint32_t EGR;         /*!< TIM event generation register,            Address offset: 0x14 */
  __IO uint32_t CCMR1;       /*!< TIM capture/compare mode register 1,      Address offset: 0x18 */
  __IO uint32_t CCMR2;       /*!< TIM capture/compare mode register 2,      Address offset: 0x1C */
  __IO uint32_t CCER;        /*!< TIM capture/compare enable register,      Address offset: 0x20 */
  __IO uint32_t CNT;         /*!< TIM counter register,                     Address offset: 0x24 */
  __IO uint32_t PSC;         /*!< TIM prescaler,                            Address offset: 0x28 */
  __IO uint32_t ARR;         /*!< TIM auto-reload register,                 Address offset: 0x2C */
  __IO uint32_t RCR;         /*!< TIM repetition counter register,          Address offset: 0x30 */
  __IO uint32_t CCR1;        /*!< TIM capture/compare register 1,           Address offset: 0x34 */
  __IO uint32_t CCR2;        /*!< TIM capture/compare register 2,           Address offset: 0x38 */
  __IO uint32_t CCR3;        /*!< TIM capture/compare register 3,           Address offset: 0x3C */
  __IO uint32_t CCR4;        /*!< TIM capture/compare register 4,           Address offset: 0x40 */
  __IO uint32_t BDTR;        /*!< TIM break and dead-time register,         Address offset: 0x44 */
  __IO uint32_t DCR;         /*!< TIM DMA control register,                 Address offset: 0x48 */
  __IO uint32_t DMAR;        /*!< TIM DMA address for full transfer,        Address offset: 0x4C */
  __IO uint32_t OR1;         /*!< TIM option register 1,                    Address offset: 0x50 */
  __IO uint32_t CCMR3;       /*!< TIM capture/compare mode register 3,      Address offset: 0x54 */
  __IO uint32_t CCR5;        /*!< TIM capture/compare register5,            Address offset: 0x58 */
  __IO uint32_t CCR6;        /*!< TIM capture/compare register6,            Address offset: 0x5C */
  __IO uint32_t OR2;         /*!< TIM option register 2,                    Address offset: 0x60 */
  __IO uint32_t OR3;         /*!< TIM option register 3,                    Address offset: 0x64 */
} TIM_TypeDef;

#define TIM15 ((TIM_TypeDef *)TIM15_BASE)
#define TIM16 ((TIM_TypeDef *)TIM16_BASE)
///////////////////////////////////////////////////////////////////////////////
// Function prototypes
///////////////////////////////////////////////////////////////////////////////

void configureTimer(TIM_TypeDef * TIMx, int PWM);
void setTimerFreq(TIM_TypeDef * TIMx, uint16_t freq, uint16_t prescale_val);
void delay_millis(TIM_TypeDef * TIMx, uint16_t ms, uint16_t prescale_val);

#endif

main.c Source code:

/*********************************************************************
*                    SEGGER Microcontroller GmbH                     *
*                        The Embedded Experts                        *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

File    : main.c
Purpose : Play specified song
Madeleiene Kan
mkan@hmc.edu
10/6/25

*/

// Includes for libraries
#include "C:\Users\mkan\Documents\GitHub\e155-lab4\lib\STM32L432KC_FLASH.h"
#include "C:\Users\mkan\Documents\GitHub\e155-lab4\lib\STM32L432KC_RCC.h"
#include "C:\Users\mkan\Documents\GitHub\e155-lab4\lib\STM32L432KC_TIMER.h"
#include "C:\Users\mkan\Documents\GitHub\e155-lab4\lib\STM32L432KC_GPIO.h"
#include <stdio.h>
#include <stdint.h>

// Define macros for constants
#define LED_PIN           6
#define DELAY_DURATION_MS    500

const int notes[][2] = {
{659,   125},
{623,   125},
{659,   125},
{623,   125},
{659,   125},
{494,   125},
{587,   125},
{523,   125},
{440,   250},
{  0,   125},
{262,   125},
{330,   125},
{440,   125},
{494,   250},
{  0,   125},
{330,   125},
{416,   125},
{494,   125},
{523,   250},
{  0,   125},
{330,   125},
{659,   125},
{623,   125},
{659,   125},
{623,   125},
{659,   125},
{494,   125},
{587,   125},
{523,   125},
{440,   250},
{  0,   125},
{262,   125},
{330,   125},
{440,   125},
{494,   250},
{  0,   125},
{330,   125},
{523,   125},
{494,   125},
{440,   250},
{  0,   125},
{494,   125},
{523,   125},
{587,   125},
{659,   375},
{392,   125},
{699,   125},
{659,   125},
{587,   375},
{349,   125},
{659,   125},
{587,   125},
{523,   375},
{330,   125},
{587,   125},
{523,   125},
{494,   250},
{  0,   125},
{330,   125},
{659,   125},
{  0,   250},
{659,   125},
{1319,  125},
{  0,   250},
{623,   125},
{659,   125},
{  0,   250},
{623,   125},
{659,   125},
{623,   125},
{659,   125},
{623,   125},
{659,   125},
{494,   125},
{587,   125},
{523,   125},
{440,   250},
{  0,   125},
{262,   125},
{330,   125},
{440,   125},
{494,   250},
{  0,   125},
{330,   125},
{416,   125},
{494,   125},
{523,   250},
{  0,   125},
{330,   125},
{659,   125},
{623,   125},
{659,   125},
{623,   125},
{659,   125},
{494,   125},
{587,   125},
{523,   125},
{440,   250},
{  0,   125},
{262,   125},
{330,   125},
{440,   125},
{494,   250},
{  0,   125},
{330,   125},
{523,   125},
{494,   125},
{440,   500},
{  0,   0}};

const int hopes[][2] = {
  {349, 250}, // F4
  {  0, 125},
  {698, 250}, // F5
  {  0, 125},
  {523, 500}, // C4
  {  0, 125},
  {466, 250}, // B4 flat
  {  0, 125},
  {698, 250},
  {  0, 125},
  {349, 500},
  {  0, 125},
  {349, 250},
  {  0, 125},
  {440, 250}, // A4
  {  0, 125},
  {880, 375}, // A5
  {  0, 125},
  {932, 125}, // B6 flat
  {  0, 125},
  {880, 250}, // A5
  {  0, 125},
  {698, 250}, // F5
  {  0, 125},
  {1174, 63}, // D6
  {  0, 30},
  {932, 63}, // B6 flat
  {  0, 30},
  {880, 63},
  {  0, 30},
  {698, 63}, //F5
  {  0, 30},
  {587, 63}, //D5
  {  0, 30},
  {523, 63}, //C5

  {0, 0}
};

    int main(void) {
    configureFlash();
    configureClock();
    // Turn on clock to GPIOA
    RCC->AHB2ENR |= (1 << 0);
    pinMode(LED_PIN, GPIO_ALT);
    GPIO->AFRL |= (0b1110 << 4*LED_PIN); // set alternate function for  PA6 to AF14

    // configure timers
    uint16_t presc_freq = 0;
    uint16_t presc_dur = 4000;

    configureTimer(TIM15, 0); // upcount mode
    configureTimer(TIM16, 1); // PWM mode
  
    // Play Fur Elise
    for (int i = 0; i<(sizeof(notes)/sizeof(notes[0])); i++) {
       uint16_t freq = (uint16_t) notes[i][0];
       uint16_t ms = (uint16_t) notes[i][1];
       setTimerFreq(TIM16, freq, presc_freq);
       delay_millis(TIM15, ms, presc_dur);  
    }

    // Play Hopes and Dreams
      for (int i = 0; i<(sizeof(hopes)/sizeof(hopes[0])); i++) {
        uint16_t freq = (uint16_t) hopes[i][0];
        uint16_t ms = (uint16_t) hopes[i][1];
        setTimerFreq(TIM16, freq, presc_freq);
        delay_millis(TIM15, ms, presc_dur);  
    }
    
    
    return 0;
 
}
  1. I asked copilot. The sample code it generated was helpful, but it didn’t cover the RCC config necessary to enable the timers and the GPIO pins. Also, I ended up ising Timer 16 and 17, which it did not cover. It seems clear that the AI can be informative, but it cannot read the datasheet. Here are the results:
To generate frequencies between 220 Hz and 1 kHz on the STM32L432KC and output them to a GPIO pin, here’s a complete breakdown of your best options and how to configure them:

---

### 🧠 Recommended Timers for Frequency Generation

The STM32L432KC features several timers, but for outputting waveforms to GPIO pins, **general-purpose timers** are ideal:

| Timer | Type              | Output to GPIO | Channels | Notes |
|-------|-------------------|----------------|----------|-------|
| TIM2  | General-purpose   | ✅ Yes          | 4        | 32-bit counter, flexible |
| TIM5  | General-purpose   | ✅ Yes          | 4        | 32-bit counter, ideal for low frequencies |
| TIM1  | Advanced-control  | ✅ Yes          | 4        | More complex, used for PWM |
| TIM6/7| Basic timers      | ❌ No           | 0        | No direct GPIO output |
| LPTIM1| Low-power timer   | ✅ Yes          | 1        | Good for ultra-low-power apps |

🔧 **Best choice for GPIO output**: **TIM2 or TIM5**  
These are general-purpose timers with multiple output channels and direct GPIO mapping via alternate function (AF) settings.

---

### 📐 Frequency Calculation Formula

To generate a specific frequency, use this formula:

f_timer = SystemCoreClock / ((PSC + 1) × (ARR + 1))


Where:
- `SystemCoreClock` is typically 80 MHz on STM32L432KC
- `PSC` = Prescaler value
- `ARR` = Auto-reload register value

You can fix one value (usually keep `PSC` low for better resolution) and solve for the other.

---

### 🛠️ Key Registers to Configure

To set up a timer for frequency output:

1. **Enable Timer Clock**
   - RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

2. **Configure GPIO Pin for Alternate Function**
   - Set GPIO mode to AF
   - Choose correct AF (e.g., AF1 for TIM2_CH1 on PA0)

3. **Set Prescaler and ARR**
   ```c
   TIM2->PSC = prescaler_value;
   TIM2->ARR = auto_reload_value;
  1. Configure Output Compare Mode

    TIM2->CCMR1 |= (6 << TIM_CCMR1_OC1M_Pos); // PWM mode 1
    TIM2->CCER |= TIM_CCER_CC1E;              // Enable output on CH1
    TIM2->CCR1 = ARR / 2;                     // 50% duty cycle
  2. Enable Timer

    TIM2->CR1 |= TIM_CR1_CEN;

🔌 GPIO Mapping Tips

To connect timer output to a GPIO pin: - Use the Alternate Function (AF) mapping in the datasheet. - Example: TIM2_CH1 is mapped to PA0 with AF1. - Configure the pin: c GPIOA->MODER &= ~(3 << (0 * 2)); GPIOA->MODER |= (2 << (0 * 2)); // Alternate function GPIOA->AFR[0] |= (1 << (0 * 4)); // AF1 for TIM2_CH1


🧮 Example: Generating 440 Hz with TIM2

Assuming SystemCoreClock = 80 MHz:

PSC = 181;        // Prescaler
ARR = 999;        // Auto-reload
f = 80e6 / ((181+1)*(999+1)) = ~440 Hz

Would you like help writing a full initialization function or configuring multiple frequencies dynamically?

```