Koncepcje, pomysły i inne wynalazki

To nie jest miejsce tylko dla początkujących, wszyscy jesteśmy w czymś początkujący i wymieniamy się doświadczeniami.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » poniedziałek 09 lis 2020, 18:22

Wiadomo, że najlepszym nauczycielem jest własna praca i własny
eksperyment. Jednak takie podejście często jest narażone na „błądzenie we mgle”.
Postanowiłem przedstawić w tym wątku kilka własnych drobnych rozwiązań,
inspiracji, idei i czasami gotowych rozwiązań. Ta koncepcja już od jakiegoś czasu
mnie nurtowała, to takie dziwne, ulotne wrażenie, że trzeba coś zrobić. Może tych
kilka małych przyczynków połączy się w jedną większą całość i podobnie jak
Archimedesa zainspiruje do okrzyku „eureka”, jakie to proste.
Należy się tu spodziewać bardzo szerokiego spektrum...


Duża pamięć EEPROM z interfejsem SPI
pel01_il00.jpg
Potrzeba jest matką wynalazku, to już wiadomo. A potrzeba jest taka, by jakiś system z prockiem na pokładzie miał możliwość wprowadzenia i przechowywania całkiem dużo różnych parametrów i nastaw. To oznacza zastosowanie jakiejś pamięci nieulotnej. Tu rzecz jasna pojęcie „dużo” jest wielce mgliste, więc przybliżając temat można stwierdzić, że 128kB (pojemność pamięci 1 megabit) nie jest przesadnie dużą pamięcią, jednak na początek się nadaje. Może z czasem będzie konieczność poszukania innych rozwiązań. Na tę chwilę może być. Musi to być pamięć nieulotna ale zapisywalna przez procka. Rozpatrywałem kilka pomysłów (między innymi pamięci FLASH) i stanęło na pamięci EEPROM. Z dostępnej gamy wylosowałem układ o symbolu M95M01. Jest to pamięć EEPROM z interfejsem SPI (to dosyć istotne, by dostęp do pamięci był szybki). Zaletą jest mały footprint i wymagane niewielkie zasoby mikrokontrolera do jej obsługi, to tylko 5 drutów. Jest pamięcią o dostępie szeregowym, więc nie może być użytkowana jako „stała” pamięć włączona w przestrzeń adresową procka. No ale taka jest transakcja wiązana: coś za coś (pamięć równoległa to dużo drutów).
pel01_il01.png
Układ EEPROM wymaga kilku sygnałów do jej obróbki:
  • CS – chip select – sygnał wyboru układu,
  • SDO – serial data output – wyjściowe (z punktu widzenia pamięci) dane szeregowe,
  • CLK – sygnał zegarowy,
  • SDI – serial data input – wejściowe (z punktu widzenia pamięci) dane szeregowe,
  • WP – write protect – sygnał ochrony przez zapisem,
  • HOLD – sygnał do wstrzymywania pracy pamięci,
  • GND i VCC – do zasilania.
Użycie pamięci wymaga wystawienia sygnału wyboru CS. Tradycyjnie jest to sygnał ze stanem aktywnym jako zero logiczne. Wygarnianie danych szeregowych do zapisu oraz odczytu jest synchronizowane sygnałem zegarowym. Przy przesyłaniu szeregowym, dane zaczynają się od bitu MSB (najbardziej znaczącego). Po wystąpieniu stanu aktywnego sygnału wyboru, pamięć oczekuje polecenia do wykonania, no sama z siebie nic nie zrobi. Jest to kod 8-bitowy wysyłany szeregowo do pamięci. W zależności od tego kodu, w dalszej kolejności występują specyficzne dla wykonywanego polecenia działania. Kody poleceń są następujące:
  • 0000 0110 – aktywacja zezwolenia na zapis, każde polecenie wymaga zezwolenia na zapis, bez tego nic nie przeniknie do wnętrza pamięci,
  • 0000 0100 – deaktywacja zezwolenia na zapis, po każdej zakończonej operacji zapisu, zezwolenie również deaktywuje się samo, dy dostać się do środka ponownie należy uzyskać zezwolenie, czytać można do woli,
  • 0000 0101 – odczyt statusu pamięci, jeżeli wystąpił zapis do pamięci, to dopóki akcja ta nie zostanie zakończona, układ EEPROM ignoruje wszystko, wykonanie kolejnego kroku wymaga zakończenia poprzedniego, odczytując ten status można się dowiedzieć, czy akcja zapisu się zakończyła, odczyt może być realizowany w każdej chwili, czyli: kto pyta, nie błądzi,
  • 0000 0001 – zapis do rejestru statusowego (konieczne jest posiadanie zezwolenia), można określić jakie rejony przestrzeni mają swoją indywidualną ochronę,
  • 0000 0011 – odczyt pamięci, po podaniu adresu, pamięć można czytać dowolnie długo (dopóki jest aktywny sygnał wyboru), pamięć sama wykonuje autoinkrementację adresu wskazującego na czytaną komórkę,
  • 0000 0010 – zapis do pamięci (tradycyjnie konieczny jest glejt, inaczej nie przejdzie), po podaniu kodu i w dalszej kolejności adresu, każde kolejne dane są zapisywane w pamięci z autoinkrementacją adresu.
