AVR g++ - Wysokopoziomowe odwołanie do pinów

0

Witam,

Ostatnio musiałem zrobić coś na Avr, ale po kilkumiesięcznych zmaganiach z c# zapisy typu `PORTA |= (1<<x);` nie były mi przyjemne dla oka.

Pomyślałem - jak dużo tego i parametryzować trzeba to templaty. G++ nie chciał przyjąć jako parametru szablonu adresów portów. Dałem spokój z nimi po chwili szukania na necie.

Potem pomyślałem - dużo tego, w zależności od procka są inne porty itp. Napiszę szybko generator kodu. Wyszło takie coś http://www.github.com/pixellos/CodeGenerator.

Niby ładnie działa, wywalone do innego pliku nagłówkowego.
Tylko czy takie rozwiązanie nie jest mało eleganckie?

Przykładowy output dla A i 2 pin

class PortA{
        public:
	    void static Set(uint8_t uCharValue)
       {
            PORTA |= uCharValue;
       }

        void static Clear(uint8_t uCharValue)
       {
            PORTA &= ~(uCharValue);
       }

        void static Toggle(uint8_t uCharValue)
       {
            PORTA ^= uCharValue;
       }

        uint8_t static Check()
       {
            return PINA;
       }

        void static AsOutput(uint8_t uCharValue)
       {
            DDRA &= ~(uCharValue);
       }

        void static AsInput(uint8_t uCharValue)
       {
            DDRA |= uCharValue;
       }
       };class Pin_A0{
public:
	    void static Set()
	    {
		    PORTA |= 1 << 0;
	    }

	    void static Clear()
	    {
		    PORTA &= ~(1<<0);
	    }

	    void static Toggle()
	    {
		    PORTA ^= (1<<0);
	    }

	    bool Check()
	    {
		    return ((PINA >> 0) & 1);
	    }

	    void static AsOutput()
	    {
		    DDRA &= ~(1<<0);
	    }

	    void static AsInput()
	    {
		    DDRA |= 1<<0;
	    }
        };class Pin_A1{
public:
	    void static Set()
	    {
		    PORTA |= 1 << 1;
	    }

	    void static Clear()
	    {
		    PORTA &= ~(1<<1);
	    }

	    void static Toggle()
	    {
		    PORTA ^= (1<<1);
	    }

	    bool Check()
	    {
		    return ((PINA >> 1) & 1);
	    }

	    void static AsOutput()
	    {
		    DDRA &= ~(1<<1);
	    }

	    void static AsInput()
	    {
		    DDRA |= 1<<1;
	    }
        };
1

Klikalne linki się daje ;)

Trochę nie chce mi się wierzyć, że nie mogłeś adresów portów użyć jako parametrów szablonu, ale w sumie nie wiem jak one są zadeklarowane.

Jeśli inaczej się nie da to ok, ale:

  1. \n lub \n\n przed nową klasą, tragicznie teraz to wygląda
  2. dlaczego Clear przyjmuje parametr? Z nazwy funkcji nie wynika, że działa tylko na części pinów.
  3. dlaczego Toggle przyjmuje parametr? Z nazwy funkcji nie wynika, że działa tylko na części pinów.
  4. jak wszystko jest public, to nie wygodniej użyć struct?
0

Zaraz poprawię formatowanie wygenerowanego kodu, Z tym Set i Clear to sobie pomyślałem, żeby furtkę na jakieś 0b10101001 dać, ale jak o tym wspominasz to masz rację z tą nazwą, troszkę nietrafiona. Ale czy przeciążona funkcja, która może przyjmować parametr lub nie będzie ok, czy dać nazwę typu BitSet?

Co do struct vs class jak wspomniałem - przyzwyczajony jestem do klas z c#, nawet nie pomyślałem o structach.

Co do templatów - w c++ jestem bardzo słaby - tak naprawdę mój kontakt z c++ ograniczał się do std::cout i cin :D, ale dostałem Errora przy kompilacji

 
Severity	Code	Description	Project	File	Line
Error		'*' cannot appear in a constant-expression	MotorServoController	C:\Users\rogoz\Documents\Atmel Studio\7.0\MotorServoController\MotorServoController\main.cpp	39
1

Ja bym dał ClearBits i ToggleBits lub podobnie nazwane jako osobne funkcje.

Set jest ok, bo domyślnie nadpisuje wszystkie obecne wartości. Edit: widzę, żeSet zmienia wartości na 1 bitom które są zaznaczone. W takim razie to samo co wyżej.

