Odjechani.com.pl

Pełna wersja: Zagadka programistyczna
Aktualnie przeglądasz uproszczoną wersję forum. Kliknij tutaj, by zobaczyć wersję z pełnym formatowaniem.
Stron: 1 2
Tak próbuję wykminić co robię źle i nie wiem już, skończyły mi się pomysły, zapewne problem jest banalny i pomyślałem, że nic nie zaszkodzi gdy opublikuję tu ten go tu, a nóż ktoś wpadnie na rozwiązanie przede mną. Poniżej mam kod w C, który sprawdza cykliczny kod nadmiarowy CRC pomiaru wilgotności, bądź temperatury. Kod znaleziony w sieci.

unsigned int checkCRC() {

	  volatile uint32_t data = (((RXBuffer[1]<<8))|((RXBuffer[2])));

	  volatile uint32_t remainder = data << 8; //Pad with 8 bits because we have to add in the check value

	  remainder |= RXBuffer[3]; //Add on the check value

	  volatile uint32_t divisor = 0x988000;

	  volatile uint8_t i;

	  for (i = 0 ; i < 16 ; i++) //Operate on only 16 positions of max 24. The remaining 8 are our remainder and should be zero when we're done.
	  {

	    if( remainder & (uint32_t)1<<(uint32_t)(23 - i) ) //Check if there is a one in the left position
	      remainder ^= divisor;

	    divisor >>= 1; //Rotate the divsor max 16 times so that we have 8 bits left of a remainder
	  }


	if (remainder == 0) {
		return data;
	} else {
		return 999;
	}


RXBuffer przechowuje trzy wartości, wartość pierwsza [1], to pierwszy bajt ciągu (16 bitowego) zawierającego pomiar, a RXBuffer [2], to jego druga część. RXBuffer[3], to kod nadmiarowy.  0x988000, to dzielnik, można go rozpisać binarnie, wtedy można łatwo na kartce policzyć.

Gdy zmienna remainder jest równa zero, to znaczy, że nie było błędu transmisji, jeśli jest inaczej, wystąpił błąd, zwracana jest wartość 999. Teraz problem polega na tym, że chodź kod działa, to po przekroczeniu pewnej wartości pomiarowej następuje błąd, wynik osiąga wartości daleko poza właściwymi, ale nie wynika to z błędu transmisji, gdyż wartości w buforze są poprawne, co wyliczyłem na kartce. Przykładowa wartość zwracana, która jest zawyżona 4294940982, kiedy powinna ona być rzędu kilkudziesięciu tysięcy. 

Domyślam się, że przy większych wartościach wejściowych z bufora, po drodze następuje pomieszanie obliczeń, nie wiem gdzie. Funkcję tę powinno się po drobnych przeróbkach dać skompilować do programu konsolowego, bez większego trudu.

Przykładowe dane wejściowe, które są odczytywane poprawnie:

1. 01101000
2. 00111010
3 (CRC).  01111100 

Zmienna data = 26682 (wartość poprawna)

Przykładowe dane, które są odczytywane błędnie:

1. 10101111
2. 11100010
3 (CRC). 11000101

Zmienna data = 4294946786 (ogromna) 

Oczywiście wartości te, gdy nie mają błędów CRC są przeliczane na temperaturę lub wilgotność. 

Jak ktoś to wykmini, to będzie super. ;)
Mógłbyś wrzucić opis tego kodu od autora?
Oryginalny kod:

//Give this function the 2 byte message (measurement) and the check_value byte from the HTU21D
//If it returns 0, then the transmission was good
//If it returns something other than 0, then the communication was corrupted
//From: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html
//POLYNOMIAL = 0x0131 = x^8 + x^5 + x^4 + 1 : http://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks
#define SHIFTED_DIVISOR 0x988000 //This is the 0x0131 polynomial shifted to farthest left of three bytes

byte HTU21D::check_crc(uint16_t message_from_sensor, uint8_t check_value_from_sensor)
{
  //Test cases from datasheet:
  //message = 0xDC, checkvalue is 0x79
  //message = 0x683A, checkvalue is 0x7C
  //message = 0x4E85, checkvalue is 0x6B

  uint32_t remainder = (uint32_t)message_from_sensor << 8; //Pad with 8 bits because we have to add in the check value
  remainder |= check_value_from_sensor; //Add on the check value

  uint32_t divsor = (uint32_t)SHIFTED_DIVISOR;

  for (int i = 0 ; i < 16 ; i++) //Operate on only 16 positions of max 24. The remaining 8 are our remainder and should be zero when we're done.
  {
    //Serial.print("remainder: ");
    //Serial.println(remainder, BIN);
    //Serial.print("divsor:    ");
    //Serial.println(divsor, BIN);
    //Serial.println();

    if( remainder & (uint32_t)1<<(23 - i) ) //Check if there is a one in the left position
      remainder ^= divsor;

    divsor >>= 1; //Rotate the divsor max 16 times so that we have 8 bits left of a remainder
  }

  return (byte)remainder;
}

