Skocz do zawartości

ARM MIKSOWANIE C z ASM - własny sprintf()


virtualny

Pomocna odpowiedź

Podoba mi się! Może kiedyś wreszcie sam sięgnę po pisanie w assemblerze - na razie tylko czytam.

Jedna uwaga, aby odwrócić kolejność bajtów nie potrzebujesz drugiego bufora, a tylko jeden dodatkowy bajt. Wymieniasz pierwszy z ostatnim, drugi z przedostatnim itd... Choć przy małym buforze dodatkowy kod może być dłuższy od zysku z pozbycia się jednego bufora. A może druga funkcja odwracająca ciąg "w miejscu"?

Link do komentarza
Share on other sites

38 minut temu, bjrk napisał:

tylko jeden dodatkowy bajt.

Da się i bez niego, podobno procek ma jakieś rejestry... a nawet bez nich się da. Proponuję spróbować - to może być ciekawe zadanie!

Poza tym można jeszcze inaczej: umieszczać znaki w buforze od ostatniej pozycji, a po zakończeniu przesunąć zawartość bufora na właściwe miejsce.

A tak dla formalności: ta funkcja nazywa się "utoa" a nie "sprintf". Również potrzebna, ale robi zupełnie coś innego. Pisanie sprintfa w asemblerze pozostawmy masochistom, ale utoa i itoa (mam nadzieję że będzie następna?) jak najbardziej mogą się przydać.

Link do komentarza
Share on other sites

Dzięki za reakcję.

Dwa bufory VS 1 bufor - bardziej fascynowało mnie ustawianie zmiennych w porządku jakim ja chcę, a nie linker czy kompilator...

"utoa" unsigned to ASCII? racja - to mój skrót myślowy, kiedy wykonywałem "sprintf(&buffor, %d, x)" 🙂

Następne itoa? Myślałem że następne ma być odwrotne działanie, żeby string decymalny skonwertować do uint32_t... Sklecenie itoa na bazie tego, co jest powinno być proste - zobaczymy...

Napisałem wersję z jednym buforem i nie było to wcale takie proste - myślę że bjrk nie zastanawiał się jakie implikacje przy takim przepisywaniu rodzi fakt stringu o długości parzystej i nieparzystej.

 

Program w wersji dla leniwych pozwala konwertować zadane wartości poprzez wpisywanie ich bezpośrednio do zmiennej za pomocą STM32 ST-LINK Utility.

01.thumb.jpg.3a6fea5b53dd62bb28fea3bebe35cc26.jpg

02.thumb.jpg.3a3332daaccd35035961b8f71e4dbd32.jpg

 

Plik main.c wygląda obecnie tak:

/**
 ******************************************************************************
 * @file           : main.c
 ******************************************************************************
 */

#include <stdint.h>



extern int my_sprintf(char* out_buff, uint32_t num);

__attribute__ ((section(".outBuffer"), used)) char OUT_BUFFER[11];     // at 0x20000000

__attribute__ ((section(".Var_value"),  used)) uint32_t value;         // at 0x20000010
__attribute__ ((section(".Var_length"), used)) uint32_t datalength;    // at 0x20000014

int main(void)
{
    uint32_t i;
    value = 0xc000;


    while(1){
        datalength = my_sprintf(OUT_BUFFER, value);
        while(i == value);
        i = value;
    }
}
//==========================================================================

 

Sekcja w skrypcie dla linkera:

//======================================
  .my_Buffers(NOLOAD) :
  {
    . = ALIGN(1);
    . = ABSOLUTE (0x20000000);
    *(.outBuffer)
   
    . = ABSOLUTE (0x20000010);
    *(.Var_value)
    . = ABSOLUTE (0x20000014);
    *(.Var_length)

  } >RAM
//==================================

I funkcja w ASM:


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

    .thumb
    .thumb_func
    .syntax unified
    .global my_sprintf
    .type   my_sprintf, %function

    .text
    .align    2

