USBasp Programator AVR

Tydzień temu sfinalizowałem pracę nad moim nowym projektem. Jest nim programator uC z rodziny AVR oparty na USBasp.  Programator ten współpracuje z wszystkimi normalnymi systemami operacyjnymi. Jego głównymi zaletami są:

  • nie wymaga LPT, gdyż korzysta z USB. Co raz trudniej o laptopa z wyprowadzonym portem LPT, tak więc jest to wyjście w stronę użytkowników z komputerami przenośnymi.
  • umożliwia ZNACZNIE szybsze wgrywanie firmware niż programatory LPT.
  • jest mały (:

Programator bezproblemowo współpracuje z popularnym oprogramowaniem avrdude. Na płytce umieszczono trzy zworki. Pierwsza z nich (piny 1-2) służy do wyboru prędkości programowania, zwarcie oznacza programowanie układów taktowanych zegarem < 1.5 MHz. Zwora druga (piny 3-4) służy do umożliwienia zaprogramowania samego programatora, co jest przydatne, gdyż prace nad firmware ciągle trwają. Zwora (5-6) służy do zasilania układu docelowego z programatora. Należy pamiętać, że napięcie zasilające pobierane jest wprost z portu USB, a zatem obciążenie nie może być większe niż 0,5 A. Poniżej przedstawiam zdjęcia programatora.

Generator sygnałów VGA w VHDL

W dzisiejszym wpisie zaprezentuję jak wykonać generator sygnałów VGA dla trybu 640×480@60Hz. Jest to element umożliwiający wyświetlanie czegokolwiek na matrycy/kineskopie monitora, także, o ile zajmujemy się projektowaniem GPU, warto poświęcić mu trochę uwagi (:

Każdy monitor ze złączem w standardzie VGA wymaga podawania pięciu sygnałów:

  • R,G,B – składowe kolorów (“treść” obrazu)
  • v_sync – synchronizacja pionowa
  • h_sync – synchronizacja pozioma

Poszczególne sygnały muszą spełniać pewne normy czasowe, które zostały opisane (również dla innych rozdzielczości) na tej stronie. Obraz jest wyświetlany z częstotliwością 25MHz (jest to wartość przybliżona, rzeczywista jest nieco większa, ale nie ma to znaczenia) na pojedynczy piksel, przy czym należy zauważyć, iż całkowity obraz ma wymiary 800×525px, z czego obszar aktywny zajmuje oczywiście 640×480px. Czas spędzony w pozostałym obszarze jest używany do cofania plamki do pozycji początkowej oraz przechodzenia do nowej linii. Jest to “pamiątka” po erze monitorów CRT które wyświetlały obraz z użyciem działa elektronowego, o czym więcej tutaj.

Wyróżniamy cztery podstawowe etapy synchronizacji poziomej:

  1. 640 px – obszar aktywny (Horizontal Display, HD)
  2. 16 px – obszar wygaszania plamki i “przygotowania do powrotu” (Horizontal Front Porch, HF)
  3. 96 px – obszar powrotu plamki (Horizontal Return, HR)
  4. 48 px – obszar zapalania plamki i “przygotowania do rysowania” (Horizontal Back Porch, HB)

Sumarycznie etapy te trwają 800px. Sygnał synchronizacji poziomej powiniem mieć wartość ‘0′ podczas trwania etapu nr 3 oraz wartość ‘1′ we wszystkich pozostałych.

Analogicznie, dla sychrnonizacji pionowej, mamy:

  1. 480 li – obszar aktywny (Vertical Display, VD)
  2. 10 li – (Vertical Front Porch, VF)
  3. 33 li – (Vertical Back Porch, VB)
  4. 2 li – powrót plamki (Vertical Return, VR)

Sumarycznie 525 linii. Sygnał synchronizacji pionowej powinien mieć wartość ‘0′ podczas etapu nr 3, w pozostałych wypadkach powinien mieć wartość ‘1′. Posileni takim ładunkiem teorii możnemy przystąpić do wcielania naszego generatora w życie.

Pierwszym krokiem jest dobór sygnałów wejściowych i wyjściowych naszego generatora. Ja rozpisałem je następująco:

entity vga_640_gen is
port (  – common signals
clk, rst : in std_logic;
– vga signals
pix_x, pix_y : out std_logic_vector(9 downto 0);
hs, vs, blank, pix_clk : out std_logic);
end vga_640_gen;

Sygnały clk, i rst to nic innego jak wejście zegara, który na płycie Nexys 2 ma częstotliwość 50MHz, tak więc po podzieleniu na 2 idealnie nadaje się na zegar pikseli 25MHz oraz sygnał resetu całego generatora. 10-bitowe sygnały pix_x oraz pix_y zawierają informację odnośnie aktualnie wyświetlanego piksela. Linie hs i vs to linie synchronizacji odpowiednio: poziomej i pionowej. Sygnał blank informuje nas o tym czy generator znajduje się w obszarze aktywnym czy też nie, zaś pix_clk to wyjście zegara pikseli 25Mhz.

Przystępujemy do implementacji architektury generatora:

architecture Behavioral of vga_640_gen is

– signals for pixel clock generation
signal pix_clk_reg, pix_clk_next : std_logic;

signal blank_reg, blank_next : std_logic;

– internal signals
signal h_tick, v_tick : std_logic;
signal h_sync_reg, h_sync_next : std_logic;
signal v_sync_reg, v_sync_next : std_logic;

– counters for horizontal and vertical sync
signal v_count_reg, v_count_next : std_logic_vector(9 downto 0);
signal h_count_reg, h_count_next : std_logic_vector(9 downto 0);

– horizontal and vertical syncs generating constants
constant HD : integer := 640;
constant HF : integer := 16;
constant HB : integer := 48;
constant HR : integer := 96;
constant VD : integer := 480;
constant VF : integer := 10;
constant VB : integer := 33;
constant VR : integer := 2;

begin

– update process
update : process (clk, rst)
begin
if (rst = ‘1′) then
h_count_reg &lt;= (others =&gt; ‘0′);
v_count_reg &lt;= (others =&gt; ‘0′);

pix_clk_reg &lt;= ‘0′;
h_sync_reg &lt;= ‘0′;
v_sync_reg &lt;= ‘0′;

blank_reg &lt;= ‘0′;
elsif rising_edge(clk) then
pix_clk_reg &lt;= pix_clk_next;

h_count_reg &lt;= h_count_next;
v_count_reg &lt;= v_count_next;

h_sync_reg &lt;= h_sync_next;
v_sync_reg &lt;= v_sync_next;

blank_reg &lt;= blank_next;
end if;
end process update;

– clock generation
pix_clk_next &lt;= not pix_clk_reg;

– blank generation
blank_next &lt;= ‘0′ when (h_count_reg &lt; HD) and (v_count_reg &lt; VD) else ‘1′;

– horizontal and vertical tick signal generation
h_tick &lt;= ‘1′ when h_count_reg = (HD+HF+HB+HR-1) else ‘0′;
v_tick &lt;= ‘1′ when v_count_reg = (VD+VF+VB+VR-1) else ‘0′;

– sync signals generation
h_sync_next &lt;= ‘1′ when (h_count_reg &gt;= (HD+HF)) and
(h_count_reg &lt;= (HD+HF+HR-1)) else ‘0′;
v_sync_next &lt;= ‘1′ when (v_count_reg &gt;= (VD+VF)) and
(v_count_reg &lt;= (VD+VF+VR-1)) else ‘0′;

– horizontal counter process
h_counter : process (pix_clk_reg, h_tick, h_count_reg)
begin
if pix_clk_reg = ‘1′ then
if (h_tick = ‘1′) then
h_count_next &lt;= (others =&gt; ‘0′);
else
h_count_next &lt;= h_count_reg + 1;
end if;
else
h_count_next &lt;= h_count_reg;
end if;
end process h_counter;

– vertical counter process
v_counter : process (pix_clk_reg, h_tick, v_tick, v_count_reg)
begin
if pix_clk_reg = ‘1′ and h_tick = ‘1′ then
if (v_tick = ‘1′) then
v_count_next &lt;= (others =&gt; ‘0′);
else
v_count_next &lt;= v_count_reg + 1;
end if;
else
v_count_next &lt;= v_count_reg;
end if;
end process v_counter;

– pixel clock signal
pix_clk &lt;= pix_clk_reg;

– blank signal
blank &lt;= blank_reg;

– generate horizontal sync signal
hs &lt;= h_sync_reg;
– generate vertical sync signal
vs &lt;= v_sync_reg;

– output pixel counter signals
pix_x &lt;= h_count_reg;
pix_y &lt;= v_count_reg;

end Behavioral;

Proces update odpowiada za przypisywanie nowych wartości do wszystkich rejestrów, jakie zostały użyte w implementacji. Dzięki temu mamy pełną synchronizację wewnątrz bloku generatora VGA i możliwość pracy całego FPGA z wysoką częstotliwością taktowania. Zegar jest uzyskany poprzez negowanie “samego siebie” (linia 56) z częstotliwością zegara, co w rezultacie owocuje przebiegiem o częstotliwości 25Mhz. Proces h_count odpowiada za aktualizację wartości licznika synchronizacji poziomej, dla wartości mniejszych niż 800 następuje zwiększenie wartości licznika o 1, dla wartości 800 następuje wyzerowanie licznika. Podobnie funkcjonuje proces v_count tyle że on jest synchronizowany przejściem do nowej linii. Sygnał blank ma wartość ‘0′ gdy aktualnie wyświetlany piksel należy do obszaru aktywnego (linia 59). Sygnały hs i vs są generowane zgodnie z “przepisem” podanym wcześniej (linie 66 i 68).

Test generatora

Aby przetestować generator należałoby spróbować wyświetlić jakikolwiek obraz na monitorze. Najprostszym sposobem jest wygenerowanie tzw. xor pattern, tzn takiego obrazu, gdzie kolor każdego piksela ma wartość obliczoną jako xor jego współrzędnej x oraz y. Przykładowy plik testowy wygląda więc następująco:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity test is
port( clk : in std_logic;

vga_hs, vga_vs : out std_logic;
vga_rgb : out std_logic_vector(7 downto 0));
end test;

architecture Behavioral of test is
signal pix_x, pix_y : std_logic_vector(9 downto 0);
signal blank : std_logic;
begin

vga_gen : entity vga_640_gen
port map(clk =&gt; clk, rst =&gt; ‘0′,
pix_x =&gt; pix_x, pix_y =&gt; pix_y,
hs =&gt; vga_hs, vs =&gt; vga_vs, blank =&gt; blank, pix_clk =&gt; open);

vga_rgb &lt;= pix_x(7 downto 0) xor pix_y(7 downto 0) when blank = ‘0′ else x"00";

end Behavioral;

Warto zauważyć, że sygnały wyjściowe vga_rgb powinny mieć wartość zera logicznego w momencie, gdy plamka znajduje się poza aktywnym obszarem wyświetlania (w tym celu z generatora wyprowadzono sygnał blank). Jeśli tego nie zrobimy, to niektóre monitory zaczynają wariować, obraz jest przesunięty i słychać syczenie i gwizdy (:. Kod wgrany do FPGA, znajdującego się na płytce Nexys 2, daje takie oto efekty:

monitor

FPGA Nexys 2 Board

Nie dawniej niż miesiąc temu w moje ręce wpadła płyta ewaluacyjna Nexys 2 z układem FPGA typu Spartan XC3S500E. Płytę tę otrzymałem w celu realizacji części praktycznej mojej pracy magisterskiej, której tematem jest zaprojektowanie rdzenia procesora graficznego z akceleracją 2D. Płytka zawiera:

  • FPGA: XC3s500E,
  • RAM: 128Mb (8M x 16b) Micron Pseudo-Static RAM,
  • FLASH: 128 Mb Intel StrataFlash,
  • złącze RS232,
  • złącze VGA z przetwornikiem DAC 8bit,
  • 4 przyciski, 8 przełączników,
  • 8 diód led, 4 wyświetlacze 7-segmentowe,
  • złącze rozszerzeń.

Wszystko byłoby wspaniale, gdyby nie fakt że w roli pamięci zastosowano Pseudo Static RAM, a nie po prostu konwencjonalny Static RAM z jakimś sensownym czasem dostępu na poziomie 15 ns. Użyta kość odpowiada na rządania odczytu/zapisu dopiero po ok 70-80 ns, co jest wysoce niezadowalające w momencie, gdy chcemy wykorzystać ową pamięć jako bufor na dane graficzne… Pewnikiem będę musiał wykonać swoją płytkę zawierającą FPGA z pamięcią nieulotną (oznaczenia tych układów Xilinxa, które posiadają wbudowaną pamięć nieulotną kończą się na “AN”) oraz szybki statyczny RAM (kości 1Mbit są dostępne np. w tme.pl).

Niebawem zamieszczę informację o postępach moich prac nad GPU. Póki co ograniczę się do stwierdzenia, iż posiadam zaprojektowany RAMDAC, układ rysowania wypełnionych prostokątów, arbitera wewnętrznej magistrali, bank rejestrów konfiguracyjnych GPU oraz kilka innych przydatnych bajerów. Całość testuję  z użyciem złącza VGA dostępnego na płycie. Poniżej zamieszczam zdjęcie całości.

nexys2

GPS Tracker

Niedawno zakończyłem prace nad urządzeniem, które nazwałem GPS Tracker. Urządzenie to służy do raportowania pozycji samochodów ciężarowych lub osobowych, z wykorzystaniem pozycji z GPS, przesyłanej przez sieć GSM. Wykonanie zostało zlecone przez indywidualnego klienta.

Urządzenie wykorzystuje moduł SIM300D do wysyłania danych przez GPRS. Sam moduł jest rozwiązaniem dedykowanym zarówno do transmisji danych z wykorzystaniem wspomnianego GPRS jak również do transmisji głosu. Komunikacja z modułem odbywa się przy wykorzystaniu RS232 w pełnej 9-sygnałowej wersji. Umożliwia to sprawowanie kontroli nad szybkością transmisji. Do modułu podłączono złącze karty SIM z wyrzutnikiem. Antena jest przyłączona za pomocą gniazda SMA. Moduł został zakupiony w TME.

W roli uC zastosowałem AT91SAM7S64, gdyż takowy zalegał u mnie w warsztacie. Firmware został napisany z wykorzystaniem przerwań i timerów (i innych fajnych rzeczy..), tak aby uniknąć stosowania wielu procedur blokujących. Taka metoda pisana firmware w ostateczności przyczynia się do zmniejszenia awaryjności całego urządzenia, co jest szczególnie istotne w przypadku urządzenia do którego dostęp będzie później utrudniony.

Wykorzystany moduł GPS to FGPMMOSL1 zakupiony z Maritexu. Wybór został podyktowany tylko i wyłącznie ceną (: jednak zapewniam, iż moduł spisuje się bardzo dobrze, wraz z aktywną anteną można obierać sygnał z satelity w kamienicy (5m od okna), w której mieszkam. Moduł jest gotowy do pracy po ok. 30 sekundach od włączenia zasilania. Prędkość transmisji wynosi 9800 baud, jest więc dwukrotnie większa od standardowej prędkości protokołu NMEA. Z racji tego, że zastosowany AT91SAM7 dysponuje tylko dwoma USARTami, z czego jeden (USART1) jest podłączony do modułu GSM to linia TXD została wykorzystana do wysyłania komunikatów debugujących podczas gdy RXD jest podłączona do GPS. Takie współdzielenie linii jednego USART powoduje to, że debug pracuje z taką samą prędkością co GPS, zatem większa prędkość GPS oznacza w ostateczności przyspieszenie transmisji debugu.

Urządzenie zostało również wyposażone w złącze USB, które umożliwia konfigurację parametrów pracy całości. Firmware zapewnia obsługę urządzenia klasy CDC będącego, tym przypadku, zwykłym portem szeregowym. Warto zaznaczyć, że nie posiłkowałem się biblioteką Atmela dla USB, lecz klasę CDC napisałem całkowicie samodzielnie. (Nie jest to jedyna rzecz jaką implementowałem samodzielnie, nawet printf do debugu jest mój, co prawda nieco zubożony, ale taki właśnie miał być (: ).

Krótka specyfikacja:

  • Wymiary płytki: 64×50 [mm]
  • Wysokość najwyższego elementu: 12 [mm]
  • Średni pobór prądu: ~40 [mA]
  • Napięcie zasilania: od 5 do 35 [V]
  • Złącza: USB, Zasilanie, 2x SMA (GPS, GSM), JTAG (przystosowany do pracy z moim programatorkiem).
  • 3 Diody sygnalizacyjne (NET – GPRS, PWR  – Zasilanie, STAT – Status pracy)

Podstawowe problemy techniki

Najważniejszym elementem każdego urządzenia elektronicznego jest zasilacz, gdyż jak wiadomo 90% urządzeń działa lepiej gdy są poprawnie zasilone (:. Należy mieć na uwadze fakt, że podczas nadawania komunikatu przez moduł GSM pobór prądu może w szczycie osiągnąć 2A, dlatego konstrukcja zasilacza musi być dokładnie przemyślana. Ponadto należy uwzględnić to, że napięcie w instalacji ciężarówki wynosi 24V, zaś w samochodach osobowych występuje 12V. Wybór padł na układ LM2594 będący przetwornicą step-down. Wykorzystano wersję nieregulowaną, o napięciu wyjściowym wynoszącym 5V. Wykorzystanie wersji 3,3V nie wchodziło w grę, gdyż moduł SIM300D do poprawnej pracy wymaga przynajmniej 3,6V. Napięcie z przetwornicy jest obniżane do poziomu ok 3,8-3,9V za pomocą układu LM1117-3,3V, którego wyprowadzenie GND jest dołączone do masy urządzenia za pośrednictwem diody krzemowej. Aby umożliwić chwilowe pobranie dużych prądów urządzenie zostało wyposażone w baterię kondensatorów tantalowych 330uF. Na płytce przewidziano miejsce dla trzech takich kondensatorów, jednak empirycznie stwierdziłem, że dwa w zupełności wystarczą i moduł nie uskarża się na ‘under voltage operation’. Warto zwrócić uwagę na to czy scalak przetwornicy pracuje ze stałą czy zmienną częstotliwością kluczowania dławika. W przypadku zmiennej częstotliwości może (w momencie dużych obciążeń) dochodzić do efektów dzwiękowych w dławiku (:. Wybrany układ pracuje z częstotliwością stałą, wynoszącą 150kHz, tak więc zdecydowanie ponad pasmem akustycznym.

Bardzo ważnym aspektem jest zapewnienie odpowiedniej grubości ścieżek doprowadzających napięcie do modułu GSM. W przypadku za wąskich ścieżek i dużych poborów prądu następują niepożądane spadki napięć.

Jakie są potencjalne trudności podczas realizacji takiego projektu?

Pierwszą trudnością jest poprawna implementacja procedur obsługujących błędy pracy modułu GSM. Jeżeli urządzenie wisi w momencie gdy padł zasięg, to znaczy dokładnie tyle, że programista nie zadbał o jakość firmware. Braki zasięgu się zdarzają, szczególnie w sieci Orange.

Następnym problemem są ‘ginące’ pakiety w sieci GPRS. Należy zadbać o mechanizm potwierdzeń dostarczenia pakietów z danymi. Można wykorzystać protokół TCP zamiast UDP, ale nastręcza to dodatkowych problemów w momencie utraty zasięgu (trzeba na nowo ustanawiać połączenie socketów, a na serwerze socket cały czas zostaje otwarty) Na dobrą sprawę wystarczy UDP z prostym komunikatem potwierdzającym ze strony serwera.

Należy buforować dane, czasem utrata zasięgu może potrwać kilka minut, nie można dopuścić by dane nam wyparowały! w momencie powrotu zasięgu należy wysłać zgromadzoną paczkę danych. Serwer musi obsługiwać komunikaty zawierające informacje o kilku lokalizacjach. Komplikuje to protokół, ale tylko nieznacznie.

W przypadku utraty sygnału GPS, należy to sygnalizować do serwera jakimś prostym komunikatem (po co tracić pieniądze na nadmiarową ilość przesyłanych danych). Co nam daje taka informacja? Serwer wie, że urządzenie żyje, a problemy są przejściowe. Ponadto może wyświetlić komunikat o problemach z GPS.

Należy zaimplementować obsługę PIN. Kierowcy mają ciągoty do korzystania z kart SIM, znajdujących się w urządzeniu do wykonywania prywatnych rozmów (:.

Zaprojektowane urządzenie współpracuje aktualnie z aplikacją wykorzystującą Google Maps. Jest to najprostsza i dość niedoskonała metoda wizualizacji zgromadzonych informacji (niedokładność map). Warto zatem zastanowić się nad zakupem innych mapek.

Pytania? Wątpliwości?

To tyle ode mnie, jeśli ktokolwiek z Czytelników jest zainteresowany projektem to proszę o kontakt mailowy. Możliwe jest wykonanie większej liczby urządzeń, na specjalne zamówienie. Na zakończenie zamieszczam zdjęcie strony wierzchniej (GSM i GPS są przylutowane od spodu):

gpsgsm_1gpsgsm_2

GCC i funkcje o zmiennej liczbie argumentów

Niniejszy wpis jest poświęcony zagadnieniu wykorzystania funkcji o zmiennej liczbie argumentów w swoich projektach. Najpopularniejszym przykładem funkcji posiadającą taką właściwość jest funkcja printf, której prototyp wygląda mniej-więcej tak:

int printf(char *fmt, ...);

Notacja ‘…’ w polu argumentów funkcji informuje kompilator o tym, iż funkcja może przybierać dowolną liczbę argumentów. Powstaje podstawowe pytanie:  w jaki sposób takie argumenty są przekazywane do funkcji skoro ich liczba nie jest wprost określona? Jak się do nich odwoływać?

Twórcy kompilatora GCC rozwiązali sprawę przekazując takie parametry w formie obszaru pamięci zorganizowanego w specyficzny sposób. Na potrzeby rozważań ustalmy, iż funkcja, którą będziemy badać  będzie posiadała następujący prototyp:

void foo(char *arg, ...);

Jeżeli kolejne argumenty funkcji oznaczymy przez arg0 … arg n to zostaną one umieszczone w pamięci w sposób przedstawiony na poniższym rysunku:gcc_va_args

Wyjaśnienia wymaga zapis a_sizeof(x). Jest to makro zwracające rozmiar zmiennej (w bajtach) będącej jego argumentem w postaci ‘wyrównanej’ do architektury dla której kompilujemy oprogramowanie. Przykładowo, jeśli architektura jest 32 bitowa to wartość dla zmiennej 1-bajtowej wynosi 4, a dla zmiennej 5-bajtowej wynosi 8. Powyższy rysunek mówi nam, iż każdy kolejny argument jest umieszczony po poprzednim w odległości równej a_sizeof(x), gdzie x to argument poprzedni.

Skoro wiemy już jak zmienne są rozmieszczone w pamięci to możemy zdefiniować sobie makra do poruszania się po nich i odczytywania ich wartości. Zestaw makr przygotowanych przeze mnie ma następującą postać:

#define __a_size(n)           ((sizeof(n) + sizeof(char *) – 1) &amp; ~(sizeof(char *) – 1))

#define va_start(ptr, v)        (ptr = (va_list)(&amp;v) + __a_size(v))
#define va_arg(ptr, t)          ( *(t *)((ptr += __a_size(v)) – __a_size(t)))
#define va_end(ptr)             (ptr = (va_list) 0)

typedef char * va_list

Makro __a_size(n) jest odpowiednikiem notacji a_sizeof(n) z rysunku. Makro va_start(ptr, v) ustawia wskaźnik ptr, tak aby wskazywał na pierwszą zmienną ze zbioru. Aby poprawnie ustawić wskaźnik niezbędny jest adres ostatniej ‘normalnej’ zmiennej (na rysunku jest zmienna arg). Makro va_arg(ptr, t) powoduje zwrócenie zmiennej wskazywanej przez ptr oraz ustawienie ptr tak by wskazywał na następną zmienną. Parametr t makra określa typ zmiennej jaką chcemy zwrócić. Jest on niezbędny, gdyż od niego zależy ‘odległość’ do następnej zmiennej w pamięci. Makro va_end(ptr) służy do wyczyszczenia wartości wskaźnika ptr.

Wyposażeni w taki zestaw makr możemy zacząć naszą przygodę z funkcjami o zmiennej liczbie argumentów. Dla przykładu napiszemy funkcję wypisującą wartość wszystkich zmiennych typu int podanych jako argumenty. Listing funkcji jest następujący:

void foo(int num_ints,)
{
int i, x;
va_list va;

va_start(va, num_ints);

for(i = 0; i &lt; num_ints; i++) {
x = va_arg(va, int);
printf("arg: %d, val: %d\n", i, x);
}
}

Przykładowe wywołanie:

foo(3, 1, 2, 3);

I jego rezultat:

arg: 0, val: 1
arg: 1, val: 2
arg: 2, val: 3

USB JTAG i OpenOCD 0.1.0 + Eclipse

Jakiś czas temu światło dzienne ujrzała nowa wersja popularnego oprogramowania OpenOCD, służącego do programowania i debugowania uC. W związku z tym, jakże wspaniałym, wydarzeniem uznałem za zasadne przedstawienie Czytelnikowi treści skryptów, służących do programowania pamięci flash mikrokontrolerów AT91SAM7S64. Wybór mikrokontrolera jest podyktowany tym, iż znalazł on zastosowanie w dużej części wykonanych przeze mnie urządzeń. W roli JTAG’a wykorzystałem, własnoręcznie wykonany, klon Turtelizera 2.

W pakiecie OpenOCD dołączono wiele skryptów opisujących sposób pracy interfejsów JTAG, jak również dla poszczególnych układów docelowych (targetów). Nic nie stoi na przeszkodzie aby je wykorzystać i znacząco ułatwić sobie życie. Przykładowy skrypt flashujący uC plikiem main.hex wygląda następująco:

source [find openocd-0.1.0/src/target/interface/turtelizer2.cfg] # (1)
source [find openocd-0.1.0/src/target/target/sam7s64.cfg]        # (2)

init                                  # (3)
reset halt                            # (4)
flash write_image main.hex 0          # (5)
reset run                             # (6)
shutdown                              # (7)

Pierwsza linia zawiera ściężkę prowadzącą do opisu użytego interfejsu JTAG, zaś linia (2) wskazuje na plik z opisem układu docelowego. Jeśli Czytelnik w swej pracy wykorzystuje inne uklady docelowe należy przejrzeć katalog target w folderze instalacyjnym OpenOCD w poszukiwaniu odpowiedniego pliku. Z każdym dniem społeczność pracująca nad OpenOCD uzupełnia zbiór plików konfiguracyjnych o nowe targety. Pozostałe linie mają składnię podobną do znanej z poprzednich wersji OpenOCD. Powodują one inicjalizację komunikacji z targetem oraz jego zresetowanie i wstrzymanie (3, 4). Flashowanie odbywa się dzięki komendzie zawartej w linii (5), której pierwszym parametrem jest operacja jaką wykonujemy na pamięci flash, po niej następuje nazwa pliku oraz offset od początku przestrzeni adresowej uC pod jaki zostaną zapisane dane. Linie (6, 7) kolejno resetują mikrokontroler i “wprawiają go w ruch” oraz kończą pracę OpenOCD. Jak wynika z załączonego przykładu skrypty, dzięki gotowym zbiorom plików konfiguracyjnych, uległy znacznemu uproszczeniu.

Skrypt służący do debugowania jest równie prosty co powyższy, używany do flashowania. Jego treść przedstawia się w sposób zgodny z przdestawionym na listingu:

source [find ./openocd-0.1.0/src/target/interface/turtelizer2.cfg] # (1)

# Change the default telnet port...
telnet_port 4444                     # (2)

# GDB connects here
gdb_port 3333                        # (3)

# GDB can also flash my flash!
gdb_memory_map enable                # (4)
gdb_flash_program enable             # (5)
gdb_breakpoint_override hard         # (6)

source [find ./openocd-0.1.0/src/target/target/sam7s64.cfg]        # (7)

init                                 # (8)

Przeznaczenie linii  (1, 7)  jest znane z poprzedniego skryptu. Linia (2) służy do konfiguracji portu na którym będzie nasłuchiwać OpenOCD. Linia (3) ustawia port pracy samego debuggera GDB. Linie (4, 5) umożliwiają programowanie pamięci flash uC z wykorzystaniem GDB. Linia (6) ustawia rodzaj stosowanych breakpoinów na breakpointy hardwareowe. Opcja ta jest niezbędna w momencie gdy firmware został zlinkowany tak by pracować w pamięci flash a nie w pamięci ram. Należy mieć świadomość, iż liczba jednocześnie stosowanych breakpointów hardwareowych jest bardzo ograniczona, a w przypadku AT91SAM7S64 wynosi dokładnie dwa. Linia (8) służy do uruchomienia procesu debugującego.

Tak przygotowany skrypt wyśmienicie współpracuje ze środowiskiem Eclipse. Konfigurację Eclipsa z doinstalowanym pluginem Zylin Embedded CDT rozpoczynamy od dodania nowej Debug Configuration typu Zylin Embedded Debug (Native). Należy wybrać nazwę projektu dla którego konfigurujemy debug oraz plik wykonywalny w formacie elf, który zawiera m. in. symbole niezbędne dla debugu. Plik taki powstaje każdorazowo w wyniku zlinkowania skompilowanych źródeł projektu.

debug_conf

Następnie przechodzimy do zakładki Debbuger, gdzie ustawiamy jako aplikację debugującą arm-elf-gcc lub innym, w zależności z jakich kompilatorów korzystamy. W zakładce Commands ustawiamy komendy wysyłane podczas inicjalizacji oraz samego uruchomienia trybu debug. Komendy, jakie należy umieścić w sekcji Initialize commands są następujące:

target remote localhost:3333
monitor reset halt

Sekcję ‘Run commands’ pozostawiamy pustą. Tak przygotowany Eclipse jest gotowy do odpluskwiania (ech…), zaraz po uruchomieniu opisanego wcześniej skryptu uruchamiającego debug mode.

Turtelizer 2 JTAG Cloned.

Truly Integrated Turtelizer 2 Clone to kolejna implementacja popularnego Turtelizera 2, którego wersja pierwotna została opisana na ethrenut.de. Programator został zaprojektowany tak aby współpracować z oprogramowaniem OpenOCD. 10-pinowe złącze JTAG jest pinowo kompatybilne z pierwowzorem. Cały projekt jest oparty na popularnym scalaku FT 2232 firmy FTDI. Scalak ten ma wsparcie zarówno ze strony systemu operacyjnego Windows jak również Linux. Prócz samego interfejsu JTAG programator został wyposażony  w port RS232 o prędkości do 115200 bps. Konwersję poziomów napięć dla RSa zapewnia ST3232, który jest podobnież jednym z lepszych konwerterów poziomów napięć spośród scalaków dostępnych na rynku. Aby zabezpieczyć komputer przed możliwością wystąpienia efektów typu światło-dzwięk-zapach, powstających w wyniku tzw. przepięć wyjścia układu zostały połączone poprzez bufory, które dodatkowo umożliwiają współpracę z targetami zasilanymi napięciami innymi niż to, którym zasilany jest sam JTAG.

Aby móc użyć portu szeregowego w systemie Linux należy załadować moduł jądra o nazwie ftdi_sio. Z racji tego iż programator korzysta z nietypowego vendor_id oraz product_id (nietypowe = różne od domyślnego dla kości FT2232) ładowanie modułu należy przeprowadzić w następujący sposób:

# modprobe ftdi_sio vendor=0x403 product=0xbdc8

Sterowniki dla systemu Windows znajdują się w pakiecie OpenOCD.  Wieść niesie, że podobno działają, ja niestety potwierdzić tego nie mogę gdyż nie posiadam takowego systemu operacyjnego (bida, panie..).

Na zakończenie zamieszczam zdjęcie programatora mej produkcji:

Programator JTAG