"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring. Starring: - @gornekich - NFC refactoring project lead, architect, senior developer - @gsurkov - architect, senior developer - @RebornedBrain - senior developer Supporting roles: - @skotopes, @DrZlo13, @hedger - general architecture advisors, code review - @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance Special thanks: @bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
		
			
				
	
	
		
			382 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			382 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "digital_sequence.h"
 | |
| #include "digital_signal_i.h"
 | |
| 
 | |
| #include <furi.h>
 | |
| #include <furi_hal_bus.h>
 | |
| 
 | |
| #include <stm32wbxx_ll_dma.h>
 | |
| #include <stm32wbxx_ll_tim.h>
 | |
| 
 | |
| /**
 | |
|  * To enable debug output on an additional pin, set DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN to the required
 | |
|  * GpioPin variable. It can be passed at compile time via the --extra-define fbt switch.
 | |
|  * NOTE: This pin must be on the same GPIO port as the main pin.
 | |
|  *
 | |
|  * Example:
 | |
|  * ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3
 | |
|  */
 | |
| 
 | |
| #define TAG "DigitalSequence"
 | |
| 
 | |
| /* Special value used to indicate the end of DMA ring buffer. */
 | |
| #define DIGITAL_SEQUENCE_TIMER_MAX 0xFFFFFFFFUL
 | |
| 
 | |
| /* Time to wait in loops before returning */
 | |
| #define DIGITAL_SEQUENCE_LOCK_WAIT_MS 10UL
 | |
| #define DIGITAL_SEQUENCE_LOCK_WAIT_TICKS (DIGITAL_SEQUENCE_LOCK_WAIT_MS * 1000 * 64)
 | |
| 
 | |
| #define DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE 2
 | |
| 
 | |
| /* Maximum capacity of the DMA ring buffer. */
 | |
| #define DIGITAL_SEQUENCE_RING_BUFFER_SIZE 128
 | |
| 
 | |
| #define DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE 2
 | |
| 
 | |
| /* Maximum amount of registered signals. */
 | |
| #define DIGITAL_SEQUENCE_BANK_SIZE 32
 | |
| 
 | |
| typedef enum {
 | |
|     DigitalSequenceStateIdle,
 | |
|     DigitalSequenceStateActive,
 | |
| } DigitalSequenceState;
 | |
| 
 | |
| typedef struct {
 | |
|     uint32_t data[DIGITAL_SEQUENCE_RING_BUFFER_SIZE];
 | |
|     uint32_t write_pos;
 | |
|     uint32_t read_pos;
 | |
| } DigitalSequenceRingBuffer;
 | |
| 
 | |
| typedef uint32_t DigitalSequenceGpioBuffer[DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE];
 | |
| 
 | |
| typedef const DigitalSignal* DigitalSequenceSignalBank[DIGITAL_SEQUENCE_BANK_SIZE];
 | |
| 
 | |
| struct DigitalSequence {
 | |
|     const GpioPin* gpio;
 | |
| 
 | |
|     uint32_t size;
 | |
|     uint32_t max_size;
 | |
|     uint8_t* data;
 | |
| 
 | |
|     LL_DMA_InitTypeDef dma_config_gpio;
 | |
|     LL_DMA_InitTypeDef dma_config_timer;
 | |
| 
 | |
|     DigitalSequenceGpioBuffer gpio_buf;
 | |
|     DigitalSequenceRingBuffer timer_buf;
 | |
|     DigitalSequenceSignalBank signals;
 | |
|     DigitalSequenceState state;
 | |
| };
 | |
| 
 | |
| DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) {
 | |
|     furi_assert(size);
 | |
|     furi_assert(gpio);
 | |
| 
 | |
|     DigitalSequence* sequence = malloc(sizeof(DigitalSequence));
 | |
| 
 | |
|     sequence->gpio = gpio;
 | |
|     sequence->max_size = size;
 | |
| 
 | |
|     sequence->data = malloc(sequence->max_size);
 | |
| 
 | |
|     sequence->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t)&gpio->port->BSRR;
 | |
|     sequence->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)sequence->gpio_buf;
 | |
|     sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
 | |
|     sequence->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR;
 | |
|     sequence->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
 | |
|     sequence->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
 | |
|     sequence->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
 | |
|     sequence->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
 | |
|     sequence->dma_config_gpio.NbData = DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE;
 | |
|     sequence->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
 | |
|     sequence->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH;
 | |
| 
 | |
