- trigger on rising edge only - ignore too short pulses - possible noise (config option) - ignore invalid pulse orders - debug log count of ignored sequences
		
			
				
	
	
		
			195 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "speedsensor.hpp"
 | |
| #include "esp_timer.h"
 | |
| #include <ctime>
 | |
| 
 | |
| //===== config =====
 | |
| #define TIMEOUT_NO_ROTATION 1000 //RPM set to 0 when no pulses within that time (ms)
 | |
| static const char* TAG = "speedSensor";
 | |
| 
 | |
| 
 | |
| //initialize ISR only once (for multiple instances)
 | |
| bool speedSensor::isrIsInitialized = false;
 | |
| 
 | |
| 
 | |
| uint32_t min(uint32_t a, uint32_t b){
 | |
| 	if (a>b) return b;
 | |
| 	else return a;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //=========================================
 | |
| //========== ISR onEncoderRising ==========
 | |
| //=========================================
 | |
| //handle gpio rising edge event
 | |
| //determines direction and rotational speed with a speedSensor object
 | |
| void IRAM_ATTR onEncoderRising(void *arg)
 | |
| {
 | |
| 	speedSensor *sensor = (speedSensor *)arg;
 | |
| 	int currentState = gpio_get_level(sensor->config.gpioPin);
 | |
| 
 | |
| 	// time since last edge in us
 | |
| 	uint32_t currentTime = esp_timer_get_time();
 | |
| 	uint32_t timeElapsed = currentTime - sensor->lastEdgeTime;
 | |
| 	sensor->lastEdgeTime = currentTime; // update last edge time
 | |
| 
 | |
| 	// store duration of last pulse
 | |
| 	sensor->pulseDurations[sensor->pulseCounter] = timeElapsed;
 | |
| 	sensor->pulseCounter++;
 | |
| 
 | |
| 	// check if 3rd pulse has occoured (one sequence recorded)
 | |
| 	if (sensor->pulseCounter >= 3)
 | |
| 	{
 | |
| 		sensor->pulseCounter = 0; // reset count
 | |
| 
 | |
| 		// simplify variable names
 | |
| 		uint32_t pulse1 = sensor->pulseDurations[0];
 | |
| 		uint32_t pulse2 = sensor->pulseDurations[1];
 | |
| 		uint32_t pulse3 = sensor->pulseDurations[2];
 | |
| 
 | |
| 		// find shortest pulse
 | |
| 		sensor->shortestPulse = min(pulse1, min(pulse2, pulse3));
 | |
| 
 | |
| 		// ignore this pulse sequence if one pulse is too short (possible noise)
 | |
| 		if (sensor->shortestPulse < sensor->config.minPulseDurationUs)
 | |
| 		{
 | |
| 			sensor->debug_countIgnoredSequencesTooShort++;
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		//-- Determine direction based on pulse order ---
 | |
| 		int directionNew = 0;
 | |
| 		if (sensor->shortestPulse == pulse1) // short...
 | |
| 		{
 | |
| 			if (pulse2 < pulse3) // short-medium-long
 | |
| 				directionNew = 1;
 | |
| 			else // short-long-medium (invaild)
 | |
| 			{
 | |
| 				sensor->debug_countIgnoredSequencesInvalidOrder++;
 | |
| 				return;
 | |
| 			};
 | |
| 		}
 | |
| 		else if (sensor->shortestPulse == pulse3) //...short
 | |
| 		{
 | |
| 			if (pulse1 > pulse2) // long-medium-short
 | |
| 				directionNew = -1;
 | |
| 			else // medium-long-short (invaild)
 | |
| 			{
 | |
| 				sensor->debug_countIgnoredSequencesInvalidOrder++;
 | |
| 				return;
 | |
| 			};
 | |
| 		}
 | |
| 		else if (sensor->shortestPulse == pulse2) //...short...
 | |
| 		{
 | |
| 			// medium-short-long (invalid)
 | |
| 			// long-short-medium (invalid)
 | |
| 			sensor->debug_countIgnoredSequencesInvalidOrder++;
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// save and invert direction if necessay
 | |
| 		// TODO mutex?
 | |
| 		if (sensor->config.directionInverted)
 | |
| 			sensor->direction = -directionNew;
 | |
| 		else
 | |
| 			sensor->direction = directionNew;
 | |
| 
 | |
| 		// calculate rotational speed
 | |
| 		uint64_t pulseSum = pulse1 + pulse2 + pulse3;
 | |
| 		sensor->currentRpm = directionNew * (sensor->config.degreePerGroup / 360.0 * 60.0 / ((double)pulseSum / 1000000.0));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| //============================
 | |
| //======= constructor ========
 | |
| //============================
 | |
| speedSensor::speedSensor(speedSensor_config_t config_f){
 | |
| 	//copy config
 | |
| 	config = config_f;
 | |
| 	//init gpio and ISR
 | |
| 	init();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //==========================
 | |
| //========== init ==========
 | |
| //==========================
 | |
|  //initializes gpio pin and configures interrupt
 | |
| void speedSensor::init() {
 | |
| 	//configure pin
 | |
| 	gpio_pad_select_gpio(config.gpioPin);
 | |
| 	gpio_set_direction(config.gpioPin, GPIO_MODE_INPUT);
 | |
| 	gpio_set_pull_mode(config.gpioPin, GPIO_PULLUP_ONLY);
 | |
| 
 | |
| 	//configure interrupt
 | |
| 	gpio_set_intr_type(config.gpioPin, GPIO_INTR_POSEDGE);
 | |
| 	if (!isrIsInitialized) {
 | |
| 		gpio_install_isr_service(0);
 | |
| 		isrIsInitialized = true;
 | |
| 		ESP_LOGW(TAG, "Initialized ISR service");
 | |
| 	}
 | |
| 	gpio_isr_handler_add(config.gpioPin, onEncoderRising, this);
 | |
| 	ESP_LOGW(TAG, "[%s], configured gpio-pin %d and interrupt routine", config.logName, (int)config.gpioPin);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| //==========================
 | |
| //========= getRpm =========
 | |
| //==========================
 | |
| //get rotational speed in revolutions per minute
 | |
| float speedSensor::getRpm(){
 | |
| 	uint32_t timeElapsed = esp_timer_get_time() - lastEdgeTime;
 | |
| 	//timeout (standstill)
 | |
| 	//TODO variable timeout considering config.degreePerGroup
 | |
| 	if ((currentRpm != 0) && (esp_timer_get_time() - lastEdgeTime) > TIMEOUT_NO_ROTATION*1000){
 | |
| 		ESP_LOGW(TAG, "%s - timeout: no pulse within %dms... last pulse was %dms ago => set RPM to 0",
 | |
| 				config.logName, TIMEOUT_NO_ROTATION, timeElapsed/1000);
 | |
| 		currentRpm = 0;
 | |
| 	}
 | |
| 	//debug output (also log variables when this function is called)
 | |
| 	ESP_LOGI(TAG, "[%s] getRpm: returning stored rpm=%.3f", config.logName, currentRpm);
 | |
| 	ESP_LOGV(TAG, "[%s] rpm=%f, dir=%d, pulseCount=%d, p1=%d, p2=%d, p3=%d, shortest=%d, tooShortCount=%d, invalidOrderCount=%d",
 | |
| 			config.logName,
 | |
| 			currentRpm, 
 | |
| 			direction, 
 | |
| 			pulseCounter, 
 | |
| 			(int)pulseDurations[0]/1000,  
 | |
| 			(int)pulseDurations[1]/1000, 
 | |
| 			(int)pulseDurations[2]/1000,
 | |
| 			shortestPulse,
 | |
| 			debug_countIgnoredSequencesTooShort,
 | |
| 			debug_countIgnoredSequencesInvalidOrder);
 | |
| 
 | |
| 	//return currently stored rpm
 | |
| 	return currentRpm;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //===========================
 | |
| //========= getKmph =========
 | |
| //===========================
 | |
| //get speed in kilometers per hour
 | |
| float speedSensor::getKmph(){
 | |
| 	float currentSpeed = getRpm() * config.tireCircumferenceMeter * 60/1000;
 | |
| 	ESP_LOGI(TAG, "%s - getKmph: returning speed=%.3fkm/h", config.logName, currentSpeed);
 | |
| 	return currentSpeed;
 | |
| }
 | |
| 
 | |
| 
 | |
| //==========================
 | |
| //========= getMps =========
 | |
| //==========================
 | |
| //get speed in meters per second
 | |
| float speedSensor::getMps(){
 | |
| 	float currentSpeed = getRpm() * config.tireCircumferenceMeter / 60;
 | |
| 	ESP_LOGI(TAG, "%s - getMps: returning speed=%.3fm/s", config.logName, currentSpeed);
 | |
| 	return currentSpeed;
 | |
| }
 |