[8085] 16 bitowy generator przebiegów losowych

Kącik dla elektroniki retro - układy, urządzenia, podzespoły, literatura itp.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1274
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

[8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » niedziela 09 kwie 2023, 21:52

16 bitowy generator
przebiegów losowych


Potrzebuję 16-bitowego generatora przebiegów losowych. Jego głównych przeznaczeniem jest diagnostyka elektronicznych układów w czasie pracy w warunkach losowych i niepewnych. Wymyśliłem sobie, że zbuduję odpowiednią aparaturę pozwalającą na generowanie owych przebiegów. Ujmując to w skrócie, jest procek, który będzie generował na porcie równoległym przebieg losowy. Pogrzebałem w swojej szufladzie i padło na 8085. Zagadnienie nie jest aż tak złożone jakby mogło się wydawać a jego moc obliczeniowa w zupełności wystarczy. A z drugiej strony, to będzie miał okazję się wykazać zamiast oddawać się nieróbstwu w szufladzie.
Zanim przejdę do etapu budowy, to zrobiłem sobie badania symulacyjne. Można zaproponować wiele algorytmów dających różne rozkłady generowanych liczb. Mnie interesuje rozkład równomierny, czyli ma zostać wygenerowanych 65536 liczb i żadna nie może się powtórzyć (czyli ma wystąpić każda możliwa kombinacja). Na taką okoliczność ludzkość dopracowała się wzoru:
Xnew = M * Xold + Aczyli każda nowa liczba jest otrzymywana z poprzedniej poprzez pomnożenie jest przez stałą (stała multiplikatywna) i dodaniu innej stałej (stała addytywna). Oczywiście wystąpi problem pierwszej liczby losowej, ale to daje się rozwiązać w miarę prosty sposób. Przykładowo w przerwaniach od czasu inkrementowana jest zmienna w pamięci i jak użytkownik wciśnie przycisk „Start”, to stanowi ona wystarczająco dobrą startową zmienną losową.
By wiedzieć co w trawie piszczy i dokąd to zmierza, napisałem sobie w C program na PC do podstawowych badań. Z eksperymentów wyłonił się istotny wniosek: nie każda wartość stałych M i A daje oczekiwany rozkład generowanych liczb. Programik jest następujący:

Kod: Zaznacz cały

/*
       Test 16-bitowego generatora liczb losowych
       napisał: gaweł
       Białystok, 04.2023
       Licencja: CC BY 3.0
*/

#include <stdio.h>
#include <stdlib.h>

#define TabSize 0x10000

#define MultConst 65
#define AddConst 17

FILE * OutFile ; 
unsigned short Status [ TabSize ] ;

int main(int argc, char *argv[])
{
  unsigned long Loop ;
  unsigned short RandomV ;
  /*----------------------------------------------------------------*/
  OutFile = fopen ( "random.txt" , "w" ) ;
  for ( Loop = 0 ; Loop < TabSize ; Loop ++ )
    Status [ Loop ] = 0 ;
  RandomV = 1234 ;
  fprintf ( OutFile , "Badania losowosci: stala multiplikatywna=%d, stala addytywna=%d\n" , MultConst , AddConst ) ;
  for ( Loop = 0 ; Loop < TabSize ; Loop ++ )
  {
    RandomV = ( ( MultConst * RandomV ) + AddConst ) ;
    fprintf ( OutFile , "Liczba losowa numer %d=%d\n" , Loop , RandomV ) ;
    Status [ RandomV ] ++ ;
  } /* for */ ;
  for ( Loop = 0 ; Loop < TabSize ; Loop ++ )
  {
    fprintf ( OutFile , "Krotnosc liczby %d=%d\n" , Loop , Status [ Loop ] ) ;
  } /* for */ ;
  fprintf ( OutFile , "Tropienie statystyki\n" ) ;
  for ( Loop = 0 ; Loop < TabSize ; Loop ++ )
  {
    if ( Status [ Loop ] == 0 )
      fprintf ( OutFile , "blad liczba %d nie wygenerowala\n" , Loop ) ;
    if ( Status [ Loop ] > 1 )
      fprintf ( OutFile , "blad dla liczby %d wygenerowala się %d razy\n" , Loop , Status [ Loop ] ) ;
  } /* for */ ;
  fprintf ( OutFile , "Koniec statystyki\n" ) ;
  fclose ( OutFile ) ;
  return 0;
}

Z badań wynika, że najlepiej jest jak obie stałe są liczbami nieparzystymi. Przykładowo dla M=128 i A=9 program wpadł w pętlę i nie mógł się z niej wyrwać. Generował w kółko liczbę 17545 (17545 * 128 + 9 = 2245769, co po obcięciu do 16 bitów daje 17545). Zmiana stałej na M=129, A=9 daje już bardzo dobre rezultaty (żadna liczba się nie powtórzyła, zatem wystąpiło 65536 różnych liczb).
Również dobrze wypadły następujące pary: M=1025 A=7, M=1025 A=9, M=129 A=9, M=257 A=31, M=257 A=33, M=65 A=17, M=65 A=7. Dobór stałej M ma fundamentalne znaczenie. Starłem się dobrać stała multiplikatywną jako 2 do potęgi 0 plus 2 do potęgi jakiejś. To daje bardzo prostą realizację operacji mnożenia w prockach, które nie mają wymaganych instrukcji. By wyliczyć przykładowo nową liczbę dla parametrów M=257 i A=33 [Xnew=257 * Xold + 33 = (256 +1 ) * Xold + 33 = Xold * 256 + Xold + 33], mamy tylko sumowanie i operacje przesunięcia (nadaje się nawet dla 8085, który nie wiem, czy nawet wyciąga jednego MIPS’a).

Jeżeli ktoś jest zainteresowany wynikami badań, to zapraszam do lektury:
random.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
piotrek
User
User
Posty: 155
Rejestracja: niedziela 05 lis 2017, 02:46

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: piotrek » środa 12 kwie 2023, 19:24

Sposób, który zaimplementowałeś znany jest pod nazwą LGC (linear congruential generator).
Podobny pod względem matematycznym jest LFSR (Linear-feedback shift register), który można zaimplementować sprzętowo za pomocą rejestrów przesuwnych i bramek xor (tak jest robione w FPGA), a także software'owo z użyciem operatorów przesunięcia bitowego (szybszych niż mnożenie czy modulo).

Jeżeli ten ciąg permutacji puszczony w losowej kolejności ma być za każdym razem inny, to myślę, że ten sam efekt można uzyskać po prostu losując za każdym razem jedną liczbę z podanego przedziału.

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

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » środa 12 kwie 2023, 22:08

piotrek pisze:można zaimplementować sprzętowo za pomocą rejestrów przesuwnych i bramek xor (tak jest robione w FPGA), a także software'owo z użyciem operatorów przesunięcia bitowego (szybszych niż mnożenie czy modulo).


Zgadza się, ale FPGA zrobiło się koszmarnie drogie, więc jest to redukcja kosztów oraz jest tu przewidzianych kilka funkcji, o których nie napisałem.

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

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

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » środa 26 kwie 2023, 23:13

Tak zagłębiając się filozoficznie w temat generacji liczb losowych nasuwa się pytanie: na ile jest losowe to zdarzenie? Niby tworzące się ciągi bitowe są w jakimś stopniu przypadkowe, ale z drugiej strony w rzeczywistości wszystko jest ściśle zdeterminowane, gdyż nowe elementy wynikają z określonego algorytmu. Poznanie tej formuły pozwala określić jaki będzie następny element, więc pozostaje do rozkminy problem o determinizmie zdarzeń.
Jednak wracając do budowy generatora, konstrukcja wygląda następująco: jest procek z podstawowymi elementami niezbędnymi do jego działania, pamięć RAM oraz pamięć EPROM, zespół portów przewidziany do współpracy z otoczeniem. Z grubsza to wygląda tak:
rnd0.png

Zespół procka przedstawia się następująco:
rnd1.png

Sam procek 8085, który jak wiadomo, ma wbudowany generator taktu zegarowego, więc w prostym rozwiązaniu wystarczy podłączyć rezonator kwarcowy. Użyty 8085 może poginać z częstotliwością 3MHz (są szybsze, ale mój jest taki), jednak zastosowałem rezonator z w miarę okrągłą częstotliwością (4MHz). Sam procek wewnętrznie dzieli ją przez dwa. Okrągłość częstotliwości wynika z w miarę prostych rachunków dla timera by regulować tempo generowania ciągów bitowych. Z tego powodu w układzie znalazł się układ 8253, który będzie generował przerwania dla proca. By nie tworzyć zbyt rozbudowanego rozwiązania, wykorzystane jest wejście przerwania RST7.5 (jest to wejście z reakcją na zbocze sygnału przerywającego). Pozostałe reagują na poziom, więc konieczne byłoby dorobienie rozwiązania by stworzyć wariant reakcji na zbocze. A ponieważ jest tylko jeden sygnał przerywający, to pozostałe są niewykorzystane.
Procek 8085 ma tą przypadłość, że ma multipleksowaną szynę danych (współdzieloną z młodszą częścią adresowej). Z tego powodu występuje rejestr (dokładnie zatrzask) do uzyskania młodszej części adresu.
Do obsługi pamięci i portów są użyte dwa układy do generowania sygnałów wyboru. W przypadku pamięci to na zakresie adresowym 0000H .. 7FFFH odzywa się EPROM oraz na zakresie 8000H..FFFFH pukamy do pamięci RAM. Podobne rozwiązanie występuje przy obsłudze portów. Mamy trzy porty: 8253 i dwie sztuki 8255. By wygenerować chipselekty do wszystkich układów, sam dekoder adresowy również musi zostać odpowiednio „odpalony”. Procek do tego generuje kilka sygnałów: IO/M oraz S0 i S1. To pozwala na szczegółowe rozróżnienie cykli wykonywanych przez procka, ale z drugiej strony konieczna jest dodatkowa logika bramkowa. W moim wariancie wystarczy informacja, że procek pisze lub czyta do pamięci względnie portów (nie zależy mi na informacji, że jest w fazie fetch [M1 w wariancie ziloga] i temu podobnych).
rnd2.png

Pamiątki, to trywialna sprawa. Występuje drobne urozmaicenie: można zastosować układ 27128 lub 27256. Sprowadza się to do tego, że na pin 27 należy podać kolejny bit adresu lub podłączyć go do +5V.
rnd3.png

Porty do wyjścia liczb losowych: koncepcja wręcz trywialna: zapis do portu 8255. Jednak moim wymaganiem jest by na wyjściu wszystko pojawiło się jednocześnie. Wiadomo, że 8-bitowy procek nie jest w stanie tego zrobić, więc są zastosowane dwa rejestry, które jednym (wspólnym) sygnałem przepiszą wynik z 8255 do rejestrów wyjściowych. Akcja staje się jednoczesna i niepodzielna. Trzeci port z układu 8255 steruje różnymi jednobitowymi sygnałami (jak przykładowo przepisanie danych do rejestrów wyjściowych). By „świat zewnętrzny” wiedział o nadchodzących zmianach, wychodzi sygnał strobu (przepuszczony przez bramkę, by ochronić samego 8255 przez nieszczęściem czyhającym na zewnątrz). Strob na zewnątrz jest generowany niezależnie od strobu przepisania danych i może być programowo określony, że nowe dane nas zewnątrz sygnalizowane są narastającym lub opadającym zboczem tego sygnału. Pozostałe sygnały z portu C sterują diodami LED.
rnd4.png

Drugi port 8255 jest przewidziany do poganiania modułem alfanumerycznym LCD oraz małą klawiaturka matrycową.
rnd5.png

Całość danych wychodzi na display LED’owy oraz na złącze na zewnątrz.
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
Zegar
User
User
Posty: 320
Rejestracja: wtorek 02 lip 2019, 14:42

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: Zegar » czwartek 27 kwie 2023, 08:01

Wyszło całkiem rozległe urządzenie... W dodatku 100% retro. ;) Ale LCD można sterować bezpośrednio z magistrali i znacznie uprościć, a więc również przyspieszyć, jego obsługę. Z Z80 działa, więc tutaj też powinno. Nie ma M1, ale to nie wada lecz zaleta. Bramka jest potrzebna tylko do negacji CS.
LCD-direct.png
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
"If A = success, then the formula is A = X + Y + Z.
X is work. Y is play. Z is keep your mouth shut."
A. Einstein

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

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » czwartek 27 kwie 2023, 14:49

