[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>
This commit is contained in:
Skorpionm 2022-12-17 02:20:10 +04:00 committed by GitHub
parent 3681a5478c
commit 2dea6969fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 325 additions and 113 deletions

View File

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

View File

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

View File

@ -5,6 +5,7 @@ enum SubGhzSettingIndex {
SubGhzSettingIndexFrequency, SubGhzSettingIndexFrequency,
SubGhzSettingIndexHopping, SubGhzSettingIndexHopping,
SubGhzSettingIndexModulation, SubGhzSettingIndexModulation,
SubGhzSettingIndexSound,
SubGhzSettingIndexLock, SubGhzSettingIndexLock,
SubGhzSettingIndexRAWThesholdRSSI, SubGhzSettingIndexRAWThesholdRSSI,
}; };
@ -48,6 +49,16 @@ const uint32_t hopping_value[HOPPING_COUNT] = {
SubGhzHopperStateRunnig, 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) { uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) {
furi_assert(context); furi_assert(context);
SubGhz* subghz = 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]; 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) { static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) {
SubGhz* subghz = variable_item_get_context(item); SubGhz* subghz = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(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( variable_item_set_current_value_text(
item, subghz_setting_get_preset_name(subghz->setting, value_index)); 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) != if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
SubGhzCustomEventManagerSet) { SubGhzCustomEventManagerSet) {
variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL); variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL);

View File

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

View File

@ -86,6 +86,7 @@ uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) {
uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx(); furi_hal_subghz_flush_rx();
subghz_speaker_on(subghz);
furi_hal_subghz_rx(); furi_hal_subghz_rx();
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, subghz->txrx->worker); 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_subghz_set_frequency_and_path(frequency);
furi_hal_gpio_write(&gpio_cc1101_g0, false); furi_hal_gpio_write(&gpio_cc1101_g0, false);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
subghz_speaker_on(subghz);
bool ret = furi_hal_subghz_tx(); bool ret = furi_hal_subghz_tx();
subghz->txrx->txrx_state = SubGhzTxRxStateTx; subghz->txrx->txrx_state = SubGhzTxRxStateTx;
return ret; return ret;
@ -119,11 +121,13 @@ void subghz_idle(SubGhz* subghz) {
void subghz_rx_end(SubGhz* subghz) { void subghz_rx_end(SubGhz* subghz) {
furi_assert(subghz); furi_assert(subghz);
furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateRx); furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateRx);
if(subghz_worker_is_running(subghz->txrx->worker)) { if(subghz_worker_is_running(subghz->txrx->worker)) {
subghz_worker_stop(subghz->txrx->worker); subghz_worker_stop(subghz->txrx->worker);
furi_hal_subghz_stop_async_rx(); furi_hal_subghz_stop_async_rx();
} }
furi_hal_subghz_idle(); furi_hal_subghz_idle();
subghz_speaker_off(subghz);
subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; 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, subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path));
} }
subghz_idle(subghz); subghz_idle(subghz);
subghz_speaker_off(subghz);
notification_message(subghz->notifications, &sequence_reset_red); notification_message(subghz->notifications, &sequence_reset_red);
} }
@ -585,3 +590,40 @@ void subghz_hopper_update(SubGhz* subghz) {
subghz_rx(subghz, subghz->txrx->preset->frequency); 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);
}
}
}

View File

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

View File

@ -47,47 +47,51 @@ static int32_t music_player_worker_thread_callback(void* context) {
NoteBlockArray_it_t it; NoteBlockArray_it_t it;
NoteBlockArray_it(it, instance->notes); NoteBlockArray_it(it, instance->notes);
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);
while(instance->should_work) { float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
if(NoteBlockArray_end_p(it)) { float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
NoteBlockArray_it(it, instance->notes); float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm /
furi_delay_ms(10); note_block->duration;
} else { uint32_t dots = note_block->dots;
NoteBlock* note_block = NoteBlockArray_ref(it); while(dots > 0) {
duration += duration / 2;
dots--;
}
uint32_t next_tick = furi_get_tick() + duration;
float volume = instance->volume;
float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; if(instance->callback) {
float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); instance->callback(
float duration = note_block->semitone,
60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration; note_block->dots,
uint32_t dots = note_block->dots; note_block->duration,
while(dots > 0) { 0.0,
duration += duration / 2; instance->callback_context);
dots--; }
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);
} }
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);
} }
}
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; return 0;
} }