|     sequence->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t)&TIM2->ARR;
 | |
|     sequence->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)sequence->timer_buf.data;
 | |
|     sequence->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
 | |
|     sequence->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR;
 | |
|     sequence->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
 | |
|     sequence->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
 | |
|     sequence->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
 | |
|     sequence->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
 | |
|     sequence->dma_config_timer.NbData = DIGITAL_SEQUENCE_RING_BUFFER_SIZE;
 | |
|     sequence->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
 | |
|     sequence->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH;
 | |
| 
 | |
|     return sequence;
 | |
| }
 | |
| 
 | |
| void digital_sequence_free(DigitalSequence* sequence) {
 | |
|     furi_assert(sequence);
 | |
| 
 | |
|     free(sequence->data);
 | |
|     free(sequence);
 | |
| }
 | |
| 
 | |
| void digital_sequence_register_signal(
 | |
|     DigitalSequence* sequence,
 | |
|     uint8_t signal_index,
 | |
|     const DigitalSignal* signal) {
 | |
|     furi_assert(sequence);
 | |
|     furi_assert(signal);
 | |
|     furi_assert(signal_index < DIGITAL_SEQUENCE_BANK_SIZE);
 | |
| 
 | |
|     sequence->signals[signal_index] = signal;
 | |
| }
 | |
| 
 | |
| void digital_sequence_add_signal(DigitalSequence* sequence, uint8_t signal_index) {
 | |
|     furi_assert(sequence);
 | |
|     furi_assert(signal_index < DIGITAL_SEQUENCE_BANK_SIZE);
 | |
|     furi_assert(sequence->size < sequence->max_size);
 | |
| 
 | |
|     sequence->data[sequence->size++] = signal_index;
 | |
| }
 | |
| 
 | |
| static inline void digital_sequence_start_dma(DigitalSequence* sequence) {
 | |
|     furi_assert(sequence);
 | |
| 
 | |
|     LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &sequence->dma_config_gpio);
 | |
|     LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &sequence->dma_config_timer);
 | |
| 
 | |
|     LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
 | |
|     LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
 | |
| }
 | |
| 
 | |
| static inline void digital_sequence_stop_dma() {
 | |
|     LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
 | |
|     LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
 | |
|     LL_DMA_ClearFlag_TC1(DMA1);
 | |
|     LL_DMA_ClearFlag_TC2(DMA1);
 | |
| }
 | |
| 
 | |
| static inline void digital_sequence_start_timer() {
 | |
|     furi_hal_bus_enable(FuriHalBusTIM2);
 | |
| 
 | |
|     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, DIGITAL_SEQUENCE_TIMER_MAX);
 | |
|     LL_TIM_SetCounter(TIM2, 0);
 | |
| 
 | |
|     LL_TIM_EnableCounter(TIM2);
 | |
|     LL_TIM_EnableUpdateEvent(TIM2);
 | |
|     LL_TIM_EnableDMAReq_UPDATE(TIM2);
 | |
|     LL_TIM_GenerateEvent_UPDATE(TIM2);
 | |
| }
 | |
| 
 | |
| static void digital_sequence_stop_timer() {
 | |
|     LL_TIM_DisableCounter(TIM2);
 | |
|     LL_TIM_DisableUpdateEvent(TIM2);
 | |
|     LL_TIM_DisableDMAReq_UPDATE(TIM2);
 | |
| 
 | |
|     furi_hal_bus_disable(FuriHalBusTIM2);
 | |
| }
 | |
| 
 | |
