разбор кода

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0
добрый день товарищи программисты. Помогите пожалуйста разобрать кусок кода. Не совсем понимаю что он делает.
есть 3 массива типа char, в них закидывается значение с 16 битного ацп. А что именно делает и для чего эта функция, по пунктам,не могу разобраться.
C++:
#define BUFFER_SIZE 17                  // размер буфера передачи
#define MAXPACKETS 6                    // число пакетов

char OutBuffer[BUFFER_SIZE] = {0};       // выходной буфер     
unsigned long Signal[2] = {0};           // 
unsigned long Sig1[2] = {0};             //
unsigned long Sig2[2] = {0};             //
char PacketCount = 0;                    // счетчик пакетов 
unsigned long ReadData = {0};            // переменная 4 байта на отправку

unsigned int ReadAdcDataReg(void);


void INT1_vect_interrupt(void)
{
     char Channel;
    
    if (PacketCount < (MAXPACKETS >> 1)) Channel = 0; else Channel = 1;
    
    WriteToAdcRegister(0x38+Channel,0,0,0,1); //+Channel
    ReadData = ((unsigned long)(ReadAdcDataReg()) << 8); //(Work need)
    
    PacketCount++;
   
    if (PacketCount == (MAXPACKETS >> 1)) //5
    {
       Sig1[0] = (3*Sig1[0] + ReadData) >> 2;
       Sig1[1] = (3*Sig1[1] + Sig1[0]) >> 2;
       Signal[Channel] = (3*Signal[Channel] + Sig1[1]) >> 2;
    }
    
    
    
    if (PacketCount == MAXPACKETS) //10
    {
       Sig2[0] = (3*Sig2[0] + ReadData) >> 2;
       Sig2[1] = (3*Sig2[1] + Sig2[0]) >> 2;
       Signal[Channel] = (3*Signal[Channel] + Sig2[1]) >> 2;
       PacketCount = 0;
       SendPacketByRs();       
    }
}
вот что возвращает функция чтения ацп
C++:
unsigned int ReadAdcDataReg(void)
{
    char Index;
    unsigned int Out = 0;

    for(Index=0;Index<16;Index++)
    {
        Out <<= 1;
        
        CLEAR_SCLK;
        SET_SCLK;
        
           Out |= READ_DATA_IO;
    }
    
    return Out;
}
просто как я понимаю
C++:
Sig1[0] = (3*Sig1[0] + ReadData) >> 2;
это фильтр бегущего среднего для первого канала, но зачем их используется 3 друг за другом,не совсем понятно.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,799
579
44
Sig1[0] = (3*Sig1[0] + ReadData) >> 2;
Проще представить в виде:
X = ( 3*X + A) /4;
Тогда ясно станет что оно делает. Да, это вычисление "бегущего среднего", просто деление заменено на битовый сдвиг, что сильно облегчает вычисления.

но зачем их используется 3 друг за другом,не совсем понятно
Скорее всего для того, чтобы не получались огромные цифры при внутренних вычислениях(что может привести к неверным результатам), если бегущее среднее вычислять с большим коэффициентом.
 
Изменено:

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0
@Геннадий П,
А для чего может быть сделано 3 подряд таких устеднения?
Дело в том что код изначально писался под 24 битный ацп. Затем поменяли ацп на 16 бит. Вот мне и стало непонятно, зачем 2 байта с ацп смешают на 1 байт в лево. И начинают усреднять значения. Либо таким образом как-то математически раскладывают 16 бит на 24.


@Геннадий П,
Может есть какая формула или функция , которая 16 бит может разложить на 24 с каким-то усреднением?
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,799
579
44
@Akademik33, Ну, можно использовать например int32 (не помню, есть ли в ардуине такое) но нужно смотреть на производительность, может оказаться что три подряд вычислений с простыми числами быстрее чем одно с более сложным.

Что то типа такого:
Sig1[0] = ( 63 * (int32)Sig1[0] + ReadData) >> 6;

Хотя посмотрел, в массивах используется "unsigned long". Тогда не понимаю зачем такой паровоз.
 
Изменено:

rkit

★★★✩✩✩✩
5 Фев 2021
471
114
просто как я понимаю
C++:
Sig1[0] = (3*Sig1[0] + ReadData) >> 2;
это фильтр бегущего среднего для первого канала, но зачем их используется 3 друг за другом,не совсем понятно.
Это фильтр, но даже не напоминающий скользящее среднее. Сделан он так для того, чтобы получить требуемую характеристику фильтра.
 

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0

