Repository: philips-software/amp-hal-st
Pull request #545: feat: internal flash stm32wb55
Author: login: dcayy
Created: 2025-04-14T14:39:53Z | Merged: 2025-04-28T13:16:31Z

- implemented internal flash driver for stm32wb55 based on document an5289 "Figure 10. Algorithm to write/erase data in the flash memory"
- updated stm watchdog interface to allow internal flash driver trigger watchdog while erasing and writing
- removed connection interval setting logic inside GapPeripheralSt, added interface to do this from application. 


Diff:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 66737504..3c145939 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,7 +26,7 @@ if (HALST_STANDALONE)
     FetchContent_Declare(
         emil
         GIT_REPOSITORY https://github.com/philips-software/amp-embedded-infra-lib.git
-        GIT_TAG        c01515ad8f2e05d15c71f8887e8d6c03daea0e2b # unreleased
+        GIT_TAG        72cf794a4a253473f39fde91b021f6b394d1bfd5 # unreleased
     )
     FetchContent_MakeAvailable(emil)
 
diff --git a/hal_st/middlewares/ble_middleware/GapPeripheralSt.cpp b/hal_st/middlewares/ble_middleware/GapPeripheralSt.cpp
index 511bf7a3..6e456b24 100644
--- a/hal_st/middlewares/ble_middleware/GapPeripheralSt.cpp
+++ b/hal_st/middlewares/ble_middleware/GapPeripheralSt.cpp
@@ -1,5 +1,5 @@
 #include "hal_st/middlewares/ble_middleware/GapPeripheralSt.hpp"
-#include "infra/event/EventDispatcherWithWeakPtr.hpp"
+#include "infra/event/EventDispatcher.hpp"
 
 namespace
 {
@@ -115,6 +115,13 @@ namespace hal
         }
     }
 
+    void GapPeripheralSt::SetConnectionParameters(const services::GapConnectionParameters& connParam)
+    {
+        aci_l2cap_connection_parameter_update_req(connectionContext.connectionHandle,
+            connParam.minConnIntMultiplier, connParam.maxConnIntMultiplier,
+            connParam.slaveLatency, connParam.supervisorTimeoutMs);
+    }
+
     void GapPeripheralSt::AllowPairing(bool allow)
     {
         allowPairing = allow;
@@ -164,18 +171,9 @@ namespace hal
     {
         GapSt::HandleHciLeEnhancedConnectionCompleteEvent(metaEvent);
 
-        RequestConnectionParameterUpdate();
-
         UpdateState(services::GapState::connected);
     }
 
-    void GapPeripheralSt::RequestConnectionParameterUpdate()
-    {
-        aci_l2cap_connection_parameter_update_req(connectionContext.connectionHandle,
-            connectionParameters.minConnIntMultiplier, connectionParameters.maxConnIntMultiplier,
-            connectionParameters.slaveLatency, connectionParameters.supervisorTimeoutMs);
-    }
-
     void GapPeripheralSt::Initialize(const GapService& gapService)
     {
         uint16_t gapServiceHandle, gapDevNameCharHandle, gapAppearanceCharHandle;
diff --git a/hal_st/middlewares/ble_middleware/GapPeripheralSt.hpp b/hal_st/middlewares/ble_middleware/GapPeripheralSt.hpp
index 33a02824..2120e90b 100644
--- a/hal_st/middlewares/ble_middleware/GapPeripheralSt.hpp
+++ b/hal_st/middlewares/ble_middleware/GapPeripheralSt.hpp
@@ -22,6 +22,7 @@ namespace hal
         infra::ConstByteRange GetScanResponseData() const override;
         void Advertise(services::GapAdvertisementType type, AdvertisementIntervalMultiplier multiplier) override;
         void Standby() override;
+        void SetConnectionParameters(const services::GapConnectionParameters& connParam) override;
 
         // Implementation of GapPairing
         void AllowPairing(bool allow) override;
@@ -32,7 +33,6 @@ namespace hal
         void HandleHciLeEnhancedConnectionCompleteEvent(evt_le_meta_event* metaEvent) override;
 
     private:
-        void RequestConnectionParameterUpdate();
         void UpdateAdvertisementData();
         void UpdateState(services::GapState newstate);
         void UpdateResolvingList();
diff --git a/hal_st/middlewares/ble_middleware/GapSt.cpp b/hal_st/middlewares/ble_middleware/GapSt.cpp
index f6a56365..b0a96755 100644
--- a/hal_st/middlewares/ble_middleware/GapSt.cpp
+++ b/hal_st/middlewares/ble_middleware/GapSt.cpp
@@ -31,13 +31,6 @@ namespace hal
         }
     }
 