Zegar pisze:Wyszło całkiem rozległe urządzenie... W dodatku 100% retro.

No bo to miało być retro. Z wiekiem człowiek dziecinnieje i wraca do minionych czasów. Wtedy właśnie takimi prockami się zajmowałem :D . Poza tym, jak nie zużyję tych układów, to będę musiał je wyrzucić. Serce mnie zaboli jak będę musiał to zrobić więc łączę przyjemne z pożytecznym.

Zegar pisze:Ale LCD można sterować bezpośrednio z magistrali i znacznie uprościć, a więc również przyspieszyć, jego obsługę. Z Z80 działa, więc tutaj też powinno. Nie ma M1, ale to nie wada lecz zaleta. Bramka jest potrzebna tylko do negacji CS.

Coś podobnego robiłem z AVR (na ATM8515, bo on ma możliwość dopięcia zewnętrznej RAM) i moduł był właśnie komórką pamięci (tak jak w Ciebie). Trzeba było dać mniejszą częstotliwość taktującą, bo taki moduł udaje dosyć powolna pamięć RAM. W 8085 powinno się udać, bo 2MHz taktu daje czas dostępu większy niż 500ns.
No LCD jest trochę słabo pasujący do retro, ale potrzebuję wyświetlić trochę informacji alfanumerycznych. Chociaż ... natchnąłeś mnie pewną koncepcją rozwiązania retro. Sprawa jest jeszcze otwarta i można wnieść jeszcze korekty. Wszystko jeszcze przed nami.

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

