Żaśmiecona transmisja USB

To forum jest dla wszystkich pasjonatów mikrokontrolerów AVR Atmela. Wymiana doświadczeń i pomoc dla początkujących w pisaniu programów zarówno w C, Asemblerze jak i BASCOM. Zapraszam znawców tematu, aby pomogli wszystkim początkującym!
Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Żaśmiecona transmisja USB

Post autor: M@ciej » 09 gru 2013, 8:11

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?

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 09 gru 2013, 12:51

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.

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 11 gru 2013, 10:45

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.

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 11 gru 2013, 12:51

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:

Kod: Zaznacz cały

Config Timer0 = Timer , Prescale = 256  'Impuls co 1/14745600*256=0.000017343
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.).

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 11 gru 2013, 14:27

Jednak przy 16MHz i transmisji 9600 bodów błąd powinien być minimalny (0,16%). Nie chcę rozwalać całego programu, jak na razie mam:

Kod: Zaznacz cały

$crystal=16000000

config timer0=timer, prescale=64
load timer0, 250
co daje mi 1 ms podstawy czasu.

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 11 gru 2013, 15:39

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.

r-mik
-
Posty: 48
Rejestracja: 10 wrz 2011, 6:36
Lokalizacja: Warszawa

Post autor: r-mik » 14 gru 2013, 21:40

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(...)
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ą).

r-mik
-
Posty: 48
Rejestracja: 10 wrz 2011, 6:36
Lokalizacja: Warszawa

Post autor: r-mik » 14 gru 2013, 21:43

M@ciej pisze:(...)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.
Co ma timer0 do uarta?

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 17 gru 2013, 12:24

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:

Kod: Zaznacz cały

2
18
100

252
650
125
82

2
18
100
252


650
12582

2

18
100
252


650
12582
Pomiędzy danymi są "entery" (znak 10 + 13, lub jeszcze coś innego).
Procedyrę w Delphi mam b. prostą, w obsłudze klawisza jest:

Kod: Zaznacz cały

 odp:='';
 comport1.ReadStr(odp,100);
 memo1.Lines.add(odp);

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 17 gru 2013, 15:00

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:

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.
Ł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).

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 17 gru 2013, 17:27

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ę?

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 17 gru 2013, 19:50

Być może ten zapis "robi złą robotę":
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);
Powinno być:

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; 
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.
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").
Rozumiem że co interwał odmierzany TTimerem będzie wysyłany ciąg "RD", i dopiero po wysłaniu dane będą odbierane.
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;
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.

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 18 gru 2013, 7:58

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.

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 18 gru 2013, 11:08

M@ciej pisze:...Łańcuch zanieczyszczony jest nadal znakami CR+LF, po każdym parametrze, np.
Axxxx+CrLF+Byyyy+CrLf+Czz ... itd. ..
Ważne jak w programie mikrokontrolera wygląda instrukcja Print, a właściwie czy na jej końcu jest średnik czy nie.
Czy tak:

Kod: Zaznacz cały

Print 'A' ; xxxx
czy też tak:

Kod: Zaznacz cały

Print 'A' ; xxxx ;
Różnica jest zasadnicza w pierwszym przypadku wysłany będzie ciąg bajtów A+xxxx+CR+LF a w drugim A+xxxx. Znaczenie "+" jak w poście powyżej.

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 18 gru 2013, 12:09

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:

Kod: Zaznacz cały

if comport1.Connected then
 begin
  comport1.writestr('rd');
 end
 else
 begin
  showmessage('Port '+comport1.Port+' został nagle odłączony!');
 end;
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]

ODPOWIEDZ