virtualny Napisano Lipiec 31, 2023 Udostępnij Napisano Lipiec 31, 2023 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: przy 36MHz: 24 MHz: 12 MHz: 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 More sharing options...
ReniferRudolf Lipiec 31, 2023 Udostępnij Lipiec 31, 2023 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 More sharing options...
virtualny Lipiec 31, 2023 Autor tematu Udostępnij Lipiec 31, 2023 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: 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. 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ć. Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
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ę »