View File

@ -150,11 +150,16 @@ void notification_vibro_off() {
} }
void notification_sound_on(float freq, float volume) { 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() { void notification_sound_off() {
furi_hal_speaker_stop(); if(furi_hal_speaker_is_mine()) {
furi_hal_speaker_stop();
furi_hal_speaker_release();
}
} }
// display timer // display timer

View File

@ -1,5 +1,5 @@
entry,status,name,type,params entry,status,name,type,params
Version,+,10.1,, Version,+,11.0,,
Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.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_pin_fails,void,uint32_t
Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t"
Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* 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_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_set_volume,void,float
Function,+,furi_hal_speaker_start,void,"float, float" Function,+,furi_hal_speaker_start,void,"float, float"
Function,+,furi_hal_speaker_stop,void, 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_reset,void,
Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx,void,
Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, 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,uint32_t,uint32_t
Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t
Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath

1 entry status name type params
2 Version + 10.1 11.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
1282 Function + furi_hal_rtc_set_pin_fails void uint32_t
1283 Function + furi_hal_rtc_set_register void FuriHalRtcRegister, uint32_t
1284 Function + furi_hal_rtc_validate_datetime _Bool FuriHalRtcDateTime*
1285 Function + furi_hal_speaker_acquire _Bool uint32_t
1286 Function - furi_hal_speaker_deinit void
1287 Function - furi_hal_speaker_init void
1288 Function + furi_hal_speaker_is_mine _Bool
1289 Function + furi_hal_speaker_release void
1290 Function + furi_hal_speaker_set_volume void float
1291 Function + furi_hal_speaker_start void float, float
1292 Function + furi_hal_speaker_stop void
1320 Function + furi_hal_subghz_reset void
1321 Function + furi_hal_subghz_rx void
1322 Function + furi_hal_subghz_rx_pipe_not_empty _Bool
1323 Function + furi_hal_subghz_set_async_mirror_pin void const GpioPin*
1324 Function + furi_hal_subghz_set_frequency uint32_t uint32_t
1325 Function + furi_hal_subghz_set_frequency_and_path uint32_t uint32_t
1326 Function + furi_hal_subghz_set_path void FuriHalSubGhzPath

View File

@ -1,23 +1,66 @@
#include <furi_hal_speaker.h> #include <furi_hal_speaker.h>
#include <furi_hal_gpio.h> #include <furi_hal_gpio.h>
#include <furi_hal_resources.h> #include <furi_hal_resources.h>
#include <furi_hal_power.h>
#include <stm32wbxx_ll_tim.h> #include <stm32wbxx_ll_tim.h>
#include <furi_hal_cortex.h>
#define TAG "FuriHalSpeaker"
#define FURI_HAL_SPEAKER_TIMER TIM16 #define FURI_HAL_SPEAKER_TIMER TIM16
#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 #define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
#define FURI_HAL_SPEAKER_PRESCALER 500 #define FURI_HAL_SPEAKER_PRESCALER 500
#define FURI_HAL_SPEAKER_MAX_VOLUME 60 #define FURI_HAL_SPEAKER_MAX_VOLUME 60
static FuriMutex* furi_hal_speaker_mutex = NULL;
// #define FURI_HAL_SPEAKER_NEW_VOLUME // #define FURI_HAL_SPEAKER_NEW_VOLUME
void furi_hal_speaker_init() { void furi_hal_speaker_init() {
furi_assert(furi_hal_speaker_mutex == NULL);
furi_hal_speaker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
FURI_CRITICAL_ENTER(); FURI_CRITICAL_ENTER();
LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER); LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER);
FURI_CRITICAL_EXIT(); FURI_CRITICAL_EXIT();
FURI_LOG_I(TAG, "Init OK");
}
furi_hal_gpio_init_ex( void furi_hal_speaker_deinit() {
&gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); 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;
}
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) { 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) { void furi_hal_speaker_start(float frequency, float volume) {
furi_check(furi_hal_speaker_is_mine());
if(volume <= 0) { if(volume <= 0) {
furi_hal_speaker_stop(); furi_hal_speaker_stop();
return; return;
@ -75,6 +120,7 @@ void furi_hal_speaker_start(float frequency, float volume) {
} }
void furi_hal_speaker_set_volume(float volume) { void furi_hal_speaker_set_volume(float volume) {
furi_check(furi_hal_speaker_is_mine());
if(volume <= 0) { if(volume <= 0) {
furi_hal_speaker_stop(); furi_hal_speaker_stop();
return; return;
@ -88,6 +134,7 @@ void furi_hal_speaker_set_volume(float volume) {
} }
void furi_hal_speaker_stop() { void furi_hal_speaker_stop() {
furi_check(furi_hal_speaker_is_mine());
LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER);
} }

View File

@ -4,7 +4,6 @@
#include <furi_hal_region.h> #include <furi_hal_region.h>
#include <furi_hal_version.h> #include <furi_hal_version.h>
#include <furi_hal_rtc.h> #include <furi_hal_rtc.h>
#include <furi_hal_gpio.h>
#include <furi_hal_spi.h> #include <furi_hal_spi.h>
#include <furi_hal_interrupt.h> #include <furi_hal_interrupt.h>
#include <furi_hal_resources.h> #include <furi_hal_resources.h>
@ -17,39 +16,26 @@
#define TAG "FuriHalSubGhz" #define TAG "FuriHalSubGhz"
/* static uint32_t furi_hal_subghz_debug_gpio_buff[2];
* 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
typedef struct { typedef struct {
volatile SubGhzState state; volatile SubGhzState state;
volatile SubGhzRegulation regulation; volatile SubGhzRegulation regulation;
volatile FuriHalSubGhzPreset preset; volatile FuriHalSubGhzPreset preset;
const GpioPin* async_mirror_pin;
} FuriHalSubGhz; } FuriHalSubGhz;
volatile FuriHalSubGhz furi_hal_subghz = { volatile FuriHalSubGhz furi_hal_subghz = {
.state = SubGhzStateInit, .state = SubGhzStateInit,
.regulation = SubGhzRegulationTxRx, .regulation = SubGhzRegulationTxRx,
.preset = FuriHalSubGhzPresetIDLE, .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() { void furi_hal_subghz_init() {
furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_assert(furi_hal_subghz.state == SubGhzStateInit);
furi_hal_subghz.state = SubGhzStateIdle; 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); 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 uint32_t furi_hal_subghz_capture_delta_duration = 0;
volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL; volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL;
volatile void* furi_hal_subghz_capture_callback_context = 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); LL_TIM_ClearFlag_CC1(TIM2);
furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2); furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2);
if(furi_hal_subghz_capture_callback) { if(furi_hal_subghz_capture_callback) {
#ifdef SUBGHZ_DEBUG_CC1101_PIN if(furi_hal_subghz.async_mirror_pin != NULL)
furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, false); furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false);
#endif
furi_hal_subghz_capture_callback( furi_hal_subghz_capture_callback(
true, true,
furi_hal_subghz_capture_delta_duration, furi_hal_subghz_capture_delta_duration,
@ -395,9 +404,9 @@ static void furi_hal_subghz_capture_ISR() {
if(LL_TIM_IsActiveFlag_CC2(TIM2)) { if(LL_TIM_IsActiveFlag_CC2(TIM2)) {
LL_TIM_ClearFlag_CC2(TIM2); LL_TIM_ClearFlag_CC2(TIM2);
if(furi_hal_subghz_capture_callback) { if(furi_hal_subghz_capture_callback) {
#ifdef SUBGHZ_DEBUG_CC1101_PIN if(furi_hal_subghz.async_mirror_pin != NULL)
furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, true); furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true);
#endif
furi_hal_subghz_capture_callback( furi_hal_subghz_capture_callback(
false, false,
LL_TIM_IC_GetCaptureCH2(TIM2) - furi_hal_subghz_capture_delta_duration, 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_SetCounter(TIM2, 0);
LL_TIM_EnableCounter(TIM2); LL_TIM_EnableCounter(TIM2);
#ifdef SUBGHZ_DEBUG_CC1101_PIN // Start debug
furi_hal_gpio_init( furi_hal_subghz_start_debug();
&SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
#endif
// Switch to RX // Switch to RX
furi_hal_subghz_rx(); furi_hal_subghz_rx();
@ -478,9 +485,8 @@ void furi_hal_subghz_stop_async_rx() {
FURI_CRITICAL_ENTER(); FURI_CRITICAL_ENTER();
LL_TIM_DeInit(TIM2); LL_TIM_DeInit(TIM2);
#ifdef SUBGHZ_DEBUG_CC1101_PIN // Stop debug
furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_subghz_stop_debug();
#endif
FURI_CRITICAL_EXIT(); FURI_CRITICAL_EXIT();
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); 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_SetCounter(TIM2, 0);
LL_TIM_EnableCounter(TIM2); LL_TIM_EnableCounter(TIM2);
#ifdef SUBGHZ_DEBUG_CC1101_PIN // Start debug
furi_hal_gpio_init( if(furi_hal_subghz_start_debug()) {
&SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); 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;
const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN; dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff;
subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR);
subghz_debug_gpio_buff[1] = gpio->pin; dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
dma_config.Mode = LL_DMA_MODE_CIRCULAR;
dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff; dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
dma_config.Mode = LL_DMA_MODE_CIRCULAR; dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; dma_config.NbData = 2;
dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH;
dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config);
dma_config.NbData = 2; LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2);
dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
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
return true; return true;
} }
@ -730,10 +733,10 @@ void furi_hal_subghz_stop_async_tx() {
// Deinitialize GPIO // Deinitialize GPIO
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
#ifdef SUBGHZ_DEBUG_CC1101_PIN // Stop debug
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); if(furi_hal_subghz_stop_debug()) {
furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
#endif }
FURI_CRITICAL_EXIT(); FURI_CRITICAL_EXIT();

View File

@ -4,16 +4,63 @@
*/ */
#pragma once #pragma once
#include <furi.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/** Init speaker */
void furi_hal_speaker_init(); 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); 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); void furi_hal_speaker_set_volume(float volume);
/** Stop playback
*
* @warning no ownership check if called from ISR
*/
void furi_hal_speaker_stop(); void furi_hal_speaker_stop();
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -9,6 +9,7 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <toolbox/level_duration.h> #include <toolbox/level_duration.h>
#include <furi_hal_gpio.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -34,9 +35,9 @@ typedef enum {
/** Switchable Radio Paths */ /** Switchable Radio Paths */
typedef enum { typedef enum {
FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */ FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */
FuriHalSubGhzPath433, /**< Center Frquency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */ FuriHalSubGhzPath433, /**< Center Frequency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */
FuriHalSubGhzPath315, /**< Center Frquency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */ FuriHalSubGhzPath315, /**< Center Frequency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */
FuriHalSubGhzPath868, /**< Center Frquency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */ FuriHalSubGhzPath868, /**< Center Frequency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */
} FuriHalSubGhzPath; } FuriHalSubGhzPath;
/** SubGhz state */ /** SubGhz state */
@ -60,8 +61,17 @@ typedef enum {
SubGhzRegulationTxRx, /**TxRx*/ SubGhzRegulationTxRx, /**TxRx*/
} SubGhzRegulation; } 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 /** 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 * send it to sleep
*/ */
void furi_hal_subghz_init(); 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); 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 * @return true if not empty
*/ */
bool furi_hal_subghz_rx_pipe_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 * @return true if valid
*/ */
@ -132,7 +142,7 @@ void furi_hal_subghz_flush_rx();
*/ */
void furi_hal_subghz_flush_tx(); void furi_hal_subghz_flush_tx();
/** Shutdown Issue spwd command /** Shutdown Issue SPWD command
* @warning registers content will be lost * @warning registers content will be lost
*/ */
void furi_hal_subghz_shutdown(); void furi_hal_subghz_shutdown();
@ -146,7 +156,7 @@ void furi_hal_subghz_reset();
*/ */
void furi_hal_subghz_idle(); void furi_hal_subghz_idle();
/** Switch to Recieve /** Switch to Receive
*/ */
void furi_hal_subghz_rx(); void furi_hal_subghz_rx();
@ -172,7 +182,7 @@ uint8_t furi_hal_subghz_get_lqi();
* *
* @param value frequency in Hz * @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); 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 * @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); 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 * @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); uint32_t furi_hal_subghz_set_frequency(uint32_t value);

View File

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