STM32F407汇编,STR不写入内存

STM32F407 Assembly, STR not writing into memory

我正在尝试用纯汇编语言对我的 STM32F407 进行编程(学习目的)。 我已将调试范围缩小到一个奇怪的问题,即 STR 命令根本没有将寄存器的内容写入内存。代码如下。本质上它应该将 0x4 写入内存位置(设置 GPIO 端口),但没有。

代码如下:

    /*SCiFI statements*/
    .syntax unified
    .cpu cortex-m4
    /*.fpu fpv4-sp-d16*/
    .thumb

    /*Vector Table Symbols*/
    .global vtable
    .global reset_handler

    /*vtable setup*/
    .type vtable, %object
vtable:
    .word _estack
    .word reset_handler
    .size vtable, . - vtable

    /*reset handler setup*/
    .type reset_handler, %function
reset_handler:
    /*Stack Pointer reset*/
    LDR r0, =_estack
    MOV sp, r0

    /*GPIOC is at 0x4002 0800 - 0x4002 0BFF*/
    LDR r2, =0x40020800 /*Address register*/
    /*Set up GPIOC as output*/
    /*Set to GPIO output mode*/
    LDR r1, =0x04
    STR r1, [r2] /*******HERE'S THE ISSUE LINE*******/

dummy_loop:
    B dummy_loop
    .size reset_handler, . - reset_handler

完整的链接描述文件

estack = 0x2001c000; /*End of RAM*/

MEMORY{
    FLASH ( rx  ) : ORIGIN = 0x08000000, LENGTH = 1M
    SRAM  ( rxw ) : ORIGIN = 0x20000000, LENGTH = 112K
    GPIO  ( rw  ) : ORIGIN = 0x40020000, LENGTH = 36020400
}

正在用

编译它
arm-none-eabi-gcc -x assembler-with-cpp -c -O0 -mcpu=cortex-m4 -mthumb -Wall core.s -o core.o -g
arm-none-eabi-gcc core.o -mcpu=cortex-m4 -mthumb -Wall --specs=nosys.specs -nostdlib -lgcc -T./stm32f407.ld -o pintoggle.bin -g

其中 .s 和 .ld 文件是我在上面粘贴的文件。

怀疑者的相关 gdb 输出

(gdb) break 30
Breakpoint 1 at 0x8000012: file core.s, line 30.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) continue
Continuing.

Breakpoint 1, reset_handler () at core.s:30
30              STR r1, [r2]
(gdb) x 0x40020800
0x40020800:     0x00000000
(gdb) s
33              B dummy_loop
(gdb) x 0x40020800
0x40020800:     0x00000000
(gdb) info registers
r0             0x2001c000          536985600
r1             0x4                 4
r2             0x40020800          1073874944
r3             0x0                 0
r4             0x0                 0
r5             0x0                 0
r6             0x0                 0
r7             0x0                 0
r8             0x0                 0
r9             0x0                 0
r10            0x0                 0
r11            0x0                 0
r12            0x0                 0
sp             0x2001c000          0x2001c000
lr             0xffffffff          -1
pc             0x8000014           0x8000014 <reset_handler+12>
xpsr           0x41000000          1090519040
msp            0x2001c000          0x2001c000
psp            0x0                 0x0
control        0x0                 0 '[=13=]0'
faultmask      0x0                 0 '[=13=]0'
basepri        0x0                 0 '[=13=]0'
primask        0x0                 0 '[=13=]0'
fpscr          0x0                 0

我不确定我做错了什么。该程序不是特别复杂。操作 CPU-寄存器工作正常,但写入内存根本不起作用。

chip/board 很好。我通过 Keil 环境将一些旧的 C 代码放在上面,它运行得很好。我的目标是根本不使用任何 C,只坚持汇编和 gcc。这就是我要学习的。

编辑: 既然有人一定要运行问这个问题,问的就更多了。 https://www.efton.sk/STM32/gotcha/index.html 列出了一堆 STM32 陷阱,有趣的是这个问题排在第 1 位。

您需要先在 RCC 中启用 gpioc。

读取-修改-写入RCC_AHB1ENR 0x40023830 并设置第 2 位:

ldr r0, =0x40023830
ldr r1, [r0]
orr r1, #2
str r1, [r0]

那么您可以:

LDR r2, =0x40020800
LDR r1, =0x04
STR r1, [r2]