Przebieg sygnały odczytu rejestru statusowego pokazuje rysunek:
pel01_il02.png
Po aktywacji (sygnał wyboru CS) wysyłany jest kod identyfikujący żądanie odczytu rejestru statusowego. W rejestrze tym, poza innymi szczegółami, istotna jest flaga trwania akcji zapisu (ta operacja jak wiadomo jest powolna i czasami nie nadąża za zapotrzebowaniami). Po wysłaniu polecenia, układ pamięci w końcu skuma o co chodzi i wysyła na swój pin wyjściowy własny status. Procek nadrzędny może go zwinąć i wiedzieć co jest grane.
Przed każdą operacją zapisu należy uzyskać prawo do grzebania w pamięci. Zezwolenie jest jednorazowego użytku, więc przez kolejną zadymą należy ponownie wystąpić o prawo do zapisu. Jest to jedynie wysłanie kodu polecenia do pamięci.
pel01_il03.png
Operacja zapisu danych to trochę inna bajka. Po przesłaniu kodu polecenia zapisu do pamięci należy w dalszej kolejności wysłać adres pod jaki należy dostarczyć dane (jest to liczba 24-bitowa) i potem już tylko same dane.
pel01_il04.png
Operacja odczytu jest podobna, tylko kto inny zgarnia dane (inna noga).
Każdy eksperyment nie jest nic wart dopóki nie zostanie zrealizowany fizycznie. Moje środowisko laboratoryjne to niewielki systemik z ATMEGA32 ma pokładzie.
pel01_il05.jpg
System ten łączy się z pamięcią za pośrednictwem portu PORTC. Użyty jest moduł z prockiem ATMEGA32 (szczegóły jego styków są pokazane poniżej).
pel01_il06.png
Procek jest przypięty do małej PCB z przylutowanym układem M95M01. By było widać co się dzieje, do modułu via serial przyłączony jest ekran kompa. Obsługa pamięci jest w dużej mierze sparametryzowana. Pozwala to na dużą elastyczność, każdy wariant staje się możliwy. W eksperymencie łączenia były następujące (sygnał HOLD jest na stałe podpolaryzowany do +5V, pozostałe są przypięte do procka):

Kod: Zaznacz cały

#define EEPROMDataCSPort             PORTC
#define EEPROMDataCSDirPort          DDRC
#define EEPROMDataCSPin              7
#define EEPROMDataSDOPort            PORTC
#define EEPROMDataSDODirPort         DDRC
#define EEPROMDataSDOPin             3
#define EEPROMDataSCKPort            PORTC
#define EEPROMDataSCKDirPort         DDRC
#define EEPROMDataSCKPin             4
#define EEPROMDataSDIPort            PINC
#define EEPROMDataSDIDirPort         DDRC
#define EEPROMDataSDIPin             6
#define EEPROMDataWPPort             PORTC
#define EEPROMDataWPDirPort          DDRC
#define EEPROMDataWPPin              5
Program po uruchomieniu pisze na ekranie drobne menu i robi to co ma nakazane: może pamięć zapisać lub odczytać.
Po pierwszym włączeniu poprosiłem o zapis do pamięci:
pel01_il07.png
Po tej akcji należy przejść do kolejnego etapu dopiero po zakończeniu poprzedniego. Ze śledzenia na ekranie widać, że program w operacji zapisu miał zezwolenie (bit b1 statusu) oraz w chwili zapytania był zajęty porządkowaniem swego środka (bit b0 statusu). To nie trwało długo, bo kolejne zapytanie dało już inną odpowiedź. Przy okazji widać, że pamięć sama sobie wycofała zezwolenie za zapis. W programie nie ma takiej operacji, natomiast z wyświetlonej informacji jasno wynika: było i magicznie wcięło.
Kolejna operacja to odczyt danych.
pel01_il08.png
Program wyświetlił co ma zapisane w pamięci. Jeżeli uważne oko dostrzega różnicę, to znaczy, że dobrze widzi. W programie celowo zapis i odczyt (tej samej wielkości bloku) został zrealizowany w różne obszary (ale mające część wspólną).
No to pozostał do wykonania najbardziej istotny test: wyłączyć prąd, odczekać chwilę i ponownie włączyć.
pel01_il09.png

Dane zapisane nie zginęły. Fabrykant twierdzi, że kostka może przechowywać informacje w nieskończoność (z punktu widzenia człowieka, więcej niż 100 lat to już nieskończoność).
Eksperymentalny program:
eeprom.7z
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony poniedziałek 09 lis 2020, 20:57 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

Re: Koncepcje, pomysły i inne wynalazki

Postautor: tasza » poniedziałek 09 lis 2020, 20:01

O, a mnie takie cosik kiedyś po łepetynie chodziło tylko z szeregowymi NVRAM, może się skuszę i sklecę, a póki co wersja teoretyczna - SPI + bankowanie.
Ustrojstwo działa na takiej zasadzie, że sygnał /CS jest komutowany przez 8-bit rejestr przesuwny na zasadzie sterowania w '596 sygnałem /OE, ta kostka ma wyjścia OC, idealne wręcz do wszelkich /CS-ów i innych wybieraków, tylko trzeba je podciągnąć do VCC jakimś rezystorem. Linie DIN, CLK są wspólne i dla selektora i dla kostek-banków. Sygnałem biznesowym SETUP czyli technicznie RCLK w rejestrze zatrzaskujemy konfigurację pamięci, oczywiście trzeba dbać, aby wsunięty układ bitów był 1/8 = L, bo inaczej wyselekcjonujemy więcej niż jeden układ pamięci i będzie popelina z powodu konfliktu na DOUT (wyjścia Q pamięci).
Kostki '596 można kaskadowo łączyć (wyjście QH` zapinamy do SER następnego w łańcuchu) więc takich bloczków jak wyżej też możemy sobie teoretycznie nawstawiać do upojenia. Ograniczeniem będzie wydajność linii DIN i CLK ponieważ one są propagowane na wszelkie układy w systemie.
Ot, zabawka taka, może się komu przyda, to coś tu zapewni 8x128kB - czyli jeden megabajt danych po czterech drucikach, z możliwością rozmnażania do granic jak wyżej.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » poniedziałek 09 lis 2020, 20:51

tasza pisze:O, a mnie takie cosik kiedyś po łepetynie chodziło tylko z szeregowymi NVRAM, może się skuszę i sklecę, a póki co wersja teoretyczna - SPI + bankowanie.

Ooo, ciekawy pomysł do wykorzystania, już wiem co będę robił. A tak w ogólności, to zapraszam do działania.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » środa 11 lis 2020, 20:32

Jak to czasami pomysł rodzi pomysł. Patrząc na wersję teoretyczną zrodziła mi się inna wersja również teoretyczna. Nie mogę powiedzieć czy jest lepsza czy gorsza, ale mogę powiedzieć, że jest inna. Ideę pokazuje poniższy rysunek:
pel02_il01.png

