【问题标题】:stm32f103 memory addressingstm32f103 内存寻址
【发布时间】:2017-02-23 17:32:23
【问题描述】:

我已经使用足够的启动文件在 keil 中为 stm32f103c8t6 板编写了代码。我使用数据表中的信息直接写入内存地址。但 st-link 上传似乎将十六进制文件上传到磨损地址,例如: GPIOA = 0X4001 0800 这是根据 stm 数据表。 但 stlink 显示设备数据范围从 0x0800 0000 到 0x0800 03d4。 我的代码是一个简单的程序,可以让 portA1 的 LED 闪烁。我将 LED 连接到一个 10k 电阻上。当我使用指针指定内存位置时,为什么 stm 分配了错误的地址,或者可能有任何其他错误。代码如下。

void delay(int a);

int main(void)
{
unsigned int* GPIO_A;
GPIO_A = (unsigned int*)0x40010800 ; // Assigning GPIOA to the correct   memory location

unsigned int* GPIO_A_CRL;
GPIO_A_CRL = GPIO_A + 0x00 ; // Assigning GPIO_A_CRL to the correct memory location

/*unsigned int* GPIO_A_IDR;
GPIO_A_IDR = GPIO_A + 0X08 ; // Assigning GPIO_A_IDR to the correct memory location  */

unsigned int* GPIO_A_BSRR;
GPIO_A_BSRR = GPIO_A + 0X10 ; // Assigning GPIO_A_BSRR to the correct memory location

unsigned int* GPIO_A_BRR;
GPIO_A_BRR = GPIO_A + 0X14 ; // Assigning GPIO_A_BSRR to the correct memory   location

 unsigned int* RCC_APB2ENR;
 RCC_APB2ENR = (unsigned int*)(0x40021000 + 0X18) ; // Assigning        RCC_APB2ENR to the correct memory location

*RCC_APB2ENR = 0X04; // Set clock for GPIOA

*GPIO_A_CRL = 0X00008888 ; // Defining pin modes for GPIO_A_CRL

while(1)  // infinite loop
{
    *GPIO_A_BSRR = 0X00000002;  // Set bit 1 to 1
    delay(2); // delay
    *GPIO_A_BRR = 0x00000002;  // reset bit 1 to reset value(0)
    delay(2); // delay
}

}
void delay(int a)
{
long b = a*1000000;
for(int i=0;i<b;i++)
   {
    int c=1;
   }
}

【问题讨论】:

  • 我建议你把你的问题放在electronics.stackexchange.com
  • 我有很多不使用库github.com/dwelch67/stm32_samples/tree/master/STM32F103C8T6 直接访问寄存器的芯片示例,包括如何使用串行引导加载程序或stlink 加载。
  • 您是否能够使用您拥有的任何其他示例的工具对这部分进行编程?我想当我第一次得到这些部件时,我必须解锁闪光灯,然后一旦完成,它们就更容易编程了。
  • 请注意,如果您进行优化,您的代码中需要大量的 volatile 才能使其正常工作,不要认为这是您的问题。
  • 我想以艰苦的方式学习。而且我发现直接访问内存位置更容易

标签: embedded stm32


【解决方案1】:

编译器/链接器没有错误地分配地址;您只是误解了内存映射以及编译器和链接器(甚至处理器)的工作方式。

0x08000000 到 0x080003d4 是您的代码的位置; 0X40010800 是 GPIOA 内存映射寄存器的地址。

STM32上的0x0800000是片上闪存的起始地址。当处理器退出复位时,它从 0x08000000 加载堆栈指针寄存器,从 0x08000004 加载程序计数器寄存器。上面是interrupt vector table,上面是你的代码——或者更确切地说是编译器从你的源代码生成的机器代码。您提到的启动文件定义了提到的复位和中断向量。

指针GPIO_A 是在运行时在你的代码中分配的,指针变量的位置将在RAM中,尽管你初始化它并且从不修改它编译器可能会优化以将地址存储在 ROM 中或用内联文字替换它。

