<?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=19149&amp;mode" />

<title>ATNEL tech-forum</title>
<link href="https://forum.atnel.pl/index.php" />
<updated>2019-05-30T13:16:49+01:00</updated>

<author><name><![CDATA[ATNEL tech-forum]]></name></author>
<id>https://forum.atnel.pl/feed.php?f=56&amp;t=19149&amp;mode</id>
<entry>
<author><name><![CDATA[zbt]]></name></author>
<updated>2019-05-30T13:16:49+01:00</updated>
<published>2019-05-30T13:16:49+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=219384#p219384</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=219384#p219384"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=219384#p219384"><![CDATA[
A ja sie tak męczyłem <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /><br />Wielkie dzięki za poświęcony czas na wytłumaczenie tego !<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=7574">zbt</a> — 30 maja 2019, o 13:16</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[Januszkk]]></name></author>
<updated>2018-10-07T18:05:11+01:00</updated>
<published>2018-10-07T18:05:11+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=212400#p212400</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=212400#p212400"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=212400#p212400"><![CDATA[
Tego szukałem, właśnie się męczę z menu które chce &quot;wcisnąć&quot; do Flash-a ale poległem na wskaznikach <img src="https://forum.atnel.pl/images/smilies/icon_e_sad.gif" alt=":(" title="Smutny" /><br />Ale dzięki temu opisowi mam nadzieję przełamać impas, jeszcze raz dzięki za opis i przykłady, kawał dobrej roboty <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=8263">Januszkk</a> — 7 paź 2018, o 18:05</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[Jarecki]]></name></author>
<updated>2017-09-11T14:37:12+01:00</updated>
<published>2017-09-11T14:37:12+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194867#p194867</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194867#p194867"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194867#p194867"><![CDATA[
O KURCZE! <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /> Kawał roboty, naprawdę WIELKI szacunek i podziękowanie za podzielenie się wiedzą <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=2511">Jarecki</a> — 11 wrz 2017, o 14:37</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[Zealota]]></name></author>
<updated>2017-09-11T13:41:31+01:00</updated>
<published>2017-09-11T13:41:31+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194865#p194865</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194865#p194865"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194865#p194865"><![CDATA[
Dzięki za pouczający wpis. Czekam na następne <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=16487">Zealota</a> — 11 wrz 2017, o 13:41</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[SunRiver]]></name></author>
<updated>2017-09-10T11:37:27+01:00</updated>
<published>2017-09-10T11:37:27+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194785#p194785</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194785#p194785"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194785#p194785"><![CDATA[
Ok zatem sprzątam <img src="https://forum.atnel.pl/images/smilies/icon_e_smile.gif" alt=":)" title="Szczęśliwy" /><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=58">SunRiver</a> — 10 wrz 2017, o 11:37</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2017-09-10T11:35:26+01:00</updated>
<published>2017-09-10T11:35:26+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194784#p194784</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194784#p194784"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194784#p194784"><![CDATA[
Dziękuję bardzo za słowa uznania od samego Szefa.<br /><br />Może jeszcze w kwestii formalnej:<br /><div class="quotetitle">SunRiver napisał(a):</div><div class="quotecontent"><br />pytania dopiero jak kol. andrews napisze że zakończył cykl<br /></div><br />Jakoś mi to wcześniej umknęło, więc teraz chciałbym poinformować, że ja już &quot;zakończyłem cykl&quot; <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /><br />.<p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 10 wrz 2017, o 11:35</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[mirekk36]]></name></author>
<updated>2017-09-10T08:09:37+01:00</updated>
<published>2017-09-10T08:09:37+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194776#p194776</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194776#p194776"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194776#p194776"><![CDATA[
BARDZO ŁADNY OPIS <img src="https://forum.atnel.pl/images/smilies/icon_e_wink.gif" alt=";)" title="Puszcza oko" /> ... piękna wisienka na torcie jeśli chodzi o AVR i wykorzystywanie pamięci FLASH - specyfikatory __flash czy __memx <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> — 10 wrz 2017, o 08:09</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2017-09-17T07:21:01+01:00</updated>
<published>2017-09-09T14:28:49+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194746#p194746</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194746#p194746"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194746#p194746"><![CDATA[
<ul><ul><span style="font-size: 120%; line-height: normal"><strong>• Funkcje uniwersalne z wykorzystaniem kwalifikatora __memx</strong></span><br /><br />Chyba powszechnie wiadomo, że do obsługi danych w pamięci FLASH, służą inne funkcje bibliotek standardowych, niż do obsługi  danych w pamięci RAM (mają one przyrostek <strong><em>„_P”</em></strong> lub <strong><em>„_PF”</em></strong>). Wynika to z tego, że należy w inny sposób odczytywać takie dane, a sam wskaźnik (standardowy 16‑bitowy) nie informuje o tym, gdzie dane się znajdują. Dlatego szereg funkcji standardowych musi mieć trzy wersje. Jedna z wersji traktuje przekazywany do funkcji wskaźnik jako wskaźnik do RAM, druga (z przyrostkiem <strong><em>„_P”</em></strong>) – jako wskaźnik do FLASH poniżej limitu 64KiB, trzecia (z przyrostkiem <strong><em>„_PF”</em></strong>) – jako wskaźnik do FLASH powyżej 64KiB. Przykładami takich funkcji mogą być:<br /><ul><li><strong><em>memcpy() – memcpy_P() – memcpy_PF()</em></strong><br /><li><strong><em>strlen() – strlen_P() – strlen_PF()</em></strong><br /><li><strong><em>strcmp() – strcmp_P() – strcmp_PF()</li></ul></em></strong><br /><br />Jeśli tworzymy własne funkcje, również musimy stosować tę samą zasadę. Każdy chyba przyzna, że – choć relatywnie szybkie (ze względu na 16-bitowe wskaźniki) – nie jest to wygodne rozwiązanie.<br /><br />Problem ten możemy jednak rozwiązać, stosując jako parametry funkcji wskaźniki z kwalifikatorem <strong><em>__memx</em></strong>. Wprawdzie funkcja taka będzie nieco wolniejsza ze względu na to, że takie wskaźniki są 24‑bitowe, ale nie wszystkie funkcje są krytyczne czasowo (np. obsługa menu) i nie zawsze musimy oszczędzać każdy takt zegara, a wygoda używania jednej funkcji do obsługi danych zapisanych zarówno w pamięci FLASH jak i RAM jest moim zdaniem nieoceniona.<br /><br /><em>Jak to się dzieje, że jest to możliwe?</em><br /><br />Wskaźnik z kwalifikatorem <strong><em>__memx</em></strong> łączy w sobie wszystkie przestrzenie adresowe mikrokontrolera w ten sposób, że zawiera regiony:<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent"> |   adres startowy   |   przeznaczenie  |<br /> -----------------------------------------<br /> | 0x000000           | FLASH            |<br /> | 0x80nnnn           | RAM              |<br /> | 0x810000           | EEPROM           |<br /> | 0x820000           | FUSE             |<br /> | 0x830000           | LOCK             |<br /> | 0x840000           | SIGNATURE        |<br /> | 0x850000           | USER_SIGNATURE   |</div><br /><br />Adres startowy regionu RAM (opisany jako <strong>0x80nnnn</strong>) jest zależny od architektury mikrokontrolera, dla którego jest generowany kod. Może się on wahać od <strong>0x800040</strong> dla architektury <strong>avrtiny</strong> do <strong>0x800200</strong> dla architektury <strong>avr6</strong> (dla architektur <strong>avrxmega</strong> wynosi nawet <strong>0x802000</strong>).<br /><br />Rozmiary (czyli także adresy końcowe) poszczególnych regionów również zależą od architektury mikrokontrolera.<br /><br />Nas interesują tylko pierwsze dwa regiony. Jak łatwo zauważyć, podstawową cechą, która odróżnia te regiony jest siódmy bit najstarszego bajtu adresu. Kiedy więc przekazujemy do funkcji wskaźnik z kwalifikatorem <strong><em>__memx</em></strong> (<strong><em>bajt2:bajt1:bajt0</em></strong>), kompilator generuje kod, który na podstawie tego właśnie bitu odróżnia lokalizację danych.<br /><br /><ul><li>Jeśli ten bit jest jedynką, ładuje do rejestru <strong><em>Z</em></strong> (<strong><em>R31:R30</em></strong>) dwa młodsze bajty przekazanego adresu (<strong><em>bajt1:bajt0</em></strong>) i używa instrukcji <strong><em>ST, STD</em></strong> / <strong><em>LD, LDD</em></strong> w celu zapisania/odczytania zmiennej do/z pamięci RAM.<br /></li><li>Jeśli ten bit jest zerem, ładuje do rejestrów <strong><em>RAMPZ:R31:R30</em></strong> cały przekazany adres (<strong><em>bajt2:bajt1:bajt0</em></strong>) i używa instrukcji <strong><em>ELPM</em></strong> w celu odczytu danych z pamięci FLASH.</li></ul><br />Niestety kompilator nie obsługuje w ten sposób danych zapisanych np. w regionie EEPROM (czy też w innych regionach). Gdybyśmy próbowali przekazać do funkcji wskaźnik do pamięci EEPROM, zostanie on potraktowany jako wskaźnik do pamięci RAM (pomimo tego, że adres jest z zakresu regionu EEPROM). Kompilator sprawdza tylko siódmy bit najstarszego bajtu, a nie sprawdza bitu 0 najstarszego bajtu, który w przypadku regionu EEPROM jest równy 1 (w odróżnieniu do regionu RAM, gdzie jest zerem), więc nie może odróżnić RAM i EEPROM. Inaczej mówiąc, wszystkie adresy rozpoczynające się od jedynki są traktowane jako wskaźniki do pamięci RAM, więc chcąc operować na danych w EEPROM musimy utworzyć osobną funkcję.<br /><br />Postaram się przedstawić w poniższym przykładzie kodu, jak taką funkcję stworzyć i jak jej używać. Będzie to kod dla ATmega2560 zawierający uniwersalną funkcję do wysyłania ciągów znaków poprzez UART (start sekcji <strong><em>.strings</em></strong>: adres słowa <strong>0x10000</strong> lub adres bajtu <strong>0x20000</strong>):<br /><br />[syntax=c]// main.c<br /><br />#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br />#include &lt;stdlib.h&gt;<br /><br />// makro zwracające 24-bitowy wskaźnik do zmiennej ‘var’<br />#define get_memx_address(var)               \<br />({                                          \<br />    __int24 tmp;                            \<br />                                            \<br />    __asm__ __volatile__(                   \<br />                                            \<br />    &quot;ldi%A0, lo8(%1)&quot;       &quot;\n\t&quot;      \<br />    &quot;ldi%B0, hi8(%1)&quot;       &quot;\n\t&quot;      \<br />    &quot;ldi%C0, hh8(%1)&quot;       &quot;\n\t&quot;      \<br />    :                                       \<br />    &quot;=d&quot; (tmp)                              \<br />    :                                       \<br />    &quot;p&quot;  (&amp;(var))                           \<br />    );                                      \<br />    (__memx typeof(var)*)tmp;               \<br />})<br /><br />// początek sekcji np. 0x10000(word) lub 0x20000(byte)<br />#define STRINGS __attribute__ ((section(&quot;.strings&quot;)))<br /><br />// zdefiniowanie prędkości transmisji<br />#define BAUDRATE 9600<br />// obliczenie wartości rejestru UBRR0<br />#define UBRR0_VALUE (F_CPU - 8UL*BAUDRATE)/16/BAUDRATE // U2X0 = 0<br /><br />// zdefiniowany literał łańcuchowy<br />#define DEFINED_STRING_LITERAL &quot;Defined string literal.\n&quot;<br /><br />// ciąg znaków w pamięci RAM; sposób nie zalecany, ponieważ<br />// ciąg zajmuje zarówno pamięć FLASH jak i RAM<br />const char ram_string&#91;&#93; = &quot;RAM string.\n&quot;;<br /><br />// bufor znakowy np. do odbierania wyników<br />// działania funkcji konwertujących, takich jak 'itoa()'<br />char ram_buffer&#91;16&#93;;<br /><br />// ciąg znaków w pamięci FLASH umieszczony tam<br />// przy pomocy makra PROGMEM<br />const char flash_str_pgm&#91;&#93; PROGMEM = &quot;PROGMEM string.\n&quot;;<br /><br />// ciąg znaków w pamięci FLASH umieszczony tam<br />// przy pomocy kwalifikatora __flash<br />const __flash char flash_str_flash&#91;&#93; = &quot;__flash string.\n&quot;;<br /><br />// ciąg znaków w pamięci FLASH umieszczony tam<br />// przy pomocy kwalifikatora __memx<br />const __memx char flash_str_memx&#91;&#93; = &quot;__memx string.\n&quot;;<br /><br />// ciąg znaków w pamięci FLASH umieszczony tam<br />// przy pomocy zdefiniowanej sekcji<br />const char flash_str_custom&#91;&#93; STRINGS = &quot;FLASH custom section string.\n&quot;;<br /><br />// funkcja uniwersalna zdolna do wysłania poprzez UART<br />// dowolnego z powyższych ciągów znaków<br />void uart_puts(const __memx char * str);<br /><br />// funkcja wysyłająca pojedynczy znak<br />void uart_putc(char c);<br /><br />int main(void)<br />{<br />    uint8_t i = 0;<br />    // konfiguracja USART0<br />    // ustawienie prędkości transmisji na wartość ustawioną<br />    // przez definicję BAUDRATE<br />    UBRR0 = UBRR0_VALUE;<br />    // włączenie nadajnika i odbiornika<br />    UCSR0B = (1&lt;&lt;RXEN0) | (1&lt;&lt;TXEN0);<br />    // ramka 8-bitowa, 1 bit stopu, bez bitu parzystości<br />    UCSR0C = (1&lt;&lt;UCSZ01) | (1&lt;&lt;UCSZ00);<br />    <br />    while (1) <br />    {<br />        // --- wyświetlenie numeru pętli ---<br />        // anonimowy ciąg znaków umieszczony przez makro PSTR<br />        // w pamięci FLASH<br />        uart_puts( PSTR(&quot;Loop number: &quot;) );<br />        // sposób użycia bufora w RAM do wyświetlenia<br />        // wartości zmiennej<br />        uart_puts( utoa(++i, ram_buffer, 10) );<br />        // anonimowy ciąg znaków umieszczony w pamięci RAM;<br />        // nie zalecane, szczególnie dla dłuższych ciągów,<br />        // ponieważ ciąg zajmuje zarówno pamięć FLASH jak i RAM<br />        uart_puts( &quot;.\n&quot; );<br />        <br />        // zdefiniowany literał znakowy; jak wyżej - makro<br />        // PSTR umieści anonimowy ciąg znaków w pamięci FLASH<br />        // stamtąd będzie odczytany i wysłany przez funkcję<br />        // 'uart_puts()'<br />        uart_puts( PSTR(DEFINED_STRING_LITERAL) );<br />        <br />        // ciąg znaków w pamięci RAM; sposób nie zalecany, ponieważ<br />        // ciąg zajmuje zarówno pamięć FLASH jak i RAM<br />        uart_puts( ram_string );<br />        <br />        // ciąg znaków w pamięci FLASH umieszczony tam<br />        // przy pomocy makra PROGMEM<br />        uart_puts( flash_str_pgm );<br /><br />        // ciąg znaków w pamięci FLASH umieszczony tam<br />        // przy pomocy kwalifikatora __flash<br />        uart_puts( flash_str_flash );<br /><br />        // ciąg znaków w pamięci FLASH umieszczony tam<br />        // przy pomocy kwalifikatora __memx<br />        uart_puts( flash_str_memx );<br /><br />        // ciąg znaków w pamięci FLASH umieszczony tam<br />        // przy pomocy zdefiniowanej sekcji<br />        // należy pamiętać, że argumentem makra<br />        // 'get_memx_address' musi być pierwszy element<br />        // ciągu znaków, czyli należy użyć indeksu 0 w nawiasie<br />        // kwadratowym po nazwie zmiennej<br />        uart_puts( get_memx_address( flash_str_custom&#91;0&#93; ) );<br />    } // while (1)<br />} // main()<br /><br /><br />void uart_puts(const __memx char * str) {<br />    while (*str) uart_putc(*str++);<br />}<br /><br />void uart_putc(char c) {<br />    while ( !(UCSR0A &amp; (1&lt;&lt;UDRE0)) );<br />    UDR0 = c;<br />}[/syntax]</li></ul></li></ul><br /><span style="font-size: 130%; line-height: normal"><strong>Podsumowanie<br /></strong></span><br /><ul>Przedstawiłem tutaj kilka przykładów kodu, jednak nie sposób rozpatrzyć wszystkich możliwych przypadków obsługi danych w pamięci FLASH. W pamięci FLASH można umieścić praktycznie każdy typ danych jak np. typy całkowite, zmiennoprzecinkowe, wskaźniki, ciągi znaków, struktury, tablice. Odczyt danych poszczególnych typów będzie inny i zależny od implementacji i potrzeb danego projektu. Dlatego starałem się przedstawić tutaj ogólne reguły i zasady obsługi, aby każdy mógł samodzielnie znaleźć własny, najbardziej optymalny dla swojego projektu sposób.<br /><br />Mam nadzieję, że było wystarczająco zrozumiale. Jeśli nie, można zadawać pytania. Postaram się odpowiedzieć na miarę mojej skromnej wiedzy. Mam też nadzieję, że poradnik okaże się dla kogoś przydatny.<br /><br />Starałem się z całej siły uniknąć błędów, jednak nie mogę w pełni zagwarantować ich braku. Proszę więc tych, którzy jakiekolwiek błędy zauważą o zwrócenie uwagi. Postaram się jak najszybciej je poprawić.<br /><br /><br /></ul><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 9 wrz 2017, o 14:28</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2017-09-09T14:32:01+01:00</updated>
<published>2017-09-09T14:27:13+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194744#p194744</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194744#p194744"/>
<title type="html"><![CDATA[Re: AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194744#p194744"><![CDATA[
<ul><ul><span style="font-size: 120%; line-height: normal"><strong>• Dane w segmentach powyżej 64KiB</strong></span><br /><br />Tym razem nie będę pokazywał aż tylu przykładów z różnymi typami danych. Skoncentruję się raczej na omówieniu różnic pomiędzy obsługą danych poniżej i powyżej granicy 64KiB. Różnice te są niezależne od typu danych, więc nadal można korzystać z przykładów z poprzedniego rozdziału, zmieniając tylko to, co konieczne.<br /><br /><ul><span style="font-size: 110%; line-height: normal"><strong><em>◦ Umieszczanie danych w pamięci FLASH</em></strong></span><br /><br />Wiemy już, że zarówno makro <strong><em>PROGMEM</em></strong>, jak i kwalifikator <strong><em>__flash</em></strong> oznaczają dane do zapisu w sekcji <strong><em>.progmem.data</em></strong>, którą później linker lokuje na początku pamięci FLASH. Nie każdy chyba jednak wie, że istnieją również kwalifikatory <strong><em>__flash1 __flash2 __flash3 __flash4</em></strong> oraz <strong><em>__flash5</em></strong> , które oznaczają dane do zapisu w sekcjach (odpowiednio) <strong><em>.progmem1.data .progmem2.data .progmem3.data .progmem4.data</em></strong> oraz <strong><em>.progmem5.data</em></strong> Przeznaczeniem poszczególnych sekcji powinny być kolejne 64KiB segmenty pamięci FLASH, czyli tak jak sekcja <strong><em>.progmem.data</em></strong> (<strong><em>__flash</em></strong>) jest zapisywana w pierwszym segmencie 64KiB (czyli o indeksie <strong>0</strong>), tak sekcja <strong><em>.progmem1.data</em></strong> (<strong><em>__flash1</em></strong>) powinna być zapisana w drugim segmencie 64KiB (czyli o indeksie <strong>1</strong>), sekcja <strong><em>.progmem2.data</em></strong> (<strong><em>__flash2</em></strong>) powinna być zapisana w trzecim segmencie 64KiB (czyli o indeksie <strong>2</strong>) itd.<br /><br />Napisałem, że powinny być, dlatego że tak naprawdę domyślne skrypty linkera (te dołączone do toolchain’u) obecnie nie wspierają tej techniki. Nie wiem tak do końca jaka jest tego przyczyna, prawdopodobnie nowe skrypty są nadal w fazie testów przed dopuszczeniem do powszechnego użycia. Dokładniejsze omówienie tematu skryptów linkera wykracza jednak poza zakres tak skromnego artykułu, więc nie będę się tutaj rozpisywał (choć oczywiście można o tym poczytać gdzie indziej i poeksperymetować). Póki co powinniśmy przyjąć, że niezależnie od numeru sekcji czy też kwalifikatora, linker i tak umieszcza wszystkie dane w sekcji <strong><em>.progmem.data</em></strong> Nie należy więc używać tych numerowanych kwalifikatorów (<strong><em>__flashN</em></strong>) do deklarowania danych we FLASH, dlatego że odczyt danych może być nieprawidłowy – dane mogą zostać zapisane w innym segmencie, niż kompilator będzie się ich spodziewał. Właściwie wspomniałem o nich, ponieważ mogą się one przydać w sytuacjach, kiedy dokładnie wiemy, gdzie dane zostaną umieszczone, ale o tym później.<br /><br />Kiedy deklarujemy dużo danych do zapisu we FLASH, prędzej czy później może dojść do sytuacji, że ich łączny rozmiar plus to, co linker umieszcza przed tymi danymi (czyli np. wektory przerwań, ale czasami też inne sekcje) przekroczy limit 64KiB. Ważne jest, żebyśmy o tym wiedzieli. O ile możliwe jest (choć może być kłopotliwe) policzenie, ile zajmą nasze dane, o tyle trudno będzie przewidzieć rozmiar sekcji umieszczonych przed nimi.<br /><br /><strong>Jak się dowiedzieć, czy przekroczyliśmy już limit 64KiB i którego bloku danych to ewentualnie dotyczy?</strong><br /><br />Możemy oczywiście przeanalizować plik <strong><em>*.map</em></strong> wygenerowany podczas budowania programu, jednak jest to mało wygodne i dość czasochłonne. Spróbujmy więc wydobyć te informacje bezpośrednio z pliku <strong><em>*.elf</em></strong> w inny sposób, używając do tego celu aplikacji <strong><em>avr-objdump.exe</em></strong><br />Najpierw utworzymy plik o nazwie np. <em>flashsymbols.bat</em> zawierający tekst:<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">avr-objdump %1 -t | find &quot;O .text&quot; | sort</div><br />i zapisujemy w dogodnym dla siebie miejscu.<br /><br /><ul><strong>Atmel Studio 7</strong><br /><br />Wybieramy w menu:<br /><br /><em>Tools→External tools…</em><br /><br />Pojawi się okno do konfiguracji narzędzi zewnętrznych. Klikamy na przycisk Add i wprowadzamy:<br /><li>w polu <em>Title</em>: <span style="color: #0000FF"><em>Flash objects</em></span> lub jaką nazwę kto woli,<br /><li>w polu <em>Command</em>: podajemy pełną ścieżkę do utworzonego wcześniej pliku <strong>*.bat</strong><br /><li>w polu <em>Arguments</em>: <span style="color: #0000FF"><em>$(TargetDir)$(TargetName).elf</em></span><br /><li>w polu <em>Initial directory</em>: podajemy pełną ścieżkę do folderu, w którym znajduje się plik avr-objdump.exe – w przypadku Atmel Studio 7 standardowo znajduje się w folderze:<br /><span style="color: #0000FF"><em>C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\</em></span><br />(nie musimy nic wpisywać w Working directory, jeżeli mamy już ten folder dodany do zmiennej środowiskowej PATH w systemie Windows),</li><li>zaznaczamy tylko opcję: <em>Use Output window</em>, pozostałe pozostawiamy niezaznaczone.</li></ul><br /><ul>Zatwierdzamy wszystko klikając na przycisk <em>Apply</em>, a później na <em>OK</em>. W menu <em>Tools</em> powinna się teraz pojawić nowa pozycja o nazwie, jaką wprowadziliśmy w polu Title.</ul><br /><ul><strong>Eclipse MARS 2</strong><br /><br />Wybieramy w menu:<br /><br /><em>Run→External Tools…→External Tools Configurations…</em><br /><br />Pojawi się okno do konfiguracji narzędzi zewnętrznych. Po lewej stronie znajduje się pole z listą. Zwykle bezpośrednio po instalacji lista zawiera jeden wpis – <em>Program</em>. Zaznaczamy ten wpis. Nad listą powinien znajdować się wiersz pięciu ikonek. Klikamy na pierwszą od lewej (<em>New launch configuration</em>), po czym wpisujemy:<br /><li>w polu <em>Name</em>:  <span style="color: #0000FF"><em>Flash objects</em></span> lub jaką nazwę kto woli,</li><li>w polu <em>Location</em>: podajemy pełną ścieżkę do utworzonego wcześniej pliku <strong>*.bat</strong></li><li>w polu <em>Working Directory</em>: podajemy pełną ścieżkę do folderu, w którym znajduje się plik avr-objdump.exe – należy go szukać w podfolderze:<br /><em>[toolchain_path]<span style="color: #0000FF">\avr8\avr8-gnu-toolchain\bin\</span></em><br />(nie musimy nic wpisywać w Working directory, jeżeli mamy już ten folder dodany do zmiennej środowiskowej PATH w systemie Windows),</li><li>w polu <em>Arguments</em>: <span style="color: #0000FF"><em>${project_loc}/${config_name:${project_name}}/${project_name}.elf</em></span></li><li>wybieramy zakładkę <em>Build</em> i usuwamy lub pozostawiamy zaznaczenie przy <em>Build before launch</em> w zależności od własnych preferencji – kiedy opcja ta jest zaznaczona, w momencie uruchomienia naszego narzędzia zostanie najpierw wykonane budowanie programu; w przypadku pozostawienia tej opcji zaznaczonej zalecałbym wybranie opcji <em>The project containing selected resource</em>,</li><li>wybieramy zakładkę <em>Common</em> i na liście <em>Display in favorites</em> menu robimy zaznaczenie przy <em>External tools</em>.</li></ul><br /><ul>Zatwierdzamy klikając kolejno przyciski <em>Apply</em> i <em>Close</em>. Teraz gdy wybierzemy menu <em>Run→External tools...</em> powinniśmy zauważyć nową pozycję o nazwie wprowadzonej w polu <em>Name</em> podczas konfiguracji.</ul><br />Dzięki zastosowaniu zmiennych środowiskowych nasze narzędzie będzie uniwersalne – nie trzeba będzie go konfigurować inaczej przy każdym kolejnym projekcie. Z drugiej jednak strony należy pamiętać (dotyczy <strong>Eclipse</strong>), że w momencie uruchomienia narzędzia musimy mieć wybrany właściwy projekt (i konfigurację <em>Debug/Release</em>). Jeśli spróbujemy uruchomić nasze narzędzie, kiedy np. będzie akurat aktywne okno konsoli, operacja zakończy się błędem.<br /><br />Efekt działania naszego narzędzia będzie widoczny w <strong>Atmel Studio 7</strong> w oknie <em>Output</em>, natomiast w <strong>Eclispe</strong> w oknie <em>Console</em> i będzie wyglądał mniej więcej tak (oczywiście rekordów będzie zapewne więcej):<br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">000000e4 g     O .text   00000014 pgm_string<br />000000f8 g     O .text   00000007 pgm_array</div><br /><br />Znaczenie poszczególnych kolumn jest następujące:<ol style="list-style-type: decimal"><li>adres w pamięci FLASH w postaci liczby szesnastkowej,</li><li>literka <strong><em>g</em></strong> oznacza symbol globalny,</li><li>literka <strong><em>O</em></strong> oznacza, że jest to obiekt (a nie na przykład jedna z funkcji, które też przecież są umieszczane w pamięci FLASH),</li><li> <strong><em>.text</em></strong> oznacza sekcję w której znajduje się obiekt,</li><li>liczba w postaci szesnastkowej oznaczająca rozmiar,</li><li>nazwa symbolu, czyli nazwa stałej zadeklarowana przez nas w kodzie.</li></ol><br /><strong>WAŻNE:</strong><br /><br />Na podstawie uzyskanych w ten sposób informacji możemy odpowiednio zmieniać definicje i sposób odczytu naszych danych. Niestety istnieje tutaj pewna niedogodność <strong>niezależna od użytej metody</strong> obsługi naszych danych (<strong><em>PROGMEM</em></strong> czy <strong><em>__flash</em></strong>). W miarę rozbudowy naszego programu, kiedy będziemy dodawali nowe bloki danych do pamięci FLASH, <strong>może zmieniać się kolejność ich rozmieszczenia</strong>, przez co może być konieczne ponowne modyfikowanie programu adekwatnie do nowych adresów początkowych i końcowych. Zwykle dane są umieszczane we FLASH w odwrotnej kolejności, niż są zadeklarowane, więc dodawanie nowych danych zawsze przed wcześniej zdefiniowanymi powinno nas uchronić od problemów, ponieważ nowe dane zostaną dołączone na końcu i adresy poprzednich się nie zmienią. Nadal jednak pozostaje problem, gdy dane będziemy deklarować w innych plikach <strong><em>*.c</em></strong> – decyduje wtedy kolejność kompilacji, której nie mamy pod kontrolą korzystając z automatycznie generowanego pliku <em>Makefile</em>.<br /><br />Na dodatek trzeba też uważać na opcje kompilatora. Przykładowo wyłączenie optymalizacji (czyli ustawienie poziomu optymalizacji na <strong><em>-O0</em></strong> np. na potrzeby debugowania) może spowodować zmianę kolejności rozmieszczenia danych we FLASH i co za tym idzie nieprawidłowy ich odczyt.<br /><br />Zapewne skrypt linkera, który umieszczałby dane w odpowiednich sekcjach <strong><em>__flashN</em></strong> w dużej mierze wyeliminowałby powyższe problemy. Póki co trzeba sobie jednak radzić w inny sposób. Dobrym sposobem na uzyskanie kontroli nad rozmieszczenie danych może być tworzenie własnych sekcji w obszarze pamięci FLASH, ale o tym później. Najpierw spróbuję pokazać, jak to zrobić przy zastosowaniu standardowych, lepiej wszystkim znanych metod.<br /><br /><ul><strong><em><li>Definicja przy pomocy atrybutu PROGMEM</em></strong></li></ul><br />Stosując <strong><em>PROGMEM</em></strong>, dane definiujemy tak samo, niezależnie od tego w jakim segmencie 64KiB się znajdą. Tutaj różnica będzie polegać tylko na sposobie odczytu tych danych. Jeśli choćby jeden bajt zdefiniowanego przez nas obszaru danych we FLASH znajdzie się poza pierwszym segmentem 64KiB, należy przyjąć, że obszar taki będzie wymagał specjalnego traktowania, czyli odczytu poprzez tzw. <em>„dalekie wskaźniki”</em> (ang. <em>„far pointers”</em>). „Dalekie” ponieważ ich rozmiar jest 32-bitowy, więc mogą wskazywać na adresy położone „daleko”, czyli poza pierwszym segmentem 64KiB.<br /><br />Stwórzmy wstępnie taki oto przykładowy kod, nie uwzględniający jeszcze rozmieszczenia danych we FLASH:<br /><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br /><br />#define MAX_SIZE PTRDIFF_MAX<br /><br />// definiujemy dwie duże tablice danych<br />const uint16_t pgm_array1&#91;MAX_SIZE/sizeof(uint16_t)&#93; PROGMEM = {0};<br />const uint8_t  pgm_array2&#91;MAX_SIZE&#93; PROGMEM = {0};<br /><br />int main(void)<br />{<br />    uint16_t i;<br /><br />    while (1) <br />    {<br />        for (i=0; i&lt;MAX_SIZE/sizeof( pgm_array1&#91;0&#93; ); i++)<br />        {<br />            PORTB = pgm_read_word(&amp;pgm_array1&#91;i&#93;);<br />        }<br />        for (i=0; i&lt;MAX_SIZE; i++)<br />        {<br />            PORTC = pgm_read_byte(&amp;pgm_array2&#91;i&#93;);<br />        }<br />    }<br />}[/syntax]<br /><br />W celu sprawdzenia rozmieszczenia danych w naszym programie, najpierw wydajemy polecenie budowania (<em>Build→Build Solution [F7]</em> w Atmel Studio, lub <em>Project→Build project</em> w Eclipse), a następnie uruchamiamy nasze narzędzie (znajdujące się w menu <em>Tools</em> w Atmel Studio lub w menu <em>Run→External tools</em> w Eclipse). W wyniku tej operacji w oknie <em>Output</em> w Atmel Studio lub w oknie <em>Console</em> w Eclipse powinniśmy otrzymać następujący tekst:<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">000000cc g     O .text   00007fff pgm_array2<br />000080cb g     O .text   00007ffe pgm_array1</div><br /><br />Oznacza to, że nasza tablica <strong><em>pgm_array2</em></strong> została umieszczona pod adresem <strong>0xCC</strong> i ma rozmiar <strong>0x7FFF</strong>, natomiast tablica <strong><em>pgm_array1</em></strong> została umieszczona pod adresem <strong>0x80CB</strong> i ma rozmiar <strong>0x7FFE</strong>. Wszystkie wartości podane są w systemie szesnastkowym. Można oczywiście przekształcić je na system dziesiętny, jednak moim zdaniem lepiej dokonać obliczeń w systemie szesnastkowym, gdyż łatwiej wtedy ocenić przekroczenie limitu. Obecnie chyba każdy kalkulator posiada opcję obliczeń w systemie szesnastkowym, więc nie powinno to sprawić większego problemu.<br /><br /><em>„Far pointer”</em> jest wymagany zawsze wtedy, kiedy adres końcowy zdefiniowanego przez nas bloku danych jest większy od maksymalnej wartości, jaką może przyjąć liczba całkowita szesnastobitowa bez znaku, czyli od <strong>0xFFFF</strong> (65535). W systemie szesnastkowym jest to największa liczba, jaką można zapisać za pomocą czterech cyfr, więc każdy adres składający się z większej ilości cyfr (nie licząc poprzedzających ją zer nieznaczących) oznacza przekroczenie limitu (w systemie dziesiętnym ta granica nie jest tak oczywista).<br /><br />Ze względu na to, że nasze narzędzie nie podaje adresu końcowego musimy go obliczyć. Robimy to za pomocą wzoru:<br /><br /><strong><em>adres_końcowy = adres_początkowy + rozmiar – 1</em></strong><br /><br />więc dla pgm_array2 adres końcowy będzie równy (obliczenia oczywiście w systemie szesnastkowym):<br /><br /><strong>CC + 7FFF - 1 = 80CA</strong><br /><br />Widzimy, że adres ten jest czterocyfrowy, co oznacza, iż mieści się w zakresie liczby szesnastobitowej, możemy w takim przypadku do odczytu takiego bloku danych używać makr z grupy pgm_read_xxx(), które jako argument przyjmują adres szesnastobitowy.<br /><br />Liczymy teraz adres końcowy dla tablicy pgm_array1:<br /><br /><strong>80CB + 7FFE - 1 = 1 00C8</strong><br /><br />Jak widać adres (w systemie szesnastkowym) ostatniego elementu tablicy jest pięciocyfrowy. Oznacza to, że nie da się go zaadresować za pomocą 16-bitowego wskaźnika. W związku z tym do odczytu elementów tej tablicy nie można użyć makra <strong><em>pgm_read_word()</em></strong>, ponieważ obsługuje ono tylko adres szesnastobitowy. Zamiast tego należy zastosować makro <strong><em>pgm_read_word_far()</em></strong>. Makro to jednak wymaga wskaźnika 32-bitowego, więc nie można użyć zwyczajnie operatora &amp; do pobrania adresu odczytywanych danych, ponieważ wynikiem takiej operacji będzie wskaźnik 16-bitowy. Do pobrania adresu 32-bitowego trzeba użyć makra <strong><em>pgm_get_far_address()</em></strong>.<br /><br />Biorąc pod uwagę powyższe wymagania okazuje się, że napisany przez nas wcześniej kod odczytujący elementy tablicy  <strong><em>pgm_array1</em></strong> nie będzie działał prawidłowo. Powinien on wyglądać następująco:<br /><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br /><br />#define MAX_SIZE PTRDIFF_MAX<br /><br />// definiujemy dwie duże tablice danych<br />const uint16_t pgm_array1&#91;MAX_SIZE/sizeof(uint16_t)&#93; PROGMEM = {0};<br />const uint8_t  pgm_array2&#91;MAX_SIZE&#93; PROGMEM = {0};<br /><br />int main(void)<br />{<br />    uint16_t i;<br />    // definiujemy &quot;far pointer&quot; i przypisujemy mu adres pierwszego elementu<br />    uint_farptr_t array1_ptr = pgm_get_far_address(pgm_array1&#91;0&#93;);<br /><br />    while (1) <br />    {<br />        for (i=0; i&lt;MAX_SIZE/sizeof( pgm_array1&#91;0&#93; ); i++)<br />        {<br />            PORTB = pgm_read_word_far( array1_ptr );<br />            // do ‘wskaźnika’ dodajemy rozmiar elementu (wytłumaczenie poniżej)<br />            array1_ptr += sizeof( pgm_array1&#91;0&#93; );<br />        }<br />        for (i=0; i&lt;MAX_SIZE; i++)<br />        {<br />            PORTC = pgm_read_byte(&amp;pgm_array2&#91;i&#93;);<br />        }<br />    }<br />}[/syntax]<br /><br />Dopiero teraz tablica pgm_array1 będzie odczytywana prawidłowo.<br /><br />Należy tutaj zwrócić tutaj uwagę na pewien istotny fakt. Wskaźniki w avr-gcc mają rozmiar 16 bitów, co nie jest wystarczające do zaadresowania danych znajdujących się poza granicą 64KiB. Dlatego też należałoby użyć typu o większym zasięgu, czyli np. wskaźników 32-bitowych. Jednakże <strong><em>uint_farptr_t</em></strong> (zwracany przez makro <strong><em>pgm_get_far_address()</em></strong>) tak naprawdę nie jest wskaźnikem tylko typem całkowitym 32-bitowym. Jest to istotne ze względu na arytmetykę wskaźników. W przypadku prawdziwego wskaźnika, operacja <strong><em>array2_ptr++</em></strong> spowodowałaby zwiększenie wartości wskaźnika o 2, ponieważ elementy tablicy są dwubajtowe, więc adres kolejnego elementu jest zawsze większy o 2 od adresu poprzedniego. Jednak ze względu na to, że <strong><em>array2_ptr</em></strong> jest typu <strong><em>uint_farptr_t</em></strong>, operacja taka nie zwróci prawidłowego rezultatu – po prostu wartość zmiennej zostanie powiększona o 1, niezależnie od tego, jaki jest rozmiar elementów tablicy. Programista musi więc osobiście zadbać o prawidłowe obliczanie adresu, dlatego właśnie należy zastosować inny sposób obliczenia adresu (podczas inkrementacji wskaźnika), na przykład:<br /><br /><strong><em>array2_ptr += sizeof( pgm_array2[0] );</em></strong><br /><br />Znając powyższe zasady można sobie poradzić z zapisem i odczytem danych za pomocą makr z pliku nagłówkowego <strong><em>pgmspace.h</em></strong>, choć każdy chyba przyzna, że jest to dość kłopotliwe.<br /><br /><ul><strong><em><li>Definicja przy użyciu kwalifikatorów __flash i __memx</em></strong></li></ul><br />Podobnie jak poprzednio stwórzmy wstępnie kod, nie biorący pod uwagę granicy 64KiB:<br /><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br /><br />#define MAX_SIZE PTRDIFF_MAX<br /><br />// definiujemy dwie duże tablice danych (oczywiście w rzeczywistości<br />// powinny one zawierać dane - tutaj dla testu inicjujemy zerami)<br />const __flash uint16_t pgm_array1&#91;MAX_SIZE/sizeof(uint16_t)&#93; = {0};<br />const __flash uint8_t pgm_array2&#91;MAX_SIZE&#93; = {0};<br /><br />int main(void)<br />{<br />    uint16_t i;<br /><br />    while (1) <br />    {<br />        for (i=0; i&lt;MAX_SIZE/sizeof(pgm_array1&#91;0&#93;); i++)<br />        {<br />            PORTC = pgm_array1&#91;i&#93;;<br />        }<br />        for (i=0; i&lt;MAX_SIZE; i++)<br />        {<br />            PORTB = pgm_array2&#91;i&#93;;<br />        }<br />    }<br />}[/syntax]<br /><br />W celu sprawdzenia rozmieszczenia danych uruchamiamy nasze narzędzie (tak jak poprzednio – patrz wyżej), w wyniku czego w oknie <em>Output</em> w Atmel Studio lub w oknie <em>Console</em> w Eclipse powinno pojawić co następuje:<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">000000cc g     O .text   00007fff pgm_array2<br />000080cb g     O .text   00007ffe pgm_array1</div><br /><br />W tym przypadku to tablica <strong><em>pgm_array1</em></strong> wykracza poza limit 64KiB, więc trzeba zmienić kod tak, by odczyt danych był prawidłowy. Tym razem jednak będzie to o wiele prostsze. Wystarczy bowiem w deklaracji tablicy <strong><em>pgm_array1</em></strong> zamienić kwalifikator <strong><em>__flash</em></strong> na kwalifikator<strong><em> __memx</em></strong>:<br /><br />[syntax=c]const __memx uint16_t pgm_array1&#91;MAX_SIZE/sizeof(uint16_t)&#93; = {0};[/syntax]<br /><br /> i to wszystko, co musimy zrobić. Reszta kodu może pozostać bez zmian. Zastosowanie kwalifikatora <strong><em>__memx</em></strong> zamiast <strong><em>__flash</em></strong> spowoduje następujące zmiany w kodzie wynikowym kompilatora:</li><li>wskaźnik na elementy tablicy jest 24-bitowy, dzięki czemu jego zasięg się zwiększa,</li><li>rejestr RAMPZ jest ustawiany adekwatnie do potrzeb,</li><li>instrukcja LPM zostaje zamieniona na instrukcję ELPM, która pozwala prawidłowo odczytywać dane z całego zakresu pamięci FLASH.</li></ul><br /><ul>Można byłoby w tej chwili zadać pytanie: <em>Dlaczego w ogóle stosować kwalifikator <strong>__flash</strong>, a nie tylko <strong>__memx</strong>, skoro ten drugi jest wygodniejszy w użyciu? Można przecież za jego pomocą odczytać dowolną komórkę pamięci FLASH.</em><br /><br />Odpowiedź brzmi: <em>Ze względu na wydajność kodu (szybkość działania).</em><br /><br />Należy pamiętać, że czas wykonania operacji arytmetycznych i logicznych szczególnie w mikrokontrolerach 8‑bitowych jest zależny od rozmiaru operandów (w bajtach). Operacje odczytu i zapisu wskaźnika 24‑bitowego w pamięci RAM również zajmują więcej taktów. Poza tym użycie <strong><em>__memx</em></strong> wymaga aktualizowania na bieżąco rejestru <strong><em>RAMPZ</em></strong>, co również wymaga dodatkowych taktów. Dlatego odczyt danych za pomocą wskaźników 16‑bitowych jest szybszy. Oczywiście nikt nikomu nie zabroni stosowania <strong><em>__memx</em></strong> do odczytu danych w dowolnym miejscu wedle uznania, np. we fragmentach kodu, które nie są krytyczne czasowo.<br /><br /><ul><strong><em><li>Użycie kwalifikatorów __flashN</em></strong></ul><br />Wspomniałem wcześniej o tym, że standardowe skrypty linkera w toolchain’ie nie wspierają przestrzeni adresowych <strong><em>__flashN</em></strong>, mimo tego ich obsługa jest zaimplementowana w kompilatorze. Oznacza to, że po znalezieniu danych oznaczonych kwalifikatorem <strong><em>__flashN</em></strong>, będzie on generował kod odczytujący te dane z N-tego segmentu 64KiB (ustawiając odpowiednio rejestr <strong><em>RAMPZ</em></strong> i używając instrukcji <strong><em>ELPM</em></strong>). Piszę o tym, ponieważ mogą zdarzyć się sytuacje, w których warto z tych kwalifikatorów skorzystać.<br /><br />Przypuśćmy, że do poprzedniego przykładowego kodu dopisaliśmy jeszcze jedną dużą tablicę i tablica <strong><em>pgm_array1</em></strong> trafiła (w całości) do drugiego segmentu 64KiB (czyli o indeksie 1). Zależy nam jednak na maksymalnie wydajnym odczycie, więc użycie <strong><em>__memx</em></strong> nie będzie optymalne, ze względów, o których pisałem nieco powyżej.<br /><br />[syntax=c]// definiujemy trzy duże tablice danych (oczywiście w rzeczywistości<br />// powinny one zawierać dane - tutaj dla testu inicjujemy zerami)<br />const __flash uint16_t pgm_array1&#91;MAX_SIZE/sizeof(uint16_t)&#93; = {0};<br />const __flash uint16_t pgm_array2&#91;MAX_SIZE/sizeof(uint16_t)&#93; = {0};<br />const __flash uint8_t  pgm_array3&#91;MAX_SIZE&#93; = {0};[/syntax]<br /><br />Sprawdzamy rozmieszczenie danych:<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">000000cc g     O .text   00007fff pgm_array3<br />000080cb g     O .text   00007ffe pgm_array2<br />000100c9 g     O .text   00007ffe pgm_array1</div><br /><br />Po obliczeniu adresu końcowego:<br /><br /><strong>1 00C9 + 7FFE = 1 80C7</strong><br /><br />okazuje się, że interesująca nas tablica <strong><em>pgm_array1</em></strong> znalazła się w całości w drugim segmencie 64KiB (adres początkowy i końcowy należą do tego samego segmentu 64KiB), czyli tam, gdzie teoretycznie powinna się znaleźć po zadeklarowaniu z użyciem kwalifikatora <strong><em>__flash1</em></strong>. Jest to istotne, bo tylko w takiej sytuacji możemy użyć kwalifikatora  <strong><em>__flash1</em></strong>. Gdyby adresy początkowy i końcowy należały do różnych segmentów, konieczne byłoby użycie kwalifikatora <strong><em>__memx</em></strong>.<br /><br />W tym konkretnym przypadku można bez obaw użyć kwalifikatora <strong><em>__flash1</em></strong> do zadeklarowania tablicy <strong><em>pgm_array1</em></strong>. W efekcie można napisać taki przykładowy kod (tym razem dla odmiany przy użyciu wskaźników):<br /><br />[syntax=c]#include &lt;avr/io.h&gt;<br /><br />#define MAX_SIZE PTRDIFF_MAX<br /><br />// definiujemy trzy duże tablice danych (oczywiście w rzeczywistości<br />// powinny one zawierać dane - tutaj dla testu inicjujemy zerami)<br /><br />// tutaj można użyć __flash1, bo tablica znajduje się w całości<br />// w segmencie 64KiB o indeksie 1<br />// można też użyć __memx, ale będzie nieco wolniej :)<br />const __flash1 uint16_t pgm_array1&#91;MAX_SIZE/sizeof(uint16_t)&#93; = {0};<br /><br />// tutaj trzeba użyć __memx, ponieważ adres końcowy jest<br />// w innym segmencie 64KiB niż początkowy<br />const __memx   uint16_t pgm_array2&#91;MAX_SIZE/sizeof(uint16_t)&#93; = {0};<br />// można użyć __flash, bo tablica znajduje się w całości<br />// w segmencie 64KiB o indeksie 0<br />// lub wolniejszego, ale wygodniejszego __memx<br />const __flash  uint8_t  pgm_array3&#91;MAX_SIZE&#93; = {0};<br /><br />int main(void)<br />{<br />    uint16_t i;<br /><br />    // definiujemy wskaźniki do tablic<br />    const __flash1 uint16_t *array1_ptr = &amp;pgm_array1&#91;0&#93;;<br />    const __memx   uint16_t *array2_ptr = &amp;pgm_array2&#91;0&#93;;<br />    const __flash  uint8_t  *array3_ptr = &amp;pgm_array3&#91;0&#93;;<br /><br />    while (1) <br />    {<br />        for (i=0; i&lt;MAX_SIZE/sizeof(pgm_array1&#91;0&#93;); i++)<br />        {<br />            PORTC = *array1_ptr++;<br />            // można także: PORTC = pgm_array1&#91;i&#93;;<br />        }<br />        for (i=0; i&lt;MAX_SIZE/sizeof(pgm_array2&#91;0&#93;); i++)<br />        {<br />            PORTC = *array2_ptr++;<br />            // można także: PORTC = pgm_array2&#91;i&#93;;<br />        }<br />        for (i=0; i&lt;MAX_SIZE; i++)<br />        {<br />            PORTB = *array3_ptr++;<br />            // można także: PORTC = pgm_array3&#91;i&#93;;<br />        }<br />    }<br />}[/syntax]<br /><br />Podsumowując, taki sposób definiowania danych we FLASH może być nieco kłopotliwy, szczególnie w projektach, w których definiujemy ich dużo, bo po każdym dodaniu danych wskazane jest sprawdzenie rozmieszczenia danych i ewentualnie wykonanie korekt w kodzie ze względu na możliwość zmiany adresów. Niemniej pokonanie tych niedogodności jest niezbędne w przypadku korzystania ze standardowych skryptów linkera. Myślę jednak, że kiedy zna się z grubsza zasady, którymi kieruje się kompilator i linker, łatwo wypracować sobie skuteczne metody i wtedy nie jest to jakoś szczególnie kłopotliwe.<br /><br /><ul><strong><em><li>Dane w zdefiniowanej sekcji</em></strong></li></ul><br />Zdarzają się sytuacje, kiedy z różnych powodów chcielibyśmy umieścić nasze dane w pamięci FLASH pod konkretnym adresem (na przykład po to, by mieć możliwość maksymalnego zoptymalizowania pętli odczytu tych danych). GCC oferuje do osiągnięcia tego celu atrybut ‘section’ dodawany przy definiowaniu zmiennej, dzięki któremu można umieścić dane w zdefiniowanej wcześniej przez siebie sekcji pod wskazanym adresem.<br /><br />Najpierw musimy więc zdefiniować sekcję, w której później będziemy umieszczać nasze dane.<br /><br /><ul><strong>Atmel Studio 7</strong><br /><br />Możliwość zdefiniowania własnej sekcji znaleźć można w menu:<br /><br /><em>Project‑&gt;Properties‑&gt;Toolchain‑&gt;AVR/GNU Linker‑&gt;Memory Settings</em><br /><br />W linii opisanej jako <em>„FLASH segment”</em> należy kliknąć ikonkę <em>„Add Item”</em> i w okienku, które się pojawi wpisać oczekiwane parametry sekcji w formacie:<br /><br /><strong>.nazwa=adres_hex</strong><br /><br />gdzie:<br /><ul><li><strong><em>nazwa</em></strong> to oczywiście nazwa, jaką chcemy nadać naszej sekcji; należy oczywiście unikać nazw zdefiniowanych przez GCC – nie sposób wymienić tu wszystkie, ale głównie chodzi o sekcje: <strong><em>.text .data .bss .eeprom .noinit .initN .finiN</em></strong>,<br /><li><strong><em>adres_hex</em></strong> to adres początku sekcji w postaci heksadecymalnej (poprzedzony 0x); UWAGA: wpisać należy <strong>adres słowa</strong>, czyli adres bajtu podzielony przez 2.</li></ul><br />Przykładowo, jeśli chcemy utworzyć sekcję o nazwie .waveforms, która zaczyna się od adresu bajtu <strong>0x10100</strong> (czyli 256 bajtów za poczętkiem drugiego segmentu 64KiB), dzielimy adres bajtu przez 2, po czym wynik wpisujemy w pole tekstowe w formie:<br /><br /><strong><em>.waveforms=0x8080</em></strong></li></ul><br /><ul><strong>Eclipse MARS 2</strong><br /><br />Tego środowiska rzadko używam do programowania AVR, więc nie znam zbyt dobrze pluginu. Niestety nie udało mi się znaleźć tutaj wygodniejszego sposobu zdefiniowania sekcji (np. w opcjach projektu), więc uznałem, że trzeba to zrobić poprzez dodanie opcji linkera.<br /><br />Należy uruchomić z menu:<br /><br /><em>Project‑&gt;Properties‑&gt;C/C++ Build‑&gt;Settings‑&gt; (zakładka Tool Settings) AVR C Linker‑&gt;General</em><br /><br />i w polu tekstowym Other Arguments wpisać:<br /><br /><strong><em>-Wl,-section-start=.nazwa=adres_hex</em></strong><br /><br />gdzie:<ul><li><strong><em>nazwa</em></strong> to oczywiście nazwa, jaką chcemy nadać naszej sekcji; należy oczywiście unikać nazw zdefiniowanych przez GCC – nie sposób wymienić tu wszystkie, ale głównie chodzi o sekcje: <strong><em>.text .data .bss .eeprom .noinit .initN .finiN</em></strong>,<br /><li><strong><em>adres_hex</em></strong> to adres początku sekcji w postaci heksadecymalnej (poprzedzony 0x); w przeciwieństwie do <strong>Atmel Studio</strong> wpisujemy <strong>adres bajtu</strong>, a nie słowa, przy czym należy pamiętać, że adres musi być parzysty.</li></ul><br />Przykładowo dla sekcji o nazwie .waveforms rozpoczynającej się od adresu bajtu <strong>0x10100</strong> należy wpisać:<br /><br />-Wl,-section-start=.waveforms=0x10100</li></ul><br />Jeśli teraz będziemy chcieli, aby nasze wcześniej utworzone narzędzie wyświetliło nam informacje o rozmieszczeniu danych w naszej sekcji, powinniśmy w pliku flashsymbols.bat dodać na końcu linijkę:<br /><br /><strong><em>avr-objdump %1 -t | find &quot;O .waveforms&quot; | sort</em></strong><br /><br />W celu umieszczenia danych w nowo utworzonej sekcji musimy w momencie definiowania zmiennej nadać jej odpowiedni atrybut, przykładowo:<br /><br />[syntax=c]const uint8_t sine_wave&#91;&#93; __attribute__ ((section (&quot;.waveforms&quot;))) =<br />    { 0 /* tutaj dane naszej tablicy z wartościami próbek */};[/syntax]<br /><br />Ze względu na to, że taki zapis jest dość długi i niewygodny w użyciu, warto stworzyć makro, które uprości nam życie:<br /><br />[syntax=c]// na początku pliku *.c lub w którymś z dołączanych plików nagłówkowych<br />// definiujemy makro<br />#define WAVEFORMS __attribute__ ((section (&quot;.waveforms&quot;)))<br /><br />    // i później deklaracja w kodzie - umieszczamy dane w naszej sekcji<br />    const uint8_t sine_wave&#91;&#93; WAVEFORMS =<br />        { 0 /* tutaj dane naszej tablicy z wartościami próbek */};[/syntax]<br /><br />Nadany przez nas atrybut to informacja dla linkera, gdzie ma umieścić dane. Kompilator natomiast nie wie, gdzie znajduje się sekcja (nawet, że znajduje się w pamięci read‑only), więc istotne jest, aby zmienną poprzedzić kwalifikatorem const, ponieważ pozwoli mu to np. wygenerować błąd w przypadku omyłkowej próby modyfikacji takiej zmiennej.<br /><br />Skoro już zapisaliśmy jakieś dane w naszej sekcji, na pewno będziemy chcieli je odczytywać w trakcie działania programu. Dane jednak są umieszczone w pamięci FLASH, więc bezpośredni odczyt w stylu:<br /><br />[syntax=c]uint8_t sample = sinewave&#91;i&#93;;[/syntax]<br /><br />nie zadziała. Mamy dwie możliwości: użyć makr z pliku <strong><em>pgmspace.h</em></strong> lub zrealizować odczyt przy pomocy wskaźników z kwalifikatorami <strong><em>__flash</em></strong>, <strong><em>__flashN</em></strong> lub <strong><em>__memx</em></strong>.<br />Odczyt danych z wykorzystaniem <strong><em>pgmspace.h</em></strong> z naszej przykładowej sekcji, która znajduje się w drugim segmencie 64KiB, musimy zrealizować go przy pomocy makr <strong><em>pgm_read_xxx_far()</em></strong>, adresy pobierając za pomocą makra <strong><em>pgm_get_far_address()</em></strong>. Dodatkowo należy pamiętać o tym, że adres zwracany przez makro nie jest typowym wskaźnikiem, tylko liczbą 32‑bitową, więc programista musi zadbać osobiście np. o prawidłowe obliczanie adresu kolejnych elementów tablicy w zależności od typu tych elementów, o czym pisałem już wcześniej, lub pobierać adres każdej zmiennej wewnątrz struktury osobno (nie da się za pomocą tego wskaźnika odwołać do elementu struktury za pomocą operatora <strong><em>‑&gt;</em></strong>).<br /><br />Wybierając drugą opcję, właściwie wystarczy utworzyć wskaźnik z kwalifikatorem <strong><em>__flash1</em></strong> i za jego pomocą odczytywać dane. Oczywiście dotyczy to sytuacji (takiej jak w podanym powyżej przykładzie), kiedy blok odczytywanych danych znajduje się w całości w drugim segmencie 64KiB. Gdyby zaistniała sytuacja, w której początek i koniec danych znajdowały się w różnych sekcjach, musimy użyć kwalifikatora <strong><em>__memx</em></strong>, który operuje wskaźnikami 24‑bitowymi.<br /><br />Dla porównania podam może 2 możliwie proste przykłady:<br /><br /><ul><strong><em>użycie makr pgmspace.h</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br /><br />#define WAVEFORMS __attribute__ ((section(&quot;.mysection&quot;)))<br /><br />struct color {<br />    uint8_t red;<br />    uint8_t green;<br />    uint8_t blue;<br />    };<br /><br />// definiujemy tablicę w utworzonej sekcji<br />const uint16_t sine_wave&#91;1024&#93; WAVEFORMS =<br />    {0 /* tutaj wartości próbek */};<br />// definiujemy strukturę<br />const struct color my_color WAVEFORMS = {<br />    156, 16, 220<br />    };<br /><br />int main(void)<br />{<br />    uint16_t i;<br />    // definiujemy 'wskaźnik' do początku tablicy<br />    uint_farptr_t sine_wave_ptr = pgm_get_far_address(sine_wave);<br />    uint16_t tmp_sample;<br />    while (1)<br />    {<br />        // odczyt tablicy<br />        for (i=0; i&lt;sizeof(sine_wave)/sizeof(sine_wave&#91;0&#93;); i++)<br />        {<br />            tmp_sample = pgm_read_word_far(sine_wave_ptr);<br />            PORTD = (uint8_t)tmp_sample;<br />            PORTE = (uint8_t)(tmp_sample&gt;&gt;8);<br />            sine_wave_ptr += sizeof(sine_wave&#91;0&#93;);<br />        }<br />        <br />        //odczyt struktury<br />        PORTA = pgm_read_byte_far(pgm_get_far_address(my_color.red));<br />        PORTB = pgm_read_byte_far(pgm_get_far_address(my_color.green));<br />        PORTC = pgm_read_byte_far(pgm_get_far_address(my_color.blue));<br />    }<br />}[/syntax]<br /><br /><ul><strong><em>użycie kwalifikatora __flash1</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br /><br />#define WAVEFORMS __attribute__ ((section(&quot;.mysection&quot;)))<br /><br />struct color {<br />    uint8_t red;<br />    uint8_t green;<br />    uint8_t blue;<br />    };<br /><br />// definiujemy tablicę w utworzonej sekcji<br />const uint16_t sine_wave&#91;1024&#93; WAVEFORMS =<br />    {0 /* tutaj wartości próbek */};<br /><br />// definiujemy strukturę<br />const struct color my_color WAVEFORMS = {<br />    156, 16, 220<br />    };<br />int main(void)<br />{<br />    uint16_t i;<br /><br />    // definiujemy wskaźnik do początku tablicy<br />    const __flash1 uint16_t * sine_wave_ptr = sine_wave;<br /><br />    // definiujemy wskaźnik do struktury<br />    const __flash1 struct color * my_color_ptr = &amp;my_color;<br />    <br />    uint16_t tmp_sample;<br /><br />    while (1) <br />    {<br /><br />        // odczyt tablicy<br />        for (i=0; i&lt;sizeof(sine_wave)/sizeof(sine_wave&#91;0&#93;); i++)<br />        {<br />            tmp_sample = *sine_wave_ptr++;<br />            PORTD = (uint8_t)tmp_sample;<br />            PORTE = (uint8_t)(tmp_sample&gt;&gt;8);<br />        }<br /><br />        //odczyt struktury<br />        PORTA = my_color_ptr-&gt;red;<br />        PORTB = my_color_ptr-&gt;green;<br />        PORTC = my_color_ptr-&gt;blue;<br />    }<br />}[/syntax]<br /><br />Obydwa kody realizują to samo zadanie. Ocenę czytelności kodu oraz wygody użycia obydwu metod pozostawiam czytelnikowi.<br /><br />Niestety istnieją również pewne niedogodności związane z definiowaniem danych we własnej sekcji.<br />Jedną z nich jest pobieranie 24-bitowego wskaźnika do danych w naszej sekcji. Gdyby było konieczne zastąpienie wskaźnika z kwalifikatorem <strong><em>__flash1</em></strong> na taki z kwalifikatorem <strong><em>__memx</em></strong> (np. ze względu na położenie tablicy po obu stronach granicy segmentów 64KiB, zwykła zamiana kwalifikatorów nie wystarczy:<br /><br />[syntax=c]// const __flash1 uint16_t * sine_wave_ptr = sine_wave;<br />    const __memx uint16_t * sine_wave_ptr = sine_wave;[/syntax]<br /><br />Po wprowadzeniu takiej modyfikacji program wprawdzie się skompiluje, jednak nie będzie prawidłowo odczytywał danych. <em>Dlaczego?</em> Kompilator po prostu zmienne zdefiniowane z atrybutem <em>section</em> traktuje jako ulokowane w pamięci RAM) i stamtąd będzie próbował odczytywać dane (nie dotyczy to atrybutu <strong><em>PROGMEM</em></strong> – pobranie wskaźnika na dane oznaczone tym atrybutem będzie prawidłowe).<br />Trzeba więc użyć sposobu, dzięki któremu możliwe będzie pobranie prawidłowego („pełnego”) adresu danych. Osobiście rozwiązałem ten problem za pomocą następującego makra:<br /><br />[syntax=c]// makro zwracające 24-bitowy wskaźnik do zmiennej ‘var’<br />#define get_memx_address(var)              \<br />({                                         \<br />    __int24 tmp;                           \<br />                                           \<br />    __asm__ __volatile__(                  \<br />                                           \<br />    &quot;ldi%A0, lo8(%1)&quot;       &quot;\n\t&quot;      \<br />    &quot;ldi%B0, hi8(%1)&quot;       &quot;\n\t&quot;      \<br />    &quot;ldi%C0, hh8(%1)&quot;       &quot;\n\t&quot;      \<br />    :                                      \<br />    &quot;=d&quot; (tmp)                             \<br />    :                                      \<br />    &quot;p&quot;  (&amp;(var))                          \<br />    );                                     \<br />    (__memx typeof(var)*)tmp;              \<br />})[/syntax]<br /><br />Aby nie psuć sobie czytelności kodu wstawkami asemblerowymi i/lub udostępnić makro dla innych modułów programu, można makro umieścić w osobnym pliku nagłówkowym, który później można dołączyć dyrektywą <strong><em>#include</em></strong>.<br /><br />W celu zmiany typu wskaźników w poprzednim przykładzie kodu powinno teraz wyglądać w ten sposób:<br /><br />[syntax=c]// definiujemy wskaźnik do początku tablicy<br />// zamiast: const __flash1 uint16_t * sine_wave_ptr = sine_wave;<br />// nie:     const __memx uint16_t * sine_wave_ptr = sine_wave;<br />// tylko:<br />            const __memx uint16_t * sine_wave_ptr =<br />                get_memx_address(sine_wave&#91;0&#93;);<br />// istotne jest to, że nie używamy operatora &amp;, za to (w przypadku tablicy)<br />// musimy podać indeks elementu (musi to być wartość stała, czyli nie możemy podać<br />// w nawiasie kwadratowym nazwy zmiennej np. sine_wave&#91;i&#93;), którego adres chcemy pobrać;<br />// 0 oczywiście oznacza początek tablicy, ale możemy też pobrać adres dowolnego elementu<br />// definiujemy wskaźnik do struktury<br />// zamiast: const __flash1 uint16_t * my_color_ptr = &amp;my_color;<br />// nie:     const __memx uint16_t * my_color_ptr = &amp;my_color;<br />// tylko:<br />            const __memx struct color * my_color_ptr =<br />                get_memx_address(my_color);<br />// w przypadku struktury lub innych zmiennych (nie tablicowych) podajemy<br />// tylko nazwę zmiennej (bez operatora &amp;)[/syntax]<br /><br />Innym problemem, aczkolwiek nieco podobnej natury, jest definiowanie wskaźników do naszych danych jako zmiennych globalnych. Chodzi mi tutaj o sytuację podobną do tej z przykładu pokazującego umieszczanie ciągów znaków we FLASH, coś w stylu (skrótowo):<br /><br />[syntax=c]// globalnie, czyli przed funkcjami<br />// ciągi znaków we FLASH<br />const char _play&#91;&#93;          PROGMEM = &quot;PLAY&quot;;<br />const char _stop&#91;&#93;          PROGMEM = &quot;STOP&quot;;<br /><br />// globalnie, czyli przed funkcjami<br />// wskaźniki (zapisane we FLASH) do ciągów znaków we FLASH<br />const char * const commands&#91;&#93; PROGMEM = {<br />    _play, _stop<br />};[/syntax]<br /><br />W tym przykładzie zarówno ciągi znaków, jak i tablica zawierająca wskaźniki do tych ciągów, zostaną zapisane w pamięci FLASH.<br /><br />Bazując na tym przykładzie można by się spodziewać, że pisząc analogiczny kod, ale już z użyciem własnej sekcji, otrzymamy taki sam efekt:<br /><br />[syntax=c]// globalnie, czyli przed funkcjami<br />// tablice liczb 16-bitowych bez znaku w sekcji .waveforms (we FLASH)<br />const uint16_t sine_wave&#91;1024&#93;     WAVEFORMS = {0};<br />const uint16_t sawtooth_wave&#91;1024&#93; WAVEFORMS = {0};<br /><br />// globalnie, czyli przed funkcjami<br />// wskaźniki (zapisane we FLASH) <br />// do tablic liczb 16-bitowych bez znaku we FLASH<br />const uint16_t * const wave_ptrs&#91;&#93; WAVEFORMS = {<br />    sine_wave,  sawtooth_wave<br />};[/syntax]<br /><br />jednak byłoby tak tylko wtedy, gdybyśmy naszą sekcję <strong><em>.waveforms</em></strong> zdefiniowali w pierwszym segmencie 64KiB (ta sama zasada zresztą dotyczy również <strong><em>PROGMEM</em></strong> – jeżeli umieścimy dużo danych w pamięci programu i nasze ciągi znaków wylądują w drugiej sekcji, powyższy kod nie będzie działał prawidłowo). W związku z tym, że sekcję umieściliśmy w drugim segmencie, powyższa metoda nie zadziała.<br /><br />Myślę, że podstawowa przyczyna takiego stanu rzeczy jest dość oczywista – uzyskane w ten sposób wskaźniki mają rozmiar 16-bitowy, czyli zbyt mały, by zaadresować dane powyżej 64KiB. Ktoś mógłby powiedzieć: <em>„No ale przecież mamy do dyspozycji ‘wskaźniki’ 32‑bitowe (far pointers).”</em> Owszem, jednak ich uzyskanie wymaga użycia makra <strong><em>pgm_get_far_address()</em></strong>, którego można użyć tylko wewnątrz funkcji, czyli nie da się z jego pomocą zdefiniować zmiennej globalnej. Z kolei wewnątrz funkcji nie da się zdefiniować danych w sekcji <strong><em>.waveforms</em></strong> (w ogóle nie można używać atrybutu <em>section</em> do definiowania zmiennych lokalnych, czyli wewnątrz funkcji). Użycie kwalifikatora <strong><em>__flash</em></strong> (a tym bardziej <strong><em>__memx</em></strong>) do zdefiniowania wskaźników na nasze dane poza funkcją też nie zda egzaminu. Próba zdefiniowania tablicy wskaźników do tablic i zapisanie jej w naszej sekcji <strong><em>.waveforms</em></strong> w ten sposób:<br /><br />[syntax=c]// globalnie, czyli przed funkcjami<br />// tablice liczb 16-bitowych bez znaku w sekcji .waveforms (we FLASH)<br />const uint16_t sine_wave&#91;1024&#93;     WAVEFORMS = {0};<br />const uint16_t sawtooth_wave&#91;1024&#93; WAVEFORMS = {0};<br /><br />// globalnie, czyli przed funkcjami<br />// próba zapisu tablicy wskaźników w sekcji .waveforms (we FLASH)<br />// do tablic liczb 16-bitowych bez znaku w sekcji .waveforms (we FLASH)<br />// nie zadziała<br />const __flash1 uint16_t * const wave_ptrs&#91;&#93; WAVEFORMS = {<br />    sine_wave,  sawtooth_wave<br />};[/syntax]<br /><br />zakończy się błędem: <strong><em>„initializer element is not computable at load time”</em></strong>. Wytłumaczenie przyczyny jest dosyć zawiłe, więc nie będę tutaj opisywał tego szczegółowo, ale generalnie chodzi o to (co wynika ze standardu C), że nie można dokonać konwersji pomiędzy wskaźnikami do różnych przestrzeni adresowych w trakcie ładowania programu (czyli przed jego wejściem do funkcji <strong><em>main()</em> </strong>).<br /><br />Może się wydawać, że to nie jest poważny problem. Można przecież zdefiniować wskaźniki w pamięci RAM wewnątrz funkcji <strong><em>main()</em></strong> i później przekazywać je do różnych funkcji, ale nie zawsze jest to tak wygodne rozwiązanie, jak globalna tablica wskaźników dostępna w każdej funkcji w obrębie danego pliku. Można oczywiście zadeklarować tablicę wskaźników w RAM jako zmienną globalną, a później zainicjować ją wewnątrz funkcji <strong><em>main()</em></strong>, ale wtedy nie można zadeklarować wskaźników jako <strong><em>const</em></strong>, przez co mogą być podatne na niezamierzone modyfikacje.<br /><br />[syntax=c]// globalnie, czyli przed funkcjami<br />// tablice liczb 16-bitowych bez znaku w sekcji .waveforms (we FLASH)<br />const uint16_t sine_wave&#91;1024&#93;     WAVEFORMS = {0};<br />const uint16_t sawtooth_wave&#91;1024&#93; WAVEFORMS = {0};<br /><br />// dostępna globalnie tablica wskaźników w RAM<br />// inicjowana we funkcji main()<br />// zadziała, jednak wskaźniki nie mogą być oznaczone jako ‘const’<br />// dodatkowa niedogodność to konieczność podania rozmiaru tablicy<br />const __flash1 uint16_t * /*-&gt;const&lt;-*/ wave_global_ptrs&#91;2&#93;;<br /><br />int main(void)<br />{<br />    // zainicjowanie elementów tablicy dostępnej GLOBALNIE<br />    wave_global_ptrs&#91;0&#93; = (const __flash1 uint16_t *)sine_wave;<br />    wave_global_ptrs&#91;1&#93; = (const __flash1 uint16_t *)sawtooth_wave;<br /><br />    // definicja tablicy dostępnej tylko LOKALNIE wewnątrz funkcji main()<br />    // elementy tablicy (wskaźniki mogą być oznaczone jako ‘const’<br />    // przez co ich przypadkowa modyfikacja jest mało prawdopodobna<br />    const __flash1 uint16_t * const wave_local_ptrs&#91;&#93; = {<br />        (const __flash1 uint16_t *)sine_wave,<br />        (const __flash1 uint16_t *)sawtooth_wave<br />    };<br />    // --- reszta kodu --- <br />}[/syntax]<br /><br />Należy też wziąć pod uwagę, że możemy potrzebować tablicy zawierającej dużo więcej wskaźników (niż w przedstawionym powyżej przykładzie), po 2 bajty każdy (lub 3 w przypadku <strong><em>__memx</em></strong>) i wtedy umieszczenie ich w pamięci RAM może okazać się problemem.<br /><br />Rozwiązaniem tego problemu może być zdefiniowanie wskaźników za pomocą wartości liczbowych równych adresom danych. Oczywiście jest ono nieco kłopotliwe, pracochłonne i mało eleganckie, ale ma też duże zalety. Otrzymujemy bowiem tablicę wskaźników, która nie zajmuje pamięci RAM, jest dostępna globalnie i jest przeznaczona tylko do odczytu, dzięki czemu nie ma ryzyka nieintencjonalnej zmiany ich wartości.<br /><br />W celu utworzenia takiej tablicy wskaźników, definiujemy najpierw swoje zmienne:<br /><br />[syntax=c]// globalnie, czyli przed funkcjami<br />// tablice liczb 16-bitowych bez znaku w pamięci FLASH<br />const uint16_t sine_wave&#91;1024&#93;     WAVEFORMS = {0};<br />const uint16_t sawtooth_wave&#91;1024&#93; WAVEFORMS = {0};[/syntax]<br /><br />Następnie należy dowiedzieć się, pod jakimi adresami zastały one umieszczone. Niestety zanim zmienne nie będą w jakiś sposób użyte w kodzie, linker ich nie dołączy do pliku wynikowego. Można oczywiście napisać jakiś fragment (później do niczego niepotrzebny), w którym te dane będą użyte, aby wymusić dołączenie ich przez linker do pliku wynikowego <strong><em>*.hex</em></strong>, ale wygodniejszym i bardziej eleganckim rozwiązaniem będzie dodanie opcji <em>-u symbol</em> do linii poleceń linkera (opcja ta to skrót od opcji <em>--undefined=symbol</em>).<br />W Atmel Studio 7 służy do tego celu:<br /><br />menu <em>Project‑&gt; Properties…‑&gt; Toolchain‑&gt; AVR/GNU Linker‑&gt; Miscellaneous‑&gt; pole tekstowe Other Linker Flags</em><br /><br />lub w Eclipse MARS 2 to samo miejsce, w którym wcześniej definiowaliśmy naszą sekcję, czyli:<br /><br />menu <em>Project‑&gt;Properties‑&gt; C/C++ Build‑&gt; Settings‑&gt;  (zakładka Tool Settings) AVR C Linker‑&gt; General‑&gt;</em> pole tekstowe  <em>Other Arguments</em><br /><br />W zasadzie wpisanie jednej zmiennej znajdującej się w danej sekcji powinno spowodować, że linker dołączy całą sekcję, czyli wszystkie zmienne, które w tej sekcji zostały zdefiniowane. W naszym przypadku oznacza to, że dodanie linkerowi opcji:<br /><br /><em>-u sine_wave</em><br /><br />powinno spowodować dołączenie przez linker nie tylko tablicy <strong><em>sine_wave</em></strong>, ale także tablicy <strong><em>sawtooth_wave</em></strong>. W niektórych przypadkach (np. kiedy mamy nasze tablice umieszczone w innych sekcjach) może być konieczne dodanie pozostałych zmiennych, czyli w naszym przypadku:<br /><br /><em>-u sine_wave -u sawtooth_wave</em><br /><br />Po tym zabiegu i skompilowaniu projektu, zmienne powinny już być dołączone do pliku wynikowego, nawet jeśli nie są nigdzie używane w kodzie. Uruchomienie teraz naszego narzędzia wydobywającego informacje o rozmieszczeniu danych w pamięci FLASH (pod warunkiem dodania do pliku <strong><em>*.bat</em></strong> nazwy naszej sekcji <strong><em>.waveforms</em></strong>, jak to opisałem wcześniej) powinno wygenerować co następuje:<br /><br /><div class="codetitle"><b>Code:</b></div><div class="codecontent">00010100 g     O .waveforms   00000800 sawtooth_wave<br />00010900 g     O .waveforms   00000800 sine_wave</div><br /><br />Należy tu zwrócić uwagę na to, że kolejność umieszczenia danych w pamięci FLASH jest odwrotna do kolejności definicji w kodzie. Oczywiście możemy sobie przyjąć dowolną kolejność naszych wskaźników w tablicy, chodzi tylko o to, by mieć świadomość, jakie wartości adresów są przyporządkowane poszczególnym nazwom zmiennych. Gdybyśmy chcieli zachować taką samą kolejność wskaźników jak definicji, deklaracja naszej tablicy powinna wyglądać następująco:<br /><br />[syntax=c]// globalnie, czyli przed funkcjami<br />// tablica wskaźników do tablic liczb 16-bitowych bez znaku (w sekcji .waveforms)<br />// tablica wskaźników nie zostanie zapisana w sekcji .waveforms,<br />// tylko w pierwszym segmencie 64KiB<br />const __memx uint16_t * const __flash wave_ptrs&#91;2&#93; = {<br />    (const __memx uint16_t *)0x10900, /* sine_wave */<br />    (const __memx uint16_t *)0x10100, /* sawtooth_wave */<br />};[/syntax]<br /><br />Wprawdzie tablica wskaźników nie zostanie zapisana w naszej sekcji .waveforms, tylko (standardowo) w pierwszym segmencie 64KiB, jednak nie powinno to stanowić problemu, ponieważ zwykle tablice wskaźników nie mają jakiegoś znaczącego rozmiaru, za to obsługa zapisanych w ten sposób wskaźników będzie wygodniejsza.<br /><br />Przedstawię teraz kod demonstrujący opisany powyżej sposób. Nie jest to wprawdzie jakiś użyteczny projekt, choć powinien działać prawidłowo na mikrokontrolerze ATmega2560. Kod generuje za pomocą sygnału PWM na pinie OC1A cztery przebiegi (rozdzielczość 10-bitowa) – sinus, piła, trójkąt i użytkownika – o częstotliwości około 15Hz oraz o czasie trwania około 1 sekundy z przerwami trwającymi około 0,5 sekundy. Jeśli ktoś dysponuje mikrokontrolerem ATmega2560 i chciałby poeksperymentować, to oprócz przedstawionych poniżej plików <strong>main.c</strong> oraz  <strong>wavedata.h</strong> należy do projektu dołączyć plik <strong>wavedata.c</strong> do ściągnięcia w postaci spakowanej (ze względu na obszerność).<br /><br />Plik <strong>main.c</strong>:<br /><br />[syntax=c]// main.c<br /><br />#include &lt;avr/io.h&gt;<br />#include &lt;stddef.h&gt;<br />#include &lt;util/delay.h&gt;<br />#include &lt;avr/interrupt.h&gt;<br />#include &lt;stdbool.h&gt;<br />#include &quot;wavedata.h&quot;<br /><br />// definiujemy tablicę wskaźników do tablic<br />const __memx uint16_t * const __flash wave_ptrs&#91;&#93; = {<br />    SINE_PTR,       /* sine_wave */<br />    SAWTOOTH_PTR,   /* sawtooth_wave */<br />    TRIANGLE_PTR,   /* triangle_wave */<br />    CUSTOM_PTR      /* custom_wave */<br />};<br /><br />// zmienna przechowująca wskaźnik aktualnie generowanego przebiegu<br />volatile const __memx uint16_t * current_wave_ptr;<br /><br />// flaga stop służąca do zatrzymania generatora przebiegu<br />// po zakończeniu pełnego okresu<br />volatile bool stop_flag = false;<br /><br />// funkcja startująca generowanie przebiegu<br />void start_generator(const __memx uint16_t * wave_ptr);<br /><br />// funkcja zatrzymująca generowanie przebiegu<br />inline void stop_generator(void) { stop_flag = true;}<br /><br />// ------------------------------------------------------------------<br />int main(void) {<br /><br />    uint8_t i = 0;<br />    <br />    // konfiguracja timera<br />    // tryb 7 - fast PWM 10 bit, wyjście OC1A nieodwracające<br />    TCCR1A = (1&lt;&lt;COM1A1) | (1&lt;&lt;WGM11) | (1&lt;&lt;WGM10);<br />    TCCR1B = (1&lt;&lt;WGM12);<br />    <br />    // wartość OCR1A równa połowie rozdzielczości<br />    // co odpowiada wirtualnemu poziomowi 0V generowanego<br />    // przebiegu<br />    OCR1A = VIRTUAL_ZERO;<br />    <br />    // start generowania PWM<br />    TCCR1B |= (1&lt;&lt;CS10);<br />    <br />    // pin OC1A (pin 5 na porcie B) jako wyjście<br />    DDRB |= (1&lt;&lt;DDB5);<br />    <br />    // włączenie przerwań<br />    sei();<br />    <br />    while (1) {<br />        <br />        // uruchomienie generowania sygnału<br />        start_generator(wave_ptrs&#91;i++&#93;);<br />        // tutaj przekazujemy wskaźniki z tablicy na podstawie indeksu<br />        // inkrementowanego w pętli, ale można również wywołać funkcję<br />        // przekazując bezpośredni wskaźnik do któregoś z przebiegów np.:<br />        // start_generator(SINE_PTR);<br />        <br />        // ograniczenie wartości 'i' do ilości wskaźników w tablicy<br />        if (i == sizeof(wave_ptrs)/sizeof(wave_ptrs&#91;0&#93;)) i = 0;<br />        // włączenie opóźnienia około 1 sekundy;<br />        // ze względu na włączoną obsługę przerwań funkcja<br />        // _delay_ms() nie odmierzy czasu zbyt dokładnie<br />        // ale nie to jest tutaj celem<br />        // w trakcie opóźnienia przebieg jest generowany<br />        // poprzez procedurę obsługi przerwania<br />        _delay_ms(1000);<br />        <br />        // zatrzymanie generowania przebiegu<br />        // zatrzymanie nastąpi dopiero po zakończeniu generowania<br />        // pełnego okresu<br />        stop_generator();<br />        <br />        // przerwa między generowanymi przebiegami<br />        _delay_ms(500);<br />    }<br />}<br />// ------------------------------------------------------------------<br /><br />// procedura obsługi przerwania od przepełnienia timera 1<br />ISR(TIMER1_OVF_vect) {<br />    // zmienna zawierająca indeks tablicy dla aktualnej próbki<br />    static uint16_t j = 0;<br />    <br />    // wpisanie próbki o indeksie 'j' do rejestru OCR1A<br />    // dzięki użyciu kwalifikatora __flash i zapisaniu<br />    // tablicy wskaźników w pierwszym segmencie 64KiB<br />    // w celu wprowadzenia wartości aktualnej próbki<br />    // do rejestru OCR1A wystarczy poniższa linijka<br />    OCR1A = *(current_wave_ptr + j++);<br />    <br />    // po osiągnięciu końca tablicy zaczynamy od początku<br />    if (j == SAMPLES_CNT) j = 0;<br />    <br />    // zatrzymanie generowania sygnału dopiero po dokończeniu<br />    // generowania pełnego okresu<br />    if ( (j == 1 ) &amp;&amp; stop_flag ) {<br />        j = 0;<br />        TIMSK1 &amp;= ~(1&lt;&lt;TOIE1);<br />    }<br />}<br />// funkcja startująca generowanie przebiegu<br />void start_generator(const __memx uint16_t * wave_ptr) {<br />    // ustawienie aktualnego wskaźnika na tablicę<br />    // z żądanym przebiegiem<br />    current_wave_ptr = wave_ptr;<br />    <br />    // wyzerowanie flagi 'stop'<br />    stop_flag = false;<br />    <br />    // wyzerowanie flagi przerwania od przepełnienia<br />    TIFR1 = (1&lt;&lt;TOV1);<br />    <br />    // wyzerowanie licznika<br />    TCNT1 = 0;<br />    <br />    // zezwolenie na obsługę przerwań od przepełnienia<br />    TIMSK1 |= (1&lt;&lt;TOIE1);<br />}[/syntax]<br /><br />Plik <strong>wavedata.h</strong>:<br /><br />[syntax=c]// wavedata.h<br />#ifndef WAVEDATA_H_<br />#define WAVEDATA_H_<br /><br />// definicja rzutowania na wskaźnik __memx do danych typu int16_t<br />#define MEMX_I16_PTR(p) (const __memx uint16_t *)(p)<br /><br />// definicje adresów do poszczególnych tablic<br />#define SINE_PTR        MEMX_I16_PTR(0x11800)<br />#define SAWTOOTH_PTR    MEMX_I16_PTR(0x11000)<br />#define TRIANGLE_PTR    MEMX_I16_PTR(0x10800)<br />#define CUSTOM_PTR      MEMX_I16_PTR(0x10000)<br /><br />// ustalamy ilość elementów tablic na 1024<br />// rozmiar (w bajtach) wyniesie:<br />// 1024 * sizeof(int16_t) = 1024 * 2 = 2048 bajtów<br />#define SAMPLES_CNT    1024<br />#define VIRTUAL_ZERO        (SAMPLES_CNT/2-1)<br /><br />#endif /* WAVEDATA_H_ */[/syntax]<br /><br />Plik <strong>wavedata.c</strong> (start sekcji <strong><em>.waveforms</em></strong>: adres słowa <strong>0x8000</strong> lub adres bajtu <strong>0x10000</strong>):</li></ul></li></ul></li></ul><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 9 wrz 2017, o 14:27</p><hr />
]]></content>
</entry>
<entry>
<author><name><![CDATA[andrews]]></name></author>
<updated>2017-09-09T14:36:22+01:00</updated>
<published>2017-09-09T13:33:48+01:00</published>
<id>https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194736#p194736</id>
<link href="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194736#p194736"/>
<title type="html"><![CDATA[AVR-GCC - dane w pamięci FLASH]]></title>