Kostka 74HCT138 wręcz idealnie do tego się wpasowuje. W stanie pasywnym na wszystkich wyjściach Y0 .. Y7 panuje stan logicznej jedynki. Wystawienie stanu aktywnego dla sygnału wyboru /CS powoduje propagację jego na odpowiednie wyjście Y0 .. Y7 w zależności od kombinacji występującej na EA0 .. EA2 (takie rozszerzenie adresowe). Jednak nie ma nic za darmo. W tym wypadku trzeba zapłacić większymi zasobami w procku (sygnały EA0 .. EA2) zaangażowanymi w proces.
Nadal pozostaje problematyka związana z obciążalnością sygnałów. Cała gałąź przyłączona jako sygnał CLK może okazać się zbyt duża i wyjście (jedno) w procku może sobie nie poradzić. W przypadku sygnałów /CS jest spoko. Sygnały SDO to również spoko. W stanach pasywnych są one w trzecim stanie, więc układy nie będą się obciążać wzajemnie a do wysterowania jest jedynie wejście w procku. Takiego szczęścia nie ma sygnał SDI. Podlega identycznym ograniczeniom co CLK.
Jednak nie ma co się martwić na zapas, każdy problem jest do rozwiązania, chociaż tu są również jakieś dodatkowe koszty. Można w torze wstawić przykładowo bramkę 74HCT08, która zadba o swoją grupę sygnałów. Jednak dodatkowym kosztem jest kolejne opóźnienie wnoszone przez czas propagacji bramki. Przy dużych prędkościach te zjawiska zaczynają być dostrzegalne i nie należy ich ignorować.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » sobota 14 lis 2020, 22:00

Sterowanie silnika krokowego za pomocą mikrokontrolera

pel03_il00.jpg


Silniki krokowe, to wynalazek mający duże zastosowanie w automatyce, robotyce itp. Silnik, w którym impulsowe zasilanie prądem elektrycznym powoduje, że jego wirnik nie obraca się ruchem ciągłym, lecz wykonuje za każdym razem ruch obrotowy o ściśle ustalony kąt. Dzięki temu kąt obrotu wirnika jest ściśle zależny od liczby dostarczonych impulsów prądowych (no i rzecz jasna od cech konstrukcyjnych), a prędkość kątowa wirnika jest proporcjonalna do częstotliwości impulsów prądowych. Kąt obrotu wirnika pod wpływem działania jednego impulsu może mieć różną wartość, zależnie od budowy silnika – jest to zwykle wartość od kilku do kilkudziesięciu stopni. Silniki krokowe, zależnie od przeznaczenia, są przystosowane do wykonywania od ułamków obrotu na minutę do kilkuset obrotów na minutę. Z tych pobieżnych informacji należy wysnuć dosyć istotny wniosek, silniki krokowe służą do obrotu wirnika o określony kąt a praca ciągła nie jest jego celem głównym. Nie znaczy, że się nie da (zawsze się da), ale nie jest to ulubione zajęcie.
Z punktu widzenia budowy (w sensie rozwiązań elektrycznych), silniki można podzielić na unipolarne i bipolarne. W poniższych rozważaniach będę się skupiał nad silnikami unipolarnymi. Nazwa bierze się od tego, że impulsy sterujące są przyłączane do uzwojeń silnika zawsze w jedną stronę. Typowym rozwiązaniem jest grupa czterech uzwojeń ze wspólnym jednym końcem wyprowadzonym na zewnątrz (to implikuje 5 drucików wychodzących z silnika).
W silniku krokowym wirnik jak i stojan są „poząbkowane”.
pel03_il01.png
Jeżeli zostanie włączony prąd do uzwojenia, to „ząbki” stojana przyciągną do siebie „ząbki” wirnika, takie wzajemne przyciąganie, ale ponieważ stojan jest nieruchomy, ktoś musi ustąpić → wirnik. Włączony prąd na uzwojeniu doprowadzi do stabilnej sytuacji jak na rysunku, ząbki ustawią się naprzeciw siebie i tak będą trwać. Wyłączenie prądu doprowadzi do pewnego rozluźnienia wzajemnych relacji, natomiast włączenie prądu w następnej cewce doprowadzi do bliskiego stosunku w obrębie nowego miejsca. Liczba ząbków jest tak dobrana, że zawsze zachodzi jakieś przesunięcie „fazowe”, jak pasuje na jednym końcu, to nie zgrywa się na drugim.
pel03_il02.png
Teraz, jeżeli prąd będzie włączany w kolejnych uzwojeniach, to ząbki wirnika będą dążyć do siebie w kolejnych cewkach. Cykliczne włączanie wymusi ruch obrotowy wirnika.
pel03_il03.png
Uzbrojeni w tak elementarną wiedzę na temat działania silnika możemy pokusić się o realizację ruchu ciągłego obrotowego. Można utworzyć kawałek programu, który w przerwaniach od timera będzie realizował właściwe akcje sterujące. Przerwania od timera gwarantują równomierność akcji. Wnosząc odpowiednie korekty wartości stałych mających zastosowanie w realizacji sterowania pozwoli na regulację przykładowo prędkości obrotowej. Im więcej przerwań, tym więcej przełączeń prądu w uzwojeniach i tym większa prędkość obrotowa. Niestety silniki krokowe przy niezaprzeczalnej zalecie, jaką jest możliwość obrotu (ruszyć, obrócić i zatrzymać) o dowolny kąt (właściwie dowolną wielokrotność kąta jednego kroku, który jest cechą konstrukcyjną), mają wadę → są dosyć powolne. Jak się przegnie z prędkością, to zaczną się gubić i wypadać z synchronizmu, także bez przegięć.
Schemat układu badawczego to
pel03_il04.png
Środek uzwojeń silnika jest wyprowadzony na zewnątrz oraz każde drugie wyprowadzenie uzwojenia. Układ ten sterowany jest przez ULN2803, który do tego doskonale się nadaje, jest wyposażony w odpowiednie instrumenty. Wyjście każdego kanału ma odpowiednio włączoną diodę, których drugie końcówki są razem spięte i wyprowadzone na zewnątrz układu. Połączenie jej z plusem zasilania pozwala stworzyć elementarny układ do gaszenia dużych impulsów samoindukcji (chociaż dla silników większej mocy, należy umieścić diody jak najbliżej uzwojenia, bo efekty samoindukcji mogą zaszkodzić prockowi). Prezentowany silniczek jest dosyć małej mocy, więc każda cewka jest obsługiwana przez pojedynczy kanał w ULN2803. Przy większych silnikach można łączyć je w pary i uzyskać większe możliwości.
Program jest właściwie wręcz prymitywny. By mieć maksymalnie elastyczne podejście to sposób przyłączenia jest sparametryzowany:

Kod: Zaznacz cały

#define AWirePort                      PORTB
#define BWirePort                      PORTB
#define CWirePort                      PORTB
#define DWirePort                      PORTB
#define AWirePin                       0
#define BWirePin                       1
#define CWirePin                       2
#define DWirePin                       3
#define AWireDirectionPort             DDRB
#define BWireDirectionPort             DDRB
#define CWireDirectionPort             DDRB
#define DWireDirectionPort             DDRB
W programie końcówki są nazwane AWire, BWire, CWire i DWire. Istotny fragment to:

Kod: Zaznacz cały

static void RunStep ( uint8_t State )
{
  /*----------------------------------------------------------------*/
  switch ( State )
  {
    case 0 :
      AWirePort |= ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 1 :
      AWirePort |= ( 1 << AWirePin ) ;
      BWirePort |= ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 2 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort |= ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 3 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort |= ( 1 << BWirePin ) ;
      CWirePort |= ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 4 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort |= ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 5 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort |= ( 1 << CWirePin ) ;
      DWirePort |= ( 1 << DWirePin ) ;
      break ;
    case 6 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort |= ( 1 << DWirePin ) ;
      break ;
    case 7 :
      AWirePort |= ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort |= ( 1 << DWirePin ) ;
      break ;
  } /* switch */ ;
} /* RunStep */


SIGNAL ( TIMER0_OVF_vect )
{
  /*----------------------------------------------------------------*/
  TimerCounter ++ ;
  if ( TimerCounter > MotorSpeed )
  {
    TimerCounter = 0 ;
    RunStep ( StepMotorState ) ;
    if ( ForwardStepMotorDirection )
    {
      StepMotorState ++ ;
    } /* if ... */
    else
    {
      StepMotorState -- ;
    } /* if ... else */ ;
    StepMotorState &= 0x07 ;
  } /* if */ ;
} /* TIMER0_OVF_vect */
W przerwaniach od upływającego czasu wywoływana jest funkcja do przełączania zasilania dla uzwojeń. Jest to automat synchroniczny o 8 stanach, w których:
  • stan 1 – włączenie prądu dla uzwojenia A,
  • stan 2 – włączenie prądu dla uzwojenia A i B,
  • stan 3 – włączenie prądu dla uzwojenia B,
  • stan 4 – włączenie prądu dla uzwojenia B i C,
  • stan 5 – włączenie prądu dla uzwojenia C,
  • stan 6 – włączenie prądu dla uzwojenia C i D,
  • stan 7 – włączenie prądu dla uzwojenia D,
  • stan 8 – włączenie prądu dla uzwojenia D i A.
Jak widać, po 8 stanach, sterowanie robi nawrót do początku. Takie rozwiązanie (z półkrokiem) pozwala na większą precyzję i płynność. Jeżeli istnieje potrzeba sterowania w drugą stronę, to wszystko należy wykonać identycznie tylko w odwrotnej kolejności.
Schemat jest, program jest, pozostało rozpoznać silnik.
pel03_il05.jpg
Trochę danych mamy już gratis: rezystancja pojedynczego uzwojenia wynosi 35Ω. O resztę trzeba zatroszczyć się samemu. Oczywiście można posiłkować się.
pel03_il05a.jpg
Silnik jest zakończony złączką
pel03_il06.jpg
To w ruch idzie miernik rezystancji.
pel03_il07.jpg
Mierząc rezystancję metodą: każdy z każdym, łatwo odnaleźć punkt wspólny, to ten, gdzie rezystancja do każdego innego styku będzie identyczna (i przy okazji najmniejsza, stawia najmniejszy opór).
No to wiadomo już jedno, gdzie jest środek (na środku). Reszta to już bardziej złożona sprawa. Nie pozostaje nic innego jak tylko wyznaczyć pozostałe końcówki metodą walki. Dokładniej, to końcówki są już rozpoznane, to wszystkie pozostałe, jednak do poprawnej pracy muszą wchodzić w odpowiedniej kolejności. Zaburzenie kolejności powoduje, że silnik dostaje jakichś drgawek. Jest tylko jedna droga do sukcesu: właściwa kolejność.
Układ badawczy:
pel03_il08.jpg
pel03_il09.jpg
pel03_il10.jpg
Więc metodą prób i błędów została zdobyta wiedza jak powinien być przyłączony silniczek. Wbrew pozorom to nie jest tak, jak by się nasuwało na pierwszy rzut. Poniżej widać, że programowe wire_a (PORTB.0, kabelek biały) trafia na pin 1 złączki silnika, wire_b (PORTB.1, zielony kabelek) trafia na pin 2, pin 3, to już zostało ustalone wcześniej jest wspólnym zasilaniem i trafia na +5V (czerwony kabelek), wire_c (PORTB.2, żółty kabelek) trafia na pin 5 oraz wire_d (PORTB.3, niebieski kabelek) trafia na pin 4. Nawet gołym okiem widać, że nie jest to po kolei. Spodziewałem się jakiejś logiki w okuciu złączki, ale wyszło co innego niż zakładałem. No tak bywa, nic nie można z góry zakładać. Po włączeniu zasilania działa. Można pooglądać:
step_mot.mov

i poczytać:
step_motor-avr.7z
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » wtorek 17 lis 2020, 00:39

Sterowanie silnika krokowego z układów logicznych

