Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

is there a limit of the sampling rate to get analog signal?

Hello, I am trying to capture the analog signal from two pins.

From one channel, I could get a signal with up to a 2kHz sampling rate.

However, the problem is, I couldn't see the clean signal if I added one more channel.
The sampling rate that I can make from 2 channels was about 50~100Hz.

If I make a higher sampling rate, I can see the ruined signal.
So from the 2-channel recording, when I set the sampling rate as 50Hz and see the data after recording, the frequency looks fine (not for the shape of the signal but frequency looks fit).

However, if I set the sampling rate as 500hz, 1khz, and 2khz, I should set the sampling rate as around 200Hz on Matlab when I recover the signal to see the frequency correctly.

What I am guessing here is since recording with a specific sampling rate is essentially dividing the clock with a certain ratio, this could result from a mismatch of the timing of the signals being recorded. This leads to the loss or corruption of some parts of the signals, causing poor signal integrity.
So issue with the poor signal integrity that I am observing might be due to the timing of the shifting analog input pins when recording signals from multiple channels.

Do you think this makes sense?

Is there any idea to fix my situation?

This is the main code that I used to record the signal with one analog channel.

int main(void)
{
	
    // Start clock for accurate frequencies
    NRF_CLOCK->TASKS_HFCLKSTART = 1; 
    while(NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) 
        ;

		//outpin_toggling_sample();
    bool erase_bonds = false;

    // Initialize.
    log_init();
    timers_init();
    //buttons_leds_init(&erase_bonds);
    ble_stack_init();
    gap_params_init();
    gatt_init();
    advertising_init();
    conn_params_init();
    peer_manager_init();
  	application_timers_start();
    services_init();
    sensor_simulator_init();
    // Start execution.
    NRF_LOG_INFO("Heart Rate Sensor example started.\r\n");
//		gpio_initialize();
    advertising_start(erase_bonds);
    csense_initialize();

		SEGGER_RTT_WriteString(0,"Hello World from SEGGER!\r\n");
    pwm_init();

    while (1)
    {
        {
            pwm_update_duty_cycle(50);
        }
					
        if (NRF_LOG_PROCESS() == false)
        {
            power_manage();
						NRF_LOG_FLUSH();
					__WFI();
        }
    }
}

and other codes that handle recording and Bluetooth communication or extra setting

#define DEVICE_NAME                      "name"          		               /**< Name of device. Will be included in the advertising data. */
#define MANUFACTURER_NAME                "NordicSemiconductor"                   /**< Manufacturer. Will be passed to Device Information Service. */
#define APP_ADV_INTERVAL                 160
#define APP_ADV_TIMEOUT_IN_SECONDS       180
#define CONN_CFG_TAG                     1                                       /**< A tag that refers to the BLE stack configuration we set with @ref sd_ble_cfg_set.
																																											Default tag is @ref BLE_CONN_CFG_TAG_DEFAULT. */
/* Time between RTC interrupts. */
#define ADC_TIMER_TICKS_TIMEOUT 				 APP_TIMER_TICKS(0.5) // 1=1ms  set the sampling rate
#define HRS_BUFFER_LEN									 150 // 75  100 154

#define HEART_RATE_MEAS_INTERVAL         APP_TIMER_TICKS(1500)  // 1=1ms  7.5   19.25     /**< Heart rate measurement interval (ticks). */
//#define OUTPUT_PIN_INTERVAL							 APP_TIMER_TICKS(500) // 1.35ms 370Hz  0.5 1kHz /**< P0.18 interval 370Hz signal (ticks). */

#define SENSOR_CONTACT_DETECTED_INTERVAL APP_TIMER_TICKS(10)                     /**< Sensor Contact Detected toggle interval (ticks). */

#define MIN_CONN_INTERVAL                MSEC_TO_UNITS(320, UNIT_1_25_MS)        /**< Minimum acceptable connection interval (0.4 seconds). */ 
#define MAX_CONN_INTERVAL                MSEC_TO_UNITS(520, UNIT_1_25_MS)        /**< Maximum acceptable connection interval (0.65 second). */ 
#define SLAVE_LATENCY                    0                                       /**< Slave latency. */
#define CONN_SUP_TIMEOUT                 MSEC_TO_UNITS(10000, UNIT_10_MS)         /**< Connection supervisory timeout (10 seconds). */ \
#define FIRST_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(1000)                   /**< Time from initiating event (connect or start of notification) 
																																											to first time sd_ble_gap_conn_param_update is called (5 seconds). */ 