Awatar użytkownika
Zegar
User
User
Posty: 320
Rejestracja: wtorek 02 lip 2019, 14:42

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: Zegar » czwartek 27 kwie 2023, 15:20

gaweł pisze:Coś podobnego robiłem z AVR (na ATM8515, bo on ma możliwość dopięcia zewnętrznej RAM) i moduł był właśnie komórką pamięci (tak jak w Ciebie). Trzeba było dać mniejszą częstotliwość taktującą, bo taki moduł udaje dosyć powolna pamięć RAM. W 8085 powinno się udać, bo 2MHz taktu daje czas dostępu większy niż 500ns.

CS mam z IORQ, więc to normalne peryferium. Sprawdziłem na 4 MHz i 8 MHz i daje radę. Może nowe HD44780 są szybsze?
gaweł pisze:Wszystko jeszcze przed nami.

Coraz trudniej kupić "ładne" stare kości, więc na pewno ktoś by je chętnie przytulił. ;)
"If A = success, then the formula is A = X + Y + Z.
X is work. Y is play. Z is keep your mouth shut."
A. Einstein

Awatar użytkownika
mokrowski
User
User
Posty: 190
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: mokrowski » czwartek 04 maja 2023, 15:49

Tu masz parametry dla LFSR od Xilinxa: https://docs.xilinx.com/v/u/en-US/xapp052 Nadają się oczywiście także do implementacji programowej (kiedyś jakiejś używałem w projektach dla klienta)
Trochę podstawowe, ale użyteczne jako punkt startu dla LCG: https://en.wikipedia.org/wiki/Linear_co ... _generator
Bardzo przystępny wykład odnośnie rand_gen: https://www.youtube.com/watch?v=45Oet5qjlms&t=2s
Cała strona dobra, ale to podsumowanie: https://www.pcg-random.org/index.html

Co do zasiewu lub entropii, z braku dedykowanego układu źródła szumów, można zastosować antenę na pływającym wejściu ADC. Czasem także konkretne MCU mają braki determinizmu np. na etapie operacji z EEPROM lub innych peryferiów. Ogólnie wszystko co szumi warto wstrzyknąć do procedury generacji....
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek

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

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » wtorek 11 lip 2023, 20:25

Próbowałem pokynarzyć swój projekt, no idzie to opornie.
kyn.jpg

Wygenerowała się taka liczba problemów i pomyłek, że kur...ica mnie wzięła. Postanowiłem przejść na lepszy level i zamiast walczyć z drucikami, zrobiłem PCB.
random-bcu.png