| static inline void digital_sequence_init_gpio_buffer(
 | |
|     DigitalSequence* sequence,
 | |
|     const DigitalSignal* first_signal) {
 | |
|     const uint32_t bit_set = sequence->gpio->pin << GPIO_BSRR_BS0_Pos
 | |
| #ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN
 | |
|                              | DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << GPIO_BSRR_BS0_Pos
 | |
| #endif
 | |
|         ;
 | |
| 
 | |
|     const uint32_t bit_reset = sequence->gpio->pin << GPIO_BSRR_BR0_Pos
 | |
| #ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN
 | |
|                                | DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << GPIO_BSRR_BR0_Pos
 | |
| #endif
 | |
|         ;
 | |
| 
 | |
|     if(first_signal->start_level) {
 | |
|         sequence->gpio_buf[0] = bit_set;
 | |
|         sequence->gpio_buf[1] = bit_reset;
 | |
|     } else {
 | |
|         sequence->gpio_buf[0] = bit_reset;
 | |
|         sequence->gpio_buf[1] = bit_set;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static inline void digital_sequence_finish(DigitalSequence* sequence) {
 | |
|     if(sequence->state == DigitalSequenceStateActive) {
 | |
|         const uint32_t prev_timer = DWT->CYCCNT;
 | |
| 
 | |
|         do {
 | |
|             /* Special value has been loaded into the timer, signaling the end of transmission. */
 | |
|             if(TIM2->ARR == DIGITAL_SEQUENCE_TIMER_MAX) {
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if(DWT->CYCCNT - prev_timer > DIGITAL_SEQUENCE_LOCK_WAIT_TICKS) {
 | |
|                 DigitalSequenceRingBuffer* dma_buffer = &sequence->timer_buf;
 | |
|                 dma_buffer->read_pos = DIGITAL_SEQUENCE_RING_BUFFER_SIZE -
 | |
|                                        LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
 | |
|                 FURI_LOG_D(
 | |
|                     TAG,
 | |
|                     "[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)",
 | |
|                     DIGITAL_SEQUENCE_LOCK_WAIT_MS,
 | |
|                     TIM2->ARR,
 | |
|                     dma_buffer->read_pos,
 | |
|                     dma_buffer->write_pos);
 | |
|                 break;
 | |
|             }
 | |
|         } while(true);
 | |
|     }
 | |
| 
 | |
|     digital_sequence_stop_timer();
 | |
|     digital_sequence_stop_dma();
 | |
| }
 | |
| 
 | |
| static inline void digital_sequence_enqueue_period(DigitalSequence* sequence, uint32_t length) {
 | |
|     DigitalSequenceRingBuffer* dma_buffer = &sequence->timer_buf;
 | |
| 
 | |
|     if(sequence->state == DigitalSequenceStateActive) {
 | |
|         const uint32_t prev_timer = DWT->CYCCNT;
 | |
| 
 | |
|         do {
 | |
|             dma_buffer->read_pos =
 | |
|                 DIGITAL_SEQUENCE_RING_BUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2);
 | |
| 
 | |
|             const uint32_t size_free = (DIGITAL_SEQUENCE_RING_BUFFER_SIZE + dma_buffer->read_pos -
 | |
|                                         dma_buffer->write_pos) %
 | |
|                                        DIGITAL_SEQUENCE_RING_BUFFER_SIZE;
 | |
| 
 | |
|             if(size_free > DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE) {
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if(DWT->CYCCNT - prev_timer > DIGITAL_SEQUENCE_LOCK_WAIT_TICKS) {
 | |
|                 FURI_LOG_D(
 | |
|                     TAG,
 | |
|                     "[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)",
 | |
|                     DIGITAL_SEQUENCE_LOCK_WAIT_MS,
 | |
|                     TIM2->ARR,
 | |
|                     dma_buffer->read_pos,
 | |
|                     dma_buffer->write_pos);
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if(TIM2->ARR == DIGITAL_SEQUENCE_TIMER_MAX) {
 | |
|                 FURI_LOG_D(
 | |
|                     TAG,
 | |
|                     "[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)",
 | |
|                     TIM2->ARR,
 | |
|                     dma_buffer->read_pos,
 | |
|                     dma_buffer->write_pos);
 | |
|                 break;
 | |
|             }
 | |
|         } while(true);
 | |
|     }
 | |
| 
 | |
|     dma_buffer->data[dma_buffer->write_pos] = length;
 | |
| 
 | |
|     dma_buffer->write_pos += 1;
 | |
|     dma_buffer->write_pos %= DIGITAL_SEQUENCE_RING_BUFFER_SIZE;
 | |
| 
 | |
|     dma_buffer->data[dma_buffer->write_pos] = DIGITAL_SEQUENCE_TIMER_MAX;
 | |
| }
 | |
| 
 | |
| static inline void digital_sequence_timer_buffer_reset(DigitalSequence* sequence) {
 | |
|     sequence->timer_buf.data[0] = DIGITAL_SEQUENCE_TIMER_MAX;
 | |
|     sequence->timer_buf.read_pos = 0;
 | |
|     sequence->timer_buf.write_pos = 0;
 | |
| }
 | |
| 
 | |
| void digital_sequence_transmit(DigitalSequence* sequence) {
 | |
|     furi_assert(sequence);
 | |
|     furi_assert(sequence->size);
 | |
|     furi_assert(sequence->state == DigitalSequenceStateIdle);
 | |
| 
 | |
|     FURI_CRITICAL_ENTER();
 | |
| 
 | |
|     furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
 | |
| #ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN
 | |
|     furi_hal_gpio_init(
 | |
|         &DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
 | |
| #endif
 | |
| 
 | |
|     const DigitalSignal* signal_current = sequence->signals[sequence->data[0]];
 | |
| 
 | |
|     digital_sequence_init_gpio_buffer(sequence, signal_current);
 | |
| 
 | |
|     int32_t remainder_ticks = 0;
 | |
|     uint32_t reload_value_carry = 0;
 | |
|     uint32_t next_signal_index = 1;
 | |
| 
 | |
|     for(;;) {
 | |
|         const DigitalSignal* signal_next =
 | |
|             (next_signal_index < sequence->size) ?
 | |
|                 sequence->signals[sequence->data[next_signal_index++]] :
 | |
|                 NULL;
 | |
| 
 | |
|         for(uint32_t i = 0; i < signal_current->size; i++) {
 | |
|             const bool is_last_value = (i == signal_current->size - 1);
 | |
|             const uint32_t reload_value = signal_current->data[i] + reload_value_carry;
 | |
| 
 | |
|             reload_value_carry = 0;
 | |
| 
 | |
|             if(is_last_value) {
 | |
|                 if(signal_next != NULL) {
 | |
|                     /* Special case: signal boundary. Depending on whether the adjacent levels are equal or not,
 | |
|                      * they will be combined to a single one or handled separately. */
 | |
|                     const bool end_level = signal_current->start_level ^
 | |
|                                            ((signal_current->size % 2) == 0);
 | |
| 
 | |
|                     /* If the adjacent levels are equal, carry the current period duration over to the next signal. */
 | |
|                     if(end_level == signal_next->start_level) {
 | |
|                         reload_value_carry = reload_value;
 | |
|                     }
 | |
|                 } else {
 | |
|                     /** Special case: during the last period of the last signal, hold the output level indefinitely.
 | |
|                      * @see digital_signal.h
 | |
|                      *
 | |
|                      * Setting reload_value_carry to a non-zero value will prevent the respective period from being
 | |
|                      * added to the DMA ring buffer. */
 | |
|                     reload_value_carry = 1;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             /* A non-zero reload_value_carry means that the level was the same on the both sides of the signal boundary
 | |
|              * and the two respective periods were combined to one. */
 | |
|             if(reload_value_carry == 0) {
 | |
|                 digital_sequence_enqueue_period(sequence, reload_value);
 | |
|             }
 | |
| 
 | |
|             if(sequence->state == DigitalSequenceStateIdle) {
 | |
|                 const bool is_buffer_filled = sequence->timer_buf.write_pos >=
 | |
|                                               (DIGITAL_SEQUENCE_RING_BUFFER_SIZE -
 | |
|                                                DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE);
 | |
|                 const bool is_end_of_data = (signal_next == NULL) && is_last_value;
 | |
| 
 | |
|                 if(is_buffer_filled || is_end_of_data) {
 | |
|                     digital_sequence_start_dma(sequence);
 | |
|                     digital_sequence_start_timer();
 | |
|                     sequence->state = DigitalSequenceStateActive;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Exit the loop here when no further signals are available */
 | |
|         if(signal_next == NULL) break;
 | |
| 
 | |
|         /* Prevent the rounding error from accumulating by distributing it across multiple periods. */
 | |
|         remainder_ticks += signal_current->remainder;
 | |
|         if(remainder_ticks >= DIGITAL_SIGNAL_T_TIM_DIV2) {
 | |
|             remainder_ticks -= DIGITAL_SIGNAL_T_TIM;
 | |
|             reload_value_carry += 1;
 | |
|         }
 | |
| 
 | |
|         signal_current = signal_next;
 | |
|     };
 | |
| 
 | |
|     digital_sequence_finish(sequence);
 | |
|     digital_sequence_timer_buffer_reset(sequence);
 | |
| 
 | |
|     FURI_CRITICAL_EXIT();
 | |
| 
 | |
|     sequence->state = DigitalSequenceStateIdle;
 | |
| }
 | |
| 
 | |
| void digital_sequence_clear(DigitalSequence* sequence) {
 | |
|     furi_assert(sequence);
 | |
| 
 | |
|     sequence->size = 0;
 | |
| }
 |