如何使用 llvm 生成的汇编语言打开 stm32 板上的 LED?

How to turn on LED on stm32 board using assembly language that generated by llvm?

我的英语水平很差,因为我的母语不是英语。请理解。

我编译了一些测试代码,在 IAR 中使用 LLVM infra 运行正常,但生成的代码没有在我的测试板上运行。 详情如下。

测试目标

我想看看运行用LLVM生成的汇编代码。

测试环境

  1. 单片机:STM32L152VD(皮质M3)
  2. IDE : IAR 8.2
  3. 调试器:Segger JLink
  4. LLVM 站点:http://ellcc.org/demo/index.cgi

测试步骤(摘要)

  1. 创建在 IAR 中运行良好的测试代码。
  2. 将测试代码移动到 http://ellcc.org/demo/index.cgi 并在 select 目标之后编译。
  3. 使用生成的汇编代码创建 test.s 文件。
  4. 创建makefile生成bin文件,用make程序执行makefile
  5. 用JLink程序加载bin文件到目标板。

步骤 1

我写了没有库的简单代码如下。 此代码简单地打开 LED。

volatile int* _RCC = (int*)(0x40023800);
volatile int* _RCC_AHBENR = (int*)(0x4002381c);
volatile int* _GPIOE = (int*)0x40021000;
volatile int* _GPIOE_BSRR = (int*)(0x40021000 + 0x18);

void InitPort()
{
    const int _RCC_AHBENR_GPIOEEN = (0x00000010);
    int SetOutput = 0x00000600;

    *_RCC_AHBENR = _RCC_AHBENR_GPIOEEN;
    *_GPIOE = SetOutput;    // set mode to output

    *_GPIOE_BSRR = 0x00000020;  // set
}


int main()
{
    InitPort();

    *_GPIOE_BSRR = 0x00200000;  // reset
    
    while(1);
}

以上代码在IAR中运行正常

第 2 步

我将创建的测试代码移动到 http://ellcc.org/demo/index.cgi 并在 select 选项后按下编译按钮,如下所示。

步骤 3

我创建了 test.s 文件,其中包含在站点中生成的汇编代码,如下所示。

    .text
    .syntax unified
    .eabi_attribute 67, "2.09"
    .cpu    cortex-m3
    .eabi_attribute 6, 10
    .eabi_attribute 7, 77
    .eabi_attribute 8, 0
    .eabi_attribute 9, 2
    .eabi_attribute 34, 1
    .eabi_attribute 17, 1
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 38, 1
    .eabi_attribute 18, 4
    .eabi_attribute 26, 2
    .eabi_attribute 14, 0
    .file   "_2376_0.c"
    .globl  InitPort
    .p2align    1
    .type   InitPort,%function
    .code   16
    .thumb_func
