فهرست منبع

[FL-3040] Audio support for SubGhz (#2131)

* Furi_hal_speaker: multiple resource usage
* Furi_hal_speaker: fix multiple resource usage
* Furi_hal_speaker: fix music_player_worker
* Furi_hal_speaker: fix mutex release queue handling
* SubGhz: add furi_hal_subghz_set_debug_pin
* SubGhz: add sound SubGhz Read, SubGhz Read RAW
* furi_hal_speaker: add __attribute__((warn_unused_result)) for furi_hal_speaker_acquire()
* Furi_hal_speaker: fix review comments
* SubGhz: cleanup naming and locking timings
* SubGhz,FuriHal: fix speaker deinit logic and subghz speaker release sequence
* FuriHal: crash on speaker acquire/release from IRQ
* Furi, FuriHal: FURI_WARN_UNUSED and documentation update
* Bump api symbols version: fix broken speaker

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Skorpionm 3 سال پیش
والد
کامیت
2dea6969fe

+ 7 - 0
applications/main/subghz/helpers/subghz_types.h

@@ -28,6 +28,13 @@ typedef enum {
     SubGhzHopperStateRSSITimeOut,
 } SubGhzHopperState;
 
+/** SubGhzSpeakerState state */
+typedef enum {
+    SubGhzSpeakerStateDisable,
+    SubGhzSpeakerStateShutdown,
+    SubGhzSpeakerStateEnable,
+} SubGhzSpeakerState;
+
 /** SubGhzRxKeyState state */
 typedef enum {
     SubGhzRxKeyStateIDLE,

+ 3 - 0
applications/main/subghz/scenes/subghz_scene_read_raw.c

@@ -259,6 +259,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
         case SubGhzCustomEventViewReadRAWSendStop:
             subghz->state_notifications = SubGhzNotificationStateIDLE;
             if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+                subghz_speaker_unmute(subghz);
                 subghz_tx_stop(subghz);
                 subghz_sleep(subghz);
             }
@@ -376,10 +377,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
                     subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false);
                     subghz_protocol_raw_save_to_file_pause(
                         (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, true);
+                    subghz_speaker_mute(subghz);
                 } else {
                     subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true);
                     subghz_protocol_raw_save_to_file_pause(
                         (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false);
+                    subghz_speaker_unmute(subghz);
                 }
             }
 

+ 29 - 0
applications/main/subghz/scenes/subghz_scene_receiver_config.c

@@ -5,6 +5,7 @@ enum SubGhzSettingIndex {
     SubGhzSettingIndexFrequency,
     SubGhzSettingIndexHopping,
     SubGhzSettingIndexModulation,
+    SubGhzSettingIndexSound,
     SubGhzSettingIndexLock,
     SubGhzSettingIndexRAWThesholdRSSI,
 };
@@ -48,6 +49,16 @@ const uint32_t hopping_value[HOPPING_COUNT] = {
     SubGhzHopperStateRunnig,
 };
 
+#define SPEAKER_COUNT 2
+const char* const speaker_text[SPEAKER_COUNT] = {
+    "OFF",
+    "ON",
+};
+const uint32_t speaker_value[SPEAKER_COUNT] = {
+    SubGhzSpeakerStateShutdown,
+    SubGhzSpeakerStateEnable,
+};
+
 uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) {
     furi_assert(context);
     SubGhz* subghz = context;
@@ -167,6 +178,14 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item)
     subghz->txrx->hopper_state = hopping_value[index];
 }
 
+static void subghz_scene_receiver_config_set_speaker(VariableItem* item) {
+    SubGhz* subghz = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, speaker_text[index]);
+    subghz->txrx->speaker_state = speaker_value[index];
+}
+
 static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) {
     SubGhz* subghz = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
@@ -235,6 +254,16 @@ void subghz_scene_receiver_config_on_enter(void* context) {
     variable_item_set_current_value_text(
         item, subghz_setting_get_preset_name(subghz->setting, value_index));
 
+    item = variable_item_list_add(
+        subghz->variable_item_list,
+        "Sound:",
+        SPEAKER_COUNT,
+        subghz_scene_receiver_config_set_speaker,
+        subghz);
+    value_index = value_index_uint32(subghz->txrx->speaker_state, speaker_value, SPEAKER_COUNT);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, speaker_text[value_index]);
+
     if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
        SubGhzCustomEventManagerSet) {
         variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL);

