2020-12-10 23:55:36 +01:00
/** 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
2020-12-16 02:37:35 +01:00
timer_generate_event ( TIM ( SENSOR_SR04_TIMER ) , TIM_EGR_UG ) ; // clear shadow register
2020-12-10 23:55:36 +01:00
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
2020-12-16 02:37:35 +01:00
timer_disable_counter ( TIM ( SENSOR_SR04_TIMER ) ) ; // disable while reconfiguring
2020-12-10 23:55:36 +01:00
timer_set_counter ( TIM ( SENSOR_SR04_TIMER ) , 0 ) ; // reset timer counter
2020-12-16 02:37:35 +01:00
timer_generate_event ( TIM ( SENSOR_SR04_TIMER ) , TIM_EGR_UG ) ; // clear shadow register
2020-12-10 23:55:36 +01:00
timer_ic_set_polarity ( TIM ( SENSOR_SR04_TIMER ) , TIM_IC ( SENSOR_SR04_CHANNEL ) , TIM_IC_FALLING ) ; // capture on end of echo (falling edge)
2020-12-16 02:37:35 +01:00
timer_enable_counter ( TIM ( SENSOR_SR04_TIMER ) ) ; // re-enable
2020-12-10 23:55:36 +01:00
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
}
}