diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 82b054478..5a83dfbd7 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -1,7 +1,10 @@ #include "infrared_app_i.h" +#include + #include #include +#include #include #define TAG "InfraredApp" @@ -9,6 +12,14 @@ #define INFRARED_TX_MIN_INTERVAL_MS (50U) #define INFRARED_TASK_STACK_SIZE (2048UL) +#define INFRARED_SETTINGS_PATH INT_PATH(".infrared.settings") +#define INFRARED_SETTINGS_VERSION (0) +#define INFRARED_SETTINGS_MAGIC (0x1F) + +typedef struct { + uint8_t tx_pin; +} InfraredSettings; + static const NotificationSequence* infrared_notification_sequences[InfraredNotificationMessageCount] = { &sequence_success, @@ -181,6 +192,12 @@ static InfraredApp* infrared_alloc(void) { infrared->popup = popup_alloc(); view_dispatcher_add_view(view_dispatcher, InfraredViewPopup, popup_get_view(infrared->popup)); + infrared->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + view_dispatcher, + InfraredViewVariableList, + variable_item_list_get_view(infrared->var_item_list)); + infrared->view_stack = view_stack_alloc(); view_dispatcher_add_view( view_dispatcher, InfraredViewStack, view_stack_get_view(infrared->view_stack)); @@ -237,6 +254,9 @@ static void infrared_free(InfraredApp* infrared) { view_dispatcher_remove_view(view_dispatcher, InfraredViewPopup); popup_free(infrared->popup); + view_dispatcher_remove_view(view_dispatcher, InfraredViewVariableList); + variable_item_list_free(infrared->var_item_list); + view_dispatcher_remove_view(view_dispatcher, InfraredViewStack); view_stack_free(infrared->view_stack); @@ -431,6 +451,60 @@ void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, . va_end(args); } +void infrared_set_tx_pin(InfraredApp* infrared, FuriHalInfraredTxPin tx_pin) { + if(tx_pin < FuriHalInfraredTxPinMax) { + furi_hal_infrared_set_tx_output(tx_pin); + } else { + FuriHalInfraredTxPin tx_pin_detected = furi_hal_infrared_detect_tx_output(); + furi_hal_infrared_set_tx_output(tx_pin_detected); + if(tx_pin_detected != FuriHalInfraredTxPinInternal) { + infrared_enable_otg(infrared, true); + } + } + + infrared->app_state.tx_pin = tx_pin; +} + +void infrared_enable_otg(InfraredApp* infrared, bool enable) { + if(enable) { + furi_hal_power_enable_otg(); + } else { + furi_hal_power_disable_otg(); + } + infrared->app_state.is_otg_enabled = enable; +} + +static void infrared_load_settings(InfraredApp* infrared) { + InfraredSettings settings = {0}; + + if(!saved_struct_load( + INFRARED_SETTINGS_PATH, + &settings, + sizeof(InfraredSettings), + INFRARED_SETTINGS_MAGIC, + INFRARED_SETTINGS_VERSION)) { + FURI_LOG_D(TAG, "Failed to load settings, using defaults"); + infrared_save_settings(infrared); + } + + infrared_set_tx_pin(infrared, settings.tx_pin); +} + +void infrared_save_settings(InfraredApp* infrared) { + InfraredSettings settings = { + .tx_pin = infrared->app_state.tx_pin, + }; + + if(!saved_struct_save( + INFRARED_SETTINGS_PATH, + &settings, + sizeof(InfraredSettings), + INFRARED_SETTINGS_MAGIC, + INFRARED_SETTINGS_VERSION)) { + FURI_LOG_E(TAG, "Failed to save settings"); + } +} + void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { furi_assert(context); InfraredApp* infrared = context; @@ -471,6 +545,7 @@ void infrared_popup_closed_callback(void* context) { int32_t infrared_app(void* p) { InfraredApp* infrared = infrared_alloc(); + infrared_load_settings(infrared); infrared_make_app_folder(infrared); bool is_remote_loaded = false; @@ -513,6 +588,9 @@ int32_t infrared_app(void* p) { view_dispatcher_run(infrared->view_dispatcher); + infrared_set_tx_pin(infrared, FuriHalInfraredTxPinInternal); + infrared_enable_otg(infrared, false); infrared_free(infrared); + return 0; } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 1c074323a..1635fa0dc 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -4,6 +4,8 @@ */ #pragma once +#include + #include #include #include @@ -18,13 +20,13 @@ #include #include #include +#include #include #include #include #include - #include #include "infrared_app.h" @@ -82,11 +84,13 @@ typedef struct { bool is_learning_new_remote; /**< Learning new remote or adding to an existing one. */ bool is_debug_enabled; /**< Whether to enable or disable debugging features. */ bool is_transmitting; /**< Whether a signal is currently being transmitted. */ + bool is_otg_enabled; /**< Whether OTG power (external 5V) is enabled. */ InfraredEditTarget edit_target : 8; /**< Selected editing target (a remote or a button). */ InfraredEditMode edit_mode : 8; /**< Selected editing operation (rename or delete). */ int32_t current_button_index; /**< Selected button index (move destination). */ int32_t prev_button_index; /**< Previous button index (move source). */ uint32_t last_transmit_time; /**< Lat time a signal was transmitted. */ + FuriHalInfraredTxPin tx_pin; } InfraredAppState; /** @@ -110,6 +114,7 @@ struct InfraredApp { DialogEx* dialog_ex; /**< Standard view for displaying dialogs. */ ButtonMenu* button_menu; /**< Custom view for interacting with IR remotes. */ Popup* popup; /**< Standard view for displaying messages. */ + VariableItemList* var_item_list; /**< Standard view for displaying menus of choice items. */ ViewStack* view_stack; /**< Standard view for displaying stacked interfaces. */ InfraredDebugView* debug_view; /**< Custom view for displaying debug information. */ @@ -138,6 +143,7 @@ typedef enum { InfraredViewDialogEx, InfraredViewButtonMenu, InfraredViewPopup, + InfraredViewVariableList, InfraredViewStack, InfraredViewDebugView, InfraredViewMove, @@ -273,6 +279,32 @@ void infrared_play_notification_message( void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) _ATTRIBUTE((__format__(__printf__, 2, 3))); +/** + * @brief Set which pin will be used to transmit infrared signals. + * + * Setting tx_pin to InfraredTxPinInternal will enable transmission via + * the built-in infrared LEDs. + * + * @param[in] infrared pointer to the application instance. + * @param[in] tx_pin pin to be used for signal transmission. + */ +void infrared_set_tx_pin(InfraredApp* infrared, FuriHalInfraredTxPin tx_pin); + +/** + * @brief Enable or disable 5V at the GPIO pin 1. + * + * @param[in] infrared pointer to the application instance. + * @param[in] enable boolean value corresponding to OTG state (true = enable, false = disable) + */ +void infrared_enable_otg(InfraredApp* infrared, bool enable); + +/** + * @brief Save current settings to a file. + * + * @param[in] infrared pointer to the application instance. + */ +void infrared_save_settings(InfraredApp* infrared); + /** * @brief Common received signal callback. * diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index b53e52a2f..02d9a276f 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -22,6 +22,9 @@ enum InfraredCustomEventType { InfraredCustomEventTypeRpcButtonPressIndex, InfraredCustomEventTypeRpcButtonRelease, InfraredCustomEventTypeRpcSessionClose, + + InfraredCustomEventTypeGpioTxPinChanged, + InfraredCustomEventTypeGpioOtgChanged, }; #pragma pack(push, 1) diff --git a/applications/main/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h index 27ef2f3b1..976bb424c 100644 --- a/applications/main/infrared/scenes/infrared_scene_config.h +++ b/applications/main/infrared/scenes/infrared_scene_config.h @@ -19,6 +19,7 @@ ADD_SCENE(infrared, universal_tv, UniversalTV) ADD_SCENE(infrared, universal_ac, UniversalAC) ADD_SCENE(infrared, universal_audio, UniversalAudio) ADD_SCENE(infrared, universal_projector, UniversalProjector) +ADD_SCENE(infrared, gpio_settings, GpioSettings) ADD_SCENE(infrared, debug, Debug) ADD_SCENE(infrared, error_databases, ErrorDatabases) ADD_SCENE(infrared, rpc, Rpc) diff --git a/applications/main/infrared/scenes/infrared_scene_gpio_settings.c b/applications/main/infrared/scenes/infrared_scene_gpio_settings.c new file mode 100644 index 000000000..07782c08b --- /dev/null +++ b/applications/main/infrared/scenes/infrared_scene_gpio_settings.c @@ -0,0 +1,102 @@ +#include "../infrared_app_i.h" + +static const char* infrared_scene_gpio_settings_pin_text[] = { + "Flipper", + "2 (A7)", + "Detect", +}; + +static const char* infrared_scene_gpio_settings_otg_text[] = { + "OFF", + "ON", +}; + +static void infrared_scene_gpio_settings_pin_change_callback(VariableItem* item) { + InfraredApp* infrared = variable_item_get_context(item); + const uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, infrared_scene_gpio_settings_pin_text[index]); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, + infrared_custom_event_pack(InfraredCustomEventTypeGpioTxPinChanged, index)); +} + +static void infrared_scene_gpio_settings_otg_change_callback(VariableItem* item) { + InfraredApp* infrared = variable_item_get_context(item); + const uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, infrared_scene_gpio_settings_otg_text[index]); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, + infrared_custom_event_pack(InfraredCustomEventTypeGpioOtgChanged, index)); +} + +static void infrared_scene_gpio_settings_init(InfraredApp* infrared) { + VariableItemList* var_item_list = infrared->var_item_list; + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + var_item_list, + "Signal Output", + COUNT_OF(infrared_scene_gpio_settings_pin_text), + infrared_scene_gpio_settings_pin_change_callback, + infrared); + + value_index = infrared->app_state.tx_pin; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, infrared_scene_gpio_settings_pin_text[value_index]); + + item = variable_item_list_add( + var_item_list, + "5V on GPIO", + COUNT_OF(infrared_scene_gpio_settings_otg_text), + infrared_scene_gpio_settings_otg_change_callback, + infrared); + + if(infrared->app_state.tx_pin < FuriHalInfraredTxPinMax) { + value_index = infrared->app_state.is_otg_enabled; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text( + item, infrared_scene_gpio_settings_otg_text[value_index]); + } else { + variable_item_set_values_count(item, 1); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, "Auto"); + } +} + +void infrared_scene_gpio_settings_on_enter(void* context) { + InfraredApp* infrared = context; + infrared_scene_gpio_settings_init(infrared); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewVariableList); +} + +bool infrared_scene_gpio_settings_on_event(void* context, SceneManagerEvent event) { + bool consumed = false; + + InfraredApp* infrared = context; + + if(event.type == SceneManagerEventTypeCustom) { + const uint16_t custom_event_type = infrared_custom_event_get_type(event.event); + const uint16_t custom_event_value = infrared_custom_event_get_value(event.event); + + if(custom_event_type == InfraredCustomEventTypeGpioTxPinChanged) { + infrared_set_tx_pin(infrared, custom_event_value); + variable_item_list_reset(infrared->var_item_list); + infrared_scene_gpio_settings_init(infrared); + } else if(custom_event_type == InfraredCustomEventTypeGpioOtgChanged) { + infrared_enable_otg(infrared, custom_event_value); + } + + consumed = true; + } + + return consumed; +} + +void infrared_scene_gpio_settings_on_exit(void* context) { + InfraredApp* infrared = context; + variable_item_list_reset(infrared->var_item_list); + infrared_save_settings(infrared); +} diff --git a/applications/main/infrared/scenes/infrared_scene_start.c b/applications/main/infrared/scenes/infrared_scene_start.c index 0e23bb7b8..3d57389a3 100644 --- a/applications/main/infrared/scenes/infrared_scene_start.c +++ b/applications/main/infrared/scenes/infrared_scene_start.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexUniversalRemotes, SubmenuIndexLearnNewRemote, SubmenuIndexSavedRemotes, + SubmenuIndexGpioSettings, SubmenuIndexDebug }; @@ -35,6 +36,12 @@ void infrared_scene_start_on_enter(void* context) { SubmenuIndexSavedRemotes, infrared_scene_start_submenu_callback, infrared); + submenu_add_item( + submenu, + "GPIO Settings", + SubmenuIndexGpioSettings, + infrared_scene_start_submenu_callback, + infrared); if(infrared->app_state.is_debug_enabled) { submenu_add_item( @@ -60,19 +67,19 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(scene_manager, InfraredSceneStart, submenu_index); if(submenu_index == SubmenuIndexUniversalRemotes) { scene_manager_next_scene(scene_manager, InfraredSceneUniversal); - consumed = true; } else if(submenu_index == SubmenuIndexLearnNewRemote) { infrared->app_state.is_learning_new_remote = true; scene_manager_next_scene(scene_manager, InfraredSceneLearn); - consumed = true; } else if(submenu_index == SubmenuIndexSavedRemotes) { furi_string_set(infrared->file_path, INFRARED_APP_FOLDER); scene_manager_next_scene(scene_manager, InfraredSceneRemoteList); - consumed = true; + } else if(submenu_index == SubmenuIndexGpioSettings) { + scene_manager_next_scene(scene_manager, InfraredSceneGpioSettings); } else if(submenu_index == SubmenuIndexDebug) { scene_manager_next_scene(scene_manager, InfraredSceneDebug); - consumed = true; } + + consumed = true; } return consumed; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e80417b8e..95879cde6 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.2,, +Version,+,60.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index a4a6cb250..e0b7f6818 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.2,, +Version,+,60.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1324,7 +1324,9 @@ Function,+,furi_hal_infrared_async_tx_set_signal_sent_isr_callback,void,"FuriHal Function,+,furi_hal_infrared_async_tx_start,void,"uint32_t, float" Function,+,furi_hal_infrared_async_tx_stop,void, Function,+,furi_hal_infrared_async_tx_wait_termination,void, +Function,+,furi_hal_infrared_detect_tx_output,FuriHalInfraredTxPin, Function,+,furi_hal_infrared_is_busy,_Bool, +Function,+,furi_hal_infrared_set_tx_output,void,FuriHalInfraredTxPin Function,-,furi_hal_init,void, Function,-,furi_hal_init_early,void, Function,-,furi_hal_interrupt_init,void, diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index 6ace2ed7f..bbb00198e 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -9,12 +10,6 @@ #include #include -// #define INFRARED_TX_DEBUG - -#if defined INFRARED_TX_DEBUG -#define gpio_infrared_tx gpio_ext_pa7 -#endif - #define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200 #define INFRARED_POLARITY_SHIFT 1 @@ -81,10 +76,16 @@ typedef enum { InfraredStateMAX, } InfraredState; +static FuriHalInfraredTxPin infrared_tx_output = FuriHalInfraredTxPinInternal; static volatile InfraredState furi_hal_infrared_state = InfraredStateIdle; static InfraredTimTx infrared_tim_tx; static InfraredTimRx infrared_tim_rx; +static const GpioPin* infrared_tx_pins[FuriHalInfraredTxPinMax] = { + [FuriHalInfraredTxPinInternal] = &gpio_infrared_tx, + [FuriHalInfraredTxPinExtPA7] = &gpio_ext_pa7, +}; + static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift); static void furi_hal_infrared_async_tx_free_resources(void); static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift); @@ -352,27 +353,31 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc LL_TIM_SetAutoReload( INFRARED_DMA_TIMER, __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(INFRARED_DMA_TIMER), freq)); -#if defined INFRARED_TX_DEBUG - LL_TIM_OC_SetCompareCH1( - INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); - LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); - /* LL_TIM_OCMODE_PWM2 set by DMA */ - LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); - LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); - LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N); - LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER); -#else - LL_TIM_OC_SetCompareCH3( - INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); - LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); - /* LL_TIM_OCMODE_PWM2 set by DMA */ - LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); - LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH); - LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); - LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N); - LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER); -#endif + + if(infrared_tx_output == FuriHalInfraredTxPinInternal) { + LL_TIM_OC_SetCompareCH3( + INFRARED_DMA_TIMER, + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); + /* LL_TIM_OCMODE_PWM2 set by DMA */ + LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); + LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); + LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N); + LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER); + } else if(infrared_tx_output == FuriHalInfraredTxPinExtPA7) { + LL_TIM_OC_SetCompareCH1( + INFRARED_DMA_TIMER, + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); + /* LL_TIM_OCMODE_PWM2 set by DMA */ + LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); + LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N); + LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER); + } + LL_TIM_DisableMasterSlaveMode(INFRARED_DMA_TIMER); LL_TIM_EnableAllOutputs(INFRARED_DMA_TIMER); LL_TIM_DisableIT_UPDATE(INFRARED_DMA_TIMER); @@ -381,11 +386,13 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { LL_DMA_InitTypeDef dma_config = {0}; -#if defined INFRARED_TX_DEBUG - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR1); -#else - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR2); -#endif + + if(infrared_tx_output == FuriHalInfraredTxPinInternal) { + dma_config.PeriphOrM2MSrcAddress = (uint32_t)(&(INFRARED_DMA_TIMER->CCMR2)); + } else if(infrared_tx_output == FuriHalInfraredTxPinExtPA7) { + dma_config.PeriphOrM2MSrcAddress = (uint32_t)(&(INFRARED_DMA_TIMER->CCMR1)); + } + dma_config.MemoryOrM2MDstAddress = (uint32_t)NULL; dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.Mode = LL_DMA_MODE_NORMAL; @@ -582,7 +589,8 @@ static void furi_hal_infrared_async_tx_free_resources(void) { (furi_hal_infrared_state == InfraredStateIdle) || (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); - furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + furi_hal_gpio_init( + infrared_tx_pins[infrared_tx_output], GpioModeAnalog, GpioPullDown, GpioSpeedLow); furi_hal_interrupt_set_isr(INFRARED_DMA_CH1_IRQ, NULL, NULL); furi_hal_interrupt_set_isr(INFRARED_DMA_CH2_IRQ, NULL, NULL); @@ -643,10 +651,11 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { furi_delay_us(5); LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* DMA -> TIMx_RCR */ furi_delay_us(5); - LL_GPIO_ResetOutputPin( - gpio_infrared_tx.port, gpio_infrared_tx.pin); /* when disable it prevents false pulse */ + + const GpioPin* tx_gpio = infrared_tx_pins[infrared_tx_output]; + LL_GPIO_ResetOutputPin(tx_gpio->port, tx_gpio->pin); /* when disable it prevents false pulse */ furi_hal_gpio_init_ex( - &gpio_infrared_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); + tx_gpio, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); FURI_CRITICAL_ENTER(); LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* TIMx_RCR -> Repetition counter */ @@ -691,3 +700,23 @@ void furi_hal_infrared_async_tx_set_signal_sent_isr_callback( infrared_tim_tx.signal_sent_callback = callback; infrared_tim_tx.signal_sent_context = context; } + +FuriHalInfraredTxPin furi_hal_infrared_detect_tx_output(void) { + for(FuriHalInfraredTxPin pin = FuriHalInfraredTxPinInternal + 1; //-V1008 + pin < FuriHalInfraredTxPinMax; + ++pin) { + const GpioPin* gpio = infrared_tx_pins[pin]; + furi_hal_gpio_init(gpio, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_cortex_delay_us(1000U); + const bool level = furi_hal_gpio_read(gpio); + furi_hal_gpio_init(gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + if(!level) return pin; + } + + return FuriHalInfraredTxPinInternal; +} + +void furi_hal_infrared_set_tx_output(FuriHalInfraredTxPin tx_pin) { + furi_check(tx_pin < FuriHalInfraredTxPinMax); + infrared_tx_output = tx_pin; +} diff --git a/targets/furi_hal_include/furi_hal_infrared.h b/targets/furi_hal_include/furi_hal_infrared.h index ce2e4328e..29f7101c1 100644 --- a/targets/furi_hal_include/furi_hal_infrared.h +++ b/targets/furi_hal_include/furi_hal_infrared.h @@ -16,6 +16,12 @@ extern "C" { #define INFRARED_MAX_FREQUENCY 56000 #define INFRARED_MIN_FREQUENCY 10000 +typedef enum { + FuriHalInfraredTxPinInternal, + FuriHalInfraredTxPinExtPA7, + FuriHalInfraredTxPinMax, +} FuriHalInfraredTxPin; + typedef enum { FuriHalInfraredTxGetDataStateOk, /**< New data obtained */ FuriHalInfraredTxGetDataStateDone, /**< New data obtained, and this is end of package */ @@ -143,6 +149,29 @@ void furi_hal_infrared_async_tx_set_signal_sent_isr_callback( FuriHalInfraredTxSignalSentISRCallback callback, void* context); +/** Detect which pin has an external IR module connected. + * + * External IR modules are detected by enabling a weak pull-up + * on supported pins and testing whether the input is still low. + * + * This method works best on modules that employ a FET with a + * strong pull-down or a BJT for driving IR LEDs. + * + * The module MUST pull the input voltage down to at least 0.9V + * or lower in order for it to be detected. + * + * If no module has been detected, FuriHalInfraredTxPinInternal is returned. + * + * @return numeric identifier of the first pin with a module detected. + */ +FuriHalInfraredTxPin furi_hal_infrared_detect_tx_output(void); + +/** Set which pin will be used to transmit infrared signals. + * + * @param[in] tx_pin pin to be used for signal transmission. + */ +void furi_hal_infrared_set_tx_output(FuriHalInfraredTxPin tx_pin); + #ifdef __cplusplus } #endif