Sterowanie silnikiem krokowym nie jest jedynie domeną mikrokontrolerów. Właściwie istotna jest wiedza dotycząca działania całego mechanizmu. Ten algorytm można odtworzyć w każdym innym środowisku. Oczywista, że zastosowanie procków znacząco ułatwia zagadnienie, pozwala na osiągnięcie własności z wyższej półki. Rzeczą normalną jest, że „bogate” sterowanie wymaga dużych nakładów materiałowych. Zanim jednak wbijemy się na wyższy level, to można przetrenować warianty prostsze. Takim przykładem może być uproszczenie sterowań. Zamiast użycia automatu o 8 stanach można zastosować automat o 4 stanach, pozostają jedynie te stany automatu, gdzie włączany jest prąd dla jednej cewki (wcześniej były momenty, gdzie jednocześnie pracowały dwie wybrane cewki). Skoro jest to automat, to znaczy, że całość musi być napędzana jakimś sygnałem zegarowym. Odtwarzając algorytm działania programu w mikrokontrolerze, powstaje następujący schemat:
pel04_il01.png
Mamy tu generator sygnału zegarowego, którego częstotliwość wpływa na prędkość zmian stanu automatu, a ta z kolei determinuje czasowy cykl przełączeń prądu w uzwojeniach → wpływa na prędkość obrotową. Impulsy zegarowe są zliczane w 4-bitowym liczniku asynchronicznym, z czego 2 najwyższe bity kodują stan automatu. Młodsze bity maja funkcję zmniejszenia częstotliwości impulsów zegarowych oraz uzyskanie wypełnienia 50% (choć do tej operacji wystarczy już 1-bitowy licznik). Układ HCT138 generuje kod typu NOT(1 x 8) → cyklicznie na wyjściach układu pojawia się zero logiczne na odpowiednim wyjściu przy czym na wszystkich pozostałych utrzymuje się jedynka logiczna. No, to jest jak raz stan nieoczekiwany, gdyż wymagana dalej kombinacja jest typu na wybranym wyjściu występuje jedynka logiczna przy czym na wszystkich pozostałych są zera. Do układu ULN2803 dochodzi już taka kombinacja sygnałów, gdzie na tle samych zer występuje jedna jedynka. Ta jedynka włącza w efekcie prąd w odpowiednim uzwojeniu silnika. W sumie to prosta sprawa.
Powstał układ badawczy:
pel04_il02.jpg
pel04_il03.jpg
pel04_il04.jpg
No i to nawet kręci...
Co widać:
step_mot2.mov
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

Re: Koncepcje, pomysły i inne wynalazki

Postautor: tasza » wtorek 17 lis 2020, 20:13

Prosta końcówka mocy do silniczka krokowego unipolarnego (4 cewki ze wspólnym połączeniem) lub do taśmy LED RGB

Jak już jesteśmy przy kręceniu silniczkiem, to ja może się tu dorzucę z drobiazgiem - takie oto cosik popełniłam na koniec wakacji, zleconka od warszawskiej rodzinki. Układ jest luźną parafrazą tego, co udało się znaleźć w Google na temat IRF540+Arduino, finalny kształt projektu narzuciła zawartość szuflady, robione z tego co było pod ręką. Układzik jest o tyle fajny, że może pracować na rozłącznych masach jeżeli z jakiegokolwiek powodu nie chcemy, aby strona sterująca miała kontakt ze stroną wykonawczą. Kanały sterujące mają zabezpieczenie diodkami na wspak, można spokojnie podłączać obciążenie indukcyjne - silniczek krokowy, przekaźniki, elektromagnesy, co tam komu potrzeba.
Pdf z mozaiką do termotransferu jak i "instrukcja montażu" w załącznikach.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » wtorek 17 lis 2020, 20:38

tasza pisze:to ja może się tu dorzucę z drobiazgiem - takie oto cosik popełniłam na koniec wakacji, zleconka od warszawskiej rodzinki.

Wszystkie drobiazgi są ważne, tak jak i rodzina jest ważna (a może i najważniejsza). Własnie zacząłem coś klecić na temat optoizolacji, ale już chyba nie trzeba. Wszystko zostało już powiedziana i pokazane.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » środa 18 lis 2020, 20:52

Sterowanie silnika krokowego
za pomocą mikrokontrolera:
zaczątek maszyny CNC


Jeżeli algorytm mikrokontrolera do sterowania silnikiem krokowym rozbudować o licznik wykonanych kroków oraz po odliczeniu wymaganej ich liczby, algorytm by sam się blokował, to mamy zaczątek maszyny CNC. Sterowanie rewersyjne już jest zawarte w programie, wystarczy niewielka zmiana i powstaje całkiem nowa jakość.
Odpowiednie wysterowanie uzwojeń silnika dla danego kroku to następująca funkcja:

Kod: Zaznacz cały

static void RunStep ( uint8_t State )
{
  /*----------------------------------------------------------------*/
  switch ( State )
  {
    case 0 :
      AWirePort |= ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 1 :
      AWirePort |= ( 1 << AWirePin ) ;
      BWirePort |= ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 2 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort |= ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 3 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort |= ( 1 << BWirePin ) ;
      CWirePort |= ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 4 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort |= ( 1 << CWirePin ) ;
      DWirePort &= ~ ( 1 << DWirePin ) ;
      break ;
    case 5 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort |= ( 1 << CWirePin ) ;
      DWirePort |= ( 1 << DWirePin ) ;
      break ;
    case 6 :
      AWirePort &= ~ ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort |= ( 1 << DWirePin ) ;
      break ;
    case 7 :
      AWirePort |= ( 1 << AWirePin ) ;
      BWirePort &= ~ ( 1 << BWirePin ) ;
      CWirePort &= ~ ( 1 << CWirePin ) ;
      DWirePort |= ( 1 << DWirePin ) ;
      break ;
  } /* switch */ ;
} /* RunStep */
Jest ona wywoływana w funkcji obsługi przerwania od upływu czasu.

Kod: Zaznacz cały

SIGNAL ( TIMER0_OVF_vect )
{
  /*----------------------------------------------------------------*/
  TimerCounter ++ ;
  if ( TimerCounter > MotorSpeed )
  {
    TimerCounter = 0 ;
    if ( MotorOn )
    {
      RunStep ( StepMotorState ) ;
      if ( ForwardStepMotorDirection )
      {
        StepMotorState ++ ;
      } /* if ... */
      else
      {
        StepMotorState -- ;
      } /* if ... else */ ;
      StepMotorState &= 0x07 ;
      if ( StepCounter )
      {
        StepCounter -- ;
      } /* if ... */
      else
      {
        MotorStop ( ) ;
      } /* if ... else */ ;
    } /* if */ ;
  } /* if */ ;
} /* TIMER0_OVF_vect */
W zależności od wymaganego kierunku obrotów, choć jest to sprawa w dużej mierze umowna, gdyż zależy to przede wszystkim od kolejności przyłączonych uzwojeń. Jednak umówmy się, że obrót zgodnie z kierunkiem wskazówek zegara to są obroty lewe, oraz w przeciwnym kierunku, to są obroty prawe. Tak przyszło mi do głowy, jaki jest odsetek ludzi, którzy nie wiedzą o czym jest mowa, w dobie powszechnego stosowania zegarków z wyświetlaczem cyfrowym lub używania zegarków w telefonie to może być jakiś problem, tak przykładowo ma moje najmłodsze, słabo wyznaje się na klasycznym zegarku. Ale wracając do tematu...
W zależności od tak określonego kierunku obrotów, zmienna będąca stanem automatu sterującego zmienia wartości w odmienny sposób: albo jest inkrementowana albo dekrementowana. Każde wywołanie funkcji do wysterowania uzwojeń silnika jest zliczane. Dokładniej, to licznik ma nadaną wymaganą wartość i w każdym kroku jest dekrementowany. Po osiągnięciu zera, automat się wyłącza.
Teraz w pętli głównej jest „wbite” małe demko, to taka „Never Ending Story”.