random-fcu.png

random-fsil.png
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: 1274
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » wtorek 01 sie 2023, 23:03

No i nadeszła ta historyczna chwila...
Przyszła płytka PCB
rndf01.jpg

którą cierpliwie polutowałem i zbudowałem generator przebiegów losowych. Wszystko jest zgodnie z zasadami jakie obowiązywały w dawnych czasach.
rndf02.jpg

Teraz zostało trochę powalczyć z softem.
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: 1274
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » środa 24 kwie 2024, 22:03

Pomógł przypadek

No i nadeszła ta chwila, by dokończyć to co zostało rozpoczęte. Układ polutowany i gotowy do kontynuacji prac (no trochę mu się przeleżało). Pierwszy odpał nie obył się bez problemów. Na początek zauważyłem jakieś dziwności w obwodzie resetowania:
rnd3-01.png

Napięcie na kondensatorze od resetu nie miało ochoty podskoczyć powyżej 3,5V, nawet jak wyjąłem procka. Znaczy się, że kondek ma dużą upływność? Wyrwałem chwasta i dałem nowy. Teraz jest tak być powinno.
rnd3-02.png

W tego typu konstrukcjach, by nie walczyć z wiatrakami, to należy mieć pewność, że procek ma zabezpieczone właściwe warunki do pracy → znaczy, że dostaje dobry sygnał zegarowy. Włączam procka do prądu (na razie nic się nie grzeje, to dobry znak na przyszłość) oscylek pokazuje, że procek nawiązał jakąś tam kooperację z rezonatorem kwarcowym… ale moją uwagę przykuł wynik pokazany przez oscylka. Przy 4MHz kwarca powinno na pinie 37 (rysunek schematu wyżej) wychodzić 2 MHz, a tu jest
rnd3-03.png

jakieś niecałe 25 kHz. Trochę rozkminy, może kwarc jakiś nie teges (jak kondek z resetu). I tak całkiem przypadkiem, macając płytkę (macanie zawsze ma duży potencjał) zauważyłem, że procek nagle wypuścił 2 MHz’y na swoim wyjściu.
rnd3-04.png

Tak przyszło mi do głowy, że mu brakuje czegoś. Przypomniałem sobie, że czasami równolegle z kwarcem stosowany jest duży rezystor, no to wlutowałem równolegle do kwarca 100 kΩ, choć intelowe materiały jakoś nie pisały o tym.
rnd3-05.png

No i pomogło skutecznie. Każdorazowy odpał zasilania prowadził do wygenerowania właściwego sygnału zegarowego. Ot i taka to zagadka.
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: 1274
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » piątek 26 kwie 2024, 21:14

Kolejny krok do celu
rnd4-00.png


Mój procek umie już „mówić”, troszkę podgoniłem softa i nawet ruszył bez większych problemów. Znaczy wcześniej sprawdziłem na oscylku, czy łączy się z portami, takie zapętlone input z portu (każdego, jaki występuje w konstrukcji), no i oscylek mówi, że jest OK (są chipselekty). Skoro tak, to podłączyłem moduł LCD.
rnd4-01.png

Jak się później okazało, zabanglał z pierwszego kopa, ale wcześniej trochę musiałem się nakręcić potencjometrem od kontrastu: za pierwszym razem pojechałem nie w tą stronę i doszło do bandy. Z lekką obawą pokręciłem w drugą stronę i już myślałem, że będą problemy, gdy układ mnie zaskoczył i pojawił się napis. Prawdę mówiąc, to przełożyłem na intelowego asma istniejące procedury z avr’owego C, gdzie obsługa była sprawna. No jak się okazało, nawet się nie pomyliłem przy przekładzie.
rnd4-02.png

Do pracy wykorzystałem swój stary emulator eprom, który, jak widać nie doczekał się jeszcze obudowy. No kiedyś to nastąpi, będzie musiało.
rnd4-03.png

No teraz czeka mnie potyczka z układem 8253 i przerywadło od czasu.
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: 1274
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » niedziela 28 kwie 2024, 14:15

Przerwanka

Nawet zadziałały (z jednym problemem), ale zanim to…
rnd5-3.png

Jest układ 8253 przeznaczony do celów czasowych. Sam procek bazując na kwarcu 4 MHz wytwarza przebieg zegarowy o częstotliwości dwa razy mniejszej, który to sygnał jako CLK (rysunek wyżej) jest doprowadzony do 8253 do timer 0. Ten jest zaprogramowany jako dzielnik częstotliwości do finalnego uzyskania 1 kHz, który z kolei jest zapętlony na timer 1. Ten z kolei jest skonfigurowany do generowania przerwań po odliczeniu 100 impulsów. Powinno wyjść 10 przerwań na sekundę. Kawałek softu to:

Kod: Zaznacz cały

(...)
Timer8253       .equ   080h
(...)
Port8253        .struct
        TimerA  .byte
        TimerB  .byte
        TimerC  .byte
        Control .byte
                .endstruct
(...)
Tim0WaveGenMode .equ    00110110b                     
Tim1IrqGenMode  .equ    01110000b
(...)

Init_hardware                                      ;
        mvi     a , Tim0WaveGenMode                ; timer 0 jako generator
        out     Timer8253 + Port8253.Control       ; 1 kHz (2 MHz / 2000 )
        mvi     a , lo ( 2000 )                    ;
        out     Timer8253 + Port8253.TimerA        ;
        mvi     a , hi ( 2000 )                    ;
        out     Timer8253 + Port8253.TimerA        ;
        mvi     a , Tim1IrqGenMode                 ; timer 1 jako generator
        out     Timer8253 + Port8253.Control       ; przerwan
        mvi     a , lo ( 100 )                     ;
        out     Timer8253 + Port8253.TimerB        ;
        mvi     a , hi ( 100 )                     ;
        out     Timer8253 + Port8253.TimerB        ;
        mvi     a , Enable75Inter                  ;
        sim                                        ;
        ret                                        ;

