From 72a765ca08a1fb3e8eb7dfcf35990c0b5859219c Mon Sep 17 00:00:00 2001 From: apolisskyi Date: Fri, 11 Oct 2024 00:03:27 +0400 Subject: [PATCH] :construction: feat(NFC) add client-server protocol idea doc, FSM draft doc --- firmware/iot-risk-logger-stm32l4/.mxproject | 55 +- .../Core/Inc/FreeRTOSConfig.h | 3 - .../Core/Src/freertos.c | 11 +- .../iot-risk-logger-stm32l4/Core/Src/main.c | 4 - .../Core/Src/quadspi.c | 6 +- .../CMSIS/Device/ST/STM32L4xx/License.md | 110 +--- .../Drivers/STM32L4xx_HAL_Driver/LICENSE.txt | 12 +- firmware/iot-risk-logger-stm32l4/Makefile | 11 +- .../ST/ST25FTM/Inc/st25ftm_common.h | 223 ++++++++ .../ST/ST25FTM/Inc/st25ftm_config_template.h | 138 +++++ .../ST/ST25FTM/Inc/st25ftm_protocol.h | 100 ++++ .../Middlewares/ST/ST25FTM/LICENSE.txt | 63 +++ .../ST/ST25FTM/Src/st25ftm_common.c | 97 ++++ .../ST/ST25FTM/Src/st25ftm_protocol.c | 441 ++++++++++++++++ .../Middlewares/ST/ST25FTM/Src/st25ftm_rx.c | 488 ++++++++++++++++++ .../Middlewares/ST/ST25FTM/Src/st25ftm_tx.c | 420 +++++++++++++++ .../FreeRTOS/Source/include/FreeRTOS.h | 37 -- .../FreeRTOS/Source/include/task.h | 19 - .../Source/portable/GCC/ARM_CM4F/port.c | 6 - .../Source/portable/GCC/ARM_CM4F/portmacro.h | 2 +- .../Third_Party/FreeRTOS/Source/tasks.c | 38 +- .../ST25FTM/App/st25ftm_config.h | 151 ++++++ .../ST25FTM/Target/logger.h | 54 ++ .../USB_DEVICE/App/usbd_storage_if.c | 4 +- .../app/config/events_list/events_list.h | 17 + .../app/tasks/event_manager/event_manager.c | 3 +- .../app/tasks/nfc/README.md | 121 ++--- .../app/tasks/nfc/nfc.c | 60 ++- .../app/tasks/nfc/nfc.h | 1 + .../iot-risk-logger-stm32l4.ioc | 19 +- .../libraries/fp-sns-stbox1 | 1 - .../iot-risk-logger-stm32l4.ozone.jdebug.user | 32 +- hardware-debug/logic-analyzer.dwf3work | Bin 53417 -> 48274 bytes 33 files changed, 2394 insertions(+), 353 deletions(-) create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_common.h create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_config_template.h create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_protocol.h create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/LICENSE.txt create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_common.c create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_protocol.c create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_rx.c create mode 100644 firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_tx.c create mode 100644 firmware/iot-risk-logger-stm32l4/ST25FTM/App/st25ftm_config.h create mode 100644 firmware/iot-risk-logger-stm32l4/ST25FTM/Target/logger.h delete mode 160000 firmware/iot-risk-logger-stm32l4/libraries/fp-sns-stbox1 diff --git a/firmware/iot-risk-logger-stm32l4/.mxproject b/firmware/iot-risk-logger-stm32l4/.mxproject index 3aea35a1..6a54360b 100644 --- a/firmware/iot-risk-logger-stm32l4/.mxproject +++ b/firmware/iot-risk-logger-stm32l4/.mxproject @@ -3,12 +3,12 @@ LibFiles=Drivers/STM32L4xx_HAL_Driver/Inc/stm32l4xx_hal_pcd.h;Drivers/STM32L4xx_ [PreviousUsedMakefileFiles] SourceFiles=Core/Src/main.c;Core/Src/gpio.c;Core/Src/freertos.c;Core/Src/crc.c;Core/Src/lptim.c;Core/Src/quadspi.c;Core/Src/rng.c;Core/Src/rtc.c;USB_DEVICE/App/usb_device.c;USB_DEVICE/Target/usbd_conf.c;USB_DEVICE/App/usbd_desc.c;USB_DEVICE/App/usbd_storage_if.c;Core/Src/custom_bus.c;Core/Src/stm32l4xx_it.c;Core/Src/stm32l4xx_hal_msp.c;Core/Src/stm32l4xx_hal_timebase_tim.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pcd.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pcd_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_ll_usb.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ramfunc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_gpio.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_cortex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_exti.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_crc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_crc_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_lptim.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_qspi.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rng.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rtc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rtc_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim_ex.c;Middlewares/Third_Party/FreeRTOS/Source/croutine.c;Middlewares/Third_Party/FreeRTOS/Source/event_groups.c;Middlewares/Third_Party/FreeRTOS/Source/list.c;Middlewares/Third_Party/FreeRTOS/Source/queue.c;Middlewares/Third_Party/FreeRTOS/Source/stream_buffer.c;Middlewares/Third_Party/FreeRTOS/Source/tasks.c;Middlewares/Third_Party/FreeRTOS/Source/timers.c;Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.c;Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c;Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_core.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ctlreq.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ioreq.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_bot.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_data.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c;Drivers/CMSIS/Device/ST/STM32L4xx/Source/Templates/system_stm32l4xx.c;Core/Src/system_stm32l4xx.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pcd.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pcd_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_ll_usb.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ramfunc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_gpio.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_cortex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_exti.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_crc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_crc_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_lptim.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_qspi.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rng.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rtc.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rtc_ex.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim.c;Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim_ex.c;Middlewares/Third_Party/FreeRTOS/Source/croutine.c;Middlewares/Third_Party/FreeRTOS/Source/event_groups.c;Middlewares/Third_Party/FreeRTOS/Source/list.c;Middlewares/Third_Party/FreeRTOS/Source/queue.c;Middlewares/Third_Party/FreeRTOS/Source/stream_buffer.c;Middlewares/Third_Party/FreeRTOS/Source/tasks.c;Middlewares/Third_Party/FreeRTOS/Source/timers.c;Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.c;Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c;Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_core.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ctlreq.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ioreq.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_bot.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_data.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c;Drivers/CMSIS/Device/ST/STM32L4xx/Source/Templates/system_stm32l4xx.c;Core/Src/system_stm32l4xx.c;;;Middlewares/Third_Party/FreeRTOS/Source/croutine.c;Middlewares/Third_Party/FreeRTOS/Source/event_groups.c;Middlewares/Third_Party/FreeRTOS/Source/list.c;Middlewares/Third_Party/FreeRTOS/Source/queue.c;Middlewares/Third_Party/FreeRTOS/Source/stream_buffer.c;Middlewares/Third_Party/FreeRTOS/Source/tasks.c;Middlewares/Third_Party/FreeRTOS/Source/timers.c;Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.c;Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c;Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_core.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ctlreq.c;Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ioreq.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_bot.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_data.c;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c; -HeaderPath=Drivers/STM32L4xx_HAL_Driver/Inc;Drivers/STM32L4xx_HAL_Driver/Inc/Legacy;Middlewares/Third_Party/FreeRTOS/Source/include;Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F;Middlewares/ST/STM32_USB_Device_Library/Core/Inc;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc;Drivers/CMSIS/Device/ST/STM32L4xx/Include;Drivers/CMSIS/Include;Drivers/BSP/Components/lis2dw12;Drivers/BSP/Components/ST25DV;Core/Inc;X-CUBE-MEMS1/Target;USB_DEVICE/App;USB_DEVICE/Target; +HeaderPath=Drivers/STM32L4xx_HAL_Driver/Inc;Drivers/STM32L4xx_HAL_Driver/Inc/Legacy;Middlewares/Third_Party/FreeRTOS/Source/include;Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F;Middlewares/ST/STM32_USB_Device_Library/Core/Inc;Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc;Drivers/CMSIS/Device/ST/STM32L4xx/Include;Drivers/CMSIS/Include;Middlewares/ST/ST25FTM/Inc;Drivers/BSP/Components/lis2dw12;Drivers/BSP/Components/ST25DV;Core/Inc;ST25FTM/App;ST25FTM/Target;X-CUBE-MEMS1/Target;USB_DEVICE/App;USB_DEVICE/Target; CDefines=USE_HAL_DRIVER;STM32L412xx;USE_HAL_DRIVER;USE_HAL_DRIVER; [PreviousGenFiles] AdvancedFolderStructure=true -HeaderFileListSize=21 +HeaderFileListSize=23 HeaderFiles#0=../Core/Inc/gpio.h HeaderFiles#1=../Core/Inc/FreeRTOSConfig.h HeaderFiles#2=../Core/Inc/crc.h @@ -16,25 +16,29 @@ HeaderFiles#3=../Core/Inc/lptim.h HeaderFiles#4=../Core/Inc/quadspi.h HeaderFiles#5=../Core/Inc/rng.h HeaderFiles#6=../Core/Inc/rtc.h -HeaderFiles#7=../X-CUBE-MEMS1/Target/sensor_unicleo_id.h -HeaderFiles#8=../X-CUBE-MEMS1/Target/custom_mems_conf_app.h -HeaderFiles#9=../X-CUBE-MEMS1/Target/custom_mems_conf.h -HeaderFiles#10=../USB_DEVICE/App/usb_device.h -HeaderFiles#11=../USB_DEVICE/Target/usbd_conf.h -HeaderFiles#12=../USB_DEVICE/App/usbd_desc.h -HeaderFiles#13=../USB_DEVICE/App/usbd_storage_if.h -HeaderFiles#14=../Core/Inc/custom_bus.h -HeaderFiles#15=../Core/Inc/custom_errno.h -HeaderFiles#16=../Core/Inc/custom_conf.h -HeaderFiles#17=../Core/Inc/stm32l4xx_it.h -HeaderFiles#18=../Core/Inc/RTE_Components.h -HeaderFiles#19=../Core/Inc/stm32l4xx_hal_conf.h -HeaderFiles#20=../Core/Inc/main.h -HeaderFolderListSize=4 +HeaderFiles#7=../ST25FTM/App/st25ftm_config.h +HeaderFiles#8=../ST25FTM/Target/logger.h +HeaderFiles#9=../X-CUBE-MEMS1/Target/sensor_unicleo_id.h +HeaderFiles#10=../X-CUBE-MEMS1/Target/custom_mems_conf_app.h +HeaderFiles#11=../X-CUBE-MEMS1/Target/custom_mems_conf.h +HeaderFiles#12=../USB_DEVICE/App/usb_device.h +HeaderFiles#13=../USB_DEVICE/Target/usbd_conf.h +HeaderFiles#14=../USB_DEVICE/App/usbd_desc.h +HeaderFiles#15=../USB_DEVICE/App/usbd_storage_if.h +HeaderFiles#16=../Core/Inc/custom_bus.h +HeaderFiles#17=../Core/Inc/custom_errno.h +HeaderFiles#18=../Core/Inc/custom_conf.h +HeaderFiles#19=../Core/Inc/stm32l4xx_it.h +HeaderFiles#20=../Core/Inc/RTE_Components.h +HeaderFiles#21=../Core/Inc/stm32l4xx_hal_conf.h +HeaderFiles#22=../Core/Inc/main.h +HeaderFolderListSize=6 HeaderPath#0=../Core/Inc -HeaderPath#1=../X-CUBE-MEMS1/Target -HeaderPath#2=../USB_DEVICE/App -HeaderPath#3=../USB_DEVICE/Target +HeaderPath#1=../ST25FTM/App +HeaderPath#2=../ST25FTM/Target +HeaderPath#3=../X-CUBE-MEMS1/Target +HeaderPath#4=../USB_DEVICE/App +HeaderPath#5=../USB_DEVICE/Target HeaderFiles=; SourceFileListSize=16 SourceFiles#0=../Core/Src/gpio.c @@ -60,9 +64,14 @@ SourcePath#2=../USB_DEVICE/Target SourceFiles=; [ThirdPartyIp] -ThirdPartyIpNumber=2 -ThirdPartyIpName#0=STMicroelectronics.X-CUBE-MEMS1.10.0.0 -ThirdPartyIpName#1=STMicroelectronics.X-CUBE-NFC4.3.0.0 +ThirdPartyIpNumber=3 +ThirdPartyIpName#0=STMicroelectronics.FP-SNS-STBOX1.2.0.0 +ThirdPartyIpName#1=STMicroelectronics.X-CUBE-MEMS1.10.0.0 +ThirdPartyIpName#2=STMicroelectronics.X-CUBE-NFC4.3.0.0 + +[ThirdPartyIp#STMicroelectronics.FP-SNS-STBOX1.2.0.0] +header=../Middlewares/ST/ST25FTM/Inc/st25ftm_common.h;../Middlewares/ST/ST25FTM/Inc/st25ftm_protocol.h; +source=../Middlewares/ST/ST25FTM/Src/st25ftm_common.c;../Middlewares/ST/ST25FTM/Src/st25ftm_protocol.c;../Middlewares/ST/ST25FTM/Src/st25ftm_rx.c;../Middlewares/ST/ST25FTM/Src/st25ftm_tx.c; [ThirdPartyIp#STMicroelectronics.X-CUBE-MEMS1.10.0.0] header=../Drivers/BSP/Components/lis2dw12/lis2dw12_reg.h;../Drivers/BSP/Components/lis2dw12/lis2dw12.h; diff --git a/firmware/iot-risk-logger-stm32l4/Core/Inc/FreeRTOSConfig.h b/firmware/iot-risk-logger-stm32l4/Core/Inc/FreeRTOSConfig.h index d6e1a9cc..296ab5b7 100644 --- a/firmware/iot-risk-logger-stm32l4/Core/Inc/FreeRTOSConfig.h +++ b/firmware/iot-risk-logger-stm32l4/Core/Inc/FreeRTOSConfig.h @@ -167,7 +167,4 @@ standard names. */ /* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */ /* USER CODE END Defines */ -// @note Include SEGGER_SYSVIEW_FreeRTOS.h at the end of the file FreeRTOSConfig.h to override some RTOS definitions of tracing functions. -#include "SEGGER_SYSVIEW_FreeRTOS.h" - #endif /* FREERTOS_CONFIG_H */ diff --git a/firmware/iot-risk-logger-stm32l4/Core/Src/freertos.c b/firmware/iot-risk-logger-stm32l4/Core/Src/freertos.c index 1163860c..41f18b51 100644 --- a/firmware/iot-risk-logger-stm32l4/Core/Src/freertos.c +++ b/firmware/iot-risk-logger-stm32l4/Core/Src/freertos.c @@ -49,14 +49,9 @@ osMutexId_t i2cMutexHandle; /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; -uint32_t defaultTaskBuffer[ 128 ]; -osStaticThreadDef_t defaultTaskControlBlock; const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask", - .cb_mem = &defaultTaskControlBlock, - .cb_size = sizeof(defaultTaskControlBlock), - .stack_mem = &defaultTaskBuffer[0], - .stack_size = sizeof(defaultTaskBuffer), + .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityNormal, }; @@ -70,10 +65,6 @@ void StartDefaultTask(void *argument); extern void MX_USB_DEVICE_Init(void); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ -/* USER CODE BEGIN PREPOSTSLEEP */ -/** @see sleep.c */ -/* USER CODE END PREPOSTSLEEP */ - /** * @brief FreeRTOS initialization * @param None diff --git a/firmware/iot-risk-logger-stm32l4/Core/Src/main.c b/firmware/iot-risk-logger-stm32l4/Core/Src/main.c index 04337cce..036122ca 100644 --- a/firmware/iot-risk-logger-stm32l4/Core/Src/main.c +++ b/firmware/iot-risk-logger-stm32l4/Core/Src/main.c @@ -26,7 +26,6 @@ #include "rtc.h" #include "usb_device.h" #include "gpio.h" -#include "SEGGER_RTT.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ @@ -120,9 +119,6 @@ int main(void) /* Call init function for freertos objects (in cmsis_os2.c) */ MX_FREERTOS_Init(); - /* Init SystemView */ - SEGGER_SYSVIEW_Conf(); - /* Start scheduler */ osKernelStart(); diff --git a/firmware/iot-risk-logger-stm32l4/Core/Src/quadspi.c b/firmware/iot-risk-logger-stm32l4/Core/Src/quadspi.c index 90b69fdf..ad23205c 100644 --- a/firmware/iot-risk-logger-stm32l4/Core/Src/quadspi.c +++ b/firmware/iot-risk-logger-stm32l4/Core/Src/quadspi.c @@ -38,10 +38,10 @@ void MX_QUADSPI_Init(void) /* USER CODE END QUADSPI_Init 1 */ hqspi.Instance = QUADSPI; - hqspi.Init.ClockPrescaler = 4; // half of the system clock e.g. = 2: 48MHz/2 = 24MHz - hqspi.Init.FifoThreshold = 1; // 1 byte minimum to send or receive + hqspi.Init.ClockPrescaler = 1; + hqspi.Init.FifoThreshold = 1; hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; - hqspi.Init.FlashSize = W25Q64JV_FLASH_ADDR_SIZE_BITS; + hqspi.Init.FlashSize = 24; hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE; hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; hqspi.Init.FlashID = QSPI_FLASH_ID_1; diff --git a/firmware/iot-risk-logger-stm32l4/Drivers/CMSIS/Device/ST/STM32L4xx/License.md b/firmware/iot-risk-logger-stm32l4/Drivers/CMSIS/Device/ST/STM32L4xx/License.md index 2d1eee19..8c9d2194 100644 --- a/firmware/iot-risk-logger-stm32l4/Drivers/CMSIS/Device/ST/STM32L4xx/License.md +++ b/firmware/iot-risk-logger-stm32l4/Drivers/CMSIS/Device/ST/STM32L4xx/License.md @@ -1,83 +1,27 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: -1.You must give any other recipients of the Work or Derivative Works a copy of this License; and -2.You must cause any modified files to carry prominent notices stating that You changed the files; and -3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: - - Copyright [2019] [STMicroelectronics] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Copyright 2017 STMicroelectronics. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/firmware/iot-risk-logger-stm32l4/Drivers/STM32L4xx_HAL_Driver/LICENSE.txt b/firmware/iot-risk-logger-stm32l4/Drivers/STM32L4xx_HAL_Driver/LICENSE.txt index 3edc4d14..b40364c2 100644 --- a/firmware/iot-risk-logger-stm32l4/Drivers/STM32L4xx_HAL_Driver/LICENSE.txt +++ b/firmware/iot-risk-logger-stm32l4/Drivers/STM32L4xx_HAL_Driver/LICENSE.txt @@ -1,6 +1,6 @@ -This software component is provided to you as part of a software package and -applicable license terms are in the Package_license file. If you received this -software component outside of a package or without applicable license terms, -the terms of the BSD-3-Clause license shall apply. -You may obtain a copy of the BSD-3-Clause at: -https://opensource.org/licenses/BSD-3-Clause +This software component is provided to you as part of a software package and +applicable license terms are in the Package_license file. If you received this +software component outside of a package or without applicable license terms, +the terms of the BSD-3-Clause license shall apply. +You may obtain a copy of the BSD-3-Clause at: +https://opensource.org/licenses/BSD-3-Clause diff --git a/firmware/iot-risk-logger-stm32l4/Makefile b/firmware/iot-risk-logger-stm32l4/Makefile index a32b1b55..2d357e98 100644 --- a/firmware/iot-risk-logger-stm32l4/Makefile +++ b/firmware/iot-risk-logger-stm32l4/Makefile @@ -1,5 +1,5 @@ ########################################################################################################################## -# File automatically-generated by tool: [projectgenerator] version: [4.4.0-B60] date: [Mon Sep 02 00:07:44 GET 2024] +# File automatically-generated by tool: [projectgenerator] version: [4.4.0-B60] date: [Tue Oct 08 23:27:20 GET 2024] ########################################################################################################################## # ------------------------------------------------ @@ -108,6 +108,10 @@ Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc.c \ Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_bot.c \ Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_data.c \ Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c \ +Middlewares/ST/ST25FTM/Src/st25ftm_common.c \ +Middlewares/ST/ST25FTM/Src/st25ftm_protocol.c \ +Middlewares/ST/ST25FTM/Src/st25ftm_rx.c \ +Middlewares/ST/ST25FTM/Src/st25ftm_tx.c \ libraries/RTT/RTT/SEGGER_RTT.c \ libraries/RTT/RTT/SEGGER_RTT_printf.c \ libraries/RTT/Syscalls/SEGGER_RTT_Syscalls_GCC.c \ @@ -198,6 +202,8 @@ C_INCLUDES = \ -IX-CUBE-MEMS1/Target \ -IUSB_DEVICE/App \ -IUSB_DEVICE/Target \ +-IST25FTM/App \ +-IST25FTM/Target \ -IDrivers/STM32L4xx_HAL_Driver/Inc \ -IDrivers/STM32L4xx_HAL_Driver/Inc/Legacy \ -IMiddlewares/Third_Party/FreeRTOS/Source/include \ @@ -205,6 +211,7 @@ C_INCLUDES = \ -IMiddlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F \ -IMiddlewares/ST/STM32_USB_Device_Library/Core/Inc \ -IMiddlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc \ +-IMiddlewares/ST/ST25FTM/Inc \ -IDrivers/CMSIS/Device/ST/STM32L4xx/Include \ -IDrivers/CMSIS/Include \ -IDrivers/BSP/Components/lis2dw12 \ @@ -216,6 +223,7 @@ C_INCLUDES = \ -Ilibraries/SystemView/SEGGER \ -Ilibraries/SystemView/SYSVIEW \ -Ilibraries/SystemView/Sample/FreeRTOSV10 \ +-Ilibraries/fp-sns-stbox1/Middlewares/ST/ST25FTM/Inc \ -Iapp/core/actor \ -Iapp/core/trace \ -Iapp/config/bsp_bus \ @@ -235,6 +243,7 @@ C_INCLUDES = \ -Iapp/tasks/temperature_humidity_sensor \ -Iapp/tasks/light_sensor \ -Iapp/tasks/nfc +#-Iapp/middlewares/nfc_st25ftm # compile gcc flags ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections --print-memory-usage diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_common.h b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_common.h new file mode 100644 index 00000000..d54aa600 --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_common.h @@ -0,0 +1,223 @@ +/** + ****************************************************************************** + * @file st25ftm_common.h + * @author MMY Application Team + * @version 1.0.0 + * @date 29-July-2022 + * @brief Fast Transfer Memory protocol common header file to utilities + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#ifndef ST25FTM_COMMON_H +#define ST25FTM_COMMON_H + +#include +#include "st25ftm_protocol.h" +#include "st25ftm_config.h" + +#define ST25FTM_WAIT_TIMEOUT (1000U) + +// only used for TX +#define ST25FTM_MAX_DATA_IN_SINGLE_PACKET() ((gFtmState.tx.frameMaxLength) - sizeof(ST25FTM_Ctrl_Byte_t) ) + +#define ST25FTM_CTRL_HAS_PKT_LEN(msg) ((msg).b.pktLen != 0U) + +#define ST25FTM_CTRL_HAS_TOTAL_LEN(msg) (((msg).b.position) == (uint8_t)(ST25FTM_FIRST_PACKET)) + + +#define ST25FTM_CTRL_IS_SINGLE_PACKET(ctrl) ((ST25FTM_Packet_Position_t)(ctrl) == (ST25FTM_SINGLE_PACKET)) +#define ST25FTM_CTRL_IS_LAST_PACKET(ctrl) ((ST25FTM_Packet_Position_t)(ctrl) == (ST25FTM_LAST_PACKET)) +#define ST25FTM_CTRL_IS_COMMAND_COMPLETE(msg) (ST25FTM_CTRL_IS_SINGLE_PACKET(((ST25FTM_Ctrl_Byte_t*)msg)->b.position) ||\ + ST25FTM_CTRL_IS_LAST_PACKET(((FTM_Ctrl_Byte_t*)msg)->b.position)) + + + +#define ST25FTM_CTRL_IS_ACK_SINGLE(ackCtrl) ((ackCtrl) == (uint8_t)(ST25FTM_ACK_SINGLE_PKT)) +#define ST25FTM_CTRL_IS_SEGMENT_END(ackCtrl) ((ackCtrl) == (uint8_t)(ST25FTM_SEGMENT_END)) +#define ST25FTM_CTRL_HAS_CRC(msg) (ST25FTM_CTRL_IS_ACK_SINGLE((msg).b.ackCtrl) || \ + ST25FTM_CTRL_IS_SEGMENT_END((msg).b.ackCtrl)) + + +#define ST25FTM_GET_PKT_LEN(msg) (((ST25FTM_Header_t*)msg)->with_len.length) +#define ST25FTM_GET_TOTAL_LEN_WITH_LEN(msg) (((ST25FTM_Header_t*)msg)->with_len.totalLength) +#define ST25FTM_GET_TOTAL_LEN_WITHOUT_LEN(msg) (((ST25FTM_Header_t*)msg)->without_len.totalLength) + +#define ST25FTM_CHANGE_ENDIANESS(x) ( (x) = \ + (((x)>>24U)&0xFFU) | \ + (((x)>>8U)&0xFF00U)| \ + (((x)<<8U)&0xFF0000U)| \ + (((x)<<24U)&0xFF000000U)) + +typedef enum { + ST25FTM_SINGLE_PACKET = 0U, + ST25FTM_FIRST_PACKET = 1U, + ST25FTM_MIDDLE_PACKET = 2U, + ST25FTM_LAST_PACKET = 3U +} ST25FTM_Packet_Position_t; + +typedef enum { + ST25FTM_NO_ACK_PACKET = 0U, + ST25FTM_SEGMENT_START = 1U, + ST25FTM_SEGMENT_END = 2U, + ST25FTM_ACK_SINGLE_PKT = 3U +} ST25FTM_Packet_Acknowledge_t; + +typedef enum { + ST25FTM_SEGMENT_OK = 0, + ST25FTM_CRC_ERROR = 1, + ST25FTM_ACK_RFU = 2, + ST25FTM_ABORT_TRANSFER = 3, + ST25FTM_ACK_BUSY=-1, + ST25FTM_ACK_ERROR=-2 +} ST25FTM_Acknowledge_Status_t; + +typedef enum { + ST25FTM_CTRL_BYTE = 0x0, + ST25FTM_STATUS_BYTE = 0x80 +} ST25FTM_Packet_Identifier_t; + +typedef union { + struct { + uint8_t inSegment:1; + uint8_t segId:1; + uint8_t position:2; + uint8_t ackCtrl:2; + uint8_t pktLen:1; + uint8_t type:1; + } b; + uint8_t byte; +} ST25FTM_Ctrl_Byte_t; + +/* This struct is used to decode the received ST25FTM headers, it must be packed */ +typedef ST25FTM_PACKED(union) { + ST25FTM_PACKED(struct) { + uint8_t ctrl; + ST25FTM_Packet_Length_t length; + uint32_t totalLength; + } with_len; + ST25FTM_PACKED(struct) { + uint8_t ctrl; + uint32_t totalLength; + } without_len; +} ST25FTM_Header_t; + + +typedef struct { + ST25FTM_Ctrl_Byte_t ctrl; + uint32_t length; + uint32_t totalLength; + ST25FTM_Crc_t crc; + uint8_t* data; + uint8_t has_crc; +} ST25FTM_Packet_t; + +typedef union { + struct { + uint8_t status:4; + uint8_t rfu:3; + uint8_t type:1; + } bit; + uint8_t byte; +} ST25FTM_Status_Byte_t; + + +typedef struct { + ST25FTM_TxState_t state; + ST25FTM_TxState_t lastState; + uint8_t* cmdPtr; + uint32_t cmdLen; + uint32_t remainingData; + uint32_t frameMaxLength; + uint32_t nbError; + uint8_t lastError; + ST25FTM_Send_Ack_t sendAck; + uint8_t* dataPtr; + uint8_t* segmentPtr; + uint8_t* segmentStart; + uint32_t segmentLength; + uint32_t segmentRemainingData; + uint32_t segmentMaxLength; + uint8_t retransmit; + uint32_t pktIndex; + uint32_t segmentIndex; + uint8_t packetBuf[ST25FTM_BUFFER_LENGTH]; + uint32_t packetLength; + uint32_t segmentNumber; + ftm_data_cb getdata_cb; +} ST25Ftm_InternalTxState_t; + +typedef struct { + ST25FTM_RxState_t state; + ST25FTM_RxState_t lastState; + uint8_t isNewFrame; + uint8_t* cmdPtr; + uint32_t* cmdLen; + uint32_t maxCmdLen; + uint32_t nbError; + uint8_t lastError; + uint8_t unrecoverableError; + uint32_t frameMaxLength; + uint32_t receivedLength; + uint32_t validLength; + uint32_t validReceivedLength; + uint32_t totalValidReceivedLength; + uint8_t* segmentPtr; + uint32_t segmentLength; + uint8_t* dataPtr; + uint8_t lastAck; + uint8_t rewriteOnFieldOff; + uint8_t ignoreRetransSegment; + ST25FTM_Packet_Position_t pktPosition; + uint32_t segmentNumber; + ftm_data_cb recvdata_cb; + uint32_t readBufferOffset; +} ST25Ftm_InternalRxState_t; + +typedef enum { + ST25FTM_STATE_MACHINE_CONTINUE, + ST25FTM_STATE_MACHINE_RELEASE +} ST25FTM_StateMachineCtrl_t; + +typedef struct { + ST25FTM_State_t state; + ST25FTM_State_t lastState; + ST25FTM_Field_State_t rfField; + uint32_t totalDataLength; + uint32_t retryLength; + ST25Ftm_InternalTxState_t tx; + ST25Ftm_InternalRxState_t rx; + uint32_t lastTick; +} ST25FTM_InternalState_t; + +extern ST25FTM_InternalState_t gFtmState; + + +/* API for ftm_tx/rx */ +void ST25FTM_CRC_Initialize(void); +ST25FTM_Crc_t ST25FTM_GetCrc(uint8_t *data, uint32_t length, ST25FTM_crc_control_t control); +void logHexBuf(uint8_t* buf, uint32_t len); +ST25FTM_Acknowledge_Status_t ST25FTM_GetAcknowledgeStatus(void); +uint32_t ST25FTM_CompareTime(uint32_t a, uint32_t b); + + +/* From ftm_tx/rx */ +void ST25FTM_State_Init(void); +void ST25FTM_TxStateInit(void); +void ST25FTM_RxStateInit(void); +void ST25FTM_Transmit(void); +void ST25FTM_Receive(void); +void ST25FTM_TxResetSegment(void); + + +#endif /* ST25FTM_COMMON_H */ + diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_config_template.h b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_config_template.h new file mode 100644 index 00000000..25662e5f --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_config_template.h @@ -0,0 +1,138 @@ +/** + ****************************************************************************** + * @file st25ftm_config_template.h + * @author MMY Application Team + * @version 1.0.0 + * @date 29-July-2022 + * @brief Fast Transfer Memory config template header file + * This file should be copied to the application folder and renamed + * to st25ftm_config.h. + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#ifndef __FTM_CONFIG_H__ +#define __FTM_CONFIG_H__ +#include +#include "st25ftm_common.h" + +/*! Fast Transfer Mode buffer access status */ +typedef enum { + ST25FTM_MSG_OK =0, /*!< Message read/write ok */ + ST25FTM_MSG_ERROR, /*!< The peer device doesn't respond */ + ST25FTM_MSG_BUSY /*!< The buffer is not empty while writing */ +} ST25FTM_MessageStatus_t; + +/*! Fast Transfer Mode current message owner */ +typedef enum { + ST25FTM_MESSAGE_EMPTY = 0, /*!< There is no message */ + ST25FTM_MESSAGE_ME = 1, /*!< Current message has been written by this device */ + ST25FTM_MESSAGE_PEER = 2, /*!< Current message has been written by the peer device */ + ST25FTM_MESSAGE_OWNER_ERROR = 3 /*!< An error occured while getting the message owner */ +} ST25FTM_MessageOwner_t; + +typedef enum { + ST25FTM_CRC_START, + ST25FTM_CRC_END, + ST25FTM_CRC_ACCUMULATE, + ST25FTM_CRC_ONESHOT +} ST25FTM_crc_control_t; + +typedef uint32_t ST25FTM_Crc_t; + +#if defined ( __GNUC__ ) && !defined (__CC_ARM) +/* GNU Compiler: packed attribute must be placed after the type keyword */ +#define ST25FTM_PACKED(type) type __attribute__((packed,aligned(1))) +#else +/* ARM Compiler: packed attribute must be placed before the type keyword */ +#define ST25FTM_PACKED(type) __packed type +#endif + +/* Macro used to avoir warnings on disabled features */ +#ifndef UNUSED +#define UNUSED(x) (void)x +#endif + +/*! Length of the buffer used to store a single message data */ +#define ST25FTM_BUFFER_LENGTH (256) + +/*! Define format of the packet length field, when present */ +typedef uint8_t ST25FTM_Packet_Length_t; + +/*! Length of the buffer used to store unvalidated data */ +#define ST25FTM_SEGMENT_LEN (1024 + 16 + 12) + +/*! Define the platform function to get the ms tick */ +#define ST25FTM_TICK() /* call here a function returning the system tick value */ + +/*! Enables the crypto part of the ST25FTM library */ +#define ST25FTM_CRYPTO_ENABLE 0 + +/*! Enables debug traces for the ST25FTM library */ +#define ST25FTM_ENABLE_LOG 0 +#if (ST25FTM_ENABLE_LOG != 0) +#define ST25FTM_LOG(...) /*! Defines the platform logger function */ +#define ST25FTM_HEX2STR(buf,len) /*! Defines the platform function to stringify a data buffer */ +#else +#define ST25FTM_LOG(...) +#define ST25FTM_HEX2STR(buf,len) +#endif + +/* Interface API */ +/* Functions to implement for the platform */ +/*! Check what device wrote the current message in the FTM buffer + * @retval ST25FTM_MESSAGE_EMPTY The buffer is empty. + * @retval ST25FTM_MESSAGE_ME Message has been written by this device. + * @retval ST25FTM_MESSAGE_PEER Message has been written by the peer device. + * @retval ST25FTM_MESSAGE_OWNER_ERROR Message owner cannot be retrieved. + */ +ST25FTM_MessageOwner_t ST25FTM_GetMessageOwner(void); + +/*! Read the content of the FTM buffer. + * @param msg A buffer used to store read data + Buffer length must be greater than ST25FTM_BUFFER_LENGTH. + * @param msg_len A pointer used to return the number of bytes read. + * @retval ST25FTM_MSG_OK Message successfully read. + * @retval ST25FTM_MSG_ERROR Unable to read the message. +*/ +ST25FTM_MessageStatus_t ST25FTM_ReadMessage(uint8_t *msg, uint32_t* msg_len); + +/*! Write the FTM buffer. + * @param msg The buffer containing the data to written. + * @param msg_len Number of bytes to write. + * @retval ST25FTM_MSG_OK Message successfully written. + * @retval ST25FTM_MSG_ERROR Unable to write the message (eg: tag has been removed). + * @retval ST25FTM_MSG_BUSY FTM buffer contains a meesage that has not been read yet. +*/ +ST25FTM_MessageStatus_t ST25FTM_WriteMessage(uint8_t* msg, uint32_t msg_len); + +/*! Initialize the NFC device (dynamic tag or reader) for the FTM. +*/ +void ST25FTM_DeviceInit(void); + +/*! Check if the RF field is present (for dynamic tag only) +*/ +void ST25FTM_UpdateFieldStatus(void); + +/*! Initialize the CRC computation */ +void ST25FTM_CRC_Initialize(void); + +/*! Compute a CRC32. + * @param data Buffer containing the data on which the CRC must be computed. + * @param length Number of bytes of data in the buffer. + * @param control Define how to compute the crc: + * - starting from the initial value or from previous crc + * - Adding remaining bytes (less than word) with padding + */ +ST25FTM_Crc_t ST25FTM_GetCrc(uint8_t *data, uint32_t length, ST25FTM_crc_control_t control); + +#endif // __FTM_CONFIG_H__ diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_protocol.h b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_protocol.h new file mode 100644 index 00000000..4391537d --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Inc/st25ftm_protocol.h @@ -0,0 +1,100 @@ +/** + ****************************************************************************** + * @file st25ftm_protocol.h + * @author MMY Application Team + * @version 1.0.0 + * @date 29-July-2022 + * @brief Fast Transfer Memory protocol APIs prototypes + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#ifndef ST25FTM_PROTOCOL_H +#define ST25FTM_PROTOCOL_H +#include + +/*! ST25FTM Transmission state machine states */ +typedef enum { + ST25FTM_WRITE_IDLE, /*!< The state machine is idle */ + ST25FTM_WRITE_CMD, /*!< Start a transfer */ + ST25FTM_WRITE_SEGMENT, /*!< Start a segment */ + ST25FTM_WRITE_PKT, /*!< Write a message */ + ST25FTM_WRITE_WAIT_READ, /*!< Wait a message to be read */ + ST25FTM_WRITE_READ_ACK, /*!< Read the acknowledge from the peeer device */ + ST25FTM_WRITE_DONE, /*!< End of the transfer */ + ST25FTM_WRITE_ERROR, /*!< An unrecoverable error occured */ +} ST25FTM_TxState_t; + +/*! ST25FTM Reception state machine states */ +typedef enum { + ST25FTM_READ_IDLE, /*!< The state machine is idle */ + ST25FTM_READ_CMD, /*!< Start the reception */ + ST25FTM_READ_PKT, /*!< Read a message */ + ST25FTM_READ_WRITE_ACK, /*!< Write an acknowledge */ + ST25FTM_READ_WRITE_NACK, /*!< Write a non-ack */ + ST25FTM_READ_WRITE_ERR, /*!< Write an error while checking data integrity */ + ST25FTM_READ_WAIT_ACK_READ, /*!< Wait the ack to be read */ + ST25FTM_READ_BUFFER_FULL, /*!< Reserved for futur use */ + ST25FTM_READ_DONE, /*!< Reception has completed */ + ST25FTM_READ_ERROR /*!< An unrecoverable error occured */ +} ST25FTM_RxState_t; + +/*! ST25FTM main state machine states */ +typedef enum { + ST25FTM_IDLE = 0, /*!< The state machine is idle */ + ST25FTM_WRITE, /*!< A transmission is on-going */ + ST25FTM_READ /*!< A reception is on-going */ +} ST25FTM_State_t; + +/*! RF field state (for dynamic tag) */ +typedef enum { + ST25FTM_FIELD_OFF, /*!< RF field is OFF */ + ST25FTM_FIELD_ON /*!< RF field is ON */ +} ST25FTM_Field_State_t; + +/*! Handshake selection */ +typedef enum { + ST25FTM_SEND_WITHOUT_ACK=0, /*!< The transfer does not require acknowledges*/ + ST25FTM_SEND_WITH_ACK=1, /*!< The transfer requests acknowledges from peer device */ +} ST25FTM_Send_Ack_t; + +/*! Prototype for callbacks to get data to send or to write received data */ +typedef void (*ftm_data_cb)(uint8_t* buf, uint8_t *src, uint32_t length); + +void ST25FTM_Init(void); +void ST25FTM_SendCommand(uint8_t* data, uint32_t length, ST25FTM_Send_Ack_t ack, ftm_data_cb data_cb); +void ST25FTM_ReceiveCommand(uint8_t* data, uint32_t *length, ftm_data_cb data_cb); +void ST25FTM_Runner(void); + +ST25FTM_State_t ST25FTM_Status(void); +void ST25FTM_SetTxFrameMaxLength(uint32_t len); +uint32_t ST25FTM_GetTxFrameMaxLength(void); +void ST25FTM_SetRxFrameMaxLength(uint32_t len); +uint32_t ST25FTM_GetRxFrameMaxLength(void); +uint8_t ST25FTM_IsNewFrame(void); +ST25FTM_Field_State_t ST25FTM_GetFieldState(void); +uint32_t ST25FTM_GetTransferProgress(void); +uint32_t ST25FTM_GetAvailableDataLength(void); +uint8_t ST25FTM_ReadBuffer(uint8_t *dst, uint32_t length); +uint32_t ST25FTM_GetReadBufferOffset(void); +uint32_t ST25FTM_GetTotalLength(void); +uint32_t ST25FTM_GetRetryLength(void); +uint8_t ST25FTM_IsReceptionComplete(void); +uint8_t ST25FTM_IsTransmissionComplete(void); +uint8_t ST25FTM_CheckError(void); +uint8_t ST25FTM_IsIdle(void); +void ST25FTM_Reset(void); +void ST25FTM_SetTxSegmentMaxLength(uint32_t length); +uint32_t ST25FTM_GetTxSegmentMaxLength(void); +void ST25FTM_ResetTxSegmentMaxLength(void); + +#endif diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/LICENSE.txt b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/LICENSE.txt new file mode 100644 index 00000000..6eaa6240 --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/LICENSE.txt @@ -0,0 +1,63 @@ +This software component is provided to you as part of a software package and +applicable license terms are in the Package_license file. If you received this +software component outside of a package or without applicable license terms, +the terms of the SLA0051 license shall apply and are fully reproduced below: + +SLA0051 Rev2/May 2020 + + +SOFTWARE LICENSE AGREEMENT (“Agreement”) + +BY CLICKING ON THE "I ACCEPT" BUTTON OR BY UNZIPPING, INSTALLING, COPYING, DOWNLOADING, ACCESSING +OR OTHERWISE USING THIS SOFTWARE (HEREINAFTER “SOFTWARE” MEANS THE RELATED SOFTWARE, +DOCUMENTATION, OTHER MATERIALS, AND ANY PARTS, PERMITTED MODIFICATIONS, AND PERMITTED +DERIVATIVES THEREOF) FROM STMICROELECTRONICS INTERNATIONAL N.V, SWISS BRANCH AND/OR ITS +AFFILIATED COMPANIES (“STMICROELECTRONICS”), THE RECIPIENT, ON BEHALF OF HIMSELF OR HERSELF, OR ON +BEHALF OF ANY ENTITY BY WHICH SUCH RECIPIENT IS EMPLOYED AND/OR ENGAGED (“YOU”) AGREES TO BE +BOUND BY THIS AGREEMENT. + +You represent that you have the authority to enter into this Agreement. You will comply with all laws, including export laws. +STMicroelectronics’s failure or delay to enforce this Agreement does not waive STMicroelectronics’s rights. Swiss law, except +conflict of laws, governs this Agreement, and the parties consent to exclusive jurisdiction of courts in Switzerland for litigation of +this Agreement. + +Subject to the below disclaimer, the redistribution, reproduction and use in source and binary forms of the software or any part +thereof, with or without modification, are permitted provided that the following conditions are met: +1. Redistribution of source code (modified or not) must retain any copyright notice, this list of conditions and the following +disclaimer. +2. Redistributions in binary form, except as embedded into a microcontroller or microprocessor device or a software update +for such device, must reproduce any copyright notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of STMicroelectronics nor the names of other contributors to this software may be used to endorse or +promote products using or derived from this software or part thereof without specific written permission. +4. This software or any part thereof, including modifications and/or derivative works of this software, must be used and +execute solely and exclusively in combination with an integrated circuit that is manufactured by or for STMicroelectronics +and is an NFC tag, NFC dynamic tag, NFC reader, or UHF reader. +5. No use, reproduction or redistribution of this software may be done in any manner that would subject this software to any +Open Source Terms. “Open Source Terms” shall mean any open source license which requires as part of distribution of +software that the source code of such software is distributed therewith or otherwise made available, or open source license +that substantially complies with the Open Source definition specified at www.opensource.org and any other comparable +open source license such as for example GNU General Public License (GPL), Eclipse Public License (EPL), Apache +Software License, BSD license and MIT license. +6. STMicroelectronics has no obligation to provide any maintenance, support or updates for the software. +7. The software is and will remain the exclusive property of STMicroelectronics and its licensors. The recipient will not take +any action that jeopardizes STMicroelectronics and its licensors' proprietary rights or acquire any rights in the software, +except the limited rights specified hereunder. +8. Redistribution and use of this software partially or any part thereof other than as permitted under this license is void and +will automatically terminate your rights under this license. + +DISCLAIMER + +THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS, +IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY +INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT +SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. +EXCEPT AS EXPRESSLY PERMITTED HEREUNDER, NO LICENSE OR OTHER RIGHTS, WHETHER EXPRESS OR +IMPLIED, ARE GRANTED UNDER ANY PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS OF +STMICROELECTRONICS OR ANY THIRD PARTY \ No newline at end of file diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_common.c b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_common.c new file mode 100644 index 00000000..a79b216e --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_common.c @@ -0,0 +1,97 @@ +/** + ****************************************************************************** + * @file st25ftm_common.c + * @author MMY Application Team + * @version 1.0.0 + * @date 29-July-2022 + * @brief Fast Transfer Memory protocol common file to utilities + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#include +#include "st25ftm_common.h" +#include "st25ftm_config.h" + +ST25FTM_InternalState_t gFtmState; + +void logHexBuf(uint8_t* buf, uint32_t len) +{ +#if (ST25FTM_ENABLE_LOG != 0) + if(len > 32) + { + char data[40]; + memset(data,0,sizeof(data)); + memcpy(data, ST25FTM_HEX2STR((uint8_t*)(buf + len - 16),16),32); + ST25FTM_LOG("%s...%s\r\n",ST25FTM_HEX2STR(buf,16),data); + } else { + ST25FTM_LOG("%s\r\n",ST25FTM_HEX2STR(buf,len)); + } +#else + UNUSED(buf); + UNUSED(len); +#endif +} + +ST25FTM_Acknowledge_Status_t ST25FTM_GetAcknowledgeStatus(void) +{ + uint8_t msg[ST25FTM_BUFFER_LENGTH]; + uint32_t msg_len = 0U; + ST25FTM_Acknowledge_Status_t status; + if(ST25FTM_GetMessageOwner() == ST25FTM_MESSAGE_PEER) + { + if(ST25FTM_ReadMessage(msg, &msg_len) != ST25FTM_MSG_OK) + { + status = ST25FTM_ACK_BUSY; + } else { + if(msg[0] == ((uint8_t)ST25FTM_SEGMENT_OK | (uint8_t)ST25FTM_STATUS_BYTE)) + { + status = ST25FTM_SEGMENT_OK; + } else if (msg[0] == ((uint8_t)ST25FTM_CRC_ERROR | (uint8_t)ST25FTM_STATUS_BYTE)) + { + status = ST25FTM_CRC_ERROR; + } else { + /* Unexpected value, this is not a ACK */ + status = ST25FTM_ACK_ERROR; + } + } + } else { + status = ST25FTM_ACK_BUSY; + } + return status; +} + +void ST25FTM_State_Init(void) +{ + gFtmState.state = ST25FTM_IDLE; + gFtmState.lastState = ST25FTM_IDLE; + gFtmState.rfField = ST25FTM_FIELD_OFF; + gFtmState.totalDataLength = 0; + gFtmState.retryLength = 0; + gFtmState.lastTick = ST25FTM_TICK(); + + ST25FTM_TxStateInit(); + ST25FTM_RxStateInit(); +} + + +uint32_t ST25FTM_CompareTime(uint32_t a, uint32_t b) +{ + uint32_t result; + if(a > b) + { + result = a - b; + } else { + result = b - a; + } + return result; +} diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_protocol.c b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_protocol.c new file mode 100644 index 00000000..88aaf8a6 --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_protocol.c @@ -0,0 +1,441 @@ +/** + ****************************************************************************** + * @file st25ftm_protocol.c + * @author MMY Application Team + * @version 1.0.0 + * @date 29-July-2022 + * @brief Fast Transfer Memory protocol APIs + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#include +#include +#include +#include "st25ftm_protocol.h" +#include "st25ftm_common.h" +#include "st25ftm_config.h" + +#if (ST25FTM_ENABLE_LOG != 0) +/*! String version of the FTM Reception state machine for display */ +const char * ST25FTM_RxState_Str[] = { + + "FTM_READ_IDLE", + "FTM_READ_CMD", + "FTM_READ_PKT", + "FTM_READ_WRITE_ACK", + "FTM_READ_WRITE_NACK", + "FTM_READ_WRITE_ERR", + "FTM_READ_WAIT_ACK_READ", + "FTM_READ_BUFFER_FULL", + "FTM_READ_DONE", + "FTM_READ_ERROR" +}; + +/*! String version of the FTM main state machine for display */ +const char * ST25FTM_State_Str[] = { + "FTM_IDLE", + "FTM_WRITE", + "FTM_READ" +}; + +/*! String version of the FTM transmission state machine for display */ +const char * ST25FTM_TxState_Str[] = { + "FTM_WRITE_IDLE", + "FTM_WRITE_CMD", + "FTM_WRITE_SEGMENT", + "FTM_WRITE_PKT", + "FTM_WRITE_WAIT_READ", + "FTM_WRITE_READ_ACK", + "FTM_WRITE_DONE", + "FTM_WRITE_ERROR", +}; +#endif + +/*! Initialize the FTM state machines and the NFC device */ +void ST25FTM_Init(void) +{ + ST25FTM_State_Init(); + + (void)ST25FTM_DeviceInit(); + + ST25FTM_UpdateFieldStatus(); +} + +/*! Register the maximum frame length while transmitting + * @param len Maximum frame length in bytes + */ +void ST25FTM_SetTxFrameMaxLength(uint32_t len) +{ + gFtmState.tx.frameMaxLength = len; +} + +/*! Get the maximum frame length while transmitting + * @return The maxmum number of bytes per transmitted frame + */ +uint32_t ST25FTM_GetTxFrameMaxLength(void) +{ + return gFtmState.tx.frameMaxLength; +} + +/*! Register the maximum frame length while receiving + * @param len Maximum frame length in bytes + */ +void ST25FTM_SetRxFrameMaxLength(uint32_t len) +{ + gFtmState.rx.frameMaxLength = len; +} + +/*! Get the maximum frame length while receiving + * @return The maxmum number of bytes per received frame + */ +uint32_t ST25FTM_GetRxFrameMaxLength(void) +{ + return gFtmState.rx.frameMaxLength; +} + + +/*! Initialize a transmission + * @param data Pointer to the data buffer to be transmitted + * @param length Number of bytes to be transmitted + * @param ack Enables handchecks during the transfer + * @param data_cb Optional callback function, called to request data to send (to be set to NULL if not used) + */ +void ST25FTM_SendCommand(uint8_t* data, uint32_t length, ST25FTM_Send_Ack_t ack, ftm_data_cb data_cb) +{ + gFtmState.tx.cmdPtr = data; + gFtmState.tx.cmdLen = length; + gFtmState.tx.state = ST25FTM_WRITE_IDLE; + gFtmState.state = ST25FTM_WRITE; + gFtmState.tx.sendAck = ack; + gFtmState.tx.getdata_cb = data_cb; +} + + +/*! Initialize a reception + * @param data Pointer to the data buffer used for the reception + * @param length Pointer to a word defining the maximum number of bytes that can be received. + This parameter is also used to return the number of bytes actually read + * @param ack Enables handchecks during the transfer + * @param data_cb Optional callback function, called to write received datad (to be set to NULL if not used) + */ +void ST25FTM_ReceiveCommand(uint8_t* data, uint32_t *length, ftm_data_cb data_cb) +{ + gFtmState.rx.cmdPtr = data; + gFtmState.rx.cmdLen = length; + gFtmState.rx.maxCmdLen = *length; + gFtmState.rx.state = ST25FTM_READ_IDLE; + gFtmState.state = ST25FTM_READ; + gFtmState.rx.recvdata_cb = data_cb; +} + +/*! Run the FTM state machine */ +void ST25FTM_Runner(void) +{ + static ST25FTM_Field_State_t lastRfField = ST25FTM_FIELD_OFF; + if(gFtmState.state != gFtmState.lastState) + { + ST25FTM_LOG("State = %s\r\n",ST25FTM_State_Str[gFtmState.state]); + gFtmState.lastState = gFtmState.state; + } + + ST25FTM_UpdateFieldStatus(); + /* Do nothing if field is off */ + if(gFtmState.rfField == ST25FTM_FIELD_OFF) + { + lastRfField = ST25FTM_FIELD_OFF; + } else { + /* a field off occured while transmitting, restart at the beg of the segment + We don't know if last packet has been read or not => restart segment */ + if((lastRfField == ST25FTM_FIELD_OFF) && (gFtmState.rfField == ST25FTM_FIELD_ON)) + { + if((gFtmState.state == ST25FTM_WRITE) && (gFtmState.tx.state >= ST25FTM_WRITE_SEGMENT)) + { + ST25FTM_LOG("FIELD OFF while transmiting, restart segment\r\n"); + ST25FTM_LOG(" segmentRemainingData=%d\r\n",gFtmState.tx.segmentRemainingData); + ST25FTM_LOG(" pktIndex=%d\r\n",gFtmState.tx.pktIndex); + ST25FTM_LOG(" segmentPtr=%X\r\n",gFtmState.tx.segmentPtr); + ST25FTM_LOG(" segmentIndex=%d\r\n",gFtmState.tx.segmentIndex); + ST25FTM_LOG(" state=%s\r\n",ST25FTM_TxState_Str[gFtmState.tx.state]); + ST25FTM_TxResetSegment(); + ST25FTM_LOG("After reset:\r\n"); + ST25FTM_LOG(" segmentRemainingData=%d\r\n",gFtmState.tx.segmentRemainingData); + ST25FTM_LOG(" pktIndex=%d\r\n",gFtmState.tx.pktIndex); + ST25FTM_LOG(" segmentPtr=%X\r\n",gFtmState.tx.segmentPtr); + ST25FTM_LOG(" segmentIndex=%d\r\n",gFtmState.tx.segmentIndex); + ST25FTM_LOG(" state=%s\r\n",ST25FTM_TxState_Str[gFtmState.tx.state]); + gFtmState.lastTick = ST25FTM_TICK(); + + } + } + + if(gFtmState.state == ST25FTM_WRITE) + { + if(gFtmState.tx.state != gFtmState.tx.lastState) + { + ST25FTM_LOG("TxState = %s\r\n",ST25FTM_TxState_Str[gFtmState.tx.state]); + gFtmState.tx.lastState = gFtmState.tx.state; + gFtmState.lastTick = ST25FTM_TICK(); + } + if((ST25FTM_CompareTime(ST25FTM_TICK(),gFtmState.lastTick) > ST25FTM_WAIT_TIMEOUT) + && (gFtmState.tx.state == ST25FTM_WRITE_READ_ACK)) + { + /* a timeout occured while waiting for the RF to read packet or write a ack + reset segment transmission */ + ST25FTM_LOG("Timeout while transmitting, restart segment\r\n"); + ST25FTM_TxResetSegment(); + gFtmState.lastTick = ST25FTM_TICK(); + } + ST25FTM_Transmit(); + } else if (gFtmState.state == ST25FTM_READ) + { + if(gFtmState.rx.state != gFtmState.rx.lastState) + { + ST25FTM_LOG("RxState = %s\r\n",ST25FTM_RxState_Str[gFtmState.rx.state]); + gFtmState.rx.lastState = gFtmState.rx.state; + gFtmState.lastTick = ST25FTM_TICK(); + } + ST25FTM_Receive(); + } else { + /* do nothing */ + } + lastRfField = gFtmState.rfField; + } +} + +/*! Get the current FTM state. Resets the state machine in case an error occured during the transmission. + * @retval ST25FTM_IDLE State machine is Idle, the transfer is over. + * @retval ST25FTM_READ Reception is on-going. + * @retval ST25FTM_WRITE Transmission is on-going. + */ +ST25FTM_State_t ST25FTM_Status(void) +{ + ST25FTM_State_t state = gFtmState.state; + if(gFtmState.state == ST25FTM_READ) + { + if((gFtmState.rx.state == ST25FTM_READ_DONE) || (gFtmState.rx.state == ST25FTM_READ_ERROR)) + { + /* let the application reset the state machine */ + } + } + if(gFtmState.state == ST25FTM_WRITE) + { + if((gFtmState.tx.state == ST25FTM_WRITE_DONE) || (gFtmState.tx.state == ST25FTM_WRITE_ERROR)) + { + ST25FTM_Reset(); + } + } + return state; +} + +/*! Detect that a new reception has started. + * @retval 1 when a new recpetion has started since last call + * @retval 0 otherwise +*/ +uint8_t ST25FTM_IsNewFrame(void) +{ + uint8_t status; + if(gFtmState.rx.isNewFrame != 0U) + { + ST25FTM_LOG("*** Rx New Frame ***\r\n"); + gFtmState.rx.isNewFrame = 0U; + status = 1U; + } else { + status = 0U; + } + return status; +} + +/*! Compute the current transfer progress. + * @return THe current transfer progress (percentage). + */ +uint32_t ST25FTM_GetTransferProgress(void) +{ + uint32_t progress; + if(gFtmState.state == ST25FTM_WRITE) + { + progress = ((gFtmState.tx.cmdLen - gFtmState.tx.remainingData - gFtmState.tx.segmentRemainingData) * 100U) / gFtmState.tx.cmdLen; + } else if (gFtmState.state == ST25FTM_READ) + { + progress = (gFtmState.rx.totalValidReceivedLength * 100U) / *gFtmState.rx.cmdLen; + } else { + progress = 0; + } + return progress; +} + +/*! Get the number of byte received. + It can be used by the application to process the received data before transfer completion. + * @return The current number of valid bytes received. + */ +uint32_t ST25FTM_GetAvailableDataLength(void) +{ + return gFtmState.rx.validReceivedLength; + } + +/*! Read received bytes during the transmission, freeing space to continue the reception. + It can be used by the application to process the received data before transfer completion. + * @param dst The buffer to copy the received data. + * @param length Number of bytes to copy. + * @retval 0 if the data has been copied. + * @retval 1 otherwise. + */ +uint8_t ST25FTM_ReadBuffer(uint8_t *dst, uint32_t length) +{ + uint8_t status; + if(length <= gFtmState.rx.validReceivedLength) + { + gFtmState.rx.receivedLength -= length; + gFtmState.rx.validReceivedLength -= length; + (void)memcpy(dst,gFtmState.rx.cmdPtr,length); + (void)memmove(gFtmState.rx.cmdPtr,&gFtmState.rx.cmdPtr[length],gFtmState.rx.receivedLength); + gFtmState.rx.dataPtr -= length; + gFtmState.rx.segmentPtr -= length; + gFtmState.rx.readBufferOffset += length; + status = 0; + } else { + status = 1; + } + return status; +} + +/*! Get the current offset in the command of the next byte that will be read with ST25FTM_ReadBuffer. + It can be used by the application to keep track of the incoming data before the transfer completes. + * @return The offset of the next byte to read. + */ +uint32_t ST25FTM_GetReadBufferOffset(void) +{ + return gFtmState.rx.readBufferOffset; +} + +/*! Get the current field state (only relevant for dynamic tag device). + * @retval 1 if RF field is present. + * @retval 0 otherwise. +*/ +ST25FTM_Field_State_t ST25FTM_GetFieldState(void) +{ + return gFtmState.rfField; +} + + +/*! Get the total length of the transfer (including protocol metadata). + * @return The total number of bytes transfered. +*/ +uint32_t ST25FTM_GetTotalLength(void) +{ + return gFtmState.totalDataLength; +} + +/*! Get the number of bytes that have been resent. + * @return The number of bytes that have been resent during this transfer. +*/ +uint32_t ST25FTM_GetRetryLength(void) +{ + return gFtmState.retryLength; +} + +/*! Check if the reception has been completed. + * @retval 1 is the reception has completed. + * @retval 0 otherwise. +*/ +uint8_t ST25FTM_IsReceptionComplete(void) +{ + uint8_t isRxCompleted; + if ((gFtmState.state == ST25FTM_READ) && (gFtmState.rx.state == ST25FTM_READ_DONE)) + { + isRxCompleted = 1; + } else { + isRxCompleted = 0; + } + return isRxCompleted; +} + +/*! Check if the transmission has been completed. + * @retval 1 is the transmission has completed. + * @retval 0 otherwise. +*/ +uint8_t ST25FTM_IsTransmissionComplete(void) +{ + uint8_t isTxCompleted; + if ((gFtmState.state == ST25FTM_WRITE) && (gFtmState.tx.state == ST25FTM_WRITE_DONE)) + { + isTxCompleted = 1; + } else { + isTxCompleted = 0; + } + return isTxCompleted; +} + +/*! Check if the ST25FTM state machine is idle. + * @retval 1 The state machine is Idle. + * @retval 0 otherwise. +*/ +uint8_t ST25FTM_IsIdle(void) +{ + uint8_t isIdle; + if (gFtmState.state == ST25FTM_IDLE) + { + isIdle = 1; + } else { + isIdle = 0; + } + return isIdle; +} + +/*! Check if an error occured. + * @retval 1 An error occured. + * @retval 0 otherwise. +*/ +uint8_t ST25FTM_CheckError(void) +{ + uint8_t isError; + if((gFtmState.rx.state == ST25FTM_READ_ERROR) || (gFtmState.tx.state == ST25FTM_WRITE_ERROR)) + { + isError = 1; + } else { + isError = 0; + } + return isError; +} + +/*! Reset the ST25FTM state machine. + */ +void ST25FTM_Reset(void) +{ + gFtmState.tx.cmdPtr = NULL; + gFtmState.tx.cmdLen = 0; + gFtmState.tx.state = ST25FTM_WRITE_IDLE; + gFtmState.rx.state = ST25FTM_READ_IDLE; + gFtmState.state = ST25FTM_IDLE; +} + + +/*! Set the lenth of a segment (in bytes, max value is ST25FTM_SEGMENT_LEN) */ +void ST25FTM_SetTxSegmentMaxLength(uint32_t length) +{ + if(length <= ST25FTM_SEGMENT_LEN) + { + gFtmState.tx.segmentMaxLength = length; + } +} + +/*! Reset the lenth of a segment to its max value ST25FTM_SEGMENT_LEN */ +void ST25FTM_ResetTxSegmentMaxLength(void) +{ + gFtmState.tx.segmentMaxLength = ST25FTM_SEGMENT_LEN; +} + +/*! Get the lenth of a segment (in bytes) */ +uint32_t ST25FTM_GetTxSegmentMaxLength(void) +{ + return gFtmState.tx.segmentMaxLength; +} diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_rx.c b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_rx.c new file mode 100644 index 00000000..5d03c7ac --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_rx.c @@ -0,0 +1,488 @@ +/** + ****************************************************************************** + * @file st25ftm_rx.c + * @author MMY Application Team + * @version 1.0.0 + * @date 29-July-2022 + * @brief Fast Transfer Memory manage function for data receive + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#include "st25ftm_common.h" +#include "st25ftm_config.h" +#include + +static void ST25FTM_SetData(uint8_t* buf, uint8_t* src, uint32_t length) +{ + if(gFtmState.rx.recvdata_cb == NULL) + { + /* Default case: simply copy packet data into destination buffer */ + (void)memcpy(buf,src,length); + } else { + /* Let's the application manage the received data */ + gFtmState.rx.recvdata_cb(buf,src,length); + } +} + + +void ST25FTM_RxStateInit(void) +{ + gFtmState.rx.state = ST25FTM_READ_IDLE; + gFtmState.rx.lastState = ST25FTM_READ_IDLE; + gFtmState.rx.frameMaxLength = 0xFF; + gFtmState.rx.isNewFrame = 0; + gFtmState.rx.cmdPtr = NULL; + gFtmState.rx.cmdLen = NULL; + gFtmState.rx.maxCmdLen = 0; + gFtmState.rx.nbError = 0; + gFtmState.rx.unrecoverableError=0; + gFtmState.rx.receivedLength = 0; + gFtmState.rx.validReceivedLength = 0; + gFtmState.rx.totalValidReceivedLength = 0; + gFtmState.rx.segmentPtr = NULL; + gFtmState.rx.dataPtr = NULL; + gFtmState.rx.validLength = 0; + gFtmState.rx.lastAck = 0; + gFtmState.rx.rewriteOnFieldOff = 0; + gFtmState.rx.ignoreRetransSegment = 0; + gFtmState.rx.segmentNumber = 0; + +} + +static ST25FTM_Packet_t ST25FTM_Unpack(uint8_t *msg) +{ + ST25FTM_Packet_t pkt = {0}; + uint32_t hdr_len = sizeof(pkt.ctrl); + pkt.ctrl.byte = msg[0]; + + if(ST25FTM_CTRL_HAS_PKT_LEN(pkt.ctrl)) + { + pkt.length = ST25FTM_GET_PKT_LEN(msg); + hdr_len += sizeof(ST25FTM_Packet_Length_t); + + if(ST25FTM_CTRL_HAS_TOTAL_LEN(pkt.ctrl)) + { + pkt.totalLength = ST25FTM_GET_TOTAL_LEN_WITH_LEN(msg); + hdr_len +=sizeof(pkt.totalLength); + } + + } else { + if(ST25FTM_CTRL_HAS_TOTAL_LEN(pkt.ctrl)) + { + pkt.totalLength = msg[1]; + pkt.totalLength = (pkt.totalLength << 8U) + msg[2]; + pkt.totalLength = (pkt.totalLength << 8U) + msg[3]; + pkt.totalLength = (pkt.totalLength << 8U) + msg[4]; + hdr_len +=sizeof(pkt.totalLength); + } + pkt.length = gFtmState.rx.frameMaxLength - hdr_len; + } + if(ST25FTM_CTRL_HAS_CRC(pkt.ctrl)) + { + /* The pkt.length count the crc bytes, remove them */ + pkt.length -= 4; + } + + /* compute the begining of the payload */ + pkt.data = msg; + pkt.data += hdr_len; + return pkt; +} + +static void ST25FTM_RewindSegment(void) +{ + gFtmState.rx.dataPtr -= gFtmState.rx.segmentLength; + gFtmState.rx.receivedLength -= gFtmState.rx.segmentLength; + gFtmState.rx.segmentLength = 0; + if(gFtmState.rx.dataPtr < gFtmState.rx.cmdPtr) + { + gFtmState.rx.lastError = 11; + ST25FTM_LOG("FtmRxError11: data pointer out of band\r\n"); + } +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateRxIdle(void) +{ + gFtmState.rx.segmentPtr = gFtmState.rx.cmdPtr; + gFtmState.rx.dataPtr = gFtmState.rx.cmdPtr; + gFtmState.rx.segmentLength = 0; + gFtmState.rx.receivedLength = 0; + gFtmState.rx.validReceivedLength = 0; + gFtmState.rx.totalValidReceivedLength = 0; + ST25FTM_CRC_Initialize(); + gFtmState.rx.state = ST25FTM_READ_CMD; + gFtmState.totalDataLength=0; + gFtmState.retryLength = 0; + gFtmState.rx.state = ST25FTM_READ_CMD; + gFtmState.rx.segmentNumber = 0; + gFtmState.rx.readBufferOffset = 0; + return ST25FTM_STATE_MACHINE_CONTINUE; +} + + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateRxCommand(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_RELEASE; + + if(gFtmState.rx.receivedLength >= (gFtmState.rx.maxCmdLen)) + { + /* ERROR: receive more data than we can handle */ + gFtmState.rx.nbError++; + gFtmState.rx.unrecoverableError=1; + gFtmState.rx.state = ST25FTM_READ_CMD; + gFtmState.rx.lastError = 0; + ST25FTM_LOG("FtmRxError0 too much data received\r\n"); + ST25FTM_LOG("gFtmState.rx.receivedLength=%d\r\n",gFtmState.rx.receivedLength); + ST25FTM_LOG("gFtmState.rx.maxCmdLen=%d\r\n",gFtmState.rx.maxCmdLen); + ST25FTM_RewindSegment(); + } else if(ST25FTM_GetMessageOwner() == ST25FTM_MESSAGE_PEER) { + gFtmState.rx.rewriteOnFieldOff = 0; + gFtmState.rx.state = ST25FTM_READ_PKT; + + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else { + /* no error, do nothing */ + } + return control; +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateRxPacket(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_RELEASE; + uint8_t msg[ST25FTM_BUFFER_LENGTH]; + uint32_t msg_len = 0; + ST25FTM_Packet_t pkt; + + (void)memset(msg,0,sizeof(msg)); + if(ST25FTM_ReadMessage(msg, &msg_len) != ST25FTM_MSG_OK) + { + /* Cannot read MB, retry later */ + gFtmState.rx.state = ST25FTM_READ_PKT; + control = ST25FTM_STATE_MACHINE_RELEASE; + } else { + + ST25FTM_LOG("Rx "); + logHexBuf(msg,msg_len); + if(msg_len == 0U) + { + gFtmState.rx.lastError = 5; + ST25FTM_LOG("FtmRxError5 len = 0\r\n"); + gFtmState.rx.nbError++; + gFtmState.rx.state = ST25FTM_READ_ERROR; + control = ST25FTM_STATE_MACHINE_RELEASE; + } else { + + pkt = ST25FTM_Unpack(msg); + + gFtmState.rx.pktPosition = (ST25FTM_Packet_Position_t)pkt.ctrl.b.position; + if((pkt.ctrl.b.position == (uint8_t)ST25FTM_SINGLE_PACKET) || (pkt.ctrl.b.position == (uint8_t)ST25FTM_FIRST_PACKET)) + { + gFtmState.rx.segmentNumber = 0; + if (pkt.ctrl.b.position == (uint8_t)ST25FTM_SINGLE_PACKET) + { + /* pkt length represents the sent data including encryption (but not crc) */ + *gFtmState.rx.cmdLen = pkt.length; + } else { + /* this represents the payload (unencrypted) length */ + *gFtmState.rx.cmdLen = pkt.totalLength; + } + + if(*gFtmState.rx.cmdLen >= (gFtmState.rx.maxCmdLen)) + { + gFtmState.rx.lastError = 17; + gFtmState.rx.nbError++; + gFtmState.rx.unrecoverableError=1; + ST25FTM_LOG("FtmRxError17 Transfer is bigger than reception buffer\r\n"); + } + + if(gFtmState.totalDataLength > 0U) + { + gFtmState.retryLength += gFtmState.totalDataLength; + ST25FTM_LOG("FtmRxWarning0 Command restarted, length=%d\r\n",gFtmState.retryLength); + } + gFtmState.totalDataLength = msg_len; + gFtmState.rx.isNewFrame = 1; + if(gFtmState.rx.receivedLength != 0U) + { + /* the transmitter started a new command without completing the last one */ + gFtmState.rx.dataPtr = gFtmState.rx.cmdPtr; + gFtmState.rx.segmentPtr = gFtmState.rx.cmdPtr; + gFtmState.rx.segmentLength = 0U; + gFtmState.rx.receivedLength = 0U; + gFtmState.rx.validReceivedLength = 0U; + gFtmState.rx.totalValidReceivedLength = 0U; + gFtmState.rx.readBufferOffset = 0U; + } + } else { + if (gFtmState.totalDataLength == 0U) + { + /* we missed first packet: continue the reception and ask for retransmission */ + gFtmState.rx.nbError++; + gFtmState.rx.lastError = 13; + ST25FTM_LOG("FtmRxError13 First packet missed\r\n"); + } else { + /* no error, so do nothing */ + } + gFtmState.totalDataLength += msg_len; + + } + + if(((pkt.ctrl.b.ackCtrl & (uint8_t)ST25FTM_SEGMENT_START) != 0U) + || (pkt.ctrl.b.ackCtrl == (uint8_t)ST25FTM_ACK_SINGLE_PKT)) + { + ST25FTM_LOG("Starting Segment %d\r\n", gFtmState.rx.segmentNumber); + /* detect retransmission */ + gFtmState.rx.ignoreRetransSegment = 0U; + if(pkt.ctrl.b.segId != (gFtmState.rx.segmentNumber % 2U)) + { + ST25FTM_LOG("Retransmission %d\r\n", pkt.ctrl.b.segId ); + /* segment is retransmitted, so don't take it into account */ + gFtmState.rx.ignoreRetransSegment = 1; + } + + if(gFtmState.rx.segmentLength > 0U) + { + gFtmState.retryLength += gFtmState.rx.segmentLength; + ST25FTM_LOG("FtmRxWarning2 Segment restarted, retryLength=%d\r\n",gFtmState.retryLength); + ST25FTM_LOG("segmentLength=%d\r\n",gFtmState.rx.segmentLength); + ST25FTM_LOG("receivedLength=%d\r\n",gFtmState.rx.receivedLength); + ST25FTM_LOG("totalValidReceivedLength=%d\r\n",gFtmState.rx.totalValidReceivedLength); + } + + /* rewind if necessary (i.e. when segment_data > 0) + means that segment has been restarted */ + ST25FTM_RewindSegment(); + } + if((gFtmState.rx.receivedLength + pkt.length) > gFtmState.rx.maxCmdLen) + { + /* ERROR: receive more data than we can handle + this may happen if we miss several start of segment, restart segment */ + gFtmState.rx.nbError++; + gFtmState.rx.state = ST25FTM_READ_CMD; + gFtmState.rx.lastError = 14; + ST25FTM_LOG("FtmRxError14 too much data received\r\n"); + ST25FTM_LOG("gFtmState.rx.receivedLength=%d\r\n",gFtmState.rx.receivedLength); + ST25FTM_LOG("gFtmState.rx.maxCmdLen=%d\r\n",gFtmState.rx.maxCmdLen); + ST25FTM_RewindSegment(); + control = ST25FTM_STATE_MACHINE_RELEASE; + } else { + + //(void)memcpy(gFtmState.rx.dataPtr,pkt.data,pkt.length); + /* don't register the data, if it has already been successfully transmitted */ + if(gFtmState.rx.ignoreRetransSegment == 0) + { + ST25FTM_SetData(gFtmState.rx.dataPtr,pkt.data,pkt.length); + } + gFtmState.rx.dataPtr += pkt.length; + gFtmState.rx.segmentLength += pkt.length; + gFtmState.rx.receivedLength += pkt.length; + + if(pkt.ctrl.b.ackCtrl == (uint8_t)(ST25FTM_SEGMENT_START)) + { + ST25FTM_GetCrc(pkt.data,pkt.length,ST25FTM_CRC_START); + gFtmState.rx.state = ST25FTM_READ_CMD; + } else if ((pkt.ctrl.b.ackCtrl == (uint8_t)ST25FTM_SEGMENT_END) || (pkt.ctrl.b.ackCtrl == (uint8_t)ST25FTM_ACK_SINGLE_PKT)) + { + ST25FTM_Crc_t computed_crc = 0; + if(pkt.ctrl.b.ackCtrl == (uint8_t)ST25FTM_ACK_SINGLE_PKT) + { + computed_crc = ST25FTM_GetCrc(pkt.data,pkt.length,ST25FTM_CRC_ONESHOT); + } else if (pkt.ctrl.b.ackCtrl == (uint8_t)ST25FTM_SEGMENT_END) { + computed_crc = ST25FTM_GetCrc(pkt.data,pkt.length,ST25FTM_CRC_END); + } + gFtmState.rx.validLength = gFtmState.rx.segmentLength; + uint8_t* crc_p = pkt.data + pkt.length; + uint32_t segment_crc = crc_p[0]; + segment_crc = (segment_crc << 8) + crc_p[1]; + segment_crc = (segment_crc << 8) + crc_p[2]; + segment_crc = (segment_crc << 8) + crc_p[3]; + if(segment_crc == computed_crc) + { + gFtmState.rx.state = ST25FTM_READ_WRITE_ACK; + } else { + gFtmState.rx.state = ST25FTM_READ_WRITE_NACK; + } + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else { + /* not beginning or end ofa segment */ + if(pkt.ctrl.b.inSegment) + { + /* Accumulate CRC */ + ST25FTM_GetCrc(pkt.data,pkt.length,ST25FTM_CRC_ACCUMULATE); + } else { + /* No segment is used, consider the data as valid */ + gFtmState.rx.totalValidReceivedLength += pkt.length; + gFtmState.rx.validReceivedLength += pkt.length; + } + if((ST25FTM_CTRL_IS_SINGLE_PACKET(pkt.ctrl.b.position)) || (ST25FTM_CTRL_IS_LAST_PACKET(pkt.ctrl.b.position))) + { + if(gFtmState.rx.totalValidReceivedLength == *gFtmState.rx.cmdLen) + { + gFtmState.rx.state = ST25FTM_READ_DONE; + } else { + /* inconsistent data length */ + gFtmState.rx.nbError++; + gFtmState.rx.state = ST25FTM_READ_ERROR; + gFtmState.rx.lastError = 2; + ST25FTM_LOG("FtmRxError2 Inconsistent length\r\n"); + } + } else { + /* this is a start/middle frame, continue reception */ + gFtmState.rx.state = ST25FTM_READ_CMD; + } + } + } + } + } + return control; +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateRxWriteAck(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_RELEASE; + ST25FTM_MessageStatus_t status; + uint8_t msg[ST25FTM_BUFFER_LENGTH]; + int32_t msg_len = 1; + + (void)memset(msg,0,sizeof(msg)); + if(gFtmState.rx.unrecoverableError) + { + ST25FTM_LOG("FtmRx TxStop\r\n"); + gFtmState.rx.lastAck = 1; + msg[0] = ((uint8_t)ST25FTM_STATUS_BYTE | (uint8_t)ST25FTM_ABORT_TRANSFER); + gFtmState.rx.unrecoverableError = 0; + } else if(gFtmState.rx.state == ST25FTM_READ_WRITE_ACK) + { + ST25FTM_LOG("FtmRx TxAck\r\n"); + gFtmState.rx.lastAck = 1; + msg[0] = ((uint8_t)ST25FTM_STATUS_BYTE | (uint8_t)ST25FTM_SEGMENT_OK); + } else if (gFtmState.rx.state == ST25FTM_READ_WRITE_NACK) + { + ST25FTM_LOG("FtmRx TxNack\r\n"); + gFtmState.rx.lastAck = 0; + msg[0] = ((uint8_t)ST25FTM_STATUS_BYTE | (uint8_t)ST25FTM_CRC_ERROR); + } else { + /* Undefined Ack response */ + gFtmState.rx.lastError = 8; + ST25FTM_LOG("FtmRxError8 Unknown ack state %d\r\n",gFtmState.rx.state); + } + status = ST25FTM_WriteMessage(msg,msg_len); + if(status == ST25FTM_MSG_OK) + { + gFtmState.rx.state = ST25FTM_READ_WAIT_ACK_READ; + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else if (status == ST25FTM_MSG_BUSY) { + /* If Mailbox is busy there is a message in the mailbox + a timeout may have occured */ + gFtmState.rx.nbError++; + gFtmState.rx.lastError = 7; + ST25FTM_LOG("FtmRxError7 Mailbox not empty\r\n"); + gFtmState.rx.state = ST25FTM_READ_CMD; + } else { + /* If a RF operation is on-going, the I2C is NACKED: retry later! */ + } + return control; +} + + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateRxWaitAckRead(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_RELEASE; + ST25FTM_MessageOwner_t msgOwner = ST25FTM_GetMessageOwner(); + if((msgOwner == ST25FTM_MESSAGE_EMPTY) || (msgOwner == ST25FTM_MESSAGE_PEER)) + { + ST25FTM_LOG("lastAck=%d\r\n",gFtmState.rx.lastAck); + if(gFtmState.rx.lastAck != 0U) + { + /* only consider the data valid once the ack has been read */ + ST25FTM_LOG("ignoreRetrans=%d\r\n",gFtmState.rx.ignoreRetransSegment); + if(gFtmState.rx.ignoreRetransSegment == 0U) + { + ST25FTM_LOG("Ending Segment %d\r\n", gFtmState.rx.segmentNumber); + gFtmState.rx.segmentPtr = gFtmState.rx.dataPtr; + gFtmState.rx.validReceivedLength += gFtmState.rx.validLength; + gFtmState.rx.totalValidReceivedLength += gFtmState.rx.validLength; + gFtmState.rx.segmentNumber++; + } else { + ST25FTM_LOG("Dropping retrans %d\r\n", gFtmState.rx.segmentNumber); + gFtmState.rx.dataPtr = gFtmState.rx.segmentPtr; + gFtmState.rx.receivedLength -= gFtmState.rx.segmentLength; + } + gFtmState.rx.segmentLength = 0U; + + ST25FTM_LOG("receivedLen=%d\r\n",gFtmState.rx.receivedLength); + ST25FTM_LOG("validLen=%d\r\n",gFtmState.rx.validLength); + ST25FTM_LOG("totalvalid=%d\r\n",gFtmState.rx.totalValidReceivedLength); + } + if((ST25FTM_CTRL_IS_SINGLE_PACKET(gFtmState.rx.pktPosition) || + ST25FTM_CTRL_IS_LAST_PACKET(gFtmState.rx.pktPosition)) && + (gFtmState.rx.lastAck != 0U)) + { + if(ST25FTM_CTRL_IS_LAST_PACKET(gFtmState.rx.pktPosition) && + (gFtmState.rx.totalValidReceivedLength != *gFtmState.rx.cmdLen)) + { + /* inconsistent data length -> error */ + gFtmState.rx.lastError = 3U; + ST25FTM_LOG("FtmRxError3: Inconsistent length\r\n"); + gFtmState.rx.nbError++; + gFtmState.rx.state = ST25FTM_READ_ERROR; + control = ST25FTM_STATE_MACHINE_RELEASE; + } else { + /* no need to check received length in single packet */ + if(ST25FTM_CTRL_IS_SINGLE_PACKET(gFtmState.rx.pktPosition)) + { + *gFtmState.rx.cmdLen = gFtmState.rx.totalValidReceivedLength; + } + ST25FTM_LOG("FtmRx Ack has been read\r\n"); + gFtmState.rx.state = ST25FTM_READ_DONE; + } + } else { + /* continue reception if this is not the last packet or this was a NACK */ + ST25FTM_LOG("FtmRx Continue reception\r\n"); + gFtmState.rx.state = ST25FTM_READ_CMD; + } + } + return control; +} + + +void ST25FTM_Receive(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_CONTINUE; + while(control == ST25FTM_STATE_MACHINE_CONTINUE) + { + switch (gFtmState.rx.state) + { + case ST25FTM_READ_IDLE: + control = ST25FTM_StateRxIdle(); + break; + case ST25FTM_READ_CMD: + control = ST25FTM_StateRxCommand(); + break; + case ST25FTM_READ_PKT: + control = ST25FTM_StateRxPacket(); + break; + case ST25FTM_READ_WRITE_ACK: + case ST25FTM_READ_WRITE_NACK: + case ST25FTM_READ_WRITE_ERR: + control = ST25FTM_StateRxWriteAck(); + break; + case ST25FTM_READ_WAIT_ACK_READ: + control = ST25FTM_StateRxWaitAckRead(); + break; + default: + control = ST25FTM_STATE_MACHINE_RELEASE; + break; + } + } +} diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_tx.c b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_tx.c new file mode 100644 index 00000000..4ebea55b --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/ST/ST25FTM/Src/st25ftm_tx.c @@ -0,0 +1,420 @@ +/** + ****************************************************************************** + * @file st25ftm_tx.c + * @author MMY Application Team + * @version 1.0.0 + * @date 29-July-2022 + * @brief Fast Transfer Memory manage function for data transmit + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#include "st25ftm_protocol.h" +#include "st25ftm_common.h" +#include "st25ftm_config.h" +#include + +static void ST25FTM_GetData(uint8_t* buf, uint8_t* src, uint32_t length) +{ + if(gFtmState.tx.getdata_cb == NULL) + { + /* Default case: simply copy source data into packed buffer */ + (void)memcpy(buf,src, length); + } else { + /* Let's the application manage the input data */ + gFtmState.tx.getdata_cb(buf, src, length); + } +} + +static uint32_t ST25FTM_Pack(ST25FTM_Packet_t *pkt, uint8_t *msg) +{ + uint32_t index = 0; + uint32_t data_index = 0; + uint32_t data_payload = 0; + + msg[index] = pkt->ctrl.byte; + index++; + if(pkt->ctrl.b.pktLen != 0U) + { + msg[index] = (uint8_t)pkt->length; + index++; + } + + if(ST25FTM_CTRL_HAS_TOTAL_LEN(pkt->ctrl)) + { + uint32_t txTotalLen = pkt->totalLength; + ST25FTM_CHANGE_ENDIANESS(txTotalLen); + (void)memcpy(&msg[index],&txTotalLen,sizeof(txTotalLen)); + index += sizeof(pkt->totalLength); + } + + /* Save data index for Crc computation */ + data_index = index; + data_payload = pkt->length - ( pkt->has_crc ? 4 : 0); + ST25FTM_GetData(&msg[index],pkt->data, data_payload); + index += data_payload; + + /* compute CRC */ + if(gFtmState.tx.sendAck == ST25FTM_SEND_WITH_ACK) + { + pkt->crc = 0; + if(pkt->ctrl.b.ackCtrl == ST25FTM_SEGMENT_START) + { + ST25FTM_GetCrc(&msg[data_index],data_payload,ST25FTM_CRC_START); + } else if (pkt->ctrl.b.ackCtrl == ST25FTM_SEGMENT_END) + { + /* pkt.length contains the CRC length, remove it */ + pkt->crc = ST25FTM_GetCrc(&msg[data_index],data_payload,ST25FTM_CRC_END); + } else if (pkt->ctrl.b.ackCtrl == ST25FTM_ACK_SINGLE_PKT) { + /* Single frame segment */ + pkt->crc = ST25FTM_GetCrc(&msg[data_index],data_payload,ST25FTM_CRC_ONESHOT); + } else { + /* middle of a segment */ + ST25FTM_GetCrc(&msg[data_index],data_payload,ST25FTM_CRC_ACCUMULATE); + } + if(pkt->has_crc) + { + ST25FTM_CHANGE_ENDIANESS(pkt->crc); + (void)memcpy(&msg[index],&pkt->crc, 4); + index += 4; + } + } + + return index; +} + +void ST25FTM_TxStateInit(void) +{ + gFtmState.tx.state = ST25FTM_WRITE_IDLE; + gFtmState.tx.lastState = ST25FTM_WRITE_IDLE; + gFtmState.tx.frameMaxLength = 0xFF; + gFtmState.tx.cmdPtr = NULL; + gFtmState.tx.cmdLen = 0; + gFtmState.tx.remainingData = 0; + gFtmState.tx.nbError = 0; + gFtmState.tx.sendAck = ST25FTM_SEND_WITH_ACK; + gFtmState.tx.dataPtr = NULL; + gFtmState.tx.segmentPtr = NULL; + gFtmState.tx.segmentStart = NULL; + gFtmState.tx.segmentLength = 0; + gFtmState.tx.segmentRemainingData = 0; + gFtmState.tx.segmentMaxLength = ST25FTM_SEGMENT_LEN; + gFtmState.tx.retransmit = 0; + gFtmState.tx.pktIndex = 0; + gFtmState.tx.segmentIndex = 0; + gFtmState.tx.packetLength = 0; + gFtmState.tx.segmentNumber = 0; + gFtmState.tx.getdata_cb = NULL; + (void)memset(gFtmState.tx.packetBuf, 0, sizeof(gFtmState.tx.packetBuf)); +} + + +/************** Ftm Tx States ***************/ +static ST25FTM_StateMachineCtrl_t ST25FTM_StateTxIdle(void) +{ + ST25FTM_LOG("Tx Length %d\r\n", gFtmState.tx.cmdLen); + gFtmState.tx.remainingData = gFtmState.tx.cmdLen; + gFtmState.tx.state = ST25FTM_WRITE_CMD; + gFtmState.tx.dataPtr = gFtmState.tx.cmdPtr; + gFtmState.tx.segmentLength = 0; + gFtmState.tx.segmentRemainingData = 0; + gFtmState.tx.retransmit = 0; + gFtmState.tx.pktIndex = 0; + gFtmState.tx.segmentIndex = 0; + gFtmState.totalDataLength = 0; + gFtmState.retryLength = 0; + gFtmState.tx.segmentNumber = 0; + ST25FTM_CRC_Initialize(); + return ST25FTM_STATE_MACHINE_CONTINUE; +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateTxCommand(void) +{ + if (gFtmState.tx.remainingData > 0U) + { + uint32_t data_processed; + + ST25FTM_LOG("Starting Segment %d\r\n", gFtmState.tx.segmentNumber); + + /* prepare next segment */ + if (gFtmState.tx.sendAck == ST25FTM_SEND_WITH_ACK) { + data_processed = (gFtmState.tx.remainingData > (gFtmState.tx.segmentMaxLength - 4U)) ? + (gFtmState.tx.segmentMaxLength - 4U) : + gFtmState.tx.remainingData; + gFtmState.tx.segmentLength = data_processed + 4; + gFtmState.tx.segmentPtr = gFtmState.tx.dataPtr; + } else { + data_processed = gFtmState.tx.remainingData; + gFtmState.tx.segmentLength = data_processed; + gFtmState.tx.segmentPtr = gFtmState.tx.dataPtr; + } + gFtmState.tx.segmentStart = gFtmState.tx.segmentPtr; + gFtmState.tx.remainingData -= data_processed; + gFtmState.tx.dataPtr += data_processed; + gFtmState.tx.segmentRemainingData = gFtmState.tx.segmentLength; + gFtmState.tx.state = ST25FTM_WRITE_SEGMENT; + } else { + gFtmState.tx.state = ST25FTM_WRITE_DONE; + return ST25FTM_STATE_MACHINE_RELEASE; + } + return ST25FTM_STATE_MACHINE_CONTINUE; +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateTxSegment(void) +{ + ST25FTM_Packet_t pkt = {0}; + pkt.length = gFtmState.tx.frameMaxLength - sizeof(ST25FTM_Ctrl_Byte_t); + pkt.has_crc = 0; + + pkt.data = gFtmState.tx.segmentPtr; + pkt.ctrl.b.segId = (uint8_t)(gFtmState.tx.segmentNumber % 2U); + pkt.ctrl.b.ackCtrl = 0; + pkt.ctrl.b.inSegment = gFtmState.tx.sendAck == ST25FTM_SEND_WITH_ACK; + + ST25FTM_LOG("SegmentLen %d\r\n",gFtmState.tx.segmentLength); + /* Segment has to be sent over several packets */ + if(gFtmState.tx.segmentRemainingData > ST25FTM_MAX_DATA_IN_SINGLE_PACKET()) + { + + if(gFtmState.tx.pktIndex == 0U) + { + /* First Packet + don't mention packet length if the whole buffer is used */ + pkt.ctrl.b.pktLen = 0; + pkt.totalLength = gFtmState.tx.cmdLen; + pkt.ctrl.b.position = (uint8_t)(ST25FTM_FIRST_PACKET); + pkt.length -= sizeof(pkt.totalLength); + } else { + /* Middle Packet + don't mention packet length if the whole buffer is used */ + pkt.ctrl.b.pktLen = 0U; + pkt.ctrl.b.position = (uint8_t)(ST25FTM_MIDDLE_PACKET); + } + if(gFtmState.tx.sendAck == ST25FTM_SEND_WITH_ACK) + { + if(gFtmState.tx.segmentIndex == 0U) + { + pkt.ctrl.b.ackCtrl |= (uint8_t)(ST25FTM_SEGMENT_START); + } + if((gFtmState.tx.segmentRemainingData - pkt.length) < sizeof(pkt.crc)) + { + /* This is to make sure that CRC is not split between 2 packets */ + pkt.ctrl.b.pktLen = 1U; + pkt.length = gFtmState.tx.segmentRemainingData - sizeof(pkt.crc); + } + } + } else { + /* Single or last Packet command */ + pkt.length = gFtmState.tx.segmentRemainingData; + if(gFtmState.tx.segmentRemainingData == ST25FTM_MAX_DATA_IN_SINGLE_PACKET()) + { + /* exact fit */ + pkt.ctrl.b.pktLen = 0U; + } else { + /* smaller than data buffer */ + pkt.ctrl.b.pktLen = 1U; + } + if(gFtmState.tx.remainingData == 0U) + { + if(gFtmState.tx.pktIndex == 0U) + { + pkt.ctrl.b.position = (uint8_t)(ST25FTM_SINGLE_PACKET); + } else { + pkt.ctrl.b.position = (uint8_t)(ST25FTM_LAST_PACKET); + } + } else if (gFtmState.tx.pktIndex != 0U) { + pkt.ctrl.b.position = (uint8_t)(ST25FTM_MIDDLE_PACKET); + } else { + /* do nothing */ + } + if(gFtmState.tx.sendAck == ST25FTM_SEND_WITHOUT_ACK) + { + pkt.ctrl.b.ackCtrl = (uint8_t)(ST25FTM_NO_ACK_PACKET); + } else { + if(gFtmState.tx.sendAck == ST25FTM_SEND_WITH_ACK) + { + pkt.has_crc = 1; + } + if(gFtmState.tx.segmentLength <= ST25FTM_MAX_DATA_IN_SINGLE_PACKET()) + { + pkt.ctrl.b.ackCtrl = (uint8_t)(ST25FTM_ACK_SINGLE_PKT); + } else { + pkt.ctrl.b.ackCtrl = (uint8_t)(ST25FTM_SEGMENT_END); + } + } + } + + + ST25FTM_LOG("segmentPtr = %x\r\n",gFtmState.tx.segmentPtr); + /* Note: segmentPtr will overflow when CRC is added for last segment packet + not an issue since it will be reset for next segment */ + gFtmState.tx.segmentPtr += pkt.length; + gFtmState.tx.segmentRemainingData -= pkt.length; + gFtmState.tx.pktIndex++; + gFtmState.tx.segmentIndex++; + + gFtmState.tx.packetLength = 0; + (void)memset(gFtmState.tx.packetBuf,0,sizeof(gFtmState.tx.packetBuf)); + gFtmState.tx.packetLength = ST25FTM_Pack(&pkt,gFtmState.tx.packetBuf); + + ST25FTM_LOG("PktId %d\r\n",gFtmState.tx.pktIndex); + ST25FTM_LOG("PktLen total=%d payload=%d\r\n",gFtmState.tx.packetLength,pkt.length); + ST25FTM_LOG("Wr "); + logHexBuf(gFtmState.tx.packetBuf,gFtmState.tx.packetLength); + + gFtmState.tx.state = ST25FTM_WRITE_PKT; + return ST25FTM_STATE_MACHINE_CONTINUE; +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateTxPacket(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_RELEASE; + ST25FTM_MessageStatus_t status = ST25FTM_WriteMessage(gFtmState.tx.packetBuf,gFtmState.tx.packetLength); + if(status == ST25FTM_MSG_OK) { + gFtmState.totalDataLength += gFtmState.tx.packetLength; + gFtmState.tx.state = ST25FTM_WRITE_WAIT_READ; + } else if (status == ST25FTM_MSG_BUSY) { + /* If there is a message in the mailbox, the status is MAILBOX_BUSY + this is not expected, a timeout may have occured + it may be a new command or a NACK to request retransmit + continue with reading the MB to know what to do */ + ST25FTM_LOG("Write error, mailbox busy\r\n"); + gFtmState.tx.nbError++; + gFtmState.tx.state = ST25FTM_WRITE_WAIT_READ; + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else { + /* If a RF operation is on-going, the I2C is NACKED + retry later! */ + gFtmState.tx.state = ST25FTM_WRITE_PKT; + } + return control; +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateTxWaitRead(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_RELEASE; + ST25FTM_MessageOwner_t msgOwner = ST25FTM_GetMessageOwner(); + ST25FTM_Ctrl_Byte_t ctrl_byte; + ctrl_byte.byte = gFtmState.tx.packetBuf[0]; + if(ST25FTM_CTRL_HAS_CRC(ctrl_byte)) + { + if((msgOwner == ST25FTM_MESSAGE_EMPTY) || (msgOwner == ST25FTM_MESSAGE_PEER)) + { + gFtmState.tx.state = ST25FTM_WRITE_READ_ACK; + } + } else { + if(msgOwner == ST25FTM_MESSAGE_EMPTY) + { + if(gFtmState.tx.segmentRemainingData > 0U) + { + gFtmState.tx.state = ST25FTM_WRITE_SEGMENT; + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else if (gFtmState.tx.remainingData > 0U) { + gFtmState.tx.state = ST25FTM_WRITE_CMD; + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else { + gFtmState.tx.state = ST25FTM_WRITE_DONE; + } + } else if (msgOwner == ST25FTM_MESSAGE_PEER) { + /* this is not expected + continue with reading the MB to know what to do + it may be a new command or a NACK to request retransmit */ + gFtmState.tx.nbError++; + gFtmState.tx.state = ST25FTM_WRITE_READ_ACK; + ST25FTM_LOG("Write error, mailbox busy 2\r\n"); + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else { + /* this is still our message, do nothing */ + } + } + return control; +} + +static ST25FTM_StateMachineCtrl_t ST25FTM_StateTxReadAck(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_RELEASE; + ST25FTM_Acknowledge_Status_t ack_status; + + ack_status = ST25FTM_GetAcknowledgeStatus(); + ST25FTM_LOG("Rx Ack=%d\r\n",ack_status); + if(ack_status == ST25FTM_SEGMENT_OK) + { + ST25FTM_LOG("Ending Segment %d\r\n", gFtmState.tx.segmentNumber); + gFtmState.tx.retransmit = 0U; + gFtmState.tx.segmentIndex = 0U; + gFtmState.tx.segmentNumber++; + if(gFtmState.tx.remainingData == 0U) + { + gFtmState.tx.state = ST25FTM_WRITE_DONE; + } else { + /* there are other packets to send */ + gFtmState.tx.state = ST25FTM_WRITE_CMD; + control = ST25FTM_STATE_MACHINE_CONTINUE; + } + } else if (ack_status == ST25FTM_ACK_BUSY) { + /* do nothing */ + gFtmState.tx.state = ST25FTM_WRITE_READ_ACK; + } else if (ack_status == ST25FTM_CRC_ERROR) { + ST25FTM_TxResetSegment(); + control = ST25FTM_STATE_MACHINE_CONTINUE; + } else { + /* this is not a ACK message, it must be a new command */ + gFtmState.tx.state = ST25FTM_WRITE_ERROR; + ST25FTM_LOG("Write error, mailbox busy 3\r\n"); + } + return control; +} + +void ST25FTM_Transmit(void) +{ + ST25FTM_StateMachineCtrl_t control = ST25FTM_STATE_MACHINE_CONTINUE; + while(control == ST25FTM_STATE_MACHINE_CONTINUE) + { + switch (gFtmState.tx.state) + { + case ST25FTM_WRITE_IDLE: + control = ST25FTM_StateTxIdle(); + break; + case ST25FTM_WRITE_CMD: + control = ST25FTM_StateTxCommand(); + break; + case ST25FTM_WRITE_SEGMENT: + control = ST25FTM_StateTxSegment(); + break; + case ST25FTM_WRITE_PKT: + control = ST25FTM_StateTxPacket(); + break; + case ST25FTM_WRITE_WAIT_READ: + control = ST25FTM_StateTxWaitRead(); + break; + case ST25FTM_WRITE_READ_ACK: + control = ST25FTM_StateTxReadAck(); + break; + default: + ST25FTM_Reset(); + control = ST25FTM_STATE_MACHINE_RELEASE; + break; + } + } +} + +void ST25FTM_TxResetSegment() +{ + gFtmState.retryLength += gFtmState.tx.segmentLength - gFtmState.tx.segmentRemainingData; + /* rewind to retransmit */ + gFtmState.tx.segmentRemainingData = gFtmState.tx.segmentLength; + gFtmState.tx.pktIndex -= gFtmState.tx.segmentIndex; + gFtmState.tx.segmentPtr = gFtmState.tx.segmentStart; + gFtmState.tx.retransmit = 1; + gFtmState.tx.segmentIndex = 0; + gFtmState.tx.state = ST25FTM_WRITE_SEGMENT; +} diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h index 4c50760a..5a1a4978 100644 --- a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h @@ -160,14 +160,6 @@ extern "C" { #define INCLUDE_uxTaskGetStackHighWaterMark2 0 #endif -#ifndef INCLUDE_pxTaskGetStackStart - #define INCLUDE_pxTaskGetStackStart 0 -#endif - -#ifndef INCLUDE_pxTaskGetStackStart - #define INCLUDE_pxTaskGetStackStart 0 -#endif - #ifndef INCLUDE_eTaskGetState #define INCLUDE_eTaskGetState 0 #endif @@ -424,23 +416,6 @@ hold explicit before calling the code. */ #define tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB ) #endif -#ifndef traceREADDED_TASK_TO_READY_STATE - #define traceREADDED_TASK_TO_READY_STATE( pxTCB ) traceMOVED_TASK_TO_READY_STATE( pxTCB ) -#endif - -#ifndef traceMOVED_TASK_TO_DELAYED_LIST - #define traceMOVED_TASK_TO_DELAYED_LIST() -#endif - -#ifndef traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST - #define traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST() -#endif - -#ifndef traceMOVED_TASK_TO_SUSPENDED_LIST - #define traceMOVED_TASK_TO_SUSPENDED_LIST( pxTCB ) -#endif - - #ifndef traceQUEUE_CREATE #define traceQUEUE_CREATE( pxNewQueue ) #endif @@ -685,18 +660,6 @@ hold explicit before calling the code. */ #define traceTASK_NOTIFY_GIVE_FROM_ISR() #endif -#ifndef traceISR_EXIT_TO_SCHEDULER - #define traceISR_EXIT_TO_SCHEDULER() -#endif - -#ifndef traceISR_EXIT - #define traceISR_EXIT() -#endif - -#ifndef traceISR_ENTER - #define traceISR_ENTER() -#endif - #ifndef traceSTREAM_BUFFER_CREATE_FAILED #define traceSTREAM_BUFFER_CREATE_FAILED( xIsMessageBuffer ) #endif diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/task.h b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/task.h index 354c2ca7..4b8639cb 100644 --- a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/task.h +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/include/task.h @@ -1468,25 +1468,6 @@ UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask ) PRIVILEGED_FUNCTIO */ configSTACK_DEPTH_TYPE uxTaskGetStackHighWaterMark2( TaskHandle_t xTask ) PRIVILEGED_FUNCTION; -/** - * task.h - *
uint8_t* pxTaskGetStackStart( TaskHandle_t xTask);
- * - * INCLUDE_pxTaskGetStackStart must be set to 1 in FreeRTOSConfig.h for - * this function to be available. - * - * Returns the start of the stack associated with xTask. That is, - * the highest stack memory address on architectures where the stack grows down - * from high memory, and the lowest memory address on architectures where the - * stack grows up from low memory. - * - * @param xTask Handle of the task associated with the stack returned. - * Set xTask to NULL to return the stack of the calling task. - * - * @return A pointer to the start of the stack. - */ -uint8_t* pxTaskGetStackStart( TaskHandle_t xTask) PRIVILEGED_FUNCTION; - /* When using trace macros it is sometimes necessary to include task.h before FreeRTOS.h. When this is done TaskHookFunction_t will not yet have been defined, so the following two prototypes will cause a compilation error. This can be diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c index 37ac7ab4..9e1d1a89 100644 --- a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c @@ -492,20 +492,14 @@ void xPortSysTickHandler( void ) save and then restore the interrupt mask value as its value is already known. */ portDISABLE_INTERRUPTS(); - traceISR_ENTER(); { /* Increment the RTOS tick. */ if( xTaskIncrementTick() != pdFALSE ) { - traceISR_EXIT_TO_SCHEDULER(); /* A context switch is required. Context switching is performed in the PendSV interrupt. Pend the PendSV interrupt. */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } - else - { - traceISR_EXIT(); - } } portENABLE_INTERRUPTS(); } diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h index e0541ffb..e1e7fadf 100644 --- a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h @@ -89,7 +89,7 @@ typedef unsigned long UBaseType_t; #define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) ) #define portNVIC_PENDSVSET_BIT ( 1UL << 28UL ) -#define portEND_SWITCHING_ISR( xSwitchRequired ) { if( xSwitchRequired != pdFALSE ) { traceISR_EXIT_TO_SCHEDULER(); portYIELD(); } else { traceISR_EXIT(); } } +#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD() #define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x ) /*-----------------------------------------------------------*/ diff --git a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/tasks.c b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/tasks.c index bd43c428..f93fca03 100644 --- a/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/tasks.c +++ b/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/tasks.c @@ -220,17 +220,6 @@ count overflows. */ taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \ vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \ tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB ) - -/* - * Place the task represented by pxTCB which has been in a ready list before - * into the appropriate ready list for the task. - * It is inserted at the end of the list. - */ -#define prvReaddTaskToReadyList( pxTCB ) \ - traceREADDED_TASK_TO_READY_STATE( pxTCB ); \ - taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \ - vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \ - tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB ) /*-----------------------------------------------------------*/ /* @@ -1683,7 +1672,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ) { mtCOVERAGE_TEST_MARKER(); } - prvReaddTaskToReadyList( pxTCB ); + prvAddTaskToReadyList( pxTCB ); } else { @@ -1744,7 +1733,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ) { mtCOVERAGE_TEST_MARKER(); } - traceMOVED_TASK_TO_SUSPENDED_LIST(pxTCB); + vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) ); #if( configUSE_TASK_NOTIFICATIONS == 1 ) @@ -3893,20 +3882,6 @@ static void prvCheckTasksWaitingTermination( void ) #endif /* INCLUDE_uxTaskGetStackHighWaterMark */ /*-----------------------------------------------------------*/ -#if (INCLUDE_pxTaskGetStackStart == 1) - uint8_t* pxTaskGetStackStart( TaskHandle_t xTask) - { - TCB_t *pxTCB; - UBaseType_t uxReturn; - (void)uxReturn; - - pxTCB = prvGetTCBFromHandle( xTask ); - return ( uint8_t * ) pxTCB->pxStack; - } - -#endif /* INCLUDE_pxTaskGetStackStart */ -/*-----------------------------------------------------------*/ - #if ( INCLUDE_vTaskDelete == 1 ) static void prvDeleteTCB( TCB_t *pxTCB ) @@ -4081,7 +4056,7 @@ TCB_t *pxTCB; /* Inherit the priority before being moved into the new list. */ pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority; - prvReaddTaskToReadyList( pxMutexHolderTCB ); + prvAddTaskToReadyList( pxMutexHolderTCB ); } else { @@ -4171,7 +4146,7 @@ TCB_t *pxTCB; any other purpose if this task is running, and it must be running to give back the mutex. */ listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ - prvReaddTaskToReadyList( pxTCB ); + prvAddTaskToReadyList( pxTCB ); /* Return true to indicate that a context switch is required. This is only actually required in the corner case whereby @@ -5233,7 +5208,6 @@ const TickType_t xConstTickCount = xTickCount; /* Add the task to the suspended task list instead of a delayed task list to ensure it is not woken by a timing event. It will block indefinitely. */ - traceMOVED_TASK_TO_SUSPENDED_LIST(pxCurrentTCB); vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) ); } else @@ -5250,14 +5224,12 @@ const TickType_t xConstTickCount = xTickCount; { /* Wake time has overflowed. Place this item in the overflow list. */ - traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST(); vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); } else { /* The wake time has not overflowed, so the current block list is used. */ - traceMOVED_TASK_TO_DELAYED_LIST(); vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); /* If the task entering the blocked state was placed at the @@ -5287,13 +5259,11 @@ const TickType_t xConstTickCount = xTickCount; if( xTimeToWake < xConstTickCount ) { /* Wake time has overflowed. Place this item in the overflow list. */ - traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST(); vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); } else { /* The wake time has not overflowed, so the current block list is used. */ - traceMOVED_TASK_TO_DELAYED_LIST(); vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); /* If the task entering the blocked state was placed at the head of the diff --git a/firmware/iot-risk-logger-stm32l4/ST25FTM/App/st25ftm_config.h b/firmware/iot-risk-logger-stm32l4/ST25FTM/App/st25ftm_config.h new file mode 100644 index 00000000..7da22e4a --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/ST25FTM/App/st25ftm_config.h @@ -0,0 +1,151 @@ +/* USER CODE BEGIN Header */ +/** + ****************************************************************************** + * @file NFC_FTM\Inc\st25ftm_config.h + * @author System Research & Applications Team - Catania Lab. + * @brief FTM Configuration APIs + ****************************************************************************** + * @attention + * + * Copyright (c) 2024 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ +/* USER CODE END Header */ + +/* Define to prevent recursive inclusion ------------------------------------ */ +#ifndef __ST25FTM_CONFIG_H__ +#define __ST25FTM_CONFIG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdio.h" +#include "stm32l4xx_hal.h" + +/* Exported types ----------------------------------------------------------- */ +/*! Fast Transfer Mode buffer access status */ +typedef enum { + ST25FTM_MSG_OK =0, /*!< Message read/write ok */ + ST25FTM_MSG_ERROR, /*!< The peer device doesn't respond */ + ST25FTM_MSG_BUSY /*!< The buffer is not empty while writing */ +} ST25FTM_MessageStatus_t; + +/*! Fast Transfer Mode current message owner */ +typedef enum { + ST25FTM_MESSAGE_EMPTY = 0, /*!< There is no message */ + ST25FTM_MESSAGE_ME = 1, /*!< Current message has been written by this device */ + ST25FTM_MESSAGE_PEER = 2, /*!< Current message has been written by the peer device */ + ST25FTM_MESSAGE_OWNER_ERROR = 3 /*!< An error occured while getting the message owner */ +} ST25FTM_MessageOwner_t; + +typedef enum { + ST25FTM_CRC_START, + ST25FTM_CRC_END, + ST25FTM_CRC_ACCUMULATE, + ST25FTM_CRC_ONESHOT +} ST25FTM_crc_control_t; + +typedef uint32_t ST25FTM_Crc_t; + +/*! Define format of the packet length field, when present */ +typedef uint8_t ST25FTM_Packet_Length_t; + +/* Exported constants ------------------------------------------------------- */ + +/* Exported defines --------------------------------------------------------- */ +/*! Length of the buffer used to store a single message data */ +#define ST25FTM_BUFFER_LENGTH (256) + +/*! Length of the buffer used to store unvalidated data */ +#define ST25FTM_SEGMENT_LEN (5795) + +/*! Define the platform function to get the ms tick */ +#define ST25FTM_TICK() HAL_GetTick() /* call here a function returning the system tick value */ + +/*! Enables debug traces for the ST25FTM library */ +#define ST25FTM_ENABLE_LOG 0 + +/* Exporte variables -------------------------------------------------------- */ +extern volatile uint8_t GPO_Activated; + +/* Exported functions ------------------------------------------------------- */ +extern void DeInitMailbox(void); + +/* Interface API */ +/* Functions to implement for the platform */ +/*! Check what device wrote the current message in the FTM buffer + * @retval ST25FTM_MESSAGE_EMPTY The buffer is empty. + * @retval ST25FTM_MESSAGE_ME Message has been written by this device. + * @retval ST25FTM_MESSAGE_PEER Message has been written by the peer device. + * @retval ST25FTM_MESSAGE_OWNER_ERROR Message owner cannot be retrieved. + */ +extern ST25FTM_MessageOwner_t ST25FTM_GetMessageOwner(void); + +/*! Read the content of the FTM buffer. + * @param msg A buffer used to store read data + Buffer length must be greater than ST25FTM_BUFFER_LENGTH. + * @param msg_len A pointer used to return the number of bytes read. + * @retval ST25FTM_MSG_OK Message successfully read. + * @retval ST25FTM_MSG_ERROR Unable to read the message. +*/ +extern ST25FTM_MessageStatus_t ST25FTM_ReadMessage(uint8_t *msg, uint32_t* msg_len); + +/*! Write the FTM buffer. + * @param msg The buffer containing the data to written. + * @param msg_len Number of bytes to write. + * @retval ST25FTM_MSG_OK Message successfully written. + * @retval ST25FTM_MSG_ERROR Unable to write the message (eg: tag has been removed). + * @retval ST25FTM_MSG_BUSY FTM buffer contains a meesage that has not been read yet. +*/ +extern ST25FTM_MessageStatus_t ST25FTM_WriteMessage(uint8_t* msg, uint32_t msg_len); + +/*! Initialize the NFC device (dynamic tag or reader) for the FTM. +*/ +extern void ST25FTM_DeviceInit(void); + +/*! Check if the RF field is present (for dynamic tag only) +*/ +extern void ST25FTM_UpdateFieldStatus(void); + +/*! Initialize the CRC computation */ +extern void ST25FTM_CRC_Initialize(void); + +/*! Compute a CRC32. + * @param data Buffer containing the data on which the CRC must be computed. + * @param length Number of bytes of data in the buffer. + * @param control Define how to compute the crc: + * - starting from the initial value or from previous crc + * - Adding remaining bytes (less than word) with padding + */ +extern ST25FTM_Crc_t ST25FTM_GetCrc(uint8_t *data, uint32_t length, ST25FTM_crc_control_t control); + +/* Exported macros ---------------------------------------------------------- */ +#if defined ( __GNUC__ ) && !defined (__CC_ARM) +/* GNU Compiler: packed attribute must be placed after the type keyword */ +#define ST25FTM_PACKED(type) type __attribute__((packed,aligned(1))) +#else +/* ARM Compiler: packed attribute must be placed before the type keyword */ +#define ST25FTM_PACKED(type) __packed type +#endif + +#if (ST25FTM_ENABLE_LOG != 0) +#include "logger.h" +#define ST25FTM_LOG(...) STBOX1_PRINTF(__VA_ARGS__) +#define ST25FTM_HEX2STR(buf,len) hex2Str(buf,len) +#else +#define ST25FTM_LOG(...) +#define ST25FTM_HEX2STR(buf,len) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __ST25FTM_CONFIG_H__*/ diff --git a/firmware/iot-risk-logger-stm32l4/ST25FTM/Target/logger.h b/firmware/iot-risk-logger-stm32l4/ST25FTM/Target/logger.h new file mode 100644 index 00000000..23a629d0 --- /dev/null +++ b/firmware/iot-risk-logger-stm32l4/ST25FTM/Target/logger.h @@ -0,0 +1,54 @@ +/* USER CODE BEGIN Header */ +/** + ****************************************************************************** + * @file NFC_FTM\Inc\logger.h + * @author MMY Application Team + * @brief Header file for logger.c + ****************************************************************************** + * @attention + * + * Copyright (c) 2024 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ +/* USER CODE END Header */ + +#ifndef LOGGER_H +#define LOGGER_H +#include +#include +/* +****************************************************************************** +* INCLUDES +****************************************************************************** +*/ + +/* +****************************************************************************** +* DEFINES +****************************************************************************** +*/ +#define LOGGER_ON 1 +#define LOGGER_OFF 0 + +/*! + ***************************************************************************** + * \brief helper to convert hex data into formated string + * + * \param[in] data : pointer to buffer to be dumped. + * + * \param[in] dataLen : buffer length + * + * \return hex formated string + * + ***************************************************************************** + */ +extern char* hex2Str(unsigned char * data, size_t dataLen); + +#endif /* LOGGER_H */ + diff --git a/firmware/iot-risk-logger-stm32l4/USB_DEVICE/App/usbd_storage_if.c b/firmware/iot-risk-logger-stm32l4/USB_DEVICE/App/usbd_storage_if.c index ccb6cf11..5b8c0168 100644 --- a/firmware/iot-risk-logger-stm32l4/USB_DEVICE/App/usbd_storage_if.c +++ b/firmware/iot-risk-logger-stm32l4/USB_DEVICE/App/usbd_storage_if.c @@ -63,8 +63,8 @@ */ #define STORAGE_LUN_NBR 1 -#define STORAGE_BLK_NBR STORAGE_BLOCK_NUMBER -#define STORAGE_BLK_SIZ STORAGE_BLOCK_SIZE +#define STORAGE_BLK_NBR 0x10000 +#define STORAGE_BLK_SIZ 0x200 /* USER CODE BEGIN PRIVATE_DEFINES */ diff --git a/firmware/iot-risk-logger-stm32l4/app/config/events_list/events_list.h b/firmware/iot-risk-logger-stm32l4/app/config/events_list/events_list.h index 640b3601..d0072c0d 100644 --- a/firmware/iot-risk-logger-stm32l4/app/config/events_list/events_list.h +++ b/firmware/iot-risk-logger-stm32l4/app/config/events_list/events_list.h @@ -23,6 +23,19 @@ extern "C" { */ typedef enum { EVENT_NONE = 0, + /** + * @brief Global Commands, their codes equal to the command codes in the protocol across the system + * @note Mobile phone application should send these commands to the device + */ + // GLOBAL COMMANDS + GLOBAL_CMD_START_LOGGING = 0xC0, ///< Start logging measurements + GLOBAL_CMD_STOP_LOGGING = 0xC1, ///< Stop logging measurements + GLOBAL_CMD_WRITE_SETTINGS = 0xC2, ///< Write settings to the device + GLOBAL_CMD_READ_SETTINGS = 0xC3, ///< Read settings from the device + GLOBAL_CMD_READ_LOG_CHUNK = 0xC4, ///< Read log chunk from the device + /** + * @brief Global Events in the system + */ // GLOBAL Events GLOBAL_CMD_INITIALIZE, GLOBAL_INITIALIZE_SUCCESS, @@ -36,8 +49,12 @@ typedef enum { GLOBAL_CMD_SET_WAKE_UP_PERIOD, ///< Set wake up period in seconds GLOBAL_CMD_START_CONTINUOUS_SENSING, ///< Start continuous sensors measurement GLOBAL_CMD_TURN_OFF, ///< Turn off to power saving mode + GLOBAL_NFC_MAILBOX_WRITE, ///< NFC mailbox write data event GLOBAL_ERROR, GLOBAL_EVENTS_MAX, + /** + * @brief Local Events for the system modules + */ // INFO_LED INFO_LED_FLASH, // NFC diff --git a/firmware/iot-risk-logger-stm32l4/app/tasks/event_manager/event_manager.c b/firmware/iot-risk-logger-stm32l4/app/tasks/event_manager/event_manager.c index 6a38dcf8..dedff285 100644 --- a/firmware/iot-risk-logger-stm32l4/app/tasks/event_manager/event_manager.c +++ b/firmware/iot-risk-logger-stm32l4/app/tasks/event_manager/event_manager.c @@ -33,7 +33,8 @@ extern actor_t* ACTORS_LIST_SystemRegistry[MAX_ACTORS]; * @warning Ensure that the matrix is kept up-to-date whenever new events or actors are added to the system. */ const ACTOR_ID EV_MANAGER_SubscribersIdsMatrix[GLOBAL_EVENTS_MAX][MAX_ACTORS] = { - [GLOBAL_CMD_INITIALIZE] = {CRON_ACTOR_ID, PWRM_MANAGER_ACTOR_ID, NFC_ACTOR_ID, ACCELEROMETER_ACTOR_ID, TEMPERATURE_HUMIDITY_SENSOR_ACTOR_ID, LIGHT_SENSOR_ACTOR_ID, MEMORY_ACTOR_ID}, +// [GLOBAL_CMD_INITIALIZE] = {CRON_ACTOR_ID, PWRM_MANAGER_ACTOR_ID, NFC_ACTOR_ID, ACCELEROMETER_ACTOR_ID, TEMPERATURE_HUMIDITY_SENSOR_ACTOR_ID, LIGHT_SENSOR_ACTOR_ID, MEMORY_ACTOR_ID}, + [GLOBAL_CMD_INITIALIZE] = {CRON_ACTOR_ID, PWRM_MANAGER_ACTOR_ID, NFC_ACTOR_ID, MEMORY_ACTOR_ID}, [GLOBAL_INITIALIZE_SUCCESS] = {}, [GLOBAL_WAKE_N_READ] = {TEMPERATURE_HUMIDITY_SENSOR_ACTOR_ID, LIGHT_SENSOR_ACTOR_ID}, [GLOBAL_TEMPERATURE_HUMIDITY_MEASUREMENTS_READY] = {MEMORY_ACTOR_ID}, diff --git a/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/README.md b/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/README.md index b42d7afb..44f386c8 100644 --- a/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/README.md +++ b/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/README.md @@ -2,115 +2,52 @@ ### Overview -### State Diagram - -
- Diagram as a code - -```plantuml -@startuml -title NFC FSM -hide empty description - -STANDBY: RF free\nI2C free -MAILBOX_TRANSMISSION: Mailbox data exchange -ERROR: Error state\n\nGLOBAL_ERROR: Error message - -[*] --> STANDBY : GLOBAL_CMD_INITIALIZE -STANDBY --> MAILBOX_TRANSMISSION : NFC_GPO_INTERRUPT -STANDBY --> ERROR : ERROR -@enduml -``` -
- -### Mailbox Data Exchange Protocol (ST25FTM) Packet Format - -| **Field** | **Description** | -|-------------|----------------------------------------------------| -| **Command** | 1 byte (e.g., `WRITE_PART`, `WRITE_FINAL`, `READ`) | -| **Length** | 1 byte size of the payload in bytes | -| **Payload** | up to 252 bytes | -| **Checksum**| 2 bytes (standard CRC16 for error detection) | - -### Mailbox Data Exchange Protocol (ST25FTM) +### Communication -
- Diagram as a code - -```plantuml -@startuml -title ST25FTM Protocol Data Exchange (Abstract) +Communication between NFC module and mobile phone is based on *client-server* model. +NFC module acts as a server and mobile phone as a client. +Communication is based by data exchange via *NFC Mailbox* which is 256 bytes buffer is ST25DV. -actor "Mobile phone" as reader -participant "ST25DV Tag" as tag -participant "STM32L4 MCU" as mcu +Useful payload for now is about 128 bytes. Due to I2C reading from NOR Flash and transferring data to NFC Mailbox, *double buffer* is used in MCU SRAM. -== Initial Setup == -reader -> tag : Initiate Communication -tag -> mcu : Signal via Mailbox (Data Request) -mcu -> mcu : Prepare Data for Transmission +### Protocol Description -== Data Transmission (Chunk 1) == -mcu -> tag : Write Data Chunk 1 to Mailbox (T2H) -tag -> reader : Notify Data Ready (Chunk 1) -reader -> tag : Read Data Chunk 1 from Mailbox -reader -> reader : Verify Data Integrity (Chunk 1) -reader -> tag : Acknowledge Chunk 1 (ACK) +| Name | Size, bytes | Description | Example | +|------------|------------|------------------------------------------------------------------------------------------------------------------|---------| +| Command ID | 1 | Command to process, response duplicates it | 0xC1 | +| Payload size | 1 | Actual data size | 0x04 | +| CRC8 | 1 | Payload checksum | 0xAA | +| Payload | 0...253 | Actual data, for commands it could be address to read or settings
for response it could be e,g chunk of log | 0xAA... | -== Data Transmission (Chunk 2) == -mcu -> mcu : Prepare Next Data Chunk (Chunk 2) -mcu -> tag : Write Data Chunk 2 to Mailbox (T2H) -tag -> reader : Notify Data Ready (Chunk 2) -reader -> tag : Read Data Chunk 2 from Mailbox -reader -> reader : Verify Data Integrity (Chunk 2) -reader -> tag : Acknowledge Chunk 2 (ACK) - -== Finalization == -reader -> reader : Combine and Process Full Data -reader -> tag : End Communication - -@enduml -``` -
-### Mailbox Data Exchange Protocol (ST25FTM) for NOR Flash Data transfer +### State Diagram
Diagram as a code ```plantuml @startuml -actor "Mobile phone" as reader -participant "ST25DV Tag" as tag -participant "STM32L4 MCU" as mcu - -== Data Request == - -reader -> tag : Request Data (Read NOR Flash Page) -tag -> mcu : Signal Data Request (Mailbox Interrupt) - -== Data Chunk 1 (128 bytes) == - -mcu -> mcu : Read NOR Flash (First 128 bytes) -mcu -> tag : Write Chunk 1 to T2H Mailbox -tag -> reader : Notify Data Available (Chunk 1) -reader -> tag : Read T2H Mailbox (Chunk 1) +title NFC FSM +hide empty description -reader -> reader : Process Data (Chunk 1) -reader -> tag : ACK for Chunk 1 +STANDBY: RF free\nI2C free +MAILBOX_RECEIVE_CMD: Mailbox received a message (command) +VALIDATE_MAILBOX: Check CRC +MAILBOX_WRITE_RESPONSE: Write response to mailbox +ERROR: Error state\n\nGLOBAL_ERROR: Error message -== Data Chunk 2 (128 bytes) == +[*] --> STANDBY : GLOBAL_CMD_INITIALIZE +STANDBY --> MAILBOX_RECEIVE_CMD : GPO_INTERRUPT -mcu -> mcu : Read NOR Flash (Next 128 bytes) -mcu -> tag : Write Chunk 2 to T2H Mailbox -tag -> reader : Notify Data Available (Chunk 2) -reader -> tag : Read T2H Mailbox (Chunk 2) +MAILBOX_RECEIVE_CMD --> VALIDATE_MAILBOX : MAILBOX_READ -reader -> reader : Process Data (Chunk 2) -reader -> tag : ACK for Chunk 2 +VALIDATE_MAILBOX --> MAILBOX_WRITE_RESPONSE: CRC_ERROR +VALIDATE_MAILBOX --> MAILBOX_WRITE_RESPONSE: GLOBAL_CMD_XXX +note on link + Handles globally +end note -== Data Exchange Complete == -reader -> reader : Assemble Complete 256 bytes +MAILBOX_WRITE_RESPONSE --> STANDBY: GLOBAL_NFC_MAILBOX_WRITE @enduml ``` diff --git a/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.c b/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.c index 01b8aef0..ff5ddfda 100644 --- a/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.c +++ b/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.c @@ -11,6 +11,8 @@ static osStatus_t handleNFCFSM(NFC_Actor_t *this, message_t *message); static osStatus_t handleInit(NFC_Actor_t *this, message_t *message); +static osStatus_t handleStandby(NFC_Actor_t *this, message_t *message); +static osStatus_t handleMailboxTransmit(NFC_Actor_t *this, message_t *message); extern actor_t* ACTORS_LIST_SystemRegistry[MAX_ACTORS]; @@ -68,11 +70,11 @@ void NFC_Task(void *argument) { static osStatus_t handleNFCFSM(NFC_Actor_t *this, message_t *message) { switch (this->state) { case NFC_NO_STATE: - handleInit(this, message); - return osOK; + return handleInit(this, message); case NFC_STANDBY_STATE: - // TODO handle low power state - return osOK; + return handleStandby(this, message); + case NFC_MAILBOX_TRANSMIT_STATE: + return handleMailboxTransmit(this, message); default: return osOK; } @@ -95,14 +97,56 @@ static osStatus_t handleNFCFSM(NFC_Actor_t *this, message_t *message) { } static osStatus_t handleInit(NFC_Actor_t *this, message_t *message) { - osStatus_t status; + osStatus_t ioStatus; + ST25DV_UID uid = {0x00000000, 0x00000000}; + const ST25DV_PASSWD i2cPwd = {0x00000000, 0x00000000}; switch (message->event) { case GLOBAL_CMD_INITIALIZE: - status = NFC_ST25DVInit(&this->st25dv); - if (status != NFCTAG_OK) + ioStatus = NFC_ST25DVInit(&this->st25dv); + if (ioStatus != NFCTAG_OK) + return osError; + + ioStatus = ST25DV_PresentI2CPassword(&this->st25dv, i2cPwd); + if (ioStatus != NFCTAG_OK) return osError; - this->state = NFC_STANDBY_STATE; + + ioStatus = ST25DV_ReadUID(&this->st25dv, &uid); + if (ioStatus != NFCTAG_OK) + return osError; + + #ifdef DEBUG + fprintf(stdout, "NFC task initialized, UID: 0x%x %x\n", uid.MsbUid, uid.LsbUid); + #endif + + TO_STATE(this, NFC_STANDBY_STATE); + return osOK; + default: + return osOK; + } +} + +static osStatus_t handleStandby(NFC_Actor_t *this, message_t *message) { + switch (message->event) { + case NFC_GPO_INTERRUPT: + NFC_HandleGPOInterrupt(&this->st25dv); + + TO_STATE(this, NFC_MAILBOX_TRANSMIT_STATE); + return osOK; + default: + return osOK; + } +} + +static osStatus_t handleMailboxTransmit(NFC_Actor_t *this, message_t *message) { + switch (message->event) { + case NFC_MAILBOX_HAS_NEW_MESSAGE: + NFC_ReadMailboxTo(&this->st25dv, this->mailboxBuffer); + + // Print for debug purposes + for (int i = 0; i < ST25DV_MAX_MAILBOX_LENGTH; i++) { + fprintf(stdout, "0x%x ", this->mailboxBuffer[i]); + } return osOK; default: diff --git a/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.h b/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.h index b77007c9..39989643 100644 --- a/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.h +++ b/firmware/iot-risk-logger-stm32l4/app/tasks/nfc/nfc.h @@ -19,6 +19,7 @@ extern "C" { typedef enum { NFC_NO_STATE = 0, NFC_STANDBY_STATE, + NFC_MAILBOX_TRANSMIT_STATE, NFC_STATE_ERROR, NFC_MAX_STATE } NFC_State_t; diff --git a/firmware/iot-risk-logger-stm32l4/iot-risk-logger-stm32l4.ioc b/firmware/iot-risk-logger-stm32l4/iot-risk-logger-stm32l4.ioc index 9a9dd8da..697f8072 100644 --- a/firmware/iot-risk-logger-stm32l4/iot-risk-logger-stm32l4.ioc +++ b/firmware/iot-risk-logger-stm32l4/iot-risk-logger-stm32l4.ioc @@ -58,16 +58,19 @@ Mcu.Pin31=VP_SYS_VS_LPOM Mcu.Pin32=VP_USB_DEVICE_VS_USB_DEVICE_MSC_FS Mcu.Pin33=VP_STMicroelectronics.X-CUBE-MEMS1_VS_BoardOoPartJjAcc_1.4.0_10.0.0 Mcu.Pin34=VP_STMicroelectronics.X-CUBE-NFC4_VS_BoardOoPartJjNFC_3.0.0_3.0.0 +Mcu.Pin35=VP_STMicroelectronics.FP-SNS-STBOX1_VS_MiddlewaresJjST25FTM_1.1.0_2.0.0 Mcu.Pin4=PA2 Mcu.Pin5=PA3 Mcu.Pin6=PA6 Mcu.Pin7=PA7 Mcu.Pin8=PB0 Mcu.Pin9=PB1 -Mcu.PinsNb=35 -Mcu.ThirdParty0=STMicroelectronics.X-CUBE-MEMS1.10.0.0 -Mcu.ThirdParty1=STMicroelectronics.X-CUBE-NFC4.3.0.0 -Mcu.ThirdPartyNb=2 +Mcu.PinsNb=36 +Mcu.ThirdParty0=STMicroelectronics.FP-SNS-STBOX1.2.0.0 +Mcu.ThirdParty1=STMicroelectronics.X-CUBE-MEMS1.10.0.0 +Mcu.ThirdParty2=STMicroelectronics.X-CUBE-NFC4.3.0.0 +Mcu.ThirdParty3=STMicroelectronics.X-CUBE-NFC7.1.0.1 +Mcu.ThirdPartyNb=4 Mcu.UserConstants= Mcu.UserName=STM32L412KBTx MxCube.Version=6.12.0 @@ -261,6 +264,10 @@ SH.GPXTI7.0=GPIO_EXTI7 SH.GPXTI7.ConfNb=1 SH.GPXTI8.0=GPIO_EXTI8 SH.GPXTI8.ConfNb=1 +STMicroelectronics.FP-SNS-STBOX1.2.0.0.IPParameters=ST25FTMCcMiddlewaresJjST25FTM +STMicroelectronics.FP-SNS-STBOX1.2.0.0.MiddlewaresJjST25FTM_Checked=true +STMicroelectronics.FP-SNS-STBOX1.2.0.0.ST25FTMCcMiddlewaresJjST25FTM=true +STMicroelectronics.FP-SNS-STBOX1.2.0.0_SwParameter=ST25FTMCcMiddlewaresJjST25FTM\:true; STMicroelectronics.X-CUBE-MEMS1.10.0.0.BSP.number=1 STMicroelectronics.X-CUBE-MEMS1.10.0.0.BoardOoPartJjAccJjLIS2DW12=I2C STMicroelectronics.X-CUBE-MEMS1.10.0.0.BoardOoPartJjAcc_Checked=true @@ -283,6 +290,8 @@ STMicroelectronics.X-CUBE-NFC4.3.0.0.NFCCcBoardOoPartJjNFC4JjST25DV=true STMicroelectronics.X-CUBE-NFC4.3.0.0.ST25DV_PART_NUMBER=UFDFPN8 STMicroelectronics.X-CUBE-NFC4.3.0.0.WirelessJjlibIinfc_Checked=false STMicroelectronics.X-CUBE-NFC4.3.0.0_SwParameter=libIinfcCcWirelessJjlibIinfcJjInterface\:Template;STM32CubeIiCustomIiBSPIiDriversCcBoardOoSupportJjCustomJjNFCTAG\:true;libIinfcCcWirelessJjlibIinfcJjCore\:true;NFCCcBoardOoPartJjNFC4JjST25DV\:true; +STMicroelectronics.X-CUBE-NFC7.1.0.1.BoardOoPartJjNFC7_Checked=false +STMicroelectronics.X-CUBE-NFC7.1.0.1_SwParameter=NFC7CcBoardOoPartJjNFC7JjST25DVXXKC\:true; USB_DEVICE.CLASS_NAME_FS=MSC USB_DEVICE.IPParameters=VirtualMode,VirtualModeFS,CLASS_NAME_FS USB_DEVICE.VirtualMode=Msc @@ -301,6 +310,8 @@ VP_RTC_VS_RTC_Calendar.Mode=RTC_Calendar VP_RTC_VS_RTC_Calendar.Signal=RTC_VS_RTC_Calendar VP_RTC_VS_RTC_WakeUp_intern.Mode=WakeUp VP_RTC_VS_RTC_WakeUp_intern.Signal=RTC_VS_RTC_WakeUp_intern +VP_STMicroelectronics.FP-SNS-STBOX1_VS_MiddlewaresJjST25FTM_1.1.0_2.0.0.Mode=MiddlewaresJjST25FTM +VP_STMicroelectronics.FP-SNS-STBOX1_VS_MiddlewaresJjST25FTM_1.1.0_2.0.0.Signal=STMicroelectronics.FP-SNS-STBOX1_VS_MiddlewaresJjST25FTM_1.1.0_2.0.0 VP_STMicroelectronics.X-CUBE-MEMS1_VS_BoardOoPartJjAcc_1.4.0_10.0.0.Mode=BoardOoPartJjAcc VP_STMicroelectronics.X-CUBE-MEMS1_VS_BoardOoPartJjAcc_1.4.0_10.0.0.Signal=STMicroelectronics.X-CUBE-MEMS1_VS_BoardOoPartJjAcc_1.4.0_10.0.0 VP_STMicroelectronics.X-CUBE-NFC4_VS_BoardOoPartJjNFC_3.0.0_3.0.0.Mode=BoardOoPartJjNFC diff --git a/firmware/iot-risk-logger-stm32l4/libraries/fp-sns-stbox1 b/firmware/iot-risk-logger-stm32l4/libraries/fp-sns-stbox1 deleted file mode 160000 index 492166e0..00000000 --- a/firmware/iot-risk-logger-stm32l4/libraries/fp-sns-stbox1 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 492166e009cc3ce9c59bd5491c09d84192502a2e diff --git a/hardware-debug/iot-risk-logger-stm32l4.ozone.jdebug.user b/hardware-debug/iot-risk-logger-stm32l4.ozone.jdebug.user index 2693fdce..ef469a06 100644 --- a/hardware-debug/iot-risk-logger-stm32l4.ozone.jdebug.user +++ b/hardware-debug/iot-risk-logger-stm32l4.ozone.jdebug.user @@ -1,37 +1,39 @@ +Breakpoint=/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/app/core/gpio_ext_interrupts/gpio_ext_interrupts.c:33, State=BP_STATE_ON Breakpoint=/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/app/tasks/memory/memory.c:111:5, State=BP_STATE_DISABLED Breakpoint=/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/app/tasks/memory/memory.c:140:5, State=BP_STATE_DISABLED Breakpoint=/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/app/tasks/memory/memory.c:253:5, State=BP_STATE_DISABLED +OpenDocument="gpio_ext_interrupts.c", FilePath="/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/app/core/gpio_ext_interrupts/gpio_ext_interrupts.c", Line=0 OpenDocument="stm32l4xx_it.c", FilePath="/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/Core/Src/stm32l4xx_it.c", Line=79 OpenDocument="memory.c", FilePath="/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/app/tasks/memory/memory.c", Line=88 OpenDocument="tasks.c", FilePath="/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/Middlewares/Third_Party/FreeRTOS/Source/tasks.c", Line=3652 -OpenDocument="main.c", FilePath="/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/Core/Src/main.c", Line=64 +OpenDocument="main.c", FilePath="/Users/artempolisskyi/projects/iot-risk-logger-stm32l4/firmware/iot-risk-logger-stm32l4/Core/Src/main.c", Line=55 OpenToolbar="Debug", Floating=0, x=0, y=0 -OpenWindow="Call Stack", DockArea=RIGHT, x=0, y=2, w=361, h=92, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Registers 1", DockArea=RIGHT, x=0, y=4, w=361, h=91, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, FilteredItems=[], RefreshRate=1 -OpenWindow="Source Files", DockArea=LEFT, x=0, y=2, w=323, h=175, TabPos=1, TopOfStack=1, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Disassembly", DockArea=RIGHT, x=0, y=3, w=361, h=92, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=323, h=168, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=255 -OpenWindow="Global Data", DockArea=RIGHT, x=0, y=0, w=361, h=97, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Local Data", DockArea=RIGHT, x=0, y=1, w=361, h=92, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=0, w=323, h=107, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Functions", DockArea=LEFT, x=0, y=2, w=323, h=175, TabPos=0, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Terminal", DockArea=BOTTOM, x=0, y=0, w=302, h=315, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 -OpenWindow="Console", DockArea=BOTTOM, x=1, y=0, w=959, h=315, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Call Stack", DockArea=RIGHT, x=0, y=2, w=361, h=184, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Registers 1", DockArea=RIGHT, x=0, y=4, w=361, h=183, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, FilteredItems=[], RefreshRate=1 +OpenWindow="Source Files", DockArea=LEFT, x=0, y=2, w=323, h=359, TabPos=1, TopOfStack=1, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Disassembly", DockArea=RIGHT, x=0, y=3, w=361, h=186, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=323, h=338, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=255 +OpenWindow="Global Data", DockArea=RIGHT, x=0, y=0, w=361, h=192, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Local Data", DockArea=RIGHT, x=0, y=1, w=361, h=189, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=0, w=323, h=223, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Functions", DockArea=LEFT, x=0, y=2, w=323, h=359, TabPos=0, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Terminal", DockArea=BOTTOM, x=0, y=0, w=304, h=315, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 +OpenWindow="Console", DockArea=BOTTOM, x=1, y=0, w=970, h=315, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0 SmartViewPlugin="", Page="", Toolbar="Hidden", Window="SmartView 1" -TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[197;100;100;100;980] TableHeader="Functions", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Address";"Size";"#Insts";"Source"], ColWidths=[1236;100;100;100;100] -TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[183;269;100;100;100;272] +TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[183;269;100;100;100;100] TableHeader="Vector Catches", SortCol="", SortOrder="ASCENDING", VisibleCols=["";"Vector Catch";"Description"], ColWidths=[50;300;500] TableHeader="Break & Tracepoints", SortCol="", SortOrder="ASCENDING", VisibleCols=["";"Type";"Location";"Extras"], ColWidths=[100;100;350;224] -TableHeader="Local Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[152;206;100;100;100;100] +TableHeader="Local Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[152;206;100;100;100;284] TableHeader="Call Stack", SortCol="Function", SortOrder="ASCENDING", VisibleCols=["Function";"Stack Frame";"Source";"PC";"Return Address";"Stack Used"], ColWidths=[264;112;128;100;168;100] TableHeader="Power Sampling", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Index";"Time";"Ch 0"], ColWidths=[100;100;100] TableHeader="Task List", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Name";"Run Count";"Priority";"Status";"Timeout";"Stack Info";"ID";"Mutex Count";"Notified Value";"Notify State"], ColWidths=[110;110;110;110;110;110;110;110;110;110] TableHeader="Registers 1", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Description"], ColWidths=[213;105;224] TableHeader="Watched Data 1", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Value";"Location";"Refresh"], ColWidths=[126;177;100;100] TableHeader="RegisterSelectionDialog", SortCol="None", SortOrder="ASCENDING", VisibleCols=[], ColWidths=[] +TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[197;100;100;100;980] TableHeader="TargetExceptionDialog", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Address";"Description"], ColWidths=[200;100;100;343] WatchedExpression="subscribersIds", DisplayFormat=DISPLAY_FORMAT_HEX, Window=Watched Data 1 WatchedExpression="TH_SENS_Actor", DisplayFormat=DISPLAY_FORMAT_HEX, Window=Watched Data 1 diff --git a/hardware-debug/logic-analyzer.dwf3work b/hardware-debug/logic-analyzer.dwf3work index be5625b974b7d6ac02346398e14341c30fae21e7..8c55779fdbf795e0aa9b5d6b31f40f47848e9105 100644 GIT binary patch literal 48274 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

S8_w^ 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