Skocz do zawartości

CM-3 Prędkość wykonywania kodu


virtualny

Pomocna odpowiedź

Pisząc programową obsługę SPI stanąłem przed dylematem, jak szybko mogę przełączać sygnał CLK, czy powinienem stosować opóźnienia i jeżeli tak to jakie?

 

Napisałem program, który nie robi nic innego, jak tylko "macha" pinem PB12 zmieniając jego stan z 0 na 1. Oczywiście chcąc mierzyć takie rzeczy nie wystarczy do tego C czy C++, ponieważ nie mamy kontroli nad generowanym kodem. Do tego użyłem assemblera, który bezustannie wykonuje operację zapisu do portu:

 

// R0 = 0
// R1 = 1
// R2 = BITBAND ADDRESS OF PB12 
...
        STR R0, [R2]
        STR R1, [R2]
...

 

Po uruchomieniu takiego kodu wystarczy zmierzyć częstotliwość na pinie PB12, aby wiedzieć z jaką maksymalną częstotliwością można programowo sterować wyjściem na wybranym pinie.

Program wygląda tak:


.cpu cortex-m3
    .arch armv7-m
    .fpu softvfp

    .thumb
    .thumb_func
    .syntax unified
    .global banding

    .type   banding, %function


    .text
    .align    2
//extern void banding(void);
//========================================================
// change alias bit banding PB12
//========================================================
banding:

        CPSID  I
        LDR  R2, = 0x422181B0 // PB12 output BB address


        MOV R0, 0
        MOV R1, 1

BAND2:
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]
        STR R0, [R2]
        STR R1, [R2]


        B BAND2

//======================================================

Co istotne pętla BAND2: zrolowana do postaci:

//=====================
BAND2:
        STR R0, [R2]
        STR R1, [R2]
        B BAND2

//=====================

 

Działa z taką samą prędkością.

Odczyt pinu przy procku pędzonym 72MHz:

72MHz.thumb.jpg.c623b49acbd5d02865dcb6859ac50efd.jpg

 

przy 36MHz:

36MHz.thumb.jpg.f45cf50d1f1088feaf7d2e2c2507d42c.jpg

24 MHz:

24MHz.thumb.jpg.ba46dc38fa49a0fdb2da2b1af6c88a98.jpg

 

12 MHz:

12MHz.thumb.jpg.e36233fa47ac5bf4aa30bd54877f0dab.jpg

 

 

Wyszło na to, że gdyby procesor pędzony maksymalną częstotliwością 72MHz nie robił nic innego, tylko "produkował" ten prostokąt na PB12, nie jestem w stanie przekroczyć 6MHz, co jako sygnał CLK dla SPI nie jest jakimś super szybkim wyczynem i wiele urządzeń SPI (w moim przypadku flash SPI) potrafi obsługiwać SPI przy częstotliwości zegara w dziesiątkach megaherców, więc urządzenie "nie przegapi" tego szybkiego kliknięcia na CLK.

 

Teraz trochę rozważań para rozkazów:

        STR R0, [R2]
        STR R1, [R2]

Produkuje sygnał w każdym przypadku 12 razy wolniejszy, niż taktowanie procesora, co oznaczałoby że (poprawcie mnie jeżeli się mylę)), że oba te rozkazy zajmują 12 cykli procesora, czyli jeden rozkaz STR R0, [R2]  zajmuje 6 cykli.

 

Przy okazji sprawdziłem kilka innych rozkazów przy procku pędzonym 72MHz:

BAND2:
        STR R0, [R2]
        UMULL R4,R3,R3,R1
        STR R1, [R2]
        UMULL R4,R3,R3,R1


        B BAND2

//1MHz

BAND2:
        STR R0, [R2]
        UDIV R4,R5,R6
        STR R1, [R2]
        UDIV R4,R5,R6


        B BAND2

//750 kHz

BAND2:
        STR R0, [R2]
        SMLAL r4, r8, r5, r6
        STR R1, [R2]
        SMLAL r4, r8, r5, r6


        B BAND2

//1MHz

 

Tak więc UMULL R4,R3,R3,R1 pętla 4 rozkazów zajęła 72 cykle, teraz dzieląc przez 2 to 36 cykli i od 36 cykli odejmując 6 cykli za STR R0, [R2]  wychodzi na to, że UMULL R4,R3,R3,R1 zajmuje aż 30 cykli! 

