0
\$\begingroup\$

I measure the current via an I2C current sensor by using this STM32F302 Nucleo board. And I display internal temperature and the averaged current value in an OLED display.

enter image description here

But I do everything in while loop like polling as follows:

  while (1)
  {

        HAL_Delay(100);
        get_time();

        uint16_t I2C_value = averaging_data();
        current_val = (I2C_value/4096.0 - 0.5) *50*1000;

        if(current_val>threshold){
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
        }
        else{
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
        }

        //Rest of the code for OLED display and temperature readings

}

static uint16_t averaging_data( void )
{
    uint8_t avg_num = 250;
    uint32_t sum_data = 0;
    uint16_t new_data = 0;
    uint8_t cnt = 0;

    for ( cnt = 0; cnt < avg_num; cnt++ )
    {
        new_data = amrcurrent_read_value();
        sum_data = sum_data + new_data;
        HAL_Delay(1);
    }
    new_data = sum_data / avg_num;

    return new_data;
}

uint16_t amrcurrent_read_value ( void )
{
    uint8_t data_buf[ 2 ];
    uint16_t temp_data;

    HAL_I2C_Master_Receive(&hi2c1, SLAVE_ADDR << 1, data_buf, 2, HAL_MAX_DELAY);

    temp_data = data_buf[ 0 ];
    temp_data = temp_data << 8;
    temp_data = temp_data | data_buf[ 1 ];


    return temp_data;
}

As seen above, I average 250 samples for the current. So I can update an LED less than 500ms after current is above a threshold value. So I want LED to respond the current threshold change as soon as possible.

My problem is that, adding OLED functions and ADC measurement functions inside the same while loop makes the LED to respond slower to the current's value change.

I thought about an interrupt but in this case I couldn't figure out how to use it. What can be a remedy which doesn't require an extra circuitry? Seems not possible but I wanted to ask here anyway.

\$\endgroup\$
2
  • \$\begingroup\$ You can't respond to the change until you've measured it. If you wanted a faster response that was less averaged but faster you could insert a comparison into the ADC measurement (for example, average 25 measurements and compare, set LED state, repeat 10x for display value). \$\endgroup\$ Commented Oct 3, 2022 at 14:14
  • \$\begingroup\$ Recent I2C devices have a fast mode which has a speed of 1 Mbit/s or 3.4 Mbit/s Did you try that? i2c-bus.org/fastmode \$\endgroup\$
    – Amit M
    Commented Oct 3, 2022 at 16:48

2 Answers 2

2
\$\begingroup\$

I'd set up a timer interrupt that fires every milliseconds, and use that to read the I2C sensor and sum up each value to be used as an average.

Something like this (not including setup of interrupt etc)

volatile uint32_t sensorValue;
volatile uint32_t sensorCount;
 
 void main() {
    while (1)
    {

        HAL_Delay(100);
        get_time();

        uint16_t I2C_value = averaging_data();
        current_val = (I2C_value/4096.0 - 0.5) *50*1000;

        if(current_val>threshold){
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
        }
        else{
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
        }

        //Rest of the code for OLED display and temperature readings

    }
 }
 
static uint16_t averaging_data( void )
{
    uint32_t newSensorValue;
    uint32_t newSensorCount;
    
    __disble_irq();   // Disable interrupt while reading/writing shared data to make sure no corrupt data in case interrupt happens while reading/writing this data
    newSensorValue = sensorValue;
    newSensorCount = sensorCount;
    sensorValue = 0;
    sensorCount = 0;
    __enable_irq();
    
    if(newSensorCount > 0)
        newSensorValue /= newSensorCount;

    return (uint16_t)newSensorValue;
}


void TimerISR() {
    
    sensorValue += amrcurrent_read_value();
    sensorCount++;
    
}

In case you want to make sure you have 250 average values each time, your main loop could wait for sensorCount to become 250 before updating the display. Or you could implement a rolling average instead, still using the timer interrupt.

Performance could be improved further if needed, such as not staying in the interrupt while waiting for I2C communication etc. Also by discarding the HAL drivers that are notoriously inefficient.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the answer. To make my question clear, imagine inside the while loop there's is 5000ms delay. Why dont you put he LED logic inside the interrupt callback? I want the LED to act fast as possible when the current exceeds a threshold. Do you really need to call I2C current functions inside while(1) anymore? I hope I made my question more clear. \$\endgroup\$
    – user1999
    Commented Oct 3, 2022 at 15:47
  • \$\begingroup\$ @user1999 Sure you could. Just add some more code into the timer interrupt, checking if sensorCount is large enough (ie. enough samples have been made), and if so do the same calculations and checks as you would in the main loop. \$\endgroup\$
    – Klas-Kenny
    Commented Oct 3, 2022 at 16:47
1
\$\begingroup\$

Your averaging_data() algorithm consumes 250 samples for every one "averaged" value that it produces. Consider using a moving average algorithm or an IIR Filter algorithm instead. Either one will give you one "average" value out for every current sample.

The simplest IIR filter looks like this:

double IIR_filt(double new_sample) {
    static double value = 0.0;
    constexpr double ALPHA = ... 0.0 < ALPHA < 1.0 ...;

    value = new_sample*ALPHA + value * (1.0 - ALPHA);
    return value;
}

You'll probably have to experiment to find a satisfactory value for ALPHA. Smaller values (closer to 0.0) will give you a smoother response. Larger values (closer to 1.0) will give you faster response.

A moving average is a bit more complicated, but it will always give you the true arithmetic mean of the last N samples (e.g., the last 250 samples) if that's really what you need:

double moving_avg(double new_sample) {
    constexpr int WINDOW_SIZE = 250;
    static double window[WINDOW_SIZE];
    static int idx = 0;
    static double sum = 0.0;
    
    double discarded_sample = window[idx];
    window[idx] = new_sample;
    idx = (idx+1) % WINDOW_SIZE;

    sum -= discarded_sample;  // Beware: Error will slowly accumulate
    sum += new_sample;        //   in these two steps (see below.)

    return sum/WINDOW_SIZE;
}

You might also consider writing code to re-calculate the sum after every I-dont-know-how-many thousand or million calls because floating point arithmetic is not perfect. You don't want to allow error to continue building up forever.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.