/** library to determine range using HC-SR04 ultrasonic range sensor * @file * @author King Kévin * @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 // standard integer types #include // general utilities #include // boolean utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // real-time control clock library #include // general purpose input output library #include // timer utilities #include // 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 } }