AVR C++ Lib ettepanekute loend

Põhimõtted

  1. Ei satu vastuollu standartse AVR teegiga: võimalik kasutada mõlemaid samaaegselt.
  2. Vaid AVR C++ Lib'ga programmi kirjutades peab olema võimalik kirjutada koodi, mis on vähemalt sama efektiivne, kui kasutaks standartset AVR teeki. Samas võib teeki lisada paralleelseid võimalusi ühe ja sama asja tegemiseks, kus üks on efektiivne viis ja teine on programmeerimise seisukohalt mugav (näiteks: tavaline INTERRUPT_HANDLER ja MultiDelegate).
  3. Hoida kompileerimisaega minimaalsena

Puudulik objektide polümorfism

Võtame näiteks IO pinnid. Praegu on selline olukord, et klassideks on InputPin0, InputPin1 jne. Kui nüüd defineerida muutuja tüüpi „InputPin2“, siis kuidas anda seda muutujat edasi funktsioonile või externalina muus failis kasutada? Ainus viis on korrata tema tüüpi (koos indeksi numbriga). Aga kui nüüd indeks muudetakse 2 pealt 3 peale? Tuleb igalpoolt see number asendada?
Kas ei peaks võtma kasutusele baasklasse, abstraktsioone, liideseid? Et antud näite puhul oleks siis „InputPin2“ aluseks klass „InputPin“. Sama olukord piirab ka kõigi teiste objektide kasutamist. Tahaks teha X-Modem sidepidamise klassi üle USART-i millele saab määrata USART-i mida kasutada, kuid kuna USART0, USART1 jne. ei ole ühtset tüüpi, siis ei saagi seda paindlikuna teha.
Kindlasti aga tuleks enne objektide polümorfismi kasutuselevõttu uurida kompilaatori käitumist ja tulemuse effektiivsust.

  • Lauri - Kuna abstraktseid staatilisi liikmeid kompilaator ei luba, siis peaks kõik klassi kuuluvad funktsioonid muutma liikmefunktsioonideks. Sellisel juhul kood muutuks suure tõenäosusega ebaefektiivsemaks, kuna kõigi funktsioonide väljakutsumisel antaks kaasa parameeter this, mida tarvis ei lähe. Tegelikult praegusel hetkel probleemid puuduvad, kuna funktsiooni on võimalik defineerida ja välja kutsuda järgnevalt:
template <class InputPinX> bool IsNotSet(IntputPinX inputPin)
{
    return !inputPin.IsSet();
 
} // IsNotSet
 
int main()
{
    InputPin1<PortD> input;
 
    input.InitInput();
 
    while (IsNotSet(input) )
        ;
 
    return 0;
 
} // main
  • Kui eeltoodud kood vajadusi ei täida, siis võib teha ka nii:
  1. Defineerida interface vajaminevate funktsioonidega:
    class IUSART
    {
    public:
        bool Read(uint8_t &data) = 0;
        void Write(uint8_t data) = 0;
    };
  2. Defineerida template klass, mis vastab defineeritud interface'le.
    template <class USARTX> class AnyUSART : public IUSART
    {
    public:
        bool Read(uint8_t &data) { return USARTX::Read(data); }
        void Write(uint8_t data) { USARTX::Write(data); }
    };
  3. Ja ongi valmis:
    uint8_t ReadAndWrite(IUSART *usart, uint8_t data)
    {
        usart.Write(data);
        usart.Read(data);
     
        return data;
     
    } // ReadAndWrite
     
    int main()
    {
        AnyUSART<USART0> u1;
        AnyUSART<USART1> u2;
     
        IUSART *u = &u1;
     
        ReadAndWrite(u, 5);
     
        u = &u2;
     
        ReadAndWrite(u, 8);
     
     
        return 0;
     
    } // main

Nimede reeglid

