Żaśmiecona transmisja USB
Żaśmiecona transmisja USB
Witam.
Chciałem wykonać transmisję USB-Atmega16 poprzez FT232RL (ten prostrzy). Po skonfigurowaniu wszystkiego i wgraniu sterowników do PC, udało mi sie nawiazać łaczność. Progam w procku ma za zadanie przesłać ciąg danych, które procek zmierzył. Tymczasem transmisja jest zaśmiecona, wchodzą przypadkowo znaki z akresu 0-31. Nie wiem, czy to nie problem zegara w Atmedze, bo mam 16 MHz a transmisję ustawiłem na 9600. Program na procka napisałem w Bascomie.
Do odbioru danych na PC używam Delphi 6 z komponentem CPORT.
Czy gdzieś popełniam błąd?
Chciałem wykonać transmisję USB-Atmega16 poprzez FT232RL (ten prostrzy). Po skonfigurowaniu wszystkiego i wgraniu sterowników do PC, udało mi sie nawiazać łaczność. Progam w procku ma za zadanie przesłać ciąg danych, które procek zmierzył. Tymczasem transmisja jest zaśmiecona, wchodzą przypadkowo znaki z akresu 0-31. Nie wiem, czy to nie problem zegara w Atmedze, bo mam 16 MHz a transmisję ustawiłem na 9600. Program na procka napisałem w Bascomie.
Do odbioru danych na PC używam Delphi 6 z komponentem CPORT.
Czy gdzieś popełniam błąd?
W pierwszej kolejności należało by sprawdzić gdzie występuje problem. Czy w części programowej w komputerze PC czy układzie mikroprocesorowym. Można to sprawdzić próbą nawiązania łączności z jakiego kol-wiek programu emulującego terminal. Sprawdzić co faktycznie jest wysyłane z mikrokontrolera. Oprócz częstotliwości kwarcu istotne jest wyłączenie preskalera zegara systemowego w M16 (CKDIV8). Zestaw komponentów CPORT zawiera szereg klas w tym klasę umożliwiającą wstępne parsowanie ramki bodajże TComDataPacket.
OK, posprawdzam transmisję, jednak chciałbym wiedzieć, jakiego rezonatora użyć. Gdzieś widziałem podobny układ i rezonator miał "nie okrągłą" wartość, tylko 11xyz MHz, czy ważne jest dobranie odpowiedniej częstotliwości?
Pytanie jest dla mnie istotne, bo mierzę interwały czasowe na przerwaniach (od INT zewn. + Timer0 podstawy czasu) dość dokładnie i zmiana zegara systemowego rozreguluje mi wszystko.
Preskaler mam 64 przy zegarze 16MHz, to trzeba ustawic go na 1 (bez podziału)?
Nie rozumiem o co chodzi z parsowaniem ramek.
Pytanie jest dla mnie istotne, bo mierzę interwały czasowe na przerwaniach (od INT zewn. + Timer0 podstawy czasu) dość dokładnie i zmiana zegara systemowego rozreguluje mi wszystko.
Preskaler mam 64 przy zegarze 16MHz, to trzeba ustawic go na 1 (bez podziału)?
Nie rozumiem o co chodzi z parsowaniem ramek.
Do taktowania USART'u potrzebny jest przebieg 16 razy większy niż predkość transmisji. BascomAVR automatycznie wylicza liczbę podziału zegara systemowego potrzebna do dzielenia zegara systemowego i podstawia pod rejestr UBRR. Ponieważ wartość wpisywana do UBRR jest całkowita, i nie uwzględniana jest część ułamkowa to pojawia się tzw. BaudError. Aby błąd był jak najmniejszy dla wszystkich typowych prędkości transmisji (teoretycznie 0%) stosuje się kwarce o częstotliwościach będących wielokrotnościami iloczynu 16*115200=1,843200MHz, np. 7,3728MHz..11,0592MHz..14,7456MHz. Przy stosowaniu takich kwarców rzeczywiście może być problem przy próbie odmierzania "równych interwałów" np. 1s czasu sprzętowymi Timerami co nie znaczy że się nie da, przykładowo: Jeżeli do Timer0 wpiszemy wartość 184 (72 impulsy do zliczenia) to przerwanie od przepełnienia będzie wykonywane co 1.25ms, zliczenie ośmiuset przerwań da czas jednej sekundy. Przy kwarcu 16MHz błąd jest pomijalne mały tj. około 0,16% i nie powinien mieć to znaczącego wpływu na poprawność transmisji. Jeżeli chodzi o preskaler to chodziło mi o wyłączenie domyślnie ustawionego Fusebit'u CKDIV8 który dzieli częstotliwość zegara przez 8. Komponent clasy TComDataPacket umożliwia zdefiniowanie ciągu znaków występujących na początku i końcu danych do odebrania i zdefiniowanie metod obsługujących zdarzenia gdy w danych napływających pojawi się zdefiniowany ciąg (OnCustomStart, OnCustomStop, OnPacket). Bufor odbiorczy może zawierać przypadkowe dane z których należy odfiltrować potrzebne dane. Założymy że dane napływające do bufora odbiorczego to ":0FFFCR+LF" to ciągiem startowym będzie ":" a końcowym "CR+LF" (powrót karetki i przejście do nowego wiersza). I to jest właśnie wstępne parsowanie "ramki danych" której nie należy mylić z "ramką sprzętową" przesyłającą bajt (z bitami startu, stopu itp.).
Kod: Zaznacz cały
Config Timer0 = Timer , Prescale = 256 'Impuls co 1/14745600*256=0.000017343Jednak przy 16MHz i transmisji 9600 bodów błąd powinien być minimalny (0,16%). Nie chcę rozwalać całego programu, jak na razie mam:
co daje mi 1 ms podstawy czasu.
Kod: Zaznacz cały
$crystal=16000000
config timer0=timer, prescale=64
load timer0, 250
Tak jak napisałem powyżej, częstotliwości kwarcu nie należy zmieniać ze względu na niewielki Baud error.
Nie wiem w jaki sposób sprawdzał Pan transmisję. Jeżeli z programu terminalu udało się przesyłać ciągi znaków do i z systemu mikroprocesorowego to problem tkwi w nieprawidłowym odczycie bufora odbiorczego w programie na komputerze PC. Jeżeli z programu terminalu nie udało się nawiązać poprawnej transmisji to przyczyn może być wiele. Począwszy od błędów w programie mikrokontrolera, błędnego zaprojektowania PCB (jeżeli takowy jest) zbyt długich połączeń (dużej pojemności przewodów). Warto "podsłuchać" co dzieje się na liniach TXD, RXD między mikrokontrolerem a FT232 dołączając do tych linii wejście odbiornika np.MAX232 dołączonego do jakiegokolwiek portu Com komputera PC i podgląd przesyłanych bajtów w programie emulującym terminal. W przypadku braku sprzętowego RS232 operację podsłuchu można przeprowadzić za pomocą modułu z FT232 (oczywiście mając pewność że moduł jest sprawny) dołączając jego wejście RXD do testowanej linii.
Nie wiem w jaki sposób sprawdzał Pan transmisję. Jeżeli z programu terminalu udało się przesyłać ciągi znaków do i z systemu mikroprocesorowego to problem tkwi w nieprawidłowym odczycie bufora odbiorczego w programie na komputerze PC. Jeżeli z programu terminalu nie udało się nawiązać poprawnej transmisji to przyczyn może być wiele. Począwszy od błędów w programie mikrokontrolera, błędnego zaprojektowania PCB (jeżeli takowy jest) zbyt długich połączeń (dużej pojemności przewodów). Warto "podsłuchać" co dzieje się na liniach TXD, RXD między mikrokontrolerem a FT232 dołączając do tych linii wejście odbiornika np.MAX232 dołączonego do jakiegokolwiek portu Com komputera PC i podgląd przesyłanych bajtów w programie emulującym terminal. W przypadku braku sprzętowego RS232 operację podsłuchu można przeprowadzić za pomocą modułu z FT232 (oczywiście mając pewność że moduł jest sprawny) dołączając jego wejście RXD do testowanej linii.
11.0952. Kwarc tzw uartowy, jak np 1.8432 czy 18.432MHz. Takie kwarce dają "zerowy" błąd przy typowych prędkościach transmisji (podzielnik jest liczbą całkowitą).M@ciej pisze:OK, posprawdzam transmisję, jednak chciałbym wiedzieć, jakiego rezonatora użyć. Gdzieś widziałem podobny układ i rezonator miał "nie okrągłą" wartość, tylko 11xyz MHz, c(...)
Posprawdzałem transmisję.
Program na mikrokontrolerze oczekuje ciąg znaków "st", jeśli go znajdzie, wysyła (instrukcja Print):
2
18
100
252
650
12582
Gdy wpisuję w terminalu Bascoma ów frazes "st", dostaję bezbłędną odpowiedź, więc to CPORT w Delphi coś kiełbasi. Potrafi np. liczbę 18 wyświetlić jako 1 i oddzielnie 8, a do innej wstawi jakiś "krzak".
Oto kilka odpowiedzi zwróconych przez Delphi:
Pomiędzy danymi są "entery" (znak 10 + 13, lub jeszcze coś innego).
Procedyrę w Delphi mam b. prostą, w obsłudze klawisza jest:
Program na mikrokontrolerze oczekuje ciąg znaków "st", jeśli go znajdzie, wysyła (instrukcja Print):
2
18
100
252
650
12582
Gdy wpisuję w terminalu Bascoma ów frazes "st", dostaję bezbłędną odpowiedź, więc to CPORT w Delphi coś kiełbasi. Potrafi np. liczbę 18 wyświetlić jako 1 i oddzielnie 8, a do innej wstawi jakiś "krzak".
Oto kilka odpowiedzi zwróconych przez Delphi:
Kod: Zaznacz cały
2
18
100
252
650
125
82
2
18
100
252
650
12582
2
18
100
252
650
12582
Procedyrę w Delphi mam b. prostą, w obsłudze klawisza jest:
Kod: Zaznacz cały
odp:='';
comport1.ReadStr(odp,100);
memo1.Lines.add(odp);
Metoda odczytująca znaki przychodzące powinna być wywołana w obsłudze zdarzenia ComPort (Events) OnRxChar, jak w poniższym działającym kodzie:
Łańcuch do wysłania należy wpisać w Edit1. Program przed wysłaniem dodaje na końcu łańcucha znaki CR+LF. Należy mieć świadomość że tego typu obsługa jest stosunkowo wolna, nadaje się jednak do transmisji typu zapytanie-odpowiedź. Przypisanie Memo1.Text:=Memo1.Text+sTmp jest mało eleganckie ale na potrzeby przykładu wystarczające (Można użyć Memo1.Add() gdy dane przychodzące są parsowane). Gdy istnieje potrzeba odczytu danych napływających nie koniecznie na zapytanie ,konieczne jest stosowanie obsługi portu zrealizowanej w bardziej wydajny sposób, wielowątkowo. Przypisanie parametrów transmisji do struktury DCB (w Delphi przedefiniowany typ TDcb), następnie otwarcie portu metodą z API CreateFile itd. Czytanie z portu w jednym wątku i zapis (wysyłanie) w drugim. Polecam zapoznanie się z metodami opisanymi w pomocy Delphi (Windows SDK).
Kod: Zaznacz cały
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, CPort, Buttons;
type
TForm1 = class(TForm)
ComPort1: TComPort;
Memo1: TMemo;
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure BitBtn1Click(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
procedure ComPort1RxChar(Sender: TObject; Count: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//------------------------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
BitBtn2.Enabled:=ComPort1.Connected;
end;
//------------------------------------------------------------------------------
procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer); //Obsługa Events'u OnRxChar
Var
sTmp: string;
begin
ComPort1.ReadStr(sTmp,Count);
Memo1.Text:=Memo1.Text+sTmp;
end;
//------------------------------------------------------------------------------
procedure TForm1.BitBtn2Click(Sender: TObject);
var
TxStr: string;
begin
TxStr:=Edit1.Text+#13#10;
if ComPort1.Connected then
ComPort1.WriteStr(TxStr);
end;
//------------------------------------------------------------------------------
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
Memo1.Clear;
if ComPort1.Connected then begin
ComPort1.Close;
BitBtn1.Caption:='Connect';
end else begin
ComPort1.Open;
BitBtn1.Caption:='Disconnect';
end;
BitBtn2.Enabled:=ComPort1.Connected;
end;
//EndE--------------------------------------------------------------------------
end.
Przepraszam, w poprzednim opisie trochę namieszałem. Klawiszem wysyłam na port ciąg znaków "RD", a w evencie OnRXChar odczytuję dane.
Wszystko się rozjeżdża, gdyż COmPort działa niezależnie od klawisza, i procedura "Memo1.lines.Add powoduje taką kaszanę.
Już mi się udaje odebrać w miarę dobrą ramkę, przy czym dane będą odbierane z urządzenia na bieżąco, co ustawiany Timerem interwał czasu (timerem "Dalphi-owym").
Mam teraz problem z czyszczeniem buforu, bo po kliknięciu i wysłaniu "RD" bufor czyści się jakby w trakcie odbioru, pomimo że w evencie najpierw są odczytywane dane, potem czyszczenie buforu.
Ramkę zrobiłem tak: AxxxxByyyyCzzDuuEvvFww, gdzie A, B, C to znaki rozpoznawcze, po których występują dane xxxx,yyyy WORD i zz,vv,ww BYTE. Długości są różne, dlatego trzeba było włożyć to w ramkę. Nie wiem, czy dobrze robię?
Wszystko się rozjeżdża, gdyż COmPort działa niezależnie od klawisza, i procedura "Memo1.lines.Add powoduje taką kaszanę.
Już mi się udaje odebrać w miarę dobrą ramkę, przy czym dane będą odbierane z urządzenia na bieżąco, co ustawiany Timerem interwał czasu (timerem "Dalphi-owym").
Mam teraz problem z czyszczeniem buforu, bo po kliknięciu i wysłaniu "RD" bufor czyści się jakby w trakcie odbioru, pomimo że w evencie najpierw są odczytywane dane, potem czyszczenie buforu.
Ramkę zrobiłem tak: AxxxxByyyyCzzDuuEvvFww, gdzie A, B, C to znaki rozpoznawcze, po których występują dane xxxx,yyyy WORD i zz,vv,ww BYTE. Długości są różne, dlatego trzeba było włożyć to w ramkę. Nie wiem, czy dobrze robię?
Być może ten zapis "robi złą robotę":
Istotny jest licznik przekazywany w parametrze metody, ile bajtów jest do odebrania.
Pomysł ze znacznikami jest bardzo dobry, i tu warto, choć nie jest to wymagane zastosować klasę TComDataPacket gdzie istotny będzie pierwszy znacznik i ostatni (na końcu odbieranego ciągu). W pliku pomocy do komponentów ComPort znajdują się stosowne przykłady.
Z ostatniej chwili!
Rzeczywiście podstawianie przez metodę "memo1.Lines.add(odp)" może gubić znaki.
Poprawna obsługa (sprawdzone, wstępne parsowanie) może wyglądać tak:
Zmienna RxStr musi być zadeklarowana globalnie np. jako pole prywatne lub publiczne głównej klasy.
Warunkiem działania jest aby zwracany przez mikrokontroler string zawierał na końcu znaki CR+LF.
Znaki występujące po CR+LF zapamiętywane są w zmiennej RxStr do czasu odebrania następnych CR+LF.
Powinno być:M@ciej pisze: Procedyrę w Delphi mam b. prostą, w obsłudze klawisza jest:Kod: Zaznacz cały
odp:=''; comport1.ReadStr(odp,100); memo1.Lines.add(odp);
Kod: Zaznacz cały
procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer); //Obsługa Events'u OnRxChar
Var
sTmp: string;
begin
sTmp:='';
ComPort1.ReadStr(sTmp,Count);
//Tu parsować sTmp;
end; Pomysł ze znacznikami jest bardzo dobry, i tu warto, choć nie jest to wymagane zastosować klasę TComDataPacket gdzie istotny będzie pierwszy znacznik i ostatni (na końcu odbieranego ciągu). W pliku pomocy do komponentów ComPort znajdują się stosowne przykłady.
Rozumiem że co interwał odmierzany TTimerem będzie wysyłany ciąg "RD", i dopiero po wysłaniu dane będą odbierane.M@ciej pisze:Już mi się udaje odebrać w miarę dobrą ramkę, przy czym dane będą odbierane z urządzenia na bieżąco, co ustawiany Timerem interwał czasu (timerem "Dalphi-owym").
Z ostatniej chwili!
Rzeczywiście podstawianie przez metodę "memo1.Lines.add(odp)" może gubić znaki.
Poprawna obsługa (sprawdzone, wstępne parsowanie) może wyglądać tak:
Kod: Zaznacz cały
procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer);
Var
sTmp: string;
begin
//sTmp:='';
ComPort1.ReadStr(sTmp,Count);
RxStr:=RxStr+sTmp;
if Pos(#13#10,RxStr)<>0 then begin
Memo1.Lines.Add(Copy(RxStr,1,Pos(#13#10,RxStr)-1));
RxStr:=Copy(RxStr,Pos(#13#10,RxStr)+2,Length(RxStr)-(Pos(#13#10,RxStr)-1));
end;
end;Warunkiem działania jest aby zwracany przez mikrokontroler string zawierał na końcu znaki CR+LF.
Znaki występujące po CR+LF zapamiętywane są w zmiennej RxStr do czasu odebrania następnych CR+LF.
Jak na razie udało mi się dojść do następujacych wniosków:
- Procedura OnRxChar wywoływana jest wielokrotnie (u mnie 137 razy) i próba operacji na budowanym dopiero łańcuchu z danymi owocuje kaszaną
- Obiekt Memo1 uzyty został tylko do celów obrazowych i nie będzie zastosowany we właściwym programie
- Wstawiłem do ramki na początku ciag znaków 'BG' a na końcu 'EN', dzięki temu wiem, kiedy OnRxChar skończył odczytywać całą ramkę i zabieram się za wyodrębnianie z niej danych.
Łańcuch zanieczyszczony jest nadal znakami CR+LF, po każdym parametrze, np.
Axxxx+CrLF+Byyyy+CrLf+Czz ... itd. Oczywiście znaków "+" tam nie ma, napisałem tylko tutaj dla lepszej czytelności.
- Procedura OnRxChar wywoływana jest wielokrotnie (u mnie 137 razy) i próba operacji na budowanym dopiero łańcuchu z danymi owocuje kaszaną
- Obiekt Memo1 uzyty został tylko do celów obrazowych i nie będzie zastosowany we właściwym programie
- Wstawiłem do ramki na początku ciag znaków 'BG' a na końcu 'EN', dzięki temu wiem, kiedy OnRxChar skończył odczytywać całą ramkę i zabieram się za wyodrębnianie z niej danych.
Łańcuch zanieczyszczony jest nadal znakami CR+LF, po każdym parametrze, np.
Axxxx+CrLF+Byyyy+CrLf+Czz ... itd. Oczywiście znaków "+" tam nie ma, napisałem tylko tutaj dla lepszej czytelności.
Ważne jak w programie mikrokontrolera wygląda instrukcja Print, a właściwie czy na jej końcu jest średnik czy nie.M@ciej pisze:...Łańcuch zanieczyszczony jest nadal znakami CR+LF, po każdym parametrze, np.
Axxxx+CrLF+Byyyy+CrLf+Czz ... itd. ..
Czy tak:
Kod: Zaznacz cały
Print 'A' ; xxxxKod: Zaznacz cały
Print 'A' ; xxxx ;Nie mam średnika i wchodzą obydwa znaki CR+LF, które potem w Delphi filtruję.
Z przekazywaniem danych sobie poradziłem, niestety wynikł kolejny problem:
Wszystko działa ładnie tylko gdy kabel podłączony jest do USB i do urządzenia. Gdy kabel zostanie wypięty, program od razu sie wysypuje. Próba wyeliminowania błędu jak:
nic nie daje, gdyż zanim dojdzie do tego warunku, Delphi zatrzymuje program z blędem. Nie wiem, jak uodpornic program na brak podłączenia.[/code]
Z przekazywaniem danych sobie poradziłem, niestety wynikł kolejny problem:
Wszystko działa ładnie tylko gdy kabel podłączony jest do USB i do urządzenia. Gdy kabel zostanie wypięty, program od razu sie wysypuje. Próba wyeliminowania błędu jak:
Kod: Zaznacz cały
if comport1.Connected then
begin
comport1.writestr('rd');
end
else
begin
showmessage('Port '+comport1.Port+' został nagle odłączony!');
end;