#include <stdint.h>
#include <stdbool.h>
#include <string>
#include "Button.h"
#include "LedMode.h"
#include "PWM.h"
// ============================================================
// STM32C031 MEMORY-MAPPED PERIPHERAL ADDRESSES
// ============================================================
// RCC peripheral base address
#define RCC_BASE 0x40021000U
// GPIOA peripheral base address
#define GPIOA_BASE 0x50000000U
// ============================================================
// RCC REGISTERS
// ============================================================
// RCC_IOPENR offset = 0x34
//
// Bit 0 = GPIOA clock enable
//
// GPIOA cannot be configured/used properly until its clock
// is enabled.
volatile uint32_t* const RCC_IOPENR =
(volatile uint32_t*)(RCC_BASE + 0x34U);
// RCC_APBENR2 offset = 0x40
//
// Bit 11 = TIM1 clock enable
//
// TIM1 is used to generate PWM.
volatile uint32_t* const RCC_APBENR2 =
(volatile uint32_t*)(RCC_BASE + 0x40U);
// ============================================================
// GPIOA REGISTERS
// ============================================================
// GPIOA_MODER
// Offset = 0x00
//
// Controls GPIO pin mode.
//
// For each pin:
// 00 = Input
// 01 = Output
// 10 = Alternate Function
// 11 = Analog
volatile uint32_t* const GPIOA_MODER =
(volatile uint32_t*)(GPIOA_BASE + 0x00U);
// GPIOA_PUPDR
// Offset = 0x0C
//
// Controls pull-up / pull-down configuration.
volatile uint32_t* const GPIOA_PUPDR =
(volatile uint32_t*)(GPIOA_BASE + 0x0CU);
// GPIOA_IDR
// Offset = 0x10
//
// Input Data Register.
//
// Used to read the actual logic level of GPIO pins.
volatile uint32_t* const GPIOA_IDR =
(volatile uint32_t*)(GPIOA_BASE + 0x10U);
// GPIOA_ODR
// Offset = 0x14
//
// Output Data Register.
//
// Used to control GPIO output levels when GPIO is in
// normal output mode.
volatile uint32_t* const GPIOA_ODR =
(volatile uint32_t*)(GPIOA_BASE + 0x14U);
// GPIOA_AFRL
// Offset = 0x20
//
// Alternate Function Low Register.
//
// Used for PA0 - PA7.
volatile uint32_t* const GPIOA_AFRL =
(volatile uint32_t*)(GPIOA_BASE + 0x20U);
// GPIOA_AFRH
// Offset = 0x24
//
// Alternate Function High Register.
//
// Used for PA8 - PA15.
volatile uint32_t* const GPIOA_AFRH =
(volatile uint32_t*)(GPIOA_BASE + 0x24U);
// ============================================================
// DELAY
// ============================================================
void delay(volatile uint32_t count)
{
// Simple software busy-wait.
//
// IMPORTANT:
// count is NOT milliseconds.
//
// Actual delay depends on CPU frequency,
// compiler and optimization level.
while (count--)
{
// Do nothing.
// CPU simply decrements count.
}
}
// ============================================================
// LED / PWM PIN CONFIGURATION
// ============================================================
void ledsSetup()
{
// --------------------------------------------------------
// PA8
// --------------------------------------------------------
//
// PA8 is connected to TIM1_CH1.
//
// According to STM32C031 datasheet:
//
// PA8 + AF2 = TIM1_CH1
//
// Therefore PA8 must be:
//
// 1. Alternate Function mode
// 2. AF2 selected
//
// Clear PA8 mode bits.
//
// PA8 uses MODER bits 17:16.
//
// 11 -> clear both bits -> 00
*GPIOA_MODER &= ~(3U << (8 * 2));
// Set PA8 mode = 10
//
// 10 = Alternate Function
*GPIOA_MODER |= (2U << (8 * 2));
// --------------------------------------------------------
// PA9
// --------------------------------------------------------
//
// PA9 is connected to TIM1_CH2.
//
// According to datasheet:
//
// PA9 + AF2 = TIM1_CH2
//
// Clear PA9 mode bits
//
// PA9 uses MODER bits 19:18.
*GPIOA_MODER &= ~(3U << (9 * 2));
// Set PA9 = Alternate Function mode
*GPIOA_MODER |= (2U << (9 * 2));
// --------------------------------------------------------
// PA8 -> AF2
// --------------------------------------------------------
//
// GPIOA_AFRH handles PA8 - PA15.
//
// PA8 uses AFRH bits 3:0.
//
// Clear existing AF selection.
*GPIOA_AFRH &= ~(15U << 0);
// Select AF2.
//
// 0010 = AF2
*GPIOA_AFRH |= (2U << 0);
// --------------------------------------------------------
// PA9 -> AF2
// --------------------------------------------------------
//
// PA9 uses AFRH bits 7:4.
//
// Clear existing AF selection.
*GPIOA_AFRH &= ~(15U << 4);
// Select AF2.
*GPIOA_AFRH |= (2U << 4);
// --------------------------------------------------------
// Enable TIM1 clock
// --------------------------------------------------------
//
// RCC_APBENR2 bit 11 = TIM1EN.
//
// Without this clock, TIM1 cannot operate.
*RCC_APBENR2 |= (1U << 11);
}
// ============================================================
// UPDATE LED DUTY
// ============================================================
void updateLed(float& duty, float& direction)
{
// Increase/decrease brightness.
duty += direction;
// If brightness reaches 100%,
// reverse direction.
if (duty >= 100.00f)
{
direction = -0.01f;
}
// If brightness reaches 0% while decreasing,
// stop the decrease.
if (direction < 0 && duty <= 0.00f)
{
direction = 0.00f;
}
}
// ============================================================
// BUTTON OBJECT
// ============================================================
//
// Button is connected to PA15.
//
// Button class receives:
// GPIOA_MODER -> configure pin
// GPIOA_IDR -> read pin
// GPIOA_PUPDR -> configure pull-up/pull-down
// 15 -> PA15
//
Button btn(
GPIOA_MODER,
GPIOA_IDR,
GPIOA_PUPDR,
15
);
// ============================================================
// MAIN
// ============================================================
int main(void)
{
// --------------------------------------------------------
// 1. ENABLE GPIOA CLOCK
// --------------------------------------------------------
//
// RCC_IOPENR bit 0 = GPIOAEN
//
// GPIOA clock must be enabled before configuring PA pins.
*RCC_IOPENR |= (1U << 0);
// --------------------------------------------------------
// 2. CONFIGURE PWM PINS
// --------------------------------------------------------
//
// PA8 -> TIM1_CH1
// PA9 -> TIM1_CH2
//
ledsSetup();
// --------------------------------------------------------
// 3. INITIALIZE BUTTON
// --------------------------------------------------------
//
// PA15 becomes button input.
//
btn.init();
// --------------------------------------------------------
// 4. INITIALIZE TIMER
// --------------------------------------------------------
//
// TIM1 is initialized once.
//
// The actual timer registers such as:
//
// PSC
// ARR
// CCMR1
// CCR1
// CCR2
// CCER
// BDTR
// CR1
//
// are presumably configured inside PWM::initTimer().
//
PWM::initTimer();
// --------------------------------------------------------
// 5. CREATE PWM CHANNEL OBJECTS
// --------------------------------------------------------
// TIM1 Channel 1 -> PA8 -> Blue LED
PWM blueLed(1);
// TIM1 Channel 2 -> PA9 -> Green LED
PWM greenLed(2);
// Configure individual PWM channels.
greenLed.initChannel();
blueLed.initChannel();
// --------------------------------------------------------
// 6. INITIAL MODE
// --------------------------------------------------------
LedMode mode = LedMode::MODE_OFF;
// --------------------------------------------------------
// 7. ANIMATION VARIABLES
// --------------------------------------------------------
float direction1 = +0.05f;
float direction2 = 0.00f;
float duty1 = 0.00f;
float duty2 = 0.00f;
// Used for flashing LED2.
bool flashState = false;
// Software counters.
uint32_t counterLed1 = 0;
uint32_t counterLed2 = 0;
// Delay thresholds.
const uint32_t led1Delay = 20;
const uint32_t led2Delay = 7000;
// Pointers to PWM objects.
PWM* led1 = &greenLed;
PWM* led2 = &blueLed;
// ========================================================
// MAIN SUPER LOOP
// ========================================================
while (1)
{
// ----------------------------------------------------
// BUTTON CLICK
// ----------------------------------------------------
if (btn.wasClicked())
{
// Move to next LED mode.
++mode;
// Reset animation state.
duty1 = 0.00f;
duty2 = 0.00f;
direction1 = +0.01f;
direction2 = 0.00f;
// Simple software debounce delay.
delay(100000);
}
// ====================================================
// SELECT CURRENT LED MODE
// ====================================================
switch (mode)
{
// ------------------------------------------------
// MODE OFF
// ------------------------------------------------
case LedMode::MODE_OFF:
{
greenLed.setDuty(0);
blueLed.setDuty(0);
break;
}
// ------------------------------------------------
// MODE SLOW GLOW
// ------------------------------------------------
case LedMode::MODE_SLO_GLO:
{
// Change green brightness.
duty1 += direction1;
greenLed.setDuty(duty1);
// Blue does the opposite.
blueLed.setDuty(100.00f - duty1);
// Slow animation.
delay(5);
// Reached maximum?
if (duty1 >= 100.00f)
{
duty1 = 100.0f;
// Start decreasing.
direction1 = -0.05f;
}
// Reached minimum?
else if (duty1 <= 0.00f)
{
duty1 = 0.0f;
// Start increasing.
direction1 = +0.05f;
}
break;
}
// ------------------------------------------------
// MODE COLOR SWAP
// ------------------------------------------------
case LedMode::MODE_COLOR_SWAP:
{
// Apply current duty cycles.
led1->setDuty(duty1);
led2->setDuty(duty2);
delay(25);
// Update brightness.
updateLed(duty1, direction1);
updateLed(duty2, direction2);
// Start LED2 increasing when LED1
// becomes sufficiently dim.
if (direction1 < 0 && duty1 <= 15.00f)
{
direction2 = +0.01f;
}
// Start LED1 increasing when LED2
// becomes sufficiently dim.
if (direction2 < 0 && duty2 <= 15.00f)
{
direction1 = +0.01f;
}
break;
}
// ------------------------------------------------
// MODE FLASH
// ------------------------------------------------
case LedMode::MODE_FLASH:
{
// Increment both counters.
counterLed1++;
counterLed2++;
// --------------------------------------------
// LED1: slow brightness change
// --------------------------------------------
if (counterLed1 >= led1Delay)
{
counterLed1 = 0;
duty1 += direction1;
// Maximum brightness
if (duty1 >= 100.0f)
{
duty1 = 100.0f;
direction1 = -0.01f;
}
// Minimum brightness
else if (duty1 <= 0.0f)
{
duty1 = 0.0f;
direction1 = +0.01f;
}
// Apply new duty.
led1->setDuty(duty1);
}
// --------------------------------------------
// LED2: flashing
// --------------------------------------------
if (counterLed2 >= led2Delay)
{
counterLed2 = 0;
// Toggle flash state.
flashState = !flashState;
if (flashState)
{
// LED ON
led2->setDuty(100.0f);
}
else
{
// LED OFF
led2->setDuty(0.0f);
}
}
break;
}
}
}
}