stm32f1/lib/sensor_sr04.c

140 lines
7.9 KiB
C

/** library to determine range using HC-SR04 ultrasonic range sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2020
* @note peripherals used: timer @ref sensor_sr04_timer, GPIO @ref sensor_sr04_gpio
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <stdbool.h> // boolean utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
/* own libraries */
#include "global.h" // common methods
#include "sensor_sr04.h" // own definitions
/** @defgroup sensor_sr04_timer timer used to measure echo response length
* @{
*/
#define SENSOR_SR04_TIMER 11 /**< timer ID peripheral peripheral */
#define SENSOR_SR04_CHANNEL 1 /**< timer channel used for echo input capture */
#define SENSOR_SR04_AF GPIO_AF3 /**< timer alternate function for the capture channel */
//PB9 TIM11_CH1
/** @} */
/** @defgroup sensor_sr04_gpio GPIO used to trigger measurement and get echo
* @{
*/
#define SENSOR_SR04_TRIGGER PB8 /**< GPIO used to trigger measurement (pulled up to 5V by HC-SR04 module) */
#define SENSOR_SR04_ECHO PB9 /**< GPIO used to get echo (active high), must be an timer input capture */
/** @} */
volatile uint16_t sensor_sr04_distance = 0;
/** if an echo has been received */
static volatile bool sensor_sr04_echo = false;
void sensor_sr04_setup(void)
{
// setup trigger GPIO
rcc_periph_clock_enable(GPIO_RCC(SENSOR_SR04_TRIGGER)); // enable clock for GPIO port domain
gpio_mode_setup(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_SR04_TRIGGER)); // set pin as output
gpio_set_output_options(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_PIN(SENSOR_SR04_TRIGGER)); // set pin output as open-drain
gpio_clear(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_PIN(SENSOR_SR04_TRIGGER)); // idle low
// setup echo GPIO
rcc_periph_clock_enable(GPIO_RCC(SENSOR_SR04_ECHO)); // enable clock for GPIO port domain
gpio_mode_setup(GPIO_PORT(SENSOR_SR04_ECHO), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_SR04_ECHO)); // set pin to alternate function input capture
gpio_set_af(GPIO_PORT(SENSOR_SR04_ECHO), SENSOR_SR04_AF, GPIO_PIN(SENSOR_SR04_ECHO)); // set alternate function for input capture
// setup timer
rcc_periph_clock_enable(RCC_TIM(SENSOR_SR04_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_SR04_TIMER)); // reset timer peripheral to default
// the timers use clock from APB1 or APB2 (see memory map to figure out), derivate from AHB
// in case of STM32F401, AHB is max. 84 MHz, APB1 is max. AHB / 2 = 42 MHz but the clock is twice the speed thus 84 MHz, APB2 is max. AHB = 84 MHz
// for simplicity we use AHB for the calculation, but in fact it could be more complicated
timer_set_prescaler(TIM(SENSOR_SR04_TIMER), 36 - 1); // set prescaler so we can measure up to 28 ms (1.0 / (84E6 / 36) * 2**16 = 28.1 ms), this above the maximum distance 400 cm (400 cm * 58 us/cm ) = 23200 us, datasheet says the maximum is 25 ms)
timer_set_mode(TIM(SENSOR_SR04_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // count up
timer_enable_update_event(TIM(SENSOR_SR04_TIMER)); // use update event to catch the overflow
timer_update_on_overflow(TIM(SENSOR_SR04_TIMER)); // only set update event on overflow
timer_one_shot_mode(TIM(SENSOR_SR04_TIMER)); // stop running after overflow
timer_set_period(TIM(SENSOR_SR04_TIMER), UINT16_MAX); // use full range
// setup input capture
timer_ic_set_input(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_IN_TI(SENSOR_SR04_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_prescaler(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_ic_enable(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL)); // enable input capture
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF); // clear update flag
timer_enable_irq(TIM(SENSOR_SR04_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_SR04_TIMER), TIM_DIER_CCIE(SENSOR_SR04_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_SR04_TIMER)); // catch interrupt in service routine
}
void sensor_sr04_release(void)
{
gpio_mode_setup(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_SR04_TRIGGER)); // release pin
rcc_periph_reset_pulse(RST_TIM(SENSOR_SR04_TIMER)); // reset timer peripheral to default
rcc_periph_clock_disable(RCC_TIM(SENSOR_SR04_TIMER)); // enable clock for timer peripheral
}
void sensor_sr04_trigger(void)
{
// prepare timer
timer_disable_counter(TIM(SENSOR_SR04_TIMER)); // disable timer to prepare it
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF); // clear update flag
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL)); // clear input compare flag
timer_set_counter(TIM(SENSOR_SR04_TIMER), 0); // reset counter
timer_generate_event(TIM(SENSOR_SR04_TIMER), TIM_EGR_UG); // clear shadow register
timer_ic_set_polarity(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_RISING); // capture on incoming echo (rising edge)
sensor_sr04_echo = false; // echo has not been received yet
// send pulse to trigger measurement
gpio_set(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_PIN(SENSOR_SR04_TRIGGER)); // start pull
sleep_us(10 + 1); // pulse duration
gpio_clear(GPIO_PORT(SENSOR_SR04_TRIGGER), GPIO_PIN(SENSOR_SR04_TRIGGER)); // end pulse
// start timer to measure the echo (the rest is done in the ISR)
timer_enable_counter(TIM(SENSOR_SR04_TIMER)); // start timer
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_SR04_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF)) { // timeout reached
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_UIF); // clear flag
if (!sensor_sr04_echo) { // no echo has been received
sensor_sr04_distance = 1; // provide measurement to user: object is probably too near
} else {
sensor_sr04_distance = UINT16_MAX; // provide measurement to user: object is probably too far
}
} else if (timer_get_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL))) { // edge detected on input capture
timer_clear_flag(TIM(SENSOR_SR04_TIMER), TIM_SR_CCIF(SENSOR_SR04_CHANNEL)); // clear input compare flag
if (!sensor_sr04_echo) { // echo is incoming
timer_disable_counter(TIM(SENSOR_SR04_TIMER)); // disable while reconfiguring
timer_set_counter(TIM(SENSOR_SR04_TIMER), 0); // reset timer counter
timer_generate_event(TIM(SENSOR_SR04_TIMER), TIM_EGR_UG); // clear shadow register
timer_ic_set_polarity(TIM(SENSOR_SR04_TIMER), TIM_IC(SENSOR_SR04_CHANNEL), TIM_IC_FALLING); // capture on end of echo (falling edge)
timer_enable_counter(TIM(SENSOR_SR04_TIMER)); // re-enable
sensor_sr04_echo = true; // remember echo is incoming
} else { // end of echo
timer_disable_counter(TIM(SENSOR_SR04_TIMER)); // disable counter now that measurement is complete
const uint16_t count = TIM_CCR(SENSOR_SR04_TIMER,SENSOR_SR04_CHANNEL); // save captured bit timing (this clear also the flag)
sensor_sr04_distance =(count * (TIM_PSC(TIM(SENSOR_SR04_TIMER)) + 1) * 343 / 2) / (rcc_ahb_frequency / 1000); // calculate distance and provide result to user
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}