gdzie
Tim0WaveGenMode to stała o wartości 00110110b [00 – dotyczy timer 0, 11 – będzie dalej zapis części młodszej i później starszej, 011 – tryb 3 – generacja fali prostokątnej i 0 – zliczanie w kodzie binarnym].
Tim1IrqGenMode to stała o wartości 01110000b do generowania przerwań po odliczeniu określonej liczby impulsów [01 – dotyczy timer 1, 11 – będzie dalej zapis części młodszej i później starszej, 000 – tryb 0 – generacja przerwania po odliczeniu liczby impulsów, 0 – zliczanie binarne].
Podglądając oscylkiem co wychodzi z timer 0, to jest spoko, nawet oscylek mówi, że jest tam 1 kHz.
rnd5-4.png

Obsługa jest zaczepiona na przerwanie połówkowe 7.5 (wymaga skonfigurowania poprzez wykonanie instrukcji sim z magiczną zawartością akumulatora). Obsługa przerwania znajduje się na adresie 3C hex.

Kod: Zaznacz cały

RST75Service                                       ;
        push    psw                                ;
        mvi     a , '*'                            ;
        call    WriteChLCD                         ;
        pop     psw                                ;
        ei                                         ;
        ret                                        ;

By wiedzieć, że weszło do przerwania, w obsłudze jest wyświetlany znak gwiazdki (sprawdziłem, obsługa używa jedynie akumulatora, więc push psw wystarczy).
No i zadziałało, ale tylko raz.
rnd5-1.png

Przyzwyczajenia przykładowo z AVR’ów mogą prowadzić w maliny, tam występuje cykliczność. Wystarczy raz „powiedzieć” i działa. A tu nie zadziałało, trzeba zapętlić, powieliłem kawałek kodu:

Kod: Zaznacz cały

RST75Service                                       ;
        push    psw                                ;
        mvi     a , '*'                            ;
        call    WriteChLCD                         ;
        mvi     a , Tim1IrqGenMode                 ; timer 1 jako generator
        out     Timer8253 + Port8253.Control       ; przerwan
        mvi     a , lo ( 100 )                     ;
        out     Timer8253 + Port8253.TimerB        ;
        mvi     a , hi ( 100 )                     ;
        out     Timer8253 + Port8253.TimerB        ;
        pop     psw                                ;
        ei                                         ;

No i pomogło skutecznie:
rnd5-2.png

Patrząc na tempo pojawiających się gwiazdek, to sądzę, że 10 przerwań na sekundę może być mało. Nie jest to jakiś wielki problem, wystarczy pozmieniać nastawy dla liczników w 8253.
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: 1274
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [8085] 16 bitowy generator przebiegów losowych

Postautor: gaweł » piątek 17 maja 2024, 23:52

rnd6_00.jpg


No to jest klawiatura

Co prawda, to klawiatura jest bez klawiatury – dokładniej bez matrycy klawiatury.
rnd6_01.png

Do „obróbki” klawiatury jest dedykowany port B i połowa portu C z układu 8255. Realizacja jest matrycowa. No i nie obyło się bez pomyłek (no jakieś zaćmienie chwilowe zaistniało). Sam napisałem w komentarzu do górnej połówki portu C, że jest output jak i również port B jest input. No to jest pytanie: dlaczego jest drabinka rezystorów w porcie C zamiast być przy porcie B?
Istniejąca drabinka R503 jest zbędna, ale nie będę jej wylutowywać, w końcu 10 kΩ to żadne obciążenie dla wyjść portu. Natomiast jest ona niezbędna w porcie B. Jak żaden przycisk w matrycy nie jest naciśnięty, to port jest niewysterowany i statystycznie chętniej czyta zera niż jedynki. Musiałem dodać ośmiobitową drabinkę.
rnd6_02.jpg

Dodanie drabinki znacząco pomogło. Daje się wczytywać klawisze z matrycy.
Co prawda można by było zamienić całość stronami: port B zrobić jako wyjściowy a port C jako wejściowy (już ma drabinkę). Rozważałem taka kombinację, ale… zrobiłem tak, że port C jest wyjściem. Uzasadnienie jest raczej proste. By oblecieć całą matrycę, to obecnie potrzebuję czerech cykli (kombinacji) na wyjściach do matrycy klawiatury i czterech cykli odczytu portu B (bo po osiem bitów) i jest obleciana cała klawiatura. Inaczej potrzebnych by było 8 cykli z odczytem stanu, gdzie należy brać pod uwagę jedynie 4 bity. Tak jest szybciej.
A w kwestii szybkości, to na razie nie zmieniałem nastaw do częstotliwości generacji przerwań od upływu czasu. Obecnie jest zbyt wolno. Podłączyłem sobie analizator logiczny do wyjść portu C i mamy:
rnd6_03.png

Czasowo to wygląda następująco: 2,5 Hz, to zdecydowanie za mało, może zaistnieć konieczność długiego przytrzymania przycisku, by procek to zatrybił. To jest do korekty.
rnd6_04.png

Klawiatura jest obrabiana w przerwaniach od upływu czasu.

Kod: Zaznacz cały

