mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-27 06:50:21 +00:00
e1c6e78b2e
* added DigitalSequence to chain multiple DigitalSignals added PulseReader for hardware assisted digital signal sampling * added send_time option to start a signal at a specific DWT->CYCCNT value * fixed linter errors and undone function renaming * fixed renaming * flagged functions in api_symbols.csv * allow gpio field to stay uninitialized in digital_signal_prepare_arr() * fix test cases to match (expected) implementation * pulse_reader: build as static library Signed-off-by: g3gg0.de <git@g3gg0.de> * fix starting level detection in pulse_reader * added unit test for pulse_reader * change pulse reader test timings to 1, 10 and 100 ms * fine tuned timings for pulse_reader test * pulse_reader_stop now deinits GPIO as recommended by @gornekich * ran format_py * pulse_reader: remove from API, allow to link with faps Signed-off-by: g3gg0.de <git@g3gg0.de> * remove unit test for pulse_reader again * pulse_reader: add call to set GPIO pull direction * make structures private, add C implementation of digital_signal_update_dma() * digital_signal/pulse_reader: allow parameters for free to be NULL * digital_signal: show unoptimized and optimized code for digital_signal_update_dma() next to each other * pulse_reader: further optimize assembly code * digital_signal: reduce code complexity of digital_signal_update_dma() by only reconfiguring DMA2 * digital_signal: remove assembly code, limiting the performance but increasing portability * added recovery if the timer already expired * digital_signal: fix memory leak * digital_signal: keep lock until all DMA transfers have finished * DigitalSequence: fix issues with concatenation of same levels and spurious bit flips * DigitalSignal: use cyclic DMA buffer for sequences * update api_symbols.csv * Update api_symbols.csv for f18 target * Patches from @gornekich to fix linter warnings. * Remove some redundant if checks * Remove some magic numbers and reformat. * Remove forced terminating edge. Signed-off-by: g3gg0.de <git@g3gg0.de> Co-authored-by: gornekich <n.gorbadey@gmail.com> Co-authored-by: Tiernan Messmer <tiernan.messmer@gmail.com> Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
233 lines
8.6 KiB
C
233 lines
8.6 KiB
C
#include "pulse_reader.h"
|
|
|
|
#include <limits.h>
|
|
#include <furi.h>
|
|
#include <furi_hal.h>
|
|
#include <furi_hal_gpio.h>
|
|
|
|
#include <stm32wbxx_ll_dma.h>
|
|
#include <stm32wbxx_ll_dmamux.h>
|
|
#include <stm32wbxx_ll_tim.h>
|
|
#include <stm32wbxx_ll_exti.h>
|
|
|
|
struct PulseReader {
|
|
uint32_t* timer_buffer;
|
|
uint32_t* gpio_buffer;
|
|
uint32_t size;
|
|
uint32_t pos;
|
|
uint32_t timer_value;
|
|
uint32_t gpio_value;
|
|
uint32_t gpio_mask;
|
|
uint32_t unit_multiplier;
|
|
uint32_t unit_divider;
|
|
uint32_t bit_time;
|
|
uint32_t dma_channel;
|
|
const GpioPin* gpio;
|
|
GpioPull pull;
|
|
LL_DMA_InitTypeDef dma_config_timer;
|
|
LL_DMA_InitTypeDef dma_config_gpio;
|
|
};
|
|
|
|
#define GPIO_PIN_MAP(pin, prefix) \
|
|
(((pin) == (LL_GPIO_PIN_0)) ? prefix##0 : \
|
|
((pin) == (LL_GPIO_PIN_1)) ? prefix##1 : \
|
|
((pin) == (LL_GPIO_PIN_2)) ? prefix##2 : \
|
|
((pin) == (LL_GPIO_PIN_3)) ? prefix##3 : \
|
|
((pin) == (LL_GPIO_PIN_4)) ? prefix##4 : \
|
|
((pin) == (LL_GPIO_PIN_5)) ? prefix##5 : \
|
|
((pin) == (LL_GPIO_PIN_6)) ? prefix##6 : \
|
|
((pin) == (LL_GPIO_PIN_7)) ? prefix##7 : \
|
|
((pin) == (LL_GPIO_PIN_8)) ? prefix##8 : \
|
|
((pin) == (LL_GPIO_PIN_9)) ? prefix##9 : \
|
|
((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \
|
|
((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \
|
|
((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \
|
|
((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \
|
|
((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \
|
|
prefix##15)
|
|
|
|
#define GET_DMAMUX_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_DMAMUX_REQ_GEN_EXTI_LINE)
|
|
|
|
PulseReader* pulse_reader_alloc(const GpioPin* gpio, uint32_t size) {
|
|
PulseReader* signal = malloc(sizeof(PulseReader));
|
|
signal->timer_buffer = malloc(size * sizeof(uint32_t));
|
|
signal->gpio_buffer = malloc(size * sizeof(uint32_t));
|
|
signal->dma_channel = LL_DMA_CHANNEL_4;
|
|
signal->gpio = gpio;
|
|
signal->pull = GpioPullNo;
|
|
signal->size = size;
|
|
signal->timer_value = 0;
|
|
signal->pos = 0;
|
|
|
|
pulse_reader_set_timebase(signal, PulseReaderUnit64MHz);
|
|
pulse_reader_set_bittime(signal, 1);
|
|
|
|
signal->dma_config_timer.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
|
|
signal->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->CNT);
|
|
signal->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
|
signal->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
|
signal->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)signal->timer_buffer;
|
|
signal->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
|
signal->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
|
signal->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR;
|
|
signal->dma_config_timer.PeriphRequest =
|
|
LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */
|
|
signal->dma_config_timer.Priority = LL_DMA_PRIORITY_VERYHIGH;
|
|
|
|
signal->dma_config_gpio.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
|
|
signal->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
|
signal->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
|
signal->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
|
signal->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
|
signal->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR;
|
|
signal->dma_config_gpio.PeriphRequest =
|
|
LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */
|
|
signal->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH;
|
|
|
|
return signal;
|
|
}
|
|
|
|
void pulse_reader_set_timebase(PulseReader* signal, PulseReaderUnit unit) {
|
|
switch(unit) {
|
|
case PulseReaderUnit64MHz:
|
|
signal->unit_multiplier = 1;
|
|
signal->unit_divider = 1;
|
|
break;
|
|
case PulseReaderUnitPicosecond:
|
|
signal->unit_multiplier = 15625;
|
|
signal->unit_divider = 1;
|
|
break;
|
|
case PulseReaderUnitNanosecond:
|
|
signal->unit_multiplier = 15625;
|
|
signal->unit_divider = 1000;
|
|
break;
|
|
case PulseReaderUnitMicrosecond:
|
|
signal->unit_multiplier = 15625;
|
|
signal->unit_divider = 1000000;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void pulse_reader_set_bittime(PulseReader* signal, uint32_t bit_time) {
|
|
signal->bit_time = bit_time;
|
|
}
|
|
|
|
void pulse_reader_set_pull(PulseReader* signal, GpioPull pull) {
|
|
signal->pull = pull;
|
|
}
|
|
|
|
void pulse_reader_free(PulseReader* signal) {
|
|
furi_assert(signal);
|
|
|
|
free(signal->timer_buffer);
|
|
free(signal->gpio_buffer);
|
|
free(signal);
|
|
}
|
|
|
|
uint32_t pulse_reader_samples(PulseReader* signal) {
|
|
uint32_t dma_pos = signal->size - (uint32_t)LL_DMA_GetDataLength(DMA1, signal->dma_channel);
|
|
|
|
return ((signal->pos + signal->size) - dma_pos) % signal->size;
|
|
}
|
|
|
|
void pulse_reader_stop(PulseReader* signal) {
|
|
LL_DMA_DisableChannel(DMA1, signal->dma_channel);
|
|
LL_DMA_DisableChannel(DMA1, signal->dma_channel + 1);
|
|
LL_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0);
|
|
LL_TIM_DisableCounter(TIM2);
|
|
furi_hal_gpio_init_simple(signal->gpio, GpioModeAnalog);
|
|
}
|
|
|
|
void pulse_reader_start(PulseReader* signal) {
|
|
/* configure DMA to read from a timer peripheral */
|
|
signal->dma_config_timer.NbData = signal->size;
|
|
|
|
signal->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (signal->gpio->port->IDR);
|
|
signal->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)signal->gpio_buffer;
|
|
signal->dma_config_gpio.NbData = signal->size;
|
|
|
|
/* start counter */
|
|
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
|
|
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
|
|
LL_TIM_SetPrescaler(TIM2, 0);
|
|
LL_TIM_SetAutoReload(TIM2, 0xFFFFFFFF);
|
|
LL_TIM_SetCounter(TIM2, 0);
|
|
LL_TIM_EnableCounter(TIM2);
|
|
|
|
/* generator 0 gets fed by EXTI_LINEn */
|
|
LL_DMAMUX_SetRequestSignalID(
|
|
NULL, LL_DMAMUX_REQ_GEN_0, GET_DMAMUX_EXTI_LINE(signal->gpio->pin));
|
|
/* trigger on rising edge of the interrupt */
|
|
LL_DMAMUX_SetRequestGenPolarity(NULL, LL_DMAMUX_REQ_GEN_0, LL_DMAMUX_REQ_GEN_POL_RISING);
|
|
/* now enable request generation again */
|
|
LL_DMAMUX_EnableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0);
|
|
|
|
/* we need the EXTI to be configured as interrupt generating line, but no ISR registered */
|
|
furi_hal_gpio_init_ex(
|
|
signal->gpio, GpioModeInterruptRiseFall, signal->pull, GpioSpeedVeryHigh, GpioAltFnUnused);
|
|
|
|
/* capture current timer */
|
|
signal->pos = 0;
|
|
signal->timer_value = TIM2->CNT;
|
|
signal->gpio_mask = signal->gpio->pin;
|
|
signal->gpio_value = signal->gpio->port->IDR & signal->gpio_mask;
|
|
|
|
/* now set up DMA with these settings */
|
|
LL_DMA_Init(DMA1, signal->dma_channel, &signal->dma_config_timer);
|
|
LL_DMA_Init(DMA1, signal->dma_channel + 1, &signal->dma_config_gpio);
|
|
LL_DMA_EnableChannel(DMA1, signal->dma_channel);
|
|
LL_DMA_EnableChannel(DMA1, signal->dma_channel + 1);
|
|
}
|
|
|
|
uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us) {
|
|
uint32_t start_time = DWT->CYCCNT;
|
|
uint32_t timeout_ticks = timeout_us * (F_TIM2 / 1000000);
|
|
|
|
do {
|
|
/* get the DMA's next write position by reading "remaining length" register */
|
|
uint32_t dma_pos =
|
|
signal->size - (uint32_t)LL_DMA_GetDataLength(DMA1, signal->dma_channel);
|
|
|
|
/* the DMA has advanced in the ringbuffer */
|
|
if(dma_pos != signal->pos) {
|
|
uint32_t delta = signal->timer_buffer[signal->pos] - signal->timer_value;
|
|
uint32_t last_gpio_value = signal->gpio_value;
|
|
|
|
signal->gpio_value = signal->gpio_buffer[signal->pos];
|
|
|
|
/* check if the GPIO really toggled. if not, we lost an edge :( */
|
|
if(((last_gpio_value ^ signal->gpio_value) & signal->gpio_mask) != signal->gpio_mask) {
|
|
signal->gpio_value ^= signal->gpio_mask;
|
|
return PULSE_READER_LOST_EDGE;
|
|
}
|
|
signal->timer_value = signal->timer_buffer[signal->pos];
|
|
|
|
signal->pos++;
|
|
signal->pos %= signal->size;
|
|
|
|
uint32_t delta_unit = 0;
|
|
|
|
/* probably larger values, so choose a wider data type */
|
|
if(signal->unit_divider > 1) {
|
|
delta_unit =
|
|
(uint32_t)((uint64_t)delta * (uint64_t)signal->unit_multiplier / signal->unit_divider);
|
|
} else {
|
|
delta_unit = delta * signal->unit_multiplier;
|
|
}
|
|
|
|
/* if to be scaled to bit times, save a few instructions. should be faster */
|
|
if(signal->bit_time > 1) {
|
|
return (delta_unit + signal->bit_time / 2) / signal->bit_time;
|
|
}
|
|
|
|
return delta_unit;
|
|
}
|
|
|
|
/* check for timeout */
|
|
uint32_t elapsed = DWT->CYCCNT - start_time;
|
|
|
|
if(elapsed > timeout_ticks) {
|
|
return PULSE_READER_NO_EDGE;
|
|
}
|
|
} while(true);
|
|
}
|