mirror of
https://github.com/google/pebble.git
synced 2025-05-05 01:11:40 -04:00
212 lines
6.4 KiB
C
212 lines
6.4 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "drivers/vibe.h"
|
|
|
|
#include "board/board.h"
|
|
#include "console/prompt.h"
|
|
#include "drivers/gpio.h"
|
|
#include "drivers/periph_config.h"
|
|
#include "drivers/pmic.h"
|
|
#include "drivers/pwm.h"
|
|
#include "drivers/timer.h"
|
|
#include "kernel/util/stop.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "util/math.h"
|
|
|
|
#include "services/common/analytics/analytics.h"
|
|
#include "services/common/battery/battery_monitor.h"
|
|
#include "services/common/battery/battery_state.h"
|
|
#include "services/common/analytics/analytics.h"
|
|
|
|
#include <string.h>
|
|
|
|
|
|
//! Make a resolution of 100. Working in integer duty cycles on the following ranges:
|
|
//!
|
|
//! For a 2-direction, rotating vibe (PWM actuates an H-Bridge):
|
|
//! [0, 49] : Full-strength reverse rotation to zero-strength reverse rotation.
|
|
//! 50 : No rotation strength
|
|
//! [51, 100] : Zero-strength forward rotation to full-strength forward rotation.
|
|
//!
|
|
//! For a 1-direction vibe:
|
|
//! 0 : No vibration strength.
|
|
//! [1, 100] : Zero strength vibration to full-strength vibration.
|
|
//!
|
|
//! This must be an even value so that a half-way point exists as an edge between an equal number of
|
|
//! clock cycles on either side.
|
|
#define PWM_TIMER_UPDATE_PERIOD (100)
|
|
|
|
// Operating frequency of DRV2603 is in the [10, 250] kHz range.
|
|
#define PWM_OUTPUT_FREQUENCY_HZ (22 * 1000)
|
|
|
|
// Count clock needs to run at least as fast as the (update period * output frequency)
|
|
#define PWM_TIMER_FREQUENCY_HZ (PWM_TIMER_UPDATE_PERIOD * PWM_OUTPUT_FREQUENCY_HZ)
|
|
|
|
// 50% duty cycle means not vibrating.
|
|
#define PWM_DUTY_CYCLE_OFF (PWM_TIMER_UPDATE_PERIOD / 2)
|
|
#define PWM_DUTY_CYCLE_FULL (PWM_TIMER_UPDATE_PERIOD)
|
|
|
|
static uint8_t s_vibe_duty_cycle = PWM_DUTY_CYCLE_FULL;
|
|
static bool s_initialized = false;
|
|
|
|
void vibe_init(void) {
|
|
if (s_initialized) {
|
|
return;
|
|
}
|
|
|
|
periph_config_acquire_lock();
|
|
|
|
if (BOARD_CONFIG_VIBE.options & ActuatorOptions_Ctl) {
|
|
gpio_output_init(&BOARD_CONFIG_VIBE.ctl, GPIO_OType_PP, GPIO_Speed_2MHz);
|
|
gpio_output_set(&BOARD_CONFIG_VIBE.ctl, false);
|
|
s_initialized = true;
|
|
}
|
|
|
|
if (BOARD_CONFIG_VIBE.options & ActuatorOptions_Pwm) {
|
|
pwm_init(&BOARD_CONFIG_VIBE.pwm, PWM_TIMER_UPDATE_PERIOD, PWM_TIMER_FREQUENCY_HZ);
|
|
s_initialized = true;
|
|
}
|
|
|
|
if (BOARD_CONFIG_VIBE.options & ActuatorOptions_HBridge) {
|
|
PBL_ASSERTN(BOARD_CONFIG_VIBE.options & ActuatorOptions_Pwm);
|
|
}
|
|
|
|
periph_config_release_lock();
|
|
}
|
|
|
|
//! Enables / disables the PWM timer used for vibe control.
|
|
//! Note: assumes the timer peripheral is enabled
|
|
static void prv_vibe_pwm_enable(bool on) {
|
|
pwm_enable(&BOARD_CONFIG_VIBE.pwm, on);
|
|
|
|
static bool stop_mode_disabled = false;
|
|
if (stop_mode_disabled != on) {
|
|
if (on) {
|
|
stop_mode_disable(InhibitorVibes);
|
|
} else {
|
|
stop_mode_enable(InhibitorVibes);
|
|
}
|
|
stop_mode_disabled = on;
|
|
}
|
|
}
|
|
|
|
static uint16_t prv_get_vsys_mv(void) {
|
|
if (battery_get_charge_state().is_plugged) {
|
|
// Plugged in, use Vsys rather than Vbat
|
|
return pmic_get_vsys();
|
|
}
|
|
// Not plugged in, use latest battery reading
|
|
return battery_state_get_voltage();
|
|
}
|
|
|
|
static uint32_t prv_vibe_get_pwm_duty_cycle(int8_t strength) {
|
|
if (BOARD_CONFIG_VIBE.options & ActuatorOptions_HBridge) {
|
|
// Scale from -100..100 (strength) to 0..100 (duty cycle)
|
|
return ((PWM_DUTY_CYCLE_FULL - PWM_DUTY_CYCLE_OFF) * strength / VIBE_STRENGTH_MAX)
|
|
+ PWM_DUTY_CYCLE_OFF;
|
|
} else {
|
|
// Treat "reverse" rotation strength as if it were "forward" strength.
|
|
uint32_t duty_cycle = ABS(strength);
|
|
|
|
// Scale the duty cycle given the current battery voltage
|
|
if (BOARD_CONFIG_VIBE.vsys_scale > 0) {
|
|
const uint16_t vsys_mv = prv_get_vsys_mv();
|
|
PBL_ASSERTN(vsys_mv > 0);
|
|
duty_cycle = (BOARD_CONFIG_VIBE.vsys_scale * duty_cycle) / vsys_mv;
|
|
}
|
|
return duty_cycle;
|
|
}
|
|
}
|
|
|
|
static void prv_vibe_raw_ctl(bool on) {
|
|
if (BOARD_CONFIG_VIBE.options & ActuatorOptions_Pwm) {
|
|
const uint32_t duty_cycle = (on) ? s_vibe_duty_cycle : PWM_DUTY_CYCLE_OFF;
|
|
prv_vibe_pwm_enable(on);
|
|
pwm_set_duty_cycle(&BOARD_CONFIG_VIBE.pwm, s_vibe_duty_cycle);
|
|
}
|
|
|
|
if (BOARD_CONFIG_VIBE.options & ActuatorOptions_Ctl) {
|
|
gpio_output_set(&BOARD_CONFIG_VIBE.ctl, on);
|
|
}
|
|
}
|
|
|
|
void vibe_set_strength(int8_t strength) {
|
|
uint8_t duty_cycle = prv_vibe_get_pwm_duty_cycle(strength);
|
|
s_vibe_duty_cycle = MIN(duty_cycle, PWM_DUTY_CYCLE_FULL);
|
|
}
|
|
|
|
void vibe_ctl(bool on) {
|
|
if (!s_initialized) {
|
|
return;
|
|
}
|
|
|
|
if (on && battery_monitor_critical_lockout()) {
|
|
on = false;
|
|
}
|
|
|
|
static bool s_on = false;
|
|
if (on && !s_on) {
|
|
analytics_inc(ANALYTICS_DEVICE_METRIC_VIBRATOR_ON_COUNT, AnalyticsClient_System);
|
|
analytics_stopwatch_start(ANALYTICS_APP_METRIC_VIBRATOR_ON_TIME, AnalyticsClient_App);
|
|
analytics_stopwatch_start(ANALYTICS_DEVICE_METRIC_VIBRATOR_ON_TIME, AnalyticsClient_System);
|
|
} else if (!on && s_on) {
|
|
analytics_stopwatch_stop(ANALYTICS_APP_METRIC_VIBRATOR_ON_TIME);
|
|
analytics_stopwatch_stop(ANALYTICS_DEVICE_METRIC_VIBRATOR_ON_TIME);
|
|
}
|
|
s_on = on;
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Vibe status <%s>", on ? "on" : "off");
|
|
|
|
prv_vibe_raw_ctl(on);
|
|
}
|
|
|
|
void vibe_force_off(void) {
|
|
if (!s_initialized) {
|
|
return;
|
|
}
|
|
|
|
prv_vibe_raw_ctl(false);
|
|
}
|
|
|
|
int8_t vibe_get_braking_strength(void) {
|
|
if (BOARD_CONFIG_VIBE.options & ActuatorOptions_HBridge) {
|
|
// We support the full -100..100 range, send it all the way backwards
|
|
return VIBE_STRENGTH_MIN;
|
|
} else {
|
|
// We only support the 0..100 range, just ask it to turn off
|
|
return VIBE_STRENGTH_OFF;
|
|
}
|
|
}
|
|
|
|
|
|
void command_vibe_ctl(const char *arg) {
|
|
int strength = atoi(arg);
|
|
|
|
const bool out_of_bounds = ((strength < 0) || (strength > VIBE_STRENGTH_MAX));
|
|
const bool not_a_number = (strength == 0 && arg[0] != '0');
|
|
if (out_of_bounds || not_a_number) {
|
|
prompt_send_response("Invalid argument");
|
|
return;
|
|
}
|
|
|
|
vibe_set_strength(strength);
|
|
|
|
const bool turn_on = strength != 0;
|
|
vibe_ctl(turn_on);
|
|
prompt_send_response("OK");
|
|
}
|