//========================================================
// DECIMAL CONVERSION OF ANY UNSIGNED VALUE UP TO 32 BIT
//========================================================
       // INPUT:
       //    R0, =  ; POINTER TO OUTPUT char DATA BUFFER
       //    R1, =  ; VALUE TO DECIMAL CONVERT


       // OUTPUT: R0 = LENGTH OF PRODUCED BYTES OF STRING
//========================================================
my_sprintf:

        PUSH {R1-R5}  // store registers


        MOV   R5, #0  // LENGTH OF STRING
CALC01:
        //------------------
        //- R4 = R1 DIV 10 -
        //------------------
        LDR   R3, = 0xCCCCCCCD // MAGIC VALUE (!!!)
        UMULL R4,R3,R3,R1      // this three lines looks a bit strange
        LSR   R4,R3, #3        // but here we gotta divide by 10 (seriously!)
        //----------------------------------------------
        MOV   R2, R4         // R2 = R1 div by 10 without the rest
        MOV   R3, #0x0A      // R3 = 10
        MUL   R4, R4, R3     // R4 = R4 * 10

        // mod 10 calculate
        // R4 = R1 mod 10
        SUB   R4, R1, R4     // CALCULATE THE REST r4 = r1 - r4

        ORR   R4, R4, #0x30  // CHANGE TO ASCII NUMBER VALUE "0..9"
        STRB  R4, [R0,R5]    // store next decimal row
        ADD   R5, R5, #1     // R5 = length of string
        MOV   R1, R2         // R1 = before stored value in R2 = (R1 div 10)
        CMP   R1, #0         // R1 = 0? (that was last one operation?)
        BNE   CALC01         // if R1 != 0 then continue CALC01 loop

        // R1 = 0            // R1 = 0, R5 = length of produced string
        STRB  R1, [R0, R5]   // FIRST TIME R1 = 0 SO NULL TERMINATED STRING MAKE NULL
        PUSH  {R5}           // TEMPRARY STORE LENGTH OF STRING
        SUB   R5, R5, #1     // SET OFFSET AT THE END OF STRING (BACKWARD POSSITION)

        // R0 = POINTER TO OUTPUT NULL TERMINATED STRING
        // R5 = OFFSET TO THE END OF STRING (BACKWARD POSSITION)
        // R1 = OFFSET TO THE START OF STRING (FORWARD POSSITION) R1 = 0 AT THE END CALC01 ROUTINE
        // R4 = BACKWARD BYTE (BYTE FROM "RIGHT SIDE")
        // R2 = FORWARD  BYTE (BYTE FROM "LEFT  SIDE")
CALC02:
        LDRB  R4, [R0, R5] // GET DATA FROM THE END (FROM RIGT SIDE)
        LDRB  R2, [R0, R1] // GET DATA FROM THE START (LEFT SIDE)
        STRB  R2, [R0, R5] // GET DATA FROM THE "LEFT  SIDE INTO THE RIGHT SIDE"
        STRB  R4, [R0, R1] // GET DATA FROM THE "RIGHT SIDE INTO THE LEFT  SIDE"
        ADD   R1, R1, #1   // ACTUALIZE STRING FORWARD POSSITION
        SUB   R5, R5, #1   // ACTUALIZE STRING BACKWARD POSSITION
        CMP   R5, R1       // R5 =< R1 ?
        BEQ   END_CALC     // if R5 = R1 go to finish
        BGT   CALC02       // if R5 > R1 continue loop - otherway finish (R5 < R1)

END_CALC:
        // acording declaration this functin is "int" and
        // acordind AAPCS should be returned int value in R0
        // simple "return datalength;" in C
        POP   {R0}         // OUTPUT: R0 = LENGTH OF PRODUCED BYTES OF STRING R0 = R5

        POP {R1-R5}        // restore registers

        BX LR  // ADIOS :)

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

 

BIN_C_MIXED_ASM.zip C_MIXED_ASM.ZIP

  • Lubię! 1
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

