initial commit

This commit is contained in:
jhChun
2026-04-08 16:58:54 +09:00
commit 82e33d8bf9
2578 changed files with 1590432 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_spi.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "boards.h"
#include "nrf_log.h"
#include "LED_Parse.h"
#include "app_timer.h"
#include "measurements.h"
#include "main.h"
//#include "fstorage.h"
#include "mcp4725_i2c.h" //VGA
#include "ir_i2c.h" //IR
#include "debug_print.h"
//#define LED_S1 0x50 //VGA LED1~5
//#define LED_S2 0x50
//#define LED_S3 0x51
//#define LED_S4 0x52
//#define LED_S5 0x53
//#define LED_S6 0x51 //VGA
//#define LED_S7 0x54
//#define LED_S8 0x55
//#define LED_S9 0x56
//#define LED_S10 0x57 //LED 46~50
#define LED_S0 0x50
#define LED_S1 0x51
#define LED_S2 0x52
#define LED_S3 0x53
#define LED_S4 0x54
#define LED_S5 0x55
#define LED_S6 0x56
#define LED_S7 0x57
uint8_t LED_READ_ROM(uint8_t led_index)
{
// uint32_t err_code = NRF_SUCCESS;
uint8_t addr=0xf0;
uint8_t r_index;
r_index = (led_index)%6;
uint8_t r_data[10];
uint8_t r_value=0;
switch(led_index) {
case 0: case 1: case 2: case 3: case 4: case 5:
r_value = ir_command_read(LED_S0, addr + r_index, r_data);
break;
case 6: case 7: case 8: case 9: case 10: case 11:
r_value = ir_command_read(LED_S1, addr + r_index, r_data);
break;
case 12: case 13: case 14: case 15: case 16: case 17:
r_value = ir_command_read(LED_S2, addr + r_index, r_data);
break;
case 18: case 19: case 20: case 21: case 22: case 23:
r_value = ir_command_read(LED_S3, addr + r_index, r_data);
break;
case 24: case 25: case 26: case 27: case 28: case 29:
r_value = ir_command_read(LED_S4, addr + r_index, r_data);
break;
case 30: case 31: case 32: case 33: case 34: case 35:
r_value = ir_command_read(LED_S5, addr + r_index, r_data);
break;
case 36: case 37: case 38: case 39: case 40: case 41:
r_value = ir_command_read(LED_S6, addr + r_index, r_data);
break;
case 42: case 43: case 44: case 45: case 46: case 47:
r_value = ir_command_read(LED_S7, addr + r_index, r_data);
break;
default:
DBG_PRINTF("Invalid LED index\r\n");
// err_code = NRF_ERROR_NOT_FOUND;
break;
}
return r_value;
}
ret_code_t LED_WRITE_ROM(uint8_t led_index, int16_t led_power)
{
uint32_t err_code = NRF_SUCCESS;
uint8_t addr=0xf0;
uint8_t r_index;
r_index = (led_index)%6;
switch(led_index) {
case 0: case 1: case 2: case 3: case 4: case 5:
ir_command_write(LED_S0, addr + r_index, led_power);
break;
case 6: case 7: case 8: case 9: case 10: case 11:
ir_command_write(LED_S1, addr + r_index, led_power);
break;
case 12: case 13: case 14: case 15: case 16: case 17:
ir_command_write(LED_S2, addr + r_index, led_power);
break;
case 18: case 19: case 20: case 21: case 22: case 23:
ir_command_write(LED_S3, addr + r_index, led_power);
break;
case 24: case 25: case 26: case 27: case 28: case 29:
ir_command_write(LED_S4, addr + r_index, led_power);
break;
case 30: case 31: case 32: case 33: case 34: case 35:
ir_command_write(LED_S5, addr + r_index, led_power);
break;
case 36: case 37: case 38: case 39: case 40: case 41:
ir_command_write(LED_S6, addr + r_index, led_power);
break;
case 42: case 43: case 44: case 45: case 46: case 47:
ir_command_write(LED_S7, addr + r_index, led_power);
break;
default:
DBG_PRINTF("Invalid LED index\r\n");
err_code = NRF_ERROR_NOT_FOUND;
break;
}
DBG_PRINTF("write\r\n");
return err_code;
}

View File

@@ -0,0 +1,26 @@
#ifndef _LED_Parse_H_
#define _LED_Parse_H_
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "app_timer.h"
#include "measurements.h"
#include "main.h"
//#include "fstorage.h"
#include "mcp4725_i2c.h" //VGA
#include "ir_i2c.h" //IR
uint8_t LED_READ_ROM(uint8_t led_index);
ret_code_t LED_WRITE_ROM(uint8_t led_index, int16_t led_power);
#endif /* PARSE */

View File

@@ -0,0 +1,16 @@
/*******************************************************************************
* @file ad5272_i2c.h
* @brief AD5272 Digital Potentiometer Stub Header
* @note Stub for MINIMAL_BOOT mode - functions not implemented
******************************************************************************/
#ifndef AD5272_I2C_H
#define AD5272_I2C_H
#include <stdint.h>
/* Stub functions - do nothing in MINIMAL mode */
static inline void ad5272_i2c_init(void) { }
static inline void ad5272_normal_mode(void) { }
#endif /* AD5272_I2C_H */

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* @file ada2200_spi.c
* @author CandyPops Co.
* @version V1.0.1
* @date 2022-09-05
* @brief ADA2200 Lock-in Amplifier SPI driver (uses shared SPI2 bus)
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include "nrf.h"
#include "nrf_gpio.h"
#include "app_error.h"
#include "boards.h"
#include "nrf_delay.h"
#include "nrf_log.h"
#include "ada2200_spi.h"
#include "spi2_bus.h" /* Shared SPI2 bus */
#include "debug_print.h"
/* Use CS pin from shared bus definition */
#define ADA2200_CS_PIN SPI2_CS_ADA2200
//static uint8_t ada2200_startR[] ={ 0x00, 0x00, 0x81 }; /* {addr 16bit, data}, Reset for Defaults */
static uint8_t ada2200_start0[] ={ 0x00, 0x00, 0x18 }; /* {addr 16bit, data}, Set SDIO input only, Activate SDO */
static uint8_t ada2200_start1[] ={ 0x00, 0x2B, 0x06 }; /* {addr 16bit, data}, Clock Configuration */
static uint8_t ada2200_start2[] ={ 0x00, 0x2A, 0x18 }; /* {addr 16bit, data}, Enable Mixer, Select SDO output for Pin 13, OFF RCLK. */
static uint8_t ada2200_start3[] ={ 0x00, 0x29, 0x23 }; /* 0x27 {addr 16bit, data}, Disable SYNCO output, Select SYNCO edge location (Sync timing adjustment) */
static uint8_t ada2200_start4[] ={ 0x00, 0x2C, 0x01 }; /* {addr 16bit, data}, Enable RCLK output */
static uint8_t ada2200_stop0[] ={ 0x00, 0x00, 0x18 }; /* {addr 16bit, data}, Set SDIO input only, Activate SDO */
static uint8_t ada2200_stop1[] ={ 0x00, 0x2B, 0x06 }; /* {addr 16bit, data}, Clock Configuration */
static uint8_t ada2200_stop2[] ={ 0x00, 0x2A, 0x10 }; /* {addr 16bit, data}, Enable Mixer, Select SDO output for Pin 13, OFF RCLK. */
static uint8_t ada2200_stop3[] ={ 0x00, 0x29, 0x01 }; /* 0x07 {addr 16bit, data}, Disable SYNCO output, Select SYNCO edge location (Sync timing adjustment) */
static uint8_t ada2200_stop4[] ={ 0x00, 0x2C, 0x00 }; /* {addr 16bit, data}, Enable RCLK output */
static uint8_t m_tx_buf[3]; /**< TX buffer. */
static uint8_t m_length = sizeof(m_tx_buf); /**< Transfer length. */
void ada2200_spi_write(const void * data, size_t size)
{
/* Ensure bus is initialized */
if (!spi2_bus_is_initialized()) {
DBG_PRINTF("[ADA] auto-init SPI2\r\n");
spi2_bus_init();
}
memcpy(m_tx_buf, data, size);
/* Use shared SPI2 bus with manual CS control */
nrf_gpio_pin_clear(ADA2200_CS_PIN);
spi2_bus_transfer(m_tx_buf, size, NULL, 0);
nrf_gpio_pin_set(ADA2200_CS_PIN);
}
extern void ada2200_start(void)
{
//ada2200_spi_write(ada2200_startR, m_length);
DBG_PRINTF("[ADA] start...\r\n");
ada2200_spi_write(ada2200_start0, m_length);
DBG_PRINTF("[ADA] cmd0 OK\r\n");
nrf_delay_us(2);
ada2200_spi_write(ada2200_start1, m_length);
nrf_delay_us(2);
ada2200_spi_write(ada2200_start2, m_length);
nrf_delay_us(2);
ada2200_spi_write(ada2200_start3, m_length);
nrf_delay_us(2);
ada2200_spi_write(ada2200_start4, m_length);
nrf_delay_us(2);
}
extern void ada2200_stop(void)
{
ada2200_spi_write(ada2200_stop0, m_length);
nrf_delay_us(2);
ada2200_spi_write(ada2200_stop1, m_length);
nrf_delay_us(2);
ada2200_spi_write(ada2200_stop2, m_length);
nrf_delay_us(2);
ada2200_spi_write(ada2200_stop3, m_length);
nrf_delay_us(2);
ada2200_spi_write(ada2200_stop4, m_length);
nrf_delay_us(2);
}
extern void ada2200_init(void)
{
DBG_PRINTF("[ADA] init...\r\n");
/* Initialize shared SPI2 bus (handles already-initialized case) */
ret_code_t err = spi2_bus_init();
if (err != NRF_SUCCESS) {
APP_ERROR_CHECK(err);
}
DBG_PRINTF("[ADA] init OK\r\n");
/* CS pin is configured by spi2_bus_init() */
}
extern void ada2200_uninit(void)
{
/* Don't uninit the shared bus - other devices may be using it */
/* Just ensure CS is deasserted */
nrf_gpio_pin_set(ADA2200_CS_PIN);
}

View File

@@ -0,0 +1,25 @@
/*******************************************************************************
* @file ada2200_spi.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _ADA2200_SPI_H_
#define _ADA2200_SPI_H_
#define SPI_INSTANCE 2 /**< SPI instance index. */
#define SPI_MOSI_PIN 16
#define SPI_MISO_PIN 15 /* Not Used */
#define SPI_SCLK_PIN 14
#define SPI_CS_PIN 13
extern void ada2200_start(void);
extern void ada2200_stop(void);
extern void ada2200_init(void);
extern void ada2200_uninit(void);
#endif //_ADA2200_SPI_H_

View File

@@ -0,0 +1,317 @@
/*******************************************************************************
* @file battery_saadc.c
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#include "sdk_common.h"
#include <stdint.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_timer.h"
#include "ble_nus.h"
#include "nrf_log.h"
#include "main.h"
#include "app_timer.h"
//#include "fstorage.h"
#include "battery_saadc.h"
#include "main_timer.h"
#include "meas_pd_48.h"
#include <cmd_parse.h>
#include "debug_print.h"
#define BATTERY_REF_VOLTAGE_IN_MILLIVOLTS 600 /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
#define BATTERY_PRE_SCALING_COMPENSATION 6 /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
#define BATTERY_ADC_RES_10BITS 1023 /**< Maximum digital value for 10-bit ADC conversion. */
//#define PRESSURE_RESULT_IN_MILLI_VOLTS(adc) ((adc * 3600) / 1023)
#define PRESSURE_OFFSET_DEFAULT 0 // Pressure offset (adjust for calibration)
#define MV_PER_ADC_STEP 805 // Approx 0.805mV per 1 LSB (nRF 12bit + scaling)
/**@brief Macro to convert the result of ADC conversion in millivolts.
*
* @param[in] ADC_VALUE ADC result.
*
* @retval Result converted to millivolts.
*/
#define BATTERY_RESULT_IN_MILLI_VOLTS(ADC_VALUE)\
((((ADC_VALUE) * BATTERY_REF_VOLTAGE_IN_MILLIVOLTS) / BATTERY_ADC_RES_10BITS) * BATTERY_PRE_SCALING_COMPENSATION)
static nrf_saadc_value_t adc_bufs[2];
static int16_t pressure_adc_buf[2]; //cj add 25/11/19
static uint16_t convert_adc_to_mV(int16_t raw_adc); //cj add 25/11/19
APP_TIMER_DEF(m_battery_loop_timer_id);
#define BATTERY_LOOP_INTERVAL 5000
bool low_battery_check = false;
extern bool info4; //cmd_parse
// cj add edit 25/11/24
extern volatile uint16_t info_p1;
extern volatile uint16_t info_p2;
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
extern bool go_device_power_off;
extern volatile bool processing;
extern which_cmd_t cmd_type_t;
extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ;
extern volatile uint16_t info_batt; //48_c
extern bool go_temp; //
extern bool go_batt; //cmd_parse
extern bool motion_raw_data_enabled ;
extern bool ble_got_new_data;
extern bool motion_data_once ;
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static uint16_t convert_adc_to_mV(int16_t raw_adc)
{
if (raw_adc < 0)
raw_adc = 0;
int32_t mv = (int32_t)raw_adc * MV_PER_ADC_STEP; // ?: 805 uV
mv /= 1000;
/* Clamp output to valid range 0~3500mV */
if (mv < 0)
mv = 0;
if (mv > 3500)
mv = 3500;
return (uint16_t)mv;
}
void pressure_all_event_handler(nrf_drv_saadc_evt_t const * p_event)
{
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
int16_t p1_adc = p_event->data.done.p_buffer[0]; // AIN7
int16_t p2_adc = p_event->data.done.p_buffer[1]; // AIN4
uint16_t p1_mV = convert_adc_to_mV(p1_adc);
uint16_t p2_mV = convert_adc_to_mV(p2_adc);
/* Update info_p1/info_p2 when in PD Full mode (info4=true) */
if(info4 == true)
{
info_p1 = p1_mV;
info_p2 = p2_mV;
}
// Re-buffer
APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 2));
// uninit
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
nrf_drv_saadc_channel_uninit(1);
// UART send
if(cmd_type_t == CMD_UART)
{
DBG_PRINTF("P1:%d P2:%d\r\n", p1_mV, p2_mV);
}
else if(cmd_type_t == CMD_BLE && info4 == false)
{
DBG_PRINTF("P1:%d P2:%d\r\n", p1_mV, p2_mV);
// uint16_t len = sprintf((char*)ble_bin_buffer,
// "rpn:%04x,%04x", p1_mV, p2_mV);
uint16_t result_data[2];
result_data[0] = p1_mV;
result_data[1] = p2_mV;
format_data(ble_bin_buffer, "rpn:", result_data,2);
binary_tx_handler(ble_bin_buffer,4);
}
}
}
void battery_event_handler( nrf_drv_saadc_evt_t const * p_event )
{
static uint8_t low_battery_cnt = 0;
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
nrf_saadc_value_t register_val = 0;
uint16_t batt_lvl_in_milli_volt_0 = 0;
uint16_t batt_lvl_in_milli_volt_1 = 0;
uint32_t err_code = 0;
register_val = p_event->data.done.p_buffer[0];
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 1);
APP_ERROR_CHECK(err_code);
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
batt_lvl_in_milli_volt_0 = BATTERY_RESULT_IN_MILLI_VOLTS(register_val);
batt_lvl_in_milli_volt_1 = (batt_lvl_in_milli_volt_0) *1.42;
if(low_battery_check == true) {
low_battery_check = false;
if(batt_lvl_in_milli_volt_1 <= LOW_BATTERY_VOLTAGE) {
if(low_battery_cnt >= 10) {
low_battery_cnt = 0;
/*go to power off and fds save */
DBG_PRINTF("Save FDS parameters and then Power OFF\r\n");
go_device_power_off = true;
main_timer_start();
}else{
low_battery_cnt++;
DBG_PRINTF("WARNING!!! low_battery cnt = %d, Batt = %d(mV)\r\n", low_battery_cnt, batt_lvl_in_milli_volt_1);
}
}
}
else if (info4 == true){
info_batt = batt_lvl_in_milli_volt_1;
DBG_PRINTF("INFOTn%d\r\n\r\n", batt_lvl_in_milli_volt_1);
}
else {
if(cmd_type_t == CMD_UART) {
DBG_PRINTF("Tn%d\r\n\r\n", batt_lvl_in_milli_volt_1);
} else if(cmd_type_t == CMD_BLE) {
single_format_data(ble_bin_buffer, "rsn:", batt_lvl_in_milli_volt_1);
binary_tx_handler(ble_bin_buffer,3);
//data_tx_handler(ble_tx_buffer);
}
}
}
if (info4 == true){
go_batt =false;
go_temp = true;
main_timer_start();
}
}
/**@brief Function for configuring ADC to do battery level conversion.
*/
static void battery_configure(void)
{
ret_code_t err_code = nrf_drv_saadc_init(NULL, battery_event_handler);
APP_ERROR_CHECK(err_code);
nrf_saadc_channel_config_t config =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(&adc_bufs[0], 1);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(&adc_bufs[1], 1);
APP_ERROR_CHECK(err_code);
}
void pressure_all_configure(void)
{
ret_code_t err_code;
err_code = nrf_drv_saadc_init(NULL, pressure_all_event_handler);
if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE) {
DBG_PRINTF("SAADC init err=%d\r\n", err_code);
return;
}
nrf_saadc_channel_config_t ch0_cfg =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN7);
nrf_saadc_channel_config_t ch1_cfg =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN4);
err_code = nrf_drv_saadc_channel_init(0, &ch0_cfg);
if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE) {
DBG_PRINTF("SAADC ch0 init err=%d\r\n", err_code);
return;
}
err_code = nrf_drv_saadc_channel_init(1, &ch1_cfg);
if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE) {
DBG_PRINTF("SAADC ch1 init err=%d\r\n", err_code);
return;
}
err_code = nrf_drv_saadc_buffer_convert(pressure_adc_buf, 2);
if (err_code != NRF_SUCCESS) {
DBG_PRINTF("SAADC buf conv err=%d\r\n", err_code);
return;
}
}
void battery_level_meas(void)
{
ret_code_t err_code;
battery_configure();
err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code);
}
void pressure_all_level_meas(void) //add cj add 25/11/19
{
ret_code_t err_code;
pressure_all_configure(); /* Configure 2 ADC channels */
err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code);
}
void battery_loop(void * p_context) /* For 1sec */
{
UNUSED_PARAMETER(p_context);
if(processing==true)
{
processing = false ; // add 20241218
//low_battery_check = true;
return;}
else{
low_battery_check = true;
battery_level_meas();
}
}
void battery_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_battery_loop_timer_id, APP_TIMER_TICKS(BATTERY_LOOP_INTERVAL), NULL));
}
void battery_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_battery_loop_timer_id));
}
void battery_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_battery_loop_timer_id, APP_TIMER_MODE_REPEATED, battery_loop));
}

View File

@@ -0,0 +1,22 @@
/*******************************************************************************
* @file battery_saadc.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _BATTERY_SAADC_H_
#define _BATTERY_SAADC_H_
#define LOW_BATTERY_VOLTAGE 3100 /* Low Battery 임계값 */
void battery_level_meas(void);
void pressure_all_level_meas(void);
void battery_timer_start(void);
void battery_timer_stop(void);
void battery_timer_init(void);
#endif //_BATTERY_SAADC_H_

View File

@@ -0,0 +1,615 @@
/*******************************************************************************
* @file ble_core.c
* @brief BLE Core Functions - Stack, GAP, GATT, Advertising
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details BLE stack initialization, GAP/GATT configuration, advertising.
******************************************************************************/
#include "ble_core.h"
#include "ble_data_tx.h"
#include "power_ctrl.h"
#include "device_config.h"
#include "nordic_common.h"
#include "nrf.h"
#include "ble_hci.h"
#include "ble_advdata.h"
#include "ble_advertising.h"
#include "ble_srv_common.h"
#include "ble_conn_params.h"
#include "ble_nus.h"
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_gatt.h"
#include "nrf_ble_qwr.h"
#include "app_timer.h"
#include "app_error.h"
#include "bsp.h"
#include "debug_print.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "measurements.h"
#include "power_control.h"
#include "main.h"
#include <cmd_parse.h>
#if FEATURE_SECURE_CONNECTION
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "nrf_ble_lesc.h"
#endif
#if BLE_DFU_ENABLED
#include "ble_dfu.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_power.h"
#include "nrf_bootloader_info.h"
#include "nrf_dfu_ble_svci_bond_sharing.h"
#include "nrf_svci_async_function.h"
#include "nrf_svci_async_handler.h"
#include "ble_conn_state.h"
#endif
#include <string.h>
/*==============================================================================
* BLE SERVICE INSTANCES
*============================================================================*/
BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT);
NRF_BLE_GATT_DEF(m_gatt);
NRF_BLE_QWR_DEF(m_qwr);
BLE_ADVERTISING_DEF(m_advertising);
/*==============================================================================
* GLOBAL VARIABLES
*============================================================================*/
uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID;
uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3;
static ble_uuid_t m_adv_uuids[] =
{
{BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}
};
/*==============================================================================
* FORWARD DECLARATIONS
*============================================================================*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context);
static void on_adv_evt(ble_adv_evt_t ble_adv_evt);
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt);
static void conn_params_error_handler(uint32_t nrf_error);
static void nus_data_handler(ble_nus_evt_t * p_evt);
static void nrf_qwr_error_handler(uint32_t nrf_error);
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt);
#if BLE_DFU_ENABLED
static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event);
static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void * p_context);
static void advertising_config_get(ble_adv_modes_config_t * p_config);
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event);
#endif
/*==============================================================================
* BLE STACK INITIALIZATION
*============================================================================*/
void ble_stack_init(void)
{
ret_code_t err_code;
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
/* Configure BLE stack using default settings */
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
/* Enable BLE stack */
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
/* Register BLE event handler */
NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}
/*==============================================================================
* GAP INITIALIZATION
*============================================================================*/
void gap_params_init(void)
{
uint32_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
#if FEATURE_STATIC_PASSKEY
ble_opt_t ble_opt;
#endif
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
/* Set device name from EEPROM serial number */
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) SERIAL_NO,
strlen(SERIAL_NO));
APP_ERROR_CHECK(err_code);
/* Configure preferred connection parameters */
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
gap_conn_params.slave_latency = SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
#if FEATURE_STATIC_PASSKEY
/* Set static passkey for pairing */
ble_opt.gap_opt.passkey.p_passkey = (const uint8_t *)m_static_passkey;
err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &ble_opt);
APP_ERROR_CHECK(err_code);
#endif
}
/*==============================================================================
* GATT INITIALIZATION
*============================================================================*/
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
{
m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH;
}
}
void gatt_init(void)
{
ret_code_t err_code;
err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* CONNECTION PARAMETERS
*============================================================================*/
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
{
uint32_t err_code;
if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
{
err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
APP_ERROR_CHECK(err_code);
}
}
static void conn_params_error_handler(uint32_t nrf_error)
{
APP_ERROR_HANDLER(nrf_error);
}
void conn_params_init(void)
{
uint32_t err_code;
ble_conn_params_init_t cp_init;
memset(&cp_init, 0, sizeof(cp_init));
cp_init.p_conn_params = NULL;
cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;
cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;
cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;
cp_init.disconnect_on_fail = false;
cp_init.evt_handler = on_conn_params_evt;
cp_init.error_handler = conn_params_error_handler;
err_code = ble_conn_params_init(&cp_init);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* NUS DATA HANDLER
*============================================================================*/
extern which_cmd_t cmd_type_t;
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA)
{
cmd_type_t = CMD_BLE;
DBG_PRINTF("recv :%s \n", p_evt->params.rx_data.p_data);
DBG_PRINTF("length %d \r\n", p_evt->params.rx_data.length);
received_command_process(p_evt->params.rx_data.p_data, CMD_BLE, p_evt->params.rx_data.length);
}
}
/*==============================================================================
* QWR ERROR HANDLER
*============================================================================*/
static void nrf_qwr_error_handler(uint32_t nrf_error)
{
APP_ERROR_HANDLER(nrf_error);
}
/*==============================================================================
* DFU HANDLERS
*============================================================================*/
#if BLE_DFU_ENABLED
NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);
NRF_SDH_STATE_OBSERVER(m_buttonless_dfu_state_obs, 0) =
{
.handler = buttonless_dfu_sdh_state_observer,
};
static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event)
{
switch (event)
{
case NRF_PWR_MGMT_EVT_PREPARE_DFU:
DBG_PRINTF("Power management wants to reset to DFU mode.\r\n");
break;
default:
return true;
}
DBG_PRINTF("Power management allowed to reset to DFU mode.\r\n");
return true;
}
static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void * p_context)
{
if (state == NRF_SDH_EVT_STATE_DISABLED)
{
nrf_power_gpregret2_set(BOOTLOADER_DFU_SKIP_CRC);
nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
}
}
static void advertising_config_get(ble_adv_modes_config_t * p_config)
{
memset(p_config, 0, sizeof(ble_adv_modes_config_t));
p_config->ble_adv_fast_enabled = true;
p_config->ble_adv_fast_interval = APP_ADV_INTERVAL;
p_config->ble_adv_fast_timeout = APP_ADV_DURATION;
}
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
switch (event)
{
case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
{
DBG_PRINTF("Device is preparing to enter bootloader mode.\r\n");
ble_adv_modes_config_t config;
advertising_config_get(&config);
config.ble_adv_on_disconnect_disabled = true;
ble_advertising_modes_config_set(&m_advertising, &config);
uint32_t conn_count = ble_conn_state_for_each_connected(disconnect, NULL);
DBG_PRINTF("Disconnected %d links.\r\n", conn_count);
break;
}
case BLE_DFU_EVT_BOOTLOADER_ENTER:
DBG_PRINTF("Device will enter bootloader mode.\r\n");
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
DBG_PRINTF("Request to enter bootloader mode failed asynchroneously.\r\n");
break;
case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
DBG_PRINTF("Request to send a response to client failed.\r\n");
APP_ERROR_CHECK(false);
break;
default:
DBG_PRINTF("Unknown event from ble_dfu_buttonless.\r\n");
break;
}
}
void dfu_init(void)
{
ret_code_t err_code = ble_dfu_buttonless_async_svci_init();
APP_ERROR_CHECK(err_code);
}
#endif
/*==============================================================================
* SERVICES INITIALIZATION
*============================================================================*/
void services_init(void)
{
uint32_t err_code;
ble_nus_init_t nus_init;
nrf_ble_qwr_init_t qwr_init = {0};
/* Initialize Queued Write Module */
qwr_init.error_handler = nrf_qwr_error_handler;
err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
APP_ERROR_CHECK(err_code);
/* Initialize Nordic UART Service */
memset(&nus_init, 0, sizeof(nus_init));
nus_init.data_handler = nus_data_handler;
err_code = ble_nus_init(&m_nus, &nus_init);
APP_ERROR_CHECK(err_code);
#if BLE_DFU_ENABLED
/* Initialize DFU service */
ble_dfu_buttonless_init_t dfus_init = {0};
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
#endif
}
/*==============================================================================
* WRAPPER FUNCTIONS FOR STATIC VARIABLES
*============================================================================*/
uint32_t ble_nus_data_send_wrapper(uint8_t *p_data, uint16_t *p_length, uint16_t conn_handle)
{
return ble_nus_data_send(&m_nus, p_data, p_length, conn_handle);
}
uint32_t ble_advertising_restart_without_whitelist_wrapper(void)
{
return ble_advertising_restart_without_whitelist(&m_advertising);
}
/*==============================================================================
* ADVERTISING
*============================================================================*/
static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
uint32_t err_code;
switch (ble_adv_evt)
{
case BLE_ADV_EVT_FAST:
err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING);
APP_ERROR_CHECK(err_code);
break;
case BLE_ADV_EVT_IDLE:
/* Advertising timeout - enter sleep mode */
go_sleep_mode_enter = true;
main_timer_start();
break;
default:
break;
}
}
void advertising_init(void)
{
uint32_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = true;
#if FEATURE_NO_SLEEP
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
#else
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
#endif
init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;
init.config.ble_adv_fast_enabled = true;
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = APP_ADV_DURATION;
init.evt_handler = on_adv_evt;
err_code = ble_advertising_init(&m_advertising, &init);
APP_ERROR_CHECK(err_code);
ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
#if FEATURE_SECURE_CONNECTION
void advertising_start(bool erase_bonds)
{
extern void delete_bonds(void);
DBG_PRINTF("adv_start(erase=%d)\r\n", erase_bonds);
if (erase_bonds == true) {
bond_data_delete = false;
DBG_PRINTF("delete_bonds\r\n");
delete_bonds();
/* Advertising started by PM_EVT_PEERS_DELETE_SUCCEEDED event */
} else {
DBG_PRINTF("ble_adv_start...\r\n");
ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
DBG_PRINTF("err=%d\r\n", err_code);
APP_ERROR_CHECK(err_code);
}
}
#else
void advertising_start(void)
{
uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
/* Ignore INVALID_STATE - already advertising or not ready */
if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE) {
APP_ERROR_CHECK(err_code);
}
}
#endif
/*==============================================================================
* DISCONNECT
*============================================================================*/
void disconnect(uint16_t conn_handle, void * p_context)
{
UNUSED_PARAMETER(p_context);
ret_code_t err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
if (err_code != NRF_SUCCESS)
{
DBG_PRINTF("Failed to disconnect connection. Connection handle: %d Error: %d\r\n", conn_handle, err_code);
}
else
{
DBG_PRINTF("Disconnected connection handle %d\r\n", conn_handle);
}
}
/*==============================================================================
* BLE EVENT HANDLER
*============================================================================*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
uint32_t err_code;
extern void ble_data_tx_complete(void);
#if FEATURE_SECURE_CONNECTION
pm_handler_secure_on_connection(p_ble_evt);
#endif
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_DISCONNECTED:
DBG_PRINTF("Disconnected\r\n");
ble_connection_st = 0;
m_conn_handle = BLE_CONN_HANDLE_INVALID;
/* Put device to sleep on disconnect if active */
if (device_status == true) {
LED_ALLOFF();
PD_ALLOFF();
if (device_sleep_mode() == 0) {
device_status = false;
}
}
/* Restart advertising to allow reconnection */
/* Delay to let PC complete disconnect processing */
nrf_delay_ms(100);
#if FEATURE_SECURE_CONNECTION
advertising_start(false);
#else
advertising_start();
#endif
DBG_PRINTF("Advertising restarted\r\n");
break;
case BLE_GAP_EVT_CONNECTED:
{
DBG_PRINTF("Connected\r\n");
ble_connection_st = 1;
battery_timer_start();
m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
APP_ERROR_CHECK(err_code);
/* Set TX power to +8 dBm for connected state */
sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_CONN, m_conn_handle, 8);
err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
APP_ERROR_CHECK(err_code);
break;
}
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
{
DBG_PRINTF("PHY update request.\r\n");
/* Use 1M PHY only for better PC compatibility */
ble_gap_phys_t const phys =
{
.rx_phys = BLE_GAP_PHY_1MBPS,
.tx_phys = BLE_GAP_PHY_1MBPS,
};
err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
APP_ERROR_CHECK(err_code);
} break;
case BLE_GATTC_EVT_TIMEOUT:
DBG_PRINTF("Client Timeout.\r\n");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTS_EVT_TIMEOUT:
DBG_PRINTF("Server Timeout.\r\n");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
#if !FEATURE_SECURE_CONNECTION
/* Reject pairing when security is disabled */
err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
APP_ERROR_CHECK(err_code);
#endif
break;
#if FEATURE_SECURE_CONNECTION
case BLE_GAP_EVT_PASSKEY_DISPLAY:
{
/* Display passkey for user verification */
char passkey[7];
memcpy(passkey, p_ble_evt->evt.gap_evt.params.passkey_display.passkey, 6);
passkey[6] = 0;
DBG_PRINTF("Passkey: %s\r\n", passkey);
} break;
case BLE_GAP_EVT_AUTH_KEY_REQUEST:
#if FEATURE_STATIC_PASSKEY
/* Provide static passkey for authentication */
if (p_ble_evt->evt.gap_evt.params.auth_key_request.key_type == BLE_GAP_AUTH_KEY_TYPE_PASSKEY)
{
err_code = sd_ble_gap_auth_key_reply(p_ble_evt->evt.gap_evt.conn_handle,
BLE_GAP_AUTH_KEY_TYPE_PASSKEY,
(const uint8_t *)m_static_passkey);
APP_ERROR_CHECK(err_code);
}
#endif
break;
case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
/* Handled by LESC module */
break;
#endif
case BLE_GATTS_EVT_HVN_TX_COMPLETE:
/* TX complete - ready for next transmission */
ble_data_tx_complete();
break;
default:
break;
}
}

View File

@@ -0,0 +1,123 @@
/*******************************************************************************
* @file ble_core.h
* @brief BLE Core Functions - Stack, GAP, GATT, Advertising
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details BLE stack initialization, GAP/GATT configuration, advertising.
******************************************************************************/
#ifndef BLE_CORE_H
#define BLE_CORE_H
#include <stdint.h>
#include <stdbool.h>
#include "ble.h"
#include "ble_advertising.h"
#include "nrf_ble_gatt.h"
/*==============================================================================
* CONSTANTS
*============================================================================*/
#define APP_BLE_CONN_CFG_TAG 1
#define NUS_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN
#define APP_BLE_OBSERVER_PRIO 3
#define APP_ADV_INTERVAL 64 /* 40 ms in 0.625 ms units */
#if FEATURE_NO_SLEEP
#define APP_ADV_DURATION 0 /* Infinite advertising */
#else
#define APP_ADV_DURATION 18000 /* 180 seconds */
#endif
/* Connection Parameters */
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(20, UNIT_1_25_MS)
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(75, UNIT_1_25_MS)
#define SLAVE_LATENCY 0
#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS)
#define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000)
#define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000)
#define MAX_CONN_PARAMS_UPDATE_COUNT 3
/* GATT Constants */
#define OPCODE_LENGTH 1
#define HANDLE_LENGTH 2
/*==============================================================================
* EXTERNAL VARIABLES
*============================================================================*/
extern uint16_t m_conn_handle;
extern uint16_t m_ble_nus_max_data_len;
/*==============================================================================
* FUNCTION PROTOTYPES
*============================================================================*/
/**
* @brief Initialize BLE SoftDevice stack
*/
void ble_stack_init(void);
/**
* @brief Initialize GAP parameters
*/
void gap_params_init(void);
/**
* @brief Initialize GATT module
*/
void gatt_init(void);
/**
* @brief Initialize connection parameters module
*/
void conn_params_init(void);
/**
* @brief Initialize BLE advertising
*/
void advertising_init(void);
/**
* @brief Start BLE advertising
* @param erase_bonds If true, delete bonds before advertising
*/
#if FEATURE_SECURE_CONNECTION
void advertising_start(bool erase_bonds);
#else
void advertising_start(void);
#endif
/**
* @brief Disconnect a connection
* @param conn_handle Connection handle to disconnect
* @param p_context Unused context pointer
*/
void disconnect(uint16_t conn_handle, void *p_context);
/**
* @brief Initialize BLE services (NUS, DFU)
*/
void services_init(void);
/**
* @brief Wrapper for ble_nus_data_send (accesses static m_nus)
*/
uint32_t ble_nus_data_send_wrapper(uint8_t *p_data, uint16_t *p_length, uint16_t conn_handle);
/**
* @brief Wrapper for ble_advertising_restart_without_whitelist (accesses static m_advertising)
*/
uint32_t ble_advertising_restart_without_whitelist_wrapper(void);
#if BLE_DFU_ENABLED
/**
* @brief Initialize DFU async SVCI
*/
void dfu_init(void);
#endif
#endif /* BLE_CORE_H */

View File

@@ -0,0 +1,223 @@
/*******************************************************************************
* @file ble_data_tx.c
* @brief BLE Data Transmission Functions
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details BLE NUS data transmission with CRC16 checksum.
******************************************************************************/
#include "ble_data_tx.h"
#include "ble_core.h"
#include "ble_nus.h"
#include "crc16.h"
#include "nrf_delay.h"
#include "app_error.h"
#include "debug_print.h"
#include <string.h>
/*==============================================================================
* GLOBAL VARIABLES
*============================================================================*/
volatile bool ble_connection_st = 0;
volatile bool data_tx_in_progress = false;
uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN];
/*==============================================================================
* PRIVATE VARIABLES
*============================================================================*/
static uint8_t m_tx_buffer[BLE_NUS_MAX_DATA_LEN];
static uint16_t m_tx_len = 0;
static bool m_tx_in_progress = false;
/*==============================================================================
* DATA TRANSMISSION FUNCTIONS
*============================================================================*/
void data_tx_handler(char const *p_data_to_send)
{
if (m_tx_in_progress)
{
return;
}
/* Find string length by locating '\r' terminator */
char const *p_end_char = strchr(p_data_to_send, '\r');
if (p_end_char == NULL)
{
DBG_PRINTF("TX data has no '\\r' terminator. Aborting send.");
return;
}
uint16_t data_len = p_end_char - p_data_to_send;
/* Validate length (need space for 2-byte CRC) */
if (data_len > (BLE_NUS_MAX_DATA_LEN - 2))
{
DBG_PRINTF("TX data is too long to fit CRC. Len: %d", data_len);
return;
}
/* Prepare buffer with data and CRC16 */
memcpy(m_tx_buffer, p_data_to_send, data_len);
uint16_t crc = crc16_compute(m_tx_buffer, data_len, NULL);
m_tx_buffer[data_len] = (uint8_t)(crc & 0xFF);
m_tx_buffer[data_len + 1] = (uint8_t)((crc >> 8) & 0xFF);
m_tx_len = data_len + 2;
/* Attempt to send */
uint32_t err_code;
if (ble_connection_st == BLE_CONNECTED_ST)
{
m_tx_in_progress = true;
err_code = ble_nus_data_send_wrapper( m_tx_buffer, &m_tx_len, m_conn_handle);
if (err_code == NRF_SUCCESS)
{
DBG_PRINTF("NUS data sent to SoftDevice buffer.");
}
else if (err_code == NRF_ERROR_RESOURCES)
{
DBG_PRINTF("NUS buffer full. Will retry later.");
}
else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND)
{
ble_connection_st = BLE_DISCONNECTED_ST;
DBG_PRINTF("Failed to send NUS data, device not connected.");
m_tx_in_progress = false;
}
else
{
m_tx_in_progress = false;
APP_ERROR_CHECK(err_code);
}
}
}
void binary_tx_handler(uint8_t const *ble_bin_buff, uint16_t length)
{
uint32_t err_code;
static uint8_t tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0};
if (ble_connection_st == 0) {
DBG_PRINTF("Lost!\r\n");
return;
}
data_tx_in_progress = true;
if (length * sizeof(uint16_t) > (BLE_NUS_MAX_DATA_LEN - 2)) {
return;
}
if (ble_connection_st == BLE_CONNECTED_ST) {
memcpy(tx_buffer, ble_bin_buff, length * sizeof(uint16_t));
uint16_t crc = crc16_compute(tx_buffer, length * sizeof(uint16_t), NULL);
tx_buffer[length * sizeof(uint16_t)] = (uint8_t)(crc & 0xFF);
tx_buffer[length * sizeof(uint16_t) + 1] = (uint8_t)((crc >> 8) & 0xFF);
uint16_t total_len = length * sizeof(uint16_t) + 2;
do {
err_code = ble_nus_data_send_wrapper( tx_buffer, &total_len, m_conn_handle);
if ((err_code != NRF_ERROR_INVALID_STATE) &&
(err_code != NRF_ERROR_RESOURCES) &&
(err_code != NRF_ERROR_NOT_FOUND))
{
nrf_delay_ms(10);
}
APP_ERROR_CHECK(err_code);
} while (err_code == NRF_ERROR_RESOURCES);
data_tx_in_progress = false;
}
}
void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length)
{
binary_tx_handler(ble_bin_buff, length);
}
/*==============================================================================
* DATA FORMATTING FUNCTIONS
*============================================================================*/
void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
buffer[5] = (uint8_t)(value & 0xFF);
buffer[4] = (uint8_t)((value >> 8) & 0xFF);
}
void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++) {
buffer[4 + i * 2 + 1] = (uint8_t)(data_array[i] & 0xFF);
buffer[4 + i * 2] = (uint8_t)((data_array[i] >> 8) & 0xFF);
}
}
void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++) {
buffer[4 + i] = (uint8_t)(data_array[i] & 0xFF);
}
}
void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++) {
buffer[4 + i] = (uint8_t)(data_ascii[i] & 0xFF);
}
}
/*==============================================================================
* TX COMPLETE HANDLER (called from ble_evt_handler)
*============================================================================*/
void ble_data_tx_complete(void)
{
m_tx_in_progress = false;
}

View File

@@ -0,0 +1,82 @@
/*******************************************************************************
* @file ble_data_tx.h
* @brief BLE Data Transmission Functions
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details BLE NUS data transmission with CRC16 checksum.
* - ASCII data transmission
* - Binary data transmission
* - Data formatting functions
******************************************************************************/
#ifndef BLE_DATA_TX_H
#define BLE_DATA_TX_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "main.h"
/*==============================================================================
* EXTERNAL VARIABLES
*============================================================================*/
extern volatile bool ble_connection_st;
extern volatile bool data_tx_in_progress;
extern uint8_t ble_bin_buffer[];
/*==============================================================================
* FUNCTION PROTOTYPES
*============================================================================*/
/**
* @brief Send ASCII data over BLE NUS with CRC
* @param p_data_to_send Null-terminated string ending with '\r'
*/
void data_tx_handler(char const *p_data_to_send);
/**
* @brief Send binary data over BLE NUS with CRC
* @param ble_bin_buff Binary data buffer
* @param length Number of 16-bit words to send
*/
void binary_tx_handler(uint8_t const *ble_bin_buff, uint16_t length);
/**
* @brief Format single 16-bit value with 4-character tag
* @param buffer Output buffer (minimum 6 bytes)
* @param tag 4-character tag string
* @param value 16-bit value to format
*/
void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value);
/**
* @brief Format 16-bit array with 4-character tag
* @param buffer Output buffer
* @param tag 4-character tag string
* @param data_array Array of 16-bit values
* @param length Number of elements in array
*/
void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length);
/**
* @brief Format 8-bit array with 4-character tag
* @param buffer Output buffer
* @param tag 4-character tag string
* @param data_array Array of 8-bit values
* @param length Number of bytes in array
*/
void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length);
/**
* @brief Format ASCII string with 4-character tag
* @param buffer Output buffer
* @param tag 4-character tag string
* @param data_ascii ASCII string data
* @param length String length
*/
void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length);
#endif /* BLE_DATA_TX_H */

View File

@@ -0,0 +1,199 @@
/*******************************************************************************
* @file ble_security.c
* @brief BLE Security - Peer Manager, LESC, Bonding
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details Peer Manager initialization and security event handling.
******************************************************************************/
#include "sdk_config.h"
#include "ble_security.h"
#if FEATURE_SECURE_CONNECTION
#include "ble_core.h"
#include "ble_data_tx.h"
#include "power_ctrl.h"
#include "device_config.h"
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "nrf_ble_lesc.h"
#include "ble_conn_state.h"
#include "app_error.h"
#include "debug_print.h"
#include "battery_saadc.h"
#include "ble_quick_security.h"
#include <string.h>
/*==============================================================================
* GLOBAL VARIABLES
*============================================================================*/
bool erase_bonds = false;
/*==============================================================================
* PRIVATE VARIABLES
*============================================================================*/
static pm_peer_id_t m_peer_to_be_deleted = PM_PEER_ID_INVALID;
static uint8_t c_addr[6];
/*==============================================================================
* PEER MANAGER EVENT HANDLER
*============================================================================*/
static void pm_evt_handler(pm_evt_t const * p_evt)
{
pm_peer_data_bonding_t peer_bonding_data;
uint32_t return_code;
ret_code_t err_code;
/* Standard Peer Manager handlers */
pm_handler_on_pm_evt(p_evt);
pm_handler_disconnect_on_sec_failure(p_evt);
pm_handler_flash_clean(p_evt);
/* Security module (automatic mode handling) */
ble_security_quick_pm_handler(p_evt);
/* Application-specific event handling */
switch (p_evt->evt_id)
{
case PM_EVT_CONN_SEC_SUCCEEDED:
{
pm_conn_sec_status_t conn_sec_status;
err_code = pm_conn_sec_status_get(p_evt->conn_handle, &conn_sec_status);
APP_ERROR_CHECK(err_code);
/* Accept if: MITM protected OR dev mode (no security) */
if (conn_sec_status.mitm_protected || BLE_DEV_MODE)
{
DBG_PRINTF("Link secured. Role: %d, conn_handle: %d, Procedure: %d\r\n",
ble_conn_state_role(p_evt->conn_handle),
p_evt->conn_handle,
p_evt->params.conn_sec_succeeded.procedure);
/* Start battery monitoring after secure connection */
ble_connection_st = 1;
battery_timer_start();
}
else
{
/* Security insufficient - disconnect (production mode) */
DBG_PRINTF("Link security FAILED\r\n");
err_code = pm_peer_id_get(m_conn_handle, &m_peer_to_be_deleted);
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_disconnect(m_conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
}
}
break;
case PM_EVT_CONN_SEC_FAILED:
{
DBG_PRINTF("Security failed: peer_id=%d, error=%d\r\n",
p_evt->peer_id,
p_evt->params.conn_sec_failed.error);
/* Auto-retry if key missing (supports rebonding) */
if (p_evt->params.conn_sec_failed.error == PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING)
{
err_code = pm_conn_secure(p_evt->conn_handle, true);
if (err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(err_code);
}
}
}
break;
case PM_EVT_PEERS_DELETE_SUCCEEDED:
/* Restart advertising after bond deletion */
advertising_start(false);
break;
case PM_EVT_CONN_SEC_CONFIG_REQ:
{
/* Allow repairing for rebonding support */
pm_conn_sec_config_t conn_sec_config = {
.allow_repairing = true
};
pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
}
break;
case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
{
/* Store peer address for tracking */
return_code = pm_peer_data_bonding_load(p_evt->peer_id, &peer_bonding_data);
if (return_code == NRF_SUCCESS)
{
DBG_PRINTF("Peer updated: %02x:%02x:%02x:%02x:%02x:%02x\r\n",
peer_bonding_data.peer_ble_id.id_addr_info.addr[5],
peer_bonding_data.peer_ble_id.id_addr_info.addr[4],
peer_bonding_data.peer_ble_id.id_addr_info.addr[3],
peer_bonding_data.peer_ble_id.id_addr_info.addr[2],
peer_bonding_data.peer_ble_id.id_addr_info.addr[1],
peer_bonding_data.peer_ble_id.id_addr_info.addr[0]);
memcpy(c_addr,
peer_bonding_data.peer_ble_id.id_addr_info.addr,
sizeof(c_addr));
DBG_PRINTF("Stored c_addr[3]: %02x\r\n", c_addr[3]);
m_reset_status = 10;
}
else
{
DBG_PRINTF("Failed to load peer data: error=%d\r\n", return_code);
m_reset_status = 10;
}
}
break;
default:
break;
}
}
/*==============================================================================
* PUBLIC FUNCTIONS
*============================================================================*/
void peer_manager_init(void)
{
ret_code_t err_code;
/* Security mode auto-configuration */
ble_security_quick_init(BLE_DEV_MODE);
/* Register PM event handler */
err_code = pm_register(pm_evt_handler);
APP_ERROR_CHECK(err_code);
DBG_PRINTF("BLE Security initialized (mode=%d)\r\n", BLE_DEV_MODE);
}
void delete_bonds(void)
{
ret_code_t err_code;
DBG_PRINTF("Erase bonds!\r\n");
err_code = pm_peers_delete();
APP_ERROR_CHECK(err_code);
}
void security_idle_state_handle(void)
{
ret_code_t err_code;
err_code = nrf_ble_lesc_request_handler();
APP_ERROR_CHECK(err_code);
}
#endif /* FEATURE_SECURE_CONNECTION */

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* @file ble_security.h
* @brief BLE Security - Peer Manager, LESC, Bonding
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details Peer Manager initialization and security event handling.
******************************************************************************/
#ifndef BLE_SECURITY_H
#define BLE_SECURITY_H
#include <stdint.h>
#include <stdbool.h>
#if FEATURE_SECURE_CONNECTION
#include "ble_gap.h"
/*==============================================================================
* CONSTANTS
*============================================================================*/
#define LESC_DEBUG_MODE 0
#define SEC_PARAM_BOND 1
#define SEC_PARAM_MITM 1
#if FEATURE_STATIC_PASSKEY
#define SEC_PARAM_LESC 0
#else
#define SEC_PARAM_LESC 1
#endif
#define SEC_PARAM_KEYPRESS 0
#define SEC_PARAM_IO_CAPABILITIES BLE_GAP_IO_CAPS_DISPLAY_ONLY
#define SEC_PARAM_OOB 0
#define SEC_PARAM_MIN_KEY_SIZE 7
#define SEC_PARAM_MAX_KEY_SIZE 16
#define PASSKEY_TXT_LENGTH 8
#define PASSKEY_LENGTH 6
#define BLE_DEV_MODE 1 /* 1=Dev mode, 0=Production */
#endif
/*==============================================================================
* FUNCTION PROTOTYPES
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
/*==============================================================================
* EXTERNAL VARIABLES (SECURE CONNECTION ONLY)
*============================================================================*/
extern bool erase_bonds;
/**
* @brief Initialize Peer Manager with security configuration
*/
void peer_manager_init(void);
/**
* @brief Delete all bond information from flash
*/
void delete_bonds(void);
/**
* @brief Handle idle state for LESC
*/
void security_idle_state_handle(void);
#endif
#endif /* BLE_SECURITY_H */

View File

@@ -0,0 +1,19 @@
/*******************************************************************************
* @file ble_services.c
* @brief BLE Services - NUS and DFU (Stub file)
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details This is now a stub file. All service initialization code has been
* moved to ble_core.c to avoid static variable access issues with
* Nordic SDK macros (BLE_NUS_DEF, NRF_BLE_QWR_DEF, etc.).
*
* See ble_core.c for:
* - services_init()
* - dfu_init()
* - NUS data handler
* - DFU event handlers
******************************************************************************/
/* Empty stub - all code moved to ble_core.c */

View File

@@ -0,0 +1,33 @@
/*******************************************************************************
* @file ble_services.h
* @brief BLE Services - NUS and DFU
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details Nordic UART Service and DFU service initialization.
******************************************************************************/
#ifndef BLE_SERVICES_H
#define BLE_SERVICES_H
#include <stdint.h>
#include <stdbool.h>
/*==============================================================================
* FUNCTION PROTOTYPES
*============================================================================*/
/**
* @brief Initialize BLE services (NUS, DFU)
*/
void services_init(void);
#if BLE_DFU_ENABLED
/**
* @brief Initialize DFU async SVCI
*/
void dfu_init(void);
#endif
#endif /* BLE_SERVICES_H */

View File

@@ -0,0 +1,163 @@
/*******************************************************************************
* @file cat_i2c.c
* @brief
******************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include "nrf.h"
#include "app_error.h"
#include "boards.h"
#include "nrfx_gpiote.h"
#include "nrfx_twi.h"
#include "nrf_drv_twi.h"
#include "nrf_delay.h"
#include "cat_i2c.h"
#define CAT_I2C_ADDR 0x50
#define MAX_SERIAL_WRITE 16
//int16_t read_from_DS3930 = 0;
//uint16_t data_160_to_write = 0;
//static volatile bool m_xfer_done = false;
/* TWI instance. */
//const nrfx_twi_t m_twi_ir = NRFX_TWI_INSTANCE(IR_I2C_INSTANCE);
const nrf_drv_twi_t m_twi_cat = NRF_DRV_TWI_INSTANCE (CAT_I2C_INSTANCE);
//void twi_handler(nrfx_twi_evt_t const * p_event, void * p_context)
//{
// m_xfer_done = true;
//}
//void ir_irq_init(void){
// ret_code_t err_code;
// /* Initialize int pin */
// if (!nrfx_gpiote_is_init())
// {
// err_code = nrfx_gpiote_init();
// APP_ERROR_CHECK(err_code);
// }
// nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
// in_config.pull = NRF_GPIO_PIN_PULLDOWN;
// err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
// APP_ERROR_CHECK(err_code);
// nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
//}
//void ir_irq_uninit(void){
// nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
// nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
//}
void cat_i2c_uninit(void){
nrf_drv_twi_disable(&m_twi_cat);
nrf_drv_twi_uninit(&m_twi_cat);
//
}
void cat_i2c_init(void){
ret_code_t err_code;
const nrf_drv_twi_config_t twi_cat_config = {
.scl = CAT_I2C_SCL_PIN,
.sda = CAT_I2C_SDA_PIN,
.frequency = NRF_DRV_TWI_FREQ_100K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
.clear_bus_init = false
};
err_code = nrf_drv_twi_init(&m_twi_cat, &twi_cat_config, NULL, NULL);
APP_ERROR_CHECK(err_code);
nrf_drv_twi_enable(&m_twi_cat);
// ir_irq_init();
}
uint8_t cat_command_read(uint8_t device_id, uint8_t address, uint8_t *data)
{
uint8_t read_data = 0;
char adata[8];
ret_code_t err_code;
//address = 1|(address<<1);
address = (address & 0xFF);
err_code = nrf_drv_twi_tx(&m_twi_cat, device_id, &address, 1, true);
if (err_code != NRF_SUCCESS) {
// Handle error
// return;
}
err_code = nrf_drv_twi_rx(&m_twi_cat, device_id, data, 8);
if (err_code != NRF_SUCCESS) {
// Handle error
return 0;
}
read_data = data[0];
memcpy(adata,data,8);
printf("Data %s . \r\n", adata);
return read_data;
}
void cat_command_write(uint8_t device_id, uint8_t address, uint8_t *data)
{
//uint16_t data_to_write = 0;
uint8_t buffer[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
address = (address & 0xFF);
//buffer[0] = 0x00;
buffer[0] = (address);
// buffer[1] =(data & 0xFF);
// buffer[2] = data1+1;
// buffer[3] = data1+2;
// buffer[4] = data1+3;
// buffer[5] = data1+4;
// buffer[6] = data1+5;
memcpy(&buffer[1], data, 8 );
ret_code_t err_code;
//err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, 0x00, 1, false);
err_code = nrf_drv_twi_tx(&m_twi_cat, device_id, buffer, 9, false);
printf("Data %x %x %x %x. \r\n", buffer[0], buffer[1], buffer[2], buffer[3]);
printf("Data %s. \r\n", buffer);
//err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, buffer, 6, false);
if (err_code != NRF_SUCCESS) {
printf("TWI Error.\r\n");
}
}

View File

@@ -0,0 +1,50 @@
/*******************************************************************************
* @file ir_i2c.h
* @date 2024-07-17
******************************************************************************/
#ifndef _CAT_I2C_H_
#define _CAT_I2C_H_
#include "sdk_common.h"
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#define CAT_I2C_INSTANCE 0 /**< I2C instance index. */
#define CAT_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15)
#define CAT_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14)
//#define IR_I2C_SDA_PIN NRF_GPIO_PIN_MAP(0,25)
//#define IR_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,0)
/**
* COMMAND CONSTANTS
* Commands are 16-bit writes: bits 15:14 are 0s
* Bits 13:10 are the command value below
* Bits 9:0 are data for the command, but not all bits are used with all commands
*/
void cat_i2c_uninit(void);
void cat_i2c_init(void);
uint8_t cat_command_read(uint8_t device_id, uint8_t address, uint8_t *data);
void cat_command_write(uint8_t device_id, uint8_t address, uint8_t *data);
#endif /* !_ADA5272_I2C_H_ */

View File

@@ -0,0 +1,541 @@
/*******************************************************************************
* @file cat_interface.c
* @brief
******************************************************************************/
/* board driver */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include "nrf.h"
#include "app_error.h"
#include "boards.h"
#include "nrfx_gpiote.h"
#include "nrfx_twi.h"
#include "nrf_crypto.h"
#include "nrf_crypto_aes.h"
#include "nrf_drv_twi.h"
#include "system_interface.h"
#include "nrf_delay.h"
#include "cat_interface.h"
#include "debug_print.h"
/* I2C number and slave address for INV device */
#define ICM_I2C_ADDR 0x68
#define INV_MAX_SERIAL_WRITE 16
#define EEPROM_I2C_ADDRESS 0x50
#define EEPROM_PAGE_SIZE 64
#define EEPROM_INSTANCE 0
#define AES_BLOCK_SIZE 16
static uint8_t aes_key[16] = {
0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81
};
static uint8_t aes_iv[16] = { 0 }; // Fixed IV for simplicity
const nrfx_twi_t m_eeprom = NRFX_TWI_INSTANCE(EEPROM_INSTANCE);
ret_code_t encrypt_data(const uint8_t *input, size_t length, uint8_t *output, size_t *output_len) {
nrf_crypto_aes_context_t aes_ctx;
size_t padded_len = ((length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
uint8_t buffer[padded_len];
memcpy(buffer, input, length);
memset(&buffer[length], 0, padded_len - length); // Zero padding
ret_code_t err = nrf_crypto_aes_init(&aes_ctx, &g_nrf_crypto_aes_cbc_128_info, NRF_CRYPTO_ENCRYPT);
VERIFY_SUCCESS(err);
err = nrf_crypto_aes_key_set(&aes_ctx, aes_key);
VERIFY_SUCCESS(err);
err = nrf_crypto_aes_iv_set(&aes_ctx, aes_iv);
VERIFY_SUCCESS(err);
*output_len = padded_len;
err = nrf_crypto_aes_finalize(&aes_ctx, buffer, padded_len, output, output_len);
return err;
}
ret_code_t decrypt_data(const uint8_t *input, size_t length, uint8_t *output) {
nrf_crypto_aes_context_t aes_ctx;
uint8_t input_copy[length];
memcpy(input_copy, input, length);
ret_code_t err = nrf_crypto_aes_init(&aes_ctx, &g_nrf_crypto_aes_cbc_128_info, NRF_CRYPTO_DECRYPT);
VERIFY_SUCCESS(err);
err = nrf_crypto_aes_key_set(&aes_ctx, aes_key);
VERIFY_SUCCESS(err);
err = nrf_crypto_aes_iv_set(&aes_ctx, aes_iv);
VERIFY_SUCCESS(err);
size_t output_len = length;
err = nrf_crypto_aes_finalize(&aes_ctx, input_copy, length, output, &output_len);
return err;
}
ret_code_t eeprom_write_encrypted(uint16_t mem_address, const uint8_t *plaintext, size_t length) {
uint8_t encrypted_buf[256]; // Adjust if needed
size_t encrypted_len = 0;
ret_code_t err = encrypt_data(plaintext, length, encrypted_buf, &encrypted_len);
VERIFY_SUCCESS(err);
return eeprom_write_bytes(mem_address, encrypted_buf, encrypted_len);
}
ret_code_t eeprom_read_decrypted(uint16_t mem_address, uint8_t *plaintext, size_t original_length) {
size_t encrypted_len = ((original_length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
uint8_t encrypted_buf[256]; // Adjust as needed
ret_code_t err = eeprom_read_bytes(mem_address, encrypted_buf, encrypted_len);
VERIFY_SUCCESS(err);
return decrypt_data(encrypted_buf, encrypted_len, plaintext);
}
void eeprom_uninitialize(void){
nrfx_twi_disable(&m_eeprom);
nrfx_twi_uninit(&m_eeprom);
}
void eeprom_initialize(void){
ret_code_t err_code;
const nrfx_twi_config_t eeprom_config = {
.scl = ICM42670_I2C_SCL_PIN,
.sda = ICM42670_I2C_SDA_PIN,
.frequency = NRF_TWI_FREQ_400K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
};
err_code = nrfx_twi_init(&m_eeprom, &eeprom_config, NULL, NULL);
APP_ERROR_CHECK(err_code);
nrfx_twi_enable(&m_eeprom);
}
ret_code_t eeprom_initialize_safe(void){
ret_code_t err_code;
const nrfx_twi_config_t eeprom_config = {
.scl = ICM42670_I2C_SCL_PIN,
.sda = ICM42670_I2C_SDA_PIN,
.frequency = NRF_TWI_FREQ_400K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
};
err_code = nrfx_twi_init(&m_eeprom, &eeprom_config, NULL, NULL);
if (err_code != NRF_SUCCESS) {
return err_code;
}
nrfx_twi_enable(&m_eeprom);
return NRF_SUCCESS;
}
ret_code_t eeprom_write_page(uint16_t mem_address, const uint8_t *data)
{
uint8_t buffer[2 + EEPROM_PAGE_SIZE]; // 2 bytes for address + 64 bytes data
ret_code_t ret;
buffer[0] = (uint8_t)(mem_address >> 8); // MSB
buffer[1] = (uint8_t)(mem_address & 0xFF); // LSB
memcpy(&buffer[2], data, EEPROM_PAGE_SIZE);
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, buffer, 2 + EEPROM_PAGE_SIZE, false);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM write failed (code: %d)\n", ret);
return ret;
}
// Wait for internal EEPROM write cycle (typically ~5ms)
for (int i = 0; i < 100; i++) {
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, NULL, 0, false);
if (ret == NRF_SUCCESS)
break;
nrf_delay_us(100); // Wait 100us before retry
}
return NRF_SUCCESS;
}
ret_code_t eeprom_write_uint16_array(uint16_t start_address, const uint16_t *data, size_t count)
{
if (count != 48) {
DBG_PRINTF("Error: This function is only for writing exactly 48 uint16_t values.\n");
return NRF_ERROR_INVALID_PARAM;
}
if (start_address < 128) {
DBG_PRINTF("Error: This function is only for after 192....\n");
return NRF_ERROR_INVALID_PARAM;
}
uint8_t buffer[EEPROM_PAGE_SIZE];
ret_code_t ret;
// Write first 32 uint16_t values (64 bytes)
for (size_t i = 0; i < 32; i++) {
buffer[i * 2] = (uint8_t)(data[i] >> 8); // MSB
buffer[i * 2 + 1] = (uint8_t)(data[i] & 0xFF); // LSB
// buffer[i * 2 +1] = (uint8_t)(data[i] >> 8); // MSB
// buffer[i * 2 ] = (uint8_t)(data[i] & 0xFF); // LSB
}
ret = eeprom_write_page(start_address, buffer);
if (ret != NRF_SUCCESS) {
return ret;
}
// Write remaining 16 uint16_t values (32 bytes)
for (size_t i = 0; i < 16; i++) {
buffer[i * 2] = (uint8_t)(data[i + 32] >> 8);
buffer[i * 2 + 1] = (uint8_t)(data[i + 32] & 0xFF);
}
ret = eeprom_write_page(start_address + EEPROM_PAGE_SIZE, buffer); // next page
return ret;
}
ret_code_t eeprom_read_uint16_array(uint16_t start_address, uint16_t *data, size_t count)
{
if (count != 48) {
DBG_PRINTF("Error: This function is only for reading exactly 48 uint16_t values.\n");
return NRF_ERROR_INVALID_PARAM;
}
uint8_t buffer[EEPROM_PAGE_SIZE];
ret_code_t ret;
// Read first 64 bytes (32 uint16_t)
ret = eeprom_read_bytes(start_address, buffer, EEPROM_PAGE_SIZE);
if (ret != NRF_SUCCESS) {
return ret;
}
for (size_t i = 0; i < 32; i++) {
data[i] = ((uint16_t)buffer[i * 2] << 8) | buffer[i * 2 + 1];
}
// Read next 32 bytes (16 uint16_t)
ret = eeprom_read_bytes(start_address + EEPROM_PAGE_SIZE, buffer, 32);
if (ret != NRF_SUCCESS) {
return ret;
}
for (size_t i = 0; i < 16; i++) {
data[i + 32] = ((uint16_t)buffer[i * 2] << 8) | buffer[i * 2 + 1];
}
return NRF_SUCCESS;
}
ret_code_t eeprom_read_page(uint16_t mem_address, uint8_t *data)
{
uint8_t addr_buf[2];
ret_code_t ret;
addr_buf[0] = (uint8_t)(mem_address >> 8);
addr_buf[1] = (uint8_t)(mem_address & 0xFF);
// Send memory address first
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, addr_buf, 2, true);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM set read address failed (code: %d)\n", ret);
return ret;
}
// Read the page
ret = icm42670_twi_rx(EEPROM_I2C_ADDRESS, data, EEPROM_PAGE_SIZE);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM read failed (code: %d)\n", ret);
return ret;
}
DBG_PRINTF("EEPROM read \n");
for (int i = 0; i < EEPROM_PAGE_SIZE; i++) {
DBG_PRINTF("%02X\n", data[i]);
}
DBG_PRINTF("\r\n");
return NRF_SUCCESS;
}
ret_code_t eeprom_write_byte(uint16_t mem_address, uint8_t data)
{
uint8_t buffer[3]; // 2 bytes for address + 1 byte data
ret_code_t ret;
buffer[0] = (uint8_t)(mem_address >> 8); // MSB of address
buffer[1] = (uint8_t)(mem_address & 0xFF); // LSB of address
buffer[2] = data;
DBG_PRINTF("EEPROM write byte %02X,%02X,%02X\n", buffer[0], buffer[1], buffer[2]);
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, buffer, 3, false);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM write byte failed (code: %d)\n", ret);
return ret;
}
// Wait for internal EEPROM write cycle (typically ~5ms)
for (int i = 0; i < 100; i++) {
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, NULL, 0, false);
if (ret == NRF_SUCCESS)
break;
nrf_delay_us(100); // Wait 100us before retry
}
return NRF_SUCCESS;
}
ret_code_t eeprom_read_byte(uint16_t mem_address, uint8_t *data)
{
uint8_t addr_buf[2];
ret_code_t ret;
addr_buf[0] = (uint8_t)(mem_address >> 8); // MSB of address
addr_buf[1] = (uint8_t)(mem_address & 0xFF); // LSB of address
// Send memory address
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, addr_buf, 2, true);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM set address failed (code: %d)\n", ret);
return ret;
}
// Read 1 byte
ret = icm42670_twi_rx(EEPROM_I2C_ADDRESS, data, 1);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM read byte failed (code: %d)\n", ret);
return ret;
}
return NRF_SUCCESS;
}
ret_code_t eeprom_write_bytes(uint16_t mem_address, const uint8_t *data, size_t length)
{
ret_code_t ret;
size_t bytes_written = 0;
while (bytes_written < length) {
size_t page_offset = mem_address % EEPROM_PAGE_SIZE;
size_t bytes_to_write = EEPROM_PAGE_SIZE - page_offset;
if (bytes_to_write > (length - bytes_written)) {
bytes_to_write = length - bytes_written;
}
uint8_t buffer[2 + EEPROM_PAGE_SIZE]; // 2-byte addr + up to 64 data bytes
buffer[0] = (uint8_t)(mem_address >> 8);
buffer[1] = (uint8_t)(mem_address & 0xFF);
memcpy(&buffer[2], &data[bytes_written], bytes_to_write);
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, buffer, bytes_to_write + 2, false);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM write error at addr 0x%04X\n", mem_address);
return ret;
}
// Wait for internal EEPROM write cycle (typically ~5ms)
for (int i = 0; i < 100; i++) {
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, NULL, 0, false);
if (ret == NRF_SUCCESS)
break;
nrf_delay_us(100); // Wait 100us before retry
}
mem_address += bytes_to_write;
bytes_written += bytes_to_write;
}
return NRF_SUCCESS;
}
ret_code_t eeprom_read_bytes(uint16_t mem_address, uint8_t *data, size_t length)
{
ret_code_t ret;
uint8_t addr_buf[2];
addr_buf[0] = (uint8_t)(mem_address >> 8);
addr_buf[1] = (uint8_t)(mem_address & 0xFF);
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, addr_buf, 2, true); // send addr, no stop
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM read set address failed\n");
return ret;
}
ret = icm42670_twi_rx(EEPROM_I2C_ADDRESS, data, length);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM read failed\n");
return ret;
}
return NRF_SUCCESS;
}
ret_code_t eeprom_write_word(uint16_t mem_address, uint16_t data)
{
uint8_t buffer[4]; // 2 bytes for address + 2 bytes for data
ret_code_t ret;
buffer[0] = (uint8_t)(mem_address >> 8); // MSB of address
buffer[1] = (uint8_t)(mem_address & 0xFF); // LSB of address
buffer[2] = (uint8_t)(data & 0xFF); // LSB of data
buffer[3] = (uint8_t)(data >> 8); // MSB of data
DBG_PRINTF("EEPROM write word %02X,%02X,%02X,%02X\n", buffer[0], buffer[1], buffer[2], buffer[3]);
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, buffer, 4, false);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM write word failed (code: %d)\n", ret);
return ret;
}
// Wait for internal write cycle (~5ms typical)
for (int i = 0; i < 100; i++) {
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, NULL, 0, false);
if (ret == NRF_SUCCESS)
break;
nrf_delay_us(100); // 100us delay between polling
}
return NRF_SUCCESS;
}
ret_code_t eeprom_read_word(uint16_t mem_address, uint16_t *data)
{
uint8_t addr_buf[2];
uint8_t read_buf[2];
ret_code_t ret;
addr_buf[0] = (uint8_t)(mem_address >> 8);
addr_buf[1] = (uint8_t)(mem_address & 0xFF);
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, addr_buf, 2, true);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM set address for word read failed (code: %d)\n", ret);
return ret;
}
ret = icm42670_twi_rx(EEPROM_I2C_ADDRESS, read_buf, 2);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM read word failed (code: %d)\n", ret);
return ret;
}
*data = ((uint16_t)read_buf[1] << 8) | read_buf[0]; // Little-endian
return NRF_SUCCESS;
}
ret_code_t eeprom_write_uint32(uint16_t mem_address, uint32_t data)
{
uint8_t buffer[6]; // 2 bytes address + 4 bytes data
ret_code_t ret;
buffer[0] = (uint8_t)(mem_address >> 8); // MSB of address
buffer[1] = (uint8_t)(mem_address & 0xFF); // LSB of address
buffer[2] = (uint8_t)(data >> 24);
buffer[3] = (uint8_t)(data >> 16);
buffer[4] = (uint8_t)(data >> 8);
buffer[5] = (uint8_t)(data);
DBG_PRINTF("EEPROM write uint32: %02X %02X %02X %02X %02X %02X\n",
buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, buffer, 6, false);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM write uint32 failed (code: %d)\n", ret);
return ret;
}
// Wait for internal EEPROM write cycle
for (int i = 0; i < 100; i++) {
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, NULL, 0, false);
if (ret == NRF_SUCCESS)
break;
nrf_delay_us(100);
}
return NRF_SUCCESS;
}
ret_code_t eeprom_read_uint32(uint16_t mem_address, uint32_t *data)
{
uint8_t addr_buf[2];
uint8_t data_buf[4];
ret_code_t ret;
addr_buf[0] = (uint8_t)(mem_address >> 8);
addr_buf[1] = (uint8_t)(mem_address & 0xFF);
DBG_PRINTF("EEPROM address:%02X,%02X \n",addr_buf[0],addr_buf[1]);
// Send memory address to read from
ret = icm42670_twi_tx(EEPROM_I2C_ADDRESS, addr_buf, 2, true);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM read (addr phase) failed (code: %d)\n", ret);
return ret;
}
// Read 4 bytes of data
ret = icm42670_twi_rx(EEPROM_I2C_ADDRESS, data_buf, 4);
if (ret != NRF_SUCCESS) {
DBG_PRINTF("EEPROM read (data phase) failed (code: %d)\n", ret);
return ret;
}
*data = ((uint32_t)data_buf[0] << 24) |
((uint32_t)data_buf[1] << 16) |
((uint32_t)data_buf[2] << 8) |
((uint32_t)data_buf[3]);
DBG_PRINTF("EEPROM read uint32: %02X %02X %02X %02X -> %08X\n",
data_buf[0], data_buf[1], data_buf[2], data_buf[3], *data);
return NRF_SUCCESS;
}

View File

@@ -0,0 +1,68 @@
/*******************************************************************************
* @file app_raw_main.h
******************************************************************************/
#ifndef _CAT_INTERFACE_H_
#define _CAT_INTERFACE_H_
#include "sdk_config.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include "nordic_common.h"
#include "nrf.h"
#include "sdk_errors.h"
#define EEPROM_I2C_ADDRESS 0x50
#define EEPROM_PAGE_SIZE 64
#define SERIAL_ADDRESS 0x0030
ret_code_t encrypt_data(const uint8_t *input, size_t length, uint8_t *output, size_t *output_len);
ret_code_t decrypt_data(const uint8_t *input, size_t length, uint8_t *output);
ret_code_t eeprom_write_encrypted(uint16_t mem_address, const uint8_t *plaintext, size_t length);
ret_code_t eeprom_read_decrypted(uint16_t mem_address, uint8_t *plaintext, size_t original_length);
ret_code_t eeprom_read_page(uint16_t mem_address, uint8_t *data);
ret_code_t eeprom_write_page(uint16_t mem_address, const uint8_t *data);
ret_code_t eeprom_write_byte(uint16_t mem_address, uint8_t data);
ret_code_t eeprom_read_byte(uint16_t mem_address, uint8_t *data);
ret_code_t eeprom_write_bytes(uint16_t mem_address, const uint8_t *data, size_t length);
ret_code_t eeprom_read_bytes(uint16_t mem_address, uint8_t *data, size_t length);
ret_code_t eeprom_write_uint16_array(uint16_t start_address, const uint16_t *data, size_t count);
ret_code_t eeprom_read_uint16_array(uint16_t start_address, uint16_t *data, size_t count);
ret_code_t eeprom_write_word(uint16_t mem_address, uint16_t data);
ret_code_t eeprom_read_word(uint16_t mem_address, uint16_t *data);
void eeprom_uninitialize(void);
void eeprom_initialize(void);
ret_code_t eeprom_initialize_safe(void);
ret_code_t eeprom_write_uint32(uint16_t mem_address, uint32_t data);
ret_code_t eeprom_read_uint32(uint16_t mem_address, uint32_t *data);
#endif /* */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
/*******************************************************************************
* @file cmd.h
* @brief VivaMayo Command Handler Interface
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details Firmware-specific command interface for VivaMayo.
* Core types (ParsedCmd, CmdEntry) are defined in parser.h
* Command table (g_cmd_table) is defined in cmd.c
******************************************************************************/
#ifndef CMD_H
#define CMD_H
/* Include parser.h for ParsedCmd, CmdEntry, cmd_handler_t types */
#include "parser.h"
/*==============================================================================
* @section UTIL Utility Functions (for use by handlers)
* @brief Convenience wrappers for parser utility functions
*============================================================================*/
/**
* @brief Extract uint16 from ParsedCmd data (little endian)
* @param cmd Parsed command
* @param word_index Word index (0-based)
* @param out Output value
* @return true on success
*/
bool cmd_get_u16(const ParsedCmd *cmd, uint8_t word_index, uint16_t *out);
/**
* @brief Extract ASCII string from ParsedCmd data
* @param cmd Parsed command
* @param offset Byte offset in data
* @param out Output buffer
* @param max_len Maximum length
*/
void cmd_get_ascii(const ParsedCmd *cmd, uint8_t offset, char *out, uint8_t max_len);
#endif /* CMD_H */

View File

@@ -0,0 +1,318 @@
/*******************************************************************************
* @file cmd_parse.c
* @brief Command Parser Wrapper (Legacy Compatibility)
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-02-04
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details This file provides:
* - Legacy compatibility wrapper (received_command_process)
* - Utility functions (error handling, validation)
* - EEPROM read functions
*
* Actual parsing is handled by mt_parser/parser.c
* Command handlers are in cmd/cmd.c
******************************************************************************/
#include "cmd_parse.h"
#include "debug_print.h"
#include "parser.h"
#include "i2c_manager.h"
#include "app_timer.h"
#include "config/device_config.h"
#include "ble/ble_data_tx.h"
#include "ble_nus.h"
#include "crc16.h"
#include "cat_interface.h"
#include "storage/dr_mem.h"
#include <stdarg.h>
#include <string.h>
/*==============================================================================
* @section DEFINES Constants
*============================================================================*/
#define err_code1 65535 /* length error */
#define err_code2 65534 /* activate error */
#define err_code3 65533 /* param error */
#define err_code4 65532 /* ? missing */
#define err_code5 65531 /* CMD wrong */
#define err_code6 65530 /* CRC wrong */
/*==============================================================================
* @section EXTERN External Declarations
*============================================================================*/
extern uint8_t m_reset_status;
/* Note: SERIAL_NO, bond_data_delete, m_static_passkey, m_pd_adc_cnt,
m_pd_delay_us, m_life_cycle are declared in device_config.h */
/* Defined in measurements.h */
extern uint16_t led_pd_dac_v[];
/* Defined in main.c */
extern char ble_tx_buffer[];
extern volatile bool processing;
extern bool ble_got_new_data;
/* BLE buffer (defined in ble_data_tx.c) */
extern uint8_t ble_bin_buffer[];
/* Parser globals (defined in parser.c) */
extern dr_platform_if_t g_plat;
extern bool g_log_enable;
/*==============================================================================
* @section GLOBALS Global Variables (used by other modules)
*============================================================================*/
uint8_t resetCount = 0; /**< Reset counter */
bool info4 = false; /**< Additional info flag for PD Full mode */
/*==============================================================================
* @section STATIC Static Variables
*============================================================================*/
static uint32_t processing_start_tick = 0;
/*==============================================================================
* @section UTIL Utility Functions
*============================================================================*/
/**
* @brief Internal log function for parser
*/
static void log_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
/*==============================================================================
* @section CRC CRC Functions
*============================================================================*/
bool crc16_check(uint8_t const *p_data, uint32_t data_len, uint16_t expected_crc)
{
uint16_t computed_crc = crc16_compute(p_data, data_len, NULL);
return (computed_crc == expected_crc);
}
bool crc16_check_packet(uint8_t const *packet, uint32_t packet_len)
{
if (packet_len < 2) return false;
uint32_t data_len = packet_len - 2;
uint16_t expected_crc = (packet[packet_len - 1] << 8) | packet[packet_len - 2];
return crc16_check(packet, data_len, expected_crc);
}
/*==============================================================================
* @section ERROR Error Handling Functions
*============================================================================*/
bool length_error(const char *cmd, uint8_t target_length, uint8_t length)
{
if (target_length == length) {
return true;
}
char resp_error[4];
resp_error[0] = 'r';
resp_error[1] = cmd[1];
resp_error[2] = cmd[2];
resp_error[3] = '!';
single_format_data(ble_bin_buffer, resp_error, err_code1);
binary_tx_handler(ble_bin_buffer, 3);
return false;
}
bool activate_error(const char *cmd, bool device_status)
{
if (device_status == true) {
return true;
}
char resp_error[4];
resp_error[0] = 'r';
resp_error[1] = cmd[1];
resp_error[2] = cmd[2];
resp_error[3] = '!';
single_format_data(ble_bin_buffer, resp_error, err_code2);
binary_tx_handler(ble_bin_buffer, 3);
return false;
}
void param_error(const char *cmd)
{
char resp_error[4];
resp_error[0] = 'r';
resp_error[1] = cmd[1];
resp_error[2] = cmd[2];
resp_error[3] = '!';
single_format_data(ble_bin_buffer, resp_error, err_code3);
binary_tx_handler(ble_bin_buffer, 3);
}
void quest_error(const char *cmd)
{
char resp_error[4];
const char pass_init[6] = "123456";
if ((cmd[0] == '*') && (cmd[1] == '*') && (cmd[2] == '*') && (cmd[3] == '*')) {
if (dr_memWrite("passkey", (uint8_t *)pass_init, 6) != NRF_SUCCESS) {
DBG_PRINTF("ERR!!! passkey write fail\r\n\r\n");
}
resp_error[0] = '*';
resp_error[1] = cmd[1];
resp_error[2] = cmd[2];
resp_error[3] = '*';
single_format_data(ble_bin_buffer, resp_error, err_code4);
binary_tx_handler(ble_bin_buffer, 3);
} else {
resp_error[0] = 'r';
resp_error[1] = cmd[1];
resp_error[2] = cmd[2];
resp_error[3] = '!';
single_format_data(ble_bin_buffer, resp_error, err_code4);
binary_tx_handler(ble_bin_buffer, 3);
}
}
/*==============================================================================
* @section INIT_READ Initial Values Read (via dr_mem API)
*============================================================================*/
ret_code_t eeprom_init_values_read(void)
{
ret_code_t err_code;
/* FDS entries: Serial Number (12B, AES) */
err_code = dr_memRead("serial_no", (uint8_t *)SERIAL_NO, 12);
if (err_code != NRF_SUCCESS) return err_code;
DBG_PRINTF("\r\n SN:%s \r\n", SERIAL_NO);
/* FDS entries: Passkey (6B, AES) */
err_code = dr_memRead("passkey", (uint8_t *)m_static_passkey, 6);
if (err_code != NRF_SUCCESS) return err_code;
DBG_PRINTF("\r\n passkey-0 :%s \n", m_static_passkey);
/* FDS entries: Bond data delete (1B) */
{
uint8_t raw = 0;
err_code = dr_memRead("bond_delete", &raw, 1);
if (err_code != NRF_SUCCESS) return err_code;
bond_data_delete = (raw != 0);
}
DBG_PRINTF("\r\n bond_data_delete :%d \n", bond_data_delete);
/* FDS entries: Reset status (1B) */
err_code = dr_memRead("reset_status", &m_reset_status, 1);
if (err_code != NRF_SUCCESS) return err_code;
/* FDS entries: PD ADC count (1B) */
err_code = dr_memRead("pd_adc_cnt", &m_pd_adc_cnt, 1);
if (err_code != NRF_SUCCESS) return err_code;
if (m_pd_adc_cnt == 0 || m_pd_adc_cnt >= 255) {
m_pd_adc_cnt = 8;
}
/* FDS entries: PD delay (2B) */
err_code = dr_memRead("pd_delay", &m_pd_delay_us, 2);
if (err_code != NRF_SUCCESS) return err_code;
if (m_pd_delay_us < 5000 || m_pd_delay_us > 30000) {
m_pd_delay_us = 8000;
}
/* W25Q32 entries: AGC Gain array (96B) */
err_code = dr_memRead("agc_gain", led_pd_dac_v, 96);
if (err_code != NRF_SUCCESS) return err_code;
/* W25Q32 entries: Life cycle (4B) */
err_code = dr_memRead("life_cycle", &m_life_cycle, 4);
if (err_code != NRF_SUCCESS) return err_code;
DBG_PRINTF("\r\n m_life_cycle:%u, m_pd_delay_us:%u, m_pd_adc_cnt:%u \r\n",
m_life_cycle, m_pd_delay_us, m_pd_adc_cnt);
return NRF_SUCCESS;
}
/*==============================================================================
* @section API Main Entry Point
*============================================================================*/
/**
* @brief Process received command from UART or BLE
* @details Initializes parser on first call, then delegates to dr_cmd_parser
*/
void received_command_process(uint8_t const *data_array, which_cmd_t cmd_t, uint8_t length)
{
uint8_t r_data[BLE_NUS_MAX_DATA_LEN] = {0};
int parser_result;
(void)cmd_t; /* Currently not used by new parser */
ble_got_new_data = true;
memset(ble_tx_buffer, 0, BLE_NUS_MAX_DATA_LEN);
/* Copy data to local buffer */
for (uint16_t i = 0; i < length; i++) {
r_data[i] = data_array[i];
}
DBG_PRINTF("data : %s\r\n", r_data);
DBG_PRINTF("Length : %d\r\n", length);
/* Initialize parser (once) */
static bool parser_initialized = false;
if (!parser_initialized) {
g_plat.log = log_printf;
g_plat.tx_bin = binary_tx_handler;
g_plat.crc_check = true; /* CRC enabled */
g_log_enable = true;
parser_initialized = true;
DBG_PRINTF(">>> Parser initialized (mt_parser)\r\n");
}
/* Check if already processing */
if (processing == true) {
uint32_t now = app_timer_cnt_get();
if (processing_start_tick == 0) {
processing_start_tick = now;
}
/* Timeout check (5 seconds) */
if (app_timer_cnt_diff_compute(now, processing_start_tick) > APP_TIMER_TICKS(5000)) {
processing = false;
processing_start_tick = 0;
DBG_PRINTF("processing timeout -> force reset to false\r\n");
} else {
DBG_PRINTF("Busy - command ignored\r\n");
return;
}
}
/* Call mt_parser */
parser_result = dr_cmd_parser(r_data, length);
if (parser_result > 0) {
DBG_PRINTF(">>> Handled by mt_parser (result=%d)\r\n", parser_result);
} else if (parser_result == 9) {
DBG_PRINTF(">>> CRC/Parse error\r\n");
} else {
DBG_PRINTF(">>> Unknown command\r\n");
}
}

View File

@@ -0,0 +1,120 @@
/*******************************************************************************
* @file cmd_parse.h
* @brief Command Parser Interface (Legacy Compatibility Layer)
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-02-04
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details This header provides backward compatibility with existing code.
* Actual parsing is handled by mt_parser.
******************************************************************************/
#ifndef _CMD_PARSE_H_
#define _CMD_PARSE_H_
#include <stdint.h>
#include <stdbool.h>
#include "main.h"
/*==============================================================================
* @section API Command Processing Entry Point
*============================================================================*/
/**
* @brief Process received command from UART or BLE
* @param data_array Raw command data
* @param cmd_t Command source (CMD_UART or CMD_BLE)
* @param length Data length
*/
void received_command_process(uint8_t const *data_array, which_cmd_t cmd_t, uint8_t length);
/*==============================================================================
* @section UTIL Error Handling Functions
*============================================================================*/
/**
* @brief Send parameter error response
* @param cmd Command tag that caused the error
*/
void param_error(const char *cmd);
/**
* @brief Send activation error response
* @param cmd Command tag
* @param device_status Current device status
* @return true if error occurred
*/
bool activate_error(const char *cmd, bool device_status);
/**
* @brief Send length error response
* @param cmd Command tag
* @param target_length Expected length
* @param length Actual length
* @return true if error occurred
*/
bool length_error(const char *cmd, uint8_t target_length, uint8_t length);
/**
* @brief Send quest error response
* @param cmd Command tag
*/
void quest_error(const char *cmd);
/*==============================================================================
* @section VALID Validation Functions
*============================================================================*/
/**
* @brief Validate serial number format
* @param serial Serial number string
* @return true if valid
*/
bool is_valid_serial_no(const char *serial);
/**
* @brief Validate passkey format
* @param passkey Passkey string
* @return true if valid
*/
bool is_valid_passkey(const char *passkey);
/**
* @brief Hash serial number to passkey
* @param input Serial number string
* @return Hashed passkey value
*/
uint32_t serial_to_passkey_hash(const char *input);
/*==============================================================================
* @section CRC CRC Functions
*============================================================================*/
/**
* @brief Check CRC16 against expected value
* @param p_data Data buffer
* @param data_len Data length
* @param expected_crc Expected CRC value
* @return true if match
*/
bool crc16_check(uint8_t const *p_data, uint32_t data_len, uint16_t expected_crc);
/**
* @brief Check CRC16 in packet (last 2 bytes)
* @param packet Complete packet
* @param packet_len Packet length
* @return true if valid
*/
bool crc16_check_packet(uint8_t const *packet, uint32_t packet_len);
/*==============================================================================
* @section EEPROM EEPROM Functions
*============================================================================*/
/**
* @brief Read initial values from EEPROM
* @return NRF_SUCCESS on success
*/
ret_code_t eeprom_init_values_read(void);
#endif /* _CMD_PARSE_H_ */

View File

@@ -0,0 +1,222 @@
/*******************************************************************************
* @file device_config.c
* @brief Device Configuration Management
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details EEPROM configuration loading and default values.
******************************************************************************/
#include "device_config.h"
#include "nrf_delay.h"
#include "debug_print.h"
#include "cat_interface.h"
#include <cmd_parse.h>
#include <string.h>
#include "storage/dr_mem.h"
#include "fstorage.h"
/*==============================================================================
* GLOBAL VARIABLES
*============================================================================*/
char m_static_passkey[7] = "123456";
char SERIAL_NO[16] = "2025VIVAM00001";
char HW_NO[12] = "HW00000001";
bool bond_data_delete = true;
uint8_t m_pd_adc_cnt = 8;
uint16_t m_pd_delay_us = 8000;
uint32_t m_life_cycle = 0;
uint16_t led_pd_dac_v[48];
/*==============================================================================
* VALIDATION FUNCTIONS
*============================================================================*/
bool is_valid_serial_no(const char *serial)
{
if (serial == NULL) return false;
/* Check for reasonable serial number format */
for (int i = 0; i < 12; i++) {
char c = serial[i];
if (c == 0) break;
if (c == 0xFF) return false; /* Uninitialized EEPROM */
if (c < 0x20 || c > 0x7E) return false; /* Non-printable */
}
return true;
}
bool is_valid_passkey(const char *passkey)
{
if (passkey == NULL) return false;
/* Check for 6 digit passkey */
for (int i = 0; i < 6; i++) {
char c = passkey[i];
if (c == 0xFF) return false; /* Uninitialized EEPROM */
if (c < '0' || c > '9') return false; /* Not a digit */
}
return true;
}
/*==============================================================================
* CONFIGURATION LOADING
*============================================================================*/
void load_default_values(void)
{
/* Default Serial Number */
memset(SERIAL_NO, 0, sizeof(SERIAL_NO));
memcpy(SERIAL_NO, "2025AAAAT001", 12);
/* Default Passkey */
memset(m_static_passkey, 0, sizeof(m_static_passkey));
memcpy(m_static_passkey, "123456", 6);
/* Default measurement parameters */
m_pd_delay_us = 8000;
m_pd_adc_cnt = 8;
/* Default state */
bond_data_delete = 1;
/* Default AGC Gain values */
for (int i = 0; i < 48; i++) {
led_pd_dac_v[i] = 2048;
}
DBG_PRINTF("[CFG] Using default values\r\n");
}
static bool is_valid_hw_no(const char *hw)
{
if (hw == NULL) return false;
/* All 0x00 or 0xFF = uninitialized */
bool all_zero = true, all_ff = true;
for (int i = 0; i < 12; i++) {
if (hw[i] != 0x00) all_zero = false;
if ((uint8_t)hw[i] != 0xFF) all_ff = false;
}
if (all_zero || all_ff) return false;
/* Check printable ASCII */
for (int i = 0; i < 12; i++) {
char c = hw[i];
if (c == 0) break;
if (c < 0x20 || c > 0x7E) return false;
}
return true;
}
void load_device_configuration(void)
{
extern uint8_t m_reset_status;
ret_code_t err_code;
/* Start with RAM defaults, then override with FDS-saved values below.
* dr_memSetDefaults() removed: it was clobbering FDS-loaded m_config,
* wiping user-saved data (serial_no, passkey, etc.) on every boot
* when hw_no was still all-zeros. */
load_default_values();
/* Show m_config state right after config_load (before dr_memRead) */
{
char tmp[13] = {0};
memcpy(tmp, m_config.serial_no, 12);
DBG_PRINTF("[CFG] FDS serial_no='%s'\r\n", tmp);
memcpy(tmp, m_config.hw_no, 12);
DBG_PRINTF("[CFG] FDS hw_no='%s'\r\n", tmp);
}
/* Read FDS entries (already loaded by config_load() into m_config) */
err_code = dr_memRead("serial_no", (uint8_t *)SERIAL_NO, 12);
DBG_PRINTF("[CFG] dr_memRead S/N rc=%u valid=%d\r\n", err_code, is_valid_serial_no(SERIAL_NO));
DBG_PRINTF("[CFG] S/N hex: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\r\n",
(uint8_t)SERIAL_NO[0], (uint8_t)SERIAL_NO[1], (uint8_t)SERIAL_NO[2], (uint8_t)SERIAL_NO[3],
(uint8_t)SERIAL_NO[4], (uint8_t)SERIAL_NO[5], (uint8_t)SERIAL_NO[6], (uint8_t)SERIAL_NO[7],
(uint8_t)SERIAL_NO[8], (uint8_t)SERIAL_NO[9], (uint8_t)SERIAL_NO[10], (uint8_t)SERIAL_NO[11]);
if (err_code != NRF_SUCCESS || !is_valid_serial_no(SERIAL_NO)) {
DBG_PRINTF("[CFG] Invalid S/N - using default\r\n");
memset(SERIAL_NO, 0, sizeof(SERIAL_NO));
memcpy(SERIAL_NO, "2025AAAAT001", 12);
}
err_code = dr_memRead("passkey", (uint8_t *)m_static_passkey, 6);
if (err_code != NRF_SUCCESS || !is_valid_passkey(m_static_passkey)) {
DBG_PRINTF("[CFG] Invalid passkey - using default\r\n");
memset(m_static_passkey, 0, sizeof(m_static_passkey));
memcpy(m_static_passkey, "123456", 6);
}
{
uint8_t raw = 0;
dr_memRead("bond_delete", &raw, 1);
bond_data_delete = (raw != 0);
}
dr_memRead("reset_status", &m_reset_status, 1);
if (m_reset_status > 2) {
DBG_PRINTF("[CFG] Invalid reset_status (%u) - using 1\r\n", m_reset_status);
m_reset_status = 1;
}
dr_memRead("pd_adc_cnt", &m_pd_adc_cnt, 1);
if (m_pd_adc_cnt == 0 || m_pd_adc_cnt >= 255) {
DBG_PRINTF("[CFG] Invalid adc_cnt (%u) - using 8\r\n", m_pd_adc_cnt);
m_pd_adc_cnt = 8;
}
dr_memRead("pd_delay", &m_pd_delay_us, 2);
if (m_pd_delay_us < 5000 || m_pd_delay_us > 30000) {
DBG_PRINTF("[CFG] Invalid pd_delay (%u) - using 8000\r\n", m_pd_delay_us);
m_pd_delay_us = 8000;
}
/* W25Q32 entries - NOT read during boot
* W25Q32 is not initialized at boot (SPIM + BSP event conflict).
* agc_gain and life_cycle use RAM defaults from load_default_values().
* Actual W25Q32 values are loaded later when W25Q32 is powered on
* (via BLE command or measurement start). */
DBG_PRINTF("[CFG] Loaded: S/N=%s, delay=%u, cnt=%u, life=%u\r\n",
SERIAL_NO, m_pd_delay_us, m_pd_adc_cnt, m_life_cycle);
/* 3. config_data_t dump (FDS raw) */
{
char tmp[13] = {0};
#define CFG_DLY() nrf_delay_ms(10)
DBG_PRINTF("[CFG] === config_data_t (FDS) ===\r\n"); CFG_DLY();
DBG_PRINTF("[CFG] magic : 0x%08X\r\n", m_config.magic_number); CFG_DLY();
memcpy(tmp, m_config.hw_no, 12); tmp[12] = 0;
DBG_PRINTF("[CFG] hw_no : %s\r\n", tmp); CFG_DLY();
memcpy(tmp, m_config.serial_no, 12); tmp[12] = 0;
DBG_PRINTF("[CFG] serial : %s\r\n", tmp); CFG_DLY();
DBG_PRINTF("[CFG] passkey : %c%c%c%c%c%c\r\n",
m_config.static_passkey[0], m_config.static_passkey[1],
m_config.static_passkey[2], m_config.static_passkey[3],
m_config.static_passkey[4], m_config.static_passkey[5]); CFG_DLY();
DBG_PRINTF("[CFG] bond_del: %u\r\n", m_config.bond_data_delete); CFG_DLY();
DBG_PRINTF("[CFG] reset_st: %d\r\n", m_config.reset_status); CFG_DLY();
DBG_PRINTF("[CFG] adc_cnt : %u\r\n", m_config.pd_adc_cnt); CFG_DLY();
DBG_PRINTF("[CFG] pd_delay: %u\r\n", m_config.pd_delay_us); CFG_DLY();
/* W25Q32 entries */
DBG_PRINTF("[CFG] agc_gain[48]:\r\n"); CFG_DLY();
for (int i = 0; i < 48; i += 8) {
DBG_PRINTF(" [%2d] %u %u %u %u %u %u %u %u\r\n", i,
led_pd_dac_v[i], led_pd_dac_v[i+1],
led_pd_dac_v[i+2], led_pd_dac_v[i+3],
led_pd_dac_v[i+4], led_pd_dac_v[i+5],
led_pd_dac_v[i+6], led_pd_dac_v[i+7]); CFG_DLY();
}
DBG_PRINTF("[CFG] life : %u\r\n", m_life_cycle); CFG_DLY();
DBG_PRINTF("[CFG] ========================\r\n");
#undef CFG_DLY
}
}

View File

@@ -0,0 +1,63 @@
/*******************************************************************************
* @file device_config.h
* @brief Device Configuration Management
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @details EEPROM configuration loading and default values.
******************************************************************************/
#ifndef DEVICE_CONFIG_H
#define DEVICE_CONFIG_H
#include <stdint.h>
#include <stdbool.h>
/*==============================================================================
* CONSTANTS
*============================================================================*/
#define LED_NUM 24 /* Number of LEDs in system */
/*==============================================================================
* EXTERNAL VARIABLES
*============================================================================*/
extern char m_static_passkey[7];
extern char SERIAL_NO[16];
extern bool bond_data_delete;
extern uint8_t m_pd_adc_cnt;
extern uint16_t m_pd_delay_us;
extern uint32_t m_life_cycle;
extern uint16_t led_pd_dac_v[];
/*==============================================================================
* FUNCTION PROTOTYPES
*============================================================================*/
/**
* @brief Load device configuration from EEPROM
*/
void load_device_configuration(void);
/**
* @brief Load default configuration values
*/
void load_default_values(void);
/**
* @brief Validate serial number string
* @param serial Serial number string
* @return true if valid
*/
bool is_valid_serial_no(const char *serial);
/**
* @brief Validate passkey string
* @param passkey Passkey string
* @return true if valid
*/
bool is_valid_passkey(const char *passkey);
#endif /* DEVICE_CONFIG_H */

View File

@@ -0,0 +1,46 @@
// file: debug_print.h
#ifndef DEBUG_PRINT_H
#define DEBUG_PRINT_H
#include <stdbool.h>
#include <stdio.h>
#include "nrf_delay.h"
#include "SEGGER_RTT.h"
/* Output selection: 0=UART(printf), 1=RTT */
#define USE_RTT_OUTPUT 1
#define ENABLE_PRINTF 1 // Set to 0 to disable globally (DBG_PRINTF off, LOG_PRINTF still works)
#define LOG_FLUSH_MS 0 // UART flush delay (0 recommended due to blocking behavior)
#if USE_RTT_OUTPUT
/* RTT output */
#if ENABLE_PRINTF
#define DBG_PRINTF(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
#define DBG_PRINTF(...) // Do nothing
#endif
#define LOG_PRINTF(...) do { if (g_log_enable) SEGGER_RTT_printf(0, __VA_ARGS__); } while(0)
#else
/* UART output */
#if ENABLE_PRINTF
#define DBG_PRINTF(...) printf(__VA_ARGS__)
#else
#define DBG_PRINTF(...) // Do nothing
#endif
#if LOG_FLUSH_MS > 0
#define LOG_PRINTF(...) do { \
if (g_log_enable) { \
printf(__VA_ARGS__); \
nrf_delay_ms(LOG_FLUSH_MS); \
} \
} while(0)
#else
#define LOG_PRINTF(...) do { if (g_log_enable) printf(__VA_ARGS__); } while(0)
#endif
#endif
/* Log macro linked to g_log_enable flag (runtime controllable) */
extern bool g_log_enable;
#endif // DEBUG_PRINT_H

View File

@@ -0,0 +1,603 @@
# nRF52840 내장 Flash (FDS) 사용 가이드
## 1. 개요
### FDS (Flash Data Storage)란?
Nordic SDK에서 제공하는 내장 Flash 저장소 라이브러리.
SoftDevice와 함께 동작하며, **자동 Wear Leveling**과 **Garbage Collection**을 지원한다.
본 프로젝트에서는 디바이스 설정(시리얼 번호, 패스키, 측정 파라미터 등)을 FDS에 저장하고,
`dr_memRead` / `dr_memWrite` 추상화 API를 통해 BLE 명령어로 읽기/쓰기한다.
### 저장소 구조
```
┌──────────────────────────────────────────────┐
│ BLE 명령어 │
│ (mrh?, mrs?, mwh?, mws?, ...) │
└──────────────┬───────────────────┬────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ dr_memRead/Write │ │ dr_memRead/Write │
│ (FDS backend) │ │ (W25Q32 backend) │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 내장 Flash (FDS) │ │ 외장 Flash (W25Q32) │
│ - config_data_t │ │ - AGC Gain (96B) │
│ - 41 bytes packed │ │ - Life Cycle (4B) │
│ - Wear Leveling │ │ - SPI 인터페이스 │
│ - RBP 보호 │ │ - 4MB │
└──────────────────────┘ └──────────────────────┘
```
### FDS vs 외장 W25Q32RV 비교
| 항목 | 내장 FDS | 외장 W25Q32RV |
|------|----------|---------------|
| 용량 | ~400KB (50 pages x 4KB x 2048 words) | 4MB |
| 인터페이스 | SoftDevice API | SPI (SPIM) |
| 자동 초기화 | `fds_init()` | 수동 (`w25q32_power_on()` + `w25q32_init()`) |
| Wear Leveling | FDS 내장 (자동) | 없음 (직접 구현) |
| 쓰기 단위 | 4 bytes (word) | 256 bytes (page) |
| 지우기 단위 | 자동 GC | 4KB (sector erase) |
| 부팅 시 사용 | 즉시 가능 | 불가 (SPIM + BSP 이벤트 충돌) |
| 용도 | 디바이스 설정 (7개 필드) | 캘리브레이션 + 데이터 (2개 필드) |
---
## 2. 데이터 구조
### config_data_t (fstorage.h)
FDS에 저장되는 디바이스 설정 구조체. `#pragma pack(1)`으로 패딩 없이 41 bytes.
```c
#pragma pack(1)
typedef struct
{
uint32_t magic_number; /* 4B - 0x20231226 (데이터 무결성 확인용) */
char hw_no[12]; /* 12B - HW 번호 (공장 입력) */
char serial_no[12]; /* 12B - 시리얼 번호 */
uint8_t static_passkey[6]; /* 6B - BLE 페어링 패스키 */
uint8_t bond_data_delete; /* 1B - 본딩 데이터 삭제 플래그 */
int8_t reset_status; /* 1B - 리셋 상태 */
uint8_t pd_adc_cnt; /* 1B - ADC 샘플링 횟수 */
uint16_t pd_delay_us; /* 2B - PD 지연 시간 (us) */
} config_data_t;
```
### 메모리 레이아웃 (바이트 오프셋)
```
Offset Size 필드 기본값 FDS에 저장
────── ──── ────────────── ──────────────── ──────────
0x00 4B magic_number 0x20231226 O
0x04 12B hw_no (0x00 x 12) O
0x10 12B serial_no "2025AAAAT001" O
0x1C 6B static_passkey "123456" O
0x22 1B bond_data_delete 1 O
0x23 1B reset_status 99 O
0x24 1B pd_adc_cnt 8 O
0x25 2B pd_delay_us 8000 O
────── ────
Total: 39B (packed) → FDS word 정렬: (39 + 3) / 4 = 10 words (40B)
```
### FDS 레코드 식별자
```c
#define CONFIG_FILE (0x8010) /* File ID */
#define CONFIG_REC_KEY (0x7010) /* Record Key */
#define CONFIG_MAGIC_NUMBER_VALUE (0x20231226)
```
전체 `config_data_t`를 **하나의 FDS 레코드**로 저장한다.
읽기/쓰기 시 구조체 전체를 한 번에 처리한다.
---
## 3. 부팅 시퀀스
### 초기화 순서 (main.c)
```
[6] ble_stack_init() ← SoftDevice 활성화 (FDS 필수 전제)
[6.5] fs_storage_init() ← FDS 이벤트 핸들러 등록 + fds_init()
config_load() ← FDS에서 config_data_t 로드 → m_config 전역변수
[6.6] load_device_configuration() ← m_config → 런타임 전역변수 복사
[7] gap_params_init() ← SERIAL_NO로 BLE 디바이스 이름 설정
[8] advertising_init() ← 광고 시작
```
**핵심**: FDS는 반드시 `ble_stack_init()` 이후에 초기화해야 한다.
SoftDevice가 활성화되어야 FDS 이벤트(SOC 이벤트)가 정상 발생한다.
### fs_storage_init() 동작
```c
void fs_storage_init(void)
{
ret_code_t rc;
/* 1. 이벤트 핸들러 등록 (반드시 fds_init 이전) */
rc = fds_register(fds_evt_handler);
APP_ERROR_CHECK(rc);
/* 2. FDS 초기화 시작 (비동기) */
rc = fds_init();
APP_ERROR_CHECK(rc);
/* 3. FDS_EVT_INIT 이벤트 대기 (최대 3초) */
wait_for_fds_ready();
/* 4. FDS 상태 확인 */
fds_stat_t stat = { 0 };
rc = fds_stat(&stat);
APP_ERROR_CHECK(rc);
}
```
### config_load() 동작 흐름
```
config_load() 시작
├─ fds_record_find() 호출
│ │
│ ├─ 실패 (rc != 0) ──→ 재시도 (최대 10회, 100ms 간격)
│ │ │
│ │ ├─ 10회 모두 실패 → 새 레코드 쓰기 (기본값)
│ │ └─ 성공 → 아래 진행
│ │
│ └─ 성공 (rc == 0)
│ │
│ ├─ fds_record_open()
│ │ ├─ 실패 → 레코드 삭제 + GC + 기본값 새로 쓰기
│ │ └─ 성공 → memcpy → m_config
│ │
│ ├─ fds_record_close()
│ │
│ └─ magic_number 검증
│ ├─ 불일치 → 레코드 삭제 + 기본값으로 덮어쓰기
│ └─ 일치 → "Loaded OK" (정상 완료)
└─ 새 레코드 쓰기 (기본값)
├─ fds_default_value_set()
├─ fds_record_write() (비동기)
├─ fds_flag_write == false 대기 (이벤트 핸들러에서 해제)
└─ config_load() 재진입 (goto cfg_load_start)
```
### 재시도 로직 (핵심!)
FDS가 완전히 준비되기 전에 `fds_record_find()`를 호출하면 실패할 수 있다 (rc=34313).
이를 "레코드 없음"으로 처리하면 기존 저장 데이터를 기본값으로 덮어쓰게 된다.
```c
uint8_t cfg_retry = 0;
cfg_load_start:
memset((char *)&desc, 0, sizeof(desc));
memset((char *)&tok, 0, sizeof(tok));
rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
/* FDS가 아직 완전히 준비되지 않았을 수 있음 - 기본값 쓰기 전 재시도 */
if (rc != NRF_SUCCESS && cfg_retry < 10) {
cfg_retry++;
nrf_delay_ms(100);
goto cfg_load_start;
}
```
**이 재시도 로직이 없으면**: 부팅할 때마다 저장된 설정이 기본값으로 덮어씌워질 수 있다.
---
## 4. 데이터 저장 (config_save)
### 동작 방식
```c
void config_save(void)
{
/* 1. 이전 FDS 작업 진행 중이면 스킵 (비차단) */
if (fds_flag_write) return;
/* 2. magic_number 보정 */
if (m_config.magic_number != CONFIG_MAGIC_NUMBER_VALUE)
m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
/* 3. 기존 레코드 찾기 */
rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
if (rc == NRF_SUCCESS) {
/* 4a. 기존 레코드 업데이트 (비동기) */
fds_flag_write = true;
rc = fds_record_update(&desc, &m_dummy_record);
/* Flash 공간 부족 시 GC 후 재시도 */
if (rc == FDS_ERR_NO_SPACE_IN_FLASH) {
fds_gc();
fds_record_update(&desc, &m_dummy_record);
}
} else {
/* 4b. 새 레코드 쓰기 (비동기) */
fds_flag_write = true;
rc = fds_record_write(&desc, &m_dummy_record);
}
}
```
### 비동기 처리 주의사항
`fds_record_update()` / `fds_record_write()`는 **비동기**로 동작한다.
호출 즉시 리턴하고, 실제 Flash 쓰기는 SoftDevice 이벤트 루프에서 수행된다.
```
config_save() 호출
├─ fds_flag_write = true
├─ fds_record_update() → 즉시 리턴 (NRF_SUCCESS)
│ ... BLE 이벤트 루프에서 실제 Flash 쓰기 수행 ...
└─ fds_evt_handler(FDS_EVT_UPDATE) 콜백
└─ fds_flag_write = false
```
**BLE 콜백 컨텍스트에서 config_save()를 호출할 때**:
`fds_flag_write``false`가 될 때까지 대기하면 **교착 상태**가 발생한다.
`config_save()`는 fire-and-forget 패턴으로 설계되어 있다.
### fds_evt_handler (이벤트 핸들러)
```c
static void fds_evt_handler(fds_evt_t const *p_evt)
{
fds_last_evt = p_evt->id;
switch (p_evt->id)
{
case FDS_EVT_INIT:
m_fds_initialized = true;
break;
case FDS_EVT_WRITE:
fds_flag_write = false;
break;
case FDS_EVT_UPDATE:
fds_flag_write = false;
/* 플래그에 따라 시스템 전원 제어 */
if (go_device_power_off) device_power_off();
if (go_sleep_mode_enter) sleep_mode_enter();
if (go_NVIC_SystemReset) NVIC_SystemReset();
break;
case FDS_EVT_DEL_RECORD:
case FDS_EVT_DEL_FILE:
case FDS_EVT_GC:
break;
}
}
```
**FDS_EVT_UPDATE에서 시스템 제어**: Flash 쓰기 완료 후 전원 OFF / 슬립 / 리셋을 수행한다.
이를 통해 설정 저장이 확실히 완료된 후에만 시스템 상태가 변경된다.
---
## 5. dr_mem 추상화 계층
### 구조
`dr_memRead()` / `dr_memWrite()`는 이름(key) 기반 메모리 접근 API이다.
내부적으로 룩업 테이블을 사용하여 FDS 또는 W25Q32 백엔드로 자동 라우팅한다.
```c
/* 사용 예시 */
dr_memWrite("serial_no", "2025VIVA0001", 12); /* → FDS에 저장 */
dr_memRead("serial_no", buf, 12); /* → FDS에서 읽기 */
dr_memWrite("agc_gain", gains, 96); /* → W25Q32에 저장 */
```
### 룩업 테이블 (dr_mem.c)
| Key | Backend | Offset | Size | 기본값 |
|-----|---------|--------|------|--------|
| `hw_no` | FDS | offsetof(hw_no) = 4 | 12B | "123456789012" |
| `serial_no` | FDS | offsetof(serial_no) = 16 | 12B | "2025AAAAT001" |
| `passkey` | FDS | offsetof(static_passkey) = 28 | 6B | "123456" |
| `bond_delete` | FDS | offsetof(bond_data_delete) = 34 | 1B | 1 |
| `reset_status` | FDS | offsetof(reset_status) = 35 | 1B | 99 |
| `pd_adc_cnt` | FDS | offsetof(pd_adc_cnt) = 36 | 1B | 8 |
| `pd_delay` | FDS | offsetof(pd_delay_us) = 37 | 2B | 8000 |
| `agc_gain` | W25Q32 | 0x000000 | 96B | 2048 x 48 |
| `life_cycle` | W25Q32 | 0x000060 | 4B | 0 |
### FDS 백엔드 동작
**쓰기 (dr_memWrite → fds_backend_write)**:
```
1. 룩업 테이블에서 entry 찾기
2. m_config의 해당 필드에 memcpy
3. config_save() 호출 (비동기 FDS 기록)
```
**읽기 (dr_memRead → fds_backend_read)**:
```
1. 룩업 테이블에서 entry 찾기
2. m_config의 해당 필드에서 memcpy (RAM 읽기, Flash 접근 없음)
```
**중요**: `dr_memRead()`는 항상 RAM(`m_config`)에서 읽는다.
부팅 시 `config_load()`가 Flash → RAM 복사를 한 번 수행하므로,
이후 `dr_memRead()`는 Flash에 직접 접근하지 않는다.
---
## 6. BLE 명령어 인터페이스
### 읽기 명령어 (mr* → rr*)
| 명령어 | Key | 응답 태그 | 응답 형식 | BLE 패킷 크기 |
|--------|-----|----------|-----------|--------------|
| `mrh?` | hw_no | `rrh:` | ASCII 12B | 4+12 = 16B |
| `mrs?` | serial_no | `rrs:` | ASCII 12B | 4+12 = 16B |
| `mrp?` | passkey | `rrp:` | Byte 6B | 4+6 = 10B |
| `mrb?` | bond_delete | `rrb:` | uint16 | 4+2 = 6B |
| `mrr?` | reset_status | `rrr:` | uint16 | 4+2 = 6B |
| `mrc?` | pd_adc_cnt | `rrc:` | uint16 | 4+2 = 6B |
| `mrd?` | pd_delay | `rrd:` | uint16 | 4+2 = 6B |
| `mrg?` | agc_gain | `rrg:` | uint16 x 48 | 4+96 = 100B |
| `mrl?` | life_cycle | `rrl:` | uint16 x 2 (hi, lo) | 4+4 = 8B |
### 쓰기 명령어 (mw* → rw*)
| 명령어 | Key | 입력 형식 | 응답 태그 | 응답 의미 |
|--------|-----|----------|----------|----------|
| `mwh?DATA` | hw_no | ASCII 12B | `rwh:` | 0=성공, 1=실패 |
| `mws?DATA` | serial_no | ASCII 12B | `rws:` | 0=성공, 1=실패 |
| `mwp?DATA` | passkey | ASCII 6B | `rwp:` | 0=성공, 1=실패 |
| `mwb?VAL` | bond_delete | uint16 | `rwb:` | 0=성공, 1=실패 |
| `mwr?VAL` | reset_status | uint16 | `rwr:` | 0=성공, 1=실패 |
| `mwc?VAL` | pd_adc_cnt | uint16 | `rwc:` | 0=성공, 1=실패 |
| `mwd?VAL` | pd_delay | uint16 | `rwd:` | 0=성공, 1=실패 |
| `mwg?V1,V2,...,V48` | agc_gain | uint16 x 48 | `rwg:` | 0=성공, 1=실패 |
| `mwl?HI,LO` | life_cycle | uint16 x 2 | `rwl:` | 0=성공, 1=실패 |
### 명령어 처리 흐름 예시
```
mws?2025VIVA0001 (BLE로 수신)
├─ Cmd_mws() 핸들러 호출
│ ├─ cmd_get_ascii(cmd, 0, serial, 12) → "2025VIVA0001"
│ ├─ dr_memWrite("serial_no", serial, 12)
│ │ ├─ find_entry("serial_no") → FDS backend, offset=16, size=12
│ │ ├─ memcpy(m_config + 16, "2025VIVA0001", 12)
│ │ └─ config_save() → FDS에 비동기 기록
│ └─ dr_ble_return_1("rws:", 0) → BLE로 성공 응답
└─ BLE 응답: "rws:" + 0x0000
```
```
mrs? (BLE로 수신)
├─ Cmd_mrs() 핸들러 호출
│ ├─ dr_memRead("serial_no", buf, 12)
│ │ ├─ find_entry("serial_no") → FDS backend, offset=16, size=12
│ │ └─ memcpy(buf, m_config + 16, 12) → "2025VIVA0001"
│ ├─ ascii_format_data(ble_bin_buffer, "rrs:", buf, 12)
│ └─ binary_tx_handler(ble_bin_buffer, 8)
└─ BLE 응답: "rrs:" + "2025VIVA0001"
```
---
## 7. 런타임 전역변수 연동
### 부팅 시 데이터 흐름
```
Flash (FDS Record)
├─ config_load()
│ └─ m_config (RAM 구조체) ← FDS에서 읽은 원본
├─ load_device_configuration()
│ ├─ dr_memRead("serial_no") → SERIAL_NO[] ← BLE 디바이스 이름
│ ├─ dr_memRead("passkey") → m_static_passkey[] ← BLE 패스키
│ ├─ dr_memRead("pd_delay") → m_pd_delay_us ← 측정 파라미터
│ ├─ dr_memRead("pd_adc_cnt")→ m_pd_adc_cnt ← 측정 파라미터
│ └─ ... (유효성 검증 + 기본값 폴백)
└─ gap_params_init()
└─ sd_ble_gap_device_name_set(SERIAL_NO) ← BLE 광고 이름
```
### 전역변수 목록
| 변수 | 타입 | 원본 (m_config) | 설명 |
|------|------|----------------|------|
| `SERIAL_NO[16]` | char[] | serial_no | BLE 디바이스 이름 |
| `m_static_passkey[7]` | char[] | static_passkey | BLE 페어링 패스키 |
| `m_pd_adc_cnt` | uint8_t | pd_adc_cnt | ADC 샘플링 횟수 |
| `m_pd_delay_us` | uint16_t | pd_delay_us | PD 지연 시간 |
| `bond_data_delete` | bool | bond_data_delete | 본딩 삭제 플래그 |
| `m_life_cycle` | uint32_t | (W25Q32) | 사용 횟수 |
| `led_pd_dac_v[48]` | uint16_t[] | (W25Q32) | AGC 게인 값 |
### 쓰기 시 RAM 동기화
`mw*` 명령으로 값을 변경할 때, 일부 명령어는 전역변수도 즉시 업데이트한다:
```c
/* mwc? - pd_adc_cnt 쓰기 */
dr_memWrite("pd_adc_cnt", &u8val, 1); // m_config + FDS 저장
m_pd_adc_cnt = u8val; // 전역변수 즉시 반영
/* mwd? - pd_delay 쓰기 */
dr_memWrite("pd_delay", &val, 2); // m_config + FDS 저장
m_pd_delay_us = val; // 전역변수 즉시 반영
```
**주의**: BLE 디바이스 이름(`SERIAL_NO`)은 부팅 시 한 번만 설정된다.
`mws?`로 시리얼 번호를 변경해도 재부팅 전까지 BLE 광고 이름은 바뀌지 않는다.
---
## 8. sdk_config.h 설정
### FDS 관련 설정
```c
/* FDS 활성화 */
#define FDS_ENABLED 1
/* 가상 페이지 수 (각 페이지 = FDS_VIRTUAL_PAGE_SIZE * 4 bytes) */
#define FDS_VIRTUAL_PAGES 50
/* 가상 페이지 크기 (words) - 기본 1024 words = 4KB */
#define FDS_VIRTUAL_PAGE_SIZE 2048
/* CRC 검사 (현재 비활성화) */
#define FDS_CRC_CHECK_ON_READ 0
#define FDS_CRC_CHECK_ON_WRITE 0
/* 내부 Flash 저장소 활성화 */
#define NRF_FSTORAGE_ENABLED 1
/* SOC 이벤트 핸들러 (FDS 이벤트 전달에 필수) */
#define NRF_SDH_SOC_ENABLED 1
```
### 저장 용량 계산
```
총 FDS 영역 = 50 pages × 2048 words × 4 bytes = 400KB
레코드 크기 = (39 + 3) / 4 = 10 words = 40 bytes
오버헤드 = 레코드 헤더 3 words = 12 bytes
실제 1 레코드 = 52 bytes (데이터 40B + 헤더 12B)
```
현재 프로젝트는 **1개의 레코드**만 사용하므로 용량은 충분하다.
50 페이지 설정은 Wear Leveling 수명을 극대화하기 위한 것이다.
---
## 9. 에러 코드
| 이름 | 값 | 의미 | 대응 |
|------|-----|------|------|
| `NRF_SUCCESS` | 0 | 성공 | - |
| `FDS_ERR_NOT_FOUND` | 34313 (0x8609) | 레코드 없음 | 새 레코드 쓰기 |
| `FDS_ERR_NO_SPACE_IN_FLASH` | 34050 (0x8502) | Flash 공간 부족 | `fds_gc()` 후 재시도 |
| `FDS_ERR_RECORD_TOO_LARGE` | 34051 (0x8503) | 레코드 크기 초과 | 구조체 크기 줄이기 |
| `FDS_ERR_NOT_INITIALIZED` | 34052 (0x8504) | FDS 미초기화 | 부팅 순서 확인 |
| `FDS_ERR_CRC_CHECK_FAILED` | - | CRC 불일치 | 레코드 삭제 + 재생성 |
---
## 10. 트러블슈팅
### 문제 1: 부팅 시 저장된 값이 기본값으로 초기화됨
**증상**: BLE 명령으로 값을 저장한 후 재부팅하면 기본값으로 돌아감.
BLE 읽기 명령(mr*)으로 읽으면 저장된 값이 정상 반환됨.
**원인**: `config_load()`에서 `fds_record_find()`가 첫 호출 시 실패 (rc=34313).
FDS가 완전히 초기화되기 전에 호출되어 "레코드 없음"으로 판단,
기존 데이터를 기본값으로 덮어씀.
**해결**: 재시도 로직 추가 (최대 10회, 100ms 간격).
```c
if (rc != NRF_SUCCESS && cfg_retry < 10) {
cfg_retry++;
nrf_delay_ms(100);
goto cfg_load_start;
}
```
### 문제 2: config_save() 호출 후 값이 저장되지 않음
**증상**: `dr_memWrite()` 호출 후 `config_save()` 리턴했지만 Flash에 기록 안 됨.
**원인**: `fds_flag_write`가 이미 `true`인 상태에서 `config_save()` 호출.
이전 FDS 작업이 완료되지 않아 스킵됨.
**해결**: RTT 로그에서 `[CFG_SAVE] busy, skipped` 메시지 확인.
연속 쓰기 시 적절한 간격을 두거나, 이전 쓰기 완료 후 재시도.
### 문제 3: wait_for_fds_ready()에서 타임아웃
**증상**: `[FDS] TIMEOUT!` 메시지 출력.
**원인**: `ble_stack_init()` 전에 `fs_storage_init()` 호출.
SoftDevice가 비활성 상태이면 FDS 이벤트가 발생하지 않음.
**해결**: main.c에서 초기화 순서 확인:
```
ble_stack_init(); // 반드시 먼저!
fs_storage_init(); // 그 다음
config_load();
```
### 문제 4: Flash 공간 부족 (FDS_ERR_NO_SPACE_IN_FLASH)
**증상**: `fds_record_update()` 실패, rc=34050.
**원인**: FDS는 업데이트 시 새 위치에 기록하고 기존을 무효화(invalidate).
GC(Garbage Collection) 없이 반복 업데이트하면 유효 공간 소진.
**해결**: `fds_gc()` 호출 후 재시도 (config_save에 이미 구현됨).
```c
if (rc == FDS_ERR_NO_SPACE_IN_FLASH) {
fds_gc();
rc = fds_record_update(&desc, &m_dummy_record);
}
```
### 문제 5: printf와 DBG_PRINTF 혼동
**증상**: RTT에서 FDS 관련 로그가 보이지 않음.
**원인**: `printf()`는 UART로 출력, `DBG_PRINTF()`는 RTT로 출력.
config_load() 내부에서 printf를 사용하면 RTT에서 볼 수 없음.
**해결**: FDS 관련 함수에서는 `DBG_PRINTF()` 사용.
---
## 11. 관련 파일
| 파일 | 역할 |
|------|------|
| [fstorage.c](../fstorage.c) | FDS 초기화, config_load(), config_save() |
| [fstorage.h](../fstorage.h) | config_data_t 구조체 정의 |
| [storage/dr_mem.c](../storage/dr_mem.c) | dr_memRead/Write 구현, 룩업 테이블 |
| [storage/dr_mem.h](../storage/dr_mem.h) | dr_mem API 선언 |
| [config/device_config.c](../config/device_config.c) | 부팅 시 설정 로드 + 유효성 검증 |
| [cmd/cmd.c](../cmd/cmd.c) | BLE 명령어 핸들러 (mr*/mw*) |
| [main.c](../main.c) | 초기화 순서 |
| [pca10056/s140/config/sdk_config.h](../pca10056/s140/config/sdk_config.h) | FDS SDK 설정 |
---
*문서 작성일: 2026-03-05*
*마지막 업데이트: FDS 재시도 로직, dr_mem 추상화 계층, BLE 명령어 인터페이스 추가*

View File

@@ -0,0 +1,939 @@
# FDS 내장 메모리 모듈 - 이식(Porting) 매뉴얼
> **대상**: Claude AI 또는 개발자가 이 문서를 읽고 새 프로젝트에 FDS 메모리 모듈을 이식할 수 있도록 작성됨.
> 모든 단계를 순서대로 실행하면 빌드 가능한 상태까지 도달한다.
---
## 목차
1. [모듈 개요](#1-모듈-개요)
2. [파일 목록 및 의존성](#2-파일-목록-및-의존성)
3. [Step 1: 파일 복사](#step-1-파일-복사)
4. [Step 2: config_data_t 커스터마이징](#step-2-config_data_t-커스터마이징)
5. [Step 3: dr_mem 룩업 테이블 수정](#step-3-dr_mem-룩업-테이블-수정)
6. [Step 4: device_config 전역변수 수정](#step-4-device_config-전역변수-수정)
7. [Step 5: BLE 명령어 핸들러 추가](#step-5-ble-명령어-핸들러-추가)
8. [Step 6: main.c 초기화 순서 통합](#step-6-mainc-초기화-순서-통합)
9. [Step 7: sdk_config.h 설정](#step-7-sdk_configh-설정)
10. [Step 8: Keil 프로젝트에 파일 추가](#step-8-keil-프로젝트에-파일-추가)
11. [Step 9: 빌드 및 검증](#step-9-빌드-및-검증)
12. [핵심 주의사항](#핵심-주의사항)
13. [체크리스트](#체크리스트)
---
## 1. 모듈 개요
### 이 모듈이 하는 일
nRF52840 내장 Flash에 디바이스 설정을 저장하고, BLE 명령어로 읽기/쓰기하는 완전한 시스템.
```
┌───────────────────────────────────────────────────────┐
│ BLE 명령어 │
│ mrh? mrs? mwh? mws? ... │
└───────────┬───────────────────────────┬───────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────────┐
│ cmd/cmd.c │ │ cmd/cmd.c │
│ Cmd_mrh(), Cmd_mrs() │ │ Cmd_mwh(), Cmd_mws() │
│ (읽기 핸들러) │ │ (쓰기 핸들러) │
└───────────┬───────────┘ └──────────┬────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────┐
│ storage/dr_mem.c │
│ dr_memRead() / dr_memWrite() │
│ (이름 기반 메모리 접근 - 룩업 테이블 라우팅) │
└──────────┬──────────────────────────┬────────────────┘
│ │
▼ ▼
┌────────────────────┐ ┌───────────────────────┐
│ FDS (내장 Flash) │ │ W25Q32 (외장 Flash) │
│ config_data_t │ │ (선택사항) │
│ fstorage.c │ │ w25q32 드라이버 │
└────────────────────┘ └───────────────────────┘
```
### 모듈 구성 요소
| 계층 | 역할 | 수정 필요? |
|------|------|-----------|
| **fstorage.c/h** | FDS 초기화, 레코드 읽기/쓰기 | 구조체만 수정 |
| **storage/dr_mem.c/h** | 이름 기반 추상화 API | 룩업 테이블 수정 |
| **config/device_config.c/h** | 부팅 시 전역변수 로드 | 전역변수 수정 |
| **cmd/cmd.c** (mr*/mw* 부분) | BLE 명령어 핸들러 | 필드에 맞게 수정 |
| **mt_parser** (외부 라이브러리) | 명령어 파싱/디스패치 | 수정 불필요 |
| **ble/ble_data_tx.c/h** | BLE 데이터 전송 | 수정 불필요 |
---
## 2. 파일 목록 및 의존성
### 복사해야 할 파일 (프로젝트 내부)
```
프로젝트 루트/
├── fstorage.c ← FDS 코어 (config_load, config_save)
├── fstorage.h ← config_data_t 정의
├── storage/
│ ├── dr_mem.c ← 이름 기반 메모리 API
│ └── dr_mem.h ← dr_memRead/Write 선언
├── config/
│ ├── device_config.c ← 부팅 시 설정 로드
│ └── device_config.h ← 전역변수 선언
└── cmd/
├── cmd.c ← BLE 명령어 핸들러 (mr*/mw* 부분 추가)
└── cmd.h ← cmd_get_u16, cmd_get_ascii 래퍼
```
### 외부 의존 라이브러리 (프로젝트 외부 - 이미 존재해야 함)
```
mt_parser/ ← 명령어 파싱 라이브러리 (공용)
├── parser.c ParsedCmd, CmdEntry, dr_cmd_parser()
├── parser.h 타입 정의
└── dr_util/
├── dr_util.c dr_ble_return_1(), dr_ble_return_2()
└── dr_util.h 함수 선언
```
### Nordic SDK 의존성 (sdk_config.h에서 활성화)
| 모듈 | 용도 | 필수? |
|------|------|------|
| `FDS` | Flash Data Storage | 필수 |
| `NRF_FSTORAGE` | Flash Storage 하위 계층 | 필수 |
| `NRF_SDH_SOC` | SoftDevice SOC 이벤트 | 필수 |
| `CRC16` | 패킷 CRC 검증 | 필수 |
| `NRF_PWR_MGMT` | 대기 시 전력 관리 | 필수 |
### 펌웨어 내부 의존 (이미 존재해야 함)
| 파일 | 제공하는 것 | 용도 |
|------|-----------|------|
| `main.h` | `which_cmd_t`, `BLE_CONNECTED_ST`, `binary_tx_handler()` 선언 | BLE 전송 |
| `ble/ble_data_tx.c/h` | `format_data()`, `ascii_format_data()`, `ble_bin_buffer[]` | 데이터 포맷팅 |
| `debug_print.h` | `DBG_PRINTF` 매크로 | 디버그 출력 |
| `power/power_ctrl.c` | `device_power_off()`, `sleep_mode_enter()` | 전원 제어 |
| `drivers/w25q32/w25q32.h` | `w25q32_read()`, `w25q32_write()` | 외장 Flash (선택) |
---
## Step 1: 파일 복사
### 1.1 디렉토리 생성
```bash
# 새 프로젝트 루트에서
mkdir -p storage
mkdir -p config
mkdir -p cmd # 이미 있으면 스킵
```
### 1.2 파일 복사
**VivaMyo 프로젝트에서 복사할 파일:**
```bash
# FDS 코어
cp <VivaMayo>/fstorage.c <새 프로젝트>/fstorage.c
cp <VivaMayo>/fstorage.h <새 프로젝트>/fstorage.h
# dr_mem 추상화
cp <VivaMayo>/storage/dr_mem.c <새 프로젝트>/storage/dr_mem.c
cp <VivaMayo>/storage/dr_mem.h <새 프로젝트>/storage/dr_mem.h
# device_config
cp <VivaMayo>/config/device_config.c <새 프로젝트>/config/device_config.c
cp <VivaMayo>/config/device_config.h <새 프로젝트>/config/device_config.h
```
**cmd/cmd.c는 복사하지 않음** - 기존 cmd.c에 핸들러 코드를 추가한다 (Step 5 참조).
### 1.3 debug_print.h 확인
새 프로젝트에 `debug_print.h`가 없으면 복사:
```bash
cp <VivaMayo>/debug_print.h <새 프로젝트>/debug_print.h
```
내용 (RTT/UART 선택 가능):
```c
#ifndef DEBUG_PRINT_H
#define DEBUG_PRINT_H
#include <stdbool.h>
#include <stdio.h>
#include "nrf_delay.h"
#include "SEGGER_RTT.h"
#define USE_RTT_OUTPUT 1 // 0=UART(printf), 1=RTT
#define ENABLE_PRINTF 1 // 0=DBG_PRINTF 비활성화
#if USE_RTT_OUTPUT
#if ENABLE_PRINTF
#define DBG_PRINTF(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
#define DBG_PRINTF(...)
#endif
#define LOG_PRINTF(...) do { if (g_log_enable) SEGGER_RTT_printf(0, __VA_ARGS__); } while(0)
#else
#if ENABLE_PRINTF
#define DBG_PRINTF(...) printf(__VA_ARGS__)
#else
#define DBG_PRINTF(...)
#endif
#define LOG_PRINTF(...) do { if (g_log_enable) printf(__VA_ARGS__); } while(0)
#endif
extern bool g_log_enable;
#endif
```
---
## Step 2: config_data_t 커스터마이징
### 2.1 fstorage.h 수정
새 프로젝트에 맞게 `config_data_t` 구조체를 수정한다.
**규칙:**
- 반드시 `#pragma pack(1)` 사용 (패딩 없이 연속 저장)
- 첫 번째 필드는 반드시 `uint32_t magic_number` (데이터 무결성 검증용)
- 나머지 필드는 프로젝트 요구사항에 맞게 정의
**VivaMayo 원본:**
```c
#pragma pack(1)
typedef struct
{
uint32_t magic_number; /* 4B - 고정 (수정 금지) */
char hw_no[12]; /* 12B - HW 번호 */
char serial_no[12]; /* 12B - 시리얼 번호 */
uint8_t static_passkey[6]; /* 6B - BLE 패스키 */
uint8_t bond_data_delete; /* 1B - 본딩 삭제 플래그 */
int8_t reset_status; /* 1B - 리셋 상태 */
uint8_t pd_adc_cnt; /* 1B - ADC 샘플 수 */
uint16_t pd_delay_us; /* 2B - PD 지연 시간 */
} config_data_t; /* 39 bytes */
```
**새 프로젝트용 예시 (커스터마이징):**
```c
#pragma pack(1)
typedef struct
{
uint32_t magic_number; /* 4B - 고정 (0x새날짜값) */
char hw_no[12]; /* 12B - HW 번호 */
char serial_no[12]; /* 12B - 시리얼 번호 */
uint8_t static_passkey[6]; /* 6B - BLE 패스키 */
uint8_t bond_data_delete; /* 1B - 본딩 삭제 */
int8_t reset_status; /* 1B - 리셋 상태 */
// ---- 여기서부터 프로젝트별 필드 추가/삭제 ----
uint8_t my_custom_field; /* 1B */
uint16_t my_custom_u16; /* 2B */
} config_data_t;
```
### 2.2 magic_number 값 변경
`fstorage.c`에서 magic_number를 새 날짜로 변경:
```c
#define CONFIG_MAGIC_NUMBER_VALUE (0x20260305) /* 새 프로젝트 날짜 */
```
**중요**: magic_number를 변경하면 기존 FDS 레코드는 무효화되고 기본값으로 초기화된다.
이것은 의도된 동작이다 (새 프로젝트이므로).
### 2.3 fds_default_value_set() 수정
`fstorage.c``fds_default_value_set()` 함수를 config_data_t 필드에 맞게 수정:
```c
void fds_default_value_set(void)
{
memset(m_config.hw_no, 0, 12);
memcpy(m_config.serial_no, "2025AAAAT001", 12); /* 프로젝트 기본 시리얼 */
memcpy(m_config.static_passkey, "123456", 6); /* 기본 패스키 */
m_config.bond_data_delete = 1;
m_config.reset_status = 99;
// ---- 프로젝트별 필드 기본값 ----
m_config.my_custom_field = 0;
m_config.my_custom_u16 = 1000;
}
```
---
## Step 3: dr_mem 룩업 테이블 수정
### 3.1 storage/dr_mem.c 룩업 테이블 수정
`mem_table[]`을 config_data_t 필드에 맞게 수정한다.
**각 엔트리 형식:**
```c
{ "키이름", , offsetof(config_data_t, ), (bytes), false, & },
```
**VivaMayo 원본:**
```c
static const dr_mem_entry_t mem_table[] = {
/* FDS entries */
{ "hw_no", MEM_BACKEND_FDS, offsetof(config_data_t, hw_no), 12, false, dflt_hw_no },
{ "serial_no", MEM_BACKEND_FDS, offsetof(config_data_t, serial_no), 12, false, dflt_serial },
{ "passkey", MEM_BACKEND_FDS, offsetof(config_data_t, static_passkey), 6, false, dflt_passkey },
{ "bond_delete", MEM_BACKEND_FDS, offsetof(config_data_t, bond_data_delete), 1, false, &dflt_bond_del },
{ "reset_status", MEM_BACKEND_FDS, offsetof(config_data_t, reset_status), 1, false, &dflt_reset_st },
{ "pd_adc_cnt", MEM_BACKEND_FDS, offsetof(config_data_t, pd_adc_cnt), 1, false, &dflt_adc_cnt },
{ "pd_delay", MEM_BACKEND_FDS, offsetof(config_data_t, pd_delay_us), 2, false, &dflt_pd_delay },
/* W25Q32 entries (외장 Flash가 없으면 제거) */
{ "agc_gain", MEM_BACKEND_W25Q32, 0x000000, 96, false, dflt_agc },
{ "life_cycle", MEM_BACKEND_W25Q32, 0x000060, 4, false, &dflt_life_cycle },
};
```
**수정 방법:**
1. config_data_t에서 필드를 추가/삭제했으면 테이블도 동일하게 수정
2. `offsetof(config_data_t, 필드명)` 은 반드시 정확해야 함
3. 기본값 `static const` 변수도 파일 상단에 추가
4. **W25Q32가 없으면** W25Q32 엔트리 전부 제거 + `#include "drivers/w25q32/w25q32.h"` 제거
5. **W25Q32가 없으면** `w25q_backend_write()`, `w25q_backend_read()` 함수도 제거
### 3.2 W25Q32 없는 프로젝트 (FDS만 사용)
외장 Flash가 없는 프로젝트의 경우 dr_mem.c를 다음과 같이 단순화:
```c
/* W25Q32 관련 코드 전부 제거 */
// #include "drivers/w25q32/w25q32.h" ← 삭제
// w25q_backend_write() 함수 ← 삭제
// w25q_backend_read() 함수 ← 삭제
// mem_table에서 MEM_BACKEND_W25Q32 엔트리 ← 삭제
// dr_memWrite / dr_memRead의 switch문에서
// case MEM_BACKEND_W25Q32: ← 삭제
```
### 3.3 dr_mem.h는 수정 불필요
`dr_mem.h`는 범용 인터페이스이므로 그대로 사용한다.
---
## Step 4: device_config 전역변수 수정
### 4.1 config/device_config.h 수정
프로젝트에서 런타임에 사용하는 전역변수를 선언한다.
이 변수들은 부팅 시 `m_config` (FDS)에서 복사된다.
```c
#ifndef DEVICE_CONFIG_H
#define DEVICE_CONFIG_H
#include <stdint.h>
#include <stdbool.h>
/* ---- 프로젝트별 전역변수 extern 선언 ---- */
extern char m_static_passkey[7]; /* BLE 패스키 (6자리 + null) */
extern char SERIAL_NO[16]; /* 시리얼 번호 (BLE 디바이스 이름으로 사용) */
extern bool bond_data_delete;
// extern uint16_t my_custom_var; /* 추가 변수 */
void load_device_configuration(void);
void load_default_values(void);
bool is_valid_serial_no(const char *serial);
bool is_valid_passkey(const char *passkey);
#endif
```
### 4.2 config/device_config.c 수정
**전역변수 정의부:**
```c
char m_static_passkey[7] = "123456";
char SERIAL_NO[16] = "2025XXXX00001";
bool bond_data_delete = true;
// uint16_t my_custom_var = 0;
```
**load_default_values() 함수** - 각 전역변수의 기본값 설정:
```c
void load_default_values(void)
{
memset(SERIAL_NO, 0, sizeof(SERIAL_NO));
memcpy(SERIAL_NO, "2025AAAAT001", 12);
memset(m_static_passkey, 0, sizeof(m_static_passkey));
memcpy(m_static_passkey, "123456", 6);
bond_data_delete = 1;
// my_custom_var = 0;
}
```
**load_device_configuration() 함수** - FDS(m_config)에서 전역변수로 복사:
```c
void load_device_configuration(void)
{
ret_code_t err_code;
load_default_values();
/* FDS → 전역변수 복사 */
err_code = dr_memRead("serial_no", (uint8_t *)SERIAL_NO, 12);
if (err_code != NRF_SUCCESS || !is_valid_serial_no(SERIAL_NO)) {
memcpy(SERIAL_NO, "2025AAAAT001", 12); /* 폴백 */
}
err_code = dr_memRead("passkey", (uint8_t *)m_static_passkey, 6);
if (err_code != NRF_SUCCESS || !is_valid_passkey(m_static_passkey)) {
memcpy(m_static_passkey, "123456", 6);
}
{
uint8_t raw = 0;
dr_memRead("bond_delete", &raw, 1);
bond_data_delete = (raw != 0);
}
// dr_memRead("my_custom_key", &my_custom_var, 2);
}
```
---
## Step 5: BLE 명령어 핸들러 추가
### 5.1 cmd/cmd.c에 include 추가
```c
#include "../storage/dr_mem.h" /* dr_memRead, dr_memWrite */
#include "../fstorage.h" /* m_config */
#include "../debug_print.h"
```
### 5.2 읽기 핸들러 패턴 (mr* → rr*)
config_data_t의 각 필드 타입에 따라 4가지 패턴을 사용한다.
**패턴 A: ASCII 문자열 (char[]) 필드**
```c
static int Cmd_mrh(const ParsedCmd *cmd)
{
(void)cmd;
char buf[12];
dr_memRead("hw_no", buf, 12);
ascii_format_data(ble_bin_buffer, "rrh:", buf, 12);
binary_tx_handler(ble_bin_buffer, 8); /* (4+12)/2 = 8 words */
return 1;
}
```
**패턴 B: uint8_t 바이트 배열 필드**
```c
static int Cmd_mrp(const ParsedCmd *cmd)
{
(void)cmd;
uint8_t buf[6];
dr_memRead("passkey", buf, 6);
format_data_byte(ble_bin_buffer, "rrp:", buf, 6);
binary_tx_handler(ble_bin_buffer, 5); /* (4+6)/2 = 5 words */
return 1;
}
```
**패턴 C: 단일 숫자 값 (1~2 byte)**
```c
static int Cmd_mrb(const ParsedCmd *cmd)
{
(void)cmd;
uint8_t val = 0;
dr_memRead("bond_delete", &val, 1);
dr_ble_return_1("rrb:", (uint16_t)val); /* tag + uint16 응답 */
return 1;
}
static int Cmd_mrd(const ParsedCmd *cmd)
{
(void)cmd;
uint16_t val = 0;
dr_memRead("pd_delay", &val, 2);
dr_ble_return_1("rrd:", val);
return 1;
}
```
**패턴 D: uint32_t 값 (hi16 + lo16로 분할)**
```c
static int Cmd_mrl(const ParsedCmd *cmd)
{
(void)cmd;
uint32_t val = 0;
dr_memRead("life_cycle", &val, 4);
uint16_t hi = (uint16_t)(val >> 16);
uint16_t lo = (uint16_t)(val & 0xFFFF);
dr_ble_return_2("rrl:", hi, lo); /* tag + uint16 x 2 응답 */
return 1;
}
```
### 5.3 쓰기 핸들러 패턴 (mw* → rw*)
**패턴 A: ASCII 문자열 쓰기**
```c
static int Cmd_mwh(const ParsedCmd *cmd)
{
char hw[13];
memset(hw, 0, sizeof(hw));
cmd_get_ascii(cmd, 0, hw, 12);
ret_code_t err = dr_memWrite("hw_no", hw, 12);
dr_ble_return_1("rwh:", err == NRF_SUCCESS ? 0 : 1);
return 1;
}
```
**패턴 B: uint8/uint16 값 쓰기**
```c
static int Cmd_mwb(const ParsedCmd *cmd)
{
uint16_t val = 0;
(void)cmd_get_u16(cmd, 0, &val);
uint8_t u8val = (uint8_t)val;
ret_code_t err = dr_memWrite("bond_delete", &u8val, 1);
dr_ble_return_1("rwb:", err == NRF_SUCCESS ? 0 : 1);
return 1;
}
```
**패턴 C: uint16 배열 쓰기 + RAM 동기화**
```c
static int Cmd_mwg(const ParsedCmd *cmd)
{
uint16_t gains[48];
uint8_t i;
for (i = 0; i < 48; i++) {
if (!cmd_get_u16(cmd, i, &gains[i])) gains[i] = 0;
}
ret_code_t err = dr_memWrite("agc_gain", gains, sizeof(gains));
/* RAM 전역변수도 즉시 업데이트 */
for (i = 0; i < 48; i++) led_pd_dac_v[i] = gains[i];
dr_ble_return_1("rwg:", err == NRF_SUCCESS ? 0 : 1);
return 1;
}
```
**패턴 D: uint32 쓰기 (hi16 + lo16 결합)**
```c
static int Cmd_mwl(const ParsedCmd *cmd)
{
uint16_t hi = 0, lo = 0;
(void)cmd_get_u16(cmd, 0, &hi);
(void)cmd_get_u16(cmd, 1, &lo);
uint32_t life = ((uint32_t)hi << 16) | (uint32_t)lo;
ret_code_t err = dr_memWrite("life_cycle", &life, 4);
m_life_cycle = life; /* RAM 전역변수도 즉시 업데이트 */
dr_ble_return_1("rwl:", err == NRF_SUCCESS ? 0 : 1);
return 1;
}
```
### 5.4 binary_tx_handler 호출 시 word 수 계산
```
words = (TAG 4바이트 + DATA 바이트 수) / 2
예시:
ASCII 12B 응답: (4 + 12) / 2 = 8 words
Byte 6B 응답: (4 + 6) / 2 = 5 words
uint16 1개 응답: dr_ble_return_1 사용 (내부에서 3 words)
uint16 2개 응답: dr_ble_return_2 사용 (내부에서 4 words)
```
### 5.5 명령어 테이블에 등록
`g_cmd_table[]`에 핸들러 엔트리 추가:
```c
CmdEntry g_cmd_table[] = {
/* ... 기존 명령어들 ... */
/* N. Memory Read (dr_mem) */
{ "mrh?", true, Cmd_mrh }, /* hw_no → rrh: + 12B ASCII */
{ "mrs?", true, Cmd_mrs }, /* serial_no → rrs: + 12B ASCII */
{ "mrp?", true, Cmd_mrp }, /* passkey → rrp: + 6B */
{ "mrb?", true, Cmd_mrb }, /* bond_delete → rrb: + uint16 */
{ "mrr?", true, Cmd_mrr }, /* reset_status → rrr: + uint16 */
/* ... 프로젝트별 추가 mr* 명령어 ... */
/* O. Memory Write (dr_mem) */
{ "mwh?", true, Cmd_mwh }, /* hw_no ← 12B ASCII → rwh: */
{ "mws?", true, Cmd_mws }, /* serial_no ← 12B ASCII → rws: */
{ "mwp?", true, Cmd_mwp }, /* passkey ← 6B → rwp: */
{ "mwb?", true, Cmd_mwb }, /* bond_delete ← uint16 → rwb: */
{ "mwr?", true, Cmd_mwr }, /* reset_status ← uint16 → rwr: */
/* ... 프로젝트별 추가 mw* 명령어 ... */
};
```
### 5.6 forward declaration 추가
`g_cmd_table[]` 앞에 핸들러 함수 선언:
```c
/* Memory Read handlers */
static int Cmd_mrh(const ParsedCmd *cmd);
static int Cmd_mrs(const ParsedCmd *cmd);
static int Cmd_mrp(const ParsedCmd *cmd);
/* ... */
/* Memory Write handlers */
static int Cmd_mwh(const ParsedCmd *cmd);
static int Cmd_mws(const ParsedCmd *cmd);
static int Cmd_mwp(const ParsedCmd *cmd);
/* ... */
```
### 5.7 명명 규칙
```
읽기: mr + 필드 약자 + ? → 응답: rr + 필드 약자 + :
쓰기: mw + 필드 약자 + ? → 응답: rw + 필드 약자 + :
약자 예시:
h = hw_no
s = serial_no
p = passkey
b = bond_delete
r = reset_status
c = pd_adc_cnt (count)
d = pd_delay
g = agc_gain
l = life_cycle
```
새 필드를 추가할 때는 기존 약자와 겹치지 않는 알파벳 사용.
---
## Step 6: main.c 초기화 순서 통합
### 6.1 필수 초기화 순서
```c
int main(void)
{
/* ... GPIO, Timer 등 초기화 ... */
/* ① BLE 스택 초기화 (SoftDevice 활성화) - 반드시 FDS보다 먼저! */
ble_stack_init();
/* ② FDS 초기화 */
fs_storage_init(); /* fds_register + fds_init + wait_for_fds_ready */
/* ③ FDS에서 config_data_t 로드 (→ m_config 전역변수) */
config_load(); /* = fs_set_value() 호출해도 동일 */
/* ④ m_config → 런타임 전역변수 복사 */
load_device_configuration();
/* ⑤ GAP 파라미터 설정 (SERIAL_NO로 디바이스 이름 설정) */
gap_params_init();
gatt_init();
services_init();
advertising_init();
/* ... 나머지 초기화 ... */
for (;;) {
idle_state_handle();
}
}
```
### 6.2 순서를 반드시 지켜야 하는 이유
| 순서 | 이유 |
|------|------|
| ① → ② | SoftDevice가 FDS 이벤트를 전달해야 하므로 BLE 스택이 먼저 |
| ② → ③ | FDS 초기화 완료 후에야 레코드 검색 가능 |
| ③ → ④ | m_config에 FDS 데이터가 로드된 후에 전역변수로 복사 |
| ④ → ⑤ | SERIAL_NO가 설정된 후에 GAP 이름 설정 |
### 6.3 필요한 extern/include
```c
/* main.c에 추가할 include */
#include "fstorage.h"
#include "config/device_config.h"
/* main.c에 추가할 extern (전원 제어 플래그) */
bool go_device_power_off = false;
bool go_sleep_mode_enter = false;
bool go_NVIC_SystemReset = false;
```
---
## Step 7: sdk_config.h 설정
### 7.1 필수 설정
```c
/* FDS 활성화 */
#define FDS_ENABLED 1
#define FDS_VIRTUAL_PAGES 50 /* 50 pages (Wear Leveling용) */
#define FDS_VIRTUAL_PAGE_SIZE 2048 /* words (= 8KB per page) */
#define FDS_CRC_CHECK_ON_READ 0 /* 0 권장 (성능) */
#define FDS_CRC_CHECK_ON_WRITE 0 /* 0 권장 (성능) */
#define FDS_BACKEND 2 /* 2 = nrf_fstorage_sd (SoftDevice 사용) */
#define FDS_OP_QUEUE_SIZE 4
/* Flash Storage 활성화 */
#define NRF_FSTORAGE_ENABLED 1
/* SOC 이벤트 핸들러 (FDS가 SoftDevice에서 이벤트 받기 위해 필수) */
#define NRF_SDH_SOC_ENABLED 1
#define NRF_SDH_SOC_OBSERVER_PRIO_LEVELS 2
/* 전력 관리 (wait_for_fds_ready에서 사용) */
#define NRF_PWR_MGMT_ENABLED 1
```
### 7.2 선택 설정
```c
/* RTT 디버그 버퍼 (DBG_PRINTF 사용 시) */
#define SEGGER_RTT_CONFIG_BUFFER_SIZE_UP 1024
#define NRF_LOG_BACKEND_RTT_ENABLED 1
```
### 7.3 Flash 메모리 맵 확인
FDS가 사용하는 Flash 영역은 링커 스크립트 또는 `nrf_dfu_types.h`에서 설정.
기본적으로 **Application 영역 끝부분**에 배치된다.
FDS_VIRTUAL_PAGES를 늘리면 Application 코드 공간이 줄어드므로 주의.
---
## Step 8: Keil 프로젝트에 파일 추가
### 8.1 Source 파일 추가
Keil uVision → Project → Manage Project Items에서 다음 파일 추가:
| 그룹 | 파일 | 경로 |
|------|------|------|
| Application | `fstorage.c` | `../../fstorage.c` |
| Application | `device_config.c` | `../../config/device_config.c` |
| Application | `dr_mem.c` | `../../storage/dr_mem.c` |
| mt_parser | `parser.c` | `mt_parser/parser.c` (이미 있으면 스킵) |
| mt_parser | `dr_util.c` | `mt_parser/dr_util/dr_util.c` (이미 있으면 스킵) |
| nRF_Libraries | `fds.c` | SDK `components/libraries/fds/fds.c` |
| nRF_Libraries | `nrf_fstorage.c` | SDK `components/libraries/fstorage/nrf_fstorage.c` |
| nRF_Libraries | `nrf_fstorage_sd.c` | SDK `components/libraries/fstorage/nrf_fstorage_sd.c` |
| nRF_Libraries | `nrf_atflags.c` | SDK `components/libraries/atomic_flags/nrf_atflags.c` |
### 8.2 Include Path 추가
```
Options → C/C++ → Include Paths에 추가:
../../storage
../../config
../../cmd
<mt_parser 경로>
<mt_parser>/dr_util
```
### 8.3 Nordic SDK Include 확인
다음 SDK 경로들이 이미 Include Path에 있는지 확인:
```
.../components/libraries/fds
.../components/libraries/fstorage
.../components/libraries/atomic_flags
.../components/libraries/experimental_section_vars
```
---
## Step 9: 빌드 및 검증
### 9.1 빌드
Keil에서 Build (F7). 예상되는 에러와 해결:
| 에러 | 원인 | 해결 |
|------|------|------|
| `config_data_t` undeclared | fstorage.h include 누락 | `#include "fstorage.h"` 추가 |
| `dr_memRead` undeclared | dr_mem.h include 누락 | `#include "storage/dr_mem.h"` 추가 |
| `w25q32_read` undefined | W25Q32 드라이버 없음 | dr_mem.c에서 W25Q32 코드 제거 |
| `device_power_off` undefined | power_ctrl 미구현 | 빈 함수 작성 또는 FDS_EVT_UPDATE에서 제거 |
| Linker: FDS region overlap | Flash 영역 충돌 | FDS_VIRTUAL_PAGES 줄이기 (예: 10) |
| `go_device_power_off` undeclared | main.c에 정의 안 됨 | main.c에 `bool go_device_power_off = false;` 추가 |
### 9.2 RTT 로그로 검증
부팅 후 RTT 로그에서 다음 확인:
```
[FDS] OK ← FDS 초기화 성공
[FDS] find rc=0 ← 레코드 발견 (두 번째 부팅부터)
[FDS] Loaded OK ← 정상 로드
[CFG] FDS serial_no='2025AAAAT001' ← 시리얼 번호 확인
```
첫 부팅 시에는:
```
[FDS] OK
[FDS] find rc=34313 ← 레코드 없음 (정상)
[FDS] retry 1/10 ← 재시도
[FDS] New - writing defaults ← 기본값으로 새 레코드 생성
[FDS] find rc=0
[FDS] Loaded OK
```
### 9.3 BLE 명령어 검증
1. BLE 연결
2. `mrs?` 전송 → `rrs:2025AAAAT001` 응답 확인
3. `mws?2025TEST0001` 전송 → `rws:0000` (성공) 응답 확인
4. 재부팅
5. `mrs?` 전송 → `rrs:2025TEST0001` 응답 확인 (값 유지됨)
---
## 핵심 주의사항
### 1. config_load()의 재시도 로직을 절대 제거하지 말 것
```c
if (rc != NRF_SUCCESS && cfg_retry < 10) {
cfg_retry++;
nrf_delay_ms(100);
goto cfg_load_start;
}
```
이 코드가 없으면 부팅 시 FDS가 아직 준비되지 않았을 때 저장된 값을 기본값으로 덮어쓴다.
### 2. config_save()에서 절대 대기하지 말 것
`config_save()`는 BLE 콜백 컨텍스트에서 호출될 수 있다.
내부에서 `while(fds_flag_write)` 등으로 대기하면 **교착 상태** 발생.
fire-and-forget 패턴을 유지해야 한다.
### 3. printf vs DBG_PRINTF 구분
| 함수 | 출력 경로 | RTT에서 보이나? |
|------|----------|----------------|
| `printf()` | UART | X |
| `DBG_PRINTF()` | RTT (또는 UART, 설정에 따라) | O |
FDS 관련 디버그는 반드시 `DBG_PRINTF()` 사용.
### 4. BLE 디바이스 이름은 부팅 시 한 번만 설정됨
`gap_params_init()``sd_ble_gap_device_name_set(SERIAL_NO)`는 부팅 시 한 번만 호출.
`mws?`로 시리얼 변경해도 재부팅 전까지 BLE 광고 이름은 바뀌지 않는다.
### 5. fds_record_update는 비동기
`fds_record_update()` 호출 → 즉시 리턴 → 실제 Flash 쓰기는 나중에 수행 →
`FDS_EVT_UPDATE` 이벤트에서 `fds_flag_write = false`.
연속 쓰기 시 이전 쓰기가 완료될 때까지 다음 쓰기가 스킵될 수 있다 (`[CFG_SAVE] busy, skipped`).
### 6. #pragma pack(1) 필수
`config_data_t``#pragma pack(1)`이 없으면 컴파일러가 패딩을 삽입한다.
이 경우 `offsetof()` 값이 달라지고, FDS에서 읽은 데이터가 엉뚱한 필드에 매핑된다.
---
## 체크리스트
새 프로젝트에 이식할 때 이 체크리스트를 순서대로 확인:
```
[ ] 1. 파일 복사 (fstorage.c/h, dr_mem.c/h, device_config.c/h)
[ ] 2. config_data_t 구조체 수정 (fstorage.h)
[ ] 3. CONFIG_MAGIC_NUMBER_VALUE 날짜 변경 (fstorage.c)
[ ] 4. fds_default_value_set() 기본값 수정 (fstorage.c)
[ ] 5. dr_mem.c 룩업 테이블 수정 (필드 추가/삭제)
[ ] 6. dr_mem.c 기본값 static const 변수 수정
[ ] 7. W25Q32 없으면 dr_mem.c에서 W25Q32 관련 코드 제거
[ ] 8. device_config.h 전역변수 extern 수정
[ ] 9. device_config.c 전역변수 정의 + load 함수 수정
[ ] 10. cmd/cmd.c에 mr*/mw* 핸들러 추가
[ ] 11. cmd/cmd.c의 g_cmd_table[]에 명령어 등록
[ ] 12. main.c 초기화 순서 확인 (BLE → FDS → config_load → device_config → GAP)
[ ] 13. main.c에 go_device_power_off 등 플래그 정의
[ ] 14. sdk_config.h FDS 설정 확인
[ ] 15. Keil 프로젝트에 .c 파일 추가
[ ] 16. Keil Include Path 추가
[ ] 17. 빌드 성공 확인
[ ] 18. RTT 로그로 FDS 초기화 + 로드 확인
[ ] 19. BLE mr*/mw* 명령어 읽기/쓰기 검증
[ ] 20. 재부팅 후 값 유지 검증
```
---
## 빠른 참조: 새 필드 추가 시 수정 위치 요약
새 필드 `my_field`(uint16_t, 2B)를 추가할 때:
| 순서 | 파일 | 수정 내용 |
|------|------|----------|
| 1 | `fstorage.h` | `config_data_t``uint16_t my_field;` 추가 |
| 2 | `fstorage.c` | `fds_default_value_set()``m_config.my_field = 1000;` |
| 3 | `storage/dr_mem.c` | 기본값: `static const uint16_t dflt_my_field = 1000;` |
| 4 | `storage/dr_mem.c` | 테이블: `{ "my_field", MEM_BACKEND_FDS, offsetof(config_data_t, my_field), 2, false, &dflt_my_field }` |
| 5 | `config/device_config.h` | `extern uint16_t my_field_var;` |
| 6 | `config/device_config.c` | `uint16_t my_field_var = 1000;` + `dr_memRead("my_field", ...)` |
| 7 | `cmd/cmd.c` | `Cmd_mrf()` 읽기 핸들러 + `Cmd_mwf()` 쓰기 핸들러 |
| 8 | `cmd/cmd.c` | `g_cmd_table[]``{ "mrf?", true, Cmd_mrf }`, `{ "mwf?", true, Cmd_mwf }` |
**magic_number를 변경하면** 기존 디바이스의 저장 데이터가 초기화된다.
구조체 크기만 바뀌고 기존 필드 순서가 유지되면 magic_number를 유지해도 되지만,
필드 순서가 바뀌면 반드시 magic_number를 변경해야 한다.
---
*문서 작성일: 2026-03-05*
*기준 프로젝트: VivaMyo (ble_app_vivaMayo)*

View File

@@ -0,0 +1,589 @@
# VivaMyo BLE Application - 프로그램 아키텍처 문서
## 1. 프로젝트 개요
VivaMyo는 nRF52840 기반 BLE 의료 기기 펌웨어로, 방광 모니터링을 위한 NIRS(Near-Infrared Spectroscopy) 장치입니다.
### 1.1 주요 특징
- **MCU**: Nordic nRF52840
- **통신**: BLE (Nordic UART Service)
- **보안**: LESC 페어링, Static Passkey
- **센서**: LED 48개, Photodetector, IMU (ICM42670P), 온도센서, 압력센서
- **저장**: EEPROM (설정값 암호화 저장)
---
## 2. 시스템 아키텍처
```
┌─────────────────────────────────────────────────────────────────────┐
│ main.c │
│ (Application Entry Point) │
└──────────────────────────────┬──────────────────────────────────────┘
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌─────────────────────┐
│ BLE Stack │ │ Power Control │ │ Timer Management │
│ (ble_core.c) │ │ (power_ctrl.c) │ │ (main_timer.c) │
└───────┬───────┘ └─────────────────┘ └─────────────────────┘
┌───────────────────────────────────────────────────────────────────┐
│ nus_data_handler() │
│ (BLE Data Reception) │
└──────────────────────────────┬────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────┐
│ received_command_process() │
│ (cmd_parse.c) │
└──────────────────────────────┬────────────────────────────────────┘
┌──────────────────────┴──────────────────────┐
▼ ▼
┌───────────────────────┐ ┌─────────────────────┐
│ Mt_parser │ │ Legacy Parser │
│ (dr_cmd_parser) │ │ (cmd_parse.c) │
│ │ │ │
│ parser.c + cmd.c │ │ 기존 명령어 처리 │
└───────────┬───────────┘ └─────────────────────┘
┌───────────────────────────────────────────────────────────────────┐
│ Command Handlers (cmd.c) │
│ │
│ Cmd_mta, Cmd_sta, Cmd_mcj, Cmd_msn, Cmd_mag ... │
└───────────────────────────────────────────────────────────────────┘
```
---
## 3. Mt_parser 모듈 구조
Mt_parser는 명령어 파싱과 디스패치를 담당하는 독립 모듈입니다.
### 3.1 파일 구성
| 파일 | 경로 | 설명 |
|------|------|------|
| `parser.h` | `/mt_parser/parser.h` | 파서 인터페이스 정의 |
| `parser.c` | `/mt_parser/parser.c` | CRC 검증, TAG 추출, 명령어 디스패치 |
| `cmd.h` | `cmd/cmd.h` | 명령어 테이블 구조체 정의 |
| `cmd.c` | `cmd/cmd.c` | 명령어 핸들러 구현 및 테이블 |
| `dr_util.h/c` | `/mt_parser/dr_util/` | BLE 응답 유틸리티 함수 |
### 3.2 핵심 구조체
```c
// parser.h - 플랫폼 인터페이스
typedef struct {
void (*log)(const char *fmt, ...); // 로그 출력 함수
void (*tx_bin)(const uint8_t *buf, uint16_t len); // BLE 전송 함수
bool crc_check; // CRC 검사 활성화 여부
} dr_platform_if_t;
// cmd.h - 파싱된 명령어 구조체
typedef struct {
char tag[5]; // 4글자 명령어 + NULL ("sta?")
uint8_t data[CMD_MAX_DATA]; // TAG 이후 데이터
uint8_t data_len; // 데이터 길이
} ParsedCmd;
// cmd.h - 명령어 테이블 엔트리
typedef struct {
char tag[5]; // 명령어 TAG ("sta?")
bool enabled; // 활성화 여부
cmd_handler_t handler; // 핸들러 함수 포인터
} CmdEntry;
```
### 3.3 전역 변수
```c
extern dr_platform_if_t g_plat; // 플랫폼 인터페이스
extern bool g_log_enable; // 로그 활성화 플래그
extern CmdEntry g_cmd_table[]; // 명령어 테이블
extern const uint16_t g_cmd_count; // 명령어 개수
```
---
## 4. 명령어 처리 흐름
### 4.1 데이터 수신 경로
```
[BLE Central]
▼ (NUS RX Characteristic Write)
[ble_core.c:nus_data_handler()]
[cmd_parse.c:received_command_process(data, CMD_BLE, length)]
├─► [Mt_parser 초기화] (최초 1회)
│ g_plat.log = log_printf
│ g_plat.tx_bin = binary_tx_handler
│ g_plat.crc_check = true
[parser.c:dr_cmd_parser(data, length)]
├─► [CRC16 검증] → 실패시 "crc!" 응답
├─► [TAG 추출] → 4글자 명령어 파싱
[dr_cmd_dispatch()] → g_cmd_table 검색
├─► [명령어 발견 + 활성화] → handler() 호출
├─► [명령어 발견 + 비활성화] → return 0
└─► [명령어 미발견] → return 0 (Legacy 파서로 fallback)
```
### 4.2 CRC16 검증
```c
// 패킷 구조: [TAG(4)] [DATA(N)] [CRC_LO] [CRC_HI]
// CRC 계산: TAG + DATA 영역
// CRC 위치: 패킷 마지막 2바이트 (Little Endian)
static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len)
{
uint32_t data_len = packet_len - 2;
uint16_t expected_crc = (uint16_t)packet[packet_len - 2]
| ((uint16_t)packet[packet_len - 1] << 8);
return dr_crc16_check(packet, data_len, expected_crc);
}
```
---
## 5. 명령어 테이블 (cmd.c)
### 5.1 명령어 카테고리
| 카테고리 | 명령어 | 설명 |
|----------|--------|------|
| **A. Device Status** | `mta?`, `sta?`, `str?`, `mqq?` | 디바이스 상태 제어/조회 |
| **B. AGC/Gain** | `mag?`, `sag?`, `sar?` | Auto Gain Control |
| **C. LED DP Value** | `ssa?`, `sab?`, `ssb?`, `srb?` | LED 밝기 설정/조회 |
| **D. LED On/Off** | `ssc?`, `ssd?`, `sse?`, `ssf?`, `sif?`, `ssg?` | LED/Gain 제어 |
| **E. Simple PD** | `ssh?` | 간단한 PD 측정 |
| **F. M48 Full** | `mcj?`, `scj?`, `sdj?`, `sej?`, `sfj?`, `ssj?`, `szj?` | 48채널 PD 측정 |
| **G. IMM ADC** | `saj?` | Immediate ADC 측정 |
| **H. PD Settings** | `ssk?`, `srk?`, `ssl?`, `srl?` | ADC 횟수/지연 설정 |
| **I. Sensors** | `msn?`, `ssn?`, `spn?`, `sso?`, `ssp?` | 배터리/압력/온도/IMU |
| **J. System** | `ssq?`, `ssr?`, `sss?`, `sst?`, `ssv?` | 전원/리셋/버전 |
| **K. Serial/Passkey** | `ssz?`, `spz?`, `sqz?`, `srz?` | 시리얼번호/패스키 |
| **L. EEPROM Array** | `sez?`, `sfz?`, `sgz?` | EEPROM 배열 R/W |
| **M. HW/Life Cycle** | `siz?`, `shz?`, `sxz?`, `syz?` | HW번호/사용횟수 |
| **N. Debug** | `cmd?` | GPIO 테스트 |
### 5.2 명령어 상세 테이블
```c
CmdEntry g_cmd_table[] = {
/* Debug */
{ "cmd?", true, Cmd_cmd }, // GPIO 테스트
/* A. Device Status */
{ "mta?", true, Cmd_mta }, // Device Activate/Sleep (신규)
{ "sta?", true, Cmd_sta }, // Device Activate/Sleep (호환)
{ "str?", false, Cmd_str }, // Status Read
{ "mqq?", true, Cmd_mqq }, // Quick Measurement
/* B. AGC / Gain Measurement */
{ "mag?", true, Cmd_mag }, // Full AGC (신규)
{ "sag?", true, Cmd_mag }, // Full AGC (호환)
{ "sar?", false, Cmd_sar }, // Read LED-PD Gain Array
/* C. LED Power / DP Value */
{ "ssa?", false, Cmd_ssa }, // Single LED Power Read
{ "sab?", false, Cmd_sab }, // All LED AGC Data Read
{ "ssb?", false, Cmd_ssb }, // Single LED Power Write
{ "srb?", false, Cmd_srb }, // Read All 48 LED DP
/* D. LED On/Off & Gain Control */
{ "ssc?", false, Cmd_ssc }, // LED On/Off (index 0-47, 99=off)
{ "ssd?", false, Cmd_ssd }, // AGC Switch On/Off
{ "sse?", false, Cmd_sse }, // Measure DAC Voltage
{ "ssf?", false, Cmd_ssf }, // Set LED DAC Value
{ "sif?", false, Cmd_sif }, // Immediate Gain Set
{ "ssg?", false, Cmd_ssg }, // Set PD Channel
/* E. Simple PD Measurement */
{ "ssh?", false, Cmd_ssh }, // Simple PD Measurement
/* F. PD-ADC M48 Full Measurement */
{ "mcj?", true, Cmd_mcj }, // MODE=2 (Pressure + M48) - 신규
{ "scj?", true, Cmd_mcj }, // MODE=2 (호환)
{ "sdj?", false, Cmd_sdj }, // MODE=3 (M48 Only)
{ "sej?", false, Cmd_sej }, // MODE=4 (M48 + Batt + IMU)
{ "sfj?", false, Cmd_sfj }, // MODE=5 (M48 + Init)
{ "ssj?", false, Cmd_ssj }, // MODE=0 (Combined)
{ "szj?", false, Cmd_szj }, // FAST Mode Settings
/* G. IMM ADC */
{ "saj?", false, Cmd_saj }, // 4-LED Immediate ADC
/* H. PD-ADC Count & Delay */
{ "ssk?", false, Cmd_ssk }, // Set ADC Count (8/16/24/32)
{ "srk?", false, Cmd_srk }, // Read ADC Count
{ "ssl?", false, Cmd_ssl }, // Set PD Delay (us)
{ "srl?", false, Cmd_srl }, // Read PD Delay
/* I. Sensor Measurements */
{ "msn?", true, Cmd_msn }, // Battery Level (신규)
{ "ssn?", true, Cmd_msn }, // Battery Level (호환)
{ "spn?", false, Cmd_spn }, // Pressure Measurement
{ "sso?", false, Cmd_sso }, // Temperature Measurement
{ "ssp?", false, Cmd_ssp }, // IMU Raw Data
/* J. Power / Reset / Version */
{ "ssq?", false, Cmd_ssq }, // Power Off
{ "ssr?", false, Cmd_ssr }, // Bond Delete + Reset
{ "sss?", false, Cmd_sss }, // Device Reset
{ "sst?", false, Cmd_sst }, // Ready Response
{ "ssv?", false, Cmd_ssv }, // Firmware Version
/* K. Serial / Passkey */
{ "ssz?", false, Cmd_ssz }, // Write Serial Number
{ "spz?", false, Cmd_spz }, // Write Passkey
{ "sqz?", false, Cmd_sqz }, // Read Passkey
{ "srz?", false, Cmd_srz }, // Read Serial Number
/* L. EEPROM Array */
{ "sez?", false, Cmd_sez }, // Write 48*uint16 Array
{ "sfz?", false, Cmd_sfz }, // Read 48*uint16 Array
{ "sgz?", false, Cmd_sgz }, // Load DP Preset
/* M. Hardware No / Life Cycle */
{ "siz?", false, Cmd_siz }, // Read HW Number
{ "shz?", false, Cmd_shz }, // Write HW Number
{ "sxz?", false, Cmd_sxz }, // Write Life Cycle
{ "syz?", false, Cmd_syz }, // Read Life Cycle
};
```
### 5.3 활성화된 명령어 (enabled=true)
| 명령어 | 핸들러 | 기능 |
|--------|--------|------|
| `cmd?` | `Cmd_cmd` | GPIO 핀 제어 테스트 |
| `mta?` | `Cmd_mta` | 디바이스 활성화/슬립 |
| `sta?` | `Cmd_sta` | 디바이스 활성화/슬립 (호환) |
| `mqq?` | `Cmd_mqq` | 빠른 측정 시작 |
| `mag?` | `Cmd_mag` | Full AGC 측정 |
| `sag?` | `Cmd_mag` | Full AGC 측정 (호환) |
| `mcj?` | `Cmd_mcj` | M48 전체 측정 (MODE=2) |
| `scj?` | `Cmd_mcj` | M48 전체 측정 (호환) |
| `msn?` | `Cmd_msn` | 배터리 레벨 측정 |
| `ssn?` | `Cmd_msn` | 배터리 레벨 측정 (호환) |
---
## 6. main.c와의 연결
### 6.1 초기화 순서
```c
int main(void)
{
// 1. 기본 초기화
uart_handler_init(); // UART 디버그
log_init(); // NRF 로그
gpio_init(); // GPIO 설정 (LED, PD, GAIN_SW)
timers_init(); // 타이머 초기화
// 2. 설정 로드
load_device_configuration(); // EEPROM에서 설정 읽기
// 3. BLE 초기화
ble_stack_init(); // SoftDevice 활성화
gap_params_init(); // GAP 파라미터 (디바이스명, Passkey)
gatt_init(); // GATT 초기화
services_init(); // NUS 서비스 등록
advertising_init(); // 광고 설정
conn_params_init(); // 연결 파라미터
// 4. 전원 버튼 타이머 시작
power_ctrl_timers_start();
// 5. 메인 루프
for (;;) {
idle_state_handle(); // 전원 관리 + 보안 처리
}
}
```
### 6.2 BLE 데이터 수신 경로
```c
// ble_core.c
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA) {
// cmd_parse.c의 received_command_process() 호출
received_command_process(
p_evt->params.rx_data.p_data,
CMD_BLE,
p_evt->params.rx_data.length
);
}
}
```
### 6.3 Mt_parser 초기화 (cmd_parse.c)
```c
void received_command_process(uint8_t const *data_array, which_cmd_t cmd_t, uint8_t length)
{
// Mt_parser 초기화 (최초 1회)
static bool parser_initialized = false;
if (!parser_initialized) {
g_plat.log = log_printf; // 로그 함수 연결
g_plat.tx_bin = binary_tx_handler; // BLE TX 함수 연결
g_plat.crc_check = true; // CRC 검사 활성화
g_log_enable = true;
parser_initialized = true;
}
// Mt_parser 호출
int result = dr_cmd_parser(r_data, length);
if (result > 0) {
return; // Mt_parser에서 처리됨
}
// Mt_parser에서 처리 안됨 → Legacy 파서로 처리
// ... 기존 scmd 기반 처리 ...
}
```
---
## 7. 소스코드 구조
### 7.1 디렉토리 구조
```
ble_app_vivaMayo/
├── main.c # 메인 엔트리 포인트
├── main.h # 메인 헤더 (타입 정의, 함수 프로토타입)
├── main_timer.c/h # 애플리케이션 타이머
├── cmd_parse.c/h # 명령어 파싱 (Legacy + Mt_parser 연결)
├── cmd/
│ ├── cmd.c # 명령어 핸들러 구현 + 테이블
│ └── cmd.h # 명령어 구조체 정의
├── ble/
│ ├── ble_core.c/h # BLE 스택, GAP, GATT, 광고
│ ├── ble_data_tx.c/h # BLE 데이터 전송, 포맷팅
│ ├── ble_services.c/h # BLE 서비스
│ └── ble_security.c/h # BLE 보안 (페어링)
├── power/
│ └── power_ctrl.c/h # 전원 제어, 슬립 모드
├── peripheral/
│ └── uart_handler.c/h # UART 디버그 출력
├── config/
│ └── device_config.c/h # EEPROM 설정 관리
├── measurements.c/h # 측정 유틸리티
├── meas_pd_*.c/h # PD ADC 측정 모듈들
├── full_agc.c/h # AGC 자동 조절
├── battery_saadc.c/h # 배터리 ADC
├── icm42670p/ # IMU 드라이버
├── i2c_manager.c/h # I2C 관리
├── ada2200_spi.c/h # SPI 디바이스
├── mcp4725_i2c.c/h # DAC 제어
├── tmp235_q1.c/h # 온도 센서
├── cat_interface.c/h # CAT (압력센서) 인터페이스
└── docs/
└── PROGRAM_ARCHITECTURE.md # 이 문서
```
### 7.2 Mt_parser 디렉토리 (별도 위치)
```
/mt_parser/
├── parser.h # 파서 인터페이스
├── parser.c # 파서 구현 (CRC, TAG, 디스패치)
├── cmd.h # (참조용)
└── dr_util/
├── dr_util.h # BLE 응답 유틸리티
└── dr_util.c # dr_ble_return_1/2/3()
```
---
## 8. 명령어 프로토콜
### 8.1 패킷 구조
```
┌─────────┬──────────────────┬─────────┐
│ TAG │ DATA │ CRC │
│ 4 bytes │ N bytes │ 2 bytes │
└─────────┴──────────────────┴─────────┘
예시: sta? 명령어로 디바이스 활성화
TX: [73 74 61 3F] [00 01] [CRC_LO] [CRC_HI]
s t a ? value=1
응답: rta: value
RX: [72 74 61 3A] [00 01] [CRC_LO] [CRC_HI]
r t a : value=1
```
### 8.2 데이터 형식
| 형식 | 설명 | 바이트 순서 |
|------|------|-------------|
| uint16 | 16비트 정수 | Big Endian (MSB first) |
| ASCII | 문자열 | 순차적 |
| CRC16 | 체크섬 | Little Endian (LSB first) |
### 8.3 응답 규칙
| 요청 TAG | 응답 TAG | 예시 |
|----------|----------|------|
| `sta?` | `rta:` | 상태 응답 |
| `mcj?` | (측정 데이터) | 멀티 패킷 |
| 오류 | `xxx!` | `crc!`, `err!` |
---
## 9. 주요 핸들러 구현 예시
### 9.1 Cmd_mta (디바이스 활성화)
```c
static int Cmd_mta(const ParsedCmd *cmd)
{
uint16_t mode = 0;
resetCount = 0;
// 데이터에서 mode 추출 (word index 0)
(void)cmd_get_u16(cmd, 0, &mode);
if (mode == 1) {
// 디바이스 활성화
if (device_activated() == 0) {
device_status = true;
}
}
else if (mode == 0) {
// 슬립 모드 진입
if (device_status == true) {
if (device_sleep_mode() == 0) {
device_status = false;
}
}
}
// BLE 응답 전송
if (g_plat.tx_bin) {
single_format_data(ble_bin_buffer, "rta:", mode);
binary_tx_handler(ble_bin_buffer, 3);
}
return 1;
}
```
### 9.2 Cmd_mcj (M48 전체 측정)
```c
static int Cmd_mcj(const ParsedCmd *cmd)
{
(void)cmd;
// 디바이스 활성화 확인
if (device_status != true) {
param_error("mcj?");
return 1;
}
// 측정 모드 설정
ADC_PD_MODE = 2;
info4 = true;
ble_got_new_data = false;
processing = true;
// 압력 측정
pressure_all_level_meas();
// AGC 스위치 OFF
AGC_GAIN_SW(false);
// M48 ADC 시작
m48_samples_in_buffer = m_pd_adc_cnt;
pd_adc_m48_start = true;
// 타이머 시작
battery_timer_stop();
go_batt = true;
motion_data_once = true;
main_timer_start();
return 1;
}
```
---
## 10. 에러 코드
| 코드 | 값 | 설명 |
|------|-----|------|
| `err_code1` | 65535 | 길이 오류 |
| `err_code2` | 65534 | 활성화 오류 |
| `err_code3` | 65533 | 파라미터 오류 |
| `err_code4` | 65532 | '?' 누락 |
| `err_code5` | 65531 | 알 수 없는 명령어 |
| `err_code6` | 65530 | CRC 오류 |
---
## 11. EEPROM 주소 맵
| 주소 | 크기 | 내용 |
|------|------|------|
| 0x0010 | 12 bytes | HW_NO (암호화) |
| 0x0020 | 6 bytes | Passkey (암호화) |
| 0x0030 | 12 bytes | SERIAL_NO (암호화) |
| 0x0060 | 1 byte | bond_data_delete |
| 0x0065 | 1 byte | reset_status |
| 0x0070 | 1 byte | m_pd_adc_cnt |
| 0x0080 | 2 bytes | m_pd_delay_us |
| 0x0090 | 4 bytes | m_life_cycle |
| 0x0480 | 96 bytes | led_pd_dac_v[48] (AGC Gain) |
---
## 12. 버전 정보
- **펌웨어 버전**: FW25LIT2B102
- **디바이스명**: MEDIDEV_2004
- **문서 작성일**: 2026-01-30
- **작성자**: Claude Assistant
---
## 13. 관련 문서
- [W25Q32RV_FLASH_MEMORY.md](W25Q32RV_FLASH_MEMORY.md) - Flash 메모리 스펙
- Nordic nRF52840 Datasheet
- Nordic SDK 17.x Documentation

View File

@@ -0,0 +1,325 @@
# W25Q32RV Serial NOR Flash Memory
## Overview
W25Q32RV는 Winbond에서 제조한 32M-bit (4MB) Serial NOR Flash Memory입니다. Dual/Quad SPI 및 QPI 인터페이스를 지원하며, 제한된 공간, 핀 수, 전력을 가진 시스템에 적합한 저장 솔루션을 제공합니다.
## 주요 사양
| 항목 | 사양 |
|------|------|
| 용량 | 32M-bit (4MB) |
| 동작 전압 | 2.7V ~ 3.6V |
| 최대 클럭 주파수 | 133MHz (SPI/QPI) |
| Program/Erase 사이클 | 최소 100,000회 |
| 데이터 보존 기간 | 20년 이상 |
| 동작 온도 | -40°C ~ +105°C |
| 대기 전류 | 10µA (typ), 28µA (max @85°C) |
| Power-down 전류 | 0.1µA (typ) |
## 메모리 구조
### 메모리 배열
```
총 용량: 4,194,304 bytes (4MB)
├── 64 Blocks (64KB each)
│ └── 16 Sectors per Block (4KB each)
│ └── 16 Pages per Sector (256 bytes each)
└── 총 16,384 Pages
```
| 단위 | 크기 | 개수 | 주소 범위 |
|------|------|------|----------|
| Page | 256 bytes | 16,384 | - |
| Sector | 4KB | 1,024 | 0x000000 - 0x3FFFFF |
| Block (32KB) | 32KB | 128 | - |
| Block (64KB) | 64KB | 64 | - |
| Chip | 4MB | 1 | 0x000000 - 0x3FFFFF |
### 주소 맵핑
```
Block 0 (64KB): 0x000000 - 0x00FFFF
Block 1 (64KB): 0x010000 - 0x01FFFF
...
Block 63 (64KB): 0x3F0000 - 0x3FFFFF
```
## 핀 구성 (8-pin SOP/WSON/XSON/USON)
| Pin | 이름 | I/O | 기능 |
|-----|------|-----|------|
| 1 | /CS | I | Chip Select (Active Low) |
| 2 | DO (IO1) | I/O | Data Output / IO1 |
| 3 | /WP (IO2) | I/O | Write Protect / IO2 |
| 4 | VSS | - | Ground |
| 5 | DI (IO0) | I/O | Data Input / IO0 |
| 6 | CLK | I | Serial Clock |
| 7 | /HOLD or /RESET (IO3) | I/O | Hold/Reset / IO3 |
| 8 | VCC | - | Power Supply |
## SPI 모드
### 지원 모드
1. **Standard SPI**: CLK, /CS, DI, DO, /WP, /HOLD
2. **Dual SPI**: CLK, /CS, IO0, IO1, /WP, /HOLD
3. **Quad SPI**: CLK, /CS, IO0, IO1, IO2, IO3
4. **QPI**: CLK, /CS, IO0, IO1, IO2, IO3 (4-bit 명령어)
### SPI 모드 0/3 지원
- **Mode 0** (CPOL=0, CPHA=0): CLK idle = LOW
- **Mode 3** (CPOL=1, CPHA=1): CLK idle = HIGH
## 주요 명령어 (Instructions)
### Device ID 읽기
| 명령어 | 코드 | 설명 |
|--------|------|------|
| Read JEDEC ID | 9Fh | Manufacturer/Device ID 읽기 |
| Read Manufacturer/Device ID | 90h | MFR ID (EFh) + Device ID (15h) |
| Release Power-down/Device ID | ABh | Power-down 해제 및 ID 읽기 |
**Device ID 값:**
- Manufacturer ID: `0xEF` (Winbond)
- Device ID (8-bit): `0x15`
- Device ID (16-bit): `0x4016`
### 읽기 명령어
| 명령어 | 코드 | 더미 클럭 | 최대 주파수 |
|--------|------|-----------|-------------|
| Read Data | 03h | 0 | 66MHz |
| Fast Read | 0Bh | 8 | 133MHz |
| Fast Read Dual Output | 3Bh | 8 | 133MHz |
| Fast Read Quad Output | 6Bh | 8 | 133MHz |
| Fast Read Dual I/O | BBh | 4 (Mode bits) | 133MHz |
| Fast Read Quad I/O | EBh | 6 (Mode bits + Dummy) | 133MHz |
### 쓰기/프로그램 명령어
| 명령어 | 코드 | 설명 |
|--------|------|------|
| Write Enable | 06h | 쓰기 활성화 (WEL=1) |
| Write Disable | 04h | 쓰기 비활성화 (WEL=0) |
| Page Program | 02h | 페이지 프로그램 (최대 256 bytes) |
| Quad Input Page Program | 32h | Quad 모드 페이지 프로그램 |
### 지우기 명령어
| 명령어 | 코드 | 크기 | 시간 (typ/max) |
|--------|------|------|----------------|
| Sector Erase | 20h | 4KB | 30ms / 240ms |
| Block Erase (32KB) | 52h | 32KB | 80ms / 800ms |
| Block Erase (64KB) | D8h | 64KB | 120ms / 1200ms |
| Chip Erase | C7h/60h | 전체 | 6s / 40s |
### 상태 레지스터 명령어
| 명령어 | 코드 | 설명 |
|--------|------|------|
| Read Status Register-1 | 05h | SR1 읽기 |
| Read Status Register-2 | 35h | SR2 읽기 |
| Read Status Register-3 | 15h | SR3 읽기 |
| Write Status Register-1 | 01h | SR1 쓰기 |
| Write Status Register-2 | 31h | SR2 쓰기 |
| Write Status Register-3 | 11h | SR3 쓰기 |
### 기타 명령어
| 명령어 | 코드 | 설명 |
|--------|------|------|
| Erase/Program Suspend | 75h | Erase/Program 일시 중단 |
| Erase/Program Resume | 7Ah | Erase/Program 재개 |
| Power-down | B9h | 저전력 모드 진입 |
| Enable Reset | 66h | 리셋 활성화 |
| Reset Device | 99h | 디바이스 리셋 |
| Enter QPI Mode | 38h | QPI 모드 진입 |
| Exit QPI Mode | FFh | QPI 모드 종료 |
## 상태 레지스터 (Status Registers)
### Status Register-1 (05h)
| Bit | 이름 | 설명 | R/W |
|-----|------|------|-----|
| S7 | SRP | Status Register Protect | R/W |
| S6 | SEC | Sector Protect | R/W |
| S5 | TB | Top/Bottom Block Protect | R/W |
| S4 | BP2 | Block Protect Bit 2 | R/W |
| S3 | BP1 | Block Protect Bit 1 | R/W |
| S2 | BP0 | Block Protect Bit 0 | R/W |
| S1 | WEL | Write Enable Latch | R |
| S0 | BUSY | Erase/Write In Progress | R |
### Status Register-2 (35h)
| Bit | 이름 | 설명 | R/W |
|-----|------|------|-----|
| S15 | SUS | Suspend Status | R |
| S14 | CMP | Complement Protect | R/W |
| S13 | LB3 | Security Register Lock 3 | OTP |
| S12 | LB2 | Security Register Lock 2 | OTP |
| S11 | LB1 | Security Register Lock 1 | OTP |
| S10 | LB0 | SFDP Lock | OTP |
| S9 | QE | Quad Enable | R/W |
| S8 | SRL | Status Register Lock | R/W |
### Status Register-3 (15h)
| Bit | 이름 | 설명 | R/W |
|-----|------|------|-----|
| S23 | HOLD/RST | /HOLD or /RESET 기능 | R/W |
| S22 | DRV1 | Output Driver Strength 1 | R/W |
| S21 | DRV0 | Output Driver Strength 0 | R/W |
| S20-S16 | Reserved | 예약됨 | - |
### Output Driver Strength 설정
| DRV1 | DRV0 | 출력 임피던스 |
|------|------|--------------|
| 0 | 0 | 25Ω |
| 0 | 1 | 33Ω |
| 1 | 0 | 50Ω (기본값) |
| 1 | 1 | 100Ω |
## 타이밍 특성
### AC 타이밍 파라미터
| 파라미터 | 심볼 | Min | Max | 단위 |
|----------|------|-----|-----|------|
| Clock Frequency (Fast Read) | fR | DC | 133 | MHz |
| Clock Frequency (Read Data) | fR | DC | 66 | MHz |
| /CS Setup Time | tSLCH | 5 | - | ns |
| /CS Hold Time | tCHSL | 5 | - | ns |
| Data In Setup Time | tDVCH | 2 | - | ns |
| Data In Hold Time | tCHDX | 2.5 | - | ns |
| Clock to Output Valid | tCLQV | - | 4.5 | ns |
| Output Disable Time | tSHQZ | - | 7 | ns |
| /CS Deselect Time (Read) | tSHSL1 | 10 | - | ns |
| /CS Deselect Time (Write) | tSHSL2 | 50 | - | ns |
### 프로그램/지우기 타이밍
| 동작 | 심볼 | Typical | Maximum | 단위 |
|------|------|---------|---------|------|
| Page Program | tPP | 0.25 | 2 | ms |
| Sector Erase (4KB) | tSE | 30 | 240 | ms |
| Block Erase (32KB) | tBE1 | 80 | 800 | ms |
| Block Erase (64KB) | tBE2 | 120 | 1200 | ms |
| Chip Erase | tCE | 6 | 40 | s |
| Write Status Register | tW | 1.5 | 15 | ms |
### 전원 타이밍
| 파라미터 | 심볼 | Min | Max | 단위 |
|----------|------|-----|-----|------|
| VCC(min) to /CS Low | tVSL | 20 | - | µs |
| Power-up to Write Allowed | tPUW | 5 | - | ms |
| Power-down Entry Time | tDP | - | 3 | µs |
| Release from Power-down | tRES1 | - | 3 | µs |
| Reset Time | tRST | - | 30 | µs |
## 전기적 특성
### DC 특성
| 파라미터 | 심볼 | Min | Typ | Max | 단위 |
|----------|------|-----|-----|-----|------|
| Input Low Voltage | VIL | -0.5 | - | 0.3×VCC | V |
| Input High Voltage | VIH | 0.7×VCC | - | VCC+0.4 | V |
| Output Low Voltage | VOL | - | - | 0.2 | V |
| Output High Voltage | VOH | VCC-0.2 | - | - | V |
| Standby Current | ICC1 | - | 10 | 28 | µA |
| Power-down Current | ICC2 | - | 0.1 | 8 | µA |
| Read Current (133MHz) | ICC3 | - | 11 | 20 | mA |
| Program Current | ICC5 | - | 8 | 15 | mA |
| Erase Current | ICC6 | - | 8 | 15 | mA |
## 쓰기 보호 (Write Protection)
### Block Protect 비트 조합 (CMP=0)
| SEC | TB | BP2 | BP1 | BP0 | 보호 영역 | 크기 |
|-----|-----|-----|-----|-----|----------|------|
| X | X | 0 | 0 | 0 | None | - |
| 0 | 0 | 0 | 0 | 1 | Upper 1/64 | 64KB |
| 0 | 0 | 0 | 1 | 0 | Upper 1/32 | 128KB |
| 0 | 0 | 0 | 1 | 1 | Upper 1/16 | 256KB |
| 0 | 0 | 1 | 0 | 0 | Upper 1/8 | 512KB |
| 0 | 0 | 1 | 0 | 1 | Upper 1/4 | 1MB |
| 0 | 0 | 1 | 1 | 0 | Upper 1/2 | 2MB |
| X | X | 1 | 1 | 1 | ALL | 4MB |
## Security Registers
W25Q32RV는 3개의 256-byte Security Register를 제공합니다.
| Register | 주소 범위 |
|----------|----------|
| Security Register 1 | 0x001000 - 0x0010FF |
| Security Register 2 | 0x002000 - 0x0020FF |
| Security Register 3 | 0x003000 - 0x0030FF |
### Security Register 명령어
| 명령어 | 코드 | 설명 |
|--------|------|------|
| Erase Security Register | 44h | Security Register 지우기 |
| Program Security Register | 42h | Security Register 프로그램 |
| Read Security Register | 48h | Security Register 읽기 |
## QPI 모드
### QPI 모드 진입/종료
1. **QPI 진입 조건**: QE 비트가 1로 설정되어야 함
2. **진입**: Enter QPI (38h) 명령어 실행
3. **종료**: Exit QPI (FFh) 명령어 실행
### QPI 모드에서의 명령어 전송
- 모든 명령어, 주소, 데이터가 4비트씩 전송됨
- 명령어 전송에 2클럭만 필요 (SPI 모드의 8클럭 대비)
## 소프트웨어 리셋
디바이스를 초기 상태로 리셋하려면 두 명령어를 순차적으로 실행:
1. Enable Reset (66h)
2. Reset Device (99h)
**주의**: 리셋 중 진행 중인 Erase/Program 동작이 있으면 데이터 손상 가능
## 패키지 정보
| 패키지 | 코드 | 크기 |
|--------|------|------|
| SOP 150-mil | CN | 8-pin |
| SOP 208-mil | CS | 8-pin |
| WSON 6x5-mm | CP | 8-pad |
| XSON 2x3-mm | XH | 8-pad |
| USON 4x3-mm | UU | 8-pad |
## 주문 정보
**Part Number 형식**: W25Q32RV[Package][Temp][Option]
예시: `W25Q32RVCNJQ`
- CN: 8-pin SOP 150-mil
- J: Industrial Plus (-40°C ~ +105°C)
- Q: QE=1 (고정)
## 참조 문서
- Winbond W25Q32RV Datasheet (Revision E, November 2025)
- JEDEC JESD216 (SFDP Standard)
---
*문서 작성일: 2026-01-30*
*소스: W25Q32RV Datasheet Rev.E*

View File

@@ -0,0 +1,317 @@
# W25Q32RV 외장 Flash 초기화 가이드
## 개요
W25Q32RV는 4MB SPI NOR Flash입니다. nRF52 내장 FDS와 달리 **자동 초기화가 없으므로** 드라이버에서 직접 처리해야 합니다.
## 내장 FDS vs 외장 W25Q32RV 비교
| 항목 | 내장 FDS | 외장 W25Q32RV |
|------|----------|---------------|
| 용량 | ~8KB (설정에 따라) | 4MB |
| 인터페이스 | SoftDevice API | SPI |
| 자동 초기화 | ✅ `fds_init()` | ❌ 수동 |
| Wear Leveling | ✅ FDS 내장 | ❌ 직접 구현 |
| 포맷 필요 | 자동 처리 | Sector Erase 필수 |
| 쓰기 단위 | 4 bytes (word) | 256 bytes (page) |
| 지우기 단위 | 자동 GC | 4KB (sector) |
## 하드웨어 연결 (nRF52840)
```
nRF52840 W25Q32RV
───────── ────────
P0.xx (SCK) ───► CLK (Pin 6)
P0.xx (MOSI) ───► DI (Pin 5)
P0.xx (MISO) ◄─── DO (Pin 2)
P0.xx (CS) ───► /CS (Pin 1)
3.3V ───► VCC (Pin 8)
GND ───► VSS (Pin 4)
3.3V ───► /WP (Pin 3) [또는 GPIO로 제어]
3.3V ───► /HOLD (Pin 7) [또는 GPIO로 제어]
```
## 초기화 순서
### 1단계: SPI 초기화
```c
#include "nrf_drv_spi.h"
#define SPI_INSTANCE 0
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);
void w25q32_spi_init(void)
{
nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
spi_config.ss_pin = W25Q_CS_PIN;
spi_config.miso_pin = W25Q_MISO_PIN;
spi_config.mosi_pin = W25Q_MOSI_PIN;
spi_config.sck_pin = W25Q_SCK_PIN;
spi_config.frequency = NRF_DRV_SPI_FREQ_8M; // 최대 8MHz (nRF52 제한)
spi_config.mode = NRF_DRV_SPI_MODE_0; // CPOL=0, CPHA=0
APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, NULL, NULL));
}
```
### 2단계: 칩 확인 (JEDEC ID 읽기)
```c
#define W25Q_CMD_JEDEC_ID 0x9F
#define W25Q_MANUFACTURER_ID 0xEF // Winbond
#define W25Q_DEVICE_ID 0x4016 // W25Q32
bool w25q32_check_id(void)
{
uint8_t tx_buf[1] = { W25Q_CMD_JEDEC_ID };
uint8_t rx_buf[4] = { 0 };
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, tx_buf, 1, rx_buf, 4);
nrf_gpio_pin_set(W25Q_CS_PIN);
// rx_buf[1] = Manufacturer ID (0xEF)
// rx_buf[2] = Memory Type (0x40)
// rx_buf[3] = Capacity (0x16 = 32Mbit)
if (rx_buf[1] == 0xEF && rx_buf[2] == 0x40 && rx_buf[3] == 0x16) {
DBG_PRINTF("W25Q32RV detected!\r\n");
return true;
}
DBG_PRINTF("Flash ID mismatch: %02X %02X %02X\r\n",
rx_buf[1], rx_buf[2], rx_buf[3]);
return false;
}
```
### 3단계: 상태 확인 (BUSY 체크)
```c
#define W25Q_CMD_READ_STATUS1 0x05
#define W25Q_STATUS_BUSY 0x01
bool w25q32_is_busy(void)
{
uint8_t tx_buf[1] = { W25Q_CMD_READ_STATUS1 };
uint8_t rx_buf[2] = { 0 };
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, tx_buf, 1, rx_buf, 2);
nrf_gpio_pin_set(W25Q_CS_PIN);
return (rx_buf[1] & W25Q_STATUS_BUSY) != 0;
}
void w25q32_wait_busy(void)
{
while (w25q32_is_busy()) {
nrf_delay_us(100);
}
}
```
### 4단계: Write Enable
```c
#define W25Q_CMD_WRITE_ENABLE 0x06
void w25q32_write_enable(void)
{
uint8_t cmd = W25Q_CMD_WRITE_ENABLE;
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, &cmd, 1, NULL, 0);
nrf_gpio_pin_set(W25Q_CS_PIN);
}
```
## 섹터 지우기 (Erase)
**중요**: Flash는 1→0만 가능합니다. 0→1로 바꾸려면 반드시 Erase가 필요합니다.
```c
#define W25Q_CMD_SECTOR_ERASE 0x20 // 4KB 단위
void w25q32_sector_erase(uint32_t address)
{
uint8_t tx_buf[4];
// 4KB 경계로 정렬
address &= 0xFFFFF000;
w25q32_write_enable();
tx_buf[0] = W25Q_CMD_SECTOR_ERASE;
tx_buf[1] = (address >> 16) & 0xFF;
tx_buf[2] = (address >> 8) & 0xFF;
tx_buf[3] = address & 0xFF;
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, tx_buf, 4, NULL, 0);
nrf_gpio_pin_set(W25Q_CS_PIN);
// Erase 완료 대기 (최대 240ms)
w25q32_wait_busy();
}
```
### 지우기 시간
| 명령 | 크기 | 일반 시간 | 최대 시간 |
|------|------|-----------|-----------|
| Sector Erase (0x20) | 4KB | 30ms | 240ms |
| Block Erase (0x52) | 32KB | 80ms | 800ms |
| Block Erase (0xD8) | 64KB | 120ms | 1200ms |
| Chip Erase (0xC7) | 4MB | 6s | 40s |
## 페이지 쓰기 (Page Program)
```c
#define W25Q_CMD_PAGE_PROGRAM 0x02
#define W25Q_PAGE_SIZE 256
void w25q32_page_write(uint32_t address, const uint8_t *data, uint16_t len)
{
uint8_t tx_buf[4 + W25Q_PAGE_SIZE];
if (len > W25Q_PAGE_SIZE) {
len = W25Q_PAGE_SIZE;
}
w25q32_write_enable();
tx_buf[0] = W25Q_CMD_PAGE_PROGRAM;
tx_buf[1] = (address >> 16) & 0xFF;
tx_buf[2] = (address >> 8) & 0xFF;
tx_buf[3] = address & 0xFF;
memcpy(&tx_buf[4], data, len);
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, tx_buf, 4 + len, NULL, 0);
nrf_gpio_pin_set(W25Q_CS_PIN);
// Write 완료 대기 (최대 2ms)
w25q32_wait_busy();
}
```
## 데이터 읽기
```c
#define W25Q_CMD_READ_DATA 0x03
void w25q32_read(uint32_t address, uint8_t *data, uint16_t len)
{
uint8_t tx_buf[4];
tx_buf[0] = W25Q_CMD_READ_DATA;
tx_buf[1] = (address >> 16) & 0xFF;
tx_buf[2] = (address >> 8) & 0xFF;
tx_buf[3] = address & 0xFF;
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, tx_buf, 4, NULL, 0);
nrf_drv_spi_transfer(&spi, NULL, 0, data, len);
nrf_gpio_pin_set(W25Q_CS_PIN);
}
```
## 첫 사용 시 초기화 플로우
```
┌─────────────────────────────────────────┐
│ 1. SPI 초기화 │
│ w25q32_spi_init() │
└────────────────┬────────────────────────┘
┌─────────────────────────────────────────┐
│ 2. 칩 ID 확인 │
│ w25q32_check_id() │
│ → 실패 시 에러 처리 │
└────────────────┬────────────────────────┘
┌─────────────────────────────────────────┐
│ 3. Magic Number 확인 (주소 0x000000) │
│ w25q32_read(0, &magic, 4) │
└────────────────┬────────────────────────┘
┌───────┴───────┐
│ magic == 유효? │
└───────┬───────┘
│ │
Yes No (첫 사용/손상)
│ │
↓ ↓
┌──────────────┐ ┌─────────────────────────┐
│ 정상 사용 │ │ 4. 첫 섹터 Erase │
│ │ │ w25q32_sector_erase(0)│
└──────────────┘ │ │
│ 5. Magic Number 쓰기 │
│ w25q32_page_write(0, │
│ &magic, 4) │
│ │
│ 6. 기본값 저장 │
└─────────────────────────┘
```
## 메모리 맵 예시
```
주소 범위 용도 크기
─────────────────────────────────────────────
0x000000 - 0x000FFF Config 영역 4KB (Sector 0)
0x001000 - 0x001FFF Calibration 1 4KB (Sector 1)
0x002000 - 0x002FFF Calibration 2 4KB (Sector 2)
0x003000 - 0x00FFFF Reserved 52KB
0x010000 - 0x0FFFFF Data Log 1 960KB
0x100000 - 0x1FFFFF Data Log 2 1MB
0x200000 - 0x3FFFFF Data Log 3 2MB
─────────────────────────────────────────────
Total 4MB
```
## Power-down 모드
저전력 모드가 필요한 경우:
```c
#define W25Q_CMD_POWER_DOWN 0xB9
#define W25Q_CMD_RELEASE_PD 0xAB
void w25q32_power_down(void)
{
uint8_t cmd = W25Q_CMD_POWER_DOWN;
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, &cmd, 1, NULL, 0);
nrf_gpio_pin_set(W25Q_CS_PIN);
// 전류: 0.1µA (typ)
}
void w25q32_wake_up(void)
{
uint8_t cmd = W25Q_CMD_RELEASE_PD;
nrf_gpio_pin_clear(W25Q_CS_PIN);
nrf_drv_spi_transfer(&spi, &cmd, 1, NULL, 0);
nrf_gpio_pin_set(W25Q_CS_PIN);
nrf_delay_us(3); // tRES1: Release from power-down
}
```
## 주의사항
1. **Erase 전 확인**: 쓰기 전 해당 섹터가 이미 Erase 되어 있는지 확인
2. **Page 경계**: Page Program은 256바이트 경계를 넘지 않도록 주의
3. **Wear Leveling**: 같은 섹터 반복 사용 시 수명 단축 (최소 100,000회)
4. **전원 차단**: Erase/Program 중 전원 차단 시 데이터 손상 가능
5. **SPI 속도**: nRF52840은 최대 8MHz, W25Q32RV는 133MHz까지 지원
## 관련 파일
- [W25Q32RV_FLASH_MEMORY.md](./W25Q32RV_FLASH_MEMORY.md) - 상세 스펙
- [fstorage.c](../fstorage.c) - 내장 FDS 구현 참조
---
*문서 작성일: 2026-02-04*

View File

@@ -0,0 +1,214 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="900px" height="1200px" viewBox="-0.5 -0.5 900 1200" content="&lt;mxfile host=&quot;app.diagrams.net&quot;&gt;&lt;diagram name=&quot;VivaMayo Architecture&quot;&gt;&lt;mxGraphModel dx=&quot;900&quot; dy=&quot;1200&quot; grid=&quot;1&quot; gridSize=&quot;10&quot; guides=&quot;1&quot; tooltips=&quot;1&quot; connect=&quot;1&quot; arrows=&quot;1&quot; fold=&quot;1&quot; page=&quot;1&quot; pageScale=&quot;1&quot; pageWidth=&quot;900&quot; pageHeight=&quot;1200&quot;&gt;&lt;root&gt;&lt;mxCell id=&quot;0&quot;/&gt;&lt;mxCell id=&quot;1&quot; parent=&quot;0&quot;/&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs>
<style type="text/css">
.title { font: bold 24px 'Segoe UI', Arial, sans-serif; fill: #1a1a1a; }
.subtitle { font: 16px 'Segoe UI', Arial, sans-serif; fill: #666; }
.box-main { fill: #4a90d9; stroke: #2d5a8a; stroke-width: 2; rx: 8; }
.box-ble { fill: #5cb85c; stroke: #3d8b3d; stroke-width: 2; rx: 8; }
.box-parser { fill: #f0ad4e; stroke: #c77c00; stroke-width: 2; rx: 8; }
.box-cmd { fill: #d9534f; stroke: #a33b38; stroke-width: 2; rx: 8; }
.box-handler { fill: #9b59b6; stroke: #6c3483; stroke-width: 2; rx: 8; }
.box-peripheral { fill: #17a2b8; stroke: #0d6efd; stroke-width: 2; rx: 8; }
.box-storage { fill: #6c757d; stroke: #495057; stroke-width: 2; rx: 8; }
.box-label { font: bold 14px 'Segoe UI', Arial, sans-serif; fill: white; text-anchor: middle; }
.box-sublabel { font: 11px 'Segoe UI', Arial, sans-serif; fill: rgba(255,255,255,0.8); text-anchor: middle; }
.arrow { stroke: #333; stroke-width: 2; fill: none; marker-end: url(#arrowhead); }
.arrow-dashed { stroke: #666; stroke-width: 2; fill: none; stroke-dasharray: 5,5; marker-end: url(#arrowhead); }
.section-title { font: bold 16px 'Segoe UI', Arial, sans-serif; fill: #333; }
.note { font: 12px 'Segoe UI', Arial, sans-serif; fill: #666; }
.legend-box { stroke: #ccc; stroke-width: 1; fill: #fafafa; rx: 5; }
</style>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#333"/>
</marker>
</defs>
<!-- Background -->
<rect width="900" height="1200" fill="#ffffff"/>
<!-- Title -->
<text x="450" y="40" class="title" text-anchor="middle">VivaMayo BLE Application Architecture</text>
<text x="450" y="65" class="subtitle" text-anchor="middle">nRF52840 기반 NIRS 방광 모니터링 장치</text>
<!-- ==================== Section 1: Application Entry ==================== -->
<text x="50" y="110" class="section-title">1. Application Entry</text>
<!-- main.c -->
<rect x="350" y="130" width="200" height="60" class="box-main"/>
<text x="450" y="158" class="box-label">main.c</text>
<text x="450" y="175" class="box-sublabel">Application Entry Point</text>
<!-- ==================== Section 2: Core Modules ==================== -->
<text x="50" y="230" class="section-title">2. Core Modules</text>
<!-- BLE Stack -->
<rect x="100" y="250" width="160" height="50" class="box-ble"/>
<text x="180" y="275" class="box-label">ble_core.c</text>
<text x="180" y="290" class="box-sublabel">BLE Stack</text>
<!-- Power Control -->
<rect x="370" y="250" width="160" height="50" class="box-peripheral"/>
<text x="450" y="275" class="box-label">power_ctrl.c</text>
<text x="450" y="290" class="box-sublabel">Power Management</text>
<!-- Timer -->
<rect x="640" y="250" width="160" height="50" class="box-peripheral"/>
<text x="720" y="275" class="box-label">main_timer.c</text>
<text x="720" y="290" class="box-sublabel">Timer Management</text>
<!-- Arrows from main -->
<path d="M 400 190 L 400 210 L 180 210 L 180 250" class="arrow"/>
<path d="M 450 190 L 450 250" class="arrow"/>
<path d="M 500 190 L 500 210 L 720 210 L 720 250" class="arrow"/>
<!-- ==================== Section 3: BLE Data Flow ==================== -->
<text x="50" y="350" class="section-title">3. BLE Data Reception</text>
<!-- NUS Data Handler -->
<rect x="100" y="370" width="200" height="50" class="box-ble"/>
<text x="200" y="395" class="box-label">nus_data_handler()</text>
<text x="200" y="410" class="box-sublabel">BLE NUS RX Event</text>
<!-- Arrow -->
<path d="M 180 300 L 180 330 L 200 330 L 200 370" class="arrow"/>
<!-- ==================== Section 4: Command Processing ==================== -->
<text x="50" y="470" class="section-title">4. Command Processing</text>
<!-- received_command_process -->
<rect x="100" y="490" width="250" height="50" class="box-parser"/>
<text x="225" y="515" class="box-label">received_command_process()</text>
<text x="225" y="530" class="box-sublabel">cmd_parse.c</text>
<!-- Arrow -->
<path d="M 200 420 L 200 450 L 225 450 L 225 490" class="arrow"/>
<!-- ==================== Section 5: Parser Layer ==================== -->
<text x="50" y="590" class="section-title">5. Parser Layer (Mt_parser)</text>
<!-- dr_cmd_parser -->
<rect x="100" y="610" width="200" height="60" class="box-parser"/>
<text x="200" y="635" class="box-label">dr_cmd_parser()</text>
<text x="200" y="655" class="box-sublabel">parser.c - CRC + TAG</text>
<!-- Legacy Parser -->
<rect x="400" y="610" width="200" height="60" class="box-storage"/>
<text x="500" y="635" class="box-label">Legacy Parser</text>
<text x="500" y="655" class="box-sublabel">cmd_parse.c (fallback)</text>
<!-- Arrows -->
<path d="M 225 540 L 225 570 L 200 570 L 200 610" class="arrow"/>
<path d="M 300 640 L 400 640" class="arrow-dashed"/>
<text x="350" y="630" class="note">fallback</text>
<!-- ==================== Section 6: Command Dispatch ==================== -->
<text x="50" y="720" class="section-title">6. Command Dispatch</text>
<!-- g_cmd_table -->
<rect x="100" y="740" width="200" height="50" class="box-cmd"/>
<text x="200" y="765" class="box-label">g_cmd_table[]</text>
<text x="200" y="780" class="box-sublabel">51 Commands</text>
<!-- Arrow -->
<path d="M 200 670 L 200 740" class="arrow"/>
<!-- ==================== Section 7: Command Handlers ==================== -->
<text x="50" y="840" class="section-title">7. Command Handlers (cmd.c)</text>
<!-- Handler boxes -->
<rect x="50" y="860" width="120" height="45" class="box-handler"/>
<text x="110" y="882" class="box-label">Cmd_mta</text>
<text x="110" y="897" class="box-sublabel">Device Status</text>
<rect x="180" y="860" width="120" height="45" class="box-handler"/>
<text x="240" y="882" class="box-label">Cmd_mcj</text>
<text x="240" y="897" class="box-sublabel">M48 Measure</text>
<rect x="310" y="860" width="120" height="45" class="box-handler"/>
<text x="370" y="882" class="box-label">Cmd_msn</text>
<text x="370" y="897" class="box-sublabel">Battery</text>
<rect x="440" y="860" width="120" height="45" class="box-handler"/>
<text x="500" y="882" class="box-label">Cmd_mag</text>
<text x="500" y="897" class="box-sublabel">AGC</text>
<rect x="570" y="860" width="120" height="45" class="box-handler"/>
<text x="630" y="882" class="box-label">Cmd_cmd</text>
<text x="630" y="897" class="box-sublabel">GPIO Test</text>
<rect x="700" y="860" width="120" height="45" class="box-handler"/>
<text x="760" y="882" class="box-label">...</text>
<text x="760" y="897" class="box-sublabel">+45 more</text>
<!-- Arrows to handlers -->
<path d="M 200 790 L 200 820 L 110 820 L 110 860" class="arrow"/>
<path d="M 200 790 L 200 820 L 240 820 L 240 860" class="arrow"/>
<path d="M 200 790 L 200 820 L 370 820 L 370 860" class="arrow"/>
<path d="M 200 790 L 200 820 L 500 820 L 500 860" class="arrow"/>
<path d="M 200 790 L 200 820 L 630 820 L 630 860" class="arrow"/>
<path d="M 200 790 L 200 820 L 760 820 L 760 860" class="arrow"/>
<!-- ==================== Section 8: Hardware Layer ==================== -->
<text x="50" y="960" class="section-title">8. Hardware / Peripherals</text>
<rect x="50" y="980" width="100" height="40" class="box-peripheral"/>
<text x="100" y="1005" class="box-label">LED x48</text>
<rect x="160" y="980" width="100" height="40" class="box-peripheral"/>
<text x="210" y="1005" class="box-label">PD ADC</text>
<rect x="270" y="980" width="100" height="40" class="box-peripheral"/>
<text x="320" y="1005" class="box-label">IMU</text>
<rect x="380" y="980" width="100" height="40" class="box-peripheral"/>
<text x="430" y="1005" class="box-label">Temp</text>
<rect x="490" y="980" width="100" height="40" class="box-peripheral"/>
<text x="540" y="1005" class="box-label">Pressure</text>
<rect x="600" y="980" width="100" height="40" class="box-peripheral"/>
<text x="650" y="1005" class="box-label">Battery</text>
<rect x="710" y="980" width="100" height="40" class="box-peripheral"/>
<text x="760" y="1005" class="box-label">EEPROM</text>
<!-- ==================== Section 9: BLE Response ==================== -->
<text x="50" y="1070" class="section-title">9. BLE Response</text>
<rect x="100" y="1090" width="200" height="50" class="box-ble"/>
<text x="200" y="1115" class="box-label">binary_tx_handler()</text>
<text x="200" y="1130" class="box-sublabel">ble_data_tx.c + CRC16</text>
<rect x="400" y="1090" width="200" height="50" class="box-ble"/>
<text x="500" y="1115" class="box-label">BLE Central</text>
<text x="500" y="1130" class="box-sublabel">Mobile App</text>
<path d="M 300 1115 L 400 1115" class="arrow"/>
<!-- ==================== Legend ==================== -->
<rect x="650" y="1050" width="220" height="130" class="legend-box"/>
<text x="760" y="1072" class="section-title" text-anchor="middle">Legend</text>
<rect x="665" y="1085" width="20" height="15" class="box-main"/>
<text x="695" y="1097" class="note">Main Entry</text>
<rect x="665" y="1105" width="20" height="15" class="box-ble"/>
<text x="695" y="1117" class="note">BLE Layer</text>
<rect x="665" y="1125" width="20" height="15" class="box-parser"/>
<text x="695" y="1137" class="note">Parser Layer</text>
<rect x="780" y="1085" width="20" height="15" class="box-cmd"/>
<text x="810" y="1097" class="note">Cmd Table</text>
<rect x="780" y="1105" width="20" height="15" class="box-handler"/>
<text x="810" y="1117" class="note">Handlers</text>
<rect x="780" y="1125" width="20" height="15" class="box-peripheral"/>
<text x="810" y="1137" class="note">Peripherals</text>
<!-- Version info -->
<text x="450" y="1180" class="note" text-anchor="middle">VivaMayo Architecture v1.0 | Generated: 2026-01-30</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800px" height="980px" viewBox="-0.5 -0.5 800 980">
<defs>
<style type="text/css">
.title { font: bold 22px 'Segoe UI', Arial, sans-serif; fill: #1a1a1a; }
.subtitle { font: 14px 'Segoe UI', Arial, sans-serif; fill: #666; }
.process { fill: #e3f2fd; stroke: #1976d2; stroke-width: 2; rx: 8; }
.decision { fill: #fff3e0; stroke: #f57c00; stroke-width: 2; }
.terminal { fill: #e8f5e9; stroke: #388e3c; stroke-width: 2; rx: 20; }
.error { fill: #ffebee; stroke: #d32f2f; stroke-width: 2; rx: 8; }
.data { fill: #f3e5f5; stroke: #7b1fa2; stroke-width: 2; }
.box-label { font: bold 12px 'Segoe UI', Arial, sans-serif; fill: #333; text-anchor: middle; }
.box-sublabel { font: 10px 'Segoe UI', Arial, sans-serif; fill: #666; text-anchor: middle; }
.arrow { stroke: #333; stroke-width: 2; fill: none; marker-end: url(#arrowhead); }
.arrow-yes { stroke: #388e3c; stroke-width: 2; fill: none; marker-end: url(#arrowhead-green); }
.arrow-no { stroke: #d32f2f; stroke-width: 2; fill: none; marker-end: url(#arrowhead-red); }
.label { font: 11px 'Segoe UI', Arial, sans-serif; fill: #333; }
.label-yes { font: bold 11px 'Segoe UI', Arial, sans-serif; fill: #388e3c; }
.label-no { font: bold 11px 'Segoe UI', Arial, sans-serif; fill: #d32f2f; }
.note { font: 10px 'Segoe UI', Arial, sans-serif; fill: #666; font-style: italic; }
.packet { fill: #fafafa; stroke: #999; stroke-width: 1; }
.packet-label { font: 10px 'Courier New', monospace; fill: #333; }
</style>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#333"/>
</marker>
<marker id="arrowhead-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#388e3c"/>
</marker>
<marker id="arrowhead-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#d32f2f"/>
</marker>
</defs>
<!-- Background -->
<rect width="800" height="980" fill="#ffffff"/>
<!-- Title -->
<text x="400" y="35" class="title" text-anchor="middle">Command Processing Flow</text>
<text x="400" y="55" class="subtitle" text-anchor="middle">Mt_parser 명령어 처리 흐름도</text>
<!-- ==================== Start ==================== -->
<rect x="300" y="80" width="200" height="40" class="terminal"/>
<text x="400" y="105" class="box-label">BLE Data Received</text>
<path d="M 400 120 L 400 150" class="arrow"/>
<!-- ==================== nus_data_handler ==================== -->
<rect x="300" y="150" width="200" height="50" class="process"/>
<text x="400" y="175" class="box-label">nus_data_handler()</text>
<text x="400" y="190" class="box-sublabel">ble_core.c</text>
<path d="M 400 200 L 400 230" class="arrow"/>
<!-- ==================== received_command_process ==================== -->
<rect x="275" y="230" width="250" height="50" class="process"/>
<text x="400" y="255" class="box-label">received_command_process()</text>
<text x="400" y="270" class="box-sublabel">cmd_parse.c</text>
<path d="M 400 280 L 400 310" class="arrow"/>
<!-- ==================== dr_cmd_parser ==================== -->
<rect x="300" y="310" width="200" height="50" class="process"/>
<text x="400" y="335" class="box-label">dr_cmd_parser()</text>
<text x="400" y="350" class="box-sublabel">parser.c</text>
<path d="M 400 360 L 400 400" class="arrow"/>
<!-- ==================== CRC Check Decision ==================== -->
<polygon points="400,400 480,440 400,480 320,440" class="decision"/>
<text x="400" y="445" class="box-label">CRC OK?</text>
<!-- CRC Fail -->
<path d="M 480 440 L 580 440 L 580 400" class="arrow-no"/>
<text x="520" y="430" class="label-no">NO</text>
<rect x="530" y="360" width="120" height="40" class="error"/>
<text x="590" y="385" class="box-label">"crc!" 응답</text>
<!-- CRC OK -->
<path d="M 400 480 L 400 520" class="arrow-yes"/>
<text x="415" y="500" class="label-yes">YES</text>
<!-- ==================== TAG Extract ==================== -->
<rect x="300" y="520" width="200" height="50" class="process"/>
<text x="400" y="540" class="box-label">TAG 추출</text>
<text x="400" y="555" class="box-sublabel">"xxx?" 형식 (? 포함 4글자)</text>
<text x="400" y="568" class="box-sublabel">dr_copy_tag()</text>
<path d="M 400 570 L 400 610" class="arrow"/>
<!-- ==================== Command Table Search Decision ==================== -->
<polygon points="400,610 500,660 400,710 300,660" class="decision"/>
<text x="400" y="655" class="box-label">g_cmd_table</text>
<text x="400" y="670" class="box-label">에서 검색?</text>
<!-- Not Found -->
<path d="M 500 660 L 600 660 L 600 620" class="arrow-no"/>
<text x="540" y="650" class="label-no">NOT FOUND</text>
<rect x="530" y="580" width="140" height="40" class="process"/>
<text x="600" y="605" class="box-label">Legacy Parser로</text>
<!-- Found -->
<path d="M 400 710 L 400 750" class="arrow-yes"/>
<text x="415" y="730" class="label-yes">FOUND</text>
<!-- ==================== Enabled Check ==================== -->
<polygon points="400,750 470,785 400,820 330,785" class="decision"/>
<text x="400" y="790" class="box-label">enabled?</text>
<!-- Disabled -->
<path d="M 470 785 L 550 785" class="arrow-no"/>
<text x="500" y="775" class="label-no">false</text>
<rect x="550" y="765" width="100" height="40" class="error"/>
<text x="600" y="790" class="box-label">return 0</text>
<!-- Enabled -->
<path d="M 400 820 L 400 860" class="arrow-yes"/>
<text x="415" y="840" class="label-yes">true</text>
<!-- ==================== Handler Execute ==================== -->
<rect x="300" y="860" width="200" height="50" class="terminal"/>
<text x="400" y="885" class="box-label">handler() 실행</text>
<text x="400" y="900" class="box-sublabel">Cmd_xxx()</text>
<!-- ==================== Packet Structure ==================== -->
<text x="80" y="110" class="label" font-weight="bold">Packet Structure:</text>
<rect x="30" y="125" width="60" height="30" class="packet"/>
<text x="60" y="145" class="packet-label">TAG</text>
<text x="60" y="165" class="note">4 bytes</text>
<rect x="90" y="125" width="80" height="30" class="packet"/>
<text x="130" y="145" class="packet-label">DATA</text>
<text x="130" y="165" class="note">N bytes</text>
<rect x="170" y="125" width="60" height="30" class="packet"/>
<text x="200" y="145" class="packet-label">CRC</text>
<text x="200" y="165" class="note">2 bytes</text>
<!-- Example -->
<text x="80" y="200" class="label" font-weight="bold">Example (sta? mode=1):</text>
<!-- TAG box -->
<rect x="30" y="215" width="80" height="50" class="packet"/>
<text x="70" y="235" class="packet-label" text-anchor="middle">73 74 61 3F</text>
<text x="70" y="255" class="note" text-anchor="middle">s t a ?</text>
<!-- DATA box -->
<rect x="110" y="215" width="60" height="50" class="packet"/>
<text x="140" y="235" class="packet-label" text-anchor="middle">00 01</text>
<text x="140" y="255" class="note" text-anchor="middle">val=1</text>
<!-- CRC box -->
<rect x="170" y="215" width="60" height="50" class="packet"/>
<text x="200" y="235" class="packet-label" text-anchor="middle">XX XX</text>
<text x="200" y="255" class="note" text-anchor="middle">CRC</text>
<!-- Legend -->
<rect x="580" y="80" width="190" height="180" fill="#fafafa" stroke="#ddd" rx="5"/>
<text x="675" y="105" class="label" font-weight="bold" text-anchor="middle">Legend</text>
<rect x="595" y="120" width="25" height="18" class="process"/>
<text x="630" y="133" class="label">Process</text>
<polygon points="607,155 620,165 607,175 595,165" class="decision"/>
<text x="630" y="168" class="label">Decision</text>
<rect x="595" y="185" width="25" height="18" class="terminal"/>
<text x="630" y="198" class="label">Start/End</text>
<rect x="595" y="215" width="25" height="18" class="error"/>
<text x="630" y="228" class="label">Error</text>
<!-- Version -->
<text x="400" y="960" class="note" text-anchor="middle">Command Flow v1.0 | Generated: 2026-01-30</text>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,274 @@
/*******************************************************************************
* @file w25q32.c
* @brief W25Q32RV External SPI Flash Driver (Pure Bit-bang - bypass nrfx)
******************************************************************************/
#include "w25q32.h"
#include "../../debug_print.h"
#include "../../spi2_bus.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
/*==============================================================================
* COMMANDS
*============================================================================*/
#define CMD_JEDEC_ID 0x9F
#define CMD_READ 0x03
#define CMD_WREN 0x06
#define CMD_PP 0x02
#define CMD_SECTOR_ERASE 0x20
#define CMD_CHIP_ERASE 0xC7
#define CMD_RDSR 0x05
#define SR_BUSY 0x01
/*==============================================================================
* BIT-BANG SPI (bypass nrfx_spim completely)
*============================================================================*/
static inline void cs_low(void) { nrf_gpio_pin_clear(W25Q_CS_PIN); }
static inline void cs_high(void) { nrf_gpio_pin_set(W25Q_CS_PIN); }
/*==============================================================================
* POWER CONTROL - P0.01 + P0.11 both HIGH = W25Q32 power ON
*============================================================================*/
void w25q32_power_on(void)
{
nrf_gpio_cfg_output(W25Q_PWR_PIN1);
nrf_gpio_cfg_output(W25Q_PWR_PIN2);
nrf_gpio_pin_set(W25Q_PWR_PIN1); /* P0.01 HIGH */
nrf_gpio_pin_set(W25Q_PWR_PIN2); /* P0.11 HIGH */
nrf_delay_ms(10); /* Wait for power stabilization */
DBG_PRINTF("[W25Q] Power ON (P0.%d=H, P0.%d=H)\r\n", W25Q_PWR_PIN1, W25Q_PWR_PIN2);
}
void w25q32_power_off(void)
{
nrf_gpio_pin_clear(W25Q_PWR_PIN1);
nrf_gpio_pin_clear(W25Q_PWR_PIN2);
DBG_PRINTF("[W25Q] Power OFF\r\n");
}
static void bb_spi_init(void)
{
/* Step 1: Power ON W25Q32 */
w25q32_power_on();
/* Step 2: Release nrfx_spim ownership of pins */
if (spi2_bus_is_initialized()) {
DBG_PRINTF("[W25Q] Releasing SPI2 bus...\r\n");
spi2_bus_uninit();
nrf_delay_ms(1);
}
/* Step 3: ADA2200 CS HIGH (deselect) */
nrf_gpio_cfg_output(13); /* ADA2200 CS = P0.13 */
nrf_gpio_pin_set(13); /* HIGH = deselected */
DBG_PRINTF("[W25Q] ADA2200 CS(P0.13) = HIGH\r\n");
/* Step 4: Force pins back to GPIO mode */
nrf_gpio_cfg_default(W25Q_SCK_PIN);
nrf_gpio_cfg_default(W25Q_MOSI_PIN);
nrf_gpio_cfg_default(W25Q_MISO_PIN);
nrf_gpio_cfg_default(W25Q_CS_PIN);
/* Step 5: Configure bit-bang SPI with HIGH drive strength */
nrf_gpio_cfg(W25Q_SCK_PIN,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_H0H1,
NRF_GPIO_PIN_NOSENSE);
nrf_gpio_cfg(W25Q_MOSI_PIN,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_H0H1,
NRF_GPIO_PIN_NOSENSE);
nrf_gpio_cfg_input(W25Q_MISO_PIN, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg(W25Q_CS_PIN,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_H0H1,
NRF_GPIO_PIN_NOSENSE);
nrf_gpio_pin_clear(W25Q_SCK_PIN); /* Clock idle low (Mode 0) */
nrf_gpio_pin_set(W25Q_CS_PIN); /* CS high (deselected) */
DBG_PRINTF("[W25Q] GPIO: SCK=%d MOSI=%d MISO=%d(dedicated) CS=%d\r\n",
W25Q_SCK_PIN, W25Q_MOSI_PIN, W25Q_MISO_PIN, W25Q_CS_PIN);
/* Step 6: Check MISO state */
int miso_state = nrf_gpio_pin_read(W25Q_MISO_PIN);
DBG_PRINTF("[W25Q] MISO(P0.%d) = %s\r\n", W25Q_MISO_PIN, miso_state ? "HIGH" : "LOW");
}
static uint8_t bb_spi_transfer_byte(uint8_t tx)
{
uint8_t rx = 0;
for (int i = 7; i >= 0; i--) {
/* Set MOSI */
if (tx & (1 << i)) {
nrf_gpio_pin_set(W25Q_MOSI_PIN);
} else {
nrf_gpio_pin_clear(W25Q_MOSI_PIN);
}
/* Clock high - sample MISO */
nrf_gpio_pin_set(W25Q_SCK_PIN);
__NOP(); __NOP(); __NOP(); __NOP();
if (nrf_gpio_pin_read(W25Q_MISO_PIN)) {
rx |= (1 << i);
}
/* Clock low */
nrf_gpio_pin_clear(W25Q_SCK_PIN);
__NOP(); __NOP(); __NOP(); __NOP();
}
return rx;
}
/*==============================================================================
* PRIVATE HELPERS
*============================================================================*/
static uint8_t read_status(void)
{
uint8_t status;
cs_low();
bb_spi_transfer_byte(CMD_RDSR);
status = bb_spi_transfer_byte(0xFF);
cs_high();
return status;
}
static void wait_ready(void)
{
while (read_status() & SR_BUSY) {
nrf_delay_ms(1);
}
}
/*==============================================================================
* PUBLIC FUNCTIONS
*============================================================================*/
bool w25q32_init(void)
{
DBG_PRINTF("[W25Q] init (bit-bang)\r\n");
bb_spi_init();
nrf_delay_ms(5);
return w25q32_check_jedec();
}
bool w25q32_check_jedec(void)
{
uint8_t mfr, type, cap;
cs_low();
bb_spi_transfer_byte(CMD_JEDEC_ID);
mfr = bb_spi_transfer_byte(0xFF);
type = bb_spi_transfer_byte(0xFF);
cap = bb_spi_transfer_byte(0xFF);
cs_high();
DBG_PRINTF("[W25Q] JEDEC = %02X %02X %02X\r\n", mfr, type, cap);
/* Winbond = 0xEF, W25Q32 = 0x40 0x16 */
return (mfr == 0xEF);
}
void w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len)
{
cs_low();
bb_spi_transfer_byte(CMD_READ);
bb_spi_transfer_byte((uint8_t)(addr >> 16));
bb_spi_transfer_byte((uint8_t)(addr >> 8));
bb_spi_transfer_byte((uint8_t)(addr));
for (uint32_t i = 0; i < len; i++) {
buf[i] = bb_spi_transfer_byte(0xFF);
}
cs_high();
}
void w25q32_write(uint32_t addr, const uint8_t *buf, uint32_t len)
{
while (len) {
uint32_t page = 256 - (addr & 0xFF);
if (page > len) page = len;
/* Write Enable */
cs_low();
bb_spi_transfer_byte(CMD_WREN);
cs_high();
/* Page Program */
cs_low();
bb_spi_transfer_byte(CMD_PP);
bb_spi_transfer_byte((uint8_t)(addr >> 16));
bb_spi_transfer_byte((uint8_t)(addr >> 8));
bb_spi_transfer_byte((uint8_t)(addr));
for (uint32_t i = 0; i < page; i++) {
bb_spi_transfer_byte(buf[i]);
}
cs_high();
wait_ready();
addr += page;
buf += page;
len -= page;
}
}
void w25q32_sector_erase(uint32_t addr)
{
addr &= ~0xFFF; /* Align to 4KB sector */
/* Write Enable */
cs_low();
bb_spi_transfer_byte(CMD_WREN);
cs_high();
/* Sector Erase */
cs_low();
bb_spi_transfer_byte(CMD_SECTOR_ERASE);
bb_spi_transfer_byte((uint8_t)(addr >> 16));
bb_spi_transfer_byte((uint8_t)(addr >> 8));
bb_spi_transfer_byte((uint8_t)(addr));
cs_high();
wait_ready();
}
void w25q32_chip_erase(void)
{
DBG_PRINTF("[W25Q] Chip erase...\r\n");
/* Write Enable */
cs_low();
bb_spi_transfer_byte(CMD_WREN);
cs_high();
/* Chip Erase */
cs_low();
bb_spi_transfer_byte(CMD_CHIP_ERASE);
cs_high();
wait_ready();
DBG_PRINTF("[W25Q] Erase done\r\n");
}

View File

@@ -0,0 +1,41 @@
/*******************************************************************************
* @file w25q32.h
* @brief W25Q32RV External SPI Flash Driver (Simplified)
******************************************************************************/
#ifndef W25Q32_H
#define W25Q32_H
#include <stdbool.h>
#include <stdint.h>
/*==============================================================================
* PIN CONFIGURATION - VivaMayo hardware
*============================================================================*/
#define W25Q_CS_PIN 24 /* P0.24 - Chip Select */
#define W25Q_SCK_PIN 14 /* P0.14 - Clock (shared with ADA2200) */
#define W25Q_MOSI_PIN 16 /* P0.16 - Master Out (shared with ADA2200) */
#define W25Q_MISO_PIN 19 /* P0.19 - Master In (dedicated, avoid ADA2200 conflict) */
/* Power control pins - both HIGH = W25Q32 power ON */
#define W25Q_PWR_PIN1 1 /* P0.01 - W25Q32 dedicated */
#define W25Q_PWR_PIN2 11 /* P0.11 - W25Q32 dedicated */
/*==============================================================================
* API FUNCTIONS
*============================================================================*/
void w25q32_power_on(void);
void w25q32_power_off(void);
bool w25q32_init(void);
bool w25q32_check_jedec(void);
#define w25q32_check_id w25q32_check_jedec /* Legacy alias */
void w25q32_read(uint32_t addr, uint8_t *buf, uint32_t len);
void w25q32_write(uint32_t addr, const uint8_t *buf, uint32_t len);
void w25q32_sector_erase(uint32_t addr);
void w25q32_chip_erase(void);
#endif /* W25Q32_H */

View File

@@ -0,0 +1,401 @@
/*******************************************************************************
TEST medi50 Dec 23
******************************************************************************/
#include "sdk_config.h"
#include <string.h>
#include "app_error.h"
#include "boards.h"
#include "nrf_fstorage.h"
#include "nrf_soc.h"
#include "nrf_strerror.h"
#include "sdk_config.h"
#include "nrf_fstorage_sd.h"
#include "nrf_delay.h"
#include "ble_gap.h"
#include "fds.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "fstorage.h"
#include "nrf_pwr_mgmt.h"
#include "measurements.h"
#include "main.h"
#include "power/power_ctrl.h"
#include "debug_print.h"
/* File ID and Key used for the configuration record. */
#define CONFIG_FILE (0x8010)
#define CONFIG_REC_KEY (0x7010)
#define CONFIG_MAGIC_NUMBER_VALUE (0x20231226)
config_data_t m_config;
extern bool go_device_power_off;
extern bool go_sleep_mode_enter;
extern bool go_NVIC_SystemReset;
/* Flag to check fds initialization. */
static bool volatile m_fds_initialized;
bool fds_flag_write = false;
/* A record containing dummy configuration data. */
static fds_record_t const m_dummy_record =
{
.file_id = CONFIG_FILE,
.key = CONFIG_REC_KEY,
.data.p_data = (void const *)&m_config,
/* The length of a record is always expressed in 4-byte units (words). */
.data.length_words = (sizeof(m_config) + 3) / sizeof(uint32_t),
};
/* 공장 입력 항목 1 */
/* 공장 입력 항목 2 */
//char serial_number_dflt[12] = "2025AAMAY0FF";
//uint16_t pd_delay_us = 0;
//int8_t reset_status=0;
//uint32_t pd_adc_calibration_dflt_PD0[M_LED_NUM] =
// {1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000, /* PD01 */
//};
//uint32_t pd_adc_calibration_dflt_PD1[M_LED_NUM] =
// {1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000, /* PD1 */
//};
//uint32_t pd_adc_calibration_dflt_PD2[M_LED_NUM] =
// {1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000,1000, 1000, 1000, 1000, 1000, 1000, /* MOD1 */
//};
///* 공장 입력 항목 5 */
//uint32_t dark_noise_for_pd_dflt[PD_NUM] = /*PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PD10, PD11, PD12, PD13, PD14, PD15, PD16, PD17, PD18, PD19, PD20, */
// {1111, 1111};
/* 공장 입력 항목 8 */
//int8_t pd_adc_cnt_dflt = 8; /* 8, 16, 24, 32 */
int8_t reset_status_dflt =99;
/* 공장 입력 항목 9 */
//uint16_t led_delay_us_dflt = 8000; /* 0 ~ 65535 */
/* 공장 입력 항목 10 */
//uint16_t pd_delay_us_dflt = 8000; /* 0 ~ 65535 */
/* Static Passkey default */
uint8_t static_passkey_dflt[6] = "123456";
//
//uint16_t led_power_dp_dflt[48] = {
// //NULL, 42, 29, 41, 31, 28, 28, 42, 28, 39, 29, 27, 27, 42, 29, 40, 30, 28, 28, 40, 27, 38, 28, 25, 22
// 25,25,25,18,19,24,24,25,25,18,19,25,23,25,25,18,19,24,24,25,25,19,20,25,24,25,25,25,18,19,24,24,25,25,18,19,25,23,25,25,18,19,24,24,25,25,19,20
//};
void fds_default_value_set(void)
{
/* HW Number - empty (set via BLE command) */
memset(m_config.hw_no, 0, 12);
/* Serial Number */
memcpy(m_config.serial_no, "2025AAAAT001", 12);
/* Static Passkey */
memcpy(m_config.static_passkey, static_passkey_dflt, 6);
/* Bond data delete */
m_config.bond_data_delete = 1;
/* Reset status */
m_config.reset_status = reset_status_dflt;
/* Measurement parameters */
m_config.pd_adc_cnt = 8;
m_config.pd_delay_us = 8000;
}
static volatile uint8_t fds_last_evt = 0xFF;
static void fds_evt_handler( fds_evt_t const *p_evt )
{
fds_last_evt = p_evt->id;
switch( p_evt->id )
{
case FDS_EVT_INIT:
if( p_evt->result == NRF_SUCCESS )
{
m_fds_initialized = true;
}
break;
case FDS_EVT_WRITE:
{
fds_flag_write = false;
}
break;
case FDS_EVT_UPDATE:
{
fds_flag_write = false;
if(go_device_power_off == true) {
/* After flash writing completed, System Power Off */
device_power_off();
}
if(go_sleep_mode_enter == true) {
/* After flash writing completed, System go to Sleep Mode */
sleep_mode_enter();
}
if(go_NVIC_SystemReset == true) {
/* After flash writing completed, System Reset */
printf("Off FDS_ENVET\r\n");
NVIC_SystemReset(); //0112
}
}
break;
case FDS_EVT_DEL_RECORD:
break;
case FDS_EVT_DEL_FILE:
break;
case FDS_EVT_GC:
break;
default:
break;
}
}
/**@brief Wait for fds to initialize. */
static void wait_for_fds_ready( void )
{
uint32_t timeout = 0;
while( !m_fds_initialized )
{
nrf_pwr_mgmt_run();
nrf_delay_ms(1);
timeout++;
if (timeout > 3000) { /* 3 second timeout */
printf("[FDS] TIMEOUT!\r\n");
break;
}
}
}
void config_load( void )
{
ret_code_t rc;
fds_record_desc_t desc = { 0 };
fds_find_token_t tok = { 0 };
uint8_t cfg_retry = 0;
cfg_load_start:
memset((char *)&desc, 0, sizeof(desc));
memset((char *)&tok, 0, sizeof(tok));
rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
DBG_PRINTF("[FDS] find rc=%u\r\n", rc);
/* FDS may not be fully ready yet - retry before writing defaults */
if (rc != NRF_SUCCESS && cfg_retry < 10) {
cfg_retry++;
DBG_PRINTF("[FDS] retry %u/10\r\n", cfg_retry);
nrf_delay_ms(100);
goto cfg_load_start;
}
if( rc == NRF_SUCCESS )
{
/* A config file is in flash. Let's update it. */
fds_flash_record_t config = { 0 };
/* Open the record and read its contents. */
rc = fds_record_open(&desc, &config);
if (rc != NRF_SUCCESS) {
/* CRC error or corrupt record - delete and use defaults */
DBG_PRINTF("[FDS] open ERR=%u, deleting\r\n", rc);
(void)fds_record_delete(&desc);
fds_gc();
fds_default_value_set();
goto cfg_load_write_new;
}
/* Copy the configuration from flash into m_config. */
memcpy(&m_config, config.p_data, sizeof(config_data_t));
/* Close the record when done reading. */
rc = fds_record_close(&desc);
APP_ERROR_CHECK(rc);
DBG_PRINTF("[FDS] magic=0x%08X (expect 0x%08X)\r\n",
m_config.magic_number, CONFIG_MAGIC_NUMBER_VALUE);
if( m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE )
{ // first init
DBG_PRINTF("[FDS] FORMAT! overwriting with defaults\r\n");
rc = fds_record_delete(&desc);
APP_ERROR_CHECK(rc);
m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
// default....
fds_default_value_set();
/* Write the updated record to flash. */
rc = fds_record_update(&desc, &m_dummy_record);
if( (rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH) )
{
rc = fds_gc();
APP_ERROR_CHECK(rc);
}
else
{
APP_ERROR_CHECK(rc);
}
goto cfg_load_start;
}
DBG_PRINTF("[FDS] Loaded OK\r\n");
}
else
{
cfg_load_write_new:
DBG_PRINTF("[FDS] New - writing defaults\r\n");
/* System config not found (or corrupt); write a new one. */
m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
// default....
fds_default_value_set();
fds_flag_write = true;
rc = fds_record_write(&desc, &m_dummy_record);
if (rc != NRF_SUCCESS) {
DBG_PRINTF("[FDS] Write ERR=%u\r\n", rc);
fds_flag_write = false;
}
while( fds_flag_write )
{
nrf_pwr_mgmt_run();
}
if( (rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH) )
{
rc = fds_gc();
APP_ERROR_CHECK(rc);
}
else
{
APP_ERROR_CHECK(rc);
}
NRF_LOG_FLUSH();
goto cfg_load_start;
}
}
void config_save( void )
{
ret_code_t rc;
fds_record_desc_t desc = { 0 };
fds_find_token_t tok = { 0 };
DBG_PRINTF("[CFG_SAVE] start\r\n");
/* Skip if a previous FDS operation is still in progress (non-blocking) */
if (fds_flag_write) {
DBG_PRINTF("[CFG_SAVE] busy, skipped\r\n");
return;
}
if( m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE )
{
m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
}
memset((char *)&desc, 0, sizeof(desc));
memset((char *)&tok, 0, sizeof(tok));
rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
DBG_PRINTF("[CFG_SAVE] find rc=%u\r\n", rc);
/* Debug: show what we're about to save */
{
char tmp[13] = {0};
memcpy(tmp, m_config.hw_no, 12);
DBG_PRINTF("[CFG_SAVE] hw='%s'\r\n", tmp);
memcpy(tmp, m_config.serial_no, 12);
DBG_PRINTF("[CFG_SAVE] sn='%s'\r\n", tmp);
}
if( rc == NRF_SUCCESS )
{
fds_flag_write = true;
rc = fds_record_update(&desc, &m_dummy_record);
DBG_PRINTF("[CFG_SAVE] update rc=%u\r\n", rc);
if( rc == FDS_ERR_NO_SPACE_IN_FLASH )
{
fds_flag_write = false;
rc = fds_gc();
DBG_PRINTF("[CFG_SAVE] gc rc=%u, retry\r\n", rc);
fds_flag_write = true;
rc = fds_record_update(&desc, &m_dummy_record);
DBG_PRINTF("[CFG_SAVE] retry rc=%u\r\n", rc);
}
if( rc != NRF_SUCCESS )
{
DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
fds_flag_write = false;
}
}
else
{
DBG_PRINTF("[CFG_SAVE] not found, writing new\r\n");
fds_flag_write = true;
rc = fds_record_write(&desc, &m_dummy_record);
DBG_PRINTF("[CFG_SAVE] write rc=%u\r\n", rc);
if( rc != NRF_SUCCESS )
{
DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
fds_flag_write = false;
}
}
DBG_PRINTF("[CFG_SAVE] done\r\n");
}
void fs_set_value(void)
{
config_load();
}
void fs_storage_init(void)
{
ret_code_t rc;
/* Register first to receive an event when initialization is complete. */
rc = fds_register(fds_evt_handler);
APP_ERROR_CHECK(rc);
rc = fds_init();
APP_ERROR_CHECK(rc);
/* Wait for fds to initialize. */
wait_for_fds_ready();
fds_stat_t stat = { 0 };
rc = fds_stat(&stat);
APP_ERROR_CHECK(rc);
printf("[FDS] OK\r\n");
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* @file fstorage.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef IHP_FSTORAGE_H_
#define IHP_FSTORAGE_H_
#include "sdk_config.h"
#include "nordic_common.h"
#include <stdint.h>
#pragma pack(1)
typedef struct
{
uint32_t magic_number; /* 4B - 0x20231226 */
char hw_no[12]; /* 12B - HW Number (AES encrypted) */
char serial_no[12]; /* 12B - Serial Number (AES encrypted) */
uint8_t static_passkey[6]; /* 6B - BLE Passkey (AES encrypted) */
uint8_t bond_data_delete; /* 1B - Bond delete flag */
int8_t reset_status; /* 1B - Reset status */
uint8_t pd_adc_cnt; /* 1B - ADC sample count */
uint16_t pd_delay_us; /* 2B - PD delay (us) */
} config_data_t; /* Total: 41 bytes - FDS에 저장하는 디바이스 설정 */
extern config_data_t m_config;
void fds_default_value_set(void);
void config_load( void );
void config_save( void );
void fs_set_value(void);
void fs_storage_init(void);
#endif /* IHP_FSTORAGE_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,172 @@
/*******************************************************************************
* @file full_agc.h
* @brief Automatic Gain Control (AGC) header for NIRS measurement system
* @author Charles KWON <charleskwon@medithings.co.kr>
* @version V2.0.0
* @date 2025-12-31
*
* Copyright (c) 2025 Medithings Inc.
* All rights reserved.
*
* This header defines the interface for the AGC module which automatically
* calibrates photodetector gain for each LED-PD pair in the NIRS system.
******************************************************************************/
#ifndef _FULL_AGC_H__
#define _FULL_AGC_H__
/*============================================================================*/
/* Includes - Charles KWON */
/*============================================================================*/
#include "sdk_common.h"
/*============================================================================*/
/* Configuration Constants - Charles KWON */
/*============================================================================*/
/**
* @brief Number of photodetectors in the system
* - Charles KWON
*
* Used to determine iteration count for AGC scanning
*/
#define PD_NO 1
/*============================================================================*/
/* AGC Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Initialize and start AGC sequence for current phase
* - Charles KWON
*
* Configures LED/PD lists based on which phase is active (A or B).
* Must be called after setting full_agc_a_start or full_agc_b_start.
*/
void full_agc_start(void);
/**
* @brief Terminate AGC sequence and perform cleanup
* - Charles KWON
*
* Stops all AGC timers, uninitializes SAADC, restores HW I2C mode,
* turns off all LEDs/PDs, and clears the processing flag.
*/
void full_agc_end(void);
/*============================================================================*/
/* SAADC Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Initialize SAADC for AGC voltage measurement
* - Charles KWON
*
* Configures single-ended ADC on AIN6 (FSA5157P6X analog switch output)
* with double-buffering for continuous sampling during AGC.
*/
void full_agc_adc_init(void);
/**
* @brief Uninitialize SAADC after AGC completion
* - Charles KWON
*
* Releases SAADC resources for use by other modules.
*/
void full_agc_uninit(void);
/*============================================================================*/
/* AGC Loop Timer Functions - Charles KWON */
/*============================================================================*/
/**
* @brief AGC measurement loop timer callback
* - Charles KWON
*
* Periodically called to drive the AGC state machine.
* Each call advances the measurement sequence by one step.
*
* @param[in] p_context Unused timer context parameter
*/
void full_agc_loop(void * p_context);
/**
* @brief Start AGC measurement loop timer
* - Charles KWON
*
* Begins periodic timer that drives the AGC state machine.
*/
void full_agc_timer_start(void);
/**
* @brief Stop AGC measurement loop timer
* - Charles KWON
*/
void full_agc_timer_stop(void);
/**
* @brief Initialize AGC loop timer
* - Charles KWON
*
* Creates the repeating timer used for AGC measurement sequencing.
* Must be called during system initialization before starting AGC.
*/
void full_agc_timer_init(void);
/*============================================================================*/
/* AGC Entry Point - Charles KWON */
/*============================================================================*/
/**
* @brief Start full AGC measurement sequence
* - Charles KWON
*
* Main entry point for AGC calibration. This function:
* 1. Initializes AGC state variables
* 2. Stops battery monitoring timer
* 3. Configures Part A LED/PD lists
* 4. Initializes SAADC
* 5. Starts AGC loop timer
*
* AGC automatically transitions from Part A to Part B and
* stores results in EEPROM upon completion.
*/
void full_agc_mesurement_start(void);
/*============================================================================*/
/* AGC Data Transmission Functions - Charles KWON */
/*============================================================================*/
/**
* @brief AGC data transmission timer callback
* - Charles KWON
*
* Handles sequenced transmission of AGC calibration results over BLE.
* Data is sent in chunks to comply with BLE packet size limits.
*
* @param[in] p_context Unused timer context parameter
*/
void full_agc_send_loop(void * p_context);
/**
* @brief Start AGC data transmission timer
* - Charles KWON
*/
void full_agc_send_timer_start(void);
/**
* @brief Stop AGC data transmission timer
* - Charles KWON
*/
void full_agc_send_timer_stop(void);
/**
* @brief Initialize AGC send timer
* - Charles KWON
*
* Creates the single-shot timer used for sequenced BLE transmission.
* Must be called during system initialization.
*/
void full_agc_send_timer_init(void);
#endif /* _FULL_AGC_H__ */

View File

@@ -0,0 +1,31 @@
/*******************************************************************************
* @file hardfault_handler.c
* @brief HardFault Handler for debugging system crashes
******************************************************************************/
#include <stdint.h>
#include "debug_print.h"
void HardFault_Handler(void)
{
volatile uint32_t *stack;
__asm volatile ("mrs %0, msp" : "=r" (stack));
DBG_PRINTF("\r\n!!! HARDFAULT !!!\r\n");
DBG_PRINTF("R0 = %08lX\r\n", stack[0]);
DBG_PRINTF("R1 = %08lX\r\n", stack[1]);
DBG_PRINTF("R2 = %08lX\r\n", stack[2]);
DBG_PRINTF("R3 = %08lX\r\n", stack[3]);
DBG_PRINTF("R12 = %08lX\r\n", stack[4]);
DBG_PRINTF("LR = %08lX\r\n", stack[5]);
DBG_PRINTF("PC = %08lX\r\n", stack[6]);
DBG_PRINTF("PSR = %08lX\r\n", stack[7]);
while (1) {
/* Blink LED to indicate fault */
for (volatile uint32_t i = 0; i < 500000; i++);
NRF_P0->OUTSET = (1 << 12);
for (volatile uint32_t i = 0; i < 500000; i++);
NRF_P0->OUTCLR = (1 << 12);
}
}

View File

@@ -0,0 +1,94 @@
/*******************************************************************************
* @file i2c_manager.c
* @brief Reliable HW↔SW I2C Switching Logic (with Mode Set Logging)
******************************************************************************/
#include "i2c_manager.h"
#include "debug_print.h"
#include "nrf_delay.h"
#include "mcp4725_i2c.h"
#include "nrf_drv_twi.h"
#include "cat_interface.h"
bool HW_I2C_FRQ = true;
bool SW_I2C_FRQ = false;
/* 외부 EEPROM TWI 인스턴스 */
extern const nrf_drv_twi_t m_eeprom;
/* -------------------------------------------------------------------------- */
/* HW (TWI) 초기화 */
/* -------------------------------------------------------------------------- */
void hw_i2c_init_once(void)
{
// SW 모드일 경우 강제 해제 후 HW 전환
if (SW_I2C_FRQ)
{
//DBG_PRINTF("[I2C]SW→HW\r\n");
// SW 리소스 해제 (필요 시 추가)
SW_I2C_FRQ = false;
nrf_delay_ms(2);
}
// 이미 HW면 스킵
if (HW_I2C_FRQ)
{
// DBG_PRINTF("[I2C] HW I2C set\r\n");
return;
}
// 실제 HW 초기화
// eeprom_initialize(); /* vivaMayo: No EEPROM */
nrf_delay_ms(2);
HW_I2C_FRQ = true;
SW_I2C_FRQ = false;
// DBG_PRINTF("[I2C] HW I2C Mode set!\r\n");
}
/* -------------------------------------------------------------------------- */
/* SW (Port Bang-Bang) 초기화 */
/* -------------------------------------------------------------------------- */
void sw_i2c_init_once(void)
{
// HW 모드일 경우 강제 해제 후 SW 전환
if (HW_I2C_FRQ)
{
//DBG_PRINTF("[I2C]HW→SW\r\n");
nrf_drv_twi_disable(&m_eeprom);
nrf_drv_twi_uninit(&m_eeprom);
nrf_delay_ms(2);
HW_I2C_FRQ = false;
}
// 이미 SW 모드면 재실행 금지
if (SW_I2C_FRQ)
{
// DBG_PRINTF("[I2C] SWI2C already initialized\r\n");
return;
}
// 실제 SW 초기화
// eeprom_uninitialize(); // TWI 라인 해제 /* vivaMayo: No EEPROM */
nrf_delay_ms(1);
mcp4725_init(); // Port BangBang 방식 DAC init
SW_I2C_FRQ = true;
HW_I2C_FRQ = false;
// DBG_PRINTF("[I2C] SW I2C Mode set!\r\n");
}
/* -------------------------------------------------------------------------- */
/* 전체 리셋 */
/* -------------------------------------------------------------------------- */
void i2c_reset_state(void)
{
HW_I2C_FRQ = false;
SW_I2C_FRQ = false;
DBG_PRINTF("Flags reset\r\n");
}

View File

@@ -0,0 +1,18 @@
/*******************************************************************************
* @file i2c_manager.h
* @brief Common header for HW/SW I2C mutex control
******************************************************************************/
#ifndef __I2C_MANAGER_H__
#define __I2C_MANAGER_H__
#include <stdbool.h>
#include "app_error.h"
extern bool HW_I2C_FRQ;
extern bool SW_I2C_FRQ;
void hw_i2c_init_once(void);
void sw_i2c_init_once(void);
void i2c_reset_state(void);
#endif

View File

@@ -0,0 +1,103 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
#include "DataConverter.h"
uint8_t * inv_dc_int32_to_little8(int32_t x, uint8_t * little8)
{
little8[3] = (uint8_t)((x >> 24) & 0xff);
little8[2] = (uint8_t)((x >> 16) & 0xff);
little8[1] = (uint8_t)((x >> 8) & 0xff);
little8[0] = (uint8_t)(x & 0xff);
return little8;
}
uint8_t * inv_dc_int16_to_little8(int16_t x, uint8_t * little8)
{
little8[0] = (uint8_t)(x & 0xff);
little8[1] = (uint8_t)((x >> 8) & 0xff);
return little8;
}
uint8_t * inv_dc_int32_to_big8(int32_t x, uint8_t * big8)
{
big8[0] = (uint8_t)((x >> 24) & 0xff);
big8[1] = (uint8_t)((x >> 16) & 0xff);
big8[2] = (uint8_t)((x >> 8) & 0xff);
big8[3] = (uint8_t)(x & 0xff);
return big8;
}
int32_t inv_dc_little8_to_int32(const uint8_t * little8)
{
int32_t x = 0;
x |= ((int32_t)little8[3] << 24);
x |= ((int32_t)little8[2] << 16);
x |= ((int32_t)little8[1] << 8);
x |= ((int32_t)little8[0]);
return x;
}
int16_t inv_dc_big16_to_int16(uint8_t * data)
{
int16_t result;
result = (*data << 8);
data++;
result |= *data;
return result;
}
int16_t inv_dc_le_to_int16(const uint8_t * little8)
{
uint16_t x = 0;
x |= ((uint16_t)little8[0]);
x |= ((uint16_t)little8[1] << 8);
return (int16_t)x;
}
void inv_dc_sfix32_to_float(const int32_t * in, uint32_t len, uint8_t qx, float * out)
{
uint8_t i;
for(i = 0; i < len; ++i) {
out[i] = (float)in[i] / (1 << qx);
}
}
void inv_dc_float_to_sfix32(const float * in, uint32_t len, uint8_t qx, int32_t * out)
{
uint8_t i;
for(i = 0; i < len; ++i) {
out[i] = (int32_t)((in[i] * (1 << qx)) + ((in[i] >= 0) - 0.5f));
}
}

View File

@@ -0,0 +1,87 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup DataConverter Data Converter
* @brief Helper functions to convert integer
* @ingroup EmbUtils
* @{
*/
#ifndef _INV_DATA_CONVERTER_H_
#define _INV_DATA_CONVERTER_H_
#include "InvExport.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/** @brief Converts a 32-bit long to a little endian byte stream
*/
uint8_t INV_EXPORT * inv_dc_int32_to_little8(int32_t x, uint8_t * little8);
/** @brief Converts a 16-bit integer to a little endian byte stream
*/
uint8_t INV_EXPORT * inv_dc_int16_to_little8(int16_t x, uint8_t * little8);
/** @brief Converts a 32-bit long to a big endian byte stream
*/
uint8_t INV_EXPORT * inv_dc_int32_to_big8(int32_t x, uint8_t *big8);
/** @brief Converts a little endian byte stream into a 32-bit integer
*/
int32_t INV_EXPORT inv_dc_little8_to_int32(const uint8_t * little8);
/** @brief Converts a little endian byte stream into a 16-bit integer
*/
int16_t INV_EXPORT inv_dc_le_to_int16(const uint8_t * little8);
/** @brief Converts big endian on 16 bits into an unsigned short
*/
int16_t INV_EXPORT inv_dc_big16_to_int16(uint8_t * data);
/** @brief Converts an array of 32-bit signed fixed-point integers to an array of floats
* @param[in] in Pointer to the first element of the array of 32-bit signed fixed-point integers
* @param[in] len Length of the array
* @param[in] qx Number of bits used to represent the decimal part of the fixed-point integers
* @param[out] out Pointer to the memory area where the output will be stored
*/
void INV_EXPORT inv_dc_sfix32_to_float(const int32_t * in, uint32_t len, uint8_t qx, float * out);
/** @brief Converts an array of floats to an array of 32-bit signed fixed-point integers
* @param[in] in Pointer to the first element of the array of floats
* @param[in] len Length of the array
* @param[in] qx Number of bits used to represent the decimal part of the fixed-point integers
* @param[out] out Pointer to the memory area where the output will be stored
*/
void INV_EXPORT inv_dc_float_to_sfix32(const float * in, uint32_t len, uint8_t qx, int32_t * out);
#ifdef __cplusplus
}
#endif
#endif /* _INV_DATA_CONVERTER_H_ */
/** @} */

View File

@@ -0,0 +1,47 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
#include "ErrorHelper.h"
const char * inv_error_str(int error)
{
switch(error) {
case INV_ERROR_SUCCESS: return "Success";
case INV_ERROR: return "Unspecified error";
case INV_ERROR_NIMPL: return "Not implemented";
case INV_ERROR_TRANSPORT: return "Transport error";
case INV_ERROR_TIMEOUT: return "Timeout, action did not complete in time";
case INV_ERROR_SIZE: return "Wrong size error";
case INV_ERROR_OS: return "Operating system failure";
case INV_ERROR_IO: return "Input/Output error";
case INV_ERROR_MEM: return "Bad allocation";
case INV_ERROR_HW: return "Hardware error";
case INV_ERROR_BAD_ARG: return "Invalid arguments";
case INV_ERROR_UNEXPECTED: return "Unexpected error";
case INV_ERROR_FILE: return "Invalid file format";
case INV_ERROR_PATH: return "Invalid file path";
case INV_ERROR_IMAGE_TYPE: return "Unknown image type";
case INV_ERROR_WATCHDOG: return "Watchdog error";
default: return "Unknown error";
}
}

View File

@@ -0,0 +1,51 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup ErrorHelper Error Helper
* @brief Helper functions related to error code
* @ingroup EmbUtils
* @{
*/
#ifndef _INV_ERROR_HELPER_H_
#define _INV_ERROR_HELPER_H_
#include "InvExport.h"
#include "InvError.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Returns string describing error number
* @sa enum inv_error
*/
const char INV_EXPORT * inv_error_str(int error);
#ifdef __cplusplus
}
#endif
#endif /* _INV_ERROR_HELPER_H_ */
/** @} */

View File

@@ -0,0 +1,82 @@
/*
Copyright (c) 2014-2015 InvenSense Inc. Portions Copyright (c) 2014-2015 Movea. All rights reserved.
This software, related documentation and any modifications thereto (collectively "Software") is subject
to InvenSense and its licensors' intellectual property rights under U.S. and international copyright and
other intellectual property rights laws.
InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
and any use, reproduction, disclosure or distribution of the Software without an express license
agreement from InvenSense is strictly prohibited.
*/
#include "InvBasicMath.h"
#include <limits.h>
unsigned int InvBasicMath_log2u(unsigned int val)
{
unsigned int ret = UINT_MAX;
while (val != 0) {
val >>= 1;
ret++;
}
return ret;
}
int InvBasicMath_isAnOrthonormalMatrix(const float matrix[9])
{
// Check if matrix is orthogonal
// Matrix is orthogonal if transpose(Matrix) x Matrix = Identity
float transpose[9];
float mult[9];
int i, j;
// Compute Transpose(matrix)
for (i = 0; i < 3; i++) {
for(j = 0; j < 3; j++) {
transpose[i*3+j] = matrix[i+j*3];
}
}
// Multiply transpose x matrix
mult[0] = transpose[0]*matrix[0] + transpose[1]*matrix[3] + transpose[2]*matrix[6];
mult[1] = transpose[0]*matrix[1] + transpose[1]*matrix[4] + transpose[2]*matrix[7];
mult[2] = transpose[0]*matrix[2] + transpose[1]*matrix[5] + transpose[2]*matrix[8];
mult[3] = transpose[3]*matrix[0] + transpose[4]*matrix[3] + transpose[5]*matrix[6];
mult[4] = transpose[3]*matrix[1] + transpose[4]*matrix[4] + transpose[5]*matrix[7];
mult[5] = transpose[3]*matrix[2] + transpose[4]*matrix[5] + transpose[5]*matrix[8];
mult[6] = transpose[6]*matrix[0] + transpose[7]*matrix[3] + transpose[8]*matrix[6];
mult[7] = transpose[6]*matrix[1] + transpose[7]*matrix[4] + transpose[8]*matrix[7];
mult[8] = transpose[6]*matrix[2] + transpose[7]*matrix[5] + transpose[8]*matrix[8];
// Check that mult is identity
for (i = 0; i < 3; i++) {
for(j = 0; j < 3; j++) {
if (i == j) {
if (mult[i+j*3] != 1)
return 0;
}
else {
if (mult[i+j*3] != 0)
return 0;
}
}
}
return 1;
}
float InvBasicMath_computeMatrixDeterminant(const float matrix[9])
{
return matrix[0] * (matrix[4]*matrix[8] - matrix[7]*matrix[5])
-matrix[1] * (matrix[3]*matrix[8] - matrix[6]*matrix[5])
+matrix[2] * (matrix[3]*matrix[7] - matrix[4]*matrix[6]);
}

View File

@@ -0,0 +1,94 @@
/*
Copyright (c) 2014-2015 InvenSense Inc. Portions Copyright (c) 2014-2015 Movea. All rights reserved.
This software, related documentation and any modifications thereto (collectively "Software") is subject
to InvenSense and its licensors' intellectual property rights under U.S. and international copyright and
other intellectual property rights laws.
InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
and any use, reproduction, disclosure or distribution of the Software without an express license
agreement from InvenSense is strictly prohibited.
*/
/** @defgroup InvBasicMath InvBasicMath
@brief This file contains basic (overloadable) math functions and macros
@ingroup EmbUtils
@{
*/
#ifndef _INV_BASIC_MATH_H_
#define _INV_BASIC_MATH_H_
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Return absolute value of argument
*/
#ifndef INV_ABS
# define INV_ABS(a) ((a) < 0 ? -(a) : (a))
#endif
/** @brief Return minimum of two arguments
*/
#ifndef INV_MIN
# define INV_MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/** @brief Return maximum of two arguments
*/
#ifndef INV_MAX
# define INV_MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
/** @brief Define value for pi
*/
#ifndef INV_PI
# define INV_PI 3.14159265358979
#endif
#ifndef M_PI
# define M_PI INV_PI
#endif
/** @brief Return saturated integer
*/
#ifndef INV_SATURATE
static inline long InvBasicMath_saturatel(long in, long min, long max)
{
if (in > max)
return max;
else if (in < min)
return min;
else
return in;
}
# define INV_SATURATE(a, min, max) InvBasicMath_saturatel(a, min, max)
#endif
/** @brief Compute log2 from integer
*/
#ifndef INV_LOG2
unsigned int InvBasicMath_log2u(unsigned int val);
# define INV_LOG2(a) InvBasicMath_log2u(a)
#endif
/** @brief Check if matrix is orthonormal
* @param [in] matrix 3x3 Matrix to be checked
* @return 1 if it is an orthonormal matrix, 0 otherwise
*/
int InvBasicMath_isAnOrthonormalMatrix(const float matrix[9]);
/** @brief Compute the determinant of the matrix
* @param [in] matrix 3x3 Matrix to be checked
* @return the determinant value
*/
float InvBasicMath_computeMatrixDeterminant(const float matrix[9]);
#ifdef __cplusplus
}
#endif
#endif /* _INV_BASIC_MATH_H_ */
/** @} */

View File

@@ -0,0 +1,389 @@
/*
Copyright (c) 2014-2015 InvenSense Inc. Portions Copyright (c) 2014-2015 Movea. All rights reserved.
This software, related documentation and any modifications thereto (collectively "Software") is subject
to InvenSense and its licensors' intellectual property rights under U.S. and international copyright and
other intellectual property rights laws.
InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
and any use, reproduction, disclosure or distribution of the Software without an express license
agreement from InvenSense is strictly prohibited.
*/
/** \defgroup RingBuffer RingBuffer
\brief Macros to manage static circular buffer of any data type
\ingroup EmbUtils
\{
*/
#ifndef _RING_BUFFER_H_
#define _RING_BUFFER_H_
#include <stdint.h>
#include <string.h>
/** \brief Macro to declare a ring buffer
\param[in] type type of item contained in the ring buffer
\param[in] size number of items that can contain the ring buffer
To improve speed, size should be a power of 2
*/
#define RINGBUFFER_DECLARE(type, size) \
struct { \
uint16_t read, write; \
type buffer[size]; \
}
/** \brief Macro to declare a volatile ring buffer, i.e. modified within an interrupt context
\param[in] type type of item contained in the ring buffer
\param[in] size number of items that can contain the ring buffer
To improve speed, size should be a power of 2
*/
#define RINGBUFFER_VOLATILE_DECLARE(type, size) \
struct { \
volatile uint16_t read, write; \
volatile type buffer[size]; \
}
/** \brief Macro to declare a ring buffer
\param[in] name name of the circular buffer
\param[in] size number of items that can contain the ring buffer
To improve speed, size should be a power of 2
\param[in] type type of item contained in the ring buffer
*/
#define RINGBUFFER(name, size, type) RINGBUFFER_DECLARE(type, size) name
/** \brief Macro to declare a volatile ring buffer, i.e. modified within an interrupt context
\param[in] name name of the circular buffer
\param[in] size number of items that can contain the ring buffer
To improve speed, size should be a power of 2
\param[in] type type of item contained in the ring buffer
*/
#define RINGBUFFER_VOLATILE(name, size, type) RINGBUFFER_VOLATILE_DECLARE(type, size) name
/** \brief Macro to get maximum size of a ring buffer
\param[in] rb pointer to the ring buffer
\return maximum number of items that can contain the ringbuffer
*/
#define RINGBUFFER_MAXSIZE(rb) (sizeof((rb)->buffer)/sizeof((rb)->buffer[0]))
/** \brief Macro to get maximum size of a volatile ring buffer, i.e. modified within an interrupt context
\param[in] rb pointer to the ring buffer
\return maximum number of items that can contain the ringbuffer
*/
#define RINGBUFFER_VOLATILE_MAXSIZE(rb) RINGBUFFER_MAXSIZE(rb)
/** \brief Macro to get current size of a ring buffer
\param[in] rb pointer to the ring buffer
\return current number of items hold in the ringbuffer
*/
#define RINGBUFFER_SIZE(rb) ((uint16_t)((rb)->write - (rb)->read))
static inline uint16_t get_ringbuffer_volatile_size(void * rb)
{
struct { uint16_t read, write; } rb_var;
memcpy(&rb_var, rb, sizeof(rb_var));
return (rb_var.write - rb_var.read);
}
/** \brief Macro to get current size of a volatile ring buffer, i.e. modified within an interrupt context
\param[in] rb pointer to the ring buffer
\return current number of items hold in the ringbuffer
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_SIZE(rb) get_ringbuffer_volatile_size(rb)
/** \brief Macro to check if a ring buffer is full
\param[in] rb pointer to the ring buffer
\return 1 if there is no slot left in the ring buffer, 0 otherwise
*/
#define RINGBUFFER_FULL(rb) (RINGBUFFER_SIZE(rb) == RINGBUFFER_MAXSIZE(rb))
/** \brief Macro to check if a volatile ring buffer, i.e. modified within an interrupt context, is full
\param[in] rb pointer to the ring buffer
\return 1 if there is no slot left in the ring buffer, 0 otherwise
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_FULL(rb) (RINGBUFFER_VOLATILE_SIZE(rb) == RINGBUFFER_VOLATILE_MAXSIZE(rb))
/** \brief Macro to check if a ring buffer is empty
\param[in] rb pointer to the ring buffer
\return 1 if there is no item in the ring buffer, 0 otherwise
*/
#define RINGBUFFER_EMPTY(rb) (RINGBUFFER_SIZE(rb) == 0)
/** \brief Macro to check if a volatile ring buffer, i.e. modified within an interrupt context, is empty
\param[in] rb pointer to the ring buffer
\return 1 if there is no item in the ring buffer, 0 otherwise
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_EMPTY(rb) (RINGBUFFER_VOLATILE_SIZE(rb) == 0)
/** \brief Macro to get number of available slot in a ring buffer
\param[in] rb pointer to the ring buffer
\return number of empty slot in the ring buffer
*/
#define RINGBUFFER_AVAILABLE(rb) (RINGBUFFER_MAXSIZE(rb) - RINGBUFFER_SIZE(rb))
/** \brief Macro to get number of available slot in a volatile ring buffer, i.e. modified within an interrupt context
\param[in] rb pointer to the ring buffer
\return number of empty slot in the ring buffer
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_AVAILABLE(rb) (RINGBUFFER_VOLATILE_MAXSIZE(rb) - RINGBUFFER_VOLATILE_SIZE(rb))
/** \brief Macro to clear a ring buffer
\param[in] rb pointer to the ring buffer
*/
#define RINGBUFFER_CLEAR(rb) \
do { \
(rb)->read = 0, (rb)->write = 0; \
} while(0)
/** \brief Macro to clear a volatile ring buffer, i.e. modified within an interrupt context
\param[in] rb pointer to the ring buffer
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_CLEAR(rb) RINGBUFFER_CLEAR(rb)
/** \brief Push item by reference
\param[in] rb pointer to the ring buffer
\param[out] refData to available item slot
\warning There is no error checking done.
*/
#define RINGBUFFER_PUSHREF(rb, refData) \
do { \
refData = &(rb)->buffer[(rb)->write % RINGBUFFER_MAXSIZE(rb)]; \
++(rb)->write; \
} while(0)
/** \brief Push item by reference
\param[in] rb pointer to the ring buffer
\param[out] refData to available item slot
\warning There is no error checking done.
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_PUSHREF(rb, refData) \
do { \
uint16_t wr_ptr = (rb)->write; \
refData = &(rb)->buffer[wr_ptr % RINGBUFFER_VOLATILE_MAXSIZE(rb)]; \
++(rb)->write; \
} while(0)
/** \brief Return reference to next available slot
No push is performed
\param[in] rb pointer to the ring buffer
\param[out] refData to available item slot
\warning There is no error checking done.
*/
#define RINGBUFFER_GETREFNEXT(rb, refData) \
do { \
refData = &(rb)->buffer[(rb)->write % RINGBUFFER_MAXSIZE(rb)]; \
} while(0)
/** \brief Return reference to next available slot
No push is performed
\param[in] rb pointer to the ring buffer
\param[out] refData to available item slot
\warning There is no error checking done.
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_GETREFNEXT(rb, refData) \
do { \
uint16_t wr_ptr = (rb)->write; \
refData = &(rb)->buffer[wr_ptr % RINGBUFFER_VOLATILE_MAXSIZE(rb)]; \
} while(0)
/** \brief Increment write counter
Actually performed a push (assuming data were already copied)
\param[in] rb pointer to the ring buffer
\warning There is no error checking done.
*/
#define RINGBUFFER_INCREMENT(rb, refData) \
do { \
++(rb)->write; \
} while(0)
/** \brief Increment write counter
Actually performed a push (assuming data were already copied)
\param[in] rb pointer to the ring buffer
\warning There is no error checking done.
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_INCREMENT(rb, refData) \
do { \
++(rb)->write; \
} while(0)
/** \brief Return reference to youngest item
\param[in] rb pointer to the ring buffer
\param[out] refData reference to youngest item
\warning There is no error checking done.
*/
#define RINGBUFFER_BACK(rb, refData) \
do { \
refData = &(rb)->buffer[((rb)->write-1) % RINGBUFFER_MAXSIZE(rb)]; \
} while(0)
/** \brief Return reference to youngest item
\param[in] rb pointer to the ring buffer
\param[out] refData reference to youngest item
\warning There is no error checking done.
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_BACK(rb, refData) \
do { \
uint16_t wr_ptr = (rb)->write; \
refData = &(rb)->buffer[(wr_ptr-1) % RINGBUFFER_VOLATILE_MAXSIZE(rb)]; \
} while(0)
/** \brief Macro to push an item to a ring buffer
\param[in] rb pointer to the ring buffer
\param[in] ptrData pointer to the item to push.
\warning There is no error checking done.
You must check for fullness before pushing data
*/
#define RINGBUFFER_PUSH(rb, ptrData) \
do { \
(rb)->buffer[(rb)->write % RINGBUFFER_MAXSIZE(rb)] = *ptrData; \
++(rb)->write; \
} while(0)
/** \brief Macro to push an item to a volatile ring buffer
\param[in] rb pointer to the ring buffer
\param[in] ptrData pointer to the item to push.
\warning There is no error checking done.
You must check for fullness before pushing data
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_PUSH(rb, ptrData) \
do { \
uint16_t wr_ptr = (rb)->write; \
(rb)->buffer[wr_ptr % RINGBUFFER_VOLATILE_MAXSIZE(rb)] = *ptrData; \
++(rb)->write; \
} while(0)
/** \brief Macro to unpush an item to a ring buffer
\param[in] rb pointer to the ring buffer
\param[in] ptrData pointer to placeholder to hold unpushed item
\warning There is no error checking done.
You must check for emptiness before pushing data
*/
#define RINGBUFFER_UNPUSH(rb, ptrData) \
do { \
--(rb)->write; \
*ptrData = (rb)->buffer[(rb)->write % RINGBUFFER_MAXSIZE(rb)]; \
} while(0)
/** \brief Macro to unpush an item to a volatile ring buffer
\param[in] rb pointer to the ring buffer
\param[in] ptrData pointer to placeholder to hold unpushed item
\warning There is no error checking done.
You must check for emptiness before pushing data
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_UNPUSH(rb, ptrData) \
do { \
--(rb)->write; \
uint16_t wr_ptr = (rb)->write; \
*ptrData = (rb)->buffer[wr_ptr % RINGBUFFER_VOLATILE_MAXSIZE(rb)]; \
} while(0)
/** \brief Return reference to oldest item
\param[in] rb pointer to the ring buffer
\param[out] refData reference to oldest item
\warning There is no error checking done.
*/
#define RINGBUFFER_FRONT(rb, refData) \
do { \
refData = &(rb)->buffer[(rb)->read % RINGBUFFER_MAXSIZE(rb)]; \
} while(0)
/** \brief Return reference to oldest item
\param[in] rb pointer to the ring buffer
\param[out] refData reference to oldest item
\warning There is no error checking done.
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_FRONT(rb, refData) \
do { \
uint16_t rd_ptr = (rb)->read; \
refData = &(rb)->buffer[rd_ptr % RINGBUFFER_VOLATILE_MAXSIZE(rb)]; \
} while(0)
/** \brief Macro to pop an item from a ring buffer
\param[in] rb pointer to the ring buffer
\param[out] ptrData pointer to placeholder to hold popped item
\warning There is no error checking done.
You must check for emptiness before popping data
*/
#define RINGBUFFER_POP(rb, ptrData) \
do { \
*ptrData = (rb)->buffer[(rb)->read % RINGBUFFER_MAXSIZE(rb)]; \
++(rb)->read; \
} while(0)
/** \brief Macro to pop an item from a volatile ring buffer
\param[in] rb pointer to the ring buffer
\param[out] ptrData pointer to placeholder to hold popped item
\warning There is no error checking done.
You must check for emptiness before popping data
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_POP(rb, ptrData) \
do { \
uint16_t rd_ptr = (rb)->read; \
*ptrData = (rb)->buffer[rd_ptr % RINGBUFFER_VOLATILE_MAXSIZE(rb)]; \
++(rb)->read; \
} while(0)
/** \brief Macro to pop an item from a ring buffer (data is not copied)
\param[in] rb pointer to the ring buffer
\warning There is no error checking done.
You must check for emptiness before popping data
*/
#define RINGBUFFER_POPNLOSE(rb) \
do { \
++(rb)->read; \
} while(0)
/** \brief Macro to pop an item from a volatile ring buffer (data is not copied)
\param[in] rb pointer to the ring buffer
\warning There is no error checking done.
You must check for emptiness before popping data
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_POPNLOSE(rb) \
do { \
++(rb)->read; \
} while(0)
/** \brief Macro to unpop an item to a ring buffer
\param[in] rb pointer to the ring buffer
\param[out] ptrData pointer to to the item to unpop.
\warning There is no error checking done.
You must check for fullness before unpopping data
*/
#define RINGBUFFER_UNPOP(rb, ptrData) \
do { \
--(rb)->read; \
(rb)->buffer[(rb)->read % RINGBUFFER_MAXSIZE(rb)] = *ptrData; \
} while(0)
/** \brief Macro to unpop an item to a volatile ring buffer
\param[in] rb pointer to the ring buffer
\param[out] ptrData pointer to to the item to unpop.
\warning There is no error checking done.
You must check for fullness before unpopping data
\warning it is advised to put this in a critical section
*/
#define RINGBUFFER_VOLATILE_UNPOP(rb, ptrData) \
do { \
--(rb)->read; \
uint16_t rd_ptr = (rb)->read; \
(rb)->buffer[rd_ptr % RINGBUFFER_VOLATILE_MAXSIZE(rb)] = *ptrData; \
} while(0)
#endif
/** \} */

View File

@@ -0,0 +1,47 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @brief Custom definition for boolean type to avoid compiler discrepancies
* @{
*/
#ifndef _INV_BOOL_H_
#define _INV_BOOL_H_
typedef int inv_bool_t;
#ifndef __cplusplus
#ifndef true
#define true 1
#endif
#ifndef false
#define false 0
#endif
#endif /* __cplusplus */
#endif /* _INV_BOOL_H_ */
/** @} */

View File

@@ -0,0 +1,64 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup InvError Error code
* @brief Common error code
*
* @ingroup EmbUtils
* @{
*/
#ifndef _INV_ERROR_H_
#define _INV_ERROR_H_
/** @brief Common error code definition
*/
enum inv_error
{
INV_ERROR_SUCCESS = 0, /**< no error */
INV_ERROR = -1, /**< unspecified error */
INV_ERROR_NIMPL = -2, /**< function not implemented for given
arguments */
INV_ERROR_TRANSPORT = -3, /**< error occurred at transport level */
INV_ERROR_TIMEOUT = -4, /**< action did not complete in the expected
time window */
INV_ERROR_SIZE = -5, /**< size/length of given arguments is not
suitable to complete requested action */
INV_ERROR_OS = -6, /**< error related to OS */
INV_ERROR_IO = -7, /**< error related to IO operation */
INV_ERROR_MEM = -9, /**< not enough memory to complete requested
action */
INV_ERROR_HW = -10, /**< error at HW level */
INV_ERROR_BAD_ARG = -11, /**< provided arguments are not good to
perform requested action */
INV_ERROR_UNEXPECTED = -12, /**< something unexpected happened */
INV_ERROR_FILE = -13, /**< cannot access file or unexpected format */
INV_ERROR_PATH = -14, /**< invalid file path */
INV_ERROR_IMAGE_TYPE = -15, /**< error when image type is not managed */
INV_ERROR_WATCHDOG = -16, /**< error when device doesn't respond
to ping */
};
#endif /* _INV_ERROR_H_ */
/** @} */

View File

@@ -0,0 +1,39 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
#ifndef _INV_IDD_EXPORT_H_
#define _INV_IDD_EXPORT_H_
#if defined(_WIN32)
#if !defined(INV_EXPORT) && defined(INV_DO_DLL_EXPORT)
#define INV_EXPORT __declspec(dllexport)
#elif !defined(INV_EXPORT) && defined(INV_DO_DLL_IMPORT)
#define INV_EXPORT __declspec(dllimport)
#endif
#endif
#if !defined(INV_EXPORT)
#define INV_EXPORT
#endif
#endif /* _INV_IDD_EXPORT_H_ */

View File

@@ -0,0 +1,370 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2017 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
#include "inv_imu_defs.h"
#include "inv_imu_extfunc.h"
#include "inv_imu_driver.h"
#include "inv_imu_apex.h"
int inv_imu_apex_enable_ff(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
status |= inv_imu_start_dmp(s);
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_FF_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_FF_ENABLE_EN;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_disable_ff(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_FF_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_FF_ENABLE_DIS;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_enable_smd(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
status |= inv_imu_start_dmp(s);
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_SMD_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_SMD_ENABLE_EN;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_disable_smd(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_SMD_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_SMD_ENABLE_DIS;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_init_parameters_struct(struct inv_imu_device *s, inv_imu_apex_parameters_t *apex_inputs)
{
int status = 0;
(void)s;
/* Default parameters at POR */
apex_inputs->pedo_amp_th = APEX_CONFIG3_PEDO_AMP_TH_62_MG;
apex_inputs->pedo_step_cnt_th = 0x5;
apex_inputs->pedo_step_det_th = 0x2;
apex_inputs->pedo_sb_timer_th = APEX_CONFIG4_PEDO_SB_TIMER_TH_150_SAMPLES;
apex_inputs->pedo_hi_enrgy_th = APEX_CONFIG4_PEDO_HI_ENRGY_TH_104_MG;
apex_inputs->tilt_wait_time = APEX_CONFIG5_TILT_WAIT_TIME_4_S;
apex_inputs->power_save_time = APEX_CONFIG2_DMP_POWER_SAVE_TIME_SEL_8_S;
apex_inputs->power_save = APEX_CONFIG0_DMP_POWER_SAVE_EN;
apex_inputs->sensitivity_mode = APEX_CONFIG9_SENSITIVITY_MODE_NORMAL;
apex_inputs->low_energy_amp_th = APEX_CONFIG2_LOW_ENERGY_AMP_TH_SEL_80_MG;
apex_inputs->smd_sensitivity = APEX_CONFIG9_SMD_SENSITIVITY_0;
apex_inputs->ff_debounce_duration = APEX_CONFIG9_FF_DEBOUNCE_DURATION_2000_MS;
apex_inputs->ff_max_duration_cm = APEX_CONFIG12_FF_MAX_DURATION_204_CM;
apex_inputs->ff_min_duration_cm = APEX_CONFIG12_FF_MIN_DURATION_10_CM;
apex_inputs->lowg_peak_th = APEX_CONFIG10_LOWG_PEAK_TH_563_MG;
apex_inputs->lowg_peak_hyst = APEX_CONFIG5_LOWG_PEAK_TH_HYST_156_MG;
apex_inputs->lowg_samples_th = APEX_CONFIG10_LOWG_TIME_TH_1_SAMPLE;
apex_inputs->highg_peak_th = APEX_CONFIG11_HIGHG_PEAK_TH_2500_MG;
apex_inputs->highg_peak_hyst = APEX_CONFIG5_HIGHG_PEAK_TH_HYST_156_MG;
apex_inputs->highg_samples_th = APEX_CONFIG11_HIGHG_TIME_TH_1_SAMPLE;
return status;
}
int inv_imu_apex_configure_parameters(struct inv_imu_device *s, const inv_imu_apex_parameters_t *apex_inputs)
{
int status = 0;
uint8_t data;
uint8_t apexConfig[7];
APEX_CONFIG1_PED_ENABLE_t pedo_state;
APEX_CONFIG1_TILT_ENABLE_t tilt_state;
APEX_CONFIG1_FF_ENABLE_t ff_state;
APEX_CONFIG1_SMD_ENABLE_t smd_state;
/* DMP cannot be configured if it is running, hence make sure all APEX algorithms are off */
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &data);
pedo_state = (APEX_CONFIG1_PED_ENABLE_t)(data & APEX_CONFIG1_PED_ENABLE_MASK);
tilt_state = (APEX_CONFIG1_TILT_ENABLE_t)(data & APEX_CONFIG1_TILT_ENABLE_MASK);
ff_state = (APEX_CONFIG1_FF_ENABLE_t)(data & APEX_CONFIG1_FF_ENABLE_MASK);
smd_state = (APEX_CONFIG1_SMD_ENABLE_t)(data & APEX_CONFIG1_SMD_ENABLE_MASK);
if (pedo_state == APEX_CONFIG1_PED_ENABLE_EN)
return INV_ERROR;
if (tilt_state == APEX_CONFIG1_TILT_ENABLE_EN)
return INV_ERROR;
if (ff_state == APEX_CONFIG1_FF_ENABLE_EN)
return INV_ERROR;
if (smd_state == APEX_CONFIG1_SMD_ENABLE_EN)
return INV_ERROR;
status |= inv_imu_switch_on_mclk(s);
/* Power Save mode and low energy amplitude threshold (for Pedometer in Slow Walk mode) */
/* APEX_CONFIG2_MREG1 */
apexConfig[0] = (uint8_t)apex_inputs->power_save_time
| (uint8_t)apex_inputs->low_energy_amp_th;
/* Pedometer parameters */
/* APEX_CONFIG3_MREG1 */
apexConfig[1] = (uint8_t)apex_inputs->pedo_amp_th
| (apex_inputs->pedo_step_cnt_th & APEX_CONFIG3_PED_STEP_CNT_TH_SEL_MASK);
/* APEX_CONFIG4_MREG1 */
apexConfig[2] = ((apex_inputs->pedo_step_det_th << APEX_CONFIG4_PED_STEP_DET_TH_SEL_POS)
& APEX_CONFIG4_PED_STEP_DET_TH_SEL_MASK)
| (uint8_t)apex_inputs->pedo_sb_timer_th
| (uint8_t)apex_inputs->pedo_hi_enrgy_th;
/* Tilt, Lowg and highg parameters */
/* APEX_CONFIG5_MREG1 */
apexConfig[3] = (uint8_t)apex_inputs->tilt_wait_time
| (uint8_t)apex_inputs->lowg_peak_hyst
| (uint8_t)apex_inputs->highg_peak_hyst;
status |= inv_imu_write_reg(s, APEX_CONFIG2_MREG1, 4, &apexConfig[0]);
/* APEX_CONFIG0 */
status |= inv_imu_read_reg(s, APEX_CONFIG0, 1, &apexConfig[0]);
apexConfig[0] &= ~APEX_CONFIG0_DMP_POWER_SAVE_EN_MASK;
apexConfig[0] |= apex_inputs->power_save;
status |= inv_imu_write_reg(s, APEX_CONFIG0, 1, &apexConfig[0]);
/* free fall parameter, SMD parameter and parameters for Pedometer in Slow Walk mode */
/* APEX_CONFIG9_MREG1 */
apexConfig[0] = (uint8_t)apex_inputs->ff_debounce_duration
| (uint8_t)apex_inputs->smd_sensitivity
| (uint8_t)apex_inputs->sensitivity_mode;
/* Lowg and highg parameters and free fall parameters */
/* APEX_CONFIG10_MREG1 */
apexConfig[1] = (uint8_t)apex_inputs->lowg_peak_th
| (uint8_t)apex_inputs->lowg_samples_th;
/* APEX_CONFIG11_MREG1 */
apexConfig[2] = (uint8_t)apex_inputs->highg_peak_th
| (uint8_t)apex_inputs->highg_samples_th;
status |= inv_imu_write_reg(s, APEX_CONFIG9_MREG1, 3, &apexConfig[0]);
/* APEX_CONFIG12_MREG1 */
apexConfig[0] = (uint8_t)apex_inputs->ff_max_duration_cm
| (uint8_t)apex_inputs->ff_min_duration_cm;
status |= inv_imu_write_reg(s, APEX_CONFIG12_MREG1, 1, &apexConfig[0]);
status |= inv_imu_switch_off_mclk(s);
return status;
}
int inv_imu_apex_get_parameters(struct inv_imu_device *s, inv_imu_apex_parameters_t *apex_params)
{
int status = 0;
uint8_t data[7];
uint8_t value;
status |= inv_imu_read_reg(s, APEX_CONFIG0, 1, &value);
apex_params->power_save = (APEX_CONFIG0_DMP_POWER_SAVE_t)(value & APEX_CONFIG0_DMP_POWER_SAVE_EN_MASK);
/* Access continuous config registers (CONFIG2-CONFIG11) */
status |= inv_imu_read_reg(s, APEX_CONFIG2_MREG1, sizeof(data), &data[0]);
/* Get params from apex_config2 : dmp_power_save_time and low_energy_amp_th */
apex_params->power_save_time = (APEX_CONFIG2_DMP_POWER_SAVE_TIME_t)
(data[0] & APEX_CONFIG2_DMP_POWER_SAVE_TIME_SEL_MASK);
apex_params->low_energy_amp_th = (APEX_CONFIG2_LOW_ENERGY_AMP_TH_t)
(data[0] & APEX_CONFIG2_LOW_ENERGY_AMP_TH_SEL_MASK);
/* Get params from apex_config3 : pedo_amp_th and pedo_step_cnt_th */
apex_params->pedo_amp_th = (APEX_CONFIG3_PEDO_AMP_TH_t)
(data[1] & APEX_CONFIG3_PED_AMP_TH_SEL_MASK);
apex_params->pedo_step_cnt_th = (data[1] & APEX_CONFIG3_PED_STEP_CNT_TH_SEL_MASK)
>> APEX_CONFIG3_PED_STEP_CNT_TH_SEL_POS;
/* Get params from apex_config4 : pedo_step_det_th, pedo_sb_timer_th and pedo_hi_enrgy_th */
apex_params->pedo_step_det_th = (data[2] & APEX_CONFIG4_PED_STEP_DET_TH_SEL_MASK)
>> APEX_CONFIG4_PED_STEP_DET_TH_SEL_POS;
apex_params->pedo_sb_timer_th = (APEX_CONFIG4_PEDO_SB_TIMER_TH_t)
(data[2] & APEX_CONFIG4_PED_SB_TIMER_TH_SEL_MASK);
apex_params->pedo_hi_enrgy_th = (APEX_CONFIG4_PEDO_HI_ENRGY_TH_t)
(data[2] & APEX_CONFIG4_PED_HI_EN_TH_SEL_MASK);
/* Get params from apex_config5 : tilt_wait_time, lowg_peak_hyst and highg_peak_hyst */
apex_params->tilt_wait_time = (APEX_CONFIG5_TILT_WAIT_TIME_t)
(data[3] & APEX_CONFIG5_TILT_WAIT_TIME_SEL_MASK);
apex_params->lowg_peak_hyst = (APEX_CONFIG5_LOWG_PEAK_TH_HYST_t)
(data[3] & APEX_CONFIG5_LOWG_PEAK_TH_HYST_SEL_MASK);
apex_params->highg_peak_hyst = (APEX_CONFIG5_HIGHG_PEAK_TH_HYST_t)
(data[3] & APEX_CONFIG5_HIGHG_PEAK_TH_HYST_SEL_MASK);
/* Get params from apex_config9 : ff_debounce_duration, smd_sensitivity and sensitivity_mode */
apex_params->ff_debounce_duration = (APEX_CONFIG9_FF_DEBOUNCE_DURATION_t)
(data[4] & APEX_CONFIG9_FF_DEBOUNCE_DURATION_SEL_MASK);
apex_params->smd_sensitivity = (APEX_CONFIG9_SMD_SENSITIVITY_t)
(data[4] & APEX_CONFIG9_SMD_SENSITIVITY_SEL_MASK);
apex_params->sensitivity_mode = (APEX_CONFIG9_SENSITIVITY_MODE_t)
(data[4] & APEX_CONFIG9_SENSITIVITY_MODE_MASK);
/* Get params from apex_config10 : lowg_peak_th and lowg_samples_th */
apex_params->lowg_peak_th = (APEX_CONFIG10_LOWG_PEAK_TH_t)
(data[5] & APEX_CONFIG10_LOWG_PEAK_TH_SEL_MASK);
apex_params->lowg_samples_th = (APEX_CONFIG10_LOWG_TIME_TH_SAMPLES_t)
(data[5] & APEX_CONFIG10_LOWG_TIME_TH_SEL_MASK);
/* Get params from apex_config11 : highg_peak_th and highg_samples_th */
apex_params->highg_peak_th = (APEX_CONFIG11_HIGHG_PEAK_TH_t)
(data[6] & APEX_CONFIG11_HIGHG_PEAK_TH_SEL_MASK);
apex_params->highg_samples_th = (APEX_CONFIG11_HIGHG_TIME_TH_SAMPLES_t)
(data[6] & APEX_CONFIG11_HIGHG_TIME_TH_SEL_MASK);
/* Access apex reg 12 */
status |= inv_imu_read_reg(s, APEX_CONFIG12_MREG1, 1, &data[0]);
/* Get params from apex_config12 : ff_max_duration_cm and ff_min_duration_cm */
apex_params->ff_max_duration_cm = (APEX_CONFIG12_FF_MAX_DURATION_t)
(data[0] & APEX_CONFIG12_FF_MAX_DURATION_SEL_MASK);
apex_params->ff_min_duration_cm = (APEX_CONFIG12_FF_MIN_DURATION_t)
(data[0] & APEX_CONFIG12_FF_MIN_DURATION_SEL_MASK);
return status;
}
int inv_imu_apex_set_frequency(struct inv_imu_device *s, const APEX_CONFIG1_DMP_ODR_t frequency)
{
uint8_t value;
int status = 0;
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_DMP_ODR_MASK;
value |= frequency;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_enable_pedometer(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
status |= inv_imu_start_dmp(s);
/* Enable Pedometer */
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_PED_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_PED_ENABLE_EN;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_disable_pedometer(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
/* Disable Pedometer */
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_PED_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_PED_ENABLE_DIS;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_enable_tilt(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
status |= inv_imu_start_dmp(s);
/* Enable Tilt */
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_TILT_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_TILT_ENABLE_EN;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_disable_tilt(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
/* Disable Tilt */
status |= inv_imu_read_reg(s, APEX_CONFIG1, 1, &value);
value &= ~APEX_CONFIG1_TILT_ENABLE_MASK;
value |= (uint8_t)APEX_CONFIG1_TILT_ENABLE_DIS;
status |= inv_imu_write_reg(s, APEX_CONFIG1, 1, &value);
return status;
}
int inv_imu_apex_get_data_activity(struct inv_imu_device *s, inv_imu_apex_step_activity_t *apex_activity)
{
uint8_t data[4];
int status = inv_imu_read_reg(s, APEX_DATA0, 4, data);
apex_activity->step_cnt = data[1] << 8 | data[0];
apex_activity->step_cadence = data[2];
apex_activity->activity_class = data[3] & APEX_DATA3_ACTIVITY_CLASS_MASK;
return status;
}
int inv_imu_apex_get_data_free_fall(struct inv_imu_device *s, uint16_t *freefall_duration)
{
uint8_t data[2];
int status = inv_imu_read_reg(s, APEX_DATA4, 2, &data[0]);
*freefall_duration = (data[1] << 8) | data[0];
return status;
}

View File

@@ -0,0 +1,185 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2017 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup DriverApex IMU driver high level functions related to APEX and the DMP
* @brief High-level function to setup an IMU device
* @ingroup Driver
* @{
*/
/** @file inv_imu_apex.h
* High-level function to setup an IMU device
*/
#ifndef _INV_IMU_APEX_H_
#define _INV_IMU_APEX_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "inv_imu_defs.h"
#include "InvError.h"
#include <stdint.h>
#include <string.h>
/* Forward declarations */
struct inv_imu_device;
/** @brief IMU APEX inputs parameters definition
*/
typedef struct {
APEX_CONFIG3_PEDO_AMP_TH_t pedo_amp_th;
uint8_t pedo_step_cnt_th;
uint8_t pedo_step_det_th;
APEX_CONFIG4_PEDO_SB_TIMER_TH_t pedo_sb_timer_th;
APEX_CONFIG4_PEDO_HI_ENRGY_TH_t pedo_hi_enrgy_th;
APEX_CONFIG5_TILT_WAIT_TIME_t tilt_wait_time;
APEX_CONFIG2_DMP_POWER_SAVE_TIME_t power_save_time;
APEX_CONFIG0_DMP_POWER_SAVE_t power_save;
APEX_CONFIG9_SENSITIVITY_MODE_t sensitivity_mode;
APEX_CONFIG2_LOW_ENERGY_AMP_TH_t low_energy_amp_th;
APEX_CONFIG9_SMD_SENSITIVITY_t smd_sensitivity;
APEX_CONFIG9_FF_DEBOUNCE_DURATION_t ff_debounce_duration;
APEX_CONFIG12_FF_MAX_DURATION_t ff_max_duration_cm;
APEX_CONFIG12_FF_MIN_DURATION_t ff_min_duration_cm;
APEX_CONFIG10_LOWG_PEAK_TH_t lowg_peak_th;
APEX_CONFIG5_LOWG_PEAK_TH_HYST_t lowg_peak_hyst;
APEX_CONFIG10_LOWG_TIME_TH_SAMPLES_t lowg_samples_th;
APEX_CONFIG11_HIGHG_PEAK_TH_t highg_peak_th;
APEX_CONFIG5_HIGHG_PEAK_TH_HYST_t highg_peak_hyst;
APEX_CONFIG11_HIGHG_TIME_TH_SAMPLES_t highg_samples_th;
} inv_imu_apex_parameters_t;
/** @brief APEX pedometer outputs
*/
typedef struct inv_imu_apex_step_activity {
uint16_t step_cnt; /**< Number of steps taken */
uint8_t step_cadence; /**< Walk/run cadence in number of samples.
Format is u6.2. E.g, At 50Hz and 2Hz walk frequency, if the cadency is 25 samples.
The register will output 100. */
uint8_t activity_class; /**< Detected activity unknown (0), walk (1) or run (2) */
} inv_imu_apex_step_activity_t;
/** @brief Enable Free Fall.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_enable_ff(struct inv_imu_device *s);
/** @brief Disable Free Fall.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_disable_ff(struct inv_imu_device *s);
/** @brief Enable Significant Motion Detection.
* note : SMD requests to have the accelerometer enabled to work.
* To have good performance, it's recommended to set accelerometer ODR (Output Data Rate) to 20ms
* and the accelerometer in Low Power Mode.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_enable_smd(struct inv_imu_device *s);
/** @brief Disable Significant Motion Detection.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_disable_smd(struct inv_imu_device *s);
/** @brief Fill the APEX parameters structure with all the default parameters for APEX algorithms (pedometer, tilt)
* @param[out] apex_inputs Default input parameters. See @sa inv_imu_apex_parameters_t
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_init_parameters_struct(struct inv_imu_device *s, inv_imu_apex_parameters_t *apex_inputs);
/** @brief Configures DMP parameters for APEX algorithms (pedometer, tilt, lowg, highg).
* This programmable parameters will be decoded and propagate to the SRAM to be executed at DMP start.
* @param[in] apex_inputs The requested input parameters. See @sa inv_imu_apex_parameters_t
* @warning APEX inputs can't change on the fly, this API should be called before enabling any APEX features.
* @warning APEX configuration can't be done too frequently, but only once every 10ms.
* Otherwise it can create unknown behavior.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_configure_parameters(struct inv_imu_device *s, const inv_imu_apex_parameters_t *apex_inputs);
/** @brief Returns current DMP parameters for APEX algorithms (pedometer, tilt).
* @param[out] apex_params The current parameter, fetched from registers. See @sa inv_imu_apex_parameters_t
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_get_parameters(struct inv_imu_device *s, inv_imu_apex_parameters_t *apex_params);
/** @brief Configure DMP Output Data Rate for APEX algorithms (pedometer, tilt)
* @param[in] frequency The requested frequency.
* @sa APEX_CONFIG1_DMP_ODR_t
* @warning DMP_ODR can change on the fly, and the DMP code will accommodate necessary modifications
* @warning The user needs to take care to set Accel frequency >= DMP frequency. This is a hard constraint
since HW will not handle incorrect setting.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_set_frequency(struct inv_imu_device *s, const APEX_CONFIG1_DMP_ODR_t frequency);
/** @brief Enable APEX algorithm Pedometer.
* note : Pedometer request to have the accelerometer enabled to works
* with accelerometer frequency less than dmp frequency.
* @return 0 on success, negative value on error.
* @warning Pedometer must be turned OFF to reconfigure it
*/
int inv_imu_apex_enable_pedometer(struct inv_imu_device *s);
/** @brief Disable APEX algorithm Pedometer.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_disable_pedometer(struct inv_imu_device *s);
/** @brief Enable APEX algorithm Tilt.
* note : Tilt request to have the accelerometer enabled to works
* with accelerometer frequency less than dmp frequency.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_enable_tilt(struct inv_imu_device *s);
/** @brief Disable APEX algorithm Tilt.
* @return 0 on success, negative value on error.
*/
int inv_imu_apex_disable_tilt(struct inv_imu_device *s);
/** @brief Retrieve APEX pedometer outputs and format them
* @param[out] apex_activity Apex step and activity data value.
* @return 0 in case of success, negative value on error. See enum inv_error
*/
int inv_imu_apex_get_data_activity(struct inv_imu_device *s, inv_imu_apex_step_activity_t *apex_activity);
/** @brief Retrieve APEX free fall outputs and format them
* @param[out] Free fall duration in number of sample.
* @return 0 in case of success, negative value on error. See enum inv_error
*/
int inv_imu_apex_get_data_free_fall(struct inv_imu_device *s, uint16_t *freefall_duration);
#ifdef __cplusplus
}
#endif
#endif /* _INV_IMU_APEX_H_ */
/** @} */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,520 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2017 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup Driver IMU driver high level functions
* @brief High-level function to setup an IMU device
* @ingroup DriverIcm
* @{
*/
/** @file inv_imu_driver.h
* High-level function to setup an IMU device
*/
#ifndef _INV_IMU_DRIVER_H_
#define _INV_IMU_DRIVER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "inv_imu_defs.h"
#include "inv_imu_transport.h"
#include "InvError.h"
#include <stdint.h>
#include <string.h>
/** @brief IMU max FSR values for accel and gyro
* Dependent on chip
*/
#define ACCEL_CONFIG0_FS_SEL_MAX ACCEL_CONFIG0_FS_SEL_16g
#define GYRO_CONFIG0_FS_SEL_MAX GYRO_CONFIG0_FS_SEL_2000dps
#define ACCEL_OFFUSER_MAX_MG 1000
#define GYRO_OFFUSER_MAX_DPS 64
/** @brief IMU maximum buffer size mirrored from FIFO at polling time
* @warning fifo_idx type variable must be large enough to parse the FIFO_MIRRORING_SIZE
*/
#define FIFO_MIRRORING_SIZE 16 * 258 // packet size * max_count = 4kB
/** @brief IMU Accelerometer start-up time before having correct data
*/
#define ACC_STARTUP_TIME_US 10000
/** @brief IMU Gyroscope start-up time before having correct data
*/
#define GYR_STARTUP_TIME_US 70000
/** @brief IMU Gyroscope power off to power on duration
*/
#define GYR_POWER_OFF_DUR_US 20000
/** @brief Sensor identifier for UI control function
*/
enum inv_imu_sensor {
INV_SENSOR_ACCEL, /**< Accelerometer */
INV_SENSOR_GYRO, /**< Gyroscope */
INV_SENSOR_FSYNC_EVENT, /**< FSYNC */
INV_SENSOR_TEMPERATURE, /**< Chip temperature */
INV_SENSOR_DMP_PEDOMETER_EVENT, /**< Pedometer: step detected */
INV_SENSOR_DMP_PEDOMETER_COUNT, /**< Pedometer: step counter */
INV_SENSOR_DMP_TILT, /**< Tilt */
INV_SENSOR_DMP_FF, /**< FreeFall */
INV_SENSOR_DMP_LOWG, /**< Low G */
INV_SENSOR_DMP_SMD, /**< Significant Motion Detection */
INV_SENSOR_MAX
};
/** @brief Configure Fifo usage
*/
typedef enum {
INV_IMU_FIFO_DISABLED = 0, /**< Fifo is disabled and data source is sensors registers */
INV_IMU_FIFO_ENABLED = 1, /**< Fifo is used as data source */
}INV_IMU_FIFO_CONFIG_t;
/** @brief Sensor event structure definition
*/
typedef struct {
int sensor_mask;
uint16_t timestamp_fsync;
int16_t accel[3];
int16_t gyro[3];
int16_t temperature;
int8_t accel_high_res[3];
int8_t gyro_high_res[3];
} inv_imu_sensor_event_t;
/** @brief IMU driver states definition
*/
struct inv_imu_device {
struct inv_imu_transport transport; /**< Transport layer
Must be the first one of struct inv_imu_device */
void (*sensor_event_cb)(inv_imu_sensor_event_t *event); /**< callback executed by:
inv_imu_get_data_from_fifo (if FIFO is used)
inv_imu_get_data_from_registers (if FIFO isn't used)
May be NULL if above API are not used by application */
uint8_t fifo_data[FIFO_MIRRORING_SIZE]; /**< FIFO mirroring memory area */
uint8_t dmp_is_on; /**< DMP started status */
uint8_t endianness_data; /**< Data endianness configuration */
uint8_t fifo_highres_enabled; /**< Highres mode configuration */
INV_IMU_FIFO_CONFIG_t fifo_is_used; /**< FIFO configuration */
uint64_t gyro_start_time_us; /**< Gyro start time used to discard first samples */
uint64_t accel_start_time_us; /**< Accel start time used to discard first samples */
uint64_t gyro_power_off_tmst; /**< Gyro power off time */
};
/* Interrupt enum state for INT1, INT2, and IBI */
typedef enum {
INV_IMU_DISABLE = 0,
INV_IMU_ENABLE
} inv_imu_interrupt_value;
/** @brief Interrupt definition
*/
typedef struct {
inv_imu_interrupt_value INV_UI_FSYNC;
inv_imu_interrupt_value INV_UI_DRDY;
inv_imu_interrupt_value INV_FIFO_THS;
inv_imu_interrupt_value INV_FIFO_FULL;
inv_imu_interrupt_value INV_SMD;
inv_imu_interrupt_value INV_WOM_X;
inv_imu_interrupt_value INV_WOM_Y;
inv_imu_interrupt_value INV_WOM_Z;
inv_imu_interrupt_value INV_FF;
inv_imu_interrupt_value INV_LOWG;
inv_imu_interrupt_value INV_STEP_DET;
inv_imu_interrupt_value INV_STEP_CNT_OVFL;
inv_imu_interrupt_value INV_TILT_DET;
} inv_imu_interrupt_parameter_t;
/** @brief Configure the serial interface used to access the device and execute hardware initialization.
*
* This functions first configures serial interface passed in parameter to make sure device
* is accessible both in read and write. Thus no serial access should be done before
* successfully executing the present function.
*
* Then if requested serial interface is a primary interface (aka UI interface or AP
* interface), this function initializes the device using the following hardware settings:
* - set timestamp resolution to 16us
* - enable FIFO mechanism with the following configuration:
* - FIFO record mode i.e FIFO count unit is packet
* - FIFO snapshot mode i.e drop the data when the FIFO overflows
* - Timestamp is logged in FIFO
* - Little Endian fifo_count and fifo_data
* - generate FIFO threshold interrupt when packet count reaches FIFO watermark
* - set FIFO watermark to 1 packet
* - enable temperature and timestamp data to go to FIFO
*
*
* @param[in] s driver structure. Note that first field of this structure MUST be a struct
* inv_imu_serif.
*
* @param[in] serif pointer on serial interface structure to be used to access inv_device.
*
* @param[in] sensor_event_cb callback executed by inv_imu_get_data_from_fifo function
* each time it extracts some valid data from fifo. Or inv_imu_get_data_from_registers read data
* from register. Thus this parameter is optional as long
* as inv_imu_get_data_from_fifo/inv_imu_get_data_from_registers function is not used.
*
* @return 0 on success, negative value on error.
*/
int inv_imu_init(struct inv_imu_device *s,
struct inv_imu_serif *serif,
void (*sensor_event_cb)(inv_imu_sensor_event_t *event));
/** @brief Perform a soft reset of the device
* @return 0 on success, negative value on error.
*/
int inv_imu_device_reset(struct inv_imu_device *s);
/** @brief return WHOAMI value
* @param[out] who_am_i WHOAMI for device
* @return 0 on success, negative value on error
*/
int inv_imu_get_who_am_i(struct inv_imu_device *s, uint8_t *who_am_i);
/** @brief Enable/put accel in low power mode
* @return 0 on success, negative value on error.
* @details
* It enables accel and gyro data in the FIFO (so
* the packet format is 16 bytes). If called first,
* the configuration will be applied, otherwise it
* will be ignored if the FIFO is not empty (but since
* the new configuration is identical it is not a issue).
* @warning inv_device::register_cache::pwr_mgmt0_reg is modified by this function
*/
int inv_imu_enable_accel_low_power_mode(struct inv_imu_device *s);
/** @brief Enable/put accel in low noise mode
* @return 0 on success, negative value on error.
* @details
* It enables accel and gyro data in the FIFO (so
* the packet format is 16 bytes). If called first,
* the configuration will be applied, otherwise it
* will be ignored if the FIFO is not empty (but since
* the new configuration is identical it is not a issue).
* @warning inv_device::register_cache::pwr_mgmt0_reg is modified by this function
*/
int inv_imu_enable_accel_low_noise_mode(struct inv_imu_device *s);
/** @brief Disable all 3 axes of accel
* @return 0 on success, negative value on error.
* @details
* If both accel and gyro are turned off as a result of this
* function, they will also be removed from the FIFO and a
* FIFO reset will be performed (to guarantee no side effects
* until the next enable sensor call)
* @warning inv_device::register_cache::pwr_mgmt0_reg is modified by this function
*/
int inv_imu_disable_accel(struct inv_imu_device *s);
/** @brief Enable/put gyro in low noise mode
* @return 0 on success, negative value on error.
* @details
* It enables gyro and accel data in the FIFO (so
* the packet format is 16 bytes). If called first,
* the configuration will be applied, otherwise it
* will be ignored if the FIFO is not empty (but since
* the new configuration is identical it is not a issue).
* @warning inv_device::register_cache::pwr_mgmt0_reg is modified by this function
*/
int inv_imu_enable_gyro_low_noise_mode(struct inv_imu_device *s);
/** @brief Disable all 3 axes of gyro
* @return 0 on success, negative value on error.
* @details
* If both accel and gyro are turned off as a result of this
* function, they will also be removed from the FIFO and a
* FIFO reset will be performed (to guarantee no side effects
* until the next enable sensor call)
* @warning inv_device::register_cache::pwr_mgmt0_reg is modified by this function
*/
int inv_imu_disable_gyro(struct inv_imu_device *s);
/** @brief Enable fsync tagging functionality.
* In details it:
* - enables fsync
* - enables timestamp to registers. Once fsync is enabled fsync counter is pushed to
* fifo instead of timestamp. So timestamp is made available in registers. Note that
* this increase power consumption.
* - enables fsync related interrupt
* @return 0 on success, negative value on error.
*/
int inv_imu_enable_fsync(struct inv_imu_device *s);
/** @brief Disable fsync tagging functionality.
* In details it:
* - disables fsync
* - disables timestamp to registers. Once fsync is disabled timestamp is pushed to fifo
* instead of fsync counter. So in order to decrease power consumption, timestamp is no
* more available in registers.
* - disables fsync related interrupt
* @return 0 on success, negative value on error.
*/
int inv_imu_disable_fsync(struct inv_imu_device *s);
/** @brief Configure which interrupt source can trigger INT1.
* @param[in] interrupt_to_configure structure with the corresponding state to manage INT1.
* @return 0 on success, negative value on error.
*/
int inv_imu_set_config_int1(struct inv_imu_device *s,
inv_imu_interrupt_parameter_t *interrupt_to_configure);
/** @brief Retrieve interrupts configuration.
* @param[in] interrupt_to_configure structure with the corresponding state to manage INT1.
* @return 0 on success, negative value on error.
*/
int inv_imu_get_config_int1(struct inv_imu_device *s,
inv_imu_interrupt_parameter_t *interrupt_to_configure);
/** @brief Configure which interrupt source can trigger INT2.
* @param[in] interrupt_to_configure structure with the corresponding state to INT2.
* @return 0 on success, negative value on error.
*/
int inv_imu_set_config_int2(struct inv_imu_device *s,
inv_imu_interrupt_parameter_t *interrupt_to_configure);
/** @brief Retrieve interrupts configuration.
* @param[in] interrupt_to_configure structure with the corresponding state to manage INT2.
* @return 0 on success, negative value on error.
*/
int inv_imu_get_config_int2(struct inv_imu_device *s,
inv_imu_interrupt_parameter_t *interrupt_to_configure);
/** @brief Read all registers containing data (temperature, accelerometer and gyroscope). Then it calls
* sensor_event_cb function passed in parameter of inv_imu_init function for each packet
* @return 0 on success, negative value on error.
*/
int inv_imu_get_data_from_registers(struct inv_imu_device *s);
/** @brief Read all available packets from the FIFO. For each packet function builds a
* sensor event containing packet data and validity information. Then it calls
* sensor_event_cb funtion passed in parameter of inv_imu_init function for each
* packet.
* @return number of valid packets read on success, negative value on error.
*/
int inv_imu_get_data_from_fifo(struct inv_imu_device *s);
/** @brief Converts ACCEL_CONFIG0_ODR_t or GYRO_CONFIG0_ODR_t enums to period expressed in us
* @param[in] odr_bitfield An ACCEL_CONFIG0_ODR_t or GYRO_CONFIG0_ODR_t enum
* @return The corresponding period expressed in us
*/
uint32_t inv_imu_convert_odr_bitfield_to_us(uint32_t odr_bitfield);
/** @brief Configure accel Output Data Rate
* @param[in] frequency The requested frequency.
* @sa ACCEL_CONFIG0_ODR_t
* @return 0 on success, negative value on error.
* @warning inv_device::register_cache::accel_config0_reg is modified by this function
*/
int inv_imu_set_accel_frequency(struct inv_imu_device *s,
const ACCEL_CONFIG0_ODR_t frequency);
/** @brief Configure gyro Output Data Rate
* @param[in] frequency The requested frequency.
* @sa GYRO_CONFIG0_ODR_t
* @return 0 on success, negative value on error.
* @warning inv_device::register_cache::gyro_config0_reg is modified by this function
*/
int inv_imu_set_gyro_frequency(struct inv_imu_device *s,
const GYRO_CONFIG0_ODR_t frequency);
/** @brief Set accel full scale range
* @param[in] accel_fsr_g requested full scale range.
* @sa ACCEL_CONFIG0_FS_SEL_t.
* @return 0 on success, negative value on error.
* @warning inv_device::register_cache::accel_config0_reg is modified by this function
*/
int inv_imu_set_accel_fsr(struct inv_imu_device *s,
ACCEL_CONFIG0_FS_SEL_t accel_fsr_g);
/** @brief Access accel full scale range
* @param[out] accel_fsr_g current full scale range.
* @sa ACCEL_CONFIG0_FS_SEL_t.
* @return 0 on success, negative value on error.
* @warning inv_device::register_cache::accel_config0_reg is relied upon by this function
*/
int inv_imu_get_accel_fsr(struct inv_imu_device *s,
ACCEL_CONFIG0_FS_SEL_t *accel_fsr_g);
/** @brief Set gyro full scale range
* @param[in] gyro_fsr_dps requested full scale range.
* @sa GYRO_CONFIG0_FS_SEL_t.
* @return 0 on success, negative value on error.
* @warning inv_device::register_cache::gyro_config0_reg is modified by this function
*/
int inv_imu_set_gyro_fsr(struct inv_imu_device *s,
GYRO_CONFIG0_FS_SEL_t gyro_fsr_dps);
/** @brief Access gyro full scale range
* @param[out] gyro_fsr_dps current full scale range.
* @sa GYRO_CONFIG0_FS_SEL_t.
* @return 0 on success, negative value on error.
* @warning inv_device::register_cache::gyro_config0_reg is relied upon by this function
*/
int inv_imu_get_gyro_fsr(struct inv_imu_device *s,
GYRO_CONFIG0_FS_SEL_t *gyro_fsr_dps);
/** @brief Set accel Low-Power averaging value
* @param[in] acc_avg requested averaging value
* @sa ACCEL_CONFIG1_ACCEL_FILT_AVG_t
* @return 0 on success, negative value on error.
*/
int inv_imu_set_accel_lp_avg(struct inv_imu_device *s,
ACCEL_CONFIG1_ACCEL_FILT_AVG_t acc_avg);
/** @brief Set accel Low-Noise bandwidth value
* @param[in] acc_bw requested averaging value
* @sa ACCEL_CONFIG1_ACCEL_FILT_BW_t
* @return 0 on success, negative value on error.
*/
int inv_imu_set_accel_ln_bw(struct inv_imu_device *s,
ACCEL_CONFIG1_ACCEL_FILT_BW_t acc_bw);
/** @brief Set gyro Low-Noise bandwidth value
* @param[in] gyr_bw requested averaging value
* @sa GYRO_CONFIG1_GYRO_FILT_BW_t
* @return 0 on success, negative value on error.
*/
int inv_imu_set_gyro_ln_bw(struct inv_imu_device *s,
GYRO_CONFIG1_GYRO_FILT_BW_t gyr_bw);
/** @brief Set timestamp resolution
* @param[in] timestamp_resol requested timestamp resolution
* @sa TMST_CONFIG1_RESOL_t
* @return 0 on success, negative value on error.
*/
int inv_imu_set_timestamp_resolution(struct inv_imu_device *s,
const TMST_CONFIG1_RESOL_t timestamp_resol);
/** @brief reset IMU fifo
* @return 0 on success, negative value on error.
*/
int inv_imu_reset_fifo(struct inv_imu_device *s);
/** @brief Enable 20 bits raw acc and raw gyr data in fifo.
* @return 0 on success, negative return code otherwise
*/
int inv_imu_enable_high_resolution_fifo(struct inv_imu_device *s);
/** @brief Disable 20 bits raw acc and raw gyr data in fifo.
* @return 0 on success, negative return code otherwise
*/
int inv_imu_disable_high_resolution_fifo(struct inv_imu_device *s);
/** @brief Configure Fifo
* @param[in] fifo_config Fifo configuration method :
* if FIFO is enabled, data are pushed to FIFO and FIFO THS interrupt is set
* if FIFO is disabled, data are not pused to FIFO and DRDY interrupt is set
* @sa INV_IMU_FIFO_CONFIG_t
*/
int inv_imu_configure_fifo(struct inv_imu_device *s,
INV_IMU_FIFO_CONFIG_t fifo_config);
/** @brief Get FIFO timestamp resolution
* @return the timestamp resolution in us as a q24 or 0 in case of error
*/
int32_t inv_imu_get_fifo_timestamp_resolution_us_q24(struct inv_imu_device *s);
/** @brief Get register timestamp resolution
* @return the timestamp resolution in us as a q24 or 0 in case of error
*/
uint32_t inv_imu_get_reg_timestamp_resolution_us_q24(struct inv_imu_device *s);
/** @brief Enable Wake On Motion.
* @param[in] wom_x_th threshold value for the Wake on Motion Interrupt for X-axis accelerometer.
* @param[in] wom_y_th threshold value for the Wake on Motion Interrupt for Y-axis accelerometer.
* @param[in] wom_z_th threshold value for the Wake on Motion Interrupt for Z-axis accelerometer.
* @param[in] wom_int select which mode between AND/OR is used to generate interrupt.
* @param[in] wom_dur select the number of overthreshold event to wait before generating interrupt.
* @return 0 on success, negative value on error.
*/
int inv_imu_configure_wom(struct inv_imu_device *s,
const uint8_t wom_x_th,
const uint8_t wom_y_th,
const uint8_t wom_z_th,
WOM_CONFIG_WOM_INT_MODE_t wom_int,
WOM_CONFIG_WOM_INT_DUR_t wom_dur);
/** @brief Enable Wake On Motion.
* note : WoM requests to have the accelerometer enabled to work.
* As a consequence Fifo water-mark interrupt is disabled to only trigger WoM interrupts.
* To have good performance, it's recommended to set accelerometer ODR (Output Data Rate) to 20ms
* and the accelerometer in Low Power Mode.
* @return 0 on success, negative value on error.
*/
int inv_imu_enable_wom(struct inv_imu_device *s);
/** @brief Disable Wake On Motion.
* note : Fifo water-mark interrupt is re-enabled when WoM is disabled.
* @return 0 on success, negative value on error.
*/
int inv_imu_disable_wom(struct inv_imu_device *s);
/** @brief Start DMP for APEX algorithms and selftest
* @return 0 on success, negative value on error.
*/
int inv_imu_start_dmp(struct inv_imu_device *s);
/** @brief Reset DMP for APEX algorithms and selftest
* @return 0 on success, negative value on error.
*/
int inv_imu_reset_dmp(struct inv_imu_device *s,
const APEX_CONFIG0_DMP_MEM_RESET_t sram_reset);
/** @breif Set the UI endianness and set the inv_device endianness field
* @return 0 on success, negative value on error.
*/
int inv_imu_set_endianness(struct inv_imu_device *s,
INTF_CONFIG0_DATA_ENDIAN_t endianness);
/** @breif Read the UI endianness and set the inv_device endianness field
* @return 0 on success, negative value on error.
*/
int inv_imu_get_endianness(struct inv_imu_device *s);
/** @brief Configure Fifo decimation
* @param[in] requested decimation factor value from 2 to 256
* @return 0 on success, negative value on error.
*/
int inv_imu_configure_fifo_data_rate(struct inv_imu_device *s,
FDR_CONFIG_FDR_SEL_t dec_factor);
/** @brief Return driver version x.y.z-suffix as a char array
* @retval driver version a char array "x.y.z-suffix"
*/
const char * inv_imu_get_version(void);
#ifdef __cplusplus
}
#endif
#endif /* _INV_IMU_DRIVER_H_ */
/** @} */

View File

@@ -0,0 +1,64 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2017 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup DriverExt IMU driver extern functions
* @brief Extern functions for IMU devices
* @ingroup Driver
* @{
*/
/** @file inv_imu_extfunc.h
* Extern functions for IMU devices
*/
#ifndef _INV_IMU_EXTFUNC_H_
#define _INV_IMU_EXTFUNC_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Hook for low-level high res system sleep() function to be implemented by upper layer
* ~100us resolution is sufficient
* @param[in] us number of us the calling thread should sleep
*/
extern void inv_imu_sleep_us(uint32_t us);
/** @brief Hook for low-level high res system get_time() function to be implemented by upper layer
* Value shall be on 64bit with a 1 us resolution
* @return The current time in us
*/
extern uint64_t inv_imu_get_time_us(void);
#ifdef __cplusplus
}
#endif
#endif /* _INV_IMU_EXTFUNC_H_ */
/** @} */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,179 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
#include "inv_imu_selftest.h"
#include "inv_imu_extfunc.h"
#include "inv_imu_transport.h"
static int configure_selftest_parameters(struct inv_imu_device *s,
const inv_imu_selftest_parameters_t st_params);
int inv_imu_run_selftest(struct inv_imu_device *s,
const inv_imu_selftest_parameters_t st_params,
inv_imu_selftest_output_t *st_output)
{
int status = 0;
uint8_t value;
uint8_t data[2] = {0};
uint8_t st_done = 0;
int polling_timeout_ms = 1000;
/* Disables Gyro/Accel sensors */
status |= inv_imu_read_reg(s, PWR_MGMT0, 1, &value);
value &= ~(PWR_MGMT0_ACCEL_MODE_MASK | PWR_MGMT0_GYRO_MODE_MASK);
status |= inv_imu_write_reg(s, PWR_MGMT0, 1, &value);
/* Enable RC oscillator */
status |= inv_imu_switch_on_mclk(s);
/* Clear DMP SRAM (1 ms wait included in `inv_imu_reset_dmp()`) */
status |= inv_imu_reset_dmp(s, APEX_CONFIG0_DMP_MEM_RESET_APEX_ST_EN);
/* Update `dmp_is_on` since APEX features will have to restart from scratch */
s->dmp_is_on = 0;
/* Load self-test data */
status |= inv_imu_load_selftest_data(s);
/* Set self-test parameters */
status |= configure_selftest_parameters(s, st_params);
/*
* Enable accel and/or gyro self-test.
* If both accel and gyro self-test are enabled,
* they should be set simultaneously in the same write access
*/
status |= inv_imu_read_reg(s, SELFTEST_MREG1, 1, &value);
value &= ~SELFTEST_EN;
value |= (uint8_t)st_params.st_control;
status |= inv_imu_write_reg(s, SELFTEST_MREG1, 1, &value);
/* Poll int_status_st_done bit */
do {
inv_imu_sleep_us(1000);
status |= inv_imu_read_reg(s, INT_STATUS, 1, &st_done);
st_done &= INT_STATUS_ST_INT_MASK;
if (0 == --polling_timeout_ms)
return (status | -1); /* Return error if timeout is reached */
} while ( !st_done /* Exit if ST_DONE */
&& !status /* Or if error is detected */);
/* Read self-test results */
status |= inv_imu_read_reg(s, ST_STATUS1_MREG1, 2, &data[0]);
st_output->accel_status = (data[0] & ST_STATUS1_ACCEL_ST_PASS_MASK) >> ST_STATUS1_ACCEL_ST_PASS_POS;
st_output->ax_status = (data[0] & ST_STATUS1_AX_ST_PASS_MASK) >> ST_STATUS1_AX_ST_PASS_POS;
st_output->ay_status = (data[0] & ST_STATUS1_AY_ST_PASS_MASK) >> ST_STATUS1_AY_ST_PASS_POS;
st_output->az_status = (data[0] & ST_STATUS1_AZ_ST_PASS_MASK) >> ST_STATUS1_AZ_ST_PASS_POS;
st_output->gyro_status = (data[1] & ST_STATUS2_GYRO_ST_PASS_MASK) >> ST_STATUS2_GYRO_ST_PASS_POS;
st_output->gyro_status |= ((data[1] & ST_STATUS2_ST_INCOMPLETE_MASK) >> ST_STATUS2_ST_INCOMPLETE_POS) << 1;
st_output->gx_status = (data[1] & ST_STATUS2_GX_ST_PASS_MASK) >> ST_STATUS2_GX_ST_PASS_POS;
st_output->gy_status = (data[1] & ST_STATUS2_GY_ST_PASS_MASK) >> ST_STATUS2_GY_ST_PASS_POS;
st_output->gz_status = (data[1] & ST_STATUS2_GZ_ST_PASS_MASK) >> ST_STATUS2_GZ_ST_PASS_POS;
/* Disable self-test */
status |= inv_imu_read_reg(s, SELFTEST_MREG1, 1, &value);
value &= ~SELFTEST_EN;
value |= (uint8_t)SELFTEST_DIS;
status |= inv_imu_write_reg(s, SELFTEST_MREG1, 1, &value);
/* Reset FIFO because ST data may have been pushed to it */
status |= inv_imu_reset_fifo(s);
/* Restore idle bit */
status |= inv_imu_switch_off_mclk(s);
return status;
}
int inv_imu_init_selftest_parameters_struct(struct inv_imu_device *s,
inv_imu_selftest_parameters_t *st_params)
{
(void)s;
st_params->st_num_samples = ST_CONFIG_16_SAMPLES;
st_params->st_control = (SELFTEST_ACCEL_GYRO_ST_EN_t)SELFTEST_EN;
return 0;
}
int inv_imu_load_selftest_data(struct inv_imu_device *s)
{
int status = 0;
uint8_t value;
/* Enable RC oscillator */
status |= inv_imu_switch_on_mclk(s);
/* Set up OTP controller to reload factory-trimmed self-test response into SRAM */
status |= inv_imu_read_reg(s, OTP_CONFIG_MREG1, 1, &value);
value &= ~OTP_CONFIG_OTP_COPY_MODE_MASK;
value |= (uint8_t)OTP_CONFIG_OTP_COPY_ST_DATA;
status |= inv_imu_write_reg(s, OTP_CONFIG_MREG1, 1, &value);
/* Take the OTP macro out of power-down mode */
status |= inv_imu_read_reg(s, OTP_CTRL7_MREG2, 1, &value);
value &= ~OTP_CTRL7_OTP_PWR_DOWN_MASK;
value |= (uint8_t)OTP_CTRL7_PWR_DOWN_DIS;
status |= inv_imu_write_reg(s, OTP_CTRL7_MREG2, 1, &value);
/* Wait for voltage generator to power on */
inv_imu_sleep_us(100);
/* Host should disable INT function first before kicking off OTP copy operation */
/* Trigger OTP to reload data (this time in self-test mode) */
status |= inv_imu_read_reg(s, OTP_CTRL7_MREG2, 1, &value);
value &= ~OTP_CTRL7_OTP_RELOAD_MASK;
value |= (uint8_t)OTP_CTRL7_OTP_RELOAD_EN;
status |= inv_imu_write_reg(s, OTP_CTRL7_MREG2, 1, &value);
/* Wait for OTP reload */
inv_imu_sleep_us(20);
/* Disable RC oscillator */
status |= inv_imu_switch_off_mclk(s);
return status;
}
static int configure_selftest_parameters(struct inv_imu_device *s,
const inv_imu_selftest_parameters_t st_params)
{
int status = 0;
uint8_t value;
/* Self-test configuration cannot be updated if it already running */
status |= inv_imu_read_reg(s, SELFTEST_MREG1, 1, &value);
if ((value & SELFTEST_EN) != SELFTEST_DIS)
return INV_ERROR_UNEXPECTED;
status |= inv_imu_read_reg(s, ST_CONFIG_MREG1, 1, &value);
value &= ~((uint8_t)ST_CONFIG_ST_NUMBER_SAMPLE_MASK
| (uint8_t)ST_CONFIG_ACCEL_ST_LIM_MASK
| (uint8_t)ST_CONFIG_GYRO_ST_LIM_MASK);
value |= (uint8_t)st_params.st_num_samples
| (uint8_t)ST_CONFIG_ACCEL_ST_LIM_50
| (uint8_t)ST_CONFIG_GYRO_ST_LIM_50;
status |= inv_imu_write_reg(s, ST_CONFIG_MREG1, 1, &value);
return status;
}

View File

@@ -0,0 +1,101 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup DriverST SelfTest IMU selftest
* @brief Low-level function to run selftest on a IMU device
* @ingroup Driver
* @{
*/
/** @file inv_imu_selftest.h
* Low-level function to run selftest on a IMU device
*/
#ifndef _INV_IMU_SELFTEST_H_
#define _INV_IMU_SELFTEST_H_
#include <stdint.h>
#include "InvExport.h"
#include "inv_imu_defs.h"
#include "inv_imu_driver.h"
#ifdef __cplusplus
extern "C" {
#endif
/* forward declaration */
struct inv_imu_device;
/** @brief Self-test input parameters
*/
typedef struct {
ST_CONFIG_NUM_SAMPLES_t st_num_samples; /**< Number of samples used to perform self-test */
SELFTEST_ACCEL_GYRO_ST_EN_t st_control; /**< Define which sensor is under self-test */
} inv_imu_selftest_parameters_t;
/** @brief Self-test routine outputs
*/
typedef struct {
int8_t accel_status; /**< global accelerometer self-test passed */
int8_t gyro_status; /**< global gyroscope self-test status: st_pass (bit0), st_incomplete (bit1) */
int8_t ax_status; /**< AX self-test status */
int8_t ay_status; /**< AY self-test status */
int8_t az_status; /**< AZ self-test status */
int8_t gx_status; /**< GX self-test status */
int8_t gy_status; /**< GY self-test status */
int8_t gz_status; /**< GZ self-test status */
} inv_imu_selftest_output_t;
/**
* @brief Perform hardware self-test for Accel and Gyro
* @param[in] Self-test parameters (see inv_imu_selftest_parameters_t)
* @param[out] Self-test results (see inv_imu_selftest_output_t)
* @return 0 on completion, negative number if intermediate errors occurred
*/
int inv_imu_run_selftest(struct inv_imu_device *s,
const inv_imu_selftest_parameters_t st_params,
inv_imu_selftest_output_t *st_output);
/** @brief Fill the self-test configuration structure with default configuration
* @param[in] selftest_params self-test parameters to be initialized
* @return 0 on success, negative return code otherwise
*/
int inv_imu_init_selftest_parameters_struct(struct inv_imu_device *s,
inv_imu_selftest_parameters_t *selftest_params);
/** @brief Load self-test data
* @return 0 on success, negative return code otherwise
*/
int inv_imu_load_selftest_data(struct inv_imu_device *s);
#ifdef __cplusplus
}
#endif
#endif /* _INV_IMU_SELFTEST_H_ */
/** @} */

View File

@@ -0,0 +1,257 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
#include "inv_imu_extfunc.h"
#include "inv_imu_transport.h"
#include "inv_imu_regmap.h"
#include "InvError.h"
/* Function definition */
static uint8_t *get_register_cache_addr(struct inv_imu_device *s, uint32_t reg);
static int write_sreg(struct inv_imu_device *s, uint8_t reg, uint32_t len, const uint8_t *buf);
static int read_sreg(struct inv_imu_device *s, uint8_t reg, uint32_t len, uint8_t *buf);
static int write_mclk_reg(struct inv_imu_device *s, uint16_t regaddr, uint8_t wr_cnt, const uint8_t *buf);
static int read_mclk_reg(struct inv_imu_device *s, uint16_t regaddr, uint8_t rd_cnt, uint8_t *buf);
int inv_imu_init_transport(struct inv_imu_device *s)
{
int status = 0;
struct inv_imu_transport *t = (struct inv_imu_transport *)s;
status |= read_sreg(s, (uint8_t)PWR_MGMT0, 1, &(t->register_cache.pwr_mgmt0_reg));
status |= read_sreg(s, (uint8_t)GYRO_CONFIG0, 1, &(t->register_cache.gyro_config0_reg));
status |= read_sreg(s, (uint8_t)ACCEL_CONFIG0, 1, &(t->register_cache.accel_config0_reg));
status |= read_mclk_reg(s, (TMST_CONFIG1_MREG1 & 0xFFFF), 1, &(t->register_cache.tmst_config1_reg));
t->need_mclk_cnt = 0;
return status;
}
int inv_imu_read_reg(struct inv_imu_device *s, uint32_t reg, uint32_t len, uint8_t *buf)
{
uint32_t i;
int rc = 0;
for (i = 0; i < len; i++) {
uint8_t *cache_addr = get_register_cache_addr(s, reg + i);
if (cache_addr) {
buf[i] = *cache_addr;
} else {
if (!(reg & 0x10000)) {
rc |= read_mclk_reg(s, ((reg + i) & 0xFFFF), 1, &buf[i]);
} else {
rc |= read_sreg(s, (uint8_t)reg + i, len - i, &buf[i]);
break;
}
}
}
return rc;
}
int inv_imu_write_reg(struct inv_imu_device *s, uint32_t reg, uint32_t len, const uint8_t *buf)
{
uint32_t i;
int rc = 0;
for (i = 0; i < len; i++) {
uint8_t *cache_addr = get_register_cache_addr(s, reg + i);
if (cache_addr)
*cache_addr = buf[i];
if (!(reg & 0x10000))
rc |= write_mclk_reg(s, ((reg + i) & 0xFFFF), 1, &buf[i]);
}
if (reg & 0x10000)
rc |= write_sreg(s, (uint8_t)reg, len, buf);
return rc;
}
int inv_imu_switch_on_mclk(struct inv_imu_device *s)
{
int status = 0;
uint8_t data;
struct inv_imu_transport *t = (struct inv_imu_transport *)s;
/* set IDLE bit only if it is not set yet */
if (t->need_mclk_cnt == 0) {
status |= inv_imu_read_reg(s, PWR_MGMT0, 1, &data);
data |= PWR_MGMT0_IDLE_MASK;
status |= inv_imu_write_reg(s, PWR_MGMT0, 1, &data);
if (status)
return status;
/* Check if MCLK is ready */
do {
status = inv_imu_read_reg(s, MCLK_RDY, 1, &data);
} while ((status != 0) || !(data & MCLK_RDY_MCLK_RDY_MASK));
} else {
/* Make sure it is already on */
status |= inv_imu_read_reg(s, PWR_MGMT0, 1, &data);
if (0 == (data &= PWR_MGMT0_IDLE_MASK))
status |= INV_ERROR;
}
/* Increment the counter to keep track of number of MCLK requesters */
t->need_mclk_cnt++;
return status;
}
int inv_imu_switch_off_mclk(struct inv_imu_device *s)
{
int status = 0;
uint8_t data;
struct inv_imu_transport *t = (struct inv_imu_transport *)s;
/* Reset the IDLE but only if there is one requester left */
if (t->need_mclk_cnt == 1) {
status |= inv_imu_read_reg(s, PWR_MGMT0, 1, &data);
data &= ~PWR_MGMT0_IDLE_MASK;
status |= inv_imu_write_reg(s, PWR_MGMT0, 1, &data);
} else {
/* Make sure it is still on */
status |= inv_imu_read_reg(s, PWR_MGMT0, 1, &data);
if (0 == (data &= PWR_MGMT0_IDLE_MASK))
status |= INV_ERROR;
}
/* Decrement the counter */
t->need_mclk_cnt--;
return status;
}
/* Static function */
static uint8_t *get_register_cache_addr(struct inv_imu_device *s, uint32_t reg)
{
struct inv_imu_transport *t = (struct inv_imu_transport *)s;
switch(reg) {
case PWR_MGMT0: return &(t->register_cache.pwr_mgmt0_reg);
case GYRO_CONFIG0: return &(t->register_cache.gyro_config0_reg);
case ACCEL_CONFIG0: return &(t->register_cache.accel_config0_reg);
case TMST_CONFIG1_MREG1: return &(t->register_cache.tmst_config1_reg);
default: return (uint8_t *)0; // Not found
}
}
static int read_sreg(struct inv_imu_device *s, uint8_t reg, uint32_t len, uint8_t *buf)
{
struct inv_imu_serif *serif = (struct inv_imu_serif *)s;
if (len > serif->max_read)
return INV_ERROR_SIZE;
if (serif->read_reg(serif, reg, buf, len) != 0)
return INV_ERROR_TRANSPORT;
return 0;
}
static int write_sreg(struct inv_imu_device *s, uint8_t reg, uint32_t len, const uint8_t *buf)
{
struct inv_imu_serif *serif = (struct inv_imu_serif *)s;
if (len > serif->max_write)
return INV_ERROR_SIZE;
if (serif->write_reg(serif, reg, buf, len) != 0)
return INV_ERROR_TRANSPORT;
return 0;
}
static int read_mclk_reg(struct inv_imu_device *s, uint16_t regaddr, uint8_t rd_cnt, uint8_t *buf)
{
uint8_t data;
uint8_t blk_sel = (regaddr & 0xFF00) >> 8;
int status = 0;
// Have IMU not in IDLE mode to access MCLK domain
status |= inv_imu_switch_on_mclk(s);
// optimize by changing BLK_SEL only if not NULL
if (blk_sel)
status |= write_sreg(s, (uint8_t)BLK_SEL_R & 0xff, 1, &blk_sel);
data = (regaddr & 0x00FF);
status |= write_sreg(s, (uint8_t)MADDR_R, 1, &data);
inv_imu_sleep_us(10);
status |= read_sreg(s, (uint8_t)M_R, rd_cnt, buf);
inv_imu_sleep_us(10);
if (blk_sel) {
data = 0;
status |= write_sreg(s, (uint8_t)BLK_SEL_R, 1, &data);
}
// switch OFF MCLK if needed
status |= inv_imu_switch_off_mclk(s);
return status;
}
static int write_mclk_reg(struct inv_imu_device *s, uint16_t regaddr, uint8_t wr_cnt, const uint8_t *buf)
{
uint8_t data;
uint8_t blk_sel = (regaddr & 0xFF00) >> 8;
int status = 0;
// Have IMU not in IDLE mode to access MCLK domain
status |= inv_imu_switch_on_mclk(s);
// optimize by changing BLK_SEL only if not NULL
if (blk_sel)
status |= write_sreg(s, (uint8_t)BLK_SEL_W, 1, &blk_sel);
data = (regaddr & 0x00FF);
status |= write_sreg(s, (uint8_t)MADDR_W, 1, &data);
for (uint8_t i = 0; i < wr_cnt; i++) {
status |= write_sreg(s, (uint8_t)M_W, 1, &buf[i]);
inv_imu_sleep_us(10);
}
if (blk_sel) {
data = 0;
status = write_sreg(s, (uint8_t)BLK_SEL_W, 1, &data);
}
status |= inv_imu_switch_off_mclk(s);
return status;
}

View File

@@ -0,0 +1,122 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2015-2015 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively "Software") is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
/** @defgroup Transport IMU transport
* @brief Low-level IMU SCLK register access
* @ingroup Driver
* @{
*/
/** @file inv_imu_transport.h
* Low-level IMU SCLK register access
*/
#ifndef _INV_IMU_TRANSPORT_H_
#define _INV_IMU_TRANSPORT_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* forward declaration */
struct inv_imu_device;
/** @brief enumeration of serial interfaces available on IMU */
typedef enum
{
UI_I2C,
UI_SPI4,
UI_SPI3
} SERIAL_IF_TYPE_t;
/** @brief basesensor serial interface
*/
struct inv_imu_serif {
void *context;
int (*read_reg)(struct inv_imu_serif *serif, uint8_t reg, uint8_t *buf, uint32_t len);
int (*write_reg)(struct inv_imu_serif *serif, uint8_t reg, const uint8_t *buf, uint32_t len);
int (*configure)(struct inv_imu_serif *serif);
uint32_t max_read;
uint32_t max_write;
SERIAL_IF_TYPE_t serif_type;
};
/** @brief transport interface
*/
struct inv_imu_transport {
struct inv_imu_serif serif; /**< Warning : this field MUST be the first one of struct inv_imu_transport */
/** @brief Contains mirrored values of some IP registers */
struct register_cache {
uint8_t pwr_mgmt0_reg; /**< PWR_MGMT0, Bank: 0 */
uint8_t gyro_config0_reg; /**< GYRO_CONFIG0, Bank: 0 */
uint8_t accel_config0_reg; /**< ACCEL_CONFIG0, Bank: 0 */
uint8_t tmst_config1_reg; /**< TMST_CONFIG1, Bank: MREG_TOP1 */
} register_cache; /**< Store mostly used register values on SRAM */
uint8_t need_mclk_cnt; /**< internal counter to keep track of everyone that needs MCLK */
};
/** @brief Init cache variable.
* @return 0 in case of success, -1 for any error
*/
int inv_imu_init_transport(struct inv_imu_device *s);
/** @brief Reads data from a register on IMU.
* @param[in] reg register address to be read
* @param[in] len number of byte to be read
* @param[out] buf output data from the register
* @return 0 in case of success, -1 for any error
*/
int inv_imu_read_reg(struct inv_imu_device *s, uint32_t reg, uint32_t len, uint8_t *buf);
/** @brief Writes data to a register on IMU.
* @param[in] reg register address to be written
* @param[in] len number of byte to be written
* @param[in] buf input data to write
* @return 0 in case of success, -1 for any error
*/
int inv_imu_write_reg(struct inv_imu_device *s, uint32_t reg, uint32_t len, const uint8_t *buf);
/** @brief Enable MCLK so that MREG are clocked and system beyond SOI can be safely accessed
* @return 0 in case of success, -1 for any error
*/
int inv_imu_switch_on_mclk(struct inv_imu_device *s);
/** @brief Disable MCLK so that MREG are not clocked anymore, hence reducing power consumption
* @return 0 in case of success, -1 for any error
*/
int inv_imu_switch_off_mclk(struct inv_imu_device *s);
#ifdef __cplusplus
}
#endif
#endif /* _INV_IMU_TRANSPORT_H_ */
/** @} */

View File

@@ -0,0 +1,37 @@
/*
* ________________________________________________________________________________________________________
* Copyright (c) 2019 InvenSense Inc. All rights reserved.
*
* This software, related documentation and any modifications thereto (collectively “Software”) is subject
* to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
* and other intellectual property rights laws.
*
* InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
* and any use, reproduction, disclosure or distribution of the Software without an express license agreement
* from InvenSense is strictly prohibited.
*
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
* PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
* INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THE SOFTWARE.
* ________________________________________________________________________________________________________
*/
#ifndef _INV_IMU_VERSION_H_
#define _INV_IMU_VERSION_H_
#ifdef __cplusplus
extern "C" {
#endif
#define INV_IMU_VERSION_STRING "2.0.4"
#ifdef __cplusplus
}
#endif
#endif /* _INV_IMU_VERSION_H_ */

View File

@@ -0,0 +1,153 @@
//-----------------------------------------------------------------------------
/*
Copyright © 2014-2015 InvenSense Inc. Portions Copyright © 2014-2015 Movea. All rights reserved.
This software, related documentation and any modifications thereto (collectively “Software”) is subject
to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
and other intellectual property rights laws.
InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
and any use, reproduction, disclosure or distribution of the Software without an express license agreement
from InvenSense is strictly prohibited.
*/
//-----------------------------------------------------------------------------
#ifndef INVN_COMMON_INVN_TYPES_H_
#define INVN_COMMON_INVN_TYPES_H_
/**
* @defgroup invn_types Types
* @brief Motion Library - Type definitions.
* \details Definition of codes and error codes used within the MPL and
* returned to the user.
* Every function tries to return a meaningful error code basing
* on the occuring error condition. The error code is numeric.
*
* The available error codes and their associated values are:
* - (0) INV_SUCCESS
* - (32) INV_ERROR
* - (22 / EINVAL) INV_ERROR_INVALID_PARAMETER
* - (1 / EPERM) INV_ERROR_FEATURE_NOT_ENABLED
* - (36) INV_ERROR_FEATURE_NOT_IMPLEMENTED
* - (64) INV_ERROR_FIFO_READ_COUNT
* \todo Clean up the details documentation in order to use only the \\def one.
* \todo Add documentation to all the definitions
* \ingroup Common
* @file invn_types.h
*/
//=======================================//
//========= Integer Definition =========//
//=======================================//
#ifdef _MSC_VER
# include "inttypes.h"
#else
# include <stdint.h>
#endif
//=======================================//
//======= Fixed Point Conversion =======//
//=======================================//
//! \def INVN_FLT_TO_FXP
//! Convert the \a value from float to QN value. \ingroup invn_macro
#define INVN_FLT_TO_FXP(value, shift) ( (int32_t) ((float)(value)*(1ULL << (shift)) + ( (value>=0)-0.5f )) )
//! \def INVN_DBL_TO_FXP
//! Convert the \a value from double to QN value. \ingroup invn_macro
#define INVN_DBL_TO_FXP(value, shift) ( (int32_t) ((double)(value)*(1ULL << (shift)) + ( (value>=0)-0.5 )) )
//! \def INVN_FLT_TO_UFXP
//! Convert the \a value from float to unsigned QN value. \ingroup invn_macro
#define INVN_FLT_TO_UFXP(value, shift) ( (uint32_t) ((float)(value)*(1ULL << (shift)) + 0.5f) )
//! \def INVN_DBL_TO_UFXP
//! Convert the \a value from double to unsigned QN value. \ingroup invn_macro
#define INVN_DBL_TO_UFXP(value, shift) ( (uint32_t) ((double)(value)*(1ULL << (shift)) + 0.5) )
//! \def INVN_FXP_TO_FLT
//! Convert the \a value from QN value to float. \ingroup invn_macro
#define INVN_FXP_TO_FLT(value, shift) ( (float) (int32_t)(value) / (float)(1ULL << (shift)) )
//! \def INVN_FXP_TO_DBL
//! Convert the \a value from QN value to double. \ingroup invn_macro
#define INVN_FXP_TO_DBL(value, shift) ( (double) (int32_t)(value) / (double)(1ULL << (shift)) )
//! \def INVN_UFXP_TO_FLT
//! Convert the \a value from unsigned QN value to float. \ingroup invn_macro
#define INVN_UFXP_TO_FLT(value, shift) ( (float) (uint32_t)(value) / (float)(1ULL << (shift)) )
//! \def INVN_UFXP_TO_DBL
//! Convert the \a value from unsigned QN value to double. \ingroup invn_macro
#define INVN_UFXP_TO_DBL(value, shift) ( (double) (uint32_t)(value) / (double)(1ULL << (shift)) )
//! \def INVN_CONVERT_FLT_TO_FXP
//! Macro to convert float values from an address into QN values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_FLT_TO_FXP(fltptr, fixptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fixptr)[i] = INVN_FLT_TO_FXP((fltptr)[i], shift); }
//! \def INVN_CONVERT_FLT_TO_UFXP
//! Macro to convert float values from an address into unsigned QN values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_FLT_TO_UFXP(fltptr, fixptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fixptr)[i] = INVN_FLT_TO_UFXP((fltptr)[i], shift); }
//! \def INVN_CONVERT_DBL_TO_FXP
//! Macro to convert double values from an address into QN values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_DBL_TO_FXP(fltptr, fixptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fixptr)[i] = INVN_DBL_TO_FXP((fltptr)[i], shift); }
//! \def INVN_CONVERT_DBL_TO_UFXP
//! Macro to convert double values from an address into unsigned QN values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_DBL_TO_UFXP(fltptr, fixptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fixptr)[i] = INVN_DBL_TO_UFXP((fltptr)[i], shift); }
//! \def INVN_CONVERT_FXP_TO_FLT
//! Macro to convert QN values from an address into float values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_FXP_TO_FLT(fixptr, fltptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fltptr)[i] = INVN_FXP_TO_FLT((fixptr)[i], shift); }
//! \def INVN_CONVERT_UFXP_TO_FLT
//! Macro to convert unsigned QN values from an address into float values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_UFXP_TO_FLT(fixptr, fltptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fltptr)[i] = INVN_UFXP_TO_FLT((fixptr)[i], shift); }
//! \def INVN_CONVERT_FXP_TO_DBL
//! Macro to convert QN values from an address into double values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_FXP_TO_DBL(fixptr, fltptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fltptr)[i] = INVN_FXP_TO_DBL((fixptr)[i], shift); }
//! \def INVN_CONVERT_UFXP_TO_DBL
//! \brief Macro to convert unsigned QN values from an address into double values, and copy them to another address. \ingroup invn_macro
#define INVN_CONVERT_UFXP_TO_DBL(fixptr, fltptr, length, shift) { int32_t i; for(i=0; i<(length); ++i) (fltptr)[i] = INVN_UFXP_TO_DBL((fixptr)[i], shift); }
//=====================================//
//========= Error Definition =========//
//=====================================//
#ifndef REMOVE_INV_ERROR_T
typedef int32_t inv_error_t; /*!< Type used for error definitions. \ingroup invn_types */
#endif
//typedef int32_t mpu_error_t;
typedef int64_t mpu_time_t; /*!< Type used for mpu time. \ingroup invn_types */
// Typically I2C addresses are 8-bit, but some specifications allow for a 10-bit address
// This definition allows the length to be optimally defined for the platform
typedef uint8_t inv_i2c_addr_t; /*!< Type used for I2C addresses. \ingroup invn_types */
#ifdef __IAR_SYSTEMS_ICC__
// These are defined in standard C errno.h
#define EINVAL (22)
#define EPERM (1)
#define ENOMEM (12)
#else
#include "errno.h"
#endif
#define INVN_SUCCESS (0) /*!< Constant definition for success. \ingroup invn_types */
#define INVN_ERROR_BASE (0x20) /*!< Constant definition for basic error. Value is \b 32 \ingroup invn_types */
#define INVN_ERROR (INVN_ERROR_BASE) /*!< Constant definition for error. Value is \b 32 \ingroup invn_types */
#define INVN_ERROR_FEATURE_NOT_ENABLED (EPERM) /*!< Constant definition for feature not enabled error. \ingroup invn_types */
#define INVN_ERROR_FEATURE_NOT_IMPLEMENTED (INVN_ERROR_BASE + 4) /*!< Constant definition for feature not implemented error. \ingroup invn_types */
#define INVN_ERROR_INVALID_PARAMETER (EINVAL) /*!< Constant definition for invalid parameter error. \ingroup invn_types */
#define INVN_ERROR_FILE_OPEN (INVN_ERROR_BASE + 14) /*!< Constant definition for opening file error. \ingroup invn_types */
#define INVN_ERROR_FILE_READ (INVN_ERROR_BASE + 15) /*!< Constant definition for reading file error. \ingroup invn_types */
#define INVN_ERROR_FILE_WRITE (INVN_ERROR_BASE + 16) /*!< Constant definition for writing file error. \ingroup invn_types */
#define INVN_ERROR_INVALID_CONFIGURATION (INVN_ERROR_BASE + 17) /*!< Constant definition for invalid configuration error. \ingroup invn_types */
/* Serial Communication */
#define INVN_ERROR_SERIAL_OPEN_ERROR (INVN_ERROR_BASE + 21) /*!< Constant definition for serial open error. \ingroup invn_types */
#define INVN_ERROR_SERIAL_READ (INVN_ERROR_BASE + 22) /*!< Constant definition for serial read error. \ingroup invn_types */
#define INVN_ERROR_SERIAL_WRITE (INVN_ERROR_BASE + 23) /*!< Constant definition for serial write error. \ingroup invn_types */
/* Fifo */
#define INVN_ERROR_FIFO_OVERFLOW (INVN_ERROR_BASE + 30) /*!< Constant definition for fifo overflow error. \ingroup invn_types */
#define INVN_ERROR_FIFO_FOOTER (INVN_ERROR_BASE + 31) /*!< Constant definition for fifo footer error. \ingroup invn_types */
#define INVN_ERROR_FIFO_READ_COUNT (INVN_ERROR_BASE + 32) /*!< Constant definition for fifo read count error. \ingroup invn_types */
#define INVN_ERROR_FIFO_READ_DATA (INVN_ERROR_BASE + 33) /*!< Constant definition for fifo read data error. \ingroup invn_types */
/* OS interface errors */
#define INVN_ERROR_OS_BAD_HANDLE (INVN_ERROR_BASE + 61) /*!< Constant definition for OS bad handle error. \ingroup invn_types */
#define INVN_ERROR_OS_CREATE_FAILED (INVN_ERROR_BASE + 62) /*!< Constant definition for OS create failed error. \ingroup invn_types */
#define INVN_ERROR_OS_LOCK_FAILED (INVN_ERROR_BASE + 63) /*!< Constant definition for OS lock failed error. \ingroup invn_types */
/* Warning */
#define INVN_WARNING_SEMAPHORE_TIMEOUT (INVN_ERROR_BASE + 86) /*!< Constant definition for semaphore timeout warning. \ingroup invn_types */
#endif // INVN_COMMON_INVN_TYPES_H_

View File

@@ -0,0 +1,156 @@
/*
$License:
Copyright (C) 2018 InvenSense Corporation, All Rights Reserved.
$
*/
#ifndef _INVN_ALGO_AGM_H_
#define _INVN_ALGO_AGM_H_
#include "invn_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \defgroup AGM AGM
* \brief Algorithm that provides device orientation. Algorithm inputs are raw Accelerometer, Gyroscope and Magnetometer data.
* Algorithm outputs: calibrated sensor and 9-axis sensor fusion.
* \warning supported sampling frequency [50 Hz-1000 Hz]
* \warning supported gyroscope FSR [250 dps, 500 dps, 1000 dps, 2000 dps, 4000 dps]
* \warning supported accelerometer FSR [1 g, 2 g, 4 g, 8 g, 16 g]
*/
#define INVN_ALGO_AGM_INPUT_MASK_ACC 1 ///< Raw Accel update mask
#define INVN_ALGO_AGM_INPUT_MASK_GYR 2 ///< Raw Gyro update mask
#define INVN_ALGO_AGM_INPUT_MASK_MAG 4 ///< Raw Mag update mask
#define INVN_ALGO_AGM_OUTPUT_MASK_ACCEL_CAL 1 ///< Accel cal output update mask
#define INVN_ALGO_AGM_OUTPUT_MASK_GYRO_CAL 2 ///< Gyro cal output update mask
#define INVN_ALGO_AGM_OUTPUT_MASK_MAG_CAL 4 ///< Mag cal output update mask
#define INVN_ALGO_AGM_OUTPUT_MASK_QUAT_AG 8 ///< Game Rotation Vector (Accel and Gyro Fusion) output update mask
#define INVN_ALGO_AGM_OUTPUT_MASK_QUAT_AGM 16 ///< Rotation Vector (Accel, Gyro and Magnetometer Fusion) output update mask
#define INVN_ALGO_AGM_OUTPUT_MASK_GRAVITY 32 ///< Gravity vector output update mask
#define INVN_ALGO_AGM_OUTPUT_MASK_LINEARACC 64 ///< Linear acceleration vector output update mask
/* Forward declarations */
struct inv_icm426xx;
/*! \struct InvnAlgoAGMInput
* AGM input structure (raw data) \ingroup AGM
*/
typedef struct
{
int32_t mask; /*!< mask to specify updated inputs. */
int64_t sRimu_time_us; /*!< timestamp \f$ [\mu s]\f$ of raw accel and gyro */
int32_t sRacc_data[3]; /*!< raw accelerometer in high resolution mode. Expect Full Scale Value coded on 20 bit (i.e. +/- FSR g = 1<<19 LSB) */
int32_t sRgyr_data[3]; /*!< raw gyroscope in high resolution mode. Expect Full Scale Value coded on 20 bit (i.e. +/- FSR dps = 1<<19 LSB) */
int16_t sRtemp_data; /*!< raw temperature */
int64_t sRmag_time_us; /*!< timestamp of raw mag */
int32_t sRmag_data[3]; /*!< raw mag */
} InvnAlgoAGMInput;
/*! \struct InvnAlgoAGMOutput
* AGM output structure (calibrated sensors and fusion output) \ingroup AGM
*/
typedef struct
{
int32_t mask; /*!< mask to specify updated outputs */
int32_t acc_uncal_q16[3]; /*!< uncalibrated accelerometer (1 g = 1<<16) */
int32_t acc_cal_q16[3]; /*!< calibrated accelerometer (1 g = 1<<16) */
int32_t acc_bias_q16[3]; /*!< accelerometer bias (1 g = 1<<16)*/
int8_t acc_accuracy_flag; /*!< accelerometer accuracy from 0(non calibrated) to 3(well calibrated) */
int32_t gyr_uncal_q16[3]; /*!< uncalibrated gyroscope (1 dps = 1<<16) */
int32_t gyr_cal_q16[3]; /*!< calibrated gyroscope (1 dps = 1<<16) */
int32_t gyr_bias_q16[3]; /*!< gyro bias (1 dps = 1<<16)*/
int8_t gyr_accuracy_flag; /*!< gyro accuracy, from 0(non calibrated) to 3(well calibrated) */
int32_t mag_uncal_q16[3]; /*!< uncalibrated magnetometer (1uT = 1<<16) */
int32_t mag_cal_q16[3]; /*!< calibrated magnetometer (1uT = 1<<16) */
int32_t mag_bias_q16[3]; /*!< magnetometer bias (1uT = 1<<16) */
int8_t mag_accuracy_flag; /*!< magnetometer accuracy, from 0(non calibrated) to 3(well calibrated) */
int32_t grv_quat_q30[4]; /*!< 6-axis (accel and gyro fusion) quaternion */
int32_t rv_quat_q30[4]; /*!< 9-axis (accel, gyro and magnetometer fusion) quaternion */
int32_t rv_accuracy_q27; /*!< 9-axis (accel, gyro and magnetometer fusion) 3\sigma accuracy in rad */
int32_t gravity_q16[3]; /*!< gravity estimation in sensor frame */
int32_t linear_acc_q16[3]; /*!< linear acceleration estimation in sensor frame */
int32_t temp_degC_q16; /*!< temperature (1 \f$ [^{\circ}C]\f$ = 1<<16)*/
} InvnAlgoAGMOutput;
/*! \struct InvnAlgoAGMConfig
* AGM configuration structure (sensor related settings) \ingroup AGM
*/
typedef struct
{
int32_t * acc_bias_q16; /*!< Previously stored accel bias pointer. If pointer is NULL or 0, offset will be set to { 0, 0, 0} */
int32_t * gyr_bias_q16; /*!< Previously stored gyro bias pointer. If pointer is NULL or 0, offset will be set to { 0, 0, 0} */
int32_t * mag_bias_q16; /*!< mag_bias_q16 Previously stored mag bias pointer If pointer is NULL or 0, offset will be set to { 0, 0, 0} */
int8_t acc_accuracy; /*!< Previously stored accelerometer bias accuracy (0 to 3) */
int8_t gyr_accuracy; /*!< Previously stored gyroscope bias accuracy (0 to 3) */
int8_t mag_accuracy; /*!< Previously stored magnetometer bias accuracy (0 to 3) */
int32_t acc_fsr; /*!< accelerometer full scale range [g] */
int32_t gyr_fsr; /*!< gyroscope full scale range [dps] */
uint32_t acc_odr_us; /*!< accelerometer output data rate in \f$ [\mu s]\f$ */
uint32_t gyr_odr_us; /*!< gyroscope output data rate \f$ [\mu s]\f$ */
int32_t mag_sc_q16; /*!< magnetometer sensitivity (uT/LSB, e.g. mag_uT = (mag_sc_q16 * raw_mag_LSB)/65536) */
uint32_t mag_odr_us; /*!< magnetometer output data rate \f$ [\mu s]\f$ */
int32_t temp_sensitivity; /*!< temperature sensitivity in q30 (if temperature(\f$ ^{\circ}C \f$) = LSB * k + z, then temp_sensitivity = k) */
int32_t temp_offset; /*!< temperature offset in q16 (if temperature(\f$ ^{\circ}C \f$) = LSB * k + z, then temp_offset = z) */
} InvnAlgoAGMConfig;
/*!
* \brief Return library version x.y.z-suffix as a char array
* \retval library version a char array "x.y.z-suffix"
* \ingroup AGM
*/
const char * invn_algo_agm_version(void);
/*!
* \brief Initializes algorithms with default parameters and reset states.
* (\icm_device[in] Invensense ICM426XX device pointer. Only when gyro assisted is enabled.)
* \config[in] algo init parameters structure.
* \return initialization success indicator.
* \retval 0 Success
* \retval 1 Fail
* \ingroup AGM
*/
#ifdef WITH_GYRO_ASSIST
uint8_t invn_algo_agm_init_a(struct inv_icm426xx * icm_device, const InvnAlgoAGMConfig * config);
#else
uint8_t invn_algo_agm_init(const InvnAlgoAGMConfig * config);
#endif
/*!
* \brief Sets algo config structure.
* \config[in] config structure of the algo.
* \ingroup AGM
*/
void invn_algo_agm_set_config(const InvnAlgoAGMConfig * config);
/*!
* \brief Performs algorithm computation.
* \in inputs algorithm input. Input mask (inputs->mask) should be set with respect to new sensor data in InvnAlgoAGMInput.
* \out outputs algorithm output. Output mask (outputs->mask) reports updated outputs in InvnAlgoAGMOutput.
* \ingroup AGM
*/
void invn_algo_agm_process(const InvnAlgoAGMInput *inputs, InvnAlgoAGMOutput *outputs);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,324 @@
/*******************************************************************************
* @file app_raw.c
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#include "sdk_config.h"
#include "app_raw.h"
#include "inv_imu_extfunc.h"
#include "inv_imu_driver.h"
#include "ble_nus.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "app_util_platform.h"
#include "main.h"
#include "meas_pd_48.h"
#include <cmd_parse.h>
/*
* Print raw data or scaled data
* 0 : print raw accel, gyro and temp data
* 1 : print scaled accel, gyro and temp data in g, dps and degree Celsius
*/
#define SCALED_DATA_G_DPS 0
/* --------------------------------------------------------------------------------------
* Static and extern variables
* -------------------------------------------------------------------------------------- */
/* Just a handy variable to handle the IMU object */
static struct inv_imu_device icm_driver;
uint8_t imu_bin_buffer[BLE_NUS_MAX_DATA_LEN];
/*
* ICM mounting matrix
* Coefficients are coded as Q30 integer
*/
#if (SM_BOARD_REV == SM_REVB_DB) /* when DB or EVB are used */
static int32_t icm_mounting_matrix[9] = { 0, -(1<<30), 0,
(1<<30), 0, 0,
0, 0, (1<<30) };
#else /* For SmartMotion */
static int32_t icm_mounting_matrix[9] = {(1<<30), 0, 0,
0, (1<<30), 0,
0, 0, (1<<30)};
#endif
extern bool custom_add_data;
extern bool motion_raw_data_enabled;
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
extern bool go_pdread;
extern which_cmd_t cmd_type_t;
uint16_t ssp_data[6]={0,};
extern bool info4; //cmd_parse
extern volatile uint16_t info_imu[6];
/* --------------------------------------------------------------------------------------
* static function declaration
* -------------------------------------------------------------------------------------- */
static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3]);
/* --------------------------------------------------------------------------------------
* Functions definition
* -------------------------------------------------------------------------------------- */
int setup_imu_device(struct inv_imu_serif *icm_serif)
{
int rc = 0;
uint8_t who_am_i;
/* Init device */
rc = inv_imu_init(&icm_driver, icm_serif, imu_callback);
if (rc != INV_ERROR_SUCCESS) {
printf("!!! ERROR : Failed to initialize IMU!\r\n");
return rc;
}
/* Check WHOAMI */
rc = inv_imu_get_who_am_i(&icm_driver, &who_am_i);
if (rc != INV_ERROR_SUCCESS) {
printf("!!! ERROR : Failed to read whoami!\r\n");
return rc;
}
if (who_am_i != ICM_WHOAMI) {
printf("!!! ERROR : Bad WHOAMI value! Read 0x%02x, expected 0x%02x\r\n", who_am_i, ICM_WHOAMI);
return INV_ERROR;
}
return rc;
}
int configure_imu_device(void)
{
int rc = 0;
if (!USE_FIFO)
rc |= inv_imu_configure_fifo(&icm_driver, INV_IMU_FIFO_DISABLED);
if (USE_HIGH_RES_MODE) {
rc |= inv_imu_enable_high_resolution_fifo(&icm_driver);
} else {
rc |= inv_imu_set_accel_fsr(&icm_driver, ACCEL_CONFIG0_FS_SEL_4g);
rc |= inv_imu_set_gyro_fsr(&icm_driver, GYRO_CONFIG0_FS_SEL_2000dps);
}
if (USE_LOW_NOISE_MODE) {
rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_800_HZ);
rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_800_HZ);
rc |= inv_imu_enable_accel_low_noise_mode(&icm_driver);
} else {
rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_100_HZ);
rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_100_HZ);
rc |= inv_imu_enable_accel_low_power_mode(&icm_driver);
}
rc |= inv_imu_enable_gyro_low_noise_mode(&icm_driver);
if (!USE_FIFO)
inv_imu_sleep_us(GYR_STARTUP_TIME_US);
return rc;
}
int get_imu_data(void)
{
#if USE_FIFO
return inv_imu_get_data_from_fifo(&icm_driver);
#else
return inv_imu_get_data_from_registers(&icm_driver);
#endif
}
#if SCALED_DATA_G_DPS
static void get_accel_and_gyr_fsr(int16_t * accel_fsr_g, int16_t * gyro_fsr_dps)
{
ACCEL_CONFIG0_FS_SEL_t accel_fsr_bitfield;
GYRO_CONFIG0_FS_SEL_t gyro_fsr_bitfield;
inv_imu_get_accel_fsr(&icm_driver, &accel_fsr_bitfield);
switch(accel_fsr_bitfield) {
case ACCEL_CONFIG0_FS_SEL_2g: *accel_fsr_g = 2;
break;
case ACCEL_CONFIG0_FS_SEL_4g: *accel_fsr_g = 4;
break;
case ACCEL_CONFIG0_FS_SEL_8g: *accel_fsr_g = 8;
break;
case ACCEL_CONFIG0_FS_SEL_16g: *accel_fsr_g = 16;
break;
default: *accel_fsr_g = -1;
}
inv_imu_get_gyro_fsr(&icm_driver, &gyro_fsr_bitfield);
switch(gyro_fsr_bitfield) {
case GYRO_CONFIG0_FS_SEL_250dps: *gyro_fsr_dps = 250;
break;
case GYRO_CONFIG0_FS_SEL_500dps: *gyro_fsr_dps = 500;
break;
case GYRO_CONFIG0_FS_SEL_1000dps: *gyro_fsr_dps = 1000;
break;
case GYRO_CONFIG0_FS_SEL_2000dps: *gyro_fsr_dps = 2000;
break;
default: *gyro_fsr_dps = -1;
}
}
#endif
void imu_callback(inv_imu_sensor_event_t *event)
{
int32_t accel[3], gyro[3];
#if SCALED_DATA_G_DPS
float accel_g[3];
float gyro_dps[3];
float temp_degc;
int16_t accel_fsr_g, gyro_fsr_dps;
#endif
#if USE_FIFO
static uint64_t last_fifo_timestamp = 0;
static uint32_t rollover_num = 0;
// Handle rollover
if (last_fifo_timestamp > event->timestamp_fsync)
rollover_num++;
last_fifo_timestamp = event->timestamp_fsync;
// Compute timestamp in us
timestamp = event->timestamp_fsync + rollover_num * UINT16_MAX;
timestamp *= inv_imu_get_fifo_timestamp_resolution_us_q24(&icm_driver);
timestamp /= (1UL << 24);
if (icm_driver.fifo_highres_enabled) {
accel[0] = (((int32_t)event->accel[0] << 4)) | event->accel_high_res[0];
accel[1] = (((int32_t)event->accel[1] << 4)) | event->accel_high_res[1];
accel[2] = (((int32_t)event->accel[2] << 4)) | event->accel_high_res[2];
gyro[0] = (((int32_t)event->gyro[0] << 4)) | event->gyro_high_res[0];
gyro[1] = (((int32_t)event->gyro[1] << 4)) | event->gyro_high_res[1];
gyro[2] = (((int32_t)event->gyro[2] << 4)) | event->gyro_high_res[2];
} else {
accel[0] = event->accel[0];
accel[1] = event->accel[1];
accel[2] = event->accel[2];
gyro[0] = event->gyro[0];
gyro[1] = event->gyro[1];
gyro[2] = event->gyro[2];
}
#else
accel[0] = event->accel[0];
accel[1] = event->accel[1];
accel[2] = event->accel[2];
gyro[0] = event->gyro[0];
gyro[1] = event->gyro[1];
gyro[2] = event->gyro[2];
// Force sensor_mask so it gets displayed below
event->sensor_mask |= (1 << INV_SENSOR_TEMPERATURE);
event->sensor_mask |= (1 << INV_SENSOR_ACCEL);
event->sensor_mask |= (1 << INV_SENSOR_GYRO);
#endif
apply_mounting_matrix(icm_mounting_matrix, accel);
apply_mounting_matrix(icm_mounting_matrix, gyro);
#if SCALED_DATA_G_DPS
/*
* Convert raw data into scaled data in g and dps
*/
get_accel_and_gyr_fsr(&accel_fsr_g, &gyro_fsr_dps);
accel_g[0] = (float)(accel[0] * accel_fsr_g) / INT16_MAX;
accel_g[1] = (float)(accel[1] * accel_fsr_g) / INT16_MAX;
accel_g[2] = (float)(accel[2] * accel_fsr_g) / INT16_MAX;
gyro_dps[0] = (float)(gyro[0] * gyro_fsr_dps) / INT16_MAX;
gyro_dps[1] = (float)(gyro[1] * gyro_fsr_dps) / INT16_MAX;
gyro_dps[2] = (float)(gyro[2] * gyro_fsr_dps) / INT16_MAX;
if (USE_HIGH_RES_MODE || !USE_FIFO)
temp_degc = 25 + ((float)event->temperature / 128);
else
temp_degc = 25 + ((float)event->temperature / 2);
/*
* Output scaled data on UART link
*/
if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO))
printf("%u: %.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f, \t%.3f\r\n",
(uint32_t)timestamp,
accel_g[0], accel_g[1], accel_g[2],
temp_degc,
gyro_dps[0], gyro_dps[1], gyro_dps[2]);
#else
/*
* Output raw data on UART link
*/
if (event->sensor_mask & (1 << INV_SENSOR_ACCEL) && event->sensor_mask & (1 << INV_SENSOR_GYRO) || motion_raw_data_enabled)
{
motion_raw_data_enabled = false;
if (info4 == true)
{
info_imu[0] = (uint16_t)accel[0];
info_imu[1] = (uint16_t)accel[1];
info_imu[2] = (uint16_t) accel[2];
info_imu[3] = (uint16_t)gyro[0];
info_imu[4] = (uint16_t) gyro[1];
info_imu[5] = (uint16_t)gyro[2];
go_pdread = true;
}
else if(cmd_type_t == CMD_UART) {
printf("Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]);
} else if(cmd_type_t == CMD_BLE) {
//sprintf(ble_tx_buffer, "Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]);
ssp_data[0] = (uint16_t)accel[0];
ssp_data[1] = (uint16_t)accel[1];
ssp_data[2] = (uint16_t)accel[2];
ssp_data[3] = (uint16_t)gyro[0];
ssp_data[4] = (uint16_t)gyro[1];
ssp_data[5] = (uint16_t)gyro[2];
format_data(imu_bin_buffer, "rsp:", ssp_data,12);
printf("Tp%d,%d,%d,%d,%d,%d\r\n\r\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]);
binary_tx_handler(imu_bin_buffer,8);
if(custom_add_data==true)
{
custom_add_data = false;
}
else{
//data_tx_handler(ble_tx_buffer);
}
}
}
#endif
}
/* --------------------------------------------------------------------------------------
* Static functions definition
* -------------------------------------------------------------------------------------- */
static void apply_mounting_matrix(const int32_t matrix[9], int32_t raw[3])
{
unsigned i;
int64_t data_q30[3];
for(i = 0; i < 3; i++) {
data_q30[i] = ((int64_t)matrix[3*i+0] * raw[0]);
data_q30[i] += ((int64_t)matrix[3*i+1] * raw[1]);
data_q30[i] += ((int64_t)matrix[3*i+2] * raw[2]);
}
raw[0] = (int32_t)(data_q30[0]>>30);
raw[1] = (int32_t)(data_q30[1]>>30);
raw[2] = (int32_t)(data_q30[2]>>30);
}

View File

@@ -0,0 +1,75 @@
/*******************************************************************************
* @file app_raw.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _APP_RAW_H_
#define _APP_RAW_H_
#include "sdk_config.h"
#include <stdint.h>
#include "inv_imu_transport.h"
#include "inv_imu_defs.h"
#include "inv_imu_driver.h"
/*** Example configuration ***/
/*
* Select communication link between SmartMotion and IMU
*/
#define SERIF_TYPE UI_I2C
/*
* Set power mode flag
* Set this flag to run example in low-noise mode.
* Reset this flag to run example in low-power mode.
* Note: low-noise mode is not available with sensor data frequencies less than 12.5Hz.
*/
#define USE_LOW_NOISE_MODE 1
/*
* Select Fifo resolution Mode (default is low resolution mode)
* Low resolution mode: 16 bits data format
* High resolution mode: 20 bits data format
* Warning: Enabling High Res mode will force FSR to 16g and 2000dps
*/
#define USE_HIGH_RES_MODE 0
/*
* Select to use FIFO or to read data from registers
*/
#define USE_FIFO 0
/**
* \brief This function is in charge of reseting and initializing IMU device. It should
* be successfully executed before any access to IMU device.
*
* \return 0 on success, negative value on error.
*/
int setup_imu_device(struct inv_imu_serif *icm_serif);
/**
* \brief This function configures the device in order to output gyro and accelerometer.
* \return 0 on success, negative value on error.
*/
int configure_imu_device(void);
/**
* \brief This function extracts data from the IMU FIFO.
* \return 0 on success, negative value on error.
*/
int get_imu_data(void);
/**
* \brief This function is the custom handling packet function.
* \param[in] event structure containing sensor data from one packet
*/
void imu_callback(inv_imu_sensor_event_t *event);
#endif /* !_APP_RAW_H_ */

View File

@@ -0,0 +1,189 @@
/*******************************************************************************
* @file app_raw_main.c
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#include "sdk_config.h"
#include "app_raw.h"
#include "app_raw_main.h"
#include "RingBuffer.h"
#include "inv_imu_driver.h"
#include "system_interface.h"
/* std */
#include <stdio.h>
#include "nrf.h"
#include "app_error.h"
#include "boards.h"
#include "nrfx_gpiote.h"
#include "nrf_delay.h"
#include "app_util_platform.h"
#include "meas_pd_48.h"
#include <cmd_parse.h>
#include "i2c_manager.h"
/* --------------------------------------------------------------------------------------
* Global variables
* -------------------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------------------
* Static variables
* -------------------------------------------------------------------------------------- */
/* Flag set from IMU device irq handler */
static volatile int irq_from_device;
/* --------------------------------------------------------------------------------------
* Forward declaration
* -------------------------------------------------------------------------------------- */
static int setup_mcu(struct inv_imu_serif *icm_serif);
/*!
* @brief Sensor general interrupt handler, calls specific handlers.
*
* This function is called when an external interrupt is triggered by the sensor,
* checks interrupt registers of InvenSense Sensor to determine the source and type of interrupt
* and calls the specific interrupt handler accordingly.
*
* @param[in] NULL
*
* @param[out] NULL
*
* @return NULL
*
*/
static void inv_gpio_sensor_interrupt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
irq_from_device = 1;
}
void inv_gpio_sensor_irq_init(void)
{
ret_code_t err_code;
/* Initialize int pin */
if (!nrfx_gpiote_is_init())
{
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
in_config.pull = NRF_GPIO_PIN_PULLUP;
err_code = nrfx_gpiote_in_init(ICM42670_INT1_PIN, &in_config, inv_gpio_sensor_interrupt_handler);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_event_enable(ICM42670_INT1_PIN, true);
}
void inv_gpio_sensor_irq_uninit(void)
{
nrfx_gpiote_in_event_disable(ICM42670_INT1_PIN);
nrfx_gpiote_in_uninit(ICM42670_INT1_PIN);
/* Initialize int pin */
if (nrfx_gpiote_is_init())
{
nrfx_gpiote_uninit();
}
}
/* --------------------------------------------------------------------------------------
* Main
* -------------------------------------------------------------------------------------- */
int icm42670_init(void)
{
int rc = 0;
struct inv_imu_serif icm_serif;
rc |= setup_mcu(&icm_serif);
rc |= setup_imu_device(&icm_serif);
rc |= configure_imu_device();
if(rc != 0){
printf("!!!error during initialization\r\n");
return -1;
}
inv_gpio_sensor_irq_init();
return rc;
}
void icm42670_main(void)
{
int rc = 0;
hw_i2c_init_once();
/* Poll device for data */
if (irq_from_device) {
rc = get_imu_data();
if(rc < 0) {
printf("error while getting data\r\n");
}
irq_from_device = 0;
}
}
/* --------------------------------------------------------------------------------------
* Functions definitions
* -------------------------------------------------------------------------------------- */
/*
* This function initializes MCU on which this software is running.
* It configures:
* - a UART link used to print some messages
* - interrupt priority group and GPIO so that MCU can receive interrupts from IMU
* - a microsecond timer requested by IMU driver to compute some delay
* - a microsecond timer used to get some timestamps
* - a serial link to communicate from MCU to IMU
*/
static int setup_mcu(struct inv_imu_serif *icm_serif)
{
int rc = 0;
/* Initialize serial interface between MCU and IMU */
icm_serif->context = 0; /* no need */
icm_serif->read_reg = inv_io_hal_read_reg;
icm_serif->write_reg = inv_io_hal_write_reg;
icm_serif->max_read = 1024*32; /* maximum number of bytes allowed per serial read */
icm_serif->max_write = 1024*32; /* maximum number of bytes allowed per serial write */
icm_serif->serif_type = SERIF_TYPE;
rc |= inv_io_hal_init(icm_serif);
return rc;
}
/* --------------------------------------------------------------------------------------
* Extern functions definition
* -------------------------------------------------------------------------------------- */
/* Sleep implementation */
void inv_imu_sleep_us(uint32_t us)
{
nrf_delay_us(us);
}
/* Get time implementation */
uint64_t inv_imu_get_time_us(void)
{
return NRF_RTC1->COUNTER;
}

View File

@@ -0,0 +1,18 @@
/*******************************************************************************
* @file app_raw_main.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _APP_RAW_MAIN_H_
#define _APP_RAW_MAIN_H_
#include "sdk_config.h"
int icm42670_init(void);
void icm42670_main(void);
int icm42670_uninit(void);
#endif /* !_APP_RAW_MAIN_H_ */

View File

@@ -0,0 +1,228 @@
/*******************************************************************************
* @file system_interface.c
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
/* board driver */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include "nrf.h"
#include "app_error.h"
#include "boards.h"
#include "nrfx_gpiote.h"
#include "nrfx_twi.h"
#include "system_interface.h"
#include "nrf_delay.h"
#include "meas_pd_48.h"
/* I2C number and slave address for INV device */
#define ICM_I2C_ADDR 0x68
#define INV_MAX_SERIAL_WRITE 16
/* TWI instance. */
const nrfx_twi_t m_twi_icm42670 = NRFX_TWI_INSTANCE(ICM42670_I2C_INSTANCE);
void inv_i2c_master_uninitialize(void){
nrfx_twi_disable(&m_twi_icm42670);
nrfx_twi_uninit(&m_twi_icm42670);
}
static void inv_i2c_master_initialize(void){
ret_code_t err_code;
const nrfx_twi_config_t twi_icm42670_config = {
.scl = ICM42670_I2C_SCL_PIN,
.sda = ICM42670_I2C_SDA_PIN,
.frequency = NRF_TWI_FREQ_100K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
};
err_code = nrfx_twi_init(&m_twi_icm42670, &twi_icm42670_config, NULL, NULL);
APP_ERROR_CHECK(err_code);
nrfx_twi_enable(&m_twi_icm42670);
}
uint32_t icm42670_twi_tx( uint8_t device_id,
uint8_t const * p_data,
uint8_t length,
bool no_stop)
{
ret_code_t ret;
ret = nrfx_twi_tx(&m_twi_icm42670, device_id, p_data, length, no_stop);
return ret;
}
uint32_t icm42670_twi_rx( uint8_t device_id,
uint8_t * p_data,
uint8_t length)
{
ret_code_t ret;
ret = nrfx_twi_rx(&m_twi_icm42670, device_id, p_data, length);
return ret;
}
static unsigned long inv_i2c_master_read_register(unsigned char Address, unsigned char RegisterAddr, unsigned short RegisterLen, unsigned char *RegisterValue){
//ret_code_t ret;
uint32_t ret;
uint8_t addr8 = (uint8_t)RegisterAddr;
ret = icm42670_twi_tx(Address, &addr8, 1, true);
if(ret != NRF_SUCCESS) {
ret = icm42670_twi_tx(Address, &addr8, 1, true);
if(ret != NRF_SUCCESS) {
printf("ERR! i2c read-1\r\n");
}
}
ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen);
if(ret != NRF_SUCCESS) {
ret = icm42670_twi_rx(Address, RegisterValue, RegisterLen);
if(ret != NRF_SUCCESS) {
printf("ERR! i2c read-2\r\n");
}
}
return ret;
}
static unsigned long inv_i2c_master_write_register(unsigned char Address, unsigned char RegisterAddr, unsigned short RegisterLen, const unsigned char *RegisterValue){
uint32_t ret;
uint8_t buffer[1 + INV_MAX_SERIAL_WRITE]; /* Addr + data */
buffer[0] = (uint8_t)RegisterAddr;
memcpy(buffer+1, RegisterValue, RegisterLen);
ret = icm42670_twi_tx(Address, buffer, RegisterLen+1, false);
if(ret != NRF_SUCCESS) {
ret = icm42670_twi_tx(Address, buffer, RegisterLen+1, false);
if(ret != NRF_SUCCESS) {
printf("ERR! i2c write\r\n");
}
}
return ret;
}
int inv_io_hal_init(struct inv_imu_serif *serif)
{
switch (serif->serif_type) {
case UI_SPI4:
{
break;
}
case UI_I2C:
inv_i2c_master_initialize();
break;
default:
return -1;
}
return 0;
}
int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuffer, uint32_t rlen)
{
switch (serif->serif_type) {
case UI_SPI4:
return 0;
case UI_I2C:
return inv_i2c_master_read_register(ICM_I2C_ADDR, reg, rlen, rbuffer);
default:
return -1;
}
}
int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t * wbuffer, uint32_t wlen)
{
switch (serif->serif_type) {
case UI_SPI4:
return 0;
case UI_I2C:
return inv_i2c_master_write_register(ICM_I2C_ADDR, reg, wlen, wbuffer);
default:
return -1;
}
}
uint8_t cat_read(uint8_t device_id, uint8_t address, uint8_t *data)
{
uint8_t read_data = 0;
char adata[8];
ret_code_t err_code;
//address = 1|(address<<1);
address = (address & 0xFF);
err_code = nrfx_twi_tx(&m_twi_icm42670, device_id, &address, 1, true);
if (err_code != NRF_SUCCESS) {
// Handle error
// return;
}
err_code = nrfx_twi_rx(&m_twi_icm42670, device_id, data, 8);
if (err_code != NRF_SUCCESS) {
// Handle error
return 0;
}
read_data = data[0];
memcpy(adata,data,8);
printf("Data %s . \r\n", adata);
return read_data;
}
void cat_write(uint8_t device_id, uint8_t address, uint8_t *data){
uint8_t buffer[7]={0x00,0x00,0x00,0x00,0x00,0x00,0x00};
address = (address & 0xFF);
buffer[0] = (address);
//buffer[1] =(data & 0xFF);
memcpy(buffer+1,data,6);
ret_code_t err_code;
//err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, 0x00, 1, false);
err_code = nrfx_twi_tx(&m_twi_icm42670, device_id, buffer, 2, false);
// err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, buffer, 2, false);
// nrfx_twi_rx(&m_twi_icm42670, device_id, p_data, length);
// nrfx_twi_tx(&m_twi_icm42670, device_id, p_data, length, no_stop);
printf("Data %x %x %x %x. \r\n", buffer[0], buffer[1], buffer[2], buffer[3]);
//err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, buffer, 6, false);
if (err_code != NRF_SUCCESS) {
printf("TWI Error.");
}
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* @file system_interface.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _SYSTEM_INTERFACE_H_
#define _SYSTEM_INTERFACE_H_
#include "inv_imu_transport.h"
#include <stdbool.h>
/* TODO: Move that somewhere else */
#ifndef TO_MASK
#define TO_MASK(a) (1U << (unsigned)(a))
#endif
#define ICM42670_I2C_INSTANCE 0 /**< I2C instance index. */
#define ICM42670_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15)
#define ICM42670_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14)
#define ICM42670_INT1_PIN NRF_GPIO_PIN_MAP(1,13)
#define ICM42670_INT2_PIN NRF_GPIO_PIN_MAP(0,26)
uint32_t icm42670_twi_tx( uint8_t device_id,
uint8_t const * p_data,
uint8_t length,
bool no_stop);
uint32_t icm42670_twi_rx( uint8_t device_id,
uint8_t * p_data,
uint8_t length);
uint8_t cat_read (uint8_t device_id, uint8_t address, uint8_t *data);
void cat_write (uint8_t device_id, uint8_t address, uint8_t *data);
void inv_i2c_master_uninitialize(void);
int inv_io_hal_init(struct inv_imu_serif *serif);
int inv_io_hal_read_reg(struct inv_imu_serif *serif, uint8_t reg, uint8_t * rbuffer, uint32_t rlen);
int inv_io_hal_write_reg(struct inv_imu_serif *serif, uint8_t reg, const uint8_t * wbuffer, uint32_t wlen);
#endif /* !_SYSTEM_INTERFACE_H_ */

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* @file imu_stub.c
* @brief Stub implementations for IMU and meas_config functions
* @note TODO: Replace with real implementations
******************************************************************************/
#include <stdint.h>
#include <stdbool.h>
/*==============================================================================
* IMU STUBS
*============================================================================*/
volatile bool g_imu_active = false;
int imu_read_direct(void)
{
/* TODO: Implement direct I2C register read + BLE send */
return -1;
}
int imu_read_cached(void)
{
/* TODO: Implement cached memory read + BLE send */
return -1;
}
void imu_active_timer_start(void)
{
/* TODO: Start 1-sec IMU active streaming timer */
}
void imu_active_timer_stop(void)
{
/* TODO: Stop 1-sec IMU active streaming timer */
}
/*==============================================================================
* MEASUREMENT CONFIG STUBS
*============================================================================*/
static uint8_t s_freq_idx = 0;
static uint8_t s_cycles = 5;
static uint8_t s_avg_count = 1;
uint8_t meas_config_get_freq_idx(void) { return s_freq_idx; }
uint8_t meas_config_get_cycles(void) { return s_cycles; }
uint8_t meas_config_get_avg_count(void) { return s_avg_count; }
void meas_config_set_freq_idx(uint8_t v) { s_freq_idx = v; }
void meas_config_set_cycles(uint8_t v) { s_cycles = v; }
void meas_config_set_avg_count(uint8_t v) { s_avg_count = v; }
void meas_config_save(void)
{
/* TODO: Save measurement config to FDS */
}

View File

@@ -0,0 +1,23 @@
#ifndef IMU_STUB_H
#define IMU_STUB_H
#include <stdint.h>
#include <stdbool.h>
/* IMU */
extern volatile bool g_imu_active;
int imu_read_direct(void);
int imu_read_cached(void);
void imu_active_timer_start(void);
void imu_active_timer_stop(void);
/* Measurement Config */
uint8_t meas_config_get_freq_idx(void);
uint8_t meas_config_get_cycles(void);
uint8_t meas_config_get_avg_count(void);
void meas_config_set_freq_idx(uint8_t v);
void meas_config_set_cycles(uint8_t v);
void meas_config_set_avg_count(uint8_t v);
void meas_config_save(void);
#endif

View File

@@ -0,0 +1,168 @@
/*******************************************************************************
* @file ir_i2c.c
* @brief
******************************************************************************/
/* board driver */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include "nrf.h"
#include "app_error.h"
#include "boards.h"
#include "nrfx_gpiote.h"
#include "nrfx_twi.h"
#include "nrf_drv_twi.h"
#include "nrf_delay.h"
#include "ir_i2c.h"
#include "debug_print.h"
/* I2C number and slave address for DS39305272 */
#define LED_1_I2C_ADDR 0x50
#define AD5272_MAX_SERIAL_WRITE 16
int16_t read_from_DS3930 = 0;
uint16_t data_160_to_write = 0;
static volatile bool m_xfer_done = false;
/* TWI instance. */
//const nrfx_twi_t m_twi_ir = NRFX_TWI_INSTANCE(IR_I2C_INSTANCE);
const nrf_drv_twi_t m_twi_ir = NRF_DRV_TWI_INSTANCE(IR_I2C_INSTANCE);
//void twi_handler(nrfx_twi_evt_t const * p_event, void * p_context)
//{
// m_xfer_done = true;
//}
//void ir_irq_init(void){
// ret_code_t err_code;
// /* Initialize int pin */
// if (!nrfx_gpiote_is_init())
// {
// err_code = nrfx_gpiote_init();
// APP_ERROR_CHECK(err_code);
// }
// nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
// in_config.pull = NRF_GPIO_PIN_PULLDOWN;
// err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
// APP_ERROR_CHECK(err_code);
// nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
//}
//void ir_irq_uninit(void){
// nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
// nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
//}
void ir_i2c_uninit(void){
nrf_drv_twi_disable(&m_twi_ir);
nrf_drv_twi_uninit(&m_twi_ir);
//
}
void ir_i2c_init(void){
ret_code_t err_code;
const nrf_drv_twi_config_t twi_ir_config = {
.scl = IR_I2C_SCL_PIN,
.sda = IR_I2C_SDA_PIN,
.frequency = NRF_DRV_TWI_FREQ_100K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
.clear_bus_init = false
};
err_code = nrf_drv_twi_init(&m_twi_ir, &twi_ir_config, NULL, NULL);
APP_ERROR_CHECK(err_code);
if (err_code != NRF_SUCCESS) {
DBG_PRINTF("TWI ir_irq_init Error.");
}
nrf_drv_twi_enable(&m_twi_ir);
// ir_irq_init();
}
uint8_t ir_command_read(uint8_t device_id, uint8_t address, uint8_t *data)
{
uint8_t read_data = 0;
char adata[8];
ret_code_t err_code;
//address = 1|(address<<1);
address = (address & 0xFF);
err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, &address, 1, true);
if (err_code != NRF_SUCCESS) {
// Handle error
// return;
}
err_code = nrf_drv_twi_rx(&m_twi_ir, device_id, data, 8);
if (err_code != NRF_SUCCESS) {
// Handle error
DBG_PRINTF("TWI read Error.");
return 0;
}
read_data = data[0];
memcpy(adata,data,8);
// DBG_PRINTF("ir Data %x %x %x %x. \r\n", device_id, address, data[0], data[1]);
//DBG_PRINTF("ir Data read %s . \r\n", adata);
return read_data;
}
void ir_command_write(uint8_t device_id, uint8_t address, uint8_t data)
{
//uint16_t data_to_write = 0;
uint8_t buffer[7]={0x00,0x00,0x00,0x00,0x00,0x00,0x00};
address = (address & 0xFF);
//buffer[0] = 0x00;
buffer[0] = (address);
buffer[1] =(data & 0xFF);
// buffer[2] = data1+1;
// buffer[3] = data1+2;
// buffer[4] = data1+3;
// buffer[5] = data1+4;
// buffer[6] = data1+5;
//memcpy(&buffer[1], data, length );
ret_code_t err_code;
//err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, 0x00, 1, false);
err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, buffer, 2, false);
DBG_PRINTF("ir Write Data %x %x %x %x. \r\n", device_id, buffer[0], buffer[1], buffer[2]);
//err_code = nrf_drv_twi_tx(&m_twi_ir, device_id, buffer, 6, false);
if (err_code != NRF_SUCCESS) {
DBG_PRINTF("TWI Error.");
}
}

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* @file ir_i2c.h
* @date 2024-07-17
******************************************************************************/
#ifndef _IR_I2C_H_
#define _IR_I2C_H_
#include "sdk_common.h"
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#define IR_I2C_INSTANCE 1 /**< I2C instance index. */
//#define IR_I2C_INSTANCE 1 /**< I2C instance index. */
//#define IR_I2C_SDA_PIN NRF_GPIO_PIN_MAP(0,10)
//#define IR_I2C_SCL_PIN NRF_GPIO_PIN_MAP(0,9)
#define IR_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15)
#define IR_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14)
/**
* COMMAND CONSTANTS
* Commands are 16-bit writes: bits 15:14 are 0s
* Bits 13:10 are the command value below
* Bits 9:0 are data for the command, but not all bits are used with all commands
*/
// The NOP command is included for completeness. It is a valid I2C operation.
#define AD5272_COMMAND_NOP 0x00 // Do nothing. Why you would want to do this I don't know
// write the 10 or 8 data bits to the RDAC wiper register (it must be unlocked first)
#define AD5272_RDAC_WRITE 0x01
#define AD5272_RDAC_READ 0x02 // read the RDAC wiper register
#define AD5272_50TP_WRITE 0x03 // store RDAC setting to 50-TP
// SW reset: refresh RDAC with last 50-TP stored value
// If not 50-TP value, reset to 50% I think???
// data bits are all dont cares
#define AD5272_RDAC_REFRESH 0x04 // TODO refactor this to AD5272_SOFT_RESET
// read contents of 50-TP in next frame, at location in data bits 5:0,
// see Table 16 page 22 Rev D datasheet
// location 0x0 is reserved, 0x01 is first programmed wiper location, 0x32 is 50th programmed wiper location
#define AD5272_50TP_WIPER_READ 0x05
/**
* Read contents of last-programmed 50-TP location
* This is the location used in SW Reset command 4 or on POR
*/
#define AD5272_50TP_LAST_USED 0x06
#define AD5272_CONTROL_WRITE 0x07 // data bits 2:0 are the control bits
#define AD5272_CONTROL_READ 0x08 // data bits all dont cares
#define AD5272_SHUTDOWN 0x09 // data bit 0 set = shutdown, cleared = normal mode
/**
* Control bits are three bits written with command 7
*/
// enable writing to the 50-TP memory by setting this control bit C0
// default is cleared so 50-TP writing is disabled
// only 50 total writes are possible!
#define AD5272_50TP_WRITE_ENABLE 0x01
// enable writing to volatile RADC wiper by setting this control bit C1
// otherwise it is frozen to the value in the 50-TP memory
// default is cleared, can't write to the wiper
#define AD5272_RDAC_WIPER_WRITE_ENABLE 0x02
// enable high precision calibration by clearing this control bit C2
// set this bit to disable high accuracy mode (dunno why you would want to)
// default is 0 = emabled
#define AD5272_RDAC_CALIB_DISABLE 0x04
// 50TP memory has been successfully programmed if this bit is set
#define AD5272_50TP_WRITE_SUCCESS 0x08
#define AD5272_NORMAL_MODE 0x01
#define AD5272_SHUTDOWN_MODE 0x01
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0,17)
#endif
void ir_i2c_uninit(void);
void ir_i2c_init(void);
void ir_irq_init(void);
void ir_irq_uninit(void);
uint8_t ir_command_read(uint8_t device_id, uint8_t address, uint8_t *data);
void ir_command_write(uint8_t device_id, uint8_t address, uint8_t data);
void ad5272_i2c_is_busy(void);
int8_t ad5272_write_and_read_rdac (uint16_t data_16_to_write);
int16_t ad5272_read_rdac (void);
int8_t ad5272_write_rdac (uint16_t data_16_to_write);
void read_all_50tp (void);
void ad5272_RDAC_refresh(void);
void ad5272_shutdown_mode(void);
void ad5272_normal_mode(void);
#endif /* !_ADA5272_I2C_H_ */

View File

@@ -0,0 +1,407 @@
/*******************************************************************************
* @file main.c
* @brief MEDiThings VivaMayo - Main Application Entry Point
* @author Charles KWON <charleskwon@medithings.co.kr>
* @date 2025-01-30
* @copyright (c) 2025 Medithings Inc. All rights reserved.
*
* @version 1.17
* @note 2025-01-30 Refactored into modular structure (Charles KWON)
* @note 2025-12-31 Added comprehensive function documentation (Charles KWON)
* @note 2025-11-27 Firmware cleanup and refactoring (Charles KWON)
******************************************************************************/
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "debug_print.h"
#include "app_uart.h"
/* Nordic SDK */
#include "nordic_common.h"
#include "nrf.h"
#include "nrf_gpio.h"
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_delay.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "app_timer.h"
#include "app_error.h"
#include "bsp.h"
#include "bsp_btn_ble.h"
#include "ble_nus.h"
/* Application Modules */
#include "ble/ble_core.h"
#include "ble/ble_services.h"
#include "ble/ble_data_tx.h"
#include "power/power_ctrl.h"
#include "peripheral/uart_handler.h"
#include "config/device_config.h"
#if FEATURE_SECURE_CONNECTION
#include "ble/ble_security.h"
#include "nrf_ble_lesc.h"
#endif
/* Hardware and Measurement Modules */
#include "main.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "measurements.h"
#include "full_agc.h"
#include "meas_pd_voltage_simple.h"
#include "meas_pd_voltage_half.h"
/* #include "meas_pd_voltage_full.h" */ /* Moved to unuse/ - FEATURE_PRINTF=0, not used */
#include "meas_pd_voltage_custom.h"
#include "meas_pd_imm.h"
#include "meas_pd_48.h"
#include "power_control.h"
#include "cat_interface.h"
#include "fstorage.h"
#include "drivers/w25q32/w25q32.h"
/* Crypto */
#include "nrf_crypto.h"
/*******************************************************************************
* @section BUILD_CONFIG Build Configuration
* @brief Compile-time feature flags and debug settings
* @details Controls boot mode selection between minimal (Power+BLE only) and
* full initialization (includes EEPROM and all peripherals).
* Set DEBUG_MINIMAL_BOOT=1 for rapid debugging without EEPROM dependency.
******************************************************************************/
#define DEBUG_MINIMAL_BOOT 1 /**< 1: Power+BLE only (no EEPROM), 0: Full boot */
/*******************************************************************************
* @section CONSTANTS Symbolic Constants
* @brief Application-wide constant definitions
* @details Magic numbers and compile-time constants used for error handling,
* fault detection, and system-level operations.
******************************************************************************/
#define DEAD_BEEF 0xDEADBEEF
/*******************************************************************************
* @section GLOBALS Global Variables
* @brief Shared runtime state and data buffers
* @details BLE transmission buffers, command type state, and cross-module
* flags. External declarations reference state from other modules.
* @warning Global state should be accessed with care in interrupt context.
******************************************************************************/
char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
uint16_t ble_bin_buff[BLE_NUS_MAX_DATA_LEN/2];
which_cmd_t cmd_type_t;
/* External state flags from other modules */
extern bool ble_got_new_data;
extern bool motion_data_once;
extern bool adc_enabled;
extern bool con_single;
extern bool info4;
extern uint8_t add_cycle;
extern bool motion_raw_data_enabled;
/*******************************************************************************
* @section FWD_DECL Forward Declarations
* @brief Static function prototypes
* @details Internal initialization and handler functions declared here for
* proper ordering. Visibility limited to this translation unit.
******************************************************************************/
static void timers_init(void);
static void log_init(void);
static void power_management_init(void);
static void buttons_leds_init(bool * p_erase_bonds);
#if !DEBUG_MINIMAL_BOOT
static void gpio_init(void);
#endif
static void idle_state_handle(void);
/*******************************************************************************
* @section ASSERT Assert Handler
* @brief System fault callback for SDK assertions
* @details Invoked by Nordic SDK on fatal errors. Provides fault location
* (file and line) for debugging via app_error_handler.
******************************************************************************/
void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
app_error_handler(DEAD_BEEF, line_num, p_file_name);
}
/*******************************************************************************
* @section INIT Initialization Functions
* @brief System and peripheral initialization routines
* @details Configures logging, power management, timers, and BSP components.
* Call order is critical - see main() for proper sequencing.
******************************************************************************/
static void log_init(void)
{
ret_code_t err_code = NRF_LOG_INIT(NULL);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEFAULT_BACKENDS_INIT();
}
static void power_management_init(void)
{
ret_code_t err_code;
err_code = nrf_pwr_mgmt_init();
APP_ERROR_CHECK(err_code);
}
static void timers_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
/* Power control timers */
power_ctrl_timers_init();
/* Application timers */
main_timer_init();
battery_timer_init();
imm_check_timer_init();
m48_check_timer_init();
full_agc_timer_init();
full_agc_send_timer_init();
mea_send_timer_init();
power_timer_init();
w25q_test_timer_init();
/* full_timer_init() - Moved to unuse/, FEATURE_PRINTF=0 so not used */
}
static void buttons_leds_init(bool * p_erase_bonds)
{
bsp_event_t startup_event;
uint32_t err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, power_bsp_event_handler);
APP_ERROR_CHECK(err_code);
err_code = bsp_btn_ble_init(NULL, &startup_event);
APP_ERROR_CHECK(err_code);
*p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);
}
/*******************************************************************************
* @section PWR_HOLD Power Hold Initialization
* @brief Latches power supply on boot
* @details Configures P0.08 (POWER_HOLD) as output HIGH to maintain VCC.
* CRITICAL: Must be the first call in main() - any delay risks
* power loss if button is released before latch is set.
* @note Uses direct register access for minimum latency.
******************************************************************************/
static void power_hold_init(void)
{
/* P0.08 = POWER_HOLD, configure as output and set HIGH to maintain power */
NRF_P0->DIRSET = (1 << 8);
NRF_P0->OUTSET = (1 << 8);
}
/*******************************************************************************
* @section MIN_GPIO Minimal GPIO Initialization (Debug Mode)
* @brief Reduced GPIO setup for rapid debugging
* @details Initializes only essential pins: POWER_BUTTON input, POWER_HOLD
* output, and power control GPIOs. Skips LEDs, PD array, gain
* switches, and EEPROM control for faster boot.
* @pre DEBUG_MINIMAL_BOOT must be set to 1
******************************************************************************/
static void minimal_gpio_init(void)
{
nrf_gpio_cfg_input(POWER_BUTTON, NRF_GPIO_PIN_NOPULL);
nrf_gpio_cfg_output(POWER_HOLD);
nrf_gpio_pin_set(POWER_HOLD);
power_gpio_init();
LOG_PRINTF("[GPIO] Minimal OK\r\n");
}
/*******************************************************************************
* @section MIN_CFG Default Configuration Loader (Debug Mode)
* @brief Loads hardcoded defaults bypassing EEPROM
* @details Sets factory defaults for serial number, passkey, PD timing,
* and bonding state. Used when EEPROM is unavailable or for
* rapid iteration during development.
* @pre DEBUG_MINIMAL_BOOT must be set to 1
******************************************************************************/
static void load_default_config(void)
{
/* Direct assignment of variables defined in device_config.h */
memset(SERIAL_NO, 0, sizeof(SERIAL_NO));
memcpy(SERIAL_NO, "2025VIVAM00001", 14);
memset(m_static_passkey, 0, sizeof(m_static_passkey));
memcpy(m_static_passkey, "123456", 6);
m_pd_delay_us = 8000;
m_pd_adc_cnt = 8;
bond_data_delete = true;
LOG_PRINTF("[CFG] Default (S/N=%s)\r\n", SERIAL_NO);
}
/*******************************************************************************
* @section FULL_GPIO Full GPIO Initialization (Production Mode)
* @brief Complete peripheral GPIO configuration
* @details Initializes all hardware interfaces: LEDs, photodiode array (PD),
* trigger switches, AGC gain control, power management, and EEPROM.
* All outputs are set to safe default states (LEDs off, PD off).
* @pre DEBUG_MINIMAL_BOOT must be set to 0
******************************************************************************/
#if !DEBUG_MINIMAL_BOOT
static void gpio_init(void)
{
nrf_gpio_cfg_input(POWER_BUTTON, NRF_GPIO_PIN_NOPULL);
LED_CONFIG();
LED_ALLOFF();
PD_CONFIG();
PD_ALLOFF();
trig_r_CONFIG();
trig_SW(false);
GAIN_SW_CONFIG();
AGC_GAIN_SW(false);
power_gpio_init();
eeprom_control(OFF);
}
#endif
/*******************************************************************************
* @section IDLE Idle State Handler
* @brief Low-power idle loop processing
* @details Handles security operations (LESC if enabled), flushes NRF_LOG
* buffer, then enters System ON sleep via nrf_pwr_mgmt_run().
* CPU wakes on any enabled interrupt (BLE, timer, GPIO).
******************************************************************************/
static void idle_state_handle(void)
{
#if FEATURE_SECURE_CONNECTION
security_idle_state_handle();
#endif
if (NRF_LOG_PROCESS() == false) {
nrf_pwr_mgmt_run();
}
}
/*******************************************************************************
* @section MAIN Application Entry Point
* @brief System initialization and main loop
* @details Boot sequence: Power latch → UART → Logging → GPIO → Timers →
* Config → BSP → Power Mgmt → BLE Stack → GAP → GATT → Services →
* Advertising → Connection Params → Power Button Handler.
* Main loop runs idle_state_handle() for low-power operation.
* @note Initialization order is critical for proper hardware bringup.
******************************************************************************/
/* LED slow blink macro (approximately 0.5 second period) */
#define LED_BLINK() do { \
NRF_P0->OUTSET = (1 << 12); \
for (volatile uint32_t _d = 0; _d < 1000000; _d++); \
NRF_P0->OUTCLR = (1 << 12); \
for (volatile uint32_t _d = 0; _d < 1000000; _d++); \
} while(0)
#define LED_PAUSE() do { \
for (volatile uint32_t _d = 0; _d < 2000000; _d++); \
} while(0)
int main(void)
{
#if FEATURE_SECURE_CONNECTION
bool erase_bonds_local = false;
#endif
/* Configure LED output (P0.12) */
NRF_P0->DIRSET = (1 << 12);
power_hold_init();
if (power_off_duble_prohibit) return 0;
cnt_s = 0;
uart_handler_init();
nrf_delay_ms(100); /* Wait for UART peripheral stabilization */
log_init();
g_log_enable = true; /* Enable boot logging (set to false to disable boot logs) */
LOG_PRINTF("\r\n\r\n");
LOG_PRINTF("========================================\r\n");
LOG_PRINTF(" vivaMayo UART OK\r\n");
LOG_PRINTF("========================================\r\n");
#if DEBUG_MINIMAL_BOOT
LOG_PRINTF("[1] GPIO (minimal)\r\n");
minimal_gpio_init();
#else
LOG_PRINTF("[1] GPIO (full)\r\n");
gpio_init();
#endif
info4 = false;
LOG_PRINTF("[2] Timers\r\n");
timers_init();
LOG_PRINTF("[4] Buttons/LEDs\r\n");
#if FEATURE_SECURE_CONNECTION
buttons_leds_init(&erase_bonds_local);
erase_bonds = erase_bonds_local;
#else
bool dummy_erase;
buttons_leds_init(&dummy_erase);
#endif
LOG_PRINTF("[5] PWR\r\n"); nrf_delay_ms(10);
power_management_init();
LOG_PRINTF("[6] BLE\r\n"); nrf_delay_ms(10);
ble_stack_init();
/* Flash Storage - must be after BLE stack (SoftDevice required) */
/* and before GAP params (to use loaded config for device name) */
LOG_PRINTF("[6.5] FDS\r\n"); nrf_delay_ms(10);
fs_storage_init();
config_load();
LOG_PRINTF("[6.6] Config\r\n");
load_device_configuration();
/* W25Q32 Flash - NOT initialized during boot
* Initialize via BLE command after advertising starts
* (부팅 시 SPIM + BSP 이벤트 충돌로 SYSTEM OFF 발생 방지) */
LOG_PRINTF("[7] GAP\r\n"); nrf_delay_ms(10);
gap_params_init();
gatt_init();
services_init();
LOG_PRINTF("[8] ADV\r\n"); nrf_delay_ms(10);
advertising_init();
conn_params_init();
power_ctrl_timers_start();
/* advertising_start() is invoked from power_ctrl.c after 5ms delay */
nrf_delay_ms(20);
LOG_PRINTF("=== READY ===\r\n");
/*----------------------------------------------------------
* Main Loop: Idle State Handler
*----------------------------------------------------------*/
for (;;)
{
idle_state_handle();
}
}

View File

@@ -0,0 +1,215 @@
/*******************************************************************************
* @file main.h
* @brief Main application header for BLE Bladder Patch NIRS device
* @author Charles KWON <charleskwon@medithings.co.kr>
* @version V2.0.0
* @date 2025-12-31
*
* Copyright (c) 2025 Medithings Inc.
* All rights reserved.
*
* This file contains type definitions, enumerations, and function prototypes
* for the main application module of the BLE-enabled NIRS bladder monitoring
* device based on nRF52840.
******************************************************************************/
#ifndef MAIN_H__
#define MAIN_H__
/*============================================================================*/
/* Includes - Charles KWON */
/*============================================================================*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include "boards.h"
/*============================================================================*/
/* Type Definitions - Charles KWON */
/*============================================================================*/
/**
* @brief Generic ON/OFF control enumeration
* - Charles KWON
*
* Used for controlling power states of various device components
* such as LEDs, photodetectors, and other peripherals.
*/
typedef enum
{
OFF = 0, /**< Device/component is OFF */
ON = 1 /**< Device/component is ON */
} on_off_cont_t;
/**
* @brief Command source type enumeration
* - Charles KWON
*
* Identifies the source of incoming commands to route
* responses appropriately.
*/
typedef enum
{
CMD_BLE = 0, /**< Command received via BLE (Nordic UART Service) */
CMD_UART = 1 /**< Command received via physical UART interface */
} which_cmd_t;
#if FEATURE_CHAMBER_AUTO_TEST
/**
* @brief Automatic measurement mode enumeration
* - Charles KWON
*
* Defines different automatic testing modes for chamber calibration
* and production testing scenarios.
*/
typedef enum
{
SIMPLE_AUTO_MODE = 0, /**< Simple automatic measurement mode */
HALF_AUTO_MODE = 1, /**< Semi-automatic measurement mode */
FULL_AUTO_MODE = 2, /**< Full automatic measurement mode */
NONE_AUTO_MODE = 3 /**< Manual mode (no automation) */
} auto_meas_mode_t;
#endif
/**
* @brief BLE connection status enumeration
* - Charles KWON
*
* Tracks the current BLE connection state for controlling
* device behavior based on connectivity.
*/
typedef enum
{
BLE_DISCONNECTED_ST = 0, /**< BLE is disconnected */
BLE_CONNECTED_ST = 1 /**< BLE is connected to a central device */
} ble_status_t;
/*============================================================================*/
/* Function Prototypes - Command Processing - Charles KWON */
/*============================================================================*/
/**
* @brief Process received command data
* - Charles KWON
*
* Parses and executes commands received from BLE or UART.
* Commands control measurement modes, device settings, and queries.
*
* @param[in] data_array Pointer to received command data buffer
* @param[in] cmd_t Source of the command (BLE or UART)
* @param[in] length Length of the command data in bytes
*/
void received_command_process(uint8_t const *data_array, which_cmd_t cmd_t, uint8_t length);
/*============================================================================*/
/* Function Prototypes - Data Transmission - Charles KWON */
/*============================================================================*/
/**
* @brief Transmit ASCII string data
* - Charles KWON
*
* Sends null-terminated string data over the active communication
* channel (BLE NUS or UART) with flow control.
*
* @param[in] p_data_to_send Pointer to null-terminated string to transmit
*/
void data_tx_handler(char const *p_data_to_send);
/**
* @brief Transmit binary data
* - Charles KWON
*
* Sends raw binary data over BLE NUS with specified length.
* Used for transmitting measurement data and binary payloads.
*
* @param[in] ble_bin_buff Pointer to binary data buffer
* @param[in] length Number of bytes to transmit
*/
void binary_tx_handler(uint8_t const *ble_bin_buff, uint16_t length);
/**
* @brief Format single 16-bit value with tag
* - Charles KWON
*
* Creates a tagged binary packet containing a single 16-bit value.
* Format: [tag bytes][value_high][value_low]
*
* @param[out] buffer Destination buffer for formatted data
* @param[in] tag 4-character tag string identifier
* @param[in] value 16-bit value to encode
*/
void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value);
/**
* @brief Format 16-bit array with tag
* - Charles KWON
*
* Creates a tagged binary packet containing an array of 16-bit values.
* Format: [tag bytes][data_0_high][data_0_low]...[data_n_high][data_n_low]
*
* @param[out] buffer Destination buffer for formatted data
* @param[in] tag 4-character tag string identifier
* @param[in] data_array Pointer to array of 16-bit values
* @param[in] length Number of elements in the array
*/
void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length);
/**
* @brief Format 8-bit array with tag
* - Charles KWON
*
* Creates a tagged binary packet containing an array of 8-bit values.
* Format: [tag bytes][data_0]...[data_n]
*
* @param[out] buffer Destination buffer for formatted data
* @param[in] tag 4-character tag string identifier
* @param[in] data_array Pointer to array of 8-bit values
* @param[in] length Number of elements in the array
*/
void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length);
/**
* @brief Format ASCII string with tag
* - Charles KWON
*
* Creates a tagged packet containing ASCII string data.
* Format: [tag bytes][ascii_char_0]...[ascii_char_n]
*
* @param[out] buffer Destination buffer for formatted data
* @param[in] tag 4-character tag string identifier
* @param[in] data_ascii Pointer to ASCII string data
* @param[in] length Number of characters to include
*/
void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length);
/*============================================================================*/
/* External Variables - Charles KWON */
/*============================================================================*/
/**
* @brief Flag indicating BLE TX operation in progress
*
* Set to true when a BLE transmission is pending completion.
* Used to prevent overlapping transmissions and implement flow control.
*/
extern volatile bool data_tx_in_progress;
/**
* @brief Current BLE connection status
*
* True when device is connected to a BLE central.
* Used to control device behavior based on connectivity state.
*/
extern volatile bool ble_connection_st;
/**
* @brief Flag indicating command processing in progress
*
* Set to true during command execution to prevent
* concurrent command processing.
*/
extern volatile bool processing;
#endif /* MAIN_H__ */

View File

@@ -0,0 +1,152 @@
/*******************************************************************************
* @file main_timer.c
* @brief Main Loop Timer Handler
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "app_util_platform.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_log.h"
#include "nrf_drv_gpiote.h"
#include "battery_saadc.h"
#include "app_timer.h"
#include "main.h"
#include "app_raw_main.h"
#include "main_timer.h"
#include "tmp235_q1.h"
#include "meas_pd_imm.h"
/* #include "meas_pd_voltage_full.h" */ /* Moved to unuse/ */
#include "mcp4725_i2c.h"
#include "power_control.h"
#include "power/power_ctrl.h"
#include <cmd_parse.h>
#include "meas_pd_48.h"
#include "debug_print.h"
#include "i2c_manager.h"
APP_TIMER_DEF(m_main_loop_timer_id);
#if FEATURE_DETAIL_VALUE_FULL
#define MAIN_LOOP_INTERVAL 80
extern which_cmd_t cmd_type_t;
#else
#define MAIN_LOOP_INTERVAL 10
#endif
bool go_batt = false;
bool go_temp = false;
bool go_pdread = false;
/* Defined in power_ctrl.c */
extern bool go_device_power_off;
extern bool go_sleep_mode_enter;
extern bool go_NVIC_SystemReset;
bool motion_raw_data_enabled = false;
bool ble_got_new_data = false;
bool motion_data_once = false;
bool adc_enabled = false;
static uint16_t cnt_adc = 0;
void main_loop(void * p_context)
{
UNUSED_PARAMETER(p_context);
/* Motion Data Sampling */
if (motion_raw_data_enabled == true) {
main_timer_stop();
if (motion_data_once == true) {
hw_i2c_init_once();
icm42670_main();
} else {
if (ble_got_new_data == false) {
DBG_PRINTF("IMU \r\n");
icm42670_main();
nrf_delay_ms(10);
motion_raw_data_enabled = true;
main_timer_start();
}
}
}
if (go_batt == true) {
DBG_PRINTF("IMU BATT\r\n");
main_timer_stop();
go_batt = false;
battery_level_meas();
}
if (go_temp == true) {
DBG_PRINTF("IMU Temp\r\n");
main_timer_stop();
go_temp = false;
motion_data_once = true;
tmp235_voltage_level_meas();
}
if (go_pdread == true) {
main_timer_stop();
go_pdread = false;
m48_adc_start_init();
}
if (adc_enabled == true) {
main_timer_stop();
DBG_PRINTF("PD48 ADC=%d\r\n", cnt_adc);
if (ble_got_new_data == false) {
if (cnt_adc < 500) {
cnt_adc++;
} else if (cnt_adc == 500) {
cnt_adc = 0;
}
main_timer_start();
}
}
/* System Control */
if (go_device_power_off == true) {
main_timer_stop();
DBG_PRINTF("Off main_timer\r\n");
device_power_off();
}
if (go_sleep_mode_enter == true) {
main_timer_stop();
DBG_PRINTF("sleep main timer\r\n");
sleep_mode_enter();
}
if (go_NVIC_SystemReset == true) {
main_timer_stop();
NVIC_SystemReset();
}
}
void main_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_main_loop_timer_id, APP_TIMER_TICKS(MAIN_LOOP_INTERVAL), NULL));
}
void main_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_main_loop_timer_id));
}
void main_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_main_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, main_loop));
}

View File

@@ -0,0 +1,17 @@
/*******************************************************************************
* @file timer_routine.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef TIMER_ROUTINE_H__
#define TIMER_ROUTINE_H__
void main_timer_start(void);
void main_timer_stop(void);
void main_timer_init(void);
#endif //TIMER_ROUTINE_H__

View File

@@ -0,0 +1,114 @@
/*******************************************************************************
* @file mcp4725_adc.c
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "ble_nus.h"
#include "mcp4725_adc.h"
#include "main.h"
#include "debug_print.h"
#define MCP4725_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
#define MCP4725_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
#define MCP4725_ADC_RES_10BITS 1024.0f /**< Maximum digital value for 10-bit ADC conversion. */
/**@brief Macro to convert the result of ADC conversion in millivolts.
*
* @param[in] ADC_VALUE ADC result.
*
* @retval Result converted to millivolts.
*/
#define MCP4725_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\
((((ADC_VALUE) * MCP4725_REF_VOLTAGE_IN_MILLIVOLTS) / MCP4725_ADC_RES_10BITS) * MCP4725_PRE_SCALING_COMPENSATION)
#define ADC_SAMPLES_IN_BUFFER 1
static nrf_saadc_value_t mcp4725_adc_buf[2][ADC_SAMPLES_IN_BUFFER];
float mcp4725_voltage_in_milli_volts = 0;
//extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
extern which_cmd_t cmd_type_t;
extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ;
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
void mcp4725_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* ADC_GAIN reading */
{
float Vref = 3.3f; /* It same as Vdd */
float dac_value = 0.0f;
uint16_t dac_value_16=0;
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
nrf_saadc_value_t adc_result;
nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, ADC_SAMPLES_IN_BUFFER);
adc_result = p_event->data.done.p_buffer[0];
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
mcp4725_voltage_in_milli_volts = MCP4725_VOUT_IN_MILLI_VOLTS(adc_result);
#if FEATURE_DETAIL_VALUE_AGC
DBG_PRINTF("AGC read Vol: %f(mV)\r\n", mcp4725_voltage_in_milli_volts);
#endif
/* For MCP4725, Dn = (Vout/Vref) x 4096, Vref = Vdd */
dac_value = (((mcp4725_voltage_in_milli_volts/Vref) * 4096.0f)/1000.0f); /* Unit is Volt */
if(cmd_type_t == CMD_UART) {
DBG_PRINTF("Te%d\r\n\r\n",(uint16_t)(dac_value + 0.5f));
} else if(cmd_type_t == CMD_BLE) {
dac_value_16 = (uint16_t)(dac_value + 0.5f);
single_format_data(ble_bin_buffer, "rse:", dac_value_16);
binary_tx_handler(ble_bin_buffer,3);
// sprintf(ble_tx_buffer, "Te%d\r\n",(uint16_t)(dac_value + 0.5f));
// data_tx_handler(ble_tx_buffer);
}
}
}
void mcp4725_adc_init(void)
{
ret_code_t err_code = nrf_drv_saadc_init(NULL, mcp4725_voltage_handler);
APP_ERROR_CHECK(err_code);
nrf_saadc_channel_config_t config =
NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN6); /* FSA5157P6X Voltage Output Measurement */
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(mcp4725_adc_buf[0], ADC_SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(mcp4725_adc_buf[1], ADC_SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code);
}
void mcp4725_voltage_level_meas(void)
{
mcp4725_adc_init();
}

View File

@@ -0,0 +1,16 @@
/*******************************************************************************
* @file mcp4725.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _MCP4725_ADC_H_
#define _MCP4725_ADC_H_
void mcp4725_adc_init(void);
void mcp4725_voltage_level_meas(void);
#endif /* !_MCP4725_ADC_H_ */

View File

@@ -0,0 +1,513 @@
/*******************************************************************************
* @file mcp4725_i2c.c
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
/* board driver */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <math.h>
#include "nrf.h"
#include "app_error.h"
#include "boards.h"
#include "nrfx_gpiote.h"
#include "mcp4725_i2c.h"
#include "nrf_delay.h"
#include "debug_print.h"
/* I2C number and slave address for MCP4725 */
#define MCP4725_I2C_ADDR_7bit 0x66
uint8_t MCP4725_I2C_ADDR = MCP4725_I2C_ADDR_7bit << 1;
uint16_t _lastValue;
uint8_t _powerDownMode;
uint32_t _lastWriteEEPROM;
void mcp4725_i2c_initialize(void)
{
SCL_OUT();
SDA_OUT();
SCL_H();
SDA_H();
}
static uint8_t mcp4725_i2c_write(uint8_t data)
{
for(uint8_t i = 0; i < 8; i++)
{
// MSB first
if(data & 0x80) SDA_H();
else SDA_L();
i2c_clock();
data = data << 1;
}
// read ACK
SDA_H(); // leave SDA HI
SDA_IN(); // change direction to input on SDA line
i2c_delay();
SCL_H(); // clock back up
i2c_delay();
uint8_t ack = SDA_READ(); // get the ACK bit
SCL_L();
SDA_OUT(); // change direction back to output
if(ack) {
DBG_PRINTF("ACK Extra=%d,Data=%d\r\n", ack,data);
}
return ack;
}
static uint8_t mcp4725_i2c_read(bool ack)
{
uint8_t data;
SDA_H(); // leave SDA HI
SDA_IN(); // change direction to input on SDA line
data = 0;
for(uint8_t i = 0; i < 8; i++)
{
// MSB first
data = data << 1;
SCL_H();
do{
}while(SCL_READ() != 0); // Wait for any SCL clock stratching
i2c_delay();
if(SDA_READ()) // get the Data bit
data |= 1;
else
data |= 0;
SCL_L(); // clock LO
i2c_delay();
}
SDA_OUT(); // change direction back to output
// send ACK
if(ack == ACK) SDA_L();
else if(ack == NACK) SDA_H();
i2c_clock();
SDA_H(); // leave with SDA HI
return data;
}
void mcp4725_writeFastMode(const uint16_t value)
{
uint8_t low = value & 0xFF;
uint8_t high = ((value >> 8) & 0x0F); // set C2,c1 == 0,0(FastMode Write) pd1,pd0 == 0,0 (Normal Mode)
i2c_start();
mcp4725_i2c_write(MCP4725_I2C_ADDR|TWI_WRITE); // slave address
//uint8_t address = MCP4725_I2C_ADDR | TWI_WRITE;
//DBG_PRINTF("[I2C] Write to 0x%02X\r\n", address); //oxCC
mcp4725_i2c_write(high); // value1 ~ 3
mcp4725_i2c_write(low);
i2c_stop();
}
void mcp4725_generalCall(const uint8_t gc)
{
i2c_start();
mcp4725_i2c_write(0x00); // First Byte 0x00
mcp4725_i2c_write(gc); // Second Byte, General Call Reset = 0x06, General Call Wake-Up = 0x09
i2c_stop();
}
void mcp4725_init(void)
{
mcp4725_i2c_initialize();
}
// DAC value is reset to EEPROM value
// need to reflect this in cached value
void mcp4725_powerOnReset(void)
{
mcp4725_generalCall(MCP4725_GC_RESET);
}
// _powerDownMode DAC resets to 0 -- PDM EEPROM stays same !!!
// need to reflect this in cached value
void mcp4725_powerOnWakeUp(void)
{
mcp4725_generalCall(MCP4725_GC_WAKEUP);
}
/* 현재 DAC값 읽어서 그 값에 Power Down 넣고 Write */
void mcp4725_PowerDownMode(void)
{
uint8_t low = 0x00; //read_value & 0xFF;
uint8_t high = 0x00; //((read_value >> 8) & 0x0F);
high = high | (MCP4725_PDMODE_1K << 4); // set C2,c1 == 0,0(FastMode Write) pd1,pd0 == 0,1 (Power Down Mode, 1Kohm to GND)
i2c_start();
mcp4725_i2c_write(MCP4725_I2C_ADDR|TWI_WRITE); // slave address
mcp4725_i2c_write(high); // value
mcp4725_i2c_write(low);
i2c_stop();
}
////////////////////////////////////////////////////////////////////////////////
uint16_t mcp4725_readDAC(void)
{
while(!mcp4725_ready());
uint8_t buffer[3];
mcp4725_readRegister(buffer, 3);
uint16_t value = buffer[1];
value = value << 4;
value = value + (buffer[2] >> 4);
return value;
}
// ready checks if the last write to EEPROM has been written.
// until ready all writes to the MCP4725 are ignored!
bool mcp4725_ready(void)
{
uint8_t buffer[1];
mcp4725_readRegister(buffer, 1);
return ((buffer[0] & 0x80) > 0);
}
void mcp4725_writeRegisterMode(const uint16_t value, uint8_t reg)
{
uint8_t high = (value / 16);
uint8_t low = (value & 0x0F) << 4;
reg = reg | (_powerDownMode << 1);
i2c_start();
mcp4725_i2c_write(MCP4725_I2C_ADDR|TWI_WRITE); // slave address
mcp4725_i2c_write(reg); // configuration
mcp4725_i2c_write(high); // value1 ~ 3
mcp4725_i2c_write(low);
i2c_stop();
}
void mcp4725_readRegister(uint8_t* buffer, const uint8_t length)
{
i2c_start();
mcp4725_i2c_write(MCP4725_I2C_ADDR|TWI_READ); // slave address with READ
switch(length) {
case 1:
buffer[0] = mcp4725_i2c_read(NACK); // NACK, SDA = 1;
break;
case 2:
buffer[0] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[1] = mcp4725_i2c_read(NACK); // NACK, SDA = 1;
break;
case 3:
buffer[0] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[1] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[2] = mcp4725_i2c_read(NACK); // NACK, SDA = 1;
break;
case 4:
buffer[0] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[1] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[2] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[3] = mcp4725_i2c_read(NACK); // NACK, SDA = 1;
break;
case 5:
buffer[0] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[1] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[2] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[3] = mcp4725_i2c_read(ACK); // ACK, SDA = 0;
buffer[4] = mcp4725_i2c_read(NACK); // NACK, SDA = 1;
break;
default:
#if FEATURE_PRINTF
DBG_PRINTF("ERR!! mcp4725_i2c_readregister\r\n");
#endif
break;
}
i2c_stop();
}
void mcp4725_setValue(const uint16_t value)
{
if (value == _lastValue) return;
if (value > MCP4725_MAXVALUE) return;
mcp4725_writeFastMode(value);
_lastValue = value;
}
uint16_t mcp4725_getValue(void)
{
return _lastValue;
}
void mcp4725_setPercentage(float percentage)
{
if ((percentage > 100) || (percentage < 0))
DBG_PRINTF("ERR!!! mcp4725 percentage error\r\n");
mcp4725_setValue(round(percentage * (0.01 * MCP4725_MAXVALUE)));
}
// unfortunately it is not possible to write a different value
// to the DAC and EEPROM simultaneously or write EEPROM only.
void mcp4725_writeDAC(const uint16_t value, const bool EEPROM)
{
if (value > MCP4725_MAXVALUE)
#if FEATURE_PRINTF
DBG_PRINTF("ERR!!! mcp4725 writeDAC error\r\n");
#endif
while(!mcp4725_ready());
mcp4725_writeRegisterMode(value, EEPROM ? MCP4725_DACEEPROM : MCP4725_DAC);
_lastValue = value;
}
uint16_t mcp4725_readEEPROM(void)
{
while(!mcp4725_ready());
uint8_t buffer[5];
mcp4725_readRegister(buffer, 5);
uint16_t value = buffer[3] & 0x0F;
value = value << 8;
value = value + buffer[4];
return value;
}
// depending on bool EEPROM the value of PDM is written to
// (false) DAC or
// (true) DAC & EEPROM,
void mcp4725_writePowerDownMode(const uint8_t PDM, const bool EEPROM)
{
_powerDownMode = (PDM & 0x03); // mask PDM bits only (written later low level)
_lastValue = mcp4725_readDAC();
_powerDownMode = mcp4725_readPowerDownModeDAC();
mcp4725_writeDAC(_lastValue, EEPROM);
}
uint8_t mcp4725_readPowerDownModeEEPROM(void)
{
while(!mcp4725_ready());
uint8_t buffer[4];
mcp4725_readRegister(buffer, 4);
uint8_t value = (buffer[3] >> 5) & 0x03;
return value;
}
uint8_t mcp4725_readPowerDownModeDAC(void)
{
while(!mcp4725_ready()); // TODO needed?
uint8_t buffer[1];
mcp4725_readRegister(buffer, 1);
uint8_t value = (buffer[0] >> 1) & 0x03;
return value;
}
void ds_i2c_start(void) {
SDA_H();//nrf_gpio_pin_set(I2C_SDA_PIN);
SCL_H();//nrf_gpio_pin_set(I2C_SCL_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SDA_L();//nrf_gpio_pin_clear(I2C_SDA_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SCL_L();//nrf_gpio_pin_clear(I2C_SCL_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
}
void ds_i2c_stop(void) {
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SDA_L();//nrf_gpio_pin_clear(I2C_SDA_PIN);
SCL_H();// nrf_gpio_pin_set(I2C_SCL_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SDA_H();//nrf_gpio_pin_set(I2C_SDA_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
}
void i2c_write_bit(uint8_t bit) {
if (bit) {
SDA_H();
} else {
SDA_L();
}
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SCL_H(); //nrf_gpio_pin_set(SCL_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SCL_L();//nrf_gpio_pin_clear(SCL_PIN);
}
uint8_t i2c_read_bit(void) {
uint8_t bit;
SDA_IN();//nrf_gpio_cfg_input(SDA_PIN, NRF_GPIO_PIN_NOPULL);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SCL_H(); ////nrf_gpio_pin_set(SCL_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
bit = SDA_READ();//nrf_gpio_pin_read(SDA_PIN);
SCL_L(); //nrf_gpio_pin_clear(SCL_PIN);
SDA_OUT();//nrf_gpio_cfg_output(SDA_PIN);
return bit;
}
uint8_t i2c_write_byte(uint8_t byte) {
for (uint8_t i = 0; i < 8; i++) {
i2c_write_bit(byte & 0x80);
byte <<= 1;
}
return i2c_read_bit(); // Read ACK/NACK
}
uint8_t i2c_read_byte(uint8_t ack) {
uint8_t byte = 0;
SDA_IN();//nrf_gpio_cfg_input(SDA_PIN, NRF_GPIO_PIN_NOPULL);
for (uint8_t i = 0; i < 8; i++) {
byte <<= 1;
byte |= i2c_read_bit();
}
SDA_OUT();//nrf_gpio_cfg_output(SDA_PIN);
i2c_write_bit(!ack); // Send ACK/NACK
return byte;
}
void i2c_repeated_start(void) {
SDA_H();//nrf_gpio_pin_set(SDA_PIN);
SCL_H();//nrf_gpio_pin_set(SCL_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SDA_L();////nrf_gpio_pin_clear(SDA_PIN);
i2c_delay();//nrf_delay_us(I2C_DELAY_US);
SCL_L();//nrf_gpio_pin_clear(SCL_PIN);
}
// i2c_start();
// i2c_write_byte(I2C_ADDRESS << 1); // Write address
// i2c_write_byte(0x00); // Register address
// i2c_write_byte(0xFF); // Data
// i2c_stop();
// // Read example
// i2c_start();
// i2c_write_byte((I2C_ADDRESS << 1) | 1); // Read address
// uint8_t data = i2c_read_byte(0); // Read data with NACK
// i2c_stop();
void DS3930_write(uint8_t id, uint8_t addr, uint8_t wdata)
{
//i2c_start();
// ds_i2c_stop();
ds_i2c_start();
i2c_write_byte(id << 1); // Write address
i2c_write_byte(addr); // Register address
i2c_write_byte(wdata); // Data
// mcp4725_i2c_write(id << 1);
// mcp4725_i2c_write(addr);
// mcp4725_i2c_write(wdata);
//
// }
//i2c_delay();
//i2c_stop();
ds_i2c_stop();
}
uint8_t DS3930_read(uint8_t id, uint8_t addr, uint8_t* rdata)
{
ds_i2c_start();
i2c_write_byte(id << 1); // Write address
i2c_write_byte(addr); // Register address
i2c_repeated_start();
i2c_write_byte((id << 1) | 1); // Read address
uint8_t data = i2c_read_byte(0); // Read data with NACK
ds_i2c_stop();
rdata[0] = data;
DBG_PRINTF("Data 0x%x . \r\n", data);
//i2c_stop();
return data;
}
//void CAT24_write(uint8_t id, uint8_t addr, uint8_t* wdata)
//{
////i2c_start();
//// ds_i2c_stop();
// ds_i2c_start();
// i2c_write_byte(id << 1); // Write address
// i2c_write_byte(addr); // Register address
// i2c_write_byte(wdata); // Data
//
//// mcp4725_i2c_write(id << 1);
//// mcp4725_i2c_write(addr);
//// mcp4725_i2c_write(wdata);
////
//// }
////i2c_delay();
////i2c_stop();
// ds_i2c_stop();
//}
//uint8_t CAT24_read(uint8_t id, uint8_t addr, uint8_t* rdata,uint8_t length)
//{
// ds_i2c_start();
// i2c_write_byte(id << 1); // Write address
// i2c_write_byte(addr); // Register address
// i2c_repeated_start();
// i2c_write_byte((id << 1) | 1); // Read address
// uint8_t data = i2c_read_byte(0); // Read data with NACK
// ds_i2c_stop();
// rdata[0] = data;
// DBG_PRINTF("Data 0x%x . \r\n", data);
// //i2c_stop();
// return data;
//}

View File

@@ -0,0 +1,92 @@
/*******************************************************************************
* @file mcp4725_i2c.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#ifndef _MCP4725_I2C_H_
#define _MCP4725_I2C_H_
#define MCP4725_I2C_SDA_PIN NRF_GPIO_PIN_MAP(1,15)//cj edit
#define MCP4725_I2C_SCL_PIN NRF_GPIO_PIN_MAP(1,14) //cd eidt
#define SDA_H() nrf_gpio_pin_set(MCP4725_I2C_SDA_PIN) // SDA pin high
#define SDA_L() nrf_gpio_pin_clear(MCP4725_I2C_SDA_PIN) // SDA pin low
#define SCL_H() nrf_gpio_pin_set(MCP4725_I2C_SCL_PIN) // SCL pin high
#define SCL_L() nrf_gpio_pin_clear(MCP4725_I2C_SCL_PIN) // SCL pin low
#define SDA_OUT() nrf_gpio_cfg_output(MCP4725_I2C_SDA_PIN) // SDA pin output mode
#define SCL_OUT() nrf_gpio_cfg_output(MCP4725_I2C_SCL_PIN) // SCL pin output mode
#define SDA_IN() nrf_gpio_cfg_input(MCP4725_I2C_SDA_PIN, NRF_GPIO_PIN_NOPULL) // SDA pin input mode
#define SDA_READ() nrf_gpio_pin_read(MCP4725_I2C_SDA_PIN) // SDA pin read
#define SCL_READ() nrf_gpio_pin_read(MCP4725_I2C_SCL_PIN) // SCL pin read
#define i2c_delay() nrf_delay_us(1) // delay time for 400khz clock 1
#define i2c_clock() { i2c_delay(); SCL_H(); i2c_delay(); SCL_L(); }
#define i2c_start() { SDA_H(); i2c_delay(); SCL_H(); i2c_delay(); SDA_L(); i2c_delay(); SCL_L(); i2c_delay();}
#define i2c_stop() { SDA_L(); i2c_delay(); SCL_H(); i2c_delay(); SDA_H(); i2c_delay(); }
#define TWI_WRITE 0x00
#define TWI_READ 0x01
#define ACK true
#define NACK false
#define DAC_write false
#define EEPROM_write true
// constants
#define MCP4725_MAXVALUE 4095
// registerMode
#define MCP4725_DAC 0x40
#define MCP4725_DACEEPROM 0x60
// reset & wake up
#define MCP4725_GC_RESET 0x06
#define MCP4725_GC_WAKEUP 0x09
// powerDown Mode - TODO ENUM?
#define MCP4725_PDMODE_NORMAL 0x00
#define MCP4725_PDMODE_1K 0x01
#define MCP4725_PDMODE_100K 0x02
#define MCP4725_PDMODE_500K 0x03
void mcp4725_i2c_initialize(void);
void mcp4725_writeFastMode(const uint16_t value);
void mcp4725_generalCall(const uint8_t gc);
void mcp4725_init(void);
void mcp4725_powerOnReset(void);
void mcp4725_powerOnWakeUp(void);
void mcp4725_PowerDownMode(void);
/* * * * * * * * * * * * * * * * * * * * */
uint16_t mcp4725_readDAC(void);
bool mcp4725_ready(void);
uint16_t mcp4725_readDAC(void);
bool mcp4725_ready(void);
void mcp4725_writeRegisterMode(const uint16_t value, uint8_t reg);
void mcp4725_readRegister(uint8_t* buffer, const uint8_t length);
void mcp4725_setValue(const uint16_t value);
uint16_t mcp4725_getValue(void);
void mcp4725_setPercentage(float percentage);
void mcp4725_writeDAC(const uint16_t value, const bool EEPROM);
uint16_t mcp4725_readEEPROM(void);
void mcp4725_writePowerDownMode(const uint8_t PDM, const bool EEPROM);
uint8_t mcp4725_readPowerDownModeEEPROM(void);
uint8_t mcp4725_readPowerDownModeDAC(void);
void DS3930_write(uint8_t id, uint8_t addr, uint8_t wdata);
uint8_t DS3930_read(uint8_t id, uint8_t addr, uint8_t* rdata);
#endif /* !_MCP4725_I2C_H_ */

View File

@@ -0,0 +1,905 @@
/*******************************************************************************
* @file meas_pd_48.c
* @brief M48 LED-PD measurement module for NIRS system
* @author Charles KWON <charleskwon@medithings.co.kr>
* @version V2.0.0
* @date 2025-12-31
*
* Copyright (c) 2025 Medithings Inc.
* All rights reserved.
*
* This module implements the M48 (24/48 LED) measurement sequence for the
* NIRS bladder monitoring system. It handles sequential LED activation,
* ADC sampling via PPI-triggered SAADC, and data transmission over BLE.
*
* ============================================================================
* MEASUREMENT FLOW (mcj? command example - ADC_PD_MODE 2)
* ============================================================================
*
* 1. Command Reception (parser.c - Cmd_mcj):
* - BLE/UART receives "mcj?" command
* - Sets ADC_PD_MODE = 2 (Half 0-23, single shot)
* - Sets info4 = true (include batt, temp, IMU, pressure data)
* - Calls m48_adc_start_init() to begin measurement
*
* 2. Initialization (m48_adc_start_init):
* - Stops battery timer (to prevent ADC conflict)
* - Copies LED_list_m48_1h[24] -> CURRENT_list_m48[24]
* - Sets CURRENT_LED_NO = 24
* - Initializes I2C, SAADC, GPIOTE, PPI
* - Configures first LED (LED0) via CAT9532 I2C
* - Sets MCP4725 DAC value from led_pd_dac_v[0] (from EEPROM/AGC)
* - Starts ADC sampling
*
* 3. ADC Sampling Loop (m48_voltage_handler - called 24 times):
* For each LED[i] (i = 0 to 23):
* a) PPI triggers 32 SAADC samples on ADA2200 SYNCO rising edge
* b) When buffer full, m48_voltage_handler() interrupt fires:
* - Averages 32 samples -> m48_led_pd_data[i]
* - Turns off current LED
* - If more LEDs remain:
* * Increments m_pd_adc_cnt
* * Configures next LED via CAT9532
* * Sets DAC value for next LED
* * Calls m48_adc_start() for next measurement
* - If last LED done:
* * Calls m48_adc_end_final()
*
* 4. Finalization (m48_adc_end_final):
* - Disables PPI, GPIOTE, SAADC
* - Uninitializes peripherals
* - If info4 == true, reads:
* * Battery voltage (uint16_t)
* * Temperature (uint16_t)
* * IMU data (6 x uint16_t)
* * Pressure P1, P2 (2 x uint16_t)
* - Formats response with "rcj:" tag
* - Sends via BLE: [batt, temp, imu[6], p1, p2, led_data[24]]
* - Restarts battery timer
* - Clears processing flag
*
* ============================================================================
* RESPONSE DATA FORMAT (rcj: tag - 72 bytes total)
* ============================================================================
*
* Byte[0-3]: "rcj:" tag (4 bytes)
* Byte[4-5]: Battery voltage (uint16_t, big-endian)
* Byte[6-7]: Temperature (uint16_t, big-endian)
* Byte[8-19]: IMU data (6 x uint16_t = 12 bytes)
* Byte[20-21]: Pressure P1 (uint16_t)
* Byte[22-23]: Pressure P2 (uint16_t)
* Byte[24-71]: LED measurement data (24 x uint16_t = 48 bytes)
*
* ============================================================================
* Measurement Modes (ADC_PD_MODE):
* ============================================================================
* 0: Full 48 LED measurement (single shot)
* 1: Full 48 LED measurement (continuous)
* 2: Half measurement - LEDs 0-23 (single shot) - mcj? command
* 3: Half measurement - LEDs 0-23 (continuous)
* 4: Half measurement - LEDs 24-47 (single shot)
* 5: Half measurement - LEDs 24-47 (continuous)
*
* Hardware Dependencies:
* - ADA2200 Lock-in Amplifier (SYNCO pin P0.17 for PPI trigger)
* - MCP4725 DAC for PD gain control (I2C)
* - CAT9532 LED driver (I2C)
* - SAADC for differential ADC measurement (AIN0/AIN1)
* - EEPROM for storing AGC calibration values (addr 0x0480)
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "nrfx_gpiote.h"
#include "app_timer.h"
#include "nrf_drv_timer.h"
#include "nrf_delay.h"
#include "nrf_drv_ppi.h"
#include "ada2200_spi.h"
#include "ble_nus.h"
#include "measurements.h"
#include "meas_pd_48.h"
#include "mcp4725_i2c.h"
//#include "ad5272_i2c.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "tmp235_q1.h"
#include "main.h"
#include "app_raw_main.h"
#include <cmd_parse.h>
#include "debug_print.h"
/*============================================================================*/
/* External Variables - Charles KWON */
/*============================================================================*/
/** @brief Flag to include additional sensor data (batt, temp, IMU, pressure) */
extern bool info4;
/*============================================================================*/
/* Configuration Variables - Charles KWON */
/*============================================================================*/
/**
* @brief Current measurement mode
* - Charles KWON
*
* Values:
* 0: Full 48 LED (single) 1: Full 48 LED (continuous)
* 2: Half 0-23 (single) 3: Half 0-23 (continuous)
* 4: Half 24-47 (single) 5: Half 24-47 (continuous)
*/
uint8_t ADC_PD_MODE = 0;
/** @brief Number of ADC samples per LED measurement */
uint8_t m48_samples_in_buffer = 8;
/** @brief Current number of LEDs to measure */
uint8_t CURRENT_LED_NO = 24;
/** @brief Working LED index list for current measurement */
uint8_t CURRENT_list_m48[m48_LED_NO] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
/*============================================================================*/
/* LED Configuration Arrays - Charles KWON */
/*============================================================================*/
/** @brief Full LED list (0-23) */
static const uint8_t LED_list_m48[m48_LED_NO] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
/** @brief First half LED list (0-23) for modes 2,3 */
static const uint8_t LED_list_m48_1h[m48_LED_NO_H] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
/** @brief Second half LED list (24-47) for modes 4,5 */
static const uint8_t LED_list_m48_2h[m48_LED_NO_H] = {24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47};
/** @brief PD delay in microseconds */
extern uint16_t m_pd_delay_us;
/*============================================================================*/
/* State Variables - Charles KWON */
/*============================================================================*/
/** @brief Command processing flag */
extern volatile bool processing;
/** @brief BLE TX in progress flag */
extern volatile bool data_tx_in_progress;
/** @brief BLE connection status */
extern volatile bool ble_connection_st;
/** @brief Temperature measurement trigger */
extern bool go_temp;
/** @brief Battery measurement trigger */
extern bool go_batt;
/*============================================================================*/
/* Sensor Data Storage - Charles KWON */
/*============================================================================*/
/** @brief Battery level (mV) */
uint16_t volatile info_batt = 0;
/** @brief Temperature reading */
uint16_t volatile info_temp = 0;
/** @brief IMU data array [accX, accY, accZ, gyrX, gyrY, gyrZ] */
uint16_t volatile info_imu[6] = {0, 1, 2, 3, 4, 5};
/** @brief Pressure sensor 1 value */
uint16_t volatile info_p1 = 0;
/** @brief Pressure sensor 2 value */
uint16_t volatile info_p2 = 0;
/*============================================================================*/
/* Measurement State Machine Variables - Charles KWON */
/*============================================================================*/
/** @brief Measurement cycle counter */
uint32_t m48_cnt;
/** @brief Current PD index (-1 = not started) */
static int8_t pd_no = -1;
/** @brief Current LED index (-1 = not started) */
static int8_t led_no = -1;
/** @brief Current buffer index */
static int8_t buf_no = 0;
/** @brief Elapsed time in milliseconds */
static int32_t t_ms = 0;
/** @brief Order counter for response sequencing */
uint8_t order_pd = 0;
/*============================================================================*/
/* ADC Data Buffers - Charles KWON */
/*============================================================================*/
/**
* @brief Raw ADC sample buffer for each LED
* - Charles KWON
*
* Dimensions: [24 LEDs][32 samples max per LED]
*/
int16_t buff_m48_cycle[m48_LED_NO][m48_CYCLE_CNT] = {0};
/** @brief Summed ADC values for each LED (signed) */
int16_t m48_cycle_send_buff[m48_LED_NO] = {0};
/** @brief Summed ADC values for each LED (unsigned, for BLE TX) */
uint16_t single_bi_m48_cycle_send_buff[m48_LED_NO] = {0};
/** @brief ADC buffer size including delay samples */
#define SAMPLES_IN_BUFFER (4095 + 32)
/** @brief Double-buffered ADC sample storage */
static nrf_saadc_value_t pd_m48_adc_buf[2][SAMPLES_IN_BUFFER];
/**
* @brief Combined sensor + LED data buffer for BLE transmission
* - Charles KWON
*
* Layout: [batt, temp, imu[6], p1, p2, led_data[24]]
*/
uint16_t single_info_m48_cycle_send_buff[m48_LED_NO] = {0};
/*============================================================================*/
/* Measurement State Flags - Charles KWON */
/*============================================================================*/
/** @brief M48 measurement start trigger */
bool pd_adc_m48_start = false;
/** @brief M48 continuous measurement running */
bool pd_adc_m48_running = false;
/** @brief M48 measurement in progress */
bool m48_testing = false;
/** @brief New BLE data received flag */
extern bool ble_got_new_data;
/** @brief IMU raw data enabled flag */
extern bool motion_raw_data_enabled;
/*============================================================================*/
/* Communication Buffers - Charles KWON */
/*============================================================================*/
/** @brief BLE binary transmission buffer */
uint8_t m48_bin_buffer[BLE_NUS_MAX_DATA_LEN];
/** @brief Command source type */
extern which_cmd_t cmd_type_t;
/*============================================================================*/
/* Timer Definitions - Charles KWON */
/*============================================================================*/
/** @brief Timer for measurement time tracking */
APP_TIMER_DEF(m_m48_check_loop_timer_id);
/** @brief Send interval for delayed transmission */
#if FEATURE_DELAY
#define m48_SEND_LOOP_INTERVAL 500
#else
#define m48_SEND_LOOP_INTERVAL 100
#endif
/** @brief Check loop interval (1ms) */
#define m48_CHECK_LOOP_INTERVAL 1
/** @brief PPI channel for SYNCO->SAADC trigger */
static nrf_ppi_channel_t m_ppi_channel;
/*============================================================================*/
/* PPI Configuration - Charles KWON */
/*============================================================================*/
#if !FEATURE_PRINTF
/**
* @brief Initialize PPI for ADA2200 SYNCO triggered ADC sampling
* - Charles KWON
*
* Connects ADA2200 SYNCO rising edge to SAADC sample task.
*/
void m48_ppi_init(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
uint32_t gpiote_event_addr = nrf_drv_gpiote_in_event_addr_get(ADA2200_SYNCO_PIN);
uint32_t saadc_sample_task_addr = nrf_drv_saadc_sample_task_get();
err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
gpiote_event_addr,
saadc_sample_task_addr);
APP_ERROR_CHECK(err_code);
}
/**
* @brief Uninitialize PPI
* - Charles KWON
*/
void m48_ppi_uninit(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_uninit();
APP_ERROR_CHECK(err_code);
}
/**
* @brief Enable PPI channel for ADC sampling
* - Charles KWON
*/
void m48_sampling_event_enable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
/**
* @brief Disable PPI channel
* - Charles KWON
*/
void m48_sampling_event_disable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_disable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
#endif
/*============================================================================*/
/* ADC Interrupt Handler - Charles KWON */
/*============================================================================*/
/**
* @brief SAADC event handler for M48 measurement
* - Charles KWON
*
* Implements the LED-PD measurement state machine:
* 1. On first call: activate first LED and set DAC gain
* 2. Store ADC samples for current LED
* 3. Advance to next LED or complete measurement
* 4. On completion: sum samples, format response, send via BLE
*
* Response tags by mode:
* - Mode 0: rsj: (Full with sensor data)
* - Mode 2: rcj: (Half 0-23 with sensor data)
* - Mode 3: rdj: (Half 0-23 continuous)
* - Mode 4: rej: (Half 24-47 with sensor data)
* - Mode 5: rfj: (Half 24-47 continuous)
*
* @param[in] p_event Pointer to SAADC event structure
*/
static void m48_voltage_handler(nrf_drv_saadc_evt_t const * p_event)
{
ret_code_t err_code;
int16_t sum = 0;
uint32_t m48_clk_delay = m_pd_delay_us / 16;
/* Handle BLE disconnection during measurement */
if (ble_connection_st == 0) {
DBG_PRINTF("m48 ADC STOP 1\r\n");
led_off(99);
pd_off(99);
pd_adc_m48_start = false;
pd_adc_m48_running = false;
m48_testing = false;
info4 = false;
DBG_PRINTF("LOST_AT48\r\n");
processing = false;
go_batt = false;
go_temp = false;
m48_adc_end_final();
} else {
if (p_event->type == NRF_DRV_SAADC_EVT_DONE) {
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer,
m48_samples_in_buffer + m48_clk_delay);
APP_ERROR_CHECK(err_code);
/*----------------------------------------------------------------*/
/* First call: Initialize LED/PD */
/*----------------------------------------------------------------*/
if (led_no == -1) {
led_no = 0;
pd_no = 0;
led_on(CURRENT_list_m48[led_no]);
led_pd_matching_value_set(CURRENT_list_m48[led_no]);
} else {
/*------------------------------------------------------------*/
/* Store ADC samples for current LED */
/*------------------------------------------------------------*/
for (uint16_t i = m48_clk_delay; i < m48_clk_delay + m48_samples_in_buffer; i++) {
buff_m48_cycle[buf_no][i - m48_clk_delay] = p_event->data.done.p_buffer[i];
}
#if FEATURE_PRINTF
DBG_PRINTF("-----------------Read ADC // led_no = %d(%d), pd_no = %d(%d), buf_no = %d\r\n\r\n",
led_no, LED_list_m48[led_no], pd_no, PD_list_m48[pd_no], buf_no);
#endif
buf_no++;
/*------------------------------------------------------------*/
/* Advance to next LED or complete */
/*------------------------------------------------------------*/
if (pd_no >= -1) {
if (led_no < CURRENT_LED_NO - 1) {
/* Next LED */
led_no++;
led_on(CURRENT_list_m48[led_no]);
led_pd_matching_value_set(CURRENT_list_m48[led_no]);
} else if (led_no >= CURRENT_LED_NO - 1) {
/*----------------------------------------------------*/
/* All LEDs measured - process and send results */
/*----------------------------------------------------*/
pd_no = -1;
led_no = -1;
#if FEATURE_PRINTF
DBG_PRINTF("\r\nEnded\r\n");
#endif
/* Sum samples for each LED */
uint8_t k = 0;
sum = 0;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
for (uint16_t j = 0; j < m48_samples_in_buffer; j++) {
sum += buff_m48_cycle[i][j];
}
m48_cycle_send_buff[k++] = sum;
sum = 0;
}
buf_no = 0;
if (ble_connection_st == 0) {
DBG_PRINTF("m24 ADC STOP 1");
} else {
pd_adc_m48_start = false;
DBG_PRINTF("FINISH SEND\r\n");
m48_testing = false;
if (cmd_type_t == CMD_UART) {
/* UART response handled elsewhere */
} else if (cmd_type_t == CMD_BLE) {
/* Convert to unsigned for BLE transmission */
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
DBG_PRINTF("%d,", m48_cycle_send_buff[i]);
single_bi_m48_cycle_send_buff[i] = (uint16_t)(m48_cycle_send_buff[i]);
}
/*--------------------------------------------*/
/* Format response with sensor data */
/*--------------------------------------------*/
if (info4 == true) {
single_info_m48_cycle_send_buff[0] = info_batt;
single_info_m48_cycle_send_buff[1] = info_temp;
for (uint16_t i = 0; i < 6; i++) {
single_info_m48_cycle_send_buff[i + 2] = info_imu[i];
}
single_info_m48_cycle_send_buff[8] = info_p1;
single_info_m48_cycle_send_buff[9] = info_p2;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
single_info_m48_cycle_send_buff[i + 10] = single_bi_m48_cycle_send_buff[i];
}
/* Send response based on mode */
if (ADC_PD_MODE == 0) {
format_data(m48_bin_buffer, "rsj:", single_info_m48_cycle_send_buff, 56);
binary_tx_handler(m48_bin_buffer, 58);
} else if (ADC_PD_MODE == 2) {
format_data(m48_bin_buffer, "rcj:", single_info_m48_cycle_send_buff, 34);
binary_tx_handler(m48_bin_buffer, 36);
} else if (ADC_PD_MODE == 3) {
format_data(m48_bin_buffer, "rdj:", single_info_m48_cycle_send_buff, 34);
binary_tx_handler(m48_bin_buffer, 36);
} else if (ADC_PD_MODE == 4) {
format_data(m48_bin_buffer, "rej:", single_info_m48_cycle_send_buff, 34);
binary_tx_handler(m48_bin_buffer, 36);
} else if (ADC_PD_MODE == 5) {
format_data(m48_bin_buffer, "rfj:", single_info_m48_cycle_send_buff, 34);
binary_tx_handler(m48_bin_buffer, 36);
}
} else {
/* Send LED data only (no sensor data) */
char resp[4];
if (ADC_PD_MODE == 2) {
format_data(m48_bin_buffer, "rdj:", single_bi_m48_cycle_send_buff, 24);
binary_tx_handler(m48_bin_buffer, 26);
} else if (ADC_PD_MODE == 4) {
format_data(m48_bin_buffer, "rfj:", single_bi_m48_cycle_send_buff, 24);
binary_tx_handler(m48_bin_buffer, 26);
} else if (ADC_PD_MODE == 3) {
sprintf(resp, "rd%01X:", order_pd);
format_data(m48_bin_buffer, resp, single_bi_m48_cycle_send_buff, 24);
binary_tx_handler(m48_bin_buffer, 26);
} else if (ADC_PD_MODE == 5) {
sprintf(resp, "rf%01X:", order_pd);
format_data(m48_bin_buffer, resp, single_bi_m48_cycle_send_buff, 24);
binary_tx_handler(m48_bin_buffer, 26);
}
}
DBG_PRINTF("\r\n %d ms \r\n", t_ms);
}
/*------------------------------------------------*/
/* Handle continuous mode or cleanup */
/*------------------------------------------------*/
if (pd_adc_m48_running == true) {
if (info4 == true) {
m48_adc_end_final();
pd_adc_m48_start = true;
go_batt = true;
main_timer_start();
} else {
t_ms = 0;
m48_cnt = 0;
m48_testing = true;
m48_adc_start();
}
} else {
/* Single shot - cleanup */
if (info4 == true) {
info4 = false;
}
led_off(99);
pd_off(99);
processing = false;
m48_adc_end_final();
}
}
}
}
}
}
}
}
/*============================================================================*/
/* Timer Callback - Charles KWON */
/*============================================================================*/
/**
* @brief Check timer callback for measurement timing
* - Charles KWON
*
* Tracks elapsed time during measurement.
*
* @param[in] p_context Unused timer context
*/
void m48_check_loop(void * p_context)
{
UNUSED_PARAMETER(p_context);
m48_check_timer_stop();
if (m48_testing == false) {
DBG_PRINTF("%d ms \r\n", t_ms);
} else {
t_ms++;
m48_check_timer_start();
}
}
/*============================================================================*/
/* Measurement Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Initialize and start M48 measurement based on mode
* - Charles KWON
*
* Called from main_timer to begin measurement sequence.
* Configures LED list based on ADC_PD_MODE setting.
*/
void m48_adc_start_init(void)
{
/* Handle new data received during continuous mode */
if (ble_got_new_data == true) {
if (pd_adc_m48_running == true) {
led_off(99);
pd_off(99);
pd_adc_m48_start = false;
pd_adc_m48_running = false;
DBG_PRINTF("FINISH NEWDATA\r\n");
processing = false;
go_batt = false;
go_temp = false;
motion_raw_data_enabled = false;
m48_testing = false;
if (info4 == true) {
info4 = false;
} else {
m48_adc_end_final();
}
}
} else if (pd_adc_m48_start == true) {
/* Configure measurement based on mode */
switch (ADC_PD_MODE) {
case 0: /* Full 48 LED (single) */
pd_adc_m48_running = false;
CURRENT_LED_NO = m48_LED_NO;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
CURRENT_list_m48[i] = LED_list_m48[i];
}
break;
case 1: /* Full 48 LED (continuous) */
pd_adc_m48_running = true;
CURRENT_LED_NO = m48_LED_NO;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
CURRENT_list_m48[i] = LED_list_m48[i];
}
break;
case 2: /* Half 0-23 (single) - mcj? */
pd_adc_m48_running = false;
CURRENT_LED_NO = m48_LED_NO_H;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
CURRENT_list_m48[i] = LED_list_m48_1h[i];
}
break;
case 3: /* Half 0-23 (continuous) */
pd_adc_m48_running = true;
CURRENT_LED_NO = m48_LED_NO_H;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
CURRENT_list_m48[i] = LED_list_m48_1h[i];
}
break;
case 4: /* Half 24-47 (single) */
pd_adc_m48_running = false;
CURRENT_LED_NO = m48_LED_NO_H;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
CURRENT_list_m48[i] = LED_list_m48_2h[i];
DBG_PRINTF("%d,", CURRENT_list_m48[i]);
}
break;
case 5: /* Half 24-47 (continuous) */
pd_adc_m48_running = true;
CURRENT_LED_NO = m48_LED_NO_H;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
CURRENT_list_m48[i] = LED_list_m48_2h[i];
}
break;
default:
pd_adc_m48_running = false;
CURRENT_LED_NO = m48_LED_NO;
for (uint16_t i = 0; i < CURRENT_LED_NO; i++) {
CURRENT_list_m48[i] = LED_list_m48[i];
}
break;
}
t_ms = 0;
m48_cnt = 0;
pd_adc_m48_start = false;
m48_testing = true;
m48_adc_start2();
m48_check_timer_start();
}
}
/**
* @brief Initialize measurement buffers and state
* - Charles KWON
*/
void m48_adc_start(void)
{
pd_no = -1;
led_no = -1;
buf_no = 0;
/* Clear measurement buffers */
for (uint16_t i = 0; i < m48_LED_NO; i++) {
for (uint16_t j = 0; j < m48_CYCLE_CNT; j++) {
buff_m48_cycle[i][j] = 0;
if (ble_got_new_data == true) {
if (pd_adc_m48_running == true) {
pd_adc_m48_start = false;
pd_adc_m48_running = false;
}
}
}
}
/* Update order counter for continuous mode */
if (order_pd >= 15) {
order_pd = 0;
} else {
order_pd++;
}
}
/**
* @brief Start M48 measurement with full initialization
* - Charles KWON
*/
void m48_adc_start2(void)
{
m48_adc_start();
m48_adc_init();
m48_irq_init();
m48_ppi_init();
m48_sampling_event_enable();
}
/**
* @brief Stop ADC sampling (partial cleanup)
* - Charles KWON
*/
void m48_adc_end(void)
{
DBG_PRINTF("m48_adc_end\r\n");
m48_sampling_event_disable();
}
/**
* @brief Complete measurement cleanup
* - Charles KWON
*
* Disables PPI, uninitializes SAADC, restarts battery timer.
*/
void m48_adc_end_final(void)
{
DBG_PRINTF("adc_end_final\r\n");
m48_sampling_event_disable();
m48_irq_uninit();
m48_ppi_uninit();
m48_adc_uninit();
battery_timer_start();
}
/*============================================================================*/
/* SAADC Configuration - Charles KWON */
/*============================================================================*/
/**
* @brief Initialize SAADC for M48 measurement
* - Charles KWON
*
* Configures differential ADC on AIN0/AIN1 with 10-bit resolution.
*/
void m48_adc_init(void)
{
#if FEATURE_PRINTF
DBG_PRINTF("m48_adc_init\r\n");
#endif
static nrfx_saadc_config_t default_config;
default_config.resolution = (nrf_saadc_resolution_t)NRFX_SAADC_CONFIG_RESOLUTION;
default_config.oversample = (nrf_saadc_oversample_t)NRFX_SAADC_CONFIG_OVERSAMPLE;
default_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY;
default_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE;
static nrf_saadc_channel_config_t config;
config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
config.gain = NRF_SAADC_GAIN1_6;
config.reference = NRF_SAADC_REFERENCE_INTERNAL;
config.acq_time = NRF_SAADC_ACQTIME_3US;
config.mode = NRF_SAADC_MODE_DIFFERENTIAL;
config.burst = NRF_SAADC_BURST_DISABLED;
config.pin_p = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN0);
config.pin_n = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN1);
ret_code_t err_code = nrf_drv_saadc_init(&default_config, m48_voltage_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(pd_m48_adc_buf[0],
m48_samples_in_buffer + m_pd_delay_us / 16);
APP_ERROR_CHECK(err_code);
}
/**
* @brief Uninitialize SAADC
* - Charles KWON
*/
void m48_adc_uninit(void)
{
#if FEATURE_PRINTF
DBG_PRINTF("pd_m48_adc_uninit\r\n");
#endif
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
}
/*============================================================================*/
/* GPIO Interrupt Configuration - Charles KWON */
/*============================================================================*/
#if !FEATURE_PRINTF
/**
* @brief Initialize GPIOTE for ADA2200 SYNCO pin
* - Charles KWON
*
* Configures rising edge detection on SYNCO for PPI trigger.
*/
void m48_irq_init(void)
{
ret_code_t err_code;
if (!nrfx_gpiote_is_init()) {
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
in_config.pull = NRF_GPIO_PIN_PULLDOWN;
err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
}
/**
* @brief Uninitialize GPIOTE for SYNCO pin
* - Charles KWON
*/
void m48_irq_uninit(void)
{
nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
}
#endif
/*============================================================================*/
/* Timer Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Start measurement check timer
* - Charles KWON
*/
void m48_check_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_m48_check_loop_timer_id,
APP_TIMER_TICKS(m48_CHECK_LOOP_INTERVAL), NULL));
}
/**
* @brief Stop measurement check timer
* - Charles KWON
*/
void m48_check_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_m48_check_loop_timer_id));
}
/**
* @brief Initialize measurement check timer
* - Charles KWON
*/
void m48_check_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_m48_check_loop_timer_id,
APP_TIMER_MODE_SINGLE_SHOT, m48_check_loop));
}

View File

@@ -0,0 +1,265 @@
/*******************************************************************************
* @file meas_pd_48.h
* @brief M48 LED-PD Measurement Module Header for NIRS System
* @author Charles KWON <charleskwon@medithings.co.kr>
* @version V2.0.0
* @date 2025-12-31
*
* Copyright (c) 2025 Medithings Inc.
* All rights reserved.
*
* This header defines the interface for the M48 measurement module which
* controls 24/48 LED-PD pair measurements using SAADC with PPI triggering
* from the ADA2200 Lock-in Amplifier SYNCO signal.
*
* Hardware Configuration:
* - ADC Input: Differential measurement on AIN0/AIN1
* - Trigger: ADA2200 SYNCO pin (P0.17) via GPIOTE/PPI
* - Gain Control: MCP4725 DAC via I2C
* - LED Control: CAT9532 via I2C
******************************************************************************/
#ifndef _MEAS_PD_48_H__
#define _MEAS_PD_48_H__
/*============================================================================*/
/* Includes - Charles KWON */
/*============================================================================*/
#include "sdk_common.h"
#include "nrf_drv_saadc.h"
/*============================================================================*/
/* Hardware Pin Configuration - Charles KWON */
/*============================================================================*/
/**
* @brief ADA2200 Lock-in Amplifier SYNCO output pin
* - Charles KWON
*
* This pin provides the synchronization signal for ADC sampling.
* Connected to GPIOTE for triggering SAADC via PPI.
*/
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0, 17)
#endif
/*============================================================================*/
/* Measurement Configuration Constants - Charles KWON */
/*============================================================================*/
/**
* @brief Number of photodetectors in the system
* - Charles KWON
*
* Currently configured for single PD operation (PD0).
*/
#define m48_PD_NO 1
/**
* @brief Number of LEDs for full measurement sequence
* - Charles KWON
*
* Full measurement uses 24 LEDs (LED0-LED23) with single PD.
* Modified from original 48 LED configuration (cj edit 25/10/14).
*/
#define m48_LED_NO 24
/**
* @brief Number of LEDs for half measurement sequence
* - Charles KWON
*
* Half measurement uses 24 LEDs for either:
* - Part A: LED0-LED23 (ADC_PD_MODE 3)
* - Part B: LED24-LED47 (ADC_PD_MODE 4)
*/
#define m48_LED_NO_H 24
/**
* @brief Number of ADC samples per LED measurement cycle
* - Charles KWON
*
* Each LED measurement consists of 32 consecutive ADC samples
* triggered by SYNCO signal for averaging/processing.
*/
#define m48_CYCLE_CNT 32
/*============================================================================*/
/* PPI Configuration Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Initialize PPI channel for SYNCO-triggered ADC sampling
* - Charles KWON
*
* Configures PPI to connect GPIOTE SYNCO event to SAADC SAMPLE task.
* This enables hardware-triggered ADC sampling synchronized with
* the ADA2200 lock-in amplifier output.
*/
void m48_ppi_init(void);
/**
* @brief Uninitialize PPI channel
* - Charles KWON
*
* Releases PPI resources after measurement completion.
*/
void m48_ppi_uninit(void);
/**
* @brief Enable PPI sampling event
* - Charles KWON
*
* Activates the SYNCO-to-SAADC PPI channel to begin
* hardware-triggered sampling.
*/
void m48_sampling_event_enable(void);
/**
* @brief Disable PPI sampling event
* - Charles KWON
*
* Deactivates the PPI channel to stop hardware-triggered sampling.
*/
void m48_sampling_event_disable(void);
/*============================================================================*/
/* SAADC Functions - Charles KWON */
/*============================================================================*/
/**
* @brief ADC interrupt handler for voltage measurements
* - Charles KWON
*
* Callback function invoked when ADC buffer is full.
* Processes raw ADC values and advances to next LED in sequence.
*
* @param[in] p_event Pointer to SAADC event structure containing
* conversion results and event type
*
* @note This is a static function - declaration kept for documentation
*/
static void m48_voltage_handler(nrf_drv_saadc_evt_t const * p_event);
/**
* @brief Initialize and start first ADC measurement sequence
* - Charles KWON
*
* Entry point for M48 measurement. Initializes hardware,
* configures first LED, and begins ADC sampling.
*/
void m48_adc_start_init(void);
/**
* @brief Start ADC sampling for current LED
* - Charles KWON
*
* Configures ADC buffer and enables sampling for the
* currently selected LED in the measurement sequence.
*/
void m48_adc_start(void);
/**
* @brief Start ADC sampling with secondary buffer
* - Charles KWON
*
* Alternative start function using secondary ADC buffer
* for double-buffered operation.
*/
void m48_adc_start2(void);
/**
* @brief End current LED measurement and advance to next
* - Charles KWON
*
* Called after ADC interrupt to transition to next LED
* in the measurement sequence.
*/
void m48_adc_end(void);
/**
* @brief Finalize measurement sequence
* - Charles KWON
*
* Called when all LEDs have been measured. Sends results
* over BLE and cleans up hardware resources.
*/
void m48_adc_end_final(void);
/**
* @brief Initialize SAADC for differential measurement
* - Charles KWON
*
* Configures SAADC with:
* - Differential input on AIN0 (positive) / AIN1 (negative)
* - Internal reference (0.6V)
* - 1/4 gain for optimal range
* - 14-bit resolution
*/
void m48_adc_init(void);
/**
* @brief Uninitialize SAADC
* - Charles KWON
*
* Releases SAADC resources for use by other modules.
*/
void m48_adc_uninit(void);
/*============================================================================*/
/* GPIO Interrupt Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Initialize GPIOTE for SYNCO signal detection
* - Charles KWON
*
* Configures GPIOTE channel to detect rising edges on
* ADA2200 SYNCO pin for PPI triggering.
*/
void m48_irq_init(void);
/**
* @brief Uninitialize GPIOTE
* - Charles KWON
*
* Releases GPIOTE resources after measurement completion.
*/
void m48_irq_uninit(void);
/*============================================================================*/
/* Measurement Timer Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Measurement check timer callback
* - Charles KWON
*
* Periodic timer callback for monitoring measurement progress
* and handling timeout conditions.
*
* @param[in] p_context Unused timer context parameter
*/
void m48_check_loop(void * p_context);
/**
* @brief Start measurement check timer
* - Charles KWON
*/
void m48_check_timer_start(void);
/**
* @brief Stop measurement check timer
* - Charles KWON
*/
void m48_check_timer_stop(void);
/**
* @brief Initialize measurement check timer
* - Charles KWON
*
* Creates the timer used for measurement monitoring.
* Must be called during system initialization.
*/
void m48_check_timer_init(void);
#endif /* _MEAS_PD_48_H__ */

View File

@@ -0,0 +1,785 @@
/*******************************************************************************
TEST medi50 Dec 23
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "nrfx_gpiote.h"
#include "app_timer.h"
#include "nrf_drv_timer.h"
#include "nrf_delay.h"
#include "nrf_drv_ppi.h"
#include "ada2200_spi.h"
#include "ble_nus.h"
//#include "fstorage.h"
#include "measurements.h"
#include "meas_pd_buff.h"
#include "mcp4725_i2c.h"
#include "ad5272_i2c.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "main.h"
#include "app_raw_main.h"
//#define CUSTOM_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
//#define CUSTOM_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
//#define CUSTOM_ADC_RES_10BITS 1024.0f /**< Maximum digital value for 10-bit ADC conversion. */
//#define st_c_max 20
///**@brief Macro to convert the result of ADC conversion in millivolts.
// *
// * @param[in] ADC_VALUE ADC result.
// *
// * @retval Result converted to millivolts.
// */
//#define CUSTOM_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\
// (((((ADC_VALUE) * CUSTOM_REF_VOLTAGE_IN_MILLIVOLTS) / CUSTOM_ADC_RES_10BITS) * CUSTOM_PRE_SCALING_COMPENSATION)*2)
uint16_t b_t_cnt=5;
uint8_t buff_samples_in_buffer = 8;
//uint32_t s_cnt;
uint8_t LED_list_buff[buff_LED_NO]={0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0
,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0};
//uint8_t PD_list_custom[4];
//uint8_t led_custom_list_a[5] = {2,3,4,5,6};
//uint8_t pd_custom_list_a[4] = {5,7,8,10};
//uint8_t led_custom_list_b[5] = {20,21,22,23,24};
//uint8_t pd_custom_list_b[4] = {16,14,13,11};
//int32_t c_max;
extern volatile bool processing;
extern volatile bool data_tx_in_progress;
extern volatile bool ble_connection_st;
uint32_t buff_cnt;
static int8_t pd_no = -1;
static int8_t led_no = -1;
static int8_t buf_no = 0;
static int8_t order = 0;
uint16_t bsel_led_index0 =0;
uint16_t bsel_led_index1 =1;
uint16_t bsel_led_index2 =2;
uint16_t bsel_led_index3 =3;
static int32_t t_ms = 0;
int16_t buff_cycle_buff[buff_LED_NO][buff_CYCLE_CNT] =
{
{0, 0, 0, 0, 0, 0, 0, 0}, //0
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //10
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //20
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //30
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //40
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //50
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //60
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //70
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //80
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0}, //90
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0}, //9
};
int16_t buff_cycle_send_buff[buff_LED_NO] = {0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0
,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0};
//int16_t buff_cycle_send_buff1[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff2[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff3[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff4[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff5[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff6[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff7[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff8[buff_LED_NO] = {0, };
//int16_t buff_cycle_send_buff9[buff_LED_NO] = {0, };
uint16_t bi_buff_cycle_send_buff[60][buff_LED_NO] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0
,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0},
};
uint16_t single_bi_buff_cycle_send_buff[buff_LED_NO] =
{0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0
,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0,0};
#define SAMPLES_IN_BUFFER 4095+32
static nrf_saadc_value_t pd_buff_adc_buf[2][SAMPLES_IN_BUFFER];
//bool pd_adc_custom_a_start = false;
//bool pd_adc_custom_b_start = false;
//bool pd_adc_custom_c_start = false;
//bool pd_adc_custom_d_start = false;
//bool pd_adc_custom_end = false;
//bool pd_adc_custom_start = false;
bool pd_adc_buff_start = false;
bool pd_adc_buff_running = false;
bool buff_testing = false;
extern uint16_t m_pd_delay_us;
//
//bool custom_add_data;
extern bool ble_got_new_data;
extern bool motion_raw_data_enabled;
//extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
uint8_t buff_bin_buffer[BLE_NUS_MAX_DATA_LEN];
//add imu
extern which_cmd_t cmd_type_t;
APP_TIMER_DEF(m_buff_check_loop_timer_id);
APP_TIMER_DEF(m_buff_send_loop_timer_id);
#if FEATURE_DELAY
#define CUSTOM_SEND_LOOP_INTERVAL 500
#else
#define BUFF_SEND_LOOP_INTERVAL 100
#endif
#define BUFF_CHECK_LOOP_INTERVAL 1
static nrf_ppi_channel_t m_ppi_channel;
#if !FEATURE_PRINTF
void buff_ppi_init(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
uint32_t gpiote_event_addr = nrf_drv_gpiote_in_event_addr_get(ADA2200_SYNCO_PIN);
uint32_t saadc_sample_task_addr = nrf_drv_saadc_sample_task_get();
/* setup ppi channel so that timer compare event is triggering sample task in SAADC */
err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
gpiote_event_addr,
saadc_sample_task_addr);
APP_ERROR_CHECK(err_code);
}
void buff_ppi_uninit(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_uninit();
APP_ERROR_CHECK(err_code);
}
void buff_sampling_event_enable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
void buff_sampling_event_disable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_disable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
#endif
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void buff_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* PD Voltage reading */
{
ret_code_t err_code;
int16_t sum = 0;
uint32_t buff_clk_delay = m_pd_delay_us/16;
if(ble_connection_st == 0) {
//imm_adc_end();
//custom_send_timer_stop();
printf("Custom ADC STOP 1\r\n");
}
else{
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, buff_samples_in_buffer + buff_clk_delay);
APP_ERROR_CHECK(err_code);
if(led_no == -1) {
led_no = 0;
pd_no = 0;
led_on(LED_list_buff[led_no]);
led_pd_matching_value_set(LED_list_buff[led_no]); /* MCP4725 DAC setting and PD on */
} else {
for(uint16_t i = buff_clk_delay; i < buff_clk_delay + buff_samples_in_buffer; i++){
buff_cycle_buff[buf_no][i-buff_clk_delay] = p_event->data.done.p_buffer[i];
}
#if FEATURE_PRINTF
printf("-----------------Read ADC // led_no = %d(%d), pd_no = %d(%d), buf_no = %d\r\n\r\n", led_no, LED_list_custom[led_no], pd_no, PD_list_custom[pd_no], buf_no);
#endif
buf_no++;
if(pd_no >= - 1) {
//pd_no = 0;
if(led_no < buff_LED_NO - 1) {
led_no++;
led_on(LED_list_buff[led_no]);
led_pd_matching_value_set(LED_list_buff[led_no]); /* MCP4725 DAC setting and PD on */
} else if(led_no >= buff_LED_NO - 1) {
pd_no = -1;
led_no = -1;
#if FEATURE_PRINTF
printf("\r\nEnded\r\n");
#endif
//imm_adc_end();
//imm_adc_end_final();
///////////////////////////////////////////////////////////////////////////////////////////
uint8_t k =0;
sum = 0;
for(uint16_t i = 0; i < buff_LED_NO; i++){
for(uint16_t j = 0; j < buff_samples_in_buffer; j++){
sum += buff_cycle_buff[i][j];
}
buff_cycle_send_buff[k++] = sum;
sum = 0;
}
buf_no = 0;
if(ble_connection_st == 0) {
printf("Custom ADC STOP 1");
pd_adc_buff_start=false;
pd_adc_buff_running=false;
printf ("FINISH SEND\r\n");
processing = false;
buff_testing = false;
buff_adc_end_final();
}
else {
if(cmd_type_t == CMD_UART) {
//custom_send_timer_stop();
}else if(cmd_type_t == CMD_BLE) {
// printf("%d ms \r\n",t_ms);
//printf("value 4: %d,%d,%d,%d \r\n",imm_cycle_send_buff[0] ,imm_cycle_send_buff[1] ,imm_cycle_send_buff[2] ,imm_cycle_send_buff[3] );
for(uint16_t i = 0; i < buff_LED_NO; i++){
printf("%d,",buff_cycle_send_buff[i]);
bi_buff_cycle_send_buff[buff_cnt][i]=(uint16_t)(buff_cycle_send_buff[i]);
//single_bi_buff_cycle_send_buff[i]=(uint16_t)(buff_cycle_send_buff[i]);
}
printf("%d ms \r\n",t_ms);
// format_data(buff_bin_buffer, "rjb:", bi_buff_cycle_send_buff, 200);
//
//
// binary_tx_handler(buff_bin_buffer,102);
}
// if(ble_connection_st == 1) {
// battery_timer_start();
// }
led_off(99);
pd_off(99);
buff_adc_start_init();
}
}
}
}
}
}
}
void buff_check_loop(void * p_context)
{
UNUSED_PARAMETER(p_context);
buff_check_timer_stop();
if ( buff_testing == false)
{
printf("%d ms \r\n",t_ms);
// sprintf(custom_tx_buffer, "Measure Time :%d ms \r\n",t_ms);
}
else
{
t_ms++;
buff_check_timer_start();
}
}
void buff_send_loop(void * p_context) /* For x ms */
{
UNUSED_PARAMETER(p_context);
buff_send_timer_stop();
char resp[4];
if(ble_connection_st == BLE_DISCONNECTED_ST) {
// order=0;
// full_send_timer_stop();
buff_adc_end_final();
printf("BUFF ADC STOP 2\r\n");
processing = false;
}
else if(order<4){
sprintf(resp,"r%02d:", order);
format_data(buff_bin_buffer, resp, bi_buff_cycle_send_buff[order],100);
binary_tx_handler(buff_bin_buffer,102);
order++;
buff_send_timer_start();
}
else
{
printf("ddddd%d ms \r\n",t_ms);
processing = false;
buff_adc_end_final();
//battery_timer_start();
}
}
// switch(order) {
// case 0:
// sprintf(resp,"r%02X:", order);
// format_data(buff_bin_buffer, resp, bi_buff_cycle_send_buff[0],100);
// binary_tx_handler(buff_bin_buffer,102);
// break;
// case 1:
// format_data(buff_bin_buffer, "rb1:", bi_buff_cycle_send_buff[1],100);
// binary_tx_handler(buff_bin_buffer,102);
//
// break;
// case 2:
// format_data(buff_bin_buffer, "rb2:", bi_buff_cycle_send_buff[2],100);
// binary_tx_handler(buff_bin_buffer,102);
//
// break;
// case 3:
// format_data(buff_bin_buffer, "rb3:", bi_buff_cycle_send_buff[3],100);
// binary_tx_handler(buff_bin_buffer,102);
// break;
// case 4:
// format_data(buff_bin_buffer, "rb4:", bi_buff_cycle_send_buff[4],100);
// binary_tx_handler(buff_bin_buffer,102);
// break;
//
// case 5:
// format_data(buff_bin_buffer, "rb5:", bi_buff_cycle_send_buff[5],100);
// binary_tx_handler(buff_bin_buffer,102);
// break;
//
//
// case 6:
// format_data(buff_bin_buffer, "rb6:", bi_buff_cycle_send_buff[6],100);
// binary_tx_handler(buff_bin_buffer,102);
// break;
// case 7:
// format_data(buff_bin_buffer, "rb7:", bi_buff_cycle_send_buff[7],100);
// binary_tx_handler(buff_bin_buffer,102);
//
// break;
// case 8:
// format_data(buff_bin_buffer, "rb8:", bi_buff_cycle_send_buff[8],100);
// binary_tx_handler(buff_bin_buffer,102);
//
// break;
// case 9:
// format_data(buff_bin_buffer, "rb9:", bi_buff_cycle_send_buff[9],100);
// binary_tx_handler(buff_bin_buffer,102);
// break;
//
//
//
//
//
// case 10:
// printf("Measure Time : %d ms \r\n",t_ms);
// //sprintf(full_tx_buffer, "Measure Time :%d ms \r\n",t_ms);
// // data_tx_handler(full_tx_buffer);
// order = 0;
// return;
//#if FEATURE_PRINTF
// printf("full_send Completed\r\n");
//#endif
// default:
//
// break;
// }
void buff_adc_start_init(void)
{
if (ble_got_new_data ==true)
{
pd_adc_buff_start=false;
pd_adc_buff_running=false;
printf ("FINISH SEND\r\n");
led_off(99);
pd_off(99);
processing = false;
buff_testing = false;
buff_adc_end_final();
}
else if (pd_adc_buff_running==true){
if(buff_cnt>=b_t_cnt){
led_off(99);
pd_off(99);
pd_adc_buff_start=false;
pd_adc_buff_running=false;
printf ("FIFNISH SEND\r\n");
buff_testing = false;
order =0;
buff_send_timer_start();
//buff_adc_start();
}
else{
buff_adc_start();
buff_cnt++;
}
}
else if (pd_adc_buff_start==true)
{
// custom_testing = false;
// motion_raw_data_enabled = true;
// custom_add_data = true;
// icm42670_main();
for(uint8_t i =0; i < 25; i++) {
LED_list_buff[i*4] = bsel_led_index0;
LED_list_buff[i*4+1] = bsel_led_index1;
LED_list_buff[i*4+2] = bsel_led_index2;
LED_list_buff[i*4+3] = bsel_led_index3;
// data_tx_handler(ble_tx_buffer);
}
for(uint8_t i =0; i < 60; i++) {
for(uint8_t j =0; j < 100; j++) {
bi_buff_cycle_send_buff[i][j]=0;
}
}
t_ms=0;
buff_cnt=0;
pd_adc_buff_start=false;
pd_adc_buff_running=true;
buff_testing = true;
// custom_check_timer_start();
// c_cnt=0;
// pd_adc_custom_start=false;
// pd_adc_custom_a_start=true;
buff_adc_start2();
buff_check_timer_start();
// c_cnt++;
}
else
{
}
}
void buff_adc_start(void)
{
pd_no = -1;
led_no = -1;
buf_no = 0;
for(uint16_t i = 0; i < buff_LED_NO; i++) {
for(uint16_t j = 0; j < buff_CYCLE_CNT; j++) {
buff_cycle_buff[i][j] = 0;
}
}
}
void buff_adc_start2(void)
{
buff_adc_start();
buff_adc_init();
buff_irq_init();
buff_ppi_init();
buff_sampling_event_enable();
}
void buff_adc_end(void)
{
printf("adc_end\r\n");
buff_sampling_event_disable();
}
void buff_adc_end_final(void)
{
printf("adc_end_for_good\r\n");
buff_sampling_event_disable();
buff_irq_uninit();
buff_ppi_uninit();
buff_adc_uninit();
}
void buff_adc_init(void)
{
#if FEATURE_PRINTF
printf("custom_adc_init\r\n");
#endif
static nrfx_saadc_config_t default_config;
default_config.resolution = (nrf_saadc_resolution_t)NRFX_SAADC_CONFIG_RESOLUTION; /* Resolution is 10bits */
default_config.oversample = (nrf_saadc_oversample_t)NRFX_SAADC_CONFIG_OVERSAMPLE; /* Over Sampling Disabled */
default_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY; /* Interrupt Priority is 0(Highest) */
default_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE; /* Low Power Mode is Disabled */
static nrf_saadc_channel_config_t config;
config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
config.gain = NRF_SAADC_GAIN1_6;
config.reference = NRF_SAADC_REFERENCE_INTERNAL;
config.acq_time = NRF_SAADC_ACQTIME_3US;
config.mode = NRF_SAADC_MODE_DIFFERENTIAL;
config.burst = NRF_SAADC_BURST_DISABLED;
config.pin_p = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN0);
config.pin_n = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN1);
ret_code_t err_code = nrf_drv_saadc_init(&default_config, buff_voltage_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(pd_buff_adc_buf[0], buff_samples_in_buffer + m_pd_delay_us/16);
APP_ERROR_CHECK(err_code);
}
void buff_adc_uninit(void)
{
#if FEATURE_PRINTF
printf("pd_custom_adc_uninit\r\n");
#endif
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
}
#if !FEATURE_PRINTF
void buff_irq_init(void)
{
ret_code_t err_code;
/* Initialize int pin */
if (!nrfx_gpiote_is_init())
{
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
in_config.pull = NRF_GPIO_PIN_PULLDOWN;
err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
}
void buff_irq_uninit(void)
{
nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
}
#endif
void buff_send_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_buff_send_loop_timer_id, APP_TIMER_TICKS(BUFF_SEND_LOOP_INTERVAL), NULL));
}
void buff_send_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_buff_send_loop_timer_id));
}
void buff_send_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_buff_send_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, buff_send_loop));
}
void buff_check_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_buff_check_loop_timer_id, APP_TIMER_TICKS(BUFF_CHECK_LOOP_INTERVAL), NULL));
}
void buff_check_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_buff_check_loop_timer_id));
}
void buff_check_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_buff_check_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, buff_check_loop));
}

View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* @file meas_pd_buff.h
******************************************************************************/
#ifndef _MEAS_PD_BUFF_H__
#define _MEAS_PD_BUFF_H__
#include "sdk_common.h"
#include "nrf_drv_saadc.h"
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0,17)
#endif
#define buff_PD_NO 1/////4
#define buff_LED_NO 100////5
#define buff_CYCLE_CNT 8 ////32
void buff_ppi_init(void);
void buff_ppi_uninit(void);
void buff_sampling_event_enable(void);
void buff_sampling_event_disable(void);
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void buff_voltage_handler(nrf_drv_saadc_evt_t const * p_event); /* PD Voltage reading */
void buff_adc_start_init(void);
void buff_adc_start(void);
void buff_adc_start2(void);
//void custom_adc_start2(void);
//void custom_adc_total_start(void);
void buff_adc_end(void);
void buff_adc_end_final(void);
void buff_adc_init(void);
void buff_adc_uninit(void);
void buff_irq_init(void);
void buff_irq_uninit(void);
void buff_send_start(void);
void buff_send_loop(void * p_context); /* For x ms */
void buff_send_timer_start(void);
void buff_send_timer_stop(void);;
void buff_send_timer_init(void);
void buff_check_loop(void * p_context);
void buff_check_timer_start(void);
void buff_check_timer_stop(void);;
void buff_check_timer_init(void);
#endif /* _MEAS_PD_buff_H__ */

View File

@@ -0,0 +1,522 @@
/*******************************************************************************
TEST medi50 Dec 23
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "nrfx_gpiote.h"
#include "app_timer.h"
#include "nrf_drv_timer.h"
#include "nrf_delay.h"
#include "nrf_drv_ppi.h"
#include "ada2200_spi.h"
#include "ble_nus.h"
//#include "fstorage.h"
#include "measurements.h"
#include "meas_pd_imm.h"
#include "mcp4725_i2c.h"
//#include "ad5272_i2c.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "main.h"
#include "app_raw_main.h"
#include "debug_print.h"
//#define CUSTOM_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
//#define CUSTOM_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
//#define CUSTOM_ADC_RES_10BITS 1024.0f /**< Maximum digital value for 10-bit ADC conversion. */
//#define st_c_max 20
///**@brief Macro to convert the result of ADC conversion in millivolts.
// *
// * @param[in] ADC_VALUE ADC result.
// *
// * @retval Result converted to millivolts.
// */
//#define CUSTOM_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\
// (((((ADC_VALUE) * CUSTOM_REF_VOLTAGE_IN_MILLIVOLTS) / CUSTOM_ADC_RES_10BITS) * CUSTOM_PRE_SCALING_COMPENSATION)*2)
static const uint8_t imm_samples_in_buffer = 8;
//uint32_t s_cnt;
uint8_t LED_list_imm[4];
//uint8_t PD_list_custom[4];
//uint8_t led_custom_list_a[5] = {2,3,4,5,6};
//uint8_t pd_custom_list_a[4] = {5,7,8,10};
//uint8_t led_custom_list_b[5] = {20,21,22,23,24};
//uint8_t pd_custom_list_b[4] = {16,14,13,11};
int32_t c_max;
extern volatile bool processing;
extern volatile bool data_tx_in_progress;
extern volatile bool ble_connection_st;
extern uint16_t m_pd_delay_us;
uint32_t c_cnt;
static int8_t pd_no = -1;
static int8_t led_no = -1;
static int8_t buf_no = 0;
uint16_t sel_led_index0 =0;
uint16_t sel_led_index1 =1;
uint16_t sel_led_index2 =2;
uint16_t sel_led_index3 =3;
//static int8_t c_max = 5;
static int32_t t_ms = 0;
int16_t imm_cycle_buff[4][imm_CYCLE_CNT] =
{
{0, 0, 0, 0, 0, 0, 0, 0}, //0
{0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0} //3
};
int16_t imm_cycle_send_buff[imm_LED_NO] = {0, 0, 0, 0};
uint16_t bi_imm_cycle_send_buff[imm_LED_NO] = {0, 0, 0, 0};
#define SAMPLES_IN_BUFFER 4095+32
static nrf_saadc_value_t pd_imm_adc_buf[2][SAMPLES_IN_BUFFER];
bool pd_adc_custom_a_start = false;
bool pd_adc_custom_b_start = false;
bool pd_adc_custom_c_start = false;
bool pd_adc_custom_d_start = false;
bool pd_adc_custom_end = false;
bool pd_adc_custom_start = false;
bool pd_adc_imm_start = false;
bool pd_adc_imm_running = false;
bool custom_testing = false;
//
bool custom_add_data;
extern bool ble_got_new_data;
extern bool motion_raw_data_enabled;
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
uint8_t imm_bin_buffer[BLE_NUS_MAX_DATA_LEN];
//add imu
uint8_t order_imm=0;
extern which_cmd_t cmd_type_t;
APP_TIMER_DEF(m_custom_check_loop_timer_id);
//APP_TIMER_DEF(m_custom_send_loop_timer_id);
#if FEATURE_DELAY
#define CUSTOM_SEND_LOOP_INTERVAL 500
#else
#define CUSTOM_SEND_LOOP_INTERVAL 100
#endif
#define CUSTOM_CHECK_LOOP_INTERVAL 1
static nrf_ppi_channel_t m_ppi_channel;
#if !FEATURE_PRINTF
void imm_ppi_init(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
uint32_t gpiote_event_addr = nrf_drv_gpiote_in_event_addr_get(ADA2200_SYNCO_PIN);
uint32_t saadc_sample_task_addr = nrf_drv_saadc_sample_task_get();
/* setup ppi channel so that timer compare event is triggering sample task in SAADC */
err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
gpiote_event_addr,
saadc_sample_task_addr);
APP_ERROR_CHECK(err_code);
}
void imm_ppi_uninit(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_uninit();
APP_ERROR_CHECK(err_code);
}
void imm_sampling_event_enable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
void imm_sampling_event_disable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_disable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
#endif
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void imm_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* PD Voltage reading */
{
ret_code_t err_code;
int16_t sum = 0;
uint32_t custom_clk_delay = m_pd_delay_us/16;
if(ble_connection_st == 0) {
//imm_adc_end();
//custom_send_timer_stop();
DBG_PRINTF("Custom ADC STOP 1\r\n");
}
else{
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, imm_samples_in_buffer + custom_clk_delay);
APP_ERROR_CHECK(err_code);
if(led_no == -1) {
led_no = 0;
pd_no = 0;
led_on(LED_list_imm[led_no]);
led_pd_matching_value_set(LED_list_imm[led_no]); /* MCP4725 DAC setting and PD on */
} else {
for(uint16_t i = custom_clk_delay; i < custom_clk_delay + imm_samples_in_buffer; i++){
imm_cycle_buff[buf_no][i-custom_clk_delay] = p_event->data.done.p_buffer[i];
}
#if FEATURE_PRINTF
DBG_PRINTF("-----------------Read ADC // led_no = %d(%d), pd_no = %d(%d), buf_no = %d\r\n\r\n", led_no, LED_list_custom[led_no], pd_no, PD_list_custom[pd_no], buf_no);
#endif
buf_no++;
if(pd_no >= - 1) {
//pd_no = 0;
if(led_no < imm_LED_NO - 1) {
led_no++;
led_on(LED_list_imm[led_no]);
led_pd_matching_value_set(LED_list_imm[led_no]); /* MCP4725 DAC setting and PD on */
} else if(led_no >= imm_LED_NO - 1) {
pd_no = -1;
led_no = -1;
#if FEATURE_PRINTF
DBG_PRINTF("\r\nEnded\r\n");
#endif
//imm_adc_end();
//imm_adc_end_final();
///////////////////////////////////////////////////////////////////////////////////////////
uint8_t k =0;
sum = 0;
for(uint16_t i = 0; i < 4; i++){
for(uint16_t j = 0; j < imm_samples_in_buffer; j++){
sum += imm_cycle_buff[i][j];
}
imm_cycle_send_buff[k++] = sum;
sum = 0;
}
buf_no = 0;
if(ble_connection_st == 0) {
DBG_PRINTF("Custom ADC STOP 1");
pd_adc_imm_start=false;
pd_adc_imm_running=false;
DBG_PRINTF ("FINISH SEND\r\n");
processing = false;
custom_testing = false;
imm_adc_end_final();
}
else {
char resp[4];
if(cmd_type_t == CMD_UART) {
//custom_send_timer_stop();
DBG_PRINTF("value 4: %d,%d,%d,%d \r\n",imm_cycle_send_buff[0] ,imm_cycle_send_buff[1] ,imm_cycle_send_buff[2] ,imm_cycle_send_buff[3] );
}else if(cmd_type_t == CMD_BLE) {
DBG_PRINTF("%d ms \r\n",t_ms);
DBG_PRINTF("value 4: %d,%d,%d,%d \r\n",imm_cycle_send_buff[0] ,imm_cycle_send_buff[1] ,imm_cycle_send_buff[2] ,imm_cycle_send_buff[3] );
for(uint16_t i = 0; i < 4; i++){
bi_imm_cycle_send_buff[i]=(uint16_t)(imm_cycle_send_buff[i]);
}
sprintf(resp,"rF%01X:", order_imm);
format_data(imm_bin_buffer, resp, bi_imm_cycle_send_buff, 4);
binary_tx_handler(imm_bin_buffer,6);
}
// if(ble_connection_st == 1) {
// battery_timer_start();
// }
// led_off(99);
// pd_off(99);
imm_adc_start_init();
}
}
}
}
}
}
}
void imm_check_loop(void * p_context)
{
UNUSED_PARAMETER(p_context);
imm_check_timer_stop();
if ( custom_testing == false)
{
DBG_PRINTF("%d ms \r\n",t_ms);
// sprintf(custom_tx_buffer, "Measure Time :%d ms \r\n",t_ms);
}
else
{
t_ms++;
imm_check_timer_start();
}
}
void imm_adc_start_init(void)
{
if (ble_got_new_data ==true)
{
pd_adc_imm_start=false;
pd_adc_imm_running=false;
DBG_PRINTF ("FINISH SEND\r\n");
led_off(99);
pd_off(99);
processing = false;
custom_testing = false;
imm_adc_end_final();
}
else if (pd_adc_imm_running==true){
imm_adc_start();
}
else if (pd_adc_imm_start==true)
{
// custom_testing = false;
// motion_raw_data_enabled = true;
// custom_add_data = true;
// icm42670_main();
//for(uint8_t i =0; i < imm_LED_NO; i++) {
LED_list_imm[0] = sel_led_index0;
LED_list_imm[1] = sel_led_index1;
LED_list_imm[2] = sel_led_index2;
LED_list_imm[3] = sel_led_index3;
// data_tx_handler(ble_tx_buffer);
t_ms=0;
pd_adc_imm_start=false;
pd_adc_imm_running=true;
custom_testing = true;
// custom_check_timer_start();
c_cnt=0;
// pd_adc_custom_start=false;
// pd_adc_custom_a_start=true;
imm_adc_start2();
imm_check_timer_start();
c_cnt++;
}
else
{
}
}
void imm_adc_start(void)
{
pd_no = -1;
led_no = -1;
buf_no = 0;
t_ms=0;
for(uint16_t i = 0; i < 4; i++) {
for(uint16_t j = 0; j < imm_CYCLE_CNT; j++) {
imm_cycle_buff[i][j] = 0;
}
}
if(order_imm>=15){
order_imm=0;
}
else
{ order_imm++;
}
}
void imm_adc_start2(void)
{
imm_adc_start();
imm_adc_init();
imm_irq_init();
imm_ppi_init();
imm_sampling_event_enable();
}
void imm_adc_end(void)
{
DBG_PRINTF("adc_end\r\n");
imm_sampling_event_disable();
}
void imm_adc_end_final(void)
{
DBG_PRINTF("adc_end_for_good\r\n");
imm_sampling_event_disable();
imm_irq_uninit();
imm_ppi_uninit();
imm_adc_uninit();
battery_timer_start();
}
void imm_adc_init(void)
{
#if FEATURE_PRINTF
DBG_PRINTF("custom_adc_init\r\n");
#endif
static nrfx_saadc_config_t default_config;
default_config.resolution = (nrf_saadc_resolution_t)NRFX_SAADC_CONFIG_RESOLUTION; /* Resolution is 10bits */
default_config.oversample = (nrf_saadc_oversample_t)NRFX_SAADC_CONFIG_OVERSAMPLE; /* Over Sampling Disabled */
default_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY; /* Interrupt Priority is 0(Highest) */
default_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE; /* Low Power Mode is Disabled */
static nrf_saadc_channel_config_t config;
config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
config.gain = NRF_SAADC_GAIN1_6;
config.reference = NRF_SAADC_REFERENCE_INTERNAL;
config.acq_time = NRF_SAADC_ACQTIME_3US;
config.mode = NRF_SAADC_MODE_DIFFERENTIAL;
config.burst = NRF_SAADC_BURST_DISABLED;
config.pin_p = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN0);
config.pin_n = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN1);
ret_code_t err_code = nrf_drv_saadc_init(&default_config, imm_voltage_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(pd_imm_adc_buf[0], imm_samples_in_buffer + m_pd_delay_us/16);
APP_ERROR_CHECK(err_code);
}
void imm_adc_uninit(void)
{
#if FEATURE_PRINTF
DBG_PRINTF("pd_custom_adc_uninit\r\n");
#endif
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
}
#if !FEATURE_PRINTF
void imm_irq_init(void)
{
ret_code_t err_code;
/* Initialize int pin */
if (!nrfx_gpiote_is_init())
{
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
in_config.pull = NRF_GPIO_PIN_PULLDOWN;
err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
}
void imm_irq_uninit(void)
{
nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
}
#endif
void imm_check_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_custom_check_loop_timer_id, APP_TIMER_TICKS(CUSTOM_CHECK_LOOP_INTERVAL), NULL));
}
void imm_check_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_custom_check_loop_timer_id));
}
void imm_check_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_custom_check_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, imm_check_loop));
}

View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* @file meas_pd_imm.h
******************************************************************************/
#ifndef _MEAS_PD_IMM_H__
#define _MEAS_PD_IMM_H__
#include "sdk_common.h"
#include "nrf_drv_saadc.h"
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0,17)
#endif
#define imm_PD_NO 1/////4
#define imm_LED_NO 4////5
#define imm_CYCLE_CNT 8 ////32
void imm_ppi_init(void);
void imm_ppi_uninit(void);
void imm_sampling_event_enable(void);
void imm_sampling_event_disable(void);
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void imm_voltage_handler(nrf_drv_saadc_evt_t const * p_event); /* PD Voltage reading */
void imm_adc_start_init(void);
void imm_adc_start(void);
void imm_adc_start2(void);
//void custom_adc_start2(void);
//void custom_adc_total_start(void);
void imm_adc_end(void);
void imm_adc_end_final(void);
void imm_adc_init(void);
void imm_adc_uninit(void);
void imm_irq_init(void);
void imm_irq_uninit(void);
//void custom_send_start(void);
//void custom_send_loop(void * p_context); /* For x ms */
//void custom_send_timer_start(void);
//void custom_send_timer_stop(void);;
//void custom_send_timer_init(void);
void imm_check_loop(void * p_context);
void imm_check_timer_start(void);
void imm_check_timer_stop(void);;
void imm_check_timer_init(void);
#endif /* _MEAS_PD_VOLTAGE_CUSTOM_H__ */

View File

@@ -0,0 +1,885 @@
/*******************************************************************************
TEST medi50 Dec 23
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "nrfx_gpiote.h"
#include "app_timer.h"
#include "nrf_drv_timer.h"
#include "nrf_delay.h"
#include "nrf_drv_ppi.h"
#include "ada2200_spi.h"
#include "ble_nus.h"
#include "fstorage.h"
#include "measurements.h"
#include "meas_pd_voltage_custom.h"
#include "mcp4725_i2c.h"
#include "ad5272_i2c.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "main.h"
#include "app_raw_main.h"
#define CUSTOM_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
#define CUSTOM_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
#define CUSTOM_ADC_RES_10BITS 1024.0f /**< Maximum digital value for 10-bit ADC conversion. */
#define st_c_max 20
/**@brief Macro to convert the result of ADC conversion in millivolts.
*
* @param[in] ADC_VALUE ADC result.
*
* @retval Result converted to millivolts.
*/
#define CUSTOM_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\
(((((ADC_VALUE) * CUSTOM_REF_VOLTAGE_IN_MILLIVOLTS) / CUSTOM_ADC_RES_10BITS) * CUSTOM_PRE_SCALING_COMPENSATION)*2)
uint8_t custom_samples_in_buffer = 8;
uint32_t s_cnt;
uint8_t LED_list_custom[5];
uint8_t PD_list_custom[4];
uint8_t led_custom_list_a[5] = {2,3,4,5,6};
uint8_t pd_custom_list_a[4] = {5,7,8,10};
uint8_t led_custom_list_b[5] = {20,21,22,23,24};
uint8_t pd_custom_list_b[4] = {16,14,13,11};
int32_t c_max;
extern volatile bool processing;
extern volatile bool data_tx_in_progress;
extern volatile bool ble_connection_st;
uint32_t c_cnt;
static int8_t pd_no = -1;
static int8_t led_no = -1;
static int8_t buf_no = 0;
//static int8_t c_max = 5;
static int32_t t_ms = 0;
double custom_cycle_buff[36][CUSTOM_CYCLE_CNT] =
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //0
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //10
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //11
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //12
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //13
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //14
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //15
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //16
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //17
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //18
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //19
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //20
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //21
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //22
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //23
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //24
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //25
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //26
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //27
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //28
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //29
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //30
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //31
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //32
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //33
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //34
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //35
};
double custom_cycle_send_buff[CUSTOM_LED_NO][CUSTOM_PD_NO] =
{
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
};
#define SAMPLES_IN_BUFFER 4095+32
static nrf_saadc_value_t pd_custom_adc_buf[2][SAMPLES_IN_BUFFER];
bool pd_adc_custom_a_start = false;
bool pd_adc_custom_b_start = false;
bool pd_adc_custom_c_start = false;
bool pd_adc_custom_d_start = false;
bool pd_adc_custom_end = false;
bool pd_adc_custom_start = false;
bool custom_testing = false;
//
bool custom_add_data;
extern bool motion_raw_data_enabled;
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
//add imu
char custom_tx_buffer[BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_0[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_1[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_2[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_3[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_4[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_5[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_6[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_7[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_8[st_c_max][BLE_NUS_MAX_DATA_LEN];
char custom_tx_d_buffer_9[st_c_max][BLE_NUS_MAX_DATA_LEN];
extern which_cmd_t cmd_type_t;
APP_TIMER_DEF(m_custom_check_loop_timer_id);
APP_TIMER_DEF(m_custom_send_loop_timer_id);
#if FEATURE_DELAY
#define CUSTOM_SEND_LOOP_INTERVAL 500
#else
#define CUSTOM_SEND_LOOP_INTERVAL 100
#endif
#define CUSTOM_CHECK_LOOP_INTERVAL 1
#if FEATURE_DEBUG_REPEAT_TEST
extern uint32_t rpt_cnt;
#endif
#if FEATURE_PRINTF
APP_TIMER_DEF(m_custom_loop_timer_id);
#define CUSTOM_LOOP_INTERVAL 1
void custom_timer_loop(void * p_context)
{
UNUSED_PARAMETER(p_context);
ret_code_t err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code);
}
void custom_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_custom_loop_timer_id, APP_TIMER_TICKS(CUSTOM_LOOP_INTERVAL), NULL));
}
void custom_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_custom_loop_timer_id));
}
void custom_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_custom_loop_timer_id, APP_TIMER_MODE_REPEATED, custom_timer_loop));
}
#else
static nrf_ppi_channel_t m_ppi_channel;
#endif
#if !FEATURE_PRINTF
void custom_ppi_init(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
uint32_t gpiote_event_addr = nrf_drv_gpiote_in_event_addr_get(ADA2200_SYNCO_PIN);
uint32_t saadc_sample_task_addr = nrf_drv_saadc_sample_task_get();
/* setup ppi channel so that timer compare event is triggering sample task in SAADC */
err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
gpiote_event_addr,
saadc_sample_task_addr);
APP_ERROR_CHECK(err_code);
}
void custom_ppi_uninit(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_uninit();
APP_ERROR_CHECK(err_code);
}
void custom_sampling_event_enable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
void custom_sampling_event_disable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_disable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
#endif
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void custom_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* PD Voltage reading */
{
ret_code_t err_code;
double sum = 0.0f;
uint32_t custom_clk_delay = m_config.pd_delay_us/16;
if(ble_connection_st == 0) {
custom_adc_end();
custom_send_timer_stop();
printf("Custom ADC STOP 1\r\n");
}
else{
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, custom_samples_in_buffer + custom_clk_delay);
APP_ERROR_CHECK(err_code);
if(led_no == -1) {
led_no = 0;
pd_no = 0;
led_on(LED_list_custom[led_no]);
led_pd_matching_value_set(LED_list_custom[led_no]); /* MCP4725 DAC setting and PD on */
} else {
for(uint16_t i = custom_clk_delay; i < custom_clk_delay + custom_samples_in_buffer; i++){
custom_cycle_buff[buf_no][i-custom_clk_delay] = (CUSTOM_VOUT_IN_MILLI_VOLTS(p_event->data.done.p_buffer[i])) * -1;
}
#if FEATURE_PRINTF
printf("-----------------Read ADC // led_no = %d(%d), pd_no = %d(%d), buf_no = %d\r\n\r\n", led_no, LED_list_custom[led_no], pd_no, PD_list_custom[pd_no], buf_no);
#endif
buf_no++;
if(pd_no < CUSTOM_PD_NO - 1) {
pd_no++;
led_pd_matching_value_set(LED_list_custom[led_no]); /* MCP4725 DAC setting and PD on */
}else if(pd_no >= CUSTOM_PD_NO - 1) {
pd_no = 0;
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
if(led_no < CUSTOM_LED_NO - 1) {
led_no++;
led_on(LED_list_custom[led_no]);
led_pd_matching_value_set(LED_list_custom[led_no]); /* MCP4725 DAC setting and PD on */
} else if(led_no >= CUSTOM_LED_NO - 1) {
pd_no = -1;
led_no = -1;
#if FEATURE_PRINTF
printf("\r\nEnded\r\n");
#endif
custom_adc_end();
#if FEATURE_DETAIL_VALUE_CUSTOM
sum = 0;
printf("\r\nCustom_============custom_cycle_buff =\r\n");
for(uint16_t i = 0; i < 36; i++){
for(uint16_t j = 0; j < custom_samples_in_buffer; j++){
printf("%lf\r\n", custom_cycle_buff[i][j]);
}
for(uint16_t k = 0; k < custom_samples_in_buffer; k++){
sum += custom_cycle_buff[i][k];
}
printf("\t%lf\r\n", sum);
sum = 0;
}
printf("\r\n");
#endif
///////////////////////////////////////////////////////////////////////////////////////////
uint8_t k =0;
sum = 0;
for(uint16_t i = 0; i < 20; i++){
for(uint16_t j = 0; j < custom_samples_in_buffer; j++){
sum += custom_cycle_buff[i][j];
}
custom_cycle_send_buff[i/4][k++] = sum;
sum = 0;
if(k >= 4) k = 0;
}
///////////////////////////////////////////////////////////////////////////////////////////
/* Send data */
/*********************/
if(cmd_type_t == CMD_UART) {
printf("S%dTi%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n",c_cnt, LED_list_custom[0], custom_cycle_send_buff[0][0], custom_cycle_send_buff[0][1], custom_cycle_send_buff[0][2], custom_cycle_send_buff[0][3]);//, custom_cycle_send_buff[0][4], custom_cycle_send_buff[0][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n", LED_list_custom[1], custom_cycle_send_buff[1][0], custom_cycle_send_buff[1][1], custom_cycle_send_buff[1][2], custom_cycle_send_buff[1][3]);//, custom_cycle_send_buff[1][4], custom_cycle_send_buff[1][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n", LED_list_custom[2], custom_cycle_send_buff[2][0], custom_cycle_send_buff[2][1], custom_cycle_send_buff[2][2], custom_cycle_send_buff[2][3]);//, custom_cycle_send_buff[2][4], custom_cycle_send_buff[2][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n", LED_list_custom[3], custom_cycle_send_buff[3][0], custom_cycle_send_buff[3][1], custom_cycle_send_buff[3][2], custom_cycle_send_buff[3][3]);//, custom_cycle_send_buff[3][4], custom_cycle_send_buff[3][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n", LED_list_custom[4], custom_cycle_send_buff[4][0], custom_cycle_send_buff[4][1], custom_cycle_send_buff[4][2], custom_cycle_send_buff[4][3]);//, custom_cycle_send_buff[4][4], custom_cycle_send_buff[4][5]);
printf("\r\n");
} else if(cmd_type_t == CMD_BLE ) {
if(pd_adc_custom_a_start == true ) {
sprintf(custom_tx_d_buffer_0[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n",c_cnt, LED_list_custom[0], custom_cycle_send_buff[0][0], custom_cycle_send_buff[0][1], custom_cycle_send_buff[0][2], custom_cycle_send_buff[0][3]);
sprintf(custom_tx_d_buffer_1[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n",c_cnt, LED_list_custom[1], custom_cycle_send_buff[1][0], custom_cycle_send_buff[1][1], custom_cycle_send_buff[1][2], custom_cycle_send_buff[1][3]);
sprintf(custom_tx_d_buffer_2[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n",c_cnt, LED_list_custom[2], custom_cycle_send_buff[2][0], custom_cycle_send_buff[2][1], custom_cycle_send_buff[2][2], custom_cycle_send_buff[2][3]);
sprintf(custom_tx_d_buffer_3[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n",c_cnt, LED_list_custom[3], custom_cycle_send_buff[3][0], custom_cycle_send_buff[3][1], custom_cycle_send_buff[3][2], custom_cycle_send_buff[3][3]);
sprintf(custom_tx_d_buffer_4[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\r\n",c_cnt, LED_list_custom[4], custom_cycle_send_buff[4][0], custom_cycle_send_buff[4][1], custom_cycle_send_buff[4][2], custom_cycle_send_buff[4][3]);
}else if(pd_adc_custom_b_start == true) {
sprintf(custom_tx_d_buffer_5[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf\r\n",c_cnt, LED_list_custom[0], custom_cycle_send_buff[0][0], custom_cycle_send_buff[0][1], custom_cycle_send_buff[0][2], custom_cycle_send_buff[0][3]);
sprintf(custom_tx_d_buffer_6[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf\r\n",c_cnt, LED_list_custom[1], custom_cycle_send_buff[1][0], custom_cycle_send_buff[1][1], custom_cycle_send_buff[1][2], custom_cycle_send_buff[1][3]);
sprintf(custom_tx_d_buffer_7[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf\r\n",c_cnt, LED_list_custom[2], custom_cycle_send_buff[2][0], custom_cycle_send_buff[2][1], custom_cycle_send_buff[2][2], custom_cycle_send_buff[2][3]);
sprintf(custom_tx_d_buffer_8[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf\r\n",c_cnt, LED_list_custom[3], custom_cycle_send_buff[3][0], custom_cycle_send_buff[3][1], custom_cycle_send_buff[3][2], custom_cycle_send_buff[3][3]);
sprintf(custom_tx_d_buffer_9[c_cnt], "M%d,Ti%d,\t%lf,\t%lf,\t%lf,\t%lf\r\n",c_cnt, LED_list_custom[4], custom_cycle_send_buff[4][0], custom_cycle_send_buff[4][1], custom_cycle_send_buff[4][2], custom_cycle_send_buff[4][3]);
}
}
/*********************/
buf_no = 0;
if(ble_connection_st == 0) {
custom_adc_end();
custom_send_timer_stop();
printf("Custom ADC STOP 1");
}
else if(pd_adc_custom_a_start == true) { // B mode
pd_adc_custom_a_start = false;
pd_adc_custom_b_start = true;
custom_adc_start();
}else if(pd_adc_custom_b_start == true) { // Completed
pd_adc_custom_b_start = false;
// }
// else if(pd_adc_custom_a_start == true) { // Completed
// pd_adc_custom_a_start = false;
if(cmd_type_t == CMD_UART) {
custom_send_timer_stop();
}else if(cmd_type_t == CMD_BLE) {
}
// if(ble_connection_st == 1) {
// battery_timer_start();
// }
led_off(99);
pd_off(99);
custom_adc_total_start();
}
}
}
}
}
}
}
void custom_check_loop(void * p_context)
{
UNUSED_PARAMETER(p_context);
custom_check_timer_stop();
if ( custom_testing == false)
{
printf("%d ms \r\n",t_ms);
sprintf(custom_tx_buffer, "Measure Time :%d ms \r\n",t_ms);
}
else
{
t_ms++;
custom_check_timer_start();
}
}
void custom_send_loop(void * p_context) /* For x ms */
{
UNUSED_PARAMETER(p_context);
custom_send_timer_stop();
if(ble_connection_st){
if (data_tx_in_progress==true) {
// Data transmission is still in progress
printf("skip \r\n");
return;
}
static uint8_t order = 0;
switch(order) {
case 0:
data_tx_handler(custom_tx_d_buffer_0[s_cnt]);
break;
case 1:
data_tx_handler(custom_tx_d_buffer_1[s_cnt]);
break;
case 2:
data_tx_handler(custom_tx_d_buffer_2[s_cnt]);
break;
case 3:
data_tx_handler(custom_tx_d_buffer_3[s_cnt]);
break;
case 4:
data_tx_handler(custom_tx_d_buffer_4[s_cnt]);
break;
case 5:
data_tx_handler(custom_tx_d_buffer_5[s_cnt]);
break;
case 6:
data_tx_handler(custom_tx_d_buffer_6[s_cnt]);
break;
case 7:
data_tx_handler(custom_tx_d_buffer_7[s_cnt]);
break;
case 8:
data_tx_handler(custom_tx_d_buffer_8[s_cnt]);
break;
case 9:
data_tx_handler(custom_tx_d_buffer_9[s_cnt]);
order = 0;
custom_send_start();
#if FEATURE_PRINTF
printf("custom_send Completed\r\n");
#endif
return;
default:
break;
}
if(ble_connection_st == 0) {
custom_send_timer_stop();
order = 0;
printf("Custom ADC STOP 2\r\n");}
else{
order++;
custom_send_timer_start();
}
}
}
void custom_send_start(void)
{
s_cnt++;
if (s_cnt>c_max)
{
custom_send_timer_stop();
data_tx_handler(ble_tx_buffer);
data_tx_handler(custom_tx_buffer);
battery_timer_start();
}
else
{
custom_send_timer_start();
}
}
void custom_adc_total_start(void)
{
if (pd_adc_custom_start==true)
{
// custom_testing = false;
// motion_raw_data_enabled = true;
// custom_add_data = true;
// icm42670_main();
//
// data_tx_handler(ble_tx_buffer);
t_ms=0;
custom_testing = true;
custom_check_timer_start();
c_cnt=0;
pd_adc_custom_start=false;
pd_adc_custom_a_start=true;
custom_adc_start();
c_cnt++;
}
else if (c_cnt<c_max)
{
pd_adc_custom_a_start=true;
custom_adc_start();
c_cnt++;
}
else if (c_cnt==c_max)
{
custom_testing = false;
motion_raw_data_enabled = true;
custom_add_data = true;
icm42670_main();
if(cmd_type_t == CMD_BLE){
s_cnt=0;
custom_send_start();
}
c_cnt++;
printf ("FINISH SEND\r\n");
processing = false;
}
}
void custom_adc_start(void)
{
pd_no = -1;
led_no = -1;
buf_no = 0;
if(pd_adc_custom_a_start == true) {
#if FEATURE_PRINTF
printf("\r\n===== Custom_A start ====================\r\n");
printf("LED : ");
#endif
for(uint8_t i =0; i < CUSTOM_LED_NO; i++) {
LED_list_custom[i] = led_custom_list_a[i];
#if FEATURE_PRINTF
printf("%d ", LED_list_custom[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n");
printf("PD : ");
#endif
for(uint8_t i =0; i < CUSTOM_PD_NO; i++) {
PD_list_custom[i] = pd_custom_list_a[i];
#if FEATURE_PRINTF
printf("%d ", PD_list_custom[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
}
if(pd_adc_custom_b_start == true) {
#if FEATURE_PRINTF
printf("\r\n===== Custom_B start ====================\r\n");
printf("LED : ");
#endif
for(uint8_t i =0; i < CUSTOM_LED_NO; i++) {
LED_list_custom[i] = led_custom_list_b[i];
#if FEATURE_PRINTF
printf("%d ", LED_list_custom[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n");
printf("PD : ");
#endif
for(uint8_t i =0; i < CUSTOM_PD_NO; i++) {
PD_list_custom[i] = pd_custom_list_b[i];
#if FEATURE_PRINTF
printf("%d ", PD_list_custom[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
}
// if(pd_adc_custom_c_start == true) {
//#if FEATURE_PRINTF
// printf("\r\n===== Custom_C start ====================\r\n");
// printf("LED : ");
//#endif
// for(uint8_t i =0; i < CUSTOM_LED_NO; i++) {
// LED_list_custom[i] = led_custom_list_c[i];
//#if FEATURE_PRINTF
// printf("%d ", LED_list_custom[i]);
//#endif
// }
//
//#if FEATURE_PRINTF
// printf("\r\n");
// printf("PD : ");
//#endif
// for(uint8_t i =0; i < CUSTOM_PD_NO; i++) {
// PD_list_custom[i] = pd_custom_list_c[i];
//#if FEATURE_PRINTF
// printf("%d ", PD_list_custom[i]);
//#endif
// }
//#if FEATURE_PRINTF
// printf("\r\n\r\n");
//#endif
// }
// if(pd_adc_custom_d_start == true) {
//#if FEATURE_PRINTF
// printf("\r\n===== Custom_D start ====================\r\n");
// printf("LED : ");
//#endif
// for(uint8_t i =0; i < CUSTOM_LED_NO; i++) {
// LED_list_custom[i] = led_custom_list_d[i];
//#if FEATURE_PRINTF
// printf("%d ", LED_list_custom[i]);
//#endif
// }
//
//#if FEATURE_PRINTF
// printf("\r\n");
// printf("PD : ");
//#endif
// for(uint8_t i =0; i < CUSTOM_PD_NO; i++) {
// PD_list_custom[i] = pd_custom_list_d[i];
//#if FEATURE_PRINTF
// printf("%d ", PD_list_custom[i]);
//#endif
// }
//#if FEATURE_PRINTF
// printf("\r\n\r\n");
//#endif
// }
for(uint16_t i = 0; i < 72; i++) {
for(uint16_t j = 0; j < CUSTOM_CYCLE_CNT; j++) {
custom_cycle_buff[i][j] = 0.0f;
}
}
for(uint8_t i = 0; i < CUSTOM_LED_NO; i++) {
for(uint8_t j = 0; j < CUSTOM_PD_NO; j++) {
custom_cycle_send_buff[i][j] = 0.0f;
}
}
custom_adc_start2();
}
void custom_adc_start2(void)
{
custom_adc_init();
#if FEATURE_PRINTF
custom_timer_init();
custom_timer_start();
#else
custom_irq_init();
custom_ppi_init();
custom_sampling_event_enable();
#endif
}
void custom_adc_end(void)
{
#if FEATURE_PRINTF
printf("custom_adc_end\r\n");
custom_timer_stop();
#endif
#if !FEATURE_PRINTF
custom_sampling_event_disable();
custom_irq_uninit();
custom_ppi_uninit();
#endif
custom_adc_uninit();
}
void custom_adc_end_2(void)
{
#if FEATURE_PRINTF
printf("custom_adc_end\r\n");
custom_timer_stop();
#endif
#if !FEATURE_PRINTF
custom_sampling_event_disable();
custom_irq_uninit();
custom_ppi_uninit();
#endif
custom_adc_uninit();
}
void custom_adc_init(void)
{
#if FEATURE_PRINTF
printf("custom_adc_init\r\n");
#endif
static nrfx_saadc_config_t default_config;
default_config.resolution = (nrf_saadc_resolution_t)NRFX_SAADC_CONFIG_RESOLUTION; /* Resolution is 10bits */
default_config.oversample = (nrf_saadc_oversample_t)NRFX_SAADC_CONFIG_OVERSAMPLE; /* Over Sampling Disabled */
default_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY; /* Interrupt Priority is 0(Highest) */
default_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE; /* Low Power Mode is Disabled */
static nrf_saadc_channel_config_t config;
config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
config.gain = NRF_SAADC_GAIN1_6;
config.reference = NRF_SAADC_REFERENCE_INTERNAL;
config.acq_time = NRF_SAADC_ACQTIME_3US;
config.mode = NRF_SAADC_MODE_DIFFERENTIAL;
config.burst = NRF_SAADC_BURST_DISABLED;
config.pin_p = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN0);
config.pin_n = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN1);
ret_code_t err_code = nrf_drv_saadc_init(&default_config, custom_voltage_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_buffer_convert(pd_custom_adc_buf[0], custom_samples_in_buffer + m_config.pd_delay_us/16);
APP_ERROR_CHECK(err_code);
}
void custom_adc_uninit(void)
{
#if FEATURE_PRINTF
printf("pd_custom_adc_uninit\r\n");
#endif
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
}
#if !FEATURE_PRINTF
void custom_irq_init(void)
{
ret_code_t err_code;
/* Initialize int pin */
if (!nrfx_gpiote_is_init())
{
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
in_config.pull = NRF_GPIO_PIN_PULLDOWN;
err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
}
void custom_irq_uninit(void)
{
nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
}
#endif
void custom_send_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_custom_send_loop_timer_id, APP_TIMER_TICKS(CUSTOM_SEND_LOOP_INTERVAL), NULL));
}
void custom_send_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_custom_send_loop_timer_id));
}
void custom_send_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_custom_send_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, custom_send_loop));
}
void custom_check_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_custom_check_loop_timer_id, APP_TIMER_TICKS(CUSTOM_CHECK_LOOP_INTERVAL), NULL));
}
void custom_check_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_custom_check_loop_timer_id));
}
void custom_check_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_custom_check_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, custom_check_loop));
}

View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* @file meas_pd_voltage_custom.h
******************************************************************************/
#ifndef _MEAS_PD_VOLTAGE_CUSTOM_H__
#define _MEAS_PD_VOLTAGE_CUSTOM_H__
#include "sdk_common.h"
#include "nrf_drv_saadc.h"
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0,17)
#endif
#define CUSTOM_PD_NO 4
#define CUSTOM_LED_NO 5
#define CUSTOM_CYCLE_CNT 32
void custom_ppi_init(void);
void custom_ppi_uninit(void);
void custom_sampling_event_enable(void);
void custom_sampling_event_disable(void);
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void imm_voltage_handler(nrf_drv_saadc_evt_t const * p_event); /* PD Voltage reading */
void custom_adc_start(void);
void custom_adc_start2(void);
void custom_adc_total_start(void);
void custom_adc_end(void);
void custom_adc_end_2(void);
void custom_adc_init(void);
void custom_adc_uninit(void);
#if !FEATURE_PRINTF
void custom_irq_init(void);
void custom_irq_uninit(void);
#endif
void custom_send_start(void);
void custom_send_loop(void * p_context); /* For x ms */
void custom_send_timer_start(void);
void custom_send_timer_stop(void);;
void custom_send_timer_init(void);
void custom_check_loop(void * p_context);
void custom_check_timer_start(void);
void custom_check_timer_stop(void);;
void custom_check_timer_init(void);
#endif /* _MEAS_PD_VOLTAGE_CUSTOM_H__ */

View File

@@ -0,0 +1,66 @@
/*******************************************************************************
* @file meas_pd_voltage_full.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _MEAS_PD_VOLTAGE_FULL_H__
#define _MEAS_PD_VOLTAGE_FULL_H__
#include "sdk_common.h"
#include "nrf_drv_saadc.h"
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0,17)
#endif
#define FULL_PD_NO 1
#define FULL_LED_NO 24
#define FULL_CYCLE_CNT 32 /* Dummy=65535/16us(Max Delay), Cycle_Max_Cnt=32*/
#if FEATURE_PRINTF
void full_timer_loop(void * p_context);
void full_timer_start(void);
void full_timer_stop(void);
void full_timer_init(void);
#endif
void full_ppi_init(void);
void full_ppi_uninit(void);
void full_sampling_event_enable(void);
void full_sampling_event_disable(void);
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void full_voltage_handler(nrf_drv_saadc_evt_t const * p_event); /* PD Voltage reading */
void full_adc_start_first(uint8_t type,uint8_t PD_PICK ,uint8_t LED_PICK);
void full_adc_start(void);
void full_adc_start2(void);
void full_adc_end(void);
void full_adc_init(void);
void full_adc_uninit(void);
#if !FEATURE_PRINTF
void full_irq_init(void);
void full_irq_uninit(void);
#endif
void ble_Tx_process(void);
void full_send_loop(void * p_context); /* For x ms */
void full_send_timer_start(void);
void full_send_timer_stop(void);;
void full_send_timer_init(void);
void full_check_loop(void * p_context);
void full_check_timer_start(void);
void full_check_timer_stop(void);;
void full_check_timer_init(void);
#endif /* _MEAS_PD_VOLTAGE_FULL_H__ */

View File

@@ -0,0 +1,836 @@
/*******************************************************************************
TEST medi50 Dec 23
******************************************************************************/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "nrfx_gpiote.h"
#include "app_timer.h"
#include "nrf_drv_timer.h"
#include "nrf_delay.h"
#include "nrf_drv_ppi.h"
#include "ada2200_spi.h"
#include "ble_nus.h"
#include "fstorage.h"
#include "measurements.h"
#include "meas_pd_voltage_half.h"
#include "mcp4725_i2c.h"
#include "ad5272_i2c.h"
#include "main_timer.h"
#include "battery_saadc.h"
#include "main.h"
#include "power_control.h"
#define HALF_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
#define HALF_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
#define HALF_ADC_RES_10BITS 1024.0f /**< Maximum digital value for 10-bit ADC conversion. */
/**@brief Macro to convert the result of ADC conversion in millivolts.
*
* @param[in] ADC_VALUE ADC result.
*
* @retval Result converted to millivolts.
*/
#define HALF_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\
(((((ADC_VALUE) * HALF_REF_VOLTAGE_IN_MILLIVOLTS) / HALF_ADC_RES_10BITS) * HALF_PRE_SCALING_COMPENSATION)*2)
uint8_t half_samples_in_buffer = 8;
uint8_t LED_list_half[6];
uint8_t PD_list_half[6];
uint8_t led_half_list_a[6] = {1,2,3,4,5,6};
uint8_t pd_half_list_a[6] = {5,6,7,8,9,10};
uint8_t led_half_list_b[6] = {7,8,9,10,11,12};
uint8_t pd_half_list_b[6] = {6,5,4,3,2,1};
uint8_t led_half_list_c[6] = {13,14,15,16,17,18};
uint8_t pd_half_list_c[6] = {15,16,17,18,19,20};
uint8_t led_half_list_d[6] = {19,20,21,22,23,24};
uint8_t pd_half_list_d[6] = {16,15,14,13,12,11};
bool prestatus;
static int8_t pd_no = -1;
static int8_t led_no = -1;
static int8_t buf_no = 0;
static uint8_t order = 0;
double half_cycle_buff[36][HALF_CYCLE_CNT] =
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //0
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //1
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //3
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //4
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //5
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //6
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //7
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //8
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //9
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //10
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //11
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //12
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //13
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //14
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //15
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //16
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //17
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //18
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //19
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //20
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //21
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //22
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //23
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //24
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //25
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //26
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //27
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //28
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //29
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //30
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //31
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //32
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //33
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //34
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //35
};
double half_cycle_send_buff[HALF_LED_NO][HALF_PD_NO] =
{
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0}
};
#define SAMPLES_IN_BUFFER 4095+32
static nrf_saadc_value_t pd_half_adc_buf[2][SAMPLES_IN_BUFFER];
bool pd_adc_half_a_start = false;
bool pd_adc_half_b_start = false;
bool pd_adc_half_c_start = false;
bool pd_adc_half_d_start = false;
bool pd_adc_half_end = false;
char half_tx_buffer_0[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_1[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_2[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_3[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_4[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_5[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_6[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_7[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_8[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_9[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_10[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_11[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_12[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_13[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_14[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_15[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_16[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_17[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_18[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_19[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_20[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_21[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_22[BLE_NUS_MAX_DATA_LEN];
char half_tx_buffer_23[BLE_NUS_MAX_DATA_LEN];
extern which_cmd_t cmd_type_t;
APP_TIMER_DEF(m_half_send_loop_timer_id);
#if FEATURE_DELAY
#define HALF_SEND_LOOP_INTERVAL 500
#else
#define HALF_SEND_LOOP_INTERVAL 100
#endif
extern volatile bool ble_connection_st;
#if FEATURE_DEBUG_REPEAT_TEST
extern uint32_t rpt_cnt;
#endif
#if FEATURE_PRINTF
APP_TIMER_DEF(m_half_loop_timer_id);
#define HALF_LOOP_INTERVAL 1
void half_timer_loop(void * p_context)
{
UNUSED_PARAMETER(p_context);
ret_code_t err_code = nrf_drv_saadc_sample();
APP_ERROR_CHECK(err_code);
}
void half_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_half_loop_timer_id, APP_TIMER_TICKS(HALF_LOOP_INTERVAL), NULL));
}
void half_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_half_loop_timer_id));
}
void half_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_half_loop_timer_id, APP_TIMER_MODE_REPEATED, half_timer_loop));
}
#else
static nrf_ppi_channel_t m_ppi_channel;
#endif
#if !FEATURE_PRINTF
void half_ppi_init(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
uint32_t gpiote_event_addr = nrf_drv_gpiote_in_event_addr_get(ADA2200_SYNCO_PIN);
uint32_t saadc_sample_task_addr = nrf_drv_saadc_sample_task_get();
/* setup ppi channel so that timer compare event is triggering sample task in SAADC */
err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
gpiote_event_addr,
saadc_sample_task_addr);
APP_ERROR_CHECK(err_code);
}
void half_ppi_uninit(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_uninit();
APP_ERROR_CHECK(err_code);
}
void half_sampling_event_enable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
void half_sampling_event_disable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_disable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
#endif
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void half_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* PD Voltage reading */
{
ret_code_t err_code;
double sum = 0.0f;
uint32_t half_clk_delay = m_config.pd_delay_us/16;
if (prestatus==true)
{ half_clk_delay =10;}
if(ble_connection_st == 0) {
half_adc_end();
half_send_timer_stop();
printf("Half ADC STOP 1\r\n");
}
else{
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, half_samples_in_buffer + half_clk_delay);
APP_ERROR_CHECK(err_code);
if(led_no == -1) {
led_no = 0;
pd_no = 0;
led_on(LED_list_half[led_no]);
led_pd_matching_value_set(LED_list_half[led_no], PD_list_half[pd_no]); /* MCP4725 DAC setting and PD on */
} else {
#if FEATURE_FOR_SCOPE
nrf_gpio_pin_set(ADC_CLK_18);
nrf_gpio_pin_clear(ADC_CLK_18);
#endif
for(uint16_t i = half_clk_delay; i < half_clk_delay + half_samples_in_buffer; i++){
half_cycle_buff[buf_no][i-half_clk_delay] = (HALF_VOUT_IN_MILLI_VOLTS(p_event->data.done.p_buffer[i])) * -1;
}
#if FEATURE_PRINTF
printf("-----------------Read ADC // led_no = %d(%d), pd_no = %d(%d), buf_no = %d\r\n\r\n", led_no, LED_list_half[led_no], pd_no, PD_list_half[pd_no], buf_no);
#endif
buf_no++;
if(pd_no < HALF_PD_NO - 1) {
pd_no++;
led_pd_matching_value_set(LED_list_half[led_no], PD_list_half[pd_no]); /* MCP4725 DAC setting and PD on */
}else if(pd_no >= HALF_PD_NO - 1) {
pd_no = 0;
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
if(led_no < HALF_LED_NO - 1) {
led_no++;
led_on(LED_list_half[led_no]);
led_pd_matching_value_set(LED_list_half[led_no], PD_list_half[pd_no]); /* MCP4725 DAC setting and PD on */
} else if(led_no >= HALF_LED_NO - 1) {
pd_no = -1;
led_no = -1;
#if FEATURE_PRINTF
printf("\r\nEnded\r\n");
#endif
half_adc_end();
#if FEATURE_DETAIL_VALUE_HALF
sum = 0;
printf("\r\nHalf_============half_cycle_buff =\r\n");
for(uint16_t i = 0; i < 36; i++){
for(uint16_t j = 0; j < half_samples_in_buffer; j++){
printf("%lf\r\n", half_cycle_buff[i][j]);
}
for(uint16_t k = 0; k < half_samples_in_buffer; k++){
sum += half_cycle_buff[i][k];
}
printf("\t%lf\r\n", sum);
sum = 0;
}
printf("\r\n");
#endif
///////////////////////////////////////////////////////////////////////////////////////////
uint8_t k =0;
sum = 0;
for(uint16_t i = 0; i < 36; i++){
for(uint16_t j = 0; j < half_samples_in_buffer; j++){
sum += half_cycle_buff[i][j];
}
half_cycle_send_buff[i/6][k++] = sum;
sum = 0;
if(k >= 6) k = 0;
}
///////////////////////////////////////////////////////////////////////////////////////////
/* Send data */
/*********************/
if(cmd_type_t == CMD_UART && prestatus==false) {
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[0], half_cycle_send_buff[0][0], half_cycle_send_buff[0][1], half_cycle_send_buff[0][2], half_cycle_send_buff[0][3], half_cycle_send_buff[0][4], half_cycle_send_buff[0][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[1], half_cycle_send_buff[1][0], half_cycle_send_buff[1][1], half_cycle_send_buff[1][2], half_cycle_send_buff[1][3], half_cycle_send_buff[1][4], half_cycle_send_buff[1][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[2], half_cycle_send_buff[2][0], half_cycle_send_buff[2][1], half_cycle_send_buff[2][2], half_cycle_send_buff[2][3], half_cycle_send_buff[2][4], half_cycle_send_buff[2][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[3], half_cycle_send_buff[3][0], half_cycle_send_buff[3][1], half_cycle_send_buff[3][2], half_cycle_send_buff[3][3], half_cycle_send_buff[3][4], half_cycle_send_buff[3][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[4], half_cycle_send_buff[4][0], half_cycle_send_buff[4][1], half_cycle_send_buff[4][2], half_cycle_send_buff[4][3], half_cycle_send_buff[4][4], half_cycle_send_buff[4][5]);
printf("Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[5], half_cycle_send_buff[5][0], half_cycle_send_buff[5][1], half_cycle_send_buff[5][2], half_cycle_send_buff[5][3], half_cycle_send_buff[5][4], half_cycle_send_buff[5][5]);
printf("\r\n");
} else if(cmd_type_t == CMD_BLE && prestatus==false) {
if(pd_adc_half_a_start == true) {
sprintf(half_tx_buffer_0, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[0], half_cycle_send_buff[0][0], half_cycle_send_buff[0][1], half_cycle_send_buff[0][2], half_cycle_send_buff[0][3], half_cycle_send_buff[0][4], half_cycle_send_buff[0][5]);
sprintf(half_tx_buffer_1, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[1], half_cycle_send_buff[1][0], half_cycle_send_buff[1][1], half_cycle_send_buff[1][2], half_cycle_send_buff[1][3], half_cycle_send_buff[1][4], half_cycle_send_buff[1][5]);
sprintf(half_tx_buffer_2, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[2], half_cycle_send_buff[2][0], half_cycle_send_buff[2][1], half_cycle_send_buff[2][2], half_cycle_send_buff[2][3], half_cycle_send_buff[2][4], half_cycle_send_buff[2][5]);
sprintf(half_tx_buffer_3, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[3], half_cycle_send_buff[3][0], half_cycle_send_buff[3][1], half_cycle_send_buff[3][2], half_cycle_send_buff[3][3], half_cycle_send_buff[3][4], half_cycle_send_buff[3][5]);
sprintf(half_tx_buffer_4, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[4], half_cycle_send_buff[4][0], half_cycle_send_buff[4][1], half_cycle_send_buff[4][2], half_cycle_send_buff[4][3], half_cycle_send_buff[4][4], half_cycle_send_buff[4][5]);
sprintf(half_tx_buffer_5, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[5], half_cycle_send_buff[5][0], half_cycle_send_buff[5][1], half_cycle_send_buff[5][2], half_cycle_send_buff[5][3], half_cycle_send_buff[5][4], half_cycle_send_buff[5][5]);
}else if(pd_adc_half_b_start == true) {
sprintf(half_tx_buffer_6, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[0], half_cycle_send_buff[0][0], half_cycle_send_buff[0][1], half_cycle_send_buff[0][2], half_cycle_send_buff[0][3], half_cycle_send_buff[0][4], half_cycle_send_buff[0][5]);
sprintf(half_tx_buffer_7, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[1], half_cycle_send_buff[1][0], half_cycle_send_buff[1][1], half_cycle_send_buff[1][2], half_cycle_send_buff[1][3], half_cycle_send_buff[1][4], half_cycle_send_buff[1][5]);
sprintf(half_tx_buffer_8, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[2], half_cycle_send_buff[2][0], half_cycle_send_buff[2][1], half_cycle_send_buff[2][2], half_cycle_send_buff[2][3], half_cycle_send_buff[2][4], half_cycle_send_buff[2][5]);
sprintf(half_tx_buffer_9, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[3], half_cycle_send_buff[3][0], half_cycle_send_buff[3][1], half_cycle_send_buff[3][2], half_cycle_send_buff[3][3], half_cycle_send_buff[3][4], half_cycle_send_buff[3][5]);
sprintf(half_tx_buffer_10, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[4], half_cycle_send_buff[4][0], half_cycle_send_buff[4][1], half_cycle_send_buff[4][2], half_cycle_send_buff[4][3], half_cycle_send_buff[4][4], half_cycle_send_buff[4][5]);
sprintf(half_tx_buffer_11, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[5], half_cycle_send_buff[5][0], half_cycle_send_buff[5][1], half_cycle_send_buff[5][2], half_cycle_send_buff[5][3], half_cycle_send_buff[5][4], half_cycle_send_buff[5][5]);
}else if(pd_adc_half_c_start == true) {
sprintf(half_tx_buffer_12, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[0], half_cycle_send_buff[0][0], half_cycle_send_buff[0][1], half_cycle_send_buff[0][2], half_cycle_send_buff[0][3], half_cycle_send_buff[0][4], half_cycle_send_buff[0][5]);
sprintf(half_tx_buffer_13, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[1], half_cycle_send_buff[1][0], half_cycle_send_buff[1][1], half_cycle_send_buff[1][2], half_cycle_send_buff[1][3], half_cycle_send_buff[1][4], half_cycle_send_buff[1][5]);
sprintf(half_tx_buffer_14, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[2], half_cycle_send_buff[2][0], half_cycle_send_buff[2][1], half_cycle_send_buff[2][2], half_cycle_send_buff[2][3], half_cycle_send_buff[2][4], half_cycle_send_buff[2][5]);
sprintf(half_tx_buffer_15, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[3], half_cycle_send_buff[3][0], half_cycle_send_buff[3][1], half_cycle_send_buff[3][2], half_cycle_send_buff[3][3], half_cycle_send_buff[3][4], half_cycle_send_buff[3][5]);
sprintf(half_tx_buffer_16, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[4], half_cycle_send_buff[4][0], half_cycle_send_buff[4][1], half_cycle_send_buff[4][2], half_cycle_send_buff[4][3], half_cycle_send_buff[4][4], half_cycle_send_buff[4][5]);
sprintf(half_tx_buffer_17, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[5], half_cycle_send_buff[5][0], half_cycle_send_buff[5][1], half_cycle_send_buff[5][2], half_cycle_send_buff[5][3], half_cycle_send_buff[5][4], half_cycle_send_buff[5][5]);
}else if(pd_adc_half_d_start == true) {
sprintf(half_tx_buffer_18, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[0], half_cycle_send_buff[0][0], half_cycle_send_buff[0][1], half_cycle_send_buff[0][2], half_cycle_send_buff[0][3], half_cycle_send_buff[0][4], half_cycle_send_buff[0][5]);
sprintf(half_tx_buffer_19, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[1], half_cycle_send_buff[1][0], half_cycle_send_buff[1][1], half_cycle_send_buff[1][2], half_cycle_send_buff[1][3], half_cycle_send_buff[1][4], half_cycle_send_buff[1][5]);
sprintf(half_tx_buffer_20, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[2], half_cycle_send_buff[2][0], half_cycle_send_buff[2][1], half_cycle_send_buff[2][2], half_cycle_send_buff[2][3], half_cycle_send_buff[2][4], half_cycle_send_buff[2][5]);
sprintf(half_tx_buffer_21, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[3], half_cycle_send_buff[3][0], half_cycle_send_buff[3][1], half_cycle_send_buff[3][2], half_cycle_send_buff[3][3], half_cycle_send_buff[3][4], half_cycle_send_buff[3][5]);
sprintf(half_tx_buffer_22, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[4], half_cycle_send_buff[4][0], half_cycle_send_buff[4][1], half_cycle_send_buff[4][2], half_cycle_send_buff[4][3], half_cycle_send_buff[4][4], half_cycle_send_buff[4][5]);
sprintf(half_tx_buffer_23, "Ti%d,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf,\t%lf\r\n", LED_list_half[5], half_cycle_send_buff[5][0], half_cycle_send_buff[5][1], half_cycle_send_buff[5][2], half_cycle_send_buff[5][3], half_cycle_send_buff[5][4], half_cycle_send_buff[5][5]);
}
}
/*********************/
buf_no = 0;
if(ble_connection_st == 0) {
half_adc_end();
half_send_timer_stop();
printf("Half ADC STOP 1\r\n");
}
else if(pd_adc_half_a_start == true) { // B mode
pd_adc_half_a_start = false;
pd_adc_half_b_start = true;
half_adc_start();
}else if(pd_adc_half_b_start == true) { // C mode
pd_adc_half_b_start = false;
pd_adc_half_c_start = true;
half_adc_start();
}else if(pd_adc_half_c_start == true) { // D mode
pd_adc_half_c_start = false;
pd_adc_half_d_start = true;
half_adc_start();
}else if(pd_adc_half_d_start == true) { // Completed
pd_adc_half_d_start = false;
if(cmd_type_t == CMD_UART && prestatus==false) {
half_send_timer_stop();
}else if(cmd_type_t == CMD_BLE && prestatus==false) {
half_send_timer_start();
}
if(ble_connection_st == BLE_CONNECTED_ST) {
battery_timer_start();
}
led_off(99);
pd_off(99);
}
}
}
}
}
}
}
void half_send_loop(void * p_context) /* For x ms */
{
UNUSED_PARAMETER(p_context);
half_send_timer_stop();
switch(order) {
case 0:
data_tx_handler(half_tx_buffer_0);
break;
case 1:
data_tx_handler(half_tx_buffer_1);
break;
case 2:
data_tx_handler(half_tx_buffer_2);
break;
case 3:
data_tx_handler(half_tx_buffer_3);
break;
case 4:
data_tx_handler(half_tx_buffer_4);
break;
case 5:
data_tx_handler(half_tx_buffer_5);
break;
case 6:
data_tx_handler(half_tx_buffer_6);
break;
case 7:
data_tx_handler(half_tx_buffer_7);
break;
case 8:
data_tx_handler(half_tx_buffer_8);
break;
case 9:
data_tx_handler(half_tx_buffer_9);
break;
case 10:
data_tx_handler(half_tx_buffer_10);
break;
case 11:
data_tx_handler(half_tx_buffer_11);
break;
case 12:
data_tx_handler(half_tx_buffer_12);
break;
case 13:
data_tx_handler(half_tx_buffer_13);
break;
case 14:
data_tx_handler(half_tx_buffer_14);
break;
case 15:
data_tx_handler(half_tx_buffer_15);
break;
case 16:
data_tx_handler(half_tx_buffer_16);
break;
case 17:
data_tx_handler(half_tx_buffer_17);
break;
case 18:
data_tx_handler(half_tx_buffer_18);
break;
case 19:
data_tx_handler(half_tx_buffer_19);
break;
case 20:
data_tx_handler(half_tx_buffer_20);
break;
case 21:
data_tx_handler(half_tx_buffer_21);
break;
case 22:
data_tx_handler(half_tx_buffer_22);
break;
case 23:
data_tx_handler(half_tx_buffer_23);
order = 0;
#if FEATURE_PRINTF
printf("half_send Completed\r\n");
#endif
return;
default:
break;
}
order++;
if(ble_connection_st == 0) {
half_send_timer_stop();
order = 0;
printf("Half ADC STOP 2\r\n");}
else{
half_send_timer_start();
}
}
void half_adc_start(void)
{
pd_no = -1;
led_no = -1;
buf_no = 0;
if(pd_adc_half_a_start == true) {
#if FEATURE_PRINTF
printf("\r\n===== Half_A start ====================\r\n");
printf("LED : ");
#endif
for(uint8_t i =0; i < HALF_LED_NO; i++) {
LED_list_half[i] = led_half_list_a[i];
#if FEATURE_PRINTF
printf("%d ", LED_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n");
printf("PD : ");
#endif
for(uint8_t i =0; i < HALF_PD_NO; i++) {
PD_list_half[i] = pd_half_list_a[i];
#if FEATURE_PRINTF
printf("%d ", PD_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
}
if(pd_adc_half_b_start == true) {
#if FEATURE_PRINTF
printf("\r\n===== Half_B start ====================\r\n");
printf("LED : ");
#endif
for(uint8_t i =0; i < HALF_LED_NO; i++) {
LED_list_half[i] = led_half_list_b[i];
#if FEATURE_PRINTF
printf("%d ", LED_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n");
printf("PD : ");
#endif
for(uint8_t i =0; i < HALF_PD_NO; i++) {
PD_list_half[i] = pd_half_list_b[i];
#if FEATURE_PRINTF
printf("%d ", PD_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
}
if(pd_adc_half_c_start == true) {
#if FEATURE_PRINTF
printf("\r\n===== Half_C start ====================\r\n");
printf("LED : ");
#endif
for(uint8_t i =0; i < HALF_LED_NO; i++) {
LED_list_half[i] = led_half_list_c[i];
#if FEATURE_PRINTF
printf("%d ", LED_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n");
printf("PD : ");
#endif
for(uint8_t i =0; i < HALF_PD_NO; i++) {
PD_list_half[i] = pd_half_list_c[i];
#if FEATURE_PRINTF
printf("%d ", PD_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
}
if(pd_adc_half_d_start == true) {
#if FEATURE_PRINTF
printf("\r\n===== Half_D start ====================\r\n");
printf("LED : ");
#endif
for(uint8_t i =0; i < HALF_LED_NO; i++) {
LED_list_half[i] = led_half_list_d[i];
#if FEATURE_PRINTF
printf("%d ", LED_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n");
printf("PD : ");
#endif
for(uint8_t i =0; i < HALF_PD_NO; i++) {
PD_list_half[i] = pd_half_list_d[i];
#if FEATURE_PRINTF
printf("%d ", PD_list_half[i]);
#endif
}
#if FEATURE_PRINTF
printf("\r\n\r\n");
#endif
}
for(uint16_t i = 0; i < 72; i++) {
for(uint16_t j = 0; j < HALF_CYCLE_CNT; j++) {
half_cycle_buff[i][j] = 0.0f;
}
}
for(uint8_t i = 0; i < HALF_LED_NO; i++) {
for(uint8_t j = 0; j < HALF_PD_NO; j++) {
half_cycle_send_buff[i][j] = 0.0f;
}
}
half_adc_start2();
order =0;
}
void half_adc_start2(void)
{
half_adc_init();
#if FEATURE_PRINTF
half_timer_init();
half_timer_start();
#else
half_irq_init();
half_ppi_init();
half_sampling_event_enable();
//ada2200_start();
#endif
}
void half_adc_end(void)
{
#if FEATURE_PRINTF
printf("half_adc_end\r\n");
half_timer_stop();
#endif
#if !FEATURE_PRINTF
half_sampling_event_disable();
half_irq_uninit();
half_ppi_uninit();
#endif
half_adc_uninit();
//ada2200_stop();
nrf_delay_ms(5);
if(prestatus == true){
printf("1st\r\n");
if(device_activated() == 0)
{printf("2nd\r\n");
}
}
}
void half_adc_init(void)
{
#if FEATURE_PRINTF
printf("half_adc_init\r\n");
#endif
static nrfx_saadc_config_t default_config;
default_config.resolution = (nrf_saadc_resolution_t)NRFX_SAADC_CONFIG_RESOLUTION; /* Resolution is 10bits */
default_config.oversample = (nrf_saadc_oversample_t)NRFX_SAADC_CONFIG_OVERSAMPLE; /* Over Sampling Disabled */
default_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY; /* Interrupt Priority is 0(Highest) */
default_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE; /* Low Power Mode is Disabled */
static nrf_saadc_channel_config_t config;
config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
config.gain = NRF_SAADC_GAIN1_6;
config.reference = NRF_SAADC_REFERENCE_INTERNAL;
config.acq_time = NRF_SAADC_ACQTIME_3US;
config.mode = NRF_SAADC_MODE_DIFFERENTIAL;
config.burst = NRF_SAADC_BURST_DISABLED;
config.pin_p = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN0);
config.pin_n = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN1);
ret_code_t err_code = nrf_drv_saadc_init(&default_config, half_voltage_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
if (prestatus==true)
{
err_code = nrf_drv_saadc_buffer_convert(pd_half_adc_buf[0], half_samples_in_buffer + 10);
}
else
{
err_code = nrf_drv_saadc_buffer_convert(pd_half_adc_buf[0], half_samples_in_buffer + m_config.pd_delay_us/16);
}
APP_ERROR_CHECK(err_code);
}
void half_adc_uninit(void)
{
#if FEATURE_PRINTF
printf("pd_half_adc_uninit\r\n");
#endif
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
}
#if !FEATURE_PRINTF
void half_irq_init(void)
{
ret_code_t err_code;
/* Initialize int pin */
if (!nrfx_gpiote_is_init())
{
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
in_config.pull = NRF_GPIO_PIN_PULLDOWN;
err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
}
void half_irq_uninit(void)
{
nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
}
#endif
void half_send_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_half_send_loop_timer_id, APP_TIMER_TICKS(HALF_SEND_LOOP_INTERVAL), NULL));
}
void half_send_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_half_send_loop_timer_id));
}
void half_send_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_half_send_loop_timer_id, APP_TIMER_MODE_SINGLE_SHOT, half_send_loop));
}

View File

@@ -0,0 +1,51 @@
/*******************************************************************************
* @file meas_pd_voltage_half.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _MEAS_PD_VOLTAGE_HALF_H__
#define _MEAS_PD_VOLTAGE_HALF_H__
#include "sdk_common.h"
#include "nrf_drv_saadc.h"
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0,17)
#endif
#define HALF_PD_NO 6
#define HALF_LED_NO 6
#define HALF_CYCLE_CNT 32
void half_ppi_init(void);
void half_ppi_uninit(void);
void half_sampling_event_enable(void);
void half_sampling_event_disable(void);
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void half_voltage_handler(nrf_drv_saadc_evt_t const * p_event); /* PD Voltage reading */
void half_adc_start(void);
void half_adc_start2(void);
void half_adc_end(void);
void half_adc_init(void);
void half_adc_uninit(void);
#if !FEATURE_PRINTF
void half_irq_init(void);
void half_irq_uninit(void);
#endif
void half_send_loop(void * p_context); /* For x ms */
void half_send_timer_start(void);
void half_send_timer_stop(void);;
void half_send_timer_init(void);
#endif /* _MEAS_PD_VOLTAGE_HALF_H__ */

View File

@@ -0,0 +1,349 @@
/*******************************************************************************
TEST medi50 Dec 23
if(resetCount>=3) return true; 3 COUNT 25/11/04 CJ CHUN
******************************************************************************/
#include "sdk_common.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_drv_saadc.h"
#include "app_timer.h"
#include "nrfx_gpiote.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "nrf_delay.h"
#include "ada2200_spi.h"
#include "battery_saadc.h"
#include "ble_nus.h"
#include "meas_pd_voltage_simple.h"
#include "main.h"
#include <cmd_parse.h>
//#include "fstorage.h"
#include "power_control.h"
#include "debug_print.h"
#define PD_REF_VOLTAGE_IN_MILLIVOLTS 600.0f /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
#define PD_PRE_SCALING_COMPENSATION 6.0f /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
#define PD_ADC_RES_10BITS 1023.0f /**< Maximum digital value for 10-bit ADC conversion. */
// remark for avoid warning error
// static nrf_saadc_value_t pressure_adc_buf[2]; // chanul 2ea for simple each 1ea
/**@brief Macro to convert the result of ADC conversion in millivolts.
*
* @param[in] ADC_VALUE ADC result.
*
* @retval Result converted to millivolts.
*/
#define PD_VOUT_IN_MILLI_VOLTS(ADC_VALUE)\
(((((ADC_VALUE) * PD_REF_VOLTAGE_IN_MILLIVOLTS) / PD_ADC_RES_10BITS) * PD_PRE_SCALING_COMPENSATION)*2)
#define SIM_SAMPLES_IN_BUFFER 128
static nrf_saadc_value_t pd_adc_buf[2][SIM_SAMPLES_IN_BUFFER];
uint8_t simple_samples_in_buffer = 8;
#define SAMPLE_CYCLE_CNT 128
float simple_cycle_buff[SAMPLE_CYCLE_CNT]; /* For Cycle-8, Cycle-16, Cycle-24, Cycle-32 */
float simple_cycle_send_buff = 0.0f;
int16_t bi_simple_cycle_buff[SAMPLE_CYCLE_CNT];
uint16_t ubi_simple_cycle_buff[SAMPLE_CYCLE_CNT];/* For Cycle-8, Cycle-16, Cycle-24, Cycle-32 */
int16_t bi_simple_cycle_send_buff = 0;
extern uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] ;
static nrf_ppi_channel_t m_ppi_channel;
extern uint8_t m_pd_adc_cnt;
extern char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN];
extern bool lock_check;
extern which_cmd_t cmd_type_t;
#if FEATURE_CHAMBER_AUTO_TEST
extern auto_meas_mode_t auto_test_mode;
#endif
extern volatile bool ble_connection_st;
extern bool ble_got_new_data;
bool con_single;
uint8_t rep = 0;
extern uint8_t resetCount; //cj add
//APP_TIMER_DEF(m_simple_send_loop_timer_id);
//#define SIMPLE_SEND_LOOP_INTERVAL 100
#define PATTERN_LENGTH 8
#define NORMAL_AMPLITUDE_THRESHOLD 40 // values above ~50 in magnitude are normal
bool is_lockin_pattern(int16_t *pattern, uint8_t length) {
int16_t avg_amplitude = 0;
uint8_t positive_count = 0;
uint8_t negative_count = 0;
// Calculate average amplitude and count positives/negatives
for (uint8_t i = 0; i < length; i++) {
int16_t value = pattern[i];
avg_amplitude += abs(value);
if (value >= 0) {
positive_count++;
} else {
negative_count++;
}
}
avg_amplitude /= length;
DBG_PRINTF("lock avg amplitude=%d,p_c=%d,n_c=%d\r\n\r\n", avg_amplitude,positive_count,negative_count);
// Check conditions:
// - Large enough average amplitude
// - Exactly 1 positive and (length - 1) negative values
if(resetCount>=3) return true;
if (avg_amplitude > NORMAL_AMPLITUDE_THRESHOLD &&
positive_count == 1 && negative_count == (length - 1)) {
return true; // lock-in (normal) pattern
} else {
return false; // abnormal pattern
}
}
void simple_ppi_init(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
uint32_t gpiote_event_addr = nrf_drv_gpiote_in_event_addr_get(ADA2200_SYNCO_PIN);
uint32_t saadc_sample_task_addr = nrf_drv_saadc_sample_task_get();
/* setup ppi channel so that timer compare event is triggering sample task in SAADC */
err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
gpiote_event_addr,
saadc_sample_task_addr);
APP_ERROR_CHECK(err_code);
}
void simple_ppi_uninit(void)
{
ret_code_t err_code;
err_code = nrf_drv_ppi_uninit();
APP_ERROR_CHECK(err_code);
}
void simple_sampling_event_enable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
void simple_sampling_event_disable(void)
{
ret_code_t err_code = nrf_drv_ppi_channel_disable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}
/**@brief Function for handling the ADC interrupt.
*
* @details This function will fetch the conversion result from the ADC, convert the value into
* percentage and send it to peer.
*/
static void simple_voltage_handler(nrf_drv_saadc_evt_t const * p_event) /* PD Voltage reading */
{
ret_code_t err_code;
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, simple_samples_in_buffer);
APP_ERROR_CHECK(err_code);
for(uint8_t i = 0; i < simple_samples_in_buffer; i++) {
bi_simple_cycle_buff[i] = p_event->data.done.p_buffer[i];
}
simple_mesurement_stop();
simple_cycle_send_buff = 0;
bi_simple_cycle_send_buff = 0;
for(uint8_t i = 0; i < simple_samples_in_buffer; i++) {
simple_cycle_buff[i] = PD_VOUT_IN_MILLI_VOLTS(bi_simple_cycle_buff[i]);
//simple_cycle_buff[i] = simple_cycle_buff[i] * -1;
bi_simple_cycle_send_buff += bi_simple_cycle_buff[i];
simple_cycle_send_buff += simple_cycle_buff[i];
}
}
if(con_single==true)
{
//return;
}
else
{
if(cmd_type_t == CMD_UART) {
DBG_PRINTF("Th%f\r\n\r\n", simple_cycle_send_buff);
DBG_PRINTF( "MOD: %f,%f,%f,%f,%f,%f,%f,%f\r\n", simple_cycle_buff[0], simple_cycle_buff[1], simple_cycle_buff[2], simple_cycle_buff[3], simple_cycle_buff[4]
, simple_cycle_buff[5], simple_cycle_buff[6], simple_cycle_buff[7]);
} else if(cmd_type_t == CMD_BLE) {
// if(lock_check == true){
DBG_PRINTF("Th%f\r\n\r\n", simple_cycle_send_buff);
DBG_PRINTF( "MOD: %f,%f,%f,%f,%f,%f,%f,%f\r\n", simple_cycle_buff[0], simple_cycle_buff[1], simple_cycle_buff[2], simple_cycle_buff[3], simple_cycle_buff[4]
, simple_cycle_buff[5], simple_cycle_buff[6], simple_cycle_buff[7]);
for(uint8_t i = 0; i < simple_samples_in_buffer; i++) {
ubi_simple_cycle_buff[i] = (uint16_t)bi_simple_cycle_buff[i];
}
format_data(ble_bin_buffer, "rsh:", ubi_simple_cycle_buff, (m_pd_adc_cnt*2));
binary_tx_handler(ble_bin_buffer,(m_pd_adc_cnt+2));
}
}
if (lock_check == true) {
if (is_lockin_pattern(bi_simple_cycle_buff,8) == false){
if(device_reactivated() == 0)
{
DBG_PRINTF("reset!!\r\n");
}
}
resetCount++;
DBG_PRINTF("Reset Count :%d \r\n",resetCount);
}
else
{
}
}
void simple_adc_init(void)
{
static nrfx_saadc_config_t default_config;
default_config.resolution = (nrf_saadc_resolution_t)NRFX_SAADC_CONFIG_RESOLUTION; /* Resolution is 10bits */
default_config.oversample = (nrf_saadc_oversample_t)NRFX_SAADC_CONFIG_OVERSAMPLE; /* Over Sampling Disabled */
default_config.interrupt_priority = NRFX_SAADC_CONFIG_IRQ_PRIORITY; /* Interrupt Priority is 0(Highest) */
default_config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE; /* Low Power Mode is Disabled */
static nrf_saadc_channel_config_t config;
config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
config.gain = NRF_SAADC_GAIN1_6;
config.reference = NRF_SAADC_REFERENCE_INTERNAL;
config.acq_time = NRF_SAADC_ACQTIME_3US;
config.mode = NRF_SAADC_MODE_DIFFERENTIAL;
config.burst = NRF_SAADC_BURST_DISABLED;
config.pin_p = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN0);
config.pin_n = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN1);
ret_code_t err_code = nrf_drv_saadc_init(&default_config, simple_voltage_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_saadc_channel_init(0, &config);
APP_ERROR_CHECK(err_code);
if(con_single==true){
err_code = nrf_drv_saadc_buffer_convert(pd_adc_buf[0], SIM_SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);
}
else{
err_code = nrf_drv_saadc_buffer_convert(pd_adc_buf[0], simple_samples_in_buffer);
APP_ERROR_CHECK(err_code);
}
}
void simple_adc_uninit(void)
{
nrf_drv_saadc_uninit();
nrf_drv_saadc_channel_uninit(0);
}
void simple_irq_init(void){
ret_code_t err_code;
/* Initialize int pin */
if (!nrfx_gpiote_is_init())
{
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
in_config.pull = NRF_GPIO_PIN_PULLDOWN;
err_code = nrfx_gpiote_in_init(ADA2200_SYNCO_PIN, &in_config, NULL);
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_event_enable(ADA2200_SYNCO_PIN, true);
}
void simple_irq_uninit(void){
nrfx_gpiote_in_event_disable(ADA2200_SYNCO_PIN);
nrfx_gpiote_in_uninit(ADA2200_SYNCO_PIN);
}
void simple_mesurement_start(void){
battery_timer_stop();
nrf_delay_ms(10);
memset(simple_cycle_buff, 0, SAMPLE_CYCLE_CNT);
nrf_delay_ms(10);
simple_irq_init();
nrf_delay_ms(10);
simple_adc_init();
nrf_delay_ms(10);
simple_ppi_init();
nrf_delay_ms(10);
simple_sampling_event_enable();
nrf_delay_ms(10);
}
void simple_mesurement_stop(void){
if(ble_connection_st == 1) {
battery_timer_start();
}
simple_sampling_event_disable();
simple_adc_uninit();
simple_ppi_uninit();
simple_irq_uninit();
}

View File

@@ -0,0 +1,36 @@
/*******************************************************************************
* @file meas_pd_voltage_simple.h
* @author CandyPops Co.
* @version V1.0.0
* @date 2022-09-05
* @brief
******************************************************************************/
#ifndef _MEAS_PD_VOLTAGE_SIMPLE_H__
#define _MEAS_PD_VOLTAGE_SIMPLE_H__
#include "sdk_common.h"
#ifndef ADA2200_SYNCO_PIN
#define ADA2200_SYNCO_PIN NRF_GPIO_PIN_MAP(0,17)
#endif
void simple_ppi_init(void);
void simple_ppi_uninit(void);
void simple_sampling_event_enable(void);
void simple_sampling_event_disable(void);
void simple_adc_init(void);
void simple_adc_uninit(void);
void simple_irq_init(void);
void simple_irq_uninit(void);
void simple_mesurement_start(void);
void simple_mesurement_stop(void);
void simple_send_loop(void * p_context); /* For x ms */
void simple_send_timer_start(void);
void simple_send_timer_stop(void);;
void simple_send_timer_init(void);
#endif /* _MEAS_PD_VOLTAGE_SIMPLE_H__ */

View File

@@ -0,0 +1,853 @@
/*******************************************************************************
* @file measurements.c
* @brief LED/PD measurement control module for NIRS system
* @author Charles KWON <charleskwon@medithings.co.kr>
* @version V2.0.0
* @date 2025-12-31
*
* Copyright (c) 2025 Medithings Inc.
* All rights reserved.
*
* This module provides LED and photodetector (PD) control functions for the
* NIRS (Near-Infrared Spectroscopy) bladder monitoring system.
*
* ============================================================================
* MODULE OVERVIEW
* ============================================================================
*
* Hardware Components Controlled:
* - 48 NIR LEDs (LED0-LED47) via CAT9532 I2C LED driver
* - 4 Photodetectors (PD0, PD1, MOD, MOD2) via FSA5157P6X analog switch
* - MCP4725 DAC for PD gain control (I2C, 12-bit)
* - DS3930 EEPROM for LED power calibration storage
*
* LED-PD Mapping:
* - LED 0-23 -> PD0 (photodetector 0)
* - LED 24-47 -> PD1 (photodetector 1)
* - MOD/MOD2 -> Lock-in amplifier modulation channels
*
* DAC Gain Range:
* - MCP4725 output: 0.1V ~ 1.1V
* - DAC value range: 125 ~ 1366 (12-bit)
* - Default value: 1000 (mid-range)
*
* ============================================================================
* MAIN FUNCTIONS
* ============================================================================
*
* LED Control:
* - led_on(index) : Turn on specific LED (0-47)
* - led_off(index) : Turn off LED (99=all off, 98=clear)
* - led_select(index) : Select LED via GPIO multiplexer
*
* PD Control:
* - pd_on(index) : Turn on specific PD (0-3)
* - pd_off(index) : Turn off PD (99=all off)
* - pd_select(index) : Select PD channel
*
* Gain Control:
* - pd_gain_set(led) : Set PD gain from led_pd_dac_v[] lookup table
* - imm_gain_set(dac) : Set immediate DAC value
* - led_pd_mod_set() : Set modulation gain for lock-in
*
* EEPROM Operations:
* - led_power_read(index) : Read LED power from ROM
* - led_power_save_mem(index) : Save LED power to ROM
* - led_power_save_mem_48() : Save all 48 LED powers
*
******************************************************************************/
/*============================================================================*/
/* Includes - Charles KWON */
/*============================================================================*/
#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_spi.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "boards.h"
#include "bsp.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "app_util_platform.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_log.h"
#include "ble_nus.h"
#include "LED_Parse.h"
#include "app_timer.h"
#include "measurements.h"
#include "main.h"
//#include "ad5272_i2c.h"
#include "mcp4725_i2c.h"
#include <cmd_parse.h>
#include "debug_print.h"
#include "i2c_manager.h"
/*============================================================================*/
/* Configuration Constants - Charles KWON */
/*============================================================================*/
/**
* @brief Total number of LEDs in the system
* - Charles KWON
*
* Currently using 24 LEDs (LED0-LED23) for single PD configuration.
* Can be extended to 48 for dual PD configuration.
*/
#define LED_NUM 24
/** @brief Timer interval for BLE data transmission (ms) */
#define MEA_SEND_LOOP_INTERVAL 100
/*============================================================================*/
/* State Variables - Charles KWON */
/*============================================================================*/
/**
* @brief Currently activated LED index
* - Charles KWON
*
* Tracks which LED is currently turned on.
* Value 99 indicates no LED is active (all off).
*/
uint8_t activated_led = 99;
/**
* @brief Currently activated photodetector index
* - Charles KWON
*
* Tracks which PD channel is currently selected.
* Value 99 indicates no PD is active (all off).
*/
uint8_t activated_pd = 99;
/*============================================================================*/
/* Timer Definitions - Charles KWON */
/*============================================================================*/
/** @brief Timer for sequenced BLE data transmission */
APP_TIMER_DEF(m_mea_send_loop_timer_id);
/** @brief Counter for BLE transmission sequence */
static uint8_t cnt = 0;
/*============================================================================*/
/* Communication Buffers - Charles KWON */
/*============================================================================*/
/** @brief TX buffer for BLE data transmission */
char mea_tx_buffer[BLE_NUS_MAX_DATA_LEN];
/** @brief External flag indicating command processing state */
extern volatile bool processing;
/*============================================================================*/
/* LED-PD Gain Calibration Table - Charles KWON */
/*============================================================================*/
/**
* @brief DAC values for LED-PD gain matching
* - Charles KWON
*
* Each LED has a corresponding DAC value that sets the optimal
* PD amplifier gain for that LED-PD pair. Values are calibrated
* during AGC (Automatic Gain Control) and stored in EEPROM.
*
* DAC Range:
* - Minimum: 125 (0.1V output)
* - Maximum: 1366 (1.1V output)
* - Default: 1000 (mid-range)
*
* This array is populated from EEPROM at startup or after AGC.
* Defined in device_config.c
*/
extern uint16_t led_pd_dac_v[];
/**
* @brief DAC value when all LEDs are off
* - Charles KWON
*
* Used as baseline/reference gain when no LED is active.
*/
uint16_t led_off_dac_v = 1360;
/**
* @brief Default PD channel when LED is off
* - Charles KWON
*/
uint8_t led_off_pd = 0;
/*============================================================================*/
/* BLE Data Transmission Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Start LED-PD gain array printout via BLE
* - Charles KWON
*
* Initiates sequenced transmission of the led_pd_dac_v[] array
* over BLE. Data is sent in chunks to comply with BLE MTU limits.
*/
void led_pd_gain_array_printout(void)
{
cnt = 0;
mea_send_timer_start();
}
/**
* @brief Timer callback for sending LED-PD gain data via BLE
* - Charles KWON
*
* Called periodically to transmit gain calibration data.
* Sends data in 3 chunks (cnt 0-2), then outputs debug info
* and clears the processing flag.
*
* Output Format: "Tag-Currrnt [cnt], [dac0], [dac1], ..., [dac23]\r\n"
*
* @param[in] p_context Unused timer context
*/
void mea_send_loop(void *p_context)
{
UNUSED_PARAMETER(p_context);
DBG_PRINTF("\r\n1CNT =%d\r\n", cnt);
mea_send_timer_stop();
if (cnt < 6) {
uint8_t i = (int)(cnt);
if (cnt < 3) {
/* Build string dynamically for easy LED_NUM changes */
int offset = sprintf(mea_tx_buffer, "Tag-Currrnt %d", i);
for (uint8_t j = 0; j < LED_NUM; j++) {
offset += sprintf(mea_tx_buffer + offset, ",\t%d", led_pd_dac_v[j]);
}
sprintf(mea_tx_buffer + offset, "\r\n");
data_tx_handler(mea_tx_buffer);
}
DBG_PRINTF("\r\n2CNT =%d\r\n", cnt);
mea_send_timer_start();
} else {
/* Final iteration: print debug summary */
DBG_PRINTF("\r\nLED-PD Gain Array =\r\n");
for (uint16_t j = 0; j < LED_NUM; j++) {
DBG_PRINTF("%d,\t", led_pd_dac_v[j]);
}
DBG_PRINTF("\r\n");
processing = false;
}
cnt++;
}
/*============================================================================*/
/* LED Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Turn on specified LED
* - Charles KWON
*
* Activates the specified LED through the CAT9532 LED driver.
* Also enables the analog switch trigger for signal routing.
*
* @param[in] led_index LED index (0-47 for specific LED, <=100 for valid range)
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on failure
*
* @note Sets activated_led to track current LED state
* @note Calls trig_SW(true) to enable signal path
*/
ret_code_t led_on(uint8_t led_index)
{
uint32_t err_code = NRF_SUCCESS;
#if FEATURE_PRINTF
DBG_PRINTF("\tled_on, %d =====================\r\n", led_index);
#endif
if (led_index <= 100) {
activated_led = led_index;
trig_SW(true); /* Enable analog switch for signal routing */
if (NRF_SUCCESS != led_select(led_index)) {
DBG_PRINTF("ERR!!! LED Select\r\n");
return NRF_ERROR_INTERNAL;
}
} else {
DBG_PRINTF("ERR!!! led_index Failed! %d\r\n", led_index);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/**
* @brief Turn off LED
* - Charles KWON
*
* Deactivates LED and disables the analog switch trigger.
*
* @param[in] led_index 99 = all LEDs off, 98 = clear state
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on invalid index
*
* @note Calls trig_SW(false) to disable signal path
*/
ret_code_t led_off(uint8_t led_index)
{
uint32_t err_code = NRF_SUCCESS;
trig_SW(false); /* Disable analog switch */
#if FEATURE_PRINTF
DBG_PRINTF("led_off\r\n");
#endif
if (led_index == 99) {
LED_ALLOFF(); /* Turn off all LEDs */
activated_led = 99;
} else if (led_index == 98) {
LED99(); /* Clear LED state */
DBG_PRINTF("clear \r\n");
activated_led = 98;
} else {
DBG_PRINTF("ERR!!! led_index Failed! %d\r\n", led_index);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/*============================================================================*/
/* Gain Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Set modulation gain via MCP4725 DAC
* - Charles KWON
*
* Configures the DAC for lock-in amplifier modulation mode.
* Used when measuring with the MOD photodetector channel.
*
* @param[in] mod_gain DAC value (0-2000)
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on failure
*
* @note Initializes SW I2C if not already done
* @note Activates MOD channel (PD index 2)
*/
ret_code_t led_pd_mod_set(uint16_t mod_gain)
{
uint32_t err_code = NRF_SUCCESS;
if (mod_gain <= 2000) {
sw_i2c_init_once(); /* Ensure I2C is initialized */
mcp4725_writeFastMode(mod_gain);
if (NRF_SUCCESS != pd_on(2)) { /* Enable MOD channel */
DBG_PRINTF("ERR!!! MOD_on\r\n");
return NRF_ERROR_INTERNAL;
}
} else {
DBG_PRINTF("ERR!!! lockin Test Failed!\r\n");
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/**
* @brief Set PD gain matching value for specified LED
* - Charles KWON
*
* Configures the PD amplifier gain based on the LED index.
* Automatically selects the appropriate PD channel:
* - LED 0-23 -> PD0
* - LED 24-47 -> PD1
* - LED 99 -> Default PD (led_off_pd)
*
* @param[in] led_index LED index for gain lookup
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on failure
*/
ret_code_t led_pd_matching_value_set(uint8_t led_index)
{
uint32_t err_code = NRF_SUCCESS;
if (led_index <= 100) {
/* Set DAC gain from lookup table */
if (NRF_SUCCESS != pd_gain_set(led_index)) {
DBG_PRINTF("ERR!!! pd_gain_set\r\n");
return NRF_ERROR_INTERNAL;
}
/* Determine PD channel based on LED index */
uint8_t pd_index;
if (led_index < 24) {
pd_index = 0; /* PD0 for LED 0-23 */
} else if ((led_index < 48) && (led_index > 23)) {
pd_index = 1; /* PD1 for LED 24-47 */
} else if (led_index == 99) {
pd_index = led_off_pd; /* Default PD when off */
}
/* Activate the selected PD channel */
if (NRF_SUCCESS != pd_on(pd_index)) {
DBG_PRINTF("ERR!!! pd_on\r\n");
return NRF_ERROR_INTERNAL;
}
} else {
DBG_PRINTF("ERR!!! led_indexFailed! %d\r\n", led_index);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/*============================================================================*/
/* Photodetector Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Turn on specified photodetector
* - Charles KWON
*
* Activates one of the 4 PD channels via FSA5157P6X analog switch.
*
* PD Channels:
* - 0: PD0 (for LED 0-23)
* - 1: PD1 (for LED 24-47)
* - 2: MOD (lock-in modulation)
* - 3: MOD2 (secondary modulation)
*
* @param[in] pd_index Photodetector index (0-3)
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on failure
*/
ret_code_t pd_on(uint8_t pd_index)
{
uint32_t err_code = NRF_SUCCESS;
#if FEATURE_FOR_SCOPE
/* Debug pulse for oscilloscope timing */
nrf_gpio_pin_set(PD_CLK_26);
nrf_gpio_pin_clear(PD_CLK_26);
#endif
#if FEATURE_PRINTF
DBG_PRINTF("pd_on, %d ===== \r\n", pd_index);
#endif
if (pd_index <= 3) {
activated_pd = pd_index;
if (NRF_SUCCESS != pd_select(pd_index)) {
DBG_PRINTF("ERR!!! pd_on Failed! %d\r\n", pd_index);
return NRF_ERROR_INTERNAL;
}
} else {
DBG_PRINTF("ERR!!! pd_index Failed! %d\r\n", pd_index);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/**
* @brief Turn off photodetector
* - Charles KWON
*
* Deactivates all PD channels. Only accepts index 99 for safety.
*
* @param[in] pd_index Must be 99 for all-off operation
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on invalid index
*/
ret_code_t pd_off(uint8_t pd_index)
{
uint32_t err_code = NRF_SUCCESS;
#if FEATURE_PRINTF
DBG_PRINTF("pd_off\r\n");
#endif
if (pd_index == 99) {
PD_ALLOFF();
activated_pd = 99;
} else {
DBG_PRINTF("ERR!!! PD Off Failed! %d\r\n", pd_index);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/*============================================================================*/
/* LED Power EEPROM Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Read all 24 LED power values from ROM
* - Charles KWON
*
* Reads LED power calibration values from DS3930 EEPROM.
* Each read has 1ms delay for EEPROM access timing.
*
* @param[out] data Output buffer for 24 uint16_t values
*/
void led_power_read_48(uint16_t *data)
{
for (uint8_t i = 0; i < 24; i++) {
data[i] = LED_READ_ROM(i);
nrf_delay_ms(1); /* EEPROM access delay */
}
}
/**
* @brief Read single LED power value from ROM
* - Charles KWON
*
* @param[in] led_index LED index (0-47)
* @return LED power value, or NRF_ERROR_INTERNAL on invalid index
*/
int16_t led_power_read(uint8_t led_index)
{
int16_t led_power;
if (led_index <= 47) {
led_power = LED_READ_ROM(led_index);
} else {
DBG_PRINTF("ERR!!! led_index Failed! %d\r\n", led_index);
return NRF_ERROR_INTERNAL;
}
return led_power;
}
/**
* @brief Save LED power value to ROM
* - Charles KWON
*
* Writes LED power calibration value to DS3930 EEPROM.
*
* @param[in] led_index LED index (0-47)
* @param[in] led_power Power value (0-255)
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on failure
*/
ret_code_t led_power_save_mem(uint8_t led_index, int16_t led_power)
{
uint32_t err_code = NRF_SUCCESS;
if ((led_index <= 47) || (led_power <= 255)) {
if (NRF_SUCCESS != LED_WRITE_ROM(led_index, led_power)) {
DBG_PRINTF("ERR!!! DS3930 1\r\n");
err_code = NRF_ERROR_INTERNAL;
}
} else {
DBG_PRINTF("ERR!!! led_index || pd_index Failed! %d, %d\r\n", led_index, led_power);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/**
* @brief Save same LED power value to all 48 LEDs
* - Charles KWON
*
* Writes identical power value to all LED EEPROM locations.
* Useful for factory reset or uniform calibration.
*
* @param[in] led_index Unused parameter
* @param[in] led_power Power value to set for all LEDs
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on any write failure
*
* @note 10ms delay between writes for EEPROM programming time
*/
ret_code_t led_power_save_mem_6(uint8_t led_index, int16_t led_power)
{
uint32_t err_code = NRF_SUCCESS;
for (uint8_t i = 0; i < 48; i++) {
if (NRF_SUCCESS != LED_WRITE_ROM(i, led_power)) {
DBG_PRINTF("ERR!!! DS3930 1\r\n");
err_code = NRF_ERROR_INTERNAL;
}
nrf_delay_ms(10); /* EEPROM programming delay */
}
return err_code;
}
/**
* @brief Save array of LED power values to all 48 LEDs
* - Charles KWON
*
* Writes individual power values for each LED from input array.
*
* @param[in] led_power Array of 48 power values (uint8_t)
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on any write failure
*
* @note 10ms delay between writes for EEPROM programming time
*/
ret_code_t led_power_save_mem_48(uint8_t *led_power)
{
uint32_t err_code = NRF_SUCCESS;
for (uint8_t i = 0; i < 48; i++) {
if (NRF_SUCCESS != LED_WRITE_ROM(i, (int16_t)(led_power[i]))) {
DBG_PRINTF("ERR!!! DS3930 1\r\n");
err_code = NRF_ERROR_INTERNAL;
}
nrf_delay_ms(10); /* EEPROM programming delay */
}
return err_code;
}
/**
* @brief Set LED power (placeholder function)
* - Charles KWON
*
* Currently not implemented. Reserved for future LED current control.
*
* @param[in] led_index LED index
* @return NRF_SUCCESS always
*/
ret_code_t led_power_set(uint8_t led_index)
{
uint32_t err_code = NRF_SUCCESS;
return err_code;
}
/*============================================================================*/
/* DAC Gain Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Set PD gain via MCP4725 DAC based on LED-PD matching table
* - Charles KWON
*
* Looks up the calibrated DAC value for the specified LED from
* led_pd_dac_v[] array and programs the MCP4725 DAC.
*
* @param[in] activated_led LED index for gain lookup (0-47, 99=off)
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on invalid index
*
* @note Uses SW I2C for MCP4725 communication
* @note LED 99 uses led_off_dac_v as default gain
*/
ret_code_t pd_gain_set(uint8_t activated_led)
{
uint32_t err_code = NRF_SUCCESS;
#if FEATURE_PRINTF
DBG_PRINTF("<<pd_gain_set L%d, P%d>>\r\n", activated_led, pd_index);
#endif
if (activated_led <= 47) {
sw_i2c_init_once(); /* Ensure I2C is ready */
mcp4725_writeFastMode(led_pd_dac_v[activated_led]);
} else if (activated_led == 99) {
mcp4725_writeFastMode(led_off_dac_v); /* Use default off-state gain */
} else {
DBG_PRINTF("ERR!!! led_index Failed! %d\r\n", activated_led);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/**
* @brief Set immediate DAC gain value
* - Charles KWON
*
* Directly programs the MCP4725 DAC with specified value.
* Used for manual gain adjustment and testing.
*
* @param[in] imm_dac DAC value (0-2000)
* @return NRF_SUCCESS on success, NRF_ERROR_INTERNAL on out-of-range
*/
ret_code_t imm_gain_set(uint16_t imm_dac)
{
uint32_t err_code = NRF_SUCCESS;
if (imm_dac <= 2000) {
sw_i2c_init_once();
mcp4725_writeFastMode(imm_dac);
DBG_PRINTF("dac_v %d\r\n", imm_dac);
} else {
DBG_PRINTF("ERR!!! range Failed! %d\r\n", activated_led);
return NRF_ERROR_INTERNAL;
}
return err_code;
}
/*============================================================================*/
/* Hardware Selection Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Select and activate specific LED via GPIO multiplexer
* - Charles KWON
*
* Programs the CAT9532 LED driver to activate the specified LED.
* LED selection is done via I2C commands to set the appropriate
* output pins on the LED driver IC.
*
* @param[in] led_index LED index (0-47 for specific LED, 99=clear/off)
* @return NRF_SUCCESS on success, NRF_ERROR_NOT_FOUND on invalid index
*
* @note 1ms delay after selection for GPIO stabilization
* @note LED macros (LED0, LED1, etc.) are defined in LED_Parse.h
*/
ret_code_t led_select(uint8_t led_index)
{
uint32_t err_code = NRF_SUCCESS;
switch (led_index) {
case 0: LED0(); break;
case 1: LED1(); break;
case 2: LED2(); break;
case 3: LED3(); break;
case 4: LED4(); break;
case 5: LED5(); break;
case 6: LED6(); break;
case 7: LED7(); break;
case 8: LED8(); break;
case 9: LED9(); break;
case 10: LED10(); break;
case 11: LED11(); break;
case 12: LED12(); break;
case 13: LED13(); break;
case 14: LED14(); break;
case 15: LED15(); break;
case 16: LED16(); break;
case 17: LED17(); break;
case 18: LED18(); break;
case 19: LED19(); break;
case 20: LED20(); break;
case 21: LED21(); break;
case 22: LED22(); break;
case 23: LED23(); break;
case 24: LED24(); break;
case 25: LED25(); break;
case 26: LED26(); break;
case 27: LED27(); break;
case 28: LED28(); break;
case 29: LED29(); break;
case 30: LED30(); break;
case 31: LED31(); break;
case 32: LED32(); break;
case 33: LED33(); break;
case 34: LED34(); break;
case 35: LED35(); break;
case 36: LED36(); break;
case 37: LED37(); break;
case 38: LED38(); break;
case 39: LED39(); break;
case 40: LED40(); break;
case 41: LED41(); break;
case 42: LED42(); break;
case 43: LED43(); break;
case 44: LED44(); break;
case 45: LED45(); break;
case 46: LED46(); break;
case 47: LED47(); break;
case 99: LED99(); break; /* All LEDs off / clear state */
default:
err_code = NRF_ERROR_NOT_FOUND;
break;
}
/* GPIO stabilization delay */
nrf_delay_ms(1);
return err_code;
}
/**
* @brief Select photodetector channel
* - Charles KWON
*
* Programs the FSA5157P6X analog switch to route the selected
* PD output to the ADC input.
*
* PD Channel Mapping:
* - 0: PD0 - Primary photodetector (LED 0-23)
* - 1: PD1 - Secondary photodetector (LED 24-47)
* - 2: MOD - Lock-in amplifier modulation channel
* - 3: MOD2 - Secondary modulation channel
*
* @param[in] pd_index Photodetector index (0-3)
* @return NRF_SUCCESS on success, NRF_ERROR_NOT_FOUND on invalid index
*
* @note All PD channels are turned off before selecting new one
*/
ret_code_t pd_select(uint8_t pd_index)
{
uint32_t err_code = NRF_SUCCESS;
PD_ALLOFF(); /* Ensure clean state before switching */
switch (pd_index) {
case 0: PD0(); break; /* Photodetector 0 */
case 1: PD1(); break; /* Photodetector 1 */
case 2: MOD(); break; /* Modulation channel */
case 3: MOD2(); break; /* Secondary modulation */
default:
err_code = NRF_ERROR_NOT_FOUND;
break;
}
return err_code;
}
/*============================================================================*/
/* Timer Control Functions - Charles KWON */
/*============================================================================*/
/**
* @brief Start measurement send timer
* - Charles KWON
*
* Starts the single-shot timer for sequenced BLE data transmission.
*/
void mea_send_timer_start(void)
{
APP_ERROR_CHECK(app_timer_start(m_mea_send_loop_timer_id,
APP_TIMER_TICKS(MEA_SEND_LOOP_INTERVAL), NULL));
}
/**
* @brief Stop measurement send timer
* - Charles KWON
*/
void mea_send_timer_stop(void)
{
APP_ERROR_CHECK(app_timer_stop(m_mea_send_loop_timer_id));
}
/**
* @brief Initialize measurement send timer
* - Charles KWON
*
* Creates the single-shot timer used for sequenced BLE transmission.
* Must be called during system initialization.
*/
void mea_send_timer_init(void)
{
APP_ERROR_CHECK(app_timer_create(&m_mea_send_loop_timer_id,
APP_TIMER_MODE_SINGLE_SHOT, mea_send_loop));
}

Some files were not shown because too many files have changed in this diff Show More