3

I am dealing with a new microcontroller LPC4088 from NXP. I needed 2 weeks to study and write a working examples for peripherals: IOCONFIG, GPIO, TIMERS, PWM and ADC. Please take a look at my repositories here. This is how you will get a feeling for how I work and what my skill level is.

Until now I could simply disable interrupts and work without them. Now I want to deal with UART peripheral device which needs interrupts. I have never programmed interupts but know something about ARM interrupts. Sadly just in theory. Currently I am studying these two documents:

It became clear to me that I need to study ARM Cortex-M4 microprocessor besides the LPC4088 microcontroller which I got the hang of somehow. I know that I should put ARM exception vectors at the beginning of the program - usually in the startup code. But I don't know how to do this because what I got with the microcontroller is already compiled startup code (object file) which presumably defines exception vectors, reset handler which sets stacks for C and then jumps to function main() in C source code written by user.

After compilation of my programs using GCC ARM compiler I allways get this prompt, which must allso be the clue which I don't understand because of my inexperience with ARM mcpu's directly:

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

I was thinking of reverse ingeneering the startup code using the Segger Jlink and fixing the exception vectors there, but there must be any other way besides writing my own open source startup code... So do you have any suggestions or examples which would be even better for me.


ADD: I really looked hard and got no source code for the startup code. This is what I got:

enter image description here

The only way to somehow manipulate vectors therefore must be hidden inside the linker script, which is the only part that is still a source code and it looks like this:

/* 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
}

There is allso a makefile which looks like this and is contributing the prompt that I get at the end of every compilation:

# 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)
71GA
  • 1,132
  • 6
  • 36
  • 69
  • There must be a source code for the startup in you package. Also, have a look at CMSIS documents which list "standard" functions for the common headers core_cm4, coreFunc, etc. These header should be also available in the libraries supplied by the MCU manufacturer (here: NXP). The interrupt handlers should be declared in the MCU header file(s) which include all peripheral registers. Should also be included in the supplied package. You might have to extract them from a complete toolkit; seems impossible for the companioes these days to pack just the basi stuff into a zip without all the bloat. – too honest for this site May 02 '15 at 17:54
  • Where do yu get this output from? That is definitively nothing gcc outputs by itself. Maybe makefile, script or so obscure proprietary tool? – too honest for this site May 02 '15 at 17:58
  • 1
    @Olaf It appears to be part of the mbed makefile: https://github.com/mbedmicro/mbed/blob/master/workspace_tools/export/gcc_arm_lpc11u35_401.tmpl –  May 02 '15 at 18:55
  • So you should analyze this. I suppose it is some kind of error-checking for the firmware and - if so - there should be a way to disable it, as it is pretty uncommon during development. Normally, this function is used only for production code (unless you have to test this function). Hmm... uatomatically generated, so look into the mbed stuff (don't know this and I hate makefiles). But looks like just a reminder without actual functionality (at least in makefile). You still might check the startup-sources (bin the toolchain, if there is really no sourcecode! - btw: not gcc's fault) – too honest for this site May 02 '15 at 19:06
  • ... I use arm-none-eabi on a STM32F, so I would say it's really not gcc&co. – too honest for this site May 02 '15 at 19:13

2 Answers2

4

Ok, took me some minutes. Checkout one of the projects in this zip. There are various startup codes. Btw.: It is not so complicated to write your own. most times one has to modify it anyway for "real" projects.

The zip comes from this page. The second zip might include liker files, but possibly not for gcc (the "keil" might be a good start though). But you already have one to start with.

I just had alook at periph_blinky. Note that the startup always has to correspond with the linker script, as there are some special sections. For reading, I recommend to check out the binutils docs and, of course, the gcc docs.

There should also be some libs as I stated in a comment with CMSIS functions and the header with MCU definitions. The CMSIS stuff can also be fetched from ARM, but might require some fiddling to tailor to the actual implementation (number of MPU regions, etc.).

Oh, and I would recommend not to use the vendor libraries for peripheral access. They might be called "standard", but they are actually not, but most times include a mass of bloat like run-time initialization (using separate writes to each member!) of structs which never change. Not sure about NXP, but STM, for instance provides one of the crappiest "std"library I've ever seen.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
  • Please check my repository mentioned in my question (1st paragraph) and you will see that I never use "standard" libraries. I do write programs by reading PDF user guides. I agree, that in the end I will have to write my own startup script and this is what I was afraid of, because it will took a lot of time and I have never programmed in ARM assembly... But i do understand some of the opcodes, because I had to explain to myself LPC3143 startup code in the past and I know that it cooperates with a linker script. I hate fact that startup code isn't supplied as source code! **CMSIS libs?** WTF? – 71GA May 03 '15 at 17:56
  • The part about the "standard" libs was just a general hint. Sadly, most examples in the net use this stuff and make things much more complicated than direct hardware access in a driver. Jut to make clear: I am a strong advocate of hardware abstraction by drivers. But this HAL-stuf actually does not provide any efective abstraction at all, as they are still quite specific. The startup in the examples given is written in C and does not actually require assembler. I wrote one myself in assembler, however and can tell you it's not that complicated, but, yes, it takes some time to get it right. – too honest for this site May 03 '15 at 18:28
  • 1
    Google is your friend! (well.. for this at least) That provides some standard headers (and naming schemes) for Cortex-CPUs, expecially the Cortex-M. Just check out the zips! Cortex-M _is_ actually not AVR and yes, you have to learn more than how to invoke a given makefile for bare-metal programming. So please don't WTF me;-} – too honest for this site May 03 '15 at 18:32
  • I want to stay away from that **HAL crap**... It looks like I will have to write my own startup code and linker script which is hard as there aren't any good youtube tutorials. But I do have the example from the LPC3143 which I know how it works. But that was ARM9, and this is Cortex-M4. Maybe I should google "startup code for Cortex-M4", then modify it and use it for my MCU. What I would appreciate now would be a good and detailed book with a lot of examples on how to write startup codes and linker scripts for Cortex-M4. And I'll just use modified makefile already provided... – 71GA May 03 '15 at 18:45
  • I do not understand why you did not check the links I provided. Did you read my answer actually? AGAIN: the zips in the links _do_ _include_ _startup_ _source_ _code_ files_! Regarding the HAL: That's exaclty what I said. And, yes, you have to larn yourself. If you don't think it is worth the effort, you better get an arduino or even try a RasPi with Python. To me it sounds a bit as if you are complaining that no one will give you a complete project or a step-by-step tutorial. But that's not how engineering works. – too honest for this site May 03 '15 at 19:22
  • I take a look into the zip file and there is a .c startup file I can't quite understand. How can this .c code work if stack isn't yet established in assembler? – 71GA May 03 '15 at 22:29
  • 1
    You really should read more about ARM, e.g. the ABI documents. Also, check out the reset behaviour of the Cortex-M. It actually _does_ set the SP from the vector 0 and the PC from 1. Check the Cortex-M4 TRM. All documents are availbale for free from ARM. Sorry, but I cannot provide an online training course here; this is far beyond the scope of this forum. However, I can tell you that it takes significantly longer than two weeks to become familar with the Cortex-M. I followed the same way some years ago. Not easy, but imo worth the effort if you are working in this area (or want to). – too honest for this site May 03 '15 at 23:34
  • I know it will be hard, but I want to know how to do this because this is how microcontroller field will really open up for me. This will never happen if I use HAL... Well at least I know how to write programs by reading PDF's - most of the people can't even do that. Thank you. Now at least I know what my next step should be. The most frustrating is when you go into a wrong direction and you realize it after years of work... – 71GA May 04 '15 at 09:23
2

Quickly glancing at the question and answer. first off why is it you think you need interrupts for the uart? I have so far never met such a beast that is required, perhaps you have a desired use case, but required?

I have many examples, all bare metal, no hal or standard libraries, etc. search for thumbulator at github and then wander sideways from there to see a few. I have little use for interrupts but likely did some somewhere for sake of an example.

As mentioned in comments, the arm docs, and just trying it out you will see that for the cortex-m the stack pointer can be set by hardware based on the first entry in the vector table, and from there you dont have to mess with it for interrupts or exceptions. this is not how a full sized arm works with its many stacks that all have to be setup.

The cortex-m is such that you can fill the vector table with addresses to C functions if your compiler complies with the (E)ABI. With gcc it will. there may be some assembly required but not as much as you would deal with elsewhere.

arm makes cores not chips, so the arm docs only get you to the edge of the core, the rest is from the chip vendor and can vary widely, how to enable and clear the interrupts for example.

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • 2
    Interrupts are the most simple way to have concurrency, expecially if you have multiple tasks with different priorities. This would be a pain in the a?? if implemented by a polling-loop. Also, why would one have the CPU busy-poll all possible sources of events, instead of letting the hardware do that? And, if you need a short reaction time for some events, there is no way without interrupts, as these can -- well -- interrupt the current lower priority task. Avoiding interrupts is so 1960ies – too honest for this site May 04 '15 at 12:54
  • Despite what you wrote, expecially for the Cortex-M, there are more parts of the MCU provided by ARM. This is expecially true for the interrupt controller, which is highly integrated into the CPU. This is different from other CPUs. To manipulate the interrupts, ARM _does_ provide a standard header to do this. You also might want to read about CMSIS and the Cortex-M4 documentation. Regarding writes to the vector table: as most ppl here, you forget this is located in Flash, so it cannot be written without aditional complications for typical MCUs. Most times, it is not necessary, anyways. – too honest for this site May 04 '15 at 13:01
  • depends on the core as to what is in it and yes you can certainly buy other components, but ultimately you end up having to deal with arm and the chip vendor for programming information. The chip vendor does not have to enable all of the features, the trm generally describes what is strapped or not and you have the source code so if you need to rip something out (or fix something) there you go. – old_timer May 04 '15 at 13:12
  • Flash or ram depends on the chip implementation. the cortex-m leans heavily to having that lower portion flash, yes. I did not say you just write to flash whenever you want to change a vector, you as the programmer determine the contents of the vector table at compile which if you choose can be written to flash along with the application. – old_timer May 04 '15 at 13:13
  • 1
    avoiding interrupts is very much a practice. now if you want to say doing your system engineering is so 1960 that is fine, because so often it is not done now which is why so many modern products are so easy to hack, so unreliable and require near constant firmware upgrades. Nobody bothered to do the engineering. interrupt vs not interrupt is only but one of the many factors that goes into system engineering. If you are trying to teach someone something that is full of all kinds of failure (baremetal), you dont start with interrupts, you end with interrupts after they have a confidence level – old_timer May 04 '15 at 13:16
  • 1
    Sorry, but that is simply wrong. Crappy firmware has noting to do with interrupts, etc. It is mostly unexperienced programmers, complex and only partially documented tools (which includes hardware documentation and software-packages), and lack of real-world testing Proper planning in advance also adds to this (incomplete) list. You confuse cause with effect. @71GA asked about interrupts; I did not push him into that direction, just tried to help him with that. However, I stand to my post. Avoiding interrupts at the expense of a more complicated system _is_ simply ... well ... unfavorable. – too honest for this site May 04 '15 at 13:35
  • 1
    The core has been named by the TO, so there is no way around. And while there is an option not to include the NVIC and other core-peripherals, (almost) every cortex-m4 derivate uses the one provided by ARM (as well as the MPU and SysTick). This is expecially true for the NXP. And yes, there is a standard for this by ARM. Regarding flash programming: yes, this is vendor-specific and of course you need more than the ARM information. But that was not the problem. – too honest for this site May 04 '15 at 13:46
  • Oh, and: you cannot change a single Word in Flash without affecting others (that would be EEPROM), so your position on how to change a single vector is just pointless and really only applies to RAM-based vector table. And, yes, this is non-standard for Cortex-M MCUs. Don't try to whitewash it;-) – too honest for this site May 04 '15 at 13:47
  • you really do have a problem reading and understanding this stuff...yes you can absolutely change a single word in a flash, you need to read more on how a flash works as well. or rephrase your statement to be correct. – old_timer May 04 '15 at 15:11
  • Thank you for your analysis on my reading skills, but you might work on your skills to express yourself as well. You should not confuse reputation points here with real-life knowledge and reputation, however. I have worked now for many years in this field and - belive it or not - appear to know Flash much more than you do. In general, Flash cannot be written by a single word. However, some Flash devices (which includes integrated Flash) can be written by a single word, **if** you only change bits from 1 to 0. Otherwise you have to erase the Flash first (changeing all bits to 1). ... – too honest for this site May 04 '15 at 17:33
  • ... _This_ erasure cannot be performed on a single word, but a whole sector of some KiB (yes, there were Flashes which actually did allow this, but that was ca. 15 years ago and only one or two MCU had it, as the cells were much too large to be cost-effective). So, you would have to read the whole sector (also called block sometimes) into RAM, change the affected word in RAM, erase the sector in Flash and write it back to Flash. Also, erasing and writing takes some ms to s to complete. Most modern MCUs stall accesses to the whole Flash during these operations or disallow accessing the Flash. – too honest for this site May 04 '15 at 17:36
  • 1
    you can change a single bit in a flash without affecting the ones around it..I never said erase, nor did you you talked about changing bytes and words. doesnt matter since you still dont understand that I was not talking about post compile time modifications to the vector table. if you have a desire to that then that is also trivial to implement, but that is another discussion. – old_timer May 04 '15 at 17:41
  • You can **not** write a single bit from 0 to 1! **But** I really seem to have to retreat as I just re-read your post and apparently had overread the "not" (how embarrrasing) - so, **sorry** **for** **this**. However, you are still wrong about the vendor-part for the Cortex-M. Also, the TRM is a generic document and actually not derivate-specific (but for the Family, e.g. Cortex-M4, Cortex-M7, etc.). As I got from all this, I wonder if you have ever actually worked with the Cortex-M CPUs deeper or at least read some reference manuals (user guides or similar - not just datasheets). – too honest for this site May 04 '15 at 17:48
  • Oh, and rearding my system engineering: I am proud I learned all this stuff (hardware, software, etc.) bottom up, but do not ignore new techniques. As I read once: "there two kinds of ignorants: those, who say 'this is old, so it is good'. And the others, who says 'this is new, it is better'". – too honest for this site May 04 '15 at 18:33
  • It looks like I will have to first try and program UART withouth interrupts, like @dwelch sugested - you read very carefuly, because you noticed that I am not to sure about how to deal with UART. Do you have any suggestions on how to deal with UART withouth interrupts? – 71GA May 05 '15 at 16:23
  • 1
    I thought you wanted to get into interrupts. For this MCU I cannot help much, but normally, you would just poll a status register before sending each byte for tx buffer empty or for rx buffer full before reading each byte from the receive buffer. That is quite common for almost all UARTs. Just study the ref-man. But this question is too broad for this forum; is there no forum like STM has? – too honest for this site May 06 '15 at 17:16