与其定义您自己的寄存器地址,不如使用供应商提供的处理器头文件(本例中为 stm32f10xx.h)更简单、更安全。当您为特定部件配置项目时,Keil 工具链包含此文件。它还包含 STM32 标准外设库(或从 here 下载),它简化了低级外设访问,并包含大量外设 I/O 示例,包括 GPIO。

有关 STM32F1xx 的完整程序员信息,仅依靠数据表是不够的——它只是告诉您部件的具体特性;您应该使用更全面的Reference Manual

【讨论】:

  • 是的,我已经阅读了它并且我现在明白了,但是代码仍然无法正常工作。我在调试时访问目标资源时出错,非法地址 0x08000000
  • 另外,我知道标题,但我想通过艰苦的方式学习它,因为它是一个简单的项目
  • @ched :学习寄存器的地址不是“硬方法”,而是毫无意义的方法。您使用的下一个处理器将完全不同。此外,使用标准寄存器名称将使您的代码更具可移植性,并允许您更轻松地使用第三方代码。有等级;低级寄存器定义、标准外设库和 STM32Cube 框架。使用低级寄存器定义不是“简单的方法”,它只是在做你所做的,只是更全面、更准确。
  • 关于连接调试器的错误;这是一个不同的问题;发布一个新的。在 Keil 中,您需要为您的项目选择匹配的设备选择正确的闪存编程驱动程序。如果您发布问题,请指定您正在使用的 MDK-ARM 版本和调试硬件类型。
  • 谢谢,但我发现写入寄存器更容易,这对我来说更有意义。但我会尝试 GPIO 库。我有一个问题,处理器上的写保护是阻止我的代码运行还是只阻止调试?
【解决方案2】:

我认为您的数据范围是正确的。指针与任何其他变量一样位于 RAM 中。重要的不是它们所在的位置,而是它们指向的内容,在您的情况下是 GPIO。代码看起来有点正确,也就是说我没有检查您要写入的寄存器的地址是否正确,或者您正在写入正确的值/寄存器。尝试先启用时钟。如果有疑问,请查看从 ST 下载的 F3 立方体中的 GPIO 示例。它使用它们的库函数,但您可以查看它们的内部。它还提供了一个文件,其中包含我建议您使用的所有寄存器的定义。

【讨论】:

  • 是的,我已经阅读了它并且我现在明白了,但是代码仍然无法正常工作。我在调试时访问目标资源时出错,非法地址 0x08000000
【解决方案3】:

我有一个来自亚洲的 2 美元 ebay,它具有相同的部件,并且在端口 C 引脚 13 上有一个 LED。

这是一个完整的基于 gnu 工具链的示例,您可以将其更改为在端口 d 引脚 0 上操作(或将您的 led 移动到 pc13)

flash.s

.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang

.thumb_func
reset:
    bl notmain
    b hang

.thumb_func
hang:   b .

blinker01.c

#define GPIOCBASE 0x40011000
#define RCCBASE 0x40021000
#define RCC_APB2ENR   (*((volatile unsigned int *)(RCCBASE+0x18)))
#define GPIO_CONTROL (*((volatile unsigned int *)(GPIOCBASE+0x04)))
#define GPIO_BSRR    (*((volatile unsigned int *)(GPIOCBASE+0x10)))
void notmain ( void )
{
    volatile unsigned int ra;

    RCC_APB2ENR|=1<<4; //enable port c
    GPIO_CONTROL=(GPIO_CONTROL&(~0xF<<20))|(0x1<<20);
    while(1)
    {
        GPIO_BSRR=1<<(13+0);
        for(ra=0;ra<200000;ra++) continue;
        GPIO_BSRR=1<<(13+16);
        for(ra=0;ra<200000;ra++) continue;
    }
}

flash.ld

MEMORY
{
    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}

SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mthumb -mcpu=cortex-m3 -march=armv7-m -c blinker01.c -o blinker01.o
arm-none-eabi-ld -o blinker01.elf -T flash.ld flash.o blinker01.o
arm-none-eabi-objdump -D blinker01.elf > blinker01.list
arm-none-eabi-objcopy blinker01.elf blinker01.bin -O binary