Ewentualnie dodałbym domyślną wartość parametru funkcji, jakieś Bits::All i enum Bits : uint8_t { All = 255 };

1

Za późno na enuma, dałem Set() które ustawia na 0xff i Clear() ustawia na 0;

Output teraz


    class PortA{
        public:
	    void static SetBits(uint8_t uCharValue)
       {
            PORTA |= uCharValue;
       }
        
        void static Set()
       {
            PORTA = 0xff;
       }

        void static ClearBits(uint8_t uCharValue)
       {
            PORTA &= ~(uCharValue);
       }

         void static Clear()
       {
            PORTA = 0;
       }

        void static ToggleBits(uint8_t uCharValue)
       {
            PORTA ^= uCharValue;
       }
        void static Toggle()
       {
            PORTA ^= 0xff;
       }

        uint8_t static Check()
       {
            return PINA;
       }
        void static AsOutput()
       {
            DDRA &= ~(0xff);
       }

        void static AsInputBits()
       {
            DDRA |= 0xff;
       }
        void static AsOutputBits(uint8_t uCharValue)
       {
            DDRA &= ~(uCharValue);
       }

        void static AsInputBits(uint8_t uCharValue)
       {
            DDRA |= uCharValue;
       }
       };

    class Pin_A0{
public:
	    void static Set()
	    {
		    PORTA |= 1 << 0;
	    }

	    void static Clear()
	    {
		    PORTA &= ~(1<<0);
	    }

	    void static Toggle()
	    {
		    PORTA ^= (1<<0);
	    }

	    bool Check()
	    {
		    return ((PINA >> 0) & 1);
	    }

	    void static AsOutput()
	    {
		    DDRA &= ~(1<<0);
	    }

	    void static AsInput()
	    {
		    DDRA |= 1<<0;
	    }
        };

    class Pin_A1{
public:
	    void static Set()
	    {
		    PORTA |= 1 << 1;
	    }

	    void static Clear()
	    {
		    PORTA &= ~(1<<1);
	    }

	    void static Toggle()
	    {
		    PORTA ^= (1<<1);
	    }

	    bool Check()
	    {
		    return ((PINA >> 1) & 1);
	    }

	    void static AsOutput()
	    {
		    DDRA &= ~(1<<1);
	    }

	    void static AsInput()
	    {
		    DDRA |= 1<<1;
	    }
        }; 
0

Ah, miałem kiedyś podobny problem, sterownik do UARTa pisałem przenośny pomiędzy różnymi atmegami. Kod ów drivera zaginął w czasie i przestrzeni ale pamiętam że rozwiązałem to w taki sposób. Pod _port podstawiasz PORTB i tak dalej.

#include <stdint.h>
#include <avr/io.h>

class PortX {
public:
    PortX(volatile uint8_t *_port) 
        : _pv(_port) {
    
    }
private:
   volatile uint8_t* const _pv;
};


int main() {
    PortX port(&PORTB);
}

1

A czemu nie użyć referencji, budowa nagłówków AVRowych aż się o to prosi:

  4 struct portOut
  5 { 
  6     private:  
  7     volatile uint8_t& io;
  8     const uint8_t port_no;
  9 
 10     public:    
 11     
 12     portOut(volatile uint8_t& io_, const uint8_t no) : 
 13         io(io_),  
 14         port_no(no) 
 15         {}
 16 
 17     void set() {      
 18         io |= (uint8_t) ((uint8_t) 1 << port_no);
 19     }
 20 
 21     void toggle() {
 22         io ^= (uint8_t) ((uint8_t) 1 << port_no);
 23     }
 24 
 25     void clear() {
 26         uint8_t tmp = (uint8_t) ((uint8_t) 1 << port_no);
 27         tmp = (uint8_t) ~tmp;
 28         io &= (uint8_t) (1 << port_no);      
 29     }
 30 };
0

Tylko te rozwiązania co podajecie zużywają więcej Pamięci programu i danych - Chciałem, by kompilator zajął się tym.

0

Fakt, odrobine wiecej, ale nie jest to znowu taka tragedia. Spójrz na źródła arduino, tam właściwie w pełni wykorzystują możliwości c++ i atmegi się nie wieszają xP
No zależy też pod jaką atmegę piszesz, bo pod ATtiny to wiadomo - troche lipa może być.

0

Dlatego nie używam andruino - na medze16 pamięci brakuje mi strasznie, a mam ich trochę :D. Libka od wyświetlacza sh1103 też swoje waży ;)