+ 1 - 0
applications/main/subghz/subghz.c

@@ -177,6 +177,7 @@ SubGhz* subghz_alloc() {
 
     subghz->txrx->txrx_state = SubGhzTxRxStateSleep;
     subghz->txrx->hopper_state = SubGhzHopperStateOFF;
+    subghz->txrx->speaker_state = SubGhzSpeakerStateDisable;
     subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
     subghz->txrx->raw_threshold_rssi = SUBGHZ_RAW_TRESHOLD_MIN;
     subghz->txrx->history = subghz_history_alloc();

+ 42 - 0
applications/main/subghz/subghz_i.c

@@ -86,6 +86,7 @@ uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) {
     uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency);
     furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
     furi_hal_subghz_flush_rx();
+    subghz_speaker_on(subghz);
     furi_hal_subghz_rx();
 
     furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, subghz->txrx->worker);
@@ -104,6 +105,7 @@ static bool subghz_tx(SubGhz* subghz, uint32_t frequency) {
     furi_hal_subghz_set_frequency_and_path(frequency);
     furi_hal_gpio_write(&gpio_cc1101_g0, false);
     furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
+    subghz_speaker_on(subghz);
     bool ret = furi_hal_subghz_tx();
     subghz->txrx->txrx_state = SubGhzTxRxStateTx;
     return ret;
@@ -119,11 +121,13 @@ void subghz_idle(SubGhz* subghz) {
 void subghz_rx_end(SubGhz* subghz) {
     furi_assert(subghz);
     furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateRx);
+
     if(subghz_worker_is_running(subghz->txrx->worker)) {
         subghz_worker_stop(subghz->txrx->worker);
         furi_hal_subghz_stop_async_rx();
     }
     furi_hal_subghz_idle();
+    subghz_speaker_off(subghz);
     subghz->txrx->txrx_state = SubGhzTxRxStateIDLE;
 }
 
@@ -212,6 +216,7 @@ void subghz_tx_stop(SubGhz* subghz) {
             subghz, subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path));
     }
     subghz_idle(subghz);
+    subghz_speaker_off(subghz);
     notification_message(subghz->notifications, &sequence_reset_red);
 }
 
@@ -585,3 +590,40 @@ void subghz_hopper_update(SubGhz* subghz) {
         subghz_rx(subghz, subghz->txrx->preset->frequency);
     }
 }
+
+void subghz_speaker_on(SubGhz* subghz) {
+    if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_acquire(30)) {
+            furi_hal_subghz_set_async_mirror_pin(&gpio_speaker);
+        } else {
+            subghz->txrx->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+void subghz_speaker_off(SubGhz* subghz) {
+    if(subghz->txrx->speaker_state != SubGhzSpeakerStateDisable) {
+        if(furi_hal_speaker_is_mine()) {
+            furi_hal_subghz_set_async_mirror_pin(NULL);
+            furi_hal_speaker_release();
+            if(subghz->txrx->speaker_state == SubGhzSpeakerStateShutdown)
+                subghz->txrx->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+void subghz_speaker_mute(SubGhz* subghz) {
+    if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            furi_hal_subghz_set_async_mirror_pin(NULL);
+        }
+    }
+}
+
+void subghz_speaker_unmute(SubGhz* subghz) {
+    if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            furi_hal_subghz_set_async_mirror_pin(&gpio_speaker);
+        }
+    }
+}

+ 5 - 0
applications/main/subghz/subghz_i.h