RST75Service                               ;ISR RST75Service ;
                                           ;begin
        push    psw                        ;
        push    b                          ;
        push    d                          ;
        push    h                          ;
        call    KeybService                ; KeybService ;
        mvi     a , Tim1IrqGenMode         ;
        out     Timer8253 + Port8253.Control
        mvi     a , lo ( Timer1Prescaler ) ;
        out     Timer8253 + Port8253.TimerB;
        mvi     a , hi ( Timer1Prescaler ) ;
        out     Timer8253 + Port8253.TimerB;
        pop     h                          ;
        pop     d                          ;
        pop     b                          ;
        pop     psw                        ;
        ei                                 ;
        ret                                ;end ;

Zrealizowana jest w konwencji automatu stanów.

Kod: Zaznacz cały

KeybService                                ;procedure KeybService ;
                                           ;begin
                                           ; case KbdModInstance.KbdState of
        lda    KbdModInstance + KbdModInstanceT.KbdState
        cpi    KbdIdle                     ;  KbdIdle :
        jnz    KyebSrv_2                   ;   begin
        call   AnyKeyActive                ;    if AnyKeyActive ( ) then
        cpi    0                           ;
        jz     KyebSrv_12                  ;     begin
        mvi    a , KbdWaitOnPress          ;      KbdModInstance.KbdState := KbdWaitOnPress ;
        sta    KbdModInstance + KbdModInstanceT.KbdState
        mvi    a , KbdDelayLimit           ;      KbdModInstance.KbdDelayCt := KbdDelayLimit ;
        sta    KbdModInstance + KbdModInstanceT.KbdDelayCt
        jmp    KyebSrv_13                  ;     end
KyebSrv_12                                 ;     else
                                           ;     begin
                                           ;      KbdModInstance.RowCounter := KbdModInstance.RowCounter + 1 ;
        lxi    h , KbdModInstance + KbdModInstanceT.RowCounter
        inr    m                           ;
        mov    a , m                       ;
        cpi    RowCounterLmt               ;      if KbdModInstance.RowCounter = RowCounterLmt then
        jnz    KyebSrv_14                  ;
        mvi    a , 0                       ;       KbdModInstance.RowCounter := 0 ;
        mov    m , a                       ;
KyebSrv_14                                 ;
                                           ;      if KbdModInstance.HardwKbdSetRowSrv <> nil then
        lxi     d , KbdModInstance + KbdModInstanceT.HardwKbdSetRowSrv
        lhlx                               ;
        mov     a , h                      ;
        ora     l                          ;
        jz      KyebSrv_15                 ;
                                           ;       KbdModInstance.HardwKbdSetRowSrv ( KbdModInstance.RowCounter ) ;
        lda     KbdModInstance + KbdModInstanceT.RowCounter
        call    CallHLService              ;
KyebSrv_15                                 ;
KyebSrv_13                                 ;     end ;
        jmp    KyebSrv_0                   ;   end ;
KyebSrv_2                                  ;
        cpi    KbdWaitOnPress              ;  KbdWaitOnPress :
        jnz    KyebSrv_3                   ;   begin
                                           ;    KbdModInstance.KbdDelayCt := KbdModInstance.KbdDelayCt - 1 ;
        lxi    h , KbdModInstance + KbdModInstanceT.KbdDelayCt
        dcr    m                           ;
        mov    a , m                       ;
        cpi    0                           ;    if KbdModInstance.KbdDelayCt = 0 then
        jnz    KyebSrv_21                  ;    begin
        call   AnyKeyActive                ;     if AnyKeyActive ( ) then
        cpi    0                           ;
        lxi    h , KbdModInstance + KbdModInstanceT.KbdState
        jz     KyebSrv_22                  ;
        mvi    m , KbdReady                ;      KbdModInstance.KbdState := KbdReady ;
        jmp    KyebSrv_23                  ;
KyebSrv_22                                 ;     else
        mvi    m , KbdIdle                 ;      KbdModInstance.KbdState := KbdIdle ;
KyebSrv_23                                 ;
KyebSrv_21                                 ;    end ;
        jmp    KyebSrv_0                   ;   end ;
KyebSrv_3                                  ;
        cpi    KbdReady                    ;  KbdReady :
        jnz    KyebSrv_4                   ;   begin
                                           ;    if KbdModInstance.HardwKbdReadSrv <> nil then
        lxi    d , KbdModInstance + KbdModInstanceT.HardwKbdReadSrv
        lhlx                               ;
        mov    a , h                       ;
        ora    l                           ;
        jz     KyebSrv_31                  ;
                                           ;     KbdModInstance.LastKeybInp := KbdModInstance.HardwKbdReadSrv ( ) &
        call   CallHLService               ;                                   KbdModInstance.BitMask
        lxi    h , KbdModInstance + KbdModInstanceT.BitMask
        ana    m                           ;
        sta    KbdModInstance + KbdModInstanceT.LastKeybInp
        jmp    KyebSrv_32                  ;
KyebSrv_31                                 ;    else
                                           ;     KbdModInstance.LastKeybInp := KbdModInstance.BitMask ;
        lda    KbdModInstance + KbdModInstanceT.BitMask
        sta    KbdModInstance + KbdModInstanceT.LastKeybInp
KyebSrv_32                                 ;
        mvi    a , KbdActive               ;    KbdModInstance.KeybState := KbdActive ;
        sta    KbdModInstance + KbdModInstanceT.KbdState
        jmp    KyebSrv_0                   ;   end ;