@rkit,
А можно поподробнее, какую именно характеристику фильтра?


@Геннадий П,
Вот и я не могу понять, какая тут идея была. А по поводу того как разложить 16 бит на 24 , что можете посоветовать? Я думал это должно быть что-то наподобие функции "map".
 

poty

★★★★★★✩
19 Фев 2020
2,957
886
Код несколько прямолинейно написан, но ничего такого я в нём не вижу. АЦП имеет 2 канала по 24 бит (3 байта) каждый. ReadAdcDataReg принимает каждый байт в виде двухбайтовой посылки (почему - надо смотреть в datasheet устройства, предположу, что есть контрольная сумма для каждого байта). Каждые три байта одного канала собираются в 24 битное число, размещаемое в unsigned long (32 битном хранилище) с помощью
ReadData = ((unsigned long)(ReadAdcDataReg()) << 8);

Код
Sig1[0] = (3*Sig1[0] + ReadData) >> 2;
рассчитывает среднее в окне 4-х отсчётов через кумулятивную сумму.
Код
Sig2[1] = (3*Sig2[1] + Sig2[0]) >> 2;
рассчитывает среднее в окне 16 отсчётов.
Код
Signal[Channel] = (3*Signal[Channel] + Sig2[1]) >> 2;
в окне 64 отсчётов.
Очевидно это сделано для того, чтобы устранить возможность переполнения 32 бит (хотя по вычислениям получается всего 24 бита + 6 бит, но я не знаю, сколько реально бит получается от АЦП).
 
  • Лойс +1
Реакции: Akademik33

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0

@poty,
Спасибо большое за такое развернутый ответ. Но все бы ничего, если бы ацп был 24 битный. Дело в то что сейчас начали ставить ацп 16 битный. А код почему-то оставили что и на 24 бита, изменили только чтение с ацп на 2 байта. Суть в том, что с устройства, на котором собрано это ацп, передает данные на программу в компьютер. Передаете его в виде пакета из 17 байт. 3 байта из этого пакета несут в себе значение с ацп. И вся загвоздка в том, что с тем куском вычислений, которое я скинул в первом сообщении, программа каким-то образом работает, и заполняет все 3 байта в пакете , а без этого усредняюшего фильтра она отправляет только 2 байта. И программа на компе сходит с ума от таких знаний.
Собственно мне и надо как-то выяснить, каким путем в этом усредняющем фильтре, так пропорционально раскладываются 2 байта на 3.
 

poty

★★★★★★✩
19 Фев 2020
2,957
886
Ничего никуда не раскладывается. "Фильтру" всё равно, приходит на него 16 бит или 24, он работает одинаково. Имеет значение лишь то, как из входного потока формируется ReadData.
Передающая данные на компьютер функция вообще "не знает" сколько байт принято и должна всегда отправлять одинаковое количество байт.
Вообще, чтобы предметно говорить, нужна информация о формате передачи с АЦП и что происходит в SendPacketByRs().
 

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0
@poty,без проблем. Вот кусок кода
C++:
void SendPacketByRs(void)
{
    FillBuffer(1,0);
    BytesForSend = 16;
    Serial.write = OutBuffer[0];
}

void UART_TX_interrupt(void) 
{  
   if (BytesForSend == 0) return; //Message Complete

   Serial.write = OutBuffer[BUFFER_SIZE-BytesForSend];
   BytesForSend--;  
}

void FillBuffer(char NoByte,char Channel)
{
    ReadData = Signal[Channel];
     OutBuffer[NoByte+3] =     (((ReadData & 0x80)==0)?0:0x1) |
                              (((ReadData & 0x8000)==0)?0:0x2) |
                              (((ReadData & 0x800000)==0)?0:0x4) | 0x08;

    OutBuffer[NoByte] =  ReadData & 0x7F;
    OutBuffer[NoByte+1] = (ReadData>>8) & 0x7F;
    OutBuffer[NoByte+2] = (ReadData>>16) & 0x7F;

    OutBuffer[NoByte+3] |= (Key[Channel]==0)?0:0x10;
}
 

poty

