NRF25 using Butterworth bandpass filter for ECG by SAADC

HI everyone, i have a problem when implementing Butterworth bandpass filter in SAADC module to cancel out the noise of our ECG module.

I used " nrf_saadc_value_t m_buffer_pool[2][SAMPLE_BUFFER_LEN] " and " nrfx_saadc_buffer_convert(m_buffer_pool[0], SAMPLE_BUFFER_LEN) "

As far as i had known, the VOLTAGE signal of the analog input will be transfered to the memory of MCU and converted to an appropriate level of BITS, then these value will be stored in registers in terms of BITS. I used a software filter based on the code on this Github link: github.com/.../filter-c

My question is: How can the Butterworth filter cancel out the noises and only select the band of desirable frequency for my project. Does it work only on frequency generated by the electrical voltage Or it can also filter the BITS value in the memory?

Thanks in advance.

Parents
  • Hello,

    according to the example.c on the github page that you linked it should work like this:

    1. you create a filter with the desired parameters like this:

    BWBandPass* filter = create_bw_band_pass_filter(4, 250, 2, 45);

    (in this example it would be a 4th order band pass filter with a sampling rate of 250 Hz with a frequency band of 2 ... 45 Hz)

    2. assuming you have one buffer for the unfiltered data from the ADC (let's call that buffer "unfiltered") and one buffer to store the filtered data in (called "filtered"), and both are SAMPLE_BUFFER_LEN long, you can apply the filter to the data like this:

    for(int i = 0; i < SAMPLE_BUFFER_LEN; i++){

       filtered[i] = bw_band_pass(filter, unfiltered[i]);

    }

    3. when you are done with filtering you can dispose of the filter with:

    free_bw_band_pass(filter);

    4. Now the "filtered" array will contain the resulting signal, without noise.

    I tried the filter above with a synthetic test signal that contains a 25Hz sine wave with some 100 Hz noise and a DC offset. The result looks like this: (upper half is the original signal with distortions, lower half is the output of the filter)

    As you can see, the noise and the DC offset is gone in the filtered signal, so the filter seems to be doing what it's supposed to do.

    Best regards

    Frank

  • one buffer to store the filtered data

    can you please explain more about this one? Do i have to create another timer handler to fill the filtered data into?

  • My project is to read data from ECG sensor, i used butterworth filter to cancel the high and low noises frequency, i mean not the bandpass but the low pass first to cancel out the 0.5Hz noise, then i the high pass to cancel out the 45Hz noises.

    i have some troubles when doing my code part

    this is my code:

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    #define SAMPLE_BUFFER_LEN 5 


    static nrf_saadc_value_t m_buffer_pool[2][SAMPLE_BUFFER_LEN];

    static const nrf_drv_timer_t m_timer = NRF_DRV_TIMER_INSTANCE(1);

    static nrf_ppi_channel_t m_ppi_channel;

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    void timer_handler(nrf_timer_event_t event_type, void * p_context)
    { }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    void timer_with_ppi_init(void)
    {
    ret_code_t err_code;

    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;

    err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
    APP_ERROR_CHECK(err_code);

    uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 2);

    nrf_drv_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);

    nrf_drv_timer_enable(&m_timer);

    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);

    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, timer_compare_event_addr, saadc_sample_task_addr);
    APP_ERROR_CHECK(err_code);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    void saadc_callback_handler(nrf_drv_saadc_evt_t const * p_event)
    {
    float val;

    if(p_event -> type == NRFX_SAADC_EVT_DONE)
    {
    ret_code_t err_code;

    err_code = nrfx_saadc_buffer_convert(p_event -> data.done.p_buffer, SAMPLE_BUFFER_LEN);
    APP_ERROR_CHECK(err_code);

    for(int i = 0; i<SAMPLE_BUFFER_LEN; i++)
    {
     NRF_LOG_RAW_INFO("%d\n\r", p_event -> data.done.p_buffer[i]);

    }

    }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    void saadc_init(void)
    {
    ret_code_t err_code;

    nrf_saadc_channel_config_t channel_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);

    err_code = nrf_drv_saadc_init(NULL, saadc_callback_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_saadc_channel_init(0, &channel_config);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_saadc_buffer_convert(m_buffer_pool[0], SAMPLE_BUFFER_LEN);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_saadc_buffer_convert(m_buffer_pool[1], SAMPLE_BUFFER_LEN);
    APP_ERROR_CHECK(err_code);
    }

  • Hi,

    looking at your code i'd say you can do the filtering inside the callback function named "saadc_callback_handler". After you started the AD conversion with "nrfx_saadc_buffer_convert(m_buffer_pool[0], SAMPLE_BUFFER_LEN);", this callback function will be called when your buffer has been filled. You can do the filtering inside of the for-loop that you already have in there. The "p_event" parameter that is passed into the callback will contain a pointer to your data, so you can use "p_event -> data.done.p_buffer[i]" to access the i-th element, same as in the "NRF_LOG_RAW_INFO(...)" command.

    So similar to the example above you could just change the for-loop like this:

    for(int i = 0; i < SAMPLE_BUFFER_LEN; i++){

       filtered[i] = bw_band_pass(filter, p_event -> data.done.p_buffer[i]);

    }

    Create the filter (like in the example) before the for-loop and destroy it after the loop.

    The only remaining question is where to store the filtered data. Easiest way to try it would be to declare another buffer for the filtered data globally, like you did with the "nrf_saadc_value_t m_buffer_pool".

    The filter outputs floating point numbers, so you should declare a floating point array for the result, like this:

    float filtered[SAMPLE_BUFFER_LEN];

    Best regards

    Frank

  • Thanks so much! Now i can compile the code successfully without any errors

    but the termial output the value  " 0 " continuously no matter how i change the input data by the potentiometer

    I created the global  float filtered[SAMPLE_BUFFER_LEN];  as you suggested, then this is my adc handler:

    void saadc_callback_handler(nrf_drv_saadc_evt_t const * p_event)
    {
        float val;

        if(p_event -> type == NRFX_SAADC_EVT_DONE)
        {
            ret_code_t err_code;

            err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLE_BUFFER_LEN);
            APP_ERROR_CHECK(err_code);

            BWLowPass* filter = create_bw_low_pass_filter(10, 500, 30);

            for(int i = 0; i < SAMPLE_BUFFER_LEN; i++)
            {
                 filtered[i] = bw_low_pass(filter, p_event -> data.done.p_buffer[i]);

            }

            NRF_LOG_RAW_INFO("%d\n\r", filtered);

            free_bw_low_pass(filter);
        }
    }

    Did i do it ritght? or i have to create another for loop for NRF_LOG_RAW_INFO() to output the values?

  • Hi,

    the filtering looks correct.

    I think the problem here is the NRF_LOG_RAW_INFO command. I think it can't be used to log floating point values. The documentation says you should log floating point values like this:

    NRF_LOG_INFO("My float number" NRF_LOG_FLOAT_MARKER "\r\n", NRF_LOG_FLOAT(f)));

    where you can replace "My float number" with your own text, and "f" with the floating point value to print.

    See here: link

    Also you need to use "filtered[i]", otherwise the log function will try to print the address of the array, not the contents of one array element.

    Finally, if you move the log statement inside the for-loop it will print all the values, not only the last one.

    So try this:

            for(int i = 0; i < SAMPLE_BUFFER_LEN; i++)
            {
                 filtered[i] = bw_low_pass(filter, p_event -> data.done.p_buffer[i]);

                 NRF_LOG_INFO("My float number" NRF_LOG_FLOAT_MARKER "\r\n", 

                        NRF_LOG_FLOAT(filtered[i]));

            }

    Hope this works now, i don't use this NRF_LOG_INFO stuff normally.

    Best regards

    Frank

  • sorry, it didn't work

    i think the problem is LOG module, i used this module for a long time and get familiar with this

    How can you output the data through UART port to the laptop screen? My segger embedded had trouble with PRINTF, i dont know how to transfer the data through UART without LOG module

Reply Children
Related