KyebSrv_4                                  ;
        cpi    KbdActive                   ;  KbdActive :
        jnz    KyebSrv_5                   ;   begin
                                           ;    DecodeKey ( KbdModInstance.LastKeybInp ) ;
        lda    KbdModInstance + KbdModInstanceT.LastKeybInp
        call   DecodeKey                   ;
        mvi    a , KbdWaitOnFree           ;    KbdModInstance.KeybState := KbdWaitOnFree ;
        sta    KbdModInstance + KbdModInstanceT.KbdState
        jmp    KyebSrv_0                   ;   end ;
KyebSrv_5                                  ;
        cpi    KbdWaitOnFree               ;  KbdWaitOnFree :
        jnz    KyebSrv_6                   ;   begin
        call   AnyKeyActive                ;    if not AnyKeyActive ( ) then
        cpi    0                           ;
        jnz    KyebSrv_61                  ;
        mvi    a , KbdIdle                 ;     KbdModInstance.KeybState := KbdIdle ;
        sta    KbdModInstance + KbdModInstanceT.KbdState
KyebSrv_61                                 ;
KyebSrv_6                                  ;   end ;
KyebSrv_0                                  ; end (* case *) ;
        ret                                ;end ;

Zostały tu wykorzystane odpowiedniki struktury[C]/rekordów[Pascal] i z tego powodu właśnie napisałem sobie sam kompilera języka asm. Tak przy okazji uwzględnione są nieudokumentowane instrukcje i są nawet zastosowane w programie. Ponieważ program działa, to można powiedzieć, że te instrukcje może i są nieudokumentowane, ale są prawdziwe.

Kod: Zaznacz cały

KbdBuffSz       .equ   4
KbdCyclicRecT   .struct             ; KbdCyclicRecT = record
        CycFr   .byte               ;                  CycFr : byte ;
        CycTo   .byte               ;                  CycTo : byte ;
        Buffer  .byte   KbdBuffSz   ;                  Buffer : array[0..KbdBuffSz-1] of char ;
                .endstruct          ;                 end (* record *) ;
;*******************************************
KbdIdle        .equ    1            ; wartosci typu
KbdWaitOnPress .equ    2            ; wyliczeniowego
KbdReady       .equ    3            ; dla automatu
KbdActive      .equ    4            ; stanu
KbdWaitOnFree  .equ    5            ;
;*******************************************
KbdModInstanceT     .struct         ; KbdCyclicRecT = record
    KbdCyclic       .byte  sizeof ( KbdCyclicRecT ) 
                            ;                KbdCyclic : KbdCyclicRecT ;
    ExtraKbdReadSrv .word   ; pointer ;      ExtraKbdReadSrv : procedure ;
    HardwKbdReadSrv .word   ; pointer ;      HardwKbdReadSrv : function : byte ;
    HardwKbdSetRowSrv .word ; pointer ;      HardwKbdSetRowSrv : procedure ( byte ) ;
    EncodeTableRow0 .word   ; pointer ;      EncodeTableRow0   : ^ char ;
    EncodeTableRow1 .word   ; pointer ;      EncodeTableRow1   : ^ char ;
    EncodeTableRow2 .word   ; pointer ;      EncodeTableRow2   : ^ char ;
    EncodeTableRow3 .word   ; pointer ;      EncodeTableRow3   : ^ char ;
    KbdState        .byte   ;                KbdState          : byte ;
    KbdDelayCt      .byte   ;                KbdDelayCt        : byte ;
    RowCounter      .byte   ;                RowCounter        : byte ;
    LastKeybInp     .byte   ;                LastKeybInp       : byte ;
    BitMask         .byte   ;                BitMask           : byte ;
               .endstruct   ;             end (* record *) ;

Taka konstrukcja dla kompilera wnosi jedynie wielkości poszczególnych pól i ofsety do nich. Jak trzeba do struktury dodać pole, to się je dodaje, kompiler to łyka (sam przeliczy ofsety):

Kod: Zaznacz cały

KbdModInstanceT                  STRUCT, size=0019 hex [25 dec], orgin=0000 hex
        KbdCyclic                        size=0006 hex [6 dec], offset=0 dec
        ExtraKbdReadSrv                  size=0002 hex [2 dec], offset=+6 dec
        HardwKbdReadSrv                  size=0002 hex [2 dec], offset=+8 dec
        HardwKbdSetRowSrv                size=0002 hex [2 dec], offset=+10 dec
        EncodeTableRow0                  size=0002 hex [2 dec], offset=+12 dec
        EncodeTableRow1                  size=0002 hex [2 dec], offset=+14 dec
        EncodeTableRow2                  size=0002 hex [2 dec], offset=+16 dec
        EncodeTableRow3                  size=0002 hex [2 dec], offset=+18 dec
        KbdState                         size=0001 hex [1 dec], offset=+20 dec
        KbdDelayCt                       size=0001 hex [1 dec], offset=+21 dec
        RowCounter                       size=0001 hex [1 dec], offset=+22 dec
        LastKeybInp                      size=0001 hex [1 dec], offset=+23 dec
        BitMask                          size=0001 hex [1 dec], offset=+24 dec

Przykładowo: pole KbdState jest +20 bajtów od początku zmiennej (KbfModInstance):

Kod: Zaznacz cały

        .org    RAMSpace
KbdModInstance          .KbdModInstanceT

Instrukcja

Kod: Zaznacz cały

        lda    KbdModInstance + KbdModInstanceT.KbdState

ładuje do akumulatora określone pole: jako adres zmiennej + ofset pola w obszarze zmiennej. Człowiek nie musi się pocić, a kompiler się nie myli.
Powyższa procedura KeyService, wynajduje wciśnięty przycisk jako scan-kod. Jest on później transponowany na znak ASCII w oparciu o cztery tablice (dla każdego wiersza oddzielnie).