-    const services::GapConnectionParameters GapSt::connectionParameters{
-        6,
-        6,
-        0,
-        500,
-    };
-
     GapSt::GapSt(hal::HciEventSource& hciEventSource, services::BondStorageSynchronizer& bondStorageSynchronizer, const Configuration& configuration)
         : HciEventSink(hciEventSource)
         , bondStorageSynchronizer(bondStorageSynchronizer)
diff --git a/hal_st/middlewares/ble_middleware/GapSt.hpp b/hal_st/middlewares/ble_middleware/GapSt.hpp
index 701195c0..9eb273b5 100644
--- a/hal_st/middlewares/ble_middleware/GapSt.hpp
+++ b/hal_st/middlewares/ble_middleware/GapSt.hpp
@@ -95,8 +95,6 @@ namespace hal
 
         ConnectionContext connectionContext;
 
-        static const services::GapConnectionParameters connectionParameters;
-
         const uint16_t invalidConnection = 0xffff;
 
         // Set PHY
diff --git a/hal_st/stm32fxxx/CMakeLists.txt b/hal_st/stm32fxxx/CMakeLists.txt
index b9136e29..a8b5cfd2 100644
--- a/hal_st/stm32fxxx/CMakeLists.txt
+++ b/hal_st/stm32fxxx/CMakeLists.txt
@@ -13,6 +13,7 @@ target_link_libraries(hal_st.stm32fxxx PUBLIC
     services.util
     st.hal_driver
     hal_st.cortex
+    $<$<STREQUAL:${TARGET_MCU},stm32wb55>:hal_st.stm32_wpan>
 )
 
 target_sources(hal_st.stm32fxxx PRIVATE
@@ -60,6 +61,8 @@ target_sources(hal_st.stm32fxxx PRIVATE
     $<$<STREQUAL:${TARGET_MCU_FAMILY},stm32h5xx>:FlashInternalHighCycleAreaStm.hpp>
     FlashInternalStm.cpp
     FlashInternalStm.hpp
+    $<$<STREQUAL:${TARGET_MCU},stm32wb55>:FlashInternalStmBle.cpp>
+    $<$<STREQUAL:${TARGET_MCU},stm32wb55>:FlashInternalStmBle.hpp>
     GpioStm.cpp
     GpioStm.hpp
     $<$<NOT:$<STREQUAL:${TARGET_MCU_FAMILY},stm32g0xx>>:I2cStm.cpp>
diff --git a/hal_st/stm32fxxx/FlashInternalStmBle.cpp b/hal_st/stm32fxxx/FlashInternalStmBle.cpp
new file mode 100644
index 00000000..cd398e82
--- /dev/null
+++ b/hal_st/stm32fxxx/FlashInternalStmBle.cpp
@@ -0,0 +1,199 @@
+#include "hal_st/stm32fxxx/FlashInternalStmBle.hpp"
+#include "infra/event/EventDispatcher.hpp"
+#include "shci.h"
+#include "st/STM32WBxx_HAL_Driver/Inc/stm32wbxx_hal_def.h"
+#include "stm32wbxx_ll_hsem.h"
+#include <cstdint>
+
+namespace hal
+{
+    FlashInternalStmBle::FlashInternalStmBle(uint32_t numberOfSectors, uint32_t sizeOfEachSector, infra::ConstByteRange flashMemory, WatchDogStm& watchdog)
+        : FlashHomogeneousInternalStm(numberOfSectors, sizeOfEachSector, flashMemory)
+        , flashMemory(flashMemory)
+        , watchdog(watchdog)
+        , hwSemInterruptHandler(HSEM_IRQn, [this]()
+              {
+                  HsemInterruptHandler();
+              })
+        , nmiHandler(NonMaskableInt_IRQn, [this]()
+              {
+                  EccErrorHandler();
+              })
+    {
+        SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7);
+    }
+
+    void FlashInternalStmBle::WriteBuffer(infra::ConstByteRange buffer, uint32_t address, infra::Function<void()> onDone)
+    {
+        onWriteDone = onDone;
+
+        HAL_FLASH_Unlock();
+        flashAlign.Align(address, buffer);
+        chunkToWrite = flashAlign.First();
+        TryWrite();
+    }
+
+    void FlashInternalStmBle::EraseSectors(uint32_t beginIndex, uint32_t endIndex, infra::Function<void()> onDone)
+    {
+        onEraseDone = onDone;
+
+        HAL_FLASH_Unlock();
+        SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON);
+        currentEraseIndex = beginIndex;
+        endEraseIndex = endIndex;
+        TryErase();
+    }
+
+    void FlashInternalStmBle::WaitForCpu2AllowFlashOperation(infra::Function<void()> onAvailable)
+    {
+        if (LL_HSEM_GetStatus(HSEM, hwBlockFlashReqByCpu2) == 0)
+            onAvailable();
+        else
+        {
+            onHwSemaphoreAvailable = onAvailable;
+            LL_HSEM_ClearFlag_C1ICR(HSEM, __HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2));
+            HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2));
+        }
+    }
+
+    void FlashInternalStmBle::HsemInterruptHandler()
+    {
+        uint32_t statusreg = HSEM_COMMON->MISR;
+        /*Disable Interrupts*/
+        HSEM_COMMON->IER &= ~((uint32_t)statusreg);
+        /*Clear Flags*/
+        HSEM_COMMON->ICR = ((uint32_t)statusreg);
+
+        HAL_HSEM_DeactivateNotification(__HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2));
+        if (statusreg == __HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2))
+            onHwSemaphoreAvailable();
+    }
+
+    void FlashInternalStmBle::EccErrorHandler()
+    {
+        if (__HAL_FLASH_GET_FLAG(FLASH_FLAG_ECCD))
+        {
+            __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ECCD);
+            uint32_t errorAddress = READ_BIT(FLASH->ECCR, FLASH_ECCR_ADDR_ECC);
+            uint32_t errorSector = SectorOfAddress(errorAddress);
+            uint32_t pageError = 0;
+            FLASH_EraseInitTypeDef eraseInitStruct{ .TypeErase = FLASH_TYPEERASE_PAGES, .Page = errorSector, .NbPages = 1 };
+            HAL_FLASHEx_Erase(&eraseInitStruct, &pageError);
+        }
+    }
+
+    void FlashInternalStmBle::TryWrite()
+    {
+        constexpr uint32_t maxWriteSize = 2048; // 2KB write takes ~21ms, which is similar to one sector erase
+        uint32_t writtenSize = 0;
+
+        while (chunkToWrite != nullptr && writtenSize < maxWriteSize)
+        {
+            if (!SingleOperation(FlashOperation::write))
+            {
+                WaitForCpu2AllowFlashOperation([this]()
+                    {
+                        TryWrite();
+                    });
+                return;
+            }
+            writtenSize += sizeof(uint64_t);
+        }
+
+        if (chunkToWrite == nullptr)
+        {
+            HAL_FLASH_Lock();
+            infra::EventDispatcher::Instance().Schedule([this]()
+                {
+                    onWriteDone();
+                });
+        }
+        else if (writtenSize >= maxWriteSize)
+            infra::EventDispatcher::Instance().Schedule([this]()
+                {
+                    TryWrite();
+                });
+    }
+
+    void FlashInternalStmBle::TryErase()
+    {
+        if (currentEraseIndex == endEraseIndex)
+        {
+            SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF);
+            HAL_FLASH_Lock();
+            infra::EventDispatcher::Instance().Schedule([this]()
+                {
+                    onEraseDone();
+                });
+        }
+        else
+        {
+            if (!SingleOperation(FlashOperation::erase))
+                WaitForCpu2AllowFlashOperation([this]()
+                    {
+                        TryErase();
+                    });
+            else
+                infra::EventDispatcher::Instance().Schedule([this]()
+                    {
+                        TryErase();
+                    });
+        }
+    }
+
+    bool FlashInternalStmBle::SingleOperation(FlashOperation operation)
+    {
+        CriticalSectionScoped criticalSectionScoped(watchdog);
+
+        if (LL_HSEM_1StepLock(HSEM, hwBlockFlashReqByCpu2) == 0)
+        {
+            if (operation == FlashOperation::write)
+                SingleWrite();
+            else if (operation == FlashOperation::erase)
+                SingleErase();
+            LL_HSEM_ReleaseLock(HSEM, hwBlockFlashReqByCpu2, 0);
+            return true;
+        }
+        else
+            return false;
+    }
+
+    void FlashInternalStmBle::SingleWrite()
+    {
+        uint32_t fullAddress = reinterpret_cast<uint32_t>(flashMemory.begin() + chunkToWrite->alignedAddress);
+        const uint64_t data = *reinterpret_cast<const uint64_t*>(chunkToWrite->data.begin());
+        auto result = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, fullAddress, data);
+        really_assert(result == HAL_OK);
+
+        chunkToWrite->data.pop_front(sizeof(uint64_t));
+        chunkToWrite->alignedAddress += sizeof(uint64_t);
+        if (chunkToWrite->data.empty())
+            chunkToWrite = flashAlign.Next();
+    }
+
+    void FlashInternalStmBle::SingleErase()
+    {
+        uint32_t pageError = 0;
+        FLASH_EraseInitTypeDef eraseInitStruct;
+        eraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
+        eraseInitStruct.Page = currentEraseIndex;
+        eraseInitStruct.NbPages = 1;
+        auto result = HAL_FLASHEx_Erase(&eraseInitStruct, &pageError);
+        really_assert(result == HAL_OK);
+        currentEraseIndex++;
+    }
+
+    FlashInternalStmBle::CriticalSectionScoped::CriticalSectionScoped(WatchDogStm& watchdog)
+        : watchdog(watchdog)
+    {
+        primaskBit = __get_PRIMASK();
+        __disable_irq();
+        watchdog.WatchDogRefresh();
+    }
+
+    FlashInternalStmBle::CriticalSectionScoped::~CriticalSectionScoped()
+    {
+        __set_PRIMASK(primaskBit);
+        watchdog.WatchDogRefresh();
+    }
+}
diff --git a/hal_st/stm32fxxx/FlashInternalStmBle.hpp b/hal_st/stm32fxxx/FlashInternalStmBle.hpp
new file mode 100644
index 00000000..180ee478
--- /dev/null
+++ b/hal_st/stm32fxxx/FlashInternalStmBle.hpp
@@ -0,0 +1,66 @@
+
+#ifndef HAL_FLASH_INTERNAL_STM_BLE_HPP
+#define HAL_FLASH_INTERNAL_STM_BLE_HPP
+
+#include "hal_st/cortex/InterruptCortex.hpp"
+#include "hal_st/stm32fxxx/FlashInternalStm.hpp"
+#include "hal_st/stm32fxxx/WatchDogStm.hpp"
+#include "services/util/FlashAlign.hpp"
+#include <cstdint>
+
+namespace hal
+{
+    class FlashInternalStmBle
+        : public FlashHomogeneousInternalStm
+    {
+    public:
+        FlashInternalStmBle(uint32_t numberOfSectors, uint32_t sizeOfEachSector, infra::ConstByteRange flashMemory, WatchDogStm& watchdog);
+
+        void WriteBuffer(infra::ConstByteRange buffer, uint32_t address, infra::Function<void()> onDone) override;
+        void EraseSectors(uint32_t beginIndex, uint32_t endIndex, infra::Function<void()> onDone) override;
+
+    private:
+        enum class FlashOperation
+        {
+            write,
+            erase
+        };
+
+        void WaitForCpu2AllowFlashOperation(infra::Function<void()> onAvailable);
+        void HsemInterruptHandler();
+        void EccErrorHandler();
+        void TryWrite();
+        void TryErase();
+        bool SingleOperation(FlashOperation operation);
+        void SingleWrite();
+        void SingleErase();
+
+        class CriticalSectionScoped
+        {
+        public:
+            CriticalSectionScoped(WatchDogStm& watchdog);
+            ~CriticalSectionScoped();
+
+        private:
+            uint32_t primaskBit;
+            WatchDogStm& watchdog;
+        };
+
+        static constexpr uint32_t hwBlockFlashReqByCpu2 = 7;
+        infra::ConstByteRange flashMemory;
+        WatchDogStm& watchdog;
+        ImmediateInterruptHandler hwSemInterruptHandler;
+        ImmediateInterruptHandler nmiHandler;
+        infra::Function<void()> onHwSemaphoreAvailable;
+
+        services::FlashAlign::WithAlignment<sizeof(uint64_t)> flashAlign;
+        services::FlashAlign::Chunk* chunkToWrite;
+        infra::Function<void()> onWriteDone;
+
+        uint32_t currentEraseIndex;
+        uint32_t endEraseIndex;
+        infra::Function<void()> onEraseDone;
+    };
+}
+
+#endif
diff --git a/hal_st/stm32fxxx/STM32WB55_Internal_Flash_Usage_with_BLE.md b/hal_st/stm32fxxx/STM32WB55_Internal_Flash_Usage_with_BLE.md
new file mode 100644
index 00000000..d1ee6f90
--- /dev/null
+++ b/hal_st/stm32fxxx/STM32WB55_Internal_Flash_Usage_with_BLE.md
@@ -0,0 +1,62 @@
+# STM32WB55 Internal Flash Usage with BLE Stack 
+
+## Scope
+
+This document explains the limitation of using internal flash next to running a BLE stack in STM32WB55 series chips and the associated design decisions. The information on limitations is based on application note[1], experimentations and discussions with ST.
+
+### Limitations on internal flash usage
+
+Relevant part in application note[1] is section 4.7
+CPU2, the radio cpu, cannot fetch and execute code from internal flash if CPU1, the application cpu, is writing to or erasing from flash. If CPU2 has time-critical code execution, for instance while advertising, scanning or connected, then CPU1 access to flash controlled. 
+
+For this purpose CPU1 needs to follow an algorithm to gain access to flash[1].
+In this algorithm Sem2 and Sem6 are only needed if the application is not running on bare metal. Therefore, we don't have to use these locks. We only need to use Sem7.
+
+By Sem7, CPU2 protects its timing by restricting flash access of CPU1. It gives Sem7 only if it is guaranteed to perform 1 flash operation before the next radio event. Sem7 will be given access by CPU2 when:
+- In Standby state: Always. There is no time-critical operation.
+- In Advertising/Scanning/Connected states: 
+  - For writing: If radio is idle and there is a ~4ms(time to write a chunk) buffer before the next radio event.
+  - For erasing: If radio is idle and there is a ~25ms(time to erase a sector) buffer before the next radio event.
+
+CPU1 must indicate before asking for Sem7 if it intends to write or erase. When CPU1 gets the sem7, it must:
+- Not keep the sem7 for longer but immediately start the flash operation
+- Only perform one flash operation and give back the semaphore at the completion
+
+### Experiments
+
+While connected or advertising, using the flash access algortihm[1], sem7 is requested repeatedly in quick successions to see the gaps.
+
+1. While advertising: 
+  - With 18.75ms or 25ms interval, sem7 cannot be obtained for erasing. This is expected as there is only <18.75ms or <25ms between two radio events, which is less than 25ms. 
+  - Note that radio events also take time so between two events is less than advertising interval. One advertisement event is usually 3tx+3rx packets length with a slight dependency to advertisement data size for tx packets
+  - With 31.25ms interval sem7 can be obtained. But we see a period of not exactly 31.25ms but longer. This is also expected since advertisement is always pseudo-randomly delayed by 0-10ms against air packet conflicts in the advertisement channels. 
+2. While connected:
+  - Results are similar to advertisement with one important difference. Radio events while connected do not have a fixed length. 
+  - If neiter peers sends data, than connection event is just one empty tx and one empty rx packets long. - If peers are sending data, in theory the whole time between the two connection events can be occupied leaving no space for CPU1 flash operation. In practive this is only an issue between two embedded peers as with Phones as centrals, number of packets for a single connection event is limited. For iPhones it is at max 6, for Androids it can be 8, 10, 12, etc but it is always restricted.
+  - For example, with a connection interval of 75ms, there should be always enough time for performing an erase.
+
+### Design for modifying flash during radio operations
+
+The idea to modify the flash during radio operations is that to increase the advertisement/scanning/connection interval sufficiently for the particular flash operation.
+- Advertisement interval: Controlled by peripheral
+- Scanning interval and window: Controlled by central
+- Connection interval: Can be proposed by peripheral but eventually decided by central
+
+From the central side, it is always possible to control all relevant properties.
+From the peripheral side regarding connection interval, peripheral needs to propose a desired high enough connection interval and central decides the actual value. 
+Practically all the phones as centrals conforms with peripheral suggested values as long as it is not too low.
+
+Some guideline values are:
+
+|             | Write                   | Erase                    |                                                                       |
+|-------------|-------------------------|--------------------------|-----------------------------------------------------------------------|
+| Advertising | interval > 10ms         | interval > 30ms          |                                                                       |
+| Connected   | interval > 75ms         | interval > 75ms          | Assuming 20(10tx+10rx) full packets for a single connection interval. |
+|             |                         |                          | Radio time = ~45ms                                                    | 
+| Scanning    | interval - window > 5ms | interval - window > 25ms |                                                                       |
+|             |                         |                          |                                                                       |
+
+
+## References
+
+[1] AN5289, How to build wireless applications with STM32WB MCUs, 18.0, https://www.st.com/resource/en/application_note/an5289-how-to-build-wireless-applications-with-stm32wb-mcus-stmicroelectronics.pdf
diff --git a/hal_st/stm32fxxx/WatchDogStm.cpp b/hal_st/stm32fxxx/WatchDogStm.cpp
index 3315b1b5..7aaa1806 100644
--- a/hal_st/stm32fxxx/WatchDogStm.cpp
+++ b/hal_st/stm32fxxx/WatchDogStm.cpp
@@ -33,10 +33,15 @@ namespace hal
             });
     }
 
