diff --git a/src/clock.c b/src/clock.c index 32e6ff78..09f53d64 100644 --- a/src/clock.c +++ b/src/clock.c @@ -51,6 +51,16 @@ void sleep_ticks(uint32_t ticks) ; } +/** + * @brief Sleep (i.e.: do nothing) for a number of seconds. + * + * @param[in] seconds Sleep duration, in seconds. + */ +void sleep_seconds(float seconds) +{ + sleep_ticks((uint32_t)(seconds * SYSTICK_FREQUENCY_HZ)); +} + /** * @brief Start the stopwatch to measure elapsed time. * diff --git a/src/clock.h b/src/clock.h index 10298f52..478933be 100644 --- a/src/clock.h +++ b/src/clock.h @@ -11,6 +11,7 @@ void each(uint32_t period, void (*function)(void), uint32_t during); uint32_t get_clock_ticks(void); uint32_t read_cycle_counter(void); void sleep_ticks(uint32_t ticks); +void sleep_seconds(float seconds); void stopwatch_start(void); float stopwatch_stop(void); void sleep_us(uint32_t us); diff --git a/src/hmi.c b/src/hmi.c index 895d382a..5317252b 100644 --- a/src/hmi.c +++ b/src/hmi.c @@ -110,6 +110,36 @@ void blink_collision(void) led_right_off(); } +/** + * @brief Warn low battery using speaker sounds. + */ +void speaker_warn_low_battery(void) +{ + speaker_play('C', 4, 0, 0.05); + sleep_ticks(50); + speaker_play('C', 3, 0, 0.05); + sleep_ticks(50); +} + +/** + * @brief Notify about an error with a low pitch sustained sound. + */ +void speaker_play_error(void) +{ + speaker_play('C', 3, 0, 2.); +} + +/** + * @brief Play three fast, high tones to note a successful operation. + */ +void speaker_play_success(void) +{ + for (int i = 0; i < 3; i++) { + speaker_play('C', 8, 0, 0.05); + sleep_ticks(50); + } +} + /** * @brief Function to read button left. */ diff --git a/src/hmi.h b/src/hmi.h index 052a6558..b2bf3e97 100644 --- a/src/hmi.h +++ b/src/hmi.h @@ -5,6 +5,7 @@ #include "clock.h" #include "detection.h" +#include "speaker.h" #include "speed.h" void led_left_toggle(void); @@ -18,6 +19,9 @@ void led_right_off(void); void led_bluepill_off(void); void repeat_blink(uint8_t count, uint16_t time); void blink_collision(void); +void speaker_warn_low_battery(void); +void speaker_play_error(void); +void speaker_play_success(void); bool button_left_read(void); bool button_right_read(void); bool button_left_read_consecutive(uint32_t count); diff --git a/src/main.c b/src/main.c index b0ad6956..f7746db0 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ #include "serial.h" #include "setup.h" #include "solve.h" +#include "speaker.h" #include "speed.h" static void competition(void); @@ -46,6 +47,24 @@ static void user_configuration(bool run) set_speed_mode(mode, run); } +/** + * @brief Check battery voltage and warn if the voltage is getting too low. + */ +static void check_battery_voltage(void) +{ + float voltage; + + voltage = get_battery_voltage(); + if (voltage < 3.6) + speaker_warn_low_battery(); + if (voltage < 3.5) + speaker_warn_low_battery(); + if (voltage < 3.4) + speaker_warn_low_battery(); + if (voltage < 3.3) + speaker_play_error(); +} + /** * @brief Includes the functions to be executed before robot starts to move. */ @@ -55,6 +74,7 @@ static void before_moving(void) disable_walls_control(); repeat_blink(10, 100); sleep_us(5000000); + check_battery_voltage(); led_left_on(); led_right_on(); wait_front_sensor_close_signal(0.12); @@ -75,9 +95,11 @@ static void after_moving(void) reset_motion(); blink_collision(); } else { + speaker_play_success(); repeat_blink(10, 100); } reset_motion(); + check_battery_voltage(); } /** diff --git a/src/setup.c b/src/setup.c index 540d89bd..70ed2942 100644 --- a/src/setup.c +++ b/src/setup.c @@ -218,7 +218,7 @@ void setup_spi_low_speed(void) * @see Reference manual (RM0008) "TIMx functional description" and in * particular "PWM mode" section. */ -static void setup_pwm(void) +static void setup_motor_driver(void) { timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); @@ -252,6 +252,48 @@ static void setup_pwm(void) timer_enable_counter(TIM3); } +/** + * @brief Setup PWM for the speaker. + * + * TIM1 is used to generate the PWM signals for the speaker: + * + * - Configure channel 3 as output GPIO. + * - Edge-aligned, up-counting timer. + * - Prescale to increment timer counter at TIM1CLK_FREQUENCY_HZ. + * - Set output compare mode to PWM1 (output is active when the counter is + * less than the compare register contents and inactive otherwise. + * - Disable output compare output (speaker is off by default). + * - Enable outputs in the break subsystem. + * + * @see Reference manual (RM0008) "TIMx functional description" and in + * particular "PWM mode" section. + */ +void setup_speaker(void) +{ + rcc_periph_reset_pulse(RST_TIM1); + + gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, + GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_TIM1_CH3); + + /* Make sure to turn emitters off */ + gpio_clear(GPIOA, GPIO8 | GPIO9); + gpio_clear(GPIOB, GPIO8 | GPIO9); + + timer_set_mode(TIM1, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, + TIM_CR1_DIR_UP); + + timer_set_prescaler( + TIM1, (rcc_apb2_frequency / SPEAKER_BASE_FREQUENCY_HZ - 1)); + timer_set_repetition_counter(TIM1, 0); + timer_enable_preload(TIM1); + timer_continuous_mode(TIM1); + + timer_disable_oc_output(TIM1, TIM_OC3); + timer_set_oc_mode(TIM1, TIM_OC3, TIM_OCM_PWM1); + + timer_enable_break_main_output(TIM1); +} + /** * @brief Configure timer to read a quadrature encoder. * @@ -428,9 +470,10 @@ static void setup_adc2(void) * * @see Reference manual (RM0008) "Advanced-control timers" */ -static void setup_timer1(void) +void setup_emitters(void) { rcc_periph_reset_pulse(RST_TIM1); + timer_set_mode(TIM1, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); timer_set_clock_division(TIM1, 0x00); @@ -452,9 +495,9 @@ void setup(void) setup_adc2(); setup_usart(); setup_encoders(); - setup_pwm(); + setup_motor_driver(); setup_mpu(); setup_systick(); - setup_timer1(); + setup_emitters(); setup_adc1(); } diff --git a/src/setup.h b/src/setup.h index 8da9b7ea..ee18907f 100644 --- a/src/setup.h +++ b/src/setup.h @@ -26,6 +26,7 @@ /** System clock frequency is set in `setup_clock` */ #define SYSCLK_FREQUENCY_HZ 72000000 +#define SPEAKER_BASE_FREQUENCY_HZ 1000000 #define SYSTICK_FREQUENCY_HZ 1000 #define DRIVER_PWM_PERIOD 1024 @@ -59,6 +60,8 @@ #define BATTERY_LOW_LIMIT_VOLTAGE 3.3 void setup(void); +void setup_emitters(void); +void setup_speaker(void); void setup_spi_low_speed(void); void setup_spi_high_speed(void); void enable_systick_interruption(void); diff --git a/src/speaker.c b/src/speaker.c new file mode 100644 index 00000000..3ad10ee4 --- /dev/null +++ b/src/speaker.c @@ -0,0 +1,81 @@ +#include "speaker.h" + +/** + * Number of semitones above C, for each note in an octave. + * + * Notes are in order: {A, B, C, D, E, F, G}. + */ +uint8_t SEMITONES[8] = {9, 11, 0, 2, 4, 5, 7}; + +/** + * Base or reference note, used for the pitch. + */ +uint8_t BASE_NOTE = 9; +uint8_t BASE_OCTAVE = 4; +float BASE_FREQUENCY = 440.; + +/** + * @brief Set the frequency for the speaker. + * + * Frequency is set modulating the PWM signal sent to the speaker. + * + * @param[in] hz Frequency, in Hertz. + */ +static void speaker_set_frequency(float hz) +{ + uint16_t period; + + period = (uint16_t)(SPEAKER_BASE_FREQUENCY_HZ / hz); + timer_set_period(TIM1, period); + timer_set_oc_value(TIM1, TIM_OC3, period / 2); +} + +/** + * @brief Turn on the speaker to play the set frequency. + */ +static void speaker_on(void) +{ + timer_enable_counter(TIM1); + timer_enable_oc_output(TIM1, TIM_OC3); +} + +/** + * @brief Turn off the speaker. + */ +static void speaker_off(void) +{ + timer_disable_counter(TIM1); + timer_disable_oc_output(TIM1, TIM_OC3); +} + +/** + * @brief Play a note through the speaker. + * + * Even if this function uses the scientific notation, notes are played with + * the concert pitch (standard pitch). That means A above middle C is the + * reference note, played at 440 Hz. + * + * @param[in] note Which note to play, in scientific notation. + * @param[in] octave Which octave to play, in scientific notation. + * @param[in] accidental Number of semitones to sum to the note. + * @param[in] duration Duration of the note, in seconds. + * + * @note The speaker and emitters both share TIM1. While playing a note, the + * emitters will be completely disabled and enabled back only after finishing + * playing the note. + */ +void speaker_play(char note, uint8_t octave, int8_t accidental, float duration) +{ + int16_t sound; + float frequency; + + sound = SEMITONES[note - 'A'] - BASE_NOTE + (octave - BASE_OCTAVE) * 12; + sound += accidental; + frequency = pow(2, sound / 12.) * BASE_FREQUENCY; + setup_speaker(); + speaker_set_frequency(frequency); + speaker_on(); + sleep_seconds(duration); + speaker_off(); + setup_emitters(); +} diff --git a/src/speaker.h b/src/speaker.h new file mode 100644 index 00000000..39b90066 --- /dev/null +++ b/src/speaker.h @@ -0,0 +1,12 @@ +#ifndef __SPEAKER_H +#define __SPEAKER_H + +#include + +#include + +#include "setup.h" + +void speaker_play(char note, uint8_t octave, int8_t accidental, float duration); + +#endif /* __SPEAKER_H */