然后检查列表

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000025    stmdaeq r0, {r0, r2, r5}
 8000008:   0800002b    stmdaeq r0, {r0, r1, r3, r5}
 800000c:   0800002b    stmdaeq r0, {r0, r1, r3, r5}
 8000010:   0800002b    stmdaeq r0, {r0, r1, r3, r5}
 8000014:   0800002b    stmdaeq r0, {r0, r1, r3, r5}
 8000018:   0800002b    stmdaeq r0, {r0, r1, r3, r5}
 800001c:   0800002b    stmdaeq r0, {r0, r1, r3, r5}
 8000020:   0800002b    stmdaeq r0, {r0, r1, r3, r5}

08000024 <reset>:
 8000024:   f000 f802   bl  800002c <notmain>
 8000028:   e7ff        b.n 800002a <hang>

0800002a <hang>:
 800002a:   e7fe        b.n 800002a <hang>

向量表必须在正确的位置。向量必须是与它们所在的地址或 1 的地址(gnu 汇编器中的 .thumb_func 使下一个标签成为函数,如果我们将该标签用于任何事情,则为我们做 or 一件事)。如果您的二进制文件不是以向量表开头或地址是偶数,那么游戏结束了,您将无法启动。

英特尔十六进制版本的二进制文件(如果您的工具可以处理)

:020000040800F2
:1000000000100020250000082B0000082B0000082D
:100010002B0000082B0000082B0000082B00000814
:100020002B00000800F002F8FFE7FEE74FF4005550
:1000300000204FF00054154A154E1368154943F03F
:10004000100313603268144B02F07F4242F48012B6
:1000500082B032600D600190019A9A420FD90C6013
:100060000190019A9A42F5D8019A01320192019ABF
:100070009A42F9D90D600190019A9A42EFD8019AFB
:1000800001320192019A9A42F9D9E8E71810024028
:0C00900004100140101001403F0D03005F
:0400000508000000EF
:00000001FF

相同的记录版本。

S0110000626C696E6B657230312E73726563CB
S3150800000000100020250000082B0000082B0000081F
S315080000102B0000082B0000082B0000082B00000806
S315080000202B00000800F002F8FFE7FEE74FF4005542
S3150800003000204FF00054154A154E1368154943F031
S31508000040100313603268144B02F07F4242F48012A8
S3150800005082B032600D600190019A9A420FD90C6005
S315080000600190019A9A42F5D8019A01320192019AB1
S315080000709A42F9D90D600190019A9A42EFD8019AED
S3150800008001320192019A9A42F9D9E8E7181002401A
S3110800009004100140101001403F0D030051
S70508000000F2

如果你将你的 led 移到 pc13 上,理想情况下你可以直接使用这些二进制文件之一。

如果你有 openocd 然后 telnet 到它 (telnet localhost 4444)

> halt
> flash write_image erase /path/to/blinker01.srec
> reset

它会开始闪烁 LED

【讨论】:

  • 看来我将不得不暂停我的裸机计划并首先熟悉 stm32。非常感谢您的帮助。我对 GNU 的理解还不够好。 keil可以做裸机吗?感谢您的帮助
  • 是的,你没有理由不能使用 keil。从历史上看,这是 keils 的主要市场。
  • 在设置过程中包含启动文件后,我尝试使用 keil 并写入寄存器,但它不起作用。我想知道是否必须使用 gnu 工具链来完成裸机
  • 几乎可以保证不是这种情况,如果它不能支持 C 语言,那将是一个相当糟糕的编译器。
  • 我认为你做得对,易变的东西是 C 而不是 GNU,我建议你尝试一下,你可以按原样使用我的 C 代码并修改端口 D0 和/或只使用 PC13 但是使用 Keil 而不是 GNU 构建。我建议您尝试弄清楚如何检查二进制文件以查看它们是否正确执行基本操作(向量表是 cortex-m 样式而不是传统的,这是所有拇指代码没有手臂等)。
猜你喜欢
  • 2011-03-12
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多