Kod: Zaznacz cały

Row0EncodeTable .defb   11111110b , '1'
                .defb   11111101b , '2'
                .defb   11111011b , '3'
                .defb   11110111b , '4'
                .defb   11101111b , '5'
                .defb   11011111b , '6'
                .defb   10111111b , '7'
                .defb   01111111b , '8'
                .defb   0 , 0
;-------------------------------------------
Row1EncodeTable .defb   11111110b , 'a'
                .defb   11111101b , 'b'
                .defb   11111011b , 'c'
                .defb   11110111b , 'd'
                .defb   11101111b , 'e'
                .defb   11011111b , 'f'
                .defb   10111111b , 'g'
                .defb   01111111b , 'h'
                .defb   0 , 0
;-------------------------------------------
Row2EncodeTable .defb   11111110b , 'A'
                .defb   11111101b , 'B'
                .defb   11111011b , 'C'
                .defb   11110111b , 'D'
                .defb   11101111b , 'E'
                .defb   11011111b , 'F'
                .defb   10111111b , 'G'
                .defb   01111111b , 'H'
                .defb   0 , 0
;-------------------------------------------
Row3EncodeTable .defb   11111110b , '!'
                .defb   11111101b , '@'
                .defb   11111011b , '#'
                .defb   11110111b , '$'
                .defb   11101111b , '%'
                .defb   11011111b , '^'
                .defb   10111111b , '&'
                .defb   01111111b , '*'
                .defb   0 , 0

Moduł klawiatury jest całkowicie „abstrakcyjny”, to znaczy nie dotyka sprzętu. Za współpracę odpowiedzialne są dołączane w czasie inicjacji funkcje i procedury.

Kod: Zaznacz cały

Init_variable                       ;procedure Init_variable ;
                                    ;begin
        mvi     a , 0ffh            ; SoftKbdInit(0ffh,HardvInpKbd,HardvSetRowKbd) ;
        lxi     d , HardvInpKbd     ;
        lxi     h , HardvSetRowKbd  ;
        call    SoftKbdInit         ;
        mvi     a , 0               ; SetEncodeTable ( 0 , Row0EncodeTable ) ;
        lxi     h , Row0EncodeTable ;
        call    SetEncodeTable      ;
        mvi     a , 1               ; SetEncodeTable ( 1 , Row1EncodeTable ) ;
        lxi     h , Row1EncodeTable ;
        call    SetEncodeTable      ;
        mvi     a , 2               ; SetEncodeTable ( 2 , Row2EncodeTable ) ;
        lxi     h , Row2EncodeTable ;
        call    SetEncodeTable      ;
        mvi     a , 3               ; SetEncodeTable ( 3 , Row3EncodeTable ) ;
        lxi     h , Row3EncodeTable ;
        call    SetEncodeTable      ;
        ret                         ;end ;

gdzie

Kod: Zaznacz cały

HardvInpKbd                                ;function HardvInpKbd : byte[A] ;
                                           ;begin
        in      KBDPort + Port8255.PortB   ; HardvInpKbd := KBDPort.PortB ;
        ret                                ;end ;
.
.
.
HardvSetRowKbd                             ;procedure HardvSetRowKbd(Row[A]:byte);
                                           ;begin
        call    SetAllRowOnHigh            ; SetAllRowOnHigh ;
                                           ; case KbdModInstance.RowCounter of
        lda    KbdModInstance + KbdModInstanceT.RowCounter
        cpi    0                           ;  0 :
        jnz    HvdSeRSrv_1                 ;
        mvi    a , ClrRow0                 ;   KBDPort.PortC[4] := 0 ;
        out    KBDPort + Port8255.Control  ;
        jmp    HvdSeRSrv_0                 ;
HvdSeRSrv_1                                ;
        cpi    1                           ;  1 :
        jnz    HvdSeRSrv_2                 ;
        mvi    a , ClrRow1                 ;   KBDPort.PortC[5] := 0 ;
        out    KBDPort + Port8255.Control  ;
        jmp    HvdSeRSrv_0                 ;
HvdSeRSrv_2                                ;
        cpi    2                           ;  2 :
        jnz    HvdSeRSrv_3                 ;
        mvi    a , ClrRow2                 ;
        out    KBDPort + Port8255.Control  ;   KBDPort.PortC[6] := 0 ;
        jmp    HvdSeRSrv_0                 ;
HvdSeRSrv_3                                ;
        cpi    3                           ;  3 :
        jnz    HvdSeRSrv_4                 ;   KBDPort.PortC[7] := 0 ;
        mvi    a , ClrRow3                 ;
        out    KBDPort + Port8255.Control  ;
        jmp    HvdSeRSrv_0                 ;
HvdSeRSrv_4                                ; end (* case *) ;
HvdSeRSrv_0                                ;
        ret                                ;end ;

Po zainicjowaniu struktury, dalej już „jedzie” bazując na instrukcji pchl.
rnd6_05.jpg

Wyprowadziłem sobie klawiaturę na stykówkę, ale miałem tylko 8-pinową „smyczkę” zamiast 12-pinowej, więc klawiatura jest trochę ucięta: drutem łączę jedno z czerech wyjść z jednym z 4 wejść (zamiast 8). Program przekodowuje to na znaki i wyświetla je na LCD.
rnd6_06.jpg

No i niech ktoś mnie przekona, że w asm nie da się programować jak w Pascal’u.
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 „Retro”

Kto jest online

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