#define MAX_CONN_PARAMS_UPDATE_COUNT     3                                       /**< Number of attempts before giving up the connectio0n parameter negotiation. */ 
#define SEC_PARAM_BOND                   1                                       /**< Perform bonding. */
#define SEC_PARAM_MITM                   0                                       /**< Man In The Middle protection not required. */
#define SEC_PARAM_LESC                   0                                       /**< LE Secure Connections not enabled. */
#define SEC_PARAM_KEYPRESS               0                                       /**< Keypress notifications not enabled. */
#define SEC_PARAM_IO_CAPABILITIES        BLE_GAP_IO_CAPS_NONE                    /**< No I/O capabilities. */
#define SEC_PARAM_OOB                    0                                       /**< Out Of Band data not available. */
#define SEC_PARAM_MIN_KEY_SIZE           7                                       /**< Minimum encryption key size. */
#define SEC_PARAM_MAX_KEY_SIZE           16                                      /**< Maximum encryption key size. */

#define DEAD_BEEF                        0xDEADBEEF                                 /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */

#define APP_FEATURE_NOT_SUPPORTED        BLE_GATT_STATUS_ATTERR_APP_BEGIN + 2       /**< Reply when unsupported features are requested. */

/* Analog inputs. */
#define INPUT_CNT								1
static int AIN_INX = 0;

//#define AIN_1                   1 // P0.03/AIN 1
#define AIN_3                     2 // P0.04/AIN 2 for Input value
/* For output pin 28 : LED toggling, find 'GPIO_OUTPUT_PIN_NUMBER'  */
//#define GPIO_OUTPUT_PIN_NUMBER   29 // P0.21 for stimulate signal
//#define INTERNAL_SIGNAL_PIN			 18 // P0.18 for 370Hz pulse signal
//#define INTERNAL_GND_PIN				 22	// P0.11 for 370Hz pulse signal
#define GPIO_GND_PIN						 12 // P0.12 - NOT USE 
//#define USE_INTERNAL_SIGNAL 			0 // 1: use internal signal , 0: NOT use internal signal

/* Masks of analog channels to be used by library. */
#define PAD_1_MASK              (1UL << AIN_3)
//#define PAD_2_MASK              (1UL << AIN_7)
 
#define PAD_ID_0                0
#define PAD_ID_1                1

#define UART_TX_BUF_SIZE 512                              /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 1                                /**< UART RX buffer size. */

//---------------------------------------------------

static uint16_t  m_conn_handle = BLE_CONN_HANDLE_INVALID; /**< Handle of the current connection. */
static ble_bas_t m_bas;                                   /**< Structure used to identify the battery service. */
static ble_hrs_t m_hrs;                                   /**< Structure used to identify the heart rate service. */
//static bool      m_rr_interval_enabled = true;            /**< Flag for enabling and disabling the registration of new RR interval measurements (the purpose of disabling this is just to test sending HRM without RR interval data. */

static nrf_ble_gatt_t m_gatt;                             /**< Structure for gatt module*/

static sensorsim_cfg_t   m_heart_rate_sim_cfg;            /**< Heart Rate sensor simulator configuration. */
static sensorsim_state_t m_heart_rate_sim_state;          /**< Heart Rate sensor simulator state. */

volatile uint32_t threshold_value_pad1 = 3000;
volatile uint32_t threshold_value_pad2 = 900;

/* Variables needed to properly configure threshold. */
volatile uint32_t max_value[2];
volatile uint32_t min_value[2];
#define read_val_max  1810 // 3598 // -12 ~ 1798 = 0 ~ 2.412V // 3598 (3600-2) is all values from nrf chip 
																	 //e.g) 97mV = 75, 300mV = 300*1810/2412 = 225, 500mV = 500*1810/2412 = 375