Namespace sisaldab täispikka nime liidese kohta, mida ta sisaldab. Struktuur, kui see sisaldub kohe namespace sees, käib sama reegli järgi, mis namespace ise. Põhjus selles, et tavaliselt pannakse faili algusesse rida „using namespace“ ja võib juhtuda, et namespace sisu võib kattuda ning mis tõenäolisem - kaob selgus. Struktuurisiseste struktuuride, funktsioonide ja muutujate nimedes aga ei sisaldu liidest iseloomustav sõna (kui just tungivat vajadust pole).
Nimedes ei ole soovitav kasutada lühendid. Eranditeks on näiteks EEPROM ja USART.
Muutujanimed algavad väikese tähega, kõik teised nimed suure tähega. Makronimed on alati läbivalt suurtähtedega; sõnad on eraldatud alakriipsudega. Sisemised makrod (makrod, mida lõppkasutajal ei ole tarvidust kasutada) algavad ja lõppevad kahe alakriipsuga.
Namespace'i, enum'de, enum'i liikmete, struktuuride ja funktsioonide nimedes on kõik sõnad suure algustähega. Ülejäänud tähed on väikesed. Sõnad on omavahel kokkukirjutatud (ilma alakriipsuta). Primitiivsete tüüpide nimed on läbivalt väiketähtedega ja sufiksiga „x_t“, kus x tähistab tüübi suurust bittides (näiteks: int8_t, reg16_t). Kõik sisemised funktsioonid(funktsioonid, mida lõppkasutajal ei ole tarvidust kasutada) on viidud Internal namespace'i.

Kompileerimisaja lühendamine

Kompileerimisaeg on jätkuvalt masendavalt pikk ja aina pikeneb. Võimalused selle lühendamised oleksid:

  1. Registeri struktuur defineerida ainult siis, kui selle kasutamise tõenäosus on suur: konkreetset registrit puudutav DECLARE_XBIT_REGISTER tõsta ringi teema järgi Timer_mxxx.h-sse, Interrupt_mxxx.h-sse või mõnesse muusse päisefaili. Ehk siis vähendada IO.h mahtu.
  2. Vähendada sõltuvust standartsest AVR teegist: <avr/io.h>'d ei lisata.
  3. Windows API eeskujul võtta kasutusele taoline makro nagu seda on WIN32_LEAN_AND_MEAN. Antud kontekstis võiksid olla näiteks makrod EXCLUDE_USART1, INCLUDE_ONLY_TIMER_COUNTER1, EXCLUDE_PORTS. Vaikimisi lisatakse kõik; kui on defineeritud mõni võtmemakrodest, siis lisatakse ainult see osa, mis on võtmemakrodega määratud.
    • Kuna INCLUDE_ONLY_XXXXXX programmeerimine on komplitseeritud, siis kasutusele võiks võtta ainult EXCLUDE_XXXXXX.
  4. Alternatiiviks eelmisele punktile on päisefailide arvu suurendamine.
  5. Uurida, kas AVR-GCC toetab Precompiled Header't.

Väliste katkestuste fail

Praegusel kujul olev väliste katkestuste fail „Interrupt_xxx“ nimetada ümber „ExternalInterrupt“-iks.

  • Lauri - ühest küljest failinimi venib liiga pikaks, teisest küljest on vaja eraldada Interrupt.h kaheks erinevaks failiks. Niisiis tuleks siiski luua Interrupt.h kõrvale ExternalInterrupt.h.

Project Wizard

Võiks olla väike programmike “Project Wizard“, mis küsib projekti nime, asukohta, kuhu see luuakse, seadet, taktsagedust ja muud taolist ning koostaks selle info põhjal makefile'i, Programmers Notepad'i projektifaili ja sinna juurde kuuluvad lähtefailid, mis sisaldavad põhja kommentaaridega .

Type bool

Kompilaator annab vea IO.h's „redeclaration of C++ built-in type 'bool'“. Kas küsimus on vaid kompilaatori valikutes või on tegemist kompilaatori täiendusega? Sellise vea tekkimise peaks välistama #if/#endif abiga. Arvan, et tegemist on kompilaatori neljanda versiooni täiendusega.

Inline funktsioonid

Kõigile inline funktsioonidele lisada atribuut always_inline. Praegusel juhul kompilaatori poolt genereeritud masinakood on efektiivne vaid juhul, kui optimeerimise valik on määratud „2“. Manual atribuutide kohta asub siin

  • Mikk - teeme ära. IO faili lihtsalt rida:
#define inline __attribute__((always_inline))
  • Lauri - päris nii lihtne see ei ole:
#define __ALWAYS_INLINE__ __attribute__((always_inline))
 
struct SomeStruct
{
    // Kohe siia struktuuri sisse koodi kirjutada ei saa
    inline void SomeFunction() __ALWAYS_INLINE__;
};
void SomeStruct::SomeFunction()
{
    /* Siia vajalik kood */
}

New ja delete operaator

Võtta STDC++ teegist kasutusele new ja delete operaatorid. AVR-GCC jaoks vajalik kood on saadaval siin. Lauri - niikaua, kuni ei ole loodud new ja delete operaatoritele täisstandartset toetust, lisame oma teegile selle programmijupi.

Väliste katkestuste definitsioon

Väliste katkestuste struktuuride loomise võiks makro peale viia kuna kood on neil kõigil suhteliselt sama.

  • Lauri - ei ole hea mõte, sest dokumentatsiooni generaator ei tunnista makrosid vajalikul määral. Pealegi makrode kasutamine taolistel juhtudel viivad kergesti lohakusvigadeni: suurendab tõenäosust, et imeväikesed erinevused kontrolleritel võivad jääda märkamata.
  • Mikk - Kui igal kontrolleril eraldi kood välja kirjutada, siis hilisemal millegi muutmisel tuleb väga palju tööd teha. Pigem juhtub viga kuhugile pikka ja pealtnäha monotoonsesse koodi, kui jääb eripära märkamata. Eripärade jaoks võib kas olemasolevasse makrosse lisaattribuudid lisada või uue teha. Vaevalt, et Atmeli mehed neid erandeid massiliselt produtseerivad. Dokumentatsiooniga peab natuke mõtlema, aga võibolla leiab mõne eraldiseisva c precompileri millest saab koodi läbi lasta ja siis dokumentatsiooni genereerida.

#include faili nimed

Teegisisesed #include direktiivid anda kohaliku faili nimena, ehk jutumärkidega mitte nurksulgudega. Lauri - OK.

Enum või Typedef enum

Kumb variant on parem, kas:

    typedef enum Reference
    {
        AREF        = 0x00,
        AVCC        = 0x40,
        Internal    = 0xC0
 
    } Reference;

, mis tekitab dokumentatsiooni ebavajalikku informatsiooni ja kompilaatorile liigset tööd, või:

    enum Reference
    {
        AREF        = 0x00,
        AVCC        = 0x40,
        Internal    = 0xC0
 
    }; // Reference

, mis vanemate kompilaatorite puhul nõudis muutuja defineerimiseks täpsemat tüübi määratlemist (nt: enum Reference variableFromReferenceType;) ja ei ole minuteada seepärast üldtunnustatud ülesmärkimise viis.
Mikk - teine variant on parem.

Declare/define ?

Kas makro DECLARE_8BIT_REGISTER(registerName) peaks olema nimetatud hoopiski DEFINE_8BIT_REGISTER(registerName) ?

  • Mikk - pooldan kehtivat varianti.

Bitimaskid

Kas iga registristruktuuri juurde lisada bitimaskide loend? Kirjaviis muutuks pikemaks ja kompileerimisaeg pikeneks, aga suur võit oleks turvalisuses. Praegusel juhul taolise lolluse peale kompilaator viga ei anna:

SetBits<_TCCR1A>(_PB6); // PB6 ei ole TCCR1A registri bitt
PortC::SetAsOutput(PC2 | _PC5 | _PC7); // PC2 asemel peaks kirjas olema _PC2

Sellisel juhul aga kompilaator keelduks tööd edukalt lõpetamast:

SetBits<_TCCR1A>(_TCCR1A::_PB6); // VIGA
PortC::SetAsOutput(
          PortC::PC2 /* VIGA */
        | PortC::_PC5 /* OK */
        | DDRC::_PC7 /* OK */);
  • Lauri - bitimaskide viimine enum'desse toob kaasa tohutu hulga lisatööd, kui just ei soovi väga ebaefektiivset lahendust (koledat pealekauba). Seega registrite bitimaskid jäävad makrodega defineerituks.



<-- Tagasi projekti lehele