Dla Portx.set, clear,toogle dla porówniania
Program memory - 1.2% Data 0%,
1.9% 0.8%

Z podmianą wskaźnika 1.9%, 0.2%

0

Hmm, pamięci w sensie RAMu czy FLASHa? Bo wstawianie po n definicji tej samej klasy tylko ze zmienionym portem zmarnuje pewnie więcej FLASHa niż (jedna) klasa z podstawionymi referencjami/wskaźnikami.

0

Zmierzyłeś ile zajmują w realnej aplikacji? Jeśli nie, to nie zakładaj nic a priori.
EDIT: widzę, że tak. @Proxima pisze dobrze, template'y dodatkowo zwiększą Ci zajętość flasha

0

@alagner kompilator na o3 sobie to wszystko ściąga do podstawowych instrukcji. Testy robiłem na o3.

0

No tak, mówisz o tej klasie z początkowych twoich postów?
Wstaw tyle szablonów tej klasy ile chcesz mieć portów w obiekcie, i wtedy zmierz.
Potem stwórz tyle samo instancji klas z referencją i też zmierz. Wszystko będzie wtedy jasne.

0

A jeszcze pomyślmy o klasie, która spełaniała by funkcje pinu - w swojej klasie musiałaby mieć zapisany publiczy portx, pinx, ddrx. I albo takich obiektów 40, albo co użycie podmieniać zmienne.

user imageuser imageuser image

Jak coś źle zrozumiałem w stosunku do waszych implementacji to sorry, tak naprawdę w c++ piszę od dzisiaj :P

2

To jeszcze raz:
możesz zrobić dwojako: odnosić się do każdego PINU osobno i traktować go jak obiekt lub też odnosić się do całego PORTU jako obiektu i przekazywać numer pinu do metody.
Pytanie kolejne: gdzie mają znajdować się obiekty sterujące IO, jaki ma być ich scope?

BTW - masz świadomość, że optymalizacja O3 jest na czas wykonania, a Os na rozmiar?

1

Ad tych szablonów @Pixello, to spójrzmy na to jak opakowany w makra jest PORTB.
#define PORTB _SFR_IO8(0x05)
link0
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define __SFR_OFFSET 0x20
link1
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
link2
I oto cała tajemnica dlaczego 'szablony nie działają'. Wszystko działa tylko raczej takiej masy makr tam nie idzie wcisnąć (wychodzi lvalue).
Ale jest lekarstwo, jeśli zależy ci na szablonach! Odzoruj budowę tych makr w klasie szablonowej, a jako parametr szablonu przekazuj po prostu sprzętowy numer portu.

0

@Proxima, @alagner ciągle piszecie, że szablony to zły sposób, ale ciągle nie powiedzieliście dlaczego. Ja na prawdę chcę wiedzieć - mam wkuć sobie taką regułkę na pamięć?

2

Bo nagenerujesz od cholery kodu. Dla każdej osobnej konkretyzacji szablonu.

2

Ale przecież kompilator/linker to wywali, więc w czym problem?

http://goo.gl/XfMSmK jak widać jak nie jest pobierany adres to nie jest generowany żaden zbędny kod. Dla -Os, -O2, -O3. Dla -O1 clang wygnerował, gcc nie.

Pisząc pod konkretne środowisko łatwo jest sprawdzić czy kompilator zachowuje się rozsądnie czy nie i na tej podstawie zadecydować.

edit: compiler explorer ma też AVR gcc, co powinno rozwiać wszelkie wątpliwości: http://goo.gl/18NQ44

1

@kq io (w bibliotece) jest zdefiniowane tak #define (*(volatile uint8_t*) 0x2f) IO. Chyba prościej napisać port portA(IO) aniżeli męczyć się z szablonami, żeby tylko zmienić nawiasy na ostre, nie sądzę żeby GCC zrobiło różnicę czy napiszesz IO |= (1<<4); i to IO będzie szablonowe czy nie skoro i tak adres jest stały...

W ARM szablony to mają jeszcze jakiś sens bo rejestry są popakowane w formie typedefowanych struktur, ale tutaj imho to jest dokładanie sobie roboty.

1

Jeśli IO będzie miało dodatkową dereferencję wskaźnika (this), co jest trudniejsze do zoptymalizowania dla kompilatora w przypadku zmiennych innych niż lokalne to może to mieć narzut wydajnościowy. A co do męczenia się z szablonami to nie widzę za bardzo gdzie tu "męka", std::addressof powinno wystarczyć.

