diff --git a/app/os/CMakeLists.txt b/app/os/CMakeLists.txt index 0e6ba00395..21b3132205 100644 --- a/app/os/CMakeLists.txt +++ b/app/os/CMakeLists.txt @@ -40,6 +40,8 @@ target_link_libraries(LekaOS IMUKit MotionKit EventLoopKit + CoreDAC + AudioKit ) target_link_custom_leka_targets(LekaOS) diff --git a/app/os/main.cpp b/app/os/main.cpp index 13f6636019..0bee642b23 100644 --- a/app/os/main.cpp +++ b/app/os/main.cpp @@ -10,9 +10,11 @@ #include "rtos/Thread.h" #include "ActivityKit.h" +#include "AudioKit.h" #include "ChooseReinforcer.h" #include "CoreBattery.h" #include "CoreBufferedSerial.h" +#include "CoreDAC.h" #include "CoreDMA2D.hpp" #include "CoreDSI.hpp" #include "CoreFlashIS25LP016D.h" @@ -37,6 +39,7 @@ #include "CoreSDRAM.hpp" #include "CoreSPI.h" #include "CoreSTM32Hal.h" +#include "CoreSTM32HalBasicTimer.h" #include "CoreTimeout.h" #include "CoreVideo.hpp" #include "DisplayTags.h" @@ -65,6 +68,7 @@ #include "SuperSimon.h" #include "VideoKit.h" #include "bootutil/bootutil.h" +#include "commands/BehaviorCommand.h" #include "commands/LedFullCommand.h" #include "commands/LedRangeCommand.h" #include "commands/LedSingleCommand.h" @@ -229,6 +233,21 @@ namespace motors { } // namespace motors +auto hal = CoreSTM32Hal {}; + +namespace audio { + + namespace internal { + + extern "C" auto hal_timer = CoreSTM32HalBasicTimer {hal}; + extern "C" auto coredac = CoreDAC {hal, hal_timer}; + + } // namespace internal + + auto kit = AudioKit {internal::hal_timer, internal::coredac}; + +} // namespace audio + namespace display::internal { auto event_loop = EventLoopKit {}; @@ -236,7 +255,6 @@ namespace display::internal { auto corell = CoreLL {}; auto pixel = CGPixel {corell}; - auto hal = CoreSTM32Hal {}; auto coresdram = CoreSDRAM {hal}; auto coredma2d = CoreDMA2D {hal}; auto coredsi = CoreDSI {hal}; @@ -278,8 +296,8 @@ namespace motion::internal { auto motionkit = MotionKit {motors::left::motor, motors::right::motor, imukit, motion::internal::timeout}; -auto behaviorkit = BehaviorKit {videokit, ledkit, motors::left::motor, motors::right::motor}; -auto reinforcerkit = ReinforcerKit {videokit, ledkit, motionkit}; +auto behaviorkit = BehaviorKit {videokit, ledkit, motors::left::motor, motors::right::motor, audio::kit}; +auto reinforcerkit = ReinforcerKit {videokit, ledkit, audio::kit, motionkit}; namespace command { @@ -292,6 +310,7 @@ namespace command { auto led_range = LedRangeCommand {leds::ears, leds::belt}; auto motors = MotorsCommand {motors::left::motor, motors::right::motor}; auto reinforcer = ReinforcerCommand {reinforcerkit}; + auto behavior = BehaviorCommand {behaviorkit}; } // namespace internal @@ -301,6 +320,7 @@ namespace command { &internal::led_range, &internal::motors, &internal::reinforcer, + &internal::behavior, }); } // namespace command @@ -565,6 +585,8 @@ auto main() -> int imu::lsm6dsox.init(); imukit.init(); + audio::kit.initialize(); + robot::controller.initializeComponents(); robot::controller.registerOnUpdateLoadedCallback(firmware::setPendingUpdate); robot::controller.registerOnFactoryResetNotificationCallback(factory_reset::set); diff --git a/config/mbed_app.json b/config/mbed_app.json index f9bb933cae..d65055a6ce 100644 --- a/config/mbed_app.json +++ b/config/mbed_app.json @@ -4,6 +4,14 @@ "USE_HAL_JPEG_REGISTER_CALLBACKS": { "macro_name": "USE_HAL_JPEG_REGISTER_CALLBACKS", "value": "1U" + }, + "USE_HAL_TIM_REGISTER_CALLBACKS": { + "macro_name": "USE_HAL_TIM_REGISTER_CALLBACKS", + "value": "1U" + }, + "USE_HAL_DAC_REGISTER_CALLBACKS": { + "macro_name": "USE_HAL_DAC_REGISTER_CALLBACKS", + "value": "1U" } }, "target_overrides": { diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 4282cae5cf..daa3090cc5 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(${DRIVERS_DIR}/CoreBufferedSerial) add_subdirectory(${DRIVERS_DIR}/CoreEventFlags) add_subdirectory(${DRIVERS_DIR}/CoreEventQueue) +add_subdirectory(${DRIVERS_DIR}/CoreDAC) add_subdirectory(${DRIVERS_DIR}/CoreI2C) add_subdirectory(${DRIVERS_DIR}/CoreInterruptIn) add_subdirectory(${DRIVERS_DIR}/CoreLL) diff --git a/drivers/CoreDAC/CMakeLists.txt b/drivers/CoreDAC/CMakeLists.txt new file mode 100644 index 0000000000..c2e26193d8 --- /dev/null +++ b/drivers/CoreDAC/CMakeLists.txt @@ -0,0 +1,28 @@ +# Leka - LekaOS +# Copyright 2024 APF France handicap +# SPDX-License-Identifier: Apache-2.0 + +add_library(CoreDAC STATIC) + +target_include_directories(CoreDAC + PUBLIC + include +) + +target_sources(CoreDAC + PRIVATE + source/CoreSTM32HalBasicTimer.cpp + source/CoreDAC.cpp +) + +if(NOT(${CMAKE_PROJECT_NAME} STREQUAL "LekaOSUnitTests")) + target_sources(CoreDAC + PUBLIC + source/HAL_IRQHandlers.cpp + ) +endif() + +target_link_libraries(CoreDAC + mbed-os + CoreSTM32Hal +) diff --git a/drivers/CoreDAC/include/CoreDAC.h b/drivers/CoreDAC/include/CoreDAC.h new file mode 100644 index 0000000000..5b41fcced6 --- /dev/null +++ b/drivers/CoreDAC/include/CoreDAC.h @@ -0,0 +1,49 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "interface/drivers/DAC.h" +#include "interface/drivers/STM32Hal.h" +#include "interface/drivers/STM32HalBasicTimer.h" + +namespace leka { + +class CoreDAC : public interface::DACDMA +{ + public: + CoreDAC(interface::STM32Hal &hal, interface::STM32HalBasicTimer &hal_timer); + + [[nodiscard]] auto getHandle() -> DAC_HandleTypeDef & final; + + void initialize() final; + void terminate() final; + + void registerDataToPlay(std::span data) final; + void registerDMACallbacks(std::function const &on_half_transfer, + std::function const &on_complete_transfer) final; + + void start() final; + void stop() final; + + private: + void _registerMspCallbacks(); + void _initializeDMA(); + + interface::STM32Hal &_hal; + interface::STM32HalBasicTimer &_hal_timer; + + DAC_HandleTypeDef _hdac {}; + DMA_HandleTypeDef _hdma {}; + + std::span _data; + std::function _on_half_transfer {}; + std::function _on_complete_transfer {}; +}; + +} // namespace leka diff --git a/drivers/CoreDAC/include/CoreSTM32HalBasicTimer.h b/drivers/CoreDAC/include/CoreSTM32HalBasicTimer.h new file mode 100644 index 0000000000..b389ad6d4e --- /dev/null +++ b/drivers/CoreDAC/include/CoreSTM32HalBasicTimer.h @@ -0,0 +1,41 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "interface/drivers/STM32HalBasicTimer.h" + +namespace leka { + +class CoreSTM32HalBasicTimer : public interface::STM32HalBasicTimer +{ + static constexpr float DEFAULT_AUDIO_FILE_SAMPLE_RATE = 44'100; + + public: + CoreSTM32HalBasicTimer(interface::STM32Hal &hal); + + [[nodiscard]] auto getHandle() -> TIM_HandleTypeDef & final; + + void registerCallback(std::function const &callback); + void linkDACTimer(DAC_ChannelConfTypeDef *config) final; + + void initialize(float frequency = DEFAULT_AUDIO_FILE_SAMPLE_RATE) final; + void terminate() final; + + void start() final; + void stop() final; + + private: + void _registerMspCallbacks(); + + interface::STM32Hal &_hal; + + TIM_HandleTypeDef _htim {}; + + std::function _callback {}; +}; + +} // namespace leka diff --git a/drivers/CoreDAC/source/CoreDAC.cpp b/drivers/CoreDAC/source/CoreDAC.cpp new file mode 100644 index 0000000000..c3a28f899e --- /dev/null +++ b/drivers/CoreDAC/source/CoreDAC.cpp @@ -0,0 +1,116 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include "CoreDAC.h" + +using namespace leka; + +CoreDAC::CoreDAC(interface::STM32Hal &hal, interface::STM32HalBasicTimer &hal_timer) : _hal(hal), _hal_timer(hal_timer) +{ + _hdac.Instance = DAC; +} + +auto CoreDAC::getHandle() -> DAC_HandleTypeDef & +{ + return _hdac; +} + +void CoreDAC::initialize() +{ + _registerMspCallbacks(); + + _hal.HAL_DAC_Init(&_hdac); + + DAC_ChannelConfTypeDef config = {}; + config.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; + _hal_timer.linkDACTimer(&config); + _hal.HAL_DAC_ConfigChannel(&_hdac, &config, DAC_CHANNEL_1); + + static const auto &self = *this; + _hal.HAL_DAC_RegisterCallback(&_hdac, HAL_DAC_CH1_HALF_COMPLETE_CB_ID, + []([[maybe_unused]] DAC_HandleTypeDef *hdac) { + if (self._on_half_transfer != nullptr) { + self._on_half_transfer(); + } + }); + _hal.HAL_DAC_RegisterCallback(&_hdac, HAL_DAC_CH1_COMPLETE_CB_ID, []([[maybe_unused]] DAC_HandleTypeDef *hdac) { + if (self._on_complete_transfer != nullptr) { + self._on_complete_transfer(); + } + }); +} + +void CoreDAC::terminate() +{ + _hal.HAL_DAC_DeInit(&_hdac); +} + +void CoreDAC::registerDataToPlay(std::span data) +{ + _data = data; +} + +void CoreDAC::registerDMACallbacks(std::function const &on_half_transfer, + std::function const &on_complete_transfer) +{ + _on_half_transfer = on_half_transfer; + _on_complete_transfer = on_complete_transfer; +} + +void CoreDAC::start() +{ + _hal_timer.start(); + _hal.HAL_DAC_Start_DMA(&_hdac, DAC_CHANNEL_1, reinterpret_cast(_data.data()), _data.size(), + DAC_ALIGN_12B_R); +} + +void CoreDAC::stop() +{ + _hal.HAL_DAC_Stop_DMA(&_hdac, DAC_CHANNEL_1); +} + +void CoreDAC::_registerMspCallbacks() +{ + static auto &self = *this; + + _hal.HAL_DAC_RegisterCallback(&_hdac, HAL_DAC_MSPINIT_CB_ID, []([[maybe_unused]] DAC_HandleTypeDef *hdac) { + __HAL_LINKDMA(&self._hdac, DMA_Handle1, self._hdma); + self._initializeDMA(); + + self._hal.HAL_RCC_DAC_CLK_ENABLE(); + }); + + _hal.HAL_DAC_RegisterCallback(&_hdac, HAL_DAC_MSPDEINIT_CB_ID, []([[maybe_unused]] DAC_HandleTypeDef *hdac) { + self._hal.HAL_DMA_DeInit(&self._hdma); + + self._hal.HAL_RCC_DAC_CLK_DISABLE(); + }); +} + +void CoreDAC::_initializeDMA() +{ + _hal.HAL_RCC_DMA1_CLK_ENABLE(); + + _hal.HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 3, 0); + _hal.HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn); + + _hdma.Instance = DMA1_Stream5; // DMA1_Stream5 is the only DMA channel for DAC + + _hdma.Init.Channel = DMA_CHANNEL_7; + _hdma.Init.Direction = DMA_MEMORY_TO_PERIPH; + _hdma.Init.PeriphInc = DMA_PINC_DISABLE; + _hdma.Init.MemInc = DMA_MINC_ENABLE; + _hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + _hdma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + _hdma.Init.Mode = DMA_CIRCULAR; + _hdma.Init.Priority = DMA_PRIORITY_LOW; + _hdma.Init.FIFOMode = DMA_FIFOMODE_ENABLE; + _hdma.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL; + _hdma.Init.MemBurst = DMA_MBURST_SINGLE; + // Single mem burst is more ressource consuming than 4 burst or more + // However the buffer apparently needs to be of a size multiple of the burst mode chosen + _hdma.Init.PeriphBurst = DMA_PBURST_SINGLE; + + _hal.HAL_DMA_Init(&_hdma); +} diff --git a/drivers/CoreDAC/source/CoreSTM32HalBasicTimer.cpp b/drivers/CoreDAC/source/CoreSTM32HalBasicTimer.cpp new file mode 100644 index 0000000000..bb54da1113 --- /dev/null +++ b/drivers/CoreDAC/source/CoreSTM32HalBasicTimer.cpp @@ -0,0 +1,86 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include "CoreSTM32HalBasicTimer.h" +#include + +using namespace leka; + +CoreSTM32HalBasicTimer::CoreSTM32HalBasicTimer(interface::STM32Hal &hal) : _hal(hal) +{ + _htim.Instance = TIM6; +} + +auto CoreSTM32HalBasicTimer::getHandle() -> TIM_HandleTypeDef & +{ + return _htim; +} + +void CoreSTM32HalBasicTimer::registerCallback(std::function const &callback) +{ + _callback = callback; +} + +void CoreSTM32HalBasicTimer::initialize(float frequency) +{ + _registerMspCallbacks(); + + // CK_Timer = CK_INT / ((Prescaler + 1) * (Period + 1)) + const auto CK_INT = float {108'000'000.0}; + auto divider = static_cast(std::round(CK_INT / frequency)); + + _htim.Init.Prescaler = 0; + _htim.Init.Period = divider - 1; // ? min 1 + _htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + _hal.HAL_TIM_Base_Init(&_htim); + + auto timerMasterConfig = TIM_MasterConfigTypeDef {}; + timerMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; + _hal.HAL_TIMEx_MasterConfigSynchronization(&_htim, &timerMasterConfig); + + static const auto &self = *this; + _hal.HAL_TIM_RegisterCallback(&_htim, HAL_TIM_PERIOD_ELAPSED_CB_ID, []([[maybe_unused]] TIM_HandleTypeDef *htim) { + if (self._callback != nullptr) { + self._callback(); + } + }); +} + +void CoreSTM32HalBasicTimer::_registerMspCallbacks() +{ + static const auto &self = *this; + + _hal.HAL_TIM_RegisterCallback(&_htim, HAL_TIM_BASE_MSPINIT_CB_ID, []([[maybe_unused]] TIM_HandleTypeDef *htim) { + self._hal.HAL_RCC_TIM6_CLK_ENABLE(); + + self._hal.HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0x00, 0x00); + self._hal.HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn); + }); + + _hal.HAL_TIM_RegisterCallback(&_htim, HAL_TIM_BASE_MSPDEINIT_CB_ID, []([[maybe_unused]] TIM_HandleTypeDef *htim) { + self._hal.HAL_RCC_TIM6_CLK_DISABLE(); + }); +} + +void CoreSTM32HalBasicTimer::linkDACTimer(DAC_ChannelConfTypeDef *config) +{ + if (config != nullptr) { + config->DAC_Trigger = DAC_TRIGGER_T6_TRGO; + } +} + +void CoreSTM32HalBasicTimer::terminate() +{ + _hal.HAL_TIM_Base_DeInit(&_htim); +} + +void CoreSTM32HalBasicTimer::start() +{ + _hal.HAL_TIM_Base_Start_IT(&_htim); +} + +void CoreSTM32HalBasicTimer::stop() +{ + _hal.HAL_TIM_Base_Stop_IT(&_htim); +} diff --git a/drivers/CoreDAC/source/HAL_IRQHandlers.cpp b/drivers/CoreDAC/source/HAL_IRQHandlers.cpp new file mode 100644 index 0000000000..a4aca95cab --- /dev/null +++ b/drivers/CoreDAC/source/HAL_IRQHandlers.cpp @@ -0,0 +1,26 @@ +#include "CoreDAC.h" +#include "CoreSTM32HalBasicTimer.h" + +extern "C" { + +namespace audio::internal { + extern leka::CoreSTM32HalBasicTimer hal_timer; + extern leka::CoreDAC coredac; +} // namespace audio::internal + +void TIM6_DAC_IRQHandler() +{ + HAL_TIM_IRQHandler(&audio::internal::hal_timer.getHandle()); +} + +void TIM7_DAC_IRQHandler() +{ + HAL_TIM_IRQHandler(&audio::internal::hal_timer.getHandle()); +} + +void DMA1_Stream5_IRQHandler() +{ + HAL_DMA_IRQHandler(audio::internal::coredac.getHandle().DMA_Handle1); +} + +} // extern "C" diff --git a/drivers/CoreSTM32Hal/include/CoreSTM32Hal.h b/drivers/CoreSTM32Hal/include/CoreSTM32Hal.h index 7daa41772a..2453a066f8 100644 --- a/drivers/CoreSTM32Hal/include/CoreSTM32Hal.h +++ b/drivers/CoreSTM32Hal/include/CoreSTM32Hal.h @@ -22,10 +22,19 @@ class CoreSTM32Hal : public interface::STM32Hal void HAL_RCC_GPIOI_CLK_ENABLE() final; void HAL_RCC_GPIOJ_CLK_ENABLE() final; + void HAL_RCC_TIM6_CLK_ENABLE() final; + void HAL_RCC_TIM6_CLK_DISABLE() final; + void HAL_RCC_TIM7_CLK_ENABLE() final; + void HAL_RCC_TIM7_CLK_DISABLE() final; + void HAL_RCC_FMC_CLK_ENABLE() final; + void HAL_RCC_DMA1_CLK_ENABLE() final; void HAL_RCC_DMA2_CLK_ENABLE() final; + void HAL_RCC_DAC_CLK_ENABLE() final; + void HAL_RCC_DAC_CLK_DISABLE() final; + void HAL_RCC_JPEG_CLK_ENABLE() final; void HAL_RCC_JPEG_FORCE_RESET() final; void HAL_RCC_JPEG_RELEASE_RESET() final; @@ -104,6 +113,25 @@ class CoreSTM32Hal : public interface::STM32Hal auto HAL_JPEG_Pause(JPEG_HandleTypeDef *hjpeg, uint32_t XferSelection) -> HAL_StatusTypeDef final; auto HAL_JPEG_Resume(JPEG_HandleTypeDef *hjpeg, uint32_t XferSelection) -> HAL_StatusTypeDef final; + + auto HAL_TIM_Base_Init(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef final; + auto HAL_TIMEx_MasterConfigSynchronization(TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef *sMasterConfig) + -> HAL_StatusTypeDef final; + auto HAL_TIM_RegisterCallback(TIM_HandleTypeDef *htim, HAL_TIM_CallbackIDTypeDef CallbackID, + pTIM_CallbackTypeDef pCallback) -> HAL_StatusTypeDef final; + auto HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef final; + auto HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef final; + auto HAL_TIM_Base_DeInit(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef final; + + auto HAL_DAC_Init(DAC_HandleTypeDef *hdac) -> HAL_StatusTypeDef final; + auto HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac, DAC_ChannelConfTypeDef *sConfig, uint32_t Channel) + -> HAL_StatusTypeDef final; + auto HAL_DAC_RegisterCallback(DAC_HandleTypeDef *hdac, HAL_DAC_CallbackIDTypeDef CallbackID, + pDAC_CallbackTypeDef pCallback) -> HAL_StatusTypeDef final; + auto HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, + uint32_t Alignment) -> HAL_StatusTypeDef final; + auto HAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel) -> HAL_StatusTypeDef final; + auto HAL_DAC_DeInit(DAC_HandleTypeDef *hdac) -> HAL_StatusTypeDef final; }; } // namespace leka diff --git a/drivers/CoreSTM32Hal/source/CoreSTM32Hal.cpp b/drivers/CoreSTM32Hal/source/CoreSTM32Hal.cpp index c9352501ad..2beb724e2a 100644 --- a/drivers/CoreSTM32Hal/source/CoreSTM32Hal.cpp +++ b/drivers/CoreSTM32Hal/source/CoreSTM32Hal.cpp @@ -41,16 +41,51 @@ void CoreSTM32Hal::HAL_RCC_GPIOJ_CLK_ENABLE() __HAL_RCC_GPIOJ_CLK_ENABLE(); // NOLINT } +void CoreSTM32Hal::HAL_RCC_TIM6_CLK_ENABLE() +{ + __HAL_RCC_TIM6_CLK_ENABLE(); // NOLINT +} + +void CoreSTM32Hal::HAL_RCC_TIM6_CLK_DISABLE() +{ + __HAL_RCC_TIM6_CLK_DISABLE(); // NOLINT +} + +void CoreSTM32Hal::HAL_RCC_TIM7_CLK_ENABLE() +{ + __HAL_RCC_TIM7_CLK_ENABLE(); // NOLINT +} + +void CoreSTM32Hal::HAL_RCC_TIM7_CLK_DISABLE() +{ + __HAL_RCC_TIM7_CLK_DISABLE(); // NOLINT +} + void CoreSTM32Hal::HAL_RCC_FMC_CLK_ENABLE() { __HAL_RCC_FMC_CLK_ENABLE(); // NOLINT } +void CoreSTM32Hal::HAL_RCC_DMA1_CLK_ENABLE() +{ + __HAL_RCC_DMA1_CLK_ENABLE(); // NOLINT +} + void CoreSTM32Hal::HAL_RCC_DMA2_CLK_ENABLE() { __HAL_RCC_DMA2_CLK_ENABLE(); // NOLINT } +void CoreSTM32Hal::HAL_RCC_DAC_CLK_ENABLE() +{ + __HAL_RCC_DAC_CLK_ENABLE(); // NOLINT +} + +void CoreSTM32Hal::HAL_RCC_DAC_CLK_DISABLE() +{ + __HAL_RCC_DAC_CLK_DISABLE(); // NOLINT +} + void CoreSTM32Hal::HAL_RCC_JPEG_CLK_ENABLE() { __HAL_RCC_JPEG_CLK_ENABLE(); // NOLINT @@ -311,4 +346,69 @@ auto CoreSTM32Hal::HAL_JPEG_Resume(JPEG_HandleTypeDef *hjpeg, uint32_t XferSelec return ::HAL_JPEG_Resume(hjpeg, XferSelection); } +auto CoreSTM32Hal::HAL_TIM_Base_Init(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef +{ + return ::HAL_TIM_Base_Init(htim); +} + +auto CoreSTM32Hal::HAL_TIMEx_MasterConfigSynchronization(TIM_HandleTypeDef *htim, + TIM_MasterConfigTypeDef *sMasterConfig) -> HAL_StatusTypeDef +{ + return ::HAL_TIMEx_MasterConfigSynchronization(htim, sMasterConfig); +} + +auto CoreSTM32Hal::HAL_TIM_RegisterCallback(TIM_HandleTypeDef *htim, HAL_TIM_CallbackIDTypeDef CallbackID, + pTIM_CallbackTypeDef pCallback) -> HAL_StatusTypeDef +{ + return ::HAL_TIM_RegisterCallback(htim, CallbackID, pCallback); +} + +auto CoreSTM32Hal::HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef +{ + return ::HAL_TIM_Base_Start_IT(htim); +} + +auto CoreSTM32Hal::HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef +{ + return ::HAL_TIM_Base_Stop_IT(htim); +} + +auto CoreSTM32Hal::HAL_TIM_Base_DeInit(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef +{ + return ::HAL_TIM_Base_DeInit(htim); +} + +auto CoreSTM32Hal::HAL_DAC_Init(DAC_HandleTypeDef *hdac) -> HAL_StatusTypeDef +{ + return ::HAL_DAC_Init(hdac); +} + +auto CoreSTM32Hal::HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac, DAC_ChannelConfTypeDef *sConfig, uint32_t Channel) + -> HAL_StatusTypeDef +{ + return ::HAL_DAC_ConfigChannel(hdac, sConfig, Channel); +} + +auto CoreSTM32Hal::HAL_DAC_RegisterCallback(DAC_HandleTypeDef *hdac, HAL_DAC_CallbackIDTypeDef CallbackID, + pDAC_CallbackTypeDef pCallback) -> HAL_StatusTypeDef +{ + return ::HAL_DAC_RegisterCallback(hdac, CallbackID, pCallback); +} + +auto CoreSTM32Hal::HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, + uint32_t Alignment) -> HAL_StatusTypeDef +{ + return ::HAL_DAC_Start_DMA(hdac, Channel, pData, Length, Alignment); +} + +auto CoreSTM32Hal::HAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel) -> HAL_StatusTypeDef +{ + return ::HAL_DAC_Stop_DMA(hdac, Channel); +} + +auto CoreSTM32Hal::HAL_DAC_DeInit(DAC_HandleTypeDef *hdac) -> HAL_StatusTypeDef +{ + return ::HAL_DAC_DeInit(hdac); +} + } // namespace leka diff --git a/include/interface/drivers/DAC.h b/include/interface/drivers/DAC.h new file mode 100644 index 0000000000..6ec3464e49 --- /dev/null +++ b/include/interface/drivers/DAC.h @@ -0,0 +1,38 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "interface/drivers/STM32Hal.h" + +namespace leka::interface { + +class DACBase +{ + public: + virtual ~DACBase() = default; + + [[nodiscard]] virtual auto getHandle() -> DAC_HandleTypeDef & = 0; + + virtual void initialize() = 0; + virtual void terminate() = 0; + + virtual void start() = 0; + virtual void stop() = 0; +}; + +class DACDMA : public DACBase +{ + public: + virtual ~DACDMA() = default; + + virtual void registerDataToPlay(std::span data) = 0; + virtual void registerDMACallbacks(std::function const &on_half_transfer, + std::function const &on_complete_transfer) = 0; +}; + +} // namespace leka::interface diff --git a/include/interface/drivers/STM32Hal.h b/include/interface/drivers/STM32Hal.h index b70089f827..4b5f56ad86 100644 --- a/include/interface/drivers/STM32Hal.h +++ b/include/interface/drivers/STM32Hal.h @@ -21,10 +21,20 @@ class STM32Hal virtual void HAL_RCC_GPIOI_CLK_ENABLE() = 0; virtual void HAL_RCC_GPIOJ_CLK_ENABLE() = 0; + virtual void HAL_RCC_TIM6_CLK_ENABLE() = 0; + virtual void HAL_RCC_TIM6_CLK_DISABLE() = 0; + + virtual void HAL_RCC_TIM7_CLK_ENABLE() = 0; + virtual void HAL_RCC_TIM7_CLK_DISABLE() = 0; + virtual void HAL_RCC_FMC_CLK_ENABLE() = 0; + virtual void HAL_RCC_DMA1_CLK_ENABLE() = 0; virtual void HAL_RCC_DMA2_CLK_ENABLE() = 0; + virtual void HAL_RCC_DAC_CLK_ENABLE() = 0; + virtual void HAL_RCC_DAC_CLK_DISABLE() = 0; + virtual void HAL_RCC_JPEG_CLK_ENABLE() = 0; virtual void HAL_RCC_JPEG_FORCE_RESET() = 0; virtual void HAL_RCC_JPEG_RELEASE_RESET() = 0; @@ -106,6 +116,25 @@ class STM32Hal virtual auto HAL_JPEG_Pause(JPEG_HandleTypeDef *hjpeg, uint32_t XferSelection) -> HAL_StatusTypeDef = 0; virtual auto HAL_JPEG_Resume(JPEG_HandleTypeDef *hjpeg, uint32_t XferSelection) -> HAL_StatusTypeDef = 0; + + virtual auto HAL_TIM_Base_Init(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef = 0; + virtual auto HAL_TIMEx_MasterConfigSynchronization(TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef *sMasterConfig) + -> HAL_StatusTypeDef = 0; + virtual auto HAL_TIM_RegisterCallback(TIM_HandleTypeDef *htim, HAL_TIM_CallbackIDTypeDef CallbackID, + pTIM_CallbackTypeDef pCallback) -> HAL_StatusTypeDef = 0; + virtual auto HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef = 0; + virtual auto HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef = 0; + virtual auto HAL_TIM_Base_DeInit(TIM_HandleTypeDef *htim) -> HAL_StatusTypeDef = 0; + + virtual auto HAL_DAC_Init(DAC_HandleTypeDef *hdac) -> HAL_StatusTypeDef = 0; + virtual auto HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac, DAC_ChannelConfTypeDef *sConfig, uint32_t Channel) + -> HAL_StatusTypeDef = 0; + virtual auto HAL_DAC_RegisterCallback(DAC_HandleTypeDef *hdac, HAL_DAC_CallbackIDTypeDef CallbackID, + pDAC_CallbackTypeDef pCallback) -> HAL_StatusTypeDef = 0; + virtual auto HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, + uint32_t Alignment) -> HAL_StatusTypeDef = 0; + virtual auto HAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel) -> HAL_StatusTypeDef = 0; + virtual auto HAL_DAC_DeInit(DAC_HandleTypeDef *hdac) -> HAL_StatusTypeDef = 0; }; } // namespace leka::interface diff --git a/include/interface/drivers/STM32HalBasicTimer.h b/include/interface/drivers/STM32HalBasicTimer.h new file mode 100644 index 0000000000..73fee09ee3 --- /dev/null +++ b/include/interface/drivers/STM32HalBasicTimer.h @@ -0,0 +1,27 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "interface/drivers/STM32Hal.h" + +namespace leka::interface { + +class STM32HalBasicTimer +{ + public: + virtual ~STM32HalBasicTimer() = default; + + [[nodiscard]] virtual auto getHandle() -> TIM_HandleTypeDef & = 0; + + virtual void linkDACTimer(DAC_ChannelConfTypeDef *config) = 0; + + virtual void initialize(float frequency) = 0; + virtual void terminate() = 0; + + virtual void start() = 0; + virtual void stop() = 0; +}; + +} // namespace leka::interface diff --git a/include/interface/platform/File.h b/include/interface/platform/File.h index b991d7e40b..df16153fc1 100644 --- a/include/interface/platform/File.h +++ b/include/interface/platform/File.h @@ -21,6 +21,8 @@ struct File { virtual auto read(std::span buffer) -> std::size_t = 0; virtual auto write(std::span data) -> std::size_t = 0; + virtual auto read(std::span buffer) -> std::size_t = 0; + virtual auto read(std::span buffer) -> std::size_t = 0; virtual auto write(std::span data) -> std::size_t = 0; diff --git a/libs/AudioKit/CMakeLists.txt b/libs/AudioKit/CMakeLists.txt new file mode 100644 index 0000000000..ee44148731 --- /dev/null +++ b/libs/AudioKit/CMakeLists.txt @@ -0,0 +1,24 @@ +# Leka - LekaOS +# Copyright 2024 APF France handicap +# SPDX-License-Identifier: Apache-2.0 + +add_library(AudioKit STATIC) + +target_include_directories(AudioKit + PUBLIC + include +) + +target_sources(AudioKit + PRIVATE + source/AudioKit.cpp + source/WavFile.cpp +) + +target_link_libraries(AudioKit + mbed-os + CoreSTM32Hal + CoreDAC + CoreEventQueue + FileManagerKit +) diff --git a/libs/AudioKit/include/AudioKit.h b/libs/AudioKit/include/AudioKit.h new file mode 100644 index 0000000000..79f1e74e22 --- /dev/null +++ b/libs/AudioKit/include/AudioKit.h @@ -0,0 +1,53 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "CoreEventQueue.h" +#include "DigitalOut.h" +#include "FileManagerKit.h" +#include "WavFile.h" +#include "interface/drivers/DAC.h" +#include "interface/drivers/STM32HalBasicTimer.h" + +namespace leka { + +class AudioKit +{ + public: + explicit AudioKit(interface::STM32HalBasicTimer &dac_timer, interface::DACDMA &dac) + : _dac_timer(dac_timer), _dac(dac) + { + // nothing to do + } + + void initialize(); + + void enableAudio(); + void disableAudio(); + + void play(const std::filesystem::path &path); + void stop(); + + void setData(uint32_t offset); + void run(); + + private: + mbed::DigitalOut _audio_enable {SOUND_ENABLE, 1}; + + interface::STM32HalBasicTimer &_dac_timer; + interface::DACDMA &_dac; + + FileManagerKit::File _file {}; + WavFile _wav_file {_file}; + + CoreEventQueue _event_queue {}; + + static constexpr uint32_t played_data_size {2000}; + std::array played_data {}; + + static constexpr uint8_t _repetition {10}; +}; + +} // namespace leka diff --git a/libs/AudioKit/include/WavFile.h b/libs/AudioKit/include/WavFile.h new file mode 100644 index 0000000000..3de031ac90 --- /dev/null +++ b/libs/AudioKit/include/WavFile.h @@ -0,0 +1,57 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "interface/platform/File.h" + +namespace leka { + +class WavFile +{ + constexpr static uint32_t blockID_fileType = 0x46464952; // corresponds to the letters 'RIFF' in reverse order + constexpr static uint32_t blockID_fileFormat = 0x45564157; // corresponds to the letters 'WAVE' in reverse order + constexpr static uint32_t blockID_format = 0x20746D66; // corresponds to the letters 'fmt ' in reverse order + constexpr static uint32_t blockID_data = 0x61746164; // corresponds to the letters 'data' in reverse order + + public: + using WavHeader = struct wavHeader { + uint32_t FileTypeBlockID; + uint32_t FileSize; // Nb bytes in file -8 bytes + uint32_t FileFormatID; + uint32_t FormatBlockID; + uint32_t FormatBlockSize; // Nb bytes in format block -16 bytes + uint16_t AudioFormat; + uint16_t NumChannels; + uint32_t SamplingRate; + uint32_t BytePerSec; + uint16_t BytePerSampleBlock; + uint16_t BitsPerSample; + uint32_t DataBlockID; + uint32_t DataSize; + }; + + explicit WavFile(interface::File &file) : _file(file) {} + + auto open(std::filesystem::path filename) -> bool; + + [[nodiscard]] auto getHeader() const -> const WavHeader &; + + auto read(std::span buffer) -> bool; + [[nodiscard]] auto isEndOfFile() const -> bool; + + private: + void readHeader(); + + interface::File &_file; + + const std::filesystem::path directory {"/fs/home/wav"}; + const std::string extension {".wav"}; + + WavHeader _header {}; + + bool _is_eof {false}; +}; + +} // namespace leka diff --git a/libs/AudioKit/source/AudioKit.cpp b/libs/AudioKit/source/AudioKit.cpp new file mode 100644 index 0000000000..b312a8adf8 --- /dev/null +++ b/libs/AudioKit/source/AudioKit.cpp @@ -0,0 +1,86 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include "AudioKit.h" + +#include "rtos/ThisThread.h" + +#include "LogKit.h" + +using namespace leka; +using namespace std::chrono_literals; + +void AudioKit::initialize() +{ + _event_queue.dispatch_forever(); + + auto onHalfTransfer = [this] { setData(0); }; + auto onCompleteTransfer = [this] { setData(played_data_size / 2); }; + + _dac.registerDMACallbacks([this, onHalfTransfer] { _event_queue.call(onHalfTransfer); }, + [this, onCompleteTransfer] { _event_queue.call(onCompleteTransfer); }); + _dac.registerDataToPlay(played_data); + + constexpr auto file_sample_rate = float {44'100.0}; + constexpr auto timer_rate = float {file_sample_rate * _repetition}; + + _dac_timer.initialize(timer_rate); + _dac.initialize(); +} + +void AudioKit::enableAudio() +{ + _audio_enable = 1; +} + +void AudioKit::disableAudio() +{ + stop(); + _audio_enable = 0; +} + +void AudioKit::play(const std::filesystem::path &path) +{ + auto is_open = _wav_file.open(path); + if (!is_open) { + return; + } + + setData(0); + setData(played_data_size / 2); + + enableAudio(); + + run(); +} + +void AudioKit::stop() +{ + _dac.stop(); +} + +void AudioKit::setData(uint32_t offset) +{ + if (_wav_file.isEndOfFile()) { + stop(); + return; + } + + constexpr auto file_data_size = played_data_size / _repetition / 2; + auto file_data = std::array {}; + + _wav_file.read(file_data); + + for (uint32_t i = 0; i < file_data.size(); i++) { + auto normalized_value = static_cast((file_data.at(i) + 0x8000) >> 4); + std::fill_n(played_data.begin() + offset + i * _repetition, _repetition, normalized_value); + } + + rtos::ThisThread::sleep_for(2ms); // Related to played_data_size and _repetition +} + +void AudioKit::run() +{ + _dac.start(); +} diff --git a/libs/AudioKit/source/WavFile.cpp b/libs/AudioKit/source/WavFile.cpp new file mode 100644 index 0000000000..6183c5be90 --- /dev/null +++ b/libs/AudioKit/source/WavFile.cpp @@ -0,0 +1,56 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include "WavFile.h" +#include + +#include "FileManagerKit.h" + +using namespace leka; + +auto WavFile::open(std::filesystem::path filename) -> bool +{ + auto path = directory / filename; + path += extension; + + if (FileManagerKit::file_is_missing(path)) { + return false; + } + + auto is_open = _file.open(path, "r"); + + readHeader(); + + _is_eof = false; + + return is_open; +} + +void WavFile::readHeader() +{ + std::array buffer {}; + + _file.read(buffer); + + std::memcpy(&_header, &buffer, sizeof(_header)); +} + +auto WavFile::getHeader() const -> const WavHeader & +{ + return _header; +} + +auto WavFile::read(std::span buffer) -> bool +{ + auto bytes_read = _file.read(buffer); + + _is_eof = bytes_read != std::size(buffer); + auto is_not_eof = !_is_eof; + return is_not_eof; +} + +auto WavFile::isEndOfFile() const -> bool +{ + return _is_eof; +} diff --git a/libs/BehaviorKit/CMakeLists.txt b/libs/BehaviorKit/CMakeLists.txt index 4366423873..6e983956ac 100644 --- a/libs/BehaviorKit/CMakeLists.txt +++ b/libs/BehaviorKit/CMakeLists.txt @@ -18,6 +18,7 @@ target_link_libraries(BehaviorKit LedKit VideoKit CoreMotor + AudioKit ) if(${CMAKE_PROJECT_NAME} STREQUAL "LekaOSUnitTests") diff --git a/libs/BehaviorKit/include/BehaviorKit.h b/libs/BehaviorKit/include/BehaviorKit.h index cfb7b82fe7..d27b281a36 100644 --- a/libs/BehaviorKit/include/BehaviorKit.h +++ b/libs/BehaviorKit/include/BehaviorKit.h @@ -4,6 +4,7 @@ #pragma once +#include "AudioKit.h" #include "interface/drivers/Motor.h" #include "interface/libs/LedKit.h" #include "interface/libs/VideoKit.h" @@ -14,15 +15,12 @@ class BehaviorKit { public: explicit BehaviorKit(interface::VideoKit &videokit, interface::LedKit &ledkit, interface::Motor &motor_left, - interface::Motor &motor_right) - : _videokit(videokit), _ledkit(ledkit), _motor_left(motor_left), _motor_right(motor_right) + interface::Motor &motor_right, AudioKit &audiokit) + : _videokit(videokit), _ledkit(ledkit), _motor_left(motor_left), _motor_right(motor_right), _audiokit(audiokit) { // nothing do to } - void spinLeft(float speed); - void spinRight(float speed); - void launching(); void sleeping(); void waiting(); @@ -30,6 +28,7 @@ class BehaviorKit void blinkOnCharge(); void lowBattery(); + void mediumLowBattery(); void chargingEmpty(); void chargingLow(); @@ -41,6 +40,8 @@ class BehaviorKit void bleConnectionWithVideo(); void working(); + void magicCardDetected(); + void fileExchange(); void stop(); @@ -50,6 +51,7 @@ class BehaviorKit interface::LedKit &_ledkit; interface::Motor &_motor_left; interface::Motor &_motor_right; + AudioKit &_audiokit; }; } // namespace leka diff --git a/libs/BehaviorKit/source/BehaviorKit.cpp b/libs/BehaviorKit/source/BehaviorKit.cpp index 776fa5b97e..994f68f409 100644 --- a/libs/BehaviorKit/source/BehaviorKit.cpp +++ b/libs/BehaviorKit/source/BehaviorKit.cpp @@ -13,20 +13,9 @@ namespace leka { using namespace std::chrono; -void BehaviorKit::spinLeft(float speed) -{ - _motor_left.spin(Rotation::clockwise, speed); - _motor_right.spin(Rotation::clockwise, speed); -} - -void BehaviorKit::spinRight(float speed) -{ - _motor_left.spin(Rotation::counterClockwise, speed); - _motor_right.spin(Rotation::counterClockwise, speed); -} - void BehaviorKit::launching() { + _audiokit.play("37-WELCOME"); _videokit.displayImage(fs::home::img::system::robot_misc_splash_screen_large_400); } @@ -49,12 +38,19 @@ void BehaviorKit::blinkOnCharge() void BehaviorKit::lowBattery() { + _audiokit.play("27-BATTERIE MINIME"); _ledkit.stop(); _videokit.displayImage(fs::home::img::system::robot_battery_empty_must_be_charged); _motor_left.stop(); _motor_right.stop(); } +void BehaviorKit::mediumLowBattery() +{ + _audiokit.play("3-BATTERIE FAIBLE"); + _videokit.playVideoOnce(fs::home::vid::actions::robot_animation_action_yawning_no_eyebrows); +} + void BehaviorKit::chargingEmpty() { _videokit.displayImage(fs::home::img::system::robot_battery_charging_empty_red); @@ -77,16 +73,19 @@ void BehaviorKit::chargingHigh() void BehaviorKit::chargingFull() { + _audiokit.play("8-FIN DE CHARGE"); _videokit.displayImage(fs::home::img::system::robot_battery_charging_quarter_4_green); } void BehaviorKit::bleConnectionWithoutVideo() { + _audiokit.play("25-CONNEXION BT"); _ledkit.start(&led::animation::ble_connection); } void BehaviorKit::bleConnectionWithVideo() { + _audiokit.play("25-CONNEXION BT"); _ledkit.start(&led::animation::ble_connection); _videokit.playVideoOnce(fs::home::vid::system::robot_system_ble_connection_wink_no_eyebrows); } @@ -96,6 +95,11 @@ void BehaviorKit::working() _videokit.displayImage(fs::home::img::system::robot_face_smiling_slightly); } +void BehaviorKit::magicCardDetected() +{ + _audiokit.play("21-TAG DETEC"); +} + void BehaviorKit::fileExchange() { // TODO(@ladislas): add file exchange image @@ -108,6 +112,7 @@ void BehaviorKit::stop() _videokit.stopVideo(); _motor_left.stop(); _motor_right.stop(); + _audiokit.stop(); } } // namespace leka diff --git a/libs/BehaviorKit/tests/BehaviorKit_test.cpp b/libs/BehaviorKit/tests/BehaviorKit_test.cpp index 76ac9973d2..892c3cb39c 100644 --- a/libs/BehaviorKit/tests/BehaviorKit_test.cpp +++ b/libs/BehaviorKit/tests/BehaviorKit_test.cpp @@ -44,26 +44,6 @@ TEST_F(BehaviorKitTest, initialization) ASSERT_NE(&behaviorkit, nullptr); } -TEST_F(BehaviorKitTest, spinLeftAnySpeed) -{ - auto expected_speed = 0.7; - - EXPECT_CALL(mock_motor_left, spin(Rotation::clockwise, expected_speed)); - EXPECT_CALL(mock_motor_right, spin(Rotation::clockwise, expected_speed)); - - behaviorkit.spinLeft(expected_speed); -} - -TEST_F(BehaviorKitTest, spinRightAnySpeed) -{ - auto expected_speed = 0.3; - - EXPECT_CALL(mock_motor_left, spin(Rotation::counterClockwise, expected_speed)); - EXPECT_CALL(mock_motor_right, spin(Rotation::counterClockwise, expected_speed)); - - behaviorkit.spinRight(expected_speed); -} - TEST_F(BehaviorKitTest, launching) { EXPECT_CALL(mock_videokit, displayImage); diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index f498e76cf5..ce0d24ddce 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 add_subdirectory(${LIBS_DIR}/ActivityKit) +add_subdirectory(${LIBS_DIR}/AudioKit) add_subdirectory(${LIBS_DIR}/BatteryKit) add_subdirectory(${LIBS_DIR}/BehaviorKit) add_subdirectory(${LIBS_DIR}/BLEKit) diff --git a/libs/CommandKit/CMakeLists.txt b/libs/CommandKit/CMakeLists.txt index e45b24d8e4..2af43d7fb0 100644 --- a/libs/CommandKit/CMakeLists.txt +++ b/libs/CommandKit/CMakeLists.txt @@ -19,6 +19,7 @@ target_link_libraries(CommandKit CoreLED CoreMotor ReinforcerKit + BehaviorKit EventLoopKit mbed-os ) diff --git a/libs/CommandKit/include/commands/BehaviorCommand.h b/libs/CommandKit/include/commands/BehaviorCommand.h new file mode 100644 index 0000000000..da34d43f1f --- /dev/null +++ b/libs/CommandKit/include/commands/BehaviorCommand.h @@ -0,0 +1,128 @@ +// Leka - LekaOS +// Copyright 2022 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "BehaviorKit.h" +#include "Utils.h" +#include "interface/Command.h" + +namespace leka { + +struct BehaviorCommand : interface::Command { + explicit BehaviorCommand(BehaviorKit &kit) : _behaviorkit(kit) {} + + auto id() -> uint8_t override { return cmd::id; } + + auto data() -> uint8_t * override + { + args = {}; + return args.data(); + } + + [[nodiscard]] auto size() const -> std::size_t override { return std::size(args); } + + auto execute() -> bool override + { + auto [id, chcksm] = std::tuple_cat(args); + + auto expected = [&] { return utils::math::checksum8(std::span {args.data(), args.size() - 1}); }; + + if (chcksm != expected()) { + return false; + } + + switch (id) { + case cmd::behavior::stop: + _behaviorkit.stop(); + break; + case cmd::behavior::launching: + _behaviorkit.launching(); + break; + case cmd::behavior::sleeping: + _behaviorkit.sleeping(); + break; + case cmd::behavior::waiting: + _behaviorkit.waiting(); + break; + case cmd::behavior::blink_on_charge: + _behaviorkit.blinkOnCharge(); + break; + case cmd::behavior::low_battery: + _behaviorkit.lowBattery(); + break; + case cmd::behavior::charging_empty: + _behaviorkit.chargingEmpty(); + break; + case cmd::behavior::charging_low: + _behaviorkit.chargingLow(); + break; + case cmd::behavior::charging_medium: + _behaviorkit.chargingMedium(); + break; + case cmd::behavior::charging_high: + _behaviorkit.chargingHigh(); + break; + case cmd::behavior::charging_full: + _behaviorkit.chargingFull(); + break; + case cmd::behavior::ble_connection_without_video: + _behaviorkit.bleConnectionWithoutVideo(); + break; + case cmd::behavior::ble_connection_with_video: + _behaviorkit.bleConnectionWithVideo(); + break; + case cmd::behavior::working: + _behaviorkit.working(); + break; + case cmd::behavior::file_exchange: + _behaviorkit.fileExchange(); + break; + case cmd::behavior::magic_card_detected: + _behaviorkit.magicCardDetected(); + break; + case cmd::behavior::medium_low_battery: + _behaviorkit.mediumLowBattery(); + break; + default: + _behaviorkit.stop(); + break; + } + + return true; + } + + private: + struct cmd { + static constexpr auto id = uint8_t {0x60}; + static constexpr auto size = uint8_t {1 + 1}; // id + page + Checksum + + struct behavior { + static constexpr auto stop = uint8_t {0x00}; + static constexpr auto launching = uint8_t {0x01}; + static constexpr auto sleeping = uint8_t {0x02}; + static constexpr auto waiting = uint8_t {0x03}; + static constexpr auto blink_on_charge = uint8_t {0x04}; + static constexpr auto low_battery = uint8_t {0x05}; + static constexpr auto charging_empty = uint8_t {0x06}; + static constexpr auto charging_low = uint8_t {0x07}; + static constexpr auto charging_medium = uint8_t {0x08}; + static constexpr auto charging_high = uint8_t {0x09}; + static constexpr auto charging_full = uint8_t {0x0A}; + static constexpr auto ble_connection_without_video = uint8_t {0x0B}; + static constexpr auto ble_connection_with_video = uint8_t {0x0C}; + static constexpr auto working = uint8_t {0x0D}; + static constexpr auto file_exchange = uint8_t {0x0E}; + static constexpr auto magic_card_detected = uint8_t {0x0F}; + static constexpr auto medium_low_battery = uint8_t {0x10}; + }; + }; + + std::array args {}; + BehaviorKit &_behaviorkit; +}; + +} // namespace leka diff --git a/libs/FileManagerKit/include/FileManagerKit.h b/libs/FileManagerKit/include/FileManagerKit.h index b519adeddc..75fe88c64c 100644 --- a/libs/FileManagerKit/include/FileManagerKit.h +++ b/libs/FileManagerKit/include/FileManagerKit.h @@ -25,6 +25,8 @@ struct File : public interface::File, public mbed::NonCopyable { auto read(std::span buffer) -> std::size_t final; auto write(std::span data) -> std::size_t final; + auto read(std::span buffer) -> std::size_t final; + auto read(std::span buffer) -> std::size_t final; auto write(std::span data) -> std::size_t final; diff --git a/libs/FileManagerKit/source/File.cpp b/libs/FileManagerKit/source/File.cpp index ba30698850..35bcfdba53 100644 --- a/libs/FileManagerKit/source/File.cpp +++ b/libs/FileManagerKit/source/File.cpp @@ -60,6 +60,11 @@ auto File::read(std::span buffer) -> std::size_t return std::fread(buffer.data(), sizeof(uint8_t), buffer.size(), _file.get()); } +auto File::read(std::span buffer) -> std::size_t +{ + return std::fread(buffer.data(), sizeof(int16_t), buffer.size(), _file.get()); +} + auto File::write(std::span data) -> std::size_t { return std::fwrite(data.data(), sizeof(uint8_t), data.size(), _file.get()); diff --git a/libs/ReinforcerKit/CMakeLists.txt b/libs/ReinforcerKit/CMakeLists.txt index 1adefaa2d5..45a9f3cce0 100644 --- a/libs/ReinforcerKit/CMakeLists.txt +++ b/libs/ReinforcerKit/CMakeLists.txt @@ -17,6 +17,8 @@ target_sources(ReinforcerKit target_link_libraries(ReinforcerKit LedKit MotionKit + CoreDAC + AudioKit ) if(${CMAKE_PROJECT_NAME} STREQUAL "LekaOSUnitTests") diff --git a/libs/ReinforcerKit/include/ReinforcerKit.h b/libs/ReinforcerKit/include/ReinforcerKit.h index cb77530133..d2d9f63423 100644 --- a/libs/ReinforcerKit/include/ReinforcerKit.h +++ b/libs/ReinforcerKit/include/ReinforcerKit.h @@ -8,6 +8,7 @@ #include #include +#include "AudioKit.h" #include "MotionKit.hpp" namespace leka { @@ -15,8 +16,9 @@ namespace leka { class ReinforcerKit { public: - explicit ReinforcerKit(interface::VideoKit &videokit, interface::LedKit &ledkit, MotionKit &motion_kit) - : _videokit(videokit), _ledkit(ledkit), _motionkit(motion_kit) + explicit ReinforcerKit(interface::VideoKit &videokit, interface::LedKit &ledkit, AudioKit &audiokit, + MotionKit &motion_kit) + : _videokit(videokit), _ledkit(ledkit), _audiokit(audiokit), _motionkit(motion_kit) { // nothing do to } @@ -38,6 +40,7 @@ class ReinforcerKit private: interface::VideoKit &_videokit; interface::LedKit &_ledkit; + AudioKit &_audiokit; MotionKit &_motionkit; Reinforcer _default_reinforcer = Reinforcer::Rainbow; const float kThreeTurnDegrees = 1080.F; diff --git a/libs/ReinforcerKit/source/ReinforcerKit.cpp b/libs/ReinforcerKit/source/ReinforcerKit.cpp index 59dde69348..85ef8290d9 100644 --- a/libs/ReinforcerKit/source/ReinforcerKit.cpp +++ b/libs/ReinforcerKit/source/ReinforcerKit.cpp @@ -44,12 +44,14 @@ void ReinforcerKit::playDefault() void ReinforcerKit::stop() { _ledkit.stop(); + _audiokit.stop(); _motionkit.stop(); _videokit.stopVideo(); } void ReinforcerKit::playBlinkGreen() { + _audiokit.play("1-ACTIVITE REUSSIE"); _videokit.playVideoOnce("/fs/home/vid/system/robot-system-reinforcer-happy-no_eyebrows.avi"); _ledkit.start(&led::animation::blink_green); _motionkit.startYawRotation(kThreeTurnDegrees, Rotation::clockwise, [this] { _ledkit.stop(); }); @@ -57,6 +59,7 @@ void ReinforcerKit::playBlinkGreen() void ReinforcerKit::playSpinBlink() { + _audiokit.play("1-ACTIVITE REUSSIE"); _videokit.playVideoOnce("/fs/home/vid/system/robot-system-reinforcer-happy-no_eyebrows.avi"); _ledkit.start(&led::animation::spin_blink); _motionkit.startYawRotation(kThreeTurnDegrees, Rotation::counterClockwise, [this] { _ledkit.stop(); }); @@ -64,6 +67,7 @@ void ReinforcerKit::playSpinBlink() void ReinforcerKit::playFire() { + _audiokit.play("1-ACTIVITE REUSSIE"); _videokit.playVideoOnce("/fs/home/vid/system/robot-system-reinforcer-happy-no_eyebrows.avi", [this] { _ledkit.stop(); }); _ledkit.start(&led::animation::fire); @@ -71,6 +75,7 @@ void ReinforcerKit::playFire() void ReinforcerKit::playSprinkles() { + _audiokit.play("1-ACTIVITE REUSSIE"); _videokit.playVideoOnce("/fs/home/vid/system/robot-system-reinforcer-happy-no_eyebrows.avi", [this] { _ledkit.stop(); }); _ledkit.start(&led::animation::sprinkles); @@ -78,6 +83,7 @@ void ReinforcerKit::playSprinkles() void ReinforcerKit::playRainbow() { + _audiokit.play("1-ACTIVITE REUSSIE"); _videokit.playVideoOnce("/fs/home/vid/system/robot-system-reinforcer-happy-no_eyebrows.avi", [this] { _ledkit.stop(); }); _ledkit.start(&led::animation::rainbow); diff --git a/libs/RobotKit/include/RobotController.h b/libs/RobotKit/include/RobotController.h index 86f5c04d51..2f9fd57e3b 100644 --- a/libs/RobotKit/include/RobotController.h +++ b/libs/RobotKit/include/RobotController.h @@ -447,6 +447,7 @@ class RobotController : public interface::RobotController // ! be changed. It is a temporary fix for #1311 // TODO(@leka/dev-embedded): remove when fixed _service_magic_card.setMagicCard(card); + _behaviorkit.magicCardDetected(); onMagicCardAvailable(card); }); diff --git a/spikes/CMakeLists.txt b/spikes/CMakeLists.txt index cc3bd94479..23f862caa5 100644 --- a/spikes/CMakeLists.txt +++ b/spikes/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(${SPIKES_DIR}/lk_command_kit) add_subdirectory(${SPIKES_DIR}/lk_config_kit) add_subdirectory(${SPIKES_DIR}/lk_coreled) add_subdirectory(${SPIKES_DIR}/lk_core_touch_sensor) +add_subdirectory(${SPIKES_DIR}/lk_dac) add_subdirectory(${SPIKES_DIR}/lk_event_queue) add_subdirectory(${SPIKES_DIR}/lk_file_reception) add_subdirectory(${SPIKES_DIR}/lk_file_manager_kit) @@ -57,6 +58,7 @@ add_dependencies(spikes_leka spike_lk_command_kit spike_lk_coreled spike_lk_core_touch_sensor + spike_lk_dac spike_lk_event_queue spike_lk_file_reception spike_lk_file_manager_kit diff --git a/spikes/lk_activity_kit/CMakeLists.txt b/spikes/lk_activity_kit/CMakeLists.txt index d551bd3a0f..95bf3b7589 100644 --- a/spikes/lk_activity_kit/CMakeLists.txt +++ b/spikes/lk_activity_kit/CMakeLists.txt @@ -32,6 +32,8 @@ target_link_libraries(spike_lk_activity_kit IMUKit MotionKit EventLoopKit + CoreDAC + AudioKit ) target_link_custom_leka_targets(spike_lk_activity_kit) diff --git a/spikes/lk_activity_kit/main.cpp b/spikes/lk_activity_kit/main.cpp index 453d817223..8e46602427 100644 --- a/spikes/lk_activity_kit/main.cpp +++ b/spikes/lk_activity_kit/main.cpp @@ -10,9 +10,11 @@ #include "rtos/Thread.h" #include "ActivityKit.h" +#include "AudioKit.h" #include "BehaviorKit.h" #include "ChooseReinforcer.h" #include "CoreBufferedSerial.h" +#include "CoreDAC.h" #include "CoreDMA2D.hpp" #include "CoreDSI.hpp" #include "CoreFont.hpp" @@ -33,6 +35,7 @@ #include "CoreSDRAM.hpp" #include "CoreSPI.h" #include "CoreSTM32Hal.h" +#include "CoreSTM32HalBasicTimer.h" #include "CoreTimeout.h" #include "CoreVideo.hpp" #include "DisplayTags.h" @@ -160,6 +163,21 @@ namespace motors { } // namespace motors +auto hal = CoreSTM32Hal {}; + +namespace audio { + + namespace internal { + + extern "C" auto hal_timer = CoreSTM32HalBasicTimer {hal}; + extern "C" auto coredac = CoreDAC {hal, hal_timer}; + + } // namespace internal + + auto kit = AudioKit {internal::hal_timer, internal::coredac}; + +} // namespace audio + namespace display::internal { auto event_loop = EventLoopKit {}; @@ -167,7 +185,6 @@ namespace display::internal { auto corell = CoreLL {}; auto pixel = CGPixel {corell}; - auto hal = CoreSTM32Hal {}; auto coresdram = CoreSDRAM {hal}; auto coredma2d = CoreDMA2D {hal}; auto coredsi = CoreDSI {hal}; @@ -209,8 +226,8 @@ namespace motion::internal { auto motionkit = MotionKit {motors::left::motor, motors::right::motor, imukit, motion::internal::timeout}; -auto behaviorkit = BehaviorKit {videokit, ledkit, motors::left::motor, motors::right::motor}; -auto reinforcerkit = ReinforcerKit {videokit, ledkit, motionkit}; +auto behaviorkit = BehaviorKit {videokit, ledkit, motors::left::motor, motors::right::motor, audio::kit}; +auto reinforcerkit = ReinforcerKit {videokit, ledkit, audio::kit, motionkit}; namespace rfid { @@ -281,6 +298,8 @@ auto main() -> int ledkit.init(); + audio::kit.initialize(); + videokit.initializeScreen(); videokit.displayImage("/fs/home/img/system/robot-misc-splash_screen-large-400.jpg"); diff --git a/spikes/lk_audio/CMakeLists.txt b/spikes/lk_audio/CMakeLists.txt index afa028ed84..79b81d2cac 100644 --- a/spikes/lk_audio/CMakeLists.txt +++ b/spikes/lk_audio/CMakeLists.txt @@ -15,7 +15,10 @@ target_sources(spike_lk_audio ) target_link_libraries(spike_lk_audio - FileManagerKit + CoreSTM32Hal + CoreDAC + AudioKit + BLEKit ) target_link_custom_leka_targets(spike_lk_audio) diff --git a/spikes/lk_audio/main.cpp b/spikes/lk_audio/main.cpp index 3483f70263..991017ef8b 100644 --- a/spikes/lk_audio/main.cpp +++ b/spikes/lk_audio/main.cpp @@ -1,34 +1,45 @@ // Leka - LekaOS -// Copyright 2022 APF France handicap +// Copyright 2024 APF France handicap // SPDX-License-Identifier: Apache-2.0 -#include -#include - -#include "drivers/AnalogOut.h" -#include "drivers/DigitalOut.h" -#include "platform/mbed_wait_api.h" #include "rtos/ThisThread.h" -#include "rtos/Thread.h" +#include "BLEKit.h" +#include "BLEServiceConfig.h" + +#include "AudioKit.h" +#include "CoreDAC.h" +#include "CoreSTM32Hal.h" +#include "CoreSTM32HalBasicTimer.h" #include "FATFileSystem.h" -#include "FileManagerKit.h" #include "LogKit.h" #include "SDBlockDevice.h" using namespace leka; using namespace std::chrono_literals; -auto sd_bd = SDBlockDevice {SD_SPI_MOSI, SD_SPI_MISO, SD_SPI_SCK}; -auto fatfs = FATFileSystem {"fs"}; +auto sd_bd = SDBlockDevice {SD_SPI_MOSI, SD_SPI_MISO, SD_SPI_SCK}; +auto fatfs = FATFileSystem {"fs"}; +auto filename = std::filesystem::path {}; + +auto hal = CoreSTM32Hal {}; + +namespace audio { + +namespace internal { + + extern "C" auto hal_timer = CoreSTM32HalBasicTimer {hal}; + extern "C" auto coredac = CoreDAC {hal, hal_timer}; -const auto sound_file_path = std::filesystem::path {"/fs/home/wav/fur-elise.wav"}; -auto file = FileManagerKit::File {sound_file_path}; +} // namespace internal -auto thread_audio = rtos::Thread {}; +auto kit = AudioKit {internal::hal_timer, internal::coredac}; -auto audio_enable = mbed::DigitalOut {SOUND_ENABLE, 1}; -auto audio_output = mbed::AnalogOut {MCU_SOUND_OUT}; +} // namespace audio + +auto service_config = BLEServiceConfig {}; +auto services = std::to_array({&service_config}); +auto blekit = BLEKit {}; void initializeSD() { @@ -40,29 +51,10 @@ void initializeSD() fatfs.mount(&sd_bd); } -void playSound() +void play(std::string filename) { - static const auto _n_bytes_to_read = int {512}; // arbitrary - auto _buffer = std::array {0}; - - auto _ns_sample_rate = uint32_t {22676}; // 1,000,000,000 / 44,100 (in ns) - auto _ns_sample_rate_adapted = _ns_sample_rate * 1.7; // arbitrary, 1s in MCU is not exactly 1s in real life - auto bytesread = uint32_t {_n_bytes_to_read}; - - /* START READ WAV */ - while (bytesread == _n_bytes_to_read) { - // Read "_n_bytes_to_read" from file at each iteration. Real bytes read is given by "bytesread" - if (bytesread = file.read(_buffer.data(), _n_bytes_to_read); bytesread != 0) { - // Play every 2-bytes (sound encoded in 16 bits) - for (uint32_t j = 0; j < bytesread; j += 4) { // Play one channel, data for stereo are alternate - audio_output.write_u16((_buffer.at(j + 1) + 0x8000) >> - 1); // offset for int16 data (0x8000) and volume 50% (>>1) - - wait_ns(_ns_sample_rate_adapted); // adjust play speed - } - } - } - /* END READ WAV*/ + log_info("Play file: %s", filename.c_str()); + audio::kit.play(filename); } auto main() -> int @@ -70,18 +62,22 @@ auto main() -> int logger::init(); log_info("Hello, World!\n\n"); + rtos::ThisThread::sleep_for(1s); initializeSD(); - if (FileManagerKit::file_is_missing(sound_file_path)) { - return 1; - } + audio::kit.initialize(); - while (true) { - file.open(sound_file_path); - playSound(); - file.close(); + blekit.setServices(services); + blekit.init(); + + service_config.onRobotNameUpdated([](const std::array &robot_name) { + const auto *end_index = std::find(robot_name.begin(), robot_name.end(), '\0'); + filename = std::string {robot_name.begin(), end_index}; + play(filename); + }); - rtos::ThisThread::sleep_for(1s); + while (true) { + rtos::ThisThread::sleep_for(1min); } } diff --git a/spikes/lk_behavior_kit/CMakeLists.txt b/spikes/lk_behavior_kit/CMakeLists.txt index 346086848f..2e6ec13dd0 100644 --- a/spikes/lk_behavior_kit/CMakeLists.txt +++ b/spikes/lk_behavior_kit/CMakeLists.txt @@ -18,6 +18,8 @@ target_link_libraries(spike_lk_behavior_kit BehaviorKit CorePwm EventLoopKit + CoreDAC + AudioKit ) target_link_custom_leka_targets(spike_lk_behavior_kit) diff --git a/spikes/lk_behavior_kit/main.cpp b/spikes/lk_behavior_kit/main.cpp index 5eb6f7c0c8..4a59856d2d 100644 --- a/spikes/lk_behavior_kit/main.cpp +++ b/spikes/lk_behavior_kit/main.cpp @@ -7,7 +7,9 @@ #include "drivers/HighResClock.h" #include "rtos/ThisThread.h" +#include "AudioKit.h" #include "BehaviorKit.h" +#include "CoreDAC.h" #include "CoreDMA2D.hpp" #include "CoreDSI.hpp" #include "CoreFont.hpp" @@ -25,6 +27,7 @@ #include "CoreSDRAM.hpp" #include "CoreSPI.h" #include "CoreSTM32Hal.h" +#include "CoreSTM32HalBasicTimer.h" #include "CoreVideo.hpp" #include "EventLoopKit.h" #include "FATFileSystem.h" @@ -142,6 +145,21 @@ namespace motors { } // namespace motors +auto hal = CoreSTM32Hal {}; + +namespace audio { + + namespace internal { + + extern "C" auto hal_timer = CoreSTM32HalBasicTimer {hal}; + extern "C" auto coredac = CoreDAC {hal, hal_timer}; + + } // namespace internal + + auto kit = AudioKit {internal::hal_timer, internal::coredac}; + +} // namespace audio + namespace display { namespace internal { @@ -151,7 +169,6 @@ namespace display { auto corell = CoreLL {}; auto pixel = CGPixel {corell}; - auto hal = CoreSTM32Hal {}; auto coresdram = CoreSDRAM {hal}; auto coredma2d = CoreDMA2D {hal}; auto coredsi = CoreDSI {hal}; @@ -172,7 +189,7 @@ namespace display { } // namespace display -auto behaviorkit = BehaviorKit {display::videokit, leds::kit, motors::left::motor, motors::right::motor}; +auto behaviorkit = BehaviorKit {display::videokit, leds::kit, motors::left::motor, motors::right::motor, audio::kit}; auto hello = HelloWorld {}; } // namespace @@ -186,6 +203,7 @@ auto main() -> int leds::kit.init(); sd::init(); + audio::kit.initialize(); display::internal::corelcd.turnOn(); display::videokit.initializeScreen(); diff --git a/spikes/lk_command_kit/CMakeLists.txt b/spikes/lk_command_kit/CMakeLists.txt index 1e9bf3303a..a389adc39a 100644 --- a/spikes/lk_command_kit/CMakeLists.txt +++ b/spikes/lk_command_kit/CMakeLists.txt @@ -19,7 +19,6 @@ target_link_libraries(spike_lk_command_kit CoreMotor CorePwm CoreSPI - BehaviorKit CommandKit LedKit VideoKit @@ -29,6 +28,8 @@ target_link_libraries(spike_lk_command_kit MotionKit CoreTimeout EventLoopKit + CoreDAC + AudioKit ) target_link_custom_leka_targets(spike_lk_command_kit) diff --git a/spikes/lk_command_kit/main.cpp b/spikes/lk_command_kit/main.cpp index 030837fa1f..0873c116dc 100644 --- a/spikes/lk_command_kit/main.cpp +++ b/spikes/lk_command_kit/main.cpp @@ -7,7 +7,9 @@ #include "rtos/ThisThread.h" +#include "AudioKit.h" #include "CommandKit.h" +#include "CoreDAC.h" #include "CoreDMA2D.hpp" #include "CoreDSI.hpp" #include "CoreFont.hpp" @@ -27,6 +29,7 @@ #include "CoreSDRAM.hpp" #include "CoreSPI.h" #include "CoreSTM32Hal.h" +#include "CoreSTM32HalBasicTimer.h" #include "CoreTimeout.h" #include "CoreVideo.hpp" #include "EventLoopKit.h" @@ -38,6 +41,7 @@ #include "ReinforcerKit.h" #include "SDBlockDevice.h" #include "VideoKit.h" +#include "commands/BehaviorCommand.h" #include "commands/LedFullCommand.h" #include "commands/LedRangeCommand.h" #include "commands/LedSingleCommand.h" @@ -109,6 +113,21 @@ CoreLSM6DSOX lsm6dsox(internal::i2c, internal::drdy_irq); auto imukit = IMUKit {imu::lsm6dsox}; +auto hal = CoreSTM32Hal {}; + +namespace audio { + +namespace internal { + + extern "C" auto hal_timer = CoreSTM32HalBasicTimer {hal}; + extern "C" auto coredac = CoreDAC {hal, hal_timer}; + +} // namespace internal + +auto kit = AudioKit {internal::hal_timer, internal::coredac}; + +} // namespace audio + namespace motion::internal { CoreTimeout timeout {}; @@ -126,7 +145,6 @@ namespace internal { auto corell = CoreLL {}; auto pixel = CGPixel {corell}; - auto hal = CoreSTM32Hal {}; auto coresdram = CoreSDRAM {hal}; auto coredma2d = CoreDMA2D {hal}; auto coredsi = CoreDSI {hal}; @@ -147,7 +165,8 @@ auto videokit = VideoKit {internal::event_loop, internal::corevideo}; } // namespace display -auto reinforcerkit = ReinforcerKit {display::videokit, ledkit, motionkit}; +auto behaviorkit = BehaviorKit {display::videokit, ledkit, motor::left, motor::right, audio::kit}; +auto reinforcerkit = ReinforcerKit {display::videokit, ledkit, audio::kit, motionkit}; namespace command { @@ -161,6 +180,7 @@ namespace internal { auto led_range = LedRangeCommand {leds::ears, leds::belt}; auto motors = MotorsCommand {motor::left, motor::right}; auto reinforcer = ReinforcerCommand {reinforcerkit}; + auto behavior = BehaviorCommand {behaviorkit}; } // namespace internal @@ -211,6 +231,7 @@ auto list = std::to_array({ &internal::led_full, &internal::led_range, &internal::reinforcer, + &internal::behavior, }); } // namespace command @@ -248,6 +269,7 @@ auto main() -> int logger::init(); initializeSD(); + audio::kit.initialize(); display::videokit.initializeScreen(); display::internal::corelcd.turnOn(); cmdkit.registerCommand(command::list); diff --git a/spikes/lk_dac/CMakeLists.txt b/spikes/lk_dac/CMakeLists.txt new file mode 100644 index 0000000000..0de1b5fb64 --- /dev/null +++ b/spikes/lk_dac/CMakeLists.txt @@ -0,0 +1,22 @@ +# Leka - LekaOS +# Copyright 2024 APF France handicap +# SPDX-License-Identifier: Apache-2.0 + +add_mbed_executable(spike_lk_dac) + +target_include_directories(spike_lk_dac + PRIVATE + . +) + +target_sources(spike_lk_dac + PRIVATE + main.cpp +) + +target_link_libraries(spike_lk_dac + CoreSTM32Hal + CoreDAC +) + +target_link_custom_leka_targets(spike_lk_dac) diff --git a/spikes/lk_dac/main.cpp b/spikes/lk_dac/main.cpp new file mode 100644 index 0000000000..a5429e6097 --- /dev/null +++ b/spikes/lk_dac/main.cpp @@ -0,0 +1,77 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "rtos/ThisThread.h" + +#include "CoreDAC.h" +#include "CoreSTM32Hal.h" +#include "CoreSTM32HalBasicTimer.h" +#include "DigitalOut.h" +#include "LogKit.h" + +using namespace leka; +using namespace std::chrono_literals; + +auto hal = CoreSTM32Hal {}; + +namespace audio::internal { + +extern "C" auto hal_timer = CoreSTM32HalBasicTimer {hal}; +extern "C" auto coredac = CoreDAC {hal, hal_timer}; + +} // namespace audio::internal + +auto audio_enable = mbed::DigitalOut {SOUND_ENABLE, 1}; + +constexpr uint32_t sample_rate_hz = 44'100; +constexpr auto coefficient = 10; + +void fillBufferWithSinWave(uint16_t *buffer, uint32_t samples_per_period, uint16_t maxValue, uint16_t minValue) +{ + auto resolution = 2.0 * M_PI / samples_per_period; + + auto sin0_1 = [](double value) { return (sin(value) + 1.0) / 2.0; }; + auto normalization = [maxValue, minValue](double standard_value) { + return standard_value * (maxValue - minValue) + minValue; + }; + + for (uint32_t sample = 0; sample < samples_per_period; sample += coefficient) { + auto standard_value = sin0_1(sample * resolution); + auto normalized_value = normalization(standard_value); + std::fill_n(buffer + sample, coefficient, static_cast(normalized_value)); + } +} + +auto main() -> int +{ + logger::init(); + + log_info("Hello, World!\n\n"); + + audio::internal::hal_timer.initialize(sample_rate_hz * coefficient); + audio::internal::coredac.initialize(); + + const uint32_t frequency = 440; + const uint16_t maxVal = 0xFFF; + const uint16_t minVal = 0x000; + std::array buffer {}; + fillBufferWithSinWave(buffer.data(), buffer.size(), maxVal, minVal); + + audio::internal::coredac.registerDataToPlay(buffer); + + log_info("buffer size: %d", buffer.size()); + log_info("Start sound"); + audio::internal::coredac.start(); + + rtos::ThisThread::sleep_for(1s); + + log_info("Stop sound"); + audio::internal::coredac.stop(); + + while (true) { + rtos::ThisThread::sleep_for(1min); + } +} diff --git a/spikes/lk_reinforcer/CMakeLists.txt b/spikes/lk_reinforcer/CMakeLists.txt index c78ddce17a..2357b4b7fd 100644 --- a/spikes/lk_reinforcer/CMakeLists.txt +++ b/spikes/lk_reinforcer/CMakeLists.txt @@ -27,6 +27,8 @@ target_link_libraries(spike_lk_reinforcer MotionKit ReinforcerKit CoreTimeout + CoreDAC + AudioKit ) target_link_custom_leka_targets(spike_lk_reinforcer) diff --git a/spikes/lk_reinforcer/main.cpp b/spikes/lk_reinforcer/main.cpp index 478f9000fa..6c2a08097d 100644 --- a/spikes/lk_reinforcer/main.cpp +++ b/spikes/lk_reinforcer/main.cpp @@ -7,7 +7,9 @@ #include "drivers/HighResClock.h" #include "rtos/ThisThread.h" +#include "AudioKit.h" #include "CGGraphics.hpp" +#include "CoreDAC.h" #include "CoreDMA2D.hpp" #include "CoreDSI.hpp" #include "CoreFont.hpp" @@ -25,6 +27,7 @@ #include "CoreSDRAM.hpp" #include "CoreSPI.h" #include "CoreSTM32Hal.h" +#include "CoreSTM32HalBasicTimer.h" #include "CoreTimeout.h" #include "CoreVideo.hpp" #include "EventLoopKit.h" @@ -148,6 +151,21 @@ namespace imu { auto imukit = IMUKit {imu::lsm6dsox}; +auto hal = CoreSTM32Hal {}; + +namespace audio { + + namespace internal { + + extern "C" auto hal_timer = CoreSTM32HalBasicTimer {hal}; + extern "C" auto coredac = CoreDAC {hal, hal_timer}; + + } // namespace internal + + auto kit = AudioKit {internal::hal_timer, internal::coredac}; + +} // namespace audio + namespace motion::internal { CoreTimeout timeout {}; @@ -163,7 +181,6 @@ namespace display::internal { auto corell = CoreLL {}; auto pixel = CGPixel {corell}; - auto hal = CoreSTM32Hal {}; auto coresdram = CoreSDRAM {hal}; auto coredma2d = CoreDMA2D {hal}; auto coredsi = CoreDSI {hal}; @@ -181,7 +198,7 @@ namespace display::internal { } // namespace display::internal auto videokit = VideoKit {display::internal::event_loop, display::internal::corevideo}; -auto reinforcerkit = ReinforcerKit {videokit, ledkit, motionkit}; +auto reinforcerkit = ReinforcerKit {videokit, ledkit, audio::kit, motionkit}; auto hello = HelloWorld {}; @@ -201,6 +218,7 @@ auto main() -> int rtos::ThisThread::sleep_for(1s); ledkit.init(); + audio::kit.initialize(); videokit.initializeScreen(); imu::lsm6dsox.init(); imukit.init(); diff --git a/tests/unit/headers/mbed/mbed_config.h b/tests/unit/headers/mbed/mbed_config.h index e7fee6c006..5b057607c0 100644 --- a/tests/unit/headers/mbed/mbed_config.h +++ b/tests/unit/headers/mbed/mbed_config.h @@ -288,6 +288,8 @@ #define SMP_DB_MAX_DEVICES 3 // set by library:cordio #define TARGET_LSE_DRIVE_LOAD_LEVEL RCC_LSEDRIVE_LOW // set by target:MCU_STM32F7 #define USE_HAL_JPEG_REGISTER_CALLBACKS 1U // set by application +#define USE_HAL_TIM_REGISTER_CALLBACKS 1U // set by application +#define USE_HAL_DAC_REGISTER_CALLBACKS 1U // set by application // Macros #define WSF_MS_PER_TICK 1 // defined by library:cordio #define _RTE_ // defined by library:rtos diff --git a/tests/unit/mocks/mocks/leka/CoreSTM32Hal.h b/tests/unit/mocks/mocks/leka/CoreSTM32Hal.h index 2d950de3e7..5751098106 100644 --- a/tests/unit/mocks/mocks/leka/CoreSTM32Hal.h +++ b/tests/unit/mocks/mocks/leka/CoreSTM32Hal.h @@ -19,9 +19,15 @@ class CoreSTM32Hal : public interface::STM32Hal MOCK_METHOD(void, HAL_RCC_GPIOH_CLK_ENABLE, (), (override)); MOCK_METHOD(void, HAL_RCC_GPIOI_CLK_ENABLE, (), (override)); MOCK_METHOD(void, HAL_RCC_GPIOJ_CLK_ENABLE, (), (override)); - + MOCK_METHOD(void, HAL_RCC_TIM6_CLK_ENABLE, (), (override)); + MOCK_METHOD(void, HAL_RCC_TIM6_CLK_DISABLE, (), (override)); + MOCK_METHOD(void, HAL_RCC_TIM7_CLK_ENABLE, (), (override)); + MOCK_METHOD(void, HAL_RCC_TIM7_CLK_DISABLE, (), (override)); MOCK_METHOD(void, HAL_RCC_FMC_CLK_ENABLE, (), (override)); + MOCK_METHOD(void, HAL_RCC_DMA1_CLK_ENABLE, (), (override)); MOCK_METHOD(void, HAL_RCC_DMA2_CLK_ENABLE, (), (override)); + MOCK_METHOD(void, HAL_RCC_DAC_CLK_ENABLE, (), (override)); + MOCK_METHOD(void, HAL_RCC_DAC_CLK_DISABLE, (), (override)); MOCK_METHOD(void, HAL_RCC_JPEG_CLK_ENABLE, (), (override)); MOCK_METHOD(void, HAL_RCC_JPEG_FORCE_RESET, (), (override)); MOCK_METHOD(void, HAL_RCC_JPEG_RELEASE_RESET, (), (override)); @@ -113,6 +119,27 @@ class CoreSTM32Hal : public interface::STM32Hal MOCK_METHOD(HAL_StatusTypeDef, HAL_JPEG_Pause, (JPEG_HandleTypeDef *, uint32_t), (override)); MOCK_METHOD(HAL_StatusTypeDef, HAL_JPEG_Resume, (JPEG_HandleTypeDef *, uint32_t), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_TIM_Base_Init, (TIM_HandleTypeDef * htim), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_TIMEx_MasterConfigSynchronization, + (TIM_HandleTypeDef * htim, TIM_MasterConfigTypeDef *sMasterConfig), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_TIM_RegisterCallback, + (TIM_HandleTypeDef * htim, HAL_TIM_CallbackIDTypeDef CallbackID, pTIM_CallbackTypeDef pCallback), + (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_TIM_Base_Start_IT, (TIM_HandleTypeDef * htim), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_TIM_Base_Stop_IT, (TIM_HandleTypeDef * htim), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_TIM_Base_DeInit, (TIM_HandleTypeDef * htim), (override)); + + MOCK_METHOD(HAL_StatusTypeDef, HAL_DAC_Init, (DAC_HandleTypeDef * hdac), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_DAC_ConfigChannel, + (DAC_HandleTypeDef * hdac, DAC_ChannelConfTypeDef *sConfig, uint32_t Channel), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_DAC_RegisterCallback, + (DAC_HandleTypeDef * hdac, HAL_DAC_CallbackIDTypeDef CallbackID, pDAC_CallbackTypeDef pCallback), + (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_DAC_Start_DMA, + (DAC_HandleTypeDef * hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, uint32_t Alignment), + (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_DAC_Stop_DMA, (DAC_HandleTypeDef * hdac, uint32_t Channel), (override)); + MOCK_METHOD(HAL_StatusTypeDef, HAL_DAC_DeInit, (DAC_HandleTypeDef * hdac), (override)); }; } // namespace leka::mock diff --git a/tests/unit/mocks/mocks/leka/File.h b/tests/unit/mocks/mocks/leka/File.h index eab081d9be..87c14adbcd 100644 --- a/tests/unit/mocks/mocks/leka/File.h +++ b/tests/unit/mocks/mocks/leka/File.h @@ -27,6 +27,8 @@ class File : public interface::File MOCK_METHOD(size_t, read, (std::span), (override)); MOCK_METHOD(size_t, write, (std::span), (override)); + MOCK_METHOD(size_t, read, (std::span), (override)); + MOCK_METHOD(size_t, read, (uint8_t *, uint32_t), (override)); MOCK_METHOD(size_t, write, (const uint8_t *, uint32_t), (override)); diff --git a/tests/unit/mocks/mocks/leka/STM32HalBasicTimer.h b/tests/unit/mocks/mocks/leka/STM32HalBasicTimer.h new file mode 100644 index 0000000000..fef8693733 --- /dev/null +++ b/tests/unit/mocks/mocks/leka/STM32HalBasicTimer.h @@ -0,0 +1,26 @@ +// Leka - LekaOS +// Copyright 2024 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "gmock/gmock.h" +#include "interface/drivers/STM32HalBasicTimer.h" + +namespace leka::mock { + +class STM32HalBasicTimer : public interface::STM32HalBasicTimer +{ + public: + MOCK_METHOD(TIM_HandleTypeDef &, getHandle, (), (override)); + + MOCK_METHOD(void, linkDACTimer, (DAC_ChannelConfTypeDef *), (override)); + + MOCK_METHOD(void, initialize, (float), (override)); + MOCK_METHOD(void, terminate, (), (override)); + + MOCK_METHOD(void, start, (), (override)); + MOCK_METHOD(void, stop, (), (override)); +}; + +} // namespace leka::mock