diff options
Diffstat (limited to 'Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_AT_Server/Core/Src/app_entry.c')
-rw-r--r-- | Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_AT_Server/Core/Src/app_entry.c | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_AT_Server/Core/Src/app_entry.c b/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_AT_Server/Core/Src/app_entry.c new file mode 100644 index 000000000..53b5bfd2a --- /dev/null +++ b/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_AT_Server/Core/Src/app_entry.c @@ -0,0 +1,647 @@ +/* USER CODE BEGIN Header */ +/** + ****************************************************************************** + * File Name : app_entry.c + * Description : Entry application source file for STM32WPAN Middleware. + ****************************************************************************** + * @attention + * + * Copyright (c) 2019-2021 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 */ + +/* Includes ------------------------------------------------------------------*/ +#include "app_common.h" +#include "main.h" +#include "app_entry.h" +#include "app_ble.h" +#include "ble.h" +#include "tl.h" +#include "stm32_seq.h" +#include "shci_tl.h" +#include "stm32_lpm.h" +#include "app_debug.h" +#include "shci.h" +#include "dbg_trace.h" +#include "otp.h" + +/* Private includes -----------------------------------------------------------*/ +/* USER CODE BEGIN Includes */ + +/* USER CODE END Includes */ + +/* Private typedef -----------------------------------------------------------*/ +extern RTC_HandleTypeDef hrtc; +/* USER CODE BEGIN PTD */ + +/* USER CODE END PTD */ + +/* Private defines -----------------------------------------------------------*/ +#define POOL_SIZE (CFG_TLBLE_EVT_QUEUE_LENGTH*4U*DIVC(( sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE ), 4U)) + +/* USER CODE BEGIN PD */ + +/* USER CODE END PD */ + +/* Private macros ------------------------------------------------------------*/ +/* USER CODE BEGIN PM */ + +/* USER CODE END PM */ + +/* Private variables ---------------------------------------------------------*/ +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t EvtPool[POOL_SIZE]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t SystemCmdBuffer; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t SystemSpareEvtBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t BleSpareEvtBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; + +/* USER CODE BEGIN PV */ + +/* USER CODE END PV */ + +/* Private functions prototypes-----------------------------------------------*/ +static void Config_HSE(void); +static void Reset_Device( void ); +static void Reset_IPCC( void ); +static void Reset_BackupDomain( void ); +static void System_Init( void ); +static void SystemPower_Config( void ); +static void appe_Tl_Init( void ); +static void APPE_SysStatusNot( SHCI_TL_CmdStatus_t status ); +static void APPE_SysUserEvtRx( void * pPayload ); +static void APPE_SysEvtReadyProcessing( void * pPayload ); +static void APPE_SysEvtError( void * pPayload); +static void Init_Exti( void ); +static void Init_Rtc( void ); +static void Init_Smps( void ); +/* USER CODE BEGIN PFP */ +static void Led_Init( void ); +static void Button_Init( void ); +/* USER CODE END PFP */ + +/* Functions Definition ------------------------------------------------------*/ +void MX_APPE_Config( void ) +{ + /** + * The OPTVERR flag is wrongly set at power on + * It shall be cleared before using any HAL_FLASH_xxx() api + */ + __HAL_FLASH_CLEAR_FLAG( FLASH_FLAG_OPTVERR ); + + /** + * Reset some configurations so that the system behave in the same way + * when either out of nReset or Power On + */ + Reset_Device( ); + + /* Tune the HSE internal load capacitors - P-NUCLEO-WB55.Nucleo board */ + Config_HSE(); + + return; +} + +void MX_APPE_Init( void ) +{ + System_Init( ); /**< System initialization */ + + SystemPower_Config(); /**< Configure the system Power Mode */ + + HW_TS_Init(hw_ts_InitMode_Full, &hrtc); /**< Initialize the TimerServer */ + +/* USER CODE BEGIN APPE_Init_1 */ +/* Configure the debug support if needed */ + APPD_Init(); + + /** + * The Standby mode should not be entered before the initialization is over + * The default state of the Low Power Manager is to allow the Standby Mode so an request is needed here + */ + UTIL_LPM_SetOffMode(1 << CFG_LPM_APP, UTIL_LPM_DISABLE); + + Led_Init(); + Button_Init(); +/* USER CODE END APPE_Init_1 */ + appe_Tl_Init(); /* Initialize all transport layers */ + + /** + * From now, the application is waiting for the ready event ( VS_HCI_C2_Ready ) + * received on the system channel before starting the Stack + * This system event is received with APPE_SysUserEvtRx() + */ +/* USER CODE BEGIN APPE_Init_2 */ + +/* USER CODE END APPE_Init_2 */ + return; +} +/* USER CODE BEGIN FD */ + +/* USER CODE END FD */ + +/************************************************************* + * + * LOCAL FUNCTIONS + * + *************************************************************/ +static void Reset_Device( void ) +{ +#if ( CFG_HW_RESET_BY_FW == 1 ) + Reset_BackupDomain(); + + Reset_IPCC(); +#endif + + return; +} + +static void Reset_BackupDomain( void ) +{ + if ((LL_RCC_IsActiveFlag_PINRST() != FALSE) && (LL_RCC_IsActiveFlag_SFTRST() == FALSE)) + { + HAL_PWR_EnableBkUpAccess(); /**< Enable access to the RTC registers */ + + /** + * Write twice the value to flush the APB-AHB bridge + * This bit shall be written in the register before writing the next one + */ + HAL_PWR_EnableBkUpAccess(); + + __HAL_RCC_BACKUPRESET_FORCE(); + __HAL_RCC_BACKUPRESET_RELEASE(); + } + + return; +} + +static void Reset_IPCC( void ) +{ + LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_IPCC); + + LL_C1_IPCC_ClearFlag_CHx( + IPCC, + LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 + | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + + LL_C2_IPCC_ClearFlag_CHx( + IPCC, + LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 + | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + + LL_C1_IPCC_DisableTransmitChannel( + IPCC, + LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 + | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + + LL_C2_IPCC_DisableTransmitChannel( + IPCC, + LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 + | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + + LL_C1_IPCC_DisableReceiveChannel( + IPCC, + LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 + | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + + LL_C2_IPCC_DisableReceiveChannel( + IPCC, + LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 + | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + + return; +} + +static void Config_HSE(void) +{ +/* !!! WARNING !!! Following code is valid only for P-NUCLEO-WB55 boards. +Code must be reviewed and optionally reimplemented depending on the target HW +and HSE capacitor tuning value storage location. +Please read AN5042 - HSE trimming for RF applications using the STM32WB series. */ + OTP_ID0_t * p_otp; + + /** + * Read HSE_Tuning from OTP + */ + p_otp = (OTP_ID0_t *) OTP_Read(0); + if (p_otp) + { + LL_RCC_HSE_SetCapacitorTuning(p_otp->hse_tuning); + } + + return; +} + +static void System_Init( void ) +{ + Init_Smps( ); + + Init_Exti( ); + + Init_Rtc( ); + + return; +} + +static void Init_Smps( void ) +{ +#if (CFG_USE_SMPS != 0) + /** + * Configure and enable SMPS + * + * The SMPS configuration is not yet supported by CubeMx + * when SMPS output voltage is set to 1.4V, the RF output power is limited to 3.7dBm + * the SMPS output voltage shall be increased for higher RF output power + */ + LL_PWR_SMPS_SetStartupCurrent(LL_PWR_SMPS_STARTUP_CURRENT_80MA); + LL_PWR_SMPS_SetOutputVoltageLevel(LL_PWR_SMPS_OUTPUT_VOLTAGE_1V40); + LL_PWR_SMPS_Enable(); +#endif + + return; +} + +static void Init_Exti( void ) +{ + /**< Disable all wakeup interrupt on CPU1 except IPCC(36), HSEM(38) */ + LL_EXTI_DisableIT_0_31(~0); + LL_EXTI_DisableIT_32_63( (~0) & (~(LL_EXTI_LINE_36 | LL_EXTI_LINE_38)) ); + + return; +} + +static void Init_Rtc( void ) +{ + /* Disable RTC registers write protection */ + LL_RTC_DisableWriteProtection(RTC); + + LL_RTC_WAKEUP_SetClock(RTC, CFG_RTC_WUCKSEL_DIVIDER); + + /* Enable RTC registers write protection */ + LL_RTC_EnableWriteProtection(RTC); + + return; +} + +/** + * @brief Configure the system for power optimization + * + * @note This API configures the system to be ready for low power mode + * + * @param None + * @retval None + */ +static void SystemPower_Config(void) +{ + /** + * Select HSI as system clock source after Wake Up from Stop mode + */ + LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); + + /* Initialize low power manager */ + UTIL_LPM_Init(); + /* Initialize the CPU2 reset value before starting CPU2 with C2BOOT */ + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + +#if (CFG_USB_INTERFACE_ENABLE != 0) + /** + * Enable USB power + */ + HAL_PWREx_EnableVddUSB(); +#endif + + return; +} + +static void appe_Tl_Init( void ) +{ + TL_MM_Config_t tl_mm_config; + SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf; + /**< Reference table initialization */ + TL_Init(); + + /**< System channel initialization */ + UTIL_SEQ_RegTask( 1<< CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, UTIL_SEQ_RFU, shci_user_evt_proc ); + SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&SystemCmdBuffer; + SHci_Tl_Init_Conf.StatusNotCallBack = APPE_SysStatusNot; + shci_init(APPE_SysUserEvtRx, (void*) &SHci_Tl_Init_Conf); + + /**< Memory Manager channel initialization */ + tl_mm_config.p_BleSpareEvtBuffer = BleSpareEvtBuffer; + tl_mm_config.p_SystemSpareEvtBuffer = SystemSpareEvtBuffer; + tl_mm_config.p_AsynchEvtPool = EvtPool; + tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; + TL_MM_Init( &tl_mm_config ); + + TL_Enable(); + + return; +} + +static void APPE_SysStatusNot( SHCI_TL_CmdStatus_t status ) +{ + UNUSED(status); + return; +} + +/** +* The type of the payload for a system user event is tSHCI_UserEvtRxParam +* When the system event is both : +* - a ready event (subevtcode = SHCI_SUB_EVT_CODE_READY) +* - reported by the FUS (sysevt_ready_rsp == FUS_FW_RUNNING) +* The buffer shall not be released +* ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable ) +* When the status is not filled, the buffer is released by default +*/ +static void APPE_SysUserEvtRx( void * pPayload ) +{ + TL_AsynchEvt_t *p_sys_event; + WirelessFwInfo_t WirelessInfo; + + p_sys_event = (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); + + /* Read the firmware version of both the wireless firmware and the FUS */ + SHCI_GetWirelessFwInfo( &WirelessInfo ); + APP_DBG_MSG("Wireless Firmware version %d.%d.%d\n", WirelessInfo.VersionMajor, WirelessInfo.VersionMinor, WirelessInfo.VersionSub); + APP_DBG_MSG("Wireless Firmware build %d\n", WirelessInfo.VersionReleaseType); + APP_DBG_MSG("FUS version %d.%d.%d\n\n", WirelessInfo.FusVersionMajor, WirelessInfo.FusVersionMinor, WirelessInfo.FusVersionSub); + + switch(p_sys_event->subevtcode) + { + case SHCI_SUB_EVT_CODE_READY: + APPE_SysEvtReadyProcessing(pPayload); + break; + + case SHCI_SUB_EVT_ERROR_NOTIF: + APPE_SysEvtError(pPayload); + break; + + case SHCI_SUB_EVT_BLE_NVM_RAM_UPDATE: + APP_DBG_MSG("-- BLE NVM RAM HAS BEEN UPDATED BY CMO+ \n"); + APP_DBG_MSG("SHCI_SUB_EVT_BLE_NVM_RAM_UPDATE : StartAddress = %lx , Size = %ld\n", + ((SHCI_C2_BleNvmRamUpdate_Evt_t*)p_sys_event->payload)->StartAddress, + ((SHCI_C2_BleNvmRamUpdate_Evt_t*)p_sys_event->payload)->Size); + break; + + case SHCI_SUB_EVT_NVM_START_WRITE: + APP_DBG_MSG("SHCI_SUB_EVT_NVM_START_WRITE : NumberOfWords = %ld\n", + ((SHCI_C2_NvmStartWrite_Evt_t*)p_sys_event->payload)->NumberOfWords); + break; + + case SHCI_SUB_EVT_NVM_END_WRITE: + APP_DBG_MSG("SHCI_SUB_EVT_NVM_END_WRITE\n"); + break; + + case SHCI_SUB_EVT_NVM_START_ERASE: + APP_DBG_MSG("SHCI_SUB_EVT_NVM_START_ERASE : NumberOfSectors = %ld\n", + ((SHCI_C2_NvmStartErase_Evt_t*)p_sys_event->payload)->NumberOfSectors); + break; + + case SHCI_SUB_EVT_NVM_END_ERASE: + APP_DBG_MSG("SHCI_SUB_EVT_NVM_END_ERASE\n"); + break; + + default: + break; + } + + return; +} + +/** +* @brief Notify when ready system event from the M0 firmware +*/ +static void APPE_SysEvtReadyProcessing( void * pPayload ) +{ + TL_AsynchEvt_t *p_sys_event; + SHCI_C2_Ready_Evt_t *p_sys_ready_event; + + SHCI_C2_CONFIG_Cmd_Param_t config_param = {0}; + uint32_t RevisionID=0; + + p_sys_event = (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); + p_sys_ready_event = (SHCI_C2_Ready_Evt_t*) p_sys_event->payload; + + if(p_sys_ready_event->sysevt_ready_rsp == WIRELESS_FW_RUNNING) + { + /** + * The wireless firmware is running on the CPU2 + */ + APP_DBG_MSG("SHCI_SUB_EVT_CODE_READY - WIRELESS_FW_RUNNING \n"); + + /* Traces channel initialization */ + APPD_EnableCPU2( ); + + /* Enable all events Notification */ + config_param.PayloadCmdSize = SHCI_C2_CONFIG_PAYLOAD_CMD_SIZE; + config_param.EvtMask1 = SHCI_C2_CONFIG_EVTMASK1_BIT0_ERROR_NOTIF_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT2_THREAD_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT3_NVM_START_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT4_NVM_END_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT5_NVM_START_ERASE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT6_NVM_END_ERASE_ENABLE; + + + /* Read revision identifier */ + /** + * @brief Return the device revision identifier + * @note This field indicates the revision of the device. + * @rmtoll DBGMCU_IDCODE REV_ID LL_DBGMCU_GetRevisionID + * @retval Values between Min_Data=0x00 and Max_Data=0xFFFF + */ + RevisionID = LL_DBGMCU_GetRevisionID(); + + APP_DBG_MSG("DBGMCU_GetRevisionID= %lx \n\n", RevisionID); + + config_param.RevisionID = RevisionID; + (void)SHCI_C2_Config(&config_param); + + APP_BLE_Init( ); + UTIL_LPM_SetOffMode(1U << CFG_LPM_APP, UTIL_LPM_ENABLE); + + } + else if (p_sys_ready_event->sysevt_ready_rsp == FUS_FW_RUNNING) + { + /** + * The FUS firmware is running on the CPU2 + * In the scope of this application, there should be no case when we get here + */ + APP_DBG_MSG("SHCI_SUB_EVT_CODE_READY - FUS_FW_RUNNING \n"); + + /* The packet shall not be released as this is not supported by the FUS */ + ((tSHCI_UserEvtRxParam*)pPayload)->status = SHCI_TL_UserEventFlow_Disable; + + } else { + + APP_DBG_MSG("SHCI_SUB_EVT_CODE_READY – UNEXPECTED CASE \n"); + } + return; +} + +/** +* @brief Notify a system error coming from the M0 firmware +*/ +static void APPE_SysEvtError( void * pPayload) +{ + TL_AsynchEvt_t *p_sys_event; + SCHI_SystemErrCode_t *p_sys_error_code; + + p_sys_event = (TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload); + p_sys_error_code = (SCHI_SystemErrCode_t*) p_sys_event->payload; + + APP_DBG_MSG("SHCI_SUB_EVT_ERROR_NOTIF WITH REASON %x \n",(*p_sys_error_code)); + + if ((*p_sys_error_code) == ERR_BLE_INIT) { + /* Error during BLE stack initialization */ + APP_DBG_MSG("SHCI_SUB_EVT_ERROR_NOTIF WITH REASON – ERR_BLE_INIT \n"); + } else { + APP_DBG_MSG("SHCI_SUB_EVT_ERROR_NOTIF WITH REASON – BLE ERROR \n"); + } + return; +} +/* USER CODE BEGIN FD_LOCAL_FUNCTIONS */ +static void Led_Init( void ) +{ +#if (CFG_LED_SUPPORTED == 1) + /** + * Leds Initialization + */ + + BSP_LED_Init(LED_BLUE); + BSP_LED_Init(LED_GREEN); + BSP_LED_Init(LED_RED); + + BSP_LED_On(LED_GREEN); +#endif + + return; +} + +static void Button_Init( void ) +{ +#if (CFG_BUTTON_SUPPORTED == 1) + /** + * Button Initialization + */ + + BSP_PB_Init(BUTTON_SW1, BUTTON_MODE_EXTI); + BSP_PB_Init(BUTTON_SW2, BUTTON_MODE_EXTI); + BSP_PB_Init(BUTTON_SW3, BUTTON_MODE_EXTI); +#endif + + return; +} +/* USER CODE END FD_LOCAL_FUNCTIONS */ + +/************************************************************* + * + * WRAP FUNCTIONS + * + *************************************************************/ +void HAL_Delay(uint32_t Delay) +{ + uint32_t tickstart = HAL_GetTick(); + uint32_t wait = Delay; + + /* Add a freq to guarantee minimum wait */ + if (wait < HAL_MAX_DELAY) + { + wait += HAL_GetTickFreq(); + } + + while ((HAL_GetTick() - tickstart) < wait) + { + /************************************************************************************ + * ENTER SLEEP MODE + ***********************************************************************************/ + LL_LPM_EnableSleep( ); /**< Clear SLEEPDEEP bit of Cortex System Control Register */ + + /** + * This option is used to ensure that store operations are completed + */ + #if defined ( __CC_ARM) + __force_stores(); + #endif + + __WFI( ); + } +} + +void MX_APPE_Process(void) +{ + /* USER CODE BEGIN MX_APPE_Process_1 */ + + /* USER CODE END MX_APPE_Process_1 */ + UTIL_SEQ_Run(UTIL_SEQ_DEFAULT); + /* USER CODE BEGIN MX_APPE_Process_2 */ + + /* USER CODE END MX_APPE_Process_2 */ +} + +void UTIL_SEQ_Idle( void ) +{ +#if ( CFG_LPM_SUPPORTED == 1) + UTIL_LPM_EnterLowPower( ); +#endif + return; +} + +/** + * @brief This function is called by the scheduler each time an event + * is pending. + * + * @param evt_waited_bm : Event pending. + * @retval None + */ +void UTIL_SEQ_EvtIdle( UTIL_SEQ_bm_t task_id_bm, UTIL_SEQ_bm_t evt_waited_bm ) +{ + UTIL_SEQ_Run( UTIL_SEQ_DEFAULT ); +} + +void shci_notify_asynch_evt(void* pdata) +{ + UTIL_SEQ_SetTask( 1<<CFG_TASK_SYSTEM_HCI_ASYNCH_EVT_ID, CFG_SCH_PRIO_0); + return; +} + +void shci_cmd_resp_release(uint32_t flag) +{ + UTIL_SEQ_SetEvt( 1<< CFG_IDLEEVT_SYSTEM_HCI_CMD_EVT_RSP_ID ); + return; +} + +void shci_cmd_resp_wait(uint32_t timeout) +{ + UTIL_SEQ_WaitEvt( 1<< CFG_IDLEEVT_SYSTEM_HCI_CMD_EVT_RSP_ID ); + return; +} +#if (CFG_BUTTON_SUPPORTED == 1) +/* USER CODE BEGIN FD_WRAP_FUNCTIONS */ +void HAL_GPIO_EXTI_Callback( uint16_t GPIO_Pin ) +{ + switch (GPIO_Pin) + { + case BUTTON_SW1_PIN: + APP_BLE_Key_Button1_Action(); + break; + + case BUTTON_SW2_PIN: + APP_BLE_Key_Button2_Action(); + break; + + case BUTTON_SW3_PIN: + APP_BLE_Key_Button3_Action(); + break; + + default: + break; + + } + return; +} +#endif +/* USER CODE END FD_WRAP_FUNCTIONS */ |