Tak czy inaczej, napisałem jedynie, że Twój argument przeciw - "bo nagenerujesz od cholery kodu" - nie ma racji bytu. Za mało doświadczenia mam w AVR, żeby coś innego jednoznacznie stwierdzać

0

Było pytanie o scope - zamierzam wrzucić to do jakiegoś namespace Avr::Atmega16a a wszystkie metody uczynić public static - bo przecież są one środowiskiem.

Napisałem troszkę kodu z tym rozwiązaniem i dla mnie jest bardzo "programmer-friendly". Myślałem o rozszerzenie tego generatora o pliki xml definiujące uC i generować statyczne metody do ustawiania Liczników, Przerwań itp według nich. Przy okazji ukrywa to implementację. Według Was ma to sens czy to dodatkowa, nie potrzebna robota?

Bardzo proszę o opinie, krytykę, bo jedynie przez to mogę się rozwinąć, a że na razie nie mam żadnej okazji do jakiejkolwiek rozmowy na żywo z programistą/code review (uczę się w domu, technikum 3 klasa), to mogę swoimi pomysłami zmierzać w "króliczą norę", więc feedback jest mi bardzo potrzebny ;).

Pozdrawiam

0

A zmierzyłeś ile zajmują przykładowo 10 portów wygenerowanych w ten sposób? Bo to by była chyba sprawa decydująca.

0

Nic nie zajmują - są statyczne, kompilator je ściąga do 0.

1

Skoro tak.. To czemu nie? Poczekaj aż ktoś bardziej doświadczony się wypowie bo ja tyle samo lat co ty mam ; P Jak na moje to użyłbym metody mojej lub @alagner , ale głównie dlatego że używam większych atmeg i lekki overhead nie przeszkadza mi. (Ewentualnie bym optymalizował jakby brakło miejsca, nie wcześniej)

0

1f9a3bafae.png

3

O, ja właśnie coś takie napisałem/piszę. Ostatecznie kod wygląda np. tak:

Avr::Io::Gpio<0>::setDirection(Avr::Io::Direction::OUTPUT);
Avr::Io::Gpio<0>::setValue(true);
Avr::Timer::BusyDelayMs(1.0);
Avr::Io::Gpio<0>::setValue(false);

To bardzo ułatwia robienie czegokolwiek, a kodu wynikowego jest tyle samo jak gdyby pisać to ręcznie. Kompilatory są wystarczająco sprytne.

Zrezygnowałem z jakiegoś generowania zewnętrznymi narzędziami kodu. To co można generować generuje makrami, przynajmniej nie potrzeba do tego nic z zewnątrz. Wymyślnie rozwiązania "generujące" są bez sensu IMO, bo i tak trzeba napisać opis co generować - ostatecznie opisu (czy to dla generatora, czy to makr, czy wprost w kodzie) jest podobna ilość. No chyba, że to coś bardzo skomplikowanego, ale to przecież tylko AVR...

Biblioteki C++ niestety nie ma, ale to co potrzeba można napisać samemu. Dodatkowo warto używać/opakować co się da z avrlibc.

Ogólnie polecam, fajna zabawa.

1

@Pixello sprawdziłem jak to wygląda, generalnie faktycznie Twoja metoda daje najmniejszy kod, przynajmniej na O3. Pytanie jeszcze co chcesz zrobić bo np. nie przekażesz sobie np. obiektu typu IO do funkcji czy nie umieścisz go w klasie. Ale oczywiście wedle woli, zależy co Ci potrzebne.

0

Generalnie, zgodnie z zasadą SRP, chciałem oddzielić abstrakcję (algorytmy działania) od implementacji, a implementacje uczynić zrozumiałą dla "czytacza", bo PortA |= 0xab; nie mówi dużo.
Po poznaniu dobroci debugerów i przejrzystego kodu postanowiłem uprościć sobie życie.
Jak jakieś pół roku temu w c pisałem większość moich błędów polegała na odwołaniu się do nie tego portu, pinu lub wpisania 0b0101110 zamiast 0b01011110.

Na kartce mam napisane kilka rzeczy które tak chcę zaimplementować i przetestować przed pisaniem kodu właściwego - min przerwania , 2 timery, i2c, uart. Mam nadzieję, że uchroni mnie to przed głupimi błędami, a błędy będą łatwiej lokalizowalne. Co do zmieniania pinów w implementacji na razie będę robił to przez #define LedRed Pin_A1.

1 użytkowników online, w tym zalogowanych: 0, gości: 1