★★★★★★✩
19 Фев 2020
2,957
886
@Akademik33, код мне не нравится, если честно, но сейчас не об этом. Покажите мне, что меняется в этом коде при 16-битном входе? Он в любом случае должен передать 17 байт. Или я что-то не понимаю? Опишите проблему попонятнее!
 

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0
@poty,17 байт передается с ацп на комп. Из них, первый байт это особый id для проги на компе, далее передаются 3 байта со значением ацп и четвертый байт передает состояние кнопки+4младших бита из этого байта работают как маска,для реализации (в программе) двухполярного сигнала.В них просто записывается старший бит каждого бйта ацп,а программа уже воспринимает какой знак ставить -или +. То-есть 0 с ацп это "-2000" в программе, 32767 с ацп это "0" в программе, а 65535 с ацп это "+2000" в программе.А следующие 12 байт ,это еще 4 канала по аналогии с первым.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,799
579
44
@Akademik33, Мне кажется, проще было бы передавать программе все данные как есть, а уже в программе их разбирать и обрабатывать.
 

poty

★★★★★★✩
19 Фев 2020
2,957
886

@Akademik33, не вводите никого в заблуждение. Код, приведенный выше принимает значение с АЦП. Компьютер работает уже с этим proxy. Я не понимаю, в чём проблема. Что не так при подключении 16 бит АЦП? Если не можете обобщить, приведите пример.
 

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0

@poty,
С кодом все так. Прошу прошения, если ввел в заблуждение. Просто надо понять, каким образом 16 бит раскладываются на 24. Т.к если в коде закомментировать фильтр и отравлять riadData напрямую в signalChanel. То 17 битной посылке, младший из трех байт ацп всегда в нулях. А если работать через фильтр, то каким-то образом начинает меняться младший байт значения ацп в этой же 17 байтной посылке . Я не понимаю как это происходит. Поэтому прошу объяснить более грамотных программистов в отличии от меня.
 

poty

★★★★★★✩
19 Фев 2020
2,957
886
Ваш вопрос раскладывается на 2:
  • почему 16-битный АЦП (2 байта информации) заполняет старшие байты 24-битного числа (3 байта)? Для ответа на этот вопрос нужно знать протокол обмена с конкретным примененным АЦП. Информацию об этом Вы не предоставили. Предполагаю, что первые два байта от АЦП - реальные значения с АЦП, а последний байт - какой-нибудь вспомогательный регистр, равный 0. Фактически, если Вы используете другой АЦП, то функцию приёма нужно переписать под протокол этого АЦП.
  • почему при прямом использовании полученных данных младший байт всегда 0 (ответ - см. п.1), а при использовании усреднения в нём появляются значения? Так функция усреднения и занимается тем, что интегрирует некоторое количество последовательных данных и затем нормализует их, производя деление на размер выборки. Допустим имеется ряд получаемых целых чисел, с кратностью 10 (т.е. 10, 20, 30, 40...). Возьмем для примера ряд 40, 60, 20, 10. Подаём их на сглаживающий фильтр с выборкой 4, имеющий начальное значение 0. На выходе получим:
(0*3 + 40)/4=10
(10*3 + 60)/4=22,5=~22
(22*3 + 20)/4=21,5=~21
(21*3 + 10)/4=18,25=~18
...
Как видим, операция деления заполняет последний разряд.
 

Kir

★✩✩✩✩✩✩
28 Мар 2020
69
16
@Akademik33,
Вот эта строка - ReadData = ((unsigned long)(ReadAdcDataReg()) << 8) , по сути, приводит 16 бит к 24-м. Так как, при тех же равных условиях (Опорное напряжение, величина входного сигнала, и усиление, если оно есть), 24-битное АЦП давало бы результат примерно в этих диапазонах. Когда включен фильтр, при делении, разряды перемещались в младший байт, при отключённом фильтре никаких дополнительных операций не происходит, и младший байт всегда остается 0-м.

Очень странно выглядит вот этот фрагмент:
C++:
OutBuffer[NoByte+3] = (((ReadData & 0x80)==0)?0:0x1) |
                      (((ReadData & 0x8000)==0)?0:0x2) |
                      (((ReadData & 0x800000)==0)?0:0x4) | 0x08;

    OutBuffer[NoByte]   =  ReadData & 0x7F;
    OutBuffer[NoByte+1] = (ReadData>>8) & 0x7F;
    OutBuffer[NoByte+2] = (ReadData>>16) & 0x7F;
Такое ощущение, что тут нарочно из 3 байт значения "выковыривают" старший бит, чтобы char оставался положительным,
а потом на приемной стороне восстанавливают, типа, если старший бит стоял, то прибавить ещё 128, довольно забавно)))
Никогда такого не видел, все можно сделать намного проще, но, быть может есть трудности с разбором на стороне ПК, хотя это странно.
 

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0