Ogólnie zasada jest dość prosta. Działa, to na podobnej zasadzie, co tu: https://pl.wikipedia.org/wiki/Cykliczny_kod_nadmiarowy

Tłumacząc to nieco ściślej, najpierw dostajesz trzy wartości, np.

1. 01101000
2. 00111010
3 (CRC).  01111100

Dwie pierwsze składają się na całość, czyli 16bit

0110100000111010

Potem przesuwa się powyższą wartość o 8 bitów w lewo, powstaje 24 bit.

011010000011101000000000

Potem dodaje się trzecią wartość z bufora do powyższego ciągu, czyli powstaje:

011010000011101001111100

Teraz bierze się dzielnik, który zajmuje 9 bitów:

100110001

W ten sposób powstaje działanie, które należy wyzerować jak w wikipedii:

011010000011101001111100
_100110001

Robi, to ta funkcja, ale coś z nią jest nie tak, bo nie zeruje większych wartości wejściowych, a w zasadzie dostaje świra wywalając kosmos.

Należy pamiętać, że dzielnik został przekształcony do wartości 24 bitowej, czyli jego hex wynosi 0x988000, by programowo, można było przeprowadzić tę operację.

PS: Ja już znalazłem odpowiedź i tak jak przypuszczałem, rozwiązanie było banalne, ale dam Wam pogłówkować, dodam, że algorytm jest ok, tylko coś z danymi trzeba zrobić.
Mam zamiar zająć się tą zagadką, tylko mam jedno pytanie. Czy kod w ostatnim poście to C++? Sugeruje to ta linijka:
byte HTU21D::check_crc(uint16_t message_from_sensor, uint8_t check_value_from_sensor)
Importowano bibliotekę stdint.h.
To nie to, mam na myśli zapis:
byte HTU21D::check_crc
To sugeruje, że "check_crc" jest metodą o typie zwrotnym "byte" klasy "HTU21D". A w czystym C nie ma klas.
Tak, akurat ten kod pochodzi z arduino, a tam jest dostępny C++, aczkolwiek został on już przebudowany na C dla moich zastosowań i nie w tym tkwił problem.
Wprowadziłem te problematyczne dane do tablicy jako zmiennej globalnej, na której funkcja działa. Od razu mówię, że nic nie zmieniałem w samej funkcji.
(04.08.2016, 17:02)Swordancer napisał(a): [ -> ]Przykładowe dane, które są odczytywane błędnie:

1. 10101111
2. 11100010
3 (CRC). 11000101

Zmienna data = 4294946786 (ogromna)
1. 10101111=175
2. 11100010=226
3 (CRC). 11000101=197
Dla tych danych funkcja zwraca u mnie wartość 45026 a nie 4294946786 jak u Ciebie. Skoro wartość 26682 jest ok a 45026 nie, znaczy, że zmienna miała za mały rozmiar aby to pomieścić. Zrobiłem symulację u siebie, jak zapiszę to do zmiennej dwu-bajtowej bez znaku(unsigned short int) działa dobrze, bo zmienna ma maksymalny zakres do 65535. Jeśli użyję zmiennej dwu-bajtowej ze znakiem(short int) z zakresem do 32767 wystąpi błąd, bo 45026 się nie zmieści. Zrobiłem u siebie symulację i jak zapiszę wynik do short inta to mi zwraca 4294946786 a jak do unsigned short inta to zwraca 45026. Element, gdzie działało źle:
    short x=checkCRC();
    printf("%u", x);
 Kod działający dobrze:
#include <stdio.h>
uint32_t RXBuffer[]={0,175,226,197};
unsigned int checkCRC() {

      volatile uint32_t data = (((RXBuffer[1]<<8))|((RXBuffer[2])));

      volatile uint32_t remainder = data << 8; //Pad with 8 bits because we have to add in the check value

      remainder |= RXBuffer[3]; //Add on the check value

      volatile uint32_t divisor = 0x988000;

      volatile uint8_t i;

      for (i = 0 ; i < 16 ; i++) //Operate on only 16 positions of max 24. The remaining 8 are our remainder and should be zero when we're done.
      {

        if( remainder & (uint32_t)1<<(uint32_t)(23 - i) ) //Check if there is a one in the left position
          remainder ^= divisor;

        divisor >>= 1; //Rotate the divsor max 16 times so that we have 8 bits left of a remainder
      }


    if (remainder == 0) {
        return data;
    } else {
        return 999;
    }
}
int main(void)
{
    printf("%u", checkCRC());
    return 0;
}
BTW. Na przyszłość nie używaj zmiennych globalnych, to aż kłuje w oczy ;) I niech ta funkcja zwraca uint32_t, skoro operuje tylko na takich danych aby uniknąć problemów z różnymi rozmiarami unsigned inta na różnych kompilatorach czy sprzęcie.
Tak, zdaje się, że właśnie w tej pierdółce leżał błąd, ale to już dawno było rozwiązane, zapomniałem o tym wątku.

Zmienne globalne, to podstawa, nie rozumiem dlaczego mam ich nie używać? Bufor globalny stworzyłem, bo tak jest po prostu prościej i wygodniej, skoro oddzielna biblioteka przechwytuje dane na liniach transmisyjnych dopiero gdy się pojawią, więc nie widzę sensu by nie korzystać już z zajętej pamięci i po prostu odczytywać je gdy nachodzi mnie ochota. Po co za każdym razem wymieniać argumenty między funkcjami? Wyobrażasz sobie pobieranie całej tablicy do funkcji jako niezależną tablicę lokalną? Po co? By zawalić pamięć na okres przeliczeń? Kiedy nie ma technicznych konieczności, to absolutnie nic nie muszę.

Owszem w uniwersalnych funkcjach ładnie jest tak przeładowywać bufor, czy jakieś zmienne, ale tutaj schemat jest tak stały, że tworzenie kopii bufora nie ma sensu, lepiej działać na istniejącym już globalnym, szczególnie, że taki drobny myk uczy, że zużycie pamięci w mikrokontrolerze ma ogromne znaczenie, taki nawyk, nawet przy prostych kodach zużywających jej 10%.

Ogólnie np. zmiennych globalnych używam do zliczania czasu zdarzeń, bo nie mogę wprowadzać asynchronicznych procesów, więc wykorzystuję je jaki liczniki. Po za tym, zmienne globalne są równie ważne co lokalne i nie musze ich kopiować, gdy nie mam takiej potrzeby.

Co do uint32_t, możliwe, że na tym polegał błąd, ale nie analizuję już tego kodu. Chyba właśnie chodziło, o rozmiar inta, ale nie pamięta. Tak czy siak, to byłą pierdoła jakaś, niczym brak klamry, ciężko się dopatrzyć, gdy kompilator nie podkreśla gdzie. :D
(26.01.2017, 16:39)Swordancer napisał(a): [ -> ]Zmienne globalne, to podstawa, nie rozumiem dlaczego mam ich nie używać? Bufor globalny stworzyłem, bo tak jest po prostu prościej i wygodniej, skoro oddzielna biblioteka przechwytuje dane na liniach transmisyjnych dopiero gdy się pojawią, więc nie widzę sensu by nie korzystać już z zajętej pamięci i po prostu odczytywać je gdy nachodzi mnie ochota.
Nie wiem jak to dokładnie wygląda na mikrokontrolerach ale w programowaniu podobno używanie zmiennych globalnych to nie jest dobra praktyka.
(26.01.2017, 16:39)Swordancer napisał(a): [ -> ]Wyobrażasz sobie pobieranie całej tablicy do funkcji jako niezależną tablicę lokalną? Po co? By zawalić pamięć na okres przeliczeń? Kiedy nie ma technicznych konieczności, to absolutnie nic nie muszę.
Co? Przecież jak przekazujesz tablicę do funkcji to nie przekazujesz jej jakieś lokalnej kopii tylko wskaźnik do niej. A technicznie mówiąc funkcja będzie używać lokalnej kopii wskaźnika tzn. Możesz operatorem dereferencji zmienić zawartość pamięci na którą wskazuje wskaźnik ale nie możesz zmienić adresu, na który wskaźnik wskazuje chyba, że przekażesz do funkcji wskaźnik wskazujący na wskaźnik. Ale fakt, że te 2-4 bajty w zależności od sprzętu i kompilatora zaoszczędzisz.
Stron: 1 2