Kod: Zaznacz cały

int main ( void )
{
  uint8_t Loop ;
  /*----------------------------------------------------------------*/
  HardwareInit ( ) ;
  EnvirInit ( ) ;
  SoftwareInit ( ) ;
  MotorStop ( ) ;
  sei ( ) ;
  for ( ; ; )
  {
    ForwardStepMotorDirection = 1 ;
    MotorOn = 1 ;
    StepCounter = 250 ;
    TimerCounter = 0 ;
    for ( Loop = 0 ; Loop < 16 ; Loop ++ )
      LongDelay ( ) ;
    ForwardStepMotorDirection = 0 ;
    MotorOn = 1 ;
    StepCounter = 250 ;
    TimerCounter = 0 ;
    for ( Loop = 0 ; Loop < 16 ; Loop ++ )
      LongDelay ( ) ;
  } /* for */ ;
  return ( 0 ) ;
} /* main */

step_mot3.MOV
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » piątek 20 lis 2020, 19:28

Eliminacja drgań styków

pel05_il00.jpg
Potrzeba jest matką wynalazku. Jest cały ogrom tematów eksperymentalnych, gdzie zachodzi potrzeba wyprodukowania „czystego” sygnału prostokątnego. Jest to dosyć istotne zagadnienie, gdyż potrafi popsuć całość badań i zabawy. Ręczne wyprodukowanie impulsu choćby za pomocą zwykłego przycisku, który będzie zwierać do masy sygnał wstępnie podpolaryzowany do VCC, jest praktycznie niemożliwe. O dzwonieniu styków to słyszał już chyba każdy, a walka z tym niekorzystnym zjawiskiem to całkiem spore zagadnienie. Oczywiście tu przychodzi ze wsparciem technika cyfrowa. Takim fajnym rozwiązaniem jest wykorzystanie do tego przerzutnika typu RS. To najprostszy wariant „komórki pamiętającej” i składa się z dwóch bramek NAD specyficznie „zapętlonych”. Można do tego użyć bramek NOR, ale te mają trochę gorsze własności (chodzi o stan niedozwolony). Przy użyciu dwóch bramek NAND stanem niedozwolonym są dwa zera logiczne na wejściu przerzutnika, w przypadku bramek NOR, stanem zabronionym są dwie jedynki na wejściu. O co chodzi z tym stanem zabronionym? Wysterowanie przerzutnika zbudowanego z bramek NAND dwoma zerami oznacza, że układ zgłupieje i będzie to stan niestabilny. Kiedyś (no dawno to było, ale było) zdawało mi się, że to spowoduje jakieś uszkodzenie przerzutnika, bo skoro jest stan zabroniony... Nic z tych rzeczy, nic się nie uwali, tylko działać będzie tak trochę krzaczasto (inaczej mówiąc pójdzie w krzaki).
Często jest niezbędny piękny, niczym nie zaśmiecony, krystalicznie czysty, po prostu idealny impuls. Jest wiele sytuacji, gdzie nie może być inaczej. Właśnie na potrzeby badawczo-eksperymentalne zbudowałem sobie takie coś do „zadawania” stanów z „palca”. Schemat tego „przydasia” jest następujący:
pel05_il01.png
Właściwie to niezbędna jest połowa układu, czyli dwie bramki. Jednak by się nic nie zmarnowało, to pozostałe bramki zostały użyte do sygnalizacji aktualnego stanu oraz do separacji wyjścia od samego przerzutnika. Tak całkiem przy okazji wyszło takie fajne coś, podanie na którekolwiek wejście logicznego zera powoduje wpis do przerzutnika jedynki (i zera do przerzutnika stowarzyszonego), czyli taka „logiczna” negacja sygnału wejściowego. Po przejściu przez bramkę robiącą za negator, wychodzi na wyjście stan oczekiwany. Ostatnia bramka współdziała z LED'em i tu również przydaje się negacja (LED świeci, jak na wyjściu panuje jedynka).
Jak działa taki przerzutnik?
pel05_il02.png
Jeżeli do przerzutnika przypięty jest przełącznika (taki, co jedną parę styków łączy rozłączając drugą), to przerzutnik jest wysterowany do wpisu określonej wartości występującej na wejściu, gdyż na jedno wejście jest podane zero logiczne a na drugim występuje jedynka logiczna. Nawet można odłączyć przełącznik od układu, wtedy oba wejścia mają stan logicznej jedynki. Taki stan oznacza, że nic nie trzeba robić, przerzutnik jest w stanie dotychczasowym i nie zmienia stanu, no po prostu pamięta. Zmiana stanu przełącznika oznacza, że wejście, które miało dotychczas jedynkę dostaje zero a w drugim zmiana jest odwrotna: zero przechodzi na jedynkę. Wystąpienie zera oznacza wpis na wyjście „własnej” bramki stanu logicznej jedynki, czyli zbocze na wejściu (choć w rzeczywistości jest istotny stan logiczny, ale przejście z jedynki na zero można rozpatrywać jako zbocze) spowoduje przejście przerzutnika do stanu. Jeżeli jest używany taki przełącznik, jak na schemacie, to wystąpi na wyjściu zmiana stanu z zera na jedynkę dając ładny impuls.
Taki przydaś w kooperacji z przełącznikiem produkuje wręcz doskonały impuls. Naciśnięcie przycisku w pierwszej kolejności daje rozłączenie dotychczas zwartych styków i po jakimś czasie zwarcie drugiej pary styków, no chyba, że ktoś potrafi śmignąć to z prędkością światła (to będzie jednocześnie). Rozłączenie styków, rzecz jasna wygeneruje ciąg impulsów, ale te będą oznaczały wpis jedynki (lub zera, kwestia punktu widzenia, bo zagadnienie jest symetryczne) lub pamiętanie dotychczasowego stanu, a była tam właśnie jedynka (lub zero patrząc z innego punktu widzenia). Połączenie drugiej pary styków wygeneruje ponownie ciąg impulsów i pierwszy impuls z takiego dzwonka spowoduje przerzut. Znowu będzie powtórka ale już niejako lustrzana. Co się dzieje w układzie, pokazuje rysunek.
pel05_il03.png
Przydatność przydasia pokazuje takie środowisko eksperymentalne. Ze zwykłego przełącznika podawany jest sygnał na wielobitowy licznik. Jego stan jest wyświetlony na diodach LED. Schemat:
pel05_il04.png
Laboratorium badawcze to:
pel05_il05.jpg
pel05_il06.jpg
Generując ciąg impulsów z „surowego” przełącznika można się zdziwić, licznik losowo przeskakuje do nieoczekiwanych stanów.
pel05_il07.png
Zastosowanie przydasia, prostuje wszelkie problemy i całość działa tak, jak jest oczekiwane.