-    void WatchDogStm::Interrupt()
+    void WatchDogStm::WatchDogRefresh()
     {
         HAL_WWDG_Refresh(&handle);
         WWDG->SR = 0;
+    }
+
+    void WatchDogStm::Interrupt()
+    {
+        WatchDogRefresh();
         if (++delay == 41) // 41 * 36ms = 1.5s
             onExpired();
     }
diff --git a/hal_st/stm32fxxx/WatchDogStm.hpp b/hal_st/stm32fxxx/WatchDogStm.hpp
index 774b25d6..ae6cf184 100644
--- a/hal_st/stm32fxxx/WatchDogStm.hpp
+++ b/hal_st/stm32fxxx/WatchDogStm.hpp
@@ -27,6 +27,7 @@ namespace hal
 
         WatchDogStm(const infra::Function<void()>& onExpired, const Config& config = Config());
 
+        void WatchDogRefresh();
         void Interrupt();
 
     private:


Reviews:
user: login: oguzcanoguz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T07:51:20Z
user: login: richardapeters
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-28T10:01:12Z

Review comments:
diff_hunk: @@ -0,0 +1,67 @@
+
+#ifndef HAL_FLASH_INTERNAL_STM_BLE_HPP
+#define HAL_FLASH_INTERNAL_STM_BLE_HPP
+
+#include "hal_st/cortex/InterruptCortex.hpp"
+#include "hal_st/stm32fxxx/FlashInternalStm.hpp"
+#include "hal_st/stm32fxxx/WatchDogStm.hpp"
+#include "services/util/FlashAlign.hpp"
+#include <cstdint>
+
+namespace hal
+{
+    class FlashInternalStmBle
+        : public FlashHomogeneousInternalStm
+    {
+    public:
+        static constexpr uint32_t hwBlockFlashReqByCpu2 = 7;
+
+        FlashInternalStmBle(uint32_t numberOfSectors, uint32_t sizeOfEachSector, infra::ConstByteRange flashMemory, WatchDogStm& watchdog);
+
+        void WriteBuffer(infra::ConstByteRange buffer, uint32_t address, infra::Function<void()> onDone) override;
+        void EraseSectors(uint32_t beginIndex, uint32_t endIndex, infra::Function<void()> onDone) override;
+
+    private:
+        enum class FlashOperation
+        {
+            write,
+            erase
+        };
+
+        void WaitForCpu2AllowFlashOperation(infra::Function<void()> onAvailable);
+        void HsemInterruptHandler();
+        void EccErrorHandler();
+        void TryFlashWrite();
+        void TryFlashErase();
+        bool FlashSingleOperation(FlashOperation operation);
+        void FlashSingleWrite();
path: hal_st/stm32fxxx/FlashInternalStmBle.hpp
user: login: oguzcanoguz
body: I think 'Flash' keyword is redundant in most of the members since it is obvious by the context, class name.
created_at: 2025-04-18T07:43:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+
+#ifndef HAL_FLASH_INTERNAL_STM_BLE_HPP
+#define HAL_FLASH_INTERNAL_STM_BLE_HPP
+
+#include "hal_st/cortex/InterruptCortex.hpp"
+#include "hal_st/stm32fxxx/FlashInternalStm.hpp"
+#include "hal_st/stm32fxxx/WatchDogStm.hpp"
+#include "services/util/FlashAlign.hpp"
+#include <cstdint>
+
+namespace hal
+{
+    class FlashInternalStmBle
+        : public FlashHomogeneousInternalStm
+    {
+    public:
+        static constexpr uint32_t hwBlockFlashReqByCpu2 = 7;
path: hal_st/stm32fxxx/FlashInternalStmBle.hpp
user: login: oguzcanoguz
body: why public?
created_at: 2025-04-18T07:44:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,199 @@
+#include "hal_st/stm32fxxx/FlashInternalStmBle.hpp"
+#include "infra/event/EventDispatcher.hpp"
+#include "shci.h"
+#include "st/STM32WBxx_HAL_Driver/Inc/stm32wbxx_hal_def.h"
+#include "stm32wbxx_ll_hsem.h"
+#include <cstdint>
+
+namespace hal
+{
+    FlashInternalStmBle::FlashInternalStmBle(uint32_t numberOfSectors, uint32_t sizeOfEachSector, infra::ConstByteRange flashMemory, WatchDogStm& watchdog)
+        : FlashHomogeneousInternalStm(numberOfSectors, sizeOfEachSector, flashMemory)
+        , flashMemory(flashMemory)
+        , watchdog(watchdog)
+        , hwSemInterruptHandler(HSEM_IRQn, [this]()
+              {
+                  HsemInterruptHandler();
+              })
+        , nmiHandler(NonMaskableInt_IRQn, [this]()
+              {
+                  EccErrorHandler();
+              })
+    {
+        SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7);
+    }
+
+    void FlashInternalStmBle::WriteBuffer(infra::ConstByteRange buffer, uint32_t address, infra::Function<void()> onDone)
+    {
+        onWriteDone = onDone;
+
+        HAL_FLASH_Unlock();
+        flashAlign.Align(address, buffer);
+        chunkToWrite = flashAlign.First();
+        TryFlashWrite();
+    }
+
+    void FlashInternalStmBle::EraseSectors(uint32_t beginIndex, uint32_t endIndex, infra::Function<void()> onDone)
+    {
+        onEraseDone = onDone;
+
+        HAL_FLASH_Unlock();
+        SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON);
+        currentEraseIndex = beginIndex;
+        endEraseIndex = endIndex;
+        TryFlashErase();
+    }
+
+    void FlashInternalStmBle::WaitForCpu2AllowFlashOperation(infra::Function<void()> onAvailable)
+    {
+        if (LL_HSEM_GetStatus(HSEM, hwBlockFlashReqByCpu2) == 0)
+            onAvailable();
+        else
+        {
+            onHwSemaphoreAvailable = onAvailable;
+            LL_HSEM_ClearFlag_C1ICR(HSEM, __HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2));
+            HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2));
+        }
+    }
+
+    void FlashInternalStmBle::HsemInterruptHandler()
+    {
+        uint32_t statusreg = HSEM_COMMON->MISR;
+        /*Disable Interrupts*/
+        HSEM_COMMON->IER &= ~((uint32_t)statusreg);
+        /*Clear Flags*/
+        HSEM_COMMON->ICR = ((uint32_t)statusreg);
+
+        HAL_HSEM_DeactivateNotification(__HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2));
+        if (statusreg == __HAL_HSEM_SEMID_TO_MASK(hwBlockFlashReqByCpu2))
+            onHwSemaphoreAvailable();
+    }
+
+    void FlashInternalStmBle::EccErrorHandler()
+    {
+        if (__HAL_FLASH_GET_FLAG(FLASH_FLAG_ECCD))
+        {
+            __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ECCD);
+            uint32_t errorAddress = READ_BIT(FLASH->ECCR, FLASH_ECCR_ADDR_ECC);
+            uint32_t errorSector = SectorOfAddress(errorAddress);
+            uint32_t pageError = 0;
+            FLASH_EraseInitTypeDef eraseInitStruct{ .TypeErase = FLASH_TYPEERASE_PAGES, .Page = errorSector, .NbPages = 1 };
+            HAL_FLASHEx_Erase(&eraseInitStruct, &pageError);
+        }
+    }
+
+    void FlashInternalStmBle::TryFlashWrite()
+    {
+        constexpr uint32_t maxWriteSize = 2048; // 2KB write takes ~21ms, which is similar to one sector erase
+        uint32_t writtenSize = 0;
+
+        while (chunkToWrite != nullptr && writtenSize < maxWriteSize)
+        {
+            if (!FlashSingleOperation(FlashOperation::write))
+            {
+                WaitForCpu2AllowFlashOperation([this]()
+                    {
+                        TryFlashWrite();
+                    });
+                return;
+            }
+            writtenSize += sizeof(uint64_t);
+        }
+
+        if (chunkToWrite == nullptr)
+        {
+            HAL_FLASH_Lock();
+            infra::EventDispatcher::Instance().Schedule([this]()
+                {
+                    onWriteDone();
+                });
+        }
+        else if (writtenSize >= maxWriteSize)
+            infra::EventDispatcher::Instance().Schedule([this]()
+                {
+                    TryFlashWrite();
+                });
+    }
+
+    void FlashInternalStmBle::TryFlashErase()
+    {
+        if (currentEraseIndex == endEraseIndex)
+        {
+            SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF);
+            HAL_FLASH_Lock();
+            infra::EventDispatcher::Instance().Schedule([this]()
+                {
+                    onEraseDone();
+                });
+        }
+        else
+        {
+            if (!FlashSingleOperation(FlashOperation::erase))
+                WaitForCpu2AllowFlashOperation([this]()
+                    {
+                        TryFlashErase();
+                    });
+            else
+                infra::EventDispatcher::Instance().Schedule([this]()
+                    {
+                        TryFlashErase();
+                    });
+        }
+    }
+
+    bool FlashInternalStmBle::FlashSingleOperation(FlashOperation operation)
+    {
+        CriticalSectionScoped criticalSectionScoped(watchdog);
+        bool lockCpu2FlashReq = LL_HSEM_1StepLock(HSEM, hwBlockFlashReqByCpu2) == 0;
path: hal_st/stm32fxxx/FlashInternalStmBle.cpp
user: login: oguzcanoguz
body: I am curious if we can class-ify this operation as you did nicely with CriticalSectionScoped?
created_at: 2025-04-18T07:50:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-14T14:42:52Z
body: ## [🦙 MegaLinter](https://megalinter.io/8.6.0) status: ⚠️ [WARNING](https://github.com/philips-software/amp-hal-st/actions/runs/14708466216)

| Descriptor  |                                               Linter                                                |Files|Fixed|                                   Errors                                    |Warnings|Elapsed time|
|-------------|-----------------------------------------------------------------------------------------------------|-----|----:|-----------------------------------------------------------------------------|--------|------------|
|✅ ACTION    |[actionlint](https://megalinter.io/8.6.0/descriptors/action_actionlint)                              |    6|     |                                                                            0|       0|0.25s       |
|✅ CPP       |[clang-format](https://megalinter.io/8.6.0/descriptors/cpp_clang_format)                             |   14|    3|                                                                            0|       0|0.37s       |
|✅ CPP       |[cppcheck](https://megalinter.io/8.6.0/descriptors/cpp_cppcheck)                                     |   14|     |                                                                            0|       0|0.75s       |
|✅ DOCKERFILE|[hadolint](https://megalinter.io/8.6.0/descriptors/dockerfile_hadolint)                              |    1|     |                                                                            0|       0|0.34s       |
|✅ JSON      |[jsonlint](https://megalinter.io/8.6.0/descriptors/json_jsonlint)                                    |    7|     |                                                                            0|       0|0.15s       |
|✅ JSON      |[prettier](https://megalinter.io/8.6.0/descriptors/json_prettier)                                    |    7|    2|                                                                            0|       0|0.63s       |
|⚠️ MARKDOWN  |[markdownlint](https://megalinter.io/8.6.0/descriptors/markdown_markdownlint)                        |    6|    1|[10](https://github.com/philips-software/amp-hal-st/actions/runs/14708466216)|       0|1.06s       |
|⚠️ MARKDOWN  |[markdown-link-check](https://megalinter.io/8.6.0/descriptors/markdown_markdown_link_check)          |    6|     |[2](https://github.com/philips-software/amp-hal-st/actions/runs/14708466216) |       0|121.6s      |
|✅ MARKDOWN  |[markdown-table-formatter](https://megalinter.io/8.6.0/descriptors/markdown_markdown_table_formatter)|    6|    1|                                                                            0|       0|0.27s       |
|✅ REPOSITORY|[checkov](https://megalinter.io/8.6.0/descriptors/repository_checkov)                                |yes  |     |no                                                                           |no      |27.12s      |
|✅ REPOSITORY|[git_diff](https://megalinter.io/8.6.0/descriptors/repository_git_diff)                              |yes  |     |no                                                                           |no      |0.1s        |
|✅ REPOSITORY|[grype](https://megalinter.io/8.6.0/descriptors/repository_grype)                                    |yes  |     |no                                                                           |no      |21.11s      |
|✅ REPOSITORY|[ls-lint](https://megalinter.io/8.6.0/descriptors/repository_ls_lint)                                |yes  |     |no                                                                           |no      |0.06s       |
|✅ REPOSITORY|[secretlint](https://megalinter.io/8.6.0/descriptors/repository_secretlint)                          |yes  |     |no                                                                           |no      |37.17s      |
|✅ REPOSITORY|[syft](https://megalinter.io/8.6.0/descriptors/repository_syft)                                      |yes  |     |no                                                                           |no      |1.1s        |
|✅ REPOSITORY|[trivy](https://megalinter.io/8.6.0/descriptors/repository_trivy)                                    |yes  |     |no                                                                           |no      |7.99s       |
|✅ REPOSITORY|[trivy-sbom](https://megalinter.io/8.6.0/descriptors/repository_trivy_sbom)                          |yes  |     |no                                                                           |no      |1.13s       |
|✅ REPOSITORY|[trufflehog](https://megalinter.io/8.6.0/descriptors/repository_trufflehog)                          |yes  |     |no                                                                           |no      |9.06s       |
|⚠️ SPELL     |[lychee](https://megalinter.io/8.6.0/descriptors/spell_lychee)                                       |   32|     |[2](https://github.com/philips-software/amp-hal-st/actions/runs/14708466216) |       0|74.02s      |
|✅ YAML      |[prettier](https://megalinter.io/8.6.0/descriptors/yaml_prettier)                                    |    9|    1|                                                                            0|       0|0.75s       |
|✅ YAML      |[v8r](https://megalinter.io/8.6.0/descriptors/yaml_v8r)                                              |    9|     |                                                                            0|       0|4.22s       |
|✅ YAML      |[yamllint](https://megalinter.io/8.6.0/descriptors/yaml_yamllint)                                    |    9|     |                                                                            0|       0|0.45s       |

See detailed report in [MegaLinter reports](https://github.com/philips-software/amp-hal-st/actions/runs/14708466216)

[![MegaLinter is graciously provided by OX Security](https://raw.githubusercontent.com/oxsecurity/megalinter/main/docs/assets/images/ox-banner.png)](https://www.ox.security/?ref=megalinter)

<!-- megalinter: github-comment-reporter workflow='Linting & Formatting' jobid='linter' -->

author_association: CONTRIBUTOR
user: login: dcayy
created_at: 2025-04-15T06:49:18Z
body: @oguzcanoguz need to wait for [this](https://github.com/philips-software/amp-embedded-infra-lib/pull/850) PR in emil first to have the interface change included
author_association: CONTRIBUTOR