<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="pl-pl">
<link rel="self" type="application/atom+xml" href="https://forum.atnel.pl/feed.php?f=56&amp;t=16484&amp;mode" />

<title>ATNEL tech-forum</title>
<link href="https://forum.atnel.pl/index.php" />
<updated>2018-10-08T18:56:23+01:00</updated>

<author><name><![CDATA[ATNEL tech-forum]]></name></author>
<id>https://forum.atnel.pl/feed.php?f=56&amp;t=16484&amp;mode</id>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2018-10-08T18:56:23+01:00</updated>
<published>2018-10-08T18:56:23+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=212424#p212424</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=212424#p212424"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=212424#p212424"><![CDATA[
<div class="quotetitle">Januszkk napisał(a):</div><div class="quotecontent"><br />brakuje mi tylko opisu asemblera online<br /></div><br />Wstawki &quot;inline assembly&quot; to wbrew pozorom temat odmienny (od tego, co tu przedstawiłem), dość obszerny i na tyle skomplikowany, że wymaga osobnego artykułu, aby go solidnie opisać.<br /><br />Jeśli chodzi o Twój przykład, to tak naprawdę tam masz tylko jedną instrukcję w asm, reszta to język C.<br /><br />Podstawowa korzyść ze stosowania &quot;inline assembly&quot; jest taka, że pisząc kod w asm pozostawiamy kompilatorowi C dobór rejestrów, dzięki czemu ma on większe możliwości optymalizacji kodu. Instrukcja <strong><em>reti</em></strong> nie korzysta z żadnych rejestrów, więc nic tu się nie da zoptymalizować. Ponadto, pisząc akurat procedurę obsługi przerwania, kompilator i tak musi zapamiętać na stosie wszystkie używane wewnątrz procedury rejestry, więc tu i tak nic nie da się zoptymalizować.<br /><br />Oczywiście można zrobić tak, jak to przedstawiłeś, jednak zysku z zastosowania &quot;inline assembly&quot; tutaj nie będzie, a osobiście wydaje mi się, że czytelniejsze byłoby (bez konieczności definiowania struktur, pól bitowych i wskaźników na rejestry) dodanie do projektu pliku <strong><em>*.s</em></strong> z zawartością:[syntax=asm]#define __SFR_OFFSET    0<br /><br />#include &lt;avr/io.h&gt;<br /><br />    .section .text<br /><br />    .global TIMER0_OVF_vect<br /> <br />TIMER0_OVF_vect:<br />    sbi GPIOR0, 2 // instrukcja ustawi bit 2 w rejestrze GPIOR0<br />    reti[/syntax]<br />Dodatkowo mamy pewność, że instrukcja SBI na pewno będzie użyta, a w przypadku próby zmiany GPIOR0 np. na GPIOR1 (który jest poza zasięgiem instrukcji SBI), program asemblujący zgłosi błąd. W przypadku Twojego rozwiązania zmiana na GPIOR1 spowodowałaby po prostu wygenerowanie przez kompilator (bez ostrzeżenia) innego kodu (np. z użyciem instrukcji OUT, który byłby w tym przypadku błędny), trzeba więc sprawdzać po kompilacji plik <strong><em>*.lss</em></strong><br /><br />Ewentualnie, aby operować nazwami symbolicznymi zamiast wartościami liczbowymi czy też nazwami rejestrów, można też dodać plik nagłówkowy zawierający takie nazwy symboliczne i dołączyć go dyrektywą <em>#include</em> zarówno do pliku <strong><em>*.s</em></strong>, jak i do odpowiedniego pliku źródłowego <strong><em>*.c</em></strong><br /><br />Proszę mnie źle nie zrozumieć, ja nie neguję zalet wstawek &quot;inline assembly&quot;, jednak moim skromnym zdaniem czytelność kodu z takimi wstawkami drastycznie spada, dlatego stosuję je tylko tam, gdzie faktycznie można odnieść z tego konkretną korzyść. Ale wiadomo, każdy robi jak mu wygodniej <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 8 paź 2018, o 18:56</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[Januszkk]]></name></author>
<updated>2018-10-07T19:30:22+01:00</updated>
<published>2018-10-07T19:30:22+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=212404#p212404</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=212404#p212404"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=212404#p212404"><![CDATA[
Gratulacje za fajny wykład, brakuje mi tylko opisu asemblera online, bo jest trudny i każdy opis jest cenny.<br />Żeby nie być gołosłowny to daję przykład najkrótszego przerwania od zegara. Ustawia ono jeden bit który <br />pozniej testujemy w głownej pętli, warunkiem jest żeby bit był ustawiany za pomocą sbi co możemy sprawdzić w pliku *.lss wgenerowanym przez avr studio:<br /><br />[syntax=c]ISR(TIMER2_COMPA_vect, ISR_NAKED) //cyka co 10mS<br />{<br />     INT-&gt;T_10ms=1;<br />__asm__ __volatile__<br />(&quot;reti \n\t&quot;  ::);<br />}[/syntax]<br /><br />a deklaracja zmiennej wygląda tak:<br />[syntax=c]typedef struct {<br />uint8_t T_10ms:1;<br />} volatile IO;[/syntax]<br /><br />[syntax=c]IO * const INT=(IO*)&amp;GPIOR0;[/syntax]<br />oczywiscie procek musi być nowszych które mają rejestry GPIOR.<br /><br /><strong><span style="color: #808000">------------------------ [ Dodano po: 2 minutach ]</span></strong><br /><br />Zapomniałem, w main testujemy flagę:<br />[syntax=c]if (INT-&gt;T_10ms)<br />{INT-&gt;T_10ms=0;<br />        // dalszy kod<br />         }[/syntax]<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=8263">Januszkk</a> — 7 paź 2018, o 19:30</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[mopsiok]]></name></author>
<updated>2017-06-04T15:21:41+01:00</updated>
<published>2017-06-04T15:21:41+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=190276#p190276</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=190276#p190276"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=190276#p190276"><![CDATA[
Temat już trochę stary, ale również chciałem się przyłączyć do podziękowań. Świetnie opisane, łatwe do wykorzystania - stukrotne dzięki <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" />. Nie przypuszczałem że wstawki asemblerowe mogą być tak wygodne w użyciu. W projektach zawsze odpuszczałem asemblera przez - jak mi się wtedy wydawało - &quot;niekompatybilność&quot; przyjemnych w użyciu argumentów z C i rejestrów ogólnego przeznaczenia.<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=1371">mopsiok</a> — 4 cze 2017, o 15:21</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2016-12-18T09:37:30+01:00</updated>
<published>2016-12-18T09:37:30+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=177774#p177774</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=177774#p177774"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=177774#p177774"><![CDATA[
Przepraszam, że dłuższy czas nie odpowiadałem w tym wątku, jednak otrzymałem tyle pochwał, że zabrakło mi słów na podziękowania <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /><br /><br />Teraz piszę, dlatego że edytowałem swój pierwszy post. Chciałem tylko poinformować, że nie wprowadziłem żadnych znaczących zmian, poprawiłem tylko kilka &quot;literówek&quot;, które przypadkowo zauważyłem. Mam nadzieję, że teraz już nie ma błędów.<br /><br />Pozdrawiam,<br /><ul>andrews</ul><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 18 gru 2016, o 09:37</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[IUVOit]]></name></author>
<updated>2016-10-17T07:44:12+01:00</updated>
<published>2016-10-17T07:44:12+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171829#p171829</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171829#p171829"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171829#p171829"><![CDATA[
Ale musiales sie napracowac <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":-)" title="Szczęśliwy" /> Dzieki!<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=94">IUVOit</a> — 17 paź 2016, o 07:44</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[zoom]]></name></author>
<updated>2016-10-16T08:14:19+01:00</updated>
<published>2016-10-16T08:14:19+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171729#p171729</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171729#p171729"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171729#p171729"><![CDATA[
Ja chyba też wydrukuję i chętnie dokładnie przestudiuję, na pewno się przyda! Dzięki!<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=685">zoom</a> — 16 paź 2016, o 08:14</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[PITERK]]></name></author>
<updated>2016-10-15T23:05:15+01:00</updated>
<published>2016-10-15T23:05:15+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171724#p171724</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171724#p171724"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171724#p171724"><![CDATA[
Cześć. <br />Pozwolisz, że sobie wydrukuje Twój artykuł, oprawie i położę obok książek Mirka, co by mieć zawsze pod ręką. <br />Dzięki za super poradnik.<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=2913">PITERK</a> — 15 paź 2016, o 23:05</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[j23]]></name></author>
<updated>2016-10-15T21:36:10+01:00</updated>
<published>2016-10-15T21:36:10+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171722#p171722</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171722#p171722"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171722#p171722"><![CDATA[
Konkretny rzetelnie napisany poradnik. Są w nim niektóre informacje (np. na temat konfiguracji środowiska) gdzie w innych poradnikach, albo są albo i nie. Wiedzy jest w sam raz tyle, żeby było wiadomo co z czym się je - czyli nie za dużo, nie za mało (w sam raz).<br />Ja również dziękuję za poradnik.<br />Pozdrawiam! j23<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=4504">j23</a> — 15 paź 2016, o 21:36</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[micky]]></name></author>
<updated>2016-10-15T20:33:59+01:00</updated>
<published>2016-10-15T20:33:59+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171710#p171710</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171710#p171710"/>
<title type="html"><![CDATA[Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171710#p171710"><![CDATA[
Bardzo fajny i ciekawy artykul, bo postem to grzech go nazwac. Przyjemnie i rzeczowo opisany.  Dziekuje!<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=1546">micky</a> — 15 paź 2016, o 20:33</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[krzysssztof]]></name></author>
<updated>2016-10-15T19:13:18+01:00</updated>
<published>2016-10-15T19:13:18+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171703#p171703</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171703#p171703"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171703#p171703"><![CDATA[
To tyle tekstu można upchnąć w jednym poście ?<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=4274">krzysssztof</a> — 15 paź 2016, o 19:13</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[KazioB]]></name></author>
<updated>2016-10-15T17:49:12+01:00</updated>
<published>2016-10-15T17:49:12+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171695#p171695</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171695#p171695"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171695#p171695"><![CDATA[
Super!!! dzięki za podzielenie się wiedzą.<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=13570">KazioB</a> — 15 paź 2016, o 17:49</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2016-10-15T16:49:04+01:00</updated>
<published>2016-10-15T16:49:04+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171687#p171687</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171687#p171687"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171687#p171687"><![CDATA[
Nie bardzo wiem, co powiedzieć, a właściwie napisać.<br />Wypada chyba jeszcze raz podziękować za tak pozytywne oceny.<br />Szczerze mówiąc nie spodziewałem się nawet, że artykuł wzbudzi tak spore zainteresowanie.<br />Cieszę się, że się spodobało i że będzie przydatne, bo to oznacza, że mój czas (którego nadmiarem nie dysponuję) nie poszedł na marne.<br /><br />Pozostaje mi życzyć skutecznego miksowania, żeby się coś dobrego z tego upiekło <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 15 paź 2016, o 16:49</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[Daro69]]></name></author>
<updated>2016-10-15T15:17:25+01:00</updated>
<published>2016-10-15T15:17:25+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171678#p171678</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171678#p171678"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171678#p171678"><![CDATA[
Ja cie....<br />To się chyba mówi na to - elaborat. <img src="https://forum.atnel.pl/images/smilies/icon_e_biggrin.gif" alt=":D" title="Bardzo szczęśliwy" /><br />Zauważyłem że wszystkie Twoje posty zawierają dużo cennych informacji.<br />Dzięki kolego <strong>andrews</strong> za poświęcony czas i przekazanie szczypty swojej wiedzy. <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /><br />Tu żadna dobra informacja się nie zmarnuje,<br />ktoś zawsze gdzieś coś spożytkuje w swoich projektach. <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=11549">Daro69</a> — 15 paź 2016, o 15:17</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[SIND]]></name></author>
<updated>2016-10-15T14:40:59+01:00</updated>
<published>2016-10-15T14:40:59+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171676#p171676</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171676#p171676"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171676#p171676"><![CDATA[
Dzięki  <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" />  za czas poświęcony na stworzenie tak fajnego artykułu . Przyda się taka wiedza w ...  pigułce ... <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /> <br />Wielki szacunek dla wiedzy .<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=9217">SIND</a> — 15 paź 2016, o 14:40</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[cikciok]]></name></author>
<updated>2016-10-15T00:35:43+01:00</updated>
<published>2016-10-15T00:35:43+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171620#p171620</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171620#p171620"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171620#p171620"><![CDATA[
Naprawdę świetny artykuł. Gratuluję!<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=2366">cikciok</a> — 15 paź 2016, o 00:35</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[tomek]]></name></author>
<updated>2016-10-14T23:05:51+01:00</updated>
<published>2016-10-14T23:05:51+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171615#p171615</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171615#p171615"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171615#p171615"><![CDATA[
doskonały artykuł ... dzięki za olbrzymi wkład pracy żebyśmy mogli pogłębić wiedzę ....<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=178">tomek</a> — 14 paź 2016, o 23:05</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[maverick_as]]></name></author>
<updated>2016-10-14T21:55:02+01:00</updated>
<published>2016-10-14T21:55:02+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171607#p171607</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171607#p171607"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171607#p171607"><![CDATA[
<img src="https://forum.atnel.pl/images/smilies/icon_e_biggrin.gif" alt=":D" title="Bardzo szczęśliwy" />  <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" />  <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" />  <img src="https://forum.atnel.pl/images/smilies/icon_cool.gif" alt="8-)" title="Cool" />  <img src="https://forum.atnel.pl/images/smilies/icon_lol.gif" alt=":lol:" title="Śmieje się" />  <img src="https://forum.atnel.pl/images/smilies/icon_razz.gif" alt=":P" title="Pokazuje język" /> Dziękuję baaaaardzo!!!<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=9169">maverick_as</a> — 14 paź 2016, o 21:55</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[michal1210]]></name></author>
<updated>2016-10-14T21:51:12+01:00</updated>
<published>2016-10-14T21:51:12+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171606#p171606</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171606#p171606"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171606#p171606"><![CDATA[
Super! Dzięki !<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=998">michal1210</a> — 14 paź 2016, o 21:51</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[sq5rix]]></name></author>
<updated>2016-10-14T21:13:20+01:00</updated>
<published>2016-10-14T21:13:20+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171594#p171594</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171594#p171594"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171594#p171594"><![CDATA[
Bardzo dziękuję - super poradnik!<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=5966">sq5rix</a> — 14 paź 2016, o 21:13</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[vaffanculo]]></name></author>
<updated>2016-10-14T20:37:06+01:00</updated>
<published>2016-10-14T20:37:06+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171591#p171591</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171591#p171591"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171591#p171591"><![CDATA[
Podziękowania z mojej strony za ogrom bardzo przydatnej wiedzy, którą nam przekazałeś. Na pewno się przyda niejednokrotnie.<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=1784">vaffanculo</a> — 14 paź 2016, o 20:37</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2016-10-14T19:33:23+01:00</updated>
<published>2016-10-14T19:33:23+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171583#p171583</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171583#p171583"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171583#p171583"><![CDATA[
Dziękuję za słowa uznania <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /><br />Mam nadzieję, że to nie były pochopne opinie <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /><br />Będzie mi miło, jeśli komuś się przyda to co napisałem.<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 14 paź 2016, o 19:33</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[MirkoT]]></name></author>
<updated>2016-10-14T19:21:34+01:00</updated>
<published>2016-10-14T19:21:34+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171582#p171582</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171582#p171582"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171582#p171582"><![CDATA[
No i pięknie to opisałeś.<br />Kawał dobrej roboty. Gratuluję wiedzy!<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=189">MirkoT</a> — 14 paź 2016, o 19:21</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[mirekk36]]></name></author>
<updated>2016-10-14T19:21:00+01:00</updated>
<published>2016-10-14T19:21:00+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171581#p171581</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171581#p171581"/>
<title type="html"><![CDATA[Re: Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171581#p171581"><![CDATA[
No .... no ... uuups ... aż mnie zatkało <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /> .... No! takiego arta to się nie spodziewałem ... REWELACJA i myślę, że będzie MEGA SMAKOWITYM przysmakiem dla każdego konesera dań złożonych z C i ASM <img src="https://forum.atnel.pl/images/smilies/icon_lol.gif" alt=":lol:" title="Śmieje się" /><br /><br />cosik pięknego <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=54">mirekk36</a> — 14 paź 2016, o 19:21</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2016-12-18T09:27:57+01:00</updated>
<published>2016-10-14T19:11:16+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171580#p171580</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171580#p171580"/>
<title type="html"><![CDATA[Miksowanie kodu C i ASM przy użyciu GCC]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=16484&amp;p=171580#p171580"><![CDATA[
<img src="https://obrazkiforum.atnel.pl/54/4babe017fbfaf978adb704ae61b5f37c.jpg" alt="Obrazek" /><br /><span style="font-size: 175%; line-height: normal">Prolog</span><br /><br />Jakiś czas temu odpowiadałem w wątku traktującym o wstawkach asemblerowych w kodzie napisanym w języku C. Wprawdzie autor wątku jakoś stracił zainteresowanie, jednak ze względu na zapewnienia kolegi Mirka, że inni czytelnicy tego forum też interesują się tym tematem, postanowiłem go nieco dokładniej opisać.<br /><br /><span style="font-size: 175%; line-height: normal">Podstawowe wiadomości</span><br /><br />Zakładam, że każdy z czytających ten artykuł posiada podstawową wiedzę z dziedziny programowania zarówno w C jak i w asemblerze, dlatego nie będę tu zbyt szczegółowo wszystkiego opisywał. Przypomnę jednak kilka pojęć istotnych dla omawianego tematu i przedstawię kilka ważnych uwag.<br /><br />Dla uproszczenia ograniczę się do opisania programów, których objętość danych w pamięci FLASH nie przekracza 64KB, a łącznie z kodem wykonywalnym 128KB. Powyżej tej granicy mieszanie kodu nieco się komplikuje ze względu na konieczność użycia wskaźników o rozmiarze przekraczającym 16 bitów, ale to raczej temat na inny artykuł. Myślę jednak, że to ograniczenie nie będzie większym problemem, gdyż linker umieszcza dane w pamięci FLASH na samym początku, zaraz po wektorach przerwań, dopiero później funkcje. Sytuacje, kiedy zapisujemy do FLASH dane o rozmiarze większym od 64KB nie zdarzają się chyba często, więc w większości przypadków 16-bitowe adresowanie będzie wystarczające.<br /><br />Opisane tutaj zasady działania kompilatora i sposoby mieszania kodu dotyczą głównie aktualnego toolchain'a Atmela w wersji 3.5.3.1700. Jak powszechnie wiadomo oprogramowanie w dzisiejszych czasach dosyć szybko ewoluuje, więc nie mogę zagwarantować, że dokładnie te same zasady we wszystkich szczegółach dotyczą starszych wersji i będą dotyczyły nowszych wersji toolchain'a.<br /><br /><ul><li><span style="font-size: 125%; line-height: normal">Zasadność mieszania kodu ASM i C</span><br /><br />Właściwie to potrzeba wstawiania kodu asemblera do kodu w języku C nie zdarza się zbyt często. Obecnie kompilatory C potrafią naprawdę bardzo dobrze optymalizować kod. Zwykle robią to równie dobrze lub nawet lepiej niż programista, szczególnie w przypadku tych bardziej skomplikowanych funkcji.<br /><br />Zdarzają się jednak sytuacje, kiedy (moim zdaniem) takie wstawki mają sens. Przypuśćmy przykładowo, że potrzebujemy wydajnej (szybkiej) funkcji lub procedury obsługi przerwania, w której optymalnym typem danych będzie liczba całkowita np. 40-bitowa (5 bajtów). Język C nie wspiera wprost operacji na takich liczbach.<br /><br />Można wprawdzie zastosować typ nadmiarowy 64-bitowy (8 bajtów zamiast 5), ale to spowoduje (mowa oczywiście o mikrokontrolerze 8-bitowym), że każde ładowanie zmiennej z pamięci do rejestrów i odwrotnie, wszelkie operacje dodawania, odejmowania, porównania i logiczne (nie wspominając o ewentualnej konieczności maskowania nieużywanych bajtów) będą zajmowały kilka (3, 6 lub nawet 9) taktów więcej, niż byłoby to konieczne dla operacji na pięciu bajtach, zamiast na ośmiu. W przypadku operacji mnożenia lub dzielenia te różnice mogą sięgać nawet kilkudziesięciu taktów, szczególnie w mikrokontrolerach nie obsługujących instrukcji <strong>mul</strong>, <strong>muls</strong> itp.<br /><br />Można też ewentualnie próbować obejść problem stosując jakieś triki ze strukturami i/lub uniami i/lub tablicami, ale to z kolei gmatwa kod i w dodatku wcale nie daje gwarancji, że będzie on optymalny. Dlatego (przynajmniej dla mnie) łatwiej rozwiązać zadanie za pomocą funkcji napisanej w asemblerze.<br /><br />Pewnie znalazłyby się jeszcze inne argumenty do stosowania wstawek, ale pozostawmy może te rozważania. Pokażę po prostu jak to technicznie wykonać, a każdy sam zdecyduje, czy i kiedy wstawki stosować. Oczywiście nie polecam nadużywania, ponieważ to na prawdę w niektórych przypadkach może nawet pogorszyć sprawę. Można najpierw poeksperymentować, pisząc takie same funkcje w języku C i w języku ASM sprawdzając, kto wygeneruje wydajniejszy kod wynikowy - programista ASM czy kompilator C.<br /><br /></li><li><span style="font-size: 125%; line-height: normal">Preprocesor</span><br /><br />W AVR GCC pliki zawierające kod asemblera  mają rozszerzenia <strong>*.s</strong> (małe s) lub <strong>*.S</strong> (duże S). Aplikacja tłumacząca kod asemblera na wynikowy kod maszynowy (<strong>avr-as.exe</strong>) podczas procesu budowania nie jest wywoływana bezpośrednio, lecz poprzez aplikację <strong>avr-gcc.exe</strong>, która przed uruchomieniem avr-as.exe decyduje o użyciu lub nie użyciu preprocesora. Kiedyś decyzja ta podejmowana była właśnie na podstawie rozszerzenia pliku. W przypadku dużego S preprocesor był używany, a w przypadku małego s – nie.<br /><br />Podobno w systemach operacyjnych, które nie biorą pod uwagę wielkości liter w nazwach plików (np. Windows) były z tym jakieś problemy, więc z tego i/lub z innych powodów zasada ta została zmieniona. Obecnie opcja programu avr-gcc.exe<br /><strong>-x assembler</strong><br />oznacza przekazanie pliku bezpośrednio do asemblacji, natomiast<br /><strong>-x assembler-with-cpp</strong><br />wymusza wcześniejsze użycie preprocesora.<br /><br />Dlaczego właściwie preprocesor C ma przetwarzać plik z kodem ASM? Otóż skoro już miksujemy kod C i ASM, to przecież fajnie byłoby mieć pewne zdefiniowane stałe w programie dostępne jednocześnie w obu kodach. Trochę niewygodnie byłoby definiować stałą w dwóch miejscach (ryzyko pomyłki) i później jeszcze pamiętać, żeby ją w razie potrzeby zmienić w dwóch miejscach. Program asemblujący nie rozumie jednak tych wszystkich komentarzy, dyrektyw typowych dla języka C i będzie generował błędy składni. Jednak dzięki preprocesorowi możemy dołączyć ten sam plik nagłówkowy do obu kodów, a preprocesor przed asemblacją pliku z kodem ASM zamieni wszystkie symbole na ich zdefiniowane wartości, po czym pousuwa dyrektywy preprocesora i komentarze C-style, dzięki czemu asembler będzie mógł prawidłowo wygenerować kod wynikowy.<br /><br />Preprocesor będzie więc nam potrzebny. W Atmel Studio 7 opcja użycia preprocesora jest domyślnie włączona zarówno dla plików typu „Assembler File (.s)” jak i dla „Preprocessing Assembler File (.S)”. Zgodnie z moją wiedzą nie można tej opcji wyłączyć inaczej, jak tylko poprzez ręczne edytowanie pliku Makefile. Dopóki więc będziemy używać automatycznie generowanego Makefile, nie powinno być problemów.<br /><br />W Eclipse preprocesor dla plików z ASM również powinien być domyślnie włączony, ale tutaj można go wyłączyć (choć my nie powinniśmy tego robić, chyba że mamy uzasadniony powód) wyłączając opcję „Use preprocessor” we właściwościach projektu:<br /><em>Project-&gt;Properties-&gt;C/C++ Build-&gt;Settings-&gt;zakładka Tool Settings-&gt;AVR Assembler-&gt;General</em> (jeśli dołączymy kilka plików z kodem ASM, można tę opcję włączyć lub wyłączyć dla każdego pliku osobno we właściwościach pliku). Tutaj też nie powinno być problemu, dopóki ktoś tej opcji nie wyłączy.<br /><br />Mimo tego, że w obu środowiskach opcja preprocesora jest domyślnie włączona, postanowiłem o tym napisać, bo moim zdaniem dobrze jest wiedzieć o co  chodzi z tym preprocesorem, szczególnie gdyby ktoś korzystał ze starszej wersji avr-gcc (w dodatku w systemie unixowym), ewentualnie uruchamiał kompilację z wiersza poleceń lub zechciał ręcznie edytować plik Makefile.<br /><br /></li><li><span style="font-size: 125%; line-height: normal">Deklaracje kontra etykiety - używanie słowa kluczowego .extern</span><br /><br />Kompilator języka C wymaga od programisty precyzyjnego zadeklarowania zmiennych i funkcji. Potrzebuje tych informacji głównie w celu wygenerowania poprawnego kodu maszynowego (np. użycie właściwych instrukcji ASM w zależności od tego, czy zmienna całkowita jest ze znakiem, czy bez) oraz analizy semantycznej (np. kontrola typów podczas przypisania wartości jednej zmiennej do innej lub argumentów przekazywanych do funkcji).<br /><br />W przypadku asemblera to na programiście spoczywa obowiązek prawidłowej obsługi poszczególnych zmiennych, przekazywania prawidłowych argumentów do funkcji i wykonywania wszystkich innych zadań, które wykonuje kompilator C. Tutaj deklaracja zmiennej czy funkcji odbywa się poprzez nadanie im etykiet, które po zakończeniu budowania aplikacji stają się liczbami reprezentującymi adres komórki pamięci RAM lub FLASH. Etykiety nie mówią nic o typie zmiennej, o ilości i typach argumentów przekazywanych do funkcji, o wartości zwracanej przez funkcję. Ba, na podstawie samej etykiety nie można nawet stwierdzić, czy dotyczy ona zmiennej, stałej w pamięci programu czy może funkcji.<br /><br />Piszę o tym, ponieważ czasami spotykam próby deklarowania w pliku ASM konstrukcje tego rodzaju:<br /><strong>.extern uint8_t nazwa</strong><br />co oczywiście nie ma większego sensu z dwóch powodów. Po pierwsze dla asemblera typ <strong>uint8_t</strong> i tak jest niezrozumiały - liczy się tylko słowo <strong>'nazwa'</strong> będące dla asemblera etykietą (symbolem zmiennej). Po drugie samo użycie <strong>.extern</strong> jest przez GCC assembler akceptowane ze względu na kompatybilność z innymi asemblerami, nie jest jednak wymagane, gdyż program <strong>as</strong> (w naszym przypadku <strong>avr-as</strong>) traktuje wszystkie niezdefiniowane (w danym pliku) symbole jako <strong>.extern</strong>. Jeśli tylko w innym pliku naszego projektu lub w bibliotekach standardowych istnieje taki symbol (nazwa zmiennej lub funkcji), asemblacja przebiegnie bez błędów. Czy kod będzie działał prawidłowo, to już inna sprawa. Zależy to tylko od tego, czy programista nie pomyli np. typów zmiennych lub nazwy funkcji z nazwą zmiennej itp.<br /><br /></li><li><span style="font-size: 125%; line-height: normal">Kwestia optymalizacji kompilatora C</span><br /><br />W czasie wykonywania każdej funkcji używane są (przynajmniej niektóre) rejestry - nie da się tego uniknąć. Należy więc liczyć się z tym, że zawartość niektórych rejestrów po wykonaniu funkcji może być inna, niż przed jej wykonaniem. Jeśli nie chcemy utracić zawartości jakichś rejestrów, należy je przed wywołaniem funkcji zapamiętać (np. na stosie) lub przechować te wartości w rejestrach, o których wiemy, że nie zostaną zmienione. Zapamiętywanie rejestrów na stosie wiąże się z zaangażowaniem pewnych zasobów (np. czasu procesora, zajętości RAM), więc lepiej więc tego unikać.<br /><br />Jedną ze strategii optymalizacyjnych kompilatora C jest właśnie takie dobieranie rejestrów w trakcie wykonywania programu, aby zminimalizować potrzebę zapamiętywania ich wartości. Niestety dołączając do projektu pliki ASM, w których &quot;na sztywno&quot; definiujemy rejestry użyte wewnątrz funkcji, zawężamy mu nieco pole manewru, przez co optymalizacja może być mniej skuteczna. Lepsze efekty pod tym względem daje <em>inline assembler</em>, gdyż pozostawia kompilatorowi dobór rejestrów wykorzystywanych we funkcji, jednak jak dla mnie jego składnia jest stosunkowo trudna, a kod mało czytelny i osobiście nie lubię go stosować, chyba że do definiowana naprawdę prostych funkcji.<br /><br />Istnieje jeszcze jeden problem podobnej natury. Zdarza się, że chcąc przyspieszyć dostęp do jakichś newralgicznych danych, zechcemy zarezerwować dla jakiejś zmiennej (lub kilku) rejestr (rejestry) mikrokontrolera. Można to oczywiście zrobić używając słowa kluczowego 'register' deklarując zmienną globalną (stosunkowo bezpiecznie jest używać do tego celu rejestry z zakresu <strong>r2-r7</strong>):<br />[syntax=c]register uint8_t my_var asm(&quot;r2&quot;);[/syntax]<br />jednak trzeba mieć świadomość, że to zablokuje dostęp do tego rejestru kompilatorowi, również utrudniając mu zadanie optymalizacji.<br /><br />Nie należy się tym oczywiście zniechęcać. Dobrze jest jednak o tym wiedzieć i pamiętać, aby nie przesadzać z długością i ilością funkcji pisanych w ASM oraz z ilością używanych w nich rejestrów.<br /><br />Na koniec mam też dobrą wiadomość. Przerwania w mikrokontrolerze występują w zupełnie przypadkowych momentach. Skoro nie da się przewidzieć tych momentów, kompilator i tak <strong>nie będzie mógł zoptymalizować kodu</strong> poprzez taki dobór rejestrów, by nie trzeba było ich zapamiętywać, gdyż (ze względu na tę przypadkowość) procedura obsługi przerwania <strong>musi zawsze zapamiętać wszystkie rejestry oraz rejestr statusowy SREG</strong> na początku procedury i przywrócić ich stan przed powrotem do programu głównego. <strong>Dlatego też napisanie procedury obsługi przerwania w ASM nie wpłynie na możliwości optymalizacyjne kompilatora C</strong>. Ewentualne użycie nadmiernej liczby rejestrów wpłynie jedynie na czas wykonania samej procedury, ale nie wpłynie na optymalizację kompilatora C. W związku z tym właśnie w procedurach obsługi przerwań będziemy mieli prawdziwe pole do popisu.</li></ul><br /><span style="font-size: 175%; line-height: normal">Różnice w składni asemblera</span><br /><br />Asembler AVR GCC i ten Atmela różnią się nieco składnią. Oczywiście nie dotyczy to samych instrukcji ASM, a raczej sposobu deklarowania stałych, danych w pamięci RAM lub FLASH, dołączania plików itp. Odnoszę wrażenie, że trochę trudno dotrzeć do szczegółowej dokumentacji, więc przedstawię najważniejsze różnice, które udało mi się znaleźć.<br /><br /><ul><li><span style="font-size: 125%; line-height: normal">Dołączanie plików z definicjami mikrokontrolera:</span><br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">    Atmel assembler (avrasm2)    |     GCC assembler (avr-as)<br />---------------------------------|--------------------------<br />.include &quot;m644pdef.inc&quot;          | #include &lt;avr/io.h&gt;<br />#include &quot;m644pdef.inc&quot;          |</div><br /></li><li><span style="font-size: 125%; line-height: normal">Nadawanie rejestrom nazw symbolicznych:</span><br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">    Atmel assembler (avrasm2)    |     GCC assembler (avr-as)<br />---------------------------------|--------------------------<br />.def my_reg=r16                  | #define my_reg r16 <br />                                 | my_reg = 16</div><br /></li><li><span style="font-size: 125%; line-height: normal">Definiowanie stałych:</span><br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">    Atmel assembler (avrasm2)      |     GCC assembler (avr-as)<br />-----------------------------------|--------------------------<br />.equ mask=0x21 ; nie można zmienić | #define mask 0x21<br />.set mask=0x21 ; można zmienić     | .equ mask, 0x21<br />      ; w dalszej części programu  | .set mask, 0x21</div><br /></li><li><span style="font-size: 125%; line-height: normal">Wydobywanie poszczególnych bajtów ze stałych wielobajtowych.</span><br /><br />Kiedy zdefiniujemy stałą, której wartość wykracza poza 8 bitów, i będziemy chcieli ją załadować do kilku rejestrów, musimy wydobyć jakoś poszczególne bajty z takiej stałej. Służą do tego specjalne instrukcje dla preprocesora umieszczane w kodzie:<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">   bity   |    Atmel assembler (avrasm2)    |    GCC assembler (avr-as)<br />----------|---------------------------------|------------------------------<br />  0 - 7   |   low(expression)               |   lo8(expression)<br />----------|---------------------------------|------------------------------<br />  8 - 15  |   high(expression)              |   hi8(expression)<br />          |   byte2(expression)             |<br />----------|---------------------------------|------------------------------<br /> 16 - 23  |   byte3(expression)             |   hh8(expression)<br />          |                                 |   hlo8(expression)<br />----------|---------------------------------|------------------------------<br /> 24 - 31  |   byte4(expression)             |   hhi8(expression)</div><br /><br /><strong>Przykład dla GCC assembler:</strong><br /><br />[syntax=asm]#define moja_stala_32bit 1258453 ; hex 00 13 33 D5<br /><br />    .section .text<br /><br />load_const_to_registers:<br />    ldi r16, lo8(moja_stala_32bit)  ; r16 = 0xD5<br />    ldi r17, hi8(moja_stala_32bit)  ; r17 = 0x33<br />    ldi r18, hlo8(moja_stala_32bit) ; r18 = 0x13<br />    ldi r19, hhi8(moja_stala_32bit) ; r19 = 0x00<br />    ret[/syntax]<br /><br />AVR GCC assembler oferuje dodatkowo instrukcje, które są przydatne przy pobieraniu adresu, gdyż przetwarzają adres bajtu we flash na adres słowa. Jest to przydatne na przykład, gdy chcemy wywołać funkcję poprzez wskaźnik za pomocą instrukcji ASM <strong>icall</strong> (indirect call) lub wykonać skok do określonej przez rejestr wskaźnikowy lokalizacji w programie za pomocą instrukcji <strong>ijmp</strong> (indirect jump).<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">   bity   |    GCC assembler (avr-as)<br />----------|-----------------------------------------------------------<br />  0 - 7   |   pm_lo8(expression)<br />----------|-----------------------------------------------------------<br />  8 - 15  |   pm_hi8(expression)<br />----------|-----------------------------------------------------------<br /> 16 - 23  |   pm_hh8(expression)  ; tylko w przypadku pamięci flash<br />          |                       ; powyżej 64K słów (128KB)</div><br /><br /><strong>Przykład:</strong><br /><br />[syntax=asm].section .text<br /><br />funkcja_1:<br />    ldi zl, pm_lo8(funkcja_2)<br />    ldi zh, pm_hi8(funkcja_2)<br />    icall<br />    ret<br /><br />funkcja_2:<br />    ret[/syntax]<br /></li><li><span style="font-size: 125%; line-height: normal">Sekcje / segmenty.</span><br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">    Atmel assembler (avrasm2)      |     GCC assembler (avr-as)<br />-----------------------------------|--------------------------<br />.dseg                              | .section .data<br />.cseg                              | .section .text<br />.eseg                              | .section .eeprom</div><br /></li><li><span style="font-size: 125%; line-height: normal">Deklarowanie stałych w pamięci FLASH lub EEPROM:</span><br /><br />Oczywiście deklaracje należy umieszczać w odpowiedniej sekcji (<strong>.section .text</strong> lub <strong>.section .eeprom</strong>) oraz przed każdą deklaracją musi znajdować się etykieta, ponieważ tylko w ten sposób będzie można określić adres, pod którym dane się znajdują.<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">    Atmel assembler (avrasm2)     |     GCC assembler (avr-as)<br />----------------------------------|--------------------------------------<br />              ciąg znaków (zakończony znakiem '\0')<br />.db „example text”, 0             | .asciz „example text” // znak '\0'<br />                                  |       // jest dodawany automatycznie<br />-------------------------------------------------------------------------<br />                         stałe 1-bajtowe<br />.db 0, 0x26, 0b01101001           | .byte 0, 0x26, 0b01101001<br />-------------------------------------------------------------------------<br />                         stałe 2-bajtowe<br />.dw 0, 5846, 0x15AF               | .word  0, 5846, 0x15AF<br />-------------------------------------------------------------------------<br />                         stałe 4-bajtowe<br />.dd 15, 158933, 0x056A0D2E        | .long 15, 158933, 0x056A0D2E<br />-------------------------------------------------------------------------<br />                         stałe 8-bajtowe<br />.dq 689958741, 0x54A6d32F5011AE1C | .quad 689958741, 0x54A6d32F5011AE1C <br />-------------------------------------------------------------------------</div><br /></li><li><span style="font-size: 125%; line-height: normal">Deklarowanie zmiennych w pamięci RAM:</span><br /><br />Tak jak poprzednio należy również pamiętać o umieszczeniu deklaracji w odpowiedniej sekcji (tym razem w <strong>.section .data</strong>) i opatrzeniu ich etykietami.<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">    Atmel assembler (avrasm2)     |     GCC assembler (avr-as)<br />----------------------------------|--------------------------------------------------<br />              ciąg znaków (zakończony znakiem '\0')<br />.byte 13  ; liczba oznacza ilość  | .asciz „example text” ; znak '\0'<br />          ; znaków w ciągu (+1)   |        ; jest dodawany automatycznie<br />-------------------------------------------------------------------------------------<br />                       zmienne 1-bajtowe<br />.byte 3  ; rezerwuje 3 bajty      | .byte 0, 0x26, 0b01101001 ;3 bajty<br />-------------------------------------------------------------------------------------<br />                      zmienne 2-bajtowe<br />.byte 6  ; rezerwuje 3*2 bajty    | .word  0, 5846, 0x15AF ; 3*2 bajty<br />-------------------------------------------------------------------------------------<br />                      zmienne 32-bitowe<br />.byte 12 ; rezerwuje 3*4 bajty    | .long 15, 158933, 0x056A0D2E ; 3*4 bajty<br />-------------------------------------------------------------------------------------<br />                      zmienne 64-bitowe<br />.byte 16 ; rezerwuje 2*8 bajtów   | .quad 689958741, 0x54A6d32F5011AE1C ; 2*8 bajtów<br />-------------------------------------------------------------------------------------</div><br /><br />Tutaj jednak różnice nie dotyczą tylko składni. W przypadku projektu napisanego tylko w języku asemblera (Atmel assembler), z przyczyn oczywistych na programiście spoczywa obowiązek zainicjowania zmiennych odpowiednimi wartościami przed ich użyciem.<br /><br />Jeśli tworzymy projekt w języku C, to kompilator powinien zainicjować za nas zmienne zadeklarowane w kodzie ASM wartościami, które podamy przy deklaracji, np.:<br />[syntax=asm].section .data<br />my_asm_var: .byte 7 ; to polecenie rezerwuje miejsce dla zmiennej jednobajtowej<br />                    ; i przypisuje jej wartość 7<br />my_asm_var: .byte 7, 19, 121 ; to polecenie rezerwuje miejsce dla tablicy trzech<br />                    ; elementów jednobajtowych i przypisuje im wartości 7, 19 i 121[/syntax]<br /><br />Z moich doświadczeń wynika jednak, że <strong>nie zawsze to robi</strong> (nie udało mi się znaleźć odpowiedzi na pytanie, czy jest to działanie zamierzone).<br /><br />Jeśli w kodzie C będziemy mieli zadeklarowaną co najmniej jedną zmienną statyczną z przypisaniem wartości niezerowej, np.:<br />[syntax=c]// globalnie, czyli poza funkcjami<br />uint8_t my_c_var = 5;<br />// lub wewnątrz jakiejś funkcji<br />static uint8_t my_c_var = 5;[/syntax]<br />to kompilator C wygeneruje kod, który podczas startu mikrokontrolera zainicjuje zarówno zmienne zadeklarowane w kodzie C, jak i te zadeklarowane w kodzie ASM.<br /><br />Jeśli w kodzie C nie będziemy mieli zadeklarowanej takiej zmiennej, o której mowa powyżej, to kompilator zwyczajnie podczas startu mikrokontrolera wyzeruje tylko wszystkie komórki pamięci przeznaczone na zmienne (zadeklarowane w kodzie C), uwzględni wprawdzie miejsce na zmienne zadeklarowane w ASM, ale pominie tworzenie kodu inicjującego wartości.<br /><br />Jeśli wystąpi taka sytuacja, a zależy nam na zainicjowaniu zmiennych zadeklarowanych w kodzie ASM konkretnymi wartościami podczas startu, mamy dwa sposoby rozwiązania problemu:<br /><ol style="list-style-type: decimal"><li>Przeniesienie deklaracji zmiennej do pliku C i tam przypisanie jej wartości (później opcjonalnie zadeklarowanie w pliku ASM jako .extern, choć nie jest to wymagane). Jeśli jednak nie będzie to zmienna współdzielona przez oba kody, nie jest to (moim zdaniem) dobre rozwiązanie, dlatego że zmienna używana <strong>tylko w pliku ASM powinna być tylko dla tego pliku widoczna</strong> (osiągalna).</li><li>Dopisanie w kodzie ASM procedury wpisującej wartości do odpowiednich komórek pamięci, i umieszczenie jej np. w sekcji .init1, której kod jest wykonywany podczas startu mikrokontrolera, jeszcze przed wejściem do funkcji main().<br /><br /><strong><em>Przykład:</em></strong><br />[syntax=asm]#include &lt;avr/io.h&gt;<br />; ewentualne definicje<br /><br />    .section .data<br />; zmienna, która ma być widoczna tylko w pliku asm<br />moja_zmienna_asm: .byte 0x21<br /><br />; zainicjowanie wartości zmiennej<br />    .section .init1,&quot;ax&quot;,@progbits<br /><br />    ldi r16, 0x21<br />    sts moja_zmienna_asm, r16<br /><br />    .section .text<br /><br />    .global moja_funkcja_asm<br /><br />moja_funkcja_asm:<br />    ; tutaj kod funkcji<br />    ret[/syntax]</li></ol><br /></li><li><span style="font-size: 125%; line-height: normal">Operacje na rejestrach wejścia/wyjścia</span><br /><br />Mikrokontrolery posiadają szereg rejestrów specjalnych (tzw. rejestry wejścia/wyjścia, tutaj przyjmijmy skrótową nazwę: rejestry i/o), które służą do sterowania pracą wbudowanych w niego układów peryferyjnych. W mikrokontrolerach AVR 8-bitowych rejestry te są podzielone na dwie grupy.<br /><br />Pierwsze 64 rejestry mogą być odczytywane i modyfikowane zarówno przez instrukcje asemblera <strong>in</strong> oraz <strong>out</strong> operujące na przestrzeni adresowej i/0 jak i poprzez instrukcje <strong>load</strong> oraz <strong>store</strong> operujące na przestrzeni adresowej pamięci danych SRAM. Oczywiście nie ma takich instrukcji asemblera <strong>load</strong> oraz <strong>store</strong>. Pod tym pojęciem rozumiem tutaj wszystkie instrukcje czytające z pamięci (np. <strong>ld</strong>, <strong>lds</strong>) jak i zapisujące do pamięci (np. <strong>st</strong>, <strong>sts</strong>).<br /><br />Jeśli mikrokontroler jest rozbudowany na tyle, że musi mieć więcej niż 64 rejestry (maksymalnie może mieć 160 dodatkowych rejestrów), dodatkowe rejestry są osiągalne tylko poprzez instrukcje <strong>load</strong> oraz <strong>store</strong>.<br /><br />W tej chwili interesuje nas ta pierwsza grupa rejestrów. Ich adresy w przestrzeni i/o mieszczą się w zakresie 0-63 (0x00-0x3F heksadecymalnie), natomiast w przestrzeni adresowej pamięci mają adresy 32-95 (0x20-0x5F heksadecymalnie). Relacja między adresami jest taka, że te w przestrzeni pamięci są większe o 32 (0x20 heksadecymalnie) od tych w przestrzeni i/o.<br /><br />Z punktu widzenia szybkości wykonania programu korzystniejsze są instrukcje in oraz out, dlatego dobrze jest korzystać z nich (zamiast <strong>load</strong> oraz <strong>store</strong>) kiedy to tylko możliwe. Korzystając z Atmel assembler robimy to zwyczajnie pisząc np.:<br /><br />[syntax=asm]in r16, PINA[/syntax]<br /><br />GCC assembler korzysta jednak z tego samego pliku nagłówkowego (&lt;avr/io.h&gt;), co kompilator C. Wszystkie adresy rejestrów i/o są tam zdefiniowane jako adresy w przestrzeni adresowej pamięci. Kiedy kompilator uzna, że należy użyć instrukcji operujących na przestrzeni adresowej i/o, automatycznie odejmuje 0x20 od adresu podczas generowania takiej instrukcji. Niestety, jeśli piszemy wstawki asemblerowe musimy sami zadbać o użycie właściwego adresu. Jeśli więc chcemy użyć instrukcji <strong>in</strong> lub <strong>out</strong> (ewentualnie którejś z instrukcji operowania na bitach rejestru i/o, np. <strong>sbi</strong>), powinniśmy użyć do tego makra <strong>_SFR_IO_ADDR()</strong>, przykładowo:<br /><br />[syntax=asm]in r16, _SFR_IO_ADDR(PINA)[/syntax]<br /><br />Może to być nieco kłopotliwe, istnieje jednak sposób, aby pozbyć się tej niedogodności. Polega on na dodaniu na samym początku pliku ASM (jeszcze przed dyrektywą #include &lt;avr/io.h&gt;) definicji:<br /><br />[syntax=asm]#define __SFR_OFFSET 0<br />#include &lt;avr/io.h&gt;<br />    ; pozostałe definicje dyrektywy<br />    ; jakiś kod<br />    in r16, PINA   ; bez konieczności użycia _SFR_IO_ADDR()<br />    ; reszta kodu[/syntax]<br /><br /><em><strong><span style="color: #0000BF">We wszystkich przedstawionych tu przykładowych fragmentach kodu zostało przyjęte, że powyższa definicja została umieszczona na początku pliku.</span></strong></em></li></ul><br /><span style="font-size: 175%; line-height: normal">Reguły stosowane przez kompilator GCC</span><br /><br />Mieszając kod ASM z kodem C musimy stosować takie same reguły co kompilator C. Przykładowo, chcąc wywołać funkcję napisaną w języku C z kodu w języku ASM, musimy wiedzieć, w jakich rejestrach umieścić argumenty przekazywane do funkcji i w jakich rejestrach oczekiwać wartości zwracanych przez funkcję. W odwrotnym przypadku, gdy piszemy w kodzie ASM funkcję, która będzie wywoływana z kodu C, też musimy wiedzieć, w jakich rejestrach kompilator umieści argumenty i w jakich będzie oczekiwał wartości zwracanej przez naszą funkcję. Poza tym kompilator C traktuje niektóre rejestry lub grupy rejestrów w ściśle określony sposób i my też musimy te same zasady stosować.<br /><br /><ul><li><span style="font-size: 125%; line-height: normal">Przekazywanie argumentów do funkcji</span><br /><br />W języku C funkcje mogą (choć nie muszą) przyjmować argumenty, czyli innymi słowami jakieś dane, którymi będą operować (wykonywać jakieś obliczenia, porównania itp.). Przekazanie argumentu do funkcji polega na podaniu ich w nawiasie po nazwie funkcji.<br /><br />W języku ASM samo wywołanie jest stosunkowo proste. Służy do tego celu np. instrukcja <strong>call</strong>, która ma jeden operand. Tym operandem jest etykieta, która wyznacza adres początku funkcji (dokładniej adres słowa w pamięci FLASH, w którym znajduje się pierwsza instrukcja funkcji).<br /><br />Skoro instrukcja <strong>call</strong> ma tylko jeden operand, to jak przekazać argumenty do funkcji?<br />Można to zrobić na przykład w taki sposób:<br />- najpierw do wybranych rejestrów wprowadzić dane, które chcemy przekazać do funkcji,<br />- później wywołać funkcję np. za pomocą instrukcji <strong>call</strong>,<br />- wywołana funkcja odczytuje dane z wybranych rejestrów i wykonuje na nich wymagane operacje.<br /><br />Jeśli wywoływana funkcja jest również napisana przez nas w ASM, to możemy do tego celu wyznaczyć dowolne rejestry wedle uznania. Wystarczy, że funkcję napiszemy tak, aby czytała dane z właściwych rejestrów. Jeśli jednak w pliku ASM wywołujemy funkcję napisaną w języku C, musimy wiedzieć, w których rejestrach funkcja ta oczekuje argumentów, ponieważ nie my o tym decydujemy, tylko kompilator.<br /><br />Na szczęście kompilator C stosuje pewne stałe reguły określające, w których rejestrach mają być przekazywane argumenty do funkcji. Zostały do tego celu wyznaczone rejestry <strong>r25 - r8</strong>. Rejestry są pogrupowane w pary. Najmniej znaczący bajt argumentu jest zawsze umieszczany w rejestrze o parzystym numerze. Kolejne bajty argumentu są umieszczane w kolejnych rejestrach o wyższych numerach. Kolejne argumenty są umieszczane w rejestrach poczynając od wyższych numerów, kończąc na niższych. Precyzyjnie sposób wyznaczania rejestrów do przekazania argumentów wygląda następująco:<br /><ol style="list-style-type: decimal">Za rejestr (nazwijmy go) &quot;bazowy&quot; przyjmujemy <strong>r26</strong> i wykonujemy następujące kroki<br /><li>Jeśli rozmiar argumentu jest liczbą nieparzystą, dodajemy do rozmiaru 1.</li><li>Tak obliczony rozmiar odejmujemy od numeru rejestru bazowego, uzyskując nowy numer rejestru bazowego.</li><li>Jeśli nowy numer rejestru bazowego jest większy od <strong>r8</strong>, będzie do niego wpisany najmniej znaczący bajt naszego argumentu. Kolejne bajty będą wpisane do kolejnych rejestrów (w kierunku wyższych numerów).</li><li>Jeśli numer rejestru będzie mniejszy od <strong>r8</strong>, argument zostanie umieszczony w pamięci RAM. Tutaj nie będziemy rozpatrywać takiego przypadku, ponieważ zakładamy pisanie stosunkowo prostych funkcji jak wstawek ASM. Za pomocą wyznaczonych do tego celu rejestrów możemy do funkcji przekazać maksymalnie 18 bajtów, więc w zdecydowanej większości przypadków będzie to ilość wystarczająca.</li><li>Jeśli aktualny argument powinien być umieszczony w pamięci RAM, poprzestajemy na tym, ponieważ to oznacza, że wszystkie następne argumenty również muszą być umieszczone w RAM.</li><li>Jeśli mamy do przekazania następny argument, wracamy do punktu 1. (oczywiście przyjmując do obliczeń nowo wyznaczony numer rejestru bazowego).</li></ol><br />Wygląda to może nieco skomplikowanie, więc podam kilka przykładów:<br /><br /><strong><em>Przykład 1:</em></strong><br /><br />[syntax=c]void funkcja(uint32_t arg1, int8_t arg2, int16_t arg3);[/syntax]<br /><strong>arg1:</strong> 4 bajty<br /><br /><ol style="list-style-type: decimal"><li><strong>4</strong> jest liczbą parzystą, więc pozostaje bez zmian.</li><li><strong>26 - 4 = 22</strong> rejestr bazowy dla pierwszego argumentu to <strong>r22</strong>.</li><li>Numer rejestru <strong>r22</strong> jest większy od numeru rejestru <strong>r8</strong> więc przypisujemy:<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">r22 = arg1&#91;byte0&#93;<br />r23 = arg1&#91;byte1&#93;<br />r24 = arg1&#91;byte2&#93;<br />r25 = arg1&#91;byte3&#93;</div></li><li>Nie dotyczy naszego przypadku.</li><li>Nie dotyczy naszego przypadku.</li><li>Kontynuujemy z następnym argumentem.</li></ol><br /><strong>arg2:</strong> 1 bajt<br /><br /><ol style="list-style-type: decimal"><li><strong>1</strong> jest liczbą nieparzystą, więc dodajemy 1 i otrzymujemy rozmiar równy <strong>2</strong></li><li><strong>22 - 2 = 20</strong> rejestr bazowy dla drugiego argumentu to <strong>r20</strong>.</li><li>Numer rejestru r20 jest większy od numeru rejestru r8 więc przypisujemy:<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">r20 = arg2&#91;byte0&#93;</div></li><li>Nie dotyczy naszego przypadku.</li><li>Nie dotyczy naszego przypadku.</li><li>Kontynuujemy z następnym argumentem.</li></ol><br /><strong>arg3:</strong> 2 bajty<br /><br /><ol style="list-style-type: decimal"><li>2 jest liczbą parzystą, więc pozostaje bez zmian.</li><li>20 - 2 = 18 rejestr bazowy dla pierwszego argumentu to r18.</li><li>Numer rejestru r18 jest większy od numeru rejestru r8 więc przypisujemy:<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">r18 = arg3&#91;byte0&#93;<br />r19 = arg3&#91;byte1&#93;</div></li><li>Nie dotyczy naszego przypadku.</li><li>Nie dotyczy naszego przypadku.</li><li>Kończymy - to był ostatni argument.</li></ol><br /><strong><em>Przykład 2:</em></strong><br /><br />[syntax=c]void funkcja(uint32_t *arg1, int8_t *arg2, uint8_t arg3);[/syntax]<br /><br /><strong>arg1:</strong> 2 bajty ( wskaźnik = 16 bit )<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">r24 = arg1&#91;byte0&#93;<br />r25 = arg1&#91;byte1&#93;</div><br /><strong>arg2:</strong> 2 bajty<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">r22 = arg2&#91;byte0&#93;<br />r23 = arg2&#91;byte1&#93;</div><br /><strong>arg3:</strong> 1 bajt<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">r20 = arg3&#91;byte0&#93;</div><br /><br /><br /><strong><em>Przykład 3:</em></strong><br /><br />[syntax=c]void funkcja(uint8_t arg1, uint8_t *arg2, int64_t arg3);[/syntax]<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">r14 = arg3&#91;byte0&#93;<br />r15 = arg3&#91;byte1&#93;<br />r16 = arg3&#91;byte2&#93;<br />r17 = arg3&#91;byte3&#93;<br />r18 = arg3&#91;byte4&#93;<br />r19 = arg3&#91;byte5&#93;<br />r20 = arg3&#91;byte6&#93;<br />r21 = arg3&#91;byte7&#93;<br /><br />r22 = arg2&#91;byte0&#93;<br />r23 = arg2&#91;byte1&#93;<br /><br />r24 = arg1&#91;byte0&#93;<br /></div><br /></li><li><span style="font-size: 125%; line-height: normal">Wartości zwracane przez funkcje</span><br /><br />Jeżeli pisząc w ASM będziemy wywoływali funkcję napisaną w C lub będziemy pisali funkcję w ASM wywoływaną w pliku C, musimy znać też rejestry, w których - po wykonaniu funkcji - znajdzie się wartość zwracana przez tę funkcję (oczywiście tylko wtedy, gdy jakaś wartość ma być zwracana, bo przecież nie zawsze musi być).<br /><br />Tutaj sprawa wygląda prościej, gdyż zwracana wartość jest jedna, a nie kilka. Wartość zwracana przez funkcję jest umieszczana w rejestrach, jeśli jej rozmiar nie przekroczy <strong>8 bajtów</strong>, czyli zdecydowanie mniej niż w przypadku argumentów przekazywanych do funkcji, ale i tak w większości przypadków rozmiar <strong>64-bitowy</strong> jest wystarczający. Właściwie w avr-libc nie ma np. typu całkowitego o większym rozmiarze, a typy zmiennoprzecinkowe float oraz double mają po 32-bity. Jedynym przypadkiem przekroczenia limitu będzie więc zwrócenie przez funkcję struktury (poprzez wartość) o rozmiarze większym od ośmiu bajtów. Moim zdaniem jednak zarówno przekazywanie do funkcji, jak i zwracanie przez funkcję dużych struktur poprzez wartość w ośmiobitowych  mikrokontrolerach, mających stosunkowo małe pojemności pamięci RAM, nie jest dobrą praktyką.<br /><br />Wyznaczanie rejestrów wygląda podobnie, jak w przypadku argumentów (rejestr bazowy to także <strong>r26</strong>), a więc przyporządkowanie rejestrów będzie wyglądać tak:<br /><strong>1 bajt</strong><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">byte0<br /> r24</div><br /><br /><strong>2 bajty</strong><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">&#91;byte1&#93;   &#91;byte0&#93;<br />  r25       r24</div><br /><br /><strong>4 bajty</strong><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">&#91;byte3&#93;   &#91;byte2&#93;   &#91;byte1&#93;   &#91;byte0&#93;<br />  r25       r24       r23       r22</div><br /><br /><strong>8 bajtów</strong><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">&#91;byte7&#93;   &#91;byte6&#93;   &#91;byte5&#93;   &#91;byte4&#93;   &#91;byte3&#93;   &#91;byte2&#93;   &#91;byte1&#93;   &#91;byte0&#93;<br />  r25       r24       r23       r22       r21       r20       r19       r18</div><br /><br /></li><li><span style="font-size: 125%; line-height: normal">Rejestry stałe</span><br /><br />Rejestry stałe to rejestry o specjalnym przeznaczeniu, które nie są alokowane przez kompilator wprost do operacji na danych:<br /><ul><li><strong>r0</strong><br />Jest to rejestr do przechowywania tymczasowych danych, którego wartość nie musi być przywracana po jego użyciu. Jedynym wyjątkiem są procedury obsługi przerwań, gdzie rejestr ten - jeśli jest używany wewnątrz procedury, jego zawartość musi być zapamiętana w prologu i przywrócona w epilogu.<br />W <em>inline assembler</em> może być używany za pośrednictwem symbolu <strong><em>__tmp_reg__</em></strong> jako rejestr tymczasowy.<br /></li><li><strong>r1</strong><br />Rejestr ten jest przeznaczony do przechowywania wartości zerowej. Jego wartość musi być zawsze równa zero. Nie zawsze jednak da się to osiągnąć, choćby ze względu na to, że jest to jeden z rejestrów wyjściowych dla instrukcji <strong>mul</strong>. Jeśli jednak jego wartość zostanie zmieniona przez jakąś funkcję, musi być ponownie wyzerowana (najpóźniej przed jej zakończeniem). Nie należy jednak zerować tego rejestru, kiedy jego wartość została zmieniona w procedurze obsługi przerwania. Należy go zapamiętać w prologu i przywrócić w epilogu, gdyż nigdy nie wiadomo, w jakim momencie wystąpi przerwanie i jaka będzie w tym momencie wartość tego rejestru (po zakończeniu procedury musi być taka sama jak przed).<br />Rejestr ten może być przydatny np. przy instrukcjach porównania. Nie wszystkie rejestry mają możliwość porównania z wartością stałą (instrukcja <strong>cpi</strong> obsługuje tylko rejestry <strong>r16-r31</strong>). Dzięki temu rejestrowi można szybko porównać dowolny rejestr z wartością 0x00 (przy pomocy instrukcji <strong>cp</strong>, która obsługuje wszystkie rejestry), bez konieczności wykonania najpierw operacji zerowania. Niestety nie wolno w ten sposób używać rejestru <strong>r1</strong> w procedurze obsługi przerwania, gdyż nie można wtedy założyć, że rejestr ten ma wartość zero.<br />W <em>inline assembler</em> można go używać za pomocą symbolu <strong><em>__zero_reg__</em></strong> jako rejestr o wartości zerowej.</li></ul><br /></li><li><span style="font-size: 125%; line-height: normal">Rejestry niszczone przez funkcje</span><br /><br />Każda funkcja musi używać rejestrów, co oznacza, że zawartość rejestrów podczas wykonywania funkcji zostaje zmieniona. Kompilator C przyjmuje, że zawartość rejestrów <strong>r18-r27</strong>, <strong>r30-r31</strong>, <strong>r0</strong>, <strong>flaga T w SREG</strong> może ulec zniszczeniu podczas działania funkcji.<br /><br />Jeśli pisząc wstawkę w ASM zależy nam na zachowaniu wartości któregoś z wyżej wymienionych rejestrów po zakończeniu wykonania funkcji napisanej w C, musimy przed jej wywołaniem zapamiętać tę wartość (np. na stosie).<br /><br />Z kolei pisząc funkcję w ASM, która będzie wywoływana w kodzie C, możemy dowolnie korzystać z tych rejestrów, nie martwiąc się o to, że zniszczymy ich zawartość. Nie trzeba ich zapamiętywać na początku i przywracać na końcu funkcji.<br /><br />Wyjątkiem są tutaj oczywiście (o czym pisałem już wcześniej) <strong>procedury obsługi przerwań</strong>, które zawsze muszą zapamiętywać wszystkie używane przez siebie rejestry, jak i rejestr statusowy SREG.<br /><br /></li><li><span style="font-size: 125%; line-height: normal">Rejestry zachowywane przez funkcje</span><br /><br />Odwrotna zasada dotyczy pozostałych rejestrów, czyli <strong>r2-r17</strong>, <strong>r28-r29</strong>. Wywołując w pliku ASM funkcję C możemy założyć, że zawartość tych rejestrów nie zostanie zmieniona.<br /><br />Pisząc w ASM funkcję wywoływaną później w pliku C, jeśli chcemy skorzystać z tych rejestrów, musimy zadbać o zapamiętanie zawartości tych rejestrów na początku funkcji i przywrócenie na końcu.<br /><br />Do tej grupy rejestrów można zaliczyć również rejestr <strong>r1</strong>, jednak należy pamiętać, że rejestr ten jest rejestrem specjalnym (&quot;zerowym&quot;) i obowiązują go trochę inne reguły opisane wcześniej.</li></ul><br /><span style="font-size: 175%; line-height: normal">Miksowanie</span><br /><br />Zaznaczam, że nie jest tutaj moim zamiarem udowadnianie, że kod napisany przez programistę jest wydajniejszy od tego wygenerowanego przez kompilator. Chodzi mi tylko o pokazanie, jak technicznie wykonać miksowanie, więc przedstawione tu przykładowe fragmenty kodu będą stosunkowo banalne i zapewne niepraktyczne.  Na koniec podam nieco obszerniejszy przykład przy okazji omawiania wykorzystania wspólnego pliku nagłówkowego.<br /><br /><em>Jak już wspomniałem wcześniej (podrozdział &quot;Operacje na rejestrach wejścia/wyjścia&quot;), możliwość użycia instrukcji <strong>in</strong> i <strong>out</strong> zamiast <strong>load</strong> i <strong>store</strong> jest uzależnione od adresu rejestru. W poniższych przykładach przyjąłem adresy rejestrów mikrokontrolera <strong>ATmega644P</strong></em><br /><br /><ul><li><span style="font-size: 125%; line-height: normal">Współdzielenie zmiennych</span><br /><br />Wprawdzie podałem tu przykłady współdzielenia zmiennych przez zwykłe funkcje, jednak bardziej uzasadnione jest współdzielenie zmiennych np. poprzez funkcje C i procedurę obsługi przerwania napisaną w ASM. Moim zdaniem, w przypadku zwykłych funkcji, lepiej przekazywać dane poprzez argumenty i zwracanie wartości.<br /><br />Oczywiście w przypadku współdzielenia zmiennej pomiędzy procedurą obsługi przerwania w ASM a funkcjami C, należy pamiętać o dodaniu słowa kluczowego volatile.<br /><br /><ul><li><span style="font-size: 110%; line-height: normal"><strong><em>Zmienna zadeklarowana w pliku C dostępna dla kodu ASM</em></strong></span><br /><br />W pliku C deklarujemy zmienną globalną:<br /><br />[syntax=c]uint16_t counter;[/syntax]<br /><br />W pliku ASM można zadeklarować zmienną <strong>.extern</strong>, choć nie jest to konieczne:<br /><br />[syntax=asm].extern counter[/syntax]<br /><br />Przypominam, że w plikach ASM nie deklarujemy typu zmiennej. Pomimo tego, że deklaracja <strong>.extern</strong> nie jest konieczna, uważam że warto to zrobić np. w taki sposób (typ można podać w komentarzu):<br /><br />[syntax=asm]; zmienna globalna w pliku *.c<br />; wynik obliczeń funkcji 'moja_funkcja'<br />.extern counter ; uint16_t[/syntax]<br /><br />Dzięki temu, bez konieczności przełączania się do pliku *.c możemy sobie przypomnieć nazwę, typ i przeznaczenie zmiennej.<br /><br /><strong>Przykład użycia:</strong><br /><br />[syntax=asm].extern counter<br /><br />.global moja_funkcja<br /><br />moja_funkcja:<br />    ; tutaj ewentualny prolog, czyli zapamiętanie wartości<br />    ; używanych rejestrów na stosie<br />    lds  r24, counter      ; wczytujemy młodszy bajt<br />    lds  r25, counter+1    ; wczytujemy starszy bajt<br />    ; tutaj obliczenia<br />    sts  counter,   r24    ; zapisujemy młodszy bajt<br />    sts  counter+1, r25    ; zapisujemy starszy bajt<br />    ; tutaj ewentualny epilog, czyli przywrócenie wartości<br />    ; używanych rejestrów ze stosu<br />    ret[/syntax]<br /></li><li><span style="font-size: 110%; line-height: normal"><strong><em>Zmienna zadeklarowana w pliku ASM dostępna dla kodu C</em></strong></span><br /><br />W pliku ASM deklarujemy zmienną w sekcji .data:<br /><br />[syntax=asm].section .data<br /><br />; ewentualny opis zmiennej<br />counter: .word 0    ; uint16_t[/syntax]<br /><br />Tutaj należy pamiętać o możliwej konieczności zainicjowania wartości zmiennej, co opisałem w podrozdziale: &quot;Deklarowanie zmiennych w pamięci RAM&quot;, gdyż kompilator może za nas tego nie zrobić.<br /><br />W pliku C deklarujemy zmienną extern:<br /><br />[syntax=c]extern uint16_t counter;[/syntax]<br /><br />Pomimo tego, że w pliku ASM zmienna counter nie ma typu (tzn. ma tylko domyślnie przyjęty przez programistę), w pliku C musimy bezwzględnie podać typ zmiennej.</li></ul><br /></li><li><span style="font-size: 125%; line-height: normal">Wywoływanie funkcji napisanej w ASM z kodu napisanego w C</span><br /><br />W kodzie ASM etykiet używamy nie tylko do oznaczenia zmiennych czy też początku funkcji. Część etykiet, zwykle większa część, jest przeznaczona do oznaczenia miejsc, do których będą wykonywane skoki warunkowe (np. instrukcje brne, breq itp.), skoki bezwarunkowe (np. instrukcje rjmp, jmp itp.) lub do oznaczenia początków funkcji lokalnych, pomocniczych, które używane będą tylko w kodzie ASM. Nie jest wskazane, aby niepotrzebne etykiety były widoczne dla kodu C. Dlatego należy specjalnie oznaczyć tylko etykiety oznaczające wejścia do funkcji, które będą wywoływane z kodu C. Służy do tego słowo kluczowe .global<br /><br /><ul><li><span style="font-size: 110%; line-height: normal"><strong><em>Funkcja bez parametrów nie zwracająca wartości</em></strong></span><br /><br />Deklaracja i wywołanie funkcji w pliku C:<br /><br />[syntax=c]// deklaracja funkcji<br />void clear_timer1(void);<br /><br />// wywołanie funkcji<br />clear_timer1();[/syntax]<br /><br />Definicja funkcji w pliku ASM:<br /><br />[syntax=asm].global clear_timer1<br /><br />clear_timer1:<br />    ; jeśli korzystamy z przerwań należy zapewnić<br />    ; atomowy dostęp do 16-bitowych rejestrów<br />    cli<br />    ; nie jest to procedura obsługi przerwania<br />    ; tylko zwykła funkcja, możemy więc użyć<br />    ; rejestru r1 jako rejestru z wartością zerową<br />    ; rejestry TCNT1 w ATmega644P są poza zakresem i/o<br />    ; musimy użyć instrukcji sts<br />    sts TCNT1L, r1<br />    sts TCNT1H, r1<br />    ; jeśli korzystamy z przerwań, po wykonaniu<br />    ; atomowej operacji na rejestrze 16-bitowym,<br />    ; musimy ponownie włączyć globalne zezwolenie<br />    ; na przerwania<br />    sei<br />    ret[/syntax]<br /></li><li><span style="font-size: 110%; line-height: normal"><strong><em>Funkcja z parametrami nie zwracająca wartości</em></strong></span><br /><br />Deklaracja i wywołanie funkcji w pliku C:<br /><br />[syntax=c]// deklaracja funkcji<br />void set_timer1_pwm(uint16_t period, uint16_t pulse_width);<br /><br />// wywołanie funkcji<br />uint16_t per = 12500, width = 2000;<br />set_timer1_pwm(per, width);[/syntax]<br /><br />Definicja funkcji w pliku ASM:<br /><br />[syntax=asm];<br />    .global set_timer1_pwm<br /><br />set_timer1_pwm:<br />    cli<br />    ; argument 'period' znajduje się w rejestrach r25:r24<br />    ; wpisujemy go do rejestrów OCR1AH:OCR1AL<br />    ; podczas zapisu do rejestrów 16-bit najpierw bajt starszy<br />    sts OCR1AH, r25<br />    sts OCR1AL, r24<br />    ; argument 'pulse_width' znajduje się w rejestrach r23:r22<br />    ; wpisujemy go do rejestrów OCR1BH:OCR1BL<br />    sts OCR1BH, r23<br />    sts OCR1BL, r22<br />    sei<br />    ret[/syntax]<br /></li><li><span style="font-size: 110%; line-height: normal"><strong><em>Funkcja bez parametrów zwracająca wartość</em></strong></span><br /><br />Deklaracja i wywołanie funkcji w pliku C:<br /><br />[syntax=c]// deklaracja funkcji<br />uint16_t get_t1_input_capture(void);<br /><br />// wywołanie funkcji<br />uint16_t icp = get_t1_input_capture();[/syntax]<br /><br />Definicja funkcji w pliku ASM:<br /><br />[syntax=asm];<br />    .global get_t1_input_capture<br /><br />get_t1_input_capture:<br />    cli<br />    ; wartość zwracana musi znaleźć się w rejestrach r25:r24<br />    ; podczas odczytu z rejestrów 16-bit najpierw bajt młodszy<br />    lds r24, ICR1L<br />    lds r25, ICR1H<br />    sei<br />    ret[/syntax]<br /></li><li><span style="font-size: 110%; line-height: normal"><strong><em>Funkcja z parametrami zwracająca wartość</em></strong></span><br /><br />Deklaracja i wywołanie funkcji w pliku C:<br /><br />[syntax=c]// deklaracja funkcji<br />uint8_t get_array_element(uint8_t *array_ptr, uint8_t element_id);<br /><br />// wywołanie funkcji<br />uint8_t my_array&#91;&#93; = {15,21,233,187};<br />uint8_t element = get_array_element(my_array, 2);[/syntax]<br /><br />Definicja funkcji w pliku ASM:<br /><br />[syntax=asm];<br />    .global get_array_element<br /><br />get_array_element:<br />    ; dla treningu użyjemy rejestrów, których wartość<br />    ; musi być zachowana<br />    push yl     ; r28<br />    push yh     ; r29<br />    ; pierwszy argument: 16-bitowy wskaźnik do tablicy<br />    ; kompilator C umieści w rejestrach r25:r24<br />    ; drugi argument: 8-bitowy indeks tablicy<br />    ; kompilator C umieści w rejestrze r22<br />    ; by uzyskać wskaźnik do odczytywanego elementu<br />    ; tablicy musimy te dwa argumenty dodać<br />    add r24, r22<br />    adc r25, r1     ; wartość r1 = 0<br />    ; wartość rejestrów r25:r24 jest wskaźnikiem<br />    ; na odczytywany element tablicy<br />    ; przenosimy ten wskaźnik do rejestru<br />    ; wskaźnikowego 'y'<br />    movw y, r24<br />    ; 8-bitowa wartość odczytana z tablicy musi<br />    ; zostać zwrócona przez funkcję w rejestrze r24<br />    ld r24, y<br />    ; przywracamy wartości użytych rejestrów,<br />    ; które zapamiętaliśmy na początku<br />    pop yh      ; r29<br />    pop yl      ; r28<br />    ret[/syntax]<br /></li><li><span style="font-size: 110%; line-height: normal"><strong><em>Procedura obsługi przerwania</em></strong></span><br /><br />Procedura obsługi przerwania to specjalny rodzaj funkcji. Jest ona wywoływana automatycznie przez mikrokontroler w momencie wystąpienia zdarzenia. W przeciwieństwie do zwykłej funkcji nie da się przewidzieć, w którym momencie wykonywania programu głównego procedura zostanie wywołana, dlatego obowiązują tu inne reguły:<br /><ol style="list-style-type: decimal"><li>O ile funkcję możemy nazwać dowolnie, o tyle nazwa procedury obsługi przerwania (w przypadku asemblera - etykieta globalna) musi być zgodna z nazwą zdefiniowaną w plikach nagłówkowych mikrokontrolera dla danego przerwania. Prościej mówiąc, musi mieś nazwę, którą wpisalibyśmy w nawiasie używając makra ISR() pisząc procedurę w języku C (oczywiście przy użyciu GCC).</li><li><strong>Wszystkie rejestry używane wewnątrz procedury oraz rejestr statusowy SREG muszą zostać zapamiętane na stosie w prologu i odtworzone w epilogu procedury.</strong> Dotyczy to również rejestrów, których zniszczenie zawartości jest dopuszczalne w normalnej funkcji jak i rejestrów stałych: tymczasowego <strong>r0</strong> (który w normalnej funkcji nie musi być zapamiętywany i odtwarzany) oraz zerowego <strong>r1</strong> (który w normalnej funkcji musi zostać na końcu wyzerowany, jeśli jego zawartość uległa zmianie).<br />W wyjątkowych przypadkach, jeśli żadna z instrukcji wewnątrz procedury nie zmienia zawartości SREG, możemy pominąć jego zapamiętywanie i przywracanie.<br />Z drugiej strony należy pamiętać, że w przypadku wywołania jakiejś funkcji wewnątrz procedury, musimy uwzględnić w prologu i epilogu rejestry przez tę funkcję niszczone. </li><li>W odróżnieniu od normalnej funkcji kończonej instrukcją powrotu <strong>ret</strong>, procedura obsługi przerwania musi być zakończona instrukcją powrotu <strong>reti</strong>.</li></ol><br />Typowa konstrukcja procedury obsługi przerwania (dla przykładu przyjmijmy przepełnienie timer'a 0) powinna wyglądać tak:<br /><br />[syntax=asm];<br />    .global TIMER0_OVF_vect<br /><br />TIMER0_OVF_vect:<br />    ; ------ prolog - początek ----------<br />    push r16<br />    in r16, SREG<br />    push r16<br />    ; inne rejestry używane w procedurze<br />    ; przykładowo:<br />    push r26<br />    push r27<br />    ; ----- prolog - koniec -------------<br />    ;<br />    ; tutaj właściwy kod procedury<br />    ;<br />    ; ----- epilog - początek -----------<br />    pop r27<br />    pop r26<br />    pop r16<br />    out SREG, r16<br />    pop r16<br />    ; ----- epilog - koniec -------------<br />    reti[/syntax]<br /><br /><br />W przypadku, kiedy np. chcemy w procedurze tylko ustawić flagę odpowiednio interpretowaną później w programie głównym, (w niektórych mikrokontrolerach) możemy użyć do tego rejestru i/o - GPIOR. W procedurze obsługi przerwania zmieniamy tylko jeden rejestr, jednak żadna z instrukcji nie zmienia żadnego bitu w rejestrze SREG. Cała procedura mogłaby wtedy wyglądać np. tak:<br /><br />[syntax=asm].global TIMER0_OVF_vect<br /><br />TIMER0_OVF_vect:<br />    push r16<br />    ldi r16, 1<br />    out GPIOR1, r16<br />    pop r16<br />    reti[/syntax]<br /><br />Tak naprawdę to tutaj będziemy mieli główne pole do popisu. Napisanie procedury obsługi przerwania nie wpływa na jakość optymalizacji kompilatora, o czym pisałem wcześniej. Zauważyłem za to tendencje kompilatora do odkładania (w procedurach obsługi przerwań) na stos niepotrzebnych rejestrów, szczególnie w przypadku bardziej rozbudowanych procedur wywołujących dodatkowo jakieś funkcje. Dzięki temu, pisząc w ASM, można oszczędzić kilka cennych taktów. Dodatkowo mamy szansę np. na skrócenie operacji arytmetycznych poprzez ograniczenie rozmiaru zmiennej do niezbędnego minimum.</li></ul><br /></li><li><span style="font-size: 125%; line-height: normal">Wywoływanie funkcji napisanej w C z kodu napisanego w ASM</span><br /><br /><ul><li><strong><em><span style="font-size: 110%; line-height: normal">Funkcja bez parametrów nie zwracająca wartości</span></em></strong><br /><br />Definicja funkcji w pliku C:<br /><br />[syntax=c]void clear_timer1(void)<br />{<br />    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)<br />    {<br />        TCNT1 = 0;<br />    }<br />}[/syntax]<br /><br />Wywołanie funkcji w pliku ASM:<br /><br />[syntax=asm]; nie wymaga żadnych dodatkowych zabiegów<br />; wystarczy w odpowiednim miejscu<br />; umieścić instrukcję:<br />call clear_timer1[/syntax]<br /></li><li><strong><em><span style="font-size: 110%; line-height: normal">Funkcja z parametrami nie zwracająca wartości</span></em></strong><br /><br />Definicja funkcji w pliku C:<br /><br />[syntax=c]void set_timer1_pwm(uint16_t period, uint16_t pulse_width)<br />{<br />    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)<br />    {<br />        OCR1A = period;<br />        OCR1B = pulse_width;<br />    }<br />}[/syntax]<br /><br />Wywołanie funkcji w pliku ASM:<br /><br />[syntax=asm]; wymagane umieszczenie argumentów<br />; w odpowiednich rejestrach<br />; 'period' w rejestrach r25:r24<br />; przykładowo (wartość 12500 = 0x30D4):<br />ldi r25, 0x30<br />ldi r24, 0xD4<br />; 'pulse_width' w rejestrach r23:r22<br />; przykładowo (wartość 2000 = 0x07D0):<br />ldi r23, 0x07<br />ldi r22, 0xD0<br />; dopiero teraz wywołujemy funkcję<br />call set_timer1_pwm[/syntax]<br /></li><li><strong><em><span style="font-size: 110%; line-height: normal">Funkcja bez parametrów zwracająca wartość</span></em></strong><br /><br />Definicja funkcji w pliku C:<br /><br />[syntax=c]uint16_t get_t1_input_capture(void)<br />{<br />    uint16_t icr;<br />    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)<br />    {<br />        icr = ICR1;<br />    }<br />    return icr;<br />}[/syntax]<br /><br />Wywołanie funkcji w pliku ASM:<br /><br />[syntax=asm]; Funkcja nie posiada parametrów, więc w odpowiednim<br />; miejscu kodu po prostu ją wywołujemy. Jedyne<br />; co musimy ewentualnie zrobić, to zapamiętać wartości<br />; rejestrów, które chcemy zachować, a wywołanie<br />; funkcji może zniszczyć. Przyjmijmy, że chcemy zachować<br />; rejestry r19:r18, gdyż zawierają wartość z którą<br />; będzie porównywana wartość odczytana z ICR1<br />push r18<br />push r19<br />; wywołujemy funkcję<br />call get_t1_input_capture<br />; przywracamy wartość rejestrów<br />pop r19<br />pop r18<br />; wartość zwrócona będzie w rejestrach r25:r24<br />; wykonujemy na niej żądane operacje, przykładowo:<br />cp r24, r18<br />cpc r25, r19<br />brlo lable_if_lower<br />; dalsza część kodu[/syntax]<br /></li><li><strong><em><span style="font-size: 110%; line-height: normal">Funkcja z parametrami zwracająca wartość</span></em></strong><br /><br />Definicja funkcji w pliku C:<br /><br />[syntax=c]uint8_t get_array_element(uint8_t *array_ptr, uint8_t element_id)<br />{<br />    return array_ptr&#91;element_id&#93;;<br />}[/syntax]<br /><br />Wywołanie funkcji w pliku ASM:<br /><br />[syntax=asm]; zakładamy, że mamy w pliku *.c<br />; zadeklarowaną tablicę:<br />; uint8_t my_array&#91;&#93; = {0,1,2,3};<br />; argument pierwszy 'array_ptr'<br />; rejestry r25:r24<br />ldi r24, lo8(my_array)<br />ldi r25, hi8(my_array)<br />; argument drugi 'element_id'<br />; rejestr r22<br />ldi r22, 2 ; pobieramy drugi element<br />; wywołanie funkcji<br />call get_array_element<br />; wartość zwrócona przez funkcję (1 bajt)<br />; znajduje się w rejestrze r24<br />; wykonujemy na niej żądane operacje<br />; przykładowo<br />cpi r24, 125<br />brne not_equal<br />; dalsza część kodu[/syntax]<br /></li><li><strong><em><span style="font-size: 110%; line-height: normal">Funkcja z biblioteki standardowej C</span></em></strong><br /><br />Pisząc w języku asembler możemy również używać funkcji z bibliotek standardowych C. Nie trzeba w tym celu dołączać plików nagłówkowych dyrektywą #include. Wystarczy tak jak w przypadku naszych funkcji umieścić ewentualne argumenty w odpowiednich rejestrach, wywołać funkcję i odczytać wynik z odpowiednich rejestrów.<br /><br />Przykładowe wyliczenie wartości bezwzględnej za pomocą funkcji <strong>abs()</strong> z biblioteki <strong>&lt;stdlib.h&gt;</strong>:<br /><br />[syntax=asm]; zakładamy zdefiniowanie w sekcji .data<br />my_val:     .word -5684<br />; później w kodzie w sekcji .text:<br />;<br />    ; umieszczamy argument typu int<br />    ; (będzie to nasza zmienna 'my_val')<br />    ; w rejestrach r25:r24<br />    ldi xl, lo8(my_val)<br />    ldi xh, hi8(my_val)<br />    ld r24, x+<br />    ld r25, x<br />    ; wywołujemy funkcję<br />    call abs<br />    ; teraz w rejestrach r25:r24 znajduje się<br />    ; wartość zwrócona przez funkcję,<br />    ; czyli wartość bezwzględna zmiennej 'my_val'[/syntax]</li></ul><br /></li><li><span style="font-size: 125%; line-height: normal">Korzystanie ze wspólnego pliku nagłówkowego</span><br /><br />Na koniec mały projekt przykładowy, pokazujący w jaki sposób korzystać ze wspólnego pliku nagłówkowego zarówno dla C jak i asemblera, dzięki czemu można uniknąć konieczności podwójnego definiowania stałych używanych w programie.<br /><br />Projekt napisany został dla mikrokontrolera ATmega644P. Przedstawia wprawdzie obsługę klawiatury z debouncing'iem opartym o timer, ale nie to jest jego głównym celem.  Koncentrowałem się przede wszystkim na pokazaniu ogólnej struktury projektu wykorzystującego mieszany kod ze współdzielonym plikiem nagłówkowym, więc choć program powinien działać, nie mogę zagwarantować pełnej niezawodności oraz optymalności kodu.<br /><br />Projekt składa się z trzech plików, które przedstawiłem poniżej (dokładniejszy opis w komentarzach):<br /><br /><strong>plik &quot;main.c&quot;</strong><br />[syntax=c]/*<br /> * main.c<br /> *<br /> * Created: 2016-09-12 19:35:09<br /> * Author : Andrews<br /> */ <br /><br />#include &lt;avr/io.h&gt;<br />#include &lt;stdbool.h&gt;<br />#include &lt;avr/interrupt.h&gt;<br />#include &quot;keyboard.h&quot;<br /><br />// Zmienna współdzielona z procedurą obsługi przerwania<br />// napisaną w ASM. Wartość zmiennej jest aktualizowana<br />// przez procedurę obsługi przerwania, kiedy procedura<br />// odczyta dwukrotnie ten sam kod klawiatury<br />volatile enum Key pressed_key = NO_KEY;<br /><br />// Funkcja konfigurująca port klawiatury<br />// oraz timer<br />void kbrd_init(void);<br /><br />int main(void)<br />{<br />    // Ustawienie pinów portu A jako wyjść<br />    // by można było obserwować efekty wciskania<br />    // klawiszy<br />    DDRA = 0x0F;<br />    <br />    // Zmienna blokująca wielokrotne wykonanie<br />    // kodu obsługi klawisza przed jego puszczeniem<br />    bool key_processed = false;<br />    <br />    // Zmienna 'pressed_key' może zostać zmieniona<br />    // przez procedurę obsługi przerwania w dowolnym<br />    // momencie. Zmienna pomocnicza zagwarantuje,<br />    // że wartość zmiennej się nie zmieni<br />    // do momentu zakończenia analizy<br />    enum Key pk;<br />    <br />    kbrd_init();<br />    sei();<br />    <br />    while (1)<br />    {<br />        pk = pressed_key;<br />        if (key_processed)<br />        {<br />            // Oczekiwanie na zwolnienie przycisku<br />            if (pk==NO_KEY)<br />            {<br />                key_processed = false;<br />                // Tutaj ewentualny kod wykonywany<br />                // po zwolnieniu klawisza, przykładowo:<br />                PORTA = 0x00;<br />            }            <br />        }<br />        else<br />        {<br />            // Poniższy warunek zagwarantuje,<br />            // że kod obsługi klawisza będzie<br />            // wykonany tylko raz na jedno<br />            // przyciśnięcie<br />            if (pk!=NO_KEY) key_processed = true;<br />            switch (pk)<br />            {<br />            // Tutaj kody obsługi poszczególnych klawiszy<br />                case UP:<br />                    // przykładowo:<br />                    PORTA = 0x01;<br />                    break;<br />                case DOWN:<br />                    PORTA = 0x02;<br />                    break;<br />                case ESC:<br />                    PORTA = 0x04;<br />                    break;<br />                case ENTER:<br />                    PORTA = 0x08;<br />                    break;<br />                case NO_KEY:<br />                default:<br />                    break;<br />            }<br />        }<br />    }<br />}<br /><br />// Funkcja inicjująca obsługę klawiatury<br />void kbrd_init(void)<br />{<br />    // Włączenie pull-up dla pinów,<br />    // do których są podłączone klawisze<br />    KBRD_PORT = KBRD_bm;<br />    <br />    // Konfiguracja timera<br />    // Założenie: F_CPU=16MHz<br />    TCCR2A = (1&lt;&lt;WGM21);    // tryb CTC<br />    TCCR2B = (7&lt;&lt;CS20);     // prescaler 1024<br />    OCR2A = 155;            // przerwanie co ok. 10ms<br />    TIMSK2 = (1&lt;&lt;OCIE2A);   // włączenie przerwania od porównania<br />}[/syntax]<br /><br /><br /><strong>plik &quot;keyboard.h&quot;</strong><br />[syntax=c]/*<br /> * keyboard.h<br /> *<br /> * Created: 2016-09-12 19:47:56<br /> *  Author: Andrews<br /> */ <br /><br />#ifndef KEYBOARD_H_<br />#define KEYBOARD_H_<br /><br />// *************************************************************<br />// Fragment przeznaczony do edycji.<br />// Można ustawić własne porty i numery pinów<br />// należy tylko zwrócić uwagę, by nie kolidowały<br />// z pinami portem i pinami, wykorzystanymi w pętli głównej<br />// programu do sygnalizacji.<br />// (przyciski podłączone pomiędzy pin mikrokontrolera a masę)<br />#define KBRD_PORT       PORTB<br />#define KBRD_PIN        PINB<br />#define KEY_UP_bp       0<br />#define KEY_DOWN_bp     1<br />#define KEY_ESC_bp      2<br />#define KEY_ENTER_bp    3<br />//**************************************************************<br /><br />#define KBRD_bm   ( ( 1&lt;&lt;(KEY_UP_bp) )      |\<br />                    ( 1&lt;&lt;(KEY_DOWN_bp) )    |\<br />                    ( 1&lt;&lt;(KEY_ESC_bp) )     |\<br />                    ( 1&lt;&lt;(KEY_ENTER_bp) ) )<br /><br />#define KEY_UP_bm       ( KBRD_bm &amp; ~( 1&lt;&lt;(KEY_UP_bp) ) )<br />#define KEY_DOWN_bm     ( KBRD_bm &amp; ~( 1&lt;&lt;(KEY_DOWN_bp) ) )<br />#define KEY_ESC_bm      ( KBRD_bm &amp; ~( 1&lt;&lt;(KEY_ESC_bp) ) )<br />#define KEY_ENTER_bm    ( KBRD_bm &amp; ~( 1&lt;&lt;(KEY_ENTER_bp) ) )<br /><br /><br />// Definicja typu enum Key przeznaczona tylko dla pliku main.c<br />#ifndef __ASSEMBLER__<br /><br />enum Key {<br />    NO_KEY  = KBRD_bm,<br />    UP      = KEY_UP_bm,<br />    DOWN    = KEY_DOWN_bm,<br />    ESC     = KEY_ESC_bm,<br />    ENTER   = KEY_ENTER_bm<br />};<br /><br />#endif<br /><br />#endif /* KEYBOARD_H_ */[/syntax]<br /><br /><strong>plik &quot;timer_isr.S&quot;</strong><br />[syntax=asm]/*<br /> * timer_isr.S<br /> *<br /> * Created: 2016-09-12 19:39:35<br /> *  Author: Andrews<br /> */<br />#define __SFR_OFFSET    0<br /><br />#include &lt;avr/io.h&gt;<br />#include &quot;keyboard.h&quot;<br /><br />; zmienna zadeklarowana w pliku main.c<br />; będziemy do niej wpisywać informację<br />; o wciśniętym klawiszu<br />.extern pressed_key ; uint8_t<br /><br />; ===============================================<br />    .section .data<br /><br />; zmienna lokalna, widoczna tylko dla asm<br />; poprzednio odczytany stan klawiatury<br />prev_code: .byte KBRD_bm  ; enum Key<br /><br />; ===============================================<br />; W pliku 'main.c' jest zadeklarowana zmienna<br />; statyczna 'pressed_key' z przypisaną wartością,<br />; więc nasza zmienna lokalna 'prev_code' powinna<br />; zostać zainicjowana prawidłowo<br />; przez kompilator C (pisałem o tym wcześniej).<br />; W innym przypadku w tym miejscu powinniśmy<br />; zainicjować naszą zmienną sami.<br />;<br />;    .section .init1,&quot;ax&quot;,@progbits<br />;<br />;    ldi r16, KBRD_bm<br />;    sts prev_code, r16<br />;<br />; ===============================================<br /><br />    .section .text<br /><br /><br />    .global TIMER2_COMPA_vect<br />    <br />; Zdecydowanie łatwiej operować nazwami symbolicznymi<br />; niż numerami rejestrów. Możemy je zdefiniować tak,<br />; jak jest pokazane poniżej.<br />; Dyrektywa asemblera .set (w przeciwieństwie do .equ)<br />; pozwala na wielokrotne definiowanie tego samego<br />; symbolu, dzięki czemu w innej funkcji możemy<br />; przypisać inny numer rejestru do tego samego symbolu,<br />; przykładowo:<br />; .set temp, 22<br /><br />.set temp, 16<br />.set prev, 17<br /><br />TIMER2_COMPA_vect:<br />; ------------------ prolog --------------------<br />    push temp<br />    in temp, SREG<br />    push temp<br />    push prev<br />; ---------------- koniec prologu ---------------<br />    lds prev, prev_code<br />    in temp, KBRD_PIN<br />    andi temp, KBRD_bm<br />    cp temp, prev<br />    brne not_equ<br />    sts pressed_key, prev<br />not_equ:<br />    sts prev_code, temp<br />; ------------------ epilog --------------------<br />    pop prev<br />    pop temp<br />    out SREG, temp<br />    pop temp<br />; --------------- koniec epilogu ----------------<br />    reti[/syntax]</li></ul><br /><span style="font-size: 175%; line-height: normal">Podsumowanie</span><br /><br />Obawiam się trochę, że bardziej się skoncentrowałem na tym, żeby było rzeczowo, niż żeby było ciekawie. Mimo tego liczę na to, że znajdzie się ktoś, kto przeczyta to w całości unikając zaśnięcia. Mam również nadzieję, że udało mi się zebrać w jednym miejscu wszystkie informacje niezbędne do stworzenia projektu w języku C zawierającego wstawki w kodzie ASM (który się skompiluje i będzie działał prawidłowo), że było wyczerpująco i zrozumiale, no i że komuś się to kiedyś do czegoś przyda.<br /><br />W razie pytań postaram się odpowiedzieć na miarę mojej skromnej wiedzy, jednak proszę o cierpliwość, ponieważ zapewne nie zawsze będę miał czas, żeby zrobić to natychmiast.<br /><br />Pozdrawiam<br /><ul><ul>andrews</ul></ul><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 14 paź 2016, o 19:11</p><hr />
]]></content>
</entry>
</feed>