Skip to content

QMK extra feature to make an arbitrary PWM pin LED breathe

Notifications You must be signed in to change notification settings

BlueDrink9/qmk_breathing_led

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Breathing LED Control

Overview

This module provides control over LEDs to create a "breathing" effect, where the LED intensity cycles in a pattern that resembles a breathing rhythm. This is based off the same effect for the backlight feature, but adapted to multiple separate PWM pins (so you can have multiple LEDs breathing at different rates, for example). I use this to express a bigger range of layers with only 2 LEDs.

This is set up for STM32 PWM only.

Features

  • Control the breathing effect on multiple LED channels.
  • Configure the breathing period (how fast it breathes) for each channel independently.
  • Enable or disable the breathing effect dynamically.

This is provided as-is, you'll probably need to modify it for your specific setup. Just putting it out in case it's useful. Currently it assumes all the pins you are using are on the same timer

Usage

Installation

Clone this repo into your keymap folder, and add the following to rules.mk

SRC += breathing/breathing.c

Then add this to keymap.c

#include "breathing/breathing.h"

Initialization

Before using the breathing control functions, initialize the system with:

void keyboard_pre_init_user(void) {
    breathing_init();
}

Starting the Breathing Effect

You need to know the channels for the pins you use. These will noted in the datasheet for your MCU; for example for the bluepill, A2 and A3 are timer 2 channels 2 and 3.

To start the breathing effect on a specific channel with a specified period, use:

void start_breathing(uint8_t channel, int8_t period);
  • channel: The LED channel to start the breathing effect on. Specific to each pin (assuming they use the same timer).
  • period: The period of the breathing cycle in seconds, i.e., how long it takes to breathe a full cycle.

Stopping the Breathing Effect

To stop the breathing effect on a specific channel, use:

void stop_breathing(uint8_t channel);
  • channel: The LED channel to stop the breathing effect on.

Checking Breathing Status

To check if any channel is currently running a breathing effect, use:

bool is_breathing(void);

Other convenience functions

// Turn on 100%
void pwm_on(uint8_t channel);
// Turn off
void pwm_off(uint8_t channel);
// Turn to specified percentage (out of 10000)
void pwm_set(uint8_t channel, uint8_t percent);

Custom breathing pattern

By default, this uses a sin pattern. To generate basic sin % breathing curve in python:

from math import sin, pi
breathing_steps = 255
print([int(sin(x/breathing_steps*pi)**4*100) for x in range(breathing_steps)])

For a custom gaussian pattern:

from math import exp
breathing_steps = 255
# Mean (center) of the Gaussian curve
mu = breathing_steps / 2
# Standard deviation (width) of the Gaussian curve. REduce to narrow (more off)
sigma = breathing_steps / 12
gaussian_values = [int(exp(-(x - mu)**2 / (2 * sigma**2)) * 1000) for x in range(breathing_steps)]
vals = ','.join([str(v) for v in gaussian_values])
print(f"static const uint16_t breathing_table_narrow_gaussian[BREATHING_STEPS] = {{{vals}}};")

To pass custom patterns:

static const uint16_t step_table_sin[BREATHING_STEPS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 17, 19, 21, 24, 27, 30, 33, 37, 41, 45, 50, 54, 59, 65, 70, 76, 83, 89, 96, 103, 111, 119, 127, 136, 145, 154, 164, 174, 184, 195, 205, 217, 228, 240, 253, 265, 278, 291, 304, 318, 332, 346, 361, 375, 390, 405, 420, 436, 451, 467, 482, 498, 514, 530, 546, 562, 578, 594, 610, 626, 642, 657, 673, 688, 704, 719, 734, 748, 763, 777, 791, 804, 818, 831, 843, 855, 867, 879, 889, 900, 910, 920, 929, 937, 945, 953, 960, 966, 972, 978, 983, 987, 990, 993, 996, 998, 999, 999, 999, 999, 998, 996, 993, 990, 987, 983, 978, 972, 966, 960, 953, 945, 937, 929, 920, 910, 900, 889, 879, 867, 855, 843, 831, 818, 804, 791, 777, 763, 748, 734, 719, 704, 688, 673, 657, 642, 626, 610, 594, 578, 562, 546, 530, 514, 498, 482, 467, 451, 436, 420, 405, 390, 375, 361, 346, 332, 318, 304, 291, 278, 265, 253, 240, 228, 217, 205, 195, 184, 174, 164, 154, 145, 136, 127, 119, 111, 103, 96, 89, 83, 76, 70, 65, 59, 54, 50, 45, 41, 37, 33, 30, 27, 24, 21, 19, 17, 14, 13, 11, 9, 8, 7, 6, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
void start_breathing_with_pattern(uint8_t channel, int8_t period, const uint16_t *step_table);

Where step_table is a pointer to a declared array of BREATHING_STEPS percentage values (out of 1000, where 100% = 1000 and 0.5% = 5).

Configuration

Define these in config.h

  • BREATHING_PINS: Define this macro with the array of pins used for breathing LEDs, e.g. `#define BREATHING_PINS { A2, A3 };
  • N_BREATHING_PINS: Size of array above.
  • BREATHING_LED_PWM: Name of the timer you are using. E.g. #define BREATHING_LED_PWM &PWMD2
  • PWM_OUTPUT_ACTIVE_HL: 1 if your LED is on when voltage is applied, 0 if it is on when driven low.

Define these in halconf.h

#define HAL_USE_PWM TRUE

#include_next <halconf.h>

You probably also need to ensure the timer for your pins is active. For bluepill, A3 is timer 2 so I need to enable that. However, timer 2 is the default for other functions on the board, so I need to first change the main timer to 3, to free up timer 2 (and pin A3) for the LED PWM.

#undef STM32_ST_USE_TIMER
#define STM32_ST_USE_TIMER 3
#undef STM32_PWM_USE_TIM2
#define STM32_PWM_USE_TIM2 TRUE

About

QMK extra feature to make an arbitrary PWM pin LED breathe

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages