changeset 1032:33b91584d827 Puls_Integration

New CV Pulse: The basic infrastructure for external puls measurement via Bluetooth has been added. Precondition is an OSTC with an activated central role. The OSTC will then search for a BLE device with puls measurement service. Reading data and visualization is not implemented yet.
author Ideenmodellierer
date Mon, 28 Jul 2025 18:34:45 +0200
parents cd4561c33758
children 5f66e44d69f0
files Common/CPU1-F429.ld Common/Inc/configuration.h Discovery/Inc/cv_heartbeat.h Discovery/Inc/ostc.h Discovery/Inc/tStructure.h Discovery/Inc/text_multilanguage.h Discovery/Src/base.c Discovery/Src/cv_heartbeat.c Discovery/Src/ostc.c Discovery/Src/tMenuCvOption.c Discovery/Src/tMenuEdit.c Discovery/Src/tMenuEditCvOption.c Discovery/Src/text_multilanguage.c
diffstat 13 files changed, 676 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/Common/CPU1-F429.ld	Mon Jul 28 18:32:23 2025 +0200
+++ b/Common/CPU1-F429.ld	Mon Jul 28 18:34:45 2025 +0200
@@ -208,7 +208,7 @@
     /* Define Known Address for Each Font */
     /* Flash Sector 23 is protected (bootloader font + image) => use end of sector 22 */
 
