203 lines
5.5 KiB
C
203 lines
5.5 KiB
C
#include "speed_sensor.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "esp_attr.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
static const char *TAG = "SPEED_SENSOR";
|
|
|
|
static gpio_num_t s_pin = GPIO_NUM_NC;
|
|
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
static volatile uint32_t s_pulse_total = 0;
|
|
static volatile int64_t s_last_valid_pulse_us = 0;
|
|
static volatile uint32_t s_dt_history_us[5] = {0};
|
|
static volatile uint8_t s_dt_history_count = 0;
|
|
static volatile uint8_t s_dt_history_index = 0;
|
|
|
|
static const float WHEEL_CIRCUMFERENCE_M = 0.229336f;
|
|
static const float DISTANCE_PER_PULSE_KM = 0.000114668f;
|
|
static const int64_t PULSE_MIN_VALID_US = 15000; // reject implausible pulses (> ~27.5 km/h with 7.3 cm wheel)
|
|
static const int64_t SPEED_ZERO_TIMEOUT_US = 2200000; // 2.2s timeout for slow manual rotation
|
|
|
|
static inline void clear_history_unsafe(void)
|
|
{
|
|
memset((void *)s_dt_history_us, 0, sizeof(s_dt_history_us));
|
|
s_dt_history_count = 0;
|
|
s_dt_history_index = 0;
|
|
s_last_valid_pulse_us = 0;
|
|
}
|
|
|
|
static uint32_t median_dt_us_unsafe(void)
|
|
{
|
|
if (s_dt_history_count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t temp[5] = {0};
|
|
for (uint8_t i = 0; i < s_dt_history_count; i++) {
|
|
temp[i] = s_dt_history_us[i];
|
|
}
|
|
|
|
for (uint8_t i = 1; i < s_dt_history_count; i++) {
|
|
uint32_t key = temp[i];
|
|
int8_t j = (int8_t)i - 1;
|
|
while (j >= 0 && temp[j] > key) {
|
|
temp[j + 1] = temp[j];
|
|
j--;
|
|
}
|
|
temp[j + 1] = key;
|
|
}
|
|
|
|
return temp[s_dt_history_count / 2];
|
|
}
|
|
|
|
static void IRAM_ATTR speed_sensor_isr_handler(void *arg)
|
|
{
|
|
(void)arg;
|
|
const int64_t now_us = esp_timer_get_time();
|
|
|
|
portENTER_CRITICAL_ISR(&s_lock);
|
|
|
|
if (s_last_valid_pulse_us != 0) {
|
|
const int64_t delta_us = now_us - s_last_valid_pulse_us;
|
|
if (delta_us <= 0 || delta_us < PULSE_MIN_VALID_US) {
|
|
portEXIT_CRITICAL_ISR(&s_lock);
|
|
return;
|
|
}
|
|
|
|
if (s_dt_history_count >= 2) {
|
|
const uint32_t recent_median_us = median_dt_us_unsafe();
|
|
if (recent_median_us > 0) {
|
|
const uint32_t lower_bound_us = (recent_median_us * 50U) / 100U;
|
|
if ((uint32_t)delta_us < lower_bound_us) {
|
|
portEXIT_CRITICAL_ISR(&s_lock);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
s_dt_history_us[s_dt_history_index] = (uint32_t)delta_us;
|
|
s_dt_history_index = (uint8_t)((s_dt_history_index + 1U) % 5U);
|
|
if (s_dt_history_count < 5U) {
|
|
s_dt_history_count++;
|
|
}
|
|
}
|
|
|
|
s_last_valid_pulse_us = now_us;
|
|
s_pulse_total++;
|
|
portEXIT_CRITICAL_ISR(&s_lock);
|
|
}
|
|
|
|
esp_err_t speed_sensor_init(gpio_num_t pin)
|
|
{
|
|
s_pin = pin;
|
|
|
|
portENTER_CRITICAL(&s_lock);
|
|
s_pulse_total = 0;
|
|
clear_history_unsafe();
|
|
portEXIT_CRITICAL(&s_lock);
|
|
|
|
const bool internal_pull_supported = !(pin == GPIO_NUM_34 || pin == GPIO_NUM_35 || pin == GPIO_NUM_36 || pin == GPIO_NUM_39);
|
|
|
|
const gpio_config_t io_conf = {
|
|
.pin_bit_mask = (1ULL << pin),
|
|
.mode = GPIO_MODE_INPUT,
|
|
.pull_up_en = internal_pull_supported ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
.intr_type = GPIO_INTR_NEGEDGE,
|
|
};
|
|
|
|
esp_err_t err = gpio_config(&io_conf);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
err = gpio_install_isr_service(0);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
return err;
|
|
}
|
|
|
|
err = gpio_isr_handler_add(pin, speed_sensor_isr_handler, NULL);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Speed sensor ready on GPIO %d (internal pull-up: %s)", pin, internal_pull_supported ? "on" : "off");
|
|
return ESP_OK;
|
|
}
|
|
|
|
uint32_t speed_sensor_get_pulse_total(void)
|
|
{
|
|
portENTER_CRITICAL(&s_lock);
|
|
const uint32_t total = s_pulse_total;
|
|
portEXIT_CRITICAL(&s_lock);
|
|
return total;
|
|
}
|
|
|
|
uint32_t speed_sensor_get_last_dt_ms(void)
|
|
{
|
|
uint32_t median_us = 0;
|
|
portENTER_CRITICAL(&s_lock);
|
|
median_us = median_dt_us_unsafe();
|
|
portEXIT_CRITICAL(&s_lock);
|
|
return median_us / 1000U;
|
|
}
|
|
|
|
void speed_sensor_update_timeout(void)
|
|
{
|
|
portENTER_CRITICAL(&s_lock);
|
|
const int64_t last_pulse_us = s_last_valid_pulse_us;
|
|
if (last_pulse_us != 0) {
|
|
const int64_t now_us = esp_timer_get_time();
|
|
if ((now_us - last_pulse_us) > SPEED_ZERO_TIMEOUT_US) {
|
|
clear_history_unsafe();
|
|
}
|
|
}
|
|
portEXIT_CRITICAL(&s_lock);
|
|
}
|
|
|
|
float speed_sensor_get_speed_kmh(void)
|
|
{
|
|
uint32_t median_dt_us = 0;
|
|
uint8_t history_count = 0;
|
|
int64_t last_pulse_us = 0;
|
|
|
|
portENTER_CRITICAL(&s_lock);
|
|
history_count = s_dt_history_count;
|
|
median_dt_us = median_dt_us_unsafe();
|
|
last_pulse_us = s_last_valid_pulse_us;
|
|
portEXIT_CRITICAL(&s_lock);
|
|
|
|
if (last_pulse_us == 0 || median_dt_us == 0 || history_count < 2) {
|
|
return 0.0f;
|
|
}
|
|
|
|
const int64_t now_us = esp_timer_get_time();
|
|
if ((now_us - last_pulse_us) > SPEED_ZERO_TIMEOUT_US) {
|
|
return 0.0f;
|
|
}
|
|
|
|
const float dt_s = (float)median_dt_us / 1000000.0f;
|
|
if (dt_s <= 0.0f) {
|
|
return 0.0f;
|
|
}
|
|
|
|
const float distance_per_pulse_m = WHEEL_CIRCUMFERENCE_M / 2.0f;
|
|
const float speed_kmh = (distance_per_pulse_m / dt_s) * 3.6f;
|
|
return speed_kmh < 0.05f ? 0.0f : speed_kmh;
|
|
}
|
|
|
|
float speed_sensor_get_distance_km(void)
|
|
{
|
|
portENTER_CRITICAL(&s_lock);
|
|
const uint32_t total = s_pulse_total;
|
|
portEXIT_CRITICAL(&s_lock);
|
|
return (float)total * DISTANCE_PER_PULSE_KM;
|
|
}
|