float iDisp_ctrl = 256 / read_val_max;
#define VOLTAGE_OFFSET 12 // 1800
volatile uint32_t current_val = 0; 
volatile uint32_t previous_val = 0;
																	 
APP_TIMER_DEF(m_csense_timer_id);

#define USE_MUX 0
int MUX_Channel = 5;
int cur_MUX = 1;


/// handling bluetooth

static void heart_rate_meas_timeout_handler(void * p_context)
{
		ret_code_t      err_code;
    UNUSED_PARAMETER(p_context);

#if USE_MUX // select channel
		
		switch(5)
		{
			case 1:
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, Channel1_buf);
				break;
			case 2:
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, Channel2_buf);
				break;
			case 3:
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, Channel3_buf);
				break;
			case 4:
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, Channel4_buf);
				break;
			case 5:
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, InputVal_buf);
				break;
		}

#else
		//err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, InputVal_buf);
		//array_initialize(InputVal_buf);
		//cur_buf[AIN_INX] = 0;
		if (AIN_INX == 0){
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, InputVal2_buf);
				array_initialize(InputVal2_buf);
				buf1 = 0;
		}
		else{
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, InputVal1_buf);
				array_initialize(InputVal1_buf);
				buf0 = 0;
		}
		
		//}
//		err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, InputVal_buf);
#endif
		//NRF_LOG_INFO("Input_Buf: %d\r\n",InputVal_buf[20]);

	
		if ((err_code != NRF_SUCCESS) &&
				(err_code != NRF_ERROR_INVALID_STATE) &&
				(err_code != NRF_ERROR_RESOURCES) &&
				(err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
			 )
		{
			APP_ERROR_HANDLER(err_code);
		}
		else;

				
#if USE_MUX
		array_initialize(Channel1_buf);
		array_initialize(Channel2_buf);
		array_initialize(Channel3_buf);
		array_initialize(Channel4_buf);
		array_initialize(InputVal_buf);
		cur_buf[AIN_INX] = 0;
		cur_MUX = 1;
#else

//		array_initialize(InputVal_buf);
//		cur_buf[AIN_INX] = 0;
		tmp = 0;
		btmpChk = true;
		//LOG_INTERNAL(0,"","InputVal_buf #: %d \n", chk);//saadcPinVal);

#endif

}

//// recording