InitPort:
    .fnstart
    sub sp, #8
    movs    r0, #16
    str r0, [sp, #4]
    mov.w   r1, #1536
    str r1, [sp]
    movw    r1, :lower16:_RCC_AHBENR
    movt    r1, :upper16:_RCC_AHBENR
    ldr r1, [r1]
    str r0, [r1]
    ldr r0, [sp]
    movw    r1, :lower16:_GPIOE
    movt    r1, :upper16:_GPIOE
    ldr r1, [r1]
    str r0, [r1]
    movw    r0, :lower16:_GPIOE_BSRR
    movt    r0, :upper16:_GPIOE_BSRR
    ldr r0, [r0]
    movs    r1, #32
    str r1, [r0]
    add sp, #8
    bx  lr
.Lfunc_end0:
    .size   InitPort, .Lfunc_end0-InitPort
    .cantunwind
    .fnend

    .globl  main
    .p2align    1
    .type   main,%function
    .code   16
    .thumb_func
main:
    .fnstart
    push    {r7, lr}
    mov r7, sp
    sub sp, #8
    movs    r0, #0
    str r0, [sp, #4]
    bl  InitPort
    movw    r0, :lower16:_GPIOE_BSRR
    movt    r0, :upper16:_GPIOE_BSRR
    ldr r0, [r0]
    mov.w   lr, #2097152
    str.w   lr, [r0]
    b   .LBB1_1
.LBB1_1:
    b   .LBB1_1
.Lfunc_end1:
    .size   main, .Lfunc_end1-main
    .cantunwind
    .fnend

    .type   _RCC,%object
    .data
    .globl  _RCC
    .p2align    2
_RCC:
    .long   1073887232
    .size   _RCC, 4

    .type   _RCC_AHBENR,%object
    .globl  _RCC_AHBENR
    .p2align    2
_RCC_AHBENR:
    .long   1073887260
    .size   _RCC_AHBENR, 4

    .type   _GPIOE,%object
    .globl  _GPIOE
    .p2align    2
_GPIOE:
    .long   1073876992
    .size   _GPIOE, 4

    .type   _GPIOE_BSRR,%object
    .globl  _GPIOE_BSRR
    .p2align    2
_GPIOE_BSRR:
    .long   1073877016
    .size   _GPIOE_BSRR, 4


    .ident  "ecc version 2017-08-23 (http://ellcc.org) based on clang version 6.0.0 (trunk 311547)"
    .section    ".note.GNU-stack","",%progbits

第 4 步

我创建了 makefile 来生成 bin 文件,如下所示。 这是makefile的内容。

bin: test.s
    @echo "Running target all"
    arm-none-eabi-as c:/backend/files/test.s -o c:/backend/files/test.o
    arm-none-eabi-ld -Ttext=0x08000000 c:/backend/files/test.o -o c:/backend/files/test.elf
    arm-none-eabi-objdump -D c:/backend/files/test.elf
    arm-none-eabi-objcopy c:/backend/files/test.elf -O binary c:/backend/files/test.bin

clean:
    @echo "Running target clean"
    rm -f *.o
    rm -f *.elf
    rm -f *.bin

我用make程序执行了上面的makefile,得到了test.o、test.elf、test.bin个文件。

第 5 步

我用 JLink.exe (seggar) 加载了 bin 文件并使用 go 命令执行但注意到发生在船上。 (我加载bin文件到板子时使用的命令是“loadbin C:\backend\files\test.bin, 0x08000000”)

结论

到这里是我所做的一切。 我按照上面的方法做了,但是与 IAR 生成的代码不同,LLVM infra 生成的汇编代码没有被操作。 我想知道我做错了什么以及如何解决以实现目标。 任何帮助将不胜感激。

谢谢。


附加信息

没有像 RTOS 这样的板载软件。 下图是我用来测试的整个结构。 只有main.cpp文件是源代码。其他文件由EWARM生成IDE。

地图文件内容如下

###############################################################################
#
# IAR ELF Linker V8.22.2.15995/W32 for ARM                24/Oct/2020  19:22:32
# Copyright 2007-2018 IAR Systems AB.
#
#    Output file  =  C:\Users\jjw\Desktop\hobby\Test\Debug\Exe\Test.out
#    Map file     =  C:\Users\jjw\Desktop\hobby\Test\Debug\List\Test.map
#    Command line =  
#        -f C:\Users\jjw\AppData\Local\Temp\EW7E50.tmp
#        (C:\Users\jjw\Desktop\hobby\Test\Debug\Obj\main.o -o
#        C:\Users\jjw\Desktop\hobby\Test\Debug\Exe\Test.out --redirect
#        _Printf=_PrintfFullNoMb --redirect _Scanf=_ScanfFullNoMb --map
#        C:\Users\jjw\Desktop\hobby\Test\Debug\List\Test.map --config
#        "C:\Program Files (x86)\IAR Systems\Embedded Workbench
#        8.0\arm\CONFIG\generic_cortex.icf" --semihosting --entry
#        __iar_program_start --redirect __iar_sh_stdout=__iar_sh_stdout_swo
#        --vfe --text_out locale)
#
###############################################################################

*******************************************************************************
*** RUNTIME MODEL ATTRIBUTES
***

CppFlavor        = *
__CPP_Exceptions = Disabled
__CPP_Language   = C++14
__SystemLibrary  = DLib
__dlib_version   = 6


*******************************************************************************
*** HEAP SELECTION
***

The basic heap was selected because no calls to memory allocation
functions were found in the application outside of system library
functions, and there are calls to deallocation functions in the
application.


*******************************************************************************
*** PLACEMENT SUMMARY
***

"A0":  place at 0x00000000 { ro section .intvec };
"P1":  place in [from 0x00000000 to 0x0007ffff] { ro };
define block CSTACK with size = 1K, alignment = 8 { };
define block PROC_STACK with size = 0M, alignment = 8 { };
define block HEAP with size = 2K, alignment = 8 { };
"P2":  place in [from 0x20000000 to 0x2000ffff] {
          rw, block CSTACK, block PROC_STACK, block HEAP };
initialize by copy { rw };

  Section            Kind        Address   Size  Object
  -------            ----        -------   ----  ------
"A0":                                      0x40
  .intvec            ro code  0x00000000   0x40  vector_table_M.o [4]
                            - 0x00000040   0x40

"P1":                                     0x104
  .text              ro code  0x00000040   0x3c  main.o [1]
  .text              ro code  0x0000007c   0x2c  copy_init3.o [4]
  .text              ro code  0x000000a8   0x28  data_init.o [4]
  .iar.init_table    const    0x000000d0   0x14  - Linker created -
  .text              ro code  0x000000e4   0x1e  cmain.o [4]
  .text              ro code  0x00000102    0x4  low_level_init.o [3]
  .text              ro code  0x00000106    0x4  exit.o [3]
  .text              ro code  0x0000010a    0x2  vector_table_M.o [4]
  .text              ro code  0x0000010c    0xa  cexit.o [4]
  .rodata            const    0x00000116    0x1  unwind_debug.o [5]
  .text              ro code  0x00000118   0x14  exit.o [5]
  .text              ro code  0x0000012c    0xc  cstartup_M.o [4]
  Initializer bytes  const    0x00000138    0xc  <for P2-1>
  .rodata            const    0x00000144    0x0  copy_init3.o [4]
                            - 0x00000144  0x104

"P2", part 1 of 2:                          0xc
  P2-1                        0x20000000    0xc  <Init block>
    .data            inited   0x20000000    0x4  main.o [1]
    .data            inited   0x20000004    0x4  main.o [1]
    .data            inited   0x20000008    0x4  main.o [1]
                            - 0x2000000c    0xc

"P2", part 2 of 2:                        0x400
  CSTACK                      0x20000010  0x400  <Block>
    CSTACK           uninit   0x20000010  0x400  <Block tail>
                            - 0x20000410  0x400


*******************************************************************************
*** INIT TABLE
***

          Address     Size
          -------     ----
Copy (__iar_copy_init3)
    1 source range, total size 0xc:
          0x00000138   0xc
    1 destination range, total size 0xc:
          0x20000000   0xc



*******************************************************************************
*** MODULE SUMMARY
***

    Module            ro code  ro data  rw data
    ------            -------  -------  -------
C:\Users\jjw\Desktop\hobby\Test\Debug\Obj: [1]
    main.o                 60       12       12
    -------------------------------------------
    Total:                 60       12       12

command line: [2]
    -------------------------------------------
    Total:

dl7M_tln.a: [3]
    exit.o                  4
    low_level_init.o        4
    -------------------------------------------
    Total:                  8

rt7M_tl.a: [4]
    cexit.o                10
    cmain.o                30
    copy_init3.o           44
    cstartup_M.o           12
    data_init.o            40
    vector_table_M.o       66
    -------------------------------------------
    Total:                202

shb_l.a: [5]
    exit.o                 20
    unwind_debug.o                   1
    -------------------------------------------
    Total:                 20        1

    Gaps                    1
    Linker created                  20    1 024
-----------------------------------------------
    Grand Total:          291       33    1 036


*******************************************************************************
*** ENTRY LIST
***

Entry                      Address  Size  Type      Object
-----                      -------  ----  ----      ------
.iar.init_table$$Base   0x000000d0         --   Gb  - Linker created -
.iar.init_table$$Limit  0x000000e4         --   Gb  - Linker created -
?main                   0x000000e5        Code  Gb  cmain.o [4]
CSTACK$$Base            0x20000010         --   Gb  - Linker created -
CSTACK$$Limit           0x20000410         --   Gb  - Linker created -
InitPort()              0x00000041  0x1e  Code  Gb  main.o [1]
Region$$Table$$Base     0x000000d0         --   Gb  - Linker created -
Region$$Table$$Limit    0x000000e4         --   Gb  - Linker created -
_GPIOE                  0x20000004   0x4  Data  Gb  main.o [1]
_GPIOE_BSRR             0x20000008   0x4  Data  Gb  main.o [1]
_RCC_AHBENR             0x20000000   0x4  Data  Gb  main.o [1]
__cmain                 0x000000e5        Code  Gb  cmain.o [4]
__exit                  0x00000119  0x14  Code  Gb  exit.o [5]
__iar_copy_init3        0x0000007d  0x2c  Code  Gb  copy_init3.o [4]
__iar_data_init3        0x000000a9  0x28  Code  Gb  data_init.o [4]
__iar_debug_exceptions  0x00000116   0x1  Data  Gb  unwind_debug.o [5]
__iar_program_start     0x0000012d        Code  Gb  cstartup_M.o [4]
__iar_systems$$module {Abs}
                        0x00000001        Data  Gb  command line/config [2]
__low_level_init        0x00000103   0x4  Code  Gb  low_level_init.o [3]
__vector_table          0x00000000        Data  Gb  vector_table_M.o [4]
_call_main              0x000000f1        Code  Gb  cmain.o [4]
_exit                   0x0000010d        Code  Gb  cexit.o [4]
_main                   0x000000ff        Code  Gb  cmain.o [4]
exit                    0x00000107   0x4  Code  Gb  exit.o [3]
main                    0x0000005f  0x12  Code  Gb  main.o [1]


[1] = C:\Users\jjw\Desktop\hobby\Test\Debug\Obj
[2] = command line
[3] = dl7M_tln.a
[4] = rt7M_tl.a
[5] = shb_l.a

    291 bytes of readonly  code memory
     33 bytes of readonly  data memory
  1 036 bytes of readwrite data memory

Errors: none
Warnings: none

icf文件内容如下

/*###ICF### Section handled by ICF editor, don't touch! ****/
/*-Editor annotation file-*/
/* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_4.xml" */
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x00000000;
/*-Memory Regions-*/
define symbol __ICFEDIT_region_IROM1_start__ = 0x00000000;
define symbol __ICFEDIT_region_IROM1_end__   = 0x0007FFFF;
define symbol __ICFEDIT_region_IROM2_start__ = 0x0;
define symbol __ICFEDIT_region_IROM2_end__   = 0x0;
define symbol __ICFEDIT_region_EROM1_start__ = 0x0;
define symbol __ICFEDIT_region_EROM1_end__   = 0x0;
define symbol __ICFEDIT_region_EROM2_start__ = 0x0;
define symbol __ICFEDIT_region_EROM2_end__   = 0x0;
define symbol __ICFEDIT_region_EROM3_start__ = 0x0;
define symbol __ICFEDIT_region_EROM3_end__   = 0x0;
define symbol __ICFEDIT_region_IRAM1_start__ = 0x20000000;
define symbol __ICFEDIT_region_IRAM1_end__   = 0x2000FFFF;
define symbol __ICFEDIT_region_IRAM2_start__ = 0x0;
define symbol __ICFEDIT_region_IRAM2_end__   = 0x0;
define symbol __ICFEDIT_region_ERAM1_start__ = 0x0;
define symbol __ICFEDIT_region_ERAM1_end__   = 0x0;
define symbol __ICFEDIT_region_ERAM2_start__ = 0x0;
define symbol __ICFEDIT_region_ERAM2_end__   = 0x0;
define symbol __ICFEDIT_region_ERAM3_start__ = 0x0;
define symbol __ICFEDIT_region_ERAM3_end__   = 0x0;
/*-Sizes-*/
define symbol __ICFEDIT_size_cstack__     = 0x400;
define symbol __ICFEDIT_size_proc_stack__ = 0x0;
define symbol __ICFEDIT_size_heap__       = 0x800;
/**** End of ICF editor section. ###ICF###*/

define memory mem with size = 4G;
define symbol use_IROM1 = (__ICFEDIT_region_IROM1_start__ != 0x0 || __ICFEDIT_region_IROM1_end__ != 0x0);
define symbol use_IROM2 = (__ICFEDIT_region_IROM2_start__ != 0x0 || __ICFEDIT_region_IROM2_end__ != 0x0);
define symbol use_EROM1 = (__ICFEDIT_region_EROM1_start__ != 0x0 || __ICFEDIT_region_EROM1_end__ != 0x0);
define symbol use_EROM2 = (__ICFEDIT_region_EROM2_start__ != 0x0 || __ICFEDIT_region_EROM2_end__ != 0x0);
define symbol use_EROM3 = (__ICFEDIT_region_EROM3_start__ != 0x0 || __ICFEDIT_region_EROM3_end__ != 0x0);
define symbol use_IRAM1 = (__ICFEDIT_region_IRAM1_start__ != 0x0 || __ICFEDIT_region_IRAM1_end__ != 0x0);
define symbol use_IRAM2 = (__ICFEDIT_region_IRAM2_start__ != 0x0 || __ICFEDIT_region_IRAM2_end__ != 0x0);
define symbol use_ERAM1 = (__ICFEDIT_region_ERAM1_start__ != 0x0 || __ICFEDIT_region_ERAM1_end__ != 0x0);
define symbol use_ERAM2 = (__ICFEDIT_region_ERAM2_start__ != 0x0 || __ICFEDIT_region_ERAM2_end__ != 0x0);
define symbol use_ERAM3 = (__ICFEDIT_region_ERAM3_start__ != 0x0 || __ICFEDIT_region_ERAM3_end__ != 0x0);

if (use_IROM1)
{
  define region IROM1_region = mem:[from __ICFEDIT_region_IROM1_start__ to __ICFEDIT_region_IROM1_end__];
}
else
{
  define region IROM1_region = [];
}

if (use_IROM2)
{
  define region IROM2_region = mem:[from __ICFEDIT_region_IROM2_start__ to __ICFEDIT_region_IROM2_end__];
}
else
{
  define region IROM2_region = [];
}
define region IROM_region = IROM1_region | IROM2_region;

if (use_EROM1)
{
  define region EROM1_region = mem:[from __ICFEDIT_region_EROM1_start__ to __ICFEDIT_region_EROM1_end__];
}
else
{
  define region EROM1_region = [];
}
if (use_EROM2)
{
  define region EROM2_region = mem:[from __ICFEDIT_region_EROM2_start__ to __ICFEDIT_region_EROM2_end__];
}
else
{
  define region EROM2_region = [];
}
if (use_EROM3)
{
  define region EROM3_region = mem:[from __ICFEDIT_region_EROM3_start__ to __ICFEDIT_region_EROM3_end__];
}
else
{
  define region EROM3_region = [];
}
define region EROM_region = EROM1_region | EROM2_region | EROM3_region;

if (use_IRAM1)
{
  define region IRAM1_region = mem:[from __ICFEDIT_region_IRAM1_start__ to __ICFEDIT_region_IRAM1_end__];
}
else
{
  define region IRAM1_region = [];
}
if (use_IRAM2)
{
  define region IRAM2_region = mem:[from __ICFEDIT_region_IRAM2_start__ to __ICFEDIT_region_IRAM2_end__];
}
else
{
  define region IRAM2_region = [];
}
define region IRAM_region = IRAM1_region | IRAM2_region;

if (use_ERAM1)
{
  define region ERAM1_region = mem:[from __ICFEDIT_region_ERAM1_start__ to __ICFEDIT_region_ERAM1_end__];
}
else
{
  define region ERAM1_region = [];
}
if (use_ERAM2)
{
  define region ERAM2_region = mem:[from __ICFEDIT_region_ERAM2_start__ to __ICFEDIT_region_ERAM2_end__];
}
else
{
  define region ERAM2_region = [];
}
if (use_ERAM3)
{
  define region ERAM3_region = mem:[from __ICFEDIT_region_ERAM3_start__ to __ICFEDIT_region_ERAM3_end__];
}
else
{
  define region ERAM3_region = [];
}
define region ERAM_region = ERAM1_region | ERAM2_region | ERAM3_region;

do not initialize  { section .noinit };
initialize by copy { readwrite };
if (isdefinedsymbol(__USE_DLIB_PERTHREAD))
{
  // Required in a multi-threaded application
  initialize by copy with packing = none { section __DLIB_PERTHREAD };
}

place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };

if (!isempty(IROM_region))
{
  place in IROM_region  { readonly };
}

if (!isempty(EROM_region))
{
  place in EROM_region  { readonly section application_specific_ro };
}

if (!isempty(IRAM_region))
{
  define block CSTACK     with alignment = 8, size = __ICFEDIT_size_cstack__     { };
  define block PROC_STACK with alignment = 8, size = __ICFEDIT_size_proc_stack__ { };
  define block HEAP       with alignment = 8, size = __ICFEDIT_size_heap__       { };
  place in IRAM_region  { readwrite, block CSTACK, block PROC_STACK, block HEAP };
}

if (!isempty(ERAM_region))
{
  place in ERAM_region  { readwrite section application_specific_rw };
}

在EWARM工具中,我使用JLink调试器上传了上面的源代码。 JLink调试器和我的板子的连接如图。

我也试过手动上传由 EWARM 工具生成的 bin 文件(没有使用 EWARM 功能),如下所示。

下面的方法与上传LLVM生成的bin文件的方法相同。 结果,EWARM bin 文件是 LED 打开,但 llvm 文件不是。

我检查了上传EWARM bin文件和上传LLVM bin文件时的寄存器值不同。 (PC、SP、MSP)

以下是上传EWARM bin文件后起点处的寄存器值。 (这是操作的)

以下是上传LLVM bin文件后起点处的寄存器值。 (这个没有操作)

我认为这个问题的原因可能是程序计数器的值,(主)堆栈指针不正确。如果这个推断是对的,第一次的寄存器值如何配置?

如果需要更多信息,请告诉我。我要认真解决这个问题

需要更多信息才能正确回答这个问题..

  • 板子上还有其他软件吗(bootloader,os)?
  • most arm 目标上的地址 0x08000000 映射到连接到 bootflash 的硬件总线。 Jlink在刷芯片吗?
  • 你的 IAR workbench 运行 是模拟器吗?

听起来您 运行 没有任何引导加载程序或 os。在这种情况下,您需要按照 cortex M3 芯片手册中的启动程序进行操作。

例如

  • 启用电源域
  • 设置时钟
  • 初始化你的堆栈

一个更简单的方法可能是查看您的目标是否支持 uboot。如果它是一个开发板,那么 most devbords 有一些默认的软件加载,你可以使用它们。设置好核心硬件后,您就可以开始 运行 您的代码了。

所以除了一些事情之外,你走在正确的道路上。您正在使用 bsrr 重置然后设置然后立即重置输出引脚。首先,要打开 LED,您的电路板设计需要引脚为低电平还是高电平?如果低,那么你的 main.c 代码就可以了,如果高,那么它应该闪烁得如此之快,以至于你需要一个范围或类似的东西,你的眼睛不会看到它。

我有很多 stm32 板,有很多不同的芯片。我没有这一个或这个家族的一个,但没关系,我会通过一些东西来寻找,展示你如何完全控制所有代码,然后你可以回过头来使用你的工具并检查输出并查看问题是二进制文件还是您如何将其加载到零件中。人们会假设,如果您可以构建一种方式并使用相同的 tool/command 加载并且它“有效”但构建一种不同的方式并且它不起作用那么它不是二进制文件的加载而是 build/software.

我正在使用 NUCLEO-F446RE 开发板。 PA5 上有一个 LED。你有 gnu 工具,我有 gnu 工具,所以你将能够使用这些工具来构建这个项目(如果你选择这样做,可以根据你的需要进行修改)。

flash.ld

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

flash.s

.cpu cortex-m3
.thumb

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

.word hang
.word hang
.word hang
.word hang

.word hang
.word hang
.word hang
.word hang

.word hang
.word hang
.word hang
.word hang

.thumb_func
reset:
    bl main
    b hang
.thumb_func
hang:   b .

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

.thumb_func
.globl bounce
bounce:
    bx lr

main.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void bounce ( unsigned int );

#define RCCBASE         0x40023800
#define RCC_AHB1ENR     (RCCBASE+0x30)
#define RCC_APB1ENR     (RCCBASE+0x40)

#define GPIOABASE       0x40020000
#define GPIOA_MODER     (GPIOABASE+0x00)
#define GPIOA_BSRR      (GPIOABASE+0x18)

static void led_init ( void )
{
    unsigned int ra;

    ra=GET32(RCC_AHB1ENR);
    ra|=1<<0; //enable GPIOA
    PUT32(RCC_AHB1ENR,ra);

    ra=GET32(GPIOA_MODER);
    ra&=~(3<<(5<<1)); //PA5
    ra|= (1<<(5<<1)); //PA5
    PUT32(GPIOA_MODER,ra);
}

static void led_on ( void )
{
    PUT32(GPIOA_BSRR,((1<<5)<< 0));
}

static void led_off ( void )
{
    PUT32(GPIOA_BSRR,((1<<5)<<16));
}

int main ( void )
{
    unsigned int rx;

    led_init();
    while(1)
    {
        led_on();
        for(rx=0;rx<400000;rx++) bounce(rx);
        led_off();
        for(rx=0;rx<400000;rx++) bounce(rx);
   }
    return(0);
}

建造

arm-linux-gnueabi-as --warn --fatal-warnings -mcpu=cortex-m3 flash.s -o flash.o
arm-linux-gnueabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m3 -mthumb -S main.c -o main.s
arm-linux-gnueabi-as --warn --fatal-warnings -mcpu=cortex-m3 main.s -o main.o
arm-linux-gnueabi-ld -nostdlib -nostartfiles -T flash.ld flash.o main.o -o blinker.elf
arm-linux-gnueabi-objdump -D blinker.elf > blinker.list
arm-linux-gnueabi-objcopy -O binary blinker.elf blinker.bin

您不必使用所有这些命令行选项,进行实验(但要检查输出)。

在使用二进制文件之前检查它

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000041    stmdaeq r0, {r0, r6}
 8000008:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800000c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000010:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000014:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000018:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800001c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000020:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000024:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000028:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800002c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000030:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000034:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000038:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800003c:   08000047    stmdaeq r0, {r0, r1, r2, r6}

08000040 <reset>:
 8000040:   f000 f808   bl  8000054 <main>
 8000044:   e7ff        b.n 8000046 <hang>

08000046 <hang>:
 8000046:   e7fe        b.n 8000046 <hang>

第一部分是向量table它需要在0x08000000

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000041    stmdaeq r0, {r0, r6}
 8000008:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800000c:   08000047    stmdaeq r0, {r0, r1, r2, r6}

我使用 objdump 生成了它,所以无论如何它都会尝试反汇编这些字节。所以当你看到上面的内容时,重要的是这个

08000000 <_start>:
 8000000:   20001000
 8000004:   08000041
 8000008:   08000047
 800000c:   08000047

第一项是堆栈指针初始值,您可能有更多的内存,简单地将堆栈指针设置为最大地址加一或 0x20000000 + ram 的数量并不少见。这个小例子几乎不使用堆栈,而且应用程序非常小,所以 0x1000 字节绰绰有余。

接下来这么多是向量本身,它们需要是与 1

ORRED 的处理程序地址
08000040 <reset>:
08000046 <hang>:

如果你没有看到那东西就不会启动并且游戏已经结束,在矢量 table 链接到正确的地址并至少包含之前不要尝试使用二进制文件前两个词堆栈指针初始化和重置处理程序。

我包括了许多其他向量来捕获故障,如果您的代码没有错误并且构建正确,那么您不需要它们来实现这样的事情。

08000054 <main>:
 8000054:   b570        push    {r4, r5, r6, lr}
 8000056:   4816        ldr r0, [pc, #88]   ; (80000b0 <main+0x5c>)
 8000058:   f7ff fff8   bl  800004c <GET32>
 800005c:   f040 0101   orr.w   r1, r0, #1
 8000060:   4813        ldr r0, [pc, #76]   ; (80000b0 <main+0x5c>)

orr.w 指令表示这是为 thumb2、armv7-m 构建的。这对我的董事会 (cortex-m4) 和您的董事会 (cortex-m3) 都很好,但如果这是 cortex-m0 或 cortex-m0+,此代码将失败并导致需要故障处理程序的故障,即使它是一个无限循环(而不是向量条目是进一步扰乱内核并可能使尝试使用调试器进行调试变得更糟的指令)。 arm 如何处理包括统一语法在内的不幸副作用是,您无法从汇编语言中准确判断您将获得什么,好吧,通过实践,但查看它的最佳方式是反汇编。

所以这段代码有可能会起作用。这个 nucleo 板是 mbed 风格的,所以它本身就是一个可移动驱动器,你只需将 .bin 文件复制过来。

PUT32/GET32 基于经验,抽象层有很多好处,句号。您可以使用易失性指针,我很快就会展示。

同样最好read-modify-write这些寄存器成为一种习惯,这部分和这些寄存器都有很好的文档,这是post重置代码前面没有其他代码(rtos,库等),因此可以安全地假设您可以简单地将值塞入寄存器(不是时钟启用寄存器重置为 0x00008000 并且您正在禁用 GPIOG,为什么启用它?谁知道)

0x00000020 vs (1<<5) 是个人偏好,我根据代码和情况自己使用,在这种情况下我的偏好是清楚地看到密码。

for(rx=0;rx<400000;rx++) bounce(rx);

这是一个简单的延迟,不需要volatile,在这种情况下编译器无法在文件外进行优化,因此必须实现循环。该值是手动调整的,不要指望这会产生任何类型的可靠速率,只要让它足够一点就可以看到 LED 闪烁不太快也不太慢。一旦你看到它工作,然后将值加倍,减半,re-build,re-load 并查看 LED 闪烁率变化,这是一个粗略的测试,可以看到闪烁代码是你刚刚生成的代码而不是您或其他人遗留下来的东西,不想以错误的假设结束,即您编写的某些代码有效,而工具却使您失败并且它们没有将新程序加载到闪存中。

易失性指针方法,这可能与您的问题有关。

void bounce ( unsigned int );

#define RCCBASE         0x40023800
#define RCC_AHB1ENR (*((volatile unsigned int *)(RCCBASE+0x30)))

#define GPIOABASE       0x40020000
#define GPIOA_MODER (*((volatile unsigned int *)(GPIOABASE+0x00)))
#define GPIOA_BSRR  (*((volatile unsigned int *)(GPIOABASE+0x18)))

static void led_init ( void )
{
    RCC_AHB1ENR = 0x00100001;
    bounce(0);
    GPIOA_MODER = 0xA8000400;
}

static void led_on ( void )
{
    GPIOA_BSRR = 0x00000020;
}

static void led_off ( void )
{
    GPIOA_BSRR = 0x00200000;
}

int main ( void )
{
    unsigned int rx;

    led_init();
    while(1)
    {
        led_on();
        for(rx=0;rx<400000;rx++) bounce(rx);
        led_off();
        for(rx=0;rx<400000;rx++) bounce(rx);
   }
    return(0);
}

这是怎么回事:

    RCC_AHB1ENR = 0x00100001;
    bounce(0);
    GPIOA_MODER = 0xA8000400;

我在我的文档中找不到该声明,但问题是通过将值插入这两个寄存器,在外围设备启用和我们开始尝试之间存在少量时钟写信给它。 read-modify-write 方法,特别是使用抽象函数提供了大量延迟。所以在这种情况下,我实验性地添加了一个虚拟调用来燃烧一些时间。这对我来说已经足够了。

使用 volatile read-modify-write 也足够了。

RCC_AHB1ENR = 0x00100001;
GPIOA_MODER |= 0x400;

在另一个STM32部件上研究这个,不管出于什么原因,也许是这个原因,你可以在时钟启用之前读取模式寄存器或者模式寄存器的复位值,根本不需要启用外设,所以读取通过该解决方案进行,然后修改写入会在处理器和总线之间消耗一些时钟,从而提供允许写入工作所需的延迟。您的代码可能存在此问题,并且两个编译器生成代码的方式可能不同。我从研究中得知 llvm/clang 和 gnu 对 volatile 的含义有不同的看法。我们很快就会看到。

我有意进行此构建,以便为 gnu 案例生成 main.s,即使这是不必要的步骤。

    RCC_AHB1ENR = 0x00100001;

 8000060:   4b0d        ldr r3, [pc, #52]   ; (8000098 <main+0x44>)
 8000062:   490e        ldr r1, [pc, #56]   ; (800009c <main+0x48>)
 8000064:   4a0e        ldr r2, [pc, #56]   ; (80000a0 <main+0x4c>)
 8000066:   6019        str r1, [r3, #0]

    GPIOA_MODER |= 0x400;

 8000068:   6813        ldr r3, [r2, #0]
 800006a:   4e0e        ldr r6, [pc, #56]   ; (80000a4 <main+0x50>)
 800006c:   f443 6380   orr.w   r3, r3, #1024   ; 0x400
 8000070:   4d0d        ldr r5, [pc, #52]   ; (80000a8 <main+0x54>)
 8000072:   6013        str r3, [r2, #0]

 8000098:   40023830
 800009c:   00100001
 80000a0:   40020000
 80000a4:   40020018

这是竞争条件:

 8000060:   490c        ldr r1, [pc, #48]   ; (8000094 <main+0x40>)
 8000062:   480d        ldr r0, [pc, #52]   ; (8000098 <main+0x44>)
 8000064:   4b0d        ldr r3, [pc, #52]   ; (800009c <main+0x48>)
 8000066:   4a0e        ldr r2, [pc, #56]   ; (80000a0 <main+0x4c>)
 8000068:   4e0e        ldr r6, [pc, #56]   ; (80000a4 <main+0x50>)
 800006a:   4d0f        ldr r5, [pc, #60]   ; (80000a8 <main+0x54>)

    RCC_AHB1ENR = 0x00100001;

 800006c:   6008        str r0, [r1, #0]

    GPIOA_MODER = 0xA8000400;

 800006e:   601a        str r2, [r3, #0]

 8000094:   40023830    andmi   r3, r2, r0, lsr r8
 8000098:   00100001    andseq  r0, r0, r1
 800009c:   40020000    andmi   r0, r2, r0
 80000a0:   a8000400    stmdage r0, {sl}
 80000a4:   40020018    andmi   r0, r2, r8, lsl r0

编译器准备了前面的两个store,然后背靠背做了,有ahb总线相关的时钟,但显然不够。

我没有看到你正在使用的网页,我“简单地”(即使在快速的计算机上也需要永恒)为这些目标构建一个交叉编译器 llvm/clang(这些天是我可以让它正常工作的唯一方法,apt-gotten 与三元组不适用于版本 10 或 11,无论我最后尝试的是什么)。我也从源代码中推出自己的 gnu 工具,但无论如何。

llvm

 8000062:   f641 2680   movw    r6, #6784   ; 0x1a80
 8000066:   f04f 0820   mov.w   r8, #32
 800006a:   f44f 1900   mov.w   r9, #2097152    ; 0x200000
 800006e:   f2c4 0002   movt    r0, #16386  ; 0x4002
 8000072:   f2c0 0110   movt    r1, #16
 8000076:   f2c4 0502   movt    r5, #16386  ; 0x4002
 800007a:   f2c0 0606   movt    r6, #6
 800007e:   6001        str r1, [r0, #0]
 8000080:   f240 4000   movw    r0, #1024   ; 0x400
 8000084:   f6ca 0000   movt    r0, #43008  ; 0xa800
 8000088:   f845 0c18   str.w   r0, [r5, #-24]

所以用 llvm

RCC_AHB1ENR = 0x00100001;
GPIOA_MODER = 0xA8000400;

可以毫无延迟地背靠背,不一定是因为 volatile,而是编译器如何选择排列指令以及它选择使用什么指令。

也明白这是 gcc 10.2.0 版本,没有理由假设 prior/different 版本产生相同的代码。也没有任何理由假设 IAR 如果不使用 gnu 或其他工具链会生成相同的代码。您需要检查反汇编,了解可能出现问题的位置等。您可以很容易地看到不喜欢我的 PUT32/GET32 read-modify-write 并简单地将那几行代码更改为 volatile 指针的人可能会导致要中断的程序。凭借经验,人们应该看到高级代码中的差异可能导致竞争条件,因为这些寄存器修改的执行速度已经改变,并且时间确实很重要。在这种情况下,顺序当然很重要,所以 re-arranging 它们会失败,但也会计时,试图让你的代码更快,删除一个用于调试的 printf 然后一切都中断了,首先想到的是我是否更改了代码到功能上等效的东西,如果那是真的那么下一个想法是时间,添加很多延迟然后开始删除它们。

你现在可以使用我的 flash.ld 和 flash.s 轻松地重复所有这些,你的 main.c 变成 main.s,或者使用我的 main.c,其中之一,并将三个寄存器替换为数据表中的地址。

所以我们可能会假设,因为您理想情况下只更改 main.c/main.s,那么向量 table 不是问题,二进制文件在其他方面没问题。

*_RCC_AHBENR = _RCC_AHBENR_GPIOEEN;
*_GPIOE = SetOutput;    // set mode to output

至少让调制解调器注册一个read-modify-write,或者延迟一下看看你是否也看到了竞争条件。

int SetOutput = 0x00000600;

*_RCC_AHBENR = _RCC_AHBENR_GPIOEEN;
*_GPIOE = SetOutput;    // set mode to output

*_GPIOE_BSRR = 0x00000020;  // set

bsrr 值表明你的 led 在 pin5(端口 E)上,这是在模式下设置的第 10 位你有第 10 位和第 11 位设置为 0x600,是否有原因,不会伤害尝试打开 LED。

然后基本上你有

*_GPIOE_BSRR = 0x00000020;  // set

紧随其后的是

*_GPIOE_BSRR = 0x00200000;  // reset

然后你进入一个无限循环,它不再改变任何东西 PE5 应该永远处于低电平或者直到你重置然后它得到一个 handful/dozen 时钟长的信号。

你的llvm网页代码中可能没有竞争条件:

movw    r1, :lower16:_RCC_AHBENR
movt    r1, :upper16:_RCC_AHBENR
ldr r1, [r1]
str r0, [r1]
ldr r0, [sp]
movw    r1, :lower16:_GPIOE
movt    r1, :upper16:_GPIOE
ldr r1, [r1]
str r0, [r1]

有可能还是工具

arm-none-eabi-as c:/backend/files/test.s -o c:/backend/files/test.o
arm-none-eabi-ld -Ttext=0x08000000 c:/backend/files/test.o -o c:/backend/files/test.elf

对我来说是你生成的汇编语言

arm-none-eabi-as main.s -o main.o
arm-none-eabi-ld -Ttext=0x08000000 main.o -o main.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000008000000
arm-none-eabi-objdump -D main.elf 

main.elf:     file format elf32-littlearm


Disassembly of section .text:

08000000 <InitPort>:
 8000000:   b082        sub sp, #8
 8000002:   2010        movs    r0, #16
 8000004:   9001        str r0, [sp, #4]
 8000006:   f44f 61c0   mov.w   r1, #1536   ; 0x600
 800000a:   9100        str r1, [sp, #0]
 800000c:   f240 0168   movw    r1, #104    ; 0x68
 8000010:   f6c0 0101   movt    r1, #2049   ; 0x801

所以最初的问题就在您的屏幕上。

 8000000:   b082        sub sp, #8
 8000002:   2010        movs    r0, #16
 8000004:   9001        str r0, [sp, #4]
 8000006:   f44f 61c0   mov.w   r1, #1536   ; 0x600

arm-none-eabi-objcopy main.elf -O binary main.bin
hexdump -C main.bin 
00000000  82 b0 10 20 01 90 4f f4  c0 61 00 91 40 f2 68 01  |... ..O..a..@.h.|
00000010  c0 f6 01 01 09 68 08 60  00 98 40 f2 6c 01 c0 f6  |.....h.`..@.l...|
00000020  01 01 09 68 08 60 40 f2  70 00 c0 f6 01 00 00 68  |...h.`@.p......h|

矢量 table 看起来像这样:

0x08000000: 0x2010b082
0x08000004: 0xF44F9001

这根本行不通,它甚至可能会尝试从该地址获取数据,但游戏会立即结束。

所以最简短的回答是你没有提供矢量 table 也没有 bootstrap。

现在了解我的情况,这是我的 bootstrap:

bl main

通常对于单片机,您希望将 .data 从闪存复制到 ram,然后零 .bss,您需要一个更复杂的链接器脚本来识别这些区域,并且链接器脚本和 bootstrap 代码密切相关(并且是特定于工具链的,不假设要移植到其他工具链)。我既不使用 .data 也不使用也不关心 .bss 项是否为零,所以我的链接器脚本很简单,我的 bootstrap 设置堆栈指针并输入 C 代码,因为 cortex-m 处理堆栈指针,我所要做的就是调用 C 入口点。由于 cortex-m 的工作原理,您实际上可以这样做:

flash.s

.cpu cortex-m3
.thumb

.thumb_func
.global _start
_start:
.word 0x20001000
.word main

.thumb_func
.globl bounce
bounce:
    bx lr

但这只有在你不依赖 .data 或 .bss 或上帝保佑你认为可以用 C 代码而不是 bootstrap 初始化它们的情况下才有效(当然是用 asm 编写的) .

通用 C 支持的正确答案是 borrow/modify/create 一个复杂的链接描述文件,您可以使用它来获取工具来帮助您创建变量来标识 .data 的开始和结束或开始和大小(两者都是在 flash 和 ram 中)和 .bss(在 ram 中)和 copy and zero,可能至少生成一个 int argc(为 1)和 argv[0],以防用户感到需要。

C 库实现通常包含更多链接器脚本内容,尽管这不是必需的,这正是某些人倾向于这样做的方式,同样还有更多 bootstrap 内容,当然,这是正确的地方对于其中一些事情。如果可以避免,我不会使用 C 库,因为它会使项目立即变得更大,而且其中很多都需要一个假系统,然后您必须实施假系统才能使它们工作。

很明显,我的这个非常简单的例子就是这样,它对你可以用它做什么有严格的限制,但它证明了成功,将你完全与任何可能干扰成功的库代码隔离开来(通过尝试做事绕过库代码或库代码及其 bootstrap 项目可能会干扰您直接访问寄存器的成功)。

另请注意,在我的实现中,我依靠命令行预先获取向量 table,很多人会:

.cpu cortex-m3
.thumb

.section .vectors

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

.text

.thumb_func
reset:
    bl main
    b hang
.thumb_func
hang:   b .

然后是

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

注意

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

是不是各种破:

Disassembly of section .bob:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000001    stmdaeq r0, {r0}

Disassembly of section .ted:

08000000 <reset>:
 8000000:   f000 f808   bl  8000014 <main>
 8000004:   e7ff        b.n 8000006 <hang>

08000006 <hang>:
 8000006:   e7fe        b.n 8000006 <hang>

并且无法开机。在尝试对零件进行编程之前,请务必在 cortex-m 版本上检查向量 table。不是你的情况,也不是我的情况,但是有 some/many 解决方案,其中 re-program 部分的能力在很大程度上依赖于部分的二进制文件,其中包含所有加载程序代码并且没有任何挂起或是坏了,有一大堆这样的木板我就不提了。

许多使用 Arduino 环境的人都会遇到这种情况,如果您像这样滚动自己的信号灯,那么首先会失败,这将破坏您再次通过沙箱加载零件的能力。但是如果你的目标是构建他们所有的代码并且发生了这种情况你仍然会被砖砌(仍然可以使用 boot0 和串行或 usb 等或 swd 进入 stm32 部分,一些供应商的部分你可以很容易地变砖并且不能用 swd 恢复)。 (您正在使用的 jlink 是使用 swd(串行线调试)进入零件并对闪存进行编程)。