-
Notifications
You must be signed in to change notification settings - Fork 81
GPIO Signals
One approach is to use furi_hal_gpio_write
to set the logic level of a pin. You can then delay using furi_delay_ms
or furi_delay_us
for the appropriate time until you transition the logic level again. One challenge with this approach is to remember that the instructions also take some amount of time, so delays in the us may not be accurate. Also, your process could get interrupted, so you may want to request the scheduler to not switch away from your process using furi_kernel_lock
.
In this example, we use configure pin A7 as OutputPushPull. calling gpio_write with true
will cause the pin to be 3.3 volts; while writing a false
will cause the pin to be 0.0 volts. We create 20 pulses, and we wait 150ms between each toggle. 150+150 = 300ms, which is a frequency of 3.333 Hz with a duty cycle of 50%.
#include <furi_hal.h>
void bit_bang_with_gpio_write() {
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
for(int i = 0; i < 20; i++) {
furi_hal_gpio_write(&gpio_ext_pa7, true);
furi_delay_ms(150);
furi_hal_gpio_write(&gpio_ext_pa7, false);
furi_delay_ms(150);
}
// Uninit GPIO
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullNo, GpioSpeedVeryHigh);
}
Another approach is to use write the port's BSRR
register to set the logic level of a pin. This is similar to the previous example; except we use a register to set (GPIO_BSRR_BS0_Pos
) or reset (GPIO_BSRR_BR0_Pos
) the logic level. In general, it's recommended what you use furi_hal_gpio_write
instead of directly manipulating registers. Later when we learn about DMA, knowing that there is a memory address &gpio_ext_pa7.port->BSRR
that you can write values to (gpio_ext_pa7.pin << GPIO_BSRR_BS0_Pos
, gpio_ext_pa7.pin << GPIO_BSRR_BR0_Pos
) can be helpful.
#include <furi_hal.h>
void bit_bang_with_register_write() {
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
for(int i = 0; i < 20; i++) {
gpio_ext_pa7.port->BSRR = gpio_ext_pa7.pin << GPIO_BSRR_BS0_Pos;
furi_delay_ms(150);
gpio_ext_pa7.port->BSRR = gpio_ext_pa7.pin << GPIO_BSRR_BR0_Pos;
furi_delay_ms(150);
}
// Uninit GPIO
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullNo, GpioSpeedVeryHigh);
}
There is a nice library if you want to output a pulse-width modulation (PWM) signal at a given frequency (in Hertz) and duty cycle. This API is easy to use, and it automatically initializes the GPIO pin A7 for output. Internally this uses TIM1
to generate the signal on CH1N
using GpioAltFn1TIM1
. This API allows you to start a PWM signal on pin A7 and then your code can do other things without having to worry about the pin. The disadvantage of this API is the frequency is an uint32_t, so you can only set integer amounts (like 3Hz or 4Hz) instead of some fractional speed like 3.333 Hz.
#include <furi.h>
#include <furi_hal_pwm.h>
void furi_hal_pwm() {
furi_hal_pwm_start(FuriHalPwmOutputIdTim1PA7, 4, 50); // Pin A7, 4 Hz, 50% duty cycle
furi_delay_ms(5000); // Wait 5 seconds
furi_hal_pwm_stop(FuriHalPwmOutputIdTim1PA7); // Turn off pin A7.
}
You can use a Timer API to control GPIO. The Timer has Alternate functions that can be tied to particular GPIO pins. It is also possible to use interrupts (and the end of the timer) or Direct Memory Access (DMA) to toggle a GPIO pin, which may be required if the GPIO pin doesn't have a timer associated with it.
Pin A7 : GpioAltFn1TIM1 : TIM1, CH1N
Below is an example of using a timer (TIM1) with CH1N to control pin A7 in Toggle mode. The Flipper Zero runs with an internal clock at 64000000 Hz. We divide that frequency using DIV1
, giving us the same 64000000 Hz. We then trigger with a prescaler of 64000-1, so every 64000 input we get one pulse. This means we have 1000 pulses per second (1 kHz). We set an Autoreload to 150-1 (so every 150 pulses, which is 150ms) we have an event. We set the event to OCMODE_TOGGLE so we toggle between HIGH and LOW output on CH1N. The result is a toggle every 150ms, so 150ms+150ms = 300ms, which is a frequency of 3.333 Hz with a duty cycle of 50%.
#include <stm32wbxx_ll_dma.h>
void toggle() {
furi_hal_gpio_init_ex(
&gpio_ext_pa7, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn1TIM1);
furi_hal_bus_enable(FuriHalBusTIM1);
LL_TIM_InitTypeDef tim_init = {
.CounterMode = LL_TIM_COUNTERMODE_UP,
.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1,
.Prescaler = 64000 - 1,
.RepetitionCounter = 0,
.Autoreload = 150 - 1,
};
LL_TIM_Init(TIM1, &tim_init);
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_EnableARRPreload(TIM1);
LL_TIM_OC_InitTypeDef tim_oc_init = {
.OCMode = LL_TIM_OCMODE_TOGGLE,
.OCNPolarity = LL_TIM_OCPOLARITY_HIGH,
.OCNState = LL_TIM_OCSTATE_ENABLE,
.OCNIdleState = LL_TIM_OCIDLESTATE_LOW,
// In this example, we are only using CH1N so we don't need to set these...
//.OCPolarity = LL_TIM_OCPOLARITY_HIGH,
//.OCState = LL_TIM_OCSTATE_ENABLE,
//.OCIdleState = LL_TIM_OCIDLESTATE_LOW,
};
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH1, &tim_oc_init);
LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableAllOutputs(TIM1);
}
void toggle_done() {
// Uninit GPIO
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullNo, GpioSpeedVeryHigh);
LL_TIM_DisableAllOutputs(TIM1);
LL_TIM_DisableCounter(TIM1);
furi_hal_bus_disable(FuriHalBusTIM1);
}
Below is an example of using a timer (TIM1) with CH1N to control pin A7 in PWM mode. The Flipper Zero runs with an internal clock at 64000000 Hz. We divide that frequency using DIV1
, giving us the same 64000000 Hz. We then trigger with a prescaler of 64000-1, so every 64000 input we get one pulse. This means we have 1000 pulses per second (1 kHz). We set an Autoreload to 300-1 (so every 300 pulses, which is 300ms) we have an event (the end of our PWM signal). We set the event to OCMODE_PWM1 so we have a PWM output on CH1N. We set the CompareValue to 1/2 of our Autoreload value, so when the count matches the CompareValue (on the 150th pulse) we will go from HIGH to LOW for the remainder of the cycle. The result is a PWM of 300ms, which is a frequency of 3.333 Hz with a duty cycle of 50% (due to our CompareValue being 50% of the Autoreload.)
#include <stm32wbxx_ll_dma.h>
void pwm() {
furi_hal_gpio_init_ex(
&gpio_ext_pa7, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn1TIM1);
furi_hal_bus_enable(FuriHalBusTIM1);
LL_TIM_InitTypeDef tim_init = {
.CounterMode = LL_TIM_COUNTERMODE_UP,
.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1,
.Prescaler = 64000 - 1,
.RepetitionCounter = 0,
.Autoreload = 300 - 1,
};
LL_TIM_Init(TIM1, &tim_init);
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_EnableARRPreload(TIM1);
LL_TIM_OC_InitTypeDef tim_oc_init = {
.CompareValue = tim_init.Autoreload / 2,
.OCMode = LL_TIM_OCMODE_PWM1,
.OCNPolarity = LL_TIM_OCPOLARITY_HIGH,
.OCNState = LL_TIM_OCSTATE_ENABLE,
.OCNIdleState = LL_TIM_OCIDLESTATE_LOW,
// In this example, we are only using CH1N so we don't need to set these...
//.OCPolarity = LL_TIM_OCPOLARITY_HIGH,
//.OCState = LL_TIM_OCSTATE_ENABLE,
//.OCIdleState = LL_TIM_OCIDLESTATE_LOW,
};
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH1, &tim_oc_init);
// EnablePreload for the CompareValue to work...
LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1N);
LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableAllOutputs(TIM1);
}
In this example we use Direct Memory Access (DMA) to update the timer's ARR
register (which is the Autoreload counter). We configure DMA to read the word (32-bit) values from a buffer, incrementing the index, running in a circular mode (when it gets to the end it loops back to the start of the buffer). We write to the AAR
register` and don't increment (so each write is to the Autoreload counter). We do the DMA action each time TIM1 completes.
#include <stm32wbxx_ll_dma.h>
uint32_t durations[] = {100-1, 100-1, 200-1, 200-1, 500-1, 500-1, 1000-1, 1000-1};
void toggle_with_dma_duration() {
LL_DMA_InitTypeDef dma_led_transition_timer = {
.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH,
.PeriphOrM2MSrcAddress = (uint32_t)&TIM1->ARR,
.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT,
.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD,
.MemoryOrM2MDstAddress = (uint32_t)durations,
.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT,
.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD,
.Mode = LL_DMA_MODE_CIRCULAR,
.NbData = COUNT_OF(durations),
.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP,
.Priority = LL_DMA_PRIORITY_HIGH,
};
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_led_transition_timer);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
furi_hal_gpio_init_ex(
&gpio_ext_pa7, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn1TIM1);
furi_hal_bus_enable(FuriHalBusTIM1);
LL_TIM_InitTypeDef tim_init = {
.CounterMode = LL_TIM_COUNTERMODE_UP,
.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1,
.Prescaler = 64000 - 1,
.RepetitionCounter = 0,
.Autoreload = 100 - 1,
};
LL_TIM_Init(TIM1, &tim_init);
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_EnableARRPreload(TIM1);
LL_TIM_OC_InitTypeDef tim_oc_init = {
.OCMode = LL_TIM_OCMODE_TOGGLE,
.OCNPolarity = LL_TIM_OCPOLARITY_HIGH,
.OCNState = LL_TIM_OCSTATE_ENABLE,
.OCNIdleState = LL_TIM_OCIDLESTATE_LOW,
};
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH1, &tim_oc_init);
LL_TIM_EnableAllOutputs(TIM1);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableUpdateEvent(TIM1);
LL_TIM_EnableDMAReq_UPDATE(TIM1);
LL_TIM_GenerateEvent_UPDATE(TIM1);
}
In this example we use Direct Memory Access (DMA) to update the timer's CCR1
register (which is the CompareValue). We configure DMA to read the word (32-bit) values from a buffer, incrementing the index, running in a circular mode (when it gets to the end it loops back to the start of the buffer). We write to the CCR1
register` and don't increment (so each write is to the CompareValue counter). We do the DMA action each time TIM1 completes (each cycle we update the duty cycle).
#include <stm32wbxx_ll_dma.h>
uint32_t duty_cycles[] = {10-1, 20-1, 30-1, 40-1, 50-1, 60-1, 70-1, 80-1, 90-1, 100-1};
void pwm_dma_duty() {
LL_DMA_InitTypeDef dma_led_transition_timer = {
.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH,
.PeriphOrM2MSrcAddress = (uint32_t)&TIM1->CCR1,
.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT,
.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD,
.MemoryOrM2MDstAddress = (uint32_t)duty_cycles,
.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT,
.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD,
.Mode = LL_DMA_MODE_CIRCULAR,
.NbData = COUNT_OF(duty_cycles),
.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP,
.Priority = LL_DMA_PRIORITY_HIGH,
};
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_led_transition_timer);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
furi_hal_gpio_init_ex(
&gpio_ext_pa7, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn1TIM1);
furi_hal_bus_enable(FuriHalBusTIM1);
LL_TIM_InitTypeDef tim_init = {
.CounterMode = LL_TIM_COUNTERMODE_UP,
.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1,
.Prescaler = 64000 - 1,
.RepetitionCounter = 0,
.Autoreload = 100-1,
};
LL_TIM_Init(TIM1, &tim_init);
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_EnableARRPreload(TIM1);
LL_TIM_OC_InitTypeDef tim_oc_init = {
.OCMode = LL_TIM_OCMODE_PWM1,
.CompareValue = 5, // set by DMA
.OCNPolarity = LL_TIM_OCPOLARITY_HIGH,
.OCNState = LL_TIM_OCSTATE_ENABLE,
.OCNIdleState = LL_TIM_OCIDLESTATE_LOW,
};
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH1, &tim_oc_init);
LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); // TIM1_CCR1
LL_TIM_EnableAllOutputs(TIM1);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableUpdateEvent(TIM1);
LL_TIM_EnableDMAReq_UPDATE(TIM1);
LL_TIM_GenerateEvent_UPDATE(TIM1);
}
In this example we use Direct Memory Access (DMA) to update the timer's CCR1
register (which is the CompareValue). We configure DMA to read the word (32-bit) values from a buffer, incrementing the index, running in a circular mode (when it gets to the end it loops back to the start of the buffer). We write to the CCR1
register` and don't increment (so each write is to the CompareValue counter). We do the DMA action each time TIM1 completes (each cycle we update the duty cycle).
The DMA buffer we are reading from is configured in our interrupt routine. At the Half-Transfer (HT) point we fill the inactive buffer with data. At the Transfer-Complete (TC) point we swap buffers that DMA1 CH1 is using.
#include <furi.h>
#include <furi_hal.h>
#include <stm32wbxx_ll_dma.h>
uint32_t buff1[8] = {99, 99, 99, 99, 99, 99, 99, 99};
uint32_t buff2[8] = {0};
volatile bool buff1_is_active = true;
volatile uint32_t delay = 100;
static void dma_isr(void* context) {
UNUSED(context);
if(LL_DMA_IsActiveFlag_HT1(DMA1)) {
LL_DMA_ClearFlag_HT1(DMA1);
delay += 100;
if(delay > 400) {
delay = 100;
}
uint32_t* buff = (!buff1_is_active) ? buff1 : buff2;
for(size_t i = 0; i < COUNT_OF(buff1); i++) {
buff[i] = delay - 1;
}
}
if(LL_DMA_IsActiveFlag_TC1(DMA1)) {
LL_DMA_ClearFlag_TC1(DMA1);
buff1_is_active = !buff1_is_active;
if(buff1_is_active) {
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, (uint32_t)buff1);
} else {
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, (uint32_t)buff2);
}
}
}
void toggle_with_dma_duration_isr_buffer() {
LL_DMA_InitTypeDef dma_led_transition_timer = {
.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH,
.PeriphOrM2MSrcAddress = (uint32_t)&TIM1->ARR,
.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT,
.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD,
.MemoryOrM2MDstAddress = (uint32_t)buff1,
.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT,
.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD,
.Mode = LL_DMA_MODE_CIRCULAR,
.NbData = COUNT_OF(buff1),
.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP,
.Priority = LL_DMA_PRIORITY_HIGH,
};
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_led_transition_timer);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_ClearFlag_HT1(DMA1);
LL_DMA_ClearFlag_TC1(DMA1);
LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdDma1Ch1, 4, dma_isr, NULL);
furi_hal_gpio_init_ex(
&gpio_ext_pa7, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn1TIM1);
furi_hal_bus_enable(FuriHalBusTIM1);
LL_TIM_InitTypeDef tim_init = {
.CounterMode = LL_TIM_COUNTERMODE_UP,
.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1,
.Prescaler = 64000 - 1,
.RepetitionCounter = 0,
.Autoreload = 100 - 1,
};
LL_TIM_Init(TIM1, &tim_init);
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_EnableARRPreload(TIM1);
LL_TIM_OC_InitTypeDef tim_oc_init = {
.OCMode = LL_TIM_OCMODE_TOGGLE,
.OCNPolarity = LL_TIM_OCPOLARITY_HIGH,
.OCNState = LL_TIM_OCSTATE_ENABLE,
.OCNIdleState = LL_TIM_OCIDLESTATE_LOW,
};
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH1, &tim_oc_init);
LL_TIM_EnableAllOutputs(TIM1);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableUpdateEvent(TIM1);
LL_TIM_EnableDMAReq_UPDATE(TIM1);
LL_TIM_GenerateEvent_UPDATE(TIM1);
}
In this example we use Direct Memory Access (DMA) CH1 to update the timer's RCR
register (which is the RepetitionCounter). We configure DMA to read the word (32-bit) values from a buffer, incrementing the index, running in a circular mode (when it gets to the end it loops back to the start of the buffer). We write to the RCR
register` and don't increment (so each write is to the Repetition counter). The RepetitionCounter will decrement every time the Autoreload count is met, but the DMA actions will only fire when the RepetitionCounter is 0 & the Autoreload count is met. This means the counter represents how many cycles to output.
We use DMA CH2 to update the timer's CCMR1
register (which is the state of the OCMode). We configure DMA to read the word value from a buffer, incrementing the index, running in a circular mode. The data read switches between FORCED_INACTIVE (so GPIO PIN is LOW) vs PWM1 mode (so GPIO PIN is PWM signal).
In the below sample we blink 2, 4 and 8 times with a duty cycle of 25% (100 of 400) and a frequency of 1000/400 = 2.5 Hz. We also have off for 2 cycles (800ms) between the blink counts.
uint32_t counts[] = {2, 2, 4, 2, 8, 2};
#define CCMR_OFF LL_TIM_OCMODE_FORCED_INACTIVE
#define CCMR_ON LL_TIM_OCMODE_PWM1
uint32_t enabled[] = {CCMR_OFF, CCMR_ON};
void pwm_dma_repeat_enabled() {
LL_DMA_InitTypeDef dma_led_transition_timer = {
.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH,
.PeriphOrM2MSrcAddress = (uint32_t)&TIM1->RCR,
.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT,
.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD,
.MemoryOrM2MDstAddress = (uint32_t)counts,
.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT,
.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD,
.Mode = LL_DMA_MODE_CIRCULAR,
.NbData = COUNT_OF(counts),
.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP,
.Priority = LL_DMA_PRIORITY_HIGH,
};
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_led_transition_timer);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_InitTypeDef dma_led_transition_enabled = {
.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH,
.PeriphOrM2MSrcAddress = (uint32_t)&TIM1->CCMR1,
.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT,
.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD,
.MemoryOrM2MDstAddress = (uint32_t)enabled,
.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT,
.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD,
.Mode = LL_DMA_MODE_CIRCULAR,
.NbData = COUNT_OF(enabled),
.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP,
.Priority = LL_DMA_PRIORITY_HIGH,
};
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_led_transition_enabled);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
furi_hal_gpio_init_ex(
&gpio_ext_pa7, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn1TIM1);
furi_hal_bus_enable(FuriHalBusTIM1);
LL_TIM_InitTypeDef tim_init = {
.CounterMode = LL_TIM_COUNTERMODE_UP,
.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1,
.Prescaler = 64000 - 1,
.RepetitionCounter = 0,
.Autoreload = 400-1,
};
LL_TIM_Init(TIM1, &tim_init);
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_EnableARRPreload(TIM1);
LL_TIM_OC_InitTypeDef tim_oc_init = {
.OCMode = LL_TIM_OCMODE_FORCED_INACTIVE, // LL_TIM_OCMODE_PWM1,
.CompareValue = 100-1,
.OCNPolarity = LL_TIM_OCPOLARITY_HIGH,
.OCNState = LL_TIM_OCSTATE_ENABLE,
.OCNIdleState = LL_TIM_OCIDLESTATE_LOW,
};
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH1, &tim_oc_init);
LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); // TIM1_CCR1
LL_TIM_EnableAllOutputs(TIM1);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableUpdateEvent(TIM1);
LL_TIM_EnableDMAReq_UPDATE(TIM1);
LL_TIM_GenerateEvent_UPDATE(TIM1);
}