Miganie diodą w stylu arduino

Forum poświęcone układom SBC (Single Board Komputer) opartych o SoC Amlogic
Regulamin forum
Obrazek

Amlogic Khadas VIM1 BASIC
SoC: Amlogic S905 Quad Core ARM Cortex-A53 Mali-T450MP5 GPU UHD H.265/VP9 60fps HDR10 oraz HLG HDR

Obrazek

Amlogic Khadas VIM2 PRO
SoC: Amlogic S912 Octa Core ARM Cortex-A53 Mali-T820MP3 GPU HW UHD H.265/VP9 60fps HDR10 oraz HLG HDR
ODPOWIEDZ
Awatar użytkownika
elvis
Użytkownik
Posty: 57
Rejestracja: 30 lis 2018, 17:50

Miganie diodą w stylu arduino

Post autor: elvis » 20 cze 2019, 22:48

Większość osób, które miało chociaż trochę do czynienia z linuxem na SBC wie, że nic nie jest proste. Na takim AVR można było zapisać wartość do kilku rejestrów i już była pięknie migająca dioda. Natomiast pod linuksem są jakieś cuda w rodzaju /sys/class/gpio/export, wszystko jest plikiem, a miganie diodą zajmuje mnóstwo czasu procesora.

Okazuje się że to jednak tylko część prawdy. Nawet większe mikroprocesory nadal komunikują się z peryferiami za pomocą rejestrów. W przypadku porządnych układów jak np. stm32mp1, czy imx6ull znajdziemy dokładną dokumentację każdego rejestru wraz z pełnym opisem. Amlogic dostarcza tylko szczątkową informację, a dokładniej coś takiego:

Obrazek

Używając zaawansowanych algorytmów kryptograficznych opartych o sztuczne i niesztuczne sieci neuronowe można domyślić się kilku rzeczy. Po pierwsze jest jakiś adres: 0xc8834400. Po drugie są nazwy pinów - ja wybrałem GPIOH_5 i podłączyłem do niego diodę. Opis wyprowadzeń Khadas jest dostępny na stronie producenta:

Obrazek

Łącząc wszystkie poszlaki domyślamy się, że do sterowania GPIOH używane są trzy rejestry:
  • OEN - pewnie output enable, coś jak DDR na AVR
  • OUT - wyjście, taki PORT z AVR
  • IN - wejście, to się chyba PIN nazywało, ale głowy nie dam
W sumie wygląda to jak AVR, tak naprawdę jest znacznie gorzej, ale nie będziemy narzekać. W tej obszernej dokumentacji znajdziemy również adresy rejestrów, żeby nie było za łatwo musimy jest sobie pomnożyć przez 4.

Dochodzimy do najtrudniejszego - jak dostać się do rejestrów? Na AVR wystarczyłoby po prostu odwołać się do odpowiedniego adresu. Ale tutaj mamy linuksa, wszystkie programy w przestrzeni wirtualnej - próba adresowania rodem z AVR zakończy nasz program zanim się na dobre zacznie.
Okazuje się, że nie jest tak źle. W linuksie prawie wszystko jest plikiem, ale niektóre pliki są wyjątkowe. Jednym z takich cudów jest urządzenie /dev/mem. Pozwala ono na wiele niebezpiecznych sztuczek, dlatego ostatnio nie zawsze jest domyślnie dołączane do jądra, ale jeśli sami skompilujemy system, możemy je dodać.
Urządzenie jest plikiem, jak wszystkie inne, więc w programie musimy najpierw taki plik otworzyć:

Kod: Zaznacz cały

fd = open("/dev/mem", O_RDWR);
Teraz sztuczka - linux posiada funkcję mmap, która pozwala na zamapowanie pliku bezpośrednio do pamięci. Używając jej oraz właśnie otwartego pliku, możemy uzyskać dostęp do fizycznych adresów, czyli bezpośrednio do rejestrów.
Kod wygląda następująco:

Kod: Zaznacz cały

gpio = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_BASE);
Stała GPIO_BASE to adres rejestrów, który rozszyfrowaliśmy w tej pięknej dokumentacji od amlogic. 0x1000 to wielkość obszaru, natomiast wynik działania funkcji to wskaźnik w pamięci wirtualnej procesu. Za jego pomocą będziemy mogli odwoływać się do rejestrów jak na AVR.

Trochę się rozpisałem, więc może czas na cały program, a później jeszcze napiszę kilka słów wyjaśnienia:

Kod: Zaznacz cały

#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define GPIO_BASE       0xc8834000

#define GPIOH_EON       0x10f
#define GPIOH_OUT       0x110
#define GPIOH_IN        0x111

#define LED_PIN         5
#define LED_BIT         (1u << (20 + LED_PIN))

inline void gpio_write(void *base, int reg, uint32_t value)
{
        volatile uint32_t *p = (volatile uint32_t*)(base + reg * 4);
        *p = value;
}

inline uint32_t gpio_read(void *base, int reg)
{
        volatile uint32_t *p = (volatile uint32_t*)(base + reg * 4);
        return *p;
}

inline void gpio_set(void *base, int reg, uint32_t mask)
{
        uint32_t value = gpio_read(base, reg);
        value |= mask;
        gpio_write(base, reg, value);
}

inline void gpio_clear(void *base, int reg, uint32_t mask)
{
        uint32_t value = gpio_read(base, reg);
        value &= ~mask;
        gpio_write(base, reg, value);
}

int main(int argc, char *argv[])
{
        int i, fd;
        void *gpio;

        printf("Hello world!\n");

        fd = open("/dev/mem", O_RDWR);
        if (fd == -1)
                perror("memory device open error");

        gpio = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED,
                   fd, GPIO_BASE);
        if (gpio == MAP_FAILED)
                perror("mmap");

        gpio_clear(gpio, GPIOH_EON, LED_BIT);

        for (i = 0; i < 3; i++) {
                gpio_set(gpio, GPIOH_OUT, LED_BIT);
                usleep(200000);
                gpio_clear(gpio, GPIOH_OUT, LED_BIT);
                usleep(200000);
        }

        munmap(gpio, 0x1000);
        close(fd);

        return 0;
}
Pierwsze wyjaśnienie to wartość GPIO_BASE. W dokumentacji jest 0xc8834400, ale mmap wymaga wyrównywania do wielkości strony, czyli pewnie 4KB, albo 8KB. W każdym razie użyłem 0xc8834000, a 0x400 dodałem do adresów rejestrów. Ponieważ adresy są mnożone przez 4, wiec dodałem 0x100, stąd GPIOH_EON zamiast 0x0f ma wartość 0x10f.
Funkcje gpio_write, gpio_read, gpio_set i gpio_clear to takie nakładki na bezpośrednie dostępy do rejestrów. Na AVR pewnie używalibyśmy po prostu =, |= oraz &= , tutaj też tak można, ale w linuksie raczej używa się funkcji inline. Chociaż jak ktoś preferuje styl AVR - to też zadziała.
I na koniec ostatni fragment z kryptografii - okazuje się, że rejestr EON jest zanegowany. czyli wpisując zero, ustawiamy pin jako wyjście. Takie Enable Output Negative...

Podsumowanie
Moim zdaniem największą wadą płytek Khadas jest brak dokumentacji. Jak widać nawet miganie diodą do zadanie prawdziwie wywiadowcze. Nawet nie chcę myśleć jak skomplikowane byłoby obsłużenie bardziej zaawansowanych modułów.
Chciałem jednak pokazać, że pod linuksem można pisać programy w stylu AVR. Gdybyśmy zamiast amlogic s905x używali stm32mp1 to nawet rejestry wyglądałyby znajomo.

ODPOWIEDZ