Note/FYI 由于竞争条件,这可能无法工作:

ldr r0, =0x40023830
ldr r1, [r0]
orr r1, #2
ldr r2, =0x40020800
ldr r3, =0x4
str r1, [r0]
str r3, [r2]

在启用和启用 gpio 的时钟之间需要一定数量的时钟,但这应该可行

ldr r0, =0x40023830
ldr r1, [r0]
orr r1, #2
ldr r2, =0x40020800
ldr r3, =0x4
str r1, [r0]
ldr r4, [r2]
str r3, [r2]

或者像上面那样按顺序做事(用一些指令准备写入)。

ldr r0, =0x40023830
ldr r1, [r0]
orr r1, #2
str r1, [r0]

LDR r2, =0x40020800
LDR r1, =0x04
STR r1, [r2]

只需使用 binutils,根本没有理由与 gcc 混淆。

flash.s

.thumb
.syntax unified

.global _start
_start:
.word 0x20001000
.word reset
.word loop
.word loop

.thumb_func
loop:   b .

.type reset, %function
reset:
    ldr r0, =0x40023830
    ldr r1, [r0]
    orr r1, #2
    str r1, [r0]

    LDR r2, =0x40020800
    LDR r1, =0x04
    STR r1, [r2]

    ldr r0, =0x40020818
    ldr r1, =0x00000002
    ldr r2, =0x00020000
loop_top:
    str r1,[r0]
    bl delay
    str r2,[r0]
    bl delay
    b loop_top

.thumb_func
delay:
    ldr r3,=0x200000
delay_loop:
    subs r3,#1
    bne delay_loop
    bx lr
    

flash.ld

MEMORY
{
    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text   : { *(.text*)   } > rom
    .bss    : { *(.bss*)    } > ram
}

构建:

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m4 flash.s -o flash.o
arm-none-eabi-ld -nostdlib -nostartfiles -T flash.ld flash.o -o flash.elf
arm-none-eabi-objdump -D flash.elf > flash.list
arm-none-eabi-objcopy -O binary flash.elf flash.bin

检查:

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000013    stmdaeq r0, {r0, r1, r4}
 8000008:   08000011    stmdaeq r0, {r0, r4}
 800000c:   08000011    stmdaeq r0, {r0, r4}

08000010 <loop>:
 8000010:   e7fe        b.n 8000010 <loop>

