140 lines
7.9 KiB
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
|
|
}
|
|
}
|
|
|