void csense_handler(nrf_drv_csense_evt_t * p_event_struct)
{
    switch (p_event_struct->analog_channel)
    {
        case AIN_3:
				input_Val = (int32_t)p_event_struct->read_value + VOLTAGE_OFFSET;
				
				if(input_Val <= 0){
					input_Val = 0;
				}
				else if(input_Val >= read_val_max){
					input_Val = read_val_max;
				}
				default:
				break;
    }
		
#if USE_MUX
		if((cur_buf[AIN_INX] < HRS_BUFFER_LEN) && (cur_buf[AIN_INX] >= 0)){
			if(cur_MUX == 1){
				Channel1_buf[cur_buf[AIN_INX]] = (uint16_t)input_Val/iDisp_ctrl + VOLTAGE_OFFSET;
				InputVal_buf[cur_buf[AIN_INX]*4+cur_MUX-1] = Channel1_buf[cur_buf[AIN_INX]];
				cur_MUX = 2;
			}
			else if(cur_MUX == 2){
				Channel2_buf[cur_buf[AIN_INX]] = (uint16_t)input_Val/iDisp_ctrl + VOLTAGE_OFFSET;
				InputVal_buf[cur_buf[AIN_INX]*4+cur_MUX-1] = Channel2_buf[cur_buf[AIN_INX]];
				cur_MUX = 3;
			}
			else if(cur_MUX == 3){
				Channel3_buf[cur_buf[AIN_INX]] = (uint16_t)input_Val/iDisp_ctrl + VOLTAGE_OFFSET;
				InputVal_buf[cur_buf[AIN_INX]*4+cur_MUX-1] = Channel3_buf[cur_buf[AIN_INX]];
				cur_MUX = 4;
			}
			else if(cur_MUX == 4){
				Channel4_buf[cur_buf[AIN_INX]] = (uint16_t)input_Val/iDisp_ctrl + VOLTAGE_OFFSET;
				InputVal_buf[cur_buf[AIN_INX]*4+cur_MUX-1] = Channel4_buf[cur_buf[AIN_INX]];
				cur_MUX = 1;
				cur_buf[AIN_INX]++;
			}
			else;
			//InputVal_buf[cur_buf[AIN_INX]] = (uint16_t)input_Val/iDisp_ctrl;
			//cur_buf[AIN_INX]++;
			
		}
		else{
/*			cur_buf[AIN_INX] = 0;
			array_initialize(Channel1_buf);
			array_initialize(Channel2_buf);
			array_initialize(Channel3_buf);
			array_initialize(Channel4_buf);*/
		}
#else
//  	if((cur_buf[AIN_INX] < HRS_BUFFER_LEN) && (cur_buf[AIN_INX] >= 0)){
			if (AIN_INX == 0){
				if ((buf0 < HRS_BUFFER_LEN) && (buf0 >= 0)){
					InputVal1_buf[buf0] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal1_buf[buf0] >= 255) InputVal1_buf[buf0] = 255;
					buf0++;
				}
				else{
					InputVal2_buf[buf1] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal2_buf[buf1] >= 255) InputVal2_buf[buf1] = 255;
					buf1++;
					buf0 = 0;
					AIN_INX = 1;
				}
			}
			else if (AIN_INX == 1){
				if ((buf1 < HRS_BUFFER_LEN) && (buf1 >= 0)){
					InputVal2_buf[buf1] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal2_buf[buf1] >= 255) InputVal2_buf[buf1] = 255;
					buf1++;
				}
				else{
					InputVal1_buf[buf0] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal1_buf[buf0] >= 255) InputVal1_buf[buf0] = 255;
					buf0++;
					buf1 = 0;
					AIN_INX = 0;
				}
			}
			else;
#endif
}


//// timing init
void timerInit(uint16_t period_us) // function for timer init
{	
	NRF_TIMER4->TASKS_STOP	 = 1;			// stop the timer
	NRF_TIMER4->TASKS_CLEAR  = 1;			// clear
	NRF_TIMER4->MODE		 = TIMER_MODE_MODE_Timer;	// set as timer mode
	NRF_TIMER4->BITMODE		 = TIMER_BITMODE_BITMODE_32Bit; // timer counter as 32bit
	NRF_TIMER4->PRESCALER	 = 1; //prescaler as 10
	NRF_TIMER4->CC[0]		 = (float)8000000* ((float)period_us / 1000000.0f);
	NRF_TIMER4->INTENSET	 = 65536; // timer intrupt active
	NRF_TIMER4->SHORTS		 = 0x01; // short cut register activate
	NVIC_EnableIRQ(TIMER4_IRQn); // timer interupt function activate
	NRF_TIMER4->TASKS_START  = 1;
}

and the code that I changed to make 2 channel recording

static int AIN_INX = 0;

#define AIN_1                   0 // P0.03/AIN 1
#define AIN_3                   2 // P0.04/AIN 2 for Input value
#define PAD_1_MASK              (1UL << AIN_1)
#define PAD_2_MASK              (1UL << AIN_3)
 
#define PAD_ID_0                0
#define PAD_ID_1                1

#define UART_TX_BUF_SIZE 512                              /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 1                                /**< UART RX buffer size. */

volatile uint32_t threshold_value_pad1 = 3000;
volatile uint32_t threshold_value_pad2 = 3000;


#define ADC_TIMER_TICKS_TIMEOUT	APP_TIMER_TICKS(1)      // decide sampling rate
#define HRS_BUFFER_LEN	 150

uint16_t InputVal1_buf[HRS_BUFFER_LEN];
uint16_t InputVal2_buf[HRS_BUFFER_LEN];