Tyle samo czyli 30 cykli zajmuje rozkaz SMLAL r4, r8, r5, r6.

 

Dla fali 750 kHz odpowiednio:

1MHz to 72 cykle, 0.5 MHz to, 144 cykle, czyli 0.75MHz to będzie 72 + (72/2) = 72+36 = 108 cykli.

108/2 =54 i minus 6 cykli za STR R), [R2]   48 (!!!) 

Czyli rozkaz dzielenia UDIV R4, R5, R6 zajmuje procesorowi 48 CYKLI ! To chyba najdłużej działający rozkaz w CM3.

 

Program w załączniku.___103BB.ZIP

Link do komentarza
Share on other sites

2 godziny temu, virtualny napisał:

Teraz trochę rozważań para rozkazów:

        STR R0, [R2]
        STR R1, [R2]

Produkuje sygnał w każdym przypadku 12 razy wolniejszy, niż taktowanie procesora, co oznaczałoby że (poprawcie mnie jeżeli się mylę)), że oba te rozkazy zajmują 12 cykli procesora, czyli jeden rozkaz STR R0, [R2]  zajmuje 6 cykli.

Trochę wyżej napisałeś prawdę, mowiąc, że mierzysz:
 

3 godziny temu, virtualny napisał:

z jaką maksymalną częstotliwością można programowo sterować wyjściem na wybranym pinie

