Skocz do zawartości

Programowanie STM32L4 w asm (CubeIDE)


ReniferRudolf

Pomocna odpowiedź

Interesuje mnie najlepsza ścieżka postępowania w środowisku CubeIDE, aby tworzyć i debugować proste projekty w assemblerze. Jeśli taka wiedza jest na forum 🙂, to będę wdzięczy za jakieś (wystarczą całkiem ogólne) inspiracje. Być może powinienem użyć innych środowisk/narzędzi?

PS.
Na razie po wstępnym szybkim przejrzeniu forum st.com znalazłem pewien bardzo konkretny przykład postępowania w CubeIDE, jednak dla nieco innej płytki (F446RE): https://community.st.com/s/question/0D53W00001cGGtBSAW/how-to-run-assembly-code-in-stm32-cube-ide-nucleo-f446re

W projekcie w języku C znajduje się wyłącznie funkcja main() użytkownika nie zawierająca nic poza wywołaniem procedury assemblerowej. Zaś procedura assemblerowa zawiera (!!) jakiś kluczowy kod inicjalizacyjny. Jeszcze tego nie wiem, ale zakładam, że ten kod inicjalizacyjny to jest właśnie absolutnie minimalna konfiguracja rejestrów, tak aby ARM core na płytce w ogóle podjął wykonywanie instrukcji (z opisu wynika, że to jest przykład projekt w "czystym" assemblerze, czyli taki który zadziała bez jakiekolwiek dodatkowego kodu dodawanego przez środowisko CubeIDE).

Link do komentarza
Share on other sites

@ReniferRudolf witam na forum! Osobiście w tej w kwestii nic nie podpowiem, może @Elvis będzie mógł coś podpowiedzieć 😉 

Zapytam tylko z ciekawości - dlaczego assembler? Już dawno nie słyszałem, aby ktoś podczas nauki nowej rodziny mikrokontrolerów zaczynał w asm. Nie lepiej zacząć chociaż od bibliotek Low Layer, jeśli HAL całkiem odpada?

Link do komentarza
Share on other sites

3 godziny temu, Treker napisał:

Zapytam tylko z ciekawości - dlaczego assembler? Już dawno nie słyszałem, aby ktoś podczas nauki nowej rodziny mikrokontrolerów zaczynał w asm. Nie lepiej zacząć chociaż od bibliotek Low Layer, jeśli HAL całkiem odpada?

Jasne - jeśli chodzi o przerobienie kursu, naukę mikrokontrolera z całym otoczeniem, realizację własnych projektów itp. - oczywiście wyborem jest język C. Natomiast chciałbym mieć środowisko do poćwiczenia/przypomnienia sobie programowania w architekturze ARM (robiłem te rzeczy lata temu...), to może mi się przydać w przyszłości w jakiś niszowych zastosowaniach. Chciałbym więc do tego teraz wrócić mając na biurku procesor ARM, do którego mógłbym wgrać sobie dowolny kod bez żadnych "otoczek".

  • Lubię! 2
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

(edytowany)

W tej chwili najbardziej interesujący dla mnie jest wybór środowiska / zestawu narzędzi, z którym jako "targetu" do programowania i debugowania będę mógł używać płytki STM.

Zgrubsza widzę już z powyższych odpowiedzi kilka możliwych ścieżek, które będę za jakiś czas sprawdzał / wdrażał. Z całą pewnością na początku zastosuję ten sposób (z wykorzystaniem CubeIDE i standardowego projektu w C):

3 godziny temu, virtualny napisał:

Generalnie jest to dość proste - dodajesz do plików swój plik z rozszerzeniem "s" i w nim piszesz swoje procedury.


 

Edytowano przez ReniferRudolf
Link do komentarza
Share on other sites

