Задача состоит в том, чтобы вычислить среднее значение по 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.