@@ -53,6 +53,7 @@ struct SubGhzTxRx {
     uint16_t idx_menu_chosen;
     SubGhzTxRxState txrx_state;
     SubGhzHopperState hopper_state;
+    SubGhzSpeakerState speaker_state;
     uint8_t hopper_timeout;
     uint8_t hopper_idx_frequency;
     SubGhzRxKeyState rx_key_state;
@@ -131,3 +132,7 @@ void subghz_file_name_clear(SubGhz* subghz);
 bool subghz_path_is_file(FuriString* path);
 uint32_t subghz_random_serial(void);
 void subghz_hopper_update(SubGhz* subghz);
+void subghz_speaker_on(SubGhz* subghz);
+void subghz_speaker_off(SubGhz* subghz);
+void subghz_speaker_mute(SubGhz* subghz);
+void subghz_speaker_unmute(SubGhz* subghz);

+ 42 - 38
applications/plugins/music_player/music_player_worker.c

@@ -47,47 +47,51 @@ static int32_t music_player_worker_thread_callback(void* context) {
 
     NoteBlockArray_it_t it;
     NoteBlockArray_it(it, instance->notes);
-
-    while(instance->should_work) {
-        if(NoteBlockArray_end_p(it)) {
-            NoteBlockArray_it(it, instance->notes);
-            furi_delay_ms(10);
-        } else {
-            NoteBlock* note_block = NoteBlockArray_ref(it);
-
-            float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
-            float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
-            float duration =
-                60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration;
-            uint32_t dots = note_block->dots;
-            while(dots > 0) {
-                duration += duration / 2;
-                dots--;
-            }
-            uint32_t next_tick = furi_get_tick() + duration;
-            float volume = instance->volume;
-
-            if(instance->callback) {
-                instance->callback(
-                    note_block->semitone,
-                    note_block->dots,
-                    note_block->duration,
-                    0.0,
-                    instance->callback_context);
-            }
-
-            furi_hal_speaker_stop();
-            furi_hal_speaker_start(frequency, volume);
-            while(instance->should_work && furi_get_tick() < next_tick) {
-                volume *= 0.9945679;
-                furi_hal_speaker_set_volume(volume);
-                furi_delay_ms(2);
+    if(furi_hal_speaker_acquire(1000)) {
+        while(instance->should_work) {
+            if(NoteBlockArray_end_p(it)) {
+                NoteBlockArray_it(it, instance->notes);
+                furi_delay_ms(10);
+            } else {
+                NoteBlock* note_block = NoteBlockArray_ref(it);
+
+                float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
+                float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
+                float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm /
+                                 note_block->duration;
+                uint32_t dots = note_block->dots;
+                while(dots > 0) {
+                    duration += duration / 2;
+                    dots--;
+                }
+                uint32_t next_tick = furi_get_tick() + duration;
+                float volume = instance->volume;
+
+                if(instance->callback) {
+                    instance->callback(
+                        note_block->semitone,
+                        note_block->dots,
+                        note_block->duration,
+                        0.0,
+                        instance->callback_context);
+                }
+
+                furi_hal_speaker_stop();
+                furi_hal_speaker_start(frequency, volume);
+                while(instance->should_work && furi_get_tick() < next_tick) {
+                    volume *= 0.9945679;
+                    furi_hal_speaker_set_volume(volume);
+                    furi_delay_ms(2);
+                }
+                NoteBlockArray_next(it);
             }
-            NoteBlockArray_next(it);
         }
-    }
 
-    furi_hal_speaker_stop();
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    } else {
+        FURI_LOG_E(TAG, "Speaker system is busy with another process.");
+    }
 
     return 0;
 }

+ 7 - 2
applications/services/notification/notification_app.c

@@ -150,11 +150,16 @@ void notification_vibro_off() {
 }
 
 void notification_sound_on(float freq, float volume) {
-    furi_hal_speaker_start(freq, volume);
+    if(furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(freq, volume);
+    }
 }
 
 void notification_sound_off() {
-    furi_hal_speaker_stop();
+    if(furi_hal_speaker_is_mine()) {
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    }
 }
 
 // display timer

+ 6 - 1
firmware/targets/f7/api_symbols.csv

@@ -1,5 +1,5 @@
 entry,status,name,type,params
-Version,+,10.1,,
+Version,+,11.0,,
 Header,+,applications/services/bt/bt_service/bt.h,,
 Header,+,applications/services/cli/cli.h,,
 Header,+,applications/services/cli/cli_vcp.h,,
@@ -1282,7 +1282,11 @@ Function,+,furi_hal_rtc_set_log_level,void,uint8_t
 Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t
 Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t"
 Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime*
+Function,+,furi_hal_speaker_acquire,_Bool,uint32_t
+Function,-,furi_hal_speaker_deinit,void,
 Function,-,furi_hal_speaker_init,void,
+Function,+,furi_hal_speaker_is_mine,_Bool,
+Function,+,furi_hal_speaker_release,void,
 Function,+,furi_hal_speaker_set_volume,void,float
 Function,+,furi_hal_speaker_start,void,"float, float"
 Function,+,furi_hal_speaker_stop,void,
@@ -1316,6 +1320,7 @@ Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*"
 Function,+,furi_hal_subghz_reset,void,
 Function,+,furi_hal_subghz_rx,void,
 Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool,
+Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin*
 Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t
 Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t
 Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath

+ 49 - 2
firmware/targets/f7/furi_hal/furi_hal_speaker.c

@@ -1,23 +1,66 @@
 #include <furi_hal_speaker.h>
 #include <furi_hal_gpio.h>
 #include <furi_hal_resources.h>
+#include <furi_hal_power.h>
 
 #include <stm32wbxx_ll_tim.h>
+#include <furi_hal_cortex.h>
+
+#define TAG "FuriHalSpeaker"
 
 #define FURI_HAL_SPEAKER_TIMER TIM16
 #define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
 #define FURI_HAL_SPEAKER_PRESCALER 500
 #define FURI_HAL_SPEAKER_MAX_VOLUME 60
 
+static FuriMutex* furi_hal_speaker_mutex = NULL;
+
 // #define FURI_HAL_SPEAKER_NEW_VOLUME
 
 void furi_hal_speaker_init() {
+    furi_assert(furi_hal_speaker_mutex == NULL);
+    furi_hal_speaker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
     FURI_CRITICAL_ENTER();
     LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER);
     FURI_CRITICAL_EXIT();
+    FURI_LOG_I(TAG, "Init OK");
+}
+
+void furi_hal_speaker_deinit() {
+    furi_check(furi_hal_speaker_mutex != NULL);
+    LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER);
+    furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_mutex_free(furi_hal_speaker_mutex);
+    furi_hal_speaker_mutex = NULL;
+}
 
-    furi_hal_gpio_init_ex(
-        &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16);
+bool furi_hal_speaker_acquire(uint32_t timeout) {
+    furi_check(!FURI_IS_IRQ_MODE());
+
+    if(furi_mutex_acquire(furi_hal_speaker_mutex, timeout) == FuriStatusOk) {
+        furi_hal_power_insomnia_enter();
+        furi_hal_gpio_init_ex(
+            &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void furi_hal_speaker_release() {
+    furi_check(!FURI_IS_IRQ_MODE());
+    furi_check(furi_hal_speaker_is_mine());
+
+    furi_hal_speaker_stop();
+    furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_power_insomnia_exit();
+
+    furi_check(furi_mutex_release(furi_hal_speaker_mutex) == FuriStatusOk);
+}
+
+bool furi_hal_speaker_is_mine() {
+    return (FURI_IS_IRQ_MODE()) ||
+           (furi_mutex_get_owner(furi_hal_speaker_mutex) == furi_thread_get_current_id());
 }
 
 static inline uint32_t furi_hal_speaker_calculate_autoreload(float frequency) {
@@ -54,6 +97,8 @@ static inline uint32_t furi_hal_speaker_calculate_compare(float volume) {
 }
 
 void furi_hal_speaker_start(float frequency, float volume) {
+    furi_check(furi_hal_speaker_is_mine());
+
     if(volume <= 0) {
         furi_hal_speaker_stop();
         return;
@@ -75,6 +120,7 @@ void furi_hal_speaker_start(float frequency, float volume) {
 }
 
 void furi_hal_speaker_set_volume(float volume) {
+    furi_check(furi_hal_speaker_is_mine());
     if(volume <= 0) {
         furi_hal_speaker_stop();
         return;
@@ -88,6 +134,7 @@ void furi_hal_speaker_set_volume(float volume) {
 }
 
 void furi_hal_speaker_stop() {
+    furi_check(furi_hal_speaker_is_mine());
     LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
     LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER);
 }

+ 65 - 62
firmware/targets/f7/furi_hal/furi_hal_subghz.c

@@ -4,7 +4,6 @@
 #include <furi_hal_region.h>
 #include <furi_hal_version.h>
 #include <furi_hal_rtc.h>
-#include <furi_hal_gpio.h>
 #include <furi_hal_spi.h>
 #include <furi_hal_interrupt.h>
 #include <furi_hal_resources.h>
@@ -17,39 +16,26 @@
 
 #define TAG "FuriHalSubGhz"
 
-/*
- * Uncomment define to enable duplication of
- * IO GO0 CC1101 to an external comb.
- * Debug pin can be assigned
- *      gpio_ext_pc0
- *      gpio_ext_pc1
- *      gpio_ext_pc3
- *      gpio_ext_pb2
- *      gpio_ext_pb3
- *      gpio_ext_pa4
- *      gpio_ext_pa6
- *      gpio_ext_pa7
- * Attention this setting switches pin to output. 
- * Make sure it is not connected directly to power or ground
- */
-
-//#define SUBGHZ_DEBUG_CC1101_PIN gpio_ext_pa7
-#ifdef SUBGHZ_DEBUG_CC1101_PIN
-uint32_t subghz_debug_gpio_buff[2];
-#endif
+static uint32_t furi_hal_subghz_debug_gpio_buff[2];
 
 typedef struct {
     volatile SubGhzState state;
     volatile SubGhzRegulation regulation;
     volatile FuriHalSubGhzPreset preset;
+    const GpioPin* async_mirror_pin;
 } FuriHalSubGhz;
 
 volatile FuriHalSubGhz furi_hal_subghz = {
     .state = SubGhzStateInit,
     .regulation = SubGhzRegulationTxRx,
     .preset = FuriHalSubGhzPresetIDLE,
+    .async_mirror_pin = NULL,
 };
 
+void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) {
+    furi_hal_subghz.async_mirror_pin = pin;
+}
+
 void furi_hal_subghz_init() {
     furi_assert(furi_hal_subghz.state == SubGhzStateInit);
     furi_hal_subghz.state = SubGhzStateIdle;
@@ -372,6 +358,29 @@ void furi_hal_subghz_set_path(FuriHalSubGhzPath path) {
     furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz);
 }
 
+static bool furi_hal_subghz_start_debug() {
+    bool ret = false;
+    if(furi_hal_subghz.async_mirror_pin != NULL) {
+        furi_hal_gpio_init(
+            furi_hal_subghz.async_mirror_pin,
+            GpioModeOutputPushPull,
+            GpioPullNo,
+            GpioSpeedVeryHigh);
+        ret = true;
+    }
+    return ret;
+}
+
+static bool furi_hal_subghz_stop_debug() {
+    bool ret = false;
+    if(furi_hal_subghz.async_mirror_pin != NULL) {
+        furi_hal_gpio_init(
+            furi_hal_subghz.async_mirror_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+        ret = true;
+    }
+    return ret;
+}
+
 volatile uint32_t furi_hal_subghz_capture_delta_duration = 0;
 volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL;
 volatile void* furi_hal_subghz_capture_callback_context = NULL;
@@ -382,9 +391,9 @@ static void furi_hal_subghz_capture_ISR() {
         LL_TIM_ClearFlag_CC1(TIM2);
         furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2);
         if(furi_hal_subghz_capture_callback) {
-#ifdef SUBGHZ_DEBUG_CC1101_PIN
-            furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, false);
-#endif
+            if(furi_hal_subghz.async_mirror_pin != NULL)
+                furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false);
+
             furi_hal_subghz_capture_callback(
                 true,
                 furi_hal_subghz_capture_delta_duration,
@@ -395,9 +404,9 @@ static void furi_hal_subghz_capture_ISR() {
     if(LL_TIM_IsActiveFlag_CC2(TIM2)) {
         LL_TIM_ClearFlag_CC2(TIM2);
         if(furi_hal_subghz_capture_callback) {
-#ifdef SUBGHZ_DEBUG_CC1101_PIN
-            furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, true);
-#endif
+            if(furi_hal_subghz.async_mirror_pin != NULL)
+                furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true);
+
             furi_hal_subghz_capture_callback(
                 false,
                 LL_TIM_IC_GetCaptureCH2(TIM2) - furi_hal_subghz_capture_delta_duration,
@@ -459,10 +468,8 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void*
     LL_TIM_SetCounter(TIM2, 0);
     LL_TIM_EnableCounter(TIM2);
 
-#ifdef SUBGHZ_DEBUG_CC1101_PIN
-    furi_hal_gpio_init(
-        &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
-#endif
+    // Start debug
+    furi_hal_subghz_start_debug();
 
     // Switch to RX
     furi_hal_subghz_rx();
@@ -478,9 +485,8 @@ void furi_hal_subghz_stop_async_rx() {
     FURI_CRITICAL_ENTER();
     LL_TIM_DeInit(TIM2);
 
-#ifdef SUBGHZ_DEBUG_CC1101_PIN
-    furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
-#endif
+    // Stop debug
+    furi_hal_subghz_stop_debug();
 
     FURI_CRITICAL_EXIT();
     furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
@@ -673,30 +679,27 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void*
     LL_TIM_SetCounter(TIM2, 0);
     LL_TIM_EnableCounter(TIM2);
 
-#ifdef SUBGHZ_DEBUG_CC1101_PIN
-    furi_hal_gpio_init(
-        &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
-
-    const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN;
-    subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER;
-    subghz_debug_gpio_buff[1] = gpio->pin;
-
-    dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff;
-    dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR);
-    dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
-    dma_config.Mode = LL_DMA_MODE_CIRCULAR;
-    dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
-    dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
-    dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
-    dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
-    dma_config.NbData = 2;
-    dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
-    dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH;
-    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config);
-    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2);
-    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
-
-#endif
+    // Start debug
+    if(furi_hal_subghz_start_debug()) {
+        const GpioPin* gpio = furi_hal_subghz.async_mirror_pin;
+        furi_hal_subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER;
+        furi_hal_subghz_debug_gpio_buff[1] = gpio->pin;
+
+        dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff;
+        dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR);
+        dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
+        dma_config.Mode = LL_DMA_MODE_CIRCULAR;
+        dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+        dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+        dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
+        dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
+        dma_config.NbData = 2;
+        dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
+        dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH;
+        LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config);
+        LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2);
+        LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
+    }
 
     return true;
 }
@@ -730,10 +733,10 @@ void furi_hal_subghz_stop_async_tx() {
     // Deinitialize GPIO
     furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
 
-#ifdef SUBGHZ_DEBUG_CC1101_PIN
-    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
-    furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
-#endif
+    // Stop debug
+    if(furi_hal_subghz_stop_debug()) {
+        LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
+    }
 
     FURI_CRITICAL_EXIT();
 

+ 47 - 0
firmware/targets/furi_hal_include/furi_hal_speaker.h

@@ -4,16 +4,63 @@
  */
 #pragma once
 
+#include <furi.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+/** Init speaker */
 void furi_hal_speaker_init();
 
+/** Deinit speaker */
+void furi_hal_speaker_deinit();
+
+/** Acquire speaker ownership
+ *
+ * @warning    You must acquire speaker ownership before use
+ *
+ * @param      timeout  Timeout during which speaker ownership must be acquired
+ *
+ * @return     bool  returns true on success
+ */
+FURI_WARN_UNUSED bool furi_hal_speaker_acquire(uint32_t timeout);
+
+/** Release speaker ownership
+ *
+ * @warning    You must release speaker ownership after use
+ */
+void furi_hal_speaker_release();
+
+/** Check current process speaker ownership
+ *
+ * @warning    always returns true if called from ISR
+ *
+ * @return     bool returns true if process owns speaker
+ */
+bool furi_hal_speaker_is_mine();
+
+/** Play a note
+ *
+ * @warning    no ownership check if called from ISR
+ *
+ * @param      frequency  The frequency
+ * @param      volume     The volume
+ */
 void furi_hal_speaker_start(float frequency, float volume);
 
+/** Set volume
+ *
+ * @warning    no ownership check if called from ISR
+ *
+ * @param      volume  The volume
+ */
 void furi_hal_speaker_set_volume(float volume);
 
+/** Stop playback
+ *
+ * @warning    no ownership check if called from ISR
+ */
 void furi_hal_speaker_stop();
 
 #ifdef __cplusplus

+ 21 - 11
firmware/targets/furi_hal_include/furi_hal_subghz.h

@@ -9,6 +9,7 @@
 #include <stdint.h>
 #include <stddef.h>
 #include <toolbox/level_duration.h>
+#include <furi_hal_gpio.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -34,9 +35,9 @@ typedef enum {
 /** Switchable Radio Paths */
 typedef enum {
     FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */
-    FuriHalSubGhzPath433, /**< Center Frquency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */
-    FuriHalSubGhzPath315, /**< Center Frquency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */
-    FuriHalSubGhzPath868, /**< Center Frquency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */
+    FuriHalSubGhzPath433, /**< Center Frequency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */
+    FuriHalSubGhzPath315, /**< Center Frequency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */
+    FuriHalSubGhzPath868, /**< Center Frequency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */
 } FuriHalSubGhzPath;
 
 /** SubGhz state */
@@ -60,8 +61,17 @@ typedef enum {
     SubGhzRegulationTxRx, /**TxRx*/
 } SubGhzRegulation;
 
+/* Mirror RX/TX async modulation signal to specified pin
+ *
+ * @warning    Configures pin to output mode. Make sure it is not connected
+ *             directly to power or ground.
+ *
+ * @param[in]  pin   pointer to the gpio pin structure or NULL to disable
+ */
+void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin);
+
 /** Initialize and switch to power save mode Used by internal API-HAL
- * initalization routine Can be used to reinitialize device to safe state and
+ * initialization routine Can be used to reinitialize device to safe state and
  * send it to sleep
  */
 void furi_hal_subghz_init();
@@ -105,13 +115,13 @@ void furi_hal_subghz_load_patable(const uint8_t data[8]);
  */
 void furi_hal_subghz_write_packet(const uint8_t* data, uint8_t size);
 
-/** Check if recieve pipe is not empty
+/** Check if receive pipe is not empty
  *
  * @return     true if not empty
  */
 bool furi_hal_subghz_rx_pipe_not_empty();
 
-/** Check if recieved data crc is valid
+/** Check if received data crc is valid
  *
  * @return     true if valid
  */
@@ -132,7 +142,7 @@ void furi_hal_subghz_flush_rx();
  */
 void furi_hal_subghz_flush_tx();
 
-/** Shutdown Issue spwd command
+/** Shutdown Issue SPWD command
  * @warning    registers content will be lost
  */
 void furi_hal_subghz_shutdown();
@@ -146,7 +156,7 @@ void furi_hal_subghz_reset();
  */
 void furi_hal_subghz_idle();
 
-/** Switch to Recieve
+/** Switch to Receive
  */
 void furi_hal_subghz_rx();
 
@@ -172,7 +182,7 @@ uint8_t furi_hal_subghz_get_lqi();
  *
  * @param      value  frequency in Hz
  *
- * @return     true if frequncy is valid, otherwise false
+ * @return     true if frequency is valid, otherwise false
  */
 bool furi_hal_subghz_is_frequency_valid(uint32_t value);
 
@@ -181,7 +191,7 @@ bool furi_hal_subghz_is_frequency_valid(uint32_t value);
  *
  * @param      value  frequency in Hz
  *
- * @return     real frequency in herz
+ * @return     real frequency in Hz
  */
 uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value);
 
@@ -189,7 +199,7 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value);
  *
  * @param      value  frequency in Hz
  *
- * @return     real frequency in herz
+ * @return     real frequency in Hz
  */
 uint32_t furi_hal_subghz_set_frequency(uint32_t value);
 

+ 4 - 0
furi/core/common_defines.h

@@ -11,6 +11,10 @@ extern "C" {
 
 #include <cmsis_compiler.h>
 
+#ifndef FURI_WARN_UNUSED
+#define FURI_WARN_UNUSED __attribute__((warn_unused_result))
+#endif
+
 #ifndef FURI_IS_IRQ_MASKED
 #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U)
 #endif