-.lower_fonts 	0x080A0000  : {
+.lower_fonts 	0x080AA000  : {
 	 *(.lower_fonts.image_data_*)
 	 
 	 *(.lower_fonts.*) 
--- a/Common/Inc/configuration.h	Mon Jul 28 18:32:23 2025 +0200
+++ b/Common/Inc/configuration.h	Mon Jul 28 18:34:45 2025 +0200
@@ -96,6 +96,9 @@
 /* Enable to have a faster transfer speed between bluetooth module and CPU */
 #define ENABLE_FAST_COMM
 
+/* Enable to have support of Polar HC10 heartbeat sensor active */
+#define ENABLE_PULSE_SENSOR_BT
+
 /* Enable RTE sleep mode debugging */
 /* #define ENABLE_SLEEP_DEBUG */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Discovery/Inc/cv_heartbeat.h	Mon Jul 28 18:34:45 2025 +0200
@@ -0,0 +1,101 @@
+///////////////////////////////////////////////////////////////////////////////
+/// -*- coding: UTF-8 -*-
+///
+/// \file   Discovery/Inc/cv_heartbeat.h
+/// \brief  Function definitions for connecting to a Polar HC10 heartbeat sensor
+/// \date   3 July 2025
+
+///////////////////////////////////////////////////////////////////////////////
+/// \par Copyright (c) 2014-2015 Heinrichs Weikamp gmbh
+///
+///     This program is free software: you can redistribute it and/or modify
+///     it under the terms of the GNU General Public License as published by
+///     the Free Software Foundation, either version 3 of the License, or
+///     (at your option) any later version.
+///
+///     This program is distributed in the hope that it will be useful,
+///     but WITHOUT ANY WARRANTY; without even the implied warranty of
+///     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+///     GNU General Public License for more details.
+///
+///     You should have received a copy of the GNU General Public License
+///     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//////////////////////////////////////////////////////////////////////////////
+
+#ifndef INC_CV_HEARTBEAT_H_
+#define INC_CV_HEARTBEAT_H_
+
+#include <stdint.h>
+
+
+#define BLUEMOD_ADDR_SIZE		(20u)		/* length of address respond */
+#define BLUEMOD_RSSI_SIZE		(5u)
+#define BLUEMOD_NAME_SIZE		(40u)
+
+void openEdit_Heartbeat(void);
+
+typedef enum
+{
+	NO_INDICATOR = 0,
+	DEVICE_INDICATOR,
+	CONNECTION_INDICATOR,
+	SERVICE_INDICATOR,
+	OK_INDICATOR,			/* module control */
+	ERROR_INDICATOR			/* module control */
+} indicatior_t;
+
+typedef enum
+{
+	BT_READ_NOTHING = 0,
+	BT_READ_DEVICE_ADDR,
+	BT_READ_DEVICE_RSSI,
+	BT_READ_DEVICE_NAME,
+	BT_READ_CON_DETAILS,
+	BT_READ_SERV_HANDLE,
+	BT_READ_SERV_START,
+	BT_READ_SERV_END,
+	BT_READ_SERV_UUID
+} readDataType_t;
+
+typedef enum
+ {
+	SENSOR_HB_OFFLINE = 0,		/* Default Status no data available  */
+	SENSOR_HB_ENABLE_BLE,
+	SENSOR_HB_CHECK_CONFIG,
+	SENSOR_HB_DISCOVER,
+	SENSOR_HB_CONNECT,
+	SENSOR_HB_SERVICES,
+	SENSOR_HB_RESTART,
+	SENSOR_HB_DETECTION_INDICATOR,		/* searching for indicators to identify data items */
+	SENSOR_HB_DETECTION_RSSI,
+	SENSOR_HB_DETECTION_NAME,
+	SENSOR_HB_DETECTION_MAN,
+	SENSOR_HB_DETECTION_UUID,
+	SENSOR_HB_FOUND,		/* A device providing the requested service was found */
+	SENSOR_HB_CONNECTED,	/* Connection to heartbeat sensor established */
+ 	SENSOR_HB_OFFLINEMODE,	/* Oflline measurement started */
+ } sensorHeartbeat_State_t;
+
+typedef struct
+{
+	uint8_t address[BLUEMOD_ADDR_SIZE];
+	uint8_t rssi[BLUEMOD_RSSI_SIZE];
+	uint8_t name[BLUEMOD_NAME_SIZE];
+} btDdeviceData_t;
+
+
+typedef struct
+{
+	uint8_t handle;
+	uint8_t start[6];
+	uint8_t end[6];
+	uint8_t uuid[50];
+} btDeviceService_t;
+
+
+
+sensorHeartbeat_State_t cv_heartbeat_getState();
+void refresh_Heartbeat(void);
+void cv_heartbeat_Control(void);
+
+#endif /* INC_CV_HEARTBEAT_H_ */
--- a/Discovery/Inc/ostc.h	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Inc/ostc.h	Mon Jul 28 18:34:45 2025 +0200
@@ -60,6 +60,10 @@
 #define DISPLAY_VERSION_LCD						(0u)
 #define DISPLAY_VERSION_NEW						(1u)
 
+#define CHUNK_SIZE				(150u)		/* the DMA will handle chunk size transfers */
+#define CHUNKS_PER_BUFFER		(3u)
+
+
 /* Exported variables --------------------------------------------------------*/
 
 extern SPI_HandleTypeDef hspiDisplay;
@@ -82,6 +86,7 @@
 void MX_SPI_Init(void);
 void MX_GPIO_Init(void);
 void MX_UART_Init(void);
+void MX_UART_BT_Init_DMA();
 uint8_t MX_UART_ButtonAdjust(uint8_t *array);
 
 void MX_SmallCPU_Reset_To_Boot(void);
@@ -104,5 +109,8 @@
 void SetDisplayVersion(uint8_t version);
 uint8_t isNewDisplay(void);
 
+uint8_t UART_getChar();
+void UART_StartDMARx();
+
 
 #endif // OSTC_H
--- a/Discovery/Inc/tStructure.h	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Inc/tStructure.h	Mon Jul 28 18:34:45 2025 +0200
@@ -76,6 +76,7 @@
 #define StIDEBUG		_MB(0,7,0,0,0)
 #define StISENINFO		_MB(0,8,0,0,0)
 #define StIPREDIVE		_MB(0,9,0,0,0)
+#define StILOGGER		_MB(0,10,0,0,0)
 
 #define StI_GoToLogbook			_MB(0,1,1,0,0)
 #define StI_GoToPlanner			_MB(0,1,2,0,0)
@@ -396,6 +397,8 @@
 #define StMOption_Timer					_MB(2,10,2,0,0)
 #define StMOption_Timer_Value			_MB(2,10,2,1,0)
 
+#define StMOption_Heartbeat 			_MB(2,10,3,0,0)
+
 /* PAGE 11 */
 #define StMPLAN		_MB(2,11,0,0,0)
 
--- a/Discovery/Inc/text_multilanguage.h	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Inc/text_multilanguage.h	Mon Jul 28 18:34:45 2025 +0200
@@ -395,6 +395,7 @@
 		TXT2BYTE_TIMEZONE,
 
 		TXT2BYTE_BUZZER,
+		TXT2BYTE_Pulse,
 
 		TXT2BYTE_END,
 };
--- a/Discovery/Src/base.c	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Src/base.c	Mon Jul 28 18:34:45 2025 +0200
@@ -501,6 +501,7 @@
             resetToFirmwareUpdate();
 
         tCCR_control();
+
         if( tComm_control() )// will stop while loop if tComm Mode started until exit from UART
         {
             createDiveSettings();
@@ -532,6 +533,8 @@
         }
         if(DoDisplayRefresh)							/* set every 100ms by timer interrupt */
         {
+        	cv_heartbeat_Control();
+
 	        DoDisplayRefresh = 0;
 
 	        updateSetpointStateUsed();
@@ -727,6 +730,13 @@
 {
 	SStateList status;
 	get_globalStateList(&status);
+
+	if((status.base != 0) && (InfoLogger_isUpdated()))
+	{
+		openInfo_Logger();
+		get_globalStateList(&status);
+	}
+
 	switch(status.base)
 	{
 	case BaseHome:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Discovery/Src/cv_heartbeat.c	Mon Jul 28 18:34:45 2025 +0200
@@ -0,0 +1,437 @@
+///////////////////////////////////////////////////////////////////////////////
+/// -*- coding: UTF-8 -*-
+///
+/// \file   Discovery/Src/cv_heartbeat.c
+/// \brief  providing functionality to connect OSTC to a Polar HC10 heartbeat sensor
+/// \author heinrichs weikamp gmbh
+/// \date   03-July-2025
+///
+/// $Id$
+///////////////////////////////////////////////////////////////////////////////
+/// \par Copyright (c) 2014-2025 Heinrichs Weikamp gmbh
+///
+///     This program is free software: you can redistribute it and/or modify
+///     it under the terms of the GNU General Public License as published by
+///     the Free Software Foundation, either version 3 of the License, or
+///     (at your option) any later version.
+///
+///     This program is distributed in the hope that it will be useful,
+///     but WITHOUT ANY WARRANTY; without even the implied warranty of
+///     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+///     GNU General Public License for more details.
+///
+///     You should have received a copy of the GNU General Public License
+///     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//////////////////////////////////////////////////////////////////////////////
+
+#include "cv_heartbeat.h"
+#include "tMenuEdit.h"
+
+#include "gfx_fonts.h"
+#include "tHome.h"
+#include "ostc.h"
+#include "tComm.h"
+#include "tInfoLogger.h"
+
+static sensorHeartbeat_State_t heartbeatState = SENSOR_HB_OFFLINE;
+
+static uint8_t OnAction_Heartbeat(uint32_t editId, uint8_t blockNumber, uint8_t digitNumber, uint8_t digitContent, uint8_t action);
+static uint32_t startDetection_ms;
+
+#define MAX_BT_DEVICE 10		/* max number of device which may be handled */
+static btDdeviceData_t btDeviceList[MAX_BT_DEVICE];
+static btDeviceService_t curDeviceService[10];
+static uint8_t curServiceIndex = 0;
+static uint8_t curBtIndex = 0;
+static uint8_t connHandle = ' ';
+
+static indicatior_t checkIndicators(uint8_t* pdata)
+{
+#if 0
+	static uint8_t foundRSSI = 0;
+	static uint8_t foundNAME = 0;
+	static uint8_t foundMNF = 0;
+	static uint8_t foundUUID = 0;
+	static uint8_t foundOK = 0;
+#endif
+	indicatior_t ret = NO_INDICATOR;
+
+	if(strcmp((char*)pdata,"+UBTD:") == 0)
+	{
+		ret = DEVICE_INDICATOR;
+	}
+	else if(strcmp((char*)pdata,"+UUBTACLC:") == 0)
+	{
+		ret = CONNECTION_INDICATOR;
+	}
+	else if(strcmp((char*)pdata,"+UBTGDP:") == 0)
+	{
+		ret = SERVICE_INDICATOR;
+	}
+
+	return ret;
+}
+
+static void handleOK()
+{
+	switch(heartbeatState)
+	{
+		case SENSOR_HB_ENABLE_BLE:	heartbeatState = SENSOR_HB_CHECK_CONFIG;
+							break;
+		case SENSOR_HB_CHECK_CONFIG: heartbeatState = SENSOR_HB_DISCOVER;
+							break;
+		case SENSOR_HB_RESTART: 	heartbeatState = SENSOR_HB_OFFLINE;
+							break;
+		case SENSOR_HB_DISCOVER: heartbeatState = SENSOR_HB_CONNECT;
+							break;
+		case SENSOR_HB_SERVICES: heartbeatState = SENSOR_HB_OFFLINE;
+							break;
+		default:
+							break;
+	}
+}
+
+static void handleERROR()
+{
+
+}
+
+static uint8_t getDeviceList()
+{
+	static uint8_t firstDevice = 1;
+	static uint8_t curLine[MAX_CHAR_PER_LINE];			/* holds complete line and is used for logging */
+	static uint8_t curLineIndex = 0;
+	static uint8_t parameter[40];		/* content of the parameter in read state */
+	static uint8_t writeIndex = 0;
+	static uint8_t complete = 0;
+
+	static readDataType_t readType = BT_READ_NOTHING;
+
+	char text[40];
+	uint8_t data = 0;
+	data = UART_getChar();
+
+	while((data != 0) && (complete == 0))
+	{
+		if(curLineIndex == MAX_CHAR_PER_LINE)		/* avoid overflow */
+		{
+			InfoLogger_writeLine(curLine,curLineIndex,0);
+			memset(curLine,0,sizeof(curLine));
+			curLineIndex = 0;
+		}
+		if((data == '\r') || (data == '\n'))
+		{
+			if(curLineIndex > 0)
+			{
+				InfoLogger_writeLine(curLine,curLineIndex,0);
+				if(strcmp((char*)curLine,"OK") == 0)
+				{
+					handleOK();
+				}
+				else
+				{
+					if(strcmp((char*)curLine,"ERROR") == 0)
+					{
+						handleERROR();
+					}
+				}
+			}
+			switch(readType)
+			{
+				case BT_READ_DEVICE_NAME: 	if(writeIndex < BLUEMOD_NAME_SIZE)
+											{
+												memcpy (btDeviceList[curBtIndex].name, parameter, writeIndex);
+											}
+								break;
+				case BT_READ_SERV_UUID: 	if(writeIndex < 50)
+											{
+												memcpy(curDeviceService[curServiceIndex].uuid, parameter, writeIndex);
+											}
+											curServiceIndex++;
+								break;
+				default:
+					break;
+			}
+			curLineIndex = 0;
+			writeIndex = 0;
+			memset(curLine,0,sizeof(curLine));
+			readType = BT_READ_NOTHING;
+		}
+		else
+		{
+			if(curLineIndex < MAX_CHAR_PER_LINE) curLine[curLineIndex++] = data;
+
+			if(data == ':')
+			{
+				switch(checkIndicators(curLine))
+				{
+					case DEVICE_INDICATOR: 		readType = BT_READ_DEVICE_ADDR;
+						break;
+					case CONNECTION_INDICATOR:  readType = BT_READ_CON_DETAILS;
+						break;
+					case SERVICE_INDICATOR: 	readType = BT_READ_SERV_HANDLE;
+						break;
+					default:
+						break;
+				}
+				writeIndex = 0;
+				memset(parameter,0,sizeof(parameter));
+			}
+			else
+			{
+				if(data == ',')					/* parameter end */
+				{
+					switch(readType)
+					{
+						case BT_READ_DEVICE_ADDR:  if(writeIndex < BLUEMOD_ADDR_SIZE-1)
+													{
+														if(firstDevice)
+														{
+															firstDevice = 0;
+														}
+														else
+														{
+															curBtIndex++;
+														}
+														parameter[writeIndex-1] = 0;	/*remove 'p' from address */
+														strcpy((char*)btDeviceList[curBtIndex].address, (char*)parameter);
+													}
+													readType = BT_READ_DEVICE_RSSI;
+								break;
+						case BT_READ_DEVICE_RSSI:	if(writeIndex < BLUEMOD_RSSI_SIZE-1)
+													{
+														strcpy((char*)btDeviceList[curBtIndex].rssi, (char*)parameter);
+													}
+													readType = BT_READ_DEVICE_NAME;
+								break;
+						case BT_READ_DEVICE_NAME: 	if(writeIndex < BLUEMOD_NAME_SIZE-1)
+													{
+														memcpy(btDeviceList[curBtIndex].name, parameter, writeIndex);
+													}
+													readType = BT_READ_NOTHING;
+										break;
+						case BT_READ_CON_DETAILS:	connHandle = parameter[0];
+													heartbeatState = SENSOR_HB_SERVICES;
+													readType = BT_READ_NOTHING;
+										break;
+						case BT_READ_SERV_HANDLE:	curDeviceService[curServiceIndex].handle = parameter[0];
+													readType = BT_READ_SERV_START;
+										break;
+						case BT_READ_SERV_START: 	if(writeIndex < 6)
+													{
+														memcpy(curDeviceService[curServiceIndex].start, parameter, writeIndex);
+													}
+													readType = BT_READ_SERV_END;
+										break;
+						case BT_READ_SERV_END: 		if(writeIndex < 6)
+													{
+														memcpy(curDeviceService[curServiceIndex].end, parameter, writeIndex);
+													}
+													readType = BT_READ_SERV_UUID;
+										break;
+
+						default:					readType = BT_READ_NOTHING;
+							break;
+					}
+					writeIndex = 0;
+					memset(parameter,0 , sizeof(parameter));
+				}
+				else
+				{
+				//	if(readType != BT_READ_NOTHING)
+					{
+						parameter[writeIndex++] = data;
+					}
+				}
+			}
+		}
+		data = UART_getChar();
+	}
+	return complete;
+}
+
+sensorHeartbeat_State_t cv_heartbeat_getState()
+{
+	return heartbeatState;
+}
+
+void openEdit_Heartbeat(void)
+{
+    SSettings *settings = settingsGetPointer();
+
+    char text[32];
+    snprintf(text, 32, "\001%c%c", TXT_2BYTE, TXT2BYTE_Pulse);
+    write_topline(text);
+
+    set_globalState(StMOption_Heartbeat);
+    resetMenuEdit(CLUT_MenuPageCvOption);
+
+    snprintf(text, 32, "%c%c", TXT_2BYTE, TXT2BYTE_SensorDetect);
+    write_field_button(StMOption_Heartbeat, 30, 299, ME_Y_LINE1, &FontT48, text);
+
+    write_buttonTextline(TXT2BYTE_ButtonMinus, TXT2BYTE_ButtonEnter, TXT2BYTE_ButtonPlus);
+
+    setEvent(StMOption_Heartbeat, (uint32_t)OnAction_Heartbeat);
+}
+
+static uint8_t OnAction_Heartbeat(uint32_t editId, uint8_t blockNumber, uint8_t digitNumber, uint8_t digitContent, uint8_t action)
+{
+	switch(heartbeatState)
+	{
+		case SENSOR_HB_OFFLINE:
+				HAL_UART_AbortReceive_IT(&UartHandle);
+				MX_UART_BT_Init_DMA();
+				UART_StartDMARx();
+				heartbeatState = SENSOR_HB_ENABLE_BLE;
+				startDetection_ms = HAL_GetTick();
+				curBtIndex = 0;
+				memset(btDeviceList, 0, sizeof(btDeviceList));
+			break;
+
+		default:
+			break;
+	}
+    return UNSPECIFIC_RETURN;
+}
+
+void cv_heartbeat_Control(void)
+{
+	static uint8_t action = 0;
+	static uint8_t retry = 0;
+	static uint8_t lastState = 0;
+	static uint8_t devicesIndex = 0;
+
+
+	char cmd[50];
+
+	cmd[0] = 0;
+
+	getDeviceList();
+
+	if(action == 3)
+	{
+		action = 0;
+		switch(heartbeatState)
+		{
+			case SENSOR_HB_ENABLE_BLE:	HAL_Delay(1000);
+										snprintf(cmd, sizeof(cmd), "+++" ); //"AT+UBTD=2,1,5000\r\n"
+										InfoLogger_writeLine((uint8_t*)cmd,3,1);
+										HAL_UART_Transmit(&UartHandle, (uint8_t*)cmd, 3, 5000);
+										HAL_Delay(1000);
+										cmd[0] = 0;
+				break;
+			case SENSOR_HB_CHECK_CONFIG:  snprintf(cmd, sizeof(cmd), "AT+UBTCFG=2\r\n" );	// AT+UBTLE?
+
+#if 0
+				if(settingsGetPointer()->dive_mode == DIVEMODE_OC)
+				{
+					snprintf(cmd, sizeof(cmd), "AT+UBTLE=2\r\n" ); //+UBTLE=1 //"AT+UBTD=2,1,5000\r\n"
+				}
+				else
+				{
+					snprintf(cmd, sizeof(cmd), "AT+UBTLE=3\r\n" ); //+UBTLE=1 //"AT+UBTD=2,1,5000\r\n"
+				}
+#endif
+				break;
+			case SENSOR_HB_DISCOVER:	if(lastState != SENSOR_HB_DISCOVER)
+										{
+											snprintf(cmd, sizeof(cmd), "AT+UBTD=2,1\r\n" ); //+UBTLE=1 //"AT+UBTD=2,1,5000\r\n"
+											devicesIndex = 0;
+										}
+
+										//snprintf(cmd, sizeof(cmd), "AT&W\r\n" ); //  AT+UBTD=2,1\r\n "AT+UBTD=2,1,5000\r\n"
+				break;
+#if 0
+			case SENSOR_HB_RESTART:	snprintf(cmd, sizeof(cmd), "AT+UBTD=2,1\r\n" ); //+UBTLE=1 //"AT+UBTD=2,1,5000\r\n"
+
+									//	snprintf(cmd, sizeof(cmd), "AT+CPWROFF\r\n" ); //  AT+UBTD=2,1\r\n "AT+UBTD=2,1,5000\r\n"
+				break;
+#endif
+			case SENSOR_HB_CONNECT:		if(curBtIndex != devicesIndex)
+										{
+											snprintf(cmd, sizeof(cmd), "AT+UBTACLC=%s\r\n",btDeviceList[devicesIndex].address);
+											devicesIndex++;
+										}
+				break;
+			case SENSOR_HB_SERVICES:	if((connHandle >= '0') && (connHandle <= '9') && (lastState != SENSOR_HB_SERVICES))
+										{
+											snprintf(cmd, sizeof(cmd), "AT+UBTGDP=0%c\r\n",connHandle);
+										}
+										else
+										{
+											heartbeatState = SENSOR_HB_OFFLINE;
+										}
+				break;
+
+			default:
+				break;
+		}
+		if(cmd[0] != 0)
+		{
+			{
+				InfoLogger_writeLine((uint8_t*)cmd,strlen(cmd),1);
+				HAL_UART_Transmit(&UartHandle, (uint8_t*)cmd, strlen(cmd), 5000);
+				retry++;
+				if(retry == 3)
+				{
+					heartbeatState = SENSOR_HB_OFFLINE;
+				}
+			}
+		}
+		if(lastState != heartbeatState)
+		{
+			lastState = heartbeatState;
+			retry = 0;
+		}
+	}
+	else
+	{
+		action++;
+	}
+}
+void refresh_Heartbeat(void)
+{
+	char text[32];
+	uint8_t index = 0;
+
+	snprintf(text, 32, "\001%c%c", TXT_2BYTE, TXT2BYTE_Pulse);
+	write_topline(text);
+
+	switch(heartbeatState)
+	{
+		case SENSOR_HB_OFFLINE:
+		default:							snprintf(text, 32, "%c%c", TXT_2BYTE, TXT2BYTE_SensorDetect);
+											write_label_var(30, 299, ME_Y_LINE1, &FontT48, text);
+
+	    									if(curBtIndex > 0)
+	    									{
+	    										while((index < curBtIndex) && (index < 3))
+	    										{
+													snprintf(text, 40, "%s", btDeviceList[index].address);
+													write_label_var(  30, 300,  ME_Y_LINE3 + (index * ME_Y_LINE_STEP), &FontT48, text);
+													index++;
+	    										}
+	    									}
+			break;
+		case SENSOR_HB_ENABLE_BLE:			snprintf(text, 32, "Activate BLE");
+											write_label_var(  30, 300, ME_Y_LINE1, &FontT48, text);
+			break;
+		case SENSOR_HB_DISCOVER:
+	    									snprintf(text, 32, "Searching");
+	    									write_label_var(  30, 300, ME_Y_LINE1, &FontT48, text);
+
+	    									if(curBtIndex > 0)
+	    									{
+	    										while((index < curBtIndex) && (index < 4))
+	    										{
+													snprintf(text, 40, "%s", btDeviceList[index].address);
+													write_label_var(  30, 300,  ME_Y_LINE2 + (index * ME_Y_LINE_STEP), &FontT48, text);
+													index++;
+	    										}
+	    									}
+			break;
+	}
+
+    write_buttonTextline(TXT2BYTE_ButtonMinus, TXT2BYTE_ButtonEnter, TXT2BYTE_ButtonPlus);
+}
+
--- a/Discovery/Src/ostc.c	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Src/ostc.c	Mon Jul 28 18:34:45 2025 +0200
@@ -27,8 +27,10 @@
 //////////////////////////////////////////////////////////////////////////////
 
 /* Includes ------------------------------------------------------------------*/
+#include "configuration.h"
 #include "ostc.h"
 #include "stm32f4xx_hal.h"
+#include "cv_heartbeat.h"
 
 #ifndef BOOTLOADER_STANDALONE
 #include "tCCR.h"
@@ -54,6 +56,11 @@
 
 /* Private variables with external access via get_xxx() function -------------*/
 static uint8_t	hardwareDisplay = 0;		//< either OSTC4 LCD (=0) or new Screen (=1)
+
+static uint16_t rxBufRead = 0;
+static uint16_t rxBufWrite = 0;
+static uint8_t rxBufferUart[CHUNK_SIZE * CHUNKS_PER_BUFFER];		/* The complete buffer has a X * chunk size to allow variations in buffer read time */
+
 /* Private function prototypes -----------------------------------------------*/
 
 /* Exported functions --------------------------------------------------------*/
@@ -380,6 +387,64 @@
 #endif
 }
 
+static DMA_HandleTypeDef  hdma_uart_BT_rx;
+
+void MX_UART_BT_Init_DMA()
+{
+
+	__DMA2_CLK_ENABLE();
+	 __HAL_RCC_DMA2_CLK_ENABLE();
+
+	hdma_uart_BT_rx.Instance = DMA2_Stream2;
+	hdma_uart_BT_rx.Init.Channel = DMA_CHANNEL_4;
+	hdma_uart_BT_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
+	hdma_uart_BT_rx.Init.PeriphInc = DMA_PINC_DISABLE;
+	hdma_uart_BT_rx.Init.MemInc = DMA_MINC_ENABLE;
+	hdma_uart_BT_rx.Init.PeriphDataAlignment = DMA_MDATAALIGN_BYTE;
+	hdma_uart_BT_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
+	hdma_uart_BT_rx.Init.Mode = DMA_NORMAL;
+	hdma_uart_BT_rx.Init.Priority = DMA_PRIORITY_LOW;
+	hdma_uart_BT_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
+	HAL_DMA_Init(&hdma_uart_BT_rx);
+
+	__HAL_LINKDMA(&UartHandle, hdmarx, hdma_uart_BT_rx);
+
+	HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
+	HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
+}
+
+
+uint8_t UART_getChar()
+{
+	uint8_t retChar = 0;
+
+	if((rxBufRead != rxBufWrite) && (rxBufferUart[rxBufRead] != 0))
+	{
+		retChar = rxBufferUart[rxBufRead];
+		rxBufferUart[rxBufRead++] = 0;
+		if(rxBufRead == CHUNK_SIZE * CHUNKS_PER_BUFFER)
+		{
+			rxBufRead = 0;
+		}
+	}
+	return retChar;
+}
+
+void UART_StartDMARx()
+{
+	HAL_UART_Receive_DMA (&UartHandle, &rxBufferUart[rxBufWrite], CHUNK_SIZE);
+	rxBufWrite += CHUNK_SIZE;
+	if(rxBufWrite >= CHUNK_SIZE * CHUNKS_PER_BUFFER)
+	{
+		rxBufWrite = 0;
+	}
+}
+
+void DMA2_Stream2_IRQHandler(void)
+{
+  HAL_DMA_IRQHandler(&hdma_uart_BT_rx);
+}
+
 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
 {
     if(huart == &UartHandle)
@@ -390,7 +455,20 @@
 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
 {
     if(huart == &UartHandle)
-        UartReady = SET;
+    {
+#ifdef ENABLE_PULSE_SENSOR_BT
+    	if(cv_heartbeat_getState() != SENSOR_HB_OFFLINE)
+    	{
+    		UART_StartDMARx();
+    	}
+    	else
+    	{
+    		UartReady = SET;
+    	}
+#else
+    	UartReady = SET;
+#endif
+    }
     else
     if(huart == &UartIR_HUD_Handle)
     {
--- a/Discovery/Src/tMenuCvOption.c	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Src/tMenuCvOption.c	Mon Jul 28 18:34:45 2025 +0200
@@ -76,6 +76,15 @@
     }
     nextline(text,&textPointer);
 
+#ifdef ENABLE_PULSE_SENSOR_BT
+    if (line == 0 || line == 3)
+    {
+   		textPointer += snprintf(&text[textPointer], 21, "%c%c", TXT_2BYTE, TXT2BYTE_Pulse);
+    }
+    nextline(text,&textPointer);
+
+#endif
+
     return StMOption;
 }
 void tMCvOption_checkLineStatus(void)
--- a/Discovery/Src/tMenuEdit.c	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Src/tMenuEdit.c	Mon Jul 28 18:34:45 2025 +0200
@@ -42,6 +42,7 @@
 #include "tMenuEditSystem.h"
 #include "tMenuEditXtra.h"
 #include "tMenuEditCustom.h"
+#include "cv_heartbeat.h"
 
 /* Private types -------------------------------------------------------------*/
 #define TEXTSIZE 16
@@ -256,6 +257,11 @@
 	 	 case StMCustom3_CViewSelection5:
 	 	 case StMCustom3_CViewSelection6: refreshFct = CustomviewDivemode_refresh;
 	 	 	 break;
+#ifdef ENABLE_PULSE_SENSOR_BT
+	 	case (StMOption_Heartbeat):		refreshFct = refresh_Heartbeat;
+	 		break;
+#endif
+
 #ifdef ENABLE_MOTION_CONTROL
 	 	 case (StMCustom5_CViewPortCalib & MaskFieldDigit):
 	 	 case StMCustom5_CViewPortLayout:
--- a/Discovery/Src/tMenuEditCvOption.c	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Src/tMenuEditCvOption.c	Mon Jul 28 18:34:45 2025 +0200
@@ -35,6 +35,8 @@
 #include "tMenuEdit.h"
 #include "tHome.h"
 
+#include "cv_heartbeat.h"
+
 /* Private function prototypes -----------------------------------------------*/
 static void openEdit_Timer(void);
 void openEdit_Compass(void);
@@ -56,13 +58,13 @@
     switch(line)
     {
 		case 1:
-		default:
-			resetMenuEdit(CLUT_MenuPageHardware);
-			openEdit_Compass();
-		break;
-		case 2:
-			openEdit_Timer();
-		break;
+		default:	resetMenuEdit(CLUT_MenuPageHardware);
+					openEdit_Compass();
+			break;
+		case 2:		openEdit_Timer();
+			break;
+		case 3: 	openEdit_Heartbeat();
+			break;
     }
 }
 
--- a/Discovery/Src/text_multilanguage.c	Mon Jul 28 18:32:23 2025 +0200
+++ b/Discovery/Src/text_multilanguage.c	Mon Jul 28 18:34:45 2025 +0200
@@ -2005,6 +2005,14 @@
 static uint8_t text_IT_Reverse[] = "Invertire";
 static uint8_t text_ES_Reverse[] = "Invertir";
 
+static uint8_t text_EN_Pulse[] = "Heartbeat sensor (BT)";
+static uint8_t text_DE_Pulse[] = "Pulssensor (BT)";
+static uint8_t text_FR_Pulse[] = "";
+static uint8_t text_IT_Pulse[] = "";
+static uint8_t text_ES_Pulse[] = "";
+
+
+
 /* Lookup Table -------------------------------------------------------------*/
 
 const tText text_array[] =
@@ -2319,6 +2327,7 @@
  	{(uint8_t)TXT2BYTE_Log, 			{text_EN_Log, text_DE_Log, text_FR_Log, text_IT_Log, text_ES_Log}},
 	{(uint8_t)TXT2BYTE_Reverse, 		{text_EN_Reverse, text_DE_Reverse, text_FR_Reverse, text_IT_Reverse, text_ES_Reverse}},
 
+	{(uint8_t)TXT2BYTE_Pulse, 			{text_EN_Pulse, text_DE_Pulse, text_FR_Pulse, text_IT_Pulse, text_ES_Pulse}},
 
 
 };