[FL-3762] Configurable Infrared TX output (#3484)

* Implement Gui for the feature
* Implement furi_hal side
* Implement IR module detection
* Fix PVS warnings
* Add comments

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
Georgii Surkov
2024-03-25 11:56:56 +03:00
committed by GitHub
parent 6de2934394
commit adafe96924
10 changed files with 326 additions and 43 deletions

View File

@@ -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,,
1 entry status name type params
2 Version + 60.2 60.3
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h

View File

@@ -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,
1 entry status name type params
2 Version + 60.2 60.3
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/cli/cli.h
1324 Function + furi_hal_infrared_async_tx_start void uint32_t, float
1325 Function + furi_hal_infrared_async_tx_stop void
1326 Function + furi_hal_infrared_async_tx_wait_termination void
1327 Function + furi_hal_infrared_detect_tx_output FuriHalInfraredTxPin
1328 Function + furi_hal_infrared_is_busy _Bool
1329 Function + furi_hal_infrared_set_tx_output void FuriHalInfraredTxPin
1330 Function - furi_hal_init void
1331 Function - furi_hal_init_early void
1332 Function - furi_hal_interrupt_init void

View File

@@ -1,6 +1,7 @@
#include <furi_hal_infrared.h>
#include <furi_hal_interrupt.h>
#include <furi_hal_resources.h>
#include <furi_hal_cortex.h>
#include <furi_hal_bus.h>
#include <stm32wbxx_ll_tim.h>
@@ -9,12 +10,6 @@
#include <furi.h>
#include <math.h>
// #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;
}

View File

@@ -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