Dodam, że KEIL i EWARM mają niebagatelny ficzer, którego nie ma w CubeIDE. Jest to symulator ARM. Peryferiów chyba nie wyemulują, ale kod krok po kroku ze śledzeniem flag bardzo dobrze. Jeżeli naprawdę chcesz tylko pisać w ASM, to najbardziej przyjazny będzie KEIL - tam nawet nie musisz plików assemblera tworzyć, wystarczy w pliku C dyrektywa "__asm" jak widać powyżej. W GCC pisanie z dyrektywą ASM w plikach C wygląda jak dowcip, czy utrudnienie, żeby każdą jedną linię umieszczać w cudzysłowiu... Albo każdą linię budować w taki sposób:

__ASM volatile ("cpsid i" : : : "memory");

Szkoda czasu i nerwów. No i musisz pamiętać wszystkie assemblerowe niuanse jak eksportować zmienną, procedurę, określić składnię, rdzeń itp.

  .syntax unified
  .cpu cortex-m3
  .fpu softvfp
  .thumb

.global g_pfnVectors
.global Default_Handler

/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss

.equ  BootRAM, 0xF108F85F
/**
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called.
 * @param  None
 * @retval : None
*/

  .section .text.Reset_Handler
  .weak Reset_Handler
  .type Reset_Handler, %function
Reset_Handler:


 

  • Lubię! 1
Link do komentarza
Share on other sites

(edytowany)

Zrobiłem trochę eksperymentów, które pozwoliły zorientować się, co gdzie można znaleźć w obszernej dokumentacji, w środowisku CubeIDE, w generowanym kodzie itp. Kilka wniosków, gdyby ktoś chciał bawić się w uruchamianie na Nucleo-L476R kodu w assemblerze.

1. Odnośnie znalezionego wcześniej przykładu:

Dnia 16.06.2023 o 15:59, ReniferRudolf napisał:

Na razie po wstępnym szybkim przejrzeniu forum st.com znalazłem pewien bardzo konkretny przykład postępowania w CubeIDE, jednak dla nieco innej płytki (F446RE): https://community.st.com/s/question/0D53W00001cGGtBSAW/how-to-run-assembly-code-in-stm32-cube-ide-nucleo-f446re
 

Przykład w tym linku jest zgrubną ilustracją ogólnej idei, jak utworzyć projekt assembler w CubeIDE. Natomiast treść kodu w ASM w tym przykładzie nie jest ani poprawna (inicjalizacja clock-ów) ani kompletna (wycięte fragmenty), aby na płytce Nucleo-L476R uruchomić mruganie wbudowaną diodą, tak że proszę nie tracić czasu na wklejanie go do swoich projektów. 

2. Domyślne wartości rejestrów mikrokontrolera po sygnale Reset nie wymagają żadnej zmiany, aby Core pobierał i wykonywał kolejne rozkazy z pamięci. Jest to w sumie dość logiczne... Jeżeli utworzymy nowy pusty projekt w CubeIDE, to śledząc debuggerem funkcje HAL_Init() i SystemClock_Config() zobaczymy, że wprawdzie zapisują one kilka rejestrów związanych z konfiguracją linii zegarowych, ale zapisywane wartości są identyczne z wartościami po Resecie, albo nie dotyczą działania podstawowych podsystemów mikrokontrolera, więc można je pominąć z całą pewnością w programach typu Hello World.

Odpada więc w zasadzie pytanie o potrzebę jakiejś "ogólnej" inicjalizacji w ASM podstawowego systemu mikrokontrolera. 

3. Interesujący jest rzut oka na zawartość pliku Core/Startup/startup_xxxx.s, który jest linkowany z każdym projektem w języku C. Ten plik startup_xxxx.s zawiera de facto pierwsze instrukcje, które Core wykona natychmiast po włączeniu zasilania, jeszcze przed funkcją main(). Jest to między innymi: inicjalizacja wskaźnika stosu (SP), ustawienie domyślnej "procedury obsługi" dla wszystkich ewentualnych przerwań (po prostu... pętla nieskończona, aby złapać w debuggerze stan programu) oraz oczywiście skok do funkcji main().

Z tego kodu możemy (najpewniej, choć nie sprawdzałem, czy trzeba coś zmienić przy budowaniu projektu etc.) skoczyć bezpośrednio do początku naszego kodu w ASM. Albo możemy zastosować prostą sztuczkę opisaną w p. 1 - czyli wyciąć WSZYSTKO, co CubeIDE wygeneruje w pliku main.c i umieścić w funkcji main() wywołanie naszej funkcji zaimplementowanej w ASM.

