diff --git a/applications/gesture_recognition/.clang-format b/applications/gesture_recognition/.clang-format new file mode 100644 index 0000000..6c1aca7 --- /dev/null +++ b/applications/gesture_recognition/.clang-format @@ -0,0 +1,24 @@ +Standard: Cpp11 +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 0 +AccessModifierOffset: -4 +NamespaceIndentation: All +BreakBeforeBraces: Custom +PointerAlignment: Left +AllowShortCaseLabelsOnASingleLine: 'true' +IndentCaseLabels: 'true' +BraceWrapping: + AfterEnum: true + AfterStruct: true + AfterClass: true + SplitEmptyFunction: true + AfterControlStatement: true + AfterNamespace: false + AfterFunction: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + SplitEmptyRecord: true + SplitEmptyNamespace: true diff --git a/applications/gesture_recognition/CMakeLists.txt b/applications/gesture_recognition/CMakeLists.txt new file mode 100644 index 0000000..2b30cdc --- /dev/null +++ b/applications/gesture_recognition/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(nrf_edgeai_thingy53) + +set(CMAKE_BUILD_TYPE Debug) + +file(GLOB_RECURSE APP_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/src/**") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/bsp) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/nrf_edgeai_lib) + +target_sources(app PRIVATE ${APP_SOURCE_FILES}) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/applications/gesture_recognition/Kconfig b/applications/gesture_recognition/Kconfig new file mode 100644 index 0000000..a6ca4b6 --- /dev/null +++ b/applications/gesture_recognition/Kconfig @@ -0,0 +1,11 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "Kconfig.zephyr" + +config DATA_COLLECTION_MODE + bool "Enble Data Collection Mode (no inference run)" + default n diff --git a/applications/gesture_recognition/Kconfig.sysbuild b/applications/gesture_recognition/Kconfig.sysbuild new file mode 100644 index 0000000..58cf626 --- /dev/null +++ b/applications/gesture_recognition/Kconfig.sysbuild @@ -0,0 +1,13 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "share/sysbuild/Kconfig" + +config NRF_DEFAULT_IPC_RADIO + default y + +config NETCORE_IPC_RADIO_BT_HCI_IPC + default y diff --git a/applications/gesture_recognition/README.md b/applications/gesture_recognition/README.md new file mode 100644 index 0000000..dff2dde --- /dev/null +++ b/applications/gesture_recognition/README.md @@ -0,0 +1,199 @@ +# nRF Edge AI Nordic Thingy:53 Gesture Based BLE Remote Control Device + +- [Overview](#overview) +- [Hardware Used](#hw-used) +- [Setup Software Environment](#setup-sw-env) +- [Setup Firmware Project](#setup-fw-proj) +- [How The Project Works](#how-works) + +## Overview
+ +This project demonstrates a gesture based remote control device using [__Nordic Thingy:53__](https://www.nordicsemi.com/Products/Development-hardware/Nordic-Thingy-53). The development kit could be connected to the PC via Bluetooth as a HID device and using gestures the user can control media stream or slides of the presentation. Based on accelerometer and gyroscope data the nRF Edge AI model could recognize __8 classes__ of gestures: Swipe Right, Swipe Left, Double Shake, Double Tap, Rotation Clockwise and Counter clockwise, No Gestures(IDLE) and Unknown Gesture. Use-case demonstration [video](https://youtu.be/qDFdxapLbrA). Raw dataset used for model training, which you can use to train your own model, or augment it with your own data and train a more robust model is located [here](https://files.nordicsemi.com/artifactory/edge-ai/external/nordic53thingy_remote_ctrl_train_v101.csv). + +## Hardware Used
+ +[__Nordic Thingy:53 Multi-protocol IoT prototyping platform__](https://www.nordicsemi.com/Products/Development-hardware/Nordic-Thingy-53) + +The Nordic Thingy:53™ is an easy-to-use IoT prototyping platform. It makes it possible to create prototypes and proofs-of-concept without building custom hardware. The Thingy:53 is built around the nRF5340 SoC, the flagship dual-core wireless SoC. The processing power and memory size of its dual Arm Cortex-M33 processors enables it to run embedded machine learning (ML) models directly on the device. + +The Thingy:53 also includes many different integrated sensors, like environmental-, and color and light sensors, accelerometers, and a magnetometer, which all can be taken advantage of without additional hardware. It is powered by a rechargeable Li-Po battery that can be charged via USB-C. There is also an external 4-pin JST connector compatible with the Stemma/Qwiic/Grove standards for hardware accessories. + +![nordic-thingy-kit-img](resources/nordic_thingy.jpg) + +## Setup Software Environment
+ +To set this project up, you will need to install the following software: +- Visual Studio Code (https://code.visualstudio.com) +- nRF Connect for VS Code (https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-VS-Code) +- [**Optional**] TeraTerm Terminal (https://teratermproject.github.io/index-en.html) + +## Setup Firmware Project
+ +1. Clone this repository: https://github.com/Neuton-tinyML/neuton-nordic-thingy53-ble-remotecontrol +2. In the VS code, `Open folder` where you did clone the repository. +3. `Manage toolchain` install toolchain version **v3.1.0**. `Manage SDK` install version **v3.1.0** in nRF Connect tab. + +![sw-install-step3-img](resources/sw-install-step-3.jpg) + +4. `Add Build configuration` for Nordic Thingy 53 dev kit + +![sw-install-step4-img](resources/sw-install-step-4.jpg) + +5. Choose `thingy53_nrf5340_cpuapp` in the `Board` selector and click `Build Configuration` + +![sw-install-step5-img](resources/sw-install-step-5.jpg) + +6. After your build will be configured you should see the following options: + +![sw-install-step6-img](resources/sw-install-step-6.jpg) + +**IMPORTANT** If your `thingy53_nrf5340_cpuapp_defconfig` file does not has **`CONFIG_FPU=y`** you should add this because nRF Edge AI library is compiled with `-mfloat-abi=hard` flag + +![sw-install-step5-1-img](resources/sw-install-step-5-1-important.jpg) + +8. Now turn on your Thingy 53 dev kit and connect to your PC via debugger and USB + +![sw-install-step7-img](resources/connected-devkit-sw-install-step-7.jpg) + +9. Build & Flash the device with firmware + +![sw-install-step8-img](resources/sw-install-step-8.jpg) + +10. After successfull device programming you can open any serial port terminal and you should see the following messages: + +```` +*** Booting nRF Connect SDK 7a22da43c1d4 *** +Set up button at gpio@842800 pin 14 +nRF Edge AI Gestures Recognition Demo: + Application version: 3.0.0 + nRF Edge AI Runtime Version: 1.0.0 + nRF Edge AI Lab Solution id: 84622 +Bluetooth initialized +Advertising successfully started +```` + +MacOs CLI commands examples: +- Check connected usb devices: `ls -l /dev/cu.usb*` +- Output to serial port. Use your actual usb-device name: `stty -f /dev/cu.usbmodem101 115200 | cat /dev/cu.usbmodem101` +- Save serial port output to file, if necessary: `stty -f /dev/cu.usbmodem101 115200 | cat /dev/cu.usbmodem101 | tee filename.csv` + + +11. Explore the project and nRF Edge AI model capabilities! + +### Data collection firmware build + +It is possible to create a special build that will output the raw data of the acceleromater and gyro sensors in the serial port. +This way it's possible to capture data for training new models, to test and implement new use cases. +The output of the sensors will integers of 16 bits, separated by a comma, in the following order + +``` +,,,,, +``` +*(Please note column headers are not included)* + +The output rate will be the configured sampling frequency, default value being 100Hz. + +To build this version, the following option must be enabled in the `prj.conf` file + +``` +CONFIG_DATA_COLLECTION_MODE=y +``` + +The project must be build and flashed again as described in the step **(9)**. + +No inference will be performed in this mode, it's just intended to simplify the capture of new datasets + +# How the project works
+ +Once the device is up and running, Bluetooth advertising starts as a HID device and waits for connection request from the PC. + +You can connect devices in the same way as, for example, a regular Bluetooth keyboard. + +1. For Windows 10 PC you can go to `Settings -> Bluetooth & other devices -> Add Bluetooth or other device`. + +![bt_step_1](resources/ble_connect_1.png) + +2. The device should appear in `Add a device` window, choose the device for pairing. + +![bt_step_2](resources/device_ble_scanning.jpg) + +3. After Bluetooth pairing the device should appear in your `Mouse, keyboard, & pen` section + +![bt_step_2](resources/device_ble_connected.jpg) + +4. In the serial port terminal you should se the following logs messages: + +``` +Connected 9C:B6:D0:C0:CE:FC (public) +Security changed: 9C:B6:D0:C0:CE:FC (public) level 2 +Input CCCD enabled +Input attribute handle: 0 +Consumer CCCD enabled +``` + +After Bluetooth connection the device will change LED indication from RED LED glowing to GREEN or BLUE LEDs glowing depending on __Keyboard control mode__. + +The project has two Keyboard control modes: __Presentation Control__ and __Music Control__. You can switch between control modes by pushing user button `BTN0`, for different control modes there is a different LED indication. If the device in __Presentation Control__ mode the LED glows BLUE color, if the device in __Music Control__ mode the LED glows GREEN color: + +__LED indication in different device states:__ + +| No Bluetooth connection | Presentation Control mode | Music Control mode | +| ------------------------ |---------------------------- | ----------------------- | +| ![Alt Text](resources/device-led-no-ble-connect.gif) |![Alt Text](resources/device-led-ble-connect-presentation-mode.gif) |![Alt Text](resources/device-led-ble-connect-music-mode.gif) | + + +Depending on the control mode, recognized gestures will be mapped to different keyboard keys: + +__Gestures to Keyboard Keys Mapping__ + +| | Presentation Control | Music Control | +| ----------------------------- | ---------- | ----------------- | +| Double Shake | F5 | Media Play/Pause | +| Double Tap | ESCAPE | Media Mute | +| Swipe Right | Arrow Right| Media Next | +| Swipe Left | Arrow Left | Media Previous | +| Rotation Clockwise | Not used | Media Volume Up | +| Rotation Counter clockwise | Not used | Media Volume Down | + +__How to Make Gestures__ +> **_NOTE:_** The dataset for creating this model is immature and this affects the generalization of the model on different persons, so please follow the instructions for good gesture recognition. + +To begin with, please make sure that the default (initial) position of the device is the same as following: + +![gestures-img-1](resources/initial_orientation.gif) + +Next, follow the images on how to make gestures. For better recognition use your wrists more when making gestures, and not your whole hand: + +__Swipe Right & Left__ +| | | +| -------------------------------------- | --------------------------------------------- | +| ![Alt Text](resources/swipe_right.gif) | ![Alt Text](resources/swipe_left.gif) | +| Swipe Right | Swipe Left | + +__Rotation Clockwise & Counter Clockwise__ + +| | | +| -------------------------------------- | --------------------------------------------- | +| ![Alt Text](resources/rotation_right.gif) | ![Alt Text](resources/rotation_left.gif) | +| Rotation Clockwise(Right) | Rotation Counter Clockwise(Left) | + +__Double Shake & Double Tap__ + +| | | +| -------------------------------------- | --------------------------------------------- | +| ![Alt Text](resources/double_shake.gif) | ![Alt Text](resources/double_tap.gif) | +| Double Shake | Double Tap | + +When performing gestures with the device, in the serial port terminal, you should see the similar messages: + +``` +Predicted class: DOUBLE SHAKE, with probability 96 % +BLE HID Key 8 sent successfully +Predicted class: SWIPE RIGHT, with probability 99 % +BLE HID Key 32 sent successfully +Predicted class: SWIPE LEFT, with probability 99 % +BLE HID Key 16 sent successfully +Predicted class: ROTATION RIGHT, with probability 93 % +BLE HID Key 1 sent successfully +``` +Have fun and use this model for your future gesture control projects! diff --git a/applications/gesture_recognition/prj.conf b/applications/gesture_recognition/prj.conf new file mode 100644 index 0000000..a372ac3 --- /dev/null +++ b/applications/gesture_recognition/prj.conf @@ -0,0 +1,103 @@ +# +# Copyright (c) 2018 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# nRF Edge AI +CONFIG_NRF_EDGEAI=y +# nRF Edge AI dependencies +CONFIG_NEWLIB_LIBC=y +CONFIG_FPU=y + +# Enable Bluetooth +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_DEVICE_NAME="T53_sensor_hub" + +# Enable buttons and LEDs +CONFIG_DK_LIBRARY=y + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 + +# Configure Thingy:53 sensors +CONFIG_STDOUT_CONSOLE=y +CONFIG_I2C=y +CONFIG_SPI=y +CONFIG_BMI270_TRIGGER_GLOBAL_THREAD=y +CONFIG_SENSOR=y +CONFIG_ADC=y + +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=1 + +################################################################################ +# Enable USB CDC ACM +CONFIG_USB_DEVICE_REMOTE_WAKEUP=n +CONFIG_USB_NRFX_WORK_QUEUE_STACK_SIZE=1200 +CONFIG_USB_DEVICE_PRODUCT="Thingy:53 Application" +CONFIG_USB_DEVICE_VID=0x1915 +CONFIG_USB_DEVICE_PID=0x530C + +# Enable CDC ACM (USB-UART) +CONFIG_USB_CDC_ACM=y +CONFIG_UART_LINE_CTRL=y + +# Enable console +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_LOG_MODE_MINIMAL=n + +CONFIG_COUNTER=y + +# Enable FPU +CONFIG_FPU=y + +# Bluetooth configuration +CONFIG_NCS_SAMPLES_DEFAULTS=y + +CONFIG_BT=y +CONFIG_BT_LOG_LEVEL_DBG=n +CONFIG_BT_MAX_CONN=2 +CONFIG_BT_MAX_PAIRED=2 +CONFIG_BT_SMP=y +CONFIG_BT_L2CAP_TX_BUF_COUNT=5 +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_DEVICE_NAME="Neuton NRF RemoteControl" +CONFIG_BT_DEVICE_APPEARANCE=961 + +CONFIG_BT_BAS=y +CONFIG_BT_HIDS=y +CONFIG_BT_HIDS_MAX_CLIENT_COUNT=1 +CONFIG_BT_HIDS_DEFAULT_PERM_RW_ENCRYPT=y +CONFIG_BT_GATT_UUID16_POOL_SIZE=40 +CONFIG_BT_GATT_CHRC_POOL_SIZE=20 + +CONFIG_BT_CONN_CTX=y + +CONFIG_BT_DIS=y +CONFIG_BT_DIS_PNP=y +CONFIG_BT_DIS_MANUF="NordicSemiconductor" +CONFIG_BT_DIS_PNP_VID_SRC=2 +CONFIG_BT_DIS_PNP_VID=0x1915 +CONFIG_BT_DIS_PNP_PID=0xEEEF +CONFIG_BT_DIS_PNP_VER=0x0100 + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 + +CONFIG_BT_SETTINGS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y +CONFIG_SETTINGS=y + +CONFIG_DK_LIBRARY=y + +# Enable logging +CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y +CONFIG_USB_DEVICE_LOG_LEVEL_DBG=n + +# LED configuration +CONFIG_PWM=y +CONFIG_DATA_COLLECTION_MODE=n diff --git a/applications/gesture_recognition/resources/ble_connect_1.png b/applications/gesture_recognition/resources/ble_connect_1.png new file mode 100644 index 0000000..a29b34d Binary files /dev/null and b/applications/gesture_recognition/resources/ble_connect_1.png differ diff --git a/applications/gesture_recognition/resources/connected-devkit-sw-install-step-7.jpg b/applications/gesture_recognition/resources/connected-devkit-sw-install-step-7.jpg new file mode 100644 index 0000000..5d6cd43 Binary files /dev/null and b/applications/gesture_recognition/resources/connected-devkit-sw-install-step-7.jpg differ diff --git a/applications/gesture_recognition/resources/device-led-ble-connect-music-mode.gif b/applications/gesture_recognition/resources/device-led-ble-connect-music-mode.gif new file mode 100644 index 0000000..959b701 Binary files /dev/null and b/applications/gesture_recognition/resources/device-led-ble-connect-music-mode.gif differ diff --git a/applications/gesture_recognition/resources/device-led-ble-connect-presentation-mode.gif b/applications/gesture_recognition/resources/device-led-ble-connect-presentation-mode.gif new file mode 100644 index 0000000..68a630c Binary files /dev/null and b/applications/gesture_recognition/resources/device-led-ble-connect-presentation-mode.gif differ diff --git a/applications/gesture_recognition/resources/device-led-no-ble-connect.gif b/applications/gesture_recognition/resources/device-led-no-ble-connect.gif new file mode 100644 index 0000000..c58d1a9 Binary files /dev/null and b/applications/gesture_recognition/resources/device-led-no-ble-connect.gif differ diff --git a/applications/gesture_recognition/resources/device_ble_connected.jpg b/applications/gesture_recognition/resources/device_ble_connected.jpg new file mode 100644 index 0000000..6476aa1 Binary files /dev/null and b/applications/gesture_recognition/resources/device_ble_connected.jpg differ diff --git a/applications/gesture_recognition/resources/device_ble_scanning.jpg b/applications/gesture_recognition/resources/device_ble_scanning.jpg new file mode 100644 index 0000000..17837cd Binary files /dev/null and b/applications/gesture_recognition/resources/device_ble_scanning.jpg differ diff --git a/applications/gesture_recognition/resources/double_shake.gif b/applications/gesture_recognition/resources/double_shake.gif new file mode 100644 index 0000000..fe34312 Binary files /dev/null and b/applications/gesture_recognition/resources/double_shake.gif differ diff --git a/applications/gesture_recognition/resources/double_tap.gif b/applications/gesture_recognition/resources/double_tap.gif new file mode 100644 index 0000000..ee02364 Binary files /dev/null and b/applications/gesture_recognition/resources/double_tap.gif differ diff --git a/applications/gesture_recognition/resources/initial_orientation.gif b/applications/gesture_recognition/resources/initial_orientation.gif new file mode 100644 index 0000000..5f2707b Binary files /dev/null and b/applications/gesture_recognition/resources/initial_orientation.gif differ diff --git a/applications/gesture_recognition/resources/nordic_thingy.jpg b/applications/gesture_recognition/resources/nordic_thingy.jpg new file mode 100644 index 0000000..9466332 Binary files /dev/null and b/applications/gesture_recognition/resources/nordic_thingy.jpg differ diff --git a/applications/gesture_recognition/resources/rotation_left.gif b/applications/gesture_recognition/resources/rotation_left.gif new file mode 100644 index 0000000..52767e0 Binary files /dev/null and b/applications/gesture_recognition/resources/rotation_left.gif differ diff --git a/applications/gesture_recognition/resources/rotation_right.gif b/applications/gesture_recognition/resources/rotation_right.gif new file mode 100644 index 0000000..122e693 Binary files /dev/null and b/applications/gesture_recognition/resources/rotation_right.gif differ diff --git a/applications/gesture_recognition/resources/sw-install-step-3.jpg b/applications/gesture_recognition/resources/sw-install-step-3.jpg new file mode 100644 index 0000000..5046d09 Binary files /dev/null and b/applications/gesture_recognition/resources/sw-install-step-3.jpg differ diff --git a/applications/gesture_recognition/resources/sw-install-step-4.jpg b/applications/gesture_recognition/resources/sw-install-step-4.jpg new file mode 100644 index 0000000..395af78 Binary files /dev/null and b/applications/gesture_recognition/resources/sw-install-step-4.jpg differ diff --git a/applications/gesture_recognition/resources/sw-install-step-5-1-important.jpg b/applications/gesture_recognition/resources/sw-install-step-5-1-important.jpg new file mode 100644 index 0000000..1896abd Binary files /dev/null and b/applications/gesture_recognition/resources/sw-install-step-5-1-important.jpg differ diff --git a/applications/gesture_recognition/resources/sw-install-step-5-2-important.jpg b/applications/gesture_recognition/resources/sw-install-step-5-2-important.jpg new file mode 100644 index 0000000..3738700 Binary files /dev/null and b/applications/gesture_recognition/resources/sw-install-step-5-2-important.jpg differ diff --git a/applications/gesture_recognition/resources/sw-install-step-5.jpg b/applications/gesture_recognition/resources/sw-install-step-5.jpg new file mode 100644 index 0000000..8e0eaf5 Binary files /dev/null and b/applications/gesture_recognition/resources/sw-install-step-5.jpg differ diff --git a/applications/gesture_recognition/resources/sw-install-step-6.jpg b/applications/gesture_recognition/resources/sw-install-step-6.jpg new file mode 100644 index 0000000..d4158e4 Binary files /dev/null and b/applications/gesture_recognition/resources/sw-install-step-6.jpg differ diff --git a/applications/gesture_recognition/resources/sw-install-step-8.jpg b/applications/gesture_recognition/resources/sw-install-step-8.jpg new file mode 100644 index 0000000..e5bd674 Binary files /dev/null and b/applications/gesture_recognition/resources/sw-install-step-8.jpg differ diff --git a/applications/gesture_recognition/resources/swipe_left.gif b/applications/gesture_recognition/resources/swipe_left.gif new file mode 100644 index 0000000..d5abef0 Binary files /dev/null and b/applications/gesture_recognition/resources/swipe_left.gif differ diff --git a/applications/gesture_recognition/resources/swipe_right.gif b/applications/gesture_recognition/resources/swipe_right.gif new file mode 100644 index 0000000..3614694 Binary files /dev/null and b/applications/gesture_recognition/resources/swipe_right.gif differ diff --git a/applications/gesture_recognition/sample.yaml b/applications/gesture_recognition/sample.yaml new file mode 100644 index 0000000..bacc394 --- /dev/null +++ b/applications/gesture_recognition/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: Hello World sample, the simplest Zephyr + application + name: hello world +common: + tags: introduction + integration_platforms: + - native_posix + harness: console + harness_config: + type: one_line + regex: + - "Hello World! (.*)" +tests: + sample.basic.helloworld: + tags: introduction diff --git a/applications/gesture_recognition/src/app_version.h b/applications/gesture_recognition/src/app_version.h new file mode 100644 index 0000000..3896dae --- /dev/null +++ b/applications/gesture_recognition/src/app_version.h @@ -0,0 +1,39 @@ +/* 2023-06-09T11:22:25Z */ + +/* ---------------------------------------------------------------------- +Copyright (c) 2022-2024 Neuton.AI, Inc. +* +The source code and its binary form are being made available on the following terms: +Redistribution, use, and modification are permitted provided that the following +conditions are met: +* +1. Redistributions of source code and/or its binary form must retain the above copyright notice, +* this list of conditions (and the disclaimer) either in the body of the source code or in +* the documentation and/or other materials provided with the distribution of the binary form, as +applicable. +* +2. The name of the copyright holder may not be used to endorse or promote products derived from this +* source code or its binary form without specific prior written permission of Neuton.AI, Inc. +* +3. Disclaimer. THIS SOURCE CODE AND ITS BINARY FORM ARE PROVIDED BY THE COPYRIGHT HOLDER "AS IS". +* THE COPYRIGHT HOLDER HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +* PARTICULAR PURPOSE. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE HELD LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS +* OF THIRD PARTIES; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +* IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE OR ITS BINARY FORM, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +---------------------------------------------------------------------- */ +#ifndef __APPLICATION_VERSION_H__ +#define __APPLICATION_VERSION_H__ + + +#define APP_VERSION_MAJOR 3 +#define APP_VERSION_MINOR 0 +#define APP_VERSION_PATCH 1 + + +#endif /* __APPLICATION_VERSION_H__ */ diff --git a/applications/gesture_recognition/src/ble/hid/ble_hid.c b/applications/gesture_recognition/src/ble/hid/ble_hid.c new file mode 100644 index 0000000..72ec9bd --- /dev/null +++ b/applications/gesture_recognition/src/ble/hid/ble_hid.c @@ -0,0 +1,530 @@ +#include "ble_hid.h" + +#include +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////// + +#define KEY_ARROW_LEFT (0x50) +#define KEY_ARROW_RIGHT (0x4F) +#define KEY_F5 (0x3E) +#define KEY_ESP (0x29) + +#define KEY_MEDIA_VOLUME_UP ( 1 << 0 ) +#define KEY_MEDIA_VOLUME_DOWN ( 1 << 1 ) +#define KEY_MEDIA_MUTE ( 1 << 2 ) +#define KEY_MEDIA_PLAY_PAUSE ( 1 << 3 ) +#define KEY_MEDIA_PREV_TRACK ( 1 << 4 ) +#define KEY_MEDIA_NEXT_TRACK ( 1 << 5 ) + +#if CONFIG_SAMPLE_BT_USE_AUTHENTICATION +/* Require encryption using authenticated link-key. */ +#define SAMPLE_BT_PERM_READ BT_GATT_PERM_READ_AUTHEN +#define SAMPLE_BT_PERM_WRITE BT_GATT_PERM_WRITE_AUTHEN +#else +/* Require encryption. */ +#define SAMPLE_BT_PERM_READ BT_GATT_PERM_READ_ENCRYPT +#define SAMPLE_BT_PERM_WRITE BT_GATT_PERM_WRITE_ENCRYPT +#endif + +////////////////////////////////////////////////////////////////////////////// + +enum +{ + HIDS_REMOTE_WAKE = BIT(0), + HIDS_NORMALLY_CONNECTABLE = BIT(1), +}; + +struct hids_info +{ + uint16_t version; /* version number of base USB HID Specification */ + uint8_t code; /* country HID Device hardware is localized for. */ + uint8_t flags; +} __packed; + +struct hids_report +{ + uint8_t id; /* report id */ + uint8_t type; /* report type */ +} __packed; + +enum +{ + HIDS_INPUT = 0x01, + HIDS_OUTPUT = 0x02, + HIDS_FEATURE = 0x03, +}; + +////////////////////////////////////////////////////////////////////////////// + +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_ALL, + BT_UUID_16_ENCODE(BT_UUID_HIDS_VAL), + BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)), +}; + +static const struct bt_data sd[] = { + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +static struct hids_info info = { + .version = 0x0000, + .code = 0x00, + .flags = HIDS_NORMALLY_CONNECTABLE, +}; + +static struct hids_report input = { + .id = 0x01, + .type = HIDS_INPUT, +}; + +static struct hids_report input_consumer = { + .id = 0x02, + .type = HIDS_INPUT, +}; + +static bool ble_connected_ = false; +static ble_connection_cb_t user_conn_callback_ = NULL; + +static bool ccc_enabled_ = false; +static uint8_t ctrl_point; +static uint8_t consumer_report; + +static uint8_t report_map[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x06, // Usage (Keyboard) + 0xA1, 0x01, // Collection (Application) + + 0x05, 0x07, // Usage Page (Keyboard/Keypad) + 0x85, 0x01, // Report ID (1) + 0x19, 0xE0, // Usage Minimum (Keyboard Left Control) + 0x29, 0xE7, // Usage Maximum (Keyboard Right GUI) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x02, // Input (Data, Variable, Absolute) ; Modifier keys + + 0x95, 0x01, // Report Count (1) + 0x75, 0x08, // Report Size (8) + 0x81, 0x01, // Input (Constant) ; Reserved byte + + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Page (LEDs) + 0x85, 0x01, // Report ID (1) + 0x19, 0x01, // Usage Minimum (Num Lock) + 0x29, 0x05, // Usage Maximum (Kana) + 0x91, 0x02, // Output (Data, Variable, Absolute) ; LED report + + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x03, // Output (Constant) ; LED report padding + + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x65, // Logical Maximum (101) + 0x05, 0x07, // Usage Page (Keyboard/Keypad) + 0x19, 0x00, // Usage Minimum (Reserved (no event indicated)) + 0x29, 0x65, // Usage Maximum (Keyboard Application) + 0x81, 0x00, // Input (Data, Array) ; Key arrays (6 bytes) + + 0xC0, // End Collection + + // Consumer Control Report + 0x05, 0x0C, // Usage Page (Consumer Devices) + 0x09, 0x01, // Usage (Consumer Control) + 0xA1, 0x01, // Collection (Application) + + 0x85, 0x02, // Report ID (2) + 0x05, 0x0C, // Usage Page (Consumer Devices) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + + 0x09, 0xE9, // Usage (Volume Up) + 0x09, 0xEA, // Usage (Volume Down) + 0x09, 0xE2, // Usage (Mute) + 0x09, 0xCD, // Usage (Play/Pause) + 0x19, 0xB5, // Usage Minimum (Scan Next Track) + 0x29, 0xB8, // Usage Maximum (Scan Previous Track) + + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x02, // Input (Data, Variable, Absolute) ; Media keys + + 0xC0 // End Collection +}; + +////////////////////////////////////////////////////////////////////////////// + +static ssize_t read_info(struct bt_conn* conn, + const struct bt_gatt_attr* attr, void* buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_info)); +} + +////////////////////////////////////////////////////////////////////////////// + +static ssize_t read_report_map(struct bt_conn* conn, + const struct bt_gatt_attr* attr, void* buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_map, + sizeof(report_map)); +} + +////////////////////////////////////////////////////////////////////////////// + +static ssize_t read_report(struct bt_conn* conn, + const struct bt_gatt_attr* attr, void* buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_report)); +} + +////////////////////////////////////////////////////////////////////////////// + +static void input_ccc_changed(const struct bt_gatt_attr* attr, uint16_t value) +{ + printk("Input CCCD %s\n", value == BT_GATT_CCC_NOTIFY ? "enabled" : "disabled"); + printk("Input attribute handle: %d\n", attr->handle); + + ccc_enabled_ = (value == BT_GATT_CCC_NOTIFY); +} + +////////////////////////////////////////////////////////////////////////////// + +static ssize_t read_input_report(struct bt_conn* conn, + const struct bt_gatt_attr* attr, void* buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); +} + +////////////////////////////////////////////////////////////////////////////// + +static ssize_t write_ctrl_point(struct bt_conn* conn, + const struct bt_gatt_attr* attr, + const void* buf, uint16_t len, uint16_t offset, + uint8_t flags) +{ + + uint8_t* value = attr->user_data; + + printk("write_ctrl_point\n"); + + if (offset + len > sizeof(ctrl_point)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + memcpy(value + offset, buf, len); + + return len; +} + +////////////////////////////////////////////////////////////////////////////// + +static ssize_t read_consumer_report(struct bt_conn* conn, + const struct bt_gatt_attr* attr, + void* buf, uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); +} + +////////////////////////////////////////////////////////////////////////////// + +static void consumer_ccc_changed(const struct bt_gatt_attr* attr, uint16_t value) +{ + printk("Consumer CCCD %s\n", value == BT_GATT_CCC_NOTIFY ? "enabled" : "disabled"); +} + +////////////////////////////////////////////////////////////////////////////// + +BT_GATT_SERVICE_DEFINE(hog_svc, + BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS), + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_INFO, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, read_info, NULL, &info), + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT_MAP, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, read_report_map, NULL, NULL), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + SAMPLE_BT_PERM_READ, + read_input_report, NULL, NULL), + BT_GATT_CCC(input_ccc_changed, + SAMPLE_BT_PERM_READ | SAMPLE_BT_PERM_WRITE), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ, + read_report, NULL, &input), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + SAMPLE_BT_PERM_READ, + read_consumer_report, NULL, &consumer_report), + BT_GATT_CCC(consumer_ccc_changed, + SAMPLE_BT_PERM_READ | SAMPLE_BT_PERM_WRITE), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ, + read_report, NULL, &input_consumer), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, + BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, + NULL, write_ctrl_point, &ctrl_point), ); + +////////////////////////////////////////////////////////////////////////////// + +static void connected(struct bt_conn* conn, uint8_t err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (err) + { + printk("Failed to connect to %s (%u)\n", addr, err); + return; + } + + printk("Connected %s\n", addr); + + if (bt_conn_set_security(conn, BT_SECURITY_L2)) + { + printk("Failed to set security\n"); + } + + ble_connected_ = true; + + if (user_conn_callback_) + user_conn_callback_(ble_connected_); +} + +////////////////////////////////////////////////////////////////////////////// + +static void disconnected(struct bt_conn* conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Disconnected from %s (reason 0x%02x)\n", addr, reason); + + ble_connected_ = false; + ccc_enabled_ = false; + + if (user_conn_callback_) + user_conn_callback_(ble_connected_); + + // If disconnected due to authentication failure, clear all pairing info + if (reason == BT_HCI_ERR_AUTH_FAIL || reason == BT_HCI_ERR_PIN_OR_KEY_MISSING) { + printk("Authentication related disconnect, clearing pairing info\n"); + bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY); + } + + // Restart advertising + int err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); + if (err) + { + printk("Advertising failed to start (err %d)\n", err); + return; + } + printk("Advertising successfully started\n"); +} + +////////////////////////////////////////////////////////////////////////////// + +static void security_changed(struct bt_conn* conn, bt_security_t level, + enum bt_security_err err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (!err) + { + printk("Security changed: %s level %u\n", addr, level); + } else + { + printk("Security failed: %s level %u err %d\n", addr, level, + err); + + bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY); + } +} + +////////////////////////////////////////////////////////////////////////////// + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, + .security_changed = security_changed, +}; + +////////////////////////////////////////////////////////////////////////////// + +static void bt_ready(int err) +{ + if (err) + { + printk("Bluetooth init failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + if (IS_ENABLED(CONFIG_SETTINGS)) + { + settings_load(); + } + + err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); + if (err) + { + printk("Advertising failed to start (err %d)\n", err); + return; + } + + printk("Advertising successfully started\n"); +} + +////////////////////////////////////////////////////////////////////////////// + +static void auth_passkey_display(struct bt_conn* conn, unsigned int passkey) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Passkey for %s: %06u\n", addr, passkey); +} + +////////////////////////////////////////////////////////////////////////////// + +static void auth_cancel(struct bt_conn* conn) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Pairing cancelled: %s\n", addr); +} + +////////////////////////////////////////////////////////////////////////////// + +static struct bt_conn_auth_cb auth_cb_display = { + .passkey_display = auth_passkey_display, + .passkey_entry = NULL, + .cancel = auth_cancel, +}; + +////////////////////////////////////////////////////////////////////////////// + +int ble_hid_init(ble_connection_cb_t cb) +{ + int err; + err = bt_enable(bt_ready); + + if (err) + { + printk("Bluetooth init failed (err %d)\n", err); + return err; + } + + user_conn_callback_ = cb; + + if (IS_ENABLED(CONFIG_SAMPLE_BT_USE_AUTHENTICATION)) + { + bt_conn_auth_cb_register(&auth_cb_display); + } + + return err; +} + +////////////////////////////////////////////////////////////////////////////// + +int ble_hid_send_key(ble_hid_key_t key) +{ + int res = -1; + + if (!ble_connected_ || !ccc_enabled_) + return -1; + + uint8_t report[8] = {0}; + size_t report_len = sizeof(report); + + int attr_index = 5; + + switch (key) + { + case BLE_HID_KEY_ARROW_LEFT: + report[2] = KEY_ARROW_LEFT; + break; + case BLE_HID_KEY_ARROW_RIGHT: + report[2] = KEY_ARROW_RIGHT; + break; + case BLE_HID_KEY_F5: + report[2] = KEY_F5; + break; + case BLE_HID_KEY_ESC: + report[2] = KEY_ESP; + break; + case BLE_HID_KEY_MEDIA_PREV_TRACK: + report[0] = KEY_MEDIA_PREV_TRACK; + report_len = 2; + attr_index = 10; + break; + case BLE_HID_KEY_MEDIA_NEXT_TRACK: + report[0] = KEY_MEDIA_NEXT_TRACK; + report_len = 2; + attr_index = 10; + break; + case BLE_HID_KEY_MEDIA_MUTE: + report[0] = KEY_MEDIA_MUTE; + report_len = 2; + attr_index = 10; + break; + case BLE_HID_KEY_MEDIA_PLAY_PAUSE: + report[0] = KEY_MEDIA_PLAY_PAUSE; + report_len = 2; + attr_index = 10; + break; + case BLE_HID_KEY_MEDIA_VOLUME_UP: + report[0] = KEY_MEDIA_VOLUME_UP; + report_len = 2; + attr_index = 10; + break; + case BLE_HID_KEY_MEDIA_VOLUME_DOWN: + report[0] = KEY_MEDIA_VOLUME_DOWN; + report_len = 2; + attr_index = 10; + break; + default: + return res; + } + + res = bt_gatt_notify(NULL, &hog_svc.attrs[attr_index], report, report_len); + + if (res) + { + printk("Failed to send key, error = %d\n", res); + return false; + } + else + { + printk("BLE HID Key %d sent successfully\n", report[0]); + } + + /* reset report */ + memset(report, 0, sizeof(report)); + + res = bt_gatt_notify(NULL, &hog_svc.attrs[attr_index], report, report_len); + return res; +} \ No newline at end of file diff --git a/applications/gesture_recognition/src/ble/hid/ble_hid.h b/applications/gesture_recognition/src/ble/hid/ble_hid.h new file mode 100644 index 0000000..f09c51d --- /dev/null +++ b/applications/gesture_recognition/src/ble/hid/ble_hid.h @@ -0,0 +1,75 @@ +/** + * + * @defgroup ble_hid Bluetooth HID interface + * @{ + * @ingroup ble + * + * + */ +#ifndef __BLE_HID_H__ +#define __BLE_HID_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +/** + * @brief Supported HID keys to emulate keyboard + */ +typedef enum +{ + BLE_HID_KEY_ARROW_LEFT = 0, + BLE_HID_KEY_ARROW_RIGHT, + BLE_HID_KEY_F5, + BLE_HID_KEY_ESC, + BLE_HID_KEY_MEDIA_PREV_TRACK, + BLE_HID_KEY_MEDIA_NEXT_TRACK, + BLE_HID_KEY_MEDIA_PLAY_PAUSE, + BLE_HID_KEY_MEDIA_MUTE, + BLE_HID_KEY_MEDIA_VOLUME_UP, + BLE_HID_KEY_MEDIA_VOLUME_DOWN, + + BLE_HID_KEYS_count +} ble_hid_key_t; + +/** + * @brief BLE connection callback, this callback will be called when state of the connection is changed + * + * @param connected BLE connected state, true if connected, otherwise false + */ +typedef void (*ble_connection_cb_t)(bool connected); + +/** + * @brief Initialize BLE HID profile and start advertasing + * + * @param cb Connection callback @ref ble_connection_cb_t + * + * @return Operation status, 0 for success + */ +int ble_hid_init(ble_connection_cb_t cb); + +/** + * @brief Send keyboard key via HID profile + * + * @param key Keyboard key @ref ble_hid_key_t + * + * @return Operation status, 0 for success + */ +int ble_hid_send_key(ble_hid_key_t key); + + + +#ifdef __cplusplus +} +#endif // __cplusplus + + +#endif // __BLE_HID_H__ + +/** + * @} + */ diff --git a/applications/gesture_recognition/src/bsp/bsp_common.h b/applications/gesture_recognition/src/bsp/bsp_common.h new file mode 100644 index 0000000..eafb2d5 --- /dev/null +++ b/applications/gesture_recognition/src/bsp/bsp_common.h @@ -0,0 +1,244 @@ +/** + * + * @defgroup bsp_common Common + * @{ + * @ingroup bsp + * + * @brief BSP common useful utils, types and macro. + * + */ +#ifndef __BSP_COMMON_H__ +#define __BSP_COMMON_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/** + * @brief Macro for performing rounded integer division (as opposed to truncating the result). + * + * @param[in] A Numerator. + * @param[in] B Denominator. + * + * @return Rounded (integer) result of dividing A by B. + */ +#ifndef ROUNDED_DIV +#define ROUNDED_DIV(A, B) (((A) + ((B) / 2)) / (B)) +#endif + +/** + * @brief Macro for counting items in an object. + * + * @param[in] x An object for which the counting will be made. + * + * @return A number of items in an object. + */ +#ifndef COUNT_OF +#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) +#endif + +/** + * @brief Macro for getting offset for a field in the provided type. + * + * @param[in] type Provided type. + * @param[in] field Field in the provided type. + * + * @return Offset of a field in the provided type. + */ +#ifndef OFFSET_OF +#define OFFSET_OF(type, field) ((unsigned long) &(((type *) 0)->field)) +#endif + +/** + * @brief Macro for obtaining the minimum value of two. + * + * @param[in] A First value. + * @param[in] B Second value. + * + * @return Minimum value of two set values. + */ +#ifndef MIN +#define MIN(A, B) (( (A) < (B) ) ? (A) : (B)) +#endif + +/** + * @brief Macro for obtaining the maximum value of two. + * + * @param[in] A First value. + * @param[in] B Second value. + * + * @return Maximum value of two set values. + */ +#ifndef MAX +#define MAX(A, B) (( (A) > (B) ) ? (A) : (B)) +#endif + +/** + * @brief Macro for obtaining the constraint value between min and max values. + * + * @param[in] val input value. + * @param[in] vmin lower border. + * @param[in] vmax higher border. + * + * @return Constraint value between min and max. + */ +#ifndef CONSTRAIN +#define CONSTRAIN(val, vmin, vmax) ((val)>(vmax)?(vmax):(val)<(vmin)?(vmin):(val)) +#endif + +/** + * @brief Macro for unused argument. + * + * @param[in] x Unused argument. + * + * @return None. + */ +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif + +/** @brief Void value using for returns or argument for void-argument functions. */ +#define VOID_VALUE + +/** + * @brief Macro for checking argument for NULL. + * Macro will call return with the status BSP_STATUS_NULL_ARGUMENT. + * + * @param[in] x Argument to be checked. + * + */ +#define BSP_NULL_CHECK(x) \ + do \ + { \ + if ( (x) == NULL ) \ + { \ + return BSP_STATUS_NULL_ARGUMENT; \ + } \ + } while(0) + +/** + * @brief Macro for verifying that the provided status is BSP_STATUS_SUCCESS. It will cause the exterior + * function to return an error code if it is not @ref BSP_STATUS_SUCCESS. + * + * @param[in] status Status to check vs BSP_STATUS_SUCCESS. + */ +#define BSP_VERIFY_SUCCESS(status) \ +do \ +{ \ + if ((status) != BSP_STATUS_SUCCESS) \ + { \ + return (status); \ + } \ +} while(0) + +/** + * @brief Macro for verifying that the provided argumets is valid. It will cause the exterior + * function to return an error code if it is not @ref BSP_STATUS_INVALID_ARGUMENT. + * + * @param[in] is_valid boolean comparison on the validity of the argument. + */ +#define BSP_VERIFY_VALID_ARG(is_valid) \ +do \ +{ \ + if (!(is_valid)) \ + { \ + return BSP_STATUS_INVALID_ARGUMENT; \ + } \ +} while(0) + +/** + * @brief Macro for verifying any boolean condition and returning status if condition failed + * + * @param[in] err_cond boolean condition to be checked. + * @param[in] err Return status if condition failed. + */ +#define BSP_RETURN_IF(err_cond, err) __RETURN_CONDITIONAL(err_cond, err) + +/** + * @brief Return if expr == true. + * + * @param[in] expr Expression for validating. + * @param[in] ret_val Returning value. + */ +#ifndef __RETURN_CONDITIONAL +# define __RETURN_CONDITIONAL(expr, ret_val) \ + do \ + { \ + if ((expr) == true) \ + { \ + return ret_val; \ + } \ + } \ + while (0) +#endif + +/** Generic callback type */ +typedef void(*bsp_generic_cb_t)(void); + +/** Base interrupt request handler type */ +typedef bsp_generic_cb_t bsp_irq_handler_t; + +/** + * @brief Base async data ready callback type + * + * @param[in] data A pointer to the data buffer that was passed to the async function + * @param[in] data_size Size of data that was passed to the async function, in bytes + */ +typedef void(*bsp_async_drdy_cb_t)(void* data, uint32_t data_size); + +/** + * Generic bsp operation status code + * + * This enumeration is used by various bsp subsystems to provide + * information on their status. It may also be used by functions as a + * return code. + */ +typedef enum bsp_status_e +{ + /** Operation successful */ + BSP_STATUS_SUCCESS, + + /** The operation failed because the module is already in the + * requested mode */ + BSP_STATUS_ALREADY_IN_MODE, + + /** There was an error communicating with hardware */ + BSP_STATUS_HARDWARE_ERROR, + + /** The operation failed with an unspecified error */ + BSP_STATUS_UNSPECIFIED_ERROR, + + /** The argument supplied to the operation was invalid */ + BSP_STATUS_INVALID_ARGUMENT, + + /** The argument supplied to the operation was NULL */ + BSP_STATUS_NULL_ARGUMENT, + + /** The operation failed because the module was busy */ + BSP_STATUS_BUSY, + + /** The requested operation was not available */ + BSP_STATUS_UNAVAILABLE, + + /** The operation or service not supported */ + BSP_STATUS_NOT_SUPPORTED, + + /** The requested operation timeout */ + BSP_STATUS_TIMEOUT, +} bsp_status_t; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __BSP_COMMON_H__ */ + +/** + * @} + */ diff --git a/applications/gesture_recognition/src/bsp/button/bsp_button.c b/applications/gesture_recognition/src/bsp/button/bsp_button.c new file mode 100644 index 0000000..64c2e68 --- /dev/null +++ b/applications/gesture_recognition/src/bsp/button/bsp_button.c @@ -0,0 +1,82 @@ +#include "bsp_button.h" +#include +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////// + +#define SW0_NODE DT_ALIAS(sw0) +#if !DT_NODE_HAS_STATUS(SW0_NODE, okay) +#error "Unsupported board: sw0 devicetree alias is not defined" +#endif + +////////////////////////////////////////////////////////////////////////////// + +static const struct gpio_dt_spec button_sw0_ = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0}); +static struct gpio_callback button_cb_data_; +static bsp_button_click_handler_t button_click_handler_ = NULL; + +////////////////////////////////////////////////////////////////////////////// + +static bool is_pressed_(void) +{ + return gpio_pin_get_dt(&button_sw0_) > 0; +} + +////////////////////////////////////////////////////////////////////////////// + +void button_interrupt(const struct device* dev, struct gpio_callback* cb, uint32_t pins) +{ + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(pins); + + if (button_click_handler_) + { + button_click_handler_(is_pressed_()); + } +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_button_init(void) +{ + int ret; + + if (!device_is_ready(button_sw0_.port)) + { + printk("Error: button device %s is not ready\n", button_sw0_.port->name); + return ENODEV; + } + + ret = gpio_pin_configure_dt(&button_sw0_, GPIO_INPUT); + if (ret != 0) + { + printk("Error %d: failed to configure %s pin %d\n", ret, button_sw0_.port->name, button_sw0_.pin); + return ret; + } + + ret = gpio_pin_interrupt_configure_dt(&button_sw0_, GPIO_INT_EDGE_BOTH); + + if (ret != 0) + { + printk("Error %d: failed to configure interrupt on %s pin %d\n", ret, button_sw0_.port->name, button_sw0_.pin); + return ret; + } + + gpio_init_callback(&button_cb_data_, button_interrupt, BIT(button_sw0_.pin)); + gpio_add_callback(button_sw0_.port, &button_cb_data_); + printk("Set up button at %s pin %d\r\n", button_sw0_.port->name, button_sw0_.pin); + + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +void bsp_button_reg_click_handler(bsp_button_click_handler_t click_handler) +{ + button_click_handler_ = click_handler; +} \ No newline at end of file diff --git a/applications/gesture_recognition/src/bsp/button/bsp_button.h b/applications/gesture_recognition/src/bsp/button/bsp_button.h new file mode 100644 index 0000000..59b1a81 --- /dev/null +++ b/applications/gesture_recognition/src/bsp/button/bsp_button.h @@ -0,0 +1,43 @@ +/** + * + * @defgroup bsp_button Button control functions + * @{ + * @ingroup bsp + * + * + */ +#ifndef __BSP_BUTTON_H__ +#define __BSP_BUTTON_H__ + +#include + +typedef void (*bsp_button_click_handler_t)(bool is_pressed); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * @brief Initialize button module + * + * @return Operation status result, 0 for success + */ +int bsp_button_init(void); + +/** + * @brief Register button click handler + * + * @param click_handler Button click handler + */ +void bsp_button_reg_click_handler(bsp_button_click_handler_t click_handler); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __BSP_BUTTON_H__ */ + +/** + * @} + */ \ No newline at end of file diff --git a/applications/gesture_recognition/src/bsp/led/bsp_led.c b/applications/gesture_recognition/src/bsp/led/bsp_led.c new file mode 100644 index 0000000..8150767 --- /dev/null +++ b/applications/gesture_recognition/src/bsp/led/bsp_led.c @@ -0,0 +1,191 @@ +#include "bsp_led.h" +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////// + +static const struct pwm_dt_spec red_pwm_led_ = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0)); +static const struct pwm_dt_spec green_pwm_led_ = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1)); +static const struct pwm_dt_spec blue_pwm_led_ = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led2)); + +////////////////////////////////////////////////////////////////////////////// + +// not calibrated, fixed for specific board +#define PWM_PERIOD PWM_MSEC(20) + +////////////////////////////////////////////////////////////////////////////// + +static int init_led_(const struct pwm_dt_spec pwm_led) +{ + int ret = 0; + if (!device_is_ready(pwm_led.dev)) + { + printk("PWM LED Init error '%s' device_is_ready()\n", pwm_led.dev->name); + return ret; + } + + ret = pwm_set_dt(&pwm_led, PWM_PERIOD, 0); + if (ret) + { + printk("Error %d: failed to set pulse width for %s\n", ret, pwm_led.dev->name); + return ret; + } + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +static int set_led_(const struct pwm_dt_spec pwm_led, float brightness) +{ + int ret = 0; + uint32_t pulse = brightness * PWM_PERIOD; + ret = pwm_set_pulse_dt(&pwm_led, pulse); + if (ret < 0) + { + printk("LED Init error pwm_set_pulse_dt()\n"); + return ret; + } + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_init(void) +{ + int ret; + ret = init_led_(red_pwm_led_); + BSP_RETURN_IF(ret != 0, ret); + + ret = init_led_(green_pwm_led_); + BSP_RETURN_IF(ret != 0, ret); + + ret = init_led_(blue_pwm_led_); + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_set_red(float brightness) +{ + pwm_set_pulse_dt(&green_pwm_led_, 0); + pwm_set_pulse_dt(&blue_pwm_led_, 0); + return set_led_(red_pwm_led_, brightness); +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_set_green(float brightness) +{ + pwm_set_pulse_dt(&red_pwm_led_, 0); + pwm_set_pulse_dt(&blue_pwm_led_, 0); + return set_led_(green_pwm_led_, brightness); +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_set_blue(float brightness) +{ + pwm_set_pulse_dt(&red_pwm_led_, 0); + pwm_set_pulse_dt(&green_pwm_led_, 0); + return set_led_(blue_pwm_led_, brightness); +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_set_rgb(float r, float g, float b) +{ + int ret; + uint32_t red = r * PWM_PERIOD; + uint32_t green = g * PWM_PERIOD; + uint32_t blue = b * PWM_PERIOD; + ret = pwm_set_pulse_dt(&red_pwm_led_, red); + BSP_RETURN_IF(ret != 0, ret); + + ret = pwm_set_pulse_dt(&green_pwm_led_, green); + BSP_RETURN_IF(ret != 0, ret); + + ret = pwm_set_pulse_dt(&blue_pwm_led_, blue); + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_off(void) +{ + int ret; + ret = pwm_set_pulse_dt(&red_pwm_led_, 0); + BSP_RETURN_IF(ret != 0, ret); + + ret = pwm_set_pulse_dt(&green_pwm_led_, 0); + BSP_RETURN_IF(ret != 0, ret); + + ret = pwm_set_pulse_dt(&blue_pwm_led_, 0); + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_blink_red(float brightness, int32_t on_ms, int32_t off_ms) +{ + int ret; + ret = bsp_led_set_red(brightness); + BSP_RETURN_IF(ret != 0, ret); + + k_msleep(on_ms); + + ret = bsp_led_off(); + + k_msleep(off_ms); + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_blink_green(float brightness, int32_t on_ms, int32_t off_ms) +{ + int ret; + ret = bsp_led_set_green(brightness); + BSP_RETURN_IF(ret != 0, ret); + + k_msleep(on_ms); + + ret = bsp_led_off(); + + k_msleep(off_ms); + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_blink_blue(float brightness, int32_t on_ms, int32_t off_ms) +{ + int ret; + ret = bsp_led_set_blue(brightness); + BSP_RETURN_IF(ret != 0, ret); + + k_msleep(on_ms); + + ret = bsp_led_off(); + + k_msleep(off_ms); + return ret; +} + +////////////////////////////////////////////////////////////////////////////// + +int bsp_led_blink_rgb(float r, float g, float b, int32_t on_ms, int32_t off_ms) +{ + int ret; + ret = bsp_led_set_rgb(r, g, b); + + BSP_RETURN_IF(ret != 0, ret); + + k_msleep(on_ms); + + ret = bsp_led_off(); + + k_msleep(off_ms); + return ret; +} \ No newline at end of file diff --git a/applications/gesture_recognition/src/bsp/led/bsp_led.h b/applications/gesture_recognition/src/bsp/led/bsp_led.h new file mode 100644 index 0000000..cd459b1 --- /dev/null +++ b/applications/gesture_recognition/src/bsp/led/bsp_led.h @@ -0,0 +1,127 @@ +/** + * + * @defgroup bsp_led LEDs control functions + * @{ + * @ingroup bsp + * + * @brief This module provides LEDs control functions. + * + */ +#ifndef __BSP_LED_H__ +#define __BSP_LED_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/** + * @brief Initialize LEDs + * + * @return Operation status, 0 for success + */ +int bsp_led_init(void); + +/** + * @brief Turn on red LED with specific brightness + * + * @param brightness LED brightness in range 0 - 1 + * + * @return Operation status, 0 for success + */ +int bsp_led_set_red(float brightness); + +/** + * @brief Turn on green LED with specific brightness + * + * @param brightness LED brightness in range 0 - 1 + * + * @return Operation status, 0 for success + */ +int bsp_led_set_green(float brightness); + +/** + * @brief Turn on blue LED with specific brightness + * + * @param brightness LED brightness in range 0 - 1 + * + * @return Operation status, 0 for success + */ +int bsp_led_set_blue(float brightness); + +/** + * @brief Turn on RGB LED with specific brightness for each color + * + * @param r Red LED brightness in range 0 - 1 + * @param g Green LED brightness in range 0 - 1 + * @param b Blue LED brightness in range 0 - 1 + * + * @return Operation status, 0 for success + */ +int bsp_led_set_rgb(float r, float g, float b); + +/** + * @brief Turn off all LEDs + * + * @return Operation status, 0 for success + */ +int bsp_led_off(void); + +/** + * @brief Blink red LED with specific brightness + * + * @param brightness LED brightness in range 0 - 1 + * @param on_ms LED turns on time in milliseconds + * @param off_ms LED turns off time in milliseconds + * + * @return Operation status, 0 for success + */ +int bsp_led_blink_red(float brightness, int32_t on_ms, int32_t off_ms); + +/** + * @brief Blink green LED with specific brightness + * + * @param brightness LED brightness in range 0 - 1 + * @param on_ms LED turns on time in milliseconds + * @param off_ms LED turns off time in milliseconds + * + * @return Operation status, 0 for success + */ +int bsp_led_blink_green(float brightness, int32_t on_ms, int32_t off_ms); + +/** + * @brief Blink blue LED with specific brightness + * + * @param brightness LED brightness in range 0 - 1 + * @param on_ms LED turns on time in milliseconds + * @param off_ms LED turns off time in milliseconds + * + * @return Operation status, 0 for success + */ +int bsp_led_blink_blue(float brightness, int32_t on_ms, int32_t off_ms); + +/** + * @brief Blink RGB LED with specific brightness for each color + * + * @param r Red LED brightness in range 0 - 1 + * @param g Green LED brightness in range 0 - 1 + * @param b Blue LED brightness in range 0 - 1 + * @param on_ms RGB LED turns on time in milliseconds + * @param off_ms RGB LED turns off time in milliseconds + * + * @return Operation status, 0 for success + */ +int bsp_led_blink_rgb(float r, float g, float b, int32_t on_ms, int32_t off_ms); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __BSP_LED_H__ */ + +/** + * @} + */ diff --git a/applications/gesture_recognition/src/bsp/sensor/imu/bsp_imu.c b/applications/gesture_recognition/src/bsp/sensor/imu/bsp_imu.c new file mode 100644 index 0000000..6ca9cf6 --- /dev/null +++ b/applications/gesture_recognition/src/bsp/sensor/imu/bsp_imu.c @@ -0,0 +1,122 @@ +#include "bsp_imu.h" + +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////// + +static struct +{ + bool initialized; + bsp_generic_cb_t data_ready_cb; + const struct device* dev; +} imu_ctx_ = {0}; + +////////////////////////////////////////////////////////////////////////////// + +static void data_read_timer_handler(struct k_timer *timer) +{ + (void) timer; + if (imu_ctx_.data_ready_cb) + { + imu_ctx_.data_ready_cb(); + } +} + +K_TIMER_DEFINE(data_ready_timer_, data_read_timer_handler, NULL); + +////////////////////////////////////////////////////////////////////////////// + +bsp_status_t bsp_imu_init(const bsp_imu_config_t* p_config, + bsp_generic_cb_t data_ready_cb) +{ + BSP_NULL_CHECK(p_config); + + if (imu_ctx_.dev == NULL) + imu_ctx_.dev = DEVICE_DT_GET_ONE(bosch_bmi270); + + BSP_RETURN_IF(imu_ctx_.dev == NULL, BSP_STATUS_HARDWARE_ERROR); + + int res = 0; + struct sensor_value full_scale = {0}; + struct sensor_value sampling_freq = {0}; + struct sensor_value oversampling = {0}; + + /* Setting scale in G, due to loss of precision if the SI unit m/s^2 + * is used + */ + full_scale.val1 = p_config->accel_fs_g; /* G */ + full_scale.val2 = 0; + sampling_freq.val1 = p_config->data_rate_hz; /* Hz. Performance mode */ + sampling_freq.val2 = 0; + oversampling.val1 = 1; /* Normal mode */ + oversampling.val2 = 0; + + res = sensor_attr_set(imu_ctx_.dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_FULL_SCALE, &full_scale); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + res = sensor_attr_set(imu_ctx_.dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_OVERSAMPLING, &oversampling); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + res = sensor_attr_set(imu_ctx_.dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_SAMPLING_FREQUENCY, &sampling_freq); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + /* Setting scale in degrees/s to match the sensor scale */ + full_scale.val1 = p_config->gyro_fs_dps; /* dps */ + full_scale.val2 = 0; + sampling_freq.val1 = p_config->data_rate_hz; /* Hz. Performance mode */ + sampling_freq.val2 = 0; + oversampling.val1 = 1; /* Normal mode */ + oversampling.val2 = 0; + + res = sensor_attr_set(imu_ctx_.dev, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_FULL_SCALE, &full_scale); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + res = sensor_attr_set(imu_ctx_.dev, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_OVERSAMPLING, &oversampling); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + imu_ctx_.data_ready_cb = data_ready_cb; + const uint32_t data_ready_timer_period = 1000 / p_config->data_rate_hz; + k_timer_start(&data_ready_timer_, K_MSEC(data_ready_timer_period), K_MSEC(data_ready_timer_period)); + + /* Set sampling frequency last as this also sets the appropriate + * power mode. If already sampling, change sampling frequency to + * 0.0Hz before changing other attributes + */ + res = sensor_attr_set(imu_ctx_.dev, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_SAMPLING_FREQUENCY, &sampling_freq); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + return BSP_STATUS_SUCCESS; +} + +////////////////////////////////////////////////////////////////////////////// + +bsp_status_t bsp_imu_read(bsp_imu_data_t* const p_data) +{ + BSP_NULL_CHECK(p_data); + BSP_RETURN_IF(imu_ctx_.dev == NULL, BSP_STATUS_HARDWARE_ERROR); + + struct sensor_value acc[3], gyr[3]; + + int res = sensor_sample_fetch(imu_ctx_.dev); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + res = sensor_channel_get(imu_ctx_.dev, SENSOR_CHAN_ACCEL_XYZ, acc); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + res = sensor_channel_get(imu_ctx_.dev, SENSOR_CHAN_GYRO_XYZ, gyr); + BSP_RETURN_IF(res != 0, BSP_STATUS_HARDWARE_ERROR); + + for (int i = 0; i < 3; i++) + { + p_data->accel[i].phys = (float)sensor_value_to_double(&acc[i]); + p_data->accel[i].raw = (p_data->accel[i].phys * 1000); + + p_data->gyro[i].phys = (float)sensor_value_to_double(&gyr[i]); + p_data->gyro[i].raw = (p_data->gyro[i].phys * 1000); + } + + return BSP_STATUS_SUCCESS; +} \ No newline at end of file diff --git a/applications/gesture_recognition/src/bsp/sensor/imu/bsp_imu.h b/applications/gesture_recognition/src/bsp/sensor/imu/bsp_imu.h new file mode 100644 index 0000000..c77a9f8 --- /dev/null +++ b/applications/gesture_recognition/src/bsp/sensor/imu/bsp_imu.h @@ -0,0 +1,92 @@ +/** + * + * @defgroup bsp_imu Inertial Measurement Unit (IMU) + * @{ + * @ingroup bsp + * + * @brief This module provides IMU sensor control functions. + * + */ +#ifndef __BSP_SENSOR_IMU_H__ +#define __BSP_SENSOR_IMU_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Accelerometer full scale variants */ +#define BSP_IMU_ACCEL_SCALE_2G (2) +#define BSP_IMU_ACCEL_SCALE_4G (4) +#define BSP_IMU_ACCEL_SCALE_8G (8) +#define BSP_IMU_ACCEL_SCALE_16G (16) + +/** Gyroscope full scale variants */ +#define BSP_IMU_ACCEL_SCALE_125DPS (125) +#define BSP_IMU_ACCEL_SCALE_250DPS (250) +#define BSP_IMU_ACCEL_SCALE_500DPS (500) +#define BSP_IMU_ACCEL_SCALE_1000DPS (1000) +#define BSP_IMU_ACCEL_SCALE_2000DPS (2000) + +/** + * @brief IMU sensor configurations + */ +typedef struct bsp_imu_config_s +{ + /** Accelerometer full scale in G */ + int32_t accel_fs_g; + + /** Gyroscope full scale in DPS */ + int32_t gyro_fs_dps; + + /** IMU data rate in Hz */ + int32_t data_rate_hz; +} bsp_imu_config_t; + +/** Inertial sensor data */ +typedef struct bsp_imu_data_s +{ + /** Accelerometer data */ + struct + { + int16_t raw; + float phys; + } accel[3]; + /** Gyroscope data */ + struct + { + int16_t raw; + float phys; + } gyro[3]; +} bsp_imu_data_t; + +/** + * @brief Initialize and start generation of IMU sensor data + * + * @param p_config IMU configuration settings @ref bsp_imu_config_t + * @param data_ready_cb Data ready callback, provided callback will be called when new data sample is ready for reading + * + * @return Operation status @ref bsp_status_t + */ +bsp_status_t bsp_imu_init(const bsp_imu_config_t* p_config, + bsp_generic_cb_t data_ready_cb); + +/** + * @brief Read IMU sensor data + * + * @param p_data Pointer to data to be filled @ref bsp_imu_data_t + * + * @return Operation status @ref bsp_status_t + */ +bsp_status_t bsp_imu_read(bsp_imu_data_t* const p_data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BSP_SENSOR_IMU_H__ */ + +/** + * @} + */ diff --git a/applications/gesture_recognition/src/inference_postprocessing.c b/applications/gesture_recognition/src/inference_postprocessing.c new file mode 100644 index 0000000..7516bfc --- /dev/null +++ b/applications/gesture_recognition/src/inference_postprocessing.c @@ -0,0 +1,161 @@ +// ///////////////////////// Package Header Files //////////////////////////// +#include "inference_postprocessing.h" + +// ////////////////////// Standard C++ Header Files ////////////////////////// +// /////////////////////// Standard C Header Files /////////////////////////// +#include + +/// +#define PREVIOUS_PREDICTION_NUM (3) +/// + +////////////////////////////////////////////////////////////////////////////// + +typedef struct prediction_ctx_s +{ + /** Prediction target */ + uint16_t target; + + /** Prediction probability */ + float probability; +} prediction_ctx_t; + +typedef struct prediction_tracer_s +{ + /** Current prediction index */ + int index; + + /** Current prediction target */ + uint16_t target; + + /** Previous predictions context */ + prediction_ctx_t prev[PREVIOUS_PREDICTION_NUM]; +} prediction_tracer_t; + +/** + * @brief Class prediction conditions for postprocessing + * + */ +typedef struct class_prediction_condition_s +{ + /** Minimum number of repetitions of a class for prediction */ + uint16_t min_repeat_count; + + /** Minimum probability threshold for prediction */ + float probability_threshold; +} class_prediction_condition_t; + +////////////////////////////////////////////////////////////////////////////// + +static const class_prediction_condition_t* get_class_condition_(uint8_t predicted_target); +static const char* get_name_by_target_(uint8_t predicted_target); + +////////////////////////////////////////////////////////////////////////////// + +void inference_postprocess(const uint16_t predicted_target, + const float prob, + const bool do_postprocessing, + inference_postprocess_cb_t callback) +{ + uint16_t target = predicted_target; + float probability = prob; + + static prediction_tracer_t tracer_ = {0U}; + + if (do_postprocessing) { + + if ((target == CLASS_LABEL_UNKNOWN) || (target == CLASS_LABEL_IDLE)) { + /** Reset tracer for UNKNOWN and IDLE classes */ + tracer_.index = 0; + tracer_.target = target; + } else { + if (tracer_.index >= PREVIOUS_PREDICTION_NUM) + tracer_.index = 0; + + /** Reset tracer if predicted class is not the same as previous */ + if (tracer_.target != target) { + tracer_.index = 0; + tracer_.target = target; + } + + tracer_.prev[tracer_.index].probability = probability; + tracer_.index++; + + const class_prediction_condition_t* class_condition = get_class_condition_(target); + if (class_condition == NULL) { + return; + } + + /** Сlass is labled as CLASS_LABEL_UNKNOWN if the number of repetitions does not exceed the threshold */ + if (tracer_.index >= class_condition->min_repeat_count) { + /** Calculate average probability for last N predictions of the same class */ + float average_prob = 0.0f; + + for (int i = 0; i < tracer_.index; ++i) + average_prob += tracer_.prev[i].probability; + + average_prob = average_prob / tracer_.index; + + /** If average probability is less the class probability threshold, + * the class is labled as CLASS_LABEL_UNKNOWN */ + if (average_prob < class_condition->probability_threshold) + target = CLASS_LABEL_UNKNOWN; + else + probability = average_prob; + + /** Reset tracer index for non-repetative classes */ + if ((target != CLASS_LABEL_ROTATION_RIGHT) || (target != CLASS_LABEL_ROTATION_LEFT)) + tracer_.index = 0; + } else { + target = CLASS_LABEL_UNKNOWN; + } + } + } + + /** Provide result to user callback */ + if (callback) + callback(target, probability, get_name_by_target_(target), !do_postprocessing); +} + +////////////////////////////////////////////////////////////////////////////// + +static const char* get_name_by_target_(uint8_t predicted_target) +{ + + static const char* LABEL_VS_NAME[] = + { + [CLASS_LABEL_IDLE] = "IDLE", + [CLASS_LABEL_UNKNOWN] = "UNKNOWN", + [CLASS_LABEL_SWIPE_LEFT] = "SWIPE LEFT", + [CLASS_LABEL_SWIPE_RIGHT] = "SWIPE RIGHT", + [CLASS_LABEL_DOUBLE_SHAKE] = "DOUBLE SHAKE", + [CLASS_LABEL_DOUBLE_THUMB] = "DOUBLE THUMB", + [CLASS_LABEL_ROTATION_RIGHT] = "ROTATION RIGHT", + [CLASS_LABEL_ROTATION_LEFT] = "ROTATION LEFT" + }; + + static const uint8_t LABELS_CNT = sizeof(LABEL_VS_NAME) / sizeof(LABEL_VS_NAME[0]); + + return (predicted_target < LABELS_CNT) ? LABEL_VS_NAME[predicted_target] : NULL; +} + +////////////////////////////////////////////////////////////////////////////// + +static const class_prediction_condition_t* get_class_condition_(uint8_t predicted_target) +{ + static const class_prediction_condition_t LABEL_VS_CONFIG[] = + { + [CLASS_LABEL_IDLE] = {0, 0.0}, + [CLASS_LABEL_UNKNOWN] = {0, 0.0}, + [CLASS_LABEL_SWIPE_LEFT] = {2, 0.8}, + [CLASS_LABEL_SWIPE_RIGHT] = {2, 0.8}, + [CLASS_LABEL_DOUBLE_SHAKE] = {2, 0.7}, + [CLASS_LABEL_DOUBLE_THUMB] = {2, 0.7}, + [CLASS_LABEL_ROTATION_RIGHT] = {2, 0.7}, + [CLASS_LABEL_ROTATION_LEFT] = {2, 0.7}, + }; + + static const uint8_t LABELS_CNT = sizeof(LABEL_VS_CONFIG) / sizeof(LABEL_VS_CONFIG[0]); + + return (predicted_target < LABELS_CNT) ? &LABEL_VS_CONFIG[predicted_target] : NULL; +} diff --git a/applications/gesture_recognition/src/inference_postprocessing.h b/applications/gesture_recognition/src/inference_postprocessing.h new file mode 100644 index 0000000..5a12740 --- /dev/null +++ b/applications/gesture_recognition/src/inference_postprocessing.h @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2021 Nordic Semiconductor ASA +* SPDX-License-Identifier: Apache-2.0 +*/ +#ifndef INFERENCE_POSTPROCESSING_H__ +#define INFERENCE_POSTPROCESSING_H__ + +#include +#include + +typedef enum +{ + CLASS_LABEL_IDLE, ///< CLASS_LABEL_IDLE + CLASS_LABEL_UNKNOWN, ///< CLASS_LABEL_UNKNOWN + CLASS_LABEL_SWIPE_RIGHT, ///< CLASS_LABEL_SWIPE_RIGHT + CLASS_LABEL_SWIPE_LEFT, ///< CLASS_LABEL_SWIPE_LEFT + CLASS_LABEL_DOUBLE_SHAKE, ///< CLASS_LABEL_DOUBLE_SHAKE + CLASS_LABEL_DOUBLE_THUMB, ///< CLASS_LABEL_DOUBLE_THUMB + CLASS_LABEL_ROTATION_RIGHT, ///< CLASS_LABEL_ROTATION_RIGHT + CLASS_LABEL_ROTATION_LEFT, /// < CLASS_LABEL_ROTATION_LEFT +} class_label_t; + +/** + * @brief Inference Result (prediction) postprocessing callback + * + * @param[in] class_label Label of the predicted class @ref class_label_t + * @param[in] probability Probability of the predicted class + * @param[in] class_name Name of predicted class, null-terminated string + * @param[in] is_raw If true the postprocessing logic was not applied to this prediction + * + */ +typedef void (*inference_postprocess_cb_t)(const class_label_t class_label, + const float probability, + const char* class_name, + const bool is_raw); + +/** + * @brief Postprocess the Neuton library RAW inference output + * + * @param[in] predicted_target Predicted target(class) + * @param[in] probability Predicted probability of the target + * @param[in] do_postprocessing If false, no postprocessing is applied and the raw prediction goes to the user callback unchanged + * @param[in] callback Inference Result (prediction) ready user callback, @ref inference_postprocess_cb_t + */ +void inference_postprocess(const uint16_t predicted_target, + const float probability, + const bool do_postprocessing, + inference_postprocess_cb_t callback); + + + +#endif /* INFERENCE_POSTPROCESSING_H__ */ diff --git a/applications/gesture_recognition/src/main.c b/applications/gesture_recognition/src/main.c new file mode 100644 index 0000000..37a9b28 --- /dev/null +++ b/applications/gesture_recognition/src/main.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include