从一开始了解ARM中断(Cortex-M4)

3
我正在处理来自NXP的新型微控制器LPC4088。我需要两周时间研究和编写外设的工作示例:IOCONFIG、GPIO、TIMERS、PWM和ADC。请查看我的存储库here。这是您了解我的工作方式和技能水平的方式。
到目前为止,我可以简单地禁用中断并在没有中断的情况下工作。现在我想处理需要中断的UART外设设备。我从未编写过中断程序,但对ARM中断有一些了解。目前我正在学习这两个文档:
- LPC4088用户指南(NVIC p.80 & UART1 p.461), - Cortex-M4设备通用用户手册

我意识到需要学习 ARM Cortex-M4 微处理器,除了已经掌握的 LPC4088 微控制器。我知道应该将 ARM 异常向量放在程序的开头,通常是在启动代码中。但是我不知道如何做到这一点,因为我得到的是已编译的启动代码(目标文件),其中可能定义了异常向量和复位处理程序, 将 C 栈设置并跳转到用户编写的 C 源代码中的 main() 函数。

使用 GCC ARM 编译器编译我的程序后,我总是得到以下提示,这也必须是我不理解的关键,因为我没有直接使用 ARM mcpu 的经验:

*****
***** You must modify vector checksum value in *.bin and *.hex files.
*****

我正在考虑使用Segger Jlink对启动代码进行反向工程,并在那里修复异常向量,但除了编写自己的开源启动代码之外,一定还有其他方法...因此,您有任何建议或示例会更好。

添加:我真的很努力地寻找了启动代码的源代码,但是没有找到。这就是我得到的内容:

enter image description here

因此,某种程度上操纵向量的唯一途径必须隐藏在链接脚本中,它是仍然是源代码的唯一部分,它看起来像这样:
/* Linker script for mbed LPC1768 */

/* Linker script to configure memory regions. */
MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 512K
  RAM (rwx) : ORIGIN = 0x100000E8, LENGTH = (64K - 0xE8)

  USB_RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 16K
  ETH_RAM(rwx) : ORIGIN = 0x20004000, LENGTH = 16K
}

/* Linker script to place sections and symbol values. Should be used together
 * with other linker script that defines memory regions FLASH and RAM.
 * It references following symbols, which must be defined in code:
 *   Reset_Handler : Entry of reset handler
 * 
 * It defines following symbols, which code can use without definition:
 *   __exidx_start
 *   __exidx_end
 *   __etext
 *   __data_start__
 *   __preinit_array_start
 *   __preinit_array_end
 *   __init_array_start
 *   __init_array_end
 *   __fini_array_start
 *   __fini_array_end
 *   __data_end__
 *   __bss_start__
 *   __bss_end__
 *   __end__
 *   end
 *   __HeapLimit
 *   __StackLimit
 *   __StackTop
 *   __stack
 */
ENTRY(Reset_Handler)

