Beginner Question - Assembly (ARMv7): Small Code Analysis with questions

86 Views Asked by At

STM32F102C8T6 is programmed with Assembly-Code.

The on-board LED should "blink", that is not the case.

Please analyse my code-sample.

I need explanations to some code-parts:

(1) "MOVW"-use in right context of 32-bit code?

(2) Part of declaration with text like ~(1 << 20), possible?

(3) Right use of not global-declared sub-functions?


  .syntax unified

  .text
  .global ASM_SystemInit
  .global ASM_Function
  .thumb_func

  .equ  RCC_BASE,  0x40021000
  .equ  RCC_APB2_ENR,   0x40021018       // STM32f103C8T6 - Adresse von RCC_APB2ENR - 0x18

  .equ  GPIOC_BASE, 0x40011000      // GPIOC-Basisadresse nach "memory-map", Datenblatt
  .equ  GPIOC_CRH,  0x40011004      // GPIOC-BASE + 0x04 ist das Konfigurationsregister
  .equ  GPIOC_ODR, 0x4001100C       // GPIOC_BASE + 0x0C ist das Ausgaberegister (Rerenz-Handbuch, Seite 194)

  .equ  GPIOCEN, 1 << 4         // Seite 141, Referenz-Dokumentation, eventuell Wert 4, prüfen !!!

  .equ  CRH_OUT_1,  ~(1 << 20)      // Konfiguration GPIOC, Setzen der vier Konfigurations-Bits nach Datenblatt, Seite 161, Bit-Sequenz: 0010
  .equ  CRH_OUT_2,  ~(1 << 21)
  .equ  CRH_OUT_3,  1 << 22
  .equ  CRH_OUT_4,  ~(1 << 23)

  .equ  LED_BLUE,   1 << 13     // PC13

  .equ  COUNTER,        10000
//------------------------------------------------------------------------------------------------
  ASM_SystemInit:

  // Setzen der Uhr im Programm
  LDR   R0,=RCC_BASE
  LDR   R1,[R0]
  ORR   R1,GPIOCEN
  STR   R1,[R0]         // Absatz beschreibt das Freischalten der Uhr für Port C

  LDR   R0,=GPIOC_CRH
  LDR   R1,=(CRH_OUT_4|CRH_OUT_3|CRH_OUT_2|CRH_OUT_1)
  STR   R1,[R0]
  MOV   R1,#0

  LDR   R2,=GPIOC_ODR   // Absatz beschreibt Konfiguration von PC13 als Ouput


  ASM_Function:

  Blink:

    MOVW    R1,=#LED_BLUE
    STR R1,[R2]
    LDR R3,=#COUNTER
    BL delay1

  delay1:
  SUBS R3,R3,#1
  BNE delay1

  B Blink

I tried the code sample on a ARMv7, 32-bit architecture, stm32f103C8T6

1

There are 1 best solutions below

8
old_timer On
.thumb
.equ  CRH_OUT_1,  ~(1 << 20)      // Konfiguration GPIOC, Setzen der vier Konfigurations-Bits nach Datenblatt, Seite 161, Bit-Sequenz: 0010
.equ  CRH_OUT_2,  ~(1 << 21)
.equ  CRH_OUT_3,  1 << 22
.equ  CRH_OUT_4,  ~(1 << 23)

LDR   R1,=(CRH_OUT_4|CRH_OUT_3|CRH_OUT_2|CRH_OUT_1)

I can see two immediate problems. The first is as expected:

Disassembly of section .text:

00000000 <.text>:
   0:   f04f 31ff   mov.w   r1, #4294967295 ; 0xffffffff

You asked it to be all ones and it is all ones. Which makes all the ports alternate function open drain.

Second is that you wanted 0010 for output max speed 2Mhz push pull which that is the correct pattern but if you were to use this then it is wrong.

.equ  CRH_OUT_1,  ~(1 << 20)
.equ  CRH_OUT_2,  ~(1 << 21)
.equ  CRH_OUT_3,  1 << 22
.equ  CRH_OUT_4,  ~(1 << 23)

that is 0100

.equ  CRH_OUT_1,  ~(1 << 20)
.equ  CRH_OUT_2,  1 << 21
.equ  CRH_OUT_3,  ~(1 << 22)
.equ  CRH_OUT_4,  ~(1 << 23)

this is 0010.

You should read-modify-write and outside the register, all too often in C folks do the clear and set steps against the register, which can cause problems or glitches...

Let's try:

.equ  CRH_OUT_MASK,  ~((1 << 23)|(1 << 22)|(0 << 21)|(1 << 20))
.equ  CRH_OUT_SET,    ((0 << 23)|(0 << 22)|(1 << 21)|(0 << 20))

LDR   R0,=GPIOC_CRH
LDR  R1,[R0]
LDR R2,=CRH_OUT_MASK
AND R1,R2
LDR R2,=CRH_OUT_SET
ORR R1,R2
STR R1,[R0]