[używając rozkazu STR - moje dopowiedzenie]. No a liczba cykli potrzebna do tego nie jest chyba tożsama (i jest większa) niż liczba cykli wykonania rozkazu STR, ze względu na istnienie "po drodze do pinu" busów peryferyjnych (jak to napisać po polskiemu? 🤔😉

Tu ciekawy artykulik chyba dokładnie w obszarze Twoich zainteresowań: http://www.scaprile.com/2021/10/28/gpio-handling-in-arm-cortex-m/ 

Link do komentarza
Share on other sites

2 godziny temu, ReniferRudolf napisał:

Trochę wyżej napisałeś prawdę, mowiąc, że mierzysz:

Dzięki za reakcję. Po Twoim poście zacząłem się zastanawiać jak to będzie działać z RAM, z BB RAM itd. Zacząłem szperać, aż znalazłem zapomniane przeze mnie boje Szczywronka, który rozkminiał temat. Oto jego wyniki:

Szczywronek.thumb.jpg.75d179eb34addd0c910f68153ef28efb.jpg

 

Zacząłem się zastanawiać, jak to się opóźnia i próbować na różne sposoby - widać w tabelce od Szczywronka, że stosując aliasowanie przez BITBANDING opóźnienie jest 3 krotnie większe, jak by ta atomowa operacja "po cichu" wykonywała RMW (read-modify-write).

 

Tak więc w zależności do czego się odwołujemy i przez jaki interfejs daje to różną prędkość. Przykładowo działając na RAM przez BITBANDING, czyli stosując taką sekwencję:

banding:

        CPSID  I

        LDR  R2, = 0x422181B0 // PB12 output BB address
        LDR  R3, = 0x22000000 // BB RAM POINTER


        MOV R0, 0x00
        MOV R1, 0x01

BAND2:
        STR R0, [R2]
        STR R0, [R3]
        STR R1, [R2]
        STR R1, [R3]

        B BAND2

sygnał zwolnił, ale nie do oczekiwanych 3 MHz, tylko do 3.6MHz

Z kolei odwołując się bezpośrednio do RAM:

banding:

        CPSID  I

        LDR  R2, = 0x422181B0 // PB12 output BB address
        LDR  R3, = 0x02000000 // RAM POINTER


        MOV R0, 0x00
        MOV R1, 0x01

BAND2:
        STR R0, [R2]
        STR R0, [R3]
        STR R1, [R2]
        STR R1, [R3]

        B BAND2

Sygnał był na poziomie 4.5 MHz !!! To dawało by dla bezpośredniego zapisu do RAM STR R0,[R3] tylko 2 cykle !!!

Głównym powodem moich dociekań było to, czy moje software-owe "klikanie"  CLK nie będzie przypadkiem zbyt szybkie dla urządzenia SPI, które mogłoby przypadkiem "niezauważyć" tego CLK. Drugim moim powodem była ciekawość 🙂

OK - teraz wykonując taką sekwencję, KTÓRA NIEUŻYWA BITBANDINGU,  KORZYSTA Z REJESTRU BSSR (LUB ODR) PODNIOSŁA SIĘ CZĘSTOTLIWOŚĆ SYGNAŁU Z 6 DO 18 MHz!!! 

To daje 2 cykle na rozkaz STR Rx, [Rp] i stanowi to znaczącą różnicę. Użycie poczwórnej sekwencji zapisu plus rozkazu skoku bezpośredniego jest widoczne na oscylogramie, tu już jak w przypadku BB nie ma jednolitego potoku - widoczna przerwa, to właśnie rozkaz skoku.

72MHz_BSSR.thumb.jpg.4aaa70bdea32eecbdabe2655f7f6aa69.jpg

 

Tak wygląda program, który wyciąga 18MHz na porcie, przy 72MHz taktowaniu procka. To daje 2 cykle na rozkaz.

 

banding:

        CPSID  I

        //LDR  R2, = 0x422181B0 // PB12 output BB address
        //LDR  R2, = 0x40010C0C // PB ODR REGISTER

        LDR  R2, = 0x40010C10 // PB BSRR REGISTER
        LDR  R3, = 0x22000000 // RAM POINTER


        MOV R0, 0x00001000
        MOV R1, 0x10000000

BAND2:
        STR R0, [R2]
        STR R1, [R2]

        STR R0, [R2]
        STR R1, [R2]

        STR R0, [R2]
        STR R1, [R2]

        STR R0, [R2]
        STR R1, [R2]

        B BAND2

 

Pomyśli ktoś, że te 18 MHz, to tylko przy permanentnym zaangażowaniu procesora w ten wątek, więc muszę wyjaśnić w jaki sposób "klikam" te CLK, że faktycznie dałoby to owe 18 MHz.

Oto procedura wysyłająca programowo 1 bajt po SPI:

//=============================================================
flash_S1B:
        PUSH   {R0-R6}
        // R0 = DATA TO SEND
        // R1 = F_CS POINTER 0x422101bc
        // R2 = BIT'S COUNTER
        // R3 = 0 FOR LOW LINE SET
        // R4 = 1 FOR HIGH LINE SET
        // R5 = F_MOSI POINTER 0x4221819c
        // R6 = F_CLK  POINTER 0x422181a0




        MOV    R3, #0 // FOR CLICK 0 BB
        MOV    R4, #1 // FOR CLICK 1 BB

        LDR    R1, = 0x422101bc // R1 = F_CS
        STR    R3, [R1]         // F_CS = 0 ... ENABLE CS

        LDR    R5, = 0x4221819c // R5 = F_MOSI
        LDR    R6, = 0x422181a0 // R6 = F_CLK


        MOV    R2, 8    // COUNT 8 BIT'S

        LSL    R0, R0, #24  // MOVE 24 TIMES TO THE LEFT

F_S1B1:
        LSLS  R0, R0, #1 // SEND ONE BIT
        BCC   F_S1B2
        STR   R4, [R5]   // F_MOSI = 1
        B     F_S1B3
F_S1B2:
        STR   R3, [R5]   // F_MOSI = 0
F_S1B3:
        STR   R3, [R6]   // F_CLK BLINK TO 0
        STR   R4, [R6]   // F_CLK BLINK TO 1
        SUBS  R2, R2, #1 // CHECK BIT'S SEND
        BNE   F_S1B1

        POP    {R0-R6}
        BX     LR
//=============================================================

Przy etykiecie F_S1B3: właśnie robię takie szybkie przełączenie CLK, chociaż cała procedura trwa o wiele dłużej niż potrzeba, to tutaj mogłoby to prowadzić do przekłamań, gdyby urządzenie po drugiej stronie było zbyt wolne. Oczywiście można by zmodyfikować kod żeby dodać opóźnienie na przykład przez wpisanie rozkazu SUBS R2, R1, #1 , czy dodanie zwykłego NOP'a. Przede wszystkim trzeba mieć świadomość powstających zagrożeń mogących wpływać na poprawność działania programu. W moim przypadku nawet te 18MHz nie byłoby najprawdopodobniej żadnym problemem, ale przy wykorzystaniu jakichś CM-4 168MHz i więcej należałoby już uważać.

CLK.jpg

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.