SECTIONS
{
    .text :
    {
        KEEP(*(.isr_vector))
        *(.text*)

        KEEP(*(.init))
        KEEP(*(.fini))

        /* .ctors */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
        *(SORT(.ctors.*))
        *(.ctors)

        /* .dtors */
        *crtbegin.o(.dtors)
        *crtbegin?.o(.dtors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
        *(SORT(.dtors.*))
        *(.dtors)

        *(.rodata*)

        KEEP(*(.eh_frame*))
    } > FLASH

    .ARM.extab : 
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > FLASH

    __exidx_start = .;
    .ARM.exidx :
    {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > FLASH
    __exidx_end = .;

    __etext = .;

    .data : AT (__etext)
    {
        __data_start__ = .;
        Image$$RW_IRAM1$$Base = .;
        *(vtable)
        *(.data*)

        . = ALIGN(4);
        /* preinit data */
        PROVIDE (__preinit_array_start = .);
        KEEP(*(.preinit_array))
        PROVIDE (__preinit_array_end = .);

        . = ALIGN(4);
        /* init data */
        PROVIDE (__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        PROVIDE (__init_array_end = .);


        . = ALIGN(4);
        /* finit data */
        PROVIDE (__fini_array_start = .);
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        PROVIDE (__fini_array_end = .);

        . = ALIGN(4);
        /* All data end */
        __data_end__ = .;

    } > RAM


    .bss :
    {
        __bss_start__ = .;
        *(.bss*)
        *(COMMON)
        __bss_end__ = .;
        Image$$RW_IRAM1$$ZI$$Limit = . ;
    } > RAM


    .heap :
    {
        __end__ = .;
        end = __end__;
        *(.heap*)
        __HeapLimit = .;
    } > RAM

    /* .stack_dummy section doesn't contains any symbols. It is only
     * used for linker to calculate size of stack sections, and assign
     * values to stack symbols later */
    .stack_dummy :
    {
        *(.stack)
    } > RAM

    /* Set stack top to end of RAM, and stack limit move down by
     * size of stack_dummy section */
    __StackTop = ORIGIN(RAM) + LENGTH(RAM);
    __StackLimit = __StackTop - SIZEOF(.stack_dummy);
    PROVIDE(__stack = __StackTop);

    /* Check if data + heap + stack exceeds RAM limit */
    ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")


    /* Code can explicitly ask for data to be 
       placed in these higher RAM banks where
       they will be left uninitialized. 
    */
    .AHBSRAM0 (NOLOAD):
    {
        Image$$RW_IRAM2$$Base = . ;
        *(AHBSRAM0)
        Image$$RW_IRAM2$$ZI$$Limit = .;
    } > USB_RAM

    .AHBSRAM1 (NOLOAD):
    {
        Image$$RW_IRAM3$$Base = . ;
        *(AHBSRAM1)
        Image$$RW_IRAM3$$ZI$$Limit = .;
    } > ETH_RAM
}

还有一个Makefile文件,看起来像这样,并且贡献了我在每次编译结束时得到的提示:

# This file was automagically generated by mbed.org. For more information, 
# see http://mbed.org/handbook/Exporting-to-GCC-ARM-Embedded

GCC_BIN = 
PROJECT = executaable
OBJECTS = ./main.o 
SYS_OBJECTS = ./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM/startup_LPC408x.o ./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM/retarget.o ./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM/system_LPC407x_8x_177x_8x.o ./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM/board.o ./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM/cmsis_nvic.o 
INCLUDE_PATHS = -I. -I./mbed -I./mbed/TARGET_LPC4088 -I./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM -I./mbed/TARGET_LPC4088/TARGET_NXP -I./mbed/TARGET_LPC4088/TARGET_NXP/TARGET_LPC408X -I./mbed/TARGET_LPC4088/TARGET_NXP/TARGET_LPC408X/TARGET_LPC4088 
LIBRARY_PATHS = -L./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM 
LIBRARIES = -lmbed 
LINKER_SCRIPT = ./mbed/TARGET_LPC4088/TOOLCHAIN_GCC_ARM/LPC4088.ld

############################################################################### 
AS      = $(GCC_BIN)arm-none-eabi-as
CC      = $(GCC_BIN)arm-none-eabi-gcc
CPP     = $(GCC_BIN)arm-none-eabi-g++
LD      = $(GCC_BIN)arm-none-eabi-gcc
OBJCOPY = $(GCC_BIN)arm-none-eabi-objcopy
OBJDUMP = $(GCC_BIN)arm-none-eabi-objdump
SIZE    = $(GCC_BIN)arm-none-eabi-size

CPU = -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=softfp
CC_FLAGS = $(CPU) -c -g -fno-common -fmessage-length=0 -Wall -fno-exceptions -ffunction-sections -fdata-sections -fomit-frame-pointer
CC_FLAGS += -MMD -MP
CC_SYMBOLS = -DTARGET_LPC4088 -DTARGET_M4 -DTARGET_CORTEX_M -DTARGET_NXP -DTARGET_LPC408X -DTOOLCHAIN_GCC_ARM -DTOOLCHAIN_GCC -D__CORTEX_M4 -DARM_MATH_CM4 -D__FPU_PRESENT=1 -DMBED_BUILD_TIMESTAMP=1429428454.91 -D__MBED__=1 

LD_FLAGS = $(CPU) -Wl,--gc-sections --specs=nano.specs -u _printf_float -u _scanf_float -Wl,--wrap,main
LD_FLAGS += -Wl,-Map=$(PROJECT).map,--cref
LD_SYS_LIBS = -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys

ifeq ($(DEBUG), 1)
  CC_FLAGS += -DDEBUG -O0
else
  CC_FLAGS += -DNDEBUG -Os
endif

all: $(PROJECT).bin $(PROJECT).hex 

clean:
    rm -f $(PROJECT).bin $(PROJECT).elf $(PROJECT).hex $(PROJECT).map $(PROJECT).lst $(OBJECTS) $(DEPS)

.s.o:
    $(AS) $(CPU) -o $@ $<

.c.o:
    $(CC)  $(CC_FLAGS) $(CC_SYMBOLS) -std=gnu99   $(INCLUDE_PATHS) -o $@ $<

.cpp.o:
    $(CPP) $(CC_FLAGS) $(CC_SYMBOLS) -std=gnu++98 -fno-rtti $(INCLUDE_PATHS) -o $@ $<


$(PROJECT).elf: $(OBJECTS) $(SYS_OBJECTS)
    $(LD) $(LD_FLAGS) -T$(LINKER_SCRIPT) $(LIBRARY_PATHS) -o $@ $^ $(LIBRARIES) $(LD_SYS_LIBS) $(LIBRARIES) $(LD_SYS_LIBS)
    @echo ""
    @echo "*****"
    @echo "***** You must modify vector checksum value in *.bin and *.hex files."
    @echo "*****"
    @echo ""
    $(SIZE) $@

$(PROJECT).bin: $(PROJECT).elf
    @$(OBJCOPY) -O binary $< $@

$(PROJECT).hex: $(PROJECT).elf
    @$(OBJCOPY) -O ihex $< $@

$(PROJECT).lst: $(PROJECT).elf
    @$(OBJDUMP) -Sdh $< > $@

lst: $(PROJECT).lst

size:
    $(SIZE) $(PROJECT).elf

DEPS = $(OBJECTS:.o=.d) $(SYS_OBJECTS:.o=.d)
-include $(DEPS)

你的软件包中必须有启动源代码。同时,查看CMSIS文档,其中列出了常见头文件core_cm4、coreFunc等的“标准”函数。这些头文件也应该在MCU制造商(例如:NXP)提供的库中可用。中断处理程序应该在MCU头文件中声明,这些头文件包括所有外设寄存器。它们也应该包含在提供的软件包中。你可能需要从完整的工具包中提取它们;现在的公司似乎不可能只将基本内容打包成一个zip文件而没有任何臃肿。 - too honest for this site
你从哪里得到这个输出的?这绝对不是gcc自己输出的。也许是makefile、脚本或某些不为人知的专有工具? - too honest for this site
1
@Olaf 看起来这是mbed的makefile的一部分:https://github.com/mbedmicro/mbed/blob/master/workspace_tools/export/gcc_arm_lpc11u35_401.tmpl - user149341
所以你应该分析一下。我猜这是固件的某种错误检查,如果是这样的话,应该有一种方法可以禁用它,因为在开发过程中这是相当不常见的。通常,这个函数只用于生产代码(除非你必须测试这个函数)。嗯...自动生成的,所以看看mbed的东西(不知道这个,我讨厌makefiles)。但看起来只是一个提醒,没有实际功能(至少在makefile中是这样)。你仍然可以检查启动源码(如果真的没有源代码,请在工具链中删除!-顺便说一句:不是gcc的错)。 - too honest for this site
我在STM32F上使用arm-none-eabi,所以我认为这真的不是gcc&co。 - too honest for this site
2个回答

4
好的,我花了一些时间。在这个zip中检查一个项目。有各种启动代码。顺便说一句:编写自己的启动代码并不那么复杂。大多数时候,人们必须为“真实”项目进行修改。
这个zip来自于这个页面。第二个zip可能包括链接器文件,但可能不适用于gcc(“keil”可能是一个很好的开始)。但你已经有一个可以开始的。
我刚才看了periph_blinky。请注意,启动程序始终必须与链接脚本相对应,因为有一些特殊部分。阅读方面,我建议查看binutils文档和,当然,gcc文档。
还应该有一些库,如我在评论中所述的CMSIS函数和具有MCU定义的头文件。CMSIS的东西也可以从ARM获取,但可能需要一些调整以适应实际实现(MPU区域数量等)。
哦,我建议不要使用供应商库进行外设访问。它们可能被称为“标准”,但实际上它们并不是,但大多数时候包含一堆像运行时初始化(使用单独的写入到每个成员!)这样的废物结构,而这些结构永远不会改变。不确定NXP是否如此,但是例如STM提供了我见过的最糟糕的“std”库之一。

请检查我在问题(第一段)中提到的存储库,您会发现我从不使用“标准”库。我通过阅读PDF用户指南编写程序。我同意,最终我将不得不编写自己的启动脚本,这正是我担心的,因为这需要很长时间,而且我从未在ARM汇编中编程过...但我确实理解一些操作码,因为我曾经不得不解释LPC3143启动代码,并且我知道它与链接器脚本合作。我讨厌启动代码没有作为源代码提供的事实!CMSIS libs?什么鬼? - 71GA
1
Google是你的朋友!(至少对于这个问题是这样)它为Cortex-CPU提供了一些标准头文件(和命名方案),特别是Cortex-M。只需查看这些zip文件! Cortex-M实际上不是AVR,是的,你需要学习更多关于裸机编程的知识,而不仅仅是如何调用给定的Makefile。所以请不要让我感到困惑;-} - too honest for this site
我查看了压缩文件,里面有一个 .c 启动文件,但我无法完全理解。如果在汇编中尚未建立堆栈,这个 .c 代码如何工作? - 71GA
1
你真的应该多了解一下ARM,例如ABI文档。此外,查看Cortex-M的复位行为。它实际上从向量0设置SP和从1设置PC。请查看Cortex-M4 TRM。所有文档都可以从ARM免费获取。很抱歉,我无法在这里提供在线培训课程;这远远超出了本论坛的范围。然而,我可以告诉你,要熟悉Cortex-M需要比两周更长的时间。几年前我也走过同样的路。不容易,但如果你在这个领域工作(或想要工作),那么值得努力。 - too honest for this site
我知道这很难,但我想知道如何做,因为这是微控制器领域真正开启的方式。如果我使用HAL,这永远不会发生...好吧,至少我知道如何通过阅读PDF编写程序——大多数人甚至无法做到这一点。谢谢。现在我至少知道我的下一步应该是什么了。最令人沮丧的是当你走错方向并在多年的工作后才意识到这一点... - 71GA
显示剩余3条评论

2
快速浏览问题和答案。首先,为什么您认为需要中断来处理串口?我从未遇到过必须使用中断的情况,也许您有所需的用例,但是必须吗?
我有许多示例,全部都是裸机编程,没有使用 HAL 或标准库等。在 GitHub 上搜索 Thumbulator,然后从那里漫步,就可以看到一些示例。我很少使用中断,但可能会在某个地方使用中断作为示例。
正如评论中提到的,ARM 文档以及尝试后,您会发现对于 Cortex-M,堆栈指针可以根据向量表中的第一个条目由硬件设置,并且从那里开始,您无需为中断或异常干扰它。这不是全尺寸 ARM 的工作方式,因为它具有许多必须设置的堆栈。
Cortex-M 可以使用 C 函数地址填充向量表,如果您的编译器符合 (E)ABI,则可以使用 gcc 进行编译。可能需要一些汇编语言,但不像其他地方那样需要太多。
ARM 制造核心而不是芯片,因此 ARM 文档只能帮助您了解核心的边缘,其余部分取决于芯片供应商,例如如何启用和清除中断。

2
中断是实现并发的最简单方式,特别是当您有多个具有不同优先级的任务时。如果通过轮询循环来实现,这将是一种痛苦的方式。此外,为什么要让CPU忙于轮询所有可能的事件源,而不是让硬件完成呢?如果您需要对某些事件进行快速反应,则没有中断的方法,因为这些中断可以打断当前较低优先级的任务。避免中断就像上世纪60年代一样过时了。 - too honest for this site
1
避免中断是一种非常重要的实践。如果你想说做系统工程已经过时了,那也没关系,因为现在往往不会这样做,这就是为什么许多现代产品容易被黑客攻击、不可靠并需要频繁的固件升级的原因。没有人费心去做工程。中断与否只是系统工程中众多因素之一。如果你试图教授某些充满各种失败(裸机)的东西,你不会从中断开始,而是在他们有信心后再介绍中断。 - old_timer
1
核心已经由 TO 命名,因此没有办法绕过去。虽然有一种选择不包括 NVIC 和其他核心外设,但 (几乎) 每个 Cortex-M4 衍生产品都使用 ARM 提供的一个 (以及 MPU 和 SysTick)。这对于 NXP 尤其如此。是的,ARM 有一个标准。关于闪存编程:是的,这是供应商特定的,当然你需要更多的信息而不仅仅是 ARM 的信息。但这不是问题所在。 - too honest for this site
1
你可以在不影响周围位的情况下快速更改单个位。我从未说过擦除,你也没有提到过更改字节和单词。但这并不重要,因为你仍然不理解我所说的不是针对向量表进行编译后修改的。如果你有这样的需求,那么实现起来也很简单,但那是另一个讨论话题。 - old_timer
1
我以为你想要进入中断。对于这个MCU,我帮不上太多忙,但通常,在发送每个字节之前,您只需轮询状态寄存器以获取tx缓冲区为空或rx缓冲区已满的状态,然后在从接收缓冲区读取每个字节之前进行读取。这对于几乎所有UART都很常见。只需研究参考手册即可。但是,这个问题对于这个论坛来说太宽泛了;难道没有像STM一样的论坛吗? - too honest for this site
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接