08000012 <reset>:
 8000012:   480d        ldr r0, [pc, #52]   ; (8000048 <delay_loop+0x8>)
 8000014:   6801        ldr r1, [r0, #0]
 8000016:   f041 0102   orr.w   r1, r1, #2
 800001a:   6001        str r1, [r0, #0]
 800001c:   4a0b        ldr r2, [pc, #44]   ; (800004c <delay_loop+0xc>)
 800001e:   f04f 0104   mov.w   r1, #4
 8000022:   6011        str r1, [r2, #0]
 8000024:   480a        ldr r0, [pc, #40]   ; (8000050 <delay_loop+0x10>)
 8000026:   f04f 0102   mov.w   r1, #2
 800002a:   f44f 3200   mov.w   r2, #131072 ; 0x20000

0800002e <loop_top>:
 800002e:   6001        str r1, [r0, #0]
 8000030:   f000 f804   bl  800003c <delay>
 8000034:   6002        str r2, [r0, #0]
 8000036:   f000 f801   bl  800003c <delay>
 800003a:   e7f8        b.n 800002e <loop_top>

0800003c <delay>:
 800003c:   f44f 1300   mov.w   r3, #2097152    ; 0x200000

08000040 <delay_loop>:
 8000040:   3b01        subs    r3, #1
 8000042:   d1fd        bne.n   8000040 <delay_loop>
 8000044:   4770        bx  lr
 8000046:   38300000    ldmdacc r0!, {} ; <UNPREDICTABLE>
 800004a:   08004002    stmdaeq r0, {r1, lr}
 800004e:   08184002    ldmdaeq r8, {r1, lr}
 8000052:   Address 0x0000000008000052 is out of bounds.

向量看起来不错,不会立即挂起

我非常讨厌统一的语法,当然是 binutils,但是写这个:

delay_loop:
    sub r3,#1
    bne delay_loop

没有 gnu 汇编器下的统一语法,实际上生成的是 subs 而不是 sub,这让人们感到困惑(他们发表评论)。所以我在上面使用了统一的语法。如果您刚开始,可能应该只启用统一语法并以这种方式学习,叹息。 (对于你现在所在的位置,只需在前面某处启用它并继续做你正在做的事情)。

我对 gdb 没有用,但也许它在那里也可以工作,但是如果你远程登录到 openocd,你当然可以这样做:要么通过重置进入无限循环来破坏你的代码,或者取决于调试工具只是重置而不是启动代码,但是您可以在调试接口上写入这些控制寄存器,并在编写代码以执行相同操作之前查看它们的工作情况。一个可以节省一些时间(或者可能需要更长的时间,取决于您的 coding/debugging 风格)。

mdw 0x40023830
mww 0x40023830 0x00000002 (need to read-modify-write any other ones in there from the prior read)
mdw 0x40020800
mww 0x40020800 0x4
mdw 0x40020800

你应该在那里看到 0x4。

然后弄乱0x40020818来改变输出引脚的状态。


DMA 与此处无关。你有处理器,然后是它的主要 ahb/etc 总线,它可能会通过内存控制器和其他总线,直到它到达处理该控制寄存器的逻辑。然后,该时钟的使能通过一定数量的门到达该逻辑块(在本例中为 gpioc)的时钟使能。可能至少需要一个时钟来锁存使能,然后可能至少需要另一个外围时钟周期来启用时钟门以允许时钟通过 gpio。现在这里的特定贡献者通常会在看到这样的代码时发表此评论。还有一些但不是全部的STM32文档专门告诉你要延迟多少us或者ms。

写入可以是即发即弃,地址和数据是事务的一部分,最靠近芯片的第一级内存控制器在技术上可以获取这两项并告诉处理器写入已完成(即使它不是)允许它做下一件事(例如另一个 STR)。时钟控制逻辑之类的东西和 GPIO 之类的外围设备可能位于也可能不位于同一组总线上,但最终它们会分开。

就像从你家寄两封信到同城的两个地址一样,你把地址写在信封上,投进信箱,寄到哪里,你就可以回去房子,做点别的。这两个人可能乘坐相同的卡车和飞机一路前往目的地城镇的相同 post 办公室,但最终会分开并走不同的路径,这可能需要不同的时间。

这称为竞争条件,它们非常真实,发生的次数比我们希望的要多,但这不是每次出现问题时都开始恐慌或担心的事情。通常,供应商会在文档或勘误表中指出存在竞争条件。像奥运会、马拉松、NASCAR 中的田径比赛一样,两件或两件以上的事情试图先到达终点线,或者在这种情况下是按顺序。

这就是为什么简单地删除某些代码中的 printf 可能会产生毁灭性的结果,因为 printf 会在前后的事情之间造成很大的延迟。删除延迟,可能会导致竞争。

这种特殊情况并不少见,你有一个控制块,比如时钟启用或控制地址解码的东西(假设你有处理器地址 space 的区域,你可以他们的设计指向某物,但如果你改变它指向的东西然后立即尝试谈论你刚刚告诉它指向的东西你可能有竞争条件)。

写入是即发即弃,但读取必须一直到外设再返回,所以最坏的情况是 time/path。现在我已经研究了读取路径和写入路径分开的逻辑,你也可以在那里进行比赛,但更多的是例外而不是规则。因此,例如,如果您写入一些控制寄存器,然后将其读回,人们会希望设计人员对此进行序列化,并且读取发生在写入之后,它会完成外围设备的完整行程并返回,所以当它返回时让处理器继续,该寄存器肯定已写入。

在这种情况下,您可能会延迟写入 rcc 以及可能不同的总线到达 rcc 与 gpio,从而导致可能的时序差异,从而允许这场比赛。

在你有的部分或其他stm32部分,你可以尝试上面提示的实验。使用 STR 执行 rcc 启用,然后下一条指令对模式执行 STR 以将引脚更改为输出,然后您可以花时间更改引脚的状态以说打开 LED。如果当您在商店之间延迟一定数量的时钟时它确实有效,但当它们背靠背时不起作用。给你。

最大的谜团是为什么读取工作,STR,LDR,STR 在我发现竞争条件的芯片上工作。那没有意义。下一个巨大的谜团是,如果您读取非零的现代寄存器,您可以很好地感觉到您实际上正在读取该寄存器(在这种情况下为 GPIOA 和 GPIOB 现代寄存器),并且在 rcc 中禁用了 gpio时钟使能。正确的值回来了。这对我来说似乎是一种绕过比赛条件的技巧。这很可能是他们有一些芯片的情况,他们有一个已经在那里使用的库,然后下一个芯片正在设计中并且不起作用,不会去强制更新 hal给大家这一部分的时候可以在设计中快速解决。

所以我只测试了几个不同的部分但是

STR (the write that enables the clock)
LDR (of the moder)
STR (of the moder)

的部分工作
STR (the write that enables the clock)
STR (of the moder)

失败。


DMA,直接内存访问。

因此,让我们考虑一下具有小缓冲区的 PWM 控制器或 UART,可能只有一个值用于 transaction/period 并且一个值位于传输缓冲区中等待下一个。作为程序员,您可能会根据设计有一些选择,有一个代码循环来轮询状态寄存器,等待表明正在传输保持寄存器值且现在为“空”的指示。然后你最好 want/need 在先前值完全传输之前或 pwm 的那个时间段或发生任何事情之前写入新值。

如果你想保持外围设备的输出为线速或没有间隙或重复或任何外围设备的输出,你需要消耗大量的处理时间。现在,如果您的应用程序没有做任何其他事情,我强烈反对您是否正在学习使用此外围设备,这就是您的起点。但是下一个选项可能是一个中断,你设置一个处理程序,当中断发生时,你从一个更大的缓冲区中拉出你正在维护的(在 ram 中)来提供这个外围设备,如果你能确保处理程序足够快并且没有其他更高优先级事情延迟处理程序启动(实时)然后这将起作用。

第三个并不总是可用的东西是 DMA。您以某种方式告诉外设或第三方 dma 控制器,这是我要发送的数据块,这是我要发送的数据,逻辑必须设计为具有触发 dma 的连接,导致一个或任意数量的项目移动到外围设备中。仍然可能存在导致延迟和竞争条件的争用,但如果您 want/need 每个周期都向外围设备提供一些东西,那么这是您最好的选择。

你听说过 dma 的一个相同的相似概念,人们也认为这个是免费的,因为它神奇地发生在后台并且不会影响处理器。说一个内存传输,设置一个 DMA 引擎,它可能是某些系统的一部分,以进行从一个地方到另一个地方的传输或数据,而不是必须做 memcpy 或其他类似的事情(通常用于移动一帧的事情)像素数据到视频卡或其他类似的东西,这些天视频卡为您生成像素做了很多工作)。有些系统是免费的,有些系统则不是,因为它使用相同的总线,所以如果总线正在被 dma 引擎使用,它会导致处理器不得不停止,这肯定会影响处理器。

我看到一些处理器在 dma 传输时完全 stopped/stalled,我看到一个浪费的总线是大约四分之三的东西,在任何时候都有一个时钟周期如果您碰巧想要进行传输,那么为 dma 预留的那么多,然后将使用 cycle/block 的时间。基本上你总是受到 dma 的影响,即使它没有发生(这是一个 DSP,其中执行一致性非常重要并且显然比尽可能快地运行代码更重要)。

DMA 用于很多事情,m 是错误的,它并不总是表示内存,它可能是从一个 fifo(技术上来说是一个 sram)到一些外围设备。尽管名称如此,它通常意味着另一个可以启动总线事务的总线控制器,这样您就不必通过代码通过主处理器创建这些事务。

老实说,您还没有准备好使用 DMA,也没有准备好中断。轮询你的方式,创建大量一次性代码,即使你正在为一个特定的项目工作。对于您追求的每个功能的每个外围设备,创建一个或多个临时应用程序并弄清楚它是如何工作的。如果你遇到一个神秘的情况,一切正常,你添加或删除了一行代码,而这行代码本应与它完全无关,然后事情就崩溃了,那么如果它是编译代码,那么质疑编译器的输出,检查它。

如果它是 asm(或高级),则可能是某种竞争条件(也可能是其他原因)。它们确实很少见,经常被记录(最终),但是当它们发生攻击时,它们可能需要很长时间才能弄清楚,有时您永远无法真正确定,但是添加延迟可以解决它,或者阅读三遍并取两个匹配的东西,或任何 hack,然后你继续生活。我只是警告你一些关于这些 STM32 设计的百分比的非常真实的事情。这可能会导致您像最初将您带到这里一样头疼。