Задача состоит в том, чтобы вычислить среднее значение по 64 выборкам, используя только 16-битные регистры и процессор без операций с плавающей запятой.

Одним из примеров такого процессора в наши дни является ULP (процессор со сверхнизким энергопотреблением) ESP32. Вы бы использовали его, например. считывать с АЦП (аналогово-цифровой преобразователь), усреднять выборки и, если достигнуто определенное число, пробуждать основной процессор. В основном применимо в сценариях с питанием от батареи, когда вы хотите спать большую часть времени и выполнять определенные задачи, только если условие было выполнено.

ULP мало что делает и имеет очень сокращенный набор инструкций. Среди прочего, он может считывать с АЦП, активировать основной процессор, складывать, вычитать, сдвигать. Немного, но достаточно для работы.

В сценарии, приведенном ниже, мы читаем с 13-битного АЦП, вычисляем среднее значение и, если значение превышает определенный порог, пробуждаем основной процессор.

Ниже приведена только часть расчета среднего значения с использованием 16-битных чисел, без операций с плавающей запятой и сравнения его со средним значением, рассчитанным обычным образом(a+b / numSamples).

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
uint16_t adc_avg_result;
uint16_t sum_hi;
uint16_t sum_lo;
uint16_t adc_oversampling_factor = 64; // must be 2^x
void main() {
    time_t t;
    // intializes random number generator
    srand((unsigned) time(&t));
    
    int tempArr[adc_oversampling_factor]; 
    
    unsigned tmp = 0, i = 0;
    sum_hi = 0;
    sum_lo = 0;
    
    // avg routine
    for (i = 0; i < adc_oversampling_factor; i++) {
        // get adc reading
        tmp = rand() & 0x1fff; // 13 bit number
        
        // save to temp array just for the purposes of calculating the control average
        tempArr[i] = tmp;
        
        // check if sum_lo + tmp will overflow
        if (tmp > (0xFFFF - sum_lo)) {
            // update the high 16 bits
            sum_hi += 1;
        }
        // update low 16 bits
        sum_lo += tmp;
    }
    
    // calculate get average from the proto 32 bit number;
    // delete the high and the low parts by 64 (2^6 = 64)
    adc_avg_result = (sum_hi << 10) + (sum_lo >> 6);
    printf("avg1 = %d\n", adc_avg_result);
    
    // calculate control average to ensure correctness
    int sum = 0;
    for(int i = 0; i<adc_oversampling_factor;i++){
        sum += tempArr[i];
    }
    int avg_normal = sum/adc_oversampling_factor;
    
    printf("\navg2 = %d\n", avg_normal);
    
    // if equal, we've made it
    assert(adc_avg_result == avg_normal);
}

Вы можете запустить приведенный выше код на https://www.onlinegdb.com/ и проверить результаты самостоятельно.

Вы заметите, что мы сохраняем результаты в двух отдельных переменных: одну для суммы и одну для суммы переполнений. Затем мы делим с помощью операций сдвига на log(64), поэтому мы сдвигаем нижнюю часть вправо на 6. Верхнюю часть или переполнение нам нужно сначала сдвинуть на 16, чтобы получить правильное битовое положение, а затем сдвинуть вправо на 6, что в сумме дает нам сдвиг влево на 10.

Это может хорошо работать для любого числа до 16 бит.

Первоначально я нашел этот код на github, написанный duff2013, вы можете найти его по адресу https://github.com/duff2013/ulptool/blob/master/src/ulp_examples/ulpcc/ulpcc_adc/adc.c.