diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 4022a1d25..a3ad98dad 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.13,, +Version,+,7.20,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2066,6 +2066,14 @@ Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, siz Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" +Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" +Function,-,pulse_reader_free,void,PulseReader* +Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" +Function,-,pulse_reader_samples,uint32_t,PulseReader* +Function,-,pulse_reader_set_bittime,void,"PulseReader*, uint32_t" +Function,-,pulse_reader_set_timebase,void,"PulseReader*, PulseReaderUnit" +Function,-,pulse_reader_start,void,PulseReader* +Function,-,pulse_reader_stop,void,PulseReader* Function,-,putc,int,"int, FILE*" Function,-,putc_unlocked,int,"int, FILE*" Function,-,putchar,int,int diff --git a/lib/SConscript b/lib/SConscript index 60ffabfa9..44e79a653 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -4,6 +4,7 @@ env.Append( LINT_SOURCES=[ "lib/app-scened-template", "lib/digital_signal", + "lib/pulse_reader", "lib/drivers", "lib/flipper_format", "lib/infrared", diff --git a/lib/misc.scons b/lib/misc.scons index 91ad276a0..1f951000e 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -3,6 +3,7 @@ Import("env") env.Append( CPPPATH=[ "#/lib/digital_signal", + "#/lib/pulse_reader", "#/lib/fnv1a_hash", "#/lib/heatshrink", "#/lib/micro-ecc", @@ -25,6 +26,7 @@ sources = [] libs_recurse = [ "digital_signal", + "pulse_reader", "micro-ecc", "one_wire", "u8g2", diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 805a34e0d..eb7329f05 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -627,7 +627,7 @@ void nfc_worker_emulate_nfcv(NfcWorker* nfc_worker) { nfcv_emu_init(nfc_data, nfcv_data); while(nfc_worker->state == NfcWorkerStateNfcVEmulate) { - if(nfcv_emu_loop(nfc_data, nfcv_data, 1000)) { + if(nfcv_emu_loop(nfc_data, nfcv_data, 50)) { if(nfc_worker->callback) { nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); } diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c index 9ef0172a9..2e35a5a98 100644 --- a/lib/nfc/protocols/nfcv.c +++ b/lib/nfc/protocols/nfcv.c @@ -8,10 +8,6 @@ #include #include #include -#include -#include -#include -#include #include "nfcv.h" #include "nfc_util.h" @@ -154,8 +150,11 @@ bool nfcv_read_card( /* emulation part */ -static const uint32_t clocks_in_ms = 64 * 1000; -static const uint32_t bit_time = 64 * 9.44f; +#define F_SC 13560000 /* MHz */ +#define PULSE_DURATION_PS (128*1000000000000/F_SC) /* ps */ + + +PulseReader *reader_signal = NULL; DigitalSignal* nfcv_resp_pulse_32 = NULL; DigitalSignal* nfcv_resp_unmod = NULL; @@ -565,17 +564,6 @@ void nfcv_emu_handle_packet(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, ui } } -#define COUNT(x) ((sizeof(x))/(sizeof(x[0]))) -uint32_t nfcv_timer_buffer_src[32]; -uint32_t nfcv_timer_buffer[1024]; -volatile uint32_t nfcv_gpio_calls = 0; - -void nfcv_gpio_cb(void* ctx) { - UNUSED(ctx); - - nfcv_gpio_calls++; -} - void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { nfcv_emu_alloc(); rfal_platform_spi_acquire(); @@ -596,65 +584,26 @@ void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { FURI_LOG_D(TAG, " Privacy pass: 0x%08lX", nfcv_read_be(nfcv_data->sub_data.slix_l.key_privacy, 4)); FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix_l.privacy ? "ON" : "OFF"); - memset(nfcv_timer_buffer_src, 0xEE, sizeof(nfcv_timer_buffer_src)); - memset(nfcv_timer_buffer, 0xFA, sizeof(nfcv_timer_buffer)); - - /* configure DMA to read from a timer peripheral */ - LL_DMA_InitTypeDef dma_config = {}; - dma_config.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) &(TIM2->CNT); - dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - dma_config.MemoryOrM2MDstAddress = (uint32_t) nfcv_timer_buffer; - dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - dma_config.Mode = LL_DMA_MODE_CIRCULAR; - dma_config.NbData = COUNT(nfcv_timer_buffer); /* executes LL_DMA_SetDataLength */ - dma_config.PeriphRequest = LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */ - dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; - - /* now set up DMA with these settings */ - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4); - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_4, &dma_config); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_4); - - /* make some noise on the counter */ - LL_TIM_DisableCounter(TIM2); - 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); - - /* make sure request generation is disabled before modifying registers */ - LL_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0); - /* generator 0 gets fed by EXTI_LINE4 */ - LL_DMAMUX_SetRequestSignalID(NULL, LL_DMAMUX_REQ_GEN_0, LL_DMAMUX_REQ_GEN_EXTI_LINE4); - /* trigger on any edge */ - 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(&gpio_spi_r_miso, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh, GpioAltFnUnused); - + /* allocate a 512 edge buffer, more than enough */ + reader_signal = pulse_reader_alloc(&gpio_spi_r_miso, 512); + /* timebase shall be 1ps */ + pulse_reader_set_timebase(reader_signal, PulseReaderUnitPicosecond); + /* and configure to already calculate the number of bits */ + pulse_reader_set_bittime(reader_signal, PULSE_DURATION_PS); + pulse_reader_start(reader_signal); } void nfcv_emu_deinit() { furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); rfal_platform_spi_release(); nfcv_emu_free(); + + pulse_reader_free(reader_signal); } bool nfcv_emu_loop(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, uint32_t timeout_ms) { bool ret = false; - uint32_t next_sleep = DWT->CYCCNT + (timeout_ms * clocks_in_ms); - uint32_t timeout = 0; - uint32_t last_change = 0; - uint32_t edges_received = 0; - uint32_t frame_state = NFCV_FRAME_STATE_SOF1; uint32_t periods_previous = 0; uint8_t frame_payload[128]; @@ -662,221 +611,140 @@ bool nfcv_emu_loop(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, uint32_t ti uint32_t byte_value = 0; uint32_t bits_received = 0; char reset_reason[128]; - - bool prev = furi_hal_gpio_read(&gpio_spi_r_miso); - - bool in_critical = false; - FURI_CRITICAL_DEFINE(); - + bool wait_for_pulse = false; while(true) { - bool state = furi_hal_gpio_read(&gpio_spi_r_miso); - uint32_t cur_time = DWT->CYCCNT; + uint32_t periods = pulse_reader_receive(reader_signal, timeout_ms * 1000); - if(state != prev) { - uint32_t delta = cur_time - last_change; - uint32_t periods = (delta + bit_time/2) / bit_time; - - last_change = cur_time; - prev = state; - next_sleep = cur_time + (timeout_ms * clocks_in_ms); - - /* start edge counting on first rising edge */ - if(0 && (state || edges_received)) { - edges_received++; - - /* ignore periods which are too long, might happen on field start */ - if(periods > 1024) { - continue; - } - - switch(frame_state) { - case NFCV_FRAME_STATE_SOF1: - /* got a rising edge, was it one period? */ - if(state) { - if(periods == 1) { - FURI_CRITICAL_ENTER_ADV(); - in_critical = true; - timeout = cur_time + bit_time * 16; - frame_state = NFCV_FRAME_STATE_SOF2; - } else { - snprintf(reset_reason, sizeof(reset_reason), "SOF: Expected 1 period, got %lu", periods); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - } - break; - - case NFCV_FRAME_STATE_SOF2: - /* waiting for the second low period, telling us about coding */ - if(!state) { - timeout = cur_time + bit_time * 16; - if(periods == 6) { - frame_state = NFCV_FRAME_STATE_CODING_256; - periods_previous = 0; - } else if(periods == 4) { - frame_state = NFCV_FRAME_STATE_CODING_4; - periods_previous = 2; - } else { - snprintf(reset_reason, sizeof(reset_reason), "SOF: Expected 4/6 periods, got %lu", periods); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - } - break; - - case NFCV_FRAME_STATE_CODING_256: - if(!state) { - timeout = cur_time + bit_time * 1024; - if(periods_previous > periods) { - snprintf(reset_reason, sizeof(reset_reason), "1oo256: Missing %lu periods from previous symbol, got %lu", periods_previous, periods); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - /* previous symbol left us with some pulse periods */ - periods -= periods_previous; - - if(periods > 512) { - snprintf(reset_reason, sizeof(reset_reason), "1oo256: %lu periods is too much", periods); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - if(periods == 2) { - frame_state = NFCV_FRAME_STATE_EOF; - break; - } - - periods_previous = 512 - (periods + 1); - byte_value = (periods - 1) / 2; - frame_payload[frame_pos++] = (uint8_t)byte_value; - - } else { - if(periods != 1) { - snprintf(reset_reason, sizeof(reset_reason), "1oo256: Expected a single low pulse"); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - } - break; - - case NFCV_FRAME_STATE_CODING_4: - /* evaluate high periods on falling edge */ - if(!state) { - timeout = cur_time + bit_time * 16; - if(periods_previous > periods) { - snprintf(reset_reason, sizeof(reset_reason), "1oo4: Missing %lu periods from previous symbol, got %lu", periods_previous, periods); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - /* previous symbol left us with some pulse periods */ - periods -= periods_previous; - periods_previous = 0; - - byte_value >>= 2; - bits_received += 2; - - if(periods == 1) { - byte_value |= 0x00 << 6; - periods_previous = 6; - } else if(periods == 3) { - byte_value |= 0x01 << 6; - periods_previous = 4; - } else if(periods == 5) { - byte_value |= 0x02 << 6; - periods_previous = 2; - } else if(periods == 7) { - byte_value |= 0x03 << 6; - periods_previous = 0; - } else if(periods == 2) { - frame_state = NFCV_FRAME_STATE_EOF; - break; - } else { - snprintf(reset_reason, sizeof(reset_reason), "1oo4: Expected 1/3/5/7 low pulses, but got %lu", periods); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - if(bits_received >= 8) { - frame_payload[frame_pos++] = (uint8_t)byte_value; - bits_received = 0; - } - } else { - if(periods != 1) { - snprintf(reset_reason, sizeof(reset_reason), "1oo4: Expected a single low pulse"); - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - } - break; - } - } + if(periods == PULSE_READER_NO_EDGE) { + break; } + if(wait_for_pulse) { + wait_for_pulse = false; + if(periods != 1) { + snprintf(reset_reason, sizeof(reset_reason), "SOF: Expected a single low pulse in state %lu, but got %lu", frame_state, periods); + frame_state = NFCV_FRAME_STATE_RESET; + } + continue; + } + + switch(frame_state) { + case NFCV_FRAME_STATE_SOF1: + if(periods == 1) { + frame_state = NFCV_FRAME_STATE_SOF2; + } else { + frame_state = NFCV_FRAME_STATE_SOF1; + break; + } + break; + + case NFCV_FRAME_STATE_SOF2: + /* waiting for the second low period, telling us about coding */ + if(periods == 6) { + frame_state = NFCV_FRAME_STATE_CODING_256; + periods_previous = 0; + wait_for_pulse = true; + } else if(periods == 4) { + frame_state = NFCV_FRAME_STATE_CODING_4; + periods_previous = 2; + wait_for_pulse = true; + } else { + //snprintf(reset_reason, sizeof(reset_reason), "SOF: Expected 4/6 periods, got %lu", periods); + frame_state = NFCV_FRAME_STATE_SOF1; + } + break; + + case NFCV_FRAME_STATE_CODING_256: + + if(periods_previous > periods) { + snprintf(reset_reason, sizeof(reset_reason), "1oo256: Missing %lu periods from previous symbol, got %lu", periods_previous, periods); + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + + if(periods > 512) { + snprintf(reset_reason, sizeof(reset_reason), "1oo256: %lu periods is too much", periods); + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } + + periods_previous = 512 - (periods + 1); + byte_value = (periods - 1) / 2; + frame_payload[frame_pos++] = (uint8_t)byte_value; + + wait_for_pulse = true; + + break; + + case NFCV_FRAME_STATE_CODING_4: + if(periods_previous > periods) { + snprintf(reset_reason, sizeof(reset_reason), "1oo4: Missing %lu periods from previous symbol, got %lu", periods_previous, periods); + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + periods_previous = 0; + + byte_value >>= 2; + bits_received += 2; + + if(periods == 1) { + byte_value |= 0x00 << 6; + periods_previous = 6; + } else if(periods == 3) { + byte_value |= 0x01 << 6; + periods_previous = 4; + } else if(periods == 5) { + byte_value |= 0x02 << 6; + periods_previous = 2; + } else if(periods == 7) { + byte_value |= 0x03 << 6; + periods_previous = 0; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } else { + snprintf(reset_reason, sizeof(reset_reason), "1oo4: Expected 1/3/5/7 low pulses, but got %lu", periods); + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + if(bits_received >= 8) { + frame_payload[frame_pos++] = (uint8_t)byte_value; + bits_received = 0; + } + wait_for_pulse = true; + break; + } + /* post-state-machine cleanup and reset */ if(frame_state == NFCV_FRAME_STATE_RESET) { - timeout = 0; - edges_received = 0; frame_state = NFCV_FRAME_STATE_SOF1; - FURI_CRITICAL_EXIT(); - printf("Reset state machine, reason: %s\r\n", reset_reason); - in_critical = false; - furi_delay_ms(50); + + FURI_LOG_D(TAG, "Resetting state machine, reason: '%s'", reset_reason); } else if(frame_state == NFCV_FRAME_STATE_EOF) { - FURI_CRITICAL_EXIT(); - in_critical = false; break; } - - /* no edges detected */ - if(timeout && cur_time > timeout) { - break; - } - - /* might exit early on overflows. guess thats okay. */ - if(cur_time > next_sleep) { - break; - } - } - - if(in_critical) { - FURI_CRITICAL_EXIT(); - in_critical = false; } if(frame_state == NFCV_FRAME_STATE_EOF) { - - furi_hal_gpio_write(&gpio_spi_r_mosi, false); - furi_delay_us(10); - furi_hal_gpio_write(&gpio_spi_r_mosi, true); - furi_delay_us(10); - furi_hal_gpio_write(&gpio_spi_r_mosi, false); - furi_delay_us(10); - furi_hal_gpio_write(&gpio_spi_r_mosi, true); - furi_delay_us(10); - furi_hal_gpio_write(&gpio_spi_r_mosi, false); - + /* we know that this code uses TIM2, so stop pulse reader */ + pulse_reader_stop(reader_signal); nfcv_emu_handle_packet(nfc_data, nfcv_data, frame_payload, frame_pos); + pulse_reader_start(reader_signal); ret = true; } - printf("edges_received: %lu\r\n", edges_received); - printf("nfcv_gpio_calls: %lu\r\n", nfcv_gpio_calls); - printf("nfcv_timer_buffer: %ld", LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_4)); - - uint32_t prev_timer = 0; - for(uint32_t pos = 0; pos < COUNT(nfcv_timer_buffer) - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_4); pos++) { - if((pos % 4) == 0) { - printf("\r\n"); - } - printf(" 0x%08lX ", (nfcv_timer_buffer[pos] - prev_timer)); - - prev_timer = nfcv_timer_buffer[pos]; - } - printf("\r\n"); - //furi_delay_ms(100); - - return ret; } diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h index be5b090f4..25a8550bd 100644 --- a/lib/nfc/protocols/nfcv.h +++ b/lib/nfc/protocols/nfcv.h @@ -4,6 +4,7 @@ #include #include +#include #include "nfc_util.h" #include diff --git a/lib/pulse_reader/pulse_reader.c b/lib/pulse_reader/pulse_reader.c new file mode 100644 index 000000000..25add2caa --- /dev/null +++ b/lib/pulse_reader/pulse_reader.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pulse_reader.h" + + +#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->dma_channel = LL_DMA_CHANNEL_4; + signal->gpio = gpio; + signal->size = size; + signal->timer_value = 0; + signal->pos = 0; + + signal->unit = PulseReaderUnitPicosecond; + signal->bit_time = 1; + + return signal; +} + +void pulse_reader_set_timebase(PulseReader* signal, PulseReaderUnit unit) { + signal->unit = unit; +} + +void pulse_reader_set_bittime(PulseReader* signal, uint32_t bit_time) { + signal->bit_time = bit_time; +} + +void pulse_reader_free(PulseReader* signal) { + free(signal->timer_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_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0); + LL_TIM_DisableCounter(TIM2); +} + +void pulse_reader_start(PulseReader* signal) { + + memset(signal->timer_buffer, 0x00, signal->size * sizeof(uint32_t)); + + /* configure DMA to read from a timer peripheral */ + LL_DMA_InitTypeDef dma_config = {}; + dma_config.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) &(TIM2->CNT); + dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + dma_config.MemoryOrM2MDstAddress = (uint32_t) signal->timer_buffer; + dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.Mode = LL_DMA_MODE_CIRCULAR; + dma_config.NbData = signal->size; /* executes LL_DMA_SetDataLength */ + dma_config.PeriphRequest = LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */ + dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; + + /* start counter */ + LL_TIM_DisableCounter(TIM2); + 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); + + /* make sure request generation is disabled before modifying registers */ + LL_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0); + /* 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, GpioPullNo, GpioSpeedVeryHigh, GpioAltFnUnused); + + /* capture current timer */ + signal->pos = 0; + signal->start_level = furi_hal_gpio_read(signal->gpio); + signal->timer_value = TIM2->CNT; + + /* now set up DMA with these settings */ + LL_DMA_DisableChannel(DMA1, signal->dma_channel); + LL_DMA_Init(DMA1, signal->dma_channel, &dma_config); + LL_DMA_EnableChannel(DMA1, signal->dma_channel); +} + +uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us) { + + uint32_t start_time = DWT->CYCCNT; + uint32_t timeout_ticks = timeout_us * (F_CPU/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; + + signal->timer_value = signal->timer_buffer[signal->pos]; + signal->pos++; + signal->pos %= signal->size; + + uint32_t delta_unit = delta * signal->unit; + uint32_t bits = (delta_unit + signal->bit_time / 2) / signal->bit_time; + + return bits; + } + + /* check for timeout */ + uint32_t elapsed = DWT->CYCCNT - start_time; + + if(elapsed > timeout_ticks) { + return PULSE_READER_NO_EDGE; + } + + furi_delay_ms(0); + + } while(true); +} diff --git a/lib/pulse_reader/pulse_reader.h b/lib/pulse_reader/pulse_reader.h new file mode 100644 index 000000000..1042fab89 --- /dev/null +++ b/lib/pulse_reader/pulse_reader.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PULSE_READER_NO_EDGE 0xFFFFFFFFUL +#define F_CPU 64000000UL + +/** + * unit of the edge durations to return + */ +typedef enum { + PulseReaderUnit64MHz = 1, + PulseReaderUnitPicosecond = 15625 +} PulseReaderUnit; + + +typedef struct { + bool start_level; + uint32_t* timer_buffer; + uint32_t size; + uint32_t pos; + uint32_t timer_value; + PulseReaderUnit unit; + uint32_t bit_time; + uint32_t dma_channel; + const GpioPin* gpio; +} PulseReader; + + +/** Allocate a PulseReader object + * + * Allocates memory for a ringbuffer and initalizes the object + * + * @param[in] gpio the GPIO to use. will get configured as input. + * @param[in] size number of edges to buffer + */ +PulseReader* pulse_reader_alloc(const GpioPin* gpio, uint32_t size); + + +/** Free a PulseReader object + * + * Frees all memory of the given object + * + * @param[in] signal previously allocated PulseReader object. + */ +void pulse_reader_free(PulseReader* signal); + + +/** Start signal capturing + * + * Initializes DMA1, TIM2 and DMAMUX_REQ_GEN_0 to automatically capture timer values + * + * @param[in] signal previously allocated PulseReader object. + */ +void pulse_reader_start(PulseReader* signal); + +/** Stop signal capturing + * + * Frees DMA1, TIM2 and DMAMUX_REQ_GEN_0 + * + * @param[in] signal previously allocated PulseReader object. + */ +void pulse_reader_stop(PulseReader* signal); + + +/** Recevie a sample from ringbuffer + * + * Waits for the specified time until a new edge gets detected. + * If not configured otherwise, the pulse duration will be in picosecond resolution. + * If a bittime was configured, the return value will contain the properly rounded + * number of bit times measured. + * + * @param[in] signal previously allocated PulseReader object. + * @param[in] timeout_us time to wait for a signal [µs] + * + * @returns the scaled value of the pulse duration + */ +uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us); + + +/** Get available samples + * + * Get the number of available samples in the ringbuffer + * + * @param[in] signal previously allocated PulseReader object. + * + * @returns the number of samples in buffer + */ +uint32_t pulse_reader_samples(PulseReader* signal); + + +/** Set timebase + * + * Set the timebase to be used when returning pulse duration. + * + * @param[in] signal previously allocated PulseReader object. + * @param[in] unit PulseReaderUnit64MHz or PulseReaderUnitPicosecond + */ +void pulse_reader_set_timebase(PulseReader* signal, PulseReaderUnit unit); + + + +/** Set bit time + * + * Set the number of timebase units per bit. + * When set, the pulse_reader_receive() will return an already rounded + * bit count value instead of the raw duration. + * + * Set to 1 to return duration again. + * + * @param[in] signal previously allocated PulseReader object. + * @param[in] bit_time + */ +void pulse_reader_set_bittime(PulseReader* signal, uint32_t bit_time); + + +#ifdef __cplusplus +} +#endif