Nie widziałem trudności, bo gdy nieparzysta długość, to centralny bajt już jest na miejscu, a ostatni (pusty) bajt jest zawsze na miejscu i można go zignorować przy odwracaniu.

Edytowano przez bjrk
Uzupełnienie
Link do komentarza
Share on other sites

23 minuty temu, virtualny napisał:

"utoa" unsigned to ASCII?

Dokładnie tak - z tą różnicą że przyjmujesz radix == 10.

A dlaczego itoa? Bo kod będzie podobny, a nawet możesz wykorzystać funkcję utoa (nawet niekoniecznie przez wywołanie a po prostu skok do jakiegoś punktu wewnątrz funkcji.

Komplementarna para (atou/atoi) to jakby następny krok, i podobnie funkcja atoi może wykorzystać kod atou.

 

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

my_itoa został dopisany. Niewiele trzeba było do szczęścia. Plik main.c wygląda tak:

#include <stdint.h>

extern int my_itoa(char* out_buff, int32_t num);
extern int my_sprintf(char* out_buff, uint32_t num);

__attribute__ ((section(".outBuffer"), used)) char OUT_BUFFER[12];     // at 0x20000000

__attribute__ ((section(".Var_length"), used)) uint32_t datalength;    // at 0x2000000C
__attribute__ ((section(".Var_value"),  used)) uint32_t value;         // at 0x20000010

int main(void)
{
    uint32_t i;
    int x = 0x80000000;
    value = (uint32_t) x;

    while(1){
        datalength = my_itoa(OUT_BUFFER, (int32_t) value);
        while(i == value);
        i = value;
    }
}

Plik assemblera:

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

    .thumb
    .thumb_func
    .syntax unified
    .global my_sprintf
    .global my_itoa
    .type   my_sprintf, %function
    .type   my_itoa, %function

    .text
    .align    2

//========================================================
// DECIMAL CONVERSION OF ANY UNSIGNED VALUE UP TO 32 BIT
//========================================================
       // INPUT:
       //    R0, =  ; POINTER TO OUTPUT char DATA BUFFER
       //    R1, =  ; VALUE TO DECIMAL CONVERT


       // OUTPUT: R0 = LENGTH OF PRODUCED BYTES OF STRING
//========================================================
my_sprintf:

        PUSH {R1-R5}  // store registers


        MOV   R5, #0  // LENGTH OF STRING
CALC01:
        //------------------
        //- R4 = R1 DIV 10 -
        //------------------
        LDR   R3, = 0xCCCCCCCD // MAGIC VALUE (!!!)
        UMULL R4,R3,R3,R1      // this three lines looks a bit strange
        LSR   R4,R3, #3        // but here we gotta divide by 10 (seriously!)
        //----------------------------------------------
        MOV   R2, R4         // R2 = R1 div by 10 without the rest
        MOV   R3, #0x0A      // R3 = 10
        MUL   R4, R4, R3     // R4 = R4 * 10

        // mod 10 calculate
        // R4 = R1 mod 10
        SUB   R4, R1, R4     // CALCULATE THE REST r4 = r1 - r4

        ORR   R4, R4, #0x30  // CHANGE TO ASCII NUMBER VALUE "0..9"
        STRB  R4, [R0,R5]    // store next decimal row
        ADD   R5, R5, #1     // R5 = length of string
        MOV   R1, R2         // R1 = before stored value in R2 = (R1 div 10)
        CMP   R1, #0         // R1 = 0? (that was last one operation?)
        BNE   CALC01         // if R1 != 0 then continue CALC01 loop

        // R1 = 0            // R1 = 0, R5 = length of produced string
        STRB  R1, [R0, R5]   // FIRST TIME R1 = 0 SO NULL TERMINATED STRING MAKE NULL
        PUSH  {R5}           // TEMPRARY STORE LENGTH OF STRING
        SUB   R5, R5, #1     // SET OFFSET AT THE END OF STRING (BACKWARD POSSITION)

        // R0 = POINTER TO OUTPUT NULL TERMINATED STRING
        // R5 = OFFSET TO THE END OF STRING (BACKWARD POSSITION)
        // R1 = OFFSET TO THE START OF STRING (FORWARD POSSITION) R1 = 0 AT THE END CALC01 ROUTINE
        // R4 = BACKWARD BYTE (BYTE FROM "RIGHT SIDE")
        // R2 = FORWARD  BYTE (BYTE FROM "LEFT  SIDE")
CALC02:
        LDRB  R4, [R0, R5] // GET DATA FROM THE END (FROM RIGT SIDE)
        LDRB  R2, [R0, R1] // GET DATA FROM THE START (LEFT SIDE)
        STRB  R2, [R0, R5] // GET DATA FROM THE "LEFT  SIDE INTO THE RIGHT SIDE"
        STRB  R4, [R0, R1] // GET DATA FROM THE "RIGHT SIDE INTO THE LEFT  SIDE"
        ADD   R1, R1, #1   // ACTUALIZE STRING FORWARD POSSITION
        SUB   R5, R5, #1   // ACTUALIZE STRING BACKWARD POSSITION
        CMP   R5, R1       // R5 =< R1 ?
        BEQ   END_CALC     // if R5 = R1 go to finish
        BGT   CALC02       // if R5 > R1 continue loop - otherway finish (R5 < R1)

END_CALC:
        // acording declaration this functin is "int" and
        // acordind AAPCS should be returned int value in R0
        // simple "return datalength;" in C
        POP   {R0}         // OUTPUT: R0 = LENGTH OF PRODUCED BYTES OF STRING R0 = R5

        POP {R1-R5}        // restore registers

        BX LR  // ADIOS :)

//=====================================================
//========================================================
// DECIMAL CONVERSION OF SIGNED 32 BIT VALUE
//========================================================
       // INPUT:
       //    R0, =  ; POINTER TO OUTPUT char DATA BUFFER
       //    R1, =  ; VALUE TO DECIMAL CONVERT


       // OUTPUT: R0 = LENGTH OF PRODUCED BYTES OF STRING
//========================================================
my_itoa:
      CMP   R1, 0       // IS OUR VALUE NEGATIVE?
      BGE   my_sprintf  // IF IS POSSITIVE GO TO my_sprintf
      PUSH  {LR}        // PC address go back to the main function
      PUSH  {R1}        // save our value for sign print to buffer
      MOV   R1, '-'
      STRB  R1, [R0]    // STORE "-" SIGN AT THE START TEXT BUFFER
      ADD   R0, R0, #1  // MOVE UP 1 BYTE POINTER TO OUTBUFFER
      POP   {R1}
      NEG   R1, R1      // CHANGE VALUE TO POSSITIVE NUMBER
      BL    my_sprintf  // CONVERT VALUE TO DECIMAL STRING
                        // now in R0 is datalength from my_sprintf
      ADD   R0, R0, #1  // INCREASSE LENGTH OF STRING MORE 1 BYTE OF SIGN "-"
      POP   {PC}        // GO BACK TO THE MAIN FUNCTION
//======================================================

A tak wygląda działanie programu s ST-Link Utility (można zmieniać wartość do konwersji pod adresem 0x20000010):

01.thumb.jpg.170b9883293fbc023a045dcc3f6f5f5f.jpg

Wartość długości stringu została przeniesiona pod adres 0x2000000c

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

No to teraz funkcje komplementarne - atoX 🙂

A potem może wprowadzenie radix? Nie wiem jak w Twoich zastosowaniach, ale ostatnio musiałem zastosować ósemkową notację na wyjściu (tworzenie stringa rozumianego przez kompilator C). Co prawda dziś robię takie rzeczy w Pythonie, ale umiejętność zrobienia tego na najniższym poziomie czasem się przydaje... 

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

atoX - równie prosty algorytm odwrotny do utoa: "mnóż rezultat przez 10 i dodawaj kolejną liczbę ze stringu" - tak w skrócie.

plik main.c:

#include <stdint.h>

extern int my_itoa(char* out_buff, int32_t num);
extern int my_sprintf(char* out_buff, uint32_t num);
extern int my_atoX(char* out_buff);
__attribute__ ((section(".outBuffer"), used)) char OUT_BUFFER[12];     // at 0x20000000
__attribute__ ((section(".Var_length"), used)) uint32_t datalength;    // at 0x2000000C
__attribute__ ((section(".Var_value"),  used)) uint32_t value;         // at 0x20000010
__attribute__ ((section(".Var_CMP"),  used))    int32_t compared;      // at 0x20000014

int main(void)
{
    uint32_t i;
    int x = 0x80000000;
    value = (uint32_t) x;

    while(1){
        datalength = my_itoa(OUT_BUFFER, (int32_t) value); // convert signed int 32 to ASCII
        compared = my_atoX(OUT_BUFFER);                    // convert ASCII NULL terminated string to 32bit integer
        while(i == value);
        i = value;
    }
}

 

funkcja atoX:

//========================================================
//  CONVERSION string to 32bit integer
//========================================================
       // INPUT:
       //    R0, =  ; POINTER TO OUTPUT char DATA BUFFER

       // OUTPUT: R0 = calculated integer
//========================================================
my_atoX:
      PUSH  {R1-R5}        // SAVE USED REGISTER

      LDRB  R1, [R0]       // GET THE FIRS CHAR - IS NEGATIVE SIGN?
      CMP   R1, 0x2D
      BNE   POSSITIVE

      ADD   R0,R0, #1      // MOVE UP 1 BYTE POINTER TO OUTBUFFER

POSSITIVE:

      MOV    R2, #0        // RESULT VALUE OF CONVERSION
      MOV    R3, #0        // OFFSET IN BUFFER
      MOV    R5, #0x0A

CONVERT_X:
      LDRB   R4, [R0, R3]  // LOAD ONE BYTE OF STRING
      CMP    R4, #0
      BEQ    END_ATOX      // IS THE END OF STRING?
      ANDS   R4, 0x0F      // UPDATE FLAGS AND REMOVE ASCII NUMBER 0x30..0x39
      MUL    R2, R2, R5    // R2 = R2 * 10
      ADD    R2, R2, R4    // ADD NEXT DECIMAL COLUMN
      ADD    R3, R3, #1    // OFFSET TO NEXT CHAR
      CMP    R3, #14       // IS ZERO NULL TERMINATED STRING?
      BNE    CONVERT_X     // CONTINUE CONVERT LOOP  IF NO REACHED R3 UP TO 14

CONVERT_ERROR:             // TO MANY CHAR - WE GOTTA AN STRING TERMINATE ERROR
      MOV    R0, 0xFFFFFFFF
      B      NO_NEG        // AN ERROR VALUE LOAD TO R0 ... NEVERMIND :)
END_ATOX:
      MOV    R0, R2        // RETURN OUR CALCULATED CONVERSION VALUE
      CMP    R1, #0x2D     // WAS HERE NEGATIVE SIGN?
      BNE    NO_NEG
      NEG    R0, R0        // IF SIGN WAS NEGATIVE SO DO NEG OUR VALUE
NO_NEG:
      POP    {R1-R5}       // RESTORE USED REGISTERS
      BX     LR  // GO BACK
//======================================================

Program konwertuje integera ze znakiem do stringa, a następnie wywołuje funkcję my_atoX, która na odwrót konwertuje string do binarnej liczby 32 bitowej. Tak wygląda zabawa przez ST-LINK'a:

01.thumb.jpg.67890959a25b766bb7e5e7340b4f8386.jpg

 

W załączeniu program i osobno sama binka do wgrania.

Ufff... eot

 

 

BIN_C_MIXED_ASM.zip C_MIXED_ASM.ZIP

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