Załączony poniżej kod zawiera dwa "punkty wejścia", czyli globalnie widoczne etykiety, które nazwałem sobie ASM_Init oraz ASM_Function i które mogą być wywoływane jako funkcje z main.c. Poniższy kod uruchamia się i działa prawidłowo na płytce Nucleo-L476R dołączonej do kursu - zapala i gasi diodę podłączoną do pinu PA oznaczonego stałą PIN_NUM (dla wbudowanej diody LD2 trzeba ustawić "5").

/*
 * assembler.s
 *
 */

  .syntax unified

  .text
  .global ASM_Init
  .global ASM_Function
  .thumb_func

  .equ  RCC_BASE_ADDR,  0x40021000
  .equ  o_RCC_CR,       0x00
  .equ  o_RCC_AHB2ENR,  0x4C
  .equ AHB2ENR_GPIOAEN, 0x01

  .equ  PIN_NUM,       11                              // GPIO-A pin number
  .equ  GPIO_BASE_ADDR,   0x48000000
  .equ  o_GPIOA_ODR,      0x14
  .equ  o_GPIOA_MODER,    0x00
  .equ  MODER_PINMODE_OUTPUT,   1 << (PIN_NUM * 2)     // value for bits[2*PIN_NUM+1 : 2*PIN_NUM]
  .equ  MODER_PINMODE_MASK,    ~(3 << (PIN_NUM * 2))   // mask for bits[2*PIN_NUM+1 : 2*PIN_NUM]

  .equ  COUNTER_ON,        300000    // period of delay for output high state
  .equ  COUNTER_OFF,       300000    // period of delay for output low state

ASM_Init:
  // Enable GPIO port A clock; RCC_AHB2ENR reg, Bit 0 (GPIOAEN) = 1
  ldr r1, =RCC_BASE_ADDR
  ldr r0, [r1, o_RCC_AHB2ENR]
  orr r0, AHB2ENR_GPIOAEN
  str r0, [r1, o_RCC_AHB2ENR]
  ldr r0, [r1, o_RCC_AHB2ENR]      // delay after writing AHB2ENR

  // Set GPIO port A pin X as output; GPIOA_ODR reg
  ldr r1, =GPIO_BASE_ADDR
  ldr r0, [r1, o_GPIOA_MODER]
  and r0, MODER_PINMODE_MASK
  orr r0, MODER_PINMODE_OUTPUT
  str r0, [r1, o_GPIOA_MODER]

  bx lr   // Return from function

ASM_Function:
  ldr r0, [r1, o_GPIOA_ODR]

turn_ON:
  // Set GPIO pin high and delay
  orr r0, 1 << PIN_NUM
  str r0, [r1, o_GPIOA_ODR]
  ldr r2, =COUNTER_ON
  bl delay

turn_OFF:
  // Set GPIO pin low and delay
  and r0, ~(1 << PIN_NUM)
  str r0, [r1, o_GPIOA_ODR]
  ldr r2, =COUNTER_OFF
  bl delay

  b turn_ON   // Never-ending loop

delay:    // r2 contains the delay counter
  subs r2, r2, #1
  bne delay
  bx lr

 

Edytowano przez ReniferRudolf
  • Lubię! 2
Link do komentarza
Share on other sites

Przykład odejmowania w ASM liczb 64 bitowych:


 

;r0 11 lsw of 1st 64bit number
;r1 22 msw of 1st 64bit number
;r2 ab lsw of 2nd 64bit number
;r3 cd  msw of 2nd 64bit number


subs r4, r0, r2      ; First subtract the lsb's and update cpsr      ; r4 = r0 - r2
sbc r5, r1, r3      ; Now subtract the msb's                                ; r5 = r1 - r3 - ~C

 

Poniżej 3 procedury, pierwsza mnożenie dwóch 32 bitowych liczb z wynikiem 64bit, oraz 2 osobne procedury dzielenia liczby 32 bitowej przez 16 bitowe (dla poprawnego działania 16bit).


 

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB


; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler

__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

                AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]


;***********************************************************
var_lo      RN 0 
var_hi      RN 1
var3        RN 2
cnt_lop     RN 3


           MOV R0, #1

           LDR var_lo, = 0xFFFFCCCC
           LDR var3,   = 0xCCCCFFFF
           
           SUB var_hi, var_hi, var_hi
           MOV  cnt_lop, #33
           CMP  cnt_lop, #44
           
mul_lop           
           BCC  mul32_ror
           
           ADDS var_hi, var_hi, var3

mul32_ror           
           RRXS  var_hi, var_hi
           RRXS  var_lo, var_lo
           
           MRS r4, APSR
           
           
           SUBS cnt_lop, cnt_lop, #1;
           BEQ end_m32        

           MSR  APSR, r4
           B mul_lop
           
end_m32
           
           
           NOP
           
           

;***********************************************************

; $Bot  Rejestr zawierajacy DZIELNIK.
; $Top  Rejestr, w którym przechowywana jest DZIELNA przed wykonaniem instrukcji. Po wykonaniu instrukcji przechowuje reszte.
; $Div - Rejestr, w którym umieszczony jest iloraz dzielenia. Moze to byc NULL (""), jesli wymagana jest tylko reszta.
; $Temp - Rejestr tymczasowy uzywany podczas obliczen.


Top      RN 5 ; DZIELNA NA KONCU RESZTA
Bot      RN 4 ; DIVISOR - DZIELNIK
Div      RN 0 ; REMAINDER OF QUOTIENT RESZTA Z DZIELENIA
Temp     RN 2 ; TEMPORARY VALUE

        LDR Top, =0xFFFFFFF6
        MOV Bot, #0x0A

        MOV     Temp, Bot              ; Put divisor in $Temp
        CMP     Temp, Top, LSR #1      ; double it until
LPX0    
        MOVLS   Temp, Temp, LSL #1   ; 2 * $Temp > $Top
        CMP     Temp, Top, LSR #1
        BLS     LPX0                   ; The b means search backwards

        MOV Div, #0                    ; Initialize quotient

LPX1    CMP     Top, Temp         ; Can we subtract $Temp?
        SUBCS   Top, Top, Temp    ; If we can, do so is null
        ADC     Div, Div, Div  ; Double $Div
        MOV     Temp, Temp, LSR #1  ; Halve $Temp,
        CMP     Temp, Bot           ; and loop until
        BHS     LPX1                  ; less than divisor
        
;***************************************************************

         
VAR1        RN 0 ;DZIELNA 
VAR2        RN 1 ;DZIELNIK
MOD10       RN 2 ;
LC          RN 3 ;


         LDR VAR1, = 0xFFFFFFF6 
        MOV VAR2, #0xFFFA
        MOV MOD10, #0
        MOV LC, #32
        
LP01
        LSLS    VAR1, #1
        LSL     MOD10, #1
        RRX     MOD10, MOD10
        ROR     MOD10, MOD10, #31
        CMP     MOD10, VAR2
        BCC     LP02
        ORR     VAR1, #1
        SUB     MOD10, MOD10, VAR2
LP02
        SUBS LC, LC, #1;
        BNE LP01

LOOP
        B        LOOP

        ENDP

 

Jeżeli chodzi o dzielenie, to ARM ma już własne rozkazy dzielenia dla 32 bitowych liczb, natomiast bardziej interesujące było to po prostu zrobienie tego "na piechotę, w sposób jaki rozszerzało się arytmetyką procesorów 8 bit. Do tego znalazłem 2 różne algorytmy dzielenia i musiałem sprawdzić, czy obydwa działają (tak - działają!). Bardzo pouczające i pomocne jest obserwowanie działania znaczników i zawartości poszczególnych rejestrów procesora - sprawdzałem to w KEIL ARM simulator. Poniżej widok debugu w KEIL:

debug.thumb.jpg.025eeda12625e3e85e41d68e2576fcca.jpg

  • Lubię! 1
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • 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.