O problemach można przekonać się na własne oczy:
deb_bez.wmv
deb_z.wmv
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Koncepcje, pomysły i inne wynalazki

Postautor: gaweł » niedziela 29 lis 2020, 02:54

Konwersja liczb zmiennoprzecinkowych

Zagadnienie konwersji liczb zmiennoprzecinkowych
było już opisane (tutaj). Podany algorytm został wykombinowany
całe wieki temu. Był, jaki był, rewelacja może to nie była,
ale działał. Jest popularne porzekadło, że jak działa, to może
nie tykać, bo może być gorzej. Jednak jak dostrzega się
pewne wady rozwiązania, to można sobie coś ulepszyć.
Oczywiście nie od razu „Kraków zbudowano”, więc kosztowało
mnie to trochę pracy. Nie wszystko daje się przewidzieć, więc
czasami wymaga to realizacji pewnych badać i eksperymentów.


Zdarza się, że w systemach prockowych zachodzi konieczność konwersji liczb zmiennoprzecinkowych na postać tekstową. Niby to żaden problem, gdyż w standardowych zestawach procedur w języku C są ku temu odpowiednie mechanizmy. Jednak w tym wszystkim jest jedno małe ale... Otóż dołączenie owych procedur generuje całkiem spory kawałek kodu, który musi być dolinkowany do naszego programu. W małych systemach prockowych, gdzie wszystkiego jest mało, to może stanowić pewien problem. Jest mało zarówno pamięci FLASH na dolinkowywany kod oraz mało jest pamięci RAM. Szczególnie dotkliwy jest problem małych zasobów właśnie pamięci RAM. Zaproponowane rozwiązanie jest optymalizowane pod względem zużycia zasobów tej pamięci.
Jednym z sensownych rozwiązań jest utworzenie własnych algorytmów. O ile konwersja liczb całkowitych nie jest jakimś przesadnie skomplikowanym problemem, tu ujawniają się pewne problemy. Dotyczą one przede wszystkim zaokrągleń. Standardowe mechanizmy konwersji liczba zmiennoprzecinkowych do liczb całkowitych realizowane jest przez obcięcie ułamka. W połowie przypadków jest to do zaakceptowania a w połowie nie i tu chodzi o tę drugą połowę. Rozpatrzmy kawałek kodu programu:

Kod: Zaznacz cały

static unsigned char Number2 [ ] = "uciecie 123.45" ;
. . .
  signed long TempLong ;
  double TempFloat ;