<content type="html" xml:base="https://forum.atnel.pl/viewtopic.php?t=19149&amp;p=194736#p194736"><![CDATA[
<span style="font-size: 130%; line-height: normal"><strong>Wstęp</strong></span><br /><br /><ul>Język C w zasadzie został stworzony dla architektury Von Neumann, w której dane i kod wykonywalny współdzielą tę samą przestrzeń adresową. W związku z tym standard języka C nie przewiduje mechanizmów czy też słów kluczowych, które wspierałyby obsługę oddzielnych przestrzeni adresowych. Kompilatory przeznaczone dla architektury Harwardzkiej muszą stosować różnego rodzaju sztuczki, aby się z tą obsługą uporać.<br /><br />W AVR-GCC początkowo zostało to rozwiązane za pomocą nadawania atrybutu <strong><em>PROGMEM</em></strong> danym przeznaczonym do zapisu w pamięci programu (FLASH). Metoda ta wymaga jednak zastosowania kłopotliwych w użyciu makr. Kłopotliwych nie tylko ze względu na pogorszenie czytelności kodu, ale także ze względu na utratę przez kompilator kontroli na typami danych odczytywanymi w ten sposób.<br /><br />Później, od wersji 4.7, zostało dodane (oprócz <em>PROGMEM</em>) inne rozwiązanie. Polega ono na skierowaniu danych do umieszczenia we FLASH poprzez nadanie stałej w momencie definicji kwalifikatora <strong><em>__flash</em></strong> lub <strong><em>__memx</em></strong>. Kwalifikator taki zostaje przypisany do konkretnej stałej, dzięki czemu składnia odczytu danych z pamięci FLASH praktycznie jest taka sama, jak przy odczycie zmiennych z pamięci RAM (kompilator wie, skąd odczytywać dane na podstawie kwalifikatora) i dodatkowo zostaje zachowana w pełni kontrola typów.<br /><br />Obecnie mamy dwie opcje obsługi danych w pamięci FLASH. Pierwsza z nich jest nieco kłopotliwa w użyciu ze względu na konieczność stosowania makr zmniejszających czytelność kodu i utratę kontroli nad typami. Druga metoda nadal znajduje się w fazie testów, więc można mieć obawy, że nie wszystko działa zawsze zgodnie z założeniem. Osobiście korzystam z tej metody od dłuższego czasu i nie spotkałem się z sytuacją, żeby coś działało nieprawidłowo. Moim skromnym zdaniem warto znać tę drugą, nowszą metodę ze względu na lepszą czytelność kodu i zachowanie kontroli typów.<br /><br />Postaram się tutaj opisać i porównać obydwie metody oraz wskazać ewentualne problemy, jakie można napotkać przy ich stosowaniu.</ul><br /><span style="font-size: 130%; line-height: normal"><strong>Podstawy teoretyczne</strong></span><br /><br /><ul>Na początek chciałbym opisać kilka istotnych dla przedmiotu sprawy zagadnień. Ich znajomość pozwoli lepiej zrozumieć, na czym polega problematyka odczytu danych z pamięci programu.<br /><br /><ul><span style="font-size: 120%; line-height: normal"><strong>• Adresowanie pamięci FLASH</strong></span><br /><br />Pojemność pamięci FLASH w ośmiobitowych mikrokontrolerach AVR podawana jest wprawdzie w bajtach, jednak instrukcje mikrokontrolera mają rozmiar dwóch lub czterech bajtów. W związku z tym pamięć programu zorganizowana jest jako słowa 16-bitowe. Wynika z tego oczywiście, że w mikrokontrolerze o pojemności N[KiB] można umieścić maksymalnie N/2 instrukcji.<br /><br />Pomimo takiej organizacji pamięci FLASH, możliwe jest także odczytanie pojedynczego bajtu.<br /><br /><ul><span style="font-size: 110%; line-height: normal"><strong><em>◦ Adres słowa</em></strong></span><br />Wszystkie instrukcje sterujące wykonywaniem programu, takie jak instrukcje skoku warunkowego i bezwarunkowego (względnego i bezwzględnego) czy też wywołania podprocedury, używają adresu słowa.<br />Rejestr <strong>PC</strong> (<strong>P</strong>rogram <strong>C</strong>ounter) również zawiera adres słowa – adres aktualnie wykonywanej instrukcji. Jego szerokość (w bitach) jest ściśle związana z pojemnością pamięci FLASH w słowach.<br />Za pomocą 16-bitowego wskaźnika czy też rejestru <strong>PC</strong> można zaadresować maksymalnie 128KiB (64Ki słów) pamięci FLASH.<br /><br /><span style="font-size: 110%; line-height: normal"><strong><em>◦ Adres bajtu</em></strong></span><br />Instrukcja odczytu danych z pamięci FLASH (<strong><em>LPM</em></strong> – <em><strong>L</strong>oad <strong>P</strong>rogram <strong>M</strong>emory</em>) korzysta z adresu bajtu. Adres ten musi zostać umieszczony w rejestrze wskaźnikowym <strong><em>Z</em></strong> (R31:R30). Adres taki jest tworzony poprzez pomnożenie adresu słowa przez <strong>2</strong> (lub przesunięcie bitowe o jeden bit w lewo). Wartość najmniej znaczącego bitu decyduje o tym, który bajt słowa instrukcja ma odczytać:<br /><li><strong>Z[bit0]=0</strong>: odczytany zostanie mniej znaczący bajt słowa,<br /><li><strong>Z[bit0]=1</strong>: odczytany zostanie bardziej znaczący bit słowa</ul><br /><ul>Za pomocą 16-bitowego wskaźnika <strong><em>Z</em></strong> można zaadresować maksymalnie 64KiB pamięci FLASH.<br /><br /></ul><span style="font-size: 120%; line-height: normal"><strong>• Adresowanie rozszerzone</strong></span><br /><br />Generalnie rejestry wskaźnikowe w ośmiobitowych mikrokontrolerach AVR mają szerokość 16 bitów. Pozwala to na zaadresowanie maksymalnie 64KiB danych w pamięci. W zupełności wystarcza to do obsługi pamięci RAM, jednak istnieją mikrokontrolery, w których pojemność pamięci FLASH kilkakrotnie przekracza tę wartość.<br /><br /><em>W jaki sposób, w takim przypadku, zaadresować dane spoza limitu 64KiB?</em><br /><br />Otóż mikrokontrolery z tak dużymi pojemnościami FLASH mają dodatkowe rejestry rozszerzające rejestr <strong><em>Z</em></strong> o ilość bitów potrzebną do zaadresowania całego obszaru pamięci programu. Tymi rejestrami są:<br /><br /><ul><li><strong><em>RAMPZ</em></strong> używany przez instrukcję <strong><em>ELPM</em></strong> (<em><strong>E</strong>xtended <strong>L</strong>oad <strong>P</strong>rogram Memory</em>) w połączeniu z rejestrem <strong><em>Z</em></strong> (RAMPZ:R31:R30) do zaadresowania bajtu danych w pamięci programu – rejestr ten występuje tylko w MCU o pojemności FLASH większej od 64KiB,</li></ul><br /><ul><li><strong><em>EIND</em></strong> używany przez instrukcje <strong><em>EIJMP</em></strong> (<em><strong>E</strong>xtended <strong>I</strong>ndirect <strong>J</strong>u<strong>MP</strong></em>) oraz <strong><em>EICALL</em></strong> (<em><strong>E</strong>xtended <strong>I</strong>ndirect <strong>CALL</strong> to Subroutine</em>) w połączeniu z rejestrem <strong><em>Z</em></strong> (<strong><em>EIND:R31:R30</em></strong>) do zaadresowania <strong>słowa</strong> kodu wykonywalnego w pamięci programu – rejestr ten występuje tylko w MCU o pojemności FLASH większej od 128KiB (64K słów).</li></ul></li></ul><br /><ul><span style="font-size: 120%; line-height: normal"><strong>• Troszkę podstaw z procesu budowania programu</strong></span><br /><br />Przedstawię tutaj tylko wybrane wiadomości w dużym uproszczeniu. Chodzi tylko o ogólne zrozumienie zasad rozmieszczania danych i kodu w pamięci programu przez linker.<br /><br />Pisany przez nas program musi zawierać co najmniej jeden plik z kodem źródłowym (z rozszerzeniem <strong><em>*.c</em></strong>). Może też być ich więcej. Pliki te nie są bezpośrednio przetwarzane do pliku wykonywalnego. Każdy plik z kodem źródłowym jest najpierw poddany działaniu preprocesora, a następnie przetworzony przez kompilator do pliku obiektowego (plik z rozszerzeniem <strong><em>*.o</em></strong>). Następnie wszystkie pliki obiektowe powstałe w wyniku kompilacji naszych plików źródłowych (nawet, jeśli jest tylko jeden taki plik) zostają skierowane do linkera, który je łączy w jeden plik wynikowy (plik z rozszerzeniem <strong><em>*.elf</em></strong>). Pliki z rozszerzeniami <strong><em>*.hex</em></strong> oraz <strong><em>*.eep</em></strong>, którymi programujemy mikrokontroler powstają w wyniku wydobycia poprzez program <strong><em>objcopy</em></strong> (w naszym przypadku będzie to <strong><em>avr-objcopy</em></strong>) odpowiednich danych właśnie z pliku <strong><em>*.elf</em></strong>.<br /><br />Nasz plik źródłowy zawiera jednak różne dane: definicje zmiennych w RAM, dane do umieszczenia w pamięci FLASH lub EEPROM oraz kod funkcji. Kompilator musi więc te dane odpowiednio oznaczyć, aby linker mógł je prawidłowo zidentyfikować i rozmieścić. I tak na przykład zmienne w RAM są oznaczane przez kompilator w pliku obiektowym jako sekcja <strong><em>”.data”</em></strong>, dane w pamięci programu – jako sekcja <strong><em>”.progmem.data”</em></strong>, dane przeznaczone dla EEPROM – jako sekcja <strong><em>”.eeprom”</em></strong> a kod funkcji – jako sekcja <strong><em>”.text”</em></strong> (która docelowo też jest przeznaczona do zapisu w pamięci FLASH).<br /><br />Pliki obiektowe z kompilatora są plikami wejściowymi dla linkera. Plik wynikowy jest plikiem wyjściowym i również posiada różne sekcje. Nie jest jednak tak, że sekcje z plików obiektowych zawsze pokrywają się z sekcjami w pliku wynikowym. O tym, jakie będą relacje między sekcjami wejściowymi i sekcjami wyjściowymi (czyli w których sekcjach wyjściowych zostaną umieszczone poszczególne sekcje wejściowe i w jakiej kolejności) decydują opcje linkera w linii poleceń oraz specjalny plik konfiguracyjny, tak zwany skrypt linkera (zawierający kod w języku linker command language).<br /><br />Przykładowo w domyślnych skryptach linkera dla AVR zarówno sekcja wejściowa <strong><em>”.text”</em></strong> jak i (między innymi) sekcje wejściowe <strong><em>”.vectors”</em></strong> oraz <strong><em>”.progmem.data”</em></strong> są przeznaczone do tej samej sekcji wyjściowej <strong><em>”.text”</em></strong>. Skrypt linkera decyduje również o kolejności umieszczenia poszczególnych sekcji wejściowych w sekcji wyjściowej. Przykładowo standardowa kolejność w domyślnych skryptach linkera dla AVR to:<ul><li><strong><em>.vectors</em></strong> (czyli wektory przerwań)<br /><li><strong><em>.progmem.data</em></strong> (czyli dane w pamięci programu)</li><li><strong><em>.text</em></strong> (czyli funkcje – kod wykonywalny)</li></ul><br />Oczywiście to tak w uproszczeniu. Pominąłem tutaj kilka (zapewne nie mniej istotnych) sekcji, aby zbytnio nie komplikować tematu.<br /><br />Opcje i skrypty linkera to temat na osobny, całkiem obszerny artykuł, więc nie będę tutaj opisywał tego szczegółowo. Chodzi tylko o zrozumienie pewnych ogólnych zasad.</li></ul></li></ul><br /><span style="font-size: 130%; line-height: normal"><strong>Obsługa danych w pamięci FLASH</strong></span><br /><br />Postaram się teraz opisać zasady obsługi danych w pamięci programu w różnych sytuacjach przy zastosowaniu obydwu z metod oraz z możliwie dokładnym opisem. Przedstawione przykłady kodu mają za zadanie tylko pokazać zasady zapisu i odczytu danych i nie należy dopatrywać się w nich głębszego sensu, choć starałem się, aby były kompletne i działały prawidłowo w symulatorze Atmel Studio 7 (standard języka <strong><em>-std=gnu99</em></strong>). Niektóre proste przykłady do prawidłowego działania mogą wymagać wyłączenia optymalizacji, gdyż kompilator, zamiast zapisywać dane we FLASH, będzie je traktował jak makra zdefiniowane za pomocą dyrektywy <strong><em>#define</em></strong>.<br /><br /><ul><span style="font-size: 120%; line-height: normal"><strong>• Dane w pierwszym segmencie 64KiB</strong></span><br /><br />W rzeczywistości należy pamiętać o tym, że w większości przypadków w pierwszej kolejności w pamięci FLASH (począwszy od adresu 0) muszą być zapisane wektory przerwań (chyba że zmieniliśmy to stosując odpowiednie ustawienia fusbitów i rejestrów). W związku z tym limit 64KiB będzie nieco mniejszy, gdyż musimy od niego odjąć rozmiar wektorów przerwań (zależny od typu mikrokontrolera). Oczywistym jest też fakt, że ograniczenie to dotyczy tylko mikrokontrolerów o pojemności większej niż 64KiB.<br /><br />Jeśli więc piszemy program na mikrokontroler o pojemności mniejszej lub równej 64KiB lub nasze dane do umieszczenia w pamięci FLASH nie przekraczają łącznie 64KiB (pomniejszonych ewentualnie o rozmiar wektorów przerwań), wystarczy nam znajomość atrybutu <strong><em>PROGMEM</em></strong> (oraz makr z grupy <strong><em>pgm_read_xxx();</em></strong> ) lub kwalifikatora <strong><em>__flash</em></strong>.<br /><br />Właściwie w obydwu przypadkach definicji, tak za  pomocą atrybutu <strong><em>PROGMEM</em></strong> jak i przy użyciu kwalifikatora <strong><em>__flash</em></strong>, kompilator umieści dane w sekcji <strong><em>”.progmem.data”</em></strong>. Różnica polega tylko na sposobie dostępu do tych danych w kodzie źródłowym.<br /><br /><ul><span style="font-size: 110%; line-height: normal"><strong><em>◦ Pojedyncza wartość liczbowa</em></strong></span><br /><br />Wprawdzie w języku C zwykle pojedyncze stałe definiujemy raczej przy użyciu dyrektywy preprocesora <strong><em>#define</em></strong>, jednak mimo wszystko postanowiłem pokazać, jak zapisać je w pamięci FLASH, a następnie odczytać, bo być może to pozwoli lepiej zrozumieć następne przykłady.<br /><br /><ul><strong><em>Definicja przy pomocy atrybutu PROGMEM</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br />#include &lt;util/atomic.h&gt;<br /><br />// definicja pojedynczej wartości całkowitej<br />const uint16_t pulse_width PROGMEM = 5689;<br />// zmienna w RAM<br />uint16_t pulse_width_ram;<br />// definicja funkcji<br />void set_pwm_P(const uint16_t pw)<br />{<br />    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)<br />    {<br />        OCR1A = pw;<br />    }<br />}<br /><br />int main(void)<br />{<br />    // bezpośredni odczyt wartości całkowitej (FLASH-&gt;RAM)<br />    pulse_width_ram = pgm_read_word(&amp;pulse_width);<br />    // przekazanie wartości do funkcji<br />    set_pwm_P( pgm_read_word(&amp;pulse_width) );<br />    // definicja zmiennej w RAM<br />    // zawierającej wskaźnik do wartości we FLASH<br />    const uint16_t * pulse_width_ptr = &amp;pulse_width;<br />    // odczyt za pomocą wskaźnika<br />    pulse_width_ram = pgm_read_word(pulse_width_ptr);<br />    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)<br />    {<br />        OCR1A = pulse_width_ram;<br />        // lub<br />        // OCR1A = pgm_read_word(&amp;pulse_width);<br />        // lub<br />        // OCR1A = pgm_read_word(pulse_width_ptr);<br />    }<br />    while (1)<br />    {<br />    }<br />}[/syntax]<br /><br /><ul><strong><em>Definicja przy pomocy kwalifikatora __flash</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br />#include &lt;util/atomic.h&gt;<br /><br />// definicja pojedynczej wartości całkowitej<br />const __flash uint16_t pulse_width = 5689;<br />// zmienna w RAM<br />uint16_t pulse_width_ram;<br />// definicja funkcji<br />void set_pwm_P(const uint16_t pw)<br />{<br />    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)<br />    {<br />        OCR1A = pw;<br />    }<br />}<br /><br />int main(void)<br />{<br />    // bezpośredni odczyt wartości całkowitej (FLASH-&gt;RAM)<br />    pulse_width_ram = pulse_width;<br />    // przekazanie wartości do funkcji<br />    set_pwm_P( pulse_width );<br />    // definicja zmiennej w RAM<br />    // zawierającej wskaźnik do wartości we FLASH<br />    const __flash uint16_t * pulse_width_ptr = &amp;pulse_width;<br />    // odczyt za pomocą wskaźnika<br />    pulse_width_ram = *pulse_width_ptr;<br />    <br />    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)<br />    {<br />        OCR1A = pulse_width_ram;<br />        // lub<br />        // OCR1A = pulse_width;<br />        // lub<br />        // OCR1A = *pulse_width_ptr;<br />    }<br />    while (1)<br />    {<br />    }<br />}[/syntax]<br /><br />Jeżeli teraz porównamy kilka linijek kodu, to nietrudno zauważyć różnice w czytelności:<br /><br />[syntax=c]// bezpośredni odczyt wartości całkowitej (FLASH-&gt;RAM)<br />    pulse_width_ram = pgm_read_word(&amp;pulse_width);  /* PROGMEM */<br />    pulse_width_ram = pulse_width;                  /* __flash */<br />// przekazanie wartości do funkcji<br />    set_pwm_P( pgm_read_word(&amp;pulse_width) );       /* PROGMEM */<br />    set_pwm_P( pulse_width );                       /* __flash */<br />// odczyt poprzez wskaźnik<br />    OCR1A = pgm_read_word(pulse_width_ptr);         /* PROGMEM */<br />    OCR1A = *pulse_width_ptr;                       /* __flash */[/syntax]<br /><br />Dodatkowym problemem w przypadku użycia <strong><em>PROGMEM</em></strong> jest nie tylko konieczność użycia makr w stylu <strong><em>pgm_read_xxx()</em></strong>, lecz również to, że muszą one być adekwatne do odczytywanego typu stałej zapisanej w pamięci FLASH. Jeżeli w powyższym przykładzie zamiast użyć makra <strong><em>pgm_read_word()</em></strong> napiszemy <strong><em>pgm_read_byte()</em></strong>, <strong><span style="color: #8000BF">nie otrzymamy od kompilatora żadnego ostrzeżenia, a odczyt będzie nieprawidłowy</span></strong> (odczytany zostanie tylko jeden bajt).<br /><br />Odczyt danych zadeklarowanych do zapisu w pamięci programu przy użyciu kwalifikatora <strong><em>__flash</em></strong> jest pozbawiony tej wady. Kompilator zna typ odczytywanych danych i zawsze automatycznie odczyta prawidłową ilość bajtów.<br /><br /><span style="font-size: 110%; line-height: normal"><strong><em>◦ Tablica wartości stałych</em></strong></span><br /><br /><ul><strong><em>Definicja przy pomocy atrybutu PROGMEM</em></strong></ul><br />[syntax=c]/*<br /> * test_flash.c<br /> */ <br /><br />#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br />#include &lt;util/delay.h&gt;<br /><br /><br />// tablica zawierająca 4 elementy 8-bajtowe<br />// czyli 64-bitowe (uint64_t)<br />// w razie potrzeby można oczywiście użyć dowolnego<br />// typu i dowolnej ilości (odpowiednich dla tego typu)<br />// wartości<br />const uint64_t sn_array&#91;&#93; PROGMEM = {<br />    0x5200248A78AC4598,<br />    0x670125AD2F54AA2D,<br />    0x6600055AC52B6879,<br />    0xC000A2A5FD510230<br />};<br /><br />// ”proteza” funkcji odczytującej &quot;serial number&quot;<br />// z pastylki iButton (np. DS1990R)<br />uint64_t iButton_read_sn(void)<br />{<br />    return 0xC000A2A5FD510230;<br />}<br />// prosta funkcja sprawdzająca uprawnienia odczytanego<br />// numeru seryjnego do otwarcia drzwi<br />// parametry:<br />//      sn_read     - odczytany numer seryjny<br />//      allowed_sns - adres tablicy w pamięci programu<br />//                    z uprawnionymi numerami seryjnymi<br />//      cnt         - ilość elementów tablicy<br />void access_control(    uint64_t sn_read,<br />                        const uint64_t allowed_sns&#91;&#93;,<br />                        uint8_t cnt )<br />{<br />    uint64_t temp;<br />    for ( uint8_t i = 0; i&lt;cnt; i++ )<br />    {<br />        // przy tak dużym rozmiarze elementu tablicy<br />        // konieczne jest użycie funkcji memcpy_P()<br />        // do jego odczytania; elementy o mniejszym<br />        // rozmiarze należy odczytywać za pomocą<br />        // makr pgm_read_xxx() zależnie od typu<br />        memcpy_P( &amp;temp, &amp;allowed_sns&#91;i&#93;, sizeof(temp) );<br />        if ( sn_read == temp )<br />        {<br />            PORTA |= 0x01;<br />            //_delay_ms(2000);<br />            PORTA &amp;= 0xFE;<br />            //_delay_ms(1000);<br />            break;<br />        } // if ( sn_read == temp )<br />    } // for ( uint8_t i = 0; i&lt;cnt; i++ )<br />} // void access_control ()<br />int main(void)<br />{<br />    // pin 0 portu A jako wyjście sterujące dostępem<br />    DDRA = 0x01;<br />    // włączenie rezystorów pull-up na pozostałych<br />    // pinach<br />    PORTA = 0xFE;<br />    // odczyt wartości pojedynczego elementu tablicy<br />    // w pamięci FLASH (dla elementów o mniejszym rozmiarze<br />    // należy użyć makr pgm_read_xxx() w zależności od typu)<br />    uint64_t test;<br />    memcpy_P( &amp;test, &amp;sn_array&#91;2&#93;, sizeof(test) );<br />    // zmienna w RAM będąca wskaźnikiem do tablicy<br />    // w pamięci FLASH<br />    const uint64_t * test_ptr = sn_array;<br />    // odczyt sn_array&#91;1&#93; poprzez wskaźnik<br />    memcpy_P( &amp;test, test_ptr+1, sizeof(test) );<br />    while (1)<br />    {<br />        access_control( iButton_read_sn(),<br />                        sn_array,<br />                        sizeof(sn_array)/<br />                        sizeof(sn_array&#91;0&#93;) );<br />    } // while (1)<br />} // main ()[/syntax]<br /><br /><ul><strong><em>Definicja przy pomocy kwalifikatora __flash</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br />#include &lt;util/delay.h&gt;<br /><br />// tablica zawierająca 4 elementy 8-bajtowe<br />// czyli 64-bitowe (uint64_t)<br />// w razie potrzeby można oczywiście użyć dowolnego<br />// typu i dowolnej ilości (odpowiednich dla tego typu)<br />// wartości<br />const __flash uint64_t sn_array&#91;&#93; = {<br />    0x5200248A78AC4598,<br />    0x670125AD2F54AA2D,<br />    0x6600055AC52B6879,<br />    0xC000A2A5FD510230<br />};<br /><br />// ”proteza” funkcji odczytującej &quot;serial number&quot;<br />// z pastylki iButton (np. DS1990R)<br />uint64_t iButton_read_sn(void)<br />{<br />    return 0xC000A2A5FD510230;<br />}<br />// prosta funkcja sprawdzająca uprawnienia odczytanego<br />// numeru seryjnego do otwarcia drzwi<br />// parametry:<br />//      sn_read     - odczytany numer seryjny<br />//      allowed_sns - adres tablicy w pamięci programu<br />//                    z uprawnionymi numerami seryjnymi<br />//      cnt         - ilość elementów tablicy<br />void access_control(    uint64_t sn_read,<br />                        const __flash uint64_t allowed_sns&#91;&#93;,<br />                        uint8_t cnt )<br />{<br />    // nie ma konieczności deklarowania zmiennej pomocniczej<br />    // uint64_t temp;<br />    for ( uint8_t i = 0; i&lt;cnt; i++ )<br />    {<br />        // bezpośrednie porównanie odczytanego numeru<br />        // seryjnego z zapisanym w pamięci FLASH,<br />        // tak samo jak w przypadku zmiennej<br />        // umieszczonej w pamięci RAM<br />        if ( sn_read == allowed_sns&#91;i&#93; )<br />        {<br />            PORTA |= 0x01;<br />            //_delay_ms(2000);<br />            PORTA &amp;= 0xFE;<br />            //_delay_ms(1000);<br />            break;<br />        } // if ( sn_read == allowed_sns&#91;i&#93; )<br />    } // for ( uint8_t i = 0; i&lt;cnt; i++ )<br />} // void access_control()<br />// ------------------- funkcja main ---------------------------------<br />int main(void)<br />{<br />    // pin 0 portu A jako wyjście sterujące dostępem<br />    DDRA = 0x01;<br />    // włączenie rezystorów pull-up na pozostałych<br />    // pinach<br />    PORTA = 0xFE;<br />    // odczyt wartości pojedynczego elementu tablicy<br />    // w pamięci FLASH<br />    uint64_t test = sn_array&#91;2&#93;;<br />    // zmienna w RAM będąca wskaźnikiem do tablicy<br />    // w pamięci FLASH<br />    const __flash uint64_t * test_ptr = sn_array;<br />    // odczyt elementu tablicy sn_array&#91;1&#93; poprzez wskaźnik<br />    test = *(test_ptr+1);<br />    while (1)<br />    {<br />        access_control( iButton_read_sn(),<br />                        sn_array,<br />                        sizeof(sn_array)/<br />                        sizeof(sn_array&#91;0&#93;) );<br />    } // while (1)<br />} // main ()[/syntax]<br /><br /><span style="font-size: 110%; line-height: normal"><strong><em>◦ Ciągi znaków (string)</em></strong></span><br /><br />Jest to jeden z typów danych najczęściej zapisywanych w pamięci FLASH, dlatego spróbuję przedstawić nieco bardziej rozbudowany przykład.<br /><br /><ul><strong><em>Definicja przy pomocy atrybutu PROGMEM</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br />#include &lt;string.h&gt;<br /><br />// przykładowy ciąg znaków we FLASH<br />const char pgm_string&#91;&#93; PROGMEM = &quot;Test string&quot;;<br />// wskaźnik w pamięci RAM do ciągu znaków we FLASH<br />const char * pgm_string_ram_ptr = pgm_string;<br />// wskaźnik w pamięci FLASH do ciągu znaków we FLASH<br />const char * const pgm_string_pgm_ptr PROGMEM = pgm_string;<br /><br />// typ wyliczeniowy dla komend<br />typedef enum {<br />    PLAY,<br />    STOP,<br />    PAUSE,<br />    REVERSE,<br />    REWIND,<br />    FAST_FORWARD,<br />    PREVIOUS,<br />    NEXT,<br />    RECORD,<br />    EJECT,<br />    CMD_CNT /* wartość oznaczająca ilość komend */<br />} CMD;<br /><br />// ciągi znaków we FLASH<br />const char _play&#91;&#93;          PROGMEM = &quot;PLAY&quot;;<br />const char _stop&#91;&#93;          PROGMEM = &quot;STOP&quot;;<br />const char _pause&#91;&#93;         PROGMEM = &quot;PAUSE&quot;;<br />const char _reverse&#91;&#93;       PROGMEM = &quot;REVERSE&quot;;<br />const char _rewind&#91;&#93;        PROGMEM = &quot;REWIND&quot;;<br />const char _fast_forward&#91;&#93;  PROGMEM = &quot;FAST_FORWARD&quot;;<br />const char _previous&#91;&#93;      PROGMEM = &quot;PREVIOUS&quot;;<br />const char _next&#91;&#93;          PROGMEM = &quot;NEXT&quot;;<br />const char _record&#91;&#93;        PROGMEM = &quot;RECORD&quot;;<br />const char _eject&#91;&#93;         PROGMEM = &quot;EJECT&quot;;<br />// tablica wskaźników w pamięci FLASH<br />// do ciągów znaków we FLASH<br />// można tak:<br />// const char * commands&#91;&#93; PROGMEM = {<br />//                      cmd0, cmd1, cmd2, cmd3, cmd4,<br />//                      cmd5, cmd6, cmd7, cmd8, cmd9 };<br />// lecz dla pewności przyporządkowania indeksów<br />// poszczególnym komendom lepiej moim zdaniem zrobić tak:<br />const char * const commands&#91;&#93; PROGMEM = {<br />    &#91;PLAY&#93;          = _play,<br />    &#91;STOP&#93;          = _stop,<br />    &#91;PAUSE&#93;         = _pause,<br />    &#91;REVERSE&#93;       = _reverse,<br />    &#91;REWIND&#93;        = _rewind,<br />    &#91;FAST_FORWARD&#93;  = _fast_forward,<br />    &#91;PREVIOUS&#93;      = _previous,<br />    &#91;NEXT&#93;          = _next,<br />    &#91;RECORD&#93;        = _record,<br />    &#91;EJECT&#93;         = _eject<br />};<br /><br />// &quot;proteza&quot; funkcji do odbioru komend poprzez uart<br />// tutaj tylko wczytuje kolejne komendy z tablicy<br />// 'commands&#91;&#93;' we FLASH do zmiennej 'recv_cmd' w pętli<br />// głównej programu<br />void uart_receive_cmd(char * dest)<br />{<br />    static uint8_t i = 0;<br />    strcpy_P(dest, pgm_read_ptr(&amp;commands&#91;i&#93;));<br />    if (++i &gt;= CMD_CNT) i = 0;<br />}<br /><br />// &quot;proteza&quot; funkcji do wyświetlania na lcd ciągu<br />// znaków zapisanych we FLASH,<br />// tutaj wysyła na PORTA ciąg znaków z tablicy<br />// we FLASH wskazywanej przez argument funkcji<br />void lcd_str_P(const char * str)<br />{<br />    char c;<br />    while ( (c=pgm_read_byte(str++)) ) PORTA = c;<br />}<br />// ------------------- funkcja main ---------------------------------<br />int main(void)<br />{<br />    CMD id;             // iterator pętli interpretującej odebraną<br />                        // komendę<br />    char recv_cmd&#91;14&#93;;  // tablica pomocnicza dla odbieranych<br />                        // przez uart komend<br />    // odczyt pojedynczego znaku z ciągu znaków<br />    char c = pgm_read_byte(&amp;pgm_string&#91;2&#93;);<br />    // odczyt pojedynczego znaku za pomocą wskaźnika w RAM<br />    c = pgm_read_byte( pgm_string_ram_ptr+3 );<br />    // odczyt pojedynczego znaku za pomocą wskaźnika we FLASH<br />    c = pgm_read_byte( pgm_read_ptr(&amp;pgm_string_pgm_ptr)+8 );<br />    // kopiowanie całego ciągu znaków FLASH-&gt;RAM<br />    // 'pgm_string' -&gt; recv_cmd<br />    strcpy_P(recv_cmd, pgm_string);<br />    // wyświetlenie ciągu znaków<br />    lcd_str_P(pgm_read_ptr(&amp;commands&#91;REVERSE&#93;));<br />    while (1)<br />    {<br />        // wczytanie komendy do zmiennej 'recv_cmd'<br />        uart_receive_cmd(recv_cmd);<br />        // porównanie komendy ze zmiennej 'recv_cmd'<br />        // z kolejnymi komendami w tablicy 'commands&#91;&#93;'<br />        for (id = 0; id &lt; CMD_CNT; id++)<br />        {<br />            if ( strcmp_P( recv_cmd,<br />                           pgm_read_ptr(&amp;commands&#91;id&#93;)) == 0 )<br />            {<br />                switch (id)<br />                {<br />                    case PLAY:<br />                        PORTA = 0x01;<br />                        break;<br />                    case STOP:<br />                        PORTA = 0x02;<br />                        break;<br />                    case PAUSE:<br />                        PORTA = 0x04;<br />                        break;<br />                    case REVERSE:<br />                        PORTA = 0x08;<br />                        break;<br />                    case REWIND:<br />                        PORTA = 0x10;<br />                        break;<br />                    case FAST_FORWARD:<br />                        PORTA = 0x20;<br />                        break;<br />                    case PREVIOUS:<br />                        PORTA = 0x21;<br />                        break;<br />                    case NEXT:<br />                        PORTA = 0x22;<br />                        break;<br />                    case RECORD:<br />                        PORTA = 0x24;<br />                        break;<br />                    case EJECT:<br />                        PORTA = 0x28;<br />                        break;<br />                    default:<br />                        break;<br />                } // switch (id)<br />                break;<br />            } // if ( strcmp_P() == 0 )<br />        } // for (id = 0; id &lt; CMD_CNT; id++)<br />    } // while (1)<br />} // main()[/syntax]<br /><br /><ul><strong><em>Definicja przy pomocy kwalifikatora __flash</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br />#include &lt;string.h&gt;<br /><br />// makro umieszczające ciąg ‘s’ w pamięci programu<br />// i zwracające wskaźnik do tego ciągu<br />#define PGMSTR(s) ((const __flash char&#91;&#93;) { s })<br /><br />// przykładowy ciąg znaków we FLASH<br />const __flash char pgm_string&#91;&#93; = &quot;Test string&quot;;<br />// wskaźnik w pamięci RAM do ciągu znaków we FLASH<br />const __flash char * pgm_string_ram_ptr = pgm_string;<br />// wskaźnik w pamięci FLASH do ciągu znaków we FLASH<br />const __flash char * const __flash pgm_string_pgm_ptr = pgm_string;<br /><br />// typ wyliczeniowy dla komend<br />typedef enum {<br />    PLAY,<br />    STOP,<br />    PAUSE,<br />    REVERSE,<br />    REWIND,<br />    FAST_FORWARD,<br />    PREVIOUS,<br />    NEXT,<br />    RECORD,<br />    EJECT,<br />    CMD_CNT /* wartość oznaczająca ilość komend */<br />} CMD;<br /><br />// ciągi znaków we flash; można zrobić tak jak w przypadku<br />// użycia PROGMEM: najpierw ciągi znaków we FLASH<br />// const __flash char _play&#91;&#93;          = &quot;PLAY&quot;;<br />// const __flash char _stop&#91;&#93;          = &quot;STOP&quot;;<br />// const __flash char _pause&#91;&#93;         = &quot;PAUSE&quot;;<br />// const __flash char _reverse&#91;&#93;       = &quot;REVERSE&quot;;<br />// const __flash char _rewind&#91;&#93;        = &quot;REWIND&quot;;<br />// const __flash char _fast_forward&#91;&#93;  = &quot;FAST_FORWARD&quot;;<br />// const __flash char _previous&#91;&#93;      = &quot;PREVIOUS&quot;;<br />// const __flash char _next&#91;&#93;          = &quot;NEXT&quot;;<br />// const __flash char _record&#91;&#93;        = &quot;RECORD&quot;;<br />// const __flash char _eject&#91;&#93;         = &quot;EJECT&quot;;<br /><br />// a następnie wskaźniki we FLASH do tablic we FLASH<br />// const __flash char * const __flash commands&#91;&#93; = {<br />//     &#91;PLAY&#93;          = _play,<br />//     &#91;STOP&#93;          = _stop,<br />//     &#91;PAUSE&#93;         = _pause,<br />//     &#91;REVERSE&#93;       = _reverse,<br />//     &#91;REWIND&#93;        = _rewind,<br />//     &#91;FAST_FORWARD&#93;  = _fast_forward,<br />//     &#91;PREVIOUS&#93;      = _previous,<br />//     &#91;NEXT&#93;          = _next,<br />//     &#91;RECORD&#93;        = _record,<br />//     &#91;EJECT&#93;         = _eject<br />// };<br /><br />// można też prościej metodą, która nie wymaga deklarowania<br />// tablic '_play&#91;&#93;', '_stop&#91;&#93;', '_pause&#91;&#93;' itd.<br />// nie mamy wtedy wprawdzie bezpośredniego dostępu<br />// do tych ciągów, tylko poprzez tablicę wskaźników,<br />// jednak zwykle jest tak (jak w tym przypadku),<br />// że bezpośredni dostęp nie jest nam potrzebny<br />const __flash char * const __flash commands&#91;&#93; = {<br />    &#91;PLAY&#93;          = PGMSTR(&quot;PLAY&quot;),<br />    &#91;STOP&#93;          = PGMSTR(&quot;STOP&quot;),<br />    &#91;PAUSE&#93;         = PGMSTR(&quot;PAUSE&quot;),<br />    &#91;REVERSE&#93;       = PGMSTR(&quot;REVERSE&quot;),<br />    &#91;REWIND&#93;        = PGMSTR(&quot;REWIND&quot;),<br />    &#91;FAST_FORWARD&#93;  = PGMSTR(&quot;FAST_FORWARD&quot;),<br />    &#91;PREVIOUS&#93;      = PGMSTR(&quot;PREVIOUS&quot;),<br />    &#91;NEXT&#93;          = PGMSTR(&quot;NEXT&quot;),<br />    &#91;RECORD&#93;        = PGMSTR(&quot;RECORD&quot;),<br />    &#91;EJECT&#93;         = PGMSTR(&quot;EJECT&quot;)<br />};<br />// &quot;proteza&quot; funkcji do odbioru komend poprzez uart<br />// tutaj tylko wczytuje kolejne komendy z tablicy<br />// 'commands&#91;&#93;' we FLASH do zmiennej 'recv_cmd' w pętli<br />// głównej programu<br />void uart_receive_cmd(char * dest)<br />{<br />    static uint8_t i = 0;<br />    strcpy_P( dest, commands&#91;i&#93; );<br />    if (++i &gt;= CMD_CNT) i = 0;<br />}<br /><br />// &quot;proteza&quot; funkcji do wyświetlania na lcd ciągu<br />// znaków zapisanych we FLASH,<br />// tutaj wysyła na PORTA ciąg znaków z tablicy<br />// we FLASH wskazywanej przez argument funkcji<br />void lcd_str_P(const __flash char * str)<br />{<br />    char c;<br />    while ( (c=*str++) ) PORTA = c;<br />}<br /><br /><br />// ------------------- funkcja main ---------------------------------<br />int main(void)<br />{<br />    CMD id;             // iterator pętli interpretującej odebraną<br />                        // komendę<br />    char recv_cmd&#91;14&#93;;  // tablica pomocnicza dla odbieranych<br />                        // przez uart komend<br />    // odczyt pojedynczego znaku z ciągu znaków<br />    char c = pgm_string&#91;2&#93;;<br />    // odczyt pojedynczego znaku za pomocą wskaźnika w RAM<br />    c = *(pgm_string_ram_ptr+3);<br />    // odczyt pojedynczego znaku za pomocą wskaźnika we FLASH<br />    c = *(pgm_string_pgm_ptr+8);<br />    // kopiowanie całego ciągu znaków FLASH-&gt;RAM<br />    // 'pgm_string' -&gt; 'recv_cmd'<br />    strcpy_P(recv_cmd, pgm_string);<br />    // wyświetlenie ciągu znaków<br />    lcd_str_P(commands&#91;REVERSE&#93;);<br />    while (1)<br />    {<br />        // wczytanie komendy do zmiennej 'recv_cmd'<br />        uart_receive_cmd(recv_cmd);<br />        // porównanie komendy ze zmiennej 'recv_cmd'<br />        // z kolejnymi komendami w tablicy 'commands&#91;&#93;'<br />        for (id = 0; id &lt; CMD_CNT; id++)<br />        {<br />            if ( strcmp_P( recv_cmd, commands&#91;id&#93; ) == 0 )<br />            {<br />                switch (id)<br />                {<br />                    case PLAY:<br />                        PORTA = 0x01;<br />                        break;<br />                    case STOP:<br />                        PORTA = 0x02;<br />                        break;<br />                    case PAUSE:<br />                        PORTA = 0x04;<br />                        break;<br />                    case REVERSE:<br />                        PORTA = 0x08;<br />                        break;<br />                    case REWIND:<br />                        PORTA = 0x10;<br />                        break;<br />                    case FAST_FORWARD:<br />                        PORTA = 0x20;<br />                        break;<br />                    case PREVIOUS:<br />                        PORTA = 0x21;<br />                        break;<br />                    case NEXT:<br />                        PORTA = 0x22;<br />                        break;<br />                    case RECORD:<br />                        PORTA = 0x24;<br />                        break;<br />                    case EJECT:<br />                        PORTA = 0x28;<br />                        break;<br />                    default:<br />                        break;<br />                } // switch (id)<br />                break;<br />            } // if ( strcmp_P() == 0 )<br />        } // for (id = 0; id &lt; CMD_CNT; id++)<br />    } // while (1)<br />} // main()[/syntax]<br /><br />Myślę, że kolejne porównanie nie wymaga komentarza:<br /><br />[syntax=c]// odczyt pojedynczego znaku z ciągu znaków<br />    char c = pgm_read_byte(&amp;pgm_string&#91;2&#93;);                     /* PROGMEM */<br />    char c = pgm_string&#91;2&#93;;                                     /* __flash */<br />    // odczyt pojedynczego znaku za pomocą wskaźnika w RAM<br />    c = pgm_read_byte( pgm_string_ram_ptr+3 );                  /* PROGMEM */<br />    c = *(pgm_string_ram_ptr+3);                                /* __flash */<br />    // odczyt pojedynczego znaku za pomocą wskaźnika we FLASH<br />    c = pgm_read_byte( pgm_read_ptr(&amp;pgm_string_pgm_ptr)+8 );   /* PROGMEM */<br />    c = *(pgm_string_pgm_ptr+8);                                /* __flash */<br />    // wyświetlenie ciągu znaków<br />    lcd_str_P(pgm_read_ptr(&amp;commands&#91;REVERSE&#93;));                /* PROGMEM */<br />    lcd_str_P(commands&#91;REVERSE&#93;);                               /* __flash */[/syntax]<br /><br /><span style="font-size: 110%; line-height: normal"><strong><em>◦ Tablica struktur</em></strong></span><br /><br />Struktury to również typ danych dosyć często zapisywany w pamięci FLASH (może być używany  przykładowo do tworzenia elementów menu). Oczywiście poniższe przykłady to tylko pokazanie sposobu definiowania struktur w pamięci programu oraz ich późniejszego odczytu, a nie przykłady budowania i obsługi menu. Przedstawione tam funkcje nie robią niestety nic pożytecznego, za to mają pokazać, w jaki sposób przekazać dane ze struktury do funkcji oraz jak ich wewnątrz funkcji użyć.<br /><br /><ul><strong><em>Definicja przy pomocy atrybutu PROGMEM</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br /><br />#define LABELS_COUNT sizeof(labels)/sizeof(labels&#91;0&#93;)<br />// struktura opisująca kolor<br />typedef struct {<br />    uint8_t r;<br />    uint8_t g;<br />    uint8_t b;<br />} Color;<br />// struktura opisująca etykietę<br />typedef struct {<br />    uint16_t pos_x;<br />    uint16_t pos_y;<br />    uint8_t width;<br />    uint8_t height;<br />    const char * text_ptr;<br />    Color bg_color;<br />    Color fg_color;<br />} Label;<br />// w przypadku PROGMEM należy najpierw zapisać ciągi znaków<br />// w pamięci FLASH, aby podczas definiowania struktury można było<br />// umieścić wskaźniki do nich<br />const char label_0&#91;&#93; PROGMEM = &quot;Red&quot;;<br />const char label_1&#91;&#93; PROGMEM = &quot;Green&quot;;<br />const char label_2&#91;&#93; PROGMEM = &quot;Blue&quot;;<br /><br />// tablica struktur (etykiet) w pamięci FLASH<br />const Label labels&#91;&#93; PROGMEM = {<br />    {<br />        10,20,50,30,label_0,<br />        { 150,0,0 },<br />        { 255,255,255 }<br />    },<br />    {<br />        10,60,50,30,label_1,<br />        { 0,150,0 },<br />        { 255,255,255 }<br />    },<br />    {<br />        10,100,50,30,label_2,<br />        { 0,0,150 },<br />        { 255,255,255 }<br />    }<br />};<br /><br />// funkcje obsługujące struktury we FLASH<br /><br />void draw_rectangle_P(uint16_t x, uint16_t y,<br />                      uint8_t w, uint8_t h,<br />                      Color c) {<br />    // to tylko przykładowe operacje mające na celu np.<br />    // łatwiejsze zaobserwowanie wartości podczas<br />    // debugowania/symulacji<br />    TCNT1 = x;<br />    TCNT1 = y;<br />    PORTA = w;<br />    PORTA = h;<br />    PORTA = c.r;<br />    PORTA = c.g;<br />    PORTA = c.b;<br />}<br /><br />// funkcja (niby)wyświetlająca tekst<br />// w rzeczywistości musiałaby również zawierać koordynaty,<br />// które tutaj pominąłem dla uproszczenia -<br />// - przekazanie koordynatów było pokazane we funkcji<br />// draw_rectangle()<br />void draw_text_P(const char * t, Color c) {<br />    char tmp;<br />    // odczyt do końca ciągu znaków (czyli do znaku ‘\0’)<br />    while ( (tmp=pgm_read_byte(t++)) ) PORTB = tmp;<br />    PORTB = c.r;<br />    PORTB = c.g;<br />    PORTB = c.b;<br />}<br />void draw_label_P(const Label * l_ptr) {<br />    // Color to struktura składająca się z 3 bajtów, więc nie da się<br />    // odczytać jej żadnym z makr z grupy pgm_read_xxx(), należy użyć<br />    // zmiennej pomocniczej oraz funkcji memcpy_P()<br />    Color tmp_color;<br />    memcpy_P(&amp;tmp_color, &amp;l_ptr-&gt;bg_color, sizeof(tmp_color));<br />    // pozostałe elementy struktury możemy odczytać za pomocą makr<br />    // pgm_read_xxx() przekazując bezpośrednio do funkcji<br />    // draw_rectangle()<br />    draw_rectangle_P(pgm_read_word(&amp;l_ptr-&gt;pos_x),<br />                     pgm_read_word(&amp;l_ptr-&gt;pos_y),<br />                     pgm_read_byte(&amp;l_ptr-&gt;width),<br />                     pgm_read_byte(&amp;l_ptr-&gt;height),<br />                     tmp_color);<br />    // ponownie odczyt struktury Color wymaga użycia funkcji mamcpy_P()<br />    memcpy_P(&amp;tmp_color, &amp;l_ptr-&gt;fg_color, sizeof(tmp_color));<br />    // do funkcji draw_text_P() musimy przekazać wskaźnik do ciągu znaków<br />    // nie możemy tego zrobić bezpośrednio za pomocą operatora &amp;<br />    // ponieważ jest on zapisany wewnątrz struktury Label w pamięci FLASH<br />    // należy więc użyć makra pgm_read_ptr()<br />    draw_text_P(pgm_read_ptr(&amp;l_ptr-&gt;text_ptr), tmp_color);<br />}<br /><br />// funkcja (niby:) wyświetlająca menu<br />void draw_menu_P() {<br />    uint8_t i;<br />    for (i=0; i&lt;LABELS_COUNT; i++)<br />    {<br />        draw_label_P(&amp;labels&#91;i&#93;);<br />    }<br />}<br /><br /><br />// ------------------- funkcja main ---------------------------------<br />int main(void)<br />{<br />    uint8_t ui8;<br />    uint16_t ui16;<br />    const Label *l_ptr = &amp;labels&#91;2&#93;;<br />    // odczyt wartości 16-bitowej<br />    ui16 = pgm_read_word(&amp;labels&#91;1&#93;.pos_x);<br />    <br />    // odczyt wartości 8-bitowej<br />    ui8 = pgm_read_byte(&amp;labels&#91;1&#93;.bg_color.g);<br />    <br />    // odczyt elementu struktury poprzez wskaźnik<br />    // do struktury (będącej elementem tablicy)<br />    ui16 = pgm_read_word(&amp;l_ptr-&gt;pos_y);<br />    <br />    while (1)<br />    {<br />        draw_menu_P();<br />    } // while(1)<br />} // main()[/syntax]<br /><br /><ul><strong><em>Definicja przy pomocy kwalifikatora __flash</em></strong></ul><br />[syntax=c]#include &lt;avr/io.h&gt;<br />#include &lt;avr/pgmspace.h&gt;<br /><br />#define PGMSTR(s) ((const __flash char&#91;&#93;) { s })<br />#define LABELS_COUNT sizeof(labels)/sizeof(labels&#91;0&#93;)<br /><br />// struktura opisująca kolor<br />typedef struct {<br />    uint8_t r;<br />    uint8_t g;<br />    uint8_t b;<br />} Color;<br />// struktura opisująca etykietę<br />typedef struct {<br />    uint16_t pos_x;<br />    uint16_t pos_y;<br />    uint8_t width;<br />    uint8_t height;<br />    // tutaj istotne jest przekazanie kompilatorowi<br />    // informacji, że poniższy wskaźnik będzie<br />    // odnosił się do pamięci FLASH<br />    const __flash char * text_ptr;<br />    Color bg_color;<br />    Color fg_color;<br />} Label;<br /><br />// w przypadku __flash ciągi znaków możemy definiować<br />// bezpośrednio wewnątrz struktury za pomocą makra PGMSTR()<br />// zostaną utworzone anonimowe tablice znaków w pamięci<br />// FLASH, a wskaźniki do nich zostaną umieszczone w strukturach,<br />// czyli również w pamięci FLASH<br />// tablica struktur (etykiet) w pamięci FLASH<br />const __flash Label labels&#91;&#93; = {<br />    {<br />        10,20,50,30,<br />        PGMSTR(&quot;Red&quot;),<br />        { 150,0,0 },<br />        { 255,255,255 }<br />    },<br />    {<br />        11,61,51,31,<br />        PGMSTR(&quot;Green&quot;),<br />        { 0,150,0 },<br />        { 255,255,255 }<br />    },<br />    {<br />        12,102,52,32,<br />        PGMSTR(&quot;Blue&quot;),<br />        { 0,0,150 },<br />        { 255,255,255 }<br />    }<br />};<br /><br />// funkcje obsługujące struktury we FLASH<br />void draw_rectangle_P(uint16_t x, uint16_t y,<br />                      uint8_t w, uint8_t h,<br />                      Color c) {<br />    // to tylko przykładowe operacje mające na celu np.<br />    // łatwiejsze zaobserwowanie wartości podczas<br />    // debugowania/symulacji<br />    TCNT1 = x;<br />    TCNT1 = y;<br />    PORTA = w;<br />    PORTA = h;<br />    PORTA = c.r;<br />    PORTA = c.g;<br />    PORTA = c.b;<br />}<br /><br />// funkcja (niby:) wyświetlająca tekst<br />// w rzeczywistości musiałaby również zawierać koordynaty,<br />// które tutaj pominąłem dla uproszczenia -<br />// - przekazanie koordynatów było pokazane we funkcji<br />// draw_rectangle()<br />void draw_text_P(const __flash char * t, Color c) {<br />    char tmp;<br />    while ( (tmp = *t++) ) PORTB = tmp;<br />    PORTB = c.r;<br />    PORTB = c.g;<br />    PORTB = c.b;<br />}<br /><br />void draw_label_P(const __flash Label * l_ptr) {<br />    // w przypadku użycia __flash możemy elementy struktury<br />    // przekazać bezpośrednio do funkcji, bez konieczności<br />    // użycia funkcji memcpy_P() czy też makr pgm_read_xxx()<br />    draw_rectangle_P(l_ptr-&gt;pos_x, l_ptr-&gt;pos_y,<br />                     l_ptr-&gt;width, l_ptr-&gt;height,<br />                     l_ptr-&gt;bg_color);<br />    // odczyt wskaźnika zapisanego w strukturze w pamięci FLASH<br />    // możemy wykonać tak, jakby był w pamięci RAM przekazując go<br />    // bezpośrednio do funkcji<br />    draw_text_P(l_ptr-&gt;text_ptr, l_ptr-&gt;fg_color);<br />}<br />// (niby)funkcja wyświetlająca menu<br />void draw_menu_P() {<br />    uint8_t i;<br />    for (i=0; i&lt;LABELS_COUNT; i++)<br />    {<br />        draw_label_P(&amp;labels&#91;i&#93;);<br />    }<br />}<br /><br />// ------------------- funkcja main ---------------------------------<br />int main(void)<br />{<br />    uint8_t ui8;<br />    uint16_t ui16;<br />    const __flash Label *l_ptr = &amp;labels&#91;2&#93;;<br />    <br />    // odczyt wartości 16-bitowej<br />    ui16 = labels&#91;1&#93;.pos_x;<br />    <br />    // odczyt wartości 8-bitowej<br />    ui8 = labels&#91;1&#93;.bg_color.g;<br />    <br />    // odczyt elementu struktury poprzez wskaźnik<br />    // do struktury (będącej elementem tablicy)<br />    ui16 = l_ptr-&gt;pos_y;<br />    <br />    <br />    while (1)<br />    {<br />        draw_menu_P();<br />    } // while(1)<br />} // main()[/syntax]<br /><br />Jeszcze jedno porównanie:<br /><br />[syntax=c]// –-- funkcja draw_label() w przypadku użycia PROGMEM ---------------------<br />void draw_label_P(const Label * l_ptr) {                       /* PROGMEM */<br />    Color tmp_color;                                           /* PROGMEM */<br />    memcpy_P(&amp;tmp_color, &amp;l_ptr-&gt;bg_color, sizeof(tmp_color)); /* PROGMEM */<br />    draw_rectangle_P(pgm_read_word(&amp;l_ptr-&gt;pos_x),             /* PROGMEM */<br />                     pgm_read_word(&amp;l_ptr-&gt;pos_y),             /* PROGMEM */<br />                     pgm_read_byte(&amp;l_ptr-&gt;width),             /* PROGMEM */<br />                     pgm_read_byte(&amp;l_ptr-&gt;height),            /* PROGMEM */<br />                     tmp_color);                               /* PROGMEM */<br />    memcpy_P(&amp;tmp_color, &amp;l_ptr-&gt;fg_color, sizeof(tmp_color)); /* PROGMEM */<br />    draw_text_P(pgm_read_ptr(&amp;l_ptr-&gt;text_ptr), tmp_color);    /* PROGMEM */<br />}<br /><br />// –-- ta sama funkcja w przypadku użycia __flash --------------------------<br />void draw_label_P(const __flash Label * label_ptr) {           /* __flash */<br />    draw_rectangle_P(l_ptr-&gt;pos_x, l_ptr-&gt;pos_y,               /* __flash */<br />                   l_ptr-&gt;width, l_ptr-&gt;height,                /* __flash */<br />                   l_ptr-&gt;bg_color);                           /* __flash */<br />    draw_text_P(l_ptr-&gt;text_ptr, l_ptr-&gt;fg_color);             /* __flash */<br />}[/syntax]<br /><br /><span style="font-size: 110%; line-height: normal"><strong><em>◦ Dane w pierwszym segmencie 64KiB – podsumowanie</em></strong></span><br /><br />Ze względów oczywistych niemożliwe jest pokazanie przykładów wszystkich możliwych kombinacji. Omówienie różnych typów danych oraz sposobów ich użycia wykracza poza ramy tego artykułu. Moim celem było jedynie pokazanie specyfiki umieszczania tych danych w pamięci programu przy użyciu dwóch możliwych metod i pokazanie różnic między tymi metodami. Myślę, że każdy kto potrafi już operować danymi w pamięci RAM (tzn. definiować, uzyskiwać do nich dostęp bezpośredni lub poprzez wskaźniki, przekazywać jako argumenty do funkcji) w łatwy sposób, na podstawie powyższych przykładów, powinien stworzyć własny kod spełniający jego oczekiwania.</ul></ul><p>Statystyki: Napisane przez <a href="https://forum.atnel.pl/memberlist.php?mode=viewprofile&amp;u=14165">andrews</a> — 9 wrz 2017, o 13:33</p><hr />
]]></content>
</entry>
</feed>