That looks good:

   0:   4804        ldr r0, [pc, #16]   ; (14 <.text+0x14>)
   2:   6801        ldr r1, [r0, #0]
   4:   f46f 0250   mvn.w   r2, #13631488   ; 0xd00000
   8:   4011        ands    r1, r2
   a:   f44f 1200   mov.w   r2, #2097152    ; 0x200000
   e:   4311        orrs    r1, r2
  10:   6001        str r1, [r0, #0]

another way perhaps for readability is:

.equ  CRH_OUT_MASK,  ~(0xF<<((13-8)<<2))
.equ  CRH_OUT_SET,    (0x2<<((13-8)<<2))

LDR   R0,=GPIOC_CRH
LDR  R1,[R0]
LDR R2,=CRH_OUT_MASK
AND R1,R2
LDR R2,=CRH_OUT_SET
ORR R1,R2
STR R1,[R0]

That way you can see this is pin 13, the register starts at pin 8 (config register HIGH) and there are 4 bits per pin, so 13-8 times 4. And you want to generically just zero all four bits and then set them to the pattern you want, no games easy to read.

Next:

MOVW    R1,=#LED_BLUE
LDR R3,=#COUNTER

So you should make up your mind =# does not apply to either of these use cases, for the former it is #number the latter it is =address where the address can be a constant...

You already know how to do the ldr rd,=address trick so just use that, the gnu tools 1) support this syntax (assembly is specific to the tool not the target) 2) find the optimal instruction. Other assemblers do not necessarily do either of those things and you get into a game of figuring it out yourself or just generating a pool entry and a pc relative load.

.equ  LED_BLUE,   1 << 13 
.equ  COUNTER,        10000

LDR R1,=LED_BLUE
LDR R3,=COUNTER


   0:   f44f 5100   mov.w   r1, #8192   ; 0x2000
   4:   f242 7310   movw    r3, #10000  ; 0x2710

And yes if you take this approach then you can put any (32 bit) value in the .equ and it will work:

movw r0,1<<0
movw r0,1<<1
movw r0,1<<15
movw r0,1<<16
movw r0,1<<17
movw r0,1<<30
movw r0,1<<31


so.s: Assembler messages:
so.s:8: Error: immediate value out of range -- `movw r0,1<<16'
so.s:9: Error: immediate value out of range -- `movw r0,1<<17'
so.s:10: Error: immediate value out of range -- `movw r0,1<<30'
so.s:11: Error: immediate value out of range -- `movw r0,1<<31'

This cortex-m3 is armv7-m NOT armv7, huge difference. You should not be doing any assembly language without the cortex-m3 technical reference manual from arm, not a generic cortex-m one but the pdf specific (not online) to cortex-m3 it is called specifically the technical reference manual. (The docs from st tell you this part uses an arm cortex-m3)(the reference manual for this part rm00008 from st is a must have as well as often the datasheet helps but the reference manual is the key document, not the programmers manual, in neither case do you necessarily want the programmers manual as it is as confusing as it helps).

And the cortex-m3 TRM tells you this is architecture armv7-m so you need the Architectural Reference Manual for armv7-m again not armv7 and not some online generic thing specifically the armv7-m architectural reference manual, pdf. In that document you will see the instruction set details and see the rules for movw.

Now the syntax in the ARM ARM is for ARMs tools, if you are using gnu which I suspect (you are not using arms tools) then the syntax can vary from the document to the assembly language you need for gnu and you would ideally want gnu documentation but that is...a nightmare...if you can navigate it and get certain nuggets. While using gnu is going to have the best success and support even if the output is medium quality, because of the sheer volume of users, but most are not bare metal. and most are not arm although many are.

So for syntax things you kinda need to look at the gazillion examples out there, more than you can ever read in your lifetime. And pull clues, or just search so as most syntax questions have been answered. movw deals with the lower 16 bits, if you want to do all 32 bits with the armv7-m level of thumb 2 extensions it takes two instructions if I remember, I gend to write for armv6-m unless I have to (if you want to mess with cortex-ms going forward you need the armv6-m as well which applies to the cortex-m0 and m0+ and some flavors of armv8-m (baseline? mainline is armv7-m?)).

If you go backward to the original thumb, which you can find in the thumb part of the armv5 architectural reference manual that covered armv4t and armv5t and some of armv6 (not the cortex-m) the older manuals have easier to read pseudo code btw. you can build basically for all thumb variants and it will run on all cortex-ms or you can build for cortex-m0 and it will run on all cortex-ms (if you get into the habit of movw code will fail or hang on a cortex-m0 and some m8s)

  Blink:

    MOVW    R1,=#LED_BLUE
    STR R1,[R2]
    LDR R3,=#COUNTER
    BL delay1

  delay1:
  SUBS R3,R3,#1
  BNE delay1

  B Blink

So, make up your mind here: you either want to call delay as a function or just have a loop with a delay.

Make it a loop:

  Blink:

    MOVW    R1,=#LED_BLUE
    STR R1,[R2]
    LDR R3,=#COUNTER

    delay1:
    SUBS R3,R3,#1
    BNE delay1

    B Blink


  Blink:

    MOVW    R1,=#LED_BLUE
    STR R1,[R2]
    LDR R3,=#COUNTER
    BL delay1
    B Blink

  delay1:
  SUBS R3,R3,#1
  BNE delay1
  BX LR

It is a loop either way but if you want to call a function then return from the function and put the loop in the right place.

Also this will not make it blink you are simply setting the pin high which depending on the wiring on the pcb will either make the led always on or always off, if you want it to blink then you need to turn it on and then off and then on and then off... or you need toggle.

You can certainly use odr:

  Blink:

    MOVW    R1,=#LED_BLUE
    STR R1,[R2]
    LDR R3,=#COUNTER
    BL delay1
    MOVW    R1,=0
    STR R1,[R2]
    LDR R3,=#COUNTER
    BL delay1
    B Blink

Or you can use bsrr, and maybe even get a little tricky. I am not running this on a board nor in this case building it so if I have typos you have to debug them:

.equ  GPIOC_BSRR, 0x40011010 
.equ  P13_SET   , (1<<(13+ 0))
.equ  P13_CLR   , (1<<(13+16))
.equ  P13_TOGGLE, (P13_SET|P13_CLR)

ldr r2,=GPIOC_BSRR
ldr r4,=P13_CLR
ldr r5,=P13_TOGGLE

Blink:
    EOR R4,R5
    STR R4,[R2]
    LDR R3,=COUNTER
    BL delay1
    B Blink

delay1:
SUBS R3,R3,#1
BNE delay1