. . .
  TempFloat = 123.45 ;
  TempLong = ( signed long ) TempFloat ;
  WriteTextLCD ( Number2 ) ;
  SignedLongToStr ( Number , ' ' , 6 , TempLong ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
W rzeczywistości procka AVR daje to następujący efekt:
fcnv_il01.jpg
Kompilator wygenerował kod, który obciął część ułamkową. W przypadku, gdy zachodzi potrzeba wyświetlania takiej liczby bez części ułamkowej, to jest do przyjęcia. Jednak w następującym przypadku już tak nie jest.

Kod: Zaznacz cały

static unsigned char Number3 [ ] = "uciecie 12345.67" ;
. . .
  TempFloat = 12345.67 ;
  TempLong = ( signed long ) TempFloat ;
  WriteTextLCD ( Number3 ) ;
  SignedLongToStr ( Number , ' ' , 6 , TempLong ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
Powyższy kawałek kodu daje efekt:
fcnv_il02.jpg
Oczekiwany wynik odbiega od uzyskanego. Jest prosty sposób na pokonanie tego problemu → przed ucięciem ułamka, do liczby, która poddawana jest konwersji należy dodać pięć dziesiątych. Jeżeli część ułamkowa liczby jest mniejsza od jednej drugiej, to dodanie wspomnianej liczby nie spowoduje, że suma „przeskoczy” na kolejną wartość całkowitą. W przeciwnym wypadku, część całkowita zostanie zwiększona i po klasycznym ucięciu ułamka, wynik będzie zgodny z oczekiwaniem.
Tak jest z liczbami dodatnimi. Dla liczb ujemnych jest podobnie: w połowie przypadków jest OK a w drugiej połowie już nie (zagadnienie jest lustrzane ale dotyczy innych kawałków).

Kod: Zaznacz cały

static unsigned char Number4 [ ] = "uciecie -123.45" ;
. . .
  TempFloat = -123.45 ;
  TempLong = ( signed long ) TempFloat ;
  WriteTextLCD ( Number4 ) ;
  SignedLongToStr ( Number , ' ' , 6 , TempLong ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
fcnv_il03.jpg
Jest całkiem OK, ale inny wariant jest nie jest zadawalający:

Kod: Zaznacz cały

static unsigned char Number5 [ ] = "uciecie -123.75" ;
. . .
  TempFloat = -123.75 ;
  TempLong = ( signed long ) TempFloat ;
  WriteTextLCD ( Number5 ) ;
  SignedLongToStr ( Number , ' ' , 6 , TempLong ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
fcnv_il04.jpg
W wyniku eksperymentów należy wyciągnąć wniosek, że poprawne zaokrąglenie dla liczb dodatnich wymaga dodania jednej drugiej, dla liczb ujemnych należy ją odjąć.
Chcąc uzyskać określoną liczbę cyfr po ułamku dziesiętnym, liczbę przed konwersją należy pomnożyć przez 10 do potęgi <liczba cyfr po kropce>. Zaokrąglając tak powstałą liczbę do części całkowitej wszystko zachowa się w sposób naturalny i poprawny i wystarczy tylko „wdrukować” znak przecinka (kropki) dziesiętnego we właściwe miejsce. W starym rozwiązaniu były realizowane różne kombinacje, by wypracować decyzję, czy należy zwiększyć część całkowitą liczby, czy nie. Stare wady należy eliminować, z tego powodu jest korekta rozwiązania. Zachowuje się ono następująco:

Kod: Zaznacz cały

static unsigned char Number6 [ ] = "konw. 123.4567 (1)" ;
. . .
  TempFloat = 123.4567 ;
  ClrScrLCD ( ) ;
  WriteTextLCD ( Number6 ) ;
  FloatToStr ( Number , ' ' , 10 , 1 , TempFloat ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
  LongDelay ( ) ;
  ClrScrLCD ( ) ;
  WriteTextLCD ( Number7 ) ;
Konwersja z jedną cyfrą po przecinku, część dziesiąta ułamka została zaokrąglona:
fcnv_il05.jpg
Konwersja (ta sama liczba) z dwoma cyframi po przecinku

Kod: Zaznacz cały

static unsigned char Number7 [ ] = "konw. 123.4567 (2)" ;
. . .
  TempFloat = 123.4567 ;
  ClrScrLCD ( ) ;
  WriteTextLCD ( Number7 ) ;
  FloatToStr ( Number , ' ' , 12 , 2 , TempFloat ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
fcnv_il06.jpg
Trzy cyfry po przecinku to:

Kod: Zaznacz cały

static unsigned char Number8 [ ] = "konw. 123.4567 (3)" ;
. . .
  TempFloat = 123.4567 ;
  WriteTextLCD ( Number8 ) ;
  FloatToStr ( Number , ' ' , 13 , 3 , TempFloat ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
fcnv_il07.jpg
Pięć cyfr po przecinku to:
fcnv_il08.jpg
Inna liczba, która będzie stwarzać trochę problemów to:

Kod: Zaznacz cały

static unsigned char Number10 [ ] = "konw. 123.999 (3)" ;
. . .
  TempFloat = 123.999 ;
  WriteTextLCD ( Number10 ) ;
  FloatToStr ( Number , ' ' , 13 , 3 , TempFloat ) ;
  NewLineLCD ( ) ;
  WriteTextLCD ( Number ) ;
daje efekt
fcnv_il09.jpg
Kilka „wariacji” na temat, to:
fcnv_il10.jpg
fcnv_il11.jpg
I dla tej samej liczby ze zmienionym znakiem:
fcnv_il12.jpg
fcnv_il13.jpg
fcnv_il14.jpg
Przedmiotowa funkcja do konwersji to:

Kod: Zaznacz cały

void FloatToStr ( unsigned char * Number ,
                  unsigned char EmptyCh ,
                  unsigned char ConvWidth ,
                  unsigned char FractionWidth ,
                  double        Value )
{
  unsigned char Loop ;
  unsigned char * ChPtr ;
  unsigned char Minus = 0 ;
  signed long CardVal ;
  signed long Divider ;
  /*-------------------------------------------------------------*/
  if ( Value < 0 )
  {
    Minus = 1 ;
    Value = - Value ;
  } /* if */ ;
  if ( ( FractionWidth + 2 ) >= ConvWidth )
    FractionWidth = 0 ;
  Divider = 1 ;
  if ( FractionWidth )
  {
    for ( Loop = 0 ; Loop < FractionWidth ; Loop ++ )
    {
      Value *= 10.0 ;
      Divider *= 10 ;
    } /* for */ ;
  } /* if */ ;
  if ( FractionWidth > 6 )
    FractionWidth = 6 ;
  CardVal = ( signed long ) ( Value + 0.5 ) ;
  SignedLongToStr ( Number , EmptyCh , ConvWidth - FractionWidth - 1 ,
                    CardVal / Divider ) ;
  if ( FractionWidth )
  {
    ChPtr = Number + ( ConvWidth - FractionWidth - 1 ) ;
    * ChPtr ++ = '.' ;
    SignedLongToStr ( ChPtr , '0' , FractionWidth ,
                      CardVal % Divider ) ;
  } /* if */ ;
  if ( Minus )
  {
    if ( * Number == ' ' )
    {
      ChPtr = Number ;
      while ( * ChPtr == ' ' )
        ChPtr ++ ;
      ChPtr -- ;
      * ChPtr = '-' ;
    } /* if */ ;
  } /* if */ ;
} /* FloatToStr */
która wewnątrz posiłkuje się funkcją konwersji liczb całkowitych:

Kod: Zaznacz cały

void SignedLongToStr ( unsigned char * Number ,
                       unsigned char EmptyCh ,
                       unsigned char ConvWidth ,
                       signed long   Value )
{
  unsigned char Loop ;
  unsigned char Minus = 0 ;
  /*---------------------------------------------------------------*/
  if ( Value < 0 )
  {
    Minus = 1 ;
    Value = - Value ;
  } /* if */ ;
  for ( Loop = 0 ; Loop < ConvWidth ; Loop ++ )
    * ( Number + Loop ) = EmptyCh ;
  * ( Number + ConvWidth ) = 0 ;
  Loop = ConvWidth - 1 ;
  for ( ; ; )
  {
    * ( Number + Loop ) = ( unsigned char ) ( (Value % 10)+0x30 ) ;
    Value = Value / 10 ;
    if ( ! Loop )
      break ;
    Loop -- ;
    if ( ! Value )
    {
      if ( Minus )
        * ( Number + Loop ) = '-' ;
      break ;
    } /* if */ ;
  } /* for */ ;
} /* SignedLongToStr */
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „Podstawy elektroniki - teoria i praktyka”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 7 gości