dash-examples/examples/pwm/pwm.c

227 lines
7.9 KiB
C

// Dash LED PWM Example
//
// Use timers to control the LEDs with PWM and light them to any desired color.
// Will loop through all possible hues of color on the LEDs.
//
// Copyright (c) 2015 Tony DiCola
// Released under a MIT license: http://opensource.org/licenses/MIT
#include <math.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/systick.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/timer.h>
// Configuration:
#define PWM_FREQ 500 // Desired update rate of the LED PWM signal.
// Global state:
volatile uint32_t systick_millis = 0; // Millisecond counter.
// Delay for the specified number of milliseconds.
// This is implemented by configuring the systick timer to increment a count
// every millisecond and then busy waiting in a loop.
static void delay(uint32_t milliseconds) {
uint32_t target = systick_millis + milliseconds;
while (target > systick_millis);
}
// Setup the systick timer to increment a count every millisecond. This is
// useful for implementing a delay function based on wall clock time.
static void systick_setup(void) {
// By default the Dash CPU will use an internal 16mhz oscillator for the CPU
// clock speed. To make the systick timer reset every millisecond (or 1000
// times a second) set its reload value to:
// CPU_CLOCK_HZ / 1000
systick_set_reload(16000);
// Set the systick clock source to the main CPU clock and enable it and its
// reload interrupt.
systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
systick_counter_enable();
systick_interrupt_enable();
}
// Systick timer reload interrupt handler. Called every time the systick timer
// reaches its reload value.
void sys_tick_handler(void) {
// Increment the global millisecond count.
systick_millis++;
}
// Setup and configure the timers that will generate a PWM signal to dim the LEDs.
static void pwm_setup(void) {
// Enable the GPIO and timer clocks that will be used.
rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_GPIOB);
rcc_periph_clock_enable(RCC_TIM1);
rcc_periph_clock_enable(RCC_TIM4);
// Now setup the LED GPIOs to show their associated timer PWM channel output.
// The mapping of LED GPIOs to timer channels is (from Table 10 of the datasheet):
// - PA8 (blue LED) = Timer 1, channel 1
// - PB6 (red LED) = Timer 4, channel 1
// - PB7 (green LED) = Timer 4, channel 2
// Setup each LED to use its alternative function (GPIO_MODE_AF).
gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO8); // PA8, blue LED
gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6); // PB6, red LED
gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO7); // PB7, green LED
// Configure the alternate function for each LED to use the timer PWM output.
// See Table 10 in the datasheet for full matrix of pins and their alternate
// function values.
gpio_set_af(GPIOA, GPIO_AF1, GPIO8);
gpio_set_af(GPIOB, GPIO_AF2, GPIO6);
gpio_set_af(GPIOB, GPIO_AF2, GPIO7);
// Configure both timers for PWM mode. In PWM mode the timers will count up
// for a specified period and the output of each timer channel will be set
// high or low depending on if the timer value is above or below a threshold.
// First configure timer 1.
// Reset the timer configuration and then set it up to use the CPU clock,
// center-aligned PWM, and an increasing rate.
timer_reset(TIM1);
timer_set_mode(TIM1, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_CENTER_1, TIM_CR1_DIR_UP);
// Divide counter by 16 to scale it down from 16mhz clock speed to a 1mhz rate.
timer_set_prescaler(TIM1, 16);
// Set timer period by solving:
// PWM frequency = timer clock speed / timer period
timer_set_period(TIM1, 1000000/PWM_FREQ);
timer_enable_break_main_output(TIM1); // Must be called for advanced timers
// like this one. Unclear what this
// does or why it's necessary but the
// libopencm3 timer and STM32 docs
// mention it.
// Apply the exact same configuration to timer 4.
timer_reset(TIM4);
timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_CENTER_1, TIM_CR1_DIR_UP);
timer_set_prescaler(TIM4, 16);
timer_set_period(TIM4, 1000000/PWM_FREQ);
timer_enable_break_main_output(TIM4);
// Now setup each timer channel that is connected to a LED. Each channel can
// have a different PWM threshold set so that it can be uniquely controlled
// (the rest of the timer configuration like period, etc. is shared by all
// channels on a timer).
// PA8 Blue LED is timer 1, channel 1.
// Enable the channel PWM output.
timer_enable_oc_output(TIM1, TIM_OC1);
// Set the PWM mode to 2 which means the PWM signal is low (LED turns on) when
// the timer value is below the threshold and high (LED off) above it.
timer_set_oc_mode(TIM1, TIM_OC1, TIM_OCM_PWM2);
// PB6 Red LED is timer 4, channel 1.
timer_enable_oc_output(TIM4, TIM_OC1);
timer_set_oc_mode(TIM4, TIM_OC1, TIM_OCM_PWM2);
// PB7 Green LED is timer 4, channel 2.
timer_enable_oc_output(TIM4, TIM_OC2);
timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM2);
// Turn on both timers.
timer_enable_counter(TIM1);
timer_enable_counter(TIM4);
// Set the threshold for each channel to zero so they are off. Setting the
// threshold to any value between 0 and 2000 will set the LED intensity (with
// 0 = off and 2000 = fully lit).
timer_set_oc_value(TIM1, TIM_OC1, 0); // Blue LED
timer_set_oc_value(TIM4, TIM_OC1, 0); // Red LED
timer_set_oc_value(TIM4, TIM_OC2, 0); // Green LED
}
// Set the color of the LED to the provided red, green, and blue intensity
// value. Each component should be a value from 0 to 1.0 where 0 is off and
// 1.0 is full intensity. For example a cyan (full green and blue) color would
// be led_color(0, 1, 1).
static void led_color(float red, float green, float blue) {
// Scale each floating point value to be within the range of possible timer
// periods (0 to 2000 if running at 500hz), then set the timer channel
// threshold to that value.
timer_set_oc_value(TIM1, TIM_OC1, blue*(1000000/PWM_FREQ));
timer_set_oc_value(TIM4, TIM_OC1, red*(1000000/PWM_FREQ));
timer_set_oc_value(TIM4, TIM_OC2, green*(1000000/PWM_FREQ));
}
// Convert from HSV to RGB color formats. HSV is nice for pretty hue animations.
// Hue (h) is a float from 0 to 360 degrees, saturation (s) is a float from 0 to
// 1 (full saturation), and value (v) is a float from 0 to 1 (full value).
// Based on code from:
// http://www.cs.rit.edu/~ncs/color/t_convert.html
static void hsv_to_rgb(float h, float s, float v, float* r, float* g, float* b) {
if (s == 0) {
// achromatic (grey)
*r = *g = *b = v;
return;
}
h /= 60; // sector 0 to 5
int i = floor(h);
float f = h - i; // factorial part of h
float p = v * (1 - s);
float q = v * (1 - s * f);
float t = v * (1 - s * (1 - f));
switch (i) {
case 0:
*r = v;
*g = t;
*b = p;
break;
case 1:
*r = q;
*g = v;
*b = p;
break;
case 2:
*r = p;
*g = v;
*b = t;
break;
case 3:
*r = p;
*g = q;
*b = v;
break;
case 4:
*r = t;
*g = p;
*b = v;
break;
default: // case 5:
*r = v;
*g = p;
*b = q;
break;
}
}
int main(void) {
// Setup systick timer and PWM channels.
systick_setup();
pwm_setup();
float hue = 0.0;
// Main loop.
while (true) {
// Convert the current hue to a RGB color.
float r, g, b;
hsv_to_rgb(hue, 1.0, 1.0, &r, &g, &b);
// Set the LED to the specified color.
led_color(r, g, b);
// Now increment the hue, wrapping back to zero when necessary, and delay
// for a bit until the next loop.
hue += 1.0;
if (hue >= 360.0) {
hue = 0.0;
}
delay(10);
}
return 0;
}