static void heart_rate_meas_timeout_handler(void * p_context)
{
	ret_code_t      err_code;
    UNUSED_PARAMETER(p_context);
		if (AIN_INX == 0){
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, InputVal2_buf);
				array_initialize(InputVal2_buf);
				buf1 = 0;
		}
		else{
				err_code = ble_hrs_heart_rate_measurement_send(&m_hrs, InputVal1_buf);
				array_initialize(InputVal1_buf);
				buf0 = 0;
		}

		if ((err_code != NRF_SUCCESS) &&
				(err_code != NRF_ERROR_INVALID_STATE) &&
				(err_code != NRF_ERROR_RESOURCES) &&
				(err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
			 )
		{
			APP_ERROR_HANDLER(err_code);
		}
		else;


		tmp = 0;
		btmpChk = true;
}

void csense_handler(nrf_drv_csense_evt_t * p_event_struct)
{   
switch (p_event_struct->analog_channel)
    {
				case AIN_1:
				if(buf_chk == false){
					input_Val = (int32_t)p_event_struct->read_value + VOLTAGE_OFFSET;
					
					if(input_Val <= 0){
						input_Val = 0;
					}
					else if(input_Val >= read_val_max){
						input_Val = read_val_max;
					}
					buf_chk = true;
				}
				
				case AIN_3:
				if(buf_chk == true){
				input_Val = (int32_t)p_event_struct->read_value + VOLTAGE_OFFSET;
				
				if(input_Val <= 0){
					input_Val = 0;
				}
				else if(input_Val >= read_val_max){
					input_Val = read_val_max;
				}
				buf_chk = true;
				}
				default:
				break;
    }
			if (AIN_INX == 0){
				if ((buf0 < HRS_BUFFER_LEN) && (buf0 >= 0)){
					InputVal1_buf[buf0] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal1_buf[buf0] >= 255) InputVal1_buf[buf0] = 255;
					buf0++;
				}
				else{
					InputVal2_buf[buf1] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal2_buf[buf1] >= 255) InputVal2_buf[buf1] = 255;
					buf1++;
					buf0 = 0;
					AIN_INX = 1;
				}
			}
			else if (AIN_INX == 1){
				if ((buf1 < HRS_BUFFER_LEN) && (buf1 >= 0)){
					InputVal2_buf[buf1] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal2_buf[buf1] >= 255) InputVal2_buf[buf1] = 255;
					buf1++;
				}
				else{
					InputVal1_buf[buf0] = (int16_t)input_Val*255/read_val_max ;
					if(InputVal1_buf[buf0] >= 255) InputVal1_buf[buf0] = 255;
					buf0++;
					buf1 = 0;
					AIN_INX = 0;
				}
			}
			else;

}

void csense_initialize(void)
{
    ret_code_t err_code;

    nrf_drv_csense_config_t csense_config = { 0 };

#if USE_COMP == 0
    csense_config.output_pin = OUTPUT_PIN;
#endif
    NRF_LOG_INFO("csense_initialize.\r\n");

    err_code = nrf_drv_csense_init(&csense_config, csense_handler);
    APP_ERROR_CHECK(err_code);

    nrf_drv_csense_channels_enable(PAD_1_MASK | PAD_2_MASK);
}

int main(void)
{
	
    NRF_CLOCK->TASKS_HFCLKSTART = 1; 
    while(NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) 
        ;

    bool erase_bonds = false;

    log_init();
    timers_init();
    ble_stack_init();
    gap_params_init();
    gatt_init();
    advertising_init();
    conn_params_init();
    peer_manager_init();
  	application_timers_start();
    services_init();
    sensor_simulator_init();

    NRF_LOG_INFO("Heart Rate Sensor example started.\r\n");

    advertising_start(erase_bonds);
    csense_initialize();

	SEGGER_RTT_WriteString(0,"Hello World from SEGGER!\r\n");

    pwm_init();

    // Enter main loop.
    while (1)
    {

        {
            pwm_update_duty_cycle(50);
        }
			
			
        if (NRF_LOG_PROCESS() == false)
        {
            power_manage();
						NRF_LOG_FLUSH();
					__WFI();
        }
    }
}

Do you think there is any line that has to be fixed or a suggestion so that I can make 2 channel recording with a 1kHz sampling rate?

The image of the channel recorded signal is below (they suppose to be s perfect sine wave)

Please let me know if there is something else I can provide to fix this...

Or there is any codes or samples that I can make as references.

Related