@poty,
(0*3 + 40)/4=10
(10*3 + 60)/4=22,5=~22
(22*3 + 20)/4=21,5=~21
(21*3 + 10)/4=18,25=~18
...
Как видим, операция деления заполняет последний разряд.

Точно...... Спасибо большое!!!! Он же устедняя раскладывает на число с запятой. И числа после запятой пишутся в младший байт судя по всему. Спасибо ещё раз @poty , что направил на правильную мысль. 🤝


@Kir,
А можно пример, как можно это сделать по другому?. Я попробую. Я пробовал без фильтра инкрементировать в readData по единице, объявив переменную 3 байта. И программа все адекватно понимала и принимала равные значения от-2000 до +2000. Программа на компе меняет значение на единицу при каждом 15 инкременте. То есть разницу в 15 единиц с ацп она усредняет до единицы.

Я бы в понедельник снял видео, как все это работает. Но я не знаю как сюда его выложить.
 

Kir

★✩✩✩✩✩✩
28 Мар 2020
69
16
@Akademik33,
Фильтр тут вообще ни при чем. В вашей реализации для упаковки передаваемых данных совершаются лишние действия, которые не добавляют простоты, как при формировании пакета, так и при его разборе, не говоря о расширении в перспективе.

Если отталкиваться от вашей уже сложенной структуры:
Пакет включается в себя однобайтный ID, и данные для 4-х каналов. Каждый канал представлен 32-битами, из которых 24 бита отводится для хранения значения с АЦП и 8 для хранения маски, в вашем случае с нажатых кнопок.

Тогда структуру для описания 1-го канала можно представить так:
C++:
struct channel_t {
    uint32_t adc  : 24;
    uint32_t mask : 8;
};
из таких структур легко построить массив с нужным числом экземпляров, и собрать в пакета
C++:
struct packet {
    uint8_t id;
    channel_t channel[4];
};
можно даже немного обобщить на случай если изменится число каналов
C++:
template<size_t MAX_NUM>
struct packet {
    uint8_t   id;
    channel_t channel[MAX_NUM];
};
Но тут есть проблема, если на ардуине все будет хорошо, то на ПК из-за выравнивания все сломается.
Для этого данные нужно хранить в массиве из однобайтных чисел, как у вас сейчас, что удобства не добавляет.
Было бы неплохо всю черновую работу спихнуть на компилятор, чтобы он сам заботился куда и что нужно сдвигать
И это можно реализовать, если сделать packet полноценным объектом, который будет выполнять роль
сериализатора и десериализатора. Кроме того, можно пойти чуть дальше и обобщить по принимаемой структуре channel_t
C++:
template<typename TYPE_PACK, size_t MAX_NUM>
class packet final {
public:
    TYPE_PACK& operator[](size_t index) {
        TYPE_PACK* instance = reinterpret_cast<TYPE_PACK*>(
                        &data[1] + index * sizeof(TYPE_PACK));
        return *instance;
    }

    void    SetID(uint8_t id) { data[0] = id;   }
    uint8_t GetID()     const { return data[0]; }

private:
    uint8_t data[(sizeof(TYPE_PACK) * MAX_NUM) + 1];
};
тогда функции заполнения буфера(пакета) и чтения на стороне ПК можно будет переписать проще, примерно вот так
C++:
void FillBuffer(uint8_t channel) {
    out[channel].adc  = signal[channel];
    out[channel].mask = (Key[channel] == 0) ? (0) : (0x10);
}


// Функция обработки на ПК
void ADC_Process(uint8_t channel) {
    auto finalResult = map(recv[channel].adc, 0, MaxByResolution(24), -2000, 2000);
    auto mask        = recv[channel].mask;
}
Более полный пример, пусть и весьма синтетический, можно посмотреть тут
 

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0

@Kir,
Спасибо большое за такое подробное разъяснение, но я думаю мне это будет сложно реализовать. Я тут в одной строке то запутался так. А уж про такой код что предложили вы, я долго не разберусь))).
 

Arhat109

★★★★✩✩✩
9 Июн 2019
473
203
@Kir, это довольно типовое применение при передачах через 7-и битный канал. 8-й бит гонять отдельно, а потом добавлять его..
 

Akademik33

✩✩✩✩✩✩✩
19 Сен 2020
30
0

@Arhat109,
Спасибо. Я в итоге так и не разобрался до конца с усреднением. Решил использовать ацп на 24 бита...